#![deny(clippy::all, missing_docs, unused_crate_dependencies)]
#![allow(clippy::tabs_in_doc_comments, clippy::uninit_vec)]
#![no_std]
extern crate alloc;
#[cfg(test)] mod test;
use core::{cmp::Ordering, convert::TryInto, str};
use alloc::{format, string::String, vec::Vec};
#[cfg(feature = "serde")]
use serde::{de::Error as DeError, Deserialize, Deserializer, Serializer};
use smallvec::SmallVec;
pub type Result<T, E = Error> = core::result::Result<T, E>;
const HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
static HEX_TO_DIGIT: [Option<u8>; 256] = {
let mut table = [None; 256];
let mut i = 0;
while i <= 9 {
table[b'0' as usize + i] = Some(i as u8);
i += 1;
}
i = 0;
while i <= 5 {
table[b'a' as usize + i] = Some((10 + i) as u8);
table[b'A' as usize + i] = Some((10 + i) as u8);
i += 1;
}
table
};
pub trait TryFromHex
where
Self: Sized,
{
fn try_from_hex<H>(hex: H) -> Result<Self>
where
H: AsRef<[u8]>;
}
macro_rules! impl_num_try_from_hex {
($($t:ty,)+) => {
$(impl TryFromHex for $t {
fn try_from_hex<H>(hex: H) -> Result<Self>
where
H: AsRef<[u8]>,
{
let hex = strip_0x(hex.as_ref());
let hex = str::from_utf8(hex).map_err(Error::Utf8Error)?;
Self::from_str_radix(hex, 16).map_err(Error::ParseIntError)
}
})+
};
}
impl_num_try_from_hex! {
isize,
i8,
i16,
i32,
i64,
i128,
usize,
u8,
u16,
u32,
u64,
u128,
}
impl<const N: usize> TryFromHex for [u8; N] {
fn try_from_hex<H>(hex: H) -> Result<Self>
where
H: AsRef<[u8]>,
{
let bytes = hex2bytes(hex)?;
slice2array(&bytes)
}
}
impl TryFromHex for SmallVec<[u8; 64]> {
fn try_from_hex<H>(hex: H) -> Result<Self>
where
H: AsRef<[u8]>,
{
hex2bytes(hex)
}
}
impl TryFromHex for Vec<u8> {
fn try_from_hex<H>(hex: H) -> Result<Self>
where
H: AsRef<[u8]>,
{
hex2bytes(hex).map(|sv| sv.into_vec())
}
}
pub trait Hex {
fn hex<P>(self, prefix: P) -> String
where
P: AsRef<str>;
}
macro_rules! impl_num_hex {
($($t:ty,)+) => {
$(
impl Hex for $t {
fn hex<P>(self, prefix: P) -> String
where
P: AsRef<str>
{
let prefix = prefix.as_ref();
format!("{prefix}{self:x}")
}
}
impl Hex for &$t {
fn hex<P>(self, prefix: P) -> String
where
P: AsRef<str>
{
let prefix = prefix.as_ref();
format!("{prefix}{self:x}")
}
}
)+
};
}
impl_num_hex! {
isize,
i8,
i16,
i32,
i64,
i128,
usize,
u8,
u16,
u32,
u64,
u128,
}
impl<const N: usize> Hex for [u8; N] {
fn hex<P>(self, prefix: P) -> String
where
P: AsRef<str>,
{
bytes2hex(prefix, self)
}
}
impl<const N: usize> Hex for &[u8; N] {
fn hex<P>(self, prefix: P) -> String
where
P: AsRef<str>,
{
bytes2hex(prefix, self)
}
}
impl Hex for &[u8] {
fn hex<P>(self, prefix: P) -> String
where
P: AsRef<str>,
{
bytes2hex(prefix, self)
}
}
impl Hex for Vec<u8> {
fn hex<P>(self, prefix: P) -> String
where
P: AsRef<str>,
{
bytes2hex(prefix, self)
}
}
impl Hex for &Vec<u8> {
fn hex<P>(self, prefix: P) -> String
where
P: AsRef<str>,
{
bytes2hex(prefix, self)
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
InvalidLength,
InvalidCharacter {
character: char,
index: usize,
},
MismatchedLength {
expect: usize,
},
Utf8Error(core::str::Utf8Error),
ParseIntError(core::num::ParseIntError),
}
pub fn slice2array<T, const N: usize>(slice: &[T]) -> Result<[T; N]>
where
T: Copy,
{
slice.try_into().map_err(|_| Error::MismatchedLength { expect: N })
}
pub fn slice2array_unchecked<T, const N: usize>(slice: &[T]) -> [T; N]
where
T: Copy,
{
slice2array(slice).unwrap()
}
pub fn slice2array_ref<T, const N: usize>(slice: &[T]) -> Result<&[T; N]>
where
T: Copy,
{
slice.try_into().map_err(|_| Error::MismatchedLength { expect: N })
}
pub fn slice2array_ref_unchecked<T, const N: usize>(slice: &[T]) -> &[T; N]
where
T: Copy,
{
slice2array_ref(slice).unwrap()
}
pub fn prefix_with<A, T, const N: usize>(any: A, element: T) -> [T; N]
where
A: AsRef<[T]>,
T: Copy,
{
pad_array(any, element, true)
}
pub fn suffix_with<A, T, const N: usize>(any: A, element: T) -> [T; N]
where
A: AsRef<[T]>,
T: Copy,
{
pad_array(any, element, false)
}
pub fn slice_n_into<T, V, const N: usize>(slice: &[T]) -> Result<V>
where
T: Copy,
V: From<[T; N]>,
{
Ok(slice2array(slice)?.into())
}
pub fn slice_n_into_unchecked<T, V, const N: usize>(slice: &[T]) -> V
where
T: Copy,
V: From<[T; N]>,
{
slice2array_unchecked(slice).into()
}
pub fn vec2array<T, const N: usize>(vec: Vec<T>) -> Result<[T; N]> {
vec.try_into().map_err(|_| Error::MismatchedLength { expect: N })
}
pub fn vec2array_unchecked<T, const N: usize>(vec: Vec<T>) -> [T; N] {
vec2array(vec).unwrap()
}
pub fn vec_n_into<T, V, const N: usize>(vec: Vec<T>) -> Result<V>
where
V: From<[T; N]>,
{
Ok(vec2array(vec)?.into())
}
pub fn vec_n_into_unchecked<T, V, const N: usize>(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 !byte.is_ascii_hexdigit() {
Err(Error::InvalidCharacter { character: *byte as _, index: i })?;
}
}
Ok(
unsafe { str::from_utf8_unchecked(bytes) },
)
}
pub fn hex_bytes2hex_str_unchecked(bytes: &[u8]) -> &str {
unsafe { str::from_utf8_unchecked(bytes) }
}
pub fn bytes2hex<P, B>(prefix: P, bytes: B) -> String
where
P: AsRef<str>,
B: AsRef<[u8]>,
{
let prefix = prefix.as_ref();
let bytes = bytes.as_ref();
let cap = prefix.len() + bytes.len() * 2;
let mut hex_bytes = <SmallVec<[u8; 128]>>::with_capacity(cap);
hex_bytes.extend_from_slice(prefix.as_bytes());
unsafe {
hex_bytes.set_len(cap);
}
let hex_ptr = unsafe { hex_bytes.as_mut_ptr().add(prefix.len()) };
for (i, &byte) in bytes.iter().enumerate() {
let high = HEX_CHARS[(byte >> 4) as usize];
let low = HEX_CHARS[(byte & 0x0f) as usize];
unsafe {
*hex_ptr.add(i * 2) = high;
*hex_ptr.add(i * 2 + 1) = low;
}
}
unsafe { String::from_utf8_unchecked(hex_bytes.into_vec()) }
}
pub fn hex2array<H, const N: usize>(hex: H) -> Result<[u8; N]>
where
H: AsRef<[u8]>,
{
vec2array(hex2bytes(hex)?.into_vec())
}
pub fn hex2array_unchecked<H, const N: usize>(hex: H) -> [u8; N]
where
H: AsRef<[u8]>,
{
hex2bytes_unchecked(hex).into_vec().try_into().unwrap()
}
pub fn hex2bytes<H>(hex: H) -> Result<SmallVec<[u8; 64]>>
where
H: AsRef<[u8]>,
{
let hex = strip_0x(hex.as_ref());
if hex.len() % 2 != 0 {
Err(Error::InvalidLength)?;
}
let cap = hex.len() / 2;
let mut bytes = <SmallVec<[u8; 64]>>::with_capacity(cap);
unsafe {
bytes.set_len(cap);
}
let bytes_ptr = bytes.as_mut_ptr();
for i in 0..cap {
let high = HEX_TO_DIGIT[hex[i * 2] as usize]
.ok_or(Error::InvalidCharacter { character: hex[i * 2] as char, index: i * 2 })?;
let low = HEX_TO_DIGIT[hex[i * 2 + 1] as usize].ok_or(Error::InvalidCharacter {
character: hex[i * 2 + 1] as char,
index: i * 2 + 1,
})?;
unsafe {
*bytes_ptr.add(i) = (high << 4) | low;
}
}
Ok(bytes)
}
pub fn hex2bytes_unchecked<H>(hex: H) -> SmallVec<[u8; 64]>
where
H: AsRef<[u8]>,
{
let hex = strip_0x(hex.as_ref());
let cap = hex.len() / 2;
let mut bytes = <SmallVec<[u8; 64]>>::with_capacity(cap);
unsafe {
bytes.set_len(cap);
}
let bytes_ptr = bytes.as_mut_ptr();
for i in 0..cap {
let high = HEX_TO_DIGIT[hex[i * 2] as usize].unwrap();
let low = HEX_TO_DIGIT[hex[i * 2 + 1] as usize].unwrap();
unsafe {
*bytes_ptr.add(i) = (high << 4) | low;
}
}
bytes
}
pub fn hex2slice<H>(hex: H, slice: &mut [u8]) -> Result<&[u8]>
where
H: AsRef<[u8]>,
{
let hex = strip_0x(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(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_vec().into())
}
pub fn hex_into_unchecked<H, T>(hex: H) -> T
where
H: AsRef<[u8]>,
T: From<Vec<u8>>,
{
hex2bytes_unchecked(hex).into_vec().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) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: From<Vec<u8>>,
{
let hex_str = <&str>::deserialize(hex)?;
hex2bytes(hex_str)
.map(|sv| sv.into_vec().into())
.map_err(|e| D::Error::custom(format!("{e:?}")))
}
#[cfg(feature = "serde")]
pub fn hex_deserialize_n_into<'de, D, T, const N: usize>(hex: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: From<[u8; N]>,
{
let hex_str = <&str>::deserialize(hex)?;
hex2array(hex_str).map(Into::into).map_err(|e| D::Error::custom(format!("{e:?}")))
}
#[cfg(feature = "serde")]
pub fn de_try_from_hex<'de, D, T>(hex: D) -> Result<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 ser_hex<S, T>(hex: T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Hex,
{
serializer.serialize_str(&hex.hex("0x"))
}
#[cfg(feature = "serde")]
pub fn ser_hex_without_prefix<S, T>(hex: T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Hex,
{
serializer.serialize_str(&hex.hex(""))
}
#[inline(always)]
fn strip_0x(hex: &[u8]) -> &[u8] {
if let Some(hex) = hex.strip_prefix(b"0x") {
hex
} else {
hex
}
}
#[inline(always)]
fn hex_ascii2digit(hex_ascii: &u8) -> Option<u8> {
HEX_TO_DIGIT[*hex_ascii as usize]
}
#[inline(always)]
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)
}
#[inline(always)]
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()
}
#[inline(always)]
fn pad_array<A, T, const N: usize>(any: A, element: T, pad_start: bool) -> [T; N]
where
A: AsRef<[T]>,
T: Copy,
{
let a = any.as_ref();
match a.len().cmp(&N) {
Ordering::Equal => slice2array_unchecked(a),
Ordering::Greater => slice2array_unchecked(&a[..N]),
Ordering::Less => {
let mut padded = [element; N];
if pad_start {
padded[N - a.len()..].copy_from_slice(a);
} else {
padded[..a.len()].copy_from_slice(a);
}
padded
},
}
}