1 package org
.argeo
.api
.uuid
;
3 import java
.security
.MessageDigest
;
4 import java
.security
.NoSuchAlgorithmException
;
5 import java
.time
.Duration
;
6 import java
.time
.temporal
.Temporal
;
7 import java
.util
.BitSet
;
8 import java
.util
.Objects
;
9 import java
.util
.Random
;
10 import java
.util
.UUID
;
13 * Implementation of the basic RFC4122 algorithms.
15 * @see "https://datatracker.ietf.org/doc/html/rfc4122"
17 public abstract class AbstractUuidFactory
implements UuidFactory
{
20 * TIME-BASED (version 1)
23 private final static long MOST_SIG_VERSION1
= (1l << 12);
24 private final static long LEAST_SIG_RFC4122_VARIANT
= (1l << 63);
27 protected UUID
createTimeUUID(long timestamp
, long clockSequence
, byte[] node
, int offset
) {
28 Objects
.requireNonNull(node
, "Node array cannot be null");
29 if (node
.length
< offset
+ 6)
30 throw new IllegalArgumentException("Node array must be at least 6 bytes long");
32 long mostSig
= MOST_SIG_VERSION1
// base for version 1 UUID
33 | ((timestamp
& 0xFFFFFFFFL
) << 32) // time_low
34 | (((timestamp
>> 32) & 0xFFFFL
) << 16) // time_mid
35 | ((timestamp
>> 48) & 0x0FFFL
);// time_hi_and_version
37 long leastSig
= LEAST_SIG_RFC4122_VARIANT
// base for Leach–Salz UUID
38 | (((clockSequence
& 0x3F00) >> 8) << 56) // clk_seq_hi_res
39 | ((clockSequence
& 0xFF) << 48) // clk_seq_low
40 | (node
[offset
] & 0xFFL
) //
41 | ((node
[offset
+ 1] & 0xFFL
) << 8) //
42 | ((node
[offset
+ 2] & 0xFFL
) << 16) //
43 | ((node
[offset
+ 3] & 0xFFL
) << 24) //
44 | ((node
[offset
+ 4] & 0xFFL
) << 32) //
45 | ((node
[offset
+ 5] & 0xFFL
) << 40); //
46 UUID uuid
= new UUID(mostSig
, leastSig
);
49 assert uuid
.version() == 1;
50 assert uuid
.variant() == 2;
51 assert uuid
.node() == BitSet
.valueOf(node
).toLongArray()[0];
52 assert uuid
.timestamp() == timestamp
;
53 assert uuid
.clockSequence() == clockSequence
;
58 protected UUID
timeUUID(Temporal time
, long clockSequence
, byte[] node
, int offset
) {
60 Duration duration
= Duration
.between(TimeUuid
.TIMESTAMP_ZERO
, time
);
61 // Number of 100 ns intervals in one second: 1000000000 / 100 = 10000000
62 long timestamp
= duration
.getSeconds() * 10000000 + duration
.getNano() / 100;
63 return createTimeUUID(timestamp
, clockSequence
, node
, offset
);
67 * NAME BASED (version 3 and 5)
69 protected UUID
createNameUUIDv5(UUID namespace
, byte[] name
) {
70 return createNameUUIDv5Static(namespace
, name
);
73 static UUID
createNameUUIDv5Static(UUID namespace
, byte[] name
) {
74 Objects
.requireNonNull(namespace
, "Namespace cannot be null");
75 Objects
.requireNonNull(name
, "Name cannot be null");
77 byte[] bytes
= sha1(UuidBinaryUtils
.toBytes(namespace
), name
);
79 bytes
[6] |= 0x50;// v5
81 bytes
[8] |= 0x80;// variant 1
82 UUID result
= UuidBinaryUtils
.fromBytes(bytes
, 0);
86 protected UUID
createNameUUIDv3(UUID namespace
, byte[] name
) {
87 return createNameUUIDv3Static(namespace
, name
);
90 static UUID
createNameUUIDv3Static(UUID namespace
, byte[] name
) {
91 Objects
.requireNonNull(namespace
, "Namespace cannot be null");
92 Objects
.requireNonNull(name
, "Name cannot be null");
94 byte[] arr
= new byte[name
.length
+ 16];
95 UuidBinaryUtils
.copyBytes(namespace
, arr
, 0);
96 System
.arraycopy(name
, 0, arr
, 16, name
.length
);
97 return UUID
.nameUUIDFromBytes(arr
);
103 protected UUID
createRandomUUID(Random random
) {
104 byte[] arr
= new byte[16];
105 random
.nextBytes(arr
);
109 arr
[8] |= 0x80;// variant 1
110 return UuidBinaryUtils
.fromBytes(arr
);
116 /** Guarantees that a byte array of length 6 will be returned. */
117 protected static byte[] toNodeIdBytes(byte[] source
, int offset
) {
120 if (offset
< 0 || offset
+ 6 > source
.length
)
121 throw new ArrayIndexOutOfBoundsException(offset
);
122 byte[] nodeId
= new byte[6];
123 System
.arraycopy(source
, offset
, nodeId
, 0, 6);
128 * Force this node id to be identified as no MAC address.
130 * @see "https://datatracker.ietf.org/doc/html/rfc4122#section-4.5"
132 protected static void forceToNoMacAddress(byte[] nodeId
, int offset
) {
133 assert nodeId
!= null && offset
< nodeId
.length
;
134 nodeId
[offset
] = (byte) (nodeId
[offset
] | 1);
141 private final static String MD5
= "MD5";
142 private final static String SHA1
= "SHA1";
144 protected static byte[] sha1(byte[]... bytes
) {
145 MessageDigest digest
= getSha1Digest();
146 for (byte[] arr
: bytes
)
148 byte[] checksum
= digest
.digest();
152 protected static byte[] md5(byte[]... bytes
) {
153 MessageDigest digest
= getMd5Digest();
154 for (byte[] arr
: bytes
)
156 byte[] checksum
= digest
.digest();
160 protected static MessageDigest
getSha1Digest() {
161 return getDigest(SHA1
);
164 protected static MessageDigest
getMd5Digest() {
165 return getDigest(MD5
);
168 private static MessageDigest
getDigest(String name
) {
170 return MessageDigest
.getInstance(name
);
171 } catch (NoSuchAlgorithmException e
) {
172 throw new UnsupportedOperationException(name
+ " digest is not avalaible", e
);