@chainsafe/ssz
Version:
Simple Serialize
216 lines • 9.18 kB
JavaScript
import { getNodesAtDepth, subtreeFillToContents } from "@chainsafe/persistent-merkle-tree";
import { addLengthNode, assertValidArrayLength } from "./arrayBasic.js";
export function minSizeArrayComposite(elementType, minCount) {
// Variable Length
if (elementType.fixedSize === null) {
return minCount * (4 + elementType.minSize);
}
// Fixed length
return minCount * elementType.minSize;
}
export function maxSizeArrayComposite(elementType, maxCount) {
// Variable Length
if (elementType.fixedSize === null) {
return maxCount * (4 + elementType.maxSize);
}
// Fixed length
return maxCount * elementType.maxSize;
}
export function value_serializedSizeArrayComposite(elementType, length, value) {
// Variable Length
if (elementType.fixedSize === null) {
let size = 0;
for (let i = 0; i < length; i++) {
size += 4 + elementType.value_serializedSize(value[i]);
}
return size;
}
// Fixed length
return length * elementType.fixedSize;
}
/**
* @param length In List length = value.length, Vector length = fixed value
*/
export function value_serializeToBytesArrayComposite(elementType, length, output, offset, value) {
// Variable length
if (elementType.fixedSize === null) {
let variableIndex = offset + length * 4;
for (let i = 0; i < length; i++) {
// write offset
output.dataView.setUint32(offset + i * 4, variableIndex - offset, true);
// write serialized element to variable section
variableIndex = elementType.value_serializeToBytes(output, variableIndex, value[i]);
}
return variableIndex;
}
// Fixed length
for (let i = 0; i < length; i++) {
elementType.value_serializeToBytes(output, offset + i * elementType.fixedSize, value[i]);
}
return offset + length * elementType.fixedSize;
}
export function value_deserializeFromBytesArrayComposite(elementType, data, start, end, arrayProps) {
const offsets = readOffsetsArrayComposite(elementType.fixedSize, data.dataView, start, end, arrayProps);
const length = offsets.length; // Capture length before pushing end offset
const values = new Array(length);
// offests include the last element end
for (let i = 0; i < length; i++) {
// The offsets are relative to the start
const startEl = start + offsets[i];
const endEl = i === length - 1 ? end : start + offsets[i + 1];
values[i] = elementType.value_deserializeFromBytes(data, startEl, endEl);
}
return values;
}
/**
* @param length In List length = value.length, Vector length = fixed value
*/
export function tree_serializedSizeArrayComposite(elementType, length, depth, node) {
// Variable Length
if (elementType.fixedSize === null) {
const nodes = getNodesAtDepth(node, depth, 0, length);
let size = 0;
for (let i = 0; i < nodes.length; i++) {
size += 4 + elementType.tree_serializedSize(nodes[i]);
}
return size;
}
// Fixed length
return length * elementType.fixedSize;
}
/**
* @param length In List length = value.length, Vector length = fixed value
*/
export function tree_serializeToBytesArrayComposite(elementType, length, depth, node, output, offset, cachedNodes = null) {
const nodes = cachedNodes ?? getNodesAtDepth(node, depth, 0, length);
// Variable Length
// Indices contain offsets, which are indices deeper in the byte array
if (elementType.fixedSize === null) {
let variableIndex = offset + length * 4;
const { dataView } = output;
for (let i = 0; i < nodes.length; i++) {
// write offset
dataView.setUint32(offset + i * 4, variableIndex - offset, true);
// write serialized element to variable section
variableIndex = elementType.tree_serializeToBytes(output, variableIndex, nodes[i]);
}
return variableIndex;
}
// Fixed length
for (let i = 0; i < nodes.length; i++) {
offset = elementType.tree_serializeToBytes(output, offset, nodes[i]);
}
return offset;
}
export function tree_deserializeFromBytesArrayComposite(elementType, chunkDepth, data, start, end, arrayProps) {
const offsets = readOffsetsArrayComposite(elementType.fixedSize, data.dataView, start, end, arrayProps);
const length = offsets.length; // Capture length before pushing end offset
const nodes = new Array(length);
// offests include the last element end
for (let i = 0; i < length; i++) {
// The offsets are relative to the start
const startEl = start + offsets[i];
const endEl = i === length - 1 ? end : start + offsets[i + 1];
nodes[i] = elementType.tree_deserializeFromBytes(data, startEl, endEl);
}
// Abstract converting data to LeafNode to allow for custom data representation, such as the hashObject
const chunksNode = subtreeFillToContents(nodes, chunkDepth);
// TODO: Add LeafNode.fromUint()
if (arrayProps.isList) {
return addLengthNode(chunksNode, length);
}
return chunksNode;
}
export function value_getBlocksBytesArrayComposite(elementType, length, value, blocksBuffer) {
const blockBytesLen = Math.ceil(length / 2) * 64;
if (blockBytesLen > blocksBuffer.length) {
throw new Error(`blocksBuffer is too small: ${blocksBuffer.length} < ${blockBytesLen}`);
}
const blocksBytes = blocksBuffer.subarray(0, blockBytesLen);
for (let i = 0; i < length; i++) {
elementType.hashTreeRootInto(value[i], blocksBytes, i * 32);
}
const isOddChunk = length % 2 === 1;
if (isOddChunk) {
// similar to append zeroHash(0)
blocksBytes.subarray(length * 32, blockBytesLen).fill(0);
}
return blocksBytes;
}
function readOffsetsArrayComposite(elementFixedSize, data, start, end, arrayProps) {
const size = end - start;
let offsets;
// Variable Length
// Indices contain offsets, which are indices deeper in the byte array
if (elementFixedSize === null) {
offsets = readVariableOffsetsArrayComposite(data, start, size);
}
// Fixed length
else {
// There's no valid CompositeType with fixed size 0, it's un-rechable code. But prevents diving by zero
/* istanbul ignore if */
if (elementFixedSize === 0) {
throw Error("element fixed length is 0");
}
if (size % elementFixedSize !== 0) {
throw Error(`size ${size} is not multiple of element fixedSize ${elementFixedSize}`);
}
const length = size / elementFixedSize;
offsets = new Uint32Array(length);
for (let i = 0; i < length; i++) {
offsets[i] = i * elementFixedSize;
}
}
// Vector + List length validation
assertValidArrayLength(offsets.length, arrayProps);
return offsets;
}
/**
* Reads the values of contiguous variable offsets. Provided buffer includes offsets that point to position
* within `size`. This function also validates that all offsets are in range.
*/
function readVariableOffsetsArrayComposite(dataView, start, size) {
if (size === 0) {
return new Uint32Array(0);
}
// all elements are variable-sized
// indices contain offsets, which are indices deeper in the byte array
// The serialized data will start with offsets of all the serialized objects (BYTES_PER_LENGTH_OFFSET bytes each)
const firstOffset = dataView.getUint32(start, true);
// Using the first offset, we can compute the length of the list (divide by BYTES_PER_LENGTH_OFFSET), as it gives
// us the total number of bytes in the offset data
const offsetDataLength = firstOffset;
if (firstOffset === 0) {
throw Error("First offset must be > 0");
}
if (offsetDataLength % 4 !== 0) {
throw Error("Offset data length not multiple of 4");
}
const offsetCount = offsetDataLength / 4;
const offsets = new Uint32Array(offsetCount);
offsets[0] = firstOffset;
// ArrayComposite has a contiguous section of offsets, then the data
//
// [offset 1] [offset 2] [data 1 ..........] [data 2 ..]
// 0x 08000000 0e000000 010002000300 01000200
//
// Ensure that:
// - Offsets point to regions of > 0 bytes, i.e. are increasing
// - Offsets don't point to bytes outside of the array's size
//
// In the example above the first offset is 8, so 8 / 4 = 2 offsets.
// Then, read the rest of offsets to get offsets = [8, 14]
for (let offsetIdx = 1; offsetIdx < offsetCount; offsetIdx++) {
const offset = dataView.getUint32(start + offsetIdx * 4, true);
offsets[offsetIdx] = offset;
// Offsets must point to data within the Array bytes section
if (offset > size) {
throw new Error(`Offset out of bounds ${offset} > ${size}`);
}
if (offset < offsets[offsetIdx - 1]) {
throw new Error(`Offsets must be increasing ${offset} < ${offsets[offsetIdx - 1]}`);
}
}
return offsets;
}
//# sourceMappingURL=arrayComposite.js.map