"use strict"; // The default Buffer size if one is not provided. const DEFAULT_SMARTBUFFER_SIZE = 4096; // The default string encoding to use for reading/writing strings. const DEFAULT_SMARTBUFFER_ENCODING = 'utf8'; class SmartBuffer { /** * Creates a new SmartBuffer instance. * * @param arg1 { Number | BufferEncoding | Buffer | SmartBufferOptions } * @param arg2 { BufferEncoding } */ constructor(arg1, arg2) { this.length = 0; this.encoding = DEFAULT_SMARTBUFFER_ENCODING; this.writeOffset = 0; this.readOffset = 0; // Initial buffer size provided if (typeof arg1 === 'number') { if (Number.isFinite(arg1) && Number.isInteger(arg1) && arg1 > 0) { this.buff = Buffer.allocUnsafe(arg1); } else { throw new Error('Invalid size provided. Size must be a valid integer greater than zero.'); } } else if (typeof arg1 === 'string') { if (Buffer.isEncoding(arg1)) { this.buff = Buffer.allocUnsafe(DEFAULT_SMARTBUFFER_SIZE); this.encoding = arg1; } else { throw new Error('Invalid encoding provided. Please specify a valid encoding the internal Node.js Buffer supports.'); } } else if (arg1 instanceof Buffer) { this.buff = arg1; this.length = arg1.length; } else if (SmartBuffer.isSmartBufferOptions(arg1)) { // Checks for encoding if (arg1.encoding) { if (Buffer.isEncoding(arg1.encoding)) { this.encoding = arg1.encoding; } else { throw new Error('Invalid encoding provided. Please specify a valid encoding the internal Node.js Buffer supports.'); } } // Checks for initial size length if (arg1.size) { if (Number.isFinite(arg1.size) && Number.isInteger(arg1.size) && arg1.size > 0) { this.buff = Buffer.allocUnsafe(arg1.size); } else { throw new Error('Invalid size provided. Size must be a valid integer greater than zero.'); } } else if (arg1.buff) { if (arg1.buff instanceof Buffer) { this.buff = arg1.buff; this.length = arg1.buff.length; } else { throw new Error('Invalid buffer provided in SmartBufferOptions.'); } } else { this.buff = Buffer.allocUnsafe(DEFAULT_SMARTBUFFER_SIZE); } } else if (typeof arg1 === 'object') { throw new Error('Invalid object supplied to SmartBuffer constructor.'); } else { this.buff = Buffer.allocUnsafe(DEFAULT_SMARTBUFFER_SIZE); } // Check for encoding (Buffer, Encoding) constructor. if (typeof arg2 === 'string') { if (Buffer.isEncoding(arg2)) { this.encoding = arg2; } else { throw new Error('Invalid encoding provided. Please specify a valid encoding the internal Node.js Buffer supports.'); } } } /** * Creates a new SmartBuffer instance with the provided internal Buffer size and optional encoding. * * @param size { Number } The size of the internal Buffer. * @param encoding { String } The BufferEncoding to use for strings. * * @return { SmartBuffer } */ static fromSize(size, encoding) { return new this({ size: size, encoding: encoding }); } /** * Creates a new SmartBuffer instance with the provided Buffer and optional encoding. * * @param buffer { Buffer } The Buffer to use as the internal Buffer value. * @param encoding { String } The BufferEncoding to use for strings. * * @return { SmartBuffer } */ static fromBuffer(buff, encoding) { return new this({ buff: buff, encoding: encoding }); } /** * Creates a new SmartBuffer instance with the provided SmartBufferOptions options. * * @param options { SmartBufferOptions } The options to use when creating the SmartBuffer instance. */ static fromOptions(options) { return new this(options); } /** * Ensures that the internal Buffer is large enough to write data. * * @param minLength { Number } The minimum length of the data that needs to be written. * @param offset { Number } The offset of the data to be written. */ ensureWriteable(minLength, offset) { const offsetVal = typeof offset === 'number' ? offset : 0; // Ensure there is enough internal Buffer capacity. this.ensureCapacity(this.length + minLength + offsetVal); // If offset is provided, copy data into appropriate location in regards to the offset. if (typeof offset === 'number') { this.buff.copy(this.buff, offsetVal + minLength, offsetVal, this.buff.length); } // Adjust instance length. this.length = Math.max(this.length + minLength, offsetVal + minLength); } /** * Ensures that the internal Buffer is large enough to write at least the given amount of data. * * @param minLength { Number } The minimum length of the data needs to be written. */ ensureCapacity(minLength) { const oldLength = this.buff.length; if (minLength > oldLength) { let data = this.buff; let newLength = (oldLength * 3) / 2 + 1; if (newLength < minLength) { newLength = minLength; } this.buff = Buffer.allocUnsafe(newLength); data.copy(this.buff, 0, 0, oldLength); } } /** * Reads a numeric number value using the provided function. * * @param func { Function(offset: number) => number } The function to read data on the internal Buffer with. * @param byteSize { Number } The number of bytes read. * * @param { Number } */ readNumberValue(func, byteSize) { // Call Buffer.readXXXX(); const value = func.call(this.buff, this.readOffset); // Adjust internal read offset this.readOffset += byteSize; return value; } /** * Writes a numeric number value using the provided function. * * @param func { Function(offset: number, offset?) => number} The function to write data on the internal Buffer with. * @param byteSize { Number } The number of bytes written. * @param value { Number } The number value to write. * @param offset { Number } the offset to write the number at. * */ writeNumberValue(func, byteSize, value, offset) { const offsetVal = typeof offset === 'number' ? offset : this.writeOffset; // Ensure there is enough internal Buffer capacity. (raw offset is passed) this.ensureWriteable(byteSize, offset); // Call buffer.writeXXXX(); func.call(this.buff, value, offsetVal); // Adjusts internal write offset this.writeOffset += byteSize; } // Signed integers /** * Reads an Int8 value from the current read position. * * @return { Number } */ readInt8() { return this.readNumberValue(Buffer.prototype.readUInt8, 1); } /** * Reads an Int16BE value from the current read position. * * @return { Number } */ readInt16BE() { return this.readNumberValue(Buffer.prototype.readUInt16BE, 2); } /** * Reads an Int16LE value from the current read position. * * @return { Number } */ readInt16LE() { return this.readNumberValue(Buffer.prototype.readUInt16LE, 2); } /** * Reads an Int32BE value from the current read position. * * @return { Number } */ readInt32BE() { return this.readNumberValue(Buffer.prototype.readUInt32BE, 4); } /** * Reads an Int32LE value from the current read position. * * @return { Number } */ readInt32LE() { return this.readNumberValue(Buffer.prototype.readUInt32LE, 4); } /** * Writes an Int8 value to the current write position (or at optional offset). * * @param value { Number } The value to write. * @param offset { Number } The offset to write the value at. * * @return this */ writeInt8(value, offset) { this.writeNumberValue(Buffer.prototype.writeInt8, 1, value, offset); return this; } /** * Writes an Int16BE value to the current write position (or at optional offset). * * @param value { Number } The value to write. * @param offset { Number } The offset to write the value at. * * @return this */ writeInt16BE(value, offset) { this.writeNumberValue(Buffer.prototype.writeInt16BE, 2, value, offset); return this; } /** * Writes an Int16LE value to the current write position (or at optional offset). * * @param value { Number } The value to write. * @param offset { Number } The offset to write the value at. * * @return this */ writeInt16LE(value, offset) { this.writeNumberValue(Buffer.prototype.writeInt16LE, 2, value, offset); return this; } /** * Writes an Int32BE value to the current write position (or at optional offset). * * @param value { Number } The value to write. * @param offset { Number } The offset to write the value at. * * @return this */ writeInt32BE(value, offset) { this.writeNumberValue(Buffer.prototype.writeInt32BE, 4, value, offset); return this; } /** * Writes an Int32LE value to the current write position (or at optional offset). * * @param value { Number } The value to write. * @param offset { Number } The offset to write the value at. * * @return this */ writeInt32LE(value, offset) { this.writeNumberValue(Buffer.prototype.writeInt32LE, 4, value, offset); return this; } // Unsigned Integers /** * Reads an UInt8 value from the current read position. * * @return { Number } */ readUInt8() { return this.readNumberValue(Buffer.prototype.readUInt8, 1); } /** * Reads an UInt16BE value from the current read position. * * @return { Number } */ readUInt16BE() { return this.readNumberValue(Buffer.prototype.readUInt16BE, 2); } /** * Reads an UInt16LE value from the current read position. * * @return { Number } */ readUInt16LE() { return this.readNumberValue(Buffer.prototype.readUInt16LE, 2); } /** * Reads an UInt32BE value from the current read position. * * @return { Number } */ readUInt32BE() { return this.readNumberValue(Buffer.prototype.readUInt32BE, 4); } /** * Reads an UInt32LE value from the current read position. * * @return { Number } */ readUInt32LE() { return this.readNumberValue(Buffer.prototype.readUInt32LE, 4); } /** * Writes an UInt8 value to the current write position (or at optional offset). * * @param value { Number } The value to write. * @param offset { Number } The offset to write the value at. * * @return this */ writeUInt8(value, offset) { this.writeNumberValue(Buffer.prototype.writeUInt8, 1, value, offset); return this; } /** * Writes an UInt16BE value to the current write position (or at optional offset). * * @param value { Number } The value to write. * @param offset { Number } The offset to write the value at. * * @return this */ writeUInt16BE(value, offset) { this.writeNumberValue(Buffer.prototype.writeUInt16BE, 2, value, offset); return this; } /** * Writes an UInt16LE value to the current write position (or at optional offset). * * @param value { Number } The value to write. * @param offset { Number } The offset to write the value at. * * @return this */ writeUInt16LE(value, offset) { this.writeNumberValue(Buffer.prototype.writeUInt16LE, 2, value, offset); return this; } /** * Writes an UInt32BE value to the current write position (or at optional offset). * * @param value { Number } The value to write. * @param offset { Number } The offset to write the value at. * * @return this */ writeUInt32BE(value, offset) { this.writeNumberValue(Buffer.prototype.writeUInt32BE, 4, value, offset); return this; } /** * Writes an UInt32LE value to the current write position (or at optional offset). * * @param value { Number } The value to write. * @param offset { Number } The offset to write the value at. * * @return this */ writeUInt32LE(value, offset) { this.writeNumberValue(Buffer.prototype.writeUInt32LE, 4, value, offset); return this; } // Floating Point /** * Reads an FloatBE value from the current read position. * * @return { Number } */ readFloatBE() { return this.readNumberValue(Buffer.prototype.readFloatBE, 4); } /** * Reads an FloatLE value from the current read position. * * @return { Number } */ readFloatLE() { return this.readNumberValue(Buffer.prototype.readFloatLE, 4); } /** * Writes a FloatBE value to the current write position (or at optional offset). * * @param value { Number } The value to write. * @param offset { Number } The offset to write the value at. * * @return this */ writeFloatBE(value, offset) { this.writeNumberValue(Buffer.prototype.writeFloatBE, 4, value, offset); return this; } /** * Writes a FloatLE value to the current write position (or at optional offset). * * @param value { Number } The value to write. * @param offset { Number } The offset to write the value at. * * @return this */ writeFloatLE(value, offset) { this.writeNumberValue(Buffer.prototype.writeFloatLE, 4, value, offset); return this; } // Double Floating Point /** * Reads an DoublEBE value from the current read position. * * @return { Number } */ readDoubleBE() { return this.readNumberValue(Buffer.prototype.readDoubleBE, 8); } /** * Reads an DoubleLE value from the current read position. * * @return { Number } */ readDoubleLE() { return this.readNumberValue(Buffer.prototype.readDoubleLE, 8); } /** * Writes a DoubleBE value to the current write position (or at optional offset). * * @param value { Number } The value to write. * @param offset { Number } The offset to write the value at. * * @return this */ writeDoubleBE(value, offset) { this.writeNumberValue(Buffer.prototype.writeDoubleBE, 8, value, offset); return this; } /** * Writes a DoubleLE value to the current write position (or at optional offset). * * @param value { Number } The value to write. * @param offset { Number } The offset to write the value at. * * @return this */ writeDoubleLE(value, offset) { this.writeNumberValue(Buffer.prototype.writeDoubleLE, 8, value, offset); return this; } // Strings /** * Reads a String from the current read position. * * @param length { Number } The number of bytes to read as a String. * @param encoding { String } The BufferEncoding to use for the string (Defaults to instance level encoding). * * @return { String } */ readString(length, encoding) { const lengthVal = Math.min(length, this.length - this.readOffset) || this.length - this.readOffset; const value = this.buff.slice(this.readOffset, this.readOffset + lengthVal).toString(encoding || this.encoding); this.readOffset += lengthVal; return value; } /** * Writes a String to the current write position. * * @param value { String } The String value to write. * @param arg2 { Number | String } The offset to write the string to, or the BufferEncoding to use. * @param encoding { String } The BufferEncoding to use for writing strings (defaults to instance encoding). */ writeString(value, arg2, encoding) { let offsetVal = this.writeOffset; let encodingVal = this.encoding; // Check for offset if (typeof arg2 === 'number') { offsetVal = arg2; } else if (typeof arg2 === 'string') { if (Buffer.isEncoding(arg2)) { encodingVal = arg2; } else { throw new Error('Invalid encoding provided. Please specify a valid encoding the internal Node.js Buffer supports.'); } } // Check for encoding (third param) if (typeof encoding === 'string') { if (Buffer.isEncoding(encoding)) { encodingVal = encoding; } else { throw new Error('Invalid encoding provided. Please specify a valid encoding the internal Node.js Buffer supports.'); } } // Calculate bytelength of string. const byteLength = Buffer.byteLength(value, encodingVal); // Ensure there is enough internal Buffer capacity. this.ensureWriteable(byteLength, offsetVal); // Write value this.buff.write(value, offsetVal, byteLength, encodingVal); // Increment internal Buffer write offset; this.writeOffset += byteLength; return this; } /** * Reads a null-terminated String from the current read position. * * @param encoding { String } The BufferEncoding to use for the string (Defaults to instance level encoding). * * @return { String } */ readStringNT(encoding) { // Set null character position to the end SmartBuffer instance. let nullPos = this.length; // Find next null character (if one is not found, default from above is used) for (let i = this.readOffset; i < this.length; i++) { if (this.buff[i] === 0x00) { nullPos = i; break; } } // Read string value const value = this.buff.slice(this.readOffset, nullPos); // Increment internal Buffer read offset this.readOffset = nullPos + 1; return value.toString(encoding || this.encoding); } /** * Writes a null-terminated String to the current write position. * * @param value { String } The String value to write. * @param arg2 { Number | String } The offset to write the string to, or the BufferEncoding to use. * @param encoding { String } The BufferEncoding to use for writing strings (defaults to instance encoding). */ writeStringNT(value, offset, encoding) { // Write Values this.writeString(value, offset, encoding); this.writeUInt8(0x00, (typeof offset === 'number' ? offset + value.length : this.writeOffset)); } // Buffers /** * Reads a Buffer from the internal read position. * * @param length { Number } The length of data to read as a Buffer. * * @return { Buffer } */ readBuffer(length) { const lengthVal = typeof length === 'number' ? length : this.length; const endPoint = Math.min(this.length, this.readOffset + lengthVal); // Read buffer value const value = this.buff.slice(this.readOffset, endPoint); // Increment internal Buffer read offset this.readOffset = endPoint; return value; } /** * Writes a Buffer to the current write position. * * @param value { Buffer } The Buffer to write. * @param offset { Number } The offset to write the Buffer to. */ writeBuffer(value, offset) { const offsetVal = typeof offset === 'number' ? offset : this.writeOffset; // Ensure there is enough internal Buffer capacity. this.ensureWriteable(value.length, offsetVal); // Write buffer value value.copy(this.buff, offsetVal); // Increment internal Buffer write offset this.writeOffset += value.length; return this; } /** * Reads a null-terminated Buffer from the current read poisiton. * * @return { Buffer } */ readBufferNT() { // Set null character position to the end SmartBuffer instance. let nullPos = this.length; // Find next null character (if one is not found, default from above is used) for (let i = this.readOffset; i < this.length; i++) { if (this.buff[i] === 0x00) { nullPos = i; break; } } // Read value const value = this.buff.slice(this.readOffset, nullPos); // Increment internal Buffer read offset this.readOffset = nullPos + 1; return value; } /** * Writes a null-terminated Buffer to the current write position. * * @param value { Buffer } The Buffer to write. * @param offset { Number } The offset to write the Buffer to. */ writeBufferNT(value, offset) { // Write Values this.writeBuffer(value, offset); this.writeUInt8(0, (typeof offset === 'number' ? offset + value.length : this.writeOffset)); return this; } /** * Clears the SmartBuffer instance to its original empty state. */ clear() { this.writeOffset = 0; this.readOffset = 0; this.length = 0; } /** * Gets the remaining data left to be read from the SmartBuffer instance. * * @return { Number } */ remaining() { return this.length - this.readOffset; } /** * Moves the read offset forward. * * @param amount { Number } The amount to move the read offset forward by. */ skip(amount) { if (this.readOffset + amount > this.length) { throw new Error('Target position is beyond the bounds of the SmartBuffer size.'); } this.readOffset += amount; } /** * Moves the read offset backwards. * * @param amount { Number } The amount to move the read offset backwards by. */ rewind(amount) { if (this.readOffset - amount < 0) { throw new Error('Target position is beyond the bounds of the SmartBuffer size.'); } this.readOffset -= amount; } /** * Moves the read offset to a specific position. * * @param position { Number } The position to move the read offset to. */ skipTo(position) { this.moveTo(position); } /** * Moves the read offset to a specific position. * * @param position { Number } The position to move the read offset to. */ moveTo(position) { if (position > this.length) { throw new Error('Target position is beyond the bounds of the SmartBuffer size.'); } this.readOffset = position; } /** * Gets the value of the internal managed Buffer * * @param { Buffer } */ toBuffer() { return this.buff.slice(0, this.length); } /** * Gets the String value of the internal managed Buffer * * @param encoding { String } The BufferEncoding to display the Buffer as (defaults to instance level encoding). */ toString(encoding) { const encodingVal = typeof encoding === 'string' ? encoding : this.encoding; if (Buffer.isEncoding(encodingVal)) { return this.buff.toString(encodingVal, 0, this.length); } else { throw new Error('Invalid encoding provided. Please specify a valid encoding the internal Node.js Buffer supports.'); } } /** * Destroys the SmartBuffer instance. */ destroy() { this.clear(); } /** * Type checking function that determines if an object is a SmartBufferOptions object. */ static isSmartBufferOptions(options) { const castOptions = options; return castOptions && (castOptions.encoding !== undefined || castOptions.size !== undefined || castOptions.buff !== undefined); } } module.exports = SmartBuffer; //# sourceMappingURL=smartbuffer.js.map