@ndn/tlv
This package is part of NDNts, Named Data Networking libraries for the modern web.
This package implements Type-Length-Value structure encoder and decoder as specified in NDN Packet Format v0.3.
It has full support for TLV evolvability guidelines.
import { Encoder, Decoder, EvDecoder, NNI, StructBuilder, StructFieldNNI, StructFieldText } from "@ndn/tlv";
import { Name, TT as l3TT, StructFieldName } from "@ndn/packet";
import assert from "node:assert/strict";
Encoder
The Encoder has an internal buffer of Uint8Array type.
It prepends any encodable items to the internal buffer, and reallocates a larger buffer when necessary.
let encoder = new Encoder();
encoder.encode(new Name("/A"));
assert.deepEqual(encoder.output, Uint8Array.of(0x07, 0x03, 0x08, 0x01, 0x41));
encoder = new Encoder();
encoder.encode([0xB0, Uint8Array.of(0xC0, 0xC1)]);
assert.deepEqual(encoder.output, Uint8Array.of(0xB0, 0x02, 0xC0, 0xC1));
encoder.encode(NNI(0x200110));
assert.deepEqual(encoder.output, Uint8Array.of(0x00, 0x20, 0x01, 0x10, 0xB0, 0x02, 0xC0, 0xC1));
encoder = new Encoder();
encoder.encode([0xB0, Uint8Array.of(0xC0, 0xC1), new Name("/A")]);
assert.deepEqual(encoder.output,
Uint8Array.of(0xB0, 0x07, 0xC0, 0xC1, 0x07, 0x03, 0x08, 0x01, 0x41));
Decoder
The Decoder is a basic sequential decoder.
let decoder = new Decoder(Uint8Array.of(0x08, 0x01, 0x41, 0xFF));
const { type, length, value } = decoder.read();
assert.equal(type, 0x08);
assert.equal(length, 1);
assert.deepEqual(value, Uint8Array.of(0x41));
assert.throws(() => decoder.read());
decoder = new Decoder(Uint8Array.of(0x07, 0x03, 0x08, 0x01, 0x41));
const name = decoder.decode(Name);
assert(name instanceof Name);
assert.equal(name.toString(), "/8=A");
EvDecoder
The EvDecoder is a decoder that is aware of TLV evolvability guidelines.
It's used to implement decoding functions of TLV objects, such as Interest.decodeFrom.
Suppose we want to decode Adjacency type in NLSR's LSDB Dataset:
Adjacency = ADJACENCY-TYPE TLV-LENGTH
Name
Uri
Cost
Uri = URI-TYPE TLV-LENGTH *VCHAR
Cost = COST-TYPE TLV-LENGTH nonNegativeInteger
ADJACENCY-TYPE = 0x84
URI-TYPE = 0x8D
COST-TYPE = 0x8C
class Adjacency {
public name = new Name();
public uri = "";
public cost = 0;
}
const TT = {
...l3TT,
Adjacency: 0x84,
Cost: 0x8C,
Uri: 0x8D,
} as const;
const EVD = new EvDecoder<Adjacency>("Adjacency", TT.Adjacency)
.add(TT.Name, (t, { decoder }) => t.name = decoder.decode(Name), { required: true })
.add(TT.Uri, (t, { text }) => t.uri = text, { required: true })
.add(TT.Cost, (t, { nni }) => t.cost = nni, { required: true });
const adjacencyWire = Uint8Array.of(
0x84, 0x0D,
0x07, 0x03, 0x08, 0x01, 0x41,
0x8D, 0x01, 0x42,
0xF0, 0x00,
0x8C, 0x01, 0x80,
);
const adjacency = EVD.decode(new Adjacency(), new Decoder(adjacencyWire));
assert.equal(adjacency.name.toString(), "/8=A");
assert.equal(adjacency.uri, "B");
assert.equal(adjacency.cost, 128);
StructBuilder
The StructBuilder is a helper for defining a class that represents a TLV structure.
It allows you to define the typing, constructor, encoder, and decoder, while writing each field only once.
const buildAdj = new StructBuilder("Adjacency", TT.Adjacency)
.add(TT.Name, "name", StructFieldName, { required: true })
.add(TT.Uri, "uri", StructFieldText, { required: true })
.add(TT.Cost, "cost", StructFieldNNI, { required: true });
class Adj extends buildAdj.baseClass<Adj>() {}
buildAdj.subclass = Adj;
const adj0 = new Adj();
adj0.name = new Name("/A");
adj0.uri = "B";
adj0.cost = 128;
const adj0Wire = Encoder.encode(adj0);
assert.deepEqual(adj0Wire, Uint8Array.of(
0x84, 0x0B,
0x07, 0x03, 0x08, 0x01, 0x41,
0x8D, 0x01, 0x42,
0x8C, 0x01, 0x80,
));
const adj1 = Decoder.decode(adjacencyWire, Adj);
assert.equal(adj1.name.toString(), "/8=A");
assert.equal(adj1.uri, "B");
assert.equal(adj1.cost, 128);
StructBuilder enables rapid development of TLV based structures, but is less flexible than writing code with Encoder, Decoder, and EvDecoder.
Some limitations are:
- You cannot write JSDoc for individual fields.
- You cannot decode multiple TLV-TYPE numbers into the same field (counterexample:
Name with typed name components).
- You cannot encode the structure with different TLV-TYPE numbers (counterexample:
SigInfo encoded as either ISigInfo or DSigInfo).