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