From 76a0320f99cad0f161e150e65a2039ca21c8b824 Mon Sep 17 00:00:00 2001 From: Xinyu Ma Date: Sat, 24 Feb 2024 03:19:38 -0800 Subject: [PATCH] Add NTSchema's SVS (in progress) --- deno.lock | 5 +- src/namespace/base-node.ts | 4 +- src/namespace/expressing-point.ts | 13 +-- src/namespace/mod.ts | 1 + src/namespace/nt-schema.ts | 6 +- src/namespace/segmented-object/fetcher.ts | 2 +- .../segmented-object/segmented-object.test.ts | 6 +- .../segmented-object/segmented-object.ts | 8 +- src/namespace/sync/sync.ts | 79 +++++++++++++++++++ 9 files changed, 105 insertions(+), 19 deletions(-) create mode 100644 src/namespace/sync/sync.ts diff --git a/deno.lock b/deno.lock index 8e57a11..94beea4 100644 --- a/deno.lock +++ b/deno.lock @@ -200,11 +200,12 @@ "npm:event-iterator@^2.0.0", "npm:eventemitter3@^5.0.1", "npm:it-pushable@^3.2.3", - "npm:jose@^5.2.1", + "npm:jose@^5.2.2", "npm:p-defer@^4.0.0", "npm:streaming-iterables@^8.0.1", "npm:tslib@^2.6.2", - "npm:type-fest@^4.10.2", + "npm:type-fest@^4.10.3", + "npm:wait-your-turn@^1.0.1", "npm:y-protocols@^1.0.6", "npm:yjs@^13.6.12" ] diff --git a/src/namespace/base-node.ts b/src/namespace/base-node.ts index bac600d..637909c 100644 --- a/src/namespace/base-node.ts +++ b/src/namespace/base-node.ts @@ -36,11 +36,13 @@ export class BaseNode { public verifyPacket( matched: schemaTree.StrictMatch, pkt: Verifier.Verifiable, - deadline: number, + deadline: number | undefined, + context: Record, ) { console.warn(`Silently drop unverified packet ${matched.name.toString()}`); pkt; deadline; + context; return Promise.resolve(false); } diff --git a/src/namespace/expressing-point.ts b/src/namespace/expressing-point.ts index c3fd70a..2fc55f6 100644 --- a/src/namespace/expressing-point.ts +++ b/src/namespace/expressing-point.ts @@ -17,9 +17,10 @@ export interface ExpressingPointEvents extends BaseNodeEvents { verify( args: { target: schemaTree.StrictMatch; - deadline: number; + deadline: number | undefined; pkt: Verifier.Verifiable; prevResult: VerifyResult; + context: Record; }, ): Promise; @@ -69,7 +70,8 @@ export class ExpressingPoint extends BaseNode { public override async verifyPacket( matched: schemaTree.StrictMatch, pkt: Verifier.Verifiable, - deadline: number, + deadline: number | undefined, + context: Record, ) { const verifyResult = await this.onVerify.chain( VerifyResult.Unknown, @@ -80,7 +82,7 @@ export class ExpressingPoint extends BaseNode { prevResult: ret, }], ), - { target: matched, pkt, deadline, prevResult: VerifyResult.Unknown }, + { target: matched, pkt, deadline, prevResult: VerifyResult.Unknown, context }, ); return verifyResult >= VerifyResult.Pass; } @@ -103,7 +105,7 @@ export class ExpressingPoint extends BaseNode { // Signed Interests are required to carry AppParam, but may be zero length. // To guarantee everything is good in case the underlying library returns `undefined` when zero length, check both. if (interest.appParameters || interest.sigInfo) { - if (!await this.verifyPacket(matched, interest, deadline)) { + if (!await this.verifyPacket(matched, interest, deadline, {})) { // Unverified Interest. Drop return; } @@ -136,6 +138,7 @@ export class ExpressingPoint extends BaseNode { lifetimeMs?: number; deadline?: number; verifier?: Verifier; + verificationContext?: Record; } = {}, ): Promise { // Construct Interest, but without signing, so the parameter digest is not there @@ -200,7 +203,7 @@ export class ExpressingPoint extends BaseNode { signal: opts.abortSignal as any, retx: this.config.retx, // Note: the verifier is at the LeafNode if CanBePrefix is set - verifier: opts.verifier ?? this.handler!.getVerifier(deadline), + verifier: opts.verifier ?? this.handler!.getVerifier(deadline, opts.verificationContext), }); // (no await) Save (cache) the data in the storage diff --git a/src/namespace/mod.ts b/src/namespace/mod.ts index a3ca918..a710ff1 100644 --- a/src/namespace/mod.ts +++ b/src/namespace/mod.ts @@ -5,3 +5,4 @@ export * from './nt-schema.ts'; export * from './base-node.ts'; export * from './expressing-point.ts'; export * from './leaf-node.ts'; +export * from './segmented-object/segmented-object.ts'; diff --git a/src/namespace/nt-schema.ts b/src/namespace/nt-schema.ts index cb5d47f..6ba33f9 100644 --- a/src/namespace/nt-schema.ts +++ b/src/namespace/nt-schema.ts @@ -16,7 +16,7 @@ export enum VerifyResult { export interface NamespaceHandler { get endpoint(): Endpoint | undefined; get attachedPrefix(): Name | undefined; - getVerifier(deadline: number): Verifier; + getVerifier(deadline: number | undefined, verificationContext?: Record): Verifier; storeData(data: Data): Promise; } @@ -42,14 +42,14 @@ export class NtSchema implements NamespaceHandler, AsyncDisposable { return schemaTree.match(this.tree, name.slice(prefixLength)); } - public getVerifier(deadline: number): Verifier { + public getVerifier(deadline: number | undefined, verificationContext?: Record): Verifier { return { verify: async (pkt: Verifier.Verifiable) => { const matched = this.match(pkt.name); if (!matched || !matched.resource) { throw new Error('Unexpected packet'); } - if (!await schemaTree.call(matched, 'verifyPacket', pkt, deadline)) { + if (!await schemaTree.call(matched, 'verifyPacket', pkt, deadline, verificationContext ?? {})) { throw new Error('Unverified packet'); } }, diff --git a/src/namespace/segmented-object/fetcher.ts b/src/namespace/segmented-object/fetcher.ts index 1940af5..42f37a1 100644 --- a/src/namespace/segmented-object/fetcher.ts +++ b/src/namespace/segmented-object/fetcher.ts @@ -6,7 +6,7 @@ import * as Pattern from '../name-pattern.ts'; import * as Schema from '../schema-tree.ts'; // @deno-types="@ndn/segmented-object/lib/fetch/logic.d.ts" import { FetchLogic } from '@ndn/segmented-object/lib/fetch/logic_browser.js'; -import { LeafNode } from '../mod.ts'; +import { LeafNode } from '../leaf-node.ts'; import { NNI } from '@ndn/tlv'; import { EventChain } from '../../utils/mod.ts'; diff --git a/src/namespace/segmented-object/segmented-object.test.ts b/src/namespace/segmented-object/segmented-object.test.ts index b7b2af9..6814919 100644 --- a/src/namespace/segmented-object/segmented-object.test.ts +++ b/src/namespace/segmented-object/segmented-object.test.ts @@ -9,7 +9,7 @@ import { NtSchema, VerifyResult } from '../nt-schema.ts'; import { InMemoryStorage } from '../../storage/mod.ts'; import { LeafNode } from '../leaf-node.ts'; import * as Tree from '../schema-tree.ts'; -import { SegmentedObject } from './segmented-object.ts'; +import { SegmentedObjectNode } from './segmented-object.ts'; export const b = ([value]: TemplateStringsArray) => new TextEncoder().encode(value); @@ -32,7 +32,7 @@ Deno.test('SegmentedObject.1 Basic fetching', async () => { return VerifyResult.Fail; } }); - const segObjNode = schema.set('/object', SegmentedObject, { + const segObjNode = schema.set('/object', SegmentedObjectNode, { leafNode, lifetimeAfterRto: 100, }); @@ -98,7 +98,7 @@ Deno.test('SegmentedObject.2 Basic provide', async () => { return undefined; } }); - const segObjNode = schema.set('/object', SegmentedObject, { + const segObjNode = schema.set('/object', SegmentedObjectNode, { leafNode, lifetimeAfterRto: 100, }); diff --git a/src/namespace/segmented-object/segmented-object.ts b/src/namespace/segmented-object/segmented-object.ts index 6185db5..1e51cbd 100644 --- a/src/namespace/segmented-object/segmented-object.ts +++ b/src/namespace/segmented-object/segmented-object.ts @@ -8,7 +8,7 @@ import { EventIterator } from 'event-iterator'; import { BaseNode } from '../base-node.ts'; import * as Pattern from '../name-pattern.ts'; import * as Schema from '../schema-tree.ts'; -import { LeafNode } from '../mod.ts'; +import { LeafNode } from '../leaf-node.ts'; import { Fetcher, FetcherOptions, PipelineOptions } from './fetcher.ts'; export interface Result extends PromiseLike, AsyncIterable { @@ -143,7 +143,7 @@ export type SegmentedObjectOpts = { pipelineOpts?: PipelineOptions; }; -export class SegmentedObject extends BaseNode { +export class SegmentedObjectNode extends BaseNode { lifetimeAfterRto: number; segmentPattern: string; segmentType: number; @@ -163,7 +163,7 @@ export class SegmentedObject extends BaseNode { } public async provide( - matched: Schema.StrictMatch, + matched: Schema.StrictMatch, source: ChunkSource, opts: { freshnessMs?: number; @@ -190,7 +190,7 @@ export class SegmentedObject extends BaseNode { } public need( - matched: Schema.StrictMatch, + matched: Schema.StrictMatch, opts: { abortSignal?: AbortSignal; lifetimeAfterRto?: number; diff --git a/src/namespace/sync/sync.ts b/src/namespace/sync/sync.ts new file mode 100644 index 0000000..948b143 --- /dev/null +++ b/src/namespace/sync/sync.ts @@ -0,0 +1,79 @@ +import { Signer, Verifier } from '@ndn/packet'; +import { StateVector, SvSync } from '@ndn/svs'; +import { BaseNode } from '../base-node.ts'; +import * as Schema from '../schema-tree.ts'; +import { EventChain } from '../../utils/event-chain.ts'; +import { ExpressingPointEvents } from '../expressing-point.ts'; + +export type SvsInstNodeEvents = Pick; + +export type SvsInstNodeOpts = { + lifetimeMs?: number; + steadyTimerMs?: number; + suppressionTimerMs?: number; + interestSigner?: Signer; +}; + +/** + * SVS works more like a separated component from NTSchema, because + * it runs at some specific prefix(es) instead of a general pattern. + * Each SVS instance has its own state, and there is little to share. + */ +export class SvsInstNode extends BaseNode { + /** Verify Interest event. */ + public readonly onVerify = new EventChain(); + + constructor( + public readonly config: SvsInstNodeOpts, + describe?: string, + ) { + super(describe); + } + + public static readonly fireSvsInstance = (inst: SvSync) => { + (inst as unknown as { resetTimer: (immediate?: boolean) => void }).resetTimer(true); + }; + + public async createSvsInst( + matched: Schema.StrictMatch, + opts: { + lifetimeMs?: number; + steadyTimerMs?: number; + suppressionTimerMs?: number; + signer?: Signer; + verifier?: Verifier; + initialStateVector?: StateVector; + initialize?: (sync: SvSync) => PromiseLike; + } = {}, + ): Promise { + const signer = opts.signer ?? this.config.interestSigner; + const verifier = opts.verifier ?? this.handler!.getVerifier(undefined, {}); + const lifetimeMs = opts.lifetimeMs ?? this.config.lifetimeMs; + if (!signer || !verifier || lifetimeMs === undefined) { + throw new Error( + `[${this.describe}:createSvsInst] Unable to create SVS instance when signer, ` + + `verifier or lifetimeMs is missing in config.`, + ); + } + const steadyTimer = [opts.steadyTimerMs ?? this.config.steadyTimerMs ?? 30000, 0.1] satisfies SvSync.Timer; + const suppressionTimer = [ + opts.suppressionTimerMs ?? this.config.suppressionTimerMs ?? 200, + 0.5, + ] satisfies SvSync.Timer; + const describe = this.describe ? `${this.describe}(${matched.name.toString()})` : undefined; + + const ret = await SvSync.create({ + endpoint: this.handler!.endpoint!, + syncPrefix: matched.name, + signer: signer, + verifier: verifier, + syncInterestLifetime: lifetimeMs, + steadyTimer: steadyTimer, + suppressionTimer: suppressionTimer, + describe: describe, + initialStateVector: new StateVector(opts.initialStateVector), + initialize: opts.initialize, + }); + return ret; + } +}