Skip to main content

base64_ng/engine/
encode.rs

1#[cfg(feature = "alloc")]
2use crate::SecretBuffer;
3use crate::{
4    Alphabet, EncodeError, EncodedBuffer, Engine, LineWrap, checked_encoded_len, encode_backend,
5    encode_base64_value, wipe_bytes, wipe_tail, write_wrapped_byte, write_wrapped_bytes,
6};
7
8impl<A, const PAD: bool> Engine<A, PAD>
9where
10    A: Alphabet,
11{
12    /// Encodes a fixed-size input into a fixed-size output array in const contexts.
13    ///
14    /// Stable Rust does not yet allow this API to return an array whose length
15    /// is computed from `INPUT_LEN` directly. Instead, the caller supplies the
16    /// output length through the destination type and this function panics
17    /// during const evaluation if the length is wrong.
18    ///
19    /// # Panics
20    ///
21    /// Panics if `OUTPUT_LEN` is not exactly the encoded length for `INPUT_LEN`
22    /// and this engine's padding policy, or if that length overflows `usize`.
23    ///
24    /// # Examples
25    ///
26    /// ```
27    /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
28    ///
29    /// const HELLO: [u8; 8] = STANDARD.encode_array(b"hello");
30    /// const URL_SAFE: [u8; 3] = URL_SAFE_NO_PAD.encode_array(b"\xfb\xff");
31    ///
32    /// assert_eq!(&HELLO, b"aGVsbG8=");
33    /// assert_eq!(&URL_SAFE, b"-_8");
34    /// ```
35    ///
36    /// Incorrect output lengths fail during const evaluation:
37    ///
38    /// ```compile_fail
39    /// use base64_ng::STANDARD;
40    ///
41    /// const TOO_SHORT: [u8; 7] = STANDARD.encode_array(b"hello");
42    /// ```
43    #[must_use]
44    pub const fn encode_array<const INPUT_LEN: usize, const OUTPUT_LEN: usize>(
45        &self,
46        input: &[u8; INPUT_LEN],
47    ) -> [u8; OUTPUT_LEN] {
48        let Some(required) = checked_encoded_len(INPUT_LEN, PAD) else {
49            panic!("encoded base64 length overflows usize");
50        };
51        assert!(
52            required == OUTPUT_LEN,
53            "base64 output array has incorrect length"
54        );
55
56        let mut output = [0u8; OUTPUT_LEN];
57        let mut read = 0;
58        let mut write = 0;
59        while INPUT_LEN - read >= 3 {
60            let b0 = input[read];
61            let b1 = input[read + 1];
62            let b2 = input[read + 2];
63
64            output[write] = encode_base64_value::<A>(b0 >> 2);
65            output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
66            output[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
67            output[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
68
69            read += 3;
70            write += 4;
71        }
72
73        match INPUT_LEN - read {
74            0 => {}
75            1 => {
76                let b0 = input[read];
77                output[write] = encode_base64_value::<A>(b0 >> 2);
78                output[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
79                write += 2;
80                if PAD {
81                    output[write] = b'=';
82                    output[write + 1] = b'=';
83                }
84            }
85            2 => {
86                let b0 = input[read];
87                let b1 = input[read + 1];
88                output[write] = encode_base64_value::<A>(b0 >> 2);
89                output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
90                output[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
91                if PAD {
92                    output[write + 3] = b'=';
93                }
94            }
95            _ => unreachable!(),
96        }
97
98        output
99    }
100
101    /// Encodes `input` into `output`, returning the number of bytes written.
102    pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
103        encode_backend::encode_slice::<A, PAD>(input, output)
104    }
105
106    /// Encodes `input` into `output` with line wrapping.
107    ///
108    /// The wrapping policy inserts line endings between encoded lines and does
109    /// not append a trailing line ending after the final line.
110    ///
111    /// # Security
112    ///
113    /// When `output` has enough spare room, this method may temporarily stage
114    /// the unwrapped encoded form in the tail of `output` before copying the
115    /// wrapped representation to the front and wiping the staging range. For
116    /// secret-bearing payloads where a same-process observer must not see
117    /// transient encoded material in caller-owned output, prefer the
118    /// alloc-gated `encode_wrapped_secret` helper or manage a private staging
119    /// buffer at the application boundary.
120    ///
121    /// # Examples
122    ///
123    /// ```
124    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
125    ///
126    /// let wrap = LineWrap::new(4, LineEnding::Lf);
127    /// let mut output = [0u8; 9];
128    /// let written = STANDARD
129    ///     .encode_slice_wrapped(b"hello", &mut output, wrap)
130    ///     .unwrap();
131    ///
132    /// assert_eq!(&output[..written], b"aGVs\nbG8=");
133    /// ```
134    pub fn encode_slice_wrapped(
135        &self,
136        input: &[u8],
137        output: &mut [u8],
138        wrap: LineWrap,
139    ) -> Result<usize, EncodeError> {
140        let required = self.wrapped_encoded_len(input.len(), wrap)?;
141        if output.len() < required {
142            return Err(EncodeError::OutputTooSmall {
143                required,
144                available: output.len(),
145            });
146        }
147
148        let encoded_len =
149            checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
150        if encoded_len == 0 {
151            return Ok(0);
152        }
153
154        // If the temporary in-buffer layout size overflows, fall back to the
155        // fixed scratch buffer path rather than relying on saturated arithmetic.
156        let combined_required = match required.checked_add(encoded_len) {
157            Some(len) => len,
158            None => usize::MAX,
159        };
160        if output.len() < combined_required {
161            let mut scratch = [0u8; 1024];
162            let mut input_offset = 0;
163            let mut output_offset = 0;
164            let mut column = 0;
165
166            while input_offset < input.len() {
167                let remaining = input.len() - input_offset;
168                let mut take = remaining.min(768);
169                if remaining > take {
170                    take -= take % 3;
171                }
172                if take == 0 {
173                    take = remaining;
174                }
175
176                let encoded = match self
177                    .encode_slice(&input[input_offset..input_offset + take], &mut scratch)
178                {
179                    Ok(encoded) => encoded,
180                    Err(err) => {
181                        wipe_bytes(&mut scratch);
182                        return Err(err);
183                    }
184                };
185                if let Err(err) = write_wrapped_bytes(
186                    &scratch[..encoded],
187                    output,
188                    &mut output_offset,
189                    &mut column,
190                    wrap,
191                ) {
192                    wipe_bytes(&mut scratch);
193                    return Err(err);
194                }
195                wipe_bytes(&mut scratch[..encoded]);
196                input_offset += take;
197            }
198
199            Ok(output_offset)
200        } else {
201            let encoded =
202                self.encode_slice(input, &mut output[required..required + encoded_len])?;
203            let mut output_offset = 0;
204            let mut column = 0;
205            let mut read = required;
206            while read < required + encoded {
207                let byte = output[read];
208                write_wrapped_byte(byte, output, &mut output_offset, &mut column, wrap)?;
209                read += 1;
210            }
211            wipe_bytes(&mut output[required..required + encoded]);
212            Ok(output_offset)
213        }
214    }
215
216    /// Encodes `input` with line wrapping and clears all bytes after the
217    /// encoded prefix.
218    ///
219    /// If encoding fails, the entire output buffer is cleared before the error
220    /// is returned.
221    pub fn encode_slice_wrapped_clear_tail(
222        &self,
223        input: &[u8],
224        output: &mut [u8],
225        wrap: LineWrap,
226    ) -> Result<usize, EncodeError> {
227        let written = match self.encode_slice_wrapped(input, output, wrap) {
228            Ok(written) => written,
229            Err(err) => {
230                wipe_bytes(output);
231                return Err(err);
232            }
233        };
234        wipe_tail(output, written);
235        Ok(written)
236    }
237
238    /// Encodes `input` with line wrapping into a stack-backed buffer.
239    ///
240    /// This is useful for MIME/PEM-style protocols where heap allocation is
241    /// unnecessary. If encoding fails, the internal backing array is cleared
242    /// before the error is returned.
243    pub fn encode_wrapped_buffer<const CAP: usize>(
244        &self,
245        input: &[u8],
246        wrap: LineWrap,
247    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
248        let mut output = EncodedBuffer::new();
249        let written =
250            match self.encode_slice_wrapped_clear_tail(input, output.as_mut_capacity(), wrap) {
251                Ok(written) => written,
252                Err(err) => {
253                    output.clear();
254                    return Err(err);
255                }
256            };
257        output.set_filled(written)?;
258        Ok(output)
259    }
260
261    /// Encodes `input` with line wrapping into a newly allocated byte vector.
262    #[cfg(feature = "alloc")]
263    #[must_use = "for secret-bearing payloads use encode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
264    pub fn encode_wrapped_vec(
265        &self,
266        input: &[u8],
267        wrap: LineWrap,
268    ) -> Result<alloc::vec::Vec<u8>, EncodeError> {
269        let required = self.wrapped_encoded_len(input.len(), wrap)?;
270        let mut output = alloc::vec![0; required];
271        let written = self.encode_slice_wrapped(input, &mut output, wrap)?;
272        output.truncate(written);
273        Ok(output)
274    }
275
276    /// Encodes `input` with line wrapping into a newly allocated UTF-8 string.
277    #[cfg(feature = "alloc")]
278    pub fn encode_wrapped_string(
279        &self,
280        input: &[u8],
281        wrap: LineWrap,
282    ) -> Result<alloc::string::String, EncodeError> {
283        let output = self.encode_wrapped_vec(input, wrap)?;
284        match alloc::string::String::from_utf8(output) {
285            Ok(output) => Ok(output),
286            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
287        }
288    }
289
290    /// Encodes `input` with line wrapping into a redacted owned secret buffer.
291    ///
292    /// This is useful when the wrapped encoded representation itself is
293    /// sensitive and should not be accidentally logged through formatting.
294    #[cfg(feature = "alloc")]
295    pub fn encode_wrapped_secret(
296        &self,
297        input: &[u8],
298        wrap: LineWrap,
299    ) -> Result<SecretBuffer, EncodeError> {
300        self.encode_wrapped_vec(input, wrap)
301            .map(SecretBuffer::from_vec)
302    }
303
304    /// Encodes `input` into `output` and clears all bytes after the encoded
305    /// prefix.
306    ///
307    /// If encoding fails, the entire output buffer is cleared before the error
308    /// is returned.
309    ///
310    /// # Examples
311    ///
312    /// ```
313    /// use base64_ng::STANDARD;
314    ///
315    /// let mut output = [0xff; 12];
316    /// let written = STANDARD
317    ///     .encode_slice_clear_tail(b"hello", &mut output)
318    ///     .unwrap();
319    ///
320    /// assert_eq!(&output[..written], b"aGVsbG8=");
321    /// assert!(output[written..].iter().all(|byte| *byte == 0));
322    /// ```
323    pub fn encode_slice_clear_tail(
324        &self,
325        input: &[u8],
326        output: &mut [u8],
327    ) -> Result<usize, EncodeError> {
328        let written = match self.encode_slice(input, output) {
329            Ok(written) => written,
330            Err(err) => {
331                wipe_bytes(output);
332                return Err(err);
333            }
334        };
335        wipe_tail(output, written);
336        Ok(written)
337    }
338
339    /// Encodes `input` into a stack-backed buffer.
340    ///
341    /// This helper is useful for short values where callers want the
342    /// convenience of an owned result without enabling `alloc`.
343    ///
344    /// # Examples
345    ///
346    /// ```
347    /// use base64_ng::STANDARD;
348    ///
349    /// let encoded = STANDARD.encode_buffer::<8>(b"hello").unwrap();
350    ///
351    /// assert_eq!(encoded.as_str(), "aGVsbG8=");
352    /// ```
353    pub fn encode_buffer<const CAP: usize>(
354        &self,
355        input: &[u8],
356    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
357        let mut output = EncodedBuffer::new();
358        let written = match self.encode_slice_clear_tail(input, output.as_mut_capacity()) {
359            Ok(written) => written,
360            Err(err) => {
361                output.clear();
362                return Err(err);
363            }
364        };
365        output.set_filled(written)?;
366        Ok(output)
367    }
368
369    /// Encodes `input` into a newly allocated byte vector.
370    #[cfg(feature = "alloc")]
371    #[must_use = "for secret-bearing payloads use encode_secret, which returns a redacted buffer with drop-time cleanup"]
372    pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
373        let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
374        let mut output = alloc::vec![0; required];
375        let written = self.encode_slice(input, &mut output)?;
376        output.truncate(written);
377        Ok(output)
378    }
379
380    /// Encodes `input` into a redacted owned secret buffer.
381    ///
382    /// This is useful when the encoded representation itself is sensitive and
383    /// should not be accidentally logged through formatting.
384    #[cfg(feature = "alloc")]
385    pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
386        self.encode_vec(input).map(SecretBuffer::from_vec)
387    }
388
389    /// Encodes `input` into a newly allocated UTF-8 string.
390    ///
391    /// Base64 output is ASCII by construction. This helper is available with
392    /// the `alloc` feature and has the same encoding semantics as
393    /// [`Self::encode_slice`].
394    ///
395    /// # Examples
396    ///
397    /// ```
398    /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
399    ///
400    /// assert_eq!(STANDARD.encode_string(b"hello").unwrap(), "aGVsbG8=");
401    /// assert_eq!(URL_SAFE_NO_PAD.encode_string(b"\xfb\xff").unwrap(), "-_8");
402    /// ```
403    #[cfg(feature = "alloc")]
404    pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
405        let output = self.encode_vec(input)?;
406        match alloc::string::String::from_utf8(output) {
407            Ok(output) => Ok(output),
408            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
409        }
410    }
411}