Skip to main content

ps_uuid/methods/
gen_v2.rs

1use crate::{UuidConstructionError, UUID};
2
3impl UUID {
4    /// Generate v2 UUID (DCE Security)
5    ///
6    /// # Errors
7    /// - `TimestampBeforeEpoch` is returned if the current time predates 1582-10-15.
8    /// - `TimestampOverflow` is returned if the current time exceeds 5236-03-31.
9    pub fn gen_v2(domain: u8, local_id: u32) -> Result<Self, UuidConstructionError> {
10        let mut uuid = Self::gen_v1()?;
11
12        // Replace time_low with local_id (in big-endian order)
13        uuid.bytes[0..4].copy_from_slice(&local_id.to_be_bytes());
14        uuid.bytes[9] = domain;
15
16        Ok(uuid.with_version(2))
17    }
18}
19
20#[cfg(test)]
21mod tests {
22    #![allow(clippy::expect_used)]
23    use super::*;
24
25    // ---------------------------------------------------------------------
26    // Helpers
27    // ---------------------------------------------------------------------
28
29    /// Convenience wrapper that panics on construction failure.
30    fn v2(domain: u8, local_id: u32) -> UUID {
31        UUID::gen_v2(domain, local_id).expect("failed to build v2 UUID")
32    }
33
34    /// Extract the 32-bit value stored in the first four bytes
35    /// (\( \text{time\_low} \) / Local-ID field).
36    fn local_id(u: &UUID) -> u32 {
37        u32::from_be_bytes(
38            u.bytes[0..4]
39                .try_into()
40                .expect("UUID timestamp slice should be exactly 4 bytes"),
41        )
42    }
43
44    /// True iff the RFC-4122 variant bits are `10`.
45    const fn is_rfc4122_variant(u: &UUID) -> bool {
46        (u.bytes[8] & 0b1100_0000) == 0b1000_0000
47    }
48
49    // ---------------------------------------------------------------------
50    // Tests
51    // ---------------------------------------------------------------------
52
53    #[test]
54    fn version_is_always_2() {
55        for &domain in &[0, 1, 2, 42] {
56            let u = v2(domain, 0xDEAD_BEEF);
57            assert_eq!(u.get_version(), Some(2));
58        }
59    }
60
61    #[test]
62    fn variant_bits_remain_rfc4122() {
63        let u = v2(1, 123);
64        assert!(is_rfc4122_variant(&u));
65    }
66
67    #[test]
68    fn local_id_is_encoded_big_endian() {
69        for &id in &[0, 1, 0x1234_5678, u32::MAX] {
70            let u = v2(2, id);
71            assert_eq!(local_id(&u), id);
72        }
73    }
74
75    #[test]
76    fn domain_is_written_to_clock_seq_low() {
77        for domain in 0u8..=10 {
78            let u = v2(domain, 7);
79            assert_eq!(u.bytes[9], domain);
80        }
81    }
82
83    #[test]
84    fn different_local_ids_produce_distinct_uuids() {
85        let u1 = v2(1, 0xAAAA_BBBB);
86        let u2 = v2(1, 0xCCCC_DDDD);
87        assert_ne!(
88            u1.bytes, u2.bytes,
89            "UUIDs must differ when the Local-ID field changes"
90        );
91    }
92
93    #[test]
94    fn different_domains_produce_distinct_uuids() {
95        let u1 = v2(0, 42);
96        let u2 = v2(7, 42);
97        assert_ne!(
98            u1.bytes, u2.bytes,
99            "UUIDs must differ when the Domain field changes"
100        );
101    }
102}