qubit-codec-misc 0.8.3

Miscellaneous byte and text format codecs for Rust applications
Documentation
// =============================================================================
//    Copyright (c) 2026 Haixing Hu.
//
//    SPDX-License-Identifier: Apache-2.0
//
//    Licensed under the Apache License, Version 2.0.
// =============================================================================
//! Tests for complete Base64 quantum encoding and decoding.

use base64::DecodeError;
use qubit_codec::Codec;
use qubit_codec_misc::Base64ErrorKind;
use qubit_codec_misc::Base64QuantumCodec;
use qubit_codec_misc::MiscCodecError;

#[test]
fn test_base64_quantum_standard_roundtrip() {
    let mut codec = Base64QuantumCodec::standard();
    let mut output = [0u8; 4];

    let (decoded, consumed) = unsafe { Codec::decode(&mut codec, b"YWJj", 0).expect("base64 quantum should decode") };
    let written = unsafe { Codec::encode(&mut codec, b"abc", &mut output, 0).expect("base64 quantum should encode") };

    assert_eq!(*b"abc", decoded);
    assert_eq!(4, consumed.get());
    assert_eq!(4, written);
    assert_eq!(b"YWJj", &output);
}

#[test]
fn test_base64_quantum_url_safe_roundtrip() {
    let mut codec = Base64QuantumCodec::url_safe();
    let mut output = [0u8; 4];

    let (decoded, consumed) = unsafe { Codec::decode(&mut codec, b"-__u", 0).expect("URL-safe quantum should decode") };
    let written = unsafe {
        Codec::encode(&mut codec, &[0xfb, 0xff, 0xee], &mut output, 0).expect("URL-safe quantum should encode")
    };

    assert_eq!([0xfb, 0xff, 0xee], decoded);
    assert_eq!(4, consumed.get());
    assert_eq!(4, written);
    assert_eq!(b"-__u", &output);
}

#[test]
fn test_base64_quantum_alphabet_and_errors() {
    let mut standard = Base64QuantumCodec::default();
    let mut url_safe = Base64QuantumCodec::url_safe();

    let (decoded, _) = unsafe { Codec::decode(&mut standard, b"++//", 0) }.expect("standard symbols should decode");
    assert_eq!([0xfb, 0xef, 0xff], decoded);

    let (decoded, _) = unsafe { Codec::decode(&mut standard, b"0123", 0) }.expect("digit symbols should decode");
    assert_eq!([0xd3, 0x5d, 0xb7], decoded);
    let invalid = unsafe { Codec::decode(&mut url_safe, b"@@@@", 0) }.expect_err("invalid Base64 quantum should fail");
    assert_eq!(Some(4), super::invalid_consumed(invalid));
    let invalid = unsafe { Codec::decode(&mut url_safe, b"@@@@", 0) }.expect_err("invalid Base64 quantum should fail");
    assert!(matches!(
        super::invalid_source(invalid),
        MiscCodecError::InvalidBase64 {
            kind: Base64ErrorKind::InvalidByte,
            input_index: Some(0),
            symbol: Some(b'@'),
            input_length: None,
            source: DecodeError::InvalidByte(0, b'@'),
        }
    ));
}

#[test]
fn test_base64_quantum_reports_invalid_units_at_each_position() {
    let invalid_inputs = [b"A@AA".as_slice(), b"AA@A", b"AAA@"];

    for input in invalid_inputs {
        let invalid = unsafe { Codec::decode(&mut Base64QuantumCodec::standard(), input, 0) }
            .expect_err("invalid Base64 unit should fail");
        assert_eq!(Some(4), super::invalid_consumed(invalid));

        let invalid = unsafe { Codec::decode(&mut Base64QuantumCodec::standard(), input, 0) }
            .expect_err("invalid Base64 unit should fail");
        assert!(matches!(
            super::invalid_source(invalid),
            MiscCodecError::InvalidBase64 {
                kind: Base64ErrorKind::InvalidByte,
                symbol: Some(b'@'),
                input_length: None,
                source: DecodeError::InvalidByte(_, b'@'),
                ..
            }
        ));
    }
}

#[test]
fn test_base64_quantum_rejects_foreign_alphabet_and_padding() {
    for input in [b"-AAA".as_slice(), b"_AAA"] {
        // SAFETY: each input contains the four units required by this codec.
        let failure = unsafe { Codec::decode(&mut Base64QuantumCodec::standard(), input, 0) }
            .expect_err("standard alphabet should reject URL-safe symbols");
        assert_eq!(Some(4), failure.consumed_units().map(|width| width.get()));
        assert!(matches!(
            failure.invalid_source(),
            Some(MiscCodecError::InvalidBase64 {
                kind: Base64ErrorKind::InvalidByte,
                input_index: Some(0),
                symbol: Some(_),
                input_length: None,
                source: DecodeError::InvalidByte(0, _),
            })
        ));
    }
    for input in [b"+AAA".as_slice(), b"/AAA"] {
        // SAFETY: each input contains the four units required by this codec.
        let failure = unsafe { Codec::decode(&mut Base64QuantumCodec::url_safe(), input, 0) }
            .expect_err("URL-safe alphabet should reject standard symbols");
        assert_eq!(Some(4), failure.consumed_units().map(|width| width.get()));
        assert!(matches!(
            failure.invalid_source(),
            Some(MiscCodecError::InvalidBase64 {
                kind: Base64ErrorKind::InvalidByte,
                input_index: Some(0),
                symbol: Some(_),
                input_length: None,
                source: DecodeError::InvalidByte(0, _),
            })
        ));
    }
    for input in [b"AA==".as_slice(), b"AAA="] {
        for mut codec in [Base64QuantumCodec::standard(), Base64QuantumCodec::url_safe()] {
            // SAFETY: each input contains the four units required by this codec.
            let failure =
                unsafe { Codec::decode(&mut codec, input, 0) }.expect_err("quantum codec should reject padding");
            assert_eq!(Some(4), failure.consumed_units().map(|width| width.get()));
            assert!(matches!(
                failure.invalid_source(),
                Some(MiscCodecError::InvalidBase64 {
                    kind: Base64ErrorKind::InvalidByte,
                    symbol: Some(b'='),
                    input_length: None,
                    source: DecodeError::InvalidByte(_, b'='),
                    ..
                })
            ));
        }
    }
}

#[test]
fn test_base64_quantum_structured_invalid_symbols_and_source() {
    let cases = [
        (Base64QuantumCodec::standard(), b'-'),
        (Base64QuantumCodec::standard(), b'_'),
        (Base64QuantumCodec::url_safe(), b'+'),
        (Base64QuantumCodec::url_safe(), b'/'),
        (Base64QuantumCodec::standard(), b'='),
        (Base64QuantumCodec::url_safe(), b'='),
        (Base64QuantumCodec::standard(), b' '),
        (Base64QuantumCodec::url_safe(), b' '),
        (Base64QuantumCodec::standard(), 0xff),
        (Base64QuantumCodec::url_safe(), 0xff),
    ];
    for (codec, invalid) in cases {
        for position in 0..4 {
            let mut codec = codec;
            let mut input = *b"__AAAA";
            let index = 2 + position;
            input[index] = invalid;
            // SAFETY: four readable units start at input index two.
            let failure = unsafe { Codec::decode(&mut codec, &input, 2) }.unwrap_err();
            assert_eq!(failure.consumed_units().map(|n| n.get()), Some(4));
            let error = failure.invalid_source().expect("domain error");
            match error {
                MiscCodecError::InvalidBase64 {
                    kind,
                    input_index,
                    symbol,
                    input_length,
                    source,
                } => {
                    assert_eq!(*kind, Base64ErrorKind::InvalidByte);
                    assert_eq!(*input_index, Some(index));
                    assert_eq!(*symbol, Some(invalid));
                    assert_eq!(*input_length, None);
                    assert_eq!(*source, DecodeError::InvalidByte(index, invalid));
                }
                other => panic!("unexpected error: {other:?}"),
            }
            assert_eq!(
                std::error::Error::source(error).and_then(|source| source.downcast_ref::<DecodeError>()),
                Some(&DecodeError::InvalidByte(index, invalid)),
            );
        }
    }
}

#[test]
fn test_base64_quantum_respects_nonzero_offsets() {
    let cases = [
        (Base64QuantumCodec::standard(), *b"abc", *b"YWJj"),
        (Base64QuantumCodec::url_safe(), [0xfb, 0xff, 0xee], *b"-__u"),
    ];
    for (mut codec, value, units) in cases {
        for offset in [1, 2] {
            let mut input = vec![b'@'; offset];
            input.extend_from_slice(&units);
            input.extend_from_slice(b"@@");
            // SAFETY: four readable units start at offset.
            let (decoded, consumed) =
                unsafe { Codec::decode(&mut codec, &input, offset) }.expect("quantum at nonzero offset must decode");
            assert_eq!(value, decoded);
            assert_eq!(4, consumed.get());

            let mut output = [0xa5; 8];
            // SAFETY: eight slots include four writable units at offset 1 or 2.
            let written = unsafe { Codec::encode(&mut codec, &value, &mut output, offset) }
                .expect("quantum at nonzero offset must encode");
            assert_eq!(4, written);
            assert_eq!(units.as_slice(), &output[offset..offset + 4]);
            assert!(output[..offset].iter().all(|byte| *byte == 0xa5));
            assert!(output[offset + 4..].iter().all(|byte| *byte == 0xa5));
        }
    }
}

#[test]
fn test_base64_quantum_reports_absolute_invalid_indices() {
    for mut codec in [Base64QuantumCodec::standard(), Base64QuantumCodec::url_safe()] {
        for offset in [1, 2] {
            for position in 0..4 {
                let mut input = vec![b'@'; offset];
                input.extend_from_slice(b"AAAA");
                input.extend_from_slice(b"@@");
                input[offset + position] = b'@';
                // SAFETY: a complete four-unit quantum is readable at offset.
                let failure = unsafe { Codec::decode(&mut codec, &input, offset) }
                    .expect_err("invalid quantum unit must be rejected");
                assert_eq!(Some(4), failure.consumed_units().map(|width| width.get()));
                match failure.invalid_source() {
                    Some(MiscCodecError::InvalidBase64 {
                        kind,
                        input_index,
                        symbol,
                        input_length,
                        source,
                    }) => {
                        assert_eq!(*kind, Base64ErrorKind::InvalidByte);
                        assert_eq!(*input_index, Some(offset + position));
                        assert_eq!(*symbol, Some(b'@'));
                        assert_eq!(*input_length, None);
                        assert_eq!(*source, DecodeError::InvalidByte(offset + position, b'@'));
                    }
                    other => panic!("unexpected failure: {other:?}"),
                }
            }
        }
    }
}