base64_ng/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]
2#![deny(unsafe_code)]
3#![deny(missing_docs)]
4#![deny(clippy::all)]
5#![deny(clippy::pedantic)]
6#![allow(clippy::missing_errors_doc)]
7
8//! `base64-ng` is a `no_std`-first Base64 encoder and decoder.
9//!
10//! This initial release provides strict scalar RFC 4648-style behavior and
11//! caller-owned output buffers. Future SIMD fast paths, including AVX, NEON,
12//! and wasm `simd128` candidates, will be required to match this scalar module
13//! byte-for-byte.
14//!
15//! # Examples
16//!
17//! Encode and decode with caller-owned buffers:
18//!
19//! ```
20//! use base64_ng::{STANDARD, checked_encoded_len};
21//!
22//! let input = b"hello";
23//! const ENCODED_CAPACITY: usize = match checked_encoded_len(5, true) {
24//! Some(len) => len,
25//! None => panic!("encoded length overflow"),
26//! };
27//! let mut encoded = [0u8; ENCODED_CAPACITY];
28//! let encoded_len = STANDARD.encode_slice(input, &mut encoded).unwrap();
29//! assert_eq!(&encoded[..encoded_len], b"aGVsbG8=");
30//!
31//! let mut decoded = [0u8; 5];
32//! let decoded_len = STANDARD.decode_slice(&encoded, &mut decoded).unwrap();
33//! assert_eq!(&decoded[..decoded_len], input);
34//! ```
35//!
36//! Use the URL-safe no-padding engine:
37//!
38//! ```
39//! use base64_ng::URL_SAFE_NO_PAD;
40//!
41//! let mut encoded = [0u8; 3];
42//! let encoded_len = URL_SAFE_NO_PAD.encode_slice(b"\xfb\xff", &mut encoded).unwrap();
43//! assert_eq!(&encoded[..encoded_len], b"-_8");
44//! ```
45//!
46//! # Sensitive Decode Policy
47//!
48//! The default engines such as [`STANDARD`] and [`URL_SAFE_NO_PAD`] are strict
49//! scalar encoders/decoders with localized diagnostics. They are not
50//! constant-time token validators or key-material decoders: strict decode and
51//! validation may branch or return early based on malformed input, and strict
52//! [`DecodeError`] values can include input-derived bytes and indexes. Do not
53//! log strict decode errors verbatim for secret-bearing input; log
54//! [`DecodeError::kind`] instead. Use [`ct::STANDARD`],
55//! [`ct::URL_SAFE_NO_PAD`], or [`Engine::ct_decoder`] for secret-bearing
56//! payloads where decode timing posture matters more than exact error indexes.
57//!
58//! Recommended heap-owning pattern for secret-bearing standard Base64:
59//!
60//! ```
61//! # #[cfg(feature = "alloc")]
62//! # {
63//! use base64_ng::ct;
64//!
65//! let expected = b"session-key";
66//! let decoded = ct::STANDARD.decode_secret(b"c2Vzc2lvbi1rZXk=").unwrap();
67//!
68//! assert!(decoded.constant_time_eq_public_len(expected));
69//! # }
70//! ```
71//!
72//! For shared-memory, enclave-adjacent, HSM-style, or multi-principal
73//! deployments where even transient writes into caller-owned output are
74//! unacceptable, use [`ct::CtEngine::decode_slice_staged_clear_tail`] with a
75//! private staging buffer.
76//!
77//! # Zeroization Caveat
78//!
79//! Cleanup APIs and redacted buffers use dependency-free best-effort wiping:
80//! byte-wise volatile zero writes followed by an architecture-gated inline
81//! assembly barrier plus a hardware store-ordering fence where stable Rust
82//! supports it, and a compiler fence on all targets. This resists common
83//! compiler dead-store elimination and orders the issued zero stores on native
84//! supported architectures, but it is not a formal zeroization guarantee and
85//! cannot clear historical copies, registers, cache lines, write buffers, swap,
86//! hibernation images, core dumps, cold-boot remanence, or OS-level memory
87//! snapshots.
88//! High-assurance applications should apply their own approved zeroization
89//! policy to caller-owned buffers at the protocol boundary. Architectures
90//! without a native wipe barrier fail closed by default unless
91//! `allow-compiler-fence-only-wipe` is enabled after platform review. On
92//! `wasm32`, the wipe barrier is compiler-fence-only and cannot constrain
93//! downstream wasm runtime JITs. For that reason, `wasm32` builds fail closed
94//! by default. Enable `allow-wasm32-best-effort-wipe` only when the deployment
95//! explicitly accepts compiler-fence-only cleanup and applies its own memory
96//! strategy.
97
98#[cfg(feature = "alloc")]
99extern crate alloc;
100
101#[cfg(all(target_arch = "wasm32", not(feature = "allow-wasm32-best-effort-wipe")))]
102compile_error!(
103 "base64-ng: wasm32 builds use a compiler-fence-only wipe barrier that cannot \
104 constrain downstream wasm runtime JITs. Enable \
105 `allow-wasm32-best-effort-wipe` to accept this limitation and use \
106 caller-owned, platform-approved zeroization for high-assurance wasm deployments."
107);
108
109#[cfg(all(
110 not(miri),
111 not(feature = "allow-compiler-fence-only-wipe"),
112 not(any(
113 target_arch = "aarch64",
114 target_arch = "arm",
115 target_arch = "riscv32",
116 target_arch = "riscv64",
117 target_arch = "wasm32",
118 target_arch = "x86",
119 target_arch = "x86_64",
120 ))
121))]
122compile_error!(
123 "base64-ng: this architecture has no native hardware wipe barrier in \
124 base64-ng. Enable `allow-compiler-fence-only-wipe` only after reviewing \
125 docs/UNSAFE.md and applying platform-approved memory hygiene controls."
126);
127
128mod alphabet;
129mod buffers;
130mod cleanup;
131pub mod ct;
132mod errors;
133mod length;
134mod profiles;
135mod scalar;
136mod wrap;
137
138pub use alphabet::{
139 Alphabet, AlphabetError, Bcrypt, Crypt, Standard, UrlSafe, decode_alphabet_byte,
140 validate_alphabet,
141};
142pub(crate) use alphabet::{encode_base64_value, encode_base64_value_runtime};
143pub use buffers::{DecodedBuffer, EncodedBuffer, ExposedDecodedArray, ExposedEncodedArray};
144#[cfg(feature = "alloc")]
145pub use buffers::{ExposedSecretString, ExposedSecretVec, SecretBuffer};
146pub(crate) use cleanup::{wipe_bytes, wipe_tail};
147#[cfg(feature = "alloc")]
148pub(crate) use cleanup::{wipe_vec_all, wipe_vec_spare_capacity};
149pub(crate) use ct::{
150 constant_time_eq_fixed_width_array, constant_time_eq_public_len, ct_mask_eq_u8, ct_mask_lt_u8,
151};
152#[cfg(test)]
153pub(crate) use ct::{ct_padded_final_quantum, report_ct_error};
154pub use errors::{DecodeError, DecodeErrorKind, EncodeError};
155pub use length::{
156 LineEnding, LineWrap, checked_encoded_len, checked_wrapped_encoded_len, decoded_capacity,
157 decoded_len, encoded_len, wrapped_encoded_len,
158};
159pub(crate) use length::{decoded_len_padded, decoded_len_unpadded};
160pub use profiles::{BCRYPT, CRYPT, MIME, PEM, PEM_CRLF, Profile};
161#[cfg(kani)]
162pub(crate) use scalar::decode_byte;
163pub(crate) use scalar::{
164 decode_chunk, decode_tail_unpadded, read_quad, validate_chunk, validate_decode,
165 validate_tail_unpadded,
166};
167pub(crate) use wrap::{
168 compact_wrapped_input, decode_legacy_to_slice, decode_wrapped_to_slice, is_legacy_whitespace,
169 validate_legacy_decode, validate_wrapped_decode, write_wrapped_byte, write_wrapped_bytes,
170};
171
172#[cfg(feature = "simd")]
173mod simd;
174
175/// Runtime backend reporting for security-sensitive deployments.
176///
177/// This module does not enable acceleration. It exposes the backend posture so
178/// callers can log, assert, or audit whether execution is scalar-only or merely
179/// detecting future SIMD candidates.
180pub mod runtime;
181
182#[cfg(feature = "stream")]
183pub mod stream;
184
185/// Standard Base64 engine with padding.
186///
187/// This default strict engine is not a constant-time token validator or
188/// key-material decoder. Use [`ct::STANDARD`] or [`Engine::ct_decoder`] for the
189/// matching constant-time-oriented decoder when timing posture matters.
190#[doc(alias = "ct")]
191#[doc(alias = "constant_time")]
192#[doc(alias = "sensitive")]
193pub const STANDARD: Engine<Standard, true> = Engine::new();
194
195/// Standard Base64 engine without padding.
196///
197/// This default strict engine is not a constant-time token validator or
198/// key-material decoder. Use [`ct::STANDARD_NO_PAD`] or [`Engine::ct_decoder`]
199/// for the matching constant-time-oriented decoder when timing posture
200/// matters.
201#[doc(alias = "ct")]
202#[doc(alias = "constant_time")]
203#[doc(alias = "sensitive")]
204pub const STANDARD_NO_PAD: Engine<Standard, false> = Engine::new();
205
206/// URL-safe Base64 engine with padding.
207///
208/// This default strict engine is not a constant-time token validator or
209/// key-material decoder. Use [`ct::URL_SAFE`] or [`Engine::ct_decoder`] for the
210/// matching constant-time-oriented decoder when timing posture matters.
211#[doc(alias = "ct")]
212#[doc(alias = "constant_time")]
213#[doc(alias = "sensitive")]
214pub const URL_SAFE: Engine<UrlSafe, true> = Engine::new();
215
216/// URL-safe Base64 engine without padding.
217///
218/// This default strict engine is not a constant-time token validator or
219/// key-material decoder. Use [`ct::URL_SAFE_NO_PAD`] or [`Engine::ct_decoder`]
220/// for the matching constant-time-oriented decoder when timing posture
221/// matters.
222#[doc(alias = "ct")]
223#[doc(alias = "constant_time")]
224#[doc(alias = "sensitive")]
225pub const URL_SAFE_NO_PAD: Engine<UrlSafe, false> = Engine::new();
226
227/// bcrypt-style Base64 engine without padding.
228///
229/// This uses the bcrypt alphabet with the crate's normal Base64 bit packing.
230/// It does not parse complete bcrypt password-hash strings. This default strict
231/// engine is not a constant-time token validator or key-material decoder; use
232/// [`Engine::ct_decoder`] for the matching constant-time-oriented decoder when
233/// timing posture matters.
234#[doc(alias = "ct")]
235#[doc(alias = "constant_time")]
236#[doc(alias = "sensitive")]
237pub const BCRYPT_NO_PAD: Engine<Bcrypt, false> = Engine::new();
238
239/// Unix `crypt(3)`-style Base64 engine without padding.
240///
241/// This uses the `crypt(3)` alphabet with the crate's normal Base64 bit
242/// packing. It does not parse complete password-hash strings. This default
243/// strict engine is not a constant-time token validator or key-material
244/// decoder; use [`Engine::ct_decoder`] for the matching constant-time-oriented
245/// decoder when timing posture matters.
246#[doc(alias = "ct")]
247#[doc(alias = "constant_time")]
248#[doc(alias = "sensitive")]
249pub const CRYPT_NO_PAD: Engine<Crypt, false> = Engine::new();
250
251/// Encodes `input` as strict standard padded Base64.
252///
253/// This is a convenience wrapper around [`Engine::encode_string`] on
254/// [`STANDARD`] for callers migrating from simpler Base64 APIs. It requires
255/// the `alloc` feature because it returns an owned string.
256///
257/// # Examples
258///
259/// ```
260/// assert_eq!(base64_ng::encode(b"hello").unwrap(), "aGVsbG8=");
261/// ```
262#[cfg(feature = "alloc")]
263pub fn encode(input: &[u8]) -> Result<alloc::string::String, EncodeError> {
264 STANDARD.encode_string(input)
265}
266
267/// Decodes strict standard padded Base64 into an owned byte vector.
268///
269/// This is a convenience wrapper around [`Engine::decode_vec`] on
270/// [`STANDARD`].
271/// It uses the normal strict decoder, not the [`crate::ct`] module, and may
272/// branch or return early on malformed input. For secret-bearing payloads where
273/// malformed-input timing matters, use
274/// [`crate::ct::CtEngine::decode_secret`] through [`crate::ct::STANDARD`]
275/// instead.
276///
277/// # Examples
278///
279/// ```
280/// assert_eq!(base64_ng::decode("aGVsbG8=").unwrap(), b"hello");
281/// ```
282#[cfg(feature = "alloc")]
283#[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
284pub fn decode(input: impl AsRef<[u8]>) -> Result<alloc::vec::Vec<u8>, DecodeError> {
285 STANDARD.decode_vec(input.as_ref())
286}
287
288/// Compares two fixed-width byte arrays without a length-mismatch branch.
289///
290/// Use this helper when the value length itself should not be represented as a
291/// timing-distinct branch in the comparison API. The array length `N` is a
292/// compile-time public type fact, and the helper scans exactly `N` bytes before
293/// returning. The final equality result remains public. This is still a
294/// dependency-free, constant-time-oriented best-effort helper, not a formally
295/// verified cryptographic comparison primitive.
296///
297/// # Examples
298///
299/// ```
300/// use base64_ng::constant_time_eq_fixed_width;
301///
302/// assert!(constant_time_eq_fixed_width(b"token", b"token"));
303/// assert!(!constant_time_eq_fixed_width(b"token", b"Token"));
304/// ```
305#[must_use]
306pub fn constant_time_eq_fixed_width<const N: usize>(left: &[u8; N], right: &[u8; N]) -> bool {
307 constant_time_eq_fixed_width_array(left, right)
308}
309
310/// Compares two byte slices with a public length-mismatch branch.
311///
312/// Equal-length inputs are scanned fully before returning. Different lengths
313/// return `false` immediately because length is treated as public. This is a
314/// dependency-free, constant-time-oriented best-effort helper, not a formally
315/// verified cryptographic MAC, password, or bearer-token comparison primitive.
316///
317/// # Security
318///
319/// This helper is intended to avoid ordinary early-exit equality on values
320/// whose length is public. It is not a formal constant-time guarantee and
321/// should not be the sole primitive admitted at MAC, password, or bearer-token
322/// protocol boundaries in high-assurance systems. Use a reviewed comparison
323/// primitive at that boundary when your dependency policy allows one.
324///
325/// # Examples
326///
327/// ```
328/// assert!(base64_ng::constant_time_eq(b"token", b"token"));
329/// assert!(!base64_ng::constant_time_eq(b"token", b"Token"));
330/// assert!(!base64_ng::constant_time_eq(b"token", b"token2"));
331/// ```
332#[must_use]
333pub fn constant_time_eq(left: &[u8], right: &[u8]) -> bool {
334 constant_time_eq_public_len(left, right)
335}
336
337/// A zero-sized Base64 engine parameterized by alphabet and padding policy.
338pub struct Engine<A, const PAD: bool> {
339 alphabet: core::marker::PhantomData<A>,
340}
341
342impl<A, const PAD: bool> Clone for Engine<A, PAD> {
343 fn clone(&self) -> Self {
344 *self
345 }
346}
347
348impl<A, const PAD: bool> Copy for Engine<A, PAD> {}
349
350impl<A, const PAD: bool> core::fmt::Debug for Engine<A, PAD> {
351 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
352 formatter
353 .debug_struct("Engine")
354 .field("padded", &PAD)
355 .finish()
356 }
357}
358
359impl<A, const PAD: bool> core::fmt::Display for Engine<A, PAD> {
360 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
361 write!(formatter, "padded={PAD}")
362 }
363}
364
365impl<A, const PAD: bool> Default for Engine<A, PAD> {
366 fn default() -> Self {
367 Self {
368 alphabet: core::marker::PhantomData,
369 }
370 }
371}
372
373impl<A, const PAD: bool> Eq for Engine<A, PAD> {}
374
375impl<A, const PAD: bool> PartialEq for Engine<A, PAD> {
376 fn eq(&self, _other: &Self) -> bool {
377 true
378 }
379}
380
381impl<A, const PAD: bool> Engine<A, PAD>
382where
383 A: Alphabet,
384{
385 /// Creates a new engine value.
386 #[must_use]
387 pub const fn new() -> Self {
388 Self {
389 alphabet: core::marker::PhantomData,
390 }
391 }
392
393 /// Returns whether this engine uses padded Base64.
394 #[must_use]
395 pub const fn is_padded(&self) -> bool {
396 PAD
397 }
398
399 /// Returns this engine as an unwrapped profile.
400 ///
401 /// Use [`Profile::new`] or [`Profile::checked_new`] when a strict
402 /// line-wrapping policy should travel with the profile.
403 #[must_use]
404 pub const fn profile(&self) -> Profile<A, PAD> {
405 Profile::new(*self, None)
406 }
407
408 /// Returns the matching constant-time-oriented decoder for this engine's
409 /// alphabet and padding policy.
410 ///
411 /// The returned decoder is still an explicit opt-in to the [`ct`] module's
412 /// slower, opaque-error, constant-time-oriented scalar path.
413 #[must_use]
414 pub const fn ct_decoder(&self) -> ct::CtEngine<A, PAD> {
415 ct::CtEngine::new()
416 }
417
418 /// Wraps a `std::io::Write` value in a streaming Base64 encoder.
419 ///
420 /// This is a convenience constructor for [`stream::Encoder::new`] that
421 /// keeps the selected engine attached to the call site.
422 ///
423 /// ```
424 /// use std::io::Write;
425 /// use base64_ng::STANDARD;
426 ///
427 /// let mut encoder = STANDARD.encoder_writer(Vec::new());
428 /// encoder.write_all(b"hello").unwrap();
429 /// assert_eq!(encoder.finish().unwrap(), b"aGVsbG8=");
430 /// ```
431 #[cfg(feature = "stream")]
432 #[must_use]
433 pub fn encoder_writer<W>(&self, inner: W) -> stream::Encoder<W, A, PAD> {
434 stream::Encoder::new(inner, *self)
435 }
436
437 /// Wraps a `std::io::Write` value in a streaming Base64 decoder.
438 ///
439 /// This is a convenience constructor for [`stream::Decoder::new`] that
440 /// keeps the selected engine attached to the call site.
441 ///
442 /// ```
443 /// use std::io::Write;
444 /// use base64_ng::STANDARD;
445 ///
446 /// let mut decoder = STANDARD.decoder_writer(Vec::new());
447 /// decoder.write_all(b"aGVsbG8=").unwrap();
448 /// assert_eq!(decoder.finish().unwrap(), b"hello");
449 /// ```
450 ///
451 /// # Security
452 ///
453 /// Streaming decoders use the normal strict decode path, not the
454 /// [`crate::ct`] module. Do not use this adapter for secret-bearing
455 /// payloads when malformed-input timing matters.
456 #[cfg(feature = "stream")]
457 #[must_use]
458 pub fn decoder_writer<W>(&self, inner: W) -> stream::Decoder<W, A, PAD> {
459 stream::Decoder::new(inner, *self)
460 }
461
462 /// Wraps a `std::io::Read` value in a streaming Base64 encoder.
463 ///
464 /// This is a convenience constructor for [`stream::EncoderReader::new`]
465 /// that keeps the selected engine attached to the call site.
466 ///
467 /// ```
468 /// use std::io::Read;
469 /// use base64_ng::STANDARD;
470 ///
471 /// let mut reader = STANDARD.encoder_reader(&b"hello"[..]);
472 /// let mut encoded = String::new();
473 /// reader.read_to_string(&mut encoded).unwrap();
474 /// assert_eq!(encoded, "aGVsbG8=");
475 /// ```
476 #[cfg(feature = "stream")]
477 #[must_use]
478 pub fn encoder_reader<R>(&self, inner: R) -> stream::EncoderReader<R, A, PAD> {
479 stream::EncoderReader::new(inner, *self)
480 }
481
482 /// Wraps a `std::io::Read` value in a streaming Base64 decoder.
483 ///
484 /// This is a convenience constructor for [`stream::DecoderReader::new`]
485 /// that keeps the selected engine attached to the call site.
486 ///
487 /// ```
488 /// use std::io::Read;
489 /// use base64_ng::STANDARD;
490 ///
491 /// let mut reader = STANDARD.decoder_reader(&b"aGVsbG8="[..]);
492 /// let mut decoded = Vec::new();
493 /// reader.read_to_end(&mut decoded).unwrap();
494 /// assert_eq!(decoded, b"hello");
495 /// ```
496 ///
497 /// # Security
498 ///
499 /// Streaming decoder readers use the normal strict decode path, not the
500 /// [`crate::ct`] module. Do not use this adapter for secret-bearing
501 /// payloads when malformed-input timing matters.
502 #[cfg(feature = "stream")]
503 #[must_use]
504 pub fn decoder_reader<R>(&self, inner: R) -> stream::DecoderReader<R, A, PAD> {
505 stream::DecoderReader::new(inner, *self)
506 }
507
508 /// Returns the encoded length for this engine's padding policy.
509 pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
510 encoded_len(input_len, PAD)
511 }
512
513 /// Returns the encoded length for this engine, or `None` on overflow.
514 #[must_use]
515 pub const fn checked_encoded_len(&self, input_len: usize) -> Option<usize> {
516 checked_encoded_len(input_len, PAD)
517 }
518
519 /// Returns the encoded length after applying a line wrapping policy.
520 ///
521 /// The returned length includes inserted line endings but does not include
522 /// a trailing line ending after the final encoded line.
523 pub const fn wrapped_encoded_len(
524 &self,
525 input_len: usize,
526 wrap: LineWrap,
527 ) -> Result<usize, EncodeError> {
528 wrapped_encoded_len(input_len, PAD, wrap)
529 }
530
531 /// Returns the encoded length after line wrapping, or `None` on overflow or
532 /// invalid line wrapping.
533 #[must_use]
534 pub const fn checked_wrapped_encoded_len(
535 &self,
536 input_len: usize,
537 wrap: LineWrap,
538 ) -> Option<usize> {
539 checked_wrapped_encoded_len(input_len, PAD, wrap)
540 }
541
542 /// Returns the exact decoded length implied by input length and padding.
543 ///
544 /// This validates padding placement and impossible lengths, but it does not
545 /// validate alphabet membership or non-canonical trailing bits.
546 pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
547 decoded_len(input, PAD)
548 }
549
550 /// Returns the exact decoded length for the explicit legacy profile.
551 ///
552 /// The legacy profile ignores ASCII space, tab, carriage return, and line
553 /// feed bytes before applying the same alphabet, padding, and canonical-bit
554 /// checks as strict decoding.
555 pub fn decoded_len_legacy(&self, input: &[u8]) -> Result<usize, DecodeError> {
556 validate_legacy_decode::<A, PAD>(input)
557 }
558
559 /// Returns the exact decoded length for a line-wrapped profile.
560 ///
561 /// The wrapped profile accepts only the configured line ending. Non-final
562 /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
563 /// may be shorter. A single trailing line ending after the final line is
564 /// accepted.
565 pub fn decoded_len_wrapped(&self, input: &[u8], wrap: LineWrap) -> Result<usize, DecodeError> {
566 validate_wrapped_decode::<A, PAD>(input, wrap)
567 }
568
569 /// Validates strict Base64 input without writing decoded bytes.
570 ///
571 /// This applies the same alphabet, padding, and canonical-bit checks as
572 /// [`Self::decode_slice`]. Use this method when malformed-input
573 /// diagnostics matter; use [`Self::validate`] when a boolean is enough.
574 /// This default validator is not constant-time; use
575 /// [`crate::ct::CtEngine::validate_result`] through [`Self::ct_decoder`]
576 /// for secret-bearing payloads where timing posture matters.
577 ///
578 /// # Examples
579 ///
580 /// ```
581 /// use base64_ng::STANDARD;
582 ///
583 /// STANDARD.validate_result(b"aGVsbG8=").unwrap();
584 /// assert!(STANDARD.validate_result(b"aGVsbG8").is_err());
585 /// ```
586 pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
587 validate_decode::<A, PAD>(input).map(|_| ())
588 }
589
590 /// Returns whether `input` is valid strict Base64 for this engine.
591 ///
592 /// This is a convenience wrapper around [`Self::validate_result`] and is
593 /// not constant-time. Use [`crate::ct::CtEngine::validate`] through
594 /// [`Self::ct_decoder`] for secret-bearing payloads where timing posture
595 /// matters.
596 ///
597 /// # Examples
598 ///
599 /// ```
600 /// use base64_ng::URL_SAFE_NO_PAD;
601 ///
602 /// assert!(URL_SAFE_NO_PAD.validate(b"-_8"));
603 /// assert!(!URL_SAFE_NO_PAD.validate(b"+/8"));
604 /// ```
605 #[must_use]
606 pub fn validate(&self, input: &[u8]) -> bool {
607 self.validate_result(input).is_ok()
608 }
609
610 /// Validates input using the explicit legacy whitespace profile.
611 ///
612 /// ASCII space, tab, carriage return, and line feed bytes are ignored
613 /// before applying the same alphabet, padding, and canonical-bit checks as
614 /// strict decoding.
615 ///
616 /// # Examples
617 ///
618 /// ```
619 /// use base64_ng::STANDARD;
620 ///
621 /// STANDARD.validate_legacy_result(b" aG\r\nVsbG8= ").unwrap();
622 /// assert!(STANDARD.validate_legacy_result(b" aG-=").is_err());
623 /// ```
624 pub fn validate_legacy_result(&self, input: &[u8]) -> Result<(), DecodeError> {
625 validate_legacy_decode::<A, PAD>(input).map(|_| ())
626 }
627
628 /// Returns whether `input` is valid for the explicit legacy whitespace
629 /// profile.
630 ///
631 /// This is a convenience wrapper around [`Self::validate_legacy_result`].
632 ///
633 /// # Examples
634 ///
635 /// ```
636 /// use base64_ng::STANDARD;
637 ///
638 /// assert!(STANDARD.validate_legacy(b" aG\r\nVsbG8= "));
639 /// assert!(!STANDARD.validate_legacy(b"aG-V"));
640 /// ```
641 #[must_use]
642 pub fn validate_legacy(&self, input: &[u8]) -> bool {
643 self.validate_legacy_result(input).is_ok()
644 }
645
646 /// Validates input using a strict line-wrapped profile.
647 ///
648 /// This is stricter than [`Self::validate_legacy_result`]: it accepts only
649 /// the configured line ending and enforces the configured line length for
650 /// every non-final line.
651 ///
652 /// # Examples
653 ///
654 /// ```
655 /// use base64_ng::{LineEnding, LineWrap, STANDARD};
656 ///
657 /// let wrap = LineWrap::new(4, LineEnding::Lf);
658 /// STANDARD.validate_wrapped_result(b"aGVs\nbG8=", wrap).unwrap();
659 /// assert!(STANDARD.validate_wrapped_result(b"aG\nVsbG8=", wrap).is_err());
660 /// ```
661 pub fn validate_wrapped_result(&self, input: &[u8], wrap: LineWrap) -> Result<(), DecodeError> {
662 validate_wrapped_decode::<A, PAD>(input, wrap).map(|_| ())
663 }
664
665 /// Returns whether `input` is valid for a strict line-wrapped profile.
666 ///
667 /// This is a convenience wrapper around [`Self::validate_wrapped_result`].
668 ///
669 /// # Examples
670 ///
671 /// ```
672 /// use base64_ng::{LineEnding, LineWrap, STANDARD};
673 ///
674 /// let wrap = LineWrap::new(4, LineEnding::Lf);
675 /// assert!(STANDARD.validate_wrapped(b"aGVs\nbG8=", wrap));
676 /// assert!(!STANDARD.validate_wrapped(b"aG\nVsbG8=", wrap));
677 /// ```
678 #[must_use]
679 pub fn validate_wrapped(&self, input: &[u8], wrap: LineWrap) -> bool {
680 self.validate_wrapped_result(input, wrap).is_ok()
681 }
682
683 /// Encodes a fixed-size input into a fixed-size output array in const contexts.
684 ///
685 /// Stable Rust does not yet allow this API to return an array whose length
686 /// is computed from `INPUT_LEN` directly. Instead, the caller supplies the
687 /// output length through the destination type and this function panics
688 /// during const evaluation if the length is wrong.
689 ///
690 /// # Panics
691 ///
692 /// Panics if `OUTPUT_LEN` is not exactly the encoded length for `INPUT_LEN`
693 /// and this engine's padding policy, or if that length overflows `usize`.
694 ///
695 /// # Examples
696 ///
697 /// ```
698 /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
699 ///
700 /// const HELLO: [u8; 8] = STANDARD.encode_array(b"hello");
701 /// const URL_SAFE: [u8; 3] = URL_SAFE_NO_PAD.encode_array(b"\xfb\xff");
702 ///
703 /// assert_eq!(&HELLO, b"aGVsbG8=");
704 /// assert_eq!(&URL_SAFE, b"-_8");
705 /// ```
706 ///
707 /// Incorrect output lengths fail during const evaluation:
708 ///
709 /// ```compile_fail
710 /// use base64_ng::STANDARD;
711 ///
712 /// const TOO_SHORT: [u8; 7] = STANDARD.encode_array(b"hello");
713 /// ```
714 #[must_use]
715 pub const fn encode_array<const INPUT_LEN: usize, const OUTPUT_LEN: usize>(
716 &self,
717 input: &[u8; INPUT_LEN],
718 ) -> [u8; OUTPUT_LEN] {
719 let Some(required) = checked_encoded_len(INPUT_LEN, PAD) else {
720 panic!("encoded base64 length overflows usize");
721 };
722 assert!(
723 required == OUTPUT_LEN,
724 "base64 output array has incorrect length"
725 );
726
727 let mut output = [0u8; OUTPUT_LEN];
728 let mut read = 0;
729 let mut write = 0;
730 while INPUT_LEN - read >= 3 {
731 let b0 = input[read];
732 let b1 = input[read + 1];
733 let b2 = input[read + 2];
734
735 output[write] = encode_base64_value::<A>(b0 >> 2);
736 output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
737 output[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
738 output[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
739
740 read += 3;
741 write += 4;
742 }
743
744 match INPUT_LEN - read {
745 0 => {}
746 1 => {
747 let b0 = input[read];
748 output[write] = encode_base64_value::<A>(b0 >> 2);
749 output[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
750 write += 2;
751 if PAD {
752 output[write] = b'=';
753 output[write + 1] = b'=';
754 }
755 }
756 2 => {
757 let b0 = input[read];
758 let b1 = input[read + 1];
759 output[write] = encode_base64_value::<A>(b0 >> 2);
760 output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
761 output[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
762 if PAD {
763 output[write + 3] = b'=';
764 }
765 }
766 _ => unreachable!(),
767 }
768
769 output
770 }
771
772 /// Encodes `input` into `output`, returning the number of bytes written.
773 pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
774 scalar::encode_slice::<A, PAD>(input, output)
775 }
776
777 /// Encodes `input` into `output` with line wrapping.
778 ///
779 /// The wrapping policy inserts line endings between encoded lines and does
780 /// not append a trailing line ending after the final line.
781 ///
782 /// # Examples
783 ///
784 /// ```
785 /// use base64_ng::{LineEnding, LineWrap, STANDARD};
786 ///
787 /// let wrap = LineWrap::new(4, LineEnding::Lf);
788 /// let mut output = [0u8; 9];
789 /// let written = STANDARD
790 /// .encode_slice_wrapped(b"hello", &mut output, wrap)
791 /// .unwrap();
792 ///
793 /// assert_eq!(&output[..written], b"aGVs\nbG8=");
794 /// ```
795 pub fn encode_slice_wrapped(
796 &self,
797 input: &[u8],
798 output: &mut [u8],
799 wrap: LineWrap,
800 ) -> Result<usize, EncodeError> {
801 let required = self.wrapped_encoded_len(input.len(), wrap)?;
802 if output.len() < required {
803 return Err(EncodeError::OutputTooSmall {
804 required,
805 available: output.len(),
806 });
807 }
808
809 let encoded_len =
810 checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
811 if encoded_len == 0 {
812 return Ok(0);
813 }
814
815 // If the temporary in-buffer layout size overflows, fall back to the
816 // fixed scratch buffer path rather than relying on saturated arithmetic.
817 let combined_required = match required.checked_add(encoded_len) {
818 Some(len) => len,
819 None => usize::MAX,
820 };
821 if output.len() < combined_required {
822 let mut scratch = [0u8; 1024];
823 let mut input_offset = 0;
824 let mut output_offset = 0;
825 let mut column = 0;
826
827 while input_offset < input.len() {
828 let remaining = input.len() - input_offset;
829 let mut take = remaining.min(768);
830 if remaining > take {
831 take -= take % 3;
832 }
833 if take == 0 {
834 take = remaining;
835 }
836
837 let encoded = match self
838 .encode_slice(&input[input_offset..input_offset + take], &mut scratch)
839 {
840 Ok(encoded) => encoded,
841 Err(err) => {
842 wipe_bytes(&mut scratch);
843 return Err(err);
844 }
845 };
846 if let Err(err) = write_wrapped_bytes(
847 &scratch[..encoded],
848 output,
849 &mut output_offset,
850 &mut column,
851 wrap,
852 ) {
853 wipe_bytes(&mut scratch);
854 return Err(err);
855 }
856 wipe_bytes(&mut scratch[..encoded]);
857 input_offset += take;
858 }
859
860 Ok(output_offset)
861 } else {
862 let encoded =
863 self.encode_slice(input, &mut output[required..required + encoded_len])?;
864 let mut output_offset = 0;
865 let mut column = 0;
866 let mut read = required;
867 while read < required + encoded {
868 let byte = output[read];
869 write_wrapped_byte(byte, output, &mut output_offset, &mut column, wrap)?;
870 read += 1;
871 }
872 wipe_bytes(&mut output[required..required + encoded]);
873 Ok(output_offset)
874 }
875 }
876
877 /// Encodes `input` with line wrapping and clears all bytes after the
878 /// encoded prefix.
879 ///
880 /// If encoding fails, the entire output buffer is cleared before the error
881 /// is returned.
882 pub fn encode_slice_wrapped_clear_tail(
883 &self,
884 input: &[u8],
885 output: &mut [u8],
886 wrap: LineWrap,
887 ) -> Result<usize, EncodeError> {
888 let written = match self.encode_slice_wrapped(input, output, wrap) {
889 Ok(written) => written,
890 Err(err) => {
891 wipe_bytes(output);
892 return Err(err);
893 }
894 };
895 wipe_tail(output, written);
896 Ok(written)
897 }
898
899 /// Encodes `input` with line wrapping into a stack-backed buffer.
900 ///
901 /// This is useful for MIME/PEM-style protocols where heap allocation is
902 /// unnecessary. If encoding fails, the internal backing array is cleared
903 /// before the error is returned.
904 pub fn encode_wrapped_buffer<const CAP: usize>(
905 &self,
906 input: &[u8],
907 wrap: LineWrap,
908 ) -> Result<EncodedBuffer<CAP>, EncodeError> {
909 let mut output = EncodedBuffer::new();
910 let written =
911 match self.encode_slice_wrapped_clear_tail(input, output.as_mut_capacity(), wrap) {
912 Ok(written) => written,
913 Err(err) => {
914 output.clear();
915 return Err(err);
916 }
917 };
918 output.set_filled(written)?;
919 Ok(output)
920 }
921
922 /// Encodes `input` with line wrapping into a newly allocated byte vector.
923 #[cfg(feature = "alloc")]
924 #[must_use = "for secret-bearing payloads use encode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
925 pub fn encode_wrapped_vec(
926 &self,
927 input: &[u8],
928 wrap: LineWrap,
929 ) -> Result<alloc::vec::Vec<u8>, EncodeError> {
930 let required = self.wrapped_encoded_len(input.len(), wrap)?;
931 let mut output = alloc::vec![0; required];
932 let written = self.encode_slice_wrapped(input, &mut output, wrap)?;
933 output.truncate(written);
934 Ok(output)
935 }
936
937 /// Encodes `input` with line wrapping into a newly allocated UTF-8 string.
938 #[cfg(feature = "alloc")]
939 pub fn encode_wrapped_string(
940 &self,
941 input: &[u8],
942 wrap: LineWrap,
943 ) -> Result<alloc::string::String, EncodeError> {
944 let output = self.encode_wrapped_vec(input, wrap)?;
945 match alloc::string::String::from_utf8(output) {
946 Ok(output) => Ok(output),
947 Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
948 }
949 }
950
951 /// Encodes `input` with line wrapping into a redacted owned secret buffer.
952 ///
953 /// This is useful when the wrapped encoded representation itself is
954 /// sensitive and should not be accidentally logged through formatting.
955 #[cfg(feature = "alloc")]
956 pub fn encode_wrapped_secret(
957 &self,
958 input: &[u8],
959 wrap: LineWrap,
960 ) -> Result<SecretBuffer, EncodeError> {
961 self.encode_wrapped_vec(input, wrap)
962 .map(SecretBuffer::from_vec)
963 }
964
965 /// Encodes `input` into `output` and clears all bytes after the encoded
966 /// prefix.
967 ///
968 /// If encoding fails, the entire output buffer is cleared before the error
969 /// is returned.
970 ///
971 /// # Examples
972 ///
973 /// ```
974 /// use base64_ng::STANDARD;
975 ///
976 /// let mut output = [0xff; 12];
977 /// let written = STANDARD
978 /// .encode_slice_clear_tail(b"hello", &mut output)
979 /// .unwrap();
980 ///
981 /// assert_eq!(&output[..written], b"aGVsbG8=");
982 /// assert!(output[written..].iter().all(|byte| *byte == 0));
983 /// ```
984 pub fn encode_slice_clear_tail(
985 &self,
986 input: &[u8],
987 output: &mut [u8],
988 ) -> Result<usize, EncodeError> {
989 let written = match self.encode_slice(input, output) {
990 Ok(written) => written,
991 Err(err) => {
992 wipe_bytes(output);
993 return Err(err);
994 }
995 };
996 wipe_tail(output, written);
997 Ok(written)
998 }
999
1000 /// Encodes `input` into a stack-backed buffer.
1001 ///
1002 /// This helper is useful for short values where callers want the
1003 /// convenience of an owned result without enabling `alloc`.
1004 ///
1005 /// # Examples
1006 ///
1007 /// ```
1008 /// use base64_ng::STANDARD;
1009 ///
1010 /// let encoded = STANDARD.encode_buffer::<8>(b"hello").unwrap();
1011 ///
1012 /// assert_eq!(encoded.as_str(), "aGVsbG8=");
1013 /// ```
1014 pub fn encode_buffer<const CAP: usize>(
1015 &self,
1016 input: &[u8],
1017 ) -> Result<EncodedBuffer<CAP>, EncodeError> {
1018 let mut output = EncodedBuffer::new();
1019 let written = match self.encode_slice_clear_tail(input, output.as_mut_capacity()) {
1020 Ok(written) => written,
1021 Err(err) => {
1022 output.clear();
1023 return Err(err);
1024 }
1025 };
1026 output.set_filled(written)?;
1027 Ok(output)
1028 }
1029
1030 /// Encodes `input` into a newly allocated byte vector.
1031 #[cfg(feature = "alloc")]
1032 #[must_use = "for secret-bearing payloads use encode_secret, which returns a redacted buffer with drop-time cleanup"]
1033 pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
1034 let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
1035 let mut output = alloc::vec![0; required];
1036 let written = self.encode_slice(input, &mut output)?;
1037 output.truncate(written);
1038 Ok(output)
1039 }
1040
1041 /// Encodes `input` into a redacted owned secret buffer.
1042 ///
1043 /// This is useful when the encoded representation itself is sensitive and
1044 /// should not be accidentally logged through formatting.
1045 #[cfg(feature = "alloc")]
1046 pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
1047 self.encode_vec(input).map(SecretBuffer::from_vec)
1048 }
1049
1050 /// Encodes `input` into a newly allocated UTF-8 string.
1051 ///
1052 /// Base64 output is ASCII by construction. This helper is available with
1053 /// the `alloc` feature and has the same encoding semantics as
1054 /// [`Self::encode_slice`].
1055 ///
1056 /// # Examples
1057 ///
1058 /// ```
1059 /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
1060 ///
1061 /// assert_eq!(STANDARD.encode_string(b"hello").unwrap(), "aGVsbG8=");
1062 /// assert_eq!(URL_SAFE_NO_PAD.encode_string(b"\xfb\xff").unwrap(), "-_8");
1063 /// ```
1064 #[cfg(feature = "alloc")]
1065 pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
1066 let output = self.encode_vec(input)?;
1067 match alloc::string::String::from_utf8(output) {
1068 Ok(output) => Ok(output),
1069 Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
1070 }
1071 }
1072
1073 /// Encodes the first `input_len` bytes of `buffer` in place.
1074 ///
1075 /// The buffer must have enough spare capacity for the encoded output. The
1076 /// implementation writes from right to left, so unread input bytes are not
1077 /// overwritten before they are encoded.
1078 ///
1079 /// # Panics
1080 ///
1081 /// Panics only if an internal right-to-left encode invariant is violated.
1082 /// This indicates a bug in `base64-ng`; valid or malformed caller input is
1083 /// reported through [`EncodeError`] instead.
1084 ///
1085 /// # Examples
1086 ///
1087 /// ```
1088 /// use base64_ng::STANDARD;
1089 ///
1090 /// let mut buffer = [0u8; 8];
1091 /// buffer[..5].copy_from_slice(b"hello");
1092 /// let encoded = STANDARD.encode_in_place(&mut buffer, 5).unwrap();
1093 /// assert_eq!(encoded, b"aGVsbG8=");
1094 /// ```
1095 pub fn encode_in_place<'a>(
1096 &self,
1097 buffer: &'a mut [u8],
1098 input_len: usize,
1099 ) -> Result<&'a mut [u8], EncodeError> {
1100 if input_len > buffer.len() {
1101 return Err(EncodeError::InputTooLarge {
1102 input_len,
1103 buffer_len: buffer.len(),
1104 });
1105 }
1106
1107 let required = checked_encoded_len(input_len, PAD).ok_or(EncodeError::LengthOverflow)?;
1108 if buffer.len() < required {
1109 return Err(EncodeError::OutputTooSmall {
1110 required,
1111 available: buffer.len(),
1112 });
1113 }
1114
1115 let mut read = input_len;
1116 let mut write = required;
1117
1118 match input_len % 3 {
1119 0 => {}
1120 1 => {
1121 read -= 1;
1122 let b0 = buffer[read];
1123 if PAD {
1124 write -= 4;
1125 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
1126 buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
1127 buffer[write + 2] = b'=';
1128 buffer[write + 3] = b'=';
1129 } else {
1130 write -= 2;
1131 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
1132 buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
1133 }
1134 }
1135 2 => {
1136 read -= 2;
1137 let b0 = buffer[read];
1138 let b1 = buffer[read + 1];
1139 if PAD {
1140 write -= 4;
1141 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
1142 buffer[write + 1] =
1143 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
1144 buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
1145 buffer[write + 3] = b'=';
1146 } else {
1147 write -= 3;
1148 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
1149 buffer[write + 1] =
1150 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
1151 buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
1152 }
1153 }
1154 _ => unreachable!(),
1155 }
1156
1157 while read > 0 {
1158 read -= 3;
1159 write -= 4;
1160 let b0 = buffer[read];
1161 let b1 = buffer[read + 1];
1162 let b2 = buffer[read + 2];
1163
1164 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
1165 buffer[write + 1] =
1166 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
1167 buffer[write + 2] =
1168 encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
1169 buffer[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
1170 }
1171
1172 // The right-to-left loop consumes exactly three input bytes for every
1173 // four output bytes. If this invariant changes, returning a shifted
1174 // slice would silently corrupt the in-place output.
1175 assert_eq!(
1176 write, 0,
1177 "encode_in_place invariant violated: right-to-left loop did not complete"
1178 );
1179 Ok(&mut buffer[..required])
1180 }
1181
1182 /// Encodes the first `input_len` bytes of `buffer` in place and clears all
1183 /// bytes after the encoded prefix.
1184 ///
1185 /// If encoding fails because `input_len` is too large, the output buffer is
1186 /// too small, or the encoded length overflows `usize`, the entire buffer is
1187 /// cleared before the error is returned.
1188 ///
1189 /// # Examples
1190 ///
1191 /// ```
1192 /// use base64_ng::STANDARD;
1193 ///
1194 /// let mut buffer = [0xff; 12];
1195 /// buffer[..5].copy_from_slice(b"hello");
1196 /// let encoded = STANDARD.encode_in_place_clear_tail(&mut buffer, 5).unwrap();
1197 /// assert_eq!(encoded, b"aGVsbG8=");
1198 /// ```
1199 pub fn encode_in_place_clear_tail<'a>(
1200 &self,
1201 buffer: &'a mut [u8],
1202 input_len: usize,
1203 ) -> Result<&'a mut [u8], EncodeError> {
1204 let len = match self.encode_in_place(buffer, input_len) {
1205 Ok(encoded) => encoded.len(),
1206 Err(err) => {
1207 wipe_bytes(buffer);
1208 return Err(err);
1209 }
1210 };
1211 wipe_tail(buffer, len);
1212 Ok(&mut buffer[..len])
1213 }
1214
1215 /// Decodes `input` into `output`, returning the number of bytes written.
1216 ///
1217 /// This is strict decoding. Whitespace, mixed alphabets, malformed padding,
1218 /// and trailing non-padding data are rejected.
1219 ///
1220 /// # Security
1221 ///
1222 /// This default scalar decoder prioritizes strict validation, exact error
1223 /// reporting, and ordinary throughput. It may branch or return early based
1224 /// on byte validity, malformed input, padding position, and output
1225 /// capacity. It also reports exact failure positions and invalid byte
1226 /// values through [`DecodeError`]. Do not use this method for token
1227 /// comparison, key-material decoding, or secret-bearing validation where
1228 /// malformed-input timing matters. Do not log strict decode errors
1229 /// verbatim for secret-bearing input; log [`DecodeError::kind`] instead.
1230 /// Use [`crate::ct`],
1231 /// [`crate::ct::STANDARD`], [`crate::ct::URL_SAFE_NO_PAD`], or
1232 /// [`Self::ct_decoder`] with `decode_slice_clear_tail` for
1233 /// constant-time-oriented secret decoding.
1234 #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
1235 pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
1236 scalar::decode_slice::<A, PAD>(input, output)
1237 }
1238
1239 /// Decodes `input` into `output` and clears all bytes after the decoded
1240 /// prefix.
1241 ///
1242 /// If decoding fails, the entire output buffer is cleared before the error
1243 /// is returned.
1244 ///
1245 /// # Examples
1246 ///
1247 /// ```
1248 /// use base64_ng::STANDARD;
1249 ///
1250 /// let mut output = [0xff; 8];
1251 /// let written = STANDARD
1252 /// .decode_slice_clear_tail(b"aGk=", &mut output)
1253 /// .unwrap();
1254 ///
1255 /// assert_eq!(&output[..written], b"hi");
1256 /// assert!(output[written..].iter().all(|byte| *byte == 0));
1257 /// ```
1258 pub fn decode_slice_clear_tail(
1259 &self,
1260 input: &[u8],
1261 output: &mut [u8],
1262 ) -> Result<usize, DecodeError> {
1263 let written = match self.decode_slice(input, output) {
1264 Ok(written) => written,
1265 Err(err) => {
1266 wipe_bytes(output);
1267 return Err(err);
1268 }
1269 };
1270 wipe_tail(output, written);
1271 Ok(written)
1272 }
1273
1274 /// Decodes `input` into a stack-backed buffer.
1275 ///
1276 /// This helper is useful for short decoded values where callers want the
1277 /// convenience of an owned result without enabling `alloc`.
1278 ///
1279 /// # Examples
1280 ///
1281 /// ```
1282 /// use base64_ng::STANDARD;
1283 ///
1284 /// let decoded = STANDARD.decode_buffer::<5>(b"aGVsbG8=").unwrap();
1285 ///
1286 /// assert_eq!(decoded.as_bytes(), b"hello");
1287 /// ```
1288 pub fn decode_buffer<const CAP: usize>(
1289 &self,
1290 input: &[u8],
1291 ) -> Result<DecodedBuffer<CAP>, DecodeError> {
1292 let mut output = DecodedBuffer::new();
1293 let written = match self.decode_slice_clear_tail(input, output.as_mut_capacity()) {
1294 Ok(written) => written,
1295 Err(err) => {
1296 output.clear();
1297 return Err(err);
1298 }
1299 };
1300 output.set_filled(written)?;
1301 Ok(output)
1302 }
1303
1304 /// Decodes `input` using the explicit legacy whitespace profile.
1305 ///
1306 /// ASCII space, tab, carriage return, and line feed bytes are ignored.
1307 /// Alphabet selection, padding placement, trailing data after padding, and
1308 /// non-canonical trailing bits remain strict.
1309 ///
1310 /// # Security
1311 ///
1312 /// This method uses the normal strict decode path after legacy whitespace
1313 /// handling. It may branch or return early based on malformed input and is
1314 /// not a constant-time token validator or key-material decoder. Use
1315 /// [`crate::ct`] for secret-bearing payloads.
1316 #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
1317 pub fn decode_slice_legacy(
1318 &self,
1319 input: &[u8],
1320 output: &mut [u8],
1321 ) -> Result<usize, DecodeError> {
1322 let required = validate_legacy_decode::<A, PAD>(input)?;
1323 if output.len() < required {
1324 return Err(DecodeError::OutputTooSmall {
1325 required,
1326 available: output.len(),
1327 });
1328 }
1329 decode_legacy_to_slice::<A, PAD>(input, output)
1330 }
1331
1332 /// Decodes `input` using the explicit legacy whitespace profile and clears
1333 /// all bytes after the decoded prefix.
1334 ///
1335 /// If validation or decoding fails, the entire output buffer is cleared
1336 /// before the error is returned.
1337 ///
1338 /// # Examples
1339 ///
1340 /// ```
1341 /// use base64_ng::STANDARD;
1342 ///
1343 /// let mut output = [0xff; 8];
1344 /// let written = STANDARD
1345 /// .decode_slice_legacy_clear_tail(b" aG\r\nk= ", &mut output)
1346 /// .unwrap();
1347 ///
1348 /// assert_eq!(&output[..written], b"hi");
1349 /// assert!(output[written..].iter().all(|byte| *byte == 0));
1350 /// ```
1351 pub fn decode_slice_legacy_clear_tail(
1352 &self,
1353 input: &[u8],
1354 output: &mut [u8],
1355 ) -> Result<usize, DecodeError> {
1356 let written = match self.decode_slice_legacy(input, output) {
1357 Ok(written) => written,
1358 Err(err) => {
1359 wipe_bytes(output);
1360 return Err(err);
1361 }
1362 };
1363 wipe_tail(output, written);
1364 Ok(written)
1365 }
1366
1367 /// Decodes `input` into a stack-backed buffer using the explicit legacy
1368 /// whitespace profile.
1369 ///
1370 /// ASCII space, tab, carriage return, and line feed bytes are ignored.
1371 /// Alphabet selection, padding placement, trailing data after padding, and
1372 /// non-canonical trailing bits remain strict. If decoding fails, the
1373 /// internal backing array is cleared before the error is returned.
1374 pub fn decode_buffer_legacy<const CAP: usize>(
1375 &self,
1376 input: &[u8],
1377 ) -> Result<DecodedBuffer<CAP>, DecodeError> {
1378 let mut output = DecodedBuffer::new();
1379 let written = match self.decode_slice_legacy_clear_tail(input, output.as_mut_capacity()) {
1380 Ok(written) => written,
1381 Err(err) => {
1382 output.clear();
1383 return Err(err);
1384 }
1385 };
1386 output.set_filled(written)?;
1387 Ok(output)
1388 }
1389
1390 /// Decodes `input` using a strict line-wrapped profile.
1391 ///
1392 /// The wrapped profile accepts only the configured line ending. Non-final
1393 /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
1394 /// may be shorter. A single trailing line ending after the final line is
1395 /// accepted.
1396 ///
1397 /// # Security
1398 ///
1399 /// This method uses the normal strict decode path after line-profile
1400 /// validation. It may branch or return early based on malformed input and
1401 /// is not a constant-time token validator or key-material decoder. Use
1402 /// [`crate::ct`] for secret-bearing payloads.
1403 #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
1404 pub fn decode_slice_wrapped(
1405 &self,
1406 input: &[u8],
1407 output: &mut [u8],
1408 wrap: LineWrap,
1409 ) -> Result<usize, DecodeError> {
1410 let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
1411 if output.len() < required {
1412 return Err(DecodeError::OutputTooSmall {
1413 required,
1414 available: output.len(),
1415 });
1416 }
1417 decode_wrapped_to_slice::<A, PAD>(input, output, wrap)
1418 }
1419
1420 /// Decodes `input` using a strict line-wrapped profile and clears all bytes
1421 /// after the decoded prefix.
1422 ///
1423 /// If validation or decoding fails, the entire output buffer is cleared
1424 /// before the error is returned.
1425 pub fn decode_slice_wrapped_clear_tail(
1426 &self,
1427 input: &[u8],
1428 output: &mut [u8],
1429 wrap: LineWrap,
1430 ) -> Result<usize, DecodeError> {
1431 let written = match self.decode_slice_wrapped(input, output, wrap) {
1432 Ok(written) => written,
1433 Err(err) => {
1434 wipe_bytes(output);
1435 return Err(err);
1436 }
1437 };
1438 wipe_tail(output, written);
1439 Ok(written)
1440 }
1441
1442 /// Decodes `input` using a strict line-wrapped profile into a stack-backed
1443 /// buffer.
1444 ///
1445 /// The wrapped profile accepts only the configured line ending. Non-final
1446 /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
1447 /// may be shorter. A single trailing line ending after the final line is
1448 /// accepted. If decoding fails, the internal backing array is cleared
1449 /// before the error is returned.
1450 pub fn decode_wrapped_buffer<const CAP: usize>(
1451 &self,
1452 input: &[u8],
1453 wrap: LineWrap,
1454 ) -> Result<DecodedBuffer<CAP>, DecodeError> {
1455 let mut output = DecodedBuffer::new();
1456 let written =
1457 match self.decode_slice_wrapped_clear_tail(input, output.as_mut_capacity(), wrap) {
1458 Ok(written) => written,
1459 Err(err) => {
1460 output.clear();
1461 return Err(err);
1462 }
1463 };
1464 output.set_filled(written)?;
1465 Ok(output)
1466 }
1467
1468 /// Decodes `input` into a newly allocated byte vector.
1469 ///
1470 /// This is strict decoding with the same semantics as [`Self::decode_slice`].
1471 #[cfg(feature = "alloc")]
1472 #[must_use = "for secret-bearing payloads use decode_secret, which returns a redacted buffer with drop-time cleanup"]
1473 pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
1474 let required = validate_decode::<A, PAD>(input)?;
1475 let mut output = alloc::vec![0; required];
1476 let written = match self.decode_slice(input, &mut output) {
1477 Ok(written) => written,
1478 Err(err) => {
1479 wipe_bytes(&mut output);
1480 return Err(err);
1481 }
1482 };
1483 output.truncate(written);
1484 Ok(output)
1485 }
1486
1487 /// Decodes `input` into a redacted owned secret buffer.
1488 ///
1489 /// On malformed input, the intermediate output buffer is cleared before the
1490 /// error is returned by [`Self::decode_vec`].
1491 #[cfg(feature = "alloc")]
1492 pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
1493 self.decode_vec(input).map(SecretBuffer::from_vec)
1494 }
1495
1496 /// Decodes `input` into a newly allocated byte vector using the explicit
1497 /// legacy whitespace profile.
1498 #[cfg(feature = "alloc")]
1499 #[must_use = "for secret-bearing payloads use decode_secret_legacy, which returns a redacted buffer with drop-time cleanup"]
1500 pub fn decode_vec_legacy(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
1501 let required = validate_legacy_decode::<A, PAD>(input)?;
1502 let mut output = alloc::vec![0; required];
1503 let written = match self.decode_slice_legacy(input, &mut output) {
1504 Ok(written) => written,
1505 Err(err) => {
1506 wipe_bytes(&mut output);
1507 return Err(err);
1508 }
1509 };
1510 output.truncate(written);
1511 Ok(output)
1512 }
1513
1514 /// Decodes `input` into a redacted owned secret buffer using the explicit
1515 /// legacy whitespace profile.
1516 ///
1517 /// ASCII space, tab, carriage return, and line feed bytes are ignored.
1518 /// Alphabet selection, padding placement, trailing data after padding, and
1519 /// non-canonical trailing bits remain strict.
1520 #[cfg(feature = "alloc")]
1521 pub fn decode_secret_legacy(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
1522 self.decode_vec_legacy(input).map(SecretBuffer::from_vec)
1523 }
1524
1525 /// Decodes line-wrapped input into a newly allocated byte vector.
1526 #[cfg(feature = "alloc")]
1527 #[must_use = "for secret-bearing payloads use decode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
1528 pub fn decode_wrapped_vec(
1529 &self,
1530 input: &[u8],
1531 wrap: LineWrap,
1532 ) -> Result<alloc::vec::Vec<u8>, DecodeError> {
1533 let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
1534 let mut output = alloc::vec![0; required];
1535 let written = match self.decode_slice_wrapped(input, &mut output, wrap) {
1536 Ok(written) => written,
1537 Err(err) => {
1538 wipe_bytes(&mut output);
1539 return Err(err);
1540 }
1541 };
1542 output.truncate(written);
1543 Ok(output)
1544 }
1545
1546 /// Decodes line-wrapped input into a redacted owned secret buffer.
1547 ///
1548 /// The wrapped profile accepts only the configured line ending. Non-final
1549 /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
1550 /// may be shorter. A single trailing line ending after the final line is
1551 /// accepted.
1552 #[cfg(feature = "alloc")]
1553 pub fn decode_wrapped_secret(
1554 &self,
1555 input: &[u8],
1556 wrap: LineWrap,
1557 ) -> Result<SecretBuffer, DecodeError> {
1558 self.decode_wrapped_vec(input, wrap)
1559 .map(SecretBuffer::from_vec)
1560 }
1561
1562 /// Decodes `buffer` in place using a strict line-wrapped profile.
1563 ///
1564 /// The wrapped profile accepts only the configured line ending. Non-final
1565 /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
1566 /// may be shorter. A single trailing line ending after the final line is
1567 /// accepted.
1568 ///
1569 /// # Security
1570 ///
1571 /// This method compacts line endings in place before decoding. If
1572 /// validation or decoding fails, the buffer contents are unspecified and
1573 /// may contain a compacted encoded prefix. On success, bytes after the
1574 /// returned decoded prefix may retain the compacted encoded representation.
1575 /// Use
1576 /// [`Self::decode_in_place_wrapped_clear_tail`] when the buffer may be
1577 /// reused or freed without a caller-managed wipe; treat that clear-tail
1578 /// variant as the default for secret-bearing wrapped payloads.
1579 ///
1580 /// # Examples
1581 ///
1582 /// ```
1583 /// use base64_ng::{LineEnding, LineWrap, STANDARD};
1584 ///
1585 /// let mut buffer = *b"aGVs\nbG8=";
1586 /// let decoded = STANDARD
1587 /// .decode_in_place_wrapped(&mut buffer, LineWrap::new(4, LineEnding::Lf))
1588 /// .unwrap();
1589 ///
1590 /// assert_eq!(decoded, b"hello");
1591 /// ```
1592 pub fn decode_in_place_wrapped<'a>(
1593 &self,
1594 buffer: &'a mut [u8],
1595 wrap: LineWrap,
1596 ) -> Result<&'a mut [u8], DecodeError> {
1597 let _required = validate_wrapped_decode::<A, PAD>(buffer, wrap)?;
1598 let compacted = compact_wrapped_input(buffer, wrap)?;
1599 let len = Self::decode_slice_to_start(&mut buffer[..compacted])?;
1600 Ok(&mut buffer[..len])
1601 }
1602
1603 /// Decodes `buffer` in place using a strict line-wrapped profile and clears
1604 /// all bytes after the decoded prefix.
1605 ///
1606 /// If validation or decoding fails, the entire buffer is cleared before the
1607 /// error is returned.
1608 ///
1609 /// # Examples
1610 ///
1611 /// ```
1612 /// use base64_ng::{LineEnding, LineWrap, STANDARD};
1613 ///
1614 /// let mut buffer = *b"aGVs\nbG8=";
1615 /// let len = STANDARD
1616 /// .decode_in_place_wrapped_clear_tail(&mut buffer, LineWrap::new(4, LineEnding::Lf))
1617 /// .unwrap()
1618 /// .len();
1619 ///
1620 /// assert_eq!(&buffer[..len], b"hello");
1621 /// assert!(buffer[len..].iter().all(|byte| *byte == 0));
1622 /// ```
1623 pub fn decode_in_place_wrapped_clear_tail<'a>(
1624 &self,
1625 buffer: &'a mut [u8],
1626 wrap: LineWrap,
1627 ) -> Result<&'a mut [u8], DecodeError> {
1628 if let Err(err) = validate_wrapped_decode::<A, PAD>(buffer, wrap) {
1629 wipe_bytes(buffer);
1630 return Err(err);
1631 }
1632
1633 let compacted = match compact_wrapped_input(buffer, wrap) {
1634 Ok(compacted) => compacted,
1635 Err(err) => {
1636 wipe_bytes(buffer);
1637 return Err(err);
1638 }
1639 };
1640
1641 let len = match Self::decode_slice_to_start(&mut buffer[..compacted]) {
1642 Ok(len) => len,
1643 Err(err) => {
1644 wipe_bytes(buffer);
1645 return Err(err);
1646 }
1647 };
1648 wipe_tail(buffer, len);
1649 Ok(&mut buffer[..len])
1650 }
1651
1652 /// Decodes the buffer in place and returns the decoded prefix.
1653 ///
1654 /// On success, bytes after the returned decoded prefix may retain encoded
1655 /// input bytes. Use [`Self::decode_in_place_clear_tail`] when the buffer
1656 /// may be reused or freed without a caller-managed wipe.
1657 ///
1658 /// # Security
1659 ///
1660 /// This default scalar decoder prioritizes strict validation, exact error
1661 /// reporting, and ordinary throughput. It may branch or return early based
1662 /// on malformed input and reports exact failure positions and invalid byte
1663 /// values through [`DecodeError`]. Do not use this method for token
1664 /// comparison, key-material decoding, or secret-bearing validation where
1665 /// malformed-input timing matters. Do not log strict decode errors
1666 /// verbatim for secret-bearing input; log [`DecodeError::kind`] instead.
1667 ///
1668 /// # Examples
1669 ///
1670 /// ```
1671 /// use base64_ng::STANDARD_NO_PAD;
1672 ///
1673 /// let mut buffer = *b"Zm9vYmFy";
1674 /// let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
1675 /// assert_eq!(decoded, b"foobar");
1676 /// ```
1677 pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
1678 let len = Self::decode_slice_to_start(buffer)?;
1679 Ok(&mut buffer[..len])
1680 }
1681
1682 /// Decodes the buffer in place and clears all bytes after the decoded prefix.
1683 ///
1684 /// If decoding fails, the entire buffer is cleared before the error is
1685 /// returned. Use this variant when the encoded or partially decoded data is
1686 /// sensitive and the caller wants best-effort cleanup without adding a
1687 /// dependency.
1688 ///
1689 /// # Examples
1690 ///
1691 /// ```
1692 /// use base64_ng::STANDARD;
1693 ///
1694 /// let mut buffer = *b"aGk=";
1695 /// let decoded = STANDARD.decode_in_place_clear_tail(&mut buffer).unwrap();
1696 /// assert_eq!(decoded, b"hi");
1697 /// ```
1698 pub fn decode_in_place_clear_tail<'a>(
1699 &self,
1700 buffer: &'a mut [u8],
1701 ) -> Result<&'a mut [u8], DecodeError> {
1702 let len = match Self::decode_slice_to_start(buffer) {
1703 Ok(len) => len,
1704 Err(err) => {
1705 wipe_bytes(buffer);
1706 return Err(err);
1707 }
1708 };
1709 wipe_tail(buffer, len);
1710 Ok(&mut buffer[..len])
1711 }
1712
1713 /// Decodes `buffer` in place using the explicit legacy whitespace profile.
1714 ///
1715 /// Ignored whitespace is compacted out before decoding. If validation
1716 /// fails, the buffer contents are unspecified. On success, bytes after the
1717 /// returned decoded prefix may retain the compacted encoded
1718 /// representation. Use [`Self::decode_in_place_legacy_clear_tail`] when the
1719 /// buffer may be reused or freed without a caller-managed wipe.
1720 pub fn decode_in_place_legacy<'a>(
1721 &self,
1722 buffer: &'a mut [u8],
1723 ) -> Result<&'a mut [u8], DecodeError> {
1724 let _required = validate_legacy_decode::<A, PAD>(buffer)?;
1725 let mut write = 0;
1726 let mut read = 0;
1727 while read < buffer.len() {
1728 let byte = buffer[read];
1729 if !is_legacy_whitespace(byte) {
1730 buffer[write] = byte;
1731 write += 1;
1732 }
1733 read += 1;
1734 }
1735 let len = Self::decode_slice_to_start(&mut buffer[..write])?;
1736 Ok(&mut buffer[..len])
1737 }
1738
1739 /// Decodes `buffer` in place using the explicit legacy whitespace profile
1740 /// and clears all bytes after the decoded prefix.
1741 ///
1742 /// If validation or decoding fails, the entire buffer is cleared before the
1743 /// error is returned.
1744 pub fn decode_in_place_legacy_clear_tail<'a>(
1745 &self,
1746 buffer: &'a mut [u8],
1747 ) -> Result<&'a mut [u8], DecodeError> {
1748 if let Err(err) = validate_legacy_decode::<A, PAD>(buffer) {
1749 wipe_bytes(buffer);
1750 return Err(err);
1751 }
1752
1753 let mut write = 0;
1754 let mut read = 0;
1755 while read < buffer.len() {
1756 let byte = buffer[read];
1757 if !is_legacy_whitespace(byte) {
1758 buffer[write] = byte;
1759 write += 1;
1760 }
1761 read += 1;
1762 }
1763
1764 let len = match Self::decode_slice_to_start(&mut buffer[..write]) {
1765 Ok(len) => len,
1766 Err(err) => {
1767 wipe_bytes(buffer);
1768 return Err(err);
1769 }
1770 };
1771 wipe_tail(buffer, len);
1772 Ok(&mut buffer[..len])
1773 }
1774
1775 fn decode_slice_to_start(buffer: &mut [u8]) -> Result<usize, DecodeError> {
1776 let _required = validate_decode::<A, PAD>(buffer)?;
1777 let input_len = buffer.len();
1778 let mut read = 0;
1779 let mut write = 0;
1780 while read + 4 <= input_len {
1781 let chunk = read_quad(buffer, read)?;
1782 let available = buffer.len();
1783 let output_tail = buffer.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
1784 required: write,
1785 available,
1786 })?;
1787 let written = decode_chunk::<A, PAD>(chunk, output_tail)
1788 .map_err(|err| err.with_index_offset(read))?;
1789 read += 4;
1790 write += written;
1791 if written < 3 {
1792 if read != input_len {
1793 return Err(DecodeError::InvalidPadding { index: read - 4 });
1794 }
1795 return Ok(write);
1796 }
1797 }
1798
1799 let rem = input_len - read;
1800 if rem == 0 {
1801 return Ok(write);
1802 }
1803 if PAD {
1804 return Err(DecodeError::InvalidLength);
1805 }
1806 let mut tail = [0u8; 3];
1807 tail[..rem].copy_from_slice(&buffer[read..input_len]);
1808 decode_tail_unpadded::<A>(&tail[..rem], &mut buffer[write..])
1809 .map_err(|err| err.with_index_offset(read))
1810 .map(|n| write + n)
1811 }
1812}
1813
1814#[cfg(kani)]
1815mod kani_proofs {
1816 use super::{
1817 STANDARD, Standard, checked_encoded_len, ct, decode_byte, decode_chunk,
1818 decode_tail_unpadded, decoded_capacity, validate_tail_unpadded,
1819 };
1820
1821 #[kani::proof]
1822 fn checked_encoded_len_is_bounded_for_small_inputs() {
1823 let len = usize::from(kani::any::<u8>());
1824 let padded = kani::any::<bool>();
1825 let encoded = checked_encoded_len(len, padded).expect("u8 input length cannot overflow");
1826
1827 assert!(encoded >= len);
1828 assert!(encoded <= len / 3 * 4 + 4);
1829 }
1830
1831 #[kani::proof]
1832 fn decoded_capacity_is_bounded_for_small_inputs() {
1833 let len = usize::from(kani::any::<u8>());
1834 let capacity = decoded_capacity(len);
1835
1836 assert!(capacity <= len / 4 * 3 + 2);
1837 }
1838
1839 #[kani::proof]
1840 #[kani::unwind(70)]
1841 fn standard_in_place_decode_returns_prefix_within_buffer() {
1842 let mut buffer = kani::any::<[u8; 8]>();
1843 let result = STANDARD.decode_in_place(&mut buffer);
1844
1845 if let Ok(decoded) = result {
1846 assert!(decoded.len() <= 8);
1847 }
1848 }
1849
1850 #[kani::proof]
1851 #[kani::unwind(70)]
1852 fn standard_decode_slice_returns_written_within_output() {
1853 let input = kani::any::<[u8; 4]>();
1854 let mut output = kani::any::<[u8; 3]>();
1855 let result = STANDARD.decode_slice(&input, &mut output);
1856
1857 if let Ok(written) = result {
1858 assert!(written <= output.len());
1859 }
1860 }
1861
1862 #[kani::proof]
1863 #[kani::unwind(70)]
1864 fn standard_decode_chunk_returns_written_within_output() {
1865 let input = kani::any::<[u8; 4]>();
1866 let mut output = kani::any::<[u8; 3]>();
1867 let result = decode_chunk::<Standard, true>(input, &mut output);
1868
1869 if let Ok(written) = result {
1870 assert!(written <= output.len());
1871 assert!(written <= 3);
1872 }
1873 }
1874
1875 #[kani::proof]
1876 #[kani::unwind(70)]
1877 fn standard_decode_chunk_bit_packing_matches_decoded_values() {
1878 let input = kani::any::<[u8; 4]>();
1879 let mut output = kani::any::<[u8; 3]>();
1880 let result = decode_chunk::<Standard, true>(input, &mut output);
1881
1882 if let Ok(written) = result {
1883 let v0 = decode_byte::<Standard>(input[0], 0).expect("successful chunk has v0");
1884 let v1 = decode_byte::<Standard>(input[1], 1).expect("successful chunk has v1");
1885
1886 assert!(output[0] == ((v0 << 2) | (v1 >> 4)));
1887
1888 if written >= 2 {
1889 let v2 = decode_byte::<Standard>(input[2], 2).expect("successful chunk has v2");
1890 assert!(output[1] == ((v1 << 4) | (v2 >> 2)));
1891 }
1892
1893 if written == 3 {
1894 let v2 = decode_byte::<Standard>(input[2], 2).expect("successful chunk has v2");
1895 let v3 = decode_byte::<Standard>(input[3], 3).expect("successful chunk has v3");
1896 assert!(output[2] == ((v2 << 6) | v3));
1897 }
1898 }
1899 }
1900
1901 #[kani::proof]
1902 #[kani::unwind(70)]
1903 fn standard_validate_tail_unpadded_accepts_or_rejects_without_panic() {
1904 let input = kani::any::<[u8; 3]>();
1905 let len = usize::from(kani::any::<u8>() % 4);
1906 let result = validate_tail_unpadded::<Standard>(&input[..len]);
1907
1908 if result.is_ok() {
1909 assert!(len == 0 || len == 2 || len == 3);
1910 }
1911 }
1912
1913 #[kani::proof]
1914 #[kani::unwind(70)]
1915 fn standard_decode_two_byte_tail_returns_written_within_output() {
1916 let input = kani::any::<[u8; 2]>();
1917 let mut output = kani::any::<[u8; 1]>();
1918 let result = decode_tail_unpadded::<Standard>(&input, &mut output);
1919
1920 if let Ok(written) = result {
1921 assert!(written <= output.len());
1922 assert!(written == 1);
1923 }
1924 }
1925
1926 #[kani::proof]
1927 #[kani::unwind(70)]
1928 fn standard_decode_three_byte_tail_returns_written_within_output() {
1929 let input = kani::any::<[u8; 3]>();
1930 let mut output = kani::any::<[u8; 2]>();
1931 let result = decode_tail_unpadded::<Standard>(&input, &mut output);
1932
1933 if let Ok(written) = result {
1934 assert!(written <= output.len());
1935 assert!(written == 2);
1936 }
1937 }
1938
1939 #[kani::proof]
1940 #[kani::unwind(70)]
1941 fn standard_decode_slice_clear_tail_clears_output_on_error() {
1942 let input = kani::any::<[u8; 4]>();
1943 let mut output = kani::any::<[u8; 3]>();
1944 let result = STANDARD.decode_slice_clear_tail(&input, &mut output);
1945
1946 if result.is_err() {
1947 assert!(output.iter().all(|byte| *byte == 0));
1948 }
1949 }
1950
1951 #[kani::proof]
1952 #[kani::unwind(70)]
1953 fn standard_encode_slice_returns_written_within_output() {
1954 let input = kani::any::<[u8; 3]>();
1955 let mut output = kani::any::<[u8; 4]>();
1956 let result = STANDARD.encode_slice(&input, &mut output);
1957
1958 if let Ok(written) = result {
1959 assert!(written <= output.len());
1960 }
1961 }
1962
1963 #[kani::proof]
1964 #[kani::unwind(70)]
1965 fn standard_encode_in_place_returns_prefix_within_buffer() {
1966 let mut buffer = kani::any::<[u8; 8]>();
1967 let input_len = usize::from(kani::any::<u8>() % 9);
1968 let result = STANDARD.encode_in_place(&mut buffer, input_len);
1969
1970 if let Ok(encoded) = result {
1971 assert!(encoded.len() <= 8);
1972 }
1973 }
1974
1975 #[kani::proof]
1976 #[kani::unwind(70)]
1977 fn standard_clear_tail_decode_clears_buffer_on_error() {
1978 let mut buffer = kani::any::<[u8; 4]>();
1979 let result = STANDARD.decode_in_place_clear_tail(&mut buffer);
1980
1981 if result.is_err() {
1982 assert!(buffer.iter().all(|byte| *byte == 0));
1983 }
1984 }
1985
1986 #[kani::proof]
1987 #[kani::unwind(70)]
1988 fn ct_standard_decode_slice_returns_written_within_output() {
1989 let input = kani::any::<[u8; 4]>();
1990 let mut output = kani::any::<[u8; 3]>();
1991 let result = ct::STANDARD.decode_slice_clear_tail(&input, &mut output);
1992
1993 if let Ok(written) = result {
1994 assert!(written <= output.len());
1995 }
1996 }
1997
1998 #[kani::proof]
1999 #[kani::unwind(70)]
2000 fn ct_standard_decode_slice_clear_tail_clears_output_on_error() {
2001 let input = kani::any::<[u8; 4]>();
2002 let mut output = kani::any::<[u8; 3]>();
2003 let result = ct::STANDARD.decode_slice_clear_tail(&input, &mut output);
2004
2005 if result.is_err() {
2006 assert!(output.iter().all(|byte| *byte == 0));
2007 }
2008 }
2009
2010 #[kani::proof]
2011 #[kani::unwind(70)]
2012 fn ct_standard_decode_in_place_clear_tail_clears_buffer_on_error() {
2013 let mut buffer = kani::any::<[u8; 4]>();
2014 let result = ct::STANDARD.decode_in_place_clear_tail(&mut buffer);
2015
2016 if result.is_err() {
2017 assert!(buffer.iter().all(|byte| *byte == 0));
2018 }
2019 }
2020
2021 #[kani::proof]
2022 #[kani::unwind(70)]
2023 fn ct_standard_validate_matches_decode_for_one_quantum() {
2024 let input = kani::any::<[u8; 4]>();
2025 let mut output = kani::any::<[u8; 3]>();
2026
2027 let validate_ok = ct::STANDARD.validate_result(&input).is_ok();
2028 let decode_ok = ct::STANDARD
2029 .decode_slice_clear_tail(&input, &mut output)
2030 .is_ok();
2031
2032 assert!(validate_ok == decode_ok);
2033 }
2034}
2035
2036#[cfg(test)]
2037mod tests {
2038 use super::*;
2039
2040 fn fill_pattern(output: &mut [u8], seed: usize) {
2041 for (index, byte) in output.iter_mut().enumerate() {
2042 let value = (index * 73 + seed * 19) % 256;
2043 *byte = u8::try_from(value).unwrap();
2044 }
2045 }
2046
2047 fn assert_encode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
2048 where
2049 A: Alphabet,
2050 {
2051 let engine = Engine::<A, PAD>::new();
2052 let mut dispatched = [0x55; 256];
2053 let mut scalar = [0xaa; 256];
2054
2055 let dispatched_result = engine.encode_slice(input, &mut dispatched);
2056 let scalar_result = scalar::scalar_reference_encode_slice::<A, PAD>(input, &mut scalar);
2057
2058 assert_eq!(dispatched_result, scalar_result);
2059 if let Ok(written) = dispatched_result {
2060 assert_eq!(&dispatched[..written], &scalar[..written]);
2061 }
2062
2063 let required = checked_encoded_len(input.len(), PAD).unwrap();
2064 if required > 0 {
2065 let mut dispatched_short = [0x55; 256];
2066 let mut scalar_short = [0xaa; 256];
2067 let available = required - 1;
2068
2069 assert_eq!(
2070 engine.encode_slice(input, &mut dispatched_short[..available]),
2071 scalar::scalar_reference_encode_slice::<A, PAD>(
2072 input,
2073 &mut scalar_short[..available],
2074 )
2075 );
2076 }
2077 }
2078
2079 fn assert_decode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
2080 where
2081 A: Alphabet,
2082 {
2083 let engine = Engine::<A, PAD>::new();
2084 let mut dispatched = [0x55; 128];
2085 let mut scalar = [0xaa; 128];
2086
2087 let dispatched_result = engine.decode_slice(input, &mut dispatched);
2088 let scalar_result = scalar::scalar_reference_decode_slice::<A, PAD>(input, &mut scalar);
2089
2090 assert_eq!(dispatched_result, scalar_result);
2091 if let Ok(written) = dispatched_result {
2092 assert_eq!(&dispatched[..written], &scalar[..written]);
2093
2094 if written > 0 {
2095 let mut dispatched_short = [0x55; 128];
2096 let mut scalar_short = [0xaa; 128];
2097 let available = written - 1;
2098
2099 assert_eq!(
2100 engine.decode_slice(input, &mut dispatched_short[..available]),
2101 scalar::scalar_reference_decode_slice::<A, PAD>(
2102 input,
2103 &mut scalar_short[..available],
2104 )
2105 );
2106 }
2107 }
2108 }
2109
2110 fn assert_backend_round_trip_matches_scalar<A, const PAD: bool>(input: &[u8])
2111 where
2112 A: Alphabet,
2113 {
2114 assert_encode_backend_matches_scalar::<A, PAD>(input);
2115
2116 let mut encoded = [0; 256];
2117 let encoded_len =
2118 scalar::scalar_reference_encode_slice::<A, PAD>(input, &mut encoded).unwrap();
2119 assert_decode_backend_matches_scalar::<A, PAD>(&encoded[..encoded_len]);
2120 }
2121
2122 fn assert_standard_decode_chunk_matches_input(input: &[u8]) {
2123 let mut encoded = [0u8; 4];
2124 let encoded_len = STANDARD.encode_slice(input, &mut encoded).unwrap();
2125 assert_eq!(encoded_len, 4);
2126
2127 let chunk = [encoded[0], encoded[1], encoded[2], encoded[3]];
2128 let mut decoded = [0u8; 3];
2129 let decoded_len = decode_chunk::<Standard, true>(chunk, &mut decoded).unwrap();
2130
2131 assert_eq!(decoded_len, input.len());
2132 assert_eq!(&decoded[..decoded_len], input);
2133 }
2134
2135 #[test]
2136 fn backend_dispatch_matches_scalar_reference_for_canonical_inputs() {
2137 let mut input = [0; 128];
2138
2139 for input_len in 0..=input.len() {
2140 fill_pattern(&mut input[..input_len], input_len);
2141 let input = &input[..input_len];
2142
2143 assert_backend_round_trip_matches_scalar::<Standard, true>(input);
2144 assert_backend_round_trip_matches_scalar::<Standard, false>(input);
2145 assert_backend_round_trip_matches_scalar::<UrlSafe, true>(input);
2146 assert_backend_round_trip_matches_scalar::<UrlSafe, false>(input);
2147 }
2148 }
2149
2150 #[test]
2151 fn backend_dispatch_matches_scalar_reference_for_malformed_inputs() {
2152 for input in [
2153 &b"Z"[..],
2154 b"====",
2155 b"AA=A",
2156 b"Zh==",
2157 b"Zm9=",
2158 b"Zm9v$g==",
2159 b"Zm9vZh==",
2160 ] {
2161 assert_decode_backend_matches_scalar::<Standard, true>(input);
2162 }
2163
2164 for input in [&b"Z"[..], b"AA=A", b"Zh", b"Zm9", b"Zm9vYg$"] {
2165 assert_decode_backend_matches_scalar::<Standard, false>(input);
2166 }
2167
2168 assert_decode_backend_matches_scalar::<UrlSafe, true>(b"AA+A");
2169 assert_decode_backend_matches_scalar::<UrlSafe, false>(b"AA/A");
2170 assert_decode_backend_matches_scalar::<Standard, true>(b"AA-A");
2171 assert_decode_backend_matches_scalar::<Standard, false>(b"AA_A");
2172 }
2173
2174 #[test]
2175 fn decode_chunk_bit_packing_matches_exhaustive_small_inputs() {
2176 for byte in u8::MIN..=u8::MAX {
2177 assert_standard_decode_chunk_matches_input(&[byte]);
2178 }
2179
2180 for first in u8::MIN..=u8::MAX {
2181 for second in u8::MIN..=u8::MAX {
2182 assert_standard_decode_chunk_matches_input(&[first, second]);
2183 }
2184 }
2185 }
2186
2187 #[test]
2188 fn decode_chunk_bit_packing_matches_representative_full_quanta() {
2189 const SAMPLES: [u8; 16] = [
2190 0, 1, 2, 15, 16, 31, 32, 63, 64, 95, 127, 128, 191, 192, 254, 255,
2191 ];
2192
2193 for first in SAMPLES {
2194 for second in SAMPLES {
2195 for third in SAMPLES {
2196 assert_standard_decode_chunk_matches_input(&[first, second, third]);
2197 }
2198 }
2199 }
2200 }
2201
2202 #[test]
2203 fn ct_padded_final_quantum_fails_closed_for_invalid_padding_count() {
2204 let (_, invalid_byte, invalid_padding, written) =
2205 ct_padded_final_quantum::<Standard>(*b"ABCD", 3);
2206
2207 assert_ne!(invalid_byte, 0);
2208 assert_ne!(invalid_padding, 0);
2209 assert_eq!(written, 0);
2210 assert_eq!(
2211 report_ct_error(invalid_byte, invalid_padding),
2212 Err(DecodeError::InvalidInput)
2213 );
2214 }
2215
2216 #[cfg(feature = "simd")]
2217 #[test]
2218 fn simd_dispatch_scaffold_keeps_scalar_active() {
2219 assert_eq!(simd::active_backend(), simd::ActiveBackend::Scalar);
2220 let _candidate = simd::detected_candidate();
2221 }
2222
2223 #[test]
2224 fn encodes_standard_vectors() {
2225 let vectors = [
2226 (&b""[..], &b""[..]),
2227 (&b"f"[..], &b"Zg=="[..]),
2228 (&b"fo"[..], &b"Zm8="[..]),
2229 (&b"foo"[..], &b"Zm9v"[..]),
2230 (&b"foob"[..], &b"Zm9vYg=="[..]),
2231 (&b"fooba"[..], &b"Zm9vYmE="[..]),
2232 (&b"foobar"[..], &b"Zm9vYmFy"[..]),
2233 ];
2234 for (input, expected) in vectors {
2235 let mut output = [0u8; 16];
2236 let written = STANDARD.encode_slice(input, &mut output).unwrap();
2237 assert_eq!(&output[..written], expected);
2238 }
2239 }
2240
2241 #[test]
2242 fn decodes_standard_vectors() {
2243 let vectors = [
2244 (&b""[..], &b""[..]),
2245 (&b"Zg=="[..], &b"f"[..]),
2246 (&b"Zm8="[..], &b"fo"[..]),
2247 (&b"Zm9v"[..], &b"foo"[..]),
2248 (&b"Zm9vYg=="[..], &b"foob"[..]),
2249 (&b"Zm9vYmE="[..], &b"fooba"[..]),
2250 (&b"Zm9vYmFy"[..], &b"foobar"[..]),
2251 ];
2252 for (input, expected) in vectors {
2253 let mut output = [0u8; 16];
2254 let written = STANDARD.decode_slice(input, &mut output).unwrap();
2255 assert_eq!(&output[..written], expected);
2256 }
2257 }
2258
2259 #[test]
2260 fn supports_unpadded_url_safe() {
2261 let mut encoded = [0u8; 16];
2262 let written = URL_SAFE_NO_PAD
2263 .encode_slice(b"\xfb\xff", &mut encoded)
2264 .unwrap();
2265 assert_eq!(&encoded[..written], b"-_8");
2266
2267 let mut decoded = [0u8; 2];
2268 let written = URL_SAFE_NO_PAD
2269 .decode_slice(&encoded[..written], &mut decoded)
2270 .unwrap();
2271 assert_eq!(&decoded[..written], b"\xfb\xff");
2272 }
2273
2274 #[test]
2275 fn decodes_in_place() {
2276 let mut buffer = *b"Zm9vYmFy";
2277 let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
2278 assert_eq!(decoded, b"foobar");
2279 }
2280
2281 #[test]
2282 fn rejects_non_canonical_padding_bits() {
2283 let mut output = [0u8; 4];
2284 assert_eq!(
2285 STANDARD.decode_slice(b"Zh==", &mut output),
2286 Err(DecodeError::InvalidPadding { index: 1 })
2287 );
2288 assert_eq!(
2289 STANDARD.decode_slice(b"Zm9=", &mut output),
2290 Err(DecodeError::InvalidPadding { index: 2 })
2291 );
2292 }
2293}