File size: 6,089 Bytes
2b64d42 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | /**
* Protobuf wire format codec β zero-dependency, schema-less.
*
* Wire types:
* 0 = Varint (int32, uint64, bool, enum)
* 1 = Fixed64 (double, fixed64)
* 2 = LenDelim (string, bytes, embedded messages)
* 5 = Fixed32 (float, fixed32)
*/
// βββ Varint ββββββββββββββββββββββββββββββββββββββββββββββββ
export function encodeVarint(value) {
const bytes = [];
// BigInt path for negatives (two's-complement uint64) and any int > 2^31
// since JS `>>>` truncates to uint32 and silently corrupts larger varints.
if (typeof value === 'bigint' || value < 0 || value > 0x7FFFFFFF) {
let b = (typeof value === 'bigint' ? value : BigInt(value)) & 0xFFFFFFFFFFFFFFFFn;
while (true) {
const byte = Number(b & 0x7Fn);
b >>= 7n;
if (b === 0n) { bytes.push(byte); break; }
bytes.push(byte | 0x80);
}
return Buffer.from(bytes);
}
let v = Number(value);
do {
let byte = v & 0x7F;
v >>>= 7;
if (v > 0) byte |= 0x80;
bytes.push(byte);
} while (v > 0);
return Buffer.from(bytes);
}
export function decodeVarint(buf, offset = 0) {
// Fast path: read up to 4 bytes (28 bits) with plain int math β covers
// all field tags and most small ints without touching BigInt. Fall through
// to BigInt for anything larger so uint64 values (request_id, credit
// counters, timestamps) decode accurately without sign/truncation bugs.
let result = 0, shift = 0, pos = offset;
while (pos < buf.length && shift < 28) {
const byte = buf[pos++];
result |= (byte & 0x7F) << shift;
if (!(byte & 0x80)) return { value: result >>> 0, length: pos - offset };
shift += 7;
}
if (pos >= buf.length) throw new Error('Truncated varint');
// Continuation byte needed beyond 28 bits β switch to BigInt.
let big = BigInt(result >>> 0);
let bigShift = BigInt(shift);
while (pos < buf.length) {
const byte = buf[pos++];
big |= BigInt(byte & 0x7F) << bigShift;
if (!(byte & 0x80)) {
// Return Number if safely representable, else BigInt.
const asNum = Number(big);
return { value: Number.isSafeInteger(asNum) ? asNum : big, length: pos - offset };
}
bigShift += 7n;
if (bigShift >= 64n) throw new Error('Varint overflow');
}
throw new Error('Truncated varint');
}
// βββ Field-level writers (standalone functions) ββββββββββββ
function makeTag(field, wireType) {
return encodeVarint((field << 3) | wireType);
}
/** Write a varint field (wire type 0). */
export function writeVarintField(field, value) {
return Buffer.concat([makeTag(field, 0), encodeVarint(value)]);
}
/** Write a length-delimited string field (wire type 2). */
export function writeStringField(field, str) {
if (!str && str !== '') return Buffer.alloc(0);
const data = Buffer.from(str, 'utf-8');
return Buffer.concat([makeTag(field, 2), encodeVarint(data.length), data]);
}
/** Write a length-delimited bytes field (wire type 2). */
export function writeBytesField(field, data) {
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
return Buffer.concat([makeTag(field, 2), encodeVarint(buf.length), buf]);
}
/** Write an embedded message field (wire type 2). */
export function writeMessageField(field, msgBuf) {
if (!msgBuf || msgBuf.length === 0) return Buffer.alloc(0);
return Buffer.concat([makeTag(field, 2), encodeVarint(msgBuf.length), msgBuf]);
}
/** Write a fixed64 field (wire type 1). */
export function writeFixed64Field(field, buf8) {
return Buffer.concat([makeTag(field, 1), buf8]);
}
/** Write a bool field (wire type 0), only if true. */
export function writeBoolField(field, value) {
if (!value) return Buffer.alloc(0);
return writeVarintField(field, 1);
}
// βββ Parser ββββββββββββββββββββββββββββββββββββββββββββββββ
/**
* Parse a protobuf buffer into an array of { field, wireType, value }.
* For varint (0): value is a Number.
* For lendelim (2): value is a Buffer (caller decides string vs message).
* For fixed64 (1): value is an 8-byte Buffer.
* For fixed32 (5): value is a 4-byte Buffer.
*/
export function parseFields(buf) {
const fields = [];
let pos = 0;
while (pos < buf.length) {
const tag = decodeVarint(buf, pos);
pos += tag.length;
const fieldNum = Number(tag.value) >>> 3;
const wireType = Number(tag.value) & 0x07;
let value;
switch (wireType) {
case 0: { // varint
const v = decodeVarint(buf, pos);
pos += v.length;
value = v.value;
break;
}
case 1: { // fixed64
if (pos + 8 > buf.length) throw new Error(`truncated fixed64 at offset ${pos}`);
value = buf.subarray(pos, pos + 8);
pos += 8;
break;
}
case 2: { // length-delimited
const len = decodeVarint(buf, pos);
pos += len.length;
const sz = Number(len.value);
if (sz < 0 || pos + sz > buf.length) {
throw new Error(`truncated len-delim field ${fieldNum} at offset ${pos} (need ${sz}, have ${buf.length - pos})`);
}
value = buf.subarray(pos, pos + sz);
pos += sz;
break;
}
case 5: { // fixed32
if (pos + 4 > buf.length) throw new Error(`truncated fixed32 at offset ${pos}`);
value = buf.subarray(pos, pos + 4);
pos += 4;
break;
}
default:
throw new Error(`Unknown wire type ${wireType} at offset ${pos}`);
}
fields.push({ field: fieldNum, wireType, value });
}
return fields;
}
/** Get first field matching number and optional wire type. */
export function getField(fields, num, wireType) {
return fields.find(f => f.field === num && (wireType === undefined || f.wireType === wireType)) || null;
}
/** Get all fields matching number. */
export function getAllFields(fields, num) {
return fields.filter(f => f.field === num);
}
|