UNPKG

@ndn/tlv

Version:
147 lines (146 loc) 4.41 kB
import { asDataView, assert } from "@ndn/util"; function sizeofVarNum(n) { if (n < 0xFD) { return 1; } if (n <= 0xFFFF) { return 3; } if (n <= 0xFFFFFFFF) { return 5; } // 64-bit integers may lose precision in Number type, and it's rarely useful throw new Error("VAR-NUMBER is too large"); } function writeVarNum(room, dv, off, n) { if (n < 0xFD) { room[off++] = n; } else if (n <= 0xFFFF) { room[off++] = 0xFD; dv.setUint16(off, n); } else { room[off++] = 0xFE; dv.setUint32(off, n); } } /** TLV encoder that accepts objects in reverse order. */ export class Encoder { constructor(initSize = 2048) { this.buf = new ArrayBuffer(initSize); this.off = initSize; } buf; off; /** Return encoding output size. */ get size() { return this.buf.byteLength - this.off; } /** Obtain encoding output. */ get output() { return new Uint8Array(this.buf, this.off); } /** * Make room to prepend an object. * @param sizeofObject - Object size. * @returns Room to write object. */ prependRoom(sizeofObject) { if (this.off < sizeofObject) { this.grow(sizeofObject); } this.off -= sizeofObject; return new Uint8Array(this.buf, this.off, sizeofObject); } /** Prepend TLV-TYPE and TLV-LENGTH. */ prependTypeLength(tlvType, tlvLength) { const sizeofT = sizeofVarNum(tlvType); const sizeofL = sizeofVarNum(tlvLength); const room = this.prependRoom(sizeofT + sizeofL); const dv = asDataView(room); writeVarNum(room, dv, 0, tlvType); writeVarNum(room, dv, sizeofT, tlvLength); } /** * Prepend TLV-VALUE. * * @remarks * Elements are prepended in the reverse order, so that they would appear in the output * in the same order as the parameter order. */ prependValue(...tlvValue) { for (let i = tlvValue.length - 1; i >= 0; --i) { this.encode(tlvValue[i]); } } prependTlv(tlvType, arg2, ...tlvValue) { const hasOmitEmpty = arg2 === Encoder.OmitEmpty; if (!hasOmitEmpty) { tlvValue.unshift(arg2); } const sizeBefore = this.size; this.prependValue(...tlvValue); const tlvLength = this.size - sizeBefore; if (tlvLength > 0 || !hasOmitEmpty) { this.prependTypeLength(tlvType, tlvLength); } } /** Prepend `Encodable`. */ encode(obj) { if (obj instanceof Uint8Array) { this.prependRoom(obj.length).set(obj); } else if (typeof obj?.encodeTo === "function") { obj.encodeTo(this); } else if (Array.isArray(obj)) { if (typeof obj[0] === "number") { this.prependTlv(...obj); } else { this.prependValue(...obj); } } else { assert(obj === undefined || obj === false, "obj is not Encodable"); } } grow(sizeofRoom) { const sizeofGrowth = 2048 + sizeofRoom; const buf = new ArrayBuffer(sizeofGrowth + this.size); new Uint8Array(buf, sizeofGrowth).set(this.output); this.buf = buf; this.off = sizeofGrowth; } } (function (Encoder) { /** * Indicate that TLV should be skipped if TLV-VALUE is empty. * @see {@link EncodableTlv} */ Encoder.OmitEmpty = Symbol("@ndn/tlv#OmitEmpty"); /** Encode a single object into Uint8Array. */ function encode(obj, initBufSize) { const encoder = new Encoder(initBufSize); encoder.encode(obj); return encoder.output; } Encoder.encode = encode; /** * Extract the encoding output of an element while writing to a parent encoder. * @param obj - Encodable element. * @param cb - Function to receive the encoding output of `obj`. * @returns Wrapped Encodable object. */ function extract(obj, cb) { return { encodeTo(encoder) { const sizeBefore = encoder.size; encoder.encode(obj); cb(encoder.output.subarray(0, encoder.size - sizeBefore)); }, }; } Encoder.extract = extract; })(Encoder || (Encoder = {}));