1use bytes::Bytes;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub struct Bash(blake3::Hash);
8
9impl Bash {
10 pub const LEN: usize = 32;
11
12 pub fn delimited(components: &[&[u8]], delimiter: u8) -> Self {
15 let mut hasher = blake3::Hasher::new();
16 for component in components {
17 hasher.update(component);
18 hasher.update(&[delimiter]);
19 }
20 Self(hasher.finalize())
21 }
22
23 pub fn length_prefixed(components: &[&[u8]]) -> Self {
25 let mut hasher = blake3::Hasher::new();
26 for component in components {
27 hasher.update(&(component.len() as u64).to_le_bytes());
28 hasher.update(component);
29 }
30 Self(hasher.finalize())
31 }
32
33 pub fn as_bytes(&self) -> &[u8; Bash::LEN] {
34 self.0.as_bytes()
35 }
36}
37
38impl std::fmt::Display for Bash {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.write_str(self.0.to_hex().as_str())
41 }
42}
43
44impl AsRef<[u8]> for Bash {
45 fn as_ref(&self) -> &[u8] {
46 self.as_bytes()
47 }
48}
49
50impl From<Bash> for [u8; Bash::LEN] {
51 fn from(bash: Bash) -> Self {
52 bash.0.into()
53 }
54}
55
56impl From<[u8; Bash::LEN]> for Bash {
57 fn from(bytes: [u8; Bash::LEN]) -> Self {
58 Self(blake3::Hash::from_bytes(bytes))
59 }
60}
61
62impl From<Bash> for Bytes {
63 fn from(bash: Bash) -> Self {
64 Bytes::copy_from_slice(bash.as_bytes())
65 }
66}
67
68impl serde::Serialize for Bash {
69 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
70 where
71 S: serde::Serializer,
72 {
73 serializer.serialize_str(&self.0.to_hex())
74 }
75}
76
77impl<'de> serde::Deserialize<'de> for Bash {
78 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
79 where
80 D: serde::Deserializer<'de>,
81 {
82 let s = String::deserialize(deserializer)?;
83 let hash = blake3::Hash::from_hex(s.as_bytes()).map_err(serde::de::Error::custom)?;
84 Ok(Self(hash))
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::Bash;
91
92 #[test]
93 fn bash_len_prefixed_components_are_unambiguous() {
94 let bash1 = Bash::length_prefixed(&[b"a\0", b"b"]);
95 let bash2 = Bash::length_prefixed(&[b"a", b"\0b"]);
96
97 assert_ne!(bash1, bash2);
98 }
99}