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'; /** @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 { // This property is not meant for direct serialization, but simply an indication that this type originates from this package. _bsontype!: 'UUID'; 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) { if (typeof input === 'undefined') { // The most common use case (blank id, new UUID() instance) this.id = UUID.generate(); } else if (input instanceof UUID) { this[kId] = Buffer.from(input.id); this.__id = input.__id; } else if (ArrayBuffer.isView(input) && input.byteLength === BYTE_LENGTH) { this.id = ensureBuffer(input); } else if (typeof input === 'string') { this.id = 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).' ); } } /** * 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()}")`; } } Object.defineProperty(UUID.prototype, '_bsontype', { value: 'UUID' });