use crate::error::Error;
#[cfg(target_arch = "aarch64")]
pub(crate) mod aarch64;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub(crate) mod x86;
const NIL: u8 = u8::MAX;
const fn init_unhex_array(check_case: CheckCase) -> [u8; 256] {
let mut arr = [0; 256];
let mut i = 0;
while i < 256 {
arr[i] = match i as u8 {
b'0'..=b'9' => i as u8 - b'0',
b'a'..=b'f' => match check_case {
CheckCase::Lower | CheckCase::None => i as u8 - b'a' + 10,
_ => NIL,
},
b'A'..=b'F' => match check_case {
CheckCase::Upper | CheckCase::None => i as u8 - b'A' + 10,
_ => NIL,
},
_ => NIL,
};
i += 1;
}
arr
}
const fn init_unhex4_array(check_case: CheckCase) -> [u8; 256] {
let unhex_arr = init_unhex_array(check_case);
let mut unhex4_arr = [NIL; 256];
let mut i = 0;
while i < 256 {
if unhex_arr[i] != NIL {
unhex4_arr[i] = unhex_arr[i] << 4;
}
i += 1;
}
unhex4_arr
}
static UNHEX: [u8; 256] = init_unhex_array(CheckCase::None);
static UNHEX_LOWER: [u8; 256] = init_unhex_array(CheckCase::Lower);
static UNHEX_UPPER: [u8; 256] = init_unhex_array(CheckCase::Upper);
static UNHEX4: [u8; 256] = init_unhex4_array(CheckCase::None);
#[inline]
pub fn hex_check(src: &[u8]) -> bool {
hex_check_with_case(src, CheckCase::None)
}
#[inline]
pub fn hex_check_with_case(src: &[u8], check_case: CheckCase) -> bool {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
match crate::vectorization_support() {
crate::Vectorization::AVX512 => {
unsafe { x86::hex_check_avx512_with_case(src, check_case) }
}
crate::Vectorization::AVX2 => {
unsafe { x86::hex_check_avx2_with_case(src, check_case) }
}
crate::Vectorization::SSE41 => {
unsafe { x86::hex_check_sse_with_case(src, check_case) }
}
crate::Vectorization::None => hex_check_fallback_with_case(src, check_case),
}
}
#[cfg(target_arch = "aarch64")]
{
match crate::vectorization_support() {
crate::Vectorization::Neon => {
unsafe { aarch64::hex_check_neon_with_case(src, check_case) }
}
crate::Vectorization::None => hex_check_fallback_with_case(src, check_case),
}
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
hex_check_fallback_with_case(src, check_case)
}
pub(crate) fn hex_check_fallback_with_case(src: &[u8], check_case: CheckCase) -> bool {
match check_case {
CheckCase::None => src.iter().all(|&x| UNHEX[x as usize] != NIL),
CheckCase::Lower => src.iter().all(|&x| UNHEX_LOWER[x as usize] != NIL),
CheckCase::Upper => src.iter().all(|&x| UNHEX_UPPER[x as usize] != NIL),
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
pub enum CheckCase {
#[default]
None,
Lower,
Upper,
}
#[inline]
pub fn hex_decode<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a mut [u8], Error> {
hex_decode_with_case(src, dst, CheckCase::None)
}
#[inline(always)]
pub fn hex_decode_with_case<'a>(
src: &[u8],
dst: &'a mut [u8],
check_case: CheckCase,
) -> Result<&'a mut [u8], Error> {
if !src.len().is_multiple_of(2) {
return Err(Error::OddLength);
}
let len = src.len() / 2;
let dst = dst
.get_mut(..len)
.ok_or(Error::OutputTooSmall { required: len })?;
if decode_checked(src, dst, check_case).is_err() {
decode_diagnosed(src, dst, check_case)?;
}
Ok(dst)
}
#[inline]
pub fn hex_decode_array<const N: usize>(src: &[u8]) -> Result<[u8; N], Error> {
hex_decode_array_with_case(src, CheckCase::None)
}
#[inline]
pub fn hex_decode_array_with_case<const N: usize>(
src: &[u8],
check_case: CheckCase,
) -> Result<[u8; N], Error> {
if !src.len().is_multiple_of(2) {
return Err(Error::OddLength);
}
let actual = src.len() / 2;
if actual != N {
return Err(Error::LengthMismatch {
expected: N,
actual,
});
}
let mut bytes = [0; N];
#[cfg(target_arch = "aarch64")]
if N == 4 && crate::vectorization_support() == crate::Vectorization::Neon {
let mut input = [b'0'; 16];
input[..src.len()].copy_from_slice(src);
let mut output = [0; 8];
hex_decode_with_case(&input, &mut output, check_case)?;
bytes.copy_from_slice(&output[..N]);
return Ok(bytes);
}
if N > OWNED_DECODE_THRESHOLD {
decode_owned_large(src, &mut bytes, check_case)?;
} else {
hex_decode_with_case(src, &mut bytes, check_case)?;
}
Ok(bytes)
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[inline]
pub fn hex_decode_vec(src: &[u8]) -> Result<alloc::vec::Vec<u8>, Error> {
hex_decode_vec_with_case(src, CheckCase::None)
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[inline]
pub fn hex_decode_vec_with_case(
src: &[u8],
check_case: CheckCase,
) -> Result<alloc::vec::Vec<u8>, Error> {
if !src.len().is_multiple_of(2) {
return Err(Error::OddLength);
}
let mut bytes = alloc::vec![0; src.len() / 2];
if bytes.len() > OWNED_DECODE_THRESHOLD {
decode_owned_large(src, &mut bytes, check_case)?;
} else {
hex_decode_with_case(src, &mut bytes, check_case)?;
}
Ok(bytes)
}
const OWNED_DECODE_THRESHOLD: usize = 1024;
#[inline(always)]
fn decode_owned_large(src: &[u8], dst: &mut [u8], case: CheckCase) -> Result<(), Error> {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
let decoded = match crate::vectorization_support() {
crate::Vectorization::AVX512 => {
unsafe { x86::hex_decode_avx512_owned(src, dst, case) }
}
crate::Vectorization::AVX2 => {
unsafe { x86::hex_decode_avx2_owned(src, dst, case) }
}
_ => decode_checked(src, dst, case),
};
#[cfg(target_arch = "aarch64")]
let decoded = if crate::vectorization_support() == crate::Vectorization::Neon {
unsafe { aarch64::hex_decode_neon_owned(src, dst, case) }
} else {
decode_checked(src, dst, case)
};
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
let decoded = decode_checked(src, dst, case);
if decoded.is_err() {
decode_diagnosed(src, dst, case)?;
}
Ok(())
}
#[inline(always)]
pub(crate) fn decode_checked(src: &[u8], dst: &mut [u8], check_case: CheckCase) -> Result<(), ()> {
if dst.len() < 8 {
return hex_decode_short_scalar(src, dst, check_case);
}
#[cfg(target_arch = "aarch64")]
let len = dst.len();
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
match crate::vectorization_support() {
crate::Vectorization::AVX512 => {
unsafe { x86::hex_decode_avx512_checked(src, dst, check_case) }
}
crate::Vectorization::AVX2 => {
unsafe { x86::hex_decode_avx2_checked(src, dst, check_case) }
}
crate::Vectorization::SSE41 => {
unsafe { x86::hex_decode_sse41_checked(src, dst, check_case) }
}
crate::Vectorization::None => {
if !hex_check_fallback_with_case(src, check_case) {
return Err(());
}
hex_decode_fallback(src, dst);
Ok(())
}
}
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
{
#[cfg(target_arch = "aarch64")]
if (17..=32).contains(&len) && crate::vectorization_support() == crate::Vectorization::Neon
{
return unsafe { aarch64::hex_decode_bounded_neon(src, dst, check_case) };
}
#[cfg(target_arch = "aarch64")]
if (8..=16).contains(&len) && crate::vectorization_support() == crate::Vectorization::Neon {
return unsafe { aarch64::hex_decode_short_neon(src, dst, check_case) };
}
if !hex_check_with_case(src, check_case) {
return Err(());
}
hex_decode_unchecked(src, dst);
Ok(())
}
}
#[inline]
pub(crate) fn hex_decode_short_scalar(
src: &[u8],
dst: &mut [u8],
case: CheckCase,
) -> Result<(), ()> {
let table = match case {
CheckCase::None => &UNHEX,
CheckCase::Lower => &UNHEX_LOWER,
CheckCase::Upper => &UNHEX_UPPER,
};
let mut decoded = 0u64;
for pair in src.as_chunks::<2>().0 {
let high = table[usize::from(pair[0])];
let low = table[usize::from(pair[1])];
if high | low == NIL {
return Err(());
}
decoded = decoded << 8 | u64::from(high << 4 | low);
}
for slot in dst.iter_mut().rev() {
*slot = decoded as u8;
decoded >>= 8;
}
Ok(())
}
#[cold]
fn decode_diagnosed(src: &[u8], dst: &mut [u8], case: CheckCase) -> Result<(), Error> {
let table = match case {
CheckCase::None => &UNHEX,
CheckCase::Lower => &UNHEX_LOWER,
CheckCase::Upper => &UNHEX_UPPER,
};
let skipped = if src.len() > 64 {
src.chunks_exact(64)
.take_while(|chunk| hex_check_with_case(chunk, case))
.count()
* 64
} else {
0
};
for (offset, &byte) in src[skipped..].iter().enumerate() {
let index = skipped + offset;
if table[usize::from(byte)] == NIL {
return Err(Error::InvalidChar { index, byte });
}
}
hex_decode_fallback(src, dst);
Ok(())
}
#[cfg(any(test, not(any(target_arch = "x86", target_arch = "x86_64"))))]
pub(crate) fn hex_decode_unchecked(src: &[u8], dst: &mut [u8]) {
let len = core::cmp::min(src.len() / 2, dst.len());
let src = &src[..len * 2];
let dst = &mut dst[..len];
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
match crate::vectorization_support() {
crate::Vectorization::AVX512 => {
unsafe { x86::hex_decode_avx512(src, dst) }
}
crate::Vectorization::AVX2 => {
unsafe { x86::hex_decode_avx2(src, dst) }
}
crate::Vectorization::SSE41 => {
unsafe { x86::hex_decode_sse41(src, dst) }
}
crate::Vectorization::None => hex_decode_fallback(src, dst),
}
}
#[cfg(target_arch = "aarch64")]
match crate::vectorization_support() {
crate::Vectorization::Neon => {
unsafe { aarch64::hex_decode_neon(src, dst) }
}
crate::Vectorization::None => hex_decode_fallback(src, dst),
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
hex_decode_fallback(src, dst);
}
#[inline]
pub(crate) fn hex_decode_fallback(src: &[u8], dst: &mut [u8]) {
for (slot, bytes) in dst.iter_mut().zip(src.chunks_exact(2)) {
let a = UNHEX4[bytes[0] as usize];
let b = UNHEX[bytes[1] as usize];
*slot = a | b;
}
}