// The wire codec: the subset of msgpack MNP uses (maps, strings, integers, // binary, arrays, null), and hex for the ids the transport mints. // ── Minimal msgpack encode/decode ──────────────────────────────────────────── // Covers the subset used by MNP: maps, strings, integers, binary, arrays, null. function msgpack_encode(obj) { const parts = []; _encodeValue(obj, parts); const total = parts.reduce((s, p) => s + p.length, 0); const result = new Uint8Array(total); let off = 0; for (const p of parts) { result.set(p, off); off += p.length; } return result; } function _encodeValue(val, parts) { if (val === null || val === undefined) { parts.push(new Uint8Array([0xc0])); } else if (typeof val === 'boolean') { parts.push(new Uint8Array([val ? 0xc3 : 0xc2])); } else if (typeof val === 'number') { if (Number.isInteger(val)) { if (val >= 0 && val <= 127) { parts.push(new Uint8Array([val])); } else if (val >= 0 && val <= 0xff) { parts.push(new Uint8Array([0xcc, val])); } else if (val >= 0 && val <= 0xffff) { const b = new Uint8Array(3); b[0] = 0xcd; new DataView(b.buffer).setUint16(1, val, false); parts.push(b); } else if (val >= 0 && val <= 0xffffffff) { const b = new Uint8Array(5); b[0] = 0xce; new DataView(b.buffer).setUint32(1, val, false); parts.push(b); } else if (val >= 0 && val <= Number.MAX_SAFE_INTEGER) { // Same split as the 0xcf decoder case above, in reverse — without // this, a value over 0xffffffff fell to the plain int32 branch // below and silently wrapped to a wrong, unrelated number instead // of failing loudly. const b = new Uint8Array(9); b[0] = 0xcf; const dv = new DataView(b.buffer); dv.setUint32(1, Math.floor(val / 4294967296), false); dv.setUint32(5, val % 4294967296, false); parts.push(b); } else if (val >= -32 && val < 0) { parts.push(new Uint8Array([val & 0xff])); } else if (val >= -128 && val < 0) { const b = new Uint8Array(2); b[0] = 0xd0; b[1] = val & 0xff; parts.push(b); } else { const b = new Uint8Array(5); b[0] = 0xd2; new DataView(b.buffer).setInt32(1, val, false); parts.push(b); } } else { const b = new Uint8Array(9); b[0] = 0xcb; new DataView(b.buffer).setFloat64(1, val, false); parts.push(b); } } else if (typeof val === 'string') { const encoded = new TextEncoder().encode(val); if (encoded.length <= 31) { parts.push(new Uint8Array([0xa0 | encoded.length])); } else if (encoded.length <= 0xff) { parts.push(new Uint8Array([0xd9, encoded.length])); } else if (encoded.length <= 0xffff) { const b = new Uint8Array(3); b[0] = 0xda; new DataView(b.buffer).setUint16(1, encoded.length, false); parts.push(b); } else { const b = new Uint8Array(5); b[0] = 0xdb; new DataView(b.buffer).setUint32(1, encoded.length, false); parts.push(b); } parts.push(encoded); } else if (val instanceof Uint8Array) { if (val.length <= 0xff) { parts.push(new Uint8Array([0xc4, val.length])); } else if (val.length <= 0xffff) { const b = new Uint8Array(3); b[0] = 0xc5; new DataView(b.buffer).setUint16(1, val.length, false); parts.push(b); } else { const b = new Uint8Array(5); b[0] = 0xc6; new DataView(b.buffer).setUint32(1, val.length, false); parts.push(b); } parts.push(val); } else if (Array.isArray(val)) { if (val.length <= 15) { parts.push(new Uint8Array([0x90 | val.length])); } else if (val.length <= 0xffff) { const b = new Uint8Array(3); b[0] = 0xdc; new DataView(b.buffer).setUint16(1, val.length, false); parts.push(b); } else { const b = new Uint8Array(5); b[0] = 0xdd; new DataView(b.buffer).setUint32(1, val.length, false); parts.push(b); } for (const item of val) _encodeValue(item, parts); } else if (typeof val === 'object') { const keys = Object.keys(val); if (keys.length <= 15) { parts.push(new Uint8Array([0x80 | keys.length])); } else if (keys.length <= 0xffff) { const b = new Uint8Array(3); b[0] = 0xde; new DataView(b.buffer).setUint16(1, keys.length, false); parts.push(b); } else { const b = new Uint8Array(5); b[0] = 0xdf; new DataView(b.buffer).setUint32(1, keys.length, false); parts.push(b); } for (const k of keys) { _encodeValue(k, parts); _encodeValue(val[k], parts); } } } function msgpack_decode(buf) { const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength); const [val] = _decodeValue(buf, view, 0); return val; } function _decodeValue(buf, view, offset) { const byte = buf[offset]; if (byte <= 0x7f) return [byte, offset + 1]; if ((byte & 0xe0) === 0xe0) return [byte - 256, offset + 1]; if ((byte & 0xa0) === 0xa0) { const len = byte & 0x1f; return [new TextDecoder().decode(buf.slice(offset + 1, offset + 1 + len)), offset + 1 + len]; } if ((byte & 0xf0) === 0x90) { const len = byte & 0x0f; return _decodeArray(buf, view, offset + 1, len); } if ((byte & 0xf0) === 0x80) { const len = byte & 0x0f; return _decodeMap(buf, view, offset + 1, len); } switch (byte) { case 0xc0: return [null, offset + 1]; case 0xc2: return [false, offset + 1]; case 0xc3: return [true, offset + 1]; case 0xc4: { const len = buf[offset + 1]; return [buf.slice(offset + 2, offset + 2 + len), offset + 2 + len]; } case 0xc5: { const len = view.getUint16(offset + 1, false); return [buf.slice(offset + 3, offset + 3 + len), offset + 3 + len]; } case 0xc6: { const len = view.getUint32(offset + 1, false); return [buf.slice(offset + 5, offset + 5 + len), offset + 5 + len]; } case 0xcc: return [buf[offset + 1], offset + 2]; case 0xcd: return [view.getUint16(offset + 1, false), offset + 3]; case 0xce: return [view.getUint32(offset + 1, false), offset + 5]; // uint64/int64 — never emitted by this file's own encoder (a JS number // above 0xffffffff falls to float64 there), but the node's real msgpack // library sends a plain uint64 for any Python int over ~4.3 billion, and // a raw byte count crosses that easily (found live: IndexProgress. // scanned_bytes/total_bytes in the handshake ack, indexer.py, for a // group whose total library size exceeds ~4 GB). Split into two 32-bit // halves rather than DataView's getBigUint64/getBigInt64 — a BigInt // would silently poison every arithmetic use of these fields elsewhere // (percentage math, comparisons) — and every real byte count fits in a // plain JS number well under Number.MAX_SAFE_INTEGER (2^53). case 0xcf: { const hi = view.getUint32(offset + 1, false); const lo = view.getUint32(offset + 5, false); return [hi * 4294967296 + lo, offset + 9]; } case 0xd3: { const hi = view.getInt32(offset + 1, false); const lo = view.getUint32(offset + 5, false); return [hi * 4294967296 + lo, offset + 9]; } case 0xcb: return [view.getFloat64(offset + 1, false), offset + 9]; case 0xd0: return [view.getInt8(offset + 1), offset + 2]; case 0xd1: return [view.getInt16(offset + 1, false), offset + 3]; case 0xd2: return [view.getInt32(offset + 1, false), offset + 5]; case 0xd9: { const len = buf[offset + 1]; return [new TextDecoder().decode(buf.slice(offset + 2, offset + 2 + len)), offset + 2 + len]; } case 0xda: { const len = view.getUint16(offset + 1, false); return [new TextDecoder().decode(buf.slice(offset + 3, offset + 3 + len)), offset + 3 + len]; } case 0xdb: { const len = view.getUint32(offset + 1, false); return [new TextDecoder().decode(buf.slice(offset + 5, offset + 5 + len)), offset + 5 + len]; } case 0xdc: { const len = view.getUint16(offset + 1, false); return _decodeArray(buf, view, offset + 3, len); } case 0xdd: { const len = view.getUint32(offset + 1, false); return _decodeArray(buf, view, offset + 5, len); } case 0xde: { const len = view.getUint16(offset + 1, false); return _decodeMap(buf, view, offset + 3, len); } case 0xdf: { const len = view.getUint32(offset + 1, false); return _decodeMap(buf, view, offset + 5, len); } default: throw new Error(`Unknown msgpack type: 0x${byte.toString(16)}`); } } function _decodeArray(buf, view, offset, count) { const arr = []; for (let i = 0; i < count; i++) { const [val, newOff] = _decodeValue(buf, view, offset); arr.push(val); offset = newOff; } return [arr, offset]; } function _decodeMap(buf, view, offset, count) { const obj = {}; for (let i = 0; i < count; i++) { const [key, off1] = _decodeValue(buf, view, offset); const [val, off2] = _decodeValue(buf, view, off1); obj[key] = val; offset = off2; } return [obj, offset]; } function _hex(bytes) { return [...bytes].map(b => b.toString(16).padStart(2, '0')).join(''); }