1 package org
.argeo
.api
.uuid
;
3 import java
.net
.InetAddress
;
4 import java
.net
.NetworkInterface
;
5 import java
.net
.SocketException
;
6 import java
.net
.UnknownHostException
;
7 import java
.security
.MessageDigest
;
8 import java
.security
.NoSuchAlgorithmException
;
9 import java
.time
.Duration
;
10 import java
.time
.temporal
.Temporal
;
11 import java
.util
.Objects
;
12 import java
.util
.Random
;
13 import java
.util
.UUID
;
16 * Implementation of the basic RFC4122 algorithms.
18 * @see https://datatracker.ietf.org/doc/html/rfc4122
20 public abstract class AbstractUuidFactory
implements UuidFactory
{
23 * TIME-BASED (version 1)
26 private final static long MOST_SIG_VERSION1
= (1l << 12);
27 private final static long LEAST_SIG_RFC4122_VARIANT
= (1l << 63);
29 protected UUID
newTimeUUID(long timestamp
, long clockSequence
, byte[] node
, int offset
) {
30 Objects
.requireNonNull(node
, "Node array cannot be null");
31 if (node
.length
< offset
+ 6)
32 throw new IllegalArgumentException("Node array must be at least 6 bytes long");
34 long mostSig
= MOST_SIG_VERSION1
// base for version 1 UUID
35 | ((timestamp
& 0xFFFFFFFFL
) << 32) // time_low
36 | (((timestamp
>> 32) & 0xFFFFL
) << 16) // time_mid
37 | ((timestamp
>> 48) & 0x0FFFL
);// time_hi_and_version
39 long leastSig
= LEAST_SIG_RFC4122_VARIANT
// base for Leach–Salz UUID
40 | (((clockSequence
& 0x3F00) >> 8) << 56) // clk_seq_hi_res
41 | ((clockSequence
& 0xFF) << 48) // clk_seq_low
42 | (node
[offset
] & 0xFFL
) //
43 | ((node
[offset
+ 1] & 0xFFL
) << 8) //
44 | ((node
[offset
+ 2] & 0xFFL
) << 16) //
45 | ((node
[offset
+ 3] & 0xFFL
) << 24) //
46 | ((node
[offset
+ 4] & 0xFFL
) << 32) //
47 | ((node
[offset
+ 5] & 0xFFL
) << 40); //
48 UUID uuid
= new UUID(mostSig
, leastSig
);
51 // assert uuid.node() == BitSet.valueOf(node).toLongArray()[0];
52 // assert uuid.node() == longFromBytes(node);
53 assert uuid
.timestamp() == timestamp
;
54 assert uuid
.clockSequence() == clockSequence
55 : "uuid.clockSequence()=" + uuid
.clockSequence() + " clockSequence=" + clockSequence
;
56 assert uuid
.version() == 1;
57 assert uuid
.variant() == 2;
61 public UUID
timeUUID(Temporal time
, long clockSequence
, byte[] node
, int offset
) {
63 Duration duration
= Duration
.between(TimeUuidState
.GREGORIAN_START
, time
);
64 // Number of 100 ns intervals in one second: 1000000000 / 100 = 10000000
65 long timestamp
= duration
.getSeconds() * 10000000 + duration
.getNano() / 100;
66 return newTimeUUID(timestamp
, clockSequence
, node
, offset
);
69 protected byte[] getHardwareAddress() {
70 InetAddress localHost
;
72 localHost
= InetAddress
.getLocalHost();
74 NetworkInterface nic
= NetworkInterface
.getByInetAddress(localHost
);
75 return nic
.getHardwareAddress();
76 } catch (SocketException e
) {
79 } catch (UnknownHostException e
) {
85 * NAME BASED (version 3 and 5)
88 protected UUID
newNameUUIDv5(UUID namespace
, byte[] name
) {
89 Objects
.requireNonNull(namespace
, "Namespace cannot be null");
90 Objects
.requireNonNull(name
, "Name cannot be null");
92 byte[] bytes
= sha1(toBytes(namespace
), name
);
94 bytes
[6] |= 0x50;// v5
96 bytes
[8] |= 0x80;// variant 1
97 UUID result
= fromBytes(bytes
, 0);
101 protected UUID
newNameUUIDv3(UUID namespace
, byte[] name
) {
102 Objects
.requireNonNull(namespace
, "Namespace cannot be null");
103 Objects
.requireNonNull(name
, "Name cannot be null");
105 byte[] arr
= new byte[name
.length
+ 16];
106 copyBytes(namespace
, arr
, 0);
107 System
.arraycopy(name
, 0, arr
, 16, name
.length
);
108 return UUID
.nameUUIDFromBytes(arr
);
114 protected UUID
newRandomUUID(Random random
) {
115 byte[] arr
= new byte[16];
116 random
.nextBytes(arr
);
120 arr
[8] |= 0x80;// variant 1
121 return fromBytes(arr
);
128 private final static String MD5
= "MD5";
129 private final static String SHA1
= "SHA1";
131 protected byte[] sha1(byte[]... bytes
) {
132 MessageDigest digest
= getSha1Digest();
133 for (byte[] arr
: bytes
)
135 byte[] checksum
= digest
.digest();
139 protected byte[] md5(byte[]... bytes
) {
140 MessageDigest digest
= getMd5Digest();
141 for (byte[] arr
: bytes
)
143 byte[] checksum
= digest
.digest();
147 protected MessageDigest
getSha1Digest() {
148 return getDigest(SHA1
);
151 protected MessageDigest
getMd5Digest() {
152 return getDigest(MD5
);
155 private MessageDigest
getDigest(String name
) {
157 return MessageDigest
.getInstance(name
);
158 } catch (NoSuchAlgorithmException e
) {
159 throw new UnsupportedOperationException(name
+ " digest is not avalaible", e
);
167 * Convert bytes to an UUID. Byte array must not be null and be exactly of
170 protected UUID
fromBytes(byte[] data
) {
171 Objects
.requireNonNull(data
, "Byte array must not be null");
172 if (data
.length
!= 16)
173 throw new IllegalArgumentException("Byte array as length " + data
.length
);
174 return fromBytes(data
, 0);
178 * Convert bytes to an UUID, starting to read the array at this offset.
180 protected UUID
fromBytes(byte[] data
, int offset
) {
181 Objects
.requireNonNull(data
, "Byte array cannot be null");
184 for (int i
= offset
; i
< 8 + offset
; i
++)
185 msb
= (msb
<< 8) | (data
[i
] & 0xff);
186 for (int i
= 8 + offset
; i
< 16 + offset
; i
++)
187 lsb
= (lsb
<< 8) | (data
[i
] & 0xff);
188 return new UUID(msb
, lsb
);
191 protected long longFromBytes(byte[] data
) {
193 for (int i
= 0; i
< data
.length
; i
++)
194 msb
= (msb
<< 8) | (data
[i
] & 0xff);
198 protected byte[] toBytes(UUID uuid
) {
199 Objects
.requireNonNull(uuid
, "UUID cannot be null");
200 long msb
= uuid
.getMostSignificantBits();
201 long lsb
= uuid
.getLeastSignificantBits();
202 return toBytes(msb
, lsb
);
205 protected void copyBytes(UUID uuid
, byte[] arr
, int offset
) {
206 Objects
.requireNonNull(uuid
, "UUID cannot be null");
207 long msb
= uuid
.getMostSignificantBits();
208 long lsb
= uuid
.getLeastSignificantBits();
209 copyBytes(msb
, lsb
, arr
, offset
);
213 * Converts an UUID hex representation without '-' to the standard form (with
216 public String
compactToStd(String compact
) {
217 if (compact
.length() != 32)
218 throw new IllegalArgumentException(
219 "Compact UUID '" + compact
+ "' has length " + compact
.length() + " and not 32.");
220 StringBuilder sb
= new StringBuilder(36);
221 for (int i
= 0; i
< 32; i
++) {
222 if (i
== 8 || i
== 12 || i
== 16 || i
== 20)
224 sb
.append(compact
.charAt(i
));
226 String std
= sb
.toString();
227 assert std
.length() == 36;
228 assert UUID
.fromString(std
).toString().equals(std
);
233 * Converts an UUID hex representation without '-' to an {@link UUID}.
235 public UUID
fromCompact(String compact
) {
236 return UUID
.fromString(compactToStd(compact
));
239 /** To a 32 characters hex string without '-'. */
240 public String
toCompact(UUID uuid
) {
241 return toHexString(toBytes(uuid
));
244 final protected static char[] hexArray
= "0123456789abcdef".toCharArray();
246 /** Convert two longs to a byte array with length 16. */
247 protected byte[] toBytes(long long1
, long long2
) {
248 byte[] result
= new byte[16];
249 for (int i
= 0; i
< 8; i
++)
250 result
[i
] = (byte) ((long1
>> ((7 - i
) * 8)) & 0xff);
251 for (int i
= 8; i
< 16; i
++)
252 result
[i
] = (byte) ((long2
>> ((15 - i
) * 8)) & 0xff);
256 protected void copyBytes(long long1
, long long2
, byte[] arr
, int offset
) {
257 assert arr
.length
>= 16 + offset
;
258 for (int i
= offset
; i
< 8 + offset
; i
++)
259 arr
[i
] = (byte) ((long1
>> ((7 - i
) * 8)) & 0xff);
260 for (int i
= 8 + offset
; i
< 16 + offset
; i
++)
261 arr
[i
] = (byte) ((long2
>> ((15 - i
) * 8)) & 0xff);
264 /** Converts a byte array to an hex String. */
265 protected String
toHexString(byte[] bytes
) {
266 char[] hexChars
= new char[bytes
.length
* 2];
267 for (int j
= 0; j
< bytes
.length
; j
++) {
268 int v
= bytes
[j
] & 0xFF;
269 hexChars
[j
* 2] = hexArray
[v
>>> 4];
270 hexChars
[j
* 2 + 1] = hexArray
[v
& 0x0F];
272 return new String(hexChars
);
275 protected byte[] toNodeId(byte[] source
, int offset
) {
278 if (offset
< 0 || offset
+ 6 > source
.length
)
279 throw new ArrayIndexOutOfBoundsException(offset
);
280 byte[] nodeId
= new byte[6];
281 System
.arraycopy(source
, offset
, nodeId
, 0, 6);
289 * Converts an UUID to a binary string (list of 0 and 1), with a separator to
290 * make it more readable.
292 public static String
toBinaryString(UUID uuid
, int charsPerSegment
, char separator
) {
293 Objects
.requireNonNull(uuid
, "UUID cannot be null");
294 String binaryString
= toBinaryString(uuid
);
295 StringBuilder sb
= new StringBuilder(128 + (128 / charsPerSegment
));
296 for (int i
= 0; i
< binaryString
.length(); i
++) {
297 if (i
!= 0 && i
% charsPerSegment
== 0)
298 sb
.append(separator
);
299 sb
.append(binaryString
.charAt(i
));
301 return sb
.toString();
304 /** Converts an UUID to a binary string (list of 0 and 1). */
305 public static String
toBinaryString(UUID uuid
) {
306 Objects
.requireNonNull(uuid
, "UUID cannot be null");
307 String most
= zeroTo64Chars(Long
.toBinaryString(uuid
.getMostSignificantBits()));
308 String least
= zeroTo64Chars(Long
.toBinaryString(uuid
.getLeastSignificantBits()));
309 String binaryString
= most
+ least
;
310 assert binaryString
.length() == 128;
315 * Force this node id to be identified as no MAC address.
317 * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.5
319 public static void forceToNoMacAddress(byte[] nodeId
, int offset
) {
320 assert nodeId
!= null && offset
< nodeId
.length
;
321 nodeId
[offset
] = (byte) (nodeId
[offset
] | 1);
324 private static String
zeroTo64Chars(String str
) {
325 assert str
.length() <= 64;
326 if (str
.length() < 64) {
327 StringBuilder sb
= new StringBuilder(64);
328 for (int i
= 0; i
< 64 - str
.length(); i
++)
331 return sb
.toString();