1/**
2 * Sideband framing (src/Git/Protocol/Sideband.ts): the single-copy frame, the
3 * whole-chunk framer the native pump uses, and the deterministic re-chunker
4 * that writes a bundle's pre-framed twin — whose output must be a pure
5 * function of the input bytes, however the input is chunked.
6 */
7import {
8 SIDEBAND_DATA_MAX,
9 sidebandFrame,
10 sidebandFrameAll,
11 sidebandFramedLength,
12 sidebandRechunk,
13} from "@/Git/Protocol/Sideband.ts";
14import { describe, expect, test } from "alchemy-test";
15import * as Effect from "effect/Effect";
16import * as Stream from "effect/Stream";
17
18const bytes = (n: number, seed = 1) => {
19 const out = new Uint8Array(n);
20 let x = seed;
21 for (let i = 0; i < n; i++) {
22 x = (x * 1103515245 + 12345) & 0x7fffffff;
23 out[i] = x & 0xff;
24 }
25 return out;
26};
27
28/** Parses band-1 frames back into data; asserts every frame is well-formed. */
29const deframe = (framed: Uint8Array) => {
30 const pieces: Array<Uint8Array> = [];
31 let pos = 0;
32 while (pos < framed.length) {
33 const len = Number.parseInt(
34 new TextDecoder().decode(framed.subarray(pos, pos + 4)),
35 16,
36 );
37 expect(len).toBeGreaterThan(5);
38 expect(len).toBeLessThanOrEqual(SIDEBAND_DATA_MAX + 5);
39 expect(framed[pos + 4]).toBe(1);
40 pieces.push(framed.subarray(pos + 5, pos + len));
41 pos += len;
42 }
43 expect(pos).toBe(framed.length);
44 const total = pieces.reduce((n, p) => n + p.length, 0);
45 const out = new Uint8Array(total);
46 let at = 0;
47 for (const p of pieces) {
48 out.set(p, at);
49 at += p.length;
50 }
51 return { data: out, frames: pieces.map((p) => p.length) };
52};
53
54describe(
55 "sidebandFrame / sidebandFrameAll",
56 { tags: ["unit", "local"] },
57 () => {
58 test("sidebandFrame is header + data in one buffer", () => {
59 const data = bytes(10);
60 const frame = sidebandFrame(1, data);
61 expect(frame.length).toBe(15);
62 expect(new TextDecoder().decode(frame.subarray(0, 4))).toBe("000f");
63 expect(frame[4]).toBe(1);
64 expect(Array.from(frame.subarray(5))).toEqual(Array.from(data));
65 });
66
67 test("sidebandFrameAll cuts exactly at SIDEBAND_DATA_MAX and round-trips", () => {
68 const data = bytes(SIDEBAND_DATA_MAX * 2 + 123);
69 const framed = sidebandFrameAll(1, data);
70 expect(framed.length).toBe(sidebandFramedLength(data.length));
71 const back = deframe(framed);
72 expect(back.frames).toEqual([SIDEBAND_DATA_MAX, SIDEBAND_DATA_MAX, 123]);
73 expect(Array.from(back.data)).toEqual(Array.from(data));
74 });
75
76 test("sidebandFrameAll of an empty chunk is empty", () => {
77 expect(sidebandFrameAll(1, new Uint8Array(0)).length).toBe(0);
78 });
79 },
80);
81
82describe("sidebandRechunk", { tags: ["unit", "local"] }, () => {
83 const run = (chunks: ReadonlyArray<Uint8Array>) =>
84 Effect.runPromise(
85 Stream.fromIterable(chunks).pipe(
86 sidebandRechunk(1),
87 Stream.runCollect,
88 Effect.map((c) => {
89 const parts = Array.from(c);
90 const total = parts.reduce((n, p) => n + p.length, 0);
91 const out = new Uint8Array(total);
92 let at = 0;
93 for (const p of parts) {
94 out.set(p, at);
95 at += p.length;
96 }
97 return out;
98 }),
99 ),
100 );
101
102 test("output is independent of input chunking", async () => {
103 const data = bytes(SIDEBAND_DATA_MAX * 3 + 4567, 7);
104 const whole = await run([data]);
105 const tiny = await run(
106 Array.from({ length: Math.ceil(data.length / 1000) }, (_, i) =>
107 data.subarray(i * 1000, Math.min((i + 1) * 1000, data.length)),
108 ),
109 );
110 const uneven = await run([
111 data.subarray(0, 3),
112 data.subarray(3, SIDEBAND_DATA_MAX + 1),
113 data.subarray(SIDEBAND_DATA_MAX + 1),
114 ]);
115 expect(whole.length).toBe(sidebandFramedLength(data.length));
116 expect(Array.from(tiny)).toEqual(Array.from(whole));
117 expect(Array.from(uneven)).toEqual(Array.from(whole));
118 const back = deframe(whole);
119 expect(back.frames).toEqual([
120 SIDEBAND_DATA_MAX,
121 SIDEBAND_DATA_MAX,
122 SIDEBAND_DATA_MAX,
123 4567,
124 ]);
125 expect(Array.from(back.data)).toEqual(Array.from(data));
126 });
127
128 test("an exact multiple of the frame size has no short tail; empty input frames nothing", async () => {
129 const data = bytes(SIDEBAND_DATA_MAX * 2, 3);
130 const framed = await run([data.subarray(0, 100), data.subarray(100)]);
131 expect(deframe(framed).frames).toEqual([
132 SIDEBAND_DATA_MAX,
133 SIDEBAND_DATA_MAX,
134 ]);
135 expect((await run([])).length).toBe(0);
136 expect((await run([new Uint8Array(0)])).length).toBe(0);
137 });
138});