Skip to content

Commit

Permalink
feat: support v6 uuids
Browse files Browse the repository at this point in the history
  • Loading branch information
broofa committed Jun 3, 2024
1 parent 3fd0f18 commit 9b365da
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 0 deletions.
48 changes: 48 additions & 0 deletions src/v1tov6.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import parse from './parse.js';
import rng from './rng.js';
import stringify from './stringify.js';
import version from './version.js';

export default function v1tov6(uuid, randomize = false) {
if (version(uuid) !== 1) {
throw new Error('id is not a valid v1 UUID');
}

const v1Bytes = parse(uuid);

if (randomize) {
const rnds = rng();
v1Bytes[8] = (rnds[0] & 0x3f) | 0x80;
v1Bytes[9] = rnds[1];
v1Bytes[10] = rnds[2];
v1Bytes[11] = rnds[3];
v1Bytes[12] = rnds[4];
v1Bytes[13] = rnds[5];
v1Bytes[14] = rnds[6];
v1Bytes[15] = rnds[7];
}

const v6Bytes = Uint8Array.from([
((v1Bytes[6] & 0x0f) << 4) | ((v1Bytes[7] >> 4) & 0x0f),
((v1Bytes[7] & 0x0f) << 4) | ((v1Bytes[4] & 0xf0) >> 4),
((v1Bytes[4] & 0x0f) << 4) | ((v1Bytes[5] & 0xf0) >> 4),
((v1Bytes[5] & 0x0f) << 4) | ((v1Bytes[0] & 0xf0) >> 4),

((v1Bytes[0] & 0x0f) << 4) | ((v1Bytes[1] & 0xf0) >> 4),
((v1Bytes[1] & 0x0f) << 4) | ((v1Bytes[2] & 0xf0) >> 4),

0x60 | (v1Bytes[2] & 0x0f),
v1Bytes[3],

v1Bytes[8],
v1Bytes[9],
v1Bytes[10],
v1Bytes[11],
v1Bytes[12],
v1Bytes[13],
v1Bytes[14],
v1Bytes[15],
]);

return stringify(v6Bytes);
}
50 changes: 50 additions & 0 deletions src/v6tov1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import parse from './parse.js';
import stringify from './stringify.js';
import version from './version.js';

/**
* Convert a v1 UUID to a v6 UUID.
*
* Note: Per https://www.rfc-editor.org/rfc/rfc9562.html#section-5.6-4, the
* clock_seq and node fields SHOULD be randomized to aid in collision resistance
* and security. However. this behavior is not enabled by default for two reasons:
*
* 1. Doing so makes the conversion non-reversible. I.e. `v6tov1(v1tov6(uuid))
* !== uuid`.
* 2. Doing so makes the conversion non-deterministic. I.e. `v1tov6(uuid) !==
* v1tov6(uuid)`
*
* Callers wishing to enable the RFC-recommended randomization can do so by
* passing `true` for the second parameter.
*/
export default function v6tov1(uuid, randomize = false) {
if (version(uuid) !== 6) {
throw new Error('id is not a valid v6 UUID');
}

const v1Bytes = parse(uuid);

const v6Bytes = Uint8Array.from([
((v1Bytes[3] & 0x0f) << 4) | ((v1Bytes[4] >> 4) & 0x0f),
((v1Bytes[4] & 0x0f) << 4) | ((v1Bytes[5] & 0xf0) >> 4),
((v1Bytes[5] & 0x0f) << 4) | (v1Bytes[6] & 0x0f),
v1Bytes[7],

((v1Bytes[1] & 0x0f) << 4) | ((v1Bytes[2] & 0xf0) >> 4),
((v1Bytes[2] & 0x0f) << 4) | ((v1Bytes[3] & 0xf0) >> 4),

0x10 | ((v1Bytes[0] & 0xf0) >> 4),
((v1Bytes[0] & 0x0f) << 4) | ((v1Bytes[1] & 0xf0) >> 4),

v1Bytes[8],
v1Bytes[9],
v1Bytes[10],
v1Bytes[11],
v1Bytes[12],
v1Bytes[13],
v1Bytes[14],
v1Bytes[15],
]);

return stringify(v6Bytes);
}
26 changes: 26 additions & 0 deletions test/unit/v6.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import assert from 'assert';
import v1tov6 from '../../src/v1tov6.js';
import v6tov1 from '../../src/v6tov1.js';

describe('v1 <-> v6 conversion', () => {
const v1Fixture = 'f1207660-21d2-11ef-8c4f-419efbd44d48';
const v6Fixture = '1ef21d2f-1207-6660-8c4f-419efbd44d48';

test('v1 -> v6 conversion', () => {
const id = v1tov6(v1Fixture);
assert.equal(id, v6Fixture);
});

test('v1 -> v6 conversion (w', () => {
const id = v1tov6(v1Fixture, true);

//
assert.notEqual(id, v6Fixture);
assert.equal(id.slice(0, 24), v6Fixture.slice(0, 24));
});

test('v6 -> v1 conversion', () => {
const id = v6tov1(v6Fixture);
assert.equal(id, v1Fixture);
});
});

0 comments on commit 9b365da

Please sign in to comment.