Skip to main content

ps_uuid/methods/
new_v5.rs

1use crate::{Sha1, UUID};
2
3impl UUID {
4    /// Builds an RFC-4122 Version-5 UUID from `namespace || name`.
5    ///
6    /// Steps
7    /// 1. Hash `namespace.bytes || name` with SHA-1.
8    /// 2. Pass the first 16 bytes of the digest to `from_parts_v5`.
9    #[must_use]
10    pub fn new_v5<N>(namespace: &Self, name: N) -> Self
11    where
12        N: AsRef<[u8]>,
13    {
14        let mut hasher = Sha1::new();
15
16        hasher.update(namespace.as_bytes());
17        hasher.update(name.as_ref());
18
19        let digest = hasher.finalize();
20
21        Self::from_parts_v5(&digest[..16])
22    }
23}
24
25// ────────────────────────────────────────────────────────────────────────────
26// Test-suite
27// ────────────────────────────────────────────────────────────────────────────
28#[cfg(test)]
29mod tests {
30    use super::*;
31    use crate::Sha1;
32
33    const fn is_rfc4122_variant(b: u8) -> bool {
34        (b & 0b1100_0000) == 0b1000_0000
35    }
36
37    #[test]
38    fn version_and_variant_are_correct() {
39        let ns = UUID { bytes: [0u8; 16] };
40        let uuid = UUID::new_v5(&ns, "abc");
41        assert_eq!(uuid.get_version(), Some(5));
42        assert!(is_rfc4122_variant(uuid.bytes[8]));
43    }
44
45    #[test]
46    fn identical_inputs_yield_identical_uuids() {
47        let ns = UUID {
48            bytes: [0x55u8; 16],
49        };
50        let name = b"the-same-name";
51        assert_eq!(
52            UUID::new_v5(&ns, name).bytes,
53            UUID::new_v5(&ns, name).bytes,
54            "Deterministic output expected"
55        );
56    }
57
58    #[test]
59    fn matches_manual_digest() {
60        let ns = UUID { bytes: [7u8; 16] };
61        let name = b"xyz";
62
63        // via API
64        let via_api = UUID::new_v5(&ns, name);
65
66        // Manual SHA-1 → from_parts_v5
67        let mut h = Sha1::new();
68        h.update(ns.as_bytes());
69        h.update(name);
70        let digest = h.finalize();
71        let mut dig = [0u8; 20];
72        dig.copy_from_slice(&digest);
73        let via_parts = UUID::from_parts_v5(dig);
74
75        assert_eq!(via_api.bytes, via_parts.bytes);
76    }
77
78    #[test]
79    fn rfc_example_python_org() {
80        // RFC-4122 Appendix C (DNS namespace + "python.org")
81        const DNS_NS: [u8; 16] = [
82            0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4,
83            0x30, 0xc8,
84        ];
85        const EXPECTED: [u8; 16] = [
86            0x88, 0x63, 0x13, 0xe1, 0x3b, 0x8a, 0x53, 0x72, 0x9b, 0x90, 0x0c, 0x9a, 0xee, 0x19,
87            0x9e, 0x5d,
88        ];
89
90        let ns = UUID { bytes: DNS_NS };
91        let uuid = UUID::new_v5(&ns, "python.org");
92        assert_eq!(uuid.bytes, EXPECTED);
93    }
94}