Skip to main content

base64_ng/engine/
encode_in_place.rs

1use crate::{
2    Alphabet, EncodeError, Engine, checked_encoded_len, encode_base64_value_runtime, wipe_bytes,
3    wipe_tail,
4};
5
6impl<A, const PAD: bool> Engine<A, PAD>
7where
8    A: Alphabet,
9{
10    /// Encodes the first `input_len` bytes of `buffer` in place.
11    ///
12    /// The buffer must have enough spare capacity for the encoded output. The
13    /// implementation writes from right to left, so unread input bytes are not
14    /// overwritten before they are encoded.
15    ///
16    /// # Panics
17    ///
18    /// Panics only if an internal right-to-left encode invariant is violated.
19    /// This indicates a bug in `base64-ng`; valid or malformed caller input is
20    /// reported through [`EncodeError`] instead.
21    ///
22    /// # Examples
23    ///
24    /// ```
25    /// use base64_ng::STANDARD;
26    ///
27    /// let mut buffer = [0u8; 8];
28    /// buffer[..5].copy_from_slice(b"hello");
29    /// let encoded = STANDARD.encode_in_place(&mut buffer, 5).unwrap();
30    /// assert_eq!(encoded, b"aGVsbG8=");
31    /// ```
32    pub fn encode_in_place<'a>(
33        &self,
34        buffer: &'a mut [u8],
35        input_len: usize,
36    ) -> Result<&'a mut [u8], EncodeError> {
37        if input_len > buffer.len() {
38            return Err(EncodeError::InputTooLarge {
39                input_len,
40                buffer_len: buffer.len(),
41            });
42        }
43
44        let required = checked_encoded_len(input_len, PAD).ok_or(EncodeError::LengthOverflow)?;
45        if buffer.len() < required {
46            return Err(EncodeError::OutputTooSmall {
47                required,
48                available: buffer.len(),
49            });
50        }
51
52        let mut read = input_len;
53        let mut write = required;
54
55        match input_len % 3 {
56            0 => {}
57            1 => {
58                read -= 1;
59                let b0 = buffer[read];
60                if PAD {
61                    write -= 4;
62                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
63                    buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
64                    buffer[write + 2] = b'=';
65                    buffer[write + 3] = b'=';
66                } else {
67                    write -= 2;
68                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
69                    buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
70                }
71            }
72            2 => {
73                read -= 2;
74                let b0 = buffer[read];
75                let b1 = buffer[read + 1];
76                if PAD {
77                    write -= 4;
78                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
79                    buffer[write + 1] =
80                        encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
81                    buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
82                    buffer[write + 3] = b'=';
83                } else {
84                    write -= 3;
85                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
86                    buffer[write + 1] =
87                        encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
88                    buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
89                }
90            }
91            _ => unreachable!(),
92        }
93
94        while read > 0 {
95            read -= 3;
96            write -= 4;
97            let b0 = buffer[read];
98            let b1 = buffer[read + 1];
99            let b2 = buffer[read + 2];
100
101            buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
102            buffer[write + 1] =
103                encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
104            buffer[write + 2] =
105                encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
106            buffer[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
107        }
108
109        // The right-to-left loop consumes exactly three input bytes for every
110        // four output bytes. If this invariant changes, returning a shifted
111        // slice would silently corrupt the in-place output.
112        assert_eq!(
113            write, 0,
114            "encode_in_place invariant violated: right-to-left loop did not complete"
115        );
116        Ok(&mut buffer[..required])
117    }
118
119    /// Encodes the first `input_len` bytes of `buffer` in place and clears all
120    /// bytes after the encoded prefix.
121    ///
122    /// If encoding fails because `input_len` is too large, the output buffer is
123    /// too small, or the encoded length overflows `usize`, the entire buffer is
124    /// cleared before the error is returned.
125    ///
126    /// # Examples
127    ///
128    /// ```
129    /// use base64_ng::STANDARD;
130    ///
131    /// let mut buffer = [0xff; 12];
132    /// buffer[..5].copy_from_slice(b"hello");
133    /// let encoded = STANDARD.encode_in_place_clear_tail(&mut buffer, 5).unwrap();
134    /// assert_eq!(encoded, b"aGVsbG8=");
135    /// ```
136    pub fn encode_in_place_clear_tail<'a>(
137        &self,
138        buffer: &'a mut [u8],
139        input_len: usize,
140    ) -> Result<&'a mut [u8], EncodeError> {
141        let len = match self.encode_in_place(buffer, input_len) {
142            Ok(encoded) => encoded.len(),
143            Err(err) => {
144                wipe_bytes(buffer);
145                return Err(err);
146            }
147        };
148        wipe_tail(buffer, len);
149        Ok(&mut buffer[..len])
150    }
151}