From c0d30e80816053cd151f0789e7f4bcdf5c6c5ac1 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 23 Oct 2026 17:48:02 +0200 Subject: [PATCH] Test fixtures: the Rust owner's Idunn runtime records, and one fake Idunn RUDP endpoint tests/fixtures/idunn-runtime holds expected.cc, activation.cc, the activation seed, the presence identity credential and the machine-id it is bound to, copied from CultLib 217a9480 (Rust-written; test keys only). fake-idunn-rudp.ts is the accept server idunn-rudp-health.test.ts built inline, now keeping a session per remote and every delivered frame, so a boot test can receive several pulses. Co-Authored-By: Claude Opus 5.5 --- tests/fixtures/fake-idunn-rudp.ts | 64 ++++++++++++++++++ tests/fixtures/idunn-runtime/.gitattributes | 1 + tests/fixtures/idunn-runtime/README.md | 11 +++ .../idunn-runtime/activation-key.seed | 1 + tests/fixtures/idunn-runtime/activation.cc | Bin 0 -> 798 bytes tests/fixtures/idunn-runtime/expected.cc | Bin 0 -> 1067 bytes tests/fixtures/idunn-runtime/machine-id | 1 + .../provider-identity.credential | Bin 0 -> 682 bytes tests/idunn-rudp-health.test.ts | 41 ++--------- 9 files changed, 83 insertions(+), 36 deletions(-) create mode 100644 tests/fixtures/fake-idunn-rudp.ts create mode 100644 tests/fixtures/idunn-runtime/.gitattributes create mode 100644 tests/fixtures/idunn-runtime/README.md create mode 100644 tests/fixtures/idunn-runtime/activation-key.seed create mode 100644 tests/fixtures/idunn-runtime/activation.cc create mode 100644 tests/fixtures/idunn-runtime/expected.cc create mode 100644 tests/fixtures/idunn-runtime/machine-id create mode 100644 tests/fixtures/idunn-runtime/provider-identity.credential diff --git a/tests/fixtures/fake-idunn-rudp.ts b/tests/fixtures/fake-idunn-rudp.ts new file mode 100644 index 0000000..e084666 --- /dev/null +++ b/tests/fixtures/fake-idunn-rudp.ts @@ -0,0 +1,64 @@ +/** + * A stand-in for Idunn's health endpoint: it accepts each RUDP connect, acks + * the frame that follows and keeps every delivered frame. Heimdall opens a + * fresh socket and session per publication, so sessions are kept per remote. + */ +import dgram from "node:dgram"; +import { + CultNetRudpSession, + decodeRudpPacket, + encodeRudpPacket, + type CultNetTransportFrame, +} from "cultnet-ts"; + +export interface FakeIdunnRudp { + port: number; + frames: CultNetTransportFrame[]; + /** Resolves with the nth delivered frame (0-based) once it has arrived. */ + frame(index: number): Promise; + close(): void; +} + +export async function startFakeIdunnRudp(): Promise { + const server = dgram.createSocket("udp4"); + const sessions = new Map(); + const frames: CultNetTransportFrame[] = []; + const waiting: Array<{ index: number; resolve: (frame: CultNetTransportFrame) => void }> = []; + + server.on("message", (wire, remote) => { + const packet = decodeRudpPacket(wire); + const key = `${remote.address}:${remote.port}`; + if (packet.packetType === "connect") { + const session = new CultNetRudpSession({ + connectionId: 0x1d0d0001, + initialSequence: 1, + resendDelayMs: 100, + }); + sessions.set(key, session); + server.send(encodeRudpPacket(session.acceptConnect(packet, Date.now())), remote.port, remote.address); + return; + } + const session = sessions.get(key); + if (!session) return; + const result = session.receive(packet, Date.now()); + if (result.reply) server.send(encodeRudpPacket(result.reply), remote.port, remote.address); + for (const frame of result.delivered) { + frames.push(frame); + for (const waiter of waiting.filter((entry) => entry.index === frames.length - 1)) waiter.resolve(frame); + } + if (result.delivered.length > 0) { + server.send(encodeRudpPacket(session.createAck()), remote.port, remote.address); + } + }); + await new Promise((resolve) => server.bind(0, "127.0.0.1", resolve)); + + return { + port: server.address().port, + frames, + frame: (index) => + frames[index] + ? Promise.resolve(frames[index]!) + : new Promise((resolve) => waiting.push({ index, resolve })), + close: () => server.close(), + }; +} diff --git a/tests/fixtures/idunn-runtime/.gitattributes b/tests/fixtures/idunn-runtime/.gitattributes new file mode 100644 index 0000000..fa1385d --- /dev/null +++ b/tests/fixtures/idunn-runtime/.gitattributes @@ -0,0 +1 @@ +* -text diff --git a/tests/fixtures/idunn-runtime/README.md b/tests/fixtures/idunn-runtime/README.md new file mode 100644 index 0000000..0f26117 --- /dev/null +++ b/tests/fixtures/idunn-runtime/README.md @@ -0,0 +1,11 @@ +# Idunn runtime fixtures + +Written by the Rust owner, not by Heimdall's tests. Copied byte for byte from CultLib +`217a9480`, `packages/cultnet-ts/test/fixtures/idunn-runtime/web` (`expected.cc`, +`activation.cc`, `activation-key.seed`, `provider-identity.credential`) and that directory's +`machine-id`. CultLib regenerates them with `packages/cultnet-rs/examples/idunn_runtime_fixture.rs` +(its own README has the Yggdrasil command); the provider identity is bound to `machine-id`, so a +test that decodes it supplies that machine id instead of `/etc/machine-id`. + +Every key here is a test key. `provider-identity.credential` is the shape Idunn passes on the +presence-identity descriptor: key fields are 32-integer arrays, which only the Rust owner writes. diff --git a/tests/fixtures/idunn-runtime/activation-key.seed b/tests/fixtures/idunn-runtime/activation-key.seed new file mode 100644 index 0000000..7b6d37f --- /dev/null +++ b/tests/fixtures/idunn-runtime/activation-key.seed @@ -0,0 +1 @@ +¯ÂOd×f¯l*oµ‘ÀFªv‰n�hLûÑpšS´p \ No newline at end of file diff --git a/tests/fixtures/idunn-runtime/activation.cc b/tests/fixtures/idunn-runtime/activation.cc new file mode 100644 index 0000000000000000000000000000000000000000..3931baf2d0ad2e084e2704913f368f46605cef11 GIT binary patch literal 798 zcmbtOOKTHR6mDrDh#<4Z?5WQE!R>{*&xdssSR4V zbL&ZLn)P~+HtTtu*fUb+aZBcLqaL)v?t0wQ^$2hBsYRLnv+Z)`CnR!ACbnEH7etkJ z{gEECY33&tt9`LIBsNiAU9O%ub+J=4Td)Hq2n(HUn~?E`v zrQVT=SzkshLmV-MA{Htw7-GnUMq0{&V5>0p?Z#@L)F{jeK!gRx%&~|=r1d>(!pOgR zqot9>_QEImdFkD^;OCBX=k(p>FZ1u=;rQC-5jfBo-g0N`M;GQE*vnrpwGSnj9X|SO R8N2`h literal 0 HcmV?d00001 diff --git a/tests/fixtures/idunn-runtime/expected.cc b/tests/fixtures/idunn-runtime/expected.cc new file mode 100644 index 0000000000000000000000000000000000000000..dbf7153b5b59ff258a410787f85a7f7695945346 GIT binary patch literal 1067 zcmbtTO-jQ+6jqPm2_!2QW->EP(n(f=3wI(YE)e1)f)^cUGO+_m5dg?Mh(=t^{#J6|^nyb~)X=~rkdyrdUz(`nQ=(fk)l&1Ui|13|@W26UEflF;TLMu{#!oG2Z`Jdiz{ht`DBdRc>Yh zr{(LmA{A5=mrisugai*JbRCN4Py6VY$lxiAhn7Iq_U= zUO#QO{gh{6r(E}Z-{wuh7`GkMvQ5GWYjKCumN10?>HsAz$Ft0a*YpUpOv=2bLwztC zHfAmBtxAs4JeFYudH}p5gGm?WoEQ$@`-5sSJxy{6H-VJhhtcI42BA%a)gdlp4Lq3A Yl99UT-&d&+?}m@RDb=sJ?vcfdFMc*;F8}}l literal 0 HcmV?d00001 diff --git a/tests/fixtures/idunn-runtime/machine-id b/tests/fixtures/idunn-runtime/machine-id new file mode 100644 index 0000000..a71716f --- /dev/null +++ b/tests/fixtures/idunn-runtime/machine-id @@ -0,0 +1 @@ +c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1 \ No newline at end of file diff --git a/tests/fixtures/idunn-runtime/provider-identity.credential b/tests/fixtures/idunn-runtime/provider-identity.credential new file mode 100644 index 0000000000000000000000000000000000000000..2e617139cb114b951bfdc3fca179a02ff872830c GIT binary patch literal 682 zcmb7?KWG#|6vq7nT3OhrSZQLRm_2vqW@cxuvKA}?Drv%IW_BjGve`{?dv`IF61B03 zU_fkyNRSk%0zoTn1kn#{0^_E%dsB)BeWjJe_#1P3Dl_pU%bF3|5>G`JvXU1?BAFz1=zhLBGR;Ls;j^PfzHDg6y_Lowh7cQNvvhZlz%}W;`q$V%3yqs-@BU1l_84XD1uQ33Q zdRK$@uP zrFq$_by4U#ugbknP1P^6)D~6gvex7w6(N@6B1 z#^E0aO4Im_m8;mrw!!0xKDG{HZD9%T@f}~$3~?_&+`%S3;Q0+_je?J E0GWjm%m4rY literal 0 HcmV?d00001 diff --git a/tests/idunn-rudp-health.test.ts b/tests/idunn-rudp-health.test.ts index 66127d4..fa40393 100644 --- a/tests/idunn-rudp-health.test.ts +++ b/tests/idunn-rudp-health.test.ts @@ -1,18 +1,12 @@ -import dgram from "node:dgram"; import { mkdtemp } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { decode } from "@msgpack/msgpack"; -import { - CultNetRudpSession, - decodeRudpPacket, - encodeRudpPacket, - parseCultNetMessage, - type CultNetTransportFrame, -} from "cultnet-ts"; +import { parseCultNetMessage } from "cultnet-ts"; import { describe, expect, it, vi } from "vitest"; import { loadConfig } from "../src/config.js"; import { publishIdunnRudpHealth } from "../src/idunn-rudp-health.js"; +import { startFakeIdunnRudp } from "./fixtures/fake-idunn-rudp.js"; vi.mock("node:fs/promises", async (importOriginal) => { const original = await importOriginal(); @@ -27,37 +21,12 @@ vi.mock("node:fs/promises", async (importOriginal) => { describe("Heimdall Idunn health publication", () => { it("completes the canonical RUDP handshake before publishing its signed frame", async () => { - const server = dgram.createSocket("udp4"); - const session = new CultNetRudpSession({ - connectionId: 0x1d0d0001, - initialSequence: 1, - resendDelayMs: 100, - }); - let resolveFrame!: (frame: CultNetTransportFrame) => void; - const frameReceived = new Promise((resolve) => { - resolveFrame = resolve; - }); - - server.on("message", (wire, remote) => { - const packet = decodeRudpPacket(wire); - if (packet.packetType === "connect") { - server.send(encodeRudpPacket(session.acceptConnect(packet, Date.now())), remote.port, remote.address); - return; - } - const result = session.receive(packet, Date.now()); - if (result.reply) server.send(encodeRudpPacket(result.reply), remote.port, remote.address); - for (const frame of result.delivered) resolveFrame(frame); - if (result.delivered.length > 0) { - server.send(encodeRudpPacket(session.createAck()), remote.port, remote.address); - } - }); - await new Promise((resolve) => server.bind(0, "127.0.0.1", resolve)); + const server = await startFakeIdunnRudp(); try { const tmp = await mkdtemp(path.join(os.tmpdir(), "heimdall-idunn-rudp-")); - const address = server.address(); const config = loadConfig({ - GC_ACCESS_IDUNN_RUDP_HEALTH: `127.0.0.1:${address.port}`, + GC_ACCESS_IDUNN_RUDP_HEALTH: `127.0.0.1:${server.port}`, GC_ACCESS_PROVIDER_HEALTH_IDENTITY_PATH: path.join(tmp, "provider-health-identity.cc"), }); const observedAt = "2026-08-22T00:45:00.000Z"; @@ -78,7 +47,7 @@ describe("Heimdall Idunn health publication", () => { observedAt, presence, }); - const frame = await frameReceived; + const frame = await server.frame(0); const message = parseCultNetMessage(decode(frame.payload), "cultnet.schema.v0"); expect(frame.channelId).toBe("schema");