Skip to main content

dig_urn_protocol/
bytes.rs

1//! [`Bytes32`] — the crate's own 32-byte value type.
2//!
3//! `dig-urn-protocol` is a LEAF crate (no `dig-*` dependencies), so it defines its own newtype for
4//! the 32-byte identifiers a URN carries — the store id, the generation root hash, the retrieval
5//! key, and merkle roots/leaves. It is byte-compatible with `digstore_core::Bytes32`: a caller
6//! bridges the two with `Bytes32::from(bytes.0)` / `bytes.0`.
7
8use core::fmt;
9
10/// A 32-byte value (a store id, a root hash, a retrieval key, or a merkle node).
11///
12/// Rendered canonically as **lowercase** hex on the wire; parsing rejects any input that is not
13/// exactly 64 hex digits.
14#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
15pub struct Bytes32(pub [u8; 32]);
16
17impl Bytes32 {
18    /// Parse exactly 64 hex digits into 32 bytes. Rejects wrong-length or non-hex input.
19    ///
20    /// Uppercase hex is accepted on input (so an over-tolerant producer round-trips), but
21    /// [`Bytes32::to_hex`] always re-emits lowercase — the canonical form.
22    pub fn from_hex(hex_str: &str) -> Result<Bytes32, InvalidBytes32> {
23        if hex_str.len() != 64 {
24            return Err(InvalidBytes32);
25        }
26        let mut out = [0u8; 32];
27        hex::decode_to_slice(hex_str, &mut out).map_err(|_| InvalidBytes32)?;
28        Ok(Bytes32(out))
29    }
30
31    /// Render as 64 lowercase hex digits (the canonical wire form).
32    pub fn to_hex(&self) -> String {
33        hex::encode(self.0)
34    }
35}
36
37impl From<[u8; 32]> for Bytes32 {
38    fn from(raw: [u8; 32]) -> Self {
39        Bytes32(raw)
40    }
41}
42
43impl fmt::Debug for Bytes32 {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        write!(f, "Bytes32({})", self.to_hex())
46    }
47}
48
49impl fmt::Display for Bytes32 {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        f.write_str(&self.to_hex())
52    }
53}
54
55/// The input was not exactly 64 lowercase-or-uppercase hex digits.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub struct InvalidBytes32;
58
59impl fmt::Display for InvalidBytes32 {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        f.write_str("value must be exactly 64 hex digits (32 bytes)")
62    }
63}
64
65impl std::error::Error for InvalidBytes32 {}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn roundtrips_lowercase_hex() {
73        let h = "aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899";
74        assert_eq!(Bytes32::from_hex(h).unwrap().to_hex(), h);
75    }
76
77    #[test]
78    fn normalizes_uppercase_to_lowercase() {
79        let upper = "AABB".to_string() + &"00".repeat(30);
80        let lower = "aabb".to_string() + &"00".repeat(30);
81        assert_eq!(Bytes32::from_hex(&upper).unwrap().to_hex(), lower);
82    }
83
84    #[test]
85    fn rejects_wrong_length() {
86        assert_eq!(Bytes32::from_hex("1111"), Err(InvalidBytes32));
87        assert_eq!(Bytes32::from_hex(&"11".repeat(33)), Err(InvalidBytes32));
88    }
89
90    #[test]
91    fn rejects_non_hex() {
92        assert_eq!(Bytes32::from_hex(&"zz".repeat(32)), Err(InvalidBytes32));
93    }
94
95    #[test]
96    fn from_array_and_display() {
97        let b = Bytes32::from([0x11u8; 32]);
98        assert_eq!(b.to_string(), "11".repeat(32));
99        assert!(format!("{b:?}").contains(&"11".repeat(32)));
100    }
101}