1import { describe, expect, it } from "vitest";2import {3 CHIRP_MAGIC_BYTES,4 CHIRP_PAYLOAD_LENGTH,5 CHIRP_PROTOCOL_VERSION,6 DEVICE_TYPE_ANDROID,7 DEVICE_TYPE_EXTENSION,8} from "./sync-types";9import {10 ChirpDecodeError,11 crc32,12 decodeChirpPayload,13 encodeChirpPayload,14} from "./chirp-payload";1516function fixedPayload() {17 return {18 version: CHIRP_PROTOCOL_VERSION,19 deviceType: DEVICE_TYPE_EXTENSION,20 ephemeralPublicKey: Uint8Array.from(21 Array.from({ length: 32 }, (_, i) => (i * 7 + 11) & 0xff),22 ),23 sessionNonce: Uint8Array.from(24 Array.from({ length: 16 }, (_, i) => (i * 31 + 1) & 0xff),25 ),26 };27}2829describe("crc32", () => {30 it("matches known IEEE 802.3 vectors", () => {31 expect(crc32(new Uint8Array())).toBe(0x00000000);32 expect(crc32(new TextEncoder().encode("123456789"))).toBe(0xcbf43926);33 expect(crc32(new TextEncoder().encode("a"))).toBe(0xe8b7be43);34 });35});3637describe("chirp payload codec", () => {38 it("round-trips identity for a fixed payload", () => {39 const original = fixedPayload();40 const bytes = encodeChirpPayload(original);41 expect(bytes.length).toBe(CHIRP_PAYLOAD_LENGTH);42 const decoded = decodeChirpPayload(bytes);43 expect(decoded.version).toBe(original.version);44 expect(decoded.deviceType).toBe(original.deviceType);45 expect(decoded.ephemeralPublicKey).toEqual(original.ephemeralPublicKey);46 expect(decoded.sessionNonce).toEqual(original.sessionNonce);47 });4849 it("writes magic bytes PEAC at offset 0", () => {50 const bytes = encodeChirpPayload(fixedPayload());51 expect(bytes.subarray(0, 4)).toEqual(CHIRP_MAGIC_BYTES);52 });5354 it("zero-fills the reserved padding region", () => {55 const bytes = encodeChirpPayload(fixedPayload());56 for (let i = 58; i < 64; i++) expect(bytes[i]).toBe(0);57 });5859 it("rejects wrong-length input", () => {60 expect(() => decodeChirpPayload(new Uint8Array(63))).toThrow(ChirpDecodeError);61 expect(() => decodeChirpPayload(new Uint8Array(65))).toThrow(ChirpDecodeError);62 });6364 it("rejects bad magic", () => {65 const bytes = encodeChirpPayload(fixedPayload());66 bytes[0] = 0;67 expect(() => decodeChirpPayload(bytes)).toThrow(/magic/);68 });6970 it("rejects unsupported version", () => {71 const bytes = encodeChirpPayload(fixedPayload());72 bytes[4] = 0xff;73 74 expect(() => decodeChirpPayload(bytes)).toThrow(/version/);75 });7677 it("rejects unknown device type", () => {78 const bytes = encodeChirpPayload(fixedPayload());79 bytes[5] = 0x09;80 expect(() => decodeChirpPayload(bytes)).toThrow(/device type/);81 });8283 it("rejects checksum tamper", () => {84 const bytes = encodeChirpPayload(fixedPayload());85 bytes[10] ^= 1; 86 expect(() => decodeChirpPayload(bytes)).toThrow(/CRC32/);87 });8889 it("rejects non-zero padding", () => {90 const bytes = encodeChirpPayload(fixedPayload());91 92 93 bytes[63] = 0xab;94 const fresh = crc32(bytes.subarray(0, 54));95 bytes[54] = (fresh >>> 24) & 0xff;96 bytes[55] = (fresh >>> 16) & 0xff;97 bytes[56] = (fresh >>> 8) & 0xff;98 bytes[57] = fresh & 0xff;99 expect(() => decodeChirpPayload(bytes)).toThrow(/padding/);100 });101102 it("encodes android device type byte", () => {103 const bytes = encodeChirpPayload({ ...fixedPayload(), deviceType: DEVICE_TYPE_ANDROID });104 expect(bytes[5]).toBe(0x02);105 });106});