1import { describe, expect, it } from "vitest";2import {3 padSyncFrame,4 syncFrameBucket,5 unpadSyncFrame,6} from "./sync-padding";78describe("sync frame padding (E7)", () => {9 it("round-trips payloads of every shape, including empty", () => {10 for (const size of [0, 1, 5, 251, 252, 253, 1000, 70_000]) {11 const payload = Uint8Array.from({ length: size }, (_, i) => (i * 31) & 0xff);12 expect(Array.from(unpadSyncFrame(padSyncFrame(payload)))).toEqual(13 Array.from(payload),14 );15 }16 });1718 it("collapses every small control frame to one indistinguishable size", () => {19 20 21 const hello = new TextEncoder().encode('{"type":"SYNC_HELLO","vaultId":"' + "a".repeat(64) + '"}');22 const done = new TextEncoder().encode('{"type":"SYNC_DONE"}');23 expect(padSyncFrame(hello).length).toBe(padSyncFrame(done).length);24 expect(padSyncFrame(done).length).toBe(256);25 });2627 it("pins the bucket ladder — cross-platform KAT, must match peach-android", () => {28 const cases: Array<[number, number]> = [29 [0, 256],30 [252, 256],31 [253, 512],32 [1024, 2048],33 [65_532, 65_536],34 [65_533, 131_072],35 [200_000, 262_144],36 ];37 for (const [payload, expected] of cases) {38 expect(syncFrameBucket(4 + payload)).toBe(expected);39 expect(padSyncFrame(new Uint8Array(payload)).length).toBe(expected);40 }41 });4243 it("keeps large-frame overhead bounded rather than doubling", () => {44 45 46 const twelveMiB = 12 * 1024 * 1024;47 const padded = syncFrameBucket(twelveMiB);48 expect(padded).toBeGreaterThanOrEqual(twelveMiB);49 expect(padded - twelveMiB).toBeLessThan(64 * 1024);50 });5152 it("rejects a frame whose declared length exceeds what it carries", () => {53 const forged = new Uint8Array(256);54 new DataView(forged.buffer).setUint32(0, 9999, false);55 expect(() => unpadSyncFrame(forged)).toThrow(/more payload than it carries/);56 });5758 it("rejects a frame shorter than its length header", () => {59 expect(() => unpadSyncFrame(new Uint8Array(3))).toThrow(/length header/);60 });61});