SnowLuma v2.4.2 source snapshot
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run

This commit is contained in:
TanTan committed 2026-11-13 09:02:00 +08:00
commit fa9c73363d
1367 files changed
+355735

No files matched your search

@@ -0,0 +1,94 @@
import { describe, expect, it } from 'vitest';
import { schemaCodecs } from '../schema';
const bytesSchema = 'interface Msg { data: pb<1,bytes>; }';
describe('encoded buffers', () => {
it('round-trips messages around allocation and slab boundaries', () => {
const codec = schemaCodecs<{ data: Uint8Array }>(bytesSchema);
for (const length of [
0, 1, 61, 62, 63, 64, 65, 125, 127, 128, 4092, 4093, 4094, 4095, 4096, 8192,
]) {
const data = Uint8Array.from({ length }, (_, i) => (i * 13) & 255);
const encoded = codec.encode({ data });
expect(codec.decode(encoded)).toEqual({ data });
if (encoded.length <= 64 || encoded.length > 4096) {
expect(encoded.byteOffset).toBe(0);
expect(encoded.buffer.byteLength).toBe(encoded.length);
}
}
});
it('keeps retained results intact across slab rotation and subsequent mutations', () => {
const codec = schemaCodecs<{ data: Uint8Array }>(bytesSchema);
const retained = Array.from({ length: 200 }, (_, index) => {
const data = new Uint8Array([63, 128, 800, 1300][index % 4]).fill(index);
const encoded = codec.encode({ data });
return { data, encoded, snapshot: encoded.slice() };
});
expect(retained.some(({ encoded }) => encoded.byteOffset > 0)).toBe(true);
for (const { data, encoded, snapshot } of retained) {
expect(encoded).toEqual(snapshot);
expect(codec.decode(encoded)).toEqual({ data });
}
retained[0].encoded.fill(0);
for (const { encoded, snapshot } of retained.slice(1)) {
expect(encoded).toEqual(snapshot);
}
});
it('supports encoding from a getter without reusing the inner result', () => {
const codec = schemaCodecs<{ data: Uint8Array }>(bytesSchema);
const inner = new Uint8Array(128).fill(7);
const outer = new Uint8Array(256).fill(9);
let nested: Uint8Array | undefined;
const encoded = codec.encode({
get data() {
nested = codec.encode({ data: inner });
return outer;
},
});
expect(codec.decode(encoded)).toEqual({ data: outer });
expect(codec.decode(nested!)).toEqual({ data: inner });
});
it('replaces a slab whose backing buffer was transferred', () => {
const codec = schemaCodecs<{ data: Uint8Array }>(bytesSchema);
const data = new Uint8Array(128).fill(3);
const first = codec.encode({ data });
const transferred = structuredClone(first, { transfer: [first.buffer as ArrayBuffer] });
expect(first.byteLength).toBe(0);
expect(codec.decode(transferred)).toEqual({ data });
expect(codec.decode(codec.encode({ data }))).toEqual({ data });
});
it('decodes unaligned pooled messages containing strings, fixed values and nested data', () => {
const codec = schemaCodecs<{
text: string;
ratio: number;
inner: { value: bigint; data: Uint8Array };
}>(`
interface Inner {value:pb<1,fixed_64>;data:pb<2,bytes>;}
interface Msg {text:pb<1,string>;ratio:pb<2,double>;inner:pb<3,Inner>;}
`);
let unaligned = false;
for (let i = 0; i < 100; i++) {
const value = {
text: '中文🚀'.repeat(8) + i,
ratio: Math.PI + i,
inner: { value: (1n << 63n) + BigInt(i), data: new Uint8Array(17 + i).fill(i) },
};
const encoded = codec.encode(value);
unaligned ||= encoded.byteOffset % 8 !== 0;
expect(codec.decode(encoded)).toEqual(value);
}
expect(unaligned).toBe(true);
});
});
@@ -0,0 +1,245 @@
import { describe, it, expect } from 'vitest';
import { schemaRegistry } from '../schema';
import { generateCode } from '../../../src/codegen/generator';
const simple = 'interface SimpleMsg { value: pb<1, uint_32>; }';
const nested =
'interface Inner { value: pb<1, uint_32>; } interface Outer { inner: pb<1, Inner>; }';
const string = 'interface StringMsg { text: pb<1, string>; }';
const bool = 'interface BoolMsg { active: pb<1, bool>; }';
const repeated =
'interface RepeatedMsg { ids: pb_repeated<1, uint_32>; names: pb_repeated<2, string>; }';
function makeRoundTrip(schema: string, msgName: string) {
const gen = generateCode(schemaRegistry(schema, `${msgName}.ts`));
return new Function(
gen + `\nreturn { enc: protobuf_encode_${msgName}, dec: protobuf_decode_${msgName} };`,
)() as {
enc: (obj: any) => Uint8Array;
dec: (data: Uint8Array) => any;
};
}
describe('code generator', () => {
it('allocates one output buffer for nested repeated messages', () => {
const schema = `interface Leaf { text: pb<1,string>; } interface Msg { children: pb_repeated<1,Leaf>; }`;
const registry = schemaRegistry(schema, 'allocation.ts');
const code = generateCode(registry, { encodeRoots: ['Msg'], decodeRoots: [] });
let allocations = 0;
class CountingBytes extends Uint8Array {
constructor(size: number) {
super(size);
allocations++;
}
static get [Symbol.species]() {
return Uint8Array;
}
}
const encode = new Function('Uint8Array', code + '\nreturn protobuf_encode_Msg;')(
CountingBytes,
);
const input = { children: Array.from({ length: 32 }, (_, i) => ({ text: '你好' + i })) };
const encoded = encode(input);
expect(allocations).toBe(1);
expect(makeRoundTrip(schema, 'Msg').dec(encoded)).toEqual(input);
});
it('omits unused helpers and decoder functions from encode-only output', () => {
const code = generateCode(
schemaRegistry('interface Msg { value: pb<1,uint_32>; }', 'minimal.ts'),
{
encodeRoots: ['Msg'],
decodeRoots: [],
},
);
expect(code).not.toContain('TextDecoder');
expect(code).not.toContain('TextEncoder');
expect(code).not.toContain('DataView');
expect(code).not.toContain('BigInt');
expect(code).not.toContain('protobuf_decode_');
});
it('round-trips a deep mixture of strings and nested messages', () => {
const schema = Array.from(
{ length: 24 },
(_, i) =>
`interface M${i} { text: pb<1,string>; ${i < 23 ? `next: pb<2,M${i + 1}>;` : ''} }`,
).join('\n');
let input: any = { text: 'end' };
for (let i = 22; i >= 0; i--) {
input = { text: 'level' + i, next: input };
}
const { enc, dec } = makeRoundTrip(schema, 'M0');
expect(dec(enc(input))).toEqual(input);
});
it('round-trips int_32 and preserves five-byte negative varints', () => {
const { enc, dec } = makeRoundTrip(
`interface IntMsg {
value: pb<1, int_32>;
values: pb_repeated<2, int_32>;
}`,
'IntMsg',
);
expect([...enc({ value: -1 })]).toEqual([0x08, 0xff, 0xff, 0xff, 0xff, 0x0f]);
const input = { value: -2147483648, values: [-1, -2147483648, 0, 2147483647] };
expect(dec(enc(input))).toEqual(input);
});
it('preserves fixed_32 unsigned values and sfixed_32 signed values', () => {
const { enc, dec } = makeRoundTrip(
`interface FixedMsg {
unsigned: pb<1, fixed_32>;
signed: pb<2, sfixed_32>;
values: pb_repeated<3, fixed_32>;
}`,
'FixedMsg',
);
const input = {
unsigned: 0xffffffff,
signed: -2147483648,
values: [0x80000000, 0xffffffff],
};
expect(dec(enc(input))).toEqual(input);
});
it('uses bigint defaults for all 64-bit integer fields', () => {
const { enc, dec } = makeRoundTrip(
`interface Defaults {
unsigned: pb<1, uint_64>;
signed: pb<2, int_64>;
zigzag: pb<3, sint_64>;
fixed: pb<4, fixed_64>;
sfixed: pb<5, sfixed_64>;
}`,
'Defaults',
);
const result = dec(new Uint8Array());
expect(result).toEqual({ unsigned: 0n, signed: 0n, zigzag: 0n, fixed: 0n, sfixed: 0n });
expect(enc(result)).toEqual(new Uint8Array());
});
it('decodes tags with the highest valid field number', () => {
const { enc, dec } = makeRoundTrip(
'interface HighTag { value: pb<536870911, uint_32>; }',
'HighTag',
);
const bytes = new Uint8Array([0xf8, 0xff, 0xff, 0xff, 0x0f, 7]);
expect(enc({ value: 7 })).toEqual(bytes);
expect(dec(bytes)).toEqual({ value: 7 });
});
it('generates encode/decode for simple uint_32', () => {
const gen = generateCode(schemaRegistry(simple, 't.ts'));
expect(gen).toContain('function protobuf_encode_SimpleMsg(obj)');
expect(gen).toContain('function protobuf_decode_SimpleMsg(data');
// Tag for field 1, varint should be pre-computed as 0x08
expect(gen).toContain('buf[offset++] = 8;');
});
it('generates nested message code', () => {
const gen = generateCode(schemaRegistry(nested, 't.ts'));
expect(gen).toContain('protobuf_encode_Inner(');
expect(gen).toContain('protobuf_decode_Inner(data, offset, offset + _len)');
});
it('round-trip uint_32', () => {
const { enc, dec } = makeRoundTrip(simple, 'SimpleMsg');
expect(dec(enc({ value: 42 })).value).toBe(42);
});
it('round-trip nested message', () => {
const { enc, dec } = makeRoundTrip(nested, 'Outer');
expect(dec(enc({ inner: { value: 999 } })).inner.value).toBe(999);
});
it('round-trip string', () => {
const { enc, dec } = makeRoundTrip(string, 'StringMsg');
expect(dec(enc({ text: 'hello world' })).text).toBe('hello world');
});
it('round-trip unicode string', () => {
const { enc, dec } = makeRoundTrip(string, 'StringMsg');
expect(dec(enc({ text: '你好,protobuf🚀' })).text).toBe('你好,protobuf🚀');
});
it('round-trip bool', () => {
const { enc, dec } = makeRoundTrip(bool, 'BoolMsg');
expect(dec(enc({ active: true })).active).toBe(true);
const empty = enc({ active: false });
expect(empty.length).toBe(0);
expect(dec(empty).active).toBe(false);
});
it('proto3 default encoding (0 → empty)', () => {
const { enc } = makeRoundTrip(simple, 'SimpleMsg');
expect(enc({ value: 0 }).length).toBe(0);
});
it('round-trip repeated uint_32', () => {
const { enc, dec } = makeRoundTrip(repeated, 'RepeatedMsg');
const result = dec(enc({ ids: [10, 20, 30], names: [] }));
expect(result.ids).toEqual([10, 20, 30]);
expect(result.names).toEqual([]);
});
it('round-trip repeated string', () => {
const { enc, dec } = makeRoundTrip(repeated, 'RepeatedMsg');
const result = dec(enc({ ids: [], names: ['hello', 'world'] }));
expect(result.ids).toEqual([]);
expect(result.names).toEqual(['hello', 'world']);
});
it('repeated empty arrays round-trip', () => {
const { enc, dec } = makeRoundTrip(repeated, 'RepeatedMsg');
const result = dec(enc({ ids: [], names: [] }));
expect(result.ids).toEqual([]);
expect(result.names).toEqual([]);
});
it('round-trip bigint-backed 64-bit integers', () => {
const schema = `
interface BigIntMsg {
unsigned: pb<1, uint_64>;
signed: pb<2, int_64>;
zigzag: pb<3, sint_64>;
fixed: pb<4, fixed_64>;
sfixed: pb<5, sfixed_64>;
ids: pb_repeated<6, uint_64>;
}`;
const { enc, dec } = makeRoundTrip(schema, 'BigIntMsg');
const input = {
unsigned: 18446744073709551615n,
signed: -1n,
zigzag: -1234567890123456789n,
fixed: 0x0102030405060708n,
sfixed: -0x0102030405060708n,
ids: [1n, 2n, 9007199254740993n],
};
expect(dec(enc(input))).toEqual(input);
});
it('round-trip sint_32 with negative values', () => {
const schema = `
interface Sint32Msg {
value: pb<1, sint_32>;
values: pb_repeated<2, sint_32>;
}`;
const { enc, dec } = makeRoundTrip(schema, 'Sint32Msg');
const input = { value: -789, values: [-1, 0, 1, -789, 2147483647, -2147483648] };
expect(dec(enc(input))).toEqual(input);
});
it('round-trip double as ieee754 number', () => {
const schema = `
interface DoubleMsg {
value: pb<1, double>;
values: pb_repeated<2, double>;
}`;
const { enc, dec } = makeRoundTrip(schema, 'DoubleMsg');
const result = dec(enc({ value: Math.PI, values: [Math.E, -0.5] }));
expect(result.value).toBeCloseTo(Math.PI);
expect(result.values[0]).toBeCloseTo(Math.E);
expect(result.values[1]).toBeCloseTo(-0.5);
});
});
@@ -0,0 +1,212 @@
import { deepStrictEqual } from 'node:assert';
import { describe, it, expect } from 'vitest';
import { schemaRegistry } from '../schema';
import { generateCode } from '../../../src/codegen/generator';
const code = generateCode(
schemaRegistry(`
interface Text { value: pb<1,string>; }
interface Nested { texts: pb_repeated<1,Text>; }
`),
);
const { encode, decode, encodeNested, decodeNested } = new Function(`${code}
return {
encode: protobuf_encode_Text, decode: protobuf_decode_Text,
encodeNested: protobuf_encode_Nested, decodeNested: protobuf_decode_Nested,
};`)() as {
decode(bytes: Uint8Array): { value: string };
encode(value: { value: string }): Uint8Array;
encodeNested(value: { texts: { value: string }[] }): Uint8Array;
decodeNested(bytes: Uint8Array): { texts: { value: string }[] };
};
const encoder = new TextEncoder();
const decoder = new TextDecoder();
function wire(payload: Uint8Array): Uint8Array {
const prefix = [10];
let length = payload.length;
do {
prefix.push((length & 127) | (length >= 128 ? 128 : 0));
length >>>= 7;
} while (length);
return Uint8Array.from([...prefix, ...payload]);
}
describe('UTF-8 codecs', () => {
it('matches TextEncoder across native encoding boundaries, including unpaired surrogates', () => {
for (const length of [31, 32, 33, 42, 43, 63, 64, 65, 127, 128, 129, 4096]) {
for (const unit of ['a', 'é', '中文', '🚀', '\ud800', '\udc00', 'a\ud800']) {
const value = unit.repeat(length);
// Generic iterable equality repeats per-byte work on these large views.
deepStrictEqual(encode({ value }), wire(encoder.encode(value)));
}
}
});
it('preserves every ASCII byte across short and native decoding boundaries', () => {
for (const length of [
0, 1, 3, 4, 7, 8, 15, 16, 23, 24, 30, 31, 32, 33, 63, 64, 127, 128, 4096,
]) {
for (let byte = 0; byte < 128; byte++) {
const value = String.fromCharCode(byte).repeat(length);
deepStrictEqual(
encode({ value }),
length ? wire(encoder.encode(value)) : new Uint8Array(),
);
expect(decode(wire(encoder.encode(value)))).toEqual({ value });
}
}
});
it('matches TextDecoder for Unicode after ASCII prefixes and at slice boundaries', () => {
const suffixes = [
'\u0080',
'é',
'\u07ff',
'\u0800',
'中文',
'\ud7ff',
'\ue000',
'\uffff',
'🚀',
'\u{10000}',
'\u{10ffff}',
'\ufeff',
'\ufeffx\ufeff',
'\ud800',
'\udc00',
];
for (let length = 0; length <= 64; length++) {
for (const suffix of suffixes) {
const value = 'a'.repeat(length) + suffix;
const payload = encoder.encode(value);
const bytes = wire(payload);
const padded = Uint8Array.from([255, 255, ...bytes, 128, 128]);
expect(decode(padded.subarray(2, 2 + bytes.length)).value).toBe(
decoder.decode(payload),
);
}
}
});
it('matches replacement behavior for all one-byte and two-byte sequences', () => {
const payload = new Uint8Array(2);
for (let first = 0; first < 256; first++) {
payload[0] = first;
const one = payload.subarray(0, 1);
expect(decode(wire(one)).value).toBe(decoder.decode(one));
for (let second = 0; second < 256; second++) {
payload[1] = second;
expect(decode(wire(payload)).value).toBe(decoder.decode(payload));
}
}
});
it('matches TextDecoder across randomized valid Unicode strings', () => {
let state = 0x5a17cafe;
for (let sample = 0; sample < 5000; sample++) {
let value = 'a'.repeat(sample % 17);
for (let index = 0; index < sample % 21; index++) {
state = (Math.imul(state, 1664525) + 1013904223) >>> 0;
value += String.fromCodePoint(state % 0x110000);
}
const payload = encoder.encode(value);
expect(decode(wire(payload)).value).toBe(decoder.decode(payload));
}
});
it('matches TextDecoder for invalid and truncated multibyte sequences', () => {
const sequences = [
[0xe0, 0x80, 0x80],
[0xed, 0xa0, 0x80],
[0xf0, 0x80, 0x80, 0x80],
[0xf4, 0x90, 0x80, 0x80],
[0xf5, 0x80, 0x80, 0x80],
[0xff],
[0xc2],
[0xe1, 0x80],
[0xf1, 0x80, 0x80],
[0xe1, 65, 0x80],
[0xf1, 0x80, 65, 0x80],
[0xef, 0xbb, 0xbf, 0xff],
];
for (let length = 0; length <= 33; length++) {
for (const sequence of sequences) {
const payload = Uint8Array.from([...Array(length).fill(65), ...sequence]);
expect(decode(wire(payload)).value).toBe(decoder.decode(payload));
}
}
let state = 0x42abcdef;
for (let sample = 0; sample < 5000; sample++) {
const payload = Uint8Array.from({ length: sample % 65 }, () => {
state = (Math.imul(state, 1664525) + 1013904223) >>> 0;
return state >>> 24;
});
expect(decode(wire(payload)).value).toBe(decoder.decode(payload));
}
});
it('keeps nested and repeated strings independent', () => {
const texts = [
'',
'ascii',
'a'.repeat(31),
'a'.repeat(32),
'中文🚀',
'a\ufeffb',
'\ufeffx',
'中文🚀'.repeat(64),
].map((value) => ({ value }));
expect(decodeNested(encodeNested({ texts }))).toEqual({
texts: texts.map(({ value }) => ({ value: decoder.decode(encoder.encode(value)) })),
});
// A truncated UTF-8 sequence must not consume bytes from the following field.
const first = wire(Uint8Array.from([0xf0, 0x9f]));
const second = wire(encoder.encode('next'));
const nested = Uint8Array.from([10, first.length, ...first, 10, second.length, ...second]);
expect(decodeNested(nested)).toEqual({ texts: [{ value: '�' }, { value: 'next' }] });
});
it('measures UTF-8 correctly when a code point crosses a scratch-buffer boundary', () => {
for (const length of [4093, 4094, 4095, 4096, 4097, 8191]) {
for (const suffix of ['é', '中', '🚀', '\ud800', '\udc00', '\ud800x']) {
const value = 'a'.repeat(length) + suffix + '中文🚀'.repeat(4000);
deepStrictEqual(encode({ value }), wire(encoder.encode(value)));
}
}
});
it('preserves replacement and BOM behavior after long malformed inputs', () => {
for (const length of [127, 128, 129, 255, 4095, 4096, 4097]) {
for (const sequence of [
[0xff],
[0xe0, 0x80, 0x80],
[0xed, 0xa0, 0x80],
[0xf0, 0x9f],
[0xef, 0xbb, 0xbf],
]) {
for (const prefix of [[], [0xef, 0xbb, 0xbf]]) {
const payload = Uint8Array.from([
...prefix,
...Array(length).fill(65),
...sequence,
]);
expect(decode(wire(payload)).value).toBe(decoder.decode(payload));
const valid = encoder.encode('中文🚀'.repeat(64));
expect(decode(wire(valid)).value).toBe(decoder.decode(valid));
}
}
}
});
});
@@ -0,0 +1,202 @@
import { describe, it, expect } from 'vitest';
import { schemaCodecs as codecs } from '../schema';
describe('generated wire format', () => {
it('matches TextEncoder at UTF-8 length-prefix boundaries', () => {
const codec = codecs<{ value: string }>('interface Msg {value:pb<1,string>;}');
for (const length of [127, 128, 16383, 16384]) {
for (const value of ['a'.repeat(length), '界🚀\ud800'.repeat(length)]) {
const payload = new TextEncoder().encode(value);
const prefix = [10];
let remaining = payload.length;
do {
prefix.push((remaining % 128) | (remaining >= 128 ? 128 : 0));
remaining = Math.floor(remaining / 128);
} while (remaining);
const expected = new Uint8Array(prefix.length + payload.length);
expected.set(prefix);
expected.set(payload, prefix.length);
expect(codec.encode({ value })).toEqual(expected);
expect(codec.decode(expected)).toEqual({
value: new TextDecoder().decode(payload),
});
}
}
}, 30_000);
it('decodes floating-point, fixed-width and byte fields from unaligned subarrays', () => {
const codec = codecs<{ f: number; d: number; fixed: bigint; data: Uint8Array }>(
`interface Msg {f:pb<1,float>;d:pb<2,double>;fixed:pb<3,fixed_64>;data:pb<4,bytes>;}`,
);
const input = {
f: Math.fround(1 / 3),
d: -Number.MAX_VALUE,
fixed: (1n << 64n) - 1n,
data: new Uint8Array([0, 1, 255]),
};
const wire = codec.encode(input);
for (const padding of [1, 3, 7, 31]) {
const buffer = new Uint8Array(wire.length + padding + 5).fill(255);
buffer.set(wire, padding);
expect(codec.decode(buffer.subarray(padding, padding + wire.length))).toEqual(input);
}
});
it('matches an independent bigint varint reference at every byte boundary', () => {
const codec = codecs<{ value: bigint }>('interface Msg { value: pb<1, uint_64>; }');
const values = new Set<bigint>([0n, 1n, (1n << 64n) - 1n]);
for (let bit = 1n; bit < 64n; bit++) {
values.add((1n << bit) - 1n);
values.add(1n << bit);
values.add((1n << bit) + 1n);
}
for (const value of values) {
let rest = value;
const bytes = [];
do {
bytes.push(Number(rest & 127n) | (rest > 127n ? 128 : 0));
rest >>= 7n;
} while (rest);
const wire = new Uint8Array([8, ...bytes]);
expect(codec.encode({ value })).toEqual(value === 0n ? new Uint8Array() : wire);
expect(codec.decode(wire)).toEqual({ value });
}
});
it('decodes nonzero booleans whose low 32 bits are zero', () => {
const codec = codecs<{ value: boolean }>('interface Msg { value: pb<1, bool>; }');
expect(codec.decode(new Uint8Array([8, 128, 128, 128, 128, 16]))).toEqual({ value: true });
});
const uint32 = codecs<{ value: number }>('interface Msg { value: pb<1, uint_32>; }');
const varints: [number, number[]][] = [
[1, [0x01]],
[127, [0x7f]],
[128, [0x80, 0x01]],
[300, [0xac, 0x02]],
[16384, [0x80, 0x80, 0x01]],
[0xffffffff, [0xff, 0xff, 0xff, 0xff, 0x0f]],
];
it.each(varints)('encodes uint32 %i using the expected wire bytes', (value, bytes) => {
expect([...uint32.encode({ value })]).toEqual([0x08, ...bytes]);
});
it.each(varints)('decodes uint32 %i from independent wire bytes', (value, bytes) => {
expect(uint32.decode(new Uint8Array([0x08, ...bytes]))).toEqual({ value });
});
it('omits scalar zero and decodes an explicit zero', () => {
expect(uint32.encode({ value: 0 })).toEqual(new Uint8Array());
expect(uint32.decode(new Uint8Array([8, 0]))).toEqual({ value: 0 });
});
it.each(['hello', '', '你好', '🚀', '\ud800', '\udc00'])('encodes UTF-8 %j', (value) => {
const codec = codecs<{ value: string }>('interface Msg { value: pb<1, string>; }');
const payload = new TextEncoder().encode(value);
const bytes = new Uint8Array([0x0a, payload.length, ...payload]);
expect(codec.encode({ value })).toEqual(value === '' ? new Uint8Array() : bytes);
expect(codec.decode(bytes)).toEqual({ value: new TextDecoder().decode(payload) });
});
it('encodes bytes with a length prefix and copies the decoded data', () => {
const codec = codecs<{ value: Uint8Array }>('interface Msg { value: pb<1, bytes>; }');
const bytes = new Uint8Array([10, 3, 1, 128, 255]);
const value = new Uint8Array([1, 128, 255]);
expect(codec.encode({ value })).toEqual(bytes);
const result = codec.decode(bytes);
bytes[2] = 0;
expect(result.value).toEqual(value);
});
it.each([
['varint', [0x10, 0xac, 0x02]],
['64-bit', [0x11, 1, 2, 3, 4, 5, 6, 7, 8]],
['length-delimited', [0x12, 3, 0x41, 0x42, 0x43]],
['32-bit', [0x15, 1, 2, 3, 4]],
])('skips unknown %s fields and resumes decoding', (_name, bytes) => {
expect(uint32.decode(new Uint8Array([...(bytes as number[]), 8, 7]))).toEqual({ value: 7 });
});
const packedCases: [string, (number | bigint | boolean)[], number[]][] = [
['uint_32', [1, 300], [1, 0xac, 2]],
['int_32', [1, -2], [1, 0xfe, ...Array(8).fill(0xff), 1]],
['sint_32', [-1, 1], [1, 2]],
['bool', [true, false], [1, 0]],
['uint_64', [1n, 300n], [1, 0xac, 2]],
['int_64', [1n, -2n], [1, 0xfe, ...Array(8).fill(0xff), 1]],
['sint_64', [-1n, 1n], [1, 2]],
['fixed_32', [0xffffffff, 1], [255, 255, 255, 255, 1, 0, 0, 0]],
['sfixed_32', [-1, 1], [255, 255, 255, 255, 1, 0, 0, 0]],
['fixed_64', [1n], [1, 0, 0, 0, 0, 0, 0, 0]],
['sfixed_64', [-1n], Array(8).fill(255)],
['float', [1], [0, 0, 128, 63]],
['double', [1], [0, 0, 0, 0, 0, 0, 240, 63]],
];
it.each(packedCases)(
'decodes packed, unpacked and split %s arrays',
(type, values, payload) => {
const codec = codecs<{ values: typeof values }>(
`interface Msg { values: pb_repeated<1, ${type}>; }`,
);
const packed = [10, payload.length, ...payload];
const unpacked = codec.encode({ values });
expect(codec.decode(new Uint8Array(packed))).toEqual({ values });
expect(codec.decode(new Uint8Array([...packed, ...unpacked, ...packed]))).toEqual({
values: [...values, ...values, ...values],
});
expect(codec.decode(new Uint8Array([10, 0]))).toEqual({ values: [] });
},
);
const malformedCodec = codecs(`
interface Inner { value: pb<1, uint_32>; }
interface Msg {
value: pb<1, uint_32>;
big: pb<2, uint_64>;
text: pb<3, string>;
data: pb<4, bytes>;
float: pb<5, float>;
double: pb<6, double>;
inner: pb<7, Inner>;
values: pb_repeated<8, uint_32>;
fixed: pb_repeated<9, fixed_32>;
}
`);
const invalidCases: [string, number[]][] = [
['zero tag', [0]],
['invalid wire type', [15]],
['truncated tag', [128]],
['overflowing tag', [255, 255, 255, 255, 31]],
['missing varint', [8]],
['unterminated varint', [8, 128]],
['overflowing varint', [8, ...Array(10).fill(255)]],
['truncated bigint', [16, 128]],
['overflowing bigint', [16, ...Array(9).fill(255), 2]],
['missing string length', [26]],
['truncated string', [26, 2, 65]],
['overflowing length', [26, 255, 255, 255, 255, 31]],
['truncated bytes', [34, 2, 1]],
['truncated float', [45, 0]],
['truncated double', [49, 0]],
['nested message exceeds parent', [58, 5, 8, 1]],
['nested varint exceeds nested boundary', [58, 2, 8, 128, 8, 7]],
['packed varint exceeds packed boundary', [66, 1, 128, 8, 7]],
['packed fixed32 has partial value', [74, 3, 1, 2, 3, 8, 7]],
['unknown truncated varint', [80, 128]],
['unknown truncated fixed64', [81, 0]],
['unknown truncated bytes', [82, 5, 1]],
['unknown truncated fixed32', [85, 0]],
];
it.each(invalidCases)('rejects %s', (_name, bytes) => {
expect(() => malformedCodec.decode(new Uint8Array(bytes))).toThrow(/protobuf/);
});
});