UNPKG

@ndn/tlv

Version:
133 lines (132 loc) 4.52 kB
import { assert } from "@ndn/util"; import { printTT } from "./string_browser.js"; const AUTO_ORDER_SKIP = 100; function nest(evd) { return (target, { decoder }) => { evd.decode(target, decoder); }; } function isCritical(tt) { return tt <= 0x1F || tt % 2 === 1; } /** * TLV-VALUE decoder that understands Packet Format v0.3 evolvability guidelines. * @typeParam T - Target type being decoded. */ export class EvDecoder { typeName; topTT; rules = new Map(); requiredTT = new Set(); nextOrder = AUTO_ORDER_SKIP; isCritical = isCritical; unknownHandler; /** Callbacks before decoding TLV-VALUE. */ beforeObservers = []; /** Callbacks after decoding TLV-VALUE. */ afterObservers = []; /** * Constructor. * @param typeName - type name, used in error messages. * @param topTT - If specified, the top-level TLV-TYPE will be checked to be in this list. */ constructor(typeName, topTT = []) { this.typeName = typeName; this.topTT = typeof topTT === "number" ? [topTT] : topTT; } applyDefaultsToRuleOptions({ order = (this.nextOrder += AUTO_ORDER_SKIP), required = false, repeat = false, } = {}) { return { order, required, repeat }; } /** * Add a decoding rule. * @param tt - TLV-TYPE to match this rule. * @param cb - Callback or nested EvDecoder to handle element TLV. * @param opts - Additional rule options. */ add(tt, cb, opts = {}) { const ro = this.applyDefaultsToRuleOptions(opts); assert(!this.rules.has(tt), "duplicate rule for same TLV-TYPE"); this.rules.set(tt, { ...ro, cb: cb instanceof EvDecoder ? nest(cb) : cb, }); if (ro.required) { this.requiredTT.add(tt); } return this; } /** Set callback to determine whether TLV-TYPE is critical. */ setIsCritical(cb) { this.isCritical = cb; return this; } /** Set callback to handle unknown elements. */ setUnknown(cb) { this.unknownHandler = cb; return this; } /** Decode TLV to target object. */ decode(target, decoder) { const topTlv = decoder.read(); if (this.topTT.length > 0 && !this.topTT.includes(topTlv.type)) { throw new Error(`TLV-TYPE ${printTT(topTlv.type)} is not ${this.typeName}`); } return this.decodeV(target, topTlv.vd, topTlv); } /** Decode TLV-VALUE to target object. */ decodeValue(target, vd) { return this.decodeV(target, vd); } decodeV(target, vd, topTlv) { for (const cb of this.beforeObservers) { cb(target, topTlv); } let currentOrder = 0; const foundTT = new Set(); const missingTT = new Set(this.requiredTT); while (!vd.eof) { const tlv = vd.read(); const tt = tlv.type; const rule = this.rules.get(tt); if (!rule) { if (!this.unknownHandler?.(target, tlv, currentOrder)) { this.handleUnrecognized(tt, "unknown"); } continue; } if (currentOrder > rule.order) { this.handleUnrecognized(tt, "out of order"); continue; } currentOrder = rule.order; if (!rule.repeat && foundTT.has(tt)) { throw new Error(`TLV-TYPE ${printTT(tt)} cannot repeat in ${this.typeName}`); } foundTT.add(tt); missingTT.delete(tt); rule.cb(target, tlv); } if (missingTT.size > 0) { throw new Error(`TLV-TYPE ${Array.from(missingTT, printTT).join(",")} missing in ${this.typeName}`); } for (const cb of this.afterObservers) { cb(target, topTlv); } return target; } handleUnrecognized(tt, reason) { if (this.isCritical(tt)) { throw new Error(`TLV-TYPE ${printTT(tt)} is ${reason} in ${this.typeName}`); } } } (function (EvDecoder) { /** * IsCritical callback that always returns `false`. * Any unrecognized or out-of-order TLV elements would be ignored. */ EvDecoder.neverCritical = () => false; /** * IsCritical callback that always returns `true`. * Any unrecognized or out-of-order TLV elements would cause an error. */ EvDecoder.alwaysCritical = () => true; })(EvDecoder || (EvDecoder = {}));