@ton/core
Version:
Core TypeScript library that implements low level primitives for TON blockchain.
300 lines (269 loc) • 9.04 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 { BitReader } from "../BitReader";
import { BitString } from "../BitString";
import { Cell } from "../Cell";
import { topologicalSort } from "./utils/topologicalSort";
import { bitsForNumber } from "../../utils/bitsForNumber";
import { BitBuilder } from "../BitBuilder";
import { getBitsDescriptor, getRefsDescriptor } from "./descriptor";
import { bitsToPaddedBuffer } from "../utils/paddedBits";
import { crc32c } from "../../utils/crc32c";
function getHashesCount(levelMask: number) {
return getHashesCountFromMask(levelMask & 7)
}
function getHashesCountFromMask(mask: number) {
let n = 0;
for (let i = 0; i < 3; i++) {
n += (mask & 1);
mask = mask >> 1;
}
return n+1; // 1 repr + up to 3 higher hashes
}
function readCell(reader: BitReader, sizeBytes: number) {
// D1
const d1 = reader.loadUint(8);
const refsCount = d1 % 8;
const exotic = !!(d1 & 8);
// D2
const d2 = reader.loadUint(8);
const dataBytesize = Math.ceil(d2 / 2);
const paddingAdded = !!(d2 % 2);
const levelMask = d1 >> 5;
const hasHashes = (d1 & 16) != 0;
const hash_bytes = 32;
const hashesSize = hasHashes ? getHashesCount(levelMask) * hash_bytes : 0;
const depthSize = hasHashes ? getHashesCount(levelMask) * 2 : 0;
reader.skip(hashesSize * 8);
reader.skip(depthSize * 8);
// Bits
let bits = BitString.EMPTY;
if (dataBytesize > 0) {
if (paddingAdded) {
bits = reader.loadPaddedBits(dataBytesize * 8);
} else {
bits = reader.loadBits(dataBytesize * 8);
}
}
// Refs
let refs: number[] = [];
for (let i = 0; i < refsCount; i++) {
refs.push(reader.loadUint(sizeBytes * 8));
}
// Result
return {
bits,
refs,
exotic
};
}
function calcCellSize(cell: Cell, sizeBytes: number) {
return 2 /* D1+D2 */ + Math.ceil(cell.bits.length / 8) + cell.refs.length * sizeBytes;
}
export function parseBoc(src: Buffer) {
let reader = new BitReader(new BitString(src, 0, src.length * 8));
let magic = reader.loadUint(32);
if (magic === 0x68ff65f3) {
let size = reader.loadUint(8);
let offBytes = reader.loadUint(8);
let cells = reader.loadUint(size * 8);
let roots = reader.loadUint(size * 8); // Must be 1
let absent = reader.loadUint(size * 8);
let totalCellSize = reader.loadUint(offBytes * 8);
let index = reader.loadBuffer(cells * offBytes);
let cellData = reader.loadBuffer(totalCellSize);
return {
size,
offBytes,
cells,
roots,
absent,
totalCellSize,
index,
cellData,
root: [0]
};
} else if (magic === 0xacc3a728) {
let size = reader.loadUint(8);
let offBytes = reader.loadUint(8);
let cells = reader.loadUint(size * 8);
let roots = reader.loadUint(size * 8); // Must be 1
let absent = reader.loadUint(size * 8);
let totalCellSize = reader.loadUint(offBytes * 8);
let index = reader.loadBuffer(cells * offBytes);
let cellData = reader.loadBuffer(totalCellSize);
let crc32 = reader.loadBuffer(4);
if (!crc32c(src.subarray(0, src.length - 4)).equals(crc32)) {
throw Error('Invalid CRC32C');
}
return {
size,
offBytes,
cells,
roots,
absent,
totalCellSize,
index,
cellData,
root: [0]
};
} else if (magic === 0xb5ee9c72) {
let hasIdx = reader.loadUint(1);
let hasCrc32c = reader.loadUint(1);
let hasCacheBits = reader.loadUint(1);
let flags = reader.loadUint(2); // Must be 0
let size = reader.loadUint(3);
let offBytes = reader.loadUint(8);
let cells = reader.loadUint(size * 8);
let roots = reader.loadUint(size * 8);
let absent = reader.loadUint(size * 8);
let totalCellSize = reader.loadUint(offBytes * 8);
let root: number[] = [];
for (let i = 0; i < roots; i++) {
root.push(reader.loadUint(size * 8));
}
let index: Buffer | null = null;
if (hasIdx) {
index = reader.loadBuffer(cells * offBytes);
}
let cellData = reader.loadBuffer(totalCellSize);
if (hasCrc32c) {
let crc32 = reader.loadBuffer(4);
if (!crc32c(src.subarray(0, src.length - 4)).equals(crc32)) {
throw Error('Invalid CRC32C');
}
}
return {
size,
offBytes,
cells,
roots,
absent,
totalCellSize,
index,
cellData,
root
};
} else {
throw Error('Invalid magic');
}
}
export function deserializeBoc(src: Buffer) {
//
// Parse BOC
//
let boc = parseBoc(src);
let reader = new BitReader(new BitString(boc.cellData, 0, boc.cellData.length * 8));
//
// Load cells
//
let cells: { bits: BitString, refs: number[], exotic: boolean, result: Cell | null }[] = [];
for (let i = 0; i < boc.cells; i++) {
let cll = readCell(reader, boc.size);
cells.push({ ...cll, result: null });
}
//
// Build cells
//
for (let i = cells.length - 1; i >= 0; i--) {
if (cells[i].result) {
throw Error('Impossible');
}
let refs: Cell[] = [];
for (let r of cells[i].refs) {
if (!cells[r].result) {
throw Error('Invalid BOC file');
}
refs.push(cells[r].result!);
}
cells[i].result = new Cell({ bits: cells[i].bits, refs, exotic: cells[i].exotic });
}
//
// Load roots
//
let roots: Cell[] = [];
for (let i = 0; i < boc.root.length; i++) {
roots.push(cells[boc.root[i]].result!);
}
//
// Return
//
return roots;
}
function writeCellToBuilder(cell: Cell, refs: number[], sizeBytes: number, to: BitBuilder) {
let d1 = getRefsDescriptor(cell.refs, cell.mask.value, cell.type);
let d2 = getBitsDescriptor(cell.bits);
to.writeUint(d1, 8);
to.writeUint(d2, 8);
to.writeBuffer(bitsToPaddedBuffer(cell.bits));
for (let r of refs) {
to.writeUint(r, sizeBytes * 8);
}
}
export function serializeBoc(root: Cell, opts: { idx: boolean, crc32: boolean }) {
// Sort cells
let allCells = topologicalSort(root);
// Calculcate parameters
let cellsNum = allCells.length;
let has_idx = opts.idx;
let has_crc32c = opts.crc32;
let has_cache_bits = false;
let flags = 0;
let sizeBytes = Math.max(Math.ceil(bitsForNumber(cellsNum, 'uint') / 8), 1);
let totalCellSize: number = 0;
let index: number[] = [];
for (let c of allCells) {
let sz = calcCellSize(c.cell, sizeBytes);
totalCellSize += sz;
index.push(totalCellSize);
}
let offsetBytes = Math.max(Math.ceil(bitsForNumber(totalCellSize, 'uint') / 8), 1);
let totalSize = (
4 + // magic
1 + // flags and s_bytes
1 + // offset_bytes
3 * sizeBytes + // cells_num, roots, complete
offsetBytes + // full_size
1 * sizeBytes + // root_idx
(has_idx ? cellsNum * offsetBytes : 0) +
totalCellSize +
(has_crc32c ? 4 : 0)
) * 8;
// Serialize
let builder = new BitBuilder(totalSize);
builder.writeUint(0xb5ee9c72, 32); // Magic
builder.writeBit(has_idx); // Has index
builder.writeBit(has_crc32c); // Has crc32c
builder.writeBit(has_cache_bits); // Has cache bits
builder.writeUint(flags, 2); // Flags
builder.writeUint(sizeBytes, 3); // Size bytes
builder.writeUint(offsetBytes, 8); // Offset bytes
builder.writeUint(cellsNum, sizeBytes * 8); // Cells num
builder.writeUint(1, sizeBytes * 8); // Roots num
builder.writeUint(0, sizeBytes * 8); // Absent num
builder.writeUint(totalCellSize, offsetBytes * 8); // Total cell size
builder.writeUint(0, sizeBytes * 8); // Root id == 0
if (has_idx) { // Index
for (let i = 0; i < cellsNum; i++) {
builder.writeUint(index[i], offsetBytes * 8);
}
}
for (let i = 0; i < cellsNum; i++) { // Cells
writeCellToBuilder(allCells[i].cell, allCells[i].refs, sizeBytes, builder);
}
if (has_crc32c) {
let crc32 = crc32c(builder.buffer()) // builder.buffer() is fast since it doesn't allocate new memory
builder.writeBuffer(crc32);
}
// Sanity Check
let res = builder.buffer();
if (res.length !== totalSize / 8) {
throw Error('Internal error');
}
return res;
}