refactored ./src/types/path-set (#84)

Refactored PathSet, Path, and Hop types. Constructing these types with Buffers, and using class instead of makeClass.
This commit is contained in:
Nathan Nichols
2020-07-13 16:35:08 -05:00
parent ba04ea5f1f
commit 51ad4e36fc
9 changed files with 330 additions and 178 deletions

View File

@@ -87,6 +87,7 @@ module.exports = {
'@typescript-eslint/no-unsafe-call': 'off', '@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off', '@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off', '@typescript-eslint/no-unsafe-assignment': 'off',
"spaced-comment": ["error", "always"],
}, },
}, },
{ {

View File

@@ -16,6 +16,16 @@ class BinaryParser {
this.bytes = Buffer.from(hexBytes, "hex"); this.bytes = Buffer.from(hexBytes, "hex");
} }
/**
* Peek the first byte of the BinaryParser
*
* @returns The first byte of the BinaryParser
*/
peek(): number {
assert(this.bytes.byteLength !== 0);
return this.bytes[0];
}
/** /**
* Consume the first n bytes of the BinaryParser * Consume the first n bytes of the BinaryParser
* *
@@ -33,7 +43,10 @@ class BinaryParser {
* @return The bytes * @return The bytes
*/ */
read(n: number): Buffer { read(n: number): Buffer {
assert(n <= this.bytes.byteLength, n + " greater than " + this.bytes.byteLength); assert(
n <= this.bytes.byteLength,
n + " greater than " + this.bytes.byteLength
);
const slice = this.bytes.slice(0, n); const slice = this.bytes.slice(0, n);
this.skip(n); this.skip(n);

View File

@@ -1,120 +1,250 @@
/* eslint-disable no-unused-expressions */ import { AccountID } from "./account-id";
import { Currency } from "./currency";
import { makeClass } from "../utils/make-class"; import { BinaryParser } from "../serdes/binary-parser";
const { SerializedType, ensureArrayLikeIs } = require("./serialized-type"); import { SerializedTypeClass } from "./serialized-type";
const { Currency } = require("./currency");
const { AccountID } = require("./account-id");
/**
* Constants for separating Paths in a PathSet
*/
const PATHSET_END_BYTE = 0x00; const PATHSET_END_BYTE = 0x00;
const PATH_SEPARATOR_BYTE = 0xff; const PATH_SEPARATOR_BYTE = 0xff;
/**
* Constant for masking types of a Hop
*/
const TYPE_ACCOUNT = 0x01; const TYPE_ACCOUNT = 0x01;
const TYPE_CURRENCY = 0x10; const TYPE_CURRENCY = 0x10;
const TYPE_ISSUER = 0x20; const TYPE_ISSUER = 0x20;
const Hop = makeClass( /**
{ * The object representation of a Hop, an issuer AccountID, an account AccountID, and a Currency
statics: { */
from(value) { interface HopObject {
if (value instanceof this) { issuer?: string;
return value; account?: string;
} currency?: string;
const hop = new Hop(); }
value.issuer && (hop.issuer = AccountID.from(value.issuer));
value.account && (hop.account = AccountID.from(value.account));
value.currency && (hop.currency = Currency.from(value.currency));
return hop;
},
parse(parser, type) {
const hop = new Hop();
type & TYPE_ACCOUNT && (hop.account = AccountID.fromParser(parser));
type & TYPE_CURRENCY && (hop.currency = Currency.fromParser(parser));
type & TYPE_ISSUER && (hop.issuer = AccountID.fromParser(parser));
return hop;
},
},
toJSON() {
const type = this.type();
const ret = <any>{};
type & TYPE_ACCOUNT && (ret.account = this.account.toJSON());
type & TYPE_ISSUER && (ret.issuer = this.issuer.toJSON());
type & TYPE_CURRENCY && (ret.currency = this.currency.toJSON());
return ret;
},
type() {
let type = 0;
this.issuer && (type += TYPE_ISSUER);
this.account && (type += TYPE_ACCOUNT);
this.currency && (type += TYPE_CURRENCY);
return type;
},
},
undefined
);
const Path = makeClass( /**
{ * Serialize and Deserialize a Hop
inherits: Array, */
statics: { class Hop extends SerializedTypeClass {
from(value) { /**
return ensureArrayLikeIs(Path, value).withChildren(Hop); * Create a Hop from a HopObject
}, *
}, * @param value Either a hop or HopObject to create a hop with
toJSON() { * @returns a Hop
return this.map((k) => k.toJSON()); */
}, static from(value: Hop | HopObject): Hop {
}, if (value instanceof Hop) {
undefined return value;
); }
const PathSet = makeClass( const bytes: Array<Buffer> = [Buffer.from([0])];
{
mixins: SerializedType, if (value.account) {
inherits: Array, bytes.push(AccountID.from(value.account).toBytes());
statics: { bytes[0][0] |= TYPE_ACCOUNT;
from(value) { }
return ensureArrayLikeIs(PathSet, value).withChildren(Path);
}, if (value.currency) {
fromParser(parser) { bytes.push(Currency.from(value.currency).toBytes());
const pathSet = new this(); bytes[0][0] |= TYPE_CURRENCY;
let path; }
while (!parser.end()) {
const type = parser.readUInt8(); if (value.issuer) {
if (type === PATHSET_END_BYTE) { bytes.push(AccountID.from(value.issuer).toBytes());
break; bytes[0][0] |= TYPE_ISSUER;
} }
if (type === PATH_SEPARATOR_BYTE) {
path = null; return new Hop(Buffer.concat(bytes));
continue; }
}
if (!path) { /**
path = new Path(); * Construct a Hop from a BinaryParser
pathSet.push(path); *
} * @param parser BinaryParser to read the Hop from
path.push(Hop.parse(parser, type)); * @returns a Hop
} */
return pathSet; static fromParser(parser: BinaryParser): Hop {
}, const type = parser.readUInt8();
}, const bytes: Array<Buffer> = [Buffer.from([type])];
toJSON() {
return this.map((k) => k.toJSON()); if (type & TYPE_ACCOUNT) {
}, bytes.push(parser.read(AccountID.width));
toBytesSink(sink) { }
let n = 0;
this.forEach((path) => { if (type & TYPE_CURRENCY) {
if (n++ !== 0) { bytes.push(parser.read(Currency.width));
sink.put([PATH_SEPARATOR_BYTE]); }
}
path.forEach((hop) => { if (type & TYPE_ISSUER) {
sink.put([hop.type()]); bytes.push(parser.read(AccountID.width));
hop.account && hop.account.toBytesSink(sink); }
hop.currency && hop.currency.toBytesSink(sink);
hop.issuer && hop.issuer.toBytesSink(sink); return new Hop(Buffer.concat(bytes));
}); }
});
sink.put([PATHSET_END_BYTE]); /**
}, * Get the JSON interpretation of this hop
}, *
undefined * @returns a HopObject, an JS object with optional account, issuer, and currency
); */
toJSON(): HopObject {
const hopParser = new BinaryParser(this.bytes.toString("hex"));
const type = hopParser.readUInt8();
const result: HopObject = {};
if (type & TYPE_ACCOUNT) {
result.account = AccountID.fromParser(hopParser).toJSON();
}
if (type & TYPE_CURRENCY) {
result.currency = Currency.fromParser(hopParser).toJSON();
}
if (type & TYPE_ISSUER) {
result.issuer = AccountID.fromParser(hopParser).toJSON();
}
return result;
}
/**
* get a number representing the type of this hop
*
* @returns a number to be bitwise and-ed with TYPE_ constants to describe the types in the hop
*/
type(): number {
return this.bytes[0];
}
}
/**
* Class for serializing/deserializing Paths
*/
class Path extends SerializedTypeClass {
/**
* construct a Path from an array of Hops
*
* @param value Path or array of HopObjects to construct a Path
* @returns the Path
*/
static from(value: Path | Array<HopObject>): Path {
if (value instanceof Path) {
return value;
}
const bytes: Array<Buffer> = [];
value.forEach((hop: HopObject) => {
bytes.push(Hop.from(hop).toBytes());
});
return new Path(Buffer.concat(bytes));
}
/**
* Read a Path from a BinaryParser
*
* @param parser BinaryParser to read Path from
* @returns the Path represented by the bytes read from the BinaryParser
*/
static fromParser(parser: BinaryParser): Path {
const bytes: Array<Buffer> = [];
while (!parser.end()) {
bytes.push(Hop.fromParser(parser).toBytes());
if (
parser.peek() === PATHSET_END_BYTE ||
parser.peek() === PATH_SEPARATOR_BYTE
) {
break;
}
}
return new Path(Buffer.concat(bytes));
}
/**
* Get the JSON representation of this Path
*
* @returns an Array of HopObject constructed from this.bytes
*/
toJSON() {
const json: Array<HopObject> = [];
const pathParser = new BinaryParser(this.bytes.toString("hex"));
while (!pathParser.end()) {
json.push(Hop.fromParser(pathParser).toJSON());
}
return json;
}
}
/**
* Deserialize and Serialize the PathSet type
*/
class PathSet extends SerializedTypeClass {
/**
* Construct a PathSet from an Array of Arrays representing paths
*
* @param value A PathSet or Array of Array of HopObjects
* @returns the PathSet constructed from value
*/
static from(value: PathSet | Array<Array<HopObject>>): PathSet {
if (value instanceof PathSet) {
return value;
}
const bytes: Array<Buffer> = [];
value.forEach((path: Array<HopObject>) => {
bytes.push(Path.from(path).toBytes());
bytes.push(Buffer.from([PATH_SEPARATOR_BYTE]));
});
bytes[bytes.length - 1] = Buffer.from([PATHSET_END_BYTE]);
return new PathSet(Buffer.concat(bytes));
}
/**
* Construct a PathSet from a BinaryParser
*
* @param parser A BinaryParser to read PathSet from
* @returns the PathSet read from parser
*/
static fromParser(parser: BinaryParser): PathSet {
const bytes: Array<Buffer> = [];
while (!parser.end()) {
bytes.push(Path.fromParser(parser).toBytes());
bytes.push(parser.read(1));
if (bytes[bytes.length - 1][0] == PATHSET_END_BYTE) {
break;
}
}
return new PathSet(Buffer.concat(bytes));
}
/**
* Get the JSON representation of this PathSet
*
* @returns an Array of Array of HopObjects, representing this PathSet
*/
toJSON(): Array<Array<HopObject>> {
const json: Array<Array<HopObject>> = [];
const pathParser = new BinaryParser(this.bytes.toString("hex"));
while (!pathParser.end()) {
json.push(Path.fromParser(pathParser).toJSON());
pathParser.skip(1);
}
return json;
}
}
export { PathSet }; export { PathSet };

View File

@@ -5,11 +5,13 @@ import { BinaryParser } from "../serdes/binary-parser";
* Derived UInt class for serializing/deserializing 16 bit UInt * Derived UInt class for serializing/deserializing 16 bit UInt
*/ */
class UInt16 extends UInt { class UInt16 extends UInt {
protected static readonly width: number = 16 / 8 //2 protected static readonly width: number = 16 / 8; // 2
static readonly defaultUInt16: UInt16 = new UInt16(Buffer.alloc(UInt16.width)) static readonly defaultUInt16: UInt16 = new UInt16(
Buffer.alloc(UInt16.width)
);
constructor(bytes: Buffer) { constructor(bytes: Buffer) {
super(bytes ?? UInt16.defaultUInt16.bytes) super(bytes ?? UInt16.defaultUInt16.bytes);
} }
static fromParser(parser: BinaryParser): UInt { static fromParser(parser: BinaryParser): UInt {
@@ -18,22 +20,22 @@ class UInt16 extends UInt {
/** /**
* Construct a UInt16 object from a number * Construct a UInt16 object from a number
* *
* @param val UInt16 object or number * @param val UInt16 object or number
*/ */
static from(val: UInt16 | number): UInt16 { static from(val: UInt16 | number): UInt16 {
if(val instanceof UInt16) { if (val instanceof UInt16) {
return val; return val;
} }
let buf = Buffer.alloc(UInt16.width); const buf = Buffer.alloc(UInt16.width);
buf.writeUInt16BE(val); buf.writeUInt16BE(val);
return new UInt16(buf); return new UInt16(buf);
} }
/** /**
* get the value of a UInt16 object * get the value of a UInt16 object
* *
* @returns the number represented by this.bytes * @returns the number represented by this.bytes
*/ */
valueOf(): number { valueOf(): number {

View File

@@ -5,11 +5,13 @@ import { BinaryParser } from "../serdes/binary-parser";
* Derived UInt class for serializing/deserializing 32 bit UInt * Derived UInt class for serializing/deserializing 32 bit UInt
*/ */
class UInt32 extends UInt { class UInt32 extends UInt {
protected static readonly width: number = 32 / 8 //4 protected static readonly width: number = 32 / 8; // 4
static readonly defaultUInt32: UInt32 = new UInt32(Buffer.alloc(UInt32.width)) static readonly defaultUInt32: UInt32 = new UInt32(
Buffer.alloc(UInt32.width)
);
constructor(bytes: Buffer) { constructor(bytes: Buffer) {
super(bytes ?? UInt32.defaultUInt32.bytes) super(bytes ?? UInt32.defaultUInt32.bytes);
} }
static fromParser(parser: BinaryParser): UInt { static fromParser(parser: BinaryParser): UInt {
@@ -18,22 +20,22 @@ class UInt32 extends UInt {
/** /**
* Construct a UInt32 object from a number * Construct a UInt32 object from a number
* *
* @param val UInt32 object or number * @param val UInt32 object or number
*/ */
static from(val: UInt32 | number): UInt32 { static from(val: UInt32 | number): UInt32 {
if(val instanceof UInt32) { if (val instanceof UInt32) {
return val; return val;
} }
let buf = Buffer.alloc(UInt32.width); const buf = Buffer.alloc(UInt32.width);
buf.writeUInt32BE(val); buf.writeUInt32BE(val);
return new UInt32(buf); return new UInt32(buf);
} }
/** /**
* get the value of a UInt32 object * get the value of a UInt32 object
* *
* @returns the number represented by this.bytes * @returns the number represented by this.bytes
*/ */
valueOf(): number { valueOf(): number {

View File

@@ -7,11 +7,13 @@ const HEX_REGEX = /^[A-F0-9]{16}$/;
* Derived UInt class for serializing/deserializing 64 bit UInt * Derived UInt class for serializing/deserializing 64 bit UInt
*/ */
class UInt64 extends UInt { class UInt64 extends UInt {
protected static readonly width: number = 64 / 8 //8 protected static readonly width: number = 64 / 8; // 8
static readonly defaultUInt64: UInt64 = new UInt64(Buffer.alloc(UInt64.width)) static readonly defaultUInt64: UInt64 = new UInt64(
Buffer.alloc(UInt64.width)
);
constructor(bytes: Buffer) { constructor(bytes: Buffer) {
super(bytes ?? UInt64.defaultUInt64.bytes) super(bytes ?? UInt64.defaultUInt64.bytes);
} }
static fromParser(parser: BinaryParser): UInt { static fromParser(parser: BinaryParser): UInt {
@@ -20,31 +22,30 @@ class UInt64 extends UInt {
/** /**
* Construct a UInt64 object * Construct a UInt64 object
* *
* @param val A UInt64, hex-string, bigint, or number * @param val A UInt64, hex-string, bigint, or number
* @returns A UInt64 object * @returns A UInt64 object
*/ */
static from(val: UInt64 | string | bigint | number): UInt64 { static from(val: UInt64 | string | bigint | number): UInt64 {
if(val instanceof UInt64) { if (val instanceof UInt64) {
return val; return val;
} }
let buf = Buffer.alloc(UInt64.width); let buf = Buffer.alloc(UInt64.width);
if(typeof val === "number") { if (typeof val === "number") {
if(val < 0) { if (val < 0) {
throw new Error("value must be an unsigned integer"); throw new Error("value must be an unsigned integer");
} }
buf.writeBigUInt64BE(BigInt(val)); buf.writeBigUInt64BE(BigInt(val));
} } else if (typeof val === "string") {
else if (typeof val === "string") { if (!HEX_REGEX.test(val)) {
if(!HEX_REGEX.test(val)) { throw new Error(val + "is not a valid hex-string");
throw new Error(val + "is not a valid hex-string")
} }
buf = Buffer.from(val, "hex") buf = Buffer.from(val, "hex");
} } else {
else { // typeof val === bigint // typeof val === bigint
buf.writeBigUInt64BE(val) buf.writeBigUInt64BE(val);
} }
return new UInt64(buf); return new UInt64(buf);
@@ -52,16 +53,16 @@ class UInt64 extends UInt {
/** /**
* The JSON representation of a UInt64 object * The JSON representation of a UInt64 object
* *
* @returns a hex-string * @returns a hex-string
*/ */
toJSON(): string { toJSON(): string {
return this.bytes.toString('hex').toUpperCase(); return this.bytes.toString("hex").toUpperCase();
} }
/** /**
* Get the value of the UInt64 * Get the value of the UInt64
* *
* @returns the number represented buy this.bytes * @returns the number represented buy this.bytes
*/ */
valueOf(): bigint { valueOf(): bigint {
@@ -70,7 +71,7 @@ class UInt64 extends UInt {
/** /**
* Get the bytes representation of the UInt64 object * Get the bytes representation of the UInt64 object
* *
* @returns 8 bytes representing the UInt64 * @returns 8 bytes representing the UInt64
*/ */
toBytes(): Buffer { toBytes(): Buffer {

View File

@@ -5,11 +5,11 @@ import { BinaryParser } from "../serdes/binary-parser";
* Derived UInt class for serializing/deserializing 8 bit UInt * Derived UInt class for serializing/deserializing 8 bit UInt
*/ */
class UInt8 extends UInt { class UInt8 extends UInt {
protected static readonly width: number = 8 / 8 //1 protected static readonly width: number = 8 / 8; // 1
static readonly defaultUInt8: UInt8 = new UInt8(Buffer.alloc(UInt8.width)) static readonly defaultUInt8: UInt8 = new UInt8(Buffer.alloc(UInt8.width));
constructor(bytes: Buffer) { constructor(bytes: Buffer) {
super(bytes ?? UInt8.defaultUInt8.bytes) super(bytes ?? UInt8.defaultUInt8.bytes);
} }
static fromParser(parser: BinaryParser): UInt { static fromParser(parser: BinaryParser): UInt {
@@ -18,22 +18,22 @@ class UInt8 extends UInt {
/** /**
* Construct a UInt8 object from a number * Construct a UInt8 object from a number
* *
* @param val UInt8 object or number * @param val UInt8 object or number
*/ */
static from(val: UInt8 | number): UInt8 { static from(val: UInt8 | number): UInt8 {
if(val instanceof UInt8) { if (val instanceof UInt8) {
return val; return val;
} }
let buf = Buffer.alloc(UInt8.width); const buf = Buffer.alloc(UInt8.width);
buf.writeUInt8(val); buf.writeUInt8(val);
return new UInt8(buf); return new UInt8(buf);
} }
/** /**
* get the value of a UInt8 object * get the value of a UInt8 object
* *
* @returns the number represented by this.bytes * @returns the number represented by this.bytes
*/ */
valueOf(): number { valueOf(): number {

View File

@@ -2,28 +2,28 @@ import { ComparableClass } from "./serialized-type";
/** /**
* Compare numbers and bigints n1 and n2 * Compare numbers and bigints n1 and n2
* *
* @param n1 First object to compare * @param n1 First object to compare
* @param n2 Second object to compare * @param n2 Second object to compare
* @returns -1, 0, or 1, depending on how the two objects compare * @returns -1, 0, or 1, depending on how the two objects compare
*/ */
function compare(n1: number | bigint, n2: number | bigint): number { function compare(n1: number | bigint, n2: number | bigint): number {
return n1 < n2 ? -1 : n1 == n2 ? 0 : 1 return n1 < n2 ? -1 : n1 == n2 ? 0 : 1;
} }
/** /**
* Base class for serializing and deserializing unsigned integers. * Base class for serializing and deserializing unsigned integers.
*/ */
abstract class UInt extends ComparableClass { abstract class UInt extends ComparableClass {
protected static width: number protected static width: number;
constructor(bytes: Buffer) { constructor(bytes: Buffer) {
super(bytes) super(bytes);
} }
/** /**
* Overload of compareTo for Comparable * Overload of compareTo for Comparable
* *
* @param other other UInt to compare this to * @param other other UInt to compare this to
* @returns -1, 0, or 1 depending on how the objects relate to each other * @returns -1, 0, or 1 depending on how the objects relate to each other
*/ */
@@ -33,19 +33,17 @@ abstract class UInt extends ComparableClass {
/** /**
* Convert a UInt object to JSON * Convert a UInt object to JSON
* *
* @returns number or string represented by this.bytes * @returns number or string represented by this.bytes
*/ */
toJSON(): number | string { toJSON(): number | string {
let val = this.valueOf() const val = this.valueOf();
return typeof val === "number" return typeof val === "number" ? val : val.toString();
? val
: val.toString();
} }
/** /**
* Get the value of the UInt represented by this.bytes * Get the value of the UInt represented by this.bytes
* *
* @returns the value * @returns the value
*/ */
abstract valueOf(): number | bigint; abstract valueOf(): number | bigint;

View File

@@ -8,18 +8,18 @@ import { BytesList } from "../serdes/binary-serializer";
*/ */
class Vector256 extends SerializedTypeClass { class Vector256 extends SerializedTypeClass {
constructor(bytes: Buffer) { constructor(bytes: Buffer) {
super(bytes) super(bytes);
} }
/** /**
* Construct a Vector256 from a BinaryParser * Construct a Vector256 from a BinaryParser
* *
* @param parser BinaryParser to * @param parser BinaryParser to
* @param hint length of the vector, in bytes, optional * @param hint length of the vector, in bytes, optional
* @returns a Vector256 object * @returns a Vector256 object
*/ */
static fromParser(parser: BinaryParser, hint?: number): Vector256 { static fromParser(parser: BinaryParser, hint?: number): Vector256 {
let bytesList = new BytesList(); const bytesList = new BytesList();
const bytes = hint ?? parser.size(); const bytes = hint ?? parser.size();
const hashes = bytes / 32; const hashes = bytes / 32;
for (let i = 0; i < hashes; i++) { for (let i = 0; i < hashes; i++) {
@@ -30,17 +30,17 @@ class Vector256 extends SerializedTypeClass {
/** /**
* Construct a Vector256 object from an array of hashes * Construct a Vector256 object from an array of hashes
* *
* @param value A Vector256 object or array of hex-strings representing Hash256's * @param value A Vector256 object or array of hex-strings representing Hash256's
* @returns a Vector256 object * @returns a Vector256 object
*/ */
static from(value: Vector256 | Array<string>): Vector256 { static from(value: Vector256 | Array<string>): Vector256 {
if(value instanceof Vector256) { if (value instanceof Vector256) {
return value; return value;
} }
let bytesList = new BytesList(); const bytesList = new BytesList();
value.forEach(hash => { value.forEach((hash) => {
Hash256.from(hash).toBytesSink(bytesList); Hash256.from(hash).toBytesSink(bytesList);
}); });
return new Vector256(bytesList.toBytes()); return new Vector256(bytesList.toBytes());
@@ -48,19 +48,24 @@ class Vector256 extends SerializedTypeClass {
/** /**
* Return an Array of hex-strings represented by this.bytes * Return an Array of hex-strings represented by this.bytes
* *
* @returns An Array of strings representing the Hash256 objects * @returns An Array of strings representing the Hash256 objects
*/ */
toJSON(): Array<string> { toJSON(): Array<string> {
if(this.bytes.byteLength % 32 !== 0) { if (this.bytes.byteLength % 32 !== 0) {
throw new Error("Invalid bytes for Vector256") throw new Error("Invalid bytes for Vector256");
} }
let result: Array<string> = [] const result: Array<string> = [];
for(let i = 0; i < this.bytes.byteLength; i += 32) { for (let i = 0; i < this.bytes.byteLength; i += 32) {
result.push(this.bytes.slice(i,i+32).toString('hex').toUpperCase()) result.push(
this.bytes
.slice(i, i + 32)
.toString("hex")
.toUpperCase()
);
} }
return result return result;
} }
} }