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