• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bkwoka / UUIDv7 / 23068604513

13 Mar 2026 08:09PM UTC coverage: 98.693% (-0.1%) from 98.817%
23068604513

push

github

bkwoka
bump: version 1.2.1

- Updated version information in library.json, library.properties, and src/UUID7.h
- Enhanced CHANGELOG.md with fixes for millis() overflow and ESP32 native timers
- Documented new STM32 fleet-unique IDs example and EasyUUID7 API reference

302 of 306 relevant lines covered (98.69%)

67.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.51
/src/UUID7.cpp
1
// SPDX-License-Identifier: MIT
2
// Copyright (c) 2026 bkwoka
3
// Repository: https://github.com/bkwoka/UUIDv7
4

5
#include "UUID7.h"
6
#include <string.h>
7

8
// --- PLATFORM INCLUDES & CRITICAL SECTION GUARD ---
9
#if defined(ARDUINO)
10
#include <Arduino.h>
11
#endif
12

13
// Forward declaration of platform specific globals if needed
14
#if defined(PLATFORMIO_ESP32) || defined(ARDUINO_ARCH_ESP32)
15
#include "esp_system.h"
16
#include "esp_timer.h"
17
// Thread safety guard for ESP32 multi-core environments
18
static portMUX_TYPE _uuid_spinlock = portMUX_INITIALIZER_UNLOCKED;
19
#elif defined(PLATFORMIO_ESP8266) || defined(ESP8266)
20
extern "C" {
21
#include "user_interface.h"
22
}
23
#elif defined(ARDUINO_ARCH_AVR) || defined(__AVR__)
24
#include <util/atomic.h>
25
#elif defined(ARDUINO_ARCH_RP2040)
26
// Support for RP2040 multi-core environments using hardware spinlocks.
27
#include <hardware/structs/rosc.h>
28
#if defined(PICO_SDK_VERSION_MAJOR)
29
#include <hardware/sync.h>
30
static spin_lock_t *_uuid_rp2040_spinlock = nullptr;
31
__attribute__((constructor)) static void _uuid_init_spinlock() {
32
  // Attempt to claim a hardware spinlock for thread-safe access.
33
  int lock_num = spin_lock_claim_unused(false);
34
  if (lock_num >= 0) {
35
    _uuid_rp2040_spinlock = spin_lock_init(lock_num);
36
  }
37
}
38
#endif
39
#elif defined(ARDUINO_ARCH_STM32)
40
// STM32 HAL is integrated via the Arduino selection.
41
#elif defined(PLATFORMIO_NATIVE)
42
#include <chrono>
43
#include <mutex>
44
#include <random>
45
#include <thread>
46
static std::mutex _uuid_mutex;
47
static inline void yield() { std::this_thread::yield(); }
18✔
48
#else
49
#if !defined(ARDUINO)
50
static inline void yield() {} // Fallback for non-Arduino bare-metal platforms
51
#endif
52
#endif
53

54
// RAII Guard for Critical Sections
55
class UUID7Guard {
56
public:
57
  UUID7Guard(void (*lock_cb)(void), void (*unlock_cb)(void))
142✔
58
      : _lock_cb(lock_cb), _unlock_cb(unlock_cb) {
142✔
59
    if (_lock_cb) {
142✔
60
      // Use user-provided lock callback if available
61
      _lock_cb();
4✔
62
      return;
4✔
63
    }
64
#if defined(PLATFORMIO_ESP32) || defined(ARDUINO_ARCH_ESP32)
65
    portENTER_CRITICAL_SAFE(&_uuid_spinlock);
66
#elif defined(ARDUINO_ARCH_AVR) || defined(__AVR__)
67
    _sreg = SREG;
68
    cli();
69
#elif defined(ARDUINO_ARCH_RP2040) && defined(PICO_SDK_VERSION_MAJOR)
70
    if (_uuid_rp2040_spinlock) {
71
      _saved_irq = spin_lock_blocking(_uuid_rp2040_spinlock);
72
    } else {
73
      noInterrupts();
74
    }
75
#elif defined(PLATFORMIO_ESP8266) || defined(ESP8266) ||                       \
76
    defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_STM32)
77
    noInterrupts();
78
#elif defined(PLATFORMIO_NATIVE)
79
    _uuid_mutex.lock();
138✔
80
#endif
81
  }
82

83
  ~UUID7Guard() {
280✔
84
    if (_unlock_cb) {
142✔
85
      // Use user-provided unlock callback if available
86
      _unlock_cb();
4✔
87
      return;
4✔
88
    }
89
#if defined(PLATFORMIO_ESP32) || defined(ARDUINO_ARCH_ESP32)
90
    portEXIT_CRITICAL_SAFE(&_uuid_spinlock);
91
#elif defined(ARDUINO_ARCH_AVR) || defined(__AVR__)
92
    SREG = _sreg;
93
#elif defined(ARDUINO_ARCH_RP2040) && defined(PICO_SDK_VERSION_MAJOR)
94
    if (_uuid_rp2040_spinlock) {
95
      spin_unlock(_uuid_rp2040_spinlock, _saved_irq);
96
    } else {
97
      interrupts();
98
    }
99
#elif defined(PLATFORMIO_ESP8266) || defined(ESP8266) ||                       \
100
    defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_STM32)
101
    interrupts();
102
#elif defined(PLATFORMIO_NATIVE)
103
    _uuid_mutex.unlock();
138✔
104
#endif
105
  }
142✔
106

107
  UUID7Guard(const UUID7Guard &) = delete;
108
  UUID7Guard &operator=(const UUID7Guard &) = delete;
109

110
private:
111
  void (*_lock_cb)(void);
112
  void (*_unlock_cb)(void);
113
#if defined(ARDUINO_ARCH_AVR) || defined(__AVR__)
114
  uint8_t _sreg;
115
#elif defined(ARDUINO_ARCH_RP2040) && defined(PICO_SDK_VERSION_MAJOR)
116
  uint32_t _saved_irq;
117
#endif
118
};
119

120
// --- INTERNAL HELPERS ---
121
#if defined(ARDUINO_ARCH_AVR) || defined(__AVR__)
122
static inline uint32_t uuid_mix32(uint32_t k) {
123
  k ^= k >> 16;
124
  k *= 0x85ebca6b;
125
  k ^= k >> 13;
126
  k *= 0xc2b2ae35;
127
  k ^= k >> 16;
128
  return k;
129
}
130
static uint32_t uuid_xorshift32(uint32_t *state) {
131
  uint32_t x = *state;
132
  x ^= x << 13;
133
  x ^= x >> 17;
134
  x ^= x << 5;
135
  *state = x;
136
  return x;
137
}
138
#endif
139

140
// --- CLASS IMPLEMENTATION ---
141

142
UUID7::UUID7(fill_random_fn rng, void *rng_ctx, now_ms_fn now,
76✔
143
             void *now_ctx) noexcept
76✔
144
    : _version(UUID_VERSION_7), _overflowPolicy(UUID_OVERFLOW_FAIL_FAST),
76✔
145
      _rng(rng),
76✔
146
      // _rng_ctx defaults to 'this' so the static default_fill_random can
147
      // access instance variables (like _entropyAnalogPin) via a void* cast.
148
      _rng_ctx(rng ? rng_ctx : this), _now(now), _now_ctx(now_ctx),
76✔
149
#ifndef UUID7_OPTIMIZE_SIZE
150
      _last_ts_ms(0),
38✔
151
#endif
152
      _load(nullptr), _save(nullptr), _storage_ctx(nullptr),
76✔
153
      _save_interval_ms(10000), _last_saved_ts_ms(0), _entropy_mixer(0),
76✔
154
      _regressionThresholdMs(10000), _lock_cb(nullptr), _unlock_cb(nullptr) {
76✔
155
  memset(_b, 0, sizeof(_b));
76✔
156
#ifdef UUID7_OPTIMIZE_SIZE
157
  memset(_last_ts_48, 0, 6);
38✔
158
#endif
159

160
#if defined(ARDUINO_ARCH_AVR) || defined(__AVR__)
161
#if defined(A0)
162
  _entropyAnalogPin = A0;
163
#else
164
  // Default to pin 14 (A0) for ATmega328P based boards (Uno/Nano)
165
  _entropyAnalogPin = 14;
166
#endif
167
#else
168
  _entropyAnalogPin = -1;
76✔
169
#endif
170
}
76✔
171

172
void UUID7::setVersion(UUIDVersion v) { _version = v; }
10✔
173

174
void UUID7::setStorage(uuid_load_fn load_fn, uuid_save_fn save_fn, void *ctx,
8✔
175
                       uint32_t auto_save_interval_ms) {
176
  _load = load_fn;
8✔
177
  _save = save_fn;
8✔
178
  _storage_ctx = ctx;
8✔
179
  _save_interval_ms = auto_save_interval_ms;
8✔
180
}
8✔
181

182
void UUID7::load() {
6✔
183
  if (_load) {
6✔
184
    uint64_t saved_ts = _load(_storage_ctx);
6✔
185
    if (saved_ts > 0) {
6✔
186
      _last_saved_ts_ms = saved_ts;
4✔
187
      uint64_t target = saved_ts + _save_interval_ms;
4✔
188

189
#ifdef UUID7_OPTIMIZE_SIZE
190
      _last_ts_48[5] = target & 0xFF;
2✔
191
      target >>= 8;
2✔
192
      _last_ts_48[4] = target & 0xFF;
2✔
193
      target >>= 8;
2✔
194
      _last_ts_48[3] = target & 0xFF;
2✔
195
      target >>= 8;
2✔
196
      _last_ts_48[2] = target & 0xFF;
2✔
197
      target >>= 8;
2✔
198
      _last_ts_48[1] = target & 0xFF;
2✔
199
      target >>= 8;
2✔
200
      _last_ts_48[0] = target & 0xFF;
2✔
201
#else
202
      _last_ts_ms = target;
2✔
203
#endif
204
    }
205
  }
206
}
6✔
207

208
void UUID7::default_fill_random(uint8_t *dest, size_t len, void *ctx) noexcept {
78✔
209
#if defined(PLATFORMIO_ESP32) || defined(ARDUINO_ARCH_ESP32)
210
  (void)ctx;
211
  // ESP32 Hardware RNG
212
  size_t i = 0;
213
  while (i + 4 <= len) {
214
    uint32_t r = esp_random();
215
    memcpy(dest + i, &r, 4);
216
    i += 4;
217
  }
218
  if (i < len) {
219
    uint32_t r = esp_random();
220
    while (i < len) {
221
      dest[i++] = (uint8_t)(r & 0xFF);
222
      r >>= 8;
223
    }
224
  }
225

226
#elif defined(PLATFORMIO_ESP8266) || defined(ESP8266)
227
  (void)ctx;
228
  // ESP8266 Hardware RNG (WDEV)
229
  os_get_random((unsigned char *)dest, len);
230

231
#elif defined(ARDUINO_ARCH_RP2040)
232
  (void)ctx;
233
  // RP2040 Hardware ROSC (Ring Oscillator)
234
  // Supported on both Earle Philhower and MBED cores.
235
  // Provides high-entropy jitter but requires multiple clock cycles per bit.
236
  for (size_t i = 0; i < len; i++) {
237
    uint8_t r = 0;
238
    for (int bit = 0; bit < 8; bit++) {
239
      r = (r << 1) | (rosc_hw->randombit & 1);
240
    }
241
    dest[i] = r;
242
  }
243

244
#elif defined(ARDUINO_ARCH_STM32)
245
  (void)ctx;
246
  /*
247
   * STM32 Entropy Strategy:
248
   * Combines multiple hardware/temporal sources to maximize uniqueness
249
   * even on devices lacking a dedicated TRNG peripheral.
250
   */
251

252
  // 1. Initial platform-provided randomness
253
  for (size_t i = 0; i < len; i++) {
254
    dest[i] = (uint8_t)random(256);
255
  }
256

257
  // 2. Hardware-Unique Identification (UID Mix)
258
  // Incorporates the 96-bit Unique Device ID to ensure spatial uniqueness.
259
  uint32_t uid[3];
260
  uid[0] = HAL_GetUIDw0();
261
  uid[1] = HAL_GetUIDw1();
262
  uid[2] = HAL_GetUIDw2();
263

264
  for (size_t i = 0; i < len; i++) {
265
    dest[i] ^= (uint8_t)(uid[i % 3] >> ((i % 4) * 8));
266
  }
267

268
  // 3. Jitter Mix: Temporal entropy from high-resolution SysTick value
269
  uint32_t tick = SysTick->VAL;
270
  dest[0] ^= (uint8_t)(tick & 0xFF);
271
  dest[len - 1] ^= (uint8_t)((tick >> 8) & 0xFF);
272

273
#elif defined(ARDUINO_ARCH_AVR) || defined(__AVR__)
274
  // ctx is intentionally used here to access _entropyAnalogPin via UUID7* cast.
275
#warning                                                                       \
276
    "UUID7: Using fallback entropy (ADC noise + Clock Jitter). Not cryptographically secure."
277

278
  static uint32_t rng_state = 0;
279

280
  if (rng_state == 0) {
281
    uint32_t entropy = 0;
282
    uint8_t stack_var;
283
    entropy ^= uuid_mix32((uint16_t)(uintptr_t)&stack_var);
284
    entropy ^= uuid_mix32((uint32_t)((uintptr_t)default_fill_random));
285
    entropy ^= uuid_mix32(micros());
286

287
    int16_t analog_pin =
288
        ctx ? static_cast<UUID7 *>(ctx)->_entropyAnalogPin : -1;
289
    if (analog_pin >= 0) {
290
      for (int i = 0; i < 4; i++) {
291
        (void)analogRead(analog_pin);
292
      }
293
      for (int i = 0; i < 8; i++) {
294
        unsigned long t_start = micros();
295
        uint16_t val = analogRead(analog_pin);
296
        unsigned long t_end = micros();
297
        entropy = uuid_mix32(entropy ^ val);
298
        entropy = uuid_mix32(entropy ^ (t_end - t_start));
299
        delayMicroseconds(10 + (val & 0x0F));
300
      }
301
    }
302

303
    for (int i = 0; i < 4; i++) {
304
      unsigned long t1 = micros();
305
      for (volatile uint8_t k = 0; k < (uint8_t)((t1 & 0x07) * 10); k++) {
306
        __asm__("nop");
307
      }
308
      unsigned long t2 = micros();
309
      entropy ^= uuid_mix32(t2 ^ t1);
310
    }
311

312
    if (entropy == 0)
313
      entropy = 0xBADC0FFE;
314
    rng_state = entropy;
315
  }
316

317
  size_t i = 0;
318
  while (i < len) {
319
    rng_state ^= (uint32_t)micros();
320
    uint32_t r = uuid_xorshift32(&rng_state);
321
    for (int b = 0; b < 4 && i < len; b++) {
322
      dest[i++] = (uint8_t)(r & 0xFF);
323
      r >>= 8;
324
    }
325
  }
326

327
#elif defined(PLATFORMIO_NATIVE)
328
  (void)ctx;
329
  static std::random_device rd;
78✔
330
  static std::mt19937_64 eng(rd());
78✔
331
  static std::uniform_int_distribution<uint8_t> dist(0, 255);
78✔
332
  for (size_t k = 0; k < len; k++) {
1,326✔
333
    dest[k] = dist(eng);
1,248✔
334
  }
335
#else
336
  (void)ctx;
337
#ifdef ARDUINO
338
#warning                                                                       \
339
    "UUID7: Using Arduino random() fallback. Ensure it is seeded with randomSeed() for uniqueness!"
340
  for (size_t i = 0; i < len; i++) {
341
    dest[i] = (uint8_t)(random(256));
342
  }
343
#else
344
// Minimal LCG fallback for environments without a standard RNG source.
345
#warning                                                                       \
346
    "UUID7: Using insecure LCG fallback. Not recommended for production use."
347
  static uint32_t s = 0x12345678U;
348
  for (size_t i = 0; i < len; ++i) {
349
    s = 1664525U * s + 1013904223U;
350
    dest[i] = (uint8_t)(s >> 24);
351
  }
352
#endif
353
#endif
354
}
78✔
355

356
uint64_t UUID7::default_now_ms(void *ctx) noexcept {
20✔
357
  (void)ctx;
358
#if defined(PLATFORMIO_NATIVE)
359
  using namespace std::chrono;
360
  return (uint64_t)duration_cast<milliseconds>(
20✔
361
             system_clock::now().time_since_epoch())
40✔
362
      .count();
20✔
363
#elif defined(PLATFORMIO_ESP32) || defined(ARDUINO_ARCH_ESP32)
364
  // ESP32 provides a native 64-bit microsecond timer. Overflow occurs in
365
  // ~292,000 years.
366
  return (uint64_t)(esp_timer_get_time() / 1000ULL);
367
#elif defined(ARDUINO)
368
  // Fallback for AVR, ESP8266, STM32, and RP2040.
369
  // Standard millis() returns a uint32_t which overflows after
370
  // approximately 49.7 days. A static accumulator is used to extend the value
371
  // to 64 bits.
372
  //
373
  // NOTE: These static variables are accessed outside the spinlock.
374
  // On single-core platforms (AVR, ESP8266, STM32) this is safe.
375
  // On RP2040 dual-core, a concurrent wraparound detection could cause
376
  // s_epoch_offset to be incremented twice (~once per 49 days). The result
377
  // is a ~99-day forward jump, which triggers clock-regression fallback
378
  // (UUIDv4 for that call). Probability is negligible in practice.
379
  static uint32_t s_prev_ms = 0;
380
  static uint64_t s_epoch_offset = 0;
381

382
  uint32_t now = millis();
383
  if (now < s_prev_ms) {
384
    // Increment epoch offset by 2^32 milliseconds
385
    s_epoch_offset += 0x100000000ULL;
386
  }
387
  s_prev_ms = now;
388

389
  return s_epoch_offset + now;
390
#else
391
  return 0;
392
#endif
393
}
394

395
bool UUID7::_incrementRandom() noexcept {
18✔
396
  // Increment 74-bit random field (bytes 15 down to 6).
397
  // Respects version (v7) and variant (RFC) bit masking.
398
  for (int i = 15; i >= 9; i--) {
49✔
399
    if (++_b[i] != 0)
45✔
400
      return true;
14✔
401
  }
402

403
  // Increment byte 8 while preserving variant bits (top 2 bits)
404
  uint8_t b8 = _b[8] & 0x3F;
4✔
405
  b8++;
4✔
406
  _b[8] = (_b[8] & 0xC0) | (b8 & 0x3F);
4✔
407
  if ((b8 & 0x40) == 0)
4✔
408
    return true; // No overflow if carry didn't reach bit 6
×
409

410
  // Carry to byte 7
411
  if (++_b[7] != 0)
4✔
412
    return true;
×
413

414
  // Increment byte 6 while preserving version bits (top 4 bits)
415
  uint8_t b6 = _b[6] & 0x0F;
4✔
416
  b6++;
4✔
417
  _b[6] = (_b[6] & 0xF0) | (b6 & 0x0F);
4✔
418

419
  if ((b6 & 0x10) == 0)
4✔
420
    return true; // No overflow if carry didn't reach bit 4
×
421

422
  return false;
4✔
423
}
424
bool UUID7::generate() {
110✔
425
  fill_random_fn rng = _rng ? _rng : &UUID7::default_fill_random;
110✔
426

427
  if (_version == UUID_VERSION_4) {
110✔
428
    rng(_b, sizeof(_b), _rng_ctx);
10✔
429
    uint8_t sum = 0;
10✔
430
    for (size_t i = 0; i < sizeof(_b); i++)
170✔
431
      sum |= _b[i];
160✔
432
    if (sum == 0)
10✔
433
      return false;
2✔
434

435
    _b[6] = (_b[6] & 0x0F) | 0x40; // Set UUID version to 4 (Random)
8✔
436
    _b[8] = (_b[8] & 0x3F) | 0x80; // Set variant to RFC 4122 (10b)
8✔
437
    return true;
8✔
438
  }
439

440
  now_ms_fn now_func = _now ? _now : &UUID7::default_now_ms;
100✔
441
  bool overflow_state = false;
100✔
442

443
  while (true) {
444
    uint8_t temp_rand[16];
445
    rng(temp_rand, sizeof(temp_rand), _rng_ctx);
118✔
446

447
    uint8_t sum = 0;
118✔
448
    for (size_t i = 0; i < sizeof(temp_rand); i++)
2,006✔
449
      sum |= temp_rand[i];
1,888✔
450
    if (sum == 0)
118✔
451
      return false;
100✔
452

453
    // Fetch current time outside the critical section to prevent potential
454
    // deadlocks with time providers that use blocking I/O (e.g., I2C RTC or NTP
455
    // sync).
456
    uint64_t now_ms = now_func(_now_ctx);
116✔
457
    if (now_ms == 0)
116✔
458
      return false;
2✔
459

460
#ifdef UUID7_OPTIMIZE_SIZE
461
    // Pre-calculate 48-bit timestamp array outside the critical section for
462
    // performance.
463
    uint8_t now_48[6];
464
    uint64_t tmp = now_ms & 0x0000FFFFFFFFFFFFULL;
57✔
465
    now_48[5] = tmp & 0xFF;
57✔
466
    tmp >>= 8;
57✔
467
    now_48[4] = tmp & 0xFF;
57✔
468
    tmp >>= 8;
57✔
469
    now_48[3] = tmp & 0xFF;
57✔
470
    tmp >>= 8;
57✔
471
    now_48[2] = tmp & 0xFF;
57✔
472
    tmp >>= 8;
57✔
473
    now_48[1] = tmp & 0xFF;
57✔
474
    tmp >>= 8;
57✔
475
    now_48[0] = tmp & 0xFF;
57✔
476
#endif
477

478
    bool save_needed = false;
114✔
479
    uint64_t ts_to_save = 0;
114✔
480
    bool success = false;
114✔
481
    bool overflow_occurred = false;
114✔
482

483
    {
484
      // Ensure thread-safe access to monotonicity state
485
      UUID7Guard lock(_lock_cb, _unlock_cb);
114✔
486

487
      if (_entropy_mixer != 0) {
114✔
488
        for (int i = 0; i < 8; i++) {
18✔
489
          temp_rand[8 + i] ^= (uint8_t)(_entropy_mixer >> (i * 8));
16✔
490
        }
491
      }
492

493
#ifdef UUID7_OPTIMIZE_SIZE
494
      int cmp = memcmp(now_48, _last_ts_48, 6);
57✔
495
      bool major_regression = false;
57✔
496

497
      if (cmp < 0) {
57✔
498
        uint64_t last_ts = 0;
5✔
499
        for (int i = 0; i < 6; i++)
35✔
500
          last_ts = (last_ts << 8) | _last_ts_48[i];
30✔
501
        if (now_ms + _regressionThresholdMs < last_ts) {
5✔
502
          major_regression = true;
2✔
503
        } else {
504
          // Minor clock regression or race condition: Clamp to the last seen
505
          // timestamp to maintain monotonicity within the same millisecond
506
          // window.
507
          memcpy(now_48, _last_ts_48, 6);
3✔
508
          cmp = 0; // Force entry into same-millisecond logic
3✔
509
        }
510
      }
511

512
      if (major_regression) {
57✔
513
        // Major clock regression: Fall back to UUIDv4-like random generation
514
        // as per RFC 9562 recommendations to prevent collisions.
515
        temp_rand[6] = (temp_rand[6] & 0x0F) | 0x40; // v4 bits
2✔
516
        temp_rand[8] = (temp_rand[8] & 0x3F) | 0x80; // variant bits
2✔
517
        memcpy(_b, temp_rand, 16);
2✔
518
        return true;
2✔
519
      }
520

521
      if (cmp > 0) {
55✔
522
        memcpy(_last_ts_48, now_48, 6);
36✔
523
        memcpy(_b, temp_rand, 16);
36✔
524
        success = true;
36✔
525
        overflow_state = false;
36✔
526
      } else {
527
        if (overflow_state) {
19✔
528
          overflow_occurred = true;
8✔
529
        } else {
530
          bool initialized = (_b[6] & 0xF0) == 0x70;
11✔
531
          if (!initialized) {
11✔
532
            memcpy(_b, temp_rand, 16);
2✔
533
            success = true;
2✔
534
          } else {
535
            // Increment internal counter for same-millisecond monotonicity.
536
            if (!_incrementRandom()) {
9✔
537
              overflow_occurred = true;
2✔
538
              overflow_state = true;
2✔
539
            } else {
540
              success = true;
7✔
541
            }
542
          }
543
        }
544
      }
545
      if (success) {
55✔
546
        memcpy(_b, _last_ts_48, 6);
45✔
547
      }
548
#else
549
      if (now_ms + _regressionThresholdMs < _last_ts_ms) {
57✔
550
        // Major clock regression: Fall back to UUIDv4-like random generation.
551
        temp_rand[6] = (temp_rand[6] & 0x0F) | 0x40; // v4 bits
2✔
552
        temp_rand[8] = (temp_rand[8] & 0x3F) | 0x80; // variant bits
2✔
553
        memcpy(_b, temp_rand, 16);
2✔
554
        return true;
2✔
555
      }
556

557
      if (now_ms < _last_ts_ms) {
55✔
558
        // Minor clock regression: Clamp to the last seen timestamp.
559
        now_ms = _last_ts_ms;
3✔
560
      }
561

562
      if (now_ms > _last_ts_ms) {
55✔
563
        _last_ts_ms = now_ms;
36✔
564
        memcpy(_b, temp_rand, 16);
36✔
565
        success = true;
36✔
566
        overflow_state = false;
36✔
567
      } else {
568
        // Process monotonicity for the same millisecond window.
569
        if (overflow_state) {
19✔
570
          overflow_occurred = true;
8✔
571
        } else {
572
          bool initialized = (_b[6] & 0xF0) == 0x70;
11✔
573
          if (!initialized) {
11✔
574
            memcpy(_b, temp_rand, 16);
2✔
575
            success = true;
2✔
576
          } else {
577
            // Attempt to increment the sub-millisecond counter for
578
            // monotonicity.
579
            if (!_incrementRandom()) {
9✔
580
              overflow_occurred = true;
2✔
581
              overflow_state = true;
2✔
582
            } else {
583
              success = true;
7✔
584
            }
585
          }
586
        }
587
      }
588
      if (success) {
55✔
589
        uint64_t ts_copy = now_ms;
45✔
590
        ts_copy &= 0x0000FFFFFFFFFFFFULL;
45✔
591
        _b[5] = (uint8_t)(ts_copy & 0xFF);
45✔
592
        ts_copy >>= 8;
45✔
593
        _b[4] = (uint8_t)(ts_copy & 0xFF);
45✔
594
        ts_copy >>= 8;
45✔
595
        _b[3] = (uint8_t)(ts_copy & 0xFF);
45✔
596
        ts_copy >>= 8;
45✔
597
        _b[2] = (uint8_t)(ts_copy & 0xFF);
45✔
598
        ts_copy >>= 8;
45✔
599
        _b[1] = (uint8_t)(ts_copy & 0xFF);
45✔
600
        ts_copy >>= 8;
45✔
601
        _b[0] = (uint8_t)(ts_copy & 0xFF);
45✔
602
      }
603
#endif
604
      if (success && _save &&
110✔
605
          (now_ms > _last_saved_ts_ms + _save_interval_ms)) {
12✔
606
        save_needed = true;
6✔
607
        _last_saved_ts_ms = now_ms;
6✔
608
        ts_to_save = now_ms;
6✔
609
      }
610

611
      if (success) {
110✔
612
        _b[6] = (_b[6] & 0x0F) | ((uint8_t)_version << 4);
90✔
613
        _b[8] = (_b[8] & 0x3F) | 0x80;
90✔
614
      }
615
    }
114✔
616

617
    if (success) {
110✔
618
      if (save_needed && _save) {
90✔
619
        _save(ts_to_save, _storage_ctx);
6✔
620
      }
621
      return true;
90✔
622
    }
623

624
    if (overflow_occurred) {
20✔
625
      if (_overflowPolicy == UUID_OVERFLOW_FAIL_FAST) {
20✔
626
        return false;
2✔
627
      } else {
628
#if defined(ARDUINO)
629
        delay(
630
            1); // On RTOS platforms (ESP32/RP2040) this yields to the scheduler
631
#else
632
        yield();
18✔
633
#endif
634
        continue;
18✔
635
      }
636
    }
637
    return false;
×
638
  }
18✔
639
}
640

641
bool UUID7::toString(char *out, size_t buflen, bool uppercase,
30✔
642
                     bool dashes) const noexcept {
643
  size_t required = dashes ? 37 : 33;
30✔
644
  if (!out || buflen < required)
30✔
645
    return false;
4✔
646

647
  static const char hexLower[] = "0123456789abcdef";
648
  static const char hexUpper[] = "0123456789ABCDEF";
649
  const char *hex = uppercase ? hexUpper : hexLower;
26✔
650

651
  // Create a local thread-safe snapshot of the internal buffer.
652
  uint8_t local_b[16];
653
  {
654
    UUID7Guard lock(_lock_cb, _unlock_cb);
26✔
655
    memcpy(local_b, _b, 16);
26✔
656
  }
26✔
657

658
  const uint8_t *p = local_b;
26✔
659
  char *s = out;
26✔
660

661
  // Fast hex conversion with optional hyphenation
662
  for (int i = 0; i < 16; i++) {
442✔
663
    if (dashes) {
416✔
664
      if (i == 4 || i == 6 || i == 8 || i == 10) {
320✔
665
        *s++ = '-';
80✔
666
      }
667
    }
668
    *s++ = hex[(*p >> 4) & 0x0F];
416✔
669
    *s++ = hex[*p++ & 0x0F];
416✔
670
  }
671

672
  *s = '\0';
26✔
673
  return true;
26✔
674
}
675

676
bool UUID7::parseFromString(const char *str, uint8_t out[16]) noexcept {
26✔
677
  if (!str || !out)
26✔
678
    return false;
4✔
679

680
  size_t len = strlen(str);
22✔
681
  bool dashed;
682

683
  if (len == 36) {
22✔
684
    dashed = true;
12✔
685
  } else if (len == 32) {
10✔
686
    dashed = false;
2✔
687
  } else {
688
    return false; // Invalid length
8✔
689
  }
690

691
  const char *p = str;
14✔
692

693
  auto hexval = [](char c) -> int {
400✔
694
    if (c >= '0' && c <= '9')
400✔
695
      return c - '0';
282✔
696
    if (c >= 'a' && c <= 'f')
118✔
697
      return 10 + c - 'a';
97✔
698
    if (c >= 'A' && c <= 'F')
21✔
699
      return 10 + c - 'A';
19✔
700
    return -1;
2✔
701
  };
702

703
  for (int i = 0; i < 16; i++) {
212✔
704
    // Skip dashes at specific positions (only if format is dashed)
705
    // UUID structure: 4-2-2-2-6 bytes
706
    // Byte indices: 4, 6, 8, 10 start new groups
707
    if (dashed && (i == 4 || i == 6 || i == 8 || i == 10)) {
202✔
708
      if (*p != '-')
42✔
709
        return false; // Strict format check
2✔
710
      p++;
40✔
711
    }
712

713
    int hi = hexval(*p++);
200✔
714
    int lo = hexval(*p++);
200✔
715

716
    if (hi < 0 || lo < 0)
200✔
717
      return false; // Invalid char
2✔
718

719
    out[i] = (uint8_t)((hi << 4) | lo);
198✔
720
  }
721

722
  return true;
10✔
723
}
724

725
uint64_t UUID7::getTimestamp() const noexcept {
4✔
726
  if (!isV7())
4✔
727
    return 0;
2✔
728
  uint64_t ts = 0;
2✔
729
  uint8_t snap[6];
730
  {
731
    UUID7Guard lock(_lock_cb, _unlock_cb);
2✔
732
    memcpy(snap, _b, 6);
2✔
733
  }
2✔
734
  for (int i = 0; i < 6; i++) {
14✔
735
    ts = (ts << 8) | snap[i];
12✔
736
  }
737
  return ts;
2✔
738
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc