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 cleanup;
109mod profiles;
110
111pub use alphabet::{
112    Alphabet, AlphabetError, Bcrypt, Crypt, Standard, UrlSafe, decode_alphabet_byte,
113    validate_alphabet,
114};
115pub(crate) use alphabet::{encode_base64_value, encode_base64_value_runtime};
116pub(crate) use cleanup::{wipe_bytes, wipe_tail};
117#[cfg(feature = "alloc")]
118pub(crate) use cleanup::{wipe_vec_all, wipe_vec_spare_capacity};
119pub use profiles::{BCRYPT, CRYPT, MIME, PEM, PEM_CRLF, Profile};
120
121#[cfg(feature = "simd")]
122mod simd;
123
124/// Runtime backend reporting for security-sensitive deployments.
125///
126/// This module does not enable acceleration. It exposes the backend posture so
127/// callers can log, assert, or audit whether execution is scalar-only or merely
128/// detecting future SIMD candidates.
129pub mod runtime;
130
131#[cfg(feature = "stream")]
132pub mod stream;
133
134/// Constant-time-oriented scalar decoding APIs.
135///
136/// This module is separate from the default decoder so callers can opt into a
137/// slower path with a narrower timing target. It avoids lookup tables indexed
138/// by secret input bytes while mapping Base64 symbols and reports malformed
139/// content through one opaque error. It is not documented as a formally
140/// verified cryptographic constant-time API.
141///
142/// # Security
143///
144/// Input length, decoded length, selected alphabet, and final success or
145/// failure remain public. The clear-tail methods wipe caller-owned output on
146/// error, but decoded bytes are written during the fixed-shape decode loop
147/// before final validation is reported. In shared-memory, enclave, or HSM-style
148/// threat models where another component can observe the output buffer during
149/// the call, prefer [`crate::ct::CtEngine::decode_slice_staged_clear_tail`]
150/// with a private staging buffer. In those deployments,
151/// [`crate::ct::CtEngine::decode_slice_clear_tail`] is not sufficient by
152/// itself because it wipes caller-owned output only after the internal decode
153/// loop reaches the final error gate.
154///
155/// The dependency-free comparison helpers on redacted buffers are
156/// constant-time-oriented best effort, not formally audited MAC or token
157/// comparison primitives. Applications that can admit dependencies and need a
158/// reviewed comparison primitive should use one at the protocol boundary.
159///
160/// The CT decoder exposes only clear-tail and stack-backed decode APIs. The
161/// former non-clear-tail methods were removed before the `1.0` stable boundary
162/// because they could leave decoded plaintext in caller-owned buffers after
163/// malformed input errors.
164///
165/// ```compile_fail
166/// use base64_ng::ct;
167///
168/// let mut output = [0u8; 8];
169/// let _ = ct::STANDARD.decode_slice(b"aGk=", &mut output);
170/// ```
171///
172/// ```compile_fail
173/// use base64_ng::ct;
174///
175/// let mut buffer = *b"aGk=";
176/// let _ = ct::STANDARD.decode_in_place(&mut buffer);
177/// ```
178pub mod ct {
179    use super::{
180        Alphabet, DecodeError, DecodedBuffer, Standard, UrlSafe, ct_decode_in_place,
181        ct_decode_slice, ct_decode_slice_staged_clear_tail, ct_decoded_len, ct_validate_decode,
182    };
183    use core::marker::PhantomData;
184
185    /// Standard Base64 constant-time-oriented decoder with padding.
186    pub const STANDARD: CtEngine<Standard, true> = CtEngine::new();
187
188    /// Standard Base64 constant-time-oriented decoder without padding.
189    pub const STANDARD_NO_PAD: CtEngine<Standard, false> = CtEngine::new();
190
191    /// URL-safe Base64 constant-time-oriented decoder with padding.
192    pub const URL_SAFE: CtEngine<UrlSafe, true> = CtEngine::new();
193
194    /// URL-safe Base64 constant-time-oriented decoder without padding.
195    pub const URL_SAFE_NO_PAD: CtEngine<UrlSafe, false> = CtEngine::new();
196
197    /// A zero-sized constant-time-oriented Base64 decoder.
198    ///
199    /// # Security
200    ///
201    /// For ordinary secret-bearing inputs, prefer
202    /// [`Self::decode_slice_clear_tail`], [`Self::decode_buffer`], or
203    /// [`Self::decode_in_place_clear_tail`]. For shared-memory,
204    /// enclave-adjacent, HSM-style, or multi-principal deployments where
205    /// another component can observe caller-owned output during the call, use
206    /// [`Self::decode_slice_staged_clear_tail`] with a private staging buffer
207    /// so malformed input cannot transiently write decoded bytes into the
208    /// public output buffer before the final error gate.
209    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
210    pub struct CtEngine<A, const PAD: bool> {
211        alphabet: PhantomData<A>,
212    }
213
214    impl<A, const PAD: bool> CtEngine<A, PAD>
215    where
216        A: Alphabet,
217    {
218        /// Creates a new constant-time-oriented decoder engine.
219        #[must_use]
220        pub const fn new() -> Self {
221            Self {
222                alphabet: PhantomData,
223            }
224        }
225
226        /// Returns whether this constant-time-oriented decoder expects padded
227        /// input.
228        #[must_use]
229        pub const fn is_padded(&self) -> bool {
230            PAD
231        }
232
233        /// Validates `input` without writing decoded bytes.
234        ///
235        /// This uses the same constant-time-oriented symbol mapping and opaque
236        /// malformed-input error behavior as
237        /// [`Self::decode_slice_clear_tail`]. Input length, padding length, and
238        /// final success or failure remain public.
239        ///
240        /// # Examples
241        ///
242        /// ```
243        /// use base64_ng::ct;
244        ///
245        /// ct::STANDARD.validate_result(b"aGVsbG8=").unwrap();
246        /// assert!(ct::STANDARD.validate_result(b"aGVsbG8").is_err());
247        /// ```
248        pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
249            ct_validate_decode::<A, PAD>(input)
250        }
251
252        /// Returns whether `input` is valid for this constant-time-oriented
253        /// decoder.
254        ///
255        /// This is a convenience wrapper around [`Self::validate_result`].
256        ///
257        /// # Examples
258        ///
259        /// ```
260        /// use base64_ng::ct;
261        ///
262        /// assert!(ct::URL_SAFE_NO_PAD.validate(b"-_8"));
263        /// assert!(!ct::URL_SAFE_NO_PAD.validate(b"+/8"));
264        /// ```
265        #[must_use]
266        pub fn validate(&self, input: &[u8]) -> bool {
267            self.validate_result(input).is_ok()
268        }
269
270        /// Returns the exact decoded length for valid input.
271        ///
272        /// This uses the same constant-time-oriented validation policy as
273        /// [`Self::validate_result`] before returning a length. Input length,
274        /// padding length, and final success or failure remain public.
275        pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
276            ct_decoded_len::<A, PAD>(input)
277        }
278
279        /// Decodes `input` into `output` and clears all bytes after the
280        /// decoded prefix.
281        ///
282        /// If decoding fails, the entire output buffer is cleared before the
283        /// error is returned. Use this variant for sensitive payloads where
284        /// partially decoded bytes from rejected input should not remain in the
285        /// caller-owned output buffer.
286        ///
287        /// # Security: Transient Plaintext Window
288        ///
289        /// Decoded bytes are written to `output` progressively during the
290        /// fixed-shape decode loop before malformed-input detection is
291        /// complete. On error, the entire `output` is wiped before returning,
292        /// but a concurrent same-process observer with access to `output`
293        /// during the call may observe transient partial plaintext from valid
294        /// leading quanta. For shared-memory, enclave-adjacent, HSM-style, or
295        /// multi-principal deployments where even transient writes are
296        /// unacceptable, use [`Self::decode_slice_staged_clear_tail`] with a
297        /// private staging buffer.
298        ///
299        /// # Examples
300        ///
301        /// ```
302        /// use base64_ng::ct;
303        ///
304        /// let mut output = [0xff; 8];
305        /// let written = ct::STANDARD
306        ///     .decode_slice_clear_tail(b"aGk=", &mut output)
307        ///     .unwrap();
308        ///
309        /// assert_eq!(&output[..written], b"hi");
310        /// assert!(output[written..].iter().all(|byte| *byte == 0));
311        /// ```
312        #[must_use = "handle decode errors; use decode_slice_staged_clear_tail for shared-memory or HSM-style threat models"]
313        pub fn decode_slice_clear_tail(
314            &self,
315            input: &[u8],
316            output: &mut [u8],
317        ) -> Result<usize, DecodeError> {
318            let written = match ct_decode_slice::<A, PAD>(input, output) {
319                Ok(written) => written,
320                Err(err) => {
321                    crate::wipe_bytes(output);
322                    return Err(err);
323                }
324            };
325            crate::wipe_tail(output, written);
326            Ok(written)
327        }
328
329        /// Decodes through caller-provided private staging before copying into
330        /// `output`.
331        ///
332        /// This variant is for shared-memory or sandboxed deployments where
333        /// the caller-owned `output` buffer must not contain transient decoded
334        /// bytes from malformed input. The `staging` buffer must be at least
335        /// the decoded length of `input` and must not be shared with
336        /// untrusted concurrent observers. On success, decoded bytes are
337        /// copied from `staging` into `output`; on error, both buffers are
338        /// cleared before returning.
339        ///
340        /// Input length, final success or failure, and decoded length remain
341        /// public.
342        #[must_use = "handle decode errors; staged decode is for shared-memory or HSM-style threat models"]
343        pub fn decode_slice_staged_clear_tail(
344            &self,
345            input: &[u8],
346            output: &mut [u8],
347            staging: &mut [u8],
348        ) -> Result<usize, DecodeError> {
349            ct_decode_slice_staged_clear_tail::<A, PAD>(input, output, staging)
350        }
351
352        /// Decodes `input` into a stack-backed buffer.
353        ///
354        /// This uses the same constant-time-oriented scalar decoder as
355        /// [`Self::decode_slice_clear_tail`] and clears the internal backing
356        /// array before returning an error.
357        ///
358        /// # Examples
359        ///
360        /// ```
361        /// use base64_ng::ct;
362        ///
363        /// let decoded = ct::STANDARD.decode_buffer::<5>(b"aGVsbG8=").unwrap();
364        ///
365        /// assert_eq!(decoded.as_bytes(), b"hello");
366        /// ```
367        pub fn decode_buffer<const CAP: usize>(
368            &self,
369            input: &[u8],
370        ) -> Result<DecodedBuffer<CAP>, DecodeError> {
371            let mut output = DecodedBuffer::new();
372            let written = match self.decode_slice_clear_tail(input, &mut output.bytes) {
373                Ok(written) => written,
374                Err(err) => {
375                    output.clear();
376                    return Err(err);
377                }
378            };
379            output.len = written;
380            Ok(output)
381        }
382
383        /// Decodes `buffer` in place and clears all bytes after the decoded
384        /// prefix.
385        ///
386        /// If decoding fails, the entire buffer is cleared before the error is
387        /// returned.
388        ///
389        /// # Security: Transient Plaintext Window
390        ///
391        /// This in-place API writes decoded bytes into `buffer` during the
392        /// fixed-shape decode loop before malformed-input detection is
393        /// complete. On error, the entire buffer is wiped before returning,
394        /// but concurrent same-process observers with access to the same memory
395        /// can observe transient partial plaintext. Use
396        /// [`Self::decode_slice_staged_clear_tail`] with a private staging
397        /// buffer when shared-memory or enclave-adjacent deployments cannot
398        /// tolerate that window.
399        ///
400        /// # Examples
401        ///
402        /// ```
403        /// use base64_ng::ct;
404        ///
405        /// let mut buffer = *b"aGk=";
406        /// let decoded = ct::STANDARD.decode_in_place_clear_tail(&mut buffer).unwrap();
407        ///
408        /// assert_eq!(decoded, b"hi");
409        /// ```
410        pub fn decode_in_place_clear_tail<'a>(
411            &self,
412            buffer: &'a mut [u8],
413        ) -> Result<&'a mut [u8], DecodeError> {
414            let len = match ct_decode_in_place::<A, PAD>(buffer) {
415                Ok(len) => len,
416                Err(err) => {
417                    crate::wipe_bytes(buffer);
418                    return Err(err);
419                }
420            };
421            crate::wipe_tail(buffer, len);
422            Ok(&mut buffer[..len])
423        }
424    }
425
426    impl<A, const PAD: bool> core::fmt::Display for CtEngine<A, PAD> {
427        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
428            write!(formatter, "ct padded={PAD}")
429        }
430    }
431}
432
433/// Standard Base64 engine with padding.
434///
435/// This default strict engine is not a constant-time token validator or
436/// key-material decoder. Use [`ct::STANDARD`] or [`Engine::ct_decoder`] for the
437/// matching constant-time-oriented decoder when timing posture matters.
438#[doc(alias = "ct")]
439#[doc(alias = "constant_time")]
440#[doc(alias = "sensitive")]
441pub const STANDARD: Engine<Standard, true> = Engine::new();
442
443/// Standard Base64 engine without padding.
444///
445/// This default strict engine is not a constant-time token validator or
446/// key-material decoder. Use [`ct::STANDARD_NO_PAD`] or [`Engine::ct_decoder`]
447/// for the matching constant-time-oriented decoder when timing posture
448/// matters.
449#[doc(alias = "ct")]
450#[doc(alias = "constant_time")]
451#[doc(alias = "sensitive")]
452pub const STANDARD_NO_PAD: Engine<Standard, false> = Engine::new();
453
454/// URL-safe Base64 engine with padding.
455///
456/// This default strict engine is not a constant-time token validator or
457/// key-material decoder. Use [`ct::URL_SAFE`] or [`Engine::ct_decoder`] for the
458/// matching constant-time-oriented decoder when timing posture matters.
459#[doc(alias = "ct")]
460#[doc(alias = "constant_time")]
461#[doc(alias = "sensitive")]
462pub const URL_SAFE: Engine<UrlSafe, true> = Engine::new();
463
464/// URL-safe Base64 engine without padding.
465///
466/// This default strict engine is not a constant-time token validator or
467/// key-material decoder. Use [`ct::URL_SAFE_NO_PAD`] or [`Engine::ct_decoder`]
468/// for the matching constant-time-oriented decoder when timing posture
469/// matters.
470#[doc(alias = "ct")]
471#[doc(alias = "constant_time")]
472#[doc(alias = "sensitive")]
473pub const URL_SAFE_NO_PAD: Engine<UrlSafe, false> = Engine::new();
474
475/// bcrypt-style Base64 engine without padding.
476///
477/// This uses the bcrypt alphabet with the crate's normal Base64 bit packing.
478/// It does not parse complete bcrypt password-hash strings. This default strict
479/// engine is not a constant-time token validator or key-material decoder; use
480/// [`Engine::ct_decoder`] for the matching constant-time-oriented decoder when
481/// timing posture matters.
482#[doc(alias = "ct")]
483#[doc(alias = "constant_time")]
484#[doc(alias = "sensitive")]
485pub const BCRYPT_NO_PAD: Engine<Bcrypt, false> = Engine::new();
486
487/// Unix `crypt(3)`-style Base64 engine without padding.
488///
489/// This uses the `crypt(3)` alphabet with the crate's normal Base64 bit
490/// packing. It does not parse complete password-hash strings. This default
491/// strict engine is not a constant-time token validator or key-material
492/// decoder; use [`Engine::ct_decoder`] for the matching constant-time-oriented
493/// decoder when timing posture matters.
494#[doc(alias = "ct")]
495#[doc(alias = "constant_time")]
496#[doc(alias = "sensitive")]
497pub const CRYPT_NO_PAD: Engine<Crypt, false> = Engine::new();
498
499/// Line ending used by wrapped Base64 output.
500#[derive(Clone, Copy, Debug, Eq, PartialEq)]
501pub enum LineEnding {
502    /// Line feed (`\n`).
503    Lf,
504    /// Carriage return followed by line feed (`\r\n`).
505    CrLf,
506}
507
508impl LineEnding {
509    /// Returns a stable printable identifier for this line ending.
510    #[must_use]
511    pub const fn name(self) -> &'static str {
512        match self {
513            Self::Lf => "LF",
514            Self::CrLf => "CRLF",
515        }
516    }
517
518    /// Returns the text representation of this line ending.
519    #[must_use]
520    pub const fn as_str(self) -> &'static str {
521        match self {
522            Self::Lf => "\n",
523            Self::CrLf => "\r\n",
524        }
525    }
526
527    /// Returns the byte representation of this line ending.
528    #[must_use]
529    pub const fn as_bytes(self) -> &'static [u8] {
530        self.as_str().as_bytes()
531    }
532
533    /// Returns the byte length of this line ending.
534    #[must_use]
535    pub const fn byte_len(self) -> usize {
536        match self {
537            Self::Lf => 1,
538            Self::CrLf => 2,
539        }
540    }
541}
542
543impl core::fmt::Display for LineEnding {
544    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
545        formatter.write_str(self.name())
546    }
547}
548
549/// Base64 line wrapping policy.
550///
551/// `line_len` is measured in encoded Base64 bytes, not source input bytes.
552/// Encoders insert line endings between lines and do not append a trailing line
553/// ending after the final line.
554#[derive(Clone, Copy, Debug, Eq, PartialEq)]
555pub struct LineWrap {
556    /// Maximum encoded bytes per line.
557    pub line_len: usize,
558    /// Line ending inserted between wrapped lines.
559    pub line_ending: LineEnding,
560}
561
562impl LineWrap {
563    /// MIME-style wrapping: 76 columns with CRLF endings.
564    pub const MIME: Self = Self::new(76, LineEnding::CrLf);
565    /// PEM-style wrapping: 64 columns with LF endings.
566    pub const PEM: Self = Self::new(64, LineEnding::Lf);
567    /// PEM-style wrapping: 64 columns with CRLF endings.
568    pub const PEM_CRLF: Self = Self::new(64, LineEnding::CrLf);
569
570    /// Creates a wrapping policy.
571    ///
572    /// This constructor is intended for fixed, trusted values such as
573    /// compile-time MIME or PEM profile constants. Use [`Self::checked_new`]
574    /// when the line length comes from configuration, network input, file
575    /// metadata, or another untrusted runtime source.
576    ///
577    /// # Panics
578    ///
579    /// Panics when `line_len` is zero. Base64 wrapping requires a non-zero
580    /// encoded line length; accepting zero would make progress impossible for
581    /// wrapped encoders. This constructor is callable at runtime, so do not
582    /// pass attacker-controlled or externally configured values here; use
583    /// [`Self::checked_new`] for those cases.
584    #[must_use]
585    pub const fn new(line_len: usize, line_ending: LineEnding) -> Self {
586        assert!(line_len != 0, "base64 line wrap length must be non-zero");
587        Self {
588            line_len,
589            line_ending,
590        }
591    }
592
593    /// Creates a wrapping policy, returning `None` when the line length is
594    /// invalid.
595    ///
596    /// Base64 line-wrapping requires a non-zero encoded line length. This
597    /// helper is useful when accepting a wrapping policy from configuration or
598    /// another untrusted source.
599    #[must_use]
600    pub const fn checked_new(line_len: usize, line_ending: LineEnding) -> Option<Self> {
601        if line_len == 0 {
602            None
603        } else {
604            Some(Self::new(line_len, line_ending))
605        }
606    }
607
608    /// Returns the maximum encoded bytes per line.
609    #[must_use]
610    pub const fn line_len(self) -> usize {
611        self.line_len
612    }
613
614    /// Returns the line ending inserted between wrapped lines.
615    #[must_use]
616    pub const fn line_ending(self) -> LineEnding {
617        self.line_ending
618    }
619
620    /// Returns whether this wrapping policy can be used by the encoder.
621    #[must_use]
622    pub const fn is_valid(self) -> bool {
623        self.line_len != 0
624    }
625}
626
627impl core::fmt::Display for LineWrap {
628    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
629        write!(formatter, "{}:{}", self.line_len, self.line_ending.name())
630    }
631}
632
633/// Stack-backed encoded Base64 output.
634///
635/// This type is intended for short values where heap allocation would be
636/// unnecessary but manually sizing and passing a separate output slice is
637/// noisy. Its visible bytes are produced by crate encoders, so [`Self::as_str`]
638/// can return `&str` without exposing a fallible UTF-8 conversion to callers.
639///
640/// The backing array is cleared when the value is dropped. This is best-effort
641/// data-retention reduction and is not a formal zeroization guarantee.
642///
643/// On `wasm32` targets, the wipe barrier uses only a compiler fence. The wasm
644/// runtime JIT may still optimize or retain cleared bytes in ways this crate
645/// cannot control. `wasm32` builds fail closed by default; enable
646/// `allow-wasm32-best-effort-wipe` only when the deployment explicitly accepts
647/// this limitation and applies its own memory strategy around stack-backed
648/// buffers.
649pub struct EncodedBuffer<const CAP: usize> {
650    bytes: [u8; CAP],
651    len: usize,
652}
653
654/// Owned stack array extracted from [`EncodedBuffer`].
655///
656/// This wrapper keeps the extracted encoded bytes on the crate's best-effort
657/// drop-time cleanup path. Use
658/// [`Self::into_exposed_unprotected_array_caller_must_zeroize`] only when a
659/// bare array is unavoidable and the caller will handle cleanup.
660pub struct ExposedEncodedArray<const CAP: usize> {
661    bytes: [u8; CAP],
662    len: usize,
663}
664
665impl<const CAP: usize> ExposedEncodedArray<CAP> {
666    /// Wraps an encoded backing array and visible length.
667    ///
668    /// # Panics
669    ///
670    /// Panics if `len` is greater than `CAP`.
671    #[must_use]
672    pub const fn from_array(bytes: [u8; CAP], len: usize) -> Self {
673        assert!(len <= CAP, "visible length exceeds array capacity");
674        Self { bytes, len }
675    }
676
677    /// Returns the visible encoded bytes.
678    #[must_use]
679    pub fn as_bytes(&self) -> &[u8] {
680        &self.bytes[..self.len]
681    }
682
683    /// Returns the number of visible encoded bytes.
684    #[must_use]
685    pub const fn len(&self) -> usize {
686        self.len
687    }
688
689    /// Returns whether there are no visible encoded bytes.
690    #[must_use]
691    pub const fn is_empty(&self) -> bool {
692        self.len == 0
693    }
694
695    /// Returns the backing array capacity.
696    #[must_use]
697    pub const fn capacity(&self) -> usize {
698        CAP
699    }
700
701    /// Consumes the wrapper and returns a bare array plus visible length.
702    ///
703    /// This is an unprotected escape hatch. The returned array will not be
704    /// cleared by this crate on drop. Callers must clear it with their own
705    /// approved zeroization policy.
706    ///
707    /// # Security
708    ///
709    /// Treat this as a cleanup-boundary API. Failing to clear the returned
710    /// array leaves the encoded bytes in ordinary caller-owned memory until
711    /// overwritten by later stack or heap activity.
712    #[must_use = "caller must zeroize the returned array"]
713    pub fn into_exposed_unprotected_array_caller_must_zeroize(mut self) -> ([u8; CAP], usize) {
714        let len = self.len;
715        self.len = 0;
716        (core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
717    }
718}
719
720impl<const CAP: usize> Drop for ExposedEncodedArray<CAP> {
721    fn drop(&mut self) {
722        wipe_bytes(&mut self.bytes);
723        self.len = 0;
724    }
725}
726
727impl<const CAP: usize> core::fmt::Debug for ExposedEncodedArray<CAP> {
728    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
729        formatter
730            .debug_struct("ExposedEncodedArray")
731            .field("bytes", &"<redacted>")
732            .field("len", &self.len)
733            .field("capacity", &CAP)
734            .finish()
735    }
736}
737
738impl<const CAP: usize> EncodedBuffer<CAP> {
739    /// Creates an empty encoded buffer.
740    #[must_use]
741    pub const fn new() -> Self {
742        Self {
743            bytes: [0u8; CAP],
744            len: 0,
745        }
746    }
747
748    /// Returns the number of visible encoded bytes.
749    #[must_use]
750    pub const fn len(&self) -> usize {
751        self.len
752    }
753
754    /// Returns whether the buffer has no visible encoded bytes.
755    #[must_use]
756    pub const fn is_empty(&self) -> bool {
757        self.len == 0
758    }
759
760    /// Returns whether the visible encoded bytes fill the stack backing array.
761    #[must_use]
762    pub const fn is_full(&self) -> bool {
763        self.len == CAP
764    }
765
766    /// Returns the stack capacity in bytes.
767    #[must_use]
768    pub const fn capacity(&self) -> usize {
769        CAP
770    }
771
772    /// Returns the number of unused bytes in the stack backing array.
773    #[must_use]
774    pub const fn remaining_capacity(&self) -> usize {
775        CAP - self.len
776    }
777
778    /// Returns the visible encoded bytes.
779    #[must_use]
780    pub fn as_bytes(&self) -> &[u8] {
781        &self.bytes[..self.len]
782    }
783
784    /// Returns the visible encoded bytes as UTF-8 text.
785    ///
786    /// Encoded Base64 output is produced as ASCII by this crate, so this
787    /// method should not fail unless an internal invariant has been broken.
788    /// It is provided for callers that prefer a fallible accessor over
789    /// [`Self::as_str`].
790    pub fn as_utf8(&self) -> Result<&str, core::str::Utf8Error> {
791        core::str::from_utf8(self.as_bytes())
792    }
793
794    /// Returns the visible encoded bytes as UTF-8.
795    ///
796    /// # Panics
797    ///
798    /// Panics only if the crate's internal invariant is broken and the buffer
799    /// contains non-UTF-8 bytes.
800    #[must_use]
801    pub fn as_str(&self) -> &str {
802        match self.as_utf8() {
803            Ok(output) => output,
804            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
805        }
806    }
807
808    /// Compares this encoded output to `other` without short-circuiting on the
809    /// first differing byte.
810    ///
811    /// Length and the final equality result remain public. Different lengths
812    /// return `false` immediately; use this helper only when the compared
813    /// lengths are public protocol facts or have been normalized by the
814    /// caller. For equal-length inputs, this helper scans every byte before
815    /// returning. It is constant-time-oriented best effort, not a formal
816    /// cryptographic constant-time guarantee. This comparison is deliberately
817    /// explicit: redacted buffer types do not implement [`PartialEq`] because
818    /// `==` would make a best-effort helper look like a formal token/MAC
819    /// comparison primitive.
820    ///
821    /// Do not use this helper as the sole MAC, bearer-token, password-hash, or
822    /// authentication-secret comparison primitive in high-assurance systems.
823    /// Applications that can admit dependencies should use a reviewed
824    /// constant-time comparison primitive, such as `subtle`, at the protocol
825    /// boundary.
826    #[doc(alias = "constant_time_eq")]
827    #[must_use]
828    pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
829        constant_time_eq_public_len(self.as_bytes(), other)
830    }
831
832    /// Consumes the wrapper and returns the backing array plus visible length
833    /// inside a drop-wiping exposed wrapper.
834    ///
835    /// This is an explicit escape hatch for no-alloc interop with APIs that
836    /// require ownership of a fixed array. The returned
837    /// [`ExposedEncodedArray`] remains redacted by formatting and clears its
838    /// backing array on drop.
839    #[must_use]
840    pub fn into_exposed_array(mut self) -> ExposedEncodedArray<CAP> {
841        let len = self.len;
842        self.len = 0;
843        ExposedEncodedArray::from_array(core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
844    }
845
846    /// Clears the visible bytes and the full backing array.
847    pub fn clear(&mut self) {
848        wipe_bytes(&mut self.bytes);
849        self.len = 0;
850    }
851
852    /// Clears bytes after the visible prefix.
853    pub fn clear_tail(&mut self) {
854        wipe_tail(&mut self.bytes, self.len);
855    }
856}
857
858impl<const CAP: usize> AsRef<[u8]> for EncodedBuffer<CAP> {
859    fn as_ref(&self) -> &[u8] {
860        self.as_bytes()
861    }
862}
863
864impl<const CAP: usize> Clone for EncodedBuffer<CAP> {
865    /// Clones the visible encoded bytes into a second stack-backed buffer.
866    ///
867    /// Security note: cloning duplicates the visible bytes in memory. Both the
868    /// original and the clone must be dropped or explicitly cleared before the
869    /// duplicated bytes are gone on the crate's best-effort cleanup path. The
870    /// compiler may also create temporary stack copies while performing the
871    /// copy; those intermediates are outside this crate's cleanup boundary.
872    /// Avoid cloning encoded secret material; use `SecretBuffer` when redacted
873    /// formatting and heap-owned secret handling are required.
874    fn clone(&self) -> Self {
875        let mut output = Self::new();
876        output.bytes[..self.len].copy_from_slice(self.as_bytes());
877        output.len = self.len;
878        output
879    }
880}
881
882impl<const CAP: usize> core::fmt::Debug for EncodedBuffer<CAP> {
883    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
884        formatter
885            .debug_struct("EncodedBuffer")
886            .field("bytes", &"<redacted>")
887            .field("len", &self.len)
888            .field("capacity", &CAP)
889            .finish()
890    }
891}
892
893impl<const CAP: usize> core::fmt::Display for EncodedBuffer<CAP> {
894    /// Writes the full Base64 text.
895    ///
896    /// Security note: this is intentionally not redacted. Do not use
897    /// `EncodedBuffer` for encoded secrets that may reach logs or error
898    /// messages; use `SecretBuffer` for redacted formatting.
899    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
900        formatter.write_str(self.as_str())
901    }
902}
903
904impl<const CAP: usize> Default for EncodedBuffer<CAP> {
905    fn default() -> Self {
906        Self::new()
907    }
908}
909
910impl<const CAP: usize> Drop for EncodedBuffer<CAP> {
911    fn drop(&mut self) {
912        self.clear();
913    }
914}
915
916impl<const CAP: usize> TryFrom<&[u8]> for EncodedBuffer<CAP> {
917    type Error = EncodeError;
918
919    /// Encodes bytes into strict standard padded Base64 in a stack-backed
920    /// buffer.
921    ///
922    /// Use [`Engine::encode_buffer`] or [`Profile::encode_buffer`] when a
923    /// different alphabet, padding mode, or line-wrapping profile is required.
924    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
925        STANDARD.encode_buffer(input)
926    }
927}
928
929impl<const CAP: usize, const N: usize> TryFrom<&[u8; N]> for EncodedBuffer<CAP> {
930    type Error = EncodeError;
931
932    /// Encodes a byte array into strict standard padded Base64 in a
933    /// stack-backed buffer.
934    ///
935    /// Use [`Engine::encode_buffer`] or [`Profile::encode_buffer`] when a
936    /// different alphabet, padding mode, or line-wrapping profile is required.
937    fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
938        Self::try_from(&input[..])
939    }
940}
941
942impl<const CAP: usize> TryFrom<&str> for EncodedBuffer<CAP> {
943    type Error = EncodeError;
944
945    /// Encodes UTF-8 text bytes into strict standard padded Base64 in a
946    /// stack-backed buffer.
947    ///
948    /// This treats the string as raw input bytes. Use
949    /// [`Engine::encode_buffer`] or [`Profile::encode_buffer`] when a
950    /// different alphabet, padding mode, or line-wrapping profile is required.
951    fn try_from(input: &str) -> Result<Self, Self::Error> {
952        Self::try_from(input.as_bytes())
953    }
954}
955
956/// Stack-backed decoded Base64 output.
957///
958/// This type is intended for short decoded values where heap allocation would
959/// be unnecessary but manually sizing and passing a separate output slice is
960/// noisy. Decoded data may be binary or secret-bearing, so formatting is
961/// redacted and contents are exposed only through explicit byte accessors.
962///
963/// The backing array is cleared when the value is dropped. This is best-effort
964/// data-retention reduction and is not a formal zeroization guarantee.
965///
966/// On `wasm32` targets, the wipe barrier uses only a compiler fence. The wasm
967/// runtime JIT may still optimize or retain cleared bytes in ways this crate
968/// cannot control. `wasm32` builds fail closed by default; enable
969/// `allow-wasm32-best-effort-wipe` only when the deployment explicitly accepts
970/// this limitation and applies its own memory strategy around stack-backed
971/// buffers.
972pub struct DecodedBuffer<const CAP: usize> {
973    bytes: [u8; CAP],
974    len: usize,
975}
976
977/// Owned stack array extracted from [`DecodedBuffer`].
978///
979/// This wrapper keeps the extracted decoded bytes on the crate's best-effort
980/// drop-time cleanup path. Use
981/// [`Self::into_exposed_unprotected_array_caller_must_zeroize`] only when a
982/// bare array is unavoidable and the caller will handle cleanup.
983pub struct ExposedDecodedArray<const CAP: usize> {
984    bytes: [u8; CAP],
985    len: usize,
986}
987
988impl<const CAP: usize> ExposedDecodedArray<CAP> {
989    /// Wraps a decoded backing array and visible length.
990    ///
991    /// # Panics
992    ///
993    /// Panics if `len` is greater than `CAP`.
994    #[must_use]
995    pub const fn from_array(bytes: [u8; CAP], len: usize) -> Self {
996        assert!(len <= CAP, "visible length exceeds array capacity");
997        Self { bytes, len }
998    }
999
1000    /// Returns the visible decoded bytes.
1001    #[must_use]
1002    pub fn as_bytes(&self) -> &[u8] {
1003        &self.bytes[..self.len]
1004    }
1005
1006    /// Returns the number of visible decoded bytes.
1007    #[must_use]
1008    pub const fn len(&self) -> usize {
1009        self.len
1010    }
1011
1012    /// Returns whether there are no visible decoded bytes.
1013    #[must_use]
1014    pub const fn is_empty(&self) -> bool {
1015        self.len == 0
1016    }
1017
1018    /// Returns the backing array capacity.
1019    #[must_use]
1020    pub const fn capacity(&self) -> usize {
1021        CAP
1022    }
1023
1024    /// Consumes the wrapper and returns a bare array plus visible length.
1025    ///
1026    /// This is an unprotected escape hatch. The returned array will not be
1027    /// cleared by this crate on drop. Callers must clear it with their own
1028    /// approved zeroization policy.
1029    ///
1030    /// # Security
1031    ///
1032    /// Treat this as a cleanup-boundary API. Failing to clear the returned
1033    /// array leaves decoded bytes, which may be secret-bearing, in ordinary
1034    /// caller-owned memory until overwritten by later stack or heap activity.
1035    #[must_use = "caller must zeroize the returned array"]
1036    pub fn into_exposed_unprotected_array_caller_must_zeroize(mut self) -> ([u8; CAP], usize) {
1037        let len = self.len;
1038        self.len = 0;
1039        (core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
1040    }
1041}
1042
1043impl<const CAP: usize> Drop for ExposedDecodedArray<CAP> {
1044    fn drop(&mut self) {
1045        wipe_bytes(&mut self.bytes);
1046        self.len = 0;
1047    }
1048}
1049
1050impl<const CAP: usize> core::fmt::Debug for ExposedDecodedArray<CAP> {
1051    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1052        formatter
1053            .debug_struct("ExposedDecodedArray")
1054            .field("bytes", &"<redacted>")
1055            .field("len", &self.len)
1056            .field("capacity", &CAP)
1057            .finish()
1058    }
1059}
1060
1061impl<const CAP: usize> DecodedBuffer<CAP> {
1062    /// Creates an empty decoded buffer.
1063    #[must_use]
1064    pub const fn new() -> Self {
1065        Self {
1066            bytes: [0u8; CAP],
1067            len: 0,
1068        }
1069    }
1070
1071    /// Returns the number of visible decoded bytes.
1072    #[must_use]
1073    pub const fn len(&self) -> usize {
1074        self.len
1075    }
1076
1077    /// Returns whether the buffer has no visible decoded bytes.
1078    #[must_use]
1079    pub const fn is_empty(&self) -> bool {
1080        self.len == 0
1081    }
1082
1083    /// Returns whether the visible decoded bytes fill the stack backing array.
1084    #[must_use]
1085    pub const fn is_full(&self) -> bool {
1086        self.len == CAP
1087    }
1088
1089    /// Returns the stack capacity in bytes.
1090    #[must_use]
1091    pub const fn capacity(&self) -> usize {
1092        CAP
1093    }
1094
1095    /// Returns the number of unused bytes in the stack backing array.
1096    #[must_use]
1097    pub const fn remaining_capacity(&self) -> usize {
1098        CAP - self.len
1099    }
1100
1101    /// Returns the visible decoded bytes.
1102    #[must_use]
1103    pub fn as_bytes(&self) -> &[u8] {
1104        &self.bytes[..self.len]
1105    }
1106
1107    /// Returns the visible decoded bytes as UTF-8 text.
1108    ///
1109    /// Decoded Base64 output is arbitrary bytes, so this method is fallible.
1110    /// Use [`Self::as_bytes`] when the decoded payload is binary or when text
1111    /// validation belongs to a higher protocol layer.
1112    pub fn as_utf8(&self) -> Result<&str, core::str::Utf8Error> {
1113        core::str::from_utf8(self.as_bytes())
1114    }
1115
1116    /// Compares this decoded output to `other` without short-circuiting on the
1117    /// first differing byte.
1118    ///
1119    /// Length and the final equality result remain public. Different lengths
1120    /// return `false` immediately; use this helper only when the compared
1121    /// lengths are public protocol facts or have been normalized by the
1122    /// caller. For equal-length inputs, this helper scans every byte before
1123    /// returning. It is constant-time-oriented best effort, not a formal
1124    /// cryptographic constant-time guarantee. This comparison is deliberately
1125    /// explicit: redacted buffer types do not implement [`PartialEq`] because
1126    /// `==` would make a best-effort helper look like a formal token/MAC
1127    /// comparison primitive.
1128    ///
1129    /// Do not use this helper as the sole MAC, bearer-token, password-hash, or
1130    /// authentication-secret comparison primitive in high-assurance systems.
1131    /// Applications that can admit dependencies should use a reviewed
1132    /// constant-time comparison primitive, such as `subtle`, at the protocol
1133    /// boundary.
1134    #[doc(alias = "constant_time_eq")]
1135    #[must_use]
1136    pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
1137        constant_time_eq_public_len(self.as_bytes(), other)
1138    }
1139
1140    /// Consumes the wrapper and returns the backing array plus visible length
1141    /// inside a drop-wiping exposed wrapper.
1142    ///
1143    /// This is an explicit escape hatch for no-alloc interop with APIs that
1144    /// require ownership of a fixed array. The returned
1145    /// [`ExposedDecodedArray`] remains redacted by formatting and clears its
1146    /// backing array on drop.
1147    #[must_use]
1148    pub fn into_exposed_array(mut self) -> ExposedDecodedArray<CAP> {
1149        let len = self.len;
1150        self.len = 0;
1151        ExposedDecodedArray::from_array(core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
1152    }
1153
1154    /// Clears the visible bytes and the full backing array.
1155    pub fn clear(&mut self) {
1156        wipe_bytes(&mut self.bytes);
1157        self.len = 0;
1158    }
1159
1160    /// Clears bytes after the visible prefix.
1161    pub fn clear_tail(&mut self) {
1162        wipe_tail(&mut self.bytes, self.len);
1163    }
1164}
1165
1166impl<const CAP: usize> AsRef<[u8]> for DecodedBuffer<CAP> {
1167    fn as_ref(&self) -> &[u8] {
1168        self.as_bytes()
1169    }
1170}
1171
1172impl<const CAP: usize> Clone for DecodedBuffer<CAP> {
1173    /// Clones the visible decoded bytes into a second stack-backed buffer.
1174    ///
1175    /// Security note: cloning duplicates decoded bytes in memory. Both the
1176    /// original and the clone must be dropped or explicitly cleared before the
1177    /// duplicated bytes are gone on the crate's best-effort cleanup path. The
1178    /// compiler may also create temporary stack copies while performing the
1179    /// copy; those intermediates are outside this crate's cleanup boundary. For
1180    /// high-assurance applications, avoid cloning decoded key material and use
1181    /// `SecretBuffer` for heap-owned secrets without a `Clone` implementation.
1182    fn clone(&self) -> Self {
1183        let mut output = Self::new();
1184        output.bytes[..self.len].copy_from_slice(self.as_bytes());
1185        output.len = self.len;
1186        output
1187    }
1188}
1189
1190impl<const CAP: usize> core::fmt::Debug for DecodedBuffer<CAP> {
1191    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1192        formatter
1193            .debug_struct("DecodedBuffer")
1194            .field("bytes", &"<redacted>")
1195            .field("len", &self.len)
1196            .field("capacity", &CAP)
1197            .finish()
1198    }
1199}
1200
1201impl<const CAP: usize> Default for DecodedBuffer<CAP> {
1202    fn default() -> Self {
1203        Self::new()
1204    }
1205}
1206
1207impl<const CAP: usize> Drop for DecodedBuffer<CAP> {
1208    fn drop(&mut self) {
1209        self.clear();
1210    }
1211}
1212
1213impl<const CAP: usize> TryFrom<&[u8]> for DecodedBuffer<CAP> {
1214    type Error = DecodeError;
1215
1216    /// Decodes strict standard padded Base64 into a stack-backed buffer.
1217    ///
1218    /// Use [`Engine::decode_buffer`] or [`Profile::decode_buffer`] when a
1219    /// different alphabet, padding mode, or line-wrapping profile is required.
1220    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
1221        STANDARD.decode_buffer(input)
1222    }
1223}
1224
1225impl<const CAP: usize, const N: usize> TryFrom<&[u8; N]> for DecodedBuffer<CAP> {
1226    type Error = DecodeError;
1227
1228    /// Decodes a strict standard padded Base64 byte array into a stack-backed
1229    /// buffer.
1230    ///
1231    /// Use [`Engine::decode_buffer`] or [`Profile::decode_buffer`] when a
1232    /// different alphabet, padding mode, or line-wrapping profile is required.
1233    fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
1234        Self::try_from(&input[..])
1235    }
1236}
1237
1238impl<const CAP: usize> TryFrom<&str> for DecodedBuffer<CAP> {
1239    type Error = DecodeError;
1240
1241    /// Decodes strict standard padded Base64 text into a stack-backed buffer.
1242    ///
1243    /// Use [`Engine::decode_buffer`] or [`Profile::decode_buffer`] when a
1244    /// different alphabet, padding mode, or line-wrapping profile is required.
1245    fn try_from(input: &str) -> Result<Self, Self::Error> {
1246        Self::try_from(input.as_bytes())
1247    }
1248}
1249
1250impl<const CAP: usize> core::str::FromStr for DecodedBuffer<CAP> {
1251    type Err = DecodeError;
1252
1253    /// Decodes strict standard padded Base64 text into a stack-backed buffer.
1254    ///
1255    /// Use [`Engine::decode_buffer`] or [`Profile::decode_buffer`] when a
1256    /// different alphabet, padding mode, or line-wrapping profile is required.
1257    fn from_str(input: &str) -> Result<Self, Self::Err> {
1258        Self::try_from(input)
1259    }
1260}
1261
1262/// Owned sensitive bytes with redacted formatting and drop-time cleanup.
1263///
1264/// `SecretBuffer` is available with the `alloc` feature. It is intended for
1265/// decoded keys, tokens, and other values that should not be accidentally
1266/// logged. The buffer exposes contents only through explicit reveal methods.
1267///
1268/// Spare vector capacity is cleared when wrapping owned bytes. On drop,
1269/// initialized bytes and vector spare capacity are cleared with the crate's
1270/// internal best-effort wipe helpers. This is data-retention reduction, not a
1271/// formal zeroization guarantee, and it cannot make claims about allocator
1272/// behavior or historical copies outside the wrapper.
1273///
1274/// # Platform Memory Controls
1275///
1276/// `SecretBuffer` does not lock its allocation into physical memory. The OS
1277/// may page its contents to disk, include them in hibernation images, or expose
1278/// them through crash dumps. High-assurance deployments must combine
1279/// `SecretBuffer` with platform memory-locking where available, encrypted or
1280/// disabled swap, crash-dump suppression, and allocator isolation appropriate
1281/// for their environment.
1282///
1283/// On `wasm32` targets, the wipe barrier uses only a compiler fence. The wasm
1284/// runtime JIT may still optimize or retain cleared bytes in ways this crate
1285/// cannot control. `wasm32` builds fail closed by default; enable
1286/// `allow-wasm32-best-effort-wipe` only when the deployment explicitly accepts
1287/// this limitation and applies its own memory strategy around owned secret
1288/// buffers.
1289#[cfg(feature = "alloc")]
1290pub struct SecretBuffer {
1291    bytes: alloc::vec::Vec<u8>,
1292}
1293
1294/// Owned secret bytes extracted from [`SecretBuffer`].
1295///
1296/// This wrapper keeps redacted formatting, best-effort spare-capacity clearing
1297/// at construction time, and best-effort full wipe on drop after a
1298/// [`SecretBuffer`] is consumed for owned interop. Use
1299/// [`Self::into_exposed_unprotected_vec_caller_must_zeroize`] only when a raw
1300/// `Vec<u8>` is unavoidable and the caller will handle cleanup.
1301#[cfg(feature = "alloc")]
1302pub struct ExposedSecretVec {
1303    bytes: alloc::vec::Vec<u8>,
1304}
1305
1306#[cfg(feature = "alloc")]
1307impl ExposedSecretVec {
1308    /// Wraps an owned vector as exposed secret material.
1309    #[must_use]
1310    pub fn from_vec(mut bytes: alloc::vec::Vec<u8>) -> Self {
1311        wipe_vec_spare_capacity(&mut bytes);
1312        Self { bytes }
1313    }
1314
1315    /// Returns the number of initialized secret bytes.
1316    #[must_use]
1317    pub fn len(&self) -> usize {
1318        self.bytes.len()
1319    }
1320
1321    /// Returns whether the buffer contains no initialized secret bytes.
1322    #[must_use]
1323    pub fn is_empty(&self) -> bool {
1324        self.bytes.is_empty()
1325    }
1326
1327    /// Reveals the secret bytes.
1328    ///
1329    /// This method is intentionally named to make secret access explicit at the
1330    /// call site.
1331    #[must_use]
1332    pub fn expose_secret(&self) -> &[u8] {
1333        &self.bytes
1334    }
1335
1336    /// Reveals the secret bytes mutably.
1337    ///
1338    /// This method is intentionally named to make secret access explicit at the
1339    /// call site.
1340    #[must_use]
1341    pub fn expose_secret_mut(&mut self) -> &mut [u8] {
1342        &mut self.bytes
1343    }
1344
1345    /// Consumes the wrapper and returns a raw `Vec<u8>`.
1346    ///
1347    /// This is an unprotected escape hatch. The returned vector is no longer
1348    /// redacted by formatting and will not be cleared by this crate on drop.
1349    /// Callers must clear it with their own approved zeroization policy.
1350    #[must_use = "caller must zeroize the returned Vec"]
1351    pub fn into_exposed_unprotected_vec_caller_must_zeroize(mut self) -> alloc::vec::Vec<u8> {
1352        core::mem::take(&mut self.bytes)
1353    }
1354}
1355
1356#[cfg(feature = "alloc")]
1357impl core::fmt::Debug for ExposedSecretVec {
1358    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1359        formatter
1360            .debug_struct("ExposedSecretVec")
1361            .field("bytes", &"<redacted>")
1362            .field("len", &self.len())
1363            .finish()
1364    }
1365}
1366
1367#[cfg(feature = "alloc")]
1368impl core::fmt::Display for ExposedSecretVec {
1369    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1370        formatter.write_str("<redacted>")
1371    }
1372}
1373
1374#[cfg(feature = "alloc")]
1375impl Drop for ExposedSecretVec {
1376    fn drop(&mut self) {
1377        wipe_vec_all(&mut self.bytes);
1378    }
1379}
1380
1381#[cfg(feature = "alloc")]
1382struct WipeVecGuard {
1383    bytes: alloc::vec::Vec<u8>,
1384}
1385
1386#[cfg(feature = "alloc")]
1387impl WipeVecGuard {
1388    fn from_vec(bytes: alloc::vec::Vec<u8>) -> Self {
1389        Self { bytes }
1390    }
1391
1392    fn into_validated_secret_string(mut self) -> alloc::string::String {
1393        wipe_vec_spare_capacity(&mut self.bytes);
1394        let bytes = core::mem::take(&mut self.bytes);
1395        core::mem::forget(self);
1396        string_from_validated_secret_bytes(bytes)
1397    }
1398}
1399
1400#[cfg(feature = "alloc")]
1401impl Drop for WipeVecGuard {
1402    fn drop(&mut self) {
1403        wipe_vec_all(&mut self.bytes);
1404    }
1405}
1406
1407#[cfg(feature = "alloc")]
1408impl AsRef<[u8]> for ExposedSecretVec {
1409    fn as_ref(&self) -> &[u8] {
1410        self.expose_secret()
1411    }
1412}
1413
1414#[cfg(feature = "alloc")]
1415impl AsMut<[u8]> for ExposedSecretVec {
1416    fn as_mut(&mut self) -> &mut [u8] {
1417        self.expose_secret_mut()
1418    }
1419}
1420
1421/// Owned secret UTF-8 text extracted from [`SecretBuffer`].
1422///
1423/// This wrapper keeps redacted formatting, best-effort spare-capacity clearing
1424/// at construction time, and best-effort full wipe on drop after a
1425/// [`SecretBuffer`] is consumed for string interop. Use
1426/// [`Self::into_exposed_unprotected_string_caller_must_zeroize`] only when a
1427/// raw `String` is unavoidable and the caller will handle cleanup.
1428#[cfg(feature = "alloc")]
1429pub struct ExposedSecretString {
1430    text: alloc::string::String,
1431}
1432
1433#[cfg(feature = "alloc")]
1434impl ExposedSecretString {
1435    /// Wraps an owned UTF-8 string as exposed secret text.
1436    #[must_use]
1437    pub fn from_string(text: alloc::string::String) -> Self {
1438        let mut bytes = text.into_bytes();
1439        wipe_vec_spare_capacity(&mut bytes);
1440        let text = string_from_validated_secret_bytes(bytes);
1441        Self { text }
1442    }
1443
1444    /// Returns the length of the secret text in bytes.
1445    #[must_use]
1446    pub fn len(&self) -> usize {
1447        self.text.len()
1448    }
1449
1450    /// Returns whether the secret text is empty.
1451    #[must_use]
1452    pub fn is_empty(&self) -> bool {
1453        self.text.is_empty()
1454    }
1455
1456    /// Reveals the secret text.
1457    ///
1458    /// This method is intentionally named to make secret access explicit at
1459    /// the call site.
1460    #[must_use]
1461    pub fn expose_secret(&self) -> &str {
1462        &self.text
1463    }
1464
1465    /// Reveals the secret text as bytes.
1466    ///
1467    /// This method is intentionally named to make secret access explicit at
1468    /// the call site.
1469    #[must_use]
1470    pub fn expose_secret_bytes(&self) -> &[u8] {
1471        self.text.as_bytes()
1472    }
1473
1474    /// Consumes the wrapper and returns a raw `String`.
1475    ///
1476    /// This is an unprotected escape hatch. The returned string is no longer
1477    /// redacted by formatting and will not be cleared by this crate on drop.
1478    /// Callers must clear it with their own approved zeroization policy.
1479    #[must_use = "caller must zeroize the returned String"]
1480    pub fn into_exposed_unprotected_string_caller_must_zeroize(mut self) -> alloc::string::String {
1481        core::mem::take(&mut self.text)
1482    }
1483}
1484
1485#[cfg(feature = "alloc")]
1486impl core::fmt::Debug for ExposedSecretString {
1487    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1488        formatter
1489            .debug_struct("ExposedSecretString")
1490            .field("text", &"<redacted>")
1491            .field("len", &self.len())
1492            .finish()
1493    }
1494}
1495
1496#[cfg(feature = "alloc")]
1497impl core::fmt::Display for ExposedSecretString {
1498    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1499        formatter.write_str("<redacted>")
1500    }
1501}
1502
1503#[cfg(feature = "alloc")]
1504impl Drop for ExposedSecretString {
1505    fn drop(&mut self) {
1506        let mut bytes = core::mem::take(&mut self.text).into_bytes();
1507        wipe_vec_all(&mut bytes);
1508    }
1509}
1510
1511#[cfg(feature = "alloc")]
1512impl AsRef<str> for ExposedSecretString {
1513    fn as_ref(&self) -> &str {
1514        self.expose_secret()
1515    }
1516}
1517
1518#[cfg(feature = "alloc")]
1519impl SecretBuffer {
1520    /// Wraps an existing vector as sensitive material.
1521    #[must_use]
1522    pub fn from_vec(mut bytes: alloc::vec::Vec<u8>) -> Self {
1523        wipe_vec_spare_capacity(&mut bytes);
1524        Self { bytes }
1525    }
1526
1527    /// Copies a slice into an owned sensitive buffer.
1528    #[must_use]
1529    pub fn from_slice(bytes: &[u8]) -> Self {
1530        Self::from_vec(bytes.to_vec())
1531    }
1532
1533    /// Returns the number of initialized secret bytes.
1534    #[must_use]
1535    pub fn len(&self) -> usize {
1536        self.bytes.len()
1537    }
1538
1539    /// Returns whether the buffer contains no initialized secret bytes.
1540    #[must_use]
1541    pub fn is_empty(&self) -> bool {
1542        self.bytes.is_empty()
1543    }
1544
1545    /// Reveals the secret bytes.
1546    ///
1547    /// This method is intentionally named to make secret access explicit at the
1548    /// call site.
1549    #[must_use]
1550    pub fn expose_secret(&self) -> &[u8] {
1551        &self.bytes
1552    }
1553
1554    /// Reveals the secret bytes as UTF-8 text.
1555    ///
1556    /// This method is intentionally named to make secret access explicit at the
1557    /// call site. Secret material may be arbitrary binary data, so this method
1558    /// is fallible.
1559    pub fn expose_secret_utf8(&self) -> Result<&str, core::str::Utf8Error> {
1560        core::str::from_utf8(self.expose_secret())
1561    }
1562
1563    /// Reveals the secret bytes mutably.
1564    ///
1565    /// This method is intentionally named to make secret access explicit at the
1566    /// call site.
1567    #[must_use]
1568    pub fn expose_secret_mut(&mut self) -> &mut [u8] {
1569        &mut self.bytes
1570    }
1571
1572    /// Consumes the wrapper and returns owned secret bytes.
1573    ///
1574    /// This is an explicit escape hatch for interop with APIs that require an
1575    /// owned vector-like value. The returned [`ExposedSecretVec`] remains
1576    /// redacted by formatting and clears its vector on drop.
1577    #[must_use]
1578    pub fn into_exposed_vec(mut self) -> ExposedSecretVec {
1579        ExposedSecretVec::from_vec(core::mem::take(&mut self.bytes))
1580    }
1581
1582    /// Consumes the wrapper and returns the owned secret bytes as UTF-8 text.
1583    ///
1584    /// This is an explicit escape hatch for interop with APIs that require an
1585    /// owned string-like value. The returned [`ExposedSecretString`] remains
1586    /// redacted by formatting and clears its heap allocation on drop.
1587    ///
1588    /// If the secret bytes are not valid UTF-8, the original redacted wrapper
1589    /// is returned unchanged.
1590    #[must_use = "handle invalid UTF-8 errors and keep the returned wrapper protected"]
1591    pub fn try_into_exposed_string(self) -> Result<ExposedSecretString, Self> {
1592        if core::str::from_utf8(self.expose_secret()).is_err() {
1593            return Err(self);
1594        }
1595
1596        // Keep the bytes behind a wiping guard until the final infallible
1597        // ownership transfer into `String`.
1598        let mut exposed = self.into_exposed_vec();
1599        let guard = WipeVecGuard::from_vec(core::mem::take(&mut exposed.bytes));
1600        drop(exposed);
1601        Ok(ExposedSecretString::from_string(
1602            guard.into_validated_secret_string(),
1603        ))
1604    }
1605
1606    /// Compares this secret to `other` without short-circuiting on the first
1607    /// differing byte.
1608    ///
1609    /// Length and the final equality result remain public. Different lengths
1610    /// return `false` immediately; use this helper only when the compared
1611    /// lengths are public protocol facts or have been normalized by the
1612    /// caller. For equal-length inputs, this helper scans every byte before
1613    /// returning. It is constant-time-oriented best effort, not a formal
1614    /// cryptographic constant-time guarantee. This comparison is deliberately
1615    /// explicit: redacted buffer types do not implement [`PartialEq`] because
1616    /// `==` would make a best-effort helper look like a formal token/MAC
1617    /// comparison primitive.
1618    ///
1619    /// Do not use this helper as the sole MAC, bearer-token, password-hash, or
1620    /// authentication-secret comparison primitive in high-assurance systems.
1621    /// Applications that can admit dependencies should use a reviewed
1622    /// constant-time comparison primitive, such as `subtle`, at the protocol
1623    /// boundary.
1624    #[doc(alias = "constant_time_eq")]
1625    #[must_use]
1626    pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
1627        constant_time_eq_public_len(self.expose_secret(), other)
1628    }
1629
1630    /// Clears the initialized bytes and makes the buffer empty.
1631    pub fn clear(&mut self) {
1632        wipe_vec_all(&mut self.bytes);
1633        self.bytes.clear();
1634    }
1635}
1636
1637#[cfg(feature = "alloc")]
1638impl core::fmt::Debug for SecretBuffer {
1639    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1640        formatter
1641            .debug_struct("SecretBuffer")
1642            .field("bytes", &"<redacted>")
1643            .field("len", &self.len())
1644            .finish()
1645    }
1646}
1647
1648#[cfg(feature = "alloc")]
1649impl core::fmt::Display for SecretBuffer {
1650    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1651        formatter.write_str("<redacted>")
1652    }
1653}
1654
1655#[cfg(feature = "alloc")]
1656impl Drop for SecretBuffer {
1657    fn drop(&mut self) {
1658        wipe_vec_all(&mut self.bytes);
1659    }
1660}
1661
1662#[cfg(feature = "alloc")]
1663impl From<alloc::vec::Vec<u8>> for SecretBuffer {
1664    /// Wraps an owned vector as sensitive material.
1665    ///
1666    /// Spare capacity is cleared immediately before the vector is stored.
1667    /// Use [`SecretBuffer::from_slice`] when the source data is borrowed.
1668    fn from(bytes: alloc::vec::Vec<u8>) -> Self {
1669        Self::from_vec(bytes)
1670    }
1671}
1672
1673#[cfg(feature = "alloc")]
1674impl From<alloc::string::String> for SecretBuffer {
1675    /// Wraps an owned UTF-8 string as sensitive material.
1676    ///
1677    /// The string is consumed without copying its initialized bytes. Spare
1678    /// vector capacity is cleared immediately before the bytes are stored.
1679    fn from(text: alloc::string::String) -> Self {
1680        Self::from_vec(text.into_bytes())
1681    }
1682}
1683
1684#[cfg(feature = "alloc")]
1685impl<const CAP: usize> From<EncodedBuffer<CAP>> for SecretBuffer {
1686    /// Copies visible encoded bytes from a stack-backed buffer into an owned
1687    /// redacted buffer.
1688    ///
1689    /// The consumed stack-backed buffer clears its backing array when it is
1690    /// dropped at the end of the conversion.
1691    fn from(buffer: EncodedBuffer<CAP>) -> Self {
1692        Self::from_slice(buffer.as_bytes())
1693    }
1694}
1695
1696#[cfg(feature = "alloc")]
1697impl<const CAP: usize> From<DecodedBuffer<CAP>> for SecretBuffer {
1698    /// Copies visible decoded bytes from a stack-backed buffer into an owned
1699    /// redacted buffer.
1700    ///
1701    /// The consumed stack-backed buffer clears its backing array when it is
1702    /// dropped at the end of the conversion.
1703    fn from(buffer: DecodedBuffer<CAP>) -> Self {
1704        Self::from_slice(buffer.as_bytes())
1705    }
1706}
1707
1708#[cfg(feature = "alloc")]
1709impl TryFrom<&[u8]> for SecretBuffer {
1710    type Error = DecodeError;
1711
1712    /// Decodes strict standard padded Base64 into a redacted owned buffer.
1713    ///
1714    /// Use [`Engine::decode_secret`] or [`Profile::decode_secret`] when a
1715    /// different alphabet, padding mode, or line-wrapping profile is required.
1716    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
1717        STANDARD.decode_secret(input)
1718    }
1719}
1720
1721#[cfg(feature = "alloc")]
1722impl<const N: usize> TryFrom<&[u8; N]> for SecretBuffer {
1723    type Error = DecodeError;
1724
1725    /// Decodes a strict standard padded Base64 byte array into a redacted
1726    /// owned buffer.
1727    ///
1728    /// Use [`Engine::decode_secret`] or [`Profile::decode_secret`] when a
1729    /// different alphabet, padding mode, or line-wrapping profile is required.
1730    fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
1731        Self::try_from(&input[..])
1732    }
1733}
1734
1735#[cfg(feature = "alloc")]
1736impl TryFrom<&str> for SecretBuffer {
1737    type Error = DecodeError;
1738
1739    /// Decodes strict standard padded Base64 text into a redacted owned buffer.
1740    ///
1741    /// Use [`Engine::decode_secret`] or [`Profile::decode_secret`] when a
1742    /// different alphabet, padding mode, or line-wrapping profile is required.
1743    fn try_from(input: &str) -> Result<Self, Self::Error> {
1744        Self::try_from(input.as_bytes())
1745    }
1746}
1747
1748#[cfg(feature = "alloc")]
1749impl core::str::FromStr for SecretBuffer {
1750    type Err = DecodeError;
1751
1752    /// Decodes strict standard padded Base64 text into a redacted owned buffer.
1753    ///
1754    /// Use [`Engine::decode_secret`] or [`Profile::decode_secret`] when a
1755    /// different alphabet, padding mode, or line-wrapping profile is required.
1756    fn from_str(input: &str) -> Result<Self, Self::Err> {
1757        Self::try_from(input)
1758    }
1759}
1760
1761/// Returns the encoded length for an input length and padding policy.
1762///
1763/// This function returns [`EncodeError::LengthOverflow`] instead of panicking.
1764/// Use [`checked_encoded_len`] when an `Option<usize>` is more convenient.
1765///
1766/// # Examples
1767///
1768/// ```
1769/// use base64_ng::encoded_len;
1770///
1771/// assert_eq!(encoded_len(5, true).unwrap(), 8);
1772/// assert_eq!(encoded_len(5, false).unwrap(), 7);
1773/// assert!(encoded_len(usize::MAX, true).is_err());
1774/// ```
1775pub const fn encoded_len(input_len: usize, padded: bool) -> Result<usize, EncodeError> {
1776    match checked_encoded_len(input_len, padded) {
1777        Some(len) => Ok(len),
1778        None => Err(EncodeError::LengthOverflow),
1779    }
1780}
1781
1782/// Returns the encoded length after applying a line wrapping policy.
1783///
1784/// The returned length includes inserted line endings but does not include a
1785/// trailing line ending after the final encoded line.
1786///
1787/// # Examples
1788///
1789/// ```
1790/// use base64_ng::{LineEnding, LineWrap, wrapped_encoded_len};
1791///
1792/// let wrap = LineWrap::new(4, LineEnding::Lf);
1793/// assert_eq!(wrapped_encoded_len(5, true, wrap).unwrap(), 9);
1794/// ```
1795pub const fn wrapped_encoded_len(
1796    input_len: usize,
1797    padded: bool,
1798    wrap: LineWrap,
1799) -> Result<usize, EncodeError> {
1800    if wrap.line_len == 0 {
1801        return Err(EncodeError::InvalidLineWrap { line_len: 0 });
1802    }
1803
1804    let Some(encoded) = checked_encoded_len(input_len, padded) else {
1805        return Err(EncodeError::LengthOverflow);
1806    };
1807    if encoded == 0 {
1808        return Ok(0);
1809    }
1810
1811    let breaks = (encoded - 1) / wrap.line_len;
1812    let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
1813        return Err(EncodeError::LengthOverflow);
1814    };
1815    match encoded.checked_add(line_ending_bytes) {
1816        Some(len) => Ok(len),
1817        None => Err(EncodeError::LengthOverflow),
1818    }
1819}
1820
1821/// Returns the encoded length after line wrapping, or `None` on overflow or
1822/// invalid line wrapping.
1823///
1824/// The returned length includes inserted line endings but does not include a
1825/// trailing line ending after the final encoded line.
1826///
1827/// # Examples
1828///
1829/// ```
1830/// use base64_ng::{LineEnding, LineWrap, checked_wrapped_encoded_len};
1831///
1832/// let wrap = LineWrap::new(4, LineEnding::Lf);
1833/// assert_eq!(checked_wrapped_encoded_len(5, true, wrap), Some(9));
1834/// assert_eq!(LineWrap::checked_new(0, LineEnding::Lf), None);
1835/// ```
1836#[must_use]
1837pub const fn checked_wrapped_encoded_len(
1838    input_len: usize,
1839    padded: bool,
1840    wrap: LineWrap,
1841) -> Option<usize> {
1842    if wrap.line_len == 0 {
1843        return None;
1844    }
1845
1846    let Some(encoded) = checked_encoded_len(input_len, padded) else {
1847        return None;
1848    };
1849    if encoded == 0 {
1850        return Some(0);
1851    }
1852
1853    let breaks = (encoded - 1) / wrap.line_len;
1854    let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
1855        return None;
1856    };
1857    encoded.checked_add(line_ending_bytes)
1858}
1859
1860/// Returns the encoded length, or `None` if it would overflow `usize`.
1861///
1862/// # Examples
1863///
1864/// ```
1865/// use base64_ng::checked_encoded_len;
1866///
1867/// assert_eq!(checked_encoded_len(5, true), Some(8));
1868/// assert_eq!(checked_encoded_len(usize::MAX, true), None);
1869/// ```
1870#[must_use]
1871pub const fn checked_encoded_len(input_len: usize, padded: bool) -> Option<usize> {
1872    let groups = input_len / 3;
1873    if groups > usize::MAX / 4 {
1874        return None;
1875    }
1876    let full = groups * 4;
1877    let rem = input_len % 3;
1878    if rem == 0 {
1879        Some(full)
1880    } else if padded {
1881        full.checked_add(4)
1882    } else {
1883        full.checked_add(rem + 1)
1884    }
1885}
1886
1887/// Compares two fixed-width byte arrays without a length-mismatch branch.
1888///
1889/// Use this helper when the value length itself should not be represented as a
1890/// timing-distinct branch in the comparison API. The array length `N` is a
1891/// compile-time public type fact, and the helper scans exactly `N` bytes before
1892/// returning. The final equality result remains public. This is still a
1893/// dependency-free, constant-time-oriented best-effort helper, not a formally
1894/// verified cryptographic comparison primitive.
1895///
1896/// # Examples
1897///
1898/// ```
1899/// use base64_ng::constant_time_eq_fixed_width;
1900///
1901/// assert!(constant_time_eq_fixed_width(b"token", b"token"));
1902/// assert!(!constant_time_eq_fixed_width(b"token", b"Token"));
1903/// ```
1904#[must_use]
1905pub fn constant_time_eq_fixed_width<const N: usize>(left: &[u8; N], right: &[u8; N]) -> bool {
1906    constant_time_eq_fixed_width_array(left, right)
1907}
1908
1909/// Returns the maximum decoded length for an encoded input length.
1910///
1911/// # Examples
1912///
1913/// ```
1914/// use base64_ng::decoded_capacity;
1915///
1916/// assert_eq!(decoded_capacity(8), 6);
1917/// assert_eq!(decoded_capacity(7), 5);
1918/// ```
1919#[must_use]
1920pub const fn decoded_capacity(encoded_len: usize) -> usize {
1921    let rem = encoded_len % 4;
1922    encoded_len / 4 * 3
1923        + if rem == 2 {
1924            1
1925        } else if rem == 3 {
1926            2
1927        } else {
1928            0
1929        }
1930}
1931
1932/// Returns the exact decoded length implied by input length and padding.
1933///
1934/// This validates padding placement and impossible lengths, but it does not
1935/// validate alphabet membership or non-canonical trailing bits.
1936///
1937/// # Examples
1938///
1939/// ```
1940/// use base64_ng::decoded_len;
1941///
1942/// assert_eq!(decoded_len(b"aGVsbG8=", true).unwrap(), 5);
1943/// assert_eq!(decoded_len(b"aGVsbG8", false).unwrap(), 5);
1944/// ```
1945pub fn decoded_len(input: &[u8], padded: bool) -> Result<usize, DecodeError> {
1946    if padded {
1947        decoded_len_padded(input)
1948    } else {
1949        decoded_len_unpadded(input)
1950    }
1951}
1952
1953#[inline]
1954pub(crate) const fn ct_mask_bit(bit: u8) -> u8 {
1955    0u8.wrapping_sub(bit & 1)
1956}
1957
1958#[inline]
1959pub(crate) const fn ct_mask_nonzero_u8(value: u8) -> u8 {
1960    let wide = value as u16;
1961    let negative = 0u16.wrapping_sub(wide);
1962    let nonzero = ((wide | negative) >> 8) as u8;
1963    ct_mask_bit(nonzero)
1964}
1965
1966#[inline]
1967pub(crate) const fn ct_mask_eq_u8(left: u8, right: u8) -> u8 {
1968    !ct_mask_nonzero_u8(left ^ right)
1969}
1970
1971#[inline]
1972pub(crate) const fn ct_mask_lt_u8(left: u8, right: u8) -> u8 {
1973    let diff = (left as u16).wrapping_sub(right as u16);
1974    ct_mask_bit((diff >> 8) as u8)
1975}
1976
1977#[inline(never)]
1978fn constant_time_eq_public_len(left: &[u8], right: &[u8]) -> bool {
1979    if left.len() != right.len() {
1980        return false;
1981    }
1982
1983    constant_time_eq_same_len(left, right)
1984}
1985
1986#[inline(never)]
1987fn constant_time_eq_fixed_width_array<const N: usize>(left: &[u8; N], right: &[u8; N]) -> bool {
1988    constant_time_eq_same_len(left, right)
1989}
1990
1991#[inline(never)]
1992#[allow(unsafe_code)]
1993fn constant_time_eq_same_len(left: &[u8], right: &[u8]) -> bool {
1994    let mut diff = 0u8;
1995    for (left, right) in left.iter().zip(right) {
1996        diff = core::hint::black_box(
1997            core::hint::black_box(diff) | core::hint::black_box(*left ^ *right),
1998        );
1999        // SAFETY: `diff` is an initialized local `u8`; the volatile read is a
2000        // dependency-free optimizer barrier for the accumulation value and does
2001        // not access caller memory.
2002        diff = unsafe { core::ptr::read_volatile(&raw const diff) };
2003    }
2004    ct_error_gate_barrier(diff, 0);
2005    // SAFETY: `diff` is an initialized local `u8`; this final volatile read
2006    // keeps the public equality comparison dependent on a post-barrier load of
2007    // the accumulated value.
2008    let result = unsafe { core::ptr::read_volatile(&raw const diff) };
2009    result == 0
2010}
2011
2012#[cfg(feature = "alloc")]
2013fn string_from_validated_secret_bytes(bytes: alloc::vec::Vec<u8>) -> alloc::string::String {
2014    match alloc::string::String::from_utf8(bytes) {
2015        Ok(string) => string,
2016        Err(error) => {
2017            let mut bytes = error.into_bytes();
2018            wipe_vec_all(&mut bytes);
2019            alloc::string::String::new()
2020        }
2021    }
2022}
2023
2024mod backend {
2025    use super::{
2026        Alphabet, DecodeError, EncodeError, checked_encoded_len, decode_padded, decode_unpadded,
2027        encode_base64_value_runtime,
2028    };
2029
2030    pub(super) fn encode_slice<A, const PAD: bool>(
2031        input: &[u8],
2032        output: &mut [u8],
2033    ) -> Result<usize, EncodeError>
2034    where
2035        A: Alphabet,
2036    {
2037        #[cfg(feature = "simd")]
2038        match super::simd::active_backend() {
2039            super::simd::ActiveBackend::Scalar => {}
2040        }
2041
2042        scalar_encode_slice::<A, PAD>(input, output)
2043    }
2044
2045    pub(super) fn decode_slice<A, const PAD: bool>(
2046        input: &[u8],
2047        output: &mut [u8],
2048    ) -> Result<usize, DecodeError>
2049    where
2050        A: Alphabet,
2051    {
2052        #[cfg(feature = "simd")]
2053        match super::simd::active_backend() {
2054            super::simd::ActiveBackend::Scalar => {}
2055        }
2056
2057        scalar_decode_slice::<A, PAD>(input, output)
2058    }
2059
2060    #[cfg(test)]
2061    pub(super) fn scalar_reference_encode_slice<A, const PAD: bool>(
2062        input: &[u8],
2063        output: &mut [u8],
2064    ) -> Result<usize, EncodeError>
2065    where
2066        A: Alphabet,
2067    {
2068        scalar_encode_slice::<A, PAD>(input, output)
2069    }
2070
2071    #[cfg(test)]
2072    pub(super) fn scalar_reference_decode_slice<A, const PAD: bool>(
2073        input: &[u8],
2074        output: &mut [u8],
2075    ) -> Result<usize, DecodeError>
2076    where
2077        A: Alphabet,
2078    {
2079        scalar_decode_slice::<A, PAD>(input, output)
2080    }
2081
2082    fn scalar_encode_slice<A, const PAD: bool>(
2083        input: &[u8],
2084        output: &mut [u8],
2085    ) -> Result<usize, EncodeError>
2086    where
2087        A: Alphabet,
2088    {
2089        let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
2090        if output.len() < required {
2091            return Err(EncodeError::OutputTooSmall {
2092                required,
2093                available: output.len(),
2094            });
2095        }
2096
2097        let mut read = 0;
2098        let mut write = 0;
2099        while read + 3 <= input.len() {
2100            let b0 = input[read];
2101            let b1 = input[read + 1];
2102            let b2 = input[read + 2];
2103
2104            output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2105            output[write + 1] =
2106                encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2107            output[write + 2] =
2108                encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
2109            output[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
2110
2111            read += 3;
2112            write += 4;
2113        }
2114
2115        match input.len() - read {
2116            0 => {}
2117            1 => {
2118                let b0 = input[read];
2119                output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2120                output[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
2121                write += 2;
2122                if PAD {
2123                    output[write] = b'=';
2124                    output[write + 1] = b'=';
2125                    write += 2;
2126                }
2127            }
2128            2 => {
2129                let b0 = input[read];
2130                let b1 = input[read + 1];
2131                output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2132                output[write + 1] =
2133                    encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2134                output[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
2135                write += 3;
2136                if PAD {
2137                    output[write] = b'=';
2138                    write += 1;
2139                }
2140            }
2141            _ => unreachable!(),
2142        }
2143
2144        Ok(write)
2145    }
2146
2147    fn scalar_decode_slice<A, const PAD: bool>(
2148        input: &[u8],
2149        output: &mut [u8],
2150    ) -> Result<usize, DecodeError>
2151    where
2152        A: Alphabet,
2153    {
2154        if input.is_empty() {
2155            return Ok(0);
2156        }
2157
2158        if PAD {
2159            decode_padded::<A>(input, output)
2160        } else {
2161            decode_unpadded::<A>(input, output)
2162        }
2163    }
2164}
2165
2166/// A zero-sized Base64 engine parameterized by alphabet and padding policy.
2167pub struct Engine<A, const PAD: bool> {
2168    alphabet: core::marker::PhantomData<A>,
2169}
2170
2171impl<A, const PAD: bool> Clone for Engine<A, PAD> {
2172    fn clone(&self) -> Self {
2173        *self
2174    }
2175}
2176
2177impl<A, const PAD: bool> Copy for Engine<A, PAD> {}
2178
2179impl<A, const PAD: bool> core::fmt::Debug for Engine<A, PAD> {
2180    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2181        formatter
2182            .debug_struct("Engine")
2183            .field("padded", &PAD)
2184            .finish()
2185    }
2186}
2187
2188impl<A, const PAD: bool> core::fmt::Display for Engine<A, PAD> {
2189    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2190        write!(formatter, "padded={PAD}")
2191    }
2192}
2193
2194impl<A, const PAD: bool> Default for Engine<A, PAD> {
2195    fn default() -> Self {
2196        Self {
2197            alphabet: core::marker::PhantomData,
2198        }
2199    }
2200}
2201
2202impl<A, const PAD: bool> Eq for Engine<A, PAD> {}
2203
2204impl<A, const PAD: bool> PartialEq for Engine<A, PAD> {
2205    fn eq(&self, _other: &Self) -> bool {
2206        true
2207    }
2208}
2209
2210impl<A, const PAD: bool> Engine<A, PAD>
2211where
2212    A: Alphabet,
2213{
2214    /// Creates a new engine value.
2215    #[must_use]
2216    pub const fn new() -> Self {
2217        Self {
2218            alphabet: core::marker::PhantomData,
2219        }
2220    }
2221
2222    /// Returns whether this engine uses padded Base64.
2223    #[must_use]
2224    pub const fn is_padded(&self) -> bool {
2225        PAD
2226    }
2227
2228    /// Returns this engine as an unwrapped profile.
2229    ///
2230    /// Use [`Profile::new`] or [`Profile::checked_new`] when a strict
2231    /// line-wrapping policy should travel with the profile.
2232    #[must_use]
2233    pub const fn profile(&self) -> Profile<A, PAD> {
2234        Profile::new(*self, None)
2235    }
2236
2237    /// Returns the matching constant-time-oriented decoder for this engine's
2238    /// alphabet and padding policy.
2239    ///
2240    /// The returned decoder is still an explicit opt-in to the [`ct`] module's
2241    /// slower, opaque-error, constant-time-oriented scalar path.
2242    #[must_use]
2243    pub const fn ct_decoder(&self) -> ct::CtEngine<A, PAD> {
2244        ct::CtEngine::new()
2245    }
2246
2247    /// Wraps a `std::io::Write` value in a streaming Base64 encoder.
2248    ///
2249    /// This is a convenience constructor for [`stream::Encoder::new`] that
2250    /// keeps the selected engine attached to the call site.
2251    ///
2252    /// ```
2253    /// use std::io::Write;
2254    /// use base64_ng::STANDARD;
2255    ///
2256    /// let mut encoder = STANDARD.encoder_writer(Vec::new());
2257    /// encoder.write_all(b"hello").unwrap();
2258    /// assert_eq!(encoder.finish().unwrap(), b"aGVsbG8=");
2259    /// ```
2260    #[cfg(feature = "stream")]
2261    #[must_use]
2262    pub fn encoder_writer<W>(&self, inner: W) -> stream::Encoder<W, A, PAD> {
2263        stream::Encoder::new(inner, *self)
2264    }
2265
2266    /// Wraps a `std::io::Write` value in a streaming Base64 decoder.
2267    ///
2268    /// This is a convenience constructor for [`stream::Decoder::new`] that
2269    /// keeps the selected engine attached to the call site.
2270    ///
2271    /// ```
2272    /// use std::io::Write;
2273    /// use base64_ng::STANDARD;
2274    ///
2275    /// let mut decoder = STANDARD.decoder_writer(Vec::new());
2276    /// decoder.write_all(b"aGVsbG8=").unwrap();
2277    /// assert_eq!(decoder.finish().unwrap(), b"hello");
2278    /// ```
2279    ///
2280    /// # Security
2281    ///
2282    /// Streaming decoders use the normal strict decode path, not the
2283    /// [`crate::ct`] module. Do not use this adapter for secret-bearing
2284    /// payloads when malformed-input timing matters.
2285    #[cfg(feature = "stream")]
2286    #[must_use]
2287    pub fn decoder_writer<W>(&self, inner: W) -> stream::Decoder<W, A, PAD> {
2288        stream::Decoder::new(inner, *self)
2289    }
2290
2291    /// Wraps a `std::io::Read` value in a streaming Base64 encoder.
2292    ///
2293    /// This is a convenience constructor for [`stream::EncoderReader::new`]
2294    /// that keeps the selected engine attached to the call site.
2295    ///
2296    /// ```
2297    /// use std::io::Read;
2298    /// use base64_ng::STANDARD;
2299    ///
2300    /// let mut reader = STANDARD.encoder_reader(&b"hello"[..]);
2301    /// let mut encoded = String::new();
2302    /// reader.read_to_string(&mut encoded).unwrap();
2303    /// assert_eq!(encoded, "aGVsbG8=");
2304    /// ```
2305    #[cfg(feature = "stream")]
2306    #[must_use]
2307    pub fn encoder_reader<R>(&self, inner: R) -> stream::EncoderReader<R, A, PAD> {
2308        stream::EncoderReader::new(inner, *self)
2309    }
2310
2311    /// Wraps a `std::io::Read` value in a streaming Base64 decoder.
2312    ///
2313    /// This is a convenience constructor for [`stream::DecoderReader::new`]
2314    /// that keeps the selected engine attached to the call site.
2315    ///
2316    /// ```
2317    /// use std::io::Read;
2318    /// use base64_ng::STANDARD;
2319    ///
2320    /// let mut reader = STANDARD.decoder_reader(&b"aGVsbG8="[..]);
2321    /// let mut decoded = Vec::new();
2322    /// reader.read_to_end(&mut decoded).unwrap();
2323    /// assert_eq!(decoded, b"hello");
2324    /// ```
2325    ///
2326    /// # Security
2327    ///
2328    /// Streaming decoder readers use the normal strict decode path, not the
2329    /// [`crate::ct`] module. Do not use this adapter for secret-bearing
2330    /// payloads when malformed-input timing matters.
2331    #[cfg(feature = "stream")]
2332    #[must_use]
2333    pub fn decoder_reader<R>(&self, inner: R) -> stream::DecoderReader<R, A, PAD> {
2334        stream::DecoderReader::new(inner, *self)
2335    }
2336
2337    /// Returns the encoded length for this engine's padding policy.
2338    pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
2339        encoded_len(input_len, PAD)
2340    }
2341
2342    /// Returns the encoded length for this engine, or `None` on overflow.
2343    #[must_use]
2344    pub const fn checked_encoded_len(&self, input_len: usize) -> Option<usize> {
2345        checked_encoded_len(input_len, PAD)
2346    }
2347
2348    /// Returns the encoded length after applying a line wrapping policy.
2349    ///
2350    /// The returned length includes inserted line endings but does not include
2351    /// a trailing line ending after the final encoded line.
2352    pub const fn wrapped_encoded_len(
2353        &self,
2354        input_len: usize,
2355        wrap: LineWrap,
2356    ) -> Result<usize, EncodeError> {
2357        wrapped_encoded_len(input_len, PAD, wrap)
2358    }
2359
2360    /// Returns the encoded length after line wrapping, or `None` on overflow or
2361    /// invalid line wrapping.
2362    #[must_use]
2363    pub const fn checked_wrapped_encoded_len(
2364        &self,
2365        input_len: usize,
2366        wrap: LineWrap,
2367    ) -> Option<usize> {
2368        checked_wrapped_encoded_len(input_len, PAD, wrap)
2369    }
2370
2371    /// Returns the exact decoded length implied by input length and padding.
2372    ///
2373    /// This validates padding placement and impossible lengths, but it does not
2374    /// validate alphabet membership or non-canonical trailing bits.
2375    pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
2376        decoded_len(input, PAD)
2377    }
2378
2379    /// Returns the exact decoded length for the explicit legacy profile.
2380    ///
2381    /// The legacy profile ignores ASCII space, tab, carriage return, and line
2382    /// feed bytes before applying the same alphabet, padding, and canonical-bit
2383    /// checks as strict decoding.
2384    pub fn decoded_len_legacy(&self, input: &[u8]) -> Result<usize, DecodeError> {
2385        validate_legacy_decode::<A, PAD>(input)
2386    }
2387
2388    /// Returns the exact decoded length for a line-wrapped profile.
2389    ///
2390    /// The wrapped profile accepts only the configured line ending. Non-final
2391    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
2392    /// may be shorter. A single trailing line ending after the final line is
2393    /// accepted.
2394    pub fn decoded_len_wrapped(&self, input: &[u8], wrap: LineWrap) -> Result<usize, DecodeError> {
2395        validate_wrapped_decode::<A, PAD>(input, wrap)
2396    }
2397
2398    /// Validates strict Base64 input without writing decoded bytes.
2399    ///
2400    /// This applies the same alphabet, padding, and canonical-bit checks as
2401    /// [`Self::decode_slice`]. Use this method when malformed-input
2402    /// diagnostics matter; use [`Self::validate`] when a boolean is enough.
2403    /// This default validator is not constant-time; use
2404    /// [`crate::ct::CtEngine::validate_result`] through [`Self::ct_decoder`]
2405    /// for secret-bearing payloads where timing posture matters.
2406    ///
2407    /// # Examples
2408    ///
2409    /// ```
2410    /// use base64_ng::STANDARD;
2411    ///
2412    /// STANDARD.validate_result(b"aGVsbG8=").unwrap();
2413    /// assert!(STANDARD.validate_result(b"aGVsbG8").is_err());
2414    /// ```
2415    pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
2416        validate_decode::<A, PAD>(input).map(|_| ())
2417    }
2418
2419    /// Returns whether `input` is valid strict Base64 for this engine.
2420    ///
2421    /// This is a convenience wrapper around [`Self::validate_result`] and is
2422    /// not constant-time. Use [`crate::ct::CtEngine::validate`] through
2423    /// [`Self::ct_decoder`] for secret-bearing payloads where timing posture
2424    /// matters.
2425    ///
2426    /// # Examples
2427    ///
2428    /// ```
2429    /// use base64_ng::URL_SAFE_NO_PAD;
2430    ///
2431    /// assert!(URL_SAFE_NO_PAD.validate(b"-_8"));
2432    /// assert!(!URL_SAFE_NO_PAD.validate(b"+/8"));
2433    /// ```
2434    #[must_use]
2435    pub fn validate(&self, input: &[u8]) -> bool {
2436        self.validate_result(input).is_ok()
2437    }
2438
2439    /// Validates input using the explicit legacy whitespace profile.
2440    ///
2441    /// ASCII space, tab, carriage return, and line feed bytes are ignored
2442    /// before applying the same alphabet, padding, and canonical-bit checks as
2443    /// strict decoding.
2444    ///
2445    /// # Examples
2446    ///
2447    /// ```
2448    /// use base64_ng::STANDARD;
2449    ///
2450    /// STANDARD.validate_legacy_result(b" aG\r\nVsbG8= ").unwrap();
2451    /// assert!(STANDARD.validate_legacy_result(b" aG-=").is_err());
2452    /// ```
2453    pub fn validate_legacy_result(&self, input: &[u8]) -> Result<(), DecodeError> {
2454        validate_legacy_decode::<A, PAD>(input).map(|_| ())
2455    }
2456
2457    /// Returns whether `input` is valid for the explicit legacy whitespace
2458    /// profile.
2459    ///
2460    /// This is a convenience wrapper around [`Self::validate_legacy_result`].
2461    ///
2462    /// # Examples
2463    ///
2464    /// ```
2465    /// use base64_ng::STANDARD;
2466    ///
2467    /// assert!(STANDARD.validate_legacy(b" aG\r\nVsbG8= "));
2468    /// assert!(!STANDARD.validate_legacy(b"aG-V"));
2469    /// ```
2470    #[must_use]
2471    pub fn validate_legacy(&self, input: &[u8]) -> bool {
2472        self.validate_legacy_result(input).is_ok()
2473    }
2474
2475    /// Validates input using a strict line-wrapped profile.
2476    ///
2477    /// This is stricter than [`Self::validate_legacy_result`]: it accepts only
2478    /// the configured line ending and enforces the configured line length for
2479    /// every non-final line.
2480    ///
2481    /// # Examples
2482    ///
2483    /// ```
2484    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
2485    ///
2486    /// let wrap = LineWrap::new(4, LineEnding::Lf);
2487    /// STANDARD.validate_wrapped_result(b"aGVs\nbG8=", wrap).unwrap();
2488    /// assert!(STANDARD.validate_wrapped_result(b"aG\nVsbG8=", wrap).is_err());
2489    /// ```
2490    pub fn validate_wrapped_result(&self, input: &[u8], wrap: LineWrap) -> Result<(), DecodeError> {
2491        validate_wrapped_decode::<A, PAD>(input, wrap).map(|_| ())
2492    }
2493
2494    /// Returns whether `input` is valid for a strict line-wrapped profile.
2495    ///
2496    /// This is a convenience wrapper around [`Self::validate_wrapped_result`].
2497    ///
2498    /// # Examples
2499    ///
2500    /// ```
2501    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
2502    ///
2503    /// let wrap = LineWrap::new(4, LineEnding::Lf);
2504    /// assert!(STANDARD.validate_wrapped(b"aGVs\nbG8=", wrap));
2505    /// assert!(!STANDARD.validate_wrapped(b"aG\nVsbG8=", wrap));
2506    /// ```
2507    #[must_use]
2508    pub fn validate_wrapped(&self, input: &[u8], wrap: LineWrap) -> bool {
2509        self.validate_wrapped_result(input, wrap).is_ok()
2510    }
2511
2512    /// Encodes a fixed-size input into a fixed-size output array in const contexts.
2513    ///
2514    /// Stable Rust does not yet allow this API to return an array whose length
2515    /// is computed from `INPUT_LEN` directly. Instead, the caller supplies the
2516    /// output length through the destination type and this function panics
2517    /// during const evaluation if the length is wrong.
2518    ///
2519    /// # Panics
2520    ///
2521    /// Panics if `OUTPUT_LEN` is not exactly the encoded length for `INPUT_LEN`
2522    /// and this engine's padding policy, or if that length overflows `usize`.
2523    ///
2524    /// # Examples
2525    ///
2526    /// ```
2527    /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
2528    ///
2529    /// const HELLO: [u8; 8] = STANDARD.encode_array(b"hello");
2530    /// const URL_SAFE: [u8; 3] = URL_SAFE_NO_PAD.encode_array(b"\xfb\xff");
2531    ///
2532    /// assert_eq!(&HELLO, b"aGVsbG8=");
2533    /// assert_eq!(&URL_SAFE, b"-_8");
2534    /// ```
2535    ///
2536    /// Incorrect output lengths fail during const evaluation:
2537    ///
2538    /// ```compile_fail
2539    /// use base64_ng::STANDARD;
2540    ///
2541    /// const TOO_SHORT: [u8; 7] = STANDARD.encode_array(b"hello");
2542    /// ```
2543    #[must_use]
2544    pub const fn encode_array<const INPUT_LEN: usize, const OUTPUT_LEN: usize>(
2545        &self,
2546        input: &[u8; INPUT_LEN],
2547    ) -> [u8; OUTPUT_LEN] {
2548        let Some(required) = checked_encoded_len(INPUT_LEN, PAD) else {
2549            panic!("encoded base64 length overflows usize");
2550        };
2551        assert!(
2552            required == OUTPUT_LEN,
2553            "base64 output array has incorrect length"
2554        );
2555
2556        let mut output = [0u8; OUTPUT_LEN];
2557        let mut read = 0;
2558        let mut write = 0;
2559        while INPUT_LEN - read >= 3 {
2560            let b0 = input[read];
2561            let b1 = input[read + 1];
2562            let b2 = input[read + 2];
2563
2564            output[write] = encode_base64_value::<A>(b0 >> 2);
2565            output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2566            output[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
2567            output[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
2568
2569            read += 3;
2570            write += 4;
2571        }
2572
2573        match INPUT_LEN - read {
2574            0 => {}
2575            1 => {
2576                let b0 = input[read];
2577                output[write] = encode_base64_value::<A>(b0 >> 2);
2578                output[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
2579                write += 2;
2580                if PAD {
2581                    output[write] = b'=';
2582                    output[write + 1] = b'=';
2583                }
2584            }
2585            2 => {
2586                let b0 = input[read];
2587                let b1 = input[read + 1];
2588                output[write] = encode_base64_value::<A>(b0 >> 2);
2589                output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2590                output[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
2591                if PAD {
2592                    output[write + 3] = b'=';
2593                }
2594            }
2595            _ => unreachable!(),
2596        }
2597
2598        output
2599    }
2600
2601    /// Encodes `input` into `output`, returning the number of bytes written.
2602    pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
2603        backend::encode_slice::<A, PAD>(input, output)
2604    }
2605
2606    /// Encodes `input` into `output` with line wrapping.
2607    ///
2608    /// The wrapping policy inserts line endings between encoded lines and does
2609    /// not append a trailing line ending after the final line.
2610    ///
2611    /// # Examples
2612    ///
2613    /// ```
2614    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
2615    ///
2616    /// let wrap = LineWrap::new(4, LineEnding::Lf);
2617    /// let mut output = [0u8; 9];
2618    /// let written = STANDARD
2619    ///     .encode_slice_wrapped(b"hello", &mut output, wrap)
2620    ///     .unwrap();
2621    ///
2622    /// assert_eq!(&output[..written], b"aGVs\nbG8=");
2623    /// ```
2624    pub fn encode_slice_wrapped(
2625        &self,
2626        input: &[u8],
2627        output: &mut [u8],
2628        wrap: LineWrap,
2629    ) -> Result<usize, EncodeError> {
2630        let required = self.wrapped_encoded_len(input.len(), wrap)?;
2631        if output.len() < required {
2632            return Err(EncodeError::OutputTooSmall {
2633                required,
2634                available: output.len(),
2635            });
2636        }
2637
2638        let encoded_len =
2639            checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
2640        if encoded_len == 0 {
2641            return Ok(0);
2642        }
2643
2644        // If the temporary in-buffer layout size overflows, fall back to the
2645        // fixed scratch buffer path rather than relying on saturated arithmetic.
2646        let combined_required = match required.checked_add(encoded_len) {
2647            Some(len) => len,
2648            None => usize::MAX,
2649        };
2650        if output.len() < combined_required {
2651            let mut scratch = [0u8; 1024];
2652            let mut input_offset = 0;
2653            let mut output_offset = 0;
2654            let mut column = 0;
2655
2656            while input_offset < input.len() {
2657                let remaining = input.len() - input_offset;
2658                let mut take = remaining.min(768);
2659                if remaining > take {
2660                    take -= take % 3;
2661                }
2662                if take == 0 {
2663                    take = remaining;
2664                }
2665
2666                let encoded = match self
2667                    .encode_slice(&input[input_offset..input_offset + take], &mut scratch)
2668                {
2669                    Ok(encoded) => encoded,
2670                    Err(err) => {
2671                        wipe_bytes(&mut scratch);
2672                        return Err(err);
2673                    }
2674                };
2675                if let Err(err) = write_wrapped_bytes(
2676                    &scratch[..encoded],
2677                    output,
2678                    &mut output_offset,
2679                    &mut column,
2680                    wrap,
2681                ) {
2682                    wipe_bytes(&mut scratch);
2683                    return Err(err);
2684                }
2685                wipe_bytes(&mut scratch[..encoded]);
2686                input_offset += take;
2687            }
2688
2689            Ok(output_offset)
2690        } else {
2691            let encoded =
2692                self.encode_slice(input, &mut output[required..required + encoded_len])?;
2693            let mut output_offset = 0;
2694            let mut column = 0;
2695            let mut read = required;
2696            while read < required + encoded {
2697                let byte = output[read];
2698                write_wrapped_byte(byte, output, &mut output_offset, &mut column, wrap)?;
2699                read += 1;
2700            }
2701            wipe_bytes(&mut output[required..required + encoded]);
2702            Ok(output_offset)
2703        }
2704    }
2705
2706    /// Encodes `input` with line wrapping and clears all bytes after the
2707    /// encoded prefix.
2708    ///
2709    /// If encoding fails, the entire output buffer is cleared before the error
2710    /// is returned.
2711    pub fn encode_slice_wrapped_clear_tail(
2712        &self,
2713        input: &[u8],
2714        output: &mut [u8],
2715        wrap: LineWrap,
2716    ) -> Result<usize, EncodeError> {
2717        let written = match self.encode_slice_wrapped(input, output, wrap) {
2718            Ok(written) => written,
2719            Err(err) => {
2720                wipe_bytes(output);
2721                return Err(err);
2722            }
2723        };
2724        wipe_tail(output, written);
2725        Ok(written)
2726    }
2727
2728    /// Encodes `input` with line wrapping into a stack-backed buffer.
2729    ///
2730    /// This is useful for MIME/PEM-style protocols where heap allocation is
2731    /// unnecessary. If encoding fails, the internal backing array is cleared
2732    /// before the error is returned.
2733    pub fn encode_wrapped_buffer<const CAP: usize>(
2734        &self,
2735        input: &[u8],
2736        wrap: LineWrap,
2737    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
2738        let mut output = EncodedBuffer::new();
2739        let written = match self.encode_slice_wrapped_clear_tail(input, &mut output.bytes, wrap) {
2740            Ok(written) => written,
2741            Err(err) => {
2742                output.clear();
2743                return Err(err);
2744            }
2745        };
2746        output.len = written;
2747        Ok(output)
2748    }
2749
2750    /// Encodes `input` with line wrapping into a newly allocated byte vector.
2751    #[cfg(feature = "alloc")]
2752    #[must_use = "for secret-bearing payloads use encode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
2753    pub fn encode_wrapped_vec(
2754        &self,
2755        input: &[u8],
2756        wrap: LineWrap,
2757    ) -> Result<alloc::vec::Vec<u8>, EncodeError> {
2758        let required = self.wrapped_encoded_len(input.len(), wrap)?;
2759        let mut output = alloc::vec![0; required];
2760        let written = self.encode_slice_wrapped(input, &mut output, wrap)?;
2761        output.truncate(written);
2762        Ok(output)
2763    }
2764
2765    /// Encodes `input` with line wrapping into a newly allocated UTF-8 string.
2766    #[cfg(feature = "alloc")]
2767    pub fn encode_wrapped_string(
2768        &self,
2769        input: &[u8],
2770        wrap: LineWrap,
2771    ) -> Result<alloc::string::String, EncodeError> {
2772        let output = self.encode_wrapped_vec(input, wrap)?;
2773        match alloc::string::String::from_utf8(output) {
2774            Ok(output) => Ok(output),
2775            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
2776        }
2777    }
2778
2779    /// Encodes `input` with line wrapping into a redacted owned secret buffer.
2780    ///
2781    /// This is useful when the wrapped encoded representation itself is
2782    /// sensitive and should not be accidentally logged through formatting.
2783    #[cfg(feature = "alloc")]
2784    pub fn encode_wrapped_secret(
2785        &self,
2786        input: &[u8],
2787        wrap: LineWrap,
2788    ) -> Result<SecretBuffer, EncodeError> {
2789        self.encode_wrapped_vec(input, wrap)
2790            .map(SecretBuffer::from_vec)
2791    }
2792
2793    /// Encodes `input` into `output` and clears all bytes after the encoded
2794    /// prefix.
2795    ///
2796    /// If encoding fails, the entire output buffer is cleared before the error
2797    /// is returned.
2798    ///
2799    /// # Examples
2800    ///
2801    /// ```
2802    /// use base64_ng::STANDARD;
2803    ///
2804    /// let mut output = [0xff; 12];
2805    /// let written = STANDARD
2806    ///     .encode_slice_clear_tail(b"hello", &mut output)
2807    ///     .unwrap();
2808    ///
2809    /// assert_eq!(&output[..written], b"aGVsbG8=");
2810    /// assert!(output[written..].iter().all(|byte| *byte == 0));
2811    /// ```
2812    pub fn encode_slice_clear_tail(
2813        &self,
2814        input: &[u8],
2815        output: &mut [u8],
2816    ) -> Result<usize, EncodeError> {
2817        let written = match self.encode_slice(input, output) {
2818            Ok(written) => written,
2819            Err(err) => {
2820                wipe_bytes(output);
2821                return Err(err);
2822            }
2823        };
2824        wipe_tail(output, written);
2825        Ok(written)
2826    }
2827
2828    /// Encodes `input` into a stack-backed buffer.
2829    ///
2830    /// This helper is useful for short values where callers want the
2831    /// convenience of an owned result without enabling `alloc`.
2832    ///
2833    /// # Examples
2834    ///
2835    /// ```
2836    /// use base64_ng::STANDARD;
2837    ///
2838    /// let encoded = STANDARD.encode_buffer::<8>(b"hello").unwrap();
2839    ///
2840    /// assert_eq!(encoded.as_str(), "aGVsbG8=");
2841    /// ```
2842    pub fn encode_buffer<const CAP: usize>(
2843        &self,
2844        input: &[u8],
2845    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
2846        let mut output = EncodedBuffer::new();
2847        let written = match self.encode_slice_clear_tail(input, &mut output.bytes) {
2848            Ok(written) => written,
2849            Err(err) => {
2850                output.clear();
2851                return Err(err);
2852            }
2853        };
2854        output.len = written;
2855        Ok(output)
2856    }
2857
2858    /// Encodes `input` into a newly allocated byte vector.
2859    #[cfg(feature = "alloc")]
2860    #[must_use = "for secret-bearing payloads use encode_secret, which returns a redacted buffer with drop-time cleanup"]
2861    pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
2862        let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
2863        let mut output = alloc::vec![0; required];
2864        let written = self.encode_slice(input, &mut output)?;
2865        output.truncate(written);
2866        Ok(output)
2867    }
2868
2869    /// Encodes `input` into a redacted owned secret buffer.
2870    ///
2871    /// This is useful when the encoded representation itself is sensitive and
2872    /// should not be accidentally logged through formatting.
2873    #[cfg(feature = "alloc")]
2874    pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
2875        self.encode_vec(input).map(SecretBuffer::from_vec)
2876    }
2877
2878    /// Encodes `input` into a newly allocated UTF-8 string.
2879    ///
2880    /// Base64 output is ASCII by construction. This helper is available with
2881    /// the `alloc` feature and has the same encoding semantics as
2882    /// [`Self::encode_slice`].
2883    ///
2884    /// # Examples
2885    ///
2886    /// ```
2887    /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
2888    ///
2889    /// assert_eq!(STANDARD.encode_string(b"hello").unwrap(), "aGVsbG8=");
2890    /// assert_eq!(URL_SAFE_NO_PAD.encode_string(b"\xfb\xff").unwrap(), "-_8");
2891    /// ```
2892    #[cfg(feature = "alloc")]
2893    pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
2894        let output = self.encode_vec(input)?;
2895        match alloc::string::String::from_utf8(output) {
2896            Ok(output) => Ok(output),
2897            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
2898        }
2899    }
2900
2901    /// Encodes the first `input_len` bytes of `buffer` in place.
2902    ///
2903    /// The buffer must have enough spare capacity for the encoded output. The
2904    /// implementation writes from right to left, so unread input bytes are not
2905    /// overwritten before they are encoded.
2906    ///
2907    /// # Examples
2908    ///
2909    /// ```
2910    /// use base64_ng::STANDARD;
2911    ///
2912    /// let mut buffer = [0u8; 8];
2913    /// buffer[..5].copy_from_slice(b"hello");
2914    /// let encoded = STANDARD.encode_in_place(&mut buffer, 5).unwrap();
2915    /// assert_eq!(encoded, b"aGVsbG8=");
2916    /// ```
2917    pub fn encode_in_place<'a>(
2918        &self,
2919        buffer: &'a mut [u8],
2920        input_len: usize,
2921    ) -> Result<&'a mut [u8], EncodeError> {
2922        if input_len > buffer.len() {
2923            return Err(EncodeError::InputTooLarge {
2924                input_len,
2925                buffer_len: buffer.len(),
2926            });
2927        }
2928
2929        let required = checked_encoded_len(input_len, PAD).ok_or(EncodeError::LengthOverflow)?;
2930        if buffer.len() < required {
2931            return Err(EncodeError::OutputTooSmall {
2932                required,
2933                available: buffer.len(),
2934            });
2935        }
2936
2937        let mut read = input_len;
2938        let mut write = required;
2939
2940        match input_len % 3 {
2941            0 => {}
2942            1 => {
2943                read -= 1;
2944                let b0 = buffer[read];
2945                if PAD {
2946                    write -= 4;
2947                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2948                    buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
2949                    buffer[write + 2] = b'=';
2950                    buffer[write + 3] = b'=';
2951                } else {
2952                    write -= 2;
2953                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2954                    buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
2955                }
2956            }
2957            2 => {
2958                read -= 2;
2959                let b0 = buffer[read];
2960                let b1 = buffer[read + 1];
2961                if PAD {
2962                    write -= 4;
2963                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2964                    buffer[write + 1] =
2965                        encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2966                    buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
2967                    buffer[write + 3] = b'=';
2968                } else {
2969                    write -= 3;
2970                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2971                    buffer[write + 1] =
2972                        encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2973                    buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
2974                }
2975            }
2976            _ => unreachable!(),
2977        }
2978
2979        while read > 0 {
2980            read -= 3;
2981            write -= 4;
2982            let b0 = buffer[read];
2983            let b1 = buffer[read + 1];
2984            let b2 = buffer[read + 2];
2985
2986            buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2987            buffer[write + 1] =
2988                encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2989            buffer[write + 2] =
2990                encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
2991            buffer[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
2992        }
2993
2994        // The right-to-left loop consumes exactly three input bytes for every
2995        // four output bytes. If this invariant changes, returning a shifted
2996        // slice would silently corrupt the in-place output.
2997        debug_assert_eq!(write, 0);
2998        Ok(&mut buffer[..required])
2999    }
3000
3001    /// Encodes the first `input_len` bytes of `buffer` in place and clears all
3002    /// bytes after the encoded prefix.
3003    ///
3004    /// If encoding fails because `input_len` is too large, the output buffer is
3005    /// too small, or the encoded length overflows `usize`, the entire buffer is
3006    /// cleared before the error is returned.
3007    ///
3008    /// # Examples
3009    ///
3010    /// ```
3011    /// use base64_ng::STANDARD;
3012    ///
3013    /// let mut buffer = [0xff; 12];
3014    /// buffer[..5].copy_from_slice(b"hello");
3015    /// let encoded = STANDARD.encode_in_place_clear_tail(&mut buffer, 5).unwrap();
3016    /// assert_eq!(encoded, b"aGVsbG8=");
3017    /// ```
3018    pub fn encode_in_place_clear_tail<'a>(
3019        &self,
3020        buffer: &'a mut [u8],
3021        input_len: usize,
3022    ) -> Result<&'a mut [u8], EncodeError> {
3023        let len = match self.encode_in_place(buffer, input_len) {
3024            Ok(encoded) => encoded.len(),
3025            Err(err) => {
3026                wipe_bytes(buffer);
3027                return Err(err);
3028            }
3029        };
3030        wipe_tail(buffer, len);
3031        Ok(&mut buffer[..len])
3032    }
3033
3034    /// Decodes `input` into `output`, returning the number of bytes written.
3035    ///
3036    /// This is strict decoding. Whitespace, mixed alphabets, malformed padding,
3037    /// and trailing non-padding data are rejected.
3038    ///
3039    /// # Security
3040    ///
3041    /// This default scalar decoder prioritizes strict validation, exact error
3042    /// reporting, and ordinary throughput. It may branch or return early based
3043    /// on byte validity, malformed input, padding position, and output
3044    /// capacity. It also reports exact failure positions and invalid byte
3045    /// values through [`DecodeError`]. Do not use this method for token
3046    /// comparison, key-material decoding, or secret-bearing validation where
3047    /// malformed-input timing matters. Use [`crate::ct`],
3048    /// [`crate::ct::STANDARD`], [`crate::ct::URL_SAFE_NO_PAD`], or
3049    /// [`Self::ct_decoder`] with `decode_slice_clear_tail` for
3050    /// constant-time-oriented secret decoding.
3051    #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
3052    pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
3053        backend::decode_slice::<A, PAD>(input, output)
3054    }
3055
3056    /// Decodes `input` into `output` and clears all bytes after the decoded
3057    /// prefix.
3058    ///
3059    /// If decoding fails, the entire output buffer is cleared before the error
3060    /// is returned.
3061    ///
3062    /// # Examples
3063    ///
3064    /// ```
3065    /// use base64_ng::STANDARD;
3066    ///
3067    /// let mut output = [0xff; 8];
3068    /// let written = STANDARD
3069    ///     .decode_slice_clear_tail(b"aGk=", &mut output)
3070    ///     .unwrap();
3071    ///
3072    /// assert_eq!(&output[..written], b"hi");
3073    /// assert!(output[written..].iter().all(|byte| *byte == 0));
3074    /// ```
3075    pub fn decode_slice_clear_tail(
3076        &self,
3077        input: &[u8],
3078        output: &mut [u8],
3079    ) -> Result<usize, DecodeError> {
3080        let written = match self.decode_slice(input, output) {
3081            Ok(written) => written,
3082            Err(err) => {
3083                wipe_bytes(output);
3084                return Err(err);
3085            }
3086        };
3087        wipe_tail(output, written);
3088        Ok(written)
3089    }
3090
3091    /// Decodes `input` into a stack-backed buffer.
3092    ///
3093    /// This helper is useful for short decoded values where callers want the
3094    /// convenience of an owned result without enabling `alloc`.
3095    ///
3096    /// # Examples
3097    ///
3098    /// ```
3099    /// use base64_ng::STANDARD;
3100    ///
3101    /// let decoded = STANDARD.decode_buffer::<5>(b"aGVsbG8=").unwrap();
3102    ///
3103    /// assert_eq!(decoded.as_bytes(), b"hello");
3104    /// ```
3105    pub fn decode_buffer<const CAP: usize>(
3106        &self,
3107        input: &[u8],
3108    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
3109        let mut output = DecodedBuffer::new();
3110        let written = match self.decode_slice_clear_tail(input, &mut output.bytes) {
3111            Ok(written) => written,
3112            Err(err) => {
3113                output.clear();
3114                return Err(err);
3115            }
3116        };
3117        output.len = written;
3118        Ok(output)
3119    }
3120
3121    /// Decodes `input` using the explicit legacy whitespace profile.
3122    ///
3123    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
3124    /// Alphabet selection, padding placement, trailing data after padding, and
3125    /// non-canonical trailing bits remain strict.
3126    ///
3127    /// # Security
3128    ///
3129    /// This method uses the normal strict decode path after legacy whitespace
3130    /// handling. It may branch or return early based on malformed input and is
3131    /// not a constant-time token validator or key-material decoder. Use
3132    /// [`crate::ct`] for secret-bearing payloads.
3133    #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
3134    pub fn decode_slice_legacy(
3135        &self,
3136        input: &[u8],
3137        output: &mut [u8],
3138    ) -> Result<usize, DecodeError> {
3139        let required = validate_legacy_decode::<A, PAD>(input)?;
3140        if output.len() < required {
3141            return Err(DecodeError::OutputTooSmall {
3142                required,
3143                available: output.len(),
3144            });
3145        }
3146        decode_legacy_to_slice::<A, PAD>(input, output)
3147    }
3148
3149    /// Decodes `input` using the explicit legacy whitespace profile and clears
3150    /// all bytes after the decoded prefix.
3151    ///
3152    /// If validation or decoding fails, the entire output buffer is cleared
3153    /// before the error is returned.
3154    ///
3155    /// # Examples
3156    ///
3157    /// ```
3158    /// use base64_ng::STANDARD;
3159    ///
3160    /// let mut output = [0xff; 8];
3161    /// let written = STANDARD
3162    ///     .decode_slice_legacy_clear_tail(b" aG\r\nk= ", &mut output)
3163    ///     .unwrap();
3164    ///
3165    /// assert_eq!(&output[..written], b"hi");
3166    /// assert!(output[written..].iter().all(|byte| *byte == 0));
3167    /// ```
3168    pub fn decode_slice_legacy_clear_tail(
3169        &self,
3170        input: &[u8],
3171        output: &mut [u8],
3172    ) -> Result<usize, DecodeError> {
3173        let written = match self.decode_slice_legacy(input, output) {
3174            Ok(written) => written,
3175            Err(err) => {
3176                wipe_bytes(output);
3177                return Err(err);
3178            }
3179        };
3180        wipe_tail(output, written);
3181        Ok(written)
3182    }
3183
3184    /// Decodes `input` into a stack-backed buffer using the explicit legacy
3185    /// whitespace profile.
3186    ///
3187    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
3188    /// Alphabet selection, padding placement, trailing data after padding, and
3189    /// non-canonical trailing bits remain strict. If decoding fails, the
3190    /// internal backing array is cleared before the error is returned.
3191    pub fn decode_buffer_legacy<const CAP: usize>(
3192        &self,
3193        input: &[u8],
3194    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
3195        let mut output = DecodedBuffer::new();
3196        let written = match self.decode_slice_legacy_clear_tail(input, &mut output.bytes) {
3197            Ok(written) => written,
3198            Err(err) => {
3199                output.clear();
3200                return Err(err);
3201            }
3202        };
3203        output.len = written;
3204        Ok(output)
3205    }
3206
3207    /// Decodes `input` using a strict line-wrapped profile.
3208    ///
3209    /// The wrapped profile accepts only the configured line ending. Non-final
3210    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
3211    /// may be shorter. A single trailing line ending after the final line is
3212    /// accepted.
3213    ///
3214    /// # Security
3215    ///
3216    /// This method uses the normal strict decode path after line-profile
3217    /// validation. It may branch or return early based on malformed input and
3218    /// is not a constant-time token validator or key-material decoder. Use
3219    /// [`crate::ct`] for secret-bearing payloads.
3220    #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
3221    pub fn decode_slice_wrapped(
3222        &self,
3223        input: &[u8],
3224        output: &mut [u8],
3225        wrap: LineWrap,
3226    ) -> Result<usize, DecodeError> {
3227        let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
3228        if output.len() < required {
3229            return Err(DecodeError::OutputTooSmall {
3230                required,
3231                available: output.len(),
3232            });
3233        }
3234        decode_wrapped_to_slice::<A, PAD>(input, output, wrap)
3235    }
3236
3237    /// Decodes `input` using a strict line-wrapped profile and clears all bytes
3238    /// after the decoded prefix.
3239    ///
3240    /// If validation or decoding fails, the entire output buffer is cleared
3241    /// before the error is returned.
3242    pub fn decode_slice_wrapped_clear_tail(
3243        &self,
3244        input: &[u8],
3245        output: &mut [u8],
3246        wrap: LineWrap,
3247    ) -> Result<usize, DecodeError> {
3248        let written = match self.decode_slice_wrapped(input, output, wrap) {
3249            Ok(written) => written,
3250            Err(err) => {
3251                wipe_bytes(output);
3252                return Err(err);
3253            }
3254        };
3255        wipe_tail(output, written);
3256        Ok(written)
3257    }
3258
3259    /// Decodes `input` using a strict line-wrapped profile into a stack-backed
3260    /// buffer.
3261    ///
3262    /// The wrapped profile accepts only the configured line ending. Non-final
3263    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
3264    /// may be shorter. A single trailing line ending after the final line is
3265    /// accepted. If decoding fails, the internal backing array is cleared
3266    /// before the error is returned.
3267    pub fn decode_wrapped_buffer<const CAP: usize>(
3268        &self,
3269        input: &[u8],
3270        wrap: LineWrap,
3271    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
3272        let mut output = DecodedBuffer::new();
3273        let written = match self.decode_slice_wrapped_clear_tail(input, &mut output.bytes, wrap) {
3274            Ok(written) => written,
3275            Err(err) => {
3276                output.clear();
3277                return Err(err);
3278            }
3279        };
3280        output.len = written;
3281        Ok(output)
3282    }
3283
3284    /// Decodes `input` into a newly allocated byte vector.
3285    ///
3286    /// This is strict decoding with the same semantics as [`Self::decode_slice`].
3287    #[cfg(feature = "alloc")]
3288    #[must_use = "for secret-bearing payloads use decode_secret, which returns a redacted buffer with drop-time cleanup"]
3289    pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
3290        let required = validate_decode::<A, PAD>(input)?;
3291        let mut output = alloc::vec![0; required];
3292        let written = match self.decode_slice(input, &mut output) {
3293            Ok(written) => written,
3294            Err(err) => {
3295                wipe_bytes(&mut output);
3296                return Err(err);
3297            }
3298        };
3299        output.truncate(written);
3300        Ok(output)
3301    }
3302
3303    /// Decodes `input` into a redacted owned secret buffer.
3304    ///
3305    /// On malformed input, the intermediate output buffer is cleared before the
3306    /// error is returned by [`Self::decode_vec`].
3307    #[cfg(feature = "alloc")]
3308    pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
3309        self.decode_vec(input).map(SecretBuffer::from_vec)
3310    }
3311
3312    /// Decodes `input` into a newly allocated byte vector using the explicit
3313    /// legacy whitespace profile.
3314    #[cfg(feature = "alloc")]
3315    #[must_use = "for secret-bearing payloads use decode_secret_legacy, which returns a redacted buffer with drop-time cleanup"]
3316    pub fn decode_vec_legacy(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
3317        let required = validate_legacy_decode::<A, PAD>(input)?;
3318        let mut output = alloc::vec![0; required];
3319        let written = match self.decode_slice_legacy(input, &mut output) {
3320            Ok(written) => written,
3321            Err(err) => {
3322                wipe_bytes(&mut output);
3323                return Err(err);
3324            }
3325        };
3326        output.truncate(written);
3327        Ok(output)
3328    }
3329
3330    /// Decodes `input` into a redacted owned secret buffer using the explicit
3331    /// legacy whitespace profile.
3332    ///
3333    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
3334    /// Alphabet selection, padding placement, trailing data after padding, and
3335    /// non-canonical trailing bits remain strict.
3336    #[cfg(feature = "alloc")]
3337    pub fn decode_secret_legacy(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
3338        self.decode_vec_legacy(input).map(SecretBuffer::from_vec)
3339    }
3340
3341    /// Decodes line-wrapped input into a newly allocated byte vector.
3342    #[cfg(feature = "alloc")]
3343    #[must_use = "for secret-bearing payloads use decode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
3344    pub fn decode_wrapped_vec(
3345        &self,
3346        input: &[u8],
3347        wrap: LineWrap,
3348    ) -> Result<alloc::vec::Vec<u8>, DecodeError> {
3349        let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
3350        let mut output = alloc::vec![0; required];
3351        let written = match self.decode_slice_wrapped(input, &mut output, wrap) {
3352            Ok(written) => written,
3353            Err(err) => {
3354                wipe_bytes(&mut output);
3355                return Err(err);
3356            }
3357        };
3358        output.truncate(written);
3359        Ok(output)
3360    }
3361
3362    /// Decodes line-wrapped input into a redacted owned secret buffer.
3363    ///
3364    /// The wrapped profile accepts only the configured line ending. Non-final
3365    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
3366    /// may be shorter. A single trailing line ending after the final line is
3367    /// accepted.
3368    #[cfg(feature = "alloc")]
3369    pub fn decode_wrapped_secret(
3370        &self,
3371        input: &[u8],
3372        wrap: LineWrap,
3373    ) -> Result<SecretBuffer, DecodeError> {
3374        self.decode_wrapped_vec(input, wrap)
3375            .map(SecretBuffer::from_vec)
3376    }
3377
3378    /// Decodes `buffer` in place using a strict line-wrapped profile.
3379    ///
3380    /// The wrapped profile accepts only the configured line ending. Non-final
3381    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
3382    /// may be shorter. A single trailing line ending after the final line is
3383    /// accepted.
3384    ///
3385    /// # Security
3386    ///
3387    /// This method compacts line endings in place before decoding. If
3388    /// validation or decoding fails, the buffer contents are unspecified and
3389    /// may contain a compacted encoded prefix. On success, bytes after the
3390    /// returned decoded prefix may retain the compacted encoded representation.
3391    /// Use
3392    /// [`Self::decode_in_place_wrapped_clear_tail`] when the buffer may be
3393    /// reused or freed without a caller-managed wipe; treat that clear-tail
3394    /// variant as the default for secret-bearing wrapped payloads.
3395    ///
3396    /// # Examples
3397    ///
3398    /// ```
3399    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
3400    ///
3401    /// let mut buffer = *b"aGVs\nbG8=";
3402    /// let decoded = STANDARD
3403    ///     .decode_in_place_wrapped(&mut buffer, LineWrap::new(4, LineEnding::Lf))
3404    ///     .unwrap();
3405    ///
3406    /// assert_eq!(decoded, b"hello");
3407    /// ```
3408    pub fn decode_in_place_wrapped<'a>(
3409        &self,
3410        buffer: &'a mut [u8],
3411        wrap: LineWrap,
3412    ) -> Result<&'a mut [u8], DecodeError> {
3413        let _required = validate_wrapped_decode::<A, PAD>(buffer, wrap)?;
3414        let compacted = compact_wrapped_input(buffer, wrap)?;
3415        let len = Self::decode_slice_to_start(&mut buffer[..compacted])?;
3416        Ok(&mut buffer[..len])
3417    }
3418
3419    /// Decodes `buffer` in place using a strict line-wrapped profile and clears
3420    /// all bytes after the decoded prefix.
3421    ///
3422    /// If validation or decoding fails, the entire buffer is cleared before the
3423    /// error is returned.
3424    ///
3425    /// # Examples
3426    ///
3427    /// ```
3428    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
3429    ///
3430    /// let mut buffer = *b"aGVs\nbG8=";
3431    /// let len = STANDARD
3432    ///     .decode_in_place_wrapped_clear_tail(&mut buffer, LineWrap::new(4, LineEnding::Lf))
3433    ///     .unwrap()
3434    ///     .len();
3435    ///
3436    /// assert_eq!(&buffer[..len], b"hello");
3437    /// assert!(buffer[len..].iter().all(|byte| *byte == 0));
3438    /// ```
3439    pub fn decode_in_place_wrapped_clear_tail<'a>(
3440        &self,
3441        buffer: &'a mut [u8],
3442        wrap: LineWrap,
3443    ) -> Result<&'a mut [u8], DecodeError> {
3444        if let Err(err) = validate_wrapped_decode::<A, PAD>(buffer, wrap) {
3445            wipe_bytes(buffer);
3446            return Err(err);
3447        }
3448
3449        let compacted = match compact_wrapped_input(buffer, wrap) {
3450            Ok(compacted) => compacted,
3451            Err(err) => {
3452                wipe_bytes(buffer);
3453                return Err(err);
3454            }
3455        };
3456
3457        let len = match Self::decode_slice_to_start(&mut buffer[..compacted]) {
3458            Ok(len) => len,
3459            Err(err) => {
3460                wipe_bytes(buffer);
3461                return Err(err);
3462            }
3463        };
3464        wipe_tail(buffer, len);
3465        Ok(&mut buffer[..len])
3466    }
3467
3468    /// Decodes the buffer in place and returns the decoded prefix.
3469    ///
3470    /// On success, bytes after the returned decoded prefix may retain encoded
3471    /// input bytes. Use [`Self::decode_in_place_clear_tail`] when the buffer
3472    /// may be reused or freed without a caller-managed wipe.
3473    ///
3474    /// # Security
3475    ///
3476    /// This default scalar decoder prioritizes strict validation, exact error
3477    /// reporting, and ordinary throughput. It may branch or return early based
3478    /// on malformed input and reports exact failure positions and invalid byte
3479    /// values through [`DecodeError`]. Do not use this method for token
3480    /// comparison, key-material decoding, or secret-bearing validation where
3481    /// malformed-input timing matters.
3482    ///
3483    /// # Examples
3484    ///
3485    /// ```
3486    /// use base64_ng::STANDARD_NO_PAD;
3487    ///
3488    /// let mut buffer = *b"Zm9vYmFy";
3489    /// let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
3490    /// assert_eq!(decoded, b"foobar");
3491    /// ```
3492    pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
3493        let len = Self::decode_slice_to_start(buffer)?;
3494        Ok(&mut buffer[..len])
3495    }
3496
3497    /// Decodes the buffer in place and clears all bytes after the decoded prefix.
3498    ///
3499    /// If decoding fails, the entire buffer is cleared before the error is
3500    /// returned. Use this variant when the encoded or partially decoded data is
3501    /// sensitive and the caller wants best-effort cleanup without adding a
3502    /// dependency.
3503    ///
3504    /// # Examples
3505    ///
3506    /// ```
3507    /// use base64_ng::STANDARD;
3508    ///
3509    /// let mut buffer = *b"aGk=";
3510    /// let decoded = STANDARD.decode_in_place_clear_tail(&mut buffer).unwrap();
3511    /// assert_eq!(decoded, b"hi");
3512    /// ```
3513    pub fn decode_in_place_clear_tail<'a>(
3514        &self,
3515        buffer: &'a mut [u8],
3516    ) -> Result<&'a mut [u8], DecodeError> {
3517        let len = match Self::decode_slice_to_start(buffer) {
3518            Ok(len) => len,
3519            Err(err) => {
3520                wipe_bytes(buffer);
3521                return Err(err);
3522            }
3523        };
3524        wipe_tail(buffer, len);
3525        Ok(&mut buffer[..len])
3526    }
3527
3528    /// Decodes `buffer` in place using the explicit legacy whitespace profile.
3529    ///
3530    /// Ignored whitespace is compacted out before decoding. If validation
3531    /// fails, the buffer contents are unspecified. On success, bytes after the
3532    /// returned decoded prefix may retain the compacted encoded
3533    /// representation. Use [`Self::decode_in_place_legacy_clear_tail`] when the
3534    /// buffer may be reused or freed without a caller-managed wipe.
3535    pub fn decode_in_place_legacy<'a>(
3536        &self,
3537        buffer: &'a mut [u8],
3538    ) -> Result<&'a mut [u8], DecodeError> {
3539        let _required = validate_legacy_decode::<A, PAD>(buffer)?;
3540        let mut write = 0;
3541        let mut read = 0;
3542        while read < buffer.len() {
3543            let byte = buffer[read];
3544            if !is_legacy_whitespace(byte) {
3545                buffer[write] = byte;
3546                write += 1;
3547            }
3548            read += 1;
3549        }
3550        let len = Self::decode_slice_to_start(&mut buffer[..write])?;
3551        Ok(&mut buffer[..len])
3552    }
3553
3554    /// Decodes `buffer` in place using the explicit legacy whitespace profile
3555    /// and clears all bytes after the decoded prefix.
3556    ///
3557    /// If validation or decoding fails, the entire buffer is cleared before the
3558    /// error is returned.
3559    pub fn decode_in_place_legacy_clear_tail<'a>(
3560        &self,
3561        buffer: &'a mut [u8],
3562    ) -> Result<&'a mut [u8], DecodeError> {
3563        if let Err(err) = validate_legacy_decode::<A, PAD>(buffer) {
3564            wipe_bytes(buffer);
3565            return Err(err);
3566        }
3567
3568        let mut write = 0;
3569        let mut read = 0;
3570        while read < buffer.len() {
3571            let byte = buffer[read];
3572            if !is_legacy_whitespace(byte) {
3573                buffer[write] = byte;
3574                write += 1;
3575            }
3576            read += 1;
3577        }
3578
3579        let len = match Self::decode_slice_to_start(&mut buffer[..write]) {
3580            Ok(len) => len,
3581            Err(err) => {
3582                wipe_bytes(buffer);
3583                return Err(err);
3584            }
3585        };
3586        wipe_tail(buffer, len);
3587        Ok(&mut buffer[..len])
3588    }
3589
3590    fn decode_slice_to_start(buffer: &mut [u8]) -> Result<usize, DecodeError> {
3591        let _required = validate_decode::<A, PAD>(buffer)?;
3592        let input_len = buffer.len();
3593        let mut read = 0;
3594        let mut write = 0;
3595        while read + 4 <= input_len {
3596            let chunk = read_quad(buffer, read)?;
3597            let available = buffer.len();
3598            let output_tail = buffer.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
3599                required: write,
3600                available,
3601            })?;
3602            let written = decode_chunk::<A, PAD>(chunk, output_tail)
3603                .map_err(|err| err.with_index_offset(read))?;
3604            read += 4;
3605            write += written;
3606            if written < 3 {
3607                if read != input_len {
3608                    return Err(DecodeError::InvalidPadding { index: read - 4 });
3609                }
3610                return Ok(write);
3611            }
3612        }
3613
3614        let rem = input_len - read;
3615        if rem == 0 {
3616            return Ok(write);
3617        }
3618        if PAD {
3619            return Err(DecodeError::InvalidLength);
3620        }
3621        let mut tail = [0u8; 3];
3622        tail[..rem].copy_from_slice(&buffer[read..input_len]);
3623        decode_tail_unpadded::<A>(&tail[..rem], &mut buffer[write..])
3624            .map_err(|err| err.with_index_offset(read))
3625            .map(|n| write + n)
3626    }
3627}
3628
3629fn write_wrapped_bytes(
3630    input: &[u8],
3631    output: &mut [u8],
3632    output_offset: &mut usize,
3633    column: &mut usize,
3634    wrap: LineWrap,
3635) -> Result<(), EncodeError> {
3636    for byte in input {
3637        write_wrapped_byte(*byte, output, output_offset, column, wrap)?;
3638    }
3639    Ok(())
3640}
3641
3642fn write_wrapped_byte(
3643    byte: u8,
3644    output: &mut [u8],
3645    output_offset: &mut usize,
3646    column: &mut usize,
3647    wrap: LineWrap,
3648) -> Result<(), EncodeError> {
3649    if *column == wrap.line_len {
3650        let line_ending = wrap.line_ending.as_bytes();
3651        let mut index = 0;
3652        while index < line_ending.len() {
3653            if *output_offset >= output.len() {
3654                return Err(EncodeError::OutputTooSmall {
3655                    required: *output_offset + 1,
3656                    available: output.len(),
3657                });
3658            }
3659            output[*output_offset] = line_ending[index];
3660            *output_offset += 1;
3661            index += 1;
3662        }
3663        *column = 0;
3664    }
3665
3666    if *output_offset >= output.len() {
3667        return Err(EncodeError::OutputTooSmall {
3668            required: *output_offset + 1,
3669            available: output.len(),
3670        });
3671    }
3672    output[*output_offset] = byte;
3673    *output_offset += 1;
3674    *column += 1;
3675    Ok(())
3676}
3677
3678/// Encoding error.
3679#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3680pub enum EncodeError {
3681    /// The encoded output length would overflow `usize`.
3682    LengthOverflow,
3683    /// The requested line wrapping policy is invalid.
3684    InvalidLineWrap {
3685        /// Requested line length.
3686        line_len: usize,
3687    },
3688    /// The caller-provided input length exceeds the provided buffer.
3689    InputTooLarge {
3690        /// Requested input bytes.
3691        input_len: usize,
3692        /// Available buffer bytes.
3693        buffer_len: usize,
3694    },
3695    /// The output buffer is too small.
3696    OutputTooSmall {
3697        /// Required output bytes.
3698        required: usize,
3699        /// Available output bytes.
3700        available: usize,
3701    },
3702}
3703
3704impl core::fmt::Display for EncodeError {
3705    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3706        match self {
3707            Self::LengthOverflow => f.write_str("base64 output length overflows usize"),
3708            Self::InvalidLineWrap { line_len } => {
3709                write!(f, "base64 line wrap length {line_len} is invalid")
3710            }
3711            Self::InputTooLarge {
3712                input_len,
3713                buffer_len,
3714            } => write!(
3715                f,
3716                "base64 input length {input_len} exceeds buffer length {buffer_len}"
3717            ),
3718            Self::OutputTooSmall {
3719                required,
3720                available,
3721            } => write!(
3722                f,
3723                "base64 output buffer too small: required {required}, available {available}"
3724            ),
3725        }
3726    }
3727}
3728
3729#[cfg(feature = "std")]
3730impl std::error::Error for EncodeError {}
3731
3732/// Decoding error.
3733#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3734pub enum DecodeError {
3735    /// The encoded input is malformed, but the decoder intentionally does not
3736    /// disclose a more specific error class.
3737    InvalidInput,
3738    /// The encoded input length is impossible for the selected padding policy.
3739    InvalidLength,
3740    /// A byte is not valid for the selected alphabet.
3741    InvalidByte {
3742        /// Byte index in the input.
3743        index: usize,
3744        /// Invalid byte value.
3745        byte: u8,
3746    },
3747    /// Padding is missing, misplaced, or non-canonical.
3748    InvalidPadding {
3749        /// Byte index where padding became invalid.
3750        index: usize,
3751    },
3752    /// Line wrapping is missing, misplaced, or uses the wrong line ending.
3753    InvalidLineWrap {
3754        /// Byte index where line wrapping became invalid.
3755        index: usize,
3756    },
3757    /// The output buffer is too small.
3758    OutputTooSmall {
3759        /// Required output bytes.
3760        required: usize,
3761        /// Available output bytes.
3762        available: usize,
3763    },
3764    /// The caller-provided constant-time staging buffer is too small.
3765    StagingTooSmall {
3766        /// Required staging bytes.
3767        required: usize,
3768        /// Available staging bytes.
3769        available: usize,
3770    },
3771}
3772
3773impl core::fmt::Display for DecodeError {
3774    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3775        match self {
3776            Self::InvalidInput => f.write_str("malformed base64 input"),
3777            Self::InvalidLength => f.write_str("invalid base64 input length"),
3778            Self::InvalidByte { index, byte } => {
3779                write!(f, "invalid base64 byte 0x{byte:02x} at index {index}")
3780            }
3781            Self::InvalidPadding { index } => write!(f, "invalid base64 padding at index {index}"),
3782            Self::InvalidLineWrap { index } => {
3783                write!(f, "invalid base64 line wrapping at index {index}")
3784            }
3785            Self::OutputTooSmall {
3786                required,
3787                available,
3788            } => write!(
3789                f,
3790                "base64 decode output buffer too small: required {required}, available {available}"
3791            ),
3792            Self::StagingTooSmall {
3793                required,
3794                available,
3795            } => write!(
3796                f,
3797                "base64 decode staging buffer too small: required {required}, available {available}"
3798            ),
3799        }
3800    }
3801}
3802
3803impl DecodeError {
3804    fn with_index_offset(self, offset: usize) -> Self {
3805        match self {
3806            Self::InvalidByte { index, byte } => Self::InvalidByte {
3807                index: index + offset,
3808                byte,
3809            },
3810            Self::InvalidPadding { index } => Self::InvalidPadding {
3811                index: index + offset,
3812            },
3813            Self::InvalidLineWrap { index } => Self::InvalidLineWrap {
3814                index: index + offset,
3815            },
3816            Self::InvalidInput
3817            | Self::InvalidLength
3818            | Self::OutputTooSmall { .. }
3819            | Self::StagingTooSmall { .. } => self,
3820        }
3821    }
3822}
3823
3824#[cfg(feature = "std")]
3825impl std::error::Error for DecodeError {}
3826
3827struct LegacyBytes<'a> {
3828    input: &'a [u8],
3829    index: usize,
3830}
3831
3832impl<'a> LegacyBytes<'a> {
3833    const fn new(input: &'a [u8]) -> Self {
3834        Self { input, index: 0 }
3835    }
3836
3837    fn next_byte(&mut self) -> Option<(usize, u8)> {
3838        while self.index < self.input.len() {
3839            let index = self.index;
3840            let byte = self.input[index];
3841            self.index += 1;
3842            if !is_legacy_whitespace(byte) {
3843                return Some((index, byte));
3844            }
3845        }
3846        None
3847    }
3848}
3849
3850fn validate_legacy_decode<A: Alphabet, const PAD: bool>(
3851    input: &[u8],
3852) -> Result<usize, DecodeError> {
3853    let mut bytes = LegacyBytes::new(input);
3854    let mut chunk = [0u8; 4];
3855    let mut indexes = [0usize; 4];
3856    let mut chunk_len = 0;
3857    let mut required = 0;
3858    let mut terminal_seen = false;
3859
3860    while let Some((index, byte)) = bytes.next_byte() {
3861        if terminal_seen {
3862            return Err(DecodeError::InvalidPadding { index });
3863        }
3864
3865        chunk[chunk_len] = byte;
3866        indexes[chunk_len] = index;
3867        chunk_len += 1;
3868
3869        if chunk_len == 4 {
3870            let written =
3871                validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
3872            required += written;
3873            terminal_seen = written < 3;
3874            chunk_len = 0;
3875        }
3876    }
3877
3878    if chunk_len == 0 {
3879        return Ok(required);
3880    }
3881    if PAD {
3882        return Err(DecodeError::InvalidLength);
3883    }
3884
3885    validate_tail_unpadded::<A>(&chunk[..chunk_len])
3886        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
3887    Ok(required + decoded_capacity(chunk_len))
3888}
3889
3890fn decode_legacy_to_slice<A: Alphabet, const PAD: bool>(
3891    input: &[u8],
3892    output: &mut [u8],
3893) -> Result<usize, DecodeError> {
3894    let mut bytes = LegacyBytes::new(input);
3895    let mut chunk = [0u8; 4];
3896    let mut indexes = [0usize; 4];
3897    let mut chunk_len = 0;
3898    let mut write = 0;
3899    let mut terminal_seen = false;
3900
3901    while let Some((index, byte)) = bytes.next_byte() {
3902        if terminal_seen {
3903            return Err(DecodeError::InvalidPadding { index });
3904        }
3905
3906        chunk[chunk_len] = byte;
3907        indexes[chunk_len] = index;
3908        chunk_len += 1;
3909
3910        if chunk_len == 4 {
3911            let available = output.len();
3912            let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
3913                required: write,
3914                available,
3915            })?;
3916            let written = decode_chunk::<A, PAD>(chunk, output_tail)
3917                .map_err(|err| map_chunk_error(err, &indexes))?;
3918            write += written;
3919            terminal_seen = written < 3;
3920            chunk_len = 0;
3921        }
3922    }
3923
3924    if chunk_len == 0 {
3925        return Ok(write);
3926    }
3927    if PAD {
3928        return Err(DecodeError::InvalidLength);
3929    }
3930
3931    decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
3932        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
3933        .map(|n| write + n)
3934}
3935
3936struct WrappedBytes<'a> {
3937    input: &'a [u8],
3938    wrap: LineWrap,
3939    index: usize,
3940    line_len: usize,
3941}
3942
3943impl<'a> WrappedBytes<'a> {
3944    const fn new(input: &'a [u8], wrap: LineWrap) -> Result<Self, DecodeError> {
3945        if wrap.line_len == 0 {
3946            return Err(DecodeError::InvalidLineWrap { index: 0 });
3947        }
3948        Ok(Self {
3949            input,
3950            wrap,
3951            index: 0,
3952            line_len: 0,
3953        })
3954    }
3955
3956    fn next_byte(&mut self) -> Result<Option<(usize, u8)>, DecodeError> {
3957        loop {
3958            if self.index == self.input.len() {
3959                return Ok(None);
3960            }
3961
3962            if self.starts_with_line_ending() {
3963                let line_end_index = self.index;
3964                if self.line_len == 0 {
3965                    return Err(DecodeError::InvalidLineWrap {
3966                        index: line_end_index,
3967                    });
3968                }
3969
3970                self.index += self.wrap.line_ending.byte_len();
3971                if self.index == self.input.len() {
3972                    self.line_len = 0;
3973                    return Ok(None);
3974                }
3975
3976                if self.line_len != self.wrap.line_len {
3977                    return Err(DecodeError::InvalidLineWrap {
3978                        index: line_end_index,
3979                    });
3980                }
3981                self.line_len = 0;
3982                continue;
3983            }
3984
3985            let byte = self.input[self.index];
3986            if matches!(byte, b'\r' | b'\n') {
3987                return Err(DecodeError::InvalidLineWrap { index: self.index });
3988            }
3989
3990            self.line_len += 1;
3991            if self.line_len > self.wrap.line_len {
3992                return Err(DecodeError::InvalidLineWrap { index: self.index });
3993            }
3994
3995            let index = self.index;
3996            self.index += 1;
3997            return Ok(Some((index, byte)));
3998        }
3999    }
4000
4001    fn starts_with_line_ending(&self) -> bool {
4002        let line_ending = self.wrap.line_ending.as_bytes();
4003        let Some(end) = self.index.checked_add(line_ending.len()) else {
4004            return false;
4005        };
4006        end <= self.input.len() && &self.input[self.index..end] == line_ending
4007    }
4008}
4009
4010fn validate_wrapped_decode<A: Alphabet, const PAD: bool>(
4011    input: &[u8],
4012    wrap: LineWrap,
4013) -> Result<usize, DecodeError> {
4014    let mut bytes = WrappedBytes::new(input, wrap)?;
4015    let mut chunk = [0u8; 4];
4016    let mut indexes = [0usize; 4];
4017    let mut chunk_len = 0;
4018    let mut required = 0;
4019    let mut terminal_seen = false;
4020
4021    while let Some((index, byte)) = bytes.next_byte()? {
4022        if terminal_seen {
4023            return Err(DecodeError::InvalidPadding { index });
4024        }
4025
4026        chunk[chunk_len] = byte;
4027        indexes[chunk_len] = index;
4028        chunk_len += 1;
4029
4030        if chunk_len == 4 {
4031            let written =
4032                validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
4033            required += written;
4034            terminal_seen = written < 3;
4035            chunk_len = 0;
4036        }
4037    }
4038
4039    if chunk_len == 0 {
4040        return Ok(required);
4041    }
4042    if PAD {
4043        return Err(DecodeError::InvalidLength);
4044    }
4045
4046    validate_tail_unpadded::<A>(&chunk[..chunk_len])
4047        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
4048    Ok(required + decoded_capacity(chunk_len))
4049}
4050
4051fn decode_wrapped_to_slice<A: Alphabet, const PAD: bool>(
4052    input: &[u8],
4053    output: &mut [u8],
4054    wrap: LineWrap,
4055) -> Result<usize, DecodeError> {
4056    let mut bytes = WrappedBytes::new(input, wrap)?;
4057    let mut chunk = [0u8; 4];
4058    let mut indexes = [0usize; 4];
4059    let mut chunk_len = 0;
4060    let mut write = 0;
4061    let mut terminal_seen = false;
4062
4063    while let Some((index, byte)) = bytes.next_byte()? {
4064        if terminal_seen {
4065            return Err(DecodeError::InvalidPadding { index });
4066        }
4067
4068        chunk[chunk_len] = byte;
4069        indexes[chunk_len] = index;
4070        chunk_len += 1;
4071
4072        if chunk_len == 4 {
4073            let available = output.len();
4074            let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
4075                required: write,
4076                available,
4077            })?;
4078            let written = decode_chunk::<A, PAD>(chunk, output_tail)
4079                .map_err(|err| map_chunk_error(err, &indexes))?;
4080            write += written;
4081            terminal_seen = written < 3;
4082            chunk_len = 0;
4083        }
4084    }
4085
4086    if chunk_len == 0 {
4087        return Ok(write);
4088    }
4089    if PAD {
4090        return Err(DecodeError::InvalidLength);
4091    }
4092
4093    decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
4094        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
4095        .map(|n| write + n)
4096}
4097
4098fn compact_wrapped_input(buffer: &mut [u8], wrap: LineWrap) -> Result<usize, DecodeError> {
4099    if !wrap.is_valid() {
4100        return Err(DecodeError::InvalidLineWrap { index: 0 });
4101    }
4102
4103    let line_ending = wrap.line_ending.as_bytes();
4104    let line_ending_len = line_ending.len();
4105    let mut read = 0;
4106    let mut write = 0;
4107
4108    while read < buffer.len() {
4109        let line_end = read + line_ending_len;
4110        if buffer.get(read..line_end) == Some(line_ending) {
4111            read = line_end;
4112            continue;
4113        }
4114
4115        buffer[write] = buffer[read];
4116        write += 1;
4117        read += 1;
4118    }
4119
4120    Ok(write)
4121}
4122
4123#[inline]
4124const fn is_legacy_whitespace(byte: u8) -> bool {
4125    matches!(byte, b' ' | b'\t' | b'\r' | b'\n')
4126}
4127
4128fn map_chunk_error(err: DecodeError, indexes: &[usize; 4]) -> DecodeError {
4129    match err {
4130        DecodeError::InvalidByte { index, byte } => DecodeError::InvalidByte {
4131            index: indexes[index],
4132            byte,
4133        },
4134        DecodeError::InvalidPadding { index } => DecodeError::InvalidPadding {
4135            index: indexes[index],
4136        },
4137        DecodeError::InvalidInput
4138        | DecodeError::InvalidLineWrap { .. }
4139        | DecodeError::InvalidLength
4140        | DecodeError::OutputTooSmall { .. }
4141        | DecodeError::StagingTooSmall { .. } => err,
4142    }
4143}
4144
4145fn map_partial_chunk_error(err: DecodeError, indexes: &[usize; 4], len: usize) -> DecodeError {
4146    match err {
4147        DecodeError::InvalidByte { index, byte } if index < len => DecodeError::InvalidByte {
4148            index: indexes[index],
4149            byte,
4150        },
4151        DecodeError::InvalidPadding { index } if index < len => DecodeError::InvalidPadding {
4152            index: indexes[index],
4153        },
4154        DecodeError::InvalidByte { .. }
4155        | DecodeError::InvalidPadding { .. }
4156        | DecodeError::InvalidLineWrap { .. }
4157        | DecodeError::InvalidInput
4158        | DecodeError::InvalidLength
4159        | DecodeError::OutputTooSmall { .. }
4160        | DecodeError::StagingTooSmall { .. } => err,
4161    }
4162}
4163
4164fn decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
4165    if !input.len().is_multiple_of(4) {
4166        return Err(DecodeError::InvalidLength);
4167    }
4168    let required = decoded_len_padded(input)?;
4169    if output.len() < required {
4170        return Err(DecodeError::OutputTooSmall {
4171            required,
4172            available: output.len(),
4173        });
4174    }
4175
4176    let mut read = 0;
4177    let mut write = 0;
4178    while read < input.len() {
4179        let chunk = read_quad(input, read)?;
4180        let available = output.len();
4181        let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
4182            required: write,
4183            available,
4184        })?;
4185        let written = decode_chunk::<A, true>(chunk, output_tail)
4186            .map_err(|err| err.with_index_offset(read))?;
4187        read += 4;
4188        write += written;
4189        if written < 3 && read != input.len() {
4190            return Err(DecodeError::InvalidPadding { index: read - 4 });
4191        }
4192    }
4193    Ok(write)
4194}
4195
4196fn validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
4197    if input.is_empty() {
4198        return Ok(0);
4199    }
4200
4201    if PAD {
4202        validate_padded::<A>(input)
4203    } else {
4204        validate_unpadded::<A>(input)
4205    }
4206}
4207
4208fn validate_padded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
4209    if !input.len().is_multiple_of(4) {
4210        return Err(DecodeError::InvalidLength);
4211    }
4212    let required = decoded_len_padded(input)?;
4213
4214    let mut read = 0;
4215    while read < input.len() {
4216        let chunk = read_quad(input, read)?;
4217        let written =
4218            validate_chunk::<A, true>(chunk).map_err(|err| err.with_index_offset(read))?;
4219        read += 4;
4220        if written < 3 && read != input.len() {
4221            return Err(DecodeError::InvalidPadding { index: read - 4 });
4222        }
4223    }
4224
4225    Ok(required)
4226}
4227
4228fn validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
4229    let required = decoded_len_unpadded(input)?;
4230
4231    let mut read = 0;
4232    while read + 4 <= input.len() {
4233        let chunk = read_quad(input, read)?;
4234        validate_chunk::<A, false>(chunk).map_err(|err| err.with_index_offset(read))?;
4235        read += 4;
4236    }
4237    validate_tail_unpadded::<A>(&input[read..]).map_err(|err| err.with_index_offset(read))?;
4238
4239    Ok(required)
4240}
4241
4242fn decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
4243    let required = decoded_len_unpadded(input)?;
4244    if output.len() < required {
4245        return Err(DecodeError::OutputTooSmall {
4246            required,
4247            available: output.len(),
4248        });
4249    }
4250
4251    let mut read = 0;
4252    let mut write = 0;
4253    while read + 4 <= input.len() {
4254        let chunk = read_quad(input, read)?;
4255        let available = output.len();
4256        let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
4257            required: write,
4258            available,
4259        })?;
4260        let written = decode_chunk::<A, false>(chunk, output_tail)
4261            .map_err(|err| err.with_index_offset(read))?;
4262        read += 4;
4263        write += written;
4264    }
4265    decode_tail_unpadded::<A>(&input[read..], &mut output[write..])
4266        .map_err(|err| err.with_index_offset(read))
4267        .map(|n| write + n)
4268}
4269
4270fn decoded_len_padded(input: &[u8]) -> Result<usize, DecodeError> {
4271    if input.is_empty() {
4272        return Ok(0);
4273    }
4274    if !input.len().is_multiple_of(4) {
4275        return Err(DecodeError::InvalidLength);
4276    }
4277
4278    let Some((&last, before_last_prefix)) = input.split_last() else {
4279        return Ok(0);
4280    };
4281    let Some(&before_last) = before_last_prefix.last() else {
4282        return Err(DecodeError::InvalidLength);
4283    };
4284
4285    let mut padding = 0;
4286    if last == b'=' {
4287        padding += 1;
4288    }
4289    if before_last == b'=' {
4290        padding += 1;
4291    }
4292    if padding == 0
4293        && let Some(index) = input.iter().position(|byte| *byte == b'=')
4294    {
4295        return Err(DecodeError::InvalidPadding { index });
4296    }
4297    if padding > 0 {
4298        let first_pad = input.len() - padding;
4299        if let Some(index) = input[..first_pad].iter().position(|byte| *byte == b'=') {
4300            return Err(DecodeError::InvalidPadding { index });
4301        }
4302    }
4303    Ok(input.len() / 4 * 3 - padding)
4304}
4305
4306fn decoded_len_unpadded(input: &[u8]) -> Result<usize, DecodeError> {
4307    if input.len() % 4 == 1 {
4308        return Err(DecodeError::InvalidLength);
4309    }
4310    if let Some(index) = input.iter().position(|byte| *byte == b'=') {
4311        return Err(DecodeError::InvalidPadding { index });
4312    }
4313    Ok(decoded_capacity(input.len()))
4314}
4315
4316fn read_quad(input: &[u8], offset: usize) -> Result<[u8; 4], DecodeError> {
4317    let end = offset.checked_add(4).ok_or(DecodeError::InvalidLength)?;
4318    match input.get(offset..end) {
4319        Some([b0, b1, b2, b3]) => Ok([*b0, *b1, *b2, *b3]),
4320        _ => Err(DecodeError::InvalidLength),
4321    }
4322}
4323
4324fn first_padding_index_unchecked(input: [u8; 4]) -> usize {
4325    let [b0, b1, b2, b3] = input;
4326    if b0 == b'=' {
4327        0
4328    } else if b1 == b'=' {
4329        1
4330    } else if b2 == b'=' {
4331        2
4332    } else if b3 == b'=' {
4333        3
4334    } else {
4335        debug_assert!(
4336            false,
4337            "first_padding_index_unchecked called with no padding"
4338        );
4339        4
4340    }
4341}
4342
4343fn validate_chunk<A: Alphabet, const PAD: bool>(input: [u8; 4]) -> Result<usize, DecodeError> {
4344    let [b0, b1, b2, b3] = input;
4345    let _v0 = decode_byte::<A>(b0, 0)?;
4346    let v1 = decode_byte::<A>(b1, 1)?;
4347
4348    match (b2, b3) {
4349        (b'=', b'=') if PAD => {
4350            if v1 & 0b0000_1111 != 0 {
4351                return Err(DecodeError::InvalidPadding { index: 1 });
4352            }
4353            Ok(1)
4354        }
4355        (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
4356        (_, b'=') if PAD => {
4357            let v2 = decode_byte::<A>(b2, 2)?;
4358            if v2 & 0b0000_0011 != 0 {
4359                return Err(DecodeError::InvalidPadding { index: 2 });
4360            }
4361            Ok(2)
4362        }
4363        (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
4364            index: first_padding_index_unchecked(input),
4365        }),
4366        _ => {
4367            decode_byte::<A>(b2, 2)?;
4368            decode_byte::<A>(b3, 3)?;
4369            Ok(3)
4370        }
4371    }
4372}
4373
4374fn decode_chunk<A: Alphabet, const PAD: bool>(
4375    input: [u8; 4],
4376    output: &mut [u8],
4377) -> Result<usize, DecodeError> {
4378    let [b0, b1, b2, b3] = input;
4379    let v0 = decode_byte::<A>(b0, 0)?;
4380    let v1 = decode_byte::<A>(b1, 1)?;
4381
4382    match (b2, b3) {
4383        (b'=', b'=') if PAD => {
4384            if output.is_empty() {
4385                return Err(DecodeError::OutputTooSmall {
4386                    required: 1,
4387                    available: output.len(),
4388                });
4389            }
4390            if v1 & 0b0000_1111 != 0 {
4391                return Err(DecodeError::InvalidPadding { index: 1 });
4392            }
4393            output[0] = (v0 << 2) | (v1 >> 4);
4394            Ok(1)
4395        }
4396        (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
4397        (_, b'=') if PAD => {
4398            if output.len() < 2 {
4399                return Err(DecodeError::OutputTooSmall {
4400                    required: 2,
4401                    available: output.len(),
4402                });
4403            }
4404            let v2 = decode_byte::<A>(b2, 2)?;
4405            if v2 & 0b0000_0011 != 0 {
4406                return Err(DecodeError::InvalidPadding { index: 2 });
4407            }
4408            output[0] = (v0 << 2) | (v1 >> 4);
4409            output[1] = (v1 << 4) | (v2 >> 2);
4410            Ok(2)
4411        }
4412        (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
4413            index: first_padding_index_unchecked(input),
4414        }),
4415        _ => {
4416            if output.len() < 3 {
4417                return Err(DecodeError::OutputTooSmall {
4418                    required: 3,
4419                    available: output.len(),
4420                });
4421            }
4422            let v2 = decode_byte::<A>(b2, 2)?;
4423            let v3 = decode_byte::<A>(b3, 3)?;
4424            output[0] = (v0 << 2) | (v1 >> 4);
4425            output[1] = (v1 << 4) | (v2 >> 2);
4426            output[2] = (v2 << 6) | v3;
4427            Ok(3)
4428        }
4429    }
4430}
4431
4432fn validate_tail_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
4433    match input {
4434        [] => Ok(()),
4435        [b0, b1] => {
4436            decode_byte::<A>(*b0, 0)?;
4437            let v1 = decode_byte::<A>(*b1, 1)?;
4438            if v1 & 0b0000_1111 != 0 {
4439                return Err(DecodeError::InvalidPadding { index: 1 });
4440            }
4441            Ok(())
4442        }
4443        [b0, b1, b2] => {
4444            decode_byte::<A>(*b0, 0)?;
4445            decode_byte::<A>(*b1, 1)?;
4446            let v2 = decode_byte::<A>(*b2, 2)?;
4447            if v2 & 0b0000_0011 != 0 {
4448                return Err(DecodeError::InvalidPadding { index: 2 });
4449            }
4450            Ok(())
4451        }
4452        _ => Err(DecodeError::InvalidLength),
4453    }
4454}
4455
4456fn decode_tail_unpadded<A: Alphabet>(
4457    input: &[u8],
4458    output: &mut [u8],
4459) -> Result<usize, DecodeError> {
4460    match input {
4461        [] => Ok(0),
4462        [b0, b1] => {
4463            let Some(out0) = output.first_mut() else {
4464                return Err(DecodeError::OutputTooSmall {
4465                    required: 1,
4466                    available: output.len(),
4467                });
4468            };
4469            let v0 = decode_byte::<A>(*b0, 0)?;
4470            let v1 = decode_byte::<A>(*b1, 1)?;
4471            if v1 & 0b0000_1111 != 0 {
4472                return Err(DecodeError::InvalidPadding { index: 1 });
4473            }
4474            *out0 = (v0 << 2) | (v1 >> 4);
4475            Ok(1)
4476        }
4477        [b0, b1, b2] => {
4478            let available = output.len();
4479            let Some([out0, out1]) = output.get_mut(..2) else {
4480                return Err(DecodeError::OutputTooSmall {
4481                    required: 2,
4482                    available,
4483                });
4484            };
4485            let v0 = decode_byte::<A>(*b0, 0)?;
4486            let v1 = decode_byte::<A>(*b1, 1)?;
4487            let v2 = decode_byte::<A>(*b2, 2)?;
4488            if v2 & 0b0000_0011 != 0 {
4489                return Err(DecodeError::InvalidPadding { index: 2 });
4490            }
4491            *out0 = (v0 << 2) | (v1 >> 4);
4492            *out1 = (v1 << 4) | (v2 >> 2);
4493            Ok(2)
4494        }
4495        _ => Err(DecodeError::InvalidLength),
4496    }
4497}
4498
4499fn decode_byte<A: Alphabet>(byte: u8, index: usize) -> Result<u8, DecodeError> {
4500    A::decode(byte).ok_or(DecodeError::InvalidByte { index, byte })
4501}
4502
4503fn ct_decode_slice<A: Alphabet, const PAD: bool>(
4504    input: &[u8],
4505    output: &mut [u8],
4506) -> Result<usize, DecodeError> {
4507    if input.is_empty() {
4508        return Ok(0);
4509    }
4510
4511    if PAD {
4512        ct_decode_padded::<A>(input, output)
4513    } else {
4514        ct_decode_unpadded::<A>(input, output)
4515    }
4516}
4517
4518fn ct_decode_slice_staged_clear_tail<A: Alphabet, const PAD: bool>(
4519    input: &[u8],
4520    output: &mut [u8],
4521    staging: &mut [u8],
4522) -> Result<usize, DecodeError> {
4523    let required = match ct_decoded_len::<A, PAD>(input) {
4524        Ok(required) => required,
4525        Err(err) => {
4526            wipe_bytes(output);
4527            wipe_bytes(staging);
4528            return Err(err);
4529        }
4530    };
4531
4532    if output.len() < required {
4533        wipe_bytes(output);
4534        wipe_bytes(staging);
4535        return Err(DecodeError::OutputTooSmall {
4536            required,
4537            available: output.len(),
4538        });
4539    }
4540
4541    if staging.len() < required {
4542        wipe_bytes(output);
4543        wipe_bytes(staging);
4544        return Err(DecodeError::StagingTooSmall {
4545            required,
4546            available: staging.len(),
4547        });
4548    }
4549
4550    let written = match ct_decode_slice::<A, PAD>(input, &mut staging[..required]) {
4551        Ok(written) => written,
4552        Err(err) => {
4553            wipe_bytes(output);
4554            wipe_bytes(staging);
4555            return Err(err);
4556        }
4557    };
4558
4559    output[..written].copy_from_slice(&staging[..written]);
4560    wipe_bytes(staging);
4561    wipe_tail(output, written);
4562    Ok(written)
4563}
4564
4565fn ct_decode_in_place<A: Alphabet, const PAD: bool>(
4566    buffer: &mut [u8],
4567) -> Result<usize, DecodeError> {
4568    if buffer.is_empty() {
4569        return Ok(0);
4570    }
4571
4572    if PAD {
4573        ct_decode_padded_in_place::<A>(buffer)
4574    } else {
4575        ct_decode_unpadded_in_place::<A>(buffer)
4576    }
4577}
4578
4579#[inline(never)]
4580#[allow(unsafe_code)]
4581fn ct_error_gate_barrier(invalid_byte: u8, invalid_padding: u8) {
4582    core::hint::black_box(invalid_byte | invalid_padding);
4583    core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
4584
4585    #[cfg(all(not(miri), any(target_arch = "x86", target_arch = "x86_64")))]
4586    {
4587        // SAFETY: `lfence` does not access memory and is used as a speculation
4588        // barrier before the public success/failure branch is observed.
4589        unsafe {
4590            core::arch::asm!("lfence", options(nostack, preserves_flags, nomem));
4591        }
4592    }
4593
4594    #[cfg(all(not(miri), target_arch = "aarch64"))]
4595    {
4596        // SAFETY: these barriers do not access memory. `isb sy` is a
4597        // best-effort speculation boundary and `hint #20` is the architectural
4598        // CSDB hint encoding on AArch64.
4599        unsafe {
4600            core::arch::asm!("isb sy", "hint #20", options(nostack, preserves_flags));
4601        }
4602    }
4603
4604    #[cfg(all(not(miri), target_arch = "arm"))]
4605    {
4606        // SAFETY: `isb sy` does not access memory and is used as the best
4607        // available stable ARM speculation boundary for this crate.
4608        unsafe {
4609            core::arch::asm!("isb sy", options(nostack, preserves_flags));
4610        }
4611    }
4612
4613    #[cfg(all(not(miri), any(target_arch = "riscv32", target_arch = "riscv64")))]
4614    {
4615        // RISC-V base ISA does not provide a canonical speculation barrier.
4616        // `fence rw, rw` is the available ordering primitive for the CT public
4617        // result gate and is reported separately as `ordering-fence`.
4618        // SAFETY: the assembly block does not access memory.
4619        unsafe {
4620            core::arch::asm!("fence rw, rw", options(nostack, preserves_flags));
4621        }
4622    }
4623}
4624
4625fn ct_validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<(), DecodeError> {
4626    if input.is_empty() {
4627        return Ok(());
4628    }
4629
4630    if PAD {
4631        ct_validate_padded::<A>(input)
4632    } else {
4633        ct_validate_unpadded::<A>(input)
4634    }
4635}
4636
4637fn ct_decoded_len<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
4638    ct_validate_decode::<A, PAD>(input)?;
4639    if input.is_empty() {
4640        return Ok(0);
4641    }
4642
4643    if PAD {
4644        Ok(input.len() / 4 * 3 - ct_padding_len(input))
4645    } else {
4646        let full_quads = input.len() / 4 * 3;
4647        match input.len() % 4 {
4648            0 => Ok(full_quads),
4649            2 => Ok(full_quads + 1),
4650            3 => Ok(full_quads + 2),
4651            _ => Err(DecodeError::InvalidLength),
4652        }
4653    }
4654}
4655
4656fn ct_validate_padded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
4657    if !input.len().is_multiple_of(4) {
4658        return Err(DecodeError::InvalidLength);
4659    }
4660
4661    let padding = ct_padding_len(input);
4662    let mut invalid_byte = 0u8;
4663    let mut invalid_padding = 0u8;
4664    let mut read = 0;
4665
4666    while read + 4 < input.len() {
4667        let [b0, b1, b2, b3] =
4668            read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
4669        let (_, valid0) = ct_decode_alphabet_byte::<A>(b0);
4670        let (_, valid1) = ct_decode_alphabet_byte::<A>(b1);
4671        let (_, valid2) = ct_decode_alphabet_byte::<A>(b2);
4672        let (_, valid3) = ct_decode_alphabet_byte::<A>(b3);
4673
4674        invalid_byte |= !valid0;
4675        invalid_byte |= !valid1;
4676        invalid_byte |= !valid2;
4677        invalid_byte |= !valid3;
4678        invalid_padding |= ct_mask_eq_u8(b2, b'=');
4679        invalid_padding |= ct_mask_eq_u8(b3, b'=');
4680        read += 4;
4681    }
4682
4683    let final_chunk =
4684        read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
4685    let (_, final_invalid_byte, final_invalid_padding, _) =
4686        ct_padded_final_quantum::<A>(final_chunk, padding);
4687    invalid_byte |= final_invalid_byte;
4688    invalid_padding |= final_invalid_padding;
4689
4690    report_ct_error(invalid_byte, invalid_padding)
4691}
4692
4693fn ct_validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
4694    if input.len() % 4 == 1 {
4695        return Err(DecodeError::InvalidLength);
4696    }
4697
4698    let mut invalid_byte = 0u8;
4699    let mut invalid_padding = 0u8;
4700    let mut read = 0;
4701
4702    while read + 4 <= input.len() {
4703        let [b0, b1, b2, b3] =
4704            read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
4705        let (_, valid0) = ct_decode_alphabet_byte::<A>(b0);
4706        let (_, valid1) = ct_decode_alphabet_byte::<A>(b1);
4707        let (_, valid2) = ct_decode_alphabet_byte::<A>(b2);
4708        let (_, valid3) = ct_decode_alphabet_byte::<A>(b3);
4709
4710        invalid_byte |= !valid0;
4711        invalid_byte |= !valid1;
4712        invalid_byte |= !valid2;
4713        invalid_byte |= !valid3;
4714        invalid_padding |= ct_mask_eq_u8(b0, b'=');
4715        invalid_padding |= ct_mask_eq_u8(b1, b'=');
4716        invalid_padding |= ct_mask_eq_u8(b2, b'=');
4717        invalid_padding |= ct_mask_eq_u8(b3, b'=');
4718
4719        read += 4;
4720    }
4721
4722    match read_tail_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding) {
4723        [] => {}
4724        [b0, b1] => {
4725            let (_, valid0) = ct_decode_alphabet_byte::<A>(*b0);
4726            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
4727            invalid_byte |= !valid0;
4728            invalid_byte |= !valid1;
4729            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
4730            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
4731            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
4732        }
4733        [b0, b1, b2] => {
4734            let (_, valid0) = ct_decode_alphabet_byte::<A>(*b0);
4735            let (_, valid1) = ct_decode_alphabet_byte::<A>(*b1);
4736            let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
4737            invalid_byte |= !valid0;
4738            invalid_byte |= !valid1;
4739            invalid_byte |= !valid2;
4740            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
4741            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
4742            invalid_padding |= ct_mask_eq_u8(*b2, b'=');
4743            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
4744        }
4745        _ => {
4746            invalid_byte = 0xff;
4747            invalid_padding = 0xff;
4748        }
4749    }
4750
4751    report_ct_error(invalid_byte, invalid_padding)
4752}
4753
4754fn ct_padded_final_quantum<A: Alphabet>(
4755    input: [u8; 4],
4756    padding: usize,
4757) -> ([u8; 3], u8, u8, usize) {
4758    let [b0, b1, b2, b3] = input;
4759    let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
4760    let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
4761    let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
4762    let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
4763
4764    let padding_byte = match padding {
4765        0 => 0,
4766        1 => 1,
4767        2 => 2,
4768        _ => return ([0; 3], 0xff, 0xff, 0),
4769    };
4770    let no_padding = ct_mask_eq_u8(padding_byte, 0);
4771    let one_padding = ct_mask_eq_u8(padding_byte, 1);
4772    let two_padding = ct_mask_eq_u8(padding_byte, 2);
4773    let require_v2 = no_padding | one_padding;
4774    let require_v3 = no_padding;
4775
4776    let invalid_byte = !valid0 | !valid1 | (!valid2 & require_v2) | (!valid3 & require_v3);
4777    let invalid_padding = (ct_mask_nonzero_u8(v1 & 0b0000_1111) & two_padding)
4778        | ((ct_mask_eq_u8(b2, b'=') | ct_mask_nonzero_u8(v2 & 0b0000_0011)) & one_padding)
4779        | ((ct_mask_eq_u8(b2, b'=') | ct_mask_eq_u8(b3, b'=')) & no_padding);
4780
4781    (
4782        [(v0 << 2) | (v1 >> 4), (v1 << 4) | (v2 >> 2), (v2 << 6) | v3],
4783        invalid_byte,
4784        invalid_padding,
4785        3 - padding,
4786    )
4787}
4788
4789fn ct_decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
4790    if !input.len().is_multiple_of(4) {
4791        return Err(DecodeError::InvalidLength);
4792    }
4793
4794    let padding = ct_padding_len(input);
4795    let required = input.len() / 4 * 3 - padding;
4796    if output.len() < required {
4797        return Err(DecodeError::OutputTooSmall {
4798            required,
4799            available: output.len(),
4800        });
4801    }
4802
4803    let mut invalid_byte = 0u8;
4804    let mut invalid_padding = 0u8;
4805    let mut write = 0;
4806    let mut read = 0;
4807
4808    while read + 4 < input.len() {
4809        let [b0, b1, b2, b3] =
4810            read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
4811        let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
4812        let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
4813        let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
4814        let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
4815
4816        invalid_byte |= !valid0;
4817        invalid_byte |= !valid1;
4818        invalid_byte |= !valid2;
4819        invalid_byte |= !valid3;
4820        invalid_padding |= ct_mask_eq_u8(b2, b'=');
4821        invalid_padding |= ct_mask_eq_u8(b3, b'=');
4822        output[write] = (v0 << 2) | (v1 >> 4);
4823        output[write + 1] = (v1 << 4) | (v2 >> 2);
4824        output[write + 2] = (v2 << 6) | v3;
4825        write += 3;
4826        read += 4;
4827    }
4828
4829    let final_chunk =
4830        read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
4831    let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
4832        ct_padded_final_quantum::<A>(final_chunk, padding);
4833    invalid_byte |= final_invalid_byte;
4834    invalid_padding |= final_invalid_padding;
4835    output[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
4836    write += final_written;
4837
4838    report_ct_error(invalid_byte, invalid_padding)?;
4839    Ok(write)
4840}
4841
4842fn ct_decode_padded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
4843    if !buffer.len().is_multiple_of(4) {
4844        return Err(DecodeError::InvalidLength);
4845    }
4846
4847    let padding = ct_padding_len(buffer);
4848    let required = buffer.len() / 4 * 3 - padding;
4849    if required > buffer.len() {
4850        wipe_bytes(buffer);
4851        return Err(DecodeError::InvalidInput);
4852    }
4853
4854    let mut invalid_byte = 0u8;
4855    let mut invalid_padding = 0u8;
4856    let mut write = 0;
4857    let mut read = 0;
4858
4859    while read + 4 < buffer.len() {
4860        let [b0, b1, b2, b3] =
4861            read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
4862        let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
4863        let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
4864        let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
4865        let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
4866
4867        invalid_byte |= !valid0;
4868        invalid_byte |= !valid1;
4869        invalid_byte |= !valid2;
4870        invalid_byte |= !valid3;
4871        invalid_padding |= ct_mask_eq_u8(b2, b'=');
4872        invalid_padding |= ct_mask_eq_u8(b3, b'=');
4873        buffer[write] = (v0 << 2) | (v1 >> 4);
4874        buffer[write + 1] = (v1 << 4) | (v2 >> 2);
4875        buffer[write + 2] = (v2 << 6) | v3;
4876        write += 3;
4877        read += 4;
4878    }
4879
4880    let final_chunk =
4881        read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
4882    let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
4883        ct_padded_final_quantum::<A>(final_chunk, padding);
4884    invalid_byte |= final_invalid_byte;
4885    invalid_padding |= final_invalid_padding;
4886    buffer[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
4887    write += final_written;
4888
4889    if write != required {
4890        ct_error_gate_barrier(invalid_byte, invalid_padding);
4891        wipe_bytes(buffer);
4892        return Err(DecodeError::InvalidInput);
4893    }
4894    if let Err(err) = report_ct_error(invalid_byte, invalid_padding) {
4895        wipe_bytes(buffer);
4896        return Err(err);
4897    }
4898    Ok(write)
4899}
4900
4901fn ct_decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
4902    if input.len() % 4 == 1 {
4903        return Err(DecodeError::InvalidLength);
4904    }
4905
4906    let required = decoded_capacity(input.len());
4907    if output.len() < required {
4908        return Err(DecodeError::OutputTooSmall {
4909            required,
4910            available: output.len(),
4911        });
4912    }
4913
4914    let mut invalid_byte = 0u8;
4915    let mut invalid_padding = 0u8;
4916    let mut write = 0;
4917    let mut read = 0;
4918
4919    while read + 4 <= input.len() {
4920        let [b0, b1, b2, b3] =
4921            read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
4922        let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
4923        let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
4924        let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
4925        let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
4926
4927        invalid_byte |= !valid0;
4928        invalid_byte |= !valid1;
4929        invalid_byte |= !valid2;
4930        invalid_byte |= !valid3;
4931        invalid_padding |= ct_mask_eq_u8(b0, b'=');
4932        invalid_padding |= ct_mask_eq_u8(b1, b'=');
4933        invalid_padding |= ct_mask_eq_u8(b2, b'=');
4934        invalid_padding |= ct_mask_eq_u8(b3, b'=');
4935
4936        output[write] = (v0 << 2) | (v1 >> 4);
4937        output[write + 1] = (v1 << 4) | (v2 >> 2);
4938        output[write + 2] = (v2 << 6) | v3;
4939        read += 4;
4940        write += 3;
4941    }
4942
4943    match read_tail_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding) {
4944        [] => {}
4945        [b0, b1] => {
4946            let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
4947            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
4948            invalid_byte |= !valid0;
4949            invalid_byte |= !valid1;
4950            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
4951            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
4952            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
4953            output[write] = (v0 << 2) | (v1 >> 4);
4954            write += 1;
4955        }
4956        [b0, b1, b2] => {
4957            let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
4958            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
4959            let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
4960            invalid_byte |= !valid0;
4961            invalid_byte |= !valid1;
4962            invalid_byte |= !valid2;
4963            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
4964            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
4965            invalid_padding |= ct_mask_eq_u8(*b2, b'=');
4966            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
4967            output[write] = (v0 << 2) | (v1 >> 4);
4968            output[write + 1] = (v1 << 4) | (v2 >> 2);
4969            write += 2;
4970        }
4971        _ => {
4972            invalid_byte = 0xff;
4973            invalid_padding = 0xff;
4974        }
4975    }
4976
4977    report_ct_error(invalid_byte, invalid_padding)?;
4978    Ok(write)
4979}
4980
4981fn ct_decode_unpadded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
4982    if buffer.len() % 4 == 1 {
4983        return Err(DecodeError::InvalidLength);
4984    }
4985
4986    let required = decoded_capacity(buffer.len());
4987    if required > buffer.len() {
4988        wipe_bytes(buffer);
4989        return Err(DecodeError::InvalidInput);
4990    }
4991
4992    let mut invalid_byte = 0u8;
4993    let mut invalid_padding = 0u8;
4994    let mut write = 0;
4995    let mut read = 0;
4996
4997    while read + 4 <= buffer.len() {
4998        let [b0, b1, b2, b3] =
4999            read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
5000        let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
5001        let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
5002        let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
5003        let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
5004
5005        invalid_byte |= !valid0;
5006        invalid_byte |= !valid1;
5007        invalid_byte |= !valid2;
5008        invalid_byte |= !valid3;
5009        invalid_padding |= ct_mask_eq_u8(b0, b'=');
5010        invalid_padding |= ct_mask_eq_u8(b1, b'=');
5011        invalid_padding |= ct_mask_eq_u8(b2, b'=');
5012        invalid_padding |= ct_mask_eq_u8(b3, b'=');
5013
5014        buffer[write] = (v0 << 2) | (v1 >> 4);
5015        buffer[write + 1] = (v1 << 4) | (v2 >> 2);
5016        buffer[write + 2] = (v2 << 6) | v3;
5017        read += 4;
5018        write += 3;
5019    }
5020
5021    let tail = read_tail_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
5022    match tail {
5023        [] => {}
5024        [b0, b1] => {
5025            let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
5026            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
5027            invalid_byte |= !valid0;
5028            invalid_byte |= !valid1;
5029            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
5030            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
5031            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
5032            buffer[write] = (v0 << 2) | (v1 >> 4);
5033            write += 1;
5034        }
5035        [b0, b1, b2] => {
5036            let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
5037            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
5038            let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
5039            invalid_byte |= !valid0;
5040            invalid_byte |= !valid1;
5041            invalid_byte |= !valid2;
5042            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
5043            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
5044            invalid_padding |= ct_mask_eq_u8(*b2, b'=');
5045            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
5046            buffer[write] = (v0 << 2) | (v1 >> 4);
5047            buffer[write + 1] = (v1 << 4) | (v2 >> 2);
5048            write += 2;
5049        }
5050        _ => {
5051            invalid_byte = 0xff;
5052            invalid_padding = 0xff;
5053        }
5054    }
5055
5056    if write != required {
5057        ct_error_gate_barrier(invalid_byte, invalid_padding);
5058        wipe_bytes(buffer);
5059        return Err(DecodeError::InvalidInput);
5060    }
5061    if let Err(err) = report_ct_error(invalid_byte, invalid_padding) {
5062        wipe_bytes(buffer);
5063        return Err(err);
5064    }
5065    Ok(write)
5066}
5067
5068fn read_tail(input: &[u8], offset: usize) -> Result<&[u8], DecodeError> {
5069    input.get(offset..).ok_or(DecodeError::InvalidLength)
5070}
5071
5072fn read_quad_or_mark_invalid(
5073    input: &[u8],
5074    offset: usize,
5075    invalid_byte: &mut u8,
5076    invalid_padding: &mut u8,
5077) -> [u8; 4] {
5078    if let Ok(quad) = read_quad(input, offset) {
5079        quad
5080    } else {
5081        debug_assert!(
5082            false,
5083            "read_quad failed inside length-validated constant-time decode loop"
5084        );
5085        *invalid_byte = 0xff;
5086        *invalid_padding = 0xff;
5087        [0; 4]
5088    }
5089}
5090
5091fn read_tail_or_mark_invalid<'a>(
5092    input: &'a [u8],
5093    offset: usize,
5094    invalid_byte: &mut u8,
5095    invalid_padding: &mut u8,
5096) -> &'a [u8] {
5097    if let Ok(tail) = read_tail(input, offset) {
5098        tail
5099    } else {
5100        debug_assert!(
5101            false,
5102            "read_tail failed inside length-validated constant-time decode loop"
5103        );
5104        *invalid_byte = 0xff;
5105        *invalid_padding = 0xff;
5106        &[]
5107    }
5108}
5109
5110#[inline(never)]
5111#[allow(unsafe_code)]
5112fn ct_decode_alphabet_byte<A: Alphabet>(byte: u8) -> (u8, u8) {
5113    let mut decoded = 0u8;
5114    let mut valid = 0u8;
5115    let mut candidate = 0u8;
5116
5117    while candidate < 64 {
5118        let matches = core::hint::black_box(ct_mask_eq_u8(
5119            core::hint::black_box(byte),
5120            core::hint::black_box(A::ENCODE[candidate as usize]),
5121        ));
5122        decoded = core::hint::black_box(
5123            core::hint::black_box(decoded) | core::hint::black_box(candidate & matches),
5124        );
5125        // SAFETY: `decoded` is an initialized local `u8`; the volatile read is
5126        // an optimizer barrier for the fixed 64-iteration alphabet scan and
5127        // does not access caller memory.
5128        decoded = unsafe { core::ptr::read_volatile(&raw const decoded) };
5129        valid =
5130            core::hint::black_box(core::hint::black_box(valid) | core::hint::black_box(matches));
5131        // SAFETY: `valid` is an initialized local `u8`; the volatile read is an
5132        // optimizer barrier for the fixed 64-iteration alphabet scan and does
5133        // not access caller memory.
5134        valid = unsafe { core::ptr::read_volatile(&raw const valid) };
5135        candidate += 1;
5136    }
5137
5138    (decoded, valid)
5139}
5140
5141fn ct_padding_len(input: &[u8]) -> usize {
5142    let Some((&last, before_last_prefix)) = input.split_last() else {
5143        return 0;
5144    };
5145    let Some(&before_last) = before_last_prefix.last() else {
5146        return 0;
5147    };
5148    usize::from(ct_mask_eq_u8(last, b'=') & 1) + usize::from(ct_mask_eq_u8(before_last, b'=') & 1)
5149}
5150
5151fn report_ct_error(invalid_byte: u8, invalid_padding: u8) -> Result<(), DecodeError> {
5152    ct_error_gate_barrier(invalid_byte, invalid_padding);
5153
5154    if (invalid_byte | invalid_padding) != 0 {
5155        Err(DecodeError::InvalidInput)
5156    } else {
5157        Ok(())
5158    }
5159}
5160
5161#[cfg(kani)]
5162mod kani_proofs {
5163    use super::{
5164        STANDARD, Standard, checked_encoded_len, ct, decode_byte, decode_chunk,
5165        decode_tail_unpadded, decoded_capacity, validate_tail_unpadded,
5166    };
5167
5168    #[kani::proof]
5169    fn checked_encoded_len_is_bounded_for_small_inputs() {
5170        let len = usize::from(kani::any::<u8>());
5171        let padded = kani::any::<bool>();
5172        let encoded = checked_encoded_len(len, padded).expect("u8 input length cannot overflow");
5173
5174        assert!(encoded >= len);
5175        assert!(encoded <= len / 3 * 4 + 4);
5176    }
5177
5178    #[kani::proof]
5179    fn decoded_capacity_is_bounded_for_small_inputs() {
5180        let len = usize::from(kani::any::<u8>());
5181        let capacity = decoded_capacity(len);
5182
5183        assert!(capacity <= len / 4 * 3 + 2);
5184    }
5185
5186    #[kani::proof]
5187    #[kani::unwind(3)]
5188    fn standard_in_place_decode_returns_prefix_within_buffer() {
5189        let mut buffer = kani::any::<[u8; 8]>();
5190        let result = STANDARD.decode_in_place(&mut buffer);
5191
5192        if let Ok(decoded) = result {
5193            assert!(decoded.len() <= 8);
5194        }
5195    }
5196
5197    #[kani::proof]
5198    #[kani::unwind(3)]
5199    fn standard_decode_slice_returns_written_within_output() {
5200        let input = kani::any::<[u8; 4]>();
5201        let mut output = kani::any::<[u8; 3]>();
5202        let result = STANDARD.decode_slice(&input, &mut output);
5203
5204        if let Ok(written) = result {
5205            assert!(written <= output.len());
5206        }
5207    }
5208
5209    #[kani::proof]
5210    #[kani::unwind(3)]
5211    fn standard_decode_chunk_returns_written_within_output() {
5212        let input = kani::any::<[u8; 4]>();
5213        let mut output = kani::any::<[u8; 3]>();
5214        let result = decode_chunk::<Standard, true>(input, &mut output);
5215
5216        if let Ok(written) = result {
5217            assert!(written <= output.len());
5218            assert!(written <= 3);
5219        }
5220    }
5221
5222    #[kani::proof]
5223    #[kani::unwind(3)]
5224    fn standard_decode_chunk_bit_packing_matches_decoded_values() {
5225        let input = kani::any::<[u8; 4]>();
5226        let mut output = kani::any::<[u8; 3]>();
5227        let result = decode_chunk::<Standard, true>(input, &mut output);
5228
5229        if let Ok(written) = result {
5230            let v0 = decode_byte::<Standard>(input[0], 0).expect("successful chunk has v0");
5231            let v1 = decode_byte::<Standard>(input[1], 1).expect("successful chunk has v1");
5232
5233            assert!(output[0] == ((v0 << 2) | (v1 >> 4)));
5234
5235            if written >= 2 {
5236                let v2 = decode_byte::<Standard>(input[2], 2).expect("successful chunk has v2");
5237                assert!(output[1] == ((v1 << 4) | (v2 >> 2)));
5238            }
5239
5240            if written == 3 {
5241                let v2 = decode_byte::<Standard>(input[2], 2).expect("successful chunk has v2");
5242                let v3 = decode_byte::<Standard>(input[3], 3).expect("successful chunk has v3");
5243                assert!(output[2] == ((v2 << 6) | v3));
5244            }
5245        }
5246    }
5247
5248    #[kani::proof]
5249    #[kani::unwind(3)]
5250    fn standard_validate_tail_unpadded_accepts_or_rejects_without_panic() {
5251        let input = kani::any::<[u8; 3]>();
5252        let len = usize::from(kani::any::<u8>() % 4);
5253        let result = validate_tail_unpadded::<Standard>(&input[..len]);
5254
5255        if result.is_ok() {
5256            assert!(len == 0 || len == 2 || len == 3);
5257        }
5258    }
5259
5260    #[kani::proof]
5261    #[kani::unwind(3)]
5262    fn standard_decode_two_byte_tail_returns_written_within_output() {
5263        let input = kani::any::<[u8; 2]>();
5264        let mut output = kani::any::<[u8; 1]>();
5265        let result = decode_tail_unpadded::<Standard>(&input, &mut output);
5266
5267        if let Ok(written) = result {
5268            assert!(written <= output.len());
5269            assert!(written == 1);
5270        }
5271    }
5272
5273    #[kani::proof]
5274    #[kani::unwind(3)]
5275    fn standard_decode_three_byte_tail_returns_written_within_output() {
5276        let input = kani::any::<[u8; 3]>();
5277        let mut output = kani::any::<[u8; 2]>();
5278        let result = decode_tail_unpadded::<Standard>(&input, &mut output);
5279
5280        if let Ok(written) = result {
5281            assert!(written <= output.len());
5282            assert!(written == 2);
5283        }
5284    }
5285
5286    #[kani::proof]
5287    #[kani::unwind(3)]
5288    fn standard_decode_slice_clear_tail_clears_output_on_error() {
5289        let input = kani::any::<[u8; 4]>();
5290        let mut output = kani::any::<[u8; 3]>();
5291        let result = STANDARD.decode_slice_clear_tail(&input, &mut output);
5292
5293        if result.is_err() {
5294            assert!(output.iter().all(|byte| *byte == 0));
5295        }
5296    }
5297
5298    #[kani::proof]
5299    #[kani::unwind(3)]
5300    fn standard_encode_slice_returns_written_within_output() {
5301        let input = kani::any::<[u8; 3]>();
5302        let mut output = kani::any::<[u8; 4]>();
5303        let result = STANDARD.encode_slice(&input, &mut output);
5304
5305        if let Ok(written) = result {
5306            assert!(written <= output.len());
5307        }
5308    }
5309
5310    #[kani::proof]
5311    #[kani::unwind(4)]
5312    fn standard_encode_in_place_returns_prefix_within_buffer() {
5313        let mut buffer = kani::any::<[u8; 8]>();
5314        let input_len = usize::from(kani::any::<u8>() % 9);
5315        let result = STANDARD.encode_in_place(&mut buffer, input_len);
5316
5317        if let Ok(encoded) = result {
5318            assert!(encoded.len() <= 8);
5319        }
5320    }
5321
5322    #[kani::proof]
5323    #[kani::unwind(3)]
5324    fn standard_clear_tail_decode_clears_buffer_on_error() {
5325        let mut buffer = kani::any::<[u8; 4]>();
5326        let result = STANDARD.decode_in_place_clear_tail(&mut buffer);
5327
5328        if result.is_err() {
5329            assert!(buffer.iter().all(|byte| *byte == 0));
5330        }
5331    }
5332
5333    #[kani::proof]
5334    #[kani::unwind(3)]
5335    fn ct_standard_decode_slice_returns_written_within_output() {
5336        let input = kani::any::<[u8; 4]>();
5337        let mut output = kani::any::<[u8; 3]>();
5338        let result = ct::STANDARD.decode_slice_clear_tail(&input, &mut output);
5339
5340        if let Ok(written) = result {
5341            assert!(written <= output.len());
5342        }
5343    }
5344
5345    #[kani::proof]
5346    #[kani::unwind(3)]
5347    fn ct_standard_decode_slice_clear_tail_clears_output_on_error() {
5348        let input = kani::any::<[u8; 4]>();
5349        let mut output = kani::any::<[u8; 3]>();
5350        let result = ct::STANDARD.decode_slice_clear_tail(&input, &mut output);
5351
5352        if result.is_err() {
5353            assert!(output.iter().all(|byte| *byte == 0));
5354        }
5355    }
5356
5357    #[kani::proof]
5358    #[kani::unwind(3)]
5359    fn ct_standard_decode_in_place_clear_tail_clears_buffer_on_error() {
5360        let mut buffer = kani::any::<[u8; 4]>();
5361        let result = ct::STANDARD.decode_in_place_clear_tail(&mut buffer);
5362
5363        if result.is_err() {
5364            assert!(buffer.iter().all(|byte| *byte == 0));
5365        }
5366    }
5367
5368    #[kani::proof]
5369    #[kani::unwind(3)]
5370    fn ct_standard_validate_matches_decode_for_one_quantum() {
5371        let input = kani::any::<[u8; 4]>();
5372        let mut output = kani::any::<[u8; 3]>();
5373
5374        let validate_ok = ct::STANDARD.validate_result(&input).is_ok();
5375        let decode_ok = ct::STANDARD
5376            .decode_slice_clear_tail(&input, &mut output)
5377            .is_ok();
5378
5379        assert!(validate_ok == decode_ok);
5380    }
5381}
5382
5383#[cfg(test)]
5384mod tests {
5385    use super::*;
5386
5387    fn fill_pattern(output: &mut [u8], seed: usize) {
5388        for (index, byte) in output.iter_mut().enumerate() {
5389            let value = (index * 73 + seed * 19) % 256;
5390            *byte = u8::try_from(value).unwrap();
5391        }
5392    }
5393
5394    fn assert_encode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
5395    where
5396        A: Alphabet,
5397    {
5398        let engine = Engine::<A, PAD>::new();
5399        let mut dispatched = [0x55; 256];
5400        let mut scalar = [0xaa; 256];
5401
5402        let dispatched_result = engine.encode_slice(input, &mut dispatched);
5403        let scalar_result = backend::scalar_reference_encode_slice::<A, PAD>(input, &mut scalar);
5404
5405        assert_eq!(dispatched_result, scalar_result);
5406        if let Ok(written) = dispatched_result {
5407            assert_eq!(&dispatched[..written], &scalar[..written]);
5408        }
5409
5410        let required = checked_encoded_len(input.len(), PAD).unwrap();
5411        if required > 0 {
5412            let mut dispatched_short = [0x55; 256];
5413            let mut scalar_short = [0xaa; 256];
5414            let available = required - 1;
5415
5416            assert_eq!(
5417                engine.encode_slice(input, &mut dispatched_short[..available]),
5418                backend::scalar_reference_encode_slice::<A, PAD>(
5419                    input,
5420                    &mut scalar_short[..available],
5421                )
5422            );
5423        }
5424    }
5425
5426    fn assert_decode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
5427    where
5428        A: Alphabet,
5429    {
5430        let engine = Engine::<A, PAD>::new();
5431        let mut dispatched = [0x55; 128];
5432        let mut scalar = [0xaa; 128];
5433
5434        let dispatched_result = engine.decode_slice(input, &mut dispatched);
5435        let scalar_result = backend::scalar_reference_decode_slice::<A, PAD>(input, &mut scalar);
5436
5437        assert_eq!(dispatched_result, scalar_result);
5438        if let Ok(written) = dispatched_result {
5439            assert_eq!(&dispatched[..written], &scalar[..written]);
5440
5441            if written > 0 {
5442                let mut dispatched_short = [0x55; 128];
5443                let mut scalar_short = [0xaa; 128];
5444                let available = written - 1;
5445
5446                assert_eq!(
5447                    engine.decode_slice(input, &mut dispatched_short[..available]),
5448                    backend::scalar_reference_decode_slice::<A, PAD>(
5449                        input,
5450                        &mut scalar_short[..available],
5451                    )
5452                );
5453            }
5454        }
5455    }
5456
5457    fn assert_backend_round_trip_matches_scalar<A, const PAD: bool>(input: &[u8])
5458    where
5459        A: Alphabet,
5460    {
5461        assert_encode_backend_matches_scalar::<A, PAD>(input);
5462
5463        let mut encoded = [0; 256];
5464        let encoded_len =
5465            backend::scalar_reference_encode_slice::<A, PAD>(input, &mut encoded).unwrap();
5466        assert_decode_backend_matches_scalar::<A, PAD>(&encoded[..encoded_len]);
5467    }
5468
5469    fn assert_standard_decode_chunk_matches_input(input: &[u8]) {
5470        let mut encoded = [0u8; 4];
5471        let encoded_len = STANDARD.encode_slice(input, &mut encoded).unwrap();
5472        assert_eq!(encoded_len, 4);
5473
5474        let chunk = [encoded[0], encoded[1], encoded[2], encoded[3]];
5475        let mut decoded = [0u8; 3];
5476        let decoded_len = decode_chunk::<Standard, true>(chunk, &mut decoded).unwrap();
5477
5478        assert_eq!(decoded_len, input.len());
5479        assert_eq!(&decoded[..decoded_len], input);
5480    }
5481
5482    #[test]
5483    fn backend_dispatch_matches_scalar_reference_for_canonical_inputs() {
5484        let mut input = [0; 128];
5485
5486        for input_len in 0..=input.len() {
5487            fill_pattern(&mut input[..input_len], input_len);
5488            let input = &input[..input_len];
5489
5490            assert_backend_round_trip_matches_scalar::<Standard, true>(input);
5491            assert_backend_round_trip_matches_scalar::<Standard, false>(input);
5492            assert_backend_round_trip_matches_scalar::<UrlSafe, true>(input);
5493            assert_backend_round_trip_matches_scalar::<UrlSafe, false>(input);
5494        }
5495    }
5496
5497    #[test]
5498    fn backend_dispatch_matches_scalar_reference_for_malformed_inputs() {
5499        for input in [
5500            &b"Z"[..],
5501            b"====",
5502            b"AA=A",
5503            b"Zh==",
5504            b"Zm9=",
5505            b"Zm9v$g==",
5506            b"Zm9vZh==",
5507        ] {
5508            assert_decode_backend_matches_scalar::<Standard, true>(input);
5509        }
5510
5511        for input in [&b"Z"[..], b"AA=A", b"Zh", b"Zm9", b"Zm9vYg$"] {
5512            assert_decode_backend_matches_scalar::<Standard, false>(input);
5513        }
5514
5515        assert_decode_backend_matches_scalar::<UrlSafe, true>(b"AA+A");
5516        assert_decode_backend_matches_scalar::<UrlSafe, false>(b"AA/A");
5517        assert_decode_backend_matches_scalar::<Standard, true>(b"AA-A");
5518        assert_decode_backend_matches_scalar::<Standard, false>(b"AA_A");
5519    }
5520
5521    #[test]
5522    fn decode_chunk_bit_packing_matches_exhaustive_small_inputs() {
5523        for byte in u8::MIN..=u8::MAX {
5524            assert_standard_decode_chunk_matches_input(&[byte]);
5525        }
5526
5527        for first in u8::MIN..=u8::MAX {
5528            for second in u8::MIN..=u8::MAX {
5529                assert_standard_decode_chunk_matches_input(&[first, second]);
5530            }
5531        }
5532    }
5533
5534    #[test]
5535    fn decode_chunk_bit_packing_matches_representative_full_quanta() {
5536        const SAMPLES: [u8; 16] = [
5537            0, 1, 2, 15, 16, 31, 32, 63, 64, 95, 127, 128, 191, 192, 254, 255,
5538        ];
5539
5540        for first in SAMPLES {
5541            for second in SAMPLES {
5542                for third in SAMPLES {
5543                    assert_standard_decode_chunk_matches_input(&[first, second, third]);
5544                }
5545            }
5546        }
5547    }
5548
5549    #[test]
5550    fn ct_padded_final_quantum_fails_closed_for_invalid_padding_count() {
5551        let (_, invalid_byte, invalid_padding, written) =
5552            ct_padded_final_quantum::<Standard>(*b"ABCD", 3);
5553
5554        assert_ne!(invalid_byte, 0);
5555        assert_ne!(invalid_padding, 0);
5556        assert_eq!(written, 0);
5557        assert_eq!(
5558            report_ct_error(invalid_byte, invalid_padding),
5559            Err(DecodeError::InvalidInput)
5560        );
5561    }
5562
5563    #[cfg(feature = "simd")]
5564    #[test]
5565    fn simd_dispatch_scaffold_keeps_scalar_active() {
5566        assert_eq!(simd::active_backend(), simd::ActiveBackend::Scalar);
5567        let _candidate = simd::detected_candidate();
5568    }
5569
5570    #[test]
5571    fn encodes_standard_vectors() {
5572        let vectors = [
5573            (&b""[..], &b""[..]),
5574            (&b"f"[..], &b"Zg=="[..]),
5575            (&b"fo"[..], &b"Zm8="[..]),
5576            (&b"foo"[..], &b"Zm9v"[..]),
5577            (&b"foob"[..], &b"Zm9vYg=="[..]),
5578            (&b"fooba"[..], &b"Zm9vYmE="[..]),
5579            (&b"foobar"[..], &b"Zm9vYmFy"[..]),
5580        ];
5581        for (input, expected) in vectors {
5582            let mut output = [0u8; 16];
5583            let written = STANDARD.encode_slice(input, &mut output).unwrap();
5584            assert_eq!(&output[..written], expected);
5585        }
5586    }
5587
5588    #[test]
5589    fn decodes_standard_vectors() {
5590        let vectors = [
5591            (&b""[..], &b""[..]),
5592            (&b"Zg=="[..], &b"f"[..]),
5593            (&b"Zm8="[..], &b"fo"[..]),
5594            (&b"Zm9v"[..], &b"foo"[..]),
5595            (&b"Zm9vYg=="[..], &b"foob"[..]),
5596            (&b"Zm9vYmE="[..], &b"fooba"[..]),
5597            (&b"Zm9vYmFy"[..], &b"foobar"[..]),
5598        ];
5599        for (input, expected) in vectors {
5600            let mut output = [0u8; 16];
5601            let written = STANDARD.decode_slice(input, &mut output).unwrap();
5602            assert_eq!(&output[..written], expected);
5603        }
5604    }
5605
5606    #[test]
5607    fn supports_unpadded_url_safe() {
5608        let mut encoded = [0u8; 16];
5609        let written = URL_SAFE_NO_PAD
5610            .encode_slice(b"\xfb\xff", &mut encoded)
5611            .unwrap();
5612        assert_eq!(&encoded[..written], b"-_8");
5613
5614        let mut decoded = [0u8; 2];
5615        let written = URL_SAFE_NO_PAD
5616            .decode_slice(&encoded[..written], &mut decoded)
5617            .unwrap();
5618        assert_eq!(&decoded[..written], b"\xfb\xff");
5619    }
5620
5621    #[test]
5622    fn decodes_in_place() {
5623        let mut buffer = *b"Zm9vYmFy";
5624        let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
5625        assert_eq!(decoded, b"foobar");
5626    }
5627
5628    #[test]
5629    fn rejects_non_canonical_padding_bits() {
5630        let mut output = [0u8; 4];
5631        assert_eq!(
5632            STANDARD.decode_slice(b"Zh==", &mut output),
5633            Err(DecodeError::InvalidPadding { index: 1 })
5634        );
5635        assert_eq!(
5636            STANDARD.decode_slice(b"Zm9=", &mut output),
5637            Err(DecodeError::InvalidPadding { index: 2 })
5638        );
5639    }
5640}