Skip to content

Commit

Permalink
nfdmgmt: ControlCommand.call => invoke
Browse files Browse the repository at this point in the history
  • Loading branch information
yoursunny committed Dec 25, 2023
1 parent 2300576 commit 74ee8ca
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 116 deletions.
8 changes: 4 additions & 4 deletions integ/nfdmgmt-interop/facemgmt.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import assert from "node:assert/strict";

import { closeUplinks, openUplinks } from "@ndn/cli-common";
import { ControlCommand, ControlParameters } from "@ndn/nfdmgmt";
import { ControlParameters, invoke } from "@ndn/nfdmgmt";

await openUplinks();

const res0 = await ControlCommand.call("faces/create", {
const res0 = await invoke("faces/create", {
uri: "udp4://127.0.0.1:7001",
facePersistency: ControlParameters.FacePersistency.OnDemand,
});
Expand All @@ -16,7 +16,7 @@ console.log(body0.toString());
assert.equal(body0.facePersistency, ControlParameters.FacePersistency.OnDemand);
const faceId = body0.faceId!;

const res1 = await ControlCommand.call("faces/update", {
const res1 = await invoke("faces/update", {
faceId,
facePersistency: ControlParameters.FacePersistency.Permanent,
});
Expand All @@ -27,7 +27,7 @@ console.log(body1.toString());
assert.equal(body1.faceId, faceId);
assert.equal(body1.facePersistency, ControlParameters.FacePersistency.Permanent);

const res2 = await ControlCommand.call("faces/destroy", {
const res2 = await invoke("faces/destroy", {
faceId,
});
assert.equal(res2.statusCode, 200);
Expand Down
10 changes: 9 additions & 1 deletion packages/nfdmgmt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
This package is part of [NDNts](https://yoursunny.com/p/NDNts/), Named Data Networking libraries for the modern web.

This package implements basic support for [NFD Management protocol](https://redmine.named-data.net/projects/nfd/wiki/Management).
In particular, it enables prefix registration on NFD.
It includes both a generic variant and a NFD-specific variant with additional typing.

* [X] ControlCommand
* [X] generic: `invokeGeneric`, `ControlResponse`
* [X] NFD: `invoke`, `ControlParameters`
* [ ] StatusDataset
* [ ] NotificationStream

This implementation is validated against NFD using [nfdmgmt-interop](../../integ/nfdmgmt-interop/).

```ts
import { enableNfdPrefixReg } from "@ndn/nfdmgmt";
Expand Down
49 changes: 49 additions & 0 deletions packages/nfdmgmt/src/control-command-generic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Component, digestSigning, Interest, Name, SignedInterestPolicy, type Signer, TT } from "@ndn/packet";
import { Decoder, type Encodable, Encoder } from "@ndn/tlv";

import { ControlResponse } from "./control-response";
import { CommonOptions } from "./options";

const defaultSIP = new SignedInterestPolicy(SignedInterestPolicy.Nonce(), SignedInterestPolicy.Time());

export interface ControlCommandOptions extends CommonOptions {
/**
* Command Interest signer.
* Default is digest signing.
*/
signer?: Signer;

/**
* Signed Interest policy for the command Interest.
* Default is including SigNonce and SigTime in the signed Interest.
*/
signedInterestPolicy?: SignedInterestPolicy;
}

/**
* Invoke generic ControlCommand and wait for response.
* @param command command name components.
* @param params command parameters.
* @param opts target prefix and other options.
* @returns command response.
*/
export async function invokeGeneric(command: string, params: Encodable, opts: ControlCommandOptions = {}): Promise<ControlResponse> {
const { endpoint, prefix, verifier } = CommonOptions.applyDefaults(opts);
const {
signer = digestSigning,
signedInterestPolicy = defaultSIP,
} = opts;

const interest = new Interest(new Name([
...prefix.comps,
...command.split("/"),
new Component(TT.GenericNameComponent, Encoder.encode(params)),
]));
await signedInterestPolicy.makeSigner(signer).sign(interest);

const data = await endpoint.consume(interest, {
describe: `ControlCommand(${command})`,
verifier,
});
return Decoder.decode(data.content, ControlResponse);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,41 @@ import { Name, TT } from "@ndn/packet";
import { Decoder, type Encodable, type Encoder, EvDecoder, NNI } from "@ndn/tlv";
import { toUtf8 } from "@ndn/util";

import { type ControlCommandOptions, invokeGeneric } from "./control-command-generic";
import { type ControlResponse } from "./control-response";

/**
* Pick fields from ControlParameters.Fields.
* R are required.
* O are optional.
*/
type CP<R extends keyof ControlParameters.Fields, O extends keyof ControlParameters.Fields> =
Required<Pick<ControlParameters.Fields, R>> & Pick<ControlParameters.Fields, O>;

/** Declare required and optional fields of each command. */
interface Commands {
"faces/create": CP<"uri", "localUri" | "facePersistency" | "baseCongestionMarkingInterval" |
"defaultCongestionThreshold" | "mtu" | "flags" | "mask">;
"faces/update": CP<never, "faceId" | "facePersistency" | "baseCongestionMarkingInterval" |
"defaultCongestionThreshold" | "flags" | "mask">;
"faces/destroy": CP<"faceId", never>;
"strategy-choice/set": CP<"name" | "strategy", never>;
"strategy-choice/unset": CP<"name", never>;
"rib/register": CP<"name", "faceId" | "origin" | "cost" | "flags" | "expirationPeriod">;
"rib/unregister": CP<"name", "faceId" | "origin">;
}

/**
* Invoke NFD ControlCommand and wait for response.
* @param command command module and verb.
* @param params command parameters.
* @param opts other options.
* @returns command response.
*/
export async function invoke<C extends keyof Commands>(command: C, params: Commands[C], opts: ControlCommandOptions = {}): Promise<ControlResponse> {
return invokeGeneric(command, new ControlParameters(params), opts);
}

/** NFD Management ControlParameters struct. */
export class ControlParameters {
public static decodeFrom(decoder: Decoder): ControlParameters {
Expand Down
90 changes: 0 additions & 90 deletions packages/nfdmgmt/src/control-command.ts

This file was deleted.

5 changes: 3 additions & 2 deletions packages/nfdmgmt/src/mod.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./control-command";
export * from "./control-parameters";
export * from "./control-command-generic";
export * from "./control-command-nfd";
export * from "./control-response";
export { localhopPrefix, localhostPrefix, getPrefix } from "./options";
export * from "./prefix-reg";
export * from "./sign-interest-02";
41 changes: 41 additions & 0 deletions packages/nfdmgmt/src/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Endpoint } from "@ndn/endpoint";
import { Name, noopSigning, type Verifier } from "@ndn/packet";

export const localhostPrefix = new Name("/localhost/nfd");
export const localhopPrefix = new Name("/localhop/nfd");

/**
* Determine the NFD management prefix.
* @param isLocal whether the client is connected to a NFD local face.
* @returns NFD management prefix.
*/
export function getPrefix(isLocal = false) {
return isLocal ? localhostPrefix : localhopPrefix;
}

export interface CommonOptions {
/** Endpoint for communication. */
endpoint?: Endpoint;

/**
* NFD management prefix.
* @default getPrefix()
*/
prefix?: Name;

/**
* Data verifier.
* Default is no verification.
*/
verifier?: Verifier;
}

export namespace CommonOptions {
export function applyDefaults({
endpoint = new Endpoint(),
prefix = localhostPrefix,
verifier = noopSigning,
}: CommonOptions): Required<CommonOptions> {
return { endpoint, prefix, verifier };
}
}
17 changes: 9 additions & 8 deletions packages/nfdmgmt/src/prefix-reg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { Closers } from "@ndn/util";
import map from "obliterator/map.js";
import type { Except } from "type-fest";

import { ControlCommand } from "./control-command";
import { ControlParameters } from "./control-parameters";
import type { ControlCommandOptions } from "./control-command-generic";
import { ControlParameters, invoke } from "./control-command-nfd";
import { getPrefix } from "./options";

type CommandOptions = Except<ControlCommand.Options, "endpoint">;
type CommandOptions = Except<ControlCommandOptions, "endpoint" | "prefix">;
type RouteOptions = Pick<ControlParameters.Fields, "origin" | "cost" | "flags">;
type Options = CommandOptions & RouteOptions & {
retry?: ReadvertiseDestination.RetryOptions;
Expand All @@ -32,7 +33,7 @@ interface State {
}

class NfdPrefixReg extends ReadvertiseDestination<State> {
private readonly commandOptions: CommandOptions;
private readonly commandOptions: Except<ControlCommandOptions, "endpoint">;
private readonly routeOptions: RouteOptions;
private readonly refreshInterval: number | false;
private readonly preloadCertName?: Name;
Expand All @@ -44,7 +45,7 @@ class NfdPrefixReg extends ReadvertiseDestination<State> {
super(opts.retry);

this.commandOptions = {
commandPrefix: ControlCommand.getPrefix(face.attributes.local),
prefix: getPrefix(face.attributes.local),
...opts,
};

Expand All @@ -69,7 +70,7 @@ class NfdPrefixReg extends ReadvertiseDestination<State> {
super.disable();
}

private async tap(): Promise<[opts: ControlCommand.Options, untap: () => void]> {
private async tap(): Promise<[opts: ControlCommandOptions, untap: () => void]> {
const tapFace = TapFace.create(this.face);
tapFace.addRoute("/");
const endpoint = new Endpoint({
Expand Down Expand Up @@ -131,7 +132,7 @@ class NfdPrefixReg extends ReadvertiseDestination<State> {
protected override async doAdvertise(name: Name, state: State) {
const [opts, untap] = await this.tap();
try {
const cr = await ControlCommand.call("rib/register", {
const cr = await invoke("rib/register", {
name,
origin: this.routeOptions.origin,
cost: this.routeOptions.cost,
Expand Down Expand Up @@ -168,7 +169,7 @@ class NfdPrefixReg extends ReadvertiseDestination<State> {
}
const [opts, untap] = await this.tap();
try {
const cr = await ControlCommand.call("rib/unregister", {
const cr = await invoke("rib/unregister", {
name,
origin: this.routeOptions.origin,
}, opts);
Expand Down
16 changes: 5 additions & 11 deletions packages/nfdmgmt/tests/prefix-reg.t.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,34 @@ import { Forwarder, type FwFace, FwPacket } from "@ndn/fw";
import { NoopFace } from "@ndn/fw/test-fixture/noop-face";
import { Certificate, generateSigningKey, KeyChain, ValidityPeriod } from "@ndn/keychain";
import { Bridge } from "@ndn/l3face/test-fixture/bridge";
import { Component, Data, Interest, Name, ParamsDigest } from "@ndn/packet";
import { Component, Data, Interest, type Name, ParamsDigest } from "@ndn/packet";
import { Decoder, Encoder, NNI } from "@ndn/tlv";
import { Closers, delay } from "@ndn/util";
import { TypedEventTarget } from "typescript-event-target";
import { afterEach, expect, test } from "vitest";

import { ControlCommand, ControlParameters, ControlResponse, enableNfdPrefixReg } from "..";
import { ControlParameters, ControlResponse, enableNfdPrefixReg, localhopPrefix, localhostPrefix } from "..";

const closers = new Closers();
afterEach(closers.close);

interface Row {
faceIsLocal?: boolean;
commandPrefix?: Name;
expectedPrefix: Name;
}

const TABLE: Row[] = [
{
faceIsLocal: true,
expectedPrefix: ControlCommand.localhostPrefix,
expectedPrefix: localhostPrefix,
},
{
faceIsLocal: false,
expectedPrefix: ControlCommand.localhopPrefix,
},
{
commandPrefix: new Name("/Q"),
expectedPrefix: new Name("/Q"),
expectedPrefix: localhopPrefix,
},
];

test.each(TABLE)("reg %#", async ({ faceIsLocal, commandPrefix, expectedPrefix }) => {
test.each(TABLE)("reg %#", async ({ faceIsLocal, expectedPrefix }) => {
const fw = Forwarder.create();
closers.push(fw);

Expand Down Expand Up @@ -74,7 +69,6 @@ test.each(TABLE)("reg %#", async ({ faceIsLocal, commandPrefix, expectedPrefix }
const uplink = fw.addFace(uplinkL3, { local: faceIsLocal });
closers.push(uplink);
enableNfdPrefixReg(uplink, {
commandPrefix,
retry: {
minTimeout: 1,
maxTimeout: 1,
Expand Down

0 comments on commit 74ee8ca

Please sign in to comment.