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;
}
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;
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
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)
*/
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
*/
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;
/**
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) {
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
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
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;
}
}
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
/** 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;
}
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;
/*
* 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();
/**
/**
* 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();
/**
* 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();
}
/*
* 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;
}
return false;
}
+ /** Whether this {@link UUID} is name based (v3 or v5). */
static boolean isNameBased(UUID uuid) {
return uuid.version() == 3 || uuid.version() == 5;
}