@ndn/tlv
Version:
NDNts: TLV
147 lines (146 loc) • 4.41 kB
JavaScript
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 = {}));