Skip to main content

ps_uuid/methods/
new_v1.rs

1use crate::{UuidConstructionError, UUID};
2use std::time::SystemTime;
3
4impl UUID {
5    /// Create an RFC 4122 version-1 (time-based) UUID
6    /// from a `SystemTime`, clock sequence, and a 48-bit node identifier.
7    ///
8    /// # Errors
9    /// - `TimestampBeforeEpoch` is returned if `time` predates 1582-10-15.
10    /// - `TimestampOverflow` is returned if `time` exceeds 5236-03-31.
11    pub fn new_v1(
12        time: SystemTime,
13        clock_seq: u16,
14        node_id: [u8; 6],
15    ) -> Result<Self, UuidConstructionError> {
16        // ------------------------------------------------------------------
17        // 1. Convert time -> 100 ns ticks since Gregorian epoch
18        // ------------------------------------------------------------------
19        let ticks = Self::system_time_to_ticks(time)?;
20
21        // ------------------------------------------------------------------
22        // 2. Split the 60-bit timestamp into the three wire fields
23        // ------------------------------------------------------------------
24        let time_low: u32 = (ticks & 0xFFFF_FFFF) as u32;
25        let time_mid: u16 = ((ticks >> 32) & 0xFFFF) as u16;
26        let time_hi: u16 = ((ticks >> 48) & 0x0FFF) as u16; // only 12 bits
27
28        // ------------------------------------------------------------------
29        // 3. Construct UUID
30        // ------------------------------------------------------------------
31
32        Ok(Self::from_parts_v1(
33            time_low, time_mid, time_hi, clock_seq, node_id,
34        ))
35    }
36}
37
38#[cfg(test)]
39mod tests {
40    #![allow(clippy::expect_used)]
41    use std::time::{Duration, UNIX_EPOCH};
42
43    use super::*;
44    use crate::{Gregorian, Variant, UUID};
45
46    // Helper: build the “ground truth” through from_parts_v1
47    fn manual(time: SystemTime, node: [u8; 6]) -> UUID {
48        let dur = time
49            .duration_since(Gregorian::epoch())
50            .expect("test timestamp should be after Gregorian epoch");
51        let ticks = dur.as_secs() * 10_000_000 + u64::from(dur.subsec_nanos() / 100);
52
53        let time_low = (ticks & 0xFFFF_FFFF) as u32;
54        let time_mid = ((ticks >> 32) & 0xFFFF) as u16;
55        let time_hi = ((ticks >> 48) & 0x0FFF) as u16;
56        let cs = 0x2A3Bu16; // arbitrary but deterministic for manual build
57
58        UUID::from_parts_v1(time_low, time_mid, time_hi, cs, node)
59    }
60
61    #[test]
62    fn builds_same_bytes_as_manual_version() {
63        let t = UNIX_EPOCH + Duration::from_secs(1_700_000_000); // 2023-11-14
64        let mac = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
65
66        let auto =
67            UUID::new_v1(t, rand::random(), mac).expect("new_v1 should succeed for valid inputs");
68        let bytes = auto.as_bytes();
69
70        // Structural guarantees -------------------------------------------
71        assert_eq!(auto.get_version(), Some(1));
72        assert_eq!(auto.get_variant(), Variant::OSF);
73
74        // Check timestamp and node id equality except for the clock sequence
75        let manual = manual(t, mac);
76        // time_low, time_mid, time_hi
77        assert_eq!(&bytes[0..8], &manual.as_bytes()[0..8]);
78        // node id
79        assert_eq!(&bytes[10..16], &mac);
80    }
81
82    #[test]
83    fn timestamp_before_gregorian_is_rejected() {
84        // 31 Dec 1400 00:00:00 UTC
85        let ancient = UNIX_EPOCH - Duration::from_secs(17_834_668_800);
86        let err = UUID::new_v1(ancient, rand::random(), [0; 6])
87            .expect_err("new_v1 should reject timestamps before Gregorian epoch");
88        assert_eq!(err, UuidConstructionError::TimestampBeforeEpoch);
89    }
90
91    #[test]
92    fn timestamp_overflow_is_rejected() {
93        // 60-bit tick field can hold ~ 36 089  days  … choose a far-future date
94        let too_far = UNIX_EPOCH
95            + Duration::from_secs(
96                u64::try_from(1u128 << 60).expect("2^60 should fit into u64") / 10_000_000
97                    + 12_219_292_800
98                    + 10,
99            );
100
101        let err = UUID::new_v1(too_far, rand::random(), [0; 6])
102            .expect_err("new_v1 should reject timestamps that overflow");
103        assert_eq!(err, UuidConstructionError::TimestampOverflow);
104    }
105
106    #[test]
107    fn variant_and_version_bits_are_correct() {
108        let uuid = UUID::new_v1(SystemTime::now(), rand::random(), [1, 2, 3, 4, 5, 6])
109            .expect("new_v1 should succeed for valid inputs");
110        let b = uuid.as_bytes();
111
112        // Variant = 10xxxxxx
113        assert_eq!(b[8] >> 6, 0b10);
114        // Version = 1
115        assert_eq!(b[6] >> 4, 0b0001);
116    }
117
118    #[test]
119    fn new_v1_rejects_time_before_1582_10_15() {
120        // 1582-10-15 00:00:00 UTC is 12 216 652 800 s before the Unix epoch.
121        let before_gregorian = Gregorian::epoch() - Duration::from_secs(1);
122
123        eprintln!("{before_gregorian:?}");
124
125        let err = UUID::new_v1(before_gregorian, rand::random(), [0; 6])
126            .expect_err("timestamp prior to Gregorian epoch must fail");
127
128        assert!(
129            matches!(err, UuidConstructionError::TimestampBeforeEpoch),
130            "wrong error variant: got {err:?}"
131        );
132    }
133
134    #[test]
135    fn new_v1_rejects_time_after_5236_03_31() {
136        // One tick past the maximum 0x0FFF_FFFF_FFFF_FFFF timestamp (ticks are 100 ns).
137        const MAX_TICKS: u64 = 0x0FFF_FFFF_FFFF_FFFF;
138        let overflow = SystemTime::UNIX_EPOCH + Duration::from_nanos(MAX_TICKS + 1) * 100;
139
140        let err = UUID::new_v1(overflow, rand::random(), [0; 6])
141            .expect_err("timestamp overflow must fail");
142
143        assert!(
144            matches!(err, UuidConstructionError::TimestampOverflow),
145            "wrong error variant: got {err:?}"
146        );
147    }
148}