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, Standard, UrlSafe, ct_decode_in_place, ct_decode_slice,
1347        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        /// Validates `input` without writing decoded bytes.
1382        ///
1383        /// This uses the same constant-time-oriented symbol mapping and opaque
1384        /// malformed-input error behavior as [`Self::decode_slice`]. Input
1385        /// length, padding length, and final success or failure remain public.
1386        ///
1387        /// # Examples
1388        ///
1389        /// ```
1390        /// use base64_ng::ct;
1391        ///
1392        /// ct::STANDARD.validate_result(b"aGVsbG8=").unwrap();
1393        /// assert!(ct::STANDARD.validate_result(b"aGVsbG8").is_err());
1394        /// ```
1395        pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
1396            ct_validate_decode::<A, PAD>(input)
1397        }
1398
1399        /// Returns whether `input` is valid for this constant-time-oriented
1400        /// decoder.
1401        ///
1402        /// This is a convenience wrapper around [`Self::validate_result`].
1403        ///
1404        /// # Examples
1405        ///
1406        /// ```
1407        /// use base64_ng::ct;
1408        ///
1409        /// assert!(ct::URL_SAFE_NO_PAD.validate(b"-_8"));
1410        /// assert!(!ct::URL_SAFE_NO_PAD.validate(b"+/8"));
1411        /// ```
1412        #[must_use]
1413        pub fn validate(&self, input: &[u8]) -> bool {
1414            self.validate_result(input).is_ok()
1415        }
1416
1417        /// Decodes `input` into `output`, returning the number of bytes
1418        /// written.
1419        ///
1420        /// This path uses branch-minimized arithmetic for Base64 symbol
1421        /// mapping and avoids secret-indexed lookup tables. Input length,
1422        /// padding length, output length, and final success or failure remain
1423        /// public. Malformed content errors are intentionally opaque and
1424        /// non-localized; use the normal strict decoder when exact diagnostics
1425        /// are required.
1426        ///
1427        /// # Examples
1428        ///
1429        /// ```
1430        /// use base64_ng::ct;
1431        ///
1432        /// let mut output = [0u8; 5];
1433        /// let written = ct::STANDARD
1434        ///     .decode_slice(b"aGVsbG8=", &mut output)
1435        ///     .unwrap();
1436        ///
1437        /// assert_eq!(&output[..written], b"hello");
1438        /// ```
1439        pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
1440            ct_decode_slice::<A, PAD>(input, output)
1441        }
1442
1443        /// Decodes `input` into `output` and clears all bytes after the
1444        /// decoded prefix.
1445        ///
1446        /// If decoding fails, the entire output buffer is cleared before the
1447        /// error is returned. Use this variant for sensitive payloads where
1448        /// partially decoded bytes from rejected input should not remain in the
1449        /// caller-owned output buffer.
1450        ///
1451        /// # Examples
1452        ///
1453        /// ```
1454        /// use base64_ng::ct;
1455        ///
1456        /// let mut output = [0xff; 8];
1457        /// let written = ct::STANDARD
1458        ///     .decode_slice_clear_tail(b"aGk=", &mut output)
1459        ///     .unwrap();
1460        ///
1461        /// assert_eq!(&output[..written], b"hi");
1462        /// assert!(output[written..].iter().all(|byte| *byte == 0));
1463        /// ```
1464        pub fn decode_slice_clear_tail(
1465            &self,
1466            input: &[u8],
1467            output: &mut [u8],
1468        ) -> Result<usize, DecodeError> {
1469            let written = match self.decode_slice(input, output) {
1470                Ok(written) => written,
1471                Err(err) => {
1472                    crate::wipe_bytes(output);
1473                    return Err(err);
1474                }
1475            };
1476            crate::wipe_tail(output, written);
1477            Ok(written)
1478        }
1479
1480        /// Decodes `buffer` in place and returns the decoded prefix.
1481        ///
1482        /// This uses the constant-time-oriented scalar decoder while reading
1483        /// each Base64 quantum into local values before writing decoded bytes
1484        /// back to the front of the same buffer.
1485        ///
1486        /// # Examples
1487        ///
1488        /// ```
1489        /// use base64_ng::ct;
1490        ///
1491        /// let mut buffer = *b"aGk=";
1492        /// let decoded = ct::STANDARD.decode_in_place(&mut buffer).unwrap();
1493        ///
1494        /// assert_eq!(decoded, b"hi");
1495        /// ```
1496        pub fn decode_in_place<'a>(
1497            &self,
1498            buffer: &'a mut [u8],
1499        ) -> Result<&'a mut [u8], DecodeError> {
1500            let len = ct_decode_in_place::<A, PAD>(buffer)?;
1501            Ok(&mut buffer[..len])
1502        }
1503
1504        /// Decodes `buffer` in place and clears all bytes after the decoded
1505        /// prefix.
1506        ///
1507        /// If decoding fails, the entire buffer is cleared before the error is
1508        /// returned.
1509        ///
1510        /// # Examples
1511        ///
1512        /// ```
1513        /// use base64_ng::ct;
1514        ///
1515        /// let mut buffer = *b"aGk=";
1516        /// let decoded = ct::STANDARD.decode_in_place_clear_tail(&mut buffer).unwrap();
1517        ///
1518        /// assert_eq!(decoded, b"hi");
1519        /// ```
1520        pub fn decode_in_place_clear_tail<'a>(
1521            &self,
1522            buffer: &'a mut [u8],
1523        ) -> Result<&'a mut [u8], DecodeError> {
1524            let len = match ct_decode_in_place::<A, PAD>(buffer) {
1525                Ok(len) => len,
1526                Err(err) => {
1527                    crate::wipe_bytes(buffer);
1528                    return Err(err);
1529                }
1530            };
1531            crate::wipe_tail(buffer, len);
1532            Ok(&mut buffer[..len])
1533        }
1534    }
1535}
1536
1537/// Standard Base64 engine with padding.
1538pub const STANDARD: Engine<Standard, true> = Engine::new();
1539
1540/// Standard Base64 engine without padding.
1541pub const STANDARD_NO_PAD: Engine<Standard, false> = Engine::new();
1542
1543/// URL-safe Base64 engine with padding.
1544pub const URL_SAFE: Engine<UrlSafe, true> = Engine::new();
1545
1546/// URL-safe Base64 engine without padding.
1547pub const URL_SAFE_NO_PAD: Engine<UrlSafe, false> = Engine::new();
1548
1549/// bcrypt-style Base64 engine without padding.
1550///
1551/// This uses the bcrypt alphabet with the crate's normal Base64 bit packing.
1552/// It does not parse complete bcrypt password-hash strings.
1553pub const BCRYPT_NO_PAD: Engine<Bcrypt, false> = Engine::new();
1554
1555/// Unix `crypt(3)`-style Base64 engine without padding.
1556///
1557/// This uses the `crypt(3)` alphabet with the crate's normal Base64 bit
1558/// packing. It does not parse complete password-hash strings.
1559pub const CRYPT_NO_PAD: Engine<Crypt, false> = Engine::new();
1560
1561/// Line ending used by wrapped Base64 output.
1562#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1563pub enum LineEnding {
1564    /// Line feed (`\n`).
1565    Lf,
1566    /// Carriage return followed by line feed (`\r\n`).
1567    CrLf,
1568}
1569
1570impl LineEnding {
1571    /// Returns the byte representation of this line ending.
1572    #[must_use]
1573    pub const fn as_bytes(self) -> &'static [u8] {
1574        match self {
1575            Self::Lf => b"\n",
1576            Self::CrLf => b"\r\n",
1577        }
1578    }
1579
1580    /// Returns the byte length of this line ending.
1581    #[must_use]
1582    pub const fn byte_len(self) -> usize {
1583        match self {
1584            Self::Lf => 1,
1585            Self::CrLf => 2,
1586        }
1587    }
1588}
1589
1590/// Base64 line wrapping policy.
1591///
1592/// `line_len` is measured in encoded Base64 bytes, not source input bytes.
1593/// Encoders insert line endings between lines and do not append a trailing line
1594/// ending after the final line.
1595#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1596pub struct LineWrap {
1597    /// Maximum encoded bytes per line.
1598    pub line_len: usize,
1599    /// Line ending inserted between wrapped lines.
1600    pub line_ending: LineEnding,
1601}
1602
1603impl LineWrap {
1604    /// MIME-style wrapping: 76 columns with CRLF endings.
1605    pub const MIME: Self = Self::new(76, LineEnding::CrLf);
1606    /// PEM-style wrapping: 64 columns with LF endings.
1607    pub const PEM: Self = Self::new(64, LineEnding::Lf);
1608    /// PEM-style wrapping: 64 columns with CRLF endings.
1609    pub const PEM_CRLF: Self = Self::new(64, LineEnding::CrLf);
1610
1611    /// Creates a wrapping policy.
1612    #[must_use]
1613    pub const fn new(line_len: usize, line_ending: LineEnding) -> Self {
1614        Self {
1615            line_len,
1616            line_ending,
1617        }
1618    }
1619}
1620
1621#[allow(unsafe_code)]
1622fn wipe_bytes(bytes: &mut [u8]) {
1623    for byte in bytes {
1624        // SAFETY: `byte` comes from a unique mutable slice iterator, so the
1625        // pointer is non-null, aligned, valid for one `u8` write, and does not
1626        // alias another live mutable reference during this iteration.
1627        unsafe {
1628            core::ptr::write_volatile(byte, 0);
1629        }
1630    }
1631    core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
1632}
1633
1634fn wipe_tail(bytes: &mut [u8], start: usize) {
1635    wipe_bytes(&mut bytes[start..]);
1636}
1637
1638/// Stack-backed encoded Base64 output.
1639///
1640/// This type is intended for short values where heap allocation would be
1641/// unnecessary but manually sizing and passing a separate output slice is
1642/// noisy. Its visible bytes are produced by crate encoders, so [`Self::as_str`]
1643/// can return `&str` without exposing a fallible UTF-8 conversion to callers.
1644///
1645/// The backing array is cleared when the value is dropped. This is best-effort
1646/// data-retention reduction and is not a formal zeroization guarantee.
1647pub struct EncodedBuffer<const CAP: usize> {
1648    bytes: [u8; CAP],
1649    len: usize,
1650}
1651
1652impl<const CAP: usize> EncodedBuffer<CAP> {
1653    /// Creates an empty encoded buffer.
1654    #[must_use]
1655    pub const fn new() -> Self {
1656        Self {
1657            bytes: [0u8; CAP],
1658            len: 0,
1659        }
1660    }
1661
1662    /// Returns the number of visible encoded bytes.
1663    #[must_use]
1664    pub const fn len(&self) -> usize {
1665        self.len
1666    }
1667
1668    /// Returns whether the buffer has no visible encoded bytes.
1669    #[must_use]
1670    pub const fn is_empty(&self) -> bool {
1671        self.len == 0
1672    }
1673
1674    /// Returns the stack capacity in bytes.
1675    #[must_use]
1676    pub const fn capacity(&self) -> usize {
1677        CAP
1678    }
1679
1680    /// Returns the visible encoded bytes.
1681    #[must_use]
1682    pub fn as_bytes(&self) -> &[u8] {
1683        &self.bytes[..self.len]
1684    }
1685
1686    /// Returns the visible encoded bytes as UTF-8.
1687    ///
1688    /// # Panics
1689    ///
1690    /// Panics only if the crate's internal invariant is broken and the buffer
1691    /// contains non-UTF-8 bytes.
1692    #[must_use]
1693    pub fn as_str(&self) -> &str {
1694        match core::str::from_utf8(self.as_bytes()) {
1695            Ok(output) => output,
1696            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
1697        }
1698    }
1699
1700    /// Clears the visible bytes and the full backing array.
1701    pub fn clear(&mut self) {
1702        wipe_bytes(&mut self.bytes);
1703        self.len = 0;
1704    }
1705
1706    /// Clears bytes after the visible prefix.
1707    pub fn clear_tail(&mut self) {
1708        wipe_tail(&mut self.bytes, self.len);
1709    }
1710}
1711
1712impl<const CAP: usize> AsRef<[u8]> for EncodedBuffer<CAP> {
1713    fn as_ref(&self) -> &[u8] {
1714        self.as_bytes()
1715    }
1716}
1717
1718impl<const CAP: usize> Clone for EncodedBuffer<CAP> {
1719    fn clone(&self) -> Self {
1720        let mut output = Self::new();
1721        output.bytes[..self.len].copy_from_slice(self.as_bytes());
1722        output.len = self.len;
1723        output
1724    }
1725}
1726
1727impl<const CAP: usize> core::fmt::Debug for EncodedBuffer<CAP> {
1728    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1729        formatter
1730            .debug_struct("EncodedBuffer")
1731            .field("bytes", &"<redacted>")
1732            .field("len", &self.len)
1733            .field("capacity", &CAP)
1734            .finish()
1735    }
1736}
1737
1738impl<const CAP: usize> Default for EncodedBuffer<CAP> {
1739    fn default() -> Self {
1740        Self::new()
1741    }
1742}
1743
1744impl<const CAP: usize> Drop for EncodedBuffer<CAP> {
1745    fn drop(&mut self) {
1746        self.clear();
1747    }
1748}
1749
1750impl<const CAP: usize> Eq for EncodedBuffer<CAP> {}
1751
1752impl<const CAP: usize> PartialEq for EncodedBuffer<CAP> {
1753    fn eq(&self, other: &Self) -> bool {
1754        self.as_bytes() == other.as_bytes()
1755    }
1756}
1757
1758/// Owned sensitive bytes with redacted formatting and drop-time cleanup.
1759///
1760/// `SecretBuffer` is available with the `alloc` feature. It is intended for
1761/// decoded keys, tokens, and other values that should not be accidentally
1762/// logged. The buffer exposes contents only through explicit reveal methods.
1763///
1764/// On drop, initialized bytes are cleared with the crate's internal best-effort
1765/// wipe helper. This is data-retention reduction, not a formal zeroization
1766/// guarantee, and it cannot make claims about allocator spare capacity or
1767/// historical copies outside the wrapper.
1768#[cfg(feature = "alloc")]
1769pub struct SecretBuffer {
1770    bytes: alloc::vec::Vec<u8>,
1771}
1772
1773#[cfg(feature = "alloc")]
1774impl SecretBuffer {
1775    /// Wraps an existing vector as sensitive material.
1776    #[must_use]
1777    pub fn from_vec(bytes: alloc::vec::Vec<u8>) -> Self {
1778        Self { bytes }
1779    }
1780
1781    /// Copies a slice into an owned sensitive buffer.
1782    #[must_use]
1783    pub fn from_slice(bytes: &[u8]) -> Self {
1784        Self {
1785            bytes: bytes.to_vec(),
1786        }
1787    }
1788
1789    /// Returns the number of initialized secret bytes.
1790    #[must_use]
1791    pub fn len(&self) -> usize {
1792        self.bytes.len()
1793    }
1794
1795    /// Returns whether the buffer contains no initialized secret bytes.
1796    #[must_use]
1797    pub fn is_empty(&self) -> bool {
1798        self.bytes.is_empty()
1799    }
1800
1801    /// Reveals the secret bytes.
1802    ///
1803    /// This method is intentionally named to make secret access explicit at the
1804    /// call site.
1805    #[must_use]
1806    pub fn expose_secret(&self) -> &[u8] {
1807        &self.bytes
1808    }
1809
1810    /// Reveals the secret bytes mutably.
1811    ///
1812    /// This method is intentionally named to make secret access explicit at the
1813    /// call site.
1814    #[must_use]
1815    pub fn expose_secret_mut(&mut self) -> &mut [u8] {
1816        &mut self.bytes
1817    }
1818
1819    /// Clears the initialized bytes and makes the buffer empty.
1820    pub fn clear(&mut self) {
1821        wipe_bytes(&mut self.bytes);
1822        self.bytes.clear();
1823    }
1824}
1825
1826#[cfg(feature = "alloc")]
1827impl Clone for SecretBuffer {
1828    fn clone(&self) -> Self {
1829        Self::from_slice(self.expose_secret())
1830    }
1831}
1832
1833#[cfg(feature = "alloc")]
1834impl core::fmt::Debug for SecretBuffer {
1835    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1836        formatter
1837            .debug_struct("SecretBuffer")
1838            .field("bytes", &"<redacted>")
1839            .field("len", &self.len())
1840            .finish()
1841    }
1842}
1843
1844#[cfg(feature = "alloc")]
1845impl core::fmt::Display for SecretBuffer {
1846    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1847        formatter.write_str("<redacted>")
1848    }
1849}
1850
1851#[cfg(feature = "alloc")]
1852impl Drop for SecretBuffer {
1853    fn drop(&mut self) {
1854        wipe_bytes(&mut self.bytes);
1855    }
1856}
1857
1858#[cfg(feature = "alloc")]
1859impl Eq for SecretBuffer {}
1860
1861#[cfg(feature = "alloc")]
1862impl PartialEq for SecretBuffer {
1863    fn eq(&self, other: &Self) -> bool {
1864        self.expose_secret() == other.expose_secret()
1865    }
1866}
1867
1868/// A named Base64 profile with an engine and optional strict line wrapping.
1869///
1870/// Profiles are convenience values for protocol-shaped Base64. They keep the
1871/// same strict alphabet, padding, canonical-bit, and output-buffer rules as
1872/// [`Engine`], while carrying the wrapping policy for MIME/PEM-like formats.
1873#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1874pub struct Profile<A, const PAD: bool> {
1875    engine: Engine<A, PAD>,
1876    wrap: Option<LineWrap>,
1877}
1878
1879impl<A, const PAD: bool> Profile<A, PAD>
1880where
1881    A: Alphabet,
1882{
1883    /// Creates a profile from an engine and optional strict line wrapping.
1884    #[must_use]
1885    pub const fn new(engine: Engine<A, PAD>, wrap: Option<LineWrap>) -> Self {
1886        Self { engine, wrap }
1887    }
1888
1889    /// Returns the underlying engine.
1890    #[must_use]
1891    pub const fn engine(&self) -> Engine<A, PAD> {
1892        self.engine
1893    }
1894
1895    /// Returns the strict wrapping policy carried by this profile, if any.
1896    #[must_use]
1897    pub const fn line_wrap(&self) -> Option<LineWrap> {
1898        self.wrap
1899    }
1900
1901    /// Returns the encoded length for this profile.
1902    pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
1903        match self.wrap {
1904            Some(wrap) => wrapped_encoded_len(input_len, PAD, wrap),
1905            None => encoded_len(input_len, PAD),
1906        }
1907    }
1908
1909    /// Returns the exact decoded length for this profile.
1910    pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
1911        match self.wrap {
1912            Some(wrap) => self.engine.decoded_len_wrapped(input, wrap),
1913            None => self.engine.decoded_len(input),
1914        }
1915    }
1916
1917    /// Validates input according to this profile without writing decoded bytes.
1918    pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
1919        match self.wrap {
1920            Some(wrap) => self.engine.validate_wrapped_result(input, wrap),
1921            None => self.engine.validate_result(input),
1922        }
1923    }
1924
1925    /// Returns whether `input` is valid for this profile.
1926    #[must_use]
1927    pub fn validate(&self, input: &[u8]) -> bool {
1928        self.validate_result(input).is_ok()
1929    }
1930
1931    /// Encodes `input` into `output` according to this profile.
1932    pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
1933        match self.wrap {
1934            Some(wrap) => self.engine.encode_slice_wrapped(input, output, wrap),
1935            None => self.engine.encode_slice(input, output),
1936        }
1937    }
1938
1939    /// Encodes `input` into `output` and clears all bytes after the encoded
1940    /// prefix.
1941    pub fn encode_slice_clear_tail(
1942        &self,
1943        input: &[u8],
1944        output: &mut [u8],
1945    ) -> Result<usize, EncodeError> {
1946        match self.wrap {
1947            Some(wrap) => self
1948                .engine
1949                .encode_slice_wrapped_clear_tail(input, output, wrap),
1950            None => self.engine.encode_slice_clear_tail(input, output),
1951        }
1952    }
1953
1954    /// Encodes `input` into a stack-backed buffer.
1955    ///
1956    /// This is useful for short values where heap allocation is unnecessary.
1957    /// If encoding fails, the internal backing array is cleared before the
1958    /// error is returned.
1959    pub fn encode_buffer<const CAP: usize>(
1960        &self,
1961        input: &[u8],
1962    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
1963        let mut output = EncodedBuffer::new();
1964        let written = match self.encode_slice_clear_tail(input, &mut output.bytes) {
1965            Ok(written) => written,
1966            Err(err) => {
1967                output.clear();
1968                return Err(err);
1969            }
1970        };
1971        output.len = written;
1972        Ok(output)
1973    }
1974
1975    /// Decodes `input` into `output` according to this profile.
1976    pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
1977        match self.wrap {
1978            Some(wrap) => self.engine.decode_slice_wrapped(input, output, wrap),
1979            None => self.engine.decode_slice(input, output),
1980        }
1981    }
1982
1983    /// Decodes `input` into `output` and clears all bytes after the decoded
1984    /// prefix.
1985    pub fn decode_slice_clear_tail(
1986        &self,
1987        input: &[u8],
1988        output: &mut [u8],
1989    ) -> Result<usize, DecodeError> {
1990        match self.wrap {
1991            Some(wrap) => self
1992                .engine
1993                .decode_slice_wrapped_clear_tail(input, output, wrap),
1994            None => self.engine.decode_slice_clear_tail(input, output),
1995        }
1996    }
1997
1998    /// Encodes `input` into a newly allocated byte vector.
1999    #[cfg(feature = "alloc")]
2000    pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
2001        match self.wrap {
2002            Some(wrap) => self.engine.encode_wrapped_vec(input, wrap),
2003            None => self.engine.encode_vec(input),
2004        }
2005    }
2006
2007    /// Encodes `input` into a redacted owned secret buffer.
2008    #[cfg(feature = "alloc")]
2009    pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
2010        self.encode_vec(input).map(SecretBuffer::from_vec)
2011    }
2012
2013    /// Encodes `input` into a newly allocated UTF-8 string.
2014    #[cfg(feature = "alloc")]
2015    pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
2016        match self.wrap {
2017            Some(wrap) => self.engine.encode_wrapped_string(input, wrap),
2018            None => self.engine.encode_string(input),
2019        }
2020    }
2021
2022    /// Decodes `input` into a newly allocated byte vector.
2023    #[cfg(feature = "alloc")]
2024    pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
2025        match self.wrap {
2026            Some(wrap) => self.engine.decode_wrapped_vec(input, wrap),
2027            None => self.engine.decode_vec(input),
2028        }
2029    }
2030
2031    /// Decodes `input` into a redacted owned secret buffer.
2032    #[cfg(feature = "alloc")]
2033    pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
2034        self.decode_vec(input).map(SecretBuffer::from_vec)
2035    }
2036}
2037
2038/// MIME Base64 profile: standard alphabet, padding, 76-column CRLF wrapping.
2039pub const MIME: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::MIME));
2040
2041/// PEM Base64 profile: standard alphabet, padding, 64-column LF wrapping.
2042pub const PEM: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::PEM));
2043
2044/// PEM Base64 profile with CRLF line endings.
2045pub const PEM_CRLF: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::PEM_CRLF));
2046
2047/// bcrypt-style no-padding Base64 profile.
2048///
2049/// This profile carries the bcrypt alphabet and no padding. It does not parse
2050/// complete bcrypt password-hash strings.
2051pub const BCRYPT: Profile<Bcrypt, false> = Profile::new(BCRYPT_NO_PAD, None);
2052
2053/// Unix `crypt(3)`-style no-padding Base64 profile.
2054///
2055/// This profile carries the `crypt(3)` alphabet and no padding. It does not
2056/// parse complete password-hash strings.
2057pub const CRYPT: Profile<Crypt, false> = Profile::new(CRYPT_NO_PAD, None);
2058
2059/// Returns the encoded length for an input length and padding policy.
2060///
2061/// This function returns [`EncodeError::LengthOverflow`] instead of panicking.
2062/// Use [`checked_encoded_len`] when an `Option<usize>` is more convenient.
2063///
2064/// # Examples
2065///
2066/// ```
2067/// use base64_ng::encoded_len;
2068///
2069/// assert_eq!(encoded_len(5, true).unwrap(), 8);
2070/// assert_eq!(encoded_len(5, false).unwrap(), 7);
2071/// assert!(encoded_len(usize::MAX, true).is_err());
2072/// ```
2073pub const fn encoded_len(input_len: usize, padded: bool) -> Result<usize, EncodeError> {
2074    match checked_encoded_len(input_len, padded) {
2075        Some(len) => Ok(len),
2076        None => Err(EncodeError::LengthOverflow),
2077    }
2078}
2079
2080/// Returns the encoded length after applying a line wrapping policy.
2081///
2082/// The returned length includes inserted line endings but does not include a
2083/// trailing line ending after the final encoded line.
2084///
2085/// # Examples
2086///
2087/// ```
2088/// use base64_ng::{LineEnding, LineWrap, wrapped_encoded_len};
2089///
2090/// let wrap = LineWrap::new(4, LineEnding::Lf);
2091/// assert_eq!(wrapped_encoded_len(5, true, wrap).unwrap(), 9);
2092/// ```
2093pub const fn wrapped_encoded_len(
2094    input_len: usize,
2095    padded: bool,
2096    wrap: LineWrap,
2097) -> Result<usize, EncodeError> {
2098    if wrap.line_len == 0 {
2099        return Err(EncodeError::InvalidLineWrap { line_len: 0 });
2100    }
2101
2102    let Some(encoded) = checked_encoded_len(input_len, padded) else {
2103        return Err(EncodeError::LengthOverflow);
2104    };
2105    if encoded == 0 {
2106        return Ok(0);
2107    }
2108
2109    let breaks = (encoded - 1) / wrap.line_len;
2110    let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
2111        return Err(EncodeError::LengthOverflow);
2112    };
2113    match encoded.checked_add(line_ending_bytes) {
2114        Some(len) => Ok(len),
2115        None => Err(EncodeError::LengthOverflow),
2116    }
2117}
2118
2119/// Returns the encoded length, or `None` if it would overflow `usize`.
2120///
2121/// # Examples
2122///
2123/// ```
2124/// use base64_ng::checked_encoded_len;
2125///
2126/// assert_eq!(checked_encoded_len(5, true), Some(8));
2127/// assert_eq!(checked_encoded_len(usize::MAX, true), None);
2128/// ```
2129#[must_use]
2130pub const fn checked_encoded_len(input_len: usize, padded: bool) -> Option<usize> {
2131    let groups = input_len / 3;
2132    if groups > usize::MAX / 4 {
2133        return None;
2134    }
2135    let full = groups * 4;
2136    let rem = input_len % 3;
2137    if rem == 0 {
2138        Some(full)
2139    } else if padded {
2140        full.checked_add(4)
2141    } else {
2142        full.checked_add(rem + 1)
2143    }
2144}
2145
2146/// Returns the maximum decoded length for an encoded input length.
2147///
2148/// # Examples
2149///
2150/// ```
2151/// use base64_ng::decoded_capacity;
2152///
2153/// assert_eq!(decoded_capacity(8), 6);
2154/// assert_eq!(decoded_capacity(7), 5);
2155/// ```
2156#[must_use]
2157pub const fn decoded_capacity(encoded_len: usize) -> usize {
2158    let rem = encoded_len % 4;
2159    encoded_len / 4 * 3
2160        + if rem == 2 {
2161            1
2162        } else if rem == 3 {
2163            2
2164        } else {
2165            0
2166        }
2167}
2168
2169/// Returns the exact decoded length implied by input length and padding.
2170///
2171/// This validates padding placement and impossible lengths, but it does not
2172/// validate alphabet membership or non-canonical trailing bits.
2173///
2174/// # Examples
2175///
2176/// ```
2177/// use base64_ng::decoded_len;
2178///
2179/// assert_eq!(decoded_len(b"aGVsbG8=", true).unwrap(), 5);
2180/// assert_eq!(decoded_len(b"aGVsbG8", false).unwrap(), 5);
2181/// ```
2182pub fn decoded_len(input: &[u8], padded: bool) -> Result<usize, DecodeError> {
2183    if padded {
2184        decoded_len_padded(input)
2185    } else {
2186        decoded_len_unpadded(input)
2187    }
2188}
2189
2190/// Validates a 64-byte Base64 alphabet table.
2191///
2192/// A valid alphabet must contain exactly 64 unique visible ASCII bytes and must
2193/// not contain the padding byte `=`.
2194///
2195/// # Examples
2196///
2197/// ```
2198/// use base64_ng::{Alphabet, Standard, validate_alphabet};
2199///
2200/// validate_alphabet(&Standard::ENCODE).unwrap();
2201/// ```
2202pub const fn validate_alphabet(encode: &[u8; 64]) -> Result<(), AlphabetError> {
2203    let mut index = 0;
2204    while index < encode.len() {
2205        let byte = encode[index];
2206        if !is_visible_ascii(byte) {
2207            return Err(AlphabetError::InvalidByte { index, byte });
2208        }
2209        if byte == b'=' {
2210            return Err(AlphabetError::PaddingByte { index });
2211        }
2212
2213        let mut duplicate = index + 1;
2214        while duplicate < encode.len() {
2215            if encode[duplicate] == byte {
2216                return Err(AlphabetError::DuplicateByte {
2217                    first: index,
2218                    second: duplicate,
2219                    byte,
2220                });
2221            }
2222            duplicate += 1;
2223        }
2224
2225        index += 1;
2226    }
2227
2228    Ok(())
2229}
2230
2231/// Decodes one byte by scanning a caller-provided alphabet table.
2232///
2233/// This helper is intended for custom [`Alphabet`] implementations. Validate
2234/// the table with [`validate_alphabet`] before trusting the alphabet in a
2235/// protocol or public API.
2236///
2237/// # Examples
2238///
2239/// ```
2240/// use base64_ng::{Alphabet, decode_alphabet_byte};
2241///
2242/// struct DotSlash;
2243///
2244/// impl Alphabet for DotSlash {
2245///     const ENCODE: [u8; 64] =
2246///         *b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
2247///
2248///     fn decode(byte: u8) -> Option<u8> {
2249///         decode_alphabet_byte(byte, &Self::ENCODE)
2250///     }
2251/// }
2252///
2253/// assert_eq!(DotSlash::decode(b'.'), Some(0));
2254/// assert_eq!(DotSlash::decode(b'9'), Some(63));
2255/// ```
2256#[must_use]
2257pub const fn decode_alphabet_byte(byte: u8, encode: &[u8; 64]) -> Option<u8> {
2258    let mut index = 0;
2259    let mut value = 0;
2260    while index < encode.len() {
2261        if encode[index] == byte {
2262            return Some(value);
2263        }
2264        index += 1;
2265        value += 1;
2266    }
2267    None
2268}
2269
2270/// A Base64 alphabet.
2271pub trait Alphabet {
2272    /// Encoding table indexed by 6-bit values.
2273    const ENCODE: [u8; 64];
2274
2275    /// Encode one 6-bit value into an alphabet byte.
2276    ///
2277    /// The default implementation scans the alphabet table instead of using a
2278    /// secret-indexed table lookup. Built-in alphabets override this with the
2279    /// branch-minimized ASCII arithmetic mapper. Custom alphabets that keep the
2280    /// default method prioritize timing posture over throughput: every emitted
2281    /// Base64 byte performs a fixed 64-entry scan. For massive payloads with
2282    /// user-defined alphabets, profile this cost and consider an audited custom
2283    /// override only if the alphabet has a structure that can be mapped without
2284    /// secret-indexed table access.
2285    #[must_use]
2286    fn encode(value: u8) -> u8 {
2287        encode_alphabet_value(value, &Self::ENCODE)
2288    }
2289
2290    /// Decode one byte into a 6-bit value.
2291    fn decode(byte: u8) -> Option<u8>;
2292}
2293
2294const fn is_visible_ascii(byte: u8) -> bool {
2295    byte >= 0x21 && byte <= 0x7e
2296}
2297
2298/// The RFC 4648 standard Base64 alphabet.
2299#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
2300pub struct Standard;
2301
2302impl Alphabet for Standard {
2303    const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2304
2305    #[inline]
2306    fn encode(value: u8) -> u8 {
2307        encode_ascii_base64(value, Self::ENCODE[62], Self::ENCODE[63])
2308    }
2309
2310    #[inline]
2311    fn decode(byte: u8) -> Option<u8> {
2312        decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
2313    }
2314}
2315
2316/// The RFC 4648 URL-safe Base64 alphabet.
2317#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
2318pub struct UrlSafe;
2319
2320impl Alphabet for UrlSafe {
2321    const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
2322
2323    #[inline]
2324    fn encode(value: u8) -> u8 {
2325        encode_ascii_base64(value, Self::ENCODE[62], Self::ENCODE[63])
2326    }
2327
2328    #[inline]
2329    fn decode(byte: u8) -> Option<u8> {
2330        decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
2331    }
2332}
2333
2334/// The bcrypt Base64 alphabet.
2335///
2336/// This alphabet is commonly used by bcrypt hash strings. It is provided as an
2337/// alphabet/profile building block; `base64-ng` does not parse or verify full
2338/// bcrypt password-hash records.
2339#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
2340pub struct Bcrypt;
2341
2342impl Alphabet for Bcrypt {
2343    const ENCODE: [u8; 64] = *b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
2344
2345    #[inline]
2346    fn decode(byte: u8) -> Option<u8> {
2347        decode_alphabet_byte(byte, &Self::ENCODE)
2348    }
2349}
2350
2351/// The Unix `crypt(3)` Base64 alphabet.
2352///
2353/// This alphabet is provided as an explicit legacy interoperability profile.
2354/// `base64-ng` does not parse or verify complete password-hash records.
2355#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
2356pub struct Crypt;
2357
2358impl Alphabet for Crypt {
2359    const ENCODE: [u8; 64] = *b"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2360
2361    #[inline]
2362    fn decode(byte: u8) -> Option<u8> {
2363        decode_alphabet_byte(byte, &Self::ENCODE)
2364    }
2365}
2366
2367#[inline]
2368const fn encode_base64_value<A: Alphabet>(value: u8) -> u8 {
2369    encode_alphabet_value(value, &A::ENCODE)
2370}
2371
2372#[inline]
2373fn encode_base64_value_runtime<A: Alphabet>(value: u8) -> u8 {
2374    A::encode(value)
2375}
2376
2377#[inline]
2378const fn encode_alphabet_value(value: u8, encode: &[u8; 64]) -> u8 {
2379    let mut output = 0;
2380    let mut index = 0;
2381    let mut candidate = 0;
2382    while index < encode.len() {
2383        output |= encode[index] & ct_mask_eq_u8(value, candidate);
2384        index += 1;
2385        candidate += 1;
2386    }
2387    output
2388}
2389
2390#[inline]
2391const fn encode_ascii_base64(value: u8, value_62_byte: u8, value_63_byte: u8) -> u8 {
2392    let upper = ct_mask_lt_u8(value, 26);
2393    let lower = ct_mask_lt_u8(value.wrapping_sub(26), 26);
2394    let digit = ct_mask_lt_u8(value.wrapping_sub(52), 10);
2395    let value_62 = ct_mask_eq_u8(value, 0x3e);
2396    let value_63 = ct_mask_eq_u8(value, 0x3f);
2397
2398    (value.wrapping_add(b'A') & upper)
2399        | (value.wrapping_sub(26).wrapping_add(b'a') & lower)
2400        | (value.wrapping_sub(52).wrapping_add(b'0') & digit)
2401        | (value_62_byte & value_62)
2402        | (value_63_byte & value_63)
2403}
2404
2405#[inline]
2406fn decode_ascii_base64(byte: u8, value_62_byte: u8, value_63_byte: u8) -> Option<u8> {
2407    let upper = ct_mask_lt_u8(byte.wrapping_sub(b'A'), 26);
2408    let lower = ct_mask_lt_u8(byte.wrapping_sub(b'a'), 26);
2409    let digit = ct_mask_lt_u8(byte.wrapping_sub(b'0'), 10);
2410    let value_62 = ct_mask_eq_u8(byte, value_62_byte);
2411    let value_63 = ct_mask_eq_u8(byte, value_63_byte);
2412    let valid = upper | lower | digit | value_62 | value_63;
2413
2414    let decoded = (byte.wrapping_sub(b'A') & upper)
2415        | (byte.wrapping_sub(b'a').wrapping_add(26) & lower)
2416        | (byte.wrapping_sub(b'0').wrapping_add(52) & digit)
2417        | (0x3e & value_62)
2418        | (0x3f & value_63);
2419
2420    if valid == 0 { None } else { Some(decoded) }
2421}
2422
2423#[inline]
2424const fn ct_mask_bit(bit: u8) -> u8 {
2425    0u8.wrapping_sub(bit & 1)
2426}
2427
2428#[inline]
2429const fn ct_mask_nonzero_u8(value: u8) -> u8 {
2430    let wide = value as u16;
2431    let negative = 0u16.wrapping_sub(wide);
2432    let nonzero = ((wide | negative) >> 8) as u8;
2433    ct_mask_bit(nonzero)
2434}
2435
2436#[inline]
2437const fn ct_mask_eq_u8(left: u8, right: u8) -> u8 {
2438    !ct_mask_nonzero_u8(left ^ right)
2439}
2440
2441#[inline]
2442const fn ct_mask_lt_u8(left: u8, right: u8) -> u8 {
2443    let diff = (left as u16).wrapping_sub(right as u16);
2444    ct_mask_bit((diff >> 8) as u8)
2445}
2446
2447mod backend {
2448    use super::{
2449        Alphabet, DecodeError, EncodeError, checked_encoded_len, decode_padded, decode_unpadded,
2450        encode_base64_value_runtime,
2451    };
2452
2453    pub(super) fn encode_slice<A, const PAD: bool>(
2454        input: &[u8],
2455        output: &mut [u8],
2456    ) -> Result<usize, EncodeError>
2457    where
2458        A: Alphabet,
2459    {
2460        #[cfg(feature = "simd")]
2461        match super::simd::active_backend() {
2462            super::simd::ActiveBackend::Scalar => {}
2463        }
2464
2465        scalar_encode_slice::<A, PAD>(input, output)
2466    }
2467
2468    pub(super) fn decode_slice<A, const PAD: bool>(
2469        input: &[u8],
2470        output: &mut [u8],
2471    ) -> Result<usize, DecodeError>
2472    where
2473        A: Alphabet,
2474    {
2475        #[cfg(feature = "simd")]
2476        match super::simd::active_backend() {
2477            super::simd::ActiveBackend::Scalar => {}
2478        }
2479
2480        scalar_decode_slice::<A, PAD>(input, output)
2481    }
2482
2483    #[cfg(test)]
2484    pub(super) fn scalar_reference_encode_slice<A, const PAD: bool>(
2485        input: &[u8],
2486        output: &mut [u8],
2487    ) -> Result<usize, EncodeError>
2488    where
2489        A: Alphabet,
2490    {
2491        scalar_encode_slice::<A, PAD>(input, output)
2492    }
2493
2494    #[cfg(test)]
2495    pub(super) fn scalar_reference_decode_slice<A, const PAD: bool>(
2496        input: &[u8],
2497        output: &mut [u8],
2498    ) -> Result<usize, DecodeError>
2499    where
2500        A: Alphabet,
2501    {
2502        scalar_decode_slice::<A, PAD>(input, output)
2503    }
2504
2505    fn scalar_encode_slice<A, const PAD: bool>(
2506        input: &[u8],
2507        output: &mut [u8],
2508    ) -> Result<usize, EncodeError>
2509    where
2510        A: Alphabet,
2511    {
2512        let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
2513        if output.len() < required {
2514            return Err(EncodeError::OutputTooSmall {
2515                required,
2516                available: output.len(),
2517            });
2518        }
2519
2520        let mut read = 0;
2521        let mut write = 0;
2522        while read + 3 <= input.len() {
2523            let b0 = input[read];
2524            let b1 = input[read + 1];
2525            let b2 = input[read + 2];
2526
2527            output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2528            output[write + 1] =
2529                encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2530            output[write + 2] =
2531                encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
2532            output[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
2533
2534            read += 3;
2535            write += 4;
2536        }
2537
2538        match input.len() - read {
2539            0 => {}
2540            1 => {
2541                let b0 = input[read];
2542                output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2543                output[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
2544                write += 2;
2545                if PAD {
2546                    output[write] = b'=';
2547                    output[write + 1] = b'=';
2548                    write += 2;
2549                }
2550            }
2551            2 => {
2552                let b0 = input[read];
2553                let b1 = input[read + 1];
2554                output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2555                output[write + 1] =
2556                    encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2557                output[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
2558                write += 3;
2559                if PAD {
2560                    output[write] = b'=';
2561                    write += 1;
2562                }
2563            }
2564            _ => unreachable!(),
2565        }
2566
2567        Ok(write)
2568    }
2569
2570    fn scalar_decode_slice<A, const PAD: bool>(
2571        input: &[u8],
2572        output: &mut [u8],
2573    ) -> Result<usize, DecodeError>
2574    where
2575        A: Alphabet,
2576    {
2577        if input.is_empty() {
2578            return Ok(0);
2579        }
2580
2581        if PAD {
2582            decode_padded::<A>(input, output)
2583        } else {
2584            decode_unpadded::<A>(input, output)
2585        }
2586    }
2587}
2588
2589/// A zero-sized Base64 engine parameterized by alphabet and padding policy.
2590pub struct Engine<A, const PAD: bool> {
2591    alphabet: core::marker::PhantomData<A>,
2592}
2593
2594impl<A, const PAD: bool> Clone for Engine<A, PAD> {
2595    fn clone(&self) -> Self {
2596        *self
2597    }
2598}
2599
2600impl<A, const PAD: bool> Copy for Engine<A, PAD> {}
2601
2602impl<A, const PAD: bool> core::fmt::Debug for Engine<A, PAD> {
2603    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2604        formatter
2605            .debug_struct("Engine")
2606            .field("padded", &PAD)
2607            .finish()
2608    }
2609}
2610
2611impl<A, const PAD: bool> Default for Engine<A, PAD> {
2612    fn default() -> Self {
2613        Self {
2614            alphabet: core::marker::PhantomData,
2615        }
2616    }
2617}
2618
2619impl<A, const PAD: bool> Eq for Engine<A, PAD> {}
2620
2621impl<A, const PAD: bool> PartialEq for Engine<A, PAD> {
2622    fn eq(&self, _other: &Self) -> bool {
2623        true
2624    }
2625}
2626
2627impl<A, const PAD: bool> Engine<A, PAD>
2628where
2629    A: Alphabet,
2630{
2631    /// Creates a new engine value.
2632    #[must_use]
2633    pub const fn new() -> Self {
2634        Self {
2635            alphabet: core::marker::PhantomData,
2636        }
2637    }
2638
2639    /// Returns the encoded length for this engine's padding policy.
2640    pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
2641        encoded_len(input_len, PAD)
2642    }
2643
2644    /// Returns the encoded length for this engine, or `None` on overflow.
2645    #[must_use]
2646    pub const fn checked_encoded_len(&self, input_len: usize) -> Option<usize> {
2647        checked_encoded_len(input_len, PAD)
2648    }
2649
2650    /// Returns the encoded length after applying a line wrapping policy.
2651    ///
2652    /// The returned length includes inserted line endings but does not include
2653    /// a trailing line ending after the final encoded line.
2654    pub const fn wrapped_encoded_len(
2655        &self,
2656        input_len: usize,
2657        wrap: LineWrap,
2658    ) -> Result<usize, EncodeError> {
2659        wrapped_encoded_len(input_len, PAD, wrap)
2660    }
2661
2662    /// Returns the exact decoded length implied by input length and padding.
2663    ///
2664    /// This validates padding placement and impossible lengths, but it does not
2665    /// validate alphabet membership or non-canonical trailing bits.
2666    pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
2667        decoded_len(input, PAD)
2668    }
2669
2670    /// Returns the exact decoded length for the explicit legacy profile.
2671    ///
2672    /// The legacy profile ignores ASCII space, tab, carriage return, and line
2673    /// feed bytes before applying the same alphabet, padding, and canonical-bit
2674    /// checks as strict decoding.
2675    pub fn decoded_len_legacy(&self, input: &[u8]) -> Result<usize, DecodeError> {
2676        validate_legacy_decode::<A, PAD>(input)
2677    }
2678
2679    /// Returns the exact decoded length for a line-wrapped profile.
2680    ///
2681    /// The wrapped profile accepts only the configured line ending. Non-final
2682    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
2683    /// may be shorter. A single trailing line ending after the final line is
2684    /// accepted.
2685    pub fn decoded_len_wrapped(&self, input: &[u8], wrap: LineWrap) -> Result<usize, DecodeError> {
2686        validate_wrapped_decode::<A, PAD>(input, wrap)
2687    }
2688
2689    /// Validates strict Base64 input without writing decoded bytes.
2690    ///
2691    /// This applies the same alphabet, padding, and canonical-bit checks as
2692    /// [`Self::decode_slice`]. Use this method when malformed-input
2693    /// diagnostics matter; use [`Self::validate`] when a boolean is enough.
2694    ///
2695    /// # Examples
2696    ///
2697    /// ```
2698    /// use base64_ng::STANDARD;
2699    ///
2700    /// STANDARD.validate_result(b"aGVsbG8=").unwrap();
2701    /// assert!(STANDARD.validate_result(b"aGVsbG8").is_err());
2702    /// ```
2703    pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
2704        validate_decode::<A, PAD>(input).map(|_| ())
2705    }
2706
2707    /// Returns whether `input` is valid strict Base64 for this engine.
2708    ///
2709    /// This is a convenience wrapper around [`Self::validate_result`].
2710    ///
2711    /// # Examples
2712    ///
2713    /// ```
2714    /// use base64_ng::URL_SAFE_NO_PAD;
2715    ///
2716    /// assert!(URL_SAFE_NO_PAD.validate(b"-_8"));
2717    /// assert!(!URL_SAFE_NO_PAD.validate(b"+/8"));
2718    /// ```
2719    #[must_use]
2720    pub fn validate(&self, input: &[u8]) -> bool {
2721        self.validate_result(input).is_ok()
2722    }
2723
2724    /// Validates input using the explicit legacy whitespace profile.
2725    ///
2726    /// ASCII space, tab, carriage return, and line feed bytes are ignored
2727    /// before applying the same alphabet, padding, and canonical-bit checks as
2728    /// strict decoding.
2729    ///
2730    /// # Examples
2731    ///
2732    /// ```
2733    /// use base64_ng::STANDARD;
2734    ///
2735    /// STANDARD.validate_legacy_result(b" aG\r\nVsbG8= ").unwrap();
2736    /// assert!(STANDARD.validate_legacy_result(b" aG-=").is_err());
2737    /// ```
2738    pub fn validate_legacy_result(&self, input: &[u8]) -> Result<(), DecodeError> {
2739        validate_legacy_decode::<A, PAD>(input).map(|_| ())
2740    }
2741
2742    /// Returns whether `input` is valid for the explicit legacy whitespace
2743    /// profile.
2744    ///
2745    /// This is a convenience wrapper around [`Self::validate_legacy_result`].
2746    ///
2747    /// # Examples
2748    ///
2749    /// ```
2750    /// use base64_ng::STANDARD;
2751    ///
2752    /// assert!(STANDARD.validate_legacy(b" aG\r\nVsbG8= "));
2753    /// assert!(!STANDARD.validate_legacy(b"aG-V"));
2754    /// ```
2755    #[must_use]
2756    pub fn validate_legacy(&self, input: &[u8]) -> bool {
2757        self.validate_legacy_result(input).is_ok()
2758    }
2759
2760    /// Validates input using a strict line-wrapped profile.
2761    ///
2762    /// This is stricter than [`Self::validate_legacy_result`]: it accepts only
2763    /// the configured line ending and enforces the configured line length for
2764    /// every non-final line.
2765    ///
2766    /// # Examples
2767    ///
2768    /// ```
2769    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
2770    ///
2771    /// let wrap = LineWrap::new(4, LineEnding::Lf);
2772    /// STANDARD.validate_wrapped_result(b"aGVs\nbG8=", wrap).unwrap();
2773    /// assert!(STANDARD.validate_wrapped_result(b"aG\nVsbG8=", wrap).is_err());
2774    /// ```
2775    pub fn validate_wrapped_result(&self, input: &[u8], wrap: LineWrap) -> Result<(), DecodeError> {
2776        validate_wrapped_decode::<A, PAD>(input, wrap).map(|_| ())
2777    }
2778
2779    /// Returns whether `input` is valid for a strict line-wrapped profile.
2780    ///
2781    /// This is a convenience wrapper around [`Self::validate_wrapped_result`].
2782    ///
2783    /// # Examples
2784    ///
2785    /// ```
2786    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
2787    ///
2788    /// let wrap = LineWrap::new(4, LineEnding::Lf);
2789    /// assert!(STANDARD.validate_wrapped(b"aGVs\nbG8=", wrap));
2790    /// assert!(!STANDARD.validate_wrapped(b"aG\nVsbG8=", wrap));
2791    /// ```
2792    #[must_use]
2793    pub fn validate_wrapped(&self, input: &[u8], wrap: LineWrap) -> bool {
2794        self.validate_wrapped_result(input, wrap).is_ok()
2795    }
2796
2797    /// Encodes a fixed-size input into a fixed-size output array in const contexts.
2798    ///
2799    /// Stable Rust does not yet allow this API to return an array whose length
2800    /// is computed from `INPUT_LEN` directly. Instead, the caller supplies the
2801    /// output length through the destination type and this function panics
2802    /// during const evaluation if the length is wrong.
2803    ///
2804    /// # Panics
2805    ///
2806    /// Panics if `OUTPUT_LEN` is not exactly the encoded length for `INPUT_LEN`
2807    /// and this engine's padding policy, or if that length overflows `usize`.
2808    ///
2809    /// # Examples
2810    ///
2811    /// ```
2812    /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
2813    ///
2814    /// const HELLO: [u8; 8] = STANDARD.encode_array(b"hello");
2815    /// const URL_SAFE: [u8; 3] = URL_SAFE_NO_PAD.encode_array(b"\xfb\xff");
2816    ///
2817    /// assert_eq!(&HELLO, b"aGVsbG8=");
2818    /// assert_eq!(&URL_SAFE, b"-_8");
2819    /// ```
2820    ///
2821    /// Incorrect output lengths fail during const evaluation:
2822    ///
2823    /// ```compile_fail
2824    /// use base64_ng::STANDARD;
2825    ///
2826    /// const TOO_SHORT: [u8; 7] = STANDARD.encode_array(b"hello");
2827    /// ```
2828    #[must_use]
2829    pub const fn encode_array<const INPUT_LEN: usize, const OUTPUT_LEN: usize>(
2830        &self,
2831        input: &[u8; INPUT_LEN],
2832    ) -> [u8; OUTPUT_LEN] {
2833        let Some(required) = checked_encoded_len(INPUT_LEN, PAD) else {
2834            panic!("encoded base64 length overflows usize");
2835        };
2836        assert!(
2837            required == OUTPUT_LEN,
2838            "base64 output array has incorrect length"
2839        );
2840
2841        let mut output = [0u8; OUTPUT_LEN];
2842        let mut read = 0;
2843        let mut write = 0;
2844        while INPUT_LEN - read >= 3 {
2845            let b0 = input[read];
2846            let b1 = input[read + 1];
2847            let b2 = input[read + 2];
2848
2849            output[write] = encode_base64_value::<A>(b0 >> 2);
2850            output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2851            output[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
2852            output[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
2853
2854            read += 3;
2855            write += 4;
2856        }
2857
2858        match INPUT_LEN - read {
2859            0 => {}
2860            1 => {
2861                let b0 = input[read];
2862                output[write] = encode_base64_value::<A>(b0 >> 2);
2863                output[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
2864                write += 2;
2865                if PAD {
2866                    output[write] = b'=';
2867                    output[write + 1] = b'=';
2868                }
2869            }
2870            2 => {
2871                let b0 = input[read];
2872                let b1 = input[read + 1];
2873                output[write] = encode_base64_value::<A>(b0 >> 2);
2874                output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2875                output[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
2876                if PAD {
2877                    output[write + 3] = b'=';
2878                }
2879            }
2880            _ => unreachable!(),
2881        }
2882
2883        output
2884    }
2885
2886    /// Encodes `input` into `output`, returning the number of bytes written.
2887    pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
2888        backend::encode_slice::<A, PAD>(input, output)
2889    }
2890
2891    /// Encodes `input` into `output` with line wrapping.
2892    ///
2893    /// The wrapping policy inserts line endings between encoded lines and does
2894    /// not append a trailing line ending after the final line.
2895    ///
2896    /// # Examples
2897    ///
2898    /// ```
2899    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
2900    ///
2901    /// let wrap = LineWrap::new(4, LineEnding::Lf);
2902    /// let mut output = [0u8; 9];
2903    /// let written = STANDARD
2904    ///     .encode_slice_wrapped(b"hello", &mut output, wrap)
2905    ///     .unwrap();
2906    ///
2907    /// assert_eq!(&output[..written], b"aGVs\nbG8=");
2908    /// ```
2909    pub fn encode_slice_wrapped(
2910        &self,
2911        input: &[u8],
2912        output: &mut [u8],
2913        wrap: LineWrap,
2914    ) -> Result<usize, EncodeError> {
2915        let required = self.wrapped_encoded_len(input.len(), wrap)?;
2916        if output.len() < required {
2917            return Err(EncodeError::OutputTooSmall {
2918                required,
2919                available: output.len(),
2920            });
2921        }
2922
2923        let encoded_len =
2924            checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
2925        if encoded_len == 0 {
2926            return Ok(0);
2927        }
2928
2929        if output.len() < required.saturating_add(encoded_len) {
2930            let mut scratch = [0u8; 1024];
2931            let mut input_offset = 0;
2932            let mut output_offset = 0;
2933            let mut column = 0;
2934
2935            while input_offset < input.len() {
2936                let remaining = input.len() - input_offset;
2937                let mut take = remaining.min(768);
2938                if remaining > take {
2939                    take -= take % 3;
2940                }
2941                if take == 0 {
2942                    take = remaining;
2943                }
2944
2945                let encoded =
2946                    self.encode_slice(&input[input_offset..input_offset + take], &mut scratch)?;
2947                write_wrapped_bytes(
2948                    &scratch[..encoded],
2949                    output,
2950                    &mut output_offset,
2951                    &mut column,
2952                    wrap,
2953                );
2954                wipe_bytes(&mut scratch[..encoded]);
2955                input_offset += take;
2956            }
2957
2958            Ok(output_offset)
2959        } else {
2960            let encoded =
2961                self.encode_slice(input, &mut output[required..required + encoded_len])?;
2962            let mut output_offset = 0;
2963            let mut column = 0;
2964            let mut read = required;
2965            while read < required + encoded {
2966                let byte = output[read];
2967                write_wrapped_byte(byte, output, &mut output_offset, &mut column, wrap);
2968                read += 1;
2969            }
2970            wipe_bytes(&mut output[required..required + encoded]);
2971            Ok(output_offset)
2972        }
2973    }
2974
2975    /// Encodes `input` with line wrapping and clears all bytes after the
2976    /// encoded prefix.
2977    ///
2978    /// If encoding fails, the entire output buffer is cleared before the error
2979    /// is returned.
2980    pub fn encode_slice_wrapped_clear_tail(
2981        &self,
2982        input: &[u8],
2983        output: &mut [u8],
2984        wrap: LineWrap,
2985    ) -> Result<usize, EncodeError> {
2986        let written = match self.encode_slice_wrapped(input, output, wrap) {
2987            Ok(written) => written,
2988            Err(err) => {
2989                wipe_bytes(output);
2990                return Err(err);
2991            }
2992        };
2993        wipe_tail(output, written);
2994        Ok(written)
2995    }
2996
2997    /// Encodes `input` with line wrapping into a newly allocated byte vector.
2998    #[cfg(feature = "alloc")]
2999    pub fn encode_wrapped_vec(
3000        &self,
3001        input: &[u8],
3002        wrap: LineWrap,
3003    ) -> Result<alloc::vec::Vec<u8>, EncodeError> {
3004        let required = self.wrapped_encoded_len(input.len(), wrap)?;
3005        let mut output = alloc::vec![0; required];
3006        let written = self.encode_slice_wrapped(input, &mut output, wrap)?;
3007        output.truncate(written);
3008        Ok(output)
3009    }
3010
3011    /// Encodes `input` with line wrapping into a newly allocated UTF-8 string.
3012    #[cfg(feature = "alloc")]
3013    pub fn encode_wrapped_string(
3014        &self,
3015        input: &[u8],
3016        wrap: LineWrap,
3017    ) -> Result<alloc::string::String, EncodeError> {
3018        let output = self.encode_wrapped_vec(input, wrap)?;
3019        match alloc::string::String::from_utf8(output) {
3020            Ok(output) => Ok(output),
3021            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
3022        }
3023    }
3024
3025    /// Encodes `input` into `output` and clears all bytes after the encoded
3026    /// prefix.
3027    ///
3028    /// If encoding fails, the entire output buffer is cleared before the error
3029    /// is returned.
3030    ///
3031    /// # Examples
3032    ///
3033    /// ```
3034    /// use base64_ng::STANDARD;
3035    ///
3036    /// let mut output = [0xff; 12];
3037    /// let written = STANDARD
3038    ///     .encode_slice_clear_tail(b"hello", &mut output)
3039    ///     .unwrap();
3040    ///
3041    /// assert_eq!(&output[..written], b"aGVsbG8=");
3042    /// assert!(output[written..].iter().all(|byte| *byte == 0));
3043    /// ```
3044    pub fn encode_slice_clear_tail(
3045        &self,
3046        input: &[u8],
3047        output: &mut [u8],
3048    ) -> Result<usize, EncodeError> {
3049        let written = match self.encode_slice(input, output) {
3050            Ok(written) => written,
3051            Err(err) => {
3052                wipe_bytes(output);
3053                return Err(err);
3054            }
3055        };
3056        wipe_tail(output, written);
3057        Ok(written)
3058    }
3059
3060    /// Encodes `input` into a stack-backed buffer.
3061    ///
3062    /// This helper is useful for short values where callers want the
3063    /// convenience of an owned result without enabling `alloc`.
3064    ///
3065    /// # Examples
3066    ///
3067    /// ```
3068    /// use base64_ng::STANDARD;
3069    ///
3070    /// let encoded = STANDARD.encode_buffer::<8>(b"hello").unwrap();
3071    ///
3072    /// assert_eq!(encoded.as_str(), "aGVsbG8=");
3073    /// ```
3074    pub fn encode_buffer<const CAP: usize>(
3075        &self,
3076        input: &[u8],
3077    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
3078        let mut output = EncodedBuffer::new();
3079        let written = match self.encode_slice_clear_tail(input, &mut output.bytes) {
3080            Ok(written) => written,
3081            Err(err) => {
3082                output.clear();
3083                return Err(err);
3084            }
3085        };
3086        output.len = written;
3087        Ok(output)
3088    }
3089
3090    /// Encodes `input` into a newly allocated byte vector.
3091    #[cfg(feature = "alloc")]
3092    pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
3093        let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
3094        let mut output = alloc::vec![0; required];
3095        let written = self.encode_slice(input, &mut output)?;
3096        output.truncate(written);
3097        Ok(output)
3098    }
3099
3100    /// Encodes `input` into a redacted owned secret buffer.
3101    ///
3102    /// This is useful when the encoded representation itself is sensitive and
3103    /// should not be accidentally logged through formatting.
3104    #[cfg(feature = "alloc")]
3105    pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
3106        self.encode_vec(input).map(SecretBuffer::from_vec)
3107    }
3108
3109    /// Encodes `input` into a newly allocated UTF-8 string.
3110    ///
3111    /// Base64 output is ASCII by construction. This helper is available with
3112    /// the `alloc` feature and has the same encoding semantics as
3113    /// [`Self::encode_slice`].
3114    ///
3115    /// # Examples
3116    ///
3117    /// ```
3118    /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
3119    ///
3120    /// assert_eq!(STANDARD.encode_string(b"hello").unwrap(), "aGVsbG8=");
3121    /// assert_eq!(URL_SAFE_NO_PAD.encode_string(b"\xfb\xff").unwrap(), "-_8");
3122    /// ```
3123    #[cfg(feature = "alloc")]
3124    pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
3125        let output = self.encode_vec(input)?;
3126        match alloc::string::String::from_utf8(output) {
3127            Ok(output) => Ok(output),
3128            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
3129        }
3130    }
3131
3132    /// Encodes the first `input_len` bytes of `buffer` in place.
3133    ///
3134    /// The buffer must have enough spare capacity for the encoded output. The
3135    /// implementation writes from right to left, so unread input bytes are not
3136    /// overwritten before they are encoded.
3137    ///
3138    /// # Examples
3139    ///
3140    /// ```
3141    /// use base64_ng::STANDARD;
3142    ///
3143    /// let mut buffer = [0u8; 8];
3144    /// buffer[..5].copy_from_slice(b"hello");
3145    /// let encoded = STANDARD.encode_in_place(&mut buffer, 5).unwrap();
3146    /// assert_eq!(encoded, b"aGVsbG8=");
3147    /// ```
3148    pub fn encode_in_place<'a>(
3149        &self,
3150        buffer: &'a mut [u8],
3151        input_len: usize,
3152    ) -> Result<&'a mut [u8], EncodeError> {
3153        if input_len > buffer.len() {
3154            return Err(EncodeError::InputTooLarge {
3155                input_len,
3156                buffer_len: buffer.len(),
3157            });
3158        }
3159
3160        let required = checked_encoded_len(input_len, PAD).ok_or(EncodeError::LengthOverflow)?;
3161        if buffer.len() < required {
3162            return Err(EncodeError::OutputTooSmall {
3163                required,
3164                available: buffer.len(),
3165            });
3166        }
3167
3168        let mut read = input_len;
3169        let mut write = required;
3170
3171        match input_len % 3 {
3172            0 => {}
3173            1 => {
3174                read -= 1;
3175                let b0 = buffer[read];
3176                if PAD {
3177                    write -= 4;
3178                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
3179                    buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
3180                    buffer[write + 2] = b'=';
3181                    buffer[write + 3] = b'=';
3182                } else {
3183                    write -= 2;
3184                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
3185                    buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
3186                }
3187            }
3188            2 => {
3189                read -= 2;
3190                let b0 = buffer[read];
3191                let b1 = buffer[read + 1];
3192                if PAD {
3193                    write -= 4;
3194                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
3195                    buffer[write + 1] =
3196                        encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
3197                    buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
3198                    buffer[write + 3] = b'=';
3199                } else {
3200                    write -= 3;
3201                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
3202                    buffer[write + 1] =
3203                        encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
3204                    buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
3205                }
3206            }
3207            _ => unreachable!(),
3208        }
3209
3210        while read > 0 {
3211            read -= 3;
3212            write -= 4;
3213            let b0 = buffer[read];
3214            let b1 = buffer[read + 1];
3215            let b2 = buffer[read + 2];
3216
3217            buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
3218            buffer[write + 1] =
3219                encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
3220            buffer[write + 2] =
3221                encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
3222            buffer[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
3223        }
3224
3225        debug_assert_eq!(write, 0);
3226        Ok(&mut buffer[..required])
3227    }
3228
3229    /// Encodes the first `input_len` bytes of `buffer` in place and clears all
3230    /// bytes after the encoded prefix.
3231    ///
3232    /// If encoding fails because `input_len` is too large, the output buffer is
3233    /// too small, or the encoded length overflows `usize`, the entire buffer is
3234    /// cleared before the error is returned.
3235    ///
3236    /// # Examples
3237    ///
3238    /// ```
3239    /// use base64_ng::STANDARD;
3240    ///
3241    /// let mut buffer = [0xff; 12];
3242    /// buffer[..5].copy_from_slice(b"hello");
3243    /// let encoded = STANDARD.encode_in_place_clear_tail(&mut buffer, 5).unwrap();
3244    /// assert_eq!(encoded, b"aGVsbG8=");
3245    /// ```
3246    pub fn encode_in_place_clear_tail<'a>(
3247        &self,
3248        buffer: &'a mut [u8],
3249        input_len: usize,
3250    ) -> Result<&'a mut [u8], EncodeError> {
3251        let len = match self.encode_in_place(buffer, input_len) {
3252            Ok(encoded) => encoded.len(),
3253            Err(err) => {
3254                wipe_bytes(buffer);
3255                return Err(err);
3256            }
3257        };
3258        wipe_tail(buffer, len);
3259        Ok(&mut buffer[..len])
3260    }
3261
3262    /// Decodes `input` into `output`, returning the number of bytes written.
3263    ///
3264    /// This is strict decoding. Whitespace, mixed alphabets, malformed padding,
3265    /// and trailing non-padding data are rejected.
3266    pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
3267        backend::decode_slice::<A, PAD>(input, output)
3268    }
3269
3270    /// Decodes `input` into `output` and clears all bytes after the decoded
3271    /// prefix.
3272    ///
3273    /// If decoding fails, the entire output buffer is cleared before the error
3274    /// is returned.
3275    ///
3276    /// # Examples
3277    ///
3278    /// ```
3279    /// use base64_ng::STANDARD;
3280    ///
3281    /// let mut output = [0xff; 8];
3282    /// let written = STANDARD
3283    ///     .decode_slice_clear_tail(b"aGk=", &mut output)
3284    ///     .unwrap();
3285    ///
3286    /// assert_eq!(&output[..written], b"hi");
3287    /// assert!(output[written..].iter().all(|byte| *byte == 0));
3288    /// ```
3289    pub fn decode_slice_clear_tail(
3290        &self,
3291        input: &[u8],
3292        output: &mut [u8],
3293    ) -> Result<usize, DecodeError> {
3294        let written = match self.decode_slice(input, output) {
3295            Ok(written) => written,
3296            Err(err) => {
3297                wipe_bytes(output);
3298                return Err(err);
3299            }
3300        };
3301        wipe_tail(output, written);
3302        Ok(written)
3303    }
3304
3305    /// Decodes `input` using the explicit legacy whitespace profile.
3306    ///
3307    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
3308    /// Alphabet selection, padding placement, trailing data after padding, and
3309    /// non-canonical trailing bits remain strict.
3310    pub fn decode_slice_legacy(
3311        &self,
3312        input: &[u8],
3313        output: &mut [u8],
3314    ) -> Result<usize, DecodeError> {
3315        let required = validate_legacy_decode::<A, PAD>(input)?;
3316        if output.len() < required {
3317            return Err(DecodeError::OutputTooSmall {
3318                required,
3319                available: output.len(),
3320            });
3321        }
3322        decode_legacy_to_slice::<A, PAD>(input, output)
3323    }
3324
3325    /// Decodes `input` using the explicit legacy whitespace profile and clears
3326    /// all bytes after the decoded prefix.
3327    ///
3328    /// If validation or decoding fails, the entire output buffer is cleared
3329    /// before the error is returned.
3330    ///
3331    /// # Examples
3332    ///
3333    /// ```
3334    /// use base64_ng::STANDARD;
3335    ///
3336    /// let mut output = [0xff; 8];
3337    /// let written = STANDARD
3338    ///     .decode_slice_legacy_clear_tail(b" aG\r\nk= ", &mut output)
3339    ///     .unwrap();
3340    ///
3341    /// assert_eq!(&output[..written], b"hi");
3342    /// assert!(output[written..].iter().all(|byte| *byte == 0));
3343    /// ```
3344    pub fn decode_slice_legacy_clear_tail(
3345        &self,
3346        input: &[u8],
3347        output: &mut [u8],
3348    ) -> Result<usize, DecodeError> {
3349        let written = match self.decode_slice_legacy(input, output) {
3350            Ok(written) => written,
3351            Err(err) => {
3352                wipe_bytes(output);
3353                return Err(err);
3354            }
3355        };
3356        wipe_tail(output, written);
3357        Ok(written)
3358    }
3359
3360    /// Decodes `input` using a strict line-wrapped profile.
3361    ///
3362    /// The wrapped profile accepts only the configured line ending. Non-final
3363    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
3364    /// may be shorter. A single trailing line ending after the final line is
3365    /// accepted.
3366    pub fn decode_slice_wrapped(
3367        &self,
3368        input: &[u8],
3369        output: &mut [u8],
3370        wrap: LineWrap,
3371    ) -> Result<usize, DecodeError> {
3372        let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
3373        if output.len() < required {
3374            return Err(DecodeError::OutputTooSmall {
3375                required,
3376                available: output.len(),
3377            });
3378        }
3379        decode_wrapped_to_slice::<A, PAD>(input, output, wrap)
3380    }
3381
3382    /// Decodes `input` using a strict line-wrapped profile and clears all bytes
3383    /// after the decoded prefix.
3384    ///
3385    /// If validation or decoding fails, the entire output buffer is cleared
3386    /// before the error is returned.
3387    pub fn decode_slice_wrapped_clear_tail(
3388        &self,
3389        input: &[u8],
3390        output: &mut [u8],
3391        wrap: LineWrap,
3392    ) -> Result<usize, DecodeError> {
3393        let written = match self.decode_slice_wrapped(input, output, wrap) {
3394            Ok(written) => written,
3395            Err(err) => {
3396                wipe_bytes(output);
3397                return Err(err);
3398            }
3399        };
3400        wipe_tail(output, written);
3401        Ok(written)
3402    }
3403
3404    /// Decodes `input` into a newly allocated byte vector.
3405    ///
3406    /// This is strict decoding with the same semantics as [`Self::decode_slice`].
3407    #[cfg(feature = "alloc")]
3408    pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
3409        let required = validate_decode::<A, PAD>(input)?;
3410        let mut output = alloc::vec![0; required];
3411        let written = match self.decode_slice(input, &mut output) {
3412            Ok(written) => written,
3413            Err(err) => {
3414                wipe_bytes(&mut output);
3415                return Err(err);
3416            }
3417        };
3418        output.truncate(written);
3419        Ok(output)
3420    }
3421
3422    /// Decodes `input` into a redacted owned secret buffer.
3423    ///
3424    /// On malformed input, the intermediate output buffer is cleared before the
3425    /// error is returned by [`Self::decode_vec`].
3426    #[cfg(feature = "alloc")]
3427    pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
3428        self.decode_vec(input).map(SecretBuffer::from_vec)
3429    }
3430
3431    /// Decodes `input` into a newly allocated byte vector using the explicit
3432    /// legacy whitespace profile.
3433    #[cfg(feature = "alloc")]
3434    pub fn decode_vec_legacy(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
3435        let required = validate_legacy_decode::<A, PAD>(input)?;
3436        let mut output = alloc::vec![0; required];
3437        let written = match self.decode_slice_legacy(input, &mut output) {
3438            Ok(written) => written,
3439            Err(err) => {
3440                wipe_bytes(&mut output);
3441                return Err(err);
3442            }
3443        };
3444        output.truncate(written);
3445        Ok(output)
3446    }
3447
3448    /// Decodes line-wrapped input into a newly allocated byte vector.
3449    #[cfg(feature = "alloc")]
3450    pub fn decode_wrapped_vec(
3451        &self,
3452        input: &[u8],
3453        wrap: LineWrap,
3454    ) -> Result<alloc::vec::Vec<u8>, DecodeError> {
3455        let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
3456        let mut output = alloc::vec![0; required];
3457        let written = match self.decode_slice_wrapped(input, &mut output, wrap) {
3458            Ok(written) => written,
3459            Err(err) => {
3460                wipe_bytes(&mut output);
3461                return Err(err);
3462            }
3463        };
3464        output.truncate(written);
3465        Ok(output)
3466    }
3467
3468    /// Decodes the buffer in place and returns the decoded prefix.
3469    ///
3470    /// # Examples
3471    ///
3472    /// ```
3473    /// use base64_ng::STANDARD_NO_PAD;
3474    ///
3475    /// let mut buffer = *b"Zm9vYmFy";
3476    /// let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
3477    /// assert_eq!(decoded, b"foobar");
3478    /// ```
3479    pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
3480        let len = Self::decode_slice_to_start(buffer)?;
3481        Ok(&mut buffer[..len])
3482    }
3483
3484    /// Decodes the buffer in place and clears all bytes after the decoded prefix.
3485    ///
3486    /// If decoding fails, the entire buffer is cleared before the error is
3487    /// returned. Use this variant when the encoded or partially decoded data is
3488    /// sensitive and the caller wants best-effort cleanup without adding a
3489    /// dependency.
3490    ///
3491    /// # Examples
3492    ///
3493    /// ```
3494    /// use base64_ng::STANDARD;
3495    ///
3496    /// let mut buffer = *b"aGk=";
3497    /// let decoded = STANDARD.decode_in_place_clear_tail(&mut buffer).unwrap();
3498    /// assert_eq!(decoded, b"hi");
3499    /// ```
3500    pub fn decode_in_place_clear_tail<'a>(
3501        &self,
3502        buffer: &'a mut [u8],
3503    ) -> Result<&'a mut [u8], DecodeError> {
3504        let len = match Self::decode_slice_to_start(buffer) {
3505            Ok(len) => len,
3506            Err(err) => {
3507                wipe_bytes(buffer);
3508                return Err(err);
3509            }
3510        };
3511        wipe_tail(buffer, len);
3512        Ok(&mut buffer[..len])
3513    }
3514
3515    /// Decodes `buffer` in place using the explicit legacy whitespace profile.
3516    ///
3517    /// Ignored whitespace is compacted out before decoding. If validation
3518    /// fails, the buffer contents are unspecified.
3519    pub fn decode_in_place_legacy<'a>(
3520        &self,
3521        buffer: &'a mut [u8],
3522    ) -> Result<&'a mut [u8], DecodeError> {
3523        let _required = validate_legacy_decode::<A, PAD>(buffer)?;
3524        let mut write = 0;
3525        let mut read = 0;
3526        while read < buffer.len() {
3527            let byte = buffer[read];
3528            if !is_legacy_whitespace(byte) {
3529                buffer[write] = byte;
3530                write += 1;
3531            }
3532            read += 1;
3533        }
3534        let len = Self::decode_slice_to_start(&mut buffer[..write])?;
3535        Ok(&mut buffer[..len])
3536    }
3537
3538    /// Decodes `buffer` in place using the explicit legacy whitespace profile
3539    /// and clears all bytes after the decoded prefix.
3540    ///
3541    /// If validation or decoding fails, the entire buffer is cleared before the
3542    /// error is returned.
3543    pub fn decode_in_place_legacy_clear_tail<'a>(
3544        &self,
3545        buffer: &'a mut [u8],
3546    ) -> Result<&'a mut [u8], DecodeError> {
3547        if let Err(err) = validate_legacy_decode::<A, PAD>(buffer) {
3548            wipe_bytes(buffer);
3549            return Err(err);
3550        }
3551
3552        let mut write = 0;
3553        let mut read = 0;
3554        while read < buffer.len() {
3555            let byte = buffer[read];
3556            if !is_legacy_whitespace(byte) {
3557                buffer[write] = byte;
3558                write += 1;
3559            }
3560            read += 1;
3561        }
3562
3563        let len = match Self::decode_slice_to_start(&mut buffer[..write]) {
3564            Ok(len) => len,
3565            Err(err) => {
3566                wipe_bytes(buffer);
3567                return Err(err);
3568            }
3569        };
3570        wipe_tail(buffer, len);
3571        Ok(&mut buffer[..len])
3572    }
3573
3574    fn decode_slice_to_start(buffer: &mut [u8]) -> Result<usize, DecodeError> {
3575        let input_len = buffer.len();
3576        let mut read = 0;
3577        let mut write = 0;
3578        while read + 4 <= input_len {
3579            let chunk = read_quad(buffer, read)?;
3580            let available = buffer.len();
3581            let output_tail = buffer.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
3582                required: write,
3583                available,
3584            })?;
3585            let written = decode_chunk::<A, PAD>(chunk, output_tail)
3586                .map_err(|err| err.with_index_offset(read))?;
3587            read += 4;
3588            write += written;
3589            if written < 3 {
3590                if read != input_len {
3591                    return Err(DecodeError::InvalidPadding { index: read - 4 });
3592                }
3593                return Ok(write);
3594            }
3595        }
3596
3597        let rem = input_len - read;
3598        if rem == 0 {
3599            return Ok(write);
3600        }
3601        if PAD {
3602            return Err(DecodeError::InvalidLength);
3603        }
3604        let mut tail = [0u8; 3];
3605        tail[..rem].copy_from_slice(&buffer[read..input_len]);
3606        decode_tail_unpadded::<A>(&tail[..rem], &mut buffer[write..])
3607            .map_err(|err| err.with_index_offset(read))
3608            .map(|n| write + n)
3609    }
3610}
3611
3612fn write_wrapped_bytes(
3613    input: &[u8],
3614    output: &mut [u8],
3615    output_offset: &mut usize,
3616    column: &mut usize,
3617    wrap: LineWrap,
3618) {
3619    for byte in input {
3620        write_wrapped_byte(*byte, output, output_offset, column, wrap);
3621    }
3622}
3623
3624fn write_wrapped_byte(
3625    byte: u8,
3626    output: &mut [u8],
3627    output_offset: &mut usize,
3628    column: &mut usize,
3629    wrap: LineWrap,
3630) {
3631    if *column == wrap.line_len {
3632        let line_ending = wrap.line_ending.as_bytes();
3633        let mut index = 0;
3634        while index < line_ending.len() {
3635            output[*output_offset] = line_ending[index];
3636            *output_offset += 1;
3637            index += 1;
3638        }
3639        *column = 0;
3640    }
3641
3642    output[*output_offset] = byte;
3643    *output_offset += 1;
3644    *column += 1;
3645}
3646
3647/// Encoding error.
3648#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3649pub enum EncodeError {
3650    /// The encoded output length would overflow `usize`.
3651    LengthOverflow,
3652    /// The requested line wrapping policy is invalid.
3653    InvalidLineWrap {
3654        /// Requested line length.
3655        line_len: usize,
3656    },
3657    /// The caller-provided input length exceeds the provided buffer.
3658    InputTooLarge {
3659        /// Requested input bytes.
3660        input_len: usize,
3661        /// Available buffer bytes.
3662        buffer_len: usize,
3663    },
3664    /// The output buffer is too small.
3665    OutputTooSmall {
3666        /// Required output bytes.
3667        required: usize,
3668        /// Available output bytes.
3669        available: usize,
3670    },
3671}
3672
3673impl core::fmt::Display for EncodeError {
3674    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3675        match self {
3676            Self::LengthOverflow => f.write_str("base64 output length overflows usize"),
3677            Self::InvalidLineWrap { line_len } => {
3678                write!(f, "base64 line wrap length {line_len} is invalid")
3679            }
3680            Self::InputTooLarge {
3681                input_len,
3682                buffer_len,
3683            } => write!(
3684                f,
3685                "base64 input length {input_len} exceeds buffer length {buffer_len}"
3686            ),
3687            Self::OutputTooSmall {
3688                required,
3689                available,
3690            } => write!(
3691                f,
3692                "base64 output buffer too small: required {required}, available {available}"
3693            ),
3694        }
3695    }
3696}
3697
3698#[cfg(feature = "std")]
3699impl std::error::Error for EncodeError {}
3700
3701/// Alphabet validation error.
3702#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3703pub enum AlphabetError {
3704    /// The alphabet contains a non-visible-ASCII byte.
3705    InvalidByte {
3706        /// Byte index in the alphabet table.
3707        index: usize,
3708        /// Invalid byte value.
3709        byte: u8,
3710    },
3711    /// The alphabet contains the padding byte `=`.
3712    PaddingByte {
3713        /// Byte index in the alphabet table.
3714        index: usize,
3715    },
3716    /// The alphabet maps more than one value to the same byte.
3717    DuplicateByte {
3718        /// First byte index.
3719        first: usize,
3720        /// Second byte index.
3721        second: usize,
3722        /// Duplicated byte value.
3723        byte: u8,
3724    },
3725}
3726
3727impl core::fmt::Display for AlphabetError {
3728    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3729        match self {
3730            Self::InvalidByte { index, byte } => {
3731                write!(
3732                    f,
3733                    "invalid base64 alphabet byte 0x{byte:02x} at index {index}"
3734                )
3735            }
3736            Self::PaddingByte { index } => {
3737                write!(f, "base64 alphabet contains padding byte at index {index}")
3738            }
3739            Self::DuplicateByte {
3740                first,
3741                second,
3742                byte,
3743            } => write!(
3744                f,
3745                "base64 alphabet byte 0x{byte:02x} is duplicated at indexes {first} and {second}"
3746            ),
3747        }
3748    }
3749}
3750
3751#[cfg(feature = "std")]
3752impl std::error::Error for AlphabetError {}
3753
3754/// Decoding error.
3755#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3756pub enum DecodeError {
3757    /// The encoded input is malformed, but the decoder intentionally does not
3758    /// disclose a more specific error class.
3759    InvalidInput,
3760    /// The encoded input length is impossible for the selected padding policy.
3761    InvalidLength,
3762    /// A byte is not valid for the selected alphabet.
3763    InvalidByte {
3764        /// Byte index in the input.
3765        index: usize,
3766        /// Invalid byte value.
3767        byte: u8,
3768    },
3769    /// Padding is missing, misplaced, or non-canonical.
3770    InvalidPadding {
3771        /// Byte index where padding became invalid.
3772        index: usize,
3773    },
3774    /// Line wrapping is missing, misplaced, or uses the wrong line ending.
3775    InvalidLineWrap {
3776        /// Byte index where line wrapping became invalid.
3777        index: usize,
3778    },
3779    /// The output buffer is too small.
3780    OutputTooSmall {
3781        /// Required output bytes.
3782        required: usize,
3783        /// Available output bytes.
3784        available: usize,
3785    },
3786}
3787
3788impl core::fmt::Display for DecodeError {
3789    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3790        match self {
3791            Self::InvalidInput => f.write_str("malformed base64 input"),
3792            Self::InvalidLength => f.write_str("invalid base64 input length"),
3793            Self::InvalidByte { index, byte } => {
3794                write!(f, "invalid base64 byte 0x{byte:02x} at index {index}")
3795            }
3796            Self::InvalidPadding { index } => write!(f, "invalid base64 padding at index {index}"),
3797            Self::InvalidLineWrap { index } => {
3798                write!(f, "invalid base64 line wrapping at index {index}")
3799            }
3800            Self::OutputTooSmall {
3801                required,
3802                available,
3803            } => write!(
3804                f,
3805                "base64 decode output buffer too small: required {required}, available {available}"
3806            ),
3807        }
3808    }
3809}
3810
3811impl DecodeError {
3812    fn with_index_offset(self, offset: usize) -> Self {
3813        match self {
3814            Self::InvalidByte { index, byte } => Self::InvalidByte {
3815                index: index + offset,
3816                byte,
3817            },
3818            Self::InvalidPadding { index } => Self::InvalidPadding {
3819                index: index + offset,
3820            },
3821            Self::InvalidLineWrap { index } => Self::InvalidLineWrap {
3822                index: index + offset,
3823            },
3824            Self::InvalidInput | Self::InvalidLength | Self::OutputTooSmall { .. } => self,
3825        }
3826    }
3827}
3828
3829#[cfg(feature = "std")]
3830impl std::error::Error for DecodeError {}
3831
3832fn validate_legacy_decode<A: Alphabet, const PAD: bool>(
3833    input: &[u8],
3834) -> Result<usize, DecodeError> {
3835    let mut chunk = [0u8; 4];
3836    let mut indexes = [0usize; 4];
3837    let mut chunk_len = 0;
3838    let mut required = 0;
3839    let mut terminal_seen = false;
3840
3841    for (index, byte) in input.iter().copied().enumerate() {
3842        if is_legacy_whitespace(byte) {
3843            continue;
3844        }
3845        if terminal_seen {
3846            return Err(DecodeError::InvalidPadding { index });
3847        }
3848
3849        chunk[chunk_len] = byte;
3850        indexes[chunk_len] = index;
3851        chunk_len += 1;
3852
3853        if chunk_len == 4 {
3854            let written =
3855                validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
3856            required += written;
3857            terminal_seen = written < 3;
3858            chunk_len = 0;
3859        }
3860    }
3861
3862    if chunk_len == 0 {
3863        return Ok(required);
3864    }
3865    if PAD {
3866        return Err(DecodeError::InvalidLength);
3867    }
3868
3869    validate_tail_unpadded::<A>(&chunk[..chunk_len])
3870        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
3871    Ok(required + decoded_capacity(chunk_len))
3872}
3873
3874fn decode_legacy_to_slice<A: Alphabet, const PAD: bool>(
3875    input: &[u8],
3876    output: &mut [u8],
3877) -> Result<usize, DecodeError> {
3878    let mut chunk = [0u8; 4];
3879    let mut indexes = [0usize; 4];
3880    let mut chunk_len = 0;
3881    let mut write = 0;
3882    let mut terminal_seen = false;
3883
3884    for (index, byte) in input.iter().copied().enumerate() {
3885        if is_legacy_whitespace(byte) {
3886            continue;
3887        }
3888        if terminal_seen {
3889            return Err(DecodeError::InvalidPadding { index });
3890        }
3891
3892        chunk[chunk_len] = byte;
3893        indexes[chunk_len] = index;
3894        chunk_len += 1;
3895
3896        if chunk_len == 4 {
3897            let available = output.len();
3898            let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
3899                required: write,
3900                available,
3901            })?;
3902            let written = decode_chunk::<A, PAD>(chunk, output_tail)
3903                .map_err(|err| map_chunk_error(err, &indexes))?;
3904            write += written;
3905            terminal_seen = written < 3;
3906            chunk_len = 0;
3907        }
3908    }
3909
3910    if chunk_len == 0 {
3911        return Ok(write);
3912    }
3913    if PAD {
3914        return Err(DecodeError::InvalidLength);
3915    }
3916
3917    decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
3918        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
3919        .map(|n| write + n)
3920}
3921
3922struct WrappedBytes<'a> {
3923    input: &'a [u8],
3924    wrap: LineWrap,
3925    index: usize,
3926    line_len: usize,
3927}
3928
3929impl<'a> WrappedBytes<'a> {
3930    const fn new(input: &'a [u8], wrap: LineWrap) -> Result<Self, DecodeError> {
3931        if wrap.line_len == 0 {
3932            return Err(DecodeError::InvalidLineWrap { index: 0 });
3933        }
3934        Ok(Self {
3935            input,
3936            wrap,
3937            index: 0,
3938            line_len: 0,
3939        })
3940    }
3941
3942    fn next_byte(&mut self) -> Result<Option<(usize, u8)>, DecodeError> {
3943        loop {
3944            if self.index == self.input.len() {
3945                return Ok(None);
3946            }
3947
3948            if self.starts_with_line_ending() {
3949                let line_end_index = self.index;
3950                if self.line_len == 0 {
3951                    return Err(DecodeError::InvalidLineWrap {
3952                        index: line_end_index,
3953                    });
3954                }
3955
3956                self.index += self.wrap.line_ending.byte_len();
3957                if self.index == self.input.len() {
3958                    self.line_len = 0;
3959                    return Ok(None);
3960                }
3961
3962                if self.line_len != self.wrap.line_len {
3963                    return Err(DecodeError::InvalidLineWrap {
3964                        index: line_end_index,
3965                    });
3966                }
3967                self.line_len = 0;
3968                continue;
3969            }
3970
3971            let byte = self.input[self.index];
3972            if matches!(byte, b'\r' | b'\n') {
3973                return Err(DecodeError::InvalidLineWrap { index: self.index });
3974            }
3975
3976            self.line_len += 1;
3977            if self.line_len > self.wrap.line_len {
3978                return Err(DecodeError::InvalidLineWrap { index: self.index });
3979            }
3980
3981            let index = self.index;
3982            self.index += 1;
3983            return Ok(Some((index, byte)));
3984        }
3985    }
3986
3987    fn starts_with_line_ending(&self) -> bool {
3988        let line_ending = self.wrap.line_ending.as_bytes();
3989        let end = self.index + line_ending.len();
3990        end <= self.input.len() && &self.input[self.index..end] == line_ending
3991    }
3992}
3993
3994fn validate_wrapped_decode<A: Alphabet, const PAD: bool>(
3995    input: &[u8],
3996    wrap: LineWrap,
3997) -> Result<usize, DecodeError> {
3998    let mut bytes = WrappedBytes::new(input, wrap)?;
3999    let mut chunk = [0u8; 4];
4000    let mut indexes = [0usize; 4];
4001    let mut chunk_len = 0;
4002    let mut required = 0;
4003    let mut terminal_seen = false;
4004
4005    while let Some((index, byte)) = bytes.next_byte()? {
4006        if terminal_seen {
4007            return Err(DecodeError::InvalidPadding { index });
4008        }
4009
4010        chunk[chunk_len] = byte;
4011        indexes[chunk_len] = index;
4012        chunk_len += 1;
4013
4014        if chunk_len == 4 {
4015            let written =
4016                validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
4017            required += written;
4018            terminal_seen = written < 3;
4019            chunk_len = 0;
4020        }
4021    }
4022
4023    if chunk_len == 0 {
4024        return Ok(required);
4025    }
4026    if PAD {
4027        return Err(DecodeError::InvalidLength);
4028    }
4029
4030    validate_tail_unpadded::<A>(&chunk[..chunk_len])
4031        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
4032    Ok(required + decoded_capacity(chunk_len))
4033}
4034
4035fn decode_wrapped_to_slice<A: Alphabet, const PAD: bool>(
4036    input: &[u8],
4037    output: &mut [u8],
4038    wrap: LineWrap,
4039) -> Result<usize, DecodeError> {
4040    let mut bytes = WrappedBytes::new(input, wrap)?;
4041    let mut chunk = [0u8; 4];
4042    let mut indexes = [0usize; 4];
4043    let mut chunk_len = 0;
4044    let mut write = 0;
4045    let mut terminal_seen = false;
4046
4047    while let Some((index, byte)) = bytes.next_byte()? {
4048        if terminal_seen {
4049            return Err(DecodeError::InvalidPadding { index });
4050        }
4051
4052        chunk[chunk_len] = byte;
4053        indexes[chunk_len] = index;
4054        chunk_len += 1;
4055
4056        if chunk_len == 4 {
4057            let available = output.len();
4058            let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
4059                required: write,
4060                available,
4061            })?;
4062            let written = decode_chunk::<A, PAD>(chunk, output_tail)
4063                .map_err(|err| map_chunk_error(err, &indexes))?;
4064            write += written;
4065            terminal_seen = written < 3;
4066            chunk_len = 0;
4067        }
4068    }
4069
4070    if chunk_len == 0 {
4071        return Ok(write);
4072    }
4073    if PAD {
4074        return Err(DecodeError::InvalidLength);
4075    }
4076
4077    decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
4078        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
4079        .map(|n| write + n)
4080}
4081
4082#[inline]
4083const fn is_legacy_whitespace(byte: u8) -> bool {
4084    matches!(byte, b' ' | b'\t' | b'\r' | b'\n')
4085}
4086
4087fn map_chunk_error(err: DecodeError, indexes: &[usize; 4]) -> DecodeError {
4088    match err {
4089        DecodeError::InvalidByte { index, byte } => DecodeError::InvalidByte {
4090            index: indexes[index],
4091            byte,
4092        },
4093        DecodeError::InvalidPadding { index } => DecodeError::InvalidPadding {
4094            index: indexes[index],
4095        },
4096        DecodeError::InvalidInput
4097        | DecodeError::InvalidLineWrap { .. }
4098        | DecodeError::InvalidLength
4099        | DecodeError::OutputTooSmall { .. } => err,
4100    }
4101}
4102
4103fn map_partial_chunk_error(err: DecodeError, indexes: &[usize; 4], len: usize) -> DecodeError {
4104    match err {
4105        DecodeError::InvalidByte { index, byte } if index < len => DecodeError::InvalidByte {
4106            index: indexes[index],
4107            byte,
4108        },
4109        DecodeError::InvalidPadding { index } if index < len => DecodeError::InvalidPadding {
4110            index: indexes[index],
4111        },
4112        DecodeError::InvalidByte { .. }
4113        | DecodeError::InvalidPadding { .. }
4114        | DecodeError::InvalidLineWrap { .. }
4115        | DecodeError::InvalidInput
4116        | DecodeError::InvalidLength
4117        | DecodeError::OutputTooSmall { .. } => err,
4118    }
4119}
4120
4121fn decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
4122    if !input.len().is_multiple_of(4) {
4123        return Err(DecodeError::InvalidLength);
4124    }
4125    let required = decoded_len_padded(input)?;
4126    if output.len() < required {
4127        return Err(DecodeError::OutputTooSmall {
4128            required,
4129            available: output.len(),
4130        });
4131    }
4132
4133    let mut read = 0;
4134    let mut write = 0;
4135    while read < input.len() {
4136        let chunk = read_quad(input, read)?;
4137        let available = output.len();
4138        let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
4139            required: write,
4140            available,
4141        })?;
4142        let written = decode_chunk::<A, true>(chunk, output_tail)
4143            .map_err(|err| err.with_index_offset(read))?;
4144        read += 4;
4145        write += written;
4146        if written < 3 && read != input.len() {
4147            return Err(DecodeError::InvalidPadding { index: read - 4 });
4148        }
4149    }
4150    Ok(write)
4151}
4152
4153fn validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
4154    if input.is_empty() {
4155        return Ok(0);
4156    }
4157
4158    if PAD {
4159        validate_padded::<A>(input)
4160    } else {
4161        validate_unpadded::<A>(input)
4162    }
4163}
4164
4165fn validate_padded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
4166    if !input.len().is_multiple_of(4) {
4167        return Err(DecodeError::InvalidLength);
4168    }
4169    let required = decoded_len_padded(input)?;
4170
4171    let mut read = 0;
4172    while read < input.len() {
4173        let chunk = read_quad(input, read)?;
4174        let written =
4175            validate_chunk::<A, true>(chunk).map_err(|err| err.with_index_offset(read))?;
4176        read += 4;
4177        if written < 3 && read != input.len() {
4178            return Err(DecodeError::InvalidPadding { index: read - 4 });
4179        }
4180    }
4181
4182    Ok(required)
4183}
4184
4185fn validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
4186    let required = decoded_len_unpadded(input)?;
4187
4188    let mut read = 0;
4189    while read + 4 <= input.len() {
4190        let chunk = read_quad(input, read)?;
4191        validate_chunk::<A, false>(chunk).map_err(|err| err.with_index_offset(read))?;
4192        read += 4;
4193    }
4194    validate_tail_unpadded::<A>(&input[read..]).map_err(|err| err.with_index_offset(read))?;
4195
4196    Ok(required)
4197}
4198
4199fn decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
4200    let required = decoded_len_unpadded(input)?;
4201    if output.len() < required {
4202        return Err(DecodeError::OutputTooSmall {
4203            required,
4204            available: output.len(),
4205        });
4206    }
4207
4208    let mut read = 0;
4209    let mut write = 0;
4210    while read + 4 <= input.len() {
4211        let chunk = read_quad(input, read)?;
4212        let available = output.len();
4213        let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
4214            required: write,
4215            available,
4216        })?;
4217        let written = decode_chunk::<A, false>(chunk, output_tail)
4218            .map_err(|err| err.with_index_offset(read))?;
4219        read += 4;
4220        write += written;
4221    }
4222    decode_tail_unpadded::<A>(&input[read..], &mut output[write..])
4223        .map_err(|err| err.with_index_offset(read))
4224        .map(|n| write + n)
4225}
4226
4227fn decoded_len_padded(input: &[u8]) -> Result<usize, DecodeError> {
4228    if input.is_empty() {
4229        return Ok(0);
4230    }
4231    if !input.len().is_multiple_of(4) {
4232        return Err(DecodeError::InvalidLength);
4233    }
4234    let mut padding = 0;
4235    if input[input.len() - 1] == b'=' {
4236        padding += 1;
4237    }
4238    if input[input.len() - 2] == b'=' {
4239        padding += 1;
4240    }
4241    if padding == 0
4242        && let Some(index) = input.iter().position(|byte| *byte == b'=')
4243    {
4244        return Err(DecodeError::InvalidPadding { index });
4245    }
4246    if padding > 0 {
4247        let first_pad = input.len() - padding;
4248        if let Some(index) = input[..first_pad].iter().position(|byte| *byte == b'=') {
4249            return Err(DecodeError::InvalidPadding { index });
4250        }
4251    }
4252    Ok(input.len() / 4 * 3 - padding)
4253}
4254
4255fn decoded_len_unpadded(input: &[u8]) -> Result<usize, DecodeError> {
4256    if input.len() % 4 == 1 {
4257        return Err(DecodeError::InvalidLength);
4258    }
4259    if let Some(index) = input.iter().position(|byte| *byte == b'=') {
4260        return Err(DecodeError::InvalidPadding { index });
4261    }
4262    Ok(decoded_capacity(input.len()))
4263}
4264
4265fn read_quad(input: &[u8], offset: usize) -> Result<[u8; 4], DecodeError> {
4266    let end = offset.checked_add(4).ok_or(DecodeError::InvalidLength)?;
4267    match input.get(offset..end) {
4268        Some([b0, b1, b2, b3]) => Ok([*b0, *b1, *b2, *b3]),
4269        _ => Err(DecodeError::InvalidLength),
4270    }
4271}
4272
4273fn first_padding_index(input: [u8; 4]) -> usize {
4274    let [b0, b1, b2, b3] = input;
4275    if b0 == b'=' {
4276        0
4277    } else if b1 == b'=' {
4278        1
4279    } else if b2 == b'=' {
4280        2
4281    } else if b3 == b'=' {
4282        3
4283    } else {
4284        0
4285    }
4286}
4287
4288fn validate_chunk<A: Alphabet, const PAD: bool>(input: [u8; 4]) -> Result<usize, DecodeError> {
4289    let [b0, b1, b2, b3] = input;
4290    let _v0 = decode_byte::<A>(b0, 0)?;
4291    let v1 = decode_byte::<A>(b1, 1)?;
4292
4293    match (b2, b3) {
4294        (b'=', b'=') if PAD => {
4295            if v1 & 0b0000_1111 != 0 {
4296                return Err(DecodeError::InvalidPadding { index: 1 });
4297            }
4298            Ok(1)
4299        }
4300        (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
4301        (_, b'=') if PAD => {
4302            let v2 = decode_byte::<A>(b2, 2)?;
4303            if v2 & 0b0000_0011 != 0 {
4304                return Err(DecodeError::InvalidPadding { index: 2 });
4305            }
4306            Ok(2)
4307        }
4308        (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
4309            index: first_padding_index(input),
4310        }),
4311        _ => {
4312            decode_byte::<A>(b2, 2)?;
4313            decode_byte::<A>(b3, 3)?;
4314            Ok(3)
4315        }
4316    }
4317}
4318
4319fn decode_chunk<A: Alphabet, const PAD: bool>(
4320    input: [u8; 4],
4321    output: &mut [u8],
4322) -> Result<usize, DecodeError> {
4323    let [b0, b1, b2, b3] = input;
4324    let v0 = decode_byte::<A>(b0, 0)?;
4325    let v1 = decode_byte::<A>(b1, 1)?;
4326
4327    match (b2, b3) {
4328        (b'=', b'=') if PAD => {
4329            if output.is_empty() {
4330                return Err(DecodeError::OutputTooSmall {
4331                    required: 1,
4332                    available: output.len(),
4333                });
4334            }
4335            if v1 & 0b0000_1111 != 0 {
4336                return Err(DecodeError::InvalidPadding { index: 1 });
4337            }
4338            output[0] = (v0 << 2) | (v1 >> 4);
4339            Ok(1)
4340        }
4341        (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
4342        (_, b'=') if PAD => {
4343            if output.len() < 2 {
4344                return Err(DecodeError::OutputTooSmall {
4345                    required: 2,
4346                    available: output.len(),
4347                });
4348            }
4349            let v2 = decode_byte::<A>(b2, 2)?;
4350            if v2 & 0b0000_0011 != 0 {
4351                return Err(DecodeError::InvalidPadding { index: 2 });
4352            }
4353            output[0] = (v0 << 2) | (v1 >> 4);
4354            output[1] = (v1 << 4) | (v2 >> 2);
4355            Ok(2)
4356        }
4357        (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
4358            index: first_padding_index(input),
4359        }),
4360        _ => {
4361            if output.len() < 3 {
4362                return Err(DecodeError::OutputTooSmall {
4363                    required: 3,
4364                    available: output.len(),
4365                });
4366            }
4367            let v2 = decode_byte::<A>(b2, 2)?;
4368            let v3 = decode_byte::<A>(b3, 3)?;
4369            output[0] = (v0 << 2) | (v1 >> 4);
4370            output[1] = (v1 << 4) | (v2 >> 2);
4371            output[2] = (v2 << 6) | v3;
4372            Ok(3)
4373        }
4374    }
4375}
4376
4377fn validate_tail_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
4378    match input.len() {
4379        0 => Ok(()),
4380        2 => {
4381            decode_byte::<A>(input[0], 0)?;
4382            let v1 = decode_byte::<A>(input[1], 1)?;
4383            if v1 & 0b0000_1111 != 0 {
4384                return Err(DecodeError::InvalidPadding { index: 1 });
4385            }
4386            Ok(())
4387        }
4388        3 => {
4389            decode_byte::<A>(input[0], 0)?;
4390            decode_byte::<A>(input[1], 1)?;
4391            let v2 = decode_byte::<A>(input[2], 2)?;
4392            if v2 & 0b0000_0011 != 0 {
4393                return Err(DecodeError::InvalidPadding { index: 2 });
4394            }
4395            Ok(())
4396        }
4397        _ => Err(DecodeError::InvalidLength),
4398    }
4399}
4400
4401fn decode_tail_unpadded<A: Alphabet>(
4402    input: &[u8],
4403    output: &mut [u8],
4404) -> Result<usize, DecodeError> {
4405    match input.len() {
4406        0 => Ok(0),
4407        2 => {
4408            if output.is_empty() {
4409                return Err(DecodeError::OutputTooSmall {
4410                    required: 1,
4411                    available: output.len(),
4412                });
4413            }
4414            let v0 = decode_byte::<A>(input[0], 0)?;
4415            let v1 = decode_byte::<A>(input[1], 1)?;
4416            if v1 & 0b0000_1111 != 0 {
4417                return Err(DecodeError::InvalidPadding { index: 1 });
4418            }
4419            output[0] = (v0 << 2) | (v1 >> 4);
4420            Ok(1)
4421        }
4422        3 => {
4423            if output.len() < 2 {
4424                return Err(DecodeError::OutputTooSmall {
4425                    required: 2,
4426                    available: output.len(),
4427                });
4428            }
4429            let v0 = decode_byte::<A>(input[0], 0)?;
4430            let v1 = decode_byte::<A>(input[1], 1)?;
4431            let v2 = decode_byte::<A>(input[2], 2)?;
4432            if v2 & 0b0000_0011 != 0 {
4433                return Err(DecodeError::InvalidPadding { index: 2 });
4434            }
4435            output[0] = (v0 << 2) | (v1 >> 4);
4436            output[1] = (v1 << 4) | (v2 >> 2);
4437            Ok(2)
4438        }
4439        _ => Err(DecodeError::InvalidLength),
4440    }
4441}
4442
4443fn decode_byte<A: Alphabet>(byte: u8, index: usize) -> Result<u8, DecodeError> {
4444    A::decode(byte).ok_or(DecodeError::InvalidByte { index, byte })
4445}
4446
4447fn ct_decode_slice<A: Alphabet, const PAD: bool>(
4448    input: &[u8],
4449    output: &mut [u8],
4450) -> Result<usize, DecodeError> {
4451    if input.is_empty() {
4452        return Ok(0);
4453    }
4454
4455    if PAD {
4456        ct_decode_padded::<A>(input, output)
4457    } else {
4458        ct_decode_unpadded::<A>(input, output)
4459    }
4460}
4461
4462fn ct_decode_in_place<A: Alphabet, const PAD: bool>(
4463    buffer: &mut [u8],
4464) -> Result<usize, DecodeError> {
4465    if buffer.is_empty() {
4466        return Ok(0);
4467    }
4468
4469    if PAD {
4470        ct_decode_padded_in_place::<A>(buffer)
4471    } else {
4472        ct_decode_unpadded_in_place::<A>(buffer)
4473    }
4474}
4475
4476fn ct_validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<(), DecodeError> {
4477    if input.is_empty() {
4478        return Ok(());
4479    }
4480
4481    if PAD {
4482        ct_validate_padded::<A>(input)
4483    } else {
4484        ct_validate_unpadded::<A>(input)
4485    }
4486}
4487
4488fn ct_validate_padded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
4489    if !input.len().is_multiple_of(4) {
4490        return Err(DecodeError::InvalidLength);
4491    }
4492
4493    let padding = ct_padding_len(input);
4494    let mut invalid_byte = 0u8;
4495    let mut invalid_padding = 0u8;
4496    let mut read = 0;
4497
4498    while read + 4 < input.len() {
4499        let [b0, b1, b2, b3] = read_quad(input, read)?;
4500        let (_, valid0) = ct_decode_ascii_base64::<A>(b0);
4501        let (_, valid1) = ct_decode_ascii_base64::<A>(b1);
4502        let (_, valid2) = ct_decode_ascii_base64::<A>(b2);
4503        let (_, valid3) = ct_decode_ascii_base64::<A>(b3);
4504
4505        invalid_byte |= !valid0;
4506        invalid_byte |= !valid1;
4507        invalid_byte |= !valid2;
4508        invalid_byte |= !valid3;
4509        invalid_padding |= ct_mask_eq_u8(b2, b'=');
4510        invalid_padding |= ct_mask_eq_u8(b3, b'=');
4511        read += 4;
4512    }
4513
4514    let final_chunk = read_quad(input, read)?;
4515    let (_, final_invalid_byte, final_invalid_padding, _) =
4516        ct_padded_final_quantum::<A>(final_chunk, padding);
4517    invalid_byte |= final_invalid_byte;
4518    invalid_padding |= final_invalid_padding;
4519
4520    report_ct_error(invalid_byte, invalid_padding)
4521}
4522
4523fn ct_validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
4524    if input.len() % 4 == 1 {
4525        return Err(DecodeError::InvalidLength);
4526    }
4527
4528    let mut invalid_byte = 0u8;
4529    let mut invalid_padding = 0u8;
4530    let mut read = 0;
4531
4532    while read + 4 <= input.len() {
4533        let b0 = input[read];
4534        let b1 = input[read + 1];
4535        let b2 = input[read + 2];
4536        let b3 = input[read + 3];
4537        let (_, valid0) = ct_decode_ascii_base64::<A>(b0);
4538        let (_, valid1) = ct_decode_ascii_base64::<A>(b1);
4539        let (_, valid2) = ct_decode_ascii_base64::<A>(b2);
4540        let (_, valid3) = ct_decode_ascii_base64::<A>(b3);
4541
4542        invalid_byte |= !valid0;
4543        invalid_byte |= !valid1;
4544        invalid_byte |= !valid2;
4545        invalid_byte |= !valid3;
4546        invalid_padding |= ct_mask_eq_u8(b0, b'=');
4547        invalid_padding |= ct_mask_eq_u8(b1, b'=');
4548        invalid_padding |= ct_mask_eq_u8(b2, b'=');
4549        invalid_padding |= ct_mask_eq_u8(b3, b'=');
4550
4551        read += 4;
4552    }
4553
4554    match input.len() - read {
4555        0 => {}
4556        2 => {
4557            let b0 = input[read];
4558            let b1 = input[read + 1];
4559            let (_, valid0) = ct_decode_ascii_base64::<A>(b0);
4560            let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
4561            invalid_byte |= !valid0;
4562            invalid_byte |= !valid1;
4563            invalid_padding |= ct_mask_eq_u8(b0, b'=');
4564            invalid_padding |= ct_mask_eq_u8(b1, b'=');
4565            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
4566        }
4567        3 => {
4568            let b0 = input[read];
4569            let b1 = input[read + 1];
4570            let b2 = input[read + 2];
4571            let (_, valid0) = ct_decode_ascii_base64::<A>(b0);
4572            let (_, valid1) = ct_decode_ascii_base64::<A>(b1);
4573            let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
4574            invalid_byte |= !valid0;
4575            invalid_byte |= !valid1;
4576            invalid_byte |= !valid2;
4577            invalid_padding |= ct_mask_eq_u8(b0, b'=');
4578            invalid_padding |= ct_mask_eq_u8(b1, b'=');
4579            invalid_padding |= ct_mask_eq_u8(b2, b'=');
4580            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
4581        }
4582        _ => return Err(DecodeError::InvalidLength),
4583    }
4584
4585    report_ct_error(invalid_byte, invalid_padding)
4586}
4587
4588fn ct_padded_final_quantum<A: Alphabet>(
4589    input: [u8; 4],
4590    padding: usize,
4591) -> ([u8; 3], u8, u8, usize) {
4592    let [b0, b1, b2, b3] = input;
4593    let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
4594    let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
4595    let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
4596    let (v3, valid3) = ct_decode_ascii_base64::<A>(b3);
4597
4598    let padding_byte = padding.to_le_bytes()[0];
4599    let no_padding = ct_mask_eq_u8(padding_byte, 0);
4600    let one_padding = ct_mask_eq_u8(padding_byte, 1);
4601    let two_padding = ct_mask_eq_u8(padding_byte, 2);
4602    let require_v2 = no_padding | one_padding;
4603    let require_v3 = no_padding;
4604
4605    let invalid_byte = !valid0 | !valid1 | (!valid2 & require_v2) | (!valid3 & require_v3);
4606    let invalid_padding = (ct_mask_nonzero_u8(v1 & 0b0000_1111) & two_padding)
4607        | ((ct_mask_eq_u8(b2, b'=') | ct_mask_nonzero_u8(v2 & 0b0000_0011)) & one_padding)
4608        | ((ct_mask_eq_u8(b2, b'=') | ct_mask_eq_u8(b3, b'=')) & no_padding);
4609
4610    (
4611        [(v0 << 2) | (v1 >> 4), (v1 << 4) | (v2 >> 2), (v2 << 6) | v3],
4612        invalid_byte,
4613        invalid_padding,
4614        3 - padding,
4615    )
4616}
4617
4618fn ct_decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
4619    if !input.len().is_multiple_of(4) {
4620        return Err(DecodeError::InvalidLength);
4621    }
4622
4623    let padding = ct_padding_len(input);
4624    let required = input.len() / 4 * 3 - padding;
4625    if output.len() < required {
4626        return Err(DecodeError::OutputTooSmall {
4627            required,
4628            available: output.len(),
4629        });
4630    }
4631
4632    let mut invalid_byte = 0u8;
4633    let mut invalid_padding = 0u8;
4634    let mut write = 0;
4635    let mut read = 0;
4636
4637    while read + 4 < input.len() {
4638        let [b0, b1, b2, b3] = read_quad(input, read)?;
4639        let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
4640        let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
4641        let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
4642        let (v3, valid3) = ct_decode_ascii_base64::<A>(b3);
4643
4644        invalid_byte |= !valid0;
4645        invalid_byte |= !valid1;
4646        invalid_byte |= !valid2;
4647        invalid_byte |= !valid3;
4648        invalid_padding |= ct_mask_eq_u8(b2, b'=');
4649        invalid_padding |= ct_mask_eq_u8(b3, b'=');
4650        output[write] = (v0 << 2) | (v1 >> 4);
4651        output[write + 1] = (v1 << 4) | (v2 >> 2);
4652        output[write + 2] = (v2 << 6) | v3;
4653        write += 3;
4654        read += 4;
4655    }
4656
4657    let final_chunk = read_quad(input, read)?;
4658    let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
4659        ct_padded_final_quantum::<A>(final_chunk, padding);
4660    invalid_byte |= final_invalid_byte;
4661    invalid_padding |= final_invalid_padding;
4662    output[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
4663    write += final_written;
4664
4665    report_ct_error(invalid_byte, invalid_padding)?;
4666    Ok(write)
4667}
4668
4669fn ct_decode_padded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
4670    if !buffer.len().is_multiple_of(4) {
4671        return Err(DecodeError::InvalidLength);
4672    }
4673
4674    let padding = ct_padding_len(buffer);
4675    let required = buffer.len() / 4 * 3 - padding;
4676    debug_assert!(required <= buffer.len());
4677
4678    let mut invalid_byte = 0u8;
4679    let mut invalid_padding = 0u8;
4680    let mut write = 0;
4681    let mut read = 0;
4682
4683    while read + 4 < buffer.len() {
4684        let [b0, b1, b2, b3] = read_quad(buffer, read)?;
4685        let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
4686        let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
4687        let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
4688        let (v3, valid3) = ct_decode_ascii_base64::<A>(b3);
4689
4690        invalid_byte |= !valid0;
4691        invalid_byte |= !valid1;
4692        invalid_byte |= !valid2;
4693        invalid_byte |= !valid3;
4694        invalid_padding |= ct_mask_eq_u8(b2, b'=');
4695        invalid_padding |= ct_mask_eq_u8(b3, b'=');
4696        buffer[write] = (v0 << 2) | (v1 >> 4);
4697        buffer[write + 1] = (v1 << 4) | (v2 >> 2);
4698        buffer[write + 2] = (v2 << 6) | v3;
4699        write += 3;
4700        read += 4;
4701    }
4702
4703    let final_chunk = read_quad(buffer, read)?;
4704    let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
4705        ct_padded_final_quantum::<A>(final_chunk, padding);
4706    invalid_byte |= final_invalid_byte;
4707    invalid_padding |= final_invalid_padding;
4708    buffer[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
4709    write += final_written;
4710
4711    debug_assert_eq!(write, required);
4712    report_ct_error(invalid_byte, invalid_padding)?;
4713    Ok(write)
4714}
4715
4716fn ct_decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
4717    if input.len() % 4 == 1 {
4718        return Err(DecodeError::InvalidLength);
4719    }
4720
4721    let required = decoded_capacity(input.len());
4722    if output.len() < required {
4723        return Err(DecodeError::OutputTooSmall {
4724            required,
4725            available: output.len(),
4726        });
4727    }
4728
4729    let mut invalid_byte = 0u8;
4730    let mut invalid_padding = 0u8;
4731    let mut write = 0;
4732    let mut read = 0;
4733
4734    while read + 4 <= input.len() {
4735        let b0 = input[read];
4736        let b1 = input[read + 1];
4737        let b2 = input[read + 2];
4738        let b3 = input[read + 3];
4739        let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
4740        let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
4741        let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
4742        let (v3, valid3) = ct_decode_ascii_base64::<A>(b3);
4743
4744        invalid_byte |= !valid0;
4745        invalid_byte |= !valid1;
4746        invalid_byte |= !valid2;
4747        invalid_byte |= !valid3;
4748        invalid_padding |= ct_mask_eq_u8(b0, b'=');
4749        invalid_padding |= ct_mask_eq_u8(b1, b'=');
4750        invalid_padding |= ct_mask_eq_u8(b2, b'=');
4751        invalid_padding |= ct_mask_eq_u8(b3, b'=');
4752
4753        output[write] = (v0 << 2) | (v1 >> 4);
4754        output[write + 1] = (v1 << 4) | (v2 >> 2);
4755        output[write + 2] = (v2 << 6) | v3;
4756        read += 4;
4757        write += 3;
4758    }
4759
4760    match input.len() - read {
4761        0 => {}
4762        2 => {
4763            let b0 = input[read];
4764            let b1 = input[read + 1];
4765            let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
4766            let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
4767            invalid_byte |= !valid0;
4768            invalid_byte |= !valid1;
4769            invalid_padding |= ct_mask_eq_u8(b0, b'=');
4770            invalid_padding |= ct_mask_eq_u8(b1, b'=');
4771            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
4772            output[write] = (v0 << 2) | (v1 >> 4);
4773            write += 1;
4774        }
4775        3 => {
4776            let b0 = input[read];
4777            let b1 = input[read + 1];
4778            let b2 = input[read + 2];
4779            let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
4780            let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
4781            let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
4782            invalid_byte |= !valid0;
4783            invalid_byte |= !valid1;
4784            invalid_byte |= !valid2;
4785            invalid_padding |= ct_mask_eq_u8(b0, b'=');
4786            invalid_padding |= ct_mask_eq_u8(b1, b'=');
4787            invalid_padding |= ct_mask_eq_u8(b2, b'=');
4788            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
4789            output[write] = (v0 << 2) | (v1 >> 4);
4790            output[write + 1] = (v1 << 4) | (v2 >> 2);
4791            write += 2;
4792        }
4793        _ => return Err(DecodeError::InvalidLength),
4794    }
4795
4796    report_ct_error(invalid_byte, invalid_padding)?;
4797    Ok(write)
4798}
4799
4800fn ct_decode_unpadded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
4801    if buffer.len() % 4 == 1 {
4802        return Err(DecodeError::InvalidLength);
4803    }
4804
4805    let required = decoded_capacity(buffer.len());
4806    debug_assert!(required <= buffer.len());
4807
4808    let mut invalid_byte = 0u8;
4809    let mut invalid_padding = 0u8;
4810    let mut write = 0;
4811    let mut read = 0;
4812
4813    while read + 4 <= buffer.len() {
4814        let b0 = buffer[read];
4815        let b1 = buffer[read + 1];
4816        let b2 = buffer[read + 2];
4817        let b3 = buffer[read + 3];
4818        let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
4819        let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
4820        let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
4821        let (v3, valid3) = ct_decode_ascii_base64::<A>(b3);
4822
4823        invalid_byte |= !valid0;
4824        invalid_byte |= !valid1;
4825        invalid_byte |= !valid2;
4826        invalid_byte |= !valid3;
4827        invalid_padding |= ct_mask_eq_u8(b0, b'=');
4828        invalid_padding |= ct_mask_eq_u8(b1, b'=');
4829        invalid_padding |= ct_mask_eq_u8(b2, b'=');
4830        invalid_padding |= ct_mask_eq_u8(b3, b'=');
4831
4832        buffer[write] = (v0 << 2) | (v1 >> 4);
4833        buffer[write + 1] = (v1 << 4) | (v2 >> 2);
4834        buffer[write + 2] = (v2 << 6) | v3;
4835        read += 4;
4836        write += 3;
4837    }
4838
4839    match buffer.len() - read {
4840        0 => {}
4841        2 => {
4842            let b0 = buffer[read];
4843            let b1 = buffer[read + 1];
4844            let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
4845            let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
4846            invalid_byte |= !valid0;
4847            invalid_byte |= !valid1;
4848            invalid_padding |= ct_mask_eq_u8(b0, b'=');
4849            invalid_padding |= ct_mask_eq_u8(b1, b'=');
4850            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
4851            buffer[write] = (v0 << 2) | (v1 >> 4);
4852            write += 1;
4853        }
4854        3 => {
4855            let b0 = buffer[read];
4856            let b1 = buffer[read + 1];
4857            let b2 = buffer[read + 2];
4858            let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
4859            let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
4860            let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
4861            invalid_byte |= !valid0;
4862            invalid_byte |= !valid1;
4863            invalid_byte |= !valid2;
4864            invalid_padding |= ct_mask_eq_u8(b0, b'=');
4865            invalid_padding |= ct_mask_eq_u8(b1, b'=');
4866            invalid_padding |= ct_mask_eq_u8(b2, b'=');
4867            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
4868            buffer[write] = (v0 << 2) | (v1 >> 4);
4869            buffer[write + 1] = (v1 << 4) | (v2 >> 2);
4870            write += 2;
4871        }
4872        _ => return Err(DecodeError::InvalidLength),
4873    }
4874
4875    debug_assert_eq!(write, required);
4876    report_ct_error(invalid_byte, invalid_padding)?;
4877    Ok(write)
4878}
4879
4880#[inline]
4881fn ct_decode_ascii_base64<A: Alphabet>(byte: u8) -> (u8, u8) {
4882    let upper = ct_mask_lt_u8(byte.wrapping_sub(b'A'), 26);
4883    let lower = ct_mask_lt_u8(byte.wrapping_sub(b'a'), 26);
4884    let digit = ct_mask_lt_u8(byte.wrapping_sub(b'0'), 10);
4885    let value_62 = ct_mask_eq_u8(byte, A::ENCODE[62]);
4886    let value_63 = ct_mask_eq_u8(byte, A::ENCODE[63]);
4887    let valid = upper | lower | digit | value_62 | value_63;
4888
4889    let decoded = (byte.wrapping_sub(b'A') & upper)
4890        | (byte.wrapping_sub(b'a').wrapping_add(26) & lower)
4891        | (byte.wrapping_sub(b'0').wrapping_add(52) & digit)
4892        | (0x3e & value_62)
4893        | (0x3f & value_63);
4894
4895    (decoded, valid)
4896}
4897
4898fn ct_padding_len(input: &[u8]) -> usize {
4899    let last = input[input.len() - 1];
4900    let before_last = input[input.len() - 2];
4901    usize::from(ct_mask_eq_u8(last, b'=') & 1) + usize::from(ct_mask_eq_u8(before_last, b'=') & 1)
4902}
4903
4904fn report_ct_error(invalid_byte: u8, invalid_padding: u8) -> Result<(), DecodeError> {
4905    if (invalid_byte | invalid_padding) != 0 {
4906        Err(DecodeError::InvalidInput)
4907    } else {
4908        Ok(())
4909    }
4910}
4911
4912#[cfg(kani)]
4913mod kani_proofs {
4914    use super::{STANDARD, checked_encoded_len, ct, decoded_capacity};
4915
4916    #[kani::proof]
4917    fn checked_encoded_len_is_bounded_for_small_inputs() {
4918        let len = usize::from(kani::any::<u8>());
4919        let padded = kani::any::<bool>();
4920        let encoded = checked_encoded_len(len, padded).expect("u8 input length cannot overflow");
4921
4922        assert!(encoded >= len);
4923        assert!(encoded <= len / 3 * 4 + 4);
4924    }
4925
4926    #[kani::proof]
4927    fn decoded_capacity_is_bounded_for_small_inputs() {
4928        let len = usize::from(kani::any::<u8>());
4929        let capacity = decoded_capacity(len);
4930
4931        assert!(capacity <= len / 4 * 3 + 2);
4932    }
4933
4934    #[kani::proof]
4935    #[kani::unwind(3)]
4936    fn standard_in_place_decode_returns_prefix_within_buffer() {
4937        let mut buffer = kani::any::<[u8; 8]>();
4938        let result = STANDARD.decode_in_place(&mut buffer);
4939
4940        if let Ok(decoded) = result {
4941            assert!(decoded.len() <= 8);
4942        }
4943    }
4944
4945    #[kani::proof]
4946    #[kani::unwind(3)]
4947    fn standard_decode_slice_returns_written_within_output() {
4948        let input = kani::any::<[u8; 4]>();
4949        let mut output = kani::any::<[u8; 3]>();
4950        let result = STANDARD.decode_slice(&input, &mut output);
4951
4952        if let Ok(written) = result {
4953            assert!(written <= output.len());
4954        }
4955    }
4956
4957    #[kani::proof]
4958    #[kani::unwind(3)]
4959    fn standard_decode_slice_clear_tail_clears_output_on_error() {
4960        let input = kani::any::<[u8; 4]>();
4961        let mut output = kani::any::<[u8; 3]>();
4962        let result = STANDARD.decode_slice_clear_tail(&input, &mut output);
4963
4964        if result.is_err() {
4965            assert!(output.iter().all(|byte| *byte == 0));
4966        }
4967    }
4968
4969    #[kani::proof]
4970    #[kani::unwind(3)]
4971    fn standard_encode_slice_returns_written_within_output() {
4972        let input = kani::any::<[u8; 3]>();
4973        let mut output = kani::any::<[u8; 4]>();
4974        let result = STANDARD.encode_slice(&input, &mut output);
4975
4976        if let Ok(written) = result {
4977            assert!(written <= output.len());
4978        }
4979    }
4980
4981    #[kani::proof]
4982    #[kani::unwind(4)]
4983    fn standard_encode_in_place_returns_prefix_within_buffer() {
4984        let mut buffer = kani::any::<[u8; 8]>();
4985        let input_len = usize::from(kani::any::<u8>() % 9);
4986        let result = STANDARD.encode_in_place(&mut buffer, input_len);
4987
4988        if let Ok(encoded) = result {
4989            assert!(encoded.len() <= 8);
4990        }
4991    }
4992
4993    #[kani::proof]
4994    #[kani::unwind(3)]
4995    fn standard_clear_tail_decode_clears_buffer_on_error() {
4996        let mut buffer = kani::any::<[u8; 4]>();
4997        let result = STANDARD.decode_in_place_clear_tail(&mut buffer);
4998
4999        if result.is_err() {
5000            assert!(buffer.iter().all(|byte| *byte == 0));
5001        }
5002    }
5003
5004    #[kani::proof]
5005    #[kani::unwind(3)]
5006    fn ct_standard_decode_slice_returns_written_within_output() {
5007        let input = kani::any::<[u8; 4]>();
5008        let mut output = kani::any::<[u8; 3]>();
5009        let result = ct::STANDARD.decode_slice(&input, &mut output);
5010
5011        if let Ok(written) = result {
5012            assert!(written <= output.len());
5013        }
5014    }
5015
5016    #[kani::proof]
5017    #[kani::unwind(3)]
5018    fn ct_standard_decode_slice_clear_tail_clears_output_on_error() {
5019        let input = kani::any::<[u8; 4]>();
5020        let mut output = kani::any::<[u8; 3]>();
5021        let result = ct::STANDARD.decode_slice_clear_tail(&input, &mut output);
5022
5023        if result.is_err() {
5024            assert!(output.iter().all(|byte| *byte == 0));
5025        }
5026    }
5027
5028    #[kani::proof]
5029    #[kani::unwind(3)]
5030    fn ct_standard_decode_in_place_clear_tail_clears_buffer_on_error() {
5031        let mut buffer = kani::any::<[u8; 4]>();
5032        let result = ct::STANDARD.decode_in_place_clear_tail(&mut buffer);
5033
5034        if result.is_err() {
5035            assert!(buffer.iter().all(|byte| *byte == 0));
5036        }
5037    }
5038
5039    #[kani::proof]
5040    #[kani::unwind(3)]
5041    fn ct_standard_validate_matches_decode_for_one_quantum() {
5042        let input = kani::any::<[u8; 4]>();
5043        let mut output = kani::any::<[u8; 3]>();
5044
5045        let validate_ok = ct::STANDARD.validate_result(&input).is_ok();
5046        let decode_ok = ct::STANDARD.decode_slice(&input, &mut output).is_ok();
5047
5048        assert!(validate_ok == decode_ok);
5049    }
5050}
5051
5052#[cfg(test)]
5053mod tests {
5054    use super::*;
5055
5056    fn fill_pattern(output: &mut [u8], seed: usize) {
5057        for (index, byte) in output.iter_mut().enumerate() {
5058            let value = (index * 73 + seed * 19) % 256;
5059            *byte = u8::try_from(value).unwrap();
5060        }
5061    }
5062
5063    fn assert_encode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
5064    where
5065        A: Alphabet,
5066    {
5067        let engine = Engine::<A, PAD>::new();
5068        let mut dispatched = [0x55; 256];
5069        let mut scalar = [0xaa; 256];
5070
5071        let dispatched_result = engine.encode_slice(input, &mut dispatched);
5072        let scalar_result = backend::scalar_reference_encode_slice::<A, PAD>(input, &mut scalar);
5073
5074        assert_eq!(dispatched_result, scalar_result);
5075        if let Ok(written) = dispatched_result {
5076            assert_eq!(&dispatched[..written], &scalar[..written]);
5077        }
5078
5079        let required = checked_encoded_len(input.len(), PAD).unwrap();
5080        if required > 0 {
5081            let mut dispatched_short = [0x55; 256];
5082            let mut scalar_short = [0xaa; 256];
5083            let available = required - 1;
5084
5085            assert_eq!(
5086                engine.encode_slice(input, &mut dispatched_short[..available]),
5087                backend::scalar_reference_encode_slice::<A, PAD>(
5088                    input,
5089                    &mut scalar_short[..available],
5090                )
5091            );
5092        }
5093    }
5094
5095    fn assert_decode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
5096    where
5097        A: Alphabet,
5098    {
5099        let engine = Engine::<A, PAD>::new();
5100        let mut dispatched = [0x55; 128];
5101        let mut scalar = [0xaa; 128];
5102
5103        let dispatched_result = engine.decode_slice(input, &mut dispatched);
5104        let scalar_result = backend::scalar_reference_decode_slice::<A, PAD>(input, &mut scalar);
5105
5106        assert_eq!(dispatched_result, scalar_result);
5107        if let Ok(written) = dispatched_result {
5108            assert_eq!(&dispatched[..written], &scalar[..written]);
5109
5110            if written > 0 {
5111                let mut dispatched_short = [0x55; 128];
5112                let mut scalar_short = [0xaa; 128];
5113                let available = written - 1;
5114
5115                assert_eq!(
5116                    engine.decode_slice(input, &mut dispatched_short[..available]),
5117                    backend::scalar_reference_decode_slice::<A, PAD>(
5118                        input,
5119                        &mut scalar_short[..available],
5120                    )
5121                );
5122            }
5123        }
5124    }
5125
5126    fn assert_backend_round_trip_matches_scalar<A, const PAD: bool>(input: &[u8])
5127    where
5128        A: Alphabet,
5129    {
5130        assert_encode_backend_matches_scalar::<A, PAD>(input);
5131
5132        let mut encoded = [0; 256];
5133        let encoded_len =
5134            backend::scalar_reference_encode_slice::<A, PAD>(input, &mut encoded).unwrap();
5135        assert_decode_backend_matches_scalar::<A, PAD>(&encoded[..encoded_len]);
5136    }
5137
5138    #[test]
5139    fn backend_dispatch_matches_scalar_reference_for_canonical_inputs() {
5140        let mut input = [0; 128];
5141
5142        for input_len in 0..=input.len() {
5143            fill_pattern(&mut input[..input_len], input_len);
5144            let input = &input[..input_len];
5145
5146            assert_backend_round_trip_matches_scalar::<Standard, true>(input);
5147            assert_backend_round_trip_matches_scalar::<Standard, false>(input);
5148            assert_backend_round_trip_matches_scalar::<UrlSafe, true>(input);
5149            assert_backend_round_trip_matches_scalar::<UrlSafe, false>(input);
5150        }
5151    }
5152
5153    #[test]
5154    fn backend_dispatch_matches_scalar_reference_for_malformed_inputs() {
5155        for input in [
5156            &b"Z"[..],
5157            b"====",
5158            b"AA=A",
5159            b"Zh==",
5160            b"Zm9=",
5161            b"Zm9v$g==",
5162            b"Zm9vZh==",
5163        ] {
5164            assert_decode_backend_matches_scalar::<Standard, true>(input);
5165        }
5166
5167        for input in [&b"Z"[..], b"AA=A", b"Zh", b"Zm9", b"Zm9vYg$"] {
5168            assert_decode_backend_matches_scalar::<Standard, false>(input);
5169        }
5170
5171        assert_decode_backend_matches_scalar::<UrlSafe, true>(b"AA+A");
5172        assert_decode_backend_matches_scalar::<UrlSafe, false>(b"AA/A");
5173        assert_decode_backend_matches_scalar::<Standard, true>(b"AA-A");
5174        assert_decode_backend_matches_scalar::<Standard, false>(b"AA_A");
5175    }
5176
5177    #[cfg(feature = "simd")]
5178    #[test]
5179    fn simd_dispatch_scaffold_keeps_scalar_active() {
5180        assert_eq!(simd::active_backend(), simd::ActiveBackend::Scalar);
5181        let _candidate = simd::detected_candidate();
5182    }
5183
5184    #[test]
5185    fn encodes_standard_vectors() {
5186        let vectors = [
5187            (&b""[..], &b""[..]),
5188            (&b"f"[..], &b"Zg=="[..]),
5189            (&b"fo"[..], &b"Zm8="[..]),
5190            (&b"foo"[..], &b"Zm9v"[..]),
5191            (&b"foob"[..], &b"Zm9vYg=="[..]),
5192            (&b"fooba"[..], &b"Zm9vYmE="[..]),
5193            (&b"foobar"[..], &b"Zm9vYmFy"[..]),
5194        ];
5195        for (input, expected) in vectors {
5196            let mut output = [0u8; 16];
5197            let written = STANDARD.encode_slice(input, &mut output).unwrap();
5198            assert_eq!(&output[..written], expected);
5199        }
5200    }
5201
5202    #[test]
5203    fn decodes_standard_vectors() {
5204        let vectors = [
5205            (&b""[..], &b""[..]),
5206            (&b"Zg=="[..], &b"f"[..]),
5207            (&b"Zm8="[..], &b"fo"[..]),
5208            (&b"Zm9v"[..], &b"foo"[..]),
5209            (&b"Zm9vYg=="[..], &b"foob"[..]),
5210            (&b"Zm9vYmE="[..], &b"fooba"[..]),
5211            (&b"Zm9vYmFy"[..], &b"foobar"[..]),
5212        ];
5213        for (input, expected) in vectors {
5214            let mut output = [0u8; 16];
5215            let written = STANDARD.decode_slice(input, &mut output).unwrap();
5216            assert_eq!(&output[..written], expected);
5217        }
5218    }
5219
5220    #[test]
5221    fn supports_unpadded_url_safe() {
5222        let mut encoded = [0u8; 16];
5223        let written = URL_SAFE_NO_PAD
5224            .encode_slice(b"\xfb\xff", &mut encoded)
5225            .unwrap();
5226        assert_eq!(&encoded[..written], b"-_8");
5227
5228        let mut decoded = [0u8; 2];
5229        let written = URL_SAFE_NO_PAD
5230            .decode_slice(&encoded[..written], &mut decoded)
5231            .unwrap();
5232        assert_eq!(&decoded[..written], b"\xfb\xff");
5233    }
5234
5235    #[test]
5236    fn decodes_in_place() {
5237        let mut buffer = *b"Zm9vYmFy";
5238        let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
5239        assert_eq!(decoded, b"foobar");
5240    }
5241
5242    #[test]
5243    fn rejects_non_canonical_padding_bits() {
5244        let mut output = [0u8; 4];
5245        assert_eq!(
5246            STANDARD.decode_slice(b"Zh==", &mut output),
5247            Err(DecodeError::InvalidPadding { index: 1 })
5248        );
5249        assert_eq!(
5250            STANDARD.decode_slice(b"Zm9=", &mut output),
5251            Err(DecodeError::InvalidPadding { index: 2 })
5252        );
5253    }
5254}