-
-
Notifications
You must be signed in to change notification settings - Fork 35.4k
crypto: add uuidv7 monotonic counter #62601
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
base: main
Are you sure you want to change the base?
Changes from all commits
5e312c1
19bb93c
a5f3be5
2f90718
c798973
e60619e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -348,6 +348,9 @@ let uuidData; | |
| let uuidNotBuffered; | ||
| let uuidBatch = 0; | ||
|
|
||
| let v7LastTimestamp = -1; | ||
| let v7Counter = 0; | ||
|
|
||
| let hexBytesCache; | ||
| function getHexBytes() { | ||
| if (hexBytesCache === undefined) { | ||
|
|
@@ -415,48 +418,86 @@ function randomUUID(options) { | |
| return disableEntropyCache ? getUnbufferedUUID() : getBufferedUUID(); | ||
| } | ||
|
|
||
| function writeTimestamp(buf, offset) { | ||
| function advanceV7(seed) { | ||
| const now = DateNow(); | ||
| const msb = now / (2 ** 32); | ||
| buf[offset] = msb >>> 8; | ||
| buf[offset + 1] = msb; | ||
| buf[offset + 2] = now >>> 24; | ||
| buf[offset + 3] = now >>> 16; | ||
| buf[offset + 4] = now >>> 8; | ||
| buf[offset + 5] = now; | ||
| if (now > v7LastTimestamp) { | ||
| v7LastTimestamp = now; | ||
| v7Counter = seed & 0xFFF; | ||
| } else { | ||
| v7Counter++; | ||
| if (v7Counter > 0xFFF) { | ||
| v7LastTimestamp++; | ||
| v7Counter = 0; | ||
| } | ||
| } | ||
|
Comment on lines
+423
to
+432
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is problematic if the clock moves backwards significantly, as the timestamp ends up frozen until such a time as the clock catches up. I don't know how other implementations handle this possibility.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From postgresql: https://raw.githubusercontent.com/postgres/postgres/dbf217c1c7c2744a18db489c255255e07cfbb110/src/backend/utils/adt/uuid.c If the wall clock returns a value that isn't at least
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. from uuidjs https://github.com/uuidjs/uuid/blob/main/src/v7.ts#L65 just ignored |
||
| } | ||
|
|
||
| function makeUUIDv7(buf, offset, monotonic = false) { | ||
| let timestamp, randA; | ||
| if (monotonic) { | ||
| const seed = ((buf[offset + 6] & 0x0f) << 8) | buf[offset + 7]; | ||
| advanceV7(seed); | ||
| timestamp = v7LastTimestamp; | ||
| randA = v7Counter; | ||
| } else { | ||
| timestamp = DateNow(); | ||
| randA = ((buf[offset + 6] & 0x0f) << 8) | buf[offset + 7]; | ||
| } | ||
| const kHexBytes = getHexBytes(); | ||
| const msb = timestamp / (2 ** 32); | ||
| return kHexBytes[msb >>> 8] + | ||
| kHexBytes[msb & 0xff] + | ||
| kHexBytes[timestamp >>> 24] + | ||
| kHexBytes[(timestamp >>> 16) & 0xff] + | ||
| '-' + | ||
| kHexBytes[(timestamp >>> 8) & 0xff] + | ||
| kHexBytes[timestamp & 0xff] + | ||
| '-' + | ||
| kHexBytes[(randA >>> 8) | 0x70] + | ||
| kHexBytes[randA & 0xff] + | ||
| '-' + | ||
| kHexBytes[(buf[offset + 8] & 0x3f) | 0x80] + | ||
| kHexBytes[buf[offset + 9]] + | ||
| '-' + | ||
| kHexBytes[buf[offset + 10]] + | ||
| kHexBytes[buf[offset + 11]] + | ||
| kHexBytes[buf[offset + 12]] + | ||
| kHexBytes[buf[offset + 13]] + | ||
| kHexBytes[buf[offset + 14]] + | ||
| kHexBytes[buf[offset + 15]]; | ||
| } | ||
|
|
||
| function getBufferedUUIDv7() { | ||
| function getBufferedUUIDv7(monotonic = false) { | ||
| uuidData ??= secureBuffer(16 * kBatchSize); | ||
| if (uuidData === undefined) | ||
| throw new ERR_OPERATION_FAILED('Out of memory'); | ||
|
|
||
| if (uuidBatch === 0) randomFillSync(uuidData); | ||
| uuidBatch = (uuidBatch + 1) % kBatchSize; | ||
| const offset = uuidBatch * 16; | ||
| writeTimestamp(uuidData, offset); | ||
| return serializeUUID(uuidData, 0x70, 0x80, offset); | ||
| return makeUUIDv7(uuidData, uuidBatch * 16, monotonic); | ||
| } | ||
|
|
||
| function getUnbufferedUUIDv7() { | ||
| function getUnbufferedUUIDv7(monotonic = false) { | ||
| uuidNotBuffered ??= secureBuffer(16); | ||
| if (uuidNotBuffered === undefined) | ||
| throw new ERR_OPERATION_FAILED('Out of memory'); | ||
| randomFillSync(uuidNotBuffered, 6); | ||
| writeTimestamp(uuidNotBuffered, 0); | ||
| return serializeUUID(uuidNotBuffered, 0x70, 0x80); | ||
| return makeUUIDv7(uuidNotBuffered, 0, monotonic); | ||
| } | ||
|
|
||
| function randomUUIDv7(options) { | ||
| if (options !== undefined) | ||
| validateObject(options, 'options'); | ||
| const { | ||
| disableEntropyCache = false, | ||
| monotonic = false, | ||
| } = options || kEmptyObject; | ||
|
|
||
| validateBoolean(disableEntropyCache, 'options.disableEntropyCache'); | ||
| validateBoolean(monotonic, 'options.monotonic'); | ||
|
|
||
| return disableEntropyCache ? getUnbufferedUUIDv7() : getBufferedUUIDv7(); | ||
| return disableEntropyCache ? | ||
| getUnbufferedUUIDv7(monotonic) : getBufferedUUIDv7(monotonic); | ||
| } | ||
|
|
||
| function createRandomPrimeJob(type, size, options) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prematurely incrementing the timestamp is a permissible way to handle counter overflow in the RFC, but I think we need to be really clear in the documentation that the potential timestamp drift in the generated UUIDs is effectively unbounded, and could diverge ad infinitum if generating UUIDs at very high frequency.