#![no_std]
extern crate alloc;
use alloc::{string::String, vec::Vec};
use core::{char, convert::TryInto, i16, i32, num::ParseIntError};
#[cfg(feature = "serde")]
use serde::{de::Error as DeError, Deserialize, Deserializer};
pub type Bytes = Vec<u8>;
pub type Hex = String;
pub type ArrayBytesResult<T> = Result<T, Error>;
pub trait TryFromHex
where
Self: Sized,
{
fn try_from_hex(hex: impl AsRef<str>) -> ArrayBytesResult<Self>;
}
macro_rules! impl_num_from_hex {
($t:ty) => {
impl TryFromHex for $t {
fn try_from_hex(hex: impl AsRef<str>) -> ArrayBytesResult<Self> {
let hex = hex.as_ref();
Self::from_str_radix(&hex[if hex.starts_with("0x") { 2 } else { 0 }..], 16)
.map_err(|e| Error::ParseIntError(e))
}
}
};
}
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)]
#[cfg_attr(test, derive(PartialEq))]
pub enum Error {
InvalidLength { length: usize },
InvalidCharBoundary { index: usize },
ParseIntError(ParseIntError),
}
#[macro_export]
macro_rules! dyn2array {
($dyn:expr, $len:expr) => {{
unsafe { *($dyn.as_ptr() as *const [u8; $len]) }
}};
}
#[macro_export]
macro_rules! dyn_into {
($dyn:expr, $len:expr) => {{
unsafe { *($dyn.as_ptr() as *const [u8; $len]) }.into()
}};
}
pub fn hex2bytes(hex: impl AsRef<str>) -> ArrayBytesResult<Bytes> {
let hex = hex.as_ref();
if hex.len() % 2 != 0 {
return Err(Error::InvalidLength {
length: if hex.starts_with("0x") {
hex.len() - 2
} else {
hex.len()
},
});
}
let mut bytes = Bytes::new();
for i in (if hex.starts_with("0x") { 2 } else { 0 }..hex.len()).step_by(2) {
for i in i + 1..i + 3 {
if !hex.is_char_boundary(i) {
return Err(Error::InvalidCharBoundary { index: i });
}
}
bytes.push(u8::from_str_radix(&hex[i..i + 2], 16).unwrap());
}
Ok(bytes)
}
pub fn hex2bytes_unchecked(hex: impl AsRef<str>) -> Bytes {
let hex = hex.as_ref();
(if hex.starts_with("0x") { 2 } else { 0 }..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
.collect()
}
pub fn hex2array<const N: usize>(hex: impl AsRef<str>) -> ArrayBytesResult<[u8; N]> {
hex2bytes(hex)?
.try_into()
.map_err(|e: Bytes| Error::InvalidLength { length: e.len() })
}
pub fn hex2array_unchecked<const N: usize>(hex: impl AsRef<str>) -> [u8; N] {
hex2bytes_unchecked(hex).try_into().unwrap()
}
#[deprecated(since = "1.2.0", note = "use `fn hex2array_unchecked` instead.")]
#[macro_export]
macro_rules! hex2array_unchecked {
($hex:expr, $len:expr) => {{
$crate::dyn2array!($crate::hex2bytes_unchecked($hex), $len)
}};
}
pub fn hex_try_into<H, T, const N: usize>(hex: H) -> ArrayBytesResult<T>
where
H: AsRef<str>,
T: From<[u8; N]>,
{
Ok(hex2array(hex)?.into())
}
pub fn hex_into_unchecked<H, T, const N: usize>(hex: H) -> T
where
H: AsRef<str>,
T: From<[u8; N]>,
{
hex2array_unchecked(hex).into()
}
#[cfg(feature = "serde")]
pub fn hex_deserialize_into<'de, D, T, const N: usize>(hex: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: From<[u8; N]>,
{
Ok(hex2array_unchecked(<&str>::deserialize(hex)?).into())
}
#[cfg(feature = "serde")]
pub fn hexd2num<'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 hexd2bytes<'de, D>(hex: D) -> Result<Bytes, D::Error>
where
D: Deserializer<'de>,
{
let hex = <&str>::deserialize(hex)?;
hex2bytes(hex).map_err(|_| D::Error::custom(alloc::format!("Invalid hex str `{}`", hex)))
}
pub fn bytes2hex(prefix: impl AsRef<str>, bytes: impl AsRef<[u8]>) -> Hex {
let prefix = prefix.as_ref();
let bytes = bytes.as_ref();
let mut hex = Hex::with_capacity(prefix.len() + bytes.len() * 2);
for byte in prefix.chars() {
hex.push(byte);
}
for byte in bytes.iter() {
hex.push(char::from_digit((byte >> 4) as _, 16).unwrap());
hex.push(char::from_digit((byte & 0xf) as _, 16).unwrap());
}
hex
}
#[cfg(test)]
mod test {
use crate::*;
macro_rules! bytes {
($v:expr; $n:expr) => {{
let mut v = Bytes::new();
for _ in 0..$n {
v.push($v);
}
v
}};
}
#[derive(Debug, PartialEq)]
struct LJF([u8; 17]);
impl From<[u8; 17]> for LJF {
fn from(array: [u8; 17]) -> Self {
Self(array)
}
}
#[test]
fn dyn2array_should_work() {
for v in 0u8..16 {
assert_eq!([v; 8], dyn2array!(bytes![v; 10], 8));
}
}
#[test]
fn dyn_into_should_work() {
let ljf: LJF = dyn_into!(*b"Love Jane Forever Forever Forever", 17);
assert_eq!(ljf, LJF(*b"Love Jane Forever"));
let ljf: LJF = dyn_into!(b"Love Jane Forever Forever Forever".to_vec(), 17);
assert_eq!(ljf, LJF(*b"Love Jane Forever"));
}
#[test]
fn hex2bytes_should_work() {
assert_eq!(
hex2bytes("4c6f7665204a616e6520466f7265766572").unwrap(),
*b"Love Jane Forever"
);
assert_eq!(
hex2bytes("0x4c6f7665204a616e6520466f7265766572").unwrap(),
*b"Love Jane Forever"
);
assert_eq!(
hex2bytes(Hex::from("我爱你")).unwrap_err(),
Error::InvalidLength { length: 9 }
);
assert_eq!(
hex2bytes(Hex::from("0x我爱你")).unwrap_err(),
Error::InvalidLength { length: 9 }
);
assert_eq!(
hex2bytes(Hex::from("我爱你 ")).unwrap_err(),
Error::InvalidCharBoundary { index: 1 }
);
assert_eq!(
hex2bytes(Hex::from(" 我爱你")).unwrap_err(),
Error::InvalidCharBoundary { index: 2 }
);
}
#[test]
fn hex2bytes_unchecked_should_work() {
assert_eq!(
hex2bytes_unchecked("4c6f7665204a616e6520466f7265766572"),
*b"Love Jane Forever"
);
assert_eq!(
hex2bytes_unchecked("0x4c6f7665204a616e6520466f7265766572"),
*b"Love Jane Forever"
);
}
#[test]
fn hex2array_should_work() {
assert_eq!(
hex2array("4c6f7665204a616e6520466f7265766572").unwrap(),
*b"Love Jane Forever"
);
assert_eq!(
hex2array("0x4c6f7665204a616e6520466f7265766572").unwrap(),
*b"Love Jane Forever"
);
}
#[test]
fn hex2array_unchecked_should_work() {
assert_eq!(
hex2array_unchecked("4c6f7665204a616e6520466f7265766572"),
*b"Love Jane Forever"
);
assert_eq!(
hex2array_unchecked("0x4c6f7665204a616e6520466f7265766572"),
*b"Love Jane Forever"
);
assert_eq!(
hex2array_unchecked!("4c6f7665204a616e6520466f7265766572", 17),
*b"Love Jane Forever"
);
assert_eq!(
hex2array_unchecked!("0x4c6f7665204a616e6520466f7265766572", 17),
*b"Love Jane Forever"
);
}
#[test]
fn hex_try_into_should_work() {
assert_eq!(
hex_try_into::<_, LJF, 17>("0x4c6f7665204a616e6520466f7265766572").unwrap(),
LJF(*b"Love Jane Forever")
);
}
#[test]
fn hex_into_should_work() {
assert_eq!(
hex_into_unchecked::<_, LJF, 17>("0x4c6f7665204a616e6520466f7265766572"),
LJF(*b"Love Jane Forever")
);
}
#[test]
fn bytes2hex_should_work() {
assert_eq!(
bytes2hex("", b"Love Jane Forever"),
Hex::from("4c6f7665204a616e6520466f7265766572")
);
assert_eq!(
bytes2hex("0x", b"Love Jane Forever"),
Hex::from("0x4c6f7665204a616e6520466f7265766572")
);
}
}