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,
5    encode_base64_value, scalar, 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        scalar::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    /// # Examples
112    ///
113    /// ```
114    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
115    ///
116    /// let wrap = LineWrap::new(4, LineEnding::Lf);
117    /// let mut output = [0u8; 9];
118    /// let written = STANDARD
119    ///     .encode_slice_wrapped(b"hello", &mut output, wrap)
120    ///     .unwrap();
121    ///
122    /// assert_eq!(&output[..written], b"aGVs\nbG8=");
123    /// ```
124    pub fn encode_slice_wrapped(
125        &self,
126        input: &[u8],
127        output: &mut [u8],
128        wrap: LineWrap,
129    ) -> Result<usize, EncodeError> {
130        let required = self.wrapped_encoded_len(input.len(), wrap)?;
131        if output.len() < required {
132            return Err(EncodeError::OutputTooSmall {
133                required,
134                available: output.len(),
135            });
136        }
137
138        let encoded_len =
139            checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
140        if encoded_len == 0 {
141            return Ok(0);
142        }
143
144        // If the temporary in-buffer layout size overflows, fall back to the
145        // fixed scratch buffer path rather than relying on saturated arithmetic.
146        let combined_required = match required.checked_add(encoded_len) {
147            Some(len) => len,
148            None => usize::MAX,
149        };
150        if output.len() < combined_required {
151            let mut scratch = [0u8; 1024];
152            let mut input_offset = 0;
153            let mut output_offset = 0;
154            let mut column = 0;
155
156            while input_offset < input.len() {
157                let remaining = input.len() - input_offset;
158                let mut take = remaining.min(768);
159                if remaining > take {
160                    take -= take % 3;
161                }
162                if take == 0 {
163                    take = remaining;
164                }
165
166                let encoded = match self
167                    .encode_slice(&input[input_offset..input_offset + take], &mut scratch)
168                {
169                    Ok(encoded) => encoded,
170                    Err(err) => {
171                        wipe_bytes(&mut scratch);
172                        return Err(err);
173                    }
174                };
175                if let Err(err) = write_wrapped_bytes(
176                    &scratch[..encoded],
177                    output,
178                    &mut output_offset,
179                    &mut column,
180                    wrap,
181                ) {
182                    wipe_bytes(&mut scratch);
183                    return Err(err);
184                }
185                wipe_bytes(&mut scratch[..encoded]);
186                input_offset += take;
187            }
188
189            Ok(output_offset)
190        } else {
191            let encoded =
192                self.encode_slice(input, &mut output[required..required + encoded_len])?;
193            let mut output_offset = 0;
194            let mut column = 0;
195            let mut read = required;
196            while read < required + encoded {
197                let byte = output[read];
198                write_wrapped_byte(byte, output, &mut output_offset, &mut column, wrap)?;
199                read += 1;
200            }
201            wipe_bytes(&mut output[required..required + encoded]);
202            Ok(output_offset)
203        }
204    }
205
206    /// Encodes `input` with line wrapping and clears all bytes after the
207    /// encoded prefix.
208    ///
209    /// If encoding fails, the entire output buffer is cleared before the error
210    /// is returned.
211    pub fn encode_slice_wrapped_clear_tail(
212        &self,
213        input: &[u8],
214        output: &mut [u8],
215        wrap: LineWrap,
216    ) -> Result<usize, EncodeError> {
217        let written = match self.encode_slice_wrapped(input, output, wrap) {
218            Ok(written) => written,
219            Err(err) => {
220                wipe_bytes(output);
221                return Err(err);
222            }
223        };
224        wipe_tail(output, written);
225        Ok(written)
226    }
227
228    /// Encodes `input` with line wrapping into a stack-backed buffer.
229    ///
230    /// This is useful for MIME/PEM-style protocols where heap allocation is
231    /// unnecessary. If encoding fails, the internal backing array is cleared
232    /// before the error is returned.
233    pub fn encode_wrapped_buffer<const CAP: usize>(
234        &self,
235        input: &[u8],
236        wrap: LineWrap,
237    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
238        let mut output = EncodedBuffer::new();
239        let written =
240            match self.encode_slice_wrapped_clear_tail(input, output.as_mut_capacity(), wrap) {
241                Ok(written) => written,
242                Err(err) => {
243                    output.clear();
244                    return Err(err);
245                }
246            };
247        output.set_filled(written)?;
248        Ok(output)
249    }
250
251    /// Encodes `input` with line wrapping into a newly allocated byte vector.
252    #[cfg(feature = "alloc")]
253    #[must_use = "for secret-bearing payloads use encode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
254    pub fn encode_wrapped_vec(
255        &self,
256        input: &[u8],
257        wrap: LineWrap,
258    ) -> Result<alloc::vec::Vec<u8>, EncodeError> {
259        let required = self.wrapped_encoded_len(input.len(), wrap)?;
260        let mut output = alloc::vec![0; required];
261        let written = self.encode_slice_wrapped(input, &mut output, wrap)?;
262        output.truncate(written);
263        Ok(output)
264    }
265
266    /// Encodes `input` with line wrapping into a newly allocated UTF-8 string.
267    #[cfg(feature = "alloc")]
268    pub fn encode_wrapped_string(
269        &self,
270        input: &[u8],
271        wrap: LineWrap,
272    ) -> Result<alloc::string::String, EncodeError> {
273        let output = self.encode_wrapped_vec(input, wrap)?;
274        match alloc::string::String::from_utf8(output) {
275            Ok(output) => Ok(output),
276            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
277        }
278    }
279
280    /// Encodes `input` with line wrapping into a redacted owned secret buffer.
281    ///
282    /// This is useful when the wrapped encoded representation itself is
283    /// sensitive and should not be accidentally logged through formatting.
284    #[cfg(feature = "alloc")]
285    pub fn encode_wrapped_secret(
286        &self,
287        input: &[u8],
288        wrap: LineWrap,
289    ) -> Result<SecretBuffer, EncodeError> {
290        self.encode_wrapped_vec(input, wrap)
291            .map(SecretBuffer::from_vec)
292    }
293
294    /// Encodes `input` into `output` and clears all bytes after the encoded
295    /// prefix.
296    ///
297    /// If encoding fails, the entire output buffer is cleared before the error
298    /// is returned.
299    ///
300    /// # Examples
301    ///
302    /// ```
303    /// use base64_ng::STANDARD;
304    ///
305    /// let mut output = [0xff; 12];
306    /// let written = STANDARD
307    ///     .encode_slice_clear_tail(b"hello", &mut output)
308    ///     .unwrap();
309    ///
310    /// assert_eq!(&output[..written], b"aGVsbG8=");
311    /// assert!(output[written..].iter().all(|byte| *byte == 0));
312    /// ```
313    pub fn encode_slice_clear_tail(
314        &self,
315        input: &[u8],
316        output: &mut [u8],
317    ) -> Result<usize, EncodeError> {
318        let written = match self.encode_slice(input, output) {
319            Ok(written) => written,
320            Err(err) => {
321                wipe_bytes(output);
322                return Err(err);
323            }
324        };
325        wipe_tail(output, written);
326        Ok(written)
327    }
328
329    /// Encodes `input` into a stack-backed buffer.
330    ///
331    /// This helper is useful for short values where callers want the
332    /// convenience of an owned result without enabling `alloc`.
333    ///
334    /// # Examples
335    ///
336    /// ```
337    /// use base64_ng::STANDARD;
338    ///
339    /// let encoded = STANDARD.encode_buffer::<8>(b"hello").unwrap();
340    ///
341    /// assert_eq!(encoded.as_str(), "aGVsbG8=");
342    /// ```
343    pub fn encode_buffer<const CAP: usize>(
344        &self,
345        input: &[u8],
346    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
347        let mut output = EncodedBuffer::new();
348        let written = match self.encode_slice_clear_tail(input, output.as_mut_capacity()) {
349            Ok(written) => written,
350            Err(err) => {
351                output.clear();
352                return Err(err);
353            }
354        };
355        output.set_filled(written)?;
356        Ok(output)
357    }
358
359    /// Encodes `input` into a newly allocated byte vector.
360    #[cfg(feature = "alloc")]
361    #[must_use = "for secret-bearing payloads use encode_secret, which returns a redacted buffer with drop-time cleanup"]
362    pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
363        let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
364        let mut output = alloc::vec![0; required];
365        let written = self.encode_slice(input, &mut output)?;
366        output.truncate(written);
367        Ok(output)
368    }
369
370    /// Encodes `input` into a redacted owned secret buffer.
371    ///
372    /// This is useful when the encoded representation itself is sensitive and
373    /// should not be accidentally logged through formatting.
374    #[cfg(feature = "alloc")]
375    pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
376        self.encode_vec(input).map(SecretBuffer::from_vec)
377    }
378
379    /// Encodes `input` into a newly allocated UTF-8 string.
380    ///
381    /// Base64 output is ASCII by construction. This helper is available with
382    /// the `alloc` feature and has the same encoding semantics as
383    /// [`Self::encode_slice`].
384    ///
385    /// # Examples
386    ///
387    /// ```
388    /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
389    ///
390    /// assert_eq!(STANDARD.encode_string(b"hello").unwrap(), "aGVsbG8=");
391    /// assert_eq!(URL_SAFE_NO_PAD.encode_string(b"\xfb\xff").unwrap(), "-_8");
392    /// ```
393    #[cfg(feature = "alloc")]
394    pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
395        let output = self.encode_vec(input)?;
396        match alloc::string::String::from_utf8(output) {
397            Ok(output) => Ok(output),
398            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
399        }
400    }
401}