Skip to main content

ps_uuid/methods/
new_v3.rs

1use crate::{Md5, UUID};
2
3impl UUID {
4    /// Builds an RFC-4122 Version-3 UUID from `namespace || name`.
5    ///
6    /// The function
7    ///   1. hashes the concatenation `namespace.bytes || name` with MD5,
8    ///   2. calls `from_parts_v3` to set version = 3 + RFC-4122 variant,
9    ///   3. returns the finished UUID.
10    #[must_use]
11    pub fn new_v3<N>(namespace: &Self, name: N) -> Self
12    where
13        N: AsRef<[u8]>,
14    {
15        let mut hasher = Md5::new();
16
17        hasher.update(namespace.as_bytes());
18        hasher.update(name.as_ref());
19
20        let digest = hasher.finalize();
21
22        Self::from_parts_v3(digest)
23    }
24}
25
26// ────────────────────────────────────────────────────────────────────────────
27// Test-suite
28// ────────────────────────────────────────────────────────────────────────────
29#[cfg(test)]
30mod tests {
31    use crate::UUID;
32
33    use super::*;
34
35    // Helper: two MSBs must be `10`
36    const fn is_rfc4122_variant(b: u8) -> bool {
37        (b & 0b1100_0000) == 0b1000_0000
38    }
39
40    #[test]
41    fn version_and_variant_are_correct() {
42        let ns = UUID { bytes: [0u8; 16] };
43        let uuid = UUID::new_v3(&ns, "abc");
44        assert_eq!(uuid.get_version(), Some(3));
45        assert!(is_rfc4122_variant(uuid.bytes[8]));
46    }
47
48    #[test]
49    fn identical_inputs_yield_identical_uuids() {
50        let ns = UUID {
51            bytes: [0x42u8; 16],
52        };
53        let name = "same-name";
54        assert_eq!(
55            UUID::new_v3(&ns, name).bytes,
56            UUID::new_v3(&ns, name).bytes,
57            "Deterministic output expected"
58        );
59    }
60
61    #[test]
62    fn matches_manual_digest() {
63        let ns = UUID { bytes: [1u8; 16] };
64        let name = b"xyz";
65
66        // via API
67        let via_api = UUID::new_v3(&ns, name);
68
69        // manual digest  →  from_parts_v3
70        let mut h = Md5::new();
71        h.update(ns.as_bytes());
72        h.update(name);
73        let digest = h.finalize();
74        let mut dig = [0u8; 16];
75        dig.copy_from_slice(&digest);
76        let via_parts = UUID::from_parts_v3(dig);
77
78        assert_eq!(via_api.bytes, via_parts.bytes);
79    }
80
81    #[test]
82    fn rfc_example_python_org() {
83        // RFC-4122 Appendix C (DNS namespace + "python.org")
84        const DNS_NS: [u8; 16] = [
85            0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4,
86            0x30, 0xc8,
87        ];
88        const EXPECTED: [u8; 16] = [
89            0x6f, 0xa4, 0x59, 0xea, 0xee, 0x8a, 0x3c, 0xa4, 0x89, 0x4e, 0xdb, 0x77, 0xe1, 0x60,
90            0x35, 0x5e,
91        ];
92
93        let ns = UUID { bytes: DNS_NS };
94        let uuid = UUID::new_v3(&ns, "python.org");
95        assert_eq!(uuid.bytes, EXPECTED);
96    }
97}