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"] {
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"] {
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()] {
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;
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"@@");
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];
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'@';
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:?}"),
}
}
}
}
}