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