/** * DTP-06 — replay protection via per-client nonce LRU. */ import { describe, expect, it } from "@suaveplan/testing/runner"; import { type DriveTemplateError, DTP_CODES, TEMPLATE_NONCE_LRU_DEFAULT_CAPACITY, TemplateNonceLru, validateTemplate, } from "../src/index.js"; import { makeKeys, makeNonce, signed } from "./_helpers.js"; describe("DTP-06 — replay protection", () => { it("hydrate with same (driveId, nonce) twice rejects on second call", { timeout: 5000, }, () => { const keys = makeKeys(); const lru = new TemplateNonceLru(); const tpl = signed( { issuer: keys.publicHex, nonce: makeNonce(0x42), }, keys ); const ctx = { clock: () => Date.now(), nonceLru: lru }; validateTemplate(tpl, ctx); try { validateTemplate(tpl, ctx); throw new Error("expected REPLAY rejection"); } catch (err) { expect((err as DriveTemplateError).code).toBe( DTP_CODES.TEMPLATE_REPLAY ); } }); it("LRU capacity is the documented default", { timeout: 5000 }, () => { expect(TEMPLATE_NONCE_LRU_DEFAULT_CAPACITY).toBe(1024); }); it("LRU evicts oldest entry past capacity", { timeout: 5000 }, () => { const lru = new TemplateNonceLru(2); const a = makeNonce(1); const b = makeNonce(2); const c = makeNonce(3); lru.remember("d", a); lru.remember("d", b); lru.remember("d", c); expect(lru.has("d", a)).toBe(false); expect(lru.has("d", b)).toBe(true); expect(lru.has("d", c)).toBe(true); expect(lru.size).toBe(2); }); it("LRU reinserts on duplicate to refresh recency", { timeout: 5000, }, () => { const lru = new TemplateNonceLru(2); const a = makeNonce(1); const b = makeNonce(2); lru.remember("d", a); lru.remember("d", b); lru.remember("d", a); // refresh const c = makeNonce(3); lru.remember("d", c); // evicts b, not a expect(lru.has("d", a)).toBe(true); expect(lru.has("d", b)).toBe(false); expect(lru.has("d", c)).toBe(true); }); it("LRU rejects non-positive capacity", { timeout: 5000 }, () => { expect(() => new TemplateNonceLru(0)).toThrow(); expect(() => new TemplateNonceLru(-1)).toThrow(); expect(() => new TemplateNonceLru(1.5)).toThrow(); }); it("clear() empties the LRU", { timeout: 5000 }, () => { const lru = new TemplateNonceLru(8); lru.remember("d", makeNonce(1)); expect(lru.size).toBe(1); lru.clear(); expect(lru.size).toBe(0); }); it("capacity getter exposes the configured cap", { timeout: 5000, }, () => { const lru = new TemplateNonceLru(99); expect(lru.capacity).toBe(99); }); });