tonweb
Version:
TonWeb - JavaScript API for TON blockchain
337 lines (305 loc) • 8.42 kB
JavaScript
const {BN, bytesToHex} = require("../utils");
class BitString {
/**
* @param length {number} length of BitString in bits
*/
constructor(length) {
this.array = Uint8Array.from({length: Math.ceil(length / 8)}, () => 0);
this.cursor = 0;
this.length = length;
}
/**
* @return {number}
*/
getFreeBits() {
return this.length - this.cursor;
}
/**
* @return {number}
*/
getUsedBits() {
return this.cursor;
}
/**
* @return {number}
*/
getUsedBytes() {
return Math.ceil(this.cursor / 8);
}
/**
* @param n {number}
* @return {boolean} bit value at position `n`
*/
get(n) {
return (this.array[(n / 8) | 0] & (1 << (7 - (n % 8)))) > 0;
}
/**
* @private
* @param n {number}
*/
checkRange(n) {
if (n > this.length) {
throw Error("BitString overflow");
}
}
/**
* Set bit value to 1 at position `n`
* @param n {number}
*/
on(n) {
this.checkRange(n);
this.array[(n / 8) | 0] |= 1 << (7 - (n % 8));
}
/**
* Set bit value to 0 at position `n`
* @param n {number}
*/
off(n) {
this.checkRange(n);
this.array[(n / 8) | 0] &= ~(1 << (7 - (n % 8)));
}
/**
* Toggle bit value at position `n`
* @param n {number}
*/
toggle(n) {
this.checkRange(n);
this.array[(n / 8) | 0] ^= 1 << (7 - (n % 8));
}
/**
* forEach every bit
* @param callback {function(boolean): void}
*/
forEach(callback) {
const max = this.cursor;
for (let x = 0; x < max; x++) {
callback(this.get(x));
}
}
/**
* Write bit and increase cursor
* @param b {boolean | number}
*/
writeBit(b) {
if (b && b > 0) {
this.on(this.cursor);
} else {
this.off(this.cursor);
}
this.cursor = this.cursor + 1;
}
/**
* @param ba {Array<boolean | number>}
*/
writeBitArray(ba) {
for (let i = 0; i < ba.length; i++) {
this.writeBit(ba[i]);
}
}
/**
* Write unsigned int
* @param number {number | BN}
* @param bitLength {number} size of uint in bits
*/
writeUint(number, bitLength) {
if (isNaN(number)) throw new Error('writeUint: value is NaN');
if (number === null) throw new Error('writeUint: value is null');
if (number === undefined) throw new Error('writeUint: value is undefined');
if (!bitLength) throw new Error('writeUint: no bitLength');
number = new BN(number);
if (
bitLength == 0 ||
(number.toString(2).length > bitLength)
) {
if (number == 0) return;
throw Error("bitLength is too small for number, got number=" + number + ",bitLength=" + bitLength);
}
const s = number.toString(2, bitLength);
for (let i = 0; i < bitLength; i++) {
this.writeBit(s[i] == 1);
}
}
/**
* Write signed int
* @param number {number | BN}
* @param bitLength {number} size of int in bits
*/
writeInt(number, bitLength) {
if (isNaN(number)) throw new Error('writeInt: value is NaN');
if (number === null) throw new Error('writeInt: value is null');
if (number === undefined) throw new Error('writeInt: value is undefined');
if (!bitLength) throw new Error('writeInt: no bitLength');
number = new BN(number);
if (bitLength == 1) {
if (number == -1) {
this.writeBit(true);
return;
}
if (number == 0) {
this.writeBit(false);
return;
}
throw Error("Bitlength is too small for number");
} else {
if (number.isNeg()) {
this.writeBit(true);
const b = new BN(2);
const nb = b.pow(new BN(bitLength - 1));
this.writeUint(nb.add(number), bitLength - 1);
} else {
this.writeBit(false);
this.writeUint(number, bitLength - 1);
}
}
}
/**
* Write unsigned 8-bit int
* @param ui8 {number}
*/
writeUint8(ui8) {
this.writeUint(ui8, 8);
}
/**
* Write array of unsigned 8-bit ints
* @param ui8 {Uint8Array}
*/
writeBytes(ui8) {
for (let i = 0; i < ui8.length; i++) {
this.writeUint8(ui8[i]);
}
}
/**
* Write UTF-8 string
*
* @param value {string}
*/
writeString(value) {
this.writeBytes(
new TextEncoder().encode(value)
);
}
/**
* @param amount {number | BN} in nanograms
*/
writeGrams(amount) {
if (amount == 0) {
this.writeUint(0, 4);
} else {
amount = new BN(amount);
const l = Math.ceil((amount.toString(16).length) / 2);
this.writeUint(l, 4);
this.writeUint(amount, l * 8);
}
}
/**
* @param amount {number | BN} in nanotons
*/
writeCoins(amount) {
this.writeGrams(amount);
}
//addr_none$00 = MsgAddressExt;
//addr_std$10 anycast:(Maybe Anycast)
// workchain_id:int8 address:uint256 = MsgAddressInt;
/**
* @param address {Address | null}
*/
writeAddress(address) {
if (address == null) {
this.writeUint(0, 2);
} else {
this.writeUint(2, 2);
this.writeUint(0, 1); // TODO split addresses (anycast)
this.writeInt(address.wc, 8);
this.writeBytes(address.hashPart);
}
}
/**
* write another BitString to this BitString
* @param anotherBitString {BitString}
*/
writeBitString(anotherBitString) {
anotherBitString.forEach(x => {
this.writeBit(x);
});
}
clone() {
const result = new BitString(0);
result.array = this.array.slice(0);
result.length = this.length
result.cursor = this.cursor;
return result;
}
/**
* @return {string} hex
*/
toString() {
return this.toHex();
}
/**
* @return {Uint8Array}
*/
getTopUppedArray() {
const ret = this.clone();
let tu = Math.ceil(ret.cursor / 8) * 8 - ret.cursor;
if (tu > 0) {
tu = tu - 1;
ret.writeBit(true);
while (tu > 0) {
tu = tu - 1;
ret.writeBit(false);
}
}
ret.array = ret.array.slice(0, Math.ceil(ret.cursor / 8));
return ret.array;
}
/**
* like Fift
* @return {string}
*/
toHex() {
if (this.cursor % 4 === 0) {
const s = bytesToHex(this.array.slice(0, Math.ceil(this.cursor / 8))).toUpperCase();
if (this.cursor % 8 === 0) {
return s;
} else {
return s.substr(0, s.length - 1);
}
} else {
const temp = this.clone();
temp.writeBit(1);
while (temp.cursor % 4 !== 0) {
temp.writeBit(0);
}
const hex = temp.toHex().toUpperCase();
return hex + '_';
}
}
/**
* set this cell data to match provided topUppedArray
* @param array {Uint8Array}
* @param fullfilledBytes {boolean}
*/
setTopUppedArray(array, fullfilledBytes = true) {
this.length = array.length * 8;
this.array = array;
this.cursor = this.length;
if (fullfilledBytes || !this.length) {
return;
} else {
let foundEndBit = false;
for (let c = 0; c < 7; c++) {
this.cursor -= 1;
if (this.get(this.cursor)) {
foundEndBit = true;
this.off(this.cursor);
break;
}
}
if (!foundEndBit) {
console.log(array, fullfilledBytes);
throw new Error("Incorrect TopUppedArray");
}
}
}
}
module.exports = {BitString};