Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,180 @@
/**
* 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);
}
});
});