#[must_use]
pub fn to_hex_lower(bytes: &[u8]) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut s = String::with_capacity(bytes.len() * 2);
for b in bytes {
s.push(HEX[(b >> 4) as usize] as char);
s.push(HEX[(b & 0x0f) as usize] as char);
}
s
}
#[must_use]
pub fn hex_digit(c: u8) -> Option<u8> {
match c {
b'0'..=b'9' => Some(c - b'0'),
b'a'..=b'f' => Some(c - b'a' + 10),
b'A'..=b'F' => Some(c - b'A' + 10),
_ => None,
}
}
#[must_use]
pub fn parse_hex<const N: usize>(s: &str) -> Option<[u8; N]> {
if s.len() != N * 2 {
return None;
}
let bytes = s.as_bytes();
let mut out = [0u8; N];
for (i, byte) in out.iter_mut().enumerate() {
let high = hex_digit(bytes[i * 2])?;
let low = hex_digit(bytes[i * 2 + 1])?;
*byte = (high << 4) | low;
}
Some(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_16() {
let id = [
0x00, 0x12, 0xab, 0xff, 0x9c, 0x3d, 0x40, 0x55, 1, 2, 3, 4, 5, 6, 7, 8,
];
let s = to_hex_lower(&id);
assert_eq!(s.len(), 32);
assert_eq!(parse_hex::<16>(&s), Some(id));
}
#[test]
fn parse_accepts_uppercase() {
assert_eq!(parse_hex::<2>("AbCd"), Some([0xab, 0xcd]));
}
#[test]
fn parse_rejects_wrong_length_and_nonhex() {
assert_eq!(parse_hex::<2>("abc"), None);
assert_eq!(parse_hex::<2>("abcg"), None);
}
}