]> git.argeo.org Git - lgpl/argeo-commons.git/blob - uuid/AbstractUuidFactory.java
Prepare next development cycle
[lgpl/argeo-commons.git] / uuid / AbstractUuidFactory.java
1 package org.argeo.api.uuid;
2
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;
11
12 /**
13 * Implementation of the basic RFC4122 algorithms.
14 *
15 * @see https://datatracker.ietf.org/doc/html/rfc4122
16 */
17 public abstract class AbstractUuidFactory implements UuidFactory {
18
19 /*
20 * TIME-BASED (version 1)
21 */
22
23 private final static long MOST_SIG_VERSION1 = (1l << 12);
24 private final static long LEAST_SIG_RFC4122_VARIANT = (1l << 63);
25
26 protected UUID newTimeUUID(long timestamp, long clockSequence, byte[] node, int offset) {
27 Objects.requireNonNull(node, "Node array cannot be null");
28 if (node.length < offset + 6)
29 throw new IllegalArgumentException("Node array must be at least 6 bytes long");
30
31 long mostSig = MOST_SIG_VERSION1 // base for version 1 UUID
32 | ((timestamp & 0xFFFFFFFFL) << 32) // time_low
33 | (((timestamp >> 32) & 0xFFFFL) << 16) // time_mid
34 | ((timestamp >> 48) & 0x0FFFL);// time_hi_and_version
35
36 long leastSig = LEAST_SIG_RFC4122_VARIANT // base for Leach–Salz UUID
37 | (((clockSequence & 0x3F00) >> 8) << 56) // clk_seq_hi_res
38 | ((clockSequence & 0xFF) << 48) // clk_seq_low
39 | (node[offset] & 0xFFL) //
40 | ((node[offset + 1] & 0xFFL) << 8) //
41 | ((node[offset + 2] & 0xFFL) << 16) //
42 | ((node[offset + 3] & 0xFFL) << 24) //
43 | ((node[offset + 4] & 0xFFL) << 32) //
44 | ((node[offset + 5] & 0xFFL) << 40); //
45 UUID uuid = new UUID(mostSig, leastSig);
46
47 // tests
48 assert uuid.version() == 1;
49 assert uuid.variant() == 2;
50 assert uuid.node() == BitSet.valueOf(node).toLongArray()[0];
51 assert uuid.timestamp() == timestamp;
52 assert uuid.clockSequence() == clockSequence;
53 return uuid;
54 }
55
56 protected UUID timeUUID(Temporal time, long clockSequence, byte[] node, int offset) {
57 // TODO add checks
58 Duration duration = Duration.between(TimeUuid.TIMESTAMP_ZERO, time);
59 // Number of 100 ns intervals in one second: 1000000000 / 100 = 10000000
60 long timestamp = duration.getSeconds() * 10000000 + duration.getNano() / 100;
61 return newTimeUUID(timestamp, clockSequence, node, offset);
62 }
63
64 /*
65 * NAME BASED (version 3 and 5)
66 */
67
68 protected UUID newNameUUIDv5(UUID namespace, byte[] name) {
69 Objects.requireNonNull(namespace, "Namespace cannot be null");
70 Objects.requireNonNull(name, "Name cannot be null");
71
72 byte[] bytes = sha1(UuidBinaryUtils.toBytes(namespace), name);
73 bytes[6] &= 0x0f;
74 bytes[6] |= 0x50;// v5
75 bytes[8] &= 0x3f;
76 bytes[8] |= 0x80;// variant 1
77 UUID result = UuidBinaryUtils.fromBytes(bytes, 0);
78 return result;
79 }
80
81 protected UUID newNameUUIDv3(UUID namespace, byte[] name) {
82 Objects.requireNonNull(namespace, "Namespace cannot be null");
83 Objects.requireNonNull(name, "Name cannot be null");
84
85 byte[] arr = new byte[name.length + 16];
86 UuidBinaryUtils.copyBytes(namespace, arr, 0);
87 System.arraycopy(name, 0, arr, 16, name.length);
88 return UUID.nameUUIDFromBytes(arr);
89 }
90
91 /*
92 * RANDOM v4
93 */
94 protected UUID newRandomUUID(Random random) {
95 byte[] arr = new byte[16];
96 random.nextBytes(arr);
97 arr[6] &= 0x0f;
98 arr[6] |= 0x40;// v4
99 arr[8] &= 0x3f;
100 arr[8] |= 0x80;// variant 1
101 return UuidBinaryUtils.fromBytes(arr);
102 }
103
104 /*
105 * SPI UTILITIES
106 */
107 /** Guarantees that a byte array of length 6 will be returned. */
108 protected static byte[] toNodeIdBytes(byte[] source, int offset) {
109 if (source == null)
110 return null;
111 if (offset < 0 || offset + 6 > source.length)
112 throw new ArrayIndexOutOfBoundsException(offset);
113 byte[] nodeId = new byte[6];
114 System.arraycopy(source, offset, nodeId, 0, 6);
115 return nodeId;
116 }
117
118 /**
119 * Force this node id to be identified as no MAC address.
120 *
121 * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.5
122 */
123 protected static void forceToNoMacAddress(byte[] nodeId, int offset) {
124 assert nodeId != null && offset < nodeId.length;
125 nodeId[offset] = (byte) (nodeId[offset] | 1);
126 }
127
128 /*
129 * DIGEST UTILITIES
130 */
131
132 private final static String MD5 = "MD5";
133 private final static String SHA1 = "SHA1";
134
135 protected static byte[] sha1(byte[]... bytes) {
136 MessageDigest digest = getSha1Digest();
137 for (byte[] arr : bytes)
138 digest.update(arr);
139 byte[] checksum = digest.digest();
140 return checksum;
141 }
142
143 protected static byte[] md5(byte[]... bytes) {
144 MessageDigest digest = getMd5Digest();
145 for (byte[] arr : bytes)
146 digest.update(arr);
147 byte[] checksum = digest.digest();
148 return checksum;
149 }
150
151 protected static MessageDigest getSha1Digest() {
152 return getDigest(SHA1);
153 }
154
155 protected static MessageDigest getMd5Digest() {
156 return getDigest(MD5);
157 }
158
159 private static MessageDigest getDigest(String name) {
160 try {
161 return MessageDigest.getInstance(name);
162 } catch (NoSuchAlgorithmException e) {
163 throw new UnsupportedOperationException(name + " digest is not avalaible", e);
164 }
165 }
166
167 }