Comparing version 0.0.20191223-beta.1 to 0.0.20200606
export interface Decodable<R> { | ||
decodeFrom(decoder: Decoder): R; | ||
decodeFrom: (decoder: Decoder) => R; | ||
} | ||
@@ -45,2 +45,4 @@ /** TLV decoder. */ | ||
readonly nni: number; | ||
/** TLV-VALUE as UTF-8 string */ | ||
readonly text: string; | ||
/** siblings before this TLV */ | ||
@@ -47,0 +49,0 @@ readonly before: Uint8Array; |
@@ -1,2 +0,3 @@ | ||
import { NNI } from "./mod.js"; | ||
import { NNI } from "./nni.js"; | ||
import { fromUtf8 } from "./string.js"; | ||
class DecodedTlv { | ||
@@ -31,2 +32,5 @@ constructor(type, buf, offsetT, offsetV, offsetE) { | ||
} | ||
get text() { | ||
return fromUtf8(this.value); | ||
} | ||
get before() { | ||
@@ -33,0 +37,0 @@ return this.buf.subarray(0, this.offsetT); |
@@ -5,3 +5,3 @@ /** | ||
export interface EncodableObj { | ||
encodeTo(encoder: Encoder): void; | ||
encodeTo: (encoder: Encoder) => void; | ||
} | ||
@@ -49,3 +49,3 @@ /** | ||
/** Prepend an Encodable object. */ | ||
encode(obj: Encodable | ReadonlyArray<Encodable>): void; | ||
encode(obj: Encodable | readonly Encodable[]): void; | ||
private grow; | ||
@@ -56,5 +56,5 @@ } | ||
const OmitEmpty: unique symbol; | ||
function encode(obj: Encodable | ReadonlyArray<Encodable>, initBufSize?: number): Uint8Array; | ||
function encode(obj: Encodable | readonly Encodable[], initBufSize?: number): Uint8Array; | ||
/** Extract the encoding output of an element while writing to a larger encoder. */ | ||
function extract(obj: Encodable | ReadonlyArray<Encodable>, cb: (output: Uint8Array) => void): Encodable; | ||
function extract(obj: Encodable | readonly Encodable[], cb: (output: Uint8Array) => void): Encodable; | ||
} |
@@ -103,4 +103,3 @@ function sizeofVarNum(n) { | ||
if (obj instanceof Uint8Array) { | ||
const dst = this.prependRoom(obj.byteLength); | ||
dst.set(obj); | ||
this.prependRoom(obj.byteLength).set(obj); | ||
} | ||
@@ -107,0 +106,0 @@ else if (typeof obj === "object" && typeof obj.encodeTo === "function") { |
@@ -1,2 +0,2 @@ | ||
import { Decoder } from "./mod"; | ||
import { Decoder } from "./decoder"; | ||
/** Invoked when a matching TLV element is found. */ | ||
@@ -11,2 +11,4 @@ declare type ElementCallback<T> = (target: T, tlv: Decoder.Tlv) => void; | ||
order: number; | ||
/** Whether TLV element must appear at least once. */ | ||
required: boolean; | ||
/** Whether TLV element may appear more than once. */ | ||
@@ -29,2 +31,3 @@ repeat: boolean; | ||
private rules; | ||
private requiredTlvTypes; | ||
private nextOrder; | ||
@@ -39,3 +42,3 @@ private isCriticalCb; | ||
*/ | ||
constructor(typeName: string, topTT?: number | ReadonlyArray<number>); | ||
constructor(typeName: string, topTT?: number | readonly number[]); | ||
/** | ||
@@ -54,6 +57,8 @@ * Add a decoding rule. | ||
setTop(cb: TopElementCallback<T>): this; | ||
/** Decode to target object. */ | ||
/** Decode TLV to target object. */ | ||
decode<R extends T = T>(target: R, decoder: Decoder): R; | ||
/** Decode TLV-VALUE to target object. */ | ||
decodeValue<R extends T = T>(target: R, vd: Decoder): R; | ||
private handleUnrecognized; | ||
} | ||
export {}; |
@@ -1,2 +0,2 @@ | ||
import { printTT } from "./mod.js"; | ||
import { printTT } from "./string.js"; | ||
const AUTO_ORDER_SKIP = 100; | ||
@@ -18,5 +18,7 @@ function nest(evd) { | ||
this.typeName = typeName; | ||
this.rules = {}; | ||
this.rules = new Map(); | ||
this.requiredTlvTypes = new Set(); | ||
this.nextOrder = AUTO_ORDER_SKIP; | ||
this.isCriticalCb = isCritical; | ||
// eslint-disable-next-line no-negated-condition | ||
this.topTT = !topTT ? [] : Array.isArray(topTT) ? topTT : [topTT]; | ||
@@ -33,10 +35,17 @@ this.unknownCb = () => false; | ||
add(tt, cb, options) { | ||
if (typeof this.rules[tt] !== "undefined") { | ||
if (this.rules.has(tt)) { | ||
throw new Error(`TLV-TYPE ${printTT(tt)} already has a rule`); | ||
} | ||
if (cb instanceof EvDecoder) { | ||
cb = nest(cb); | ||
const rule = { | ||
cb: cb instanceof EvDecoder ? nest(cb) : cb, | ||
order: this.nextOrder, | ||
required: false, | ||
repeat: false, | ||
...options, | ||
}; | ||
this.nextOrder += AUTO_ORDER_SKIP; | ||
this.rules.set(tt, rule); | ||
if (rule.required) { | ||
this.requiredTlvTypes.add(tt); | ||
} | ||
this.rules[tt] = { cb, order: this.nextOrder, repeat: false, ...options }; | ||
this.nextOrder += AUTO_ORDER_SKIP; | ||
return this; | ||
@@ -59,16 +68,22 @@ } | ||
} | ||
/** Decode to target object. */ | ||
/** Decode TLV to target object. */ | ||
decode(target, decoder) { | ||
const topTlv = decoder.read(); | ||
const { type, vd } = topTlv; | ||
if (this.topTT.length && !this.topTT.includes(type)) { | ||
if (this.topTT.length > 0 && !this.topTT.includes(type)) { | ||
throw new Error(`TLV-TYPE ${printTT(type)} is not ${this.typeName}`); | ||
} | ||
this.topCb(target, topTlv); | ||
return this.decodeValue(target, vd); | ||
} | ||
/** Decode TLV-VALUE to target object. */ | ||
decodeValue(target, vd) { | ||
let currentOrder = 0; | ||
let currentCount = 0; | ||
const missingTlvTypes = new Set(this.requiredTlvTypes); | ||
while (!vd.eof) { | ||
const tlv = vd.read(); | ||
const tt = tlv.type; | ||
const rule = this.rules[tt]; | ||
missingTlvTypes.delete(tt); | ||
const rule = this.rules.get(tt); | ||
if (typeof rule === "undefined") { | ||
@@ -94,2 +109,5 @@ if (!this.unknownCb(target, tlv, currentOrder)) { | ||
} | ||
if (missingTlvTypes.size > 0) { | ||
throw new Error(`TLV-TYPE ${Array.from(missingTlvTypes).map(printTT).join(",")} ${missingTlvTypes.size === 1 ? "is" : "are"} missing in ${this.typeName}`); | ||
} | ||
return target; | ||
@@ -96,0 +114,0 @@ } |
@@ -1,2 +0,3 @@ | ||
import { Decoder, Encodable } from "./mod"; | ||
import { Decoder } from "./decoder"; | ||
import { Encodable } from "./encoder"; | ||
/** An TLV element that allows extension sub element. */ | ||
@@ -27,3 +28,3 @@ export interface Extensible { | ||
*/ | ||
decode(obj: T, tlv: Decoder.Tlv, accumulator?: R): R; | ||
decode: (obj: T, tlv: Decoder.Tlv, accumulator?: R) => R; | ||
/** | ||
@@ -35,3 +36,3 @@ * Encode extension element. | ||
*/ | ||
encode(obj: T, value: R): Encodable; | ||
encode: (obj: T, value: R) => Encodable; | ||
} | ||
@@ -38,0 +39,0 @@ export declare namespace Extension { |
@@ -53,5 +53,5 @@ export var Extensible; | ||
}) | ||
.sort(({ tt: ttA, ext: { order: orderA } }, { tt: ttB, ext: { order: orderB } }) => ((orderA !== null && orderA !== void 0 ? orderA : ttA)) - ((orderB !== null && orderB !== void 0 ? orderB : ttB))) | ||
.sort(({ tt: ttA, ext: { order: orderA } }, { tt: ttB, ext: { order: orderB } }) => (orderA !== null && orderA !== void 0 ? orderA : ttA) - (orderB !== null && orderB !== void 0 ? orderB : ttB)) | ||
.map(({ tt, value, ext }) => ext.encode(source, value)); | ||
} | ||
} |
@@ -1,6 +0,6 @@ | ||
export * from "./string"; | ||
export * from "./decoder"; | ||
export * from "./encoder"; | ||
export * from "./ev-decoder"; | ||
export * from "./extensible"; | ||
export * from "./nni"; | ||
export * from "./decoder"; | ||
export * from "./extensible"; | ||
export * from "./ev-decoder"; | ||
export * from "./string"; |
@@ -1,6 +0,6 @@ | ||
export * from "./string.js"; | ||
export * from "./decoder.js"; | ||
export * from "./encoder.js"; | ||
export * from "./ev-decoder.js"; | ||
export * from "./extensible.js"; | ||
export * from "./nni.js"; | ||
export * from "./decoder.js"; | ||
export * from "./extensible.js"; | ||
export * from "./ev-decoder.js"; | ||
export * from "./string.js"; |
@@ -1,8 +0,40 @@ | ||
import { Encodable } from "./mod"; | ||
declare type Len = 1 | 4; | ||
import { Encodable, Encoder } from "./encoder"; | ||
declare class Nni1 { | ||
private readonly n; | ||
constructor(n: number); | ||
encodeTo(encoder: Encoder): void; | ||
} | ||
declare class Nni2 { | ||
private readonly n; | ||
constructor(n: number); | ||
encodeTo(encoder: Encoder): void; | ||
} | ||
declare class Nni4 { | ||
private readonly n; | ||
constructor(n: number); | ||
encodeTo(encoder: Encoder): void; | ||
} | ||
declare type Len = 1 | 2 | 4 | 8; | ||
interface Options<LenT = Len> { | ||
/** If set, use/enforce specific TLV-LENGTH. */ | ||
len?: LenT; | ||
/** If true, allow approximate integers. */ | ||
unsafe?: boolean; | ||
} | ||
declare const EncodeNniClass: { | ||
1: typeof Nni1; | ||
2: typeof Nni2; | ||
4: typeof Nni4; | ||
}; | ||
/** Create Encodable from non-negative integer. */ | ||
export declare function NNI(n: number, len?: Len): Encodable; | ||
export declare function NNI(n: number | bigint, { len, unsafe, }?: Options<Extract<Len, keyof typeof EncodeNniClass>>): Encodable; | ||
export declare namespace NNI { | ||
/** Decode non-negative integer. */ | ||
function decode(value: Uint8Array, len?: Len): number; | ||
/** Decode non-negative integer as number. */ | ||
function decode(value: Uint8Array, opts?: Options & { | ||
big?: false; | ||
}): number; | ||
/** Decode non-negative integer as bigint. */ | ||
function decode(value: Uint8Array, opts: Options & { | ||
big: true; | ||
}): bigint; | ||
/** Error if n exceeds [min,max] range. */ | ||
@@ -9,0 +41,0 @@ function constrain(n: number, typeName: string, max?: number, min?: number): number; |
171
lib/nni.js
@@ -1,86 +0,135 @@ | ||
import { Encoder } from "./mod.js"; | ||
class NNI0 { | ||
import { Encoder } from "./encoder.js"; | ||
import { toHex } from "./string.js"; | ||
class Nni1 { | ||
constructor(n) { | ||
this.n = n; | ||
} | ||
static decode(value) { | ||
const dv = Encoder.asDataView(value); | ||
switch (dv.byteLength) { | ||
case 1: | ||
return dv.getUint8(0); | ||
case 2: | ||
return dv.getUint16(0); | ||
case 4: | ||
return dv.getUint32(0); | ||
case 8: { | ||
const n = dv.getUint32(0) * 0x100000000 + dv.getUint32(4); | ||
if (!Number.isSafeInteger(n)) { | ||
throw new Error("integer is too large"); | ||
} | ||
return n; | ||
} | ||
} | ||
throw new Error("invalid TLV-LENGTH"); | ||
encodeTo(encoder) { | ||
encoder.prependRoom(1)[0] = this.n; | ||
} | ||
} | ||
class Nni2 { | ||
constructor(n) { | ||
this.n = n; | ||
} | ||
encodeTo(encoder) { | ||
if (this.n <= 0xFF) { | ||
encoder.prependRoom(1)[0] = this.n; | ||
} | ||
else if (this.n <= 0xFFFF) { | ||
Encoder.asDataView(encoder.prependRoom(2)).setUint16(0, this.n); | ||
} | ||
else if (this.n <= 0xFFFFFFFF) { | ||
Encoder.asDataView(encoder.prependRoom(4)).setUint32(0, this.n); | ||
} | ||
else if (Number.isSafeInteger(this.n)) { | ||
const dv = Encoder.asDataView(encoder.prependRoom(8)); | ||
dv.setUint32(0, this.n / 0x100000000); | ||
dv.setUint32(4, this.n % 0x100000000); | ||
} | ||
else { | ||
throw new Error("integer is too large"); | ||
} | ||
Encoder.asDataView(encoder.prependRoom(2)).setUint16(0, this.n); | ||
} | ||
} | ||
class NNI1 { | ||
class Nni4 { | ||
constructor(n) { | ||
this.n = n; | ||
} | ||
static decode(value) { | ||
if (value.byteLength !== 1) { | ||
throw new Error("invalid TLV-LENGTH"); | ||
} | ||
return value[0]; | ||
encodeTo(encoder) { | ||
Encoder.asDataView(encoder.prependRoom(4)).setUint32(0, this.n); | ||
} | ||
} | ||
class Nni8Number { | ||
constructor(n) { | ||
this.n = n; | ||
} | ||
encodeTo(encoder) { | ||
encoder.prependRoom(1)[0] = this.n; | ||
const dv = Encoder.asDataView(encoder.prependRoom(8)); | ||
dv.setUint32(0, this.n / 0x100000000); | ||
dv.setUint32(4, this.n % 0x100000000); | ||
} | ||
} | ||
class NNI4 { | ||
class Nni8Big { | ||
constructor(n) { | ||
this.n = n; | ||
} | ||
static decode(value) { | ||
if (value.byteLength !== 4) { | ||
throw new Error("invalid TLV-LENGTH"); | ||
} | ||
return Encoder.asDataView(value).getUint32(0); | ||
} | ||
encodeTo(encoder) { | ||
Encoder.asDataView(encoder.prependRoom(4)).setUint32(0, this.n); | ||
Encoder.asDataView(encoder.prependRoom(8)).setBigUint64(0, this.n); | ||
} | ||
} | ||
const NniClass = { | ||
0: NNI0, | ||
1: NNI1, | ||
4: NNI4, | ||
const supportsBigInt = !!(DataView.prototype.getBigUint64 && DataView.prototype.setBigUint64); | ||
const [BIG_ZERO, BIGUINT8_MAX, BIGUINT16_MAX, BIGUINT32_MAX, BIGUINT64_MAX,] = supportsBigInt ? | ||
[ | ||
BigInt(0), | ||
BigInt("0xFF"), | ||
BigInt("0xFFFF"), | ||
BigInt("0xFFFFFFFF"), | ||
BigInt("0xFFFFFFFFFFFFFFFF"), | ||
] : | ||
/* istanbul ignore next */ | ||
[Number.NaN, Number.NaN, Number.NaN, Number.NaN, Number.NaN]; | ||
function decode32(dv) { | ||
switch (dv.byteLength) { | ||
case 1: | ||
return dv.getUint8(0); | ||
case 2: | ||
return dv.getUint16(0); | ||
case 4: | ||
return dv.getUint32(0); | ||
} | ||
throw new Error("incorrect TLV-LENGTH of NNI"); | ||
} | ||
const decodeBig = supportsBigInt ? | ||
(dv) => { | ||
if (dv.byteLength === 8) { | ||
return dv.getBigUint64(0); | ||
} | ||
return BigInt(decode32(dv)); | ||
} : | ||
/* istanbul ignore next */ | ||
() => Number.NaN; | ||
const EncodeNniClass = { | ||
1: Nni1, | ||
2: Nni2, | ||
4: Nni4, | ||
}; | ||
/** Create Encodable from non-negative integer. */ | ||
export function NNI(n, len) { | ||
return new NniClass[(len !== null && len !== void 0 ? len : 0)](n); | ||
export function NNI(n, { len, unsafe = false, } = {}) { | ||
if (len) { | ||
return new EncodeNniClass[len](Number(n)); | ||
} | ||
if (typeof n === "number") { | ||
switch (true) { | ||
case n < 0: | ||
throw new RangeError("NNI cannot be negative"); | ||
case n < 0x100: | ||
return new Nni1(n); | ||
case n < 0x10000: | ||
return new Nni2(n); | ||
case n < 0x100000000: | ||
return new Nni4(n); | ||
case unsafe: | ||
case Number.isSafeInteger(n): | ||
return new Nni8Number(n); | ||
default: | ||
throw new RangeError("NNI is too large"); | ||
} | ||
} | ||
switch (true) { | ||
case n < BIG_ZERO: | ||
throw new RangeError("NNI cannot be negative"); | ||
case n <= BIGUINT8_MAX: | ||
return new Nni1(Number(n)); | ||
case n <= BIGUINT16_MAX: | ||
return new Nni2(Number(n)); | ||
case n <= BIGUINT32_MAX: | ||
return new Nni4(Number(n)); | ||
case n <= BIGUINT64_MAX: | ||
return new Nni8Big(n); | ||
default: | ||
throw new RangeError("NNI is too large"); | ||
} | ||
} | ||
(function (NNI) { | ||
/** Decode non-negative integer. */ | ||
function decode(value, len) { | ||
return NniClass[(len !== null && len !== void 0 ? len : 0)].decode(value); | ||
function decode(value, { len, big = false, unsafe = false, } = {}) { | ||
if (len && value.byteLength !== len) { | ||
throw new Error(`incorrect TLV-LENGTH of NNI${len}`); | ||
} | ||
const dv = Encoder.asDataView(value); | ||
if (big) { | ||
return decodeBig(dv); | ||
} | ||
if (dv.byteLength === 8) { | ||
const n = dv.getUint32(0) * 0x100000000 + dv.getUint32(4); | ||
if (!unsafe && !Number.isSafeInteger(n)) { | ||
throw new RangeError(`NNI is too large ${toHex(value)}`); | ||
} | ||
return n; | ||
} | ||
return decode32(dv); | ||
} | ||
@@ -87,0 +136,0 @@ NNI.decode = decode; |
/** Pretty-print TLV-TYPE number. */ | ||
export declare function printTT(tlvType: number): string; | ||
/** Convert byte array to hexadecimal string. */ | ||
/** Convert byte array to upper-case hexadecimal string. */ | ||
export declare function toHex(buf: Uint8Array): string; | ||
@@ -8,4 +8,6 @@ /** | ||
* | ||
* This function does not have error handling. Use on trusted input only. | ||
* This function lacks error handling. Use on trusted input only. | ||
*/ | ||
export declare function fromHex(s: string): Uint8Array; | ||
export declare function toUtf8(s: string): Uint8Array; | ||
export declare function fromUtf8(buf: Uint8Array): string; |
@@ -22,8 +22,8 @@ /** Pretty-print TLV-TYPE number. */ | ||
} | ||
/** Convert byte array to hexadecimal string. */ | ||
/** Convert byte array to upper-case hexadecimal string. */ | ||
export function toHex(buf) { | ||
const table = getHexTable(); | ||
const a = new Array(buf.length); | ||
for (let i = 0; i < buf.length; ++i) { | ||
a[i] = table[buf[i]]; | ||
for (const [i, element] of buf.entries()) { | ||
a[i] = table[element]; | ||
} | ||
@@ -35,3 +35,3 @@ return a.join(""); | ||
* | ||
* This function does not have error handling. Use on trusted input only. | ||
* This function lacks error handling. Use on trusted input only. | ||
*/ | ||
@@ -41,5 +41,13 @@ export function fromHex(s) { | ||
for (let i = 0; i < b.length; ++i) { | ||
b[i] = parseInt(s.substr(i * 2, 2), 16); | ||
b[i] = Number.parseInt(s.slice(i * 2, (i + 1) * 2), 16); | ||
} | ||
return b; | ||
} | ||
const textEncoder = new TextEncoder(); | ||
const textDecoder = new TextDecoder(); | ||
export function toUtf8(s) { | ||
return textEncoder.encode(s); | ||
} | ||
export function fromUtf8(buf) { | ||
return textDecoder.decode(buf); | ||
} |
{ | ||
"name": "@ndn/tlv", | ||
"version": "0.0.20191223-beta.1", | ||
"version": "0.0.20200606", | ||
"description": "NDNts: TLV", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -42,3 +42,3 @@ # @ndn/tlv | ||
assert.deepEqual(encoder.output, | ||
Uint8Array.of(0xB0, 0x07, 0xC0, 0xC1, 0x07, 0x03, 0x08, 0x01, 0x41)); | ||
Uint8Array.of(0xB0, 0x07, 0xC0, 0xC1, 0x07, 0x03, 0x08, 0x01, 0x41)); | ||
``` | ||
@@ -65,3 +65,3 @@ | ||
assert(name instanceof Name); | ||
assert.equal(name.toString(), "/A"); | ||
assert.equal(name.toString(), "/8=A"); | ||
``` | ||
@@ -92,5 +92,5 @@ | ||
class Adjacency { | ||
public name: Name = new Name(); | ||
public uri: string = ""; | ||
public cost: number = 0; | ||
public name = new Name(); | ||
public uri = ""; | ||
public cost = 0; | ||
} | ||
@@ -108,5 +108,5 @@ | ||
const EVD = new EvDecoder<Adjacency>("Adjacency", TT.Adjacency) | ||
.add(TT.Name, (t, { decoder }) => t.name = decoder.decode(Name)) | ||
.add(TT.Uri, (t, { value }) => t.uri = new TextDecoder().decode(value)) | ||
.add(TT.Cost, (t, { nni }) => t.cost = nni); | ||
.add(TT.Name, (t, { decoder }) => t.name = decoder.decode(Name), { required: true }) | ||
.add(TT.Uri, (t, { text }) => t.uri = text, { required: true }) | ||
.add(TT.Cost, (t, { nni }) => t.cost = nni, { required: true }); | ||
// Each rule declares a possible sub TLV. | ||
@@ -131,8 +131,5 @@ // They are added in the order of expected appearance. | ||
const adjacency = EVD.decode(new Adjacency(), adjacencyDecoder); | ||
assert.equal(adjacency.name.toString(), "/A"); | ||
assert.equal(adjacency.name.toString(), "/8=A"); | ||
assert.equal(adjacency.uri, "B"); | ||
assert.equal(adjacency.cost, 128); | ||
// Currently, EvDecoder itself cannot enforce a certain sub TLV is present. | ||
// Pull Requests are welcome to add 'options.required' argument. | ||
``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
35404
935
131