diff --git a/README.md b/README.md index ee4e3643..f405894f 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,17 @@ -- This file is auto-generated from README_js.md. Changes should be made there. --> -# uuid [![Build Status](https://github.com/kelektiv/node-uuid/workflows/CI/badge.svg)](https://github.com/kelektiv/node-uuid/actions) +# uuid [![Build Status](https://github.com/uuidjs/uuid/workflows/CI/badge.svg)](https://github.com/uuidjs/uuid/actions) -Simple, fast generation of [RFC4122](http://www.ietf.org/rfc/rfc4122.txt) UUIDS. +Simple, fast generation of [RFC4122](http://www.ietf.org/rfc/rfc4122.txt) UUIDs. Features: - Support for version 1, 3, 4 and 5 UUIDs - Cross-platform: CommonJS build for Node.js and [ECMAScript Modules](#ecmascript-modules) for the browser. -- Uses cryptographically-strong random number APIs (when available) -- Zero-dependency, small footprint (... but not [this small](https://gist.github.com/982883)) +- Uses cryptographically-strong random number APIs +- Zero-dependency, small footprint ## Quickstart - Node.js/CommonJS @@ -114,8 +114,81 @@ npm run build ## API +### Version 4 (Random) + +```javascript +const uuid = require('uuid'); + +// Incantations +uuid.v4(); +uuid.v4(options); +uuid.v4(options, buffer, offset); +``` + +Generate and return a RFC4122 v4 UUID. + +- `options` - (Object) Optional uuid state to apply. Properties may include: + - `random` - (Number[16]) Array of 16 numbers (0-255) to use in place of randomly generated values. Takes precedence over `options.rng`. + - `rng` - (Function) Random # generator function that returns an Array[16] of byte values (0-255). Alternative to `options.random`. +- `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written. +- `offset` - (Number) Starting index in `buffer` at which to begin writing. + +Returns `buffer`, if specified, otherwise the string form of the UUID + +Example: Generate string UUID with predefined `random` values + +```javascript +const v4options = { + random: [ + 0x10, + 0x91, + 0x56, + 0xbe, + 0xc4, + 0xfb, + 0xc1, + 0xea, + 0x71, + 0xb4, + 0xef, + 0xe1, + 0x67, + 0x1c, + 0x58, + 0x36, + ], +}; +uuid.v4(v4options); // ⇨ '109156be-c4fb-41ea-b1b4-efe1671c5836' + +``` + +Example: Generate two IDs in a single buffer + +```javascript +const buffer = new Array(); +uuid.v4(null, buffer, 0); // ⇨ + // [ + // 155, 29, 235, 77, 59, + // 125, 75, 173, 155, 221, + // 43, 13, 123, 61, 203, + // 109 + // ] +uuid.v4(null, buffer, 16); // ⇨ + // [ + // 155, 29, 235, 77, 59, 125, 75, 173, + // 155, 221, 43, 13, 123, 61, 203, 109, + // 27, 157, 107, 205, 187, 253, 75, 45, + // 155, 93, 171, 141, 251, 189, 75, 237 + // ] + +``` + ### Version 1 (Timestamp + Node) +⚠️⚠️⚠️ **Please make sure to check whether you really need the timestamp properties of Version 1 UUIDs +before using them. In many cases, Version 4 random UUIDs are the better choice. [This +FAQ](https://github.com/tc39/proposal-uuid#faq) covers more details.** ⚠️⚠️⚠️ + ```javascript const uuid = require('uuid'); @@ -128,12 +201,12 @@ uuid.v1(options, buffer, offset); Generate and return a RFC4122 v1 (timestamp-based) UUID. - `options` - (Object) Optional uuid state to apply. Properties may include: - - `node` - (Array) Node id as Array of 6 bytes (per 4.1.6). Default: Randomly generated ID. See note 1. - `clockseq` - (Number between 0 - 0x3fff) RFC clock sequence. Default: An internally maintained clockseq is used. - `msecs` - (Number) Time in milliseconds since unix Epoch. Default: The current time is used. - `nsecs` - (Number between 0-9999) additional time, in 100-nanosecond units. Ignored if `msecs` is unspecified. Default: internal uuid counter is used, as per 4.2.1.2. - + - `random` - (Number[16]) Array of 16 numbers (0-255) to use for initialization of `node` and `clockseq` as described above. Takes precedence over `options.rng`. + - `rng` - (Function) Random # generator function that returns an Array[16] of byte values (0-255). Alternative to `options.random`. - `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written. - `offset` - (Number) Starting index in `buffer` at which to begin writing. @@ -203,75 +276,6 @@ uuid.v3('hello world', MY_NAMESPACE); // ⇨ '042ffd34-d989-321c-ad06-f608261724 ``` -### Version 4 (Random) - -```javascript -const uuid = require('uuid'); - -// Incantations -uuid.v4(); -uuid.v4(options); -uuid.v4(options, buffer, offset); -``` - -Generate and return a RFC4122 v4 UUID. - -- `options` - (Object) Optional uuid state to apply. Properties may include: - - `random` - (Number[16]) Array of 16 numbers (0-255) to use in place of randomly generated values - - `rng` - (Function) Random # generator function that returns an Array[16] of byte values (0-255) -- `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written. -- `offset` - (Number) Starting index in `buffer` at which to begin writing. - -Returns `buffer`, if specified, otherwise the string form of the UUID - -Example: Generate string UUID with predefined `random` values - -```javascript -const v4options = { - random: [ - 0x10, - 0x91, - 0x56, - 0xbe, - 0xc4, - 0xfb, - 0xc1, - 0xea, - 0x71, - 0xb4, - 0xef, - 0xe1, - 0x67, - 0x1c, - 0x58, - 0x36, - ], -}; -uuid.v4(v4options); // ⇨ '109156be-c4fb-41ea-b1b4-efe1671c5836' - -``` - -Example: Generate two IDs in a single buffer - -```javascript -const buffer = new Array(); -uuid.v4(null, buffer, 0); // ⇨ - // [ - // 155, 29, 235, 77, 59, - // 125, 75, 173, 155, 221, - // 43, 13, 123, 61, 203, - // 109 - // ] -uuid.v4(null, buffer, 16); // ⇨ - // [ - // 155, 29, 235, 77, 59, 125, 75, 173, - // 155, 221, 43, 13, 123, 61, 203, 109, - // 27, 157, 107, 205, 187, 253, 75, 45, - // 155, 93, 171, 141, 251, 189, 75, 237 - // ] - -``` - ### Version 5 (Namespace) ```javascript diff --git a/README_js.md b/README_js.md index 5b1de0ed..504d5e57 100644 --- a/README_js.md +++ b/README_js.md @@ -15,17 +15,17 @@ require('crypto').randomBytes = function() { }; ``` -# uuid [![Build Status](https://github.com/kelektiv/node-uuid/workflows/CI/badge.svg)](https://github.com/kelektiv/node-uuid/actions) +# uuid [![Build Status](https://github.com/uuidjs/uuid/workflows/CI/badge.svg)](https://github.com/uuidjs/uuid/actions) -Simple, fast generation of [RFC4122](http://www.ietf.org/rfc/rfc4122.txt) UUIDS. +Simple, fast generation of [RFC4122](http://www.ietf.org/rfc/rfc4122.txt) UUIDs. Features: - Support for version 1, 3, 4 and 5 UUIDs - Cross-platform: CommonJS build for Node.js and [ECMAScript Modules](#ecmascript-modules) for the browser. -- Uses cryptographically-strong random number APIs (when available) -- Zero-dependency, small footprint (... but not [this small](https://gist.github.com/982883)) +- Uses cryptographically-strong random number APIs +- Zero-dependency, small footprint ## Quickstart - Node.js/CommonJS @@ -123,8 +123,67 @@ npm run build ## API +### Version 4 (Random) + +```javascript +const uuid = require('uuid'); + +// Incantations +uuid.v4(); +uuid.v4(options); +uuid.v4(options, buffer, offset); +``` + +Generate and return a RFC4122 v4 UUID. + +- `options` - (Object) Optional uuid state to apply. Properties may include: + - `random` - (Number[16]) Array of 16 numbers (0-255) to use in place of randomly generated values. Takes precedence over `options.rng`. + - `rng` - (Function) Random # generator function that returns an Array[16] of byte values (0-255). Alternative to `options.random`. +- `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written. +- `offset` - (Number) Starting index in `buffer` at which to begin writing. + +Returns `buffer`, if specified, otherwise the string form of the UUID + +Example: Generate string UUID with predefined `random` values + +```javascript --run v4 +const v4options = { + random: [ + 0x10, + 0x91, + 0x56, + 0xbe, + 0xc4, + 0xfb, + 0xc1, + 0xea, + 0x71, + 0xb4, + 0xef, + 0xe1, + 0x67, + 0x1c, + 0x58, + 0x36, + ], +}; +uuid.v4(v4options); // RESULT +``` + +Example: Generate two IDs in a single buffer + +```javascript --run v4 +const buffer = new Array(); +uuid.v4(null, buffer, 0); // RESULT +uuid.v4(null, buffer, 16); // RESULT +``` + ### Version 1 (Timestamp + Node) +⚠️⚠️⚠️ **Please make sure to check whether you really need the timestamp properties of Version 1 UUIDs +before using them. In many cases, Version 4 random UUIDs are the better choice. [This +FAQ](https://github.com/tc39/proposal-uuid#faq) covers more details.** ⚠️⚠️⚠️ + ```javascript const uuid = require('uuid'); @@ -137,12 +196,12 @@ uuid.v1(options, buffer, offset); Generate and return a RFC4122 v1 (timestamp-based) UUID. - `options` - (Object) Optional uuid state to apply. Properties may include: - - `node` - (Array) Node id as Array of 6 bytes (per 4.1.6). Default: Randomly generated ID. See note 1. - `clockseq` - (Number between 0 - 0x3fff) RFC clock sequence. Default: An internally maintained clockseq is used. - `msecs` - (Number) Time in milliseconds since unix Epoch. Default: The current time is used. - `nsecs` - (Number between 0-9999) additional time, in 100-nanosecond units. Ignored if `msecs` is unspecified. Default: internal uuid counter is used, as per 4.2.1.2. - + - `random` - (Number[16]) Array of 16 numbers (0-255) to use for initialization of `node` and `clockseq` as described above. Takes precedence over `options.rng`. + - `rng` - (Function) Random # generator function that returns an Array[16] of byte values (0-255). Alternative to `options.random`. - `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written. - `offset` - (Number) Starting index in `buffer` at which to begin writing. @@ -197,61 +256,6 @@ Example: uuid.v3('hello world', MY_NAMESPACE); // RESULT ``` -### Version 4 (Random) - -```javascript -const uuid = require('uuid'); - -// Incantations -uuid.v4(); -uuid.v4(options); -uuid.v4(options, buffer, offset); -``` - -Generate and return a RFC4122 v4 UUID. - -- `options` - (Object) Optional uuid state to apply. Properties may include: - - `random` - (Number[16]) Array of 16 numbers (0-255) to use in place of randomly generated values - - `rng` - (Function) Random # generator function that returns an Array[16] of byte values (0-255) -- `buffer` - (Array | Buffer) Array or buffer where UUID bytes are to be written. -- `offset` - (Number) Starting index in `buffer` at which to begin writing. - -Returns `buffer`, if specified, otherwise the string form of the UUID - -Example: Generate string UUID with predefined `random` values - -```javascript --run v4 -const v4options = { - random: [ - 0x10, - 0x91, - 0x56, - 0xbe, - 0xc4, - 0xfb, - 0xc1, - 0xea, - 0x71, - 0xb4, - 0xef, - 0xe1, - 0x67, - 0x1c, - 0x58, - 0x36, - ], -}; -uuid.v4(v4options); // RESULT -``` - -Example: Generate two IDs in a single buffer - -```javascript --run v4 -const buffer = new Array(); -uuid.v4(null, buffer, 0); // RESULT -uuid.v4(null, buffer, 16); // RESULT -``` - ### Version 5 (Namespace) ```javascript diff --git a/examples/browser-esmodules/README.md b/examples/browser-esmodules/README.md index 81c73e7e..ad463e4f 100644 --- a/examples/browser-esmodules/README.md +++ b/examples/browser-esmodules/README.md @@ -2,7 +2,7 @@ ``` npm install -npm test +npm start ``` Then navigate to `example.html`. diff --git a/src/rng-browser.js b/src/rng-browser.js index 1a96ee5f..d91b4bc2 100644 --- a/src/rng-browser.js +++ b/src/rng-browser.js @@ -1,41 +1,21 @@ -// Unique ID creation requires a high quality random # generator. In the -// browser this is a little complicated due to unknown quality of Math.random() -// and inconsistent support for the `crypto` API. We do the best we can via -// feature-detection +// Unique ID creation requires a high quality random # generator. In the browser we therefore +// require the crypto API and do not support built-in fallback to lower quality random number +// generators (like Math.random()). -// getRandomValues needs to be invoked in a context where "this" is a Crypto -// implementation. Also, find the complete implementation of crypto on IE11. +// getRandomValues needs to be invoked in a context where "this" is a Crypto implementation. Also, +// find the complete implementation of crypto (msCrypto) on IE11. var getRandomValues = (typeof crypto != 'undefined' && crypto.getRandomValues && crypto.getRandomValues.bind(crypto)) || (typeof msCrypto != 'undefined' && typeof window.msCrypto.getRandomValues == 'function' && msCrypto.getRandomValues.bind(msCrypto)); -let rng; - -if (getRandomValues) { - // WHATWG crypto RNG - http://wiki.whatwg.org/wiki/Crypto - var rnds8 = new Uint8Array(16); // eslint-disable-line no-undef - - rng = function whatwgRNG() { - getRandomValues(rnds8); - return rnds8; - }; -} else { - // Math.random()-based (RNG) - // - // If all else fails, use Math.random(). It's fast, but is of unspecified - // quality. - var rnds = new Array(16); - - rng = function mathRNG() { - for (var i = 0, r; i < 16; i++) { - if ((i & 0x03) === 0) r = Math.random() * 0x100000000; - rnds[i] = (r >>> ((i & 0x03) << 3)) & 0xff; - } - - return rnds; - }; +var rnds8 = new Uint8Array(16); // eslint-disable-line no-undef +export default function rng() { + if (!getRandomValues) { + throw new Error( + 'uuid: This browser does not seem to support crypto.getRandomValues(). If you need to support this browser, please provide a custom random number generator through options.rng.', + ); + } + return getRandomValues(rnds8); } - -export default rng; diff --git a/src/rng.js b/src/rng.js index de783100..33513bb8 100644 --- a/src/rng.js +++ b/src/rng.js @@ -1,5 +1,5 @@ import crypto from 'crypto'; -export default function nodeRNG() { +export default function rng() { return crypto.randomBytes(16); } diff --git a/src/v1.js b/src/v1.js index 4a02c1c9..594616e7 100644 --- a/src/v1.js +++ b/src/v1.js @@ -26,7 +26,7 @@ function v1(options, buf, offset) { // specified. We do this lazily to minimize issues related to insufficient // system entropy. See #189 if (node == null || clockseq == null) { - var seedBytes = rng(); + var seedBytes = options.random || (options.rng || rng)(); if (node == null) { // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1) node = _nodeId = [ diff --git a/test/unit/rng.test.js b/test/unit/rng.test.js new file mode 100644 index 00000000..f7cd6364 --- /dev/null +++ b/test/unit/rng.test.js @@ -0,0 +1,23 @@ +import assert from 'assert'; +import rng from '../../src/rng.js'; +import rngBrowser from '../../src/rng-browser.js'; + +describe('rng', () => { + test('Node.js RNG', () => { + var bytes = rng(); + assert.equal(bytes.length, 16); + + for (var i = 0; i < bytes.length; i++) { + assert.equal(typeof bytes[i], 'number'); + } + }); + + test('Browser without crypto.getRandomValues()', () => { + assert.throws(() => { + rngBrowser(); + }); + }); + + // Test of whatwgRNG missing for now since with esmodules we can no longer manipulate the + // require.cache. +}); diff --git a/test/unit/v1-random.test.js b/test/unit/v1-random.test.js new file mode 100644 index 00000000..e74dfc50 --- /dev/null +++ b/test/unit/v1-random.test.js @@ -0,0 +1,34 @@ +import assert from 'assert'; +import v1 from '../../src/v1.js'; + +// Since the clockseq is cached in the module this test must run in a separate file in order to +// initialize the v1 clockseq with controlled random data. +describe('v1', () => { + const randomBytesFixture = [ + 0x10, + 0x91, + 0x56, + 0xbe, + 0xc4, + 0xfb, + 0xc1, + 0xea, + 0x71, + 0xb4, + 0xef, + 0xe1, + 0x67, + 0x1c, + 0x58, + 0x36, + ]; + + test('explicit options.random produces expected id', () => { + const id = v1({ + msecs: 1321651533573, + nsecs: 5432, + random: randomBytesFixture, + }); + assert.strictEqual(id, 'd9428888-122b-11e1-81ea-119156bec4fb'); + }); +}); diff --git a/test/unit/v1-rng.test.js b/test/unit/v1-rng.test.js new file mode 100644 index 00000000..7e15ab88 --- /dev/null +++ b/test/unit/v1-rng.test.js @@ -0,0 +1,34 @@ +import assert from 'assert'; +import v1 from '../../src/v1.js'; + +// Since the clockseq is cached in the module this test must run in a separate file in order to +// initialize the v1 clockseq with controlled random data. +describe('v1', () => { + const randomBytesFixture = [ + 0x10, + 0x91, + 0x56, + 0xbe, + 0xc4, + 0xfb, + 0xc1, + 0xea, + 0x71, + 0xb4, + 0xef, + 0xe1, + 0x67, + 0x1c, + 0x58, + 0x36, + ]; + + test('explicit options.random produces expected id', () => { + const id = v1({ + msecs: 1321651533573, + nsecs: 5432, + rng: () => randomBytesFixture, + }); + assert.strictEqual(id, 'd9428888-122b-11e1-81ea-119156bec4fb'); + }); +}); diff --git a/test/unit/v1.test.js b/test/unit/v1.test.js new file mode 100644 index 00000000..299bfef9 --- /dev/null +++ b/test/unit/v1.test.js @@ -0,0 +1,102 @@ +import assert from 'assert'; +import v1 from '../../src/v1.js'; + +// Verify ordering of v1 ids created with explicit times +const TIME = 1321644961388; // 2011-11-18 11:36:01.388-08:00 + +describe('v1', () => { + test('v1 sort order (default)', () => { + const ids = [v1(), v1(), v1(), v1(), v1()]; + + const sorted = [...ids].sort((a, b) => { + a = a + .split('-') + .reverse() + .join('-'); + b = b + .split('-') + .reverse() + .join('-'); + return a < b ? -1 : a > b ? 1 : 0; + }); + + assert.deepEqual(ids, sorted); + }); + + // Verify ordering of v1 ids created with explicit times + test('v1 sort order (time option)', () => { + const ids = [ + v1({ msecs: TIME - 10 * 3600 * 1000 }), + v1({ msecs: TIME - 1 }), + v1({ msecs: TIME }), + v1({ msecs: TIME + 1 }), + v1({ msecs: TIME + 28 * 24 * 3600 * 1000 }), + ]; + + const sorted = [...ids].sort((a, b) => { + a = a + .split('-') + .reverse() + .join('-'); + b = b + .split('-') + .reverse() + .join('-'); + return a < b ? -1 : a > b ? 1 : 0; + }); + + assert.deepEqual(ids, sorted); + }); + + test('msec', () => { + assert(v1({ msecs: TIME }) !== v1({ msecs: TIME }), 'IDs created at same msec are different'); + }); + + test('exception thrown when > 10k ids created in 1ms', () => { + assert.throws(function() { + v1({ msecs: TIME, nsecs: 10000 }); + }, 'throws when > 10K ids created in 1 ms'); + }); + + test('clock regression by msec', () => { + // Verify clock regression bumps clockseq + const uidt = v1({ msecs: TIME }); + const uidtb = v1({ msecs: TIME - 1 }); + assert( + parseInt(uidtb.split('-')[3], 16) - parseInt(uidt.split('-')[3], 16) === 1, + 'Clock regression by msec increments the clockseq', + ); + }); + + test('clock regression by nsec', () => { + // Verify clock regression bumps clockseq + const uidtn = v1({ msecs: TIME, nsecs: 10 }); + const uidtnb = v1({ msecs: TIME, nsecs: 9 }); + assert( + parseInt(uidtnb.split('-')[3], 16) - parseInt(uidtn.split('-')[3], 16) === 1, + 'Clock regression by nsec increments the clockseq', + ); + }); + + test('explicit options product expected id', () => { + // Verify explicit options produce expected id + const id = v1({ + msecs: 1321651533573, + nsecs: 5432, + clockseq: 0x385c, + node: [0x61, 0xcd, 0x3c, 0xbb, 0x32, 0x10], + }); + assert(id === 'd9428888-122b-11e1-b85c-61cd3cbb3210', 'Explicit options produce expected id'); + }); + + test('ids spanning 1ms boundary are 100ns apart', () => { + // Verify adjacent ids across a msec boundary are 1 time unit apart + const u0 = v1({ msecs: TIME, nsecs: 9999 }); + const u1 = v1({ msecs: TIME + 1, nsecs: 0 }); + + const before = u0.split('-')[0]; + const after = u1.split('-')[0]; + const dt = parseInt(after, 16) - parseInt(before, 16); + assert(dt === 1, 'Ids spanning 1ms boundary are 100ns apart'); + }); +}); diff --git a/test/unit/unit.test.js b/test/unit/v35.test.js similarity index 59% rename from test/unit/unit.test.js rename to test/unit/v35.test.js index c5b0d648..ef9c50f7 100644 --- a/test/unit/unit.test.js +++ b/test/unit/v35.test.js @@ -1,141 +1,11 @@ import assert from 'assert'; import md5 from '../../src/md5.js'; import md5Browser from '../../src/md5-browser.js'; -import rng from '../../src/rng.js'; -import rngBrowser from '../../src/rng-browser.js'; import sha1 from '../../src/sha1.js'; import sha1Browser from '../../src/sha1-browser.js'; -import v1 from '../../src/v1.js'; import v3 from '../../src/v3.js'; import v5 from '../../src/v5.js'; -describe('rng', () => { - test('nodeRNG', () => { - assert.equal(rng.name, 'nodeRNG'); - - var bytes = rng(); - assert.equal(bytes.length, 16); - - for (var i = 0; i < bytes.length; i++) { - assert.equal(typeof bytes[i], 'number'); - } - }); - - test('mathRNG', () => { - assert.equal(rngBrowser.name, 'mathRNG'); - - var bytes = rng(); - assert.equal(bytes.length, 16); - - for (var i = 0; i < bytes.length; i++) { - assert.equal(typeof bytes[i], 'number'); - } - }); - - // Test of whatwgRNG missing for now since with esmodules we can no longer manipulate the - // require.cache. -}); - -// Verify ordering of v1 ids created with explicit times -const TIME = 1321644961388; // 2011-11-18 11:36:01.388-08:00 - -describe('v1', () => { - test('v1 sort order (default)', () => { - const ids = [v1(), v1(), v1(), v1(), v1()]; - - const sorted = [...ids].sort((a, b) => { - a = a - .split('-') - .reverse() - .join('-'); - b = b - .split('-') - .reverse() - .join('-'); - return a < b ? -1 : a > b ? 1 : 0; - }); - - assert.deepEqual(ids, sorted); - }); - - // Verify ordering of v1 ids created with explicit times - test('v1 sort order (time option)', () => { - const ids = [ - v1({ msecs: TIME - 10 * 3600 * 1000 }), - v1({ msecs: TIME - 1 }), - v1({ msecs: TIME }), - v1({ msecs: TIME + 1 }), - v1({ msecs: TIME + 28 * 24 * 3600 * 1000 }), - ]; - - const sorted = [...ids].sort((a, b) => { - a = a - .split('-') - .reverse() - .join('-'); - b = b - .split('-') - .reverse() - .join('-'); - return a < b ? -1 : a > b ? 1 : 0; - }); - - assert.deepEqual(ids, sorted); - }); - - test('msec', () => { - assert(v1({ msecs: TIME }) !== v1({ msecs: TIME }), 'IDs created at same msec are different'); - }); - - test('exception thrown when > 10k ids created in 1ms', () => { - assert.throws(function() { - v1({ msecs: TIME, nsecs: 10000 }); - }, 'throws when > 10K ids created in 1 ms'); - }); - - test('clock regression by msec', () => { - // Verify clock regression bumps clockseq - const uidt = v1({ msecs: TIME }); - const uidtb = v1({ msecs: TIME - 1 }); - assert( - parseInt(uidtb.split('-')[3], 16) - parseInt(uidt.split('-')[3], 16) === 1, - 'Clock regression by msec increments the clockseq', - ); - }); - - test('clock regression by nsec', () => { - // Verify clock regression bumps clockseq - const uidtn = v1({ msecs: TIME, nsecs: 10 }); - const uidtnb = v1({ msecs: TIME, nsecs: 9 }); - assert( - parseInt(uidtnb.split('-')[3], 16) - parseInt(uidtn.split('-')[3], 16) === 1, - 'Clock regression by nsec increments the clockseq', - ); - }); - - test('explicit options product expected id', () => { - // Verify explicit options produce expected id - const id = v1({ - msecs: 1321651533573, - nsecs: 5432, - clockseq: 0x385c, - node: [0x61, 0xcd, 0x3c, 0xbb, 0x32, 0x10], - }); - assert(id === 'd9428888-122b-11e1-b85c-61cd3cbb3210', 'Explicit options produce expected id'); - }); - - test('ids spanning 1ms boundary are 100ns apart', () => { - // Verify adjacent ids across a msec boundary are 1 time unit apart - const u0 = v1({ msecs: TIME, nsecs: 9999 }); - const u1 = v1({ msecs: TIME + 1, nsecs: 0 }); - - const before = u0.split('-')[0]; - const after = u1.split('-')[0]; - const dt = parseInt(after, 16) - parseInt(before, 16); - assert(dt === 1, 'Ids spanning 1ms boundary are 100ns apart'); - }); -}); - describe('v5', () => { const HASH_SAMPLES = [ { diff --git a/test/unit/v4.test.js b/test/unit/v4.test.js new file mode 100644 index 00000000..5049e1dd --- /dev/null +++ b/test/unit/v4.test.js @@ -0,0 +1,74 @@ +import assert from 'assert'; +import v4 from '../../src/v4.js'; + +describe('v4', () => { + const randomBytesFixture = [ + 0x10, + 0x91, + 0x56, + 0xbe, + 0xc4, + 0xfb, + 0xc1, + 0xea, + 0x71, + 0xb4, + 0xef, + 0xe1, + 0x67, + 0x1c, + 0x58, + 0x36, + ]; + const expectedBytes = [16, 145, 86, 190, 196, 251, 65, 234, 177, 180, 239, 225, 103, 28, 88, 54]; + + test('subsequent UUIDs are different', () => { + const id1 = v4(); + const id2 = v4(); + assert(id1 !== id2); + }); + + test('explicit options.random produces expected result', () => { + const id = v4({ + random: randomBytesFixture, + }); + assert.strictEqual(id, '109156be-c4fb-41ea-b1b4-efe1671c5836'); + }); + + test('explicit options.rng produces expected result', () => { + const id = v4({ + rng: () => randomBytesFixture, + }); + assert.strictEqual(id, '109156be-c4fb-41ea-b1b4-efe1671c5836'); + }); + + test('fills one UUID into a buffer as expected', () => { + const buffer = new Array(); + v4( + { + random: randomBytesFixture, + }, + buffer, + ); + assert.deepEqual(buffer, expectedBytes); + }); + + test('fills two UUIDs into a buffer as expected', () => { + const buffer = new Array(); + v4( + { + random: randomBytesFixture, + }, + buffer, + 0, + ); + v4( + { + random: randomBytesFixture, + }, + buffer, + 16, + ); + assert.deepEqual(buffer, expectedBytes.concat(expectedBytes)); + }); +});