UNPKG

@ndn/tlv

Version:
145 lines (144 loc) 5.37 kB
import { assert } from "@ndn/util"; import {} from "./decoder_node.js"; import {} from "./encoder_node.js"; import { EvDecoder } from "./ev-decoder_node.js"; import { encodeFields, makeField, sortFields } from "./impl-field_node.js"; /** * Helper to build a base class that represents a TLV structure. * * @remarks * StructBuilder allows you to define the typing, constructor, encoder, and decoder, while writing * each field only once. To be compatible with StructBuilder, the TLV structure being described * shall contain a sequence of sub-TLV elements with distinct TLV-TYPE numbers, where each * sub-TLV-TYPE appears zero, one, or multiple times. * * To use StructBuilder, calling code should follow these steps: * 1. Invoke `.add()` method successively to define sub-TLV elements. * 2. Obtain a base class via `.baseClass()` method, which contains one field for each sub-TLV-TYPE * as defined, along with constructor, encoding, and decoding functions. * 3. Declare a subclass deriving from this base class, to add more functionality. * 4. Assign the subclass constructor to `.subclass` property of the builder. */ export class StructBuilder { typeName; topTT; /** * Constructor. * @param typeName - Type name, used in error messages. * @param topTT - If specified, encode as complete TLV; otherwise, encode as TLV-VALUE only. */ constructor(typeName, topTT) { this.typeName = typeName; this.topTT = topTT; this.EVD = new EvDecoder(typeName, topTT); } /** * Subclass constructor. * This must be assigned, otherwise decoding function will not work. */ subclass; fields = []; flagBits = []; EVD; /** Access EvDecoder for certain customizations. */ static evdOf(sb) { return sb.EVD; } /** Retrieve field names. */ static keysOf(sb) { return sb.fields.map(({ key }) => key); } /** * Add a field. * @param tt - TLV-TYPE number. * @param key - Field name on the base class. * @param type - Field type. * @param opts - Field options. * @returns StructBuilder annotated with field typing. */ add(tt, key, type, opts = {}) { const field = makeField(tt, key, type, opts, this.EVD); const { flagPrefix = key, flagBits } = opts; if (flagBits) { field.asString = function* (v) { if (typeof v !== "number") { return; } yield ` ${key}=0x${v.toString(16).toUpperCase()}(`; let delim = ""; for (const [str, bit] of Object.entries(flagBits)) { if ((v & bit) !== 0) { yield `${delim}${str}`; delim = "|"; } } yield ")"; }; for (const [str, bit] of Object.entries(flagBits)) { const prop = flagPrefix + str.slice(0, 1).toUpperCase() + str.slice(1); this.flagBits.push({ key, prop, bit }); } } this.fields.push(field); return this; } /** Change IsCritical on the EvDecoder. */ setIsCritical(cb) { this.EVD.setIsCritical(cb); return this; } /** * Obtain a base class for the TLV structure class. * @typeParam S - Subclass type. */ baseClass() { sortFields(this.fields); const b = this; // eslint-disable-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias return class { constructor() { for (const { key, newValue: construct } of b.fields) { this[key] = construct(); } for (const { key, prop, bit } of b.flagBits) { Object.defineProperty(this, prop, { configurable: true, enumerable: false, get() { return (((this)[key] ?? 0) & bit) !== 0; }, set(v) { (this)[key] ??= 0; if (v) { (this)[key] |= bit; } else { (this)[key] &= ~bit; } }, }); } } encodeTo(encoder) { const elements = encodeFields(b.fields, this); if (b.topTT === undefined) { encoder.encode(elements); } else { encoder.prependTlv(b.topTT, ...elements); } } static decodeFrom(decoder) { assert(b.subclass, `StructBuilder(${b.typeName}).subclass is unset`); const t = new b.subclass(); return b.EVD[b.topTT === undefined ? "decodeValue" : "decode"](t, decoder); } toString() { const tokens = [b.typeName]; for (const { key, asString } of b.fields) { tokens.push(...asString(this[key])); } return tokens.join(""); } }; } }