92 lines
2.4 KiB
Rust
92 lines
2.4 KiB
Rust
//! 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<u8, DecodeError> {
|
|
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<str>) -> Result<Vec<u8>, 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()
|
|
}
|