#![allow(clippy::tabs_in_doc_comments)]
#![deny(missing_docs)]
#![no_std]
extern crate alloc;
#[cfg(test)] mod test;
use core::{convert::TryInto, mem, result::Result as CoreResult};
use alloc::{string::String, vec::Vec};
#[cfg(feature = "serde")] use serde::{de::Error as DeError, Deserialize, Deserializer};
pub type Result<T> = CoreResult<T, Error>;
pub trait TryFromHex
where
Self: Sized,
{
fn try_from_hex<H>(hex: H) -> Result<Self>
where
H: AsRef<str>;
}
macro_rules! impl_num_from_hex {
($t:ty) => {
impl TryFromHex for $t {
fn try_from_hex<H>(hex: H) -> Result<Self>
where
H: AsRef<str>,
{
let hex = strip_0x(hex.as_ref());
Self::from_str_radix(hex, 16).map_err(Error::ParseIntError)
}
}
};
}
impl_num_from_hex!(isize);
impl_num_from_hex!(i8);
impl_num_from_hex!(i16);
impl_num_from_hex!(i32);
impl_num_from_hex!(i64);
impl_num_from_hex!(i128);
impl_num_from_hex!(usize);
impl_num_from_hex!(u8);
impl_num_from_hex!(u16);
impl_num_from_hex!(u32);
impl_num_from_hex!(u64);
impl_num_from_hex!(u128);
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
InvalidLength,
InvalidCharacter {
character: char,
index: usize,
},
MismatchedLength {
expect: usize,
},
ParseIntError(core::num::ParseIntError),
}
pub fn slice2array<const N: usize, T>(slice: &[T]) -> Result<[T; N]>
where
T: Copy,
{
slice.try_into().map_err(|_| Error::MismatchedLength { expect: N })
}
pub fn slice2array_unchecked<const N: usize, T>(slice: &[T]) -> [T; N]
where
T: Copy,
{
slice2array(slice).unwrap()
}
pub fn slice_n_into<const N: usize, T, V>(slice: &[T]) -> Result<V>
where
T: Copy,
V: From<[T; N]>,
{
Ok(slice2array(slice)?.into())
}
pub fn slice_n_into_unchecked<const N: usize, T, V>(slice: &[T]) -> V
where
T: Copy,
V: From<[T; N]>,
{
slice2array_unchecked(slice).into()
}
pub fn vec2array<const N: usize, T>(vec: Vec<T>) -> Result<[T; N]> {
vec.try_into().map_err(|_| Error::MismatchedLength { expect: N })
}
pub fn vec2array_unchecked<const N: usize, T>(vec: Vec<T>) -> [T; N] {
vec2array(vec).unwrap()
}
pub fn vec_n_into<const N: usize, T, V>(vec: Vec<T>) -> Result<V>
where
V: From<[T; N]>,
{
Ok(vec2array(vec)?.into())
}
pub fn vec_n_into_unchecked<const N: usize, T, V>(vec: Vec<T>) -> V
where
V: From<[T; N]>,
{
vec2array_unchecked(vec).into()
}
pub fn hex_bytes2hex_str(bytes: &[u8]) -> Result<&str> {
for (i, byte) in bytes.iter().enumerate().skip(if bytes.starts_with(b"0x") { 2 } else { 0 }) {
if !is_hex_ascii(byte) {
Err(Error::InvalidCharacter { character: *byte as _, index: i })?;
}
}
Ok(unsafe {
#[allow(clippy::transmute_bytes_to_str)]
mem::transmute(bytes)
})
}
pub unsafe fn hex_bytes2hex_str_unchecked(bytes: &[u8]) -> &str {
#[allow(clippy::transmute_bytes_to_str)]
mem::transmute(bytes)
}
pub fn bytes2hex<B>(prefix: &str, bytes: B) -> String
where
B: AsRef<[u8]>,
{
let bytes = bytes.as_ref();
let mut hex = String::with_capacity(prefix.len() + bytes.len() * 2);
prefix.chars().for_each(|byte| hex.push(byte));
bytes.iter().for_each(|byte| {
hex.push(char::from_digit((byte >> 4) as _, 16).unwrap());
hex.push(char::from_digit((byte & 0xf) as _, 16).unwrap());
});
hex
}
pub fn hex2array<H, const N: usize>(hex: H) -> Result<[u8; N]>
where
H: AsRef<[u8]>,
{
vec2array(hex2bytes(hex.as_ref())?)
}
pub fn hex2array_unchecked<H, const N: usize>(hex: H) -> [u8; N]
where
H: AsRef<[u8]>,
{
hex2bytes_unchecked(hex).try_into().unwrap()
}
pub fn hex2bytes<H>(hex: H) -> Result<Vec<u8>>
where
H: AsRef<[u8]>,
{
let hex = strip_0x_bytes(hex.as_ref());
if hex.len() % 2 != 0 {
Err(Error::InvalidLength)?;
}
let mut bytes = Vec::new();
for i in (0..hex.len()).step_by(2) {
bytes.push(hex2byte((&hex[i], i), (&hex[i + 1], i + 1))?);
}
Ok(bytes)
}
pub fn hex2bytes_unchecked<H>(hex: H) -> Vec<u8>
where
H: AsRef<[u8]>,
{
let hex = strip_0x_bytes(hex.as_ref());
(0..hex.len()).step_by(2).map(|i| hex2byte_unchecked(&hex[i], &hex[i + 1])).collect()
}
pub fn hex2slice<H>(hex: H, slice: &mut [u8]) -> Result<&[u8]>
where
H: AsRef<[u8]>,
{
let hex = strip_0x_bytes(hex.as_ref());
if hex.len() % 2 != 0 {
Err(Error::InvalidLength)?;
}
let expected_len = hex.len() >> 1;
if expected_len != slice.len() {
Err(Error::MismatchedLength { expect: expected_len })?;
}
for (byte, i) in slice.iter_mut().zip((0..hex.len()).step_by(2)) {
*byte = hex2byte((&hex[i], i), (&hex[i + 1], i + 1))?;
}
Ok(slice)
}
pub fn hex2slice_unchecked<H>(hex: H, slice: &mut [u8]) -> &[u8]
where
H: AsRef<[u8]>,
{
let hex = strip_0x_bytes(hex.as_ref());
slice
.iter_mut()
.zip((0..hex.len()).step_by(2))
.for_each(|(byte, i)| *byte = hex2byte_unchecked(&hex[i], &hex[i + 1]));
slice
}
pub fn hex_into<H, T>(hex: H) -> Result<T>
where
H: AsRef<[u8]>,
T: From<Vec<u8>>,
{
Ok(hex2bytes(hex.as_ref())?.into())
}
pub fn hex_into_unchecked<H, T>(hex: H) -> T
where
H: AsRef<[u8]>,
T: From<Vec<u8>>,
{
hex2bytes_unchecked(hex).into()
}
pub fn hex_n_into<H, T, const N: usize>(hex: H) -> Result<T>
where
H: AsRef<[u8]>,
T: From<[u8; N]>,
{
Ok(hex2array(hex)?.into())
}
pub fn hex_n_into_unchecked<H, T, const N: usize>(hex: H) -> T
where
H: AsRef<[u8]>,
T: From<[u8; N]>,
{
hex2array_unchecked(hex).into()
}
#[cfg(feature = "serde")]
pub fn hex_deserialize_into<'de, D, T>(hex: D) -> CoreResult<T, D::Error>
where
D: Deserializer<'de>,
T: From<Vec<u8>>,
{
Ok(hex2bytes_unchecked(<&str>::deserialize(hex)?).into())
}
#[cfg(feature = "serde")]
pub fn hex_deserialize_n_into<'de, D, T, const N: usize>(hex: D) -> CoreResult<T, D::Error>
where
D: Deserializer<'de>,
T: From<[u8; N]>,
{
Ok(hex2array_unchecked(<&str>::deserialize(hex)?).into())
}
#[cfg(feature = "serde")]
pub fn de_hex2num<'de, D, T>(hex: D) -> CoreResult<T, D::Error>
where
D: Deserializer<'de>,
T: TryFromHex,
{
let hex = <&str>::deserialize(hex)?;
T::try_from_hex(hex).map_err(|_| D::Error::custom(alloc::format!("Invalid hex str `{}`", hex)))
}
#[cfg(feature = "serde")]
pub fn de_hex2bytes<'de, D>(hex: D) -> CoreResult<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
let hex = <&str>::deserialize(hex)?;
hex2bytes(hex).map_err(|_| D::Error::custom(alloc::format!("Invalid hex str `{}`", hex)))
}
fn strip_0x(hex: &str) -> &str {
if let Some(hex) = hex.strip_prefix("0x") {
hex
} else {
hex
}
}
fn strip_0x_bytes(hex: &[u8]) -> &[u8] {
if let Some(hex) = hex.strip_prefix(b"0x") {
hex
} else {
hex
}
}
fn is_hex_ascii(byte: &u8) -> bool {
let byte = byte | 0b10_0000;
matches!(byte, b'0'..=b'9' | b'a'..=b'f')
}
fn hex_ascii2digit(hex_ascii: &u8) -> Option<u8> {
let hex_ascii = hex_ascii | 0b10_0000;
match hex_ascii {
b'0'..=b'9' => Some(hex_ascii - b'0'),
b'a'..=b'f' => Some(hex_ascii - b'a' + 10),
_ => None,
}
}
fn hex2byte(hex_ascii_1: (&u8, usize), hex_ascii_2: (&u8, usize)) -> Result<u8> {
let byte = hex_ascii2digit(hex_ascii_1.0)
.ok_or(Error::InvalidCharacter { character: *hex_ascii_1.0 as _, index: hex_ascii_1.1 })?
<< 4 | hex_ascii2digit(hex_ascii_2.0)
.ok_or(Error::InvalidCharacter { character: *hex_ascii_2.0 as _, index: hex_ascii_2.1 })?;
Ok(byte)
}
fn hex2byte_unchecked(hex_ascii_1: &u8, hex_ascii_2: &u8) -> u8 {
hex_ascii2digit(hex_ascii_1).unwrap() << 4 | hex_ascii2digit(hex_ascii_2).unwrap()
}