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
85 .try_into()
86 .map_err(|_| DigestError(format!("Expected a Vec of length {N} but it was {len}")))?;
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 #[must_use]
132 pub fn name(&self) -> &'static str {
133 match self {
134 HashType::Md5(_) => "md5",
135 HashType::SHA1(_) => "sha1",
136 HashType::SHA256(_) => "sha256",
137 HashType::SHA384(_) => "sha384",
138 HashType::SHA512(_) => "sha512",
139 }
140 }
141
142 #[must_use]
144 pub fn the_hash(&self) -> String {
145 match self {
146 HashType::Md5(h) => h.to_string(),
147 HashType::SHA1(h) => h.to_string(),
148 HashType::SHA256(h) => h.to_string(),
149 HashType::SHA384(h) => h.to_string(),
150 HashType::SHA512(h) => h.to_string(),
151 }
152 }
153}
154
155impl TryFrom<String> for HashType {
156 type Error = DigestError;
157
158 fn try_from(value: String) -> Result<Self, Self::Error> {
159 let decoded = hex::decode(&value).unwrap();
160 Ok(match decoded.len() {
161 16 => HashType::Md5(Digest::try_from(decoded)?),
162 20 => HashType::SHA1(Digest::try_from(decoded)?),
163 32 => HashType::SHA256(Digest::try_from(decoded)?),
164 48 => HashType::SHA384(Digest::try_from(decoded)?),
165 64 => HashType::SHA512(Digest::try_from(decoded)?),
166 _ => return Err(DigestError(format!("unknown hash size {}", value.len()))),
167 })
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn strings() {
177 let digest = Digest([0x00, 0x11, 0x22, 0x33]);
178 assert_eq!(digest.to_string(), "00112233");
179 assert!(HashType::try_from(String::from("00112233")).is_err());
180 }
181
182 #[test]
183 fn sha1() {
184 const TEST: &str = "3204c1ca863c2068214900e831fb8047b934bf88";
185
186 let digest = HashType::try_from(String::from(TEST)).unwrap();
187 assert_eq!(digest.name(), "sha1");
188
189 if let HashType::Md5(_) = digest {
190 panic!("Failed: SHA-1 hash was made into MD-5");
191 }
192
193 if let HashType::SHA256(_) = digest {
194 panic!("Failed: SHA-1 hash was made into SHA-256");
195 }
196
197 if let HashType::SHA384(_) = digest {
198 panic!("Failed: SHA-1 hash was made into SHA-384");
199 }
200
201 if let HashType::SHA512(_) = digest {
202 panic!("Failed: SHA-1 hash was made into SHA-512");
203 }
204 }
205
206 #[test]
207 fn sha256() {
208 const TEST: &str = "d154b8420fc56a629df2e6d918be53310d8ac39a926aa5f60ae59a66298969a0";
209
210 let digest = HashType::try_from(String::from(TEST)).unwrap();
211 assert_eq!(digest.name(), "sha256");
212
213 if let HashType::Md5(_) = digest {
214 panic!("Failed: SHA-256 hash was made into MD-5");
215 }
216
217 if let HashType::SHA1(_) = digest {
218 panic!("Failed: SHA-256 hash was made into SHA-1");
219 }
220
221 if let HashType::SHA384(_) = digest {
222 panic!("Failed: SHA-256 hash was made into SHA-384");
223 }
224
225 if let HashType::SHA512(_) = digest {
226 panic!("Failed: SHA-256 hash was made into SHA-512");
227 }
228 }
229
230 #[test]
231 fn sha512() {
232 const TEST: &str = "dafe60f7d02b0151909550d6f20343d0fe374b044d40221c13295a312489e1b702edbeac99ffda85f61b812b1ddd0c9394cda0c1162bffb716f04d996ff73cdf";
233
234 let digest = HashType::try_from(String::from(TEST)).unwrap();
235 assert_eq!(digest.name(), "sha512");
236
237 if let HashType::Md5(_) = digest {
238 panic!("Failed: SHA-512 hash was made into MD-5");
239 }
240
241 if let HashType::SHA1(_) = digest {
242 panic!("Failed: SHA-512 hash was made into SHA-1");
243 }
244
245 if let HashType::SHA256(_) = digest {
246 panic!("Failed: SHA-512 hash was made into SHA-256");
247 }
248
249 if let HashType::SHA384(_) = digest {
250 panic!("Failed: SHA-512 hash was made into SHA-384");
251 }
252 }
253}