@typeberry/trie
Advanced tools
Comparing version 0.0.1-aae42cd to 0.0.1-c48d761
{ | ||
"name": "@typeberry/trie", | ||
"version": "0.0.1-aae42cd", | ||
"version": "0.0.1-c48d761", | ||
"main": "trie.js", | ||
@@ -5,0 +5,0 @@ "author": "Fluffy Labs", |
@@ -0,19 +1,47 @@ | ||
/** | ||
* A variable-length blob of bytes with a concise text representation. | ||
* | ||
* The structure is used as convenience wrapper for [`Uint8Array`], | ||
* especially if the data is coming from a hex-encoded string. | ||
*/ | ||
export declare class BytesBlob { | ||
readonly buffer: Uint8Array; | ||
readonly length: number; | ||
constructor(data: Uint8Array); | ||
private constructor(); | ||
/** | ||
* Display a hex-encoded version of this byte blob. | ||
*/ | ||
toString(): string; | ||
static fromBytes(v: number[]): BytesBlob; | ||
/** Create a new [`BytesBlob`] from existing [`Uint8Array`]. */ | ||
static fromBlob(v: Uint8Array): BytesBlob; | ||
/** Create a new [`BytesBlob`] from an array of bytes. */ | ||
static fromNumbers(v: number[]): BytesBlob; | ||
/** Parse a hex-encoded bytes blob without `0x` prefix. */ | ||
static parseBlobNoPrefix(v: string): BytesBlob; | ||
/** Parse a hex-encoded bytes blob with `0x` prefix. */ | ||
static parseBlob(v: string): BytesBlob; | ||
} | ||
/** | ||
* A convenience wrapper for a fix-length sequence of bytes. | ||
*/ | ||
export declare class Bytes<T extends number> { | ||
/** Raw bytes array. */ | ||
readonly raw: Uint8Array; | ||
/** Length of the bytes array. */ | ||
readonly length: T; | ||
constructor(raw: Uint8Array, len: T); | ||
private constructor(); | ||
/** Return hex encoding of the sequence. */ | ||
toString(): string; | ||
/** Compare the sequence to another one. */ | ||
isEqualTo(other: Bytes<T>): boolean; | ||
/** Create new [`Bytes<X>`] given a backing buffer and it's length. */ | ||
static fromBlob<X extends number>(v: Uint8Array, len: X): Bytes<X>; | ||
/** Create an empty [`Bytes<X>`] of given length. */ | ||
static zero<X extends number>(len: X): Bytes<X>; | ||
/** Create a [`Bytes<X>`] with all bytes filled with given input number. */ | ||
static fill<X extends number>(len: X, input: number): Bytes<X>; | ||
/** Parse a hex-encoded fixed-length bytes without `0x` prefix. */ | ||
static parseBytesNoPrefix<X extends number>(v: string, len: X): Bytes<X>; | ||
/** Parse a hex-encoded fixed-length bytes with `0x` prefix. */ | ||
static parseBytes<X extends number>(v: string, len: X): Bytes<X>; | ||
} |
162
trie.js
@@ -16,10 +16,8 @@ 'use strict'; | ||
function bytesToHexString(buffer) { | ||
// TODO [ToDr] consider using TextDecoder API? | ||
let s = "0x"; | ||
for (const v of buffer) { | ||
s += v.toString(16).padStart(2, "0"); | ||
} | ||
return s; | ||
} | ||
/** | ||
* A variable-length blob of bytes with a concise text representation. | ||
* | ||
* The structure is used as convenience wrapper for [`Uint8Array`], | ||
* especially if the data is coming from a hex-encoded string. | ||
*/ | ||
class BytesBlob { | ||
@@ -32,9 +30,19 @@ constructor(data) { | ||
} | ||
/** | ||
* Display a hex-encoded version of this byte blob. | ||
*/ | ||
toString() { | ||
return bytesToHexString(this.buffer); | ||
} | ||
static fromBytes(v) { | ||
/** Create a new [`BytesBlob`] from existing [`Uint8Array`]. */ | ||
static fromBlob(v) { | ||
return new BytesBlob(v); | ||
} | ||
/** Create a new [`BytesBlob`] from an array of bytes. */ | ||
static fromNumbers(v) { | ||
check(v.find((x) => (x & 0xff) !== x) === undefined, "BytesBlob.fromNumbers used with non-byte number array."); | ||
const arr = new Uint8Array(v); | ||
return new BytesBlob(arr); | ||
} | ||
/** Parse a hex-encoded bytes blob without `0x` prefix. */ | ||
static parseBlobNoPrefix(v) { | ||
@@ -45,3 +53,2 @@ const len = v.length; | ||
} | ||
// NOTE [ToDr] alloc | ||
const buffer = new ArrayBuffer(len / 2); | ||
@@ -51,12 +58,7 @@ const bytes = new Uint8Array(buffer); | ||
const c = v.substring(i, i + 2); | ||
// TODO [ToDr] [opti] Remove string concat and simply parse each nibble manually | ||
// (switch from 0..f) | ||
const parsed = Number(`0x${c}`); | ||
if (Number.isNaN(parsed)) { | ||
throw new Error(`Invalid characters in hex byte string: ${c}`); | ||
} | ||
bytes[i / 2] = parsed; | ||
bytes[i / 2] = byteFromString(c); | ||
} | ||
return new BytesBlob(bytes); | ||
} | ||
/** Parse a hex-encoded bytes blob with `0x` prefix. */ | ||
static parseBlob(v) { | ||
@@ -69,2 +71,5 @@ if (!v.startsWith("0x")) { | ||
} | ||
/** | ||
* A convenience wrapper for a fix-length sequence of bytes. | ||
*/ | ||
class Bytes { | ||
@@ -76,5 +81,7 @@ constructor(raw, len) { | ||
} | ||
/** Return hex encoding of the sequence. */ | ||
toString() { | ||
return bytesToHexString(this.raw); | ||
} | ||
/** Compare the sequence to another one. */ | ||
isEqualTo(other) { | ||
@@ -91,5 +98,18 @@ if (this.length !== other.length) { | ||
} | ||
/** Create new [`Bytes<X>`] given a backing buffer and it's length. */ | ||
static fromBlob(v, len) { | ||
return new Bytes(v, len); | ||
} | ||
/** Create an empty [`Bytes<X>`] of given length. */ | ||
static zero(len) { | ||
return new Bytes(new Uint8Array(len), len); | ||
} | ||
/** Create a [`Bytes<X>`] with all bytes filled with given input number. */ | ||
static fill(len, input) { | ||
check((input & 0xff) === input, "Input has to be a byte."); | ||
const bytes = Bytes.zero(len); | ||
bytes.raw.fill(input, 0, len); | ||
return bytes; | ||
} | ||
/** Parse a hex-encoded fixed-length bytes without `0x` prefix. */ | ||
static parseBytesNoPrefix(v, len) { | ||
@@ -102,2 +122,3 @@ if (v.length > 2 * len) { | ||
} | ||
/** Parse a hex-encoded fixed-length bytes with `0x` prefix. */ | ||
static parseBytes(v, len) { | ||
@@ -111,3 +132,99 @@ if (v.length > 2 * len + 2) { | ||
} | ||
function byteFromString(s) { | ||
check(s.length === 2, "Two-character string expected"); | ||
const a = numberFromCharCode(s.charCodeAt(0)); | ||
const b = numberFromCharCode(s.charCodeAt(1)); | ||
return (a << 4) | b; | ||
} | ||
const CODE_OF_0 = "0".charCodeAt(0); | ||
const CODE_OF_9 = "9".charCodeAt(0); | ||
const CODE_OF_a = "a".charCodeAt(0); | ||
const CODE_OF_f = "f".charCodeAt(0); | ||
const CODE_OF_A = "A".charCodeAt(0); | ||
const CODE_OF_F = "F".charCodeAt(0); | ||
const VALUE_OF_A = 0xa; | ||
function numberFromCharCode(x) { | ||
if (x >= CODE_OF_0 && x <= CODE_OF_9) { | ||
return x - CODE_OF_0; | ||
} | ||
if (x >= CODE_OF_a && x <= CODE_OF_f) { | ||
return x - CODE_OF_a + VALUE_OF_A; | ||
} | ||
if (x >= CODE_OF_A && x <= CODE_OF_F) { | ||
return x - CODE_OF_A + VALUE_OF_A; | ||
} | ||
throw new Error(`Invalid characters in hex byte string: ${String.fromCharCode(x)}`); | ||
} | ||
function bytesToHexString(buffer) { | ||
const nibbleToString = (n) => { | ||
if (n >= VALUE_OF_A) { | ||
return String.fromCharCode(n + CODE_OF_a - VALUE_OF_A); | ||
} | ||
return String.fromCharCode(n + CODE_OF_0); | ||
}; | ||
let s = "0x"; | ||
for (const v of buffer) { | ||
s += nibbleToString(v >>> 4); | ||
s += nibbleToString(v & 0xf); | ||
} | ||
return s; | ||
} | ||
/** | ||
* A sequence of bits with a packed in-memory representation. | ||
*/ | ||
class BitVec { | ||
/** | ||
* Wrap an existing bytes and treat them as [`BitVec`] | ||
*/ | ||
static fromBlob(data, bitLength) { | ||
return new BitVec(data, bitLength); | ||
} | ||
static fromBytes(data, bitLength) { | ||
return new BitVec(data.raw, bitLength); | ||
} | ||
/** | ||
* Create new [`BitVec`] with all values set to `false`. | ||
*/ | ||
static empty(bitLength) { | ||
const data = new Uint8Array(Math.ceil(bitLength / 8)); | ||
return new BitVec(data, bitLength); | ||
} | ||
constructor(data, bitLength) { | ||
this.data = data; | ||
this.bitLength = bitLength; | ||
check(data.length * 8 >= bitLength, `Not enough bytes in the data array. Need ${data.length * 8} has ${bitLength}.`); | ||
this.byteLength = Math.ceil(bitLength / 8); | ||
} | ||
/** Return a raw in-memory representation of this [`BitVec`]. */ | ||
raw() { | ||
return this.data.subarray(0, this.byteLength); | ||
} | ||
/** | ||
* Set the bit at index `idx` to value `val`. | ||
*/ | ||
setBit(idx, val) { | ||
check(idx < this.bitLength, `Index out of bounds. Need ${idx} has ${this.bitLength}.`); | ||
const byteIndex = Math.floor(idx / 8); | ||
const bitIndexInByte = idx % 8; | ||
const mask = 1 << bitIndexInByte; | ||
if (val) { | ||
this.data[byteIndex] |= mask; | ||
} | ||
else { | ||
this.data[byteIndex] &= ~mask; | ||
} | ||
} | ||
/** | ||
* Return `true` if the bit at index `idx` is set. | ||
*/ | ||
isSet(idx) { | ||
check(idx < this.bitLength, `Index out of bounds. Need ${idx} has ${this.bitLength}.`); | ||
const byteIndex = Math.floor(idx / 8); | ||
const bitIndexInByte = idx % 8; | ||
const mask = 1 << bitIndexInByte; | ||
return (this.data[byteIndex] & mask) > 0; | ||
} | ||
} | ||
/** Regular hash length */ | ||
@@ -205,7 +322,7 @@ const HASH_BYTES = 32; | ||
getLeft() { | ||
return new Bytes(this.node.data.subarray(0, HASH_BYTES), HASH_BYTES); | ||
return Bytes.fromBlob(this.node.data.subarray(0, HASH_BYTES), HASH_BYTES); | ||
} | ||
/** Get the hash of the right sub-trie. */ | ||
getRight() { | ||
return new Bytes(this.node.data.subarray(HASH_BYTES), HASH_BYTES); | ||
return Bytes.fromBlob(this.node.data.subarray(HASH_BYTES), HASH_BYTES); | ||
} | ||
@@ -257,3 +374,3 @@ } | ||
getKey() { | ||
return new Bytes(this.node.data.subarray(1, TRUNCATED_KEY_BYTES + 1), TRUNCATED_KEY_BYTES); | ||
return Bytes.fromBlob(this.node.data.subarray(1, TRUNCATED_KEY_BYTES + 1), TRUNCATED_KEY_BYTES); | ||
} | ||
@@ -278,3 +395,3 @@ /** | ||
const len = this.getValueLength(); | ||
return new BytesBlob(this.node.data.subarray(HASH_BYTES, HASH_BYTES + len)); | ||
return BytesBlob.fromBlob(this.node.data.subarray(HASH_BYTES, HASH_BYTES + len)); | ||
} | ||
@@ -288,3 +405,3 @@ /** | ||
getValueHash() { | ||
return new Bytes(this.node.data.subarray(HASH_BYTES), HASH_BYTES); | ||
return Bytes.fromBlob(this.node.data.subarray(HASH_BYTES), HASH_BYTES); | ||
} | ||
@@ -552,4 +669,5 @@ } | ||
exports.BitVec = BitVec; | ||
exports.Bytes = Bytes; | ||
exports.BytesBlob = BytesBlob; | ||
exports.InMemoryTrie = InMemoryTrie; |
35353
13
957
23
2
3
0
23