From 184c2198d13557450c1db6aac41d9a4dfab040a6 Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Fri, 5 Aug 2022 16:08:16 -0400 Subject: [PATCH 01/11] made UUID a subclass of Binary --- src/binary.ts | 44 ++++++++++++++++++------------ src/uuid.ts | 26 +++++++++--------- test/node/type_identifier_tests.js | 4 +-- test/node/uuid_tests.js | 20 -------------- test/types/bson.test-d.ts | 2 +- 5 files changed, 42 insertions(+), 54 deletions(-) diff --git a/src/binary.ts b/src/binary.ts index f424a547..3b837507 100644 --- a/src/binary.ts +++ b/src/binary.ts @@ -1,9 +1,10 @@ import { Buffer } from 'buffer'; import { ensureBuffer } from './ensure_buffer'; import { uuidHexStringToBuffer } from './uuid_utils'; -import { UUID, UUIDExtended } from './uuid'; +import type { UUIDExtended, UUID } from './uuid'; import type { EJSONOptions } from './extended_json'; import { BSONError, BSONTypeError } from './error'; +declare const require: (_m: './uuid') => { UUID: typeof UUID }; /** @public */ export type BinarySequence = Uint8Array | Buffer | number[]; @@ -72,7 +73,7 @@ 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 | Binary, subType?: number) { if (!(this instanceof Binary)) return new Binary(buffer, subType); if ( @@ -87,25 +88,31 @@ export class Binary { ); } - this.sub_type = subType ?? Binary.BSON_BINARY_SUBTYPE_DEFAULT; - - if (buffer == null) { - // create an empty binary buffer - this.buffer = Buffer.alloc(Binary.BUFFER_SIZE); - this.position = 0; + if (buffer != null && (buffer as Binary)._bsontype === 'Binary') { + this.sub_type = (buffer as Binary).sub_type; + this.buffer = (buffer as Binary).buffer; + this.position = (buffer as Binary).position; } else { - if (typeof buffer === 'string') { - // string - this.buffer = Buffer.from(buffer, 'binary'); - } else if (Array.isArray(buffer)) { - // number[] - this.buffer = Buffer.from(buffer); + this.sub_type = subType ?? Binary.BSON_BINARY_SUBTYPE_DEFAULT; + + if (buffer == null) { + // create an empty binary buffer + this.buffer = Buffer.alloc(Binary.BUFFER_SIZE); + this.position = 0; } 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; + this.position = this.buffer.byteLength; + } } } @@ -243,6 +250,7 @@ export class Binary { } toUUID(): UUID { + const { UUID } = require('./uuid'); if (this.sub_type === Binary.SUBTYPE_UUID) { return new UUID(this.buffer.slice(0, this.position)); } diff --git a/src/uuid.ts b/src/uuid.ts index 625e57de..4e9a531e 100644 --- a/src/uuid.ts +++ b/src/uuid.ts @@ -4,6 +4,7 @@ import { Binary } from './binary'; import { bufferToUuidHexString, uuidHexStringToBuffer, uuidValidateString } from './uuid_utils'; import { isUint8Array, randomBytes } from './parser/utils'; import { BSONTypeError } from './error'; +import { BSON_BINARY_SUBTYPE_UUID_NEW } from './constants'; /** @public */ export type UUIDExtended = { @@ -18,10 +19,7 @@ const kId = Symbol('id'); * A class representation of the BSON UUID type. * @public */ -export class UUID { - // This property is not meant for direct serialization, but simply an indication that this type originates from this package. - _bsontype!: 'UUID'; - +export class UUID extends Binary { static cacheHexString: boolean; /** UUID Bytes @internal */ @@ -35,21 +33,25 @@ export class UUID { * @param input - Can be a 32 or 36 character hex string (dashes excluded/included) or a 16 byte binary Buffer. */ constructor(input?: string | Buffer | UUID) { - if (typeof input === 'undefined') { - // The most common use case (blank id, new UUID() instance) - this.id = UUID.generate(); + let bytes; + let hexStr; + if (input == null) { + bytes = UUID.generate(); } else if (input instanceof UUID) { - this[kId] = Buffer.from(input.id); - this.__id = input.__id; + bytes = Buffer.from(input[kId]); + hexStr = input.__id; } else if (ArrayBuffer.isView(input) && input.byteLength === BYTE_LENGTH) { - this.id = ensureBuffer(input); + bytes = ensureBuffer(input); } else if (typeof input === 'string') { - this.id = uuidHexStringToBuffer(input); + bytes = uuidHexStringToBuffer(input); } else { throw new BSONTypeError( 'Argument passed in UUID constructor must be a UUID, a 16 byte Buffer or a 32/36 character hex string (dashes excluded/included, format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).' ); } + super(bytes, BSON_BINARY_SUBTYPE_UUID_NEW); + this[kId] = bytes; + this.__id = hexStr; } /** @@ -205,5 +207,3 @@ export class UUID { return `new UUID("${this.toHexString()}")`; } } - -Object.defineProperty(UUID.prototype, '_bsontype', { value: 'UUID' }); diff --git a/test/node/type_identifier_tests.js b/test/node/type_identifier_tests.js index cda29de1..2dde9e12 100644 --- a/test/node/type_identifier_tests.js +++ b/test/node/type_identifier_tests.js @@ -64,7 +64,7 @@ describe('_bsontype identifier', () => { it('should be equal to BSONRegExp for BSONRegExp', () => { expect(BSONRegExp.prototype._bsontype).to.equal('BSONRegExp'); }); - it('should be equal to UUID for UUID', () => { - expect(UUID.prototype._bsontype).to.equal('UUID'); + it('should be equal to Binary for UUID', () => { + expect(UUID.prototype._bsontype).to.equal('Binary'); }); }); diff --git a/test/node/uuid_tests.js b/test/node/uuid_tests.js index f48360d8..90121d4c 100644 --- a/test/node/uuid_tests.js +++ b/test/node/uuid_tests.js @@ -6,8 +6,6 @@ const { inspect } = require('util'); const { validate: uuidStringValidate, version: uuidStringVersion } = require('uuid'); const BSON = require('../register-bson'); const BSONTypeError = BSON.BSONTypeError; -const BSON_DATA_BINARY = BSON.BSON_DATA_BINARY; -const BSON_BINARY_SUBTYPE_UUID_NEW = BSON.BSON_BINARY_SUBTYPE_UUID_NEW; // Test values const UPPERCASE_DASH_SEPARATED_UUID_STRING = 'AAAAAAAA-AAAA-4AAA-AAAA-AAAAAAAAAAAA'; @@ -166,24 +164,6 @@ describe('UUID', () => { }); describe('serialize', () => { - it('should have a valid UUID _bsontype with Object input without error', () => { - const output = BSON.serialize({ uuid: new BSON.UUID() }); - expect(output[4]).to.equal(BSON_DATA_BINARY); - expect(output[14]).to.equal(BSON_BINARY_SUBTYPE_UUID_NEW); - }); - - it('should have a valid UUID _bsontype with Map input without error', () => { - const output = BSON.serialize(new Map([['uuid', new BSON.UUID()]])); - expect(output[4]).to.equal(BSON_DATA_BINARY); - expect(output[14]).to.equal(BSON_BINARY_SUBTYPE_UUID_NEW); - }); - - it('should have as a valid UUID _bsontype with Array input without error', () => { - const output = BSON.serialize({ a: [new BSON.UUID()] }); - expect(output[11]).to.equal(BSON_DATA_BINARY); - expect(output[18]).to.equal(BSON_BINARY_SUBTYPE_UUID_NEW); - }); - it('should serialize BSON.UUID() input the same as BSON.UUID().toBinary()', () => { const exampleUUID = new BSON.UUID(); const toBinarySerialization = BSON.serialize({ uuid: exampleUUID.toBinary() }); diff --git a/test/types/bson.test-d.ts b/test/types/bson.test-d.ts index d92196e4..0a04870a 100644 --- a/test/types/bson.test-d.ts +++ b/test/types/bson.test-d.ts @@ -73,4 +73,4 @@ expectType<'Long'>(Long.prototype._bsontype) expectType<'MaxKey'>(MaxKey.prototype._bsontype) expectType<'MinKey'>(MinKey.prototype._bsontype) expectType<'BSONRegExp'>(BSONRegExp.prototype._bsontype) -expectType<'UUID'>(UUID.prototype._bsontype) +expectType<'Binary'>(UUID.prototype._bsontype) From 02dfb20f97a8680e31da1f12e267ae0ea8f3079d Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Fri, 5 Aug 2022 16:10:59 -0400 Subject: [PATCH 02/11] Removed extra UUID dead code in serialize.ts --- src/parser/serializer.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/parser/serializer.ts b/src/parser/serializer.ts index f20ddc36..e76402aa 100644 --- a/src/parser/serializer.ts +++ b/src/parser/serializer.ts @@ -837,8 +837,6 @@ export function serializeInto( ); } else if (value['_bsontype'] === 'Binary') { index = serializeBinary(buffer, key, value, index, true); - } else if (value['_bsontype'] === 'UUID') { - index = serializeBinary(buffer, key, value.toBinary(), index); } else if (value['_bsontype'] === 'Symbol') { index = serializeSymbol(buffer, key, value, index, true); } else if (value['_bsontype'] === 'DBRef') { @@ -940,8 +938,6 @@ export function serializeInto( index = serializeFunction(buffer, key, value, index, checkKeys, depth, serializeFunctions); } else if (value['_bsontype'] === 'Binary') { index = serializeBinary(buffer, key, value, index); - } else if (value['_bsontype'] === 'UUID') { - index = serializeBinary(buffer, key, value.toBinary(), index); } else if (value['_bsontype'] === 'Symbol') { index = serializeSymbol(buffer, key, value, index); } else if (value['_bsontype'] === 'DBRef') { @@ -1047,8 +1043,6 @@ export function serializeInto( index = serializeFunction(buffer, key, value, index, checkKeys, depth, serializeFunctions); } else if (value['_bsontype'] === 'Binary') { index = serializeBinary(buffer, key, value, index); - } else if (value['_bsontype'] === 'UUID') { - index = serializeBinary(buffer, key, value.toBinary(), index); } else if (value['_bsontype'] === 'Symbol') { index = serializeSymbol(buffer, key, value, index); } else if (value['_bsontype'] === 'DBRef') { From 2c7877a55ed41390d7eb1bfd2ac47c0020875508 Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Fri, 5 Aug 2022 16:17:13 -0400 Subject: [PATCH 03/11] moved UUID class into binary file --- src/binary.ts | 208 ++++++++++++++++++++++++++++++++++++++++++++++++- src/bson.ts | 6 +- src/uuid.ts | 209 -------------------------------------------------- 3 files changed, 206 insertions(+), 217 deletions(-) delete mode 100644 src/uuid.ts diff --git a/src/binary.ts b/src/binary.ts index 3b837507..e542a1ef 100644 --- a/src/binary.ts +++ b/src/binary.ts @@ -1,10 +1,10 @@ import { Buffer } from 'buffer'; import { ensureBuffer } from './ensure_buffer'; -import { uuidHexStringToBuffer } from './uuid_utils'; -import type { UUIDExtended, UUID } from './uuid'; +import { bufferToUuidHexString, uuidHexStringToBuffer, uuidValidateString } from './uuid_utils'; +import { isUint8Array, randomBytes } from './parser/utils'; import type { EJSONOptions } from './extended_json'; import { BSONError, BSONTypeError } from './error'; -declare const require: (_m: './uuid') => { UUID: typeof UUID }; +import { BSON_BINARY_SUBTYPE_UUID_NEW } from './constants'; /** @public */ export type BinarySequence = Uint8Array | Buffer | number[]; @@ -250,7 +250,6 @@ export class Binary { } toUUID(): UUID { - const { UUID } = require('./uuid'); if (this.sub_type === Binary.SUBTYPE_UUID) { return new UUID(this.buffer.slice(0, this.position)); } @@ -300,3 +299,204 @@ export class Binary { } Object.defineProperty(Binary.prototype, '_bsontype', { value: 'Binary' }); + +/** @public */ +export type UUIDExtended = { + $uuid: string; +}; +const BYTE_LENGTH = 16; + +const kId = Symbol('id'); + +/** + * A class representation of the BSON UUID type. + * @public + */ +export class UUID extends Binary { + static cacheHexString: boolean; + + /** UUID Bytes @internal */ + private [kId]!: Buffer; + /** UUID hexString cache @internal */ + private __id?: string; + + /** + * Create an UUID type + * + * @param input - Can be a 32 or 36 character hex string (dashes excluded/included) or a 16 byte binary Buffer. + */ + constructor(input?: string | Buffer | UUID) { + let bytes; + let hexStr; + if (input == null) { + bytes = UUID.generate(); + } else if (input instanceof UUID) { + bytes = Buffer.from(input[kId]); + hexStr = input.__id; + } else if (ArrayBuffer.isView(input) && input.byteLength === BYTE_LENGTH) { + bytes = ensureBuffer(input); + } else if (typeof input === 'string') { + bytes = uuidHexStringToBuffer(input); + } else { + throw new BSONTypeError( + 'Argument passed in UUID constructor must be a UUID, a 16 byte Buffer or a 32/36 character hex string (dashes excluded/included, format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).' + ); + } + super(bytes, BSON_BINARY_SUBTYPE_UUID_NEW); + this[kId] = bytes; + this.__id = hexStr; + } + + /** + * The UUID bytes + * @readonly + */ + get id(): Buffer { + return this[kId]; + } + + set id(value: Buffer) { + this[kId] = value; + + if (UUID.cacheHexString) { + this.__id = bufferToUuidHexString(value); + } + } + + /** + * Generate a 16 byte uuid v4 buffer used in UUIDs + */ + + /** + * Returns the UUID id as a 32 or 36 character hex string representation, excluding/including dashes (defaults to 36 character dash separated) + * @param includeDashes - should the string exclude dash-separators. + * */ + toHexString(includeDashes = true): string { + if (UUID.cacheHexString && this.__id) { + return this.__id; + } + + const uuidHexString = bufferToUuidHexString(this.id, includeDashes); + + if (UUID.cacheHexString) { + this.__id = uuidHexString; + } + + return uuidHexString; + } + + /** + * Converts the id into a 36 character (dashes included) hex string, unless a encoding is specified. + */ + toString(encoding?: string): string { + return encoding ? this.id.toString(encoding) : this.toHexString(); + } + + /** + * Converts the id into its JSON string representation. + * A 36 character (dashes included) hex string in the format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + */ + toJSON(): string { + return this.toHexString(); + } + + /** + * Compares the equality of this UUID with `otherID`. + * + * @param otherId - UUID instance to compare against. + */ + equals(otherId: string | Buffer | UUID): boolean { + if (!otherId) { + return false; + } + + if (otherId instanceof UUID) { + return otherId.id.equals(this.id); + } + + try { + return new UUID(otherId).id.equals(this.id); + } catch { + return false; + } + } + + /** + * Creates a Binary instance from the current UUID. + */ + toBinary(): Binary { + return new Binary(this.id, Binary.SUBTYPE_UUID); + } + + /** + * Generates a populated buffer containing a v4 uuid + */ + static generate(): Buffer { + const bytes = randomBytes(BYTE_LENGTH); + + // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` + // Kindly borrowed from https://github.com/uuidjs/uuid/blob/master/src/v4.js + bytes[6] = (bytes[6] & 0x0f) | 0x40; + bytes[8] = (bytes[8] & 0x3f) | 0x80; + + return Buffer.from(bytes); + } + + /** + * Checks if a value is a valid bson UUID + * @param input - UUID, string or Buffer to validate. + */ + static isValid(input: string | Buffer | UUID): boolean { + if (!input) { + return false; + } + + if (input instanceof UUID) { + return true; + } + + if (typeof input === 'string') { + return uuidValidateString(input); + } + + if (isUint8Array(input)) { + // check for length & uuid version (https://tools.ietf.org/html/rfc4122#section-4.1.3) + if (input.length !== BYTE_LENGTH) { + return false; + } + + try { + // get this byte as hex: xxxxxxxx-xxxx-XXxx-xxxx-xxxxxxxxxxxx + // check first part as uuid version: xxxxxxxx-xxxx-Xxxx-xxxx-xxxxxxxxxxxx + return parseInt(input[6].toString(16)[0], 10) === Binary.SUBTYPE_UUID; + } catch { + return false; + } + } + + return false; + } + + /** + * Creates an UUID from a hex string representation of an UUID. + * @param hexString - 32 or 36 character hex string (dashes excluded/included). + */ + static createFromHexString(hexString: string): UUID { + const buffer = uuidHexStringToBuffer(hexString); + return new UUID(buffer); + } + + /** + * Converts to a string representation of this Id. + * + * @returns return the 36 character hex string representation. + * @internal + */ + [Symbol.for('nodejs.util.inspect.custom')](): string { + return this.inspect(); + } + + inspect(): string { + return `new UUID("${this.toHexString()}")`; + } +} diff --git a/src/bson.ts b/src/bson.ts index bff2b031..1d181f9e 100644 --- a/src/bson.ts +++ b/src/bson.ts @@ -1,5 +1,5 @@ import { Buffer } from 'buffer'; -import { Binary } from './binary'; +import { Binary, UUID, UUID } from './binary'; import { Code } from './code'; import { DBRef } from './db_ref'; import { Decimal128 } from './decimal128'; @@ -20,8 +20,7 @@ import { serializeInto as internalSerialize, SerializeOptions } from './parser/s import { BSONRegExp } from './regexp'; import { BSONSymbol } from './symbol'; import { Timestamp } from './timestamp'; -import { UUID } from './uuid'; -export type { BinaryExtended, BinaryExtendedLegacy, BinarySequence } from './binary'; +export type { UUIDExtended, BinaryExtended, BinaryExtendedLegacy, BinarySequence } from './binary'; export type { CodeExtended } from './code'; export { BSON_BINARY_SUBTYPE_BYTE_ARRAY, @@ -73,7 +72,6 @@ export type { BSONRegExpExtended, BSONRegExpExtendedLegacy } from './regexp'; export type { BSONSymbolExtended } from './symbol'; export type { LongWithoutOverrides, TimestampExtended, TimestampOverrides } from './timestamp'; export { LongWithoutOverridesClass } from './timestamp'; -export type { UUIDExtended } from './uuid'; export type { SerializeOptions, DeserializeOptions }; export { Code, diff --git a/src/uuid.ts b/src/uuid.ts deleted file mode 100644 index 4e9a531e..00000000 --- a/src/uuid.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { Buffer } from 'buffer'; -import { ensureBuffer } from './ensure_buffer'; -import { Binary } from './binary'; -import { bufferToUuidHexString, uuidHexStringToBuffer, uuidValidateString } from './uuid_utils'; -import { isUint8Array, randomBytes } from './parser/utils'; -import { BSONTypeError } from './error'; -import { BSON_BINARY_SUBTYPE_UUID_NEW } from './constants'; - -/** @public */ -export type UUIDExtended = { - $uuid: string; -}; - -const BYTE_LENGTH = 16; - -const kId = Symbol('id'); - -/** - * A class representation of the BSON UUID type. - * @public - */ -export class UUID extends Binary { - static cacheHexString: boolean; - - /** UUID Bytes @internal */ - private [kId]!: Buffer; - /** UUID hexString cache @internal */ - private __id?: string; - - /** - * Create an UUID type - * - * @param input - Can be a 32 or 36 character hex string (dashes excluded/included) or a 16 byte binary Buffer. - */ - constructor(input?: string | Buffer | UUID) { - let bytes; - let hexStr; - if (input == null) { - bytes = UUID.generate(); - } else if (input instanceof UUID) { - bytes = Buffer.from(input[kId]); - hexStr = input.__id; - } else if (ArrayBuffer.isView(input) && input.byteLength === BYTE_LENGTH) { - bytes = ensureBuffer(input); - } else if (typeof input === 'string') { - bytes = uuidHexStringToBuffer(input); - } else { - throw new BSONTypeError( - 'Argument passed in UUID constructor must be a UUID, a 16 byte Buffer or a 32/36 character hex string (dashes excluded/included, format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).' - ); - } - super(bytes, BSON_BINARY_SUBTYPE_UUID_NEW); - this[kId] = bytes; - this.__id = hexStr; - } - - /** - * The UUID bytes - * @readonly - */ - get id(): Buffer { - return this[kId]; - } - - set id(value: Buffer) { - this[kId] = value; - - if (UUID.cacheHexString) { - this.__id = bufferToUuidHexString(value); - } - } - - /** - * Generate a 16 byte uuid v4 buffer used in UUIDs - */ - - /** - * Returns the UUID id as a 32 or 36 character hex string representation, excluding/including dashes (defaults to 36 character dash separated) - * @param includeDashes - should the string exclude dash-separators. - * */ - toHexString(includeDashes = true): string { - if (UUID.cacheHexString && this.__id) { - return this.__id; - } - - const uuidHexString = bufferToUuidHexString(this.id, includeDashes); - - if (UUID.cacheHexString) { - this.__id = uuidHexString; - } - - return uuidHexString; - } - - /** - * Converts the id into a 36 character (dashes included) hex string, unless a encoding is specified. - */ - toString(encoding?: string): string { - return encoding ? this.id.toString(encoding) : this.toHexString(); - } - - /** - * Converts the id into its JSON string representation. - * A 36 character (dashes included) hex string in the format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - */ - toJSON(): string { - return this.toHexString(); - } - - /** - * Compares the equality of this UUID with `otherID`. - * - * @param otherId - UUID instance to compare against. - */ - equals(otherId: string | Buffer | UUID): boolean { - if (!otherId) { - return false; - } - - if (otherId instanceof UUID) { - return otherId.id.equals(this.id); - } - - try { - return new UUID(otherId).id.equals(this.id); - } catch { - return false; - } - } - - /** - * Creates a Binary instance from the current UUID. - */ - toBinary(): Binary { - return new Binary(this.id, Binary.SUBTYPE_UUID); - } - - /** - * Generates a populated buffer containing a v4 uuid - */ - static generate(): Buffer { - const bytes = randomBytes(BYTE_LENGTH); - - // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` - // Kindly borrowed from https://github.com/uuidjs/uuid/blob/master/src/v4.js - bytes[6] = (bytes[6] & 0x0f) | 0x40; - bytes[8] = (bytes[8] & 0x3f) | 0x80; - - return Buffer.from(bytes); - } - - /** - * Checks if a value is a valid bson UUID - * @param input - UUID, string or Buffer to validate. - */ - static isValid(input: string | Buffer | UUID): boolean { - if (!input) { - return false; - } - - if (input instanceof UUID) { - return true; - } - - if (typeof input === 'string') { - return uuidValidateString(input); - } - - if (isUint8Array(input)) { - // check for length & uuid version (https://tools.ietf.org/html/rfc4122#section-4.1.3) - if (input.length !== BYTE_LENGTH) { - return false; - } - - try { - // get this byte as hex: xxxxxxxx-xxxx-XXxx-xxxx-xxxxxxxxxxxx - // check first part as uuid version: xxxxxxxx-xxxx-Xxxx-xxxx-xxxxxxxxxxxx - return parseInt(input[6].toString(16)[0], 10) === Binary.SUBTYPE_UUID; - } catch { - return false; - } - } - - return false; - } - - /** - * Creates an UUID from a hex string representation of an UUID. - * @param hexString - 32 or 36 character hex string (dashes excluded/included). - */ - static createFromHexString(hexString: string): UUID { - const buffer = uuidHexStringToBuffer(hexString); - return new UUID(buffer); - } - - /** - * Converts to a string representation of this Id. - * - * @returns return the 36 character hex string representation. - * @internal - */ - [Symbol.for('nodejs.util.inspect.custom')](): string { - return this.inspect(); - } - - inspect(): string { - return `new UUID("${this.toHexString()}")`; - } -} From 461d69083dabcc037725d594e376ebf3eb4aa65f Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Fri, 5 Aug 2022 16:19:02 -0400 Subject: [PATCH 04/11] removed import typo --- src/bson.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bson.ts b/src/bson.ts index 1d181f9e..d32cfe83 100644 --- a/src/bson.ts +++ b/src/bson.ts @@ -1,5 +1,5 @@ import { Buffer } from 'buffer'; -import { Binary, UUID, UUID } from './binary'; +import { Binary, UUID } from './binary'; import { Code } from './code'; import { DBRef } from './db_ref'; import { Decimal128 } from './decimal128'; From 471361b673682d7a3eabb59a0152571d7ffe35e3 Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Fri, 5 Aug 2022 16:23:49 -0400 Subject: [PATCH 05/11] removed edits to Binary constructor --- src/binary.ts | 42 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/src/binary.ts b/src/binary.ts index e542a1ef..b7b70707 100644 --- a/src/binary.ts +++ b/src/binary.ts @@ -73,7 +73,7 @@ export class Binary { * @param buffer - a buffer object containing the binary data. * @param subType - the option binary type. */ - constructor(buffer?: string | BinarySequence | Binary, subType?: number) { + constructor(buffer?: string | BinarySequence, subType?: number) { if (!(this instanceof Binary)) return new Binary(buffer, subType); if ( @@ -88,32 +88,26 @@ export class Binary { ); } - if (buffer != null && (buffer as Binary)._bsontype === 'Binary') { - this.sub_type = (buffer as Binary).sub_type; - this.buffer = (buffer as Binary).buffer; - this.position = (buffer as Binary).position; - } else { - this.sub_type = subType ?? Binary.BSON_BINARY_SUBTYPE_DEFAULT; + this.sub_type = subType ?? Binary.BSON_BINARY_SUBTYPE_DEFAULT; - if (buffer == null) { - // create an empty binary buffer - this.buffer = Buffer.alloc(Binary.BUFFER_SIZE); - this.position = 0; + 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 = Buffer.from(buffer, 'binary'); + } else if (Array.isArray(buffer)) { + // number[] + this.buffer = Buffer.from(buffer); } else { - 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; + // Buffer | TypedArray | ArrayBuffer + this.buffer = ensureBuffer(buffer); } - } + + this.position = this.buffer.byteLength; + } /** From 06bf6651d87cbb6ad4d05946e1d021f899bce123 Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Fri, 5 Aug 2022 16:23:52 -0400 Subject: [PATCH 06/11] removed edits to Binary constructor --- src/binary.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/binary.ts b/src/binary.ts index b7b70707..ac8f6cd9 100644 --- a/src/binary.ts +++ b/src/binary.ts @@ -107,7 +107,7 @@ export class Binary { } this.position = this.buffer.byteLength; - + } } /** From 72e5236ee984b33171968b4cc7064fe95864c2bf Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Wed, 10 Aug 2022 15:11:25 -0400 Subject: [PATCH 07/11] PR changes --- src/binary.ts | 20 ++++++++------------ test/node/uuid_tests.js | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/binary.ts b/src/binary.ts index ac8f6cd9..e55a09f6 100644 --- a/src/binary.ts +++ b/src/binary.ts @@ -298,9 +298,7 @@ Object.defineProperty(Binary.prototype, '_bsontype', { value: 'Binary' }); export type UUIDExtended = { $uuid: string; }; -const BYTE_LENGTH = 16; - -const kId = Symbol('id'); +const UUID_BYTE_LENGTH = 16; /** * A class representation of the BSON UUID type. @@ -309,8 +307,6 @@ const kId = Symbol('id'); export class UUID extends Binary { static cacheHexString: boolean; - /** UUID Bytes @internal */ - private [kId]!: Buffer; /** UUID hexString cache @internal */ private __id?: string; @@ -325,9 +321,9 @@ export class UUID extends Binary { if (input == null) { bytes = UUID.generate(); } else if (input instanceof UUID) { - bytes = Buffer.from(input[kId]); + bytes = Buffer.from(input.buffer); hexStr = input.__id; - } else if (ArrayBuffer.isView(input) && input.byteLength === BYTE_LENGTH) { + } else if (ArrayBuffer.isView(input) && input.byteLength === UUID_BYTE_LENGTH) { bytes = ensureBuffer(input); } else if (typeof input === 'string') { bytes = uuidHexStringToBuffer(input); @@ -337,7 +333,7 @@ export class UUID extends Binary { ); } super(bytes, BSON_BINARY_SUBTYPE_UUID_NEW); - this[kId] = bytes; + this.buffer = bytes; this.__id = hexStr; } @@ -346,11 +342,11 @@ export class UUID extends Binary { * @readonly */ get id(): Buffer { - return this[kId]; + return this.buffer; } set id(value: Buffer) { - this[kId] = value; + this.buffer = value; if (UUID.cacheHexString) { this.__id = bufferToUuidHexString(value); @@ -426,7 +422,7 @@ export class UUID extends Binary { * Generates a populated buffer containing a v4 uuid */ static generate(): Buffer { - const bytes = randomBytes(BYTE_LENGTH); + const bytes = randomBytes(UUID_BYTE_LENGTH); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` // Kindly borrowed from https://github.com/uuidjs/uuid/blob/master/src/v4.js @@ -455,7 +451,7 @@ export class UUID extends Binary { if (isUint8Array(input)) { // check for length & uuid version (https://tools.ietf.org/html/rfc4122#section-4.1.3) - if (input.length !== BYTE_LENGTH) { + if (input.length !== UUID_BYTE_LENGTH) { return false; } diff --git a/test/node/uuid_tests.js b/test/node/uuid_tests.js index 90121d4c..f1458542 100644 --- a/test/node/uuid_tests.js +++ b/test/node/uuid_tests.js @@ -6,6 +6,8 @@ const { inspect } = require('util'); const { validate: uuidStringValidate, version: uuidStringVersion } = require('uuid'); const BSON = require('../register-bson'); const BSONTypeError = BSON.BSONTypeError; +const BSON_DATA_BINARY = BSON.BSON_DATA_BINARY; +const BSON_BINARY_SUBTYPE_UUID_NEW = BSON.BSON_BINARY_SUBTYPE_UUID_NEW; // Test values const UPPERCASE_DASH_SEPARATED_UUID_STRING = 'AAAAAAAA-AAAA-4AAA-AAAA-AAAAAAAAAAAA'; @@ -170,5 +172,23 @@ describe('UUID', () => { const plainUUIDSerialization = BSON.serialize({ uuid: exampleUUID }); expect(plainUUIDSerialization).to.deep.equal(toBinarySerialization); }); + + it('should have a valid UUID _bsontype with Object input without error', () => { + const output = BSON.serialize({ uuid: new BSON.UUID() }); + expect(output[4]).to.equal(BSON_DATA_BINARY); + expect(output[14]).to.equal(BSON_BINARY_SUBTYPE_UUID_NEW); + }); + + it('should have a valid UUID _bsontype with Map input without error', () => { + const output = BSON.serialize(new Map([['uuid', new BSON.UUID()]])); + expect(output[4]).to.equal(BSON_DATA_BINARY); + expect(output[14]).to.equal(BSON_BINARY_SUBTYPE_UUID_NEW); + }); + + it('should have as a valid UUID _bsontype with Array input without error', () => { + const output = BSON.serialize({ a: [new BSON.UUID()] }); + expect(output[11]).to.equal(BSON_DATA_BINARY); + expect(output[18]).to.equal(BSON_BINARY_SUBTYPE_UUID_NEW); + }); }); }); From cf143ac79482fd58488ae44db43205367278c8eb Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Thu, 11 Aug 2022 11:43:23 -0400 Subject: [PATCH 08/11] added EJSON tests --- test/node/extended_json_tests.js | 35 ++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/node/extended_json_tests.js b/test/node/extended_json_tests.js index e8f97457..37632bf6 100644 --- a/test/node/extended_json_tests.js +++ b/test/node/extended_json_tests.js @@ -6,6 +6,7 @@ const vm = require('vm'); // BSON types const Binary = BSON.Binary; +const UUID = BSON.UUID; const Code = BSON.Code; const DBRef = BSON.DBRef; const Decimal128 = BSON.Decimal128; @@ -739,4 +740,38 @@ Converting circular structure to EJSON: }); }); }); + + describe('UUID stringify', () => { + const uuid = new UUID(); + const stringifiedPlainUUID = EJSON.stringify({ u: uuid }); + it('should return same values for UUID.toBinary() and UUID', () => { + const stringifiedToBinary = EJSON.stringify({ u: uuid.toBinary() }); + expect(stringifiedToBinary).to.deep.equal(stringifiedPlainUUID); + }); + it('should serialize to correct subType', () => { + const stringifiedUUIDtoObject = JSON.parse(stringifiedPlainUUID); + const stringifiedBinaryNewUUIDSubType = '04'; + expect(stringifiedUUIDtoObject.u.$binary.subType).to.equal(stringifiedBinaryNewUUIDSubType); + }); + }); + + describe('UUID parse', () => { + const uuid = new UUID(); + const stringifiedPlainUUID = EJSON.stringify({ u: uuid }); + it('should return same values for UUID.toBinary() and UUID', () => { + const stringifiedToBinary = EJSON.stringify({ u: uuid.toBinary() }); + const parsedToBinary = EJSON.parse(stringifiedToBinary); + const parsedPlainUUID = EJSON.parse(stringifiedPlainUUID); + expect(parsedToBinary).to.deep.equal(parsedPlainUUID); + }); + it('should parse both input formats the same way', () => { + const parsedUndashedInput = EJSON.parse( + `{"u":{"$binary":{"base64":"vDzrMPEAQOGkA8wGUNSOxw==","subType":"04"}}}` + ); + const parsedDashedInput = EJSON.parse( + `{"u":{"$uuid":"bc3ceb30-f100-40e1-a403-cc0650d48ec7"}}` + ); + expect(parsedUndashedInput).to.deep.equal(parsedDashedInput); + }); + }); }); From 736682fbc8b7f0280abd6852b5e2555f2f9dc055 Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Thu, 11 Aug 2022 11:53:41 -0400 Subject: [PATCH 09/11] Anna's nice to haves --- src/binary.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/binary.ts b/src/binary.ts index e55a09f6..601f5caa 100644 --- a/src/binary.ts +++ b/src/binary.ts @@ -353,10 +353,6 @@ export class UUID extends Binary { } } - /** - * Generate a 16 byte uuid v4 buffer used in UUIDs - */ - /** * Returns the UUID id as a 32 or 36 character hex string representation, excluding/including dashes (defaults to 36 character dash separated) * @param includeDashes - should the string exclude dash-separators. @@ -455,13 +451,7 @@ export class UUID extends Binary { return false; } - try { - // get this byte as hex: xxxxxxxx-xxxx-XXxx-xxxx-xxxxxxxxxxxx - // check first part as uuid version: xxxxxxxx-xxxx-Xxxx-xxxx-xxxxxxxxxxxx - return parseInt(input[6].toString(16)[0], 10) === Binary.SUBTYPE_UUID; - } catch { - return false; - } + return (input[6] & 0xf0) === 0x40; } return false; From bfffe27b04a34ab77a97f1086067335d3e16d720 Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Thu, 11 Aug 2022 11:55:41 -0400 Subject: [PATCH 10/11] annas nice to haves part 2 --- src/binary.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/binary.ts b/src/binary.ts index 601f5caa..7c1f4c79 100644 --- a/src/binary.ts +++ b/src/binary.ts @@ -451,7 +451,7 @@ export class UUID extends Binary { return false; } - return (input[6] & 0xf0) === 0x40; + return (input[6] & 0xf0) === 0x40 && (input[8] & 0x80) === 0x80; } return false; From 1bbf163b9d6b22b003931569f82b7d15459648d0 Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Thu, 11 Aug 2022 12:00:35 -0400 Subject: [PATCH 11/11] got rid of extra this.buffer assignment --- src/binary.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/binary.ts b/src/binary.ts index 7c1f4c79..032a1e02 100644 --- a/src/binary.ts +++ b/src/binary.ts @@ -333,7 +333,6 @@ export class UUID extends Binary { ); } super(bytes, BSON_BINARY_SUBTYPE_UUID_NEW); - this.buffer = bytes; this.__id = hexStr; }