Skip to content

Commit

Permalink
feat(UintArray): Adds support for Uint8Arrays
Browse files Browse the repository at this point in the history
All API calls can now handle taking in a Uint8Array instead of
a buffer. Consumers will still need to globally provide
a polyfill for Buffer, as it is still used internally.
  • Loading branch information
daprahamian authored Jun 19, 2018
1 parent 7f8d1b6 commit 2a54053
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 35 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ The BSON `serializeWithBufferAndIndex` method takes an object, a target buffer i

* `BSON.serializeWithBufferAndIndex(object, buffer, options)`
* @param {Object} object the JavaScript object to serialize.
* @param {Buffer} buffer the Buffer you pre-allocated to store the serialized BSON object.
* @param {Buffer|Uint8Array} buffer the Buffer you pre-allocated to store the serialized BSON object.
* @param {Boolean} [options.checkKeys=false] the serializer will check if keys are valid.
* @param {Boolean} [options.serializeFunctions=false] serialize the JavaScript functions.
* @param {Boolean} [options.ignoreUndefined=true] ignore undefined fields.
Expand Down Expand Up @@ -128,7 +128,7 @@ The BSON `deserialize` method takes a Node.js Buffer and an optional options obj
The BSON `deserializeStream` method takes a Node.js Buffer, `startIndex` and allow more control over deserialization of a Buffer containing concatenated BSON documents.

* `BSON.deserializeStream(buffer, startIndex, numberOfDocuments, documents, docStartIndex, options)`
* @param {Buffer} buffer the buffer containing the serialized set of BSON documents.
* @param {Buffer|Uint8Array} buffer the buffer containing the serialized set of BSON documents.
* @param {Number} startIndex the start index in the data Buffer where the deserialization is to start.
* @param {Number} numberOfDocuments number of documents to deserialize.
* @param {Array} documents an array where to store the deserialized documents.
Expand Down
13 changes: 10 additions & 3 deletions lib/bson/bson.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ var deserialize = require('./parser/deserializer'),
serializer = require('./parser/serializer'),
calculateObjectSize = require('./parser/calculate_size');

const ensureBuffer = require('./ensure_buffer');

/**
* @ignore
* @api private
Expand Down Expand Up @@ -82,7 +84,7 @@ BSON.prototype.serialize = function serialize(object, options) {
* Serialize a Javascript object using a predefined Buffer and index into the buffer, useful when pre-allocating the space for serialization.
*
* @param {Object} object the Javascript object to serialize.
* @param {Buffer} buffer the Buffer you pre-allocated to store the serialized BSON object.
* @param {Buffer|Uint8Array} buffer the Buffer you pre-allocated to store the serialized BSON object.
* @param {Boolean} [options.checkKeys] the serializer will check if keys are valid.
* @param {Boolean} [options.serializeFunctions=false] serialize the javascript functions **(default:false)**.
* @param {Boolean} [options.ignoreUndefined=true] ignore undefined fields **(default:true)**.
Expand Down Expand Up @@ -110,6 +112,9 @@ BSON.prototype.serializeWithBufferAndIndex = function(object, finalBuffer, optio
serializeFunctions,
ignoreUndefined
);

finalBuffer = ensureBuffer(finalBuffer);

buffer.copy(finalBuffer, startIndex, 0, serializationIndex);

// Return the index
Expand All @@ -119,7 +124,7 @@ BSON.prototype.serializeWithBufferAndIndex = function(object, finalBuffer, optio
/**
* Deserialize data as BSON.
*
* @param {Buffer} buffer the buffer containing the serialized set of BSON documents.
* @param {Buffer|Uint8Array} buffer the buffer containing the serialized set of BSON documents.
* @param {Object} [options.evalFunctions=false] evaluate functions in the BSON document scoped to the object deserialized.
* @param {Object} [options.cacheFunctions=false] cache evaluated functions for reuse.
* @param {Object} [options.cacheFunctionsCrc32=false] use a crc32 code for caching, otherwise use the string of the function.
Expand All @@ -133,6 +138,7 @@ BSON.prototype.serializeWithBufferAndIndex = function(object, finalBuffer, optio
* @api public
*/
BSON.prototype.deserialize = function(buffer, options) {
buffer = ensureBuffer(buffer);
return deserialize(buffer, options);
};

Expand All @@ -159,7 +165,7 @@ BSON.prototype.calculateObjectSize = function(object, options) {
/**
* Deserialize stream data as BSON documents.
*
* @param {Buffer} data the buffer containing the serialized set of BSON documents.
* @param {Buffer|Uint8Array} data the buffer containing the serialized set of BSON documents.
* @param {Number} startIndex the start index in the data Buffer where the deserialization is to start.
* @param {Number} numberOfDocuments number of documents to deserialize.
* @param {Array} documents an array where to store the deserialized documents.
Expand All @@ -185,6 +191,7 @@ BSON.prototype.deserializeStream = function(
options
) {
options = Object.assign({ allowObjectSmallerThanBufferSize: true }, options);
data = ensureBuffer(data);
var index = startIndex;
// Loop over all documents
for (var i = 0; i < numberOfDocuments; i++) {
Expand Down
20 changes: 20 additions & 0 deletions lib/bson/ensure_buffer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict';

/**
* Makes sure that, if a Uint8Array is passed in, it is wrapped in a Buffer.
*
* @param {Buffer|Uint8Array} potentialBuffer The potential buffer
* @returns {Buffer} the input if potentialBuffer is a buffer, or a buffer that
* wraps a passed in Uint8Array
* @throws {TypeError} If anything other than a Buffer or Uint8Array is passed in
*/
module.exports = function ensureBuffer(potentialBuffer) {
if (potentialBuffer instanceof Buffer) {
return potentialBuffer;
}
if (potentialBuffer instanceof Uint8Array) {
return new Buffer(potentialBuffer.buffer);
}

throw new TypeError('Must use either Buffer or Uint8Array');
};
89 changes: 59 additions & 30 deletions test/node/bson_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@ var ISODate = function(string) {
} else throw new Error('Invalid ISO 8601 date given.', __filename);
};

function runTestsOnBytesForBufferAndUint8Array(bytes, testFn) {
let serialized_data = '';
// Convert to chars
for (let i = 0; i < bytes.length; i++) {
serialized_data = serialized_data + BinaryParser.fromByte(bytes[i]);
}

const uint8Array = Uint8Array.from(bytes);
const buffer = new Buffer(serialized_data, 'binary');

[uint8Array, buffer].forEach(testFn);
}

describe('BSON', function() {
/**
* @ignore
Expand Down Expand Up @@ -224,16 +237,19 @@ describe('BSON', function() {
0,
0
];
var serialized_data = '';
// Convert to chars
for (var i = 0; i < bytes.length; i++) {
serialized_data = serialized_data + BinaryParser.fromByte(bytes[i]);
}

var object = createBSON().deserialize(new Buffer(serialized_data, 'binary'));
expect('a_1').to.equal(object.name);
expect(false).to.equal(object.unique);
expect(1).to.equal(object.key.a);
runTestsOnBytesForBufferAndUint8Array(bytes, data => {
let object = createBSON().deserialize(data);
expect('a_1').to.equal(object.name);
expect(false).to.equal(object.unique);
expect(1).to.equal(object.key.a);

object = createBSON().deserialize(Uint8Array.from(bytes));
expect('a_1').to.equal(object.name);
expect(false).to.equal(object.unique);
expect(1).to.equal(object.key.a);
});

done();
});

Expand Down Expand Up @@ -525,29 +541,26 @@ describe('BSON', function() {
0,
0
];
var serialized_data = '';

// Convert to chars
for (var i = 0; i < bytes.length; i++) {
serialized_data = serialized_data + BinaryParser.fromByte(bytes[i]);
}
runTestsOnBytesForBufferAndUint8Array(bytes, data => {
const object = createBSON().deserialize(data);
// Perform tests
expect('hello').to.equal(object.string);
expect([1, 2, 3]).to.deep.equal(object.array);
expect(1).to.equal(object.hash.a);
expect(2).to.equal(object.hash.b);
expect(object.date != null).to.be.ok;
expect(object.oid != null).to.be.ok;
expect(object.binary != null).to.be.ok;
expect(42).to.equal(object.int);
expect(33.3333).to.equal(object.float);
expect(object.regexp != null).to.be.ok;
expect(true).to.equal(object.boolean);
expect(object.where != null).to.be.ok;
expect(object.dbref != null).to.be.ok;
expect(object[null] == null).to.be.ok;
});

var object = createBSON().deserialize(new Buffer(serialized_data, 'binary'));
// Perform tests
expect('hello').to.equal(object.string);
expect([1, 2, 3]).to.deep.equal(object.array);
expect(1).to.equal(object.hash.a);
expect(2).to.equal(object.hash.b);
expect(object.date != null).to.be.ok;
expect(object.oid != null).to.be.ok;
expect(object.binary != null).to.be.ok;
expect(42).to.equal(object.int);
expect(33.3333).to.equal(object.float);
expect(object.regexp != null).to.be.ok;
expect(true).to.equal(object.boolean);
expect(object.where != null).to.be.ok;
expect(object.dbref != null).to.be.ok;
expect(object[null] == null).to.be.ok;
done();
});

Expand Down Expand Up @@ -2317,4 +2330,20 @@ describe('BSON', function() {
expect(false).to.equal(id.equals(undefined));
done();
});

it('Should serialize the same values to a Buffer and a Uint8Array', function() {
const testData = { darmok: 'jalad' };

const dataLength = createBSON().serialize(testData).length;
const buffer = new Buffer(dataLength);
const uint8Array = new Uint8Array(dataLength);

createBSON().serializeWithBufferAndIndex(testData, buffer);
createBSON().serializeWithBufferAndIndex(testData, uint8Array);

const bufferRaw = Array.prototype.slice.call(buffer, 0);
const uint8ArrayRaw = Array.prototype.slice.call(uint8Array, 0);

expect(bufferRaw).to.deep.equal(uint8ArrayRaw);
});
});
61 changes: 61 additions & 0 deletions test/node/ensure_buffer_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use strict';

const ensureBuffer = require('../../lib/bson/ensure_buffer');
const expect = require('chai').expect;

describe('ensureBuffer tests', function() {
it('should be a function', function() {
expect(ensureBuffer).to.be.a('function');
});

it('should return the exact same buffer if a buffer is passed in', function() {
const bufferIn = new Buffer(10);
let bufferOut;

expect(function() {
bufferOut = ensureBuffer(bufferIn);
}).to.not.throw(Error);

expect(bufferOut).to.equal(bufferIn);
});

it('should wrap a UInt8Array with a buffer', function() {
const arrayIn = Uint8Array.from([1, 2, 3]);
let bufferOut;

expect(function() {
bufferOut = ensureBuffer(arrayIn);
}).to.not.throw(Error);

expect(bufferOut).to.be.an.instanceOf(Buffer);
expect(bufferOut.buffer).to.equal(arrayIn.buffer);
});

[0, 12, -1, '', 'foo', null, undefined, ['list'], {}, /x/].forEach(function(item) {
it(`should throw if input is ${typeof item}: ${item}`, function() {
expect(function() {
ensureBuffer(item);
}).to.throw(TypeError);
});
});

[
/* eslint-disable */
Int8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array
/* eslint-enable */
].forEach(function(TypedArray) {
it(`should throw if input is typed array ${TypedArray.name}`, function() {
const typedArray = new TypedArray();
expect(function() {
ensureBuffer(typedArray);
}).to.throw(TypeError);
});
});
});

0 comments on commit 2a54053

Please sign in to comment.