Skip to content

Commit 0e6c10b

Browse files
ctavanawwitbroofaLinusU
authoredJul 22, 2020
feat: add parse/stringify/validate/version/NIL APIs (#479)
Add the following new APIs: - uuid.NIL The nil UUID string (all zeros) - uuid.parse() Convert UUID string to array of bytes - uuid.stringify() Convert array of bytes to UUID string - uuid.validate() Test a string to see if it is a valid UUID - uuid.version() Detect RFC version of a UUID This commit also adds more accurate validation when stringifying UUIDs and improves UUID parsing performance (for v3/v5 UUIDs) significantly (Thanks @awwit!). Co-authored-by: Christoph Tavan <dev@tavan.de> Co-authored-by: Ignat Prokopovich <ignatius.awwit@gmail.com> Co-authored-by: Robert Kieffer <robert@broofa.com> Co-authored-by: Linus Unnebäck <linus@folkdatorn.se>
1 parent cba367a commit 0e6c10b

25 files changed

+819
-420
lines changed
 

‎README.md

+186-160
Large diffs are not rendered by default.

‎README_js.md

+190-147
Large diffs are not rendered by default.

‎bundlewatch.config.json

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
22
"files": [
3-
{ "path": "./examples/browser-rollup/dist/v1-size.js", "maxSize": "0.8 kB" },
4-
{ "path": "./examples/browser-rollup/dist/v3-size.js", "maxSize": "1.8 kB" },
5-
{ "path": "./examples/browser-rollup/dist/v4-size.js", "maxSize": "0.5 kB" },
6-
{ "path": "./examples/browser-rollup/dist/v5-size.js", "maxSize": "1.2 kB" },
3+
{ "path": "./examples/browser-rollup/dist/v1-size.js", "maxSize": "1.0 kB" },
4+
{ "path": "./examples/browser-rollup/dist/v3-size.js", "maxSize": "2.1 kB" },
5+
{ "path": "./examples/browser-rollup/dist/v4-size.js", "maxSize": "0.7 kB" },
6+
{ "path": "./examples/browser-rollup/dist/v5-size.js", "maxSize": "1.5 kB" },
77

8-
{ "path": "./examples/browser-webpack/dist/v1-size.js", "maxSize": "1.0 kB" },
9-
{ "path": "./examples/browser-webpack/dist/v3-size.js", "maxSize": "2.0 kB" },
10-
{ "path": "./examples/browser-webpack/dist/v4-size.js", "maxSize": "0.7 kB" },
11-
{ "path": "./examples/browser-webpack/dist/v5-size.js", "maxSize": "1.4 kB" }
8+
{ "path": "./examples/browser-webpack/dist/v1-size.js", "maxSize": "1.3 kB" },
9+
{ "path": "./examples/browser-webpack/dist/v3-size.js", "maxSize": "2.5 kB" },
10+
{ "path": "./examples/browser-webpack/dist/v4-size.js", "maxSize": "1.0 kB" },
11+
{ "path": "./examples/browser-webpack/dist/v5-size.js", "maxSize": "1.9 kB" }
1212
]
1313
}

‎examples/benchmark/benchmark.html

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
<script src="./node_modules/uuid/dist/umd/uuidv3.min.js"></script>
66
<script src="./node_modules/uuid/dist/umd/uuidv4.min.js"></script>
77
<script src="./node_modules/uuid/dist/umd/uuidv5.min.js"></script>
8+
<script src="./node_modules/uuid/dist/umd/uuidParse.min.js"></script>
9+
<script src="./node_modules/uuid/dist/umd/uuidStringify.min.js"></script>
810
<script src="./node_modules/lodash/lodash.js"></script>
911
<script src="./node_modules/benchmark/benchmark.js"></script>
1012
<script src="./benchmark.js"></script>

‎examples/benchmark/benchmark.js

+90-40
Original file line numberDiff line numberDiff line change
@@ -5,46 +5,96 @@ const uuidv1 = (typeof window !== 'undefined' && window.uuidv1) || require('uuid
55
const uuidv4 = (typeof window !== 'undefined' && window.uuidv4) || require('uuid').v4;
66
const uuidv3 = (typeof window !== 'undefined' && window.uuidv3) || require('uuid').v3;
77
const uuidv5 = (typeof window !== 'undefined' && window.uuidv5) || require('uuid').v5;
8+
const uuidParse = (typeof window !== 'undefined' && window.uuidParse) || require('uuid').parse;
9+
const uuidStringify =
10+
(typeof window !== 'undefined' && window.uuidStringify) || require('uuid').stringify;
811

912
console.log('Starting. Tests take ~1 minute to run ...');
1013

11-
const array = new Array(16);
12-
13-
const suite = new Benchmark.Suite({
14-
onError(event) {
15-
console.error(event.target.error);
16-
},
17-
});
18-
19-
suite
20-
.add('uuidv1()', function () {
21-
uuidv1();
22-
})
23-
.add('uuidv1() fill existing array', function () {
24-
try {
25-
uuidv1(null, array, 0);
26-
} catch (err) {
27-
// The spec (https://tools.ietf.org/html/rfc4122#section-4.2.1.2) defines that only 10M/s v1
28-
// UUIDs can be generated on a single node. This library throws an error if we hit that limit
29-
// (which can happen on modern hardware and modern Node.js versions).
30-
}
31-
})
32-
.add('uuidv4()', function () {
33-
uuidv4();
34-
})
35-
.add('uuidv4() fill existing array', function () {
36-
uuidv4(null, array, 0);
37-
})
38-
.add('uuidv3()', function () {
39-
uuidv3('hello.example.com', uuidv3.DNS);
40-
})
41-
.add('uuidv5()', function () {
42-
uuidv5('hello.example.com', uuidv5.DNS);
43-
})
44-
.on('cycle', function (event) {
45-
console.log(event.target.toString());
46-
})
47-
.on('complete', function () {
48-
console.log('Fastest is ' + this.filter('fastest').map('name'));
49-
})
50-
.run();
14+
function testParseAndStringify() {
15+
const suite = new Benchmark.Suite({
16+
onError(event) {
17+
console.error(event.target.error);
18+
},
19+
});
20+
21+
const BYTES = [
22+
0x0f,
23+
0x5a,
24+
0xbc,
25+
0xd1,
26+
0xc1,
27+
0x94,
28+
0x47,
29+
0xf3,
30+
0x90,
31+
0x5b,
32+
0x2d,
33+
0xf7,
34+
0x26,
35+
0x3a,
36+
0x08,
37+
0x4b,
38+
];
39+
40+
suite
41+
.add('uuidStringify()', function () {
42+
uuidStringify(BYTES);
43+
})
44+
.add('uuidParse()', function () {
45+
uuidParse('0f5abcd1-c194-47f3-905b-2df7263a084b');
46+
})
47+
.on('cycle', function (event) {
48+
console.log(event.target.toString());
49+
})
50+
.on('complete', function () {
51+
console.log('---\n');
52+
})
53+
.run();
54+
}
55+
56+
function testGeneration() {
57+
const array = new Array(16);
58+
59+
const suite = new Benchmark.Suite({
60+
onError(event) {
61+
console.error(event.target.error);
62+
},
63+
});
64+
65+
suite
66+
.add('uuidv1()', function () {
67+
uuidv1();
68+
})
69+
.add('uuidv1() fill existing array', function () {
70+
try {
71+
uuidv1(null, array, 0);
72+
} catch (err) {
73+
// The spec (https://tools.ietf.org/html/rfc4122#section-4.2.1.2) defines that only 10M/s v1
74+
// UUIDs can be generated on a single node. This library throws an error if we hit that limit
75+
// (which can happen on modern hardware and modern Node.js versions).
76+
}
77+
})
78+
.add('uuidv4()', function () {
79+
uuidv4();
80+
})
81+
.add('uuidv4() fill existing array', function () {
82+
uuidv4(null, array, 0);
83+
})
84+
.add('uuidv3()', function () {
85+
uuidv3('hello.example.com', uuidv3.DNS);
86+
})
87+
.add('uuidv5()', function () {
88+
uuidv5('hello.example.com', uuidv5.DNS);
89+
})
90+
.on('cycle', function (event) {
91+
console.log(event.target.toString());
92+
})
93+
.on('complete', function () {
94+
console.log('Fastest is ' + this.filter('fastest').map('name'));
95+
})
96+
.run();
97+
}
98+
99+
testParseAndStringify();
100+
testGeneration();

‎package-lock.json

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"lint-staged": "10.2.11",
7474
"npm-run-all": "4.1.5",
7575
"prettier": "2.0.5",
76+
"random-seed": "0.3.0",
7677
"rollup": "2.18.0",
7778
"rollup-plugin-terser": "6.1.0",
7879
"runmd": "1.3.2",

‎rollup.config.js

+7
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,11 @@ export default [
2020
chunk('v3', 'uuidv3'),
2121
chunk('v4', 'uuidv4'),
2222
chunk('v5', 'uuidv5'),
23+
24+
chunk('nil', 'uuidNIL'),
25+
26+
chunk('version', 'uuidVersion'),
27+
chunk('validate', 'uuidValidate'),
28+
chunk('parse', 'uuidParse'),
29+
chunk('stringify', 'uuidStringify'),
2330
];

‎src/bytesToUuid.js

-40
This file was deleted.

‎src/index.js

+5
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@ export { default as v1 } from './v1.js';
22
export { default as v3 } from './v3.js';
33
export { default as v4 } from './v4.js';
44
export { default as v5 } from './v5.js';
5+
export { default as NIL } from './nil.js';
6+
export { default as version } from './version.js';
7+
export { default as validate } from './validate.js';
8+
export { default as stringify } from './stringify.js';
9+
export { default as parse } from './parse.js';

‎src/nil.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default '00000000-0000-0000-0000-000000000000';

‎src/parse.js

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import validate from './validate.js';
2+
3+
function parse(uuid) {
4+
if (!validate(uuid)) {
5+
throw TypeError('Invalid UUID');
6+
}
7+
8+
let v;
9+
const arr = new Uint8Array(16);
10+
11+
// Parse ########-....-....-....-............
12+
arr[0] = (v = parseInt(uuid.slice(0, 8), 16)) >>> 24;
13+
arr[1] = (v >>> 16) & 0xff;
14+
arr[2] = (v >>> 8) & 0xff;
15+
arr[3] = v & 0xff;
16+
17+
// Parse ........-####-....-....-............
18+
arr[4] = (v = parseInt(uuid.slice(9, 13), 16)) >>> 8;
19+
arr[5] = v & 0xff;
20+
21+
// Parse ........-....-####-....-............
22+
arr[6] = (v = parseInt(uuid.slice(14, 18), 16)) >>> 8;
23+
arr[7] = v & 0xff;
24+
25+
// Parse ........-....-....-####-............
26+
arr[8] = (v = parseInt(uuid.slice(19, 23), 16)) >>> 8;
27+
arr[9] = v & 0xff;
28+
29+
// Parse ........-....-....-....-############
30+
// (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes)
31+
arr[10] = ((v = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000) & 0xff;
32+
arr[11] = (v / 0x100000000) & 0xff;
33+
arr[12] = (v >>> 24) & 0xff;
34+
arr[13] = (v >>> 16) & 0xff;
35+
arr[14] = (v >>> 8) & 0xff;
36+
arr[15] = v & 0xff;
37+
38+
return arr;
39+
}
40+
41+
export default parse;

‎src/regex.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;

‎src/sha1-browser.js

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ function sha1(bytes) {
2929
for (let i = 0; i < msg.length; ++i) {
3030
bytes.push(msg.charCodeAt(i));
3131
}
32+
} else if (!Array.isArray(bytes)) {
33+
// Convert Array-like to Array
34+
bytes = Array.prototype.slice.call(bytes);
3235
}
3336

3437
bytes.push(0x80);

‎src/stringify.js

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import validate from './validate.js';
2+
3+
/**
4+
* Convert array of 16 byte values to UUID string format of the form:
5+
* XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
6+
*/
7+
const byteToHex = [];
8+
9+
for (let i = 0; i < 256; ++i) {
10+
byteToHex.push((i + 0x100).toString(16).substr(1));
11+
}
12+
13+
function stringify(arr, offset = 0) {
14+
// Note: Be careful editing this code! It's been tuned for performance
15+
// and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434
16+
const uuid = (
17+
byteToHex[arr[offset + 0]] +
18+
byteToHex[arr[offset + 1]] +
19+
byteToHex[arr[offset + 2]] +
20+
byteToHex[arr[offset + 3]] +
21+
'-' +
22+
byteToHex[arr[offset + 4]] +
23+
byteToHex[arr[offset + 5]] +
24+
'-' +
25+
byteToHex[arr[offset + 6]] +
26+
byteToHex[arr[offset + 7]] +
27+
'-' +
28+
byteToHex[arr[offset + 8]] +
29+
byteToHex[arr[offset + 9]] +
30+
'-' +
31+
byteToHex[arr[offset + 10]] +
32+
byteToHex[arr[offset + 11]] +
33+
byteToHex[arr[offset + 12]] +
34+
byteToHex[arr[offset + 13]] +
35+
byteToHex[arr[offset + 14]] +
36+
byteToHex[arr[offset + 15]]
37+
).toLowerCase();
38+
39+
// Consistency check for valid UUID. If this throws, it's likely due to one
40+
// of the following:
41+
// - One or more input array values don't map to a hex octet (leading to
42+
// "undefined" in the uuid)
43+
// - Invalid input values for the RFC `version` or `variant` fields
44+
if (!validate(uuid)) {
45+
throw TypeError('Stringified UUID is invalid');
46+
}
47+
48+
return uuid;
49+
}
50+
51+
export default stringify;

‎src/v1.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import rng from './rng.js';
2-
import bytesToUuid from './bytesToUuid.js';
2+
import stringify from './stringify.js';
33

44
// **`v1()` - Generate time-based UUID**
55
//
@@ -109,7 +109,7 @@ function v1(options, buf, offset) {
109109
b[i + n] = node[n];
110110
}
111111

112-
return buf || bytesToUuid(b);
112+
return buf || stringify(b);
113113
}
114114

115115
export default v1;

0 commit comments

Comments
 (0)
Please sign in to comment.