490 lines
14 KiB
JavaScript
490 lines
14 KiB
JavaScript
|
var crypto = require("crypto");
|
||
|
|
||
|
// error codes
|
||
|
var invalidNamespace =
|
||
|
"options.namespace must be a string or a Buffer " +
|
||
|
"containing a valid UUID, or a UUID object";
|
||
|
|
||
|
var invalidName =
|
||
|
"options.name must be either a string or a Buffer";
|
||
|
|
||
|
var invalidMacAddress =
|
||
|
"invalid options.mac - must either not be set, the value `false`, " +
|
||
|
"a Buffer of length 6, or a MAC address as a string";
|
||
|
|
||
|
var moreThan10000 =
|
||
|
"can not generate more than 10000 UUIDs per second";
|
||
|
|
||
|
// Node ID according to rfc4122#section-4.5
|
||
|
var randomHost = crypto.randomBytes(16);
|
||
|
randomHost[0] = randomHost[0] | 0x01;
|
||
|
|
||
|
// randomize clockSeq initially, as per rfc4122#section-4.1.5
|
||
|
var seed = crypto.randomBytes(2);
|
||
|
var clockSeq = (seed[0] | (seed[1] << 8)) & 0x3fff;
|
||
|
|
||
|
// clock values
|
||
|
var lastMTime = 0;
|
||
|
var lastNTime = 0;
|
||
|
|
||
|
// lookup table hex to byte
|
||
|
var hex2byte = {};
|
||
|
|
||
|
// lookup table byte to hex
|
||
|
var byte2hex = [];
|
||
|
|
||
|
// populate lookup tables
|
||
|
for (var i = 0; i < 256; i++) {
|
||
|
var hex = (i + 0x100).toString(16).substr(1);
|
||
|
hex2byte[hex] = i;
|
||
|
byte2hex[i] = hex;
|
||
|
}
|
||
|
|
||
|
var newBufferFromSize;
|
||
|
var newBufferFromBuffer;
|
||
|
if (Buffer.allocUnsafe) {
|
||
|
// both `Buffer.allocUnsafe` and `Buffer.from` are added in
|
||
|
// Node.js v5.10.0
|
||
|
/* istanbul ignore next */
|
||
|
newBufferFromSize = function newBufferFromSize(size) {
|
||
|
return Buffer.allocUnsafe(size);
|
||
|
};
|
||
|
/* istanbul ignore next */
|
||
|
newBufferFromBuffer = function newBufferFromBuffer(buf) {
|
||
|
return Buffer.from(buf);
|
||
|
};
|
||
|
} else {
|
||
|
/* istanbul ignore next */
|
||
|
newBufferFromSize = function(size) {
|
||
|
return new Buffer(size);
|
||
|
};
|
||
|
/* istanbul ignore next */
|
||
|
newBufferFromBuffer = function(buf) {
|
||
|
return new Buffer(buf);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function parseMacAddress(address) {
|
||
|
var buffer = newBufferFromSize(6);
|
||
|
buffer[0] = hex2byte[address[0] + address[1]];
|
||
|
buffer[1] = hex2byte[address[3] + address[4]];
|
||
|
buffer[2] = hex2byte[address[6] + address[7]];
|
||
|
buffer[3] = hex2byte[address[9] + address[10]];
|
||
|
buffer[4] = hex2byte[address[12] + address[13]];
|
||
|
buffer[5] = hex2byte[address[15] + address[16]];
|
||
|
return buffer;
|
||
|
}
|
||
|
|
||
|
// MAC address for v1 uuids
|
||
|
var macAddress = randomHost;
|
||
|
var macAddressLoaded = false;
|
||
|
|
||
|
function loadMacAddress() {
|
||
|
require("macaddress").one(function (err, result) {
|
||
|
if (!err) {
|
||
|
macAddress = parseMacAddress(result);
|
||
|
}
|
||
|
macAddressLoaded = true;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// UUID class
|
||
|
var UUID = function (uuid) {
|
||
|
|
||
|
var check = UUID.check(uuid);
|
||
|
if (!check) {
|
||
|
throw "not a UUID";
|
||
|
}
|
||
|
|
||
|
this.version = check.version;
|
||
|
this.variant = check.variant;
|
||
|
|
||
|
this[check.format] = uuid;
|
||
|
};
|
||
|
|
||
|
UUID.prototype.toString = function () {
|
||
|
if (!this.ascii) {
|
||
|
this.ascii = UUID.stringify(this.binary);
|
||
|
}
|
||
|
return this.ascii;
|
||
|
};
|
||
|
|
||
|
UUID.prototype.toBuffer = function () {
|
||
|
if (!this.binary) {
|
||
|
this.binary = UUID.parse(this.ascii);
|
||
|
}
|
||
|
return newBufferFromBuffer(this.binary);
|
||
|
};
|
||
|
|
||
|
UUID.prototype.inspect = function () {
|
||
|
return "UUID v" + this.version + " " + this.toString();
|
||
|
};
|
||
|
|
||
|
function error(message, callback) {
|
||
|
if (callback) {
|
||
|
callback(message, null);
|
||
|
} else {
|
||
|
throw new Error(message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// read stringified uuid into a Buffer
|
||
|
function parse(string) {
|
||
|
|
||
|
var buffer = newBufferFromSize(16);
|
||
|
var j = 0;
|
||
|
for (var i = 0; i < 16; i++) {
|
||
|
buffer[i] = hex2byte[string[j++] + string[j++]];
|
||
|
if (i === 3 || i === 5 || i === 7 || i === 9) {
|
||
|
j += 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return buffer;
|
||
|
}
|
||
|
|
||
|
// according to rfc4122#section-4.1.1
|
||
|
function getVariant(bits) {
|
||
|
switch (bits) {
|
||
|
case 0: case 1: case 3:
|
||
|
return "ncs";
|
||
|
case 4: case 5:
|
||
|
return "rfc4122";
|
||
|
case 6:
|
||
|
return "microsoft";
|
||
|
default:
|
||
|
return "future";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function check(uuid, offset) {
|
||
|
|
||
|
if (typeof uuid === "string") {
|
||
|
uuid = uuid.toLowerCase();
|
||
|
|
||
|
if (!/^[a-f0-9]{8}(\-[a-f0-9]{4}){3}\-([a-f0-9]{12})$/.test(uuid)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (uuid === "00000000-0000-0000-0000-000000000000") {
|
||
|
return { version: undefined, variant: "nil", format: "ascii" };
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
version: (hex2byte[uuid[14] + uuid[15]] & 0xf0) >> 4,
|
||
|
variant: getVariant((hex2byte[uuid[19] + uuid[20]] & 0xe0) >> 5),
|
||
|
format: "ascii"
|
||
|
};
|
||
|
}
|
||
|
|
||
|
if (uuid instanceof Buffer) {
|
||
|
offset = offset || 0;
|
||
|
|
||
|
if (uuid.length < offset + 16) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
for (var i = 0; i < 16; i++) {
|
||
|
if (uuid[offset + i] !== 0) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (i === 16) {
|
||
|
return { version: undefined, variant: "nil", format: "binary" };
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
version: (uuid[offset + 6] & 0xf0) >> 4,
|
||
|
variant: getVariant((uuid[offset + 8] & 0xe0) >> 5),
|
||
|
format: "binary"
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// v1
|
||
|
function uuidTimeBased(nodeId, options, callback) {
|
||
|
|
||
|
var mTime = Date.now();
|
||
|
var nTime = lastNTime + 1;
|
||
|
var delta = (mTime - lastMTime) + (nTime - lastNTime) / 10000;
|
||
|
|
||
|
if (delta < 0) {
|
||
|
clockSeq = (clockSeq + 1) & 0x3fff;
|
||
|
nTime = 0;
|
||
|
} else if (mTime > lastMTime) {
|
||
|
nTime = 0;
|
||
|
} else if (nTime >= 10000) {
|
||
|
return moreThan10000;
|
||
|
}
|
||
|
|
||
|
lastMTime = mTime;
|
||
|
lastNTime = nTime;
|
||
|
|
||
|
// unix timestamp to gregorian epoch as per rfc4122#section-4.5
|
||
|
mTime += 12219292800000;
|
||
|
|
||
|
var buffer = newBufferFromSize(16);
|
||
|
var myClockSeq = options.clockSeq === undefined ?
|
||
|
clockSeq : (options.clockSeq & 0x3fff);
|
||
|
var timeLow = ((mTime & 0xfffffff) * 10000 + nTime) % 0x100000000;
|
||
|
var timeHigh = (mTime / 0x100000000 * 10000) & 0xfffffff;
|
||
|
|
||
|
buffer[0] = timeLow >>> 24 & 0xff;
|
||
|
buffer[1] = timeLow >>> 16 & 0xff;
|
||
|
buffer[2] = timeLow >>> 8 & 0xff;
|
||
|
buffer[3] = timeLow & 0xff;
|
||
|
|
||
|
buffer[4] = timeHigh >>> 8 & 0xff;
|
||
|
buffer[5] = timeHigh & 0xff;
|
||
|
|
||
|
buffer[6] = (timeHigh >>> 24 & 0x0f) | 0x10;
|
||
|
buffer[7] = (timeHigh >>> 16 & 0x3f) | 0x80;
|
||
|
|
||
|
buffer[8] = myClockSeq >>> 8;
|
||
|
buffer[9] = myClockSeq & 0xff;
|
||
|
|
||
|
var result;
|
||
|
switch (options.encoding && options.encoding[0]) {
|
||
|
case "b":
|
||
|
case "B":
|
||
|
buffer[10] = nodeId[0];
|
||
|
buffer[11] = nodeId[1];
|
||
|
buffer[12] = nodeId[2];
|
||
|
buffer[13] = nodeId[3];
|
||
|
buffer[14] = nodeId[4];
|
||
|
buffer[15] = nodeId[5];
|
||
|
result = buffer;
|
||
|
break;
|
||
|
case "o":
|
||
|
case "U":
|
||
|
buffer[10] = nodeId[0];
|
||
|
buffer[11] = nodeId[1];
|
||
|
buffer[12] = nodeId[2];
|
||
|
buffer[13] = nodeId[3];
|
||
|
buffer[14] = nodeId[4];
|
||
|
buffer[15] = nodeId[5];
|
||
|
result = new UUID(buffer);
|
||
|
break;
|
||
|
default:
|
||
|
result = byte2hex[buffer[0]] + byte2hex[buffer[1]] +
|
||
|
byte2hex[buffer[2]] + byte2hex[buffer[3]] + "-" +
|
||
|
byte2hex[buffer[4]] + byte2hex[buffer[5]] + "-" +
|
||
|
byte2hex[buffer[6]] + byte2hex[buffer[7]] + "-" +
|
||
|
byte2hex[buffer[8]] + byte2hex[buffer[9]] + "-" +
|
||
|
byte2hex[nodeId[0]] + byte2hex[nodeId[1]] +
|
||
|
byte2hex[nodeId[2]] + byte2hex[nodeId[3]] +
|
||
|
byte2hex[nodeId[4]] + byte2hex[nodeId[5]];
|
||
|
break;
|
||
|
}
|
||
|
if (callback) {
|
||
|
setImmediate(function () {
|
||
|
callback(null, result);
|
||
|
});
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
// v3 + v5
|
||
|
function uuidNamed(hashFunc, version, arg1, arg2) {
|
||
|
|
||
|
var options = arg1 || {};
|
||
|
var callback = typeof arg1 === "function" ? arg1 : arg2;
|
||
|
|
||
|
var namespace = options.namespace;
|
||
|
var name = options.name;
|
||
|
|
||
|
var hash = crypto.createHash(hashFunc);
|
||
|
|
||
|
if (typeof namespace === "string") {
|
||
|
if (!check(namespace)) {
|
||
|
return error(invalidNamespace, callback);
|
||
|
}
|
||
|
namespace = parse(namespace);
|
||
|
} else if (namespace instanceof UUID) {
|
||
|
namespace = namespace.toBuffer();
|
||
|
} else if (!(namespace instanceof Buffer) || namespace.length !== 16) {
|
||
|
return error(invalidNamespace, callback);
|
||
|
}
|
||
|
|
||
|
var nameIsNotAString = typeof name !== "string";
|
||
|
if (nameIsNotAString && !(name instanceof Buffer)) {
|
||
|
return error(invalidName, callback);
|
||
|
}
|
||
|
|
||
|
hash.update(namespace);
|
||
|
hash.update(options.name, nameIsNotAString ? "binary" : "utf8");
|
||
|
|
||
|
var buffer = hash.digest();
|
||
|
|
||
|
var result;
|
||
|
switch (options.encoding && options.encoding[0]) {
|
||
|
case "b":
|
||
|
case "B":
|
||
|
buffer[6] = (buffer[6] & 0x0f) | version;
|
||
|
buffer[8] = (buffer[8] & 0x3f) | 0x80;
|
||
|
result = buffer;
|
||
|
break;
|
||
|
case "o":
|
||
|
case "U":
|
||
|
buffer[6] = (buffer[6] & 0x0f) | version;
|
||
|
buffer[8] = (buffer[8] & 0x3f) | 0x80;
|
||
|
result = new UUID(buffer);
|
||
|
break;
|
||
|
default:
|
||
|
result = byte2hex[buffer[0]] + byte2hex[buffer[1]] +
|
||
|
byte2hex[buffer[2]] + byte2hex[buffer[3]] + "-" +
|
||
|
byte2hex[buffer[4]] + byte2hex[buffer[5]] + "-" +
|
||
|
byte2hex[(buffer[6] & 0x0f) | version] +
|
||
|
byte2hex[buffer[7]] + "-" +
|
||
|
byte2hex[(buffer[8] & 0x3f) | 0x80] +
|
||
|
byte2hex[buffer[9]] + "-" +
|
||
|
byte2hex[buffer[10]] + byte2hex[buffer[11]] +
|
||
|
byte2hex[buffer[12]] + byte2hex[buffer[13]] +
|
||
|
byte2hex[buffer[14]] + byte2hex[buffer[15]];
|
||
|
break;
|
||
|
}
|
||
|
if (callback) {
|
||
|
setImmediate(function () {
|
||
|
callback(null, result);
|
||
|
});
|
||
|
} else {
|
||
|
return result;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// v4
|
||
|
function uuidRandom(arg1, arg2) {
|
||
|
|
||
|
var options = arg1 || {};
|
||
|
var callback = typeof arg1 === "function" ? arg1 : arg2;
|
||
|
|
||
|
var buffer = crypto.randomBytes(16);
|
||
|
|
||
|
buffer[6] = (buffer[6] & 0x0f) | 0x40;
|
||
|
buffer[8] = (buffer[8] & 0x3f) | 0x80;
|
||
|
|
||
|
var result;
|
||
|
switch (options.encoding && options.encoding[0]) {
|
||
|
case "b":
|
||
|
case "B":
|
||
|
result = buffer;
|
||
|
break;
|
||
|
case "o":
|
||
|
case "U":
|
||
|
result = new UUID(buffer);
|
||
|
break;
|
||
|
default:
|
||
|
result = byte2hex[buffer[0]] + byte2hex[buffer[1]] +
|
||
|
byte2hex[buffer[2]] + byte2hex[buffer[3]] + "-" +
|
||
|
byte2hex[buffer[4]] + byte2hex[buffer[5]] + "-" +
|
||
|
byte2hex[(buffer[6] & 0x0f) | 0x40] +
|
||
|
byte2hex[buffer[7]] + "-" +
|
||
|
byte2hex[(buffer[8] & 0x3f) | 0x80] +
|
||
|
byte2hex[buffer[9]] + "-" +
|
||
|
byte2hex[buffer[10]] + byte2hex[buffer[11]] +
|
||
|
byte2hex[buffer[12]] + byte2hex[buffer[13]] +
|
||
|
byte2hex[buffer[14]] + byte2hex[buffer[15]];
|
||
|
break;
|
||
|
}
|
||
|
if (callback) {
|
||
|
setImmediate(function () {
|
||
|
callback(null, result);
|
||
|
});
|
||
|
} else {
|
||
|
return result;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// v4 fast
|
||
|
function uuidRandomFast() {
|
||
|
|
||
|
var r1 = Math.random() * 0x100000000;
|
||
|
var r2 = Math.random() * 0x100000000;
|
||
|
var r3 = Math.random() * 0x100000000;
|
||
|
var r4 = Math.random() * 0x100000000;
|
||
|
|
||
|
return byte2hex[ r1 & 0xff] +
|
||
|
byte2hex[ r1 >>> 8 & 0xff] +
|
||
|
byte2hex[ r1 >>> 16 & 0xff] +
|
||
|
byte2hex[ r1 >>> 24 & 0xff] + "-" +
|
||
|
byte2hex[ r2 & 0xff] +
|
||
|
byte2hex[ r2 >>> 8 & 0xff] + "-" +
|
||
|
byte2hex[(r2 >>> 16 & 0x0f) | 0x40] +
|
||
|
byte2hex[ r2 >>> 24 & 0xff] + "-" +
|
||
|
byte2hex[(r3 & 0x3f) | 0x80] +
|
||
|
byte2hex[ r3 >>> 8 & 0xff] + "-" +
|
||
|
byte2hex[ r3 >>> 16 & 0xff] +
|
||
|
byte2hex[ r3 >>> 24 & 0xff] +
|
||
|
byte2hex[ r4 & 0xff] +
|
||
|
byte2hex[ r4 >>> 8 & 0xff] +
|
||
|
byte2hex[ r4 >>> 16 & 0xff] +
|
||
|
byte2hex[ r4 >>> 24 & 0xff];
|
||
|
}
|
||
|
|
||
|
function stringify(buffer) {
|
||
|
return byte2hex[buffer[0]] + byte2hex[buffer[1]] +
|
||
|
byte2hex[buffer[2]] + byte2hex[buffer[3]] + "-" +
|
||
|
byte2hex[buffer[4]] + byte2hex[buffer[5]] + "-" +
|
||
|
byte2hex[buffer[6]] + byte2hex[buffer[7]] + "-" +
|
||
|
byte2hex[buffer[8]] + byte2hex[buffer[9]] + "-" +
|
||
|
byte2hex[buffer[10]] + byte2hex[buffer[11]] +
|
||
|
byte2hex[buffer[12]] + byte2hex[buffer[13]] +
|
||
|
byte2hex[buffer[14]] + byte2hex[buffer[15]];
|
||
|
}
|
||
|
|
||
|
UUID.stringify = stringify;
|
||
|
|
||
|
UUID.parse = parse;
|
||
|
|
||
|
UUID.check = check;
|
||
|
|
||
|
// according to rfc4122#section-4.1.7
|
||
|
UUID.nil = new UUID("00000000-0000-0000-0000-000000000000");
|
||
|
|
||
|
// from rfc4122#appendix-C
|
||
|
UUID.namespace = {
|
||
|
dns: new UUID("6ba7b810-9dad-11d1-80b4-00c04fd430c8"),
|
||
|
url: new UUID("6ba7b811-9dad-11d1-80b4-00c04fd430c8"),
|
||
|
oid: new UUID("6ba7b812-9dad-11d1-80b4-00c04fd430c8"),
|
||
|
x500: new UUID("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
|
||
|
};
|
||
|
|
||
|
UUID.v1 = function v1(arg1, arg2) {
|
||
|
|
||
|
var options = arg1 || {};
|
||
|
var callback = typeof arg1 === "function" ? arg1 : arg2;
|
||
|
|
||
|
var nodeId = options.mac;
|
||
|
|
||
|
if (nodeId === undefined) {
|
||
|
if(!macAddressLoaded) {
|
||
|
loadMacAddress();
|
||
|
}
|
||
|
if (!macAddressLoaded && callback) {
|
||
|
setImmediate(function () {
|
||
|
UUID.v1(options, callback);
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
return uuidTimeBased(macAddress, options, callback);
|
||
|
}
|
||
|
if (nodeId === false) {
|
||
|
return uuidTimeBased(randomHost, options, callback);
|
||
|
}
|
||
|
return uuidTimeBased(parseMacAddress(nodeId), options, callback);
|
||
|
};
|
||
|
|
||
|
UUID.v4 = uuidRandom;
|
||
|
|
||
|
UUID.v4fast = uuidRandomFast;
|
||
|
|
||
|
UUID.v3 = function (options, callback) {
|
||
|
return uuidNamed("md5", 0x30, options, callback);
|
||
|
};
|
||
|
|
||
|
UUID.v5 = function (options, callback) {
|
||
|
return uuidNamed("sha1", 0x50, options, callback);
|
||
|
};
|
||
|
|
||
|
module.exports = UUID;
|