use super::{error::Error, utils::b64_char_to_index};
use num_traits::{PrimInt, ops::checked::CheckedShl, sign::Unsigned};
pub fn decode_to_int<T, N>(stream: T) -> Result<N, Error>
where
T: AsRef<str>,
N: PrimInt + Unsigned + CheckedShl + 'static,
{
let mut out: N = N::zero();
for c in stream.as_ref().chars() {
let b64_val = b64_char_to_index(c)?;
let wide_val = N::from(b64_val).ok_or(Error::IntegerOverflow)?;
if out.leading_zeros() < 6 {
return Err(Error::IntegerOverflow);
}
out = out
.checked_shl(6)
.and_then(|shifted| shifted.checked_add(&wide_val))
.ok_or(Error::IntegerOverflow)?;
}
Ok(out)
}
#[cfg(test)]
mod test {
use super::decode_to_int;
use crate::utils::error::Error;
use rstest::rstest;
#[rstest]
#[case("A", 0)]
#[case("B", 1)]
#[case("b", 27)]
#[case("Ab", 27)]
#[case("BQ", 80)]
#[case("__", 4095)]
#[case("BAA", 4096)]
#[case("Bd7", 6011)]
#[case("____", 16_777_215)]
fn base64_to_u32(#[case] b64: &str, #[case] output: u32) {
let actual: u32 = decode_to_int(b64).unwrap();
assert_eq!(actual, output);
}
#[rstest]
#[case("A", 0)]
#[case("B", 1)]
#[case("b", 27)]
#[case("Ab", 27)]
#[case("BQ", 80)]
fn base64_to_u8(#[case] b64: &str, #[case] output: u8) {
let actual: u8 = decode_to_int(b64).unwrap();
assert_eq!(actual, output);
}
#[rstest]
#[case("A", 0)]
#[case("B", 1)]
#[case("b", 27)]
#[case("Ab", 27)]
#[case("BQ", 80)]
fn base64_to_u16(#[case] b64: &str, #[case] output: u16) {
let actual: u16 = decode_to_int(b64).unwrap();
assert_eq!(actual, output);
}
#[rstest]
#[case("__", 4095)]
#[case("BAA", 4096)]
#[case("Bd7", 6011)]
#[case("____", 16_777_215)]
fn base64_to_u64(#[case] b64: &str, #[case] output: u64) {
let actual: u64 = decode_to_int(b64).unwrap();
assert_eq!(actual, output);
}
#[rstest]
#[case("__", 4095)]
#[case("BAA", 4096)]
#[case("Bd7", 6011)]
#[case("____", 16_777_215)]
fn base64_to_usize(#[case] b64: &str, #[case] output: usize) {
let actual: usize = decode_to_int(b64).unwrap();
assert_eq!(actual, output);
}
#[rstest]
#[case("__")]
#[case("BAA")]
#[case("E_")]
fn base64_to_u8_should_overflow(#[case] b64: &str) {
let actual = decode_to_int::<_, u8>(b64);
assert!(actual.is_err(), "Expected an overflow error, but got Ok");
let err = actual.unwrap_err();
assert!(
matches!(err, Error::IntegerOverflow),
"Expected Error::IntegerOverflow, but got {err:?}"
);
}
#[test]
fn decode_empty_string_is_zero() {
let result: u32 = decode_to_int("").unwrap();
assert_eq!(result, 0);
}
#[test]
fn decode_single_a_is_zero() {
let result: u32 = decode_to_int("A").unwrap();
assert_eq!(result, 0);
}
#[test]
fn decode_rejects_invalid_char_plus() {
let result = decode_to_int::<_, u32>("+");
let err = result.unwrap_err();
assert_eq!(err, Error::InvalidBase64Char('+'));
}
#[test]
fn decode_rejects_space() {
let result = decode_to_int::<_, u32>(" ");
let err = result.unwrap_err();
assert_eq!(err, Error::InvalidBase64Char(' '));
}
#[test]
fn decode_u16_overflow() {
let result = decode_to_int::<_, u16>("BAAA");
let err = result.unwrap_err();
assert_eq!(err, Error::IntegerOverflow);
}
#[test]
fn decode_max_u8() {
let result: u8 = decode_to_int("D_").unwrap();
assert_eq!(result, 255);
}
#[test]
fn decode_just_over_u8_max() {
let result = decode_to_int::<_, u8>("EA");
let err = result.unwrap_err();
assert_eq!(err, Error::IntegerOverflow);
}
#[test]
fn decode_max_u16() {
let result: u16 = decode_to_int("P__").unwrap();
assert_eq!(result, 65535);
}
#[test]
fn decode_rejects_slash() {
let result = decode_to_int::<_, u32>("/");
let err = result.unwrap_err();
assert_eq!(err, Error::InvalidBase64Char('/'));
}
#[test]
fn decode_rejects_equals() {
let result = decode_to_int::<_, u32>("=");
let err = result.unwrap_err();
assert_eq!(err, Error::InvalidBase64Char('='));
}
#[test]
fn decode_rejects_invalid_in_middle() {
let result = decode_to_int::<_, u32>("A+B");
let err = result.unwrap_err();
assert_eq!(err, Error::InvalidBase64Char('+'));
}
}