1use std::{fmt, io::Write, str::FromStr};
4
5use serde::{ser::SerializeTuple, Deserialize, Serialize};
6
7mod fs;
8#[cfg(feature = "md5sum")]
9pub mod io;
10pub mod padded;
11
12#[cfg(feature = "md5sum")]
13pub use fs::md5sum;
14
15#[allow(clippy::upper_case_acronyms)]
22#[derive(PartialEq, Eq, Copy, Clone)]
23pub struct MD5Sum(pub [u8; 16]);
24
25impl MD5Sum {
26 #[cfg(feature = "md5sum")]
28 pub fn compute<B: AsRef<[u8]> + ?Sized>(data: &B) -> Self {
29 Self(md5::compute(data).0)
30 }
31
32 pub fn from_hex_bytes(bytes: &[u8]) -> Result<Self, Error> {
34 if bytes.len() != 32 {
35 return Err(Error::InvalidLength(bytes.len()));
36 }
37
38 let mut arr = [0u8; 16];
39 let mut k = 4;
40
41 for (bi, b) in bytes.iter().copied().enumerate() {
42 let v = match b {
43 b'0'..=b'9' => b - b'0',
44 b'a'..=b'f' => b - b'a' + 10,
45 b'A'..=b'F' => b - b'A' + 10,
46 _ => return Err(Error::InvalidByte { index: bi as u8, b }),
47 };
48 arr[bi >> 1] += v << k;
49 k = 4 - k;
50 }
51
52 Ok(Self(arr))
53 }
54}
55
56impl fmt::Debug for MD5Sum {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 for i in 0..16 {
59 write!(f, "{:02x}", self.0[i])?;
60 }
61 Ok(())
62 }
63}
64
65impl fmt::Display for MD5Sum {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 <Self as fmt::Debug>::fmt(self, f)
68 }
69}
70
71#[derive(Debug)]
72pub enum Error {
74 InvalidLength(usize),
76 InvalidByte {
78 index: u8,
80 b: u8,
82 },
83}
84
85impl std::error::Error for Error {}
86impl fmt::Display for Error {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 match self {
89 Self::InvalidLength(v) => write!(f, "Invalid byte count for md5sum: {}", v),
90 Self::InvalidByte { index, b } => {
91 write!(f, "Invalid byte {} at {} for md5sum", b, index)
92 }
93 }
94 }
95}
96
97impl FromStr for MD5Sum {
98 type Err = Error;
99
100 fn from_str(input: &str) -> Result<Self, Self::Err> {
101 Self::from_hex_bytes(input.as_bytes())
102 }
103}
104
105struct MD5Visitor;
106
107impl<'de> serde::de::Visitor<'de> for MD5Visitor {
108 type Value = MD5Sum;
109
110 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
111 write!(f, "md5sum hex string")
112 }
113
114 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
115 where
116 E: serde::de::Error,
117 {
118 MD5Sum::from_str(v).map_err(|e| E::custom(e.to_string()))
119 }
120}
121
122impl Serialize for MD5Sum {
123 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
124 where
125 S: serde::Serializer,
126 {
127 let mut bytes = [0u8; 32];
128 write!(bytes.as_mut(), "{:?}", self).unwrap();
129 if serializer.is_human_readable() {
130 let str = unsafe { std::str::from_utf8_unchecked(&bytes[..]) };
131 serializer.serialize_str(str)
132 } else {
133 let mut m = serializer.serialize_tuple(32)?;
134 for b in bytes {
135 m.serialize_element(&b)?;
136 }
137 m.end()
138 }
139 }
140}
141
142impl<'de> Deserialize<'de> for MD5Sum {
143 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
144 where
145 D: serde::Deserializer<'de>,
146 {
147 if deserializer.is_human_readable() {
148 deserializer.deserialize_str(MD5Visitor)
149 } else {
150 let bytes = <[u8; 32]>::deserialize(deserializer)?;
151 match MD5Sum::from_hex_bytes(&bytes) {
152 Ok(sum) => Ok(sum),
153 Err(e) => Err(<D::Error as serde::de::Error>::custom(e.to_string())),
154 }
155 }
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::MD5Sum;
162
163 const ARR: [u8; 16] = [
164 0x33, 0x7e, 0x24, 0xd7, 0x26, 0xfd, 0x72, 0x8f, 0x92, 0x95, 0x7a, 0x2c, 0x90, 0x8d, 0xde, 0xe6,
166 ];
167 const STR: &str = "337e24d726fd728f92957a2c908ddee6";
168
169 #[test]
170 fn test() {
171 let s = MD5Sum::from_hex_bytes(STR.as_bytes()).unwrap();
172 assert_eq!(s.0, ARR);
173 let f = format!("{:?}", s);
174 assert_eq!(f, STR);
175 }
176
177 #[test]
178 fn test_bincode() {
179 let s: MD5Sum = bincode::deserialize(STR.as_bytes()).unwrap();
180 assert_eq!(s.0, ARR);
181 let bytes = bincode::serialize(&s).unwrap();
182 assert_eq!(&bytes, STR.as_bytes());
183 }
184
185 #[test]
186 fn test_json() {
187 let input = format!("\"{}\"", STR);
188 let s: MD5Sum = serde_json::from_str(&input).unwrap();
189 assert_eq!(s.0, ARR);
190 let output = serde_json::to_string(&s).unwrap();
191 assert_eq!(output, input);
192 }
193}