Skip to content

Commit

Permalink
feat: add v6 method
Browse files Browse the repository at this point in the history
  • Loading branch information
broofa committed Jun 4, 2024
1 parent 4710dee commit f7efb7f
Show file tree
Hide file tree
Showing 22 changed files with 211 additions and 56 deletions.
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"files.autoSave": "afterDelay",
"editor.formatOnSave": false,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
5 changes: 5 additions & 0 deletions bundlewatch.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"path": "./examples/browser-rollup/dist/v5-size.js",
"maxSize": "1.5 kB"
},
{ "path": "./examples/browser-rollup/dist/v6-size.js", "maxSize": "1.3 kB" },
{ "path": "./examples/browser-rollup/dist/v7-size.js", "maxSize": "0.8 kB" },

{
Expand All @@ -38,6 +39,10 @@
"path": "./examples/browser-webpack/dist/v5-size.js",
"maxSize": "1.5 kB"
},
{
"path": "./examples/browser-webpack/dist/v6-size.js",
"maxSize": "1.3 kB"
},
{ "path": "./examples/browser-webpack/dist/v7-size.js", "maxSize": "0.8 kB" }
]
}
3 changes: 3 additions & 0 deletions examples/benchmark/benchmark.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ export default function benchmark(uuid, Benchmark) {
.add('uuid.v5()', function () {
uuid.v5('hello.example.com', uuid.v5.DNS);
})
.add('uuid.v6()', function () {
uuid.v6();
})
.add('uuid.v7()', function () {
uuid.v7();
})
Expand Down
4 changes: 4 additions & 0 deletions examples/browser-esmodules/example.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
v3 as uuidv3,
v4 as uuidv4,
v5 as uuidv5,
v6 as uuidv6,
v6ToV1 as uuidv6ToV1,
v7 as uuidv7,
} from './node_modules/uuid/dist/esm-browser/index.js';
Expand Down Expand Up @@ -48,6 +49,8 @@ console.log('uuidv5() URL', uuidv5('http://example.com/hello', uuidv5.URL));
// const MY_NAMESPACE = '1b671a64-40d5-491e-99b0-da01ff1f3341';
console.log('uuidv5() MY_NAMESPACE', uuidv5('Hello, World!', MY_NAMESPACE));

console.log('uuidv6()', uuidv6());

// v6 <-> v1 conversion
const V1_ID = 'f1207660-21d2-11ef-8c4f-419efbd44d48';
const V6_ID = '1ef21d2f-1207-6660-8c4f-419efbd44d48';
Expand All @@ -73,6 +76,7 @@ console.log('uuid.v3() MY_NAMESPACE', uuid.v3('Hello, World!', MY_NAMESPACE));
console.log('uuid.v5() DNS', uuid.v5('hello.example.com', uuid.v5.DNS));
console.log('uuid.v5() URL', uuid.v5('http://example.com/hello', uuid.v5.URL));
console.log('uuid.v5() MY_NAMESPACE', uuid.v5('Hello, World!', MY_NAMESPACE));
console.log('uuid.v6()', uuid.v6());

console.log('uuid.v1ToV6()', uuid.v1ToV6(V1_ID));
console.log('uuid.v6ToV1()', uuid.v6ToV1(V6_ID));
Expand Down
4 changes: 4 additions & 0 deletions examples/browser-rollup/example-all.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
v3 as uuidv3,
v4 as uuidv4,
v5 as uuidv5,
v6 as uuidv6,
v6ToV1 as uuidv6ToV1,
v7 as uuidv7,
} from 'uuid';
Expand Down Expand Up @@ -52,6 +53,8 @@ testpage(function (addTest, done) {
// const MY_NAMESPACE = '1b671a64-40d5-491e-99b0-da01ff1f3341';
addTest('uuidv5() MY_NAMESPACE', uuidv5('Hello, World!', MY_NAMESPACE));

addTest('uuidv6()', uuidv6());

// v6 <-> v1 conversion
const V1_ID = 'f1207660-21d2-11ef-8c4f-419efbd44d48';
const V6_ID = '1ef21d2f-1207-6660-8c4f-419efbd44d48';
Expand All @@ -77,6 +80,7 @@ testpage(function (addTest, done) {
addTest('uuid.v5() DNS', uuid.v5('hello.example.com', uuid.v5.DNS));
addTest('uuid.v5() URL', uuid.v5('http://example.com/hello', uuid.v5.URL));
addTest('uuid.v5() MY_NAMESPACE', uuid.v5('Hello, World!', MY_NAMESPACE));
addTest('uuid.v6()', uuid.v6());

addTest('uuid.v1ToV6()', uuid.v1ToV6(V1_ID));
addTest('uuid.v6ToV1()', uuid.v6ToV1(V6_ID));
Expand Down
8 changes: 8 additions & 0 deletions examples/browser-rollup/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ module.exports = [
},
plugins,
},
{
input: './size-v6.js',
output: {
file: 'dist/v6-size.js',
format: 'cjs',
},
plugins,
},
{
input: './size-v7.js',
output: {
Expand Down
3 changes: 3 additions & 0 deletions examples/browser-rollup/size-v6.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { v6 as uuidv6 } from 'uuid';

uuidv6();
4 changes: 4 additions & 0 deletions examples/browser-webpack/example-all-require.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const {
v3: uuidv3,
v4: uuidv4,
v5: uuidv5,
v6: uuidv6,
v6ToV1: uuidv6ToV1,
v7: uuidv7,
validate: uuidValidate,
Expand Down Expand Up @@ -58,6 +59,8 @@ testpage(function (addTest, done) {
// const MY_NAMESPACE = '1b671a64-40d5-491e-99b0-da01ff1f3341';
addTest('uuidv5() MY_NAMESPACE', uuidv5('Hello, World!', MY_NAMESPACE));

addTest('uuidv6()', uuidv6());

// Utility functions
addTest('NIL_UUID', NIL_UUID);
addTest('MAX_UUID', MAX_UUID);
Expand All @@ -77,6 +80,7 @@ testpage(function (addTest, done) {
addTest('uuid.v5() DNS', uuid.v5('hello.example.com', uuid.v5.DNS));
addTest('uuid.v5() URL', uuid.v5('http://example.com/hello', uuid.v5.URL));
addTest('uuid.v5() MY_NAMESPACE', uuid.v5('Hello, World!', MY_NAMESPACE));
addTest('uuid.v6()', uuid.v6());

addTest('uuid.v1ToV6()', uuid.v1ToV6(V1_ID));
addTest('uuid.v6ToV1()', uuid.v6ToV1(V6_ID));
Expand Down
4 changes: 4 additions & 0 deletions examples/browser-webpack/example-all.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
v3 as uuidv3,
v4 as uuidv4,
v5 as uuidv5,
v6 as uuidv6,
v6ToV1 as uuidv6ToV1,
v7 as uuidv7,
} from 'uuid';
Expand Down Expand Up @@ -52,6 +53,8 @@ testpage(function (addTest, done) {
// const MY_NAMESPACE = '1b671a64-40d5-491e-99b0-da01ff1f3341';
addTest('uuidv5() MY_NAMESPACE', uuidv5('Hello, World!', MY_NAMESPACE));

addTest('uuidv6()', uuidv6());

// v6 <-> v1 conversion
const V1_ID = 'f1207660-21d2-11ef-8c4f-419efbd44d48';
const V6_ID = '1ef21d2f-1207-6660-8c4f-419efbd44d48';
Expand All @@ -77,6 +80,7 @@ testpage(function (addTest, done) {
addTest('uuid.v5() DNS', uuid.v5('hello.example.com', uuid.v5.DNS));
addTest('uuid.v5() URL', uuid.v5('http://example.com/hello', uuid.v5.URL));
addTest('uuid.v5() MY_NAMESPACE', uuid.v5('Hello, World!', MY_NAMESPACE));
addTest('uuid.v6()', uuid.v6());

addTest('uuid.v1ToV6()', uuid.v1ToV6(V1_ID));
addTest('uuid.v6ToV1()', uuid.v6ToV1(V6_ID));
Expand Down
3 changes: 3 additions & 0 deletions examples/browser-webpack/size-v6.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { v6 as uuidv6 } from 'uuid';

uuidv6();
1 change: 1 addition & 0 deletions examples/browser-webpack/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = {
'v3-size': './size-v3.js',
'v4-size': './size-v4.js',
'v5-size': './size-v5.js',
'v6-size': './size-v6.js',
'v7-size': './size-v7.js',
},
// Webpack now produces builds that are incompatible with IE11:
Expand Down
4 changes: 4 additions & 0 deletions examples/node-commonjs/example.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const {
v3: uuidv3,
v4: uuidv4,
v5: uuidv5,
v6: uuidv6,
v6ToV1: uuidv6ToV1,
v7: uuidv7,
validate: uuidValidate,
Expand Down Expand Up @@ -48,6 +49,8 @@ console.log('uuidv5() URL', uuidv5('http://example.com/hello', uuidv5.URL));
// const MY_NAMESPACE = '1b671a64-40d5-491e-99b0-da01ff1f3341';
console.log('uuidv5() MY_NAMESPACE', uuidv5('Hello, World!', MY_NAMESPACE));

console.log('uuidv6()', uuidv6());

// v6 <-> v1 conversion
const V1_ID = 'f1207660-21d2-11ef-8c4f-419efbd44d48';
const V6_ID = '1ef21d2f-1207-6660-8c4f-419efbd44d48';
Expand All @@ -73,6 +76,7 @@ console.log('uuid.v3() MY_NAMESPACE', uuid.v3('Hello, World!', MY_NAMESPACE));
console.log('uuid.v5() DNS', uuid.v5('hello.example.com', uuid.v5.DNS));
console.log('uuid.v5() URL', uuid.v5('http://example.com/hello', uuid.v5.URL));
console.log('uuid.v5() MY_NAMESPACE', uuid.v5('Hello, World!', MY_NAMESPACE));
console.log('uuid.v6()', uuid.v6());

console.log('uuid.v1ToV6()', uuid.v1ToV6(V1_ID));
console.log('uuid.v6ToV1()', uuid.v6ToV1(V6_ID));
Expand Down
4 changes: 4 additions & 0 deletions examples/node-esmodules/example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
v3 as uuidv3,
v4 as uuidv4,
v5 as uuidv5,
v6 as uuidv6,
v6ToV1 as uuidv6ToV1,
v7 as uuidv7,
} from 'uuid';
Expand Down Expand Up @@ -48,6 +49,8 @@ console.log('uuidv5() URL', uuidv5('http://example.com/hello', uuidv5.URL));
// const MY_NAMESPACE = '1b671a64-40d5-491e-99b0-da01ff1f3341';
console.log('uuidv5() MY_NAMESPACE', uuidv5('Hello, World!', MY_NAMESPACE));

console.log('uuidv6()', uuidv6());

// v6 <-> v1 conversion
const V1_ID = 'f1207660-21d2-11ef-8c4f-419efbd44d48';
const V6_ID = '1ef21d2f-1207-6660-8c4f-419efbd44d48';
Expand All @@ -73,6 +76,7 @@ console.log('uuid.v3() MY_NAMESPACE', uuid.v3('Hello, World!', MY_NAMESPACE));
console.log('uuid.v5() DNS', uuid.v5('hello.example.com', uuid.v5.DNS));
console.log('uuid.v5() URL', uuid.v5('http://example.com/hello', uuid.v5.URL));
console.log('uuid.v5() MY_NAMESPACE', uuid.v5('Hello, World!', MY_NAMESPACE));
console.log('uuid.v6()', uuid.v6());

console.log('uuid.v1ToV6()', uuid.v1ToV6(V1_ID));
console.log('uuid.v6ToV1()', uuid.v6ToV1(V6_ID));
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export { default as v1ToV6 } from './v1ToV6.js';
export { default as v3 } from './v3.js';
export { default as v4 } from './v4.js';
export { default as v5 } from './v5.js';
export { default as v6 } from './v6.js';
export { default as v6ToV1 } from './v6ToV1.js';
export { default as v7 } from './v7.js';
export { default as validate } from './validate.js';
Expand Down
2 changes: 1 addition & 1 deletion src/v1.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function v1(options, buf, offset) {
const seedBytes = options.random || (options.rng || rng)();

if (node == null) {
// Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1)
// Per 4.5, create a 48-bit node id, (47 random bits + multicast bit = 1)
node = _nodeId = [
seedBytes[0] | 0x01,
seedBytes[1],
Expand Down
56 changes: 32 additions & 24 deletions src/v1ToV6.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,53 @@
import parse from './parse.js';
import rng from './rng.js';
import stringify from './stringify.js';
import version from './version.js';
import { unsafeStringify } from './stringify.js';

/**
* Convert a v1 UUID to a v6 UUID.
* 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:
* clock_seq and node fields SHOULD be set to random values for each new v6 UUID
* to aid in collision resistance and security. This function does not do this
* by default, however, 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.
* Callers needing the RFC-recommended randomization can enable this by passing
* `true` for the `randomize` argument.
*
* @param {string|Uint8Array} uuid - The v1 UUID to convert to v6
* @param {boolean} [randomize=false] - Whether to randomize the clock_seq and
* node fields
* @returns {string|Uint8Array} The v6 UUID as a string or Uint8Array
*/
export default function v1ToV6(uuid, randomize = false) {
if (version(uuid) !== 1) {
throw new Error('id is not a valid v1 UUID');
}
// Non-string UUIDs are documented as being Uint8Arrays, but we don't enforce
// that. They just need to be "array-like". And some day when we port this
// to TypeScript we'll have to take an actual stance on this.
const v1Bytes = typeof uuid === 'string' ? parse(uuid) : uuid;

const v1Bytes = parse(uuid);
const v6Bytes = _v1ToV6(v1Bytes);

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];
v6Bytes[8] = (rnds[0] & 0x3f) | 0x80;
v6Bytes[9] = rnds[1];
v6Bytes[10] = rnds[2];
v6Bytes[11] = rnds[3];
v6Bytes[12] = rnds[4];
v6Bytes[13] = rnds[5];
v6Bytes[14] = rnds[6];
v6Bytes[15] = rnds[7];
}
return typeof uuid === 'string' ? unsafeStringify(v6Bytes) : v6Bytes;
}

const v6Bytes = Uint8Array.from([
// Do the field transformation needed for v1 -> v6
function _v1ToV6(v1Bytes, randomize = false) {
return Uint8Array.of(
((v1Bytes[6] & 0x0f) << 4) | ((v1Bytes[7] >> 4) & 0x0f),
((v1Bytes[7] & 0x0f) << 4) | ((v1Bytes[4] & 0xf0) >> 4),
((v1Bytes[4] & 0x0f) << 4) | ((v1Bytes[5] & 0xf0) >> 4),
Expand All @@ -56,8 +66,6 @@ export default function v1ToV6(uuid, randomize = false) {
v1Bytes[12],
v1Bytes[13],
v1Bytes[14],
v1Bytes[15],
]);

return stringify(v6Bytes);
v1Bytes[15]
);
}
47 changes: 47 additions & 0 deletions src/v6.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { unsafeStringify } from './stringify.js';
import v1 from './v1.js';
import v1ToV6 from './v1ToV6.js';

/**
*
* @param {object} options
* @param {Uint8Array=} buf
* @param {number=} offset
* @returns
*/
export default function v6(options, buf, offset = 0) {
// v6 is just a v1 UUID with different field layout.
let bytes = v1(options, new Uint8Array(16));

// Reorder the fields to v6 layout. Per RFC9562, we randomize the clock_seq
// and node fields for each UUID. Randomize node and clock_seq here to get
// the RFC-recommended behavior (we restore these below if they're provided in
// options.)
bytes = v1ToV6(bytes, true);

// Apply options.clockseq?
const clockseq = options?.clockseq;
if (clockseq !== undefined) {
bytes[8] = ((clockseq >> 8) & 0x3f) | 0x80;
bytes[9] = clockseq & 0xff;
}

// Apply options.node?
const node = options?.node;
if (node !== undefined) {
for (let i = 0; i < 6; i++) {
bytes[10 + i] = node[i];
}
}

if (buf) {
// Copy to buffer
for (let i = 0; i < 16; i++) {
buf[offset + i] = bytes[i];
}
return buf;
}

// Otherwise return UUID as a string
return unsafeStringify(bytes);
}
Loading

0 comments on commit f7efb7f

Please sign in to comment.