From 83d7d9e6d81fc35764a9000b6d7b17a0e7d8cc92 Mon Sep 17 00:00:00 2001 From: Yazhong Liu Date: Thu, 2 Oct 2014 16:24:58 -0700 Subject: [PATCH] buffer: add generic functions for (u)int ops Add generic functions for (U)Int read/write operations on Buffers. These support up to and including 48 bit reads and writes. Include documentation and tests. Additional work done by Trevor Norris to include 40 and 48 bit write support. Because bitwise operations cannot be used on values greater than 32 bits, the operations have been replaced with mathematical calculations. Regardless, they are still faster than floating point operations. Reviewed-by: Trevor Norris --- doc/api/buffer.markdown | 42 +++++++++++ lib/buffer.js | 151 +++++++++++++++++++++++++++++++++++++ test/simple/test-buffer.js | 87 ++++++++++++++++++++- 3 files changed, 278 insertions(+), 2 deletions(-) diff --git a/doc/api/buffer.markdown b/doc/api/buffer.markdown index fb86accebb..e4ba04ed7f 100644 --- a/doc/api/buffer.markdown +++ b/doc/api/buffer.markdown @@ -180,6 +180,48 @@ The method will not write partial characters. len = buf.write('\u00bd + \u00bc = \u00be', 0); console.log(len + " bytes: " + buf.toString('utf8', 0, len)); +### buf.writeUIntLE(value, offset, byteLength[, noAssert]) +### buf.writeUIntBE(value, offset, byteLength[, noAssert]) +### buf.writeIntLE(value, offset, byteLength[, noAssert]) +### buf.writeIntBE(value, offset, byteLength[, noAssert]) + +* `value` {Number} Bytes to be written to buffer +* `offset` {Number} `0 <= offset <= buf.length` +* `byteLength` {Number} `0 < byteLength <= 6` +* `noAssert` {Boolean} Default: false +* Return: {Number} + +Writes `value` to the buffer at the specified `offset` and `byteLength`. +Supports up to 48 bits of accuracy. For example: + + var b = new Buffer(6); + b.writeUIntBE(0x1234567890ab, 0, 6); + // + +Set `noAssert` to `true` to skip validation of `value` and `offset`. Defaults +to `false`. + +### buf.readUIntLE(offset, byteLength[, noAssert]) +### buf.readUIntBE(offset, byteLength[, noAssert]) +### buf.readIntLE(offset, byteLength[, noAssert]) +### buf.readIntBE(offset, byteLength[, noAssert]) + +* `offset` {Number} `0 <= offset <= buf.length` +* `byteLength` {Number} `0 < byteLength <= 6` +* `noAssert` {Boolean} Default: false +* Return: {Number} + +A generalized version of all numeric read methods. Supports up to 48 bits of +accuracy. For example: + + var b = new Buffer(6); + b.writeUint16LE(0x90ab, 0); + b.writeUInt32LE(0x12345678, 2); + b.readUIntLE(0, 6).toString(16); // Specify 6 bytes (48 bits) + // output: '1234567890ab' + +Set `noAssert` to true to skip validation of `offset`. This means that `offset` +may be beyond the end of the buffer. Defaults to `false`. ### buf.toString([encoding][, start][, end]) diff --git a/lib/buffer.js b/lib/buffer.js index 80f6a4271f..a674991988 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -482,6 +482,37 @@ function checkOffset(offset, ext, length) { } +Buffer.prototype.readUIntLE = function(offset, byteLength, noAssert) { + offset = offset >>> 0; + byteLength = byteLength >>> 0; + if (!noAssert) + checkOffset(offset, byteLength, this.length); + + var val = this[offset]; + var mul = 1; + var i = 0; + while (++i < byteLength && (mul *= 0x100)) + val += this[offset + i] * mul; + + return val; +}; + + +Buffer.prototype.readUIntBE = function(offset, byteLength, noAssert) { + offset = offset >>> 0; + byteLength = byteLength >>> 0; + if (!noAssert) + checkOffset(offset, byteLength, this.length); + + var val = this[offset + --byteLength]; + var mul = 1; + while (byteLength > 0 && (mul *= 0x100)) + val += this[offset + --byteLength] * mul; + + return val; +}; + + Buffer.prototype.readUInt8 = function(offset, noAssert) { offset = offset >>> 0; if (!noAssert) @@ -530,6 +561,46 @@ Buffer.prototype.readUInt32BE = function(offset, noAssert) { }; +Buffer.prototype.readIntLE = function(offset, byteLength, noAssert) { + offset = offset >>> 0; + byteLength = byteLength >>> 0; + if (!noAssert) + checkOffset(offset, byteLength, this.length); + + var val = this[offset]; + var mul = 1; + var i = 0; + while (++i < byteLength && (mul *= 0x100)) + val += this[offset + i] * mul; + mul *= 0x80; + + if (val >= mul) + val -= Math.pow(2, 8 * byteLength); + + return val; +}; + + +Buffer.prototype.readIntBE = function(offset, byteLength, noAssert) { + offset = offset >>> 0; + byteLength = byteLength >>> 0; + if (!noAssert) + checkOffset(offset, byteLength, this.length); + + var i = byteLength; + var mul = 1; + var val = this[offset + --i]; + while (i > 0 && (mul *= 0x100)) + val += this[offset + --i] * mul; + mul *= 0x80; + + if (val >= mul) + val -= Math.pow(2, 8 * byteLength); + + return val; +}; + + Buffer.prototype.readInt8 = function(offset, noAssert) { offset = offset >>> 0; if (!noAssert) @@ -623,6 +694,40 @@ function checkInt(buffer, value, offset, ext, max, min) { } +Buffer.prototype.writeUIntLE = function(value, offset, byteLength, noAssert) { + value = +value; + offset = offset >>> 0; + byteLength = byteLength >>> 0; + if (!noAssert) + checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0); + + var mul = 1; + var i = 0; + this[offset] = value; + while (++i < byteLength && (mul *= 0x100)) + this[offset + i] = (value / mul) >>> 0; + + return offset + byteLength; +}; + + +Buffer.prototype.writeUIntBE = function(value, offset, byteLength, noAssert) { + value = +value; + offset = offset >>> 0; + byteLength = byteLength >>> 0; + if (!noAssert) + checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0); + + var i = byteLength - 1; + var mul = 1; + this[offset + i] = value; + while (--i >= 0 && (mul *= 0x100)) + this[offset + i] = (value / mul) >>> 0; + + return offset + byteLength; +}; + + Buffer.prototype.writeUInt8 = function(value, offset, noAssert) { value = +value; offset = offset >>> 0; @@ -681,6 +786,52 @@ Buffer.prototype.writeUInt32BE = function(value, offset, noAssert) { }; +Buffer.prototype.writeIntLE = function(value, offset, byteLength, noAssert) { + value = +value; + offset = offset >>> 0; + if (!noAssert) { + checkInt(this, + value, + offset, + byteLength, + Math.pow(2, 8 * byteLength - 1) - 1, + -Math.pow(2, 8 * byteLength - 1)); + } + + var i = 0; + var mul = 1; + var sub = value < 0 ? 1 : 0; + this[offset] = value; + while (++i < byteLength && (mul *= 0x100)) + this[offset + i] = ((value / mul) >> 0) - sub; + + return offset + byteLength; +}; + + +Buffer.prototype.writeIntBE = function(value, offset, byteLength, noAssert) { + value = +value; + offset = offset >>> 0; + if (!noAssert) { + checkInt(this, + value, + offset, + byteLength, + Math.pow(2, 8 * byteLength - 1) - 1, + -Math.pow(2, 8 * byteLength - 1)); + } + + var i = byteLength - 1; + var mul = 1; + var sub = value < 0 ? 1 : 0; + this[offset + i] = value; + while (--i >= 0 && (mul *= 0x100)) + this[offset + i] = ((value / mul) >> 0) - sub; + + return offset + byteLength; +}; + + Buffer.prototype.writeInt8 = function(value, offset, noAssert) { value = +value; offset = offset >>> 0; diff --git a/test/simple/test-buffer.js b/test/simple/test-buffer.js index 4219a15be5..bf742f9348 100644 --- a/test/simple/test-buffer.js +++ b/test/simple/test-buffer.js @@ -950,8 +950,6 @@ var buf = new Buffer([0xFF]); assert.equal(buf.readUInt8(0), 255); assert.equal(buf.readInt8(0), -1); - - [16, 32].forEach(function(bits) { var buf = new Buffer(bits / 8 - 1); @@ -988,6 +986,91 @@ assert.equal(buf.readInt8(0), -1); (0xFFFFFFFF >> (32 - bits))); }); +// test for common read(U)IntLE/BE +(function() { + var buf = new Buffer([0x01, 0x02, 0x03, 0x04, 0x05, 0x06]); + + assert.equal(buf.readUIntLE(0, 1), 0x01); + assert.equal(buf.readUIntBE(0, 1), 0x01); + assert.equal(buf.readUIntLE(0, 3), 0x030201); + assert.equal(buf.readUIntBE(0, 3), 0x010203); + assert.equal(buf.readUIntLE(0, 5), 0x0504030201); + assert.equal(buf.readUIntBE(0, 5), 0x0102030405); + assert.equal(buf.readUIntLE(0, 6), 0x060504030201); + assert.equal(buf.readUIntBE(0, 6), 0x010203040506); + assert.equal(buf.readIntLE(0, 1), 0x01); + assert.equal(buf.readIntBE(0, 1), 0x01); + assert.equal(buf.readIntLE(0, 3), 0x030201); + assert.equal(buf.readIntBE(0, 3), 0x010203); + assert.equal(buf.readIntLE(0, 5), 0x0504030201); + assert.equal(buf.readIntBE(0, 5), 0x0102030405); + assert.equal(buf.readIntLE(0, 6), 0x060504030201); + assert.equal(buf.readIntBE(0, 6), 0x010203040506); +})(); + +// test for common write(U)IntLE/BE +(function() { + var buf = new Buffer(3); + buf.writeUIntLE(0x123456, 0, 3); + assert.deepEqual(buf.toJSON().data, [0x56, 0x34, 0x12]); + assert.equal(buf.readUIntLE(0, 3), 0x123456); + + buf = new Buffer(3); + buf.writeUIntBE(0x123456, 0, 3); + assert.deepEqual(buf.toJSON().data, [0x12, 0x34, 0x56]); + assert.equal(buf.readUIntBE(0, 3), 0x123456); + + buf = new Buffer(3); + buf.writeIntLE(0x123456, 0, 3); + assert.deepEqual(buf.toJSON().data, [0x56, 0x34, 0x12]); + assert.equal(buf.readIntLE(0, 3), 0x123456); + + buf = new Buffer(3); + buf.writeIntBE(0x123456, 0, 3); + assert.deepEqual(buf.toJSON().data, [0x12, 0x34, 0x56]); + assert.equal(buf.readIntBE(0, 3), 0x123456); + + buf = new Buffer(3); + buf.writeIntLE(-0x123456, 0, 3); + assert.deepEqual(buf.toJSON().data, [0xaa, 0xcb, 0xed]); + assert.equal(buf.readIntLE(0, 3), -0x123456); + + buf = new Buffer(3); + buf.writeIntBE(-0x123456, 0, 3); + assert.deepEqual(buf.toJSON().data, [0xed, 0xcb, 0xaa]); + assert.equal(buf.readIntBE(0, 3), -0x123456); + + buf = new Buffer(5); + buf.writeUIntLE(0x1234567890, 0, 5); + assert.deepEqual(buf.toJSON().data, [0x90, 0x78, 0x56, 0x34, 0x12]); + assert.equal(buf.readUIntLE(0, 5), 0x1234567890); + + buf = new Buffer(5); + buf.writeUIntBE(0x1234567890, 0, 5); + assert.deepEqual(buf.toJSON().data, [0x12, 0x34, 0x56, 0x78, 0x90]); + assert.equal(buf.readUIntBE(0, 5), 0x1234567890); + + buf = new Buffer(5); + buf.writeIntLE(0x1234567890, 0, 5); + assert.deepEqual(buf.toJSON().data, [0x90, 0x78, 0x56, 0x34, 0x12]); + assert.equal(buf.readIntLE(0, 5), 0x1234567890); + + buf = new Buffer(5); + buf.writeIntBE(0x1234567890, 0, 5); + assert.deepEqual(buf.toJSON().data, [0x12, 0x34, 0x56, 0x78, 0x90]); + assert.equal(buf.readIntBE(0, 5), 0x1234567890); + + buf = new Buffer(5); + buf.writeIntLE(-0x1234567890, 0, 5); + assert.deepEqual(buf.toJSON().data, [0x70, 0x87, 0xa9, 0xcb, 0xed]); + assert.equal(buf.readIntLE(0, 5), -0x1234567890); + + buf = new Buffer(5); + buf.writeIntBE(-0x1234567890, 0, 5); + assert.deepEqual(buf.toJSON().data, [0xed, 0xcb, 0xa9, 0x87, 0x70]); + assert.equal(buf.readIntBE(0, 5), -0x1234567890); +})(); + // test Buffer slice (function() { var buf = new Buffer('0123456789');