Skip to content

Commit 9dbd1cd

Browse files
broofarobinpokorny
andauthoredJul 18, 2024··
fix!: refactor v7 internal state and options logic, fixes #764 (#779)
Co-authored-by: Robert Kieffer <robert@broofa.com> Co-authored-by: Robin Pokorny <me@robinpokorny.com>
1 parent 7eff835 commit 9dbd1cd

File tree

5 files changed

+207
-242
lines changed

5 files changed

+207
-242
lines changed
 

‎README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -338,10 +338,10 @@ Create an RFC version 7 (random) UUID
338338
| | |
339339
| --- | --- |
340340
| [`options`] | `Object` with one or more of the following properties: |
341-
| [`options.msecs`] | RFC "timestamp" field (`Number` of milliseconds, unix epoch) |
341+
| [`options.msecs`] | RFC "timestamp" field (`Number` of milliseconds, unix epoch). Default = `Date.now()` |
342342
| [`options.random`] | `Array` of 16 random bytes (0-255) |
343343
| [`options.rng`] | Alternative to `options.random`, a `Function` that returns an `Array` of 16 random bytes (0-255) |
344-
| [`options.seq`] | 31 bit monotonic sequence counter as `Number` between 0 - 0x7fffffff |
344+
| [`options.seq`] | 32-bit sequence `Number` between 0 - 0xffffffff. This may be provided to help insure uniqueness for UUIDs generated within the same millisecond time interval. Default = random value. |
345345
| [`buffer`] | `Array \| Buffer` If specified, uuid will be written here in byte-form, starting at `offset` |
346346
| [`offset` = 0] | `Number` Index to start writing UUID bytes in `buffer` |
347347
| _returns_ | UUID `String` if no `buffer` is specified, otherwise returns `buffer` |

‎README_js.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -346,10 +346,10 @@ Create an RFC version 7 (random) UUID
346346
| | |
347347
| --- | --- |
348348
| [`options`] | `Object` with one or more of the following properties: |
349-
| [`options.msecs`] | RFC "timestamp" field (`Number` of milliseconds, unix epoch) |
349+
| [`options.msecs`] | RFC "timestamp" field (`Number` of milliseconds, unix epoch). Default = `Date.now()` |
350350
| [`options.random`] | `Array` of 16 random bytes (0-255) |
351351
| [`options.rng`] | Alternative to `options.random`, a `Function` that returns an `Array` of 16 random bytes (0-255) |
352-
| [`options.seq`] | 31 bit monotonic sequence counter as `Number` between 0 - 0x7fffffff |
352+
| [`options.seq`] | 32-bit sequence `Number` between 0 - 0xffffffff. This may be provided to help insure uniqueness for UUIDs generated within the same millisecond time interval. Default = random value. |
353353
| [`buffer`] | `Array \| Buffer` If specified, uuid will be written here in byte-form, starting at `offset` |
354354
| [`offset` = 0] | `Number` Index to start writing UUID bytes in `buffer` |
355355
| _returns_ | UUID `String` if no `buffer` is specified, otherwise returns `buffer` |

‎package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@
113113
"test:browser": "wdio run ./wdio.conf.js",
114114
"test:node": "npm-run-all --parallel examples:node:**",
115115
"test:pack": "./scripts/testpack.sh",
116-
"test:watch": "node --test --watch dist/esm/test",
117-
"test": "node --test dist/esm/test"
116+
"test:watch": "node --test --enable-source-maps --watch dist/esm/test",
117+
"test": "node --test --enable-source-maps dist/esm/test"
118118
},
119119
"repository": {
120120
"type": "git",

‎src/test/v7.test.ts

+123-110
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,40 @@
11
import * as assert from 'assert';
22
import test, { describe } from 'node:test';
33
import { Version7Options } from '../_types.js';
4-
import v7 from '../v7.js';
4+
import parse from '../parse.js';
55
import stringify from '../stringify.js';
6+
import v7, { updateV7State } from '../v7.js';
67

7-
/**
8-
* fixture bit layout:
9-
* ref: https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv7-value
10-
*
11-
* expectedBytes was calculated using this script:
12-
* https://gist.github.com/d5382ac3a1ce4ba9ba40a90d9da8cbf1
13-
*
14-
* -------------------------------
15-
* field bits value
16-
* -------------------------------
17-
* unix_ts_ms 48 0x17F22E279B0
18-
* ver 4 0x7
19-
* rand_a 12 0xCC3
20-
* var 2 b10
21-
* rand_b 62 b01, 0x8C4DC0C0C07398F
22-
* -------------------------------
23-
* total 128
24-
* -------------------------------
25-
* final: 017f22e2-79b0-7cc3-98c4-dc0c0c07398f
26-
*/
8+
// Fixture values for testing with the rfc v7 UUID example:
9+
// https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv7-value
10+
const RFC_V7 = '017f22e2-79b0-7cc3-98c4-dc0c0c07398f';
11+
const RFC_V7_BYTES = parse('017f22e2-79b0-7cc3-98c4-dc0c0c07398f');
12+
const RFC_MSECS = 0x17f22e279b0;
2713

28-
describe('v7', () => {
29-
const msecsFixture = 1645557742000;
30-
const seqFixture = 0x661b189b;
31-
32-
const randomBytesFixture = Uint8Array.of(
33-
0x10,
34-
0x91,
35-
0x56,
36-
0xbe,
37-
0xc4,
38-
0xfb,
39-
0x0c,
40-
0xc3,
41-
0x18,
42-
0xc4,
43-
0xdc,
44-
0x0c,
45-
0x0c,
46-
0x07,
47-
0x39,
48-
0x8f
49-
);
14+
// `option.seq` for the above RFC uuid
15+
const RFC_SEQ = (0x0cc3 << 20) | (0x98c4dc >> 2);
5016

51-
const expectedBytes = Uint8Array.of(
52-
1,
53-
127,
54-
34,
55-
226,
56-
121,
57-
176,
58-
124,
59-
195,
60-
152,
61-
196,
62-
220,
63-
12,
64-
12,
65-
7,
66-
57,
67-
143
68-
);
17+
// `option,random` for the above RFC uuid
18+
const RFC_RANDOM = Uint8Array.of(
19+
0x10,
20+
0x91,
21+
0x56,
22+
0xbe,
23+
0xc4,
24+
0xfb,
25+
0x0c,
26+
0xc3,
27+
0x18,
28+
0xc4,
29+
0x6c,
30+
0x0c,
31+
0x0c,
32+
0x07,
33+
0x39,
34+
0x8f
35+
);
6936

37+
describe('v7', () => {
7038
test('subsequent UUIDs are different', () => {
7139
const id1 = v7();
7240
const id2 = v7();
@@ -75,25 +43,25 @@ describe('v7', () => {
7543

7644
test('explicit options.random and options.msecs produces expected result', () => {
7745
const id = v7({
78-
random: randomBytesFixture,
79-
msecs: msecsFixture,
80-
seq: seqFixture,
46+
random: RFC_RANDOM,
47+
msecs: RFC_MSECS,
48+
seq: RFC_SEQ,
8149
});
82-
assert.strictEqual(id, '017f22e2-79b0-7cc3-98c4-dc0c0c07398f');
50+
assert.strictEqual(id, RFC_V7);
8351
});
8452

8553
test('explicit options.rng produces expected result', () => {
8654
const id = v7({
87-
rng: () => randomBytesFixture,
88-
msecs: msecsFixture,
89-
seq: seqFixture,
55+
rng: () => RFC_RANDOM,
56+
msecs: RFC_MSECS,
57+
seq: RFC_SEQ,
9058
});
91-
assert.strictEqual(id, '017f22e2-79b0-7cc3-98c4-dc0c0c07398f');
59+
assert.strictEqual(id, RFC_V7);
9260
});
9361

9462
test('explicit options.msecs produces expected result', () => {
9563
const id = v7({
96-
msecs: msecsFixture,
64+
msecs: RFC_MSECS,
9765
});
9866
assert.strictEqual(id.indexOf('017f22e2'), 0);
9967
});
@@ -102,13 +70,15 @@ describe('v7', () => {
10270
const buffer = new Uint8Array(16);
10371
const result = v7(
10472
{
105-
random: randomBytesFixture,
106-
msecs: msecsFixture,
107-
seq: seqFixture,
73+
random: RFC_RANDOM,
74+
msecs: RFC_MSECS,
75+
seq: RFC_SEQ,
10876
},
10977
buffer
11078
);
111-
assert.deepEqual(buffer, expectedBytes);
79+
stringify(buffer);
80+
81+
assert.deepEqual(buffer, RFC_V7_BYTES);
11282
assert.strictEqual(buffer, result);
11383
});
11484

@@ -117,25 +87,25 @@ describe('v7', () => {
11787

11888
v7(
11989
{
120-
random: randomBytesFixture,
121-
msecs: msecsFixture,
122-
seq: seqFixture,
90+
random: RFC_RANDOM,
91+
msecs: RFC_MSECS,
92+
seq: RFC_SEQ,
12393
},
12494
buffer,
12595
0
12696
);
12797
v7(
12898
{
129-
random: randomBytesFixture,
130-
msecs: msecsFixture,
131-
seq: seqFixture,
99+
random: RFC_RANDOM,
100+
msecs: RFC_MSECS,
101+
seq: RFC_SEQ,
132102
},
133103
buffer,
134104
16
135105
);
136106
const expected = new Uint8Array(32);
137-
expected.set(expectedBytes);
138-
expected.set(expectedBytes, 16);
107+
expected.set(RFC_V7_BYTES);
108+
expected.set(RFC_V7_BYTES, 16);
139109
assert.deepEqual(buffer, expected);
140110
});
141111

@@ -146,15 +116,15 @@ describe('v7', () => {
146116
test('lexicographical sorting is preserved', () => {
147117
let id;
148118
let prior;
149-
let msecs = msecsFixture;
119+
let msecs = RFC_MSECS;
150120
for (let i = 0; i < 20000; ++i) {
151121
if (i % 1500 === 0) {
152122
// every 1500 runs increment msecs so seq is
153123
// reinitialized, simulating passage of time
154124
msecs += 1;
155125
}
156126

157-
id = v7({ msecs });
127+
id = v7({ msecs, seq: i });
158128

159129
if (prior !== undefined) {
160130
assert.ok(prior < id, `${prior} < ${id}`);
@@ -164,46 +134,89 @@ describe('v7', () => {
164134
}
165135
});
166136

167-
test('handles seq rollover', () => {
168-
const msecs = msecsFixture;
169-
const a = v7({
170-
msecs,
171-
seq: 0x7fffffff,
172-
});
173-
174-
v7({ msecs });
175-
176-
const c = v7({ msecs });
177-
178-
assert.ok(a < c, `${a} < ${c}`);
137+
test('internal state updates properly', () => {
138+
const tests = [
139+
{
140+
title: 'new time interval',
141+
state: { msecs: 1, seq: 123 },
142+
now: 2,
143+
expected: {
144+
msecs: 2, // time interval should update
145+
seq: 0x6c318c4, // sequence should be randomized
146+
},
147+
},
148+
{
149+
title: 'same time interval',
150+
state: { msecs: 1, seq: 123 },
151+
now: 1,
152+
expected: {
153+
msecs: 1, // timestamp unchanged
154+
seq: 124, // sequence increments
155+
},
156+
},
157+
{
158+
title: 'same time interval (sequence rollover)',
159+
state: { msecs: 1, seq: 0xffffffff },
160+
now: 1,
161+
expected: {
162+
msecs: 2, // timestamp increments
163+
seq: 0, // sequence rolls over
164+
},
165+
},
166+
{
167+
title: 'time regression',
168+
state: { msecs: 2, seq: 123 },
169+
now: 1,
170+
expected: {
171+
msecs: 2, // timestamp unchanged
172+
seq: 124, // sequence increments
173+
},
174+
},
175+
{
176+
title: 'time regression (sequence rollover)',
177+
state: { msecs: 2, seq: 0xffffffff },
178+
now: 1,
179+
expected: {
180+
// timestamp increments (crazy, right? The system clock goes backwards
181+
// but the UUID timestamp moves forward? Weird, but it's what's
182+
// required to maintain monotonicity... and this is why we have unit
183+
// tests!)
184+
msecs: 3,
185+
seq: 0, // sequence rolls over
186+
},
187+
},
188+
];
189+
for (const { title, state, now, expected } of tests) {
190+
assert.deepStrictEqual(updateV7State(state, now, RFC_RANDOM), expected, `Failed: ${title}`);
191+
}
179192
});
180193

181194
test('can supply seq', () => {
182195
let seq = 0x12345;
183196
let uuid = v7({
184-
msecs: msecsFixture,
197+
msecs: RFC_MSECS,
185198
seq,
186199
});
187200

188-
assert.strictEqual(uuid.substr(0, 25), '017f22e2-79b0-7000-891a-2');
201+
assert.strictEqual(uuid.substr(0, 25), '017f22e2-79b0-7000-848d-1');
189202

190203
seq = 0x6fffffff;
191204
uuid = v7({
192-
msecs: msecsFixture,
205+
msecs: RFC_MSECS,
193206
seq,
194207
});
195208

196-
assert.strictEqual(uuid.substr(0, 25), '017f22e2-79b0-7dff-bfff-f');
209+
assert.strictEqual(uuid.substring(0, 25), '017f22e2-79b0-76ff-bfff-f');
197210
});
198211

199212
test('internal seq is reset upon timestamp change', () => {
200213
v7({
201-
msecs: msecsFixture,
214+
msecs: RFC_MSECS,
202215
seq: 0x6fffffff,
203216
});
204217

205218
const uuid = v7({
206-
msecs: msecsFixture + 1,
219+
msecs: RFC_MSECS + 1,
207220
});
208221

209222
assert.ok(uuid.indexOf('fff') !== 15);
@@ -216,18 +229,18 @@ describe('v7', () => {
216229
// convert the given number of bits (LE) to number
217230
const asNumber = (bits: number, data: bigint) => Number(BigInt.asUintN(bits, data));
218231

219-
// flip the nth bit (BE) in a BigInt
232+
// flip the nth bit (BE) in a BigInt
220233
const flip = (data: bigint, n: number) => data ^ (1n << BigInt(127 - n));
221234

222235
// Extract v7 `options` from a (BigInt) UUID
223236
const optionsFrom = (data: bigint): Version7Options => {
224-
const ms = asNumber(48, data >> (128n - 48n));
225-
const hi = asNumber(12, data >> (43n + 19n + 2n));
226-
const lo = asNumber(19, data >> 43n);
227-
const r = BigInt.asUintN(43, data);
237+
const ms = asNumber(48, data >> 80n);
238+
const hi = asNumber(12, data >> 64n);
239+
const lo = asNumber(20, data >> 42n);
240+
const r = BigInt.asUintN(42, data);
228241
return {
229242
msecs: ms,
230-
seq: (hi << 19) | lo,
243+
seq: (hi << 20) | lo,
231244
random: Uint8Array.from([
232245
...Array(10).fill(0),
233246
...Array(6)
@@ -247,8 +260,8 @@ describe('v7', () => {
247260
}
248261
const flipped = flip(data, i);
249262
assert.strictEqual(
250-
asBigInt(v7(optionsFrom(flipped), buf)),
251-
flipped,
263+
asBigInt(v7(optionsFrom(flipped), buf)).toString(16),
264+
flipped.toString(16),
252265
`Unequal uuids at bit ${i}`
253266
);
254267
assert.notStrictEqual(stringify(buf), id);

‎src/v7.ts

+78-126
Original file line numberDiff line numberDiff line change
@@ -2,154 +2,106 @@ import { UUIDTypes, Version7Options } from './_types.js';
22
import rng from './rng.js';
33
import { unsafeStringify } from './stringify.js';
44

5-
/**
6-
* UUID V7 - Unix Epoch time-based UUID
7-
*
8-
* The IETF has published RFC9562, introducing 3 new UUID versions (6,7,8). This
9-
* implementation of V7 is based on the accepted, though not yet approved,
10-
* revisions.
11-
*
12-
* RFC 9562:https://www.rfc-editor.org/rfc/rfc9562.html Universally Unique
13-
* IDentifiers (UUIDs)
14-
15-
*
16-
* Sample V7 value:
17-
* https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv7-value
18-
*
19-
* Monotonic Bit Layout: RFC rfc9562.6.2 Method 1, Dedicated Counter Bits ref:
20-
* https://www.rfc-editor.org/rfc/rfc9562.html#section-6.2-5.1
21-
*
22-
* 0 1 2 3 0 1 2 3 4 5 6
23-
* 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
24-
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
25-
* | unix_ts_ms |
26-
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
27-
* | unix_ts_ms | ver | seq_hi |
28-
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
29-
* |var| seq_low | rand |
30-
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
31-
* | rand |
32-
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
33-
*
34-
* seq is a 31 bit serialized counter; comprised of 12 bit seq_hi and 19 bit
35-
* seq_low, and randomly initialized upon timestamp change. 31 bit counter size
36-
* was selected as any bitwise operations in node are done as _signed_ 32 bit
37-
* ints. we exclude the sign bit.
38-
*/
39-
40-
let _seqLow: number | null = null;
41-
let _seqHigh: number | null = null;
42-
let _msecs = 0;
5+
type V7State = {
6+
msecs: number; // time, milliseconds
7+
seq: number; // sequence number (32-bits)
8+
};
9+
10+
const _state: V7State = {
11+
msecs: -Infinity,
12+
seq: 0,
13+
};
4314

4415
function v7(options?: Version7Options, buf?: undefined, offset?: number): string;
4516
function v7(options?: Version7Options, buf?: Uint8Array, offset?: number): Uint8Array;
4617
function v7(options?: Version7Options, buf?: Uint8Array, offset?: number): UUIDTypes {
47-
options ??= {};
48-
49-
// initialize buffer and pointer
50-
let i = (buf && offset) || 0;
51-
const b = buf || new Uint8Array(16);
52-
53-
// rnds is Uint8Array(16) filled with random bytes
54-
const rnds = options.random || (options.rng || rng)();
55-
56-
// milliseconds since unix epoch, 1970-01-01 00:00
57-
const msecs = options.msecs !== undefined ? options.msecs : Date.now();
58-
59-
// seq is user provided 31 bit counter
60-
let seq = options.seq !== undefined ? options.seq : null;
61-
62-
// initialize local seq high/low parts
63-
let seqHigh = _seqHigh;
64-
let seqLow = _seqLow;
65-
66-
// check if clock has advanced and user has not provided msecs
67-
if (msecs > _msecs && options.msecs === undefined) {
68-
_msecs = msecs;
69-
70-
// unless user provided seq, reset seq parts
71-
if (seq !== null) {
72-
seqHigh = null;
73-
seqLow = null;
74-
}
75-
}
18+
let bytes: Uint8Array;
19+
20+
if (options) {
21+
// With options: Make UUID independent of internal state
22+
bytes = v7Bytes(
23+
options.random ?? options.rng?.() ?? rng(),
24+
options.msecs,
25+
options.seq,
26+
buf,
27+
offset
28+
);
29+
} else {
30+
// No options: Use internal state
31+
const now = Date.now();
32+
const rnds = rng();
7633

77-
// if we have a user provided seq
78-
if (seq !== null) {
79-
// trim provided seq to 31 bits of value, avoiding overflow
80-
if (seq > 0x7fffffff) {
81-
seq = 0x7fffffff;
82-
}
34+
updateV7State(_state, now, rnds);
8335

84-
// split provided seq into high/low parts
85-
seqHigh = (seq >>> 19) & 0xfff;
86-
seqLow = seq & 0x7ffff;
36+
bytes = v7Bytes(rnds, _state.msecs, _state.seq, buf, offset);
8737
}
8838

89-
// randomly initialize seq
90-
if (seqHigh === null || seqLow === null) {
91-
seqHigh = rnds[6] & 0x7f;
92-
seqHigh = (seqHigh << 8) | rnds[7];
39+
return buf ? bytes : unsafeStringify(bytes);
40+
}
9341

94-
seqLow = rnds[8] & 0x3f; // pad for var
95-
seqLow = (seqLow << 8) | rnds[9];
96-
seqLow = (seqLow << 5) | (rnds[10] >>> 3);
42+
// (Private!) Do not use. This method is only exported for testing purposes
43+
// and may change without notice.
44+
export function updateV7State(state: V7State, now: number, rnds: Uint8Array) {
45+
if (now > state.msecs) {
46+
// Time has moved on! Pick a new random sequence number
47+
state.seq = (rnds[6] << 23) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9];
48+
state.msecs = now;
49+
} else {
50+
// Bump sequence counter w/ 32-bit rollover
51+
state.seq = (state.seq + 1) | 0;
52+
53+
// In case of rollover, bump timestamp to preserve monotonicity. This is
54+
// allowed by the RFC and should self-correct as the system clock catches
55+
// up. See https://www.rfc-editor.org/rfc/rfc9562.html#section-6.2-9.4
56+
if (state.seq === 0) {
57+
state.msecs++;
58+
}
9759
}
9860

99-
// increment seq if within msecs window
100-
if (msecs + 10000 > _msecs && seq === null) {
101-
if (++seqLow > 0x7ffff) {
102-
seqLow = 0;
103-
104-
if (++seqHigh > 0xfff) {
105-
seqHigh = 0;
61+
return state;
62+
}
10663

107-
// increment internal _msecs. this allows us to continue incrementing
108-
// while staying monotonic. Note, once we hit 10k milliseconds beyond system
109-
// clock, we will reset breaking monotonicity (after (2^31)*10000 generations)
110-
_msecs++;
111-
}
112-
}
113-
} else {
114-
// resetting; we have advanced more than
115-
// 10k milliseconds beyond system clock
116-
_msecs = msecs;
64+
function v7Bytes(rnds: Uint8Array, msecs?: number, seq?: number, buf?: Uint8Array, offset = 0) {
65+
if (!buf) {
66+
buf = new Uint8Array(16);
67+
offset = 0;
11768
}
11869

119-
_seqHigh = seqHigh;
120-
_seqLow = seqLow;
70+
// Defaults
71+
msecs ??= Date.now();
72+
seq ??= ((rnds[6] * 0x7f) << 24) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9];
12173

122-
// [bytes 0-5] 48 bits of local timestamp
123-
b[i++] = (_msecs / 0x10000000000) & 0xff;
124-
b[i++] = (_msecs / 0x100000000) & 0xff;
125-
b[i++] = (_msecs / 0x1000000) & 0xff;
126-
b[i++] = (_msecs / 0x10000) & 0xff;
127-
b[i++] = (_msecs / 0x100) & 0xff;
128-
b[i++] = _msecs & 0xff;
74+
// byte 0-5: timestamp (48 bits)
75+
buf[offset++] = (msecs / 0x10000000000) & 0xff;
76+
buf[offset++] = (msecs / 0x100000000) & 0xff;
77+
buf[offset++] = (msecs / 0x1000000) & 0xff;
78+
buf[offset++] = (msecs / 0x10000) & 0xff;
79+
buf[offset++] = (msecs / 0x100) & 0xff;
80+
buf[offset++] = msecs & 0xff;
12981

130-
// [byte 6] - set 4 bits of version (7) with first 4 bits seq_hi
131-
b[i++] = ((seqHigh >>> 8) & 0x0f) | 0x70;
82+
// byte 6: `version` (4 bits) | sequence bits 28-31 (4 bits)
83+
buf[offset++] = 0x70 | ((seq >>> 28) & 0x0f);
13284

133-
// [byte 7] remaining 8 bits of seq_hi
134-
b[i++] = seqHigh & 0xff;
85+
// byte 7: sequence bits 20-27 (8 bits)
86+
buf[offset++] = (seq >>> 20) & 0xff;
13587

136-
// [byte 8] - variant (2 bits), first 6 bits seq_low
137-
b[i++] = ((seqLow >>> 13) & 0x3f) | 0x80;
88+
// byte 8: `variant` (2 bits) | sequence bits 14-19 (6 bits)
89+
buf[offset++] = 0x80 | ((seq >>> 14) & 0x3f);
13890

139-
// [byte 9] 8 bits seq_low
140-
b[i++] = (seqLow >>> 5) & 0xff;
91+
// byte 9: sequence bits 6-13 (8 bits)
92+
buf[offset++] = (seq >>> 6) & 0xff;
14193

142-
// [byte 10] remaining 5 bits seq_low, 3 bits random
143-
b[i++] = ((seqLow << 3) & 0xff) | (rnds[10] & 0x07);
94+
// byte 10: sequence bits 0-5 (6 bits) | random (2 bits)
95+
buf[offset++] = ((seq << 2) & 0xff) | (rnds[10] & 0x03);
14496

145-
// [bytes 11-15] always random
146-
b[i++] = rnds[11];
147-
b[i++] = rnds[12];
148-
b[i++] = rnds[13];
149-
b[i++] = rnds[14];
150-
b[i++] = rnds[15];
97+
// bytes 11-15: random (40 bits)
98+
buf[offset++] = rnds[11];
99+
buf[offset++] = rnds[12];
100+
buf[offset++] = rnds[13];
101+
buf[offset++] = rnds[14];
102+
buf[offset++] = rnds[15];
151103

152-
return buf || unsafeStringify(b);
104+
return buf;
153105
}
154106

155107
export default v7;

0 commit comments

Comments
 (0)
Please sign in to comment.