UNPKG

@aptos-labs/ts-sdk

Version:
249 lines (221 loc) 8 kB
// Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 /* eslint-disable no-bitwise */ import { MAX_U32_NUMBER } from "./consts"; import { Uint8, Uint16, Uint32, Uint64, Uint128, Uint256 } from "../types"; /** * This interface exists to define Deserializable<T> inputs for functions that * deserialize a byte buffer into a type T. * It is not intended to be implemented or extended, because Typescript has no support * for static methods in interfaces. */ export interface Deserializable<T> { deserialize(deserializer: Deserializer): T; } export class Deserializer { private buffer: ArrayBuffer; private offset: number; constructor(data: Uint8Array) { // copies data to prevent outside mutation of buffer. this.buffer = new ArrayBuffer(data.length); new Uint8Array(this.buffer).set(data, 0); this.offset = 0; } private read(length: number): ArrayBuffer { if (this.offset + length > this.buffer.byteLength) { throw new Error("Reached to the end of buffer"); } const bytes = this.buffer.slice(this.offset, this.offset + length); this.offset += length; return bytes; } /** * Deserializes a string. UTF8 string is supported. Reads the string's bytes length "l" first, * and then reads "l" bytes of content. Decodes the byte array into a string. * * BCS layout for "string": string_length | string_content * where string_length is a u32 integer encoded as a uleb128 integer, equal to the number of bytes in string_content. * * @example * ```ts * const deserializer = new Deserializer(new Uint8Array([8, 49, 50, 51, 52, 97, 98, 99, 100])); * assert(deserializer.deserializeStr() === "1234abcd"); * ``` */ deserializeStr(): string { const value = this.deserializeBytes(); const textDecoder = new TextDecoder(); return textDecoder.decode(value); } /** * Deserializes an array of bytes. * * BCS layout for "bytes": bytes_length | bytes * where bytes_length is a u32 integer encoded as a uleb128 integer, equal to the length of the bytes array. */ deserializeBytes(): Uint8Array { const len = this.deserializeUleb128AsU32(); return new Uint8Array(this.read(len)); } /** * Deserializes an array of bytes. The number of bytes to read is already known. * */ deserializeFixedBytes(len: number): Uint8Array { return new Uint8Array(this.read(len)); } /** * Deserializes a boolean value. * * BCS layout for "boolean": One byte. "0x01" for true and "0x00" for false. */ deserializeBool(): boolean { const bool = new Uint8Array(this.read(1))[0]; if (bool !== 1 && bool !== 0) { throw new Error("Invalid boolean value"); } return bool === 1; } /** * Deserializes a uint8 number. * * BCS layout for "uint8": One byte. Binary format in little-endian representation. */ deserializeU8(): Uint8 { return new DataView(this.read(1)).getUint8(0); } /** * Deserializes a uint16 number. * * BCS layout for "uint16": Two bytes. Binary format in little-endian representation. * @example * ```ts * const deserializer = new Deserializer(new Uint8Array([0x34, 0x12])); * assert(deserializer.deserializeU16() === 4660); * ``` */ deserializeU16(): Uint16 { return new DataView(this.read(2)).getUint16(0, true); } /** * Deserializes a uint32 number. * * BCS layout for "uint32": Four bytes. Binary format in little-endian representation. * @example * ```ts * const deserializer = new Deserializer(new Uint8Array([0x78, 0x56, 0x34, 0x12])); * assert(deserializer.deserializeU32() === 305419896); * ``` */ deserializeU32(): Uint32 { return new DataView(this.read(4)).getUint32(0, true); } /** * Deserializes a uint64 number. * * BCS layout for "uint64": Eight bytes. Binary format in little-endian representation. * @example * ```ts * const deserializer = new Deserializer(new Uint8Array([0x00, 0xEF, 0xCD, 0xAB, 0x78, 0x56, 0x34, 0x12])); * assert(deserializer.deserializeU64() === 1311768467750121216); * ``` */ deserializeU64(): Uint64 { const low = this.deserializeU32(); const high = this.deserializeU32(); // combine the two 32-bit values and return (little endian) return BigInt((BigInt(high) << BigInt(32)) | BigInt(low)); } /** * Deserializes a uint128 number. * * BCS layout for "uint128": Sixteen bytes. Binary format in little-endian representation. */ deserializeU128(): Uint128 { const low = this.deserializeU64(); const high = this.deserializeU64(); // combine the two 64-bit values and return (little endian) return BigInt((high << BigInt(64)) | low); } /** * Deserializes a uint256 number. * * BCS layout for "uint256": Thirty-two bytes. Binary format in little-endian representation. */ deserializeU256(): Uint256 { const low = this.deserializeU128(); const high = this.deserializeU128(); // combine the two 128-bit values and return (little endian) return BigInt((high << BigInt(128)) | low); } /** * Deserializes a uleb128 encoded uint32 number. * * BCS use uleb128 encoding in two cases: (1) lengths of variable-length sequences and (2) tags of enum values */ deserializeUleb128AsU32(): Uint32 { let value: bigint = BigInt(0); let shift = 0; while (value < MAX_U32_NUMBER) { const byte = this.deserializeU8(); value |= BigInt(byte & 0x7f) << BigInt(shift); if ((byte & 0x80) === 0) { break; } shift += 7; } if (value > MAX_U32_NUMBER) { throw new Error("Overflow while parsing uleb128-encoded uint32 value"); } return Number(value); } /** * Helper function that primarily exists to support alternative syntax for deserialization. * That is, if we have a `const deserializer: new Deserializer(...)`, instead of having to use * `MyClass.deserialize(deserializer)`, we can call `deserializer.deserialize(MyClass)`. * * @example const deserializer = new Deserializer(new Uint8Array([1, 2, 3])); * const value = deserializer.deserialize(MyClass); // where MyClass has a `deserialize` function * // value is now an instance of MyClass * // equivalent to `const value = MyClass.deserialize(deserializer)` * @param cls The BCS-deserializable class to deserialize the buffered bytes into. * * @returns the deserialized value of class type T */ deserialize<T>(cls: Deserializable<T>): T { // NOTE: `deserialize` in `cls.deserialize(this)` here is a static method defined in `cls`, // It is separate from the `deserialize` instance method defined here in Deserializer. return cls.deserialize(this); } /** * Deserializes an array of BCS Deserializable values given an existing Deserializer * instance with a loaded byte buffer. * * @param cls The BCS-deserializable class to deserialize the buffered bytes into. * @example * // serialize a vector of addresses * const addresses = new Array<AccountAddress>( * AccountAddress.from("0x1"), * AccountAddress.from("0x2"), * AccountAddress.from("0xa"), * AccountAddress.from("0xb"), * ); * const serializer = new Serializer(); * serializer.serializeVector(addresses); * const serializedBytes = serializer.toUint8Array(); * * // deserialize the bytes into an array of addresses * const deserializer = new Deserializer(serializedBytes); * const deserializedAddresses = deserializer.deserializeVector(AccountAddress); * // deserializedAddresses is now an array of AccountAddress instances * @returns an array of deserialized values of type T */ deserializeVector<T>(cls: Deserializable<T>): Array<T> { const length = this.deserializeUleb128AsU32(); const vector = new Array<T>(); for (let i = 0; i < length; i += 1) { vector.push(this.deserialize(cls)); } return vector; } }