1use std::fmt;
9
10use thiserror::Error;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
14#[non_exhaustive]
15pub enum HexError {
16 #[error("invalid hex character")]
18 InvalidChar,
19 #[error("invalid hex length: {0}")]
21 InvalidLength(usize),
22 #[error("hex length mismatch: expected {expected} bytes, got {actual}")]
24 LengthMismatch {
25 expected: usize,
27 actual: usize,
29 },
30 #[error("hex decoder reported an internal overflow")]
34 Overflow,
35}
36
37impl From<faster_hex::Error> for HexError {
38 fn from(err: faster_hex::Error) -> Self {
39 match err {
40 faster_hex::Error::InvalidChar => Self::InvalidChar,
41 faster_hex::Error::InvalidLength(len) => Self::InvalidLength(len),
42 faster_hex::Error::Overflow => Self::Overflow,
43 }
44 }
45}
46
47#[must_use]
49pub fn encode<T>(bytes: T) -> String
50where
51 T: AsRef<[u8]>,
52{
53 faster_hex::hex_string(bytes.as_ref())
54}
55
56pub fn encode_to_slice<T>(bytes: T, out: &mut [u8]) -> Result<(), HexError>
64where
65 T: AsRef<[u8]>,
66{
67 let bytes = bytes.as_ref();
68 let expected = bytes.len() * 2;
69 if out.len() != expected {
70 return Err(HexError::LengthMismatch {
71 expected,
72 actual: out.len(),
73 });
74 }
75 faster_hex::hex_encode(bytes, out).map_err(HexError::from)?;
76 Ok(())
77}
78
79pub fn decode<T>(input: T) -> Result<Vec<u8>, HexError>
86where
87 T: AsRef<[u8]>,
88{
89 let input = input.as_ref();
90 if input.len() % 2 != 0 {
91 return Err(HexError::InvalidLength(input.len()));
92 }
93
94 let mut out = vec![0_u8; input.len() / 2];
95 faster_hex::hex_decode(input, &mut out).map_err(HexError::from)?;
96 Ok(out)
97}
98
99pub fn decode_to_slice<T>(input: T, out: &mut [u8]) -> Result<(), HexError>
109where
110 T: AsRef<[u8]>,
111{
112 let input = input.as_ref();
113 let expected = out.len() * 2;
114 if input.len() != expected {
115 return Err(HexError::LengthMismatch {
116 expected,
117 actual: input.len(),
118 });
119 }
120
121 faster_hex::hex_decode(input, out).map_err(HexError::from)?;
122 Ok(())
123}
124
125pub fn fmt_lower<T>(bytes: T, f: &mut fmt::Formatter<'_>) -> fmt::Result
134where
135 T: AsRef<[u8]>,
136{
137 for byte in bytes.as_ref() {
138 write!(f, "{byte:02x}")?;
139 }
140 Ok(())
141}
142
143#[cfg(test)]
144mod tests {
145 use hex_literal::hex;
146
147 use super::*;
148
149 #[test]
150 fn encode_roundtrip() {
151 let bytes = hex!("deadbeef");
152 let encoded = encode(bytes);
153 assert_eq!(encoded, "deadbeef");
154 assert_eq!(decode(&encoded).unwrap(), bytes);
155 }
156
157 #[test]
158 fn encode_to_slice_exact() {
159 let bytes = hex!("00112233");
160 let mut buf = [0_u8; 8];
161 encode_to_slice(bytes, &mut buf).unwrap();
162 assert_eq!(&buf, b"00112233");
163 }
164
165 #[test]
166 fn encode_to_slice_wrong_length() {
167 let bytes = hex!("ab");
168 let mut buf = [0_u8; 4];
169 let err = encode_to_slice(bytes, &mut buf).unwrap_err();
170 assert!(matches!(
171 err,
172 HexError::LengthMismatch {
173 expected: 2,
174 actual: 4
175 }
176 ));
177 }
178
179 #[test]
180 fn decode_to_slice_exact() {
181 let mut buf = [0_u8; 4];
182 decode_to_slice("deadbeef", &mut buf).unwrap();
183 assert_eq!(buf, hex!("deadbeef"));
184 }
185
186 #[test]
187 fn decode_odd_length() {
188 let err = decode("abc").unwrap_err();
189 assert!(matches!(err, HexError::InvalidLength(3)));
190 }
191
192 #[test]
193 fn decode_invalid_char() {
194 let err = decode("zz").unwrap_err();
195 assert_eq!(err, HexError::InvalidChar);
196 }
197
198 #[test]
199 fn decode_to_slice_length_mismatch() {
200 let mut buf = [0_u8; 2];
201 let err = decode_to_slice("aabbcc", &mut buf).unwrap_err();
202 assert!(matches!(
203 err,
204 HexError::LengthMismatch {
205 expected: 4,
206 actual: 6
207 }
208 ));
209 }
210
211 #[test]
212 fn fmt_lower_matches_encode() {
213 struct Wrap([u8; 4]);
214 impl fmt::Display for Wrap {
215 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216 fmt_lower(self.0, f)
217 }
218 }
219 assert_eq!(Wrap(hex!("0a1b2c3d")).to_string(), "0a1b2c3d");
220 }
221}