1use std::borrow::Borrow;
4use std::error::Error;
5use std::fmt::{Display, Formatter};
6use std::ops::Deref;
7
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9
10#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
16pub struct Digest<const N: usize>(pub [u8; N]);
17
18impl<'de, const N: usize> Deserialize<'de> for Digest<N> {
19 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
20 where
21 D: Deserializer<'de>,
22 {
23 use serde::de::Error;
24
25 let dig: String = Deserialize::deserialize(deserializer)?;
26 let dig = hex::decode(dig).map_err(|e| Error::custom(format!("invalid hex: {e}")))?;
27 let dig = dig.try_into().map_err(|v: Vec<_>| {
28 Error::custom(format!(
29 "expected digest to have length of {N}, got {}",
30 v.len()
31 ))
32 })?;
33 Ok(Digest(dig))
34 }
35}
36
37impl<const N: usize> Serialize for Digest<N> {
38 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
39 where
40 S: Serializer,
41 {
42 let hex = self.to_string();
43 serializer.serialize_str(&hex)
44 }
45}
46
47impl<const N: usize> AsRef<[u8; N]> for Digest<N> {
48 fn as_ref(&self) -> &[u8; N] {
49 &self.0
50 }
51}
52
53impl<const N: usize> Borrow<[u8; N]> for Digest<N> {
54 fn borrow(&self) -> &[u8; N] {
55 &self.0
56 }
57}
58
59impl<const N: usize> Deref for Digest<N> {
60 type Target = [u8; N];
61
62 fn deref(&self) -> &Self::Target {
63 &self.0
64 }
65}
66
67#[derive(Debug, Clone)]
69pub struct DigestError(String);
70
71impl Display for DigestError {
72 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
73 write!(f, "{}", self.0)
74 }
75}
76
77impl Error for DigestError {}
78
79impl<const N: usize> TryFrom<Vec<u8>> for Digest<N> {
80 type Error = DigestError;
81
82 fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
83 let len = value.len();
84 let array: [u8; N] = value.try_into().map_err(|_| {
85 DigestError(format!("Expected a Vec of length {} but it was {}", N, len))
86 })?;
87 Ok(Digest(array))
88 }
89}
90
91impl<const N: usize> Display for Digest<N> {
92 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
93 write!(f, "{}", hex::encode(self.0))
94 }
95}
96
97#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Ord, PartialOrd, Hash)]
99pub enum HashType {
100 Md5(Digest<16>),
102
103 SHA1(Digest<20>),
105
106 SHA256(Digest<32>),
108
109 SHA384(Digest<48>),
111
112 SHA512(Digest<64>),
114}
115
116impl Display for HashType {
117 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
118 match self {
119 HashType::Md5(h) => write!(f, "MD5: {h}"),
120 HashType::SHA1(h) => write!(f, "SHA-1: {h}"),
121 HashType::SHA256(h) => write!(f, "SHA-256: {h}"),
122 HashType::SHA384(h) => write!(f, "SHA-384: {h}"),
123 HashType::SHA512(h) => write!(f, "SHA-512: {h}"),
124 }
125 }
126}
127
128impl HashType {
129 pub fn name(&self) -> &'static str {
132 match self {
133 HashType::Md5(_) => "md5",
134 HashType::SHA1(_) => "sha1",
135 HashType::SHA256(_) => "sha256",
136 HashType::SHA384(_) => "sha384",
137 HashType::SHA512(_) => "sha512",
138 }
139 }
140
141 pub fn the_hash(&self) -> String {
143 match self {
144 HashType::Md5(h) => h.to_string(),
145 HashType::SHA1(h) => h.to_string(),
146 HashType::SHA256(h) => h.to_string(),
147 HashType::SHA384(h) => h.to_string(),
148 HashType::SHA512(h) => h.to_string(),
149 }
150 }
151}
152
153impl TryFrom<String> for HashType {
154 type Error = DigestError;
155
156 fn try_from(value: String) -> Result<Self, Self::Error> {
157 let decoded = hex::decode(&value).unwrap();
158 Ok(match decoded.len() {
159 16 => HashType::Md5(Digest::try_from(decoded)?),
160 20 => HashType::SHA1(Digest::try_from(decoded)?),
161 32 => HashType::SHA256(Digest::try_from(decoded)?),
162 48 => HashType::SHA384(Digest::try_from(decoded)?),
163 64 => HashType::SHA512(Digest::try_from(decoded)?),
164 _ => return Err(DigestError(format!("unknown hash size {}", value.len()))),
165 })
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn strings() {
175 let digest = Digest([0x00, 0x11, 0x22, 0x33]);
176 assert_eq!(digest.to_string(), "00112233");
177 assert!(HashType::try_from(String::from("00112233")).is_err());
178 }
179
180 #[test]
181 fn sha1() {
182 const TEST: &str = "3204c1ca863c2068214900e831fb8047b934bf88";
183
184 let digest = HashType::try_from(String::from(TEST)).unwrap();
185 assert_eq!(digest.name(), "sha1");
186
187 if let HashType::Md5(_) = digest {
188 panic!("Failed: SHA-1 hash was made into MD-5");
189 }
190
191 if let HashType::SHA256(_) = digest {
192 panic!("Failed: SHA-1 hash was made into SHA-256");
193 }
194
195 if let HashType::SHA384(_) = digest {
196 panic!("Failed: SHA-1 hash was made into SHA-384");
197 }
198
199 if let HashType::SHA512(_) = digest {
200 panic!("Failed: SHA-1 hash was made into SHA-512");
201 }
202 }
203
204 #[test]
205 fn sha256() {
206 const TEST: &str = "d154b8420fc56a629df2e6d918be53310d8ac39a926aa5f60ae59a66298969a0";
207
208 let digest = HashType::try_from(String::from(TEST)).unwrap();
209 assert_eq!(digest.name(), "sha256");
210
211 if let HashType::Md5(_) = digest {
212 panic!("Failed: SHA-256 hash was made into MD-5");
213 }
214
215 if let HashType::SHA1(_) = digest {
216 panic!("Failed: SHA-256 hash was made into SHA-1");
217 }
218
219 if let HashType::SHA384(_) = digest {
220 panic!("Failed: SHA-256 hash was made into SHA-384");
221 }
222
223 if let HashType::SHA512(_) = digest {
224 panic!("Failed: SHA-256 hash was made into SHA-512");
225 }
226 }
227
228 #[test]
229 fn sha512() {
230 const TEST: &str = "dafe60f7d02b0151909550d6f20343d0fe374b044d40221c13295a312489e1b702edbeac99ffda85f61b812b1ddd0c9394cda0c1162bffb716f04d996ff73cdf";
231
232 let digest = HashType::try_from(String::from(TEST)).unwrap();
233 assert_eq!(digest.name(), "sha512");
234
235 if let HashType::Md5(_) = digest {
236 panic!("Failed: SHA-512 hash was made into MD-5");
237 }
238
239 if let HashType::SHA1(_) = digest {
240 panic!("Failed: SHA-512 hash was made into SHA-1");
241 }
242
243 if let HashType::SHA256(_) = digest {
244 panic!("Failed: SHA-512 hash was made into SHA-256");
245 }
246
247 if let HashType::SHA384(_) = digest {
248 panic!("Failed: SHA-512 hash was made into SHA-384");
249 }
250 }
251}