Skip to main content

base64_ng/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2#![deny(unsafe_code)]
3#![deny(missing_docs)]
4#![deny(clippy::all)]
5#![deny(clippy::pedantic)]
6#![allow(clippy::missing_errors_doc)]
7
8//! `base64-ng` is a `no_std`-first Base64 encoder and decoder.
9//!
10//! This initial release provides strict scalar RFC 4648-style behavior and
11//! caller-owned output buffers. Future SIMD fast paths will be required to
12//! match this scalar module byte-for-byte.
13//!
14//! # Examples
15//!
16//! Encode and decode with caller-owned buffers:
17//!
18//! ```
19//! use base64_ng::{STANDARD, checked_encoded_len};
20//!
21//! let input = b"hello";
22//! const ENCODED_CAPACITY: usize = match checked_encoded_len(5, true) {
23//!     Some(len) => len,
24//!     None => panic!("encoded length overflow"),
25//! };
26//! let mut encoded = [0u8; ENCODED_CAPACITY];
27//! let encoded_len = STANDARD.encode_slice(input, &mut encoded).unwrap();
28//! assert_eq!(&encoded[..encoded_len], b"aGVsbG8=");
29//!
30//! let mut decoded = [0u8; 5];
31//! let decoded_len = STANDARD.decode_slice(&encoded, &mut decoded).unwrap();
32//! assert_eq!(&decoded[..decoded_len], input);
33//! ```
34//!
35//! Use the URL-safe no-padding engine:
36//!
37//! ```
38//! use base64_ng::URL_SAFE_NO_PAD;
39//!
40//! let mut encoded = [0u8; 3];
41//! let encoded_len = URL_SAFE_NO_PAD.encode_slice(b"\xfb\xff", &mut encoded).unwrap();
42//! assert_eq!(&encoded[..encoded_len], b"-_8");
43//! ```
44
45#[cfg(feature = "alloc")]
46extern crate alloc;
47
48#[cfg(feature = "simd")]
49mod simd;
50
51/// Runtime backend reporting for security-sensitive deployments.
52///
53/// This module does not enable acceleration. It exposes the backend posture so
54/// callers can log, assert, or audit whether execution is scalar-only or merely
55/// detecting future SIMD candidates.
56pub mod runtime {
57    /// A backend that can be reported by `base64-ng`.
58    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
59    #[non_exhaustive]
60    pub enum Backend {
61        /// The audited scalar backend.
62        Scalar,
63        /// An AVX-512 VBMI candidate was detected.
64        Avx512Vbmi,
65        /// An AVX2 candidate was detected.
66        Avx2,
67        /// An ARM NEON candidate was detected.
68        Neon,
69    }
70
71    impl Backend {
72        /// Returns the stable lowercase identifier for this backend.
73        ///
74        /// ```
75        /// assert_eq!(base64_ng::runtime::Backend::Scalar.as_str(), "scalar");
76        /// ```
77        #[must_use]
78        pub const fn as_str(self) -> &'static str {
79            match self {
80                Self::Scalar => "scalar",
81                Self::Avx512Vbmi => "avx512-vbmi",
82                Self::Avx2 => "avx2",
83                Self::Neon => "neon",
84            }
85        }
86
87        /// Returns the CPU features required before this backend may be used.
88        ///
89        /// The active backend is still scalar-only. This method exists so
90        /// security logs can record exactly which future backend feature bundle
91        /// was detected.
92        ///
93        /// ```
94        /// assert_eq!(
95        ///     base64_ng::runtime::Backend::Avx512Vbmi.required_cpu_features(),
96        ///     ["avx512f", "avx512bw", "avx512vl", "avx512vbmi"],
97        /// );
98        /// ```
99        #[must_use]
100        pub const fn required_cpu_features(self) -> &'static [&'static str] {
101            match self {
102                Self::Scalar => &[],
103                Self::Avx512Vbmi => &["avx512f", "avx512bw", "avx512vl", "avx512vbmi"],
104                Self::Avx2 => &["avx2"],
105                Self::Neon => &["neon"],
106            }
107        }
108    }
109
110    impl core::fmt::Display for Backend {
111        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
112            formatter.write_str(self.as_str())
113        }
114    }
115
116    /// Security posture for the active runtime backend.
117    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
118    #[non_exhaustive]
119    pub enum SecurityPosture {
120        /// No accelerated backend is active.
121        ScalarOnly,
122        /// SIMD support may be detected, but execution still uses scalar.
123        SimdCandidateScalarActive,
124        /// A SIMD backend is active.
125        Accelerated,
126    }
127
128    impl SecurityPosture {
129        /// Returns the stable lowercase identifier for this security posture.
130        ///
131        /// ```
132        /// assert_eq!(
133        ///     base64_ng::runtime::SecurityPosture::ScalarOnly.as_str(),
134        ///     "scalar-only",
135        /// );
136        /// ```
137        #[must_use]
138        pub const fn as_str(self) -> &'static str {
139            match self {
140                Self::ScalarOnly => "scalar-only",
141                Self::SimdCandidateScalarActive => "simd-candidate-scalar-active",
142                Self::Accelerated => "accelerated",
143            }
144        }
145    }
146
147    impl core::fmt::Display for SecurityPosture {
148        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
149            formatter.write_str(self.as_str())
150        }
151    }
152
153    /// Deployment policy for runtime backend assertions.
154    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
155    #[non_exhaustive]
156    pub enum BackendPolicy {
157        /// Require encode/decode execution to use the scalar backend.
158        ScalarExecutionOnly,
159        /// Require the crate to be built without the `simd` feature.
160        SimdFeatureDisabled,
161        /// Require no SIMD candidate to be visible to this build and target.
162        NoDetectedSimdCandidate,
163        /// Require scalar execution, the `simd` feature disabled, no detected
164        /// SIMD candidate, and the unsafe boundary enforced.
165        HighAssuranceScalarOnly,
166    }
167
168    impl BackendPolicy {
169        /// Returns the stable lowercase identifier for this policy.
170        ///
171        /// ```
172        /// assert_eq!(
173        ///     base64_ng::runtime::BackendPolicy::HighAssuranceScalarOnly.as_str(),
174        ///     "high-assurance-scalar-only",
175        /// );
176        /// ```
177        #[must_use]
178        pub const fn as_str(self) -> &'static str {
179            match self {
180                Self::ScalarExecutionOnly => "scalar-execution-only",
181                Self::SimdFeatureDisabled => "simd-feature-disabled",
182                Self::NoDetectedSimdCandidate => "no-detected-simd-candidate",
183                Self::HighAssuranceScalarOnly => "high-assurance-scalar-only",
184            }
185        }
186    }
187
188    impl core::fmt::Display for BackendPolicy {
189        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
190            formatter.write_str(self.as_str())
191        }
192    }
193
194    /// Runtime backend policy failure.
195    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
196    pub struct BackendPolicyError {
197        /// Policy that was requested.
198        pub policy: BackendPolicy,
199        /// Backend report observed when the policy failed.
200        pub report: BackendReport,
201    }
202
203    impl core::fmt::Display for BackendPolicyError {
204        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
205            write!(
206                formatter,
207                "runtime backend policy `{}` was not satisfied ({})",
208                self.policy, self.report,
209            )
210        }
211    }
212
213    #[cfg(feature = "std")]
214    impl std::error::Error for BackendPolicyError {}
215
216    /// Backend report for the current build and target.
217    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
218    pub struct BackendReport {
219        /// Backend currently used for encode/decode dispatch.
220        pub active: Backend,
221        /// Strongest backend candidate visible to the current build.
222        pub candidate: Backend,
223        /// Whether the `simd` feature is enabled in this build.
224        pub simd_feature_enabled: bool,
225        /// Whether an accelerated SIMD backend is active.
226        pub accelerated_backend_active: bool,
227        /// Whether unsafe code is confined to the dedicated SIMD boundary.
228        pub unsafe_boundary_enforced: bool,
229        /// Current security posture.
230        pub security_posture: SecurityPosture,
231    }
232
233    /// Compact structured backend snapshot for logging and policy evidence.
234    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
235    pub struct BackendSnapshot {
236        /// Stable active backend identifier.
237        pub active: &'static str,
238        /// Stable detected candidate identifier.
239        pub candidate: &'static str,
240        /// CPU features required by the detected candidate.
241        pub candidate_required_cpu_features: &'static [&'static str],
242        /// Whether the `simd` feature is enabled in this build.
243        pub simd_feature_enabled: bool,
244        /// Whether an accelerated SIMD backend is active.
245        pub accelerated_backend_active: bool,
246        /// Whether unsafe code is confined to the dedicated SIMD boundary.
247        pub unsafe_boundary_enforced: bool,
248        /// Stable security posture identifier.
249        pub security_posture: &'static str,
250    }
251
252    impl core::fmt::Display for BackendReport {
253        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
254            write!(
255                formatter,
256                "active={} candidate={} candidate_required_cpu_features=",
257                self.active, self.candidate,
258            )?;
259            write_feature_list(formatter, self.candidate_required_cpu_features())?;
260            write!(
261                formatter,
262                " simd_feature_enabled={} accelerated_backend_active={} unsafe_boundary_enforced={} security_posture={}",
263                self.simd_feature_enabled,
264                self.accelerated_backend_active,
265                self.unsafe_boundary_enforced,
266                self.security_posture,
267            )
268        }
269    }
270
271    impl BackendReport {
272        /// Returns whether this report satisfies `policy`.
273        ///
274        /// ```
275        /// let report = base64_ng::runtime::backend_report();
276        ///
277        /// assert!(
278        ///     report.satisfies(base64_ng::runtime::BackendPolicy::ScalarExecutionOnly)
279        /// );
280        /// ```
281        #[must_use]
282        pub const fn satisfies(self, policy: BackendPolicy) -> bool {
283            match policy {
284                BackendPolicy::ScalarExecutionOnly => {
285                    matches!(self.active, Backend::Scalar) && !self.accelerated_backend_active
286                }
287                BackendPolicy::SimdFeatureDisabled => !self.simd_feature_enabled,
288                BackendPolicy::NoDetectedSimdCandidate => matches!(self.candidate, Backend::Scalar),
289                BackendPolicy::HighAssuranceScalarOnly => {
290                    matches!(self.active, Backend::Scalar)
291                        && matches!(self.candidate, Backend::Scalar)
292                        && !self.simd_feature_enabled
293                        && !self.accelerated_backend_active
294                        && self.unsafe_boundary_enforced
295                }
296            }
297        }
298
299        /// Returns the CPU features required by the detected candidate.
300        ///
301        /// ```
302        /// let report = base64_ng::runtime::backend_report();
303        ///
304        /// assert_eq!(
305        ///     report.candidate_required_cpu_features(),
306        ///     report.candidate.required_cpu_features(),
307        /// );
308        /// ```
309        #[must_use]
310        pub const fn candidate_required_cpu_features(self) -> &'static [&'static str] {
311            self.candidate.required_cpu_features()
312        }
313
314        /// Returns a compact structured snapshot with stable string values.
315        ///
316        /// ```
317        /// let snapshot = base64_ng::runtime::backend_report().snapshot();
318        ///
319        /// assert_eq!(snapshot.active, "scalar");
320        /// assert!(!snapshot.accelerated_backend_active);
321        /// ```
322        #[must_use]
323        pub const fn snapshot(self) -> BackendSnapshot {
324            BackendSnapshot {
325                active: self.active.as_str(),
326                candidate: self.candidate.as_str(),
327                candidate_required_cpu_features: self.candidate_required_cpu_features(),
328                simd_feature_enabled: self.simd_feature_enabled,
329                accelerated_backend_active: self.accelerated_backend_active,
330                unsafe_boundary_enforced: self.unsafe_boundary_enforced,
331                security_posture: self.security_posture.as_str(),
332            }
333        }
334    }
335
336    /// Returns the runtime backend report for this build and target.
337    ///
338    /// ```
339    /// let report = base64_ng::runtime::backend_report();
340    ///
341    /// assert_eq!(report.active, base64_ng::runtime::Backend::Scalar);
342    /// assert!(!report.accelerated_backend_active);
343    /// ```
344    #[must_use]
345    pub fn backend_report() -> BackendReport {
346        let active = active_backend();
347        let candidate = detected_candidate();
348        let accelerated_backend_active = active != Backend::Scalar;
349        let security_posture = if accelerated_backend_active {
350            SecurityPosture::Accelerated
351        } else if candidate != Backend::Scalar {
352            SecurityPosture::SimdCandidateScalarActive
353        } else {
354            SecurityPosture::ScalarOnly
355        };
356
357        BackendReport {
358            active,
359            candidate,
360            simd_feature_enabled: cfg!(feature = "simd"),
361            accelerated_backend_active,
362            unsafe_boundary_enforced: true,
363            security_posture,
364        }
365    }
366
367    /// Requires the current runtime backend report to satisfy `policy`.
368    ///
369    /// ```
370    /// base64_ng::runtime::require_backend_policy(
371    ///     base64_ng::runtime::BackendPolicy::ScalarExecutionOnly,
372    /// )
373    /// .unwrap();
374    /// ```
375    pub fn require_backend_policy(policy: BackendPolicy) -> Result<(), BackendPolicyError> {
376        let report = backend_report();
377        if report.satisfies(policy) {
378            Ok(())
379        } else {
380            Err(BackendPolicyError { policy, report })
381        }
382    }
383
384    fn write_feature_list(
385        formatter: &mut core::fmt::Formatter<'_>,
386        features: &[&str],
387    ) -> core::fmt::Result {
388        formatter.write_str("[")?;
389        let mut index = 0;
390        while index < features.len() {
391            if index != 0 {
392                formatter.write_str(",")?;
393            }
394            formatter.write_str(features[index])?;
395            index += 1;
396        }
397        formatter.write_str("]")
398    }
399
400    #[cfg(feature = "simd")]
401    fn active_backend() -> Backend {
402        match super::simd::active_backend() {
403            super::simd::ActiveBackend::Scalar => Backend::Scalar,
404        }
405    }
406
407    #[cfg(not(feature = "simd"))]
408    const fn active_backend() -> Backend {
409        Backend::Scalar
410    }
411
412    #[cfg(feature = "simd")]
413    fn detected_candidate() -> Backend {
414        match super::simd::detected_candidate() {
415            super::simd::Candidate::Scalar => Backend::Scalar,
416            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
417            super::simd::Candidate::Avx512Vbmi => Backend::Avx512Vbmi,
418            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
419            super::simd::Candidate::Avx2 => Backend::Avx2,
420            #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
421            super::simd::Candidate::Neon => Backend::Neon,
422        }
423    }
424
425    #[cfg(not(feature = "simd"))]
426    const fn detected_candidate() -> Backend {
427        Backend::Scalar
428    }
429}
430
431#[cfg(feature = "stream")]
432pub mod stream {
433    //! Streaming Base64 wrappers for `std::io`.
434    //!
435    //! ```
436    //! use std::io::{Read, Write};
437    //! use base64_ng::{STANDARD, stream::{Decoder, DecoderReader, Encoder, EncoderReader}};
438    //!
439    //! let mut encoder = Encoder::new(Vec::new(), STANDARD);
440    //! encoder.write_all(b"he").unwrap();
441    //! encoder.write_all(b"llo").unwrap();
442    //! let encoded = encoder.finish().unwrap();
443    //! assert_eq!(encoded, b"aGVsbG8=");
444    //!
445    //! let mut reader = EncoderReader::new(&b"hello"[..], STANDARD);
446    //! let mut encoded = String::new();
447    //! reader.read_to_string(&mut encoded).unwrap();
448    //! assert_eq!(encoded, "aGVsbG8=");
449    //!
450    //! let mut decoder = Decoder::new(Vec::new(), STANDARD);
451    //! decoder.write_all(b"aGVs").unwrap();
452    //! decoder.write_all(b"bG8=").unwrap();
453    //! let decoded = decoder.finish().unwrap();
454    //! assert_eq!(decoded, b"hello");
455    //!
456    //! let mut reader = DecoderReader::new(&b"aGVsbG8="[..], STANDARD);
457    //! let mut decoded = Vec::new();
458    //! reader.read_to_end(&mut decoded).unwrap();
459    //! assert_eq!(decoded, b"hello");
460    //! ```
461
462    use super::{Alphabet, DecodeError, EncodeError, Engine};
463    use std::io::{self, Read, Write};
464
465    struct OutputQueue<const CAP: usize> {
466        buffer: [u8; CAP],
467        start: usize,
468        len: usize,
469    }
470
471    impl<const CAP: usize> OutputQueue<CAP> {
472        const fn new() -> Self {
473            Self {
474                buffer: [0; CAP],
475                start: 0,
476                len: 0,
477            }
478        }
479
480        const fn is_empty(&self) -> bool {
481            self.len == 0
482        }
483
484        fn push_slice(&mut self, input: &[u8]) -> io::Result<()> {
485            if input.len() > self.available_capacity() {
486                return Err(io::Error::new(
487                    io::ErrorKind::InvalidInput,
488                    "base64 stream output queue capacity exceeded",
489                ));
490            }
491
492            let mut read = 0;
493            while read < input.len() {
494                let write = (self.start + self.len) % CAP;
495                self.buffer[write] = input[read];
496                self.len += 1;
497                read += 1;
498            }
499
500            Ok(())
501        }
502
503        fn pop_front(&mut self) -> Option<u8> {
504            if self.len == 0 {
505                return None;
506            }
507
508            let byte = self.buffer[self.start];
509            self.buffer[self.start] = 0;
510            self.start = (self.start + 1) % CAP;
511            self.len -= 1;
512            if self.len == 0 {
513                self.start = 0;
514            }
515            Some(byte)
516        }
517
518        fn clear_all(&mut self) {
519            crate::wipe_bytes(&mut self.buffer);
520            self.start = 0;
521            self.len = 0;
522        }
523
524        const fn available_capacity(&self) -> usize {
525            CAP - self.len
526        }
527    }
528
529    /// A streaming Base64 encoder for `std::io::Write`.
530    pub struct Encoder<W, A, const PAD: bool>
531    where
532        A: Alphabet,
533    {
534        inner: Option<W>,
535        engine: Engine<A, PAD>,
536        pending: [u8; 2],
537        pending_len: usize,
538    }
539
540    impl<W, A, const PAD: bool> Encoder<W, A, PAD>
541    where
542        A: Alphabet,
543    {
544        /// Creates a new streaming encoder.
545        #[must_use]
546        pub const fn new(inner: W, engine: Engine<A, PAD>) -> Self {
547            Self {
548                inner: Some(inner),
549                engine,
550                pending: [0; 2],
551                pending_len: 0,
552            }
553        }
554
555        /// Returns a shared reference to the wrapped writer.
556        #[must_use]
557        pub fn get_ref(&self) -> &W {
558            self.inner_ref()
559        }
560
561        /// Returns a mutable reference to the wrapped writer.
562        pub fn get_mut(&mut self) -> &mut W {
563            self.inner_mut()
564        }
565
566        /// Consumes the encoder without flushing pending input.
567        ///
568        /// Prefer [`Self::finish`] when the encoded output must be complete.
569        #[must_use]
570        pub fn into_inner(mut self) -> W {
571            self.take_inner()
572        }
573
574        fn inner_ref(&self) -> &W {
575            match &self.inner {
576                Some(inner) => inner,
577                None => unreachable!("stream encoder inner writer was already taken"),
578            }
579        }
580
581        fn inner_mut(&mut self) -> &mut W {
582            match &mut self.inner {
583                Some(inner) => inner,
584                None => unreachable!("stream encoder inner writer was already taken"),
585            }
586        }
587
588        fn take_inner(&mut self) -> W {
589            match self.inner.take() {
590                Some(inner) => inner,
591                None => unreachable!("stream encoder inner writer was already taken"),
592            }
593        }
594
595        fn clear_pending(&mut self) {
596            crate::wipe_bytes(&mut self.pending);
597            self.pending_len = 0;
598        }
599    }
600
601    impl<W, A, const PAD: bool> Drop for Encoder<W, A, PAD>
602    where
603        A: Alphabet,
604    {
605        fn drop(&mut self) {
606            self.clear_pending();
607        }
608    }
609
610    impl<W, A, const PAD: bool> Encoder<W, A, PAD>
611    where
612        W: Write,
613        A: Alphabet,
614    {
615        /// Writes any pending input, flushes the wrapped writer, and returns it.
616        pub fn finish(mut self) -> io::Result<W> {
617            self.write_pending_final()?;
618            self.inner_mut().flush()?;
619            Ok(self.take_inner())
620        }
621
622        fn write_pending_final(&mut self) -> io::Result<()> {
623            if self.pending_len == 0 {
624                return Ok(());
625            }
626
627            let mut encoded = [0u8; 4];
628            let written = self
629                .engine
630                .encode_slice(&self.pending[..self.pending_len], &mut encoded)
631                .map_err(encode_error_to_io)?;
632            self.inner_mut().write_all(&encoded[..written])?;
633            self.clear_pending();
634            Ok(())
635        }
636    }
637
638    impl<W, A, const PAD: bool> Write for Encoder<W, A, PAD>
639    where
640        W: Write,
641        A: Alphabet,
642    {
643        fn write(&mut self, input: &[u8]) -> io::Result<usize> {
644            if input.is_empty() {
645                return Ok(0);
646            }
647
648            let mut consumed = 0;
649            if self.pending_len > 0 {
650                let needed = 3 - self.pending_len;
651                if input.len() < needed {
652                    self.pending[self.pending_len..self.pending_len + input.len()]
653                        .copy_from_slice(input);
654                    self.pending_len += input.len();
655                    return Ok(input.len());
656                }
657
658                let mut chunk = [0u8; 3];
659                chunk[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
660                chunk[self.pending_len..].copy_from_slice(&input[..needed]);
661
662                let mut encoded = [0u8; 4];
663                let written = self
664                    .engine
665                    .encode_slice(&chunk, &mut encoded)
666                    .map_err(encode_error_to_io)?;
667                self.inner_mut().write_all(&encoded[..written])?;
668                self.clear_pending();
669                consumed += needed;
670            }
671
672            let remaining = &input[consumed..];
673            let full_len = remaining.len() / 3 * 3;
674            let mut offset = 0;
675            let mut encoded = [0u8; 1024];
676            while offset < full_len {
677                let mut take = core::cmp::min(full_len - offset, 768);
678                take -= take % 3;
679                debug_assert!(take > 0);
680
681                let written = self
682                    .engine
683                    .encode_slice(&remaining[offset..offset + take], &mut encoded)
684                    .map_err(encode_error_to_io)?;
685                self.inner_mut().write_all(&encoded[..written])?;
686                offset += take;
687            }
688
689            let tail = &remaining[full_len..];
690            self.pending[..tail.len()].copy_from_slice(tail);
691            self.pending_len = tail.len();
692
693            Ok(input.len())
694        }
695
696        fn flush(&mut self) -> io::Result<()> {
697            self.inner_mut().flush()
698        }
699    }
700
701    fn encode_error_to_io(err: EncodeError) -> io::Error {
702        io::Error::new(io::ErrorKind::InvalidInput, err)
703    }
704
705    /// A streaming Base64 decoder for `std::io::Write`.
706    pub struct Decoder<W, A, const PAD: bool>
707    where
708        A: Alphabet,
709    {
710        inner: Option<W>,
711        engine: Engine<A, PAD>,
712        pending: [u8; 4],
713        pending_len: usize,
714        finished: bool,
715    }
716
717    impl<W, A, const PAD: bool> Decoder<W, A, PAD>
718    where
719        A: Alphabet,
720    {
721        /// Creates a new streaming decoder.
722        #[must_use]
723        pub const fn new(inner: W, engine: Engine<A, PAD>) -> Self {
724            Self {
725                inner: Some(inner),
726                engine,
727                pending: [0; 4],
728                pending_len: 0,
729                finished: false,
730            }
731        }
732
733        /// Returns a shared reference to the wrapped writer.
734        #[must_use]
735        pub fn get_ref(&self) -> &W {
736            self.inner_ref()
737        }
738
739        /// Returns a mutable reference to the wrapped writer.
740        pub fn get_mut(&mut self) -> &mut W {
741            self.inner_mut()
742        }
743
744        /// Consumes the decoder without flushing pending input.
745        ///
746        /// Prefer [`Self::finish`] when the decoded output must be complete.
747        #[must_use]
748        pub fn into_inner(mut self) -> W {
749            self.take_inner()
750        }
751
752        fn inner_ref(&self) -> &W {
753            match &self.inner {
754                Some(inner) => inner,
755                None => unreachable!("stream decoder inner writer was already taken"),
756            }
757        }
758
759        fn inner_mut(&mut self) -> &mut W {
760            match &mut self.inner {
761                Some(inner) => inner,
762                None => unreachable!("stream decoder inner writer was already taken"),
763            }
764        }
765
766        fn take_inner(&mut self) -> W {
767            match self.inner.take() {
768                Some(inner) => inner,
769                None => unreachable!("stream decoder inner writer was already taken"),
770            }
771        }
772
773        fn clear_pending(&mut self) {
774            crate::wipe_bytes(&mut self.pending);
775            self.pending_len = 0;
776        }
777    }
778
779    impl<W, A, const PAD: bool> Drop for Decoder<W, A, PAD>
780    where
781        A: Alphabet,
782    {
783        fn drop(&mut self) {
784            self.clear_pending();
785        }
786    }
787
788    impl<W, A, const PAD: bool> Decoder<W, A, PAD>
789    where
790        W: Write,
791        A: Alphabet,
792    {
793        /// Validates final pending input, flushes the wrapped writer, and returns it.
794        pub fn finish(mut self) -> io::Result<W> {
795            self.write_pending_final()?;
796            self.inner_mut().flush()?;
797            Ok(self.take_inner())
798        }
799
800        fn write_pending_final(&mut self) -> io::Result<()> {
801            if self.pending_len == 0 {
802                return Ok(());
803            }
804
805            let mut decoded = [0u8; 3];
806            let written = self
807                .engine
808                .decode_slice(&self.pending[..self.pending_len], &mut decoded)
809                .map_err(decode_error_to_io)?;
810            self.inner_mut().write_all(&decoded[..written])?;
811            self.clear_pending();
812            Ok(())
813        }
814
815        fn write_full_quad(&mut self, input: [u8; 4]) -> io::Result<()> {
816            let mut decoded = [0u8; 3];
817            let written = self
818                .engine
819                .decode_slice(&input, &mut decoded)
820                .map_err(decode_error_to_io)?;
821            self.inner_mut().write_all(&decoded[..written])?;
822            if written < 3 {
823                self.finished = true;
824            }
825            Ok(())
826        }
827    }
828
829    impl<W, A, const PAD: bool> Write for Decoder<W, A, PAD>
830    where
831        W: Write,
832        A: Alphabet,
833    {
834        fn write(&mut self, input: &[u8]) -> io::Result<usize> {
835            if input.is_empty() {
836                return Ok(0);
837            }
838            if self.finished {
839                return Err(trailing_input_after_padding_error());
840            }
841
842            let mut consumed = 0;
843            if self.pending_len > 0 {
844                let needed = 4 - self.pending_len;
845                if input.len() < needed {
846                    self.pending[self.pending_len..self.pending_len + input.len()]
847                        .copy_from_slice(input);
848                    self.pending_len += input.len();
849                    return Ok(input.len());
850                }
851
852                let mut quad = [0u8; 4];
853                quad[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
854                quad[self.pending_len..].copy_from_slice(&input[..needed]);
855                self.write_full_quad(quad)?;
856                self.clear_pending();
857                consumed += needed;
858                if self.finished && consumed < input.len() {
859                    return Err(trailing_input_after_padding_error());
860                }
861            }
862
863            let remaining = &input[consumed..];
864            let full_len = remaining.len() / 4 * 4;
865            let mut offset = 0;
866            while offset < full_len {
867                let quad = [
868                    remaining[offset],
869                    remaining[offset + 1],
870                    remaining[offset + 2],
871                    remaining[offset + 3],
872                ];
873                self.write_full_quad(quad)?;
874                offset += 4;
875                if self.finished && offset < remaining.len() {
876                    return Err(trailing_input_after_padding_error());
877                }
878            }
879
880            let tail = &remaining[full_len..];
881            self.pending[..tail.len()].copy_from_slice(tail);
882            self.pending_len = tail.len();
883
884            Ok(input.len())
885        }
886
887        fn flush(&mut self) -> io::Result<()> {
888            self.inner_mut().flush()
889        }
890    }
891
892    fn decode_error_to_io(err: DecodeError) -> io::Error {
893        io::Error::new(io::ErrorKind::InvalidInput, err)
894    }
895
896    fn trailing_input_after_padding_error() -> io::Error {
897        io::Error::new(
898            io::ErrorKind::InvalidInput,
899            "base64 decoder received trailing input after padding",
900        )
901    }
902
903    /// A streaming Base64 decoder for `std::io::Read`.
904    ///
905    /// For padded engines, this reader stops at the terminal padded Base64
906    /// block and leaves later bytes unread in the wrapped reader. This preserves
907    /// boundaries for callers that decode one Base64 payload from a larger
908    /// stream.
909    pub struct DecoderReader<R, A, const PAD: bool>
910    where
911        A: Alphabet,
912    {
913        inner: Option<R>,
914        engine: Engine<A, PAD>,
915        pending: [u8; 4],
916        pending_len: usize,
917        output: OutputQueue<3>,
918        finished: bool,
919        terminal_seen: bool,
920    }
921
922    impl<R, A, const PAD: bool> DecoderReader<R, A, PAD>
923    where
924        A: Alphabet,
925    {
926        /// Creates a new streaming decoder reader.
927        #[must_use]
928        pub fn new(inner: R, engine: Engine<A, PAD>) -> Self {
929            Self {
930                inner: Some(inner),
931                engine,
932                pending: [0; 4],
933                pending_len: 0,
934                output: OutputQueue::new(),
935                finished: false,
936                terminal_seen: false,
937            }
938        }
939
940        /// Returns a shared reference to the wrapped reader.
941        #[must_use]
942        pub fn get_ref(&self) -> &R {
943            self.inner_ref()
944        }
945
946        /// Returns a mutable reference to the wrapped reader.
947        pub fn get_mut(&mut self) -> &mut R {
948            self.inner_mut()
949        }
950
951        /// Consumes the decoder reader and returns the wrapped reader.
952        #[must_use]
953        pub fn into_inner(mut self) -> R {
954            self.take_inner()
955        }
956
957        fn inner_ref(&self) -> &R {
958            match &self.inner {
959                Some(inner) => inner,
960                None => unreachable!("stream decoder reader inner reader was already taken"),
961            }
962        }
963
964        fn inner_mut(&mut self) -> &mut R {
965            match &mut self.inner {
966                Some(inner) => inner,
967                None => unreachable!("stream decoder reader inner reader was already taken"),
968            }
969        }
970
971        fn take_inner(&mut self) -> R {
972            match self.inner.take() {
973                Some(inner) => inner,
974                None => unreachable!("stream decoder reader inner reader was already taken"),
975            }
976        }
977
978        fn clear_pending(&mut self) {
979            crate::wipe_bytes(&mut self.pending);
980            self.pending_len = 0;
981        }
982    }
983
984    impl<R, A, const PAD: bool> Drop for DecoderReader<R, A, PAD>
985    where
986        A: Alphabet,
987    {
988        fn drop(&mut self) {
989            self.clear_pending();
990            self.output.clear_all();
991        }
992    }
993
994    impl<R, A, const PAD: bool> Read for DecoderReader<R, A, PAD>
995    where
996        R: Read,
997        A: Alphabet,
998    {
999        fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
1000            if output.is_empty() {
1001                return Ok(0);
1002            }
1003
1004            while self.output.is_empty() && !self.finished {
1005                self.fill_output()?;
1006            }
1007
1008            let mut written = 0;
1009            while written < output.len() {
1010                let Some(byte) = self.output.pop_front() else {
1011                    break;
1012                };
1013                output[written] = byte;
1014                written += 1;
1015            }
1016
1017            Ok(written)
1018        }
1019    }
1020
1021    impl<R, A, const PAD: bool> DecoderReader<R, A, PAD>
1022    where
1023        R: Read,
1024        A: Alphabet,
1025    {
1026        fn fill_output(&mut self) -> io::Result<()> {
1027            if self.terminal_seen {
1028                self.finished = true;
1029                return Ok(());
1030            }
1031
1032            let mut input = [0u8; 4];
1033            let available = 4 - self.pending_len;
1034            let read = self.inner_mut().read(&mut input[..available])?;
1035            if read == 0 {
1036                self.finished = true;
1037                self.push_final_pending()?;
1038                return Ok(());
1039            }
1040
1041            self.pending[self.pending_len..self.pending_len + read].copy_from_slice(&input[..read]);
1042            self.pending_len += read;
1043            if self.pending_len < 4 {
1044                return Ok(());
1045            }
1046
1047            let quad = self.pending;
1048            self.clear_pending();
1049            self.push_decoded(&quad)?;
1050            if self.terminal_seen {
1051                self.finished = true;
1052            }
1053            Ok(())
1054        }
1055
1056        fn push_final_pending(&mut self) -> io::Result<()> {
1057            if self.pending_len == 0 {
1058                return Ok(());
1059            }
1060
1061            let mut pending = [0u8; 4];
1062            pending[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
1063            let pending_len = self.pending_len;
1064            self.clear_pending();
1065            self.push_decoded(&pending[..pending_len])
1066        }
1067
1068        fn push_decoded(&mut self, input: &[u8]) -> io::Result<()> {
1069            let mut decoded = [0u8; 3];
1070            let written = self
1071                .engine
1072                .decode_slice(input, &mut decoded)
1073                .map_err(decode_error_to_io)?;
1074            self.output.push_slice(&decoded[..written])?;
1075            if input.len() == 4 && written < 3 {
1076                self.terminal_seen = true;
1077            }
1078            Ok(())
1079        }
1080    }
1081
1082    /// A streaming Base64 encoder for `std::io::Read`.
1083    pub struct EncoderReader<R, A, const PAD: bool>
1084    where
1085        A: Alphabet,
1086    {
1087        inner: Option<R>,
1088        engine: Engine<A, PAD>,
1089        pending: [u8; 2],
1090        pending_len: usize,
1091        output: OutputQueue<1024>,
1092        finished: bool,
1093    }
1094
1095    impl<R, A, const PAD: bool> EncoderReader<R, A, PAD>
1096    where
1097        A: Alphabet,
1098    {
1099        /// Creates a new streaming encoder reader.
1100        #[must_use]
1101        pub fn new(inner: R, engine: Engine<A, PAD>) -> Self {
1102            Self {
1103                inner: Some(inner),
1104                engine,
1105                pending: [0; 2],
1106                pending_len: 0,
1107                output: OutputQueue::new(),
1108                finished: false,
1109            }
1110        }
1111
1112        /// Returns a shared reference to the wrapped reader.
1113        #[must_use]
1114        pub fn get_ref(&self) -> &R {
1115            self.inner_ref()
1116        }
1117
1118        /// Returns a mutable reference to the wrapped reader.
1119        pub fn get_mut(&mut self) -> &mut R {
1120            self.inner_mut()
1121        }
1122
1123        /// Consumes the encoder reader and returns the wrapped reader.
1124        #[must_use]
1125        pub fn into_inner(mut self) -> R {
1126            self.take_inner()
1127        }
1128
1129        fn inner_ref(&self) -> &R {
1130            match &self.inner {
1131                Some(inner) => inner,
1132                None => unreachable!("stream encoder reader inner reader was already taken"),
1133            }
1134        }
1135
1136        fn inner_mut(&mut self) -> &mut R {
1137            match &mut self.inner {
1138                Some(inner) => inner,
1139                None => unreachable!("stream encoder reader inner reader was already taken"),
1140            }
1141        }
1142
1143        fn take_inner(&mut self) -> R {
1144            match self.inner.take() {
1145                Some(inner) => inner,
1146                None => unreachable!("stream encoder reader inner reader was already taken"),
1147            }
1148        }
1149
1150        fn clear_pending(&mut self) {
1151            crate::wipe_bytes(&mut self.pending);
1152            self.pending_len = 0;
1153        }
1154    }
1155
1156    impl<R, A, const PAD: bool> Drop for EncoderReader<R, A, PAD>
1157    where
1158        A: Alphabet,
1159    {
1160        fn drop(&mut self) {
1161            self.clear_pending();
1162            self.output.clear_all();
1163        }
1164    }
1165
1166    impl<R, A, const PAD: bool> Read for EncoderReader<R, A, PAD>
1167    where
1168        R: Read,
1169        A: Alphabet,
1170    {
1171        fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
1172            if output.is_empty() {
1173                return Ok(0);
1174            }
1175
1176            while self.output.is_empty() && !self.finished {
1177                self.fill_output()?;
1178            }
1179
1180            let mut written = 0;
1181            while written < output.len() {
1182                let Some(byte) = self.output.pop_front() else {
1183                    break;
1184                };
1185                output[written] = byte;
1186                written += 1;
1187            }
1188
1189            Ok(written)
1190        }
1191    }
1192
1193    impl<R, A, const PAD: bool> EncoderReader<R, A, PAD>
1194    where
1195        R: Read,
1196        A: Alphabet,
1197    {
1198        fn fill_output(&mut self) -> io::Result<()> {
1199            let mut input = [0u8; 768];
1200            let read = self.inner_mut().read(&mut input)?;
1201            if read == 0 {
1202                self.finished = true;
1203                self.push_final_pending()?;
1204                return Ok(());
1205            }
1206
1207            let mut consumed = 0;
1208            if self.pending_len > 0 {
1209                let needed = 3 - self.pending_len;
1210                if read < needed {
1211                    self.pending[self.pending_len..self.pending_len + read]
1212                        .copy_from_slice(&input[..read]);
1213                    self.pending_len += read;
1214                    return Ok(());
1215                }
1216
1217                let mut chunk = [0u8; 3];
1218                chunk[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
1219                chunk[self.pending_len..].copy_from_slice(&input[..needed]);
1220                self.push_encoded(&chunk)?;
1221                self.clear_pending();
1222                consumed += needed;
1223            }
1224
1225            let remaining = &input[consumed..read];
1226            let full_len = remaining.len() / 3 * 3;
1227            if full_len > 0 {
1228                self.push_encoded(&remaining[..full_len])?;
1229            }
1230
1231            let tail = &remaining[full_len..];
1232            self.pending[..tail.len()].copy_from_slice(tail);
1233            self.pending_len = tail.len();
1234            Ok(())
1235        }
1236
1237        fn push_final_pending(&mut self) -> io::Result<()> {
1238            if self.pending_len == 0 {
1239                return Ok(());
1240            }
1241
1242            let mut pending = [0u8; 2];
1243            pending[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
1244            let pending_len = self.pending_len;
1245            self.clear_pending();
1246            self.push_encoded(&pending[..pending_len])
1247        }
1248
1249        fn push_encoded(&mut self, input: &[u8]) -> io::Result<()> {
1250            let mut encoded = [0u8; 1024];
1251            let written = self
1252                .engine
1253                .encode_slice(input, &mut encoded)
1254                .map_err(encode_error_to_io)?;
1255            self.output.push_slice(&encoded[..written])?;
1256            Ok(())
1257        }
1258    }
1259}
1260
1261/// Constant-time-oriented scalar decoding APIs.
1262///
1263/// This module is separate from the default decoder so callers can opt into a
1264/// slower path with a narrower timing target. It avoids lookup tables indexed
1265/// by secret input bytes while mapping Base64 symbols and reports malformed
1266/// content through one opaque error. It is not documented as a formally
1267/// verified cryptographic constant-time API.
1268pub mod ct {
1269    use super::{
1270        Alphabet, DecodeError, Standard, UrlSafe, ct_decode_in_place, ct_decode_slice,
1271        ct_validate_decode,
1272    };
1273    use core::marker::PhantomData;
1274
1275    /// Standard Base64 constant-time-oriented decoder with padding.
1276    pub const STANDARD: CtEngine<Standard, true> = CtEngine::new();
1277
1278    /// Standard Base64 constant-time-oriented decoder without padding.
1279    pub const STANDARD_NO_PAD: CtEngine<Standard, false> = CtEngine::new();
1280
1281    /// URL-safe Base64 constant-time-oriented decoder with padding.
1282    pub const URL_SAFE: CtEngine<UrlSafe, true> = CtEngine::new();
1283
1284    /// URL-safe Base64 constant-time-oriented decoder without padding.
1285    pub const URL_SAFE_NO_PAD: CtEngine<UrlSafe, false> = CtEngine::new();
1286
1287    /// A zero-sized constant-time-oriented Base64 decoder.
1288    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1289    pub struct CtEngine<A, const PAD: bool> {
1290        alphabet: PhantomData<A>,
1291    }
1292
1293    impl<A, const PAD: bool> CtEngine<A, PAD>
1294    where
1295        A: Alphabet,
1296    {
1297        /// Creates a new constant-time-oriented decoder engine.
1298        #[must_use]
1299        pub const fn new() -> Self {
1300            Self {
1301                alphabet: PhantomData,
1302            }
1303        }
1304
1305        /// Validates `input` without writing decoded bytes.
1306        ///
1307        /// This uses the same constant-time-oriented symbol mapping and opaque
1308        /// malformed-input error behavior as [`Self::decode_slice`]. Input
1309        /// length, padding length, and final success or failure remain public.
1310        ///
1311        /// # Examples
1312        ///
1313        /// ```
1314        /// use base64_ng::ct;
1315        ///
1316        /// ct::STANDARD.validate_result(b"aGVsbG8=").unwrap();
1317        /// assert!(ct::STANDARD.validate_result(b"aGVsbG8").is_err());
1318        /// ```
1319        pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
1320            ct_validate_decode::<A, PAD>(input)
1321        }
1322
1323        /// Returns whether `input` is valid for this constant-time-oriented
1324        /// decoder.
1325        ///
1326        /// This is a convenience wrapper around [`Self::validate_result`].
1327        ///
1328        /// # Examples
1329        ///
1330        /// ```
1331        /// use base64_ng::ct;
1332        ///
1333        /// assert!(ct::URL_SAFE_NO_PAD.validate(b"-_8"));
1334        /// assert!(!ct::URL_SAFE_NO_PAD.validate(b"+/8"));
1335        /// ```
1336        #[must_use]
1337        pub fn validate(&self, input: &[u8]) -> bool {
1338            self.validate_result(input).is_ok()
1339        }
1340
1341        /// Decodes `input` into `output`, returning the number of bytes
1342        /// written.
1343        ///
1344        /// This path uses branch-minimized arithmetic for Base64 symbol
1345        /// mapping and avoids secret-indexed lookup tables. Input length,
1346        /// padding length, output length, and final success or failure remain
1347        /// public. Malformed content errors are intentionally opaque and
1348        /// non-localized; use the normal strict decoder when exact diagnostics
1349        /// are required.
1350        ///
1351        /// # Examples
1352        ///
1353        /// ```
1354        /// use base64_ng::ct;
1355        ///
1356        /// let mut output = [0u8; 5];
1357        /// let written = ct::STANDARD
1358        ///     .decode_slice(b"aGVsbG8=", &mut output)
1359        ///     .unwrap();
1360        ///
1361        /// assert_eq!(&output[..written], b"hello");
1362        /// ```
1363        pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
1364            ct_decode_slice::<A, PAD>(input, output)
1365        }
1366
1367        /// Decodes `input` into `output` and clears all bytes after the
1368        /// decoded prefix.
1369        ///
1370        /// If decoding fails, the entire output buffer is cleared before the
1371        /// error is returned. Use this variant for sensitive payloads where
1372        /// partially decoded bytes from rejected input should not remain in the
1373        /// caller-owned output buffer.
1374        ///
1375        /// # Examples
1376        ///
1377        /// ```
1378        /// use base64_ng::ct;
1379        ///
1380        /// let mut output = [0xff; 8];
1381        /// let written = ct::STANDARD
1382        ///     .decode_slice_clear_tail(b"aGk=", &mut output)
1383        ///     .unwrap();
1384        ///
1385        /// assert_eq!(&output[..written], b"hi");
1386        /// assert!(output[written..].iter().all(|byte| *byte == 0));
1387        /// ```
1388        pub fn decode_slice_clear_tail(
1389            &self,
1390            input: &[u8],
1391            output: &mut [u8],
1392        ) -> Result<usize, DecodeError> {
1393            let written = match self.decode_slice(input, output) {
1394                Ok(written) => written,
1395                Err(err) => {
1396                    crate::wipe_bytes(output);
1397                    return Err(err);
1398                }
1399            };
1400            crate::wipe_tail(output, written);
1401            Ok(written)
1402        }
1403
1404        /// Decodes `buffer` in place and returns the decoded prefix.
1405        ///
1406        /// This uses the constant-time-oriented scalar decoder while reading
1407        /// each Base64 quantum into local values before writing decoded bytes
1408        /// back to the front of the same buffer.
1409        ///
1410        /// # Examples
1411        ///
1412        /// ```
1413        /// use base64_ng::ct;
1414        ///
1415        /// let mut buffer = *b"aGk=";
1416        /// let decoded = ct::STANDARD.decode_in_place(&mut buffer).unwrap();
1417        ///
1418        /// assert_eq!(decoded, b"hi");
1419        /// ```
1420        pub fn decode_in_place<'a>(
1421            &self,
1422            buffer: &'a mut [u8],
1423        ) -> Result<&'a mut [u8], DecodeError> {
1424            let len = ct_decode_in_place::<A, PAD>(buffer)?;
1425            Ok(&mut buffer[..len])
1426        }
1427
1428        /// Decodes `buffer` in place and clears all bytes after the decoded
1429        /// prefix.
1430        ///
1431        /// If decoding fails, the entire buffer is cleared before the error is
1432        /// returned.
1433        ///
1434        /// # Examples
1435        ///
1436        /// ```
1437        /// use base64_ng::ct;
1438        ///
1439        /// let mut buffer = *b"aGk=";
1440        /// let decoded = ct::STANDARD.decode_in_place_clear_tail(&mut buffer).unwrap();
1441        ///
1442        /// assert_eq!(decoded, b"hi");
1443        /// ```
1444        pub fn decode_in_place_clear_tail<'a>(
1445            &self,
1446            buffer: &'a mut [u8],
1447        ) -> Result<&'a mut [u8], DecodeError> {
1448            let len = match ct_decode_in_place::<A, PAD>(buffer) {
1449                Ok(len) => len,
1450                Err(err) => {
1451                    crate::wipe_bytes(buffer);
1452                    return Err(err);
1453                }
1454            };
1455            crate::wipe_tail(buffer, len);
1456            Ok(&mut buffer[..len])
1457        }
1458    }
1459}
1460
1461/// Standard Base64 engine with padding.
1462pub const STANDARD: Engine<Standard, true> = Engine::new();
1463
1464/// Standard Base64 engine without padding.
1465pub const STANDARD_NO_PAD: Engine<Standard, false> = Engine::new();
1466
1467/// URL-safe Base64 engine with padding.
1468pub const URL_SAFE: Engine<UrlSafe, true> = Engine::new();
1469
1470/// URL-safe Base64 engine without padding.
1471pub const URL_SAFE_NO_PAD: Engine<UrlSafe, false> = Engine::new();
1472
1473/// bcrypt-style Base64 engine without padding.
1474///
1475/// This uses the bcrypt alphabet with the crate's normal Base64 bit packing.
1476/// It does not parse complete bcrypt password-hash strings.
1477pub const BCRYPT_NO_PAD: Engine<Bcrypt, false> = Engine::new();
1478
1479/// Unix `crypt(3)`-style Base64 engine without padding.
1480///
1481/// This uses the `crypt(3)` alphabet with the crate's normal Base64 bit
1482/// packing. It does not parse complete password-hash strings.
1483pub const CRYPT_NO_PAD: Engine<Crypt, false> = Engine::new();
1484
1485/// Line ending used by wrapped Base64 output.
1486#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1487pub enum LineEnding {
1488    /// Line feed (`\n`).
1489    Lf,
1490    /// Carriage return followed by line feed (`\r\n`).
1491    CrLf,
1492}
1493
1494impl LineEnding {
1495    /// Returns the byte representation of this line ending.
1496    #[must_use]
1497    pub const fn as_bytes(self) -> &'static [u8] {
1498        match self {
1499            Self::Lf => b"\n",
1500            Self::CrLf => b"\r\n",
1501        }
1502    }
1503
1504    /// Returns the byte length of this line ending.
1505    #[must_use]
1506    pub const fn byte_len(self) -> usize {
1507        match self {
1508            Self::Lf => 1,
1509            Self::CrLf => 2,
1510        }
1511    }
1512}
1513
1514/// Base64 line wrapping policy.
1515///
1516/// `line_len` is measured in encoded Base64 bytes, not source input bytes.
1517/// Encoders insert line endings between lines and do not append a trailing line
1518/// ending after the final line.
1519#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1520pub struct LineWrap {
1521    /// Maximum encoded bytes per line.
1522    pub line_len: usize,
1523    /// Line ending inserted between wrapped lines.
1524    pub line_ending: LineEnding,
1525}
1526
1527impl LineWrap {
1528    /// MIME-style wrapping: 76 columns with CRLF endings.
1529    pub const MIME: Self = Self::new(76, LineEnding::CrLf);
1530    /// PEM-style wrapping: 64 columns with LF endings.
1531    pub const PEM: Self = Self::new(64, LineEnding::Lf);
1532    /// PEM-style wrapping: 64 columns with CRLF endings.
1533    pub const PEM_CRLF: Self = Self::new(64, LineEnding::CrLf);
1534
1535    /// Creates a wrapping policy.
1536    #[must_use]
1537    pub const fn new(line_len: usize, line_ending: LineEnding) -> Self {
1538        Self {
1539            line_len,
1540            line_ending,
1541        }
1542    }
1543}
1544
1545#[allow(unsafe_code)]
1546fn wipe_bytes(bytes: &mut [u8]) {
1547    for byte in bytes {
1548        // SAFETY: `byte` comes from a unique mutable slice iterator, so the
1549        // pointer is non-null, aligned, valid for one `u8` write, and does not
1550        // alias another live mutable reference during this iteration.
1551        unsafe {
1552            core::ptr::write_volatile(byte, 0);
1553        }
1554    }
1555    core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
1556}
1557
1558fn wipe_tail(bytes: &mut [u8], start: usize) {
1559    wipe_bytes(&mut bytes[start..]);
1560}
1561
1562/// Stack-backed encoded Base64 output.
1563///
1564/// This type is intended for short values where heap allocation would be
1565/// unnecessary but manually sizing and passing a separate output slice is
1566/// noisy. Its visible bytes are produced by crate encoders, so [`Self::as_str`]
1567/// can return `&str` without exposing a fallible UTF-8 conversion to callers.
1568///
1569/// The backing array is cleared when the value is dropped. This is best-effort
1570/// data-retention reduction and is not a formal zeroization guarantee.
1571pub struct EncodedBuffer<const CAP: usize> {
1572    bytes: [u8; CAP],
1573    len: usize,
1574}
1575
1576impl<const CAP: usize> EncodedBuffer<CAP> {
1577    /// Creates an empty encoded buffer.
1578    #[must_use]
1579    pub const fn new() -> Self {
1580        Self {
1581            bytes: [0u8; CAP],
1582            len: 0,
1583        }
1584    }
1585
1586    /// Returns the number of visible encoded bytes.
1587    #[must_use]
1588    pub const fn len(&self) -> usize {
1589        self.len
1590    }
1591
1592    /// Returns whether the buffer has no visible encoded bytes.
1593    #[must_use]
1594    pub const fn is_empty(&self) -> bool {
1595        self.len == 0
1596    }
1597
1598    /// Returns the stack capacity in bytes.
1599    #[must_use]
1600    pub const fn capacity(&self) -> usize {
1601        CAP
1602    }
1603
1604    /// Returns the visible encoded bytes.
1605    #[must_use]
1606    pub fn as_bytes(&self) -> &[u8] {
1607        &self.bytes[..self.len]
1608    }
1609
1610    /// Returns the visible encoded bytes as UTF-8.
1611    ///
1612    /// # Panics
1613    ///
1614    /// Panics only if the crate's internal invariant is broken and the buffer
1615    /// contains non-UTF-8 bytes.
1616    #[must_use]
1617    pub fn as_str(&self) -> &str {
1618        match core::str::from_utf8(self.as_bytes()) {
1619            Ok(output) => output,
1620            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
1621        }
1622    }
1623
1624    /// Clears the visible bytes and the full backing array.
1625    pub fn clear(&mut self) {
1626        wipe_bytes(&mut self.bytes);
1627        self.len = 0;
1628    }
1629
1630    /// Clears bytes after the visible prefix.
1631    pub fn clear_tail(&mut self) {
1632        wipe_tail(&mut self.bytes, self.len);
1633    }
1634}
1635
1636impl<const CAP: usize> AsRef<[u8]> for EncodedBuffer<CAP> {
1637    fn as_ref(&self) -> &[u8] {
1638        self.as_bytes()
1639    }
1640}
1641
1642impl<const CAP: usize> Clone for EncodedBuffer<CAP> {
1643    fn clone(&self) -> Self {
1644        let mut output = Self::new();
1645        output.bytes[..self.len].copy_from_slice(self.as_bytes());
1646        output.len = self.len;
1647        output
1648    }
1649}
1650
1651impl<const CAP: usize> core::fmt::Debug for EncodedBuffer<CAP> {
1652    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1653        formatter
1654            .debug_struct("EncodedBuffer")
1655            .field("bytes", &"<redacted>")
1656            .field("len", &self.len)
1657            .field("capacity", &CAP)
1658            .finish()
1659    }
1660}
1661
1662impl<const CAP: usize> Default for EncodedBuffer<CAP> {
1663    fn default() -> Self {
1664        Self::new()
1665    }
1666}
1667
1668impl<const CAP: usize> Drop for EncodedBuffer<CAP> {
1669    fn drop(&mut self) {
1670        self.clear();
1671    }
1672}
1673
1674impl<const CAP: usize> Eq for EncodedBuffer<CAP> {}
1675
1676impl<const CAP: usize> PartialEq for EncodedBuffer<CAP> {
1677    fn eq(&self, other: &Self) -> bool {
1678        self.as_bytes() == other.as_bytes()
1679    }
1680}
1681
1682/// Owned sensitive bytes with redacted formatting and drop-time cleanup.
1683///
1684/// `SecretBuffer` is available with the `alloc` feature. It is intended for
1685/// decoded keys, tokens, and other values that should not be accidentally
1686/// logged. The buffer exposes contents only through explicit reveal methods.
1687///
1688/// On drop, initialized bytes are cleared with the crate's internal best-effort
1689/// wipe helper. This is data-retention reduction, not a formal zeroization
1690/// guarantee, and it cannot make claims about allocator spare capacity or
1691/// historical copies outside the wrapper.
1692#[cfg(feature = "alloc")]
1693pub struct SecretBuffer {
1694    bytes: alloc::vec::Vec<u8>,
1695}
1696
1697#[cfg(feature = "alloc")]
1698impl SecretBuffer {
1699    /// Wraps an existing vector as sensitive material.
1700    #[must_use]
1701    pub fn from_vec(bytes: alloc::vec::Vec<u8>) -> Self {
1702        Self { bytes }
1703    }
1704
1705    /// Copies a slice into an owned sensitive buffer.
1706    #[must_use]
1707    pub fn from_slice(bytes: &[u8]) -> Self {
1708        Self {
1709            bytes: bytes.to_vec(),
1710        }
1711    }
1712
1713    /// Returns the number of initialized secret bytes.
1714    #[must_use]
1715    pub fn len(&self) -> usize {
1716        self.bytes.len()
1717    }
1718
1719    /// Returns whether the buffer contains no initialized secret bytes.
1720    #[must_use]
1721    pub fn is_empty(&self) -> bool {
1722        self.bytes.is_empty()
1723    }
1724
1725    /// Reveals the secret bytes.
1726    ///
1727    /// This method is intentionally named to make secret access explicit at the
1728    /// call site.
1729    #[must_use]
1730    pub fn expose_secret(&self) -> &[u8] {
1731        &self.bytes
1732    }
1733
1734    /// Reveals the secret bytes mutably.
1735    ///
1736    /// This method is intentionally named to make secret access explicit at the
1737    /// call site.
1738    #[must_use]
1739    pub fn expose_secret_mut(&mut self) -> &mut [u8] {
1740        &mut self.bytes
1741    }
1742
1743    /// Clears the initialized bytes and makes the buffer empty.
1744    pub fn clear(&mut self) {
1745        wipe_bytes(&mut self.bytes);
1746        self.bytes.clear();
1747    }
1748}
1749
1750#[cfg(feature = "alloc")]
1751impl Clone for SecretBuffer {
1752    fn clone(&self) -> Self {
1753        Self::from_slice(self.expose_secret())
1754    }
1755}
1756
1757#[cfg(feature = "alloc")]
1758impl core::fmt::Debug for SecretBuffer {
1759    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1760        formatter
1761            .debug_struct("SecretBuffer")
1762            .field("bytes", &"<redacted>")
1763            .field("len", &self.len())
1764            .finish()
1765    }
1766}
1767
1768#[cfg(feature = "alloc")]
1769impl core::fmt::Display for SecretBuffer {
1770    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1771        formatter.write_str("<redacted>")
1772    }
1773}
1774
1775#[cfg(feature = "alloc")]
1776impl Drop for SecretBuffer {
1777    fn drop(&mut self) {
1778        wipe_bytes(&mut self.bytes);
1779    }
1780}
1781
1782#[cfg(feature = "alloc")]
1783impl Eq for SecretBuffer {}
1784
1785#[cfg(feature = "alloc")]
1786impl PartialEq for SecretBuffer {
1787    fn eq(&self, other: &Self) -> bool {
1788        self.expose_secret() == other.expose_secret()
1789    }
1790}
1791
1792/// A named Base64 profile with an engine and optional strict line wrapping.
1793///
1794/// Profiles are convenience values for protocol-shaped Base64. They keep the
1795/// same strict alphabet, padding, canonical-bit, and output-buffer rules as
1796/// [`Engine`], while carrying the wrapping policy for MIME/PEM-like formats.
1797#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1798pub struct Profile<A, const PAD: bool> {
1799    engine: Engine<A, PAD>,
1800    wrap: Option<LineWrap>,
1801}
1802
1803impl<A, const PAD: bool> Profile<A, PAD>
1804where
1805    A: Alphabet,
1806{
1807    /// Creates a profile from an engine and optional strict line wrapping.
1808    #[must_use]
1809    pub const fn new(engine: Engine<A, PAD>, wrap: Option<LineWrap>) -> Self {
1810        Self { engine, wrap }
1811    }
1812
1813    /// Returns the underlying engine.
1814    #[must_use]
1815    pub const fn engine(&self) -> Engine<A, PAD> {
1816        self.engine
1817    }
1818
1819    /// Returns the strict wrapping policy carried by this profile, if any.
1820    #[must_use]
1821    pub const fn line_wrap(&self) -> Option<LineWrap> {
1822        self.wrap
1823    }
1824
1825    /// Returns the encoded length for this profile.
1826    pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
1827        match self.wrap {
1828            Some(wrap) => wrapped_encoded_len(input_len, PAD, wrap),
1829            None => encoded_len(input_len, PAD),
1830        }
1831    }
1832
1833    /// Returns the exact decoded length for this profile.
1834    pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
1835        match self.wrap {
1836            Some(wrap) => self.engine.decoded_len_wrapped(input, wrap),
1837            None => self.engine.decoded_len(input),
1838        }
1839    }
1840
1841    /// Validates input according to this profile without writing decoded bytes.
1842    pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
1843        match self.wrap {
1844            Some(wrap) => self.engine.validate_wrapped_result(input, wrap),
1845            None => self.engine.validate_result(input),
1846        }
1847    }
1848
1849    /// Returns whether `input` is valid for this profile.
1850    #[must_use]
1851    pub fn validate(&self, input: &[u8]) -> bool {
1852        self.validate_result(input).is_ok()
1853    }
1854
1855    /// Encodes `input` into `output` according to this profile.
1856    pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
1857        match self.wrap {
1858            Some(wrap) => self.engine.encode_slice_wrapped(input, output, wrap),
1859            None => self.engine.encode_slice(input, output),
1860        }
1861    }
1862
1863    /// Encodes `input` into `output` and clears all bytes after the encoded
1864    /// prefix.
1865    pub fn encode_slice_clear_tail(
1866        &self,
1867        input: &[u8],
1868        output: &mut [u8],
1869    ) -> Result<usize, EncodeError> {
1870        match self.wrap {
1871            Some(wrap) => self
1872                .engine
1873                .encode_slice_wrapped_clear_tail(input, output, wrap),
1874            None => self.engine.encode_slice_clear_tail(input, output),
1875        }
1876    }
1877
1878    /// Encodes `input` into a stack-backed buffer.
1879    ///
1880    /// This is useful for short values where heap allocation is unnecessary.
1881    /// If encoding fails, the internal backing array is cleared before the
1882    /// error is returned.
1883    pub fn encode_buffer<const CAP: usize>(
1884        &self,
1885        input: &[u8],
1886    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
1887        let mut output = EncodedBuffer::new();
1888        let written = match self.encode_slice_clear_tail(input, &mut output.bytes) {
1889            Ok(written) => written,
1890            Err(err) => {
1891                output.clear();
1892                return Err(err);
1893            }
1894        };
1895        output.len = written;
1896        Ok(output)
1897    }
1898
1899    /// Decodes `input` into `output` according to this profile.
1900    pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
1901        match self.wrap {
1902            Some(wrap) => self.engine.decode_slice_wrapped(input, output, wrap),
1903            None => self.engine.decode_slice(input, output),
1904        }
1905    }
1906
1907    /// Decodes `input` into `output` and clears all bytes after the decoded
1908    /// prefix.
1909    pub fn decode_slice_clear_tail(
1910        &self,
1911        input: &[u8],
1912        output: &mut [u8],
1913    ) -> Result<usize, DecodeError> {
1914        match self.wrap {
1915            Some(wrap) => self
1916                .engine
1917                .decode_slice_wrapped_clear_tail(input, output, wrap),
1918            None => self.engine.decode_slice_clear_tail(input, output),
1919        }
1920    }
1921
1922    /// Encodes `input` into a newly allocated byte vector.
1923    #[cfg(feature = "alloc")]
1924    pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
1925        match self.wrap {
1926            Some(wrap) => self.engine.encode_wrapped_vec(input, wrap),
1927            None => self.engine.encode_vec(input),
1928        }
1929    }
1930
1931    /// Encodes `input` into a redacted owned secret buffer.
1932    #[cfg(feature = "alloc")]
1933    pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
1934        self.encode_vec(input).map(SecretBuffer::from_vec)
1935    }
1936
1937    /// Encodes `input` into a newly allocated UTF-8 string.
1938    #[cfg(feature = "alloc")]
1939    pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
1940        match self.wrap {
1941            Some(wrap) => self.engine.encode_wrapped_string(input, wrap),
1942            None => self.engine.encode_string(input),
1943        }
1944    }
1945
1946    /// Decodes `input` into a newly allocated byte vector.
1947    #[cfg(feature = "alloc")]
1948    pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
1949        match self.wrap {
1950            Some(wrap) => self.engine.decode_wrapped_vec(input, wrap),
1951            None => self.engine.decode_vec(input),
1952        }
1953    }
1954
1955    /// Decodes `input` into a redacted owned secret buffer.
1956    #[cfg(feature = "alloc")]
1957    pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
1958        self.decode_vec(input).map(SecretBuffer::from_vec)
1959    }
1960}
1961
1962/// MIME Base64 profile: standard alphabet, padding, 76-column CRLF wrapping.
1963pub const MIME: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::MIME));
1964
1965/// PEM Base64 profile: standard alphabet, padding, 64-column LF wrapping.
1966pub const PEM: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::PEM));
1967
1968/// PEM Base64 profile with CRLF line endings.
1969pub const PEM_CRLF: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::PEM_CRLF));
1970
1971/// bcrypt-style no-padding Base64 profile.
1972///
1973/// This profile carries the bcrypt alphabet and no padding. It does not parse
1974/// complete bcrypt password-hash strings.
1975pub const BCRYPT: Profile<Bcrypt, false> = Profile::new(BCRYPT_NO_PAD, None);
1976
1977/// Unix `crypt(3)`-style no-padding Base64 profile.
1978///
1979/// This profile carries the `crypt(3)` alphabet and no padding. It does not
1980/// parse complete password-hash strings.
1981pub const CRYPT: Profile<Crypt, false> = Profile::new(CRYPT_NO_PAD, None);
1982
1983/// Returns the encoded length for an input length and padding policy.
1984///
1985/// This function returns [`EncodeError::LengthOverflow`] instead of panicking.
1986/// Use [`checked_encoded_len`] when an `Option<usize>` is more convenient.
1987///
1988/// # Examples
1989///
1990/// ```
1991/// use base64_ng::encoded_len;
1992///
1993/// assert_eq!(encoded_len(5, true).unwrap(), 8);
1994/// assert_eq!(encoded_len(5, false).unwrap(), 7);
1995/// assert!(encoded_len(usize::MAX, true).is_err());
1996/// ```
1997pub const fn encoded_len(input_len: usize, padded: bool) -> Result<usize, EncodeError> {
1998    match checked_encoded_len(input_len, padded) {
1999        Some(len) => Ok(len),
2000        None => Err(EncodeError::LengthOverflow),
2001    }
2002}
2003
2004/// Returns the encoded length after applying a line wrapping policy.
2005///
2006/// The returned length includes inserted line endings but does not include a
2007/// trailing line ending after the final encoded line.
2008///
2009/// # Examples
2010///
2011/// ```
2012/// use base64_ng::{LineEnding, LineWrap, wrapped_encoded_len};
2013///
2014/// let wrap = LineWrap::new(4, LineEnding::Lf);
2015/// assert_eq!(wrapped_encoded_len(5, true, wrap).unwrap(), 9);
2016/// ```
2017pub const fn wrapped_encoded_len(
2018    input_len: usize,
2019    padded: bool,
2020    wrap: LineWrap,
2021) -> Result<usize, EncodeError> {
2022    if wrap.line_len == 0 {
2023        return Err(EncodeError::InvalidLineWrap { line_len: 0 });
2024    }
2025
2026    let Some(encoded) = checked_encoded_len(input_len, padded) else {
2027        return Err(EncodeError::LengthOverflow);
2028    };
2029    if encoded == 0 {
2030        return Ok(0);
2031    }
2032
2033    let breaks = (encoded - 1) / wrap.line_len;
2034    let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
2035        return Err(EncodeError::LengthOverflow);
2036    };
2037    match encoded.checked_add(line_ending_bytes) {
2038        Some(len) => Ok(len),
2039        None => Err(EncodeError::LengthOverflow),
2040    }
2041}
2042
2043/// Returns the encoded length, or `None` if it would overflow `usize`.
2044///
2045/// # Examples
2046///
2047/// ```
2048/// use base64_ng::checked_encoded_len;
2049///
2050/// assert_eq!(checked_encoded_len(5, true), Some(8));
2051/// assert_eq!(checked_encoded_len(usize::MAX, true), None);
2052/// ```
2053#[must_use]
2054pub const fn checked_encoded_len(input_len: usize, padded: bool) -> Option<usize> {
2055    let groups = input_len / 3;
2056    if groups > usize::MAX / 4 {
2057        return None;
2058    }
2059    let full = groups * 4;
2060    let rem = input_len % 3;
2061    if rem == 0 {
2062        Some(full)
2063    } else if padded {
2064        full.checked_add(4)
2065    } else {
2066        full.checked_add(rem + 1)
2067    }
2068}
2069
2070/// Returns the maximum decoded length for an encoded input length.
2071///
2072/// # Examples
2073///
2074/// ```
2075/// use base64_ng::decoded_capacity;
2076///
2077/// assert_eq!(decoded_capacity(8), 6);
2078/// assert_eq!(decoded_capacity(7), 5);
2079/// ```
2080#[must_use]
2081pub const fn decoded_capacity(encoded_len: usize) -> usize {
2082    let rem = encoded_len % 4;
2083    encoded_len / 4 * 3
2084        + if rem == 2 {
2085            1
2086        } else if rem == 3 {
2087            2
2088        } else {
2089            0
2090        }
2091}
2092
2093/// Returns the exact decoded length implied by input length and padding.
2094///
2095/// This validates padding placement and impossible lengths, but it does not
2096/// validate alphabet membership or non-canonical trailing bits.
2097///
2098/// # Examples
2099///
2100/// ```
2101/// use base64_ng::decoded_len;
2102///
2103/// assert_eq!(decoded_len(b"aGVsbG8=", true).unwrap(), 5);
2104/// assert_eq!(decoded_len(b"aGVsbG8", false).unwrap(), 5);
2105/// ```
2106pub fn decoded_len(input: &[u8], padded: bool) -> Result<usize, DecodeError> {
2107    if padded {
2108        decoded_len_padded(input)
2109    } else {
2110        decoded_len_unpadded(input)
2111    }
2112}
2113
2114/// Validates a 64-byte Base64 alphabet table.
2115///
2116/// A valid alphabet must contain exactly 64 unique visible ASCII bytes and must
2117/// not contain the padding byte `=`.
2118///
2119/// # Examples
2120///
2121/// ```
2122/// use base64_ng::{Alphabet, Standard, validate_alphabet};
2123///
2124/// validate_alphabet(&Standard::ENCODE).unwrap();
2125/// ```
2126pub const fn validate_alphabet(encode: &[u8; 64]) -> Result<(), AlphabetError> {
2127    let mut index = 0;
2128    while index < encode.len() {
2129        let byte = encode[index];
2130        if !is_visible_ascii(byte) {
2131            return Err(AlphabetError::InvalidByte { index, byte });
2132        }
2133        if byte == b'=' {
2134            return Err(AlphabetError::PaddingByte { index });
2135        }
2136
2137        let mut duplicate = index + 1;
2138        while duplicate < encode.len() {
2139            if encode[duplicate] == byte {
2140                return Err(AlphabetError::DuplicateByte {
2141                    first: index,
2142                    second: duplicate,
2143                    byte,
2144                });
2145            }
2146            duplicate += 1;
2147        }
2148
2149        index += 1;
2150    }
2151
2152    Ok(())
2153}
2154
2155/// Decodes one byte by scanning a caller-provided alphabet table.
2156///
2157/// This helper is intended for custom [`Alphabet`] implementations. Validate
2158/// the table with [`validate_alphabet`] before trusting the alphabet in a
2159/// protocol or public API.
2160///
2161/// # Examples
2162///
2163/// ```
2164/// use base64_ng::{Alphabet, decode_alphabet_byte};
2165///
2166/// struct DotSlash;
2167///
2168/// impl Alphabet for DotSlash {
2169///     const ENCODE: [u8; 64] =
2170///         *b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
2171///
2172///     fn decode(byte: u8) -> Option<u8> {
2173///         decode_alphabet_byte(byte, &Self::ENCODE)
2174///     }
2175/// }
2176///
2177/// assert_eq!(DotSlash::decode(b'.'), Some(0));
2178/// assert_eq!(DotSlash::decode(b'9'), Some(63));
2179/// ```
2180#[must_use]
2181pub const fn decode_alphabet_byte(byte: u8, encode: &[u8; 64]) -> Option<u8> {
2182    let mut index = 0;
2183    let mut value = 0;
2184    while index < encode.len() {
2185        if encode[index] == byte {
2186            return Some(value);
2187        }
2188        index += 1;
2189        value += 1;
2190    }
2191    None
2192}
2193
2194/// A Base64 alphabet.
2195pub trait Alphabet {
2196    /// Encoding table indexed by 6-bit values.
2197    const ENCODE: [u8; 64];
2198
2199    /// Encode one 6-bit value into an alphabet byte.
2200    ///
2201    /// The default implementation scans the alphabet table instead of using a
2202    /// secret-indexed table lookup. Built-in alphabets override this with the
2203    /// branch-minimized ASCII arithmetic mapper.
2204    #[must_use]
2205    fn encode(value: u8) -> u8 {
2206        encode_alphabet_value(value, &Self::ENCODE)
2207    }
2208
2209    /// Decode one byte into a 6-bit value.
2210    fn decode(byte: u8) -> Option<u8>;
2211}
2212
2213const fn is_visible_ascii(byte: u8) -> bool {
2214    byte >= 0x21 && byte <= 0x7e
2215}
2216
2217/// The RFC 4648 standard Base64 alphabet.
2218#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
2219pub struct Standard;
2220
2221impl Alphabet for Standard {
2222    const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2223
2224    #[inline]
2225    fn encode(value: u8) -> u8 {
2226        encode_ascii_base64(value, Self::ENCODE[62], Self::ENCODE[63])
2227    }
2228
2229    #[inline]
2230    fn decode(byte: u8) -> Option<u8> {
2231        decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
2232    }
2233}
2234
2235/// The RFC 4648 URL-safe Base64 alphabet.
2236#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
2237pub struct UrlSafe;
2238
2239impl Alphabet for UrlSafe {
2240    const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
2241
2242    #[inline]
2243    fn encode(value: u8) -> u8 {
2244        encode_ascii_base64(value, Self::ENCODE[62], Self::ENCODE[63])
2245    }
2246
2247    #[inline]
2248    fn decode(byte: u8) -> Option<u8> {
2249        decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
2250    }
2251}
2252
2253/// The bcrypt Base64 alphabet.
2254///
2255/// This alphabet is commonly used by bcrypt hash strings. It is provided as an
2256/// alphabet/profile building block; `base64-ng` does not parse or verify full
2257/// bcrypt password-hash records.
2258#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
2259pub struct Bcrypt;
2260
2261impl Alphabet for Bcrypt {
2262    const ENCODE: [u8; 64] = *b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
2263
2264    #[inline]
2265    fn decode(byte: u8) -> Option<u8> {
2266        decode_alphabet_byte(byte, &Self::ENCODE)
2267    }
2268}
2269
2270/// The Unix `crypt(3)` Base64 alphabet.
2271///
2272/// This alphabet is provided as an explicit legacy interoperability profile.
2273/// `base64-ng` does not parse or verify complete password-hash records.
2274#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
2275pub struct Crypt;
2276
2277impl Alphabet for Crypt {
2278    const ENCODE: [u8; 64] = *b"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2279
2280    #[inline]
2281    fn decode(byte: u8) -> Option<u8> {
2282        decode_alphabet_byte(byte, &Self::ENCODE)
2283    }
2284}
2285
2286#[inline]
2287const fn encode_base64_value<A: Alphabet>(value: u8) -> u8 {
2288    encode_alphabet_value(value, &A::ENCODE)
2289}
2290
2291#[inline]
2292fn encode_base64_value_runtime<A: Alphabet>(value: u8) -> u8 {
2293    A::encode(value)
2294}
2295
2296#[inline]
2297const fn encode_alphabet_value(value: u8, encode: &[u8; 64]) -> u8 {
2298    let mut output = 0;
2299    let mut index = 0;
2300    let mut candidate = 0;
2301    while index < encode.len() {
2302        output |= encode[index] & ct_mask_eq_u8(value, candidate);
2303        index += 1;
2304        candidate += 1;
2305    }
2306    output
2307}
2308
2309#[inline]
2310const fn encode_ascii_base64(value: u8, value_62_byte: u8, value_63_byte: u8) -> u8 {
2311    let upper = ct_mask_lt_u8(value, 26);
2312    let lower = ct_mask_lt_u8(value.wrapping_sub(26), 26);
2313    let digit = ct_mask_lt_u8(value.wrapping_sub(52), 10);
2314    let value_62 = ct_mask_eq_u8(value, 0x3e);
2315    let value_63 = ct_mask_eq_u8(value, 0x3f);
2316
2317    (value.wrapping_add(b'A') & upper)
2318        | (value.wrapping_sub(26).wrapping_add(b'a') & lower)
2319        | (value.wrapping_sub(52).wrapping_add(b'0') & digit)
2320        | (value_62_byte & value_62)
2321        | (value_63_byte & value_63)
2322}
2323
2324#[inline]
2325fn decode_ascii_base64(byte: u8, value_62_byte: u8, value_63_byte: u8) -> Option<u8> {
2326    let upper = ct_mask_lt_u8(byte.wrapping_sub(b'A'), 26);
2327    let lower = ct_mask_lt_u8(byte.wrapping_sub(b'a'), 26);
2328    let digit = ct_mask_lt_u8(byte.wrapping_sub(b'0'), 10);
2329    let value_62 = ct_mask_eq_u8(byte, value_62_byte);
2330    let value_63 = ct_mask_eq_u8(byte, value_63_byte);
2331    let valid = upper | lower | digit | value_62 | value_63;
2332
2333    let decoded = (byte.wrapping_sub(b'A') & upper)
2334        | (byte.wrapping_sub(b'a').wrapping_add(26) & lower)
2335        | (byte.wrapping_sub(b'0').wrapping_add(52) & digit)
2336        | (0x3e & value_62)
2337        | (0x3f & value_63);
2338
2339    if valid == 0 { None } else { Some(decoded) }
2340}
2341
2342#[inline]
2343const fn ct_mask_bit(bit: u8) -> u8 {
2344    0u8.wrapping_sub(bit & 1)
2345}
2346
2347#[inline]
2348const fn ct_mask_nonzero_u8(value: u8) -> u8 {
2349    let wide = value as u16;
2350    let negative = 0u16.wrapping_sub(wide);
2351    let nonzero = ((wide | negative) >> 8) as u8;
2352    ct_mask_bit(nonzero)
2353}
2354
2355#[inline]
2356const fn ct_mask_eq_u8(left: u8, right: u8) -> u8 {
2357    !ct_mask_nonzero_u8(left ^ right)
2358}
2359
2360#[inline]
2361const fn ct_mask_lt_u8(left: u8, right: u8) -> u8 {
2362    let diff = (left as u16).wrapping_sub(right as u16);
2363    ct_mask_bit((diff >> 8) as u8)
2364}
2365
2366mod backend {
2367    use super::{
2368        Alphabet, DecodeError, EncodeError, checked_encoded_len, decode_padded, decode_unpadded,
2369        encode_base64_value_runtime,
2370    };
2371
2372    pub(super) fn encode_slice<A, const PAD: bool>(
2373        input: &[u8],
2374        output: &mut [u8],
2375    ) -> Result<usize, EncodeError>
2376    where
2377        A: Alphabet,
2378    {
2379        #[cfg(feature = "simd")]
2380        match super::simd::active_backend() {
2381            super::simd::ActiveBackend::Scalar => {}
2382        }
2383
2384        scalar_encode_slice::<A, PAD>(input, output)
2385    }
2386
2387    pub(super) fn decode_slice<A, const PAD: bool>(
2388        input: &[u8],
2389        output: &mut [u8],
2390    ) -> Result<usize, DecodeError>
2391    where
2392        A: Alphabet,
2393    {
2394        #[cfg(feature = "simd")]
2395        match super::simd::active_backend() {
2396            super::simd::ActiveBackend::Scalar => {}
2397        }
2398
2399        scalar_decode_slice::<A, PAD>(input, output)
2400    }
2401
2402    #[cfg(test)]
2403    pub(super) fn scalar_reference_encode_slice<A, const PAD: bool>(
2404        input: &[u8],
2405        output: &mut [u8],
2406    ) -> Result<usize, EncodeError>
2407    where
2408        A: Alphabet,
2409    {
2410        scalar_encode_slice::<A, PAD>(input, output)
2411    }
2412
2413    #[cfg(test)]
2414    pub(super) fn scalar_reference_decode_slice<A, const PAD: bool>(
2415        input: &[u8],
2416        output: &mut [u8],
2417    ) -> Result<usize, DecodeError>
2418    where
2419        A: Alphabet,
2420    {
2421        scalar_decode_slice::<A, PAD>(input, output)
2422    }
2423
2424    fn scalar_encode_slice<A, const PAD: bool>(
2425        input: &[u8],
2426        output: &mut [u8],
2427    ) -> Result<usize, EncodeError>
2428    where
2429        A: Alphabet,
2430    {
2431        let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
2432        if output.len() < required {
2433            return Err(EncodeError::OutputTooSmall {
2434                required,
2435                available: output.len(),
2436            });
2437        }
2438
2439        let mut read = 0;
2440        let mut write = 0;
2441        while read + 3 <= input.len() {
2442            let b0 = input[read];
2443            let b1 = input[read + 1];
2444            let b2 = input[read + 2];
2445
2446            output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2447            output[write + 1] =
2448                encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2449            output[write + 2] =
2450                encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
2451            output[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
2452
2453            read += 3;
2454            write += 4;
2455        }
2456
2457        match input.len() - read {
2458            0 => {}
2459            1 => {
2460                let b0 = input[read];
2461                output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2462                output[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
2463                write += 2;
2464                if PAD {
2465                    output[write] = b'=';
2466                    output[write + 1] = b'=';
2467                    write += 2;
2468                }
2469            }
2470            2 => {
2471                let b0 = input[read];
2472                let b1 = input[read + 1];
2473                output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2474                output[write + 1] =
2475                    encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2476                output[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
2477                write += 3;
2478                if PAD {
2479                    output[write] = b'=';
2480                    write += 1;
2481                }
2482            }
2483            _ => unreachable!(),
2484        }
2485
2486        Ok(write)
2487    }
2488
2489    fn scalar_decode_slice<A, const PAD: bool>(
2490        input: &[u8],
2491        output: &mut [u8],
2492    ) -> Result<usize, DecodeError>
2493    where
2494        A: Alphabet,
2495    {
2496        if input.is_empty() {
2497            return Ok(0);
2498        }
2499
2500        if PAD {
2501            decode_padded::<A>(input, output)
2502        } else {
2503            decode_unpadded::<A>(input, output)
2504        }
2505    }
2506}
2507
2508/// A zero-sized Base64 engine parameterized by alphabet and padding policy.
2509pub struct Engine<A, const PAD: bool> {
2510    alphabet: core::marker::PhantomData<A>,
2511}
2512
2513impl<A, const PAD: bool> Clone for Engine<A, PAD> {
2514    fn clone(&self) -> Self {
2515        *self
2516    }
2517}
2518
2519impl<A, const PAD: bool> Copy for Engine<A, PAD> {}
2520
2521impl<A, const PAD: bool> core::fmt::Debug for Engine<A, PAD> {
2522    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2523        formatter
2524            .debug_struct("Engine")
2525            .field("padded", &PAD)
2526            .finish()
2527    }
2528}
2529
2530impl<A, const PAD: bool> Default for Engine<A, PAD> {
2531    fn default() -> Self {
2532        Self {
2533            alphabet: core::marker::PhantomData,
2534        }
2535    }
2536}
2537
2538impl<A, const PAD: bool> Eq for Engine<A, PAD> {}
2539
2540impl<A, const PAD: bool> PartialEq for Engine<A, PAD> {
2541    fn eq(&self, _other: &Self) -> bool {
2542        true
2543    }
2544}
2545
2546impl<A, const PAD: bool> Engine<A, PAD>
2547where
2548    A: Alphabet,
2549{
2550    /// Creates a new engine value.
2551    #[must_use]
2552    pub const fn new() -> Self {
2553        Self {
2554            alphabet: core::marker::PhantomData,
2555        }
2556    }
2557
2558    /// Returns the encoded length for this engine's padding policy.
2559    pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
2560        encoded_len(input_len, PAD)
2561    }
2562
2563    /// Returns the encoded length for this engine, or `None` on overflow.
2564    #[must_use]
2565    pub const fn checked_encoded_len(&self, input_len: usize) -> Option<usize> {
2566        checked_encoded_len(input_len, PAD)
2567    }
2568
2569    /// Returns the encoded length after applying a line wrapping policy.
2570    ///
2571    /// The returned length includes inserted line endings but does not include
2572    /// a trailing line ending after the final encoded line.
2573    pub const fn wrapped_encoded_len(
2574        &self,
2575        input_len: usize,
2576        wrap: LineWrap,
2577    ) -> Result<usize, EncodeError> {
2578        wrapped_encoded_len(input_len, PAD, wrap)
2579    }
2580
2581    /// Returns the exact decoded length implied by input length and padding.
2582    ///
2583    /// This validates padding placement and impossible lengths, but it does not
2584    /// validate alphabet membership or non-canonical trailing bits.
2585    pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
2586        decoded_len(input, PAD)
2587    }
2588
2589    /// Returns the exact decoded length for the explicit legacy profile.
2590    ///
2591    /// The legacy profile ignores ASCII space, tab, carriage return, and line
2592    /// feed bytes before applying the same alphabet, padding, and canonical-bit
2593    /// checks as strict decoding.
2594    pub fn decoded_len_legacy(&self, input: &[u8]) -> Result<usize, DecodeError> {
2595        validate_legacy_decode::<A, PAD>(input)
2596    }
2597
2598    /// Returns the exact decoded length for a line-wrapped profile.
2599    ///
2600    /// The wrapped profile accepts only the configured line ending. Non-final
2601    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
2602    /// may be shorter. A single trailing line ending after the final line is
2603    /// accepted.
2604    pub fn decoded_len_wrapped(&self, input: &[u8], wrap: LineWrap) -> Result<usize, DecodeError> {
2605        validate_wrapped_decode::<A, PAD>(input, wrap)
2606    }
2607
2608    /// Validates strict Base64 input without writing decoded bytes.
2609    ///
2610    /// This applies the same alphabet, padding, and canonical-bit checks as
2611    /// [`Self::decode_slice`]. Use this method when malformed-input
2612    /// diagnostics matter; use [`Self::validate`] when a boolean is enough.
2613    ///
2614    /// # Examples
2615    ///
2616    /// ```
2617    /// use base64_ng::STANDARD;
2618    ///
2619    /// STANDARD.validate_result(b"aGVsbG8=").unwrap();
2620    /// assert!(STANDARD.validate_result(b"aGVsbG8").is_err());
2621    /// ```
2622    pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
2623        validate_decode::<A, PAD>(input).map(|_| ())
2624    }
2625
2626    /// Returns whether `input` is valid strict Base64 for this engine.
2627    ///
2628    /// This is a convenience wrapper around [`Self::validate_result`].
2629    ///
2630    /// # Examples
2631    ///
2632    /// ```
2633    /// use base64_ng::URL_SAFE_NO_PAD;
2634    ///
2635    /// assert!(URL_SAFE_NO_PAD.validate(b"-_8"));
2636    /// assert!(!URL_SAFE_NO_PAD.validate(b"+/8"));
2637    /// ```
2638    #[must_use]
2639    pub fn validate(&self, input: &[u8]) -> bool {
2640        self.validate_result(input).is_ok()
2641    }
2642
2643    /// Validates input using the explicit legacy whitespace profile.
2644    ///
2645    /// ASCII space, tab, carriage return, and line feed bytes are ignored
2646    /// before applying the same alphabet, padding, and canonical-bit checks as
2647    /// strict decoding.
2648    ///
2649    /// # Examples
2650    ///
2651    /// ```
2652    /// use base64_ng::STANDARD;
2653    ///
2654    /// STANDARD.validate_legacy_result(b" aG\r\nVsbG8= ").unwrap();
2655    /// assert!(STANDARD.validate_legacy_result(b" aG-=").is_err());
2656    /// ```
2657    pub fn validate_legacy_result(&self, input: &[u8]) -> Result<(), DecodeError> {
2658        validate_legacy_decode::<A, PAD>(input).map(|_| ())
2659    }
2660
2661    /// Returns whether `input` is valid for the explicit legacy whitespace
2662    /// profile.
2663    ///
2664    /// This is a convenience wrapper around [`Self::validate_legacy_result`].
2665    ///
2666    /// # Examples
2667    ///
2668    /// ```
2669    /// use base64_ng::STANDARD;
2670    ///
2671    /// assert!(STANDARD.validate_legacy(b" aG\r\nVsbG8= "));
2672    /// assert!(!STANDARD.validate_legacy(b"aG-V"));
2673    /// ```
2674    #[must_use]
2675    pub fn validate_legacy(&self, input: &[u8]) -> bool {
2676        self.validate_legacy_result(input).is_ok()
2677    }
2678
2679    /// Validates input using a strict line-wrapped profile.
2680    ///
2681    /// This is stricter than [`Self::validate_legacy_result`]: it accepts only
2682    /// the configured line ending and enforces the configured line length for
2683    /// every non-final line.
2684    ///
2685    /// # Examples
2686    ///
2687    /// ```
2688    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
2689    ///
2690    /// let wrap = LineWrap::new(4, LineEnding::Lf);
2691    /// STANDARD.validate_wrapped_result(b"aGVs\nbG8=", wrap).unwrap();
2692    /// assert!(STANDARD.validate_wrapped_result(b"aG\nVsbG8=", wrap).is_err());
2693    /// ```
2694    pub fn validate_wrapped_result(&self, input: &[u8], wrap: LineWrap) -> Result<(), DecodeError> {
2695        validate_wrapped_decode::<A, PAD>(input, wrap).map(|_| ())
2696    }
2697
2698    /// Returns whether `input` is valid for a strict line-wrapped profile.
2699    ///
2700    /// This is a convenience wrapper around [`Self::validate_wrapped_result`].
2701    ///
2702    /// # Examples
2703    ///
2704    /// ```
2705    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
2706    ///
2707    /// let wrap = LineWrap::new(4, LineEnding::Lf);
2708    /// assert!(STANDARD.validate_wrapped(b"aGVs\nbG8=", wrap));
2709    /// assert!(!STANDARD.validate_wrapped(b"aG\nVsbG8=", wrap));
2710    /// ```
2711    #[must_use]
2712    pub fn validate_wrapped(&self, input: &[u8], wrap: LineWrap) -> bool {
2713        self.validate_wrapped_result(input, wrap).is_ok()
2714    }
2715
2716    /// Encodes a fixed-size input into a fixed-size output array in const contexts.
2717    ///
2718    /// Stable Rust does not yet allow this API to return an array whose length
2719    /// is computed from `INPUT_LEN` directly. Instead, the caller supplies the
2720    /// output length through the destination type and this function panics
2721    /// during const evaluation if the length is wrong.
2722    ///
2723    /// # Panics
2724    ///
2725    /// Panics if `OUTPUT_LEN` is not exactly the encoded length for `INPUT_LEN`
2726    /// and this engine's padding policy, or if that length overflows `usize`.
2727    ///
2728    /// # Examples
2729    ///
2730    /// ```
2731    /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
2732    ///
2733    /// const HELLO: [u8; 8] = STANDARD.encode_array(b"hello");
2734    /// const URL_SAFE: [u8; 3] = URL_SAFE_NO_PAD.encode_array(b"\xfb\xff");
2735    ///
2736    /// assert_eq!(&HELLO, b"aGVsbG8=");
2737    /// assert_eq!(&URL_SAFE, b"-_8");
2738    /// ```
2739    ///
2740    /// Incorrect output lengths fail during const evaluation:
2741    ///
2742    /// ```compile_fail
2743    /// use base64_ng::STANDARD;
2744    ///
2745    /// const TOO_SHORT: [u8; 7] = STANDARD.encode_array(b"hello");
2746    /// ```
2747    #[must_use]
2748    pub const fn encode_array<const INPUT_LEN: usize, const OUTPUT_LEN: usize>(
2749        &self,
2750        input: &[u8; INPUT_LEN],
2751    ) -> [u8; OUTPUT_LEN] {
2752        let Some(required) = checked_encoded_len(INPUT_LEN, PAD) else {
2753            panic!("encoded base64 length overflows usize");
2754        };
2755        assert!(
2756            required == OUTPUT_LEN,
2757            "base64 output array has incorrect length"
2758        );
2759
2760        let mut output = [0u8; OUTPUT_LEN];
2761        let mut read = 0;
2762        let mut write = 0;
2763        while INPUT_LEN - read >= 3 {
2764            let b0 = input[read];
2765            let b1 = input[read + 1];
2766            let b2 = input[read + 2];
2767
2768            output[write] = encode_base64_value::<A>(b0 >> 2);
2769            output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2770            output[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
2771            output[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
2772
2773            read += 3;
2774            write += 4;
2775        }
2776
2777        match INPUT_LEN - read {
2778            0 => {}
2779            1 => {
2780                let b0 = input[read];
2781                output[write] = encode_base64_value::<A>(b0 >> 2);
2782                output[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
2783                write += 2;
2784                if PAD {
2785                    output[write] = b'=';
2786                    output[write + 1] = b'=';
2787                }
2788            }
2789            2 => {
2790                let b0 = input[read];
2791                let b1 = input[read + 1];
2792                output[write] = encode_base64_value::<A>(b0 >> 2);
2793                output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2794                output[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
2795                if PAD {
2796                    output[write + 3] = b'=';
2797                }
2798            }
2799            _ => unreachable!(),
2800        }
2801
2802        output
2803    }
2804
2805    /// Encodes `input` into `output`, returning the number of bytes written.
2806    pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
2807        backend::encode_slice::<A, PAD>(input, output)
2808    }
2809
2810    /// Encodes `input` into `output` with line wrapping.
2811    ///
2812    /// The wrapping policy inserts line endings between encoded lines and does
2813    /// not append a trailing line ending after the final line.
2814    ///
2815    /// # Examples
2816    ///
2817    /// ```
2818    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
2819    ///
2820    /// let wrap = LineWrap::new(4, LineEnding::Lf);
2821    /// let mut output = [0u8; 9];
2822    /// let written = STANDARD
2823    ///     .encode_slice_wrapped(b"hello", &mut output, wrap)
2824    ///     .unwrap();
2825    ///
2826    /// assert_eq!(&output[..written], b"aGVs\nbG8=");
2827    /// ```
2828    pub fn encode_slice_wrapped(
2829        &self,
2830        input: &[u8],
2831        output: &mut [u8],
2832        wrap: LineWrap,
2833    ) -> Result<usize, EncodeError> {
2834        let required = self.wrapped_encoded_len(input.len(), wrap)?;
2835        if output.len() < required {
2836            return Err(EncodeError::OutputTooSmall {
2837                required,
2838                available: output.len(),
2839            });
2840        }
2841
2842        let encoded_len =
2843            checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
2844        if encoded_len == 0 {
2845            return Ok(0);
2846        }
2847
2848        if output.len() < required.saturating_add(encoded_len) {
2849            let mut scratch = [0u8; 1024];
2850            let mut input_offset = 0;
2851            let mut output_offset = 0;
2852            let mut column = 0;
2853
2854            while input_offset < input.len() {
2855                let remaining = input.len() - input_offset;
2856                let mut take = remaining.min(768);
2857                if remaining > take {
2858                    take -= take % 3;
2859                }
2860                if take == 0 {
2861                    take = remaining;
2862                }
2863
2864                let encoded =
2865                    self.encode_slice(&input[input_offset..input_offset + take], &mut scratch)?;
2866                write_wrapped_bytes(
2867                    &scratch[..encoded],
2868                    output,
2869                    &mut output_offset,
2870                    &mut column,
2871                    wrap,
2872                );
2873                wipe_bytes(&mut scratch[..encoded]);
2874                input_offset += take;
2875            }
2876
2877            Ok(output_offset)
2878        } else {
2879            let encoded =
2880                self.encode_slice(input, &mut output[required..required + encoded_len])?;
2881            let mut output_offset = 0;
2882            let mut column = 0;
2883            let mut read = required;
2884            while read < required + encoded {
2885                let byte = output[read];
2886                write_wrapped_byte(byte, output, &mut output_offset, &mut column, wrap);
2887                read += 1;
2888            }
2889            wipe_bytes(&mut output[required..required + encoded]);
2890            Ok(output_offset)
2891        }
2892    }
2893
2894    /// Encodes `input` with line wrapping and clears all bytes after the
2895    /// encoded prefix.
2896    ///
2897    /// If encoding fails, the entire output buffer is cleared before the error
2898    /// is returned.
2899    pub fn encode_slice_wrapped_clear_tail(
2900        &self,
2901        input: &[u8],
2902        output: &mut [u8],
2903        wrap: LineWrap,
2904    ) -> Result<usize, EncodeError> {
2905        let written = match self.encode_slice_wrapped(input, output, wrap) {
2906            Ok(written) => written,
2907            Err(err) => {
2908                wipe_bytes(output);
2909                return Err(err);
2910            }
2911        };
2912        wipe_tail(output, written);
2913        Ok(written)
2914    }
2915
2916    /// Encodes `input` with line wrapping into a newly allocated byte vector.
2917    #[cfg(feature = "alloc")]
2918    pub fn encode_wrapped_vec(
2919        &self,
2920        input: &[u8],
2921        wrap: LineWrap,
2922    ) -> Result<alloc::vec::Vec<u8>, EncodeError> {
2923        let required = self.wrapped_encoded_len(input.len(), wrap)?;
2924        let mut output = alloc::vec![0; required];
2925        let written = self.encode_slice_wrapped(input, &mut output, wrap)?;
2926        output.truncate(written);
2927        Ok(output)
2928    }
2929
2930    /// Encodes `input` with line wrapping into a newly allocated UTF-8 string.
2931    #[cfg(feature = "alloc")]
2932    pub fn encode_wrapped_string(
2933        &self,
2934        input: &[u8],
2935        wrap: LineWrap,
2936    ) -> Result<alloc::string::String, EncodeError> {
2937        let output = self.encode_wrapped_vec(input, wrap)?;
2938        match alloc::string::String::from_utf8(output) {
2939            Ok(output) => Ok(output),
2940            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
2941        }
2942    }
2943
2944    /// Encodes `input` into `output` and clears all bytes after the encoded
2945    /// prefix.
2946    ///
2947    /// If encoding fails, the entire output buffer is cleared before the error
2948    /// is returned.
2949    ///
2950    /// # Examples
2951    ///
2952    /// ```
2953    /// use base64_ng::STANDARD;
2954    ///
2955    /// let mut output = [0xff; 12];
2956    /// let written = STANDARD
2957    ///     .encode_slice_clear_tail(b"hello", &mut output)
2958    ///     .unwrap();
2959    ///
2960    /// assert_eq!(&output[..written], b"aGVsbG8=");
2961    /// assert!(output[written..].iter().all(|byte| *byte == 0));
2962    /// ```
2963    pub fn encode_slice_clear_tail(
2964        &self,
2965        input: &[u8],
2966        output: &mut [u8],
2967    ) -> Result<usize, EncodeError> {
2968        let written = match self.encode_slice(input, output) {
2969            Ok(written) => written,
2970            Err(err) => {
2971                wipe_bytes(output);
2972                return Err(err);
2973            }
2974        };
2975        wipe_tail(output, written);
2976        Ok(written)
2977    }
2978
2979    /// Encodes `input` into a stack-backed buffer.
2980    ///
2981    /// This helper is useful for short values where callers want the
2982    /// convenience of an owned result without enabling `alloc`.
2983    ///
2984    /// # Examples
2985    ///
2986    /// ```
2987    /// use base64_ng::STANDARD;
2988    ///
2989    /// let encoded = STANDARD.encode_buffer::<8>(b"hello").unwrap();
2990    ///
2991    /// assert_eq!(encoded.as_str(), "aGVsbG8=");
2992    /// ```
2993    pub fn encode_buffer<const CAP: usize>(
2994        &self,
2995        input: &[u8],
2996    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
2997        let mut output = EncodedBuffer::new();
2998        let written = match self.encode_slice_clear_tail(input, &mut output.bytes) {
2999            Ok(written) => written,
3000            Err(err) => {
3001                output.clear();
3002                return Err(err);
3003            }
3004        };
3005        output.len = written;
3006        Ok(output)
3007    }
3008
3009    /// Encodes `input` into a newly allocated byte vector.
3010    #[cfg(feature = "alloc")]
3011    pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
3012        let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
3013        let mut output = alloc::vec![0; required];
3014        let written = self.encode_slice(input, &mut output)?;
3015        output.truncate(written);
3016        Ok(output)
3017    }
3018
3019    /// Encodes `input` into a redacted owned secret buffer.
3020    ///
3021    /// This is useful when the encoded representation itself is sensitive and
3022    /// should not be accidentally logged through formatting.
3023    #[cfg(feature = "alloc")]
3024    pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
3025        self.encode_vec(input).map(SecretBuffer::from_vec)
3026    }
3027
3028    /// Encodes `input` into a newly allocated UTF-8 string.
3029    ///
3030    /// Base64 output is ASCII by construction. This helper is available with
3031    /// the `alloc` feature and has the same encoding semantics as
3032    /// [`Self::encode_slice`].
3033    ///
3034    /// # Examples
3035    ///
3036    /// ```
3037    /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
3038    ///
3039    /// assert_eq!(STANDARD.encode_string(b"hello").unwrap(), "aGVsbG8=");
3040    /// assert_eq!(URL_SAFE_NO_PAD.encode_string(b"\xfb\xff").unwrap(), "-_8");
3041    /// ```
3042    #[cfg(feature = "alloc")]
3043    pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
3044        let output = self.encode_vec(input)?;
3045        match alloc::string::String::from_utf8(output) {
3046            Ok(output) => Ok(output),
3047            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
3048        }
3049    }
3050
3051    /// Encodes the first `input_len` bytes of `buffer` in place.
3052    ///
3053    /// The buffer must have enough spare capacity for the encoded output. The
3054    /// implementation writes from right to left, so unread input bytes are not
3055    /// overwritten before they are encoded.
3056    ///
3057    /// # Examples
3058    ///
3059    /// ```
3060    /// use base64_ng::STANDARD;
3061    ///
3062    /// let mut buffer = [0u8; 8];
3063    /// buffer[..5].copy_from_slice(b"hello");
3064    /// let encoded = STANDARD.encode_in_place(&mut buffer, 5).unwrap();
3065    /// assert_eq!(encoded, b"aGVsbG8=");
3066    /// ```
3067    pub fn encode_in_place<'a>(
3068        &self,
3069        buffer: &'a mut [u8],
3070        input_len: usize,
3071    ) -> Result<&'a mut [u8], EncodeError> {
3072        if input_len > buffer.len() {
3073            return Err(EncodeError::InputTooLarge {
3074                input_len,
3075                buffer_len: buffer.len(),
3076            });
3077        }
3078
3079        let required = checked_encoded_len(input_len, PAD).ok_or(EncodeError::LengthOverflow)?;
3080        if buffer.len() < required {
3081            return Err(EncodeError::OutputTooSmall {
3082                required,
3083                available: buffer.len(),
3084            });
3085        }
3086
3087        let mut read = input_len;
3088        let mut write = required;
3089
3090        match input_len % 3 {
3091            0 => {}
3092            1 => {
3093                read -= 1;
3094                let b0 = buffer[read];
3095                if PAD {
3096                    write -= 4;
3097                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
3098                    buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
3099                    buffer[write + 2] = b'=';
3100                    buffer[write + 3] = b'=';
3101                } else {
3102                    write -= 2;
3103                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
3104                    buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
3105                }
3106            }
3107            2 => {
3108                read -= 2;
3109                let b0 = buffer[read];
3110                let b1 = buffer[read + 1];
3111                if PAD {
3112                    write -= 4;
3113                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
3114                    buffer[write + 1] =
3115                        encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
3116                    buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
3117                    buffer[write + 3] = b'=';
3118                } else {
3119                    write -= 3;
3120                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
3121                    buffer[write + 1] =
3122                        encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
3123                    buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
3124                }
3125            }
3126            _ => unreachable!(),
3127        }
3128
3129        while read > 0 {
3130            read -= 3;
3131            write -= 4;
3132            let b0 = buffer[read];
3133            let b1 = buffer[read + 1];
3134            let b2 = buffer[read + 2];
3135
3136            buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
3137            buffer[write + 1] =
3138                encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
3139            buffer[write + 2] =
3140                encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
3141            buffer[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
3142        }
3143
3144        debug_assert_eq!(write, 0);
3145        Ok(&mut buffer[..required])
3146    }
3147
3148    /// Encodes the first `input_len` bytes of `buffer` in place and clears all
3149    /// bytes after the encoded prefix.
3150    ///
3151    /// If encoding fails because `input_len` is too large, the output buffer is
3152    /// too small, or the encoded length overflows `usize`, the entire buffer is
3153    /// cleared before the error is returned.
3154    ///
3155    /// # Examples
3156    ///
3157    /// ```
3158    /// use base64_ng::STANDARD;
3159    ///
3160    /// let mut buffer = [0xff; 12];
3161    /// buffer[..5].copy_from_slice(b"hello");
3162    /// let encoded = STANDARD.encode_in_place_clear_tail(&mut buffer, 5).unwrap();
3163    /// assert_eq!(encoded, b"aGVsbG8=");
3164    /// ```
3165    pub fn encode_in_place_clear_tail<'a>(
3166        &self,
3167        buffer: &'a mut [u8],
3168        input_len: usize,
3169    ) -> Result<&'a mut [u8], EncodeError> {
3170        let len = match self.encode_in_place(buffer, input_len) {
3171            Ok(encoded) => encoded.len(),
3172            Err(err) => {
3173                wipe_bytes(buffer);
3174                return Err(err);
3175            }
3176        };
3177        wipe_tail(buffer, len);
3178        Ok(&mut buffer[..len])
3179    }
3180
3181    /// Decodes `input` into `output`, returning the number of bytes written.
3182    ///
3183    /// This is strict decoding. Whitespace, mixed alphabets, malformed padding,
3184    /// and trailing non-padding data are rejected.
3185    pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
3186        backend::decode_slice::<A, PAD>(input, output)
3187    }
3188
3189    /// Decodes `input` into `output` and clears all bytes after the decoded
3190    /// prefix.
3191    ///
3192    /// If decoding fails, the entire output buffer is cleared before the error
3193    /// is returned.
3194    ///
3195    /// # Examples
3196    ///
3197    /// ```
3198    /// use base64_ng::STANDARD;
3199    ///
3200    /// let mut output = [0xff; 8];
3201    /// let written = STANDARD
3202    ///     .decode_slice_clear_tail(b"aGk=", &mut output)
3203    ///     .unwrap();
3204    ///
3205    /// assert_eq!(&output[..written], b"hi");
3206    /// assert!(output[written..].iter().all(|byte| *byte == 0));
3207    /// ```
3208    pub fn decode_slice_clear_tail(
3209        &self,
3210        input: &[u8],
3211        output: &mut [u8],
3212    ) -> Result<usize, DecodeError> {
3213        let written = match self.decode_slice(input, output) {
3214            Ok(written) => written,
3215            Err(err) => {
3216                wipe_bytes(output);
3217                return Err(err);
3218            }
3219        };
3220        wipe_tail(output, written);
3221        Ok(written)
3222    }
3223
3224    /// Decodes `input` using the explicit legacy whitespace profile.
3225    ///
3226    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
3227    /// Alphabet selection, padding placement, trailing data after padding, and
3228    /// non-canonical trailing bits remain strict.
3229    pub fn decode_slice_legacy(
3230        &self,
3231        input: &[u8],
3232        output: &mut [u8],
3233    ) -> Result<usize, DecodeError> {
3234        let required = validate_legacy_decode::<A, PAD>(input)?;
3235        if output.len() < required {
3236            return Err(DecodeError::OutputTooSmall {
3237                required,
3238                available: output.len(),
3239            });
3240        }
3241        decode_legacy_to_slice::<A, PAD>(input, output)
3242    }
3243
3244    /// Decodes `input` using the explicit legacy whitespace profile and clears
3245    /// all bytes after the decoded prefix.
3246    ///
3247    /// If validation or decoding fails, the entire output buffer is cleared
3248    /// before the error is returned.
3249    ///
3250    /// # Examples
3251    ///
3252    /// ```
3253    /// use base64_ng::STANDARD;
3254    ///
3255    /// let mut output = [0xff; 8];
3256    /// let written = STANDARD
3257    ///     .decode_slice_legacy_clear_tail(b" aG\r\nk= ", &mut output)
3258    ///     .unwrap();
3259    ///
3260    /// assert_eq!(&output[..written], b"hi");
3261    /// assert!(output[written..].iter().all(|byte| *byte == 0));
3262    /// ```
3263    pub fn decode_slice_legacy_clear_tail(
3264        &self,
3265        input: &[u8],
3266        output: &mut [u8],
3267    ) -> Result<usize, DecodeError> {
3268        let written = match self.decode_slice_legacy(input, output) {
3269            Ok(written) => written,
3270            Err(err) => {
3271                wipe_bytes(output);
3272                return Err(err);
3273            }
3274        };
3275        wipe_tail(output, written);
3276        Ok(written)
3277    }
3278
3279    /// Decodes `input` using a strict line-wrapped profile.
3280    ///
3281    /// The wrapped profile accepts only the configured line ending. Non-final
3282    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
3283    /// may be shorter. A single trailing line ending after the final line is
3284    /// accepted.
3285    pub fn decode_slice_wrapped(
3286        &self,
3287        input: &[u8],
3288        output: &mut [u8],
3289        wrap: LineWrap,
3290    ) -> Result<usize, DecodeError> {
3291        let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
3292        if output.len() < required {
3293            return Err(DecodeError::OutputTooSmall {
3294                required,
3295                available: output.len(),
3296            });
3297        }
3298        decode_wrapped_to_slice::<A, PAD>(input, output, wrap)
3299    }
3300
3301    /// Decodes `input` using a strict line-wrapped profile and clears all bytes
3302    /// after the decoded prefix.
3303    ///
3304    /// If validation or decoding fails, the entire output buffer is cleared
3305    /// before the error is returned.
3306    pub fn decode_slice_wrapped_clear_tail(
3307        &self,
3308        input: &[u8],
3309        output: &mut [u8],
3310        wrap: LineWrap,
3311    ) -> Result<usize, DecodeError> {
3312        let written = match self.decode_slice_wrapped(input, output, wrap) {
3313            Ok(written) => written,
3314            Err(err) => {
3315                wipe_bytes(output);
3316                return Err(err);
3317            }
3318        };
3319        wipe_tail(output, written);
3320        Ok(written)
3321    }
3322
3323    /// Decodes `input` into a newly allocated byte vector.
3324    ///
3325    /// This is strict decoding with the same semantics as [`Self::decode_slice`].
3326    #[cfg(feature = "alloc")]
3327    pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
3328        let required = validate_decode::<A, PAD>(input)?;
3329        let mut output = alloc::vec![0; required];
3330        let written = match self.decode_slice(input, &mut output) {
3331            Ok(written) => written,
3332            Err(err) => {
3333                wipe_bytes(&mut output);
3334                return Err(err);
3335            }
3336        };
3337        output.truncate(written);
3338        Ok(output)
3339    }
3340
3341    /// Decodes `input` into a redacted owned secret buffer.
3342    ///
3343    /// On malformed input, the intermediate output buffer is cleared before the
3344    /// error is returned by [`Self::decode_vec`].
3345    #[cfg(feature = "alloc")]
3346    pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
3347        self.decode_vec(input).map(SecretBuffer::from_vec)
3348    }
3349
3350    /// Decodes `input` into a newly allocated byte vector using the explicit
3351    /// legacy whitespace profile.
3352    #[cfg(feature = "alloc")]
3353    pub fn decode_vec_legacy(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
3354        let required = validate_legacy_decode::<A, PAD>(input)?;
3355        let mut output = alloc::vec![0; required];
3356        let written = match self.decode_slice_legacy(input, &mut output) {
3357            Ok(written) => written,
3358            Err(err) => {
3359                wipe_bytes(&mut output);
3360                return Err(err);
3361            }
3362        };
3363        output.truncate(written);
3364        Ok(output)
3365    }
3366
3367    /// Decodes line-wrapped input into a newly allocated byte vector.
3368    #[cfg(feature = "alloc")]
3369    pub fn decode_wrapped_vec(
3370        &self,
3371        input: &[u8],
3372        wrap: LineWrap,
3373    ) -> Result<alloc::vec::Vec<u8>, DecodeError> {
3374        let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
3375        let mut output = alloc::vec![0; required];
3376        let written = match self.decode_slice_wrapped(input, &mut output, wrap) {
3377            Ok(written) => written,
3378            Err(err) => {
3379                wipe_bytes(&mut output);
3380                return Err(err);
3381            }
3382        };
3383        output.truncate(written);
3384        Ok(output)
3385    }
3386
3387    /// Decodes the buffer in place and returns the decoded prefix.
3388    ///
3389    /// # Examples
3390    ///
3391    /// ```
3392    /// use base64_ng::STANDARD_NO_PAD;
3393    ///
3394    /// let mut buffer = *b"Zm9vYmFy";
3395    /// let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
3396    /// assert_eq!(decoded, b"foobar");
3397    /// ```
3398    pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
3399        let len = Self::decode_slice_to_start(buffer)?;
3400        Ok(&mut buffer[..len])
3401    }
3402
3403    /// Decodes the buffer in place and clears all bytes after the decoded prefix.
3404    ///
3405    /// If decoding fails, the entire buffer is cleared before the error is
3406    /// returned. Use this variant when the encoded or partially decoded data is
3407    /// sensitive and the caller wants best-effort cleanup without adding a
3408    /// dependency.
3409    ///
3410    /// # Examples
3411    ///
3412    /// ```
3413    /// use base64_ng::STANDARD;
3414    ///
3415    /// let mut buffer = *b"aGk=";
3416    /// let decoded = STANDARD.decode_in_place_clear_tail(&mut buffer).unwrap();
3417    /// assert_eq!(decoded, b"hi");
3418    /// ```
3419    pub fn decode_in_place_clear_tail<'a>(
3420        &self,
3421        buffer: &'a mut [u8],
3422    ) -> Result<&'a mut [u8], DecodeError> {
3423        let len = match Self::decode_slice_to_start(buffer) {
3424            Ok(len) => len,
3425            Err(err) => {
3426                wipe_bytes(buffer);
3427                return Err(err);
3428            }
3429        };
3430        wipe_tail(buffer, len);
3431        Ok(&mut buffer[..len])
3432    }
3433
3434    /// Decodes `buffer` in place using the explicit legacy whitespace profile.
3435    ///
3436    /// Ignored whitespace is compacted out before decoding. If validation
3437    /// fails, the buffer contents are unspecified.
3438    pub fn decode_in_place_legacy<'a>(
3439        &self,
3440        buffer: &'a mut [u8],
3441    ) -> Result<&'a mut [u8], DecodeError> {
3442        let _required = validate_legacy_decode::<A, PAD>(buffer)?;
3443        let mut write = 0;
3444        let mut read = 0;
3445        while read < buffer.len() {
3446            let byte = buffer[read];
3447            if !is_legacy_whitespace(byte) {
3448                buffer[write] = byte;
3449                write += 1;
3450            }
3451            read += 1;
3452        }
3453        let len = Self::decode_slice_to_start(&mut buffer[..write])?;
3454        Ok(&mut buffer[..len])
3455    }
3456
3457    /// Decodes `buffer` in place using the explicit legacy whitespace profile
3458    /// and clears all bytes after the decoded prefix.
3459    ///
3460    /// If validation or decoding fails, the entire buffer is cleared before the
3461    /// error is returned.
3462    pub fn decode_in_place_legacy_clear_tail<'a>(
3463        &self,
3464        buffer: &'a mut [u8],
3465    ) -> Result<&'a mut [u8], DecodeError> {
3466        if let Err(err) = validate_legacy_decode::<A, PAD>(buffer) {
3467            wipe_bytes(buffer);
3468            return Err(err);
3469        }
3470
3471        let mut write = 0;
3472        let mut read = 0;
3473        while read < buffer.len() {
3474            let byte = buffer[read];
3475            if !is_legacy_whitespace(byte) {
3476                buffer[write] = byte;
3477                write += 1;
3478            }
3479            read += 1;
3480        }
3481
3482        let len = match Self::decode_slice_to_start(&mut buffer[..write]) {
3483            Ok(len) => len,
3484            Err(err) => {
3485                wipe_bytes(buffer);
3486                return Err(err);
3487            }
3488        };
3489        wipe_tail(buffer, len);
3490        Ok(&mut buffer[..len])
3491    }
3492
3493    fn decode_slice_to_start(buffer: &mut [u8]) -> Result<usize, DecodeError> {
3494        let input_len = buffer.len();
3495        let mut read = 0;
3496        let mut write = 0;
3497        while read + 4 <= input_len {
3498            let chunk = read_quad(buffer, read)?;
3499            let available = buffer.len();
3500            let output_tail = buffer.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
3501                required: write,
3502                available,
3503            })?;
3504            let written = decode_chunk::<A, PAD>(chunk, output_tail)
3505                .map_err(|err| err.with_index_offset(read))?;
3506            read += 4;
3507            write += written;
3508            if written < 3 {
3509                if read != input_len {
3510                    return Err(DecodeError::InvalidPadding { index: read - 4 });
3511                }
3512                return Ok(write);
3513            }
3514        }
3515
3516        let rem = input_len - read;
3517        if rem == 0 {
3518            return Ok(write);
3519        }
3520        if PAD {
3521            return Err(DecodeError::InvalidLength);
3522        }
3523        let mut tail = [0u8; 3];
3524        tail[..rem].copy_from_slice(&buffer[read..input_len]);
3525        decode_tail_unpadded::<A>(&tail[..rem], &mut buffer[write..])
3526            .map_err(|err| err.with_index_offset(read))
3527            .map(|n| write + n)
3528    }
3529}
3530
3531fn write_wrapped_bytes(
3532    input: &[u8],
3533    output: &mut [u8],
3534    output_offset: &mut usize,
3535    column: &mut usize,
3536    wrap: LineWrap,
3537) {
3538    for byte in input {
3539        write_wrapped_byte(*byte, output, output_offset, column, wrap);
3540    }
3541}
3542
3543fn write_wrapped_byte(
3544    byte: u8,
3545    output: &mut [u8],
3546    output_offset: &mut usize,
3547    column: &mut usize,
3548    wrap: LineWrap,
3549) {
3550    if *column == wrap.line_len {
3551        let line_ending = wrap.line_ending.as_bytes();
3552        let mut index = 0;
3553        while index < line_ending.len() {
3554            output[*output_offset] = line_ending[index];
3555            *output_offset += 1;
3556            index += 1;
3557        }
3558        *column = 0;
3559    }
3560
3561    output[*output_offset] = byte;
3562    *output_offset += 1;
3563    *column += 1;
3564}
3565
3566/// Encoding error.
3567#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3568pub enum EncodeError {
3569    /// The encoded output length would overflow `usize`.
3570    LengthOverflow,
3571    /// The requested line wrapping policy is invalid.
3572    InvalidLineWrap {
3573        /// Requested line length.
3574        line_len: usize,
3575    },
3576    /// The caller-provided input length exceeds the provided buffer.
3577    InputTooLarge {
3578        /// Requested input bytes.
3579        input_len: usize,
3580        /// Available buffer bytes.
3581        buffer_len: usize,
3582    },
3583    /// The output buffer is too small.
3584    OutputTooSmall {
3585        /// Required output bytes.
3586        required: usize,
3587        /// Available output bytes.
3588        available: usize,
3589    },
3590}
3591
3592impl core::fmt::Display for EncodeError {
3593    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3594        match self {
3595            Self::LengthOverflow => f.write_str("base64 output length overflows usize"),
3596            Self::InvalidLineWrap { line_len } => {
3597                write!(f, "base64 line wrap length {line_len} is invalid")
3598            }
3599            Self::InputTooLarge {
3600                input_len,
3601                buffer_len,
3602            } => write!(
3603                f,
3604                "base64 input length {input_len} exceeds buffer length {buffer_len}"
3605            ),
3606            Self::OutputTooSmall {
3607                required,
3608                available,
3609            } => write!(
3610                f,
3611                "base64 output buffer too small: required {required}, available {available}"
3612            ),
3613        }
3614    }
3615}
3616
3617#[cfg(feature = "std")]
3618impl std::error::Error for EncodeError {}
3619
3620/// Alphabet validation error.
3621#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3622pub enum AlphabetError {
3623    /// The alphabet contains a non-visible-ASCII byte.
3624    InvalidByte {
3625        /// Byte index in the alphabet table.
3626        index: usize,
3627        /// Invalid byte value.
3628        byte: u8,
3629    },
3630    /// The alphabet contains the padding byte `=`.
3631    PaddingByte {
3632        /// Byte index in the alphabet table.
3633        index: usize,
3634    },
3635    /// The alphabet maps more than one value to the same byte.
3636    DuplicateByte {
3637        /// First byte index.
3638        first: usize,
3639        /// Second byte index.
3640        second: usize,
3641        /// Duplicated byte value.
3642        byte: u8,
3643    },
3644}
3645
3646impl core::fmt::Display for AlphabetError {
3647    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3648        match self {
3649            Self::InvalidByte { index, byte } => {
3650                write!(
3651                    f,
3652                    "invalid base64 alphabet byte 0x{byte:02x} at index {index}"
3653                )
3654            }
3655            Self::PaddingByte { index } => {
3656                write!(f, "base64 alphabet contains padding byte at index {index}")
3657            }
3658            Self::DuplicateByte {
3659                first,
3660                second,
3661                byte,
3662            } => write!(
3663                f,
3664                "base64 alphabet byte 0x{byte:02x} is duplicated at indexes {first} and {second}"
3665            ),
3666        }
3667    }
3668}
3669
3670#[cfg(feature = "std")]
3671impl std::error::Error for AlphabetError {}
3672
3673/// Decoding error.
3674#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3675pub enum DecodeError {
3676    /// The encoded input is malformed, but the decoder intentionally does not
3677    /// disclose a more specific error class.
3678    InvalidInput,
3679    /// The encoded input length is impossible for the selected padding policy.
3680    InvalidLength,
3681    /// A byte is not valid for the selected alphabet.
3682    InvalidByte {
3683        /// Byte index in the input.
3684        index: usize,
3685        /// Invalid byte value.
3686        byte: u8,
3687    },
3688    /// Padding is missing, misplaced, or non-canonical.
3689    InvalidPadding {
3690        /// Byte index where padding became invalid.
3691        index: usize,
3692    },
3693    /// Line wrapping is missing, misplaced, or uses the wrong line ending.
3694    InvalidLineWrap {
3695        /// Byte index where line wrapping became invalid.
3696        index: usize,
3697    },
3698    /// The output buffer is too small.
3699    OutputTooSmall {
3700        /// Required output bytes.
3701        required: usize,
3702        /// Available output bytes.
3703        available: usize,
3704    },
3705}
3706
3707impl core::fmt::Display for DecodeError {
3708    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3709        match self {
3710            Self::InvalidInput => f.write_str("malformed base64 input"),
3711            Self::InvalidLength => f.write_str("invalid base64 input length"),
3712            Self::InvalidByte { index, byte } => {
3713                write!(f, "invalid base64 byte 0x{byte:02x} at index {index}")
3714            }
3715            Self::InvalidPadding { index } => write!(f, "invalid base64 padding at index {index}"),
3716            Self::InvalidLineWrap { index } => {
3717                write!(f, "invalid base64 line wrapping at index {index}")
3718            }
3719            Self::OutputTooSmall {
3720                required,
3721                available,
3722            } => write!(
3723                f,
3724                "base64 decode output buffer too small: required {required}, available {available}"
3725            ),
3726        }
3727    }
3728}
3729
3730impl DecodeError {
3731    fn with_index_offset(self, offset: usize) -> Self {
3732        match self {
3733            Self::InvalidByte { index, byte } => Self::InvalidByte {
3734                index: index + offset,
3735                byte,
3736            },
3737            Self::InvalidPadding { index } => Self::InvalidPadding {
3738                index: index + offset,
3739            },
3740            Self::InvalidLineWrap { index } => Self::InvalidLineWrap {
3741                index: index + offset,
3742            },
3743            Self::InvalidInput | Self::InvalidLength | Self::OutputTooSmall { .. } => self,
3744        }
3745    }
3746}
3747
3748#[cfg(feature = "std")]
3749impl std::error::Error for DecodeError {}
3750
3751fn validate_legacy_decode<A: Alphabet, const PAD: bool>(
3752    input: &[u8],
3753) -> Result<usize, DecodeError> {
3754    let mut chunk = [0u8; 4];
3755    let mut indexes = [0usize; 4];
3756    let mut chunk_len = 0;
3757    let mut required = 0;
3758    let mut terminal_seen = false;
3759
3760    for (index, byte) in input.iter().copied().enumerate() {
3761        if is_legacy_whitespace(byte) {
3762            continue;
3763        }
3764        if terminal_seen {
3765            return Err(DecodeError::InvalidPadding { index });
3766        }
3767
3768        chunk[chunk_len] = byte;
3769        indexes[chunk_len] = index;
3770        chunk_len += 1;
3771
3772        if chunk_len == 4 {
3773            let written =
3774                validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
3775            required += written;
3776            terminal_seen = written < 3;
3777            chunk_len = 0;
3778        }
3779    }
3780
3781    if chunk_len == 0 {
3782        return Ok(required);
3783    }
3784    if PAD {
3785        return Err(DecodeError::InvalidLength);
3786    }
3787
3788    validate_tail_unpadded::<A>(&chunk[..chunk_len])
3789        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
3790    Ok(required + decoded_capacity(chunk_len))
3791}
3792
3793fn decode_legacy_to_slice<A: Alphabet, const PAD: bool>(
3794    input: &[u8],
3795    output: &mut [u8],
3796) -> Result<usize, DecodeError> {
3797    let mut chunk = [0u8; 4];
3798    let mut indexes = [0usize; 4];
3799    let mut chunk_len = 0;
3800    let mut write = 0;
3801    let mut terminal_seen = false;
3802
3803    for (index, byte) in input.iter().copied().enumerate() {
3804        if is_legacy_whitespace(byte) {
3805            continue;
3806        }
3807        if terminal_seen {
3808            return Err(DecodeError::InvalidPadding { index });
3809        }
3810
3811        chunk[chunk_len] = byte;
3812        indexes[chunk_len] = index;
3813        chunk_len += 1;
3814
3815        if chunk_len == 4 {
3816            let available = output.len();
3817            let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
3818                required: write,
3819                available,
3820            })?;
3821            let written = decode_chunk::<A, PAD>(chunk, output_tail)
3822                .map_err(|err| map_chunk_error(err, &indexes))?;
3823            write += written;
3824            terminal_seen = written < 3;
3825            chunk_len = 0;
3826        }
3827    }
3828
3829    if chunk_len == 0 {
3830        return Ok(write);
3831    }
3832    if PAD {
3833        return Err(DecodeError::InvalidLength);
3834    }
3835
3836    decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
3837        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
3838        .map(|n| write + n)
3839}
3840
3841struct WrappedBytes<'a> {
3842    input: &'a [u8],
3843    wrap: LineWrap,
3844    index: usize,
3845    line_len: usize,
3846}
3847
3848impl<'a> WrappedBytes<'a> {
3849    const fn new(input: &'a [u8], wrap: LineWrap) -> Result<Self, DecodeError> {
3850        if wrap.line_len == 0 {
3851            return Err(DecodeError::InvalidLineWrap { index: 0 });
3852        }
3853        Ok(Self {
3854            input,
3855            wrap,
3856            index: 0,
3857            line_len: 0,
3858        })
3859    }
3860
3861    fn next_byte(&mut self) -> Result<Option<(usize, u8)>, DecodeError> {
3862        loop {
3863            if self.index == self.input.len() {
3864                return Ok(None);
3865            }
3866
3867            if self.starts_with_line_ending() {
3868                let line_end_index = self.index;
3869                if self.line_len == 0 {
3870                    return Err(DecodeError::InvalidLineWrap {
3871                        index: line_end_index,
3872                    });
3873                }
3874
3875                self.index += self.wrap.line_ending.byte_len();
3876                if self.index == self.input.len() {
3877                    self.line_len = 0;
3878                    return Ok(None);
3879                }
3880
3881                if self.line_len != self.wrap.line_len {
3882                    return Err(DecodeError::InvalidLineWrap {
3883                        index: line_end_index,
3884                    });
3885                }
3886                self.line_len = 0;
3887                continue;
3888            }
3889
3890            let byte = self.input[self.index];
3891            if matches!(byte, b'\r' | b'\n') {
3892                return Err(DecodeError::InvalidLineWrap { index: self.index });
3893            }
3894
3895            self.line_len += 1;
3896            if self.line_len > self.wrap.line_len {
3897                return Err(DecodeError::InvalidLineWrap { index: self.index });
3898            }
3899
3900            let index = self.index;
3901            self.index += 1;
3902            return Ok(Some((index, byte)));
3903        }
3904    }
3905
3906    fn starts_with_line_ending(&self) -> bool {
3907        let line_ending = self.wrap.line_ending.as_bytes();
3908        let end = self.index + line_ending.len();
3909        end <= self.input.len() && &self.input[self.index..end] == line_ending
3910    }
3911}
3912
3913fn validate_wrapped_decode<A: Alphabet, const PAD: bool>(
3914    input: &[u8],
3915    wrap: LineWrap,
3916) -> Result<usize, DecodeError> {
3917    let mut bytes = WrappedBytes::new(input, wrap)?;
3918    let mut chunk = [0u8; 4];
3919    let mut indexes = [0usize; 4];
3920    let mut chunk_len = 0;
3921    let mut required = 0;
3922    let mut terminal_seen = false;
3923
3924    while let Some((index, byte)) = bytes.next_byte()? {
3925        if terminal_seen {
3926            return Err(DecodeError::InvalidPadding { index });
3927        }
3928
3929        chunk[chunk_len] = byte;
3930        indexes[chunk_len] = index;
3931        chunk_len += 1;
3932
3933        if chunk_len == 4 {
3934            let written =
3935                validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
3936            required += written;
3937            terminal_seen = written < 3;
3938            chunk_len = 0;
3939        }
3940    }
3941
3942    if chunk_len == 0 {
3943        return Ok(required);
3944    }
3945    if PAD {
3946        return Err(DecodeError::InvalidLength);
3947    }
3948
3949    validate_tail_unpadded::<A>(&chunk[..chunk_len])
3950        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
3951    Ok(required + decoded_capacity(chunk_len))
3952}
3953
3954fn decode_wrapped_to_slice<A: Alphabet, const PAD: bool>(
3955    input: &[u8],
3956    output: &mut [u8],
3957    wrap: LineWrap,
3958) -> Result<usize, DecodeError> {
3959    let mut bytes = WrappedBytes::new(input, wrap)?;
3960    let mut chunk = [0u8; 4];
3961    let mut indexes = [0usize; 4];
3962    let mut chunk_len = 0;
3963    let mut write = 0;
3964    let mut terminal_seen = false;
3965
3966    while let Some((index, byte)) = bytes.next_byte()? {
3967        if terminal_seen {
3968            return Err(DecodeError::InvalidPadding { index });
3969        }
3970
3971        chunk[chunk_len] = byte;
3972        indexes[chunk_len] = index;
3973        chunk_len += 1;
3974
3975        if chunk_len == 4 {
3976            let available = output.len();
3977            let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
3978                required: write,
3979                available,
3980            })?;
3981            let written = decode_chunk::<A, PAD>(chunk, output_tail)
3982                .map_err(|err| map_chunk_error(err, &indexes))?;
3983            write += written;
3984            terminal_seen = written < 3;
3985            chunk_len = 0;
3986        }
3987    }
3988
3989    if chunk_len == 0 {
3990        return Ok(write);
3991    }
3992    if PAD {
3993        return Err(DecodeError::InvalidLength);
3994    }
3995
3996    decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
3997        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
3998        .map(|n| write + n)
3999}
4000
4001#[inline]
4002const fn is_legacy_whitespace(byte: u8) -> bool {
4003    matches!(byte, b' ' | b'\t' | b'\r' | b'\n')
4004}
4005
4006fn map_chunk_error(err: DecodeError, indexes: &[usize; 4]) -> DecodeError {
4007    match err {
4008        DecodeError::InvalidByte { index, byte } => DecodeError::InvalidByte {
4009            index: indexes[index],
4010            byte,
4011        },
4012        DecodeError::InvalidPadding { index } => DecodeError::InvalidPadding {
4013            index: indexes[index],
4014        },
4015        DecodeError::InvalidInput
4016        | DecodeError::InvalidLineWrap { .. }
4017        | DecodeError::InvalidLength
4018        | DecodeError::OutputTooSmall { .. } => err,
4019    }
4020}
4021
4022fn map_partial_chunk_error(err: DecodeError, indexes: &[usize; 4], len: usize) -> DecodeError {
4023    match err {
4024        DecodeError::InvalidByte { index, byte } if index < len => DecodeError::InvalidByte {
4025            index: indexes[index],
4026            byte,
4027        },
4028        DecodeError::InvalidPadding { index } if index < len => DecodeError::InvalidPadding {
4029            index: indexes[index],
4030        },
4031        DecodeError::InvalidByte { .. }
4032        | DecodeError::InvalidPadding { .. }
4033        | DecodeError::InvalidLineWrap { .. }
4034        | DecodeError::InvalidInput
4035        | DecodeError::InvalidLength
4036        | DecodeError::OutputTooSmall { .. } => err,
4037    }
4038}
4039
4040fn decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
4041    if !input.len().is_multiple_of(4) {
4042        return Err(DecodeError::InvalidLength);
4043    }
4044    let required = decoded_len_padded(input)?;
4045    if output.len() < required {
4046        return Err(DecodeError::OutputTooSmall {
4047            required,
4048            available: output.len(),
4049        });
4050    }
4051
4052    let mut read = 0;
4053    let mut write = 0;
4054    while read < input.len() {
4055        let chunk = read_quad(input, read)?;
4056        let available = output.len();
4057        let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
4058            required: write,
4059            available,
4060        })?;
4061        let written = decode_chunk::<A, true>(chunk, output_tail)
4062            .map_err(|err| err.with_index_offset(read))?;
4063        read += 4;
4064        write += written;
4065        if written < 3 && read != input.len() {
4066            return Err(DecodeError::InvalidPadding { index: read - 4 });
4067        }
4068    }
4069    Ok(write)
4070}
4071
4072fn validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
4073    if input.is_empty() {
4074        return Ok(0);
4075    }
4076
4077    if PAD {
4078        validate_padded::<A>(input)
4079    } else {
4080        validate_unpadded::<A>(input)
4081    }
4082}
4083
4084fn validate_padded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
4085    if !input.len().is_multiple_of(4) {
4086        return Err(DecodeError::InvalidLength);
4087    }
4088    let required = decoded_len_padded(input)?;
4089
4090    let mut read = 0;
4091    while read < input.len() {
4092        let chunk = read_quad(input, read)?;
4093        let written =
4094            validate_chunk::<A, true>(chunk).map_err(|err| err.with_index_offset(read))?;
4095        read += 4;
4096        if written < 3 && read != input.len() {
4097            return Err(DecodeError::InvalidPadding { index: read - 4 });
4098        }
4099    }
4100
4101    Ok(required)
4102}
4103
4104fn validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
4105    let required = decoded_len_unpadded(input)?;
4106
4107    let mut read = 0;
4108    while read + 4 <= input.len() {
4109        let chunk = read_quad(input, read)?;
4110        validate_chunk::<A, false>(chunk).map_err(|err| err.with_index_offset(read))?;
4111        read += 4;
4112    }
4113    validate_tail_unpadded::<A>(&input[read..]).map_err(|err| err.with_index_offset(read))?;
4114
4115    Ok(required)
4116}
4117
4118fn decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
4119    let required = decoded_len_unpadded(input)?;
4120    if output.len() < required {
4121        return Err(DecodeError::OutputTooSmall {
4122            required,
4123            available: output.len(),
4124        });
4125    }
4126
4127    let mut read = 0;
4128    let mut write = 0;
4129    while read + 4 <= input.len() {
4130        let chunk = read_quad(input, read)?;
4131        let available = output.len();
4132        let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
4133            required: write,
4134            available,
4135        })?;
4136        let written = decode_chunk::<A, false>(chunk, output_tail)
4137            .map_err(|err| err.with_index_offset(read))?;
4138        read += 4;
4139        write += written;
4140    }
4141    decode_tail_unpadded::<A>(&input[read..], &mut output[write..])
4142        .map_err(|err| err.with_index_offset(read))
4143        .map(|n| write + n)
4144}
4145
4146fn decoded_len_padded(input: &[u8]) -> Result<usize, DecodeError> {
4147    if input.is_empty() {
4148        return Ok(0);
4149    }
4150    if !input.len().is_multiple_of(4) {
4151        return Err(DecodeError::InvalidLength);
4152    }
4153    let mut padding = 0;
4154    if input[input.len() - 1] == b'=' {
4155        padding += 1;
4156    }
4157    if input[input.len() - 2] == b'=' {
4158        padding += 1;
4159    }
4160    if padding == 0
4161        && let Some(index) = input.iter().position(|byte| *byte == b'=')
4162    {
4163        return Err(DecodeError::InvalidPadding { index });
4164    }
4165    if padding > 0 {
4166        let first_pad = input.len() - padding;
4167        if let Some(index) = input[..first_pad].iter().position(|byte| *byte == b'=') {
4168            return Err(DecodeError::InvalidPadding { index });
4169        }
4170    }
4171    Ok(input.len() / 4 * 3 - padding)
4172}
4173
4174fn decoded_len_unpadded(input: &[u8]) -> Result<usize, DecodeError> {
4175    if input.len() % 4 == 1 {
4176        return Err(DecodeError::InvalidLength);
4177    }
4178    if let Some(index) = input.iter().position(|byte| *byte == b'=') {
4179        return Err(DecodeError::InvalidPadding { index });
4180    }
4181    Ok(decoded_capacity(input.len()))
4182}
4183
4184fn read_quad(input: &[u8], offset: usize) -> Result<[u8; 4], DecodeError> {
4185    let end = offset.checked_add(4).ok_or(DecodeError::InvalidLength)?;
4186    match input.get(offset..end) {
4187        Some([b0, b1, b2, b3]) => Ok([*b0, *b1, *b2, *b3]),
4188        _ => Err(DecodeError::InvalidLength),
4189    }
4190}
4191
4192fn first_padding_index(input: [u8; 4]) -> usize {
4193    let [b0, b1, b2, b3] = input;
4194    if b0 == b'=' {
4195        0
4196    } else if b1 == b'=' {
4197        1
4198    } else if b2 == b'=' {
4199        2
4200    } else if b3 == b'=' {
4201        3
4202    } else {
4203        0
4204    }
4205}
4206
4207fn validate_chunk<A: Alphabet, const PAD: bool>(input: [u8; 4]) -> Result<usize, DecodeError> {
4208    let [b0, b1, b2, b3] = input;
4209    let _v0 = decode_byte::<A>(b0, 0)?;
4210    let v1 = decode_byte::<A>(b1, 1)?;
4211
4212    match (b2, b3) {
4213        (b'=', b'=') if PAD => {
4214            if v1 & 0b0000_1111 != 0 {
4215                return Err(DecodeError::InvalidPadding { index: 1 });
4216            }
4217            Ok(1)
4218        }
4219        (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
4220        (_, b'=') if PAD => {
4221            let v2 = decode_byte::<A>(b2, 2)?;
4222            if v2 & 0b0000_0011 != 0 {
4223                return Err(DecodeError::InvalidPadding { index: 2 });
4224            }
4225            Ok(2)
4226        }
4227        (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
4228            index: first_padding_index(input),
4229        }),
4230        _ => {
4231            decode_byte::<A>(b2, 2)?;
4232            decode_byte::<A>(b3, 3)?;
4233            Ok(3)
4234        }
4235    }
4236}
4237
4238fn decode_chunk<A: Alphabet, const PAD: bool>(
4239    input: [u8; 4],
4240    output: &mut [u8],
4241) -> Result<usize, DecodeError> {
4242    let [b0, b1, b2, b3] = input;
4243    let v0 = decode_byte::<A>(b0, 0)?;
4244    let v1 = decode_byte::<A>(b1, 1)?;
4245
4246    match (b2, b3) {
4247        (b'=', b'=') if PAD => {
4248            if output.is_empty() {
4249                return Err(DecodeError::OutputTooSmall {
4250                    required: 1,
4251                    available: output.len(),
4252                });
4253            }
4254            if v1 & 0b0000_1111 != 0 {
4255                return Err(DecodeError::InvalidPadding { index: 1 });
4256            }
4257            output[0] = (v0 << 2) | (v1 >> 4);
4258            Ok(1)
4259        }
4260        (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
4261        (_, b'=') if PAD => {
4262            if output.len() < 2 {
4263                return Err(DecodeError::OutputTooSmall {
4264                    required: 2,
4265                    available: output.len(),
4266                });
4267            }
4268            let v2 = decode_byte::<A>(b2, 2)?;
4269            if v2 & 0b0000_0011 != 0 {
4270                return Err(DecodeError::InvalidPadding { index: 2 });
4271            }
4272            output[0] = (v0 << 2) | (v1 >> 4);
4273            output[1] = (v1 << 4) | (v2 >> 2);
4274            Ok(2)
4275        }
4276        (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
4277            index: first_padding_index(input),
4278        }),
4279        _ => {
4280            if output.len() < 3 {
4281                return Err(DecodeError::OutputTooSmall {
4282                    required: 3,
4283                    available: output.len(),
4284                });
4285            }
4286            let v2 = decode_byte::<A>(b2, 2)?;
4287            let v3 = decode_byte::<A>(b3, 3)?;
4288            output[0] = (v0 << 2) | (v1 >> 4);
4289            output[1] = (v1 << 4) | (v2 >> 2);
4290            output[2] = (v2 << 6) | v3;
4291            Ok(3)
4292        }
4293    }
4294}
4295
4296fn validate_tail_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
4297    match input.len() {
4298        0 => Ok(()),
4299        2 => {
4300            decode_byte::<A>(input[0], 0)?;
4301            let v1 = decode_byte::<A>(input[1], 1)?;
4302            if v1 & 0b0000_1111 != 0 {
4303                return Err(DecodeError::InvalidPadding { index: 1 });
4304            }
4305            Ok(())
4306        }
4307        3 => {
4308            decode_byte::<A>(input[0], 0)?;
4309            decode_byte::<A>(input[1], 1)?;
4310            let v2 = decode_byte::<A>(input[2], 2)?;
4311            if v2 & 0b0000_0011 != 0 {
4312                return Err(DecodeError::InvalidPadding { index: 2 });
4313            }
4314            Ok(())
4315        }
4316        _ => Err(DecodeError::InvalidLength),
4317    }
4318}
4319
4320fn decode_tail_unpadded<A: Alphabet>(
4321    input: &[u8],
4322    output: &mut [u8],
4323) -> Result<usize, DecodeError> {
4324    match input.len() {
4325        0 => Ok(0),
4326        2 => {
4327            if output.is_empty() {
4328                return Err(DecodeError::OutputTooSmall {
4329                    required: 1,
4330                    available: output.len(),
4331                });
4332            }
4333            let v0 = decode_byte::<A>(input[0], 0)?;
4334            let v1 = decode_byte::<A>(input[1], 1)?;
4335            if v1 & 0b0000_1111 != 0 {
4336                return Err(DecodeError::InvalidPadding { index: 1 });
4337            }
4338            output[0] = (v0 << 2) | (v1 >> 4);
4339            Ok(1)
4340        }
4341        3 => {
4342            if output.len() < 2 {
4343                return Err(DecodeError::OutputTooSmall {
4344                    required: 2,
4345                    available: output.len(),
4346                });
4347            }
4348            let v0 = decode_byte::<A>(input[0], 0)?;
4349            let v1 = decode_byte::<A>(input[1], 1)?;
4350            let v2 = decode_byte::<A>(input[2], 2)?;
4351            if v2 & 0b0000_0011 != 0 {
4352                return Err(DecodeError::InvalidPadding { index: 2 });
4353            }
4354            output[0] = (v0 << 2) | (v1 >> 4);
4355            output[1] = (v1 << 4) | (v2 >> 2);
4356            Ok(2)
4357        }
4358        _ => Err(DecodeError::InvalidLength),
4359    }
4360}
4361
4362fn decode_byte<A: Alphabet>(byte: u8, index: usize) -> Result<u8, DecodeError> {
4363    A::decode(byte).ok_or(DecodeError::InvalidByte { index, byte })
4364}
4365
4366fn ct_decode_slice<A: Alphabet, const PAD: bool>(
4367    input: &[u8],
4368    output: &mut [u8],
4369) -> Result<usize, DecodeError> {
4370    if input.is_empty() {
4371        return Ok(0);
4372    }
4373
4374    if PAD {
4375        ct_decode_padded::<A>(input, output)
4376    } else {
4377        ct_decode_unpadded::<A>(input, output)
4378    }
4379}
4380
4381fn ct_decode_in_place<A: Alphabet, const PAD: bool>(
4382    buffer: &mut [u8],
4383) -> Result<usize, DecodeError> {
4384    if buffer.is_empty() {
4385        return Ok(0);
4386    }
4387
4388    if PAD {
4389        ct_decode_padded_in_place::<A>(buffer)
4390    } else {
4391        ct_decode_unpadded_in_place::<A>(buffer)
4392    }
4393}
4394
4395fn ct_validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<(), DecodeError> {
4396    if input.is_empty() {
4397        return Ok(());
4398    }
4399
4400    if PAD {
4401        ct_validate_padded::<A>(input)
4402    } else {
4403        ct_validate_unpadded::<A>(input)
4404    }
4405}
4406
4407fn ct_validate_padded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
4408    if !input.len().is_multiple_of(4) {
4409        return Err(DecodeError::InvalidLength);
4410    }
4411
4412    let padding = ct_padding_len(input);
4413    let mut invalid_byte = 0u8;
4414    let mut invalid_padding = 0u8;
4415    let mut read = 0;
4416
4417    while read + 4 < input.len() {
4418        let [b0, b1, b2, b3] = read_quad(input, read)?;
4419        let (_, valid0) = ct_decode_ascii_base64::<A>(b0);
4420        let (_, valid1) = ct_decode_ascii_base64::<A>(b1);
4421        let (_, valid2) = ct_decode_ascii_base64::<A>(b2);
4422        let (_, valid3) = ct_decode_ascii_base64::<A>(b3);
4423
4424        invalid_byte |= !valid0;
4425        invalid_byte |= !valid1;
4426        invalid_byte |= !valid2;
4427        invalid_byte |= !valid3;
4428        invalid_padding |= ct_mask_eq_u8(b2, b'=');
4429        invalid_padding |= ct_mask_eq_u8(b3, b'=');
4430        read += 4;
4431    }
4432
4433    let final_chunk = read_quad(input, read)?;
4434    let (_, final_invalid_byte, final_invalid_padding, _) =
4435        ct_padded_final_quantum::<A>(final_chunk, padding);
4436    invalid_byte |= final_invalid_byte;
4437    invalid_padding |= final_invalid_padding;
4438
4439    report_ct_error(invalid_byte, invalid_padding)
4440}
4441
4442fn ct_validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
4443    if input.len() % 4 == 1 {
4444        return Err(DecodeError::InvalidLength);
4445    }
4446
4447    let mut invalid_byte = 0u8;
4448    let mut invalid_padding = 0u8;
4449    let mut read = 0;
4450
4451    while read + 4 <= input.len() {
4452        let b0 = input[read];
4453        let b1 = input[read + 1];
4454        let b2 = input[read + 2];
4455        let b3 = input[read + 3];
4456        let (_, valid0) = ct_decode_ascii_base64::<A>(b0);
4457        let (_, valid1) = ct_decode_ascii_base64::<A>(b1);
4458        let (_, valid2) = ct_decode_ascii_base64::<A>(b2);
4459        let (_, valid3) = ct_decode_ascii_base64::<A>(b3);
4460
4461        invalid_byte |= !valid0;
4462        invalid_byte |= !valid1;
4463        invalid_byte |= !valid2;
4464        invalid_byte |= !valid3;
4465        invalid_padding |= ct_mask_eq_u8(b0, b'=');
4466        invalid_padding |= ct_mask_eq_u8(b1, b'=');
4467        invalid_padding |= ct_mask_eq_u8(b2, b'=');
4468        invalid_padding |= ct_mask_eq_u8(b3, b'=');
4469
4470        read += 4;
4471    }
4472
4473    match input.len() - read {
4474        0 => {}
4475        2 => {
4476            let b0 = input[read];
4477            let b1 = input[read + 1];
4478            let (_, valid0) = ct_decode_ascii_base64::<A>(b0);
4479            let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
4480            invalid_byte |= !valid0;
4481            invalid_byte |= !valid1;
4482            invalid_padding |= ct_mask_eq_u8(b0, b'=');
4483            invalid_padding |= ct_mask_eq_u8(b1, b'=');
4484            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
4485        }
4486        3 => {
4487            let b0 = input[read];
4488            let b1 = input[read + 1];
4489            let b2 = input[read + 2];
4490            let (_, valid0) = ct_decode_ascii_base64::<A>(b0);
4491            let (_, valid1) = ct_decode_ascii_base64::<A>(b1);
4492            let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
4493            invalid_byte |= !valid0;
4494            invalid_byte |= !valid1;
4495            invalid_byte |= !valid2;
4496            invalid_padding |= ct_mask_eq_u8(b0, b'=');
4497            invalid_padding |= ct_mask_eq_u8(b1, b'=');
4498            invalid_padding |= ct_mask_eq_u8(b2, b'=');
4499            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
4500        }
4501        _ => return Err(DecodeError::InvalidLength),
4502    }
4503
4504    report_ct_error(invalid_byte, invalid_padding)
4505}
4506
4507fn ct_padded_final_quantum<A: Alphabet>(
4508    input: [u8; 4],
4509    padding: usize,
4510) -> ([u8; 3], u8, u8, usize) {
4511    let [b0, b1, b2, b3] = input;
4512    let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
4513    let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
4514    let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
4515    let (v3, valid3) = ct_decode_ascii_base64::<A>(b3);
4516
4517    let padding_byte = padding.to_le_bytes()[0];
4518    let no_padding = ct_mask_eq_u8(padding_byte, 0);
4519    let one_padding = ct_mask_eq_u8(padding_byte, 1);
4520    let two_padding = ct_mask_eq_u8(padding_byte, 2);
4521    let require_v2 = no_padding | one_padding;
4522    let require_v3 = no_padding;
4523
4524    let invalid_byte = !valid0 | !valid1 | (!valid2 & require_v2) | (!valid3 & require_v3);
4525    let invalid_padding = (ct_mask_nonzero_u8(v1 & 0b0000_1111) & two_padding)
4526        | ((ct_mask_eq_u8(b2, b'=') | ct_mask_nonzero_u8(v2 & 0b0000_0011)) & one_padding)
4527        | ((ct_mask_eq_u8(b2, b'=') | ct_mask_eq_u8(b3, b'=')) & no_padding);
4528
4529    (
4530        [(v0 << 2) | (v1 >> 4), (v1 << 4) | (v2 >> 2), (v2 << 6) | v3],
4531        invalid_byte,
4532        invalid_padding,
4533        3 - padding,
4534    )
4535}
4536
4537fn ct_decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
4538    if !input.len().is_multiple_of(4) {
4539        return Err(DecodeError::InvalidLength);
4540    }
4541
4542    let padding = ct_padding_len(input);
4543    let required = input.len() / 4 * 3 - padding;
4544    if output.len() < required {
4545        return Err(DecodeError::OutputTooSmall {
4546            required,
4547            available: output.len(),
4548        });
4549    }
4550
4551    let mut invalid_byte = 0u8;
4552    let mut invalid_padding = 0u8;
4553    let mut write = 0;
4554    let mut read = 0;
4555
4556    while read + 4 < input.len() {
4557        let [b0, b1, b2, b3] = read_quad(input, read)?;
4558        let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
4559        let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
4560        let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
4561        let (v3, valid3) = ct_decode_ascii_base64::<A>(b3);
4562
4563        invalid_byte |= !valid0;
4564        invalid_byte |= !valid1;
4565        invalid_byte |= !valid2;
4566        invalid_byte |= !valid3;
4567        invalid_padding |= ct_mask_eq_u8(b2, b'=');
4568        invalid_padding |= ct_mask_eq_u8(b3, b'=');
4569        output[write] = (v0 << 2) | (v1 >> 4);
4570        output[write + 1] = (v1 << 4) | (v2 >> 2);
4571        output[write + 2] = (v2 << 6) | v3;
4572        write += 3;
4573        read += 4;
4574    }
4575
4576    let final_chunk = read_quad(input, read)?;
4577    let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
4578        ct_padded_final_quantum::<A>(final_chunk, padding);
4579    invalid_byte |= final_invalid_byte;
4580    invalid_padding |= final_invalid_padding;
4581    output[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
4582    write += final_written;
4583
4584    report_ct_error(invalid_byte, invalid_padding)?;
4585    Ok(write)
4586}
4587
4588fn ct_decode_padded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
4589    if !buffer.len().is_multiple_of(4) {
4590        return Err(DecodeError::InvalidLength);
4591    }
4592
4593    let padding = ct_padding_len(buffer);
4594    let required = buffer.len() / 4 * 3 - padding;
4595    debug_assert!(required <= buffer.len());
4596
4597    let mut invalid_byte = 0u8;
4598    let mut invalid_padding = 0u8;
4599    let mut write = 0;
4600    let mut read = 0;
4601
4602    while read + 4 < buffer.len() {
4603        let [b0, b1, b2, b3] = read_quad(buffer, read)?;
4604        let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
4605        let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
4606        let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
4607        let (v3, valid3) = ct_decode_ascii_base64::<A>(b3);
4608
4609        invalid_byte |= !valid0;
4610        invalid_byte |= !valid1;
4611        invalid_byte |= !valid2;
4612        invalid_byte |= !valid3;
4613        invalid_padding |= ct_mask_eq_u8(b2, b'=');
4614        invalid_padding |= ct_mask_eq_u8(b3, b'=');
4615        buffer[write] = (v0 << 2) | (v1 >> 4);
4616        buffer[write + 1] = (v1 << 4) | (v2 >> 2);
4617        buffer[write + 2] = (v2 << 6) | v3;
4618        write += 3;
4619        read += 4;
4620    }
4621
4622    let final_chunk = read_quad(buffer, read)?;
4623    let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
4624        ct_padded_final_quantum::<A>(final_chunk, padding);
4625    invalid_byte |= final_invalid_byte;
4626    invalid_padding |= final_invalid_padding;
4627    buffer[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
4628    write += final_written;
4629
4630    debug_assert_eq!(write, required);
4631    report_ct_error(invalid_byte, invalid_padding)?;
4632    Ok(write)
4633}
4634
4635fn ct_decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
4636    if input.len() % 4 == 1 {
4637        return Err(DecodeError::InvalidLength);
4638    }
4639
4640    let required = decoded_capacity(input.len());
4641    if output.len() < required {
4642        return Err(DecodeError::OutputTooSmall {
4643            required,
4644            available: output.len(),
4645        });
4646    }
4647
4648    let mut invalid_byte = 0u8;
4649    let mut invalid_padding = 0u8;
4650    let mut write = 0;
4651    let mut read = 0;
4652
4653    while read + 4 <= input.len() {
4654        let b0 = input[read];
4655        let b1 = input[read + 1];
4656        let b2 = input[read + 2];
4657        let b3 = input[read + 3];
4658        let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
4659        let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
4660        let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
4661        let (v3, valid3) = ct_decode_ascii_base64::<A>(b3);
4662
4663        invalid_byte |= !valid0;
4664        invalid_byte |= !valid1;
4665        invalid_byte |= !valid2;
4666        invalid_byte |= !valid3;
4667        invalid_padding |= ct_mask_eq_u8(b0, b'=');
4668        invalid_padding |= ct_mask_eq_u8(b1, b'=');
4669        invalid_padding |= ct_mask_eq_u8(b2, b'=');
4670        invalid_padding |= ct_mask_eq_u8(b3, b'=');
4671
4672        output[write] = (v0 << 2) | (v1 >> 4);
4673        output[write + 1] = (v1 << 4) | (v2 >> 2);
4674        output[write + 2] = (v2 << 6) | v3;
4675        read += 4;
4676        write += 3;
4677    }
4678
4679    match input.len() - read {
4680        0 => {}
4681        2 => {
4682            let b0 = input[read];
4683            let b1 = input[read + 1];
4684            let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
4685            let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
4686            invalid_byte |= !valid0;
4687            invalid_byte |= !valid1;
4688            invalid_padding |= ct_mask_eq_u8(b0, b'=');
4689            invalid_padding |= ct_mask_eq_u8(b1, b'=');
4690            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
4691            output[write] = (v0 << 2) | (v1 >> 4);
4692            write += 1;
4693        }
4694        3 => {
4695            let b0 = input[read];
4696            let b1 = input[read + 1];
4697            let b2 = input[read + 2];
4698            let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
4699            let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
4700            let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
4701            invalid_byte |= !valid0;
4702            invalid_byte |= !valid1;
4703            invalid_byte |= !valid2;
4704            invalid_padding |= ct_mask_eq_u8(b0, b'=');
4705            invalid_padding |= ct_mask_eq_u8(b1, b'=');
4706            invalid_padding |= ct_mask_eq_u8(b2, b'=');
4707            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
4708            output[write] = (v0 << 2) | (v1 >> 4);
4709            output[write + 1] = (v1 << 4) | (v2 >> 2);
4710            write += 2;
4711        }
4712        _ => return Err(DecodeError::InvalidLength),
4713    }
4714
4715    report_ct_error(invalid_byte, invalid_padding)?;
4716    Ok(write)
4717}
4718
4719fn ct_decode_unpadded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
4720    if buffer.len() % 4 == 1 {
4721        return Err(DecodeError::InvalidLength);
4722    }
4723
4724    let required = decoded_capacity(buffer.len());
4725    debug_assert!(required <= buffer.len());
4726
4727    let mut invalid_byte = 0u8;
4728    let mut invalid_padding = 0u8;
4729    let mut write = 0;
4730    let mut read = 0;
4731
4732    while read + 4 <= buffer.len() {
4733        let b0 = buffer[read];
4734        let b1 = buffer[read + 1];
4735        let b2 = buffer[read + 2];
4736        let b3 = buffer[read + 3];
4737        let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
4738        let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
4739        let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
4740        let (v3, valid3) = ct_decode_ascii_base64::<A>(b3);
4741
4742        invalid_byte |= !valid0;
4743        invalid_byte |= !valid1;
4744        invalid_byte |= !valid2;
4745        invalid_byte |= !valid3;
4746        invalid_padding |= ct_mask_eq_u8(b0, b'=');
4747        invalid_padding |= ct_mask_eq_u8(b1, b'=');
4748        invalid_padding |= ct_mask_eq_u8(b2, b'=');
4749        invalid_padding |= ct_mask_eq_u8(b3, b'=');
4750
4751        buffer[write] = (v0 << 2) | (v1 >> 4);
4752        buffer[write + 1] = (v1 << 4) | (v2 >> 2);
4753        buffer[write + 2] = (v2 << 6) | v3;
4754        read += 4;
4755        write += 3;
4756    }
4757
4758    match buffer.len() - read {
4759        0 => {}
4760        2 => {
4761            let b0 = buffer[read];
4762            let b1 = buffer[read + 1];
4763            let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
4764            let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
4765            invalid_byte |= !valid0;
4766            invalid_byte |= !valid1;
4767            invalid_padding |= ct_mask_eq_u8(b0, b'=');
4768            invalid_padding |= ct_mask_eq_u8(b1, b'=');
4769            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
4770            buffer[write] = (v0 << 2) | (v1 >> 4);
4771            write += 1;
4772        }
4773        3 => {
4774            let b0 = buffer[read];
4775            let b1 = buffer[read + 1];
4776            let b2 = buffer[read + 2];
4777            let (v0, valid0) = ct_decode_ascii_base64::<A>(b0);
4778            let (v1, valid1) = ct_decode_ascii_base64::<A>(b1);
4779            let (v2, valid2) = ct_decode_ascii_base64::<A>(b2);
4780            invalid_byte |= !valid0;
4781            invalid_byte |= !valid1;
4782            invalid_byte |= !valid2;
4783            invalid_padding |= ct_mask_eq_u8(b0, b'=');
4784            invalid_padding |= ct_mask_eq_u8(b1, b'=');
4785            invalid_padding |= ct_mask_eq_u8(b2, b'=');
4786            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
4787            buffer[write] = (v0 << 2) | (v1 >> 4);
4788            buffer[write + 1] = (v1 << 4) | (v2 >> 2);
4789            write += 2;
4790        }
4791        _ => return Err(DecodeError::InvalidLength),
4792    }
4793
4794    debug_assert_eq!(write, required);
4795    report_ct_error(invalid_byte, invalid_padding)?;
4796    Ok(write)
4797}
4798
4799#[inline]
4800fn ct_decode_ascii_base64<A: Alphabet>(byte: u8) -> (u8, u8) {
4801    let upper = ct_mask_lt_u8(byte.wrapping_sub(b'A'), 26);
4802    let lower = ct_mask_lt_u8(byte.wrapping_sub(b'a'), 26);
4803    let digit = ct_mask_lt_u8(byte.wrapping_sub(b'0'), 10);
4804    let value_62 = ct_mask_eq_u8(byte, A::ENCODE[62]);
4805    let value_63 = ct_mask_eq_u8(byte, A::ENCODE[63]);
4806    let valid = upper | lower | digit | value_62 | value_63;
4807
4808    let decoded = (byte.wrapping_sub(b'A') & upper)
4809        | (byte.wrapping_sub(b'a').wrapping_add(26) & lower)
4810        | (byte.wrapping_sub(b'0').wrapping_add(52) & digit)
4811        | (0x3e & value_62)
4812        | (0x3f & value_63);
4813
4814    (decoded, valid)
4815}
4816
4817fn ct_padding_len(input: &[u8]) -> usize {
4818    let last = input[input.len() - 1];
4819    let before_last = input[input.len() - 2];
4820    usize::from(ct_mask_eq_u8(last, b'=') & 1) + usize::from(ct_mask_eq_u8(before_last, b'=') & 1)
4821}
4822
4823fn report_ct_error(invalid_byte: u8, invalid_padding: u8) -> Result<(), DecodeError> {
4824    if (invalid_byte | invalid_padding) != 0 {
4825        Err(DecodeError::InvalidInput)
4826    } else {
4827        Ok(())
4828    }
4829}
4830
4831#[cfg(kani)]
4832mod kani_proofs {
4833    use super::{STANDARD, checked_encoded_len, decoded_capacity};
4834
4835    #[kani::proof]
4836    fn checked_encoded_len_is_bounded_for_small_inputs() {
4837        let len = usize::from(kani::any::<u8>());
4838        let padded = kani::any::<bool>();
4839        let encoded = checked_encoded_len(len, padded).expect("u8 input length cannot overflow");
4840
4841        assert!(encoded >= len);
4842        assert!(encoded <= len / 3 * 4 + 4);
4843    }
4844
4845    #[kani::proof]
4846    fn decoded_capacity_is_bounded_for_small_inputs() {
4847        let len = usize::from(kani::any::<u8>());
4848        let capacity = decoded_capacity(len);
4849
4850        assert!(capacity <= len / 4 * 3 + 2);
4851    }
4852
4853    #[kani::proof]
4854    #[kani::unwind(3)]
4855    fn standard_in_place_decode_returns_prefix_within_buffer() {
4856        let mut buffer = kani::any::<[u8; 8]>();
4857        let result = STANDARD.decode_in_place(&mut buffer);
4858
4859        if let Ok(decoded) = result {
4860            assert!(decoded.len() <= 8);
4861        }
4862    }
4863
4864    #[kani::proof]
4865    #[kani::unwind(3)]
4866    fn standard_decode_slice_returns_written_within_output() {
4867        let input = kani::any::<[u8; 4]>();
4868        let mut output = kani::any::<[u8; 3]>();
4869        let result = STANDARD.decode_slice(&input, &mut output);
4870
4871        if let Ok(written) = result {
4872            assert!(written <= output.len());
4873        }
4874    }
4875
4876    #[kani::proof]
4877    #[kani::unwind(3)]
4878    fn standard_decode_slice_clear_tail_clears_output_on_error() {
4879        let input = kani::any::<[u8; 4]>();
4880        let mut output = kani::any::<[u8; 3]>();
4881        let result = STANDARD.decode_slice_clear_tail(&input, &mut output);
4882
4883        if result.is_err() {
4884            assert!(output.iter().all(|byte| *byte == 0));
4885        }
4886    }
4887
4888    #[kani::proof]
4889    #[kani::unwind(3)]
4890    fn standard_encode_slice_returns_written_within_output() {
4891        let input = kani::any::<[u8; 3]>();
4892        let mut output = kani::any::<[u8; 4]>();
4893        let result = STANDARD.encode_slice(&input, &mut output);
4894
4895        if let Ok(written) = result {
4896            assert!(written <= output.len());
4897        }
4898    }
4899
4900    #[kani::proof]
4901    #[kani::unwind(4)]
4902    fn standard_encode_in_place_returns_prefix_within_buffer() {
4903        let mut buffer = kani::any::<[u8; 8]>();
4904        let input_len = usize::from(kani::any::<u8>() % 9);
4905        let result = STANDARD.encode_in_place(&mut buffer, input_len);
4906
4907        if let Ok(encoded) = result {
4908            assert!(encoded.len() <= 8);
4909        }
4910    }
4911
4912    #[kani::proof]
4913    #[kani::unwind(3)]
4914    fn standard_clear_tail_decode_clears_buffer_on_error() {
4915        let mut buffer = kani::any::<[u8; 4]>();
4916        let result = STANDARD.decode_in_place_clear_tail(&mut buffer);
4917
4918        if result.is_err() {
4919            assert!(buffer.iter().all(|byte| *byte == 0));
4920        }
4921    }
4922}
4923
4924#[cfg(test)]
4925mod tests {
4926    use super::*;
4927
4928    fn fill_pattern(output: &mut [u8], seed: usize) {
4929        for (index, byte) in output.iter_mut().enumerate() {
4930            let value = (index * 73 + seed * 19) % 256;
4931            *byte = u8::try_from(value).unwrap();
4932        }
4933    }
4934
4935    fn assert_encode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
4936    where
4937        A: Alphabet,
4938    {
4939        let engine = Engine::<A, PAD>::new();
4940        let mut dispatched = [0x55; 256];
4941        let mut scalar = [0xaa; 256];
4942
4943        let dispatched_result = engine.encode_slice(input, &mut dispatched);
4944        let scalar_result = backend::scalar_reference_encode_slice::<A, PAD>(input, &mut scalar);
4945
4946        assert_eq!(dispatched_result, scalar_result);
4947        if let Ok(written) = dispatched_result {
4948            assert_eq!(&dispatched[..written], &scalar[..written]);
4949        }
4950
4951        let required = checked_encoded_len(input.len(), PAD).unwrap();
4952        if required > 0 {
4953            let mut dispatched_short = [0x55; 256];
4954            let mut scalar_short = [0xaa; 256];
4955            let available = required - 1;
4956
4957            assert_eq!(
4958                engine.encode_slice(input, &mut dispatched_short[..available]),
4959                backend::scalar_reference_encode_slice::<A, PAD>(
4960                    input,
4961                    &mut scalar_short[..available],
4962                )
4963            );
4964        }
4965    }
4966
4967    fn assert_decode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
4968    where
4969        A: Alphabet,
4970    {
4971        let engine = Engine::<A, PAD>::new();
4972        let mut dispatched = [0x55; 128];
4973        let mut scalar = [0xaa; 128];
4974
4975        let dispatched_result = engine.decode_slice(input, &mut dispatched);
4976        let scalar_result = backend::scalar_reference_decode_slice::<A, PAD>(input, &mut scalar);
4977
4978        assert_eq!(dispatched_result, scalar_result);
4979        if let Ok(written) = dispatched_result {
4980            assert_eq!(&dispatched[..written], &scalar[..written]);
4981
4982            if written > 0 {
4983                let mut dispatched_short = [0x55; 128];
4984                let mut scalar_short = [0xaa; 128];
4985                let available = written - 1;
4986
4987                assert_eq!(
4988                    engine.decode_slice(input, &mut dispatched_short[..available]),
4989                    backend::scalar_reference_decode_slice::<A, PAD>(
4990                        input,
4991                        &mut scalar_short[..available],
4992                    )
4993                );
4994            }
4995        }
4996    }
4997
4998    fn assert_backend_round_trip_matches_scalar<A, const PAD: bool>(input: &[u8])
4999    where
5000        A: Alphabet,
5001    {
5002        assert_encode_backend_matches_scalar::<A, PAD>(input);
5003
5004        let mut encoded = [0; 256];
5005        let encoded_len =
5006            backend::scalar_reference_encode_slice::<A, PAD>(input, &mut encoded).unwrap();
5007        assert_decode_backend_matches_scalar::<A, PAD>(&encoded[..encoded_len]);
5008    }
5009
5010    #[test]
5011    fn backend_dispatch_matches_scalar_reference_for_canonical_inputs() {
5012        let mut input = [0; 128];
5013
5014        for input_len in 0..=input.len() {
5015            fill_pattern(&mut input[..input_len], input_len);
5016            let input = &input[..input_len];
5017
5018            assert_backend_round_trip_matches_scalar::<Standard, true>(input);
5019            assert_backend_round_trip_matches_scalar::<Standard, false>(input);
5020            assert_backend_round_trip_matches_scalar::<UrlSafe, true>(input);
5021            assert_backend_round_trip_matches_scalar::<UrlSafe, false>(input);
5022        }
5023    }
5024
5025    #[test]
5026    fn backend_dispatch_matches_scalar_reference_for_malformed_inputs() {
5027        for input in [
5028            &b"Z"[..],
5029            b"====",
5030            b"AA=A",
5031            b"Zh==",
5032            b"Zm9=",
5033            b"Zm9v$g==",
5034            b"Zm9vZh==",
5035        ] {
5036            assert_decode_backend_matches_scalar::<Standard, true>(input);
5037        }
5038
5039        for input in [&b"Z"[..], b"AA=A", b"Zh", b"Zm9", b"Zm9vYg$"] {
5040            assert_decode_backend_matches_scalar::<Standard, false>(input);
5041        }
5042
5043        assert_decode_backend_matches_scalar::<UrlSafe, true>(b"AA+A");
5044        assert_decode_backend_matches_scalar::<UrlSafe, false>(b"AA/A");
5045        assert_decode_backend_matches_scalar::<Standard, true>(b"AA-A");
5046        assert_decode_backend_matches_scalar::<Standard, false>(b"AA_A");
5047    }
5048
5049    #[cfg(feature = "simd")]
5050    #[test]
5051    fn simd_dispatch_scaffold_keeps_scalar_active() {
5052        assert_eq!(simd::active_backend(), simd::ActiveBackend::Scalar);
5053        let _candidate = simd::detected_candidate();
5054    }
5055
5056    #[test]
5057    fn encodes_standard_vectors() {
5058        let vectors = [
5059            (&b""[..], &b""[..]),
5060            (&b"f"[..], &b"Zg=="[..]),
5061            (&b"fo"[..], &b"Zm8="[..]),
5062            (&b"foo"[..], &b"Zm9v"[..]),
5063            (&b"foob"[..], &b"Zm9vYg=="[..]),
5064            (&b"fooba"[..], &b"Zm9vYmE="[..]),
5065            (&b"foobar"[..], &b"Zm9vYmFy"[..]),
5066        ];
5067        for (input, expected) in vectors {
5068            let mut output = [0u8; 16];
5069            let written = STANDARD.encode_slice(input, &mut output).unwrap();
5070            assert_eq!(&output[..written], expected);
5071        }
5072    }
5073
5074    #[test]
5075    fn decodes_standard_vectors() {
5076        let vectors = [
5077            (&b""[..], &b""[..]),
5078            (&b"Zg=="[..], &b"f"[..]),
5079            (&b"Zm8="[..], &b"fo"[..]),
5080            (&b"Zm9v"[..], &b"foo"[..]),
5081            (&b"Zm9vYg=="[..], &b"foob"[..]),
5082            (&b"Zm9vYmE="[..], &b"fooba"[..]),
5083            (&b"Zm9vYmFy"[..], &b"foobar"[..]),
5084        ];
5085        for (input, expected) in vectors {
5086            let mut output = [0u8; 16];
5087            let written = STANDARD.decode_slice(input, &mut output).unwrap();
5088            assert_eq!(&output[..written], expected);
5089        }
5090    }
5091
5092    #[test]
5093    fn supports_unpadded_url_safe() {
5094        let mut encoded = [0u8; 16];
5095        let written = URL_SAFE_NO_PAD
5096            .encode_slice(b"\xfb\xff", &mut encoded)
5097            .unwrap();
5098        assert_eq!(&encoded[..written], b"-_8");
5099
5100        let mut decoded = [0u8; 2];
5101        let written = URL_SAFE_NO_PAD
5102            .decode_slice(&encoded[..written], &mut decoded)
5103            .unwrap();
5104        assert_eq!(&decoded[..written], b"\xfb\xff");
5105    }
5106
5107    #[test]
5108    fn decodes_in_place() {
5109        let mut buffer = *b"Zm9vYmFy";
5110        let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
5111        assert_eq!(decoded, b"foobar");
5112    }
5113
5114    #[test]
5115    fn rejects_non_canonical_padding_bits() {
5116        let mut output = [0u8; 4];
5117        assert_eq!(
5118            STANDARD.decode_slice(b"Zh==", &mut output),
5119            Err(DecodeError::InvalidPadding { index: 1 })
5120        );
5121        assert_eq!(
5122            STANDARD.decode_slice(b"Zm9=", &mut output),
5123            Err(DecodeError::InvalidPadding { index: 2 })
5124        );
5125    }
5126}