From 9eb076a0a36bd86958f220328aaf2bc54bf9f1c4 Mon Sep 17 00:00:00 2001 From: robinpokorny Date: Fri, 14 Jun 2024 15:09:08 +0200 Subject: [PATCH 1/2] test: add failing tests for msecs in v7 --- test/unit/v7.test.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/unit/v7.test.js b/test/unit/v7.test.js index aa2b5430..c754bb82 100644 --- a/test/unit/v7.test.js +++ b/test/unit/v7.test.js @@ -24,6 +24,7 @@ import v7 from '../../src/v7.js'; describe('v7', () => { const msecsFixture = 1645557742000; + const msecsFutureFixture = Date.UTC('2100'); const seqFixture = 0x661b189b; const randomBytesFixture = [ @@ -63,6 +64,26 @@ describe('v7', () => { assert.strictEqual(id.indexOf('017f22e2'), 0); }); + test('explicit options.msecs after a standard call randomises seq', () => { + const id1 = v7(); + const id2 = v7({ + msecs: msecsFixture, + }); + + assert.notDeepStrictEqual(id1.slice(14, 23), id2.slice(14, 23)); + }); + + test('providing current options.msecs after past options.msecs works', () => { + const id1 = v7({ + msecs: msecsFixture, + }); + const id2 = v7({ + msecs: Date.now(), + }); + + assert.notDeepStrictEqual(id1.slice(0, 13), id2.slice(0, 13)); + }); + test('fills one UUID into a buffer as expected', () => { const buffer = []; const result = v7( @@ -169,4 +190,11 @@ describe('v7', () => { assert(uuid.indexOf('fff') !== 15); }); + + test('explicit options.msecs in the future produces expected result', () => { + const id = v7({ + msecs: msecsFutureFixture, + }); + assert.match(id, /^3bb2cc3/); + }); }); From 67b12173dc13775f6158a11fb4bfb47b5a59f58e Mon Sep 17 00:00:00 2001 From: robinpokorny Date: Fri, 14 Jun 2024 15:48:31 +0200 Subject: [PATCH 2/2] fix: allow custom timestamps in v7, fixes #764 --- src/v7.js | 41 +++++++++++++++++++++++------------------ test/unit/v7.test.js | 3 ++- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/v7.js b/src/v7.js index 515cbc7a..7dafad68 100644 --- a/src/v7.js +++ b/src/v7.js @@ -38,7 +38,8 @@ import { unsafeStringify } from './stringify.js'; let _seqLow = null; let _seqHigh = null; -let _msecs = 0; +let _prevMsecs = 0; +let _overflowMsecs = 0; function v7(options, buf, offset) { options = options || {}; @@ -60,12 +61,13 @@ function v7(options, buf, offset) { let seqHigh = _seqHigh; let seqLow = _seqLow; - // check if clock has advanced and user has not provided msecs - if (msecs > _msecs && options.msecs === undefined) { - _msecs = msecs; + // reset if msecs is not the overflow window + if (msecs < _prevMsecs || msecs > _prevMsecs + _overflowMsecs) { + _prevMsecs = msecs; + _overflowMsecs = 0; // unless user provided seq, reset seq parts - if (seq !== null) { + if (seq === null) { seqHigh = null; seqLow = null; } @@ -94,35 +96,38 @@ function v7(options, buf, offset) { } // increment seq if within msecs window - if (msecs + 10000 > _msecs && seq === null) { + if (_overflowMsecs < 10000 && seq === null) { if (++seqLow > 0x7ffff) { seqLow = 0; if (++seqHigh > 0xfff) { seqHigh = 0; - // increment internal _msecs. this allows us to continue incrementing - // while staying monotonic. Note, once we hit 10k milliseconds beyond system - // clock, we will reset breaking monotonicity (after (2^31)*10000 generations) - _msecs++; + // increment internal _overflowMsecs. This allows us to continue incrementing + // while staying monotonic. Note, once we hit 10k milliseconds beyond original + // time, we will reset breaking monotonicity (after (2^31)*10000 generations) + _overflowMsecs++; } } } else { // resetting; we have advanced more than - // 10k milliseconds beyond system clock - _msecs = msecs; + // 10k milliseconds beyond the original time + _prevMsecs = msecs; + _overflowMsecs = 0; } _seqHigh = seqHigh; _seqLow = seqLow; + const timestamp = _prevMsecs + _overflowMsecs; + // [bytes 0-5] 48 bits of local timestamp - b[i++] = (_msecs / 0x10000000000) & 0xff; - b[i++] = (_msecs / 0x100000000) & 0xff; - b[i++] = (_msecs / 0x1000000) & 0xff; - b[i++] = (_msecs / 0x10000) & 0xff; - b[i++] = (_msecs / 0x100) & 0xff; - b[i++] = _msecs & 0xff; + b[i++] = (timestamp / 0x10000000000) & 0xff; + b[i++] = (timestamp / 0x100000000) & 0xff; + b[i++] = (timestamp / 0x1000000) & 0xff; + b[i++] = (timestamp / 0x10000) & 0xff; + b[i++] = (timestamp / 0x100) & 0xff; + b[i++] = timestamp & 0xff; // [byte 6] - set 4 bits of version (7) with first 4 bits seq_hi b[i++] = ((seqHigh >>> 4) & 0x0f) | 0x70; diff --git a/test/unit/v7.test.js b/test/unit/v7.test.js index c754bb82..ffdf84bd 100644 --- a/test/unit/v7.test.js +++ b/test/unit/v7.test.js @@ -195,6 +195,7 @@ describe('v7', () => { const id = v7({ msecs: msecsFutureFixture, }); - assert.match(id, /^3bb2cc3/); + + assert(id.indexOf('03bb2cc3') === 0); }); });