cbor-edn
Version:
Parse CBOR Extended Diagnostic Notation as defined by [draft-ietf-cbor-edn-literals-16](https://www.ietf.org/archive/id/draft-ietf-cbor-edn-literals-16.html) and some CBOR working group discussions.
174 lines (173 loc) • 4.82 kB
JavaScript
import { getRanges, hasRanges, setRanges, u8toHex, } from 'cbor2/utils';
function len(items) {
let tot = 0;
for (const i of items) {
if (Array.isArray(i)) {
tot += len(i);
}
else {
tot += i.length;
}
}
return tot;
}
function allBytes(into, offset, items) {
for (const t of items) {
if (Array.isArray(t)) {
offset = allBytes(into, offset, t);
}
else if (t instanceof Uint8Array) {
into.set(t, offset);
offset += t.length;
// eslint-disable-next-line @typescript-eslint/no-use-before-define
}
else if (t instanceof ByteTree) {
t.bytes(into, offset);
offset += t.length;
}
// Impossible to have invalid entry since all pathways are checked in
// hasRegions.
}
return offset;
}
function hasRegions(item) {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
if (item instanceof ByteTree) {
return item.hasRegions;
}
if (Array.isArray(item)) {
return item.some(hasRegions);
}
if (item instanceof Uint8Array) {
return hasRanges(item);
}
throw new Error(`Invalid ByteTree item: ${item}`);
}
function getRegions(item, offset, depth = 0) {
if (Array.isArray(item)) {
const ret = [];
for (const i of item) {
const r = getRegions(i, offset, depth + 1);
const last = r[r.length - 1];
offset = last[0] + last[1];
ret.push(...r);
}
if (ret.length === 0) {
ret.push([offset, 0]);
}
return ret;
}
if (item instanceof Uint8Array) {
const r = getRanges(item);
if (!r) {
return [[offset, item.length]];
}
const ret = [];
for (const s of r) {
const t = [...s];
t[0] += offset;
ret.push(t);
}
return ret;
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
if (item instanceof ByteTree) {
if (!item.hasRegions) {
item.setRegion();
}
const ret = [];
for (const r of item.regions) {
const s = [...r];
s[0] += offset;
ret.push(s);
}
return ret;
}
throw new Error(`Invalid ByteTree item: ${item}`);
}
export class ByteTree {
mt = 0;
#length = 0;
#items = [];
#hasRegions;
#regions = [];
constructor(...item) {
this.#items = item;
this.#length = len(item);
this.#hasRegions = hasRegions(item);
if (this.#hasRegions) {
this.#regions = getRegions(item, 0);
}
}
get length() {
return this.#length;
}
get hasRegions() {
return this.#hasRegions;
}
get regions() {
return this.#regions;
}
setRegion(type) {
this.#hasRegions = true;
this.#regions = getRegions(this.#items, 0);
if (type) {
if ((this.#regions[0][0] === 0) &&
(this.#regions[0][1] === this.#length)) {
this.#regions[0][2] = type;
}
else {
this.#regions.unshift([0, this.#length, type]);
}
}
}
bytes(into, offset = 0) {
if (!into) {
if (this.#hasRegions) {
// Always copy if there are regions
into = new Uint8Array(this.#length);
setRanges(into, this.#regions);
}
else if ((this.#items.length === 1) &&
(this.#items[0] instanceof Uint8Array)) {
// Shortcut, don't copy
return this.#items[0];
}
else {
into = new Uint8Array(this.#length);
}
}
allBytes(into, offset, this.#items);
return into;
}
push(...item) {
this.#items.push(...item);
if (this.#hasRegions) {
this.#regions.push(...getRegions(item, this.#length));
}
else if (hasRegions(item)) {
this.#hasRegions = true;
this.#regions = getRegions(this.#items, 0);
}
this.#length += len(item);
}
[Symbol.for('nodejs.util.inspect.custom')]() {
return this.toString();
}
toString() {
let ret = 'ByteTree(';
ret += this.#length;
ret += ')[';
ret += this.#items.map(i => {
if (i instanceof Uint8Array) {
if (i.length) {
return `0x${u8toHex(i)}`;
}
return '""';
}
return String(i);
}).join(', ');
ret += ']';
return ret;
}
}