1#[must_use]
10pub fn lower(bytes: &[u8]) -> String {
11 use std::fmt::Write as _;
12 let mut s = String::with_capacity(bytes.len() * 2);
13 for b in bytes {
14 let _ = write!(&mut s, "{b:02x}");
15 }
16 s
17}
18
19#[must_use]
24pub fn decode(s: &str) -> Option<Vec<u8>> {
25 if !s.len().is_multiple_of(2) {
26 return None;
27 }
28 (0..s.len())
29 .step_by(2)
30 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).ok())
31 .collect()
32}
33
34#[cfg(test)]
35mod tests {
36 #![allow(clippy::pedantic, clippy::nursery, missing_docs)]
37
38 use super::*;
39
40 #[test]
41 fn round_trips_and_fails_closed() {
42 assert_eq!(lower(&[0x00, 0xab, 0xff]), "00abff");
43 assert_eq!(decode("00abff"), Some(vec![0x00, 0xab, 0xff]));
44 assert_eq!(decode(""), Some(Vec::new()));
45 assert_eq!(decode("abc"), None, "odd length fails closed");
46 assert_eq!(decode("zz"), None, "non-hex fails closed");
47 }
48}