79 lines
2.7 KiB
TypeScript
79 lines
2.7 KiB
TypeScript
/**
|
|
* DMP-11 — Response presence piggyback.
|
|
*
|
|
* `ChunkResponsePayload` includes `presence` (the responder's full
|
|
* inventory bitset) and `generation`; receivers update their peer
|
|
* index from this payload without requiring a separate `cd:advertise`.
|
|
*/
|
|
|
|
import { describe, expect, it } from "@suaveplan/testing/runner";
|
|
import {
|
|
chunkIdsToRawBitset,
|
|
decodeFramedBitset,
|
|
decodeResponsePayload,
|
|
encodeFramedBitset,
|
|
encodeResponsePayload,
|
|
GenerationTracker,
|
|
rawBitsetToChunkIds,
|
|
} from "../src/index.js";
|
|
import type { ChunkResponsePayload } from "../src/types/protocol.types.js";
|
|
|
|
const FILE_ID = "c".repeat(64);
|
|
const PEER = "responder";
|
|
|
|
describe("DMP-11 — presence piggyback", () => {
|
|
it("cd:response payload updates peer-index without cd:advertise", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const inventory = [0, 5, 17, 99];
|
|
const presence = encodeFramedBitset(
|
|
chunkIdsToRawBitset(inventory, 100),
|
|
100
|
|
);
|
|
const payload: ChunkResponsePayload = {
|
|
fileId: FILE_ID,
|
|
chunks: [
|
|
{ chunkId: 0, bytes: new Uint8Array([0xa1]) },
|
|
{ chunkId: 5, bytes: new Uint8Array([0xa2]) },
|
|
],
|
|
presence,
|
|
generation: 11,
|
|
};
|
|
const wire = encodeResponsePayload(payload);
|
|
const decoded = decodeResponsePayload(wire);
|
|
expect(decoded.generation).toBe(11);
|
|
// Receiver decodes the piggybacked bitset and updates its
|
|
// peer-index for (responder, FILE_ID) — no cd:advertise needed.
|
|
const decodedPresence = decodeFramedBitset(decoded.presence);
|
|
const recoveredInventory = rawBitsetToChunkIds(
|
|
decodedPresence.raw,
|
|
decodedPresence.bitLength
|
|
);
|
|
expect(recoveredInventory).toEqual(inventory);
|
|
|
|
const tracker = new GenerationTracker();
|
|
const observation = tracker.observe(PEER, FILE_ID, decoded.generation);
|
|
expect(observation.accept).toBe(true);
|
|
expect(tracker.peek(PEER, FILE_ID)).toBe(11);
|
|
});
|
|
|
|
it("piggyback applies even on partial responses (chunks.length < requested)", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const presence = encodeFramedBitset(
|
|
chunkIdsToRawBitset([3, 7], 16),
|
|
16
|
|
);
|
|
const partial: ChunkResponsePayload = {
|
|
fileId: FILE_ID,
|
|
chunks: [{ chunkId: 3, bytes: new Uint8Array([0xc0]) }],
|
|
presence,
|
|
generation: 4,
|
|
};
|
|
const wire = encodeResponsePayload(partial);
|
|
const round = decodeResponsePayload(wire);
|
|
expect(round.chunks.length).toBe(1);
|
|
expect(round.presence).toEqual(presence);
|
|
});
|
|
});
|