213 lines
6.6 KiB
Plaintext
213 lines
6.6 KiB
Plaintext
// Minimal msgpack encoder/decoder.
|
|
// Supports: arrays, unsigned integers, strings, booleans, binary data.
|
|
import { Wtf8Decoder } from "./wtf8.js";
|
|
// ── MessagePack format constants ────────────────────────────────────
|
|
export const MSGPACK_FIXARRAY3 = 0x93; // 3-element fixarray
|
|
export const MSGPACK_BIN8 = 0xc4;
|
|
export const MSGPACK_BIN16 = 0xc5;
|
|
export const MSGPACK_BIN32 = 0xc6;
|
|
export const MSGPACK_UINT8 = 0xcc;
|
|
// ── Bin header helpers ──────────────────────────────────────────────
|
|
/** Compute the MessagePack bin header size for a given data length. */
|
|
export function binHeaderSize(len) {
|
|
if (len < 0x100)
|
|
return 2; // BIN8: marker + 1-byte size
|
|
if (len < 0x10000)
|
|
return 3; // BIN16: marker + 2-byte size
|
|
return 5; // BIN32: marker + 4-byte size
|
|
}
|
|
/** Write a MessagePack bin header into `buf` at `off`, return new offset. */
|
|
export function writeBinHeader(buf, off, len) {
|
|
if (len < 0x100) {
|
|
buf[off++] = MSGPACK_BIN8;
|
|
buf[off++] = len;
|
|
}
|
|
else if (len < 0x10000) {
|
|
buf[off++] = MSGPACK_BIN16;
|
|
buf[off++] = (len >>> 8) & 0xff;
|
|
buf[off++] = len & 0xff;
|
|
}
|
|
else {
|
|
buf[off++] = MSGPACK_BIN32;
|
|
buf[off++] = (len >>> 24) & 0xff;
|
|
buf[off++] = (len >>> 16) & 0xff;
|
|
buf[off++] = (len >>> 8) & 0xff;
|
|
buf[off++] = len & 0xff;
|
|
}
|
|
return off;
|
|
}
|
|
const encoder = new TextEncoder();
|
|
const decoder = new Wtf8Decoder();
|
|
export class MsgpackWriter {
|
|
buf;
|
|
view;
|
|
pos;
|
|
constructor(initialSize = 256) {
|
|
this.buf = new Uint8Array(initialSize);
|
|
this.view = new DataView(this.buf.buffer);
|
|
this.pos = 0;
|
|
}
|
|
ensure(n) {
|
|
if (this.pos + n > this.buf.length) {
|
|
let newSize = this.buf.length * 2;
|
|
while (newSize < this.pos + n)
|
|
newSize *= 2;
|
|
const next = new Uint8Array(newSize);
|
|
next.set(this.buf);
|
|
this.buf = next;
|
|
this.view = new DataView(this.buf.buffer);
|
|
}
|
|
}
|
|
writeArrayHeader(length) {
|
|
if (length <= 0x0f) {
|
|
this.ensure(1);
|
|
this.buf[this.pos++] = 0x90 | length;
|
|
}
|
|
else if (length <= 0xffff) {
|
|
this.ensure(3);
|
|
this.buf[this.pos++] = 0xdc;
|
|
this.view.setUint16(this.pos, length, false);
|
|
this.pos += 2;
|
|
}
|
|
else {
|
|
this.ensure(5);
|
|
this.buf[this.pos++] = 0xdd;
|
|
this.view.setUint32(this.pos, length, false);
|
|
this.pos += 4;
|
|
}
|
|
}
|
|
writeUint(value) {
|
|
if (value <= 0x7f) {
|
|
this.ensure(1);
|
|
this.buf[this.pos++] = value;
|
|
}
|
|
else if (value <= 0xff) {
|
|
this.ensure(2);
|
|
this.buf[this.pos++] = 0xcc;
|
|
this.buf[this.pos++] = value;
|
|
}
|
|
else if (value <= 0xffff) {
|
|
this.ensure(3);
|
|
this.buf[this.pos++] = 0xcd;
|
|
this.view.setUint16(this.pos, value, false);
|
|
this.pos += 2;
|
|
}
|
|
else {
|
|
this.ensure(5);
|
|
this.buf[this.pos++] = 0xce;
|
|
this.view.setUint32(this.pos, value, false);
|
|
this.pos += 4;
|
|
}
|
|
}
|
|
writeString(str) {
|
|
const encoded = encoder.encode(str);
|
|
const len = encoded.length;
|
|
if (len <= 0x1f) {
|
|
this.ensure(1 + len);
|
|
this.buf[this.pos++] = 0xa0 | len;
|
|
}
|
|
else if (len <= 0xff) {
|
|
this.ensure(2 + len);
|
|
this.buf[this.pos++] = 0xd9;
|
|
this.buf[this.pos++] = len;
|
|
}
|
|
else if (len <= 0xffff) {
|
|
this.ensure(3 + len);
|
|
this.buf[this.pos++] = 0xda;
|
|
this.view.setUint16(this.pos, len, false);
|
|
this.pos += 2;
|
|
}
|
|
else {
|
|
this.ensure(5 + len);
|
|
this.buf[this.pos++] = 0xdb;
|
|
this.view.setUint32(this.pos, len, false);
|
|
this.pos += 4;
|
|
}
|
|
this.buf.set(encoded, this.pos);
|
|
this.pos += len;
|
|
}
|
|
writeBool(value) {
|
|
this.ensure(1);
|
|
this.buf[this.pos++] = value ? 0xc3 : 0xc2;
|
|
}
|
|
finish() {
|
|
return this.buf.subarray(0, this.pos);
|
|
}
|
|
}
|
|
export class MsgpackReader {
|
|
buf;
|
|
view;
|
|
pos;
|
|
constructor(data, offset = 0) {
|
|
this.buf = data;
|
|
this.view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
this.pos = offset;
|
|
}
|
|
readArrayHeader() {
|
|
const byte = this.buf[this.pos++];
|
|
if ((byte & 0xf0) === 0x90)
|
|
return byte & 0x0f;
|
|
if (byte === 0xdc) {
|
|
const len = this.view.getUint16(this.pos, false);
|
|
this.pos += 2;
|
|
return len;
|
|
}
|
|
if (byte === 0xdd) {
|
|
const len = this.view.getUint32(this.pos, false);
|
|
this.pos += 4;
|
|
return len;
|
|
}
|
|
throw new Error(`Expected array header, got 0x${byte.toString(16)}`);
|
|
}
|
|
readUint() {
|
|
const byte = this.buf[this.pos++];
|
|
if (byte <= 0x7f)
|
|
return byte;
|
|
if (byte === 0xcc)
|
|
return this.buf[this.pos++];
|
|
if (byte === 0xcd) {
|
|
const val = this.view.getUint16(this.pos, false);
|
|
this.pos += 2;
|
|
return val;
|
|
}
|
|
if (byte === 0xce) {
|
|
const val = this.view.getUint32(this.pos, false);
|
|
this.pos += 4;
|
|
return val;
|
|
}
|
|
throw new Error(`Expected uint, got 0x${byte.toString(16)}`);
|
|
}
|
|
readString() {
|
|
const byte = this.buf[this.pos++];
|
|
let len;
|
|
if ((byte & 0xe0) === 0xa0) {
|
|
len = byte & 0x1f;
|
|
}
|
|
else if (byte === 0xd9) {
|
|
len = this.buf[this.pos++];
|
|
}
|
|
else if (byte === 0xda) {
|
|
len = this.view.getUint16(this.pos, false);
|
|
this.pos += 2;
|
|
}
|
|
else if (byte === 0xdb) {
|
|
len = this.view.getUint32(this.pos, false);
|
|
this.pos += 4;
|
|
}
|
|
else {
|
|
throw new Error(`Expected string, got 0x${byte.toString(16)}`);
|
|
}
|
|
const str = decoder.decode(this.buf.subarray(this.pos, this.pos + len));
|
|
this.pos += len;
|
|
return str;
|
|
}
|
|
readBool() {
|
|
const byte = this.buf[this.pos++];
|
|
if (byte === 0xc3)
|
|
return true;
|
|
if (byte === 0xc2)
|
|
return false;
|
|
throw new Error(`Expected bool, got 0x${byte.toString(16)}`);
|
|
}
|
|
}
|
|
//# sourceMappingURL=msgpack.js.map |