CultLib/packages/cultcache-ts/test/payload-depth.test.ts
Meta Vens 908bbd93ff cultcache-ts tests: payload-depth store tests read the record through its catalog entry
Main's two payload-depth tests staged a record with no catalog entry and
asserted that the inspector and the single-file store infer its schema from
the payload's first element. C2a refuses a record whose schema id the catalog
lacks (merge-r1-precedence, exact binding), so the guess door is gone. The
inspector test keeps the bound check with a catalog entry that names the
schema; the store inference test goes with the door.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 04:31:43 +02:00

263 lines
15 KiB
TypeScript

import assert from "node:assert/strict";
import { mkdtemp, readFile, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "node:test";
import { encode } from "@msgpack/msgpack";
import { z } from "zod";
import * as root from "../src";
import { inspectCultCacheBytes } from "../src/cult-cache-inspector";
import {
CultCachePayloadDepthError,
MAX_PAYLOAD_DEPTH,
decodeDocumentPayload,
defineDocumentType,
documentPayloadBytes,
documentPayloadFromBytes,
encodeDocumentPayload,
} from "../src/document";
import * as codecs from "../src/codec";
import { checkPayloadDepth } from "../src/payload-depth";
const L = MAX_PAYLOAD_DEPTH;
const CANARY = "CANARY-payload-depth-91d3";
const cat = (...parts: Uint8Array[]) => Uint8Array.from(parts.flatMap((part) => [...part]));
const bytes = (...values: number[]) => Uint8Array.from(values);
const repeat = (unit: Uint8Array, times: number) => {
const out = new Uint8Array(unit.length * times);
for (let index = 0; index < times; index += 1) out.set(unit, index * unit.length);
return out;
};
// n containers nested in one another around a leaf; `open` is one container's header with one child (two for a map).
const chain = (open: Uint8Array, n: number, leaf: Uint8Array = bytes(0xc0)) => cat(repeat(open, n), leaf);
const arrays = (n: number, leaf?: Uint8Array) => chain(bytes(0x91), n, leaf);
const refused = (payload: Uint8Array, label: string) =>
assert.throws(() => checkPayloadDepth(payload), (error: unknown) =>
error instanceof CultCachePayloadDepthError && error.depth === L + 1 && error.limit === L, label);
test("the limit is 256: depth 256 passes and 257 is refused, for every array and map header width", () => {
assert.equal(L, 256);
const kinds: [string, Uint8Array][] = [
["fixarray", bytes(0x91)],
["array16", bytes(0xdc, 0, 1)],
["array32", bytes(0xdd, 0, 0, 0, 1)],
["fixmap", bytes(0x81, 0x00)],
["map16", bytes(0xde, 0, 1, 0x00)],
["map32", bytes(0xdf, 0, 0, 0, 1, 0x00)],
];
for (const [name, open] of kinds) {
assert.doesNotThrow(() => checkPayloadDepth(chain(open, L)), `${name} at the limit`);
refused(chain(open, L + 1), `${name} past the limit`);
}
});
test("an empty map or array as the innermost container counts like any other", () => {
for (const empty of [bytes(0x90), bytes(0x80), bytes(0xdc, 0, 0), bytes(0xdd, 0, 0, 0, 0), bytes(0xde, 0, 0), bytes(0xdf, 0, 0, 0, 0)]) {
assert.doesNotThrow(() => checkPayloadDepth(arrays(L - 1, empty)));
refused(arrays(L, empty), "empty innermost past the limit");
}
// [a chain of 255, a chain of 255]: the first sibling closes before the second opens, so the depth is 256, not 511.
const sibling = arrays(L - 1);
assert.doesNotThrow(() => checkPayloadDepth(cat(bytes(0x92), sibling, sibling)));
refused(cat(bytes(0x92), sibling, arrays(L)), "the second sibling is one deeper");
// A map's key and value both count as elements: {key: chain, key: chain} closes after four elements.
assert.doesNotThrow(() => checkPayloadDepth(cat(bytes(0x82, 0x00), sibling, bytes(0x01), sibling)));
refused(cat(bytes(0x82, 0x00), sibling, bytes(0x01), arrays(L)), "the second map value is one deeper");
refused(cat(bytes(0x82), arrays(L), sibling, bytes(0x01), sibling), "a map key one deeper");
});
test("a header counts every child it announces, up to the widest of each kind", () => {
const nils = (count: number) => repeat(bytes(0xc0), count);
const kinds: [string, Uint8Array, number][] = [
["the longest fixarray", bytes(0x9f), 15],
["the longest fixmap", bytes(0x8f), 30],
["array16 of two", bytes(0xdc, 0, 2), 2],
["array32 of two", bytes(0xdd, 0, 0, 0, 2), 2],
["map16 of two pairs", bytes(0xde, 0, 2), 4],
["map32 of two pairs", bytes(0xdf, 0, 0, 0, 2), 4],
];
for (const [name, header, elements] of kinds) {
assert.doesNotThrow(() => checkPayloadDepth(cat(header, nils(elements - 1), arrays(L - 1))), `${name}: the last child at the limit`);
refused(cat(header, nils(elements - 1), arrays(L)), `${name}: the last child past it`);
}
});
test("every scalar is skipped by its length, whatever bytes it holds", () => {
const filler = (count: number) => repeat(bytes(0x91), count);
const scalars: [string, Uint8Array][] = [
["nil", bytes(0xc0)], ["the never-used 0xc1", bytes(0xc1)], ["false", bytes(0xc2)], ["true", bytes(0xc3)], ["positive fixint", bytes(0x7f)], ["negative fixint", bytes(0xe0)],
["uint8", bytes(0xcc, 0x91)], ["uint16", cat(bytes(0xcd), filler(2))], ["uint32", cat(bytes(0xce), filler(4))], ["uint64", cat(bytes(0xcf), filler(8))],
["int8", bytes(0xd0, 0x91)], ["int16", cat(bytes(0xd1), filler(2))], ["int32", cat(bytes(0xd2), filler(4))], ["int64", cat(bytes(0xd3), filler(8))],
["float32", cat(bytes(0xca), filler(4))], ["float64", cat(bytes(0xcb), filler(8))],
["fixext1", cat(bytes(0xd4), filler(2))], ["fixext2", cat(bytes(0xd5), filler(3))], ["fixext4", cat(bytes(0xd6), filler(5))],
["fixext8", cat(bytes(0xd7), filler(9))], ["fixext16", cat(bytes(0xd8), filler(17))],
["fixstr", cat(bytes(0xa3), filler(3))], ["the longest fixstr", cat(bytes(0xbf), filler(31))], ["str8", cat(bytes(0xd9, 3), filler(3))], ["str16", cat(bytes(0xda, 0, 3), filler(3))],
["str32", cat(bytes(0xdb, 0, 0, 0, 3), filler(3))],
["bin8", cat(bytes(0xc4, 3), filler(3))], ["bin16", cat(bytes(0xc5, 0, 3), filler(3))], ["bin32", cat(bytes(0xc6, 0, 0, 0, 3), filler(3))],
["ext8", cat(bytes(0xc7, 3, 0x7f), filler(3))], ["ext16", cat(bytes(0xc8, 0, 3, 0x7f), filler(3))],
["ext32", cat(bytes(0xc9, 0, 0, 0, 3, 0x7f), filler(3))],
["str16 of 300 bytes", cat(bytes(0xda, 0x01, 0x2c), filler(300))],
];
for (const [name, scalar] of scalars) {
assert.doesNotThrow(() => checkPayloadDepth(cat(bytes(0x92), scalar, arrays(L - 1))), `${name} then a chain at the limit`);
refused(cat(bytes(0x92), scalar, arrays(L)), `${name} then a chain past it`);
// As the leaf of a chain: a scalar read at the wrong length leaves containers open, and the sibling chain then reads one deeper.
assert.doesNotThrow(() => checkPayloadDepth(cat(bytes(0x92), arrays(L - 1, scalar), arrays(L - 1))), `${name} closes the chain it ends`);
}
});
test("a payload that ends early, is empty or carries trailing bytes is the reader's business, not the walk's", () => {
for (const early of [[], [0xd9], [0xda, 0], [0xdb, 0, 0, 0], [0xc4], [0xc5, 0], [0xc6, 0, 0, 0], [0xc7], [0xc8, 0], [0xc9, 0, 0, 0], [0xdc, 0], [0xdd, 0, 0, 0], [0xde, 0], [0xdf, 0, 0, 0]]) {
assert.doesNotThrow(() => checkPayloadDepth(bytes(...early)), early.map((value) => value.toString(16)).join(" "));
}
assert.doesNotThrow(() => checkPayloadDepth(arrays(L - 1, bytes(0x91))), "a chain cut off before its leaf");
assert.doesNotThrow(() => checkPayloadDepth(cat(bytes(0x90), arrays(L + 50))), "bytes after the first complete value are not walked");
assert.doesNotThrow(() => checkPayloadDepth(bytes(0xc0)));
});
test("the walk is iterative: 100000 nested arrays are refused with the depth error, not a RangeError", () => {
refused(arrays(100_000), "100000 nested arrays");
refused(chain(bytes(0x81, 0x00), 100_000), "100000 nested maps");
});
test("a refusal names the subject, the depth and the limit and never a byte of the payload", () => {
const payload = chain(bytes(0x91), L + 1, cat(bytes(0xd9, CANARY.length), new TextEncoder().encode(CANARY)));
assert.throws(
() => checkPayloadDepth(payload, 'document type "t.note" payload'),
(error: unknown) => error instanceof CultCachePayloadDepthError
&& error.name === "CultCachePayloadDepthError"
&& error.message === 'document type "t.note" payload nests 257 MessagePack containers deep; the limit is 256.',
);
const canaryPayload = payload;
assert.throws(() => checkPayloadDepth(canaryPayload), (error: Error) => !error.message.includes(CANARY) && !JSON.stringify(error).includes(CANARY));
});
const note = z.object({ id: z.string(), count: z.number() });
const slotCalls: string[] = [];
const watched = { ...codecs.string, decode: (raw: unknown) => (slotCalls.push("decode"), codecs.string.decode(raw)), encode: (value: string) => (slotCalls.push("encode"), codecs.string.encode(value)) };
const guarded = defineDocumentType({
type: "t.guarded",
schemaId: "t.guarded.v0",
schema: note,
name: "id",
codec: codecs.slots<{ id: string; count: number }>({ 0: ["id", watched], 1: ["count", codecs.int32] }, "T.Guarded"),
});
test("the owner refuses a payload past the limit before a codec reads it, and a codec's bytes before they are wrapped", () => {
assert.throws(() => decodeDocumentPayload(guarded, documentPayloadFromBytes(arrays(L + 1))), CultCachePayloadDepthError);
assert.deepEqual(slotCalls, [], "the codec never saw the deep payload");
assert.deepEqual(decodeDocumentPayload(guarded, documentPayloadFromBytes(encode(["a", 1]))), { id: "a", count: 1 });
const formatterBytes = (value: unknown) => documentPayloadBytes(encodeDocumentPayload(guarded, value));
assert.deepEqual(formatterBytes({ id: "a", count: 1 }), encode(["a", 1]));
});
test("a document without a codec or formatter writes a value L deep and refuses L+1 by the walk", () => {
const anything = defineDocumentType({ type: "t.any", schemaId: "t.any.v0", schema: z.any(), name: "id" });
const nest = (n: number): unknown => (n === 1 ? [] : [nest(n - 1)]);
const atLimit = encodeDocumentPayload(anything, nest(L));
assert.deepEqual(documentPayloadBytes(atLimit), encode(nest(L), { maxDepth: 1000 }));
assert.deepEqual(decodeDocumentPayload(anything, atLimit), nest(L));
assert.throws(() => encodeDocumentPayload(anything, nest(L + 1)), (error: unknown) =>
error instanceof CultCachePayloadDepthError && error.depth === L + 1 && error.limit === L
&& error.message === 'document type "t.any" value nests 257 MessagePack containers deep; the limit is 256.');
assert.throws(() => decodeDocumentPayload(anything, documentPayloadFromBytes(arrays(L + 1))), CultCachePayloadDepthError);
});
test("the limit and its error are exported from the root, and the walk itself is not", () => {
assert.equal(root.MAX_PAYLOAD_DEPTH, 256);
assert.equal(root.CultCachePayloadDepthError, CultCachePayloadDepthError);
assert.equal("checkPayloadDepth" in root, false);
});
// A v1 store whose one record carries `payload` as bin, under a catalog entry that names its schema.
async function storeWith(payload: Uint8Array): Promise<string> {
const file = join(await mkdtemp(join(tmpdir(), "cultcache-payload-depth-")), "deep.cc");
const entry = ["sha256:stale", "tests.schema_stamped_entry", "tests.schema_stamped_entry.v1", "stale", "", [], []];
await writeFile(file, encode(["cultcache.store.v1", [entry], [["record-1", "sha256:stale", "2026-06-25T12:00:00Z", payload]]]));
return file;
}
const stamped = (containersInsideTheStamp: number) =>
cat(bytes(0x92), encode("tests.schema_stamped_entry.v1"), arrays(containersInsideTheStamp));
test("the inspector previews a payload at the limit and reports one past it, or 100000 deep, as a decode error", async () => {
const atLimit = await storeWith(stamped(L - 1));
const previewed = inspectCultCacheBytes(atLimit, await readFile(atLimit)).records[0]!;
assert.equal(previewed.payloadDecodeError, undefined);
assert.equal(previewed.schemaName, "tests.schema_stamped_entry");
for (const inside of [L, 100_000]) {
const file = await storeWith(stamped(inside));
const record = inspectCultCacheBytes(file, await readFile(file)).records[0]!;
assert.equal(record.payloadDecodeError, "document payload nests 257 MessagePack containers deep; the limit is 256.");
assert.equal(record.schemaName, "tests.schema_stamped_entry");
}
});
// A header whose length or count needs all four bytes: the low sixteen bits of the length are small, the whole is not.
const be32 = (n: number) => bytes((n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff);
test("a 32-bit length or count is read whole: the walk skips a 65539-byte scalar and counts 65540 children", () => {
const blob = repeat(bytes(0x91), 65539);
const scalars: [string, Uint8Array][] = [
["bin32", cat(bytes(0xc6), be32(65539), blob)],
["str32", cat(bytes(0xdb), be32(65539), blob)],
["ext32", cat(bytes(0xc9), be32(65539), bytes(0x7f), blob)],
];
for (const [name, scalar] of scalars) {
assert.doesNotThrow(() => checkPayloadDepth(cat(bytes(0x92), scalar, arrays(L - 1))), `${name} of 65539 bytes then a chain at the limit`);
refused(cat(bytes(0x92), scalar, arrays(L)), `${name} of 65539 bytes then a chain past it`);
}
// Both counts exceed 65535 and their low sixteen bits are small (4 children; 1 pair), so a count cut to 16 bits closes the container early.
const wide: [string, Uint8Array, number][] = [
["array32", cat(bytes(0xdd), be32(65540)), 65540],
["map32", cat(bytes(0xdf), be32(65537)), 2 * 65537],
];
for (const [name, header, children] of wide) {
const nils = repeat(bytes(0xc0), children);
refused(cat(bytes(0x92), header, nils, arrays(L)), `${name} announcing more than 65535: the sibling chain past the limit`);
assert.doesNotThrow(() => checkPayloadDepth(cat(bytes(0x92), header, nils, arrays(L - 1))), `${name}: the sibling chain at the limit`);
}
});
test("a user formatter's bytes are its own: a 0x91 grey image round-trips, while plain and codec documents are still walked", () => {
const grey = defineDocumentType({
type: "t.grey",
schemaId: "t.grey.v0",
schema: z.object({ pixels: z.custom<Uint8Array>() }),
name: "pixels",
formatter: { encode: (value) => (value as { pixels: Uint8Array }).pixels, decode: (payload) => ({ pixels: payload }) },
});
for (const size of [300, 4096]) {
const pixels = new Uint8Array(size).fill(0x91);
const written = encodeDocumentPayload(grey, { pixels });
assert.deepEqual(documentPayloadBytes(written), pixels, `${size} bytes written as the formatter made them`);
assert.deepEqual(decodeDocumentPayload(grey, written), { pixels }, `${size} bytes read back through the formatter`);
}
const anything = defineDocumentType({ type: "t.any2", schemaId: "t.any2.v0", schema: z.any(), name: "id" });
const nest = (n: number): unknown => (n === 1 ? [] : [nest(n - 1)]);
assert.throws(() => encodeDocumentPayload(anything, nest(L + 1)), CultCachePayloadDepthError);
assert.throws(() => decodeDocumentPayload(anything, documentPayloadFromBytes(arrays(L + 1))), CultCachePayloadDepthError);
// A slot that writes `depth` nested arrays, so a codec document can be made as deep as the test needs.
const nested: codecs.Codec<number> = {
typeName: "System.Object",
encode: (depth) => nest(depth),
decode: (raw) => (raw as unknown[]).length,
};
const coded = defineDocumentType({
type: "t.coded",
schemaId: "t.coded.v0",
schema: z.any(),
name: "id",
codec: codecs.slots<{ id: string; depth: number }>({ 0: ["id", codecs.string], 1: ["depth", nested] }, "T.Coded"),
});
assert.doesNotThrow(() => encodeDocumentPayload(coded, { id: "a", depth: L - 1 }));
assert.throws(() => encodeDocumentPayload(coded, { id: "a", depth: L }), CultCachePayloadDepthError);
assert.throws(() => decodeDocumentPayload(coded, documentPayloadFromBytes(arrays(L + 1))), CultCachePayloadDepthError);
});