Fix three real bugs the second ygg-verify pass found

- koffi's `_Out_` sugar still counts toward the call's declared arity in this
  version (an arity check rejected the shorter call); pass `null` at each such
  slot, since the value comes back through the named field on the result
  object regardless of what was passed.
- The negative-grep test read `dist-test/src/...`, which does not exist:
  `__dirname` at runtime is `dist-test/test`, and only compiled output is
  mirrored there, so the original source is two levels up in `src/`, not one.
- Two generated Odin test roots shared one hardcoded key id
  ("odin-root-1"), so trust negative (c) had the trust policy's root looked
  up successfully under the signer's own id and failed on signature mismatch
  instead of "not trusted" — proving the wrong rule. Each generated root now
  gets a distinct key id.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-23 12:38:53 +02:00
commit 92dc3a74d6
3 changed files with 16 additions and 7 deletions

View file

@ -217,22 +217,26 @@ function loadBindings(): NativeBindings {
const { promisify } = require("node:util") as typeof import("node:util");
const nextEventAsync = promisify(nextEvent.async) as NativeBindings["nextEventAsync"];
// Every `_Out_` parameter still occupies a call-site slot in this koffi
// version (an arity check rejects a shorter call); its value is written
// back through the named field on the returned object regardless of what
// was passed here, so `null` is a safe placeholder at each such slot.
bindingsCache = {
runtimeOpen: (appName) => runtimeOpen(appName),
runtimeOpen: (appName) => runtimeOpen(appName, null),
runtimeClose: (runtime) => runtimeClose(runtime),
listenerOpen: (runtime, host, port, pkcs12, pkcs12Length, password) =>
listenerOpen(runtime, host, port, pkcs12, pkcs12Length, password),
listenerOpen(runtime, host, port, pkcs12, pkcs12Length, password, null, null),
listenerClose: (runtime, listenerId) => listenerClose(runtime, listenerId),
connectionOpen: (runtime, host, port) => connectionOpen(runtime, host, port),
connectionOpen: (runtime, host, port) => connectionOpen(runtime, host, port, null),
connectionCertificateComplete: (runtime, connectionId, accept) =>
connectionCertificateComplete(runtime, connectionId, accept),
connectionShutdown: (runtime, connectionId, code) => connectionShutdown(runtime, connectionId, code),
streamOpen: (runtime, connectionId, kind) => streamOpen(runtime, connectionId, kind),
streamOpen: (runtime, connectionId, kind) => streamOpen(runtime, connectionId, kind, null),
streamSendFrame: (runtime, streamId, encodedFrame, length, fin) =>
streamSendFrame(runtime, streamId, encodedFrame, length, fin),
streamShutdown: (runtime, streamId, code) => streamShutdown(runtime, streamId, code),
nextEventAsync: (runtime, timeoutMs, payload, payloadCapacity) =>
nextEventAsync(runtime, timeoutMs, payload, payloadCapacity) as Promise<KoffiOutResult<NativeEventStruct>>,
nextEventAsync(runtime, timeoutMs, null, payload, payloadCapacity, null) as Promise<KoffiOutResult<NativeEventStruct>>,
lastError: (runtime, destination, capacity) => lastError(runtime, destination, capacity),
lastStatus: (runtime) => lastStatus(runtime),
};

View file

@ -245,13 +245,16 @@ function base64UrlToBase64(value: string): string {
return base64;
}
let odinRootSequence = 0;
function generateOdinRoot(): { privateKey: KeyObject; publicKey: CultMeshP256PublicKey } {
const { publicKey, privateKey } = generateKeyPairSync("ec", { namedCurve: "prime256v1" });
const jwk = publicKey.export({ format: "jwk" }) as { x: string; y: string };
odinRootSequence += 1;
return {
privateKey,
publicKey: {
keyId: "odin-root-1",
keyId: `odin-root-${odinRootSequence}`,
x: base64UrlToBase64(jwk.x),
y: base64UrlToBase64(jwk.y),
},

View file

@ -137,7 +137,9 @@ test("a full loopback connection delivers one raw frame end to end", async (t) =
});
test("negative grep: no koffi callbacks, .async only on nextEvent", () => {
const source = readFileSync(join(__dirname, "..", "src", "realtime-quic-native.ts"), "utf8");
// __dirname at runtime is dist-test/test; the source lives two levels up,
// in src/, since only .js output is mirrored under dist-test.
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) {