diff --git a/packages/ripple-binary-codec/.eslintrc.js b/packages/ripple-binary-codec/.eslintrc.js index 6a2c0bc1..df6a46f9 100644 --- a/packages/ripple-binary-codec/.eslintrc.js +++ b/packages/ripple-binary-codec/.eslintrc.js @@ -87,6 +87,7 @@ module.exports = { '@typescript-eslint/no-unsafe-call': 'off', '@typescript-eslint/no-unsafe-member-access': 'off', '@typescript-eslint/no-unsafe-assignment': 'off', + "spaced-comment": ["error", "always"], }, }, { diff --git a/packages/ripple-binary-codec/src/serdes/binary-parser.ts b/packages/ripple-binary-codec/src/serdes/binary-parser.ts index b20810df..3b17327d 100644 --- a/packages/ripple-binary-codec/src/serdes/binary-parser.ts +++ b/packages/ripple-binary-codec/src/serdes/binary-parser.ts @@ -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); diff --git a/packages/ripple-binary-codec/src/types/path-set.ts b/packages/ripple-binary-codec/src/types/path-set.ts index de1bea04..de869226 100644 --- a/packages/ripple-binary-codec/src/types/path-set.ts +++ b/packages/ripple-binary-codec/src/types/path-set.ts @@ -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 = {}; - 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.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.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): Path { + if (value instanceof Path) { + return value; + } + + const bytes: Array = []; + 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 = []; + 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 = []; + 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>): PathSet { + if (value instanceof PathSet) { + return value; + } + + const bytes: Array = []; + + value.forEach((path: Array) => { + 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 = []; + + 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> { + const json: Array> = []; + const pathParser = new BinaryParser(this.bytes.toString("hex")); + + while (!pathParser.end()) { + json.push(Path.fromParser(pathParser).toJSON()); + pathParser.skip(1); + } + + return json; + } +} export { PathSet }; diff --git a/packages/ripple-binary-codec/src/types/uint-16.ts b/packages/ripple-binary-codec/src/types/uint-16.ts index 57c92a68..0d2e12e3 100644 --- a/packages/ripple-binary-codec/src/types/uint-16.ts +++ b/packages/ripple-binary-codec/src/types/uint-16.ts @@ -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 { diff --git a/packages/ripple-binary-codec/src/types/uint-32.ts b/packages/ripple-binary-codec/src/types/uint-32.ts index 397b2687..0ea93805 100644 --- a/packages/ripple-binary-codec/src/types/uint-32.ts +++ b/packages/ripple-binary-codec/src/types/uint-32.ts @@ -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 { diff --git a/packages/ripple-binary-codec/src/types/uint-64.ts b/packages/ripple-binary-codec/src/types/uint-64.ts index 8ac882c3..34c90119 100644 --- a/packages/ripple-binary-codec/src/types/uint-64.ts +++ b/packages/ripple-binary-codec/src/types/uint-64.ts @@ -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 { diff --git a/packages/ripple-binary-codec/src/types/uint-8.ts b/packages/ripple-binary-codec/src/types/uint-8.ts index 12e46171..f81ca5a9 100644 --- a/packages/ripple-binary-codec/src/types/uint-8.ts +++ b/packages/ripple-binary-codec/src/types/uint-8.ts @@ -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 { diff --git a/packages/ripple-binary-codec/src/types/uint.ts b/packages/ripple-binary-codec/src/types/uint.ts index 8a3635e4..533fd7c5 100644 --- a/packages/ripple-binary-codec/src/types/uint.ts +++ b/packages/ripple-binary-codec/src/types/uint.ts @@ -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; diff --git a/packages/ripple-binary-codec/src/types/vector-256.ts b/packages/ripple-binary-codec/src/types/vector-256.ts index 33e91caa..6fe67e71 100644 --- a/packages/ripple-binary-codec/src/types/vector-256.ts +++ b/packages/ripple-binary-codec/src/types/vector-256.ts @@ -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): 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 { - 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 = [] - for(let i = 0; i < this.bytes.byteLength; i += 32) { - result.push(this.bytes.slice(i,i+32).toString('hex').toUpperCase()) + const result: Array = []; + 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; } }