From 0ccc8682fff58e39bf33695fe52079661310c328 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Fri, 9 Jul 2021 11:32:15 -0500 Subject: [PATCH] Layout: Accept Uint8Array type wherever the API expects a Buffer Loosen Buffer type checks to accept the Uint8Array base class. Uint8Array is preferred over Buffer due to its presence in both Node.js and browser environments. When Buffer methods are needed, create a new Buffer instance which uses the same underyling memory of the Uint8Array parameter. Signed-off-by: Justin Starry --- lib/Layout.js | 89 +++++++++++++++++++++++++++++++++++----------- test/LayoutTest.js | 18 ++++++++++ 2 files changed, 86 insertions(+), 21 deletions(-) diff --git a/lib/Layout.js b/lib/Layout.js index c2a15e6..9a18786 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 @@ -140,6 +140,25 @@ if ('undefined' !== typeof window && 'undefined' !== typeof window.Buffer) { 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); + return Buffer.from(b.buffer, b.byteOffset, b.length); +} +exports.uint8ArrayToBuffer = uint8ArrayToBuffer; + /** * Base class for layout objects. * @@ -204,9 +223,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. @@ -220,13 +239,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 @@ -248,7 +267,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. @@ -464,6 +483,7 @@ class GreedyCount extends ExternalLayout { /** @override */ decode(b, offset) { + checkUint8Array(b); if (undefined === offset) { offset = 0; } @@ -518,7 +538,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; } @@ -575,6 +595,7 @@ class UInt extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); return b.readUIntLE(offset, this.span); } @@ -583,6 +604,7 @@ class UInt extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); b.writeUIntLE(src, offset, this.span); return this.span; } @@ -617,6 +639,7 @@ class UIntBE extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); return b.readUIntBE(offset, this.span); } @@ -625,6 +648,7 @@ class UIntBE extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); b.writeUIntBE(src, offset, this.span); return this.span; } @@ -659,6 +683,7 @@ class Int extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); return b.readIntLE(offset, this.span); } @@ -667,6 +692,7 @@ class Int extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); b.writeIntLE(src, offset, this.span); return this.span; } @@ -701,6 +727,7 @@ class IntBE extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); return b.readIntBE(offset, this.span); } @@ -709,6 +736,7 @@ class IntBE extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); b.writeIntBE(src, offset, this.span); return this.span; } @@ -749,6 +777,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); @@ -760,6 +789,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; @@ -787,6 +817,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); @@ -798,6 +829,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; @@ -825,6 +857,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); @@ -836,6 +869,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; @@ -863,6 +897,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); @@ -874,6 +909,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; @@ -900,6 +936,7 @@ class Float extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); return b.readFloatLE(offset); } @@ -908,6 +945,7 @@ class Float extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); b.writeFloatLE(src, offset); return 4; } @@ -933,6 +971,7 @@ class FloatBE extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); return b.readFloatBE(offset); } @@ -941,6 +980,7 @@ class FloatBE extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); b.writeFloatBE(src, offset); return 4; } @@ -966,6 +1006,7 @@ class Double extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); return b.readDoubleLE(offset); } @@ -974,6 +1015,7 @@ class Double extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); b.writeDoubleLE(src, offset); return 8; } @@ -999,6 +1041,7 @@ class DoubleBE extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); return b.readDoubleBE(offset); } @@ -1007,6 +1050,7 @@ class DoubleBE extends Layout { if (undefined === offset) { offset = 0; } + b = uint8ArrayToBuffer(b); b.writeDoubleBE(src, offset); return 8; } @@ -1233,6 +1277,7 @@ class Structure extends Layout { /** @override */ decode(b, offset) { + checkUint8Array(b); if (undefined === offset) { offset = 0; } @@ -1754,17 +1799,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; } @@ -2259,7 +2304,7 @@ class Boolean extends BitField { /** * Contain a fixed-length block of arbitrary data, represented as a - * Buffer. + * Uint8Array. * * *Factory*: {@link module:Layout.blob|blob} * @@ -2311,6 +2356,7 @@ class Blob extends Layout { if (0 > span) { span = this.length.decode(b, offset); } + b = uint8ArrayToBuffer(b); return b.slice(offset, offset + span); } @@ -2324,14 +2370,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); @@ -2360,9 +2407,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; } @@ -2379,6 +2424,7 @@ class CString extends Layout { offset = 0; } const span = this.getSpan(b, offset); + b = uint8ArrayToBuffer(b); return b.slice(offset, offset + span - 1).toString('utf-8'); } @@ -2395,6 +2441,7 @@ class CString extends Layout { } const srcb = Buffer.from(src, 'utf8'); const span = srcb.length; + b = uint8ArrayToBuffer(b); if ((offset + span) > b.length) { throw new RangeError('encoding overruns Buffer'); } @@ -2451,9 +2498,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; } @@ -2470,6 +2515,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'); } @@ -2490,6 +2536,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/test/LayoutTest.js b/test/LayoutTest.js index 802abba..51b58dc 100644 --- a/test/LayoutTest.js +++ b/test/LayoutTest.js @@ -1762,6 +1762,24 @@ suite('Layout', function() { assert.equal(bl.property, 'bl'); }); test('basics', 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('Buffer', function() { const bl = new lo.Blob(3, 'bl'); const b = Buffer.from('0102030405', 'hex'); let bv = bl.decode(b);