1use core::num::NonZeroUsize;
4
5use super::ForgivingBase64;
6use crate::v2::{BackendFault, OutputFull, Progress, Status, Step};
7
8const INPUT_QUANTUM: usize = 4;
9const OUTPUT_QUANTUM: usize = 3;
10
11#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
13#[non_exhaustive]
14pub enum ForgivingError {
15 InvalidInput,
17 PositionOverflow,
19 Backend(BackendFault),
21 InputAfterFinish,
23 InputAfterComplete,
25 OutputTooSmall {
27 required: usize,
29 available: usize,
31 },
32 AllocationLimitExceeded {
34 required: usize,
36 limit: usize,
38 },
39 AllocationFailed {
41 requested: usize,
43 },
44}
45
46impl core::fmt::Display for ForgivingError {
47 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
48 match self {
49 Self::InvalidInput => formatter.write_str("invalid forgiving Base64 input"),
50 Self::PositionOverflow => {
51 formatter.write_str("forgiving Base64 source position overflows usize")
52 }
53 Self::Backend(fault) => write!(
54 formatter,
55 "forgiving Base64 internal backend failure: {}",
56 fault.as_str()
57 ),
58 Self::InputAfterFinish => formatter.write_str("input supplied after finish started"),
59 Self::InputAfterComplete => formatter.write_str("input supplied after completion"),
60 Self::OutputTooSmall {
61 required,
62 available,
63 } => write!(
64 formatter,
65 "forgiving Base64 output too small: required {required}, available {available}"
66 ),
67 Self::AllocationLimitExceeded { required, limit } => write!(
68 formatter,
69 "forgiving Base64 output length {required} exceeds allocation limit {limit}"
70 ),
71 Self::AllocationFailed { requested } => write!(
72 formatter,
73 "failed to reserve {requested} forgiving Base64 output bytes"
74 ),
75 }
76 }
77}
78
79#[cfg(feature = "std")]
80impl std::error::Error for ForgivingError {}
81
82#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
83enum Phase {
84 Active,
85 Finishing,
86 Complete,
87 Failed(ForgivingError),
88}
89
90#[derive(Clone, Debug, Eq, PartialEq)]
96pub struct ForgivingDecoder {
97 quantum: [u8; INPUT_QUANTUM],
98 quantum_len: usize,
99 pending: [u8; OUTPUT_QUANTUM],
100 pending_start: usize,
101 pending_len: usize,
102 terminal_padding: bool,
103 source_position: usize,
104 phase: Phase,
105}
106
107impl ForgivingBase64 {
108 #[must_use]
110 pub const fn decoder(self) -> ForgivingDecoder {
111 ForgivingDecoder::new()
112 }
113}
114
115impl ForgivingDecoder {
116 const fn new() -> Self {
117 Self {
118 quantum: [0; INPUT_QUANTUM],
119 quantum_len: 0,
120 pending: [0; OUTPUT_QUANTUM],
121 pending_start: 0,
122 pending_len: 0,
123 terminal_padding: false,
124 source_position: 0,
125 phase: Phase::Active,
126 }
127 }
128
129 pub fn update(&mut self, input: &str, output: &mut [u8]) -> Result<Step, ForgivingError> {
134 self.require_active()?;
135 self.source_position
136 .checked_add(input.len())
137 .ok_or_else(|| self.fail(ForgivingError::PositionOverflow))?;
138 let consumed = match self.plan_update(input, output.len()) {
139 Ok(consumed) => consumed,
140 Err(error) => return Err(self.fail(error)),
141 };
142
143 let mut produced = self.drain_pending(output);
144 if self.pending_len != 0 {
145 return Ok(output_full(0, produced));
146 }
147
148 for &byte in &input.as_bytes()[..consumed] {
149 if is_ascii_whitespace(byte) {
150 continue;
151 }
152
153 self.quantum[self.quantum_len] = byte;
154 self.quantum_len += 1;
155 if self.quantum_len == INPUT_QUANTUM {
156 let Some((bytes, len, terminal)) = decode_quantum(self.quantum) else {
157 return Err(self.fail(ForgivingError::InvalidInput));
158 };
159 self.quantum_len = 0;
160 self.pending = bytes;
161 self.pending_start = 0;
162 self.pending_len = len;
163 self.terminal_padding = terminal;
164 produced += self.drain_pending(&mut output[produced..]);
165 if self.pending_len != 0 {
166 break;
167 }
168 }
169 }
170 self.source_position += consumed;
171
172 if self.pending_len != 0 || consumed != input.len() {
173 Ok(output_full(consumed, produced))
174 } else {
175 Ok(Step::new(
176 Progress::new(consumed, produced),
177 Status::NeedInput,
178 ))
179 }
180 }
181
182 fn plan_update(&self, input: &str, output_len: usize) -> Result<usize, ForgivingError> {
183 let pending_written = self.pending_len.min(output_len);
184 if self.pending_len != pending_written {
185 return Ok(0);
186 }
187
188 let mut available_output = output_len - pending_written;
189 let mut quantum = self.quantum;
190 let mut quantum_len = self.quantum_len;
191 let mut terminal_padding = self.terminal_padding;
192 let mut consumed = 0;
193
194 for &byte in input.as_bytes() {
195 if is_ascii_whitespace(byte) {
196 consumed += 1;
197 continue;
198 }
199 if terminal_padding
200 || (!is_standard_symbol(byte) && byte != b'=')
201 || (byte == b'=' && quantum_len < 2)
202 || (quantum_len == 3 && quantum[2] == b'=' && byte != b'=')
203 {
204 return Err(ForgivingError::InvalidInput);
205 }
206
207 quantum[quantum_len] = byte;
208 quantum_len += 1;
209 consumed += 1;
210 if quantum_len == INPUT_QUANTUM {
211 let Some((_, decoded_len, terminal)) = decode_quantum(quantum) else {
212 return Err(ForgivingError::InvalidInput);
213 };
214 quantum_len = 0;
215 terminal_padding = terminal;
216 let written = decoded_len.min(available_output);
217 available_output -= written;
218 if written != decoded_len {
219 break;
220 }
221 }
222 }
223 Ok(consumed)
224 }
225
226 pub fn finish(&mut self, output: &mut [u8]) -> Result<Step, ForgivingError> {
228 match self.phase {
229 Phase::Active => self.phase = Phase::Finishing,
230 Phase::Finishing => {}
231 Phase::Complete => return Ok(Step::new(Progress::ZERO, Status::Complete)),
232 Phase::Failed(error) => return Err(error),
233 }
234
235 let mut produced = self.drain_pending(output);
236 if self.pending_len != 0 {
237 return Ok(output_full(0, produced));
238 }
239 if self.quantum_len != 0 {
240 let Some((bytes, len)) = decode_tail(&self.quantum[..self.quantum_len]) else {
241 return Err(self.fail(ForgivingError::InvalidInput));
242 };
243 self.quantum_len = 0;
244 self.pending = bytes;
245 self.pending_start = 0;
246 self.pending_len = len;
247 produced += self.drain_pending(&mut output[produced..]);
248 if self.pending_len != 0 {
249 return Ok(output_full(0, produced));
250 }
251 }
252 self.phase = Phase::Complete;
253 Ok(Step::new(Progress::new(0, produced), Status::Complete))
254 }
255
256 pub fn reset(&mut self) {
258 *self = Self::new();
259 }
260
261 #[must_use]
263 pub const fn source_position(&self) -> usize {
264 self.source_position
265 }
266
267 fn require_active(&self) -> Result<(), ForgivingError> {
268 match self.phase {
269 Phase::Active => Ok(()),
270 Phase::Finishing => Err(ForgivingError::InputAfterFinish),
271 Phase::Complete => Err(ForgivingError::InputAfterComplete),
272 Phase::Failed(error) => Err(error),
273 }
274 }
275
276 fn fail(&mut self, error: ForgivingError) -> ForgivingError {
277 if let Phase::Failed(existing) = self.phase {
278 existing
279 } else {
280 self.phase = Phase::Failed(error);
281 error
282 }
283 }
284
285 fn drain_pending(&mut self, output: &mut [u8]) -> usize {
286 let written = self.pending_len.min(output.len());
287 let end = self.pending_start + written;
288 output[..written].copy_from_slice(&self.pending[self.pending_start..end]);
289 self.pending_start = end;
290 self.pending_len -= written;
291 if self.pending_len == 0 {
292 self.pending_start = 0;
293 }
294 written
295 }
296}
297
298fn output_full(consumed: usize, produced: usize) -> Step {
299 Step::new(
300 Progress::new(consumed, produced),
301 Status::OutputFull(OutputFull::new(NonZeroUsize::MIN)),
302 )
303}
304
305pub(super) const fn is_ascii_whitespace(byte: u8) -> bool {
306 matches!(byte, b'\t' | b'\n' | 0x0c | b'\r' | b' ')
307}
308
309const fn is_standard_symbol(byte: u8) -> bool {
310 byte.is_ascii_alphanumeric() || matches!(byte, b'+' | b'/')
311}
312
313const fn decode_value(byte: u8) -> Option<u8> {
314 match byte {
315 b'A'..=b'Z' => Some(byte - b'A'),
316 b'a'..=b'z' => Some(byte - b'a' + 26),
317 b'0'..=b'9' => Some(byte - b'0' + 52),
318 b'+' => Some(62),
319 b'/' => Some(63),
320 _ => None,
321 }
322}
323
324fn decode_quantum(input: [u8; 4]) -> Option<([u8; 3], usize, bool)> {
325 let first = decode_value(input[0])?;
326 let second = decode_value(input[1])?;
327 let one = (first << 2) | (second >> 4);
328 match (input[2], input[3]) {
329 (b'=', b'=') => Some(([one, 0, 0], 1, true)),
330 (b'=', _) => None,
331 (third, b'=') => {
332 let third = decode_value(third)?;
333 Some(([one, (second << 4) | (third >> 2), 0], 2, true))
334 }
335 (third, fourth) => {
336 let third = decode_value(third)?;
337 let fourth = decode_value(fourth)?;
338 Some((
339 [one, (second << 4) | (third >> 2), (third << 6) | fourth],
340 3,
341 false,
342 ))
343 }
344 }
345}
346
347fn decode_tail(input: &[u8]) -> Option<([u8; 3], usize)> {
348 match input {
349 [] => Some(([0; 3], 0)),
350 [first, second] => {
351 let first = decode_value(*first)?;
352 let second = decode_value(*second)?;
353 Some(([(first << 2) | (second >> 4), 0, 0], 1))
354 }
355 [first, second, third] => {
356 let first = decode_value(*first)?;
357 let second = decode_value(*second)?;
358 let third = decode_value(*third)?;
359 Some((
360 [
361 (first << 2) | (second >> 4),
362 (second << 4) | (third >> 2),
363 0,
364 ],
365 2,
366 ))
367 }
368 _ => None,
369 }
370}