static HEX_LUT: [[u8; 2]; 256] = build_hex_lut();
const fn build_hex_lut() -> [[u8; 2]; 256] {
let mut table = [[0u8; 2]; 256];
let hex = b"0123456789abcdef";
let mut i = 0usize;
while i < 256 {
table[i][0] = hex[(i >> 4) & 0xF];
table[i][1] = hex[i & 0xF];
i += 1;
}
table
}
#[inline]
fn push_byte(out: &mut String, byte: u8) {
let pair = HEX_LUT[byte as usize];
out.push(pair[0] as char);
out.push(pair[1] as char);
}
#[must_use]
pub fn bytes_to_hex(bytes: &[u8]) -> String {
let mut out = String::with_capacity(bytes.len() * 2);
for &byte in bytes {
push_byte(&mut out, byte);
}
out
}
#[must_use]
pub fn u32_words_to_hex(words: &[u32]) -> String {
let mut out = String::with_capacity(words.len() * 8);
for &word in words {
for byte in word.to_be_bytes() {
push_byte(&mut out, byte);
}
}
out
}
#[must_use]
pub fn u64_to_hex(word: u64) -> String {
let mut out = String::with_capacity(16);
for byte in word.to_be_bytes() {
push_byte(&mut out, byte);
}
out
}
#[must_use]
pub fn u64_words_to_hex(words: &[u64]) -> String {
let mut out = String::with_capacity(words.len() * 16);
for &word in words {
for byte in word.to_be_bytes() {
push_byte(&mut out, byte);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bytes_to_hex_matches_format() {
let input: Vec<u8> = (0u16..=255u16).map(|b| b as u8).collect();
let expected: String = input.iter().map(|b| format!("{b:02x}")).collect();
assert_eq!(bytes_to_hex(&input), expected);
}
#[test]
fn u32_words_to_hex_matches_format() {
let words: [u32; 4] = [0, 1, 0xDEADBEEF, u32::MAX];
let expected: String = words.iter().map(|w| format!("{w:08x}")).collect();
assert_eq!(u32_words_to_hex(&words), expected);
}
#[test]
fn u64_words_to_hex_matches_format() {
let words: [u64; 3] = [0, u64::MAX, 0x0123_4567_89AB_CDEF];
let expected: String = words.iter().map(|w| format!("{w:016x}")).collect();
assert_eq!(u64_words_to_hex(&words), expected);
}
#[test]
fn u64_to_hex_matches_format() {
for word in [0u64, 1, 0xDEADBEEFDEADBEEF, u64::MAX] {
assert_eq!(u64_to_hex(word), format!("{word:016x}"));
}
}
}