Hex Encode And Decode
Convert bytes to lowercase hex and back, accepting an optional prefix and embedded whitespace while rejecting invalid digits.
Code
fn encode_hex(bytes: &[u8]) -> String {
const DIGITS: &[u8; 16] = b"0123456789abcdef";
let mut output = String::with_capacity(bytes.len() * 2);
for &byte in bytes {
output.push(DIGITS[(byte >> 4) as usize] as char);
output.push(DIGITS[(byte & 0x0f) as usize] as char);
}
output
}
fn hex_value(byte: u8) -> Result<u8, String> {
match byte {
b'0'..=b'9' => Ok(byte - b'0'),
b'a'..=b'f' => Ok(byte - b'a' + 10),
b'A'..=b'F' => Ok(byte - b'A' + 10),
_ => Err(format!("invalid hex digit: {}", byte as char)),
}
}
fn decode_hex(input: &str) -> Result<Vec<u8>, String> {
let trimmed = input.trim();
let cleaned = trimmed
.strip_prefix("0x")
.or_else(|| trimmed.strip_prefix("0X"))
.unwrap_or(trimmed);
let digits: Vec<u8> = cleaned
.bytes()
.filter(|byte| !byte.is_ascii_whitespace())
.collect();
if digits.len() % 2 != 0 {
return Err("hex input must have an even number of digits".to_string());
}
let mut output = Vec::with_capacity(digits.len() / 2);
for pair in digits.chunks(2) {
let high = hex_value(pair[0])?;
let low = hex_value(pair[1])?;
output.push((high << 4) | low);
}
Ok(output)
}
fn main() {
let payload = b"hello";
let encoded = encode_hex(payload);
println!("encoded: {encoded}");
println!(
"decoded: {:?}",
decode_hex(&encoded).map(|bytes| String::from_utf8_lossy(&bytes).into_owned())
);
println!("prefixed: {:?}", decode_hex("0xDE AD BE EF"));
println!("odd length: {}", decode_hex("abc").is_err());
println!("bad digit: {}", decode_hex("zz").is_err());
}Report a problem with this exampleOpens a pre-filled issue on GitHub.