@ton/core
Version:
Core TypeScript library that implements low level primitives for TON blockchain.
311 lines (269 loc) • 8.69 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 { Address } from "../address/Address";
import { ExternalAddress } from "../address/ExternalAddress";
import { bitsForNumber } from "../utils/bitsForNumber";
import { Maybe } from "../utils/maybe";
import { BitString } from "./BitString";
/**
* Class for building bit strings
*/
export class BitBuilder {
private _buffer: Buffer;
private _length: number;
constructor(size: number = 1023) {
this._buffer = Buffer.alloc(Math.ceil(size / 8));
this._length = 0;
}
/**
* Current number of bits written
*/
get length() {
return this._length;
}
/**
* Write a single bit
* @param value bit to write, true or positive number for 1, false or zero or negative for 0
*/
writeBit(value: boolean | number) {
// Check overflow
let n = this._length;
if (n > this._buffer.length * 8) {
throw new Error("BitBuilder overflow");
}
// Set bit
if ((typeof value === 'boolean' && value === true) || (typeof value === 'number' && value > 0)) {
this._buffer[(n / 8) | 0] |= 1 << (7 - (n % 8));
}
// Advance
this._length++;
}
/**
* Copy bits from BitString
* @param src source bits
*/
writeBits(src: BitString) {
for (let i = 0; i < src.length; i++) {
this.writeBit(src.at(i));
}
}
/**
* Write bits from buffer
* @param src source buffer
*/
writeBuffer(src: Buffer) {
// Special case for aligned offsets
if (this._length % 8 === 0) {
if (this._length + src.length * 8 > this._buffer.length * 8) {
throw new Error("BitBuilder overflow");
}
src.copy(this._buffer, this._length / 8);
this._length += src.length * 8;
} else {
for (let i = 0; i < src.length; i++) {
this.writeUint(src[i], 8);
}
}
}
/**
* Write uint value
* @param value value as bigint or number
* @param bits number of bits to write
*/
writeUint(value: bigint | number, bits: number) {
if (bits < 0 || !Number.isSafeInteger(bits)) {
throw Error(`invalid bit length. Got ${bits}`);
}
const v = BigInt(value);
if (bits === 0) {
if (v !== 0n) {
throw Error(`value is not zero for ${bits} bits. Got ${value}`);
} else {
return;
}
}
const vBits = (1n << BigInt(bits));
if (v < 0 || v >= vBits) {
throw Error(`bitLength is too small for a value ${value}. Got ${bits}`);
}
if (this._length + bits > this._buffer.length * 8) {
throw new Error("BitBuilder overflow");
}
const tillByte = 8 - (this._length % 8);
if (tillByte > 0) {
const bidx = Math.floor(this._length / 8);
if (bits < tillByte) {
const wb = Number(v);
this._buffer[bidx] |= wb << (tillByte - bits);
this._length += bits;
} else {
const wb = Number(v >> BigInt(bits - tillByte));
this._buffer[bidx] |= wb;
this._length += tillByte;
}
}
bits -= tillByte;
while (bits > 0) {
if (bits >= 8) {
this._buffer[this._length / 8] = Number((v >> BigInt(bits - 8)) & 0xffn);
this._length += 8;
bits -= 8;
} else {
this._buffer[this._length / 8] = Number((v << BigInt(8 - bits)) & 0xffn);
this._length += bits;
bits = 0;
}
}
}
/**
* Write int value
* @param value value as bigint or number
* @param bits number of bits to write
*/
writeInt(value: bigint | number, bits: number) {
let v = BigInt(value);
if (bits < 0 || !Number.isSafeInteger(bits)) {
throw Error(`invalid bit length. Got ${bits}`);
}
// Corner case for zero bits
if (bits === 0) {
if (value !== 0n) {
throw Error(`value is not zero for ${bits} bits. Got ${value}`);
} else {
return;
}
}
// Corner case for one bit
if (bits === 1) {
if (value !== -1n && value !== 0n) {
throw Error(`value is not zero or -1 for ${bits} bits. Got ${value}`);
} else {
this.writeBit(value === -1n);
return;
}
}
// Check input
let vBits = 1n << (BigInt(bits) - 1n);
if (v < -vBits || v >= vBits) {
throw Error(`value is out of range for ${bits} bits. Got ${value}`);
}
// Write sign
if (v < 0) {
this.writeBit(true);
v = vBits + v;
} else {
this.writeBit(false);
}
// Write value
this.writeUint(v, bits - 1);
}
/**
* Wrtie var uint value, used for serializing coins
* @param value value to write as bigint or number
* @param bits header bits to write size
*/
writeVarUint(value: number | bigint, bits: number) {
let v = BigInt(value);
if (bits < 0 || !Number.isSafeInteger(bits)) {
throw Error(`invalid bit length. Got ${bits}`);
}
if (v < 0) {
throw Error(`value is negative. Got ${value}`);
}
// Corner case for zero
if (v === 0n) {
// Write zero size
this.writeUint(0, bits);
return;
}
// Calculate size
const sizeBytes = Math.ceil((v.toString(2).length) / 8); // Fastest way in most environments
const sizeBits = sizeBytes * 8;
// Write size
this.writeUint(sizeBytes, bits);
// Write number
this.writeUint(v, sizeBits);
}
/**
* Wrtie var int value, used for serializing coins
* @param value value to write as bigint or number
* @param bits header bits to write size
*/
writeVarInt(value: number | bigint, bits: number) {
let v = BigInt(value);
if (bits < 0 || !Number.isSafeInteger(bits)) {
throw Error(`invalid bit length. Got ${bits}`);
}
// Corner case for zero
if (v === 0n) {
// Write zero size
this.writeUint(0, bits);
return;
}
// Calculate size
let v2 = v > 0 ? v : -v;
const sizeBytes = 1 + Math.ceil((v2.toString(2).length) / 8); // Fastest way in most environments
const sizeBits = sizeBytes * 8;
// Write size
this.writeUint(sizeBytes, bits);
// Write number
this.writeInt(v, sizeBits);
}
/**
* Write coins in var uint format
* @param amount amount to write
*/
writeCoins(amount: number | bigint) {
this.writeVarUint(amount, 4);
}
/**
* Write address
* @param address write address or address external
*/
writeAddress(address: Maybe<Address | ExternalAddress>) {
// Is empty address
if (address === null || address === undefined) {
this.writeUint(0, 2); // Empty address
return;
}
// Is Internal Address
if (Address.isAddress(address)) {
this.writeUint(2, 2); // Internal address
this.writeUint(0, 1); // No anycast
this.writeInt(address.workChain, 8);
this.writeBuffer(address.hash);
return;
}
// Is External Address
if (ExternalAddress.isAddress(address)) {
this.writeUint(1, 2); // External address
this.writeUint(address.bits, 9);
this.writeUint(address.value, address.bits);
return;
}
// Invalid address
throw Error(`Invalid address. Got ${address}`);
}
/**
* Build BitString
* @returns result bit string
*/
build() {
return new BitString(this._buffer, 0, this._length);
}
/**
* Build into Buffer
* @returns result buffer
*/
buffer() {
if (this._length % 8 !== 0) {
throw new Error("BitBuilder buffer is not byte aligned");
}
return this._buffer.subarray(0, this._length / 8);
}
}