95 lines
2.9 KiB
TypeScript
95 lines
2.9 KiB
TypeScript
/**
|
|
* 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);
|
|
});
|
|
});
|