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#[cfg(feature = "alloc")]
47extern crate alloc;
48
49#[cfg(feature = "simd")]
50mod simd;
51
52/// Runtime backend reporting for security-sensitive deployments.
53///
54/// This module does not enable acceleration. It exposes the backend posture so
55/// callers can log, assert, or audit whether execution is scalar-only or merely
56/// detecting future SIMD candidates.
57pub mod runtime {
58    /// A backend that can be reported by `base64-ng`.
59    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
60    #[non_exhaustive]
61    pub enum Backend {
62        /// The audited scalar backend.
63        Scalar,
64        /// An AVX-512 VBMI candidate was detected.
65        Avx512Vbmi,
66        /// An AVX2 candidate was detected.
67        Avx2,
68        /// An SSSE3/SSE4.1 candidate was detected.
69        Ssse3Sse41,
70        /// An ARM NEON candidate was detected.
71        Neon,
72        /// A wasm `simd128` candidate was detected.
73        WasmSimd128,
74    }
75
76    impl Backend {
77        /// Returns the stable lowercase identifier for this backend.
78        ///
79        /// ```
80        /// assert_eq!(base64_ng::runtime::Backend::Scalar.as_str(), "scalar");
81        /// ```
82        #[must_use]
83        pub const fn as_str(self) -> &'static str {
84            match self {
85                Self::Scalar => "scalar",
86                Self::Avx512Vbmi => "avx512-vbmi",
87                Self::Avx2 => "avx2",
88                Self::Ssse3Sse41 => "ssse3-sse4.1",
89                Self::Neon => "neon",
90                Self::WasmSimd128 => "wasm-simd128",
91            }
92        }
93
94        /// Returns the CPU features required before this backend may be used.
95        ///
96        /// The active backend is still scalar-only. This method exists so
97        /// security logs can record exactly which future backend feature bundle
98        /// was detected.
99        ///
100        /// ```
101        /// assert_eq!(
102        ///     base64_ng::runtime::Backend::Avx512Vbmi.required_cpu_features(),
103        ///     ["avx512f", "avx512bw", "avx512vl", "avx512vbmi"],
104        /// );
105        /// ```
106        #[must_use]
107        pub const fn required_cpu_features(self) -> &'static [&'static str] {
108            match self {
109                Self::Scalar => &[],
110                Self::Avx512Vbmi => &["avx512f", "avx512bw", "avx512vl", "avx512vbmi"],
111                Self::Avx2 => &["avx2"],
112                Self::Ssse3Sse41 => &["ssse3", "sse4.1"],
113                Self::Neon => &["neon"],
114                Self::WasmSimd128 => &["simd128"],
115            }
116        }
117    }
118
119    impl core::fmt::Display for Backend {
120        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
121            formatter.write_str(self.as_str())
122        }
123    }
124
125    /// Security posture for the active runtime backend.
126    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
127    #[non_exhaustive]
128    pub enum SecurityPosture {
129        /// No accelerated backend is active.
130        ScalarOnly,
131        /// SIMD support may be detected, but execution still uses scalar.
132        SimdCandidateScalarActive,
133        /// A SIMD backend is active.
134        Accelerated,
135    }
136
137    impl SecurityPosture {
138        /// Returns the stable lowercase identifier for this security posture.
139        ///
140        /// ```
141        /// assert_eq!(
142        ///     base64_ng::runtime::SecurityPosture::ScalarOnly.as_str(),
143        ///     "scalar-only",
144        /// );
145        /// ```
146        #[must_use]
147        pub const fn as_str(self) -> &'static str {
148            match self {
149                Self::ScalarOnly => "scalar-only",
150                Self::SimdCandidateScalarActive => "simd-candidate-scalar-active",
151                Self::Accelerated => "accelerated",
152            }
153        }
154    }
155
156    impl core::fmt::Display for SecurityPosture {
157        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
158            formatter.write_str(self.as_str())
159        }
160    }
161
162    /// Deployment policy for runtime backend assertions.
163    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
164    #[non_exhaustive]
165    pub enum BackendPolicy {
166        /// Require encode/decode execution to use the scalar backend.
167        ScalarExecutionOnly,
168        /// Require the crate to be built without the `simd` feature.
169        SimdFeatureDisabled,
170        /// Require no SIMD candidate to be visible to this build and target.
171        NoDetectedSimdCandidate,
172        /// Require scalar execution, the `simd` feature disabled, no detected
173        /// SIMD candidate, and the unsafe boundary enforced.
174        HighAssuranceScalarOnly,
175    }
176
177    impl BackendPolicy {
178        /// Returns the stable lowercase identifier for this policy.
179        ///
180        /// ```
181        /// assert_eq!(
182        ///     base64_ng::runtime::BackendPolicy::HighAssuranceScalarOnly.as_str(),
183        ///     "high-assurance-scalar-only",
184        /// );
185        /// ```
186        #[must_use]
187        pub const fn as_str(self) -> &'static str {
188            match self {
189                Self::ScalarExecutionOnly => "scalar-execution-only",
190                Self::SimdFeatureDisabled => "simd-feature-disabled",
191                Self::NoDetectedSimdCandidate => "no-detected-simd-candidate",
192                Self::HighAssuranceScalarOnly => "high-assurance-scalar-only",
193            }
194        }
195    }
196
197    impl core::fmt::Display for BackendPolicy {
198        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
199            formatter.write_str(self.as_str())
200        }
201    }
202
203    /// Runtime backend policy failure.
204    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
205    pub struct BackendPolicyError {
206        /// Policy that was requested.
207        pub policy: BackendPolicy,
208        /// Backend report observed when the policy failed.
209        pub report: BackendReport,
210    }
211
212    impl core::fmt::Display for BackendPolicyError {
213        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
214            write!(
215                formatter,
216                "runtime backend policy `{}` was not satisfied ({})",
217                self.policy, self.report,
218            )
219        }
220    }
221
222    #[cfg(feature = "std")]
223    impl std::error::Error for BackendPolicyError {}
224
225    /// Backend report for the current build and target.
226    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
227    pub struct BackendReport {
228        /// Backend currently used for encode/decode dispatch.
229        pub active: Backend,
230        /// Strongest backend candidate visible to the current build.
231        pub candidate: Backend,
232        /// Whether the `simd` feature is enabled in this build.
233        pub simd_feature_enabled: bool,
234        /// Whether an accelerated SIMD backend is active.
235        pub accelerated_backend_active: bool,
236        /// Whether unsafe code is confined to the dedicated SIMD boundary.
237        pub unsafe_boundary_enforced: bool,
238        /// Current security posture.
239        pub security_posture: SecurityPosture,
240    }
241
242    /// Compact structured backend snapshot for logging and policy evidence.
243    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
244    pub struct BackendSnapshot {
245        /// Stable active backend identifier.
246        pub active: &'static str,
247        /// Stable detected candidate identifier.
248        pub candidate: &'static str,
249        /// CPU features required by the detected candidate.
250        pub candidate_required_cpu_features: &'static [&'static str],
251        /// Whether the `simd` feature is enabled in this build.
252        pub simd_feature_enabled: bool,
253        /// Whether an accelerated SIMD backend is active.
254        pub accelerated_backend_active: bool,
255        /// Whether unsafe code is confined to the dedicated SIMD boundary.
256        pub unsafe_boundary_enforced: bool,
257        /// Stable security posture identifier.
258        pub security_posture: &'static str,
259    }
260
261    impl core::fmt::Display for BackendReport {
262        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
263            write!(
264                formatter,
265                "active={} candidate={} candidate_required_cpu_features=",
266                self.active, self.candidate,
267            )?;
268            write_feature_list(formatter, self.candidate_required_cpu_features())?;
269            write!(
270                formatter,
271                " simd_feature_enabled={} accelerated_backend_active={} unsafe_boundary_enforced={} security_posture={}",
272                self.simd_feature_enabled,
273                self.accelerated_backend_active,
274                self.unsafe_boundary_enforced,
275                self.security_posture,
276            )
277        }
278    }
279
280    impl BackendReport {
281        /// Returns whether this report satisfies `policy`.
282        ///
283        /// ```
284        /// let report = base64_ng::runtime::backend_report();
285        ///
286        /// assert!(
287        ///     report.satisfies(base64_ng::runtime::BackendPolicy::ScalarExecutionOnly)
288        /// );
289        /// ```
290        #[must_use]
291        pub const fn satisfies(self, policy: BackendPolicy) -> bool {
292            match policy {
293                BackendPolicy::ScalarExecutionOnly => {
294                    matches!(self.active, Backend::Scalar) && !self.accelerated_backend_active
295                }
296                BackendPolicy::SimdFeatureDisabled => !self.simd_feature_enabled,
297                BackendPolicy::NoDetectedSimdCandidate => matches!(self.candidate, Backend::Scalar),
298                BackendPolicy::HighAssuranceScalarOnly => {
299                    matches!(self.active, Backend::Scalar)
300                        && matches!(self.candidate, Backend::Scalar)
301                        && !self.simd_feature_enabled
302                        && !self.accelerated_backend_active
303                        && self.unsafe_boundary_enforced
304                }
305            }
306        }
307
308        /// Returns the CPU features required by the detected candidate.
309        ///
310        /// ```
311        /// let report = base64_ng::runtime::backend_report();
312        ///
313        /// assert_eq!(
314        ///     report.candidate_required_cpu_features(),
315        ///     report.candidate.required_cpu_features(),
316        /// );
317        /// ```
318        #[must_use]
319        pub const fn candidate_required_cpu_features(self) -> &'static [&'static str] {
320            self.candidate.required_cpu_features()
321        }
322
323        /// Returns a compact structured snapshot with stable string values.
324        ///
325        /// ```
326        /// let snapshot = base64_ng::runtime::backend_report().snapshot();
327        ///
328        /// assert_eq!(snapshot.active, "scalar");
329        /// assert!(!snapshot.accelerated_backend_active);
330        /// ```
331        #[must_use]
332        pub const fn snapshot(self) -> BackendSnapshot {
333            BackendSnapshot {
334                active: self.active.as_str(),
335                candidate: self.candidate.as_str(),
336                candidate_required_cpu_features: self.candidate_required_cpu_features(),
337                simd_feature_enabled: self.simd_feature_enabled,
338                accelerated_backend_active: self.accelerated_backend_active,
339                unsafe_boundary_enforced: self.unsafe_boundary_enforced,
340                security_posture: self.security_posture.as_str(),
341            }
342        }
343    }
344
345    /// Returns the runtime backend report for this build and target.
346    ///
347    /// ```
348    /// let report = base64_ng::runtime::backend_report();
349    ///
350    /// assert_eq!(report.active, base64_ng::runtime::Backend::Scalar);
351    /// assert!(!report.accelerated_backend_active);
352    /// ```
353    #[must_use]
354    pub fn backend_report() -> BackendReport {
355        let active = active_backend();
356        let candidate = detected_candidate();
357        let accelerated_backend_active = active != Backend::Scalar;
358        let security_posture = if accelerated_backend_active {
359            SecurityPosture::Accelerated
360        } else if candidate != Backend::Scalar {
361            SecurityPosture::SimdCandidateScalarActive
362        } else {
363            SecurityPosture::ScalarOnly
364        };
365
366        BackendReport {
367            active,
368            candidate,
369            simd_feature_enabled: cfg!(feature = "simd"),
370            accelerated_backend_active,
371            unsafe_boundary_enforced: true,
372            security_posture,
373        }
374    }
375
376    /// Requires the current runtime backend report to satisfy `policy`.
377    ///
378    /// ```
379    /// base64_ng::runtime::require_backend_policy(
380    ///     base64_ng::runtime::BackendPolicy::ScalarExecutionOnly,
381    /// )
382    /// .unwrap();
383    /// ```
384    pub fn require_backend_policy(policy: BackendPolicy) -> Result<(), BackendPolicyError> {
385        let report = backend_report();
386        if report.satisfies(policy) {
387            Ok(())
388        } else {
389            Err(BackendPolicyError { policy, report })
390        }
391    }
392
393    fn write_feature_list(
394        formatter: &mut core::fmt::Formatter<'_>,
395        features: &[&str],
396    ) -> core::fmt::Result {
397        formatter.write_str("[")?;
398        let mut index = 0;
399        while index < features.len() {
400            if index != 0 {
401                formatter.write_str(",")?;
402            }
403            formatter.write_str(features[index])?;
404            index += 1;
405        }
406        formatter.write_str("]")
407    }
408
409    #[cfg(feature = "simd")]
410    fn active_backend() -> Backend {
411        match super::simd::active_backend() {
412            super::simd::ActiveBackend::Scalar => Backend::Scalar,
413        }
414    }
415
416    #[cfg(not(feature = "simd"))]
417    const fn active_backend() -> Backend {
418        Backend::Scalar
419    }
420
421    #[cfg(feature = "simd")]
422    fn detected_candidate() -> Backend {
423        match super::simd::detected_candidate() {
424            super::simd::Candidate::Scalar => Backend::Scalar,
425            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
426            super::simd::Candidate::Avx512Vbmi => Backend::Avx512Vbmi,
427            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
428            super::simd::Candidate::Avx2 => Backend::Avx2,
429            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
430            super::simd::Candidate::Ssse3Sse41 => Backend::Ssse3Sse41,
431            #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
432            super::simd::Candidate::Neon => Backend::Neon,
433            #[cfg(target_arch = "wasm32")]
434            super::simd::Candidate::WasmSimd128 => Backend::WasmSimd128,
435        }
436    }
437
438    #[cfg(not(feature = "simd"))]
439    const fn detected_candidate() -> Backend {
440        Backend::Scalar
441    }
442}
443
444#[cfg(feature = "stream")]
445pub mod stream {
446    //! Streaming Base64 wrappers for `std::io`.
447    //!
448    //! Decoder adapters fail closed after malformed Base64 input. Use
449    //! `is_failed()` for diagnostics; unchecked `into_inner()` remains
450    //! available when the wrapped reader or writer must be explicitly
451    //! recovered after a decode error.
452    //!
453    //! ```
454    //! use std::io::{Read, Write};
455    //! use base64_ng::{STANDARD, stream::{Decoder, DecoderReader, Encoder, EncoderReader}};
456    //!
457    //! let mut encoder = Encoder::new(Vec::new(), STANDARD);
458    //! encoder.write_all(b"he").unwrap();
459    //! encoder.write_all(b"llo").unwrap();
460    //! let encoded = encoder.finish().unwrap();
461    //! assert_eq!(encoded, b"aGVsbG8=");
462    //!
463    //! let mut reader = EncoderReader::new(&b"hello"[..], STANDARD);
464    //! let mut encoded = String::new();
465    //! reader.read_to_string(&mut encoded).unwrap();
466    //! assert_eq!(encoded, "aGVsbG8=");
467    //!
468    //! let mut decoder = Decoder::new(Vec::new(), STANDARD);
469    //! decoder.write_all(b"aGVs").unwrap();
470    //! decoder.write_all(b"bG8=").unwrap();
471    //! let decoded = decoder.finish().unwrap();
472    //! assert_eq!(decoded, b"hello");
473    //!
474    //! let mut reader = DecoderReader::new(&b"aGVsbG8="[..], STANDARD);
475    //! let mut decoded = Vec::new();
476    //! reader.read_to_end(&mut decoded).unwrap();
477    //! assert_eq!(decoded, b"hello");
478    //! ```
479
480    use super::{Alphabet, DecodeError, EncodeError, Engine};
481    use std::io::{self, Read, Write};
482
483    struct OutputQueue<const CAP: usize> {
484        buffer: [u8; CAP],
485        start: usize,
486        len: usize,
487    }
488
489    impl<const CAP: usize> OutputQueue<CAP> {
490        const fn new() -> Self {
491            Self {
492                buffer: [0; CAP],
493                start: 0,
494                len: 0,
495            }
496        }
497
498        const fn is_empty(&self) -> bool {
499            self.len == 0
500        }
501
502        const fn len(&self) -> usize {
503            self.len
504        }
505
506        const fn capacity(&self) -> usize {
507            self.len + self.available_capacity()
508        }
509
510        fn push_slice(&mut self, input: &[u8]) -> io::Result<()> {
511            if input.len() > self.available_capacity() {
512                return Err(io::Error::other(
513                    "base64 stream output queue capacity exceeded",
514                ));
515            }
516
517            let mut read = 0;
518            while read < input.len() {
519                let write = (self.start + self.len) % CAP;
520                self.buffer[write] = input[read];
521                self.len += 1;
522                read += 1;
523            }
524
525            Ok(())
526        }
527
528        fn copy_front(&self, output: &mut [u8]) -> usize {
529            let count = core::cmp::min(self.len, output.len());
530            let first = core::cmp::min(count, CAP - self.start);
531            output[..first].copy_from_slice(&self.buffer[self.start..self.start + first]);
532
533            let second = count - first;
534            if second > 0 {
535                output[first..first + second].copy_from_slice(&self.buffer[..second]);
536            }
537
538            count
539        }
540
541        fn discard_front(&mut self, count: usize) {
542            let count = core::cmp::min(count, self.len);
543            let first = core::cmp::min(count, CAP - self.start);
544            crate::wipe_bytes(&mut self.buffer[self.start..self.start + first]);
545
546            let second = count - first;
547            if second > 0 {
548                crate::wipe_bytes(&mut self.buffer[..second]);
549            }
550
551            self.start = (self.start + count) % CAP;
552            self.len -= count;
553            if self.len == 0 {
554                self.start = 0;
555            }
556        }
557
558        fn pop_slice(&mut self, output: &mut [u8]) -> usize {
559            let count = self.copy_front(output);
560            self.discard_front(count);
561            count
562        }
563
564        fn clear_all(&mut self) {
565            crate::wipe_bytes(&mut self.buffer);
566            self.start = 0;
567            self.len = 0;
568        }
569
570        const fn available_capacity(&self) -> usize {
571            CAP - self.len
572        }
573    }
574
575    /// A streaming Base64 encoder for `std::io::Write`.
576    ///
577    /// Like any [`Write`] implementation, [`Write::write`] may accept only
578    /// part of the provided input. Accepted input may be held as encoded
579    /// output until [`Write::flush`], [`Self::try_finish`], [`Self::finish`],
580    /// or a later write drains the wrapped writer. Use [`Write::write_all`]
581    /// when the whole input slice must be consumed.
582    pub struct Encoder<W, A, const PAD: bool>
583    where
584        A: Alphabet,
585    {
586        inner: Option<W>,
587        engine: Engine<A, PAD>,
588        pending: [u8; 2],
589        pending_len: usize,
590        output: OutputQueue<1024>,
591        finalized: bool,
592    }
593
594    impl<W, A, const PAD: bool> Encoder<W, A, PAD>
595    where
596        A: Alphabet,
597    {
598        /// Creates a new streaming encoder.
599        #[must_use]
600        pub const fn new(inner: W, engine: Engine<A, PAD>) -> Self {
601            Self {
602                inner: Some(inner),
603                engine,
604                pending: [0; 2],
605                pending_len: 0,
606                output: OutputQueue::new(),
607                finalized: false,
608            }
609        }
610
611        /// Returns a shared reference to the wrapped writer.
612        #[must_use]
613        pub fn get_ref(&self) -> &W {
614            self.inner_ref()
615        }
616
617        /// Returns a mutable reference to the wrapped writer.
618        pub fn get_mut(&mut self) -> &mut W {
619            self.inner_mut()
620        }
621
622        /// Returns the Base64 engine used by this adapter.
623        #[must_use]
624        pub const fn engine(&self) -> Engine<A, PAD> {
625            self.engine
626        }
627
628        /// Returns whether this adapter uses padded Base64.
629        #[must_use]
630        pub const fn is_padded(&self) -> bool {
631            PAD
632        }
633
634        /// Returns the number of raw input bytes currently buffered until a
635        /// complete 3-byte Base64 encode quantum is available.
636        #[must_use]
637        pub const fn pending_len(&self) -> usize {
638            self.pending_len
639        }
640
641        /// Returns whether this encoder currently holds a partial input
642        /// quantum.
643        #[must_use]
644        pub const fn has_pending_input(&self) -> bool {
645            self.pending_len != 0
646        }
647
648        /// Returns how many additional input bytes are needed to complete the
649        /// currently buffered encode quantum.
650        ///
651        /// Returns `0` when no partial input quantum is buffered.
652        #[must_use]
653        pub const fn pending_input_needed_len(&self) -> usize {
654            if self.has_pending_input() {
655                3 - self.pending_len
656            } else {
657                0
658            }
659        }
660
661        /// Returns the number of encoded bytes buffered for the wrapped
662        /// writer after a previous write or flush could not fully drain them.
663        #[must_use]
664        pub const fn buffered_output_len(&self) -> usize {
665            self.output.len()
666        }
667
668        /// Returns the maximum number of encoded bytes this adapter can buffer
669        /// before returning bytes to the caller.
670        #[must_use]
671        pub const fn buffered_output_capacity(&self) -> usize {
672            self.output.capacity()
673        }
674
675        /// Returns how many more encoded bytes can be buffered before this
676        /// adapter must drain the wrapped writer.
677        #[must_use]
678        pub const fn buffered_output_remaining_capacity(&self) -> usize {
679            self.output.available_capacity()
680        }
681
682        /// Returns whether this encoder has encoded output waiting to be
683        /// written to the wrapped writer.
684        #[must_use]
685        pub const fn has_buffered_output(&self) -> bool {
686            !self.output.is_empty()
687        }
688
689        /// Returns whether this encoder has been finalized.
690        ///
691        /// Once this returns `true`, later non-empty writes return an error.
692        #[must_use]
693        pub const fn is_finalized(&self) -> bool {
694            self.finalized
695        }
696
697        /// Returns whether [`Self::try_into_inner`] can recover the wrapped
698        /// writer without discarding pending input.
699        #[must_use]
700        pub const fn can_into_inner(&self) -> bool {
701            !self.has_pending_input() && !self.has_buffered_output()
702        }
703
704        /// Consumes the encoder without flushing pending input.
705        ///
706        /// Prefer [`Self::finish`] when the encoded output must be complete.
707        #[must_use]
708        pub fn into_inner(mut self) -> W {
709            self.take_inner()
710        }
711
712        /// Consumes the encoder only when no partial input quantum is buffered.
713        ///
714        /// This does not flush or finalize the wrapped writer. It is a checked
715        /// alternative to [`Self::into_inner`] for callers that want to avoid
716        /// accidentally discarding pending input bytes.
717        #[allow(clippy::result_large_err)]
718        pub fn try_into_inner(mut self) -> Result<W, Self> {
719            if !self.can_into_inner() {
720                return Err(self);
721            }
722            Ok(self.take_inner())
723        }
724
725        fn inner_ref(&self) -> &W {
726            match &self.inner {
727                Some(inner) => inner,
728                None => unreachable!("stream encoder inner writer was already taken"),
729            }
730        }
731
732        fn inner_mut(&mut self) -> &mut W {
733            match &mut self.inner {
734                Some(inner) => inner,
735                None => unreachable!("stream encoder inner writer was already taken"),
736            }
737        }
738
739        fn take_inner(&mut self) -> W {
740            match self.inner.take() {
741                Some(inner) => inner,
742                None => unreachable!("stream encoder inner writer was already taken"),
743            }
744        }
745
746        fn clear_pending(&mut self) {
747            crate::wipe_bytes(&mut self.pending);
748            self.pending_len = 0;
749        }
750
751        fn clear_output(&mut self) {
752            self.output.clear_all();
753        }
754    }
755
756    impl<W, A, const PAD: bool> Drop for Encoder<W, A, PAD>
757    where
758        A: Alphabet,
759    {
760        fn drop(&mut self) {
761            self.clear_pending();
762            self.clear_output();
763        }
764    }
765
766    impl<W, A, const PAD: bool> core::fmt::Debug for Encoder<W, A, PAD>
767    where
768        A: Alphabet,
769    {
770        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
771            formatter
772                .debug_struct("Encoder")
773                .field("inner", &redacted_inner_state(self.inner.is_some()))
774                .field("engine", &self.engine)
775                .field("pending", &"<redacted>")
776                .field("pending_len", &self.pending_len)
777                .field("pending_input_needed_len", &self.pending_input_needed_len())
778                .field("buffered_output_len", &self.output.len())
779                .field("buffered_output_capacity", &self.output.capacity())
780                .field(
781                    "buffered_output_remaining_capacity",
782                    &self.output.available_capacity(),
783                )
784                .field("can_into_inner", &self.can_into_inner())
785                .field("finalized", &self.finalized)
786                .finish()
787        }
788    }
789
790    impl<W, A, const PAD: bool> Encoder<W, A, PAD>
791    where
792        W: Write,
793        A: Alphabet,
794    {
795        /// Writes any pending input and flushes the wrapped writer without
796        /// consuming this encoder.
797        ///
798        /// After this succeeds, [`Self::pending_len`] returns `0`, later
799        /// writes are rejected, and [`Self::finish`] can still be used to
800        /// recover the wrapped writer.
801        /// This is useful when a caller needs to finalize a framed payload
802        /// while keeping the stream adapter available for diagnostics or
803        /// explicit recovery.
804        pub fn try_finish(&mut self) -> io::Result<()> {
805            if !self.finalized {
806                self.queue_pending_final()?;
807                self.finalized = true;
808            }
809            self.flush()
810        }
811
812        /// Writes any pending input, flushes the wrapped writer, and returns it.
813        pub fn finish(mut self) -> io::Result<W> {
814            self.try_finish()?;
815            Ok(self.take_inner())
816        }
817
818        fn queue_pending_final(&mut self) -> io::Result<()> {
819            if self.pending_len == 0 {
820                return Ok(());
821            }
822
823            let mut pending = [0u8; 2];
824            pending[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
825            let pending_len = self.pending_len;
826            let mut encoded = [0u8; 4];
827            let result = self.queue_encoded_temp(&pending[..pending_len], &mut encoded);
828            crate::wipe_bytes(&mut pending);
829            result?;
830            self.clear_pending();
831            Ok(())
832        }
833
834        fn queue_encoded_temp(&mut self, input: &[u8], encoded: &mut [u8]) -> io::Result<()> {
835            let written = match self.engine.encode_slice(input, encoded) {
836                Ok(written) => written,
837                Err(err) => {
838                    crate::wipe_bytes(encoded);
839                    return Err(encode_error_to_io(err));
840                }
841            };
842
843            let result = self.output.push_slice(&encoded[..written]);
844            crate::wipe_bytes(encoded);
845            result
846        }
847
848        fn drain_output(&mut self) -> io::Result<()> {
849            let mut chunk = [0u8; 1024];
850            while !self.output.is_empty() {
851                let pending = self.output.copy_front(&mut chunk);
852                let result = self.inner_mut().write(&chunk[..pending]);
853                crate::wipe_bytes(&mut chunk[..pending]);
854                match result {
855                    Ok(0) => {
856                        return Err(io::Error::new(
857                            io::ErrorKind::WriteZero,
858                            "base64 stream encoder could not drain buffered output",
859                        ));
860                    }
861                    Ok(written) => {
862                        if written > pending {
863                            return Err(io::Error::new(
864                                io::ErrorKind::InvalidData,
865                                "wrapped writer reported more bytes than provided",
866                            ));
867                        }
868                        self.output.discard_front(written);
869                    }
870                    Err(err) => return Err(err),
871                }
872            }
873
874            Ok(())
875        }
876    }
877
878    impl<W, A, const PAD: bool> Write for Encoder<W, A, PAD>
879    where
880        W: Write,
881        A: Alphabet,
882    {
883        fn write(&mut self, input: &[u8]) -> io::Result<usize> {
884            if input.is_empty() {
885                self.drain_output()?;
886                return Ok(0);
887            }
888            self.drain_output()?;
889            if self.finalized {
890                return Err(io::Error::new(
891                    io::ErrorKind::InvalidInput,
892                    "base64 stream encoder received input after finalization",
893                ));
894            }
895
896            let mut consumed = 0;
897            if self.pending_len > 0 {
898                let needed = 3 - self.pending_len;
899                if input.len() < needed {
900                    self.pending[self.pending_len..self.pending_len + input.len()]
901                        .copy_from_slice(input);
902                    self.pending_len += input.len();
903                    return Ok(input.len());
904                }
905
906                let mut chunk = [0u8; 3];
907                chunk[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
908                chunk[self.pending_len..].copy_from_slice(&input[..needed]);
909
910                let mut encoded = [0u8; 4];
911                let result = self.queue_encoded_temp(&chunk, &mut encoded);
912                crate::wipe_bytes(&mut chunk);
913                result?;
914                self.clear_pending();
915                consumed += needed;
916                return Ok(consumed);
917            }
918
919            let remaining = &input[consumed..];
920            let full_len = remaining.len() / 3 * 3;
921            if full_len > 0 {
922                let mut take = core::cmp::min(full_len, 768);
923                take -= take % 3;
924                debug_assert!(take > 0);
925
926                let mut encoded = [0u8; 1024];
927                self.queue_encoded_temp(&remaining[..take], &mut encoded)?;
928                return Ok(consumed + take);
929            }
930
931            let tail = &remaining[full_len..];
932            self.pending[..tail.len()].copy_from_slice(tail);
933            self.pending_len = tail.len();
934
935            Ok(input.len())
936        }
937
938        fn flush(&mut self) -> io::Result<()> {
939            self.drain_output()?;
940            self.inner_mut().flush()
941        }
942    }
943
944    fn encode_error_to_io(err: EncodeError) -> io::Error {
945        io::Error::new(io::ErrorKind::InvalidInput, err)
946    }
947
948    /// A streaming Base64 decoder for `std::io::Write`.
949    ///
950    /// Like any [`Write`] implementation, [`Write::write`] may accept only
951    /// part of the provided input. Accepted input may be held as decoded
952    /// output until [`Write::flush`], [`Self::try_finish`], [`Self::finish`],
953    /// or a later write drains the wrapped writer. Use [`Write::write_all`]
954    /// when the whole input slice must be consumed.
955    pub struct Decoder<W, A, const PAD: bool>
956    where
957        A: Alphabet,
958    {
959        inner: Option<W>,
960        engine: Engine<A, PAD>,
961        pending: [u8; 4],
962        pending_len: usize,
963        output: OutputQueue<1024>,
964        finished: bool,
965        finalized: bool,
966        failed: bool,
967    }
968
969    impl<W, A, const PAD: bool> Decoder<W, A, PAD>
970    where
971        A: Alphabet,
972    {
973        /// Creates a new streaming decoder.
974        #[must_use]
975        pub const fn new(inner: W, engine: Engine<A, PAD>) -> Self {
976            Self {
977                inner: Some(inner),
978                engine,
979                pending: [0; 4],
980                pending_len: 0,
981                output: OutputQueue::new(),
982                finished: false,
983                finalized: false,
984                failed: false,
985            }
986        }
987
988        /// Returns a shared reference to the wrapped writer.
989        #[must_use]
990        pub fn get_ref(&self) -> &W {
991            self.inner_ref()
992        }
993
994        /// Returns a mutable reference to the wrapped writer.
995        pub fn get_mut(&mut self) -> &mut W {
996            self.inner_mut()
997        }
998
999        /// Returns the Base64 engine used by this adapter.
1000        #[must_use]
1001        pub const fn engine(&self) -> Engine<A, PAD> {
1002            self.engine
1003        }
1004
1005        /// Returns whether this adapter uses padded Base64.
1006        #[must_use]
1007        pub const fn is_padded(&self) -> bool {
1008            PAD
1009        }
1010
1011        /// Returns the number of encoded input bytes currently buffered until
1012        /// a complete 4-byte Base64 decode quantum is available.
1013        #[must_use]
1014        pub const fn pending_len(&self) -> usize {
1015            self.pending_len
1016        }
1017
1018        /// Returns whether this decoder currently holds a partial input
1019        /// quantum.
1020        #[must_use]
1021        pub const fn has_pending_input(&self) -> bool {
1022            self.pending_len != 0
1023        }
1024
1025        /// Returns how many additional input bytes are needed to complete the
1026        /// currently buffered decode quantum.
1027        ///
1028        /// Returns `0` when no partial input quantum is buffered.
1029        #[must_use]
1030        pub const fn pending_input_needed_len(&self) -> usize {
1031            if self.has_pending_input() {
1032                4 - self.pending_len
1033            } else {
1034                0
1035            }
1036        }
1037
1038        /// Returns the number of decoded bytes buffered for the wrapped writer
1039        /// after a previous write or flush could not fully drain them.
1040        #[must_use]
1041        pub const fn buffered_output_len(&self) -> usize {
1042            self.output.len()
1043        }
1044
1045        /// Returns the maximum number of decoded bytes this adapter can buffer
1046        /// before returning bytes to the caller.
1047        #[must_use]
1048        pub const fn buffered_output_capacity(&self) -> usize {
1049            self.output.capacity()
1050        }
1051
1052        /// Returns how many more decoded bytes can be buffered before this
1053        /// adapter must drain the wrapped writer.
1054        #[must_use]
1055        pub const fn buffered_output_remaining_capacity(&self) -> usize {
1056            self.output.available_capacity()
1057        }
1058
1059        /// Returns whether this decoder has decoded output waiting to be
1060        /// written to the wrapped writer.
1061        #[must_use]
1062        pub const fn has_buffered_output(&self) -> bool {
1063            !self.output.is_empty()
1064        }
1065
1066        /// Returns whether this decoder has processed a terminal padded block.
1067        ///
1068        /// Once this returns `true`, later calls to [`Write::write`] with
1069        /// additional input return an error because strict Base64 does not
1070        /// permit trailing payload bytes after padding.
1071        #[must_use]
1072        pub const fn has_terminal_padding(&self) -> bool {
1073            self.finished
1074        }
1075
1076        /// Returns whether this decoder has been finalized.
1077        ///
1078        /// Once this returns `true`, later non-empty writes return an error.
1079        #[must_use]
1080        pub const fn is_finalized(&self) -> bool {
1081            self.finalized
1082        }
1083
1084        /// Returns whether this decoder has rejected malformed Base64 input.
1085        ///
1086        /// Once this returns `true`, later writes, flushes, and finalization
1087        /// attempts return an error. The unchecked [`Self::into_inner`] method
1088        /// can still be used for explicit recovery of the wrapped writer.
1089        #[must_use]
1090        pub const fn is_failed(&self) -> bool {
1091            self.failed
1092        }
1093
1094        /// Returns whether [`Self::try_into_inner`] can recover the wrapped
1095        /// writer without discarding pending encoded input.
1096        #[must_use]
1097        pub const fn can_into_inner(&self) -> bool {
1098            !self.is_failed() && !self.has_pending_input() && !self.has_buffered_output()
1099        }
1100
1101        /// Consumes the decoder without flushing pending input.
1102        ///
1103        /// Prefer [`Self::finish`] when the decoded output must be complete.
1104        #[must_use]
1105        pub fn into_inner(mut self) -> W {
1106            self.take_inner()
1107        }
1108
1109        /// Consumes the decoder only when no partial input quantum is buffered.
1110        ///
1111        /// This does not flush or finalize the wrapped writer. It is a checked
1112        /// alternative to [`Self::into_inner`] for callers that want to avoid
1113        /// accidentally discarding pending encoded input bytes.
1114        #[allow(clippy::result_large_err)]
1115        pub fn try_into_inner(mut self) -> Result<W, Self> {
1116            if !self.can_into_inner() {
1117                return Err(self);
1118            }
1119            Ok(self.take_inner())
1120        }
1121
1122        fn inner_ref(&self) -> &W {
1123            match &self.inner {
1124                Some(inner) => inner,
1125                None => unreachable!("stream decoder inner writer was already taken"),
1126            }
1127        }
1128
1129        fn inner_mut(&mut self) -> &mut W {
1130            match &mut self.inner {
1131                Some(inner) => inner,
1132                None => unreachable!("stream decoder inner writer was already taken"),
1133            }
1134        }
1135
1136        fn take_inner(&mut self) -> W {
1137            match self.inner.take() {
1138                Some(inner) => inner,
1139                None => unreachable!("stream decoder inner writer was already taken"),
1140            }
1141        }
1142
1143        fn clear_pending(&mut self) {
1144            crate::wipe_bytes(&mut self.pending);
1145            self.pending_len = 0;
1146        }
1147
1148        fn clear_output(&mut self) {
1149            self.output.clear_all();
1150        }
1151    }
1152
1153    impl<W, A, const PAD: bool> Drop for Decoder<W, A, PAD>
1154    where
1155        A: Alphabet,
1156    {
1157        fn drop(&mut self) {
1158            self.clear_pending();
1159            self.clear_output();
1160        }
1161    }
1162
1163    impl<W, A, const PAD: bool> core::fmt::Debug for Decoder<W, A, PAD>
1164    where
1165        A: Alphabet,
1166    {
1167        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1168            formatter
1169                .debug_struct("Decoder")
1170                .field("inner", &redacted_inner_state(self.inner.is_some()))
1171                .field("engine", &self.engine)
1172                .field("pending", &"<redacted>")
1173                .field("pending_len", &self.pending_len)
1174                .field("pending_input_needed_len", &self.pending_input_needed_len())
1175                .field("buffered_output_len", &self.output.len())
1176                .field("buffered_output_capacity", &self.output.capacity())
1177                .field(
1178                    "buffered_output_remaining_capacity",
1179                    &self.output.available_capacity(),
1180                )
1181                .field("can_into_inner", &self.can_into_inner())
1182                .field("terminal_padding", &self.finished)
1183                .field("finalized", &self.finalized)
1184                .field("failed", &self.failed)
1185                .finish()
1186        }
1187    }
1188
1189    impl<W, A, const PAD: bool> Decoder<W, A, PAD>
1190    where
1191        W: Write,
1192        A: Alphabet,
1193    {
1194        /// Validates any final pending input and flushes the wrapped writer
1195        /// without consuming this decoder.
1196        ///
1197        /// After this succeeds, [`Self::pending_len`] returns `0`, later
1198        /// writes are rejected, and [`Self::finish`] can still be used to
1199        /// recover the wrapped writer.
1200        /// If the final buffered input is malformed, an error is returned and
1201        /// the caller still owns the decoder for diagnostics or explicit
1202        /// recovery.
1203        pub fn try_finish(&mut self) -> io::Result<()> {
1204            if self.failed {
1205                return Err(stream_decoder_failed_error());
1206            }
1207            if !self.finalized {
1208                self.queue_pending_final()?;
1209                self.finalized = true;
1210            }
1211            self.flush()
1212        }
1213
1214        /// Validates final pending input, flushes the wrapped writer, and returns it.
1215        pub fn finish(mut self) -> io::Result<W> {
1216            self.try_finish()?;
1217            Ok(self.take_inner())
1218        }
1219
1220        fn queue_pending_final(&mut self) -> io::Result<()> {
1221            if self.pending_len == 0 {
1222                return Ok(());
1223            }
1224
1225            let mut pending = [0u8; 4];
1226            pending[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
1227            let pending_len = self.pending_len;
1228            let mut decoded = [0u8; 3];
1229            let result = self.queue_decoded_temp(&pending[..pending_len], &mut decoded);
1230            crate::wipe_bytes(&mut pending);
1231            if let Err(err) = result {
1232                self.clear_pending();
1233                return Err(err);
1234            }
1235            self.clear_pending();
1236            Ok(())
1237        }
1238
1239        fn queue_full_quad(&mut self, mut input: [u8; 4]) -> io::Result<()> {
1240            let mut decoded = [0u8; 3];
1241            let result = self.queue_decoded_temp(&input, &mut decoded);
1242            crate::wipe_bytes(&mut input);
1243            let written = result?;
1244            if written < 3 {
1245                self.finished = true;
1246            }
1247            Ok(())
1248        }
1249
1250        fn queue_decoded_temp(&mut self, input: &[u8], decoded: &mut [u8]) -> io::Result<usize> {
1251            let written = match self.engine.decode_slice(input, decoded) {
1252                Ok(written) => written,
1253                Err(err) => {
1254                    crate::wipe_bytes(decoded);
1255                    self.failed = true;
1256                    return Err(decode_error_to_io(err));
1257                }
1258            };
1259
1260            let result = self.output.push_slice(&decoded[..written]);
1261            crate::wipe_bytes(decoded);
1262            result?;
1263            Ok(written)
1264        }
1265
1266        fn drain_output(&mut self) -> io::Result<()> {
1267            let mut chunk = [0u8; 1024];
1268            while !self.output.is_empty() {
1269                let pending = self.output.copy_front(&mut chunk);
1270                let result = self.inner_mut().write(&chunk[..pending]);
1271                crate::wipe_bytes(&mut chunk[..pending]);
1272                match result {
1273                    Ok(0) => {
1274                        return Err(io::Error::new(
1275                            io::ErrorKind::WriteZero,
1276                            "base64 stream decoder could not drain buffered output",
1277                        ));
1278                    }
1279                    Ok(written) => {
1280                        if written > pending {
1281                            return Err(io::Error::new(
1282                                io::ErrorKind::InvalidData,
1283                                "wrapped writer reported more bytes than provided",
1284                            ));
1285                        }
1286                        self.output.discard_front(written);
1287                    }
1288                    Err(err) => return Err(err),
1289                }
1290            }
1291
1292            Ok(())
1293        }
1294    }
1295
1296    impl<W, A, const PAD: bool> Write for Decoder<W, A, PAD>
1297    where
1298        W: Write,
1299        A: Alphabet,
1300    {
1301        fn write(&mut self, input: &[u8]) -> io::Result<usize> {
1302            if self.failed {
1303                return Err(stream_decoder_failed_error());
1304            }
1305            if input.is_empty() {
1306                self.drain_output()?;
1307                return Ok(0);
1308            }
1309            self.drain_output()?;
1310            if self.finalized {
1311                return Err(io::Error::new(
1312                    io::ErrorKind::InvalidInput,
1313                    "base64 stream decoder received input after finalization",
1314                ));
1315            }
1316            if self.finished {
1317                self.failed = true;
1318                return Err(trailing_input_after_padding_error());
1319            }
1320
1321            let mut consumed = 0;
1322            if self.pending_len > 0 {
1323                let needed = 4 - self.pending_len;
1324                if input.len() < needed {
1325                    self.pending[self.pending_len..self.pending_len + input.len()]
1326                        .copy_from_slice(input);
1327                    self.pending_len += input.len();
1328                    return Ok(input.len());
1329                }
1330
1331                let mut quad = [0u8; 4];
1332                quad[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
1333                quad[self.pending_len..].copy_from_slice(&input[..needed]);
1334                let result = self.queue_full_quad(quad);
1335                crate::wipe_bytes(&mut quad);
1336                if let Err(err) = result {
1337                    self.clear_pending();
1338                    return Err(err);
1339                }
1340                self.clear_pending();
1341                consumed += needed;
1342                return Ok(consumed);
1343            }
1344
1345            let remaining = &input[consumed..];
1346            let full_len = remaining.len() / 4 * 4;
1347            if full_len > 0 {
1348                let quad = [remaining[0], remaining[1], remaining[2], remaining[3]];
1349                let mut quad = quad;
1350                let result = self.queue_full_quad(quad);
1351                crate::wipe_bytes(&mut quad);
1352                result?;
1353                return Ok(consumed + 4);
1354            }
1355
1356            let tail = &remaining[full_len..];
1357            self.pending[..tail.len()].copy_from_slice(tail);
1358            self.pending_len = tail.len();
1359
1360            Ok(input.len())
1361        }
1362
1363        fn flush(&mut self) -> io::Result<()> {
1364            if self.failed {
1365                return Err(stream_decoder_failed_error());
1366            }
1367            self.drain_output()?;
1368            self.inner_mut().flush()
1369        }
1370    }
1371
1372    fn decode_error_to_io(err: DecodeError) -> io::Error {
1373        io::Error::new(io::ErrorKind::InvalidInput, err)
1374    }
1375
1376    fn trailing_input_after_padding_error() -> io::Error {
1377        io::Error::new(
1378            io::ErrorKind::InvalidInput,
1379            "base64 decoder received trailing input after padding",
1380        )
1381    }
1382
1383    fn stream_decoder_failed_error() -> io::Error {
1384        io::Error::new(
1385            io::ErrorKind::InvalidInput,
1386            "base64 stream decoder is failed after malformed input",
1387        )
1388    }
1389
1390    /// A streaming Base64 decoder for `std::io::Read`.
1391    ///
1392    /// For padded engines, this reader stops at the terminal padded Base64
1393    /// block and leaves later bytes unread in the wrapped reader. This preserves
1394    /// boundaries for callers that decode one Base64 payload from a larger
1395    /// stream.
1396    pub struct DecoderReader<R, A, const PAD: bool>
1397    where
1398        A: Alphabet,
1399    {
1400        inner: Option<R>,
1401        engine: Engine<A, PAD>,
1402        pending: [u8; 4],
1403        pending_len: usize,
1404        output: OutputQueue<3>,
1405        finished: bool,
1406        terminal_seen: bool,
1407        failed: bool,
1408    }
1409
1410    impl<R, A, const PAD: bool> DecoderReader<R, A, PAD>
1411    where
1412        A: Alphabet,
1413    {
1414        /// Creates a new streaming decoder reader.
1415        #[must_use]
1416        pub fn new(inner: R, engine: Engine<A, PAD>) -> Self {
1417            Self {
1418                inner: Some(inner),
1419                engine,
1420                pending: [0; 4],
1421                pending_len: 0,
1422                output: OutputQueue::new(),
1423                finished: false,
1424                terminal_seen: false,
1425                failed: false,
1426            }
1427        }
1428
1429        /// Returns a shared reference to the wrapped reader.
1430        #[must_use]
1431        pub fn get_ref(&self) -> &R {
1432            self.inner_ref()
1433        }
1434
1435        /// Returns a mutable reference to the wrapped reader.
1436        pub fn get_mut(&mut self) -> &mut R {
1437            self.inner_mut()
1438        }
1439
1440        /// Returns the Base64 engine used by this adapter.
1441        #[must_use]
1442        pub const fn engine(&self) -> Engine<A, PAD> {
1443            self.engine
1444        }
1445
1446        /// Returns whether this adapter uses padded Base64.
1447        #[must_use]
1448        pub const fn is_padded(&self) -> bool {
1449            PAD
1450        }
1451
1452        /// Returns the number of encoded input bytes currently buffered until
1453        /// a complete 4-byte Base64 decode quantum is available.
1454        #[must_use]
1455        pub const fn pending_len(&self) -> usize {
1456            self.pending_len
1457        }
1458
1459        /// Returns whether this decoder reader currently holds a partial input
1460        /// quantum.
1461        #[must_use]
1462        pub const fn has_pending_input(&self) -> bool {
1463            self.pending_len != 0
1464        }
1465
1466        /// Returns how many additional encoded input bytes are needed to
1467        /// complete the currently buffered decode quantum.
1468        ///
1469        /// Returns `0` when no partial input quantum is buffered.
1470        #[must_use]
1471        pub const fn pending_input_needed_len(&self) -> usize {
1472            if self.has_pending_input() {
1473                4 - self.pending_len
1474            } else {
1475                0
1476            }
1477        }
1478
1479        /// Returns the number of decoded bytes currently buffered and ready to
1480        /// be read before this adapter polls the wrapped reader again.
1481        #[must_use]
1482        pub const fn buffered_output_len(&self) -> usize {
1483            self.output.len()
1484        }
1485
1486        /// Returns the maximum number of decoded bytes this adapter can buffer
1487        /// before returning bytes to the caller.
1488        #[must_use]
1489        pub const fn buffered_output_capacity(&self) -> usize {
1490            self.output.capacity()
1491        }
1492
1493        /// Returns how many more decoded bytes can be buffered before this
1494        /// adapter must return bytes to the caller.
1495        #[must_use]
1496        pub const fn buffered_output_remaining_capacity(&self) -> usize {
1497            self.output.available_capacity()
1498        }
1499
1500        /// Returns whether this decoder reader currently has decoded output
1501        /// waiting in its internal queue.
1502        #[must_use]
1503        pub const fn has_buffered_output(&self) -> bool {
1504            !self.output.is_empty()
1505        }
1506
1507        /// Returns whether this decoder reader has seen terminal padding.
1508        ///
1509        /// For padded engines, this becomes `true` after the terminal padded
1510        /// block is decoded. The wrapped reader is then left positioned after
1511        /// that Base64 block so adjacent framed bytes can be read by the
1512        /// caller.
1513        #[must_use]
1514        pub const fn has_terminal_padding(&self) -> bool {
1515            self.terminal_seen
1516        }
1517
1518        /// Returns whether this decoder reader has reached EOF or terminal
1519        /// padding in the wrapped reader.
1520        ///
1521        /// This may become `true` before [`Self::is_finished`] when decoded
1522        /// output is still buffered for the caller.
1523        #[must_use]
1524        pub const fn has_finished_input(&self) -> bool {
1525            self.finished
1526        }
1527
1528        /// Returns whether this reader has reached EOF or terminal padding
1529        /// and has no decoded output buffered for the caller.
1530        #[must_use]
1531        pub const fn is_finished(&self) -> bool {
1532            self.finished && self.output.is_empty()
1533        }
1534
1535        /// Returns whether this decoder reader has rejected malformed Base64
1536        /// input.
1537        ///
1538        /// Once this returns `true`, later reads return an error. The unchecked
1539        /// [`Self::into_inner`] method can still be used for explicit recovery
1540        /// of the wrapped reader.
1541        #[must_use]
1542        pub const fn is_failed(&self) -> bool {
1543            self.failed
1544        }
1545
1546        /// Returns whether [`Self::try_into_inner`] can recover the wrapped
1547        /// reader without discarding buffered decoded output.
1548        #[must_use]
1549        pub const fn can_into_inner(&self) -> bool {
1550            !self.is_failed() && self.is_finished()
1551        }
1552
1553        /// Consumes the decoder reader and returns the wrapped reader.
1554        #[must_use]
1555        pub fn into_inner(mut self) -> R {
1556            self.take_inner()
1557        }
1558
1559        /// Consumes the decoder reader only after the Base64 payload is fully
1560        /// drained.
1561        ///
1562        /// For padded streams, terminal padding may leave adjacent framed bytes
1563        /// unread in the wrapped reader. This method succeeds only after all
1564        /// decoded output buffered by this adapter has been read, so recovering
1565        /// the wrapped reader does not silently discard decoded bytes.
1566        #[allow(clippy::result_large_err)]
1567        pub fn try_into_inner(mut self) -> Result<R, Self> {
1568            if !self.can_into_inner() {
1569                return Err(self);
1570            }
1571            Ok(self.take_inner())
1572        }
1573
1574        fn inner_ref(&self) -> &R {
1575            match &self.inner {
1576                Some(inner) => inner,
1577                None => unreachable!("stream decoder reader inner reader was already taken"),
1578            }
1579        }
1580
1581        fn inner_mut(&mut self) -> &mut R {
1582            match &mut self.inner {
1583                Some(inner) => inner,
1584                None => unreachable!("stream decoder reader inner reader was already taken"),
1585            }
1586        }
1587
1588        fn take_inner(&mut self) -> R {
1589            match self.inner.take() {
1590                Some(inner) => inner,
1591                None => unreachable!("stream decoder reader inner reader was already taken"),
1592            }
1593        }
1594
1595        fn clear_pending(&mut self) {
1596            crate::wipe_bytes(&mut self.pending);
1597            self.pending_len = 0;
1598        }
1599    }
1600
1601    impl<R, A, const PAD: bool> Drop for DecoderReader<R, A, PAD>
1602    where
1603        A: Alphabet,
1604    {
1605        fn drop(&mut self) {
1606            self.clear_pending();
1607            self.output.clear_all();
1608        }
1609    }
1610
1611    impl<R, A, const PAD: bool> core::fmt::Debug for DecoderReader<R, A, PAD>
1612    where
1613        A: Alphabet,
1614    {
1615        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1616            formatter
1617                .debug_struct("DecoderReader")
1618                .field("inner", &redacted_inner_state(self.inner.is_some()))
1619                .field("engine", &self.engine)
1620                .field("pending", &"<redacted>")
1621                .field("pending_len", &self.pending_len)
1622                .field("pending_input_needed_len", &self.pending_input_needed_len())
1623                .field("buffered_output_len", &self.output.len())
1624                .field("buffered_output_capacity", &self.output.capacity())
1625                .field(
1626                    "buffered_output_remaining_capacity",
1627                    &self.output.available_capacity(),
1628                )
1629                .field("can_into_inner", &self.can_into_inner())
1630                .field("finished", &self.finished)
1631                .field("terminal_padding", &self.terminal_seen)
1632                .field("failed", &self.failed)
1633                .finish()
1634        }
1635    }
1636
1637    impl<R, A, const PAD: bool> Read for DecoderReader<R, A, PAD>
1638    where
1639        R: Read,
1640        A: Alphabet,
1641    {
1642        fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
1643            if output.is_empty() {
1644                return Ok(0);
1645            }
1646            if self.failed {
1647                return Err(stream_decoder_failed_error());
1648            }
1649
1650            while self.output.is_empty() && !self.finished {
1651                self.fill_output()?;
1652            }
1653
1654            Ok(self.output.pop_slice(output))
1655        }
1656    }
1657
1658    impl<R, A, const PAD: bool> DecoderReader<R, A, PAD>
1659    where
1660        R: Read,
1661        A: Alphabet,
1662    {
1663        fn fill_output(&mut self) -> io::Result<()> {
1664            if self.failed {
1665                return Err(stream_decoder_failed_error());
1666            }
1667            if self.terminal_seen {
1668                self.finished = true;
1669                return Ok(());
1670            }
1671
1672            let mut input = [0u8; 4];
1673            let available = 4 - self.pending_len;
1674            let read = self.inner_mut().read(&mut input[..available])?;
1675            if read == 0 {
1676                crate::wipe_bytes(&mut input);
1677                self.finished = true;
1678                self.push_final_pending()?;
1679                return Ok(());
1680            }
1681
1682            self.pending[self.pending_len..self.pending_len + read].copy_from_slice(&input[..read]);
1683            crate::wipe_bytes(&mut input);
1684            self.pending_len += read;
1685            if self.pending_len < 4 {
1686                return Ok(());
1687            }
1688
1689            let mut quad = self.pending;
1690            self.clear_pending();
1691            let result = self.push_decoded(&quad);
1692            crate::wipe_bytes(&mut quad);
1693            result?;
1694            if self.terminal_seen {
1695                self.finished = true;
1696            }
1697            Ok(())
1698        }
1699
1700        fn push_final_pending(&mut self) -> io::Result<()> {
1701            if self.pending_len == 0 {
1702                return Ok(());
1703            }
1704
1705            let mut pending = [0u8; 4];
1706            pending[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
1707            let pending_len = self.pending_len;
1708            self.clear_pending();
1709            let result = self.push_decoded(&pending[..pending_len]);
1710            crate::wipe_bytes(&mut pending);
1711            result
1712        }
1713
1714        fn push_decoded(&mut self, input: &[u8]) -> io::Result<()> {
1715            let mut decoded = [0u8; 3];
1716            let written = match self.engine.decode_slice(input, &mut decoded) {
1717                Ok(written) => written,
1718                Err(err) => {
1719                    crate::wipe_bytes(&mut decoded);
1720                    self.failed = true;
1721                    return Err(decode_error_to_io(err));
1722                }
1723            };
1724            let result = self.output.push_slice(&decoded[..written]);
1725            crate::wipe_bytes(&mut decoded);
1726            result?;
1727            if input.len() == 4 && written < 3 {
1728                self.terminal_seen = true;
1729            }
1730            Ok(())
1731        }
1732    }
1733
1734    /// A streaming Base64 encoder for `std::io::Read`.
1735    pub struct EncoderReader<R, A, const PAD: bool>
1736    where
1737        A: Alphabet,
1738    {
1739        inner: Option<R>,
1740        engine: Engine<A, PAD>,
1741        pending: [u8; 2],
1742        pending_len: usize,
1743        output: OutputQueue<1024>,
1744        finished: bool,
1745    }
1746
1747    impl<R, A, const PAD: bool> EncoderReader<R, A, PAD>
1748    where
1749        A: Alphabet,
1750    {
1751        /// Creates a new streaming encoder reader.
1752        #[must_use]
1753        pub fn new(inner: R, engine: Engine<A, PAD>) -> Self {
1754            Self {
1755                inner: Some(inner),
1756                engine,
1757                pending: [0; 2],
1758                pending_len: 0,
1759                output: OutputQueue::new(),
1760                finished: false,
1761            }
1762        }
1763
1764        /// Returns a shared reference to the wrapped reader.
1765        #[must_use]
1766        pub fn get_ref(&self) -> &R {
1767            self.inner_ref()
1768        }
1769
1770        /// Returns a mutable reference to the wrapped reader.
1771        pub fn get_mut(&mut self) -> &mut R {
1772            self.inner_mut()
1773        }
1774
1775        /// Returns the Base64 engine used by this adapter.
1776        #[must_use]
1777        pub const fn engine(&self) -> Engine<A, PAD> {
1778            self.engine
1779        }
1780
1781        /// Returns whether this adapter uses padded Base64.
1782        #[must_use]
1783        pub const fn is_padded(&self) -> bool {
1784            PAD
1785        }
1786
1787        /// Returns the number of raw input bytes currently buffered until a
1788        /// complete 3-byte Base64 encode quantum is available.
1789        #[must_use]
1790        pub const fn pending_len(&self) -> usize {
1791            self.pending_len
1792        }
1793
1794        /// Returns whether this encoder reader currently holds a partial input
1795        /// quantum.
1796        #[must_use]
1797        pub const fn has_pending_input(&self) -> bool {
1798            self.pending_len != 0
1799        }
1800
1801        /// Returns how many additional raw input bytes are needed to complete
1802        /// the currently buffered encode quantum.
1803        ///
1804        /// Returns `0` when no partial input quantum is buffered.
1805        #[must_use]
1806        pub const fn pending_input_needed_len(&self) -> usize {
1807            if self.has_pending_input() {
1808                3 - self.pending_len
1809            } else {
1810                0
1811            }
1812        }
1813
1814        /// Returns the number of encoded bytes currently buffered and ready to
1815        /// be read before this adapter polls the wrapped reader again.
1816        #[must_use]
1817        pub const fn buffered_output_len(&self) -> usize {
1818            self.output.len()
1819        }
1820
1821        /// Returns the maximum number of encoded bytes this adapter can buffer
1822        /// before returning bytes to the caller.
1823        #[must_use]
1824        pub const fn buffered_output_capacity(&self) -> usize {
1825            self.output.capacity()
1826        }
1827
1828        /// Returns how many more encoded bytes can be buffered before this
1829        /// adapter must return bytes to the caller.
1830        #[must_use]
1831        pub const fn buffered_output_remaining_capacity(&self) -> usize {
1832            self.output.available_capacity()
1833        }
1834
1835        /// Returns whether this encoder reader currently has encoded output
1836        /// waiting in its internal queue.
1837        #[must_use]
1838        pub const fn has_buffered_output(&self) -> bool {
1839            !self.output.is_empty()
1840        }
1841
1842        /// Returns whether this encoder reader has reached EOF in the wrapped
1843        /// reader.
1844        ///
1845        /// This may become `true` before [`Self::is_finished`] when encoded
1846        /// output is still buffered for the caller.
1847        #[must_use]
1848        pub const fn has_finished_input(&self) -> bool {
1849            self.finished
1850        }
1851
1852        /// Returns whether this reader has reached EOF and has no encoded
1853        /// output buffered for the caller.
1854        #[must_use]
1855        pub const fn is_finished(&self) -> bool {
1856            self.finished && self.output.is_empty()
1857        }
1858
1859        /// Returns whether [`Self::try_into_inner`] can recover the wrapped
1860        /// reader without discarding pending input or buffered encoded output.
1861        #[must_use]
1862        pub const fn can_into_inner(&self) -> bool {
1863            self.is_finished()
1864        }
1865
1866        /// Consumes the encoder reader and returns the wrapped reader.
1867        #[must_use]
1868        pub fn into_inner(mut self) -> R {
1869            self.take_inner()
1870        }
1871
1872        /// Consumes the encoder reader only after the encoded stream is fully
1873        /// drained.
1874        ///
1875        /// This is a checked alternative to [`Self::into_inner`] for callers
1876        /// that want to avoid accidentally discarding pending input or encoded
1877        /// output buffered inside the adapter.
1878        #[allow(clippy::result_large_err)]
1879        pub fn try_into_inner(mut self) -> Result<R, Self> {
1880            if !self.can_into_inner() {
1881                return Err(self);
1882            }
1883            Ok(self.take_inner())
1884        }
1885
1886        fn inner_ref(&self) -> &R {
1887            match &self.inner {
1888                Some(inner) => inner,
1889                None => unreachable!("stream encoder reader inner reader was already taken"),
1890            }
1891        }
1892
1893        fn inner_mut(&mut self) -> &mut R {
1894            match &mut self.inner {
1895                Some(inner) => inner,
1896                None => unreachable!("stream encoder reader inner reader was already taken"),
1897            }
1898        }
1899
1900        fn take_inner(&mut self) -> R {
1901            match self.inner.take() {
1902                Some(inner) => inner,
1903                None => unreachable!("stream encoder reader inner reader was already taken"),
1904            }
1905        }
1906
1907        fn clear_pending(&mut self) {
1908            crate::wipe_bytes(&mut self.pending);
1909            self.pending_len = 0;
1910        }
1911    }
1912
1913    impl<R, A, const PAD: bool> Drop for EncoderReader<R, A, PAD>
1914    where
1915        A: Alphabet,
1916    {
1917        fn drop(&mut self) {
1918            self.clear_pending();
1919            self.output.clear_all();
1920        }
1921    }
1922
1923    impl<R, A, const PAD: bool> core::fmt::Debug for EncoderReader<R, A, PAD>
1924    where
1925        A: Alphabet,
1926    {
1927        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1928            formatter
1929                .debug_struct("EncoderReader")
1930                .field("inner", &redacted_inner_state(self.inner.is_some()))
1931                .field("engine", &self.engine)
1932                .field("pending", &"<redacted>")
1933                .field("pending_len", &self.pending_len)
1934                .field("pending_input_needed_len", &self.pending_input_needed_len())
1935                .field("buffered_output_len", &self.output.len())
1936                .field("buffered_output_capacity", &self.output.capacity())
1937                .field(
1938                    "buffered_output_remaining_capacity",
1939                    &self.output.available_capacity(),
1940                )
1941                .field("can_into_inner", &self.can_into_inner())
1942                .field("finished", &self.finished)
1943                .finish()
1944        }
1945    }
1946
1947    impl<R, A, const PAD: bool> Read for EncoderReader<R, A, PAD>
1948    where
1949        R: Read,
1950        A: Alphabet,
1951    {
1952        fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
1953            if output.is_empty() {
1954                return Ok(0);
1955            }
1956
1957            while self.output.is_empty() && !self.finished {
1958                self.fill_output()?;
1959            }
1960
1961            Ok(self.output.pop_slice(output))
1962        }
1963    }
1964
1965    impl<R, A, const PAD: bool> EncoderReader<R, A, PAD>
1966    where
1967        R: Read,
1968        A: Alphabet,
1969    {
1970        fn fill_output(&mut self) -> io::Result<()> {
1971            let mut input = [0u8; 768];
1972            let read = self.inner_mut().read(&mut input)?;
1973            if read == 0 {
1974                crate::wipe_bytes(&mut input);
1975                self.finished = true;
1976                self.push_final_pending()?;
1977                return Ok(());
1978            }
1979
1980            let mut consumed = 0;
1981            if self.pending_len > 0 {
1982                let needed = 3 - self.pending_len;
1983                if read < needed {
1984                    self.pending[self.pending_len..self.pending_len + read]
1985                        .copy_from_slice(&input[..read]);
1986                    self.pending_len += read;
1987                    crate::wipe_bytes(&mut input);
1988                    return Ok(());
1989                }
1990
1991                let mut chunk = [0u8; 3];
1992                chunk[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
1993                chunk[self.pending_len..].copy_from_slice(&input[..needed]);
1994                let result = self.push_encoded(&chunk);
1995                crate::wipe_bytes(&mut chunk);
1996                if let Err(err) = result {
1997                    crate::wipe_bytes(&mut input);
1998                    return Err(err);
1999                }
2000                self.clear_pending();
2001                consumed += needed;
2002            }
2003
2004            let remaining = &input[consumed..read];
2005            let full_len = remaining.len() / 3 * 3;
2006            let tail_len = remaining.len() - full_len;
2007            let mut tail = [0u8; 2];
2008            tail[..tail_len].copy_from_slice(&remaining[full_len..]);
2009            let result = if full_len > 0 {
2010                self.push_encoded(&remaining[..full_len])
2011            } else {
2012                Ok(())
2013            };
2014            crate::wipe_bytes(&mut input);
2015            if let Err(err) = result {
2016                crate::wipe_bytes(&mut tail);
2017                return Err(err);
2018            }
2019            self.pending[..tail_len].copy_from_slice(&tail[..tail_len]);
2020            crate::wipe_bytes(&mut tail);
2021            self.pending_len = tail_len;
2022            Ok(())
2023        }
2024
2025        fn push_final_pending(&mut self) -> io::Result<()> {
2026            if self.pending_len == 0 {
2027                return Ok(());
2028            }
2029
2030            let mut pending = [0u8; 2];
2031            pending[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
2032            let pending_len = self.pending_len;
2033            self.clear_pending();
2034            let result = self.push_encoded(&pending[..pending_len]);
2035            crate::wipe_bytes(&mut pending);
2036            result
2037        }
2038
2039        fn push_encoded(&mut self, input: &[u8]) -> io::Result<()> {
2040            let mut encoded = [0u8; 1024];
2041            let written = match self.engine.encode_slice(input, &mut encoded) {
2042                Ok(written) => written,
2043                Err(err) => {
2044                    crate::wipe_bytes(&mut encoded);
2045                    return Err(encode_error_to_io(err));
2046                }
2047            };
2048            let result = self.output.push_slice(&encoded[..written]);
2049            crate::wipe_bytes(&mut encoded);
2050            result
2051        }
2052    }
2053
2054    const fn redacted_inner_state(present: bool) -> &'static str {
2055        if present { "<present>" } else { "<taken>" }
2056    }
2057}
2058
2059/// Constant-time-oriented scalar decoding APIs.
2060///
2061/// This module is separate from the default decoder so callers can opt into a
2062/// slower path with a narrower timing target. It avoids lookup tables indexed
2063/// by secret input bytes while mapping Base64 symbols and reports malformed
2064/// content through one opaque error. It is not documented as a formally
2065/// verified cryptographic constant-time API.
2066pub mod ct {
2067    use super::{
2068        Alphabet, DecodeError, DecodedBuffer, Standard, UrlSafe, ct_decode_in_place,
2069        ct_decode_slice, ct_decoded_len, ct_validate_decode,
2070    };
2071    use core::marker::PhantomData;
2072
2073    /// Standard Base64 constant-time-oriented decoder with padding.
2074    pub const STANDARD: CtEngine<Standard, true> = CtEngine::new();
2075
2076    /// Standard Base64 constant-time-oriented decoder without padding.
2077    pub const STANDARD_NO_PAD: CtEngine<Standard, false> = CtEngine::new();
2078
2079    /// URL-safe Base64 constant-time-oriented decoder with padding.
2080    pub const URL_SAFE: CtEngine<UrlSafe, true> = CtEngine::new();
2081
2082    /// URL-safe Base64 constant-time-oriented decoder without padding.
2083    pub const URL_SAFE_NO_PAD: CtEngine<UrlSafe, false> = CtEngine::new();
2084
2085    /// A zero-sized constant-time-oriented Base64 decoder.
2086    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
2087    pub struct CtEngine<A, const PAD: bool> {
2088        alphabet: PhantomData<A>,
2089    }
2090
2091    impl<A, const PAD: bool> CtEngine<A, PAD>
2092    where
2093        A: Alphabet,
2094    {
2095        /// Creates a new constant-time-oriented decoder engine.
2096        #[must_use]
2097        pub const fn new() -> Self {
2098            Self {
2099                alphabet: PhantomData,
2100            }
2101        }
2102
2103        /// Returns whether this constant-time-oriented decoder expects padded
2104        /// input.
2105        #[must_use]
2106        pub const fn is_padded(&self) -> bool {
2107            PAD
2108        }
2109
2110        /// Validates `input` without writing decoded bytes.
2111        ///
2112        /// This uses the same constant-time-oriented symbol mapping and opaque
2113        /// malformed-input error behavior as [`Self::decode_slice`]. Input
2114        /// length, padding length, and final success or failure remain public.
2115        ///
2116        /// # Examples
2117        ///
2118        /// ```
2119        /// use base64_ng::ct;
2120        ///
2121        /// ct::STANDARD.validate_result(b"aGVsbG8=").unwrap();
2122        /// assert!(ct::STANDARD.validate_result(b"aGVsbG8").is_err());
2123        /// ```
2124        pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
2125            ct_validate_decode::<A, PAD>(input)
2126        }
2127
2128        /// Returns whether `input` is valid for this constant-time-oriented
2129        /// decoder.
2130        ///
2131        /// This is a convenience wrapper around [`Self::validate_result`].
2132        ///
2133        /// # Examples
2134        ///
2135        /// ```
2136        /// use base64_ng::ct;
2137        ///
2138        /// assert!(ct::URL_SAFE_NO_PAD.validate(b"-_8"));
2139        /// assert!(!ct::URL_SAFE_NO_PAD.validate(b"+/8"));
2140        /// ```
2141        #[must_use]
2142        pub fn validate(&self, input: &[u8]) -> bool {
2143            self.validate_result(input).is_ok()
2144        }
2145
2146        /// Returns the exact decoded length for valid input.
2147        ///
2148        /// This uses the same constant-time-oriented validation policy as
2149        /// [`Self::decode_slice`] before returning a length. Input length,
2150        /// padding length, and final success or failure remain public.
2151        pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
2152            ct_decoded_len::<A, PAD>(input)
2153        }
2154
2155        /// Decodes `input` into `output`, returning the number of bytes
2156        /// written.
2157        ///
2158        /// This path uses a fixed alphabet scan for Base64 symbol mapping and
2159        /// avoids secret-indexed lookup tables. Input length, padding length,
2160        /// output length, and final success or failure remain public.
2161        /// Malformed content errors are intentionally opaque and non-localized;
2162        /// use the normal strict decoder when exact diagnostics are required.
2163        ///
2164        /// # Examples
2165        ///
2166        /// ```
2167        /// use base64_ng::ct;
2168        ///
2169        /// let mut output = [0u8; 5];
2170        /// let written = ct::STANDARD
2171        ///     .decode_slice(b"aGVsbG8=", &mut output)
2172        ///     .unwrap();
2173        ///
2174        /// assert_eq!(&output[..written], b"hello");
2175        /// ```
2176        pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
2177            ct_decode_slice::<A, PAD>(input, output)
2178        }
2179
2180        /// Decodes `input` into `output` and clears all bytes after the
2181        /// decoded prefix.
2182        ///
2183        /// If decoding fails, the entire output buffer is cleared before the
2184        /// error is returned. Use this variant for sensitive payloads where
2185        /// partially decoded bytes from rejected input should not remain in the
2186        /// caller-owned output buffer.
2187        ///
2188        /// # Examples
2189        ///
2190        /// ```
2191        /// use base64_ng::ct;
2192        ///
2193        /// let mut output = [0xff; 8];
2194        /// let written = ct::STANDARD
2195        ///     .decode_slice_clear_tail(b"aGk=", &mut output)
2196        ///     .unwrap();
2197        ///
2198        /// assert_eq!(&output[..written], b"hi");
2199        /// assert!(output[written..].iter().all(|byte| *byte == 0));
2200        /// ```
2201        pub fn decode_slice_clear_tail(
2202            &self,
2203            input: &[u8],
2204            output: &mut [u8],
2205        ) -> Result<usize, DecodeError> {
2206            let written = match self.decode_slice(input, output) {
2207                Ok(written) => written,
2208                Err(err) => {
2209                    crate::wipe_bytes(output);
2210                    return Err(err);
2211                }
2212            };
2213            crate::wipe_tail(output, written);
2214            Ok(written)
2215        }
2216
2217        /// Decodes `input` into a stack-backed buffer.
2218        ///
2219        /// This uses the same constant-time-oriented scalar decoder as
2220        /// [`Self::decode_slice_clear_tail`] and clears the internal backing
2221        /// array before returning an error.
2222        ///
2223        /// # Examples
2224        ///
2225        /// ```
2226        /// use base64_ng::ct;
2227        ///
2228        /// let decoded = ct::STANDARD.decode_buffer::<5>(b"aGVsbG8=").unwrap();
2229        ///
2230        /// assert_eq!(decoded.as_bytes(), b"hello");
2231        /// ```
2232        pub fn decode_buffer<const CAP: usize>(
2233            &self,
2234            input: &[u8],
2235        ) -> Result<DecodedBuffer<CAP>, DecodeError> {
2236            let mut output = DecodedBuffer::new();
2237            let written = match self.decode_slice_clear_tail(input, &mut output.bytes) {
2238                Ok(written) => written,
2239                Err(err) => {
2240                    output.clear();
2241                    return Err(err);
2242                }
2243            };
2244            output.len = written;
2245            Ok(output)
2246        }
2247
2248        /// Decodes `buffer` in place and returns the decoded prefix.
2249        ///
2250        /// This uses the constant-time-oriented scalar decoder while reading
2251        /// each Base64 quantum into local values before writing decoded bytes
2252        /// back to the front of the same buffer.
2253        ///
2254        /// # Examples
2255        ///
2256        /// ```
2257        /// use base64_ng::ct;
2258        ///
2259        /// let mut buffer = *b"aGk=";
2260        /// let decoded = ct::STANDARD.decode_in_place(&mut buffer).unwrap();
2261        ///
2262        /// assert_eq!(decoded, b"hi");
2263        /// ```
2264        pub fn decode_in_place<'a>(
2265            &self,
2266            buffer: &'a mut [u8],
2267        ) -> Result<&'a mut [u8], DecodeError> {
2268            let len = ct_decode_in_place::<A, PAD>(buffer)?;
2269            Ok(&mut buffer[..len])
2270        }
2271
2272        /// Decodes `buffer` in place and clears all bytes after the decoded
2273        /// prefix.
2274        ///
2275        /// If decoding fails, the entire buffer is cleared before the error is
2276        /// returned.
2277        ///
2278        /// # Examples
2279        ///
2280        /// ```
2281        /// use base64_ng::ct;
2282        ///
2283        /// let mut buffer = *b"aGk=";
2284        /// let decoded = ct::STANDARD.decode_in_place_clear_tail(&mut buffer).unwrap();
2285        ///
2286        /// assert_eq!(decoded, b"hi");
2287        /// ```
2288        pub fn decode_in_place_clear_tail<'a>(
2289            &self,
2290            buffer: &'a mut [u8],
2291        ) -> Result<&'a mut [u8], DecodeError> {
2292            let len = match ct_decode_in_place::<A, PAD>(buffer) {
2293                Ok(len) => len,
2294                Err(err) => {
2295                    crate::wipe_bytes(buffer);
2296                    return Err(err);
2297                }
2298            };
2299            crate::wipe_tail(buffer, len);
2300            Ok(&mut buffer[..len])
2301        }
2302    }
2303
2304    impl<A, const PAD: bool> core::fmt::Display for CtEngine<A, PAD> {
2305        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2306            write!(formatter, "ct padded={PAD}")
2307        }
2308    }
2309}
2310
2311/// Standard Base64 engine with padding.
2312pub const STANDARD: Engine<Standard, true> = Engine::new();
2313
2314/// Standard Base64 engine without padding.
2315pub const STANDARD_NO_PAD: Engine<Standard, false> = Engine::new();
2316
2317/// URL-safe Base64 engine with padding.
2318pub const URL_SAFE: Engine<UrlSafe, true> = Engine::new();
2319
2320/// URL-safe Base64 engine without padding.
2321pub const URL_SAFE_NO_PAD: Engine<UrlSafe, false> = Engine::new();
2322
2323/// bcrypt-style Base64 engine without padding.
2324///
2325/// This uses the bcrypt alphabet with the crate's normal Base64 bit packing.
2326/// It does not parse complete bcrypt password-hash strings.
2327pub const BCRYPT_NO_PAD: Engine<Bcrypt, false> = Engine::new();
2328
2329/// Unix `crypt(3)`-style Base64 engine without padding.
2330///
2331/// This uses the `crypt(3)` alphabet with the crate's normal Base64 bit
2332/// packing. It does not parse complete password-hash strings.
2333pub const CRYPT_NO_PAD: Engine<Crypt, false> = Engine::new();
2334
2335/// Line ending used by wrapped Base64 output.
2336#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2337pub enum LineEnding {
2338    /// Line feed (`\n`).
2339    Lf,
2340    /// Carriage return followed by line feed (`\r\n`).
2341    CrLf,
2342}
2343
2344impl LineEnding {
2345    /// Returns a stable printable identifier for this line ending.
2346    #[must_use]
2347    pub const fn name(self) -> &'static str {
2348        match self {
2349            Self::Lf => "LF",
2350            Self::CrLf => "CRLF",
2351        }
2352    }
2353
2354    /// Returns the text representation of this line ending.
2355    #[must_use]
2356    pub const fn as_str(self) -> &'static str {
2357        match self {
2358            Self::Lf => "\n",
2359            Self::CrLf => "\r\n",
2360        }
2361    }
2362
2363    /// Returns the byte representation of this line ending.
2364    #[must_use]
2365    pub const fn as_bytes(self) -> &'static [u8] {
2366        self.as_str().as_bytes()
2367    }
2368
2369    /// Returns the byte length of this line ending.
2370    #[must_use]
2371    pub const fn byte_len(self) -> usize {
2372        match self {
2373            Self::Lf => 1,
2374            Self::CrLf => 2,
2375        }
2376    }
2377}
2378
2379impl core::fmt::Display for LineEnding {
2380    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2381        formatter.write_str(self.name())
2382    }
2383}
2384
2385/// Base64 line wrapping policy.
2386///
2387/// `line_len` is measured in encoded Base64 bytes, not source input bytes.
2388/// Encoders insert line endings between lines and do not append a trailing line
2389/// ending after the final line.
2390#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2391pub struct LineWrap {
2392    /// Maximum encoded bytes per line.
2393    pub line_len: usize,
2394    /// Line ending inserted between wrapped lines.
2395    pub line_ending: LineEnding,
2396}
2397
2398impl LineWrap {
2399    /// MIME-style wrapping: 76 columns with CRLF endings.
2400    pub const MIME: Self = Self::new(76, LineEnding::CrLf);
2401    /// PEM-style wrapping: 64 columns with LF endings.
2402    pub const PEM: Self = Self::new(64, LineEnding::Lf);
2403    /// PEM-style wrapping: 64 columns with CRLF endings.
2404    pub const PEM_CRLF: Self = Self::new(64, LineEnding::CrLf);
2405
2406    /// Creates a wrapping policy.
2407    #[must_use]
2408    pub const fn new(line_len: usize, line_ending: LineEnding) -> Self {
2409        Self {
2410            line_len,
2411            line_ending,
2412        }
2413    }
2414
2415    /// Creates a wrapping policy, returning `None` when the line length is
2416    /// invalid.
2417    ///
2418    /// Base64 line-wrapping requires a non-zero encoded line length. This
2419    /// helper is useful when accepting a wrapping policy from configuration or
2420    /// another untrusted source.
2421    #[must_use]
2422    pub const fn checked_new(line_len: usize, line_ending: LineEnding) -> Option<Self> {
2423        if line_len == 0 {
2424            None
2425        } else {
2426            Some(Self::new(line_len, line_ending))
2427        }
2428    }
2429
2430    /// Returns the maximum encoded bytes per line.
2431    #[must_use]
2432    pub const fn line_len(self) -> usize {
2433        self.line_len
2434    }
2435
2436    /// Returns the line ending inserted between wrapped lines.
2437    #[must_use]
2438    pub const fn line_ending(self) -> LineEnding {
2439        self.line_ending
2440    }
2441
2442    /// Returns whether this wrapping policy can be used by the encoder.
2443    #[must_use]
2444    pub const fn is_valid(self) -> bool {
2445        self.line_len != 0
2446    }
2447}
2448
2449impl core::fmt::Display for LineWrap {
2450    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2451        write!(formatter, "{}:{}", self.line_len, self.line_ending.name())
2452    }
2453}
2454
2455#[allow(unsafe_code)]
2456fn wipe_bytes(bytes: &mut [u8]) {
2457    for byte in bytes {
2458        // SAFETY: `byte` comes from a unique mutable slice iterator, so the
2459        // pointer is non-null, aligned, valid for one `u8` write, and does not
2460        // alias another live mutable reference during this iteration.
2461        unsafe {
2462            core::ptr::write_volatile(byte, 0);
2463        }
2464    }
2465    core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
2466}
2467
2468fn wipe_tail(bytes: &mut [u8], start: usize) {
2469    wipe_bytes(&mut bytes[start..]);
2470}
2471
2472#[cfg(feature = "alloc")]
2473#[allow(unsafe_code)]
2474fn wipe_vec_spare_capacity(bytes: &mut alloc::vec::Vec<u8>) {
2475    let ptr = bytes.as_mut_ptr();
2476    let mut offset = bytes.len();
2477    while offset < bytes.capacity() {
2478        // SAFETY: `offset` is within the vector allocation's spare capacity, so
2479        // the pointer is valid, aligned, and writable for one `u8`. This writes
2480        // a zero byte without reading the prior uninitialized value.
2481        unsafe {
2482            core::ptr::write_volatile(ptr.add(offset), 0);
2483        }
2484        offset += 1;
2485    }
2486    core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
2487}
2488
2489#[cfg(feature = "alloc")]
2490fn wipe_vec_all(bytes: &mut alloc::vec::Vec<u8>) {
2491    wipe_bytes(bytes);
2492    wipe_vec_spare_capacity(bytes);
2493}
2494
2495/// Stack-backed encoded Base64 output.
2496///
2497/// This type is intended for short values where heap allocation would be
2498/// unnecessary but manually sizing and passing a separate output slice is
2499/// noisy. Its visible bytes are produced by crate encoders, so [`Self::as_str`]
2500/// can return `&str` without exposing a fallible UTF-8 conversion to callers.
2501///
2502/// The backing array is cleared when the value is dropped. This is best-effort
2503/// data-retention reduction and is not a formal zeroization guarantee.
2504pub struct EncodedBuffer<const CAP: usize> {
2505    bytes: [u8; CAP],
2506    len: usize,
2507}
2508
2509impl<const CAP: usize> EncodedBuffer<CAP> {
2510    /// Creates an empty encoded buffer.
2511    #[must_use]
2512    pub const fn new() -> Self {
2513        Self {
2514            bytes: [0u8; CAP],
2515            len: 0,
2516        }
2517    }
2518
2519    /// Returns the number of visible encoded bytes.
2520    #[must_use]
2521    pub const fn len(&self) -> usize {
2522        self.len
2523    }
2524
2525    /// Returns whether the buffer has no visible encoded bytes.
2526    #[must_use]
2527    pub const fn is_empty(&self) -> bool {
2528        self.len == 0
2529    }
2530
2531    /// Returns whether the visible encoded bytes fill the stack backing array.
2532    #[must_use]
2533    pub const fn is_full(&self) -> bool {
2534        self.len == CAP
2535    }
2536
2537    /// Returns the stack capacity in bytes.
2538    #[must_use]
2539    pub const fn capacity(&self) -> usize {
2540        CAP
2541    }
2542
2543    /// Returns the number of unused bytes in the stack backing array.
2544    #[must_use]
2545    pub const fn remaining_capacity(&self) -> usize {
2546        CAP - self.len
2547    }
2548
2549    /// Returns the visible encoded bytes.
2550    #[must_use]
2551    pub fn as_bytes(&self) -> &[u8] {
2552        &self.bytes[..self.len]
2553    }
2554
2555    /// Returns the visible encoded bytes as UTF-8 text.
2556    ///
2557    /// Encoded Base64 output is produced as ASCII by this crate, so this
2558    /// method should not fail unless an internal invariant has been broken.
2559    /// It is provided for callers that prefer a fallible accessor over
2560    /// [`Self::as_str`].
2561    pub fn as_utf8(&self) -> Result<&str, core::str::Utf8Error> {
2562        core::str::from_utf8(self.as_bytes())
2563    }
2564
2565    /// Returns the visible encoded bytes as UTF-8.
2566    ///
2567    /// # Panics
2568    ///
2569    /// Panics only if the crate's internal invariant is broken and the buffer
2570    /// contains non-UTF-8 bytes.
2571    #[must_use]
2572    pub fn as_str(&self) -> &str {
2573        match self.as_utf8() {
2574            Ok(output) => output,
2575            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
2576        }
2577    }
2578
2579    /// Compares this encoded output to `other` without short-circuiting on the
2580    /// first differing byte.
2581    ///
2582    /// Length and the final equality result remain public. For equal-length
2583    /// inputs, this helper scans every byte before returning. It is
2584    /// constant-time-oriented best effort, not a formal cryptographic
2585    /// constant-time guarantee.
2586    #[must_use]
2587    pub fn constant_time_eq(&self, other: &[u8]) -> bool {
2588        constant_time_eq_public_len(self.as_bytes(), other)
2589    }
2590
2591    /// Consumes the wrapper and returns the backing array plus visible length.
2592    ///
2593    /// This is an explicit escape hatch for no-alloc interop with APIs that
2594    /// require ownership of a fixed array. The returned array is no longer
2595    /// redacted by formatting and will not be cleared by `EncodedBuffer` on
2596    /// drop; callers that keep handling sensitive data should arrange their
2597    /// own cleanup.
2598    #[must_use]
2599    pub fn into_exposed_array(mut self) -> ([u8; CAP], usize) {
2600        let len = self.len;
2601        self.len = 0;
2602        (core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
2603    }
2604
2605    /// Clears the visible bytes and the full backing array.
2606    pub fn clear(&mut self) {
2607        wipe_bytes(&mut self.bytes);
2608        self.len = 0;
2609    }
2610
2611    /// Clears bytes after the visible prefix.
2612    pub fn clear_tail(&mut self) {
2613        wipe_tail(&mut self.bytes, self.len);
2614    }
2615}
2616
2617impl<const CAP: usize> AsRef<[u8]> for EncodedBuffer<CAP> {
2618    fn as_ref(&self) -> &[u8] {
2619        self.as_bytes()
2620    }
2621}
2622
2623impl<const CAP: usize> Clone for EncodedBuffer<CAP> {
2624    fn clone(&self) -> Self {
2625        let mut output = Self::new();
2626        output.bytes[..self.len].copy_from_slice(self.as_bytes());
2627        output.len = self.len;
2628        output
2629    }
2630}
2631
2632impl<const CAP: usize> core::fmt::Debug for EncodedBuffer<CAP> {
2633    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2634        formatter
2635            .debug_struct("EncodedBuffer")
2636            .field("bytes", &"<redacted>")
2637            .field("len", &self.len)
2638            .field("capacity", &CAP)
2639            .finish()
2640    }
2641}
2642
2643impl<const CAP: usize> core::fmt::Display for EncodedBuffer<CAP> {
2644    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2645        formatter.write_str(self.as_str())
2646    }
2647}
2648
2649impl<const CAP: usize> Default for EncodedBuffer<CAP> {
2650    fn default() -> Self {
2651        Self::new()
2652    }
2653}
2654
2655impl<const CAP: usize> Drop for EncodedBuffer<CAP> {
2656    fn drop(&mut self) {
2657        self.clear();
2658    }
2659}
2660
2661impl<const CAP: usize> Eq for EncodedBuffer<CAP> {}
2662
2663impl<const CAP: usize> PartialEq for EncodedBuffer<CAP> {
2664    fn eq(&self, other: &Self) -> bool {
2665        self.constant_time_eq(other.as_bytes())
2666    }
2667}
2668
2669impl<const CAP: usize> PartialEq<&[u8]> for EncodedBuffer<CAP> {
2670    fn eq(&self, other: &&[u8]) -> bool {
2671        self.constant_time_eq(other)
2672    }
2673}
2674
2675impl<const CAP: usize, const N: usize> PartialEq<&[u8; N]> for EncodedBuffer<CAP> {
2676    fn eq(&self, other: &&[u8; N]) -> bool {
2677        self.constant_time_eq(&other[..])
2678    }
2679}
2680
2681impl<const CAP: usize> PartialEq<&str> for EncodedBuffer<CAP> {
2682    fn eq(&self, other: &&str) -> bool {
2683        self.constant_time_eq(other.as_bytes())
2684    }
2685}
2686
2687#[cfg(feature = "alloc")]
2688impl<const CAP: usize> PartialEq<alloc::string::String> for EncodedBuffer<CAP> {
2689    fn eq(&self, other: &alloc::string::String) -> bool {
2690        self.constant_time_eq(other.as_bytes())
2691    }
2692}
2693
2694impl<const CAP: usize> PartialEq<EncodedBuffer<CAP>> for &[u8] {
2695    fn eq(&self, other: &EncodedBuffer<CAP>) -> bool {
2696        other.constant_time_eq(self)
2697    }
2698}
2699
2700impl<const CAP: usize, const N: usize> PartialEq<EncodedBuffer<CAP>> for &[u8; N] {
2701    fn eq(&self, other: &EncodedBuffer<CAP>) -> bool {
2702        other.constant_time_eq(&self[..])
2703    }
2704}
2705
2706impl<const CAP: usize> PartialEq<EncodedBuffer<CAP>> for &str {
2707    fn eq(&self, other: &EncodedBuffer<CAP>) -> bool {
2708        other.constant_time_eq(self.as_bytes())
2709    }
2710}
2711
2712#[cfg(feature = "alloc")]
2713impl<const CAP: usize> PartialEq<EncodedBuffer<CAP>> for alloc::string::String {
2714    fn eq(&self, other: &EncodedBuffer<CAP>) -> bool {
2715        other.constant_time_eq(self.as_bytes())
2716    }
2717}
2718
2719impl<const CAP: usize> TryFrom<&[u8]> for EncodedBuffer<CAP> {
2720    type Error = EncodeError;
2721
2722    /// Encodes bytes into strict standard padded Base64 in a stack-backed
2723    /// buffer.
2724    ///
2725    /// Use [`Engine::encode_buffer`] or [`Profile::encode_buffer`] when a
2726    /// different alphabet, padding mode, or line-wrapping profile is required.
2727    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
2728        STANDARD.encode_buffer(input)
2729    }
2730}
2731
2732impl<const CAP: usize, const N: usize> TryFrom<&[u8; N]> for EncodedBuffer<CAP> {
2733    type Error = EncodeError;
2734
2735    /// Encodes a byte array into strict standard padded Base64 in a
2736    /// stack-backed buffer.
2737    ///
2738    /// Use [`Engine::encode_buffer`] or [`Profile::encode_buffer`] when a
2739    /// different alphabet, padding mode, or line-wrapping profile is required.
2740    fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
2741        Self::try_from(&input[..])
2742    }
2743}
2744
2745impl<const CAP: usize> TryFrom<&str> for EncodedBuffer<CAP> {
2746    type Error = EncodeError;
2747
2748    /// Encodes UTF-8 text bytes into strict standard padded Base64 in a
2749    /// stack-backed buffer.
2750    ///
2751    /// This treats the string as raw input bytes. Use
2752    /// [`Engine::encode_buffer`] or [`Profile::encode_buffer`] when a
2753    /// different alphabet, padding mode, or line-wrapping profile is required.
2754    fn try_from(input: &str) -> Result<Self, Self::Error> {
2755        Self::try_from(input.as_bytes())
2756    }
2757}
2758
2759/// Stack-backed decoded Base64 output.
2760///
2761/// This type is intended for short decoded values where heap allocation would
2762/// be unnecessary but manually sizing and passing a separate output slice is
2763/// noisy. Decoded data may be binary or secret-bearing, so formatting is
2764/// redacted and contents are exposed only through explicit byte accessors.
2765///
2766/// The backing array is cleared when the value is dropped. This is best-effort
2767/// data-retention reduction and is not a formal zeroization guarantee.
2768pub struct DecodedBuffer<const CAP: usize> {
2769    bytes: [u8; CAP],
2770    len: usize,
2771}
2772
2773impl<const CAP: usize> DecodedBuffer<CAP> {
2774    /// Creates an empty decoded buffer.
2775    #[must_use]
2776    pub const fn new() -> Self {
2777        Self {
2778            bytes: [0u8; CAP],
2779            len: 0,
2780        }
2781    }
2782
2783    /// Returns the number of visible decoded bytes.
2784    #[must_use]
2785    pub const fn len(&self) -> usize {
2786        self.len
2787    }
2788
2789    /// Returns whether the buffer has no visible decoded bytes.
2790    #[must_use]
2791    pub const fn is_empty(&self) -> bool {
2792        self.len == 0
2793    }
2794
2795    /// Returns whether the visible decoded bytes fill the stack backing array.
2796    #[must_use]
2797    pub const fn is_full(&self) -> bool {
2798        self.len == CAP
2799    }
2800
2801    /// Returns the stack capacity in bytes.
2802    #[must_use]
2803    pub const fn capacity(&self) -> usize {
2804        CAP
2805    }
2806
2807    /// Returns the number of unused bytes in the stack backing array.
2808    #[must_use]
2809    pub const fn remaining_capacity(&self) -> usize {
2810        CAP - self.len
2811    }
2812
2813    /// Returns the visible decoded bytes.
2814    #[must_use]
2815    pub fn as_bytes(&self) -> &[u8] {
2816        &self.bytes[..self.len]
2817    }
2818
2819    /// Returns the visible decoded bytes as UTF-8 text.
2820    ///
2821    /// Decoded Base64 output is arbitrary bytes, so this method is fallible.
2822    /// Use [`Self::as_bytes`] when the decoded payload is binary or when text
2823    /// validation belongs to a higher protocol layer.
2824    pub fn as_utf8(&self) -> Result<&str, core::str::Utf8Error> {
2825        core::str::from_utf8(self.as_bytes())
2826    }
2827
2828    /// Compares this decoded output to `other` without short-circuiting on the
2829    /// first differing byte.
2830    ///
2831    /// Length and the final equality result remain public. For equal-length
2832    /// inputs, this helper scans every byte before returning. It is
2833    /// constant-time-oriented best effort, not a formal cryptographic
2834    /// constant-time guarantee.
2835    #[must_use]
2836    pub fn constant_time_eq(&self, other: &[u8]) -> bool {
2837        constant_time_eq_public_len(self.as_bytes(), other)
2838    }
2839
2840    /// Consumes the wrapper and returns the backing array plus visible length.
2841    ///
2842    /// This is an explicit escape hatch for no-alloc interop with APIs that
2843    /// require ownership of a fixed array. The returned array is no longer
2844    /// redacted by formatting and will not be cleared by `DecodedBuffer` on
2845    /// drop; callers that keep handling sensitive data should arrange their
2846    /// own cleanup.
2847    #[must_use]
2848    pub fn into_exposed_array(mut self) -> ([u8; CAP], usize) {
2849        let len = self.len;
2850        self.len = 0;
2851        (core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
2852    }
2853
2854    /// Clears the visible bytes and the full backing array.
2855    pub fn clear(&mut self) {
2856        wipe_bytes(&mut self.bytes);
2857        self.len = 0;
2858    }
2859
2860    /// Clears bytes after the visible prefix.
2861    pub fn clear_tail(&mut self) {
2862        wipe_tail(&mut self.bytes, self.len);
2863    }
2864}
2865
2866impl<const CAP: usize> AsRef<[u8]> for DecodedBuffer<CAP> {
2867    fn as_ref(&self) -> &[u8] {
2868        self.as_bytes()
2869    }
2870}
2871
2872impl<const CAP: usize> Clone for DecodedBuffer<CAP> {
2873    fn clone(&self) -> Self {
2874        let mut output = Self::new();
2875        output.bytes[..self.len].copy_from_slice(self.as_bytes());
2876        output.len = self.len;
2877        output
2878    }
2879}
2880
2881impl<const CAP: usize> core::fmt::Debug for DecodedBuffer<CAP> {
2882    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2883        formatter
2884            .debug_struct("DecodedBuffer")
2885            .field("bytes", &"<redacted>")
2886            .field("len", &self.len)
2887            .field("capacity", &CAP)
2888            .finish()
2889    }
2890}
2891
2892impl<const CAP: usize> Default for DecodedBuffer<CAP> {
2893    fn default() -> Self {
2894        Self::new()
2895    }
2896}
2897
2898impl<const CAP: usize> Drop for DecodedBuffer<CAP> {
2899    fn drop(&mut self) {
2900        self.clear();
2901    }
2902}
2903
2904impl<const CAP: usize> Eq for DecodedBuffer<CAP> {}
2905
2906impl<const CAP: usize> PartialEq for DecodedBuffer<CAP> {
2907    fn eq(&self, other: &Self) -> bool {
2908        self.constant_time_eq(other.as_bytes())
2909    }
2910}
2911
2912impl<const CAP: usize> PartialEq<&[u8]> for DecodedBuffer<CAP> {
2913    fn eq(&self, other: &&[u8]) -> bool {
2914        self.constant_time_eq(other)
2915    }
2916}
2917
2918impl<const CAP: usize, const N: usize> PartialEq<&[u8; N]> for DecodedBuffer<CAP> {
2919    fn eq(&self, other: &&[u8; N]) -> bool {
2920        self.constant_time_eq(&other[..])
2921    }
2922}
2923
2924impl<const CAP: usize> PartialEq<&str> for DecodedBuffer<CAP> {
2925    fn eq(&self, other: &&str) -> bool {
2926        self.constant_time_eq(other.as_bytes())
2927    }
2928}
2929
2930#[cfg(feature = "alloc")]
2931impl<const CAP: usize> PartialEq<alloc::string::String> for DecodedBuffer<CAP> {
2932    fn eq(&self, other: &alloc::string::String) -> bool {
2933        self.constant_time_eq(other.as_bytes())
2934    }
2935}
2936
2937impl<const CAP: usize> PartialEq<DecodedBuffer<CAP>> for &[u8] {
2938    fn eq(&self, other: &DecodedBuffer<CAP>) -> bool {
2939        other.constant_time_eq(self)
2940    }
2941}
2942
2943impl<const CAP: usize, const N: usize> PartialEq<DecodedBuffer<CAP>> for &[u8; N] {
2944    fn eq(&self, other: &DecodedBuffer<CAP>) -> bool {
2945        other.constant_time_eq(&self[..])
2946    }
2947}
2948
2949impl<const CAP: usize> PartialEq<DecodedBuffer<CAP>> for &str {
2950    fn eq(&self, other: &DecodedBuffer<CAP>) -> bool {
2951        other.constant_time_eq(self.as_bytes())
2952    }
2953}
2954
2955#[cfg(feature = "alloc")]
2956impl<const CAP: usize> PartialEq<DecodedBuffer<CAP>> for alloc::string::String {
2957    fn eq(&self, other: &DecodedBuffer<CAP>) -> bool {
2958        other.constant_time_eq(self.as_bytes())
2959    }
2960}
2961
2962impl<const CAP: usize> TryFrom<&[u8]> for DecodedBuffer<CAP> {
2963    type Error = DecodeError;
2964
2965    /// Decodes strict standard padded Base64 into a stack-backed buffer.
2966    ///
2967    /// Use [`Engine::decode_buffer`] or [`Profile::decode_buffer`] when a
2968    /// different alphabet, padding mode, or line-wrapping profile is required.
2969    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
2970        STANDARD.decode_buffer(input)
2971    }
2972}
2973
2974impl<const CAP: usize, const N: usize> TryFrom<&[u8; N]> for DecodedBuffer<CAP> {
2975    type Error = DecodeError;
2976
2977    /// Decodes a strict standard padded Base64 byte array into a stack-backed
2978    /// buffer.
2979    ///
2980    /// Use [`Engine::decode_buffer`] or [`Profile::decode_buffer`] when a
2981    /// different alphabet, padding mode, or line-wrapping profile is required.
2982    fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
2983        Self::try_from(&input[..])
2984    }
2985}
2986
2987impl<const CAP: usize> TryFrom<&str> for DecodedBuffer<CAP> {
2988    type Error = DecodeError;
2989
2990    /// Decodes strict standard padded Base64 text into a stack-backed buffer.
2991    ///
2992    /// Use [`Engine::decode_buffer`] or [`Profile::decode_buffer`] when a
2993    /// different alphabet, padding mode, or line-wrapping profile is required.
2994    fn try_from(input: &str) -> Result<Self, Self::Error> {
2995        Self::try_from(input.as_bytes())
2996    }
2997}
2998
2999impl<const CAP: usize> core::str::FromStr for DecodedBuffer<CAP> {
3000    type Err = DecodeError;
3001
3002    /// Decodes strict standard padded Base64 text into a stack-backed buffer.
3003    ///
3004    /// Use [`Engine::decode_buffer`] or [`Profile::decode_buffer`] when a
3005    /// different alphabet, padding mode, or line-wrapping profile is required.
3006    fn from_str(input: &str) -> Result<Self, Self::Err> {
3007        Self::try_from(input)
3008    }
3009}
3010
3011/// Owned sensitive bytes with redacted formatting and drop-time cleanup.
3012///
3013/// `SecretBuffer` is available with the `alloc` feature. It is intended for
3014/// decoded keys, tokens, and other values that should not be accidentally
3015/// logged. The buffer exposes contents only through explicit reveal methods.
3016///
3017/// Spare vector capacity is cleared when wrapping owned bytes. On drop,
3018/// initialized bytes and vector spare capacity are cleared with the crate's
3019/// internal best-effort wipe helpers. This is data-retention reduction, not a
3020/// formal zeroization guarantee, and it cannot make claims about allocator
3021/// behavior or historical copies outside the wrapper.
3022#[cfg(feature = "alloc")]
3023pub struct SecretBuffer {
3024    bytes: alloc::vec::Vec<u8>,
3025}
3026
3027#[cfg(feature = "alloc")]
3028impl SecretBuffer {
3029    /// Wraps an existing vector as sensitive material.
3030    #[must_use]
3031    pub fn from_vec(mut bytes: alloc::vec::Vec<u8>) -> Self {
3032        wipe_vec_spare_capacity(&mut bytes);
3033        Self { bytes }
3034    }
3035
3036    /// Copies a slice into an owned sensitive buffer.
3037    #[must_use]
3038    pub fn from_slice(bytes: &[u8]) -> Self {
3039        Self::from_vec(bytes.to_vec())
3040    }
3041
3042    /// Returns the number of initialized secret bytes.
3043    #[must_use]
3044    pub fn len(&self) -> usize {
3045        self.bytes.len()
3046    }
3047
3048    /// Returns whether the buffer contains no initialized secret bytes.
3049    #[must_use]
3050    pub fn is_empty(&self) -> bool {
3051        self.bytes.is_empty()
3052    }
3053
3054    /// Reveals the secret bytes.
3055    ///
3056    /// This method is intentionally named to make secret access explicit at the
3057    /// call site.
3058    #[must_use]
3059    pub fn expose_secret(&self) -> &[u8] {
3060        &self.bytes
3061    }
3062
3063    /// Reveals the secret bytes as UTF-8 text.
3064    ///
3065    /// This method is intentionally named to make secret access explicit at the
3066    /// call site. Secret material may be arbitrary binary data, so this method
3067    /// is fallible.
3068    pub fn expose_secret_utf8(&self) -> Result<&str, core::str::Utf8Error> {
3069        core::str::from_utf8(self.expose_secret())
3070    }
3071
3072    /// Reveals the secret bytes mutably.
3073    ///
3074    /// This method is intentionally named to make secret access explicit at the
3075    /// call site.
3076    #[must_use]
3077    pub fn expose_secret_mut(&mut self) -> &mut [u8] {
3078        &mut self.bytes
3079    }
3080
3081    /// Consumes the wrapper and returns the owned secret bytes.
3082    ///
3083    /// This is an explicit escape hatch for interop with APIs that require an
3084    /// owned vector. The returned `Vec<u8>` is no longer redacted by
3085    /// formatting and will not be cleared by `SecretBuffer` on drop; callers
3086    /// that keep handling sensitive data should arrange their own cleanup.
3087    #[must_use]
3088    pub fn into_exposed_vec(mut self) -> alloc::vec::Vec<u8> {
3089        core::mem::take(&mut self.bytes)
3090    }
3091
3092    /// Consumes the wrapper and returns the owned secret bytes as UTF-8 text.
3093    ///
3094    /// This is an explicit escape hatch for interop with APIs that require an
3095    /// owned string. The returned `String` is no longer redacted by formatting
3096    /// and will not be cleared by `SecretBuffer` on drop; callers that keep
3097    /// handling sensitive data should arrange their own cleanup.
3098    ///
3099    /// If the secret bytes are not valid UTF-8, the original redacted wrapper
3100    /// is returned unchanged.
3101    pub fn try_into_exposed_string(self) -> Result<alloc::string::String, Self> {
3102        if core::str::from_utf8(self.expose_secret()).is_err() {
3103            return Err(self);
3104        }
3105
3106        match alloc::string::String::from_utf8(self.into_exposed_vec()) {
3107            Ok(text) => Ok(text),
3108            Err(error) => Err(Self::from_vec(error.into_bytes())),
3109        }
3110    }
3111
3112    /// Compares this secret to `other` without short-circuiting on the first
3113    /// differing byte.
3114    ///
3115    /// Length and the final equality result remain public. For equal-length
3116    /// inputs, this helper scans every byte before returning. It is
3117    /// constant-time-oriented best effort, not a formal cryptographic
3118    /// constant-time guarantee.
3119    #[must_use]
3120    pub fn constant_time_eq(&self, other: &[u8]) -> bool {
3121        constant_time_eq_public_len(self.expose_secret(), other)
3122    }
3123
3124    /// Clears the initialized bytes and makes the buffer empty.
3125    pub fn clear(&mut self) {
3126        wipe_vec_all(&mut self.bytes);
3127        self.bytes.clear();
3128    }
3129}
3130
3131#[cfg(feature = "alloc")]
3132impl Clone for SecretBuffer {
3133    fn clone(&self) -> Self {
3134        Self::from_slice(self.expose_secret())
3135    }
3136}
3137
3138#[cfg(feature = "alloc")]
3139impl core::fmt::Debug for SecretBuffer {
3140    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3141        formatter
3142            .debug_struct("SecretBuffer")
3143            .field("bytes", &"<redacted>")
3144            .field("len", &self.len())
3145            .finish()
3146    }
3147}
3148
3149#[cfg(feature = "alloc")]
3150impl core::fmt::Display for SecretBuffer {
3151    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3152        formatter.write_str("<redacted>")
3153    }
3154}
3155
3156#[cfg(feature = "alloc")]
3157impl Drop for SecretBuffer {
3158    fn drop(&mut self) {
3159        wipe_vec_all(&mut self.bytes);
3160    }
3161}
3162
3163#[cfg(feature = "alloc")]
3164impl Eq for SecretBuffer {}
3165
3166#[cfg(feature = "alloc")]
3167impl PartialEq for SecretBuffer {
3168    fn eq(&self, other: &Self) -> bool {
3169        self.constant_time_eq(other.expose_secret())
3170    }
3171}
3172
3173#[cfg(feature = "alloc")]
3174impl PartialEq<&[u8]> for SecretBuffer {
3175    fn eq(&self, other: &&[u8]) -> bool {
3176        self.constant_time_eq(other)
3177    }
3178}
3179
3180#[cfg(feature = "alloc")]
3181impl<const N: usize> PartialEq<&[u8; N]> for SecretBuffer {
3182    fn eq(&self, other: &&[u8; N]) -> bool {
3183        self.constant_time_eq(&other[..])
3184    }
3185}
3186
3187#[cfg(feature = "alloc")]
3188impl PartialEq<&str> for SecretBuffer {
3189    fn eq(&self, other: &&str) -> bool {
3190        self.constant_time_eq(other.as_bytes())
3191    }
3192}
3193
3194#[cfg(feature = "alloc")]
3195impl PartialEq<alloc::string::String> for SecretBuffer {
3196    fn eq(&self, other: &alloc::string::String) -> bool {
3197        self.constant_time_eq(other.as_bytes())
3198    }
3199}
3200
3201#[cfg(feature = "alloc")]
3202impl PartialEq<SecretBuffer> for &[u8] {
3203    fn eq(&self, other: &SecretBuffer) -> bool {
3204        other.constant_time_eq(self)
3205    }
3206}
3207
3208#[cfg(feature = "alloc")]
3209impl<const N: usize> PartialEq<SecretBuffer> for &[u8; N] {
3210    fn eq(&self, other: &SecretBuffer) -> bool {
3211        other.constant_time_eq(&self[..])
3212    }
3213}
3214
3215#[cfg(feature = "alloc")]
3216impl PartialEq<SecretBuffer> for &str {
3217    fn eq(&self, other: &SecretBuffer) -> bool {
3218        other.constant_time_eq(self.as_bytes())
3219    }
3220}
3221
3222#[cfg(feature = "alloc")]
3223impl PartialEq<SecretBuffer> for alloc::string::String {
3224    fn eq(&self, other: &SecretBuffer) -> bool {
3225        other.constant_time_eq(self.as_bytes())
3226    }
3227}
3228
3229#[cfg(feature = "alloc")]
3230impl From<alloc::vec::Vec<u8>> for SecretBuffer {
3231    /// Wraps an owned vector as sensitive material.
3232    ///
3233    /// Spare capacity is cleared immediately before the vector is stored.
3234    /// Use [`SecretBuffer::from_slice`] when the source data is borrowed.
3235    fn from(bytes: alloc::vec::Vec<u8>) -> Self {
3236        Self::from_vec(bytes)
3237    }
3238}
3239
3240#[cfg(feature = "alloc")]
3241impl From<alloc::string::String> for SecretBuffer {
3242    /// Wraps an owned UTF-8 string as sensitive material.
3243    ///
3244    /// The string is consumed without copying its initialized bytes. Spare
3245    /// vector capacity is cleared immediately before the bytes are stored.
3246    fn from(text: alloc::string::String) -> Self {
3247        Self::from_vec(text.into_bytes())
3248    }
3249}
3250
3251#[cfg(feature = "alloc")]
3252impl<const CAP: usize> From<EncodedBuffer<CAP>> for SecretBuffer {
3253    /// Copies visible encoded bytes from a stack-backed buffer into an owned
3254    /// redacted buffer.
3255    ///
3256    /// The consumed stack-backed buffer clears its backing array when it is
3257    /// dropped at the end of the conversion.
3258    fn from(buffer: EncodedBuffer<CAP>) -> Self {
3259        Self::from_slice(buffer.as_bytes())
3260    }
3261}
3262
3263#[cfg(feature = "alloc")]
3264impl<const CAP: usize> From<DecodedBuffer<CAP>> for SecretBuffer {
3265    /// Copies visible decoded bytes from a stack-backed buffer into an owned
3266    /// redacted buffer.
3267    ///
3268    /// The consumed stack-backed buffer clears its backing array when it is
3269    /// dropped at the end of the conversion.
3270    fn from(buffer: DecodedBuffer<CAP>) -> Self {
3271        Self::from_slice(buffer.as_bytes())
3272    }
3273}
3274
3275#[cfg(feature = "alloc")]
3276impl TryFrom<&[u8]> for SecretBuffer {
3277    type Error = DecodeError;
3278
3279    /// Decodes strict standard padded Base64 into a redacted owned buffer.
3280    ///
3281    /// Use [`Engine::decode_secret`] or [`Profile::decode_secret`] when a
3282    /// different alphabet, padding mode, or line-wrapping profile is required.
3283    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
3284        STANDARD.decode_secret(input)
3285    }
3286}
3287
3288#[cfg(feature = "alloc")]
3289impl<const N: usize> TryFrom<&[u8; N]> for SecretBuffer {
3290    type Error = DecodeError;
3291
3292    /// Decodes a strict standard padded Base64 byte array into a redacted
3293    /// owned buffer.
3294    ///
3295    /// Use [`Engine::decode_secret`] or [`Profile::decode_secret`] when a
3296    /// different alphabet, padding mode, or line-wrapping profile is required.
3297    fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
3298        Self::try_from(&input[..])
3299    }
3300}
3301
3302#[cfg(feature = "alloc")]
3303impl TryFrom<&str> for SecretBuffer {
3304    type Error = DecodeError;
3305
3306    /// Decodes strict standard padded Base64 text into a redacted owned buffer.
3307    ///
3308    /// Use [`Engine::decode_secret`] or [`Profile::decode_secret`] when a
3309    /// different alphabet, padding mode, or line-wrapping profile is required.
3310    fn try_from(input: &str) -> Result<Self, Self::Error> {
3311        Self::try_from(input.as_bytes())
3312    }
3313}
3314
3315#[cfg(feature = "alloc")]
3316impl core::str::FromStr for SecretBuffer {
3317    type Err = DecodeError;
3318
3319    /// Decodes strict standard padded Base64 text into a redacted owned buffer.
3320    ///
3321    /// Use [`Engine::decode_secret`] or [`Profile::decode_secret`] when a
3322    /// different alphabet, padding mode, or line-wrapping profile is required.
3323    fn from_str(input: &str) -> Result<Self, Self::Err> {
3324        Self::try_from(input)
3325    }
3326}
3327
3328/// A named Base64 profile with an engine and optional strict line wrapping.
3329///
3330/// Profiles are convenience values for protocol-shaped Base64. They keep the
3331/// same strict alphabet, padding, canonical-bit, and output-buffer rules as
3332/// [`Engine`], while carrying the wrapping policy for MIME/PEM-like formats.
3333#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3334pub struct Profile<A, const PAD: bool> {
3335    engine: Engine<A, PAD>,
3336    wrap: Option<LineWrap>,
3337}
3338
3339impl<A, const PAD: bool> Profile<A, PAD>
3340where
3341    A: Alphabet,
3342{
3343    /// Creates a profile from an engine and optional strict line wrapping.
3344    #[must_use]
3345    pub const fn new(engine: Engine<A, PAD>, wrap: Option<LineWrap>) -> Self {
3346        Self { engine, wrap }
3347    }
3348
3349    /// Creates a profile, returning `None` when the wrapping policy is invalid.
3350    ///
3351    /// This is useful when a profile is assembled from configuration or other
3352    /// untrusted metadata. Use [`Self::new`] for compile-time constants where
3353    /// the wrapping policy is known to be valid.
3354    #[must_use]
3355    pub const fn checked_new(engine: Engine<A, PAD>, wrap: Option<LineWrap>) -> Option<Self> {
3356        match wrap {
3357            Some(wrap) if !wrap.is_valid() => None,
3358            _ => Some(Self::new(engine, wrap)),
3359        }
3360    }
3361
3362    /// Returns whether this profile can be used by encoders and decoders.
3363    #[must_use]
3364    pub const fn is_valid(&self) -> bool {
3365        match self.wrap {
3366            Some(wrap) => wrap.is_valid(),
3367            None => true,
3368        }
3369    }
3370
3371    /// Returns the underlying engine.
3372    #[must_use]
3373    pub const fn engine(&self) -> Engine<A, PAD> {
3374        self.engine
3375    }
3376
3377    /// Returns whether this profile uses padded Base64.
3378    #[must_use]
3379    pub const fn is_padded(&self) -> bool {
3380        PAD
3381    }
3382
3383    /// Returns whether this profile carries a strict line-wrapping policy.
3384    #[must_use]
3385    pub const fn is_wrapped(&self) -> bool {
3386        self.wrap.is_some()
3387    }
3388
3389    /// Returns the strict wrapping policy carried by this profile, if any.
3390    #[must_use]
3391    pub const fn line_wrap(&self) -> Option<LineWrap> {
3392        self.wrap
3393    }
3394
3395    /// Returns the encoded line length for wrapped profiles.
3396    #[must_use]
3397    pub const fn line_len(&self) -> Option<usize> {
3398        match self.wrap {
3399            Some(wrap) => Some(wrap.line_len()),
3400            None => None,
3401        }
3402    }
3403
3404    /// Returns the line ending for wrapped profiles.
3405    #[must_use]
3406    pub const fn line_ending(&self) -> Option<LineEnding> {
3407        match self.wrap {
3408            Some(wrap) => Some(wrap.line_ending()),
3409            None => None,
3410        }
3411    }
3412
3413    /// Returns the encoded length for this profile.
3414    pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
3415        match self.wrap {
3416            Some(wrap) => wrapped_encoded_len(input_len, PAD, wrap),
3417            None => encoded_len(input_len, PAD),
3418        }
3419    }
3420
3421    /// Returns the encoded length for this profile, or `None` on overflow or
3422    /// invalid line wrapping.
3423    #[must_use]
3424    pub const fn checked_encoded_len(&self, input_len: usize) -> Option<usize> {
3425        match self.wrap {
3426            Some(wrap) => checked_wrapped_encoded_len(input_len, PAD, wrap),
3427            None => checked_encoded_len(input_len, PAD),
3428        }
3429    }
3430
3431    /// Returns the exact decoded length for this profile.
3432    pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
3433        match self.wrap {
3434            Some(wrap) => self.engine.decoded_len_wrapped(input, wrap),
3435            None => self.engine.decoded_len(input),
3436        }
3437    }
3438
3439    /// Validates input according to this profile without writing decoded bytes.
3440    pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
3441        match self.wrap {
3442            Some(wrap) => self.engine.validate_wrapped_result(input, wrap),
3443            None => self.engine.validate_result(input),
3444        }
3445    }
3446
3447    /// Returns whether `input` is valid for this profile.
3448    #[must_use]
3449    pub fn validate(&self, input: &[u8]) -> bool {
3450        self.validate_result(input).is_ok()
3451    }
3452
3453    /// Encodes `input` into `output` according to this profile.
3454    pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
3455        match self.wrap {
3456            Some(wrap) => self.engine.encode_slice_wrapped(input, output, wrap),
3457            None => self.engine.encode_slice(input, output),
3458        }
3459    }
3460
3461    /// Encodes `input` into `output` and clears all bytes after the encoded
3462    /// prefix.
3463    pub fn encode_slice_clear_tail(
3464        &self,
3465        input: &[u8],
3466        output: &mut [u8],
3467    ) -> Result<usize, EncodeError> {
3468        match self.wrap {
3469            Some(wrap) => self
3470                .engine
3471                .encode_slice_wrapped_clear_tail(input, output, wrap),
3472            None => self.engine.encode_slice_clear_tail(input, output),
3473        }
3474    }
3475
3476    /// Encodes `input` into a stack-backed buffer.
3477    ///
3478    /// This is useful for short values where heap allocation is unnecessary.
3479    /// If encoding fails, the internal backing array is cleared before the
3480    /// error is returned.
3481    pub fn encode_buffer<const CAP: usize>(
3482        &self,
3483        input: &[u8],
3484    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
3485        let mut output = EncodedBuffer::new();
3486        let written = match self.encode_slice_clear_tail(input, &mut output.bytes) {
3487            Ok(written) => written,
3488            Err(err) => {
3489                output.clear();
3490                return Err(err);
3491            }
3492        };
3493        output.len = written;
3494        Ok(output)
3495    }
3496
3497    /// Decodes `input` into `output` according to this profile.
3498    pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
3499        match self.wrap {
3500            Some(wrap) => self.engine.decode_slice_wrapped(input, output, wrap),
3501            None => self.engine.decode_slice(input, output),
3502        }
3503    }
3504
3505    /// Decodes `input` into `output` and clears all bytes after the decoded
3506    /// prefix.
3507    pub fn decode_slice_clear_tail(
3508        &self,
3509        input: &[u8],
3510        output: &mut [u8],
3511    ) -> Result<usize, DecodeError> {
3512        match self.wrap {
3513            Some(wrap) => self
3514                .engine
3515                .decode_slice_wrapped_clear_tail(input, output, wrap),
3516            None => self.engine.decode_slice_clear_tail(input, output),
3517        }
3518    }
3519
3520    /// Decodes `input` into a stack-backed buffer according to this profile.
3521    ///
3522    /// This is useful for short decoded values where heap allocation is
3523    /// unnecessary. If decoding fails, the internal backing array is cleared
3524    /// before the error is returned.
3525    pub fn decode_buffer<const CAP: usize>(
3526        &self,
3527        input: &[u8],
3528    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
3529        let mut output = DecodedBuffer::new();
3530        let written = match self.decode_slice_clear_tail(input, &mut output.bytes) {
3531            Ok(written) => written,
3532            Err(err) => {
3533                output.clear();
3534                return Err(err);
3535            }
3536        };
3537        output.len = written;
3538        Ok(output)
3539    }
3540
3541    /// Decodes `buffer` in place according to this profile.
3542    ///
3543    /// For wrapped profiles, configured line endings are compacted out before
3544    /// decoding. If validation fails, the buffer contents are unspecified.
3545    ///
3546    /// # Examples
3547    ///
3548    /// ```
3549    /// use base64_ng::{LineEnding, LineWrap, Profile, STANDARD};
3550    ///
3551    /// let profile = Profile::new(STANDARD, Some(LineWrap::new(4, LineEnding::Lf)));
3552    /// let mut buffer = *b"aGVs\nbG8=";
3553    /// let decoded = profile.decode_in_place(&mut buffer).unwrap();
3554    ///
3555    /// assert_eq!(decoded, b"hello");
3556    /// ```
3557    pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
3558        match self.wrap {
3559            Some(wrap) => self.engine.decode_in_place_wrapped(buffer, wrap),
3560            None => self.engine.decode_in_place(buffer),
3561        }
3562    }
3563
3564    /// Decodes `buffer` in place according to this profile and clears all
3565    /// bytes after the decoded prefix.
3566    ///
3567    /// If validation or decoding fails, the entire buffer is cleared before the
3568    /// error is returned.
3569    ///
3570    /// # Examples
3571    ///
3572    /// ```
3573    /// use base64_ng::{LineEnding, LineWrap, Profile, STANDARD};
3574    ///
3575    /// let profile = Profile::new(STANDARD, Some(LineWrap::new(4, LineEnding::Lf)));
3576    /// let mut buffer = *b"aGVs\nbG8=";
3577    /// let len = profile.decode_in_place_clear_tail(&mut buffer).unwrap().len();
3578    ///
3579    /// assert_eq!(&buffer[..len], b"hello");
3580    /// assert!(buffer[len..].iter().all(|byte| *byte == 0));
3581    /// ```
3582    pub fn decode_in_place_clear_tail<'a>(
3583        &self,
3584        buffer: &'a mut [u8],
3585    ) -> Result<&'a mut [u8], DecodeError> {
3586        match self.wrap {
3587            Some(wrap) => self.engine.decode_in_place_wrapped_clear_tail(buffer, wrap),
3588            None => self.engine.decode_in_place_clear_tail(buffer),
3589        }
3590    }
3591
3592    /// Encodes `input` into a newly allocated byte vector.
3593    #[cfg(feature = "alloc")]
3594    pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
3595        match self.wrap {
3596            Some(wrap) => self.engine.encode_wrapped_vec(input, wrap),
3597            None => self.engine.encode_vec(input),
3598        }
3599    }
3600
3601    /// Encodes `input` into a redacted owned secret buffer.
3602    #[cfg(feature = "alloc")]
3603    pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
3604        self.encode_vec(input).map(SecretBuffer::from_vec)
3605    }
3606
3607    /// Encodes `input` into a newly allocated UTF-8 string.
3608    #[cfg(feature = "alloc")]
3609    pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
3610        match self.wrap {
3611            Some(wrap) => self.engine.encode_wrapped_string(input, wrap),
3612            None => self.engine.encode_string(input),
3613        }
3614    }
3615
3616    /// Decodes `input` into a newly allocated byte vector.
3617    #[cfg(feature = "alloc")]
3618    pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
3619        match self.wrap {
3620            Some(wrap) => self.engine.decode_wrapped_vec(input, wrap),
3621            None => self.engine.decode_vec(input),
3622        }
3623    }
3624
3625    /// Decodes `input` into a redacted owned secret buffer.
3626    #[cfg(feature = "alloc")]
3627    pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
3628        self.decode_vec(input).map(SecretBuffer::from_vec)
3629    }
3630}
3631
3632impl<A, const PAD: bool> Default for Profile<A, PAD>
3633where
3634    A: Alphabet,
3635{
3636    fn default() -> Self {
3637        Self::new(Engine::new(), None)
3638    }
3639}
3640
3641impl<A, const PAD: bool> core::fmt::Display for Profile<A, PAD>
3642where
3643    A: Alphabet,
3644{
3645    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3646        match self.wrap {
3647            Some(wrap) => write!(formatter, "padded={PAD} wrap={wrap}"),
3648            None => write!(formatter, "padded={PAD} wrap=none"),
3649        }
3650    }
3651}
3652
3653impl<A, const PAD: bool> From<Engine<A, PAD>> for Profile<A, PAD>
3654where
3655    A: Alphabet,
3656{
3657    fn from(engine: Engine<A, PAD>) -> Self {
3658        Self::new(engine, None)
3659    }
3660}
3661
3662/// MIME Base64 profile: standard alphabet, padding, 76-column CRLF wrapping.
3663pub const MIME: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::MIME));
3664
3665/// PEM Base64 profile: standard alphabet, padding, 64-column LF wrapping.
3666pub const PEM: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::PEM));
3667
3668/// PEM Base64 profile with CRLF line endings.
3669pub const PEM_CRLF: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::PEM_CRLF));
3670
3671/// bcrypt-style no-padding Base64 profile.
3672///
3673/// This profile carries the bcrypt alphabet and no padding. It does not parse
3674/// complete bcrypt password-hash strings.
3675pub const BCRYPT: Profile<Bcrypt, false> = Profile::new(BCRYPT_NO_PAD, None);
3676
3677/// Unix `crypt(3)`-style no-padding Base64 profile.
3678///
3679/// This profile carries the `crypt(3)` alphabet and no padding. It does not
3680/// parse complete password-hash strings.
3681pub const CRYPT: Profile<Crypt, false> = Profile::new(CRYPT_NO_PAD, None);
3682
3683/// Returns the encoded length for an input length and padding policy.
3684///
3685/// This function returns [`EncodeError::LengthOverflow`] instead of panicking.
3686/// Use [`checked_encoded_len`] when an `Option<usize>` is more convenient.
3687///
3688/// # Examples
3689///
3690/// ```
3691/// use base64_ng::encoded_len;
3692///
3693/// assert_eq!(encoded_len(5, true).unwrap(), 8);
3694/// assert_eq!(encoded_len(5, false).unwrap(), 7);
3695/// assert!(encoded_len(usize::MAX, true).is_err());
3696/// ```
3697pub const fn encoded_len(input_len: usize, padded: bool) -> Result<usize, EncodeError> {
3698    match checked_encoded_len(input_len, padded) {
3699        Some(len) => Ok(len),
3700        None => Err(EncodeError::LengthOverflow),
3701    }
3702}
3703
3704/// Returns the encoded length after applying a line wrapping policy.
3705///
3706/// The returned length includes inserted line endings but does not include a
3707/// trailing line ending after the final encoded line.
3708///
3709/// # Examples
3710///
3711/// ```
3712/// use base64_ng::{LineEnding, LineWrap, wrapped_encoded_len};
3713///
3714/// let wrap = LineWrap::new(4, LineEnding::Lf);
3715/// assert_eq!(wrapped_encoded_len(5, true, wrap).unwrap(), 9);
3716/// ```
3717pub const fn wrapped_encoded_len(
3718    input_len: usize,
3719    padded: bool,
3720    wrap: LineWrap,
3721) -> Result<usize, EncodeError> {
3722    if wrap.line_len == 0 {
3723        return Err(EncodeError::InvalidLineWrap { line_len: 0 });
3724    }
3725
3726    let Some(encoded) = checked_encoded_len(input_len, padded) else {
3727        return Err(EncodeError::LengthOverflow);
3728    };
3729    if encoded == 0 {
3730        return Ok(0);
3731    }
3732
3733    let breaks = (encoded - 1) / wrap.line_len;
3734    let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
3735        return Err(EncodeError::LengthOverflow);
3736    };
3737    match encoded.checked_add(line_ending_bytes) {
3738        Some(len) => Ok(len),
3739        None => Err(EncodeError::LengthOverflow),
3740    }
3741}
3742
3743/// Returns the encoded length after line wrapping, or `None` on overflow or
3744/// invalid line wrapping.
3745///
3746/// The returned length includes inserted line endings but does not include a
3747/// trailing line ending after the final encoded line.
3748///
3749/// # Examples
3750///
3751/// ```
3752/// use base64_ng::{LineEnding, LineWrap, checked_wrapped_encoded_len};
3753///
3754/// let wrap = LineWrap::new(4, LineEnding::Lf);
3755/// assert_eq!(checked_wrapped_encoded_len(5, true, wrap), Some(9));
3756/// assert_eq!(checked_wrapped_encoded_len(5, true, LineWrap::new(0, LineEnding::Lf)), None);
3757/// ```
3758#[must_use]
3759pub const fn checked_wrapped_encoded_len(
3760    input_len: usize,
3761    padded: bool,
3762    wrap: LineWrap,
3763) -> Option<usize> {
3764    if wrap.line_len == 0 {
3765        return None;
3766    }
3767
3768    let Some(encoded) = checked_encoded_len(input_len, padded) else {
3769        return None;
3770    };
3771    if encoded == 0 {
3772        return Some(0);
3773    }
3774
3775    let breaks = (encoded - 1) / wrap.line_len;
3776    let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
3777        return None;
3778    };
3779    encoded.checked_add(line_ending_bytes)
3780}
3781
3782/// Returns the encoded length, or `None` if it would overflow `usize`.
3783///
3784/// # Examples
3785///
3786/// ```
3787/// use base64_ng::checked_encoded_len;
3788///
3789/// assert_eq!(checked_encoded_len(5, true), Some(8));
3790/// assert_eq!(checked_encoded_len(usize::MAX, true), None);
3791/// ```
3792#[must_use]
3793pub const fn checked_encoded_len(input_len: usize, padded: bool) -> Option<usize> {
3794    let groups = input_len / 3;
3795    if groups > usize::MAX / 4 {
3796        return None;
3797    }
3798    let full = groups * 4;
3799    let rem = input_len % 3;
3800    if rem == 0 {
3801        Some(full)
3802    } else if padded {
3803        full.checked_add(4)
3804    } else {
3805        full.checked_add(rem + 1)
3806    }
3807}
3808
3809/// Returns the maximum decoded length for an encoded input length.
3810///
3811/// # Examples
3812///
3813/// ```
3814/// use base64_ng::decoded_capacity;
3815///
3816/// assert_eq!(decoded_capacity(8), 6);
3817/// assert_eq!(decoded_capacity(7), 5);
3818/// ```
3819#[must_use]
3820pub const fn decoded_capacity(encoded_len: usize) -> usize {
3821    let rem = encoded_len % 4;
3822    encoded_len / 4 * 3
3823        + if rem == 2 {
3824            1
3825        } else if rem == 3 {
3826            2
3827        } else {
3828            0
3829        }
3830}
3831
3832/// Returns the exact decoded length implied by input length and padding.
3833///
3834/// This validates padding placement and impossible lengths, but it does not
3835/// validate alphabet membership or non-canonical trailing bits.
3836///
3837/// # Examples
3838///
3839/// ```
3840/// use base64_ng::decoded_len;
3841///
3842/// assert_eq!(decoded_len(b"aGVsbG8=", true).unwrap(), 5);
3843/// assert_eq!(decoded_len(b"aGVsbG8", false).unwrap(), 5);
3844/// ```
3845pub fn decoded_len(input: &[u8], padded: bool) -> Result<usize, DecodeError> {
3846    if padded {
3847        decoded_len_padded(input)
3848    } else {
3849        decoded_len_unpadded(input)
3850    }
3851}
3852
3853/// Defines a custom [`Alphabet`] from a 64-byte string literal.
3854///
3855/// The generated alphabet is validated at compile time with
3856/// [`validate_alphabet`]. Invalid, duplicate, or padding bytes fail the build
3857/// instead of creating a malformed runtime profile.
3858///
3859/// The generated implementation uses the conservative default
3860/// [`Alphabet::encode`] behavior: every emitted Base64 byte performs a fixed
3861/// 64-entry scan to avoid secret-indexed table lookups. Built-in alphabets use
3862/// optimized arithmetic mappers.
3863///
3864/// # Examples
3865///
3866/// ```
3867/// base64_ng::define_alphabet! {
3868///     struct DotSlash = b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
3869/// }
3870///
3871/// let engine = base64_ng::Engine::<DotSlash, false>::new();
3872/// let mut encoded = [0u8; 4];
3873/// let written = engine.encode_slice(&[0xff, 0xff, 0xff], &mut encoded).unwrap();
3874/// assert_eq!(&encoded[..written], b"9999");
3875/// ```
3876///
3877/// Invalid alphabets fail during compilation:
3878///
3879/// ```compile_fail
3880/// base64_ng::define_alphabet! {
3881///     struct Bad = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
3882/// }
3883/// ```
3884#[macro_export]
3885macro_rules! define_alphabet {
3886    ($(#[$meta:meta])* $vis:vis struct $name:ident = $encode:expr;) => {
3887        $(#[$meta])*
3888        #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
3889        $vis struct $name;
3890
3891        impl $crate::Alphabet for $name {
3892            const ENCODE: [u8; 64] = *$encode;
3893
3894            #[inline]
3895            fn decode(byte: u8) -> Option<u8> {
3896                $crate::decode_alphabet_byte(byte, &Self::ENCODE)
3897            }
3898        }
3899
3900        const _: [(); 1] = [(); match $crate::validate_alphabet(
3901            &<$name as $crate::Alphabet>::ENCODE,
3902        ) {
3903            Ok(()) => 1,
3904            Err(_) => 0,
3905        }];
3906    };
3907}
3908
3909/// Validates a 64-byte Base64 alphabet table.
3910///
3911/// A valid alphabet must contain exactly 64 unique visible ASCII bytes and must
3912/// not contain the padding byte `=`.
3913///
3914/// # Examples
3915///
3916/// ```
3917/// use base64_ng::{Alphabet, Standard, validate_alphabet};
3918///
3919/// validate_alphabet(&Standard::ENCODE).unwrap();
3920/// ```
3921pub const fn validate_alphabet(encode: &[u8; 64]) -> Result<(), AlphabetError> {
3922    let mut index = 0;
3923    while index < encode.len() {
3924        let byte = encode[index];
3925        if !is_visible_ascii(byte) {
3926            return Err(AlphabetError::InvalidByte { index, byte });
3927        }
3928        if byte == b'=' {
3929            return Err(AlphabetError::PaddingByte { index });
3930        }
3931
3932        let mut duplicate = index + 1;
3933        while duplicate < encode.len() {
3934            if encode[duplicate] == byte {
3935                return Err(AlphabetError::DuplicateByte {
3936                    first: index,
3937                    second: duplicate,
3938                    byte,
3939                });
3940            }
3941            duplicate += 1;
3942        }
3943
3944        index += 1;
3945    }
3946
3947    Ok(())
3948}
3949
3950/// Decodes one byte by scanning a caller-provided alphabet table.
3951///
3952/// This helper is intended for custom [`Alphabet`] implementations. Validate
3953/// the table with [`validate_alphabet`] before trusting the alphabet in a
3954/// protocol or public API. The scan always visits all 64 entries before
3955/// returning so the match position does not create an early-return timing
3956/// signal in custom alphabet decoders.
3957///
3958/// # Examples
3959///
3960/// ```
3961/// use base64_ng::{Alphabet, decode_alphabet_byte};
3962///
3963/// struct DotSlash;
3964///
3965/// impl Alphabet for DotSlash {
3966///     const ENCODE: [u8; 64] =
3967///         *b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
3968///
3969///     fn decode(byte: u8) -> Option<u8> {
3970///         decode_alphabet_byte(byte, &Self::ENCODE)
3971///     }
3972/// }
3973///
3974/// assert_eq!(DotSlash::decode(b'.'), Some(0));
3975/// assert_eq!(DotSlash::decode(b'9'), Some(63));
3976/// ```
3977#[must_use]
3978pub const fn decode_alphabet_byte(byte: u8, encode: &[u8; 64]) -> Option<u8> {
3979    let mut index = 0;
3980    let mut candidate = 0;
3981    let mut decoded = 0;
3982    let mut valid = 0;
3983    while index < encode.len() {
3984        let matches = ct_mask_eq_u8(byte, encode[index]);
3985        decoded |= candidate & matches;
3986        valid |= matches;
3987        index += 1;
3988        candidate += 1;
3989    }
3990
3991    if valid == 0 { None } else { Some(decoded) }
3992}
3993
3994/// A Base64 alphabet.
3995pub trait Alphabet {
3996    /// Encoding table indexed by 6-bit values.
3997    const ENCODE: [u8; 64];
3998
3999    /// Encode one 6-bit value into an alphabet byte.
4000    ///
4001    /// The default implementation scans the alphabet table instead of using a
4002    /// secret-indexed table lookup. Built-in alphabets override this with the
4003    /// branch-minimized ASCII arithmetic mapper. Custom alphabets that keep the
4004    /// default method prioritize timing posture over throughput: every emitted
4005    /// Base64 byte performs a fixed 64-entry scan. For massive payloads with
4006    /// user-defined alphabets, profile this cost and consider an audited custom
4007    /// override only if the alphabet has a structure that can be mapped without
4008    /// secret-indexed table access.
4009    #[must_use]
4010    fn encode(value: u8) -> u8 {
4011        encode_alphabet_value(value, &Self::ENCODE)
4012    }
4013
4014    /// Decode one byte into a 6-bit value.
4015    fn decode(byte: u8) -> Option<u8>;
4016}
4017
4018const fn is_visible_ascii(byte: u8) -> bool {
4019    byte >= 0x21 && byte <= 0x7e
4020}
4021
4022/// The RFC 4648 standard Base64 alphabet.
4023#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
4024pub struct Standard;
4025
4026impl Alphabet for Standard {
4027    const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4028
4029    #[inline]
4030    fn encode(value: u8) -> u8 {
4031        encode_ascii_base64(value, Self::ENCODE[62], Self::ENCODE[63])
4032    }
4033
4034    #[inline]
4035    fn decode(byte: u8) -> Option<u8> {
4036        decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
4037    }
4038}
4039
4040/// The RFC 4648 URL-safe Base64 alphabet.
4041#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
4042pub struct UrlSafe;
4043
4044impl Alphabet for UrlSafe {
4045    const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
4046
4047    #[inline]
4048    fn encode(value: u8) -> u8 {
4049        encode_ascii_base64(value, Self::ENCODE[62], Self::ENCODE[63])
4050    }
4051
4052    #[inline]
4053    fn decode(byte: u8) -> Option<u8> {
4054        decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
4055    }
4056}
4057
4058/// The bcrypt Base64 alphabet.
4059///
4060/// This alphabet is commonly used by bcrypt hash strings. It is provided as an
4061/// alphabet/profile building block; `base64-ng` does not parse or verify full
4062/// bcrypt password-hash records.
4063#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
4064pub struct Bcrypt;
4065
4066impl Alphabet for Bcrypt {
4067    const ENCODE: [u8; 64] = *b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
4068
4069    #[inline]
4070    fn decode(byte: u8) -> Option<u8> {
4071        decode_alphabet_byte(byte, &Self::ENCODE)
4072    }
4073}
4074
4075/// The Unix `crypt(3)` Base64 alphabet.
4076///
4077/// This alphabet is provided as an explicit legacy interoperability profile.
4078/// `base64-ng` does not parse or verify complete password-hash records.
4079#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
4080pub struct Crypt;
4081
4082impl Alphabet for Crypt {
4083    const ENCODE: [u8; 64] = *b"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
4084
4085    #[inline]
4086    fn decode(byte: u8) -> Option<u8> {
4087        decode_alphabet_byte(byte, &Self::ENCODE)
4088    }
4089}
4090
4091#[inline]
4092const fn encode_base64_value<A: Alphabet>(value: u8) -> u8 {
4093    encode_alphabet_value(value, &A::ENCODE)
4094}
4095
4096#[inline]
4097fn encode_base64_value_runtime<A: Alphabet>(value: u8) -> u8 {
4098    A::encode(value)
4099}
4100
4101#[inline]
4102const fn encode_alphabet_value(value: u8, encode: &[u8; 64]) -> u8 {
4103    let mut output = 0;
4104    let mut index = 0;
4105    let mut candidate = 0;
4106    while index < encode.len() {
4107        output |= encode[index] & ct_mask_eq_u8(value, candidate);
4108        index += 1;
4109        candidate += 1;
4110    }
4111    output
4112}
4113
4114#[inline]
4115const fn encode_ascii_base64(value: u8, value_62_byte: u8, value_63_byte: u8) -> u8 {
4116    let upper = ct_mask_lt_u8(value, 26);
4117    let lower = ct_mask_lt_u8(value.wrapping_sub(26), 26);
4118    let digit = ct_mask_lt_u8(value.wrapping_sub(52), 10);
4119    let value_62 = ct_mask_eq_u8(value, 0x3e);
4120    let value_63 = ct_mask_eq_u8(value, 0x3f);
4121
4122    (value.wrapping_add(b'A') & upper)
4123        | (value.wrapping_sub(26).wrapping_add(b'a') & lower)
4124        | (value.wrapping_sub(52).wrapping_add(b'0') & digit)
4125        | (value_62_byte & value_62)
4126        | (value_63_byte & value_63)
4127}
4128
4129#[inline]
4130fn decode_ascii_base64(byte: u8, value_62_byte: u8, value_63_byte: u8) -> Option<u8> {
4131    let upper = ct_mask_lt_u8(byte.wrapping_sub(b'A'), 26);
4132    let lower = ct_mask_lt_u8(byte.wrapping_sub(b'a'), 26);
4133    let digit = ct_mask_lt_u8(byte.wrapping_sub(b'0'), 10);
4134    let value_62 = ct_mask_eq_u8(byte, value_62_byte);
4135    let value_63 = ct_mask_eq_u8(byte, value_63_byte);
4136    let valid = upper | lower | digit | value_62 | value_63;
4137
4138    let decoded = (byte.wrapping_sub(b'A') & upper)
4139        | (byte.wrapping_sub(b'a').wrapping_add(26) & lower)
4140        | (byte.wrapping_sub(b'0').wrapping_add(52) & digit)
4141        | (0x3e & value_62)
4142        | (0x3f & value_63);
4143
4144    if valid == 0 { None } else { Some(decoded) }
4145}
4146
4147#[inline]
4148const fn ct_mask_bit(bit: u8) -> u8 {
4149    0u8.wrapping_sub(bit & 1)
4150}
4151
4152#[inline]
4153const fn ct_mask_nonzero_u8(value: u8) -> u8 {
4154    let wide = value as u16;
4155    let negative = 0u16.wrapping_sub(wide);
4156    let nonzero = ((wide | negative) >> 8) as u8;
4157    ct_mask_bit(nonzero)
4158}
4159
4160#[inline]
4161const fn ct_mask_eq_u8(left: u8, right: u8) -> u8 {
4162    !ct_mask_nonzero_u8(left ^ right)
4163}
4164
4165#[inline]
4166const fn ct_mask_lt_u8(left: u8, right: u8) -> u8 {
4167    let diff = (left as u16).wrapping_sub(right as u16);
4168    ct_mask_bit((diff >> 8) as u8)
4169}
4170
4171fn constant_time_eq_public_len(left: &[u8], right: &[u8]) -> bool {
4172    if left.len() != right.len() {
4173        return false;
4174    }
4175
4176    let diff = left.iter().zip(right).fold(0u8, |diff, (left, right)| {
4177        diff | core::hint::black_box(*left ^ *right)
4178    });
4179    diff == 0
4180}
4181
4182mod backend {
4183    use super::{
4184        Alphabet, DecodeError, EncodeError, checked_encoded_len, decode_padded, decode_unpadded,
4185        encode_base64_value_runtime,
4186    };
4187
4188    pub(super) fn encode_slice<A, const PAD: bool>(
4189        input: &[u8],
4190        output: &mut [u8],
4191    ) -> Result<usize, EncodeError>
4192    where
4193        A: Alphabet,
4194    {
4195        #[cfg(feature = "simd")]
4196        match super::simd::active_backend() {
4197            super::simd::ActiveBackend::Scalar => {}
4198        }
4199
4200        scalar_encode_slice::<A, PAD>(input, output)
4201    }
4202
4203    pub(super) fn decode_slice<A, const PAD: bool>(
4204        input: &[u8],
4205        output: &mut [u8],
4206    ) -> Result<usize, DecodeError>
4207    where
4208        A: Alphabet,
4209    {
4210        #[cfg(feature = "simd")]
4211        match super::simd::active_backend() {
4212            super::simd::ActiveBackend::Scalar => {}
4213        }
4214
4215        scalar_decode_slice::<A, PAD>(input, output)
4216    }
4217
4218    #[cfg(test)]
4219    pub(super) fn scalar_reference_encode_slice<A, const PAD: bool>(
4220        input: &[u8],
4221        output: &mut [u8],
4222    ) -> Result<usize, EncodeError>
4223    where
4224        A: Alphabet,
4225    {
4226        scalar_encode_slice::<A, PAD>(input, output)
4227    }
4228
4229    #[cfg(test)]
4230    pub(super) fn scalar_reference_decode_slice<A, const PAD: bool>(
4231        input: &[u8],
4232        output: &mut [u8],
4233    ) -> Result<usize, DecodeError>
4234    where
4235        A: Alphabet,
4236    {
4237        scalar_decode_slice::<A, PAD>(input, output)
4238    }
4239
4240    fn scalar_encode_slice<A, const PAD: bool>(
4241        input: &[u8],
4242        output: &mut [u8],
4243    ) -> Result<usize, EncodeError>
4244    where
4245        A: Alphabet,
4246    {
4247        let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
4248        if output.len() < required {
4249            return Err(EncodeError::OutputTooSmall {
4250                required,
4251                available: output.len(),
4252            });
4253        }
4254
4255        let mut read = 0;
4256        let mut write = 0;
4257        while read + 3 <= input.len() {
4258            let b0 = input[read];
4259            let b1 = input[read + 1];
4260            let b2 = input[read + 2];
4261
4262            output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
4263            output[write + 1] =
4264                encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
4265            output[write + 2] =
4266                encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
4267            output[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
4268
4269            read += 3;
4270            write += 4;
4271        }
4272
4273        match input.len() - read {
4274            0 => {}
4275            1 => {
4276                let b0 = input[read];
4277                output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
4278                output[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
4279                write += 2;
4280                if PAD {
4281                    output[write] = b'=';
4282                    output[write + 1] = b'=';
4283                    write += 2;
4284                }
4285            }
4286            2 => {
4287                let b0 = input[read];
4288                let b1 = input[read + 1];
4289                output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
4290                output[write + 1] =
4291                    encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
4292                output[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
4293                write += 3;
4294                if PAD {
4295                    output[write] = b'=';
4296                    write += 1;
4297                }
4298            }
4299            _ => unreachable!(),
4300        }
4301
4302        Ok(write)
4303    }
4304
4305    fn scalar_decode_slice<A, const PAD: bool>(
4306        input: &[u8],
4307        output: &mut [u8],
4308    ) -> Result<usize, DecodeError>
4309    where
4310        A: Alphabet,
4311    {
4312        if input.is_empty() {
4313            return Ok(0);
4314        }
4315
4316        if PAD {
4317            decode_padded::<A>(input, output)
4318        } else {
4319            decode_unpadded::<A>(input, output)
4320        }
4321    }
4322}
4323
4324/// A zero-sized Base64 engine parameterized by alphabet and padding policy.
4325pub struct Engine<A, const PAD: bool> {
4326    alphabet: core::marker::PhantomData<A>,
4327}
4328
4329impl<A, const PAD: bool> Clone for Engine<A, PAD> {
4330    fn clone(&self) -> Self {
4331        *self
4332    }
4333}
4334
4335impl<A, const PAD: bool> Copy for Engine<A, PAD> {}
4336
4337impl<A, const PAD: bool> core::fmt::Debug for Engine<A, PAD> {
4338    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
4339        formatter
4340            .debug_struct("Engine")
4341            .field("padded", &PAD)
4342            .finish()
4343    }
4344}
4345
4346impl<A, const PAD: bool> core::fmt::Display for Engine<A, PAD> {
4347    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
4348        write!(formatter, "padded={PAD}")
4349    }
4350}
4351
4352impl<A, const PAD: bool> Default for Engine<A, PAD> {
4353    fn default() -> Self {
4354        Self {
4355            alphabet: core::marker::PhantomData,
4356        }
4357    }
4358}
4359
4360impl<A, const PAD: bool> Eq for Engine<A, PAD> {}
4361
4362impl<A, const PAD: bool> PartialEq for Engine<A, PAD> {
4363    fn eq(&self, _other: &Self) -> bool {
4364        true
4365    }
4366}
4367
4368impl<A, const PAD: bool> Engine<A, PAD>
4369where
4370    A: Alphabet,
4371{
4372    /// Creates a new engine value.
4373    #[must_use]
4374    pub const fn new() -> Self {
4375        Self {
4376            alphabet: core::marker::PhantomData,
4377        }
4378    }
4379
4380    /// Returns whether this engine uses padded Base64.
4381    #[must_use]
4382    pub const fn is_padded(&self) -> bool {
4383        PAD
4384    }
4385
4386    /// Returns this engine as an unwrapped profile.
4387    ///
4388    /// Use [`Profile::new`] or [`Profile::checked_new`] when a strict
4389    /// line-wrapping policy should travel with the profile.
4390    #[must_use]
4391    pub const fn profile(&self) -> Profile<A, PAD> {
4392        Profile::new(*self, None)
4393    }
4394
4395    /// Returns the matching constant-time-oriented decoder for this engine's
4396    /// alphabet and padding policy.
4397    ///
4398    /// The returned decoder is still an explicit opt-in to the [`ct`] module's
4399    /// slower, opaque-error, constant-time-oriented scalar path.
4400    #[must_use]
4401    pub const fn ct_decoder(&self) -> ct::CtEngine<A, PAD> {
4402        ct::CtEngine::new()
4403    }
4404
4405    /// Wraps a `std::io::Write` value in a streaming Base64 encoder.
4406    ///
4407    /// This is a convenience constructor for [`stream::Encoder::new`] that
4408    /// keeps the selected engine attached to the call site.
4409    ///
4410    /// ```
4411    /// use std::io::Write;
4412    /// use base64_ng::STANDARD;
4413    ///
4414    /// let mut encoder = STANDARD.encoder_writer(Vec::new());
4415    /// encoder.write_all(b"hello").unwrap();
4416    /// assert_eq!(encoder.finish().unwrap(), b"aGVsbG8=");
4417    /// ```
4418    #[cfg(feature = "stream")]
4419    #[must_use]
4420    pub fn encoder_writer<W>(&self, inner: W) -> stream::Encoder<W, A, PAD> {
4421        stream::Encoder::new(inner, *self)
4422    }
4423
4424    /// Wraps a `std::io::Write` value in a streaming Base64 decoder.
4425    ///
4426    /// This is a convenience constructor for [`stream::Decoder::new`] that
4427    /// keeps the selected engine attached to the call site.
4428    ///
4429    /// ```
4430    /// use std::io::Write;
4431    /// use base64_ng::STANDARD;
4432    ///
4433    /// let mut decoder = STANDARD.decoder_writer(Vec::new());
4434    /// decoder.write_all(b"aGVsbG8=").unwrap();
4435    /// assert_eq!(decoder.finish().unwrap(), b"hello");
4436    /// ```
4437    #[cfg(feature = "stream")]
4438    #[must_use]
4439    pub fn decoder_writer<W>(&self, inner: W) -> stream::Decoder<W, A, PAD> {
4440        stream::Decoder::new(inner, *self)
4441    }
4442
4443    /// Wraps a `std::io::Read` value in a streaming Base64 encoder.
4444    ///
4445    /// This is a convenience constructor for [`stream::EncoderReader::new`]
4446    /// that keeps the selected engine attached to the call site.
4447    ///
4448    /// ```
4449    /// use std::io::Read;
4450    /// use base64_ng::STANDARD;
4451    ///
4452    /// let mut reader = STANDARD.encoder_reader(&b"hello"[..]);
4453    /// let mut encoded = String::new();
4454    /// reader.read_to_string(&mut encoded).unwrap();
4455    /// assert_eq!(encoded, "aGVsbG8=");
4456    /// ```
4457    #[cfg(feature = "stream")]
4458    #[must_use]
4459    pub fn encoder_reader<R>(&self, inner: R) -> stream::EncoderReader<R, A, PAD> {
4460        stream::EncoderReader::new(inner, *self)
4461    }
4462
4463    /// Wraps a `std::io::Read` value in a streaming Base64 decoder.
4464    ///
4465    /// This is a convenience constructor for [`stream::DecoderReader::new`]
4466    /// that keeps the selected engine attached to the call site.
4467    ///
4468    /// ```
4469    /// use std::io::Read;
4470    /// use base64_ng::STANDARD;
4471    ///
4472    /// let mut reader = STANDARD.decoder_reader(&b"aGVsbG8="[..]);
4473    /// let mut decoded = Vec::new();
4474    /// reader.read_to_end(&mut decoded).unwrap();
4475    /// assert_eq!(decoded, b"hello");
4476    /// ```
4477    #[cfg(feature = "stream")]
4478    #[must_use]
4479    pub fn decoder_reader<R>(&self, inner: R) -> stream::DecoderReader<R, A, PAD> {
4480        stream::DecoderReader::new(inner, *self)
4481    }
4482
4483    /// Returns the encoded length for this engine's padding policy.
4484    pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
4485        encoded_len(input_len, PAD)
4486    }
4487
4488    /// Returns the encoded length for this engine, or `None` on overflow.
4489    #[must_use]
4490    pub const fn checked_encoded_len(&self, input_len: usize) -> Option<usize> {
4491        checked_encoded_len(input_len, PAD)
4492    }
4493
4494    /// Returns the encoded length after applying a line wrapping policy.
4495    ///
4496    /// The returned length includes inserted line endings but does not include
4497    /// a trailing line ending after the final encoded line.
4498    pub const fn wrapped_encoded_len(
4499        &self,
4500        input_len: usize,
4501        wrap: LineWrap,
4502    ) -> Result<usize, EncodeError> {
4503        wrapped_encoded_len(input_len, PAD, wrap)
4504    }
4505
4506    /// Returns the encoded length after line wrapping, or `None` on overflow or
4507    /// invalid line wrapping.
4508    #[must_use]
4509    pub const fn checked_wrapped_encoded_len(
4510        &self,
4511        input_len: usize,
4512        wrap: LineWrap,
4513    ) -> Option<usize> {
4514        checked_wrapped_encoded_len(input_len, PAD, wrap)
4515    }
4516
4517    /// Returns the exact decoded length implied by input length and padding.
4518    ///
4519    /// This validates padding placement and impossible lengths, but it does not
4520    /// validate alphabet membership or non-canonical trailing bits.
4521    pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
4522        decoded_len(input, PAD)
4523    }
4524
4525    /// Returns the exact decoded length for the explicit legacy profile.
4526    ///
4527    /// The legacy profile ignores ASCII space, tab, carriage return, and line
4528    /// feed bytes before applying the same alphabet, padding, and canonical-bit
4529    /// checks as strict decoding.
4530    pub fn decoded_len_legacy(&self, input: &[u8]) -> Result<usize, DecodeError> {
4531        validate_legacy_decode::<A, PAD>(input)
4532    }
4533
4534    /// Returns the exact decoded length for a line-wrapped profile.
4535    ///
4536    /// The wrapped profile accepts only the configured line ending. Non-final
4537    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
4538    /// may be shorter. A single trailing line ending after the final line is
4539    /// accepted.
4540    pub fn decoded_len_wrapped(&self, input: &[u8], wrap: LineWrap) -> Result<usize, DecodeError> {
4541        validate_wrapped_decode::<A, PAD>(input, wrap)
4542    }
4543
4544    /// Validates strict Base64 input without writing decoded bytes.
4545    ///
4546    /// This applies the same alphabet, padding, and canonical-bit checks as
4547    /// [`Self::decode_slice`]. Use this method when malformed-input
4548    /// diagnostics matter; use [`Self::validate`] when a boolean is enough.
4549    ///
4550    /// # Examples
4551    ///
4552    /// ```
4553    /// use base64_ng::STANDARD;
4554    ///
4555    /// STANDARD.validate_result(b"aGVsbG8=").unwrap();
4556    /// assert!(STANDARD.validate_result(b"aGVsbG8").is_err());
4557    /// ```
4558    pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
4559        validate_decode::<A, PAD>(input).map(|_| ())
4560    }
4561
4562    /// Returns whether `input` is valid strict Base64 for this engine.
4563    ///
4564    /// This is a convenience wrapper around [`Self::validate_result`].
4565    ///
4566    /// # Examples
4567    ///
4568    /// ```
4569    /// use base64_ng::URL_SAFE_NO_PAD;
4570    ///
4571    /// assert!(URL_SAFE_NO_PAD.validate(b"-_8"));
4572    /// assert!(!URL_SAFE_NO_PAD.validate(b"+/8"));
4573    /// ```
4574    #[must_use]
4575    pub fn validate(&self, input: &[u8]) -> bool {
4576        self.validate_result(input).is_ok()
4577    }
4578
4579    /// Validates input using the explicit legacy whitespace profile.
4580    ///
4581    /// ASCII space, tab, carriage return, and line feed bytes are ignored
4582    /// before applying the same alphabet, padding, and canonical-bit checks as
4583    /// strict decoding.
4584    ///
4585    /// # Examples
4586    ///
4587    /// ```
4588    /// use base64_ng::STANDARD;
4589    ///
4590    /// STANDARD.validate_legacy_result(b" aG\r\nVsbG8= ").unwrap();
4591    /// assert!(STANDARD.validate_legacy_result(b" aG-=").is_err());
4592    /// ```
4593    pub fn validate_legacy_result(&self, input: &[u8]) -> Result<(), DecodeError> {
4594        validate_legacy_decode::<A, PAD>(input).map(|_| ())
4595    }
4596
4597    /// Returns whether `input` is valid for the explicit legacy whitespace
4598    /// profile.
4599    ///
4600    /// This is a convenience wrapper around [`Self::validate_legacy_result`].
4601    ///
4602    /// # Examples
4603    ///
4604    /// ```
4605    /// use base64_ng::STANDARD;
4606    ///
4607    /// assert!(STANDARD.validate_legacy(b" aG\r\nVsbG8= "));
4608    /// assert!(!STANDARD.validate_legacy(b"aG-V"));
4609    /// ```
4610    #[must_use]
4611    pub fn validate_legacy(&self, input: &[u8]) -> bool {
4612        self.validate_legacy_result(input).is_ok()
4613    }
4614
4615    /// Validates input using a strict line-wrapped profile.
4616    ///
4617    /// This is stricter than [`Self::validate_legacy_result`]: it accepts only
4618    /// the configured line ending and enforces the configured line length for
4619    /// every non-final line.
4620    ///
4621    /// # Examples
4622    ///
4623    /// ```
4624    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
4625    ///
4626    /// let wrap = LineWrap::new(4, LineEnding::Lf);
4627    /// STANDARD.validate_wrapped_result(b"aGVs\nbG8=", wrap).unwrap();
4628    /// assert!(STANDARD.validate_wrapped_result(b"aG\nVsbG8=", wrap).is_err());
4629    /// ```
4630    pub fn validate_wrapped_result(&self, input: &[u8], wrap: LineWrap) -> Result<(), DecodeError> {
4631        validate_wrapped_decode::<A, PAD>(input, wrap).map(|_| ())
4632    }
4633
4634    /// Returns whether `input` is valid for a strict line-wrapped profile.
4635    ///
4636    /// This is a convenience wrapper around [`Self::validate_wrapped_result`].
4637    ///
4638    /// # Examples
4639    ///
4640    /// ```
4641    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
4642    ///
4643    /// let wrap = LineWrap::new(4, LineEnding::Lf);
4644    /// assert!(STANDARD.validate_wrapped(b"aGVs\nbG8=", wrap));
4645    /// assert!(!STANDARD.validate_wrapped(b"aG\nVsbG8=", wrap));
4646    /// ```
4647    #[must_use]
4648    pub fn validate_wrapped(&self, input: &[u8], wrap: LineWrap) -> bool {
4649        self.validate_wrapped_result(input, wrap).is_ok()
4650    }
4651
4652    /// Encodes a fixed-size input into a fixed-size output array in const contexts.
4653    ///
4654    /// Stable Rust does not yet allow this API to return an array whose length
4655    /// is computed from `INPUT_LEN` directly. Instead, the caller supplies the
4656    /// output length through the destination type and this function panics
4657    /// during const evaluation if the length is wrong.
4658    ///
4659    /// # Panics
4660    ///
4661    /// Panics if `OUTPUT_LEN` is not exactly the encoded length for `INPUT_LEN`
4662    /// and this engine's padding policy, or if that length overflows `usize`.
4663    ///
4664    /// # Examples
4665    ///
4666    /// ```
4667    /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
4668    ///
4669    /// const HELLO: [u8; 8] = STANDARD.encode_array(b"hello");
4670    /// const URL_SAFE: [u8; 3] = URL_SAFE_NO_PAD.encode_array(b"\xfb\xff");
4671    ///
4672    /// assert_eq!(&HELLO, b"aGVsbG8=");
4673    /// assert_eq!(&URL_SAFE, b"-_8");
4674    /// ```
4675    ///
4676    /// Incorrect output lengths fail during const evaluation:
4677    ///
4678    /// ```compile_fail
4679    /// use base64_ng::STANDARD;
4680    ///
4681    /// const TOO_SHORT: [u8; 7] = STANDARD.encode_array(b"hello");
4682    /// ```
4683    #[must_use]
4684    pub const fn encode_array<const INPUT_LEN: usize, const OUTPUT_LEN: usize>(
4685        &self,
4686        input: &[u8; INPUT_LEN],
4687    ) -> [u8; OUTPUT_LEN] {
4688        let Some(required) = checked_encoded_len(INPUT_LEN, PAD) else {
4689            panic!("encoded base64 length overflows usize");
4690        };
4691        assert!(
4692            required == OUTPUT_LEN,
4693            "base64 output array has incorrect length"
4694        );
4695
4696        let mut output = [0u8; OUTPUT_LEN];
4697        let mut read = 0;
4698        let mut write = 0;
4699        while INPUT_LEN - read >= 3 {
4700            let b0 = input[read];
4701            let b1 = input[read + 1];
4702            let b2 = input[read + 2];
4703
4704            output[write] = encode_base64_value::<A>(b0 >> 2);
4705            output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
4706            output[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
4707            output[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
4708
4709            read += 3;
4710            write += 4;
4711        }
4712
4713        match INPUT_LEN - read {
4714            0 => {}
4715            1 => {
4716                let b0 = input[read];
4717                output[write] = encode_base64_value::<A>(b0 >> 2);
4718                output[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
4719                write += 2;
4720                if PAD {
4721                    output[write] = b'=';
4722                    output[write + 1] = b'=';
4723                }
4724            }
4725            2 => {
4726                let b0 = input[read];
4727                let b1 = input[read + 1];
4728                output[write] = encode_base64_value::<A>(b0 >> 2);
4729                output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
4730                output[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
4731                if PAD {
4732                    output[write + 3] = b'=';
4733                }
4734            }
4735            _ => unreachable!(),
4736        }
4737
4738        output
4739    }
4740
4741    /// Encodes `input` into `output`, returning the number of bytes written.
4742    pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
4743        backend::encode_slice::<A, PAD>(input, output)
4744    }
4745
4746    /// Encodes `input` into `output` with line wrapping.
4747    ///
4748    /// The wrapping policy inserts line endings between encoded lines and does
4749    /// not append a trailing line ending after the final line.
4750    ///
4751    /// # Examples
4752    ///
4753    /// ```
4754    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
4755    ///
4756    /// let wrap = LineWrap::new(4, LineEnding::Lf);
4757    /// let mut output = [0u8; 9];
4758    /// let written = STANDARD
4759    ///     .encode_slice_wrapped(b"hello", &mut output, wrap)
4760    ///     .unwrap();
4761    ///
4762    /// assert_eq!(&output[..written], b"aGVs\nbG8=");
4763    /// ```
4764    pub fn encode_slice_wrapped(
4765        &self,
4766        input: &[u8],
4767        output: &mut [u8],
4768        wrap: LineWrap,
4769    ) -> Result<usize, EncodeError> {
4770        let required = self.wrapped_encoded_len(input.len(), wrap)?;
4771        if output.len() < required {
4772            return Err(EncodeError::OutputTooSmall {
4773                required,
4774                available: output.len(),
4775            });
4776        }
4777
4778        let encoded_len =
4779            checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
4780        if encoded_len == 0 {
4781            return Ok(0);
4782        }
4783
4784        if output.len() < required.saturating_add(encoded_len) {
4785            let mut scratch = [0u8; 1024];
4786            let mut input_offset = 0;
4787            let mut output_offset = 0;
4788            let mut column = 0;
4789
4790            while input_offset < input.len() {
4791                let remaining = input.len() - input_offset;
4792                let mut take = remaining.min(768);
4793                if remaining > take {
4794                    take -= take % 3;
4795                }
4796                if take == 0 {
4797                    take = remaining;
4798                }
4799
4800                let encoded =
4801                    self.encode_slice(&input[input_offset..input_offset + take], &mut scratch)?;
4802                write_wrapped_bytes(
4803                    &scratch[..encoded],
4804                    output,
4805                    &mut output_offset,
4806                    &mut column,
4807                    wrap,
4808                );
4809                wipe_bytes(&mut scratch[..encoded]);
4810                input_offset += take;
4811            }
4812
4813            Ok(output_offset)
4814        } else {
4815            let encoded =
4816                self.encode_slice(input, &mut output[required..required + encoded_len])?;
4817            let mut output_offset = 0;
4818            let mut column = 0;
4819            let mut read = required;
4820            while read < required + encoded {
4821                let byte = output[read];
4822                write_wrapped_byte(byte, output, &mut output_offset, &mut column, wrap);
4823                read += 1;
4824            }
4825            wipe_bytes(&mut output[required..required + encoded]);
4826            Ok(output_offset)
4827        }
4828    }
4829
4830    /// Encodes `input` with line wrapping and clears all bytes after the
4831    /// encoded prefix.
4832    ///
4833    /// If encoding fails, the entire output buffer is cleared before the error
4834    /// is returned.
4835    pub fn encode_slice_wrapped_clear_tail(
4836        &self,
4837        input: &[u8],
4838        output: &mut [u8],
4839        wrap: LineWrap,
4840    ) -> Result<usize, EncodeError> {
4841        let written = match self.encode_slice_wrapped(input, output, wrap) {
4842            Ok(written) => written,
4843            Err(err) => {
4844                wipe_bytes(output);
4845                return Err(err);
4846            }
4847        };
4848        wipe_tail(output, written);
4849        Ok(written)
4850    }
4851
4852    /// Encodes `input` with line wrapping into a stack-backed buffer.
4853    ///
4854    /// This is useful for MIME/PEM-style protocols where heap allocation is
4855    /// unnecessary. If encoding fails, the internal backing array is cleared
4856    /// before the error is returned.
4857    pub fn encode_wrapped_buffer<const CAP: usize>(
4858        &self,
4859        input: &[u8],
4860        wrap: LineWrap,
4861    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
4862        let mut output = EncodedBuffer::new();
4863        let written = match self.encode_slice_wrapped_clear_tail(input, &mut output.bytes, wrap) {
4864            Ok(written) => written,
4865            Err(err) => {
4866                output.clear();
4867                return Err(err);
4868            }
4869        };
4870        output.len = written;
4871        Ok(output)
4872    }
4873
4874    /// Encodes `input` with line wrapping into a newly allocated byte vector.
4875    #[cfg(feature = "alloc")]
4876    pub fn encode_wrapped_vec(
4877        &self,
4878        input: &[u8],
4879        wrap: LineWrap,
4880    ) -> Result<alloc::vec::Vec<u8>, EncodeError> {
4881        let required = self.wrapped_encoded_len(input.len(), wrap)?;
4882        let mut output = alloc::vec![0; required];
4883        let written = self.encode_slice_wrapped(input, &mut output, wrap)?;
4884        output.truncate(written);
4885        Ok(output)
4886    }
4887
4888    /// Encodes `input` with line wrapping into a newly allocated UTF-8 string.
4889    #[cfg(feature = "alloc")]
4890    pub fn encode_wrapped_string(
4891        &self,
4892        input: &[u8],
4893        wrap: LineWrap,
4894    ) -> Result<alloc::string::String, EncodeError> {
4895        let output = self.encode_wrapped_vec(input, wrap)?;
4896        match alloc::string::String::from_utf8(output) {
4897            Ok(output) => Ok(output),
4898            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
4899        }
4900    }
4901
4902    /// Encodes `input` with line wrapping into a redacted owned secret buffer.
4903    ///
4904    /// This is useful when the wrapped encoded representation itself is
4905    /// sensitive and should not be accidentally logged through formatting.
4906    #[cfg(feature = "alloc")]
4907    pub fn encode_wrapped_secret(
4908        &self,
4909        input: &[u8],
4910        wrap: LineWrap,
4911    ) -> Result<SecretBuffer, EncodeError> {
4912        self.encode_wrapped_vec(input, wrap)
4913            .map(SecretBuffer::from_vec)
4914    }
4915
4916    /// Encodes `input` into `output` and clears all bytes after the encoded
4917    /// prefix.
4918    ///
4919    /// If encoding fails, the entire output buffer is cleared before the error
4920    /// is returned.
4921    ///
4922    /// # Examples
4923    ///
4924    /// ```
4925    /// use base64_ng::STANDARD;
4926    ///
4927    /// let mut output = [0xff; 12];
4928    /// let written = STANDARD
4929    ///     .encode_slice_clear_tail(b"hello", &mut output)
4930    ///     .unwrap();
4931    ///
4932    /// assert_eq!(&output[..written], b"aGVsbG8=");
4933    /// assert!(output[written..].iter().all(|byte| *byte == 0));
4934    /// ```
4935    pub fn encode_slice_clear_tail(
4936        &self,
4937        input: &[u8],
4938        output: &mut [u8],
4939    ) -> Result<usize, EncodeError> {
4940        let written = match self.encode_slice(input, output) {
4941            Ok(written) => written,
4942            Err(err) => {
4943                wipe_bytes(output);
4944                return Err(err);
4945            }
4946        };
4947        wipe_tail(output, written);
4948        Ok(written)
4949    }
4950
4951    /// Encodes `input` into a stack-backed buffer.
4952    ///
4953    /// This helper is useful for short values where callers want the
4954    /// convenience of an owned result without enabling `alloc`.
4955    ///
4956    /// # Examples
4957    ///
4958    /// ```
4959    /// use base64_ng::STANDARD;
4960    ///
4961    /// let encoded = STANDARD.encode_buffer::<8>(b"hello").unwrap();
4962    ///
4963    /// assert_eq!(encoded.as_str(), "aGVsbG8=");
4964    /// ```
4965    pub fn encode_buffer<const CAP: usize>(
4966        &self,
4967        input: &[u8],
4968    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
4969        let mut output = EncodedBuffer::new();
4970        let written = match self.encode_slice_clear_tail(input, &mut output.bytes) {
4971            Ok(written) => written,
4972            Err(err) => {
4973                output.clear();
4974                return Err(err);
4975            }
4976        };
4977        output.len = written;
4978        Ok(output)
4979    }
4980
4981    /// Encodes `input` into a newly allocated byte vector.
4982    #[cfg(feature = "alloc")]
4983    pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
4984        let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
4985        let mut output = alloc::vec![0; required];
4986        let written = self.encode_slice(input, &mut output)?;
4987        output.truncate(written);
4988        Ok(output)
4989    }
4990
4991    /// Encodes `input` into a redacted owned secret buffer.
4992    ///
4993    /// This is useful when the encoded representation itself is sensitive and
4994    /// should not be accidentally logged through formatting.
4995    #[cfg(feature = "alloc")]
4996    pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
4997        self.encode_vec(input).map(SecretBuffer::from_vec)
4998    }
4999
5000    /// Encodes `input` into a newly allocated UTF-8 string.
5001    ///
5002    /// Base64 output is ASCII by construction. This helper is available with
5003    /// the `alloc` feature and has the same encoding semantics as
5004    /// [`Self::encode_slice`].
5005    ///
5006    /// # Examples
5007    ///
5008    /// ```
5009    /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
5010    ///
5011    /// assert_eq!(STANDARD.encode_string(b"hello").unwrap(), "aGVsbG8=");
5012    /// assert_eq!(URL_SAFE_NO_PAD.encode_string(b"\xfb\xff").unwrap(), "-_8");
5013    /// ```
5014    #[cfg(feature = "alloc")]
5015    pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
5016        let output = self.encode_vec(input)?;
5017        match alloc::string::String::from_utf8(output) {
5018            Ok(output) => Ok(output),
5019            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
5020        }
5021    }
5022
5023    /// Encodes the first `input_len` bytes of `buffer` in place.
5024    ///
5025    /// The buffer must have enough spare capacity for the encoded output. The
5026    /// implementation writes from right to left, so unread input bytes are not
5027    /// overwritten before they are encoded.
5028    ///
5029    /// # Examples
5030    ///
5031    /// ```
5032    /// use base64_ng::STANDARD;
5033    ///
5034    /// let mut buffer = [0u8; 8];
5035    /// buffer[..5].copy_from_slice(b"hello");
5036    /// let encoded = STANDARD.encode_in_place(&mut buffer, 5).unwrap();
5037    /// assert_eq!(encoded, b"aGVsbG8=");
5038    /// ```
5039    pub fn encode_in_place<'a>(
5040        &self,
5041        buffer: &'a mut [u8],
5042        input_len: usize,
5043    ) -> Result<&'a mut [u8], EncodeError> {
5044        if input_len > buffer.len() {
5045            return Err(EncodeError::InputTooLarge {
5046                input_len,
5047                buffer_len: buffer.len(),
5048            });
5049        }
5050
5051        let required = checked_encoded_len(input_len, PAD).ok_or(EncodeError::LengthOverflow)?;
5052        if buffer.len() < required {
5053            return Err(EncodeError::OutputTooSmall {
5054                required,
5055                available: buffer.len(),
5056            });
5057        }
5058
5059        let mut read = input_len;
5060        let mut write = required;
5061
5062        match input_len % 3 {
5063            0 => {}
5064            1 => {
5065                read -= 1;
5066                let b0 = buffer[read];
5067                if PAD {
5068                    write -= 4;
5069                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
5070                    buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
5071                    buffer[write + 2] = b'=';
5072                    buffer[write + 3] = b'=';
5073                } else {
5074                    write -= 2;
5075                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
5076                    buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
5077                }
5078            }
5079            2 => {
5080                read -= 2;
5081                let b0 = buffer[read];
5082                let b1 = buffer[read + 1];
5083                if PAD {
5084                    write -= 4;
5085                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
5086                    buffer[write + 1] =
5087                        encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
5088                    buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
5089                    buffer[write + 3] = b'=';
5090                } else {
5091                    write -= 3;
5092                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
5093                    buffer[write + 1] =
5094                        encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
5095                    buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
5096                }
5097            }
5098            _ => unreachable!(),
5099        }
5100
5101        while read > 0 {
5102            read -= 3;
5103            write -= 4;
5104            let b0 = buffer[read];
5105            let b1 = buffer[read + 1];
5106            let b2 = buffer[read + 2];
5107
5108            buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
5109            buffer[write + 1] =
5110                encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
5111            buffer[write + 2] =
5112                encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
5113            buffer[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
5114        }
5115
5116        debug_assert_eq!(write, 0);
5117        Ok(&mut buffer[..required])
5118    }
5119
5120    /// Encodes the first `input_len` bytes of `buffer` in place and clears all
5121    /// bytes after the encoded prefix.
5122    ///
5123    /// If encoding fails because `input_len` is too large, the output buffer is
5124    /// too small, or the encoded length overflows `usize`, the entire buffer is
5125    /// cleared before the error is returned.
5126    ///
5127    /// # Examples
5128    ///
5129    /// ```
5130    /// use base64_ng::STANDARD;
5131    ///
5132    /// let mut buffer = [0xff; 12];
5133    /// buffer[..5].copy_from_slice(b"hello");
5134    /// let encoded = STANDARD.encode_in_place_clear_tail(&mut buffer, 5).unwrap();
5135    /// assert_eq!(encoded, b"aGVsbG8=");
5136    /// ```
5137    pub fn encode_in_place_clear_tail<'a>(
5138        &self,
5139        buffer: &'a mut [u8],
5140        input_len: usize,
5141    ) -> Result<&'a mut [u8], EncodeError> {
5142        let len = match self.encode_in_place(buffer, input_len) {
5143            Ok(encoded) => encoded.len(),
5144            Err(err) => {
5145                wipe_bytes(buffer);
5146                return Err(err);
5147            }
5148        };
5149        wipe_tail(buffer, len);
5150        Ok(&mut buffer[..len])
5151    }
5152
5153    /// Decodes `input` into `output`, returning the number of bytes written.
5154    ///
5155    /// This is strict decoding. Whitespace, mixed alphabets, malformed padding,
5156    /// and trailing non-padding data are rejected.
5157    ///
5158    /// This default scalar decoder prioritizes strict validation, exact error
5159    /// reporting, and ordinary throughput. It may branch or return early based
5160    /// on malformed input and padding position. Use [`crate::ct`] for
5161    /// secret-bearing payloads where timing behavior matters more than exact
5162    /// malformed-input diagnostics.
5163    pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
5164        backend::decode_slice::<A, PAD>(input, output)
5165    }
5166
5167    /// Decodes `input` into `output` and clears all bytes after the decoded
5168    /// prefix.
5169    ///
5170    /// If decoding fails, the entire output buffer is cleared before the error
5171    /// is returned.
5172    ///
5173    /// # Examples
5174    ///
5175    /// ```
5176    /// use base64_ng::STANDARD;
5177    ///
5178    /// let mut output = [0xff; 8];
5179    /// let written = STANDARD
5180    ///     .decode_slice_clear_tail(b"aGk=", &mut output)
5181    ///     .unwrap();
5182    ///
5183    /// assert_eq!(&output[..written], b"hi");
5184    /// assert!(output[written..].iter().all(|byte| *byte == 0));
5185    /// ```
5186    pub fn decode_slice_clear_tail(
5187        &self,
5188        input: &[u8],
5189        output: &mut [u8],
5190    ) -> Result<usize, DecodeError> {
5191        let written = match self.decode_slice(input, output) {
5192            Ok(written) => written,
5193            Err(err) => {
5194                wipe_bytes(output);
5195                return Err(err);
5196            }
5197        };
5198        wipe_tail(output, written);
5199        Ok(written)
5200    }
5201
5202    /// Decodes `input` into a stack-backed buffer.
5203    ///
5204    /// This helper is useful for short decoded values where callers want the
5205    /// convenience of an owned result without enabling `alloc`.
5206    ///
5207    /// # Examples
5208    ///
5209    /// ```
5210    /// use base64_ng::STANDARD;
5211    ///
5212    /// let decoded = STANDARD.decode_buffer::<5>(b"aGVsbG8=").unwrap();
5213    ///
5214    /// assert_eq!(decoded.as_bytes(), b"hello");
5215    /// ```
5216    pub fn decode_buffer<const CAP: usize>(
5217        &self,
5218        input: &[u8],
5219    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
5220        let mut output = DecodedBuffer::new();
5221        let written = match self.decode_slice_clear_tail(input, &mut output.bytes) {
5222            Ok(written) => written,
5223            Err(err) => {
5224                output.clear();
5225                return Err(err);
5226            }
5227        };
5228        output.len = written;
5229        Ok(output)
5230    }
5231
5232    /// Decodes `input` using the explicit legacy whitespace profile.
5233    ///
5234    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
5235    /// Alphabet selection, padding placement, trailing data after padding, and
5236    /// non-canonical trailing bits remain strict.
5237    pub fn decode_slice_legacy(
5238        &self,
5239        input: &[u8],
5240        output: &mut [u8],
5241    ) -> Result<usize, DecodeError> {
5242        let required = validate_legacy_decode::<A, PAD>(input)?;
5243        if output.len() < required {
5244            return Err(DecodeError::OutputTooSmall {
5245                required,
5246                available: output.len(),
5247            });
5248        }
5249        decode_legacy_to_slice::<A, PAD>(input, output)
5250    }
5251
5252    /// Decodes `input` using the explicit legacy whitespace profile and clears
5253    /// all bytes after the decoded prefix.
5254    ///
5255    /// If validation or decoding fails, the entire output buffer is cleared
5256    /// before the error is returned.
5257    ///
5258    /// # Examples
5259    ///
5260    /// ```
5261    /// use base64_ng::STANDARD;
5262    ///
5263    /// let mut output = [0xff; 8];
5264    /// let written = STANDARD
5265    ///     .decode_slice_legacy_clear_tail(b" aG\r\nk= ", &mut output)
5266    ///     .unwrap();
5267    ///
5268    /// assert_eq!(&output[..written], b"hi");
5269    /// assert!(output[written..].iter().all(|byte| *byte == 0));
5270    /// ```
5271    pub fn decode_slice_legacy_clear_tail(
5272        &self,
5273        input: &[u8],
5274        output: &mut [u8],
5275    ) -> Result<usize, DecodeError> {
5276        let written = match self.decode_slice_legacy(input, output) {
5277            Ok(written) => written,
5278            Err(err) => {
5279                wipe_bytes(output);
5280                return Err(err);
5281            }
5282        };
5283        wipe_tail(output, written);
5284        Ok(written)
5285    }
5286
5287    /// Decodes `input` into a stack-backed buffer using the explicit legacy
5288    /// whitespace profile.
5289    ///
5290    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
5291    /// Alphabet selection, padding placement, trailing data after padding, and
5292    /// non-canonical trailing bits remain strict. If decoding fails, the
5293    /// internal backing array is cleared before the error is returned.
5294    pub fn decode_buffer_legacy<const CAP: usize>(
5295        &self,
5296        input: &[u8],
5297    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
5298        let mut output = DecodedBuffer::new();
5299        let written = match self.decode_slice_legacy_clear_tail(input, &mut output.bytes) {
5300            Ok(written) => written,
5301            Err(err) => {
5302                output.clear();
5303                return Err(err);
5304            }
5305        };
5306        output.len = written;
5307        Ok(output)
5308    }
5309
5310    /// Decodes `input` using a strict line-wrapped profile.
5311    ///
5312    /// The wrapped profile accepts only the configured line ending. Non-final
5313    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
5314    /// may be shorter. A single trailing line ending after the final line is
5315    /// accepted.
5316    pub fn decode_slice_wrapped(
5317        &self,
5318        input: &[u8],
5319        output: &mut [u8],
5320        wrap: LineWrap,
5321    ) -> Result<usize, DecodeError> {
5322        let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
5323        if output.len() < required {
5324            return Err(DecodeError::OutputTooSmall {
5325                required,
5326                available: output.len(),
5327            });
5328        }
5329        decode_wrapped_to_slice::<A, PAD>(input, output, wrap)
5330    }
5331
5332    /// Decodes `input` using a strict line-wrapped profile and clears all bytes
5333    /// after the decoded prefix.
5334    ///
5335    /// If validation or decoding fails, the entire output buffer is cleared
5336    /// before the error is returned.
5337    pub fn decode_slice_wrapped_clear_tail(
5338        &self,
5339        input: &[u8],
5340        output: &mut [u8],
5341        wrap: LineWrap,
5342    ) -> Result<usize, DecodeError> {
5343        let written = match self.decode_slice_wrapped(input, output, wrap) {
5344            Ok(written) => written,
5345            Err(err) => {
5346                wipe_bytes(output);
5347                return Err(err);
5348            }
5349        };
5350        wipe_tail(output, written);
5351        Ok(written)
5352    }
5353
5354    /// Decodes `input` using a strict line-wrapped profile into a stack-backed
5355    /// buffer.
5356    ///
5357    /// The wrapped profile accepts only the configured line ending. Non-final
5358    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
5359    /// may be shorter. A single trailing line ending after the final line is
5360    /// accepted. If decoding fails, the internal backing array is cleared
5361    /// before the error is returned.
5362    pub fn decode_wrapped_buffer<const CAP: usize>(
5363        &self,
5364        input: &[u8],
5365        wrap: LineWrap,
5366    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
5367        let mut output = DecodedBuffer::new();
5368        let written = match self.decode_slice_wrapped_clear_tail(input, &mut output.bytes, wrap) {
5369            Ok(written) => written,
5370            Err(err) => {
5371                output.clear();
5372                return Err(err);
5373            }
5374        };
5375        output.len = written;
5376        Ok(output)
5377    }
5378
5379    /// Decodes `input` into a newly allocated byte vector.
5380    ///
5381    /// This is strict decoding with the same semantics as [`Self::decode_slice`].
5382    #[cfg(feature = "alloc")]
5383    pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
5384        let required = validate_decode::<A, PAD>(input)?;
5385        let mut output = alloc::vec![0; required];
5386        let written = match self.decode_slice(input, &mut output) {
5387            Ok(written) => written,
5388            Err(err) => {
5389                wipe_bytes(&mut output);
5390                return Err(err);
5391            }
5392        };
5393        output.truncate(written);
5394        Ok(output)
5395    }
5396
5397    /// Decodes `input` into a redacted owned secret buffer.
5398    ///
5399    /// On malformed input, the intermediate output buffer is cleared before the
5400    /// error is returned by [`Self::decode_vec`].
5401    #[cfg(feature = "alloc")]
5402    pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
5403        self.decode_vec(input).map(SecretBuffer::from_vec)
5404    }
5405
5406    /// Decodes `input` into a newly allocated byte vector using the explicit
5407    /// legacy whitespace profile.
5408    #[cfg(feature = "alloc")]
5409    pub fn decode_vec_legacy(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
5410        let required = validate_legacy_decode::<A, PAD>(input)?;
5411        let mut output = alloc::vec![0; required];
5412        let written = match self.decode_slice_legacy(input, &mut output) {
5413            Ok(written) => written,
5414            Err(err) => {
5415                wipe_bytes(&mut output);
5416                return Err(err);
5417            }
5418        };
5419        output.truncate(written);
5420        Ok(output)
5421    }
5422
5423    /// Decodes `input` into a redacted owned secret buffer using the explicit
5424    /// legacy whitespace profile.
5425    ///
5426    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
5427    /// Alphabet selection, padding placement, trailing data after padding, and
5428    /// non-canonical trailing bits remain strict.
5429    #[cfg(feature = "alloc")]
5430    pub fn decode_secret_legacy(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
5431        self.decode_vec_legacy(input).map(SecretBuffer::from_vec)
5432    }
5433
5434    /// Decodes line-wrapped input into a newly allocated byte vector.
5435    #[cfg(feature = "alloc")]
5436    pub fn decode_wrapped_vec(
5437        &self,
5438        input: &[u8],
5439        wrap: LineWrap,
5440    ) -> Result<alloc::vec::Vec<u8>, DecodeError> {
5441        let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
5442        let mut output = alloc::vec![0; required];
5443        let written = match self.decode_slice_wrapped(input, &mut output, wrap) {
5444            Ok(written) => written,
5445            Err(err) => {
5446                wipe_bytes(&mut output);
5447                return Err(err);
5448            }
5449        };
5450        output.truncate(written);
5451        Ok(output)
5452    }
5453
5454    /// Decodes line-wrapped input into a redacted owned secret buffer.
5455    ///
5456    /// The wrapped profile accepts only the configured line ending. Non-final
5457    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
5458    /// may be shorter. A single trailing line ending after the final line is
5459    /// accepted.
5460    #[cfg(feature = "alloc")]
5461    pub fn decode_wrapped_secret(
5462        &self,
5463        input: &[u8],
5464        wrap: LineWrap,
5465    ) -> Result<SecretBuffer, DecodeError> {
5466        self.decode_wrapped_vec(input, wrap)
5467            .map(SecretBuffer::from_vec)
5468    }
5469
5470    /// Decodes `buffer` in place using a strict line-wrapped profile.
5471    ///
5472    /// The wrapped profile accepts only the configured line ending. Non-final
5473    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
5474    /// may be shorter. A single trailing line ending after the final line is
5475    /// accepted. If validation fails, the buffer contents are unspecified.
5476    ///
5477    /// # Examples
5478    ///
5479    /// ```
5480    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
5481    ///
5482    /// let mut buffer = *b"aGVs\nbG8=";
5483    /// let decoded = STANDARD
5484    ///     .decode_in_place_wrapped(&mut buffer, LineWrap::new(4, LineEnding::Lf))
5485    ///     .unwrap();
5486    ///
5487    /// assert_eq!(decoded, b"hello");
5488    /// ```
5489    pub fn decode_in_place_wrapped<'a>(
5490        &self,
5491        buffer: &'a mut [u8],
5492        wrap: LineWrap,
5493    ) -> Result<&'a mut [u8], DecodeError> {
5494        let _required = validate_wrapped_decode::<A, PAD>(buffer, wrap)?;
5495        let compacted = compact_wrapped_input(buffer, wrap)?;
5496        let len = Self::decode_slice_to_start(&mut buffer[..compacted])?;
5497        Ok(&mut buffer[..len])
5498    }
5499
5500    /// Decodes `buffer` in place using a strict line-wrapped profile and clears
5501    /// all bytes after the decoded prefix.
5502    ///
5503    /// If validation or decoding fails, the entire buffer is cleared before the
5504    /// error is returned.
5505    ///
5506    /// # Examples
5507    ///
5508    /// ```
5509    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
5510    ///
5511    /// let mut buffer = *b"aGVs\nbG8=";
5512    /// let len = STANDARD
5513    ///     .decode_in_place_wrapped_clear_tail(&mut buffer, LineWrap::new(4, LineEnding::Lf))
5514    ///     .unwrap()
5515    ///     .len();
5516    ///
5517    /// assert_eq!(&buffer[..len], b"hello");
5518    /// assert!(buffer[len..].iter().all(|byte| *byte == 0));
5519    /// ```
5520    pub fn decode_in_place_wrapped_clear_tail<'a>(
5521        &self,
5522        buffer: &'a mut [u8],
5523        wrap: LineWrap,
5524    ) -> Result<&'a mut [u8], DecodeError> {
5525        if let Err(err) = validate_wrapped_decode::<A, PAD>(buffer, wrap) {
5526            wipe_bytes(buffer);
5527            return Err(err);
5528        }
5529
5530        let compacted = match compact_wrapped_input(buffer, wrap) {
5531            Ok(compacted) => compacted,
5532            Err(err) => {
5533                wipe_bytes(buffer);
5534                return Err(err);
5535            }
5536        };
5537
5538        let len = match Self::decode_slice_to_start(&mut buffer[..compacted]) {
5539            Ok(len) => len,
5540            Err(err) => {
5541                wipe_bytes(buffer);
5542                return Err(err);
5543            }
5544        };
5545        wipe_tail(buffer, len);
5546        Ok(&mut buffer[..len])
5547    }
5548
5549    /// Decodes the buffer in place and returns the decoded prefix.
5550    ///
5551    /// # Examples
5552    ///
5553    /// ```
5554    /// use base64_ng::STANDARD_NO_PAD;
5555    ///
5556    /// let mut buffer = *b"Zm9vYmFy";
5557    /// let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
5558    /// assert_eq!(decoded, b"foobar");
5559    /// ```
5560    pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
5561        let len = Self::decode_slice_to_start(buffer)?;
5562        Ok(&mut buffer[..len])
5563    }
5564
5565    /// Decodes the buffer in place and clears all bytes after the decoded prefix.
5566    ///
5567    /// If decoding fails, the entire buffer is cleared before the error is
5568    /// returned. Use this variant when the encoded or partially decoded data is
5569    /// sensitive and the caller wants best-effort cleanup without adding a
5570    /// dependency.
5571    ///
5572    /// # Examples
5573    ///
5574    /// ```
5575    /// use base64_ng::STANDARD;
5576    ///
5577    /// let mut buffer = *b"aGk=";
5578    /// let decoded = STANDARD.decode_in_place_clear_tail(&mut buffer).unwrap();
5579    /// assert_eq!(decoded, b"hi");
5580    /// ```
5581    pub fn decode_in_place_clear_tail<'a>(
5582        &self,
5583        buffer: &'a mut [u8],
5584    ) -> Result<&'a mut [u8], DecodeError> {
5585        let len = match Self::decode_slice_to_start(buffer) {
5586            Ok(len) => len,
5587            Err(err) => {
5588                wipe_bytes(buffer);
5589                return Err(err);
5590            }
5591        };
5592        wipe_tail(buffer, len);
5593        Ok(&mut buffer[..len])
5594    }
5595
5596    /// Decodes `buffer` in place using the explicit legacy whitespace profile.
5597    ///
5598    /// Ignored whitespace is compacted out before decoding. If validation
5599    /// fails, the buffer contents are unspecified.
5600    pub fn decode_in_place_legacy<'a>(
5601        &self,
5602        buffer: &'a mut [u8],
5603    ) -> Result<&'a mut [u8], DecodeError> {
5604        let _required = validate_legacy_decode::<A, PAD>(buffer)?;
5605        let mut write = 0;
5606        let mut read = 0;
5607        while read < buffer.len() {
5608            let byte = buffer[read];
5609            if !is_legacy_whitespace(byte) {
5610                buffer[write] = byte;
5611                write += 1;
5612            }
5613            read += 1;
5614        }
5615        let len = Self::decode_slice_to_start(&mut buffer[..write])?;
5616        Ok(&mut buffer[..len])
5617    }
5618
5619    /// Decodes `buffer` in place using the explicit legacy whitespace profile
5620    /// and clears all bytes after the decoded prefix.
5621    ///
5622    /// If validation or decoding fails, the entire buffer is cleared before the
5623    /// error is returned.
5624    pub fn decode_in_place_legacy_clear_tail<'a>(
5625        &self,
5626        buffer: &'a mut [u8],
5627    ) -> Result<&'a mut [u8], DecodeError> {
5628        if let Err(err) = validate_legacy_decode::<A, PAD>(buffer) {
5629            wipe_bytes(buffer);
5630            return Err(err);
5631        }
5632
5633        let mut write = 0;
5634        let mut read = 0;
5635        while read < buffer.len() {
5636            let byte = buffer[read];
5637            if !is_legacy_whitespace(byte) {
5638                buffer[write] = byte;
5639                write += 1;
5640            }
5641            read += 1;
5642        }
5643
5644        let len = match Self::decode_slice_to_start(&mut buffer[..write]) {
5645            Ok(len) => len,
5646            Err(err) => {
5647                wipe_bytes(buffer);
5648                return Err(err);
5649            }
5650        };
5651        wipe_tail(buffer, len);
5652        Ok(&mut buffer[..len])
5653    }
5654
5655    fn decode_slice_to_start(buffer: &mut [u8]) -> Result<usize, DecodeError> {
5656        let _required = validate_decode::<A, PAD>(buffer)?;
5657        let input_len = buffer.len();
5658        let mut read = 0;
5659        let mut write = 0;
5660        while read + 4 <= input_len {
5661            let chunk = read_quad(buffer, read)?;
5662            let available = buffer.len();
5663            let output_tail = buffer.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
5664                required: write,
5665                available,
5666            })?;
5667            let written = decode_chunk::<A, PAD>(chunk, output_tail)
5668                .map_err(|err| err.with_index_offset(read))?;
5669            read += 4;
5670            write += written;
5671            if written < 3 {
5672                if read != input_len {
5673                    return Err(DecodeError::InvalidPadding { index: read - 4 });
5674                }
5675                return Ok(write);
5676            }
5677        }
5678
5679        let rem = input_len - read;
5680        if rem == 0 {
5681            return Ok(write);
5682        }
5683        if PAD {
5684            return Err(DecodeError::InvalidLength);
5685        }
5686        let mut tail = [0u8; 3];
5687        tail[..rem].copy_from_slice(&buffer[read..input_len]);
5688        decode_tail_unpadded::<A>(&tail[..rem], &mut buffer[write..])
5689            .map_err(|err| err.with_index_offset(read))
5690            .map(|n| write + n)
5691    }
5692}
5693
5694fn write_wrapped_bytes(
5695    input: &[u8],
5696    output: &mut [u8],
5697    output_offset: &mut usize,
5698    column: &mut usize,
5699    wrap: LineWrap,
5700) {
5701    for byte in input {
5702        write_wrapped_byte(*byte, output, output_offset, column, wrap);
5703    }
5704}
5705
5706fn write_wrapped_byte(
5707    byte: u8,
5708    output: &mut [u8],
5709    output_offset: &mut usize,
5710    column: &mut usize,
5711    wrap: LineWrap,
5712) {
5713    if *column == wrap.line_len {
5714        let line_ending = wrap.line_ending.as_bytes();
5715        let mut index = 0;
5716        while index < line_ending.len() {
5717            output[*output_offset] = line_ending[index];
5718            *output_offset += 1;
5719            index += 1;
5720        }
5721        *column = 0;
5722    }
5723
5724    output[*output_offset] = byte;
5725    *output_offset += 1;
5726    *column += 1;
5727}
5728
5729/// Encoding error.
5730#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5731pub enum EncodeError {
5732    /// The encoded output length would overflow `usize`.
5733    LengthOverflow,
5734    /// The requested line wrapping policy is invalid.
5735    InvalidLineWrap {
5736        /// Requested line length.
5737        line_len: usize,
5738    },
5739    /// The caller-provided input length exceeds the provided buffer.
5740    InputTooLarge {
5741        /// Requested input bytes.
5742        input_len: usize,
5743        /// Available buffer bytes.
5744        buffer_len: usize,
5745    },
5746    /// The output buffer is too small.
5747    OutputTooSmall {
5748        /// Required output bytes.
5749        required: usize,
5750        /// Available output bytes.
5751        available: usize,
5752    },
5753}
5754
5755impl core::fmt::Display for EncodeError {
5756    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
5757        match self {
5758            Self::LengthOverflow => f.write_str("base64 output length overflows usize"),
5759            Self::InvalidLineWrap { line_len } => {
5760                write!(f, "base64 line wrap length {line_len} is invalid")
5761            }
5762            Self::InputTooLarge {
5763                input_len,
5764                buffer_len,
5765            } => write!(
5766                f,
5767                "base64 input length {input_len} exceeds buffer length {buffer_len}"
5768            ),
5769            Self::OutputTooSmall {
5770                required,
5771                available,
5772            } => write!(
5773                f,
5774                "base64 output buffer too small: required {required}, available {available}"
5775            ),
5776        }
5777    }
5778}
5779
5780#[cfg(feature = "std")]
5781impl std::error::Error for EncodeError {}
5782
5783/// Alphabet validation error.
5784#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5785pub enum AlphabetError {
5786    /// The alphabet contains a non-visible-ASCII byte.
5787    InvalidByte {
5788        /// Byte index in the alphabet table.
5789        index: usize,
5790        /// Invalid byte value.
5791        byte: u8,
5792    },
5793    /// The alphabet contains the padding byte `=`.
5794    PaddingByte {
5795        /// Byte index in the alphabet table.
5796        index: usize,
5797    },
5798    /// The alphabet maps more than one value to the same byte.
5799    DuplicateByte {
5800        /// First byte index.
5801        first: usize,
5802        /// Second byte index.
5803        second: usize,
5804        /// Duplicated byte value.
5805        byte: u8,
5806    },
5807}
5808
5809impl core::fmt::Display for AlphabetError {
5810    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
5811        match self {
5812            Self::InvalidByte { index, byte } => {
5813                write!(
5814                    f,
5815                    "invalid base64 alphabet byte 0x{byte:02x} at index {index}"
5816                )
5817            }
5818            Self::PaddingByte { index } => {
5819                write!(f, "base64 alphabet contains padding byte at index {index}")
5820            }
5821            Self::DuplicateByte {
5822                first,
5823                second,
5824                byte,
5825            } => write!(
5826                f,
5827                "base64 alphabet byte 0x{byte:02x} is duplicated at indexes {first} and {second}"
5828            ),
5829        }
5830    }
5831}
5832
5833#[cfg(feature = "std")]
5834impl std::error::Error for AlphabetError {}
5835
5836/// Decoding error.
5837#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5838pub enum DecodeError {
5839    /// The encoded input is malformed, but the decoder intentionally does not
5840    /// disclose a more specific error class.
5841    InvalidInput,
5842    /// The encoded input length is impossible for the selected padding policy.
5843    InvalidLength,
5844    /// A byte is not valid for the selected alphabet.
5845    InvalidByte {
5846        /// Byte index in the input.
5847        index: usize,
5848        /// Invalid byte value.
5849        byte: u8,
5850    },
5851    /// Padding is missing, misplaced, or non-canonical.
5852    InvalidPadding {
5853        /// Byte index where padding became invalid.
5854        index: usize,
5855    },
5856    /// Line wrapping is missing, misplaced, or uses the wrong line ending.
5857    InvalidLineWrap {
5858        /// Byte index where line wrapping became invalid.
5859        index: usize,
5860    },
5861    /// The output buffer is too small.
5862    OutputTooSmall {
5863        /// Required output bytes.
5864        required: usize,
5865        /// Available output bytes.
5866        available: usize,
5867    },
5868}
5869
5870impl core::fmt::Display for DecodeError {
5871    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
5872        match self {
5873            Self::InvalidInput => f.write_str("malformed base64 input"),
5874            Self::InvalidLength => f.write_str("invalid base64 input length"),
5875            Self::InvalidByte { index, byte } => {
5876                write!(f, "invalid base64 byte 0x{byte:02x} at index {index}")
5877            }
5878            Self::InvalidPadding { index } => write!(f, "invalid base64 padding at index {index}"),
5879            Self::InvalidLineWrap { index } => {
5880                write!(f, "invalid base64 line wrapping at index {index}")
5881            }
5882            Self::OutputTooSmall {
5883                required,
5884                available,
5885            } => write!(
5886                f,
5887                "base64 decode output buffer too small: required {required}, available {available}"
5888            ),
5889        }
5890    }
5891}
5892
5893impl DecodeError {
5894    fn with_index_offset(self, offset: usize) -> Self {
5895        match self {
5896            Self::InvalidByte { index, byte } => Self::InvalidByte {
5897                index: index + offset,
5898                byte,
5899            },
5900            Self::InvalidPadding { index } => Self::InvalidPadding {
5901                index: index + offset,
5902            },
5903            Self::InvalidLineWrap { index } => Self::InvalidLineWrap {
5904                index: index + offset,
5905            },
5906            Self::InvalidInput | Self::InvalidLength | Self::OutputTooSmall { .. } => self,
5907        }
5908    }
5909}
5910
5911#[cfg(feature = "std")]
5912impl std::error::Error for DecodeError {}
5913
5914fn validate_legacy_decode<A: Alphabet, const PAD: bool>(
5915    input: &[u8],
5916) -> Result<usize, DecodeError> {
5917    let mut chunk = [0u8; 4];
5918    let mut indexes = [0usize; 4];
5919    let mut chunk_len = 0;
5920    let mut required = 0;
5921    let mut terminal_seen = false;
5922
5923    for (index, byte) in input.iter().copied().enumerate() {
5924        if is_legacy_whitespace(byte) {
5925            continue;
5926        }
5927        if terminal_seen {
5928            return Err(DecodeError::InvalidPadding { index });
5929        }
5930
5931        chunk[chunk_len] = byte;
5932        indexes[chunk_len] = index;
5933        chunk_len += 1;
5934
5935        if chunk_len == 4 {
5936            let written =
5937                validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
5938            required += written;
5939            terminal_seen = written < 3;
5940            chunk_len = 0;
5941        }
5942    }
5943
5944    if chunk_len == 0 {
5945        return Ok(required);
5946    }
5947    if PAD {
5948        return Err(DecodeError::InvalidLength);
5949    }
5950
5951    validate_tail_unpadded::<A>(&chunk[..chunk_len])
5952        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
5953    Ok(required + decoded_capacity(chunk_len))
5954}
5955
5956fn decode_legacy_to_slice<A: Alphabet, const PAD: bool>(
5957    input: &[u8],
5958    output: &mut [u8],
5959) -> Result<usize, DecodeError> {
5960    let mut chunk = [0u8; 4];
5961    let mut indexes = [0usize; 4];
5962    let mut chunk_len = 0;
5963    let mut write = 0;
5964    let mut terminal_seen = false;
5965
5966    for (index, byte) in input.iter().copied().enumerate() {
5967        if is_legacy_whitespace(byte) {
5968            continue;
5969        }
5970        if terminal_seen {
5971            return Err(DecodeError::InvalidPadding { index });
5972        }
5973
5974        chunk[chunk_len] = byte;
5975        indexes[chunk_len] = index;
5976        chunk_len += 1;
5977
5978        if chunk_len == 4 {
5979            let available = output.len();
5980            let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
5981                required: write,
5982                available,
5983            })?;
5984            let written = decode_chunk::<A, PAD>(chunk, output_tail)
5985                .map_err(|err| map_chunk_error(err, &indexes))?;
5986            write += written;
5987            terminal_seen = written < 3;
5988            chunk_len = 0;
5989        }
5990    }
5991
5992    if chunk_len == 0 {
5993        return Ok(write);
5994    }
5995    if PAD {
5996        return Err(DecodeError::InvalidLength);
5997    }
5998
5999    decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
6000        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
6001        .map(|n| write + n)
6002}
6003
6004struct WrappedBytes<'a> {
6005    input: &'a [u8],
6006    wrap: LineWrap,
6007    index: usize,
6008    line_len: usize,
6009}
6010
6011impl<'a> WrappedBytes<'a> {
6012    const fn new(input: &'a [u8], wrap: LineWrap) -> Result<Self, DecodeError> {
6013        if wrap.line_len == 0 {
6014            return Err(DecodeError::InvalidLineWrap { index: 0 });
6015        }
6016        Ok(Self {
6017            input,
6018            wrap,
6019            index: 0,
6020            line_len: 0,
6021        })
6022    }
6023
6024    fn next_byte(&mut self) -> Result<Option<(usize, u8)>, DecodeError> {
6025        loop {
6026            if self.index == self.input.len() {
6027                return Ok(None);
6028            }
6029
6030            if self.starts_with_line_ending() {
6031                let line_end_index = self.index;
6032                if self.line_len == 0 {
6033                    return Err(DecodeError::InvalidLineWrap {
6034                        index: line_end_index,
6035                    });
6036                }
6037
6038                self.index += self.wrap.line_ending.byte_len();
6039                if self.index == self.input.len() {
6040                    self.line_len = 0;
6041                    return Ok(None);
6042                }
6043
6044                if self.line_len != self.wrap.line_len {
6045                    return Err(DecodeError::InvalidLineWrap {
6046                        index: line_end_index,
6047                    });
6048                }
6049                self.line_len = 0;
6050                continue;
6051            }
6052
6053            let byte = self.input[self.index];
6054            if matches!(byte, b'\r' | b'\n') {
6055                return Err(DecodeError::InvalidLineWrap { index: self.index });
6056            }
6057
6058            self.line_len += 1;
6059            if self.line_len > self.wrap.line_len {
6060                return Err(DecodeError::InvalidLineWrap { index: self.index });
6061            }
6062
6063            let index = self.index;
6064            self.index += 1;
6065            return Ok(Some((index, byte)));
6066        }
6067    }
6068
6069    fn starts_with_line_ending(&self) -> bool {
6070        let line_ending = self.wrap.line_ending.as_bytes();
6071        let end = self.index + line_ending.len();
6072        end <= self.input.len() && &self.input[self.index..end] == line_ending
6073    }
6074}
6075
6076fn validate_wrapped_decode<A: Alphabet, const PAD: bool>(
6077    input: &[u8],
6078    wrap: LineWrap,
6079) -> Result<usize, DecodeError> {
6080    let mut bytes = WrappedBytes::new(input, wrap)?;
6081    let mut chunk = [0u8; 4];
6082    let mut indexes = [0usize; 4];
6083    let mut chunk_len = 0;
6084    let mut required = 0;
6085    let mut terminal_seen = false;
6086
6087    while let Some((index, byte)) = bytes.next_byte()? {
6088        if terminal_seen {
6089            return Err(DecodeError::InvalidPadding { index });
6090        }
6091
6092        chunk[chunk_len] = byte;
6093        indexes[chunk_len] = index;
6094        chunk_len += 1;
6095
6096        if chunk_len == 4 {
6097            let written =
6098                validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
6099            required += written;
6100            terminal_seen = written < 3;
6101            chunk_len = 0;
6102        }
6103    }
6104
6105    if chunk_len == 0 {
6106        return Ok(required);
6107    }
6108    if PAD {
6109        return Err(DecodeError::InvalidLength);
6110    }
6111
6112    validate_tail_unpadded::<A>(&chunk[..chunk_len])
6113        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
6114    Ok(required + decoded_capacity(chunk_len))
6115}
6116
6117fn decode_wrapped_to_slice<A: Alphabet, const PAD: bool>(
6118    input: &[u8],
6119    output: &mut [u8],
6120    wrap: LineWrap,
6121) -> Result<usize, DecodeError> {
6122    let mut bytes = WrappedBytes::new(input, wrap)?;
6123    let mut chunk = [0u8; 4];
6124    let mut indexes = [0usize; 4];
6125    let mut chunk_len = 0;
6126    let mut write = 0;
6127    let mut terminal_seen = false;
6128
6129    while let Some((index, byte)) = bytes.next_byte()? {
6130        if terminal_seen {
6131            return Err(DecodeError::InvalidPadding { index });
6132        }
6133
6134        chunk[chunk_len] = byte;
6135        indexes[chunk_len] = index;
6136        chunk_len += 1;
6137
6138        if chunk_len == 4 {
6139            let available = output.len();
6140            let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
6141                required: write,
6142                available,
6143            })?;
6144            let written = decode_chunk::<A, PAD>(chunk, output_tail)
6145                .map_err(|err| map_chunk_error(err, &indexes))?;
6146            write += written;
6147            terminal_seen = written < 3;
6148            chunk_len = 0;
6149        }
6150    }
6151
6152    if chunk_len == 0 {
6153        return Ok(write);
6154    }
6155    if PAD {
6156        return Err(DecodeError::InvalidLength);
6157    }
6158
6159    decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
6160        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
6161        .map(|n| write + n)
6162}
6163
6164fn compact_wrapped_input(buffer: &mut [u8], wrap: LineWrap) -> Result<usize, DecodeError> {
6165    if !wrap.is_valid() {
6166        return Err(DecodeError::InvalidLineWrap { index: 0 });
6167    }
6168
6169    let line_ending = wrap.line_ending.as_bytes();
6170    let line_ending_len = line_ending.len();
6171    let mut read = 0;
6172    let mut write = 0;
6173
6174    while read < buffer.len() {
6175        let line_end = read + line_ending_len;
6176        if buffer.get(read..line_end) == Some(line_ending) {
6177            read = line_end;
6178            continue;
6179        }
6180
6181        buffer[write] = buffer[read];
6182        write += 1;
6183        read += 1;
6184    }
6185
6186    Ok(write)
6187}
6188
6189#[inline]
6190const fn is_legacy_whitespace(byte: u8) -> bool {
6191    matches!(byte, b' ' | b'\t' | b'\r' | b'\n')
6192}
6193
6194fn map_chunk_error(err: DecodeError, indexes: &[usize; 4]) -> DecodeError {
6195    match err {
6196        DecodeError::InvalidByte { index, byte } => DecodeError::InvalidByte {
6197            index: indexes[index],
6198            byte,
6199        },
6200        DecodeError::InvalidPadding { index } => DecodeError::InvalidPadding {
6201            index: indexes[index],
6202        },
6203        DecodeError::InvalidInput
6204        | DecodeError::InvalidLineWrap { .. }
6205        | DecodeError::InvalidLength
6206        | DecodeError::OutputTooSmall { .. } => err,
6207    }
6208}
6209
6210fn map_partial_chunk_error(err: DecodeError, indexes: &[usize; 4], len: usize) -> DecodeError {
6211    match err {
6212        DecodeError::InvalidByte { index, byte } if index < len => DecodeError::InvalidByte {
6213            index: indexes[index],
6214            byte,
6215        },
6216        DecodeError::InvalidPadding { index } if index < len => DecodeError::InvalidPadding {
6217            index: indexes[index],
6218        },
6219        DecodeError::InvalidByte { .. }
6220        | DecodeError::InvalidPadding { .. }
6221        | DecodeError::InvalidLineWrap { .. }
6222        | DecodeError::InvalidInput
6223        | DecodeError::InvalidLength
6224        | DecodeError::OutputTooSmall { .. } => err,
6225    }
6226}
6227
6228fn decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
6229    if !input.len().is_multiple_of(4) {
6230        return Err(DecodeError::InvalidLength);
6231    }
6232    let required = decoded_len_padded(input)?;
6233    if output.len() < required {
6234        return Err(DecodeError::OutputTooSmall {
6235            required,
6236            available: output.len(),
6237        });
6238    }
6239
6240    let mut read = 0;
6241    let mut write = 0;
6242    while read < input.len() {
6243        let chunk = read_quad(input, read)?;
6244        let available = output.len();
6245        let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
6246            required: write,
6247            available,
6248        })?;
6249        let written = decode_chunk::<A, true>(chunk, output_tail)
6250            .map_err(|err| err.with_index_offset(read))?;
6251        read += 4;
6252        write += written;
6253        if written < 3 && read != input.len() {
6254            return Err(DecodeError::InvalidPadding { index: read - 4 });
6255        }
6256    }
6257    Ok(write)
6258}
6259
6260fn validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
6261    if input.is_empty() {
6262        return Ok(0);
6263    }
6264
6265    if PAD {
6266        validate_padded::<A>(input)
6267    } else {
6268        validate_unpadded::<A>(input)
6269    }
6270}
6271
6272fn validate_padded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
6273    if !input.len().is_multiple_of(4) {
6274        return Err(DecodeError::InvalidLength);
6275    }
6276    let required = decoded_len_padded(input)?;
6277
6278    let mut read = 0;
6279    while read < input.len() {
6280        let chunk = read_quad(input, read)?;
6281        let written =
6282            validate_chunk::<A, true>(chunk).map_err(|err| err.with_index_offset(read))?;
6283        read += 4;
6284        if written < 3 && read != input.len() {
6285            return Err(DecodeError::InvalidPadding { index: read - 4 });
6286        }
6287    }
6288
6289    Ok(required)
6290}
6291
6292fn validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
6293    let required = decoded_len_unpadded(input)?;
6294
6295    let mut read = 0;
6296    while read + 4 <= input.len() {
6297        let chunk = read_quad(input, read)?;
6298        validate_chunk::<A, false>(chunk).map_err(|err| err.with_index_offset(read))?;
6299        read += 4;
6300    }
6301    validate_tail_unpadded::<A>(&input[read..]).map_err(|err| err.with_index_offset(read))?;
6302
6303    Ok(required)
6304}
6305
6306fn decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
6307    let required = decoded_len_unpadded(input)?;
6308    if output.len() < required {
6309        return Err(DecodeError::OutputTooSmall {
6310            required,
6311            available: output.len(),
6312        });
6313    }
6314
6315    let mut read = 0;
6316    let mut write = 0;
6317    while read + 4 <= input.len() {
6318        let chunk = read_quad(input, read)?;
6319        let available = output.len();
6320        let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
6321            required: write,
6322            available,
6323        })?;
6324        let written = decode_chunk::<A, false>(chunk, output_tail)
6325            .map_err(|err| err.with_index_offset(read))?;
6326        read += 4;
6327        write += written;
6328    }
6329    decode_tail_unpadded::<A>(&input[read..], &mut output[write..])
6330        .map_err(|err| err.with_index_offset(read))
6331        .map(|n| write + n)
6332}
6333
6334fn decoded_len_padded(input: &[u8]) -> Result<usize, DecodeError> {
6335    if input.is_empty() {
6336        return Ok(0);
6337    }
6338    if !input.len().is_multiple_of(4) {
6339        return Err(DecodeError::InvalidLength);
6340    }
6341
6342    let Some((&last, before_last_prefix)) = input.split_last() else {
6343        return Ok(0);
6344    };
6345    let Some(&before_last) = before_last_prefix.last() else {
6346        return Err(DecodeError::InvalidLength);
6347    };
6348
6349    let mut padding = 0;
6350    if last == b'=' {
6351        padding += 1;
6352    }
6353    if before_last == b'=' {
6354        padding += 1;
6355    }
6356    if padding == 0
6357        && let Some(index) = input.iter().position(|byte| *byte == b'=')
6358    {
6359        return Err(DecodeError::InvalidPadding { index });
6360    }
6361    if padding > 0 {
6362        let first_pad = input.len() - padding;
6363        if let Some(index) = input[..first_pad].iter().position(|byte| *byte == b'=') {
6364            return Err(DecodeError::InvalidPadding { index });
6365        }
6366    }
6367    Ok(input.len() / 4 * 3 - padding)
6368}
6369
6370fn decoded_len_unpadded(input: &[u8]) -> Result<usize, DecodeError> {
6371    if input.len() % 4 == 1 {
6372        return Err(DecodeError::InvalidLength);
6373    }
6374    if let Some(index) = input.iter().position(|byte| *byte == b'=') {
6375        return Err(DecodeError::InvalidPadding { index });
6376    }
6377    Ok(decoded_capacity(input.len()))
6378}
6379
6380fn read_quad(input: &[u8], offset: usize) -> Result<[u8; 4], DecodeError> {
6381    let end = offset.checked_add(4).ok_or(DecodeError::InvalidLength)?;
6382    match input.get(offset..end) {
6383        Some([b0, b1, b2, b3]) => Ok([*b0, *b1, *b2, *b3]),
6384        _ => Err(DecodeError::InvalidLength),
6385    }
6386}
6387
6388fn first_padding_index(input: [u8; 4]) -> usize {
6389    let [b0, b1, b2, b3] = input;
6390    if b0 == b'=' {
6391        0
6392    } else if b1 == b'=' {
6393        1
6394    } else if b2 == b'=' {
6395        2
6396    } else if b3 == b'=' {
6397        3
6398    } else {
6399        0
6400    }
6401}
6402
6403fn validate_chunk<A: Alphabet, const PAD: bool>(input: [u8; 4]) -> Result<usize, DecodeError> {
6404    let [b0, b1, b2, b3] = input;
6405    let _v0 = decode_byte::<A>(b0, 0)?;
6406    let v1 = decode_byte::<A>(b1, 1)?;
6407
6408    match (b2, b3) {
6409        (b'=', b'=') if PAD => {
6410            if v1 & 0b0000_1111 != 0 {
6411                return Err(DecodeError::InvalidPadding { index: 1 });
6412            }
6413            Ok(1)
6414        }
6415        (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
6416        (_, b'=') if PAD => {
6417            let v2 = decode_byte::<A>(b2, 2)?;
6418            if v2 & 0b0000_0011 != 0 {
6419                return Err(DecodeError::InvalidPadding { index: 2 });
6420            }
6421            Ok(2)
6422        }
6423        (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
6424            index: first_padding_index(input),
6425        }),
6426        _ => {
6427            decode_byte::<A>(b2, 2)?;
6428            decode_byte::<A>(b3, 3)?;
6429            Ok(3)
6430        }
6431    }
6432}
6433
6434fn decode_chunk<A: Alphabet, const PAD: bool>(
6435    input: [u8; 4],
6436    output: &mut [u8],
6437) -> Result<usize, DecodeError> {
6438    let [b0, b1, b2, b3] = input;
6439    let v0 = decode_byte::<A>(b0, 0)?;
6440    let v1 = decode_byte::<A>(b1, 1)?;
6441
6442    match (b2, b3) {
6443        (b'=', b'=') if PAD => {
6444            if output.is_empty() {
6445                return Err(DecodeError::OutputTooSmall {
6446                    required: 1,
6447                    available: output.len(),
6448                });
6449            }
6450            if v1 & 0b0000_1111 != 0 {
6451                return Err(DecodeError::InvalidPadding { index: 1 });
6452            }
6453            output[0] = (v0 << 2) | (v1 >> 4);
6454            Ok(1)
6455        }
6456        (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
6457        (_, b'=') if PAD => {
6458            if output.len() < 2 {
6459                return Err(DecodeError::OutputTooSmall {
6460                    required: 2,
6461                    available: output.len(),
6462                });
6463            }
6464            let v2 = decode_byte::<A>(b2, 2)?;
6465            if v2 & 0b0000_0011 != 0 {
6466                return Err(DecodeError::InvalidPadding { index: 2 });
6467            }
6468            output[0] = (v0 << 2) | (v1 >> 4);
6469            output[1] = (v1 << 4) | (v2 >> 2);
6470            Ok(2)
6471        }
6472        (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
6473            index: first_padding_index(input),
6474        }),
6475        _ => {
6476            if output.len() < 3 {
6477                return Err(DecodeError::OutputTooSmall {
6478                    required: 3,
6479                    available: output.len(),
6480                });
6481            }
6482            let v2 = decode_byte::<A>(b2, 2)?;
6483            let v3 = decode_byte::<A>(b3, 3)?;
6484            output[0] = (v0 << 2) | (v1 >> 4);
6485            output[1] = (v1 << 4) | (v2 >> 2);
6486            output[2] = (v2 << 6) | v3;
6487            Ok(3)
6488        }
6489    }
6490}
6491
6492fn validate_tail_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
6493    match input {
6494        [] => Ok(()),
6495        [b0, b1] => {
6496            decode_byte::<A>(*b0, 0)?;
6497            let v1 = decode_byte::<A>(*b1, 1)?;
6498            if v1 & 0b0000_1111 != 0 {
6499                return Err(DecodeError::InvalidPadding { index: 1 });
6500            }
6501            Ok(())
6502        }
6503        [b0, b1, b2] => {
6504            decode_byte::<A>(*b0, 0)?;
6505            decode_byte::<A>(*b1, 1)?;
6506            let v2 = decode_byte::<A>(*b2, 2)?;
6507            if v2 & 0b0000_0011 != 0 {
6508                return Err(DecodeError::InvalidPadding { index: 2 });
6509            }
6510            Ok(())
6511        }
6512        _ => Err(DecodeError::InvalidLength),
6513    }
6514}
6515
6516fn decode_tail_unpadded<A: Alphabet>(
6517    input: &[u8],
6518    output: &mut [u8],
6519) -> Result<usize, DecodeError> {
6520    match input {
6521        [] => Ok(0),
6522        [b0, b1] => {
6523            let Some(out0) = output.first_mut() else {
6524                return Err(DecodeError::OutputTooSmall {
6525                    required: 1,
6526                    available: output.len(),
6527                });
6528            };
6529            let v0 = decode_byte::<A>(*b0, 0)?;
6530            let v1 = decode_byte::<A>(*b1, 1)?;
6531            if v1 & 0b0000_1111 != 0 {
6532                return Err(DecodeError::InvalidPadding { index: 1 });
6533            }
6534            *out0 = (v0 << 2) | (v1 >> 4);
6535            Ok(1)
6536        }
6537        [b0, b1, b2] => {
6538            let available = output.len();
6539            let Some([out0, out1]) = output.get_mut(..2) else {
6540                return Err(DecodeError::OutputTooSmall {
6541                    required: 2,
6542                    available,
6543                });
6544            };
6545            let v0 = decode_byte::<A>(*b0, 0)?;
6546            let v1 = decode_byte::<A>(*b1, 1)?;
6547            let v2 = decode_byte::<A>(*b2, 2)?;
6548            if v2 & 0b0000_0011 != 0 {
6549                return Err(DecodeError::InvalidPadding { index: 2 });
6550            }
6551            *out0 = (v0 << 2) | (v1 >> 4);
6552            *out1 = (v1 << 4) | (v2 >> 2);
6553            Ok(2)
6554        }
6555        _ => Err(DecodeError::InvalidLength),
6556    }
6557}
6558
6559fn decode_byte<A: Alphabet>(byte: u8, index: usize) -> Result<u8, DecodeError> {
6560    A::decode(byte).ok_or(DecodeError::InvalidByte { index, byte })
6561}
6562
6563fn ct_decode_slice<A: Alphabet, const PAD: bool>(
6564    input: &[u8],
6565    output: &mut [u8],
6566) -> Result<usize, DecodeError> {
6567    if input.is_empty() {
6568        return Ok(0);
6569    }
6570
6571    if PAD {
6572        ct_decode_padded::<A>(input, output)
6573    } else {
6574        ct_decode_unpadded::<A>(input, output)
6575    }
6576}
6577
6578fn ct_decode_in_place<A: Alphabet, const PAD: bool>(
6579    buffer: &mut [u8],
6580) -> Result<usize, DecodeError> {
6581    if buffer.is_empty() {
6582        return Ok(0);
6583    }
6584
6585    if PAD {
6586        ct_decode_padded_in_place::<A>(buffer)
6587    } else {
6588        ct_decode_unpadded_in_place::<A>(buffer)
6589    }
6590}
6591
6592fn ct_validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<(), DecodeError> {
6593    if input.is_empty() {
6594        return Ok(());
6595    }
6596
6597    if PAD {
6598        ct_validate_padded::<A>(input)
6599    } else {
6600        ct_validate_unpadded::<A>(input)
6601    }
6602}
6603
6604fn ct_decoded_len<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
6605    ct_validate_decode::<A, PAD>(input)?;
6606    if input.is_empty() {
6607        return Ok(0);
6608    }
6609
6610    if PAD {
6611        Ok(input.len() / 4 * 3 - ct_padding_len(input))
6612    } else {
6613        let full_quads = input.len() / 4 * 3;
6614        match input.len() % 4 {
6615            0 => Ok(full_quads),
6616            2 => Ok(full_quads + 1),
6617            3 => Ok(full_quads + 2),
6618            _ => Err(DecodeError::InvalidLength),
6619        }
6620    }
6621}
6622
6623fn ct_validate_padded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
6624    if !input.len().is_multiple_of(4) {
6625        return Err(DecodeError::InvalidLength);
6626    }
6627
6628    let padding = ct_padding_len(input);
6629    let mut invalid_byte = 0u8;
6630    let mut invalid_padding = 0u8;
6631    let mut read = 0;
6632
6633    while read + 4 < input.len() {
6634        let [b0, b1, b2, b3] = read_quad(input, read)?;
6635        let (_, valid0) = ct_decode_alphabet_byte::<A>(b0);
6636        let (_, valid1) = ct_decode_alphabet_byte::<A>(b1);
6637        let (_, valid2) = ct_decode_alphabet_byte::<A>(b2);
6638        let (_, valid3) = ct_decode_alphabet_byte::<A>(b3);
6639
6640        invalid_byte |= !valid0;
6641        invalid_byte |= !valid1;
6642        invalid_byte |= !valid2;
6643        invalid_byte |= !valid3;
6644        invalid_padding |= ct_mask_eq_u8(b2, b'=');
6645        invalid_padding |= ct_mask_eq_u8(b3, b'=');
6646        read += 4;
6647    }
6648
6649    let final_chunk = read_quad(input, read)?;
6650    let (_, final_invalid_byte, final_invalid_padding, _) =
6651        ct_padded_final_quantum::<A>(final_chunk, padding);
6652    invalid_byte |= final_invalid_byte;
6653    invalid_padding |= final_invalid_padding;
6654
6655    report_ct_error(invalid_byte, invalid_padding)
6656}
6657
6658fn ct_validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
6659    if input.len() % 4 == 1 {
6660        return Err(DecodeError::InvalidLength);
6661    }
6662
6663    let mut invalid_byte = 0u8;
6664    let mut invalid_padding = 0u8;
6665    let mut read = 0;
6666
6667    while read + 4 <= input.len() {
6668        let [b0, b1, b2, b3] = read_quad(input, read)?;
6669        let (_, valid0) = ct_decode_alphabet_byte::<A>(b0);
6670        let (_, valid1) = ct_decode_alphabet_byte::<A>(b1);
6671        let (_, valid2) = ct_decode_alphabet_byte::<A>(b2);
6672        let (_, valid3) = ct_decode_alphabet_byte::<A>(b3);
6673
6674        invalid_byte |= !valid0;
6675        invalid_byte |= !valid1;
6676        invalid_byte |= !valid2;
6677        invalid_byte |= !valid3;
6678        invalid_padding |= ct_mask_eq_u8(b0, b'=');
6679        invalid_padding |= ct_mask_eq_u8(b1, b'=');
6680        invalid_padding |= ct_mask_eq_u8(b2, b'=');
6681        invalid_padding |= ct_mask_eq_u8(b3, b'=');
6682
6683        read += 4;
6684    }
6685
6686    match input.get(read..) {
6687        Some([]) => {}
6688        Some([b0, b1]) => {
6689            let (_, valid0) = ct_decode_alphabet_byte::<A>(*b0);
6690            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
6691            invalid_byte |= !valid0;
6692            invalid_byte |= !valid1;
6693            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
6694            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
6695            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
6696        }
6697        Some([b0, b1, b2]) => {
6698            let (_, valid0) = ct_decode_alphabet_byte::<A>(*b0);
6699            let (_, valid1) = ct_decode_alphabet_byte::<A>(*b1);
6700            let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
6701            invalid_byte |= !valid0;
6702            invalid_byte |= !valid1;
6703            invalid_byte |= !valid2;
6704            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
6705            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
6706            invalid_padding |= ct_mask_eq_u8(*b2, b'=');
6707            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
6708        }
6709        _ => return Err(DecodeError::InvalidLength),
6710    }
6711
6712    report_ct_error(invalid_byte, invalid_padding)
6713}
6714
6715fn ct_padded_final_quantum<A: Alphabet>(
6716    input: [u8; 4],
6717    padding: usize,
6718) -> ([u8; 3], u8, u8, usize) {
6719    let [b0, b1, b2, b3] = input;
6720    let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
6721    let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
6722    let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
6723    let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
6724
6725    let padding_byte = padding.to_le_bytes()[0];
6726    let no_padding = ct_mask_eq_u8(padding_byte, 0);
6727    let one_padding = ct_mask_eq_u8(padding_byte, 1);
6728    let two_padding = ct_mask_eq_u8(padding_byte, 2);
6729    let require_v2 = no_padding | one_padding;
6730    let require_v3 = no_padding;
6731
6732    let invalid_byte = !valid0 | !valid1 | (!valid2 & require_v2) | (!valid3 & require_v3);
6733    let invalid_padding = (ct_mask_nonzero_u8(v1 & 0b0000_1111) & two_padding)
6734        | ((ct_mask_eq_u8(b2, b'=') | ct_mask_nonzero_u8(v2 & 0b0000_0011)) & one_padding)
6735        | ((ct_mask_eq_u8(b2, b'=') | ct_mask_eq_u8(b3, b'=')) & no_padding);
6736
6737    (
6738        [(v0 << 2) | (v1 >> 4), (v1 << 4) | (v2 >> 2), (v2 << 6) | v3],
6739        invalid_byte,
6740        invalid_padding,
6741        3 - padding,
6742    )
6743}
6744
6745fn ct_decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
6746    if !input.len().is_multiple_of(4) {
6747        return Err(DecodeError::InvalidLength);
6748    }
6749
6750    let padding = ct_padding_len(input);
6751    let required = input.len() / 4 * 3 - padding;
6752    if output.len() < required {
6753        return Err(DecodeError::OutputTooSmall {
6754            required,
6755            available: output.len(),
6756        });
6757    }
6758
6759    let mut invalid_byte = 0u8;
6760    let mut invalid_padding = 0u8;
6761    let mut write = 0;
6762    let mut read = 0;
6763
6764    while read + 4 < input.len() {
6765        let [b0, b1, b2, b3] = read_quad(input, read)?;
6766        let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
6767        let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
6768        let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
6769        let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
6770
6771        invalid_byte |= !valid0;
6772        invalid_byte |= !valid1;
6773        invalid_byte |= !valid2;
6774        invalid_byte |= !valid3;
6775        invalid_padding |= ct_mask_eq_u8(b2, b'=');
6776        invalid_padding |= ct_mask_eq_u8(b3, b'=');
6777        output[write] = (v0 << 2) | (v1 >> 4);
6778        output[write + 1] = (v1 << 4) | (v2 >> 2);
6779        output[write + 2] = (v2 << 6) | v3;
6780        write += 3;
6781        read += 4;
6782    }
6783
6784    let final_chunk = read_quad(input, read)?;
6785    let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
6786        ct_padded_final_quantum::<A>(final_chunk, padding);
6787    invalid_byte |= final_invalid_byte;
6788    invalid_padding |= final_invalid_padding;
6789    output[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
6790    write += final_written;
6791
6792    report_ct_error(invalid_byte, invalid_padding)?;
6793    Ok(write)
6794}
6795
6796fn ct_decode_padded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
6797    if !buffer.len().is_multiple_of(4) {
6798        return Err(DecodeError::InvalidLength);
6799    }
6800
6801    let padding = ct_padding_len(buffer);
6802    let required = buffer.len() / 4 * 3 - padding;
6803    debug_assert!(required <= buffer.len());
6804
6805    let mut invalid_byte = 0u8;
6806    let mut invalid_padding = 0u8;
6807    let mut write = 0;
6808    let mut read = 0;
6809
6810    while read + 4 < buffer.len() {
6811        let [b0, b1, b2, b3] = read_quad(buffer, read)?;
6812        let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
6813        let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
6814        let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
6815        let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
6816
6817        invalid_byte |= !valid0;
6818        invalid_byte |= !valid1;
6819        invalid_byte |= !valid2;
6820        invalid_byte |= !valid3;
6821        invalid_padding |= ct_mask_eq_u8(b2, b'=');
6822        invalid_padding |= ct_mask_eq_u8(b3, b'=');
6823        buffer[write] = (v0 << 2) | (v1 >> 4);
6824        buffer[write + 1] = (v1 << 4) | (v2 >> 2);
6825        buffer[write + 2] = (v2 << 6) | v3;
6826        write += 3;
6827        read += 4;
6828    }
6829
6830    let final_chunk = read_quad(buffer, read)?;
6831    let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
6832        ct_padded_final_quantum::<A>(final_chunk, padding);
6833    invalid_byte |= final_invalid_byte;
6834    invalid_padding |= final_invalid_padding;
6835    buffer[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
6836    write += final_written;
6837
6838    debug_assert_eq!(write, required);
6839    report_ct_error(invalid_byte, invalid_padding)?;
6840    Ok(write)
6841}
6842
6843fn ct_decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
6844    if input.len() % 4 == 1 {
6845        return Err(DecodeError::InvalidLength);
6846    }
6847
6848    let required = decoded_capacity(input.len());
6849    if output.len() < required {
6850        return Err(DecodeError::OutputTooSmall {
6851            required,
6852            available: output.len(),
6853        });
6854    }
6855
6856    let mut invalid_byte = 0u8;
6857    let mut invalid_padding = 0u8;
6858    let mut write = 0;
6859    let mut read = 0;
6860
6861    while read + 4 <= input.len() {
6862        let [b0, b1, b2, b3] = read_quad(input, read)?;
6863        let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
6864        let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
6865        let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
6866        let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
6867
6868        invalid_byte |= !valid0;
6869        invalid_byte |= !valid1;
6870        invalid_byte |= !valid2;
6871        invalid_byte |= !valid3;
6872        invalid_padding |= ct_mask_eq_u8(b0, b'=');
6873        invalid_padding |= ct_mask_eq_u8(b1, b'=');
6874        invalid_padding |= ct_mask_eq_u8(b2, b'=');
6875        invalid_padding |= ct_mask_eq_u8(b3, b'=');
6876
6877        output[write] = (v0 << 2) | (v1 >> 4);
6878        output[write + 1] = (v1 << 4) | (v2 >> 2);
6879        output[write + 2] = (v2 << 6) | v3;
6880        read += 4;
6881        write += 3;
6882    }
6883
6884    match input.get(read..) {
6885        Some([]) => {}
6886        Some([b0, b1]) => {
6887            let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
6888            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
6889            invalid_byte |= !valid0;
6890            invalid_byte |= !valid1;
6891            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
6892            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
6893            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
6894            output[write] = (v0 << 2) | (v1 >> 4);
6895            write += 1;
6896        }
6897        Some([b0, b1, b2]) => {
6898            let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
6899            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
6900            let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
6901            invalid_byte |= !valid0;
6902            invalid_byte |= !valid1;
6903            invalid_byte |= !valid2;
6904            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
6905            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
6906            invalid_padding |= ct_mask_eq_u8(*b2, b'=');
6907            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
6908            output[write] = (v0 << 2) | (v1 >> 4);
6909            output[write + 1] = (v1 << 4) | (v2 >> 2);
6910            write += 2;
6911        }
6912        _ => return Err(DecodeError::InvalidLength),
6913    }
6914
6915    report_ct_error(invalid_byte, invalid_padding)?;
6916    Ok(write)
6917}
6918
6919fn ct_decode_unpadded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
6920    if buffer.len() % 4 == 1 {
6921        return Err(DecodeError::InvalidLength);
6922    }
6923
6924    let required = decoded_capacity(buffer.len());
6925    debug_assert!(required <= buffer.len());
6926
6927    let mut invalid_byte = 0u8;
6928    let mut invalid_padding = 0u8;
6929    let mut write = 0;
6930    let mut read = 0;
6931
6932    while read + 4 <= buffer.len() {
6933        let [b0, b1, b2, b3] = read_quad(buffer, read)?;
6934        let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
6935        let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
6936        let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
6937        let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
6938
6939        invalid_byte |= !valid0;
6940        invalid_byte |= !valid1;
6941        invalid_byte |= !valid2;
6942        invalid_byte |= !valid3;
6943        invalid_padding |= ct_mask_eq_u8(b0, b'=');
6944        invalid_padding |= ct_mask_eq_u8(b1, b'=');
6945        invalid_padding |= ct_mask_eq_u8(b2, b'=');
6946        invalid_padding |= ct_mask_eq_u8(b3, b'=');
6947
6948        buffer[write] = (v0 << 2) | (v1 >> 4);
6949        buffer[write + 1] = (v1 << 4) | (v2 >> 2);
6950        buffer[write + 2] = (v2 << 6) | v3;
6951        read += 4;
6952        write += 3;
6953    }
6954
6955    match read_tail(buffer, read)? {
6956        [] => {}
6957        [b0, b1] => {
6958            let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
6959            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
6960            invalid_byte |= !valid0;
6961            invalid_byte |= !valid1;
6962            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
6963            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
6964            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
6965            buffer[write] = (v0 << 2) | (v1 >> 4);
6966            write += 1;
6967        }
6968        [b0, b1, b2] => {
6969            let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
6970            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
6971            let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
6972            invalid_byte |= !valid0;
6973            invalid_byte |= !valid1;
6974            invalid_byte |= !valid2;
6975            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
6976            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
6977            invalid_padding |= ct_mask_eq_u8(*b2, b'=');
6978            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
6979            buffer[write] = (v0 << 2) | (v1 >> 4);
6980            buffer[write + 1] = (v1 << 4) | (v2 >> 2);
6981            write += 2;
6982        }
6983        _ => return Err(DecodeError::InvalidLength),
6984    }
6985
6986    debug_assert_eq!(write, required);
6987    report_ct_error(invalid_byte, invalid_padding)?;
6988    Ok(write)
6989}
6990
6991fn read_tail(input: &[u8], offset: usize) -> Result<&[u8], DecodeError> {
6992    input.get(offset..).ok_or(DecodeError::InvalidLength)
6993}
6994
6995#[inline]
6996fn ct_decode_alphabet_byte<A: Alphabet>(byte: u8) -> (u8, u8) {
6997    let mut decoded = 0u8;
6998    let mut valid = 0u8;
6999    let mut candidate = 0u8;
7000
7001    while candidate < 64 {
7002        let matches = ct_mask_eq_u8(byte, A::ENCODE[candidate as usize]);
7003        decoded |= candidate & matches;
7004        valid |= matches;
7005        candidate += 1;
7006    }
7007
7008    (decoded, valid)
7009}
7010
7011fn ct_padding_len(input: &[u8]) -> usize {
7012    let Some((&last, before_last_prefix)) = input.split_last() else {
7013        return 0;
7014    };
7015    let Some(&before_last) = before_last_prefix.last() else {
7016        return 0;
7017    };
7018    usize::from(ct_mask_eq_u8(last, b'=') & 1) + usize::from(ct_mask_eq_u8(before_last, b'=') & 1)
7019}
7020
7021fn report_ct_error(invalid_byte: u8, invalid_padding: u8) -> Result<(), DecodeError> {
7022    if (invalid_byte | invalid_padding) != 0 {
7023        Err(DecodeError::InvalidInput)
7024    } else {
7025        Ok(())
7026    }
7027}
7028
7029#[cfg(kani)]
7030mod kani_proofs {
7031    use super::{
7032        STANDARD, Standard, checked_encoded_len, ct, decode_byte, decode_chunk,
7033        decode_tail_unpadded, decoded_capacity, validate_tail_unpadded,
7034    };
7035
7036    #[kani::proof]
7037    fn checked_encoded_len_is_bounded_for_small_inputs() {
7038        let len = usize::from(kani::any::<u8>());
7039        let padded = kani::any::<bool>();
7040        let encoded = checked_encoded_len(len, padded).expect("u8 input length cannot overflow");
7041
7042        assert!(encoded >= len);
7043        assert!(encoded <= len / 3 * 4 + 4);
7044    }
7045
7046    #[kani::proof]
7047    fn decoded_capacity_is_bounded_for_small_inputs() {
7048        let len = usize::from(kani::any::<u8>());
7049        let capacity = decoded_capacity(len);
7050
7051        assert!(capacity <= len / 4 * 3 + 2);
7052    }
7053
7054    #[kani::proof]
7055    #[kani::unwind(3)]
7056    fn standard_in_place_decode_returns_prefix_within_buffer() {
7057        let mut buffer = kani::any::<[u8; 8]>();
7058        let result = STANDARD.decode_in_place(&mut buffer);
7059
7060        if let Ok(decoded) = result {
7061            assert!(decoded.len() <= 8);
7062        }
7063    }
7064
7065    #[kani::proof]
7066    #[kani::unwind(3)]
7067    fn standard_decode_slice_returns_written_within_output() {
7068        let input = kani::any::<[u8; 4]>();
7069        let mut output = kani::any::<[u8; 3]>();
7070        let result = STANDARD.decode_slice(&input, &mut output);
7071
7072        if let Ok(written) = result {
7073            assert!(written <= output.len());
7074        }
7075    }
7076
7077    #[kani::proof]
7078    #[kani::unwind(3)]
7079    fn standard_decode_chunk_returns_written_within_output() {
7080        let input = kani::any::<[u8; 4]>();
7081        let mut output = kani::any::<[u8; 3]>();
7082        let result = decode_chunk::<Standard, true>(input, &mut output);
7083
7084        if let Ok(written) = result {
7085            assert!(written <= output.len());
7086            assert!(written <= 3);
7087        }
7088    }
7089
7090    #[kani::proof]
7091    #[kani::unwind(3)]
7092    fn standard_decode_chunk_bit_packing_matches_decoded_values() {
7093        let input = kani::any::<[u8; 4]>();
7094        let mut output = kani::any::<[u8; 3]>();
7095        let result = decode_chunk::<Standard, true>(input, &mut output);
7096
7097        if let Ok(written) = result {
7098            let v0 = decode_byte::<Standard>(input[0], 0).expect("successful chunk has v0");
7099            let v1 = decode_byte::<Standard>(input[1], 1).expect("successful chunk has v1");
7100
7101            assert!(output[0] == ((v0 << 2) | (v1 >> 4)));
7102
7103            if written >= 2 {
7104                let v2 = decode_byte::<Standard>(input[2], 2).expect("successful chunk has v2");
7105                assert!(output[1] == ((v1 << 4) | (v2 >> 2)));
7106            }
7107
7108            if written == 3 {
7109                let v2 = decode_byte::<Standard>(input[2], 2).expect("successful chunk has v2");
7110                let v3 = decode_byte::<Standard>(input[3], 3).expect("successful chunk has v3");
7111                assert!(output[2] == ((v2 << 6) | v3));
7112            }
7113        }
7114    }
7115
7116    #[kani::proof]
7117    #[kani::unwind(3)]
7118    fn standard_validate_tail_unpadded_accepts_or_rejects_without_panic() {
7119        let input = kani::any::<[u8; 3]>();
7120        let len = usize::from(kani::any::<u8>() % 4);
7121        let result = validate_tail_unpadded::<Standard>(&input[..len]);
7122
7123        if result.is_ok() {
7124            assert!(len == 0 || len == 2 || len == 3);
7125        }
7126    }
7127
7128    #[kani::proof]
7129    #[kani::unwind(3)]
7130    fn standard_decode_two_byte_tail_returns_written_within_output() {
7131        let input = kani::any::<[u8; 2]>();
7132        let mut output = kani::any::<[u8; 1]>();
7133        let result = decode_tail_unpadded::<Standard>(&input, &mut output);
7134
7135        if let Ok(written) = result {
7136            assert!(written <= output.len());
7137            assert!(written == 1);
7138        }
7139    }
7140
7141    #[kani::proof]
7142    #[kani::unwind(3)]
7143    fn standard_decode_three_byte_tail_returns_written_within_output() {
7144        let input = kani::any::<[u8; 3]>();
7145        let mut output = kani::any::<[u8; 2]>();
7146        let result = decode_tail_unpadded::<Standard>(&input, &mut output);
7147
7148        if let Ok(written) = result {
7149            assert!(written <= output.len());
7150            assert!(written == 2);
7151        }
7152    }
7153
7154    #[kani::proof]
7155    #[kani::unwind(3)]
7156    fn standard_decode_slice_clear_tail_clears_output_on_error() {
7157        let input = kani::any::<[u8; 4]>();
7158        let mut output = kani::any::<[u8; 3]>();
7159        let result = STANDARD.decode_slice_clear_tail(&input, &mut output);
7160
7161        if result.is_err() {
7162            assert!(output.iter().all(|byte| *byte == 0));
7163        }
7164    }
7165
7166    #[kani::proof]
7167    #[kani::unwind(3)]
7168    fn standard_encode_slice_returns_written_within_output() {
7169        let input = kani::any::<[u8; 3]>();
7170        let mut output = kani::any::<[u8; 4]>();
7171        let result = STANDARD.encode_slice(&input, &mut output);
7172
7173        if let Ok(written) = result {
7174            assert!(written <= output.len());
7175        }
7176    }
7177
7178    #[kani::proof]
7179    #[kani::unwind(4)]
7180    fn standard_encode_in_place_returns_prefix_within_buffer() {
7181        let mut buffer = kani::any::<[u8; 8]>();
7182        let input_len = usize::from(kani::any::<u8>() % 9);
7183        let result = STANDARD.encode_in_place(&mut buffer, input_len);
7184
7185        if let Ok(encoded) = result {
7186            assert!(encoded.len() <= 8);
7187        }
7188    }
7189
7190    #[kani::proof]
7191    #[kani::unwind(3)]
7192    fn standard_clear_tail_decode_clears_buffer_on_error() {
7193        let mut buffer = kani::any::<[u8; 4]>();
7194        let result = STANDARD.decode_in_place_clear_tail(&mut buffer);
7195
7196        if result.is_err() {
7197            assert!(buffer.iter().all(|byte| *byte == 0));
7198        }
7199    }
7200
7201    #[kani::proof]
7202    #[kani::unwind(3)]
7203    fn ct_standard_decode_slice_returns_written_within_output() {
7204        let input = kani::any::<[u8; 4]>();
7205        let mut output = kani::any::<[u8; 3]>();
7206        let result = ct::STANDARD.decode_slice(&input, &mut output);
7207
7208        if let Ok(written) = result {
7209            assert!(written <= output.len());
7210        }
7211    }
7212
7213    #[kani::proof]
7214    #[kani::unwind(3)]
7215    fn ct_standard_decode_slice_clear_tail_clears_output_on_error() {
7216        let input = kani::any::<[u8; 4]>();
7217        let mut output = kani::any::<[u8; 3]>();
7218        let result = ct::STANDARD.decode_slice_clear_tail(&input, &mut output);
7219
7220        if result.is_err() {
7221            assert!(output.iter().all(|byte| *byte == 0));
7222        }
7223    }
7224
7225    #[kani::proof]
7226    #[kani::unwind(3)]
7227    fn ct_standard_decode_in_place_clear_tail_clears_buffer_on_error() {
7228        let mut buffer = kani::any::<[u8; 4]>();
7229        let result = ct::STANDARD.decode_in_place_clear_tail(&mut buffer);
7230
7231        if result.is_err() {
7232            assert!(buffer.iter().all(|byte| *byte == 0));
7233        }
7234    }
7235
7236    #[kani::proof]
7237    #[kani::unwind(3)]
7238    fn ct_standard_validate_matches_decode_for_one_quantum() {
7239        let input = kani::any::<[u8; 4]>();
7240        let mut output = kani::any::<[u8; 3]>();
7241
7242        let validate_ok = ct::STANDARD.validate_result(&input).is_ok();
7243        let decode_ok = ct::STANDARD.decode_slice(&input, &mut output).is_ok();
7244
7245        assert!(validate_ok == decode_ok);
7246    }
7247}
7248
7249#[cfg(test)]
7250mod tests {
7251    use super::*;
7252
7253    fn fill_pattern(output: &mut [u8], seed: usize) {
7254        for (index, byte) in output.iter_mut().enumerate() {
7255            let value = (index * 73 + seed * 19) % 256;
7256            *byte = u8::try_from(value).unwrap();
7257        }
7258    }
7259
7260    fn assert_encode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
7261    where
7262        A: Alphabet,
7263    {
7264        let engine = Engine::<A, PAD>::new();
7265        let mut dispatched = [0x55; 256];
7266        let mut scalar = [0xaa; 256];
7267
7268        let dispatched_result = engine.encode_slice(input, &mut dispatched);
7269        let scalar_result = backend::scalar_reference_encode_slice::<A, PAD>(input, &mut scalar);
7270
7271        assert_eq!(dispatched_result, scalar_result);
7272        if let Ok(written) = dispatched_result {
7273            assert_eq!(&dispatched[..written], &scalar[..written]);
7274        }
7275
7276        let required = checked_encoded_len(input.len(), PAD).unwrap();
7277        if required > 0 {
7278            let mut dispatched_short = [0x55; 256];
7279            let mut scalar_short = [0xaa; 256];
7280            let available = required - 1;
7281
7282            assert_eq!(
7283                engine.encode_slice(input, &mut dispatched_short[..available]),
7284                backend::scalar_reference_encode_slice::<A, PAD>(
7285                    input,
7286                    &mut scalar_short[..available],
7287                )
7288            );
7289        }
7290    }
7291
7292    fn assert_decode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
7293    where
7294        A: Alphabet,
7295    {
7296        let engine = Engine::<A, PAD>::new();
7297        let mut dispatched = [0x55; 128];
7298        let mut scalar = [0xaa; 128];
7299
7300        let dispatched_result = engine.decode_slice(input, &mut dispatched);
7301        let scalar_result = backend::scalar_reference_decode_slice::<A, PAD>(input, &mut scalar);
7302
7303        assert_eq!(dispatched_result, scalar_result);
7304        if let Ok(written) = dispatched_result {
7305            assert_eq!(&dispatched[..written], &scalar[..written]);
7306
7307            if written > 0 {
7308                let mut dispatched_short = [0x55; 128];
7309                let mut scalar_short = [0xaa; 128];
7310                let available = written - 1;
7311
7312                assert_eq!(
7313                    engine.decode_slice(input, &mut dispatched_short[..available]),
7314                    backend::scalar_reference_decode_slice::<A, PAD>(
7315                        input,
7316                        &mut scalar_short[..available],
7317                    )
7318                );
7319            }
7320        }
7321    }
7322
7323    fn assert_backend_round_trip_matches_scalar<A, const PAD: bool>(input: &[u8])
7324    where
7325        A: Alphabet,
7326    {
7327        assert_encode_backend_matches_scalar::<A, PAD>(input);
7328
7329        let mut encoded = [0; 256];
7330        let encoded_len =
7331            backend::scalar_reference_encode_slice::<A, PAD>(input, &mut encoded).unwrap();
7332        assert_decode_backend_matches_scalar::<A, PAD>(&encoded[..encoded_len]);
7333    }
7334
7335    fn assert_standard_decode_chunk_matches_input(input: &[u8]) {
7336        let mut encoded = [0u8; 4];
7337        let encoded_len = STANDARD.encode_slice(input, &mut encoded).unwrap();
7338        assert_eq!(encoded_len, 4);
7339
7340        let chunk = [encoded[0], encoded[1], encoded[2], encoded[3]];
7341        let mut decoded = [0u8; 3];
7342        let decoded_len = decode_chunk::<Standard, true>(chunk, &mut decoded).unwrap();
7343
7344        assert_eq!(decoded_len, input.len());
7345        assert_eq!(&decoded[..decoded_len], input);
7346    }
7347
7348    #[test]
7349    fn backend_dispatch_matches_scalar_reference_for_canonical_inputs() {
7350        let mut input = [0; 128];
7351
7352        for input_len in 0..=input.len() {
7353            fill_pattern(&mut input[..input_len], input_len);
7354            let input = &input[..input_len];
7355
7356            assert_backend_round_trip_matches_scalar::<Standard, true>(input);
7357            assert_backend_round_trip_matches_scalar::<Standard, false>(input);
7358            assert_backend_round_trip_matches_scalar::<UrlSafe, true>(input);
7359            assert_backend_round_trip_matches_scalar::<UrlSafe, false>(input);
7360        }
7361    }
7362
7363    #[test]
7364    fn backend_dispatch_matches_scalar_reference_for_malformed_inputs() {
7365        for input in [
7366            &b"Z"[..],
7367            b"====",
7368            b"AA=A",
7369            b"Zh==",
7370            b"Zm9=",
7371            b"Zm9v$g==",
7372            b"Zm9vZh==",
7373        ] {
7374            assert_decode_backend_matches_scalar::<Standard, true>(input);
7375        }
7376
7377        for input in [&b"Z"[..], b"AA=A", b"Zh", b"Zm9", b"Zm9vYg$"] {
7378            assert_decode_backend_matches_scalar::<Standard, false>(input);
7379        }
7380
7381        assert_decode_backend_matches_scalar::<UrlSafe, true>(b"AA+A");
7382        assert_decode_backend_matches_scalar::<UrlSafe, false>(b"AA/A");
7383        assert_decode_backend_matches_scalar::<Standard, true>(b"AA-A");
7384        assert_decode_backend_matches_scalar::<Standard, false>(b"AA_A");
7385    }
7386
7387    #[test]
7388    fn decode_chunk_bit_packing_matches_exhaustive_small_inputs() {
7389        for byte in u8::MIN..=u8::MAX {
7390            assert_standard_decode_chunk_matches_input(&[byte]);
7391        }
7392
7393        for first in u8::MIN..=u8::MAX {
7394            for second in u8::MIN..=u8::MAX {
7395                assert_standard_decode_chunk_matches_input(&[first, second]);
7396            }
7397        }
7398    }
7399
7400    #[test]
7401    fn decode_chunk_bit_packing_matches_representative_full_quanta() {
7402        const SAMPLES: [u8; 16] = [
7403            0, 1, 2, 15, 16, 31, 32, 63, 64, 95, 127, 128, 191, 192, 254, 255,
7404        ];
7405
7406        for first in SAMPLES {
7407            for second in SAMPLES {
7408                for third in SAMPLES {
7409                    assert_standard_decode_chunk_matches_input(&[first, second, third]);
7410                }
7411            }
7412        }
7413    }
7414
7415    #[cfg(feature = "simd")]
7416    #[test]
7417    fn simd_dispatch_scaffold_keeps_scalar_active() {
7418        assert_eq!(simd::active_backend(), simd::ActiveBackend::Scalar);
7419        let _candidate = simd::detected_candidate();
7420    }
7421
7422    #[test]
7423    fn encodes_standard_vectors() {
7424        let vectors = [
7425            (&b""[..], &b""[..]),
7426            (&b"f"[..], &b"Zg=="[..]),
7427            (&b"fo"[..], &b"Zm8="[..]),
7428            (&b"foo"[..], &b"Zm9v"[..]),
7429            (&b"foob"[..], &b"Zm9vYg=="[..]),
7430            (&b"fooba"[..], &b"Zm9vYmE="[..]),
7431            (&b"foobar"[..], &b"Zm9vYmFy"[..]),
7432        ];
7433        for (input, expected) in vectors {
7434            let mut output = [0u8; 16];
7435            let written = STANDARD.encode_slice(input, &mut output).unwrap();
7436            assert_eq!(&output[..written], expected);
7437        }
7438    }
7439
7440    #[test]
7441    fn decodes_standard_vectors() {
7442        let vectors = [
7443            (&b""[..], &b""[..]),
7444            (&b"Zg=="[..], &b"f"[..]),
7445            (&b"Zm8="[..], &b"fo"[..]),
7446            (&b"Zm9v"[..], &b"foo"[..]),
7447            (&b"Zm9vYg=="[..], &b"foob"[..]),
7448            (&b"Zm9vYmE="[..], &b"fooba"[..]),
7449            (&b"Zm9vYmFy"[..], &b"foobar"[..]),
7450        ];
7451        for (input, expected) in vectors {
7452            let mut output = [0u8; 16];
7453            let written = STANDARD.decode_slice(input, &mut output).unwrap();
7454            assert_eq!(&output[..written], expected);
7455        }
7456    }
7457
7458    #[test]
7459    fn supports_unpadded_url_safe() {
7460        let mut encoded = [0u8; 16];
7461        let written = URL_SAFE_NO_PAD
7462            .encode_slice(b"\xfb\xff", &mut encoded)
7463            .unwrap();
7464        assert_eq!(&encoded[..written], b"-_8");
7465
7466        let mut decoded = [0u8; 2];
7467        let written = URL_SAFE_NO_PAD
7468            .decode_slice(&encoded[..written], &mut decoded)
7469            .unwrap();
7470        assert_eq!(&decoded[..written], b"\xfb\xff");
7471    }
7472
7473    #[test]
7474    fn decodes_in_place() {
7475        let mut buffer = *b"Zm9vYmFy";
7476        let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
7477        assert_eq!(decoded, b"foobar");
7478    }
7479
7480    #[test]
7481    fn rejects_non_canonical_padding_bits() {
7482        let mut output = [0u8; 4];
7483        assert_eq!(
7484            STANDARD.decode_slice(b"Zh==", &mut output),
7485            Err(DecodeError::InvalidPadding { index: 1 })
7486        );
7487        assert_eq!(
7488            STANDARD.decode_slice(b"Zm9=", &mut output),
7489            Err(DecodeError::InvalidPadding { index: 2 })
7490        );
7491    }
7492}