Release 0.8.0 with replay-safe prioritized transport

This commit is contained in:
sergeych committed 2026-10-21 16:35:20 +04:00
1 parent 237d404bcd
commit 009d62a155
31 files changed
+1188 -130

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {ReplayWindow} from '../dist/replay.js';
test('circular replay bitmap agrees with a reference across wraparound and jumps', () => {
const window = new ReplayWindow(), seen = new Set();
let high = -1n, seed = 17;
const inputs = [4096n, 1n, 2n, 0n, 1n, 8191n, 4096n, 8192n];
for (let i=0;i<15000;i++) { seed = (Math.imul(seed,1664525)+1013904223)>>>0; inputs.push(BigInt(seed % 30000)); }
inputs.push((1n<<64n)-1n, (1n<<64n)-4096n, (1n<<64n)-4097n);
for (const n of inputs) {
const reject = seen.has(n) || (n <= high && high-n >= 4096n);
assert.equal(window.containsOrStale(n), reject);
if (!reject) { window.record(n); seen.add(n); if(n>high) high=n; assert.equal(window.containsOrStale(n),true); }
}
window.clear(); assert.equal(window.containsOrStale(0n),false);
});
+40 -4
View File
@@ -25,6 +25,7 @@ async function peer(mode = 'normal') {
return bytes.encode(encryptedPackageCodec.encode({nonce:n, encryptedMessage:session.encrypt(n,value)}));
};
async function receive(block) {
if (block.kind !== 'call') return;
if (block.name === 'L0Request') {
const request = handshakeCodec.decode(block.packedArgs);
session = exchange.serverSession(request.publicKey);
@@ -47,6 +48,10 @@ async function peer(mode = 'normal') {
const call = commandCallCodec.decode(message.payload);
let packedResult = encrypted(protectedMessageCodec.encode({kind:'response',correlation:message.correlation,priority:message.priority,payload:blockCodec.encode({kind:'response',forId:0,packedResult:call.serializedArgs})}));
if (mode === 'damage') packedResult[packedResult.length - 1] ^= 1;
if (mode === 'replay-then-valid') {
if (previous) emit({kind:'response',forId:block.id,packedResult:previous});
previous = packedResult;
}
if (mode === 'replay') { const saved = packedResult; packedResult = previous ?? saved; previous = saved; }
if (mode === 'swap') {
calls.push({id:block.id, packedResult});
@@ -58,8 +63,10 @@ async function peer(mode = 'normal') {
}
}
return {key, socket, injectCall({id=90, signedId=90n, priority=0, signedPriority=0, kind='call'}={}) {
emit({kind:'call', id, priority, name:'L0Call', packedArgs:encrypted(protectedMessageCodec.encode({kind,correlation:signedId,priority:signedPriority,payload:commandCallCodec.encode({name:'callback',serializedArgs:string.encode('payload')})}))});
}, connect: (pin, extra={}) => Client.connect('ws://test.invalid/', {expectedServerKey:pin ?? key.publicKey,session:null,
const packet = {kind:'call', id, priority, name:'L0Call', packedArgs:encrypted(protectedMessageCodec.encode({kind,correlation:signedId,priority:signedPriority,payload:commandCallCodec.encode({name:'callback',serializedArgs:string.encode('payload')})}))};
emit(packet);
return () => emit(packet);
}, skipNonces(count) { nonce += BigInt(count); }, connect: (pin, extra={}) => Client.connect('ws://test.invalid/', {expectedServerKey:pin ?? key.publicKey,session:null,
heartbeatMs:0,timeoutMs:1000,socketFactory:() => socket,...extra}), cleanup() {socket.close();session?.destroy();key.destroy();exchange.destroy(); if(peerError) throw peerError;} };
}
const echo = command('echo',string,string);
@@ -86,11 +93,12 @@ test('damaged encrypted response closes connection', async () => {
await assert.rejects(client.call(echo,'again'),e=>e.code==='closed');
} finally {p.cleanup();}
});
test('replayed encrypted response closes connection', async () => {
test('replayed encrypted response is discarded without closing connection', async () => {
const p=await peer('replay');
try {
const client=await p.connect(); assert.equal(await client.call(echo,'first'),'first');
await assert.rejects(client.call(echo,'second'),e=>e.code==='protocol'); await client.closed;
await assert.rejects(client.call(echo,'second',{timeoutMs:20}),e=>e.code==='timeout');
assert.equal(p.socket.readyState,1);
} finally {p.cleanup();}
});
test('outer forId substitution fails closed without delivering swapped responses', async () => {
@@ -130,3 +138,31 @@ for (const [name, metadata] of [
} finally {p.cleanup();}
});
}
test('a replayed response does not consume the waiter for the valid reply', async () => {
const p = await peer('replay-then-valid');
try {
const client = await p.connect();
assert.equal(await client.call(echo, 'first'), 'first');
assert.equal(await client.call(echo, 'second'), 'second');
assert.equal(p.socket.readyState, 1);
} finally { p.cleanup(); }
});
test('duplicate and stale incoming pushes are discarded without closing', async () => {
const p = await peer();
let calls = 0;
const local = new LocalInterface().on(command('callback', string, string), () => { calls++; return 'ok'; });
try {
const client = await p.connect(undefined, {local});
const replay = p.injectCall({id:0, signedId:0n, kind:'push'});
replay();
assert.equal(calls, 1);
p.skipNonces(4096);
p.injectCall({id:0, signedId:0n, kind:'push'});
replay();
assert.equal(calls, 2);
assert.equal(await client.call(echo, 'still alive'), 'still alive');
} finally { p.cleanup(); }
});