Initial Commit
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* DMP-08 — Advertisement bitset length invariant.
|
||||
*
|
||||
* In `ChunkAdvertisePayload`, the bit-length of `presence` MUST equal
|
||||
* `totalChunks`; padding bits in the final byte MUST be zero. The codec
|
||||
* rejects mismatched bitset wires with either
|
||||
* `DMP.BITSET_LENGTH_MISMATCH` or `DMP.BITSET_PADDING_NONZERO`.
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import {
|
||||
BITSET_MAGIC,
|
||||
chunkIdsToRawBitset,
|
||||
DMP_CODES,
|
||||
DmpError,
|
||||
decodeAdvertisePayload,
|
||||
encodeAdvertisePayload,
|
||||
encodeFramedBitset,
|
||||
} from "../src/index.js";
|
||||
|
||||
const FILE_ID = "b".repeat(64);
|
||||
|
||||
function makeAdvertise(presence: Uint8Array, totalChunks: number) {
|
||||
return encodeAdvertisePayload({
|
||||
fileId: FILE_ID,
|
||||
presence,
|
||||
totalChunks,
|
||||
generation: 1,
|
||||
timestamp: 0,
|
||||
});
|
||||
}
|
||||
|
||||
function makeFramedBitset(ids: number[], bitLength: number) {
|
||||
return encodeFramedBitset(chunkIdsToRawBitset(ids, bitLength), bitLength);
|
||||
}
|
||||
|
||||
describe("DMP-08 — bitset length invariant", () => {
|
||||
it("advertisement with presence.length ≠ totalChunks rejected", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const presence = makeFramedBitset([0, 5], 16);
|
||||
try {
|
||||
makeAdvertise(presence, 32);
|
||||
throw new Error("expected mismatch rejection");
|
||||
} catch (err) {
|
||||
expect(err).toBeInstanceOf(DmpError);
|
||||
expect((err as DmpError).code).toBe(
|
||||
DMP_CODES.BITSET_LENGTH_MISMATCH
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("valid advertisement (bitLength == totalChunks) round-trips", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const presence = makeFramedBitset([0, 5, 7], 8);
|
||||
const wire = makeAdvertise(presence, 8);
|
||||
const decoded = decodeAdvertisePayload(wire);
|
||||
expect(decoded.totalChunks).toBe(8);
|
||||
expect(decoded.presence).toEqual(presence);
|
||||
});
|
||||
|
||||
it("non-zero padding bits rejected", { timeout: 5000 }, () => {
|
||||
// Hand-build a framed bitset with bitLength=10 (so high bits 10..15
|
||||
// must be zero) but flip bit 14 to violate padding zeroing.
|
||||
const raw = new Uint8Array([0x01, 0xff]);
|
||||
// Build the wire envelope by hand.
|
||||
const wire = new Uint8Array(12 + raw.length);
|
||||
wire.set(BITSET_MAGIC, 0);
|
||||
const view = new DataView(
|
||||
wire.buffer,
|
||||
wire.byteOffset,
|
||||
wire.byteLength
|
||||
);
|
||||
view.setUint32(4, 10, false);
|
||||
view.setUint32(8, raw.length, false);
|
||||
wire.set(raw, 12);
|
||||
try {
|
||||
makeAdvertise(wire, 10);
|
||||
throw new Error("expected padding-nonzero rejection");
|
||||
} catch (err) {
|
||||
expect(err).toBeInstanceOf(DmpError);
|
||||
expect((err as DmpError).code).toBe(
|
||||
DMP_CODES.BITSET_PADDING_NONZERO
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user