@ndn/tlv
Version:
NDNts: TLV
148 lines (147 loc) • 4.12 kB
JavaScript
import { asDataView, fromUtf8 } from "@ndn/util";
import { NNI } from "./nni_browser.js";
class DecodedTlv {
type;
buf;
offsetT;
offsetV;
offsetE;
get length() {
return this.offsetE - this.offsetV;
}
get value() {
return this.buf.subarray(this.offsetV, this.offsetE);
}
get tlv() {
return this.buf.subarray(this.offsetT, this.offsetE);
}
get size() {
return this.offsetE - this.offsetT;
}
get decoder() {
return new Decoder(this.tlv);
}
get vd() {
return new Decoder(this.value);
}
get nni() {
return NNI.decode(this.value);
}
get nniBig() {
return NNI.decode(this.value, { big: true });
}
get text() {
return fromUtf8(this.value);
}
get before() {
return this.buf.subarray(0, this.offsetT);
}
get after() {
return this.buf.subarray(this.offsetE);
}
constructor(type, buf, offsetT, offsetV, offsetE) {
this.type = type;
this.buf = buf;
this.offsetT = offsetT;
this.offsetV = offsetV;
this.offsetE = offsetE;
}
}
/** TLV decoder. */
export class Decoder {
input;
constructor(input) {
this.input = input;
this.dv = asDataView(input);
}
dv;
offset = 0;
/** Determine whether end of input has been reached. */
get eof() {
return this.offset >= this.input.length;
}
/**
* Ensure EOF has been reached.
*
* @throws Error
* Thrown if EOF has not been reached.
*/
throwUnlessEof() {
if (!this.eof) {
throw new Error("junk after end of TLV");
}
}
/**
* Read the next TLV structure from input.
* @returns TLV structure.
*
* @throws Error
* Thrown if there isn't a complete TLV structure in the input.
*/
read() {
const offsetT = this.offset;
const type = this.readVarNum();
const length = this.readVarNum();
const offsetV = this.offset;
if (length === undefined || (this.offset += length) > this.input.length) {
throw new Error(`TLV at offset ${offsetT} is incomplete`);
}
// length!==undefined implies type!==undefined
return new DecodedTlv(type, this.input, offsetT, offsetV, this.offset);
}
/** Read a Decodable object. */
decode(d) {
return d.decodeFrom(this);
}
/**
* Read a variable-size number.
* @returns The number up to uint32 or `undefined` if there isn't a complete number.
*/
readVarNum() {
if (this.eof) {
return undefined;
}
switch (this.input[this.offset]) {
case 0xFD: {
this.offset += 3;
if (this.offset > this.input.length) {
return undefined;
}
return this.dv.getUint16(this.offset - 2);
}
case 0xFE: {
this.offset += 5;
if (this.offset > this.input.length) {
return undefined;
}
return this.dv.getUint32(this.offset - 4);
}
case 0xFF: {
// JavaScript cannot reliably represent 64-bit integers
return undefined;
}
default: {
this.offset += 1;
return this.input[this.offset - 1];
}
}
}
}
(function (Decoder) {
/**
* Decode a single object from Uint8Array.
* @param input - Input buffer, which should contain the encoded object and nothing else.
* @param d - Decodable object type.
* @returns Decoded object.
*
* @throws Error
* Thrown if the input cannot be decoded as the specified object type, or there's junk leftover.
*/
function decode(input, d) {
const decoder = new Decoder(input);
const res = d.decodeFrom(decoder);
decoder.throwUnlessEof();
return res;
}
Decoder.decode = decode;
})(Decoder || (Decoder = {}));