1#[cfg(feature = "b64")]
10use crate::error::DecodeError;
11use crate::error::TryFromError;
12
13use std::convert::{TryFrom, TryInto};
14use std::fmt;
15
16use blake2::{Blake2b512, Digest};
17
18#[cfg(feature = "b64")]
19use base64::engine::{Engine, general_purpose::URL_SAFE_NO_PAD};
20
21pub fn hash(data: impl AsRef<[u8]>) -> Hash {
22 Hasher::hash(data)
23}
24
25pub struct Hasher {
26 inner: Blake2b512,
27}
28
29impl Hasher {
30 pub fn new() -> Self {
31 Self {
32 inner: Blake2b512::new(),
33 }
34 }
35
36 pub fn update(&mut self, data: impl AsRef<[u8]>) {
37 self.inner.update(data);
38 }
39
40 pub fn finalize(self) -> Hash {
41 let arr = self.inner.finalize();
42 Hash { bytes: arr.into() }
43 }
44
45 pub fn hash(data: impl AsRef<[u8]>) -> Hash {
46 let mut hasher = Hasher::new();
47 hasher.update(data);
48 hasher.finalize()
49 }
50}
51
52#[derive(Clone, PartialEq, Eq)]
53pub struct Hash {
54 bytes: [u8; 64],
55}
56
57impl Hash {
58 pub const LEN: usize = 64;
59
60 pub fn from_slice(slice: &[u8]) -> Self {
63 slice.try_into().unwrap()
64 }
65
66 pub fn to_bytes(&self) -> [u8; 64] {
67 self.bytes
68 }
69}
70
71#[cfg(not(feature = "b64"))]
72impl fmt::Debug for Hash {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 f.debug_tuple("Hash").field(&self.as_ref()).finish()
75 }
76}
77
78#[cfg(feature = "b64")]
79impl fmt::Debug for Hash {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 f.debug_tuple("Hash").field(&self.to_string()).finish()
82 }
83}
84
85#[cfg(feature = "b64")]
86impl fmt::Display for Hash {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 base64::display::Base64Display::new(self.as_ref(), &URL_SAFE_NO_PAD)
89 .fmt(f)
90 }
91}
92
93impl From<[u8; 64]> for Hash {
94 fn from(bytes: [u8; 64]) -> Self {
95 Self { bytes }
96 }
97}
98
99impl TryFrom<&[u8]> for Hash {
100 type Error = TryFromError;
101
102 fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
103 <[u8; 64]>::try_from(v)
104 .map_err(TryFromError::from_any)
105 .map(Self::from)
106 }
107}
108
109#[cfg(feature = "b64")]
110impl crate::FromStr for Hash {
111 type Err = DecodeError;
112
113 fn from_str(s: &str) -> Result<Self, Self::Err> {
114 if s.len() != crate::calculate_b64_len(Self::LEN) {
115 return Err(DecodeError::InvalidLength);
116 }
117
118 let mut bytes = [0u8; Self::LEN];
119 URL_SAFE_NO_PAD
120 .decode_slice_unchecked(s, &mut bytes)
121 .map_err(DecodeError::inv_bytes)
122 .and_then(|_| {
123 Self::try_from(bytes.as_ref()).map_err(DecodeError::inv_bytes)
124 })
125 }
126}
127
128impl AsRef<[u8]> for Hash {
129 fn as_ref(&self) -> &[u8] {
130 &self.bytes
131 }
132}
133
134#[cfg(all(feature = "b64", feature = "serde"))]
135mod impl_serde {
136
137 use super::*;
138
139 use std::borrow::Cow;
140 use std::str::FromStr;
141
142 use _serde::de::Error;
143 use _serde::{Deserialize, Deserializer, Serialize, Serializer};
144
145 impl Serialize for Hash {
146 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
147 where
148 S: Serializer,
149 {
150 serializer.collect_str(&self)
151 }
152 }
153
154 impl<'de> Deserialize<'de> for Hash {
155 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
156 where
157 D: Deserializer<'de>,
158 {
159 let s: Cow<'_, str> = Deserialize::deserialize(deserializer)?;
160 Self::from_str(s.as_ref()).map_err(D::Error::custom)
161 }
162 }
163}
164
165#[cfg(test)]
166mod tests {
167
168 use super::*;
169
170 #[test]
171 fn hash_something() {
172 let bytes: Vec<u8> = (0..=255).collect();
173
174 let hash = Hasher::hash(bytes);
175
176 let hash_bytes = [
177 30, 204, 137, 111, 52, 211, 249, 202, 196, 132, 199, 63, 117, 246,
178 165, 251, 88, 238, 103, 132, 190, 65, 179, 95, 70, 6, 123, 156,
179 101, 198, 58, 103, 148, 211, 215, 68, 17, 44, 101, 63, 115, 221,
180 125, 235, 102, 102, 32, 76, 90, 155, 250, 91, 70, 8, 31, 193, 15,
181 219, 231, 136, 79, 165, 203, 248,
182 ];
183 assert_eq!(hash.to_bytes(), hash_bytes);
184 }
185
186 #[test]
187 #[cfg(feature = "b64")]
188 fn hash_b64() {
189 let bytes: Vec<u8> = (0..=255).collect();
190
191 let hash = Hasher::hash(bytes);
192
193 assert_eq!(
194 hash.to_string(),
195 "HsyJbzTT-crEhMc_dfal-1juZ4S-QbNfRgZ7nGXGOme\
196 U09dEESxlP3PdfetmZiBMWpv6W0YIH8EP2-eIT6XL-A"
197 );
198 }
199}