fashex 0.0.3

Conversion from bytes to hexadecimal string.
Documentation
//! Fuzz testing

#![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(target_arch = "wasm32")]
    // {
    //     use crate::backend::wasm32::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(target_arch = "wasm32")]
    // {
    //     use crate::backend::wasm32::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);
    }
}