#![allow(missing_docs, reason = "XXX")]
#![allow(unsafe_code, reason = "XXX")]
#![allow(unsafe_op_in_unsafe_fn, reason = "XXX")]
#![allow(clippy::cognitive_complexity, reason = "XXX")]
#![allow(clippy::items_after_statements, reason = "XXX")]
#![allow(clippy::missing_panics_doc, reason = "XXX")]
#![allow(clippy::too_many_lines, reason = "XXX")]
use alloc::boxed::Box;
use alloc::rc::Rc;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec;
use alloc::vec::Vec;
use core::mem::MaybeUninit;
use core::{slice, str};
use crate::backend::generic::{decode_generic_unchecked, encode_generic_unchecked};
use crate::traits::Hex;
use crate::util::{HEX_CHARS_LOWER, HEX_CHARS_UPPER};
use crate::{decode, decode_generic, encode, encode_generic};
pub fn fuzz_encode(data: &[u8]) {
let expected = data
.iter()
.flat_map(|b| {
[
HEX_CHARS_LOWER[(*b >> 4) as usize] as char,
HEX_CHARS_LOWER[(*b & 0b1111) as usize] as char,
]
})
.collect::<String>();
assert!(
data.encode_hex::<String, false>()
.unwrap()
.eq_ignore_ascii_case(&expected)
);
assert!(
data.encode_hex::<Box<str>, false>()
.unwrap()
.eq_ignore_ascii_case(&expected)
);
assert!(
data.encode_hex::<Arc<str>, false>()
.unwrap()
.eq_ignore_ascii_case(&expected)
);
assert!(
data.encode_hex::<Rc<str>, false>()
.unwrap()
.eq_ignore_ascii_case(&expected)
);
assert!(
data.encode_hex::<Vec<u8>, false>()
.unwrap()
.eq_ignore_ascii_case(expected.as_bytes())
);
assert!(
data.encode_hex::<Box<[u8]>, false>()
.unwrap()
.eq_ignore_ascii_case(expected.as_bytes())
);
assert!(
data.encode_hex::<Arc<[u8]>, false>()
.unwrap()
.eq_ignore_ascii_case(expected.as_bytes())
);
assert!(
data.encode_hex::<Rc<[u8]>, false>()
.unwrap()
.eq_ignore_ascii_case(expected.as_bytes())
);
{
let mut output = String::with_capacity(data.len() * 2);
encode::<false>(data, unsafe { output.as_mut_vec() }).unwrap();
assert!(
expected.eq_ignore_ascii_case(&output),
"expect \"{expected}\", got \"{output}\""
);
}
{
let mut output = String::with_capacity(data.len() * 2);
encode::<true>(data, unsafe { output.as_mut_vec() }).unwrap();
assert!(
expected.eq_ignore_ascii_case(&output),
"expect \"{expected}\", got \"{output}\""
);
}
{
let mut output = String::with_capacity(data.len() * 2);
{
let len =
encode_generic::<false>(data, unsafe { output.as_mut_vec().spare_capacity_mut() })
.unwrap()
.len();
unsafe {
output.as_mut_vec().set_len(len);
}
}
assert!(
expected.eq_ignore_ascii_case(&output),
"expect \"{expected}\", got \"{output}\""
);
}
{
let mut output = String::with_capacity(data.len() * 2);
{
let len =
encode_generic::<true>(data, unsafe { output.as_mut_vec().spare_capacity_mut() })
.unwrap()
.len();
unsafe {
output.as_mut_vec().set_len(len);
}
}
assert!(
expected.eq_ignore_ascii_case(&output),
"expect \"{expected}\", got \"{output}\""
);
}
macro_rules! test {
($($f:tt)+) => {
unsafe {
let mut output = vec![[MaybeUninit::<u8>::uninit(); 2]; data.len()];
$($f)+(data, &mut output);
assert!(
slice::from_raw_parts(output.as_ptr().cast::<u8>(), output.len() * 2)
.eq_ignore_ascii_case(expected.as_bytes()),
"{}: expect \"{}\", got \"{}\" ({:?})",
stringify!($($f)+),
expected,
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::<false>);
test!(encode_neon_unchecked::<true>);
}
test!(encode_generic_unchecked::<false>);
test!(encode_generic_unchecked::<true>);
#[cfg(all(feature = "experimental-loongarch64-simd", target_arch = "loongarch64"))]
{
use crate::backend::loongarch64::{encode_lasx_unchecked, encode_lsx_unchecked};
test!(encode_lsx_unchecked::<false>);
test!(encode_lsx_unchecked::<true>);
test!(encode_lasx_unchecked::<false>);
test!(encode_lasx_unchecked::<true>);
}
#[cfg(feature = "portable-simd")]
{
use crate::backend::simd::encode_simd128_unchecked;
test!(encode_simd128_unchecked::<false>);
test!(encode_simd128_unchecked::<true>);
}
#[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::<false>);
test!(encode_ssse3_unchecked::<true>);
test!(encode_avx2_unchecked::<false>);
test!(encode_avx2_unchecked::<true>);
test!(encode_avx512_unchecked::<false>);
test!(encode_avx512_unchecked::<true>);
}
}
pub fn fuzz_decode(data: &[u8]) {
let expected_lowercase = data
.iter()
.flat_map(|b| {
[
HEX_CHARS_LOWER[(*b >> 4) as usize] as char,
HEX_CHARS_LOWER[(*b & 0b1111) as usize] as char,
]
})
.collect::<String>();
let expected_uppercase = data
.iter()
.flat_map(|b| {
[
HEX_CHARS_UPPER[(*b >> 4) as usize] as char,
HEX_CHARS_UPPER[(*b & 0b1111) as usize] as char,
]
})
.collect::<String>();
assert_eq!(expected_lowercase.decode_hex::<Vec<u8>>().unwrap(), data);
assert_eq!(
&*expected_uppercase.decode_hex::<Box<[u8]>>().unwrap(),
data
);
assert_eq!(
&*expected_uppercase.decode_hex::<Arc<[u8]>>().unwrap(),
data
);
assert_eq!(&*expected_uppercase.decode_hex::<Rc<[u8]>>().unwrap(), data);
{
let mut decoded = Vec::with_capacity(data.len());
decode(expected_lowercase.as_bytes(), &mut decoded).unwrap();
assert_eq!(data, decoded.as_slice());
}
{
let mut decoded = Vec::with_capacity(data.len());
decode(expected_uppercase.as_bytes(), &mut decoded).unwrap();
assert_eq!(data, decoded.as_slice());
}
{
let mut decoded = Vec::with_capacity(data.len());
{
let len = decode_generic(expected_lowercase.as_bytes(), decoded.spare_capacity_mut())
.unwrap()
.len();
unsafe {
decoded.set_len(len);
}
}
assert_eq!(data, decoded.as_slice());
}
{
let mut decoded = Vec::with_capacity(data.len());
{
let len = decode_generic(expected_uppercase.as_bytes(), decoded.spare_capacity_mut())
.unwrap()
.len();
unsafe {
decoded.set_len(len);
}
}
assert_eq!(data, decoded.as_slice());
}
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| {
[
HEX_CHARS_LOWER[(*b >> 4) as usize] as char,
HEX_CHARS_LOWER[(*b & 0b1111) as usize] as char,
]
})
.collect::<String>();
let expected_uppercase = data
.iter()
.flat_map(|b| {
[
HEX_CHARS_UPPER[(*b >> 4) as usize] as char,
HEX_CHARS_UPPER[(*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,
);
}
};
}
fn validate(src: &[[u8; 2]]) -> Result<(), crate::error::InvalidInput> {
decode(src.as_flattened(), &mut Vec::with_capacity(src.len())).map(|_| ())
}
test!(validate);
fn validate_generic(src: &[[u8; 2]]) -> Result<(), crate::error::InvalidInput> {
decode_generic(
src.as_flattened(),
Vec::with_capacity(src.len()).spare_capacity_mut(),
)
.map(|_| ())
}
test!(validate_generic);
#[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);
}
#[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::decode_simd128_unchecked;
fn validate_simd128(src: &[[u8; 2]]) -> Result<(), crate::error::InvalidInput> {
unsafe {
decode_simd128_unchecked(src, &mut vec![MaybeUninit::<u8>::uninit(); src.len()])
}
}
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);
}
}
#[cfg(test)]
#[cfg(not(miri))]
mod smoking {
use alloc::vec::Vec;
use core::iter;
use super::*;
#[test]
fn test_fuzz() {
for size in 0..1024 {
let bytes = iter::repeat_n(fastrand::u8(..), size).collect::<Vec<_>>();
fuzz_encode(&bytes);
fuzz_decode(&bytes);
fuzz_validate(&bytes);
}
}
}