base64-ng 1.0.7

no_std-first Base64 encoding and decoding with strict APIs and a security-heavy release process
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
//! Named Base64 profiles with optional strict line wrapping.

use crate::{
    Alphabet, BCRYPT_NO_PAD, Bcrypt, CRYPT_NO_PAD, Crypt, DecodeError, DecodedBuffer, EncodeError,
    EncodedBuffer, Engine, LineEnding, LineWrap, STANDARD, Standard, checked_encoded_len,
    checked_wrapped_encoded_len, encoded_len, wrapped_encoded_len,
};

#[cfg(feature = "alloc")]
use crate::SecretBuffer;

/// A named Base64 profile with an engine and optional strict line wrapping.
///
/// Profiles are convenience values for protocol-shaped Base64. They keep the
/// same strict alphabet, padding, canonical-bit, and output-buffer rules as
/// [`Engine`], while carrying the wrapping policy for MIME/PEM-like formats.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Profile<A, const PAD: bool> {
    engine: Engine<A, PAD>,
    wrap: Option<LineWrap>,
}

impl<A, const PAD: bool> Profile<A, PAD>
where
    A: Alphabet,
{
    /// Creates a profile from an engine and optional strict line wrapping.
    #[must_use]
    pub const fn new(engine: Engine<A, PAD>, wrap: Option<LineWrap>) -> Self {
        Self { engine, wrap }
    }

    /// Creates a profile, returning `None` when the wrapping policy is invalid.
    ///
    /// This is useful when a profile is assembled from configuration or other
    /// untrusted metadata. Use [`Self::new`] for compile-time constants where
    /// the wrapping policy is known to be valid.
    #[must_use]
    pub const fn checked_new(engine: Engine<A, PAD>, wrap: Option<LineWrap>) -> Option<Self> {
        match wrap {
            Some(wrap) if !wrap.is_valid() => None,
            _ => Some(Self::new(engine, wrap)),
        }
    }

    /// Returns whether this profile can be used by encoders and decoders.
    #[must_use]
    pub const fn is_valid(&self) -> bool {
        match self.wrap {
            Some(wrap) => wrap.is_valid(),
            None => true,
        }
    }

    /// Returns the underlying engine.
    #[must_use]
    pub const fn engine(&self) -> Engine<A, PAD> {
        self.engine
    }

    /// Returns whether this profile uses padded Base64.
    #[must_use]
    pub const fn is_padded(&self) -> bool {
        PAD
    }

    /// Returns whether this profile carries a strict line-wrapping policy.
    #[must_use]
    pub const fn is_wrapped(&self) -> bool {
        self.wrap.is_some()
    }

    /// Returns the strict wrapping policy carried by this profile, if any.
    #[must_use]
    pub const fn line_wrap(&self) -> Option<LineWrap> {
        self.wrap
    }

    /// Returns the encoded line length for wrapped profiles.
    #[must_use]
    pub const fn line_len(&self) -> Option<usize> {
        match self.wrap {
            Some(wrap) => Some(wrap.line_len()),
            None => None,
        }
    }

    /// Returns the line ending for wrapped profiles.
    #[must_use]
    pub const fn line_ending(&self) -> Option<LineEnding> {
        match self.wrap {
            Some(wrap) => Some(wrap.line_ending()),
            None => None,
        }
    }

    /// Returns the encoded length for this profile.
    pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
        match self.wrap {
            Some(wrap) => wrapped_encoded_len(input_len, PAD, wrap),
            None => encoded_len(input_len, PAD),
        }
    }

    /// Returns the encoded length for this profile, or `None` on overflow or
    /// invalid line wrapping.
    #[must_use]
    pub const fn checked_encoded_len(&self, input_len: usize) -> Option<usize> {
        match self.wrap {
            Some(wrap) => checked_wrapped_encoded_len(input_len, PAD, wrap),
            None => checked_encoded_len(input_len, PAD),
        }
    }

    /// Returns the exact decoded length for this profile.
    pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
        match self.wrap {
            Some(wrap) => self.engine.decoded_len_wrapped(input, wrap),
            None => self.engine.decoded_len(input),
        }
    }

    /// Validates input according to this profile without writing decoded bytes.
    pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
        match self.wrap {
            Some(wrap) => self.engine.validate_wrapped_result(input, wrap),
            None => self.engine.validate_result(input),
        }
    }

    /// Returns whether `input` is valid for this profile.
    #[must_use]
    pub fn validate(&self, input: &[u8]) -> bool {
        self.validate_result(input).is_ok()
    }

    /// Encodes `input` into `output` according to this profile.
    pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
        match self.wrap {
            Some(wrap) => self.engine.encode_slice_wrapped(input, output, wrap),
            None => self.engine.encode_slice(input, output),
        }
    }

    /// Encodes `input` into `output` and clears all bytes after the encoded
    /// prefix.
    pub fn encode_slice_clear_tail(
        &self,
        input: &[u8],
        output: &mut [u8],
    ) -> Result<usize, EncodeError> {
        match self.wrap {
            Some(wrap) => self
                .engine
                .encode_slice_wrapped_clear_tail(input, output, wrap),
            None => self.engine.encode_slice_clear_tail(input, output),
        }
    }

    /// Encodes `input` into a stack-backed buffer.
    ///
    /// This is useful for short values where heap allocation is unnecessary.
    /// If encoding fails, the internal backing array is cleared before the
    /// error is returned.
    pub fn encode_buffer<const CAP: usize>(
        &self,
        input: &[u8],
    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
        let mut output = EncodedBuffer::new();
        let written = match self.encode_slice_clear_tail(input, output.as_mut_capacity()) {
            Ok(written) => written,
            Err(err) => {
                output.clear();
                return Err(err);
            }
        };
        output.set_filled(written)?;
        Ok(output)
    }

    /// Decodes `input` into `output` according to this profile.
    ///
    /// # Security
    ///
    /// Profile decoders use the normal strict decode path. They may branch or
    /// return early based on malformed input, padding position, wrapping, and
    /// output capacity in order to return precise [`DecodeError`] diagnostics,
    /// including exact invalid-byte values and positions.
    /// Do not use this method for token comparison, key-material decoding, or
    /// secret-bearing validation where malformed-input timing matters. Use
    /// [`crate::ct`] with a matching unwrapped engine for constant-time-oriented
    /// secret decoding.
    #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
    pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
        match self.wrap {
            Some(wrap) => self.engine.decode_slice_wrapped(input, output, wrap),
            None => self.engine.decode_slice(input, output),
        }
    }

    /// Decodes `input` into `output` and clears all bytes after the decoded
    /// prefix.
    pub fn decode_slice_clear_tail(
        &self,
        input: &[u8],
        output: &mut [u8],
    ) -> Result<usize, DecodeError> {
        match self.wrap {
            Some(wrap) => self
                .engine
                .decode_slice_wrapped_clear_tail(input, output, wrap),
            None => self.engine.decode_slice_clear_tail(input, output),
        }
    }

    /// Decodes `input` into a stack-backed buffer according to this profile.
    ///
    /// This is useful for short decoded values where heap allocation is
    /// unnecessary. If decoding fails, the internal backing array is cleared
    /// before the error is returned.
    pub fn decode_buffer<const CAP: usize>(
        &self,
        input: &[u8],
    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
        let mut output = DecodedBuffer::new();
        let written = match self.decode_slice_clear_tail(input, output.as_mut_capacity()) {
            Ok(written) => written,
            Err(err) => {
                output.clear();
                return Err(err);
            }
        };
        output.set_filled(written)?;
        Ok(output)
    }

    /// Decodes `buffer` in place according to this profile.
    ///
    /// For wrapped profiles, configured line endings are compacted out before
    /// decoding. If validation fails, the buffer contents are unspecified.
    /// On success, bytes after the returned decoded prefix may retain compacted
    /// encoded input. Use [`Self::decode_in_place_clear_tail`] when the buffer
    /// may be reused or freed without a caller-managed wipe.
    ///
    /// # Security
    ///
    /// Profile in-place decoders use the normal strict decode path. They may
    /// branch or return early based on malformed input, padding position,
    /// wrapping, and output capacity in order to return precise
    /// [`DecodeError`] diagnostics. Do not use this method for token
    /// comparison, key-material decoding, or secret-bearing validation where
    /// malformed-input timing matters.
    ///
    /// # Examples
    ///
    /// ```
    /// use base64_ng::{LineEnding, LineWrap, Profile, STANDARD};
    ///
    /// let profile = Profile::new(STANDARD, Some(LineWrap::new(4, LineEnding::Lf)));
    /// let mut buffer = *b"aGVs\nbG8=";
    /// let decoded = profile.decode_in_place(&mut buffer).unwrap();
    ///
    /// assert_eq!(decoded, b"hello");
    /// ```
    pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
        match self.wrap {
            Some(wrap) => self.engine.decode_in_place_wrapped(buffer, wrap),
            None => self.engine.decode_in_place(buffer),
        }
    }

    /// Decodes `buffer` in place according to this profile and clears all
    /// bytes after the decoded prefix.
    ///
    /// If validation or decoding fails, the entire buffer is cleared before the
    /// error is returned.
    ///
    /// # Examples
    ///
    /// ```
    /// use base64_ng::{LineEnding, LineWrap, Profile, STANDARD};
    ///
    /// let profile = Profile::new(STANDARD, Some(LineWrap::new(4, LineEnding::Lf)));
    /// let mut buffer = *b"aGVs\nbG8=";
    /// let len = profile.decode_in_place_clear_tail(&mut buffer).unwrap().len();
    ///
    /// assert_eq!(&buffer[..len], b"hello");
    /// assert!(buffer[len..].iter().all(|byte| *byte == 0));
    /// ```
    pub fn decode_in_place_clear_tail<'a>(
        &self,
        buffer: &'a mut [u8],
    ) -> Result<&'a mut [u8], DecodeError> {
        match self.wrap {
            Some(wrap) => self.engine.decode_in_place_wrapped_clear_tail(buffer, wrap),
            None => self.engine.decode_in_place_clear_tail(buffer),
        }
    }

    /// Encodes `input` into a newly allocated byte vector.
    #[cfg(feature = "alloc")]
    #[must_use = "for secret-bearing payloads use encode_secret, which returns a redacted buffer with drop-time cleanup"]
    pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
        match self.wrap {
            Some(wrap) => self.engine.encode_wrapped_vec(input, wrap),
            None => self.engine.encode_vec(input),
        }
    }

    /// Encodes `input` into a redacted owned secret buffer.
    #[cfg(feature = "alloc")]
    pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
        self.encode_vec(input).map(SecretBuffer::from_vec)
    }

    /// Encodes `input` into a newly allocated UTF-8 string.
    #[cfg(feature = "alloc")]
    pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
        match self.wrap {
            Some(wrap) => self.engine.encode_wrapped_string(input, wrap),
            None => self.engine.encode_string(input),
        }
    }

    /// Decodes `input` into a newly allocated byte vector.
    #[cfg(feature = "alloc")]
    #[must_use = "for secret-bearing payloads use decode_secret, which returns a redacted buffer with drop-time cleanup"]
    pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
        match self.wrap {
            Some(wrap) => self.engine.decode_wrapped_vec(input, wrap),
            None => self.engine.decode_vec(input),
        }
    }

    /// Decodes `input` into a redacted owned secret buffer.
    ///
    /// # Security
    ///
    /// This uses the profile's normal strict decoder, not the
    /// constant-time-oriented [`crate::ct`] module. It may branch or return
    /// early on malformed input and reports localized decode errors. For
    /// secret-bearing payloads where malformed-input timing matters, use the
    /// matching [`crate::ct::CtEngine`] explicitly and wrap successful output in
    /// [`SecretBuffer`].
    #[cfg(feature = "alloc")]
    pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
        self.decode_vec(input).map(SecretBuffer::from_vec)
    }
}

impl<A, const PAD: bool> Default for Profile<A, PAD>
where
    A: Alphabet,
{
    fn default() -> Self {
        Self::new(Engine::new(), None)
    }
}

impl<A, const PAD: bool> core::fmt::Display for Profile<A, PAD>
where
    A: Alphabet,
{
    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self.wrap {
            Some(wrap) => write!(formatter, "padded={PAD} wrap={wrap}"),
            None => write!(formatter, "padded={PAD} wrap=none"),
        }
    }
}

impl<A, const PAD: bool> From<Engine<A, PAD>> for Profile<A, PAD>
where
    A: Alphabet,
{
    fn from(engine: Engine<A, PAD>) -> Self {
        Self::new(engine, None)
    }
}

/// MIME Base64 profile: standard alphabet, padding, 76-column CRLF wrapping.
///
/// This profile uses the default strict decoder and is not a constant-time
/// token validator or key-material decoder. Use
/// [`ct::STANDARD`](crate::ct::STANDARD) with an application-level wrapping
/// policy for sensitive fixed-shape protocols.
#[doc(alias = "ct")]
#[doc(alias = "constant_time")]
#[doc(alias = "sensitive")]
pub const MIME: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::MIME));

/// PEM Base64 profile: standard alphabet, padding, 64-column LF wrapping.
///
/// This profile uses the default strict decoder and is not a constant-time
/// token validator or key-material decoder. Use
/// [`ct::STANDARD`](crate::ct::STANDARD) with an application-level wrapping
/// policy for sensitive fixed-shape protocols.
#[doc(alias = "ct")]
#[doc(alias = "constant_time")]
#[doc(alias = "sensitive")]
pub const PEM: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::PEM));

/// PEM Base64 profile with CRLF line endings.
///
/// This profile uses the default strict decoder and is not a constant-time
/// token validator or key-material decoder. Use
/// [`ct::STANDARD`](crate::ct::STANDARD) with an application-level wrapping
/// policy for sensitive fixed-shape protocols.
#[doc(alias = "ct")]
#[doc(alias = "constant_time")]
#[doc(alias = "sensitive")]
pub const PEM_CRLF: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::PEM_CRLF));

/// bcrypt-style no-padding Base64 profile.
///
/// This profile carries the bcrypt alphabet and no padding. It does not parse
/// complete bcrypt password-hash strings. Its default strict decoder is not a
/// constant-time token validator or key-material decoder; use
/// [`Profile::engine`] with [`Engine::ct_decoder`] for the matching
/// constant-time-oriented decoder when timing posture matters.
#[doc(alias = "ct")]
#[doc(alias = "constant_time")]
#[doc(alias = "sensitive")]
pub const BCRYPT: Profile<Bcrypt, false> = Profile::new(BCRYPT_NO_PAD, None);

/// Unix `crypt(3)`-style no-padding Base64 profile.
///
/// This profile carries the `crypt(3)` alphabet and no padding. It does not
/// parse complete password-hash strings. Its default strict decoder is not a
/// constant-time token validator or key-material decoder; use
/// [`Profile::engine`] with [`Engine::ct_decoder`] for the matching
/// constant-time-oriented decoder when timing posture matters.
#[doc(alias = "ct")]
#[doc(alias = "constant_time")]
#[doc(alias = "sensitive")]
pub const CRYPT: Profile<Crypt, false> = Profile::new(CRYPT_NO_PAD, None);