Skip to content

Use crypo RNG for auto ID generation to reduce conflicts. #2764

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

Merged
merged 4 commits into from
Mar 21, 2020

Conversation

wu-hui
Copy link
Contributor

@wu-hui wu-hui commented Mar 19, 2020

No description provided.

@@ -2442,7 +2442,7 @@ export class CollectionReference<T = firestore.DocumentData> extends Query<T>
// We allow omission of 'pathString' but explicitly prohibit passing in both
// 'undefined' and 'null'.
if (arguments.length === 0) {
pathString = AutoId.newId();
pathString = AutoId.newId(PlatformSupport.getPlatform());
Copy link
Contributor

Choose a reason for hiding this comment

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

AutoId.newId should just call PlatformSupport.getPlatform() for itself. If you need a version of this that takes the platform as an argument for testing, it's fine to have AutoId.newId() delegate to that version. We shouldn't have to update all callers to fix this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

* Generates a random byte `b` with `0 <= b <= max`. If `max` is negative,
* it is the same as setting it to byte max (255).
*/
randomByte(max: number): number;
Copy link
Contributor

Choose a reason for hiding this comment

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

The platforms both provide ways to get an array of random bytes. It's better to keep this interface structured in terms of exposing common platform primitives and than we can implement any derived concerns (like limiting to a max value just once).

Concretely, I think this should be randomBytes that takes a number of bytes to generate and returns a Uint8Array of those contents. (Note that Node's crypto.randomBytes returns a Buffer but that's a subclass of Uint8Array so it's a suitable return value).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

// Alphanumeric characters
const chars =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let autoId = '';
for (let i = 0; i < 20; i++) {
autoId += chars.charAt(Math.floor(Math.random() * chars.length));
// Length of `chars` is 62. We generate random values between 0 and 247.
Copy link
Contributor

Choose a reason for hiding this comment

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

Mark this explicitly inclusive. Usually "between" implies one or both end points are excluded from the set. That is, use "Random values between 0 and 247 (inclusive)."

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

// Each value maps to an 4-element interval of `chars`, for example:
// 0..3 -> 0, 4..7 -> 1, etc. This ensures a uniform distribution of
// probability for all candidates.
const v = platform.randomByte(247);
Copy link
Contributor

Choose a reason for hiding this comment

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

A possible improvement here is to request 20-40 random bytes at a time from platform.getBytes() to save the overhead of individual calls. At the very least this would save the overhead of allocating an array for every call.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@wilhuff wilhuff assigned wu-hui and unassigned wilhuff Mar 19, 2020
Copy link
Contributor Author

@wu-hui wu-hui left a comment

Choose a reason for hiding this comment

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

Note there is some formatting changes picked up by prettier, and it looks like they should be included..the checked in format is off.

// Alphanumeric characters
const chars =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let autoId = '';
for (let i = 0; i < 20; i++) {
autoId += chars.charAt(Math.floor(Math.random() * chars.length));
// Length of `chars` is 62. We generate random values between 0 and 247.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

* Generates a random byte `b` with `0 <= b <= max`. If `max` is negative,
* it is the same as setting it to byte max (255).
*/
randomByte(max: number): number;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

// Each value maps to an 4-element interval of `chars`, for example:
// 0..3 -> 0, 4..7 -> 1, etc. This ensures a uniform distribution of
// probability for all candidates.
const v = platform.randomByte(247);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -2442,7 +2442,7 @@ export class CollectionReference<T = firestore.DocumentData> extends Query<T>
// We allow omission of 'pathString' but explicitly prohibit passing in both
// 'undefined' and 'null'.
if (arguments.length === 0) {
pathString = AutoId.newId();
pathString = AutoId.newId(PlatformSupport.getPlatform());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@wu-hui wu-hui assigned wilhuff and unassigned wu-hui Mar 20, 2020
Copy link
Contributor

@wilhuff wilhuff left a comment

Choose a reason for hiding this comment

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

LGTM

autoId += chars.charAt(Math.floor(Math.random() * chars.length));
while (autoId.length < 20) {
const bytes = PlatformSupport.getPlatform().randomBytes(40);
bytes.forEach(b => {
Copy link
Contributor

Choose a reason for hiding this comment

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

You could rephrase this with a single for loop:

const numBytes = 40;
const maxValue = 62 * 4;
let bytes = PlatformSupport.getPlatform().randomBytes(numBytes);
let i = 0;
while (autoId.length < 20) {
  if (i >= numBytes) {
    bytes = PlatformSupport.getPlatform().randomBytes(numBytes);
    i = 0;
  }
  const b = bytes[i++];
  // Avoid modulus bias by discarding byte values greater than 247.
  if (b < maxValue) {
    autoId += chars.charAt(b % 62)
  }
}

Not sure this is necessarily better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right..it's hard to say which one is better. I'll stick to my version..

// Length of `chars` is 62. We only take bytes between 0 and 62*4-1
// (both inclusive). The value is then evenly mapped to indices of `char`
// via a modulo operation.
if (autoId.length < 20 && b <= 62 * 4 - 1) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This magic number deserves a constant declaration.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@wilhuff wilhuff assigned wu-hui and unassigned wilhuff Mar 20, 2020
@wu-hui wu-hui merged commit a85f81d into master Mar 21, 2020
dconeybe added a commit that referenced this pull request Apr 6, 2020
@firebase firebase locked and limited conversation to collaborators Apr 21, 2020
@dconeybe dconeybe deleted the wuandy/StrongerRNG branch October 28, 2022 16:22
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants