Skip to main content

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