/* * 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; } } }