base64-ng 2.0.1

no_std-first Base64 encoding and decoding with strict RFC 4648 APIs and optional SIMD
Documentation
use crate::{Alphabet, Standard, UrlSafe};

use super::neon::{test_direct_decode_16, test_direct_encode_12};
use super::{decode_slice_neon, encode_slice_neon};

#[test]
fn direct_neon_encode_is_exhaustive_by_input_byte_and_position() {
    for position in 0..12 {
        for value in u8::MIN..=u8::MAX {
            let mut input = [0u8; 12];
            fill_pattern(&mut input, value.wrapping_mul(17));
            input[position] = value;
            assert_direct_encode::<Standard>(&input);
            assert_direct_encode::<UrlSafe>(&input);
        }
    }
}

#[test]
fn direct_neon_decode_accepts_every_symbol_in_every_lane() {
    for position in 0..16 {
        for value in 0..64 {
            let mut standard = *b"QUJDREVGR0hJSktM";
            standard[position] = Standard::ENCODE[value];
            assert_direct_decode::<Standard>(&standard);

            let mut url_safe = *b"QUJDREVGR0hJSktM";
            url_safe[position] = UrlSafe::ENCODE[value];
            assert_direct_decode::<UrlSafe>(&url_safe);
        }
    }
}

#[test]
fn direct_neon_decode_rejects_every_non_alphabet_byte_without_writing() {
    for position in 0..16 {
        for byte in u8::MIN..=u8::MAX {
            if !Standard::ENCODE.contains(&byte) {
                let mut input = *b"QUJDREVGR0hJSktM";
                input[position] = byte;
                let mut output = [0x5a; 12];
                assert!(!test_direct_decode_16::<Standard>(&input, &mut output));
                assert_eq!(output, [0x5a; 12]);
            }
            if !UrlSafe::ENCODE.contains(&byte) {
                let mut input = *b"QUJDREVGR0hJSktM";
                input[position] = byte;
                let mut output = [0xa5; 12];
                assert!(!test_direct_decode_16::<UrlSafe>(&input, &mut output));
                assert_eq!(output, [0xa5; 12]);
            }
        }
    }
}

#[test]
fn neon_slice_paths_match_scalar_across_blocks_tails_and_padding() {
    for len in 0..=385 {
        let mut input = [0u8; 385];
        fill_pattern(&mut input, u8::try_from(len % 256).unwrap());
        assert_slice_case::<Standard, true>(&input[..len]);
        assert_slice_case::<Standard, false>(&input[..len]);
        assert_slice_case::<UrlSafe, true>(&input[..len]);
        assert_slice_case::<UrlSafe, false>(&input[..len]);
    }
}

#[test]
fn neon_strict_decode_preserves_scalar_errors_for_every_position() {
    let mut raw = [0u8; 192];
    fill_pattern(&mut raw, 0x91);
    let mut encoded = [0u8; 256];
    let encoded_len = crate::scalar::encode_slice::<Standard, true>(&raw, &mut encoded).unwrap();

    for position in 0..encoded_len {
        let original = encoded[position];
        encoded[position] = b'!';
        let mut neon = [0x55; 192];
        let mut scalar = [0xaa; 192];
        let neon_error =
            decode_slice_neon::<Standard, true>(&encoded[..encoded_len], &mut neon).unwrap_err();
        let scalar_error =
            crate::scalar::decode_slice::<Standard, true>(&encoded[..encoded_len], &mut scalar)
                .unwrap_err();
        assert_eq!(neon_error, scalar_error);
        assert_eq!(neon, [0x55; 192]);
        encoded[position] = original;
    }
}

fn assert_direct_encode<A: Alphabet>(input: &[u8; 12]) {
    let mut direct = [0x55; 16];
    let mut scalar = [0xaa; 16];
    assert!(test_direct_encode_12::<A>(input, &mut direct));
    let written = crate::scalar::encode_slice::<A, true>(input, &mut scalar).unwrap();
    assert_eq!(written, direct.len());
    assert_eq!(direct, scalar);
}

fn assert_direct_decode<A: Alphabet>(input: &[u8; 16]) {
    let mut direct = [0x55; 12];
    let mut scalar = [0xaa; 12];
    assert!(test_direct_decode_16::<A>(input, &mut direct));
    let written = crate::scalar::decode_slice::<A, false>(input, &mut scalar).unwrap();
    assert_eq!(written, direct.len());
    assert_eq!(direct, scalar);
}

fn assert_slice_case<A: Alphabet, const PAD: bool>(input: &[u8]) {
    let mut neon_encoded = [0x55; 516];
    let mut scalar_encoded = [0xaa; 516];
    let neon_len = encode_slice_neon::<A, PAD>(input, &mut neon_encoded).unwrap();
    let scalar_len = crate::scalar::encode_slice::<A, PAD>(input, &mut scalar_encoded).unwrap();
    assert_eq!(neon_len, scalar_len);
    assert_eq!(&neon_encoded[..neon_len], &scalar_encoded[..scalar_len]);

    let mut neon_decoded = [0x55; 385];
    let mut scalar_decoded = [0xaa; 385];
    let encoded_len = neon_len;
    let neon_len = decode_slice_neon::<A, PAD>(
        &neon_encoded[..encoded_len],
        &mut neon_decoded,
    )
    .unwrap_or_else(|error| {
        panic!(
            "NEON decode failed for raw_len={}, encoded_len={encoded_len}, padded={PAD}: {error:?}",
            input.len()
        )
    });
    let scalar_len =
        crate::scalar::decode_slice::<A, PAD>(&neon_encoded[..encoded_len], &mut scalar_decoded)
            .unwrap();
    assert_eq!(neon_len, scalar_len);
    assert_eq!(&neon_decoded[..neon_len], input);
    assert_eq!(&neon_decoded[..neon_len], &scalar_decoded[..scalar_len]);
}

fn fill_pattern(output: &mut [u8], seed: u8) {
    for (index, byte) in output.iter_mut().enumerate() {
        *byte = seed
            .wrapping_add(u8::try_from(index % 256).unwrap().wrapping_mul(73))
            .rotate_left(u32::try_from(index % 8).unwrap());
    }
}