UNPKG

@shinyoshiaki/binary-data

Version:

Declarative binary data encoder / decoder.

373 lines (303 loc) 8.04 kB
'use strict'; const createFunction = require('generate-function'); const LinkedList = require('internal/linked-list'); /** * An optimised partial implementation of Buffer list from `bl`. */ class BufferList { /** * @class */ constructor() { this.queue = new LinkedList(); this.offset = 0; } /** * The number of bytes in list. * @returns {number} */ get length() { return this.queue.length - this.offset; } /** * Adds an additional buffer or BufferList to the internal list. * @param {Buffer|Buffer[]|BufferList|BufferList[]} buf */ append(buf) { if (Buffer.isBuffer(buf)) { if (this.offset > 0) { const head = this.queue.shift(); this.queue.unshift(head.slice(this.offset)); this.offset = 0; } this.queue.push(buf); } else if (Array.isArray(buf)) { for (let i = 0; i < buf.length; i += 1) { this.append(buf[i]); } } else if (buf instanceof BufferList) { if (this.offset > 0) { const head = this.queue.shift(); this.queue.unshift(head.slice(this.offset)); this.offset = 0; } if (buf.offset > 0) { const head = buf.queue.shift(); buf.queue.unshift(head.slice(buf.offset)); buf.offset = 0; } let leaf = buf.queue.head; while (leaf) { this.queue.push(leaf.buffer); leaf = leaf.next; } } } /* eslint-disable consistent-return */ /** * Return the byte at the specified index. * @param {number} index Index of the byte in the Buffer list. * @returns {number} */ get(index) { let i = index; while (i >= this.length) { i -= this.length; } while (i < 0) { i += this.length; } let leaf = this.queue.head; let { offset } = this; while (leaf) { if (leaf.buffer.length - offset > i) { return leaf.buffer[i + offset]; } i -= leaf.buffer.length - offset; offset = 0; leaf = leaf.next; } } /* eslint-enable consistent-return */ /** * Returns a new Buffer object containing the bytes within the range specified. * @param {number} start * @param {number} end * @returns {Buffer} */ slice(start, end) { /* eslint-disable no-param-reassign */ if (typeof start !== 'number') { start = 0; } if (typeof end !== 'number') { end = this.length; } while (start < 0) { start += this.length; } while (end < 0) { end += this.length; } end = Math.min(end, this.length); if (start >= this.length || end === 0) { return Buffer.alloc(0); } const tmpStart = start; start = Math.min(tmpStart, end); end = Math.max(tmpStart, end); const subset = this.queue.slice(start + this.offset, end + this.offset); // Buffer.concat() doesn't have optimization for count == 1 if (subset.count === 1) { return subset.first; } let leaf = subset.head; const target = Buffer.allocUnsafe(subset.length); let offset = 0; for (let i = 0; i < subset.count; i += 1) { target.set(leaf.buffer, offset); offset += leaf.buffer.length; leaf = leaf.next; } return target; /* eslint-enable no-param-reassign */ } /** * Return a string representation of the buffer. * @param {string} encoding * @param {number} start * @param {number} end * @returns {string} */ toString(encoding, start, end) { return this.slice(start, end).toString(encoding); } /** * Shift bytes off the start of the list. * @param {number} bytes */ consume(bytes) { let remainder = bytes; while (this.length > 0) { const firstLength = this.queue.first.length - this.offset; if (remainder >= firstLength) { this.queue.shift(); remainder -= firstLength; this.offset = 0; } else { this.offset += remainder; break; } } } /** * Returns the first (least) index of an element * within the list equal to the specified value, * or -1 if none is found. * @param {number} byte * @param {number} [offset] * @returns {number} */ indexOf(byte, offset = 0) { /* eslint-disable no-param-reassign */ if (!Number.isInteger(byte)) { throw new TypeError('Invalid argument 1'); } if (byte < 0 || byte > 0xff) { throw new Error('Invalid argument 1'); } if (!Number.isInteger(offset)) { offset = 0; } while (offset >= this.length) { offset -= this.length; } while (offset < 0) { offset += this.length; } let leaf = this.queue.head; let bias = 0; const next = () => { bias += leaf.buffer.length; leaf = leaf.next; }; while (leaf) { let byteOffset = 0; if (leaf === this.queue.head) { byteOffset += this.offset; } // `offset` is point to next chunk if (offset >= leaf.buffer.length - byteOffset) { offset -= leaf.buffer.length - byteOffset; next(); continue; // eslint-disable-line no-continue } // `offset` is point to current chunk if (offset < leaf.buffer.length) { byteOffset += offset; } const index = leaf.buffer.indexOf(byte, byteOffset); if (index > -1) { return index + bias - this.offset; } next(); if (byteOffset > this.offset) { offset = 0; } } return -1; /* eslint-enable no-param-reassign */ } } const fixedReadMethods = { readDoubleBE: 8, readDoubleLE: 8, readFloatBE: 4, readFloatLE: 4, readInt32BE: 4, readInt32LE: 4, readUInt32BE: 4, readUInt32LE: 4, readInt16BE: 2, readInt16LE: 2, readUInt16BE: 2, readUInt16LE: 2, readInt8: 1, readUInt8: 1, }; Object.keys(fixedReadMethods).forEach(method => { const gen = createFunction(); gen(` function bufferlist_${method}(offset = 0) { const start = offset + this.offset; const head = this.queue.first; const size = ${gen.formats.d(fixedReadMethods[method])}; const isFirtsChunkEnough = head.length - start >= size; return isFirtsChunkEnough ? head.${method}(start) : this.slice(offset, offset + size).${method}(0) } `); BufferList.prototype[method] = gen.toFunction(); }); const metaReadMethods = ['readIntBE', 'readIntLE', 'readUIntBE', 'readUIntLE']; metaReadMethods.forEach(method => { const gen = createFunction(); gen(` function bufferlist_${method}(size, offset = 0) { const start = offset + this.offset; const head = this.queue.first; const isFirtsChunkEnough = head.length - start >= size; return isFirtsChunkEnough ? head.${method}(start, size) : this.slice(offset, offset + size).${method}(0, size) } `); BufferList.prototype[method] = gen.toFunction(); }); const fixedWriteMethods = { writeDoubleBE: 8, writeDoubleLE: 8, writeFloatBE: 4, writeFloatLE: 4, writeInt32BE: 4, writeInt32LE: 4, writeUInt32BE: 4, writeUInt32LE: 4, writeInt16BE: 2, writeInt16LE: 2, writeUInt16BE: 2, writeUInt16LE: 2, writeInt8: 1, writeUInt8: 1, }; Object.keys(fixedWriteMethods).forEach(method => { const gen = createFunction(); gen(` function bufferlist_${method}(value) { const size = ${gen.formats.d(fixedWriteMethods[method])}; const buf = Buffer.allocUnsafe(size); buf.${method}(value, 0); this.append(buf); } `); BufferList.prototype[method] = gen.toFunction(); }); const metaWriteMethods = [ 'writeIntBE', 'writeIntLE', 'writeUIntBE', 'writeUIntLE', ]; metaWriteMethods.forEach(method => { const gen = createFunction(); gen(` function bufferlist_${method}(value, size) { const buf = Buffer.allocUnsafe(size); buf.${method}(value, 0, size); this.append(buf); } `); BufferList.prototype[method] = gen.toFunction(); }); module.exports = BufferList;