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}