#![doc = include_str!("../README.md")]
use std::{convert::Into, num::ParseIntError, string::FromUtf8Error};
const MEGABYTE: usize = 1024 * 1024;
const STR_MAX_LENGTH: usize = 256 * MEGABYTE;
const HEX_BYTES: &str = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\
202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f\
404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f\
606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f\
808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f\
a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf\
c0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedf\
e0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HexError {
LengthOne,
OddLength,
NotHexChar,
ParseInt(ParseIntError),
ExceedsMaxLength,
NonAsciiChar,
InvalidUtf8(FromUtf8Error),
StringDoesNotMatchBufferLength,
}
impl From<ParseIntError> for HexError {
fn from(e: ParseIntError) -> Self {
HexError::ParseInt(e)
}
}
impl From<FromUtf8Error> for HexError {
fn from(e: FromUtf8Error) -> Self {
HexError::InvalidUtf8(e)
}
}
fn verify_ascii(byte: u8) -> Result<(), HexError> {
if byte >= 128 {
return Err(HexError::NonAsciiChar);
}
Ok(())
}
pub fn decode(raw_s: &str) -> Result<Vec<u8>, HexError> {
let sanitized_s = match raw_s.len() {
0 => return Ok(Vec::new()),
1 => return Err(HexError::LengthOne),
_ => {
if &raw_s.as_bytes()[0..2] == b"0x" {
&raw_s[2..]
} else {
raw_s
}
}
};
let sanitized_s_byte_len = sanitized_s.len();
let is_even_len = sanitized_s_byte_len % 2 == 0;
let is_lt_max_len = sanitized_s_byte_len < STR_MAX_LENGTH;
match (is_even_len, is_lt_max_len) {
(true, true) => {
let sanitized_s_bytes = sanitized_s.as_bytes();
(0..sanitized_s_byte_len)
.step_by(2)
.map(|i| {
verify_ascii(sanitized_s_bytes[i])?;
verify_ascii(sanitized_s_bytes[i + 1])?;
let s = std::str::from_utf8(&sanitized_s_bytes[i..i + 2])
.expect(
"We ensure that input slice represents ASCII above. qed.",
);
u8::from_str_radix(s, 16).map_err(Into::into)
})
.collect()
}
(true | false, false) => Err(HexError::ExceedsMaxLength),
(false, true) => Err(HexError::OddLength),
}
}
pub fn decode_to_buf(raw_s: &str, buf: &mut [u8]) -> Result<(), HexError> {
let sanitized_s_bytes = match raw_s.len() {
0 => {
if buf.is_empty() {
return Ok(());
}
return Err(HexError::StringDoesNotMatchBufferLength);
}
1 => return Err(HexError::LengthOne),
_ => {
if &raw_s.as_bytes()[0..2] == b"0x" {
&raw_s.as_bytes()[2..]
} else {
raw_s.as_bytes()
}
}
};
if sanitized_s_bytes.len() % 2 != 0 {
return Err(HexError::OddLength);
}
if sanitized_s_bytes.len() / 2 != buf.len() {
return Err(HexError::StringDoesNotMatchBufferLength);
}
for (i, b) in buf.iter_mut().enumerate() {
let str_idx = i * 2;
verify_ascii(sanitized_s_bytes[str_idx])?;
verify_ascii(sanitized_s_bytes[str_idx + 1])?;
let s = std::str::from_utf8(&sanitized_s_bytes[str_idx..str_idx + 2])
.expect("We ensure that input slice represents ASCII above. qed.");
*b = u8::from_str_radix(s, 16)?;
}
Ok(())
}
pub fn decode_from_vec(vec: Vec<u8>) -> Result<Vec<u8>, HexError> {
let hex_string = String::from_utf8(vec).map_err(HexError::from)?;
let hex_string = hex_string.trim();
decode(hex_string)
}
#[must_use]
pub fn encode(bytes: &[u8]) -> String {
bytes
.iter()
.map(|&b| {
let i = 2 * b as usize;
HEX_BYTES.get(i..i + 2).expect(
"HEX_BYTES represents 00..=ff, and thus any valid u8. qed.",
)
})
.collect()
}
#[must_use]
pub fn encode_to_vec(bytes: &[u8]) -> Vec<u8> {
encode(bytes).into_bytes()
}
pub trait FromHex: Sized {
fn from_hex(raw_s: &str) -> Result<Self, HexError>;
}
impl FromHex for Vec<u8> {
fn from_hex(raw_s: &str) -> Result<Self, HexError> {
decode(raw_s)
}
}
macro_rules! from_hex_array_impl {
($($len:expr)+) => {$(
impl FromHex for [u8; $len] {
fn from_hex(raw_s: &str) -> Result<Self, HexError> {
let mut out = [0_u8; $len];
decode_to_buf(raw_s, &mut out)?;
Ok(out)
}
}
)+}
}
from_hex_array_impl! {
1 2 6 8 10 12 14 16
32 33 34 64 65 66
128 256
384
512 768 1024 2048 4096 8192 16384
}
#[cfg(feature = "serde")]
pub mod serde {
use core::{fmt, marker::PhantomData};
use serde::{Deserializer, Serializer, de::Visitor};
use super::{FromHex, encode};
pub fn serialize<T, S>(bytes: T, serializer: S) -> Result<S::Ok, S::Error>
where
T: AsRef<[u8]>,
S: Serializer,
{
let hex = encode(bytes.as_ref());
serializer.serialize_str(&hex)
}
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: FromHex,
{
struct StrVisitor<T>(PhantomData<T>);
impl<'de, T> Visitor<'de> for StrVisitor<T>
where
T: FromHex,
{
type Value = T;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "a hex encoded string")
}
fn visit_str<E>(self, data: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
FromHex::from_hex(data)
.map_err(|e| serde::de::Error::custom(format!("{e:?}")))
}
fn visit_borrowed_str<E>(
self,
data: &'de str,
) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
FromHex::from_hex(data)
.map_err(|e| serde::de::Error::custom(format!("{e:?}")))
}
}
deserializer.deserialize_str(StrVisitor(PhantomData))
}
pub mod option {
use super::{Deserializer, FromHex, Serializer, encode};
use serde::{Deserialize, Serialize};
pub fn serialize<T, S>(
value: &Option<T>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
T: AsRef<[u8]>,
S: Serializer,
{
value
.as_ref()
.map(|bytes| encode(bytes.as_ref()))
.serialize(serializer)
}
pub fn deserialize<'de, D, T>(
deserializer: D,
) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: FromHex,
{
Option::<String>::deserialize(deserializer)?
.as_deref()
.map(FromHex::from_hex)
.map(|res| {
res.map_err(|e| serde::de::Error::custom(format!("{e:?}")))
})
.transpose()
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn decode_works_with_len_zero() {
let encoded = "";
let res = decode(encoded);
assert_eq!(res, Ok(Vec::new()));
let mut buf = [0u8; 0];
assert!(decode_to_buf(encoded, &mut buf).is_ok());
}
#[test]
fn decode_correctly_errors_with_len_one() {
let encoded = "a";
let res = decode(encoded);
assert_eq!(res, Err(HexError::LengthOne));
let encoded = " ";
let res = decode(encoded);
assert_eq!(res, Err(HexError::LengthOne));
let mut buf = [0u8; 2];
assert_eq!(decode_to_buf(encoded, &mut buf), Err(HexError::LengthOne));
}
#[test]
fn decode_correctly_errors_with_non_ascii() {
let encoded = "0fÓ";
let res = decode(encoded);
assert_eq!(res, Err(HexError::NonAsciiChar));
let mut buf = vec![0u8; encoded.len() / 2];
assert_eq!(
decode_to_buf(encoded, &mut buf),
Err(HexError::NonAsciiChar)
);
let encoded = "0x0fÓ";
let res = decode(encoded);
assert_eq!(res, Err(HexError::NonAsciiChar));
let mut buf = vec![0u8; (encoded.len() - 2) / 2];
assert_eq!(
decode_to_buf(encoded, &mut buf),
Err(HexError::NonAsciiChar)
);
let encoded = "Óff";
let res = decode(encoded);
assert_eq!(res, Err(HexError::NonAsciiChar));
let mut buf = vec![0u8; encoded.len() / 2];
assert_eq!(
decode_to_buf(encoded, &mut buf),
Err(HexError::NonAsciiChar)
);
let encoded = "C6ff584301800c5f60000000000000000000000000Óf8$6800;033333333333333333333333344444444333";
let res = decode(encoded);
assert_eq!(res, Err(HexError::NonAsciiChar));
let mut buf = vec![0u8; encoded.len() / 2];
assert_eq!(
decode_to_buf(encoded, &mut buf),
Err(HexError::NonAsciiChar)
);
}
#[test]
fn encode_and_decode_work() {
let decoded = vec![0, 0, 0, 0];
let encoded = "00000000";
assert_eq!(encode(&decoded), encoded);
assert_eq!(decode(encoded).unwrap(), decoded);
let mut buf = [0u8; 4];
assert!(decode_to_buf(encoded, &mut buf).is_ok());
assert_eq!(buf.to_vec(), decoded);
let decoded = vec![255, 0, 255];
let encoded = "ff00ff";
assert_eq!(encode(&decoded), encoded);
assert_eq!(decode(encoded).unwrap(), decoded);
let decoded = vec![31, 52, 228, 109, 140, 170, 124, 94];
let encoded = "1f34e46d8caa7c5e";
assert_eq!(encode(&decoded), encoded);
assert_eq!(decode(encoded).unwrap(), decoded);
}
#[test]
fn encode_and_decode_with_all_hex_chars() {
let decoded: Vec<_> = (0..=255u8).collect();
let encoded = HEX_BYTES;
assert_eq!(encode(&decoded), encoded);
assert_eq!(decode(encoded).unwrap(), decoded);
let mut buf = vec![0u8; encoded.len() / 2];
assert!(decode_to_buf(encoded, &mut buf).is_ok());
assert_eq!(buf.clone(), decoded);
}
#[test]
fn encode_and_decode_handles_0x_prefix_and_mixed_casing() {
let decoded = vec![
0, 0, 0, 0, 33, 154, 181, 64, 53, 108, 187, 131, 156, 190, 5, 48,
61, 119, 5, 250,
];
let address = "0x00000000219ab540356cBB839Cbe05303d7705Fa";
let mut encoded = address[2..].to_string();
encoded.make_ascii_lowercase();
assert_eq!(encode(&decoded), &encoded[..]);
assert_eq!(decode(address).unwrap(), decoded);
let mut buf = vec![0u8; (address.len() - 2) / 2];
assert!(decode_to_buf(address, &mut buf).is_ok());
assert_eq!(buf.clone(), decoded);
}
#[test]
fn decode_rejects_invalid_hex() {
let invalid = "a1b2fh";
let is_err = matches!(
decode(invalid),
Err(HexError::ParseInt(ParseIntError { .. }))
);
assert!(is_err);
let mut buf = vec![0u8; invalid.len() / 2];
let is_err = matches!(
decode_to_buf(invalid, &mut buf),
Err(HexError::ParseInt(ParseIntError { .. }))
);
assert!(is_err);
let invalid = "fff";
assert_eq!(decode(invalid), Err(HexError::OddLength));
let mut buf = vec![0u8; invalid.len() / 2];
assert_eq!(decode_to_buf(invalid, &mut buf), Err(HexError::OddLength));
}
#[test]
#[ignore = "long running test"]
fn decode_respects_max_len() {
let valid =
(0..STR_MAX_LENGTH - 2).map(|_| "f").collect::<Vec<_>>().join("");
assert!(decode(&valid).is_ok());
let invalid = format!("{valid}ff");
assert_eq!(decode(&invalid), Err(HexError::ExceedsMaxLength));
}
#[test]
fn encode_to_vec_and_decode_from_vec_with_len_zero() {
let decoded = vec![];
let encoded = vec![];
assert_eq!(encode_to_vec(&decoded), encoded);
assert_eq!(decode_from_vec(encoded).unwrap(), decoded);
}
#[test]
fn decode_from_vec_correctly_errors_with_len_one() {
let encoded = vec![0x61];
let res = decode_from_vec(encoded);
assert_eq!(res, Err(HexError::LengthOne));
}
#[test]
fn decode_from_vec_trims_string() {
let encoded = vec![0x20];
let decoded = vec![];
assert_eq!(decode_from_vec(encoded).unwrap(), decoded);
let encoded = vec![0x61, 0x20];
let res = decode_from_vec(encoded);
assert_eq!(res, Err(HexError::LengthOne));
let encoded = vec![0x61, 0x61, 0x20];
let decoded = vec![170];
assert_eq!(decode_from_vec(encoded).unwrap(), decoded);
}
#[test]
fn encode_to_vec_and_decode_from_vec_work() {
let decoded = vec![0, 0, 0, 0];
let encoded = vec![48, 48, 48, 48, 48, 48, 48, 48];
assert_eq!(encode_to_vec(&decoded), encoded);
assert_eq!(decode_from_vec(encoded).unwrap(), decoded);
let decoded = vec![0xff, 0x00, 0xff];
let encoded = vec![102, 102, 48, 48, 102, 102];
assert_eq!(encode_to_vec(&decoded), encoded);
assert_eq!(decode_from_vec(encoded).unwrap(), decoded);
let decoded = vec![31, 52, 228, 109, 140, 170, 124, 94];
let encoded = vec![
49, 102, 51, 52, 101, 52, 54, 100, 56, 99, 97, 97, 55, 99, 53, 101,
];
assert_eq!(encode_to_vec(&decoded), encoded);
assert_eq!(decode_from_vec(encoded).unwrap(), decoded);
}
#[test]
fn decode_from_vec_rejects_invalid_hex() {
let invalid = vec![240, 159, 144];
let is_err = matches!(
decode_from_vec(invalid),
Err(HexError::InvalidUtf8(FromUtf8Error { .. }))
);
assert!(is_err);
}
}