Skip to main content

ps_uuid/methods/
new_v6.rs

1use crate::{UuidConstructionError, UUID};
2use std::time::SystemTime;
3
4impl UUID {
5    /// Create an RFC 4122 **version-6** (time-ordered) UUID from a
6    /// `SystemTime`, a 14-bit clock sequence and a 48-bit node identifier.
7    ///
8    /// # Errors
9    /// - `TimestampBeforeEpoch` if `time` predates 1582-10-15.
10    /// - `TimestampOverflow`    if the 60-bit tick counter would overflow.
11    pub fn new_v6(
12        time: SystemTime,
13        clock_seq: u16,
14        node_id: [u8; 6],
15    ) -> Result<Self, UuidConstructionError> {
16        // 1. Convert wall-clock time → 100 ns ticks since 1582-10-15
17        let ticks = Self::system_time_to_ticks(time)?;
18
19        // 2. Split the 60-bit timestamp into (high, mid, low) pieces
20        let time_high: u32 = ((ticks >> 28) & 0xFFFF_FFFF) as u32; // most-significant 32
21        let time_mid: u16 = ((ticks >> 12) & 0xFFFF) as u16; // next 16
22        let time_low: u16 = (ticks & 0x0FFF) as u16; // least-significant 12
23
24        // 3. Assemble the UUID
25        Ok(Self::from_parts_v6(
26            time_high, time_mid, time_low, clock_seq, node_id,
27        ))
28    }
29}
30
31#[cfg(test)]
32mod tests {
33    #![allow(clippy::expect_used)]
34    use super::*;
35    use crate::{Gregorian, Variant};
36    use std::time::{Duration, SystemTime, UNIX_EPOCH};
37
38    // Manual reference builder using from_parts_v6 -------------------------
39    fn manual(time: SystemTime, node: [u8; 6]) -> UUID {
40        let dur = time
41            .duration_since(Gregorian::epoch())
42            .expect("test timestamp should be after Gregorian epoch");
43        let ticks = dur.as_secs() * 10_000_000 + u64::from(dur.subsec_nanos() / 100);
44
45        let time_high = ((ticks >> 28) & 0xFFFF_FFFF) as u32;
46        let time_mid = ((ticks >> 12) & 0xFFFF) as u16;
47        let time_low = (ticks & 0x0FFF) as u16;
48        let cs = 0x2A3Bu16; // deterministic for testing
49
50        UUID::from_parts_v6(time_high, time_mid, time_low, cs, node)
51    }
52
53    #[test]
54    fn builds_same_bytes_as_manual_version() {
55        let t = UNIX_EPOCH + Duration::from_secs(1_700_000_000); // 2023-11-14
56        let mac = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
57
58        let auto =
59            UUID::new_v6(t, rand::random(), mac).expect("new_v6 should succeed for valid inputs");
60        let bytes = auto.as_bytes();
61
62        // Structural guarantees
63        assert_eq!(auto.get_version(), Some(6));
64        assert_eq!(auto.get_variant(), Variant::OSF);
65
66        // Timestamp (first 8 bytes) must match reference
67        let manual = manual(t, mac);
68        assert_eq!(&bytes[0..8], &manual.as_bytes()[0..8]);
69
70        // Node identifier
71        assert_eq!(&bytes[10..16], &mac);
72    }
73
74    #[test]
75    fn timestamp_before_gregorian_is_rejected() {
76        // 31 Dec 1400 00:00:00 UTC
77        let ancient = UNIX_EPOCH - Duration::from_secs(17_834_668_800);
78        let err = UUID::new_v6(ancient, rand::random(), [0; 6])
79            .expect_err("new_v6 should reject timestamps before Gregorian epoch");
80        assert_eq!(err, UuidConstructionError::TimestampBeforeEpoch);
81    }
82
83    #[test]
84    fn timestamp_overflow_is_rejected() {
85        const MAX_TICKS: u64 = 0x0FFF_FFFF_FFFF_FFFF; // 60 bits all 1
86        let too_far = UNIX_EPOCH + Duration::from_nanos(MAX_TICKS + 1) * 100;
87
88        let err = UUID::new_v6(too_far, rand::random(), [0; 6])
89            .expect_err("new_v6 should reject timestamps that overflow");
90        assert_eq!(err, UuidConstructionError::TimestampOverflow);
91    }
92
93    #[test]
94    fn variant_and_version_bits_are_correct() {
95        let uuid = UUID::new_v6(SystemTime::now(), rand::random(), [1, 2, 3, 4, 5, 6])
96            .expect("new_v6 should succeed for valid inputs");
97        let b = uuid.as_bytes();
98
99        // Variant = 10xxxxxx
100        assert_eq!(b[8] >> 6, 0b10);
101        // Version = 6
102        assert_eq!(b[6] >> 4, 0b0110);
103    }
104
105    #[test]
106    fn new_v6_rejects_time_before_1582_10_15() {
107        let before_gregorian = Gregorian::epoch() - Duration::from_secs(1);
108        let err = UUID::new_v6(before_gregorian, rand::random(), [0; 6])
109            .expect_err("new_v6 should reject timestamps before 1582-10-15");
110        assert_eq!(err, UuidConstructionError::TimestampBeforeEpoch);
111    }
112
113    #[test]
114    fn new_v6_rejects_time_after_5236_03_31() {
115        const MAX_TICKS: u64 = 0x0FFF_FFFF_FFFF_FFFF;
116        let overflow = UNIX_EPOCH + Duration::from_nanos(MAX_TICKS + 1) * 100;
117        let err = UUID::new_v6(overflow, rand::random(), [0; 6])
118            .expect_err("new_v6 should reject timestamps after 5236-03-31");
119        assert_eq!(err, UuidConstructionError::TimestampOverflow);
120    }
121}