1#![cfg_attr(not(feature = "std"), no_std)]
37#![warn(missing_docs, clippy::all, clippy::pedantic)]
38#![allow(
39 clippy::module_name_repetitions,
40 clippy::missing_errors_doc,
41 clippy::missing_panics_doc,
42 clippy::must_use_candidate
43)]
44
45mod decode;
46mod encode;
47
48#[cfg(feature = "simd")]
49mod simd;
50
51pub use decode::{decode_in_place, decode_to_array, decode_to_slice, decoded_len};
52pub use encode::{encode_to_slice, encoded_len};
53
54#[cfg(feature = "std")]
56pub use encode::encode_to_string;
57
58#[derive(Debug, Clone, PartialEq, Eq)]
60pub enum Error {
61 OddLength,
63 OutputTooSmall,
65 InvalidByte {
67 index: usize,
69 byte: u8,
71 },
72}
73
74impl core::fmt::Display for Error {
75 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
76 match self {
77 Error::OddLength => f.write_str("hex string has odd length"),
78 Error::OutputTooSmall => f.write_str("output buffer is too small"),
79 Error::InvalidByte { index, byte } => {
80 let b = *byte;
81 write!(
82 f,
83 "invalid hex byte 0x{:02x} ('{}') at index {}",
84 b,
85 if b.is_ascii_graphic() { b as char } else { '?' },
86 index
87 )
88 }
89 }
90 }
91}
92
93#[cfg(feature = "std")]
94impl std::error::Error for Error {}
95
96#[cfg(test)]
97mod tests {
98 extern crate std;
99 use super::*;
100 use std::prelude::v1::*;
101
102 #[test]
105 fn test_error_eq_odd_length() {
106 assert_eq!(Error::OddLength, Error::OddLength);
107 }
108
109 #[test]
110 fn test_error_eq_output_too_small() {
111 assert_eq!(Error::OutputTooSmall, Error::OutputTooSmall);
112 }
113
114 #[test]
115 fn test_error_eq_invalid_byte() {
116 assert_eq!(
117 Error::InvalidByte {
118 index: 3,
119 byte: b'X'
120 },
121 Error::InvalidByte {
122 index: 3,
123 byte: b'X'
124 },
125 );
126 }
127
128 #[test]
129 fn test_error_ne_different_variants() {
130 assert_ne!(Error::OddLength, Error::OutputTooSmall);
131 assert_ne!(Error::OddLength, Error::InvalidByte { index: 0, byte: 0 },);
132 }
133
134 #[test]
135 fn test_error_ne_different_index() {
136 assert_ne!(
137 Error::InvalidByte {
138 index: 0,
139 byte: b'X'
140 },
141 Error::InvalidByte {
142 index: 1,
143 byte: b'X'
144 },
145 );
146 }
147
148 #[test]
149 fn test_error_ne_different_byte() {
150 assert_ne!(
151 Error::InvalidByte {
152 index: 0,
153 byte: b'X'
154 },
155 Error::InvalidByte {
156 index: 0,
157 byte: b'Y'
158 },
159 );
160 }
161
162 #[test]
163 fn test_error_clone() {
164 let e = Error::InvalidByte {
165 index: 7,
166 byte: 0xAB,
167 };
168 assert_eq!(e.clone(), e);
169
170 assert_eq!(Error::OddLength.clone(), Error::OddLength);
171 assert_eq!(Error::OutputTooSmall.clone(), Error::OutputTooSmall);
172 }
173
174 #[test]
177 fn test_display_odd_length() {
178 let s = std::format!("{}", Error::OddLength);
179 assert_eq!(s, "hex string has odd length");
180 }
181
182 #[test]
183 fn test_display_output_too_small() {
184 let s = std::format!("{}", Error::OutputTooSmall);
185 assert_eq!(s, "output buffer is too small");
186 }
187
188 #[test]
189 fn test_display_invalid_byte_graphic() {
190 let s = std::format!(
192 "{}",
193 Error::InvalidByte {
194 index: 4,
195 byte: b'X'
196 }
197 );
198 assert!(s.contains("0x58"), "hex value missing: {s}");
199 assert!(s.contains('X'), "char missing: {s}");
200 assert!(s.contains('4'), "index missing: {s}");
201 }
202
203 #[test]
204 fn test_display_invalid_byte_non_graphic() {
205 let s = std::format!(
207 "{}",
208 Error::InvalidByte {
209 index: 0,
210 byte: 0x01
211 }
212 );
213 assert!(s.contains("0x01"), "hex value missing: {s}");
214 assert!(s.contains('?'), "fallback char missing: {s}");
215 assert!(s.contains('0'), "index missing: {s}");
216 }
217
218 #[test]
219 fn test_display_invalid_byte_null() {
220 let s = std::format!(
221 "{}",
222 Error::InvalidByte {
223 index: 0,
224 byte: 0x00
225 }
226 );
227 assert!(s.contains("0x00"));
228 assert!(s.contains('?'));
229 }
230
231 #[test]
232 fn test_display_invalid_byte_high_ascii() {
233 let s = std::format!(
234 "{}",
235 Error::InvalidByte {
236 index: 10,
237 byte: 0xFF
238 }
239 );
240 assert!(s.contains("0xff"));
241 assert!(s.contains('?'));
242 assert!(s.contains("10"));
243 }
244
245 #[test]
246 fn test_display_invalid_byte_space() {
247 let s = std::format!(
249 "{}",
250 Error::InvalidByte {
251 index: 1,
252 byte: b' '
253 }
254 );
255 assert!(s.contains("0x20"));
256 assert!(s.contains('?'));
257 }
258
259 #[test]
262 fn test_debug_odd_length() {
263 let s = std::format!("{:?}", Error::OddLength);
264 assert_eq!(s, "OddLength");
265 }
266
267 #[test]
268 fn test_debug_output_too_small() {
269 let s = std::format!("{:?}", Error::OutputTooSmall);
270 assert_eq!(s, "OutputTooSmall");
271 }
272
273 #[test]
274 fn test_debug_invalid_byte() {
275 let s = std::format!(
276 "{:?}",
277 Error::InvalidByte {
278 index: 2,
279 byte: 0xAB
280 }
281 );
282 assert!(s.contains("InvalidByte"));
283 assert!(s.contains("index: 2"));
284 assert!(s.contains("byte: 171")); }
286
287 #[cfg(feature = "std")]
290 #[test]
291 fn test_std_error_trait_odd_length() {
292 let e: &dyn std::error::Error = &Error::OddLength;
293 assert!(e.source().is_none());
294 }
295
296 #[cfg(feature = "std")]
297 #[test]
298 fn test_std_error_trait_invalid_byte() {
299 let e: &dyn std::error::Error = &Error::InvalidByte { index: 0, byte: 0 };
300 assert!(e.source().is_none());
301 }
302
303 #[cfg(feature = "std")]
304 #[test]
305 fn test_std_error_display_matches_fmt() {
306 let errors = [
307 Error::OddLength,
308 Error::OutputTooSmall,
309 Error::InvalidByte {
310 index: 5,
311 byte: b'Z',
312 },
313 ];
314 for e in &errors {
315 let via_display = std::format!("{e}");
316 let via_error: &dyn std::error::Error = e;
317 assert_eq!(via_display, std::format!("{via_error}"));
318 }
319 }
320
321 #[test]
324 fn test_public_api_decode_to_slice() {
325 let mut buf = [0u8; 4];
326 let n = decode_to_slice(b"deadbeef", &mut buf).unwrap();
327 assert_eq!(n, 4);
328 assert_eq!(&buf, &[0xde, 0xad, 0xbe, 0xef]);
329 }
330
331 #[test]
332 fn test_public_api_decode_to_array() {
333 let arr = decode_to_array::<4>(b"deadbeef").unwrap();
334 assert_eq!(arr, [0xde, 0xad, 0xbe, 0xef]);
335 }
336
337 #[test]
338 fn test_public_api_decode_in_place() {
339 let mut buf = *b"deadbeef";
340 let n = decode_in_place(&mut buf).unwrap();
341 assert_eq!(&buf[..n], &[0xde, 0xad, 0xbe, 0xef]);
342 }
343
344 #[test]
345 fn test_public_api_decoded_len() {
346 assert_eq!(decoded_len(8).unwrap(), 4);
347 assert_eq!(decoded_len(1), Err(Error::OddLength));
348 }
349
350 #[test]
351 fn test_public_api_encode_to_slice() {
352 let mut out = [0u8; 8];
353 let n = encode_to_slice(&[0xde, 0xad, 0xbe, 0xef], &mut out, true).unwrap();
354 assert_eq!(n, 8);
355 assert_eq!(&out, b"deadbeef");
356 }
357
358 #[test]
359 fn test_public_api_encoded_len() {
360 assert_eq!(encoded_len(4), 8);
361 assert_eq!(encoded_len(0), 0);
362 }
363
364 #[cfg(feature = "std")]
365 #[test]
366 fn test_public_api_encode_to_string() {
367 let s = encode_to_string(&[0xde, 0xad, 0xbe, 0xef], true);
368 assert_eq!(s, "deadbeef");
369 }
370
371 #[test]
374 fn test_roundtrip_encode_decode() {
375 let src: std::vec::Vec<u8> = (0u8..=255).collect();
376 let mut hex = std::vec![0u8; src.len() * 2];
377 encode_to_slice(&src, &mut hex, true).unwrap();
378 let mut dst = std::vec![0u8; src.len()];
379 decode_to_slice(&hex, &mut dst).unwrap();
380 assert_eq!(dst, src);
381 }
382
383 #[test]
384 fn test_roundtrip_encode_decode_uppercase() {
385 let src: std::vec::Vec<u8> = (0u8..=255).collect();
386 let mut hex = std::vec![0u8; src.len() * 2];
387 encode_to_slice(&src, &mut hex, false).unwrap();
388 let mut dst = std::vec![0u8; src.len()];
389 decode_to_slice(&hex, &mut dst).unwrap();
390 assert_eq!(dst, src);
391 }
392}