1use alloc::string::String;
4
5#[must_use]
12pub fn to_hex_lower(bytes: &[u8]) -> String {
13 render(bytes, b'a')
14}
15
16#[must_use]
23pub fn to_hex_upper(bytes: &[u8]) -> String {
24 render(bytes, b'A')
25}
26
27fn render(bytes: &[u8], ten: u8) -> String {
29 let mut out = String::with_capacity(bytes.len().saturating_mul(2));
30 for &b in bytes {
31 out.push(digit(b >> 4, ten));
32 out.push(digit(b & 0x0F, ten));
33 }
34 out
35}
36
37fn digit(n: u8, ten: u8) -> char {
40 match n & 0x0F {
41 d @ 0..=9 => char::from(b'0' + d),
42 d => char::from(ten + (d - 10)),
43 }
44}
45
46#[cfg(test)]
47mod tests {
48 use super::{to_hex_lower, to_hex_upper};
49 use alloc::vec::Vec;
50
51 #[test]
52 fn empty_input_yields_empty_output() {
53 assert_eq!(to_hex_lower(&[]), "");
54 assert_eq!(to_hex_upper(&[]), "");
55 }
56
57 #[test]
58 fn pads_every_byte_to_two_digits() {
59 assert_eq!(to_hex_lower(&[0x00, 0x01, 0x0a, 0x0f]), "00010a0f");
61 }
62
63 #[test]
64 fn renders_the_full_byte_range() {
65 let all: Vec<u8> = (0u8..=255).collect();
66 let lower = to_hex_lower(&all);
67 assert_eq!(lower.len(), 512);
68 assert!(lower.starts_with("000102"));
69 assert!(lower.ends_with("fdfeff"));
70 assert!(lower.chars().all(|c| c.is_ascii_hexdigit()));
71 assert!(lower.chars().all(|c| !c.is_ascii_uppercase()));
72 }
73
74 #[test]
75 fn upper_and_lower_differ_only_in_case() {
76 let bytes: &[u8] = &[0xde, 0xad, 0xbe, 0xef, 0x01, 0x23];
77 assert_eq!(to_hex_lower(bytes), "deadbeef0123");
78 assert_eq!(to_hex_upper(bytes), "DEADBEEF0123");
79 assert_eq!(
80 to_hex_upper(bytes).to_ascii_lowercase(),
81 to_hex_lower(bytes)
82 );
83 }
84
85 #[test]
86 fn output_length_is_always_twice_the_input() {
87 for n in 0..64usize {
88 let buf = alloc::vec![0xa5u8; n];
89 assert_eq!(to_hex_lower(&buf).len(), n * 2);
90 assert_eq!(to_hex_upper(&buf).len(), n * 2);
91 }
92 }
93}