diff --git a/index.js b/index.js index 059480b..3436419 100644 --- a/index.js +++ b/index.js @@ -3,12 +3,6 @@ var Buffer = require('safe-buffer').Buffer var Transform = require('readable-stream').Transform var inherits = require('inherits') -function throwIfNotStringOrBuffer (val, prefix) { - if (!Buffer.isBuffer(val) && typeof val !== 'string') { - throw new TypeError(prefix + ' must be a string or a buffer') - } -} - function HashBase (blockSize) { Transform.call(this) @@ -44,10 +38,59 @@ HashBase.prototype._flush = function (callback) { callback(error) } +var useUint8Array = typeof Uint8Array !== 'undefined' +var useArrayBuffer = typeof ArrayBuffer !== 'undefined' && + typeof Uint8Array !== 'undefined' && + ArrayBuffer.isView && + (Buffer.prototype instanceof Uint8Array || Buffer.TYPED_ARRAY_SUPPORT) + +function toBuffer (data, encoding) { + // No need to do anything for exact instance + // This is only valid when safe-buffer.Buffer === buffer.Buffer, i.e. when Buffer.from/Buffer.alloc existed + if (data instanceof Buffer) return data + + // Convert strings to Buffer + if (typeof data === 'string') return Buffer.from(data, encoding) + + /* + * Wrap any TypedArray instances and DataViews + * Makes sense only on engines with full TypedArray support -- let Buffer detect that + */ + if (useArrayBuffer && ArrayBuffer.isView(data)) { + if (data.byteLength === 0) return Buffer.alloc(0) // Bug in Node.js <6.3.1, which treats this as out-of-bounds + var res = Buffer.from(data.buffer, data.byteOffset, data.byteLength) + // Recheck result size, as offset/length doesn't work on Node.js <5.10 + // We just go to Uint8Array case if this fails + if (res.byteLength === data.byteLength) return res + } + + /* + * Uint8Array in engines where Buffer.from might not work with ArrayBuffer, just copy over + * Doesn't make sense with other TypedArray instances + */ + if (useUint8Array && data instanceof Uint8Array) return Buffer.from(data) + + /* + * Old Buffer polyfill on an engine that doesn't have TypedArray support + * Also, this is from a different Buffer polyfill implementation then we have, as instanceof check failed + * Convert to our current Buffer implementation + */ + if ( + Buffer.isBuffer(data) && + data.constructor && + typeof data.constructor.isBuffer === 'function' && + data.constructor.isBuffer(data) + ) { + return Buffer.from(data) + } + + throw new TypeError('The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView.') +} + HashBase.prototype.update = function (data, encoding) { - throwIfNotStringOrBuffer(data, 'Data') if (this._finalized) throw new Error('Digest already called') - if (!Buffer.isBuffer(data)) data = Buffer.from(data, encoding) + + data = toBuffer(data, encoding) // asserts correct input type // consume data var block = this._block diff --git a/test/index.js b/test/index.js index 6dd163e..3871f87 100644 --- a/test/index.js +++ b/test/index.js @@ -68,7 +68,7 @@ test('HashBase#update', function (t) { var base = new HashBase(64) t.throws(function () { base.update(null) - }, /^TypeError: Data must be a string or a buffer$/) + }, /^TypeError: The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView.$/) t.end() }) @@ -132,6 +132,25 @@ test('HashBase#update', function (t) { t.end() }) + t.test( + 'handle UInt16Array', + { + skip: !( + ArrayBuffer.isView && + (Buffer.prototype instanceof Uint8Array || Buffer.TYPED_ARRAY_SUPPORT) + ) && 'ArrayBuffer.isView and TypedArray fully supported' + }, + function (t) { + var base = new HashBase(64) + + base._update = noop + base.update(new Uint16Array([1234, 512])) + t.same(base._block.slice(0, base._blockOffset), Buffer.from('d2040002', 'hex')) + + t.end() + } + ) + t.end() })