Skip to content

Commit

Permalink
lvs: TLV decoder
Browse files Browse the repository at this point in the history
  • Loading branch information
yoursunny committed Oct 17, 2024
1 parent 4eb1f11 commit 5a5ea12
Show file tree
Hide file tree
Showing 20 changed files with 245 additions and 32 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
},
"packageManager": "[email protected]+sha512.22721b3a11f81661ae1ec68ce1a7b879425a1ca5b991c975b074ac220b187ce56c708fe5db69f4c962c989452eee76c82877f4ee80f474cebd61ee13461b6228",
"devDependencies": {
"@types/node": "^20.16.11",
"@types/node": "^20.16.12",
"@types/wtfnode": "^0.7.3",
"@typescript/lib-dom": "npm:@types/[email protected]",
"@vitest/coverage-v8": "^2.1.3",
Expand Down
22 changes: 22 additions & 0 deletions pkg/lvs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# @ndn/lvs

This package is part of [NDNts](https://yoursunny.com/p/NDNts/), Named Data Networking libraries for the modern web.

This package implements [python-ndn Light VerSec (LVS)](https://python-ndn.readthedocs.io/en/latest/src/lvs/lvs.html) binary format.
It is still in design stage and not yet usable.

To compile LVS textual format to binary format, you need to use python-ndn:

```bash
# create Python virtual environment
python3.11 -m venv ~/lvs.venv
source ~/lvs.venv/bin/activate

# install python-ndn
pip install 'python-ndn[dev] @ git+https://github.com/named-data/python-ndn@44ef2cac915041d75cdb64a63355bd2cb0194913'

# run the compiler
python ./pkg/lvs/compile.py <~/lvs-model.txt >~/lvs-model.tlv
```

The compiled binary TLV will be importable into NDNts in the future.
File renamed without changes.
34 changes: 34 additions & 0 deletions pkg/lvs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@ndn/lvs",
"version": "0.0.0",
"description": "NDNts: Light VerSec",
"keywords": [
"NDN",
"Named Data Networking"
],
"author": "Junxiao Shi <[email protected]>",
"license": "ISC",
"files": [
"lib"
],
"type": "module",
"main": "src/mod.ts",
"module": "lib/mod_browser.js",
"sideEffects": false,
"homepage": "https://yoursunny.com/p/NDNts/",
"repository": {
"type": "git",
"url": "https://github.com/yoursunny/NDNts.git",
"directory": "pkg/lvs"
},
"publishConfig": {
"main": "lib/mod_node.js",
"types": "lib/mod.d.ts"
},
"dependencies": {
"@ndn/packet": "workspace:*",
"@ndn/tlv": "workspace:^",
"@ndn/util": "workspace:*",
"tslib": "^2.8.0"
}
}
1 change: 1 addition & 0 deletions pkg/lvs/src/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as lvstlv from "./tlv";
100 changes: 100 additions & 0 deletions pkg/lvs/src/tlv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { StructFieldComponentNested } from "@ndn/packet";
import { StructBuilder, StructFieldNNI, StructFieldText, StructFieldType } from "@ndn/tlv";
import { assert } from "@ndn/util";

export const TT = {
ComponentValue: 0x21,
PatternTag: 0x23,
NodeId: 0x25,
UserFnId: 0x27,
Identifier: 0x29,
UserFnCall: 0x31,
FnArgs: 0x33,
ConsOption: 0x41,
Constraint: 0x43,
ValueEdge: 0x51,
PatternEdge: 0x53,
KeyNodeId: 0x55,
ParentId: 0x57,
Version: 0x61,
Node: 0x63,
TagSymbol: 0x67,
NamedPatternNum: 0x69,
} as const;

export const BinfmtVersion = 0x00011000;

function makeDiscriminatedUnion<U extends {}>(sb: StructBuilder<U>): void {
const keys = StructBuilder.keysOf(sb);
StructBuilder.evdOf(sb).afterObservers.push((target) => {
let cnt = 0;
for (const key of keys) {
cnt += Number(target[key] !== undefined);
}
assert(cnt === 1, `exactly one of ${keys.join(" ")} must be set`);
});
}

const buildValueEdge = new StructBuilder("ValueEdge", TT.ValueEdge)
.add(TT.NodeId, "dest", StructFieldNNI, { required: true })
.add(TT.ComponentValue, "value", StructFieldComponentNested, { required: true });
export class ValueEdge extends buildValueEdge.baseClass<ValueEdge>() {}
buildValueEdge.subclass = ValueEdge;

const buildUserFnArg = new StructBuilder("UserFnArg", TT.FnArgs) // XXX TLV-TYPE
.add(TT.ComponentValue, "value", StructFieldComponentNested)
.add(TT.PatternTag, "tag", StructFieldNNI);
makeDiscriminatedUnion(buildUserFnArg);
export class UserFnArg extends buildUserFnArg.baseClass<UserFnArg>() {}
buildUserFnArg.subclass = UserFnArg;

const buildUserFnCall = new StructBuilder("UserFnCall", TT.UserFnCall)
.add(TT.UserFnId, "fn", StructFieldText)
.add(TT.FnArgs, "args", StructFieldType.wrap(UserFnArg), { repeat: true });
export class UserFnCall extends buildUserFnCall.baseClass<UserFnCall>() {}
buildUserFnCall.subclass = UserFnCall;

const buildConsOption = new StructBuilder("ConsOption", TT.ConsOption)
.add(TT.ComponentValue, "value", StructFieldComponentNested)
.add(TT.PatternTag, "tag", StructFieldNNI)
.add(TT.UserFnCall, "call", StructFieldType.wrap(UserFnCall));
makeDiscriminatedUnion(buildConsOption);
export class ConsOption extends buildConsOption.baseClass<ConsOption>() {}
buildConsOption.subclass = ConsOption;

const buildConstraint = new StructBuilder("Constraint", TT.Constraint)
.add(TT.ConsOption, "options", StructFieldType.wrap(ConsOption), { repeat: true });
export class Constraint extends buildConstraint.baseClass<Constraint>() {}
buildConstraint.subclass = Constraint;

const buildPatternEdge = new StructBuilder("PatternEdge", TT.PatternEdge)
.add(TT.NodeId, "dest", StructFieldNNI, { required: true })
.add(TT.PatternTag, "tag", StructFieldNNI, { required: true })
.add(TT.Constraint, "constraints", StructFieldType.wrap(Constraint), { repeat: true });
export class PatternEdge extends buildPatternEdge.baseClass<PatternEdge>() {}
buildPatternEdge.subclass = PatternEdge;

const buildNode = new StructBuilder("Node", TT.Node)
.add(TT.NodeId, "id", StructFieldNNI, { required: true })
.add(TT.ParentId, "parent", StructFieldNNI)
.add(TT.Identifier, "ruleNames", StructFieldText, { repeat: true })
.add(TT.ValueEdge, "valueEdges", StructFieldType.wrap(ValueEdge), { repeat: true })
.add(TT.PatternEdge, "patternEdges", StructFieldType.wrap(PatternEdge), { repeat: true })
.add(TT.KeyNodeId, "signConstraints", StructFieldNNI, { repeat: true });
export class Node extends buildNode.baseClass<Node>() {}
buildNode.subclass = Node;

const buildTagSymbol = new StructBuilder("TagSymbol", TT.TagSymbol)
.add(TT.PatternTag, "tag", StructFieldNNI, { required: true })
.add(TT.Identifier, "identifier", StructFieldText, { required: true });
export class TagSymbol extends buildTagSymbol.baseClass<TagSymbol>() {}
buildTagSymbol.subclass = TagSymbol;

const buildLvsModel = new StructBuilder("LvsModel")
.add(TT.Version, "version", StructFieldNNI, { required: true })
.add(TT.NodeId, "startId", StructFieldNNI, { required: true })
.add(TT.NamedPatternNum, "namedPatternCnt", StructFieldNNI, { required: true })
.add(TT.Node, "nodes", StructFieldType.wrap(Node), { repeat: true })
.add(TT.TagSymbol, "tagSymbols", StructFieldType.wrap(TagSymbol), { repeat: true });
export class LvsModel extends buildLvsModel.baseClass<LvsModel>() {}
buildLvsModel.subclass = LvsModel;
22 changes: 22 additions & 0 deletions pkg/lvs/test-fixture/lvstlv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import fs from "node:fs/promises";
import path from "node:path";

import { Decoder } from "@ndn/tlv";

import { lvstlv } from "..";

async function loadLVSTLV(filename: string): Promise<lvstlv.LvsModel> {
return Decoder.decode(
await fs.readFile(path.join(import.meta.dirname, filename)),
lvstlv.LvsModel,
);
}

/** python-ndn LVS model sample "quick example". */
export const pyndn0 = await loadLVSTLV("pyndn0.tlv");

/** python-ndn LVS model sample "signing key suggesting". */
export const pyndn1 = await loadLVSTLV("pyndn1.tlv");

/** python-ndn LVS model sample "compiler and checker demonstration". */
export const pyndn2 = await loadLVSTLV("pyndn2.tlv");
14 changes: 14 additions & 0 deletions pkg/lvs/test-fixture/pyndn0.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// https://github.com/named-data/python-ndn/blob/44ef2cac915041d75cdb64a63355bd2cb0194913/docs/src/lvs/lvs.rst#quick-example

// Site prefix is "/a/blog"
#site: "a"/"blog"
// The trust anchor name is of pattern /a/blog/KEY/<key-id>/<issuer>/<cert-id>
#root: #site/#KEY
// Posts are signed by some author's key
#article: #site/"article"/category/year/month <= #author
// An author's key is signed by an admin's key
#author: #site/role/author/#KEY & { role: "author" } <= #admin
// An admin's key is signed by the root key
#admin: #site/"admin"/admin/#KEY <= #root

#KEY: "KEY"/_/_/_
Binary file added pkg/lvs/test-fixture/pyndn0.tlv
Binary file not shown.
7 changes: 7 additions & 0 deletions pkg/lvs/test-fixture/pyndn1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// https://github.com/named-data/python-ndn/blob/44ef2cac915041d75cdb64a63355bd2cb0194913/docs/src/lvs/details.rst#signing-key-suggesting

#KEY: "KEY"/_/_/_
#article: /"article"/_topic/_ & { _topic: "eco" | "spo" } <= #author
#author: /site/"author"/_/#KEY <= #admin
#admin: /site/"admin"/_/#KEY <= #anchor
#anchor: /site/#KEY & {site: "la" | "ny" }
Binary file added pkg/lvs/test-fixture/pyndn1.tlv
Binary file not shown.
8 changes: 8 additions & 0 deletions pkg/lvs/test-fixture/pyndn2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// https://github.com/named-data/python-ndn/blob/44ef2cac915041d75cdb64a63355bd2cb0194913/docs/src/lvs/demonstration.rst

#KEY: "KEY"/_/_/_
#site: "lvs-test"
#article: #site/"article"/author/post/_version & {_version: $eq_type("v=0")} <= #author
#author: #site/"author"/author/"KEY"/_/admin/_ <= #admin
#admin: #site/"admin"/admin/#KEY <= #root
#root: #site/#KEY
Binary file added pkg/lvs/test-fixture/pyndn2.tlv
Binary file not shown.
20 changes: 20 additions & 0 deletions pkg/lvs/tests/lvstlv.t.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { console } from "@ndn/util";
import { expect, test } from "vitest";

import { lvstlv } from "..";
import { pyndn0, pyndn1, pyndn2 } from "../test-fixture/lvstlv";

test("pyndn0", () => {
console.log(pyndn0.toString());
expect(pyndn0.version).toBe(lvstlv.BinfmtVersion);
});

test("pyndn1", () => {
console.log(pyndn1.toString());
expect(pyndn1.version).toBe(lvstlv.BinfmtVersion);
});

test("pyndn2", () => {
console.log(pyndn2.toString());
expect(pyndn2.version).toBe(lvstlv.BinfmtVersion);
});
2 changes: 1 addition & 1 deletion pkg/ndncert/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"b64-lite": "^1.4.0",
"imap-emails": "^1.0.4",
"nodemailer": "^6.9.15",
"p-timeout": "^6.1.2",
"p-timeout": "^6.1.3",
"tslib": "^2.8.0",
"type-fest": "^4.26.1",
"typescript-event-target": "^1.1.1"
Expand Down
2 changes: 1 addition & 1 deletion pkg/nfdmgmt/src/control-command-nfd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class ControlParameters extends buildControlParameters.baseClass<ControlP

constructor(value: ControlParameters.Fields = {}) {
super();
for (const key of buildControlParameters.keys) {
for (const key of StructBuilder.keysOf(buildControlParameters)) {
(this as any)[key] = (value as any)[key];
}
}
Expand Down
15 changes: 10 additions & 5 deletions pkg/tlv/src/struct-builder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { assert } from "@ndn/util";
import type { Constructor, IfNever, Simplify } from "type-fest";
import type { Constructor, Except, IfNever, Simplify } from "type-fest";

import { type Decodable, type Decoder } from "./decoder";
import { type EncodableObj, type Encoder } from "./encoder";
Expand Down Expand Up @@ -93,14 +93,19 @@ export class StructBuilder<U extends {}> {
* Subclass constructor.
* This must be assigned, otherwise decoding function will not work.
*/
public subclass?: Constructor<U, []>;
public subclass?: Constructor<U, []> & Decodable<U>;
private readonly fields: Array<Field<any>> = [];
private readonly flagBits: FlagBitDesc[] = [];
private readonly EVD: EvDecoder<any>;

/** Return field names. */
public get keys(): string[] {
return this.fields.map(({ key }) => key);
/** Access EvDecoder for certain customizations. */
public static evdOf<U extends {}>(sb: StructBuilder<U>): Except<EvDecoder<U>, "add"> {
return sb.EVD;
}

/** Retrieve field names. */
public static keysOf<U extends {}>(sb: StructBuilder<U>): Array<keyof U> {
return sb.fields.map(({ key }) => key as keyof U);
}

/**
Expand Down
1 change: 1 addition & 0 deletions pkg/tlv/src/struct-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export const StructFieldText: StructFieldType<string> = {
newValue: () => "",
encode: toUtf8,
decode: ({ text }) => text,
asString: (value) => JSON.stringify(value),
};

/**
Expand Down
6 changes: 3 additions & 3 deletions pkg/tlv/tests/struct-builder.t.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ test("basic", () => {
.add(0x44, "a44", StructFieldNNI, { required: true, repeat: true });
class MyType extends b.baseClass<MyType>() {}
b.subclass = MyType;
expect(b.keys).toEqual(["a41", "a42", "a43", "a44"]);
expect(StructBuilder.keysOf(b)).toEqual(["a41", "a42", "a43", "a44"]);

const myObj = new MyType();
expect(myObj.a41).toBeUndefined();
Expand Down Expand Up @@ -275,7 +275,7 @@ test("types", () => {
"a41=0",
"a42=0",
"a43=0(unknown)",
"a44=",
"a44=\"\"",
"a45=",
"a53=false",
].join(" "));
Expand All @@ -294,7 +294,7 @@ test("types", () => {
`a41=${0xAA41}`,
`a42=${0xAA42n}`,
"a43=2(Q)",
"a44=AA44",
"a44=\"AA44\"",
"a45=AA45",
"a50=false",
"a51=true",
Expand Down
21 changes: 0 additions & 21 deletions pkg/trust-schema/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,27 +151,6 @@ await keyChain.insertCert(rootCert);
const schema = new TrustSchema(policy, [rootCert]);
```

## LVS Binary Format

This package intends to support importing [python-ndn Light VerSec (LVS)](https://python-ndn.readthedocs.io/en/latest/src/lvs/lvs.html) binary format.
This feature is still in design stage.

To compile LVS textual format to binary format, you need to use python-ndn:

```bash
# create Python virtual environment
python3.11 -m venv ~/lvs.venv
source ~/lvs.venv/bin/activate

# install python-ndn
pip install 'python-ndn[dev] @ git+https://github.com/named-data/python-ndn@177844e6142bd4616929bbbfa10a857569fbdf5b'

# run the compiler
python ./lvs/compile.py <~/lvs-model.txt >~/lvs-model.tlv
```

The compiled binary TLV will be importable into NDNts in the future.

## Trust Schema Signer

`TrustSchemaSigner` type can automatically select a signer among available certificates in the KeyChain.
Expand Down

0 comments on commit 5a5ea12

Please sign in to comment.