|
1 |
| -import { Endpoint, RetxPolicy } from '@ndn/endpoint'; |
2 |
| -import { Data, Interest, Name, Signer, type Verifier } from '@ndn/packet'; |
3 |
| -import * as namePattern from './name-pattern.ts'; |
| 1 | +import { RetxPolicy } from '@ndn/endpoint'; |
| 2 | +import { Data, Interest, Signer, type Verifier } from '@ndn/packet'; |
4 | 3 | import * as schemaTree from './schema-tree.ts';
|
5 | 4 | import { BaseNode, BaseNodeEvents } from './base-node.ts';
|
6 |
| -import { EventChain } from '../utils/event-chain.ts'; |
7 |
| - |
8 |
| -export enum VerifyResult { |
9 |
| - Fail = -1, |
10 |
| - Unknown = 0, |
11 |
| - Pass = 1, |
12 |
| - Bypass = 2, |
13 |
| -} |
| 5 | +import { EventChain, Stop } from '../utils/event-chain.ts'; |
| 6 | +import { VerifyResult } from './nt-schema.ts'; |
14 | 7 |
|
15 | 8 | export interface ExpressingPointEvents extends BaseNodeEvents {
|
16 |
| - interest(target: schemaTree.StrictMatch<ExpressingPoint>): Promise<Data | undefined>; |
17 |
| - verify(target: schemaTree.StrictMatch<ExpressingPoint>, pkt: Verifier.Verifiable): Promise<VerifyResult>; |
18 |
| - searchStorage(target: schemaTree.StrictMatch<ExpressingPoint>): Promise<Data | undefined>; |
19 |
| - saveStorage(target: schemaTree.StrictMatch<ExpressingPoint>): Promise<void>; |
| 9 | + interest( |
| 10 | + args: { |
| 11 | + target: schemaTree.StrictMatch<ExpressingPoint>; |
| 12 | + interest: Interest; |
| 13 | + deadline: number; |
| 14 | + }, |
| 15 | + ): Promise<Data | undefined>; |
| 16 | + |
| 17 | + verify( |
| 18 | + args: { |
| 19 | + target: schemaTree.StrictMatch<ExpressingPoint>; |
| 20 | + deadline: number; |
| 21 | + pkt: Verifier.Verifiable; |
| 22 | + }, |
| 23 | + ): Promise<VerifyResult>; |
| 24 | + |
| 25 | + searchStorage( |
| 26 | + args: { |
| 27 | + target: schemaTree.StrictMatch<ExpressingPoint>; |
| 28 | + interest: Interest; |
| 29 | + deadline: number; |
| 30 | + }, |
| 31 | + ): Promise<Data | undefined>; |
20 | 32 | }
|
21 | 33 |
|
22 | 34 | export type ExpressingPointOpts = {
|
23 | 35 | lifetimeMs: number;
|
24 |
| - signer: Signer; |
| 36 | + interestSigner?: Signer; |
| 37 | + canBePrefix?: boolean; |
| 38 | + mustBeFresh?: boolean; |
25 | 39 | supressInterest?: boolean;
|
26 |
| - abortSignal?: AbortSignal; |
27 |
| - modifyInterest?: Interest.Modify; |
28 | 40 | retx?: RetxPolicy;
|
29 | 41 | };
|
30 | 42 |
|
31 | 43 | export class ExpressingPoint extends BaseNode {
|
| 44 | + /** Called when Interest received */ |
32 | 45 | public readonly onInterest = new EventChain<ExpressingPointEvents['interest']>();
|
| 46 | + |
| 47 | + /** Verify Interest event. Also verifies Data if this is a LeafNode */ |
33 | 48 | public readonly onVerify = new EventChain<ExpressingPointEvents['verify']>();
|
| 49 | + |
| 50 | + /** Searching stored data from the storage */ |
34 | 51 | public readonly onSearchStorage = new EventChain<ExpressingPointEvents['searchStorage']>();
|
35 |
| - public readonly onSaveStorage = new EventChain<ExpressingPointEvents['saveStorage']>(); |
36 | 52 |
|
37 |
| - // public async need(matched: schemaTree.MatchedObject<ExpressingPoint>): Promise<Data | undefined> {} |
| 53 | + constructor( |
| 54 | + public readonly config: ExpressingPointOpts, |
| 55 | + ) { |
| 56 | + super(); |
| 57 | + } |
| 58 | + |
| 59 | + public searchCache(target: schemaTree.StrictMatch<ExpressingPoint>, interest: Interest, deadline: number) { |
| 60 | + return this.onSearchStorage.chain( |
| 61 | + undefined, |
| 62 | + (ret) => Promise.resolve(ret ? Stop : [{ target, interest, deadline }]), |
| 63 | + { target, interest, deadline }, |
| 64 | + ); |
| 65 | + } |
| 66 | + |
| 67 | + public override async verifyPacket( |
| 68 | + matched: schemaTree.StrictMatch<ExpressingPoint>, |
| 69 | + pkt: Verifier.Verifiable, |
| 70 | + deadline: number, |
| 71 | + ) { |
| 72 | + const verifyResult = await this.onVerify.chain( |
| 73 | + VerifyResult.Unknown, |
| 74 | + (ret, args) => Promise.resolve((ret < VerifyResult.Unknown || ret >= VerifyResult.Bypass) ? Stop : [args]), |
| 75 | + { target: matched, pkt, deadline }, |
| 76 | + ); |
| 77 | + return verifyResult >= VerifyResult.Pass; |
| 78 | + } |
| 79 | + |
| 80 | + public override async processInterest( |
| 81 | + matched: schemaTree.StrictMatch<ExpressingPoint>, |
| 82 | + interest: Interest, |
| 83 | + deadline: number, |
| 84 | + ): Promise<Data | undefined> { |
| 85 | + // Search storage |
| 86 | + // Reply if there is data (including AppNack). No further callback will be called if hit. |
| 87 | + // This is the same behavior as a forwarder. |
| 88 | + const cachedData = await this.searchCache(matched, interest, deadline); |
| 89 | + if (cachedData) { |
| 90 | + return cachedData; |
| 91 | + } |
| 92 | + |
| 93 | + // Validate Interest |
| 94 | + // Only done when there is a sigInfo or appParam. |
| 95 | + // Signed Interests are required to carry AppParam, but may be zero length. |
| 96 | + // To guarantee everything is good in case the underlying library returns `undefined` when zero length, check both. |
| 97 | + if (interest.appParameters || interest.sigInfo) { |
| 98 | + if (!await this.verifyPacket(matched, interest, deadline)) { |
| 99 | + // Unverified Interest. Drop |
| 100 | + return; |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | + // PreRecvInt |
| 105 | + // Used to decrypt AppParam or handle before onInterest hits, if applicable. |
| 106 | + // Do we need them? Hold for now. |
| 107 | + |
| 108 | + // OnInt |
| 109 | + const result = await this.onInterest.chain( |
| 110 | + undefined, |
| 111 | + (ret, args) => Promise.resolve(ret ? Stop : [args]), |
| 112 | + { target: matched, interest, deadline }, |
| 113 | + ); |
| 114 | + |
| 115 | + // PreSendData |
| 116 | + // Used to encrypt Data or handle after onInterest hits, if applicable. |
| 117 | + // Do we need them? Hold for now. |
| 118 | + return result; |
| 119 | + } |
| 120 | + |
| 121 | + public async need( |
| 122 | + matched: schemaTree.StrictMatch<ExpressingPoint>, |
| 123 | + opts: { |
| 124 | + appParam?: Uint8Array | string; |
| 125 | + supressInterest?: boolean; |
| 126 | + abortSignal?: AbortSignal; |
| 127 | + signer?: Signer; |
| 128 | + lifetimeMs?: number; |
| 129 | + deadline?: number; |
| 130 | + } = {}, |
| 131 | + ): Promise<Data | undefined> { |
| 132 | + // Construct Interest, but without signing, so the parameter digest is not there |
| 133 | + const interestArgs = [matched.name] as Array<Interest.CtorArg>; |
| 134 | + if (this.config.canBePrefix) { |
| 135 | + // Be aware that if CanBePrefix is set, you may need to also validate the data against the LeafNode's validator. |
| 136 | + interestArgs.push(Interest.CanBePrefix); |
| 137 | + } |
| 138 | + if (this.config.mustBeFresh ?? true) { |
| 139 | + interestArgs.push(Interest.MustBeFresh); |
| 140 | + } |
| 141 | + const appParam = opts.appParam instanceof Uint8Array |
| 142 | + ? opts.appParam |
| 143 | + : typeof opts.appParam === 'string' |
| 144 | + ? new TextEncoder().encode(opts.appParam) |
| 145 | + : undefined; |
| 146 | + if (appParam) { |
| 147 | + interestArgs.push(appParam); |
| 148 | + } |
| 149 | + // TODO: FwHint is not supported for now. Who should provide this info? |
| 150 | + const lifetimeMs = opts.lifetimeMs ?? this.config.lifetimeMs; |
| 151 | + interestArgs.push(Interest.Lifetime(lifetimeMs)); |
| 152 | + const interest = new Interest(...interestArgs); |
| 153 | + |
| 154 | + // Compute deadline |
| 155 | + const deadline = opts.deadline ?? (Date.now() + lifetimeMs); |
| 156 | + |
| 157 | + // Get a signer for this interest |
| 158 | + const signer = opts.signer ?? this.config.interestSigner; |
| 159 | + |
| 160 | + // If appParam is empty and not signed, the Interest name is final. |
| 161 | + // Otherwise, we have to construct the Interest first before searching storage. |
| 162 | + // Get a signer for Interest. |
| 163 | + let cachedData: Data | undefined = undefined; |
| 164 | + if (!appParam && !signer) { |
| 165 | + cachedData = await this.searchCache(matched, interest, deadline); |
| 166 | + if (cachedData) { |
| 167 | + return cachedData; |
| 168 | + } |
| 169 | + } |
| 170 | + |
| 171 | + // After signing the digest is there |
| 172 | + if (signer) { |
| 173 | + await signer.sign(interest); |
| 174 | + } |
| 175 | + // We may search the storage if not yet. However, it seems not useful for now. |
| 176 | + |
| 177 | + // Express the Interest if not surpressed |
| 178 | + const supressInterest = opts.supressInterest ?? this.config.supressInterest; |
| 179 | + if (supressInterest) { |
| 180 | + return undefined; |
| 181 | + } |
| 182 | + |
| 183 | + const data = await this.handler!.endpoint.consume(interest, { |
| 184 | + // deno-lint-ignore no-explicit-any |
| 185 | + signal: opts.abortSignal as any, |
| 186 | + retx: this.config.retx, |
| 187 | + // Note: the verifier is at the LeafNode if CanBePrefix is set |
| 188 | + verifier: this.handler!.getVerifier(deadline), |
| 189 | + }); |
| 190 | + |
| 191 | + // (no await) Save (cache) the data in the storage |
| 192 | + this.handler!.storeData(data); |
38 | 193 |
|
39 |
| - // public override async processInterest( |
40 |
| - // matched: schemaTree.MatchedObject<ExpressingPoint>, |
41 |
| - // interest: Interest, |
42 |
| - // ): Promise<Data | undefined> { |
43 |
| - // } |
| 194 | + return data; |
| 195 | + } |
44 | 196 | }
|
0 commit comments