Heimdall/tests/fixtures/fake-idunn-rudp.ts
Meta Vens c0d30e8081 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 <noreply@anthropic.com>
2026-10-23 17:48:02 +02:00

64 lines
2.3 KiB
TypeScript

/**
* 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<CultNetTransportFrame>;
close(): void;
}
export async function startFakeIdunnRudp(): Promise<FakeIdunnRudp> {
const server = dgram.createSocket("udp4");
const sessions = new Map<string, CultNetRudpSession>();
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<void>((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(),
};
}