musefs_format/ogg/b64.rs
1//! Incremental base64 serving for embedded art: given a requested window of the
2//! *output* base64 of an image, compute the bounded raw-input range to read and
3//! how to trim the re-encoded result. base64 encodes each 3 input bytes into 4
4//! output chars independently, so any output window `[o, o+len)` depends only on
5//! input bytes `[⌊o/4⌋·3 .. ⌈(o+len)/4⌉·3)` (clipped to the image length, whose
6//! final partial group yields the canonical `=` padding).
7
8use base64::Engine;
9
10/// The raw-input read plan for an output base64 window.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct B64Window {
13 /// First raw input byte to read.
14 pub in_start: u64,
15 /// Number of raw input bytes to read (clipped to the image length).
16 pub in_len: u64,
17 /// Leading base64 chars to drop after encoding the read bytes.
18 pub skip: usize,
19}
20
21/// Compute the input read plan to serve output base64 chars `[out_offset,
22/// out_offset+take)` of `base64(image)`, where the image is `img_total` bytes.
23pub fn b64_window(out_offset: u64, take: u64, img_total: u64) -> B64Window {
24 debug_assert!(take > 0);
25 let g0 = out_offset / 4;
26 let g1 = (out_offset + take - 1) / 4;
27 let in_start = g0 * 3;
28 let in_end = ((g1 + 1) * 3).min(img_total);
29 B64Window {
30 in_start,
31 in_len: in_end.saturating_sub(in_start),
32 skip: crate::convert::usize_from(out_offset - g0 * 4),
33 }
34}
35
36/// Encode `raw` (the bytes named by a `B64Window`) and return exactly `take`
37/// output chars starting at `skip`.
38pub fn encode_b64_slice(raw: &[u8], skip: usize, take: usize) -> Vec<u8> {
39 let enc = base64::engine::general_purpose::STANDARD.encode(raw);
40 enc.as_bytes()[skip..skip + take].to_vec()
41}
42
43/// Total base64 output length for an image of `img_total` bytes, or `None` if it
44/// overflows `u64`. Only an adversarial `img_total` can overflow; every real
45/// image is far below this.
46pub fn b64_len_checked(img_total: u64) -> Option<u64> {
47 img_total.div_ceil(3).checked_mul(4)
48}
49
50/// Total base64 output length for an image of `img_total` bytes.
51pub fn b64_len(img_total: u64) -> u64 {
52 b64_len_checked(img_total).expect("base64 output length fits u64")
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58 use base64::Engine;
59
60 fn full_b64(img: &[u8]) -> Vec<u8> {
61 base64::engine::general_purpose::STANDARD
62 .encode(img)
63 .into_bytes()
64 }
65
66 #[test]
67 fn any_window_matches_substring_of_full_encode() {
68 // Cover image lengths that hit every length-mod-3 case and various windows.
69 for &img_total in &[0u64, 1, 2, 3, 4, 5, 6, 7, 100, 257, 1024] {
70 let img: Vec<u8> = (0..img_total)
71 .map(|i| u8::try_from((i * 7 + 3) % 256).unwrap())
72 .collect();
73 let full = full_b64(&img);
74 assert_eq!(crate::convert::usize_from(b64_len(img_total)), full.len());
75 if full.is_empty() {
76 continue;
77 }
78 for o in 0..full.len() as u64 {
79 for take in 1..=(full.len() as u64 - o) {
80 let w = b64_window(o, take, img_total);
81 let raw = &img[crate::convert::usize_from(w.in_start)
82 ..crate::convert::usize_from(w.in_start + w.in_len)];
83 let got = encode_b64_slice(raw, w.skip, crate::convert::usize_from(take));
84 assert_eq!(
85 got,
86 &full[crate::convert::usize_from(o)..crate::convert::usize_from(o + take)],
87 "img_total={img_total} o={o} take={take}"
88 );
89 }
90 }
91 }
92 }
93
94 #[test]
95 fn b64_window_fields_are_exact_at_group_boundaries() {
96 // out_offset and take chosen so the -1 and /4 in g1 are observable.
97 // g0 = out_offset/4, g1 = (out_offset+take-1)/4,
98 // in_start = g0*3, in_end = min((g1+1)*3, img_total), skip = out_offset - g0*4.
99 let img_total = 1024u64;
100
101 // take=1 at offset 0: g1 = 0 (with -1). The +1 mutant gives g1=0 too here,
102 // so choose offset 3 take=1: g0=0,g1=0 vs +1 mutant g1=1 -> in_len differs.
103 let w = b64_window(3, 1, img_total);
104 assert_eq!(
105 w,
106 B64Window {
107 in_start: 0,
108 in_len: 3,
109 skip: 3
110 }
111 );
112
113 // take exactly fills group 0 (offset 0, take 4): g1=0; mutant take+1 -> g1=1.
114 let w = b64_window(0, 4, img_total);
115 assert_eq!(
116 w,
117 B64Window {
118 in_start: 0,
119 in_len: 3,
120 skip: 0
121 }
122 );
123
124 // offset 4 take 4 -> g0=1,g1=1 -> in_start=3,in_len=3,skip=0; /4->*4 mutant
125 // makes g1 huge -> in_len clamps to img_total-3 (differs).
126 let w = b64_window(4, 4, img_total);
127 assert_eq!(
128 w,
129 B64Window {
130 in_start: 3,
131 in_len: 3,
132 skip: 0
133 }
134 );
135
136 // Window spanning two groups: offset 2 take 6 -> g0=0,g1=1 -> in 0..6.
137 let w = b64_window(2, 6, img_total);
138 assert_eq!(
139 w,
140 B64Window {
141 in_start: 0,
142 in_len: 6,
143 skip: 2
144 }
145 );
146 }
147
148 #[test]
149 fn b64_window_is_overflow_free_at_the_max_validated_boundary() {
150 // For any layout that passes RegionLayout::validate, an OggArtSlice
151 // satisfies offset + len <= b64_len(art_total) AND b64_len_checked(art_total)
152 // is Some. Under those bounds b64_window's internal +/* cannot overflow.
153 // Pin the worst case: the largest art_total whose b64_len still fits u64,
154 // reading the final 4 output chars. In debug, any intermediate overflow
155 // would panic here.
156 let art_total = u64::MAX / 4 * 3; // b64_len_checked(art_total) is Some
157 assert!(b64_len_checked(art_total).is_some());
158 let total = b64_len(art_total);
159 let w = b64_window(total - 4, 4, art_total);
160 assert!(w.in_start <= art_total);
161 assert!(w.in_len <= art_total);
162 }
163}