]> git.argeo.org Git - lgpl/argeo-commons.git/blob - ConcurrentUuidFactory.java
14f6d54adb0d25467ad58c732fdc4654704a367c
[lgpl/argeo-commons.git] / ConcurrentUuidFactory.java
1 package org.argeo.api.uuid;
2
3 import static java.lang.System.Logger.Level.DEBUG;
4 import static java.lang.System.Logger.Level.INFO;
5 import static java.lang.System.Logger.Level.WARNING;
6
7 import java.lang.System.Logger;
8 import java.net.Inet4Address;
9 import java.net.Inet6Address;
10 import java.net.InetAddress;
11 import java.net.InterfaceAddress;
12 import java.net.NetworkInterface;
13 import java.net.SocketException;
14 import java.security.DrbgParameters;
15 import java.security.NoSuchAlgorithmException;
16 import java.security.SecureRandom;
17 import java.util.BitSet;
18 import java.util.Enumeration;
19 import java.util.Objects;
20 import java.util.UUID;
21
22 /**
23 * A configurable implementation of an {@link AsyncUuidFactory}, which can be
24 * used as a base class for more optimised implementations.
25 *
26 * @see https://datatracker.ietf.org/doc/html/rfc4122
27 */
28 public class ConcurrentUuidFactory extends AbstractAsyncUuidFactory {
29 private final static Logger logger = System.getLogger(ConcurrentUuidFactory.class.getName());
30
31 private Long nodeIdBase;
32
33 public ConcurrentUuidFactory(byte[] nodeId, int offset) {
34 Objects.requireNonNull(nodeId);
35 if (offset + 6 > nodeId.length)
36 throw new IllegalArgumentException("Offset too big: " + offset);
37 byte[] defaultNodeId = toNodeIdBytes(nodeId, offset);
38 nodeIdBase = NodeIdSupplier.toNodeIdBase(defaultNodeId);
39 setNodeIdSupplier(() -> nodeIdBase);
40 assert newTimeUUID().node() == BitSet.valueOf(defaultNodeId).toLongArray()[0];
41 }
42
43 public ConcurrentUuidFactory() {
44 byte[] defaultNodeId = getIpBytes();
45 nodeIdBase = NodeIdSupplier.toNodeIdBase(defaultNodeId);
46 setNodeIdSupplier(() -> nodeIdBase);
47 assert newTimeUUID().node() == BitSet.valueOf(defaultNodeId).toLongArray()[0];
48 }
49
50 /*
51 * DEFAULT
52 */
53 /**
54 * The default {@link UUID} to provide. This implementations returns
55 * {@link #timeUUID()} because it is fast and uses few resources.
56 */
57 @Override
58 public UUID get() {
59 return timeUUID();
60 }
61
62 @Override
63 protected SecureRandom newSecureRandom() {
64 SecureRandom secureRandom;
65 try {
66 secureRandom = SecureRandom.getInstance("DRBG",
67 DrbgParameters.instantiation(256, DrbgParameters.Capability.PR_AND_RESEED, "UUID".getBytes()));
68 } catch (NoSuchAlgorithmException e) {
69 try {
70 logger.log(DEBUG, "DRBG secure random not found, using strong");
71 secureRandom = SecureRandom.getInstanceStrong();
72 } catch (NoSuchAlgorithmException e1) {
73 logger.log(WARNING, "No strong secure random was found, using default");
74 secureRandom = new SecureRandom();
75 }
76 }
77 return secureRandom;
78 }
79
80 /** Returns an SHA1 digest of one of the IP addresses. */
81 protected byte[] getIpBytes() {
82 Enumeration<NetworkInterface> netInterfaces = null;
83 try {
84 netInterfaces = NetworkInterface.getNetworkInterfaces();
85 } catch (SocketException e) {
86 throw new IllegalStateException(e);
87 }
88 if (netInterfaces == null)
89 throw new IllegalStateException("No interfaces");
90
91 InetAddress selectedIpv6 = null;
92 InetAddress selectedIpv4 = null;
93 netInterfaces: while (netInterfaces.hasMoreElements()) {
94 NetworkInterface netInterface = netInterfaces.nextElement();
95 byte[] hardwareAddress = null;
96 try {
97 hardwareAddress = netInterface.getHardwareAddress();
98 if (hardwareAddress != null) {
99 // first IPv6
100 addr: for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
101 InetAddress ip = addr.getAddress();
102 if (ip instanceof Inet6Address) {
103 Inet6Address ipv6 = (Inet6Address) ip;
104 if (ipv6.isAnyLocalAddress() || ipv6.isLinkLocalAddress() || ipv6.isLoopbackAddress())
105 continue addr;
106 selectedIpv6 = ipv6;
107 break netInterfaces;
108 }
109
110 }
111 // then IPv4
112 addr: for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
113 InetAddress ip = addr.getAddress();
114 if (ip instanceof Inet4Address) {
115 Inet4Address ipv4 = (Inet4Address) ip;
116 if (ipv4.isAnyLocalAddress() || ipv4.isLinkLocalAddress() || ipv4.isLoopbackAddress())
117 continue addr;
118 selectedIpv4 = ipv4;
119 }
120
121 }
122 }
123 } catch (SocketException e) {
124 throw new IllegalStateException(e);
125 }
126 }
127 InetAddress selectedIp = selectedIpv6 != null ? selectedIpv6 : selectedIpv4;
128 if (selectedIp == null)
129 throw new IllegalStateException("No IP address found");
130 byte[] digest = sha1(selectedIp.getAddress());
131 logger.log(INFO, "Use IP " + selectedIp + " hashed as " + toHexString(digest) + " as node id");
132 byte[] nodeId = toNodeIdBytes(digest, 0);
133 // marks that this is not based on MAC address
134 forceToNoMacAddress(nodeId, 0);
135 return nodeId;
136 }
137 }