From 39a8a5f45e2cbee1351af18bae7d669092c6e541 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 23 Sep 2026 12:32:45 +0200 Subject: [PATCH] Test the Node QUIC binding, consumer and the five trust negatives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit packages/cultmesh-ts/test/realtime-quic-native.test.ts: the FFI smoke against CultMeshQuicNativeRuntime (open/close, a listener with a real PKCS12 credential, a full loopback connection delivering one raw frame end to end), plus the negative grep that .async( appears only on nextEvent and no koffi.register exists. packages/cultmesh-ts/test/realtime-quic-consumer.test.ts: the TypeScript consumer against a raw listener opened through the native runtime (connect with the correct pin; refuse a wrong pin; refuse a missing pin with no validator; a caller validator observes the target and the DER bytes; a late older generation is dropped), mirroring CultMeshNativeQuicRealtimeTransportTests.cs and CultMeshQuicRealtimeTransportTests.cs. Then the five trust negatives through CultMeshQuicRealtimeSessionManager with a static lookup source and routes signed with node:crypto (P-256, IEEE P1363 via dsaEncoding): (a) expired certificate, (b) unsigned remote route, (c) untrusted Odin root, (d) a route that verifies but pins the wrong TLS certificate, refused by the connector before isVerifiedFor is ever asked, and (e) a stale/replayed generation, proven with a stub connector whose transport fails isVerifiedFor, asserting the session manager disposes it and rejects the connect. There is no session_open nonce on the QUIC plane (route generation plus the TLS handshake is its freshness), so (e) is this shape rather than a literal nonce replay, as the cut map records. test/fixtures/quic-test.p12 and quic-test-cert.der: a self-signed ECDSA P-256 PKCS12 test credential (OpenSSL, empty password, 1088 bytes — the same shape as the native bridge's own embedded test PKCS12) and its DER certificate, used to open real raw listeners and compute the expected cert-sha256 pin at test time rather than hand-transcribing it. Discrepancy kept, reported: the C# reference's receive-side inbox also coalesces a backlog of not-yet-drained latest-only frames of the same key to just the newest; this transport applies only the generation-drop filter the cut map names (":538-560") and queues every frame that passes it. The interop lane's frames are far enough apart that this never matters in practice, but a consumer that fell behind would see more history here than the C# reference delivers. Co-Authored-By: Claude Sonnet 5 --- .../test/fixtures/quic-test-cert.der | Bin 0 -> 419 bytes .../cultmesh-ts/test/fixtures/quic-test.p12 | Bin 0 -> 1088 bytes .../test/realtime-quic-consumer.test.ts | 470 ++++++++++++++++++ .../test/realtime-quic-native.test.ts | 148 ++++++ 4 files changed, 618 insertions(+) create mode 100644 packages/cultmesh-ts/test/fixtures/quic-test-cert.der create mode 100644 packages/cultmesh-ts/test/fixtures/quic-test.p12 create mode 100644 packages/cultmesh-ts/test/realtime-quic-consumer.test.ts create mode 100644 packages/cultmesh-ts/test/realtime-quic-native.test.ts diff --git a/packages/cultmesh-ts/test/fixtures/quic-test-cert.der b/packages/cultmesh-ts/test/fixtures/quic-test-cert.der new file mode 100644 index 0000000000000000000000000000000000000000..5cce6f94005c196ccd1ed404911b03a1c1338492 GIT binary patch literal 419 zcmXqLVw`W##OS(!nTe5!NhDLU>-x>BQ5$%imz=WODe>b_>vaJGE;bIWHji_*EX+&> zs)ou2ifqiGEX+Jo$)!0Zxv9k&x`m~g$+~%oC7ETZx+ST_CB+7E;=D#?29`#~h6aY_ zhQ?9iyv87|0hCLuF_8wsZ0umWnHZstU}j`zc4A<;-xk_={aEPfjk=wwey)=^xOLR; z`6sa#u)guTAY1#SWoJ^_gA*#ZLyXK$RF|v^FPZToyRqxfl*&p)xz`rT2bU}kHV8D3 z1$so5k420{gsXt%?aOxWZATq$dY_toVUDJ(o0@?h`q?kPz3|yHM!e#cY{_sFF@z;%BHN6-iljARZ*-ac*&%UNoXmxF7@-ikxhG#ET j;-hy2PIY;jyI+2Xz**6N)MxoxdnfPwlvCCE`*}0~W~z`v literal 0 HcmV?d00001 diff --git a/packages/cultmesh-ts/test/fixtures/quic-test.p12 b/packages/cultmesh-ts/test/fixtures/quic-test.p12 new file mode 100644 index 0000000000000000000000000000000000000000..e9d03ce6c920ba83458b808287847f75dca47ec2 GIT binary patch literal 1088 zcmXqLVzFUjWHxAG{=~+q)#lOmotKfFaX}OFW0of7`#|B_22D&|2r0G&O-$85p>ifh z1|U^{kYO~4hjUnY4T1~|5nLVv6&8V)hOt&Lp)9x>$@UU?}Y-QqP zWiXIs<4kDtU`%CZVbo#~U}d?vOS3!Mzk73@$??>S!3|AJ4EwYUKh2t>CRt(7Qy0mX zE_cmzK3m`Pvw?drYMNSEW&UR7GrqUPMOm)r+r7|`Q~Yf=?zpd;ylVR2*~bi-qiw|LrBmcAg{cd}2N_2ZWhPprCbdRykG_5xe;)v3}; z?{H*ul_xoORIdqDNG_^*6B}*+HGkD!&nG23&llaiS7_n;KL4PD;1B(%VEE{g0swp|J;#zKjnV=w{Yd|1-gqjd(O2xy-egq&f1W5 zC%0@#TxGl9+^$x)$f?S!loJGI8SUdyUNqsC0gLqO*%x(LJB9I>AsJ`z7-R+2B;@Z8~$?bgQ!o;^(yODW#mZPYe3HtA&;<5T6W z@xecP7j;CuV&naJFC~7 zQ~Z_1DA~8?!inJK=YozMQir>Z-e2OHXQsVt`IQrF-oeZBRVyFZp7K*uWqVlq&+*>M zkaHm%3HHb2jXyp6DCzTYtBl>w=B35AH#EG7Sop|jDbp+Vor` zgp&2O22G5f@N@-AsEk%DO^jwhVIv^cW8;RVVID3Hme;3ljWmH>m%1}dh)A1bNT(-!F;?djT7$vFn`_q^PS%9?{9hJ-9G2|$yTm; za`czxz3;)X42F&R_WKN!eigJ_K7Fq>ck7E;Jdff_%#Dw|u(NPAJ>k4?(~H#>PV5cu zrd|!VPrNy=ebx^1?JvYk7(PB#4gTDC^;)@@^5xT;XAAiJxb1Iq{Uvkd%Z4>=`DK@V zmH*bPv|w4m%5-x_% { + const runtime = await CultMeshQuicNativeRuntime.open(); + const pkcs12 = readFileSync(FIXTURE_P12); + const { listenerId, boundPort } = runtime.listenerOpen("127.0.0.1", 0, pkcs12, ""); + return new RawQuicListener(runtime, listenerId, boundPort); + } + + /** Waits for one inbound connection and returns its native connection id. */ + async acceptOnce(): Promise { + return await new Promise((resolve) => { + this.runtime.onListenerEvent(this.listenerId, (event) => { + if (event.type === CULTMESH_QUIC_EVENT_LISTENER_NEW_CONNECTION) resolve(event.connectionId); + }); + }); + } + + /** Sends one frame to an accepted connection on a fresh stream of `kind`. */ + sendFrame(connectionId: bigint, frame: CultMeshRealtimeFrame, kind: number): void { + const streamId = this.runtime.streamOpen(connectionId, kind); + this.runtime.streamSendFrame(streamId, encodeRealtimeFrame(frame), true); + } + + async close(): Promise { + this.runtime.listenerClose(this.listenerId); + await this.runtime.release(); + } +} + +function testFrame(overrides: Partial = {}): CultMeshRealtimeFrame { + return { + channelId: "aetheria.entities", + schemaId: "eve.entity_soa.v1", + bodyId: "body:aetheria:entities", + producerEpoch: 7n, + sequence: 1n, + delivery: "latest-only", + payload: new Uint8Array([3, 4, 5]), + ...overrides, + }; +} + +test("connect with the correct pin succeeds and receives a frame", async (t) => { + if (!nativeBridgeAvailable()) return void t.skip("no native bridge available"); + const listener = await RawQuicListener.open(); + try { + const target: CultMeshRealtimeTarget = { verseId: "aetheria", authorityRuntimeId: "service:aetheria.daemon" }; + const candidate: CultMeshRealtimeCandidate = { + endpoint: `cultmesh-state+quic://127.0.0.1:${listener.boundPort}?cert-sha256=${fixturePinHex()}`, + authorityRuntimeId: target.authorityRuntimeId, + priority: 0, + generation: "gen-1", + }; + const connector = new CultMeshQuicRealtimeConnector(); + const accepted = listener.acceptOnce(); + const transport = await connector.connect(candidate, target); + try { + assert.equal(transport.transportId, "msquic-realtime"); + const connectionId = await accepted; + listener.sendFrame(connectionId, testFrame(), CULTMESH_QUIC_STREAM_LATEST_ONLY); + const received = await transport.receiveFrame(); + assert.equal(received.bodyId, "body:aetheria:entities"); + assert.equal(received.sequence, 1n); + } finally { + transport.dispose(); + } + } finally { + await listener.close(); + } +}); + +test("connect refuses a wrong advertised pin", async (t) => { + if (!nativeBridgeAvailable()) return void t.skip("no native bridge available"); + const listener = await RawQuicListener.open(); + try { + const target: CultMeshRealtimeTarget = { verseId: "aetheria", authorityRuntimeId: "service:aetheria.daemon" }; + const candidate: CultMeshRealtimeCandidate = { + endpoint: `cultmesh-state+quic://127.0.0.1:${listener.boundPort}?cert-sha256=${"0".repeat(64)}`, + authorityRuntimeId: target.authorityRuntimeId, + priority: 0, + generation: "gen-1", + }; + const connector = new CultMeshQuicRealtimeConnector(); + await assert.rejects(connector.connect(candidate, target), /certificate|rejected/i); + } finally { + await listener.close(); + } +}); + +test("connect refuses a missing certificate pin when no validator is supplied", async (t) => { + if (!nativeBridgeAvailable()) return void t.skip("no native bridge available"); + const listener = await RawQuicListener.open(); + try { + const target: CultMeshRealtimeTarget = { verseId: "aetheria", authorityRuntimeId: "service:aetheria.daemon" }; + const candidate: CultMeshRealtimeCandidate = { + endpoint: `cultmesh-state+quic://127.0.0.1:${listener.boundPort}`, + authorityRuntimeId: target.authorityRuntimeId, + priority: 0, + generation: "gen-1", + }; + const connector = new CultMeshQuicRealtimeConnector(); + await assert.rejects(connector.connect(candidate, target), /certificate|rejected/i); + } finally { + await listener.close(); + } +}); + +test("a caller-supplied validator observes the target identity and the DER bytes", async (t) => { + if (!nativeBridgeAvailable()) return void t.skip("no native bridge available"); + const listener = await RawQuicListener.open(); + try { + const target: CultMeshRealtimeTarget = { verseId: "aetheria", authorityRuntimeId: "service:aetheria.daemon" }; + const candidate: CultMeshRealtimeCandidate = { + endpoint: `cultmesh-state+quic://127.0.0.1:${listener.boundPort}`, + authorityRuntimeId: target.authorityRuntimeId, + priority: 0, + generation: "gen-1", + }; + let observedTarget: CultMeshRealtimeTarget | undefined; + let observedDerLength = 0; + const connector = new CultMeshQuicRealtimeConnector({ + validateProviderCertificate: (validatorTarget, der) => { + observedTarget = validatorTarget; + observedDerLength = der.byteLength; + return true; + }, + }); + const transport = await connector.connect(candidate, target); + transport.dispose(); + assert.deepEqual(observedTarget, target); + assert.ok(observedDerLength > 0); + } finally { + await listener.close(); + } +}); + +test("a late older generation is dropped by the receive-side filter", async (t) => { + if (!nativeBridgeAvailable()) return void t.skip("no native bridge available"); + const listener = await RawQuicListener.open(); + try { + const target: CultMeshRealtimeTarget = { verseId: "aetheria", authorityRuntimeId: "service:aetheria.daemon" }; + const candidate: CultMeshRealtimeCandidate = { + endpoint: `cultmesh-state+quic://127.0.0.1:${listener.boundPort}?cert-sha256=${fixturePinHex()}`, + authorityRuntimeId: target.authorityRuntimeId, + priority: 0, + generation: "gen-1", + }; + const connector = new CultMeshQuicRealtimeConnector(); + const accepted = listener.acceptOnce(); + const transport = await connector.connect(candidate, target); + try { + const connectionId = await accepted; + listener.sendFrame(connectionId, testFrame({ producerEpoch: 8n, sequence: 0n }), CULTMESH_QUIC_STREAM_LATEST_ONLY); + const latest = await transport.receiveFrame(); + assert.equal(latest.producerEpoch, 8n); + assert.equal(latest.sequence, 0n); + + listener.sendFrame(connectionId, testFrame({ producerEpoch: 7n, sequence: 999n }), CULTMESH_QUIC_STREAM_LATEST_ONLY); + const staleTimeout = new AbortController(); + const timer = setTimeout(() => staleTimeout.abort(), 300); + await assert.rejects(transport.receiveFrame(staleTimeout.signal)); + clearTimeout(timer); + } finally { + transport.dispose(); + } + } finally { + await listener.close(); + } +}); + +// --------------------------------------------------------------------------- +// Trust negatives, through the session manager with a static lookup source. +// --------------------------------------------------------------------------- + +function base64UrlToBase64(value: string): string { + let base64 = value.replace(/-/g, "+").replace(/_/g, "/"); + while (base64.length % 4 !== 0) base64 += "="; + return base64; +} + +function generateOdinRoot(): { privateKey: KeyObject; publicKey: CultMeshP256PublicKey } { + const { publicKey, privateKey } = generateKeyPairSync("ec", { namedCurve: "prime256v1" }); + const jwk = publicKey.export({ format: "jwk" }) as { x: string; y: string }; + return { + privateKey, + publicKey: { + keyId: "odin-root-1", + x: base64UrlToBase64(jwk.x), + y: base64UrlToBase64(jwk.y), + }, + }; +} + +interface SignedRouteOptions { + readonly verseId: string; + readonly authorityRuntimeId: string; + readonly endpoint: string; + readonly protocolIds: readonly string[]; + readonly privateKey: KeyObject; + readonly odinKeyId: string; + readonly priority?: number; + readonly generation?: string; + readonly issuedAtUnixMilliseconds?: number; + readonly expiresAtUnixMilliseconds?: number; + readonly providerKey?: CultMeshP256PublicKey; + readonly corruptSignature?: boolean; +} + +function signRoute(options: SignedRouteOptions): CultMeshAuthorityRouteMessage { + const priority = options.priority ?? 0; + const generation = options.generation ?? ""; + const issuedAt = options.issuedAtUnixMilliseconds ?? Date.now() - 60_000; + const expiresAt = options.expiresAtUnixMilliseconds ?? Date.now() + 3_600_000; + const providerKey = options.providerKey ?? { keyId: "provider-1", x: "unused", y: "unused" }; + const view: CultMeshAuthorityRouteView = { + verseId: options.verseId, + authorityRuntimeId: options.authorityRuntimeId, + endpoint: options.endpoint, + protocolIds: options.protocolIds, + priority, + generation, + certificate: { + providerKey, + odinKeyId: options.odinKeyId, + issuedAtUnixMilliseconds: issuedAt, + expiresAtUnixMilliseconds: expiresAt, + signature: "", + }, + }; + const payload = canonicalRoute(view); + const signatureBytes = options.corruptSignature + ? Buffer.alloc(64, 0x41) + : (cryptoSign("sha256", Buffer.from(payload), { key: options.privateKey, dsaEncoding: "ieee-p1363" }) as Buffer); + return { + authorityRuntimeId: options.authorityRuntimeId, + endpoint: options.endpoint, + protocolIds: [...options.protocolIds], + priority, + generation, + certificate: { + providerKeyId: providerKey.keyId, + providerPublicKeyX: providerKey.x, + providerPublicKeyY: providerKey.y, + odinKeyId: options.odinKeyId, + issuedAtUnixMilliseconds: issuedAt, + expiresAtUnixMilliseconds: expiresAt, + signature: signatureBytes.toString("base64"), + }, + }; +} + +function verseWithRoute(verseId: string, route: CultMeshAuthorityRouteMessage): CultMeshVerseDescriptorMessage { + return { + verseId, + displayName: verseId, + authorityModel: "operator-cluster", + compatibility: { transportVersion: "cultmesh.v1", rulesHash: "test", compatibleVerseIds: [], requiredPluginIds: [], optionalPluginIds: [] }, + discoveryEndpoints: [route.endpoint], + authorityRuntimeIds: [route.authorityRuntimeId], + authorityRoutes: [route], + }; +} + +const unreachableConnector: CultMeshRealtimeTransportConnector = { + connectorId: "unreachable", + priority: 0, + canConnect: () => true, + connect: async () => { + throw new Error("a trust-refused route must never reach a connector"); + }, +}; + +test("trust negative (a): an expired certificate is refused", async () => { + const target: CultMeshRealtimeTarget = { verseId: "aetheria", authorityRuntimeId: "service:aetheria.daemon" }; + const root = generateOdinRoot(); + const route = signRoute({ + verseId: target.verseId, + authorityRuntimeId: target.authorityRuntimeId, + endpoint: "cultmesh-state+quic://127.0.0.1:9443", + protocolIds: [CULTMESH_REALTIME_STATE_PROTOCOL_ID], + privateKey: root.privateKey, + odinKeyId: root.publicKey.keyId, + issuedAtUnixMilliseconds: Date.now() - 120_000, + expiresAtUnixMilliseconds: Date.now() - 60_000, + }); + const trust: CultMeshAuthorityTrustPolicy = { mode: "authenticated-remote", odinRoots: [root.publicKey] }; + const manager = new CultMeshQuicRealtimeSessionManager({ + lookupSource: new CultMeshStaticRealtimeLookupSource([verseWithRoute(target.verseId, route)]), + trust, + connectors: [unreachableConnector], + }); + await assert.rejects(manager.connect(target), /valid|expired/i); +}); + +test("trust negative (b): an unsigned remote route is refused", async () => { + const target: CultMeshRealtimeTarget = { verseId: "aetheria", authorityRuntimeId: "service:aetheria.daemon" }; + const route: CultMeshAuthorityRouteMessage = { + authorityRuntimeId: target.authorityRuntimeId, + endpoint: "cultmesh-state+quic://198.51.100.7:9443", + protocolIds: [CULTMESH_REALTIME_STATE_PROTOCOL_ID], + priority: 0, + generation: "gen-1", + }; + const trust: CultMeshAuthorityTrustPolicy = { mode: "authenticated-remote", odinRoots: [] }; + const manager = new CultMeshQuicRealtimeSessionManager({ + lookupSource: new CultMeshStaticRealtimeLookupSource([verseWithRoute(target.verseId, route)]), + trust, + connectors: [unreachableConnector], + }); + await assert.rejects(manager.connect(target), /Odin-signed/i); +}); + +test("trust negative (c): a route signed by an untrusted Odin root is refused", async () => { + const target: CultMeshRealtimeTarget = { verseId: "aetheria", authorityRuntimeId: "service:aetheria.daemon" }; + const signer = generateOdinRoot(); + const trustedRoot = generateOdinRoot(); + const route = signRoute({ + verseId: target.verseId, + authorityRuntimeId: target.authorityRuntimeId, + endpoint: "cultmesh-state+quic://127.0.0.1:9443", + protocolIds: [CULTMESH_REALTIME_STATE_PROTOCOL_ID], + privateKey: signer.privateKey, + odinKeyId: signer.publicKey.keyId, + }); + const trust: CultMeshAuthorityTrustPolicy = { mode: "authenticated-remote", odinRoots: [trustedRoot.publicKey] }; + const manager = new CultMeshQuicRealtimeSessionManager({ + lookupSource: new CultMeshStaticRealtimeLookupSource([verseWithRoute(target.verseId, route)]), + trust, + connectors: [unreachableConnector], + }); + await assert.rejects(manager.connect(target), /not trusted/i); +}); + +test("trust negative (d): a route that verifies but pins the wrong certificate is refused at the transport", async (t) => { + if (!nativeBridgeAvailable()) return void t.skip("no native bridge available"); + const listener = await RawQuicListener.open(); + try { + const target: CultMeshRealtimeTarget = { verseId: "aetheria", authorityRuntimeId: "service:aetheria.daemon" }; + const root = generateOdinRoot(); + const route = signRoute({ + verseId: target.verseId, + authorityRuntimeId: target.authorityRuntimeId, + endpoint: `cultmesh-state+quic://127.0.0.1:${listener.boundPort}?cert-sha256=${"0".repeat(64)}`, + protocolIds: [CULTMESH_REALTIME_STATE_PROTOCOL_ID], + privateKey: root.privateKey, + odinKeyId: root.publicKey.keyId, + }); + const trust: CultMeshAuthorityTrustPolicy = { mode: "authenticated-remote", odinRoots: [root.publicKey] }; + const manager = new CultMeshQuicRealtimeSessionManager({ + lookupSource: new CultMeshStaticRealtimeLookupSource([verseWithRoute(target.verseId, route)]), + trust, + connectors: [new CultMeshQuicRealtimeConnector()], + }); + await assert.rejects(manager.connect(target), /certificate|rejected/i); + } finally { + await listener.close(); + } +}); + +test("trust negative (e): a transport that fails isVerifiedFor is disposed and rejected", async () => { + const target: CultMeshRealtimeTarget = { verseId: "aetheria", authorityRuntimeId: "service:aetheria.daemon" }; + const root = generateOdinRoot(); + const route = signRoute({ + verseId: target.verseId, + authorityRuntimeId: target.authorityRuntimeId, + endpoint: "cultmesh-state+quic://127.0.0.1:9443", + protocolIds: [CULTMESH_REALTIME_STATE_PROTOCOL_ID], + privateKey: root.privateKey, + odinKeyId: root.publicKey.keyId, + generation: "route-generation-1", + }); + const trust: CultMeshAuthorityTrustPolicy = { mode: "authenticated-remote", odinRoots: [root.publicKey] }; + + let disposed = false; + const staleTransport: CultMeshRealtimeTransport = { + transportId: "msquic-realtime", + endpoint: route.endpoint, + async sendFrame() {}, + async receiveFrame() { + return testFrame(); + }, + dispose() { + disposed = true; + }, + // A replayed/stale generation: the transport proves a different route + // generation than the one the session manager just verified. + isVerifiedFor: () => false, + }; + const staleConnector: CultMeshRealtimeTransportConnector = { + connectorId: "stale", + priority: 0, + canConnect: () => true, + connect: async () => staleTransport, + }; + const manager = new CultMeshQuicRealtimeSessionManager({ + lookupSource: new CultMeshStaticRealtimeLookupSource([verseWithRoute(target.verseId, route)]), + trust, + connectors: [staleConnector], + }); + + await assert.rejects(manager.connect(target), /did not prove|No realtime state path/i); + assert.equal(disposed, true, "the session manager must dispose a transport that fails isVerifiedFor"); +}); diff --git a/packages/cultmesh-ts/test/realtime-quic-native.test.ts b/packages/cultmesh-ts/test/realtime-quic-native.test.ts new file mode 100644 index 00000000..660a83d9 --- /dev/null +++ b/packages/cultmesh-ts/test/realtime-quic-native.test.ts @@ -0,0 +1,148 @@ +// The Cut 3 FFI smoke, now run against `CultMeshQuicNativeRuntime` instead of +// raw koffi calls: open a runtime, open a listener with a real credential, +// connect a real loopback connection, exchange one frame's worth of raw +// bytes, and close everything down. This proves the binding's declared ABI +// matches the shipped bridge; frame semantics (delivery, generation) are +// `realtime-quic-consumer.test.ts`'s job. +// +// Skips instead of failing when no native bridge is available: set +// CULTMESH_QUIC_NATIVE_DIR to a directory `scripts/build-quic-native.ps1` or +// `scripts/build-quic-native.sh` built, or run from the committed +// `packages/cultmesh-ts/native/` tree once a later cut lands it. + +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import test from "node:test"; + +import { + CultMeshQuicNativeRuntime, + CULTMESH_QUIC_EVENT_LISTENER_NEW_CONNECTION, + CULTMESH_QUIC_EVENT_CONNECTION_CERTIFICATE_RECEIVED, + CULTMESH_QUIC_EVENT_CONNECTION_CONNECTED, + CULTMESH_QUIC_EVENT_STREAM_STARTED, + CULTMESH_QUIC_EVENT_STREAM_FRAME, + CULTMESH_QUIC_EVENT_STREAM_SEND_COMPLETE, + CULTMESH_QUIC_STREAM_RELIABLE, +} from "../src/realtime-quic-native"; + +const FIXTURE_P12 = join(__dirname, "fixtures", "quic-test.p12"); + +function nativeBridgeAvailable(): boolean { + const dir = process.env.CULTMESH_QUIC_NATIVE_DIR; + if (!dir) return false; + try { + const bridge = process.platform === "win32" ? "gamecult_mesh_quic_native.dll" : "libgamecult_mesh_quic_native.so"; + const dependency = process.platform === "win32" ? "msquic.dll" : "libmsquic.so.2"; + readFileSync(join(dir, bridge)); + readFileSync(join(dir, dependency)); + return true; + } catch { + return false; + } +} + +async function waitFor(collect: (resolve: (value: T) => void) => void, timeoutMs = 5_000): Promise { + return await new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error("timed out waiting for a native QUIC event")), timeoutMs); + collect((value) => { + clearTimeout(timer); + resolve(value); + }); + }); +} + +test("CultMeshQuicNativeRuntime opens and closes with no listener or connection", async (t) => { + if (!nativeBridgeAvailable()) { + t.skip("CULTMESH_QUIC_NATIVE_DIR is not set to a built native bridge."); + return; + } + const runtime = await CultMeshQuicNativeRuntime.open(); + await runtime.release(); +}); + +test("CultMeshQuicNativeRuntime opens and closes a listener with a real PKCS12 credential", async (t) => { + if (!nativeBridgeAvailable()) { + t.skip("CULTMESH_QUIC_NATIVE_DIR is not set to a built native bridge."); + return; + } + const runtime = await CultMeshQuicNativeRuntime.open(); + try { + const pkcs12 = readFileSync(FIXTURE_P12); + const { listenerId, boundPort } = runtime.listenerOpen("127.0.0.1", 0, pkcs12, ""); + assert.ok(boundPort > 0); + runtime.listenerClose(listenerId); + } finally { + await runtime.release(); + } +}); + +test("a full loopback connection delivers one raw frame end to end", async (t) => { + if (!nativeBridgeAvailable()) { + t.skip("CULTMESH_QUIC_NATIVE_DIR is not set to a built native bridge."); + return; + } + const runtime = await CultMeshQuicNativeRuntime.open(); + try { + const pkcs12 = readFileSync(FIXTURE_P12); + const { listenerId, boundPort } = runtime.listenerOpen("127.0.0.1", 0, pkcs12, ""); + + const accepted = waitFor((resolve) => { + runtime.onListenerEvent(listenerId, (event) => { + if (event.type === CULTMESH_QUIC_EVENT_LISTENER_NEW_CONNECTION) resolve(event.connectionId); + }); + }); + + const clientConnectionId = runtime.connectionOpen("127.0.0.1", boundPort); + const clientConnected = waitFor((resolve) => { + runtime.onConnectionEvent(clientConnectionId, (event) => { + if (event.type === CULTMESH_QUIC_EVENT_CONNECTION_CERTIFICATE_RECEIVED) { + runtime.connectionCertificateComplete(clientConnectionId, true); + return; + } + if (event.type === CULTMESH_QUIC_EVENT_CONNECTION_CONNECTED) resolve(); + }); + }); + + const serverConnectionId = await accepted; + const serverFrame = waitFor((resolve) => { + runtime.onConnectionEvent(serverConnectionId, (event) => { + if (event.type === CULTMESH_QUIC_EVENT_STREAM_STARTED) { + assert.equal(event.streamKind, CULTMESH_QUIC_STREAM_RELIABLE); + return; + } + if (event.type === CULTMESH_QUIC_EVENT_STREAM_FRAME) resolve(event.payload); + }); + }); + + await clientConnected; + const streamId = runtime.streamOpen(clientConnectionId, CULTMESH_QUIC_STREAM_RELIABLE); + const sendComplete = waitFor((resolve) => { + runtime.onConnectionEvent(clientConnectionId, (event) => { + if (event.type === CULTMESH_QUIC_EVENT_STREAM_SEND_COMPLETE) resolve(); + }); + }); + runtime.streamSendFrame(streamId, new Uint8Array([1, 2, 3, 4]), true); + await sendComplete; + + const payload = await serverFrame; + assert.deepEqual(Array.from(payload), [1, 2, 3, 4]); + + runtime.connectionShutdown(clientConnectionId, 0n); + runtime.connectionShutdown(serverConnectionId, 0n); + runtime.listenerClose(listenerId); + } finally { + await runtime.release(); + } +}); + +test("negative grep: no koffi callbacks, .async only on nextEvent", () => { + const source = readFileSync(join(__dirname, "..", "src", "realtime-quic-native.ts"), "utf8"); + const asyncUses = [...source.matchAll(/\.async\(/g)]; + assert.ok(asyncUses.length > 0, "expected at least one .async( use for nextEvent"); + for (const match of asyncUses) { + const before = source.slice(Math.max(0, match.index! - 40), match.index!); + assert.match(before, /nextEvent/, "the only .async( call site must be nextEvent"); + } + assert.doesNotMatch(source, /koffi\.register/, "no koffi callbacks"); +});