diff --git a/CHANGELOG.md b/CHANGELOG.md index 87e70a1..73b46c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,14 @@ # Change Log +## [1.2.3] - 2021-07-09 + +* Improve [browser compatibility][pr#29] by explicitly requiring the + `buffer` module so that bundlers can replace it with a polyfill for browser apps. +* Improve [browser compatibility][pr#29] by loosening types from Buffer to Uint8Array. + ## [1.2.2] - 2021-07-05 -* Improve [browser compatibility][pr#26] by eliminating a dependence on +* Improve [browser compatibility][pr#27] by eliminating a dependence on the Node assert module. ## [1.2.1] - 2021-04-29 diff --git a/lib/Layout.js b/lib/Layout.js index 1e9b06c..28c50f9 100644 --- a/lib/Layout.js +++ b/lib/Layout.js @@ -22,7 +22,7 @@ */ /** - * Support for translating between Buffer instances and JavaScript + * Support for translating between Uint8Array instances and JavaScript * native types. * * {@link module:Layout~Layout|Layout} is the basis of a class @@ -132,6 +132,39 @@ 'use strict'; +var Buffer; +if (typeof window !== 'undefined' && typeof window.Buffer !== 'undefined') { + Buffer = window.Buffer; +} else { + Buffer = require('buffer').Buffer; +} + +/* Check if a value is a Uint8Array. + * + * @ignore */ +function checkUint8Array(b) { + if (!(b instanceof Uint8Array)) { + throw new TypeError('b must be a Uint8Array'); + } +} +exports.checkUint8Array = checkUint8Array; + +/* Create a Buffer instance from a Uint8Array. + * + * @ignore */ +function uint8ArrayToBuffer(b) { + checkUint8Array(b); + let buffer = new Buffer(b.buffer, b.byteOffset, b.length); + + // Node v4 doesn't respect byteOffset and length arguments + if (buffer.length > b.length) { + buffer = buffer.slice(b.byteOffset, b.byteOffset + b.length); + } + + return buffer; +} +exports.uint8ArrayToBuffer = uint8ArrayToBuffer; + /** * Base class for layout objects. * @@ -196,9 +229,9 @@ class Layout { } /** - * Decode from a Buffer into an JavaScript value. + * Decode from a Uint8Array into a JavaScript value. * - * @param {Buffer} b - the buffer from which encoded data is read. + * @param {Uint8Array} b - the buffer from which encoded data is read. * * @param {Number} [offset] - the offset at which the encoded data * starts. If absent a zero offset is inferred. @@ -212,13 +245,13 @@ class Layout { } /** - * Encode a JavaScript value into a Buffer. + * Encode a JavaScript value into a Uint8Array. * * @param {(Number|Array|Object)} src - the value to be encoded into * the buffer. The type accepted depends on the (sub-)type of {@link * Layout}. * - * @param {Buffer} b - the buffer into which encoded data will be + * @param {Uint8Array} b - the buffer into which encoded data will be * written. * * @param {Number} [offset] - the offset at which the encoded data @@ -240,7 +273,7 @@ class Layout { /** * Calculate the span of a specific instance of a layout. * - * @param {Buffer} b - the buffer that contains an encoded instance. + * @param {Uint8Array} b - the buffer that contains an encoded instance. * * @param {Number} [offset] - the offset at which the encoded instance * starts. If absent a zero offset is inferred. @@ -456,6 +489,7 @@ class GreedyCount extends ExternalLayout { /** @override */ decode(b, offset) { + checkUint8Array(b); if (undefined === offset) { offset = 0; } @@ -510,7 +544,7 @@ class OffsetLayout extends ExternalLayout { * start of another layout. * * The value may be positive or negative, but an error will thrown - * if at the point of use it goes outside the span of the Buffer + * if at the point of use it goes outside the span of the Uint8Array * being accessed. */ this.offset = offset; } @@ -567,6 +601,7 @@ class UInt extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); return b.readUIntLE(offset, this.span); } @@ -575,6 +610,7 @@ class UInt extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); b.writeUIntLE(src, offset, this.span); return this.span; } @@ -609,6 +645,7 @@ class UIntBE extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); return b.readUIntBE(offset, this.span); } @@ -617,6 +654,7 @@ class UIntBE extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); b.writeUIntBE(src, offset, this.span); return this.span; } @@ -651,6 +689,7 @@ class Int extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); return b.readIntLE(offset, this.span); } @@ -659,6 +698,7 @@ class Int extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); b.writeIntLE(src, offset, this.span); return this.span; } @@ -693,6 +733,7 @@ class IntBE extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); return b.readIntBE(offset, this.span); } @@ -701,6 +742,7 @@ class IntBE extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); b.writeIntBE(src, offset, this.span); return this.span; } @@ -741,6 +783,7 @@ class NearUInt64 extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); const lo32 = b.readUInt32LE(offset); const hi32 = b.readUInt32LE(offset + 4); return roundedInt64(hi32, lo32); @@ -752,6 +795,7 @@ class NearUInt64 extends Layout { offset = 0; } const split = divmodInt64(src); + b = uint8ArrayToBuffer(b); b.writeUInt32LE(split.lo32, offset); b.writeUInt32LE(split.hi32, offset + 4); return 8; @@ -779,6 +823,7 @@ class NearUInt64BE extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); const hi32 = b.readUInt32BE(offset); const lo32 = b.readUInt32BE(offset + 4); return roundedInt64(hi32, lo32); @@ -790,6 +835,7 @@ class NearUInt64BE extends Layout { offset = 0; } const split = divmodInt64(src); + b = uint8ArrayToBuffer(b); b.writeUInt32BE(split.hi32, offset); b.writeUInt32BE(split.lo32, offset + 4); return 8; @@ -817,6 +863,7 @@ class NearInt64 extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); const lo32 = b.readUInt32LE(offset); const hi32 = b.readInt32LE(offset + 4); return roundedInt64(hi32, lo32); @@ -828,6 +875,7 @@ class NearInt64 extends Layout { offset = 0; } const split = divmodInt64(src); + b = uint8ArrayToBuffer(b); b.writeUInt32LE(split.lo32, offset); b.writeInt32LE(split.hi32, offset + 4); return 8; @@ -855,6 +903,7 @@ class NearInt64BE extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); const hi32 = b.readInt32BE(offset); const lo32 = b.readUInt32BE(offset + 4); return roundedInt64(hi32, lo32); @@ -866,6 +915,7 @@ class NearInt64BE extends Layout { offset = 0; } const split = divmodInt64(src); + b = uint8ArrayToBuffer(b); b.writeInt32BE(split.hi32, offset); b.writeUInt32BE(split.lo32, offset + 4); return 8; @@ -892,6 +942,7 @@ class Float extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); return b.readFloatLE(offset); } @@ -900,6 +951,7 @@ class Float extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); b.writeFloatLE(src, offset); return 4; } @@ -925,6 +977,7 @@ class FloatBE extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); return b.readFloatBE(offset); } @@ -933,6 +986,7 @@ class FloatBE extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); b.writeFloatBE(src, offset); return 4; } @@ -958,6 +1012,7 @@ class Double extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); return b.readDoubleLE(offset); } @@ -966,6 +1021,7 @@ class Double extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); b.writeDoubleLE(src, offset); return 8; } @@ -991,6 +1047,7 @@ class DoubleBE extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); return b.readDoubleBE(offset); } @@ -999,6 +1056,7 @@ class DoubleBE extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); b.writeDoubleBE(src, offset); return 8; } @@ -1225,6 +1283,7 @@ class Structure extends Layout { /** @override */ decode(b, offset) { + checkUint8Array(b); if (undefined === offset) { offset = 0; } @@ -1746,17 +1805,17 @@ class Union extends Layout { * If `vb` does not produce a registered variant the function returns * `undefined`. * - * @param {(Number|Buffer)} vb - either the variant number, or a + * @param {(Number|Uint8Array)} vb - either the variant number, or a * buffer from which the discriminator is to be read. * * @param {Number} offset - offset into `vb` for the start of the - * union. Used only when `vb` is an instance of {Buffer}. + * union. Used only when `vb` is an instance of {Uint8Array}. * * @return {({VariantLayout}|undefined)} */ getVariant(vb, offset) { let variant = vb; - if (Buffer.isBuffer(vb)) { + if (vb instanceof Uint8Array) { if (undefined === offset) { offset = 0; } @@ -2251,7 +2310,7 @@ class Boolean extends BitField { /** * Contain a fixed-length block of arbitrary data, represented as a - * Buffer. + * Uint8Array. * * *Factory*: {@link module:Layout.blob|blob} * @@ -2303,6 +2362,7 @@ class Blob extends Layout { if (0 > span) { span = this.length.decode(b, offset); } + b = uint8ArrayToBuffer(b); return b.slice(offset, offset + span); } @@ -2316,14 +2376,15 @@ class Blob extends Layout { if (this.length instanceof ExternalLayout) { span = src.length; } - if (!(Buffer.isBuffer(src) - && (span === src.length))) { + if (!(src instanceof Uint8Array && span === src.length)) { throw new TypeError(nameWithProperty('Blob.encode', this) - + ' requires (length ' + span + ') Buffer as src'); + + ' requires (length ' + span + ') Uint8Array as src'); } if ((offset + span) > b.length) { - throw new RangeError('encoding overruns Buffer'); + throw new RangeError('encoding overruns Uint8Array'); } + b = uint8ArrayToBuffer(b); + src = uint8ArrayToBuffer(src); b.write(src.toString('hex'), offset, span, 'hex'); if (this.length instanceof ExternalLayout) { this.length.encode(span, b, offset); @@ -2352,9 +2413,7 @@ class CString extends Layout { /** @override */ getSpan(b, offset) { - if (!Buffer.isBuffer(b)) { - throw new TypeError('b must be a Buffer'); - } + checkUint8Array(b); if (undefined === offset) { offset = 0; } @@ -2371,6 +2430,7 @@ class CString extends Layout { offset = 0; } let span = this.getSpan(b, offset); + b = uint8ArrayToBuffer(b); return b.slice(offset, offset + span - 1).toString('utf-8'); } @@ -2387,6 +2447,7 @@ class CString extends Layout { } const srcb = new Buffer(src, 'utf8'); const span = srcb.length; + b = uint8ArrayToBuffer(b); if ((offset + span) > b.length) { throw new RangeError('encoding overruns Buffer'); } @@ -2443,9 +2504,7 @@ class UTF8 extends Layout { /** @override */ getSpan(b, offset) { - if (!Buffer.isBuffer(b)) { - throw new TypeError('b must be a Buffer'); - } + checkUint8Array(b); if (undefined === offset) { offset = 0; } @@ -2462,6 +2521,7 @@ class UTF8 extends Layout { && (this.maxSpan < span)) { throw new RangeError('text length exceeds maxSpan'); } + b = uint8ArrayToBuffer(b); return b.slice(offset, offset + span).toString('utf-8'); } @@ -2482,6 +2542,7 @@ class UTF8 extends Layout { && (this.maxSpan < span)) { throw new RangeError('text length exceeds maxSpan'); } + b = uint8ArrayToBuffer(b); if ((offset + span) > b.length) { throw new RangeError('encoding overruns Buffer'); } diff --git a/package.json b/package.json index d61017a..c3514c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "buffer-layout", - "version": "1.2.2", + "version": "1.2.3", "description": "Translation between JavaScript values and Buffers", "keywords": [ "Buffer", diff --git a/test/LayoutTest.js b/test/LayoutTest.js index ebc7576..3bb0875 100644 --- a/test/LayoutTest.js +++ b/test/LayoutTest.js @@ -1499,17 +1499,17 @@ suite('Layout', function() { assert.throws(() => new lo.BitField(bs, 40), Error); }); suite('ctor argument processing', function() { - it('should infer property when passed string', function() { + test('should infer property when passed string', function() { const bs = new lo.BitStructure(lo.u8(), 'flags'); assert.strictEqual(bs.msb, false); assert.strictEqual(bs.property, 'flags'); }); - it('should respect msb without property', function() { + test('should respect msb without property', function() { const bs = new lo.BitStructure(lo.u8(), true); assert.strictEqual(bs.msb, true); assert.strictEqual(bs.property, undefined); }); - it('should accept msb with property', function() { + test('should accept msb with property', function() { const bs = new lo.BitStructure(lo.u8(), 'flags', 'flags'); assert.strictEqual(bs.msb, true); assert.strictEqual(bs.property, 'flags'); @@ -1761,6 +1761,24 @@ suite('Layout', function() { assert.equal(bl.span, 3); assert.equal(bl.property, 'bl'); }); + test('uint8array', function() { + const bl = new lo.Blob(3, 'bl'); + const a = new Uint8Array([1, 2, 3, 4, 5]); + let bv = bl.decode(a); + assert(bv instanceof Buffer); + assert.equal(bv.length, bl.span); + assert.equal(Buffer.from('010203', 'hex').compare(bv), 0); + bv = bl.decode(a, 2); + assert.equal(bl.getSpan(a), bl.span); + const src = new Uint8Array(Buffer.from('112233', 'hex')); + assert.equal(Buffer.from('030405', 'hex').compare(bv), 0); + assert.equal(bl.encode(src, a, 1), 3); + const b = lo.uint8ArrayToBuffer(a) + assert.equal(Buffer.from('0111223305', 'hex').compare(b), 0); + assert.throws(() => bl.encode('ABC', a), Error); + assert.throws(() => bl.encode(Buffer.from('0102', 'hex'), a), + Error); + }); test('basics', function() { const bl = new lo.Blob(3, 'bl'); const b = Buffer.from('0102030405', 'hex');