diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/UUIDFactory.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/UUIDFactory.java index d0e5b3f8..6c3b282f 100644 --- a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/UUIDFactory.java +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/UUIDFactory.java @@ -38,21 +38,27 @@ import java.security.SecureRandom; import java.util.*; /** - * A factory for creating UUIDs not directly supported by {@link java.util.UUID}. + * A factory for creating {@code UUID}s not directly supported by {@link java.util.UUID java.util.UUID}. *

- * This class can create - * version 1 time based, using either IEEE 802 (mac) address or random "node" value - * and version 5 SHA1 hash based UUIDs. + * 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. *

*

- * The node value for version 1 UUIDs will, by default, reflect the IEEE 802 (mac) address of one of + * 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. + *

+ *

+ * + * 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}. *

*

- * The node value for the random "node" based version 1 UUIDs will be stable for the lifetime of the VM. + * + * The node value for the random "node" based version 1 {@code UUID}s will be stable for the lifetime of the VM. *

* * @author Harald Kuhr @@ -67,12 +73,12 @@ public final class UUIDFactory { private static final String NODE_PROPERTY = "com.twelvemonkeys.util.UUID.node"; /** - * Nil UUID: {@code "00000000-0000-0000-0000-000000000000"}. + * 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. + * 128 bits set to zero. Not particularly useful, unless as a placeholder or template. * - * @see RFC 4122 + * @see RFC 4122 4.1.7. Nil UUID */ public static final UUID NIL = new UUID(0l, 0l); @@ -86,34 +92,7 @@ public final class UUIDFactory { static final long SECURE_RANDOM_NODE = getSecureRandomNode(); private static long getSecureRandomNode() { - /* - Obtain a 47-bit cryptographic quality random - number and use it as the low 47 bits of the node ID, with the least - significant bit of the first octet of the node ID set to one. This - bit is the unicast/multicast bit, which will never be set in IEEE 802 - addresses obtained from network cards. Hence, there can never be a - conflict between UUIDs generated by machines with and without network - cards. (Recall that the IEEE 802 spec talks about transmission - order) - */ - - /* - In addition, items such as the computer's name and the name of the - operating system, while not strictly speaking random, will help - differentiate the results from those obtained by other systems. - - The exact algorithm to generate a node ID using these data is system - specific, because both the data available and the functions to obtain - them are often very system specific. A generic approach, however, is - to accumulate as many sources as possible into a buffer, use a - message digest such as MD5 [4] or SHA-1 [8], take an arbitrary 6 - bytes from the hash value, and set the multicast bit as described - above. - */ - - // TODO: Verify that nextLong is still cryptographically strong after the bit masking - // TODO: Consider using the hashing approach above - + // 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; } @@ -171,11 +150,20 @@ public final class UUIDFactory { private UUIDFactory() {} - // See also http://tools.ietf.org/html/rfc4122#appendix-B - // See http://tools.ietf.org/html/rfc4122: 4.3. Algorithm for Creating a Name-Based UUID - // TODO: Naming (of the method) - // TODO: Read up on creating these UUIDs in RFC, mentions something about UUID for namespace as input..? - static UUID nameUUIDFromBytesSHA1(byte[] name) { + /** + * 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 RFC 4122 4.3. Algorithm for Creating a Name-Based UUID + * @see RFC 4122 appendix A + * @see UUID#nameUUIDFromBytes(byte[]) + */ + public static UUID nameUUIDv5FromBytes(byte[] name) { // Based on code from OpenJDK UUID#nameUUIDFromBytes + private byte[] constructor MessageDigest md; @@ -207,26 +195,39 @@ public final class UUIDFactory { return new UUID(msb, lsb); } - // Creatse version 1 node based UUIDs as specified in rfc422 - // See http://tools.ietf.org/html/rfc4122#appendix-B - // See http://en.wikipedia.org/wiki/MAC_address - // TODO: Naming (of the method) - static UUID timeNodeBasedV1() { + /** + * 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 RFC 4122 4.2. Algorithms for Creating a Time-Based UUID + * @see IEEE 802 (mac) address + * @see Overriding the node address + * + * @throws IllegalStateException if the IEEE 802 (mac) address of the computer (node) cannot be found. + */ + public static UUID timeNodeBasedUUID() { if (MAC_ADDRESS_NODE == -1) { - // TODO: OR fall back to Random?? throw new IllegalStateException("Could not determine IEEE 802 (mac) address for node"); } return new UUID(createTimeAndVersion(), createClockSeqAndNode(MAC_ADDRESS_NODE)); } - // Creates version 1 "node" based UUIDs, using 47 bit secure random number + lsb of first octet - // (unicast/multicast bit) set to 1 as described in rfc422: 4.5. Node IDs that Do Not Identify the Host - // See http://tools.ietf.org/html/rfc4122#appendix-B - // TODO: Naming (of the method) - // TODO: Document that these can never clash with node based v1 UUIDs due to unicast/multicast bit - // However, no uniqueness between multiple mavhines/vms/nodes can be guaranteed. - static UUID timeRandomBasedV1() { + /** + * 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 RFC 4122 4.5. Node IDs that Do Not Identify the Host + * @see RFC 4122 Appendix A + * @see Lifetime of random node value + * + * @throws IllegalStateException if the IEEE 802 (mac) address of the computer (node) cannot be found. + */ + public static UUID timeRandomBasedUUID() { return new UUID(createTimeAndVersion(), createClockSeqAndNode(SECURE_RANDOM_NODE)); } @@ -261,7 +262,7 @@ public final class UUIDFactory { * @return a comparator that compares UUIDs as 128 bit unsigned entities. * * @see java.lang.UUID compareTo() does not do an unsigned compare - * @see + * @see RFC 4122 Appendix A */ public static Comparator comparator() { return COMPARATOR; @@ -299,14 +300,13 @@ public final class UUIDFactory { return 0; } } - /** - * A high-resolution timer for use in creating version 1 UUIDs. + * 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 = 122192928000000000L; + private static final long JAVA_EPOCH_OFFSET_HUNDRED_NANOS = 122192928000000000L; private static int clockSeq = SECURE_RANDOM.nextInt(); @@ -324,7 +324,7 @@ public final class UUIDFactory { long millis = System.currentTimeMillis(); long nanos = System.nanoTime(); - initialTime = JAVA_EPOCH_OFFSET + millis * 10000 + (nanos / 100) % 10000; + initialTime = JAVA_EPOCH_OFFSET_HUNDRED_NANOS + millis * 10000 + (nanos / 100) % 10000; initialNanos = nanos; } diff --git a/sandbox/sandbox-common/src/test/java/com/twelvemonkeys/util/UUIDFactoryTest.java b/sandbox/sandbox-common/src/test/java/com/twelvemonkeys/util/UUIDFactoryTest.java index 5f19c7ad..b8f4539e 100644 --- a/sandbox/sandbox-common/src/test/java/com/twelvemonkeys/util/UUIDFactoryTest.java +++ b/sandbox/sandbox-common/src/test/java/com/twelvemonkeys/util/UUIDFactoryTest.java @@ -51,7 +51,7 @@ public class UUIDFactoryTest { @Test public void testVersion3NameBasedMD5NotEqualSHA1() throws UnsupportedEncodingException { UUID a = UUID.nameUUIDFromBytes(EXAMPLE_COM_UUID.getBytes("UTF-8")); - assertFalse(a.equals(UUIDFactory.nameUUIDFromBytesSHA1(EXAMPLE_COM_UUID.getBytes("UTF-8")))); + assertFalse(a.equals(UUIDFactory.nameUUIDv5FromBytes(EXAMPLE_COM_UUID.getBytes("UTF-8")))); } @Test @@ -64,27 +64,27 @@ public class UUIDFactoryTest { @Test public void testVersion5NameBasedSHA1VariantVersion() throws UnsupportedEncodingException { - UUID a = UUIDFactory.nameUUIDFromBytesSHA1(EXAMPLE_COM_UUID.getBytes("UTF-8")); + UUID a = UUIDFactory.nameUUIDv5FromBytes(EXAMPLE_COM_UUID.getBytes("UTF-8")); assertEquals(2, a.variant()); assertEquals(5, a.version()); } @Test public void testVersion5NameBasedSHA1Equals() throws UnsupportedEncodingException { - UUID a = UUIDFactory.nameUUIDFromBytesSHA1(EXAMPLE_COM_UUID.getBytes("UTF-8")); - UUID b = UUIDFactory.nameUUIDFromBytesSHA1(EXAMPLE_COM_UUID.getBytes("UTF-8")); + UUID a = UUIDFactory.nameUUIDv5FromBytes(EXAMPLE_COM_UUID.getBytes("UTF-8")); + UUID b = UUIDFactory.nameUUIDv5FromBytes(EXAMPLE_COM_UUID.getBytes("UTF-8")); assertEquals(a, b); } @Test public void testVersion5NameBasedSHA1NotEqualMD5() throws UnsupportedEncodingException { - UUID a = UUIDFactory.nameUUIDFromBytesSHA1(EXAMPLE_COM_UUID.getBytes("UTF-8")); + UUID a = UUIDFactory.nameUUIDv5FromBytes(EXAMPLE_COM_UUID.getBytes("UTF-8")); assertFalse(a.equals(UUID.nameUUIDFromBytes(EXAMPLE_COM_UUID.getBytes("UTF-8")))); } @Test public void testVersion5NameBasedSHA1FromStringRep() throws UnsupportedEncodingException { - UUID a = UUIDFactory.nameUUIDFromBytesSHA1(EXAMPLE_COM_UUID.getBytes("UTF-8")); + UUID a = UUIDFactory.nameUUIDv5FromBytes(EXAMPLE_COM_UUID.getBytes("UTF-8")); assertEquals(a, UUID.fromString(a.toString())); } @@ -92,37 +92,36 @@ public class UUIDFactoryTest { @Test public void testVersion1NodeBasedVariantVersion() { - UUID uuid = UUIDFactory.timeNodeBasedV1(); + UUID uuid = UUIDFactory.timeNodeBasedUUID(); assertEquals(2, uuid.variant()); assertEquals(1, uuid.version()); } @Test public void testVersion1NodeBasedMacAddress() { - UUID uuid = UUIDFactory.timeNodeBasedV1(); + UUID uuid = UUIDFactory.timeNodeBasedUUID(); assertEquals(UUIDFactory.MAC_ADDRESS_NODE, uuid.node()); // TODO: Test that this is actually a Mac address from the local computer, or specified through system property? } @Test public void testVersion1NodeBasedFromStringRep() { - UUID uuid = UUIDFactory.timeNodeBasedV1(); + UUID uuid = UUIDFactory.timeNodeBasedUUID(); assertEquals(uuid, UUID.fromString(uuid.toString())); } @Test public void testVersion1NodeBasedClockSeq() { - UUID uuid = UUIDFactory.timeNodeBasedV1(); + UUID uuid = UUIDFactory.timeNodeBasedUUID(); assertEquals(UUIDFactory.Clock.getClockSequence(), uuid.clockSequence()); // Test time fields (within reasonable limits +/- 100 ms or so?) - // TODO: Compare with system clock for sloppier resolution assertEquals(UUIDFactory.Clock.currentTimeHundredNanos(), uuid.timestamp(), 1e6); } @Test public void testVersion1NodeBasedTimestamp() { - UUID uuid = UUIDFactory.timeNodeBasedV1(); + UUID uuid = UUIDFactory.timeNodeBasedUUID(); // Test time fields (within reasonable limits +/- 100 ms or so?) assertEquals(UUIDFactory.Clock.currentTimeHundredNanos(), uuid.timestamp(), 1e6); } @@ -131,7 +130,7 @@ public class UUIDFactoryTest { public void testVersion1NodeBasedUniMulticastBitUnset() { // Do it a couple of times, to avoid accidentally have correct bit for (int i = 0; i < 100; i++) { - UUID uuid = UUIDFactory.timeNodeBasedV1(); + UUID uuid = UUIDFactory.timeNodeBasedUUID(); assertEquals(0, (uuid.node() >> 40) & 1); } } @@ -139,15 +138,15 @@ public class UUIDFactoryTest { @Test public void testVersion1NodeBasedUnique() { for (int i = 0; i < 100; i++) { - UUID a = UUIDFactory.timeNodeBasedV1(); - UUID b = UUIDFactory.timeNodeBasedV1(); + UUID a = UUIDFactory.timeNodeBasedUUID(); + UUID b = UUIDFactory.timeNodeBasedUUID(); assertFalse(a.equals(b)); } } @Test public void testVersion1SecureRandomVariantVersion() { - UUID uuid = UUIDFactory.timeRandomBasedV1(); + UUID uuid = UUIDFactory.timeRandomBasedUUID(); assertEquals(2, uuid.variant()); assertEquals(1, uuid.version()); @@ -155,25 +154,25 @@ public class UUIDFactoryTest { @Test public void testVersion1SecureRandomNode() { - UUID uuid = UUIDFactory.timeRandomBasedV1(); + UUID uuid = UUIDFactory.timeRandomBasedUUID(); assertEquals(UUIDFactory.SECURE_RANDOM_NODE, uuid.node()); } @Test public void testVersion1SecureRandomFromStringRep() { - UUID uuid = UUIDFactory.timeRandomBasedV1(); + UUID uuid = UUIDFactory.timeRandomBasedUUID(); assertEquals(uuid, UUID.fromString(uuid.toString())); } @Test public void testVersion1SecureRandomClockSeq() { - UUID uuid = UUIDFactory.timeRandomBasedV1(); + UUID uuid = UUIDFactory.timeRandomBasedUUID(); assertEquals(UUIDFactory.Clock.getClockSequence(), uuid.clockSequence()); } @Test public void testVersion1SecureRandomTimestamp() { - UUID uuid = UUIDFactory.timeRandomBasedV1(); + UUID uuid = UUIDFactory.timeRandomBasedUUID(); // Test time fields (within reasonable limits +/- 100 ms or so?) assertEquals(UUIDFactory.Clock.currentTimeHundredNanos(), uuid.timestamp(), 1e6); @@ -183,7 +182,7 @@ public class UUIDFactoryTest { public void testVersion1SecureRandomUniMulticastBit() { // Do it a couple of times, to avoid accidentally have correct bit for (int i = 0; i < 100; i++) { - UUID uuid = UUIDFactory.timeRandomBasedV1(); + UUID uuid = UUIDFactory.timeRandomBasedUUID(); assertEquals(1, (uuid.node() >> 40) & 1); } } @@ -191,8 +190,8 @@ public class UUIDFactoryTest { @Test public void testVersion1SecureRandomUnique() { for (int i = 0; i < 100; i++) { - UUID a = UUIDFactory.timeRandomBasedV1(); - UUID b = UUIDFactory.timeRandomBasedV1(); + UUID a = UUIDFactory.timeRandomBasedUUID(); + UUID b = UUIDFactory.timeRandomBasedUUID(); assertFalse(a.equals(b)); } } @@ -217,7 +216,7 @@ public class UUIDFactoryTest { service.shutdown(); assertTrue("Execution timed out", service.awaitTermination(10, TimeUnit.SECONDS)); - Arrays.sort(times); + Arrays.sort(times); // This is what really takes time... for (int i = 0, timesLength = times.length; i < timesLength; i++) { if (i == 0) {