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 core::mem::{needs_drop, size_of};
use std::format;

use super::{
    STRICT_STANDARD_PADDED,
    secret::{ExposedSecret, SecretArray, SecretInput, SecretOutput},
};

#[cfg(feature = "alloc")]
use super::secret::SecretVec;

#[test]
fn secret_wrappers_require_explicit_exposure_and_redact_formatting() {
    let input = SecretInput::new(b"classified");
    assert_eq!(input.expose_secret().as_bytes(), b"classified");
    assert_eq!(
        format!("{input:?}"),
        "SecretInput { bytes: \"<redacted>\", len: 10 }"
    );
    assert_eq!(format!("{input}"), "<redacted secret>");

    let exposed: ExposedSecret<'_> = input.expose_secret();
    assert_eq!(AsRef::<[u8]>::as_ref(&exposed), b"classified");
    assert_eq!(
        format!("{exposed:?}"),
        "ExposedSecret { bytes: \"<redacted>\", len: 10 }"
    );
    assert_eq!(format!("{exposed}"), "<redacted secret>");

    let mut encoded = [0u8; 16];
    let written = STRICT_STANDARD_PADDED
        .encode_into(input.expose_secret().as_bytes(), &mut encoded)
        .unwrap();
    assert_eq!(&encoded[..written], b"Y2xhc3NpZmllZA==");
}

#[test]
fn secret_output_wipes_tail_and_complete_range_on_drop() {
    let mut backing = [0xa5; 12];
    backing[..3].copy_from_slice(b"key");
    {
        let mut output = SecretOutput::from_initialized(&mut backing, 3).unwrap();
        assert_eq!(output.expose_secret().as_bytes(), b"key");
        assert_eq!(output.capacity(), 12);
        assert_eq!(
            format!("{output:?}"),
            "SecretOutput { bytes: \"<redacted>\", len: 3 }"
        );
        assert_eq!(format!("{output}"), "<redacted secret>");
        {
            let mut exposed = output.expose_secret_mut();
            assert_eq!(
                format!("{exposed:?}"),
                "ExposedSecretMut { bytes: \"<redacted>\", len: 3 }"
            );
            assert_eq!(format!("{exposed}"), "<redacted secret>");
            exposed.as_bytes_mut()[0] = b'K';
        }
        assert_eq!(output.expose_secret().as_bytes(), b"Key");
    }
    assert_eq!(backing, [0; 12]);
}

#[test]
fn secret_output_rejection_wipes_caller_storage() {
    let mut backing = [0xa5; 4];
    let error = SecretOutput::from_initialized(&mut backing, 5).unwrap_err();
    assert_eq!(error.length(), 5);
    assert_eq!(error.capacity(), 4);
    assert_eq!(backing, [0; 4]);
}

#[cfg(feature = "std")]
#[test]
fn secret_output_unwind_wipes_caller_storage() {
    let mut backing = [0xa5; 8];
    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
        let _output = SecretOutput::from_initialized(&mut backing, 8).unwrap();
        panic!("reviewed cleanup test");
    }));
    assert!(result.is_err());
    assert_eq!(backing, [0; 8]);
}

#[test]
fn declassification_is_visible_and_transfers_cleanup_responsibility() {
    let mut backing = [0xa5; 8];
    backing[..3].copy_from_slice(b"key");
    let output = SecretOutput::from_initialized(&mut backing, 3).unwrap();
    let ordinary = output.declassify();
    assert_eq!(ordinary.as_bytes(), b"key");
    let (storage, len) = ordinary.into_parts();
    assert_eq!(len, 3);
    assert_eq!(storage, b"key\0\0\0\0\0");
}

#[test]
fn fixed_secret_storage_wipes_tail_and_redacts() {
    let mut bytes = [0xa5; 8];
    bytes[..3].copy_from_slice(b"key");
    let mut secret = SecretArray::from_array(bytes, 3).unwrap();
    assert!(needs_drop::<SecretArray<8>>());
    assert_eq!(size_of::<SecretArray<8>>(), 8 + size_of::<usize>());
    assert_eq!(secret.expose_secret().as_bytes(), b"key");
    assert_eq!(&secret.backing_for_test()[3..], &[0; 5]);
    assert_eq!(
        format!("{secret:?}"),
        "SecretArray { bytes: \"<redacted>\", len: 3, capacity: 8 }"
    );
    assert_eq!(format!("{secret}"), "<redacted secret array>");

    secret.clear();
    assert!(secret.is_empty());
    assert_eq!(secret.backing_for_test(), &[0; 8]);
}

#[test]
fn fixed_secret_declassification_returns_ordinary_value() {
    let secret = SecretArray::from_array(*b"keyxxxxx", 3).unwrap();
    let ordinary = secret.declassify();
    assert_eq!(ordinary.as_bytes(), b"key");
    let (bytes, len) = ordinary.into_parts();
    assert_eq!(len, 3);
    assert_eq!(bytes, *b"key\0\0\0\0\0");
}

#[cfg(feature = "alloc")]
#[test]
fn secret_vector_replacement_uses_the_full_allocation_wipe_boundary() {
    let mut secret = SecretVec::from_vec(std::vec![0xa5; 32]);
    let displaced = secret.replace_for_test(std::vec![b'n', b'e', b'w']);
    assert_eq!(secret.expose_secret().as_bytes(), b"new");
    assert_eq!(displaced, std::vec![0; 32]);
}