Stabilise configuration and documentation of UUID factory.
authorMathieu Baudier <mbaudier@argeo.org>
Mon, 24 Jan 2022 11:45:23 +0000 (12:45 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Mon, 24 Jan 2022 11:45:23 +0000 (12:45 +0100)
org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractAsyncUuidFactory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractUuidFactory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/TimeUuidState.java
org.argeo.api.uuid/src/org/argeo/api/uuid/UuidFactory.java

index f57462388959f2fb9e64c8dd55c0da38cdaf8bd0..2b4c27f3fac693defff64b53ea2141767022a6b7 100644 (file)
@@ -64,14 +64,11 @@ public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory imple
                if (nodeIdSupplier == null)
                        throw new IllegalStateException("No node id supplier available");
                UUID uuid = new UUID(timeUuidState.getMostSignificantBits(), timeUuidState.getLeastSignificantBits());
-               long clockSequence = timeUuidState.getClockSequence(); 
-               long timestamp = timeUuidState.getLastTimestamp(); 
-               // assert uuid.node() == longFromBytes(node);
-               assert uuid.timestamp() == timestamp;
-               assert uuid.clockSequence() == clockSequence
-                               : "uuid.clockSequence()=" + uuid.clockSequence() + " clockSequence=" + clockSequence;
+
                assert uuid.version() == 1;
                assert uuid.variant() == 2;
+               assert uuid.timestamp() == timeUuidState.getLastTimestamp();
+               assert uuid.clockSequence() == timeUuidState.getClockSequence();
 
                return uuid;
        }
index 9d829987b6cd6cd047d939ddbba3da7ffb26db73..d348504adff6784bc259953bf4ff58f462aef6fd 100644 (file)
@@ -1,13 +1,10 @@
 package org.argeo.api.uuid;
 
-import java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.net.UnknownHostException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.time.Duration;
 import java.time.temporal.Temporal;
+import java.util.BitSet;
 import java.util.Objects;
 import java.util.Random;
 import java.util.UUID;
@@ -48,17 +45,15 @@ public abstract class AbstractUuidFactory implements UuidFactory {
                UUID uuid = new UUID(mostSig, leastSig);
 
                // tests
-//             assert uuid.node() == BitSet.valueOf(node).toLongArray()[0];
-               // assert uuid.node() == longFromBytes(node);
-               assert uuid.timestamp() == timestamp;
-               assert uuid.clockSequence() == clockSequence
-                               : "uuid.clockSequence()=" + uuid.clockSequence() + " clockSequence=" + clockSequence;
                assert uuid.version() == 1;
                assert uuid.variant() == 2;
+               assert uuid.node() == BitSet.valueOf(node).toLongArray()[0];
+               assert uuid.timestamp() == timestamp;
+               assert uuid.clockSequence() == clockSequence;
                return uuid;
        }
 
-       public UUID timeUUID(Temporal time, long clockSequence, byte[] node, int offset) {
+       protected UUID timeUUID(Temporal time, long clockSequence, byte[] node, int offset) {
                // TODO add checks
                Duration duration = Duration.between(TimeUuidState.GREGORIAN_START, time);
                // Number of 100 ns intervals in one second: 1000000000 / 100 = 10000000
@@ -66,21 +61,6 @@ public abstract class AbstractUuidFactory implements UuidFactory {
                return newTimeUUID(timestamp, clockSequence, node, offset);
        }
 
-       protected byte[] getHardwareAddress() {
-               InetAddress localHost;
-               try {
-                       localHost = InetAddress.getLocalHost();
-                       try {
-                               NetworkInterface nic = NetworkInterface.getByInetAddress(localHost);
-                               return nic.getHardwareAddress();
-                       } catch (SocketException e) {
-                               return null;
-                       }
-               } catch (UnknownHostException e) {
-                       return null;
-               }
-
-       }
        /*
         * NAME BASED (version 3 and 5)
         */
@@ -282,17 +262,6 @@ public abstract class AbstractUuidFactory implements UuidFactory {
                return nodeId;
        }
 
-//     protected long toNodeIdBase(byte[] node) {
-//             assert node.length == 6;
-//             return LEAST_SIG_RFC4122_VARIANT // base for Leach–Salz UUID
-//                             | (node[0] & 0xFFL) //
-//                             | ((node[1] & 0xFFL) << 8) //
-//                             | ((node[2] & 0xFFL) << 16) //
-//                             | ((node[3] & 0xFFL) << 24) //
-//                             | ((node[4] & 0xFFL) << 32) //
-//                             | ((node[5] & 0xFFL) << 40); //
-//     }
-
        /*
         * STATIC UTILITIES
         */
index 05b2f05c6b62345e0160363c7129983301d32ade..4ad2290b9c52d1ee9cb80efd240920747d7d8fa0 100644 (file)
@@ -1,12 +1,21 @@
 package org.argeo.api.uuid;
 
 import static java.lang.System.Logger.Level.DEBUG;
+import static java.lang.System.Logger.Level.INFO;
 import static java.lang.System.Logger.Level.WARNING;
 
 import java.lang.System.Logger;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
 import java.security.DrbgParameters;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
+import java.util.BitSet;
+import java.util.Enumeration;
 import java.util.Objects;
 
 /**
@@ -18,8 +27,6 @@ import java.util.Objects;
 public class ConcurrentUuidFactory extends AbstractAsyncUuidFactory {
        private final static Logger logger = System.getLogger(ConcurrentUuidFactory.class.getName());
 
-//     private byte[] defaultNodeId;
-
        private Long nodeIdBase;
 
        public ConcurrentUuidFactory(byte[] nodeId, int offset) {
@@ -29,10 +36,14 @@ public class ConcurrentUuidFactory extends AbstractAsyncUuidFactory {
                byte[] defaultNodeId = toNodeIdBytes(nodeId, offset);
                nodeIdBase = NodeIdSupplier.toNodeIdBase(defaultNodeId);
                setNodeIdSupplier(() -> nodeIdBase);
+               assert newTimeUUID().node() == BitSet.valueOf(defaultNodeId).toLongArray()[0];
        }
 
-       protected ConcurrentUuidFactory() {
-
+       public ConcurrentUuidFactory() {
+               byte[] defaultNodeId = getIpBytes();
+               nodeIdBase = NodeIdSupplier.toNodeIdBase(defaultNodeId);
+               setNodeIdSupplier(() -> nodeIdBase);
+               assert newTimeUUID().node() == BitSet.valueOf(defaultNodeId).toLongArray()[0];
        }
 
        @Override
@@ -53,12 +64,61 @@ public class ConcurrentUuidFactory extends AbstractAsyncUuidFactory {
                return secureRandom;
        }
 
-       /*
-        * TIME-BASED (version 1)
-        */
-//
-//     @Override
-//     public UUID newTimeUUID() {
-//             return newTimeUUID(timeUuidState.useTimestamp(), timeUuidState.getClockSequence(), defaultNodeId, 0);
-//     }
-}
+       /** Returns an SHA1 digest of one of the IP addresses. */
+       protected byte[] getIpBytes() {
+               Enumeration<NetworkInterface> netInterfaces = null;
+               try {
+                       netInterfaces = NetworkInterface.getNetworkInterfaces();
+               } catch (SocketException e) {
+                       throw new IllegalStateException(e);
+               }
+               if (netInterfaces == null)
+                       throw new IllegalStateException("No interfaces");
+
+               InetAddress selectedIpv6 = null;
+               InetAddress selectedIpv4 = null;
+               netInterfaces: while (netInterfaces.hasMoreElements()) {
+                       NetworkInterface netInterface = netInterfaces.nextElement();
+                       byte[] hardwareAddress = null;
+                       try {
+                               hardwareAddress = netInterface.getHardwareAddress();
+                               if (hardwareAddress != null) {
+                                       // first IPv6
+                                       addr: for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
+                                               InetAddress ip = addr.getAddress();
+                                               if (ip instanceof Inet6Address) {
+                                                       Inet6Address ipv6 = (Inet6Address) ip;
+                                                       if (ipv6.isAnyLocalAddress() || ipv6.isLinkLocalAddress() || ipv6.isLoopbackAddress())
+                                                               continue addr;
+                                                       selectedIpv6 = ipv6;
+                                                       break netInterfaces;
+                                               }
+
+                                       }
+                                       // then IPv4
+                                       addr: for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
+                                               InetAddress ip = addr.getAddress();
+                                               if (ip instanceof Inet4Address) {
+                                                       Inet4Address ipv4 = (Inet4Address) ip;
+                                                       if (ipv4.isAnyLocalAddress() || ipv4.isLinkLocalAddress() || ipv4.isLoopbackAddress())
+                                                               continue addr;
+                                                       selectedIpv4 = ipv4;
+                                               }
+
+                                       }
+                               }
+                       } catch (SocketException e) {
+                               throw new IllegalStateException(e);
+                       }
+               }
+               InetAddress selectedIp = selectedIpv6 != null ? selectedIpv6 : selectedIpv4;
+               if (selectedIp == null)
+                       throw new IllegalStateException("No IP address found");
+               byte[] digest = sha1(selectedIp.getAddress());
+               logger.log(INFO, "Use IP " + selectedIp + " hashed as " + toHexString(digest) + " as node id");
+               byte[] nodeId = toNodeIdBytes(digest, 0);
+               // marks that this is not based on MAC address
+               forceToNoMacAddress(nodeId, 0);
+               return nodeId;
+       }
+}
\ No newline at end of file
index d1ce6ded851ded57825c20be3158498f32281f3e..39c919fa6d36976bed0439a0b2a2c22138ecf41b 100644 (file)
@@ -1,15 +1,44 @@
 package org.argeo.api.uuid;
 
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.UUID;
+
+/**
+ * An {@link UUID} factory whose node id (for time based UUIDs) is the hardware
+ * MAC address as specified in RFC4122.
+ * 
+ * @see https://datatracker.ietf.org/doc/html/rfc4122.html#section-4.1.6
+ */
 public class MacAddressUuidFactory extends ConcurrentUuidFactory {
        public final static UuidFactory DEFAULT = new MacAddressUuidFactory();
 
        public MacAddressUuidFactory() {
-               setNodeIdSupplier(() -> {
-                       byte[] hardwareAddress = getHardwareAddress();
-                       byte[] macAddressNodeId = toNodeIdBytes(hardwareAddress, 0);
-                       long nodeIdBase = NodeIdSupplier.toNodeIdBase(macAddressNodeId);
-                       return nodeIdBase;
-               });
+               super(localHardwareAddressAsNodeId(), 0);
+       }
+
+       public static byte[] localHardwareAddressAsNodeId() {
+               InetAddress localHost;
+               try {
+                       localHost = InetAddress.getLocalHost();
+                       NetworkInterface nic = NetworkInterface.getByInetAddress(localHost);
+                       return hardwareAddressToNodeId(nic);
+               } catch (UnknownHostException | SocketException e) {
+                       throw new IllegalStateException(e);
+               }
+
+       }
+
+       public static byte[] hardwareAddressToNodeId(NetworkInterface nic) throws SocketException {
+               byte[] hardwareAddress = nic.getHardwareAddress();
+               final int length = 6;
+               byte[] arr = new byte[length];
+               for (int i = 0; i < length; i++) {
+                       arr[i] = hardwareAddress[length - 1 - i];
+               }
+               return arr;
        }
 
 }
index 780e8ad337070e6fc129db45e91e6646509abaa2..9c8b6dd69b294b8eb56f8cf9b66d02ae388bd40f 100644 (file)
@@ -3,6 +3,7 @@ package org.argeo.api.uuid;
 import java.time.Instant;
 import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
+import java.util.UUID;
 
 /**
  * The state of a time based UUID generator, as described and discussed in
@@ -17,14 +18,24 @@ public interface TimeUuidState {
        /** Start of the Gregorian time, used by time-based UUID (v1). */
        final static Instant GREGORIAN_START = ZonedDateTime.of(1582, 10, 15, 0, 0, 0, 0, ZoneOffset.UTC).toInstant();
 
-       long getLastTimestamp();
-
-       long getClockSequence();
-
+       /** Current node id and clock sequence for this thread. */
        long getLeastSignificantBits();
 
+       /** A new current timestamp for this thread. */
        long getMostSignificantBits();
 
+       /**
+        * The last timestamp which was produced by this thread, as returned by
+        * {@link UUID#timestamp()}.
+        */
+       long getLastTimestamp();
+
+       /**
+        * The current clock sequence for this thread, as returned by
+        * {@link UUID#clockSequence()}.
+        */
+       long getClockSequence();
+
        static boolean isNoMacAddressNodeId(byte[] nodeId) {
                return (nodeId[0] & 1) != 0;
        }
index 78bb402cf4ff13e5a44fc3b710c99b21209b0f26..91191dae2bf1fd17a4138048685a8e8d62515ad5 100644 (file)
@@ -2,6 +2,7 @@ package org.argeo.api.uuid;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import java.nio.charset.Charset;
 import java.util.Objects;
 import java.util.UUID;
 import java.util.concurrent.ThreadLocalRandom;
@@ -19,29 +20,86 @@ public interface UuidFactory extends Supplier<UUID> {
        /*
         * TIME-BASED (version 1)
         */
+       /**
+        * A new time based {@link UUID} (v1) with efforts to make it unique on this
+        * node.
+        */
        UUID timeUUID();
 
        /*
         * NAME BASED (version 3 and 5)
         */
+       /**
+        * A new {@link UUID} v5, which an SHA1 digest of namespace and the provided
+        * bytes. This use to build names and implementation MAY restrict the maximal
+        * size of the byte array.
+        * 
+        * @see UuidFactory#NAMESPACE_UUID_DNS
+        * @see UuidFactory#NAMESPACE_UUID_URL
+        * @see UuidFactory#NAMESPACE_UUID_OID
+        * @see UuidFactory#NAMESPACE_UUID_X500
+        */
        UUID nameUUIDv5(UUID namespace, byte[] data);
 
+       /**
+        * A new {@link UUID} v3, which a MD5 digest of namespace and the provided
+        * bytes. This use to build names and implementation MAY restrict the maximal
+        * size of the byte array.
+        * 
+        * @see UuidFactory#NAMESPACE_UUID_DNS
+        * @see UuidFactory#NAMESPACE_UUID_URL
+        * @see UuidFactory#NAMESPACE_UUID_OID
+        * @see UuidFactory#NAMESPACE_UUID_X500
+        */
        UUID nameUUIDv3(UUID namespace, byte[] data);
 
+       /**
+        * A convenience method to generate a name based UUID v5 based on a string,
+        * using the UTF-8 charset.
+        * 
+        * @see UuidFactory#nameUUIDv5(UUID, byte[])
+        */
        default UUID nameUUIDv5(UUID namespace, String name) {
+               return nameUUIDv5(namespace, name, UTF_8);
+       }
+
+       /**
+        * A convenience method to generate a name based UUID v5 based on a string.
+        * 
+        * @see UuidFactory#nameUUIDv5(UUID, byte[])
+        */
+       default UUID nameUUIDv5(UUID namespace, String name, Charset charset) {
                Objects.requireNonNull(name, "Name cannot be null");
-               return nameUUIDv5(namespace, name.getBytes(UTF_8));
+               return nameUUIDv5(namespace, name.getBytes(charset));
        }
 
+       /**
+        * A convenience method to generate a name based UUID v3 based on a string,
+        * using the UTF-8 charset.
+        * 
+        * @see UuidFactory#nameUUIDv3(UUID, byte[])
+        */
        default UUID nameUUIDv3(UUID namespace, String name) {
+               return nameUUIDv3(namespace, name, UTF_8);
+       }
+
+       /**
+        * A convenience method to generate a name based UUID v3 based on a string.
+        * 
+        * @see UuidFactory#nameUUIDv3(UUID, byte[])
+        */
+       default UUID nameUUIDv3(UUID namespace, String name, Charset charset) {
                Objects.requireNonNull(name, "Name cannot be null");
-               return nameUUIDv3(namespace, name.getBytes(UTF_8));
+               return nameUUIDv3(namespace, name.getBytes(charset));
        }
 
        /*
         * RANDOM (version 4)
         */
-       /** A random UUID at least as good as {@link UUID#randomUUID()}. */
+       /**
+        * A random UUID at least as good as {@link UUID#randomUUID()}, but with efforts
+        * to make it even more random, using more secure algorithms and resseeding.
+        */
        UUID randomUUIDStrong();
 
        /**
@@ -52,7 +110,11 @@ public interface UuidFactory extends Supplier<UUID> {
 
        /**
         * The default random {@link UUID} (v4) generator to use. This default
-        * implementation returns {@link #randomUUIDStrong()}.
+        * implementation returns {@link #randomUUIDStrong()}. In general, one should
+        * use {@link UUID#randomUUID()} to generate random UUID, as it is certainly the
+        * best balanced and to avoid unnecessary dependencies with an API. The
+        * implementations provided here are either when is looking for something
+        * "stronger" ({@link #randomUUIDStrong()} or faster {@link #randomUUIDWeak()}.
         */
        default UUID randomUUID() {
                return randomUUIDStrong();
@@ -60,11 +122,12 @@ public interface UuidFactory extends Supplier<UUID> {
 
        /**
         * The default {@link UUID} to provide, either random (v4) or time based (v1).
-        * This default implementations returns {@link #randomUUID()}.
+        * This default implementations returns {@link #timeUUID()} because it is
+        * supposed to be fast and use few resources.
         */
        @Override
        default UUID get() {
-               return randomUUID();
+               return timeUUID();
        }
 
        /*
@@ -77,31 +140,32 @@ public interface UuidFactory extends Supplier<UUID> {
         * Standard DNS namespace ID for type 3 or 5 UUID (as defined in Appendix C of
         * RFC4122).
         */
-       final static UUID NS_DNS = UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
+       final static UUID NAMESPACE_UUID_DNS = UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
        /**
         * Standard URL namespace ID for type 3 or 5 UUID (as defined in Appendix C of
         * RFC4122).
         */
-       final static UUID NS_URL = UUID.fromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8");
+       final static UUID NAMESPACE_UUID_URL = UUID.fromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8");
        /**
         * Standard OID namespace ID (typically an LDAP type) for type 3 or 5 UUID (as
         * defined in Appendix C of RFC4122).
         */
-       final static UUID NS_OID = UUID.fromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8");
+       final static UUID NAMESPACE_UUID_OID = UUID.fromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8");
        /**
         * Standard X500 namespace ID (typically an LDAP DN) for type 3 or 5 UUID (as
         * defined in Appendix C of RFC4122).
         */
-       final static UUID NS_X500 = UUID.fromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8");
+       final static UUID NAMESPACE_UUID_X500 = UUID.fromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8");
 
        /*
         * UTILITIES
         */
-
+       /** Whether this {@link UUID} is random (v4). */
        static boolean isRandom(UUID uuid) {
                return uuid.version() == 4;
        }
 
+       /** Whether this {@link UUID} is time based (v1). */
        static boolean isTimeBased(UUID uuid) {
                return uuid.version() == 1;
        }
@@ -119,6 +183,7 @@ public interface UuidFactory extends Supplier<UUID> {
                        return false;
        }
 
+       /** Whether this {@link UUID} is name based (v3 or v5). */
        static boolean isNameBased(UUID uuid) {
                return uuid.version() == 3 || uuid.version() == 5;
        }