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

bkwoka / UUIDv7 / 23087208152

14 Mar 2026 11:37AM UTC coverage: 98.413% (-0.2%) from 98.616%
23087208152

push

github

bkwoka
Fix: enhance RNG fail-fast (0xFF detection) and secure STM32 random() for RTOS

14 of 14 new or added lines in 1 file covered. (100.0%)

2 existing lines in 1 file now uncovered.

310 of 315 relevant lines covered (98.41%)

82.77 hits per line

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

97.16
/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 "UUID7Codec.h"
7
#include "UUID7Guard.h"
8
#include <string.h>
9

10
#if defined(PLATFORMIO_ESP32) || defined(ARDUINO_ARCH_ESP32)
11
portMUX_TYPE _uuid_spinlock = portMUX_INITIALIZER_UNLOCKED;
12
#elif defined(ARDUINO_ARCH_RP2040) && defined(PICO_SDK_VERSION_MAJOR)
13
spin_lock_t *_uuid_rp2040_spinlock = nullptr;
14
__attribute__((constructor)) static void _uuid_init_spinlock() {
15
  int lock_num = spin_lock_claim_unused(false);
16
  if (lock_num >= 0) {
17
    _uuid_rp2040_spinlock = spin_lock_init(lock_num);
18
  }
19
}
20
#elif defined(PLATFORMIO_NATIVE)
21
#include <thread>
22
std::mutex _uuid_mutex;
23
static inline void yield() { std::this_thread::yield(); }
18✔
24
#else
25
#if !defined(ARDUINO)
26
static inline void yield() {}
27
#endif
28
#endif
29

30

31

32
UUID7::UUID7(fill_random_fn rng, void *rng_ctx, now_ms_fn now,
78✔
33
             void *now_ctx) noexcept
78✔
34
    : _version(UUID_VERSION_7), _overflowPolicy(UUID_OVERFLOW_FAIL_FAST),
78✔
35
      _rng(rng),
78✔
36
      // Provide instance context to static RNG function for accessing fallback entropy parameters
37
      _rng_ctx(rng ? rng_ctx : this), _now(now), _now_ctx(now_ctx),
78✔
38
      _entropy_mixer(0),
78✔
39
      _regressionThresholdMs(10000), _lock_cb(nullptr), _unlock_cb(nullptr) {
78✔
40
  memset(_b, 0, sizeof(_b));
78✔
41

42
#if defined(ARDUINO_ARCH_AVR) || defined(__AVR__)
43
#if defined(A0)
44
  _entropyAnalogPin = A0;
45
#else
46
  // Default to pin 14 (A0) for ATmega328P based boards (Uno/Nano)
47
  _entropyAnalogPin = 14;
48
#endif
49
#else
50
  _entropyAnalogPin = -1;
78✔
51
#endif
52
}
78✔
53

54
void UUID7::setVersion(UUIDVersion v) { _version = v; }
10✔
55

56
void UUID7::setStorage(uuid_load_fn load_fn, uuid_save_fn save_fn, void *ctx,
8✔
57
                       uint32_t auto_save_interval_ms) {
58
  _persistence.load = load_fn;
8✔
59
  _persistence.save = save_fn;
8✔
60
  _persistence.ctx = ctx;
8✔
61
  _persistence.interval_ms = auto_save_interval_ms;
8✔
62
}
8✔
63

64
void UUID7::load() {
6✔
65
  if (_persistence.load) {
6✔
66
    uint64_t saved_ts = _persistence.load(_persistence.ctx);
6✔
67
    if (saved_ts > 0) {
6✔
68
      _persistence.last_saved_ms = saved_ts;
4✔
69
      uint64_t target = saved_ts + _persistence.interval_ms;
4✔
70

71
      _tsState.set(target);
4✔
72
    }
73
  }
74
}
6✔
75

76

77

78
bool UUID7::_incrementRandom() noexcept {
18✔
79
  // Continuously increment the random section, respecting variant/version boundaries.
80
  for (int i = 15; i >= 9; i--) {
48✔
81
    if (++_b[i] != 0)
44✔
82
      return true;
14✔
83
  }
84

85
  // Increment byte 8 (variant byte), preserving RFC variant flags (10x)
86
  uint8_t b8 = _b[8] & 0x3F;
4✔
87
  b8++;
4✔
88
  _b[8] = (_b[8] & 0xC0) | (b8 & 0x3F);
4✔
89
  if ((b8 & 0x40) == 0)
4✔
90
    return true; // No overflow if carry didn't reach bit 6
×
91

92

93
  if (++_b[7] != 0)
4✔
94
    return true;
×
95

96
  // Increment byte 6 (version byte), preserving UUID version flags (0111)
97
  uint8_t b6 = _b[6] & 0x0F;
4✔
98
  b6++;
4✔
99
  _b[6] = (_b[6] & 0xF0) | (b6 & 0x0F);
4✔
100

101
  if ((b6 & 0x10) == 0)
4✔
102
    return true; // No overflow if carry didn't reach bit 4
×
103

104
  return false;
4✔
105
}
106
bool UUID7::generate() {
112✔
107
  fill_random_fn rng = _rng ? _rng : &UUID7::default_fill_random;
112✔
108

109
  if (_version == UUID_VERSION_4) {
112✔
110
    uint8_t temp_rand[16];
111
    rng(temp_rand, sizeof(temp_rand), _rng_ctx);
10✔
112
    uint8_t sum_or = 0;
10✔
113
    uint8_t sum_and = 0xFF;
10✔
114
    for (size_t i = 0; i < sizeof(temp_rand); i++) {
170✔
115
      sum_or |= temp_rand[i];
160✔
116
      sum_and &= temp_rand[i];
160✔
117
    }
118
    if (sum_or == 0 || sum_and == 0xFF)
10✔
119
      return false; // Fail-fast for hardware faults (all 0x00 or all 0xFF)
2✔
120

121
    // Unconditionally mix entropy (XOR with 0 is a no-op, avoids branching)
122
    for (int i = 0; i < 8; i++) {
72✔
123
      temp_rand[8 + i] ^= (uint8_t)(_entropy_mixer >> (i * 8));
64✔
124
    }
125

126
    temp_rand[6] = (temp_rand[6] & 0x0F) | 0x40; // Set UUID version to 4 (Random)
8✔
127
    temp_rand[8] = (temp_rand[8] & 0x3F) | 0x80; // Set variant to RFC 4122 (10b)
8✔
128

129
    UUID7Guard lock(_lock_cb, _unlock_cb);
8✔
130
    memcpy(_b, temp_rand, 16);
8✔
131
    return true;
8✔
132
  }
8✔
133

134
  now_ms_fn now_func = _now ? _now : &UUID7::default_now_ms;
102✔
135
  bool overflow_state = false;
102✔
136

137
  while (true) {
138
    uint8_t temp_rand[16];
139
    rng(temp_rand, sizeof(temp_rand), _rng_ctx);
120✔
140

141
    uint8_t sum_or = 0;
120✔
142
    uint8_t sum_and = 0xFF;
120✔
143
    for (size_t i = 0; i < sizeof(temp_rand); i++) {
2,040✔
144
      sum_or |= temp_rand[i];
1,920✔
145
      sum_and &= temp_rand[i];
1,920✔
146
    }
147
    if (sum_or == 0 || sum_and == 0xFF)
120✔
148
      return false; // Fail-fast for hardware faults (all 0x00 or all 0xFF)
102✔
149

150
    // Retrieve current timestamp before acquiring the lock to avoid deadlocks 
151
    // strictly with multi-threaded/blocking time providers.
152
    uint64_t now_ms = now_func(_now_ctx);
118✔
153
    if (now_ms == 0)
118✔
154
      return false;
2✔
155

156
    bool save_needed = false;
116✔
157
    uint64_t ts_to_save = 0;
116✔
158
    bool success = false;
116✔
159
    bool overflow_occurred = false;
116✔
160

161
    {
162
      // Enforce exclusivity with RAII guard
163
      UUID7Guard lock(_lock_cb, _unlock_cb);
116✔
164

165
      // Unconditionally mix entropy (XOR with 0 is a no-op, avoids branching)
166
      for (int i = 0; i < 8; i++) {
1,044✔
167
        temp_rand[8 + i] ^= (uint8_t)(_entropy_mixer >> (i * 8));
928✔
168
      }
169

170
      int cmp = _tsState.compare(now_ms);
116✔
171
      bool major_regression = false;
116✔
172

173
      if (cmp < 0) {
116✔
174
        if (now_ms + _regressionThresholdMs < _tsState.get()) {
10✔
175
          major_regression = true;
4✔
176
        } else {
177
          // Minor regression or race condition: clamp strictly to the last monotonic state
178
          now_ms = _tsState.get();
6✔
179
          cmp = 0; // Evaluate as intra-millisecond progression
6✔
180
        }
181
      }
182

183
      if (major_regression) {
116✔
184
        // Major regression detected: initiate RFC9562 fallback (UUIDv4) to guarantee collision resistance.
185
        // We intentionally do NOT update _persistence here, as the saved timestamp
186
        // is still the correct high-water mark.
187
        temp_rand[6] = (temp_rand[6] & 0x0F) | 0x40; // v4 bits
4✔
188
        temp_rand[8] = (temp_rand[8] & 0x3F) | 0x80; // variant bits
4✔
189
        memcpy(_b, temp_rand, 16);
4✔
190
        return true;
4✔
191
      }
192

193
      if (cmp > 0) {
112✔
194
        _tsState.set(now_ms);
74✔
195
        memcpy(_b, temp_rand, 16);
74✔
196
        success = true;
74✔
197
        overflow_state = false;
74✔
198
      } else {
199
        if (overflow_state) {
38✔
200
          overflow_occurred = true;
16✔
201
        } else {
202
          bool initialized = (_b[6] & 0xF0) == 0x70;
22✔
203
          if (!initialized) {
22✔
204
            memcpy(_b, temp_rand, 16);
4✔
205
            success = true;
4✔
206
          } else {
207
            // Increment internal counter for same-millisecond monotonicity.
208
            if (!_incrementRandom()) {
18✔
209
              overflow_occurred = true;
4✔
210
              overflow_state = true;
4✔
211
            } else {
212
              success = true;
14✔
213
            }
214
          }
215
        }
216
      }
217

218
      if (success) {
112✔
219
        _tsState.stampBytes(_b);
92✔
220
      }
221
      if (success && _persistence.save &&
112✔
222
          (now_ms > _persistence.last_saved_ms + _persistence.interval_ms)) {
12✔
223
        save_needed = true;
6✔
224
        _persistence.last_saved_ms = now_ms;
6✔
225
        ts_to_save = now_ms;
6✔
226
      }
227

228
      if (success) {
112✔
229
        _b[6] = (_b[6] & 0x0F) | ((uint8_t)_version << 4);
92✔
230
        _b[8] = (_b[8] & 0x3F) | 0x80;
92✔
231
      }
232
    }
116✔
233

234
    if (success) {
112✔
235
      if (save_needed && _persistence.save) {
92✔
236
        _persistence.save(ts_to_save, _persistence.ctx);
6✔
237
      }
238
      return true;
92✔
239
    }
240

241
    if (overflow_occurred) {
20✔
242
      if (_overflowPolicy == UUID_OVERFLOW_FAIL_FAST) {
20✔
243
        return false;
2✔
244
      } else {
245
#if defined(ARDUINO)
246
        delay(1); // RTOS context yielding to prevent thread starvation during constraint resolution
247
#else
248
        yield();
18✔
249
#endif
250
        continue;
18✔
251
      }
252
    }
UNCOV
253
    return false;
×
254
  }
18✔
255
}
256

257
bool UUID7::toString(char *out, size_t buflen, bool uppercase,
30✔
258
                     bool dashes) const noexcept {
259
  uint8_t local_b[16];
260
  {
261
    UUID7Guard lock(_lock_cb, _unlock_cb);
30✔
262
    memcpy(local_b, _b, 16);
30✔
263
  }
30✔
264
  return UUID7Codec::encode(local_b, out, buflen, uppercase, dashes);
30✔
265
}
266

267
bool UUID7::parseFromString(const char *str, uint8_t out[16]) noexcept {
26✔
268
  return UUID7Codec::decode(str, out);
26✔
269
}
270

271
uint64_t UUID7::getTimestamp() const noexcept {
6✔
272
  uint8_t snap[16];
273
  {
274
    UUID7Guard lock(_lock_cb, _unlock_cb);
6✔
275
    memcpy(snap, _b, 16);
6✔
276
  }
6✔
277
  if (((snap[6] >> 4) & 0x0F) != 7)
6✔
278
    return 0;
2✔
279
    
280
  uint64_t ts = 0;
4✔
281
  for (int i = 0; i < 6; i++) {
28✔
282
    ts = (ts << 8) | snap[i];
24✔
283
  }
284
  return ts;
4✔
285
}
286

287
bool UUID7::isV7() const noexcept {
22✔
288
  uint8_t b6;
289
  {
290
    UUID7Guard lock(_lock_cb, _unlock_cb);
22✔
291
    b6 = _b[6];
22✔
292
  }
22✔
293
  return ((b6 >> 4) & 0x0F) == 7;
22✔
294
}
295

296
bool UUID7::isV4() const noexcept {
14✔
297
  uint8_t b6;
298
  {
299
    UUID7Guard lock(_lock_cb, _unlock_cb);
14✔
300
    b6 = _b[6];
14✔
301
  }
14✔
302
  return ((b6 >> 4) & 0x0F) == 4;
14✔
303
}
304

305
uint8_t UUID7::getVariant() const noexcept {
6✔
306
  uint8_t b8;
307
  {
308
    UUID7Guard lock(_lock_cb, _unlock_cb);
6✔
309
    b8 = _b[8];
6✔
310
  }
6✔
311
  return (b8 >> 6) & 0x03;
6✔
312
}
313

314
void UUID7::mixEntropy(uint64_t seed) noexcept {
2✔
315
  UUID7Guard lock(_lock_cb, _unlock_cb);
2✔
316
  _entropy_mixer = seed;
2✔
317
}
2✔
318

319
void UUID7::fromBytes(const uint8_t bytes[16]) noexcept {
8✔
320
  UUID7Guard lock(_lock_cb, _unlock_cb);
8✔
321
  memcpy(_b, bytes, 16);
8✔
322
}
8✔
323

324
bool UUID7::parse(const char *str36) noexcept {
2✔
325
  uint8_t temp[16];
326
  if (parseFromString(str36, temp)) {
2✔
327
    fromBytes(temp);
2✔
328
    return true;
2✔
329
  }
UNCOV
330
  return false;
×
331
}
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