| ; | |
| Object.defineProperty(exports, "__esModule", { value: true }); | |
| exports.IOBuffer = void 0; | |
| const text_1 = require("./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, | |
| }; | |
| 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<br> | |
| * If data is `undefined`, the buffer will be initialized with a default length of 8Kb<br> | |
| * 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 (0, text_1.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 (0, text_1.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((0, text_1.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; | |
| } | |
| } | |
| } | |
| exports.IOBuffer = IOBuffer; | |
| //# sourceMappingURL=IOBuffer.js.map |