From 0f9b75547beffdba8a8ba3474522accd98b1f8d1 Mon Sep 17 00:00:00 2001 From: Erin <166654615+doomgirl@users.noreply.github.com> Date: Wed, 10 Jul 2024 18:04:52 +0000 Subject: [PATCH 1/4] fix(v7): seq_hi --- src/v7.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v7.js b/src/v7.js index 515cbc7a..b18346ff 100644 --- a/src/v7.js +++ b/src/v7.js @@ -125,7 +125,7 @@ function v7(options, buf, offset) { b[i++] = _msecs & 0xff; // [byte 6] - set 4 bits of version (7) with first 4 bits seq_hi - b[i++] = ((seqHigh >>> 4) & 0x0f) | 0x70; + b[i++] = ((seqHigh >>> 8) & 0x0f) | 0x70; // [byte 7] remaining 8 bits of seq_hi b[i++] = seqHigh & 0xff; From 2a46c4978e63c7f2501bb6bc11d7e1ec8b53694c Mon Sep 17 00:00:00 2001 From: Erin <166654615+doomgirl@users.noreply.github.com> Date: Wed, 10 Jul 2024 19:06:31 +0000 Subject: [PATCH 2/4] test(v7): flipping any bit should change the result Probably an overkill. --- test/unit/v7.test.js | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/test/unit/v7.test.js b/test/unit/v7.test.js index aa2b5430..2f642ce8 100644 --- a/test/unit/v7.test.js +++ b/test/unit/v7.test.js @@ -1,5 +1,6 @@ import assert from 'assert'; import v7 from '../../src/v7.js'; +import parse from '../../src/parse.js'; /** * fixture bit layout: @@ -154,7 +155,7 @@ describe('v7', () => { seq, }); - assert.strictEqual(uuid.substr(0, 25), '017f22e2-79b0-7fff-bfff-f'); + assert.strictEqual(uuid.substr(0, 25), '017f22e2-79b0-7dff-bfff-f'); }); test('internal seq is reset upon timestamp change', () => { @@ -169,4 +170,33 @@ describe('v7', () => { assert(uuid.indexOf('fff') !== 15); }); + + test('flipping bits changes the result', () => { + const flip = (data, n) => data ^ (1n << (127n - n)); + const optionsFrom = (data) => { + const ms = data >> (128n - 48n); + const hi = (data >> (43n + 19n + 2n)) & 0xfffn; + const lo = (data >> 43n) & 0x7ffffn; + const r = data & 0x7ff_ffff_ffffn; + return { + msecs: Number(ms), + seq: Number((hi << 19n) | lo), + random: [ + ...Array(10).fill(0), + ...Array(6) + .fill(0) + .map((_, i) => Number((r >> (BigInt(i) * 8n)) & 0xffn)) + .reverse(), + ], + }; + }; + const id = v7(); + const data = parse(id).reduce((l, r) => (BigInt(l) << 8n) | BigInt(r)); + for (let i = 0; i < 128; ++i) { + if ([48, 49, 50, 51, 64, 65].includes(i)) { + continue; + } + assert(v7(optionsFrom(flip(data, BigInt(i)))) !== id); + } + }); }); From 2a143c13c5190107f0250b78106a231a9f0ecd9d Mon Sep 17 00:00:00 2001 From: Erin <166654615+doomgirl@users.noreply.github.com> Date: Wed, 10 Jul 2024 19:43:12 +0000 Subject: [PATCH 3/4] test(v7): invertibility --- test/unit/v7.test.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/unit/v7.test.js b/test/unit/v7.test.js index 2f642ce8..a703cedb 100644 --- a/test/unit/v7.test.js +++ b/test/unit/v7.test.js @@ -1,6 +1,6 @@ import assert from 'assert'; import v7 from '../../src/v7.js'; -import parse from '../../src/parse.js'; +import stringify from '../../src/stringify.js'; /** * fixture bit layout: @@ -172,6 +172,7 @@ describe('v7', () => { }); test('flipping bits changes the result', () => { + const asBigInt = (buf) => buf.reduce((l, r) => (BigInt(l) << 8n) | BigInt(r)); const flip = (data, n) => data ^ (1n << (127n - n)); const optionsFrom = (data) => { const ms = data >> (128n - 48n); @@ -190,13 +191,16 @@ describe('v7', () => { ], }; }; - const id = v7(); - const data = parse(id).reduce((l, r) => (BigInt(l) << 8n) | BigInt(r)); + const buf = new Uint8Array(16); + const data = asBigInt(v7({}, buf)); + const id = stringify(buf); for (let i = 0; i < 128; ++i) { if ([48, 49, 50, 51, 64, 65].includes(i)) { continue; } - assert(v7(optionsFrom(flip(data, BigInt(i)))) !== id); + const flipped = flip(data, BigInt(i)); + assert(asBigInt(v7(optionsFrom(flipped), buf)) === flipped); + assert(stringify(buf) !== id); } }); }); From 6d8e7fb8491ef2bcda2ab5f76fd8d01bb278e6d7 Mon Sep 17 00:00:00 2001 From: Erin <166654615+doomgirl@users.noreply.github.com> Date: Fri, 12 Jul 2024 11:22:46 +0000 Subject: [PATCH 4/4] test(v7): refactoring & comments --- test/unit/v7.test.js | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/test/unit/v7.test.js b/test/unit/v7.test.js index a703cedb..404b7b00 100644 --- a/test/unit/v7.test.js +++ b/test/unit/v7.test.js @@ -172,21 +172,29 @@ describe('v7', () => { }); test('flipping bits changes the result', () => { - const asBigInt = (buf) => buf.reduce((l, r) => (BigInt(l) << 8n) | BigInt(r)); - const flip = (data, n) => data ^ (1n << (127n - n)); + // convert uint8array to BigInt (BE) + const asBigInt = (buf) => buf.reduce((acc, v) => (acc << 8n) | BigInt(v), 0n); + + // convert the given number of bits (LE) to number + const asNumber = (bits, data) => Number(BigInt.asUintN(bits, data)); + + // flip the nth bit (BE) in a BigInt + const flip = (data, n) => data ^ (1n << BigInt(127 - n)); + + // Extract v7 `options` from a (BigInt) UUID const optionsFrom = (data) => { - const ms = data >> (128n - 48n); - const hi = (data >> (43n + 19n + 2n)) & 0xfffn; - const lo = (data >> 43n) & 0x7ffffn; - const r = data & 0x7ff_ffff_ffffn; + const ms = asNumber(48, data >> (128n - 48n)); + const hi = asNumber(12, data >> (43n + 19n + 2n)); + const lo = asNumber(19, data >> 43n); + const r = BigInt.asUintN(43, data); return { - msecs: Number(ms), - seq: Number((hi << 19n) | lo), + msecs: ms, + seq: (hi << 19) | lo, random: [ ...Array(10).fill(0), ...Array(6) .fill(0) - .map((_, i) => Number((r >> (BigInt(i) * 8n)) & 0xffn)) + .map((_, i) => asNumber(8, r >> (BigInt(i) * 8n))) .reverse(), ], }; @@ -194,13 +202,14 @@ describe('v7', () => { const buf = new Uint8Array(16); const data = asBigInt(v7({}, buf)); const id = stringify(buf); + const reserved = [48, 49, 50, 51, 64, 65]; for (let i = 0; i < 128; ++i) { - if ([48, 49, 50, 51, 64, 65].includes(i)) { - continue; + if (reserved.includes(i)) { + continue; // skip bits used for version and variant } - const flipped = flip(data, BigInt(i)); - assert(asBigInt(v7(optionsFrom(flipped), buf)) === flipped); - assert(stringify(buf) !== id); + const flipped = flip(data, i); + assert.strictEqual(asBigInt(v7(optionsFrom(flipped), buf)), flipped, i); + assert.notStrictEqual(stringify(buf), id); } }); });