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//! # Zeroization Caveat
57//!
58//! Cleanup APIs and redacted buffers use dependency-free best-effort wiping:
59//! byte-wise volatile zero writes followed by an architecture-gated inline
60//! assembly barrier plus a hardware store-ordering fence where stable Rust
61//! supports it, and a compiler fence on all targets. This resists common
62//! compiler dead-store elimination and orders the issued zero stores on native
63//! supported architectures, but it is not a formal zeroization guarantee and
64//! cannot clear historical copies, registers, cache lines, write buffers, swap,
65//! hibernation images, core dumps, cold-boot remanence, or OS-level memory
66//! snapshots.
67//! High-assurance applications should apply their own approved zeroization
68//! policy to caller-owned buffers at the protocol boundary. Architectures
69//! without a native wipe barrier fail closed by default unless
70//! `allow-compiler-fence-only-wipe` is enabled after platform review. On
71//! `wasm32`, the wipe barrier is compiler-fence-only and cannot constrain
72//! downstream wasm runtime JITs. For that reason, `wasm32` builds fail closed
73//! by default. Enable `allow-wasm32-best-effort-wipe` only when the deployment
74//! explicitly accepts compiler-fence-only cleanup and applies its own memory
75//! strategy.
76
77#[cfg(feature = "alloc")]
78extern crate alloc;
79
80#[cfg(all(target_arch = "wasm32", not(feature = "allow-wasm32-best-effort-wipe")))]
81compile_error!(
82    "base64-ng: wasm32 builds use a compiler-fence-only wipe barrier that cannot \
83     constrain downstream wasm runtime JITs. Enable \
84     `allow-wasm32-best-effort-wipe` to accept this limitation and use \
85     caller-owned, platform-approved zeroization for high-assurance wasm deployments."
86);
87
88#[cfg(all(
89    not(miri),
90    not(feature = "allow-compiler-fence-only-wipe"),
91    not(any(
92        target_arch = "aarch64",
93        target_arch = "arm",
94        target_arch = "riscv32",
95        target_arch = "riscv64",
96        target_arch = "wasm32",
97        target_arch = "x86",
98        target_arch = "x86_64",
99    ))
100))]
101compile_error!(
102    "base64-ng: this architecture has no native hardware wipe barrier in \
103     base64-ng. Enable `allow-compiler-fence-only-wipe` only after reviewing \
104     docs/UNSAFE.md and applying platform-approved memory hygiene controls."
105);
106
107mod alphabet;
108mod buffers;
109mod cleanup;
110mod profiles;
111
112pub use alphabet::{
113    Alphabet, AlphabetError, Bcrypt, Crypt, Standard, UrlSafe, decode_alphabet_byte,
114    validate_alphabet,
115};
116pub(crate) use alphabet::{encode_base64_value, encode_base64_value_runtime};
117pub use buffers::{DecodedBuffer, EncodedBuffer, ExposedDecodedArray, ExposedEncodedArray};
118#[cfg(feature = "alloc")]
119pub use buffers::{ExposedSecretString, ExposedSecretVec, SecretBuffer};
120pub(crate) use cleanup::{wipe_bytes, wipe_tail};
121#[cfg(feature = "alloc")]
122pub(crate) use cleanup::{wipe_vec_all, wipe_vec_spare_capacity};
123pub use profiles::{BCRYPT, CRYPT, MIME, PEM, PEM_CRLF, Profile};
124
125#[cfg(feature = "simd")]
126mod simd;
127
128/// Runtime backend reporting for security-sensitive deployments.
129///
130/// This module does not enable acceleration. It exposes the backend posture so
131/// callers can log, assert, or audit whether execution is scalar-only or merely
132/// detecting future SIMD candidates.
133pub mod runtime;
134
135#[cfg(feature = "stream")]
136pub mod stream;
137
138/// Constant-time-oriented scalar decoding APIs.
139///
140/// This module is separate from the default decoder so callers can opt into a
141/// slower path with a narrower timing target. It avoids lookup tables indexed
142/// by secret input bytes while mapping Base64 symbols and reports malformed
143/// content through one opaque error. It is not documented as a formally
144/// verified cryptographic constant-time API.
145///
146/// # Security
147///
148/// Input length, decoded length, selected alphabet, and final success or
149/// failure remain public. The clear-tail methods wipe caller-owned output on
150/// error, but decoded bytes are written during the fixed-shape decode loop
151/// before final validation is reported. In shared-memory, enclave, or HSM-style
152/// threat models where another component can observe the output buffer during
153/// the call, prefer [`crate::ct::CtEngine::decode_slice_staged_clear_tail`]
154/// with a private staging buffer. In those deployments,
155/// [`crate::ct::CtEngine::decode_slice_clear_tail`] is not sufficient by
156/// itself because it wipes caller-owned output only after the internal decode
157/// loop reaches the final error gate.
158///
159/// The dependency-free comparison helpers on redacted buffers are
160/// constant-time-oriented best effort, not formally audited MAC or token
161/// comparison primitives. Applications that can admit dependencies and need a
162/// reviewed comparison primitive should use one at the protocol boundary.
163///
164/// The CT decoder exposes only clear-tail and stack-backed decode APIs. The
165/// former non-clear-tail methods were removed before the `1.0` stable boundary
166/// because they could leave decoded plaintext in caller-owned buffers after
167/// malformed input errors.
168///
169/// ```compile_fail
170/// use base64_ng::ct;
171///
172/// let mut output = [0u8; 8];
173/// let _ = ct::STANDARD.decode_slice(b"aGk=", &mut output);
174/// ```
175///
176/// ```compile_fail
177/// use base64_ng::ct;
178///
179/// let mut buffer = *b"aGk=";
180/// let _ = ct::STANDARD.decode_in_place(&mut buffer);
181/// ```
182pub mod ct {
183    use super::{
184        Alphabet, DecodeError, DecodedBuffer, Standard, UrlSafe, ct_decode_in_place,
185        ct_decode_slice, ct_decode_slice_staged_clear_tail, ct_decoded_len, ct_validate_decode,
186    };
187    use core::marker::PhantomData;
188
189    /// Standard Base64 constant-time-oriented decoder with padding.
190    pub const STANDARD: CtEngine<Standard, true> = CtEngine::new();
191
192    /// Standard Base64 constant-time-oriented decoder without padding.
193    pub const STANDARD_NO_PAD: CtEngine<Standard, false> = CtEngine::new();
194
195    /// URL-safe Base64 constant-time-oriented decoder with padding.
196    pub const URL_SAFE: CtEngine<UrlSafe, true> = CtEngine::new();
197
198    /// URL-safe Base64 constant-time-oriented decoder without padding.
199    pub const URL_SAFE_NO_PAD: CtEngine<UrlSafe, false> = CtEngine::new();
200
201    /// A zero-sized constant-time-oriented Base64 decoder.
202    ///
203    /// # Security
204    ///
205    /// For ordinary secret-bearing inputs, prefer
206    /// [`Self::decode_slice_clear_tail`], [`Self::decode_buffer`], or
207    /// [`Self::decode_in_place_clear_tail`]. For shared-memory,
208    /// enclave-adjacent, HSM-style, or multi-principal deployments where
209    /// another component can observe caller-owned output during the call, use
210    /// [`Self::decode_slice_staged_clear_tail`] with a private staging buffer
211    /// so malformed input cannot transiently write decoded bytes into the
212    /// public output buffer before the final error gate.
213    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
214    pub struct CtEngine<A, const PAD: bool> {
215        alphabet: PhantomData<A>,
216    }
217
218    impl<A, const PAD: bool> CtEngine<A, PAD>
219    where
220        A: Alphabet,
221    {
222        /// Creates a new constant-time-oriented decoder engine.
223        #[must_use]
224        pub const fn new() -> Self {
225            Self {
226                alphabet: PhantomData,
227            }
228        }
229
230        /// Returns whether this constant-time-oriented decoder expects padded
231        /// input.
232        #[must_use]
233        pub const fn is_padded(&self) -> bool {
234            PAD
235        }
236
237        /// Validates `input` without writing decoded bytes.
238        ///
239        /// This uses the same constant-time-oriented symbol mapping and opaque
240        /// malformed-input error behavior as
241        /// [`Self::decode_slice_clear_tail`]. Input length, padding length, and
242        /// final success or failure remain public.
243        ///
244        /// # Examples
245        ///
246        /// ```
247        /// use base64_ng::ct;
248        ///
249        /// ct::STANDARD.validate_result(b"aGVsbG8=").unwrap();
250        /// assert!(ct::STANDARD.validate_result(b"aGVsbG8").is_err());
251        /// ```
252        pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
253            ct_validate_decode::<A, PAD>(input)
254        }
255
256        /// Returns whether `input` is valid for this constant-time-oriented
257        /// decoder.
258        ///
259        /// This is a convenience wrapper around [`Self::validate_result`].
260        ///
261        /// # Examples
262        ///
263        /// ```
264        /// use base64_ng::ct;
265        ///
266        /// assert!(ct::URL_SAFE_NO_PAD.validate(b"-_8"));
267        /// assert!(!ct::URL_SAFE_NO_PAD.validate(b"+/8"));
268        /// ```
269        #[must_use]
270        pub fn validate(&self, input: &[u8]) -> bool {
271            self.validate_result(input).is_ok()
272        }
273
274        /// Returns the exact decoded length for valid input.
275        ///
276        /// This uses the same constant-time-oriented validation policy as
277        /// [`Self::validate_result`] before returning a length. Input length,
278        /// padding length, and final success or failure remain public.
279        pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
280            ct_decoded_len::<A, PAD>(input)
281        }
282
283        /// Decodes `input` into `output` and clears all bytes after the
284        /// decoded prefix.
285        ///
286        /// If decoding fails, the entire output buffer is cleared before the
287        /// error is returned. Use this variant for sensitive payloads where
288        /// partially decoded bytes from rejected input should not remain in the
289        /// caller-owned output buffer.
290        ///
291        /// # Security: Transient Plaintext Window
292        ///
293        /// Decoded bytes are written to `output` progressively during the
294        /// fixed-shape decode loop before malformed-input detection is
295        /// complete. On error, the entire `output` is wiped before returning,
296        /// but a concurrent same-process observer with access to `output`
297        /// during the call may observe transient partial plaintext from valid
298        /// leading quanta. For shared-memory, enclave-adjacent, HSM-style, or
299        /// multi-principal deployments where even transient writes are
300        /// unacceptable, use [`Self::decode_slice_staged_clear_tail`] with a
301        /// private staging buffer.
302        ///
303        /// # Examples
304        ///
305        /// ```
306        /// use base64_ng::ct;
307        ///
308        /// let mut output = [0xff; 8];
309        /// let written = ct::STANDARD
310        ///     .decode_slice_clear_tail(b"aGk=", &mut output)
311        ///     .unwrap();
312        ///
313        /// assert_eq!(&output[..written], b"hi");
314        /// assert!(output[written..].iter().all(|byte| *byte == 0));
315        /// ```
316        #[must_use = "handle decode errors; use decode_slice_staged_clear_tail for shared-memory or HSM-style threat models"]
317        pub fn decode_slice_clear_tail(
318            &self,
319            input: &[u8],
320            output: &mut [u8],
321        ) -> Result<usize, DecodeError> {
322            let written = match ct_decode_slice::<A, PAD>(input, output) {
323                Ok(written) => written,
324                Err(err) => {
325                    crate::wipe_bytes(output);
326                    return Err(err);
327                }
328            };
329            crate::wipe_tail(output, written);
330            Ok(written)
331        }
332
333        /// Decodes through caller-provided private staging before copying into
334        /// `output`.
335        ///
336        /// This variant is for shared-memory or sandboxed deployments where
337        /// the caller-owned `output` buffer must not contain transient decoded
338        /// bytes from malformed input. The `staging` buffer must be at least
339        /// the decoded length of `input` and must not be shared with
340        /// untrusted concurrent observers. On success, decoded bytes are
341        /// copied from `staging` into `output`; on error, both buffers are
342        /// cleared before returning.
343        ///
344        /// Input length, final success or failure, and decoded length remain
345        /// public.
346        #[must_use = "handle decode errors; staged decode is for shared-memory or HSM-style threat models"]
347        pub fn decode_slice_staged_clear_tail(
348            &self,
349            input: &[u8],
350            output: &mut [u8],
351            staging: &mut [u8],
352        ) -> Result<usize, DecodeError> {
353            ct_decode_slice_staged_clear_tail::<A, PAD>(input, output, staging)
354        }
355
356        /// Decodes `input` into a stack-backed buffer.
357        ///
358        /// This uses the same constant-time-oriented scalar decoder as
359        /// [`Self::decode_slice_clear_tail`] and clears the internal backing
360        /// array before returning an error.
361        ///
362        /// # Examples
363        ///
364        /// ```
365        /// use base64_ng::ct;
366        ///
367        /// let decoded = ct::STANDARD.decode_buffer::<5>(b"aGVsbG8=").unwrap();
368        ///
369        /// assert_eq!(decoded.as_bytes(), b"hello");
370        /// ```
371        pub fn decode_buffer<const CAP: usize>(
372            &self,
373            input: &[u8],
374        ) -> Result<DecodedBuffer<CAP>, DecodeError> {
375            let mut output = DecodedBuffer::new();
376            let written = match self.decode_slice_clear_tail(input, output.as_mut_capacity()) {
377                Ok(written) => written,
378                Err(err) => {
379                    output.clear();
380                    return Err(err);
381                }
382            };
383            output.set_filled(written)?;
384            Ok(output)
385        }
386
387        /// Decodes `buffer` in place and clears all bytes after the decoded
388        /// prefix.
389        ///
390        /// If decoding fails, the entire buffer is cleared before the error is
391        /// returned.
392        ///
393        /// # Security: Transient Plaintext Window
394        ///
395        /// This in-place API writes decoded bytes into `buffer` during the
396        /// fixed-shape decode loop before malformed-input detection is
397        /// complete. On error, the entire buffer is wiped before returning,
398        /// but concurrent same-process observers with access to the same memory
399        /// can observe transient partial plaintext. Use
400        /// [`Self::decode_slice_staged_clear_tail`] with a private staging
401        /// buffer when shared-memory or enclave-adjacent deployments cannot
402        /// tolerate that window.
403        ///
404        /// # Examples
405        ///
406        /// ```
407        /// use base64_ng::ct;
408        ///
409        /// let mut buffer = *b"aGk=";
410        /// let decoded = ct::STANDARD.decode_in_place_clear_tail(&mut buffer).unwrap();
411        ///
412        /// assert_eq!(decoded, b"hi");
413        /// ```
414        pub fn decode_in_place_clear_tail<'a>(
415            &self,
416            buffer: &'a mut [u8],
417        ) -> Result<&'a mut [u8], DecodeError> {
418            let len = match ct_decode_in_place::<A, PAD>(buffer) {
419                Ok(len) => len,
420                Err(err) => {
421                    crate::wipe_bytes(buffer);
422                    return Err(err);
423                }
424            };
425            crate::wipe_tail(buffer, len);
426            Ok(&mut buffer[..len])
427        }
428    }
429
430    impl<A, const PAD: bool> core::fmt::Display for CtEngine<A, PAD> {
431        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
432            write!(formatter, "ct padded={PAD}")
433        }
434    }
435}
436
437/// Standard Base64 engine with padding.
438///
439/// This default strict engine is not a constant-time token validator or
440/// key-material decoder. Use [`ct::STANDARD`] or [`Engine::ct_decoder`] for the
441/// matching constant-time-oriented decoder when timing posture matters.
442#[doc(alias = "ct")]
443#[doc(alias = "constant_time")]
444#[doc(alias = "sensitive")]
445pub const STANDARD: Engine<Standard, true> = Engine::new();
446
447/// Standard Base64 engine without padding.
448///
449/// This default strict engine is not a constant-time token validator or
450/// key-material decoder. Use [`ct::STANDARD_NO_PAD`] or [`Engine::ct_decoder`]
451/// for the matching constant-time-oriented decoder when timing posture
452/// matters.
453#[doc(alias = "ct")]
454#[doc(alias = "constant_time")]
455#[doc(alias = "sensitive")]
456pub const STANDARD_NO_PAD: Engine<Standard, false> = Engine::new();
457
458/// URL-safe Base64 engine with padding.
459///
460/// This default strict engine is not a constant-time token validator or
461/// key-material decoder. Use [`ct::URL_SAFE`] or [`Engine::ct_decoder`] for the
462/// matching constant-time-oriented decoder when timing posture matters.
463#[doc(alias = "ct")]
464#[doc(alias = "constant_time")]
465#[doc(alias = "sensitive")]
466pub const URL_SAFE: Engine<UrlSafe, true> = Engine::new();
467
468/// URL-safe Base64 engine without padding.
469///
470/// This default strict engine is not a constant-time token validator or
471/// key-material decoder. Use [`ct::URL_SAFE_NO_PAD`] or [`Engine::ct_decoder`]
472/// for the matching constant-time-oriented decoder when timing posture
473/// matters.
474#[doc(alias = "ct")]
475#[doc(alias = "constant_time")]
476#[doc(alias = "sensitive")]
477pub const URL_SAFE_NO_PAD: Engine<UrlSafe, false> = Engine::new();
478
479/// bcrypt-style Base64 engine without padding.
480///
481/// This uses the bcrypt alphabet with the crate's normal Base64 bit packing.
482/// It does not parse complete bcrypt password-hash strings. This default strict
483/// engine is not a constant-time token validator or key-material decoder; use
484/// [`Engine::ct_decoder`] for the matching constant-time-oriented decoder when
485/// timing posture matters.
486#[doc(alias = "ct")]
487#[doc(alias = "constant_time")]
488#[doc(alias = "sensitive")]
489pub const BCRYPT_NO_PAD: Engine<Bcrypt, false> = Engine::new();
490
491/// Unix `crypt(3)`-style Base64 engine without padding.
492///
493/// This uses the `crypt(3)` alphabet with the crate's normal Base64 bit
494/// packing. It does not parse complete password-hash strings. This default
495/// strict engine is not a constant-time token validator or key-material
496/// decoder; use [`Engine::ct_decoder`] for the matching constant-time-oriented
497/// decoder when timing posture matters.
498#[doc(alias = "ct")]
499#[doc(alias = "constant_time")]
500#[doc(alias = "sensitive")]
501pub const CRYPT_NO_PAD: Engine<Crypt, false> = Engine::new();
502
503/// Line ending used by wrapped Base64 output.
504#[derive(Clone, Copy, Debug, Eq, PartialEq)]
505pub enum LineEnding {
506    /// Line feed (`\n`).
507    Lf,
508    /// Carriage return followed by line feed (`\r\n`).
509    CrLf,
510}
511
512impl LineEnding {
513    /// Returns a stable printable identifier for this line ending.
514    #[must_use]
515    pub const fn name(self) -> &'static str {
516        match self {
517            Self::Lf => "LF",
518            Self::CrLf => "CRLF",
519        }
520    }
521
522    /// Returns the text representation of this line ending.
523    #[must_use]
524    pub const fn as_str(self) -> &'static str {
525        match self {
526            Self::Lf => "\n",
527            Self::CrLf => "\r\n",
528        }
529    }
530
531    /// Returns the byte representation of this line ending.
532    #[must_use]
533    pub const fn as_bytes(self) -> &'static [u8] {
534        self.as_str().as_bytes()
535    }
536
537    /// Returns the byte length of this line ending.
538    #[must_use]
539    pub const fn byte_len(self) -> usize {
540        match self {
541            Self::Lf => 1,
542            Self::CrLf => 2,
543        }
544    }
545}
546
547impl core::fmt::Display for LineEnding {
548    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
549        formatter.write_str(self.name())
550    }
551}
552
553/// Base64 line wrapping policy.
554///
555/// `line_len` is measured in encoded Base64 bytes, not source input bytes.
556/// Encoders insert line endings between lines and do not append a trailing line
557/// ending after the final line.
558#[derive(Clone, Copy, Debug, Eq, PartialEq)]
559pub struct LineWrap {
560    /// Maximum encoded bytes per line.
561    pub line_len: usize,
562    /// Line ending inserted between wrapped lines.
563    pub line_ending: LineEnding,
564}
565
566impl LineWrap {
567    /// MIME-style wrapping: 76 columns with CRLF endings.
568    pub const MIME: Self = Self::new(76, LineEnding::CrLf);
569    /// PEM-style wrapping: 64 columns with LF endings.
570    pub const PEM: Self = Self::new(64, LineEnding::Lf);
571    /// PEM-style wrapping: 64 columns with CRLF endings.
572    pub const PEM_CRLF: Self = Self::new(64, LineEnding::CrLf);
573
574    /// Creates a wrapping policy.
575    ///
576    /// This constructor is intended for fixed, trusted values such as
577    /// compile-time MIME or PEM profile constants. Use [`Self::checked_new`]
578    /// when the line length comes from configuration, network input, file
579    /// metadata, or another untrusted runtime source.
580    ///
581    /// # Panics
582    ///
583    /// Panics when `line_len` is zero. Base64 wrapping requires a non-zero
584    /// encoded line length; accepting zero would make progress impossible for
585    /// wrapped encoders. This constructor is callable at runtime, so do not
586    /// pass attacker-controlled or externally configured values here; use
587    /// [`Self::checked_new`] for those cases.
588    #[must_use]
589    pub const fn new(line_len: usize, line_ending: LineEnding) -> Self {
590        assert!(line_len != 0, "base64 line wrap length must be non-zero");
591        Self {
592            line_len,
593            line_ending,
594        }
595    }
596
597    /// Creates a wrapping policy, returning `None` when the line length is
598    /// invalid.
599    ///
600    /// Base64 line-wrapping requires a non-zero encoded line length. This
601    /// helper is useful when accepting a wrapping policy from configuration or
602    /// another untrusted source.
603    #[must_use]
604    pub const fn checked_new(line_len: usize, line_ending: LineEnding) -> Option<Self> {
605        if line_len == 0 {
606            None
607        } else {
608            Some(Self::new(line_len, line_ending))
609        }
610    }
611
612    /// Returns the maximum encoded bytes per line.
613    #[must_use]
614    pub const fn line_len(self) -> usize {
615        self.line_len
616    }
617
618    /// Returns the line ending inserted between wrapped lines.
619    #[must_use]
620    pub const fn line_ending(self) -> LineEnding {
621        self.line_ending
622    }
623
624    /// Returns whether this wrapping policy can be used by the encoder.
625    #[must_use]
626    pub const fn is_valid(self) -> bool {
627        self.line_len != 0
628    }
629}
630
631impl core::fmt::Display for LineWrap {
632    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
633        write!(formatter, "{}:{}", self.line_len, self.line_ending.name())
634    }
635}
636
637/// Returns the encoded length for an input length and padding policy.
638///
639/// This function returns [`EncodeError::LengthOverflow`] instead of panicking.
640/// Use [`checked_encoded_len`] when an `Option<usize>` is more convenient.
641///
642/// # Examples
643///
644/// ```
645/// use base64_ng::encoded_len;
646///
647/// assert_eq!(encoded_len(5, true).unwrap(), 8);
648/// assert_eq!(encoded_len(5, false).unwrap(), 7);
649/// assert!(encoded_len(usize::MAX, true).is_err());
650/// ```
651pub const fn encoded_len(input_len: usize, padded: bool) -> Result<usize, EncodeError> {
652    match checked_encoded_len(input_len, padded) {
653        Some(len) => Ok(len),
654        None => Err(EncodeError::LengthOverflow),
655    }
656}
657
658/// Returns the encoded length after applying a line wrapping policy.
659///
660/// The returned length includes inserted line endings but does not include a
661/// trailing line ending after the final encoded line.
662///
663/// # Examples
664///
665/// ```
666/// use base64_ng::{LineEnding, LineWrap, wrapped_encoded_len};
667///
668/// let wrap = LineWrap::new(4, LineEnding::Lf);
669/// assert_eq!(wrapped_encoded_len(5, true, wrap).unwrap(), 9);
670/// ```
671pub const fn wrapped_encoded_len(
672    input_len: usize,
673    padded: bool,
674    wrap: LineWrap,
675) -> Result<usize, EncodeError> {
676    if wrap.line_len == 0 {
677        return Err(EncodeError::InvalidLineWrap { line_len: 0 });
678    }
679
680    let Some(encoded) = checked_encoded_len(input_len, padded) else {
681        return Err(EncodeError::LengthOverflow);
682    };
683    if encoded == 0 {
684        return Ok(0);
685    }
686
687    let breaks = (encoded - 1) / wrap.line_len;
688    let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
689        return Err(EncodeError::LengthOverflow);
690    };
691    match encoded.checked_add(line_ending_bytes) {
692        Some(len) => Ok(len),
693        None => Err(EncodeError::LengthOverflow),
694    }
695}
696
697/// Returns the encoded length after line wrapping, or `None` on overflow or
698/// invalid line wrapping.
699///
700/// The returned length includes inserted line endings but does not include a
701/// trailing line ending after the final encoded line.
702///
703/// # Examples
704///
705/// ```
706/// use base64_ng::{LineEnding, LineWrap, checked_wrapped_encoded_len};
707///
708/// let wrap = LineWrap::new(4, LineEnding::Lf);
709/// assert_eq!(checked_wrapped_encoded_len(5, true, wrap), Some(9));
710/// assert_eq!(LineWrap::checked_new(0, LineEnding::Lf), None);
711/// ```
712#[must_use]
713pub const fn checked_wrapped_encoded_len(
714    input_len: usize,
715    padded: bool,
716    wrap: LineWrap,
717) -> Option<usize> {
718    if wrap.line_len == 0 {
719        return None;
720    }
721
722    let Some(encoded) = checked_encoded_len(input_len, padded) else {
723        return None;
724    };
725    if encoded == 0 {
726        return Some(0);
727    }
728
729    let breaks = (encoded - 1) / wrap.line_len;
730    let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
731        return None;
732    };
733    encoded.checked_add(line_ending_bytes)
734}
735
736/// Returns the encoded length, or `None` if it would overflow `usize`.
737///
738/// # Examples
739///
740/// ```
741/// use base64_ng::checked_encoded_len;
742///
743/// assert_eq!(checked_encoded_len(5, true), Some(8));
744/// assert_eq!(checked_encoded_len(usize::MAX, true), None);
745/// ```
746#[must_use]
747pub const fn checked_encoded_len(input_len: usize, padded: bool) -> Option<usize> {
748    let groups = input_len / 3;
749    if groups > usize::MAX / 4 {
750        return None;
751    }
752    let full = groups * 4;
753    let rem = input_len % 3;
754    if rem == 0 {
755        Some(full)
756    } else if padded {
757        full.checked_add(4)
758    } else {
759        full.checked_add(rem + 1)
760    }
761}
762
763/// Compares two fixed-width byte arrays without a length-mismatch branch.
764///
765/// Use this helper when the value length itself should not be represented as a
766/// timing-distinct branch in the comparison API. The array length `N` is a
767/// compile-time public type fact, and the helper scans exactly `N` bytes before
768/// returning. The final equality result remains public. This is still a
769/// dependency-free, constant-time-oriented best-effort helper, not a formally
770/// verified cryptographic comparison primitive.
771///
772/// # Examples
773///
774/// ```
775/// use base64_ng::constant_time_eq_fixed_width;
776///
777/// assert!(constant_time_eq_fixed_width(b"token", b"token"));
778/// assert!(!constant_time_eq_fixed_width(b"token", b"Token"));
779/// ```
780#[must_use]
781pub fn constant_time_eq_fixed_width<const N: usize>(left: &[u8; N], right: &[u8; N]) -> bool {
782    constant_time_eq_fixed_width_array(left, right)
783}
784
785/// Returns the maximum decoded length for an encoded input length.
786///
787/// # Examples
788///
789/// ```
790/// use base64_ng::decoded_capacity;
791///
792/// assert_eq!(decoded_capacity(8), 6);
793/// assert_eq!(decoded_capacity(7), 5);
794/// ```
795#[must_use]
796pub const fn decoded_capacity(encoded_len: usize) -> usize {
797    let rem = encoded_len % 4;
798    encoded_len / 4 * 3
799        + if rem == 2 {
800            1
801        } else if rem == 3 {
802            2
803        } else {
804            0
805        }
806}
807
808/// Returns the exact decoded length implied by input length and padding.
809///
810/// This validates padding placement and impossible lengths, but it does not
811/// validate alphabet membership or non-canonical trailing bits.
812///
813/// # Examples
814///
815/// ```
816/// use base64_ng::decoded_len;
817///
818/// assert_eq!(decoded_len(b"aGVsbG8=", true).unwrap(), 5);
819/// assert_eq!(decoded_len(b"aGVsbG8", false).unwrap(), 5);
820/// ```
821pub fn decoded_len(input: &[u8], padded: bool) -> Result<usize, DecodeError> {
822    if padded {
823        decoded_len_padded(input)
824    } else {
825        decoded_len_unpadded(input)
826    }
827}
828
829#[inline]
830pub(crate) const fn ct_mask_bit(bit: u8) -> u8 {
831    0u8.wrapping_sub(bit & 1)
832}
833
834#[inline]
835pub(crate) const fn ct_mask_nonzero_u8(value: u8) -> u8 {
836    let wide = value as u16;
837    let negative = 0u16.wrapping_sub(wide);
838    let nonzero = ((wide | negative) >> 8) as u8;
839    ct_mask_bit(nonzero)
840}
841
842#[inline]
843pub(crate) const fn ct_mask_eq_u8(left: u8, right: u8) -> u8 {
844    !ct_mask_nonzero_u8(left ^ right)
845}
846
847#[inline]
848pub(crate) const fn ct_mask_lt_u8(left: u8, right: u8) -> u8 {
849    let diff = (left as u16).wrapping_sub(right as u16);
850    ct_mask_bit((diff >> 8) as u8)
851}
852
853#[inline(never)]
854fn constant_time_eq_public_len(left: &[u8], right: &[u8]) -> bool {
855    if left.len() != right.len() {
856        return false;
857    }
858
859    constant_time_eq_same_len(left, right)
860}
861
862#[inline(never)]
863fn constant_time_eq_fixed_width_array<const N: usize>(left: &[u8; N], right: &[u8; N]) -> bool {
864    constant_time_eq_same_len(left, right)
865}
866
867#[inline(never)]
868#[allow(unsafe_code)]
869fn constant_time_eq_same_len(left: &[u8], right: &[u8]) -> bool {
870    let mut diff = 0u8;
871    for (left, right) in left.iter().zip(right) {
872        diff = core::hint::black_box(
873            core::hint::black_box(diff) | core::hint::black_box(*left ^ *right),
874        );
875        // SAFETY: `diff` is an initialized local `u8`; the volatile read is a
876        // dependency-free optimizer barrier for the accumulation value and does
877        // not access caller memory.
878        diff = unsafe { core::ptr::read_volatile(&raw const diff) };
879    }
880    ct_error_gate_barrier(diff, 0);
881    // SAFETY: `diff` is an initialized local `u8`; this final volatile read
882    // keeps the public equality comparison dependent on a post-barrier load of
883    // the accumulated value.
884    let result = unsafe { core::ptr::read_volatile(&raw const diff) };
885    result == 0
886}
887
888mod backend {
889    use super::{
890        Alphabet, DecodeError, EncodeError, checked_encoded_len, decode_padded, decode_unpadded,
891        encode_base64_value_runtime,
892    };
893
894    pub(super) fn encode_slice<A, const PAD: bool>(
895        input: &[u8],
896        output: &mut [u8],
897    ) -> Result<usize, EncodeError>
898    where
899        A: Alphabet,
900    {
901        #[cfg(feature = "simd")]
902        match super::simd::active_backend() {
903            super::simd::ActiveBackend::Scalar => {}
904        }
905
906        scalar_encode_slice::<A, PAD>(input, output)
907    }
908
909    pub(super) fn decode_slice<A, const PAD: bool>(
910        input: &[u8],
911        output: &mut [u8],
912    ) -> Result<usize, DecodeError>
913    where
914        A: Alphabet,
915    {
916        #[cfg(feature = "simd")]
917        match super::simd::active_backend() {
918            super::simd::ActiveBackend::Scalar => {}
919        }
920
921        scalar_decode_slice::<A, PAD>(input, output)
922    }
923
924    #[cfg(test)]
925    pub(super) fn scalar_reference_encode_slice<A, const PAD: bool>(
926        input: &[u8],
927        output: &mut [u8],
928    ) -> Result<usize, EncodeError>
929    where
930        A: Alphabet,
931    {
932        scalar_encode_slice::<A, PAD>(input, output)
933    }
934
935    #[cfg(test)]
936    pub(super) fn scalar_reference_decode_slice<A, const PAD: bool>(
937        input: &[u8],
938        output: &mut [u8],
939    ) -> Result<usize, DecodeError>
940    where
941        A: Alphabet,
942    {
943        scalar_decode_slice::<A, PAD>(input, output)
944    }
945
946    fn scalar_encode_slice<A, const PAD: bool>(
947        input: &[u8],
948        output: &mut [u8],
949    ) -> Result<usize, EncodeError>
950    where
951        A: Alphabet,
952    {
953        let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
954        if output.len() < required {
955            return Err(EncodeError::OutputTooSmall {
956                required,
957                available: output.len(),
958            });
959        }
960
961        let mut read = 0;
962        let mut write = 0;
963        while read + 3 <= input.len() {
964            let b0 = input[read];
965            let b1 = input[read + 1];
966            let b2 = input[read + 2];
967
968            output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
969            output[write + 1] =
970                encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
971            output[write + 2] =
972                encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
973            output[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
974
975            read += 3;
976            write += 4;
977        }
978
979        match input.len() - read {
980            0 => {}
981            1 => {
982                let b0 = input[read];
983                output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
984                output[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
985                write += 2;
986                if PAD {
987                    output[write] = b'=';
988                    output[write + 1] = b'=';
989                    write += 2;
990                }
991            }
992            2 => {
993                let b0 = input[read];
994                let b1 = input[read + 1];
995                output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
996                output[write + 1] =
997                    encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
998                output[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
999                write += 3;
1000                if PAD {
1001                    output[write] = b'=';
1002                    write += 1;
1003                }
1004            }
1005            _ => unreachable!(),
1006        }
1007
1008        Ok(write)
1009    }
1010
1011    fn scalar_decode_slice<A, const PAD: bool>(
1012        input: &[u8],
1013        output: &mut [u8],
1014    ) -> Result<usize, DecodeError>
1015    where
1016        A: Alphabet,
1017    {
1018        if input.is_empty() {
1019            return Ok(0);
1020        }
1021
1022        if PAD {
1023            decode_padded::<A>(input, output)
1024        } else {
1025            decode_unpadded::<A>(input, output)
1026        }
1027    }
1028}
1029
1030/// A zero-sized Base64 engine parameterized by alphabet and padding policy.
1031pub struct Engine<A, const PAD: bool> {
1032    alphabet: core::marker::PhantomData<A>,
1033}
1034
1035impl<A, const PAD: bool> Clone for Engine<A, PAD> {
1036    fn clone(&self) -> Self {
1037        *self
1038    }
1039}
1040
1041impl<A, const PAD: bool> Copy for Engine<A, PAD> {}
1042
1043impl<A, const PAD: bool> core::fmt::Debug for Engine<A, PAD> {
1044    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1045        formatter
1046            .debug_struct("Engine")
1047            .field("padded", &PAD)
1048            .finish()
1049    }
1050}
1051
1052impl<A, const PAD: bool> core::fmt::Display for Engine<A, PAD> {
1053    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1054        write!(formatter, "padded={PAD}")
1055    }
1056}
1057
1058impl<A, const PAD: bool> Default for Engine<A, PAD> {
1059    fn default() -> Self {
1060        Self {
1061            alphabet: core::marker::PhantomData,
1062        }
1063    }
1064}
1065
1066impl<A, const PAD: bool> Eq for Engine<A, PAD> {}
1067
1068impl<A, const PAD: bool> PartialEq for Engine<A, PAD> {
1069    fn eq(&self, _other: &Self) -> bool {
1070        true
1071    }
1072}
1073
1074impl<A, const PAD: bool> Engine<A, PAD>
1075where
1076    A: Alphabet,
1077{
1078    /// Creates a new engine value.
1079    #[must_use]
1080    pub const fn new() -> Self {
1081        Self {
1082            alphabet: core::marker::PhantomData,
1083        }
1084    }
1085
1086    /// Returns whether this engine uses padded Base64.
1087    #[must_use]
1088    pub const fn is_padded(&self) -> bool {
1089        PAD
1090    }
1091
1092    /// Returns this engine as an unwrapped profile.
1093    ///
1094    /// Use [`Profile::new`] or [`Profile::checked_new`] when a strict
1095    /// line-wrapping policy should travel with the profile.
1096    #[must_use]
1097    pub const fn profile(&self) -> Profile<A, PAD> {
1098        Profile::new(*self, None)
1099    }
1100
1101    /// Returns the matching constant-time-oriented decoder for this engine's
1102    /// alphabet and padding policy.
1103    ///
1104    /// The returned decoder is still an explicit opt-in to the [`ct`] module's
1105    /// slower, opaque-error, constant-time-oriented scalar path.
1106    #[must_use]
1107    pub const fn ct_decoder(&self) -> ct::CtEngine<A, PAD> {
1108        ct::CtEngine::new()
1109    }
1110
1111    /// Wraps a `std::io::Write` value in a streaming Base64 encoder.
1112    ///
1113    /// This is a convenience constructor for [`stream::Encoder::new`] that
1114    /// keeps the selected engine attached to the call site.
1115    ///
1116    /// ```
1117    /// use std::io::Write;
1118    /// use base64_ng::STANDARD;
1119    ///
1120    /// let mut encoder = STANDARD.encoder_writer(Vec::new());
1121    /// encoder.write_all(b"hello").unwrap();
1122    /// assert_eq!(encoder.finish().unwrap(), b"aGVsbG8=");
1123    /// ```
1124    #[cfg(feature = "stream")]
1125    #[must_use]
1126    pub fn encoder_writer<W>(&self, inner: W) -> stream::Encoder<W, A, PAD> {
1127        stream::Encoder::new(inner, *self)
1128    }
1129
1130    /// Wraps a `std::io::Write` value in a streaming Base64 decoder.
1131    ///
1132    /// This is a convenience constructor for [`stream::Decoder::new`] that
1133    /// keeps the selected engine attached to the call site.
1134    ///
1135    /// ```
1136    /// use std::io::Write;
1137    /// use base64_ng::STANDARD;
1138    ///
1139    /// let mut decoder = STANDARD.decoder_writer(Vec::new());
1140    /// decoder.write_all(b"aGVsbG8=").unwrap();
1141    /// assert_eq!(decoder.finish().unwrap(), b"hello");
1142    /// ```
1143    ///
1144    /// # Security
1145    ///
1146    /// Streaming decoders use the normal strict decode path, not the
1147    /// [`crate::ct`] module. Do not use this adapter for secret-bearing
1148    /// payloads when malformed-input timing matters.
1149    #[cfg(feature = "stream")]
1150    #[must_use]
1151    pub fn decoder_writer<W>(&self, inner: W) -> stream::Decoder<W, A, PAD> {
1152        stream::Decoder::new(inner, *self)
1153    }
1154
1155    /// Wraps a `std::io::Read` value in a streaming Base64 encoder.
1156    ///
1157    /// This is a convenience constructor for [`stream::EncoderReader::new`]
1158    /// that keeps the selected engine attached to the call site.
1159    ///
1160    /// ```
1161    /// use std::io::Read;
1162    /// use base64_ng::STANDARD;
1163    ///
1164    /// let mut reader = STANDARD.encoder_reader(&b"hello"[..]);
1165    /// let mut encoded = String::new();
1166    /// reader.read_to_string(&mut encoded).unwrap();
1167    /// assert_eq!(encoded, "aGVsbG8=");
1168    /// ```
1169    #[cfg(feature = "stream")]
1170    #[must_use]
1171    pub fn encoder_reader<R>(&self, inner: R) -> stream::EncoderReader<R, A, PAD> {
1172        stream::EncoderReader::new(inner, *self)
1173    }
1174
1175    /// Wraps a `std::io::Read` value in a streaming Base64 decoder.
1176    ///
1177    /// This is a convenience constructor for [`stream::DecoderReader::new`]
1178    /// that keeps the selected engine attached to the call site.
1179    ///
1180    /// ```
1181    /// use std::io::Read;
1182    /// use base64_ng::STANDARD;
1183    ///
1184    /// let mut reader = STANDARD.decoder_reader(&b"aGVsbG8="[..]);
1185    /// let mut decoded = Vec::new();
1186    /// reader.read_to_end(&mut decoded).unwrap();
1187    /// assert_eq!(decoded, b"hello");
1188    /// ```
1189    ///
1190    /// # Security
1191    ///
1192    /// Streaming decoder readers use the normal strict decode path, not the
1193    /// [`crate::ct`] module. Do not use this adapter for secret-bearing
1194    /// payloads when malformed-input timing matters.
1195    #[cfg(feature = "stream")]
1196    #[must_use]
1197    pub fn decoder_reader<R>(&self, inner: R) -> stream::DecoderReader<R, A, PAD> {
1198        stream::DecoderReader::new(inner, *self)
1199    }
1200
1201    /// Returns the encoded length for this engine's padding policy.
1202    pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
1203        encoded_len(input_len, PAD)
1204    }
1205
1206    /// Returns the encoded length for this engine, or `None` on overflow.
1207    #[must_use]
1208    pub const fn checked_encoded_len(&self, input_len: usize) -> Option<usize> {
1209        checked_encoded_len(input_len, PAD)
1210    }
1211
1212    /// Returns the encoded length after applying a line wrapping policy.
1213    ///
1214    /// The returned length includes inserted line endings but does not include
1215    /// a trailing line ending after the final encoded line.
1216    pub const fn wrapped_encoded_len(
1217        &self,
1218        input_len: usize,
1219        wrap: LineWrap,
1220    ) -> Result<usize, EncodeError> {
1221        wrapped_encoded_len(input_len, PAD, wrap)
1222    }
1223
1224    /// Returns the encoded length after line wrapping, or `None` on overflow or
1225    /// invalid line wrapping.
1226    #[must_use]
1227    pub const fn checked_wrapped_encoded_len(
1228        &self,
1229        input_len: usize,
1230        wrap: LineWrap,
1231    ) -> Option<usize> {
1232        checked_wrapped_encoded_len(input_len, PAD, wrap)
1233    }
1234
1235    /// Returns the exact decoded length implied by input length and padding.
1236    ///
1237    /// This validates padding placement and impossible lengths, but it does not
1238    /// validate alphabet membership or non-canonical trailing bits.
1239    pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
1240        decoded_len(input, PAD)
1241    }
1242
1243    /// Returns the exact decoded length for the explicit legacy profile.
1244    ///
1245    /// The legacy profile ignores ASCII space, tab, carriage return, and line
1246    /// feed bytes before applying the same alphabet, padding, and canonical-bit
1247    /// checks as strict decoding.
1248    pub fn decoded_len_legacy(&self, input: &[u8]) -> Result<usize, DecodeError> {
1249        validate_legacy_decode::<A, PAD>(input)
1250    }
1251
1252    /// Returns the exact decoded length for a line-wrapped profile.
1253    ///
1254    /// The wrapped profile accepts only the configured line ending. Non-final
1255    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
1256    /// may be shorter. A single trailing line ending after the final line is
1257    /// accepted.
1258    pub fn decoded_len_wrapped(&self, input: &[u8], wrap: LineWrap) -> Result<usize, DecodeError> {
1259        validate_wrapped_decode::<A, PAD>(input, wrap)
1260    }
1261
1262    /// Validates strict Base64 input without writing decoded bytes.
1263    ///
1264    /// This applies the same alphabet, padding, and canonical-bit checks as
1265    /// [`Self::decode_slice`]. Use this method when malformed-input
1266    /// diagnostics matter; use [`Self::validate`] when a boolean is enough.
1267    /// This default validator is not constant-time; use
1268    /// [`crate::ct::CtEngine::validate_result`] through [`Self::ct_decoder`]
1269    /// for secret-bearing payloads where timing posture matters.
1270    ///
1271    /// # Examples
1272    ///
1273    /// ```
1274    /// use base64_ng::STANDARD;
1275    ///
1276    /// STANDARD.validate_result(b"aGVsbG8=").unwrap();
1277    /// assert!(STANDARD.validate_result(b"aGVsbG8").is_err());
1278    /// ```
1279    pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
1280        validate_decode::<A, PAD>(input).map(|_| ())
1281    }
1282
1283    /// Returns whether `input` is valid strict Base64 for this engine.
1284    ///
1285    /// This is a convenience wrapper around [`Self::validate_result`] and is
1286    /// not constant-time. Use [`crate::ct::CtEngine::validate`] through
1287    /// [`Self::ct_decoder`] for secret-bearing payloads where timing posture
1288    /// matters.
1289    ///
1290    /// # Examples
1291    ///
1292    /// ```
1293    /// use base64_ng::URL_SAFE_NO_PAD;
1294    ///
1295    /// assert!(URL_SAFE_NO_PAD.validate(b"-_8"));
1296    /// assert!(!URL_SAFE_NO_PAD.validate(b"+/8"));
1297    /// ```
1298    #[must_use]
1299    pub fn validate(&self, input: &[u8]) -> bool {
1300        self.validate_result(input).is_ok()
1301    }
1302
1303    /// Validates input using the explicit legacy whitespace profile.
1304    ///
1305    /// ASCII space, tab, carriage return, and line feed bytes are ignored
1306    /// before applying the same alphabet, padding, and canonical-bit checks as
1307    /// strict decoding.
1308    ///
1309    /// # Examples
1310    ///
1311    /// ```
1312    /// use base64_ng::STANDARD;
1313    ///
1314    /// STANDARD.validate_legacy_result(b" aG\r\nVsbG8= ").unwrap();
1315    /// assert!(STANDARD.validate_legacy_result(b" aG-=").is_err());
1316    /// ```
1317    pub fn validate_legacy_result(&self, input: &[u8]) -> Result<(), DecodeError> {
1318        validate_legacy_decode::<A, PAD>(input).map(|_| ())
1319    }
1320
1321    /// Returns whether `input` is valid for the explicit legacy whitespace
1322    /// profile.
1323    ///
1324    /// This is a convenience wrapper around [`Self::validate_legacy_result`].
1325    ///
1326    /// # Examples
1327    ///
1328    /// ```
1329    /// use base64_ng::STANDARD;
1330    ///
1331    /// assert!(STANDARD.validate_legacy(b" aG\r\nVsbG8= "));
1332    /// assert!(!STANDARD.validate_legacy(b"aG-V"));
1333    /// ```
1334    #[must_use]
1335    pub fn validate_legacy(&self, input: &[u8]) -> bool {
1336        self.validate_legacy_result(input).is_ok()
1337    }
1338
1339    /// Validates input using a strict line-wrapped profile.
1340    ///
1341    /// This is stricter than [`Self::validate_legacy_result`]: it accepts only
1342    /// the configured line ending and enforces the configured line length for
1343    /// every non-final line.
1344    ///
1345    /// # Examples
1346    ///
1347    /// ```
1348    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
1349    ///
1350    /// let wrap = LineWrap::new(4, LineEnding::Lf);
1351    /// STANDARD.validate_wrapped_result(b"aGVs\nbG8=", wrap).unwrap();
1352    /// assert!(STANDARD.validate_wrapped_result(b"aG\nVsbG8=", wrap).is_err());
1353    /// ```
1354    pub fn validate_wrapped_result(&self, input: &[u8], wrap: LineWrap) -> Result<(), DecodeError> {
1355        validate_wrapped_decode::<A, PAD>(input, wrap).map(|_| ())
1356    }
1357
1358    /// Returns whether `input` is valid for a strict line-wrapped profile.
1359    ///
1360    /// This is a convenience wrapper around [`Self::validate_wrapped_result`].
1361    ///
1362    /// # Examples
1363    ///
1364    /// ```
1365    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
1366    ///
1367    /// let wrap = LineWrap::new(4, LineEnding::Lf);
1368    /// assert!(STANDARD.validate_wrapped(b"aGVs\nbG8=", wrap));
1369    /// assert!(!STANDARD.validate_wrapped(b"aG\nVsbG8=", wrap));
1370    /// ```
1371    #[must_use]
1372    pub fn validate_wrapped(&self, input: &[u8], wrap: LineWrap) -> bool {
1373        self.validate_wrapped_result(input, wrap).is_ok()
1374    }
1375
1376    /// Encodes a fixed-size input into a fixed-size output array in const contexts.
1377    ///
1378    /// Stable Rust does not yet allow this API to return an array whose length
1379    /// is computed from `INPUT_LEN` directly. Instead, the caller supplies the
1380    /// output length through the destination type and this function panics
1381    /// during const evaluation if the length is wrong.
1382    ///
1383    /// # Panics
1384    ///
1385    /// Panics if `OUTPUT_LEN` is not exactly the encoded length for `INPUT_LEN`
1386    /// and this engine's padding policy, or if that length overflows `usize`.
1387    ///
1388    /// # Examples
1389    ///
1390    /// ```
1391    /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
1392    ///
1393    /// const HELLO: [u8; 8] = STANDARD.encode_array(b"hello");
1394    /// const URL_SAFE: [u8; 3] = URL_SAFE_NO_PAD.encode_array(b"\xfb\xff");
1395    ///
1396    /// assert_eq!(&HELLO, b"aGVsbG8=");
1397    /// assert_eq!(&URL_SAFE, b"-_8");
1398    /// ```
1399    ///
1400    /// Incorrect output lengths fail during const evaluation:
1401    ///
1402    /// ```compile_fail
1403    /// use base64_ng::STANDARD;
1404    ///
1405    /// const TOO_SHORT: [u8; 7] = STANDARD.encode_array(b"hello");
1406    /// ```
1407    #[must_use]
1408    pub const fn encode_array<const INPUT_LEN: usize, const OUTPUT_LEN: usize>(
1409        &self,
1410        input: &[u8; INPUT_LEN],
1411    ) -> [u8; OUTPUT_LEN] {
1412        let Some(required) = checked_encoded_len(INPUT_LEN, PAD) else {
1413            panic!("encoded base64 length overflows usize");
1414        };
1415        assert!(
1416            required == OUTPUT_LEN,
1417            "base64 output array has incorrect length"
1418        );
1419
1420        let mut output = [0u8; OUTPUT_LEN];
1421        let mut read = 0;
1422        let mut write = 0;
1423        while INPUT_LEN - read >= 3 {
1424            let b0 = input[read];
1425            let b1 = input[read + 1];
1426            let b2 = input[read + 2];
1427
1428            output[write] = encode_base64_value::<A>(b0 >> 2);
1429            output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
1430            output[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
1431            output[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
1432
1433            read += 3;
1434            write += 4;
1435        }
1436
1437        match INPUT_LEN - read {
1438            0 => {}
1439            1 => {
1440                let b0 = input[read];
1441                output[write] = encode_base64_value::<A>(b0 >> 2);
1442                output[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
1443                write += 2;
1444                if PAD {
1445                    output[write] = b'=';
1446                    output[write + 1] = b'=';
1447                }
1448            }
1449            2 => {
1450                let b0 = input[read];
1451                let b1 = input[read + 1];
1452                output[write] = encode_base64_value::<A>(b0 >> 2);
1453                output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
1454                output[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
1455                if PAD {
1456                    output[write + 3] = b'=';
1457                }
1458            }
1459            _ => unreachable!(),
1460        }
1461
1462        output
1463    }
1464
1465    /// Encodes `input` into `output`, returning the number of bytes written.
1466    pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
1467        backend::encode_slice::<A, PAD>(input, output)
1468    }
1469
1470    /// Encodes `input` into `output` with line wrapping.
1471    ///
1472    /// The wrapping policy inserts line endings between encoded lines and does
1473    /// not append a trailing line ending after the final line.
1474    ///
1475    /// # Examples
1476    ///
1477    /// ```
1478    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
1479    ///
1480    /// let wrap = LineWrap::new(4, LineEnding::Lf);
1481    /// let mut output = [0u8; 9];
1482    /// let written = STANDARD
1483    ///     .encode_slice_wrapped(b"hello", &mut output, wrap)
1484    ///     .unwrap();
1485    ///
1486    /// assert_eq!(&output[..written], b"aGVs\nbG8=");
1487    /// ```
1488    pub fn encode_slice_wrapped(
1489        &self,
1490        input: &[u8],
1491        output: &mut [u8],
1492        wrap: LineWrap,
1493    ) -> Result<usize, EncodeError> {
1494        let required = self.wrapped_encoded_len(input.len(), wrap)?;
1495        if output.len() < required {
1496            return Err(EncodeError::OutputTooSmall {
1497                required,
1498                available: output.len(),
1499            });
1500        }
1501
1502        let encoded_len =
1503            checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
1504        if encoded_len == 0 {
1505            return Ok(0);
1506        }
1507
1508        // If the temporary in-buffer layout size overflows, fall back to the
1509        // fixed scratch buffer path rather than relying on saturated arithmetic.
1510        let combined_required = match required.checked_add(encoded_len) {
1511            Some(len) => len,
1512            None => usize::MAX,
1513        };
1514        if output.len() < combined_required {
1515            let mut scratch = [0u8; 1024];
1516            let mut input_offset = 0;
1517            let mut output_offset = 0;
1518            let mut column = 0;
1519
1520            while input_offset < input.len() {
1521                let remaining = input.len() - input_offset;
1522                let mut take = remaining.min(768);
1523                if remaining > take {
1524                    take -= take % 3;
1525                }
1526                if take == 0 {
1527                    take = remaining;
1528                }
1529
1530                let encoded = match self
1531                    .encode_slice(&input[input_offset..input_offset + take], &mut scratch)
1532                {
1533                    Ok(encoded) => encoded,
1534                    Err(err) => {
1535                        wipe_bytes(&mut scratch);
1536                        return Err(err);
1537                    }
1538                };
1539                if let Err(err) = write_wrapped_bytes(
1540                    &scratch[..encoded],
1541                    output,
1542                    &mut output_offset,
1543                    &mut column,
1544                    wrap,
1545                ) {
1546                    wipe_bytes(&mut scratch);
1547                    return Err(err);
1548                }
1549                wipe_bytes(&mut scratch[..encoded]);
1550                input_offset += take;
1551            }
1552
1553            Ok(output_offset)
1554        } else {
1555            let encoded =
1556                self.encode_slice(input, &mut output[required..required + encoded_len])?;
1557            let mut output_offset = 0;
1558            let mut column = 0;
1559            let mut read = required;
1560            while read < required + encoded {
1561                let byte = output[read];
1562                write_wrapped_byte(byte, output, &mut output_offset, &mut column, wrap)?;
1563                read += 1;
1564            }
1565            wipe_bytes(&mut output[required..required + encoded]);
1566            Ok(output_offset)
1567        }
1568    }
1569
1570    /// Encodes `input` with line wrapping and clears all bytes after the
1571    /// encoded prefix.
1572    ///
1573    /// If encoding fails, the entire output buffer is cleared before the error
1574    /// is returned.
1575    pub fn encode_slice_wrapped_clear_tail(
1576        &self,
1577        input: &[u8],
1578        output: &mut [u8],
1579        wrap: LineWrap,
1580    ) -> Result<usize, EncodeError> {
1581        let written = match self.encode_slice_wrapped(input, output, wrap) {
1582            Ok(written) => written,
1583            Err(err) => {
1584                wipe_bytes(output);
1585                return Err(err);
1586            }
1587        };
1588        wipe_tail(output, written);
1589        Ok(written)
1590    }
1591
1592    /// Encodes `input` with line wrapping into a stack-backed buffer.
1593    ///
1594    /// This is useful for MIME/PEM-style protocols where heap allocation is
1595    /// unnecessary. If encoding fails, the internal backing array is cleared
1596    /// before the error is returned.
1597    pub fn encode_wrapped_buffer<const CAP: usize>(
1598        &self,
1599        input: &[u8],
1600        wrap: LineWrap,
1601    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
1602        let mut output = EncodedBuffer::new();
1603        let written =
1604            match self.encode_slice_wrapped_clear_tail(input, output.as_mut_capacity(), wrap) {
1605                Ok(written) => written,
1606                Err(err) => {
1607                    output.clear();
1608                    return Err(err);
1609                }
1610            };
1611        output.set_filled(written)?;
1612        Ok(output)
1613    }
1614
1615    /// Encodes `input` with line wrapping into a newly allocated byte vector.
1616    #[cfg(feature = "alloc")]
1617    #[must_use = "for secret-bearing payloads use encode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
1618    pub fn encode_wrapped_vec(
1619        &self,
1620        input: &[u8],
1621        wrap: LineWrap,
1622    ) -> Result<alloc::vec::Vec<u8>, EncodeError> {
1623        let required = self.wrapped_encoded_len(input.len(), wrap)?;
1624        let mut output = alloc::vec![0; required];
1625        let written = self.encode_slice_wrapped(input, &mut output, wrap)?;
1626        output.truncate(written);
1627        Ok(output)
1628    }
1629
1630    /// Encodes `input` with line wrapping into a newly allocated UTF-8 string.
1631    #[cfg(feature = "alloc")]
1632    pub fn encode_wrapped_string(
1633        &self,
1634        input: &[u8],
1635        wrap: LineWrap,
1636    ) -> Result<alloc::string::String, EncodeError> {
1637        let output = self.encode_wrapped_vec(input, wrap)?;
1638        match alloc::string::String::from_utf8(output) {
1639            Ok(output) => Ok(output),
1640            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
1641        }
1642    }
1643
1644    /// Encodes `input` with line wrapping into a redacted owned secret buffer.
1645    ///
1646    /// This is useful when the wrapped encoded representation itself is
1647    /// sensitive and should not be accidentally logged through formatting.
1648    #[cfg(feature = "alloc")]
1649    pub fn encode_wrapped_secret(
1650        &self,
1651        input: &[u8],
1652        wrap: LineWrap,
1653    ) -> Result<SecretBuffer, EncodeError> {
1654        self.encode_wrapped_vec(input, wrap)
1655            .map(SecretBuffer::from_vec)
1656    }
1657
1658    /// Encodes `input` into `output` and clears all bytes after the encoded
1659    /// prefix.
1660    ///
1661    /// If encoding fails, the entire output buffer is cleared before the error
1662    /// is returned.
1663    ///
1664    /// # Examples
1665    ///
1666    /// ```
1667    /// use base64_ng::STANDARD;
1668    ///
1669    /// let mut output = [0xff; 12];
1670    /// let written = STANDARD
1671    ///     .encode_slice_clear_tail(b"hello", &mut output)
1672    ///     .unwrap();
1673    ///
1674    /// assert_eq!(&output[..written], b"aGVsbG8=");
1675    /// assert!(output[written..].iter().all(|byte| *byte == 0));
1676    /// ```
1677    pub fn encode_slice_clear_tail(
1678        &self,
1679        input: &[u8],
1680        output: &mut [u8],
1681    ) -> Result<usize, EncodeError> {
1682        let written = match self.encode_slice(input, output) {
1683            Ok(written) => written,
1684            Err(err) => {
1685                wipe_bytes(output);
1686                return Err(err);
1687            }
1688        };
1689        wipe_tail(output, written);
1690        Ok(written)
1691    }
1692
1693    /// Encodes `input` into a stack-backed buffer.
1694    ///
1695    /// This helper is useful for short values where callers want the
1696    /// convenience of an owned result without enabling `alloc`.
1697    ///
1698    /// # Examples
1699    ///
1700    /// ```
1701    /// use base64_ng::STANDARD;
1702    ///
1703    /// let encoded = STANDARD.encode_buffer::<8>(b"hello").unwrap();
1704    ///
1705    /// assert_eq!(encoded.as_str(), "aGVsbG8=");
1706    /// ```
1707    pub fn encode_buffer<const CAP: usize>(
1708        &self,
1709        input: &[u8],
1710    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
1711        let mut output = EncodedBuffer::new();
1712        let written = match self.encode_slice_clear_tail(input, output.as_mut_capacity()) {
1713            Ok(written) => written,
1714            Err(err) => {
1715                output.clear();
1716                return Err(err);
1717            }
1718        };
1719        output.set_filled(written)?;
1720        Ok(output)
1721    }
1722
1723    /// Encodes `input` into a newly allocated byte vector.
1724    #[cfg(feature = "alloc")]
1725    #[must_use = "for secret-bearing payloads use encode_secret, which returns a redacted buffer with drop-time cleanup"]
1726    pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
1727        let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
1728        let mut output = alloc::vec![0; required];
1729        let written = self.encode_slice(input, &mut output)?;
1730        output.truncate(written);
1731        Ok(output)
1732    }
1733
1734    /// Encodes `input` into a redacted owned secret buffer.
1735    ///
1736    /// This is useful when the encoded representation itself is sensitive and
1737    /// should not be accidentally logged through formatting.
1738    #[cfg(feature = "alloc")]
1739    pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
1740        self.encode_vec(input).map(SecretBuffer::from_vec)
1741    }
1742
1743    /// Encodes `input` into a newly allocated UTF-8 string.
1744    ///
1745    /// Base64 output is ASCII by construction. This helper is available with
1746    /// the `alloc` feature and has the same encoding semantics as
1747    /// [`Self::encode_slice`].
1748    ///
1749    /// # Examples
1750    ///
1751    /// ```
1752    /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
1753    ///
1754    /// assert_eq!(STANDARD.encode_string(b"hello").unwrap(), "aGVsbG8=");
1755    /// assert_eq!(URL_SAFE_NO_PAD.encode_string(b"\xfb\xff").unwrap(), "-_8");
1756    /// ```
1757    #[cfg(feature = "alloc")]
1758    pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
1759        let output = self.encode_vec(input)?;
1760        match alloc::string::String::from_utf8(output) {
1761            Ok(output) => Ok(output),
1762            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
1763        }
1764    }
1765
1766    /// Encodes the first `input_len` bytes of `buffer` in place.
1767    ///
1768    /// The buffer must have enough spare capacity for the encoded output. The
1769    /// implementation writes from right to left, so unread input bytes are not
1770    /// overwritten before they are encoded.
1771    ///
1772    /// # Examples
1773    ///
1774    /// ```
1775    /// use base64_ng::STANDARD;
1776    ///
1777    /// let mut buffer = [0u8; 8];
1778    /// buffer[..5].copy_from_slice(b"hello");
1779    /// let encoded = STANDARD.encode_in_place(&mut buffer, 5).unwrap();
1780    /// assert_eq!(encoded, b"aGVsbG8=");
1781    /// ```
1782    pub fn encode_in_place<'a>(
1783        &self,
1784        buffer: &'a mut [u8],
1785        input_len: usize,
1786    ) -> Result<&'a mut [u8], EncodeError> {
1787        if input_len > buffer.len() {
1788            return Err(EncodeError::InputTooLarge {
1789                input_len,
1790                buffer_len: buffer.len(),
1791            });
1792        }
1793
1794        let required = checked_encoded_len(input_len, PAD).ok_or(EncodeError::LengthOverflow)?;
1795        if buffer.len() < required {
1796            return Err(EncodeError::OutputTooSmall {
1797                required,
1798                available: buffer.len(),
1799            });
1800        }
1801
1802        let mut read = input_len;
1803        let mut write = required;
1804
1805        match input_len % 3 {
1806            0 => {}
1807            1 => {
1808                read -= 1;
1809                let b0 = buffer[read];
1810                if PAD {
1811                    write -= 4;
1812                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
1813                    buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
1814                    buffer[write + 2] = b'=';
1815                    buffer[write + 3] = b'=';
1816                } else {
1817                    write -= 2;
1818                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
1819                    buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
1820                }
1821            }
1822            2 => {
1823                read -= 2;
1824                let b0 = buffer[read];
1825                let b1 = buffer[read + 1];
1826                if PAD {
1827                    write -= 4;
1828                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
1829                    buffer[write + 1] =
1830                        encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
1831                    buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
1832                    buffer[write + 3] = b'=';
1833                } else {
1834                    write -= 3;
1835                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
1836                    buffer[write + 1] =
1837                        encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
1838                    buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
1839                }
1840            }
1841            _ => unreachable!(),
1842        }
1843
1844        while read > 0 {
1845            read -= 3;
1846            write -= 4;
1847            let b0 = buffer[read];
1848            let b1 = buffer[read + 1];
1849            let b2 = buffer[read + 2];
1850
1851            buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
1852            buffer[write + 1] =
1853                encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
1854            buffer[write + 2] =
1855                encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
1856            buffer[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
1857        }
1858
1859        // The right-to-left loop consumes exactly three input bytes for every
1860        // four output bytes. If this invariant changes, returning a shifted
1861        // slice would silently corrupt the in-place output.
1862        debug_assert_eq!(write, 0);
1863        Ok(&mut buffer[..required])
1864    }
1865
1866    /// Encodes the first `input_len` bytes of `buffer` in place and clears all
1867    /// bytes after the encoded prefix.
1868    ///
1869    /// If encoding fails because `input_len` is too large, the output buffer is
1870    /// too small, or the encoded length overflows `usize`, the entire buffer is
1871    /// cleared before the error is returned.
1872    ///
1873    /// # Examples
1874    ///
1875    /// ```
1876    /// use base64_ng::STANDARD;
1877    ///
1878    /// let mut buffer = [0xff; 12];
1879    /// buffer[..5].copy_from_slice(b"hello");
1880    /// let encoded = STANDARD.encode_in_place_clear_tail(&mut buffer, 5).unwrap();
1881    /// assert_eq!(encoded, b"aGVsbG8=");
1882    /// ```
1883    pub fn encode_in_place_clear_tail<'a>(
1884        &self,
1885        buffer: &'a mut [u8],
1886        input_len: usize,
1887    ) -> Result<&'a mut [u8], EncodeError> {
1888        let len = match self.encode_in_place(buffer, input_len) {
1889            Ok(encoded) => encoded.len(),
1890            Err(err) => {
1891                wipe_bytes(buffer);
1892                return Err(err);
1893            }
1894        };
1895        wipe_tail(buffer, len);
1896        Ok(&mut buffer[..len])
1897    }
1898
1899    /// Decodes `input` into `output`, returning the number of bytes written.
1900    ///
1901    /// This is strict decoding. Whitespace, mixed alphabets, malformed padding,
1902    /// and trailing non-padding data are rejected.
1903    ///
1904    /// # Security
1905    ///
1906    /// This default scalar decoder prioritizes strict validation, exact error
1907    /// reporting, and ordinary throughput. It may branch or return early based
1908    /// on byte validity, malformed input, padding position, and output
1909    /// capacity. It also reports exact failure positions and invalid byte
1910    /// values through [`DecodeError`]. Do not use this method for token
1911    /// comparison, key-material decoding, or secret-bearing validation where
1912    /// malformed-input timing matters. Use [`crate::ct`],
1913    /// [`crate::ct::STANDARD`], [`crate::ct::URL_SAFE_NO_PAD`], or
1914    /// [`Self::ct_decoder`] with `decode_slice_clear_tail` for
1915    /// constant-time-oriented secret decoding.
1916    #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
1917    pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
1918        backend::decode_slice::<A, PAD>(input, output)
1919    }
1920
1921    /// Decodes `input` into `output` and clears all bytes after the decoded
1922    /// prefix.
1923    ///
1924    /// If decoding fails, the entire output buffer is cleared before the error
1925    /// is returned.
1926    ///
1927    /// # Examples
1928    ///
1929    /// ```
1930    /// use base64_ng::STANDARD;
1931    ///
1932    /// let mut output = [0xff; 8];
1933    /// let written = STANDARD
1934    ///     .decode_slice_clear_tail(b"aGk=", &mut output)
1935    ///     .unwrap();
1936    ///
1937    /// assert_eq!(&output[..written], b"hi");
1938    /// assert!(output[written..].iter().all(|byte| *byte == 0));
1939    /// ```
1940    pub fn decode_slice_clear_tail(
1941        &self,
1942        input: &[u8],
1943        output: &mut [u8],
1944    ) -> Result<usize, DecodeError> {
1945        let written = match self.decode_slice(input, output) {
1946            Ok(written) => written,
1947            Err(err) => {
1948                wipe_bytes(output);
1949                return Err(err);
1950            }
1951        };
1952        wipe_tail(output, written);
1953        Ok(written)
1954    }
1955
1956    /// Decodes `input` into a stack-backed buffer.
1957    ///
1958    /// This helper is useful for short decoded values where callers want the
1959    /// convenience of an owned result without enabling `alloc`.
1960    ///
1961    /// # Examples
1962    ///
1963    /// ```
1964    /// use base64_ng::STANDARD;
1965    ///
1966    /// let decoded = STANDARD.decode_buffer::<5>(b"aGVsbG8=").unwrap();
1967    ///
1968    /// assert_eq!(decoded.as_bytes(), b"hello");
1969    /// ```
1970    pub fn decode_buffer<const CAP: usize>(
1971        &self,
1972        input: &[u8],
1973    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
1974        let mut output = DecodedBuffer::new();
1975        let written = match self.decode_slice_clear_tail(input, output.as_mut_capacity()) {
1976            Ok(written) => written,
1977            Err(err) => {
1978                output.clear();
1979                return Err(err);
1980            }
1981        };
1982        output.set_filled(written)?;
1983        Ok(output)
1984    }
1985
1986    /// Decodes `input` using the explicit legacy whitespace profile.
1987    ///
1988    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
1989    /// Alphabet selection, padding placement, trailing data after padding, and
1990    /// non-canonical trailing bits remain strict.
1991    ///
1992    /// # Security
1993    ///
1994    /// This method uses the normal strict decode path after legacy whitespace
1995    /// handling. It may branch or return early based on malformed input and is
1996    /// not a constant-time token validator or key-material decoder. Use
1997    /// [`crate::ct`] for secret-bearing payloads.
1998    #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
1999    pub fn decode_slice_legacy(
2000        &self,
2001        input: &[u8],
2002        output: &mut [u8],
2003    ) -> Result<usize, DecodeError> {
2004        let required = validate_legacy_decode::<A, PAD>(input)?;
2005        if output.len() < required {
2006            return Err(DecodeError::OutputTooSmall {
2007                required,
2008                available: output.len(),
2009            });
2010        }
2011        decode_legacy_to_slice::<A, PAD>(input, output)
2012    }
2013
2014    /// Decodes `input` using the explicit legacy whitespace profile and clears
2015    /// all bytes after the decoded prefix.
2016    ///
2017    /// If validation or decoding fails, the entire output buffer is cleared
2018    /// before the error is returned.
2019    ///
2020    /// # Examples
2021    ///
2022    /// ```
2023    /// use base64_ng::STANDARD;
2024    ///
2025    /// let mut output = [0xff; 8];
2026    /// let written = STANDARD
2027    ///     .decode_slice_legacy_clear_tail(b" aG\r\nk= ", &mut output)
2028    ///     .unwrap();
2029    ///
2030    /// assert_eq!(&output[..written], b"hi");
2031    /// assert!(output[written..].iter().all(|byte| *byte == 0));
2032    /// ```
2033    pub fn decode_slice_legacy_clear_tail(
2034        &self,
2035        input: &[u8],
2036        output: &mut [u8],
2037    ) -> Result<usize, DecodeError> {
2038        let written = match self.decode_slice_legacy(input, output) {
2039            Ok(written) => written,
2040            Err(err) => {
2041                wipe_bytes(output);
2042                return Err(err);
2043            }
2044        };
2045        wipe_tail(output, written);
2046        Ok(written)
2047    }
2048
2049    /// Decodes `input` into a stack-backed buffer using the explicit legacy
2050    /// whitespace profile.
2051    ///
2052    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
2053    /// Alphabet selection, padding placement, trailing data after padding, and
2054    /// non-canonical trailing bits remain strict. If decoding fails, the
2055    /// internal backing array is cleared before the error is returned.
2056    pub fn decode_buffer_legacy<const CAP: usize>(
2057        &self,
2058        input: &[u8],
2059    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
2060        let mut output = DecodedBuffer::new();
2061        let written = match self.decode_slice_legacy_clear_tail(input, output.as_mut_capacity()) {
2062            Ok(written) => written,
2063            Err(err) => {
2064                output.clear();
2065                return Err(err);
2066            }
2067        };
2068        output.set_filled(written)?;
2069        Ok(output)
2070    }
2071
2072    /// Decodes `input` using a strict line-wrapped profile.
2073    ///
2074    /// The wrapped profile accepts only the configured line ending. Non-final
2075    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
2076    /// may be shorter. A single trailing line ending after the final line is
2077    /// accepted.
2078    ///
2079    /// # Security
2080    ///
2081    /// This method uses the normal strict decode path after line-profile
2082    /// validation. It may branch or return early based on malformed input and
2083    /// is not a constant-time token validator or key-material decoder. Use
2084    /// [`crate::ct`] for secret-bearing payloads.
2085    #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
2086    pub fn decode_slice_wrapped(
2087        &self,
2088        input: &[u8],
2089        output: &mut [u8],
2090        wrap: LineWrap,
2091    ) -> Result<usize, DecodeError> {
2092        let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
2093        if output.len() < required {
2094            return Err(DecodeError::OutputTooSmall {
2095                required,
2096                available: output.len(),
2097            });
2098        }
2099        decode_wrapped_to_slice::<A, PAD>(input, output, wrap)
2100    }
2101
2102    /// Decodes `input` using a strict line-wrapped profile and clears all bytes
2103    /// after the decoded prefix.
2104    ///
2105    /// If validation or decoding fails, the entire output buffer is cleared
2106    /// before the error is returned.
2107    pub fn decode_slice_wrapped_clear_tail(
2108        &self,
2109        input: &[u8],
2110        output: &mut [u8],
2111        wrap: LineWrap,
2112    ) -> Result<usize, DecodeError> {
2113        let written = match self.decode_slice_wrapped(input, output, wrap) {
2114            Ok(written) => written,
2115            Err(err) => {
2116                wipe_bytes(output);
2117                return Err(err);
2118            }
2119        };
2120        wipe_tail(output, written);
2121        Ok(written)
2122    }
2123
2124    /// Decodes `input` using a strict line-wrapped profile into a stack-backed
2125    /// buffer.
2126    ///
2127    /// The wrapped profile accepts only the configured line ending. Non-final
2128    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
2129    /// may be shorter. A single trailing line ending after the final line is
2130    /// accepted. If decoding fails, the internal backing array is cleared
2131    /// before the error is returned.
2132    pub fn decode_wrapped_buffer<const CAP: usize>(
2133        &self,
2134        input: &[u8],
2135        wrap: LineWrap,
2136    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
2137        let mut output = DecodedBuffer::new();
2138        let written =
2139            match self.decode_slice_wrapped_clear_tail(input, output.as_mut_capacity(), wrap) {
2140                Ok(written) => written,
2141                Err(err) => {
2142                    output.clear();
2143                    return Err(err);
2144                }
2145            };
2146        output.set_filled(written)?;
2147        Ok(output)
2148    }
2149
2150    /// Decodes `input` into a newly allocated byte vector.
2151    ///
2152    /// This is strict decoding with the same semantics as [`Self::decode_slice`].
2153    #[cfg(feature = "alloc")]
2154    #[must_use = "for secret-bearing payloads use decode_secret, which returns a redacted buffer with drop-time cleanup"]
2155    pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
2156        let required = validate_decode::<A, PAD>(input)?;
2157        let mut output = alloc::vec![0; required];
2158        let written = match self.decode_slice(input, &mut output) {
2159            Ok(written) => written,
2160            Err(err) => {
2161                wipe_bytes(&mut output);
2162                return Err(err);
2163            }
2164        };
2165        output.truncate(written);
2166        Ok(output)
2167    }
2168
2169    /// Decodes `input` into a redacted owned secret buffer.
2170    ///
2171    /// On malformed input, the intermediate output buffer is cleared before the
2172    /// error is returned by [`Self::decode_vec`].
2173    #[cfg(feature = "alloc")]
2174    pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
2175        self.decode_vec(input).map(SecretBuffer::from_vec)
2176    }
2177
2178    /// Decodes `input` into a newly allocated byte vector using the explicit
2179    /// legacy whitespace profile.
2180    #[cfg(feature = "alloc")]
2181    #[must_use = "for secret-bearing payloads use decode_secret_legacy, which returns a redacted buffer with drop-time cleanup"]
2182    pub fn decode_vec_legacy(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
2183        let required = validate_legacy_decode::<A, PAD>(input)?;
2184        let mut output = alloc::vec![0; required];
2185        let written = match self.decode_slice_legacy(input, &mut output) {
2186            Ok(written) => written,
2187            Err(err) => {
2188                wipe_bytes(&mut output);
2189                return Err(err);
2190            }
2191        };
2192        output.truncate(written);
2193        Ok(output)
2194    }
2195
2196    /// Decodes `input` into a redacted owned secret buffer using the explicit
2197    /// legacy whitespace profile.
2198    ///
2199    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
2200    /// Alphabet selection, padding placement, trailing data after padding, and
2201    /// non-canonical trailing bits remain strict.
2202    #[cfg(feature = "alloc")]
2203    pub fn decode_secret_legacy(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
2204        self.decode_vec_legacy(input).map(SecretBuffer::from_vec)
2205    }
2206
2207    /// Decodes line-wrapped input into a newly allocated byte vector.
2208    #[cfg(feature = "alloc")]
2209    #[must_use = "for secret-bearing payloads use decode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
2210    pub fn decode_wrapped_vec(
2211        &self,
2212        input: &[u8],
2213        wrap: LineWrap,
2214    ) -> Result<alloc::vec::Vec<u8>, DecodeError> {
2215        let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
2216        let mut output = alloc::vec![0; required];
2217        let written = match self.decode_slice_wrapped(input, &mut output, wrap) {
2218            Ok(written) => written,
2219            Err(err) => {
2220                wipe_bytes(&mut output);
2221                return Err(err);
2222            }
2223        };
2224        output.truncate(written);
2225        Ok(output)
2226    }
2227
2228    /// Decodes line-wrapped input into a redacted owned secret buffer.
2229    ///
2230    /// The wrapped profile accepts only the configured line ending. Non-final
2231    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
2232    /// may be shorter. A single trailing line ending after the final line is
2233    /// accepted.
2234    #[cfg(feature = "alloc")]
2235    pub fn decode_wrapped_secret(
2236        &self,
2237        input: &[u8],
2238        wrap: LineWrap,
2239    ) -> Result<SecretBuffer, DecodeError> {
2240        self.decode_wrapped_vec(input, wrap)
2241            .map(SecretBuffer::from_vec)
2242    }
2243
2244    /// Decodes `buffer` in place using a strict line-wrapped profile.
2245    ///
2246    /// The wrapped profile accepts only the configured line ending. Non-final
2247    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
2248    /// may be shorter. A single trailing line ending after the final line is
2249    /// accepted.
2250    ///
2251    /// # Security
2252    ///
2253    /// This method compacts line endings in place before decoding. If
2254    /// validation or decoding fails, the buffer contents are unspecified and
2255    /// may contain a compacted encoded prefix. On success, bytes after the
2256    /// returned decoded prefix may retain the compacted encoded representation.
2257    /// Use
2258    /// [`Self::decode_in_place_wrapped_clear_tail`] when the buffer may be
2259    /// reused or freed without a caller-managed wipe; treat that clear-tail
2260    /// variant as the default for secret-bearing wrapped payloads.
2261    ///
2262    /// # Examples
2263    ///
2264    /// ```
2265    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
2266    ///
2267    /// let mut buffer = *b"aGVs\nbG8=";
2268    /// let decoded = STANDARD
2269    ///     .decode_in_place_wrapped(&mut buffer, LineWrap::new(4, LineEnding::Lf))
2270    ///     .unwrap();
2271    ///
2272    /// assert_eq!(decoded, b"hello");
2273    /// ```
2274    pub fn decode_in_place_wrapped<'a>(
2275        &self,
2276        buffer: &'a mut [u8],
2277        wrap: LineWrap,
2278    ) -> Result<&'a mut [u8], DecodeError> {
2279        let _required = validate_wrapped_decode::<A, PAD>(buffer, wrap)?;
2280        let compacted = compact_wrapped_input(buffer, wrap)?;
2281        let len = Self::decode_slice_to_start(&mut buffer[..compacted])?;
2282        Ok(&mut buffer[..len])
2283    }
2284
2285    /// Decodes `buffer` in place using a strict line-wrapped profile and clears
2286    /// all bytes after the decoded prefix.
2287    ///
2288    /// If validation or decoding fails, the entire buffer is cleared before the
2289    /// error is returned.
2290    ///
2291    /// # Examples
2292    ///
2293    /// ```
2294    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
2295    ///
2296    /// let mut buffer = *b"aGVs\nbG8=";
2297    /// let len = STANDARD
2298    ///     .decode_in_place_wrapped_clear_tail(&mut buffer, LineWrap::new(4, LineEnding::Lf))
2299    ///     .unwrap()
2300    ///     .len();
2301    ///
2302    /// assert_eq!(&buffer[..len], b"hello");
2303    /// assert!(buffer[len..].iter().all(|byte| *byte == 0));
2304    /// ```
2305    pub fn decode_in_place_wrapped_clear_tail<'a>(
2306        &self,
2307        buffer: &'a mut [u8],
2308        wrap: LineWrap,
2309    ) -> Result<&'a mut [u8], DecodeError> {
2310        if let Err(err) = validate_wrapped_decode::<A, PAD>(buffer, wrap) {
2311            wipe_bytes(buffer);
2312            return Err(err);
2313        }
2314
2315        let compacted = match compact_wrapped_input(buffer, wrap) {
2316            Ok(compacted) => compacted,
2317            Err(err) => {
2318                wipe_bytes(buffer);
2319                return Err(err);
2320            }
2321        };
2322
2323        let len = match Self::decode_slice_to_start(&mut buffer[..compacted]) {
2324            Ok(len) => len,
2325            Err(err) => {
2326                wipe_bytes(buffer);
2327                return Err(err);
2328            }
2329        };
2330        wipe_tail(buffer, len);
2331        Ok(&mut buffer[..len])
2332    }
2333
2334    /// Decodes the buffer in place and returns the decoded prefix.
2335    ///
2336    /// On success, bytes after the returned decoded prefix may retain encoded
2337    /// input bytes. Use [`Self::decode_in_place_clear_tail`] when the buffer
2338    /// may be reused or freed without a caller-managed wipe.
2339    ///
2340    /// # Security
2341    ///
2342    /// This default scalar decoder prioritizes strict validation, exact error
2343    /// reporting, and ordinary throughput. It may branch or return early based
2344    /// on malformed input and reports exact failure positions and invalid byte
2345    /// values through [`DecodeError`]. Do not use this method for token
2346    /// comparison, key-material decoding, or secret-bearing validation where
2347    /// malformed-input timing matters.
2348    ///
2349    /// # Examples
2350    ///
2351    /// ```
2352    /// use base64_ng::STANDARD_NO_PAD;
2353    ///
2354    /// let mut buffer = *b"Zm9vYmFy";
2355    /// let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
2356    /// assert_eq!(decoded, b"foobar");
2357    /// ```
2358    pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
2359        let len = Self::decode_slice_to_start(buffer)?;
2360        Ok(&mut buffer[..len])
2361    }
2362
2363    /// Decodes the buffer in place and clears all bytes after the decoded prefix.
2364    ///
2365    /// If decoding fails, the entire buffer is cleared before the error is
2366    /// returned. Use this variant when the encoded or partially decoded data is
2367    /// sensitive and the caller wants best-effort cleanup without adding a
2368    /// dependency.
2369    ///
2370    /// # Examples
2371    ///
2372    /// ```
2373    /// use base64_ng::STANDARD;
2374    ///
2375    /// let mut buffer = *b"aGk=";
2376    /// let decoded = STANDARD.decode_in_place_clear_tail(&mut buffer).unwrap();
2377    /// assert_eq!(decoded, b"hi");
2378    /// ```
2379    pub fn decode_in_place_clear_tail<'a>(
2380        &self,
2381        buffer: &'a mut [u8],
2382    ) -> Result<&'a mut [u8], DecodeError> {
2383        let len = match Self::decode_slice_to_start(buffer) {
2384            Ok(len) => len,
2385            Err(err) => {
2386                wipe_bytes(buffer);
2387                return Err(err);
2388            }
2389        };
2390        wipe_tail(buffer, len);
2391        Ok(&mut buffer[..len])
2392    }
2393
2394    /// Decodes `buffer` in place using the explicit legacy whitespace profile.
2395    ///
2396    /// Ignored whitespace is compacted out before decoding. If validation
2397    /// fails, the buffer contents are unspecified. On success, bytes after the
2398    /// returned decoded prefix may retain the compacted encoded
2399    /// representation. Use [`Self::decode_in_place_legacy_clear_tail`] when the
2400    /// buffer may be reused or freed without a caller-managed wipe.
2401    pub fn decode_in_place_legacy<'a>(
2402        &self,
2403        buffer: &'a mut [u8],
2404    ) -> Result<&'a mut [u8], DecodeError> {
2405        let _required = validate_legacy_decode::<A, PAD>(buffer)?;
2406        let mut write = 0;
2407        let mut read = 0;
2408        while read < buffer.len() {
2409            let byte = buffer[read];
2410            if !is_legacy_whitespace(byte) {
2411                buffer[write] = byte;
2412                write += 1;
2413            }
2414            read += 1;
2415        }
2416        let len = Self::decode_slice_to_start(&mut buffer[..write])?;
2417        Ok(&mut buffer[..len])
2418    }
2419
2420    /// Decodes `buffer` in place using the explicit legacy whitespace profile
2421    /// and clears all bytes after the decoded prefix.
2422    ///
2423    /// If validation or decoding fails, the entire buffer is cleared before the
2424    /// error is returned.
2425    pub fn decode_in_place_legacy_clear_tail<'a>(
2426        &self,
2427        buffer: &'a mut [u8],
2428    ) -> Result<&'a mut [u8], DecodeError> {
2429        if let Err(err) = validate_legacy_decode::<A, PAD>(buffer) {
2430            wipe_bytes(buffer);
2431            return Err(err);
2432        }
2433
2434        let mut write = 0;
2435        let mut read = 0;
2436        while read < buffer.len() {
2437            let byte = buffer[read];
2438            if !is_legacy_whitespace(byte) {
2439                buffer[write] = byte;
2440                write += 1;
2441            }
2442            read += 1;
2443        }
2444
2445        let len = match Self::decode_slice_to_start(&mut buffer[..write]) {
2446            Ok(len) => len,
2447            Err(err) => {
2448                wipe_bytes(buffer);
2449                return Err(err);
2450            }
2451        };
2452        wipe_tail(buffer, len);
2453        Ok(&mut buffer[..len])
2454    }
2455
2456    fn decode_slice_to_start(buffer: &mut [u8]) -> Result<usize, DecodeError> {
2457        let _required = validate_decode::<A, PAD>(buffer)?;
2458        let input_len = buffer.len();
2459        let mut read = 0;
2460        let mut write = 0;
2461        while read + 4 <= input_len {
2462            let chunk = read_quad(buffer, read)?;
2463            let available = buffer.len();
2464            let output_tail = buffer.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
2465                required: write,
2466                available,
2467            })?;
2468            let written = decode_chunk::<A, PAD>(chunk, output_tail)
2469                .map_err(|err| err.with_index_offset(read))?;
2470            read += 4;
2471            write += written;
2472            if written < 3 {
2473                if read != input_len {
2474                    return Err(DecodeError::InvalidPadding { index: read - 4 });
2475                }
2476                return Ok(write);
2477            }
2478        }
2479
2480        let rem = input_len - read;
2481        if rem == 0 {
2482            return Ok(write);
2483        }
2484        if PAD {
2485            return Err(DecodeError::InvalidLength);
2486        }
2487        let mut tail = [0u8; 3];
2488        tail[..rem].copy_from_slice(&buffer[read..input_len]);
2489        decode_tail_unpadded::<A>(&tail[..rem], &mut buffer[write..])
2490            .map_err(|err| err.with_index_offset(read))
2491            .map(|n| write + n)
2492    }
2493}
2494
2495fn write_wrapped_bytes(
2496    input: &[u8],
2497    output: &mut [u8],
2498    output_offset: &mut usize,
2499    column: &mut usize,
2500    wrap: LineWrap,
2501) -> Result<(), EncodeError> {
2502    for byte in input {
2503        write_wrapped_byte(*byte, output, output_offset, column, wrap)?;
2504    }
2505    Ok(())
2506}
2507
2508fn write_wrapped_byte(
2509    byte: u8,
2510    output: &mut [u8],
2511    output_offset: &mut usize,
2512    column: &mut usize,
2513    wrap: LineWrap,
2514) -> Result<(), EncodeError> {
2515    if *column == wrap.line_len {
2516        let line_ending = wrap.line_ending.as_bytes();
2517        let mut index = 0;
2518        while index < line_ending.len() {
2519            if *output_offset >= output.len() {
2520                return Err(EncodeError::OutputTooSmall {
2521                    required: *output_offset + 1,
2522                    available: output.len(),
2523                });
2524            }
2525            output[*output_offset] = line_ending[index];
2526            *output_offset += 1;
2527            index += 1;
2528        }
2529        *column = 0;
2530    }
2531
2532    if *output_offset >= output.len() {
2533        return Err(EncodeError::OutputTooSmall {
2534            required: *output_offset + 1,
2535            available: output.len(),
2536        });
2537    }
2538    output[*output_offset] = byte;
2539    *output_offset += 1;
2540    *column += 1;
2541    Ok(())
2542}
2543
2544/// Encoding error.
2545#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2546pub enum EncodeError {
2547    /// The encoded output length would overflow `usize`.
2548    LengthOverflow,
2549    /// The requested line wrapping policy is invalid.
2550    InvalidLineWrap {
2551        /// Requested line length.
2552        line_len: usize,
2553    },
2554    /// The caller-provided input length exceeds the provided buffer.
2555    InputTooLarge {
2556        /// Requested input bytes.
2557        input_len: usize,
2558        /// Available buffer bytes.
2559        buffer_len: usize,
2560    },
2561    /// The output buffer is too small.
2562    OutputTooSmall {
2563        /// Required output bytes.
2564        required: usize,
2565        /// Available output bytes.
2566        available: usize,
2567    },
2568}
2569
2570impl core::fmt::Display for EncodeError {
2571    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2572        match self {
2573            Self::LengthOverflow => f.write_str("base64 output length overflows usize"),
2574            Self::InvalidLineWrap { line_len } => {
2575                write!(f, "base64 line wrap length {line_len} is invalid")
2576            }
2577            Self::InputTooLarge {
2578                input_len,
2579                buffer_len,
2580            } => write!(
2581                f,
2582                "base64 input length {input_len} exceeds buffer length {buffer_len}"
2583            ),
2584            Self::OutputTooSmall {
2585                required,
2586                available,
2587            } => write!(
2588                f,
2589                "base64 output buffer too small: required {required}, available {available}"
2590            ),
2591        }
2592    }
2593}
2594
2595#[cfg(feature = "std")]
2596impl std::error::Error for EncodeError {}
2597
2598/// Decoding error.
2599#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2600pub enum DecodeError {
2601    /// The encoded input is malformed, but the decoder intentionally does not
2602    /// disclose a more specific error class.
2603    InvalidInput,
2604    /// The encoded input length is impossible for the selected padding policy.
2605    InvalidLength,
2606    /// A byte is not valid for the selected alphabet.
2607    InvalidByte {
2608        /// Byte index in the input.
2609        index: usize,
2610        /// Invalid byte value.
2611        byte: u8,
2612    },
2613    /// Padding is missing, misplaced, or non-canonical.
2614    InvalidPadding {
2615        /// Byte index where padding became invalid.
2616        index: usize,
2617    },
2618    /// Line wrapping is missing, misplaced, or uses the wrong line ending.
2619    InvalidLineWrap {
2620        /// Byte index where line wrapping became invalid.
2621        index: usize,
2622    },
2623    /// The output buffer is too small.
2624    OutputTooSmall {
2625        /// Required output bytes.
2626        required: usize,
2627        /// Available output bytes.
2628        available: usize,
2629    },
2630    /// The caller-provided constant-time staging buffer is too small.
2631    StagingTooSmall {
2632        /// Required staging bytes.
2633        required: usize,
2634        /// Available staging bytes.
2635        available: usize,
2636    },
2637}
2638
2639impl core::fmt::Display for DecodeError {
2640    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2641        match self {
2642            Self::InvalidInput => f.write_str("malformed base64 input"),
2643            Self::InvalidLength => f.write_str("invalid base64 input length"),
2644            Self::InvalidByte { index, byte } => {
2645                write!(f, "invalid base64 byte 0x{byte:02x} at index {index}")
2646            }
2647            Self::InvalidPadding { index } => write!(f, "invalid base64 padding at index {index}"),
2648            Self::InvalidLineWrap { index } => {
2649                write!(f, "invalid base64 line wrapping at index {index}")
2650            }
2651            Self::OutputTooSmall {
2652                required,
2653                available,
2654            } => write!(
2655                f,
2656                "base64 decode output buffer too small: required {required}, available {available}"
2657            ),
2658            Self::StagingTooSmall {
2659                required,
2660                available,
2661            } => write!(
2662                f,
2663                "base64 decode staging buffer too small: required {required}, available {available}"
2664            ),
2665        }
2666    }
2667}
2668
2669impl DecodeError {
2670    fn with_index_offset(self, offset: usize) -> Self {
2671        match self {
2672            Self::InvalidByte { index, byte } => Self::InvalidByte {
2673                index: index + offset,
2674                byte,
2675            },
2676            Self::InvalidPadding { index } => Self::InvalidPadding {
2677                index: index + offset,
2678            },
2679            Self::InvalidLineWrap { index } => Self::InvalidLineWrap {
2680                index: index + offset,
2681            },
2682            Self::InvalidInput
2683            | Self::InvalidLength
2684            | Self::OutputTooSmall { .. }
2685            | Self::StagingTooSmall { .. } => self,
2686        }
2687    }
2688}
2689
2690#[cfg(feature = "std")]
2691impl std::error::Error for DecodeError {}
2692
2693struct LegacyBytes<'a> {
2694    input: &'a [u8],
2695    index: usize,
2696}
2697
2698impl<'a> LegacyBytes<'a> {
2699    const fn new(input: &'a [u8]) -> Self {
2700        Self { input, index: 0 }
2701    }
2702
2703    fn next_byte(&mut self) -> Option<(usize, u8)> {
2704        while self.index < self.input.len() {
2705            let index = self.index;
2706            let byte = self.input[index];
2707            self.index += 1;
2708            if !is_legacy_whitespace(byte) {
2709                return Some((index, byte));
2710            }
2711        }
2712        None
2713    }
2714}
2715
2716fn validate_legacy_decode<A: Alphabet, const PAD: bool>(
2717    input: &[u8],
2718) -> Result<usize, DecodeError> {
2719    let mut bytes = LegacyBytes::new(input);
2720    let mut chunk = [0u8; 4];
2721    let mut indexes = [0usize; 4];
2722    let mut chunk_len = 0;
2723    let mut required = 0;
2724    let mut terminal_seen = false;
2725
2726    while let Some((index, byte)) = bytes.next_byte() {
2727        if terminal_seen {
2728            return Err(DecodeError::InvalidPadding { index });
2729        }
2730
2731        chunk[chunk_len] = byte;
2732        indexes[chunk_len] = index;
2733        chunk_len += 1;
2734
2735        if chunk_len == 4 {
2736            let written =
2737                validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
2738            required += written;
2739            terminal_seen = written < 3;
2740            chunk_len = 0;
2741        }
2742    }
2743
2744    if chunk_len == 0 {
2745        return Ok(required);
2746    }
2747    if PAD {
2748        return Err(DecodeError::InvalidLength);
2749    }
2750
2751    validate_tail_unpadded::<A>(&chunk[..chunk_len])
2752        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
2753    Ok(required + decoded_capacity(chunk_len))
2754}
2755
2756fn decode_legacy_to_slice<A: Alphabet, const PAD: bool>(
2757    input: &[u8],
2758    output: &mut [u8],
2759) -> Result<usize, DecodeError> {
2760    let mut bytes = LegacyBytes::new(input);
2761    let mut chunk = [0u8; 4];
2762    let mut indexes = [0usize; 4];
2763    let mut chunk_len = 0;
2764    let mut write = 0;
2765    let mut terminal_seen = false;
2766
2767    while let Some((index, byte)) = bytes.next_byte() {
2768        if terminal_seen {
2769            return Err(DecodeError::InvalidPadding { index });
2770        }
2771
2772        chunk[chunk_len] = byte;
2773        indexes[chunk_len] = index;
2774        chunk_len += 1;
2775
2776        if chunk_len == 4 {
2777            let available = output.len();
2778            let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
2779                required: write,
2780                available,
2781            })?;
2782            let written = decode_chunk::<A, PAD>(chunk, output_tail)
2783                .map_err(|err| map_chunk_error(err, &indexes))?;
2784            write += written;
2785            terminal_seen = written < 3;
2786            chunk_len = 0;
2787        }
2788    }
2789
2790    if chunk_len == 0 {
2791        return Ok(write);
2792    }
2793    if PAD {
2794        return Err(DecodeError::InvalidLength);
2795    }
2796
2797    decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
2798        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
2799        .map(|n| write + n)
2800}
2801
2802struct WrappedBytes<'a> {
2803    input: &'a [u8],
2804    wrap: LineWrap,
2805    index: usize,
2806    line_len: usize,
2807}
2808
2809impl<'a> WrappedBytes<'a> {
2810    const fn new(input: &'a [u8], wrap: LineWrap) -> Result<Self, DecodeError> {
2811        if wrap.line_len == 0 {
2812            return Err(DecodeError::InvalidLineWrap { index: 0 });
2813        }
2814        Ok(Self {
2815            input,
2816            wrap,
2817            index: 0,
2818            line_len: 0,
2819        })
2820    }
2821
2822    fn next_byte(&mut self) -> Result<Option<(usize, u8)>, DecodeError> {
2823        loop {
2824            if self.index == self.input.len() {
2825                return Ok(None);
2826            }
2827
2828            if self.starts_with_line_ending() {
2829                let line_end_index = self.index;
2830                if self.line_len == 0 {
2831                    return Err(DecodeError::InvalidLineWrap {
2832                        index: line_end_index,
2833                    });
2834                }
2835
2836                self.index += self.wrap.line_ending.byte_len();
2837                if self.index == self.input.len() {
2838                    self.line_len = 0;
2839                    return Ok(None);
2840                }
2841
2842                if self.line_len != self.wrap.line_len {
2843                    return Err(DecodeError::InvalidLineWrap {
2844                        index: line_end_index,
2845                    });
2846                }
2847                self.line_len = 0;
2848                continue;
2849            }
2850
2851            let byte = self.input[self.index];
2852            if matches!(byte, b'\r' | b'\n') {
2853                return Err(DecodeError::InvalidLineWrap { index: self.index });
2854            }
2855
2856            self.line_len += 1;
2857            if self.line_len > self.wrap.line_len {
2858                return Err(DecodeError::InvalidLineWrap { index: self.index });
2859            }
2860
2861            let index = self.index;
2862            self.index += 1;
2863            return Ok(Some((index, byte)));
2864        }
2865    }
2866
2867    fn starts_with_line_ending(&self) -> bool {
2868        let line_ending = self.wrap.line_ending.as_bytes();
2869        let Some(end) = self.index.checked_add(line_ending.len()) else {
2870            return false;
2871        };
2872        end <= self.input.len() && &self.input[self.index..end] == line_ending
2873    }
2874}
2875
2876fn validate_wrapped_decode<A: Alphabet, const PAD: bool>(
2877    input: &[u8],
2878    wrap: LineWrap,
2879) -> Result<usize, DecodeError> {
2880    let mut bytes = WrappedBytes::new(input, wrap)?;
2881    let mut chunk = [0u8; 4];
2882    let mut indexes = [0usize; 4];
2883    let mut chunk_len = 0;
2884    let mut required = 0;
2885    let mut terminal_seen = false;
2886
2887    while let Some((index, byte)) = bytes.next_byte()? {
2888        if terminal_seen {
2889            return Err(DecodeError::InvalidPadding { index });
2890        }
2891
2892        chunk[chunk_len] = byte;
2893        indexes[chunk_len] = index;
2894        chunk_len += 1;
2895
2896        if chunk_len == 4 {
2897            let written =
2898                validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
2899            required += written;
2900            terminal_seen = written < 3;
2901            chunk_len = 0;
2902        }
2903    }
2904
2905    if chunk_len == 0 {
2906        return Ok(required);
2907    }
2908    if PAD {
2909        return Err(DecodeError::InvalidLength);
2910    }
2911
2912    validate_tail_unpadded::<A>(&chunk[..chunk_len])
2913        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
2914    Ok(required + decoded_capacity(chunk_len))
2915}
2916
2917fn decode_wrapped_to_slice<A: Alphabet, const PAD: bool>(
2918    input: &[u8],
2919    output: &mut [u8],
2920    wrap: LineWrap,
2921) -> Result<usize, DecodeError> {
2922    let mut bytes = WrappedBytes::new(input, wrap)?;
2923    let mut chunk = [0u8; 4];
2924    let mut indexes = [0usize; 4];
2925    let mut chunk_len = 0;
2926    let mut write = 0;
2927    let mut terminal_seen = false;
2928
2929    while let Some((index, byte)) = bytes.next_byte()? {
2930        if terminal_seen {
2931            return Err(DecodeError::InvalidPadding { index });
2932        }
2933
2934        chunk[chunk_len] = byte;
2935        indexes[chunk_len] = index;
2936        chunk_len += 1;
2937
2938        if chunk_len == 4 {
2939            let available = output.len();
2940            let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
2941                required: write,
2942                available,
2943            })?;
2944            let written = decode_chunk::<A, PAD>(chunk, output_tail)
2945                .map_err(|err| map_chunk_error(err, &indexes))?;
2946            write += written;
2947            terminal_seen = written < 3;
2948            chunk_len = 0;
2949        }
2950    }
2951
2952    if chunk_len == 0 {
2953        return Ok(write);
2954    }
2955    if PAD {
2956        return Err(DecodeError::InvalidLength);
2957    }
2958
2959    decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
2960        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
2961        .map(|n| write + n)
2962}
2963
2964fn compact_wrapped_input(buffer: &mut [u8], wrap: LineWrap) -> Result<usize, DecodeError> {
2965    if !wrap.is_valid() {
2966        return Err(DecodeError::InvalidLineWrap { index: 0 });
2967    }
2968
2969    let line_ending = wrap.line_ending.as_bytes();
2970    let line_ending_len = line_ending.len();
2971    let mut read = 0;
2972    let mut write = 0;
2973
2974    while read < buffer.len() {
2975        let line_end = read + line_ending_len;
2976        if buffer.get(read..line_end) == Some(line_ending) {
2977            read = line_end;
2978            continue;
2979        }
2980
2981        buffer[write] = buffer[read];
2982        write += 1;
2983        read += 1;
2984    }
2985
2986    Ok(write)
2987}
2988
2989#[inline]
2990const fn is_legacy_whitespace(byte: u8) -> bool {
2991    matches!(byte, b' ' | b'\t' | b'\r' | b'\n')
2992}
2993
2994fn map_chunk_error(err: DecodeError, indexes: &[usize; 4]) -> DecodeError {
2995    match err {
2996        DecodeError::InvalidByte { index, byte } => DecodeError::InvalidByte {
2997            index: indexes[index],
2998            byte,
2999        },
3000        DecodeError::InvalidPadding { index } => DecodeError::InvalidPadding {
3001            index: indexes[index],
3002        },
3003        DecodeError::InvalidInput
3004        | DecodeError::InvalidLineWrap { .. }
3005        | DecodeError::InvalidLength
3006        | DecodeError::OutputTooSmall { .. }
3007        | DecodeError::StagingTooSmall { .. } => err,
3008    }
3009}
3010
3011fn map_partial_chunk_error(err: DecodeError, indexes: &[usize; 4], len: usize) -> DecodeError {
3012    match err {
3013        DecodeError::InvalidByte { index, byte } if index < len => DecodeError::InvalidByte {
3014            index: indexes[index],
3015            byte,
3016        },
3017        DecodeError::InvalidPadding { index } if index < len => DecodeError::InvalidPadding {
3018            index: indexes[index],
3019        },
3020        DecodeError::InvalidByte { .. }
3021        | DecodeError::InvalidPadding { .. }
3022        | DecodeError::InvalidLineWrap { .. }
3023        | DecodeError::InvalidInput
3024        | DecodeError::InvalidLength
3025        | DecodeError::OutputTooSmall { .. }
3026        | DecodeError::StagingTooSmall { .. } => err,
3027    }
3028}
3029
3030fn decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
3031    if !input.len().is_multiple_of(4) {
3032        return Err(DecodeError::InvalidLength);
3033    }
3034    let required = decoded_len_padded(input)?;
3035    if output.len() < required {
3036        return Err(DecodeError::OutputTooSmall {
3037            required,
3038            available: output.len(),
3039        });
3040    }
3041
3042    let mut read = 0;
3043    let mut write = 0;
3044    while read < input.len() {
3045        let chunk = read_quad(input, read)?;
3046        let available = output.len();
3047        let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
3048            required: write,
3049            available,
3050        })?;
3051        let written = decode_chunk::<A, true>(chunk, output_tail)
3052            .map_err(|err| err.with_index_offset(read))?;
3053        read += 4;
3054        write += written;
3055        if written < 3 && read != input.len() {
3056            return Err(DecodeError::InvalidPadding { index: read - 4 });
3057        }
3058    }
3059    Ok(write)
3060}
3061
3062fn validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
3063    if input.is_empty() {
3064        return Ok(0);
3065    }
3066
3067    if PAD {
3068        validate_padded::<A>(input)
3069    } else {
3070        validate_unpadded::<A>(input)
3071    }
3072}
3073
3074fn validate_padded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
3075    if !input.len().is_multiple_of(4) {
3076        return Err(DecodeError::InvalidLength);
3077    }
3078    let required = decoded_len_padded(input)?;
3079
3080    let mut read = 0;
3081    while read < input.len() {
3082        let chunk = read_quad(input, read)?;
3083        let written =
3084            validate_chunk::<A, true>(chunk).map_err(|err| err.with_index_offset(read))?;
3085        read += 4;
3086        if written < 3 && read != input.len() {
3087            return Err(DecodeError::InvalidPadding { index: read - 4 });
3088        }
3089    }
3090
3091    Ok(required)
3092}
3093
3094fn validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
3095    let required = decoded_len_unpadded(input)?;
3096
3097    let mut read = 0;
3098    while read + 4 <= input.len() {
3099        let chunk = read_quad(input, read)?;
3100        validate_chunk::<A, false>(chunk).map_err(|err| err.with_index_offset(read))?;
3101        read += 4;
3102    }
3103    validate_tail_unpadded::<A>(&input[read..]).map_err(|err| err.with_index_offset(read))?;
3104
3105    Ok(required)
3106}
3107
3108fn decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
3109    let required = decoded_len_unpadded(input)?;
3110    if output.len() < required {
3111        return Err(DecodeError::OutputTooSmall {
3112            required,
3113            available: output.len(),
3114        });
3115    }
3116
3117    let mut read = 0;
3118    let mut write = 0;
3119    while read + 4 <= input.len() {
3120        let chunk = read_quad(input, read)?;
3121        let available = output.len();
3122        let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
3123            required: write,
3124            available,
3125        })?;
3126        let written = decode_chunk::<A, false>(chunk, output_tail)
3127            .map_err(|err| err.with_index_offset(read))?;
3128        read += 4;
3129        write += written;
3130    }
3131    decode_tail_unpadded::<A>(&input[read..], &mut output[write..])
3132        .map_err(|err| err.with_index_offset(read))
3133        .map(|n| write + n)
3134}
3135
3136fn decoded_len_padded(input: &[u8]) -> Result<usize, DecodeError> {
3137    if input.is_empty() {
3138        return Ok(0);
3139    }
3140    if !input.len().is_multiple_of(4) {
3141        return Err(DecodeError::InvalidLength);
3142    }
3143
3144    let Some((&last, before_last_prefix)) = input.split_last() else {
3145        return Ok(0);
3146    };
3147    let Some(&before_last) = before_last_prefix.last() else {
3148        return Err(DecodeError::InvalidLength);
3149    };
3150
3151    let mut padding = 0;
3152    if last == b'=' {
3153        padding += 1;
3154    }
3155    if before_last == b'=' {
3156        padding += 1;
3157    }
3158    if padding == 0
3159        && let Some(index) = input.iter().position(|byte| *byte == b'=')
3160    {
3161        return Err(DecodeError::InvalidPadding { index });
3162    }
3163    if padding > 0 {
3164        let first_pad = input.len() - padding;
3165        if let Some(index) = input[..first_pad].iter().position(|byte| *byte == b'=') {
3166            return Err(DecodeError::InvalidPadding { index });
3167        }
3168    }
3169    Ok(input.len() / 4 * 3 - padding)
3170}
3171
3172fn decoded_len_unpadded(input: &[u8]) -> Result<usize, DecodeError> {
3173    if input.len() % 4 == 1 {
3174        return Err(DecodeError::InvalidLength);
3175    }
3176    if let Some(index) = input.iter().position(|byte| *byte == b'=') {
3177        return Err(DecodeError::InvalidPadding { index });
3178    }
3179    Ok(decoded_capacity(input.len()))
3180}
3181
3182fn read_quad(input: &[u8], offset: usize) -> Result<[u8; 4], DecodeError> {
3183    let end = offset.checked_add(4).ok_or(DecodeError::InvalidLength)?;
3184    match input.get(offset..end) {
3185        Some([b0, b1, b2, b3]) => Ok([*b0, *b1, *b2, *b3]),
3186        _ => Err(DecodeError::InvalidLength),
3187    }
3188}
3189
3190fn first_padding_index_unchecked(input: [u8; 4]) -> usize {
3191    let [b0, b1, b2, b3] = input;
3192    if b0 == b'=' {
3193        0
3194    } else if b1 == b'=' {
3195        1
3196    } else if b2 == b'=' {
3197        2
3198    } else if b3 == b'=' {
3199        3
3200    } else {
3201        debug_assert!(
3202            false,
3203            "first_padding_index_unchecked called with no padding"
3204        );
3205        4
3206    }
3207}
3208
3209fn validate_chunk<A: Alphabet, const PAD: bool>(input: [u8; 4]) -> Result<usize, DecodeError> {
3210    let [b0, b1, b2, b3] = input;
3211    let _v0 = decode_byte::<A>(b0, 0)?;
3212    let v1 = decode_byte::<A>(b1, 1)?;
3213
3214    match (b2, b3) {
3215        (b'=', b'=') if PAD => {
3216            if v1 & 0b0000_1111 != 0 {
3217                return Err(DecodeError::InvalidPadding { index: 1 });
3218            }
3219            Ok(1)
3220        }
3221        (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
3222        (_, b'=') if PAD => {
3223            let v2 = decode_byte::<A>(b2, 2)?;
3224            if v2 & 0b0000_0011 != 0 {
3225                return Err(DecodeError::InvalidPadding { index: 2 });
3226            }
3227            Ok(2)
3228        }
3229        (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
3230            index: first_padding_index_unchecked(input),
3231        }),
3232        _ => {
3233            decode_byte::<A>(b2, 2)?;
3234            decode_byte::<A>(b3, 3)?;
3235            Ok(3)
3236        }
3237    }
3238}
3239
3240fn decode_chunk<A: Alphabet, const PAD: bool>(
3241    input: [u8; 4],
3242    output: &mut [u8],
3243) -> Result<usize, DecodeError> {
3244    let [b0, b1, b2, b3] = input;
3245    let v0 = decode_byte::<A>(b0, 0)?;
3246    let v1 = decode_byte::<A>(b1, 1)?;
3247
3248    match (b2, b3) {
3249        (b'=', b'=') if PAD => {
3250            if output.is_empty() {
3251                return Err(DecodeError::OutputTooSmall {
3252                    required: 1,
3253                    available: output.len(),
3254                });
3255            }
3256            if v1 & 0b0000_1111 != 0 {
3257                return Err(DecodeError::InvalidPadding { index: 1 });
3258            }
3259            output[0] = (v0 << 2) | (v1 >> 4);
3260            Ok(1)
3261        }
3262        (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
3263        (_, b'=') if PAD => {
3264            if output.len() < 2 {
3265                return Err(DecodeError::OutputTooSmall {
3266                    required: 2,
3267                    available: output.len(),
3268                });
3269            }
3270            let v2 = decode_byte::<A>(b2, 2)?;
3271            if v2 & 0b0000_0011 != 0 {
3272                return Err(DecodeError::InvalidPadding { index: 2 });
3273            }
3274            output[0] = (v0 << 2) | (v1 >> 4);
3275            output[1] = (v1 << 4) | (v2 >> 2);
3276            Ok(2)
3277        }
3278        (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
3279            index: first_padding_index_unchecked(input),
3280        }),
3281        _ => {
3282            if output.len() < 3 {
3283                return Err(DecodeError::OutputTooSmall {
3284                    required: 3,
3285                    available: output.len(),
3286                });
3287            }
3288            let v2 = decode_byte::<A>(b2, 2)?;
3289            let v3 = decode_byte::<A>(b3, 3)?;
3290            output[0] = (v0 << 2) | (v1 >> 4);
3291            output[1] = (v1 << 4) | (v2 >> 2);
3292            output[2] = (v2 << 6) | v3;
3293            Ok(3)
3294        }
3295    }
3296}
3297
3298fn validate_tail_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
3299    match input {
3300        [] => Ok(()),
3301        [b0, b1] => {
3302            decode_byte::<A>(*b0, 0)?;
3303            let v1 = decode_byte::<A>(*b1, 1)?;
3304            if v1 & 0b0000_1111 != 0 {
3305                return Err(DecodeError::InvalidPadding { index: 1 });
3306            }
3307            Ok(())
3308        }
3309        [b0, b1, b2] => {
3310            decode_byte::<A>(*b0, 0)?;
3311            decode_byte::<A>(*b1, 1)?;
3312            let v2 = decode_byte::<A>(*b2, 2)?;
3313            if v2 & 0b0000_0011 != 0 {
3314                return Err(DecodeError::InvalidPadding { index: 2 });
3315            }
3316            Ok(())
3317        }
3318        _ => Err(DecodeError::InvalidLength),
3319    }
3320}
3321
3322fn decode_tail_unpadded<A: Alphabet>(
3323    input: &[u8],
3324    output: &mut [u8],
3325) -> Result<usize, DecodeError> {
3326    match input {
3327        [] => Ok(0),
3328        [b0, b1] => {
3329            let Some(out0) = output.first_mut() else {
3330                return Err(DecodeError::OutputTooSmall {
3331                    required: 1,
3332                    available: output.len(),
3333                });
3334            };
3335            let v0 = decode_byte::<A>(*b0, 0)?;
3336            let v1 = decode_byte::<A>(*b1, 1)?;
3337            if v1 & 0b0000_1111 != 0 {
3338                return Err(DecodeError::InvalidPadding { index: 1 });
3339            }
3340            *out0 = (v0 << 2) | (v1 >> 4);
3341            Ok(1)
3342        }
3343        [b0, b1, b2] => {
3344            let available = output.len();
3345            let Some([out0, out1]) = output.get_mut(..2) else {
3346                return Err(DecodeError::OutputTooSmall {
3347                    required: 2,
3348                    available,
3349                });
3350            };
3351            let v0 = decode_byte::<A>(*b0, 0)?;
3352            let v1 = decode_byte::<A>(*b1, 1)?;
3353            let v2 = decode_byte::<A>(*b2, 2)?;
3354            if v2 & 0b0000_0011 != 0 {
3355                return Err(DecodeError::InvalidPadding { index: 2 });
3356            }
3357            *out0 = (v0 << 2) | (v1 >> 4);
3358            *out1 = (v1 << 4) | (v2 >> 2);
3359            Ok(2)
3360        }
3361        _ => Err(DecodeError::InvalidLength),
3362    }
3363}
3364
3365fn decode_byte<A: Alphabet>(byte: u8, index: usize) -> Result<u8, DecodeError> {
3366    A::decode(byte).ok_or(DecodeError::InvalidByte { index, byte })
3367}
3368
3369fn ct_decode_slice<A: Alphabet, const PAD: bool>(
3370    input: &[u8],
3371    output: &mut [u8],
3372) -> Result<usize, DecodeError> {
3373    if input.is_empty() {
3374        return Ok(0);
3375    }
3376
3377    if PAD {
3378        ct_decode_padded::<A>(input, output)
3379    } else {
3380        ct_decode_unpadded::<A>(input, output)
3381    }
3382}
3383
3384fn ct_decode_slice_staged_clear_tail<A: Alphabet, const PAD: bool>(
3385    input: &[u8],
3386    output: &mut [u8],
3387    staging: &mut [u8],
3388) -> Result<usize, DecodeError> {
3389    let required = match ct_decoded_len::<A, PAD>(input) {
3390        Ok(required) => required,
3391        Err(err) => {
3392            wipe_bytes(output);
3393            wipe_bytes(staging);
3394            return Err(err);
3395        }
3396    };
3397
3398    if output.len() < required {
3399        wipe_bytes(output);
3400        wipe_bytes(staging);
3401        return Err(DecodeError::OutputTooSmall {
3402            required,
3403            available: output.len(),
3404        });
3405    }
3406
3407    if staging.len() < required {
3408        wipe_bytes(output);
3409        wipe_bytes(staging);
3410        return Err(DecodeError::StagingTooSmall {
3411            required,
3412            available: staging.len(),
3413        });
3414    }
3415
3416    let written = match ct_decode_slice::<A, PAD>(input, &mut staging[..required]) {
3417        Ok(written) => written,
3418        Err(err) => {
3419            wipe_bytes(output);
3420            wipe_bytes(staging);
3421            return Err(err);
3422        }
3423    };
3424
3425    output[..written].copy_from_slice(&staging[..written]);
3426    wipe_bytes(staging);
3427    wipe_tail(output, written);
3428    Ok(written)
3429}
3430
3431fn ct_decode_in_place<A: Alphabet, const PAD: bool>(
3432    buffer: &mut [u8],
3433) -> Result<usize, DecodeError> {
3434    if buffer.is_empty() {
3435        return Ok(0);
3436    }
3437
3438    if PAD {
3439        ct_decode_padded_in_place::<A>(buffer)
3440    } else {
3441        ct_decode_unpadded_in_place::<A>(buffer)
3442    }
3443}
3444
3445#[inline(never)]
3446#[allow(unsafe_code)]
3447fn ct_error_gate_barrier(invalid_byte: u8, invalid_padding: u8) {
3448    core::hint::black_box(invalid_byte | invalid_padding);
3449    core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
3450
3451    #[cfg(all(not(miri), any(target_arch = "x86", target_arch = "x86_64")))]
3452    {
3453        // SAFETY: `lfence` does not access memory and is used as a speculation
3454        // barrier before the public success/failure branch is observed.
3455        unsafe {
3456            core::arch::asm!("lfence", options(nostack, preserves_flags, nomem));
3457        }
3458    }
3459
3460    #[cfg(all(not(miri), target_arch = "aarch64"))]
3461    {
3462        // SAFETY: these barriers do not access memory. `isb sy` is a
3463        // best-effort speculation boundary and `hint #20` is the architectural
3464        // CSDB hint encoding on AArch64.
3465        unsafe {
3466            core::arch::asm!("isb sy", "hint #20", options(nostack, preserves_flags));
3467        }
3468    }
3469
3470    #[cfg(all(not(miri), target_arch = "arm"))]
3471    {
3472        // SAFETY: `isb sy` does not access memory and is used as the best
3473        // available stable ARM speculation boundary for this crate.
3474        unsafe {
3475            core::arch::asm!("isb sy", options(nostack, preserves_flags));
3476        }
3477    }
3478
3479    #[cfg(all(not(miri), any(target_arch = "riscv32", target_arch = "riscv64")))]
3480    {
3481        // RISC-V base ISA does not provide a canonical speculation barrier.
3482        // `fence rw, rw` is the available ordering primitive for the CT public
3483        // result gate and is reported separately as `ordering-fence`.
3484        // SAFETY: the assembly block does not access memory.
3485        unsafe {
3486            core::arch::asm!("fence rw, rw", options(nostack, preserves_flags));
3487        }
3488    }
3489}
3490
3491fn ct_validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<(), DecodeError> {
3492    if input.is_empty() {
3493        return Ok(());
3494    }
3495
3496    if PAD {
3497        ct_validate_padded::<A>(input)
3498    } else {
3499        ct_validate_unpadded::<A>(input)
3500    }
3501}
3502
3503fn ct_decoded_len<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
3504    ct_validate_decode::<A, PAD>(input)?;
3505    if input.is_empty() {
3506        return Ok(0);
3507    }
3508
3509    if PAD {
3510        Ok(input.len() / 4 * 3 - ct_padding_len(input))
3511    } else {
3512        let full_quads = input.len() / 4 * 3;
3513        match input.len() % 4 {
3514            0 => Ok(full_quads),
3515            2 => Ok(full_quads + 1),
3516            3 => Ok(full_quads + 2),
3517            _ => Err(DecodeError::InvalidLength),
3518        }
3519    }
3520}
3521
3522fn ct_validate_padded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
3523    if !input.len().is_multiple_of(4) {
3524        return Err(DecodeError::InvalidLength);
3525    }
3526
3527    let padding = ct_padding_len(input);
3528    let mut invalid_byte = 0u8;
3529    let mut invalid_padding = 0u8;
3530    let mut read = 0;
3531
3532    while read + 4 < input.len() {
3533        let [b0, b1, b2, b3] =
3534            read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
3535        let (_, valid0) = ct_decode_alphabet_byte::<A>(b0);
3536        let (_, valid1) = ct_decode_alphabet_byte::<A>(b1);
3537        let (_, valid2) = ct_decode_alphabet_byte::<A>(b2);
3538        let (_, valid3) = ct_decode_alphabet_byte::<A>(b3);
3539
3540        invalid_byte |= !valid0;
3541        invalid_byte |= !valid1;
3542        invalid_byte |= !valid2;
3543        invalid_byte |= !valid3;
3544        invalid_padding |= ct_mask_eq_u8(b2, b'=');
3545        invalid_padding |= ct_mask_eq_u8(b3, b'=');
3546        read += 4;
3547    }
3548
3549    let final_chunk =
3550        read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
3551    let (_, final_invalid_byte, final_invalid_padding, _) =
3552        ct_padded_final_quantum::<A>(final_chunk, padding);
3553    invalid_byte |= final_invalid_byte;
3554    invalid_padding |= final_invalid_padding;
3555
3556    report_ct_error(invalid_byte, invalid_padding)
3557}
3558
3559fn ct_validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
3560    if input.len() % 4 == 1 {
3561        return Err(DecodeError::InvalidLength);
3562    }
3563
3564    let mut invalid_byte = 0u8;
3565    let mut invalid_padding = 0u8;
3566    let mut read = 0;
3567
3568    while read + 4 <= input.len() {
3569        let [b0, b1, b2, b3] =
3570            read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
3571        let (_, valid0) = ct_decode_alphabet_byte::<A>(b0);
3572        let (_, valid1) = ct_decode_alphabet_byte::<A>(b1);
3573        let (_, valid2) = ct_decode_alphabet_byte::<A>(b2);
3574        let (_, valid3) = ct_decode_alphabet_byte::<A>(b3);
3575
3576        invalid_byte |= !valid0;
3577        invalid_byte |= !valid1;
3578        invalid_byte |= !valid2;
3579        invalid_byte |= !valid3;
3580        invalid_padding |= ct_mask_eq_u8(b0, b'=');
3581        invalid_padding |= ct_mask_eq_u8(b1, b'=');
3582        invalid_padding |= ct_mask_eq_u8(b2, b'=');
3583        invalid_padding |= ct_mask_eq_u8(b3, b'=');
3584
3585        read += 4;
3586    }
3587
3588    match read_tail_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding) {
3589        [] => {}
3590        [b0, b1] => {
3591            let (_, valid0) = ct_decode_alphabet_byte::<A>(*b0);
3592            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
3593            invalid_byte |= !valid0;
3594            invalid_byte |= !valid1;
3595            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
3596            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
3597            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
3598        }
3599        [b0, b1, b2] => {
3600            let (_, valid0) = ct_decode_alphabet_byte::<A>(*b0);
3601            let (_, valid1) = ct_decode_alphabet_byte::<A>(*b1);
3602            let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
3603            invalid_byte |= !valid0;
3604            invalid_byte |= !valid1;
3605            invalid_byte |= !valid2;
3606            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
3607            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
3608            invalid_padding |= ct_mask_eq_u8(*b2, b'=');
3609            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
3610        }
3611        _ => {
3612            invalid_byte = 0xff;
3613            invalid_padding = 0xff;
3614        }
3615    }
3616
3617    report_ct_error(invalid_byte, invalid_padding)
3618}
3619
3620fn ct_padded_final_quantum<A: Alphabet>(
3621    input: [u8; 4],
3622    padding: usize,
3623) -> ([u8; 3], u8, u8, usize) {
3624    let [b0, b1, b2, b3] = input;
3625    let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
3626    let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
3627    let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
3628    let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
3629
3630    let padding_byte = match padding {
3631        0 => 0,
3632        1 => 1,
3633        2 => 2,
3634        _ => return ([0; 3], 0xff, 0xff, 0),
3635    };
3636    let no_padding = ct_mask_eq_u8(padding_byte, 0);
3637    let one_padding = ct_mask_eq_u8(padding_byte, 1);
3638    let two_padding = ct_mask_eq_u8(padding_byte, 2);
3639    let require_v2 = no_padding | one_padding;
3640    let require_v3 = no_padding;
3641
3642    let invalid_byte = !valid0 | !valid1 | (!valid2 & require_v2) | (!valid3 & require_v3);
3643    let invalid_padding = (ct_mask_nonzero_u8(v1 & 0b0000_1111) & two_padding)
3644        | ((ct_mask_eq_u8(b2, b'=') | ct_mask_nonzero_u8(v2 & 0b0000_0011)) & one_padding)
3645        | ((ct_mask_eq_u8(b2, b'=') | ct_mask_eq_u8(b3, b'=')) & no_padding);
3646
3647    (
3648        [(v0 << 2) | (v1 >> 4), (v1 << 4) | (v2 >> 2), (v2 << 6) | v3],
3649        invalid_byte,
3650        invalid_padding,
3651        3 - padding,
3652    )
3653}
3654
3655fn ct_decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
3656    if !input.len().is_multiple_of(4) {
3657        return Err(DecodeError::InvalidLength);
3658    }
3659
3660    let padding = ct_padding_len(input);
3661    let required = input.len() / 4 * 3 - padding;
3662    if output.len() < required {
3663        return Err(DecodeError::OutputTooSmall {
3664            required,
3665            available: output.len(),
3666        });
3667    }
3668
3669    let mut invalid_byte = 0u8;
3670    let mut invalid_padding = 0u8;
3671    let mut write = 0;
3672    let mut read = 0;
3673
3674    while read + 4 < input.len() {
3675        let [b0, b1, b2, b3] =
3676            read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
3677        let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
3678        let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
3679        let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
3680        let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
3681
3682        invalid_byte |= !valid0;
3683        invalid_byte |= !valid1;
3684        invalid_byte |= !valid2;
3685        invalid_byte |= !valid3;
3686        invalid_padding |= ct_mask_eq_u8(b2, b'=');
3687        invalid_padding |= ct_mask_eq_u8(b3, b'=');
3688        output[write] = (v0 << 2) | (v1 >> 4);
3689        output[write + 1] = (v1 << 4) | (v2 >> 2);
3690        output[write + 2] = (v2 << 6) | v3;
3691        write += 3;
3692        read += 4;
3693    }
3694
3695    let final_chunk =
3696        read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
3697    let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
3698        ct_padded_final_quantum::<A>(final_chunk, padding);
3699    invalid_byte |= final_invalid_byte;
3700    invalid_padding |= final_invalid_padding;
3701    output[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
3702    write += final_written;
3703
3704    report_ct_error(invalid_byte, invalid_padding)?;
3705    Ok(write)
3706}
3707
3708fn ct_decode_padded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
3709    if !buffer.len().is_multiple_of(4) {
3710        return Err(DecodeError::InvalidLength);
3711    }
3712
3713    let padding = ct_padding_len(buffer);
3714    let required = buffer.len() / 4 * 3 - padding;
3715    if required > buffer.len() {
3716        wipe_bytes(buffer);
3717        return Err(DecodeError::InvalidInput);
3718    }
3719
3720    let mut invalid_byte = 0u8;
3721    let mut invalid_padding = 0u8;
3722    let mut write = 0;
3723    let mut read = 0;
3724
3725    while read + 4 < buffer.len() {
3726        let [b0, b1, b2, b3] =
3727            read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
3728        let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
3729        let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
3730        let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
3731        let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
3732
3733        invalid_byte |= !valid0;
3734        invalid_byte |= !valid1;
3735        invalid_byte |= !valid2;
3736        invalid_byte |= !valid3;
3737        invalid_padding |= ct_mask_eq_u8(b2, b'=');
3738        invalid_padding |= ct_mask_eq_u8(b3, b'=');
3739        buffer[write] = (v0 << 2) | (v1 >> 4);
3740        buffer[write + 1] = (v1 << 4) | (v2 >> 2);
3741        buffer[write + 2] = (v2 << 6) | v3;
3742        write += 3;
3743        read += 4;
3744    }
3745
3746    let final_chunk =
3747        read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
3748    let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
3749        ct_padded_final_quantum::<A>(final_chunk, padding);
3750    invalid_byte |= final_invalid_byte;
3751    invalid_padding |= final_invalid_padding;
3752    buffer[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
3753    write += final_written;
3754
3755    if write != required {
3756        ct_error_gate_barrier(invalid_byte, invalid_padding);
3757        wipe_bytes(buffer);
3758        return Err(DecodeError::InvalidInput);
3759    }
3760    if let Err(err) = report_ct_error(invalid_byte, invalid_padding) {
3761        wipe_bytes(buffer);
3762        return Err(err);
3763    }
3764    Ok(write)
3765}
3766
3767fn ct_decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
3768    if input.len() % 4 == 1 {
3769        return Err(DecodeError::InvalidLength);
3770    }
3771
3772    let required = decoded_capacity(input.len());
3773    if output.len() < required {
3774        return Err(DecodeError::OutputTooSmall {
3775            required,
3776            available: output.len(),
3777        });
3778    }
3779
3780    let mut invalid_byte = 0u8;
3781    let mut invalid_padding = 0u8;
3782    let mut write = 0;
3783    let mut read = 0;
3784
3785    while read + 4 <= input.len() {
3786        let [b0, b1, b2, b3] =
3787            read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
3788        let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
3789        let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
3790        let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
3791        let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
3792
3793        invalid_byte |= !valid0;
3794        invalid_byte |= !valid1;
3795        invalid_byte |= !valid2;
3796        invalid_byte |= !valid3;
3797        invalid_padding |= ct_mask_eq_u8(b0, b'=');
3798        invalid_padding |= ct_mask_eq_u8(b1, b'=');
3799        invalid_padding |= ct_mask_eq_u8(b2, b'=');
3800        invalid_padding |= ct_mask_eq_u8(b3, b'=');
3801
3802        output[write] = (v0 << 2) | (v1 >> 4);
3803        output[write + 1] = (v1 << 4) | (v2 >> 2);
3804        output[write + 2] = (v2 << 6) | v3;
3805        read += 4;
3806        write += 3;
3807    }
3808
3809    match read_tail_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding) {
3810        [] => {}
3811        [b0, b1] => {
3812            let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
3813            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
3814            invalid_byte |= !valid0;
3815            invalid_byte |= !valid1;
3816            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
3817            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
3818            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
3819            output[write] = (v0 << 2) | (v1 >> 4);
3820            write += 1;
3821        }
3822        [b0, b1, b2] => {
3823            let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
3824            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
3825            let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
3826            invalid_byte |= !valid0;
3827            invalid_byte |= !valid1;
3828            invalid_byte |= !valid2;
3829            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
3830            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
3831            invalid_padding |= ct_mask_eq_u8(*b2, b'=');
3832            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
3833            output[write] = (v0 << 2) | (v1 >> 4);
3834            output[write + 1] = (v1 << 4) | (v2 >> 2);
3835            write += 2;
3836        }
3837        _ => {
3838            invalid_byte = 0xff;
3839            invalid_padding = 0xff;
3840        }
3841    }
3842
3843    report_ct_error(invalid_byte, invalid_padding)?;
3844    Ok(write)
3845}
3846
3847fn ct_decode_unpadded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
3848    if buffer.len() % 4 == 1 {
3849        return Err(DecodeError::InvalidLength);
3850    }
3851
3852    let required = decoded_capacity(buffer.len());
3853    if required > buffer.len() {
3854        wipe_bytes(buffer);
3855        return Err(DecodeError::InvalidInput);
3856    }
3857
3858    let mut invalid_byte = 0u8;
3859    let mut invalid_padding = 0u8;
3860    let mut write = 0;
3861    let mut read = 0;
3862
3863    while read + 4 <= buffer.len() {
3864        let [b0, b1, b2, b3] =
3865            read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
3866        let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
3867        let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
3868        let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
3869        let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
3870
3871        invalid_byte |= !valid0;
3872        invalid_byte |= !valid1;
3873        invalid_byte |= !valid2;
3874        invalid_byte |= !valid3;
3875        invalid_padding |= ct_mask_eq_u8(b0, b'=');
3876        invalid_padding |= ct_mask_eq_u8(b1, b'=');
3877        invalid_padding |= ct_mask_eq_u8(b2, b'=');
3878        invalid_padding |= ct_mask_eq_u8(b3, b'=');
3879
3880        buffer[write] = (v0 << 2) | (v1 >> 4);
3881        buffer[write + 1] = (v1 << 4) | (v2 >> 2);
3882        buffer[write + 2] = (v2 << 6) | v3;
3883        read += 4;
3884        write += 3;
3885    }
3886
3887    let tail = read_tail_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
3888    match tail {
3889        [] => {}
3890        [b0, b1] => {
3891            let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
3892            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
3893            invalid_byte |= !valid0;
3894            invalid_byte |= !valid1;
3895            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
3896            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
3897            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
3898            buffer[write] = (v0 << 2) | (v1 >> 4);
3899            write += 1;
3900        }
3901        [b0, b1, b2] => {
3902            let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
3903            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
3904            let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
3905            invalid_byte |= !valid0;
3906            invalid_byte |= !valid1;
3907            invalid_byte |= !valid2;
3908            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
3909            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
3910            invalid_padding |= ct_mask_eq_u8(*b2, b'=');
3911            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
3912            buffer[write] = (v0 << 2) | (v1 >> 4);
3913            buffer[write + 1] = (v1 << 4) | (v2 >> 2);
3914            write += 2;
3915        }
3916        _ => {
3917            invalid_byte = 0xff;
3918            invalid_padding = 0xff;
3919        }
3920    }
3921
3922    if write != required {
3923        ct_error_gate_barrier(invalid_byte, invalid_padding);
3924        wipe_bytes(buffer);
3925        return Err(DecodeError::InvalidInput);
3926    }
3927    if let Err(err) = report_ct_error(invalid_byte, invalid_padding) {
3928        wipe_bytes(buffer);
3929        return Err(err);
3930    }
3931    Ok(write)
3932}
3933
3934fn read_tail(input: &[u8], offset: usize) -> Result<&[u8], DecodeError> {
3935    input.get(offset..).ok_or(DecodeError::InvalidLength)
3936}
3937
3938fn read_quad_or_mark_invalid(
3939    input: &[u8],
3940    offset: usize,
3941    invalid_byte: &mut u8,
3942    invalid_padding: &mut u8,
3943) -> [u8; 4] {
3944    if let Ok(quad) = read_quad(input, offset) {
3945        quad
3946    } else {
3947        debug_assert!(
3948            false,
3949            "read_quad failed inside length-validated constant-time decode loop"
3950        );
3951        *invalid_byte = 0xff;
3952        *invalid_padding = 0xff;
3953        [0; 4]
3954    }
3955}
3956
3957fn read_tail_or_mark_invalid<'a>(
3958    input: &'a [u8],
3959    offset: usize,
3960    invalid_byte: &mut u8,
3961    invalid_padding: &mut u8,
3962) -> &'a [u8] {
3963    if let Ok(tail) = read_tail(input, offset) {
3964        tail
3965    } else {
3966        debug_assert!(
3967            false,
3968            "read_tail failed inside length-validated constant-time decode loop"
3969        );
3970        *invalid_byte = 0xff;
3971        *invalid_padding = 0xff;
3972        &[]
3973    }
3974}
3975
3976#[inline(never)]
3977#[allow(unsafe_code)]
3978fn ct_decode_alphabet_byte<A: Alphabet>(byte: u8) -> (u8, u8) {
3979    let mut decoded = 0u8;
3980    let mut valid = 0u8;
3981    let mut candidate = 0u8;
3982
3983    while candidate < 64 {
3984        let matches = core::hint::black_box(ct_mask_eq_u8(
3985            core::hint::black_box(byte),
3986            core::hint::black_box(A::ENCODE[candidate as usize]),
3987        ));
3988        decoded = core::hint::black_box(
3989            core::hint::black_box(decoded) | core::hint::black_box(candidate & matches),
3990        );
3991        // SAFETY: `decoded` is an initialized local `u8`; the volatile read is
3992        // an optimizer barrier for the fixed 64-iteration alphabet scan and
3993        // does not access caller memory.
3994        decoded = unsafe { core::ptr::read_volatile(&raw const decoded) };
3995        valid =
3996            core::hint::black_box(core::hint::black_box(valid) | core::hint::black_box(matches));
3997        // SAFETY: `valid` is an initialized local `u8`; the volatile read is an
3998        // optimizer barrier for the fixed 64-iteration alphabet scan and does
3999        // not access caller memory.
4000        valid = unsafe { core::ptr::read_volatile(&raw const valid) };
4001        candidate += 1;
4002    }
4003
4004    (decoded, valid)
4005}
4006
4007fn ct_padding_len(input: &[u8]) -> usize {
4008    let Some((&last, before_last_prefix)) = input.split_last() else {
4009        return 0;
4010    };
4011    let Some(&before_last) = before_last_prefix.last() else {
4012        return 0;
4013    };
4014    usize::from(ct_mask_eq_u8(last, b'=') & 1) + usize::from(ct_mask_eq_u8(before_last, b'=') & 1)
4015}
4016
4017fn report_ct_error(invalid_byte: u8, invalid_padding: u8) -> Result<(), DecodeError> {
4018    ct_error_gate_barrier(invalid_byte, invalid_padding);
4019
4020    if (invalid_byte | invalid_padding) != 0 {
4021        Err(DecodeError::InvalidInput)
4022    } else {
4023        Ok(())
4024    }
4025}
4026
4027#[cfg(kani)]
4028mod kani_proofs {
4029    use super::{
4030        STANDARD, Standard, checked_encoded_len, ct, decode_byte, decode_chunk,
4031        decode_tail_unpadded, decoded_capacity, validate_tail_unpadded,
4032    };
4033
4034    #[kani::proof]
4035    fn checked_encoded_len_is_bounded_for_small_inputs() {
4036        let len = usize::from(kani::any::<u8>());
4037        let padded = kani::any::<bool>();
4038        let encoded = checked_encoded_len(len, padded).expect("u8 input length cannot overflow");
4039
4040        assert!(encoded >= len);
4041        assert!(encoded <= len / 3 * 4 + 4);
4042    }
4043
4044    #[kani::proof]
4045    fn decoded_capacity_is_bounded_for_small_inputs() {
4046        let len = usize::from(kani::any::<u8>());
4047        let capacity = decoded_capacity(len);
4048
4049        assert!(capacity <= len / 4 * 3 + 2);
4050    }
4051
4052    #[kani::proof]
4053    #[kani::unwind(3)]
4054    fn standard_in_place_decode_returns_prefix_within_buffer() {
4055        let mut buffer = kani::any::<[u8; 8]>();
4056        let result = STANDARD.decode_in_place(&mut buffer);
4057
4058        if let Ok(decoded) = result {
4059            assert!(decoded.len() <= 8);
4060        }
4061    }
4062
4063    #[kani::proof]
4064    #[kani::unwind(3)]
4065    fn standard_decode_slice_returns_written_within_output() {
4066        let input = kani::any::<[u8; 4]>();
4067        let mut output = kani::any::<[u8; 3]>();
4068        let result = STANDARD.decode_slice(&input, &mut output);
4069
4070        if let Ok(written) = result {
4071            assert!(written <= output.len());
4072        }
4073    }
4074
4075    #[kani::proof]
4076    #[kani::unwind(3)]
4077    fn standard_decode_chunk_returns_written_within_output() {
4078        let input = kani::any::<[u8; 4]>();
4079        let mut output = kani::any::<[u8; 3]>();
4080        let result = decode_chunk::<Standard, true>(input, &mut output);
4081
4082        if let Ok(written) = result {
4083            assert!(written <= output.len());
4084            assert!(written <= 3);
4085        }
4086    }
4087
4088    #[kani::proof]
4089    #[kani::unwind(3)]
4090    fn standard_decode_chunk_bit_packing_matches_decoded_values() {
4091        let input = kani::any::<[u8; 4]>();
4092        let mut output = kani::any::<[u8; 3]>();
4093        let result = decode_chunk::<Standard, true>(input, &mut output);
4094
4095        if let Ok(written) = result {
4096            let v0 = decode_byte::<Standard>(input[0], 0).expect("successful chunk has v0");
4097            let v1 = decode_byte::<Standard>(input[1], 1).expect("successful chunk has v1");
4098
4099            assert!(output[0] == ((v0 << 2) | (v1 >> 4)));
4100
4101            if written >= 2 {
4102                let v2 = decode_byte::<Standard>(input[2], 2).expect("successful chunk has v2");
4103                assert!(output[1] == ((v1 << 4) | (v2 >> 2)));
4104            }
4105
4106            if written == 3 {
4107                let v2 = decode_byte::<Standard>(input[2], 2).expect("successful chunk has v2");
4108                let v3 = decode_byte::<Standard>(input[3], 3).expect("successful chunk has v3");
4109                assert!(output[2] == ((v2 << 6) | v3));
4110            }
4111        }
4112    }
4113
4114    #[kani::proof]
4115    #[kani::unwind(3)]
4116    fn standard_validate_tail_unpadded_accepts_or_rejects_without_panic() {
4117        let input = kani::any::<[u8; 3]>();
4118        let len = usize::from(kani::any::<u8>() % 4);
4119        let result = validate_tail_unpadded::<Standard>(&input[..len]);
4120
4121        if result.is_ok() {
4122            assert!(len == 0 || len == 2 || len == 3);
4123        }
4124    }
4125
4126    #[kani::proof]
4127    #[kani::unwind(3)]
4128    fn standard_decode_two_byte_tail_returns_written_within_output() {
4129        let input = kani::any::<[u8; 2]>();
4130        let mut output = kani::any::<[u8; 1]>();
4131        let result = decode_tail_unpadded::<Standard>(&input, &mut output);
4132
4133        if let Ok(written) = result {
4134            assert!(written <= output.len());
4135            assert!(written == 1);
4136        }
4137    }
4138
4139    #[kani::proof]
4140    #[kani::unwind(3)]
4141    fn standard_decode_three_byte_tail_returns_written_within_output() {
4142        let input = kani::any::<[u8; 3]>();
4143        let mut output = kani::any::<[u8; 2]>();
4144        let result = decode_tail_unpadded::<Standard>(&input, &mut output);
4145
4146        if let Ok(written) = result {
4147            assert!(written <= output.len());
4148            assert!(written == 2);
4149        }
4150    }
4151
4152    #[kani::proof]
4153    #[kani::unwind(3)]
4154    fn standard_decode_slice_clear_tail_clears_output_on_error() {
4155        let input = kani::any::<[u8; 4]>();
4156        let mut output = kani::any::<[u8; 3]>();
4157        let result = STANDARD.decode_slice_clear_tail(&input, &mut output);
4158
4159        if result.is_err() {
4160            assert!(output.iter().all(|byte| *byte == 0));
4161        }
4162    }
4163
4164    #[kani::proof]
4165    #[kani::unwind(3)]
4166    fn standard_encode_slice_returns_written_within_output() {
4167        let input = kani::any::<[u8; 3]>();
4168        let mut output = kani::any::<[u8; 4]>();
4169        let result = STANDARD.encode_slice(&input, &mut output);
4170
4171        if let Ok(written) = result {
4172            assert!(written <= output.len());
4173        }
4174    }
4175
4176    #[kani::proof]
4177    #[kani::unwind(4)]
4178    fn standard_encode_in_place_returns_prefix_within_buffer() {
4179        let mut buffer = kani::any::<[u8; 8]>();
4180        let input_len = usize::from(kani::any::<u8>() % 9);
4181        let result = STANDARD.encode_in_place(&mut buffer, input_len);
4182
4183        if let Ok(encoded) = result {
4184            assert!(encoded.len() <= 8);
4185        }
4186    }
4187
4188    #[kani::proof]
4189    #[kani::unwind(3)]
4190    fn standard_clear_tail_decode_clears_buffer_on_error() {
4191        let mut buffer = kani::any::<[u8; 4]>();
4192        let result = STANDARD.decode_in_place_clear_tail(&mut buffer);
4193
4194        if result.is_err() {
4195            assert!(buffer.iter().all(|byte| *byte == 0));
4196        }
4197    }
4198
4199    #[kani::proof]
4200    #[kani::unwind(3)]
4201    fn ct_standard_decode_slice_returns_written_within_output() {
4202        let input = kani::any::<[u8; 4]>();
4203        let mut output = kani::any::<[u8; 3]>();
4204        let result = ct::STANDARD.decode_slice_clear_tail(&input, &mut output);
4205
4206        if let Ok(written) = result {
4207            assert!(written <= output.len());
4208        }
4209    }
4210
4211    #[kani::proof]
4212    #[kani::unwind(3)]
4213    fn ct_standard_decode_slice_clear_tail_clears_output_on_error() {
4214        let input = kani::any::<[u8; 4]>();
4215        let mut output = kani::any::<[u8; 3]>();
4216        let result = ct::STANDARD.decode_slice_clear_tail(&input, &mut output);
4217
4218        if result.is_err() {
4219            assert!(output.iter().all(|byte| *byte == 0));
4220        }
4221    }
4222
4223    #[kani::proof]
4224    #[kani::unwind(3)]
4225    fn ct_standard_decode_in_place_clear_tail_clears_buffer_on_error() {
4226        let mut buffer = kani::any::<[u8; 4]>();
4227        let result = ct::STANDARD.decode_in_place_clear_tail(&mut buffer);
4228
4229        if result.is_err() {
4230            assert!(buffer.iter().all(|byte| *byte == 0));
4231        }
4232    }
4233
4234    #[kani::proof]
4235    #[kani::unwind(3)]
4236    fn ct_standard_validate_matches_decode_for_one_quantum() {
4237        let input = kani::any::<[u8; 4]>();
4238        let mut output = kani::any::<[u8; 3]>();
4239
4240        let validate_ok = ct::STANDARD.validate_result(&input).is_ok();
4241        let decode_ok = ct::STANDARD
4242            .decode_slice_clear_tail(&input, &mut output)
4243            .is_ok();
4244
4245        assert!(validate_ok == decode_ok);
4246    }
4247}
4248
4249#[cfg(test)]
4250mod tests {
4251    use super::*;
4252
4253    fn fill_pattern(output: &mut [u8], seed: usize) {
4254        for (index, byte) in output.iter_mut().enumerate() {
4255            let value = (index * 73 + seed * 19) % 256;
4256            *byte = u8::try_from(value).unwrap();
4257        }
4258    }
4259
4260    fn assert_encode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
4261    where
4262        A: Alphabet,
4263    {
4264        let engine = Engine::<A, PAD>::new();
4265        let mut dispatched = [0x55; 256];
4266        let mut scalar = [0xaa; 256];
4267
4268        let dispatched_result = engine.encode_slice(input, &mut dispatched);
4269        let scalar_result = backend::scalar_reference_encode_slice::<A, PAD>(input, &mut scalar);
4270
4271        assert_eq!(dispatched_result, scalar_result);
4272        if let Ok(written) = dispatched_result {
4273            assert_eq!(&dispatched[..written], &scalar[..written]);
4274        }
4275
4276        let required = checked_encoded_len(input.len(), PAD).unwrap();
4277        if required > 0 {
4278            let mut dispatched_short = [0x55; 256];
4279            let mut scalar_short = [0xaa; 256];
4280            let available = required - 1;
4281
4282            assert_eq!(
4283                engine.encode_slice(input, &mut dispatched_short[..available]),
4284                backend::scalar_reference_encode_slice::<A, PAD>(
4285                    input,
4286                    &mut scalar_short[..available],
4287                )
4288            );
4289        }
4290    }
4291
4292    fn assert_decode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
4293    where
4294        A: Alphabet,
4295    {
4296        let engine = Engine::<A, PAD>::new();
4297        let mut dispatched = [0x55; 128];
4298        let mut scalar = [0xaa; 128];
4299
4300        let dispatched_result = engine.decode_slice(input, &mut dispatched);
4301        let scalar_result = backend::scalar_reference_decode_slice::<A, PAD>(input, &mut scalar);
4302
4303        assert_eq!(dispatched_result, scalar_result);
4304        if let Ok(written) = dispatched_result {
4305            assert_eq!(&dispatched[..written], &scalar[..written]);
4306
4307            if written > 0 {
4308                let mut dispatched_short = [0x55; 128];
4309                let mut scalar_short = [0xaa; 128];
4310                let available = written - 1;
4311
4312                assert_eq!(
4313                    engine.decode_slice(input, &mut dispatched_short[..available]),
4314                    backend::scalar_reference_decode_slice::<A, PAD>(
4315                        input,
4316                        &mut scalar_short[..available],
4317                    )
4318                );
4319            }
4320        }
4321    }
4322
4323    fn assert_backend_round_trip_matches_scalar<A, const PAD: bool>(input: &[u8])
4324    where
4325        A: Alphabet,
4326    {
4327        assert_encode_backend_matches_scalar::<A, PAD>(input);
4328
4329        let mut encoded = [0; 256];
4330        let encoded_len =
4331            backend::scalar_reference_encode_slice::<A, PAD>(input, &mut encoded).unwrap();
4332        assert_decode_backend_matches_scalar::<A, PAD>(&encoded[..encoded_len]);
4333    }
4334
4335    fn assert_standard_decode_chunk_matches_input(input: &[u8]) {
4336        let mut encoded = [0u8; 4];
4337        let encoded_len = STANDARD.encode_slice(input, &mut encoded).unwrap();
4338        assert_eq!(encoded_len, 4);
4339
4340        let chunk = [encoded[0], encoded[1], encoded[2], encoded[3]];
4341        let mut decoded = [0u8; 3];
4342        let decoded_len = decode_chunk::<Standard, true>(chunk, &mut decoded).unwrap();
4343
4344        assert_eq!(decoded_len, input.len());
4345        assert_eq!(&decoded[..decoded_len], input);
4346    }
4347
4348    #[test]
4349    fn backend_dispatch_matches_scalar_reference_for_canonical_inputs() {
4350        let mut input = [0; 128];
4351
4352        for input_len in 0..=input.len() {
4353            fill_pattern(&mut input[..input_len], input_len);
4354            let input = &input[..input_len];
4355
4356            assert_backend_round_trip_matches_scalar::<Standard, true>(input);
4357            assert_backend_round_trip_matches_scalar::<Standard, false>(input);
4358            assert_backend_round_trip_matches_scalar::<UrlSafe, true>(input);
4359            assert_backend_round_trip_matches_scalar::<UrlSafe, false>(input);
4360        }
4361    }
4362
4363    #[test]
4364    fn backend_dispatch_matches_scalar_reference_for_malformed_inputs() {
4365        for input in [
4366            &b"Z"[..],
4367            b"====",
4368            b"AA=A",
4369            b"Zh==",
4370            b"Zm9=",
4371            b"Zm9v$g==",
4372            b"Zm9vZh==",
4373        ] {
4374            assert_decode_backend_matches_scalar::<Standard, true>(input);
4375        }
4376
4377        for input in [&b"Z"[..], b"AA=A", b"Zh", b"Zm9", b"Zm9vYg$"] {
4378            assert_decode_backend_matches_scalar::<Standard, false>(input);
4379        }
4380
4381        assert_decode_backend_matches_scalar::<UrlSafe, true>(b"AA+A");
4382        assert_decode_backend_matches_scalar::<UrlSafe, false>(b"AA/A");
4383        assert_decode_backend_matches_scalar::<Standard, true>(b"AA-A");
4384        assert_decode_backend_matches_scalar::<Standard, false>(b"AA_A");
4385    }
4386
4387    #[test]
4388    fn decode_chunk_bit_packing_matches_exhaustive_small_inputs() {
4389        for byte in u8::MIN..=u8::MAX {
4390            assert_standard_decode_chunk_matches_input(&[byte]);
4391        }
4392
4393        for first in u8::MIN..=u8::MAX {
4394            for second in u8::MIN..=u8::MAX {
4395                assert_standard_decode_chunk_matches_input(&[first, second]);
4396            }
4397        }
4398    }
4399
4400    #[test]
4401    fn decode_chunk_bit_packing_matches_representative_full_quanta() {
4402        const SAMPLES: [u8; 16] = [
4403            0, 1, 2, 15, 16, 31, 32, 63, 64, 95, 127, 128, 191, 192, 254, 255,
4404        ];
4405
4406        for first in SAMPLES {
4407            for second in SAMPLES {
4408                for third in SAMPLES {
4409                    assert_standard_decode_chunk_matches_input(&[first, second, third]);
4410                }
4411            }
4412        }
4413    }
4414
4415    #[test]
4416    fn ct_padded_final_quantum_fails_closed_for_invalid_padding_count() {
4417        let (_, invalid_byte, invalid_padding, written) =
4418            ct_padded_final_quantum::<Standard>(*b"ABCD", 3);
4419
4420        assert_ne!(invalid_byte, 0);
4421        assert_ne!(invalid_padding, 0);
4422        assert_eq!(written, 0);
4423        assert_eq!(
4424            report_ct_error(invalid_byte, invalid_padding),
4425            Err(DecodeError::InvalidInput)
4426        );
4427    }
4428
4429    #[cfg(feature = "simd")]
4430    #[test]
4431    fn simd_dispatch_scaffold_keeps_scalar_active() {
4432        assert_eq!(simd::active_backend(), simd::ActiveBackend::Scalar);
4433        let _candidate = simd::detected_candidate();
4434    }
4435
4436    #[test]
4437    fn encodes_standard_vectors() {
4438        let vectors = [
4439            (&b""[..], &b""[..]),
4440            (&b"f"[..], &b"Zg=="[..]),
4441            (&b"fo"[..], &b"Zm8="[..]),
4442            (&b"foo"[..], &b"Zm9v"[..]),
4443            (&b"foob"[..], &b"Zm9vYg=="[..]),
4444            (&b"fooba"[..], &b"Zm9vYmE="[..]),
4445            (&b"foobar"[..], &b"Zm9vYmFy"[..]),
4446        ];
4447        for (input, expected) in vectors {
4448            let mut output = [0u8; 16];
4449            let written = STANDARD.encode_slice(input, &mut output).unwrap();
4450            assert_eq!(&output[..written], expected);
4451        }
4452    }
4453
4454    #[test]
4455    fn decodes_standard_vectors() {
4456        let vectors = [
4457            (&b""[..], &b""[..]),
4458            (&b"Zg=="[..], &b"f"[..]),
4459            (&b"Zm8="[..], &b"fo"[..]),
4460            (&b"Zm9v"[..], &b"foo"[..]),
4461            (&b"Zm9vYg=="[..], &b"foob"[..]),
4462            (&b"Zm9vYmE="[..], &b"fooba"[..]),
4463            (&b"Zm9vYmFy"[..], &b"foobar"[..]),
4464        ];
4465        for (input, expected) in vectors {
4466            let mut output = [0u8; 16];
4467            let written = STANDARD.decode_slice(input, &mut output).unwrap();
4468            assert_eq!(&output[..written], expected);
4469        }
4470    }
4471
4472    #[test]
4473    fn supports_unpadded_url_safe() {
4474        let mut encoded = [0u8; 16];
4475        let written = URL_SAFE_NO_PAD
4476            .encode_slice(b"\xfb\xff", &mut encoded)
4477            .unwrap();
4478        assert_eq!(&encoded[..written], b"-_8");
4479
4480        let mut decoded = [0u8; 2];
4481        let written = URL_SAFE_NO_PAD
4482            .decode_slice(&encoded[..written], &mut decoded)
4483            .unwrap();
4484        assert_eq!(&decoded[..written], b"\xfb\xff");
4485    }
4486
4487    #[test]
4488    fn decodes_in_place() {
4489        let mut buffer = *b"Zm9vYmFy";
4490        let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
4491        assert_eq!(decoded, b"foobar");
4492    }
4493
4494    #[test]
4495    fn rejects_non_canonical_padding_bits() {
4496        let mut output = [0u8; 4];
4497        assert_eq!(
4498            STANDARD.decode_slice(b"Zh==", &mut output),
4499            Err(DecodeError::InvalidPadding { index: 1 })
4500        );
4501        assert_eq!(
4502            STANDARD.decode_slice(b"Zm9=", &mut output),
4503            Err(DecodeError::InvalidPadding { index: 2 })
4504        );
4505    }
4506}