mirror of
https://github.com/denoland/std.git
synced 2024-11-21 20:50:22 +00:00
288 lines
8.5 KiB
TypeScript
288 lines
8.5 KiB
TypeScript
|
// 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")})`,
|
||
|
);
|
||
|
}
|