use base64::Engine;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct B64Window {
pub in_start: u64,
pub in_len: u64,
pub skip: usize,
}
pub fn b64_window(out_offset: u64, take: u64, img_total: u64) -> B64Window {
debug_assert!(take > 0);
let g0 = out_offset / 4;
let g1 = (out_offset + take - 1) / 4;
let in_start = g0 * 3;
let in_end = ((g1 + 1) * 3).min(img_total);
B64Window {
in_start,
in_len: in_end.saturating_sub(in_start),
skip: crate::convert::usize_from(out_offset - g0 * 4),
}
}
pub fn encode_b64_slice(raw: &[u8], skip: usize, take: usize) -> Option<Vec<u8>> {
let enc = base64::engine::general_purpose::STANDARD.encode(raw);
let end = skip.checked_add(take)?;
enc.as_bytes().get(skip..end).map(<[u8]>::to_vec)
}
pub fn b64_len_checked(img_total: u64) -> Option<u64> {
img_total.div_ceil(3).checked_mul(4)
}
pub fn b64_len(img_total: u64) -> u64 {
b64_len_checked(img_total).expect("base64 output length fits u64")
}
#[cfg(test)]
mod tests {
use super::*;
use base64::Engine;
fn full_b64(img: &[u8]) -> Vec<u8> {
base64::engine::general_purpose::STANDARD
.encode(img)
.into_bytes()
}
#[test]
fn any_window_matches_substring_of_full_encode() {
for &img_total in &[0u64, 1, 2, 3, 4, 5, 6, 7, 100, 257, 1024] {
let img: Vec<u8> = (0..img_total)
.map(|i| u8::try_from((i * 7 + 3) % 256).unwrap())
.collect();
let full = full_b64(&img);
assert_eq!(crate::convert::usize_from(b64_len(img_total)), full.len());
if full.is_empty() {
continue;
}
for o in 0..full.len() as u64 {
for take in 1..=(full.len() as u64 - o) {
let w = b64_window(o, take, img_total);
let raw = &img[crate::convert::usize_from(w.in_start)
..crate::convert::usize_from(w.in_start + w.in_len)];
let got = encode_b64_slice(raw, w.skip, crate::convert::usize_from(take))
.expect("window lies within the encoded output");
assert_eq!(
got,
&full[crate::convert::usize_from(o)..crate::convert::usize_from(o + take)],
"img_total={img_total} o={o} take={take}"
);
}
}
}
}
#[test]
fn encode_b64_slice_returns_none_when_window_exceeds_output() {
assert_eq!(encode_b64_slice(b"abc", 0, 4), Some(b"YWJj".to_vec()));
assert_eq!(encode_b64_slice(b"abc", 2, 4), None);
assert_eq!(encode_b64_slice(b"abc", 5, 1), None);
}
#[test]
fn b64_window_fields_are_exact_at_group_boundaries() {
let img_total = 1024u64;
let w = b64_window(3, 1, img_total);
assert_eq!(
w,
B64Window {
in_start: 0,
in_len: 3,
skip: 3
}
);
let w = b64_window(0, 4, img_total);
assert_eq!(
w,
B64Window {
in_start: 0,
in_len: 3,
skip: 0
}
);
let w = b64_window(4, 4, img_total);
assert_eq!(
w,
B64Window {
in_start: 3,
in_len: 3,
skip: 0
}
);
let w = b64_window(2, 6, img_total);
assert_eq!(
w,
B64Window {
in_start: 0,
in_len: 6,
skip: 2
}
);
}
#[test]
fn b64_window_is_overflow_free_at_the_max_validated_boundary() {
let art_total = u64::MAX / 4 * 3; assert!(b64_len_checked(art_total).is_some());
let total = b64_len(art_total);
let w = b64_window(total - 4, 4, art_total);
assert!(w.in_start <= art_total);
assert!(w.in_len <= art_total);
}
}