X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=org.argeo.api.acr%2Fsrc%2Forg%2Fargeo%2Fapi%2Facr%2Fuuid%2FAbstractUuidFactory.java;h=1ecb64e12ae65044be0fa162656ef3865d8dcbee;hb=8b201d419e0de2f0df848e47dd4b48a2a10c711b;hp=7dbd8501156deaa086c9d78d4adbf20de0949ad3;hpb=8e7c19c1facf69e17bc57694f1143da05d166a26;p=lgpl%2Fargeo-commons.git diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/uuid/AbstractUuidFactory.java b/org.argeo.api.acr/src/org/argeo/api/acr/uuid/AbstractUuidFactory.java index 7dbd85011..1ecb64e12 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/uuid/AbstractUuidFactory.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/uuid/AbstractUuidFactory.java @@ -1,168 +1,34 @@ package org.argeo.api.acr.uuid; -import static java.lang.System.Logger.Level.DEBUG; - -import java.lang.System.Logger; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.net.UnknownHostException; -import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; import java.time.Duration; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.util.BitSet; +import java.time.temporal.Temporal; import java.util.Objects; import java.util.Random; import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; /** - * Static utilities to simplify and extend usage of {@link UUID}. Only the RFC - * 4122 variant (also known as Leach–Salz variant) is supported. + * Implementation of the basic RFC4122 algorithms. * * @see https://datatracker.ietf.org/doc/html/rfc4122 */ -public class AbstractUuidFactory { - /** Nil UUID (00000000-0000-0000-0000-000000000000). */ - public final static UUID NIL_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000"); - - /** Start of the Gregorian time, used by time-based UUID. */ - public final static LocalDateTime GREGORIAN_START = LocalDateTime.of(1582, 10, 15, 0, 0, 0); - - /** - * Standard DNS namespace ID for type 3 or 5 UUID (as defined in Appendix C of - * RFC4122). - */ - public final static UUID NS_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). - */ - public final static UUID NS_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). - */ - public final static UUID NS_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). - */ - public final static UUID NS_X500 = UUID.fromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8"); +public abstract class AbstractUuidFactory implements UuidFactory { /* - * INTERNAL STATIC UTILITIES + * TIME-BASED (version 1) */ - private final static Logger logger = System.getLogger(AbstractUuidFactory.class.getName()); private final static long MOST_SIG_VERSION1 = (1l << 12); private final static long LEAST_SIG_RFC4122_VARIANT = (1l << 63); - private final static int MAX_CLOCKSEQUENCE = 16384; - - private final static SecureRandom SECURE_RANDOM; - private final static Random UNSECURE_RANDOM; - private final static byte[] HARDWARE_ADDRESS; - - private final static AtomicInteger CLOCK_SEQUENCE; - - /** A start timestamp to which {@link System#nanoTime()}/100 can be added. */ - private final static long START_TIMESTAMP; - - static { - SECURE_RANDOM = new SecureRandom(); - UNSECURE_RANDOM = new Random(); - CLOCK_SEQUENCE = new AtomicInteger(SECURE_RANDOM.nextInt(MAX_CLOCKSEQUENCE)); - HARDWARE_ADDRESS = getHardwareAddress(); - - long nowVm = System.nanoTime() / 100; - Duration duration = Duration.between(GREGORIAN_START, LocalDateTime.now(ZoneOffset.UTC)); - START_TIMESTAMP = (duration.getSeconds() * 10000000 + duration.getNano() / 100) - nowVm; - } - - /* - * TIME-BASED (version 1) - */ - - private static 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; - } - - } - - private synchronized static long nextClockSequence() { - int i = CLOCK_SEQUENCE.incrementAndGet(); - while (i < 0 || i >= MAX_CLOCKSEQUENCE) { - CLOCK_SEQUENCE.set(SECURE_RANDOM.nextInt(MAX_CLOCKSEQUENCE)); - i = CLOCK_SEQUENCE.incrementAndGet(); - } - return (long) i; - } - - public static UUID timeUUIDwithRandomNode() { - long timestamp = START_TIMESTAMP + System.nanoTime() / 100; - return timeUUID(timestamp, SECURE_RANDOM); - } - - public static UUID timeUUIDwithUnsecureRandomNode() { - long timestamp = START_TIMESTAMP + System.nanoTime() / 100; - return timeUUID(timestamp, UNSECURE_RANDOM); - } - - public static UUID timeUUID(long timestamp, Random random) { - byte[] node = new byte[6]; - random.nextBytes(node); - node[0] = (byte) (node[0] | 1); - long clockSequence = nextClockSequence(); - return timeUUID(timestamp, clockSequence, node); - } - - public static UUID timeUUID() { - long timestamp = START_TIMESTAMP + System.nanoTime() / 100; - return timeUUID(timestamp); - } - - public static UUID timeUUID(long timestamp) { - if (HARDWARE_ADDRESS == null) - return timeUUID(timestamp, SECURE_RANDOM); - long clockSequence = nextClockSequence(); - return timeUUID(timestamp, clockSequence, HARDWARE_ADDRESS); - } - public static UUID timeUUID(long timestamp, NetworkInterface nic) { - byte[] node; - try { - node = nic.getHardwareAddress(); - } catch (SocketException e) { - throw new IllegalStateException("Cannot get hardware address", e); - } - long clockSequence = nextClockSequence(); - return timeUUID(timestamp, clockSequence, node); - } - - public static UUID timeUUID(LocalDateTime time, long clockSequence, byte[] node) { - Duration duration = Duration.between(GREGORIAN_START, time); - // Number of 100 ns intervals in one second: 1000000000 / 100 = 10000000 - long timestamp = duration.getSeconds() * 10000000 + duration.getNano() / 100; - return timeUUID(timestamp, clockSequence, node); - } - - public static UUID timeUUID(long timestamp, long clockSequence, byte[] node) { + protected UUID newTimeUUID(long timestamp, long clockSequence, byte[] node, int offset) { Objects.requireNonNull(node, "Node array cannot be null"); - if (node.length < 6) + if (node.length < offset + 6) throw new IllegalArgumentException("Node array must be at least 6 bytes long"); long mostSig = MOST_SIG_VERSION1 // base for version 1 UUID @@ -173,16 +39,17 @@ public class AbstractUuidFactory { long leastSig = LEAST_SIG_RFC4122_VARIANT // base for Leach–Salz UUID | (((clockSequence & 0x3F00) >> 8) << 56) // clk_seq_hi_res | ((clockSequence & 0xFF) << 48) // clk_seq_low - | (node[0] & 0xFFL) // - | ((node[1] & 0xFFL) << 8) // - | ((node[2] & 0xFFL) << 16) // - | ((node[3] & 0xFFL) << 24) // - | ((node[4] & 0xFFL) << 32) // - | ((node[5] & 0xFFL) << 40); // + | (node[offset] & 0xFFL) // + | ((node[offset + 1] & 0xFFL) << 8) // + | ((node[offset + 2] & 0xFFL) << 16) // + | ((node[offset + 3] & 0xFFL) << 24) // + | ((node[offset + 4] & 0xFFL) << 32) // + | ((node[offset + 5] & 0xFFL) << 40); // UUID uuid = new UUID(mostSig, leastSig); // tests - assert uuid.node() == BitSet.valueOf(node).toLongArray()[0]; +// 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; @@ -191,20 +58,37 @@ public class AbstractUuidFactory { return uuid; } + public 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 + long timestamp = duration.getSeconds() * 10000000 + duration.getNano() / 100; + 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) */ - public final static String MD5 = "MD5"; - public final static String SHA1 = "SHA1"; - - public static UUID nameUUIDv5(UUID namespace, String name) { + protected UUID newNameUUIDv5(UUID namespace, byte[] name) { Objects.requireNonNull(namespace, "Namespace cannot be null"); Objects.requireNonNull(name, "Name cannot be null"); - return nameUUIDv5(namespace, name.getBytes(StandardCharsets.UTF_8)); - } - public static UUID nameUUIDv5(UUID namespace, byte[] name) { byte[] bytes = sha1(toBytes(namespace), name); bytes[6] &= 0x0f; bytes[6] |= 0x50;// v5 @@ -214,37 +98,22 @@ public class AbstractUuidFactory { return result; } - public static UUID nameUUIDv3(UUID namespace, String name) { + protected UUID newNameUUIDv3(UUID namespace, byte[] name) { Objects.requireNonNull(namespace, "Namespace cannot be null"); Objects.requireNonNull(name, "Name cannot be null"); - return nameUUIDv3(namespace, name.getBytes(StandardCharsets.UTF_8)); - } - public static UUID nameUUIDv3(UUID namespace, byte[] name) { byte[] arr = new byte[name.length + 16]; copyBytes(namespace, arr, 0); System.arraycopy(name, 0, arr, 16, name.length); return UUID.nameUUIDFromBytes(arr); } - static byte[] sha1(byte[]... bytes) { - try { - MessageDigest digest = MessageDigest.getInstance(SHA1); - for (byte[] arr : bytes) - digest.update(arr); - byte[] checksum = digest.digest(); - return checksum; - } catch (NoSuchAlgorithmException e) { - throw new UnsupportedOperationException("SHA1 is not avalaible", e); - } - } - /* * RANDOM v4 */ - public static UUID unsecureRandomUUID() { + protected UUID newRandomUUID(Random random) { byte[] arr = new byte[16]; - UNSECURE_RANDOM.nextBytes(arr); + random.nextBytes(arr); arr[6] &= 0x0f; arr[6] |= 0x40;// v4 arr[8] &= 0x3f; @@ -252,6 +121,45 @@ public class AbstractUuidFactory { return fromBytes(arr); } + /* + * DIGEST UTILITIES + */ + + private final static String MD5 = "MD5"; + private final static String SHA1 = "SHA1"; + + protected byte[] sha1(byte[]... bytes) { + MessageDigest digest = getSha1Digest(); + for (byte[] arr : bytes) + digest.update(arr); + byte[] checksum = digest.digest(); + return checksum; + } + + protected byte[] md5(byte[]... bytes) { + MessageDigest digest = getMd5Digest(); + for (byte[] arr : bytes) + digest.update(arr); + byte[] checksum = digest.digest(); + return checksum; + } + + protected MessageDigest getSha1Digest() { + return getDigest(SHA1); + } + + protected MessageDigest getMd5Digest() { + return getDigest(MD5); + } + + private MessageDigest getDigest(String name) { + try { + return MessageDigest.getInstance(name); + } catch (NoSuchAlgorithmException e) { + throw new UnsupportedOperationException(name + " digest is not avalaible", e); + } + } + /* * UTILITIES */ @@ -259,7 +167,7 @@ public class AbstractUuidFactory { * Convert bytes to an UUID. Byte array must not be null and be exactly of * length 16. */ - public static UUID fromBytes(byte[] data) { + protected UUID fromBytes(byte[] data) { Objects.requireNonNull(data, "Byte array must not be null"); if (data.length != 16) throw new IllegalArgumentException("Byte array as length " + data.length); @@ -269,7 +177,7 @@ public class AbstractUuidFactory { /** * Convert bytes to an UUID, starting to read the array at this offset. */ - public static UUID fromBytes(byte[] data, int offset) { + protected UUID fromBytes(byte[] data, int offset) { Objects.requireNonNull(data, "Byte array cannot be null"); long msb = 0; long lsb = 0; @@ -280,61 +188,32 @@ public class AbstractUuidFactory { return new UUID(msb, lsb); } - public static byte[] toBytes(UUID uuid) { + protected long longFromBytes(byte[] data) { + long msb = 0; + for (int i = 0; i < data.length; i++) + msb = (msb << 8) | (data[i] & 0xff); + return msb; + } + + protected byte[] toBytes(UUID uuid) { Objects.requireNonNull(uuid, "UUID cannot be null"); long msb = uuid.getMostSignificantBits(); long lsb = uuid.getLeastSignificantBits(); return toBytes(msb, lsb); } - public static void copyBytes(UUID uuid, byte[] arr, int offset) { + protected void copyBytes(UUID uuid, byte[] arr, int offset) { Objects.requireNonNull(uuid, "UUID cannot be null"); long msb = uuid.getMostSignificantBits(); long lsb = uuid.getLeastSignificantBits(); copyBytes(msb, lsb, arr, offset); } - /** - * Converts an UUID to a binary string (list of 0 and 1), with a separator to - * make it more readable. - */ - public static String toBinaryString(UUID uuid, int charsPerSegment, char separator) { - String binaryString = toBinaryString(uuid); - StringBuilder sb = new StringBuilder(128 + (128 / charsPerSegment)); - for (int i = 0; i < binaryString.length(); i++) { - if (i != 0 && i % charsPerSegment == 0) - sb.append(separator); - sb.append(binaryString.charAt(i)); - } - return sb.toString(); - } - - /** Converts an UUID to a binary string (list of 0 and 1). */ - public static String toBinaryString(UUID uuid) { - String most = zeroTo64Chars(Long.toBinaryString(uuid.getMostSignificantBits())); - String least = zeroTo64Chars(Long.toBinaryString(uuid.getLeastSignificantBits())); - String binaryString = most + least; - assert binaryString.length() == 128; - return binaryString; - } - - private static String zeroTo64Chars(String str) { - assert str.length() <= 64; - if (str.length() < 64) { - StringBuilder sb = new StringBuilder(64); - for (int i = 0; i < 64 - str.length(); i++) - sb.append('0'); - sb.append(str); - return sb.toString(); - } else - return str; - } - /** * Converts an UUID hex representation without '-' to the standard form (with * '-'). */ - public static String compactToStd(String compact) { + public String compactToStd(String compact) { if (compact.length() != 32) throw new IllegalArgumentException( "Compact UUID '" + compact + "' has length " + compact.length() + " and not 32."); @@ -353,39 +232,19 @@ public class AbstractUuidFactory { /** * Converts an UUID hex representation without '-' to an {@link UUID}. */ - public static UUID fromCompact(String compact) { + public UUID fromCompact(String compact) { return UUID.fromString(compactToStd(compact)); } /** To a 32 characters hex string without '-'. */ - public static String toCompact(UUID uuid) { + public String toCompact(UUID uuid) { return toHexString(toBytes(uuid)); } - public static boolean isRandom(UUID uuid) { - return uuid.version() == 4; - } - - public static boolean isTimeBased(UUID uuid) { - return uuid.version() == 1; - } - - public static boolean isTimeBasedRandom(UUID uuid) { - if (uuid.version() == 1) { - BitSet node = BitSet.valueOf(new long[] { uuid.node() }); - return node.get(0); - } else - return false; - } - - public static boolean isNameBased(UUID uuid) { - return uuid.version() == 3 || uuid.version() == 5; - } - - final private static char[] hexArray = "0123456789abcdef".toCharArray(); + final protected static char[] hexArray = "0123456789abcdef".toCharArray(); /** Convert two longs to a byte array with length 16. */ - public static byte[] toBytes(long long1, long long2) { + protected byte[] toBytes(long long1, long long2) { byte[] result = new byte[16]; for (int i = 0; i < 8; i++) result[i] = (byte) ((long1 >> ((7 - i) * 8)) & 0xff); @@ -394,7 +253,7 @@ public class AbstractUuidFactory { return result; } - public static void copyBytes(long long1, long long2, byte[] arr, int offset) { + protected void copyBytes(long long1, long long2, byte[] arr, int offset) { assert arr.length >= 16 + offset; for (int i = offset; i < 8 + offset; i++) arr[i] = (byte) ((long1 >> ((7 - i) * 8)) & 0xff); @@ -403,7 +262,7 @@ public class AbstractUuidFactory { } /** Converts a byte array to an hex String. */ - public static String toHexString(byte[] bytes) { + protected String toHexString(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { int v = bytes[j] & 0xFF; @@ -413,88 +272,65 @@ public class AbstractUuidFactory { return new String(hexChars); } - /** Singleton. */ - private AbstractUuidFactory() { + protected byte[] toNodeId(byte[] source, int offset) { + if (source == null) + return null; + if (offset < 0 || offset + 6 > source.length) + throw new ArrayIndexOutOfBoundsException(offset); + byte[] nodeId = new byte[6]; + System.arraycopy(source, offset, nodeId, 0, 6); + return nodeId; } /* - * SMOKE TESTS + * STATIC UTILITIES */ - - static boolean smokeTests() throws AssertionError { - - // warm up a bit before measuring perf and logging it - int warmUpCycles = 10; - // int warmUpCycles = 10000000; - if (logger.isLoggable(DEBUG)) - for (int i = 0; i < warmUpCycles; i++) { - UUID.randomUUID(); - unsecureRandomUUID(); - timeUUID(); - timeUUIDwithRandomNode(); - nameUUIDv5(NS_DNS, "example.org"); - nameUUIDv3(NS_DNS, "example.org"); - } - - long begin; - - { - begin = System.nanoTime(); - UUID uuid = UUID.randomUUID(); - long duration = System.nanoTime() - begin; - assert isRandom(uuid); - logger.log(DEBUG, () -> uuid.toString() + " in " + duration + " ns, isRandom=" + isRandom(uuid)); - } - - { - begin = System.nanoTime(); - UUID uuid = unsecureRandomUUID(); - long duration = System.nanoTime() - begin; - assert isRandom(uuid); - logger.log(DEBUG, () -> uuid.toString() + " in " + duration + " ns, isRandom=" + isRandom(uuid)); - } - - { - begin = System.nanoTime(); - UUID uuid = timeUUID(); - long duration = System.nanoTime() - begin; - assert isTimeBased(uuid); - logger.log(DEBUG, - () -> uuid.toString() + " in " + duration + " ns, isTimeBasedRandom=" + isTimeBasedRandom(uuid)); - } - - { - begin = System.nanoTime(); - UUID uuid = timeUUIDwithRandomNode(); - long duration = System.nanoTime() - begin; - assert isTimeBasedRandom(uuid); - logger.log(DEBUG, - () -> uuid.toString() + " in " + duration + " ns, isTimeBasedRandom=" + isTimeBasedRandom(uuid)); + /** + * Converts an UUID to a binary string (list of 0 and 1), with a separator to + * make it more readable. + */ + public static String toBinaryString(UUID uuid, int charsPerSegment, char separator) { + Objects.requireNonNull(uuid, "UUID cannot be null"); + String binaryString = toBinaryString(uuid); + StringBuilder sb = new StringBuilder(128 + (128 / charsPerSegment)); + for (int i = 0; i < binaryString.length(); i++) { + if (i != 0 && i % charsPerSegment == 0) + sb.append(separator); + sb.append(binaryString.charAt(i)); } + return sb.toString(); + } - { - begin = System.nanoTime(); - UUID uuid = nameUUIDv5(NS_DNS, "example.org"); - long duration = System.nanoTime() - begin; - assert isNameBased(uuid); - // uuidgen --sha1 --namespace @dns --name example.org - assert "aad03681-8b63-5304-89e0-8ca8f49461b5".equals(uuid.toString()); - logger.log(DEBUG, () -> uuid.toString() + " in " + duration + " ns, isNameBased=" + isNameBased(uuid)); - } + /** Converts an UUID to a binary string (list of 0 and 1). */ + public static String toBinaryString(UUID uuid) { + Objects.requireNonNull(uuid, "UUID cannot be null"); + String most = zeroTo64Chars(Long.toBinaryString(uuid.getMostSignificantBits())); + String least = zeroTo64Chars(Long.toBinaryString(uuid.getLeastSignificantBits())); + String binaryString = most + least; + assert binaryString.length() == 128; + return binaryString; + } - { - begin = System.nanoTime(); - UUID uuid = nameUUIDv3(NS_DNS, "example.org"); - long duration = System.nanoTime() - begin; - assert isNameBased(uuid); - // uuidgen --md5 --namespace @dns --name example.org - assert "04738bdf-b25a-3829-a801-b21a1d25095b".equals(uuid.toString()); - logger.log(DEBUG, () -> uuid.toString() + " in " + duration + " ns, isNameBased=" + isNameBased(uuid)); - } - return AbstractUuidFactory.class.desiredAssertionStatus(); + /** + * Force this node id to be identified as no MAC address. + * + * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.5 + */ + public static void forceToNoMacAddress(byte[] nodeId, int offset) { + assert nodeId != null && offset < nodeId.length; + nodeId[offset] = (byte) (nodeId[offset] | 1); } - public static void main(String[] args) { - smokeTests(); + private static String zeroTo64Chars(String str) { + assert str.length() <= 64; + if (str.length() < 64) { + StringBuilder sb = new StringBuilder(64); + for (int i = 0; i < 64 - str.length(); i++) + sb.append('0'); + sb.append(str); + return sb.toString(); + } else + return str; } + }