UNPKG

@ndn/packet

Version:

NDNts: Network Layer Packets

268 lines (267 loc) 10.2 kB
import { Encoder, EvDecoder, NNI } from "@ndn/tlv"; import { assert, constrain, sha256 } from "@ndn/util"; import { TT } from "./an_browser.js"; import { FwHint } from "./fwhint_browser.js"; import { definePublicFields, FIELDS } from "./impl-public-fields_browser.js"; import { Name, ParamsDigest } from "./name/mod_browser.js"; import { LLSign, LLVerify } from "./security/signing_browser.js"; import { SigInfo } from "./sig-info_browser.js"; const HOPLIMIT_MAX = 255; class Fields { constructor(...args) { for (const arg of args) { if (Name.isNameLike(arg)) { this.name = Name.from(arg); } else if (arg instanceof FwHint) { this.fwHint = new FwHint(arg); } else if (arg instanceof Uint8Array) { this.appParameters = arg; } else if (arg instanceof Interest) { Object.assign(this, arg[FIELDS]); } else if (arg[ctorAssign]) { arg[ctorAssign](this); } else { throw new Error("unknown Interest constructor argument"); } } } name = new Name(); canBePrefix = false; mustBeFresh = false; fwHint; get nonce() { return this.nonce_; } set nonce(v) { this.nonce_ = v && constrain(v, "Nonce", 0xFFFFFFFF); } nonce_; get lifetime() { return this.lifetime_; } set lifetime(v) { this.lifetime_ = constrain(Math.trunc(v), "InterestLifetime"); } lifetime_ = Interest.DefaultLifetime; get hopLimit() { return this.hopLimit_; } set hopLimit(v) { this.hopLimit_ = constrain(v, "HopLimit", HOPLIMIT_MAX); } hopLimit_ = HOPLIMIT_MAX; appParameters; sigInfo; sigValue = new Uint8Array(); paramsPortion; signedPortion; } const EVD = new EvDecoder("Interest", TT.Interest) .add(TT.Name, (t, { decoder }) => t.name = decoder.decode(Name), { required: true }) .add(TT.CanBePrefix, (t) => t.canBePrefix = true) .add(TT.MustBeFresh, (t) => t.mustBeFresh = true) .add(TT.ForwardingHint, (t, { vd }) => t.fwHint = FwHint.decodeValue(vd)) .add(TT.Nonce, (t, { value }) => t.nonce = NNI.decode(value, { len: 4 })) .add(TT.InterestLifetime, (t, { nni }) => t.lifetime = nni) .add(TT.HopLimit, (t, { value }) => t.hopLimit = NNI.decode(value, { len: 1 })) .add(TT.AppParameters, (t, { value, tlv, after }) => { if (ParamsDigest.findIn(t.name, false) < 0) { throw new Error("ParamsDigest missing in parameterized Interest"); } t.appParameters = value; assert(tlv.buffer === after.buffer); t.paramsPortion = new Uint8Array(tlv.buffer, tlv.byteOffset, tlv.byteLength + after.byteLength); }) .add(TT.ISigInfo, (t, { decoder }) => t.sigInfo = decoder.decode(SigInfo)) .add(TT.ISigValue, (t, { value, tlv }) => { if (!t.name.get(-1)?.is(ParamsDigest)) { throw new Error("ParamsDigest missing or out of place in signed Interest"); } if (!t.paramsPortion) { throw new Error("AppParameters missing in signed Interest"); } if (!t.sigInfo) { throw new Error("ISigInfo missing in signed Interest"); } assert(tlv.buffer === t.paramsPortion.buffer); t.sigValue = value; // t.name.value should be readily available during decoding; // t.name.getPrefix(-1).value would require re-encoding from components const signedPart0 = t.name.value.subarray(0, -t.name.get(-1).tlv.byteLength); const signedPart1 = new Uint8Array(tlv.buffer, t.paramsPortion.byteOffset, tlv.byteOffset - t.paramsPortion.byteOffset); t.signedPortion = new Uint8Array(signedPart0.byteLength + signedPart1.byteLength); t.signedPortion.set(signedPart0, 0); t.signedPortion.set(signedPart1, signedPart0.byteLength); }); /** Interest packet. */ export class Interest { /** * Construct from flexible arguments. * * Arguments can include, in any order: * - {@link Interest} to copy from * - {@link Name} or name URI * - {@link Interest.CanBePrefix} * - {@link Interest.MustBeFresh} * - {@link FwHint} * - {@link Interest.Nonce}`(v)` * - {@link Interest.Lifetime}`(v)` * - {@link Interest.HopLimit}`(v)` * - `Uint8Array` as AppParameters */ constructor(...args) { this[FIELDS] = new Fields(...args); } [FIELDS]; static decodeFrom(decoder) { const interest = new Interest(); EVD.decode(interest[FIELDS], decoder); return interest; } encodeTo(encoder) { const { name, canBePrefix, mustBeFresh, fwHint, nonce, lifetime, hopLimit, appParameters } = this[FIELDS]; if (name.length === 0) { throw new Error("invalid empty Interest name"); } if (appParameters && ParamsDigest.findIn(name, false) < 0) { throw new Error("ParamsDigest missing"); } encoder.prependTlv(TT.Interest, name, canBePrefix && [TT.CanBePrefix], mustBeFresh && [TT.MustBeFresh], fwHint, [TT.Nonce, NNI(nonce ?? Interest.generateNonce(), { len: 4 })], lifetime !== Interest.DefaultLifetime && [TT.InterestLifetime, NNI(lifetime)], hopLimit !== HOPLIMIT_MAX && [TT.HopLimit, NNI(hopLimit, { len: 1 })], ...this.encodeParamsPortion()); } encodeParamsPortion() { const { appParameters, sigInfo, sigValue } = this[FIELDS]; if (!appParameters) { return []; } const w = [[TT.AppParameters, appParameters]]; if (sigInfo) { w.push(sigInfo.encodeAs(TT.ISigInfo), [TT.ISigValue, sigValue]); } return w; } appendParamsDigestPlaceholder() { const f = this[FIELDS]; this.name = f.name.append(ParamsDigest.PLACEHOLDER); return f.name.length - 1; } async updateParamsDigest() { const f = this[FIELDS]; let pdIndex = ParamsDigest.findIn(f.name); if (pdIndex < 0) { pdIndex = this.appendParamsDigestPlaceholder(); } f.appParameters ??= new Uint8Array(); f.paramsPortion = Encoder.encode(this.encodeParamsPortion()); const d = await sha256(f.paramsPortion); f.name = f.name.replaceAt(pdIndex, ParamsDigest.create(d)); } async validateParamsDigest(requireAppParameters = false) { const { appParameters, paramsPortion, name } = this[FIELDS]; if (!appParameters) { if (requireAppParameters) { throw new Error("AppParameters is missing"); } return; } if (!paramsPortion) { throw new Error("parameters portion is empty"); } const pdComp = name.at(ParamsDigest.findIn(name, false)); const d = await sha256(paramsPortion); // This is not a constant-time comparison. It's for integrity purpose only. if (!pdComp.equals(ParamsDigest.create(d))) { throw new Error("incorrect ParamsDigest"); } } async [LLSign.OP](sign) { const f = this[FIELDS]; let pdIndex = ParamsDigest.findIn(f.name); if (pdIndex < 0) { pdIndex = this.appendParamsDigestPlaceholder(); } else if (pdIndex !== f.name.length - 1) { throw new Error("ParamsDigest out of place for signed Interest"); } f.signedPortion = Encoder.encode([ ...f.name.getPrefix(-1).comps, [TT.AppParameters, f.appParameters], f.sigInfo?.encodeAs(TT.ISigInfo), ]); this.sigValue = await sign(f.signedPortion); return this.updateParamsDigest(); } async [LLVerify.OP](verify) { const { signedPortion, sigValue } = this[FIELDS]; await this.validateParamsDigest(); if (!signedPortion) { throw new Error("SignedPortion is missing"); } await verify(signedPortion, sigValue); } } definePublicFields(Interest, { name: ["signedPortion"], canBePrefix: [], mustBeFresh: [], fwHint: [], nonce: [], lifetime: [], hopLimit: [], appParameters: ["paramsPortion", "signedPortion"], sigInfo: ["paramsPortion", "signedPortion"], sigValue: ["paramsPortion"], }); const ctorAssign = Symbol("@ndn/packet#Interest.ctorAssign"); const modifyFields = [ "canBePrefix", "mustBeFresh", "fwHint", "lifetime", "hopLimit", ]; (function (Interest) { /** Generate a random nonce. */ function generateNonce() { return Math.trunc(Math.random() * 0x100000000); } Interest.generateNonce = generateNonce; /** Default InterestLifetime. */ Interest.DefaultLifetime = 4000; /** Constructor argument to set CanBePrefix flag. */ Interest.CanBePrefix = { [ctorAssign](f) { f.canBePrefix = true; }, }; /** Constructor argument to set MustBeFresh flag. */ Interest.MustBeFresh = { [ctorAssign](f) { f.mustBeFresh = true; }, }; /** Constructor argument to set Nonce field. */ function Nonce(v = generateNonce()) { return { [ctorAssign](f) { f.nonce = v; }, }; } Interest.Nonce = Nonce; /** Constructor argument to set InterestLifetime field. */ function Lifetime(v) { return { [ctorAssign](f) { f.lifetime = v; }, }; } Interest.Lifetime = Lifetime; /** Constructor argument to set HopLimit field. */ function HopLimit(v) { return { [ctorAssign](f) { f.hopLimit = v; }, }; } Interest.HopLimit = HopLimit; /** * Turn {@link ModifyFields} to {@link ModifyFunc}. * Return {@link ModifyFunc} as-is. */ function makeModifyFunc(input = () => undefined) { if (typeof input === "function") { return input; } const patch = {}; for (const key of modifyFields) { if (input[key] !== undefined) { patch[key] = input[key]; } } return (interest) => { Object.assign(interest, patch); }; } Interest.makeModifyFunc = makeModifyFunc; })(Interest || (Interest = {}));