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

@@ -16,6 +16,16 @@ class BinaryParser {
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
*
@@ -33,7 +43,10 @@ class BinaryParser {
* @return The bytes
*/
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);
this.skip(n);

View File

@@ -1,120 +1,250 @@
/* eslint-disable no-unused-expressions */
import { makeClass } from "../utils/make-class";
const { SerializedType, ensureArrayLikeIs } = require("./serialized-type");
const { Currency } = require("./currency");
const { AccountID } = require("./account-id");
import { AccountID } from "./account-id";
import { Currency } from "./currency";
import { BinaryParser } from "../serdes/binary-parser";
import { SerializedTypeClass } from "./serialized-type";
/**
* Constants for separating Paths in a PathSet
*/
const PATHSET_END_BYTE = 0x00;
const PATH_SEPARATOR_BYTE = 0xff;
/**
* Constant for masking types of a Hop
*/
const TYPE_ACCOUNT = 0x01;
const TYPE_CURRENCY = 0x10;
const TYPE_ISSUER = 0x20;
const Hop = makeClass(
{
statics: {
from(value) {
if (value instanceof this) {
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
);
/**
* The object representation of a Hop, an issuer AccountID, an account AccountID, and a Currency
*/
interface HopObject {
issuer?: string;
account?: string;
currency?: string;
}
const Path = makeClass(
{
inherits: Array,
statics: {
from(value) {
return ensureArrayLikeIs(Path, value).withChildren(Hop);
},
},
toJSON() {
return this.map((k) => k.toJSON());
},
},
undefined
);
/**
* 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;
}
const PathSet = makeClass(
{
mixins: SerializedType,
inherits: Array,
statics: {
from(value) {
return ensureArrayLikeIs(PathSet, value).withChildren(Path);
},
fromParser(parser) {
const pathSet = new this();
let path;
while (!parser.end()) {
const type = parser.readUInt8();
if (type === PATHSET_END_BYTE) {
break;
}
if (type === PATH_SEPARATOR_BYTE) {
path = null;
continue;
}
if (!path) {
path = new Path();
pathSet.push(path);
}
path.push(Hop.parse(parser, type));
}
return pathSet;
},
},
toJSON() {
return this.map((k) => k.toJSON());
},
toBytesSink(sink) {
let n = 0;
this.forEach((path) => {
if (n++ !== 0) {
sink.put([PATH_SEPARATOR_BYTE]);
}
path.forEach((hop) => {
sink.put([hop.type()]);
hop.account && hop.account.toBytesSink(sink);
hop.currency && hop.currency.toBytesSink(sink);
hop.issuer && hop.issuer.toBytesSink(sink);
});
});
sink.put([PATHSET_END_BYTE]);
},
},
undefined
);
const bytes: Array<Buffer> = [Buffer.from([0])];
if (value.account) {
bytes.push(AccountID.from(value.account).toBytes());
bytes[0][0] |= TYPE_ACCOUNT;
}
if (value.currency) {
bytes.push(Currency.from(value.currency).toBytes());
bytes[0][0] |= TYPE_CURRENCY;
}
if (value.issuer) {
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 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;
}
}
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 };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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