Skip to content

Commit

Permalink
Remove some of the (mostly unnecessary) perf optimizations that just …
Browse files Browse the repository at this point in the history
…make the code look strange. General cleanup and documentation.
  • Loading branch information
broofa committed Nov 18, 2011
1 parent 67db473 commit 774c6e3
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 132 deletions.
2 changes: 1 addition & 1 deletion test/test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
if (typeof(uuid) == 'undefined') {
if (!this.uuid && this.require) {
uuid = require('../uuid');
}

Expand Down
279 changes: 148 additions & 131 deletions uuid.js
Original file line number Diff line number Diff line change
@@ -1,183 +1,205 @@
/*
* Generate RFC4122 (v1 and v4) UUIDs
*
* Documentation at https://github.com/broofa/node-uuid
*/
(function() {
/*
* Generate RFC4122 (v1 and v4) UUIDs
*
* Documentation at https://github.com/broofa/node-uuid
*/

// 12219292800000 is the number of milliseconds between UUID epoch
// 1582-10-15 00:00:00 and UNIX epoch 1970-01-01 00:00:00.
var EPOCH_OFFSET = 12219292800000;

// Number of 100ns ticks of the actual resolution of the system's clock
var UUIDS_PER_TICK = 10000;
// WHATWG crypto api support.
// http://wiki.whatwg.org/wiki/Crypto
//
// (Create a static _rnds array here as well, which lets us avoid per-uuid
// array creation)
var crypto, _rnds;
if (crypto && crypto.getRandomValues) {
_rnds = new Uint32Array(4);
} else {
_rnds = new Array(4);
// Math.random does not have the cryptographic-quality guarantee for
// randomness that crypto.getRandomValues has, but it's the best we have.
crypto = {
getRandomValues: function(arr) {
for (var i = 0, l = arr.length; i < l; i++) {
_rnds[i] = Math.random() * 0x100000000;
}
}
}
}

// Use node.js Buffer class if available, otherwise use the Array class
var BufferClass = typeof(Buffer) == 'function' ? Buffer : Array;

// Buffer used for generating string uuids
var _buf = new BufferClass(16);

// Cache number <-> hex string for octet values
var toString = [];
var toNumber = {};
// Maps for number <-> hex string conversion
var _octetToHex = [];
var _hexToOctet = {};
for (var i = 0; i < 256; i++) {
toString[i] = (i + 0x100).toString(16).substr(1);
toNumber[toString[i]] = i;
_octetToHex[i] = (i + 0x100).toString(16).substr(1);
_hexToOctet[_octetToHex[i]] = i;
}

function parse(s) {
var buf = new BufferClass(16);
var i = 0;
s.toLowerCase().replace(/[0-9a-f][0-9a-f]/g, function(octet) {
buf[i++] = toNumber[octet];
/**
* Parse a uuid string into it's component octets.
*
* This is a loose parser. It parses the first 16 octet pairs it as hex
* values. If fewer than 16 are found, any remaining entries in the array
* are set to zero.
*/
function parse(s, buf, offset) {
var buf = buf || new BufferClass(16), i = offset || 0, ii = 0;
s.toLowerCase().replace(/[0-9a-f]{2}/g, function(octet) {
if (ii < 16) { // Don't overflow!
buf[i + ii++] = _hexToOctet[octet];
}
});

// Zero out remaining octets if string was short
while (ii < 16) {
buf[i+ii] = 0;
}

return buf;
}

function unparse(buf) {
var tos = toString, b = buf;
return tos[b[0]] + tos[b[1]] + tos[b[2]] + tos[b[3]] + '-' +
tos[b[4]] + tos[b[5]] + '-' +
tos[b[6]] + tos[b[7]] + '-' +
tos[b[8]] + tos[b[9]] + '-' +
tos[b[10]] + tos[b[11]] + tos[b[12]] +
tos[b[13]] + tos[b[14]] + tos[b[15]];
/**
* Generate a uuid string from octet array
*/
function unparse(buf, offset) {
var oth = _octetToHex, b = buf, i = offset || 0;
return oth[b[i + 0]] + oth[b[i + 1]] +
oth[b[i + 2]] + oth[b[i + 3]] + '-' +
oth[b[i + 4]] + oth[b[i + 5]] + '-' +
oth[b[i + 6]] + oth[b[i + 7]] + '-' +
oth[b[i + 8]] + oth[b[i + 9]] + '-' +
oth[b[i + 10]] + oth[b[i + 11]] +
oth[b[i + 12]] + oth[b[i + 13]] +
oth[b[i + 14]] + oth[b[i + 15]];
}

var ff = 0xff;
/**
* Create and return a 47-bit random node id
*/
function _randomNodeId() {
crypto.getRandomValues(_rnds);

return [
_rnds[0] & 0xff | 0x01, // Set multicast bit, per 4.1.6 and 4.5
_rnds[0] >>> 8 & 0xff,
_rnds[0] >>> 16 & 0xff,
_rnds[0] >>> 24 & 0xff,
_rnds[1] & 0xff,
_rnds[1] >>> 8 & 0xff
];
}

// Feature detect for the WHATWG crypto API. See
// http://wiki.whatwg.org/wiki/Crypto
var useCrypto = this.crypto && crypto.getRandomValues;
var rnds = useCrypto ? new Uint32Array(4) : new Array(4);
// Pre-allocate arrays to avoid per-uuid array creation
var _buf = new BufferClass(16);

if (useCrypto) {
crypto.getRandomValues(rnds);
} else {
rnds[0] = Math.random() * 0x100000000;
rnds[1] = Math.random() * 0x100000000;
rnds[2] = Math.random() * 0x100000000;
}
//
// v1 UUID support
//
// Inspired by https://github.com/LiosK/UUID.js
// and http://docs.python.org/library/uuid.html
//

// Per 4.1.4 - Offset (in msecs) from JS time to UUID (gregorian) time
var EPOCH_OFFSET = 12219292800000;

// Per 4.1.4 - UUID time has 100ns resolution
// Per 4.2.1.2 - Count of uuids may be used low resolution clocks
var UUIDS_PER_TICK = 10000;

// Generate a node value for this instance. Use a randomly generated node
// instead of a mac address. RFC suggests generating a 47 bit random integer,
// but we're limited to 32 bit in js, so we just use two 32 bit.
var node = [
rnds[0] & ff | 0x01, // Set multicast bit, see 4.1.6 and 4.5
rnds[0] >>> 8 & ff,
rnds[0] >>> 16 & ff,
rnds[0] >>> 24 & ff,
rnds[1] & ff,
rnds[1] >>> 8 & ff
];

// Use 14 bit random unsigned integer to initialize clock_seq, see 4.2.2.
var cs = rnds[2] & 0x3fff; // Cut down 32 bit random integer to 14 bit

// Used to track time-regressions for updating the clock_seq
// Per 4.5, use a random node id
var nodeId = _randomNodeId();

// Per 4.2.2, use 14 bit random unsigned integer to initialize clock_seq
var cs = _rnds[2] & 0x3fff;

// Time of previous uuid creation
var last = 0;

// Number of UUIDs that have been created during the current millisecond-
// interval. Used to simulate higher clock resolution as suggested in 4.2.1.2.
// # of UUIDs that have been created during current time tick
var count = 0;

// Inspired by https://github.com/LiosK/UUID.js
// and http://docs.python.org/library/uuid.html
// Documentation at https://github.com/broofa/node-uuid
function v1(fmt, buf, offset) {
var b = fmt != 'binary' ? _buf : (buf ? buf : new BufferClass(16));
var i = buf && offset || 0;

// Get current time (uuid epoch)
// Per 4.2.1.2, use uuid count to simulate higher resolution clock
// Get current time and simulate higher clock resolution
var now = (new Date().getTime()) + EPOCH_OFFSET;
count = (now === last) ? count + 1 : 0;

// Per 4.2.1.2, if time regresses we bump the clock sequence.
// (Or if we're generating more than 10k uuids/sec - an extremely unlikely
// case the RFC doesn't address)
if (now < last || count > UUIDS_PER_TICK) {
// Per 4.2.1.2 If generator overruns, throw an error
// (*Highly* unlikely - requires generating > 10M uuids/sec)
if (count > UUIDS_PER_TICK) {
throw new Error('uuid.v1(): Can\'t create more than 10M uuids/sec');
}

// Per 4.2.1.2, if time regresses bump the clock sequence.
if (now < last) {
cs++;
count = 0;
}
last = now;

// Timestamp, see 4.1.4
var timestamp = now;
var tl = ((timestamp & 0xfffffff) * 10000 + count) % 0x100000000;
var tmh = ((timestamp / 0x100000000) * 10000) & 0xfffffff;
var tm = tmh & 0xffff;
var th = tmh >> 16;
var thav = (th & 0xfff) | 0x1000; // Set version, see 4.1.3
last = now;

// Clock sequence
var csl = cs & 0xff;
var cshar = (cs >>> 8) | 0x80; // Set the variant, see 4.2.2
// Per 4.1.4, timestamp composition
var tl = ((now & 0xfffffff) * 10000 + count) % 0x100000000;
var tmh = (((now + count/10000) / 0x100000000) * 10000) & 0xfffffff;
var tm = tmh & 0xffff, th = tmh >> 16;
var thav = (th & 0xfff) | 0x1000; // Set version, per 4.1.3

// time_low
b[i++] = tl >>> 24 & ff;
b[i++] = tl >>> 16 & ff;
b[i++] = tl >>> 8 & ff;
b[i++] = tl & ff;
b[i++] = tl >>> 24 & 0xff;
b[i++] = tl >>> 16 & 0xff;
b[i++] = tl >>> 8 & 0xff;
b[i++] = tl & 0xff;

// time_mid
b[i++] = tm >>> 8 & ff;
b[i++] = tm & ff;
b[i++] = tm >>> 8 & 0xff;
b[i++] = tm & 0xff;

// time_high_and_version
b[i++] = thav >>> 8 & ff;
b[i++] = thav & ff;
b[i++] = thav >>> 8 & 0xff;
b[i++] = thav & 0xff;

// clock_seq_hi_and_reserved
b[i++] = cshar;
// clock_seq_hi_and_reserved (include variant, per 4.2.2)
b[i++] = (cs >>> 8) | 0x80;

// clock_seq_low
b[i++] = csl;
b[i++] = cs & 0xff;

// node
var n = 0;
b[i++] = node[n++];
b[i++] = node[n++];
b[i++] = node[n++];
b[i++] = node[n++];
b[i++] = node[n++];
b[i++] = node[n++];
b[i++] = nodeId[n++];
b[i++] = nodeId[n++];
b[i++] = nodeId[n++];
b[i++] = nodeId[n++];
b[i++] = nodeId[n++];
b[i++] = nodeId[n++];

return fmt === undefined ? unparse(b) : b;
}

//
// v4 UUID support
//

function v4(fmt, buf, offset) {
var b = fmt != 'binary' ? _buf : (buf ? buf : new BufferClass(16));
var i = buf && offset || 0;

if (useCrypto) {
crypto.getRandomValues(rnds);
} else {
rnds[0] = Math.random() * 0x100000000;
rnds[1] = Math.random() * 0x100000000;
rnds[2] = Math.random() * 0x100000000;
rnds[3] = Math.random() * 0x100000000;
crypto.getRandomValues(_rnds);

// v4 ideas are all random bits
for (var c = 0 ; c < 16; c++) {
var ri = c >> 2, rb = (c & 0x03) * 8;
b[i + c] = _rnds[ri] >>> rb & 0xff;
}

var r = rnds[0];
b[i++] = r & ff;
b[i++] = r >>> 8 & ff;
b[i++] = r >>> 16 & ff;
b[i++] = r >>> 24 & ff;
r = rnds[1];
b[i++] = r & ff;
b[i++] = r >>> 8 & ff;
b[i++] = r >>> 16 & 0x0f | 0x40; // See RFC4122 sect. 4.1.3
b[i++] = r >>> 24 & ff;
r = rnds[2];
b[i++] = r & 0x3f | 0x80; // See RFC4122 sect. 4.4
b[i++] = r >>> 8 & ff;
b[i++] = r >>> 16 & ff;
b[i++] = r >>> 24 & ff;
r = rnds[3];
b[i++] = r & ff;
b[i++] = r >>> 8 & ff;
b[i++] = r >>> 16 & ff;
b[i++] = r >>> 24 & ff;
// ... except for this
b[i + 6] = (b[i + 6] & 0x0f) | 0x40; // Per RFC4122 sect. 4.1.3
b[i + 8] = (b[i + 8] & 0x3f) | 0x80; // Per RFC4122 sect. 4.4

return fmt === undefined ? unparse(b) : b;
}
Expand All @@ -189,10 +211,5 @@
uuid.unparse = unparse;
uuid.BufferClass = BufferClass;

if (typeof(module) != 'undefined') {
module.exports = uuid;
} else {
// In browser? Set as top-level function
this.uuid = uuid;
}
return this.module ? this.module = uuid : this.uuid = uuid;
}());

0 comments on commit 774c6e3

Please sign in to comment.