Skip to main content

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(&timestamp.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}