1import { describe, expect, it } from "vitest";2import {3 padSyncFrame,4 syncFrameBucket,5 unpadSyncFrame,6 MAX_NOISE_PLAINTEXT_BYTES,7 FRAME_CONTINUATION_MARKER,8} from "./sync-padding";910describe("sync frame padding (E7)", () => {11 it("round-trips payloads of every shape, including empty", () => {12 for (const size of [0, 1, 5, 251, 252, 253, 1000, 70_000]) {13 const payload = Uint8Array.from({ length: size }, (_, i) => (i * 31) & 0xff);14 expect(Array.from(unpadSyncFrame(padSyncFrame(payload)))).toEqual(15 Array.from(payload),16 );17 }18 });1920 it("collapses every small control frame to one indistinguishable size", () => {21 22 23 const hello = new TextEncoder().encode('{"type":"SYNC_HELLO","vaultId":"' + "a".repeat(64) + '"}');24 const done = new TextEncoder().encode('{"type":"SYNC_DONE"}');25 expect(padSyncFrame(hello).length).toBe(padSyncFrame(done).length);26 expect(padSyncFrame(done).length).toBe(256);27 });2829 it("pins the bucket ladder — cross-platform KAT, must match peach-android", () => {30 const cases: Array<[number, number]> = [31 [0, 256],32 [252, 256],33 [253, 512],34 [1024, 2048],35 [65_532, 65_536],36 [65_533, 131_072],37 [200_000, 262_144],38 ];39 for (const [payload, expected] of cases) {40 expect(syncFrameBucket(4 + payload)).toBe(expected);41 expect(padSyncFrame(new Uint8Array(payload)).length).toBe(expected);42 }43 });4445 it("keeps large-frame overhead bounded rather than doubling", () => {46 47 48 const twelveMiB = 12 * 1024 * 1024;49 const padded = syncFrameBucket(twelveMiB);50 expect(padded).toBeGreaterThanOrEqual(twelveMiB);51 expect(padded - twelveMiB).toBeLessThan(64 * 1024);52 });5354 it("rejects a frame whose declared length exceeds what it carries", () => {55 const forged = new Uint8Array(256);56 new DataView(forged.buffer).setUint32(0, 9999, false);57 expect(() => unpadSyncFrame(forged)).toThrow(/more payload than it carries/);58 });5960 it("rejects a frame shorter than its length header", () => {61 expect(() => unpadSyncFrame(new Uint8Array(3))).toThrow(/length header/);62 });63});646566676869707172describe("sync frame chunking (E7b)", () => {73 it("MAX_NOISE_PLAINTEXT_BYTES matches what the crypto core allows", () => {74 75 76 77 expect(MAX_NOISE_PLAINTEXT_BYTES).toBe(65519);78 });7980 it("the bucket ladder can produce frames above the Noise limit — the bug", () => {81 82 83 84 85 86 87 const payload = Uint8Array.from({ length: 32 * 1024 }, () => 0);88 const padded = padSyncFrame(payload);89 expect(padded.byteLength).toBeGreaterThan(MAX_NOISE_PLAINTEXT_BYTES);90 });9192 it("the continuation marker is the byte the chunking protocol uses", () => {93 94 95 96 expect(FRAME_CONTINUATION_MARKER).toBe(0x01);97 });98});