/** * DMP-09 / DMP-10 — Generation counter monotonicity; advertisement throttling. * * `GenerationTracker` keeps the highest generation per `(peerId, fileId)` * and drops stale advertisements; it also accepts a one-cycle wrap to * tolerate a single anomaly past 2^31. `AdvertiseThrottle` limits * outgoing advertisements to one per `(neighbor, fileId)` per `windowMs`. */ import { describe, expect, it } from "@suaveplan/testing/runner"; import { ADVERTISE_THROTTLE_MS_DEFAULT, ADVERTISE_THROTTLE_MS_MAX, AdvertiseThrottle, DMP_CODES, DmpError, GENERATION_WRAP_THRESHOLD, GenerationTracker, } from "../src/index.js"; const PEER = "peer-A"; const FILE = "F".repeat(64); describe("DMP-09 — generation monotonicity", () => { it("first advertisement is accepted", { timeout: 5000 }, () => { const t = new GenerationTracker(); const r = t.observe(PEER, FILE, 7); expect(r.accept).toBe(true); expect(r.latest).toBe(7); }); it("higher generation is accepted", { timeout: 5000 }, () => { const t = new GenerationTracker(); t.observe(PEER, FILE, 7); const r = t.observe(PEER, FILE, 8); expect(r.accept).toBe(true); expect(r.latest).toBe(8); }); it("out-of-order advertisement is dropped", { timeout: 5000 }, () => { const t = new GenerationTracker(); t.observe(PEER, FILE, 10); const r = t.observe(PEER, FILE, 5); expect(r.accept).toBe(false); expect(r.reason).toBe("stale"); expect(t.peek(PEER, FILE)).toBe(10); }); it("duplicate generation is dropped", { timeout: 5000 }, () => { const t = new GenerationTracker(); t.observe(PEER, FILE, 4); const r = t.observe(PEER, FILE, 4); expect(r.accept).toBe(false); expect(r.reason).toBe("duplicate"); }); it("wrap detected past 2^31 delta", { timeout: 5000 }, () => { const t = new GenerationTracker(); const high = 0xff_ff_ff_ff; t.observe(PEER, FILE, high); const r = t.observe(PEER, FILE, 0); expect(r.accept).toBe(true); expect(r.latest).toBe(0); expect(GENERATION_WRAP_THRESHOLD).toBe(2 ** 31); }); it("negative or non-integer generation rejected", { timeout: 5000 }, () => { const t = new GenerationTracker(); expect(t.observe(PEER, FILE, -1).accept).toBe(false); expect(t.observe(PEER, FILE, 1.5).accept).toBe(false); }); it("dropPeer wipes that peer's entries", { timeout: 5000 }, () => { const t = new GenerationTracker(); t.observe(PEER, FILE, 5); t.observe("other", FILE, 9); t.dropPeer(PEER); expect(t.peek(PEER, FILE)).toBeUndefined(); expect(t.peek("other", FILE)).toBe(9); }); it("dropFile wipes the file's entries across peers", { timeout: 5000, }, () => { const t = new GenerationTracker(); t.observe(PEER, FILE, 5); t.observe(PEER, "other", 9); t.dropFile(FILE); expect(t.peek(PEER, FILE)).toBeUndefined(); expect(t.peek(PEER, "other")).toBe(9); }); }); describe("DMP-10 — advertisement throttle", () => { it("first call is allowed", { timeout: 5000 }, () => { const now = 0; const throttle = new AdvertiseThrottle({ clock: () => now, windowMs: ADVERTISE_THROTTLE_MS_DEFAULT, }); const r = throttle.allow("neighbor-1", FILE); expect(r.allow).toBe(true); expect(r.retryAfterMs).toBe(0); }); it("throttle coalesces multiple presence changes within 1s window", { timeout: 5000, }, () => { let now = 0; const throttle = new AdvertiseThrottle({ clock: () => now, windowMs: 1_000, }); // First emission allowed. expect(throttle.allow("n", FILE).allow).toBe(true); now = 200; // Within the window — suppressed and pending. const r2 = throttle.allow("n", FILE); expect(r2.allow).toBe(false); expect(r2.retryAfterMs).toBeGreaterThan(0); expect(throttle.hasPending("n", FILE)).toBe(true); now = 500; // Still in window. expect(throttle.allow("n", FILE).allow).toBe(false); now = 1_001; // Window has elapsed; allowed again. const r4 = throttle.allow("n", FILE); expect(r4.allow).toBe(true); expect(throttle.hasPending("n", FILE)).toBe(false); }); it("clearPending resets the pending bit", { timeout: 5000 }, () => { let now = 0; const t = new AdvertiseThrottle({ clock: () => now, windowMs: 1_000, }); t.allow("n", FILE); now = 100; t.allow("n", FILE); expect(t.hasPending("n", FILE)).toBe(true); t.clearPending("n", FILE); expect(t.hasPending("n", FILE)).toBe(false); }); it("dropNeighbor evicts only that neighbor's state", { timeout: 5000, }, () => { const t = new AdvertiseThrottle({ clock: () => 0, windowMs: 1_000, }); t.allow("a", FILE); t.allow("b", FILE); t.dropNeighbor("a"); expect(t.size).toBe(1); }); it("rejects windowMs ≤ 0 or > 60s with THROTTLE_EXCEEDED", { timeout: 5000, }, () => { try { new AdvertiseThrottle({ clock: () => 0, windowMs: 0 }); throw new Error("expected rejection"); } catch (err) { expect(err).toBeInstanceOf(DmpError); expect((err as DmpError).code).toBe(DMP_CODES.THROTTLE_EXCEEDED); } try { new AdvertiseThrottle({ clock: () => 0, windowMs: ADVERTISE_THROTTLE_MS_MAX + 1, }); throw new Error("expected rejection"); } catch (err) { expect(err).toBeInstanceOf(DmpError); expect((err as DmpError).code).toBe(DMP_CODES.THROTTLE_EXCEEDED); } }); });