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
#[cfg(feature = "alloc")]
use crate::SecretBuffer;
#[cfg(feature = "alloc")]
use crate::validate_decode;
use crate::{
Alphabet, DecodeError, DecodedBuffer, Engine, LineWrap, decode_legacy_to_slice,
decode_wrapped_to_slice, scalar, validate_legacy_decode, validate_wrapped_decode, wipe_bytes,
wipe_tail,
};
impl<A, const PAD: bool> Engine<A, PAD>
where
A: Alphabet,
{
/// Decodes `input` into `output`, returning the number of bytes written.
///
/// This is strict decoding. Whitespace, mixed alphabets, malformed padding,
/// and trailing non-padding data are rejected.
///
/// # Security
///
/// This default scalar decoder prioritizes strict validation, exact error
/// reporting, and ordinary throughput. It may branch or return early based
/// on byte validity, malformed input, padding position, and output
/// capacity. It also reports exact failure positions and invalid byte
/// values through [`DecodeError`]. Do not use this method for token
/// comparison, key-material decoding, or secret-bearing validation where
/// malformed-input timing matters. Do not log strict decode errors
/// verbatim for secret-bearing input; log [`DecodeError::kind`] instead.
/// Use [`crate::ct`],
/// [`crate::ct::STANDARD`], [`crate::ct::URL_SAFE_NO_PAD`], or
/// [`Self::ct_decoder`] with `decode_slice_clear_tail` 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> {
scalar::decode_slice::<A, PAD>(input, output)
}
/// Decodes `input` into `output` and clears all bytes after the decoded
/// prefix.
///
/// If decoding fails, the entire output buffer is cleared before the error
/// is returned.
///
/// # Examples
///
/// ```
/// use base64_ng::STANDARD;
///
/// let mut output = [0xff; 8];
/// let written = STANDARD
/// .decode_slice_clear_tail(b"aGk=", &mut output)
/// .unwrap();
///
/// assert_eq!(&output[..written], b"hi");
/// assert!(output[written..].iter().all(|byte| *byte == 0));
/// ```
pub fn decode_slice_clear_tail(
&self,
input: &[u8],
output: &mut [u8],
) -> Result<usize, DecodeError> {
let written = match self.decode_slice(input, output) {
Ok(written) => written,
Err(err) => {
wipe_bytes(output);
return Err(err);
}
};
wipe_tail(output, written);
Ok(written)
}
/// Decodes `input` into a stack-backed buffer.
///
/// This helper is useful for short decoded values where callers want the
/// convenience of an owned result without enabling `alloc`.
///
/// # Examples
///
/// ```
/// use base64_ng::STANDARD;
///
/// let decoded = STANDARD.decode_buffer::<5>(b"aGVsbG8=").unwrap();
///
/// assert_eq!(decoded.as_bytes(), b"hello");
/// ```
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 `input` using the explicit legacy whitespace profile.
///
/// ASCII space, tab, carriage return, and line feed bytes are ignored.
/// Alphabet selection, padding placement, trailing data after padding, and
/// non-canonical trailing bits remain strict.
///
/// # Security
///
/// This method uses the normal strict decode path after legacy whitespace
/// handling. It may branch or return early based on malformed input and is
/// not a constant-time token validator or key-material decoder. Use
/// [`crate::ct`] for secret-bearing payloads.
#[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
pub fn decode_slice_legacy(
&self,
input: &[u8],
output: &mut [u8],
) -> Result<usize, DecodeError> {
let required = validate_legacy_decode::<A, PAD>(input)?;
if output.len() < required {
return Err(DecodeError::OutputTooSmall {
required,
available: output.len(),
});
}
decode_legacy_to_slice::<A, PAD>(input, output)
}
/// Decodes `input` using the explicit legacy whitespace profile and clears
/// all bytes after the decoded prefix.
///
/// If validation or decoding fails, the entire output buffer is cleared
/// before the error is returned.
///
/// # Examples
///
/// ```
/// use base64_ng::STANDARD;
///
/// let mut output = [0xff; 8];
/// let written = STANDARD
/// .decode_slice_legacy_clear_tail(b" aG\r\nk= ", &mut output)
/// .unwrap();
///
/// assert_eq!(&output[..written], b"hi");
/// assert!(output[written..].iter().all(|byte| *byte == 0));
/// ```
pub fn decode_slice_legacy_clear_tail(
&self,
input: &[u8],
output: &mut [u8],
) -> Result<usize, DecodeError> {
let written = match self.decode_slice_legacy(input, output) {
Ok(written) => written,
Err(err) => {
wipe_bytes(output);
return Err(err);
}
};
wipe_tail(output, written);
Ok(written)
}
/// Decodes `input` into a stack-backed buffer using the explicit legacy
/// whitespace profile.
///
/// ASCII space, tab, carriage return, and line feed bytes are ignored.
/// Alphabet selection, padding placement, trailing data after padding, and
/// non-canonical trailing bits remain strict. If decoding fails, the
/// internal backing array is cleared before the error is returned.
pub fn decode_buffer_legacy<const CAP: usize>(
&self,
input: &[u8],
) -> Result<DecodedBuffer<CAP>, DecodeError> {
let mut output = DecodedBuffer::new();
let written = match self.decode_slice_legacy_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` using a strict line-wrapped profile.
///
/// The wrapped profile accepts only the configured line ending. Non-final
/// lines must contain exactly `wrap.line_len` encoded bytes; the final line
/// may be shorter. A single trailing line ending after the final line is
/// accepted.
///
/// # Security
///
/// This method uses the normal strict decode path after line-profile
/// validation. It may branch or return early based on malformed input and
/// is not a constant-time token validator or key-material decoder. Use
/// [`crate::ct`] for secret-bearing payloads.
#[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
pub fn decode_slice_wrapped(
&self,
input: &[u8],
output: &mut [u8],
wrap: LineWrap,
) -> Result<usize, DecodeError> {
let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
if output.len() < required {
return Err(DecodeError::OutputTooSmall {
required,
available: output.len(),
});
}
decode_wrapped_to_slice::<A, PAD>(input, output, wrap)
}
/// Decodes `input` using a strict line-wrapped profile and clears all bytes
/// after the decoded prefix.
///
/// If validation or decoding fails, the entire output buffer is cleared
/// before the error is returned.
pub fn decode_slice_wrapped_clear_tail(
&self,
input: &[u8],
output: &mut [u8],
wrap: LineWrap,
) -> Result<usize, DecodeError> {
let written = match self.decode_slice_wrapped(input, output, wrap) {
Ok(written) => written,
Err(err) => {
wipe_bytes(output);
return Err(err);
}
};
wipe_tail(output, written);
Ok(written)
}
/// Decodes `input` using a strict line-wrapped profile into a stack-backed
/// buffer.
///
/// The wrapped profile accepts only the configured line ending. Non-final
/// lines must contain exactly `wrap.line_len` encoded bytes; the final line
/// may be shorter. A single trailing line ending after the final line is
/// accepted. If decoding fails, the internal backing array is cleared
/// before the error is returned.
pub fn decode_wrapped_buffer<const CAP: usize>(
&self,
input: &[u8],
wrap: LineWrap,
) -> Result<DecodedBuffer<CAP>, DecodeError> {
let mut output = DecodedBuffer::new();
let written =
match self.decode_slice_wrapped_clear_tail(input, output.as_mut_capacity(), wrap) {
Ok(written) => written,
Err(err) => {
output.clear();
return Err(err);
}
};
output.set_filled(written)?;
Ok(output)
}
/// Decodes `input` into a newly allocated byte vector.
///
/// This is strict decoding with the same semantics as [`Self::decode_slice`].
#[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> {
let required = validate_decode::<A, PAD>(input)?;
let mut output = alloc::vec![0; required];
let written = match self.decode_slice(input, &mut output) {
Ok(written) => written,
Err(err) => {
wipe_bytes(&mut output);
return Err(err);
}
};
output.truncate(written);
Ok(output)
}
/// Decodes `input` into a redacted owned secret buffer.
///
/// On malformed input, the intermediate output buffer is cleared before the
/// error is returned by [`Self::decode_vec`].
#[cfg(feature = "alloc")]
pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
self.decode_vec(input).map(SecretBuffer::from_vec)
}
/// Decodes `input` into a newly allocated byte vector using the explicit
/// legacy whitespace profile.
#[cfg(feature = "alloc")]
#[must_use = "for secret-bearing payloads use decode_secret_legacy, which returns a redacted buffer with drop-time cleanup"]
pub fn decode_vec_legacy(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
let required = validate_legacy_decode::<A, PAD>(input)?;
let mut output = alloc::vec![0; required];
let written = match self.decode_slice_legacy(input, &mut output) {
Ok(written) => written,
Err(err) => {
wipe_bytes(&mut output);
return Err(err);
}
};
output.truncate(written);
Ok(output)
}
/// Decodes `input` into a redacted owned secret buffer using the explicit
/// legacy whitespace profile.
///
/// ASCII space, tab, carriage return, and line feed bytes are ignored.
/// Alphabet selection, padding placement, trailing data after padding, and
/// non-canonical trailing bits remain strict.
#[cfg(feature = "alloc")]
pub fn decode_secret_legacy(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
self.decode_vec_legacy(input).map(SecretBuffer::from_vec)
}
/// Decodes line-wrapped input into a newly allocated byte vector.
#[cfg(feature = "alloc")]
#[must_use = "for secret-bearing payloads use decode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
pub fn decode_wrapped_vec(
&self,
input: &[u8],
wrap: LineWrap,
) -> Result<alloc::vec::Vec<u8>, DecodeError> {
let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
let mut output = alloc::vec![0; required];
let written = match self.decode_slice_wrapped(input, &mut output, wrap) {
Ok(written) => written,
Err(err) => {
wipe_bytes(&mut output);
return Err(err);
}
};
output.truncate(written);
Ok(output)
}
/// Decodes line-wrapped input into a redacted owned secret buffer.
///
/// The wrapped profile accepts only the configured line ending. Non-final
/// lines must contain exactly `wrap.line_len` encoded bytes; the final line
/// may be shorter. A single trailing line ending after the final line is
/// accepted.
#[cfg(feature = "alloc")]
pub fn decode_wrapped_secret(
&self,
input: &[u8],
wrap: LineWrap,
) -> Result<SecretBuffer, DecodeError> {
self.decode_wrapped_vec(input, wrap)
.map(SecretBuffer::from_vec)
}
}