Skip to content

Commit 203450f

Browse files
committed
replace leb dep with vanilla implementation
1 parent 33f1c70 commit 203450f

File tree

6 files changed

+401
-9
lines changed

6 files changed

+401
-9
lines changed

package-lock.json

Lines changed: 0 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
"dependencies": {
4343
"iron-session": "^8.0.4",
4444
"jose": "~6.1.0",
45-
"leb": "^1.0.0",
4645
"pluralize": "8.0.0",
4746
"qs": "6.14.0"
4847
},

src/common/utils/leb128.spec.ts

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
import { encodeUInt32, decodeUInt32 } from './leb128';
2+
3+
describe('leb128', () => {
4+
describe('encodeUInt32', () => {
5+
describe('boundary values', () => {
6+
test('encodes 0 (1 byte: 0x00)', () => {
7+
const result = encodeUInt32(0);
8+
expect(result).toEqual(new Uint8Array([0x00]));
9+
});
10+
11+
test('encodes 127 (1 byte: 0x7F) - largest 1-byte value', () => {
12+
const result = encodeUInt32(127);
13+
expect(result).toEqual(new Uint8Array([0x7f]));
14+
});
15+
16+
test('encodes 128 (2 bytes: 0x80 0x01) - smallest 2-byte value', () => {
17+
const result = encodeUInt32(128);
18+
expect(result).toEqual(new Uint8Array([0x80, 0x01]));
19+
});
20+
21+
test('encodes 16383 (2 bytes: 0xFF 0x7F) - largest 2-byte value', () => {
22+
const result = encodeUInt32(16383);
23+
expect(result).toEqual(new Uint8Array([0xff, 0x7f]));
24+
});
25+
26+
test('encodes 16384 (3 bytes: 0x80 0x80 0x01)', () => {
27+
const result = encodeUInt32(16384);
28+
expect(result).toEqual(new Uint8Array([0x80, 0x80, 0x01]));
29+
});
30+
31+
test('encodes 2097151 (3 bytes: 0xFF 0xFF 0x7F)', () => {
32+
const result = encodeUInt32(2097151);
33+
expect(result).toEqual(new Uint8Array([0xff, 0xff, 0x7f]));
34+
});
35+
36+
test('encodes 2097152 (4 bytes: 0x80 0x80 0x80 0x01)', () => {
37+
const result = encodeUInt32(2097152);
38+
expect(result).toEqual(new Uint8Array([0x80, 0x80, 0x80, 0x01]));
39+
});
40+
41+
test('encodes 268435455 (4 bytes: 0xFF 0xFF 0xFF 0x7F)', () => {
42+
const result = encodeUInt32(268435455);
43+
expect(result).toEqual(new Uint8Array([0xff, 0xff, 0xff, 0x7f]));
44+
});
45+
46+
test('encodes 268435456 (5 bytes: 0x80 0x80 0x80 0x80 0x01)', () => {
47+
const result = encodeUInt32(268435456);
48+
expect(result).toEqual(new Uint8Array([0x80, 0x80, 0x80, 0x80, 0x01]));
49+
});
50+
51+
test('encodes 4294967295 (5 bytes: 0xFF 0xFF 0xFF 0xFF 0x0F) - MAX_UINT32', () => {
52+
const result = encodeUInt32(4294967295);
53+
expect(result).toEqual(new Uint8Array([0xff, 0xff, 0xff, 0xff, 0x0f]));
54+
});
55+
});
56+
57+
describe('common values', () => {
58+
test('encodes 42', () => {
59+
const result = encodeUInt32(42);
60+
expect(result).toEqual(new Uint8Array([0x2a]));
61+
});
62+
63+
test('encodes 300', () => {
64+
const result = encodeUInt32(300);
65+
expect(result).toEqual(new Uint8Array([0xac, 0x02]));
66+
});
67+
68+
test('encodes 1000000', () => {
69+
const result = encodeUInt32(1000000);
70+
expect(result).toEqual(new Uint8Array([0xc0, 0x84, 0x3d]));
71+
});
72+
});
73+
74+
describe('invalid inputs', () => {
75+
test('throws for negative numbers', () => {
76+
expect(() => encodeUInt32(-1)).toThrow('Value must be non-negative');
77+
});
78+
79+
test('throws for numbers > MAX_UINT32', () => {
80+
expect(() => encodeUInt32(4294967296)).toThrow('Value must not exceed 4294967295');
81+
});
82+
83+
test('throws for non-integers', () => {
84+
expect(() => encodeUInt32(3.14)).toThrow('Value must be an integer');
85+
});
86+
87+
test('throws for NaN', () => {
88+
expect(() => encodeUInt32(NaN)).toThrow('Value must be a finite number');
89+
});
90+
91+
test('throws for Infinity', () => {
92+
expect(() => encodeUInt32(Infinity)).toThrow('Value must be a finite number');
93+
});
94+
});
95+
});
96+
97+
describe('decodeUInt32', () => {
98+
describe('round-trip verification', () => {
99+
test('round-trips encode/decode for 0', () => {
100+
const encoded = encodeUInt32(0);
101+
const { value, nextIndex } = decodeUInt32(encoded);
102+
expect(value).toBe(0);
103+
expect(nextIndex).toBe(1);
104+
});
105+
106+
test('round-trips encode/decode for 127', () => {
107+
const encoded = encodeUInt32(127);
108+
const { value, nextIndex } = decodeUInt32(encoded);
109+
expect(value).toBe(127);
110+
expect(nextIndex).toBe(1);
111+
});
112+
113+
test('round-trips encode/decode for 128', () => {
114+
const encoded = encodeUInt32(128);
115+
const { value, nextIndex } = decodeUInt32(encoded);
116+
expect(value).toBe(128);
117+
expect(nextIndex).toBe(2);
118+
});
119+
120+
test('round-trips encode/decode for 16383', () => {
121+
const encoded = encodeUInt32(16383);
122+
const { value, nextIndex } = decodeUInt32(encoded);
123+
expect(value).toBe(16383);
124+
expect(nextIndex).toBe(2);
125+
});
126+
127+
test('round-trips encode/decode for 300', () => {
128+
const encoded = encodeUInt32(300);
129+
const { value, nextIndex } = decodeUInt32(encoded);
130+
expect(value).toBe(300);
131+
expect(nextIndex).toBe(2);
132+
});
133+
134+
test('round-trips encode/decode for 1000000', () => {
135+
const encoded = encodeUInt32(1000000);
136+
const { value, nextIndex } = decodeUInt32(encoded);
137+
expect(value).toBe(1000000);
138+
expect(nextIndex).toBe(3);
139+
});
140+
141+
test('round-trips encode/decode for MAX_UINT32', () => {
142+
const encoded = encodeUInt32(4294967295);
143+
const { value, nextIndex } = decodeUInt32(encoded);
144+
expect(value).toBe(4294967295);
145+
expect(nextIndex).toBe(5);
146+
});
147+
148+
test('round-trips 1000 random values between 0 and 1000000', () => {
149+
for (let i = 0; i < 1000; i++) {
150+
const original = Math.floor(Math.random() * 1000000);
151+
const encoded = encodeUInt32(original);
152+
const { value } = decodeUInt32(encoded);
153+
expect(value).toBe(original);
154+
}
155+
});
156+
});
157+
158+
describe('offset handling', () => {
159+
test('decodes from offset 0 by default', () => {
160+
const data = new Uint8Array([0x2a]); // 42
161+
const { value, nextIndex } = decodeUInt32(data);
162+
expect(value).toBe(42);
163+
expect(nextIndex).toBe(1);
164+
});
165+
166+
test('decodes from specified offset', () => {
167+
const data = new Uint8Array([0xff, 0xff, 0xac, 0x02]); // garbage, then 300
168+
const { value, nextIndex } = decodeUInt32(data, 2);
169+
expect(value).toBe(300);
170+
expect(nextIndex).toBe(4);
171+
});
172+
173+
test('returns correct nextIndex after decoding', () => {
174+
const data = encodeUInt32(128);
175+
const { nextIndex } = decodeUInt32(data);
176+
expect(nextIndex).toBe(2);
177+
});
178+
179+
test('decodes multiple values in sequence using nextIndex', () => {
180+
// Encode three values: 42, 300, 1000000
181+
const encoded1 = encodeUInt32(42);
182+
const encoded2 = encodeUInt32(300);
183+
const encoded3 = encodeUInt32(1000000);
184+
185+
// Concatenate into single buffer
186+
const buffer = new Uint8Array(
187+
encoded1.length + encoded2.length + encoded3.length,
188+
);
189+
buffer.set(encoded1, 0);
190+
buffer.set(encoded2, encoded1.length);
191+
buffer.set(encoded3, encoded1.length + encoded2.length);
192+
193+
// Decode sequentially
194+
const result1 = decodeUInt32(buffer, 0);
195+
expect(result1.value).toBe(42);
196+
197+
const result2 = decodeUInt32(buffer, result1.nextIndex);
198+
expect(result2.value).toBe(300);
199+
200+
const result3 = decodeUInt32(buffer, result2.nextIndex);
201+
expect(result3.value).toBe(1000000);
202+
});
203+
});
204+
205+
describe('invalid inputs', () => {
206+
test('throws for offset beyond buffer bounds', () => {
207+
const data = new Uint8Array([0x2a]);
208+
expect(() => decodeUInt32(data, 5)).toThrow('Offset 5 is out of bounds');
209+
});
210+
211+
test('throws for negative offset', () => {
212+
const data = new Uint8Array([0x2a]);
213+
expect(() => decodeUInt32(data, -1)).toThrow('Offset -1 is out of bounds');
214+
});
215+
216+
test('throws for truncated encoding (incomplete byte sequence)', () => {
217+
// Create a truncated encoding: 0x80 indicates more bytes follow, but none present
218+
const data = new Uint8Array([0x80]);
219+
expect(() => decodeUInt32(data)).toThrow('Truncated LEB128 encoding');
220+
});
221+
222+
test('throws for truncated multi-byte encoding', () => {
223+
// 0x80 0x80 indicates at least 3 bytes needed, but only 2 present
224+
const data = new Uint8Array([0x80, 0x80]);
225+
expect(() => decodeUInt32(data)).toThrow('Truncated LEB128 encoding');
226+
});
227+
228+
test('throws for encoding that exceeds uint32 range', () => {
229+
// 6 bytes with continuation bits (should never happen for uint32)
230+
const data = new Uint8Array([0x80, 0x80, 0x80, 0x80, 0x80, 0x01]);
231+
expect(() => decodeUInt32(data)).toThrow('LEB128 sequence exceeds maximum length for uint32');
232+
});
233+
});
234+
});
235+
236+
describe('compatibility with leb package', () => {
237+
// These tests ensure wire-format compatibility with the original leb package
238+
// If these tests pass, existing encrypted data can be decrypted after the migration
239+
240+
test('produces identical output to leb.encodeUInt32 for value 42', () => {
241+
const result = encodeUInt32(42);
242+
// Known output from leb package
243+
expect(result).toEqual(new Uint8Array([0x2a]));
244+
});
245+
246+
test('produces identical output to leb.encodeUInt32 for value 300', () => {
247+
const result = encodeUInt32(300);
248+
// Known output from leb package
249+
expect(result).toEqual(new Uint8Array([0xac, 0x02]));
250+
});
251+
252+
test('produces identical output to leb.encodeUInt32 for value 1000000', () => {
253+
const result = encodeUInt32(1000000);
254+
// Known output from leb package
255+
expect(result).toEqual(new Uint8Array([0xc0, 0x84, 0x3d]));
256+
});
257+
258+
test('decodes leb.encodeUInt32 output correctly', () => {
259+
// These are known outputs from the leb package
260+
const knownEncodings: Array<[number, number[]]> = [
261+
[0, [0x00]],
262+
[42, [0x2a]],
263+
[127, [0x7f]],
264+
[128, [0x80, 0x01]],
265+
[300, [0xac, 0x02]],
266+
[1000000, [0xc0, 0x84, 0x3d]],
267+
[4294967295, [0xff, 0xff, 0xff, 0xff, 0x0f]],
268+
];
269+
270+
for (const [expectedValue, bytes] of knownEncodings) {
271+
const { value } = decodeUInt32(new Uint8Array(bytes));
272+
expect(value).toBe(expectedValue);
273+
}
274+
});
275+
});
276+
});

0 commit comments

Comments
 (0)