Skip to main content

ps_uuid/methods/
from_parts_v5.rs

1use crate::UUID;
2
3// ────────────────────────────────────────────────────────────────────────────
4// v5 constructor from a pre-computed SHA-1 digest
5// ────────────────────────────────────────────────────────────────────────────
6
7impl UUID {
8    /// Builds an RFC-4122 Version-5 UUID from a raw 20-byte SHA-1 digest.
9    ///
10    /// The caller must supply the digest that results from hashing
11    /// `namespace.bytes || name`.
12    /// The first 16 bytes of the digest are copied into the UUID; the
13    /// version and variant fields are then fixed via `.with_version(5)`.
14    #[must_use]
15    pub fn from_parts_v5<D>(digest: D) -> Self
16    where
17        D: AsRef<[u8]>,
18    {
19        let digest = digest.as_ref();
20        let mut uuid = Self::nil();
21
22        uuid.bytes[..digest.len().min(16)].copy_from_slice(&digest[..digest.len().min(16)]);
23
24        uuid.with_version(5)
25    }
26}
27
28// ────────────────────────────────────────────────────────────────────────────
29// Tests
30// ────────────────────────────────────────────────────────────────────────────
31#[cfg(test)]
32mod tests {
33    #![allow(clippy::cast_possible_truncation, clippy::expect_used)]
34    use std::str::FromStr;
35
36    use crate::{sha1, UUID};
37
38    // Two MSBs must equal `10`.
39    const fn is_rfc4122_variant(b: u8) -> bool {
40        (b & 0b1100_0000) == 0b1000_0000
41    }
42
43    #[test]
44    fn sets_version_and_variant_correctly() {
45        for &input in &[[0u8; 20], [0xFFu8; 20]] {
46            let uuid = UUID::from_parts_v5(input);
47            assert_eq!(uuid.get_version(), Some(5));
48            assert!(is_rfc4122_variant(uuid.bytes[8]));
49        }
50    }
51
52    #[test]
53    fn preserves_all_other_bits() {
54        let mut digest = [0u8; 20];
55        for (i, b) in digest.iter_mut().enumerate() {
56            *b = i as u8;
57        }
58
59        let uuid = UUID::from_parts_v5(digest);
60
61        for i in 0..16 {
62            match i {
63                // Upper nibble of byte 6 carries the version.
64                6 => assert_eq!(uuid.bytes[6] & 0x0F, digest[6] & 0x0F),
65                // Upper two bits of byte 8 carry the variant.
66                8 => assert_eq!(uuid.bytes[8] & 0x3F, digest[8] & 0x3F),
67                _ => assert_eq!(uuid.bytes[i], digest[i]),
68            }
69        }
70    }
71
72    #[test]
73    fn matches_rfc_reference_example() {
74        // RFC-4122 Appendix C  (DNS namespace, name = "python.org")
75        let ns = UUID::from_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
76            .expect("failed to parse UUID test vector");
77        let name = b"python.org";
78
79        // Digest and UUID via `from_parts_v5`
80        let digest = sha1(&[&ns.bytes[..], name].concat());
81        let via_parts = UUID::from_parts_v5(digest);
82
83        // Expected UUID from the RFC
84        let expected = UUID::from_str("886313e1-3b8a-5372-9b90-0c9aee199e5d")
85            .expect("failed to parse UUID test vector");
86
87        assert_eq!(via_parts.bytes, expected.bytes);
88    }
89}