use alloc::string::String;
#[must_use]
pub fn to_hex_lower(bytes: &[u8]) -> String {
render(bytes, b'a')
}
#[must_use]
pub fn to_hex_upper(bytes: &[u8]) -> String {
render(bytes, b'A')
}
fn render(bytes: &[u8], ten: u8) -> String {
let mut out = String::with_capacity(bytes.len().saturating_mul(2));
for &b in bytes {
out.push(digit(b >> 4, ten));
out.push(digit(b & 0x0F, ten));
}
out
}
fn digit(n: u8, ten: u8) -> char {
match n & 0x0F {
d @ 0..=9 => char::from(b'0' + d),
d => char::from(ten + (d - 10)),
}
}
#[cfg(test)]
mod tests {
use super::{to_hex_lower, to_hex_upper};
use alloc::vec::Vec;
#[test]
fn empty_input_yields_empty_output() {
assert_eq!(to_hex_lower(&[]), "");
assert_eq!(to_hex_upper(&[]), "");
}
#[test]
fn pads_every_byte_to_two_digits() {
assert_eq!(to_hex_lower(&[0x00, 0x01, 0x0a, 0x0f]), "00010a0f");
}
#[test]
fn renders_the_full_byte_range() {
let all: Vec<u8> = (0u8..=255).collect();
let lower = to_hex_lower(&all);
assert_eq!(lower.len(), 512);
assert!(lower.starts_with("000102"));
assert!(lower.ends_with("fdfeff"));
assert!(lower.chars().all(|c| c.is_ascii_hexdigit()));
assert!(lower.chars().all(|c| !c.is_ascii_uppercase()));
}
#[test]
fn upper_and_lower_differ_only_in_case() {
let bytes: &[u8] = &[0xde, 0xad, 0xbe, 0xef, 0x01, 0x23];
assert_eq!(to_hex_lower(bytes), "deadbeef0123");
assert_eq!(to_hex_upper(bytes), "DEADBEEF0123");
assert_eq!(
to_hex_upper(bytes).to_ascii_lowercase(),
to_hex_lower(bytes)
);
}
#[test]
fn output_length_is_always_twice_the_input() {
for n in 0..64usize {
let buf = alloc::vec![0xa5u8; n];
assert_eq!(to_hex_lower(&buf).len(), n * 2);
assert_eq!(to_hex_upper(&buf).len(), n * 2);
}
}
}