base64-ng 2.0.0

no_std-first Base64 encoding and decoding with strict APIs and a security-heavy release process
Documentation
extern crate std;

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

use super::{
    contracts::{OperationError, Status, TerminalError},
    incremental::EncoderState,
    rfc4648_oracle::{self as oracle, Profile},
    specifications::{
        CodecBuilder, DecodePadding, EncodePadding, STRICT_STANDARD_PADDED,
        STRICT_STANDARD_UNPADDED, STRICT_URL_SAFE_PADDED, STRICT_URL_SAFE_UNPADDED,
    },
};

const RFC_VECTORS: &[(&[u8], &[u8])] = &[
    (b"", b""),
    (b"f", b"Zg=="),
    (b"fo", b"Zm8="),
    (b"foo", b"Zm9v"),
    (b"foob", b"Zm9vYg=="),
    (b"fooba", b"Zm9vYmE="),
    (b"foobar", b"Zm9vYmFy"),
];

#[test]
fn rfc4648_vectors_pass_every_input_and_output_partition() {
    for &(input, padded) in RFC_VECTORS {
        for profile in profiles() {
            let expected = if is_padded(profile) {
                padded.to_vec()
            } else {
                oracle::encode(profile, input)
            };
            assert_every_partition(profile, input, &expected);
        }
    }
}

#[test]
fn bounded_inputs_match_independent_oracle_for_every_partition() {
    let mut input = [0_u8; 6];
    for len in 0..=input.len() {
        for (index, byte) in input[..len].iter_mut().enumerate() {
            *byte = u8::try_from((index * 73 + len * 41) % 256).unwrap();
        }
        for profile in profiles() {
            let expected = oracle::encode(profile, &input[..len]);
            assert_every_partition(profile, &input[..len], &expected);
        }
    }
}

#[test]
fn one_byte_chunks_and_repeated_output_full_never_reconsume_input() {
    let mut state = STRICT_STANDARD_PADDED.encoder();
    let mut no_output = [];
    let first = state.update(b"foo", &mut no_output).unwrap();
    assert_eq!(first.progress().input_consumed(), 3);
    assert_eq!(first.progress().output_produced(), 0);
    assert_output_full(first.status());
    assert_eq!(state.source_position(), 3);

    for _ in 0..3 {
        let stalled = state.update(b"", &mut no_output).unwrap();
        assert_eq!(stalled.progress().input_consumed(), 0);
        assert_eq!(stalled.progress().output_produced(), 0);
        assert_output_full(stalled.status());
        assert_eq!(state.source_position(), 3);
    }

    let mut encoded = [0_u8; 4];
    for byte in &mut encoded {
        let step = state.update(b"", core::slice::from_mut(byte)).unwrap();
        assert_eq!(step.progress().input_consumed(), 0);
        assert_eq!(step.progress().output_produced(), 1);
    }
    assert_eq!(&encoded, b"Zm9v");
    assert_eq!(
        state.update(b"", &mut no_output).unwrap().status(),
        Status::NeedInput
    );
    assert_eq!(
        state.finish(&mut no_output).unwrap().status(),
        Status::Complete
    );
    assert_eq!(
        state.finish(&mut no_output).unwrap().status(),
        Status::Complete
    );
    assert_eq!(
        state.update(b"x", &mut no_output),
        Err(OperationError::Terminal(TerminalError::InputAfterComplete))
    );
}

#[test]
fn final_tails_retry_one_byte_at_a_time_for_padded_and_unpadded_codecs() {
    for profile in profiles() {
        for input in [&b"f"[..], &b"fo"[..]] {
            let expected = oracle::encode(profile, input);
            let mut state = encoder(profile);
            let mut no_output = [];
            let update = state.update(input, &mut no_output).unwrap();
            assert_eq!(update.status(), Status::NeedInput);
            assert_eq!(update.progress().input_consumed(), input.len());

            let mut actual = Vec::new();
            loop {
                let mut byte = [0_u8; 1];
                let step = state.finish(&mut byte).unwrap();
                actual.extend_from_slice(&byte[..step.progress().output_produced()]);
                match step.status() {
                    Status::OutputFull(requirement) => {
                        assert_eq!(requirement.minimum_output().get(), 1);
                    }
                    Status::Complete => break,
                    Status::NeedInput => panic!("finish requested more input"),
                }
            }
            assert_eq!(actual, expected);
        }
    }
}

#[test]
fn finish_closes_encoder_input_before_pending_output_drains() {
    let mut state = STRICT_STANDARD_PADDED.encoder();
    state.update(b"f", &mut []).unwrap();
    let finishing = state.finish(&mut []).unwrap();
    assert_output_full(finishing.status());
    assert_eq!(
        state.update(b"x", &mut []),
        Err(OperationError::Terminal(TerminalError::InputAfterFinish))
    );
    let mut output = [0_u8; 4];
    assert_eq!(
        state.finish(&mut output).unwrap().status(),
        Status::Complete
    );
    assert_eq!(&output, b"Zg==");
}

#[test]
fn runtime_custom_alphabet_uses_the_validated_owned_table() {
    const CUSTOM: [u8; 64] = *b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    let codec = CodecBuilder::from_table(CUSTOM)
        .unwrap()
        .encode_padding(EncodePadding::Unpadded)
        .decode_padding(DecodePadding::Forbid)
        .build()
        .unwrap();
    let mut state = codec.encoder();
    let mut output = [0_u8; 16];
    let update = state.update(b"custom", &mut output).unwrap();
    let written = update.progress().output_produced();
    let finish = state.finish(&mut output[written..]).unwrap();
    let total = written + finish.progress().output_produced();

    assert_eq!(&output[..total], b"W1TxbE7r");
}

#[test]
fn reset_discards_ordinary_pending_state_without_drop_overhead() {
    assert!(!core::mem::needs_drop::<EncoderState>());
    let mut state = STRICT_STANDARD_PADDED.encoder();
    let mut no_output = [];
    state.update(b"foobar", &mut no_output).unwrap();
    state.reset();
    assert_eq!(state.source_position(), 0);

    let actual = drive(&mut state, b"f", &[1], &[1]);
    assert_eq!(actual, b"Zg==");
}

fn assert_every_partition(profile: Profile, input: &[u8], expected: &[u8]) {
    let input_masks = partition_masks(input.len());
    let output_masks = partition_masks(expected.len());
    for input_mask in input_masks {
        let input_chunks = partition_lengths(input.len(), input_mask);
        for &output_mask in &output_masks {
            let output_chunks = partition_lengths(expected.len(), output_mask);
            let mut state = encoder(profile);
            let actual = drive(&mut state, input, &input_chunks, &output_chunks);
            assert_eq!(actual, expected);
        }
    }
}

fn drive(
    state: &mut EncoderState,
    input: &[u8],
    input_chunks: &[usize],
    output_chunks: &[usize],
) -> Vec<u8> {
    let mut output = Vec::new();
    let mut input_start = 0;
    let mut output_chunk = 0;

    for &input_len in input_chunks {
        let input_end = input_start + input_len;
        let mut accepted = input_start;
        loop {
            let capacity = output_chunks.get(output_chunk).copied().unwrap_or(1);
            output_chunk += 1;
            let mut destination = [0_u8; 16];
            let step = state
                .update(&input[accepted..input_end], &mut destination[..capacity])
                .unwrap();
            accepted += step.progress().input_consumed();
            output.extend_from_slice(&destination[..step.progress().output_produced()]);
            match step.status() {
                Status::NeedInput => {
                    assert_eq!(accepted, input_end);
                    break;
                }
                Status::OutputFull(requirement) => {
                    assert_eq!(requirement.minimum_output().get(), 1);
                }
                Status::Complete => panic!("update completed before finish"),
            }
        }
        input_start = input_end;
    }

    loop {
        let capacity = output_chunks.get(output_chunk).copied().unwrap_or(1);
        output_chunk += 1;
        let mut destination = [0_u8; 16];
        let step = state.finish(&mut destination[..capacity]).unwrap();
        output.extend_from_slice(&destination[..step.progress().output_produced()]);
        match step.status() {
            Status::Complete => break,
            Status::OutputFull(requirement) => {
                assert_eq!(requirement.minimum_output().get(), 1);
            }
            Status::NeedInput => panic!("finish requested more input"),
        }
    }
    output
}

fn encoder(profile: Profile) -> EncoderState {
    EncoderState::new(match profile {
        Profile::StandardPadded => STRICT_STANDARD_PADDED.settings(),
        Profile::StandardUnpadded => STRICT_STANDARD_UNPADDED.settings(),
        Profile::UrlSafePadded => STRICT_URL_SAFE_PADDED.settings(),
        Profile::UrlSafeUnpadded => STRICT_URL_SAFE_UNPADDED.settings(),
    })
}

const fn profiles() -> [Profile; 4] {
    [
        Profile::StandardPadded,
        Profile::StandardUnpadded,
        Profile::UrlSafePadded,
        Profile::UrlSafeUnpadded,
    ]
}

const fn is_padded(profile: Profile) -> bool {
    matches!(profile, Profile::StandardPadded | Profile::UrlSafePadded)
}

fn assert_output_full(status: Status) {
    let Status::OutputFull(requirement) = status else {
        panic!("expected output-full status");
    };
    assert_eq!(requirement.minimum_output().get(), 1);
}

fn partition_masks(len: usize) -> Vec<usize> {
    if len <= 1 {
        return vec![0];
    }
    (0..(1_usize << (len - 1))).collect()
}

fn partition_lengths(len: usize, mask: usize) -> Vec<usize> {
    if len == 0 {
        return vec![0];
    }
    let mut lengths = Vec::new();
    let mut start = 0;
    for boundary in 1..len {
        if mask & (1 << (boundary - 1)) != 0 {
            lengths.push(boundary - start);
            start = boundary;
        }
    }
    lengths.push(len - start);
    lengths
}

#[test]
fn explicit_clear_resets_retained_encoder_state_for_reuse() {
    let mut state = STRICT_STANDARD_PADDED.encoder();
    let mut output = [0_u8; 4];
    let step = state.update(b"se", &mut output).unwrap();
    assert_eq!(step.progress().input_consumed(), 2);
    assert_eq!(state.buffered_input_len(), 2);

    state.clear();

    assert_eq!(state, STRICT_STANDARD_PADDED.encoder());
    assert_eq!(state.source_position(), 0);
}