import { decode, encode } from './text'; const defaultByteLength = 1024 * 8; const hostBigEndian = (() => { const array = new Uint8Array(4); const view = new Uint32Array(array.buffer); return !((view[0] = 1) & array[0]); })(); const typedArrays = { int8: globalThis.Int8Array, uint8: globalThis.Uint8Array, int16: globalThis.Int16Array, uint16: globalThis.Uint16Array, int32: globalThis.Int32Array, uint32: globalThis.Uint32Array, uint64: globalThis.BigUint64Array, int64: globalThis.BigInt64Array, float32: globalThis.Float32Array, float64: globalThis.Float64Array, }; export class IOBuffer { /** * Reference to the internal ArrayBuffer object. */ buffer; /** * Byte length of the internal ArrayBuffer. */ byteLength; /** * Byte offset of the internal ArrayBuffer. */ byteOffset; /** * Byte length of the internal ArrayBuffer. */ length; /** * The current offset of the buffer's pointer. */ offset; lastWrittenByte; littleEndian; _data; _mark; _marks; /** * Create a new IOBuffer. * @param data - The data to construct the IOBuffer with. * If data is a number, it will be the new buffer's length
* If data is `undefined`, the buffer will be initialized with a default length of 8Kb
* If data is an ArrayBuffer, SharedArrayBuffer, an ArrayBufferView (Typed Array), an IOBuffer instance, * or a Node.js Buffer, a view will be created over the underlying ArrayBuffer. * @param options - An object for the options. * @returns A new IOBuffer instance. */ constructor(data = defaultByteLength, options = {}) { let dataIsGiven = false; if (typeof data === 'number') { data = new ArrayBuffer(data); } else { dataIsGiven = true; this.lastWrittenByte = data.byteLength; } const offset = options.offset ? options.offset >>> 0 : 0; const byteLength = data.byteLength - offset; let dvOffset = offset; if (ArrayBuffer.isView(data) || data instanceof IOBuffer) { if (data.byteLength !== data.buffer.byteLength) { dvOffset = data.byteOffset + offset; } data = data.buffer; } if (dataIsGiven) { this.lastWrittenByte = byteLength; } else { this.lastWrittenByte = 0; } this.buffer = data; this.length = byteLength; this.byteLength = byteLength; this.byteOffset = dvOffset; this.offset = 0; this.littleEndian = true; this._data = new DataView(this.buffer, dvOffset, byteLength); this._mark = 0; this._marks = []; } /** * Checks if the memory allocated to the buffer is sufficient to store more * bytes after the offset. * @param byteLength - The needed memory in bytes. * @returns `true` if there is sufficient space and `false` otherwise. */ available(byteLength = 1) { return this.offset + byteLength <= this.length; } /** * Check if little-endian mode is used for reading and writing multi-byte * values. * @returns `true` if little-endian mode is used, `false` otherwise. */ isLittleEndian() { return this.littleEndian; } /** * Set little-endian mode for reading and writing multi-byte values. * @returns This. */ setLittleEndian() { this.littleEndian = true; return this; } /** * Check if big-endian mode is used for reading and writing multi-byte values. * @returns `true` if big-endian mode is used, `false` otherwise. */ isBigEndian() { return !this.littleEndian; } /** * Switches to big-endian mode for reading and writing multi-byte values. * @returns This. */ setBigEndian() { this.littleEndian = false; return this; } /** * Move the pointer n bytes forward. * @param n - Number of bytes to skip. * @returns This. */ skip(n = 1) { this.offset += n; return this; } /** * Move the pointer n bytes backward. * @param n - Number of bytes to move back. * @returns This. */ back(n = 1) { this.offset -= n; return this; } /** * Move the pointer to the given offset. * @param offset - The offset to move to. * @returns This. */ seek(offset) { this.offset = offset; return this; } /** * Store the current pointer offset. * @see {@link IOBuffer#reset} * @returns This. */ mark() { this._mark = this.offset; return this; } /** * Move the pointer back to the last pointer offset set by mark. * @see {@link IOBuffer#mark} * @returns This. */ reset() { this.offset = this._mark; return this; } /** * Push the current pointer offset to the mark stack. * @see {@link IOBuffer#popMark} * @returns This. */ pushMark() { this._marks.push(this.offset); return this; } /** * Pop the last pointer offset from the mark stack, and set the current * pointer offset to the popped value. * @see {@link IOBuffer#pushMark} * @returns This. */ popMark() { const offset = this._marks.pop(); if (offset === undefined) { throw new Error('Mark stack empty'); } this.seek(offset); return this; } /** * Move the pointer offset back to 0. * @returns This. */ rewind() { this.offset = 0; return this; } /** * Make sure the buffer has sufficient memory to write a given byteLength at * the current pointer offset. * If the buffer's memory is insufficient, this method will create a new * buffer (a copy) with a length that is twice (byteLength + current offset). * @param byteLength - The needed memory in bytes. * @returns This. */ ensureAvailable(byteLength = 1) { if (!this.available(byteLength)) { const lengthNeeded = this.offset + byteLength; const newLength = lengthNeeded * 2; const newArray = new Uint8Array(newLength); newArray.set(new Uint8Array(this.buffer)); this.buffer = newArray.buffer; this.length = newLength; this.byteLength = newLength; this._data = new DataView(this.buffer); } return this; } /** * Read a byte and return false if the byte's value is 0, or true otherwise. * Moves pointer forward by one byte. * @returns The read boolean. */ readBoolean() { return this.readUint8() !== 0; } /** * Read a signed 8-bit integer and move pointer forward by 1 byte. * @returns The read byte. */ readInt8() { return this._data.getInt8(this.offset++); } /** * Read an unsigned 8-bit integer and move pointer forward by 1 byte. * @returns The read byte. */ readUint8() { return this._data.getUint8(this.offset++); } /** * Alias for {@link IOBuffer#readUint8}. * @returns The read byte. */ readByte() { return this.readUint8(); } /** * Read `n` bytes and move pointer forward by `n` bytes. * @param n - Number of bytes to read. * @returns The read bytes. */ readBytes(n = 1) { return this.readArray(n, 'uint8'); } /** * Creates an array of corresponding to the type `type` and size `size`. * For example type `uint8` will create a `Uint8Array`. * @param size - size of the resulting array * @param type - number type of elements to read * @returns The read array. */ readArray(size, type) { const bytes = typedArrays[type].BYTES_PER_ELEMENT * size; const offset = this.byteOffset + this.offset; const slice = this.buffer.slice(offset, offset + bytes); if (this.littleEndian === hostBigEndian && type !== 'uint8' && type !== 'int8') { const slice = new Uint8Array(this.buffer.slice(offset, offset + bytes)); slice.reverse(); const returnArray = new typedArrays[type](slice.buffer); this.offset += bytes; returnArray.reverse(); return returnArray; } const returnArray = new typedArrays[type](slice); this.offset += bytes; return returnArray; } /** * Read a 16-bit signed integer and move pointer forward by 2 bytes. * @returns The read value. */ readInt16() { const value = this._data.getInt16(this.offset, this.littleEndian); this.offset += 2; return value; } /** * Read a 16-bit unsigned integer and move pointer forward by 2 bytes. * @returns The read value. */ readUint16() { const value = this._data.getUint16(this.offset, this.littleEndian); this.offset += 2; return value; } /** * Read a 32-bit signed integer and move pointer forward by 4 bytes. * @returns The read value. */ readInt32() { const value = this._data.getInt32(this.offset, this.littleEndian); this.offset += 4; return value; } /** * Read a 32-bit unsigned integer and move pointer forward by 4 bytes. * @returns The read value. */ readUint32() { const value = this._data.getUint32(this.offset, this.littleEndian); this.offset += 4; return value; } /** * Read a 32-bit floating number and move pointer forward by 4 bytes. * @returns The read value. */ readFloat32() { const value = this._data.getFloat32(this.offset, this.littleEndian); this.offset += 4; return value; } /** * Read a 64-bit floating number and move pointer forward by 8 bytes. * @returns The read value. */ readFloat64() { const value = this._data.getFloat64(this.offset, this.littleEndian); this.offset += 8; return value; } /** * Read a 64-bit signed integer number and move pointer forward by 8 bytes. * @returns The read value. */ readBigInt64() { const value = this._data.getBigInt64(this.offset, this.littleEndian); this.offset += 8; return value; } /** * Read a 64-bit unsigned integer number and move pointer forward by 8 bytes. * @returns The read value. */ readBigUint64() { const value = this._data.getBigUint64(this.offset, this.littleEndian); this.offset += 8; return value; } /** * Read a 1-byte ASCII character and move pointer forward by 1 byte. * @returns The read character. */ readChar() { // eslint-disable-next-line unicorn/prefer-code-point return String.fromCharCode(this.readInt8()); } /** * Read `n` 1-byte ASCII characters and move pointer forward by `n` bytes. * @param n - Number of characters to read. * @returns The read characters. */ readChars(n = 1) { let result = ''; for (let i = 0; i < n; i++) { result += this.readChar(); } return result; } /** * Read the next `n` bytes, return a UTF-8 decoded string and move pointer * forward by `n` bytes. * @param n - Number of bytes to read. * @returns The decoded string. */ readUtf8(n = 1) { return decode(this.readBytes(n)); } /** * Read the next `n` bytes, return a string decoded with `encoding` and move pointer * forward by `n` bytes. * If no encoding is passed, the function is equivalent to @see {@link IOBuffer#readUtf8} * @param n - Number of bytes to read. * @param encoding - The encoding to use. Default is 'utf8'. * @returns The decoded string. */ decodeText(n = 1, encoding = 'utf8') { return decode(this.readBytes(n), encoding); } /** * Write 0xff if the passed value is truthy, 0x00 otherwise and move pointer * forward by 1 byte. * @param value - The value to write. * @returns This. */ writeBoolean(value) { this.writeUint8(value ? 0xff : 0x00); return this; } /** * Write `value` as an 8-bit signed integer and move pointer forward by 1 byte. * @param value - The value to write. * @returns This. */ writeInt8(value) { this.ensureAvailable(1); this._data.setInt8(this.offset++, value); this._updateLastWrittenByte(); return this; } /** * Write `value` as an 8-bit unsigned integer and move pointer forward by 1 * byte. * @param value - The value to write. * @returns This. */ writeUint8(value) { this.ensureAvailable(1); this._data.setUint8(this.offset++, value); this._updateLastWrittenByte(); return this; } /** * An alias for {@link IOBuffer#writeUint8}. * @param value - The value to write. * @returns This. */ writeByte(value) { return this.writeUint8(value); } /** * Write all elements of `bytes` as uint8 values and move pointer forward by * `bytes.length` bytes. * @param bytes - The array of bytes to write. * @returns This. */ writeBytes(bytes) { this.ensureAvailable(bytes.length); // eslint-disable-next-line @typescript-eslint/prefer-for-of for (let i = 0; i < bytes.length; i++) { this._data.setUint8(this.offset++, bytes[i]); } this._updateLastWrittenByte(); return this; } /** * Write `value` as a 16-bit signed integer and move pointer forward by 2 * bytes. * @param value - The value to write. * @returns This. */ writeInt16(value) { this.ensureAvailable(2); this._data.setInt16(this.offset, value, this.littleEndian); this.offset += 2; this._updateLastWrittenByte(); return this; } /** * Write `value` as a 16-bit unsigned integer and move pointer forward by 2 * bytes. * @param value - The value to write. * @returns This. */ writeUint16(value) { this.ensureAvailable(2); this._data.setUint16(this.offset, value, this.littleEndian); this.offset += 2; this._updateLastWrittenByte(); return this; } /** * Write `value` as a 32-bit signed integer and move pointer forward by 4 * bytes. * @param value - The value to write. * @returns This. */ writeInt32(value) { this.ensureAvailable(4); this._data.setInt32(this.offset, value, this.littleEndian); this.offset += 4; this._updateLastWrittenByte(); return this; } /** * Write `value` as a 32-bit unsigned integer and move pointer forward by 4 * bytes. * @param value - The value to write. * @returns This. */ writeUint32(value) { this.ensureAvailable(4); this._data.setUint32(this.offset, value, this.littleEndian); this.offset += 4; this._updateLastWrittenByte(); return this; } /** * Write `value` as a 32-bit floating number and move pointer forward by 4 * bytes. * @param value - The value to write. * @returns This. */ writeFloat32(value) { this.ensureAvailable(4); this._data.setFloat32(this.offset, value, this.littleEndian); this.offset += 4; this._updateLastWrittenByte(); return this; } /** * Write `value` as a 64-bit floating number and move pointer forward by 8 * bytes. * @param value - The value to write. * @returns This. */ writeFloat64(value) { this.ensureAvailable(8); this._data.setFloat64(this.offset, value, this.littleEndian); this.offset += 8; this._updateLastWrittenByte(); return this; } /** * Write `value` as a 64-bit signed bigint and move pointer forward by 8 * bytes. * @param value - The value to write. * @returns This. */ writeBigInt64(value) { this.ensureAvailable(8); this._data.setBigInt64(this.offset, value, this.littleEndian); this.offset += 8; this._updateLastWrittenByte(); return this; } /** * Write `value` as a 64-bit unsigned bigint and move pointer forward by 8 * bytes. * @param value - The value to write. * @returns This. */ writeBigUint64(value) { this.ensureAvailable(8); this._data.setBigUint64(this.offset, value, this.littleEndian); this.offset += 8; this._updateLastWrittenByte(); return this; } /** * Write the charCode of `str`'s first character as an 8-bit unsigned integer * and move pointer forward by 1 byte. * @param str - The character to write. * @returns This. */ writeChar(str) { // eslint-disable-next-line unicorn/prefer-code-point return this.writeUint8(str.charCodeAt(0)); } /** * Write the charCodes of all `str`'s characters as 8-bit unsigned integers * and move pointer forward by `str.length` bytes. * @param str - The characters to write. * @returns This. */ writeChars(str) { for (let i = 0; i < str.length; i++) { // eslint-disable-next-line unicorn/prefer-code-point this.writeUint8(str.charCodeAt(i)); } return this; } /** * UTF-8 encode and write `str` to the current pointer offset and move pointer * forward according to the encoded length. * @param str - The string to write. * @returns This. */ writeUtf8(str) { return this.writeBytes(encode(str)); } /** * Export a Uint8Array view of the internal buffer. * The view starts at the byte offset and its length * is calculated to stop at the last written byte or the original length. * @returns A new Uint8Array view. */ toArray() { return new Uint8Array(this.buffer, this.byteOffset, this.lastWrittenByte); } /** * Get the total number of bytes written so far, regardless of the current offset. * @returns - Total number of bytes. */ getWrittenByteLength() { return this.lastWrittenByte - this.byteOffset; } /** * Update the last written byte offset * @private */ _updateLastWrittenByte() { if (this.offset > this.lastWrittenByte) { this.lastWrittenByte = this.offset; } } } //# sourceMappingURL=IOBuffer.js.map