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//!
6//! Supports parsing all versions and generating version 4 (random) and version
7//! 7 (Unix-epoch time-based) UUIDs.
8//!
9//! Parsing is strict: [`Uuid::parse`] accepts only the canonical 8-4-4-4-12
10//! hexadecimal form (lowercase or uppercase). Alternative textual forms such as
11//! the simple (unhyphenated), URN or brace-wrapped representations are
12//! intentionally rejected, which avoids the lax-parsing pitfalls of some other
13//! UUID implementations.
14//!
15//! Version 7 UUIDs carry a per-thread monotonic counter. Values produced by a
16//! single thread are strictly increasing; ordering across threads is not
17//! guaranteed. If the system clock moves backwards the generator keeps the last
18//! logical timestamp and advances the counter, so ordering is preserved. See
19//! `Uuid::new_v7` for details.
20//!
21//! # Feature flags
22//!
23//! | Flag   | Description                                                       | Default |
24//! |--------|-------------------------------------------------------------------|---------|
25//! | `std`  | Enables `Uuid::new_v4` and `Uuid::new_v7` via `rand`            | Yes     |
26//! | `serde`| Enables `serde` serialization/deserialization                  | No      |
27//!
28//! When `std` is not enabled, the crate is `#![no_std]` compatible.
29//! Parsing, formatting, and version detection are all available.
30//!
31//! # Example
32//!
33//! ```rust
34//! use uuid::{Uuid, Version};
35//!
36//! // Parse a version-4 UUID from its canonical form
37//! let uuid = Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479").unwrap();
38//! assert_eq!(uuid.version(), Version::V4);
39//!
40//! // Format it without allocating
41//! let text = uuid.to_heapless_string();
42//! assert_eq!(text.as_str(), "f47ac10b-58cc-4372-a567-0e02b2c3d479");
43//!
44//! // Generate new UUIDs (requires the `std` feature, enabled by default)
45//! # #[cfg(feature = "std")] {
46//! let v4 = Uuid::new_v4();
47//! let v7 = Uuid::new_v7();
48//! assert_eq!(v4.version(), Version::V4);
49//! assert_eq!(v7.version(), Version::V7);
50//! # }
51//! ```
52
53use core::{fmt, str::FromStr};
54
55#[cfg(feature = "std")]
56use rand::Rng as _;
57
58mod hex;
59
60#[cfg(feature = "serde")]
61mod serde;
62
63#[cfg(feature = "std")]
64thread_local! {
65    /// Per-thread state for UUIDv7 monotonic counter generation.
66    ///
67    /// Tracks `(last_logical_timestamp_ms, counter_value)`. The timestamp is a
68    /// *logical* value: it never moves backwards, even if the system clock
69    /// does.
70    static V7_STATE: std::cell::RefCell<(u64, u32)> = const { std::cell::RefCell::new((0, 0)) };
71}
72
73/// A 128-bit UUID (RFC 9562).
74#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
75pub struct Uuid([u8; 16]);
76
77/// The version of a UUID (RFC 9562 §4.1).
78///
79/// Returned by [`Uuid::version`].
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
81pub enum Version {
82    /// The Nil UUID (`00000000-0000-0000-0000-000000000000`).
83    Nil,
84    /// Version 1: Date-time and MAC address.
85    V1,
86    /// Version 2: DCE Security.
87    V2,
88    /// Version 3: Name-based (MD5).
89    V3,
90    /// Version 4: Random.
91    V4,
92    /// Version 5: Name-based (SHA-1).
93    V5,
94    /// Version 6: Reordered time-based.
95    V6,
96    /// Version 7: Unix Epoch time-based.
97    V7,
98    /// Version 8: Custom / experimental.
99    V8,
100    /// The Max UUID (`ffffffff-ffff-ffff-ffff-ffffffffffff`).
101    Max,
102    /// An unrecognized or future version.
103    Unknown,
104}
105
106/// Errors that can occur when working with UUIDs.
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
108pub enum Error {
109    /// The input is not a valid UUID: neither the canonical 36-byte string
110    /// form, nor a 16-byte buffer where one is expected.
111    InvalidUuid,
112}
113
114impl fmt::Display for Error {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        match self {
117            Self::InvalidUuid => f.write_str("invalid uuid"),
118        }
119    }
120}
121
122impl core::error::Error for Error {}
123
124#[cfg(feature = "std")]
125impl Uuid {
126    /// Generate a new version 4 (random) UUID.
127    ///
128    /// # Panics
129    ///
130    /// Panics if the thread-local random number generator cannot be seeded from
131    /// the operating system's entropy source. The generator is seeded once per
132    /// thread and reused afterwards, so this can only happen on the first
133    /// generation on a thread (or after seeding fails).
134    ///
135    /// # Examples
136    ///
137    /// ```rust
138    /// use uuid::{Uuid, Version};
139    ///
140    /// let uuid = Uuid::new_v4();
141    /// assert_eq!(uuid.version(), Version::V4);
142    /// ```
143    #[inline]
144    pub fn new_v4() -> Uuid {
145        let mut bytes = [0u8; 16];
146        rand::rng().fill_bytes(&mut bytes);
147
148        // Set version nibble (bits 48-51) to 0100 (4)
149        bytes[6] = (bytes[6] & 0x0f) | 0x40;
150
151        // Set variant bits (bits 64-65) to 10
152        bytes[8] = (bytes[8] & 0x3f) | 0x80;
153
154        Uuid(bytes)
155    }
156
157    /// Generate a new version 7 UUID with a 32-bit monotonic counter.
158    ///
159    /// The 48-bit timestamp is milliseconds since the Unix epoch. A 32-bit
160    /// monotonic counter occupies `rand_a` (12 bits) and the most-significant
161    /// 20 bits of `rand_b`; the counter is placed immediately after the
162    /// timestamp so that values sort lexicographically by creation order
163    /// (RFC 9562 §6.2, Method 1).
164    ///
165    /// The counter is seeded with 31 random bits each time the logical
166    /// timestamp advances and incremented on repeated calls within the same
167    /// millisecond, so a single thread can generate at least 2³¹ UUIDs per
168    /// millisecond without repeating a value. (The most-significant bit is
169    /// reserved as a rollover guard, as suggested by RFC 9562.)
170    ///
171    /// # Clock rollback and counter overflow
172    ///
173    /// The timestamp used is a *logical* clock that never moves backwards. If
174    /// the system clock is read as being behind the last used timestamp, the
175    /// last timestamp is kept and the counter is advanced, preserving strict
176    /// monotonicity. When the counter is exhausted (fewer than 2³¹ UUIDs later)
177    /// the logical timestamp is advanced by one millisecond and the counter is
178    /// re-seeded; generation never blocks or spins.
179    ///
180    /// Monotonicity only holds within a single thread: two threads generating
181    /// UUIDs concurrently may return values out of order relative to each other.
182    ///
183    /// The 128-bit layout follows RFC 9562:
184    ///
185    /// ```text
186    /// bytes 0..6   unix_ts_ms (48 bits, big-endian)
187    /// byte  6      version (4 bits) | counter[31:28] (4 bits)
188    /// byte  7      counter[27:20] (8 bits)
189    /// byte  8      variant (2 bits) | counter[19:14] (6 bits)
190    /// byte  9      counter[13:6] (8 bits)
191    /// byte  10     counter[5:0] (6 bits) | random (2 bits)
192    /// bytes 11..16 random (40 bits)
193    /// ```
194    ///
195    /// The counter sits immediately after the timestamp/version/variant fields
196    /// and above all random bits, so the 128-bit value sorts by creation order.
197    ///
198    /// # Panics
199    ///
200    /// Panics if the system clock is before the Unix epoch, or if the
201    /// thread-local random number generator cannot be seeded from the operating
202    /// system's entropy source.
203    ///
204    /// # Examples
205    ///
206    /// ```rust
207    /// use uuid::{Uuid, Version};
208    ///
209    /// let uuid = Uuid::new_v7();
210    /// assert_eq!(uuid.version(), Version::V7);
211    /// ```
212    pub fn new_v7() -> Uuid {
213        new_v7_at(v7_now_ms())
214    }
215}
216
217/// Generate a version 7 UUID from an explicit wall-clock reading.
218///
219/// Split out from [`Uuid::new_v7`] so the counter, clock-rollback and overflow
220/// logic can be exercised deterministically in tests.
221#[cfg(feature = "std")]
222fn new_v7_at(now_ms: u64) -> Uuid {
223    let (timestamp, counter) = V7_STATE.with(|cell| {
224        let mut state = cell.borrow_mut();
225        let (mut ts, mut ctr) = *state;
226
227        if now_ms > ts {
228            // Normal case: the clock advanced, re-seed the counter.
229            ts = now_ms;
230            ctr = v7_random_counter();
231        } else if ctr == u32::MAX {
232            // Counter exhausted: advance the logical clock by one millisecond
233            // and re-seed instead of spin-waiting, so generation never blocks
234            // (RFC 9562 §6.2 "Counter Rollover Handling").
235            ts += 1;
236            ctr = v7_random_counter();
237        } else {
238            // Same millisecond, or the clock moved backwards: keep the logical
239            // timestamp and advance the counter to preserve ordering.
240            ctr += 1;
241        }
242
243        *state = (ts, ctr);
244        (ts, ctr)
245    });
246
247    let mut bytes = [0u8; 16];
248    rand::rng().fill_bytes(&mut bytes);
249
250    // unix_ts_ms: 48-bit big-endian timestamp
251    bytes[0..6].copy_from_slice(&timestamp.to_be_bytes()[2..8]);
252
253    // version (4 bits) + counter[31:28] (4 bits)
254    bytes[6] = 0x70 | ((counter >> 28) & 0x0F) as u8;
255    // counter[27:20] (8 bits)
256    bytes[7] = (counter >> 20) as u8;
257    // variant (2 bits) + counter[19:14] (6 bits)
258    bytes[8] = 0x80 | ((counter >> 14) & 0x3F) as u8;
259    // counter[13:6] (8 bits)
260    bytes[9] = (counter >> 6) as u8;
261    // counter[5:0] (6 bits) in the high bits, keeping the 2 low random bits
262    // below all counter bits so that values remain strictly increasing.
263    bytes[10] = (((counter & 0x3F) << 2) as u8) | (bytes[10] & 0x03);
264
265    Uuid(bytes)
266}
267
268impl Uuid {
269    /// Parse a UUID from its canonical 8-4-4-4-12 hexadecimal string form.
270    ///
271    /// Accepts both lowercase and uppercase hex characters.
272    ///
273    /// # Errors
274    ///
275    /// Returns [`Error::InvalidUuid`] if the input is not exactly 36 bytes,
276    /// contains misplaced hyphens, or has non-hexadecimal bytes.
277    ///
278    /// # Examples
279    ///
280    /// ```rust
281    /// use uuid::Uuid;
282    ///
283    /// let uuid = Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479").unwrap();
284    /// assert_eq!(uuid.to_string(), "f47ac10b-58cc-4372-a567-0e02b2c3d479");
285    /// ```
286    pub fn parse(input: impl AsRef<[u8]>) -> Result<Uuid, Error> {
287        let bytes = input.as_ref();
288        if bytes.len() != 36 {
289            return Err(Error::InvalidUuid);
290        }
291
292        if bytes[8] != b'-' || bytes[13] != b'-' || bytes[18] != b'-' || bytes[23] != b'-' {
293            return Err(Error::InvalidUuid);
294        }
295
296        let positions: [usize; 8] = [0, 4, 9, 14, 19, 24, 28, 32];
297        let mut buf = [0u8; 16];
298
299        for (j, &pos) in positions.iter().enumerate() {
300            let b0 = hex::decode_pair(bytes[pos], bytes[pos + 1]).ok_or(Error::InvalidUuid)?;
301            let b1 = hex::decode_pair(bytes[pos + 2], bytes[pos + 3]).ok_or(Error::InvalidUuid)?;
302            buf[j * 2] = b0;
303            buf[j * 2 + 1] = b1;
304        }
305
306        Ok(Uuid(buf))
307    }
308
309    /// Encode this UUID into its canonical 8-4-4-4-12 lowercase hexadecimal
310    /// form without allocating.
311    ///
312    /// This is a zero-allocation alternative to `Uuid::to_string`: the text is
313    /// written into a fixed-capacity [`heapless::String`] with a capacity of 36
314    /// bytes, which is exactly the length of the canonical form, so it never
315    /// spills and never touches the heap. Unlike a `String` it works in
316    /// `no_std` environments.
317    ///
318    /// # Examples
319    ///
320    /// ```rust
321    /// use uuid::Uuid;
322    ///
323    /// let uuid = Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479").unwrap();
324    /// let text = uuid.to_heapless_string();
325    /// assert_eq!(text.as_str(), "f47ac10b-58cc-4372-a567-0e02b2c3d479");
326    /// assert_eq!(text.to_string(), uuid.to_string());
327    /// ```
328    #[inline]
329    pub fn to_heapless_string(&self) -> heapless::String<36> {
330        let buf = self.encode_lower();
331
332        // SAFETY: `encode_lower` only writes ASCII hex digits (`0-9a-f`) and
333        // `-`, all of which are valid UTF-8.
334        let text = unsafe { core::str::from_utf8_unchecked(&buf) };
335
336        let mut out = heapless::String::new();
337        // The canonical form is exactly 36 bytes and `String<36>` has a 36-byte
338        // capacity, so this can never fail.
339        out.push_str(text).expect("36 bytes fit in heapless::String<36>");
340        out
341    }
342
343    /// Raw 36-byte lowercase hyphenated encoding of `self`.
344    #[inline]
345    const fn encode_lower(&self) -> [u8; 36] {
346        let bytes = &self.0;
347        let mut buf = [0u8; 36];
348
349        let groups: [(usize, usize); 5] = [(0, 8), (9, 13), (14, 18), (19, 23), (24, 36)];
350
351        let mut src = 0;
352        let mut gi = 0;
353        while gi < groups.len() {
354            let (start, end) = groups[gi];
355            let mut j = start;
356            while j < end {
357                let b = bytes[src];
358                src += 1;
359                buf[j] = hex::encode_nibble(b >> 4);
360                buf[j + 1] = hex::encode_nibble(b & 0x0f);
361                j += 2;
362            }
363            if gi < 4 {
364                buf[end] = b'-';
365            }
366            gi += 1;
367        }
368
369        buf
370    }
371
372    /// Create a UUID from a 16-byte array.
373    ///
374    /// # Examples
375    ///
376    /// ```rust
377    /// use uuid::Uuid;
378    ///
379    /// let uuid = Uuid::from_bytes([0; 16]);
380    /// assert_eq!(uuid, Uuid::nil());
381    /// ```
382    #[inline]
383    pub const fn from_bytes(bytes: [u8; 16]) -> Uuid {
384        Uuid(bytes)
385    }
386
387    /// Create a UUID from a byte slice of length 16.
388    ///
389    /// # Errors
390    ///
391    /// Returns [`Error::InvalidUuid`] if the slice is not exactly 16 bytes.
392    ///
393    /// # Examples
394    ///
395    /// ```rust
396    /// use uuid::Uuid;
397    ///
398    /// let uuid = Uuid::from_slice(&[0; 16]).unwrap();
399    /// assert_eq!(uuid, Uuid::nil());
400    /// ```
401    #[inline]
402    pub fn from_slice(bytes: &[u8]) -> Result<Uuid, Error> {
403        <[u8; 16]>::try_from(bytes).map(Uuid).map_err(|_| Error::InvalidUuid)
404    }
405
406    /// Return the 16-byte array representation.
407    ///
408    /// # Examples
409    ///
410    /// ```rust
411    /// use uuid::Uuid;
412    ///
413    /// let uuid = Uuid::nil();
414    /// assert_eq!(uuid.as_bytes(), [0; 16]);
415    /// ```
416    #[inline]
417    pub const fn as_bytes(&self) -> [u8; 16] {
418        self.0
419    }
420
421    /// Create a UUID from a `u128` value (big-endian).
422    ///
423    /// # Examples
424    ///
425    /// ```rust
426    /// use uuid::Uuid;
427    ///
428    /// let uuid = Uuid::from_u128(0);
429    /// assert_eq!(uuid, Uuid::nil());
430    /// ```
431    #[inline]
432    pub const fn from_u128(v: u128) -> Uuid {
433        Uuid([
434            (v >> 120) as u8,
435            (v >> 112) as u8,
436            (v >> 104) as u8,
437            (v >> 96) as u8,
438            (v >> 88) as u8,
439            (v >> 80) as u8,
440            (v >> 72) as u8,
441            (v >> 64) as u8,
442            (v >> 56) as u8,
443            (v >> 48) as u8,
444            (v >> 40) as u8,
445            (v >> 32) as u8,
446            (v >> 24) as u8,
447            (v >> 16) as u8,
448            (v >> 8) as u8,
449            v as u8,
450        ])
451    }
452
453    /// Return the UUID as a `u128` value (big-endian).
454    ///
455    /// # Examples
456    ///
457    /// ```rust
458    /// use uuid::Uuid;
459    ///
460    /// let uuid = Uuid::max();
461    /// assert_eq!(uuid.as_u128(), !0);
462    /// ```
463    #[inline]
464    pub const fn as_u128(&self) -> u128 {
465        u128::from_be_bytes(self.0)
466    }
467
468    /// The Nil UUID: all 128 bits set to zero.
469    ///
470    /// # Examples
471    ///
472    /// ```rust
473    /// use uuid::Uuid;
474    ///
475    /// assert_eq!(Uuid::nil().to_string(), "00000000-0000-0000-0000-000000000000");
476    /// ```
477    #[inline]
478    pub const fn nil() -> Uuid {
479        Uuid([0; 16])
480    }
481
482    /// The Max UUID: all 128 bits set to one.
483    ///
484    /// # Examples
485    ///
486    /// ```rust
487    /// use uuid::Uuid;
488    ///
489    /// assert_eq!(Uuid::max().to_string(), "ffffffff-ffff-ffff-ffff-ffffffffffff");
490    /// ```
491    #[inline]
492    pub const fn max() -> Uuid {
493        Uuid([0xff; 16])
494    }
495
496    /// Return the [`Version`] of this UUID.
497    ///
498    /// # Examples
499    ///
500    /// ```rust
501    /// use uuid::{Uuid, Version};
502    ///
503    /// assert_eq!(Uuid::nil().version(), Version::Nil);
504    /// assert_eq!(Uuid::max().version(), Version::Max);
505    /// ```
506    pub const fn version(&self) -> Version {
507        match self.0[6] >> 4 {
508            1 => return Version::V1,
509            2 => return Version::V2,
510            3 => return Version::V3,
511            4 => return Version::V4,
512            5 => return Version::V5,
513            6 => return Version::V6,
514            7 => return Version::V7,
515            8 => return Version::V8,
516            _ => {}
517        }
518
519        let mut all_zero = true;
520        let mut all_max = true;
521
522        let mut i = 0;
523        while i < 16 {
524            if self.0[i] != 0 {
525                all_zero = false;
526            }
527            if self.0[i] != 0xff {
528                all_max = false;
529            }
530            i += 1;
531        }
532
533        if all_zero {
534            return Version::Nil;
535        }
536        if all_max {
537            return Version::Max;
538        }
539
540        Version::Unknown
541    }
542
543    /// Return the Unix millisecond timestamp embedded in a UUIDv7.
544    ///
545    /// Only UUID version 7 (Unix Epoch time-based) carries a meaningful
546    /// timestamp. All other versions return `None`.
547    ///
548    /// The timestamp is a 48-bit value representing milliseconds since
549    /// the Unix epoch (1970-01-01 00:00:00 UTC).
550    ///
551    /// # Examples
552    ///
553    /// ```rust
554    /// use uuid::Uuid;
555    ///
556    /// let uuid = Uuid::nil();
557    /// assert_eq!(uuid.timestamp(), None);
558    /// ```
559    #[inline]
560    pub fn timestamp(&self) -> Option<u64> {
561        if self.version() != Version::V7 {
562            return None;
563        }
564        Some(
565            (self.0[0] as u64) << 40
566                | (self.0[1] as u64) << 32
567                | (self.0[2] as u64) << 24
568                | (self.0[3] as u64) << 16
569                | (self.0[4] as u64) << 8
570                | self.0[5] as u64,
571        )
572    }
573}
574
575impl fmt::Display for Uuid {
576    /// Formats the UUID as a lowercase 8-4-4-4-12 hyphenated string.
577    ///
578    /// # Examples
579    ///
580    /// ```rust
581    /// let uuid = uuid::Uuid::nil();
582    /// assert_eq!(uuid.to_string(), "00000000-0000-0000-0000-000000000000");
583    /// ```
584    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
585        let buf = self.encode_lower();
586
587        // SAFETY: `encode_lower` only writes ASCII hex digits (`0-9a-f`) and
588        // `-`, all of which are valid UTF-8.
589        let text = unsafe { core::str::from_utf8_unchecked(&buf) };
590        f.write_str(text)
591    }
592}
593
594impl fmt::Debug for Uuid {
595    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
596        fmt::Display::fmt(self, f)
597    }
598}
599
600impl FromStr for Uuid {
601    type Err = Error;
602
603    fn from_str(s: &str) -> Result<Uuid, Error> {
604        Uuid::parse(s)
605    }
606}
607
608impl TryFrom<&str> for Uuid {
609    type Error = Error;
610
611    #[inline]
612    fn try_from(value: &str) -> Result<Uuid, Error> {
613        Uuid::parse(value)
614    }
615}
616
617impl TryFrom<&[u8]> for Uuid {
618    type Error = Error;
619
620    #[inline]
621    fn try_from(value: &[u8]) -> Result<Uuid, Error> {
622        Uuid::parse(value)
623    }
624}
625
626#[cfg(feature = "std")]
627#[inline]
628fn v7_now_ms() -> u64 {
629    std::time::SystemTime::UNIX_EPOCH
630        .elapsed()
631        .expect("SystemTime::UNIX_EPOCH elapsed should not fail")
632        .as_millis() as u64
633}
634
635#[cfg(feature = "std")]
636#[inline]
637fn v7_random_counter() -> u32 {
638    // Reserve the most-significant bit as a rollover guard (RFC 9562 §6.2), so
639    // a freshly seeded counter always has at least 2^31 increments of headroom
640    // before it can reach u32::MAX.
641    let mut seed = [0u8; 4];
642    rand::rng().fill_bytes(&mut seed);
643    u32::from_be_bytes(seed) & 0x7FFF_FFFF
644}
645
646/// Overwrite the thread-local v7 state (test-only).
647#[cfg(all(test, feature = "std"))]
648fn v7_set_state(timestamp_ms: u64, counter: u32) {
649    V7_STATE.with(|cell| *cell.borrow_mut() = (timestamp_ms, counter));
650}
651
652/// Read the thread-local v7 state (test-only).
653#[cfg(all(test, feature = "std"))]
654fn v7_get_state() -> (u64, u32) {
655    V7_STATE.with(|cell| *cell.borrow())
656}
657
658#[cfg(test)]
659mod tests {
660    use super::*;
661
662    #[test]
663    fn nil() {
664        let uuid = Uuid::nil();
665        assert_eq!(uuid.version(), Version::Nil);
666        assert_eq!(uuid.to_string(), "00000000-0000-0000-0000-000000000000");
667        assert_eq!(Uuid::from_bytes(uuid.as_bytes()), uuid);
668        assert_eq!(Uuid::parse(uuid.to_string()).unwrap(), uuid);
669    }
670
671    #[test]
672    fn max() {
673        let uuid = Uuid::max();
674        assert_eq!(uuid.version(), Version::Max);
675        assert_eq!(uuid.to_string(), "ffffffff-ffff-ffff-ffff-ffffffffffff");
676        assert_eq!(Uuid::from_bytes(uuid.as_bytes()), uuid);
677        assert_eq!(Uuid::parse(uuid.to_string()).unwrap(), uuid);
678    }
679
680    #[test]
681    fn parse_v4() {
682        let uuid = Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479").unwrap();
683        assert_eq!(uuid.version(), Version::V4);
684    }
685
686    #[test]
687    fn parse_v4_uppercase() {
688        let uuid = Uuid::parse("F47AC10B-58CC-4372-A567-0E02B2C3D479").unwrap();
689        assert_eq!(uuid.version(), Version::V4);
690    }
691
692    #[test]
693    fn parse_v4_mixed_case() {
694        let uuid = Uuid::parse("F47ac10B-58cC-4372-A567-0e02B2c3d479").unwrap();
695        assert_eq!(uuid.version(), Version::V4);
696    }
697
698    #[test]
699    fn parse_invalid_short() {
700        assert_eq!(Uuid::parse("too-short"), Err(Error::InvalidUuid));
701    }
702
703    #[test]
704    fn parse_invalid_long() {
705        assert_eq!(
706            Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479-extra"),
707            Err(Error::InvalidUuid)
708        );
709    }
710
711    #[test]
712    fn parse_invalid_hyphen() {
713        assert_eq!(Uuid::parse("f47ac10b-58cc-4372-a567x0e02b2c3d479"), Err(Error::InvalidUuid));
714    }
715
716    #[test]
717    fn parse_invalid_hex() {
718        assert_eq!(Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d47g"), Err(Error::InvalidUuid));
719    }
720
721    #[test]
722    fn from_bytes_roundtrip() {
723        let bytes = [
724            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
725        ];
726        let uuid = Uuid::from_bytes(bytes);
727        assert_eq!(uuid.to_string(), "01020304-0506-0708-090a-0b0c0d0e0f10");
728    }
729
730    #[test]
731    fn parse_then_to_string() {
732        let s = "f47ac10b-58cc-4372-a567-0e02b2c3d479";
733        let uuid = Uuid::parse(s).unwrap();
734        assert_eq!(uuid.to_string(), s);
735    }
736
737    #[test]
738    fn eq_and_clone() {
739        let a = Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479").unwrap();
740        let b = Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479").unwrap();
741        assert_eq!(a, b);
742        assert_eq!(a.clone(), b);
743    }
744
745    #[test]
746    fn copy_works() {
747        let a = Uuid::nil();
748        let b = a;
749        assert_eq!(a, b);
750    }
751
752    #[test]
753    fn from_str_valid() {
754        let uuid: Uuid = "f47ac10b-58cc-4372-a567-0e02b2c3d479".parse().unwrap();
755        assert_eq!(uuid.version(), Version::V4);
756    }
757
758    #[test]
759    fn from_str_invalid() {
760        let result: Result<Uuid, _> = "not-a-uuid".parse();
761        assert_eq!(result, Err(Error::InvalidUuid));
762    }
763
764    #[test]
765    fn unknown_version() {
766        // Version nibble = 0, but not nil. Should be Unknown, not Nil.
767        let bytes = [
768            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
769        ];
770        let uuid = Uuid::from_bytes(bytes);
771        assert_eq!(uuid.version(), Version::Unknown);
772    }
773
774    #[test]
775    fn version_unknown_when_not_recognized() {
776        // Version nibble = 0, variant = RFC4122, but not Nil
777        let bytes = [
778            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
779        ];
780        let uuid = Uuid::from_bytes(bytes);
781        assert_eq!(uuid.version(), Version::Unknown);
782    }
783
784    #[cfg(feature = "std")]
785    #[test]
786    fn new_v4_has_correct_version() {
787        let uuid = Uuid::new_v4();
788        assert_eq!(uuid.version(), Version::V4);
789    }
790
791    #[cfg(feature = "std")]
792    #[test]
793    fn new_v4_has_correct_variant() {
794        let uuid = Uuid::new_v4();
795        // Variant bits: 10xxxxxx in byte 8
796        assert_eq!(uuid.0[8] & 0xc0, 0x80);
797    }
798
799    #[cfg(feature = "std")]
800    #[test]
801    fn new_v7_has_correct_version() {
802        let uuid = Uuid::new_v7();
803        assert_eq!(uuid.version(), Version::V7);
804    }
805
806    #[cfg(feature = "std")]
807    #[test]
808    fn new_v7_has_correct_variant() {
809        let uuid = Uuid::new_v7();
810        assert_eq!(uuid.0[8] & 0xc0, 0x80);
811    }
812
813    #[cfg(feature = "std")]
814    #[test]
815    fn new_v7_has_recent_timestamp() {
816        for _ in 0..100 {
817            let uuid = Uuid::new_v7();
818            let ts = uuid.timestamp().expect("v7 UUID should have a timestamp");
819
820            assert!(ts > 1_577_836_800_000, "timestamp should be recent: {}", ts);
821            assert!(ts < 4_102_444_800_000, "timestamp should not be in distant future: {}", ts);
822
823            let now_ms = std::time::SystemTime::UNIX_EPOCH
824                .elapsed()
825                .expect("SystemTime::UNIX_EPOCH elapsed should not fail")
826                .as_millis() as u64;
827
828            let diff = now_ms.saturating_sub(ts);
829            assert!(
830                diff < 10_000,
831                "v7 timestamp should be within 10s of system time: ts={ts}, now={now_ms}, diff={diff}",
832            );
833        }
834    }
835
836    #[cfg(feature = "std")]
837    #[test]
838    fn new_v7_has_monotonic_counter() {
839        // Generate many UUIDs and verify the counter is monotonic
840        // within the same millisecond and across timestamps.
841        let uuids: Vec<Uuid> = (0..50).map(|_| Uuid::new_v7()).collect();
842
843        for w in uuids.windows(2) {
844            let (a, b) = (w[0], w[1]);
845            let ts_a = a.timestamp().unwrap();
846            let ts_b = b.timestamp().unwrap();
847            let ctr_a = extract_v7_counter(&a);
848            let ctr_b = extract_v7_counter(&b);
849
850            assert!(
851                (ts_a, ctr_a) < (ts_b, ctr_b),
852                "UUIDs not monotonic: ts=({ts_a}, {ctr_a}), ts=({ts_b}, {ctr_b})",
853            );
854        }
855    }
856
857    /// Extract the 32-bit monotonic counter from a v7 UUID.
858    #[cfg(feature = "std")]
859    fn extract_v7_counter(uuid: &Uuid) -> u32 {
860        // counter[31:28] -> byte 6 low nibble
861        ((uuid.0[6] & 0x0F) as u32) << 28
862            // counter[27:20] -> byte 7
863            | (uuid.0[7] as u32) << 20
864            // counter[19:14] -> byte 8 low 6 bits (below variant)
865            | ((uuid.0[8] & 0x3F) as u32) << 14
866            // counter[13:6] -> byte 9
867            | (uuid.0[9] as u32) << 6
868            // counter[5:0] -> byte 10 high 6 bits
869            | ((uuid.0[10] >> 2) & 0x3F) as u32
870    }
871
872    /// Deterministic pseudo-random generator for property-style tests.
873    fn xorshift64(state: &mut u64) -> u64 {
874        let mut x = *state;
875        x ^= x << 13;
876        x ^= x >> 7;
877        x ^= x << 17;
878        *state = x;
879        x
880    }
881
882    // -- to_heapless_string -------------------------------------------------
883
884    #[test]
885    fn to_heapless_string_matches_to_string() {
886        let samples = [
887            Uuid::nil(),
888            Uuid::max(),
889            Uuid::from_u128(0x0102_0304_0506_0708_090a_0b0c_0d0e_0f10),
890            Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479").unwrap(),
891        ];
892        for uuid in samples {
893            let text = uuid.to_heapless_string();
894            assert_eq!(text.as_str(), uuid.to_string());
895            assert_eq!(text.len(), 36);
896            assert_eq!(text.as_bytes(), uuid.to_string().as_bytes());
897            assert_eq!(format!("{text}"), uuid.to_string());
898            assert_eq!(format!("{text:?}"), format!("{:?}", uuid.to_string()));
899            // PartialEq<&str> and AsRef
900            assert_eq!(text, uuid.to_string().as_str());
901            assert_eq!(AsRef::<str>::as_ref(&text), uuid.to_string());
902        }
903    }
904
905    #[test]
906    fn to_heapless_string_allocates_nothing() {
907        let uuid = Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479").unwrap();
908        let (text, c) = alloc_counter::measure(|| uuid.to_heapless_string());
909        assert_eq!(c.calls, 0, "to_heapless_string must not allocate");
910        assert_eq!(text.len(), 36);
911        assert_eq!(text.as_str(), "f47ac10b-58cc-4372-a567-0e02b2c3d479");
912    }
913
914    #[test]
915    fn parse_allocates_nothing() {
916        let (_, c) = alloc_counter::measure(|| Uuid::parse("f47ac10b-58cc-4372-a567-0e02b2c3d479").unwrap());
917        assert_eq!(c.calls, 0, "parse must not allocate");
918    }
919
920    // -- trait impls --------------------------------------------------------
921
922    #[test]
923    fn hash_ord_default() {
924        use std::collections::{BTreeMap, HashMap};
925
926        let a = Uuid::from_u128(1);
927        let b = Uuid::from_u128(2);
928
929        // Hash: usable as a HashMap key.
930        let mut map = HashMap::new();
931        map.insert(a, "a");
932        map.insert(b, "b");
933        assert_eq!(map.get(&a), Some(&"a"));
934
935        // Ord: sortable and usable as a BTreeMap key.
936        let mut v = [b, a];
937        v.sort();
938        assert_eq!(v, [a, b]);
939        let mut tree = BTreeMap::new();
940        tree.insert(b, ());
941        tree.insert(a, ());
942        assert_eq!(tree.keys().next(), Some(&a));
943
944        // Default: the nil UUID.
945        assert_eq!(Uuid::default(), Uuid::nil());
946    }
947
948    #[test]
949    fn try_from_impls() {
950        let s = "f47ac10b-58cc-4372-a567-0e02b2c3d479";
951        let expected = Uuid::parse(s).unwrap();
952        assert_eq!(Uuid::try_from(s).unwrap(), expected);
953        assert_eq!(Uuid::try_from(s.as_bytes()).unwrap(), expected);
954        assert!(Uuid::try_from("nope").is_err());
955        assert!(Uuid::try_from(&b"nope"[..]).is_err());
956    }
957
958    // -- parsing strictness / robustness ------------------------------------
959
960    #[test]
961    fn parse_strict_rejections() {
962        // Cases other implementations (notably Java's UUID.fromString) accept
963        // leniently, plus encoding/length tricks. All must be rejected.
964        let rejected: &[&[u8]] = &[
965            b"",
966            b"0",
967            b"0-0-0-0-0",
968            b"1-1-1-1-1",
969            b"00112233-4455-6677-8899-aabbccddee",    // 34 chars
970            b"00112233-4455-6677-8899-aabbccddeeff0", // 37 chars
971            b"00112233445566778899aabbccddeeff",      // simple form
972            b"urn:uuid:00112233-4455-6677-8899-aabbccddeeff",
973            b"{00112233-4455-6677-8899-aabbccddeeff}",
974            b" 00112233-4455-6677-8899-aabbccddeeff",
975            b"00112233-4455-6677-8899-aabbccddeeff ",
976            b"\t00112233-4455-6677-8899-aabbccddeeff",
977            b"00112233-4455-6677-8899-aabbccddeeff\n",
978            b"00112233-4455-6677-8899-aabbccddeefg",
979            b"00112233-4455-6677-8899-aabbccddeefG",
980            b"00112233+4455-6677-8899-aabbccddeeff",
981            b"00112233-4455-6677-8899-aabbccddee f",
982            b"00112233-4455-6677-8899-aabbccddee\x00",
983            b"00112233-4455-6677-8899-aabbccddef\xff",
984            b"00112233-4455-6677-8899-aabbccdde\xc3\xa9f", // multi-byte UTF-8
985            b"00112233-4455-6677-8899-aabbccddeeff--",
986            b"--00112233-4455-6677-8899-aabbccddee",
987            b"00112233-4455-6677-8899-aabbccddee fffffffff",
988        ];
989        for input in rejected {
990            assert_eq!(Uuid::parse(*input), Err(Error::InvalidUuid), "should reject {input:?}");
991            if let Ok(s) = std::str::from_utf8(input) {
992                assert_eq!(s.parse::<Uuid>(), Err(Error::InvalidUuid), "FromStr should reject {s:?}");
993            }
994        }
995    }
996
997    #[test]
998    fn parse_accepts_both_cases() {
999        let lower = Uuid::parse("00112233-4455-6677-8899-aabbccddeeff").unwrap();
1000        let upper = Uuid::parse("00112233-4455-6677-8899-AABBCCDDEEFF").unwrap();
1001        let mixed = Uuid::parse("00112233-4455-6677-8899-AabbCCddEEff").unwrap();
1002        assert_eq!(lower, upper);
1003        assert_eq!(lower, mixed);
1004        assert_eq!(lower.to_string(), "00112233-4455-6677-8899-aabbccddeeff");
1005    }
1006
1007    #[test]
1008    fn parse_mutation_sweep() {
1009        // Every single-byte mutation of a valid UUID must either be rejected or
1010        // produce a value that round-trips; it must never panic.
1011        let good: [u8; 36] = *b"f47ac10b-58cc-4372-a567-0e02b2c3d479";
1012        for pos in 0..36 {
1013            for byte in 0..=255u8 {
1014                let mut buf = good;
1015                buf[pos] = byte;
1016                if let Ok(uuid) = Uuid::parse(&buf[..]) {
1017                    assert_eq!(Uuid::parse(uuid.to_string()).unwrap(), uuid);
1018                }
1019            }
1020        }
1021    }
1022
1023    #[test]
1024    fn parse_random_garbage_never_panics() {
1025        let mut state = 0xdead_beef_cafe_babeu64;
1026        for _ in 0..100_000 {
1027            let len = (xorshift64(&mut state) % 65) as usize;
1028            let mut buf = [0u8; 64];
1029            for b in buf.iter_mut().take(len) {
1030                *b = xorshift64(&mut state) as u8;
1031            }
1032            // Must not panic; result is intentionally ignored.
1033            let _ = Uuid::parse(&buf[..len]);
1034        }
1035    }
1036
1037    #[test]
1038    fn roundtrip_pseudo_random() {
1039        let mut state = 0x1234_5678_9abc_def0u64;
1040        for _ in 0..100_000 {
1041            let v = ((xorshift64(&mut state) as u128) << 64) | xorshift64(&mut state) as u128;
1042            let uuid = Uuid::from_u128(v);
1043            assert_eq!(uuid.as_u128(), v);
1044            assert_eq!(Uuid::parse(uuid.to_string()).unwrap(), uuid);
1045            assert_eq!(Uuid::parse(uuid.to_heapless_string().as_str()).unwrap(), uuid);
1046        }
1047    }
1048
1049    // -- version / timestamp edge cases -------------------------------------
1050
1051    #[test]
1052    fn version_edge_cases() {
1053        // Version nibble 0 but not all-zero -> Unknown.
1054        let unknown = Uuid::from_bytes([0x00, 0, 0, 0, 0, 0, 0x00, 0x80, 0x80, 0, 0, 0, 0, 0, 0, 0x01]);
1055        assert_eq!(unknown.version(), Version::Unknown);
1056
1057        // Version nibble 0xf but not the Max UUID -> Unknown (matches RFC and
1058        // the corrected uuid-rs semantics).
1059        let almost_max = Uuid::from_bytes([0xff; 16]).as_u128() & !(1u128 << 64);
1060        assert_eq!(Uuid::from_u128(almost_max).version(), Version::Unknown);
1061
1062        // All-ones is Max even though its nibble is 0xf.
1063        assert_eq!(Uuid::max().version(), Version::Max);
1064        assert_eq!(Uuid::nil().version(), Version::Nil);
1065
1066        // Every recognized nibble maps to its version.
1067        for (nibble, expected) in [
1068            (1u8, Version::V1),
1069            (2, Version::V2),
1070            (3, Version::V3),
1071            (4, Version::V4),
1072            (5, Version::V5),
1073            (6, Version::V6),
1074            (7, Version::V7),
1075            (8, Version::V8),
1076        ] {
1077            let mut bytes = [0u8; 16];
1078            bytes[6] = nibble << 4;
1079            bytes[8] = 0x80;
1080            assert_eq!(Uuid::from_bytes(bytes).version(), expected);
1081        }
1082    }
1083
1084    #[test]
1085    fn timestamp_only_for_v7() {
1086        assert_eq!(Uuid::nil().timestamp(), None);
1087        assert_eq!(Uuid::max().timestamp(), None);
1088        // A v1 UUID carries a timestamp too, but we do not decode it.
1089        let v1 = Uuid::from_u128(0x1234_5678_9abc_1def_8000_0000_0000_0001);
1090        assert_eq!(v1.version(), Version::V1);
1091        assert_eq!(v1.timestamp(), None);
1092
1093        let v7 = Uuid::from_bytes([
1094            0x00, 0x00, 0x01, 0x90, 0x12, 0x34, 0x70, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
1095        ]);
1096        assert_eq!(v7.version(), Version::V7);
1097        assert_eq!(v7.timestamp(), Some(0x0190_1234));
1098    }
1099
1100    // -- v7 counter / clock behaviour (deterministic) -----------------------
1101
1102    #[cfg(feature = "std")]
1103    #[test]
1104    fn v7_same_ms_values_strictly_increase() {
1105        // Regression test for the byte-10 layout: within one logical
1106        // millisecond, consecutive UUIDs must be strictly increasing as u128
1107        // (i.e. lexicographically/bytewise sortable), not just by counter.
1108        v7_set_state(1_000, 0);
1109        let mut previous: Option<Uuid> = None;
1110        for _ in 0..10_000 {
1111            let uuid = new_v7_at(1_000);
1112            if let Some(prev) = previous {
1113                assert!(prev.as_u128() < uuid.as_u128(), "not increasing: {prev} -> {uuid}");
1114            }
1115            previous = Some(uuid);
1116        }
1117    }
1118
1119    #[cfg(feature = "std")]
1120    #[test]
1121    fn v7_clock_rollback_keeps_logical_timestamp() {
1122        v7_set_state(1_000, 5);
1123        // Clock reads far in the past: the logical timestamp is held and the
1124        // counter advances.
1125        let uuid = new_v7_at(500);
1126        assert_eq!(uuid.timestamp(), Some(1_000));
1127        assert_eq!(extract_v7_counter(&uuid), 6);
1128        assert_eq!(v7_get_state(), (1_000, 6));
1129
1130        let uuid = new_v7_at(500);
1131        assert_eq!(uuid.timestamp(), Some(1_000));
1132        assert_eq!(extract_v7_counter(&uuid), 7);
1133    }
1134
1135    #[cfg(feature = "std")]
1136    #[test]
1137    fn v7_counter_overflow_advances_logical_clock() {
1138        v7_set_state(100, u32::MAX);
1139        let uuid = new_v7_at(100);
1140        // The logical timestamp advanced by one ms and the counter was
1141        // re-seeded (rollover guard keeps it below 2^31), so generation does
1142        // not block.
1143        assert_eq!(uuid.timestamp(), Some(101));
1144        let counter = extract_v7_counter(&uuid);
1145        assert!(counter <= 0x7FFF_FFFF, "counter should be re-seeded: {counter}");
1146        assert_eq!(v7_get_state(), (101, counter));
1147    }
1148
1149    #[cfg(feature = "std")]
1150    #[test]
1151    fn v7_advancing_clock_reseeds_counter() {
1152        v7_set_state(100, 5);
1153        let uuid = new_v7_at(200);
1154        assert_eq!(uuid.timestamp(), Some(200));
1155        assert!(extract_v7_counter(&uuid) <= 0x7FFF_FFFF);
1156    }
1157
1158    #[cfg(feature = "std")]
1159    #[test]
1160    fn v7_real_calls_are_strictly_increasing() {
1161        let mut previous = Uuid::new_v7();
1162        for _ in 0..5_000 {
1163            let uuid = Uuid::new_v7();
1164            assert!(
1165                previous.as_u128() < uuid.as_u128(),
1166                "consecutive v7 UUIDs must be strictly increasing: {previous} -> {uuid}"
1167            );
1168            previous = uuid;
1169        }
1170    }
1171
1172    #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
1173    #[test]
1174    fn v7_multithread_unique() {
1175        use std::{
1176            collections::HashSet,
1177            sync::{Arc, Mutex},
1178        };
1179
1180        const THREADS: usize = 4;
1181        const PER_THREAD: usize = 20_000;
1182
1183        let seen = Arc::new(Mutex::new(HashSet::with_capacity(THREADS * PER_THREAD)));
1184        let handles: Vec<_> = (0..THREADS)
1185            .map(|_| {
1186                let seen = Arc::clone(&seen);
1187                std::thread::spawn(move || {
1188                    let local: Vec<Uuid> = (0..PER_THREAD).map(|_| Uuid::new_v7()).collect();
1189                    let mut seen = seen.lock().unwrap();
1190                    for uuid in local {
1191                        assert!(seen.insert(uuid), "duplicate v7 UUID across threads: {uuid}");
1192                    }
1193                })
1194            })
1195            .collect();
1196        for handle in handles {
1197            handle.join().unwrap();
1198        }
1199        assert_eq!(seen.lock().unwrap().len(), THREADS * PER_THREAD);
1200    }
1201
1202    #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
1203    #[test]
1204    fn v4_multithread_unique() {
1205        use std::{
1206            collections::HashSet,
1207            sync::{Arc, Mutex},
1208        };
1209
1210        const THREADS: usize = 4;
1211        const PER_THREAD: usize = 20_000;
1212
1213        let seen = Arc::new(Mutex::new(HashSet::with_capacity(THREADS * PER_THREAD)));
1214        let handles: Vec<_> = (0..THREADS)
1215            .map(|_| {
1216                let seen = Arc::clone(&seen);
1217                std::thread::spawn(move || {
1218                    let local: Vec<Uuid> = (0..PER_THREAD).map(|_| Uuid::new_v4()).collect();
1219                    let mut seen = seen.lock().unwrap();
1220                    for uuid in local {
1221                        assert!(seen.insert(uuid), "duplicate v4 UUID across threads: {uuid}");
1222                    }
1223                })
1224            })
1225            .collect();
1226        for handle in handles {
1227            handle.join().unwrap();
1228        }
1229        assert_eq!(seen.lock().unwrap().len(), THREADS * PER_THREAD);
1230    }
1231}