Stabilise configuration and documentation of UUID factory.
[lgpl/argeo-commons.git] / org.argeo.api.uuid / src / org / argeo / api / uuid / ConcurrentUuidFactory.java
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