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

bkwoka / UUIDv7 / 23077857423

14 Mar 2026 01:59AM UTC coverage: 98.616% (+0.02%) from 98.596%
23077857423

push

github

bkwoka
Fix: secure stack entropy from LTO and correct thread safety documentation

285 of 289 relevant lines covered (98.62%)

78.8 hits per line

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

97.22
/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,
76✔
33
             void *now_ctx) noexcept
76✔
34
    : _version(UUID_VERSION_7), _overflowPolicy(UUID_OVERFLOW_FAIL_FAST),
76✔
35
      _rng(rng),
76✔
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),
76✔
38
      _entropy_mixer(0),
76✔
39
      _regressionThresholdMs(10000), _lock_cb(nullptr), _unlock_cb(nullptr) {
76✔
40
  memset(_b, 0, sizeof(_b));
76✔
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;
76✔
51
#endif
52
}
76✔
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() {
110✔
107
  fill_random_fn rng = _rng ? _rng : &UUID7::default_fill_random;
110✔
108

109
  if (_version == UUID_VERSION_4) {
110✔
110
    uint8_t temp_rand[16];
111
    rng(temp_rand, sizeof(temp_rand), _rng_ctx);
10✔
112
    uint8_t sum = 0;
10✔
113
    for (size_t i = 0; i < sizeof(temp_rand); i++)
170✔
114
      sum |= temp_rand[i];
160✔
115
    if (sum == 0)
10✔
116
      return false;
2✔
117

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

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

126
    UUID7Guard lock(_lock_cb, _unlock_cb);
8✔
127
    memcpy(_b, temp_rand, 16);
8✔
128
    return true;
8✔
129
  }
8✔
130

131
  now_ms_fn now_func = _now ? _now : &UUID7::default_now_ms;
100✔
132
  bool overflow_state = false;
100✔
133

134
  while (true) {
135
    uint8_t temp_rand[16];
136
    rng(temp_rand, sizeof(temp_rand), _rng_ctx);
118✔
137

138
    uint8_t sum = 0;
118✔
139
    for (size_t i = 0; i < sizeof(temp_rand); i++)
2,006✔
140
      sum |= temp_rand[i];
1,888✔
141
    if (sum == 0)
118✔
142
      return false;
100✔
143

144
    // Retrieve current timestamp before acquiring the lock to avoid deadlocks 
145
    // strictly with multi-threaded/blocking time providers.
146
    uint64_t now_ms = now_func(_now_ctx);
116✔
147
    if (now_ms == 0)
116✔
148
      return false;
2✔
149

150
    bool save_needed = false;
114✔
151
    uint64_t ts_to_save = 0;
114✔
152
    bool success = false;
114✔
153
    bool overflow_occurred = false;
114✔
154

155
    {
156
      // Enforce exclusivity with RAII guard
157
      UUID7Guard lock(_lock_cb, _unlock_cb);
114✔
158

159
      // Unconditionally mix entropy (XOR with 0 is a no-op, avoids branching)
160
      for (int i = 0; i < 8; i++) {
1,026✔
161
        temp_rand[8 + i] ^= (uint8_t)(_entropy_mixer >> (i * 8));
912✔
162
      }
163

164
      int cmp = _tsState.compare(now_ms);
114✔
165
      bool major_regression = false;
114✔
166

167
      if (cmp < 0) {
114✔
168
        if (now_ms + _regressionThresholdMs < _tsState.get()) {
10✔
169
          major_regression = true;
4✔
170
        } else {
171
          // Minor regression or race condition: clamp strictly to the last monotonic state
172
          now_ms = _tsState.get();
6✔
173
          cmp = 0; // Evaluate as intra-millisecond progression
6✔
174
        }
175
      }
176

177
      if (major_regression) {
114✔
178
        // Major regression detected: initiate RFC9562 fallback (UUIDv4) to guarantee collision resistance
179
        temp_rand[6] = (temp_rand[6] & 0x0F) | 0x40; // v4 bits
4✔
180
        temp_rand[8] = (temp_rand[8] & 0x3F) | 0x80; // variant bits
4✔
181
        memcpy(_b, temp_rand, 16);
4✔
182
        return true;
4✔
183
      }
184

185
      if (cmp > 0) {
110✔
186
        _tsState.set(now_ms);
72✔
187
        memcpy(_b, temp_rand, 16);
72✔
188
        success = true;
72✔
189
        overflow_state = false;
72✔
190
      } else {
191
        if (overflow_state) {
38✔
192
          overflow_occurred = true;
16✔
193
        } else {
194
          bool initialized = (_b[6] & 0xF0) == 0x70;
22✔
195
          if (!initialized) {
22✔
196
            memcpy(_b, temp_rand, 16);
4✔
197
            success = true;
4✔
198
          } else {
199
            // Increment internal counter for same-millisecond monotonicity.
200
            if (!_incrementRandom()) {
18✔
201
              overflow_occurred = true;
4✔
202
              overflow_state = true;
4✔
203
            } else {
204
              success = true;
14✔
205
            }
206
          }
207
        }
208
      }
209

210
      if (success) {
110✔
211
        _tsState.stampBytes(_b);
90✔
212
      }
213
      if (success && _persistence.save &&
110✔
214
          (now_ms > _persistence.last_saved_ms + _persistence.interval_ms)) {
12✔
215
        save_needed = true;
6✔
216
        _persistence.last_saved_ms = now_ms;
6✔
217
        ts_to_save = now_ms;
6✔
218
      }
219

220
      if (success) {
110✔
221
        _b[6] = (_b[6] & 0x0F) | ((uint8_t)_version << 4);
90✔
222
        _b[8] = (_b[8] & 0x3F) | 0x80;
90✔
223
      }
224
    }
114✔
225

226
    if (success) {
110✔
227
      if (save_needed && _persistence.save) {
90✔
228
        _persistence.save(ts_to_save, _persistence.ctx);
6✔
229
      }
230
      return true;
90✔
231
    }
232

233
    if (overflow_occurred) {
20✔
234
      if (_overflowPolicy == UUID_OVERFLOW_FAIL_FAST) {
20✔
235
        return false;
2✔
236
      } else {
237
#if defined(ARDUINO)
238
        delay(1); // RTOS context yielding to prevent thread starvation during constraint resolution
239
#else
240
        yield();
18✔
241
#endif
242
        continue;
18✔
243
      }
244
    }
245
    return false;
×
246
  }
18✔
247
}
248

249
bool UUID7::toString(char *out, size_t buflen, bool uppercase,
30✔
250
                     bool dashes) const noexcept {
251
  uint8_t local_b[16];
252
  {
253
    UUID7Guard lock(_lock_cb, _unlock_cb);
30✔
254
    memcpy(local_b, _b, 16);
30✔
255
  }
30✔
256
  return UUID7Codec::encode(local_b, out, buflen, uppercase, dashes);
30✔
257
}
258

259
bool UUID7::parseFromString(const char *str, uint8_t out[16]) noexcept {
26✔
260
  return UUID7Codec::decode(str, out);
26✔
261
}
262

263
uint64_t UUID7::getTimestamp() const noexcept {
4✔
264
  if (!isV7())
4✔
265
    return 0;
2✔
266
  uint64_t ts = 0;
2✔
267
  uint8_t snap[6];
268
  {
269
    UUID7Guard lock(_lock_cb, _unlock_cb);
2✔
270
    memcpy(snap, _b, 6);
2✔
271
  }
2✔
272
  for (int i = 0; i < 6; i++) {
14✔
273
    ts = (ts << 8) | snap[i];
12✔
274
  }
275
  return ts;
2✔
276
}
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