@ton/core
Version:
Core TypeScript library that implements low level primitives for TON blockchain.
188 lines (162 loc) • 5.53 kB
text/typescript
/**
* Copyright (c) Whales Corp.
* All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { bitsToPaddedBuffer } from "./utils/paddedBits";
import inspectSymbol from 'symbol.inspect';
/**
* BitString is a class that represents a bitstring in a buffer with a specified offset and length
*/
export class BitString {
static readonly EMPTY = new BitString(Buffer.alloc(0), 0, 0);
// NOTE: We want to hide this fields from the user, but
// using private fields would break the compatibility
// between different versions in typescript
private readonly _offset: number;
private readonly _length: number;
private readonly _data: Buffer;
/**
* Checks if supplied object is BitString
* @param src is unknow object
* @returns true if object is BitString and false otherwise
**/
static isBitString(src: unknown): src is BitString {
return src instanceof BitString;
}
/**
* Constructing BitString from a buffer
* @param data data that contains the bitstring data. NOTE: We are expecting this buffer to be NOT modified
* @param offset offset in bits from the start of the buffer
* @param length length of the bitstring in bits
*/
constructor(data: Buffer, offset: number, length: number) {
// Check bounds
if (length < 0) {
throw new Error(`Length ${length} is out of bounds`);
}
this._length = length;
this._data = data;
this._offset = offset;
}
/**
* Returns the length of the bitstring
*/
get length() {
return this._length;
}
/**
* Returns the bit at the specified index
* @param index index of the bit
* @throws Error if index is out of bounds
* @returns true if the bit is set, false otherwise
*/
at(index: number) {
// Check bounds
if (index >= this._length) {
throw new Error(`Index ${index} > ${this._length} is out of bounds`);
}
if (index < 0) {
throw new Error(`Index ${index} < 0 is out of bounds`);
}
// Calculcate offsets
let byteIndex = (this._offset + index) >> 3;
let bitIndex = 7 - ((this._offset + index) % 8); // NOTE: We are using big endian
// Return the bit
return (this._data[byteIndex] & (1 << bitIndex)) !== 0;
}
/**
* Get a subscring of the bitstring
* @param offset
* @param length
* @returns
*/
substring(offset: number, length: number) {
// Check offset
if (offset > this._length) {
throw new Error(`Offset(${offset}) > ${this._length} is out of bounds`);
}
if (offset < 0) {
throw new Error(`Offset(${offset}) < 0 is out of bounds`);
}
// Corner case of empty string
if (length === 0) {
return BitString.EMPTY;
}
if (offset + length > this._length) {
throw new Error(`Offset ${offset} + Length ${length} > ${this._length} is out of bounds`);
}
// Create substring
return new BitString(this._data, this._offset + offset, length);
}
/**
* Try to get a buffer from the bitstring without allocations
* @param offset offset in bits
* @param length length in bits
* @returns buffer if the bitstring is aligned to bytes, null otherwise
*/
subbuffer(offset: number, length: number) {
// Check offset
if (offset > this._length) {
throw new Error(`Offset ${offset} is out of bounds`);
}
if (offset < 0) {
throw new Error(`Offset ${offset} is out of bounds`);
}
if (offset + length > this._length) {
throw new Error(`Offset + Lenght = ${offset + length} is out of bounds`);
}
// Check alignment
if (length % 8 !== 0) {
return null;
}
if ((this._offset + offset) % 8 !== 0) {
return null;
}
// Create substring
let start = ((this._offset + offset) >> 3);
let end = start + (length >> 3);
return this._data.subarray(start, end);
}
/**
* Checks for equality
* @param b other bitstring
* @returns true if the bitstrings are equal, false otherwise
*/
equals(b: BitString) {
if (this._length !== b._length) {
return false;
}
for (let i = 0; i < this._length; i++) {
if (this.at(i) !== b.at(i)) {
return false;
}
}
return true;
}
/**
* Format to canonical string
* @returns formatted bits as a string
*/
toString(): string {
const padded = bitsToPaddedBuffer(this);
if (this._length % 4 === 0) {
const s = padded.subarray(0, Math.ceil(this._length / 8)).toString('hex').toUpperCase();
if (this._length % 8 === 0) {
return s;
} else {
return s.substring(0, s.length - 1);
}
} else {
const hex = padded.toString('hex').toUpperCase();
if (this._length % 8 <= 4) {
return hex.substring(0, hex.length - 1) + '_';
} else {
return hex + '_';
}
}
}
[inspectSymbol] = () => this.toString()
}