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