use alloc::{string::String, vec::Vec};
use core::fmt::{LowerHex, Write};
#[cfg(feature = "serde")]
use serde::Serializer;
use snafu::prelude::*;
pub trait Hex {
fn from_hex(hex: &str) -> Result<Self, HexError>
where Self: Sized;
fn to_hex(&self) -> String;
}
#[derive(Debug, Snafu)]
#[allow(missing_docs)]
pub enum HexError {
#[snafu(display("Only hexadecimal characters (0-9,a-f) are permitted"))]
InvalidCharacter {},
#[snafu(display("Hex string lengths must be a multiple of 2"))]
LengthError {},
#[snafu(display("Invalid hex representation for the target type"))]
HexConversionError {},
}
pub fn to_hex<T>(bytes: &[T]) -> String
where T: LowerHex {
let mut s = String::with_capacity(bytes.len() * 2);
for byte in bytes {
write!(&mut s, "{:02x}", byte).expect("Unable to write");
}
s
}
pub fn to_hex_multiple(bytearray: &[Vec<u8>]) -> Vec<String> {
let mut result = Vec::new();
for bytes in bytearray {
result.push(to_hex(bytes))
}
result
}
pub fn from_hex(hex_str: &str) -> Result<Vec<u8>, HexError> {
let hex_trim = hex_str.trim();
if hex_trim.len() % 2 == 1 {
return Err(HexError::LengthError {});
}
if !hex_str.is_ascii() {
return Err(HexError::HexConversionError {});
}
let hex_trim = if (hex_trim.len() >= 2) && (&hex_trim[..2] == "0x") {
&hex_trim[2..]
} else {
hex_trim
};
let num_bytes = hex_trim.len() / 2;
let mut result = vec![0u8; num_bytes];
for i in 0..num_bytes {
result[i] = u8::from_str_radix(&hex_trim[2 * i..2 * (i + 1)], 16).map_err(|_| HexError::InvalidCharacter {})?;
}
Ok(result)
}
#[cfg(feature = "serde")]
pub fn serialize_to_hex<S, T>(t: &T, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Hex,
{
ser.serialize_str(&t.to_hex())
}
#[cfg(test)]
mod test {
use alloc::string::ToString;
use super::*;
#[test]
fn test_to_hex() {
assert_eq!(to_hex(&[0, 0, 0, 0]), "00000000");
assert_eq!(to_hex(&[10, 11, 12, 13]), "0a0b0c0d");
assert_eq!(to_hex(&[0, 0, 0, 255]), "000000ff");
}
#[test]
fn test_from_hex() {
assert_eq!(from_hex("00000000").unwrap(), vec![0, 0, 0, 0]);
assert_eq!(from_hex("0a0b0c0d").unwrap(), vec![10, 11, 12, 13]);
assert_eq!(from_hex(" 0a0b0c0d ").unwrap(), vec![10, 11, 12, 13]);
assert_eq!(from_hex("000000ff").unwrap(), vec![0, 0, 0, 255]);
assert_eq!(from_hex("0x800000ff").unwrap(), vec![128, 0, 0, 255]);
assert!(from_hex("800").is_err()); assert!(from_hex("8080gf").is_err()); assert!(from_hex("🖖🥴").is_err());
}
#[test]
fn test_to_hex_multiple() {
let ba = [vec![16u8, 32], vec![48, 64]];
let hexed = to_hex_multiple(&ba);
assert_eq!(hexed, ["1020", "3040"]);
}
#[test]
fn length_error() {
let result = from_hex("800");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, HexError::LengthError {}));
assert_eq!(err.to_string(), "Hex string lengths must be a multiple of 2");
}
#[test]
fn character_error() {
let result = from_hex("1234567890ABCDEFG1");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, HexError::InvalidCharacter { .. }));
assert_eq!(err.to_string(), "Only hexadecimal characters (0-9,a-f) are permitted");
}
}