@ndn/packet
Version:
NDNts: Network Layer Packets
228 lines (227 loc) • 8.18 kB
JavaScript
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 = {}));