/*
* Copyright (c) 2012, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import com.twelvemonkeys.lang.StringUtil;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.*;
/**
* A factory for creating {@code UUID}s not directly supported by {@link java.util.UUID java.util.UUID}.
* <p>
* This class can create version 1 time based {@code UUID}s, using either IEEE 802 (mac) address or random "node" value
* as well as version 5 SHA1 hash based {@code UUID}s.
* </p>
* <p>
* The timestamp value for version 1 {@code UUID}s will use a high precision clock, when available to the Java VM.
* If the Java system clock does not offer the needed precision, the timestamps will fall back to 100-nanosecond
* increments, to avoid duplicates.
* </p>
* <p>
* <a name="mac-node"></a>
* The node value for version 1 {@code UUID}s will, by default, reflect the IEEE 802 (mac) address of one of
* the network interfaces of the local computer. This node value can be manually overridden by setting
* the system property {@code "com.twelvemonkeys.util.UUID.node"} to a valid IEEE 802 address, on the form
* {@code "12:34:56:78:9a:bc"} or {@code "12-34-45-78-9a-bc"}.
* </p>
* <p>
* <a name="random-node"></a>
* The node value for the random "node" based version 1 {@code UUID}s will be stable for the lifetime of the VM.
* </p>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: UUIDFactory.java,v 1.0 27.02.12 09:45 haraldk Exp$
*
* @see <a href="http://tools.ietf.org/html/rfc4122">RFC 4122</a>
* @see <a href="http://en.wikipedia.org/wiki/Universally_unique_identifier">Wikipedia</a>
* @see java.util.UUID
*/
public final class UUIDFactory {
private static final String NODE_PROPERTY = "com.twelvemonkeys.util.UUID.node";
/**
* The Nil UUID: {@code "00000000-0000-0000-0000-000000000000"}.
*
* The nil UUID is special form of UUID that is specified to have all
* 128 bits set to zero. Not particularly useful, unless as a placeholder or template.
*
* @see <a href="http://tools.ietf.org/html/rfc4122#section-4.1.7">RFC 4122 4.1.7. Nil UUID</a>
*/
public static final UUID NIL = new UUID(0l, 0l);
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
private static final Comparator<UUID> COMPARATOR = new UUIDComparator();
// Assumes MAC address is constant, which it may not be if a network card is replaced
static final long MAC_ADDRESS_NODE = getMacAddressNode();
static final long SECURE_RANDOM_NODE = getSecureRandomNode();
private static long getSecureRandomNode() {
// Creates a completely random "node" value, with the unicast bit set to 1, as outlined in RFC 4122.
return 1l << 40 | SECURE_RANDOM.nextLong() & 0xffffffffffffl;
}
private static long getMacAddressNode() {
long[] addressNodes;
String nodeProperty = System.getProperty(NODE_PROPERTY);
// Read mac address/node from system property, to allow user-specified node addresses.
if (!StringUtil.isEmpty(nodeProperty)) {
addressNodes = parseMacAddressNodes(nodeProperty);
}
else {
addressNodes = MacAddressFinder.getMacAddressNodes();
}
// TODO: The UUID spec allows us to use multiple nodes, when available, to create more UUIDs per time unit...
// For example in a round robin fashion?
return addressNodes != null && addressNodes.length > 0 ? addressNodes[0] : -1;
}
static long[] parseMacAddressNodes(final String nodeProperty) {
// Parse comma-separated list mac addresses on format 00:11:22:33:44:55 / 00-11-22-33-44-55
String[] nodesStrings = nodeProperty.trim().split(",\\W*");
long[] addressNodes = new long[nodesStrings.length];
for (int i = 0, nodesStringsLength = nodesStrings.length; i < nodesStringsLength; i++) {
String nodesString = nodesStrings[i];
try {
String[] nodes = nodesString.split("(?<=(^|\\W)[0-9a-fA-F]{2})\\W(?=[0-9a-fA-F]{2}(\\W|$))", 6);
long nodeAddress = 0;
// Network byte order
nodeAddress |= (long) (Integer.parseInt(nodes[0], 16) & 0xff) << 40;
nodeAddress |= (long) (Integer.parseInt(nodes[1], 16) & 0xff) << 32;
nodeAddress |= (long) (Integer.parseInt(nodes[2], 16) & 0xff) << 24;
nodeAddress |= (long) (Integer.parseInt(nodes[3], 16) & 0xff) << 16;
nodeAddress |= (long) (Integer.parseInt(nodes[4], 16) & 0xff) << 8;
nodeAddress |= (long) (Integer.parseInt(nodes[5], 16) & 0xff);
addressNodes[i] = nodeAddress;
}
catch (RuntimeException e) {
// May be NumberformatException from parseInt or ArrayIndexOutOfBounds from nodes array
NumberFormatException formatException = new NumberFormatException(String.format("Bad IEEE 802 node address: '%s' (from system property %s)", nodesString, NODE_PROPERTY));
formatException.initCause(e);
throw formatException;
}
}
return addressNodes;
}
private UUIDFactory() {}
/**
* Creates a type 5 (name based) {@code UUID} based on the specified byte array.
* This method is effectively identical to {@link UUID#nameUUIDFromBytes}, except that this method
* uses a SHA1 hash instead of the MD5 hash used in the type 3 {@code UUID}s.
* RFC 4122 states that "SHA-1 is preferred" over MD5, without giving a reason for why.
*
* @param name a byte array to be used to construct a {@code UUID}
* @return a {@code UUID} generated from the specified array.
*
* @see <a href="http://tools.ietf.org/html/rfc4122#section-4.3">RFC 4122 4.3. Algorithm for Creating a Name-Based UUID</a>
* @see <a href="http://tools.ietf.org/html/rfc4122#appendix-A">RFC 4122 appendix A</a>
* @see UUID#nameUUIDFromBytes(byte[])
*/
public static UUID nameUUIDv5FromBytes(byte[] name) {
// Based on code from OpenJDK UUID#nameUUIDFromBytes + private byte[] constructor
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA1");
}
catch (NoSuchAlgorithmException nsae) {
throw new InternalError("SHA1 not supported");
}
byte[] sha1Bytes = md.digest(name);
sha1Bytes[6] &= 0x0f; /* clear version */
sha1Bytes[6] |= 0x50; /* set to version 5 */
sha1Bytes[8] &= 0x3f; /* clear variant */
sha1Bytes[8] |= 0x80; /* set to IETF variant */
long msb = 0;
long lsb = 0;
// NOTE: According to RFC 4122, only first 16 bytes are used, meaning
// bytes 17-20 in the 160 bit SHA1 hash are simply discarded in this case...
for (int i=0; i<8; i++) {
msb = (msb << 8) | (sha1Bytes[i] & 0xff);
}
for (int i=8; i<16; i++) {
lsb = (lsb << 8) | (sha1Bytes[i] & 0xff);
}
return new UUID(msb, lsb);
}
/**
* Creates a version 1 time (and node) based {@code UUID}.
* The node part is by default the IEE 802 (mac) address of one of the network cards of the current machine.
*
* @return a {@code UUID} based on the current time and the node address of this computer.
* @see <a href="http://tools.ietf.org/html/rfc4122#section-4.2">RFC 4122 4.2. Algorithms for Creating a Time-Based UUID</a>
* @see <a href="http://en.wikipedia.org/wiki/MAC_address">IEEE 802 (mac) address</a>
* @see <a href="#mac-node">Overriding the node address</a>
*
* @throws IllegalStateException if the IEEE 802 (mac) address of the computer (node) cannot be found.
*/
public static UUID timeNodeBasedUUID() {
if (MAC_ADDRESS_NODE == -1) {
throw new IllegalStateException("Could not determine IEEE 802 (mac) address for node");
}
return createTimeBasedUUIDforNode(MAC_ADDRESS_NODE);
}
/**
* Creates a version 1 time based {@code UUID} with the node part replaced by a random based value.
* The node part is computed using a 47 bit secure random number + lsb of first octet (unicast/multicast bit) set to 1.
* These {@code UUID}s can never clash with "real" node based version 1 {@code UUID}s due to the difference in
* the unicast/multicast bit, however, no uniqueness between multiple machines/vms/nodes can be guaranteed.
*
* @return a {@code UUID} based on the current time and a random generated "node" value.
* @see <a href="http://tools.ietf.org/html/rfc4122#section-4.5">RFC 4122 4.5. Node IDs that Do Not Identify the Host</a>
* @see <a href="http://tools.ietf.org/html/rfc4122#appendix-A">RFC 4122 Appendix A</a>
* @see <a href="#random-node">Lifetime of random node value</a>
*
* @throws IllegalStateException if the IEEE 802 (mac) address of the computer (node) cannot be found.
*/
public static UUID timeRandomBasedUUID() {
return createTimeBasedUUIDforNode(SECURE_RANDOM_NODE);
}
private static UUID createTimeBasedUUIDforNode(final long node) {
return new UUID(createTimeAndVersion(), createClockSeqAndNode(node));
}
// TODO: Version 2 UUID?
/*
Version 2 UUIDs are similar to Version 1 UUIDs, with the upper byte of the clock sequence replaced by the
identifier for a "local domain" (typically either the "POSIX UID domain" or the "POSIX GID domain")
and the first 4 bytes of the timestamp replaced by the user's POSIX UID or GID (with the "local domain"
identifier indicating which it is).[2][3]
*/
private static long createClockSeqAndNode(final long node) {
// Variant (2) + Clock seq high and low + node
return 0x8000000000000000l | (Clock.getClockSequence() << 48) | node & 0xffffffffffffl;
}
private static long createTimeAndVersion() {
long clockTime = Clock.currentTimeHundredNanos();
long time = clockTime << 32; // Time low
time |= (clockTime & 0xFFFF00000000L) >> 16; // Time mid
time |= ((clockTime >> 48) & 0x0FFF); // Time high
time |= 0x1000; // Version (1)
return time;
}
/**
* Returns a comparator that compares UUIDs as 128 bit unsigned entities, as mentioned in RFC 4122.
* This is different than {@link UUID#compareTo(Object)} that compares the UUIDs as signed entities.
*
* @return a comparator that compares UUIDs as 128 bit unsigned entities.
*
* @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7025832">java.lang.UUID compareTo() does not do an unsigned compare</a>
* @see <a href="http://tools.ietf.org/html/rfc4122#appendix-A">RFC 4122 Appendix A</a>
*/
public static Comparator<UUID> comparator() {
return COMPARATOR;
}
static final class UUIDComparator implements Comparator<UUID> {
public int compare(UUID left, UUID right) {
// The ordering is intentionally set up so that the UUIDs
// can simply be numerically compared as two *UNSIGNED* numbers
if (left.getMostSignificantBits() >>> 32 < right.getMostSignificantBits() >>> 32) {
return -1;
}
else if (left.getMostSignificantBits() >>> 32 > right.getMostSignificantBits() >>> 32) {
return 1;
}
else if ((left.getMostSignificantBits() & 0xffffffffl) < (right.getMostSignificantBits() & 0xffffffffl)) {
return -1;
}
else if ((left.getMostSignificantBits() & 0xffffffffl) > (right.getMostSignificantBits() & 0xffffffffl)) {
return 1;
}
else if (left.getLeastSignificantBits() >>> 32 < right.getLeastSignificantBits() >>> 32) {
return -1;
}
else if (left.getLeastSignificantBits() >>> 32 > right.getLeastSignificantBits() >>> 32) {
return 1;
}
else if ((left.getLeastSignificantBits() & 0xffffffffl) < (right.getLeastSignificantBits() & 0xffffffffl)) {
return -1;
}
else if ((left.getLeastSignificantBits() & 0xffffffffl) > (right.getLeastSignificantBits() & 0xffffffffl)) {
return 1;
}
return 0;
}
}
/**
* A high-resolution timer for use in creating version 1 {@code UUID}s.
*/
static final class Clock {
// Java: 0:00, Jan. 1st, 1970 vs UUID: 0:00, Oct 15th, 1582
private static final long JAVA_EPOCH_OFFSET_HUNDRED_NANOS = 122192928000000000L;
private static int clockSeq = SECURE_RANDOM.nextInt();
private static long initialNanos;
private static long initialTime;
private static long lastMeasuredTime;
private static long lastTime;
static {
initClock();
}
private static void initClock() {
long millis = System.currentTimeMillis();
long nanos = System.nanoTime();
initialTime = JAVA_EPOCH_OFFSET_HUNDRED_NANOS + millis * 10000 + (nanos / 100) % 10000;
initialNanos = nanos;
}
public static synchronized long currentTimeHundredNanos() {
// Measure delta since init and compute accurate time
long time;
while ((time = initialTime + (System.nanoTime() - initialNanos) / 100) < lastMeasuredTime) {
// Reset clock seq (should happen VERY rarely)
initClock();
clockSeq++;
}
lastMeasuredTime = time;
if (time <= lastTime) {
// This typically means the clock isn't accurate enough, use auto-incremented time.
// It is possible that more timestamps than available are requested for
// each time unit in the system clock, but that is extremely unlikely.
// TODO: RFC 4122 says we should wait in the case of too many requests...
time = ++lastTime;
}
else {
lastTime = time;
}
return time;
}
public static synchronized long getClockSequence() {
return clockSeq & 0x3fff;
}
}
/**
* Static inner class for 1.5 compatibility.
*/
static final class MacAddressFinder {
public static long[] getMacAddressNodes() {
List<Long> nodeAddresses = new ArrayList<Long>();
try {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
if (interfaces != null) {
while (interfaces.hasMoreElements()) {
NetworkInterface nic = interfaces.nextElement();
if (!nic.isVirtual()) {
long nodeAddress = 0;
byte[] hardware = nic.getHardwareAddress(); // 1.6 method
if (hardware != null && hardware.length == 6 && hardware[1] != (byte) 0xff) {
// Network byte order
nodeAddress |= (long) (hardware[0] & 0xff) << 40;
nodeAddress |= (long) (hardware[1] & 0xff) << 32;
nodeAddress |= (long) (hardware[2] & 0xff) << 24;
nodeAddress |= (long) (hardware[3] & 0xff) << 16;
nodeAddress |= (long) (hardware[4] & 0xff) << 8;
nodeAddress |= (long) (hardware[5] & 0xff);
nodeAddresses.add(nodeAddress);
}
}
}
}
}
catch (SocketException ex) {
return null;
}
long[] unwrapped = new long[nodeAddresses.size()];
for (int i = 0, nodeAddressesSize = nodeAddresses.size(); i < nodeAddressesSize; i++) {
unwrapped[i] = nodeAddresses.get(i);
}
return unwrapped;
}
}
}