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;
account?: string;
currency?: string;
}
/**
* Serialize and Deserialize a Hop
*/
class Hop extends SerializedTypeClass {
/**
* Create a Hop from a HopObject
*
* @param value Either a hop or HopObject to create a hop with
* @returns a Hop
*/
static from(value: Hop | HopObject): Hop {
if (value instanceof Hop) {
return value; return value;
} }
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( const bytes: Array<Buffer> = [Buffer.from([0])];
{
inherits: Array,
statics: {
from(value) {
return ensureArrayLikeIs(Path, value).withChildren(Hop);
},
},
toJSON() {
return this.map((k) => k.toJSON());
},
},
undefined
);
const PathSet = makeClass( if (value.account) {
{ bytes.push(AccountID.from(value.account).toBytes());
mixins: SerializedType, bytes[0][0] |= TYPE_ACCOUNT;
inherits: Array, }
statics: {
from(value) { if (value.currency) {
return ensureArrayLikeIs(PathSet, value).withChildren(Path); bytes.push(Currency.from(value.currency).toBytes());
}, bytes[0][0] |= TYPE_CURRENCY;
fromParser(parser) { }
const pathSet = new this();
let path; if (value.issuer) {
while (!parser.end()) { bytes.push(AccountID.from(value.issuer).toBytes());
bytes[0][0] |= TYPE_ISSUER;
}
return new Hop(Buffer.concat(bytes));
}
/**
* Construct a Hop from a BinaryParser
*
* @param parser BinaryParser to read the Hop from
* @returns a Hop
*/
static fromParser(parser: BinaryParser): Hop {
const type = parser.readUInt8(); const type = parser.readUInt8();
if (type === PATHSET_END_BYTE) { const bytes: Array<Buffer> = [Buffer.from([type])];
if (type & TYPE_ACCOUNT) {
bytes.push(parser.read(AccountID.width));
}
if (type & TYPE_CURRENCY) {
bytes.push(parser.read(Currency.width));
}
if (type & TYPE_ISSUER) {
bytes.push(parser.read(AccountID.width));
}
return new Hop(Buffer.concat(bytes));
}
/**
* Get the JSON interpretation of this hop
*
* @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; break;
} }
if (type === PATH_SEPARATOR_BYTE) {
path = null;
continue;
} }
if (!path) { return new Path(Buffer.concat(bytes));
path = new Path();
pathSet.push(path);
} }
path.push(Hop.parse(parser, type));
} /**
return pathSet; * Get the JSON representation of this Path
}, *
}, * @returns an Array of HopObject constructed from this.bytes
*/
toJSON() { toJSON() {
return this.map((k) => k.toJSON()); const json: Array<HopObject> = [];
}, const pathParser = new BinaryParser(this.bytes.toString("hex"));
toBytesSink(sink) {
let n = 0; while (!pathParser.end()) {
this.forEach((path) => { json.push(Hop.fromParser(pathParser).toJSON());
if (n++ !== 0) {
sink.put([PATH_SEPARATOR_BYTE]);
} }
path.forEach((hop) => {
sink.put([hop.type()]); return json;
hop.account && hop.account.toBytesSink(sink); }
hop.currency && hop.currency.toBytesSink(sink); }
hop.issuer && hop.issuer.toBytesSink(sink);
/**
* 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]));
}); });
});
sink.put([PATHSET_END_BYTE]); bytes[bytes.length - 1] = Buffer.from([PATHSET_END_BYTE]);
},
}, return new PathSet(Buffer.concat(bytes));
undefined }
);
/**
* 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 {
@@ -22,11 +24,11 @@ class UInt16 extends UInt {
* @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);
} }

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 {
@@ -22,11 +24,11 @@ class UInt32 extends UInt {
* @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);
} }

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 {
@@ -25,26 +27,25 @@ class UInt64 extends UInt {
* @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") {
if (!HEX_REGEX.test(val)) {
throw new Error(val + "is not a valid hex-string");
} }
else if (typeof val === "string") { buf = Buffer.from(val, "hex");
if(!HEX_REGEX.test(val)) { } else {
throw new Error(val + "is not a valid hex-string") // typeof val === bigint
} buf.writeBigUInt64BE(val);
buf = Buffer.from(val, "hex")
}
else { // typeof val === bigint
buf.writeBigUInt64BE(val)
} }
return new UInt64(buf); return new UInt64(buf);
@@ -56,7 +57,7 @@ class UInt64 extends UInt {
* @returns a hex-string * @returns a hex-string
*/ */
toJSON(): string { toJSON(): string {
return this.bytes.toString('hex').toUpperCase(); return this.bytes.toString("hex").toUpperCase();
} }
/** /**

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 {
@@ -22,11 +22,11 @@ class UInt8 extends UInt {
* @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);
} }

View File

@@ -8,17 +8,17 @@ import { ComparableClass } from "./serialized-type";
* @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);
} }
/** /**
@@ -37,10 +37,8 @@ abstract class UInt extends ComparableClass {
* @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();
} }
/** /**

View File

@@ -8,7 +8,7 @@ import { BytesList } from "../serdes/binary-serializer";
*/ */
class Vector256 extends SerializedTypeClass { class Vector256 extends SerializedTypeClass {
constructor(bytes: Buffer) { constructor(bytes: Buffer) {
super(bytes) super(bytes);
} }
/** /**
@@ -19,7 +19,7 @@ class Vector256 extends SerializedTypeClass {
* @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++) {
@@ -35,12 +35,12 @@ class Vector256 extends SerializedTypeClass {
* @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());
@@ -52,15 +52,20 @@ class Vector256 extends SerializedTypeClass {
* @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;
} }
} }