From 6e77012f7f72ce9e5a2148add9fe213a12cc5400 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Tue, 15 Sep 2020 16:22:33 -0400 Subject: [PATCH 01/10] refactor(Buffer): :recycle: Consolidate code based on Buffer Presence Update code to rely on Buffer class presence, removes redundant code to handle supporting typed arrays internally TypedArrays are serialized as BSON Binary --- package.json | 1 + src/binary.ts | 236 +++++------------- src/ensure_buffer.ts | 12 +- src/fnv1a.ts | 1 + src/map.ts | 1 + src/objectid.ts | 189 ++++++-------- src/parser/calculate_size.ts | 4 +- src/parser/deserializer.ts | 19 +- src/parser/serializer.ts | 15 +- src/parser/utils.ts | 24 +- test/node/bson_test.js | 101 +------- test/node/ensure_buffer_test.js | 6 +- test/node/promote_values_test.js | 286 +-------------------- test/node/test_full_bson.js | 411 +++---------------------------- tsconfig.json | 5 +- 15 files changed, 213 insertions(+), 1098 deletions(-) diff --git a/package.json b/package.json index e49ac91e..cc6de204 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ ], "files": [ "lib", + "src", "dist", "bson.d.ts", "etc/prepare.js", diff --git a/src/binary.ts b/src/binary.ts index a9031f05..8272fd24 100644 --- a/src/binary.ts +++ b/src/binary.ts @@ -1,6 +1,7 @@ import { Buffer } from 'buffer'; +import { ensureBuffer } from './ensure_buffer'; import type { EJSONOptions } from './extended_json'; -import { haveBuffer, isBuffer, isUint8Array } from './parser/utils'; +import type { BufferEncoding } from './parser/utils'; type BinarySequence = Uint8Array | Buffer | number[]; @@ -20,6 +21,12 @@ export type BinaryEJSON = export class Binary { _bsontype!: 'Binary'; + /** + * Binary default subtype + * @internal + */ + private static readonly BSON_BINARY_SUBTYPE_DEFAULT = 0; + /** Initial buffer default size */ static readonly BUFFER_SIZE = 256; /** Default BSON type */ @@ -37,7 +44,7 @@ export class Binary { /** User BSON type */ static readonly SUBTYPE_USER_DEFINED = 128; - buffer: BinarySequence; + buffer: Buffer; sub_type: number; position: number; @@ -45,43 +52,37 @@ export class Binary { * @param buffer - a buffer object containing the binary data. * @param subType - the option binary type. */ - constructor(buffer: string | BinarySequence, subType?: number) { + constructor(buffer?: string | BinarySequence, subType?: number) { if ( - buffer != null && + !(buffer === undefined || buffer === null) && !(typeof buffer === 'string') && - !Buffer.isBuffer(buffer) && - !(buffer instanceof Uint8Array) && + !ArrayBuffer.isView(buffer) && + !(buffer instanceof ArrayBuffer) && !Array.isArray(buffer) ) { - throw new TypeError('only String, Buffer, Uint8Array or Array accepted'); + throw new TypeError( + 'Binary can only be constructed from string, Buffer, TypedArray, or Array' + ); } - this.sub_type = subType == null ? BSON_BINARY_SUBTYPE_DEFAULT : subType; - this.position = 0; - - if (buffer != null && !(buffer instanceof Number)) { - // Only accept Buffer, Uint8Array or Arrays - if (typeof buffer === 'string') { - // Different ways of writing the length of the string for the different types - if (haveBuffer()) { - this.buffer = Buffer.from(buffer); - } else if (typeof Uint8Array !== 'undefined' || Array.isArray(buffer)) { - this.buffer = writeStringToArray(buffer); - } else { - throw new TypeError('only String, Buffer, Uint8Array or Array accepted'); - } - } else { - this.buffer = buffer; - } - this.position = buffer.length; + this.sub_type = subType ?? Binary.BSON_BINARY_SUBTYPE_DEFAULT; + + if (buffer === undefined || buffer === null) { + // create an empty binary buffer + this.buffer = Buffer.alloc(Binary.BUFFER_SIZE); + this.position = 0; + } else if (typeof buffer === 'string') { + // string + this.buffer = writeStringToArray(buffer); + this.position = this.buffer.byteLength; + } else if (Array.isArray(buffer)) { + // number[] + this.buffer = Buffer.from(buffer); + this.position = this.buffer.byteLength; } else { - if (haveBuffer()) { - this.buffer = Buffer.alloc(Binary.BUFFER_SIZE); - } else if (typeof Uint8Array !== 'undefined') { - this.buffer = new Uint8Array(new ArrayBuffer(Binary.BUFFER_SIZE)); - } else { - this.buffer = new Array(Binary.BUFFER_SIZE); - } + // Buffer | TypedArray | ArrayBuffer + this.buffer = ensureBuffer(buffer); + this.position = this.buffer.byteLength; } } @@ -114,32 +115,11 @@ export class Binary { if (this.buffer.length > this.position) { this.buffer[this.position++] = decodedByte; } else { - if (isBuffer(this.buffer)) { - // Create additional overflow buffer - const buffer = Buffer.alloc(Binary.BUFFER_SIZE + this.buffer.length); - // Combine the two buffers together - this.buffer.copy(buffer, 0, 0, this.buffer.length); - this.buffer = buffer; - this.buffer[this.position++] = decodedByte; - } else { - let buffer: Uint8Array | number[]; - // Create a new buffer (typed or normal array) - if (isUint8Array(this.buffer)) { - buffer = new Uint8Array(new ArrayBuffer(Binary.BUFFER_SIZE + this.buffer.length)); - } else { - buffer = new Array(Binary.BUFFER_SIZE + this.buffer.length); - } - - // We need to copy all the content to the new array - for (let i = 0; i < this.buffer.length; i++) { - buffer[i] = this.buffer[i]; - } - - // Reassign the buffer - this.buffer = buffer; - // Write the byte - this.buffer[this.position++] = decodedByte; - } + const buffer = Buffer.alloc(Binary.BUFFER_SIZE + this.buffer.length); + // Combine the two buffers together + this.buffer.copy(buffer, 0, 0, this.buffer.length); + this.buffer = buffer; + this.buffer[this.position++] = decodedByte; } } @@ -154,50 +134,21 @@ export class Binary { // If the buffer is to small let's extend the buffer if (this.buffer.length < offset + sequence.length) { - let buffer: Buffer | Uint8Array | null = null; - // If we are in node.js - if (isBuffer(this.buffer)) { - buffer = Buffer.alloc(this.buffer.length + sequence.length); - this.buffer.copy(buffer, 0, 0, this.buffer.length); - } else if (isUint8Array(this.buffer)) { - // Create a new buffer - buffer = new Uint8Array(new ArrayBuffer(this.buffer.length + sequence.length)); - // Copy the content - for (let i = 0; i < this.position; i++) { - buffer[i] = this.buffer[i]; - } - } + const buffer = Buffer.alloc(this.buffer.length + sequence.length); + this.buffer.copy(buffer, 0, 0, this.buffer.length); // Assign the new buffer - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.buffer = buffer!; + this.buffer = buffer; } - if (isBuffer(sequence) && isBuffer(this.buffer)) { - sequence.copy(this.buffer, offset, 0, sequence.length); + if (ArrayBuffer.isView(sequence)) { + this.buffer.set(ensureBuffer(sequence), offset); this.position = - offset + sequence.length > this.position ? offset + sequence.length : this.position; - // offset = string.length - } else if (typeof sequence === 'string' && isBuffer(this.buffer)) { + offset + sequence.byteLength > this.position ? offset + sequence.length : this.position; + } else if (typeof sequence === 'string') { this.buffer.write(sequence, offset, sequence.length, 'binary'); this.position = offset + sequence.length > this.position ? offset + sequence.length : this.position; - // offset = string.length; - } else if ( - isUint8Array(sequence) || - (Array.isArray(sequence) && typeof sequence !== 'string') - ) { - for (let i = 0; i < sequence.length; i++) { - this.buffer[offset++] = sequence[i]; - } - - this.position = offset > this.position ? offset : this.position; - } else if (typeof sequence === 'string') { - for (let i = 0; i < sequence.length; i++) { - this.buffer[offset++] = sequence.charCodeAt(i); - } - - this.position = offset > this.position ? offset : this.position; } } @@ -211,21 +162,7 @@ export class Binary { length = length && length > 0 ? length : this.position; // Let's return the data based on the type we have - if (this.buffer['slice']) { - return this.buffer.slice(position, position + length); - } - - // Create a buffer to keep the result - const buffer = - typeof Uint8Array !== 'undefined' - ? new Uint8Array(new ArrayBuffer(length)) - : new Array(length); - for (let i = 0; i < length; i++) { - buffer[i] = this.buffer[position++]; - } - - // Return the buffer - return buffer; + return this.buffer.slice(position, position + length); } /** @@ -238,36 +175,15 @@ export class Binary { asRaw = !!asRaw; // Optimize to serialize for the situation where the data == size of buffer - if (asRaw && isBuffer(this.buffer) && this.buffer.length === this.position) return this.buffer; + if (asRaw && this.buffer.length === this.position) { + return this.buffer; + } // If it's a node.js buffer object - if (isBuffer(this.buffer)) { - return asRaw - ? this.buffer.slice(0, this.position) - : this.buffer.toString('binary', 0, this.position); - } else { - if (asRaw) { - // we support the slice command use it - if (this.buffer['slice'] != null) { - return this.buffer.slice(0, this.position); - } else { - // Create a new buffer to copy content to - const newBuffer = isUint8Array(this.buffer) - ? new Uint8Array(new ArrayBuffer(this.position)) - : new Array(this.position); - - // Copy content - for (let i = 0; i < this.position; i++) { - newBuffer[i] = this.buffer[i]; - } - - // Return the buffer - return newBuffer; - } - } else { - return convertArraytoUtf8BinaryString(this.buffer, 0, this.position); - } + if (asRaw) { + return this.buffer.slice(0, this.position); } + return this.buffer.toString('binary', 0, this.position); } /** the length of the binary sequence */ @@ -277,24 +193,18 @@ export class Binary { /** @internal */ toJSON(): string { - if (!this.buffer) return ''; - const buffer = Buffer.from(this.buffer as Uint8Array); - return buffer.toString('base64'); + return this.buffer.toString('base64'); } /** @internal */ toString(format: BufferEncoding): string { - if (!this.buffer) return ''; - const buffer = Buffer.from(this.buffer.slice(0, this.position) as Uint8Array); - return buffer.toString(format); + return this.buffer.slice(0, this.position).toString(format); } /** @internal */ toExtendedJSON(options?: EJSONOptions): BinaryEJSON { options = options || {}; - const base64String = Buffer.isBuffer(this.buffer) - ? this.buffer.toString('base64') - : Buffer.from(this.buffer as Uint8Array).toString('base64'); + const base64String = this.buffer.toString('base64'); const subType = Number(this.sub_type).toString(16); if (options.legacy) { @@ -312,14 +222,11 @@ export class Binary { } /** @internal */ - static fromExtendedJSON( - doc: { $type: string; $binary: string | { subType: string; base64: string } }, - options?: EJSONOptions - ): Binary { + static fromExtendedJSON(doc: BinaryEJSON, options?: EJSONOptions): Binary { options = options || {}; let data: Buffer | undefined; let type; - if (options.legacy && typeof doc.$binary === 'string') { + if (options.legacy && '$type' in doc) { type = doc.$type ? parseInt(doc.$type, 16) : 0; data = Buffer.from(doc.$binary, 'base64'); } else { @@ -335,19 +242,10 @@ export class Binary { } } -/** - * Binary default subtype - * @internal - */ -const BSON_BINARY_SUBTYPE_DEFAULT = 0; - /** @internal */ function writeStringToArray(data: string) { // Create a buffer - const buffer = - typeof Uint8Array !== 'undefined' - ? new Uint8Array(new ArrayBuffer(data.length)) - : new Array(data.length); + const buffer = Buffer.alloc(data.length); // Write the content to the buffer for (let i = 0; i < data.length; i++) { @@ -357,22 +255,4 @@ function writeStringToArray(data: string) { return buffer; } -/** - * Convert Array ot Uint8Array to Binary String - * - * @internal - */ -function convertArraytoUtf8BinaryString( - byteArray: number[] | Uint8Array, - startIndex: number, - endIndex: number -) { - let result = ''; - for (let i = startIndex; i < endIndex; i++) { - result = result + String.fromCharCode(byteArray[i]); - } - - return result; -} - Object.defineProperty(Binary.prototype, '_bsontype', { value: 'Binary' }); diff --git a/src/ensure_buffer.ts b/src/ensure_buffer.ts index 8be84c79..0c9a897e 100644 --- a/src/ensure_buffer.ts +++ b/src/ensure_buffer.ts @@ -8,14 +8,18 @@ import { Buffer } from 'buffer'; * wraps a passed in Uint8Array * @throws TypeError If anything other than a Buffer or Uint8Array is passed in */ -export function ensureBuffer(potentialBuffer: Buffer | Uint8Array): Buffer { - if (potentialBuffer instanceof Buffer) { +export function ensureBuffer(potentialBuffer: Buffer | ArrayBufferView | ArrayBuffer): Buffer { + if (Buffer.isBuffer(potentialBuffer)) { return potentialBuffer; } - if (potentialBuffer instanceof Uint8Array) { + if (ArrayBuffer.isView(potentialBuffer)) { return Buffer.from(potentialBuffer.buffer); } - throw new TypeError('Must use either Buffer or Uint8Array'); + if (potentialBuffer instanceof ArrayBuffer) { + return Buffer.from(potentialBuffer); + } + + throw new TypeError('Must use either Buffer or TypedArray'); } diff --git a/src/fnv1a.ts b/src/fnv1a.ts index 13982adf..719939da 100644 --- a/src/fnv1a.ts +++ b/src/fnv1a.ts @@ -1,5 +1,6 @@ import { Buffer } from 'buffer'; import { Long } from './long'; +import type { BufferEncoding } from './parser/utils'; const MASK_8 = 0xff; const MASK_24 = 0xffffff; diff --git a/src/map.ts b/src/map.ts index 1b544463..2323ee37 100644 --- a/src/map.ts +++ b/src/map.ts @@ -4,6 +4,7 @@ /* We do not want to have to include DOM types just for this check */ declare const window: unknown; declare const self: unknown; +declare const global: unknown; let bsonMap: MapConstructor; diff --git a/src/objectid.ts b/src/objectid.ts index 51f56848..f2a5880a 100644 --- a/src/objectid.ts +++ b/src/objectid.ts @@ -1,6 +1,10 @@ import { Buffer } from 'buffer'; -import { deprecate, inspect } from 'util'; -import { haveBuffer, randomBytes } from './parser/utils'; +import { ensureBuffer } from './ensure_buffer'; +import { BufferEncoding, haveBuffer, randomBytes } from './parser/utils'; + +declare const require: Function; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { deprecate, inspect } = require('util'); // constants const PROCESS_UNIQUE = randomBytes(5); @@ -20,21 +24,9 @@ let i = 0; while (i < 10) decodeLookup[0x30 + i] = i++; while (i < 16) decodeLookup[0x41 - 10 + i] = decodeLookup[0x61 - 10 + i] = i++; -function convertToHex(bytes: Buffer): string { - return bytes.toString('hex'); -} - -function makeObjectIdError(invalidString: string, index: number) { - const invalidCharacter = invalidString[index]; - return new TypeError( - `ObjectId string "${invalidString}" contains invalid character "${invalidCharacter}" with character code (${invalidString.charCodeAt( - index - )}). All character codes for a non-hex string must be less than 256.` - ); -} - export interface ObjectIdLike { id: string | Buffer; + __id?: string; toHexString(): string; } @@ -47,9 +39,12 @@ export class ObjectId { /** @internal */ static index = ~~(Math.random() * 0xffffff); - static cacheHexString?: boolean; - id: string | Buffer; - __id?: string; + static cacheHexString: boolean; + + /** ObjectId Bytes @internal */ + private _id: Buffer; + /** ObjectId hexString cache @internal */ + private __id?: string; /** * Create an ObjectId type @@ -59,67 +54,77 @@ export class ObjectId { constructor(id?: string | Buffer | number | ObjectIdLike | ObjectId) { // Duck-typing to support ObjectId from different npm packages if (id instanceof ObjectId) { - this.id = id.id; + this._id = id.id; this.__id = id.__id; - return; + } + + if (typeof id === 'object' && 'id' in id) { + if ('toHexString' in id && typeof id.toHexString === 'function') { + this._id = Buffer.from(id.toHexString(), 'hex'); + } else { + this._id = typeof id.id === 'string' ? Buffer.from(id.id) : id.id; + } } // The most common use case (blank id, new objectId instance) if (id == null || typeof id === 'number') { // Generate a new id - this.id = ObjectId.generate(typeof id === 'number' ? id : undefined); + this._id = ObjectId.generate(typeof id === 'number' ? id : undefined); // If we are caching the hex string - if (ObjectId.cacheHexString) this.__id = this.toString('hex'); - // Return the object - return; + if (ObjectId.cacheHexString) { + this.__id = this.id.toString('hex'); + } } - // Check if the passed in id is valid - const valid = ObjectId.isValid(id); + if (ArrayBuffer.isView(id) && id.byteLength === 12) { + this._id = ensureBuffer(id); + } - // Throw an error if it's not a valid setup - if (!valid) { + if (typeof id === 'string') { + if (id.length === 12) { + const bytes = Buffer.from(id); + if (bytes.byteLength === 12) { + this._id = bytes; + } + } + if (id.length === 24 && checkForHexRegExp.test(id)) { + this._id = Buffer.from(id, 'hex'); + } + } + + if (typeof this['_id'] === 'undefined') { throw new TypeError( - 'Argument passed in must be a single String of 12 bytes or a string of 24 hex characters' - ); - } else if (valid && typeof id === 'string' && id.length === 24 && haveBuffer()) { - this.id = Buffer.from(id, 'hex'); - } else if (valid && typeof id === 'string' && id.length === 24) { - this.id = ObjectId.createFromHexString(id).id; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } else if ((id as any)['length'] === 12) { - // assume 12 byte string - this.id = id as string | Buffer; - } else if ( - typeof id !== 'string' && - 'toHexString' in id && - typeof id.toHexString === 'function' - ) { - // Duck-typing to support ObjectId from different npm packages - this.id = ObjectId.createFromHexString(id.toHexString()).id; - } else { - throw new TypeError( - 'Argument passed in must be a single String of 12 bytes or a string of 24 hex characters' + 'Argument passed in must be a Buffer or string of 12 bytes or a string of 24 hex characters' ); } - if (ObjectId.cacheHexString) this.__id = this.toString('hex'); + if (ObjectId.cacheHexString) { + this.__id = this.id.toString('hex'); + } + } + + /** + * The ObjectId bytes + * @readonly + */ + get id(): Buffer { + return this._id; + } + + /** @internal */ + set id(value: Buffer) { + this._id = value; + if (ObjectId.cacheHexString) { + this.__id = value.toString('hex'); + } } /** * The generation time of this ObjectId instance - * @deprecated Please use getTimestamp / createFromTime which returns a Date + * @deprecated Please use getTimestamp / createFromTime which returns an int32 epoch */ get generationTime(): number { - if (typeof this.id === 'string') { - return ( - this.id.charCodeAt(3) | - (this.id.charCodeAt(2) << 8) | - (this.id.charCodeAt(1) << 16) | - (this.id.charCodeAt(0) << 24) - ); - } - return this.id[3] | (this.id[2] << 8) | (this.id[1] << 16) | (this.id[0] << 24); + return this.id.readInt32BE(0, false); } /** @@ -128,50 +133,21 @@ export class ObjectId { */ set generationTime(value: number) { // Encode time into first 4 bytes - const bytes = [value & 0xff, (value >> 8) & 0xff, (value >> 16) & 0xff, (value >> 24) & 0xff]; - if (typeof this.id === 'string') { - let result = ''; - for (const byte of bytes) { - result += String.fromCharCode(byte); - } - result += this.id.slice(4); - this.id = result; - return; - } - this.id[3] = value & 0xff; - this.id[2] = (value >> 8) & 0xff; - this.id[1] = (value >> 16) & 0xff; - this.id[0] = (value >> 24) & 0xff; + this.id.writeUInt32BE(value, 0, false); } /** Returns the ObjectId id as a 24 character hex string representation */ toHexString(): string { - if (ObjectId.cacheHexString && this.__id) return this.__id; - - let hexString = ''; - if (!this.id || !this.id.length) { - throw new TypeError( - 'invalid ObjectId, ObjectId.id must be either a string or a Buffer, but is [' + - JSON.stringify(this.id) + - ']' - ); + if (ObjectId.cacheHexString && this.__id) { + return this.__id; } - if (this.id instanceof Buffer) { - hexString = convertToHex(this.id); - if (ObjectId.cacheHexString) this.__id = hexString; - return hexString; - } + const hexString = this.id.toString('hex'); - for (let i = 0; i < this.id.length; i++) { - const hexChar = hexTable[this.id.charCodeAt(i)]; - if (typeof hexChar !== 'string') { - throw makeObjectIdError(this.id, i); - } - hexString += hexChar; + if (ObjectId.cacheHexString && !this.__id) { + this.__id = hexString; } - if (ObjectId.cacheHexString) this.__id = hexString; return hexString; } @@ -199,10 +175,7 @@ export class ObjectId { const buffer = Buffer.alloc(12); // 4-byte timestamp - buffer[3] = time & 0xff; - buffer[2] = (time >> 8) & 0xff; - buffer[1] = (time >> 16) & 0xff; - buffer[0] = (time >> 24) & 0xff; + buffer.writeUInt32BE(time, 0, false); // 5-byte process unique buffer[4] = PROCESS_UNIQUE[0]; @@ -227,11 +200,7 @@ export class ObjectId { */ toString(format?: BufferEncoding): string { // Is the id a buffer then use the buffer toString method to return the format - if (this.id && typeof this.id !== 'string' && 'copy' in (this.id as Buffer)) { - return this.id.toString(typeof format === 'string' ? format : 'hex'); - } - - return this.toHexString(); + return this.id.toString(typeof format === 'string' ? format : 'hex'); } /** @@ -270,7 +239,7 @@ export class ObjectId { } if (typeof otherId === 'string' && ObjectId.isValid(otherId) && otherId.length === 12) { - return otherId === this.id; + return Buffer.from(otherId).equals(this.id); } if ( @@ -287,12 +256,7 @@ export class ObjectId { /** Returns the generation date (accurate up to the second) that this ID was generated. */ getTimestamp(): Date { const timestamp = new Date(); - let time: number; - if (typeof this.id !== 'string') { - time = this.id.readUInt32BE(0); - } else { - time = this.generationTime; - } + const time = this.id.readUInt32BE(0, false); timestamp.setTime(Math.floor(time) * 1000); return timestamp; } @@ -310,10 +274,7 @@ export class ObjectId { static createFromTime(time: number): ObjectId { const buffer = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); // Encode time into first 4 bytes - buffer[3] = time & 0xff; - buffer[2] = (time >> 8) & 0xff; - buffer[1] = (time >> 16) & 0xff; - buffer[0] = (time >> 24) & 0xff; + buffer.writeUInt32BE(time, 0, false); // Return the new objectId return new ObjectId(buffer); } diff --git a/src/parser/calculate_size.ts b/src/parser/calculate_size.ts index 36dc3162..3cd1aa1c 100644 --- a/src/parser/calculate_size.ts +++ b/src/parser/calculate_size.ts @@ -83,9 +83,9 @@ function calculateElement( return (name != null ? Buffer.byteLength(name, 'utf8') + 1 : 0) + (12 + 1); } else if (value instanceof Date || isDate(value)) { return (name != null ? Buffer.byteLength(name, 'utf8') + 1 : 0) + (8 + 1); - } else if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) { + } else if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) { return ( - (name != null ? Buffer.byteLength(name, 'utf8') + 1 : 0) + (1 + 4 + 1) + value.length + (name != null ? Buffer.byteLength(name, 'utf8') + 1 : 0) + (1 + 4 + 1) + value.byteLength ); } else if ( value['_bsontype'] === 'Long' || diff --git a/src/parser/deserializer.ts b/src/parser/deserializer.ts index 6bbabba1..12db117d 100644 --- a/src/parser/deserializer.ts +++ b/src/parser/deserializer.ts @@ -75,9 +75,7 @@ export function deserialize( if (size + index > buffer.length) { throw new Error( - `(bson size ${size} + options.index ${index} must be <= buffer length ${Buffer.byteLength( - buffer - )})` + `(bson size ${size} + options.index ${index} must be <= buffer length ${buffer.byteLength})` ); } @@ -156,7 +154,7 @@ function deserializeObject( } // If are at the end of the buffer there is a problem with the document - if (i >= Buffer.byteLength(buffer)) throw new Error('Bad BSON Document: illegal CString'); + if (i >= buffer.byteLength) throw new Error('Bad BSON Document: illegal CString'); const name = isArray ? arrayIndex++ : buffer.toString('utf8', index, i); index = i + 1; @@ -318,7 +316,7 @@ function deserializeObject( if (binarySize < 0) throw new Error('Negative binary type element size found'); // Is the length longer than the document - if (binarySize > Buffer.byteLength(buffer)) + if (binarySize > buffer.byteLength) throw new Error('Binary type size larger than document size'); // Decode as raw Buffer object if options specifies it @@ -344,10 +342,7 @@ function deserializeObject( object[name] = new Binary(buffer.slice(index, index + binarySize), subType); } } else { - const _buffer = - typeof Uint8Array !== 'undefined' - ? new Uint8Array(new ArrayBuffer(binarySize)) - : new Array(binarySize); + const _buffer = Buffer.alloc(binarySize); // If we have subtype 2 skip the 4 bytes for the size if (subType === Binary.SUBTYPE_BYTE_ARRAY) { binarySize = @@ -613,11 +608,7 @@ function deserializeObject( object[name] = new DBRef(namespace, oid); } else { throw new Error( - 'Detected unknown BSON type ' + - elementType.toString(16) + - ' for fieldname "' + - name + - '", are you using the latest BSON parser?' + 'Detected unknown BSON type ' + elementType.toString(16) + ' for fieldname "' + name + '"' ); } } diff --git a/src/parser/serializer.ts b/src/parser/serializer.ts index 78704810..cdd9b10d 100644 --- a/src/parser/serializer.ts +++ b/src/parser/serializer.ts @@ -1,4 +1,4 @@ -import { Buffer } from 'buffer'; +import type { Buffer } from 'buffer'; import { Binary } from '../binary'; import type { BSONSymbol, DBRef, Document, MaxKey } from '../bson'; import type { Code } from '../code'; @@ -6,6 +6,7 @@ import * as constants from '../constants'; import type { DBRefLike } from '../db_ref'; import type { Decimal128 } from '../decimal128'; import type { Double } from '../double'; +import { ensureBuffer } from '../ensure_buffer'; import { isBSONType } from '../extended_json'; import { writeIEEE754 } from '../float_parser'; import type { Int32 } from '../int_32'; @@ -354,7 +355,7 @@ function serializeObjectId( function serializeBuffer( buffer: Buffer, key: string, - value: Buffer, + value: Buffer | ArrayBuffer | ArrayBufferView, index: number, isArray?: boolean ) { @@ -368,7 +369,7 @@ function serializeBuffer( index = index + numberOfWrittenBytes; buffer[index++] = 0; // Get size of the buffer (current write point) - const size = value.length; + const size = value.byteLength; // Write the size of the string to buffer buffer[index++] = size & 0xff; buffer[index++] = (size >> 8) & 0xff; @@ -377,7 +378,7 @@ function serializeBuffer( // Write the default subtype buffer[index++] = constants.BSON_BINARY_SUBTYPE_DEFAULT; // Copy the content form the binary field to the buffer - value.copy(buffer, index, 0, size); + buffer.set(ensureBuffer(value), index); // Adjust the index index = index + size; return index; @@ -816,7 +817,7 @@ export function serializeInto( index = serializeNull(buffer, key, value, index, true); } else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') { index = serializeObjectId(buffer, key, value, index, true); - } else if (Buffer.isBuffer(value)) { + } else if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) { index = serializeBuffer(buffer, key, value, index, true); } else if (value instanceof RegExp || isRegExp(value)) { index = serializeRegExp(buffer, key, value, index, true); @@ -920,7 +921,7 @@ export function serializeInto( index = serializeNull(buffer, key, value, index); } else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') { index = serializeObjectId(buffer, key, value, index); - } else if (Buffer.isBuffer(value)) { + } else if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) { index = serializeBuffer(buffer, key, value, index); } else if (value instanceof RegExp || isRegExp(value)) { index = serializeRegExp(buffer, key, value, index); @@ -1024,7 +1025,7 @@ export function serializeInto( index = serializeNull(buffer, key, value, index); } else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') { index = serializeObjectId(buffer, key, value, index); - } else if (Buffer.isBuffer(value)) { + } else if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) { index = serializeBuffer(buffer, key, value, index); } else if (value instanceof RegExp || isRegExp(value)) { index = serializeRegExp(buffer, key, value, index); diff --git a/src/parser/utils.ts b/src/parser/utils.ts index 34bf8e8f..7146f8e0 100644 --- a/src/parser/utils.ts +++ b/src/parser/utils.ts @@ -1,3 +1,14 @@ +import { Buffer } from 'buffer'; +export type BufferEncoding = + | 'ascii' + | 'utf8' + | 'utf16le' + | 'ucs2' + | 'base64' + | 'latin1' + | 'binary' + | 'hex'; + /** * Normalizes our expected stringified form of a function across versions of node * @param fn - The function to stringify @@ -7,7 +18,7 @@ export function normalizedFunctionString(fn: Function): string { } function insecureRandomBytes(size: number): Uint8Array { - const result = new Uint8Array(size); + const result = Buffer.alloc(size); for (let i = 0; i < size; ++i) result[i] = Math.floor(Math.random() * 256); return result; } @@ -15,10 +26,11 @@ function insecureRandomBytes(size: number): Uint8Array { /* We do not want to have to include DOM types just for this check */ // eslint-disable-next-line @typescript-eslint/no-explicit-any declare let window: any; +declare let require: Function; export let randomBytes = insecureRandomBytes; if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) { - randomBytes = size => window.crypto.getRandomValues(new Uint8Array(size)); + randomBytes = size => window.crypto.getRandomValues(Buffer.alloc(size)); } else { try { // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -37,16 +49,14 @@ export function isUint8Array(value: unknown): value is Uint8Array { return Object.prototype.toString.call(value) === '[object Uint8Array]'; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +declare const global: any; + /** Call to check if your environment has `Buffer` */ export function haveBuffer(): boolean { return typeof global !== 'undefined' && typeof global.Buffer !== 'undefined'; } -/** Callable in any environment to check if value is a Buffer */ -export function isBuffer(value: unknown): value is Buffer { - return haveBuffer() && Buffer.isBuffer(value); -} - // To ensure that 0.4 of node works correctly export function isDate(d: unknown): d is Date { return isObjectLike(d) && Object.prototype.toString.call(d) === '[object Date]'; diff --git a/test/node/bson_test.js b/test/node/bson_test.js index e4938580..0dc4134d 100644 --- a/test/node/bson_test.js +++ b/test/node/bson_test.js @@ -99,103 +99,8 @@ describe('BSON', function () { * @ignore */ it('Should Correctly Deserialize object', function (done) { - var bytes = [ - 95, - 0, - 0, - 0, - 2, - 110, - 115, - 0, - 42, - 0, - 0, - 0, - 105, - 110, - 116, - 101, - 103, - 114, - 97, - 116, - 105, - 111, - 110, - 95, - 116, - 101, - 115, - 116, - 115, - 95, - 46, - 116, - 101, - 115, - 116, - 95, - 105, - 110, - 100, - 101, - 120, - 95, - 105, - 110, - 102, - 111, - 114, - 109, - 97, - 116, - 105, - 111, - 110, - 0, - 8, - 117, - 110, - 105, - 113, - 117, - 101, - 0, - 0, - 3, - 107, - 101, - 121, - 0, - 12, - 0, - 0, - 0, - 16, - 97, - 0, - 1, - 0, - 0, - 0, - 0, - 2, - 110, - 97, - 109, - 101, - 0, - 4, - 0, - 0, - 0, - 97, - 95, - 49, - 0, - 0 - ]; + // prettier-ignore + var bytes = [95, 0, 0, 0, 2, 110, 115, 0, 42, 0, 0, 0, 105, 110, 116, 101, 103, 114, 97, 116, 105, 111, 110, 95, 116, 101, 115, 116, 115, 95, 46, 116, 101, 115, 116, 95, 105, 110, 100, 101, 120, 95, 105, 110, 102, 111, 114, 109, 97, 116, 105, 111, 110, 0, 8, 117, 110, 105, 113, 117, 101, 0, 0, 3, 107, 101, 121, 0, 12, 0, 0, 0, 16, 97, 0, 1, 0, 0, 0, 0, 2, 110, 97, 109, 101, 0, 4, 0, 0, 0, 97, 95, 49, 0, 0]; let serialized_data = ''; // Convert to chars for (let i = 0; i < bytes.length; i++) { @@ -558,7 +463,7 @@ describe('BSON', function () { /** * @ignore */ - it('Should Correctly Serialize and Deserialize Integer', function (done) { + it('Should Correctly Serialize and Deserialize Integer 5', function (done) { var test_number = { doc: 5 }; var serialized_data = BSON.serialize(test_number); diff --git a/test/node/ensure_buffer_test.js b/test/node/ensure_buffer_test.js index be60c8e7..329df87d 100644 --- a/test/node/ensure_buffer_test.js +++ b/test/node/ensure_buffer_test.js @@ -40,7 +40,6 @@ describe('ensureBuffer tests', function () { }); [ - /* eslint-disable */ Int8Array, Uint8ClampedArray, Int16Array, @@ -49,13 +48,10 @@ describe('ensureBuffer tests', function () { 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); + expect(ensureBuffer(typedArray)).to.be.instanceOf(Buffer); }); }); }); diff --git a/test/node/promote_values_test.js b/test/node/promote_values_test.js index cc9c8890..e2df7bec 100644 --- a/test/node/promote_values_test.js +++ b/test/node/promote_values_test.js @@ -11,290 +11,8 @@ describe('promote values', function () { * @ignore */ it('Should Correctly Deserialize object with all wrapper types', function (done) { - var bytes = [ - 26, - 1, - 0, - 0, - 7, - 95, - 105, - 100, - 0, - 161, - 190, - 98, - 75, - 118, - 169, - 3, - 0, - 0, - 3, - 0, - 0, - 4, - 97, - 114, - 114, - 97, - 121, - 0, - 26, - 0, - 0, - 0, - 16, - 48, - 0, - 1, - 0, - 0, - 0, - 16, - 49, - 0, - 2, - 0, - 0, - 0, - 16, - 50, - 0, - 3, - 0, - 0, - 0, - 0, - 2, - 115, - 116, - 114, - 105, - 110, - 103, - 0, - 6, - 0, - 0, - 0, - 104, - 101, - 108, - 108, - 111, - 0, - 3, - 104, - 97, - 115, - 104, - 0, - 19, - 0, - 0, - 0, - 16, - 97, - 0, - 1, - 0, - 0, - 0, - 16, - 98, - 0, - 2, - 0, - 0, - 0, - 0, - 9, - 100, - 97, - 116, - 101, - 0, - 161, - 190, - 98, - 75, - 0, - 0, - 0, - 0, - 7, - 111, - 105, - 100, - 0, - 161, - 190, - 98, - 75, - 90, - 217, - 18, - 0, - 0, - 1, - 0, - 0, - 5, - 98, - 105, - 110, - 97, - 114, - 121, - 0, - 7, - 0, - 0, - 0, - 2, - 3, - 0, - 0, - 0, - 49, - 50, - 51, - 16, - 105, - 110, - 116, - 0, - 42, - 0, - 0, - 0, - 1, - 102, - 108, - 111, - 97, - 116, - 0, - 223, - 224, - 11, - 147, - 169, - 170, - 64, - 64, - 11, - 114, - 101, - 103, - 101, - 120, - 112, - 0, - 102, - 111, - 111, - 98, - 97, - 114, - 0, - 105, - 0, - 8, - 98, - 111, - 111, - 108, - 101, - 97, - 110, - 0, - 1, - 15, - 119, - 104, - 101, - 114, - 101, - 0, - 25, - 0, - 0, - 0, - 12, - 0, - 0, - 0, - 116, - 104, - 105, - 115, - 46, - 120, - 32, - 61, - 61, - 32, - 51, - 0, - 5, - 0, - 0, - 0, - 0, - 3, - 100, - 98, - 114, - 101, - 102, - 0, - 37, - 0, - 0, - 0, - 2, - 36, - 114, - 101, - 102, - 0, - 5, - 0, - 0, - 0, - 116, - 101, - 115, - 116, - 0, - 7, - 36, - 105, - 100, - 0, - 161, - 190, - 98, - 75, - 2, - 180, - 1, - 0, - 0, - 2, - 0, - 0, - 0, - 10, - 110, - 117, - 108, - 108, - 0, - 0 - ]; + // prettier-ignore + var bytes = [26, 1, 0, 0, 7, 95, 105, 100, 0, 161, 190, 98, 75, 118, 169, 3, 0, 0, 3, 0, 0, 4, 97, 114, 114, 97, 121, 0, 26, 0, 0, 0, 16, 48, 0, 1, 0, 0, 0, 16, 49, 0, 2, 0, 0, 0, 16, 50, 0, 3, 0, 0, 0, 0, 2, 115, 116, 114, 105, 110, 103, 0, 6, 0, 0, 0, 104, 101, 108, 108, 111, 0, 3, 104, 97, 115, 104, 0, 19, 0, 0, 0, 16, 97, 0, 1, 0, 0, 0, 16, 98, 0, 2, 0, 0, 0, 0, 9, 100, 97, 116, 101, 0, 161, 190, 98, 75, 0, 0, 0, 0, 7, 111, 105, 100, 0, 161, 190, 98, 75, 90, 217, 18, 0, 0, 1, 0, 0, 5, 98, 105, 110, 97, 114, 121, 0, 7, 0, 0, 0, 2, 3, 0, 0, 0, 49, 50, 51, 16, 105, 110, 116, 0, 42, 0, 0, 0, 1, 102, 108, 111, 97, 116, 0, 223, 224, 11, 147, 169, 170, 64, 64, 11, 114, 101, 103, 101, 120, 112, 0, 102, 111, 111, 98, 97, 114, 0, 105, 0, 8, 98, 111, 111, 108, 101, 97, 110, 0, 1, 15, 119, 104, 101, 114, 101, 0, 25, 0, 0, 0, 12, 0, 0, 0, 116, 104, 105, 115, 46, 120, 32, 61, 61, 32, 51, 0, 5, 0, 0, 0, 0, 3, 100, 98, 114, 101, 102, 0, 37, 0, 0, 0, 2, 36, 114, 101, 102, 0, 5, 0, 0, 0, 116, 101, 115, 116, 0, 7, 36, 105, 100, 0, 161, 190, 98, 75, 2, 180, 1, 0, 0, 2, 0, 0, 0, 10, 110, 117, 108, 108, 0, 0]; var serialized_data = ''; // Convert to chars diff --git a/test/node/test_full_bson.js b/test/node/test_full_bson.js index 5ac9082e..5d66f27d 100644 --- a/test/node/test_full_bson.js +++ b/test/node/test_full_bson.js @@ -12,103 +12,8 @@ describe('Full BSON', function () { * @ignore */ it('Should Correctly Deserialize object', function (done) { - var bytes = [ - 95, - 0, - 0, - 0, - 2, - 110, - 115, - 0, - 42, - 0, - 0, - 0, - 105, - 110, - 116, - 101, - 103, - 114, - 97, - 116, - 105, - 111, - 110, - 95, - 116, - 101, - 115, - 116, - 115, - 95, - 46, - 116, - 101, - 115, - 116, - 95, - 105, - 110, - 100, - 101, - 120, - 95, - 105, - 110, - 102, - 111, - 114, - 109, - 97, - 116, - 105, - 111, - 110, - 0, - 8, - 117, - 110, - 105, - 113, - 117, - 101, - 0, - 0, - 3, - 107, - 101, - 121, - 0, - 12, - 0, - 0, - 0, - 16, - 97, - 0, - 1, - 0, - 0, - 0, - 0, - 2, - 110, - 97, - 109, - 101, - 0, - 4, - 0, - 0, - 0, - 97, - 95, - 49, - 0, - 0 - ]; + // prettier-ignore + var bytes = [95, 0, 0, 0, 2, 110, 115, 0, 42, 0, 0, 0, 105, 110, 116, 101, 103, 114, 97, 116, 105, 111, 110, 95, 116, 101, 115, 116, 115, 95, 46, 116, 101, 115, 116, 95, 105, 110, 100, 101, 120, 95, 105, 110, 102, 111, 114, 109, 97, 116, 105, 111, 110, 0, 8, 117, 110, 105, 113, 117, 101, 0, 0, 3, 107, 101, 121, 0, 12, 0, 0, 0, 16, 97, 0, 1, 0, 0, 0, 0, 2, 110, 97, 109, 101, 0, 4, 0, 0, 0, 97, 95, 49, 0, 0]; var serialized_data = ''; // Convert to chars for (var i = 0; i < bytes.length; i++) { @@ -126,290 +31,8 @@ describe('Full BSON', function () { * @ignore */ it('Should Correctly Deserialize object with all types', function (done) { - var bytes = [ - 26, - 1, - 0, - 0, - 7, - 95, - 105, - 100, - 0, - 161, - 190, - 98, - 75, - 118, - 169, - 3, - 0, - 0, - 3, - 0, - 0, - 4, - 97, - 114, - 114, - 97, - 121, - 0, - 26, - 0, - 0, - 0, - 16, - 48, - 0, - 1, - 0, - 0, - 0, - 16, - 49, - 0, - 2, - 0, - 0, - 0, - 16, - 50, - 0, - 3, - 0, - 0, - 0, - 0, - 2, - 115, - 116, - 114, - 105, - 110, - 103, - 0, - 6, - 0, - 0, - 0, - 104, - 101, - 108, - 108, - 111, - 0, - 3, - 104, - 97, - 115, - 104, - 0, - 19, - 0, - 0, - 0, - 16, - 97, - 0, - 1, - 0, - 0, - 0, - 16, - 98, - 0, - 2, - 0, - 0, - 0, - 0, - 9, - 100, - 97, - 116, - 101, - 0, - 161, - 190, - 98, - 75, - 0, - 0, - 0, - 0, - 7, - 111, - 105, - 100, - 0, - 161, - 190, - 98, - 75, - 90, - 217, - 18, - 0, - 0, - 1, - 0, - 0, - 5, - 98, - 105, - 110, - 97, - 114, - 121, - 0, - 7, - 0, - 0, - 0, - 2, - 3, - 0, - 0, - 0, - 49, - 50, - 51, - 16, - 105, - 110, - 116, - 0, - 42, - 0, - 0, - 0, - 1, - 102, - 108, - 111, - 97, - 116, - 0, - 223, - 224, - 11, - 147, - 169, - 170, - 64, - 64, - 11, - 114, - 101, - 103, - 101, - 120, - 112, - 0, - 102, - 111, - 111, - 98, - 97, - 114, - 0, - 105, - 0, - 8, - 98, - 111, - 111, - 108, - 101, - 97, - 110, - 0, - 1, - 15, - 119, - 104, - 101, - 114, - 101, - 0, - 25, - 0, - 0, - 0, - 12, - 0, - 0, - 0, - 116, - 104, - 105, - 115, - 46, - 120, - 32, - 61, - 61, - 32, - 51, - 0, - 5, - 0, - 0, - 0, - 0, - 3, - 100, - 98, - 114, - 101, - 102, - 0, - 37, - 0, - 0, - 0, - 2, - 36, - 114, - 101, - 102, - 0, - 5, - 0, - 0, - 0, - 116, - 101, - 115, - 116, - 0, - 7, - 36, - 105, - 100, - 0, - 161, - 190, - 98, - 75, - 2, - 180, - 1, - 0, - 0, - 2, - 0, - 0, - 0, - 10, - 110, - 117, - 108, - 108, - 0, - 0 - ]; + // prettier-ignore + var bytes = [26, 1, 0, 0, 7, 95, 105, 100, 0, 161, 190, 98, 75, 118, 169, 3, 0, 0, 3, 0, 0, 4, 97, 114, 114, 97, 121, 0, 26, 0, 0, 0, 16, 48, 0, 1, 0, 0, 0, 16, 49, 0, 2, 0, 0, 0, 16, 50, 0, 3, 0, 0, 0, 0, 2, 115, 116, 114, 105, 110, 103, 0, 6, 0, 0, 0, 104, 101, 108, 108, 111, 0, 3, 104, 97, 115, 104, 0, 19, 0, 0, 0, 16, 97, 0, 1, 0, 0, 0, 16, 98, 0, 2, 0, 0, 0, 0, 9, 100, 97, 116, 101, 0, 161, 190, 98, 75, 0, 0, 0, 0, 7, 111, 105, 100, 0, 161, 190, 98, 75, 90, 217, 18, 0, 0, 1, 0, 0, 5, 98, 105, 110, 97, 114, 121, 0, 7, 0, 0, 0, 2, 3, 0, 0, 0, 49, 50, 51, 16, 105, 110, 116, 0, 42, 0, 0, 0, 1, 102, 108, 111, 97, 116, 0, 223, 224, 11, 147, 169, 170, 64, 64, 11, 114, 101, 103, 101, 120, 112, 0, 102, 111, 111, 98, 97, 114, 0, 105, 0, 8, 98, 111, 111, 108, 101, 97, 110, 0, 1, 15, 119, 104, 101, 114, 101, 0, 25, 0, 0, 0, 12, 0, 0, 0, 116, 104, 105, 115, 46, 120, 32, 61, 61, 32, 51, 0, 5, 0, 0, 0, 0, 3, 100, 98, 114, 101, 102, 0, 37, 0, 0, 0, 2, 36, 114, 101, 102, 0, 5, 0, 0, 0, 116, 101, 115, 116, 0, 7, 36, 105, 100, 0, 161, 190, 98, 75, 2, 180, 1, 0, 0, 2, 0, 0, 0, 10, 110, 117, 108, 108, 0, 0]; var serialized_data = ''; // Convert to chars for (var i = 0; i < bytes.length; i++) { @@ -447,7 +70,7 @@ describe('Full BSON', function () { /** * @ignore */ - it('Should Correctly Serialize and Deserialize Integer', function (done) { + it('Should Correctly Serialize and Deserialize Integer 5', function (done) { var test_number = { doc: 5 }; var serialized_data = BSON.serialize(test_number); expect(test_number).to.deep.equal(BSON.deserialize(serialized_data)); @@ -489,7 +112,7 @@ describe('Full BSON', function () { /** * @ignore */ - it('Should Correctly Serialize and Deserialize Integer', function (done) { + it('Should Correctly Serialize and Deserialize Integers', function (done) { var test_int = { doc: 42 }; var serialized_data = BSON.serialize(test_int); expect(test_int).to.deep.equal(BSON.deserialize(serialized_data)); @@ -658,6 +281,28 @@ describe('Full BSON', function () { done(); }); + it('Should Correctly Serialize and Deserialize a ArrayBuffer object', function () { + const arrayBuffer = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]).buffer; + const doc = { arrayBuffer }; + const serialized_data = BSON.serialize(doc); + const deserialized_data = BSON.deserialize(serialized_data); + + var v = deserialized_data.arrayBuffer.value(true); + expect(Array.from(new Uint8Array(doc.arrayBuffer))).to.have.ordered.members(Array.from(v)); + }); + + it('Should Correctly Serialize and Deserialize a Float64Array object', function () { + const floats = new Float64Array([12.34]); + const doc = { floats }; + const serialized_data = BSON.serialize(doc); + const deserialized_data = BSON.deserialize(serialized_data); + + const bytesFromBSON = Array.from(deserialized_data.floats.value(true)); + const bytesFromJS = Array.from(new Uint8Array(floats.buffer)); + expect(bytesFromJS).to.have.ordered.members(bytesFromBSON); + expect(12.34).to.equal(deserialized_data.floats.value(true).readDoubleLE(0)); + }); + it('Should Correctly fail due to attempting serialization of illegal key values', function (done) { var k = Buffer.alloc(15); for (var i = 0; i < 15; i++) k[i] = 0; diff --git a/tsconfig.json b/tsconfig.json index ce85d471..020252b3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,10 +20,11 @@ // Generate separate source maps files with sourceContent included "sourceMap": true, "inlineSourceMap": false, - "inlineSources": true, + "inlineSources": false, // API-extractor makes use of the declarations, npm script should be cleaning these up "declaration": true, - "declarationMap": true + "declarationMap": true, + "types": [] }, "ts-node": { "transpileOnly": true, From dfef14ed581f81a33ff1606f83acb1921eba8889 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Thu, 17 Sep 2020 11:10:26 -0400 Subject: [PATCH 02/10] fix: :art: Address comments --- src/binary.ts | 45 ++++++++++---------------- src/fnv1a.ts | 5 ++- src/objectid.ts | 65 +++++++++++++++----------------------- src/parser/deserializer.ts | 2 +- src/parser/utils.ts | 14 -------- 5 files changed, 44 insertions(+), 87 deletions(-) diff --git a/src/binary.ts b/src/binary.ts index 8272fd24..fa3a8d0b 100644 --- a/src/binary.ts +++ b/src/binary.ts @@ -1,7 +1,6 @@ import { Buffer } from 'buffer'; import { ensureBuffer } from './ensure_buffer'; import type { EJSONOptions } from './extended_json'; -import type { BufferEncoding } from './parser/utils'; type BinarySequence = Uint8Array | Buffer | number[]; @@ -54,7 +53,7 @@ export class Binary { */ constructor(buffer?: string | BinarySequence, subType?: number) { if ( - !(buffer === undefined || buffer === null) && + !(buffer == null) && !(typeof buffer === 'string') && !ArrayBuffer.isView(buffer) && !(buffer instanceof ArrayBuffer) && @@ -67,21 +66,22 @@ export class Binary { this.sub_type = subType ?? Binary.BSON_BINARY_SUBTYPE_DEFAULT; - if (buffer === undefined || buffer === null) { + if (buffer == null) { // create an empty binary buffer this.buffer = Buffer.alloc(Binary.BUFFER_SIZE); this.position = 0; - } else if (typeof buffer === 'string') { - // string - this.buffer = writeStringToArray(buffer); - this.position = this.buffer.byteLength; - } else if (Array.isArray(buffer)) { - // number[] - this.buffer = Buffer.from(buffer); - this.position = this.buffer.byteLength; } else { - // Buffer | TypedArray | ArrayBuffer - this.buffer = ensureBuffer(buffer); + if (typeof buffer === 'string') { + // string + this.buffer = Buffer.from(buffer, 'binary'); + } else if (Array.isArray(buffer)) { + // number[] + this.buffer = Buffer.from(buffer); + } else { + // Buffer | TypedArray | ArrayBuffer + this.buffer = ensureBuffer(buffer); + } + this.position = this.buffer.byteLength; } } @@ -197,8 +197,8 @@ export class Binary { } /** @internal */ - toString(format: BufferEncoding): string { - return this.buffer.slice(0, this.position).toString(format); + toString(format: string): string { + return this.buffer.toString(format); } /** @internal */ @@ -226,7 +226,7 @@ export class Binary { options = options || {}; let data: Buffer | undefined; let type; - if (options.legacy && '$type' in doc) { + if (options.legacy && '$type' in doc && typeof doc.$binary === 'string') { type = doc.$type ? parseInt(doc.$type, 16) : 0; data = Buffer.from(doc.$binary, 'base64'); } else { @@ -242,17 +242,4 @@ export class Binary { } } -/** @internal */ -function writeStringToArray(data: string) { - // Create a buffer - const buffer = Buffer.alloc(data.length); - - // Write the content to the buffer - for (let i = 0; i < data.length; i++) { - buffer[i] = data.charCodeAt(i); - } - // Write the string to the buffer - return buffer; -} - Object.defineProperty(Binary.prototype, '_bsontype', { value: 'Binary' }); diff --git a/src/fnv1a.ts b/src/fnv1a.ts index 719939da..09465830 100644 --- a/src/fnv1a.ts +++ b/src/fnv1a.ts @@ -1,6 +1,5 @@ import { Buffer } from 'buffer'; import { Long } from './long'; -import type { BufferEncoding } from './parser/utils'; const MASK_8 = 0xff; const MASK_24 = 0xffffff; @@ -16,7 +15,7 @@ const FNV_MASK = new Long(MASK_32, 0); * Algorithm can be found here: http://www.isthe.com/chongo/tech/comp/fnv/#FNV-1a * @internal */ -export function fnv1a32(input: string, encoding?: BufferEncoding): number { +export function fnv1a32(input: string, encoding?: string): number { encoding ??= 'utf8'; const octets = Buffer.from(input, encoding); @@ -35,7 +34,7 @@ export function fnv1a32(input: string, encoding?: BufferEncoding): number { * http://www.isthe.com/chongo/tech/comp/fnv/#xor-fold * @internal */ -export function fnv1a24(input: string, encoding?: BufferEncoding): number { +export function fnv1a24(input: string, encoding?: string): number { const _32bit = fnv1a32(input, encoding); const base = _32bit & MASK_24; const top = (_32bit >>> 24) & MASK_8; diff --git a/src/objectid.ts b/src/objectid.ts index f2a5880a..a2a5e8ae 100644 --- a/src/objectid.ts +++ b/src/objectid.ts @@ -1,6 +1,6 @@ import { Buffer } from 'buffer'; import { ensureBuffer } from './ensure_buffer'; -import { BufferEncoding, haveBuffer, randomBytes } from './parser/utils'; +import { randomBytes } from './parser/utils'; declare const require: Function; // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -30,6 +30,8 @@ export interface ObjectIdLike { toHexString(): string; } +const kId = Symbol('id'); + /** * A class representation of the BSON ObjectId type. */ @@ -42,7 +44,7 @@ export class ObjectId { static cacheHexString: boolean; /** ObjectId Bytes @internal */ - private _id: Buffer; + private [kId]: Buffer; /** ObjectId hexString cache @internal */ private __id?: string; @@ -54,22 +56,22 @@ export class ObjectId { constructor(id?: string | Buffer | number | ObjectIdLike | ObjectId) { // Duck-typing to support ObjectId from different npm packages if (id instanceof ObjectId) { - this._id = id.id; + this[kId] = id.id; this.__id = id.__id; } if (typeof id === 'object' && 'id' in id) { if ('toHexString' in id && typeof id.toHexString === 'function') { - this._id = Buffer.from(id.toHexString(), 'hex'); + this[kId] = Buffer.from(id.toHexString(), 'hex'); } else { - this._id = typeof id.id === 'string' ? Buffer.from(id.id) : id.id; + this[kId] = typeof id.id === 'string' ? Buffer.from(id.id) : id.id; } } // The most common use case (blank id, new objectId instance) if (id == null || typeof id === 'number') { // Generate a new id - this._id = ObjectId.generate(typeof id === 'number' ? id : undefined); + this[kId] = ObjectId.generate(typeof id === 'number' ? id : undefined); // If we are caching the hex string if (ObjectId.cacheHexString) { this.__id = this.id.toString('hex'); @@ -77,25 +79,22 @@ export class ObjectId { } if (ArrayBuffer.isView(id) && id.byteLength === 12) { - this._id = ensureBuffer(id); + this[kId] = ensureBuffer(id); } if (typeof id === 'string') { if (id.length === 12) { const bytes = Buffer.from(id); if (bytes.byteLength === 12) { - this._id = bytes; + this[kId] = bytes; } + } else if (id.length === 24 && checkForHexRegExp.test(id)) { + this[kId] = Buffer.from(id, 'hex'); + } else { + throw new TypeError( + 'Argument passed in must be a Buffer or string of 12 bytes or a string of 24 hex characters' + ); } - if (id.length === 24 && checkForHexRegExp.test(id)) { - this._id = Buffer.from(id, 'hex'); - } - } - - if (typeof this['_id'] === 'undefined') { - throw new TypeError( - 'Argument passed in must be a Buffer or string of 12 bytes or a string of 24 hex characters' - ); } if (ObjectId.cacheHexString) { @@ -108,12 +107,12 @@ export class ObjectId { * @readonly */ get id(): Buffer { - return this._id; + return this[kId]; } /** @internal */ set id(value: Buffer) { - this._id = value; + this[kId] = value; if (ObjectId.cacheHexString) { this.__id = value.toString('hex'); } @@ -133,7 +132,7 @@ export class ObjectId { */ set generationTime(value: number) { // Encode time into first 4 bytes - this.id.writeUInt32BE(value, 0, false); + this.id.writeUInt32BE(value, 0); } /** Returns the ObjectId id as a 24 character hex string representation */ @@ -175,7 +174,7 @@ export class ObjectId { const buffer = Buffer.alloc(12); // 4-byte timestamp - buffer.writeUInt32BE(time, 0, false); + buffer.writeUInt32BE(time, 0); // 5-byte process unique buffer[4] = PROCESS_UNIQUE[0]; @@ -198,9 +197,10 @@ export class ObjectId { * @param format - The Buffer toString format parameter. * @internal */ - toString(format?: BufferEncoding): string { + toString(format?: string): string { // Is the id a buffer then use the buffer toString method to return the format - return this.id.toString(typeof format === 'string' ? format : 'hex'); + if (format) return this.id.toString(format); + return this.toHexString(); } /** @@ -274,7 +274,7 @@ export class ObjectId { static createFromTime(time: number): ObjectId { const buffer = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); // Encode time into first 4 bytes - buffer.writeUInt32BE(time, 0, false); + buffer.writeUInt32BE(time, 0); // Return the new objectId return new ObjectId(buffer); } @@ -292,22 +292,7 @@ export class ObjectId { ); } - // Use Buffer.from method if available - if (haveBuffer()) { - return new ObjectId(Buffer.from(hexString, 'hex')); - } - - // Calculate lengths - const array = Buffer.alloc(12); - - let n = 0; - let i = 0; - while (i < 24) { - array[n++] = - (decodeLookup[hexString.charCodeAt(i++)] << 4) | decodeLookup[hexString.charCodeAt(i++)]; - } - - return new ObjectId(array); + return new ObjectId(Buffer.from(hexString, 'hex')); } /** diff --git a/src/parser/deserializer.ts b/src/parser/deserializer.ts index 12db117d..228faa61 100644 --- a/src/parser/deserializer.ts +++ b/src/parser/deserializer.ts @@ -73,7 +73,7 @@ export function deserialize( throw new Error(`buffer length ${buffer.length} must === bson size ${size}`); } - if (size + index > buffer.length) { + if (size + index > buffer.byteLength) { throw new Error( `(bson size ${size} + options.index ${index} must be <= buffer length ${buffer.byteLength})` ); diff --git a/src/parser/utils.ts b/src/parser/utils.ts index 7146f8e0..482362b4 100644 --- a/src/parser/utils.ts +++ b/src/parser/utils.ts @@ -1,13 +1,4 @@ import { Buffer } from 'buffer'; -export type BufferEncoding = - | 'ascii' - | 'utf8' - | 'utf16le' - | 'ucs2' - | 'base64' - | 'latin1' - | 'binary' - | 'hex'; /** * Normalizes our expected stringified form of a function across versions of node @@ -52,11 +43,6 @@ export function isUint8Array(value: unknown): value is Uint8Array { // eslint-disable-next-line @typescript-eslint/no-explicit-any declare const global: any; -/** Call to check if your environment has `Buffer` */ -export function haveBuffer(): boolean { - return typeof global !== 'undefined' && typeof global.Buffer !== 'undefined'; -} - // To ensure that 0.4 of node works correctly export function isDate(d: unknown): d is Date { return isObjectLike(d) && Object.prototype.toString.call(d) === '[object Date]'; From 9a38ebf0813820e26fab1eb5f2fdefb973de16ab Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Thu, 17 Sep 2020 11:11:20 -0400 Subject: [PATCH 03/10] fix: cleanup unused var --- src/parser/utils.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/parser/utils.ts b/src/parser/utils.ts index 482362b4..b7ee7aba 100644 --- a/src/parser/utils.ts +++ b/src/parser/utils.ts @@ -40,9 +40,6 @@ export function isUint8Array(value: unknown): value is Uint8Array { return Object.prototype.toString.call(value) === '[object Uint8Array]'; } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -declare const global: any; - // To ensure that 0.4 of node works correctly export function isDate(d: unknown): d is Date { return isObjectLike(d) && Object.prototype.toString.call(d) === '[object Date]'; From 379df560c2f173fa25710bb41d4b9b5555a06400 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Thu, 17 Sep 2020 14:33:44 -0400 Subject: [PATCH 04/10] fix: :bug: Fix Binary EJSON interface --- src/binary.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/binary.ts b/src/binary.ts index fa3a8d0b..ea38eafd 100644 --- a/src/binary.ts +++ b/src/binary.ts @@ -4,17 +4,15 @@ import type { EJSONOptions } from './extended_json'; type BinarySequence = Uint8Array | Buffer | number[]; -export type BinaryEJSON = - | { - $type: string; - $binary: string; - } - | { - $binary: { +export type BinaryEJSON = { + $type?: string; + $binary: + | string + | { subType: string; base64: string; }; - }; +}; /** A class representation of the BSON Binary type. */ export class Binary { @@ -226,7 +224,7 @@ export class Binary { options = options || {}; let data: Buffer | undefined; let type; - if (options.legacy && '$type' in doc && typeof doc.$binary === 'string') { + if (options.legacy && typeof doc.$binary === 'string') { type = doc.$type ? parseInt(doc.$type, 16) : 0; data = Buffer.from(doc.$binary, 'base64'); } else { From ca0b6dae362895c9f18b4f0ba1a56fbf0534ccf7 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Thu, 17 Sep 2020 14:41:17 -0400 Subject: [PATCH 05/10] feat: :art: Add more flexible typing to top level API --- src/bson.ts | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/bson.ts b/src/bson.ts index 3718f673..12bb9105 100644 --- a/src/bson.ts +++ b/src/bson.ts @@ -19,14 +19,12 @@ import { MinKey } from './min_key'; import { ObjectId } from './objectid'; import { calculateObjectSize as internalCalculateObjectSize } from './parser/calculate_size'; // Parts of the parser -import { DeserializeOptions, deserialize as internalDeserialize } from './parser/deserializer'; -import { SerializeOptions, serializeInto as internalSerialize } from './parser/serializer'; +import { deserialize as internalDeserialize, DeserializeOptions } from './parser/deserializer'; +import { serializeInto as internalSerialize, SerializeOptions } from './parser/serializer'; import { BSONRegExp } from './regexp'; import { BSONSymbol } from './symbol'; import { Timestamp } from './timestamp'; -export { SerializeOptions, DeserializeOptions }; - export { BSON_BINARY_SUBTYPE_BYTE_ARRAY, BSON_BINARY_SUBTYPE_DEFAULT, @@ -63,6 +61,7 @@ export { JS_INT_MAX, JS_INT_MIN } from './constants'; +export { SerializeOptions, DeserializeOptions }; export { Code, Map, @@ -201,9 +200,11 @@ export function serializeWithBufferAndIndex( * @param buffer - the buffer containing the serialized set of BSON documents. * @returns returns the deserialized Javascript Object. */ -export function deserialize(buffer: Buffer, options: DeserializeOptions = {}): Document { - buffer = ensureBuffer(buffer); - return internalDeserialize(buffer, options); +export function deserialize( + buffer: Buffer | ArrayBufferView | ArrayBuffer, + options: DeserializeOptions = {} +): Document { + return internalDeserialize(ensureBuffer(buffer), options); } export type CalculateObjectSizeOptions = Pick< @@ -243,7 +244,7 @@ export function calculateObjectSize( * @returns next index in the buffer after deserialization **x** numbers of documents. */ export function deserializeStream( - data: Buffer, + data: Buffer | ArrayBufferView | ArrayBuffer, startIndex: number, numberOfDocuments: number, documents: Document[], @@ -254,18 +255,21 @@ export function deserializeStream( { allowObjectSmallerThanBufferSize: true, index: 0 }, options ); - data = ensureBuffer(data); + const bufferData = ensureBuffer(data); let index = startIndex; // Loop over all documents for (let i = 0; i < numberOfDocuments; i++) { // Find size of the document const size = - data[index] | (data[index + 1] << 8) | (data[index + 2] << 16) | (data[index + 3] << 24); + bufferData[index] | + (bufferData[index + 1] << 8) | + (bufferData[index + 2] << 16) | + (bufferData[index + 3] << 24); // Update options with index internalOptions.index = index; // Parse the document at this point - documents[docStartIndex + i] = internalDeserialize(data, internalOptions); + documents[docStartIndex + i] = internalDeserialize(bufferData, internalOptions); // Adjust index by the document size index = index + size; } From e12930df61fe796dfa34821f4be44d630063fa52 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Thu, 17 Sep 2020 16:29:29 -0400 Subject: [PATCH 06/10] fix: :fire: Remove support for any typed array If any typed array other than Uint8 is passed as an object value it will be serialized in the same form that JSON supports. Which is a plain object of keys which are stringified numbers that map to each value in the array --- src/parser/serializer.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/parser/serializer.ts b/src/parser/serializer.ts index cdd9b10d..18ba3f9d 100644 --- a/src/parser/serializer.ts +++ b/src/parser/serializer.ts @@ -1,4 +1,4 @@ -import type { Buffer } from 'buffer'; +import { Buffer } from 'buffer'; import { Binary } from '../binary'; import type { BSONSymbol, DBRef, Document, MaxKey } from '../bson'; import type { Code } from '../code'; @@ -15,7 +15,7 @@ import { Map } from '../map'; import type { MinKey } from '../min_key'; import type { ObjectId } from '../objectid'; import type { BSONRegExp } from '../regexp'; -import { isDate, normalizedFunctionString } from './utils'; +import { isDate, isUint8Array, normalizedFunctionString } from './utils'; export interface SerializeOptions { /** the serializer will check if keys are valid. */ @@ -817,7 +817,7 @@ export function serializeInto( index = serializeNull(buffer, key, value, index, true); } else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') { index = serializeObjectId(buffer, key, value, index, true); - } else if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) { + } else if (Buffer.isBuffer(value) || isUint8Array(value) || value instanceof ArrayBuffer) { index = serializeBuffer(buffer, key, value, index, true); } else if (value instanceof RegExp || isRegExp(value)) { index = serializeRegExp(buffer, key, value, index, true); @@ -921,7 +921,7 @@ export function serializeInto( index = serializeNull(buffer, key, value, index); } else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') { index = serializeObjectId(buffer, key, value, index); - } else if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) { + } else if (Buffer.isBuffer(value) || isUint8Array(value) || value instanceof ArrayBuffer) { index = serializeBuffer(buffer, key, value, index); } else if (value instanceof RegExp || isRegExp(value)) { index = serializeRegExp(buffer, key, value, index); @@ -1025,7 +1025,7 @@ export function serializeInto( index = serializeNull(buffer, key, value, index); } else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') { index = serializeObjectId(buffer, key, value, index); - } else if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) { + } else if (Buffer.isBuffer(value) || isUint8Array(value) || value instanceof ArrayBuffer) { index = serializeBuffer(buffer, key, value, index); } else if (value instanceof RegExp || isRegExp(value)) { index = serializeRegExp(buffer, key, value, index); From cb8518735015d455424cbf36e579a1e5acf0dd85 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Thu, 17 Sep 2020 16:57:57 -0400 Subject: [PATCH 07/10] test: :bug: Test for object with stringified numbers as keys for typed arrays --- test/node/test_full_bson.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/node/test_full_bson.js b/test/node/test_full_bson.js index 5d66f27d..4298d4ff 100644 --- a/test/node/test_full_bson.js +++ b/test/node/test_full_bson.js @@ -297,10 +297,8 @@ describe('Full BSON', function () { const serialized_data = BSON.serialize(doc); const deserialized_data = BSON.deserialize(serialized_data); - const bytesFromBSON = Array.from(deserialized_data.floats.value(true)); - const bytesFromJS = Array.from(new Uint8Array(floats.buffer)); - expect(bytesFromJS).to.have.ordered.members(bytesFromBSON); - expect(12.34).to.equal(deserialized_data.floats.value(true).readDoubleLE(0)); + expect(deserialized_data).to.have.property('floats'); + expect(deserialized_data.floats).to.have.property('0', 12.34); }); it('Should Correctly fail due to attempting serialization of illegal key values', function (done) { From 8b6b902dc7bac5140ba5f1fd335e1e0e6ed5a598 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 18 Sep 2020 11:29:30 -0400 Subject: [PATCH 08/10] fix legacy buffer read/write calls --- src/objectid.ts | 4 ++-- src/parser/serializer.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/objectid.ts b/src/objectid.ts index a2a5e8ae..57bb0ada 100644 --- a/src/objectid.ts +++ b/src/objectid.ts @@ -123,7 +123,7 @@ export class ObjectId { * @deprecated Please use getTimestamp / createFromTime which returns an int32 epoch */ get generationTime(): number { - return this.id.readInt32BE(0, false); + return this.id.readInt32BE(0); } /** @@ -256,7 +256,7 @@ export class ObjectId { /** Returns the generation date (accurate up to the second) that this ID was generated. */ getTimestamp(): Date { const timestamp = new Date(); - const time = this.id.readUInt32BE(0, false); + const time = this.id.readUInt32BE(0); timestamp.setTime(Math.floor(time) * 1000); return timestamp; } diff --git a/src/parser/serializer.ts b/src/parser/serializer.ts index 18ba3f9d..deb1046a 100644 --- a/src/parser/serializer.ts +++ b/src/parser/serializer.ts @@ -355,7 +355,7 @@ function serializeObjectId( function serializeBuffer( buffer: Buffer, key: string, - value: Buffer | ArrayBuffer | ArrayBufferView, + value: Buffer | Uint8Array | ArrayBuffer, index: number, isArray?: boolean ) { From 2833da15a26e0707c385c25c44c5158962485761 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 18 Sep 2020 11:38:13 -0400 Subject: [PATCH 09/10] Revert ArrayBuffer serialization changes --- src/parser/serializer.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/parser/serializer.ts b/src/parser/serializer.ts index deb1046a..0bfdde6b 100644 --- a/src/parser/serializer.ts +++ b/src/parser/serializer.ts @@ -355,7 +355,7 @@ function serializeObjectId( function serializeBuffer( buffer: Buffer, key: string, - value: Buffer | Uint8Array | ArrayBuffer, + value: Buffer | Uint8Array, index: number, isArray?: boolean ) { @@ -369,7 +369,7 @@ function serializeBuffer( index = index + numberOfWrittenBytes; buffer[index++] = 0; // Get size of the buffer (current write point) - const size = value.byteLength; + const size = value.length; // Write the size of the string to buffer buffer[index++] = size & 0xff; buffer[index++] = (size >> 8) & 0xff; @@ -817,7 +817,7 @@ export function serializeInto( index = serializeNull(buffer, key, value, index, true); } else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') { index = serializeObjectId(buffer, key, value, index, true); - } else if (Buffer.isBuffer(value) || isUint8Array(value) || value instanceof ArrayBuffer) { + } else if (Buffer.isBuffer(value) || isUint8Array(value)) { index = serializeBuffer(buffer, key, value, index, true); } else if (value instanceof RegExp || isRegExp(value)) { index = serializeRegExp(buffer, key, value, index, true); @@ -921,7 +921,7 @@ export function serializeInto( index = serializeNull(buffer, key, value, index); } else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') { index = serializeObjectId(buffer, key, value, index); - } else if (Buffer.isBuffer(value) || isUint8Array(value) || value instanceof ArrayBuffer) { + } else if (Buffer.isBuffer(value) || isUint8Array(value)) { index = serializeBuffer(buffer, key, value, index); } else if (value instanceof RegExp || isRegExp(value)) { index = serializeRegExp(buffer, key, value, index); @@ -1025,7 +1025,7 @@ export function serializeInto( index = serializeNull(buffer, key, value, index); } else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') { index = serializeObjectId(buffer, key, value, index); - } else if (Buffer.isBuffer(value) || isUint8Array(value) || value instanceof ArrayBuffer) { + } else if (Buffer.isBuffer(value) || isUint8Array(value)) { index = serializeBuffer(buffer, key, value, index); } else if (value instanceof RegExp || isRegExp(value)) { index = serializeRegExp(buffer, key, value, index); From 2637c778e27ab47347a4e4c557c4d123033ad143 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Fri, 18 Sep 2020 11:51:13 -0400 Subject: [PATCH 10/10] Fix arraybuffer test --- test/node/test_full_bson.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/node/test_full_bson.js b/test/node/test_full_bson.js index 4298d4ff..7f9fe172 100644 --- a/test/node/test_full_bson.js +++ b/test/node/test_full_bson.js @@ -287,8 +287,7 @@ describe('Full BSON', function () { const serialized_data = BSON.serialize(doc); const deserialized_data = BSON.deserialize(serialized_data); - var v = deserialized_data.arrayBuffer.value(true); - expect(Array.from(new Uint8Array(doc.arrayBuffer))).to.have.ordered.members(Array.from(v)); + expect(deserialized_data.arrayBuffer).to.be.empty; }); it('Should Correctly Serialize and Deserialize a Float64Array object', function () {