UNPKG

@bare-ts/lib

Version:

TypeScript library for BARE, a compact and simple binary-serialization format

122 lines (121 loc) 3.93 kB
//! Copyright (c) 2022 Victorien Elvinger //! Licensed under the MIT License (https://mit-license.org/) import { assert, DEV } from "../util/assert.js"; import { TOO_LARGE_BUFFER } from "../util/constants.js"; import { isU32 } from "../util/validator.js"; import { BareError } from "./bare-error.js"; /** * @invariant `bytes.buffer === view.buffer` * @invariant `bytes.byteOffset === view.byteOffset` * @invariant `bytes.byteLength === view.byteLength` * @invariant `0 <= offset <= bytes.byteLength` * @invariant `bytes.byteLength <= config.maxBufferLength` * * ```txt * | {bytes,view}.buffer | * | bytes | * | view | * |<------ offset ------>| * |<----------- config.maxBufferLength ------------>| * ``` * * @sealed */ export class ByteCursor { bytes; config; /** * Read and write Offset in {@link view} and {@link bytes} */ offset = 0; view; /** * @throws {BareError} Buffer exceeds `config.maxBufferLength` */ constructor(bytes, config) { if (bytes.length > config.maxBufferLength) { throw new BareError(0, TOO_LARGE_BUFFER); } this.bytes = bytes; this.config = config; this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.length); } } /** * Check that `min` number of bytes are available. * * @throws {BareError} bytes are missing. */ export function check(bc, min) { // We try to keep this function as small as possible to allow inling. if (DEV) { assert(isU32(min)); } if (bc.offset + min > bc.bytes.length) { throw new BareError(bc.offset, "missing bytes"); } } /** * Reserve `min` number of bytes. * * @throws {BareError} Buffer exceeds `config.maxBufferLength`. */ export function reserve(bc, min) { // We try to keep this function as small as possible to allow inling. if (DEV) { assert(isU32(min)); } const minLen = (bc.offset + min) | 0; if (minLen > bc.bytes.length) { grow(bc, minLen); } } /** * Grow the underlying buffer of `bc` such that its length is * greater or equal to `minLen`. * * @throws {BareError} Buffer exceeds `config.maxBufferLength`. */ function grow(bc, minLen) { if (minLen > bc.config.maxBufferLength) { throw new BareError(0, TOO_LARGE_BUFFER); } // // | bytes,view}.buffer | // | bytes | // | view | // |<-- offset -->|<-- min -->| // |<-------- minLen -------->| // | new view | // | new bytes | // | new {bytes,view}.buffer | // |<------------- config.maxBufferLength -------------->| // const buffer = bc.bytes.buffer; let newBytes; if (isEs2024ArrayBufferLike(buffer) && // Make sure that the view covers the end of the buffer. // If it is not the case, this indicates that the user don't want // to override the trailing bytes. bc.bytes.byteOffset + bc.bytes.byteLength === buffer.byteLength && bc.bytes.byteLength + minLen <= buffer.maxByteLength) { const newLen = Math.min(minLen << 1, bc.config.maxBufferLength, buffer.maxByteLength); if (buffer instanceof ArrayBuffer) { buffer.resize(newLen); } else { buffer.grow(newLen); } newBytes = new Uint8Array(buffer, bc.bytes.byteOffset, newLen); } else { const newLen = Math.min(minLen << 1, bc.config.maxBufferLength); newBytes = new Uint8Array(newLen); newBytes.set(bc.bytes); } bc.bytes = newBytes; bc.view = new DataView(newBytes.buffer); } function isEs2024ArrayBufferLike(buffer) { return "maxByteLength" in buffer; }