uuid/uuid.rs
1#![cfg_attr(not(any(feature = "std", test)), no_std)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4//! Universally Unique IDentifiers (RFC 9562).
5//! Supports generating v4 and v7 UUIDs, and parsing all versions.
6//!
7//! UUIDs v7 support thread-local monotonic counters.
8//!
9//! # Feature flags
10//!
11//! | Flag | Description | Default |
12//! |--------|-------------------------------------------------------------------|---------|
13//! | `std` | Enables [`Uuid::new_v4`] and [`Uuid::new_v7`] via `rand` | Yes |
14//! | `serde`| Enables [`serde`] serialization/deserialization | No |
15//! | `sqlx` | Enables [`sqlx`] integration for PostgreSQL (type, encode, decode) | No |
16//!
17//! When `std` is not enabled, the crate is `#![no_std]` compatible.
18//! Parsing, formatting, and version detection are all available.
19//!
20//! # Example
21//!
22//! ```rust
23//! use uuid::{Uuid, Version};
24//!
25//! // Parse a version-4 UUID from its canonical form
26//! let uuid = Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479").unwrap();
27//! assert_eq!(uuid.version(), Version::V4);
28//!
29//! // Generate new UUIDs (requires `std` feature, enabled by default)
30//! let v4 = Uuid::new_v4();
31//! let v7 = Uuid::new_v7();
32//! assert_eq!(v4.version(), Version::V4);
33//! assert_eq!(v7.version(), Version::V7);
34//! ```
35
36use core::{fmt, str::FromStr};
37
38mod hex;
39
40#[cfg(feature = "serde")]
41mod serde;
42
43#[cfg(feature = "sqlx")]
44mod sqlx;
45
46#[cfg(feature = "std")]
47thread_local! {
48 /// Per-thread state for UUIDv7 monotonic counter generation.
49 /// Tracks `(last_timestamp_ms, counter_value)`.
50 static V7_STATE: std::cell::RefCell<(u64, u32)> = std::cell::RefCell::new((0, 0));
51}
52
53/// A 128-bit UUID (RFC 9562).
54#[derive(Clone, Copy, PartialEq, Eq)]
55pub struct Uuid([u8; 16]);
56
57/// The version of a UUID (RFC 9562 §4.1).
58///
59/// Returned by [`Uuid::version`].
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum Version {
62 /// The Nil UUID (`00000000-0000-0000-0000-000000000000`).
63 Nil,
64 /// Version 1: Date-time and MAC address.
65 V1,
66 /// Version 2: DCE Security.
67 V2,
68 /// Version 3: Name-based (MD5).
69 V3,
70 /// Version 4: Random.
71 V4,
72 /// Version 5: Name-based (SHA-1).
73 V5,
74 /// Version 6: Reordered time-based.
75 V6,
76 /// Version 7: Unix Epoch time-based.
77 V7,
78 /// Version 8: Custom / experimental.
79 V8,
80 /// The Max UUID (`ffffffff-ffff-ffff-ffff-ffffffffffff`).
81 Max,
82 /// An unrecognized or future version.
83 Unknown,
84}
85
86/// Errors that can occur when working with UUIDs.
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum Error {
89 /// The input string is not a valid UUID.
90 InvalidUuid,
91}
92
93impl fmt::Display for Error {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 match self {
96 Self::InvalidUuid => f.write_str("invalid uuid"),
97 }
98 }
99}
100
101#[cfg(feature = "std")]
102impl std::error::Error for Error {}
103
104#[cfg(feature = "std")]
105impl Uuid {
106 /// Generate a new version 4 (random) UUID.
107 ///
108 /// # Examples
109 ///
110 /// ```rust
111 /// use uuid::{Uuid, Version};
112 ///
113 /// let uuid = Uuid::new_v4();
114 /// assert_eq!(uuid.version(), Version::V4);
115 /// ```
116 #[inline]
117 pub fn new_v4() -> Uuid {
118 let mut uuid = Uuid(rand::random());
119
120 // Set version nibble (bits 48-51) to 0100 (4)
121 uuid.0[6] = (uuid.0[6] & 0x0f) | 0x40;
122
123 // Set variant bits (bits 64-65) to 10
124 uuid.0[8] = (uuid.0[8] & 0x3f) | 0x80;
125
126 uuid
127 }
128
129 /// Generate a new version 7 UUID with a 32-bit monotonic counter.
130 ///
131 /// The 48-bit timestamp is milliseconds since the Unix epoch.
132 /// A 32-bit monotonic counter occupies `rand_a` (12 bits) and the
133 /// most-significant 20 bits of `rand_b`, guaranteeing up to
134 /// 2³² UUIDs within a single millisecond per thread (per
135 /// RFC 9562 §6.2, Method 1).
136 ///
137 /// The counter is seeded with 31 random bits each time the
138 /// timestamp advances and incremented on repeated calls within
139 /// the same millisecond. If the counter does overflow, this
140 /// function spin-waits for the next millisecond tick.
141 ///
142 /// The 128-bit layout follows RFC 9562:
143 ///
144 /// ```text
145 /// 0 1 2 3
146 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
147 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
148 /// | unix_ts_ms |
149 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
150 /// | unix_ts_ms | ver | rand_a |
151 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
152 /// |var| rand_b |
153 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
154 /// | rand_b |
155 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
156 /// ```
157 ///
158 /// The 32-bit counter spans `rand_a` (12 bits) and the most-
159 /// significant 20 bits of `rand_b` (bytes 6–10).
160 ///
161 /// # Panics
162 ///
163 /// Panics if the system clock is before the Unix epoch.
164 ///
165 /// # Examples
166 ///
167 /// ```rust
168 /// use uuid::{Uuid, Version};
169 ///
170 /// let uuid = Uuid::new_v7();
171 /// assert_eq!(uuid.version(), Version::V7);
172 /// ```
173 pub fn new_v7() -> Uuid {
174 let mut timestamp = v7_now_ms();
175
176 let counter = loop {
177 match V7_STATE.with(|cell| {
178 let mut state = cell.borrow_mut();
179
180 if timestamp > state.0 {
181 state.0 = timestamp;
182 state.1 = v7_random_counter();
183 } else if timestamp == state.0 {
184 if state.1 < u32::MAX {
185 state.1 += 1;
186 } else {
187 return None; // overflow, spin until next ms
188 }
189 } else {
190 // clock moved backward, re-seed
191 state.0 = timestamp;
192 state.1 = v7_random_counter();
193 }
194 Some(state.1)
195 }) {
196 Some(ctr) => break ctr,
197 None => {
198 while v7_now_ms() == timestamp {
199 std::hint::spin_loop();
200 }
201 timestamp = v7_now_ms();
202 }
203 }
204 };
205
206 let mut uuid = Uuid(rand::random());
207
208 // unix_ts_ms: 48-bit big-endian timestamp
209 uuid.0[0..6].copy_from_slice(×tamp.to_be_bytes()[2..8]);
210 // uuid.0[0] = (ts >> 40) as u8;
211 // uuid.0[1] = (ts >> 32) as u8;
212 // uuid.0[2] = (ts >> 24) as u8;
213 // uuid.0[3] = (ts >> 16) as u8;
214 // uuid.0[4] = (ts >> 8) as u8;
215 // uuid.0[5] = ts as u8;
216
217 // version (4 bits) + counter[31:28] (4 bits)
218 uuid.0[6] = 0x70 | ((counter >> 28) & 0x0F) as u8;
219 // counter[27:20] (8 bits)
220 uuid.0[7] = (counter >> 20) as u8;
221 // variant (2 bits) + counter[19:14] (6 bits)
222 uuid.0[8] = 0x80 | ((counter >> 14) & 0x3F) as u8;
223 // counter[13:6] (8 bits)
224 uuid.0[9] = (counter >> 6) as u8;
225 // counter[5:0] (6 bits) -> preserve upper 2 random bits
226 uuid.0[10] = (uuid.0[10] & 0xC0) | (counter & 0x3F) as u8;
227
228 uuid
229 }
230}
231
232impl Uuid {
233 /// Parse a UUID from its canonical 8-4-4-4-12 hexadecimal string form.
234 ///
235 /// Accepts both lowercase and uppercase hex characters.
236 ///
237 /// # Errors
238 ///
239 /// Returns [`Error::InvalidUuid`] if the input is not exactly 36 characters,
240 /// contains misplaced hyphens, or has non-hexadecimal characters.
241 ///
242 /// # Examples
243 ///
244 /// ```rust
245 /// use uuid::Uuid;
246 ///
247 /// let uuid = Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479").unwrap();
248 /// assert_eq!(uuid.to_string(), "f47ac10b-58cc-4372-a567-0e02b2c3d479");
249 /// ```
250 pub fn parse(input: impl AsRef<[u8]>) -> Result<Uuid, Error> {
251 let bytes = input.as_ref();
252 if bytes.len() != 36 {
253 return Err(Error::InvalidUuid);
254 }
255
256 if bytes[8] != b'-' || bytes[13] != b'-' || bytes[18] != b'-' || bytes[23] != b'-' {
257 return Err(Error::InvalidUuid);
258 }
259
260 let positions: [usize; 8] = [0, 4, 9, 14, 19, 24, 28, 32];
261 let mut buf = [0u8; 16];
262
263 for (j, &pos) in positions.iter().enumerate() {
264 let b0 = hex::decode_pair(bytes[pos], bytes[pos + 1]).ok_or(Error::InvalidUuid)?;
265 let b1 = hex::decode_pair(bytes[pos + 2], bytes[pos + 3]).ok_or(Error::InvalidUuid)?;
266 buf[j * 2] = b0;
267 buf[j * 2 + 1] = b1;
268 }
269
270 Ok(Uuid(buf))
271 }
272
273 /// Create a UUID from a 16-byte array.
274 ///
275 /// # Examples
276 ///
277 /// ```rust
278 /// use uuid::Uuid;
279 ///
280 /// let uuid = Uuid::from_bytes([0; 16]);
281 /// assert_eq!(uuid, Uuid::nil());
282 /// ```
283 #[inline]
284 pub const fn from_bytes(bytes: [u8; 16]) -> Uuid {
285 Uuid(bytes)
286 }
287
288 /// Create a UUID from a byte slice of length 16.
289 ///
290 /// # Errors
291 ///
292 /// Returns [`Error::InvalidUuid`] if the slice is not exactly 16 bytes.
293 ///
294 /// # Examples
295 ///
296 /// ```rust
297 /// use uuid::Uuid;
298 ///
299 /// let uuid = Uuid::from_slice(&[0; 16]).unwrap();
300 /// assert_eq!(uuid, Uuid::nil());
301 /// ```
302 #[inline]
303 pub fn from_slice(bytes: &[u8]) -> Result<Uuid, Error> {
304 <[u8; 16]>::try_from(bytes).map(Uuid).map_err(|_| Error::InvalidUuid)
305 }
306
307 /// Return the 16-byte array representation.
308 ///
309 /// # Examples
310 ///
311 /// ```rust
312 /// use uuid::Uuid;
313 ///
314 /// let uuid = Uuid::nil();
315 /// assert_eq!(uuid.as_bytes(), [0; 16]);
316 /// ```
317 #[inline]
318 pub const fn as_bytes(&self) -> [u8; 16] {
319 self.0
320 }
321
322 /// Create a UUID from a `u128` value (big-endian).
323 ///
324 /// # Examples
325 ///
326 /// ```rust
327 /// use uuid::Uuid;
328 ///
329 /// let uuid = Uuid::from_u128(0);
330 /// assert_eq!(uuid, Uuid::nil());
331 /// ```
332 #[inline]
333 pub const fn from_u128(v: u128) -> Uuid {
334 Uuid([
335 (v >> 120) as u8,
336 (v >> 112) as u8,
337 (v >> 104) as u8,
338 (v >> 96) as u8,
339 (v >> 88) as u8,
340 (v >> 80) as u8,
341 (v >> 72) as u8,
342 (v >> 64) as u8,
343 (v >> 56) as u8,
344 (v >> 48) as u8,
345 (v >> 40) as u8,
346 (v >> 32) as u8,
347 (v >> 24) as u8,
348 (v >> 16) as u8,
349 (v >> 8) as u8,
350 v as u8,
351 ])
352 }
353
354 /// Return the UUID as a `u128` value (big-endian).
355 ///
356 /// # Examples
357 ///
358 /// ```rust
359 /// use uuid::Uuid;
360 ///
361 /// let uuid = Uuid::max();
362 /// assert_eq!(uuid.as_u128(), !0);
363 /// ```
364 #[inline]
365 pub const fn as_u128(&self) -> u128 {
366 u128::from_be_bytes(self.0)
367 }
368
369 /// The Nil UUID: all 128 bits set to zero.
370 ///
371 /// # Examples
372 ///
373 /// ```rust
374 /// use uuid::Uuid;
375 ///
376 /// assert_eq!(Uuid::nil().to_string(), "00000000-0000-0000-0000-000000000000");
377 /// ```
378 #[inline]
379 pub const fn nil() -> Uuid {
380 Uuid([0; 16])
381 }
382
383 /// The Max UUID: all 128 bits set to one.
384 ///
385 /// # Examples
386 ///
387 /// ```rust
388 /// use uuid::Uuid;
389 ///
390 /// assert_eq!(Uuid::max().to_string(), "ffffffff-ffff-ffff-ffff-ffffffffffff");
391 /// ```
392 #[inline]
393 pub const fn max() -> Uuid {
394 Uuid([0xff; 16])
395 }
396
397 /// Return the [`Version`] of this UUID.
398 ///
399 /// # Examples
400 ///
401 /// ```rust
402 /// use uuid::{Uuid, Version};
403 ///
404 /// assert_eq!(Uuid::nil().version(), Version::Nil);
405 /// assert_eq!(Uuid::max().version(), Version::Max);
406 /// ```
407 pub const fn version(&self) -> Version {
408 match self.0[6] >> 4 {
409 1 => return Version::V1,
410 2 => return Version::V2,
411 3 => return Version::V3,
412 4 => return Version::V4,
413 5 => return Version::V5,
414 6 => return Version::V6,
415 7 => return Version::V7,
416 8 => return Version::V8,
417 _ => {}
418 }
419
420 let mut all_zero = true;
421 let mut all_max = true;
422
423 let mut i = 0;
424 while i < 16 {
425 if self.0[i] != 0 {
426 all_zero = false;
427 }
428 if self.0[i] != 0xff {
429 all_max = false;
430 }
431 i += 1;
432 }
433
434 if all_zero {
435 return Version::Nil;
436 }
437 if all_max {
438 return Version::Max;
439 }
440
441 Version::Unknown
442 }
443
444 /// Return the Unix millisecond timestamp embedded in a UUIDv7.
445 ///
446 /// Only UUID version 7 (Unix Epoch time-based) carries a meaningful
447 /// timestamp. All other versions return `None`.
448 ///
449 /// The timestamp is a 48-bit value representing milliseconds since
450 /// the Unix epoch (1970-01-01 00:00:00 UTC).
451 ///
452 /// # Examples
453 ///
454 /// ```rust
455 /// use uuid::Uuid;
456 ///
457 /// let uuid = Uuid::nil();
458 /// assert_eq!(uuid.timestamp(), None);
459 /// ```
460 #[inline]
461 pub fn timestamp(&self) -> Option<u64> {
462 if self.version() != Version::V7 {
463 return None;
464 }
465 Some(
466 (self.0[0] as u64) << 40
467 | (self.0[1] as u64) << 32
468 | (self.0[2] as u64) << 24
469 | (self.0[3] as u64) << 16
470 | (self.0[4] as u64) << 8
471 | self.0[5] as u64,
472 )
473 }
474}
475
476impl fmt::Display for Uuid {
477 /// Formats the UUID as a lowercase 8-4-4-4-12 hyphenated string.
478 ///
479 /// # Examples
480 ///
481 /// ```rust
482 /// let uuid = uuid::Uuid::nil();
483 /// assert_eq!(uuid.to_string(), "00000000-0000-0000-0000-000000000000");
484 /// ```
485 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
486 let bytes = &self.0;
487 let mut buf = [0u8; 36];
488
489 let groups: [(usize, usize); 5] = [(0, 8), (9, 13), (14, 18), (19, 23), (24, 36)];
490
491 let mut src = 0;
492 for (gi, &(start, end)) in groups.iter().enumerate() {
493 let mut j = start;
494 while j < end {
495 let b = bytes[src];
496 src += 1;
497 buf[j] = hex::HEX_ENCODE[(b >> 4) as usize];
498 buf[j + 1] = hex::HEX_ENCODE[(b & 0x0f) as usize];
499 j += 2;
500 }
501 if gi < 4 {
502 buf[end] = b'-';
503 }
504 }
505
506 // SAFETY: `buf` is filled by writing only ASCII bytes from our
507 // precomputed hex encoding table plus the hyphen byte `b'-'`,
508 // all of which are valid UTF-8. The loop guarantees every position
509 // is initialized before we reach this point.
510 let s = unsafe { core::str::from_utf8_unchecked(&buf) };
511 f.write_str(s)
512 }
513}
514
515impl fmt::Debug for Uuid {
516 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
517 fmt::Display::fmt(self, f)
518 }
519}
520
521impl FromStr for Uuid {
522 type Err = Error;
523
524 fn from_str(s: &str) -> Result<Uuid, Error> {
525 Uuid::parse(s)
526 }
527}
528
529#[cfg(feature = "std")]
530#[inline]
531fn v7_now_ms() -> u64 {
532 std::time::SystemTime::UNIX_EPOCH
533 .elapsed()
534 .expect("SystemTime::UNIX_EPOCH elapsed should not fail")
535 .as_millis() as u64
536}
537
538#[cfg(feature = "std")]
539#[inline]
540fn v7_random_counter() -> u32 {
541 // clear the leftmost bit to reduce the chance of generating a counter near u32::MAX that
542 // would trigger the spinlock
543 rand::random::<u32>() & 0x7FFFFFFF
544}
545
546#[cfg(test)]
547mod tests {
548 use super::*;
549
550 #[test]
551 fn nil() {
552 let uuid = Uuid::nil();
553 assert_eq!(uuid.version(), Version::Nil);
554 assert_eq!(uuid.to_string(), "00000000-0000-0000-0000-000000000000");
555 assert_eq!(Uuid::from_bytes(uuid.as_bytes()), uuid);
556 assert_eq!(Uuid::parse(&uuid.to_string()).unwrap(), uuid);
557 }
558
559 #[test]
560 fn max() {
561 let uuid = Uuid::max();
562 assert_eq!(uuid.version(), Version::Max);
563 assert_eq!(uuid.to_string(), "ffffffff-ffff-ffff-ffff-ffffffffffff");
564 assert_eq!(Uuid::from_bytes(uuid.as_bytes()), uuid);
565 assert_eq!(Uuid::parse(&uuid.to_string()).unwrap(), uuid);
566 }
567
568 #[test]
569 fn parse_v4() {
570 let uuid = Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479").unwrap();
571 assert_eq!(uuid.version(), Version::V4);
572 }
573
574 #[test]
575 fn parse_v4_uppercase() {
576 let uuid = Uuid::parse("F47AC10B-58CC-4372-A567-0E02B2C3D479").unwrap();
577 assert_eq!(uuid.version(), Version::V4);
578 }
579
580 #[test]
581 fn parse_v4_mixed_case() {
582 let uuid = Uuid::parse("F47ac10B-58cC-4372-A567-0e02B2c3d479").unwrap();
583 assert_eq!(uuid.version(), Version::V4);
584 }
585
586 #[test]
587 fn parse_invalid_short() {
588 assert_eq!(Uuid::parse("too-short"), Err(Error::InvalidUuid));
589 }
590
591 #[test]
592 fn parse_invalid_long() {
593 assert_eq!(
594 Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479-extra"),
595 Err(Error::InvalidUuid)
596 );
597 }
598
599 #[test]
600 fn parse_invalid_hyphen() {
601 assert_eq!(Uuid::parse("f47ac10b-58cc-4372-a567x0e02b2c3d479"), Err(Error::InvalidUuid));
602 }
603
604 #[test]
605 fn parse_invalid_hex() {
606 assert_eq!(Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d47g"), Err(Error::InvalidUuid));
607 }
608
609 #[test]
610 fn from_bytes_roundtrip() {
611 let bytes = [
612 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
613 ];
614 let uuid = Uuid::from_bytes(bytes);
615 assert_eq!(uuid.to_string(), "01020304-0506-0708-090a-0b0c0d0e0f10");
616 }
617
618 #[test]
619 fn parse_then_to_string() {
620 let s = "f47ac10b-58cc-4372-a567-0e02b2c3d479";
621 let uuid = Uuid::parse(s).unwrap();
622 assert_eq!(uuid.to_string(), s);
623 }
624
625 #[test]
626 fn eq_and_clone() {
627 let a = Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479").unwrap();
628 let b = Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479").unwrap();
629 assert_eq!(a, b);
630 assert_eq!(a.clone(), b);
631 }
632
633 #[test]
634 fn copy_works() {
635 let a = Uuid::nil();
636 let b = a;
637 assert_eq!(a, b);
638 }
639
640 #[test]
641 fn from_str_valid() {
642 let uuid: Uuid = "f47ac10b-58cc-4372-a567-0e02b2c3d479".parse().unwrap();
643 assert_eq!(uuid.version(), Version::V4);
644 }
645
646 #[test]
647 fn from_str_invalid() {
648 let result: Result<Uuid, _> = "not-a-uuid".parse();
649 assert_eq!(result, Err(Error::InvalidUuid));
650 }
651
652 #[test]
653 fn unknown_version() {
654 // Version nibble = 0, but not nil. Should be Unknown, not Nil.
655 let bytes = [
656 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
657 ];
658 let uuid = Uuid::from_bytes(bytes);
659 assert_eq!(uuid.version(), Version::Unknown);
660 }
661
662 #[test]
663 fn version_unknown_when_not_recognized() {
664 // Version nibble = 0, variant = RFC4122, but not Nil
665 let bytes = [
666 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
667 ];
668 let uuid = Uuid::from_bytes(bytes);
669 assert_eq!(uuid.version(), Version::Unknown);
670 }
671
672 #[cfg(feature = "std")]
673 #[test]
674 fn new_v4_has_correct_version() {
675 let uuid = Uuid::new_v4();
676 assert_eq!(uuid.version(), Version::V4);
677 }
678
679 #[cfg(feature = "std")]
680 #[test]
681 fn new_v4_has_correct_variant() {
682 let uuid = Uuid::new_v4();
683 // Variant bits: 10xxxxxx in byte 8
684 assert_eq!(uuid.0[8] & 0xc0, 0x80);
685 }
686
687 #[cfg(feature = "std")]
688 #[test]
689 fn new_v7_has_correct_version() {
690 let uuid = Uuid::new_v7();
691 assert_eq!(uuid.version(), Version::V7);
692 }
693
694 #[cfg(feature = "std")]
695 #[test]
696 fn new_v7_has_correct_variant() {
697 let uuid = Uuid::new_v7();
698 assert_eq!(uuid.0[8] & 0xc0, 0x80);
699 }
700
701 #[cfg(feature = "std")]
702 #[test]
703 fn new_v7_has_recent_timestamp() {
704 for _ in 0..100 {
705 let uuid = Uuid::new_v7();
706 let ts = uuid.timestamp().expect("v7 UUID should have a timestamp");
707
708 assert!(ts > 1_577_836_800_000, "timestamp should be recent: {}", ts);
709 assert!(ts < 4_102_444_800_000, "timestamp should not be in distant future: {}", ts);
710
711 let now_ms = std::time::SystemTime::UNIX_EPOCH
712 .elapsed()
713 .expect("SystemTime::UNIX_EPOCH elapsed should not fail")
714 .as_millis() as u64;
715
716 let diff = now_ms.saturating_sub(ts);
717 assert!(
718 diff < 10_000,
719 "v7 timestamp should be within 10s of system time: ts={ts}, now={now_ms}, diff={diff}",
720 );
721 }
722 }
723
724 #[cfg(feature = "std")]
725 #[test]
726 fn new_v7_has_monotonic_counter() {
727 // Generate many UUIDs and verify the counter is monotonic
728 // within the same millisecond and across timestamps.
729 let uuids: Vec<Uuid> = (0..50).map(|_| Uuid::new_v7()).collect();
730
731 for w in uuids.windows(2) {
732 let (a, b) = (w[0], w[1]);
733 let ts_a = a.timestamp().unwrap();
734 let ts_b = b.timestamp().unwrap();
735 let ctr_a = extract_v7_counter(&a);
736 let ctr_b = extract_v7_counter(&b);
737
738 assert!(
739 (ts_a, ctr_a) < (ts_b, ctr_b),
740 "UUIDs not monotonic: ts=({ts_a}, {ctr_a}), ts=({ts_b}, {ctr_b})",
741 );
742 }
743 }
744
745 /// Extract the 32-bit monotonic counter from a v7 UUID.
746 #[cfg(feature = "std")]
747 fn extract_v7_counter(uuid: &Uuid) -> u32 {
748 // counter[31:28] -> byte 6 low nibble
749 ((uuid.0[6] & 0x0F) as u32) << 28
750 // counter[27:20] -> byte 7
751 | (uuid.0[7] as u32) << 20
752 // counter[19:14] -> byte 8 low 6 bits (below variant)
753 | ((uuid.0[8] & 0x3F) as u32) << 14
754 // counter[13:6] -> byte 9
755 | (uuid.0[9] as u32) << 6
756 // counter[5:0] -> byte 10 low 6 bits
757 | (uuid.0[10] & 0x3F) as u32
758 }
759}