crate::ix!();
#[inline]
fn decode_base32_val(b: u8) -> Option<u8> {
match b {
b'a'..=b'z' => Some(b - b'a'),
b'A'..=b'Z' => Some(b - b'A'),
b'2'..=b'7' => Some(b - b'2' + 26),
_ => None,
}
}
pub fn decode_base32_bytes(
p: *const u8,
pf_invalid: Option<*mut bool>,
) -> Vec<u8> {
trace!("decode_base32_bytes: start");
let bytes = unsafe {
if p.is_null() {
if let Some(ptr) = pf_invalid {
*ptr = true;
}
return Vec::new();
}
CStr::from_ptr(p as *const std::os::raw::c_char).to_bytes()
};
let mut symbols = Vec::<u8>::with_capacity(bytes.len());
let mut idx = 0;
for &b in bytes {
match decode_base32_val(b) {
Some(v) => {
symbols.push(v);
idx += 1;
}
None => break,
}
}
let mut ret = Vec::<u8>::with_capacity((symbols.len() * 5) / 8);
let mut valid =
convert_bits::<5, 8, false, _, _>(symbols.into_iter(), |c| ret.push(c));
let pad_slice = &bytes[idx..];
for &b in pad_slice {
if b != b'=' {
valid = false;
break;
}
}
valid &= bytes.len() % 8 == 0 && pad_slice.len() < 8;
if let Some(ptr) = pf_invalid {
unsafe { *ptr = !valid };
}
debug!("decode_base32_bytes: valid={valid}");
ret
}
pub fn decode_base32_bytes_strict(
s: &str,
pf_invalid: Option<*mut bool>,
) -> Vec<u8> {
trace!("decode_base32_bytes_strict: start");
let embedded_nul = s.as_bytes().contains(&0);
let mut buf: Vec<u8> = Vec::with_capacity(s.len() + 1);
buf.extend_from_slice(s.as_bytes());
buf.push(0);
let mut low_invalid = false;
let raw = decode_base32_bytes(buf.as_ptr(), Some(&mut low_invalid as *mut bool));
let invalid = embedded_nul || low_invalid;
if let Some(ptr) = pf_invalid {
unsafe { *ptr = invalid };
}
debug!(len = raw.len(), invalid, "decode_base32_bytes_strict: done");
raw
}
pub fn decode_base32_bytes_nopad_lower(
s: &str,
pf_invalid: Option<*mut bool>,
) -> Vec<u8> {
trace!(len = s.len(), "decode_base32_bytes_nopad_lower: start");
let mut acc: u32 = 0;
let mut bits: u32 = 0;
let mut out: Vec<u8> = Vec::with_capacity((s.len() * 5 + 7) / 8);
let mut invalid = false;
for (i, ch) in s.chars().enumerate() {
let c = ch.to_ascii_lowercase() as u8;
let val = match c {
b'a'..=b'z' => (c - b'a') as u32,
b'2'..=b'7' => (c - b'2') as u32 + 26,
_ => {
invalid = true;
warn!(index = i, ch = c as u32, "Invalid base32 character (nopad-lower)");
break;
}
};
acc = (acc << 5) | val;
bits += 5;
while bits >= 8 {
let byte = ((acc >> (bits - 8)) & 0xFF) as u8;
out.push(byte);
bits -= 8;
}
}
if !invalid && bits > 0 {
let mask = (1u32 << bits) - 1;
if (acc & mask) != 0 {
invalid = true;
warn!(remaining_bits = bits, "Non‑zero leftover bits in base32 tail (nopad-lower)");
}
}
if let Some(ptr) = pf_invalid {
unsafe { *ptr = invalid };
}
if invalid {
out.clear();
}
debug!(decoded_len = out.len(), invalid, "decode_base32_bytes_nopad_lower: done");
out
}
pub fn decode_base32(
s: &str,
pf_invalid: Option<*mut bool>,
) -> String {
trace!("decode_base32: start");
let embedded_nul = s.as_bytes().contains(&0);
let mut buf: Vec<u8> = Vec::with_capacity(s.len() + 1);
buf.extend_from_slice(s.as_bytes());
buf.push(0);
let mut local_invalid = false;
let raw = decode_base32_bytes(
buf.as_ptr(),
Some(&mut local_invalid as *mut bool),
);
let invalid = embedded_nul || local_invalid;
if let Some(ptr) = pf_invalid {
unsafe { *ptr = invalid };
}
String::from_utf8(raw).unwrap_or_default()
}
#[cfg(test)]
mod tests_base32_64_decoding {
use super::*;
#[traced_test]
fn base32_roundtrip_padding_edges() {
for len in 0u8..=7 {
let data: Vec<u8> = (0..len).collect();
let encoded = crate::encode::encode_base32_bytes(&data, Some(true));
let decoded = decode_base32(&encoded, None);
assert_eq!(decoded.as_bytes(), data.as_slice(), "len = {len}");
}
info!("Base32 round‑trip over padding edges verified");
}
#[traced_test]
fn base64_roundtrip_padding_edges() {
for len in 0u8..=2 {
let data: Vec<u8> = (0..len).collect();
let encoded = crate::encode::encode_base64_bytes(&data);
let decoded = decode_base64(&encoded, None);
assert_eq!(decoded.as_bytes(), data.as_slice(), "len = {len}");
}
info!("Base64 round‑trip over padding edges verified");
}
#[traced_test]
fn invalid_base32_sets_flag() {
let mut invalid = false;
let _ = decode_base32("#$%^", Some(&mut invalid as *mut bool));
assert!(invalid, "invalid flag must be set");
}
#[traced_test]
fn invalid_base64_sets_flag() {
let mut invalid = false;
let _ = decode_base64("A===", Some(&mut invalid));
assert!(invalid, "invalid flag must be set");
}
#[traced_test]
fn nopad_lower_decoder_matches_encode_helper() {
for len in [0usize, 1, 2, 5, 10, 31, 32, 35].iter().copied() {
let data: Vec<u8> = (0..len as u8).map(|v| v ^ 0xA5).collect();
let encoded = crate::encode::encode_base32_bytes(&data, Some(false));
let mut invalid = false;
let decoded = decode_base32_bytes_nopad_lower(&encoded, Some(&mut invalid as *mut bool));
assert!(!invalid, "nopad-lower decode should be valid for len={len}");
assert_eq!(decoded, data);
}
}
}