Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UUID convenience class #425

Merged
merged 21 commits into from
Apr 2, 2021

Conversation

steffenagger
Copy link
Contributor

@steffenagger steffenagger commented Feb 24, 2021

UUID convenience class

Convenience class making uuid creations easier & typed, allowing:

const uuid1 = new UUID();
const uuid2 = new UUID('aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa');
const uuid3 = new UUID('aaaaaaaaaaaa4aaaaaaaaaaaaaaaaaaa');

const bin = uuid1.toBinary();
const uuid11 = bin.toUUID(); // will throw if not of sub_type Binary.SUBTYPE_UUID

I've kept this PR as a draft, as there are still left in comments as // NOTE: that should be addressed.

Changes/approaches:

  • Introduced a UUID convenience class.
    • Helper-methods on instances of UUID and Binary to convert between eachother.
  • The implementation is kept free of additional dependencies, despite the uuid looking as a solid candidate (uuid package is still added as a dev-dependency for string validation in tests).
    • uuid's helper functions work only with strings. For validation converting the underlying buffer into a string would have been necessary - this seemed wrong in relation to perfomance - it also dictates a certain string format (must include dashes e.g.: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx').
  • Expanded crypto detection to include global, for allowing packages such as react-native-get-random-values to populate implement global.crypto. getRandomValues in the context of React Native.
    • A warning is triggered when randomBytes detection falls back to insecureRandomBytes (with added guidance for React Native).
  • [value] instanceof Buffer has been replaced with Buffer.isBuffer(value), as instanceof yields problems when multiple packages depends on Buffer implementations, in browser builds. (as each package is scoped)

}

/**
* Generate a 16 byte uuid v4 buffer used in UUIDs
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a good place to discuss how random data is generated?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rely on the 'uuid' dependency here? will it correctly find the the right crypto function across platforms? If not I believe it allows you to provide the correct function as an argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a warning if/when the current implementation falls back to using insecureRandomBytes.

Copy link
Contributor

@nbbeeken nbbeeken left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's some changes needed here to bring us in line with spec and I don't see why we can't use the uuid library as a dependency, hopefully that'll make life a bit easier here. Thanks for taking a crack at this!

src/uuid.ts Show resolved Hide resolved
src/uuid.ts Outdated Show resolved Hide resolved
src/binary.ts Show resolved Hide resolved
src/parser/deserializer.ts Outdated Show resolved Hide resolved
src/parser/serializer.ts Outdated Show resolved Hide resolved
src/parser/utils.ts Outdated Show resolved Hide resolved
}

/**
* Generate a 16 byte uuid v4 buffer used in UUIDs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rely on the 'uuid' dependency here? will it correctly find the the right crypto function across platforms? If not I believe it allows you to provide the correct function as an argument.

Comment on lines +18 to +23
it('should correctly generate a valid UUID v4 from empty constructor', () => {
const uuid = new UUID();
const uuidHexStr = uuid.toHexString();
expect(uuidStringValidate(uuidHexStr)).to.be.true;
expect(uuidStringVersion(uuidHexStr)).to.equal(Binary.SUBTYPE_UUID);
});
Copy link
Contributor Author

@steffenagger steffenagger Mar 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nondeterministic test (as it tests auto-generation). Is this welcome as a test case?

@steffenagger steffenagger marked this pull request as ready for review March 17, 2021 15:14
Copy link
Contributor

@emadum emadum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One minor nit, but this LGTM, thanks for the contribution! 👍

: 'BSON: No cryptographic implementation for random bytes present, falling back to a less secure implementation.';

const insecureRandomBytes: RandomBytesFunction = function insecureRandomBytes(size: number) {
console.warn(insecureWarning);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be better to use process.emitWarning here.

Suggested change
console.warn(insecureWarning);
process.emitWarning(insecureWarning);

Copy link
Contributor Author

@steffenagger steffenagger Mar 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, but wouldn't this throw in a browser context without crypto?

Copy link
Contributor

@nbbeeken nbbeeken left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@nbbeeken nbbeeken merged commit 76e1826 into mongodb:master Apr 2, 2021
@jamiter
Copy link

jamiter commented May 22, 2022

Thanks for all the work done to get this in!

A question though: The UUID BSON type isn't handled in the serializer:

if (typeof value === 'string') {
index = serializeString(buffer, key, value, index, true);
} else if (typeof value === 'number') {
index = serializeNumber(buffer, key, value, index, true);
} else if (typeof value === 'bigint') {
throw new BSONTypeError('Unsupported type BigInt, please use Decimal128');
} else if (typeof value === 'boolean') {
index = serializeBoolean(buffer, key, value, index, true);
} else if (value instanceof Date || isDate(value)) {
index = serializeDate(buffer, key, value, index, true);
} else if (value === undefined) {
index = serializeNull(buffer, key, value, index, true);
} else if (value === null) {
index = serializeNull(buffer, key, value, index, true);
} else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') {
index = serializeObjectId(buffer, key, value, index, true);
} else if (isUint8Array(value)) {
index = serializeBuffer(buffer, key, value, index, true);
} else if (value instanceof RegExp || isRegExp(value)) {
index = serializeRegExp(buffer, key, value, index, true);
} else if (typeof value === 'object' && value['_bsontype'] == null) {
index = serializeObject(
buffer,
key,
value,
index,
checkKeys,
depth,
serializeFunctions,
ignoreUndefined,
true,
path
);
} else if (
typeof value === 'object' &&
isBSONType(value) &&
value._bsontype === 'Decimal128'
) {
index = serializeDecimal128(buffer, key, value, index, true);
} else if (value['_bsontype'] === 'Long' || value['_bsontype'] === 'Timestamp') {
index = serializeLong(buffer, key, value, index, true);
} else if (value['_bsontype'] === 'Double') {
index = serializeDouble(buffer, key, value, index, true);
} else if (typeof value === 'function' && serializeFunctions) {
index = serializeFunction(buffer, key, value, index, checkKeys, depth, true);
} else if (value['_bsontype'] === 'Code') {
index = serializeCode(
buffer,
key,
value,
index,
checkKeys,
depth,
serializeFunctions,
ignoreUndefined,
true
);
} else if (value['_bsontype'] === 'Binary') {
index = serializeBinary(buffer, key, value, index, true);
} else if (value['_bsontype'] === 'Symbol') {
index = serializeSymbol(buffer, key, value, index, true);
} else if (value['_bsontype'] === 'DBRef') {
index = serializeDBRef(buffer, key, value, index, depth, serializeFunctions, true);
} else if (value['_bsontype'] === 'BSONRegExp') {
index = serializeBSONRegExp(buffer, key, value, index, true);
} else if (value['_bsontype'] === 'Int32') {
index = serializeInt32(buffer, key, value, index, true);
} else if (value['_bsontype'] === 'MinKey' || value['_bsontype'] === 'MaxKey') {
index = serializeMinMax(buffer, key, value, index, true);
} else if (typeof value['_bsontype'] !== 'undefined') {
throw new BSONTypeError('Unrecognized or invalid _bsontype: ' + value['_bsontype']);
}
}

I was wondering if this is on purpose, or an oversight. And would a PR be accepted to add this type to the list?

@jamiter
Copy link

jamiter commented May 22, 2022

For anyone having the same issue. This seems to solve it for me:

Object.defineProperty(UUID.prototype, 'toBSON', {
    get() {
        return this.toBinary;
    },
});

The toBSON is called before serializing the value and it's perfectly able to save the binary.

Or was to intended behaviour to call it manually for each value on each save? That get's problematic quickly, especially with nested UUIDs.

Edit: There is still the issue of deserializing though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants