std/msgpack/decode.ts
2024-08-21 16:15:14 +09:00

392 lines
12 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 {@link https://msgpack.org/ | MessagePack} binary format.
*
* If the input is not in valid message pack format, an error will be thrown.
*
* @example Usage
* ```ts
* import { decode } from "@std/msgpack/decode";
* import { assertEquals } from "@std/assert";
*
* const encoded = new Uint8Array([163, 72, 105, 33]);
*
* assertEquals(decode(encoded), "Hi!");
* ```
*
* @param data MessagePack binary data.
* @returns Decoded value from the MessagePack binary data.
*/
export function decode(data: Uint8Array): ValueType {
const pointer = { consumed: 0 };
const dataView = new DataView(
data.buffer,
data.byteOffset,
data.byteLength,
);
const value = decodeSlice(data, dataView, pointer);
if (pointer.consumed < data.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;
const u8 = uint8.subarray(pointer.consumed - size, pointer.consumed);
if (u8.length !== size) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
return decoder.decode(u8);
}
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(
"Cannot decode a key of a map: The type of key is invalid, keys must be a number or a string",
);
}
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 {
if (pointer.consumed >= uint8.length) {
throw new EvalError("Messagepack decode reached end of array prematurely");
}
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
if (pointer.consumed >= uint8.length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
const length = dataView.getUint8(pointer.consumed);
pointer.consumed++;
const u8 = uint8.subarray(pointer.consumed, pointer.consumed + length);
if (u8.length !== length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
pointer.consumed += length;
return u8;
}
case 0xc5: { // bin 16 - medium Uint8Array
if (pointer.consumed + 1 >= uint8.length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
const length = dataView.getUint16(pointer.consumed);
pointer.consumed += 2;
const u8 = uint8.subarray(pointer.consumed, pointer.consumed + length);
if (u8.length !== length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
pointer.consumed += length;
return u8;
}
case 0xc6: { // bin 32 - large Uint8Array
if (pointer.consumed + 3 >= uint8.length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
const length = dataView.getUint32(pointer.consumed);
pointer.consumed += 4;
const u8 = uint8.subarray(pointer.consumed, pointer.consumed + length);
if (u8.length !== length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
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(
"Cannot decode a slice: Large extension type 'ext' not implemented yet",
);
case 0xca: { // float 32
if (pointer.consumed + 3 >= uint8.length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
const value = dataView.getFloat32(pointer.consumed);
pointer.consumed += 4;
return value;
}
case 0xcb: { // float 64
if (pointer.consumed + 7 >= uint8.length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
const value = dataView.getFloat64(pointer.consumed);
pointer.consumed += 8;
return value;
}
case 0xcc: { // uint 8
if (pointer.consumed >= uint8.length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
const value = dataView.getUint8(pointer.consumed);
pointer.consumed += 1;
return value;
}
case 0xcd: { // uint 16
if (pointer.consumed + 1 >= uint8.length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
const value = dataView.getUint16(pointer.consumed);
pointer.consumed += 2;
return value;
}
case 0xce: { // uint 32
if (pointer.consumed + 3 >= uint8.length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
const value = dataView.getUint32(pointer.consumed);
pointer.consumed += 4;
return value;
}
case 0xcf: { // uint 64
if (pointer.consumed + 7 >= uint8.length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
const value = dataView.getBigUint64(pointer.consumed);
pointer.consumed += 8;
return value;
}
case 0xd0: { // int 8
if (pointer.consumed >= uint8.length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
const value = dataView.getInt8(pointer.consumed);
pointer.consumed += 1;
return value;
}
case 0xd1: { // int 16
if (pointer.consumed + 1 >= uint8.length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
const value = dataView.getInt16(pointer.consumed);
pointer.consumed += 2;
return value;
}
case 0xd2: { // int 32
if (pointer.consumed + 3 >= uint8.length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
const value = dataView.getInt32(pointer.consumed);
pointer.consumed += 4;
return value;
}
case 0xd3: { // int 64
if (pointer.consumed + 7 >= uint8.length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
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("Cannot decode a slice: 'fixext' not implemented yet");
case 0xd9: { // str 8 - small string
if (pointer.consumed >= uint8.length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
const length = dataView.getUint8(pointer.consumed);
pointer.consumed += 1;
return decodeString(uint8, length, pointer);
}
case 0xda: { // str 16 - medium string
if (pointer.consumed + 1 >= uint8.length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
const length = dataView.getUint16(pointer.consumed);
pointer.consumed += 2;
return decodeString(uint8, length, pointer);
}
case 0xdb: { // str 32 - large string
if (pointer.consumed + 3 >= uint8.length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
const length = dataView.getUint32(pointer.consumed);
pointer.consumed += 4;
return decodeString(uint8, length, pointer);
}
case 0xdc: { // array 16 - medium array
if (pointer.consumed + 1 >= uint8.length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
const length = dataView.getUint16(pointer.consumed);
pointer.consumed += 2;
return decodeArray(uint8, dataView, length, pointer);
}
case 0xdd: { // array 32 - large array
if (pointer.consumed + 3 >= uint8.length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
const length = dataView.getUint32(pointer.consumed);
pointer.consumed += 4;
return decodeArray(uint8, dataView, length, pointer);
}
case 0xde: { // map 16 - medium map
if (pointer.consumed + 1 >= uint8.length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
const length = dataView.getUint16(pointer.consumed);
pointer.consumed += 2;
return decodeMap(uint8, dataView, length, pointer);
}
case 0xdf: { // map 32 - large map
if (pointer.consumed + 3 >= uint8.length) {
throw new EvalError(
"Messagepack decode reached end of array prematurely",
);
}
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");
}