base64-ng 2.0.1

no_std-first Base64 encoding and decoding with strict RFC 4648 APIs and optional SIMD
Documentation
#![cfg(feature = "secrets")]

extern crate std;

use std::{format, vec::Vec};

use super::{
    CodecBuilder, DecodePadding, EncodePadding, STRICT_STANDARD_PADDED, STRICT_STANDARD_UNPADDED,
    STRICT_URL_SAFE_PADDED, STRICT_URL_SAFE_UNPADDED, TrailingBits, ValidatedAlphabet,
    compat::STANDARD_PADDED_PADDING_INDIFFERENT,
    secret::{SecretArrayFrame, SecretDecodeError, SecretFrame, SecretInput},
    secret_decoder::require_disjoint_ranges_for_test,
};

fn decode_array<const N: usize, S: super::Codec>(
    codec: &super::Base64<S>,
    chunks: &[&[u8]],
) -> Result<Vec<u8>, SecretDecodeError> {
    let mut frame = SecretArrayFrame::<N>::new(codec)?;
    for chunk in chunks {
        let progress = frame.update(&SecretInput::new(chunk))?;
        assert_eq!(progress.input_consumed(), chunk.len());
        assert_eq!(progress.output_produced(), 0);
    }
    Ok(frame.finish()?.declassify().as_bytes().to_vec())
}

#[test]
fn secret_frames_match_strict_scalar_profiles_and_chunkings() {
    let cases: &[&[u8]] = &[
        b"",
        b"f",
        b"fo",
        b"foo",
        b"foob",
        b"fooba",
        b"foobar",
        b"bounded secret frame input",
    ];
    for input in cases {
        let mut encoded = [0u8; 64];
        let written = STRICT_STANDARD_PADDED
            .encode_into(input, &mut encoded)
            .unwrap();
        let split = written.min(1);
        assert_eq!(
            decode_array::<64, _>(
                &STRICT_STANDARD_PADDED,
                &[&encoded[..split], &encoded[split..written]],
            )
            .unwrap(),
            *input
        );
        let written = STRICT_STANDARD_UNPADDED
            .encode_into(input, &mut encoded)
            .unwrap();
        assert_eq!(
            decode_array::<64, _>(&STRICT_STANDARD_UNPADDED, &[&encoded[..written]]).unwrap(),
            *input
        );
        let written = STRICT_URL_SAFE_PADDED
            .encode_into(input, &mut encoded)
            .unwrap();
        assert_eq!(
            decode_array::<64, _>(&STRICT_URL_SAFE_PADDED, &[&encoded[..written]]).unwrap(),
            *input
        );
        let written = STRICT_URL_SAFE_UNPADDED
            .encode_into(input, &mut encoded)
            .unwrap();
        assert_eq!(
            decode_array::<64, _>(&STRICT_URL_SAFE_UNPADDED, &[&encoded[..written]]).unwrap(),
            *input
        );
    }
}

#[test]
fn borrowed_frame_withholds_output_until_the_result_gate() {
    let mut staging = [0xa5; 16];
    let mut output = [0xa5; 16];
    let mut frame =
        SecretFrame::new(&STRICT_STANDARD_PADDED, 16, &mut staging, &mut output).unwrap();
    frame.update(&SecretInput::new(b"c2VjcmV0")).unwrap();
    assert_eq!(frame.state().input_len(), 8);
    let secret_output = frame.finish().unwrap();
    assert_eq!(secret_output.expose_secret().as_bytes(), b"secret");
    drop(secret_output);
    assert_eq!(staging, [0; 16]);
    assert_eq!(output, [0; 16]);
}

#[test]
fn dropping_partial_borrowed_frame_models_cancellation_cleanup() {
    let mut staging = [0xa5; 16];
    let mut output = [0xa5; 16];
    {
        let mut frame =
            SecretFrame::new(&STRICT_STANDARD_PADDED, 16, &mut staging, &mut output).unwrap();
        frame.update(&SecretInput::new(b"c2Vj")).unwrap();
    }
    assert_eq!(staging, [0; 16]);
    assert_eq!(output, [0; 16]);
}

#[test]
fn borrowed_frame_rejects_unsupported_policy_before_mutating_storage() {
    let mut staging = [0xa5; 16];
    let mut output = [0x5a; 16];
    assert!(matches!(
        SecretFrame::new(
            &STANDARD_PADDED_PADDING_INDIFFERENT,
            16,
            &mut staging,
            &mut output,
        ),
        Err(SecretDecodeError::UnsupportedPolicy)
    ));
    assert_eq!(staging, [0xa5; 16]);
    assert_eq!(output, [0x5a; 16]);
}

#[test]
fn malformed_input_is_opaque_absorbing_and_wipes_borrowed_storage() {
    let mut staging = [0xa5; 16];
    let mut output = [0xa5; 16];
    let error = {
        let mut frame =
            SecretFrame::new(&STRICT_STANDARD_PADDED, 16, &mut staging, &mut output).unwrap();
        frame.update(&SecretInput::new(b"c2Vj!mV0")).unwrap();
        frame.finish().unwrap_err()
    };
    assert_eq!(error, SecretDecodeError::InvalidInput);
    assert_eq!(format!("{error}"), "invalid secret base64 input");
    assert_eq!(staging, [0; 16]);
    assert_eq!(output, [0; 16]);

    let mut frame = SecretArrayFrame::<3>::new(&STRICT_STANDARD_PADDED).unwrap();
    let oversized = frame.update(&SecretInput::new(b"QUJDRA==")).unwrap_err();
    assert_eq!(
        oversized,
        SecretDecodeError::InputTooLarge {
            input_len: 8,
            maximum_encoded_len: 4,
        }
    );
    assert!(frame.state().is_failed());
    assert_eq!(
        frame.update(&SecretInput::new(b"")),
        Err(SecretDecodeError::Failed)
    );
    let (private, public) = frame.storage_for_test();
    assert_eq!(private, &[0; 3]);
    assert_eq!(public, &[0; 3]);
}

#[test]
fn oversized_later_update_wipes_pending_secret_state_immediately() {
    let mut frame = SecretArrayFrame::<6>::new(&STRICT_STANDARD_PADDED).unwrap();
    frame.update(&SecretInput::new(b"QUJD")).unwrap();

    assert_eq!(
        frame.update(&SecretInput::new(b"RA===")),
        Err(SecretDecodeError::InputTooLarge {
            input_len: 9,
            maximum_encoded_len: 8,
        })
    );
    assert!(frame.state().is_failed());
    assert!(frame.state().pending_is_clear_for_test());
    let (staging, output) = frame.storage_for_test();
    assert_eq!(staging, &[0; 6]);
    assert_eq!(output, &[0; 6]);
}

#[test]
fn equal_public_lengths_receive_equal_symbol_scan_work() {
    for input in [b"QUJDRA==".as_slice(), b"QU!DRA==", b"====RA=="] {
        let mut frame = SecretArrayFrame::<6>::new(&STRICT_STANDARD_PADDED).unwrap();
        frame.update(&SecretInput::new(&input[..3])).unwrap();
        frame.update(&SecretInput::new(&input[3..])).unwrap();
        assert_eq!(frame.state().symbol_scans_for_test(), input.len() * 64);
        let _ = frame.finish();
    }

    let mut frame = SecretArrayFrame::<3>::new(&STRICT_STANDARD_PADDED).unwrap();
    assert!(matches!(
        frame.update(&SecretInput::new(b"QUJDRA==")),
        Err(SecretDecodeError::InputTooLarge { .. })
    ));
    assert_eq!(frame.state().symbol_scans_for_test(), 0);
}

#[test]
fn padded_capacity_is_checked_opaquely_at_finish() {
    assert_eq!(
        decode_array::<1, _>(&STRICT_STANDARD_PADDED, &[b"Zg=="]).unwrap(),
        b"f"
    );
    assert_eq!(
        decode_array::<1, _>(&STRICT_STANDARD_PADDED, &[b"Zm9v"]),
        Err(SecretDecodeError::InvalidInput)
    );
}

#[test]
fn malformed_classes_and_positions_never_release_plaintext() {
    let canonical = b"c2VjcmV0";
    for index in 0..canonical.len() {
        for replacement in [b'!', b'=', 0xff] {
            let mut malformed = *canonical;
            malformed[index] = replacement;
            assert_eq!(
                decode_array::<16, _>(&STRICT_STANDARD_PADDED, &[&malformed]),
                Err(SecretDecodeError::InvalidInput)
            );
        }
    }
    for malformed in [b"A===".as_slice(), b"AB=C", b"AB==AAAA", b"AB=!"] {
        assert_eq!(
            decode_array::<16, _>(&STRICT_STANDARD_PADDED, &[malformed]),
            Err(SecretDecodeError::InvalidInput)
        );
    }
    for malformed in [b"A".as_slice(), b"AB=", b"ABCD="] {
        assert_eq!(
            decode_array::<16, _>(&STRICT_STANDARD_UNPADDED, &[malformed]),
            Err(SecretDecodeError::InvalidInput)
        );
    }
}

#[test]
fn successful_array_tail_stays_zero_and_declassifies_only_prefix() {
    let mut frame = SecretArrayFrame::<16>::new(&STRICT_STANDARD_PADDED).unwrap();
    assert_eq!(frame.state().maximum_decoded_len(), 16);
    assert_eq!(frame.state().maximum_encoded_len(), 24);
    frame.update(&SecretInput::new(b"a2V5")).unwrap();
    let secret = frame.finish().unwrap();
    assert_eq!(secret.expose_secret().as_bytes(), b"key");
    assert!(secret.backing_for_test()[3..].iter().all(|byte| *byte == 0));
    let ordinary = secret.declassify();
    assert_eq!(ordinary.as_bytes(), b"key");
}

#[test]
fn runtime_validated_alphabet_uses_the_same_fixed_scan_boundary() {
    let alphabet = ValidatedAlphabet::new(
        *b"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
    )
    .unwrap();
    let codec = CodecBuilder::new(alphabet)
        .encode_padding(EncodePadding::Unpadded)
        .decode_padding(DecodePadding::Forbid)
        .trailing_bits(TrailingBits::RequireCanonical)
        .build()
        .unwrap();
    let mut encoded = [0u8; 32];
    let written = codec.encode_into(b"custom", &mut encoded).unwrap();
    assert_eq!(
        decode_array::<16, _>(&codec, &[&encoded[..written]]).unwrap(),
        b"custom"
    );
}

#[test]
fn range_arithmetic_rejects_overlap_and_overflow() {
    assert_eq!(
        require_disjoint_ranges_for_test(100, 8, 107, 4),
        Err(SecretDecodeError::OverlappingBuffers)
    );
    assert_eq!(require_disjoint_ranges_for_test(100, 8, 108, 4), Ok(()));
    assert_eq!(
        require_disjoint_ranges_for_test(usize::MAX, 2, 0, 0),
        Err(SecretDecodeError::AddressRangeOverflow)
    );
}

#[cfg(feature = "std")]
#[test]
fn borrowed_frame_wipes_on_unwind() {
    let mut staging = [0xa5; 16];
    let mut output = [0xa5; 16];
    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        let mut frame =
            SecretFrame::new(&STRICT_STANDARD_PADDED, 16, &mut staging, &mut output).unwrap();
        frame.update(&SecretInput::new(b"c2VjcmV0")).unwrap();
        panic!("reviewed secret frame cleanup test");
    }));
    assert!(result.is_err());
    assert_eq!(staging, [0; 16]);
    assert_eq!(output, [0; 16]);
}

#[cfg(feature = "alloc")]
#[test]
fn vector_frame_preallocates_and_preserves_zero_spare_capacity() {
    use super::secret::SecretVecFrame;

    let mut frame = SecretVecFrame::new(&STRICT_STANDARD_PADDED, 32).unwrap();
    frame.update(&SecretInput::new(b"c2VjcmV0")).unwrap();
    let secret = frame.finish().unwrap();
    assert_eq!(secret.expose_secret().as_bytes(), b"secret");
    assert!(secret.capacity() >= 32);
    let ordinary = secret.declassify_into_unprotected_vec();
    assert_eq!(ordinary, std::vec![b's', b'e', b'c', b'r', b'e', b't']);
}

#[cfg(feature = "alloc")]
#[test]
fn vector_frame_never_reallocates_after_classified_input() {
    use super::secret::SecretVecFrame;

    let mut frame = SecretVecFrame::new(&STRICT_STANDARD_PADDED, 32).unwrap();
    let before = frame.allocation_snapshot();
    for chunk in [b"c2".as_slice(), b"Vj".as_slice(), b"cmV0".as_slice()] {
        frame.update(&SecretInput::new(chunk)).unwrap();
        assert_eq!(frame.allocation_snapshot(), before);
    }
    let secret = frame.finish().unwrap();
    assert_eq!(secret.expose_secret().as_bytes(), b"secret");
}