Skip to main content

ps_uuid/methods/
gen_v7.rs

1use std::time::{SystemTime, UNIX_EPOCH};
2
3use rand::random;
4
5use crate::{UuidConstructionError, STATE, UUID};
6
7impl UUID {
8    /// Generate an RFC-4122 **Version 7** (Unix-epoch, time-ordered) UUID.
9    ///
10    /// Steps  
11    /// 1. `STATE.next_v7` returns a strictly monotonous `SystemTime`.  
12    /// 2. That time is converted to a `Duration` since the Unix epoch.  
13    /// 3. Range checks ensure the 48-bit millisecond field is valid
14    ///    (epoch … ≈ 10889-08-02 05:31:50.655 UTC).  
15    /// 4. The remaining **64 random bits (8 bytes)** are filled with CSPRNG
16    ///    data.  
17    /// 5. `UUID::new_v7` assembles the final UUID and patches
18    ///    version & variant bits.
19    ///
20    /// # Errors
21    /// - `TimestampBeforeEpoch`  if the system clock is before 1970-01-01.
22    /// - `TimestampOverflow`     if the millisecond counter ≥ 2⁴⁸.
23    pub fn gen_v7() -> Result<Self, UuidConstructionError> {
24        // 1 — obtain monotonic timestamp
25        let timestamp = {
26            let mut guard = STATE.lock();
27            let ts = guard.next_v7(SystemTime::now());
28            drop(guard);
29            ts
30        };
31
32        // 2 — convert to Duration and validate range
33        let duration = timestamp
34            .duration_since(UNIX_EPOCH)
35            .map_err(|_| UuidConstructionError::TimestampBeforeEpoch)?;
36
37        #[allow(clippy::items_after_statements)]
38        const MAX_MILLIS: u128 = 1u128 << 48; // 2⁴⁸ ms
39        if duration.as_millis() >= MAX_MILLIS {
40            return Err(UuidConstructionError::TimestampOverflow);
41        }
42
43        // 3 — 64 bits (8 bytes) of randomness
44        let random_bytes: [u8; 8] = random();
45
46        // 4 — assemble
47        Ok(Self::new_v7(duration, random_bytes))
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    #![allow(clippy::expect_used)]
54    use super::*;
55    use std::{
56        collections::HashSet,
57        sync::{Arc, Mutex},
58        thread,
59    };
60
61    const fn version(b: &[u8; 16]) -> u8 {
62        b[6] >> 4
63    }
64    const fn variant(b: &[u8; 16]) -> u8 {
65        b[8] >> 6
66    }
67
68    #[test]
69    fn gen_v7_produces_valid_uuid() {
70        let uuid = UUID::gen_v7().expect("generation must succeed");
71        let bytes = uuid.as_bytes();
72        assert_eq!(version(bytes), 0b0111);
73        assert_eq!(variant(bytes), 0b10);
74    }
75
76    #[test]
77    fn gen_v7_uniqueness_single_thread() {
78        const N: usize = 10_000;
79        let mut seen = HashSet::with_capacity(N);
80        for _ in 0..N {
81            let s = UUID::gen_v7().expect("generation must succeed").to_string();
82            assert!(seen.insert(s), "duplicate UUID generated");
83        }
84    }
85
86    #[test]
87    fn gen_v7_thread_safety_and_uniqueness() {
88        const THREADS: usize = 8;
89        const PER_THREAD: usize = 2_000;
90
91        let global: Arc<Mutex<HashSet<UUID>>> =
92            Arc::new(Mutex::new(HashSet::with_capacity(THREADS * PER_THREAD)));
93
94        let mut handles = Vec::with_capacity(THREADS);
95        for _ in 0..THREADS {
96            let global = Arc::clone(&global);
97            handles.push(thread::spawn(move || {
98                for _ in 0..PER_THREAD {
99                    let id = UUID::gen_v7().expect("generation must succeed");
100                    let mut guard = global.lock().expect("state mutex should not be poisoned");
101                    assert!(guard.insert(id), "duplicate across threads");
102                    drop(guard);
103                }
104            }));
105        }
106        for h in handles {
107            h.join().expect("thread panicked");
108        }
109    }
110}