From 15b080dd54cb30b1b4874e1ad1e807460a7a52c9 Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Mon, 27 Jan 2020 10:54:11 +0100 Subject: [PATCH 01/10] docs: fix esmodule example readme --- examples/browser-esmodules/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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`. From f842a234a5857f00c81bf8886f1958678937536d Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Thu, 23 Jan 2020 13:32:21 +0100 Subject: [PATCH 02/10] docs: fix badge url --- README.md | 2 +- README_js.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ee4e3643..c421f5a2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ -- 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. diff --git a/README_js.md b/README_js.md index 5b1de0ed..f9c609e9 100644 --- a/README_js.md +++ b/README_js.md @@ -15,7 +15,7 @@ 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. From 8e7e111664d41241b43c57c59cc25f9b93269b52 Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Thu, 23 Jan 2020 13:33:46 +0100 Subject: [PATCH 03/10] docs: make consistent use of term "UUIDs" --- README.md | 2 +- README_js.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c421f5a2..3c30c6b7 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # 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: diff --git a/README_js.md b/README_js.md index f9c609e9..94675d0b 100644 --- a/README_js.md +++ b/README_js.md @@ -17,7 +17,7 @@ require('crypto').randomBytes = function() { # 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: From 50b0969d8715092f192e2a4c18f107891469a7cd Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Thu, 23 Jan 2020 14:04:20 +0100 Subject: [PATCH 04/10] docs: clarify precedence of options.random for v4() --- README.md | 4 ++-- README_js.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3c30c6b7..b0d4fb3e 100644 --- a/README.md +++ b/README.md @@ -217,8 +217,8 @@ 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) + - `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. diff --git a/README_js.md b/README_js.md index 94675d0b..1f2c5d01 100644 --- a/README_js.md +++ b/README_js.md @@ -211,8 +211,8 @@ 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) + - `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. From 5b0fd67053a06e76b7044bbb66eec888c2a0e4c0 Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Thu, 23 Jan 2020 13:49:42 +0100 Subject: [PATCH 05/10] test: add api test for v4() --- test/unit/v4.test.js | 74 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 test/unit/v4.test.js 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)); + }); +}); From 0f7ac97e8755c1878ccb866483ea99ef669c6ad1 Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Thu, 23 Jan 2020 14:35:54 +0100 Subject: [PATCH 06/10] test: move v3 and v5 tests to separate file --- test/unit/unit.test.js | 171 ---------------------------------------- test/unit/v35.test.js | 172 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 171 deletions(-) create mode 100644 test/unit/v35.test.js diff --git a/test/unit/unit.test.js b/test/unit/unit.test.js index c5b0d648..733f0250 100644 --- a/test/unit/unit.test.js +++ b/test/unit/unit.test.js @@ -1,13 +1,7 @@ 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', () => { @@ -135,168 +129,3 @@ describe('v1', () => { assert(dt === 1, 'Ids spanning 1ms boundary are 100ns apart'); }); }); - -describe('v5', () => { - const HASH_SAMPLES = [ - { - input: '', - sha1: 'da39a3ee5e6b4b0d3255bfef95601890afd80709', - md5: 'd41d8cd98f00b204e9800998ecf8427e', - }, - - // Extended ascii chars - { - input: - '\t\b\f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u00A1\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7\u00A8\u00A9\u00AA\u00AB\u00AC\u00AE\u00AF\u00B0\u00B1\u00B2\u00B3\u00B4\u00B5\u00B6\u00B7\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF', - sha1: 'ca4a426a3d536f14cfd79011e79e10d64de950a0', - md5: 'e8098ec21950f841731d28749129d3ee', - }, - - // A sampling from the Unicode BMP - { - input: - '\u00A5\u0104\u018F\u0256\u02B1o\u0315\u038E\u0409\u0500\u0531\u05E1\u05B6\u0920\u0903\u09A4\u0983\u0A20\u0A02\u0AA0\u0A83\u0B06\u0C05\u0C03\u1401\u16A0', - sha1: 'f2753ebc390e5f637e333c2a4179644a93ae9f65', - md5: '231b309e277b6be8bb3d6c688b7f098b', - }, - ]; - - function hashToHex(hash) { - if (hash instanceof Buffer) hash = Array.from(hash); - return hash - .map(function(b) { - return b.toString(16).padStart(2, '0'); - }) - .join(''); - } - - test('sha1 node', () => { - HASH_SAMPLES.forEach(function(sample) { - assert.equal(hashToHex(sha1(sample.input)), sample.sha1); - }); - }); - - test('sha1 browser', () => { - HASH_SAMPLES.forEach(function(sample) { - assert.equal(hashToHex(sha1Browser(sample.input)), sample.sha1); - }); - }); - - test('md5 node', () => { - HASH_SAMPLES.forEach(function(sample) { - assert.equal(hashToHex(md5(sample.input)), sample.md5); - }); - }); - - test('md5 browser', () => { - HASH_SAMPLES.forEach(function(sample) { - assert.equal(hashToHex(md5Browser(sample.input)), sample.md5); - }); - }); - - test('v3', () => { - // Expect to get the same results as http://tools.adjet.org/uuid-v3 - assert.equal(v3('hello.example.com', v3.DNS), '9125a8dc-52ee-365b-a5aa-81b0b3681cf6'); - assert.equal(v3('http://example.com/hello', v3.URL), 'c6235813-3ba4-3801-ae84-e0a6ebb7d138'); - assert.equal( - v3('hello', '0f5abcd1-c194-47f3-905b-2df7263a084b'), - 'a981a0c2-68b1-35dc-bcfc-296e52ab01ec', - ); - - // test the buffer functionality - let buf = new Array(16); - const testBuf = [ - 0x91, - 0x25, - 0xa8, - 0xdc, - 0x52, - 0xee, - 0x36, - 0x5b, - 0xa5, - 0xaa, - 0x81, - 0xb0, - 0xb3, - 0x68, - 0x1c, - 0xf6, - ]; - v3('hello.example.com', v3.DNS, buf); - assert.ok( - buf.length === testBuf.length && - buf.every(function(elem, idx) { - return elem === testBuf[idx]; - }), - ); - - // test offsets as well - buf = new Array(19); - for (let i = 0; i < 3; ++i) buf[i] = 'landmaster'; - v3('hello.example.com', v3.DNS, buf, 3); - assert.ok( - buf.length === testBuf.length + 3 && - buf.every(function(elem, idx) { - return idx >= 3 ? elem === testBuf[idx - 3] : elem === 'landmaster'; - }), - 'hello', - ); - }); - - test('v5', () => { - // Expect to get the same results as http://tools.adjet.org/uuid-v5 - assert.equal(v5('hello.example.com', v5.DNS), 'fdda765f-fc57-5604-a269-52a7df8164ec'); - assert.equal(v5('http://example.com/hello', v5.URL), '3bbcee75-cecc-5b56-8031-b6641c1ed1f1'); - assert.equal( - v5('hello', '0f5abcd1-c194-47f3-905b-2df7263a084b'), - '90123e1c-7512-523e-bb28-76fab9f2f73d', - ); - - // test the buffer functionality - let buf = new Array(16); - const testBuf = [ - 0xfd, - 0xda, - 0x76, - 0x5f, - 0xfc, - 0x57, - 0x56, - 0x04, - 0xa2, - 0x69, - 0x52, - 0xa7, - 0xdf, - 0x81, - 0x64, - 0xec, - ]; - v5('hello.example.com', v5.DNS, buf); - assert.ok( - buf.length === testBuf.length && - buf.every(function(elem, idx) { - return elem === testBuf[idx]; - }), - ); - - // test offsets as well - buf = new Array(19); - for (let i = 0; i < 3; ++i) buf[i] = 'landmaster'; - v5('hello.example.com', v5.DNS, buf, 3); - assert.ok( - buf.length === testBuf.length + 3 && - buf.every(function(elem, idx) { - return idx >= 3 ? elem === testBuf[idx - 3] : elem === 'landmaster'; - }), - ); - }); - - test('v3/v5 constants', () => { - assert.equal(v3.DNS, '6ba7b810-9dad-11d1-80b4-00c04fd430c8'); - assert.equal(v3.URL, '6ba7b811-9dad-11d1-80b4-00c04fd430c8'); - assert.equal(v5.DNS, '6ba7b810-9dad-11d1-80b4-00c04fd430c8'); - assert.equal(v5.URL, '6ba7b811-9dad-11d1-80b4-00c04fd430c8'); - }); -}); diff --git a/test/unit/v35.test.js b/test/unit/v35.test.js new file mode 100644 index 00000000..ef9c50f7 --- /dev/null +++ b/test/unit/v35.test.js @@ -0,0 +1,172 @@ +import assert from 'assert'; +import md5 from '../../src/md5.js'; +import md5Browser from '../../src/md5-browser.js'; +import sha1 from '../../src/sha1.js'; +import sha1Browser from '../../src/sha1-browser.js'; +import v3 from '../../src/v3.js'; +import v5 from '../../src/v5.js'; + +describe('v5', () => { + const HASH_SAMPLES = [ + { + input: '', + sha1: 'da39a3ee5e6b4b0d3255bfef95601890afd80709', + md5: 'd41d8cd98f00b204e9800998ecf8427e', + }, + + // Extended ascii chars + { + input: + '\t\b\f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u00A1\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7\u00A8\u00A9\u00AA\u00AB\u00AC\u00AE\u00AF\u00B0\u00B1\u00B2\u00B3\u00B4\u00B5\u00B6\u00B7\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF', + sha1: 'ca4a426a3d536f14cfd79011e79e10d64de950a0', + md5: 'e8098ec21950f841731d28749129d3ee', + }, + + // A sampling from the Unicode BMP + { + input: + '\u00A5\u0104\u018F\u0256\u02B1o\u0315\u038E\u0409\u0500\u0531\u05E1\u05B6\u0920\u0903\u09A4\u0983\u0A20\u0A02\u0AA0\u0A83\u0B06\u0C05\u0C03\u1401\u16A0', + sha1: 'f2753ebc390e5f637e333c2a4179644a93ae9f65', + md5: '231b309e277b6be8bb3d6c688b7f098b', + }, + ]; + + function hashToHex(hash) { + if (hash instanceof Buffer) hash = Array.from(hash); + return hash + .map(function(b) { + return b.toString(16).padStart(2, '0'); + }) + .join(''); + } + + test('sha1 node', () => { + HASH_SAMPLES.forEach(function(sample) { + assert.equal(hashToHex(sha1(sample.input)), sample.sha1); + }); + }); + + test('sha1 browser', () => { + HASH_SAMPLES.forEach(function(sample) { + assert.equal(hashToHex(sha1Browser(sample.input)), sample.sha1); + }); + }); + + test('md5 node', () => { + HASH_SAMPLES.forEach(function(sample) { + assert.equal(hashToHex(md5(sample.input)), sample.md5); + }); + }); + + test('md5 browser', () => { + HASH_SAMPLES.forEach(function(sample) { + assert.equal(hashToHex(md5Browser(sample.input)), sample.md5); + }); + }); + + test('v3', () => { + // Expect to get the same results as http://tools.adjet.org/uuid-v3 + assert.equal(v3('hello.example.com', v3.DNS), '9125a8dc-52ee-365b-a5aa-81b0b3681cf6'); + assert.equal(v3('http://example.com/hello', v3.URL), 'c6235813-3ba4-3801-ae84-e0a6ebb7d138'); + assert.equal( + v3('hello', '0f5abcd1-c194-47f3-905b-2df7263a084b'), + 'a981a0c2-68b1-35dc-bcfc-296e52ab01ec', + ); + + // test the buffer functionality + let buf = new Array(16); + const testBuf = [ + 0x91, + 0x25, + 0xa8, + 0xdc, + 0x52, + 0xee, + 0x36, + 0x5b, + 0xa5, + 0xaa, + 0x81, + 0xb0, + 0xb3, + 0x68, + 0x1c, + 0xf6, + ]; + v3('hello.example.com', v3.DNS, buf); + assert.ok( + buf.length === testBuf.length && + buf.every(function(elem, idx) { + return elem === testBuf[idx]; + }), + ); + + // test offsets as well + buf = new Array(19); + for (let i = 0; i < 3; ++i) buf[i] = 'landmaster'; + v3('hello.example.com', v3.DNS, buf, 3); + assert.ok( + buf.length === testBuf.length + 3 && + buf.every(function(elem, idx) { + return idx >= 3 ? elem === testBuf[idx - 3] : elem === 'landmaster'; + }), + 'hello', + ); + }); + + test('v5', () => { + // Expect to get the same results as http://tools.adjet.org/uuid-v5 + assert.equal(v5('hello.example.com', v5.DNS), 'fdda765f-fc57-5604-a269-52a7df8164ec'); + assert.equal(v5('http://example.com/hello', v5.URL), '3bbcee75-cecc-5b56-8031-b6641c1ed1f1'); + assert.equal( + v5('hello', '0f5abcd1-c194-47f3-905b-2df7263a084b'), + '90123e1c-7512-523e-bb28-76fab9f2f73d', + ); + + // test the buffer functionality + let buf = new Array(16); + const testBuf = [ + 0xfd, + 0xda, + 0x76, + 0x5f, + 0xfc, + 0x57, + 0x56, + 0x04, + 0xa2, + 0x69, + 0x52, + 0xa7, + 0xdf, + 0x81, + 0x64, + 0xec, + ]; + v5('hello.example.com', v5.DNS, buf); + assert.ok( + buf.length === testBuf.length && + buf.every(function(elem, idx) { + return elem === testBuf[idx]; + }), + ); + + // test offsets as well + buf = new Array(19); + for (let i = 0; i < 3; ++i) buf[i] = 'landmaster'; + v5('hello.example.com', v5.DNS, buf, 3); + assert.ok( + buf.length === testBuf.length + 3 && + buf.every(function(elem, idx) { + return idx >= 3 ? elem === testBuf[idx - 3] : elem === 'landmaster'; + }), + ); + }); + + test('v3/v5 constants', () => { + assert.equal(v3.DNS, '6ba7b810-9dad-11d1-80b4-00c04fd430c8'); + assert.equal(v3.URL, '6ba7b811-9dad-11d1-80b4-00c04fd430c8'); + assert.equal(v5.DNS, '6ba7b810-9dad-11d1-80b4-00c04fd430c8'); + assert.equal(v5.URL, '6ba7b811-9dad-11d1-80b4-00c04fd430c8'); + }); +}); From f5307800e53252d3dd3829522cef53c13d61bfd6 Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Thu, 23 Jan 2020 14:37:06 +0100 Subject: [PATCH 07/10] test: move rng tests to separate file --- test/unit/rng.test.js | 30 ++++++++++++++++++++++++++++++ test/unit/unit.test.js | 29 ----------------------------- 2 files changed, 30 insertions(+), 29 deletions(-) create mode 100644 test/unit/rng.test.js diff --git a/test/unit/rng.test.js b/test/unit/rng.test.js new file mode 100644 index 00000000..f804c906 --- /dev/null +++ b/test/unit/rng.test.js @@ -0,0 +1,30 @@ +import assert from 'assert'; +import rng from '../../src/rng.js'; +import rngBrowser from '../../src/rng-browser.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. +}); diff --git a/test/unit/unit.test.js b/test/unit/unit.test.js index 733f0250..299bfef9 100644 --- a/test/unit/unit.test.js +++ b/test/unit/unit.test.js @@ -1,35 +1,6 @@ import assert from 'assert'; -import rng from '../../src/rng.js'; -import rngBrowser from '../../src/rng-browser.js'; import v1 from '../../src/v1.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 From 261cec5f7d63cf2d54fdfdf9583f9f2bc5bea2de Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Thu, 23 Jan 2020 14:37:27 +0100 Subject: [PATCH 08/10] test: rename v1 test file appropriately --- test/unit/{unit.test.js => v1.test.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/unit/{unit.test.js => v1.test.js} (100%) diff --git a/test/unit/unit.test.js b/test/unit/v1.test.js similarity index 100% rename from test/unit/unit.test.js rename to test/unit/v1.test.js From 4802e8b288a067929280e83f6210487da29dd3c7 Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Thu, 23 Jan 2020 14:58:09 +0100 Subject: [PATCH 09/10] docs: make v4 UUIDs more prominent in the docs Also add a reminder that v1 UUIDs may very often not be the ideal choice. This relates to the research from https://github.com/tc39/proposal-uuid/blob/master/analysis/README.md --- README.md | 142 ++++++++++++++++++++++++++------------------------- README_js.md | 114 +++++++++++++++++++++-------------------- 2 files changed, 132 insertions(+), 124 deletions(-) diff --git a/README.md b/README.md index b0d4fb3e..d3cbfca8 100644 --- a/README.md +++ b/README.md @@ -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'); @@ -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. 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 5 (Namespace) ```javascript diff --git a/README_js.md b/README_js.md index 1f2c5d01..3d0aad2e 100644 --- a/README_js.md +++ b/README_js.md @@ -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'); @@ -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. 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 5 (Namespace) ```javascript From 3a5842b141a6e5de0ae338f391661e6b84b167c9 Mon Sep 17 00:00:00 2001 From: Christoph Tavan Date: Thu, 23 Jan 2020 14:38:38 +0100 Subject: [PATCH 10/10] feat: remove insecure fallback random number generator BREAKING CHANGE: Remove builtin support for insecure random number generators in the browser. Users who want that will have to supply their own random number generator function. Fixes #173. --- README.md | 8 +++---- README_js.md | 8 +++---- src/rng-browser.js | 46 +++++++++++-------------------------- src/rng.js | 2 +- src/v1.js | 2 +- test/unit/rng.test.js | 17 ++++---------- test/unit/v1-random.test.js | 34 +++++++++++++++++++++++++++ test/unit/v1-rng.test.js | 34 +++++++++++++++++++++++++++ 8 files changed, 96 insertions(+), 55 deletions(-) create mode 100644 test/unit/v1-random.test.js create mode 100644 test/unit/v1-rng.test.js diff --git a/README.md b/README.md index d3cbfca8..f405894f 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ 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 @@ -201,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. diff --git a/README_js.md b/README_js.md index 3d0aad2e..504d5e57 100644 --- a/README_js.md +++ b/README_js.md @@ -24,8 +24,8 @@ 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 @@ -196,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. 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 index f804c906..f7cd6364 100644 --- a/test/unit/rng.test.js +++ b/test/unit/rng.test.js @@ -3,9 +3,7 @@ import rng from '../../src/rng.js'; import rngBrowser from '../../src/rng-browser.js'; describe('rng', () => { - test('nodeRNG', () => { - assert.equal(rng.name, 'nodeRNG'); - + test('Node.js RNG', () => { var bytes = rng(); assert.equal(bytes.length, 16); @@ -14,15 +12,10 @@ describe('rng', () => { } }); - 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('Browser without crypto.getRandomValues()', () => { + assert.throws(() => { + rngBrowser(); + }); }); // Test of whatwgRNG missing for now since with esmodules we can no longer manipulate the 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'); + }); +});