#![allow(clippy::tabs_in_doc_comments)]
#![deny(missing_docs)]
#![no_std]
extern crate alloc;
#[cfg(test)] mod test;
use core::{cmp::Ordering, convert::TryInto, result::Result as CoreResult, str};
use alloc::{format, 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<[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,
}
macro_rules! impl_array_try_from_hex {
($($t:ty,)+) => {
$(impl TryFromHex for $t {
fn try_from_hex<H>(hex: H) -> Result<Self>
where
H: AsRef<[u8]>,
{
hex2array(hex)
}
})+
};
}
impl_array_try_from_hex! {
[u8; 1],
[u8; 2],
[u8; 3],
[u8; 4],
[u8; 5],
[u8; 6],
[u8; 7],
[u8; 8],
[u8; 9],
[u8; 10],
[u8; 11],
[u8; 12],
[u8; 13],
[u8; 14],
[u8; 15],
[u8; 16],
[u8; 17],
[u8; 18],
[u8; 19],
[u8; 20],
[u8; 21],
[u8; 22],
[u8; 23],
[u8; 24],
[u8; 25],
[u8; 26],
[u8; 27],
[u8; 28],
[u8; 29],
[u8; 30],
[u8; 31],
[u8; 32],
[u8; 33],
[u8; 34],
[u8; 35],
[u8; 36],
[u8; 37],
[u8; 38],
[u8; 39],
[u8; 40],
[u8; 41],
[u8; 42],
[u8; 43],
[u8; 44],
[u8; 45],
[u8; 46],
[u8; 47],
[u8; 48],
[u8; 49],
[u8; 50],
[u8; 51],
[u8; 52],
[u8; 53],
[u8; 54],
[u8; 55],
[u8; 56],
[u8; 57],
[u8; 58],
[u8; 59],
[u8; 60],
[u8; 61],
[u8; 62],
[u8; 63],
[u8; 64],
[u8; 128],
[u8; 256],
[u8; 512],
}
impl TryFromHex for Vec<u8> {
fn try_from_hex<H>(hex: H) -> Result<Self>
where
H: AsRef<[u8]>,
{
hex2bytes(hex)
}
}
pub trait Hex {
fn hex(self, prefix: &str) -> String;
}
macro_rules! impl_num_hex {
($($t:ty,)+) => {
$(
impl Hex for $t {
fn hex(self, prefix: &str) -> String {
format!("{prefix}{self:x}")
}
}
impl Hex for &$t {
fn hex(self, prefix: &str) -> String {
format!("{prefix}{self:x}")
}
}
)+
};
}
impl_num_hex! {
isize,
i8,
i16,
i32,
i64,
i128,
usize,
u8,
u16,
u32,
u64,
u128,
}
macro_rules! impl_array_hex {
($($t:ty,)+) => {
$(
impl Hex for $t {
fn hex(self, prefix: &str) -> String {
bytes2hex(prefix, self)
}
}
impl Hex for &$t {
fn hex(self, prefix: &str) -> String {
bytes2hex(prefix, self)
}
}
)+
};
}
impl_array_hex! {
Vec<u8>,
[u8; 1],
[u8; 2],
[u8; 3],
[u8; 4],
[u8; 5],
[u8; 6],
[u8; 7],
[u8; 8],
[u8; 9],
[u8; 10],
[u8; 11],
[u8; 12],
[u8; 13],
[u8; 14],
[u8; 15],
[u8; 16],
[u8; 17],
[u8; 18],
[u8; 19],
[u8; 20],
[u8; 21],
[u8; 22],
[u8; 23],
[u8; 24],
[u8; 25],
[u8; 26],
[u8; 27],
[u8; 28],
[u8; 29],
[u8; 30],
[u8; 31],
[u8; 32],
[u8; 33],
[u8; 34],
[u8; 35],
[u8; 36],
[u8; 37],
[u8; 38],
[u8; 39],
[u8; 40],
[u8; 41],
[u8; 42],
[u8; 43],
[u8; 44],
[u8; 45],
[u8; 46],
[u8; 47],
[u8; 48],
[u8; 49],
[u8; 50],
[u8; 51],
[u8; 52],
[u8; 53],
[u8; 54],
[u8; 55],
[u8; 56],
[u8; 57],
[u8; 58],
[u8; 59],
[u8; 60],
[u8; 61],
[u8; 62],
[u8; 63],
[u8; 64],
[u8; 128],
[u8; 256],
[u8; 512],
}
impl Hex for &[u8] {
fn hex(self, prefix: &str) -> String {
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 prefix_with<A, T, const N: usize>(any: A, element: T) -> [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];
padded[N - a.len()..].copy_from_slice(a);
padded
},
}
}
pub fn suffix_with<A, T, const N: usize>(any: A, element: T) -> [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];
padded[..a.len()].copy_from_slice(a);
padded
},
}
}
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 !is_hex_ascii(byte) {
Err(Error::InvalidCharacter { character: *byte as _, index: i })?;
}
}
Ok(
unsafe { str::from_utf8_unchecked(bytes) },
)
}
pub unsafe fn hex_bytes2hex_str_unchecked(bytes: &[u8]) -> &str {
str::from_utf8_unchecked(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(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(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(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())
}
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: &[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()
}