import { describe, it, expect } from "vitest"; import { PAIRING_TOKEN_BYTES, encodePairingCode, formatPairingCode, parsePairingCode, } from "./pairing-code"; const token4 = Uint8Array.of(0xde, 0xad, 0xbe, 0xef); describe("pairing-code", () => { // GOLDEN VECTOR — Android's PairingCode.kt is pinned against this exact // output. Changing it is a coordinated cross-repo change, never a local edit. it("golden vector: 192.168.1.42:49142 + deadbeef -> R2M0-2ANZ-YVFA-VFQF", () => { const code = encodePairingCode("192.168.1.42", 49142, token4); expect(code).toBe("R2M0-2ANZ-YVFA-VFQF"); expect(parsePairingCode(code)).toEqual({ ip: "192.168.1.42", port: 49142, tokenHex: "deadbeef", }); }); it("is exactly 16 characters in 4 groups — the whole point of a 4-byte token", () => { const code = encodePairingCode("192.168.1.42", 49142, token4); expect(code.replace(/-/g, "")).toHaveLength(16); expect(code).toMatch(/^[0-9A-Z]{4}-[0-9A-Z]{4}-[0-9A-Z]{4}-[0-9A-Z]{4}$/); expect(PAIRING_TOKEN_BYTES).toBe(4); }); it("refuses to encode a token of any other size", () => { // An 8-byte token is what produced the 23-character, 6-group codes; Base32 // gives 5 bits per character, so there is no encoding fix — only this gate. for (const n of [3, 5, 6, 8, 10]) { expect(() => encodePairingCode("192.168.1.42", 49142, new Uint8Array(n))).toThrow(/exactly 4 bytes/); } }); it("still decodes an older 8-byte-token code so a stale peer can pair", () => { // Real code captured from an Android emulator build before the fix. expect(parsePairingCode("1800-43XF-HPH0-2EP9-A3PR-7P0")).toEqual({ ip: "10.0.2.15", port: 44941, tokenHex: "a2013ac950ed83d8", }); }); it("preserves the full port range", () => { for (const port of [1, 8765, 49142, 65535]) { const code = encodePairingCode("192.168.4.230", port, token4); expect(parsePairingCode(code)?.port).toBe(port); } }); it("folds the ambiguous glyphs I, L and O on the way in", () => { const code = encodePairingCode("192.168.1.42", 49142, token4); const ambiguous = code.replace(/0/g, "O").replace(/1/g, "I"); expect(parsePairingCode(ambiguous)).toEqual(parsePairingCode(code)); }); it("accepts a code typed without dashes, in lowercase, with stray spaces", () => { const code = encodePairingCode("192.168.1.42", 49142, token4); const sloppy = ` ${code.replace(/-/g, " ").toLowerCase()} `; expect(parsePairingCode(sloppy)).toEqual(parsePairingCode(code)); }); it("rejects codes that are too short to carry a token", () => { // 4 IP bytes + 2 port bytes and nothing else — 10 base32 characters. expect(parsePairingCode("ABCDEFGHJK")).toBeNull(); expect(parsePairingCode("")).toBeNull(); expect(parsePairingCode("---")).toBeNull(); }); it("rejects characters outside Crockford's alphabet", () => { // U is excluded by Crockford and is NOT folded, unlike I/L/O. expect(parsePairingCode("UUUU-UUUU-UUUU-UUUU")).toBeNull(); expect(parsePairingCode("A74H-48J3-KI32-OEP!")).toBeNull(); }); it("rejects a zero port", () => { // An all-zero payload is long enough but decodes to 0.0.0.0:0 — a code // that parsed would send the dialler at an unroutable endpoint. expect(parsePairingCode("0000-0000-0000-0000")).toBeNull(); }); it("refuses to encode an out-of-range address, port, or short token", () => { expect(() => encodePairingCode("192.168.1.256", 49142, token4)).toThrow(); expect(() => encodePairingCode("192.168.1", 49142, token4)).toThrow(); expect(() => encodePairingCode("192.168.1.42", 0, token4)).toThrow(); expect(() => encodePairingCode("192.168.1.42", 70000, token4)).toThrow(); expect(() => encodePairingCode("192.168.1.42", 49142, Uint8Array.of(1, 2, 3))).toThrow(); }); it("regroups partial input as the user types", () => { expect(formatPairingCode("a74h48")).toBe("A74H-48"); expect(formatPairingCode("A74H-48J3-KI32-OEP4")).toBe("A74H-48J3-KI32-OEP4"); expect(formatPairingCode("a74h 48j3")).toBe("A74H-48J3"); expect(formatPairingCode("")).toBe(""); }); });