Skip to content

Commit

Permalink
lvs: translate model to policy
Browse files Browse the repository at this point in the history
  • Loading branch information
yoursunny committed Oct 24, 2024
1 parent 03d07f6 commit 4e0422c
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 35 deletions.
1 change: 1 addition & 0 deletions pkg/lvs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"dependencies": {
"@ndn/packet": "workspace:*",
"@ndn/tlv": "workspace:^",
"@ndn/trust-schema": "workspace:^",
"@ndn/util": "workspace:*",
"tslib": "^2.8.0"
}
Expand Down
1 change: 1 addition & 0 deletions pkg/lvs/src/mod.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * as lvstlv from "./tlv";
export * from "./translate";
128 changes: 128 additions & 0 deletions pkg/lvs/src/translate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { type Component, Name } from "@ndn/packet";
import { pattern as P, TrustSchemaPolicy } from "@ndn/trust-schema";
import { assert } from "@ndn/util";

import { type ConsOption, type Constraint, type LvsModel, type Node, type PatternEdge, ValueEdge } from "./tlv";

export function toPolicy(model: LvsModel): TrustSchemaPolicy {
return new Translator(model).translate();
}

class Translator {
constructor(private readonly model: LvsModel) {}

private readonly policy = new TrustSchemaPolicy();
private readonly tagSymbols = new Map<number, string>();
private readonly wantedNodes = new Set<number>();
private lastAutoId = 0;

public translate(): TrustSchemaPolicy {
this.gatherTagSymbols();
this.gatherNodes();
this.processPatterns();
this.processRules();
return this.policy;
}

private gatherTagSymbols(): void {
this.tagSymbols.clear();
for (const { tag, identifier } of this.model.tagSymbols) {
this.tagSymbols.set(tag, identifier);
}
}

private nameTag(tag: number): string {
// TODO
const prefix = this.tagSymbols.get(tag) ?? `_TAG_${tag}`;
const suffix = tag > this.model.namedPatternCnt ? `_${++this.lastAutoId}` : "";
return `${prefix}${suffix}`;
}

private gatherNodes(): void {
this.wantedNodes.clear();
for (const node of this.model.nodes) {
if (node.ruleNames.length > 0 || node.signConstraints.length > 0) {
this.wantedNodes.add(node.id);
}
for (const sc of node.signConstraints) {
this.wantedNodes.add(sc);
}
}
}

private processPatterns(): void {
for (const id of this.wantedNodes) {
const node = this.model.nodes[id]!;
const pattern = this.trPattern(node).simplify();
for (const name of this.namePattern(node)) {
this.policy.addPattern(name, pattern);
}
}
}

private namePattern(node: Node): string[] {
if (node.ruleNames.length === 0) {
return [`_NODE_${node.id}`];
}
return node.ruleNames;
}

private trPattern(node: Node): P.Pattern {
const parts: P.Pattern[] = [];
while (node.parent !== undefined) {
const parent = this.model.nodes[node.parent]!;
const edge = parent.findEdgeTo(node.id)!;
parts.unshift(this.trEdge(edge));
node = parent;
}
return new P.ConcatPattern(parts);
}

private trEdge(edge: ValueEdge | PatternEdge): P.Pattern {
if (edge instanceof ValueEdge) {
return this.trValue(edge.value);
}

// TODO
assert(edge.constraints.length <= 1, "PatternEdge with multiple Constraints is unimplemented");
const inner = edge.constraints[0] && this.trConstraint(edge.constraints[0]);
return new P.VariablePattern(this.nameTag(edge.tag), { inner });
}

private trConstraint(cons: Constraint): P.Pattern {
return new P.AlternatePattern(
Array.from(cons.options, (co) => this.trConsOption(co)),
);
}

private trConsOption(co: ConsOption): P.Pattern {
if (co.value) {
return this.trValue(co.value);
}
if (co.tag) {
return new P.VariablePattern(this.nameTag(co.tag));
}
assert(co.call);
// TODO
assert(false, "UserFnCall is unimplemented");
}

private trValue(value: Component): P.Pattern {
return new P.ConstPattern(new Name([value]));
}

private processRules(): void {
for (const node of this.model.nodes) {
const packetIds = this.namePattern(node);
for (const sc of node.signConstraints) {
const signerIds = this.namePattern(this.model.nodes[sc]!);

for (const packetId of packetIds) {
for (const signerId of signerIds) {
this.policy.addRule(packetId, signerId);
}
}
}
}
}
}
14 changes: 7 additions & 7 deletions pkg/lvs/test-fixture/lvstlv.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import fs from "node:fs/promises";
import fs from "node:fs";
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)),
function loadLVSTLV(filename: string): () => lvstlv.LvsModel {
return () => Decoder.decode(
fs.readFileSync(path.join(import.meta.dirname, filename)),
lvstlv.LvsModel,
);
}

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

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

/** python-ndn LVS model sample "compiler and checker demonstration". */
export const pyndn2 = await loadLVSTLV("pyndn2.tlv");
export const pyndn2 = loadLVSTLV("pyndn2.tlv");
17 changes: 0 additions & 17 deletions pkg/lvs/tests/lvstlv.t.ts

This file was deleted.

39 changes: 39 additions & 0 deletions pkg/lvs/tests/pyndn.t.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Name } from "@ndn/packet";
// import { printESM, versec } from "@ndn/trust-schema";
import { console } from "@ndn/util";
import { expect, test } from "vitest";

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

test("pyndn0", () => {
const model = pyndn0();
// console.log(model.toString());

const policy = toPolicy(model);
// console.log(printESM(policy));
// console.log(versec.print(policy));
expect(policy.canSign(
new Name("/a/blog/article/math/2022/03"),
new Name("/a/blog/author/xinyu/KEY/1/admin/1"),
)).toBeTruthy();
expect(policy.canSign(
new Name("/a/blog/author/xinyu/KEY/1/admin/1"),
new Name("/a/blog/admin/admin/KEY/1/root/1"),
)).toBeTruthy();
expect(policy.canSign(
new Name("/a/blog/author/xinyu/KEY/1/admin/1"),
new Name("/a/blog/KEY/1/self/1"),
)).toBeFalsy();
});

test("pyndn1", () => {
const model = pyndn1();
console.log(model.toString());
expect(model.nodes).toHaveLength(26);
});

test("pyndn2", () => {
const model = pyndn2();
console.log(model.toString());
});
13 changes: 2 additions & 11 deletions pkg/tlv/src/impl-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,14 @@ export interface Field<T> extends Required<EvDecoder.RuleOptions> {
asString: (v: T) => Iterable<string>;
}

interface Options extends EvDecoder.RuleOptions {
required?: boolean;
repeat?: boolean;
}

export function makeField<T>(
tt: number,
key: string,
type: StructFieldType<T>,
opts: Options,
opts: EvDecoder.RuleOptions,
evd: EvDecoder<any>,
): Field<T[]> | Field<T | undefined> {
const fo = { ...opts, ...(evd ? evd.applyDefaultsToRuleOptions(opts) : {
order: tt,
required: false,
repeat: false,
}) };
const fo = evd.applyDefaultsToRuleOptions(opts);
evd.add(
tt,
fo.repeat ?
Expand Down
11 changes: 11 additions & 0 deletions pkg/trust-schema/src/schema/pattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,17 @@ export class VariablePattern extends Pattern {
public readonly inner?: Pattern;
public readonly filter?: VariablePattern.Filter;

public override simplify(): Pattern {
const inner = this.inner?.simplify();
if (inner === this.inner) {
return this;
}
return new VariablePattern(this.id, {
...this,
inner,
});
}

private *innerMatch(value: Name, input?: Map<string, Name>): Iterable<Vars> {
if (!this.inner) {
yield new Map<string, Name>();
Expand Down

0 comments on commit 4e0422c

Please sign in to comment.