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