Skip to content

Commit

Permalink
fix: v1-v7 consistency
Browse files Browse the repository at this point in the history
  • Loading branch information
broofa committed Jul 18, 2024
1 parent 05afbb2 commit 7ccab2a
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 139 deletions.
58 changes: 24 additions & 34 deletions examples/benchmark/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

132 changes: 66 additions & 66 deletions src/test/v1.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,76 +96,76 @@ describe('v1', () => {

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
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: '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: '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: '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
{
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}`);
}
];
for (const { title, state, now, expected } of tests) {
assert.deepStrictEqual(updateV1State(state, now, RFC_RANDOM), expected, `Failed: ${title}`);
}
});
});
64 changes: 32 additions & 32 deletions src/test/v7.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 5 additions & 1 deletion src/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ function v1(options?: Version1Options, buf?: Uint8Array, offset?: number): UUIDT
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,
Expand All @@ -74,9 +76,11 @@ function v1(options?: Version1Options, buf?: Uint8Array, offset?: number): UUIDT
return buf ? bytes : unsafeStringify(bytes);
}

// (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.nsecs ??= 0;
state.msecs ??= -Infinity;
state.nsecs ??= 0;

// Update timestamp
if (now === state.msecs) {
Expand Down
12 changes: 6 additions & 6 deletions src/v7.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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];
Expand Down

0 comments on commit 7ccab2a

Please sign in to comment.