warg_crypto/hash/
dynamic.rs1use super::{Digest, HashAlgorithm, Sha256};
2use anyhow::Error;
3use serde::{Deserialize, Serialize};
4use std::{fmt, ops::Deref, str::FromStr};
5use thiserror::Error;
6
7pub enum Hasher {
8 Sha256(Sha256),
9}
10
11impl Hasher {
12 pub fn update(&mut self, bytes: &[u8]) {
13 match self {
14 Self::Sha256(d) => d.update(bytes),
15 }
16 }
17
18 pub fn finalize(self) -> AnyHash {
19 let (algo, bytes) = match self {
20 Self::Sha256(d) => (HashAlgorithm::Sha256, d.finalize().deref().into()),
21 };
22
23 AnyHash { algo, bytes }
24 }
25}
26
27impl HashAlgorithm {
28 pub fn hasher(&self) -> Hasher {
29 match self {
30 HashAlgorithm::Sha256 => Hasher::Sha256(Sha256::new()),
31 }
32 }
33
34 pub fn digest(&self, content_bytes: &[u8]) -> AnyHash {
35 let hash_bytes: Vec<u8> = match self {
36 HashAlgorithm::Sha256 => {
37 let mut d = Sha256::new();
38 d.update(content_bytes);
39 d.finalize().deref().into()
40 }
41 };
42
43 AnyHash {
44 algo: *self,
45 bytes: hash_bytes,
46 }
47 }
48}
49
50#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
51pub struct AnyHash {
52 pub(crate) algo: HashAlgorithm,
53 pub(crate) bytes: Vec<u8>,
54}
55
56impl AnyHash {
57 pub fn new(algo: HashAlgorithm, bytes: Vec<u8>) -> AnyHash {
58 AnyHash { algo, bytes }
59 }
60
61 pub fn algorithm(&self) -> HashAlgorithm {
62 self.algo
63 }
64
65 pub fn bytes(&self) -> &[u8] {
66 &self.bytes
67 }
68}
69
70impl fmt::Display for AnyHash {
71 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
72 write!(f, "{}:{}", self.algo, hex::encode(self.bytes.as_slice()))
73 }
74}
75
76impl fmt::Debug for AnyHash {
77 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
78 write!(f, "{}:{}", self.algo, hex::encode(self.bytes.as_slice()))
79 }
80}
81
82impl FromStr for AnyHash {
83 type Err = AnyHashError;
84
85 fn from_str(s: &str) -> Result<Self, Self::Err> {
86 let (algo_part, bytes_part) = s
87 .split_once(':')
88 .ok_or_else(|| AnyHashError::IncorrectStructure(s.matches(':').count() + 1))?;
89
90 if bytes_part.chars().any(|c| "ABCDEF".contains(c)) {
91 return Err(AnyHashError::UppercaseHex);
92 }
93
94 let algo = algo_part.parse::<HashAlgorithm>()?;
95 let bytes = hex::decode(bytes_part)?;
96
97 Ok(AnyHash { algo, bytes })
98 }
99}
100
101#[derive(Error, Debug)]
102pub enum AnyHashError {
103 #[error("expected two parts for hash; found {0}")]
104 IncorrectStructure(usize),
105
106 #[error("unable to parse hash algorithm: {0}")]
107 InvalidHashAlgorithm(#[from] Error),
108
109 #[error("hash contained uppercase hex values")]
110 UppercaseHex,
111
112 #[error("hexadecimal decode failed: {0}")]
113 InvalidHex(#[from] hex::FromHexError),
114}
115
116impl Serialize for AnyHash {
117 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
118 serializer.serialize_str(&self.to_string())
119 }
120}
121
122impl<'de> Deserialize<'de> for AnyHash {
123 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
124 where
125 D: serde::Deserializer<'de>,
126 {
127 Self::from_str(&String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn test_sha256_labeled_digest() {
137 let input = b"The quick brown fox jumped over the lazy dog";
138 let output = HashAlgorithm::Sha256.digest(input);
139 let output = format!("{}", output);
140
141 let expected = "sha256:7d38b5cd25a2baf85ad3bb5b9311383e671a8a142eb302b324d4a5fba8748c69";
142
143 assert_eq!(output, expected)
144 }
145
146 #[test]
147 fn test_labeled_digest_parse_rejects_uppercase() {
148 let digest_str = "sha256:7d38b5cd25a2baf85ad3bb5b9311383e671a8a142eb302b324d4a5fba8748c69";
149 assert!(digest_str.parse::<AnyHash>().is_ok());
150
151 let (algo, encoded) = digest_str.split_once(':').unwrap();
152 let digest_str = String::from(algo) + ":" + &encoded.to_uppercase();
153 assert!(digest_str.parse::<AnyHash>().is_err());
154 }
155
156 #[test]
157 fn test_labeled_digest_roundtrip() {
158 let input = "sha256:7d38b5cd25a2baf85ad3bb5b9311383e671a8a142eb302b324d4a5fba8748c69";
159 let output = format!("{}", input.parse::<AnyHash>().unwrap());
160 assert_eq!(input, &output);
161 }
162}