std/cbor/_common_decode.ts

288 lines
8.5 KiB
TypeScript
Raw Normal View History

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { concat } from "@std/bytes";
import { arrayToNumber } from "./_common.ts";
import { CborTag } from "./tag.ts";
import type { CborType } from "./types.ts";
export function decode(source: number[]): CborType {
const byte = source.pop();
if (byte == undefined) throw new RangeError("More bytes were expected");
const majorType = byte >> 5;
const aI = byte & 0b000_11111;
switch (majorType) {
case 0:
return decodeZero(source, aI);
case 1:
return decodeOne(source, aI);
case 2:
return decodeTwo(source, aI);
case 3:
return decodeThree(source, aI);
case 4:
return decodeFour(source, aI);
case 5:
return decodeFive(source, aI);
case 6:
return decodeSix(source, aI);
default: // Only possible for it to be 7
return decodeSeven(source, aI);
}
}
export function decodeZero(source: number[], aI: number): number | bigint {
if (aI < 24) return aI;
if (aI <= 27) {
return arrayToNumber(
Uint8Array.from(
source.splice(-(2 ** (aI - 24)), 2 ** (aI - 24)).reverse(),
).buffer,
true,
);
}
throw new RangeError(
`Cannot decode value (0b000_${aI.toString(2).padStart(5, "0")})`,
);
}
export function decodeOne(source: number[], aI: number): number | bigint {
if (aI > 27) {
throw new RangeError(
`Cannot decode value (0b001_${aI.toString(2).padStart(5, "0")})`,
);
}
const x = decodeZero(source, aI);
if (typeof x === "bigint") return -x - 1n;
return -x - 1;
}
export function decodeTwo(source: number[], aI: number): Uint8Array {
if (aI < 24) return Uint8Array.from(source.splice(-aI, aI).reverse());
if (aI <= 27) {
// Can safely assume `source.length < 2 ** 53` as JavaScript doesn't support an `Array` being that large.
// 2 ** 53 is the tipping point where integers loose precision.
const len = Number(
arrayToNumber(
Uint8Array.from(
source.splice(-(2 ** (aI - 24)), 2 ** (aI - 24)).reverse(),
).buffer,
true,
),
);
return Uint8Array.from(source.splice(-len, len).reverse());
}
if (aI === 31) {
let byte = source.pop();
if (byte == undefined) throw new RangeError("More bytes were expected");
const output: Uint8Array[] = [];
while (byte !== 0b111_11111) {
if (byte >> 5 !== 2) {
throw new TypeError(
`Cannot decode value (0b${(byte >> 5).toString(2).padStart(3, "0")}_${
(byte & 0b11111).toString(2).padStart(5, "0")
}) inside an indefinite length byte string`,
);
}
const aI = byte & 0b11111;
if (aI === 31) {
throw new TypeError(
"Indefinite length byte strings cannot contain indefinite length byte strings",
);
}
output.push(decodeTwo(source, aI));
byte = source.pop();
if (byte == undefined) throw new RangeError("More bytes were expected");
}
return concat(output);
}
throw new RangeError(
`Cannot decode value (0b010_${aI.toString(2).padStart(5, "0")})`,
);
}
export function decodeThree(source: number[], aI: number): string {
if (aI <= 27) return new TextDecoder().decode(decodeTwo(source, aI));
if (aI === 31) {
let byte = source.pop();
if (byte == undefined) throw new RangeError("More bytes were expected");
const output: string[] = [];
while (byte !== 0b111_11111) {
if (byte >> 5 !== 3) {
throw new TypeError(
`Cannot decode value (0b${(byte >> 5).toString(2).padStart(3, "0")}_${
(byte & 0b11111).toString(2).padStart(5, "0")
}) inside an indefinite length text string`,
);
}
const aI = byte & 0b11111;
if (aI === 31) {
throw new TypeError(
"Indefinite length text strings cannot contain indefinite length text strings",
);
}
output.push(decodeThree(source, aI));
byte = source.pop();
if (byte == undefined) throw new RangeError("More bytes were expected");
}
return output.join("");
}
throw new RangeError(
`Cannot decode value (0b011_${aI.toString(2).padStart(5, "0")})`,
);
}
export function decodeFour(source: number[], aI: number): CborType[] {
if (aI <= 27) {
const array: CborType[] = [];
// Can safely assume `source.length < 2 ** 53` as JavaScript doesn't support an `Array` being that large.
// 2 ** 53 is the tipping point where integers loose precision.
const len = aI < 24 ? aI : Number(
arrayToNumber(
Uint8Array.from(
source.splice(-(2 ** (aI - 24)), 2 ** (aI - 24)).reverse(),
).buffer,
true,
),
);
for (let i = 0; i < len; ++i) array.push(decode(source));
return array;
}
if (aI === 31) {
const array: CborType[] = [];
if (!source.length) throw new RangeError("More bytes were expected");
while (source[source.length - 1] !== 0b111_11111) {
array.push(decode(source));
if (!source.length) throw new RangeError("More bytes were expected");
}
source.pop();
return array;
}
throw new RangeError(
`Cannot decode value (0b100_${aI.toString(2).padStart(5, "0")})`,
);
}
export function decodeFive(
source: number[],
aI: number,
): { [k: string]: CborType } {
if (aI <= 27) {
const object: { [k: string]: CborType } = {};
// Can safely assume `source.length < 2 ** 53` as JavaScript doesn't support an `Array` being that large.
// 2 ** 53 is the tipping point where integers loose precision.
const len = aI < 24 ? aI : Number(
arrayToNumber(
Uint8Array.from(
source.splice(-(2 ** (aI - 24)), 2 ** (aI - 24)).reverse(),
).buffer,
true,
),
);
for (let i = 0; i < len; ++i) {
const key = decode(source);
if (typeof key !== "string") {
throw new TypeError(
`Cannot decode key of type (${typeof key}): This implementation only supports "text string" keys`,
);
}
if (object[key] !== undefined) {
throw new TypeError(
`A Map cannot have duplicate keys: Key (${key}) already exists`,
); // https://datatracker.ietf.org/doc/html/rfc8949#name-specifying-keys-for-maps
}
object[key] = decode(source);
}
return object;
}
if (aI === 31) {
const object: { [k: string]: CborType } = {};
if (!source.length) throw new RangeError("More bytes were expected");
while (source[source.length - 1] !== 0b111_11111) {
const key = decode(source);
if (typeof key !== "string") {
throw new TypeError(
`Cannot decode key of type (${typeof key}): This implementation only supports "text string" keys`,
);
}
if (object[key] !== undefined) {
throw new TypeError(
`A Map cannot have duplicate keys: Key (${key}) already exists`,
); // https://datatracker.ietf.org/doc/html/rfc8949#name-specifying-keys-for-maps
}
object[key] = decode(source);
if (!source.length) throw new RangeError("More bytes were expected");
}
source.pop();
return object;
}
throw new RangeError(
`Cannot decode value (0b101_${aI.toString(2).padStart(5, "0")})`,
);
}
export function decodeSix(
source: number[],
aI: number,
): Date | CborTag<CborType> {
if (aI > 27) {
throw new RangeError(
`Cannot decode value (0b110_${aI.toString(2).padStart(5, "0")})`,
);
}
const tagNumber = decodeZero(source, aI);
const tagContent = decode(source);
switch (BigInt(tagNumber)) {
case 0n:
if (typeof tagContent !== "string") {
throw new TypeError('Invalid TagItem: Expected a "text string"');
}
return new Date(tagContent);
case 1n:
if (typeof tagContent !== "number" && typeof tagContent !== "bigint") {
throw new TypeError(
'Invalid TagItem: Expected a "integer" or "float"',
);
}
return new Date(Number(tagContent) * 1000);
}
return new CborTag(tagNumber, tagContent);
}
export function decodeSeven(
source: number[],
aI: number,
): undefined | null | boolean | number {
switch (aI) {
case 20:
return false;
case 21:
return true;
case 22:
return null;
case 23:
return undefined;
}
if (25 <= aI && aI <= 27) {
return arrayToNumber(
Uint8Array.from(
source.splice(-(2 ** (aI - 24)), 2 ** (aI - 24)).reverse(),
).buffer,
false,
);
}
throw new RangeError(
`Cannot decode value (0b111_${aI.toString(2).padStart(5, "0")})`,
);
}