mirror of
https://github.com/denoland/std.git
synced 2024-11-22 04:59:05 +00:00
262 lines
7.4 KiB
TypeScript
262 lines
7.4 KiB
TypeScript
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
|
// This module is browser compatible.
|
|
|
|
import type { ValueType } from "./encode.ts";
|
|
|
|
/**
|
|
* Decode a value from the MessagePack binary format.
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* import { decode } from "@std/msgpack/decode";
|
|
*
|
|
* const encoded = Uint8Array.of(1, 2, 3)
|
|
*
|
|
* console.log(decode(encoded))
|
|
* ```
|
|
*/
|
|
export function decode(uint8: Uint8Array): ValueType {
|
|
const pointer = { consumed: 0 };
|
|
const dataView = new DataView(
|
|
uint8.buffer,
|
|
uint8.byteOffset,
|
|
uint8.byteLength,
|
|
);
|
|
const value = decodeSlice(uint8, dataView, pointer);
|
|
|
|
if (pointer.consumed < uint8.length) {
|
|
throw new EvalError("Messagepack decode did not consume whole array");
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
function decodeString(
|
|
uint8: Uint8Array,
|
|
size: number,
|
|
pointer: { consumed: number },
|
|
) {
|
|
pointer.consumed += size;
|
|
return decoder.decode(
|
|
uint8.subarray(pointer.consumed - size, pointer.consumed),
|
|
);
|
|
}
|
|
|
|
function decodeArray(
|
|
uint8: Uint8Array,
|
|
dataView: DataView,
|
|
size: number,
|
|
pointer: { consumed: number },
|
|
) {
|
|
const arr: ValueType[] = [];
|
|
|
|
for (let i = 0; i < size; i++) {
|
|
const value = decodeSlice(uint8, dataView, pointer);
|
|
arr.push(value);
|
|
}
|
|
|
|
return arr;
|
|
}
|
|
|
|
function decodeMap(
|
|
uint8: Uint8Array,
|
|
dataView: DataView,
|
|
size: number,
|
|
pointer: { consumed: number },
|
|
) {
|
|
const map: Record<number | string, ValueType> = {};
|
|
|
|
for (let i = 0; i < size; i++) {
|
|
const key = decodeSlice(uint8, dataView, pointer);
|
|
const value = decodeSlice(uint8, dataView, pointer);
|
|
|
|
if (typeof key !== "number" && typeof key !== "string") {
|
|
throw new EvalError(
|
|
"Messagepack decode came across an invalid type for a key of a map",
|
|
);
|
|
}
|
|
|
|
map[key] = value;
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
const decoder = new TextDecoder();
|
|
|
|
const FIXMAP_BITS = 0b1000_0000;
|
|
const FIXMAP_MASK = 0b1111_0000;
|
|
const FIXARRAY_BITS = 0b1001_0000;
|
|
const FIXARRAY_MASK = 0b1111_0000;
|
|
const FIXSTR_BITS = 0b1010_0000;
|
|
const FIXSTR_MASK = 0b1110_0000;
|
|
|
|
/**
|
|
* Given a uint8array which contains a msgpack object,
|
|
* return the value of the object as well as how many bytes
|
|
* were consumed in obtaining this object
|
|
*/
|
|
function decodeSlice(
|
|
uint8: Uint8Array,
|
|
dataView: DataView,
|
|
pointer: { consumed: number },
|
|
): ValueType {
|
|
const type = dataView.getUint8(pointer.consumed);
|
|
pointer.consumed++;
|
|
|
|
if (type <= 0x7f) { // positive fixint - really small positive number
|
|
return type;
|
|
}
|
|
|
|
if ((type & FIXMAP_MASK) === FIXMAP_BITS) { // fixmap - small map
|
|
const size = type & ~FIXMAP_MASK;
|
|
return decodeMap(uint8, dataView, size, pointer);
|
|
}
|
|
|
|
if ((type & FIXARRAY_MASK) === FIXARRAY_BITS) { // fixarray - small array
|
|
const size = type & ~FIXARRAY_MASK;
|
|
return decodeArray(uint8, dataView, size, pointer);
|
|
}
|
|
|
|
if ((type & FIXSTR_MASK) === FIXSTR_BITS) { // fixstr - small string
|
|
const size = type & ~FIXSTR_MASK;
|
|
return decodeString(uint8, size, pointer);
|
|
}
|
|
|
|
if (type >= 0xe0) { // negative fixint - really small negative number
|
|
return type - 256;
|
|
}
|
|
|
|
switch (type) {
|
|
case 0xc0: // nil
|
|
return null;
|
|
case 0xc1: // (never used)
|
|
throw new Error(
|
|
"Messagepack decode encountered a type that is never used",
|
|
);
|
|
case 0xc2: // false
|
|
return false;
|
|
case 0xc3: // true
|
|
return true;
|
|
case 0xc4: { // bin 8 - small Uint8Array
|
|
const length = dataView.getUint8(pointer.consumed);
|
|
pointer.consumed++;
|
|
const u8 = uint8.subarray(pointer.consumed, pointer.consumed + length);
|
|
pointer.consumed += length;
|
|
return u8;
|
|
}
|
|
case 0xc5: { // bin 16 - medium Uint8Array
|
|
const length = dataView.getUint16(pointer.consumed);
|
|
pointer.consumed += 2;
|
|
const u8 = uint8.subarray(pointer.consumed, pointer.consumed + length);
|
|
pointer.consumed += length;
|
|
return u8;
|
|
}
|
|
case 0xc6: { // bin 32 - large Uint8Array
|
|
const length = dataView.getUint32(pointer.consumed);
|
|
pointer.consumed += 4;
|
|
const u8 = uint8.subarray(pointer.consumed, pointer.consumed + length);
|
|
pointer.consumed += length;
|
|
return u8;
|
|
}
|
|
case 0xc7: // ext 8 - small extension type
|
|
case 0xc8: // ext 16 - medium extension type
|
|
case 0xc9: // ext 32 - large extension type
|
|
throw new Error("ext not implemented yet");
|
|
case 0xca: { // float 32
|
|
const value = dataView.getFloat32(pointer.consumed);
|
|
pointer.consumed += 4;
|
|
return value;
|
|
}
|
|
case 0xcb: { // float 64
|
|
const value = dataView.getFloat64(pointer.consumed);
|
|
pointer.consumed += 8;
|
|
return value;
|
|
}
|
|
case 0xcc: { // uint 8
|
|
const value = dataView.getUint8(pointer.consumed);
|
|
pointer.consumed += 1;
|
|
return value;
|
|
}
|
|
case 0xcd: { // uint 16
|
|
const value = dataView.getUint16(pointer.consumed);
|
|
pointer.consumed += 2;
|
|
return value;
|
|
}
|
|
case 0xce: { // uint 32
|
|
const value = dataView.getUint32(pointer.consumed);
|
|
pointer.consumed += 4;
|
|
return value;
|
|
}
|
|
case 0xcf: { // uint 64
|
|
const value = dataView.getBigUint64(pointer.consumed);
|
|
pointer.consumed += 8;
|
|
return value;
|
|
}
|
|
case 0xd0: { // int 8
|
|
const value = dataView.getInt8(pointer.consumed);
|
|
pointer.consumed += 1;
|
|
return value;
|
|
}
|
|
case 0xd1: { // int 16
|
|
const value = dataView.getInt16(pointer.consumed);
|
|
pointer.consumed += 2;
|
|
return value;
|
|
}
|
|
case 0xd2: { // int 32
|
|
const value = dataView.getInt32(pointer.consumed);
|
|
pointer.consumed += 4;
|
|
return value;
|
|
}
|
|
case 0xd3: { // int 64
|
|
const value = dataView.getBigInt64(pointer.consumed);
|
|
pointer.consumed += 8;
|
|
return value;
|
|
}
|
|
case 0xd4: // fixext 1 - 1 byte extension type
|
|
case 0xd5: // fixext 2 - 2 byte extension type
|
|
case 0xd6: // fixext 4 - 4 byte extension type
|
|
case 0xd7: // fixext 8 - 8 byte extension type
|
|
case 0xd8: // fixext 16 - 16 byte extension type
|
|
throw new Error("fixext not implemented yet");
|
|
case 0xd9: { // str 8 - small string
|
|
const length = dataView.getUint8(pointer.consumed);
|
|
pointer.consumed += 1;
|
|
return decodeString(uint8, length, pointer);
|
|
}
|
|
case 0xda: { // str 16 - medium string
|
|
const length = dataView.getUint16(pointer.consumed);
|
|
pointer.consumed += 2;
|
|
return decodeString(uint8, length, pointer);
|
|
}
|
|
case 0xdb: { // str 32 - large string
|
|
const length = dataView.getUint32(pointer.consumed);
|
|
pointer.consumed += 4;
|
|
return decodeString(uint8, length, pointer);
|
|
}
|
|
case 0xdc: { // array 16 - medium array
|
|
const length = dataView.getUint16(pointer.consumed);
|
|
pointer.consumed += 2;
|
|
return decodeArray(uint8, dataView, length, pointer);
|
|
}
|
|
case 0xdd: { // array 32 - large array
|
|
const length = dataView.getUint32(pointer.consumed);
|
|
pointer.consumed += 4;
|
|
return decodeArray(uint8, dataView, length, pointer);
|
|
}
|
|
case 0xde: { // map 16 - medium map
|
|
const length = dataView.getUint16(pointer.consumed);
|
|
pointer.consumed += 2;
|
|
return decodeMap(uint8, dataView, length, pointer);
|
|
}
|
|
case 0xdf: { // map 32 - large map
|
|
const length = dataView.getUint32(pointer.consumed);
|
|
pointer.consumed += 4;
|
|
return decodeMap(uint8, dataView, length, pointer);
|
|
}
|
|
}
|
|
|
|
// All cases are covered for numbers between 0-255. Typescript isn't smart enough to know that.
|
|
throw new Error("Unreachable");
|
|
}
|