Skip to content

Commit

Permalink
l3pkt: parameterized and signed Interest
Browse files Browse the repository at this point in the history
ParamsDigest computation is currently a placeholder.
  • Loading branch information
yoursunny committed Sep 24, 2019
1 parent 2237d7c commit ebaf067
Show file tree
Hide file tree
Showing 10 changed files with 427 additions and 99 deletions.
2 changes: 1 addition & 1 deletion packages/l3pkt/src/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const EVD = new EvDecoder<Data>("Data", TT.Data)
.add(TT.DSigInfo, (self, { decoder }) => self.sigInfo = decoder.decode(DSigInfo))
.add(TT.DSigValue, (self, { value, before }) => {
self.sigValue = value;
LLVerify.saveSignedPortion(self, before);
self[LLVerify.SIGNED] = before;
});

/** Data packet. */
Expand Down
156 changes: 128 additions & 28 deletions packages/l3pkt/src/interest.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,59 @@
import { Name, NameLike } from "@ndn/name";
import { Name, NameLike, ParamsDigest } from "@ndn/name";
import { Decoder, Encoder, EvDecoder, NNI } from "@ndn/tlv";

import { TT } from "./an";
import { LLSign, LLVerify } from "./llsign";
import { ISigInfo } from "./sig-info";

const LIFETIME_DEFAULT = 4000;
const HOPLIMIT_MAX = 255;
const FAKE_PARAMS_DIGEST = new Uint8Array((function*() {
for (let i = 0; i < 32; i += 2) {
yield 0xBE;
yield 0xEF;
}
})());

const EVD = new EvDecoder<Interest>("Interest", TT.Interest)
.add(TT.Name, (self, { decoder }) => { self.name = decoder.decode(Name); })
.add(TT.CanBePrefix, (self) => { self.canBePrefix = true; })
.add(TT.MustBeFresh, (self) => { self.mustBeFresh = true; })
.add(TT.Name, (self, { decoder }) => self.name = decoder.decode(Name))
.add(TT.CanBePrefix, (self) => self.canBePrefix = true)
.add(TT.MustBeFresh, (self) => self.mustBeFresh = true)
// TODO ForwardingHint
.add(TT.Nonce, (self, { value }) => { self.nonce = NNI.decode(value, 4); })
.add(TT.InterestLifetime, (self, { value }) => { self.lifetime = NNI.decode(value); })
.add(TT.HopLimit, (self, { value }) => { self.hopLimit = NNI.decode(value, 1); });
// TODO AppParameters, ISigInfo, ISigValue
.add(TT.Nonce, (self, { value }) => self.nonce = NNI.decode(value, 4))
.add(TT.InterestLifetime, (self, { value }) => self.lifetime = NNI.decode(value))
.add(TT.HopLimit, (self, { value }) => self.hopLimit = NNI.decode(value, 1))
.add(TT.AppParameters, (self, { value, tlv }) => {
if (ParamsDigest.findIn(self.name) < 0) {
throw new Error("ParamsDigest missing in parameterized Interest");
}
self.appParameters = value;
self[LLVerify.SIGNED] = tlv;
})
.add(TT.ISigInfo, (self, { decoder }) => self.sigInfo = decoder.decode(ISigInfo))
.add(TT.ISigValue, (self, { value, tlv }) => {
if (!ParamsDigest.match(self.name.at(-1))) {
throw new Error("ParamsDigest missing or out of place in signed Interest");
}
const appParametersTlv = self[LLVerify.SIGNED];
if (typeof appParametersTlv === "undefined") {
throw new Error("AppParameters missing in signed Interest");
}
if (typeof self.sigInfo === "undefined") {
throw new Error("ISigInfo missing in signed Interest");
}

self.sigValue = value;
self[LLVerify.SIGNED] = Encoder.encode([
self.name.getPrefix(-1).valueOnly,
new Uint8Array(appParametersTlv.buffer, appParametersTlv.byteOffset,
tlv.byteOffset - appParametersTlv.byteOffset),
]);
});

/** Interest packet. */
export class Interest {
public get name() { return this.name_; }
public set name(v) { this.name_ = v; }

public get canBePrefix() { return this.canBePrefix_; }
public set canBePrefix(v) { this.canBePrefix_ = v; }

public get mustBeFresh() { return this.mustBeFresh_; }
public set mustBeFresh(v) { this.mustBeFresh_ = v; }

public get nonce() { return this.nonce_; }
public set nonce(v) { this.nonce_ = v; }
public set nonce(v) { this.nonce_ = v && NNI.constrain(v, "Nonce", 0xFFFFFFFF); }

public get lifetime() { return this.lifetime_; }
public set lifetime(v) { this.lifetime_ = NNI.constrain(v, "InterestLifetime"); }
Expand All @@ -37,14 +62,18 @@ export class Interest {
public set hopLimit(v) { this.hopLimit_ = NNI.constrain(v, "HopLimit", HOPLIMIT_MAX); }

public static decodeFrom(decoder: Decoder): Interest {
const self = new Interest();
EVD.decode(self, decoder);
return self;
return EVD.decode(new Interest(), decoder);
}

private name_: Name = new Name();
private canBePrefix_: boolean = false;
private mustBeFresh_: boolean = false;
public name: Name = new Name();
public canBePrefix: boolean = false;
public mustBeFresh: boolean = false;
public appParameters?: Uint8Array;
public sigInfo?: ISigInfo;
public sigValue?: Uint8Array;
public [LLSign.PENDING]?: LLSign;
public [LLVerify.SIGNED]?: Uint8Array;

private nonce_: number|undefined;
private lifetime_: number = LIFETIME_DEFAULT;
private hopLimit_: number = HOPLIMIT_MAX;
Expand All @@ -60,6 +89,7 @@ export class Interest {
* - Interest.Nonce(v)
* - Interest.Lifetime(v)
* - Interest.HopLimit(v)
* - Uint8Array as AppParameters
*/
constructor(...args: Array<Interest | Interest.CtorArg>) {
args.forEach((arg) => {
Expand All @@ -75,6 +105,8 @@ export class Interest {
this.lifetime = arg.v;
} else if (arg instanceof HopLimitTag) {
this.hopLimit = arg.v;
} else if (arg instanceof Uint8Array) {
this.appParameters = arg;
} else if (arg instanceof Interest) {
Object.assign(this, arg);
} else {
Expand All @@ -84,9 +116,8 @@ export class Interest {
}

public encodeTo(encoder: Encoder) {
if (this.name.size < 1) {
throw new Error("Interest name is empty");
}
this.insertParamsDigest();
LLSign.encodeErrorIfPending(this);

const nonce = typeof this.nonce === "undefined" ?
Math.random() * 0x100000000 : this.nonce;
Expand All @@ -100,8 +131,77 @@ export class Interest {
[TT.InterestLifetime, NNI(this.lifetime)] : undefined,
this.hopLimit !== HOPLIMIT_MAX ?
[TT.HopLimit, NNI(this.hopLimit, 1)] : undefined,
this.appParameters ?
[TT.AppParameters, this.appParameters] : undefined,
this.sigInfo,
this.sigValue ?
[TT.ISigValue, this.sigValue] : undefined,
);
}

public [LLSign.PROCESS](): Promise<void> {
this.insertParamsDigest();
return LLSign.processImpl(this,
() => Encoder.encode([
this.name.getPrefix(-1).valueOnly,
[TT.AppParameters, this.appParameters],
this.sigInfo,
]),
(sig) => {
this.sigValue = this.sigInfo ? sig : undefined;
return this.updateParamsDigest();
});
}

public [LLVerify.VERIFY](verify: LLVerify): Promise<void> {
if (!this.sigValue) {
return Promise.resolve();
}
return LLVerify.verifyImpl(this, this.sigValue, verify);
}

private insertParamsDigest() {
if (this.name.size < 1) {
throw new Error("Interest name is empty");
}

let pdIndex = ParamsDigest.findIn(this.name);
let pdAppendPlaceholder = false;
if (this.sigInfo) {
if (pdIndex < 0) {
pdAppendPlaceholder = true;
} else if (pdIndex !== this.name.size - 1) {
throw new Error("ParamsDigest out of place for signed Interest");
}

if (!this.appParameters) {
this.appParameters = new Uint8Array();
}
} else if (this.appParameters) {
if (pdIndex < 0) {
pdAppendPlaceholder = true;
}
} else if (pdIndex >= 0) {
this.appParameters = new Uint8Array();
} else {
return; // not a parameterized or signed Interest
}

if (pdAppendPlaceholder) {
pdIndex = this.name.size;
this.name = this.name.append(ParamsDigest.PLACEHOLDER);
}

if (ParamsDigest.isPlaceholder(this.name.at(pdIndex)) && !this[LLSign.PENDING]) {
this[LLSign.PENDING] = () => Promise.resolve(new Uint8Array());
}
}

private async updateParamsDigest(): Promise<void> {
const pdIndex = ParamsDigest.findIn(this.name);
const newDigest = FAKE_PARAMS_DIGEST; // TODO compute digest
this.name = this.name.replaceAt(pdIndex, ParamsDigest.create(newDigest));
}
}

class NonceTag {
Expand Down Expand Up @@ -136,5 +236,5 @@ export namespace Interest {
}

export type CtorArg = NameLike | typeof CanBePrefix | typeof MustBeFresh |
LifetimeTag | HopLimitTag;
LifetimeTag | HopLimitTag | Uint8Array;
}
5 changes: 0 additions & 5 deletions packages/l3pkt/src/llsign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,6 @@ export namespace LLVerify {
[VERIFY](verify: LLVerify): Promise<void>;
}

/** Store signed portion during decoding. */
export function saveSignedPortion(obj: Verifiable, signed: Uint8Array) {
obj[SIGNED] = signed;
}

/** Perform verification. */
export function verifyImpl(obj: Verifiable, sig: Uint8Array, verify: LLVerify): Promise<void> {
const signed = obj[LLVerify.SIGNED];
Expand Down
132 changes: 129 additions & 3 deletions packages/l3pkt/tests/interest.t.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Name } from "@ndn/name";
import { Name, ParamsDigest } from "@ndn/name";
import { Decoder, Encoder } from "@ndn/tlv";
import "@ndn/tlv/test-fixture";

import { Interest } from "../src";
import { Interest, ISigInfo, LLSign, LLVerify, SigType, TT } from "../src";

test("encode", () => {
expect(() => new Interest({} as any)).toThrow();
Expand Down Expand Up @@ -76,7 +76,6 @@ test("decode", () => {
0x0A, 0x04, 0xA0, 0xA1, 0xA2, 0xA3,
0x0C, 0x02, 0x76, 0xA1,
0x22, 0x01, 0xDC,
// TODO AppParameters, ISigInfo, ISigValue
]));
interest = decoder.decode(Interest);
expect(interest.name.toString()).toBe("/A");
Expand All @@ -85,4 +84,131 @@ test("decode", () => {
expect(interest.nonce).toBe(0xA0A1A2A3);
expect(interest.lifetime).toBe(30369);
expect(interest.hopLimit).toBe(220);
expect(interest.appParameters).toBeUndefined();
expect(interest.sigInfo).toBeUndefined();
expect(interest.sigValue).toBeUndefined();
});

async function encodeWithLLSign(interest: Interest): Promise<Uint8Array> {
await interest[LLSign.PROCESS]();
return Encoder.encode(interest);
}

test("encode parameterized", async () => {
// insert empty AppParameters
let interest = new Interest(new Name("/A").append(ParamsDigest.PLACEHOLDER).append("C"));
await expect(encodeWithLLSign(interest)).resolves.toEncodeAs(({ value }) => {
expect(value).toMatchTlv(
({ decoder }) => {
const name = decoder.decode(Name);
expect(name.size).toBe(3);
expect(name.at(1).is(ParamsDigest)).toBeTruthy();
},
({ type }) => expect(type).toBe(TT.Nonce),
({ type, length }) => {
expect(type).toBe(TT.AppParameters);
expect(length).toBe(0);
},
);
});

// append ParamsDigest
interest = new Interest(new Name("/A"), new Uint8Array([0xC0, 0xC1]));
await expect(encodeWithLLSign(interest)).resolves.toEncodeAs(({ value }) => {
expect(value).toMatchTlv(
({ decoder }) => {
const name = decoder.decode(Name);
expect(name.size).toBe(2);
expect(name.at(1).is(ParamsDigest)).toBeTruthy();
},
({ type }) => expect(type).toBe(TT.Nonce),
({ type, value }) => {
expect(type).toBe(TT.AppParameters);
expect(value).toEqualUint8Array([0xC0, 0xC1]);
},
);
});
});

test("decode parameterized", async () => {
let decoder = new Decoder(Encoder.encode([
TT.Interest,
new Name("/A"),
[TT.AppParameters, new Uint8Array([0xC0, 0xC1])],
]));
expect(() => decoder.decode(Interest)).toThrow(/missing/);

decoder = new Decoder(Encoder.encode([
TT.Interest,
new Name("/A").append(ParamsDigest, new Uint8Array(32)),
[TT.AppParameters, new Uint8Array([0xC0, 0xC1])],
]));
let interest = decoder.decode(Interest);
expect(interest.name.size).toBe(2);
expect(interest.appParameters).not.toBeUndefined();

decoder = new Decoder(Encoder.encode([
TT.Interest,
new Name("/A").append(ParamsDigest, new Uint8Array(32)).append("C"),
[TT.AppParameters, new Uint8Array([0xC0, 0xC1])],
]));
interest = decoder.decode(Interest);
expect(interest.name.size).toBe(3);
expect(interest.appParameters).not.toBeUndefined();

const verify = jest.fn();
await expect(interest[LLVerify.VERIFY](verify)).resolves.toBeUndefined();
expect(verify).not.toHaveBeenCalled();
});

test("encode signed", async () => {
// error on out of place ParamsDigest
const interest = new Interest(new Name("/A").append(ParamsDigest.PLACEHOLDER).append("C"));
interest.sigInfo = new ISigInfo();
interest.sigInfo.type = SigType.Sha256;
await expect(encodeWithLLSign(interest)).rejects.toThrow(/out of place/);

// other tests in llsign.t.ts
});

test("decode signed", () => {
let decoder = new Decoder(Encoder.encode([
TT.Interest,
new Name("/A").append(ParamsDigest, new Uint8Array(32)),
[TT.ISigValue, new Uint8Array(4)],
]));
expect(() => decoder.decode(Interest)).toThrow(/missing/);

decoder = new Decoder(Encoder.encode([
TT.Interest,
new Name("/A").append(ParamsDigest, new Uint8Array(32)),
[TT.AppParameters, new Uint8Array([0xC0, 0xC1])],
[TT.ISigValue, new Uint8Array(4)],
]));
expect(() => decoder.decode(Interest)).toThrow(/missing/);

const si = new ISigInfo();
si.type = SigType.Sha256;

decoder = new Decoder(Encoder.encode([
TT.Interest,
new Name("/A").append(ParamsDigest, new Uint8Array(32)).append("C"),
[TT.AppParameters, new Uint8Array([0xC0, 0xC1])],
si,
[TT.ISigValue, new Uint8Array(4)],
]));
expect(() => decoder.decode(Interest)).toThrow(/out of place/);

decoder = new Decoder(Encoder.encode([
TT.Interest,
new Name("/A").append(ParamsDigest, new Uint8Array(32)),
[TT.AppParameters, new Uint8Array([0xC0, 0xC1])],
si,
[TT.ISigValue, new Uint8Array(4)],
]));
const interest = decoder.decode(Interest);
expect(interest.name.size).toBe(2);
expect(interest.appParameters).not.toBeUndefined();
expect(interest.sigInfo).not.toBeUndefined();
expect(interest.sigValue).not.toBeUndefined();
});

0 comments on commit ebaf067

Please sign in to comment.