Skip to main content

tape_base64/
stream.rs

1//! Conversions that take their input a piece at a time
2//!
3//! Each holds the few characters a piece boundary can leave behind, so the
4//! pieces of an input convert to exactly what the pieces joined convert to.
5
6use crate::alphabet::{Alphabet, Standard, UrlSafe};
7use crate::codec;
8use crate::error::{DecodeError, EncodeError};
9use crate::scalar;
10use crate::{Codec, Kind};
11
12/// An encode taken a piece at a time, giving what the pieces joined encode to
13pub struct Encoder {
14    kind: Kind,
15    is_padded: bool,
16    held: [u8; 2],
17    held_len: u8,
18}
19
20impl Codec {
21    /// An encoder that takes its input a piece at a time
22    pub fn encoder(&self) -> Encoder {
23        Encoder {
24            kind: self.kind,
25            is_padded: self.is_padded,
26            held: [0; 2],
27            held_len: 0,
28        }
29    }
30
31    /// A decoder that takes its input a piece at a time
32    pub fn decoder(&self) -> Decoder {
33        Decoder {
34            kind: self.kind,
35            is_padded: self.is_padded,
36            quad: [0; 3],
37            quad_len: 0,
38            total: 0,
39            run: 0,
40            pending: None,
41            state: State::Reading,
42        }
43    }
44}
45
46impl Encoder {
47    /// Encode all of `input`, returning characters written, or refuse and consume nothing
48    pub fn push(&mut self, input: &[u8], out: &mut [u8]) -> Result<usize, EncodeError> {
49        let groups = (usize::from(self.held_len) + input.len()) / 3;
50        let needed = groups * 4;
51        if out.len() < needed {
52            return Err(EncodeError::BufferTooSmall { needed });
53        }
54        match self.kind {
55            Kind::Standard => self.pushed::<Standard>(input, out, groups),
56            Kind::UrlSafe => self.pushed::<UrlSafe>(input, out, groups),
57        }
58    }
59
60    /// Write the group any held bytes belong to, completing the encoding
61    pub fn finish(self, out: &mut [u8]) -> Result<usize, EncodeError> {
62        let held = usize::from(self.held_len);
63        let count = match (held, self.is_padded) {
64            (0, _) => 0,
65            (_, true) => 4,
66            (_, false) => held + 1,
67        };
68        if out.len() < count {
69            return Err(EncodeError::BufferTooSmall { needed: count });
70        }
71        let written = match self.kind {
72            Kind::Standard => {
73                scalar::encode_rest::<Standard>(&self.held[..held], out, self.is_padded)
74            }
75            Kind::UrlSafe => {
76                scalar::encode_rest::<UrlSafe>(&self.held[..held], out, self.is_padded)
77            }
78        };
79        debug_assert_eq!(written, count);
80        Ok(written)
81    }
82
83    /// Convert `groups` whole groups out of the held bytes and `input`
84    fn pushed<A: Alphabet>(
85        &mut self,
86        input: &[u8],
87        out: &mut [u8],
88        groups: usize,
89    ) -> Result<usize, EncodeError> {
90        let held = usize::from(self.held_len);
91        let mut consumed = 0;
92        let mut written = 0;
93
94        // A group the last piece left short finishes out of this piece's first bytes.
95        if groups > 0 && held > 0 {
96            let mut group = [0u8; 3];
97            group[..held].copy_from_slice(&self.held[..held]);
98            group[held..].copy_from_slice(&input[..3 - held]);
99            scalar::encode_group::<A>(group, &mut out[..4]);
100            self.held_len = 0;
101            consumed = 3 - held;
102            written = 4;
103        }
104
105        written += codec::encode_groups::<A>(
106            &input[consumed..],
107            &mut out[written..],
108            groups - written / 4,
109        );
110
111        // The bytes past the last whole group wait for the next piece.
112        let spare = (held + input.len()) % 3;
113        if groups > 0 {
114            self.held[..spare].copy_from_slice(&input[input.len() - spare..]);
115            self.held_len = spare as u8;
116        } else {
117            self.held[held..held + input.len()].copy_from_slice(input);
118            self.held_len = (held + input.len()) as u8;
119        }
120        Ok(written)
121    }
122}
123
124/// A decode taken a piece at a time, reading and refusing where `Codec::decode` does
125pub struct Decoder {
126    kind: Kind,
127    is_padded: bool,
128    quad: [u8; 3],
129    quad_len: u8,
130    total: usize,
131    run: u8,
132    pending: Option<Pending>,
133    state: State,
134}
135
136/// Convert one whole quad into `out`, refusing a foreign character at `base` plus its place
137fn take_quad<A: Alphabet>(
138    quad: &[u8; 4],
139    base: usize,
140    out: &mut [u8],
141) -> Result<usize, DecodeError> {
142    // The walk stops on a foreign character without reporting it, as a vector block does.
143    if scalar::decode_bulk::<A>(quad, out, 1) == 0 {
144        return Err(scalar::find_invalid::<A>(quad, base));
145    }
146    Ok(3)
147}
148
149/// Refuse a buffer short of the bytes a held group of `chars` characters owes
150fn room_for(chars: usize, out: &[u8]) -> Result<(), DecodeError> {
151    let needed = chars.saturating_sub(1);
152    if out.len() < needed {
153        return Err(DecodeError::BufferTooSmall { needed });
154    }
155    Ok(())
156}
157
158/// A group that may be an ending
159struct Pending {
160    /// The characters before the padding
161    group: [u8; 3],
162    /// How many of them hold bits, two or three
163    group_len: u8,
164    /// Where the padding starts, counted over the whole input
165    pad: usize,
166}
167
168/// Where a reading stands
169enum State {
170    /// Whole groups convert as they complete
171    Reading,
172    /// A padded ending converted, and only a quiet end can follow it
173    Ended {
174        /// Character count when the ending completed
175        at: usize,
176        /// Where the ending's first padding character sits
177        pad: usize,
178    },
179    /// A refusal became certain, and only its report waits for the end
180    Refused(Option<DecodeError>),
181}
182
183impl Decoder {
184    /// Decode all of `input`, returning bytes written; what came before a refusal stays written
185    pub fn push(&mut self, input: &[u8], out: &mut [u8]) -> Result<usize, DecodeError> {
186        match self.state {
187            State::Reading => {}
188            State::Ended { .. } | State::Refused(_) => {
189                self.tally(input);
190                return Ok(0);
191            }
192        }
193        if input.is_empty() {
194            return Ok(0);
195        }
196        // A group closed by padding is written by `finish`, which is why the last quad drops out.
197        let chars = usize::from(self.quad_len) + input.len();
198        let mut quads = chars / 4;
199        if self.is_padded && chars.is_multiple_of(4) && input.last() == Some(&b'=') {
200            quads -= 1;
201        }
202        let needed = quads * 3;
203        if out.len() < needed {
204            return Err(DecodeError::BufferTooSmall { needed });
205        }
206        match self.kind {
207            Kind::Standard => self.read::<Standard>(input, out),
208            Kind::UrlSafe => self.read::<UrlSafe>(input, out),
209        }
210    }
211
212    /// Complete the decode, giving what `Codec::decode` gives for the pieces joined
213    pub fn finish(mut self, out: &mut [u8]) -> Result<usize, DecodeError> {
214        let held = usize::from(self.quad_len);
215        match core::mem::replace(&mut self.state, State::Reading) {
216            State::Reading => {
217                self.check_length()?;
218                let owed = match &self.pending {
219                    Some(pending) => usize::from(pending.group_len),
220                    None => held,
221                };
222                room_for(owed, out)?;
223                // A group held over with padding aboard is the ending, unchecked until now.
224                if let Some(pending) = self.pending.take() {
225                    return self.settle(pending, out);
226                }
227                if held > 0 {
228                    let offset = self.total - held;
229                    return match self.kind {
230                        Kind::Standard => {
231                            scalar::decode_rest::<Standard>(&self.quad[..held], out, offset)
232                        }
233                        Kind::UrlSafe => {
234                            scalar::decode_rest::<UrlSafe>(&self.quad[..held], out, offset)
235                        }
236                    };
237                }
238                Ok(0)
239            }
240            State::Ended { at, .. } if self.total == at => {
241                // An ending is still the wrong length when its groups were short.
242                self.check_length()?;
243                let owed = self
244                    .pending
245                    .as_ref()
246                    .map_or(0, |pending| usize::from(pending.group_len));
247                room_for(owed, out)?;
248                match self.pending.take() {
249                    // The ending stood, and its characters become bytes here.
250                    Some(pending) => self.settle(pending, out),
251                    None => Ok(0),
252                }
253            }
254            State::Ended { pad, .. } => self.name(Some(DecodeError::InvalidByte {
255                position: pad,
256                byte: b'=',
257            })),
258            State::Refused(cause) => self.name(cause),
259        }
260    }
261
262    /// Read a piece in the steady state, where whole groups convert as they land
263    fn read<A: Alphabet>(&mut self, input: &[u8], out: &mut [u8]) -> Result<usize, DecodeError> {
264        // A group held over with padding aboard is decided by this piece's first character.
265        let mut consumed = 0;
266        let mut written = 0;
267        if let Some(pending) = self.pending.take() {
268            let Some(&byte) = input.first() else {
269                self.pending = Some(pending);
270                return Ok(0);
271            };
272            consumed = 1;
273            self.total += 1;
274            if byte != b'=' {
275                let cause = DecodeError::InvalidByte {
276                    position: pending.pad,
277                    byte: b'=',
278                };
279                return Err(self.refuse(Some(cause), 0, &input[consumed..]));
280            }
281            // The padding repeated, so the ending held; its bytes wait for the end.
282            self.state = State::Ended {
283                at: self.total,
284                pad: pending.pad,
285            };
286            self.pending = Some(pending);
287            self.tally(&input[consumed..]);
288            return Ok(written);
289        }
290
291        let held = usize::from(self.quad_len);
292
293        // A group the last piece left short completes alone, so the bulk stage sees whole quads.
294        if held > 0 && input.len() >= 4 - held {
295            let take = 4 - held;
296            // A quad closed by padding waits for the character loop, which checks each pad.
297            let is_ending = self.is_padded && input[take - 1] == b'=';
298            if !is_ending {
299                let mut quad = [0u8; 4];
300                quad[..held].copy_from_slice(&self.quad[..held]);
301                quad[held..].copy_from_slice(&input[..take]);
302                match take_quad::<A>(&quad, self.total - held, &mut out[written..]) {
303                    Ok(count) => {
304                        written += count;
305                        self.total += take;
306                    }
307                    Err(cause) => {
308                        self.total += take;
309                        return Err(self.refuse(Some(cause), 0, &input[take..]));
310                    }
311                }
312                self.quad_len = 0;
313                consumed = take;
314            }
315        }
316
317        // Whole quads convert in runs, but a piece ending in padding keeps its last group back.
318        let ending = if self.is_padded && input.last() == Some(&b'=') {
319            4
320        } else {
321            0
322        };
323        let whole = (input.len() - consumed).saturating_sub(ending) / 4;
324        if whole > 0 {
325            let base = self.total;
326            let began = consumed;
327            let (bytes, failure) = codec::scan_quads::<A>(
328                &input[consumed..consumed + whole * 4],
329                &mut out[written..],
330                whole,
331            );
332            written += bytes;
333            let taken = bytes / 3 * 4;
334            consumed += taken;
335            self.total += taken;
336
337            if let Some(DecodeError::InvalidByte {
338                position: local,
339                byte,
340            }) = failure
341            {
342                if !self.is_padded || byte != b'=' {
343                    self.total += local + 1 - taken;
344                    let cause = DecodeError::InvalidByte {
345                        position: base + local,
346                        byte,
347                    };
348                    return Err(self.refuse(Some(cause), 0, &input[began + local + 1..]));
349                }
350            }
351        }
352
353        // What is left is short of a group, or is an ending's padding; neither commits yet.
354        for index in consumed..input.len() {
355            let byte = input[index];
356            let rest = &input[index + 1..];
357            self.total += 1;
358
359            // A group held over with padding ends the input when the next character is padding too.
360            if let Some(pending) = self.pending.take() {
361                if byte != b'=' {
362                    let cause = DecodeError::InvalidByte {
363                        position: pending.pad,
364                        byte: b'=',
365                    };
366                    return Err(self.refuse(Some(cause), 0, rest));
367                }
368                // The padding repeated, so the ending held; its bytes wait for the end.
369                self.state = State::Ended {
370                    at: self.total,
371                    pad: pending.pad,
372                };
373                self.pending = Some(pending);
374                self.tally(rest);
375                return Ok(written);
376            }
377
378            let held = usize::from(self.quad_len);
379
380            // Padding ends a group, and whether it ends the input is the next character's news.
381            if self.is_padded && byte == b'=' && held >= 2 {
382                self.pending = Some(Pending {
383                    group: [self.quad[0], self.quad[1], self.quad[2]],
384                    group_len: held as u8,
385                    pad: self.total - 1,
386                });
387                continue;
388            }
389
390            // A foreign character, padding included, is reported where a whole-input decode would.
391            if A::DECODE[byte as usize] == crate::alphabet::INVALID {
392                let position = self.total - 1;
393                return Err(self.refuse(
394                    Some(DecodeError::InvalidByte { position, byte }),
395                    u8::from(self.is_padded && byte == b'='),
396                    rest,
397                ));
398            }
399
400            if held == 3 {
401                // The group completes here, padding or not.
402                let quad = [self.quad[0], self.quad[1], self.quad[2], byte];
403                match take_quad::<A>(&quad, self.total - 4, &mut out[written..]) {
404                    Ok(count) => written += count,
405                    Err(cause) => {
406                        return Err(self.refuse(Some(cause), 0, rest));
407                    }
408                }
409                self.quad_len = 0;
410            } else {
411                self.quad[held] = byte;
412                self.quad_len += 1;
413            }
414        }
415        Ok(written)
416    }
417
418    /// Write a held group out, as the ending it has now been shown to be
419    fn settle(&mut self, pending: Pending, out: &mut [u8]) -> Result<usize, DecodeError> {
420        let held = usize::from(pending.group_len);
421        let offset = self.total - 4;
422        let group = &pending.group[..held];
423        let written = match self.kind {
424            Kind::Standard => scalar::decode_rest::<Standard>(group, out, offset)?,
425            Kind::UrlSafe => scalar::decode_rest::<UrlSafe>(group, out, offset)?,
426        };
427        self.state = State::Ended {
428            at: self.total,
429            pad: pending.pad,
430        };
431        Ok(written)
432    }
433
434    /// Move into the refused state, counting the characters behind the cause
435    fn refuse(&mut self, cause: Option<DecodeError>, run: u8, rest: &[u8]) -> DecodeError {
436        self.state = State::Refused(cause);
437        self.quad_len = 0;
438        self.pending = None;
439        self.run = run;
440        self.tally(rest);
441        cause.unwrap_or(DecodeError::InvalidPadding {
442            position: self.total.saturating_sub(3),
443        })
444    }
445
446    /// Count characters too late to change anything, the first of them ending a held group
447    fn tally(&mut self, input: &[u8]) {
448        let Some((&first, rest)) = input.split_first() else {
449            return;
450        };
451        self.total += 1;
452        if let Some(pending) = self.pending.take() {
453            let cause = DecodeError::InvalidByte {
454                position: pending.pad,
455                byte: b'=',
456            };
457            self.state = State::Refused(Some(cause));
458        }
459        self.run = if self.is_padded && first == b'=' {
460            self.run.min(2) + 1
461        } else {
462            0
463        };
464
465        // Only the last three characters can leave a run of padding behind.
466        self.total += rest.len();
467        for &byte in &rest[rest.len().saturating_sub(3)..] {
468            self.run = if self.is_padded && byte == b'=' {
469                self.run.min(2) + 1
470            } else {
471                0
472            };
473        }
474    }
475
476    /// Report a refusal the way a whole-input decode would
477    fn name(&self, cause: Option<DecodeError>) -> Result<usize, DecodeError> {
478        self.check_length()?;
479        if self.run >= 3 {
480            return Err(DecodeError::InvalidPadding {
481                position: self.total - 3,
482            });
483        }
484        Err(cause.unwrap_or(DecodeError::InvalidPadding {
485            position: self.total.saturating_sub(3),
486        }))
487    }
488
489    /// Apply the length rule a whole-input decode applies before reading
490    fn check_length(&self) -> Result<(), DecodeError> {
491        if crate::length_ok(self.total, self.is_padded) {
492            Ok(())
493        } else {
494            Err(DecodeError::InvalidLength { length: self.total })
495        }
496    }
497}