#![doc = include_str!("../README.md")]
#![no_std]
#![allow(clippy::inline_always, reason = "XXX")]
#![cfg_attr(feature = "portable-simd", feature(portable_simd))]
#![cfg_attr(
all(feature = "experimental-loongarch64-simd", target_arch = "loongarch64"),
feature(stdarch_loongarch)
)]
#[cfg(any(test, feature = "alloc"))]
extern crate alloc;
#[cfg(any(test, feature = "std"))]
extern crate std;
mod backend;
pub mod error;
#[cfg(feature = "fuzz")]
pub mod fuzz;
pub mod util;
use core::mem::MaybeUninit;
use crate::error::InvalidInput;
pub fn encode<const UPPER: bool>(
src: &[u8],
dst: &mut [MaybeUninit<u8>],
) -> Result<(), InvalidInput> {
backend::encode::<UPPER>(src, dst)
}
#[inline]
pub fn decode(src: &[u8], dst: &mut [MaybeUninit<u8>]) -> Result<(), InvalidInput> {
backend::decode(src, dst)
}
pub const fn encode_generic<const UPPER: bool>(
src: &[u8],
dst: &mut [MaybeUninit<u8>],
) -> Result<(), InvalidInput> {
if 2 * src.len() == dst.len() {
#[allow(unsafe_code, reason = "The length is validated")]
let dst: &mut [[MaybeUninit<u8>; 2]] = unsafe { dst.as_chunks_unchecked_mut() };
#[allow(unsafe_code, reason = "The length is validated")]
unsafe {
backend::generic::encode_generic_unchecked::<UPPER>(src, dst);
};
Ok(())
} else {
Err(InvalidInput)
}
}
pub const fn decode_generic(src: &[u8], dst: &mut [MaybeUninit<u8>]) -> Result<(), InvalidInput> {
if src.len() == 2 * dst.len() {
#[allow(unsafe_code, reason = "The length is validated")]
let src: &[[u8; 2]] = unsafe { src.as_chunks_unchecked() };
#[allow(unsafe_code, reason = "The length is validated")]
unsafe {
backend::generic::decode_generic_unchecked::<false>(src, dst)
}
} else {
Err(InvalidInput)
}
}
#[macro_export]
macro_rules! encode {
($bytes:expr) => {
$crate::encode!($bytes, false)
};
($bytes:expr, $uppercase:expr) => {{
const ENCODED: [u8; $bytes.len() * 2] = {
let buf: &mut [::core::mem::MaybeUninit<u8>; const { $bytes.len() * 2 }] =
&mut [::core::mem::MaybeUninit::uninit(); _];
#[allow(unsafe_code, reason = "XXX")]
let bytes = unsafe { ::core::slice::from_raw_parts($bytes.as_ptr(), $bytes.len()) };
match $crate::encode_generic::<{ $uppercase }>(bytes, buf) {
Ok(()) => {}
Err(_) => unreachable!(),
};
#[allow(unsafe_code, reason = "XXX")]
unsafe {
::core::mem::transmute::<_, _>(*buf)
}
};
#[allow(unsafe_code, reason = "XXX")]
unsafe {
::core::str::from_utf8_unchecked(&ENCODED)
}
}};
}
#[macro_export]
macro_rules! decode {
($bytes:expr) => {{
const DECODED: [u8; $bytes.len() / 2] = {
assert!(
$bytes.len() % 2 == 0,
"the length of the input must be even"
);
let buf: &mut [::core::mem::MaybeUninit<u8>; const { $bytes.len() / 2 }] =
&mut [::core::mem::MaybeUninit::uninit(); _];
#[allow(unsafe_code, reason = "XXX")]
let bytes = unsafe { ::core::slice::from_raw_parts($bytes.as_ptr(), $bytes.len()) };
match $crate::decode_generic(bytes, buf) {
Ok(()) => {}
Err(_) => panic!("invalid hexadecimal string"),
};
#[allow(unsafe_code, reason = "XXX")]
unsafe {
::core::mem::transmute::<_, _>(*buf)
}
};
&DECODED
}};
}
#[cfg(test)]
mod smoking {
#![allow(unsafe_code, reason = "XXX")]
#![allow(unsafe_op_in_unsafe_fn, reason = "XXX")]
#![allow(clippy::cognitive_complexity, reason = "XXX")]
use alloc::string::String;
use alloc::vec;
use core::mem::MaybeUninit;
use core::{slice, str};
use super::{decode, decode_generic, encode, encode_generic};
use crate::util::DIGITS_LOWER_16;
macro_rules! test {
(
Encode = $encode_f:ident;
Decode = $($decode_f:ident),*;
Case = $i:expr
) => {{
let input = $i;
let expected = input
.iter()
.flat_map(|b| [
DIGITS_LOWER_16[(*b >> 4) as usize] as char,
DIGITS_LOWER_16[(*b & 0b1111) as usize] as char,
])
.collect::<String>();
let mut output = vec![MaybeUninit::<u8>::uninit(); input.len() * 2];
$encode_f::<false>(input, &mut output).unwrap();
let output = unsafe {
slice::from_raw_parts(
output.as_ptr().cast::<u8>(),
output.len(),
)
};
assert_eq!(
output,
expected.as_bytes(),
"Encode error, expect \"{expected}\", got \"{}\" ({:?})",
str::from_utf8(output).unwrap_or("<invalid utf-8>"),
output
);
$({
let mut decoded = vec![MaybeUninit::<u8>::uninit(); input.len()];
$decode_f(output, &mut decoded).unwrap();
unsafe {
assert_eq!(
decoded.assume_init_ref(),
input,
"Decode error for {}, expect {:?}, got {:?}",
stringify!($decode_f),
input,
decoded.assume_init_ref()
);
}
})+
}};
}
#[test]
fn test_encode() {
const CASE: &[u8; 65] = &[
0xA1, 0xA4, 0xA2, 0x49, 0x4A, 0x43, 0x03, 0x31, 0x5F, 0x60, 0xE7, 0x8F, 0x17, 0x36,
0x31, 0xAD, 0xB3, 0xE4, 0xF2, 0x35, 0x33, 0x6F, 0x05, 0xF0, 0xAA, 0x52, 0xD2, 0x6F,
0x3A, 0xB7, 0x4A, 0xAB, 0x66, 0x32, 0xB0, 0xD6, 0x1C, 0x8C, 0xED, 0x85, 0x9E, 0x03,
0x90, 0x87, 0x16, 0x9C, 0xBA, 0x34, 0xAD, 0x59, 0x35, 0x66, 0xED, 0x80, 0x22, 0x85,
0xDB, 0x54, 0x5E, 0x79, 0xD3, 0x9A, 0x6F, 0x24, 0x43,
];
test!(
Encode = encode_generic;
Decode = decode_generic, decode;
Case = &CASE[..15]
);
test!(
Encode = encode_generic;
Decode = decode_generic, decode;
Case = &CASE[..16]
);
test!(
Encode = encode_generic;
Decode = decode_generic, decode;
Case = &CASE[..17]
);
test!(
Encode = encode_generic;
Decode = decode_generic, decode;
Case = &CASE[..31]
);
test!(
Encode = encode_generic;
Decode = decode_generic, decode;
Case = &CASE[..32]
);
test!(
Encode = encode_generic;
Decode = decode_generic, decode;
Case = &CASE[..33]
);
test!(
Encode = encode_generic;
Decode = decode_generic, decode;
Case = &CASE[..63]
);
test!(
Encode = encode_generic;
Decode = decode_generic, decode;
Case = &CASE[..64]
);
test!(
Encode = encode_generic;
Decode = decode_generic, decode;
Case = &CASE[..65]
);
test!(
Encode = encode;
Decode = decode_generic, decode;
Case = &CASE[..15]
);
test!(
Encode = encode;
Decode = decode_generic, decode;
Case = &CASE[..16]
);
test!(
Encode = encode;
Decode = decode_generic, decode;
Case = &CASE[..17]
);
test!(
Encode = encode;
Decode = decode_generic, decode;
Case = &CASE[..31]
);
test!(
Encode = encode;
Decode = decode_generic, decode;
Case = &CASE[..32]
);
test!(
Encode = encode;
Decode = decode_generic, decode;
Case = &CASE[..33]
);
test!(
Encode = encode;
Decode = decode_generic, decode;
Case = &CASE[..63]
);
test!(
Encode = encode;
Decode = decode_generic, decode;
Case = &CASE[..64]
);
test!(
Encode = encode;
Decode = decode_generic, decode;
Case = &CASE[..65]
);
}
}