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