1import { beforeEach, describe, expect, it, vi } from "vitest";23import { dialPairingChannel, type PairingSocket } from "./pairing-dial";4import { encodePairingCode } from "./pairing-code";56const enc = (o: unknown) => new TextEncoder().encode(JSON.stringify(o));7const dec = (b: Uint8Array) => JSON.parse(new TextDecoder().decode(b));89const LISTENER_EPHEMERAL_HEX = "a".repeat(64);10const CODE = encodePairingCode("192.168.4.77", 51555, Uint8Array.of(0xde, 0xad, 0xbe, 0xef));1112const FAKE_HANDSHAKE_HASH = Uint8Array.from([0x0f, 0xba, 0xb8, 0xcd, ...new Array(28).fill(0)]);13141516vi.mock("./noise-js", async (orig) => {17 const actual = await orig<typeof import("./noise-js")>();18 return {19 ...actual,20 noiseNKInitiator: vi.fn(async () => ({21 encrypt: (pt: Uint8Array) => pt,22 decrypt: (ct: Uint8Array) => ct,23 handshakeHash: FAKE_HANDSHAKE_HASH,24 })),25 };26});272829function fakeSocket(inbound: Array<Uint8Array | null>) {30 const sent: Uint8Array[] = [];31 let closed = false;32 let lastWasClose = false;33 const queue = [...inbound];34 const sock: PairingSocket = {35 send(bytes) { sent.push(bytes); },36 async receive() {37 const next = queue.shift();38 if (next === undefined || next === null) {39 closed = true;40 lastWasClose = true;41 throw new Error("socket closed");42 }43 lastWasClose = false;44 return next;45 },46 close() { closed = true; },47 closedBeforeFrame() { return lastWasClose; },48 };49 return { sock, sent, isClosed: () => closed };50}5152function ackThen(...rest: Array<Uint8Array | null>) {53 return fakeSocket([54 enc({ type: "PEACH_HELLO_ACK", ephemeralPublicKeyHex: LISTENER_EPHEMERAL_HEX }),55 ...rest,56 ]);57}5859beforeEach(() => { vi.clearAllMocks(); });6061describe("dialPairingChannel", () => {62 it("dials the coded endpoint", async () => {63 const openSocket = vi.fn(async () => ackThen().sock);6465 const result = await dialPairingChannel(CODE, { openSocket });6667 expect(result.ok).toBe(true);68 expect(openSocket).toHaveBeenCalledWith("192.168.4.77", 51555);69 });7071 it("presents the token as hex and returns the shared SAS", async () => {72 const s = ackThen();73 const result = await dialPairingChannel(CODE, { openSocket: async () => s.sock });7475 expect(dec(s.sent[0])).toEqual({ type: "PEACH_HELLO", token: "deadbeef" });76 expect(result.ok && result.dialled.sas).toBe("895245");77 expect(result.ok && result.dialled.host).toBe("192.168.4.77");78 expect(result.ok && result.dialled.rendezvousSecretHex).toBe(79 "a8977cd86e944e72e9cb569dbba00a7b0abbf7f456c745299aa8f66157859143",80 );81 });8283 it("rejects an addressless legacy code before opening a socket", async () => {84 const openSocket = vi.fn(async () => ackThen().sock);8586 const result = await dialPairingChannel("123456", { openSocket });8788 expect(result).toEqual({ ok: false, error: "invalid_code" });89 expect(openSocket).not.toHaveBeenCalled();90 });9192 it("reads a silent close as invalid_code, not a transport failure", async () => {93 94 const s = fakeSocket([null]);95 const result = await dialPairingChannel(CODE, { openSocket: async () => s.sock });9697 expect(result).toEqual({ ok: false, error: "invalid_code" });98 expect(s.isClosed()).toBe(true);99 });100101 it("treats a non-ACK first frame exactly like a wrong token", async () => {102 const s = fakeSocket([enc({ type: "SOMETHING_ELSE" })]);103 const result = await dialPairingChannel(CODE, { openSocket: async () => s.sock });104105 expect(result).toEqual({ ok: false, error: "invalid_code" });106 });107108 it("rejects an ACK whose ephemeral key is not 32-byte hex", async () => {109 const s = fakeSocket([enc({ type: "PEACH_HELLO_ACK", ephemeralPublicKeyHex: "nope" })]);110 const result = await dialPairingChannel(CODE, { openSocket: async () => s.sock });111112 expect(result).toEqual({ ok: false, error: "handshake_failed" });113 expect(s.isClosed()).toBe(true);114 });115116 it("reports no_host_found when the endpoint refuses the connection", async () => {117 const result = await dialPairingChannel(CODE, {118 openSocket: async () => { throw new Error("ECONNREFUSED"); },119 });120121 expect(result).toEqual({ ok: false, error: "no_host_found" });122 });123124 it("leaves the socket open on success so the caller owns its lifetime", async () => {125 const s = ackThen(enc({ hello: "world" }));126 const result = await dialPairingChannel(CODE, { openSocket: async () => s.sock });127 if (!result.ok) throw new Error("expected a dialled channel");128129 expect(s.isClosed()).toBe(false);130 await result.dialled.channel.send(enc({ type: "ANYTHING" }));131 132 expect(dec(s.sent[1])).toEqual({ type: "ANYTHING" });133 expect(dec(await result.dialled.channel.receive())).toEqual({ hello: "world" });134135 result.dialled.socket.close();136 expect(s.isClosed()).toBe(true);137 });138});