use core::fmt;
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
InvalidHexCharacter {
c: char,
index: usize,
},
OddLength,
}
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidHexCharacter { c, index } => {
write!(f, "Invalid character {} at position {}", c, index)
}
Self::OddLength => write!(f, "Odd number of digits"),
}
}
}
const fn val(c: u8, idx: usize) -> Result<u8, Error> {
match c {
b'A'..=b'F' => Ok(c - b'A' + 10),
b'a'..=b'f' => Ok(c - b'a' + 10),
b'0'..=b'9' => Ok(c - b'0'),
_ => Err(Error::InvalidHexCharacter {
c: c as char,
index: idx,
}),
}
}
pub fn decode<T>(hex: T) -> Result<Vec<u8>, Error>
where
T: AsRef<[u8]>,
{
let hex = hex.as_ref();
let len = hex.len();
if len % 2 != 0 {
return Err(Error::OddLength);
}
let mut bytes: Vec<u8> = Vec::with_capacity(len / 2);
for i in (0..len).step_by(2) {
let high = val(hex[i], i)?;
let low = val(hex[i + 1], i + 1)?;
bytes.push(high << 4 | low);
}
Ok(bytes)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decode() {
assert_eq!(
decode("666f6f626172"),
Ok(String::from("foobar").into_bytes())
);
}
#[test]
pub fn test_invalid_length() {
assert_eq!(decode("1").unwrap_err(), Error::OddLength);
assert_eq!(decode("666f6f6261721").unwrap_err(), Error::OddLength);
}
#[test]
pub fn test_invalid_char() {
assert_eq!(
decode("66ag").unwrap_err(),
Error::InvalidHexCharacter { c: 'g', index: 3 }
);
}
}