use std::error::Error;
use std::fmt;
const LOWER_HEX: &[u8; 16] = b"0123456789abcdef";
const UPPER_HEX: &[u8; 16] = b"0123456789ABCDEF";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum HexDecodeError {
OddLength {
len: usize,
},
InvalidCharacter {
index: usize,
byte: u8,
},
}
impl fmt::Display for HexDecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OddLength { len } => {
write!(f, "hex input must have even byte length, got {len}")
}
Self::InvalidCharacter { index, byte } => {
write!(
f,
"hex input contains invalid byte 0x{byte:02x} at position {index}"
)
}
}
}
}
impl Error for HexDecodeError {}
#[must_use]
pub fn encode_hex_lower(bytes: impl AsRef<[u8]>) -> String {
encode_hex_with_alphabet(bytes.as_ref(), LOWER_HEX)
}
#[must_use]
pub fn encode_hex_upper(bytes: impl AsRef<[u8]>) -> String {
encode_hex_with_alphabet(bytes.as_ref(), UPPER_HEX)
}
pub fn decode_hex(encoded: impl AsRef<str>) -> Result<Vec<u8>, HexDecodeError> {
let encoded = encoded.as_ref();
let bytes = encoded.as_bytes();
if bytes.len() % 2 != 0 {
return Err(HexDecodeError::OddLength { len: bytes.len() });
}
let mut decoded = Vec::with_capacity(bytes.len() / 2);
for (pair_index, chunk) in bytes.chunks_exact(2).enumerate() {
let high_index = pair_index * 2;
let high = decode_nibble(chunk[0], high_index)?;
let low = decode_nibble(chunk[1], high_index + 1)?;
decoded.push((high << 4) | low);
}
Ok(decoded)
}
fn encode_hex_with_alphabet(bytes: &[u8], alphabet: &[u8; 16]) -> String {
let mut encoded = String::with_capacity(bytes.len() * 2);
for byte in bytes {
encoded.push(alphabet[(byte >> 4) as usize] as char);
encoded.push(alphabet[(byte & 0x0f) as usize] as char);
}
encoded
}
fn decode_nibble(byte: u8, index: usize) -> Result<u8, HexDecodeError> {
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(HexDecodeError::InvalidCharacter { index, byte }),
}
}