//! Zero-dependency hex encoding and decoding. use std::fmt::Write; /// The type could not be decoded. #[derive(Debug)] pub enum DecodeError { /// An invalid character was encountered. InvalidCharacter(u8), /// The amount of characters was invalid. Hex strings must be in pairs of two. InvalidCharacterCount(usize), } impl std::fmt::Display for DecodeError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::InvalidCharacter(c) => { write!(f, "Invalid character: {c} not in [0123456789ABCDEF]") } Self::InvalidCharacterCount(n) => { write!(f, "Invalid character count: {n} % 2 != 0") } } } } impl std::error::Error for DecodeError {} /// Encode a given input as a hex string. /// /// # Examples /// ```rust /// let data = b"hello world!"; /// let result = smex::encode(&data); /// assert_eq!(result, "68656c6c6f20776f726c6421"); /// ``` pub fn encode(input: impl AsRef<[u8]>) -> String { let input = input.as_ref(); let mut s = String::new(); for byte in input { write!(s, "{byte:02x}").unwrap(); } s } fn val(c: u8) -> Result { 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(DecodeError::InvalidCharacter(c)), } } /// Attempt to decode a string as hex. /// /// # Errors /// The function may error if a non-hex character is encountered or if the character count is not /// evenly divisible by two. /// /// # Examples /// ```rust /// let data = b"hello world!"; /// let encoded = smex::encode(&data); /// let decoded = smex::decode(&encoded).unwrap(); /// assert_eq!(data.as_slice(), decoded.as_slice()); /// ``` /// /// The function may return an error if the given input is not valid hex. /// /// ```rust,should_panic /// let data = b"hello world!"; /// let mut encoded = smex::encode(&data); /// encoded.push('G'); /// let decoded = smex::decode(&encoded).unwrap(); /// assert_eq!(data.as_slice(), decoded.as_slice()); /// ``` pub fn decode(input: impl AsRef) -> Result, DecodeError> { let input = input.as_ref(); let len = input.len(); if len % 2 != 0 { return Err(DecodeError::InvalidCharacterCount(len)); } input .as_bytes() .chunks_exact(2) .map(|pair| Ok(val(pair[0])? << 4 | val(pair[1])?)) .collect() }