uuidv7.h
uuidv7.h
Go to the documentation of this file.
1/**
2 * @file
3 *
4 * uuidv7.h - Single-file C/C++ UUIDv7 Library
5 *
6 * @version v0.1.6
7 * @author LiosK
8 * @copyright Licensed under the Apache License, Version 2.0
9 * @see https://github.com/LiosK/uuidv7-h
10 */
11/*
12 * Copyright 2022 LiosK
13 *
14 * Licensed under the Apache License, Version 2.0 (the "License");
15 * you may not use this file except in compliance with the License.
16 * You may obtain a copy of the License at
17 *
18 * http://www.apache.org/licenses/LICENSE-2.0
19 *
20 * Unless required by applicable law or agreed to in writing, software
21 * distributed under the License is distributed on an "AS IS" BASIS,
22 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 * See the License for the specific language governing permissions and
24 * limitations under the License.
25 */
26#ifndef UUIDV7_H_BAEDKYFQ
27#define UUIDV7_H_BAEDKYFQ
28
29#include <stddef.h>
30#include <stdint.h>
31
32/**
33 * @name Status codes returned by uuidv7_generate()
34 *
35 * @{
36 */
37
38/**
39 * Indicates that the `unix_ts_ms` passed was used because no preceding UUID was
40 * specified.
41 */
42#define UUIDV7_STATUS_UNPRECEDENTED (0)
43
44/**
45 * Indicates that the `unix_ts_ms` passed was used because it was greater than
46 * the previous one.
47 */
48#define UUIDV7_STATUS_NEW_TIMESTAMP (1)
49
50/**
51 * Indicates that the counter was incremented because the `unix_ts_ms` passed
52 * was no greater than the previous one.
53 */
54#define UUIDV7_STATUS_COUNTER_INC (2)
55
56/**
57 * Indicates that the previous `unix_ts_ms` was incremented because the counter
58 * reached its maximum value.
59 */
60#define UUIDV7_STATUS_TIMESTAMP_INC (3)
61
62/**
63 * Indicates that the monotonic order of generated UUIDs was broken because the
64 * `unix_ts_ms` passed was less than the previous one by more than ten seconds.
65 */
66#define UUIDV7_STATUS_CLOCK_ROLLBACK (4)
67
68/** Indicates that an invalid `unix_ts_ms` is passed. */
69#define UUIDV7_STATUS_ERR_TIMESTAMP (-1)
70
71/**
72 * Indicates that the attempt to increment the previous `unix_ts_ms` failed
73 * because it had reached its maximum value.
74 */
75#define UUIDV7_STATUS_ERR_TIMESTAMP_OVERFLOW (-2)
76
77/** @} */
78
79#ifdef __cplusplus
80extern "C" {
81#endif
82
83/**
84 * @name Low-level primitives
85 *
86 * @{
87 */
88
89/**
90 * Generates a new UUIDv7 from the given Unix time, random bytes, and previous
91 * UUID.
92 *
93 * @param uuid_out 16-byte byte array where the generated UUID is stored.
94 * @param unix_ts_ms Current Unix time in milliseconds.
95 * @param rand_bytes At least 10-byte byte array filled with random bytes. This
96 * function consumes the leading 4 bytes or the whole 10
97 * bytes per call depending on the conditions.
98 * `uuidv7_status_n_rand_consumed()` maps the return value of
99 * this function to the number of random bytes consumed.
100 * @param uuid_prev 16-byte byte array representing the immediately preceding
101 * UUID, from which the previous timestamp and counter are
102 * extracted. This may be NULL if the caller does not care
103 * the ascending order of UUIDs within the same timestamp.
104 * This may point to the same location as `uuid_out`; this
105 * function reads the value before writing.
106 * @return One of the `UUIDV7_STATUS_*` codes that describe the
107 * characteristics of generated UUIDs. Callers can usually
108 * ignore the status unless they need to guarantee the
109 * monotonic order of UUIDs or fine-tune the generation
110 * process.
111 */
112static inline int8_t uuidv7_generate(uint8_t *uuid_out, uint64_t unix_ts_ms,
113 const uint8_t *rand_bytes,
114 const uint8_t *uuid_prev) {
115 static const uint64_t MAX_TIMESTAMP = ((uint64_t)1 << 48) - 1;
116 static const uint64_t MAX_COUNTER = ((uint64_t)1 << 42) - 1;
117
118 if (unix_ts_ms > MAX_TIMESTAMP) {
120 }
121
122 int8_t status;
123 uint64_t timestamp = 0;
124 if (uuid_prev == NULL) {
126 timestamp = unix_ts_ms;
127 } else {
128 for (int i = 0; i < 6; i++) {
129 timestamp = (timestamp << 8) | uuid_prev[i];
130 }
131
132 if (unix_ts_ms > timestamp) {
134 timestamp = unix_ts_ms;
135 } else if (unix_ts_ms + 10000 < timestamp) {
136 // ignore prev if clock moves back by more than ten seconds
138 timestamp = unix_ts_ms;
139 } else {
140 // increment prev counter
141 uint64_t counter = uuid_prev[6] & 0x0f; // skip ver
142 counter = (counter << 8) | uuid_prev[7];
143 counter = (counter << 6) | (uuid_prev[8] & 0x3f); // skip var
144 counter = (counter << 8) | uuid_prev[9];
145 counter = (counter << 8) | uuid_prev[10];
146 counter = (counter << 8) | uuid_prev[11];
147
148 if (counter++ < MAX_COUNTER) {
150 uuid_out[6] = counter >> 38; // ver + bits 0-3
151 uuid_out[7] = counter >> 30; // bits 4-11
152 uuid_out[8] = counter >> 24; // var + bits 12-17
153 uuid_out[9] = counter >> 16; // bits 18-25
154 uuid_out[10] = counter >> 8; // bits 26-33
155 uuid_out[11] = counter; // bits 34-41
156 } else {
157 // increment prev timestamp at counter overflow
159 timestamp++;
160 if (timestamp > MAX_TIMESTAMP) {
162 }
163 }
164 }
165 }
166
167 uuid_out[0] = timestamp >> 40;
168 uuid_out[1] = timestamp >> 32;
169 uuid_out[2] = timestamp >> 24;
170 uuid_out[3] = timestamp >> 16;
171 uuid_out[4] = timestamp >> 8;
172 uuid_out[5] = timestamp;
173
174 for (int i = (status == UUIDV7_STATUS_COUNTER_INC) ? 12 : 6; i < 16; i++) {
175 uuid_out[i] = *rand_bytes++;
176 }
177
178 uuid_out[6] = 0x70 | (uuid_out[6] & 0x0f); // set ver
179 uuid_out[8] = 0x80 | (uuid_out[8] & 0x3f); // set var
180
181 return status;
182}
183
184/**
185 * Determines the number of random bytes consumsed by `uuidv7_generate()` from
186 * the `UUIDV7_STATUS_*` code returned.
187 *
188 * @param status `UUIDV7_STATUS_*` code returned by `uuidv7_generate()`.
189 * @return `4` if `status` is `UUIDV7_STATUS_COUNTER_INC` or `10`
190 * otherwise.
191 */
192static inline int uuidv7_status_n_rand_consumed(int8_t status) {
193 return status == UUIDV7_STATUS_COUNTER_INC ? 4 : 10;
194}
195
196/**
197 * Encodes a UUID in the 8-4-4-4-12 hexadecimal string representation.
198 *
199 * @param uuid 16-byte byte array representing the UUID to encode.
200 * @param string_out Character array where the encoded string is stored. Its
201 * length must be 37 (36 digits + NUL) or longer.
202 */
203static inline void uuidv7_to_string(const uint8_t *uuid, char *string_out) {
204 static const char DIGITS[] = "0123456789abcdef";
205 for (int i = 0; i < 16; i++) {
206 uint_fast8_t e = uuid[i];
207 *string_out++ = DIGITS[e >> 4];
208 *string_out++ = DIGITS[e & 15];
209 if (i == 3 || i == 5 || i == 7 || i == 9) {
210 *string_out++ = '-';
211 }
212 }
213 *string_out = '\0';
214}
215
216/**
217 * Decodes the 8-4-4-4-12 hexadecimal string representation of a UUID.
218 *
219 * @param string 37-byte (36 digits + NUL) character array representing the
220 * 8-4-4-4-12 hexadecimal string representation.
221 * @param uuid_out 16-byte byte array where the decoded UUID is stored.
222 * @return Zero on success or non-zero integer on failure.
223 */
224static inline int uuidv7_from_string(const char *string, uint8_t *uuid_out) {
225 for (int i = 0; i < 32; i++) {
226 char c = *string++;
227 // clang-format off
228 uint8_t x = c == '0' ? 0 : c == '1' ? 1 : c == '2' ? 2 : c == '3' ? 3
229 : c == '4' ? 4 : c == '5' ? 5 : c == '6' ? 6 : c == '7' ? 7
230 : c == '8' ? 8 : c == '9' ? 9 : c == 'a' ? 10 : c == 'b' ? 11
231 : c == 'c' ? 12 : c == 'd' ? 13 : c == 'e' ? 14 : c == 'f' ? 15
232 : c == 'A' ? 10 : c == 'B' ? 11 : c == 'C' ? 12 : c == 'D' ? 13
233 : c == 'E' ? 14 : c == 'F' ? 15 : 0xff;
234 // clang-format on
235 if (x == 0xff) {
236 return -1; // invalid digit
237 }
238
239 if ((i & 1) == 0) {
240 uuid_out[i >> 1] = x << 4; // even i => hi 4 bits
241 } else {
242 uuid_out[i >> 1] |= x; // odd i => lo 4 bits
243 }
244
245 if ((i == 7 || i == 11 || i == 15 || i == 19) && (*string++ != '-')) {
246 return -1; // invalid format
247 }
248 }
249 if (*string != '\0') {
250 return -1; // invalid length
251 }
252 return 0; // success
253}
254
255/** @} */
256
257/**
258 * @name High-level APIs that require platform integration
259 *
260 * @{
261 */
262
263/**
264 * Generates a new UUIDv7 with the current Unix time.
265 *
266 * This declaration defines the interface to generate a new UUIDv7 with the
267 * current time, default random number generator, and global shared state
268 * holding the previously generated UUID. Since this single-file library does
269 * not provide platform-specific implementations, users need to prepare a
270 * concrete implementation (if necessary) by integrating a real-time clock,
271 * cryptographically strong random number generator, and shared state storage
272 * available in the target platform.
273 *
274 * @param uuid_out 16-byte byte array where the generated UUID is stored.
275 * @return One of the `UUIDV7_STATUS_*` codes that describe the
276 * characteristics of generated UUIDs or an
277 * implementation-dependent code. Callers can usually ignore
278 * the `UUIDV7_STATUS_*` code unless they need to guarantee the
279 * monotonic order of UUIDs or fine-tune the generation
280 * process. The implementation-dependent code must be out of
281 * the range of `int8_t` and negative if it reports an error.
282 */
283int uuidv7_new(uint8_t *uuid_out);
284
285/**
286 * Generates an 8-4-4-4-12 hexadecimal string representation of new UUIDv7.
287 *
288 * @param string_out Character array where the encoded string is stored. Its
289 * length must be 37 (36 digits + NUL) or longer.
290 * @return Return value of `uuidv7_new()`.
291 * @note Provide a concrete `uuidv7_new()` implementation to enable
292 * this function.
293 */
294static inline int uuidv7_new_string(char *string_out) {
295 uint8_t uuid[16];
296 int result = uuidv7_new(uuid);
297 uuidv7_to_string(uuid, string_out);
298 return result;
299}
300
301/** @} */
302
303#ifdef __cplusplus
304} /* extern "C" { */
305#endif
306
307#endif /* #ifndef UUIDV7_H_BAEDKYFQ */
#define UUIDV7_STATUS_ERR_TIMESTAMP
Indicates that an invalid unix_ts_ms is passed.
Definition uuidv7.h:69
static void uuidv7_to_string(const uint8_t *uuid, char *string_out)
Encodes a UUID in the 8-4-4-4-12 hexadecimal string representation.
Definition uuidv7.h:203
#define UUIDV7_STATUS_COUNTER_INC
Indicates that the counter was incremented because the unix_ts_ms passed was no greater than the prev...
Definition uuidv7.h:54
static int8_t uuidv7_generate(uint8_t *uuid_out, uint64_t unix_ts_ms, const uint8_t *rand_bytes, const uint8_t *uuid_prev)
Generates a new UUIDv7 from the given Unix time, random bytes, and previous UUID.
Definition uuidv7.h:112
#define UUIDV7_STATUS_TIMESTAMP_INC
Indicates that the previous unix_ts_ms was incremented because the counter reached its maximum value.
Definition uuidv7.h:60
#define UUIDV7_STATUS_CLOCK_ROLLBACK
Indicates that the monotonic order of generated UUIDs was broken because the unix_ts_ms passed was le...
Definition uuidv7.h:66
static int uuidv7_status_n_rand_consumed(int8_t status)
Determines the number of random bytes consumsed by uuidv7_generate() from the UUIDV7_STATUS_* code re...
Definition uuidv7.h:192
#define UUIDV7_STATUS_ERR_TIMESTAMP_OVERFLOW
Indicates that the attempt to increment the previous unix_ts_ms failed because it had reached its max...
Definition uuidv7.h:75
static int uuidv7_from_string(const char *string, uint8_t *uuid_out)
Decodes the 8-4-4-4-12 hexadecimal string representation of a UUID.
Definition uuidv7.h:224
#define UUIDV7_STATUS_NEW_TIMESTAMP
Indicates that the unix_ts_ms passed was used because it was greater than the previous one.
Definition uuidv7.h:48
#define UUIDV7_STATUS_UNPRECEDENTED
Indicates that the unix_ts_ms passed was used because no preceding UUID was specified.
Definition uuidv7.h:42
static int uuidv7_new_string(char *string_out)
Generates an 8-4-4-4-12 hexadecimal string representation of new UUIDv7.
Definition uuidv7.h:294
int uuidv7_new(uint8_t *uuid_out)
Generates a new UUIDv7 with the current Unix time.