fix!: refactor v1 internal state and options logic (#780)

This commit is contained in:
Robert Kieffer
2024-07-20 03:15:45 -10:00
committed by GitHub
parent d5623b8747
commit d5fe4e5c94
9 changed files with 343 additions and 345 deletions
-3
View File
@@ -104,9 +104,6 @@ export default function benchmark(uuid, Benchmark) {
.add('uuid.v1ToV6()', function () {
uuid.v1ToV6(V1_ID);
})
.add('uuid.v1ToV6() w/ randomization', function () {
uuid.v1ToV6(V1_ID, true);
})
.add('uuid.v6ToV1()', function () {
uuid.v6ToV1(V6_ID);
})
+24 -34
View File
@@ -25,34 +25,29 @@
"uuid": "dist/esm/bin/uuid"
},
"devDependencies": {
"@babel/cli": "7.24.6",
"@babel/core": "7.24.6",
"@babel/eslint-parser": "7.24.6",
"@babel/plugin-syntax-import-attributes": "7.24.6",
"@babel/preset-env": "7.24.6",
"@babel/eslint-parser": "7.24.7",
"@commitlint/cli": "19.3.0",
"@commitlint/config-conventional": "19.2.2",
"@eslint/js": "9.5.0",
"@types/eslint__js": "8.42.3",
"@types/random-seed": "0.3.5",
"@wdio/browserstack-service": "7.16.10",
"@wdio/cli": "7.16.10",
"@wdio/jasmine-framework": "7.16.6",
"@wdio/local-runner": "7.16.10",
"@wdio/spec-reporter": "7.16.9",
"@wdio/static-server-service": "7.16.6",
"@wdio/browserstack-service": "8.39.0",
"@wdio/cli": "8.39.0",
"@wdio/jasmine-framework": "8.39.0",
"@wdio/local-runner": "8.39.0",
"@wdio/spec-reporter": "8.39.0",
"@wdio/static-server-service": "8.39.0",
"bundlewatch": "0.3.3",
"eslint": "9.5.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-prettier": "5.1.3",
"globals": "15.3.0",
"globals": "15.6.0",
"husky": "9.0.11",
"jest": "29.7.0",
"lint-staged": "15.2.5",
"neostandard": "0.5.1",
"lint-staged": "15.2.7",
"neostandard": "0.7.2",
"npm-run-all": "4.1.5",
"optional-dev-dependency": "2.0.1",
"prettier": "3.3.0",
"random-seed": "0.3.0",
"prettier": "3.3.2",
"runmd": "1.3.9",
"standard-version": "9.5.0",
"typescript": "5.4.5",
@@ -112,34 +107,29 @@
"uuid": {
"version": "file:../../.local/uuid",
"requires": {
"@babel/cli": "7.24.6",
"@babel/core": "7.24.6",
"@babel/eslint-parser": "7.24.6",
"@babel/plugin-syntax-import-attributes": "7.24.6",
"@babel/preset-env": "7.24.6",
"@babel/eslint-parser": "7.24.7",
"@commitlint/cli": "19.3.0",
"@commitlint/config-conventional": "19.2.2",
"@eslint/js": "9.5.0",
"@types/eslint__js": "8.42.3",
"@types/random-seed": "0.3.5",
"@wdio/browserstack-service": "7.16.10",
"@wdio/cli": "7.16.10",
"@wdio/jasmine-framework": "7.16.6",
"@wdio/local-runner": "7.16.10",
"@wdio/spec-reporter": "7.16.9",
"@wdio/static-server-service": "7.16.6",
"@wdio/browserstack-service": "8.39.0",
"@wdio/cli": "8.39.0",
"@wdio/jasmine-framework": "8.39.0",
"@wdio/local-runner": "8.39.0",
"@wdio/spec-reporter": "8.39.0",
"@wdio/static-server-service": "8.39.0",
"bundlewatch": "0.3.3",
"eslint": "9.5.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-prettier": "5.1.3",
"globals": "15.3.0",
"globals": "15.6.0",
"husky": "9.0.11",
"jest": "29.7.0",
"lint-staged": "15.2.5",
"neostandard": "0.5.1",
"lint-staged": "15.2.7",
"neostandard": "0.7.2",
"npm-run-all": "4.1.5",
"optional-dev-dependency": "2.0.1",
"prettier": "3.3.0",
"random-seed": "0.3.0",
"prettier": "3.3.2",
"runmd": "1.3.9",
"standard-version": "9.5.0",
"typescript": "5.4.5",
-35
View File
@@ -1,35 +0,0 @@
import * as assert from 'assert';
import test, { describe } from 'node:test';
import v1 from '../v1.js';
// Since the clockseq is cached in the module this test must run in a separate file in order to
// initialize the v1 clockseq with controlled random data.
describe('v1-random', () => {
const randomBytesFixture = Uint8Array.of(
0x10,
0x91,
0x56,
0xbe,
0xc4,
0xfb,
0xc1,
0xea,
0x71,
0xb4,
0xef,
0xe1,
0x67,
0x1c,
0x58,
0x36
);
test('explicit options.random produces expected id', () => {
const id = v1({
msecs: 1321651533573,
nsecs: 5432,
random: randomBytesFixture,
});
assert.strictEqual(id, 'd9428888-122b-11e1-81ea-119156bec4fb');
});
});
-35
View File
@@ -1,35 +0,0 @@
import * as assert from 'assert';
import test, { describe } from 'node:test';
import v1 from '../v1.js';
// Since the clockseq is cached in the module this test must run in a separate file in order to
// initialize the v1 clockseq with controlled random data.
describe('v1-rng', () => {
const randomBytesFixture = Uint8Array.of(
0x10,
0x91,
0x56,
0xbe,
0xc4,
0xfb,
0xc1,
0xea,
0x71,
0xb4,
0xef,
0xe1,
0x67,
0x1c,
0x58,
0x36
);
test('explicit options.random produces expected id', () => {
const id = v1({
msecs: 1321651533573,
nsecs: 5432,
rng: () => randomBytesFixture,
});
assert.strictEqual(id, 'd9428888-122b-11e1-81ea-119156bec4fb');
});
});
+128 -94
View File
@@ -1,20 +1,56 @@
import * as assert from 'assert';
import test, { describe } from 'node:test';
import v1 from '../v1.js';
import parse from '../parse.js';
import v1, { updateV1State } from '../v1.js';
// Verify ordering of v1 ids created with explicit times
const TIME = 1321644961388; // 2011-11-18 11:36:01.388-08:00
// Fixture values for testing with the rfc v1 UUID example:
// https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv1-value
const RFC_V1 = 'c232ab00-9414-11ec-b3c8-9f68deced846';
const RFC_V1_BYTES = parse(RFC_V1);
// `options` for producing the above RFC UUID
const RFC_OPTIONS = {
msecs: 0x17f22e279b0,
nsecs: 0,
clockseq: 0x33c8,
node: Uint8Array.of(0x9f, 0x68, 0xde, 0xce, 0xd8, 0x46),
};
// random bytes for producing the above RFC UUID
const RFC_RANDOM = Uint8Array.of(
// unused
0,
0,
0,
0,
0,
0,
0,
0,
// clock seq
RFC_OPTIONS.clockseq >> 8,
RFC_OPTIONS.clockseq & 0xff,
// node
...RFC_OPTIONS.node
);
// Compare v1 timestamp fields chronologically
function compareV1TimeField(a: string, b: string) {
a = a.split('-').slice(0, 3).reverse().join('');
b = b.split('-').slice(0, 3).reverse().join('');
return a < b ? -1 : a > b ? 1 : 0;
}
describe('v1', () => {
test('v1 sort order (default)', () => {
const ids = [v1(), v1(), v1(), v1(), v1()];
const sorted = [...ids].sort((a, b) => {
a = a.split('-').reverse().join('-');
b = b.split('-').reverse().join('-');
return a < b ? -1 : a > b ? 1 : 0;
});
const sorted = [...ids].sort(compareV1TimeField);
assert.deepEqual(ids, sorted);
});
@@ -28,110 +64,108 @@ describe('v1', () => {
v1({ msecs: TIME + 28 * 24 * 3600 * 1000 }),
];
const sorted = [...ids].sort((a, b) => {
a = a.split('-').reverse().join('-');
b = b.split('-').reverse().join('-');
return a < b ? -1 : a > b ? 1 : 0;
});
const sorted = [...ids].sort(compareV1TimeField);
assert.deepEqual(ids, sorted);
});
test('msec', () => {
assert.ok(
v1({ msecs: TIME }) !== v1({ msecs: TIME }),
'IDs created at same msec are different'
);
test('v1(options)', () => {
assert.equal(v1({ msecs: RFC_OPTIONS.msecs, random: RFC_RANDOM }), RFC_V1, 'minimal options');
assert.equal(v1(RFC_OPTIONS), RFC_V1, 'full options');
});
test('exception thrown when > 10k ids created in 1ms', () => {
assert.throws(function () {
v1({ msecs: TIME, nsecs: 10000 });
}, 'throws when > 10K ids created in 1 ms');
test('v1(options) equality', () => {
assert.notEqual(v1({ msecs: TIME }), v1({ msecs: TIME }), 'UUIDs with minimal options differ');
assert.equal(v1(RFC_OPTIONS), v1(RFC_OPTIONS), 'UUIDs with full options are identical');
});
test('clock regression by msec', () => {
// Verify clock regression bumps clockseq
const uidt = v1({ msecs: TIME });
const uidtb = v1({ msecs: TIME - 1 });
assert.ok(
parseInt(uidtb.split('-')[3], 16) - parseInt(uidt.split('-')[3], 16) === 1,
'Clock regression by msec increments the clockseq'
);
});
test('clock regression by nsec', () => {
// Verify clock regression bumps clockseq
const uidtn = v1({ msecs: TIME, nsecs: 10 });
const uidtnb = v1({ msecs: TIME, nsecs: 9 });
assert.ok(
parseInt(uidtnb.split('-')[3], 16) - parseInt(uidtn.split('-')[3], 16) === 1,
'Clock regression by nsec increments the clockseq'
);
});
const fullOptions = {
msecs: 1321651533573,
nsecs: 5432,
clockseq: 0x385c,
node: Uint8Array.of(0x61, 0xcd, 0x3c, 0xbb, 0x32, 0x10),
};
test('explicit options produce expected id', () => {
// Verify explicit options produce expected id
const id = v1(fullOptions);
assert.ok(
id === 'd9428888-122b-11e1-b85c-61cd3cbb3210',
'Explicit options produce expected id'
);
});
test('ids spanning 1ms boundary are 100ns apart', () => {
// Verify adjacent ids across a msec boundary are 1 time unit apart
const u0 = v1({ msecs: TIME, nsecs: 9999 });
const u1 = v1({ msecs: TIME + 1, nsecs: 0 });
const before = u0.split('-')[0];
const after = u1.split('-')[0];
const dt = parseInt(after, 16) - parseInt(before, 16);
assert.ok(dt === 1, 'Ids spanning 1ms boundary are 100ns apart');
});
const expectedBytes = Uint8Array.of(
217,
66,
136,
136,
18,
43,
17,
225,
184,
92,
97,
205,
60,
187,
50,
16
);
test('fills one UUID into a buffer as expected', () => {
const buffer = new Uint8Array(16);
const result = v1(fullOptions, buffer);
assert.deepEqual(buffer, expectedBytes);
const result = v1(RFC_OPTIONS, buffer);
assert.deepEqual(buffer, RFC_V1_BYTES);
assert.strictEqual(buffer, result);
});
test('fills two UUIDs into a buffer as expected', () => {
const buffer = new Uint8Array(32);
v1(fullOptions, buffer, 0);
v1(fullOptions, buffer, 16);
v1(RFC_OPTIONS, buffer, 0);
v1(RFC_OPTIONS, buffer, 16);
const expectedBuf = new Uint8Array(32);
expectedBuf.set(expectedBytes);
expectedBuf.set(expectedBytes, 16);
expectedBuf.set(RFC_V1_BYTES);
expectedBuf.set(RFC_V1_BYTES, 16);
assert.deepEqual(buffer, expectedBuf);
});
test('v1() state transitions', () => {
// Test fixture for internal state passed into updateV1State function
const PRE_STATE = {
msecs: 10,
nsecs: 20,
clockseq: 0x1234,
node: Uint8Array.of(0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc),
};
// Note: The test code, below, passes RFC_RANDOM as the `rnds` argument for
// convenience. This allows us to test that fields have been initialized from
// the rnds argument by testing for RFC_OPTIONS values in the output state.
const tests = [
{
title: 'initial state',
state: {},
now: 10,
expected: {
msecs: 10, // -> now
nsecs: 0, // -> init
clockseq: RFC_OPTIONS.clockseq, // -> random
node: RFC_OPTIONS.node, // -> random
},
},
{
title: 'same time interval',
state: { ...PRE_STATE },
now: PRE_STATE.msecs,
expected: {
...PRE_STATE,
nsecs: 21, // -> +1
},
},
{
title: 'new time interval',
state: { ...PRE_STATE },
now: PRE_STATE.msecs + 1,
expected: {
...PRE_STATE,
msecs: PRE_STATE.msecs + 1, // -> +1
nsecs: 0, // -> init
},
},
{
title: 'same time interval (nsecs overflow)',
state: { ...PRE_STATE, nsecs: 9999 },
now: PRE_STATE.msecs,
expected: {
...PRE_STATE,
nsecs: 0, // -> init
clockseq: RFC_OPTIONS.clockseq, // -> init
node: RFC_OPTIONS.node, // -> init
},
},
{
title: 'time regression',
state: { ...PRE_STATE },
now: PRE_STATE.msecs - 1,
expected: {
...PRE_STATE,
msecs: PRE_STATE.msecs - 1, // -> now
clockseq: RFC_OPTIONS.clockseq, // -> init
node: RFC_OPTIONS.node, // -> init
},
},
];
for (const { title, state, now, expected } of tests) {
assert.deepStrictEqual(updateV1State(state, now, RFC_RANDOM), expected, `Failed: ${title}`);
}
});
});
+18 -18
View File
@@ -9,29 +9,29 @@ describe('v6', () => {
const V6_ID = '1ef21d2f-1207-6660-8c4f-419efbd44d48';
const fullOptions = {
msecs: 1321651533573,
nsecs: 5432,
msecs: 0x133b891f705,
nsecs: 0x1538,
clockseq: 0x385c,
node: Uint8Array.of(0x61, 0xcd, 0x3c, 0xbb, 0x32, 0x10),
};
const EXPECTED_BYTES = Uint8Array.of(
30,
17,
34,
189,
148,
40,
104,
136,
184,
92,
97,
205,
60,
187,
50,
16
0x1e,
0x11,
0x22,
0xbd,
0x94,
0x28,
0x68,
0x88,
0xb8,
0x5c,
0x61,
0xcd,
0x3c,
0xbb,
0x32,
0x10
);
test('default behavior', () => {
+32 -32
View File
@@ -134,7 +134,38 @@ describe('v7', () => {
}
});
test('internal state updates properly', () => {
test('can supply seq', () => {
let seq = 0x12345;
let uuid = v7({
msecs: RFC_MSECS,
seq,
});
assert.strictEqual(uuid.substr(0, 25), '017f22e2-79b0-7000-848d-1');
seq = 0x6fffffff;
uuid = v7({
msecs: RFC_MSECS,
seq,
});
assert.strictEqual(uuid.substring(0, 25), '017f22e2-79b0-76ff-bfff-f');
});
test('internal seq is reset upon timestamp change', () => {
v7({
msecs: RFC_MSECS,
seq: 0x6fffffff,
});
const uuid = v7({
msecs: RFC_MSECS + 1,
});
assert.ok(uuid.indexOf('fff') !== 15);
});
test('v7() state transitions', () => {
const tests = [
{
title: 'new time interval',
@@ -191,37 +222,6 @@ describe('v7', () => {
}
});
test('can supply seq', () => {
let seq = 0x12345;
let uuid = v7({
msecs: RFC_MSECS,
seq,
});
assert.strictEqual(uuid.substr(0, 25), '017f22e2-79b0-7000-848d-1');
seq = 0x6fffffff;
uuid = v7({
msecs: RFC_MSECS,
seq,
});
assert.strictEqual(uuid.substring(0, 25), '017f22e2-79b0-76ff-bfff-f');
});
test('internal seq is reset upon timestamp change', () => {
v7({
msecs: RFC_MSECS,
seq: 0x6fffffff,
});
const uuid = v7({
msecs: RFC_MSECS + 1,
});
assert.ok(uuid.indexOf('fff') !== 15);
});
test('flipping bits changes the result', () => {
// convert uint8array to BigInt (BE)
const asBigInt = (buf: Uint8Array) => buf.reduce((acc, v) => (acc << 8n) | BigInt(v), 0n);
+135 -88
View File
@@ -7,133 +7,180 @@ import { unsafeStringify } from './stringify.js';
// Inspired by https://github.com/LiosK/UUID.js
// and http://docs.python.org/library/uuid.html
let _nodeId: Uint8Array;
let _clockseq: number;
type V1State = {
node?: Uint8Array; // node id (47-bit random)
clockseq?: number; // sequence number (14-bit)
// Previous uuid creation time
let _lastMSecs = 0;
let _lastNSecs = 0;
// v1 & v6 timestamps are a pain to deal with. They specify time from the
// Gregorian epoch in 100ns intervals, which requires values with 57+ bits of
// precision. But that's outside the precision of IEEE754 floats (i.e. JS
// numbers). To work around this, we represent them internally using 'msecs'
// (milliseconds since unix epoch) and 'nsecs' (100-nanoseconds offset from
// `msecs`).
msecs?: number; // timestamp (milliseconds, unix epoch)
nsecs?: number; // timestamp (100-nanoseconds offset from 'msecs')
};
const _state: V1State = {};
function v1(options?: Version1Options, buf?: undefined, offset?: number): string;
function v1(options?: Version1Options, buf?: Uint8Array, offset?: number): Uint8Array;
function v1(options?: Version1Options, buf?: Uint8Array, offset?: number): UUIDTypes {
options ??= {};
let bytes: Uint8Array;
let i = (buf && offset) || 0;
const b = buf || new Uint8Array(16);
let node = options.node;
let clockseq = options.clockseq;
// v1 only: Use cached `node` and `clockseq` values
if (!options._v6) {
if (!node) {
node = _nodeId;
}
if (clockseq == null) {
clockseq = _clockseq;
// Extract _v6 flag from options, clearing options if appropriate
const isV6 = options?._v6 ?? false;
if (options) {
const optionsKeys = Object.keys(options);
if (optionsKeys.length === 1 && optionsKeys[0] === '_v6') {
options = undefined;
}
}
// Handle cases where we need entropy. We do this lazily to minimize issues
// related to insufficient system entropy. See #189
if (node == null || clockseq == null) {
const seedBytes = options.random || (options.rng || rng)();
if (options) {
// With options: Make UUID independent of internal state
bytes = v1Bytes(
options.random ?? options.rng?.() ?? rng(),
options.msecs,
options.nsecs,
options.clockseq,
options.node,
buf,
offset
);
} else {
// Without options: Make UUID from internal state
const now = Date.now();
const rnds = rng();
// Randomize node
if (node == null) {
node = Uint8Array.of(
seedBytes[0],
seedBytes[1],
seedBytes[2],
seedBytes[3],
seedBytes[4],
seedBytes[5]
);
updateV1State(_state, now, rnds);
// v1 only: cache node value for reuse
if (!_nodeId && !options._v6) {
// per RFC4122 4.5: Set MAC multicast bit (v1 only)
node[0] |= 0x01; // Set multicast bit
// Geenerate UUID. Note that v6 uses random values for `clockseq` and
// `node`.
//
// https://www.rfc-editor.org/rfc/rfc9562.html#section-5.6-4
bytes = v1Bytes(
rnds,
_state.msecs,
_state.nsecs,
// v6 UUIDs get random `clockseq` and `node` for every UUID
// https://www.rfc-editor.org/rfc/rfc9562.html#section-5.6-4
isV6 ? undefined : _state.clockseq,
isV6 ? undefined : _state.node,
buf,
offset
);
}
_nodeId = node;
}
}
return buf ? bytes : unsafeStringify(bytes);
}
// Randomize clockseq
if (clockseq == null) {
// Per 4.2.2, randomize (14 bit) clockseq
clockseq = ((seedBytes[6] << 8) | seedBytes[7]) & 0x3fff;
if (_clockseq === undefined && !options._v6) {
_clockseq = clockseq;
}
// (Private!) Do not use. This method is only exported for testing purposes
// and may change without notice.
export function updateV1State(state: V1State, now: number, rnds: Uint8Array) {
state.msecs ??= -Infinity;
state.nsecs ??= 0;
// Update timestamp
if (now === state.msecs) {
// Same msec-interval = simulate higher clock resolution by bumping `nsecs`
// https://www.rfc-editor.org/rfc/rfc9562.html#section-6.1-2.6
state.nsecs++;
// Check for `nsecs` overflow (nsecs is capped at 10K intervals / msec)
if (state.nsecs >= 10000) {
// Prior to uuid@11 this would throw an error, however the RFCs allow for
// changing the node in this case. This slightly breaks monotonicity at
// msec granularity, but that's not a significant concern.
// https://www.rfc-editor.org/rfc/rfc9562.html#section-6.1-2.16
state.node = undefined;
state.nsecs = 0;
}
} else if (now > state.msecs) {
// Reset nsec counter when clock advances to a new msec interval
state.nsecs = 0;
} else if (now < state.msecs) {
// Handle clock regression
// https://www.rfc-editor.org/rfc/rfc9562.html#section-6.1-2.7
//
// Note: Unsetting node here causes both it and clockseq to be randomized,
// below.
state.node = undefined;
}
// v1 & v6 timestamps are 100 nano-second units since the Gregorian epoch,
// (1582-10-15 00:00). JSNumbers aren't precise enough for this, so time is
// handled internally as 'msecs' (integer milliseconds) and 'nsecs'
// (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00.
let msecs = options.msecs !== undefined ? options.msecs : Date.now();
// Init node and clock sequence (do this after timestamp update which may
// reset the node) https://www.rfc-editor.org/rfc/rfc9562.html#section-5.1-7
//
// Note:
if (!state.node) {
state.node = rnds.slice(10, 16);
// Per 4.2.1.2, use count of uuid's generated during the current clock
// cycle to simulate higher resolution clock
let nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1;
// Set multicast bit
// https://www.rfc-editor.org/rfc/rfc9562.html#section-6.10-3
state.node[0] |= 0x01; // Set multicast bit
// Time since last uuid creation (in msecs)
const dt = msecs - _lastMSecs + (nsecs - _lastNSecs) / 10000;
// Per 4.2.1.2, Bump clockseq on clock regression
if (dt < 0 && options.clockseq === undefined) {
clockseq = (clockseq + 1) & 0x3fff;
// Clock sequence must be randomized
// https://www.rfc-editor.org/rfc/rfc9562.html#section-5.1-8
state.clockseq = ((rnds[8] << 8) | rnds[9]) & 0x3fff;
}
// Reset nsecs if clock regresses (new clockseq) or we've moved onto a new
// time interval
if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) {
nsecs = 0;
state.msecs = now;
return state;
}
function v1Bytes(
rnds: Uint8Array,
msecs?: number,
nsecs?: number,
clockseq?: number,
node?: Uint8Array,
buf?: Uint8Array,
offset = 0
) {
// Defaults
if (!buf) {
buf = new Uint8Array(16);
offset = 0;
}
msecs ??= Date.now();
nsecs ??= 0;
clockseq ??= ((rnds[8] << 8) | rnds[9]) & 0x3fff;
node ??= rnds.slice(10, 16);
// Per 4.2.1.2 Throw error if too many uuids are requested
if (nsecs >= 10000) {
throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");
}
_lastMSecs = msecs;
_lastNSecs = nsecs;
_clockseq = clockseq;
// Per 4.1.4 - Convert from unix epoch to Gregorian epoch
// Offset to Gregorian epoch
// https://www.rfc-editor.org/rfc/rfc9562.html#section-5.1-1
msecs += 12219292800000;
// `time_low`
const tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
b[i++] = (tl >>> 24) & 0xff;
b[i++] = (tl >>> 16) & 0xff;
b[i++] = (tl >>> 8) & 0xff;
b[i++] = tl & 0xff;
buf[offset++] = (tl >>> 24) & 0xff;
buf[offset++] = (tl >>> 16) & 0xff;
buf[offset++] = (tl >>> 8) & 0xff;
buf[offset++] = tl & 0xff;
// `time_mid`
const tmh = ((msecs / 0x100000000) * 10000) & 0xfffffff;
b[i++] = (tmh >>> 8) & 0xff;
b[i++] = tmh & 0xff;
buf[offset++] = (tmh >>> 8) & 0xff;
buf[offset++] = tmh & 0xff;
// `time_high_and_version`
b[i++] = ((tmh >>> 24) & 0xf) | 0x10; // include version
b[i++] = (tmh >>> 16) & 0xff;
buf[offset++] = ((tmh >>> 24) & 0xf) | 0x10; // include version
buf[offset++] = (tmh >>> 16) & 0xff;
// `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant)
b[i++] = (clockseq >>> 8) | 0x80;
// `clock_seq_hi_and_reserved` | variant
buf[offset++] = (clockseq >>> 8) | 0x80;
// `clock_seq_low`
b[i++] = clockseq & 0xff;
buf[offset++] = clockseq & 0xff;
// `node`
for (let n = 0; n < 6; ++n) {
b[i + n] = node[n];
buf[offset++] = node[n];
}
return buf || unsafeStringify(b);
return buf;
}
export default v1;
+6 -6
View File
@@ -3,14 +3,11 @@ import rng from './rng.js';
import { unsafeStringify } from './stringify.js';
type V7State = {
msecs: number; // time, milliseconds
seq: number; // sequence number (32-bits)
msecs?: number; // time, milliseconds
seq?: number; // sequence number (32-bits)
};
const _state: V7State = {
msecs: -Infinity,
seq: 0,
};
const _state: V7State = {};
function v7(options?: Version7Options, buf?: undefined, offset?: number): string;
function v7(options?: Version7Options, buf?: Uint8Array, offset?: number): Uint8Array;
@@ -42,6 +39,9 @@ function v7(options?: Version7Options, buf?: Uint8Array, offset?: number): UUIDT
// (Private!) Do not use. This method is only exported for testing purposes
// and may change without notice.
export function updateV7State(state: V7State, now: number, rnds: Uint8Array) {
state.msecs ??= -Infinity;
state.seq ??= 0;
if (now > state.msecs) {
// Time has moved on! Pick a new random sequence number
state.seq = (rnds[6] << 23) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9];