1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2use std::fmt::Write;
46use std::str::FromStr;
47use std::sync::Arc;
48
49use sha2::Digest;
50
51#[cfg(feature = "serde")]
52mod serde;
53
54macro_rules! define_hash_algorithms {
56 ($($variant:ident, $name:literal, [$($alias:literal),*], $size:literal, $hasher:ty;)*) => {
57 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
59 #[non_exhaustive]
60 pub enum HashAlgorithm {
61 $(
62 $variant,
63 )*
64 }
65
66 impl HashAlgorithm {
67 #[must_use]
69 pub const fn name(self) -> &'static str {
70 match self {
71 $(
72 Self::$variant => $name,
73 )*
74 }
75 }
76
77 #[must_use]
79 pub const fn names(self) -> &'static [&'static str] {
80 match self {
81 $(
82 Self::$variant => &[$name $(, $alias)*],
83 )*
84 }
85 }
86
87 pub fn hasher(self) -> Hasher {
89 match self {
90 $(
91 Self::$variant => Hasher {
92 algorithm: self,
93 inner: HasherInner::$variant(<$hasher>::new())
94 },
95 )*
96 }
97 }
98
99 #[must_use]
101 pub const fn hash_size(self) -> usize {
102 match self {
103 $(
104 Self::$variant => $size,
105 )*
106 }
107 }
108 }
109
110 impl FromStr for HashAlgorithm {
111 type Err = InvalidAlgorithmError;
112
113 fn from_str(s: &str) -> Result<Self, Self::Err> {
114 match s {
115 $(
116 $name $(| $alias)* => Ok(Self::$variant),
117 )*
118 _ => Err(InvalidAlgorithmError),
119 }
120 }
121 }
122
123 #[derive(Debug, Clone)]
125 enum HasherInner {
126 $(
127 $variant($hasher),
128 )*
129 }
130
131 impl HasherInner {
132 fn update(&mut self, bytes: &[u8]) {
134 match self {
135 $(
136 HasherInner::$variant(hasher) => hasher.update(bytes),
137 )*
138 }
139 }
140
141 #[must_use]
143 fn finalize<D>(self) -> D
144 where
145 D: for<'slice> From<&'slice [u8]>
146 {
147 match self {
148 $(
149 HasherInner::$variant(hasher) => hasher.finalize().as_slice().into(),
150 )*
151 }
152 }
153 }
154 };
155}
156
157define_hash_algorithms! {
158 Sha256, "sha256", [], 32, sha2::Sha256;
159 Sha512_256, "sha512_256", ["sha512-256"], 32, sha2::Sha512_256;
160 Sha512, "sha512", [], 64, sha2::Sha512;
161}
162
163impl HashAlgorithm {
164 #[must_use]
166 pub fn hash<D>(self, bytes: &[u8]) -> HashDigest<D>
167 where
168 D: for<'slice> From<&'slice [u8]>,
169 {
170 let mut hasher = self.hasher();
171 hasher.update(bytes);
172 hasher.finalize()
173 }
174}
175
176impl std::fmt::Display for HashAlgorithm {
177 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178 if f.alternate() {
179 f.write_str(self.names().last().expect("algorithm has names"))
180 } else {
181 match self {
182 HashAlgorithm::Sha512_256 if cfg!(feature = "legacy") => f.write_str("sha512-256"),
183 _ => f.write_str(self.name()),
184 }
185 }
186 }
187}
188
189#[derive(Debug)]
191pub struct InvalidAlgorithmError;
192
193impl std::fmt::Display for InvalidAlgorithmError {
194 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195 f.write_str("invalid hash algorithm")
196 }
197}
198
199impl std::error::Error for InvalidAlgorithmError {}
200
201#[derive(Debug, Clone)]
203#[must_use]
204pub struct Hasher {
205 algorithm: HashAlgorithm,
206 inner: HasherInner,
207}
208
209impl Hasher {
210 #[must_use]
212 pub const fn algorithm(&self) -> HashAlgorithm {
213 self.algorithm
214 }
215
216 pub fn update(&mut self, bytes: &[u8]) {
218 self.inner.update(bytes);
219 }
220
221 #[must_use]
223 pub fn finalize<D>(self) -> HashDigest<D>
224 where
225 D: for<'slice> From<&'slice [u8]>,
226 {
227 HashDigest {
228 algorithm: self.algorithm,
229 raw: self.inner.finalize(),
230 }
231 }
232}
233
234#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
236pub struct HashDigest<D = Arc<[u8]>> {
237 algorithm: HashAlgorithm,
239 raw: D,
241}
242
243impl<D> HashDigest<D>
244where
245 D: AsRef<[u8]>,
246{
247 pub fn new(algorithm: HashAlgorithm, raw: D) -> Result<Self, InvalidDigestError> {
254 if raw.as_ref().len() != algorithm.hash_size() {
255 return Err(InvalidDigestError("invalid digest size"));
256 }
257 Ok(Self::new_unchecked(algorithm, raw))
258 }
259
260 pub const fn new_unchecked(algorithm: HashAlgorithm, raw: D) -> Self {
263 Self { algorithm, raw }
264 }
265
266 pub const fn algorithm(&self) -> HashAlgorithm {
268 self.algorithm
269 }
270
271 pub fn raw(&self) -> &[u8] {
273 self.raw.as_ref()
274 }
275
276 pub fn raw_hex_string(&self) -> String {
278 hex::encode(&self.raw)
279 }
280
281 pub fn into_inner(self) -> D {
283 self.raw
284 }
285}
286
287impl<D> std::fmt::Display for HashDigest<D>
288where
289 D: AsRef<[u8]>,
290{
291 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
292 self.algorithm.fmt(f)?;
293 if cfg!(feature = "legacy") {
294 f.write_char(':')?;
295 } else {
296 f.write_char('_')?;
297 }
298 f.write_str(&self.raw_hex_string())?;
299 Ok(())
300 }
301}
302
303impl<D> AsRef<[u8]> for HashDigest<D>
304where
305 D: AsRef<[u8]>,
306{
307 fn as_ref(&self) -> &[u8] {
308 self.raw.as_ref()
309 }
310}
311
312impl<D: From<Vec<u8>>> FromStr for HashDigest<D> {
313 type Err = InvalidDigestError;
314
315 fn from_str(s: &str) -> Result<Self, Self::Err> {
316 let Some((algorithm, digest)) = s.rsplit_once([':', '_']) else {
317 return Err(InvalidDigestError("missing delimiter, expected ':' or '_'"));
318 };
319 let algorithm = HashAlgorithm::from_str(algorithm)?;
320 let Ok(raw) = hex::decode(digest) else {
321 return Err(InvalidDigestError("digest is not a hex string"));
322 };
323 if raw.len() != algorithm.hash_size() {
324 return Err(InvalidDigestError("invalid digest size"));
325 }
326 Ok(Self {
327 algorithm,
328 raw: raw.into(),
329 })
330 }
331}
332
333#[derive(Debug)]
335pub struct InvalidDigestError(pub(crate) &'static str);
336
337impl std::fmt::Display for InvalidDigestError {
338 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339 f.write_str(self.0)
340 }
341}
342
343impl std::error::Error for InvalidDigestError {}
344
345impl From<InvalidAlgorithmError> for InvalidDigestError {
346 fn from(_: InvalidAlgorithmError) -> Self {
347 InvalidDigestError("invalid hash algorithm")
348 }
349}