#![allow(missing_docs, reason = "XXX")]
#![allow(unsafe_code, reason = "XXX")]
#![allow(unsafe_op_in_unsafe_fn, reason = "XXX")]
use alloc::string::String;
use alloc::vec;
use core::mem::MaybeUninit;
use core::{slice, str};
use crate::backend::generic::{
decode_generic_unchecked, encode_generic_unchecked, validate_generic,
};
use crate::util::{DIGITS_LOWER_16, DIGITS_UPPER_16};
pub fn fuzz_encode(data: &[u8]) {
let expected_lowercase = data
.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 expected_uppercase = data
.iter()
.flat_map(|b| {
[
DIGITS_UPPER_16[(*b >> 4) as usize] as char,
DIGITS_UPPER_16[(*b & 0b1111) as usize] as char,
]
})
.collect::<String>();
macro_rules! test {
($f:ident) => {
unsafe {
let mut output = vec![[MaybeUninit::<u8>::uninit(); 2]; data.len()];
$f::<false>(data, &mut output);
assert!(
slice::from_raw_parts(output.as_ptr().cast::<u8>(), output.len() * 2)
== expected_lowercase.as_bytes(),
"{}: expect \"{}\", got \"{}\" ({:?})",
stringify!($f),
expected_lowercase,
str::from_utf8(slice::from_raw_parts(
output.as_ptr().cast::<u8>(),
output.len() * 2
))
.unwrap_or("<invalid utf-8>"),
slice::from_raw_parts(output.as_ptr().cast::<u8>(), output.len() * 2)
);
}
unsafe {
let mut output = vec![[MaybeUninit::<u8>::uninit(); 2]; data.len()];
$f::<true>(data, &mut output);
assert!(
slice::from_raw_parts(output.as_ptr().cast::<u8>(), output.len() * 2)
== expected_uppercase.as_bytes(),
"{}: expect \"{}\", got \"{}\" ({:?})",
stringify!($f),
expected_uppercase,
str::from_utf8(slice::from_raw_parts(
output.as_ptr().cast::<u8>(),
output.len() * 2
))
.unwrap_or("<invalid utf-8>"),
slice::from_raw_parts(output.as_ptr().cast::<u8>(), output.len() * 2)
);
}
};
}
#[cfg(any(target_arch = "aarch64", target_arch = "arm64ec"))]
{
use crate::backend::aarch64::encode_neon_unchecked;
test!(encode_neon_unchecked);
}
test!(encode_generic_unchecked);
#[cfg(all(feature = "experimental-loongarch64-simd", target_arch = "loongarch64"))]
{
use crate::backend::loongarch64::{encode_lasx_unchecked, encode_lsx_unchecked};
test!(encode_lsx_unchecked);
test!(encode_lasx_unchecked);
}
#[cfg(feature = "portable-simd")]
{
use crate::backend::simd::encode_simd128_unchecked;
test!(encode_simd128_unchecked);
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
use crate::backend::x86::{
encode_avx2_unchecked, encode_avx512_unchecked, encode_ssse3_unchecked,
};
test!(encode_ssse3_unchecked);
test!(encode_avx2_unchecked);
test!(encode_avx512_unchecked);
}
}
pub fn fuzz_decode(data: &[u8]) {
let expected_lowercase = data
.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 expected_uppercase = data
.iter()
.flat_map(|b| {
[
DIGITS_UPPER_16[(*b >> 4) as usize] as char,
DIGITS_UPPER_16[(*b & 0b1111) as usize] as char,
]
})
.collect::<String>();
macro_rules! test {
($($f:tt)+) => {
unsafe {
let mut decoded = vec![MaybeUninit::<u8>::uninit(); data.len()];
let _ = $($f)+(
expected_lowercase.as_bytes().as_chunks_unchecked(),
&mut decoded,
);
assert!(
slice::from_raw_parts(decoded.as_ptr().cast::<u8>(), decoded.len()) == data,
"{}: decoding \"{}\", expect {:?}, got {:?}",
stringify!($($f)+),
expected_lowercase,
data,
slice::from_raw_parts(decoded.as_ptr().cast::<u8>(), decoded.len())
);
}
unsafe {
let mut decoded = vec![MaybeUninit::<u8>::uninit(); data.len()];
let _ = $($f)+(
expected_uppercase.as_bytes().as_chunks_unchecked(),
&mut decoded,
);
assert!(
slice::from_raw_parts(decoded.as_ptr().cast::<u8>(), decoded.len()) == data,
"{}: decoding \"{}\", expect {:?}, got {:?}",
stringify!($($f)+),
expected_uppercase,
data,
slice::from_raw_parts(decoded.as_ptr().cast::<u8>(), decoded.len())
);
}
};
}
#[cfg(any(target_arch = "aarch64", target_arch = "arm64ec"))]
{
use crate::backend::aarch64::decode_neon_unchecked;
test!(decode_neon_unchecked);
}
test!(decode_generic_unchecked::<false>);
test!(decode_generic_unchecked::<true>);
#[cfg(all(feature = "experimental-loongarch64-simd", target_arch = "loongarch64"))]
{
use crate::backend::loongarch64::{decode_lasx_unchecked, decode_lsx_unchecked};
test!(decode_lsx_unchecked);
test!(decode_lasx_unchecked);
}
#[cfg(feature = "portable-simd")]
{
use crate::backend::simd::decode_simd128_unchecked;
test!(decode_simd128_unchecked);
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
use crate::backend::x86::{
decode_avx2_unchecked, decode_avx512_unchecked, decode_ssse3_unchecked,
};
test!(decode_ssse3_unchecked);
test!(decode_avx2_unchecked);
test!(decode_avx512_unchecked);
}
}
pub fn fuzz_validate(data: &[u8]) {
if data.is_empty() {
return;
}
let to_be_injected = data
.iter()
.copied()
.find(|c| !c.is_ascii_hexdigit())
.unwrap_or(0);
let expected_lowercase = data
.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 expected_uppercase = data
.iter()
.flat_map(|b| {
[
DIGITS_UPPER_16[(*b >> 4) as usize] as char,
DIGITS_UPPER_16[(*b & 0b1111) as usize] as char,
]
})
.collect::<String>();
macro_rules! test {
($f:ident) => {
unsafe {
let mut injected = expected_lowercase.clone();
let len = injected.len();
assert!(
$f(injected.as_bytes().as_chunks_unchecked()).is_ok(),
"{}: validating \"{}\", expect validation success",
stringify!($f),
expected_lowercase,
);
injected.as_mut_vec()[len / 2] = to_be_injected;
assert!(
$f(injected.as_bytes().as_chunks_unchecked()).is_err(),
"{}: validating \"{}\", injected {} at {}, expect validation failure",
stringify!($f),
expected_lowercase,
to_be_injected,
len / 2,
);
}
unsafe {
let mut injected = expected_uppercase.clone();
let len = injected.len();
assert!(
$f(injected.as_bytes().as_chunks_unchecked()).is_ok(),
"{}: validating \"{}\", expect validation success",
stringify!($f),
expected_uppercase,
);
injected.as_mut_vec()[len / 2] = to_be_injected;
assert!(
$f(injected.as_bytes().as_chunks_unchecked()).is_err(),
"{}: validating \"{}\", injected {} at {}, expect validation failure",
stringify!($f),
expected_uppercase,
to_be_injected,
len / 2,
);
}
};
}
#[cfg(any(target_arch = "aarch64", target_arch = "arm64ec"))]
{
use crate::backend::aarch64::decode_neon_unchecked;
fn validate_neon(src: &[[u8; 2]]) -> Result<(), crate::error::InvalidInput> {
unsafe { decode_neon_unchecked(src, &mut vec![MaybeUninit::<u8>::uninit(); src.len()]) }
}
test!(validate_neon);
}
test!(validate_generic);
#[cfg(all(feature = "experimental-loongarch64-simd", target_arch = "loongarch64"))]
{
use crate::backend::loongarch64::{decode_lasx_unchecked, decode_lsx_unchecked};
fn validate_lsx(src: &[[u8; 2]]) -> Result<(), crate::error::InvalidInput> {
unsafe { decode_lsx_unchecked(src, &mut vec![MaybeUninit::<u8>::uninit(); src.len()]) }
}
fn validate_lasx(src: &[[u8; 2]]) -> Result<(), crate::error::InvalidInput> {
unsafe { decode_lasx_unchecked(src, &mut vec![MaybeUninit::<u8>::uninit(); src.len()]) }
}
test!(validate_lsx);
test!(validate_lasx);
}
#[cfg(feature = "portable-simd")]
{
use crate::backend::simd::validate_simd128;
test!(validate_simd128);
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
use alloc::vec::Vec;
use crate::backend::x86::{
decode_avx2_unchecked, decode_avx512_unchecked, decode_ssse3_unchecked,
};
use crate::error::InvalidInput;
fn validate_ssse3(src: &[[u8; 2]]) -> Result<(), InvalidInput> {
unsafe {
decode_ssse3_unchecked(src, Vec::with_capacity(src.len()).spare_capacity_mut())
}
}
fn validate_avx2(src: &[[u8; 2]]) -> Result<(), InvalidInput> {
unsafe {
decode_avx2_unchecked(src, Vec::with_capacity(src.len()).spare_capacity_mut())
}
}
fn validate_avx512(src: &[[u8; 2]]) -> Result<(), InvalidInput> {
unsafe {
decode_avx512_unchecked(src, Vec::with_capacity(src.len()).spare_capacity_mut())
}
}
test!(validate_ssse3);
test!(validate_avx2);
test!(validate_avx512);
}
}