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 will be required to
12//! match this scalar module byte-for-byte.
13//!
14//! # Examples
15//!
16//! Encode and decode with caller-owned buffers:
17//!
18//! ```
19//! use base64_ng::{STANDARD, checked_encoded_len};
20//!
21//! let input = b"hello";
22//! const ENCODED_CAPACITY: usize = match checked_encoded_len(5, true) {
23//!     Some(len) => len,
24//!     None => panic!("encoded length overflow"),
25//! };
26//! let mut encoded = [0u8; ENCODED_CAPACITY];
27//! let encoded_len = STANDARD.encode_slice(input, &mut encoded).unwrap();
28//! assert_eq!(&encoded[..encoded_len], b"aGVsbG8=");
29//!
30//! let mut decoded = [0u8; 5];
31//! let decoded_len = STANDARD.decode_slice(&encoded, &mut decoded).unwrap();
32//! assert_eq!(&decoded[..decoded_len], input);
33//! ```
34//!
35//! Use the URL-safe no-padding engine:
36//!
37//! ```
38//! use base64_ng::URL_SAFE_NO_PAD;
39//!
40//! let mut encoded = [0u8; 3];
41//! let encoded_len = URL_SAFE_NO_PAD.encode_slice(b"\xfb\xff", &mut encoded).unwrap();
42//! assert_eq!(&encoded[..encoded_len], b"-_8");
43//! ```
44
45#[cfg(feature = "alloc")]
46extern crate alloc;
47
48#[cfg(feature = "simd")]
49mod simd;
50
51/// Runtime backend reporting for security-sensitive deployments.
52///
53/// This module does not enable acceleration. It exposes the backend posture so
54/// callers can log, assert, or audit whether execution is scalar-only or merely
55/// detecting future SIMD candidates.
56pub mod runtime {
57    /// A backend that can be reported by `base64-ng`.
58    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
59    #[non_exhaustive]
60    pub enum Backend {
61        /// The audited scalar backend.
62        Scalar,
63        /// An AVX-512 VBMI candidate was detected.
64        Avx512Vbmi,
65        /// An AVX2 candidate was detected.
66        Avx2,
67        /// An ARM NEON candidate was detected.
68        Neon,
69    }
70
71    impl Backend {
72        /// Returns the stable lowercase identifier for this backend.
73        ///
74        /// ```
75        /// assert_eq!(base64_ng::runtime::Backend::Scalar.as_str(), "scalar");
76        /// ```
77        #[must_use]
78        pub const fn as_str(self) -> &'static str {
79            match self {
80                Self::Scalar => "scalar",
81                Self::Avx512Vbmi => "avx512-vbmi",
82                Self::Avx2 => "avx2",
83                Self::Neon => "neon",
84            }
85        }
86
87        /// Returns the CPU features required before this backend may be used.
88        ///
89        /// The active backend is still scalar-only. This method exists so
90        /// security logs can record exactly which future backend feature bundle
91        /// was detected.
92        ///
93        /// ```
94        /// assert_eq!(
95        ///     base64_ng::runtime::Backend::Avx512Vbmi.required_cpu_features(),
96        ///     ["avx512f", "avx512bw", "avx512vl", "avx512vbmi"],
97        /// );
98        /// ```
99        #[must_use]
100        pub const fn required_cpu_features(self) -> &'static [&'static str] {
101            match self {
102                Self::Scalar => &[],
103                Self::Avx512Vbmi => &["avx512f", "avx512bw", "avx512vl", "avx512vbmi"],
104                Self::Avx2 => &["avx2"],
105                Self::Neon => &["neon"],
106            }
107        }
108    }
109
110    impl core::fmt::Display for Backend {
111        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
112            formatter.write_str(self.as_str())
113        }
114    }
115
116    /// Security posture for the active runtime backend.
117    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
118    #[non_exhaustive]
119    pub enum SecurityPosture {
120        /// No accelerated backend is active.
121        ScalarOnly,
122        /// SIMD support may be detected, but execution still uses scalar.
123        SimdCandidateScalarActive,
124        /// A SIMD backend is active.
125        Accelerated,
126    }
127
128    impl SecurityPosture {
129        /// Returns the stable lowercase identifier for this security posture.
130        ///
131        /// ```
132        /// assert_eq!(
133        ///     base64_ng::runtime::SecurityPosture::ScalarOnly.as_str(),
134        ///     "scalar-only",
135        /// );
136        /// ```
137        #[must_use]
138        pub const fn as_str(self) -> &'static str {
139            match self {
140                Self::ScalarOnly => "scalar-only",
141                Self::SimdCandidateScalarActive => "simd-candidate-scalar-active",
142                Self::Accelerated => "accelerated",
143            }
144        }
145    }
146
147    impl core::fmt::Display for SecurityPosture {
148        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
149            formatter.write_str(self.as_str())
150        }
151    }
152
153    /// Deployment policy for runtime backend assertions.
154    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
155    #[non_exhaustive]
156    pub enum BackendPolicy {
157        /// Require encode/decode execution to use the scalar backend.
158        ScalarExecutionOnly,
159        /// Require the crate to be built without the `simd` feature.
160        SimdFeatureDisabled,
161        /// Require no SIMD candidate to be visible to this build and target.
162        NoDetectedSimdCandidate,
163        /// Require scalar execution, the `simd` feature disabled, no detected
164        /// SIMD candidate, and the unsafe boundary enforced.
165        HighAssuranceScalarOnly,
166    }
167
168    impl BackendPolicy {
169        /// Returns the stable lowercase identifier for this policy.
170        ///
171        /// ```
172        /// assert_eq!(
173        ///     base64_ng::runtime::BackendPolicy::HighAssuranceScalarOnly.as_str(),
174        ///     "high-assurance-scalar-only",
175        /// );
176        /// ```
177        #[must_use]
178        pub const fn as_str(self) -> &'static str {
179            match self {
180                Self::ScalarExecutionOnly => "scalar-execution-only",
181                Self::SimdFeatureDisabled => "simd-feature-disabled",
182                Self::NoDetectedSimdCandidate => "no-detected-simd-candidate",
183                Self::HighAssuranceScalarOnly => "high-assurance-scalar-only",
184            }
185        }
186    }
187
188    impl core::fmt::Display for BackendPolicy {
189        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
190            formatter.write_str(self.as_str())
191        }
192    }
193
194    /// Runtime backend policy failure.
195    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
196    pub struct BackendPolicyError {
197        /// Policy that was requested.
198        pub policy: BackendPolicy,
199        /// Backend report observed when the policy failed.
200        pub report: BackendReport,
201    }
202
203    impl core::fmt::Display for BackendPolicyError {
204        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
205            write!(
206                formatter,
207                "runtime backend policy `{}` was not satisfied ({})",
208                self.policy, self.report,
209            )
210        }
211    }
212
213    #[cfg(feature = "std")]
214    impl std::error::Error for BackendPolicyError {}
215
216    /// Backend report for the current build and target.
217    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
218    pub struct BackendReport {
219        /// Backend currently used for encode/decode dispatch.
220        pub active: Backend,
221        /// Strongest backend candidate visible to the current build.
222        pub candidate: Backend,
223        /// Whether the `simd` feature is enabled in this build.
224        pub simd_feature_enabled: bool,
225        /// Whether an accelerated SIMD backend is active.
226        pub accelerated_backend_active: bool,
227        /// Whether unsafe code is confined to the dedicated SIMD boundary.
228        pub unsafe_boundary_enforced: bool,
229        /// Current security posture.
230        pub security_posture: SecurityPosture,
231    }
232
233    /// Compact structured backend snapshot for logging and policy evidence.
234    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
235    pub struct BackendSnapshot {
236        /// Stable active backend identifier.
237        pub active: &'static str,
238        /// Stable detected candidate identifier.
239        pub candidate: &'static str,
240        /// CPU features required by the detected candidate.
241        pub candidate_required_cpu_features: &'static [&'static str],
242        /// Whether the `simd` feature is enabled in this build.
243        pub simd_feature_enabled: bool,
244        /// Whether an accelerated SIMD backend is active.
245        pub accelerated_backend_active: bool,
246        /// Whether unsafe code is confined to the dedicated SIMD boundary.
247        pub unsafe_boundary_enforced: bool,
248        /// Stable security posture identifier.
249        pub security_posture: &'static str,
250    }
251
252    impl core::fmt::Display for BackendReport {
253        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
254            write!(
255                formatter,
256                "active={} candidate={} candidate_required_cpu_features=",
257                self.active, self.candidate,
258            )?;
259            write_feature_list(formatter, self.candidate_required_cpu_features())?;
260            write!(
261                formatter,
262                " simd_feature_enabled={} accelerated_backend_active={} unsafe_boundary_enforced={} security_posture={}",
263                self.simd_feature_enabled,
264                self.accelerated_backend_active,
265                self.unsafe_boundary_enforced,
266                self.security_posture,
267            )
268        }
269    }
270
271    impl BackendReport {
272        /// Returns whether this report satisfies `policy`.
273        ///
274        /// ```
275        /// let report = base64_ng::runtime::backend_report();
276        ///
277        /// assert!(
278        ///     report.satisfies(base64_ng::runtime::BackendPolicy::ScalarExecutionOnly)
279        /// );
280        /// ```
281        #[must_use]
282        pub const fn satisfies(self, policy: BackendPolicy) -> bool {
283            match policy {
284                BackendPolicy::ScalarExecutionOnly => {
285                    matches!(self.active, Backend::Scalar) && !self.accelerated_backend_active
286                }
287                BackendPolicy::SimdFeatureDisabled => !self.simd_feature_enabled,
288                BackendPolicy::NoDetectedSimdCandidate => matches!(self.candidate, Backend::Scalar),
289                BackendPolicy::HighAssuranceScalarOnly => {
290                    matches!(self.active, Backend::Scalar)
291                        && matches!(self.candidate, Backend::Scalar)
292                        && !self.simd_feature_enabled
293                        && !self.accelerated_backend_active
294                        && self.unsafe_boundary_enforced
295                }
296            }
297        }
298
299        /// Returns the CPU features required by the detected candidate.
300        ///
301        /// ```
302        /// let report = base64_ng::runtime::backend_report();
303        ///
304        /// assert_eq!(
305        ///     report.candidate_required_cpu_features(),
306        ///     report.candidate.required_cpu_features(),
307        /// );
308        /// ```
309        #[must_use]
310        pub const fn candidate_required_cpu_features(self) -> &'static [&'static str] {
311            self.candidate.required_cpu_features()
312        }
313
314        /// Returns a compact structured snapshot with stable string values.
315        ///
316        /// ```
317        /// let snapshot = base64_ng::runtime::backend_report().snapshot();
318        ///
319        /// assert_eq!(snapshot.active, "scalar");
320        /// assert!(!snapshot.accelerated_backend_active);
321        /// ```
322        #[must_use]
323        pub const fn snapshot(self) -> BackendSnapshot {
324            BackendSnapshot {
325                active: self.active.as_str(),
326                candidate: self.candidate.as_str(),
327                candidate_required_cpu_features: self.candidate_required_cpu_features(),
328                simd_feature_enabled: self.simd_feature_enabled,
329                accelerated_backend_active: self.accelerated_backend_active,
330                unsafe_boundary_enforced: self.unsafe_boundary_enforced,
331                security_posture: self.security_posture.as_str(),
332            }
333        }
334    }
335
336    /// Returns the runtime backend report for this build and target.
337    ///
338    /// ```
339    /// let report = base64_ng::runtime::backend_report();
340    ///
341    /// assert_eq!(report.active, base64_ng::runtime::Backend::Scalar);
342    /// assert!(!report.accelerated_backend_active);
343    /// ```
344    #[must_use]
345    pub fn backend_report() -> BackendReport {
346        let active = active_backend();
347        let candidate = detected_candidate();
348        let accelerated_backend_active = active != Backend::Scalar;
349        let security_posture = if accelerated_backend_active {
350            SecurityPosture::Accelerated
351        } else if candidate != Backend::Scalar {
352            SecurityPosture::SimdCandidateScalarActive
353        } else {
354            SecurityPosture::ScalarOnly
355        };
356
357        BackendReport {
358            active,
359            candidate,
360            simd_feature_enabled: cfg!(feature = "simd"),
361            accelerated_backend_active,
362            unsafe_boundary_enforced: true,
363            security_posture,
364        }
365    }
366
367    /// Requires the current runtime backend report to satisfy `policy`.
368    ///
369    /// ```
370    /// base64_ng::runtime::require_backend_policy(
371    ///     base64_ng::runtime::BackendPolicy::ScalarExecutionOnly,
372    /// )
373    /// .unwrap();
374    /// ```
375    pub fn require_backend_policy(policy: BackendPolicy) -> Result<(), BackendPolicyError> {
376        let report = backend_report();
377        if report.satisfies(policy) {
378            Ok(())
379        } else {
380            Err(BackendPolicyError { policy, report })
381        }
382    }
383
384    fn write_feature_list(
385        formatter: &mut core::fmt::Formatter<'_>,
386        features: &[&str],
387    ) -> core::fmt::Result {
388        formatter.write_str("[")?;
389        let mut index = 0;
390        while index < features.len() {
391            if index != 0 {
392                formatter.write_str(",")?;
393            }
394            formatter.write_str(features[index])?;
395            index += 1;
396        }
397        formatter.write_str("]")
398    }
399
400    #[cfg(feature = "simd")]
401    fn active_backend() -> Backend {
402        match super::simd::active_backend() {
403            super::simd::ActiveBackend::Scalar => Backend::Scalar,
404        }
405    }
406
407    #[cfg(not(feature = "simd"))]
408    const fn active_backend() -> Backend {
409        Backend::Scalar
410    }
411
412    #[cfg(feature = "simd")]
413    fn detected_candidate() -> Backend {
414        match super::simd::detected_candidate() {
415            super::simd::Candidate::Scalar => Backend::Scalar,
416            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
417            super::simd::Candidate::Avx512Vbmi => Backend::Avx512Vbmi,
418            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
419            super::simd::Candidate::Avx2 => Backend::Avx2,
420            #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
421            super::simd::Candidate::Neon => Backend::Neon,
422        }
423    }
424
425    #[cfg(not(feature = "simd"))]
426    const fn detected_candidate() -> Backend {
427        Backend::Scalar
428    }
429}
430
431#[cfg(feature = "stream")]
432pub mod stream {
433    //! Streaming Base64 wrappers for `std::io`.
434    //!
435    //! ```
436    //! use std::io::{Read, Write};
437    //! use base64_ng::{STANDARD, stream::{Decoder, DecoderReader, Encoder, EncoderReader}};
438    //!
439    //! let mut encoder = Encoder::new(Vec::new(), STANDARD);
440    //! encoder.write_all(b"he").unwrap();
441    //! encoder.write_all(b"llo").unwrap();
442    //! let encoded = encoder.finish().unwrap();
443    //! assert_eq!(encoded, b"aGVsbG8=");
444    //!
445    //! let mut reader = EncoderReader::new(&b"hello"[..], STANDARD);
446    //! let mut encoded = String::new();
447    //! reader.read_to_string(&mut encoded).unwrap();
448    //! assert_eq!(encoded, "aGVsbG8=");
449    //!
450    //! let mut decoder = Decoder::new(Vec::new(), STANDARD);
451    //! decoder.write_all(b"aGVs").unwrap();
452    //! decoder.write_all(b"bG8=").unwrap();
453    //! let decoded = decoder.finish().unwrap();
454    //! assert_eq!(decoded, b"hello");
455    //!
456    //! let mut reader = DecoderReader::new(&b"aGVsbG8="[..], STANDARD);
457    //! let mut decoded = Vec::new();
458    //! reader.read_to_end(&mut decoded).unwrap();
459    //! assert_eq!(decoded, b"hello");
460    //! ```
461
462    use super::{Alphabet, DecodeError, EncodeError, Engine};
463    use std::io::{self, Read, Write};
464
465    struct OutputQueue<const CAP: usize> {
466        buffer: [u8; CAP],
467        start: usize,
468        len: usize,
469    }
470
471    impl<const CAP: usize> OutputQueue<CAP> {
472        const fn new() -> Self {
473            Self {
474                buffer: [0; CAP],
475                start: 0,
476                len: 0,
477            }
478        }
479
480        const fn is_empty(&self) -> bool {
481            self.len == 0
482        }
483
484        fn push_slice(&mut self, input: &[u8]) -> io::Result<()> {
485            if input.len() > self.available_capacity() {
486                return Err(io::Error::new(
487                    io::ErrorKind::InvalidInput,
488                    "base64 stream output queue capacity exceeded",
489                ));
490            }
491
492            let mut read = 0;
493            while read < input.len() {
494                let write = (self.start + self.len) % CAP;
495                self.buffer[write] = input[read];
496                self.len += 1;
497                read += 1;
498            }
499
500            Ok(())
501        }
502
503        fn pop_front(&mut self) -> Option<u8> {
504            if self.len == 0 {
505                return None;
506            }
507
508            let byte = self.buffer[self.start];
509            self.buffer[self.start] = 0;
510            self.start = (self.start + 1) % CAP;
511            self.len -= 1;
512            if self.len == 0 {
513                self.start = 0;
514            }
515            Some(byte)
516        }
517
518        fn clear_all(&mut self) {
519            self.buffer.fill(0);
520            self.start = 0;
521            self.len = 0;
522        }
523
524        const fn available_capacity(&self) -> usize {
525            CAP - self.len
526        }
527    }
528
529    /// A streaming Base64 encoder for `std::io::Write`.
530    pub struct Encoder<W, A, const PAD: bool>
531    where
532        A: Alphabet,
533    {
534        inner: Option<W>,
535        engine: Engine<A, PAD>,
536        pending: [u8; 2],
537        pending_len: usize,
538    }
539
540    impl<W, A, const PAD: bool> Encoder<W, A, PAD>
541    where
542        A: Alphabet,
543    {
544        /// Creates a new streaming encoder.
545        #[must_use]
546        pub const fn new(inner: W, engine: Engine<A, PAD>) -> Self {
547            Self {
548                inner: Some(inner),
549                engine,
550                pending: [0; 2],
551                pending_len: 0,
552            }
553        }
554
555        /// Returns a shared reference to the wrapped writer.
556        #[must_use]
557        pub fn get_ref(&self) -> &W {
558            self.inner_ref()
559        }
560
561        /// Returns a mutable reference to the wrapped writer.
562        pub fn get_mut(&mut self) -> &mut W {
563            self.inner_mut()
564        }
565
566        /// Consumes the encoder without flushing pending input.
567        ///
568        /// Prefer [`Self::finish`] when the encoded output must be complete.
569        #[must_use]
570        pub fn into_inner(mut self) -> W {
571            self.take_inner()
572        }
573
574        fn inner_ref(&self) -> &W {
575            match &self.inner {
576                Some(inner) => inner,
577                None => unreachable!("stream encoder inner writer was already taken"),
578            }
579        }
580
581        fn inner_mut(&mut self) -> &mut W {
582            match &mut self.inner {
583                Some(inner) => inner,
584                None => unreachable!("stream encoder inner writer was already taken"),
585            }
586        }
587
588        fn take_inner(&mut self) -> W {
589            match self.inner.take() {
590                Some(inner) => inner,
591                None => unreachable!("stream encoder inner writer was already taken"),
592            }
593        }
594
595        fn clear_pending(&mut self) {
596            self.pending.fill(0);
597            self.pending_len = 0;
598        }
599    }
600
601    impl<W, A, const PAD: bool> Drop for Encoder<W, A, PAD>
602    where
603        A: Alphabet,
604    {
605        fn drop(&mut self) {
606            self.clear_pending();
607        }
608    }
609
610    impl<W, A, const PAD: bool> Encoder<W, A, PAD>
611    where
612        W: Write,
613        A: Alphabet,
614    {
615        /// Writes any pending input, flushes the wrapped writer, and returns it.
616        pub fn finish(mut self) -> io::Result<W> {
617            self.write_pending_final()?;
618            self.inner_mut().flush()?;
619            Ok(self.take_inner())
620        }
621
622        fn write_pending_final(&mut self) -> io::Result<()> {
623            if self.pending_len == 0 {
624                return Ok(());
625            }
626
627            let mut encoded = [0u8; 4];
628            let written = self
629                .engine
630                .encode_slice(&self.pending[..self.pending_len], &mut encoded)
631                .map_err(encode_error_to_io)?;
632            self.inner_mut().write_all(&encoded[..written])?;
633            self.clear_pending();
634            Ok(())
635        }
636    }
637
638    impl<W, A, const PAD: bool> Write for Encoder<W, A, PAD>
639    where
640        W: Write,
641        A: Alphabet,
642    {
643        fn write(&mut self, input: &[u8]) -> io::Result<usize> {
644            if input.is_empty() {
645                return Ok(0);
646            }
647
648            let mut consumed = 0;
649            if self.pending_len > 0 {
650                let needed = 3 - self.pending_len;
651                if input.len() < needed {
652                    self.pending[self.pending_len..self.pending_len + input.len()]
653                        .copy_from_slice(input);
654                    self.pending_len += input.len();
655                    return Ok(input.len());
656                }
657
658                let mut chunk = [0u8; 3];
659                chunk[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
660                chunk[self.pending_len..].copy_from_slice(&input[..needed]);
661
662                let mut encoded = [0u8; 4];
663                let written = self
664                    .engine
665                    .encode_slice(&chunk, &mut encoded)
666                    .map_err(encode_error_to_io)?;
667                self.inner_mut().write_all(&encoded[..written])?;
668                self.clear_pending();
669                consumed += needed;
670            }
671
672            let remaining = &input[consumed..];
673            let full_len = remaining.len() / 3 * 3;
674            let mut offset = 0;
675            let mut encoded = [0u8; 1024];
676            while offset < full_len {
677                let mut take = core::cmp::min(full_len - offset, 768);
678                take -= take % 3;
679                debug_assert!(take > 0);
680
681                let written = self
682                    .engine
683                    .encode_slice(&remaining[offset..offset + take], &mut encoded)
684                    .map_err(encode_error_to_io)?;
685                self.inner_mut().write_all(&encoded[..written])?;
686                offset += take;
687            }
688
689            let tail = &remaining[full_len..];
690            self.pending[..tail.len()].copy_from_slice(tail);
691            self.pending_len = tail.len();
692
693            Ok(input.len())
694        }
695
696        fn flush(&mut self) -> io::Result<()> {
697            self.inner_mut().flush()
698        }
699    }
700
701    fn encode_error_to_io(err: EncodeError) -> io::Error {
702        io::Error::new(io::ErrorKind::InvalidInput, err)
703    }
704
705    /// A streaming Base64 decoder for `std::io::Write`.
706    pub struct Decoder<W, A, const PAD: bool>
707    where
708        A: Alphabet,
709    {
710        inner: Option<W>,
711        engine: Engine<A, PAD>,
712        pending: [u8; 4],
713        pending_len: usize,
714        finished: bool,
715    }
716
717    impl<W, A, const PAD: bool> Decoder<W, A, PAD>
718    where
719        A: Alphabet,
720    {
721        /// Creates a new streaming decoder.
722        #[must_use]
723        pub const fn new(inner: W, engine: Engine<A, PAD>) -> Self {
724            Self {
725                inner: Some(inner),
726                engine,
727                pending: [0; 4],
728                pending_len: 0,
729                finished: false,
730            }
731        }
732
733        /// Returns a shared reference to the wrapped writer.
734        #[must_use]
735        pub fn get_ref(&self) -> &W {
736            self.inner_ref()
737        }
738
739        /// Returns a mutable reference to the wrapped writer.
740        pub fn get_mut(&mut self) -> &mut W {
741            self.inner_mut()
742        }
743
744        /// Consumes the decoder without flushing pending input.
745        ///
746        /// Prefer [`Self::finish`] when the decoded output must be complete.
747        #[must_use]
748        pub fn into_inner(mut self) -> W {
749            self.take_inner()
750        }
751
752        fn inner_ref(&self) -> &W {
753            match &self.inner {
754                Some(inner) => inner,
755                None => unreachable!("stream decoder inner writer was already taken"),
756            }
757        }
758
759        fn inner_mut(&mut self) -> &mut W {
760            match &mut self.inner {
761                Some(inner) => inner,
762                None => unreachable!("stream decoder inner writer was already taken"),
763            }
764        }
765
766        fn take_inner(&mut self) -> W {
767            match self.inner.take() {
768                Some(inner) => inner,
769                None => unreachable!("stream decoder inner writer was already taken"),
770            }
771        }
772
773        fn clear_pending(&mut self) {
774            self.pending.fill(0);
775            self.pending_len = 0;
776        }
777    }
778
779    impl<W, A, const PAD: bool> Drop for Decoder<W, A, PAD>
780    where
781        A: Alphabet,
782    {
783        fn drop(&mut self) {
784            self.clear_pending();
785        }
786    }
787
788    impl<W, A, const PAD: bool> Decoder<W, A, PAD>
789    where
790        W: Write,
791        A: Alphabet,
792    {
793        /// Validates final pending input, flushes the wrapped writer, and returns it.
794        pub fn finish(mut self) -> io::Result<W> {
795            self.write_pending_final()?;
796            self.inner_mut().flush()?;
797            Ok(self.take_inner())
798        }
799
800        fn write_pending_final(&mut self) -> io::Result<()> {
801            if self.pending_len == 0 {
802                return Ok(());
803            }
804
805            let mut decoded = [0u8; 3];
806            let written = self
807                .engine
808                .decode_slice(&self.pending[..self.pending_len], &mut decoded)
809                .map_err(decode_error_to_io)?;
810            self.inner_mut().write_all(&decoded[..written])?;
811            self.clear_pending();
812            Ok(())
813        }
814
815        fn write_full_quad(&mut self, input: [u8; 4]) -> io::Result<()> {
816            let mut decoded = [0u8; 3];
817            let written = self
818                .engine
819                .decode_slice(&input, &mut decoded)
820                .map_err(decode_error_to_io)?;
821            self.inner_mut().write_all(&decoded[..written])?;
822            if written < 3 {
823                self.finished = true;
824            }
825            Ok(())
826        }
827    }
828
829    impl<W, A, const PAD: bool> Write for Decoder<W, A, PAD>
830    where
831        W: Write,
832        A: Alphabet,
833    {
834        fn write(&mut self, input: &[u8]) -> io::Result<usize> {
835            if input.is_empty() {
836                return Ok(0);
837            }
838            if self.finished {
839                return Err(trailing_input_after_padding_error());
840            }
841
842            let mut consumed = 0;
843            if self.pending_len > 0 {
844                let needed = 4 - self.pending_len;
845                if input.len() < needed {
846                    self.pending[self.pending_len..self.pending_len + input.len()]
847                        .copy_from_slice(input);
848                    self.pending_len += input.len();
849                    return Ok(input.len());
850                }
851
852                let mut quad = [0u8; 4];
853                quad[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
854                quad[self.pending_len..].copy_from_slice(&input[..needed]);
855                self.write_full_quad(quad)?;
856                self.clear_pending();
857                consumed += needed;
858                if self.finished && consumed < input.len() {
859                    return Err(trailing_input_after_padding_error());
860                }
861            }
862
863            let remaining = &input[consumed..];
864            let full_len = remaining.len() / 4 * 4;
865            let mut offset = 0;
866            while offset < full_len {
867                let quad = [
868                    remaining[offset],
869                    remaining[offset + 1],
870                    remaining[offset + 2],
871                    remaining[offset + 3],
872                ];
873                self.write_full_quad(quad)?;
874                offset += 4;
875                if self.finished && offset < remaining.len() {
876                    return Err(trailing_input_after_padding_error());
877                }
878            }
879
880            let tail = &remaining[full_len..];
881            self.pending[..tail.len()].copy_from_slice(tail);
882            self.pending_len = tail.len();
883
884            Ok(input.len())
885        }
886
887        fn flush(&mut self) -> io::Result<()> {
888            self.inner_mut().flush()
889        }
890    }
891
892    fn decode_error_to_io(err: DecodeError) -> io::Error {
893        io::Error::new(io::ErrorKind::InvalidInput, err)
894    }
895
896    fn trailing_input_after_padding_error() -> io::Error {
897        io::Error::new(
898            io::ErrorKind::InvalidInput,
899            "base64 decoder received trailing input after padding",
900        )
901    }
902
903    /// A streaming Base64 decoder for `std::io::Read`.
904    ///
905    /// For padded engines, this reader stops at the terminal padded Base64
906    /// block and leaves later bytes unread in the wrapped reader. This preserves
907    /// boundaries for callers that decode one Base64 payload from a larger
908    /// stream.
909    pub struct DecoderReader<R, A, const PAD: bool>
910    where
911        A: Alphabet,
912    {
913        inner: Option<R>,
914        engine: Engine<A, PAD>,
915        pending: [u8; 4],
916        pending_len: usize,
917        output: OutputQueue<3>,
918        finished: bool,
919        terminal_seen: bool,
920    }
921
922    impl<R, A, const PAD: bool> DecoderReader<R, A, PAD>
923    where
924        A: Alphabet,
925    {
926        /// Creates a new streaming decoder reader.
927        #[must_use]
928        pub fn new(inner: R, engine: Engine<A, PAD>) -> Self {
929            Self {
930                inner: Some(inner),
931                engine,
932                pending: [0; 4],
933                pending_len: 0,
934                output: OutputQueue::new(),
935                finished: false,
936                terminal_seen: false,
937            }
938        }
939
940        /// Returns a shared reference to the wrapped reader.
941        #[must_use]
942        pub fn get_ref(&self) -> &R {
943            self.inner_ref()
944        }
945
946        /// Returns a mutable reference to the wrapped reader.
947        pub fn get_mut(&mut self) -> &mut R {
948            self.inner_mut()
949        }
950
951        /// Consumes the decoder reader and returns the wrapped reader.
952        #[must_use]
953        pub fn into_inner(mut self) -> R {
954            self.take_inner()
955        }
956
957        fn inner_ref(&self) -> &R {
958            match &self.inner {
959                Some(inner) => inner,
960                None => unreachable!("stream decoder reader inner reader was already taken"),
961            }
962        }
963
964        fn inner_mut(&mut self) -> &mut R {
965            match &mut self.inner {
966                Some(inner) => inner,
967                None => unreachable!("stream decoder reader inner reader was already taken"),
968            }
969        }
970
971        fn take_inner(&mut self) -> R {
972            match self.inner.take() {
973                Some(inner) => inner,
974                None => unreachable!("stream decoder reader inner reader was already taken"),
975            }
976        }
977
978        fn clear_pending(&mut self) {
979            self.pending.fill(0);
980            self.pending_len = 0;
981        }
982    }
983
984    impl<R, A, const PAD: bool> Drop for DecoderReader<R, A, PAD>
985    where
986        A: Alphabet,
987    {
988        fn drop(&mut self) {
989            self.clear_pending();
990            self.output.clear_all();
991        }
992    }
993
994    impl<R, A, const PAD: bool> Read for DecoderReader<R, A, PAD>
995    where
996        R: Read,
997        A: Alphabet,
998    {
999        fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
1000            if output.is_empty() {
1001                return Ok(0);
1002            }
1003
1004            while self.output.is_empty() && !self.finished {
1005                self.fill_output()?;
1006            }
1007
1008            let mut written = 0;
1009            while written < output.len() {
1010                let Some(byte) = self.output.pop_front() else {
1011                    break;
1012                };
1013                output[written] = byte;
1014                written += 1;
1015            }
1016
1017            Ok(written)
1018        }
1019    }
1020
1021    impl<R, A, const PAD: bool> DecoderReader<R, A, PAD>
1022    where
1023        R: Read,
1024        A: Alphabet,
1025    {
1026        fn fill_output(&mut self) -> io::Result<()> {
1027            if self.terminal_seen {
1028                self.finished = true;
1029                return Ok(());
1030            }
1031
1032            let mut input = [0u8; 4];
1033            let available = 4 - self.pending_len;
1034            let read = self.inner_mut().read(&mut input[..available])?;
1035            if read == 0 {
1036                self.finished = true;
1037                self.push_final_pending()?;
1038                return Ok(());
1039            }
1040
1041            self.pending[self.pending_len..self.pending_len + read].copy_from_slice(&input[..read]);
1042            self.pending_len += read;
1043            if self.pending_len < 4 {
1044                return Ok(());
1045            }
1046
1047            let quad = self.pending;
1048            self.clear_pending();
1049            self.push_decoded(&quad)?;
1050            if self.terminal_seen {
1051                self.finished = true;
1052            }
1053            Ok(())
1054        }
1055
1056        fn push_final_pending(&mut self) -> io::Result<()> {
1057            if self.pending_len == 0 {
1058                return Ok(());
1059            }
1060
1061            let mut pending = [0u8; 4];
1062            pending[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
1063            let pending_len = self.pending_len;
1064            self.clear_pending();
1065            self.push_decoded(&pending[..pending_len])
1066        }
1067
1068        fn push_decoded(&mut self, input: &[u8]) -> io::Result<()> {
1069            let mut decoded = [0u8; 3];
1070            let written = self
1071                .engine
1072                .decode_slice(input, &mut decoded)
1073                .map_err(decode_error_to_io)?;
1074            self.output.push_slice(&decoded[..written])?;
1075            if input.len() == 4 && written < 3 {
1076                self.terminal_seen = true;
1077            }
1078            Ok(())
1079        }
1080    }
1081
1082    /// A streaming Base64 encoder for `std::io::Read`.
1083    pub struct EncoderReader<R, A, const PAD: bool>
1084    where
1085        A: Alphabet,
1086    {
1087        inner: Option<R>,
1088        engine: Engine<A, PAD>,
1089        pending: [u8; 2],
1090        pending_len: usize,
1091        output: OutputQueue<1024>,
1092        finished: bool,
1093    }
1094
1095    impl<R, A, const PAD: bool> EncoderReader<R, A, PAD>
1096    where
1097        A: Alphabet,
1098    {
1099        /// Creates a new streaming encoder reader.
1100        #[must_use]
1101        pub fn new(inner: R, engine: Engine<A, PAD>) -> Self {
1102            Self {
1103                inner: Some(inner),
1104                engine,
1105                pending: [0; 2],
1106                pending_len: 0,
1107                output: OutputQueue::new(),
1108                finished: false,
1109            }
1110        }
1111
1112        /// Returns a shared reference to the wrapped reader.
1113        #[must_use]
1114        pub fn get_ref(&self) -> &R {
1115            self.inner_ref()
1116        }
1117
1118        /// Returns a mutable reference to the wrapped reader.
1119        pub fn get_mut(&mut self) -> &mut R {
1120            self.inner_mut()
1121        }
1122
1123        /// Consumes the encoder reader and returns the wrapped reader.
1124        #[must_use]
1125        pub fn into_inner(mut self) -> R {
1126            self.take_inner()
1127        }
1128
1129        fn inner_ref(&self) -> &R {
1130            match &self.inner {
1131                Some(inner) => inner,
1132                None => unreachable!("stream encoder reader inner reader was already taken"),
1133            }
1134        }
1135
1136        fn inner_mut(&mut self) -> &mut R {
1137            match &mut self.inner {
1138                Some(inner) => inner,
1139                None => unreachable!("stream encoder reader inner reader was already taken"),
1140            }
1141        }
1142
1143        fn take_inner(&mut self) -> R {
1144            match self.inner.take() {
1145                Some(inner) => inner,
1146                None => unreachable!("stream encoder reader inner reader was already taken"),
1147            }
1148        }
1149
1150        fn clear_pending(&mut self) {
1151            self.pending.fill(0);
1152            self.pending_len = 0;
1153        }
1154    }
1155
1156    impl<R, A, const PAD: bool> Drop for EncoderReader<R, A, PAD>
1157    where
1158        A: Alphabet,
1159    {
1160        fn drop(&mut self) {
1161            self.clear_pending();
1162            self.output.clear_all();
1163        }
1164    }
1165
1166    impl<R, A, const PAD: bool> Read for EncoderReader<R, A, PAD>
1167    where
1168        R: Read,
1169        A: Alphabet,
1170    {
1171        fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
1172            if output.is_empty() {
1173                return Ok(0);
1174            }
1175
1176            while self.output.is_empty() && !self.finished {
1177                self.fill_output()?;
1178            }
1179
1180            let mut written = 0;
1181            while written < output.len() {
1182                let Some(byte) = self.output.pop_front() else {
1183                    break;
1184                };
1185                output[written] = byte;
1186                written += 1;
1187            }
1188
1189            Ok(written)
1190        }
1191    }
1192
1193    impl<R, A, const PAD: bool> EncoderReader<R, A, PAD>
1194    where
1195        R: Read,
1196        A: Alphabet,
1197    {
1198        fn fill_output(&mut self) -> io::Result<()> {
1199            let mut input = [0u8; 768];
1200            let read = self.inner_mut().read(&mut input)?;
1201            if read == 0 {
1202                self.finished = true;
1203                self.push_final_pending()?;
1204                return Ok(());
1205            }
1206
1207            let mut consumed = 0;
1208            if self.pending_len > 0 {
1209                let needed = 3 - self.pending_len;
1210                if read < needed {
1211                    self.pending[self.pending_len..self.pending_len + read]
1212                        .copy_from_slice(&input[..read]);
1213                    self.pending_len += read;
1214                    return Ok(());
1215                }
1216
1217                let mut chunk = [0u8; 3];
1218                chunk[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
1219                chunk[self.pending_len..].copy_from_slice(&input[..needed]);
1220                self.push_encoded(&chunk)?;
1221                self.clear_pending();
1222                consumed += needed;
1223            }
1224
1225            let remaining = &input[consumed..read];
1226            let full_len = remaining.len() / 3 * 3;
1227            if full_len > 0 {
1228                self.push_encoded(&remaining[..full_len])?;
1229            }
1230
1231            let tail = &remaining[full_len..];
1232            self.pending[..tail.len()].copy_from_slice(tail);
1233            self.pending_len = tail.len();
1234            Ok(())
1235        }
1236
1237        fn push_final_pending(&mut self) -> io::Result<()> {
1238            if self.pending_len == 0 {
1239                return Ok(());
1240            }
1241
1242            let mut pending = [0u8; 2];
1243            pending[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
1244            let pending_len = self.pending_len;
1245            self.clear_pending();
1246            self.push_encoded(&pending[..pending_len])
1247        }
1248
1249        fn push_encoded(&mut self, input: &[u8]) -> io::Result<()> {
1250            let mut encoded = [0u8; 1024];
1251            let written = self
1252                .engine
1253                .encode_slice(input, &mut encoded)
1254                .map_err(encode_error_to_io)?;
1255            self.output.push_slice(&encoded[..written])?;
1256            Ok(())
1257        }
1258    }
1259}
1260
1261/// Constant-time-oriented scalar decoding APIs.
1262///
1263/// This module is separate from the default decoder so callers can opt into a
1264/// slower path with a narrower timing target. It avoids lookup tables indexed
1265/// by secret input bytes while mapping Base64 symbols and reports malformed
1266/// content through one opaque error. It is not documented as a formally
1267/// verified cryptographic constant-time API.
1268pub mod ct {
1269    use super::{Alphabet, DecodeError, Standard, UrlSafe, ct_decode_in_place, ct_decode_slice};
1270    use core::marker::PhantomData;
1271
1272    /// Standard Base64 constant-time-oriented decoder with padding.
1273    pub const STANDARD: CtEngine<Standard, true> = CtEngine::new();
1274
1275    /// Standard Base64 constant-time-oriented decoder without padding.
1276    pub const STANDARD_NO_PAD: CtEngine<Standard, false> = CtEngine::new();
1277
1278    /// URL-safe Base64 constant-time-oriented decoder with padding.
1279    pub const URL_SAFE: CtEngine<UrlSafe, true> = CtEngine::new();
1280
1281    /// URL-safe Base64 constant-time-oriented decoder without padding.
1282    pub const URL_SAFE_NO_PAD: CtEngine<UrlSafe, false> = CtEngine::new();
1283
1284    /// A zero-sized constant-time-oriented Base64 decoder.
1285    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1286    pub struct CtEngine<A, const PAD: bool> {
1287        alphabet: PhantomData<A>,
1288    }
1289
1290    impl<A, const PAD: bool> CtEngine<A, PAD>
1291    where
1292        A: Alphabet,
1293    {
1294        /// Creates a new constant-time-oriented decoder engine.
1295        #[must_use]
1296        pub const fn new() -> Self {
1297            Self {
1298                alphabet: PhantomData,
1299            }
1300        }
1301
1302        /// Decodes `input` into `output`, returning the number of bytes
1303        /// written.
1304        ///
1305        /// This path uses branch-minimized arithmetic for Base64 symbol
1306        /// mapping and avoids secret-indexed lookup tables. Input length,
1307        /// padding length, output length, and final success or failure remain
1308        /// public. Malformed content errors are intentionally opaque and
1309        /// non-localized; use the normal strict decoder when exact diagnostics
1310        /// are required.
1311        ///
1312        /// # Examples
1313        ///
1314        /// ```
1315        /// use base64_ng::ct;
1316        ///
1317        /// let mut output = [0u8; 5];
1318        /// let written = ct::STANDARD
1319        ///     .decode_slice(b"aGVsbG8=", &mut output)
1320        ///     .unwrap();
1321        ///
1322        /// assert_eq!(&output[..written], b"hello");
1323        /// ```
1324        pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
1325            ct_decode_slice::<A, PAD>(input, output)
1326        }
1327
1328        /// Decodes `input` into `output` and clears all bytes after the
1329        /// decoded prefix.
1330        ///
1331        /// If decoding fails, the entire output buffer is cleared before the
1332        /// error is returned. Use this variant for sensitive payloads where
1333        /// partially decoded bytes from rejected input should not remain in the
1334        /// caller-owned output buffer.
1335        ///
1336        /// # Examples
1337        ///
1338        /// ```
1339        /// use base64_ng::ct;
1340        ///
1341        /// let mut output = [0xff; 8];
1342        /// let written = ct::STANDARD
1343        ///     .decode_slice_clear_tail(b"aGk=", &mut output)
1344        ///     .unwrap();
1345        ///
1346        /// assert_eq!(&output[..written], b"hi");
1347        /// assert!(output[written..].iter().all(|byte| *byte == 0));
1348        /// ```
1349        pub fn decode_slice_clear_tail(
1350            &self,
1351            input: &[u8],
1352            output: &mut [u8],
1353        ) -> Result<usize, DecodeError> {
1354            let written = match self.decode_slice(input, output) {
1355                Ok(written) => written,
1356                Err(err) => {
1357                    output.fill(0);
1358                    return Err(err);
1359                }
1360            };
1361            output[written..].fill(0);
1362            Ok(written)
1363        }
1364
1365        /// Decodes `buffer` in place and returns the decoded prefix.
1366        ///
1367        /// This uses the constant-time-oriented scalar decoder while reading
1368        /// each Base64 quantum into local values before writing decoded bytes
1369        /// back to the front of the same buffer.
1370        ///
1371        /// # Examples
1372        ///
1373        /// ```
1374        /// use base64_ng::ct;
1375        ///
1376        /// let mut buffer = *b"aGk=";
1377        /// let decoded = ct::STANDARD.decode_in_place(&mut buffer).unwrap();
1378        ///
1379        /// assert_eq!(decoded, b"hi");
1380        /// ```
1381        pub fn decode_in_place<'a>(
1382            &self,
1383            buffer: &'a mut [u8],
1384        ) -> Result<&'a mut [u8], DecodeError> {
1385            let len = ct_decode_in_place::<A, PAD>(buffer)?;
1386            Ok(&mut buffer[..len])
1387        }
1388
1389        /// Decodes `buffer` in place and clears all bytes after the decoded
1390        /// prefix.
1391        ///
1392        /// If decoding fails, the entire buffer is cleared before the error is
1393        /// returned.
1394        ///
1395        /// # Examples
1396        ///
1397        /// ```
1398        /// use base64_ng::ct;
1399        ///
1400        /// let mut buffer = *b"aGk=";
1401        /// let decoded = ct::STANDARD.decode_in_place_clear_tail(&mut buffer).unwrap();
1402        ///
1403        /// assert_eq!(decoded, b"hi");
1404        /// ```
1405        pub fn decode_in_place_clear_tail<'a>(
1406            &self,
1407            buffer: &'a mut [u8],
1408        ) -> Result<&'a mut [u8], DecodeError> {
1409            let len = match ct_decode_in_place::<A, PAD>(buffer) {
1410                Ok(len) => len,
1411                Err(err) => {
1412                    buffer.fill(0);
1413                    return Err(err);
1414                }
1415            };
1416            buffer[len..].fill(0);
1417            Ok(&mut buffer[..len])
1418        }
1419    }
1420}
1421
1422/// Standard Base64 engine with padding.
1423pub const STANDARD: Engine<Standard, true> = Engine::new();
1424
1425/// Standard Base64 engine without padding.
1426pub const STANDARD_NO_PAD: Engine<Standard, false> = Engine::new();
1427
1428/// URL-safe Base64 engine with padding.
1429pub const URL_SAFE: Engine<UrlSafe, true> = Engine::new();
1430
1431/// URL-safe Base64 engine without padding.
1432pub const URL_SAFE_NO_PAD: Engine<UrlSafe, false> = Engine::new();
1433
1434/// Returns the encoded length for an input length and padding policy.
1435///
1436/// This function returns [`EncodeError::LengthOverflow`] instead of panicking.
1437/// Use [`checked_encoded_len`] when an `Option<usize>` is more convenient.
1438///
1439/// # Examples
1440///
1441/// ```
1442/// use base64_ng::encoded_len;
1443///
1444/// assert_eq!(encoded_len(5, true).unwrap(), 8);
1445/// assert_eq!(encoded_len(5, false).unwrap(), 7);
1446/// assert!(encoded_len(usize::MAX, true).is_err());
1447/// ```
1448pub const fn encoded_len(input_len: usize, padded: bool) -> Result<usize, EncodeError> {
1449    match checked_encoded_len(input_len, padded) {
1450        Some(len) => Ok(len),
1451        None => Err(EncodeError::LengthOverflow),
1452    }
1453}
1454
1455/// Returns the encoded length, or `None` if it would overflow `usize`.
1456///
1457/// # Examples
1458///
1459/// ```
1460/// use base64_ng::checked_encoded_len;
1461///
1462/// assert_eq!(checked_encoded_len(5, true), Some(8));
1463/// assert_eq!(checked_encoded_len(usize::MAX, true), None);
1464/// ```
1465#[must_use]
1466pub const fn checked_encoded_len(input_len: usize, padded: bool) -> Option<usize> {
1467    let groups = input_len / 3;
1468    if groups > usize::MAX / 4 {
1469        return None;
1470    }
1471    let full = groups * 4;
1472    let rem = input_len % 3;
1473    if rem == 0 {
1474        Some(full)
1475    } else if padded {
1476        full.checked_add(4)
1477    } else {
1478        full.checked_add(rem + 1)
1479    }
1480}
1481
1482/// Returns the maximum decoded length for an encoded input length.
1483///
1484/// # Examples
1485///
1486/// ```
1487/// use base64_ng::decoded_capacity;
1488///
1489/// assert_eq!(decoded_capacity(8), 6);
1490/// assert_eq!(decoded_capacity(7), 5);
1491/// ```
1492#[must_use]
1493pub const fn decoded_capacity(encoded_len: usize) -> usize {
1494    let rem = encoded_len % 4;
1495    encoded_len / 4 * 3
1496        + if rem == 2 {
1497            1
1498        } else if rem == 3 {
1499            2
1500        } else {
1501            0
1502        }
1503}
1504
1505/// Returns the exact decoded length implied by input length and padding.
1506///
1507/// This validates padding placement and impossible lengths, but it does not
1508/// validate alphabet membership or non-canonical trailing bits.
1509///
1510/// # Examples
1511///
1512/// ```
1513/// use base64_ng::decoded_len;
1514///
1515/// assert_eq!(decoded_len(b"aGVsbG8=", true).unwrap(), 5);
1516/// assert_eq!(decoded_len(b"aGVsbG8", false).unwrap(), 5);
1517/// ```
1518pub fn decoded_len(input: &[u8], padded: bool) -> Result<usize, DecodeError> {
1519    if padded {
1520        decoded_len_padded(input)
1521    } else {
1522        decoded_len_unpadded(input)
1523    }
1524}
1525
1526/// A Base64 alphabet.
1527pub trait Alphabet {
1528    /// Encoding table indexed by 6-bit values.
1529    const ENCODE: [u8; 64];
1530
1531    /// Decode one byte into a 6-bit value.
1532    fn decode(byte: u8) -> Option<u8>;
1533}
1534
1535/// The RFC 4648 standard Base64 alphabet.
1536#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1537pub struct Standard;
1538
1539impl Alphabet for Standard {
1540    const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1541
1542    #[inline]
1543    fn decode(byte: u8) -> Option<u8> {
1544        decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
1545    }
1546}
1547
1548/// The RFC 4648 URL-safe Base64 alphabet.
1549#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1550pub struct UrlSafe;
1551
1552impl Alphabet for UrlSafe {
1553    const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
1554
1555    #[inline]
1556    fn decode(byte: u8) -> Option<u8> {
1557        decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
1558    }
1559}
1560
1561#[inline]
1562const fn encode_base64_value<A: Alphabet>(value: u8) -> u8 {
1563    encode_ascii_base64(value, A::ENCODE[62], A::ENCODE[63])
1564}
1565
1566#[inline]
1567const fn encode_ascii_base64(value: u8, value_62_byte: u8, value_63_byte: u8) -> u8 {
1568    let upper = ct_mask_lt_u8(value, 26);
1569    let lower = ct_mask_lt_u8(value.wrapping_sub(26), 26);
1570    let digit = ct_mask_lt_u8(value.wrapping_sub(52), 10);
1571    let value_62 = ct_mask_eq_u8(value, 0x3e);
1572    let value_63 = ct_mask_eq_u8(value, 0x3f);
1573
1574    (value.wrapping_add(b'A') & upper)
1575        | (value.wrapping_sub(26).wrapping_add(b'a') & lower)
1576        | (value.wrapping_sub(52).wrapping_add(b'0') & digit)
1577        | (value_62_byte & value_62)
1578        | (value_63_byte & value_63)
1579}
1580
1581#[inline]
1582fn decode_ascii_base64(byte: u8, value_62_byte: u8, value_63_byte: u8) -> Option<u8> {
1583    let upper = ct_mask_lt_u8(byte.wrapping_sub(b'A'), 26);
1584    let lower = ct_mask_lt_u8(byte.wrapping_sub(b'a'), 26);
1585    let digit = ct_mask_lt_u8(byte.wrapping_sub(b'0'), 10);
1586    let value_62 = ct_mask_eq_u8(byte, value_62_byte);
1587    let value_63 = ct_mask_eq_u8(byte, value_63_byte);
1588    let valid = upper | lower | digit | value_62 | value_63;
1589
1590    let decoded = (byte.wrapping_sub(b'A') & upper)
1591        | (byte.wrapping_sub(b'a').wrapping_add(26) & lower)
1592        | (byte.wrapping_sub(b'0').wrapping_add(52) & digit)
1593        | (0x3e & value_62)
1594        | (0x3f & value_63);
1595
1596    if valid == 0 { None } else { Some(decoded) }
1597}
1598
1599#[inline]
1600const fn ct_mask_bit(bit: u8) -> u8 {
1601    0u8.wrapping_sub(bit & 1)
1602}
1603
1604#[inline]
1605const fn ct_mask_nonzero_u8(value: u8) -> u8 {
1606    let wide = value as u16;
1607    let negative = 0u16.wrapping_sub(wide);
1608    let nonzero = ((wide | negative) >> 8) as u8;
1609    ct_mask_bit(nonzero)
1610}
1611
1612#[inline]
1613const fn ct_mask_eq_u8(left: u8, right: u8) -> u8 {
1614    !ct_mask_nonzero_u8(left ^ right)
1615}
1616
1617#[inline]
1618const fn ct_mask_lt_u8(left: u8, right: u8) -> u8 {
1619    let diff = (left as u16).wrapping_sub(right as u16);
1620    ct_mask_bit((diff >> 8) as u8)
1621}
1622
1623mod backend {
1624    use super::{
1625        Alphabet, DecodeError, EncodeError, checked_encoded_len, decode_padded, decode_unpadded,
1626        encode_base64_value,
1627    };
1628
1629    pub(super) fn encode_slice<A, const PAD: bool>(
1630        input: &[u8],
1631        output: &mut [u8],
1632    ) -> Result<usize, EncodeError>
1633    where
1634        A: Alphabet,
1635    {
1636        #[cfg(feature = "simd")]
1637        match super::simd::active_backend() {
1638            super::simd::ActiveBackend::Scalar => {}
1639        }
1640
1641        scalar_encode_slice::<A, PAD>(input, output)
1642    }
1643
1644    pub(super) fn decode_slice<A, const PAD: bool>(
1645        input: &[u8],
1646        output: &mut [u8],
1647    ) -> Result<usize, DecodeError>
1648    where
1649        A: Alphabet,
1650    {
1651        #[cfg(feature = "simd")]
1652        match super::simd::active_backend() {
1653            super::simd::ActiveBackend::Scalar => {}
1654        }
1655
1656        scalar_decode_slice::<A, PAD>(input, output)
1657    }
1658
1659    #[cfg(test)]
1660    pub(super) fn scalar_reference_encode_slice<A, const PAD: bool>(
1661        input: &[u8],
1662        output: &mut [u8],
1663    ) -> Result<usize, EncodeError>
1664    where
1665        A: Alphabet,
1666    {
1667        scalar_encode_slice::<A, PAD>(input, output)
1668    }
1669
1670    #[cfg(test)]
1671    pub(super) fn scalar_reference_decode_slice<A, const PAD: bool>(
1672        input: &[u8],
1673        output: &mut [u8],
1674    ) -> Result<usize, DecodeError>
1675    where
1676        A: Alphabet,
1677    {
1678        scalar_decode_slice::<A, PAD>(input, output)
1679    }
1680
1681    fn scalar_encode_slice<A, const PAD: bool>(
1682        input: &[u8],
1683        output: &mut [u8],
1684    ) -> Result<usize, EncodeError>
1685    where
1686        A: Alphabet,
1687    {
1688        let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
1689        if output.len() < required {
1690            return Err(EncodeError::OutputTooSmall {
1691                required,
1692                available: output.len(),
1693            });
1694        }
1695
1696        let mut read = 0;
1697        let mut write = 0;
1698        while read + 3 <= input.len() {
1699            let b0 = input[read];
1700            let b1 = input[read + 1];
1701            let b2 = input[read + 2];
1702
1703            output[write] = encode_base64_value::<A>(b0 >> 2);
1704            output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
1705            output[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
1706            output[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
1707
1708            read += 3;
1709            write += 4;
1710        }
1711
1712        match input.len() - read {
1713            0 => {}
1714            1 => {
1715                let b0 = input[read];
1716                output[write] = encode_base64_value::<A>(b0 >> 2);
1717                output[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
1718                write += 2;
1719                if PAD {
1720                    output[write] = b'=';
1721                    output[write + 1] = b'=';
1722                    write += 2;
1723                }
1724            }
1725            2 => {
1726                let b0 = input[read];
1727                let b1 = input[read + 1];
1728                output[write] = encode_base64_value::<A>(b0 >> 2);
1729                output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
1730                output[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
1731                write += 3;
1732                if PAD {
1733                    output[write] = b'=';
1734                    write += 1;
1735                }
1736            }
1737            _ => unreachable!(),
1738        }
1739
1740        Ok(write)
1741    }
1742
1743    fn scalar_decode_slice<A, const PAD: bool>(
1744        input: &[u8],
1745        output: &mut [u8],
1746    ) -> Result<usize, DecodeError>
1747    where
1748        A: Alphabet,
1749    {
1750        if input.is_empty() {
1751            return Ok(0);
1752        }
1753
1754        if PAD {
1755            decode_padded::<A>(input, output)
1756        } else {
1757            decode_unpadded::<A>(input, output)
1758        }
1759    }
1760}
1761
1762/// A zero-sized Base64 engine parameterized by alphabet and padding policy.
1763#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1764pub struct Engine<A, const PAD: bool> {
1765    alphabet: core::marker::PhantomData<A>,
1766}
1767
1768impl<A, const PAD: bool> Engine<A, PAD>
1769where
1770    A: Alphabet,
1771{
1772    /// Creates a new engine value.
1773    #[must_use]
1774    pub const fn new() -> Self {
1775        Self {
1776            alphabet: core::marker::PhantomData,
1777        }
1778    }
1779
1780    /// Returns the encoded length for this engine's padding policy.
1781    pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
1782        encoded_len(input_len, PAD)
1783    }
1784
1785    /// Returns the encoded length for this engine, or `None` on overflow.
1786    #[must_use]
1787    pub const fn checked_encoded_len(&self, input_len: usize) -> Option<usize> {
1788        checked_encoded_len(input_len, PAD)
1789    }
1790
1791    /// Returns the exact decoded length implied by input length and padding.
1792    ///
1793    /// This validates padding placement and impossible lengths, but it does not
1794    /// validate alphabet membership or non-canonical trailing bits.
1795    pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
1796        decoded_len(input, PAD)
1797    }
1798
1799    /// Returns the exact decoded length for the explicit legacy profile.
1800    ///
1801    /// The legacy profile ignores ASCII space, tab, carriage return, and line
1802    /// feed bytes before applying the same alphabet, padding, and canonical-bit
1803    /// checks as strict decoding.
1804    pub fn decoded_len_legacy(&self, input: &[u8]) -> Result<usize, DecodeError> {
1805        validate_legacy_decode::<A, PAD>(input)
1806    }
1807
1808    /// Encodes a fixed-size input into a fixed-size output array in const contexts.
1809    ///
1810    /// Stable Rust does not yet allow this API to return an array whose length
1811    /// is computed from `INPUT_LEN` directly. Instead, the caller supplies the
1812    /// output length through the destination type and this function panics
1813    /// during const evaluation if the length is wrong.
1814    ///
1815    /// # Panics
1816    ///
1817    /// Panics if `OUTPUT_LEN` is not exactly the encoded length for `INPUT_LEN`
1818    /// and this engine's padding policy, or if that length overflows `usize`.
1819    ///
1820    /// # Examples
1821    ///
1822    /// ```
1823    /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
1824    ///
1825    /// const HELLO: [u8; 8] = STANDARD.encode_array(b"hello");
1826    /// const URL_SAFE: [u8; 3] = URL_SAFE_NO_PAD.encode_array(b"\xfb\xff");
1827    ///
1828    /// assert_eq!(&HELLO, b"aGVsbG8=");
1829    /// assert_eq!(&URL_SAFE, b"-_8");
1830    /// ```
1831    ///
1832    /// Incorrect output lengths fail during const evaluation:
1833    ///
1834    /// ```compile_fail
1835    /// use base64_ng::STANDARD;
1836    ///
1837    /// const TOO_SHORT: [u8; 7] = STANDARD.encode_array(b"hello");
1838    /// ```
1839    #[must_use]
1840    pub const fn encode_array<const INPUT_LEN: usize, const OUTPUT_LEN: usize>(
1841        &self,
1842        input: &[u8; INPUT_LEN],
1843    ) -> [u8; OUTPUT_LEN] {
1844        let Some(required) = checked_encoded_len(INPUT_LEN, PAD) else {
1845            panic!("encoded base64 length overflows usize");
1846        };
1847        assert!(
1848            required == OUTPUT_LEN,
1849            "base64 output array has incorrect length"
1850        );
1851
1852        let mut output = [0u8; OUTPUT_LEN];
1853        let mut read = 0;
1854        let mut write = 0;
1855        while INPUT_LEN - read >= 3 {
1856            let b0 = input[read];
1857            let b1 = input[read + 1];
1858            let b2 = input[read + 2];
1859
1860            output[write] = encode_base64_value::<A>(b0 >> 2);
1861            output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
1862            output[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
1863            output[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
1864
1865            read += 3;
1866            write += 4;
1867        }
1868
1869        match INPUT_LEN - read {
1870            0 => {}
1871            1 => {
1872                let b0 = input[read];
1873                output[write] = encode_base64_value::<A>(b0 >> 2);
1874                output[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
1875                write += 2;
1876                if PAD {
1877                    output[write] = b'=';
1878                    output[write + 1] = b'=';
1879                }
1880            }
1881            2 => {
1882                let b0 = input[read];
1883                let b1 = input[read + 1];
1884                output[write] = encode_base64_value::<A>(b0 >> 2);
1885                output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
1886                output[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
1887                if PAD {
1888                    output[write + 3] = b'=';
1889                }
1890            }
1891            _ => unreachable!(),
1892        }
1893
1894        output
1895    }
1896
1897    /// Encodes `input` into `output`, returning the number of bytes written.
1898    pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
1899        backend::encode_slice::<A, PAD>(input, output)
1900    }
1901
1902    /// Encodes `input` into `output` and clears all bytes after the encoded
1903    /// prefix.
1904    ///
1905    /// If encoding fails, the entire output buffer is cleared before the error
1906    /// is returned.
1907    ///
1908    /// # Examples
1909    ///
1910    /// ```
1911    /// use base64_ng::STANDARD;
1912    ///
1913    /// let mut output = [0xff; 12];
1914    /// let written = STANDARD
1915    ///     .encode_slice_clear_tail(b"hello", &mut output)
1916    ///     .unwrap();
1917    ///
1918    /// assert_eq!(&output[..written], b"aGVsbG8=");
1919    /// assert!(output[written..].iter().all(|byte| *byte == 0));
1920    /// ```
1921    pub fn encode_slice_clear_tail(
1922        &self,
1923        input: &[u8],
1924        output: &mut [u8],
1925    ) -> Result<usize, EncodeError> {
1926        let written = match self.encode_slice(input, output) {
1927            Ok(written) => written,
1928            Err(err) => {
1929                output.fill(0);
1930                return Err(err);
1931            }
1932        };
1933        output[written..].fill(0);
1934        Ok(written)
1935    }
1936
1937    /// Encodes `input` into a newly allocated byte vector.
1938    #[cfg(feature = "alloc")]
1939    pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
1940        let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
1941        let mut output = alloc::vec![0; required];
1942        let written = self.encode_slice(input, &mut output)?;
1943        output.truncate(written);
1944        Ok(output)
1945    }
1946
1947    /// Encodes `input` into a newly allocated UTF-8 string.
1948    ///
1949    /// Base64 output is ASCII by construction. This helper is available with
1950    /// the `alloc` feature and has the same encoding semantics as
1951    /// [`Self::encode_slice`].
1952    ///
1953    /// # Examples
1954    ///
1955    /// ```
1956    /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
1957    ///
1958    /// assert_eq!(STANDARD.encode_string(b"hello").unwrap(), "aGVsbG8=");
1959    /// assert_eq!(URL_SAFE_NO_PAD.encode_string(b"\xfb\xff").unwrap(), "-_8");
1960    /// ```
1961    #[cfg(feature = "alloc")]
1962    pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
1963        let output = self.encode_vec(input)?;
1964        match alloc::string::String::from_utf8(output) {
1965            Ok(output) => Ok(output),
1966            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
1967        }
1968    }
1969
1970    /// Encodes the first `input_len` bytes of `buffer` in place.
1971    ///
1972    /// The buffer must have enough spare capacity for the encoded output. The
1973    /// implementation writes from right to left, so unread input bytes are not
1974    /// overwritten before they are encoded.
1975    ///
1976    /// # Examples
1977    ///
1978    /// ```
1979    /// use base64_ng::STANDARD;
1980    ///
1981    /// let mut buffer = [0u8; 8];
1982    /// buffer[..5].copy_from_slice(b"hello");
1983    /// let encoded = STANDARD.encode_in_place(&mut buffer, 5).unwrap();
1984    /// assert_eq!(encoded, b"aGVsbG8=");
1985    /// ```
1986    pub fn encode_in_place<'a>(
1987        &self,
1988        buffer: &'a mut [u8],
1989        input_len: usize,
1990    ) -> Result<&'a mut [u8], EncodeError> {
1991        if input_len > buffer.len() {
1992            return Err(EncodeError::InputTooLarge {
1993                input_len,
1994                buffer_len: buffer.len(),
1995            });
1996        }
1997
1998        let required = checked_encoded_len(input_len, PAD).ok_or(EncodeError::LengthOverflow)?;
1999        if buffer.len() < required {
2000            return Err(EncodeError::OutputTooSmall {
2001                required,
2002                available: buffer.len(),
2003            });
2004        }
2005
2006        let mut read = input_len;
2007        let mut write = required;
2008
2009        match input_len % 3 {
2010            0 => {}
2011            1 => {
2012                read -= 1;
2013                let b0 = buffer[read];
2014                if PAD {
2015                    write -= 4;
2016                    buffer[write] = encode_base64_value::<A>(b0 >> 2);
2017                    buffer[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
2018                    buffer[write + 2] = b'=';
2019                    buffer[write + 3] = b'=';
2020                } else {
2021                    write -= 2;
2022                    buffer[write] = encode_base64_value::<A>(b0 >> 2);
2023                    buffer[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
2024                }
2025            }
2026            2 => {
2027                read -= 2;
2028                let b0 = buffer[read];
2029                let b1 = buffer[read + 1];
2030                if PAD {
2031                    write -= 4;
2032                    buffer[write] = encode_base64_value::<A>(b0 >> 2);
2033                    buffer[write + 1] =
2034                        encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2035                    buffer[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
2036                    buffer[write + 3] = b'=';
2037                } else {
2038                    write -= 3;
2039                    buffer[write] = encode_base64_value::<A>(b0 >> 2);
2040                    buffer[write + 1] =
2041                        encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2042                    buffer[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
2043                }
2044            }
2045            _ => unreachable!(),
2046        }
2047
2048        while read > 0 {
2049            read -= 3;
2050            write -= 4;
2051            let b0 = buffer[read];
2052            let b1 = buffer[read + 1];
2053            let b2 = buffer[read + 2];
2054
2055            buffer[write] = encode_base64_value::<A>(b0 >> 2);
2056            buffer[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2057            buffer[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
2058            buffer[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
2059        }
2060
2061        debug_assert_eq!(write, 0);
2062        Ok(&mut buffer[..required])
2063    }
2064
2065    /// Encodes the first `input_len` bytes of `buffer` in place and clears all
2066    /// bytes after the encoded prefix.
2067    ///
2068    /// If encoding fails because `input_len` is too large, the output buffer is
2069    /// too small, or the encoded length overflows `usize`, the entire buffer is
2070    /// cleared before the error is returned.
2071    ///
2072    /// # Examples
2073    ///
2074    /// ```
2075    /// use base64_ng::STANDARD;
2076    ///
2077    /// let mut buffer = [0xff; 12];
2078    /// buffer[..5].copy_from_slice(b"hello");
2079    /// let encoded = STANDARD.encode_in_place_clear_tail(&mut buffer, 5).unwrap();
2080    /// assert_eq!(encoded, b"aGVsbG8=");
2081    /// ```
2082    pub fn encode_in_place_clear_tail<'a>(
2083        &self,
2084        buffer: &'a mut [u8],
2085        input_len: usize,
2086    ) -> Result<&'a mut [u8], EncodeError> {
2087        let len = match self.encode_in_place(buffer, input_len) {
2088            Ok(encoded) => encoded.len(),
2089            Err(err) => {
2090                buffer.fill(0);
2091                return Err(err);
2092            }
2093        };
2094        buffer[len..].fill(0);
2095        Ok(&mut buffer[..len])
2096    }
2097
2098    /// Decodes `input` into `output`, returning the number of bytes written.
2099    ///
2100    /// This is strict decoding. Whitespace, mixed alphabets, malformed padding,
2101    /// and trailing non-padding data are rejected.
2102    pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
2103        backend::decode_slice::<A, PAD>(input, output)
2104    }
2105
2106    /// Decodes `input` into `output` and clears all bytes after the decoded
2107    /// prefix.
2108    ///
2109    /// If decoding fails, the entire output buffer is cleared before the error
2110    /// is returned.
2111    ///
2112    /// # Examples
2113    ///
2114    /// ```
2115    /// use base64_ng::STANDARD;
2116    ///
2117    /// let mut output = [0xff; 8];
2118    /// let written = STANDARD
2119    ///     .decode_slice_clear_tail(b"aGk=", &mut output)
2120    ///     .unwrap();
2121    ///
2122    /// assert_eq!(&output[..written], b"hi");
2123    /// assert!(output[written..].iter().all(|byte| *byte == 0));
2124    /// ```
2125    pub fn decode_slice_clear_tail(
2126        &self,
2127        input: &[u8],
2128        output: &mut [u8],
2129    ) -> Result<usize, DecodeError> {
2130        let written = match self.decode_slice(input, output) {
2131            Ok(written) => written,
2132            Err(err) => {
2133                output.fill(0);
2134                return Err(err);
2135            }
2136        };
2137        output[written..].fill(0);
2138        Ok(written)
2139    }
2140
2141    /// Decodes `input` using the explicit legacy whitespace profile.
2142    ///
2143    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
2144    /// Alphabet selection, padding placement, trailing data after padding, and
2145    /// non-canonical trailing bits remain strict.
2146    pub fn decode_slice_legacy(
2147        &self,
2148        input: &[u8],
2149        output: &mut [u8],
2150    ) -> Result<usize, DecodeError> {
2151        let required = validate_legacy_decode::<A, PAD>(input)?;
2152        if output.len() < required {
2153            return Err(DecodeError::OutputTooSmall {
2154                required,
2155                available: output.len(),
2156            });
2157        }
2158        decode_legacy_to_slice::<A, PAD>(input, output)
2159    }
2160
2161    /// Decodes `input` using the explicit legacy whitespace profile and clears
2162    /// all bytes after the decoded prefix.
2163    ///
2164    /// If validation or decoding fails, the entire output buffer is cleared
2165    /// before the error is returned.
2166    ///
2167    /// # Examples
2168    ///
2169    /// ```
2170    /// use base64_ng::STANDARD;
2171    ///
2172    /// let mut output = [0xff; 8];
2173    /// let written = STANDARD
2174    ///     .decode_slice_legacy_clear_tail(b" aG\r\nk= ", &mut output)
2175    ///     .unwrap();
2176    ///
2177    /// assert_eq!(&output[..written], b"hi");
2178    /// assert!(output[written..].iter().all(|byte| *byte == 0));
2179    /// ```
2180    pub fn decode_slice_legacy_clear_tail(
2181        &self,
2182        input: &[u8],
2183        output: &mut [u8],
2184    ) -> Result<usize, DecodeError> {
2185        let written = match self.decode_slice_legacy(input, output) {
2186            Ok(written) => written,
2187            Err(err) => {
2188                output.fill(0);
2189                return Err(err);
2190            }
2191        };
2192        output[written..].fill(0);
2193        Ok(written)
2194    }
2195
2196    /// Decodes `input` into a newly allocated byte vector.
2197    ///
2198    /// This is strict decoding with the same semantics as [`Self::decode_slice`].
2199    #[cfg(feature = "alloc")]
2200    pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
2201        let required = validate_decode::<A, PAD>(input)?;
2202        let mut output = alloc::vec![0; required];
2203        let written = match self.decode_slice(input, &mut output) {
2204            Ok(written) => written,
2205            Err(err) => {
2206                output.fill(0);
2207                return Err(err);
2208            }
2209        };
2210        output.truncate(written);
2211        Ok(output)
2212    }
2213
2214    /// Decodes `input` into a newly allocated byte vector using the explicit
2215    /// legacy whitespace profile.
2216    #[cfg(feature = "alloc")]
2217    pub fn decode_vec_legacy(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
2218        let required = validate_legacy_decode::<A, PAD>(input)?;
2219        let mut output = alloc::vec![0; required];
2220        let written = match self.decode_slice_legacy(input, &mut output) {
2221            Ok(written) => written,
2222            Err(err) => {
2223                output.fill(0);
2224                return Err(err);
2225            }
2226        };
2227        output.truncate(written);
2228        Ok(output)
2229    }
2230
2231    /// Decodes the buffer in place and returns the decoded prefix.
2232    ///
2233    /// # Examples
2234    ///
2235    /// ```
2236    /// use base64_ng::STANDARD_NO_PAD;
2237    ///
2238    /// let mut buffer = *b"Zm9vYmFy";
2239    /// let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
2240    /// assert_eq!(decoded, b"foobar");
2241    /// ```
2242    pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
2243        let len = Self::decode_slice_to_start(buffer)?;
2244        Ok(&mut buffer[..len])
2245    }
2246
2247    /// Decodes the buffer in place and clears all bytes after the decoded prefix.
2248    ///
2249    /// If decoding fails, the entire buffer is cleared before the error is
2250    /// returned. Use this variant when the encoded or partially decoded data is
2251    /// sensitive and the caller wants best-effort cleanup without adding a
2252    /// dependency.
2253    ///
2254    /// # Examples
2255    ///
2256    /// ```
2257    /// use base64_ng::STANDARD;
2258    ///
2259    /// let mut buffer = *b"aGk=";
2260    /// let decoded = STANDARD.decode_in_place_clear_tail(&mut buffer).unwrap();
2261    /// assert_eq!(decoded, b"hi");
2262    /// ```
2263    pub fn decode_in_place_clear_tail<'a>(
2264        &self,
2265        buffer: &'a mut [u8],
2266    ) -> Result<&'a mut [u8], DecodeError> {
2267        let len = match Self::decode_slice_to_start(buffer) {
2268            Ok(len) => len,
2269            Err(err) => {
2270                buffer.fill(0);
2271                return Err(err);
2272            }
2273        };
2274        buffer[len..].fill(0);
2275        Ok(&mut buffer[..len])
2276    }
2277
2278    /// Decodes `buffer` in place using the explicit legacy whitespace profile.
2279    ///
2280    /// Ignored whitespace is compacted out before decoding. If validation
2281    /// fails, the buffer contents are unspecified.
2282    pub fn decode_in_place_legacy<'a>(
2283        &self,
2284        buffer: &'a mut [u8],
2285    ) -> Result<&'a mut [u8], DecodeError> {
2286        let _required = validate_legacy_decode::<A, PAD>(buffer)?;
2287        let mut write = 0;
2288        let mut read = 0;
2289        while read < buffer.len() {
2290            let byte = buffer[read];
2291            if !is_legacy_whitespace(byte) {
2292                buffer[write] = byte;
2293                write += 1;
2294            }
2295            read += 1;
2296        }
2297        let len = Self::decode_slice_to_start(&mut buffer[..write])?;
2298        Ok(&mut buffer[..len])
2299    }
2300
2301    /// Decodes `buffer` in place using the explicit legacy whitespace profile
2302    /// and clears all bytes after the decoded prefix.
2303    ///
2304    /// If validation or decoding fails, the entire buffer is cleared before the
2305    /// error is returned.
2306    pub fn decode_in_place_legacy_clear_tail<'a>(
2307        &self,
2308        buffer: &'a mut [u8],
2309    ) -> Result<&'a mut [u8], DecodeError> {
2310        if let Err(err) = validate_legacy_decode::<A, PAD>(buffer) {
2311            buffer.fill(0);
2312            return Err(err);
2313        }
2314
2315        let mut write = 0;
2316        let mut read = 0;
2317        while read < buffer.len() {
2318            let byte = buffer[read];
2319            if !is_legacy_whitespace(byte) {
2320                buffer[write] = byte;
2321                write += 1;
2322            }
2323            read += 1;
2324        }
2325
2326        let len = match Self::decode_slice_to_start(&mut buffer[..write]) {
2327            Ok(len) => len,
2328            Err(err) => {
2329                buffer.fill(0);
2330                return Err(err);
2331            }
2332        };
2333        buffer[len..].fill(0);
2334        Ok(&mut buffer[..len])
2335    }
2336
2337    fn decode_slice_to_start(buffer: &mut [u8]) -> Result<usize, DecodeError> {
2338        let input_len = buffer.len();
2339        let mut read = 0;
2340        let mut write = 0;
2341        while read + 4 <= input_len {
2342            let chunk = [
2343                buffer[read],
2344                buffer[read + 1],
2345                buffer[read + 2],
2346                buffer[read + 3],
2347            ];
2348            let written = decode_chunk::<A, PAD>(&chunk, &mut buffer[write..])
2349                .map_err(|err| err.with_index_offset(read))?;
2350            read += 4;
2351            write += written;
2352            if written < 3 {
2353                if read != input_len {
2354                    return Err(DecodeError::InvalidPadding { index: read - 4 });
2355                }
2356                return Ok(write);
2357            }
2358        }
2359
2360        let rem = input_len - read;
2361        if rem == 0 {
2362            return Ok(write);
2363        }
2364        if PAD {
2365            return Err(DecodeError::InvalidLength);
2366        }
2367        let mut tail = [0u8; 3];
2368        tail[..rem].copy_from_slice(&buffer[read..input_len]);
2369        decode_tail_unpadded::<A>(&tail[..rem], &mut buffer[write..])
2370            .map_err(|err| err.with_index_offset(read))
2371            .map(|n| write + n)
2372    }
2373}
2374
2375/// Encoding error.
2376#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2377pub enum EncodeError {
2378    /// The encoded output length would overflow `usize`.
2379    LengthOverflow,
2380    /// The caller-provided input length exceeds the provided buffer.
2381    InputTooLarge {
2382        /// Requested input bytes.
2383        input_len: usize,
2384        /// Available buffer bytes.
2385        buffer_len: usize,
2386    },
2387    /// The output buffer is too small.
2388    OutputTooSmall {
2389        /// Required output bytes.
2390        required: usize,
2391        /// Available output bytes.
2392        available: usize,
2393    },
2394}
2395
2396impl core::fmt::Display for EncodeError {
2397    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2398        match self {
2399            Self::LengthOverflow => f.write_str("base64 output length overflows usize"),
2400            Self::InputTooLarge {
2401                input_len,
2402                buffer_len,
2403            } => write!(
2404                f,
2405                "base64 input length {input_len} exceeds buffer length {buffer_len}"
2406            ),
2407            Self::OutputTooSmall {
2408                required,
2409                available,
2410            } => write!(
2411                f,
2412                "base64 output buffer too small: required {required}, available {available}"
2413            ),
2414        }
2415    }
2416}
2417
2418#[cfg(feature = "std")]
2419impl std::error::Error for EncodeError {}
2420
2421/// Decoding error.
2422#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2423pub enum DecodeError {
2424    /// The encoded input is malformed, but the decoder intentionally does not
2425    /// disclose a more specific error class.
2426    InvalidInput,
2427    /// The encoded input length is impossible for the selected padding policy.
2428    InvalidLength,
2429    /// A byte is not valid for the selected alphabet.
2430    InvalidByte {
2431        /// Byte index in the input.
2432        index: usize,
2433        /// Invalid byte value.
2434        byte: u8,
2435    },
2436    /// Padding is missing, misplaced, or non-canonical.
2437    InvalidPadding {
2438        /// Byte index where padding became invalid.
2439        index: usize,
2440    },
2441    /// The output buffer is too small.
2442    OutputTooSmall {
2443        /// Required output bytes.
2444        required: usize,
2445        /// Available output bytes.
2446        available: usize,
2447    },
2448}
2449
2450impl core::fmt::Display for DecodeError {
2451    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2452        match self {
2453            Self::InvalidInput => f.write_str("malformed base64 input"),
2454            Self::InvalidLength => f.write_str("invalid base64 input length"),
2455            Self::InvalidByte { index, byte } => {
2456                write!(f, "invalid base64 byte 0x{byte:02x} at index {index}")
2457            }
2458            Self::InvalidPadding { index } => write!(f, "invalid base64 padding at index {index}"),
2459            Self::OutputTooSmall {
2460                required,
2461                available,
2462            } => write!(
2463                f,
2464                "base64 decode output buffer too small: required {required}, available {available}"
2465            ),
2466        }
2467    }
2468}
2469
2470impl DecodeError {
2471    fn with_index_offset(self, offset: usize) -> Self {
2472        match self {
2473            Self::InvalidByte { index, byte } => Self::InvalidByte {
2474                index: index + offset,
2475                byte,
2476            },
2477            Self::InvalidPadding { index } => Self::InvalidPadding {
2478                index: index + offset,
2479            },
2480            Self::InvalidInput | Self::InvalidLength | Self::OutputTooSmall { .. } => self,
2481        }
2482    }
2483}
2484
2485#[cfg(feature = "std")]
2486impl std::error::Error for DecodeError {}
2487
2488fn validate_legacy_decode<A: Alphabet, const PAD: bool>(
2489    input: &[u8],
2490) -> Result<usize, DecodeError> {
2491    let mut chunk = [0u8; 4];
2492    let mut indexes = [0usize; 4];
2493    let mut chunk_len = 0;
2494    let mut required = 0;
2495    let mut terminal_seen = false;
2496
2497    for (index, byte) in input.iter().copied().enumerate() {
2498        if is_legacy_whitespace(byte) {
2499            continue;
2500        }
2501        if terminal_seen {
2502            return Err(DecodeError::InvalidPadding { index });
2503        }
2504
2505        chunk[chunk_len] = byte;
2506        indexes[chunk_len] = index;
2507        chunk_len += 1;
2508
2509        if chunk_len == 4 {
2510            let written =
2511                validate_chunk::<A, PAD>(&chunk).map_err(|err| map_chunk_error(err, &indexes))?;
2512            required += written;
2513            terminal_seen = written < 3;
2514            chunk_len = 0;
2515        }
2516    }
2517
2518    if chunk_len == 0 {
2519        return Ok(required);
2520    }
2521    if PAD {
2522        return Err(DecodeError::InvalidLength);
2523    }
2524
2525    validate_tail_unpadded::<A>(&chunk[..chunk_len])
2526        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
2527    Ok(required + decoded_capacity(chunk_len))
2528}
2529
2530fn decode_legacy_to_slice<A: Alphabet, const PAD: bool>(
2531    input: &[u8],
2532    output: &mut [u8],
2533) -> Result<usize, DecodeError> {
2534    let mut chunk = [0u8; 4];
2535    let mut indexes = [0usize; 4];
2536    let mut chunk_len = 0;
2537    let mut write = 0;
2538    let mut terminal_seen = false;
2539
2540    for (index, byte) in input.iter().copied().enumerate() {
2541        if is_legacy_whitespace(byte) {
2542            continue;
2543        }
2544        if terminal_seen {
2545            return Err(DecodeError::InvalidPadding { index });
2546        }
2547
2548        chunk[chunk_len] = byte;
2549        indexes[chunk_len] = index;
2550        chunk_len += 1;
2551
2552        if chunk_len == 4 {
2553            let written = decode_chunk::<A, PAD>(&chunk, &mut output[write..])
2554                .map_err(|err| map_chunk_error(err, &indexes))?;
2555            write += written;
2556            terminal_seen = written < 3;
2557            chunk_len = 0;
2558        }
2559    }
2560
2561    if chunk_len == 0 {
2562        return Ok(write);
2563    }
2564    if PAD {
2565        return Err(DecodeError::InvalidLength);
2566    }
2567
2568    decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
2569        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
2570        .map(|n| write + n)
2571}
2572
2573#[inline]
2574const fn is_legacy_whitespace(byte: u8) -> bool {
2575    matches!(byte, b' ' | b'\t' | b'\r' | b'\n')
2576}
2577
2578fn map_chunk_error(err: DecodeError, indexes: &[usize; 4]) -> DecodeError {
2579    match err {
2580        DecodeError::InvalidByte { index, byte } => DecodeError::InvalidByte {
2581            index: indexes[index],
2582            byte,
2583        },
2584        DecodeError::InvalidPadding { index } => DecodeError::InvalidPadding {
2585            index: indexes[index],
2586        },
2587        DecodeError::InvalidInput
2588        | DecodeError::InvalidLength
2589        | DecodeError::OutputTooSmall { .. } => err,
2590    }
2591}
2592
2593fn map_partial_chunk_error(err: DecodeError, indexes: &[usize; 4], len: usize) -> DecodeError {
2594    match err {
2595        DecodeError::InvalidByte { index, byte } if index < len => DecodeError::InvalidByte {
2596            index: indexes[index],
2597            byte,
2598        },
2599        DecodeError::InvalidPadding { index } if index < len => DecodeError::InvalidPadding {
2600            index: indexes[index],
2601        },
2602        DecodeError::InvalidByte { .. }
2603        | DecodeError::InvalidPadding { .. }
2604        | DecodeError::InvalidInput
2605        | DecodeError::InvalidLength
2606        | DecodeError::OutputTooSmall { .. } => err,
2607    }
2608}
2609
2610fn decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
2611    if !input.len().is_multiple_of(4) {
2612        return Err(DecodeError::InvalidLength);
2613    }
2614    let required = decoded_len_padded(input)?;
2615    if output.len() < required {
2616        return Err(DecodeError::OutputTooSmall {
2617            required,
2618            available: output.len(),
2619        });
2620    }
2621
2622    let mut read = 0;
2623    let mut write = 0;
2624    while read < input.len() {
2625        let written = decode_chunk::<A, true>(&input[read..read + 4], &mut output[write..])
2626            .map_err(|err| err.with_index_offset(read))?;
2627        read += 4;
2628        write += written;
2629        if written < 3 && read != input.len() {
2630            return Err(DecodeError::InvalidPadding { index: read - 4 });
2631        }
2632    }
2633    Ok(write)
2634}
2635
2636#[cfg(feature = "alloc")]
2637fn validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
2638    if input.is_empty() {
2639        return Ok(0);
2640    }
2641
2642    if PAD {
2643        validate_padded::<A>(input)
2644    } else {
2645        validate_unpadded::<A>(input)
2646    }
2647}
2648
2649#[cfg(feature = "alloc")]
2650fn validate_padded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
2651    if !input.len().is_multiple_of(4) {
2652        return Err(DecodeError::InvalidLength);
2653    }
2654    let required = decoded_len_padded(input)?;
2655
2656    let mut read = 0;
2657    while read < input.len() {
2658        let written = validate_chunk::<A, true>(&input[read..read + 4])
2659            .map_err(|err| err.with_index_offset(read))?;
2660        read += 4;
2661        if written < 3 && read != input.len() {
2662            return Err(DecodeError::InvalidPadding { index: read - 4 });
2663        }
2664    }
2665
2666    Ok(required)
2667}
2668
2669#[cfg(feature = "alloc")]
2670fn validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
2671    let required = decoded_len_unpadded(input)?;
2672
2673    let mut read = 0;
2674    while read + 4 <= input.len() {
2675        validate_chunk::<A, false>(&input[read..read + 4])
2676            .map_err(|err| err.with_index_offset(read))?;
2677        read += 4;
2678    }
2679    validate_tail_unpadded::<A>(&input[read..]).map_err(|err| err.with_index_offset(read))?;
2680
2681    Ok(required)
2682}
2683
2684fn decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
2685    let required = decoded_len_unpadded(input)?;
2686    if output.len() < required {
2687        return Err(DecodeError::OutputTooSmall {
2688            required,
2689            available: output.len(),
2690        });
2691    }
2692
2693    let mut read = 0;
2694    let mut write = 0;
2695    while read + 4 <= input.len() {
2696        let written = decode_chunk::<A, false>(&input[read..read + 4], &mut output[write..])
2697            .map_err(|err| err.with_index_offset(read))?;
2698        read += 4;
2699        write += written;
2700    }
2701    decode_tail_unpadded::<A>(&input[read..], &mut output[write..])
2702        .map_err(|err| err.with_index_offset(read))
2703        .map(|n| write + n)
2704}
2705
2706fn decoded_len_padded(input: &[u8]) -> Result<usize, DecodeError> {
2707    if input.is_empty() {
2708        return Ok(0);
2709    }
2710    if !input.len().is_multiple_of(4) {
2711        return Err(DecodeError::InvalidLength);
2712    }
2713    let mut padding = 0;
2714    if input[input.len() - 1] == b'=' {
2715        padding += 1;
2716    }
2717    if input[input.len() - 2] == b'=' {
2718        padding += 1;
2719    }
2720    if padding == 0
2721        && let Some(index) = input.iter().position(|byte| *byte == b'=')
2722    {
2723        return Err(DecodeError::InvalidPadding { index });
2724    }
2725    if padding > 0 {
2726        let first_pad = input.len() - padding;
2727        if let Some(index) = input[..first_pad].iter().position(|byte| *byte == b'=') {
2728            return Err(DecodeError::InvalidPadding { index });
2729        }
2730    }
2731    Ok(input.len() / 4 * 3 - padding)
2732}
2733
2734fn decoded_len_unpadded(input: &[u8]) -> Result<usize, DecodeError> {
2735    if input.len() % 4 == 1 {
2736        return Err(DecodeError::InvalidLength);
2737    }
2738    if let Some(index) = input.iter().position(|byte| *byte == b'=') {
2739        return Err(DecodeError::InvalidPadding { index });
2740    }
2741    Ok(decoded_capacity(input.len()))
2742}
2743
2744fn validate_chunk<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
2745    debug_assert_eq!(input.len(), 4);
2746    let _v0 = decode_byte::<A>(input[0], 0)?;
2747    let v1 = decode_byte::<A>(input[1], 1)?;
2748
2749    match (input[2], input[3]) {
2750        (b'=', b'=') if PAD => {
2751            if v1 & 0b0000_1111 != 0 {
2752                return Err(DecodeError::InvalidPadding { index: 1 });
2753            }
2754            Ok(1)
2755        }
2756        (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
2757        (_, b'=') if PAD => {
2758            let v2 = decode_byte::<A>(input[2], 2)?;
2759            if v2 & 0b0000_0011 != 0 {
2760                return Err(DecodeError::InvalidPadding { index: 2 });
2761            }
2762            Ok(2)
2763        }
2764        (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
2765            index: input.iter().position(|byte| *byte == b'=').unwrap_or(0),
2766        }),
2767        _ => {
2768            decode_byte::<A>(input[2], 2)?;
2769            decode_byte::<A>(input[3], 3)?;
2770            Ok(3)
2771        }
2772    }
2773}
2774
2775fn decode_chunk<A: Alphabet, const PAD: bool>(
2776    input: &[u8],
2777    output: &mut [u8],
2778) -> Result<usize, DecodeError> {
2779    debug_assert_eq!(input.len(), 4);
2780    let v0 = decode_byte::<A>(input[0], 0)?;
2781    let v1 = decode_byte::<A>(input[1], 1)?;
2782
2783    match (input[2], input[3]) {
2784        (b'=', b'=') if PAD => {
2785            if output.is_empty() {
2786                return Err(DecodeError::OutputTooSmall {
2787                    required: 1,
2788                    available: output.len(),
2789                });
2790            }
2791            if v1 & 0b0000_1111 != 0 {
2792                return Err(DecodeError::InvalidPadding { index: 1 });
2793            }
2794            output[0] = (v0 << 2) | (v1 >> 4);
2795            Ok(1)
2796        }
2797        (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
2798        (_, b'=') if PAD => {
2799            if output.len() < 2 {
2800                return Err(DecodeError::OutputTooSmall {
2801                    required: 2,
2802                    available: output.len(),
2803                });
2804            }
2805            let v2 = decode_byte::<A>(input[2], 2)?;
2806            if v2 & 0b0000_0011 != 0 {
2807                return Err(DecodeError::InvalidPadding { index: 2 });
2808            }
2809            output[0] = (v0 << 2) | (v1 >> 4);
2810            output[1] = (v1 << 4) | (v2 >> 2);
2811            Ok(2)
2812        }
2813        (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
2814            index: input.iter().position(|byte| *byte == b'=').unwrap_or(0),
2815        }),
2816        _ => {
2817            if output.len() < 3 {
2818                return Err(DecodeError::OutputTooSmall {
2819                    required: 3,
2820                    available: output.len(),
2821                });
2822            }
2823            let v2 = decode_byte::<A>(input[2], 2)?;
2824            let v3 = decode_byte::<A>(input[3], 3)?;
2825            output[0] = (v0 << 2) | (v1 >> 4);
2826            output[1] = (v1 << 4) | (v2 >> 2);
2827            output[2] = (v2 << 6) | v3;
2828            Ok(3)
2829        }
2830    }
2831}
2832
2833fn validate_tail_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
2834    match input.len() {
2835        0 => Ok(()),
2836        2 => {
2837            decode_byte::<A>(input[0], 0)?;
2838            let v1 = decode_byte::<A>(input[1], 1)?;
2839            if v1 & 0b0000_1111 != 0 {
2840                return Err(DecodeError::InvalidPadding { index: 1 });
2841            }
2842            Ok(())
2843        }
2844        3 => {
2845            decode_byte::<A>(input[0], 0)?;
2846            decode_byte::<A>(input[1], 1)?;
2847            let v2 = decode_byte::<A>(input[2], 2)?;
2848            if v2 & 0b0000_0011 != 0 {
2849                return Err(DecodeError::InvalidPadding { index: 2 });
2850            }
2851            Ok(())
2852        }
2853        _ => Err(DecodeError::InvalidLength),
2854    }
2855}
2856
2857fn decode_tail_unpadded<A: Alphabet>(
2858    input: &[u8],
2859    output: &mut [u8],
2860) -> Result<usize, DecodeError> {
2861    match input.len() {
2862        0 => Ok(0),
2863        2 => {
2864            if output.is_empty() {
2865                return Err(DecodeError::OutputTooSmall {
2866                    required: 1,
2867                    available: output.len(),
2868                });
2869            }
2870            let v0 = decode_byte::<A>(input[0], 0)?;
2871            let v1 = decode_byte::<A>(input[1], 1)?;
2872            if v1 & 0b0000_1111 != 0 {
2873                return Err(DecodeError::InvalidPadding { index: 1 });
2874            }
2875            output[0] = (v0 << 2) | (v1 >> 4);
2876            Ok(1)
2877        }
2878        3 => {
2879            if output.len() < 2 {
2880                return Err(DecodeError::OutputTooSmall {
2881                    required: 2,
2882                    available: output.len(),
2883                });
2884            }
2885            let v0 = decode_byte::<A>(input[0], 0)?;
2886            let v1 = decode_byte::<A>(input[1], 1)?;
2887            let v2 = decode_byte::<A>(input[2], 2)?;
2888            if v2 & 0b0000_0011 != 0 {
2889                return Err(DecodeError::InvalidPadding { index: 2 });
2890            }
2891            output[0] = (v0 << 2) | (v1 >> 4);
2892            output[1] = (v1 << 4) | (v2 >> 2);
2893            Ok(2)
2894        }
2895        _ => Err(DecodeError::InvalidLength),
2896    }
2897}
2898
2899fn decode_byte<A: Alphabet>(byte: u8, index: usize) -> Result<u8, DecodeError> {
2900    A::decode(byte).ok_or(DecodeError::InvalidByte { index, byte })
2901}
2902
2903fn ct_decode_slice<A: Alphabet, const PAD: bool>(
2904    input: &[u8],
2905    output: &mut [u8],
2906) -> Result<usize, DecodeError> {
2907    if input.is_empty() {
2908        return Ok(0);
2909    }
2910
2911    if PAD {
2912        ct_decode_padded::<A>(input, output)
2913    } else {
2914        ct_decode_unpadded::<A>(input, output)
2915    }
2916}
2917
2918fn ct_decode_in_place<A: Alphabet, const PAD: bool>(
2919    buffer: &mut [u8],
2920) -> Result<usize, DecodeError> {
2921    if buffer.is_empty() {
2922        return Ok(0);
2923    }
2924
2925    if PAD {
2926        ct_decode_padded_in_place::<A>(buffer)
2927    } else {
2928        ct_decode_unpadded_in_place::<A>(buffer)
2929    }
2930}
2931
2932fn ct_decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
2933    if !input.len().is_multiple_of(4) {
2934        return Err(DecodeError::InvalidLength);
2935    }
2936
2937    let padding = ct_padding_len(input);
2938    let required = input.len() / 4 * 3 - padding;
2939    if output.len() < required {
2940        return Err(DecodeError::OutputTooSmall {
2941            required,
2942            available: output.len(),
2943        });
2944    }
2945
2946    let mut invalid_byte = 0u8;
2947    let mut invalid_padding = 0u8;
2948    let mut write = 0;
2949    let mut read = 0;
2950
2951    while read < input.len() {
2952        let is_last = read + 4 == input.len();
2953        let b0 = input[read];
2954        let b1 = input[read + 1];
2955        let b2 = input[read + 2];
2956        let b3 = input[read + 3];
2957        let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
2958        let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
2959        let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
2960        let (v3, valid3) = ct_decode_ascii_base64::<A>(b3);
2961
2962        invalid_byte |= !valid0;
2963        invalid_byte |= !valid1;
2964
2965        if is_last && padding == 2 {
2966            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
2967            output[write] = (v0 << 2) | (v1 >> 4);
2968            write += 1;
2969        } else if is_last && padding == 1 {
2970            invalid_byte |= !valid2;
2971            invalid_padding |= ct_mask_eq_u8(b2, b'=');
2972            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
2973            output[write] = (v0 << 2) | (v1 >> 4);
2974            output[write + 1] = (v1 << 4) | (v2 >> 2);
2975            write += 2;
2976        } else {
2977            invalid_byte |= !valid2;
2978            invalid_byte |= !valid3;
2979            invalid_padding |= ct_mask_eq_u8(b2, b'=');
2980            invalid_padding |= ct_mask_eq_u8(b3, b'=');
2981            output[write] = (v0 << 2) | (v1 >> 4);
2982            output[write + 1] = (v1 << 4) | (v2 >> 2);
2983            output[write + 2] = (v2 << 6) | v3;
2984            write += 3;
2985        }
2986
2987        read += 4;
2988    }
2989
2990    report_ct_error(invalid_byte, invalid_padding)?;
2991    Ok(write)
2992}
2993
2994fn ct_decode_padded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
2995    if !buffer.len().is_multiple_of(4) {
2996        return Err(DecodeError::InvalidLength);
2997    }
2998
2999    let padding = ct_padding_len(buffer);
3000    let required = buffer.len() / 4 * 3 - padding;
3001    debug_assert!(required <= buffer.len());
3002
3003    let mut invalid_byte = 0u8;
3004    let mut invalid_padding = 0u8;
3005    let mut write = 0;
3006    let mut read = 0;
3007
3008    while read < buffer.len() {
3009        let is_last = read + 4 == buffer.len();
3010        let b0 = buffer[read];
3011        let b1 = buffer[read + 1];
3012        let b2 = buffer[read + 2];
3013        let b3 = buffer[read + 3];
3014        let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
3015        let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
3016        let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
3017        let (v3, valid3) = ct_decode_ascii_base64::<A>(b3);
3018
3019        invalid_byte |= !valid0;
3020        invalid_byte |= !valid1;
3021
3022        if is_last && padding == 2 {
3023            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
3024            buffer[write] = (v0 << 2) | (v1 >> 4);
3025            write += 1;
3026        } else if is_last && padding == 1 {
3027            invalid_byte |= !valid2;
3028            invalid_padding |= ct_mask_eq_u8(b2, b'=');
3029            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
3030            buffer[write] = (v0 << 2) | (v1 >> 4);
3031            buffer[write + 1] = (v1 << 4) | (v2 >> 2);
3032            write += 2;
3033        } else {
3034            invalid_byte |= !valid2;
3035            invalid_byte |= !valid3;
3036            invalid_padding |= ct_mask_eq_u8(b2, b'=');
3037            invalid_padding |= ct_mask_eq_u8(b3, b'=');
3038            buffer[write] = (v0 << 2) | (v1 >> 4);
3039            buffer[write + 1] = (v1 << 4) | (v2 >> 2);
3040            buffer[write + 2] = (v2 << 6) | v3;
3041            write += 3;
3042        }
3043
3044        read += 4;
3045    }
3046
3047    debug_assert_eq!(write, required);
3048    report_ct_error(invalid_byte, invalid_padding)?;
3049    Ok(write)
3050}
3051
3052fn ct_decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
3053    if input.len() % 4 == 1 {
3054        return Err(DecodeError::InvalidLength);
3055    }
3056
3057    let required = decoded_capacity(input.len());
3058    if output.len() < required {
3059        return Err(DecodeError::OutputTooSmall {
3060            required,
3061            available: output.len(),
3062        });
3063    }
3064
3065    let mut invalid_byte = 0u8;
3066    let mut invalid_padding = 0u8;
3067    let mut write = 0;
3068    let mut read = 0;
3069
3070    while read + 4 <= input.len() {
3071        let b0 = input[read];
3072        let b1 = input[read + 1];
3073        let b2 = input[read + 2];
3074        let b3 = input[read + 3];
3075        let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
3076        let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
3077        let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
3078        let (v3, valid3) = ct_decode_ascii_base64::<A>(b3);
3079
3080        invalid_byte |= !valid0;
3081        invalid_byte |= !valid1;
3082        invalid_byte |= !valid2;
3083        invalid_byte |= !valid3;
3084        invalid_padding |= ct_mask_eq_u8(b0, b'=');
3085        invalid_padding |= ct_mask_eq_u8(b1, b'=');
3086        invalid_padding |= ct_mask_eq_u8(b2, b'=');
3087        invalid_padding |= ct_mask_eq_u8(b3, b'=');
3088
3089        output[write] = (v0 << 2) | (v1 >> 4);
3090        output[write + 1] = (v1 << 4) | (v2 >> 2);
3091        output[write + 2] = (v2 << 6) | v3;
3092        read += 4;
3093        write += 3;
3094    }
3095
3096    match input.len() - read {
3097        0 => {}
3098        2 => {
3099            let b0 = input[read];
3100            let b1 = input[read + 1];
3101            let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
3102            let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
3103            invalid_byte |= !valid0;
3104            invalid_byte |= !valid1;
3105            invalid_padding |= ct_mask_eq_u8(b0, b'=');
3106            invalid_padding |= ct_mask_eq_u8(b1, b'=');
3107            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
3108            output[write] = (v0 << 2) | (v1 >> 4);
3109            write += 1;
3110        }
3111        3 => {
3112            let b0 = input[read];
3113            let b1 = input[read + 1];
3114            let b2 = input[read + 2];
3115            let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
3116            let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
3117            let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
3118            invalid_byte |= !valid0;
3119            invalid_byte |= !valid1;
3120            invalid_byte |= !valid2;
3121            invalid_padding |= ct_mask_eq_u8(b0, b'=');
3122            invalid_padding |= ct_mask_eq_u8(b1, b'=');
3123            invalid_padding |= ct_mask_eq_u8(b2, b'=');
3124            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
3125            output[write] = (v0 << 2) | (v1 >> 4);
3126            output[write + 1] = (v1 << 4) | (v2 >> 2);
3127            write += 2;
3128        }
3129        _ => return Err(DecodeError::InvalidLength),
3130    }
3131
3132    report_ct_error(invalid_byte, invalid_padding)?;
3133    Ok(write)
3134}
3135
3136fn ct_decode_unpadded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
3137    if buffer.len() % 4 == 1 {
3138        return Err(DecodeError::InvalidLength);
3139    }
3140
3141    let required = decoded_capacity(buffer.len());
3142    debug_assert!(required <= buffer.len());
3143
3144    let mut invalid_byte = 0u8;
3145    let mut invalid_padding = 0u8;
3146    let mut write = 0;
3147    let mut read = 0;
3148
3149    while read + 4 <= buffer.len() {
3150        let b0 = buffer[read];
3151        let b1 = buffer[read + 1];
3152        let b2 = buffer[read + 2];
3153        let b3 = buffer[read + 3];
3154        let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
3155        let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
3156        let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
3157        let (v3, valid3) = ct_decode_ascii_base64::<A>(b3);
3158
3159        invalid_byte |= !valid0;
3160        invalid_byte |= !valid1;
3161        invalid_byte |= !valid2;
3162        invalid_byte |= !valid3;
3163        invalid_padding |= ct_mask_eq_u8(b0, b'=');
3164        invalid_padding |= ct_mask_eq_u8(b1, b'=');
3165        invalid_padding |= ct_mask_eq_u8(b2, b'=');
3166        invalid_padding |= ct_mask_eq_u8(b3, b'=');
3167
3168        buffer[write] = (v0 << 2) | (v1 >> 4);
3169        buffer[write + 1] = (v1 << 4) | (v2 >> 2);
3170        buffer[write + 2] = (v2 << 6) | v3;
3171        read += 4;
3172        write += 3;
3173    }
3174
3175    match buffer.len() - read {
3176        0 => {}
3177        2 => {
3178            let b0 = buffer[read];
3179            let b1 = buffer[read + 1];
3180            let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
3181            let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
3182            invalid_byte |= !valid0;
3183            invalid_byte |= !valid1;
3184            invalid_padding |= ct_mask_eq_u8(b0, b'=');
3185            invalid_padding |= ct_mask_eq_u8(b1, b'=');
3186            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
3187            buffer[write] = (v0 << 2) | (v1 >> 4);
3188            write += 1;
3189        }
3190        3 => {
3191            let b0 = buffer[read];
3192            let b1 = buffer[read + 1];
3193            let b2 = buffer[read + 2];
3194            let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
3195            let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
3196            let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
3197            invalid_byte |= !valid0;
3198            invalid_byte |= !valid1;
3199            invalid_byte |= !valid2;
3200            invalid_padding |= ct_mask_eq_u8(b0, b'=');
3201            invalid_padding |= ct_mask_eq_u8(b1, b'=');
3202            invalid_padding |= ct_mask_eq_u8(b2, b'=');
3203            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
3204            buffer[write] = (v0 << 2) | (v1 >> 4);
3205            buffer[write + 1] = (v1 << 4) | (v2 >> 2);
3206            write += 2;
3207        }
3208        _ => return Err(DecodeError::InvalidLength),
3209    }
3210
3211    debug_assert_eq!(write, required);
3212    report_ct_error(invalid_byte, invalid_padding)?;
3213    Ok(write)
3214}
3215
3216#[inline]
3217fn ct_decode_ascii_base64<A: Alphabet>(byte: u8) -> (u8, u8) {
3218    let upper = ct_mask_lt_u8(byte.wrapping_sub(b'A'), 26);
3219    let lower = ct_mask_lt_u8(byte.wrapping_sub(b'a'), 26);
3220    let digit = ct_mask_lt_u8(byte.wrapping_sub(b'0'), 10);
3221    let value_62 = ct_mask_eq_u8(byte, A::ENCODE[62]);
3222    let value_63 = ct_mask_eq_u8(byte, A::ENCODE[63]);
3223    let valid = upper | lower | digit | value_62 | value_63;
3224
3225    let decoded = (byte.wrapping_sub(b'A') & upper)
3226        | (byte.wrapping_sub(b'a').wrapping_add(26) & lower)
3227        | (byte.wrapping_sub(b'0').wrapping_add(52) & digit)
3228        | (0x3e & value_62)
3229        | (0x3f & value_63);
3230
3231    (decoded, valid)
3232}
3233
3234fn ct_padding_len(input: &[u8]) -> usize {
3235    let last = input[input.len() - 1];
3236    let before_last = input[input.len() - 2];
3237    usize::from(ct_mask_eq_u8(last, b'=') & 1) + usize::from(ct_mask_eq_u8(before_last, b'=') & 1)
3238}
3239
3240fn report_ct_error(invalid_byte: u8, invalid_padding: u8) -> Result<(), DecodeError> {
3241    if (invalid_byte | invalid_padding) != 0 {
3242        Err(DecodeError::InvalidInput)
3243    } else {
3244        Ok(())
3245    }
3246}
3247
3248#[cfg(kani)]
3249mod kani_proofs {
3250    use super::{STANDARD, checked_encoded_len, decoded_capacity};
3251
3252    #[kani::proof]
3253    fn checked_encoded_len_is_bounded_for_small_inputs() {
3254        let len = usize::from(kani::any::<u8>());
3255        let padded = kani::any::<bool>();
3256        let encoded = checked_encoded_len(len, padded).expect("u8 input length cannot overflow");
3257
3258        assert!(encoded >= len);
3259        assert!(encoded <= len / 3 * 4 + 4);
3260    }
3261
3262    #[kani::proof]
3263    fn decoded_capacity_is_bounded_for_small_inputs() {
3264        let len = usize::from(kani::any::<u8>());
3265        let capacity = decoded_capacity(len);
3266
3267        assert!(capacity <= len / 4 * 3 + 2);
3268    }
3269
3270    #[kani::proof]
3271    #[kani::unwind(3)]
3272    fn standard_in_place_decode_returns_prefix_within_buffer() {
3273        let mut buffer = kani::any::<[u8; 8]>();
3274        let result = STANDARD.decode_in_place(&mut buffer);
3275
3276        if let Ok(decoded) = result {
3277            assert!(decoded.len() <= 8);
3278        }
3279    }
3280
3281    #[kani::proof]
3282    #[kani::unwind(3)]
3283    fn standard_clear_tail_decode_clears_buffer_on_error() {
3284        let mut buffer = kani::any::<[u8; 4]>();
3285        let result = STANDARD.decode_in_place_clear_tail(&mut buffer);
3286
3287        if result.is_err() {
3288            assert!(buffer.iter().all(|byte| *byte == 0));
3289        }
3290    }
3291}
3292
3293#[cfg(test)]
3294mod tests {
3295    use super::*;
3296
3297    fn fill_pattern(output: &mut [u8], seed: usize) {
3298        for (index, byte) in output.iter_mut().enumerate() {
3299            let value = (index * 73 + seed * 19) % 256;
3300            *byte = u8::try_from(value).unwrap();
3301        }
3302    }
3303
3304    fn assert_encode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
3305    where
3306        A: Alphabet,
3307    {
3308        let engine = Engine::<A, PAD>::new();
3309        let mut dispatched = [0x55; 256];
3310        let mut scalar = [0xaa; 256];
3311
3312        let dispatched_result = engine.encode_slice(input, &mut dispatched);
3313        let scalar_result = backend::scalar_reference_encode_slice::<A, PAD>(input, &mut scalar);
3314
3315        assert_eq!(dispatched_result, scalar_result);
3316        if let Ok(written) = dispatched_result {
3317            assert_eq!(&dispatched[..written], &scalar[..written]);
3318        }
3319
3320        let required = checked_encoded_len(input.len(), PAD).unwrap();
3321        if required > 0 {
3322            let mut dispatched_short = [0x55; 256];
3323            let mut scalar_short = [0xaa; 256];
3324            let available = required - 1;
3325
3326            assert_eq!(
3327                engine.encode_slice(input, &mut dispatched_short[..available]),
3328                backend::scalar_reference_encode_slice::<A, PAD>(
3329                    input,
3330                    &mut scalar_short[..available],
3331                )
3332            );
3333        }
3334    }
3335
3336    fn assert_decode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
3337    where
3338        A: Alphabet,
3339    {
3340        let engine = Engine::<A, PAD>::new();
3341        let mut dispatched = [0x55; 128];
3342        let mut scalar = [0xaa; 128];
3343
3344        let dispatched_result = engine.decode_slice(input, &mut dispatched);
3345        let scalar_result = backend::scalar_reference_decode_slice::<A, PAD>(input, &mut scalar);
3346
3347        assert_eq!(dispatched_result, scalar_result);
3348        if let Ok(written) = dispatched_result {
3349            assert_eq!(&dispatched[..written], &scalar[..written]);
3350
3351            if written > 0 {
3352                let mut dispatched_short = [0x55; 128];
3353                let mut scalar_short = [0xaa; 128];
3354                let available = written - 1;
3355
3356                assert_eq!(
3357                    engine.decode_slice(input, &mut dispatched_short[..available]),
3358                    backend::scalar_reference_decode_slice::<A, PAD>(
3359                        input,
3360                        &mut scalar_short[..available],
3361                    )
3362                );
3363            }
3364        }
3365    }
3366
3367    fn assert_backend_round_trip_matches_scalar<A, const PAD: bool>(input: &[u8])
3368    where
3369        A: Alphabet,
3370    {
3371        assert_encode_backend_matches_scalar::<A, PAD>(input);
3372
3373        let mut encoded = [0; 256];
3374        let encoded_len =
3375            backend::scalar_reference_encode_slice::<A, PAD>(input, &mut encoded).unwrap();
3376        assert_decode_backend_matches_scalar::<A, PAD>(&encoded[..encoded_len]);
3377    }
3378
3379    #[test]
3380    fn backend_dispatch_matches_scalar_reference_for_canonical_inputs() {
3381        let mut input = [0; 128];
3382
3383        for input_len in 0..=input.len() {
3384            fill_pattern(&mut input[..input_len], input_len);
3385            let input = &input[..input_len];
3386
3387            assert_backend_round_trip_matches_scalar::<Standard, true>(input);
3388            assert_backend_round_trip_matches_scalar::<Standard, false>(input);
3389            assert_backend_round_trip_matches_scalar::<UrlSafe, true>(input);
3390            assert_backend_round_trip_matches_scalar::<UrlSafe, false>(input);
3391        }
3392    }
3393
3394    #[test]
3395    fn backend_dispatch_matches_scalar_reference_for_malformed_inputs() {
3396        for input in [
3397            &b"Z"[..],
3398            b"====",
3399            b"AA=A",
3400            b"Zh==",
3401            b"Zm9=",
3402            b"Zm9v$g==",
3403            b"Zm9vZh==",
3404        ] {
3405            assert_decode_backend_matches_scalar::<Standard, true>(input);
3406        }
3407
3408        for input in [&b"Z"[..], b"AA=A", b"Zh", b"Zm9", b"Zm9vYg$"] {
3409            assert_decode_backend_matches_scalar::<Standard, false>(input);
3410        }
3411
3412        assert_decode_backend_matches_scalar::<UrlSafe, true>(b"AA+A");
3413        assert_decode_backend_matches_scalar::<UrlSafe, false>(b"AA/A");
3414        assert_decode_backend_matches_scalar::<Standard, true>(b"AA-A");
3415        assert_decode_backend_matches_scalar::<Standard, false>(b"AA_A");
3416    }
3417
3418    #[cfg(feature = "simd")]
3419    #[test]
3420    fn simd_dispatch_scaffold_keeps_scalar_active() {
3421        assert_eq!(simd::active_backend(), simd::ActiveBackend::Scalar);
3422        let _candidate = simd::detected_candidate();
3423    }
3424
3425    #[test]
3426    fn encodes_standard_vectors() {
3427        let vectors = [
3428            (&b""[..], &b""[..]),
3429            (&b"f"[..], &b"Zg=="[..]),
3430            (&b"fo"[..], &b"Zm8="[..]),
3431            (&b"foo"[..], &b"Zm9v"[..]),
3432            (&b"foob"[..], &b"Zm9vYg=="[..]),
3433            (&b"fooba"[..], &b"Zm9vYmE="[..]),
3434            (&b"foobar"[..], &b"Zm9vYmFy"[..]),
3435        ];
3436        for (input, expected) in vectors {
3437            let mut output = [0u8; 16];
3438            let written = STANDARD.encode_slice(input, &mut output).unwrap();
3439            assert_eq!(&output[..written], expected);
3440        }
3441    }
3442
3443    #[test]
3444    fn decodes_standard_vectors() {
3445        let vectors = [
3446            (&b""[..], &b""[..]),
3447            (&b"Zg=="[..], &b"f"[..]),
3448            (&b"Zm8="[..], &b"fo"[..]),
3449            (&b"Zm9v"[..], &b"foo"[..]),
3450            (&b"Zm9vYg=="[..], &b"foob"[..]),
3451            (&b"Zm9vYmE="[..], &b"fooba"[..]),
3452            (&b"Zm9vYmFy"[..], &b"foobar"[..]),
3453        ];
3454        for (input, expected) in vectors {
3455            let mut output = [0u8; 16];
3456            let written = STANDARD.decode_slice(input, &mut output).unwrap();
3457            assert_eq!(&output[..written], expected);
3458        }
3459    }
3460
3461    #[test]
3462    fn supports_unpadded_url_safe() {
3463        let mut encoded = [0u8; 16];
3464        let written = URL_SAFE_NO_PAD
3465            .encode_slice(b"\xfb\xff", &mut encoded)
3466            .unwrap();
3467        assert_eq!(&encoded[..written], b"-_8");
3468
3469        let mut decoded = [0u8; 2];
3470        let written = URL_SAFE_NO_PAD
3471            .decode_slice(&encoded[..written], &mut decoded)
3472            .unwrap();
3473        assert_eq!(&decoded[..written], b"\xfb\xff");
3474    }
3475
3476    #[test]
3477    fn decodes_in_place() {
3478        let mut buffer = *b"Zm9vYmFy";
3479        let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
3480        assert_eq!(decoded, b"foobar");
3481    }
3482
3483    #[test]
3484    fn rejects_non_canonical_padding_bits() {
3485        let mut output = [0u8; 4];
3486        assert_eq!(
3487            STANDARD.decode_slice(b"Zh==", &mut output),
3488            Err(DecodeError::InvalidPadding { index: 1 })
3489        );
3490        assert_eq!(
3491            STANDARD.decode_slice(b"Zm9=", &mut output),
3492            Err(DecodeError::InvalidPadding { index: 2 })
3493        );
3494    }
3495}