UNPKG

@ndn/packet

Version:

NDNts: Network Layer Packets

228 lines (227 loc) 8.18 kB
import { Encoder, EvDecoder, NNI } from "@ndn/tlv"; import { constrain, sha256 } from "@ndn/util"; import { TT } from "./an_node.js"; import { definePublicFields, FIELDS } from "./impl-public-fields_node.js"; import { Component, ImplicitDigest, Name } from "./name/mod_node.js"; import { LLSign, LLVerify } from "./security/signing_node.js"; import { SigInfo } from "./sig-info_node.js"; class Fields { constructor(...args) { let isFinalBlock = false; for (const arg of args) { if (Name.isNameLike(arg)) { this.name = Name.from(arg); } else if (arg instanceof Uint8Array) { this.content = arg; } else if (arg === Data.FinalBlock) { isFinalBlock = true; } else if (arg instanceof Data) { Object.assign(this, arg[FIELDS]); } else if (arg[ctorAssign]) { arg[ctorAssign](this); } else { throw new Error("unknown Data constructor argument"); } } this.isFinalBlock = isFinalBlock; } name = new Name(); get contentType() { return this.contentType_; } set contentType(v) { this.contentType_ = constrain(v, "ContentType"); } contentType_ = 0; get freshnessPeriod() { return this.freshnessPeriod_; } set freshnessPeriod(v) { this.freshnessPeriod_ = constrain(Math.trunc(v), "FreshnessPeriod"); } freshnessPeriod_ = 0; finalBlockId; /** Determine whether FinalBlockId equals the last name component. */ get isFinalBlock() { return !!this.finalBlockId && this.name.length > 0 && this.finalBlockId.equals(this.name.get(-1)); } /** * Setting to `false` deletes FinalBlockId. * Setting to `true` assigns FinalBlockId to be the last name component. * * @throws Error * Thrown if attempting to set `true` while the name is empty. */ set isFinalBlock(v) { if (!v) { this.finalBlockId = undefined; return; } if (this.name.length === 0) { throw new Error("cannot set FinalBlockId when Name is empty"); } this.finalBlockId = this.name.get(-1); } content = new Uint8Array(); sigInfo = new SigInfo(); sigValue = new Uint8Array(); signedPortion; topTlv; topTlvDigest; } const EVD = new EvDecoder("Data", TT.Data) .add(TT.Name, (t, { decoder }) => t.name = decoder.decode(Name), { required: true }) .add(TT.MetaInfo, new EvDecoder("MetaInfo") .add(TT.ContentType, (t, { nni }) => t.contentType = nni) .add(TT.FreshnessPeriod, (t, { nni }) => t.freshnessPeriod = nni) .add(TT.FinalBlock, (t, { vd }) => t.finalBlockId = vd.decode(Component))) .add(TT.Content, (t, { value }) => t.content = value) .add(TT.DSigInfo, (t, { decoder }) => t.sigInfo = decoder.decode(SigInfo), { required: true }) .add(TT.DSigValue, (t, { value, before }) => { t.sigValue = value; t.signedPortion = before; }, { required: true }); EVD.beforeObservers.push((t, tlv) => t.topTlv = tlv.tlv); /** Data packet. */ export class Data { /** * Construct from flexible arguments. * * @remarks * Arguments can include, in any order unless otherwise specified: * - Data to copy from * - {@link Name} or name URI * - {@link Data.ContentType}`(v)` * - {@link Data.FreshnessPeriod}`(v)` * - {@link Data.FinalBlock} (must appear after Name) * - `Uint8Array` as Content */ constructor(...args) { this[FIELDS] = new Fields(...args); } [FIELDS]; static decodeFrom(decoder) { const data = new Data(); EVD.decode(data[FIELDS], decoder); return data; } encodeTo(encoder) { const f = this[FIELDS]; if (f.topTlv) { encoder.encode(f.topTlv); return; } encoder.encode(Encoder.extract([ TT.Data, Encoder.extract(this.encodeSignedPortion(), (output) => f.signedPortion = output), [TT.DSigValue, f.sigValue], ], (output) => f.topTlv = output)); } encodeSignedPortion() { const { name, contentType, freshnessPeriod, finalBlockId, content, sigInfo } = this[FIELDS]; return [ name, [ TT.MetaInfo, Encoder.OmitEmpty, contentType > 0 && [TT.ContentType, NNI(contentType)], freshnessPeriod > 0 && [TT.FreshnessPeriod, NNI(freshnessPeriod)], finalBlockId && [TT.FinalBlock, finalBlockId], ], content.length > 0 && [TT.Content, content], sigInfo.encodeAs(TT.DSigInfo), ]; } /** Return the implicit digest if it's already computed. */ getImplicitDigest() { return this[FIELDS].topTlvDigest; } /** Compute the implicit digest. */ async computeImplicitDigest() { let digest = this.getImplicitDigest(); if (!digest) { const f = this[FIELDS]; if (!f.topTlv) { Encoder.encode(this); } digest = await sha256(f.topTlv); f.topTlvDigest = digest; } return digest; } /** Return the full name if the implicit digest is already computed. */ getFullName() { const digest = this.getImplicitDigest(); if (!digest) { return undefined; } return this[FIELDS].name.append(ImplicitDigest, digest); } /** Compute the full name (name plus implicit digest). */ async computeFullName() { await this.computeImplicitDigest(); return this.getFullName(); } /** * Determine if this Data can satisfy an Interest. * @returns Promise that resolves with the result. */ async canSatisfy(interest, { isCacheLookup = false } = {}) { if (isCacheLookup && interest.mustBeFresh && this.freshnessPeriod <= 0) { return false; } if (interest.canBePrefix ? interest.name.isPrefixOf(this.name) : interest.name.equals(this.name)) { return true; } if (interest.name.length === this.name.length + 1 && interest.name.get(-1).is(ImplicitDigest)) { const fullName = this.getFullName(); if (!fullName) { return interest.name.equals(await this.computeFullName()); } return interest.name.equals(fullName); } return false; } async [LLSign.OP](sign) { const signedPortion = Encoder.encode(this.encodeSignedPortion()); this[FIELDS].signedPortion = signedPortion; this.sigValue = await sign(signedPortion); } async [LLVerify.OP](verify) { const { signedPortion, sigValue } = this[FIELDS]; if (!sigValue) { throw new Error("SigValue is missing"); } if (!signedPortion) { throw new Error("SignedPortion is missing"); } await verify(signedPortion, sigValue); } } const clearingFields = ["topTlv", "topTlvDigest", "signedPortion"]; definePublicFields(Data, { name: clearingFields, contentType: clearingFields, freshnessPeriod: clearingFields, finalBlockId: clearingFields, isFinalBlock: clearingFields, content: clearingFields, sigInfo: clearingFields, sigValue: clearingFields.slice(0, 2), }); const ctorAssign = Symbol("@ndn/packet#Data.ctorAssign"); (function (Data) { /** Constructor argument to set ContentType field. */ function ContentType(v) { return { [ctorAssign](f) { return f.contentType = v; }, }; } Data.ContentType = ContentType; /** Constructor argument to set FreshnessPeriod field. */ function FreshnessPeriod(v) { return { [ctorAssign](f) { return f.freshnessPeriod = v; }, }; } Data.FreshnessPeriod = FreshnessPeriod; /** Constructor argument to set the current packet as FinalBlock. */ Data.FinalBlock = Symbol("@ndn/packet#Data.FinalBlock"); })(Data || (Data = {}));