UNPKG

@jsprismarine/jsbinaryutils

Version:

Basic binary data managing tool written in TypeScript.

671 lines (670 loc) 20.6 kB
import assert from 'assert'; export default class BinaryStream { /** * Creates a new BinaryStream instance. * @param {Buffer|null|undefined} buffer - The array or Buffer containing binary data. * @param {number} offset - The initial pointer position. */ constructor(buffer, offset = 0) { this.binary = []; this.buffer = null; this.writeIndex = 0; this.buffer = buffer ?? null; // Keep this instance for reading this.readIndex = offset; } /** * Reads a slice of buffer by the given length. * @param {number} len */ read(len) { this.doReadAssertions(len); return this.buffer.slice(this.readIndex, (this.readIndex += len)); } write(buf) { this.binary = [...this.binary, ...buf]; this.writeIndex += buf.byteLength; } /** * Reads an unsigned byte (0 to 255). * @returns {number} */ readByte() { this.doReadAssertions(1); return this.buffer.readUInt8(this.readIndex++); } /** * Writes an unsigned byte (0 to 255). * @param {number} v */ writeByte(v) { v &= 0xff; this.binary[this.writeIndex++] = v; } /** * Reads a signed byte (-128 to 127). * @returns {number} */ readSignedByte() { this.doReadAssertions(1); return this.buffer.readInt8(this.readIndex++); } /** * Writes a signed byte (-128 to 127). * @param {number} v */ writeSignedByte(v) { if (v < 0) v = 0xff + v + 1; this.binary[this.writeIndex++] = v & 0xff; } /** * Reads a boolean (true or false). * @returns {boolean} */ readBoolean() { this.doReadAssertions(1); return !!this.readByte(); } /** * Writes a boolean (true or false). * @param {boolean} v */ writeBoolean(v) { this.writeByte(+v); } /** * Reads a 16 bit (2 bytes) signed big-endian number. * @returns {number} */ readShort() { this.doReadAssertions(2); return this.buffer.readInt16BE(this.addOffset(2)); } /** * Writes a 16 bit (2 bytes) signed big-endian number. * @param {number} v */ writeShort(v) { this.doWriteAssertions(v, -32768, 32767); this.writeByte(v >> 8); this.writeByte(v); } /** * Reads a 16 bit (2 bytes) signed little-endian number. * @returns {number} */ readShortLE() { this.doReadAssertions(2); return this.buffer.readInt16LE(this.addOffset(2)); } /** * Writes a 16 bit (2 bytes) signed big-endian number. * @param {number} v */ writeShortLE(v) { this.doWriteAssertions(v, -32768, 32767); this.writeByte(v); this.writeByte(v >> 8); } /** * Reads a 16 bit (2 bytes) unsigned big-endian number. * @returns {number} */ readUnsignedShort() { this.doReadAssertions(2); return this.buffer.readUInt16BE(this.addOffset(2)); } /** * Writes a 16 bit (2 bytes) unsigned big-endian number. * @param {number} v */ writeUnsignedShort(v) { this.doWriteAssertions(v, 0, 65535); this.writeByte(v >>> 8); this.writeByte(v); } /** * Reads a 16 bit (2 bytes) unsigned little-endian number. * @returns {number} */ readUnsignedShortLE() { this.doReadAssertions(2); return this.buffer.readUInt16LE(this.addOffset(2)); } /** * Writes a 16 bit (2 bytes) unsigned little-endian number. * @param {number} v */ writeUnsignedShortLE(v) { this.doWriteAssertions(v, 0, 65535); this.writeByte(v); this.writeByte(v >>> 8); } /** * Reads a 24 bit (3 bytes) signed big-endian number. * @returns {number} */ readTriad() { this.doReadAssertions(3); return this.buffer.readIntBE(this.addOffset(3), 3); } /** * Writes a 24 bit (3 bytes) signed big-endian number. * @param {number} v */ writeTriad(v) { this.doWriteAssertions(v, -8388608, 8388607); this.writeByte((v & 0xff0000) >> 16); // msb this.writeByte((v & 0x00ff00) >> 8); // mib this.writeByte(v & 0x0000ff); // lsb } /** * Reads a 24 bit (3 bytes) little-endian number. * @returns {number} */ readTriadLE() { this.doReadAssertions(3); return this.buffer.readIntLE(this.addOffset(3), 3); } /** * Writes a 24 bit (3 bytes) signed little-endian number. * @param {number} v */ writeTriadLE(v) { this.doWriteAssertions(v, -8388608, 8388607); this.writeByte(v & 0x0000ff); this.writeByte((v & 0x00ff00) >> 8); this.writeByte((v & 0xff0000) >> 16); } /** * Reads a 24 bit (3 bytes) unsigned big-endian number. * @returns {number} */ readUnsignedTriad() { this.doReadAssertions(3); return this.buffer.readUIntBE(this.addOffset(3), 3); } /** * Writes a 24 bit (3 bytes) unsigned big-endian number. * @param {number} v */ writeUnsignedTriad(v) { this.doWriteAssertions(v, 0, 16777215); this.writeByte((v & 0xff0000) >>> 16); // msb this.writeByte((v & 0x00ff00) >>> 8); // mib this.writeByte(v & 0x0000ff); // lsb } /** * Reads a 24 bit (3 bytes) unsigned little-endian number. * @returns {number} */ readUnsignedTriadLE() { this.doReadAssertions(3); return this.buffer.readUIntLE(this.addOffset(3), 3); } /** * Writes a 24 bit (3 bytes) unsigned little-endian number. * @param {number} v */ writeUnsignedTriadLE(v) { this.doWriteAssertions(v, 0, 16777215); this.writeByte(v & 0x0000ff); this.writeByte((v & 0x00ff00) >>> 8); this.writeByte((v & 0xff0000) >>> 16); } /** * Reads a 32 bit (4 bytes) big-endian signed number. * @returns {number} */ readInt() { this.doReadAssertions(4); return this.buffer.readInt32BE(this.addOffset(4)); } /** * Writes a 32 bit (4 bytes) big-endian signed number. * @param {number} v */ writeInt(v) { if (v < 0) v = v & (0xffffffff + v + 1); this.doWriteAssertions(v, -2147483648, 2147483647); this.writeByte(v >> 24); this.writeByte(v >> 16); this.writeByte(v >> 8); this.writeByte(v); } /** * Reads a 32 bit (4 bytes) signed number. * @returns {number} */ readIntLE() { this.doReadAssertions(4); return this.buffer.readIntLE(this.addOffset(4), 4); } /** * Writes a 32 bit (4 bytes) little-endian signed number. * @param {number} v */ writeIntLE(v) { if (v < 0) v = v & (0xffffffff + v + 1); this.doWriteAssertions(v, -2147483648, 2147483647); this.writeByte(v); this.writeByte(v >> 8); this.writeByte(v >> 16); this.writeByte(v >> 24); } /** * Reads a 32 bit (4 bytes) big-endian unsigned number. * @returns {number} */ readUnsignedInt() { this.doReadAssertions(4); return this.buffer.readUInt32BE(this.addOffset(4)); } /** * Writes a 32 bit (4 bytes) big-endian unsigned number. * @param {number} v */ writeUnsignedInt(v) { this.doWriteAssertions(v, 0, 4294967295); this.writeByte(v >>> 24); this.writeByte(v >>> 16); this.writeByte(v >>> 8); this.writeByte(v); } /** * Reads a 32 bit (4 bytes) little-endian unsigned number. * @returns {number} */ readUnsignedIntLE() { this.doReadAssertions(4); return this.buffer.readUInt32LE(this.addOffset(4)); } /** * Writes a 32 bit (4 bytes) little-endian unsigned number. * @param {number} v */ writeUnsignedIntLE(v) { this.doWriteAssertions(v, 0, 4294967295); this.writeByte(v); this.writeByte(v >>> 8); this.writeByte(v >>> 16); this.writeByte(v >>> 24); } /** * Returns a 32 bit (4 bytes) big-endian flating point number. * @returns {number} */ readFloat() { this.doReadAssertions(4); return this.buffer.readFloatBE(this.addOffset(4)); } /** * Writes a 32 bit (4 bytes) big-endian floating point number. * @param {number} v */ writeFloat(v) { this.doWriteAssertions(v, -3.4028234663852886e38, +3.4028234663852886e38); this.write(new Uint8Array(new Float32Array([v]).buffer).reverse()); } /** * Returns a 32 bit (4 bytes) big-endian flating point number. * @returns {number} */ readFloatLE() { this.doReadAssertions(4); return this.buffer.readFloatLE(this.addOffset(4)); } /** * Writes a 32 bit (4 bytes) little-endian floating point number. * @param {number} v */ writeFloatLE(v) { this.doWriteAssertions(v, -3.4028234663852886e38, +3.4028234663852886e38); this.write(new Uint8Array(new Float32Array([v]).buffer)); } /** * Returns a 64 bit (8 bytes) big-endian flating point number. * @returns {number} */ readDouble() { this.doReadAssertions(8); return this.buffer.readDoubleBE(this.addOffset(8)); } /** * Writes a 64 bit (8 bytes) big-endian floating point number. * @param {number} v */ writeDouble(v) { this.doWriteAssertions(v, -1.7976931348623157e308, +1.7976931348623157e308); this.write(new Uint8Array(new Float64Array([v]).buffer).reverse()); } /** * Returns a 64 bit (8 bytes) little-endian flating point number. * @returns {number} */ readDoubleLE() { this.doReadAssertions(8); return this.buffer.readDoubleLE(this.addOffset(8)); } /** * Writes a 64 bit (8 bytes) little-endian floating point number. * @param {number} v */ writeDoubleLE(v) { this.doWriteAssertions(v, -1.7976931348623157e308, +1.7976931348623157e308); this.write(new Uint8Array(new Float64Array([v]).buffer)); } /** * Returns a 64 bit (8 bytes) signed big-endian number. * @returns {bigint} */ readLong() { this.doReadAssertions(8); return this.buffer.readBigInt64BE(this.addOffset(8)); } /** * Writes a 64 bit (8 bytes) signed big-endian number. * @param {bigint} v */ writeLong(v) { const lo = Number(v & BigInt(0xffffffff)); this.binary[this.writeIndex + 7] = lo; this.binary[this.writeIndex + 6] = lo >> 8; this.binary[this.writeIndex + 5] = lo >> 16; this.binary[this.writeIndex + 4] = lo >> 24; const hi = Number((v >> BigInt(32)) & BigInt(0xffffffff)); this.binary[this.writeIndex + 3] = hi; this.binary[this.writeIndex + 2] = hi >> 8; this.binary[this.writeIndex + 1] = hi >> 16; this.binary[this.writeIndex] = hi >> 24; this.writeIndex += 8; } /** * Returns a 64 bit (8 bytes) signed little-endian number. * @returns {bigint} */ readLongLE() { this.doReadAssertions(8); return this.buffer.readBigInt64LE(this.addOffset(8)); } /** * Writes a 64 bit (8 bytes) signed big-endian number. * @param {bigint} v */ writeLongLE(v) { const lo = Number(v & BigInt(0xffffffff)); this.binary[this.writeIndex++] = lo; this.binary[this.writeIndex++] = lo >> 8; this.binary[this.writeIndex++] = lo >> 16; this.binary[this.writeIndex++] = lo >> 24; const hi = Number((v >> BigInt(32)) & BigInt(0xffffffff)); this.binary[this.writeIndex++] = hi; this.binary[this.writeIndex++] = hi >> 8; this.binary[this.writeIndex++] = hi >> 16; this.binary[this.writeIndex++] = hi >> 24; } /** * Returns a 64 bit (8 bytes) unsigned big-endian number. * @returns {bigint} */ readUnsignedLong() { this.doReadAssertions(8); return this.buffer.readBigUInt64BE(this.addOffset(8)); } /** * Writes a 64 bit (8 bytes) unsigned big-endian number. * @param {bigint} v */ writeUnsignedLong(v) { const lo = Number(v & BigInt(0xffffffff)); this.binary[this.writeIndex + 7] = lo; this.binary[this.writeIndex + 6] = lo >> 8; this.binary[this.writeIndex + 5] = lo >> 16; this.binary[this.writeIndex + 4] = lo >> 24; const hi = Number((v >> BigInt(32)) & BigInt(0xffffffff)); this.binary[this.writeIndex + 3] = hi; this.binary[this.writeIndex + 2] = hi >> 8; this.binary[this.writeIndex + 1] = hi >> 16; this.binary[this.writeIndex] = hi >> 24; this.writeIndex += 8; } /** * Returns a 64 bit (8 bytes) unsigned little-endian number. * @returns {bigint} */ readUnsignedLongLE() { this.doReadAssertions(8); return this.buffer.readBigUInt64LE(this.addOffset(8)); } /** * Writes a 64 bit (8 bytes) unsigned big-endian number. * @param {bigint} v */ writeUnsignedLongLE(v) { const lo = Number(v & BigInt(0xffffffff)); this.binary[this.writeIndex++] = lo; this.binary[this.writeIndex++] = lo >> 8; this.binary[this.writeIndex++] = lo >> 16; this.binary[this.writeIndex++] = lo >> 24; const hi = Number((v >> BigInt(32)) & BigInt(0xffffffff)); this.binary[this.writeIndex++] = hi; this.binary[this.writeIndex++] = hi >> 8; this.binary[this.writeIndex++] = hi >> 16; this.binary[this.writeIndex++] = hi >> 24; } /** * Reads a 32 bit (4 bytes) zigzag-encoded number. * @returns {number} */ readVarInt() { const raw = this.readUnsignedVarInt(); const temp = (((raw << 63) >> 63) ^ raw) >> 1; return temp ^ (raw & (1 << 63)); } /** * Writes a 32 bit (4 bytes) zigzag-encoded number. * @param {number} v */ writeVarInt(v) { v = (v << 32) >> 32; return this.writeUnsignedVarInt((v << 1) ^ (v >> 31)); } /** * Reads a 32 bit unsigned number. * @returns {number} */ readUnsignedVarInt() { assert(this.buffer != null, 'Reading on empty buffer!'); let value = 0; for (let i = 0; i <= 28; i += 7) { if (typeof this.buffer[this.readIndex] === 'undefined') { throw new Error('No bytes left in buffer'); } let b = this.readByte(); value |= (b & 0x7f) << i; if ((b & 0x80) === 0) { return value; } } throw new Error('VarInt did not terminate after 5 bytes!'); } /** * Writes a 32 bit unsigned number with variable-length. * @param {number} v */ writeUnsignedVarInt(v) { while ((v & 0xffffff80) !== 0) { this.writeByte((v & 0x7f) | 0x80); v >>>= 7; } this.writeByte(v & 0x7f); } /** * Reads a 64 bit zigzag-encoded variable-length number. * @returns {bigint} */ readVarLong() { const raw = this.readUnsignedVarLong(); return raw >> 1n; } /** * Writes a 64 bit unsigned zigzag-encoded number. * @param {bigint} v */ writeVarLong(v) { return this.writeUnsignedVarLong((v << 1n) ^ (v >> 63n)); } /** * Reads a 64 bit unsigned variable-length number. * @returns {bigint} */ readUnsignedVarLong() { let value = 0n; for (let i = 0; i <= 63; i += 7) { if (this.feof()) { throw new Error('No bytes left in buffer'); } const b = this.readByte(); value |= (BigInt(b) & 0x7fn) << BigInt(i); if ((b & 0x80) === 0) { return value; } } throw new Error('VarLong did not terminate after 10 bytes!'); } /** * Writes a 64 bit unsigned variable-length number. * @param {bigint} v */ writeUnsignedVarLong(v) { for (let i = 0; i < 10; ++i) { if (v >> 7n !== 0n) { this.writeByte(Number(v | 0x80n)); } else { this.writeByte(Number(v & 0x7fn)); break; } v >>= 7n; } } /** * Increases the write offset by the given length. * @param {number} length */ addOffset(length) { return (this.readIndex += length) - length; } /** * Returns whatever or not the read offset is at end of line. * @returns {number} */ feof() { if (!this.buffer) throw new Error('Buffer is write only!'); return typeof this.buffer[this.readIndex] === 'undefined'; } /** * Reads the remaining bytes and returns the buffer slice. * @returns {Buffer} */ readRemaining() { if (!this.buffer) throw new Error('Buffer is write only!'); const buf = this.buffer.slice(this.readIndex); this.readIndex = this.buffer.byteLength; return buf; } /** * Skips len bytes on the buffer. * @param {number} len */ skip(len) { assert(Number.isInteger(len), 'Cannot skip a float amount of bytes'); this.readIndex += len; } /** * Returns the encoded buffer. * @returns {Buffer} */ getBuffer() { return this.buffer !== null ? this.buffer : Buffer.from(this.binary); } /** * Sets the buffer for reading. * make sure to reset the reading index! * @param buf - The new Buffer. */ setBuffer(buf) { this.buffer = buf; } /** * Clears the whole BinaryStream instance. */ clear() { this.buffer = null; this.binary = []; this.readIndex = 0; this.writeIndex = 0; } /** * Conventional method to reuse the stream * without having to create a new BinaryStream instance. * @param buf - The new buffer instance. */ reuse(buf) { this.buffer = buf; this.binary = []; this.readIndex = 0; this.writeIndex = 0; } /** * Sets the reading index. * @param index - The new read index. */ setReadIndex(index) { assert(index > 0, 'Index must be a positive integer'); this.readIndex = index; } /** * Sets the new writing index. * @param index - The new write index. */ setWriteIndex(index) { assert(index > 0, 'Index must be a positive integer'); this.writeIndex = index; } /** * Retuns the read index. * @returns {number} */ getReadIndex() { return this.readIndex; } /** * Returns the write index. * @returns {number} */ getWriteIndex() { return this.writeIndex; } /** * Do read assertions, check if the read buffer is null. * @param {number} byteLength */ doReadAssertions(byteLength) { assert(this.buffer !== null, 'Cannot read without buffer data!'); assert(this.buffer.byteLength >= byteLength, 'Cannot read without buffer data!'); } /** * Do read assertions, check if the read buffer is null. * @param {number|bigint} num * @param {number|bigint} minVal * @param {number|bigint} maxVal */ doWriteAssertions(num, minVal, maxVal) { assert(num >= minVal && num <= maxVal, `Value out of bounds: value=${num}, min=${minVal}, max=${maxVal}`); } }