@@ -127,3 +127,158 @@ test "UUIDv7 format" {
127127
128128 try std .testing .expectEqualStrings (uuid_str_want , uuid_str_got );
129129}
130+
131+ test "RFC 9562 - UUIDv7 bit layout compliance" {
132+ var gen = initSecure ();
133+ const test_uuid = try gen .next ();
134+ var bytes : [16 ]u8 = undefined ;
135+ std .mem .writeInt (u128 , & bytes , test_uuid , .big );
136+
137+ // Verify version field (bits 48-51, octet 6 high nibble) = 0b0111 (7)
138+ const version = (bytes [6 ] & 0xF0 ) >> 4 ;
139+ try std .testing .expectEqual (@as (u8 , 0x7 ), version );
140+
141+ // Verify variant field (bits 64-65, octet 8 MSBs) = 0b10
142+ const variant = (bytes [8 ] & 0xC0 ) >> 6 ;
143+ try std .testing .expectEqual (@as (u8 , 0b10 ), variant );
144+
145+ // Verify timestamp exists (bits 0-47, octets 0-5)
146+ const timestamp = (@as (u64 , bytes [0 ]) << 40 ) |
147+ (@as (u64 , bytes [1 ]) << 32 ) |
148+ (@as (u64 , bytes [2 ]) << 24 ) |
149+ (@as (u64 , bytes [3 ]) << 16 ) |
150+ (@as (u64 , bytes [4 ]) << 8 ) |
151+ (@as (u64 , bytes [5 ]));
152+ try std .testing .expect (timestamp > 0 );
153+ }
154+
155+ test "RFC 9562 - monotonicity guarantee" {
156+ var gen = initSecure ();
157+ var last = try gen .next ();
158+
159+ // Verify monotonic ordering across 10,000 UUIDs
160+ for (0.. 10_000) | _ | {
161+ const current = try gen .next ();
162+ try std .testing .expect (current > last );
163+ last = current ;
164+ }
165+ }
166+
167+ test "RFC 9562 - string format compliance" {
168+ var gen = initSecure ();
169+ const test_uuid = try gen .next ();
170+ var buf : [36 ]u8 = undefined ;
171+ const uuid_str = toString (test_uuid , & buf );
172+
173+ // Verify length and dash positions per RFC format:
174+ // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
175+ try std .testing .expectEqual (@as (usize , 36 ), uuid_str .len );
176+ try std .testing .expectEqual (@as (u8 , '-' ), uuid_str [8 ]);
177+ try std .testing .expectEqual (@as (u8 , '-' ), uuid_str [13 ]);
178+ try std .testing .expectEqual (@as (u8 , '-' ), uuid_str [18 ]);
179+ try std .testing .expectEqual (@as (u8 , '-' ), uuid_str [23 ]);
180+
181+ // Verify all non-dash characters are hexadecimal
182+ for (uuid_str , 0.. ) | c , i | {
183+ if (i == 8 or i == 13 or i == 18 or i == 23 ) continue ;
184+ const is_hex = (c >= '0' and c <= '9' ) or
185+ (c >= 'a' and c <= 'f' ) or
186+ (c >= 'A' and c <= 'F' );
187+ try std .testing .expect (is_hex );
188+ }
189+ }
190+
191+ test "collision safety - single generator" {
192+ var gen = initSecure ();
193+ var seen = std .AutoHashMap (Uuid , void ).init (std .testing .allocator );
194+ defer seen .deinit ();
195+
196+ const count = 100_000 ;
197+ var last : Uuid = 0 ;
198+
199+ for (0.. count ) | _ | {
200+ const id = try gen .next ();
201+
202+ // Check monotonicity (must always increase)
203+ try std .testing .expect (id > last );
204+ last = id ;
205+
206+ // Check for duplicates
207+ const result = try seen .getOrPut (id );
208+ try std .testing .expect (! result .found_existing );
209+ }
210+ }
211+
212+ test "collision safety - multiple generators produce different UUIDs" {
213+ var gen1 = initSecure ();
214+ var gen2 = initSecure ();
215+
216+ const id1 = try gen1 .next ();
217+ const id2 = try gen2 .next ();
218+
219+ // Different generators should produce different UUIDs
220+ // (62 bits of randomness make collisions virtually impossible)
221+ try std .testing .expect (id1 != id2 );
222+ }
223+
224+ test "collision safety - multi-threaded generation" {
225+ const ThreadContext = struct {
226+ gen : Generator ,
227+ ids : std .ArrayList (Uuid ),
228+
229+ fn worker (ctx : * @This ()) ! void {
230+ for (0.. 1000) | _ | {
231+ const id = try ctx .gen .next ();
232+ try ctx .ids .append (id );
233+ }
234+ }
235+ };
236+
237+ const num_threads = 8 ;
238+ var threads : [num_threads ]std.Thread = undefined ;
239+ var contexts : [num_threads ]ThreadContext = undefined ;
240+
241+ // Initialize contexts
242+ for (0.. num_threads ) | i | {
243+ contexts [i ] = .{
244+ .gen = initSecure (),
245+ .ids = std .ArrayList (Uuid ).init (std .testing .allocator ),
246+ };
247+ }
248+ defer {
249+ for (0.. num_threads ) | i | {
250+ contexts [i ].ids .deinit ();
251+ }
252+ }
253+
254+ // Spawn threads
255+ for (0.. num_threads ) | i | {
256+ threads [i ] = try std .Thread .spawn (.{}, ThreadContext .worker , .{& contexts [i ]});
257+ }
258+
259+ // Wait for completion
260+ for (0.. num_threads ) | i | {
261+ threads [i ].join ();
262+ }
263+
264+ // Check each generator's UUIDs are monotonic
265+ for (contexts ) | ctx | {
266+ var last : Uuid = 0 ;
267+ for (ctx .ids .items ) | id | {
268+ try std .testing .expect (id > last );
269+ last = id ;
270+ }
271+ }
272+
273+ // Collect all UUIDs and check for duplicates
274+ var all_ids = std .AutoHashMap (Uuid , void ).init (std .testing .allocator );
275+ defer all_ids .deinit ();
276+
277+ for (contexts ) | ctx | {
278+ for (ctx .ids .items ) | id | {
279+ const result = try all_ids .getOrPut (id );
280+ try std .testing .expect (! result .found_existing );
281+ }
282+ }
283+ }
284+
0 commit comments