Skip to main content

base64_ng/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2#![deny(unsafe_code)]
3#![deny(missing_docs)]
4#![deny(clippy::all)]
5#![deny(clippy::pedantic)]
6#![allow(clippy::missing_errors_doc)]
7
8//! `base64-ng` is a `no_std`-first Base64 encoder and decoder.
9//!
10//! This initial release provides strict scalar RFC 4648-style behavior and
11//! caller-owned output buffers. Future SIMD fast paths, including AVX, NEON,
12//! and wasm `simd128` candidates, will be required to match this scalar module
13//! byte-for-byte.
14//!
15//! # Examples
16//!
17//! Encode and decode with caller-owned buffers:
18//!
19//! ```
20//! use base64_ng::{STANDARD, checked_encoded_len};
21//!
22//! let input = b"hello";
23//! const ENCODED_CAPACITY: usize = match checked_encoded_len(5, true) {
24//!     Some(len) => len,
25//!     None => panic!("encoded length overflow"),
26//! };
27//! let mut encoded = [0u8; ENCODED_CAPACITY];
28//! let encoded_len = STANDARD.encode_slice(input, &mut encoded).unwrap();
29//! assert_eq!(&encoded[..encoded_len], b"aGVsbG8=");
30//!
31//! let mut decoded = [0u8; 5];
32//! let decoded_len = STANDARD.decode_slice(&encoded, &mut decoded).unwrap();
33//! assert_eq!(&decoded[..decoded_len], input);
34//! ```
35//!
36//! Use the URL-safe no-padding engine:
37//!
38//! ```
39//! use base64_ng::URL_SAFE_NO_PAD;
40//!
41//! let mut encoded = [0u8; 3];
42//! let encoded_len = URL_SAFE_NO_PAD.encode_slice(b"\xfb\xff", &mut encoded).unwrap();
43//! assert_eq!(&encoded[..encoded_len], b"-_8");
44//! ```
45//!
46//! # Sensitive Decode Policy
47//!
48//! The default engines such as [`STANDARD`] and [`URL_SAFE_NO_PAD`] are strict
49//! scalar encoders/decoders with localized diagnostics. They are not
50//! constant-time token validators or key-material decoders: strict decode and
51//! validation may branch or return early based on malformed input. Use
52//! [`ct::STANDARD`], [`ct::URL_SAFE_NO_PAD`], or [`Engine::ct_decoder`] for
53//! secret-bearing payloads where decode timing posture matters more than exact
54//! error indexes.
55//!
56//! # Zeroization Caveat
57//!
58//! Cleanup APIs and redacted buffers use dependency-free best-effort wiping:
59//! byte-wise volatile zero writes followed by an architecture-gated inline
60//! assembly barrier plus a hardware store-ordering fence where stable Rust
61//! supports it, and a compiler fence on all targets. This resists common
62//! compiler dead-store elimination and orders the issued zero stores on native
63//! supported architectures, but it is not a formal zeroization guarantee and
64//! cannot clear historical copies, registers, cache lines, write buffers, swap,
65//! hibernation images, core dumps, cold-boot remanence, or OS-level memory
66//! snapshots.
67//! High-assurance applications should apply their own approved zeroization
68//! policy to caller-owned buffers at the protocol boundary. Architectures
69//! without a native wipe barrier fail closed by default unless
70//! `allow-compiler-fence-only-wipe` is enabled after platform review. On
71//! `wasm32`, the wipe barrier is compiler-fence-only and cannot constrain
72//! downstream wasm runtime JITs. For that reason, `wasm32` builds fail closed
73//! by default. Enable `allow-wasm32-best-effort-wipe` only when the deployment
74//! explicitly accepts compiler-fence-only cleanup and applies its own memory
75//! strategy.
76
77#[cfg(feature = "alloc")]
78extern crate alloc;
79
80#[cfg(all(target_arch = "wasm32", not(feature = "allow-wasm32-best-effort-wipe")))]
81compile_error!(
82    "base64-ng: wasm32 builds use a compiler-fence-only wipe barrier that cannot \
83     constrain downstream wasm runtime JITs. Enable \
84     `allow-wasm32-best-effort-wipe` to accept this limitation and use \
85     caller-owned, platform-approved zeroization for high-assurance wasm deployments."
86);
87
88#[cfg(all(
89    not(miri),
90    not(feature = "allow-compiler-fence-only-wipe"),
91    not(any(
92        target_arch = "aarch64",
93        target_arch = "arm",
94        target_arch = "riscv32",
95        target_arch = "riscv64",
96        target_arch = "wasm32",
97        target_arch = "x86",
98        target_arch = "x86_64",
99    ))
100))]
101compile_error!(
102    "base64-ng: this architecture has no native hardware wipe barrier in \
103     base64-ng. Enable `allow-compiler-fence-only-wipe` only after reviewing \
104     docs/UNSAFE.md and applying platform-approved memory hygiene controls."
105);
106
107#[cfg(feature = "simd")]
108mod simd;
109
110/// Runtime backend reporting for security-sensitive deployments.
111///
112/// This module does not enable acceleration. It exposes the backend posture so
113/// callers can log, assert, or audit whether execution is scalar-only or merely
114/// detecting future SIMD candidates.
115pub mod runtime {
116    /// A backend that can be reported by `base64-ng`.
117    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
118    #[non_exhaustive]
119    pub enum Backend {
120        /// The audited scalar backend.
121        Scalar,
122        /// An AVX-512 VBMI candidate was detected.
123        Avx512Vbmi,
124        /// An AVX2 candidate was detected.
125        Avx2,
126        /// An SSSE3/SSE4.1 candidate was detected.
127        Ssse3Sse41,
128        /// An ARM NEON candidate was detected.
129        Neon,
130        /// A wasm `simd128` candidate was detected.
131        WasmSimd128,
132    }
133
134    impl Backend {
135        /// Returns the stable lowercase identifier for this backend.
136        ///
137        /// ```
138        /// assert_eq!(base64_ng::runtime::Backend::Scalar.as_str(), "scalar");
139        /// ```
140        #[must_use]
141        pub const fn as_str(self) -> &'static str {
142            match self {
143                Self::Scalar => "scalar",
144                Self::Avx512Vbmi => "avx512-vbmi",
145                Self::Avx2 => "avx2",
146                Self::Ssse3Sse41 => "ssse3-sse4.1",
147                Self::Neon => "neon",
148                Self::WasmSimd128 => "wasm-simd128",
149            }
150        }
151
152        /// Returns the CPU features required before this backend may be used.
153        ///
154        /// The active backend is still scalar-only. This method exists so
155        /// security logs can record exactly which future backend feature bundle
156        /// was detected.
157        ///
158        /// ```
159        /// assert_eq!(
160        ///     base64_ng::runtime::Backend::Avx512Vbmi.required_cpu_features(),
161        ///     ["avx512f", "avx512bw", "avx512vl", "avx512vbmi"],
162        /// );
163        /// ```
164        #[must_use]
165        pub const fn required_cpu_features(self) -> &'static [&'static str] {
166            match self {
167                Self::Scalar => &[],
168                Self::Avx512Vbmi => &["avx512f", "avx512bw", "avx512vl", "avx512vbmi"],
169                Self::Avx2 => &["avx2"],
170                Self::Ssse3Sse41 => &["ssse3", "sse4.1"],
171                Self::Neon => &["neon"],
172                Self::WasmSimd128 => &["simd128"],
173            }
174        }
175    }
176
177    impl core::fmt::Display for Backend {
178        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
179            formatter.write_str(self.as_str())
180        }
181    }
182
183    /// How SIMD backend candidates were detected for this build.
184    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
185    #[non_exhaustive]
186    pub enum CandidateDetectionMode {
187        /// SIMD candidate detection is disabled because the `simd` feature is
188        /// not enabled.
189        SimdFeatureDisabled,
190        /// Candidate detection uses runtime CPU feature probing.
191        RuntimeCpuFeatures,
192        /// Candidate detection uses compile-time target features.
193        ///
194        /// This mode does not prove that the deployment CPU has the reported
195        /// feature; it only reflects how the binary was compiled.
196        CompileTimeTargetFeatures,
197    }
198
199    impl CandidateDetectionMode {
200        /// Returns the stable lowercase identifier for this detection mode.
201        ///
202        /// ```
203        /// assert_eq!(
204        ///     base64_ng::runtime::CandidateDetectionMode::SimdFeatureDisabled.as_str(),
205        ///     "simd-feature-disabled",
206        /// );
207        /// ```
208        #[must_use]
209        pub const fn as_str(self) -> &'static str {
210            match self {
211                Self::SimdFeatureDisabled => "simd-feature-disabled",
212                Self::RuntimeCpuFeatures => "runtime-cpu-features",
213                Self::CompileTimeTargetFeatures => "compile-time-target-features",
214            }
215        }
216    }
217
218    impl core::fmt::Display for CandidateDetectionMode {
219        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
220            formatter.write_str(self.as_str())
221        }
222    }
223
224    /// Security posture for the active runtime backend.
225    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
226    #[non_exhaustive]
227    pub enum SecurityPosture {
228        /// No accelerated backend is active.
229        ScalarOnly,
230        /// SIMD support may be detected, but execution still uses scalar.
231        SimdCandidateScalarActive,
232        /// A SIMD backend is active.
233        Accelerated,
234    }
235
236    impl SecurityPosture {
237        /// Returns the stable lowercase identifier for this security posture.
238        ///
239        /// ```
240        /// assert_eq!(
241        ///     base64_ng::runtime::SecurityPosture::ScalarOnly.as_str(),
242        ///     "scalar-only",
243        /// );
244        /// ```
245        #[must_use]
246        pub const fn as_str(self) -> &'static str {
247            match self {
248                Self::ScalarOnly => "scalar-only",
249                Self::SimdCandidateScalarActive => "simd-candidate-scalar-active",
250                Self::Accelerated => "accelerated",
251            }
252        }
253    }
254
255    impl core::fmt::Display for SecurityPosture {
256        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
257            formatter.write_str(self.as_str())
258        }
259    }
260
261    /// Wipe-barrier posture for this build and target.
262    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
263    #[non_exhaustive]
264    pub enum WipePosture {
265        /// The target uses a native store-ordering hardware fence in addition
266        /// to volatile writes and compiler fences.
267        ///
268        /// This describes wipe-store ordering only. It is separate from
269        /// [`CtGatePosture`], which reports whether the constant-time result
270        /// gate has a speculation barrier or only an ordering fence.
271        HardwareFence,
272        /// The target uses volatile writes and compiler fences only.
273        CompilerFenceOnly,
274    }
275
276    impl WipePosture {
277        /// Returns the stable lowercase identifier for this wipe posture.
278        #[must_use]
279        pub const fn as_str(self) -> &'static str {
280            match self {
281                Self::HardwareFence => "hardware-fence",
282                Self::CompilerFenceOnly => "compiler-fence-only",
283            }
284        }
285    }
286
287    impl core::fmt::Display for WipePosture {
288        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
289            formatter.write_str(self.as_str())
290        }
291    }
292
293    /// Constant-time result-gate barrier posture for this build and target.
294    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
295    #[non_exhaustive]
296    pub enum CtGatePosture {
297        /// The target uses a native speculation barrier before public CT
298        /// success/failure or equality-result branches.
299        ///
300        /// On `AArch64` this uses `isb sy` plus the CSDB hint encoding. Full
301        /// CSDB effectiveness depends on the deployed ARM architecture level;
302        /// older cores may treat the hint as a no-op.
303        HardwareSpeculationBarrier,
304        /// The target uses an ordering fence where the base ISA does not
305        /// provide a canonical speculation barrier.
306        OrderingFence,
307        /// The target uses compiler fences only.
308        CompilerFenceOnly,
309    }
310
311    impl CtGatePosture {
312        /// Returns the stable lowercase identifier for this CT gate posture.
313        #[must_use]
314        pub const fn as_str(self) -> &'static str {
315            match self {
316                Self::HardwareSpeculationBarrier => "hardware-speculation-barrier",
317                Self::OrderingFence => "ordering-fence",
318                Self::CompilerFenceOnly => "compiler-fence-only",
319            }
320        }
321    }
322
323    impl core::fmt::Display for CtGatePosture {
324        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
325            formatter.write_str(self.as_str())
326        }
327    }
328
329    /// Deployment policy for runtime backend assertions.
330    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
331    #[non_exhaustive]
332    pub enum BackendPolicy {
333        /// Require encode/decode execution to use the scalar backend.
334        ScalarExecutionOnly,
335        /// Require the crate to be built without the `simd` feature.
336        SimdFeatureDisabled,
337        /// Require no SIMD candidate to be visible to this build and target.
338        NoDetectedSimdCandidate,
339        /// Require scalar execution, the `simd` feature disabled, no detected
340        /// SIMD candidate, and the unsafe boundary enforced.
341        HighAssuranceScalarOnly,
342    }
343
344    impl BackendPolicy {
345        /// Returns the stable lowercase identifier for this policy.
346        ///
347        /// ```
348        /// assert_eq!(
349        ///     base64_ng::runtime::BackendPolicy::HighAssuranceScalarOnly.as_str(),
350        ///     "high-assurance-scalar-only",
351        /// );
352        /// ```
353        #[must_use]
354        pub const fn as_str(self) -> &'static str {
355            match self {
356                Self::ScalarExecutionOnly => "scalar-execution-only",
357                Self::SimdFeatureDisabled => "simd-feature-disabled",
358                Self::NoDetectedSimdCandidate => "no-detected-simd-candidate",
359                Self::HighAssuranceScalarOnly => "high-assurance-scalar-only",
360            }
361        }
362    }
363
364    impl core::fmt::Display for BackendPolicy {
365        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
366            formatter.write_str(self.as_str())
367        }
368    }
369
370    /// Runtime backend policy failure.
371    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
372    pub struct BackendPolicyError {
373        /// Policy that was requested.
374        pub policy: BackendPolicy,
375        /// Backend report observed when the policy failed.
376        pub report: BackendReport,
377    }
378
379    impl core::fmt::Display for BackendPolicyError {
380        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
381            write!(
382                formatter,
383                "runtime backend policy `{}` was not satisfied ({})",
384                self.policy, self.report,
385            )
386        }
387    }
388
389    #[cfg(feature = "std")]
390    impl std::error::Error for BackendPolicyError {}
391
392    /// Backend report for the current build and target.
393    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
394    pub struct BackendReport {
395        /// Backend currently used for encode/decode dispatch.
396        pub active: Backend,
397        /// Strongest backend candidate visible to the current build.
398        pub candidate: Backend,
399        /// Whether candidate visibility came from runtime CPU probing,
400        /// compile-time target features, or a disabled SIMD feature.
401        pub candidate_detection_mode: CandidateDetectionMode,
402        /// Whether the `simd` feature is enabled in this build.
403        pub simd_feature_enabled: bool,
404        /// Whether an accelerated SIMD backend is active.
405        pub accelerated_backend_active: bool,
406        /// Whether this build keeps the high-assurance scalar unsafe boundary.
407        ///
408        /// This is a conservative compile-time posture signal. It is `true`
409        /// only when the reserved `simd` feature is disabled; `simd` builds
410        /// expose additional private prototype boundaries and must use the
411        /// release evidence scripts for boundary validation.
412        pub unsafe_boundary_enforced: bool,
413        /// Current security posture.
414        pub security_posture: SecurityPosture,
415        /// Current wipe-barrier posture.
416        pub wipe_posture: WipePosture,
417        /// Current constant-time result-gate barrier posture.
418        pub ct_gate_posture: CtGatePosture,
419    }
420
421    /// Compact structured backend snapshot for logging and policy evidence.
422    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
423    pub struct BackendSnapshot {
424        /// Stable active backend identifier.
425        pub active: &'static str,
426        /// Stable detected candidate identifier.
427        pub candidate: &'static str,
428        /// Stable SIMD candidate detection-mode identifier.
429        pub candidate_detection_mode: &'static str,
430        /// CPU features required by the detected candidate.
431        pub candidate_required_cpu_features: &'static [&'static str],
432        /// Whether the `simd` feature is enabled in this build.
433        pub simd_feature_enabled: bool,
434        /// Whether an accelerated SIMD backend is active.
435        pub accelerated_backend_active: bool,
436        /// Whether this build keeps the high-assurance scalar unsafe boundary.
437        ///
438        /// This is `false` for `simd` builds even while execution remains
439        /// scalar-only, because those builds include additional private
440        /// prototype boundaries.
441        pub unsafe_boundary_enforced: bool,
442        /// Stable security posture identifier.
443        pub security_posture: &'static str,
444        /// Stable wipe-barrier posture identifier.
445        pub wipe_posture: &'static str,
446        /// Stable constant-time result-gate barrier posture identifier.
447        pub ct_gate_posture: &'static str,
448    }
449
450    impl core::fmt::Display for BackendReport {
451        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
452            write!(
453                formatter,
454                "active={} candidate={} candidate_detection_mode={} candidate_required_cpu_features=",
455                self.active, self.candidate, self.candidate_detection_mode,
456            )?;
457            write_feature_list(formatter, self.candidate_required_cpu_features())?;
458            write!(
459                formatter,
460                " simd_feature_enabled={} accelerated_backend_active={} unsafe_boundary_enforced={} security_posture={} wipe_posture={} ct_gate_posture={}",
461                self.simd_feature_enabled,
462                self.accelerated_backend_active,
463                self.unsafe_boundary_enforced,
464                self.security_posture,
465                self.wipe_posture,
466                self.ct_gate_posture,
467            )
468        }
469    }
470
471    impl BackendReport {
472        /// Returns whether this report satisfies `policy`.
473        ///
474        /// ```
475        /// let report = base64_ng::runtime::backend_report();
476        ///
477        /// assert!(
478        ///     report.satisfies(base64_ng::runtime::BackendPolicy::ScalarExecutionOnly)
479        /// );
480        /// ```
481        #[must_use]
482        pub const fn satisfies(self, policy: BackendPolicy) -> bool {
483            match policy {
484                BackendPolicy::ScalarExecutionOnly => {
485                    matches!(self.active, Backend::Scalar) && !self.accelerated_backend_active
486                }
487                BackendPolicy::SimdFeatureDisabled => !self.simd_feature_enabled,
488                BackendPolicy::NoDetectedSimdCandidate => matches!(self.candidate, Backend::Scalar),
489                BackendPolicy::HighAssuranceScalarOnly => {
490                    matches!(self.active, Backend::Scalar)
491                        && matches!(self.candidate, Backend::Scalar)
492                        && !self.simd_feature_enabled
493                        && !self.accelerated_backend_active
494                        && self.unsafe_boundary_enforced
495                }
496            }
497        }
498
499        /// Returns the CPU features required by the detected candidate.
500        ///
501        /// ```
502        /// let report = base64_ng::runtime::backend_report();
503        ///
504        /// assert_eq!(
505        ///     report.candidate_required_cpu_features(),
506        ///     report.candidate.required_cpu_features(),
507        /// );
508        /// ```
509        #[must_use]
510        pub const fn candidate_required_cpu_features(self) -> &'static [&'static str] {
511            self.candidate.required_cpu_features()
512        }
513
514        /// Returns a compact structured snapshot with stable string values.
515        ///
516        /// ```
517        /// let snapshot = base64_ng::runtime::backend_report().snapshot();
518        ///
519        /// assert_eq!(snapshot.active, "scalar");
520        /// assert!(!snapshot.accelerated_backend_active);
521        /// ```
522        #[must_use]
523        pub const fn snapshot(self) -> BackendSnapshot {
524            BackendSnapshot {
525                active: self.active.as_str(),
526                candidate: self.candidate.as_str(),
527                candidate_detection_mode: self.candidate_detection_mode.as_str(),
528                candidate_required_cpu_features: self.candidate_required_cpu_features(),
529                simd_feature_enabled: self.simd_feature_enabled,
530                accelerated_backend_active: self.accelerated_backend_active,
531                unsafe_boundary_enforced: self.unsafe_boundary_enforced,
532                security_posture: self.security_posture.as_str(),
533                wipe_posture: self.wipe_posture.as_str(),
534                ct_gate_posture: self.ct_gate_posture.as_str(),
535            }
536        }
537    }
538
539    /// Returns the runtime backend report for this build and target.
540    ///
541    /// ```
542    /// let report = base64_ng::runtime::backend_report();
543    ///
544    /// assert_eq!(report.active, base64_ng::runtime::Backend::Scalar);
545    /// assert!(!report.accelerated_backend_active);
546    /// ```
547    #[must_use]
548    pub fn backend_report() -> BackendReport {
549        let active = active_backend();
550        let candidate = detected_candidate();
551        let candidate_detection_mode = candidate_detection_mode();
552        let accelerated_backend_active = active != Backend::Scalar;
553        let unsafe_boundary_enforced = !cfg!(feature = "simd");
554        let security_posture = if accelerated_backend_active {
555            SecurityPosture::Accelerated
556        } else if candidate != Backend::Scalar {
557            SecurityPosture::SimdCandidateScalarActive
558        } else {
559            SecurityPosture::ScalarOnly
560        };
561
562        BackendReport {
563            active,
564            candidate,
565            candidate_detection_mode,
566            simd_feature_enabled: cfg!(feature = "simd"),
567            accelerated_backend_active,
568            unsafe_boundary_enforced,
569            security_posture,
570            wipe_posture: wipe_posture(),
571            ct_gate_posture: ct_gate_posture(),
572        }
573    }
574
575    const fn wipe_posture() -> WipePosture {
576        if cfg!(any(
577            target_arch = "aarch64",
578            target_arch = "arm",
579            target_arch = "riscv32",
580            target_arch = "riscv64",
581            target_arch = "x86",
582            target_arch = "x86_64",
583        )) {
584            WipePosture::HardwareFence
585        } else {
586            WipePosture::CompilerFenceOnly
587        }
588    }
589
590    const fn ct_gate_posture() -> CtGatePosture {
591        if cfg!(any(
592            target_arch = "aarch64",
593            target_arch = "x86",
594            target_arch = "x86_64"
595        )) {
596            CtGatePosture::HardwareSpeculationBarrier
597        } else if cfg!(any(
598            target_arch = "arm",
599            target_arch = "riscv32",
600            target_arch = "riscv64"
601        )) {
602            CtGatePosture::OrderingFence
603        } else {
604            CtGatePosture::CompilerFenceOnly
605        }
606    }
607
608    /// Requires the current runtime backend report to satisfy `policy`.
609    ///
610    /// ```
611    /// base64_ng::runtime::require_backend_policy(
612    ///     base64_ng::runtime::BackendPolicy::ScalarExecutionOnly,
613    /// )
614    /// .unwrap();
615    /// ```
616    pub fn require_backend_policy(policy: BackendPolicy) -> Result<(), BackendPolicyError> {
617        let report = backend_report();
618        if report.satisfies(policy) {
619            Ok(())
620        } else {
621            Err(BackendPolicyError { policy, report })
622        }
623    }
624
625    fn write_feature_list(
626        formatter: &mut core::fmt::Formatter<'_>,
627        features: &[&str],
628    ) -> core::fmt::Result {
629        formatter.write_str("[")?;
630        let mut index = 0;
631        while index < features.len() {
632            if index != 0 {
633                formatter.write_str(",")?;
634            }
635            formatter.write_str(features[index])?;
636            index += 1;
637        }
638        formatter.write_str("]")
639    }
640
641    #[cfg(feature = "simd")]
642    fn active_backend() -> Backend {
643        match super::simd::active_backend() {
644            super::simd::ActiveBackend::Scalar => Backend::Scalar,
645        }
646    }
647
648    #[cfg(not(feature = "simd"))]
649    const fn active_backend() -> Backend {
650        Backend::Scalar
651    }
652
653    #[cfg(feature = "simd")]
654    fn detected_candidate() -> Backend {
655        match super::simd::detected_candidate() {
656            super::simd::Candidate::Scalar => Backend::Scalar,
657            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
658            super::simd::Candidate::Avx512Vbmi => Backend::Avx512Vbmi,
659            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
660            super::simd::Candidate::Avx2 => Backend::Avx2,
661            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
662            super::simd::Candidate::Ssse3Sse41 => Backend::Ssse3Sse41,
663            #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
664            super::simd::Candidate::Neon => Backend::Neon,
665            #[cfg(target_arch = "wasm32")]
666            super::simd::Candidate::WasmSimd128 => Backend::WasmSimd128,
667        }
668    }
669
670    #[cfg(not(feature = "simd"))]
671    const fn detected_candidate() -> Backend {
672        Backend::Scalar
673    }
674
675    #[cfg(all(
676        feature = "simd",
677        feature = "std",
678        any(target_arch = "x86", target_arch = "x86_64")
679    ))]
680    const fn candidate_detection_mode() -> CandidateDetectionMode {
681        CandidateDetectionMode::RuntimeCpuFeatures
682    }
683
684    #[cfg(all(
685        feature = "simd",
686        not(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))
687    ))]
688    const fn candidate_detection_mode() -> CandidateDetectionMode {
689        CandidateDetectionMode::CompileTimeTargetFeatures
690    }
691
692    #[cfg(not(feature = "simd"))]
693    const fn candidate_detection_mode() -> CandidateDetectionMode {
694        CandidateDetectionMode::SimdFeatureDisabled
695    }
696}
697
698#[cfg(feature = "stream")]
699pub mod stream {
700    //! Streaming Base64 wrappers for `std::io`.
701    //!
702    //! Decoder adapters fail closed after malformed Base64 input. Use
703    //! `is_failed()` for diagnostics; unchecked `into_inner()` remains
704    //! available when the wrapped reader or writer must be explicitly
705    //! recovered after a decode error.
706    //!
707    //! ```
708    //! use std::io::{Read, Write};
709    //! use base64_ng::{STANDARD, stream::{Decoder, DecoderReader, Encoder, EncoderReader}};
710    //!
711    //! let mut encoder = Encoder::new(Vec::new(), STANDARD);
712    //! encoder.write_all(b"he").unwrap();
713    //! encoder.write_all(b"llo").unwrap();
714    //! let encoded = encoder.finish().unwrap();
715    //! assert_eq!(encoded, b"aGVsbG8=");
716    //!
717    //! let mut reader = EncoderReader::new(&b"hello"[..], STANDARD);
718    //! let mut encoded = String::new();
719    //! reader.read_to_string(&mut encoded).unwrap();
720    //! assert_eq!(encoded, "aGVsbG8=");
721    //!
722    //! let mut decoder = Decoder::new(Vec::new(), STANDARD);
723    //! decoder.write_all(b"aGVs").unwrap();
724    //! decoder.write_all(b"bG8=").unwrap();
725    //! let decoded = decoder.finish().unwrap();
726    //! assert_eq!(decoded, b"hello");
727    //!
728    //! let mut reader = DecoderReader::new(&b"aGVsbG8="[..], STANDARD);
729    //! let mut decoded = Vec::new();
730    //! reader.read_to_end(&mut decoded).unwrap();
731    //! assert_eq!(decoded, b"hello");
732    //! ```
733
734    use super::{Alphabet, DecodeError, EncodeError, Engine};
735    use std::io::{self, Read, Write};
736
737    struct OutputQueue<const CAP: usize> {
738        buffer: [u8; CAP],
739        start: usize,
740        len: usize,
741    }
742
743    impl<const CAP: usize> OutputQueue<CAP> {
744        const fn new() -> Self {
745            Self {
746                buffer: [0; CAP],
747                start: 0,
748                len: 0,
749            }
750        }
751
752        const fn is_empty(&self) -> bool {
753            self.len == 0
754        }
755
756        const fn len(&self) -> usize {
757            self.len
758        }
759
760        const fn capacity(&self) -> usize {
761            self.len + self.available_capacity()
762        }
763
764        fn push_slice(&mut self, input: &[u8]) -> io::Result<()> {
765            if input.len() > self.available_capacity() {
766                return Err(io::Error::other(
767                    "base64 stream output queue capacity exceeded",
768                ));
769            }
770
771            let mut read = 0;
772            while read < input.len() {
773                let write = (self.start + self.len) % CAP;
774                self.buffer[write] = input[read];
775                self.len += 1;
776                read += 1;
777            }
778
779            Ok(())
780        }
781
782        fn copy_front(&self, output: &mut [u8]) -> usize {
783            let count = core::cmp::min(self.len, output.len());
784            let first = core::cmp::min(count, CAP - self.start);
785            output[..first].copy_from_slice(&self.buffer[self.start..self.start + first]);
786
787            let second = count - first;
788            if second > 0 {
789                output[first..first + second].copy_from_slice(&self.buffer[..second]);
790            }
791
792            count
793        }
794
795        fn discard_front(&mut self, count: usize) {
796            let count = core::cmp::min(count, self.len);
797            let first = core::cmp::min(count, CAP - self.start);
798            crate::wipe_bytes(&mut self.buffer[self.start..self.start + first]);
799
800            let second = count - first;
801            if second > 0 {
802                crate::wipe_bytes(&mut self.buffer[..second]);
803            }
804
805            self.start = (self.start + count) % CAP;
806            self.len -= count;
807            if self.len == 0 {
808                self.start = 0;
809            }
810        }
811
812        fn pop_slice(&mut self, output: &mut [u8]) -> usize {
813            let count = self.copy_front(output);
814            self.discard_front(count);
815            count
816        }
817
818        fn clear_all(&mut self) {
819            crate::wipe_bytes(&mut self.buffer);
820            self.start = 0;
821            self.len = 0;
822        }
823
824        const fn available_capacity(&self) -> usize {
825            CAP - self.len
826        }
827    }
828
829    /// A streaming Base64 encoder for `std::io::Write`.
830    ///
831    /// Like any [`Write`] implementation, [`Write::write`] may accept only
832    /// part of the provided input. Accepted input may be held as encoded
833    /// output until [`Write::flush`], [`Self::try_finish`], [`Self::finish`],
834    /// or a later write drains the wrapped writer. Use [`Write::write_all`]
835    /// when the whole input slice must be consumed.
836    pub struct Encoder<W, A, const PAD: bool>
837    where
838        A: Alphabet,
839    {
840        inner: Option<W>,
841        engine: Engine<A, PAD>,
842        pending: [u8; 2],
843        pending_len: usize,
844        output: OutputQueue<1024>,
845        finalized: bool,
846    }
847
848    impl<W, A, const PAD: bool> Encoder<W, A, PAD>
849    where
850        A: Alphabet,
851    {
852        /// Creates a new streaming encoder.
853        #[must_use]
854        pub const fn new(inner: W, engine: Engine<A, PAD>) -> Self {
855            Self {
856                inner: Some(inner),
857                engine,
858                pending: [0; 2],
859                pending_len: 0,
860                output: OutputQueue::new(),
861                finalized: false,
862            }
863        }
864
865        /// Returns a shared reference to the wrapped writer.
866        #[must_use]
867        pub fn get_ref(&self) -> &W {
868            self.inner_ref()
869        }
870
871        /// Returns a mutable reference to the wrapped writer.
872        pub fn get_mut(&mut self) -> &mut W {
873            self.inner_mut()
874        }
875
876        /// Returns the Base64 engine used by this adapter.
877        #[must_use]
878        pub const fn engine(&self) -> Engine<A, PAD> {
879            self.engine
880        }
881
882        /// Returns whether this adapter uses padded Base64.
883        #[must_use]
884        pub const fn is_padded(&self) -> bool {
885            PAD
886        }
887
888        /// Returns the number of raw input bytes currently buffered until a
889        /// complete 3-byte Base64 encode quantum is available.
890        #[must_use]
891        pub const fn pending_len(&self) -> usize {
892            self.pending_len
893        }
894
895        /// Returns whether this encoder currently holds a partial input
896        /// quantum.
897        #[must_use]
898        pub const fn has_pending_input(&self) -> bool {
899            self.pending_len != 0
900        }
901
902        /// Returns how many additional input bytes are needed to complete the
903        /// currently buffered encode quantum.
904        ///
905        /// Returns `0` when no partial input quantum is buffered.
906        #[must_use]
907        pub const fn pending_input_needed_len(&self) -> usize {
908            if self.has_pending_input() {
909                3 - self.pending_len
910            } else {
911                0
912            }
913        }
914
915        /// Returns the number of encoded bytes buffered for the wrapped
916        /// writer after a previous write or flush could not fully drain them.
917        #[must_use]
918        pub const fn buffered_output_len(&self) -> usize {
919            self.output.len()
920        }
921
922        /// Returns the maximum number of encoded bytes this adapter can buffer
923        /// before returning bytes to the caller.
924        #[must_use]
925        pub const fn buffered_output_capacity(&self) -> usize {
926            self.output.capacity()
927        }
928
929        /// Returns how many more encoded bytes can be buffered before this
930        /// adapter must drain the wrapped writer.
931        #[must_use]
932        pub const fn buffered_output_remaining_capacity(&self) -> usize {
933            self.output.available_capacity()
934        }
935
936        /// Returns whether this encoder has encoded output waiting to be
937        /// written to the wrapped writer.
938        #[must_use]
939        pub const fn has_buffered_output(&self) -> bool {
940            !self.output.is_empty()
941        }
942
943        /// Returns whether this encoder has been finalized.
944        ///
945        /// Once this returns `true`, later non-empty writes return an error.
946        #[must_use]
947        pub const fn is_finalized(&self) -> bool {
948            self.finalized
949        }
950
951        /// Returns whether [`Self::try_into_inner`] can recover the wrapped
952        /// writer without discarding pending input.
953        #[must_use]
954        pub const fn can_into_inner(&self) -> bool {
955            !self.has_pending_input() && !self.has_buffered_output()
956        }
957
958        /// Consumes the encoder without flushing pending input.
959        ///
960        /// Prefer [`Self::finish`] when the encoded output must be complete.
961        #[must_use]
962        pub fn into_inner(mut self) -> W {
963            self.take_inner()
964        }
965
966        /// Consumes the encoder only when no partial input quantum is buffered.
967        ///
968        /// This does not flush or finalize the wrapped writer. It is a checked
969        /// alternative to [`Self::into_inner`] for callers that want to avoid
970        /// accidentally discarding pending input bytes.
971        #[allow(clippy::result_large_err)]
972        pub fn try_into_inner(mut self) -> Result<W, Self> {
973            if !self.can_into_inner() {
974                return Err(self);
975            }
976            Ok(self.take_inner())
977        }
978
979        fn inner_ref(&self) -> &W {
980            match &self.inner {
981                Some(inner) => inner,
982                None => unreachable!("stream encoder inner writer was already taken"),
983            }
984        }
985
986        fn inner_mut(&mut self) -> &mut W {
987            match &mut self.inner {
988                Some(inner) => inner,
989                None => unreachable!("stream encoder inner writer was already taken"),
990            }
991        }
992
993        fn take_inner(&mut self) -> W {
994            match self.inner.take() {
995                Some(inner) => inner,
996                None => unreachable!("stream encoder inner writer was already taken"),
997            }
998        }
999
1000        fn clear_pending(&mut self) {
1001            crate::wipe_bytes(&mut self.pending);
1002            self.pending_len = 0;
1003        }
1004
1005        fn clear_output(&mut self) {
1006            self.output.clear_all();
1007        }
1008    }
1009
1010    impl<W, A, const PAD: bool> Drop for Encoder<W, A, PAD>
1011    where
1012        A: Alphabet,
1013    {
1014        fn drop(&mut self) {
1015            self.clear_pending();
1016            self.clear_output();
1017        }
1018    }
1019
1020    impl<W, A, const PAD: bool> core::fmt::Debug for Encoder<W, A, PAD>
1021    where
1022        A: Alphabet,
1023    {
1024        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1025            formatter
1026                .debug_struct("Encoder")
1027                .field("inner", &redacted_inner_state(self.inner.is_some()))
1028                .field("engine", &self.engine)
1029                .field("pending", &"<redacted>")
1030                .field("pending_len", &self.pending_len)
1031                .field("pending_input_needed_len", &self.pending_input_needed_len())
1032                .field("buffered_output_len", &self.output.len())
1033                .field("buffered_output_capacity", &self.output.capacity())
1034                .field(
1035                    "buffered_output_remaining_capacity",
1036                    &self.output.available_capacity(),
1037                )
1038                .field("can_into_inner", &self.can_into_inner())
1039                .field("finalized", &self.finalized)
1040                .finish()
1041        }
1042    }
1043
1044    impl<W, A, const PAD: bool> Encoder<W, A, PAD>
1045    where
1046        W: Write,
1047        A: Alphabet,
1048    {
1049        /// Writes any pending input and flushes the wrapped writer without
1050        /// consuming this encoder.
1051        ///
1052        /// After this succeeds, [`Self::pending_len`] returns `0`, later
1053        /// writes are rejected, and [`Self::finish`] can still be used to
1054        /// recover the wrapped writer.
1055        /// This is useful when a caller needs to finalize a framed payload
1056        /// while keeping the stream adapter available for diagnostics or
1057        /// explicit recovery.
1058        pub fn try_finish(&mut self) -> io::Result<()> {
1059            if !self.finalized {
1060                self.queue_pending_final()?;
1061                self.finalized = true;
1062            }
1063            self.flush()
1064        }
1065
1066        /// Writes any pending input, flushes the wrapped writer, and returns it.
1067        pub fn finish(mut self) -> io::Result<W> {
1068            self.try_finish()?;
1069            Ok(self.take_inner())
1070        }
1071
1072        fn queue_pending_final(&mut self) -> io::Result<()> {
1073            if self.pending_len == 0 {
1074                return Ok(());
1075            }
1076
1077            let mut pending = [0u8; 2];
1078            pending[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
1079            let pending_len = self.pending_len;
1080            let mut encoded = [0u8; 4];
1081            let result = self.queue_encoded_temp(&pending[..pending_len], &mut encoded);
1082            crate::wipe_bytes(&mut pending);
1083            result?;
1084            self.clear_pending();
1085            Ok(())
1086        }
1087
1088        fn queue_encoded_temp(&mut self, input: &[u8], encoded: &mut [u8]) -> io::Result<()> {
1089            let written = match self.engine.encode_slice(input, encoded) {
1090                Ok(written) => written,
1091                Err(err) => {
1092                    crate::wipe_bytes(encoded);
1093                    return Err(encode_error_to_io(err));
1094                }
1095            };
1096
1097            let result = self.output.push_slice(&encoded[..written]);
1098            crate::wipe_bytes(encoded);
1099            result
1100        }
1101
1102        fn drain_output(&mut self) -> io::Result<()> {
1103            let mut chunk = [0u8; 1024];
1104            while !self.output.is_empty() {
1105                let pending = self.output.copy_front(&mut chunk);
1106                let result = self.inner_mut().write(&chunk[..pending]);
1107                crate::wipe_bytes(&mut chunk[..pending]);
1108                match result {
1109                    Ok(0) => {
1110                        return Err(io::Error::new(
1111                            io::ErrorKind::WriteZero,
1112                            "base64 stream encoder could not drain buffered output",
1113                        ));
1114                    }
1115                    Ok(written) => {
1116                        if written > pending {
1117                            return Err(io::Error::new(
1118                                io::ErrorKind::InvalidData,
1119                                "wrapped writer reported more bytes than provided",
1120                            ));
1121                        }
1122                        self.output.discard_front(written);
1123                    }
1124                    Err(err) => return Err(err),
1125                }
1126            }
1127
1128            Ok(())
1129        }
1130    }
1131
1132    impl<W, A, const PAD: bool> Write for Encoder<W, A, PAD>
1133    where
1134        W: Write,
1135        A: Alphabet,
1136    {
1137        fn write(&mut self, input: &[u8]) -> io::Result<usize> {
1138            if input.is_empty() {
1139                self.drain_output()?;
1140                return Ok(0);
1141            }
1142            self.drain_output()?;
1143            if self.finalized {
1144                return Err(io::Error::new(
1145                    io::ErrorKind::InvalidInput,
1146                    "base64 stream encoder received input after finalization",
1147                ));
1148            }
1149
1150            let mut consumed = 0;
1151            if self.pending_len > 0 {
1152                let needed = 3 - self.pending_len;
1153                if input.len() < needed {
1154                    self.pending[self.pending_len..self.pending_len + input.len()]
1155                        .copy_from_slice(input);
1156                    self.pending_len += input.len();
1157                    return Ok(input.len());
1158                }
1159
1160                let mut chunk = [0u8; 3];
1161                chunk[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
1162                chunk[self.pending_len..].copy_from_slice(&input[..needed]);
1163
1164                let mut encoded = [0u8; 4];
1165                let result = self.queue_encoded_temp(&chunk, &mut encoded);
1166                crate::wipe_bytes(&mut chunk);
1167                result?;
1168                self.clear_pending();
1169                consumed += needed;
1170            }
1171
1172            let remaining = &input[consumed..];
1173            let full_len = remaining.len() / 3 * 3;
1174            if full_len > 0 {
1175                let max_by_queue = self.output.available_capacity() / 4 * 3;
1176                let mut take = core::cmp::min(full_len, core::cmp::min(768, max_by_queue));
1177                take -= take % 3;
1178
1179                if take == 0 {
1180                    return Ok(consumed);
1181                }
1182
1183                let mut encoded = [0u8; 1024];
1184                self.queue_encoded_temp(&remaining[..take], &mut encoded)?;
1185                consumed += take;
1186
1187                if take < full_len {
1188                    return Ok(consumed);
1189                }
1190            }
1191
1192            let tail = &input[consumed..];
1193            self.pending[..tail.len()].copy_from_slice(tail);
1194            self.pending_len = tail.len();
1195            consumed += tail.len();
1196
1197            Ok(consumed)
1198        }
1199
1200        fn flush(&mut self) -> io::Result<()> {
1201            self.drain_output()?;
1202            self.inner_mut().flush()
1203        }
1204    }
1205
1206    fn encode_error_to_io(err: EncodeError) -> io::Error {
1207        io::Error::new(io::ErrorKind::InvalidInput, err)
1208    }
1209
1210    /// A streaming Base64 decoder for `std::io::Write`.
1211    ///
1212    /// Like any [`Write`] implementation, [`Write::write`] may accept only
1213    /// part of the provided input. Accepted input may be held as decoded
1214    /// output until [`Write::flush`], [`Self::try_finish`], [`Self::finish`],
1215    /// or a later write drains the wrapped writer. Use [`Write::write_all`]
1216    /// when the whole input slice must be consumed.
1217    ///
1218    /// # Security
1219    ///
1220    /// This adapter uses the normal strict decoder, not the [`crate::ct`]
1221    /// module. It may branch or return early based on malformed input and it
1222    /// preserves strict error diagnostics. Do not use it for secret-bearing
1223    /// payloads when malformed-input timing matters; decode a complete frame
1224    /// with the matching `ct` engine instead.
1225    pub struct Decoder<W, A, const PAD: bool>
1226    where
1227        A: Alphabet,
1228    {
1229        inner: Option<W>,
1230        engine: Engine<A, PAD>,
1231        pending: [u8; 4],
1232        pending_len: usize,
1233        output: OutputQueue<1024>,
1234        finished: bool,
1235        failed: bool,
1236        finalized: bool,
1237    }
1238
1239    impl<W, A, const PAD: bool> Decoder<W, A, PAD>
1240    where
1241        A: Alphabet,
1242    {
1243        /// Creates a new streaming decoder.
1244        ///
1245        /// # Security
1246        ///
1247        /// Streaming decoders use the normal strict decode path. They are not
1248        /// constant-time-oriented secret decoders.
1249        #[must_use]
1250        pub const fn new(inner: W, engine: Engine<A, PAD>) -> Self {
1251            Self {
1252                inner: Some(inner),
1253                engine,
1254                pending: [0; 4],
1255                pending_len: 0,
1256                output: OutputQueue::new(),
1257                finished: false,
1258                finalized: false,
1259                failed: false,
1260            }
1261        }
1262
1263        /// Returns a shared reference to the wrapped writer.
1264        #[must_use]
1265        pub fn get_ref(&self) -> &W {
1266            self.inner_ref()
1267        }
1268
1269        /// Returns a mutable reference to the wrapped writer.
1270        pub fn get_mut(&mut self) -> &mut W {
1271            self.inner_mut()
1272        }
1273
1274        /// Returns the Base64 engine used by this adapter.
1275        #[must_use]
1276        pub const fn engine(&self) -> Engine<A, PAD> {
1277            self.engine
1278        }
1279
1280        /// Returns whether this adapter uses padded Base64.
1281        #[must_use]
1282        pub const fn is_padded(&self) -> bool {
1283            PAD
1284        }
1285
1286        /// Returns the number of encoded input bytes currently buffered until
1287        /// a complete 4-byte Base64 decode quantum is available.
1288        #[must_use]
1289        pub const fn pending_len(&self) -> usize {
1290            self.pending_len
1291        }
1292
1293        /// Returns whether this decoder currently holds a partial input
1294        /// quantum.
1295        #[must_use]
1296        pub const fn has_pending_input(&self) -> bool {
1297            self.pending_len != 0
1298        }
1299
1300        /// Returns how many additional input bytes are needed to complete the
1301        /// currently buffered decode quantum.
1302        ///
1303        /// Returns `0` when no partial input quantum is buffered.
1304        #[must_use]
1305        pub const fn pending_input_needed_len(&self) -> usize {
1306            if self.has_pending_input() {
1307                4 - self.pending_len
1308            } else {
1309                0
1310            }
1311        }
1312
1313        /// Returns the number of decoded bytes buffered for the wrapped writer
1314        /// after a previous write or flush could not fully drain them.
1315        #[must_use]
1316        pub const fn buffered_output_len(&self) -> usize {
1317            self.output.len()
1318        }
1319
1320        /// Returns the maximum number of decoded bytes this adapter can buffer
1321        /// before returning bytes to the caller.
1322        #[must_use]
1323        pub const fn buffered_output_capacity(&self) -> usize {
1324            self.output.capacity()
1325        }
1326
1327        /// Returns how many more decoded bytes can be buffered before this
1328        /// adapter must drain the wrapped writer.
1329        #[must_use]
1330        pub const fn buffered_output_remaining_capacity(&self) -> usize {
1331            self.output.available_capacity()
1332        }
1333
1334        /// Returns whether this decoder has decoded output waiting to be
1335        /// written to the wrapped writer.
1336        #[must_use]
1337        pub const fn has_buffered_output(&self) -> bool {
1338            !self.output.is_empty()
1339        }
1340
1341        /// Returns whether this decoder has processed a terminal padded block.
1342        ///
1343        /// Once this returns `true`, later calls to [`Write::write`] with
1344        /// additional input return an error because strict Base64 does not
1345        /// permit trailing payload bytes after padding.
1346        #[must_use]
1347        pub const fn has_terminal_padding(&self) -> bool {
1348            self.finished
1349        }
1350
1351        /// Returns whether this decoder has been finalized.
1352        ///
1353        /// Once this returns `true`, later non-empty writes return an error.
1354        #[must_use]
1355        pub const fn is_finalized(&self) -> bool {
1356            self.finalized
1357        }
1358
1359        /// Returns whether this decoder has rejected malformed Base64 input.
1360        ///
1361        /// Once this returns `true`, later writes, flushes, and finalization
1362        /// attempts return an error. The unchecked [`Self::into_inner`] method
1363        /// can still be used for explicit recovery of the wrapped writer.
1364        #[must_use]
1365        pub const fn is_failed(&self) -> bool {
1366            self.failed
1367        }
1368
1369        /// Returns whether [`Self::try_into_inner`] can recover the wrapped
1370        /// writer without discarding pending encoded input.
1371        #[must_use]
1372        pub const fn can_into_inner(&self) -> bool {
1373            !self.is_failed() && !self.has_pending_input() && !self.has_buffered_output()
1374        }
1375
1376        /// Consumes the decoder without flushing pending input.
1377        ///
1378        /// Prefer [`Self::finish`] when the decoded output must be complete.
1379        #[must_use]
1380        pub fn into_inner(mut self) -> W {
1381            self.take_inner()
1382        }
1383
1384        /// Consumes the decoder only when no partial input quantum is buffered.
1385        ///
1386        /// This does not flush or finalize the wrapped writer. It is a checked
1387        /// alternative to [`Self::into_inner`] for callers that want to avoid
1388        /// accidentally discarding pending encoded input bytes.
1389        #[allow(clippy::result_large_err)]
1390        pub fn try_into_inner(mut self) -> Result<W, Self> {
1391            if !self.can_into_inner() {
1392                return Err(self);
1393            }
1394            Ok(self.take_inner())
1395        }
1396
1397        fn inner_ref(&self) -> &W {
1398            match &self.inner {
1399                Some(inner) => inner,
1400                None => unreachable!("stream decoder inner writer was already taken"),
1401            }
1402        }
1403
1404        fn inner_mut(&mut self) -> &mut W {
1405            match &mut self.inner {
1406                Some(inner) => inner,
1407                None => unreachable!("stream decoder inner writer was already taken"),
1408            }
1409        }
1410
1411        fn take_inner(&mut self) -> W {
1412            match self.inner.take() {
1413                Some(inner) => inner,
1414                None => unreachable!("stream decoder inner writer was already taken"),
1415            }
1416        }
1417
1418        fn clear_pending(&mut self) {
1419            crate::wipe_bytes(&mut self.pending);
1420            self.pending_len = 0;
1421        }
1422
1423        fn clear_output(&mut self) {
1424            self.output.clear_all();
1425        }
1426    }
1427
1428    impl<W, A, const PAD: bool> Drop for Decoder<W, A, PAD>
1429    where
1430        A: Alphabet,
1431    {
1432        fn drop(&mut self) {
1433            self.clear_pending();
1434            self.clear_output();
1435        }
1436    }
1437
1438    impl<W, A, const PAD: bool> core::fmt::Debug for Decoder<W, A, PAD>
1439    where
1440        A: Alphabet,
1441    {
1442        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1443            formatter
1444                .debug_struct("Decoder")
1445                .field("inner", &redacted_inner_state(self.inner.is_some()))
1446                .field("engine", &self.engine)
1447                .field("pending", &"<redacted>")
1448                .field("pending_len", &self.pending_len)
1449                .field("pending_input_needed_len", &self.pending_input_needed_len())
1450                .field("buffered_output_len", &self.output.len())
1451                .field("buffered_output_capacity", &self.output.capacity())
1452                .field(
1453                    "buffered_output_remaining_capacity",
1454                    &self.output.available_capacity(),
1455                )
1456                .field("can_into_inner", &self.can_into_inner())
1457                .field("terminal_padding", &self.finished)
1458                .field("finalized", &self.finalized)
1459                .field("failed", &self.failed)
1460                .finish()
1461        }
1462    }
1463
1464    impl<W, A, const PAD: bool> Decoder<W, A, PAD>
1465    where
1466        W: Write,
1467        A: Alphabet,
1468    {
1469        /// Validates any final pending input and flushes the wrapped writer
1470        /// without consuming this decoder.
1471        ///
1472        /// After this succeeds, [`Self::pending_len`] returns `0`, later
1473        /// writes are rejected, and [`Self::finish`] can still be used to
1474        /// recover the wrapped writer.
1475        /// If the final buffered input is malformed, an error is returned and
1476        /// the caller still owns the decoder for diagnostics or explicit
1477        /// recovery.
1478        pub fn try_finish(&mut self) -> io::Result<()> {
1479            if self.failed {
1480                return Err(stream_decoder_failed_error());
1481            }
1482            if !self.finalized {
1483                self.queue_pending_final()?;
1484                self.finalized = true;
1485            }
1486            self.flush()
1487        }
1488
1489        /// Validates final pending input, flushes the wrapped writer, and returns it.
1490        pub fn finish(mut self) -> io::Result<W> {
1491            self.try_finish()?;
1492            Ok(self.take_inner())
1493        }
1494
1495        fn queue_pending_final(&mut self) -> io::Result<()> {
1496            if self.pending_len == 0 {
1497                return Ok(());
1498            }
1499
1500            let mut pending = [0u8; 4];
1501            pending[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
1502            let pending_len = self.pending_len;
1503            let mut decoded = [0u8; 3];
1504            let result = self.queue_decoded_temp(&pending[..pending_len], &mut decoded);
1505            crate::wipe_bytes(&mut pending);
1506            if let Err(err) = result {
1507                self.clear_pending();
1508                return Err(err);
1509            }
1510            self.clear_pending();
1511            Ok(())
1512        }
1513
1514        fn queue_full_quad(&mut self, mut input: [u8; 4]) -> io::Result<()> {
1515            let mut decoded = [0u8; 3];
1516            let result = self.queue_decoded_temp(&input, &mut decoded);
1517            crate::wipe_bytes(&mut input);
1518            let written = result?;
1519            if written < 3 {
1520                self.finished = true;
1521            }
1522            Ok(())
1523        }
1524
1525        fn queue_decoded_temp(&mut self, input: &[u8], decoded: &mut [u8]) -> io::Result<usize> {
1526            let written = match self.engine.decode_slice(input, decoded) {
1527                Ok(written) => written,
1528                Err(err) => {
1529                    crate::wipe_bytes(decoded);
1530                    self.failed = true;
1531                    return Err(decode_error_to_io(err));
1532                }
1533            };
1534
1535            let result = self.output.push_slice(&decoded[..written]);
1536            crate::wipe_bytes(decoded);
1537            result?;
1538            Ok(written)
1539        }
1540
1541        fn drain_output(&mut self) -> io::Result<()> {
1542            let mut chunk = [0u8; 1024];
1543            while !self.output.is_empty() {
1544                let pending = self.output.copy_front(&mut chunk);
1545                let result = self.inner_mut().write(&chunk[..pending]);
1546                crate::wipe_bytes(&mut chunk[..pending]);
1547                match result {
1548                    Ok(0) => {
1549                        return Err(io::Error::new(
1550                            io::ErrorKind::WriteZero,
1551                            "base64 stream decoder could not drain buffered output",
1552                        ));
1553                    }
1554                    Ok(written) => {
1555                        if written > pending {
1556                            return Err(io::Error::new(
1557                                io::ErrorKind::InvalidData,
1558                                "wrapped writer reported more bytes than provided",
1559                            ));
1560                        }
1561                        self.output.discard_front(written);
1562                    }
1563                    Err(err) => return Err(err),
1564                }
1565            }
1566
1567            Ok(())
1568        }
1569    }
1570
1571    impl<W, A, const PAD: bool> Write for Decoder<W, A, PAD>
1572    where
1573        W: Write,
1574        A: Alphabet,
1575    {
1576        fn write(&mut self, input: &[u8]) -> io::Result<usize> {
1577            if self.failed {
1578                return Err(stream_decoder_failed_error());
1579            }
1580            if input.is_empty() {
1581                self.drain_output()?;
1582                return Ok(0);
1583            }
1584            self.drain_output()?;
1585            if self.finalized {
1586                return Err(io::Error::new(
1587                    io::ErrorKind::InvalidInput,
1588                    "base64 stream decoder received input after finalization",
1589                ));
1590            }
1591            if self.finished {
1592                self.failed = true;
1593                return Err(trailing_input_after_padding_error());
1594            }
1595
1596            let mut consumed = 0;
1597            if self.pending_len > 0 {
1598                let needed = 4 - self.pending_len;
1599                if input.len() < needed {
1600                    self.pending[self.pending_len..self.pending_len + input.len()]
1601                        .copy_from_slice(input);
1602                    self.pending_len += input.len();
1603                    return Ok(input.len());
1604                }
1605
1606                let mut quad = [0u8; 4];
1607                quad[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
1608                quad[self.pending_len..].copy_from_slice(&input[..needed]);
1609                let result = self.queue_full_quad(quad);
1610                crate::wipe_bytes(&mut quad);
1611                if let Err(err) = result {
1612                    self.clear_pending();
1613                    return Err(err);
1614                }
1615                self.clear_pending();
1616                consumed += needed;
1617
1618                if self.finished {
1619                    return Ok(consumed);
1620                }
1621            }
1622
1623            while input.len() - consumed >= 4 {
1624                if self.output.available_capacity() < 3 {
1625                    return Ok(consumed);
1626                }
1627
1628                let mut quad = [
1629                    input[consumed],
1630                    input[consumed + 1],
1631                    input[consumed + 2],
1632                    input[consumed + 3],
1633                ];
1634                let mut decoded = [0u8; 3];
1635                let written = match self.engine.decode_slice(&quad, &mut decoded) {
1636                    Ok(written) => written,
1637                    Err(err) => {
1638                        crate::wipe_bytes(&mut quad);
1639                        crate::wipe_bytes(&mut decoded);
1640                        self.failed = true;
1641                        if consumed > 0 {
1642                            return Ok(consumed);
1643                        }
1644
1645                        return Err(decode_error_to_io(err));
1646                    }
1647                };
1648
1649                let result = self.output.push_slice(&decoded[..written]);
1650                crate::wipe_bytes(&mut quad);
1651                crate::wipe_bytes(&mut decoded);
1652                result?;
1653                consumed += 4;
1654
1655                if written < 3 {
1656                    self.finished = true;
1657                    return Ok(consumed);
1658                }
1659            }
1660
1661            let tail = &input[consumed..];
1662            self.pending[..tail.len()].copy_from_slice(tail);
1663            self.pending_len = tail.len();
1664            consumed += tail.len();
1665
1666            Ok(consumed)
1667        }
1668
1669        fn flush(&mut self) -> io::Result<()> {
1670            if self.failed {
1671                return Err(stream_decoder_failed_error());
1672            }
1673            self.drain_output()?;
1674            self.inner_mut().flush()
1675        }
1676    }
1677
1678    fn decode_error_to_io(err: DecodeError) -> io::Error {
1679        io::Error::new(io::ErrorKind::InvalidInput, err)
1680    }
1681
1682    fn trailing_input_after_padding_error() -> io::Error {
1683        io::Error::new(
1684            io::ErrorKind::InvalidInput,
1685            "base64 decoder received trailing input after padding",
1686        )
1687    }
1688
1689    fn stream_decoder_failed_error() -> io::Error {
1690        io::Error::new(
1691            io::ErrorKind::InvalidInput,
1692            "base64 stream decoder is failed after malformed input",
1693        )
1694    }
1695
1696    fn stream_encoder_failed_error() -> io::Error {
1697        io::Error::new(
1698            io::ErrorKind::InvalidInput,
1699            "base64 stream encoder is failed after internal error",
1700        )
1701    }
1702
1703    /// A streaming Base64 decoder for `std::io::Read`.
1704    ///
1705    /// For padded engines, this reader stops at the terminal padded Base64
1706    /// block and leaves later bytes unread in the wrapped reader. This preserves
1707    /// boundaries for callers that decode one Base64 payload from a larger
1708    /// stream.
1709    ///
1710    /// # Security
1711    ///
1712    /// This adapter uses the normal strict decoder, not the [`crate::ct`]
1713    /// module. It may branch or return early based on malformed input and it
1714    /// preserves strict error diagnostics. Do not use it for secret-bearing
1715    /// payloads when malformed-input timing matters; decode a complete frame
1716    /// with the matching `ct` engine instead.
1717    pub struct DecoderReader<R, A, const PAD: bool>
1718    where
1719        A: Alphabet,
1720    {
1721        inner: Option<R>,
1722        engine: Engine<A, PAD>,
1723        pending: [u8; 4],
1724        pending_len: usize,
1725        output: OutputQueue<3>,
1726        finished: bool,
1727        terminal_seen: bool,
1728        failed: bool,
1729    }
1730
1731    impl<R, A, const PAD: bool> DecoderReader<R, A, PAD>
1732    where
1733        A: Alphabet,
1734    {
1735        /// Creates a new streaming decoder reader.
1736        ///
1737        /// # Security
1738        ///
1739        /// Streaming decoder readers use the normal strict decode path. They
1740        /// are not constant-time-oriented secret decoders.
1741        #[must_use]
1742        pub fn new(inner: R, engine: Engine<A, PAD>) -> Self {
1743            Self {
1744                inner: Some(inner),
1745                engine,
1746                pending: [0; 4],
1747                pending_len: 0,
1748                output: OutputQueue::new(),
1749                finished: false,
1750                terminal_seen: false,
1751                failed: false,
1752            }
1753        }
1754
1755        /// Returns a shared reference to the wrapped reader.
1756        #[must_use]
1757        pub fn get_ref(&self) -> &R {
1758            self.inner_ref()
1759        }
1760
1761        /// Returns a mutable reference to the wrapped reader.
1762        pub fn get_mut(&mut self) -> &mut R {
1763            self.inner_mut()
1764        }
1765
1766        /// Returns the Base64 engine used by this adapter.
1767        #[must_use]
1768        pub const fn engine(&self) -> Engine<A, PAD> {
1769            self.engine
1770        }
1771
1772        /// Returns whether this adapter uses padded Base64.
1773        #[must_use]
1774        pub const fn is_padded(&self) -> bool {
1775            PAD
1776        }
1777
1778        /// Returns the number of encoded input bytes currently buffered until
1779        /// a complete 4-byte Base64 decode quantum is available.
1780        #[must_use]
1781        pub const fn pending_len(&self) -> usize {
1782            self.pending_len
1783        }
1784
1785        /// Returns whether this decoder reader currently holds a partial input
1786        /// quantum.
1787        #[must_use]
1788        pub const fn has_pending_input(&self) -> bool {
1789            self.pending_len != 0
1790        }
1791
1792        /// Returns how many additional encoded input bytes are needed to
1793        /// complete the currently buffered decode quantum.
1794        ///
1795        /// Returns `0` when no partial input quantum is buffered.
1796        #[must_use]
1797        pub const fn pending_input_needed_len(&self) -> usize {
1798            if self.has_pending_input() {
1799                4 - self.pending_len
1800            } else {
1801                0
1802            }
1803        }
1804
1805        /// Returns the number of decoded bytes currently buffered and ready to
1806        /// be read before this adapter polls the wrapped reader again.
1807        #[must_use]
1808        pub const fn buffered_output_len(&self) -> usize {
1809            self.output.len()
1810        }
1811
1812        /// Returns the maximum number of decoded bytes this adapter can buffer
1813        /// before returning bytes to the caller.
1814        #[must_use]
1815        pub const fn buffered_output_capacity(&self) -> usize {
1816            self.output.capacity()
1817        }
1818
1819        /// Returns how many more decoded bytes can be buffered before this
1820        /// adapter must return bytes to the caller.
1821        #[must_use]
1822        pub const fn buffered_output_remaining_capacity(&self) -> usize {
1823            self.output.available_capacity()
1824        }
1825
1826        /// Returns whether this decoder reader currently has decoded output
1827        /// waiting in its internal queue.
1828        #[must_use]
1829        pub const fn has_buffered_output(&self) -> bool {
1830            !self.output.is_empty()
1831        }
1832
1833        /// Returns whether this decoder reader has seen terminal padding.
1834        ///
1835        /// For padded engines, this becomes `true` after the terminal padded
1836        /// block is decoded. The wrapped reader is then left positioned after
1837        /// that Base64 block so adjacent framed bytes can be read by the
1838        /// caller.
1839        #[must_use]
1840        pub const fn has_terminal_padding(&self) -> bool {
1841            self.terminal_seen
1842        }
1843
1844        /// Returns whether this decoder reader has reached EOF or terminal
1845        /// padding in the wrapped reader.
1846        ///
1847        /// This may become `true` before [`Self::is_finished`] when decoded
1848        /// output is still buffered for the caller.
1849        #[must_use]
1850        pub const fn has_finished_input(&self) -> bool {
1851            self.finished
1852        }
1853
1854        /// Returns whether this reader has reached EOF or terminal padding
1855        /// and has no decoded output buffered for the caller.
1856        #[must_use]
1857        pub const fn is_finished(&self) -> bool {
1858            self.finished && self.output.is_empty()
1859        }
1860
1861        /// Returns whether this decoder reader has rejected malformed Base64
1862        /// input.
1863        ///
1864        /// Once this returns `true`, later reads return an error. The unchecked
1865        /// [`Self::into_inner`] method can still be used for explicit recovery
1866        /// of the wrapped reader.
1867        #[must_use]
1868        pub const fn is_failed(&self) -> bool {
1869            self.failed
1870        }
1871
1872        /// Returns whether [`Self::try_into_inner`] can recover the wrapped
1873        /// reader without discarding buffered decoded output.
1874        #[must_use]
1875        pub const fn can_into_inner(&self) -> bool {
1876            !self.is_failed() && self.is_finished()
1877        }
1878
1879        /// Consumes the decoder reader and returns the wrapped reader.
1880        #[must_use]
1881        pub fn into_inner(mut self) -> R {
1882            self.take_inner()
1883        }
1884
1885        /// Consumes the decoder reader only after the Base64 payload is fully
1886        /// drained.
1887        ///
1888        /// For padded streams, terminal padding may leave adjacent framed bytes
1889        /// unread in the wrapped reader. This method succeeds only after all
1890        /// decoded output buffered by this adapter has been read, so recovering
1891        /// the wrapped reader does not silently discard decoded bytes.
1892        #[allow(clippy::result_large_err)]
1893        pub fn try_into_inner(mut self) -> Result<R, Self> {
1894            if !self.can_into_inner() {
1895                return Err(self);
1896            }
1897            Ok(self.take_inner())
1898        }
1899
1900        fn inner_ref(&self) -> &R {
1901            match &self.inner {
1902                Some(inner) => inner,
1903                None => unreachable!("stream decoder reader inner reader was already taken"),
1904            }
1905        }
1906
1907        fn inner_mut(&mut self) -> &mut R {
1908            match &mut self.inner {
1909                Some(inner) => inner,
1910                None => unreachable!("stream decoder reader inner reader was already taken"),
1911            }
1912        }
1913
1914        fn take_inner(&mut self) -> R {
1915            match self.inner.take() {
1916                Some(inner) => inner,
1917                None => unreachable!("stream decoder reader inner reader was already taken"),
1918            }
1919        }
1920
1921        fn clear_pending(&mut self) {
1922            crate::wipe_bytes(&mut self.pending);
1923            self.pending_len = 0;
1924        }
1925    }
1926
1927    impl<R, A, const PAD: bool> Drop for DecoderReader<R, A, PAD>
1928    where
1929        A: Alphabet,
1930    {
1931        fn drop(&mut self) {
1932            self.clear_pending();
1933            self.output.clear_all();
1934        }
1935    }
1936
1937    impl<R, A, const PAD: bool> core::fmt::Debug for DecoderReader<R, A, PAD>
1938    where
1939        A: Alphabet,
1940    {
1941        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1942            formatter
1943                .debug_struct("DecoderReader")
1944                .field("inner", &redacted_inner_state(self.inner.is_some()))
1945                .field("engine", &self.engine)
1946                .field("pending", &"<redacted>")
1947                .field("pending_len", &self.pending_len)
1948                .field("pending_input_needed_len", &self.pending_input_needed_len())
1949                .field("buffered_output_len", &self.output.len())
1950                .field("buffered_output_capacity", &self.output.capacity())
1951                .field(
1952                    "buffered_output_remaining_capacity",
1953                    &self.output.available_capacity(),
1954                )
1955                .field("can_into_inner", &self.can_into_inner())
1956                .field("finished", &self.finished)
1957                .field("terminal_padding", &self.terminal_seen)
1958                .field("failed", &self.failed)
1959                .finish()
1960        }
1961    }
1962
1963    impl<R, A, const PAD: bool> Read for DecoderReader<R, A, PAD>
1964    where
1965        R: Read,
1966        A: Alphabet,
1967    {
1968        fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
1969            if output.is_empty() {
1970                return Ok(0);
1971            }
1972            if self.failed {
1973                return Err(stream_decoder_failed_error());
1974            }
1975
1976            while self.output.is_empty() && !self.finished {
1977                self.fill_output()?;
1978            }
1979
1980            Ok(self.output.pop_slice(output))
1981        }
1982    }
1983
1984    impl<R, A, const PAD: bool> DecoderReader<R, A, PAD>
1985    where
1986        R: Read,
1987        A: Alphabet,
1988    {
1989        fn fill_output(&mut self) -> io::Result<()> {
1990            if self.failed {
1991                return Err(stream_decoder_failed_error());
1992            }
1993            if self.terminal_seen {
1994                self.finished = true;
1995                return Ok(());
1996            }
1997
1998            let mut input = [0u8; 4];
1999            let available = 4 - self.pending_len;
2000            let read = match self.inner_mut().read(&mut input[..available]) {
2001                Ok(read) => read,
2002                Err(err) => {
2003                    crate::wipe_bytes(&mut input);
2004                    return Err(err);
2005                }
2006            };
2007            if read == 0 {
2008                crate::wipe_bytes(&mut input);
2009                self.finished = true;
2010                self.push_final_pending()?;
2011                return Ok(());
2012            }
2013
2014            self.pending[self.pending_len..self.pending_len + read].copy_from_slice(&input[..read]);
2015            crate::wipe_bytes(&mut input);
2016            self.pending_len += read;
2017            if self.pending_len < 4 {
2018                return Ok(());
2019            }
2020
2021            let mut quad = self.pending;
2022            self.clear_pending();
2023            let result = self.push_decoded(&quad);
2024            crate::wipe_bytes(&mut quad);
2025            result?;
2026            if self.terminal_seen {
2027                self.finished = true;
2028            }
2029            Ok(())
2030        }
2031
2032        fn push_final_pending(&mut self) -> io::Result<()> {
2033            if self.pending_len == 0 {
2034                return Ok(());
2035            }
2036
2037            let mut pending = [0u8; 4];
2038            pending[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
2039            let pending_len = self.pending_len;
2040            self.clear_pending();
2041            let result = self.push_decoded(&pending[..pending_len]);
2042            crate::wipe_bytes(&mut pending);
2043            result
2044        }
2045
2046        fn push_decoded(&mut self, input: &[u8]) -> io::Result<()> {
2047            let mut decoded = [0u8; 3];
2048            let written = match self.engine.decode_slice(input, &mut decoded) {
2049                Ok(written) => written,
2050                Err(err) => {
2051                    crate::wipe_bytes(&mut decoded);
2052                    self.failed = true;
2053                    return Err(decode_error_to_io(err));
2054                }
2055            };
2056            let result = self.output.push_slice(&decoded[..written]);
2057            crate::wipe_bytes(&mut decoded);
2058            result?;
2059            if input.len() == 4 && written < 3 {
2060                self.terminal_seen = true;
2061            }
2062            Ok(())
2063        }
2064    }
2065
2066    /// A streaming Base64 encoder for `std::io::Read`.
2067    pub struct EncoderReader<R, A, const PAD: bool>
2068    where
2069        A: Alphabet,
2070    {
2071        inner: Option<R>,
2072        engine: Engine<A, PAD>,
2073        pending: [u8; 2],
2074        pending_len: usize,
2075        output: OutputQueue<1024>,
2076        finished: bool,
2077        failed: bool,
2078    }
2079
2080    impl<R, A, const PAD: bool> EncoderReader<R, A, PAD>
2081    where
2082        A: Alphabet,
2083    {
2084        /// Creates a new streaming encoder reader.
2085        #[must_use]
2086        pub fn new(inner: R, engine: Engine<A, PAD>) -> Self {
2087            Self {
2088                inner: Some(inner),
2089                engine,
2090                pending: [0; 2],
2091                pending_len: 0,
2092                output: OutputQueue::new(),
2093                finished: false,
2094                failed: false,
2095            }
2096        }
2097
2098        /// Returns a shared reference to the wrapped reader.
2099        #[must_use]
2100        pub fn get_ref(&self) -> &R {
2101            self.inner_ref()
2102        }
2103
2104        /// Returns a mutable reference to the wrapped reader.
2105        pub fn get_mut(&mut self) -> &mut R {
2106            self.inner_mut()
2107        }
2108
2109        /// Returns the Base64 engine used by this adapter.
2110        #[must_use]
2111        pub const fn engine(&self) -> Engine<A, PAD> {
2112            self.engine
2113        }
2114
2115        /// Returns whether this adapter uses padded Base64.
2116        #[must_use]
2117        pub const fn is_padded(&self) -> bool {
2118            PAD
2119        }
2120
2121        /// Returns the number of raw input bytes currently buffered until a
2122        /// complete 3-byte Base64 encode quantum is available.
2123        #[must_use]
2124        pub const fn pending_len(&self) -> usize {
2125            self.pending_len
2126        }
2127
2128        /// Returns whether this encoder reader currently holds a partial input
2129        /// quantum.
2130        #[must_use]
2131        pub const fn has_pending_input(&self) -> bool {
2132            self.pending_len != 0
2133        }
2134
2135        /// Returns how many additional raw input bytes are needed to complete
2136        /// the currently buffered encode quantum.
2137        ///
2138        /// Returns `0` when no partial input quantum is buffered.
2139        #[must_use]
2140        pub const fn pending_input_needed_len(&self) -> usize {
2141            if self.has_pending_input() {
2142                3 - self.pending_len
2143            } else {
2144                0
2145            }
2146        }
2147
2148        /// Returns the number of encoded bytes currently buffered and ready to
2149        /// be read before this adapter polls the wrapped reader again.
2150        #[must_use]
2151        pub const fn buffered_output_len(&self) -> usize {
2152            self.output.len()
2153        }
2154
2155        /// Returns the maximum number of encoded bytes this adapter can buffer
2156        /// before returning bytes to the caller.
2157        #[must_use]
2158        pub const fn buffered_output_capacity(&self) -> usize {
2159            self.output.capacity()
2160        }
2161
2162        /// Returns how many more encoded bytes can be buffered before this
2163        /// adapter must return bytes to the caller.
2164        #[must_use]
2165        pub const fn buffered_output_remaining_capacity(&self) -> usize {
2166            self.output.available_capacity()
2167        }
2168
2169        /// Returns whether this encoder reader currently has encoded output
2170        /// waiting in its internal queue.
2171        #[must_use]
2172        pub const fn has_buffered_output(&self) -> bool {
2173            !self.output.is_empty()
2174        }
2175
2176        /// Returns whether this encoder reader has reached EOF in the wrapped
2177        /// reader.
2178        ///
2179        /// This may become `true` before [`Self::is_finished`] when encoded
2180        /// output is still buffered for the caller.
2181        #[must_use]
2182        pub const fn has_finished_input(&self) -> bool {
2183            self.finished
2184        }
2185
2186        /// Returns whether this reader has reached EOF and has no encoded
2187        /// output buffered for the caller.
2188        #[must_use]
2189        pub const fn is_finished(&self) -> bool {
2190            self.finished && self.output.is_empty()
2191        }
2192
2193        /// Returns whether this adapter has failed closed after an internal
2194        /// stream error.
2195        #[must_use]
2196        pub const fn is_failed(&self) -> bool {
2197            self.failed
2198        }
2199
2200        /// Returns whether [`Self::try_into_inner`] can recover the wrapped
2201        /// reader without discarding pending input or buffered encoded output.
2202        #[must_use]
2203        pub const fn can_into_inner(&self) -> bool {
2204            self.is_finished() && !self.failed
2205        }
2206
2207        /// Consumes the encoder reader and returns the wrapped reader.
2208        #[must_use]
2209        pub fn into_inner(mut self) -> R {
2210            self.take_inner()
2211        }
2212
2213        /// Consumes the encoder reader only after the encoded stream is fully
2214        /// drained.
2215        ///
2216        /// This is a checked alternative to [`Self::into_inner`] for callers
2217        /// that want to avoid accidentally discarding pending input or encoded
2218        /// output buffered inside the adapter.
2219        #[allow(clippy::result_large_err)]
2220        pub fn try_into_inner(mut self) -> Result<R, Self> {
2221            if !self.can_into_inner() {
2222                return Err(self);
2223            }
2224            Ok(self.take_inner())
2225        }
2226
2227        fn inner_ref(&self) -> &R {
2228            match &self.inner {
2229                Some(inner) => inner,
2230                None => unreachable!("stream encoder reader inner reader was already taken"),
2231            }
2232        }
2233
2234        fn inner_mut(&mut self) -> &mut R {
2235            match &mut self.inner {
2236                Some(inner) => inner,
2237                None => unreachable!("stream encoder reader inner reader was already taken"),
2238            }
2239        }
2240
2241        fn take_inner(&mut self) -> R {
2242            match self.inner.take() {
2243                Some(inner) => inner,
2244                None => unreachable!("stream encoder reader inner reader was already taken"),
2245            }
2246        }
2247
2248        fn clear_pending(&mut self) {
2249            crate::wipe_bytes(&mut self.pending);
2250            self.pending_len = 0;
2251        }
2252    }
2253
2254    impl<R, A, const PAD: bool> Drop for EncoderReader<R, A, PAD>
2255    where
2256        A: Alphabet,
2257    {
2258        fn drop(&mut self) {
2259            self.clear_pending();
2260            self.output.clear_all();
2261        }
2262    }
2263
2264    impl<R, A, const PAD: bool> core::fmt::Debug for EncoderReader<R, A, PAD>
2265    where
2266        A: Alphabet,
2267    {
2268        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2269            formatter
2270                .debug_struct("EncoderReader")
2271                .field("inner", &redacted_inner_state(self.inner.is_some()))
2272                .field("engine", &self.engine)
2273                .field("pending", &"<redacted>")
2274                .field("pending_len", &self.pending_len)
2275                .field("pending_input_needed_len", &self.pending_input_needed_len())
2276                .field("buffered_output_len", &self.output.len())
2277                .field("buffered_output_capacity", &self.output.capacity())
2278                .field(
2279                    "buffered_output_remaining_capacity",
2280                    &self.output.available_capacity(),
2281                )
2282                .field("can_into_inner", &self.can_into_inner())
2283                .field("finished", &self.finished)
2284                .field("failed", &self.failed)
2285                .finish()
2286        }
2287    }
2288
2289    impl<R, A, const PAD: bool> Read for EncoderReader<R, A, PAD>
2290    where
2291        R: Read,
2292        A: Alphabet,
2293    {
2294        fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
2295            if self.failed {
2296                return Err(stream_encoder_failed_error());
2297            }
2298
2299            if output.is_empty() {
2300                return Ok(0);
2301            }
2302
2303            while self.output.is_empty() && !self.finished {
2304                self.fill_output()?;
2305            }
2306
2307            Ok(self.output.pop_slice(output))
2308        }
2309    }
2310
2311    impl<R, A, const PAD: bool> EncoderReader<R, A, PAD>
2312    where
2313        R: Read,
2314        A: Alphabet,
2315    {
2316        fn fill_output(&mut self) -> io::Result<()> {
2317            let mut input = [0u8; 768];
2318            let read = match self.inner_mut().read(&mut input) {
2319                Ok(read) => read,
2320                Err(err) => {
2321                    crate::wipe_bytes(&mut input);
2322                    return Err(err);
2323                }
2324            };
2325            if read == 0 {
2326                crate::wipe_bytes(&mut input);
2327                self.finished = true;
2328                if let Err(err) = self.push_final_pending() {
2329                    self.failed = true;
2330                    return Err(err);
2331                }
2332                return Ok(());
2333            }
2334
2335            let mut consumed = 0;
2336            if self.pending_len > 0 {
2337                let needed = 3 - self.pending_len;
2338                if read < needed {
2339                    self.pending[self.pending_len..self.pending_len + read]
2340                        .copy_from_slice(&input[..read]);
2341                    self.pending_len += read;
2342                    crate::wipe_bytes(&mut input);
2343                    return Ok(());
2344                }
2345
2346                let mut chunk = [0u8; 3];
2347                chunk[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
2348                chunk[self.pending_len..].copy_from_slice(&input[..needed]);
2349                let result = self.push_encoded(&chunk);
2350                crate::wipe_bytes(&mut chunk);
2351                if let Err(err) = result {
2352                    crate::wipe_bytes(&mut input);
2353                    self.failed = true;
2354                    return Err(err);
2355                }
2356                self.clear_pending();
2357                consumed += needed;
2358            }
2359
2360            let remaining = &input[consumed..read];
2361            let full_len = remaining.len() / 3 * 3;
2362            let tail_len = remaining.len() - full_len;
2363            let mut tail = [0u8; 2];
2364            tail[..tail_len].copy_from_slice(&remaining[full_len..]);
2365            let result = if full_len > 0 {
2366                self.push_encoded(&remaining[..full_len])
2367            } else {
2368                Ok(())
2369            };
2370            crate::wipe_bytes(&mut input);
2371            if let Err(err) = result {
2372                crate::wipe_bytes(&mut tail);
2373                self.failed = true;
2374                return Err(err);
2375            }
2376            self.pending[..tail_len].copy_from_slice(&tail[..tail_len]);
2377            crate::wipe_bytes(&mut tail);
2378            self.pending_len = tail_len;
2379            Ok(())
2380        }
2381
2382        fn push_final_pending(&mut self) -> io::Result<()> {
2383            if self.pending_len == 0 {
2384                return Ok(());
2385            }
2386
2387            let mut pending = [0u8; 2];
2388            pending[..self.pending_len].copy_from_slice(&self.pending[..self.pending_len]);
2389            let pending_len = self.pending_len;
2390            self.clear_pending();
2391            let result = self.push_encoded(&pending[..pending_len]);
2392            crate::wipe_bytes(&mut pending);
2393            result
2394        }
2395
2396        fn push_encoded(&mut self, input: &[u8]) -> io::Result<()> {
2397            let mut encoded = [0u8; 1024];
2398            let written = match self.engine.encode_slice(input, &mut encoded) {
2399                Ok(written) => written,
2400                Err(err) => {
2401                    crate::wipe_bytes(&mut encoded);
2402                    return Err(encode_error_to_io(err));
2403                }
2404            };
2405            let result = self.output.push_slice(&encoded[..written]);
2406            crate::wipe_bytes(&mut encoded);
2407            result
2408        }
2409    }
2410
2411    const fn redacted_inner_state(present: bool) -> &'static str {
2412        if present { "<present>" } else { "<taken>" }
2413    }
2414}
2415
2416/// Constant-time-oriented scalar decoding APIs.
2417///
2418/// This module is separate from the default decoder so callers can opt into a
2419/// slower path with a narrower timing target. It avoids lookup tables indexed
2420/// by secret input bytes while mapping Base64 symbols and reports malformed
2421/// content through one opaque error. It is not documented as a formally
2422/// verified cryptographic constant-time API.
2423///
2424/// # Security
2425///
2426/// Input length, decoded length, selected alphabet, and final success or
2427/// failure remain public. The clear-tail methods wipe caller-owned output on
2428/// error, but decoded bytes are written during the fixed-shape decode loop
2429/// before final validation is reported. In shared-memory, enclave, or HSM-style
2430/// threat models where another component can observe the output buffer during
2431/// the call, prefer [`crate::ct::CtEngine::decode_slice_staged_clear_tail`]
2432/// with a private staging buffer.
2433///
2434/// The dependency-free comparison helpers on redacted buffers are
2435/// constant-time-oriented best effort, not formally audited MAC or token
2436/// comparison primitives. Applications that can admit dependencies and need a
2437/// reviewed comparison primitive should use one at the protocol boundary.
2438///
2439/// The CT decoder exposes only clear-tail and stack-backed decode APIs. The
2440/// former non-clear-tail methods were removed before the `1.0` stable boundary
2441/// because they could leave decoded plaintext in caller-owned buffers after
2442/// malformed input errors.
2443///
2444/// ```compile_fail
2445/// use base64_ng::ct;
2446///
2447/// let mut output = [0u8; 8];
2448/// let _ = ct::STANDARD.decode_slice(b"aGk=", &mut output);
2449/// ```
2450///
2451/// ```compile_fail
2452/// use base64_ng::ct;
2453///
2454/// let mut buffer = *b"aGk=";
2455/// let _ = ct::STANDARD.decode_in_place(&mut buffer);
2456/// ```
2457pub mod ct {
2458    use super::{
2459        Alphabet, DecodeError, DecodedBuffer, Standard, UrlSafe, ct_decode_in_place,
2460        ct_decode_slice, ct_decode_slice_staged_clear_tail, ct_decoded_len, ct_validate_decode,
2461    };
2462    use core::marker::PhantomData;
2463
2464    /// Standard Base64 constant-time-oriented decoder with padding.
2465    pub const STANDARD: CtEngine<Standard, true> = CtEngine::new();
2466
2467    /// Standard Base64 constant-time-oriented decoder without padding.
2468    pub const STANDARD_NO_PAD: CtEngine<Standard, false> = CtEngine::new();
2469
2470    /// URL-safe Base64 constant-time-oriented decoder with padding.
2471    pub const URL_SAFE: CtEngine<UrlSafe, true> = CtEngine::new();
2472
2473    /// URL-safe Base64 constant-time-oriented decoder without padding.
2474    pub const URL_SAFE_NO_PAD: CtEngine<UrlSafe, false> = CtEngine::new();
2475
2476    /// A zero-sized constant-time-oriented Base64 decoder.
2477    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
2478    pub struct CtEngine<A, const PAD: bool> {
2479        alphabet: PhantomData<A>,
2480    }
2481
2482    impl<A, const PAD: bool> CtEngine<A, PAD>
2483    where
2484        A: Alphabet,
2485    {
2486        /// Creates a new constant-time-oriented decoder engine.
2487        #[must_use]
2488        pub const fn new() -> Self {
2489            Self {
2490                alphabet: PhantomData,
2491            }
2492        }
2493
2494        /// Returns whether this constant-time-oriented decoder expects padded
2495        /// input.
2496        #[must_use]
2497        pub const fn is_padded(&self) -> bool {
2498            PAD
2499        }
2500
2501        /// Validates `input` without writing decoded bytes.
2502        ///
2503        /// This uses the same constant-time-oriented symbol mapping and opaque
2504        /// malformed-input error behavior as
2505        /// [`Self::decode_slice_clear_tail`]. Input length, padding length, and
2506        /// final success or failure remain public.
2507        ///
2508        /// # Examples
2509        ///
2510        /// ```
2511        /// use base64_ng::ct;
2512        ///
2513        /// ct::STANDARD.validate_result(b"aGVsbG8=").unwrap();
2514        /// assert!(ct::STANDARD.validate_result(b"aGVsbG8").is_err());
2515        /// ```
2516        pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
2517            ct_validate_decode::<A, PAD>(input)
2518        }
2519
2520        /// Returns whether `input` is valid for this constant-time-oriented
2521        /// decoder.
2522        ///
2523        /// This is a convenience wrapper around [`Self::validate_result`].
2524        ///
2525        /// # Examples
2526        ///
2527        /// ```
2528        /// use base64_ng::ct;
2529        ///
2530        /// assert!(ct::URL_SAFE_NO_PAD.validate(b"-_8"));
2531        /// assert!(!ct::URL_SAFE_NO_PAD.validate(b"+/8"));
2532        /// ```
2533        #[must_use]
2534        pub fn validate(&self, input: &[u8]) -> bool {
2535            self.validate_result(input).is_ok()
2536        }
2537
2538        /// Returns the exact decoded length for valid input.
2539        ///
2540        /// This uses the same constant-time-oriented validation policy as
2541        /// [`Self::validate_result`] before returning a length. Input length,
2542        /// padding length, and final success or failure remain public.
2543        pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
2544            ct_decoded_len::<A, PAD>(input)
2545        }
2546
2547        /// Decodes `input` into `output` and clears all bytes after the
2548        /// decoded prefix.
2549        ///
2550        /// If decoding fails, the entire output buffer is cleared before the
2551        /// error is returned. Use this variant for sensitive payloads where
2552        /// partially decoded bytes from rejected input should not remain in the
2553        /// caller-owned output buffer.
2554        ///
2555        /// # Security: Transient Plaintext Window
2556        ///
2557        /// Decoded bytes are written to `output` progressively during the
2558        /// fixed-shape decode loop before malformed-input detection is
2559        /// complete. On error, the entire `output` is wiped before returning,
2560        /// but a concurrent same-process observer with access to `output`
2561        /// during the call may observe transient partial plaintext from valid
2562        /// leading quanta. For shared-memory, enclave-adjacent, HSM-style, or
2563        /// multi-principal deployments where even transient writes are
2564        /// unacceptable, use [`Self::decode_slice_staged_clear_tail`] with a
2565        /// private staging buffer.
2566        ///
2567        /// # Examples
2568        ///
2569        /// ```
2570        /// use base64_ng::ct;
2571        ///
2572        /// let mut output = [0xff; 8];
2573        /// let written = ct::STANDARD
2574        ///     .decode_slice_clear_tail(b"aGk=", &mut output)
2575        ///     .unwrap();
2576        ///
2577        /// assert_eq!(&output[..written], b"hi");
2578        /// assert!(output[written..].iter().all(|byte| *byte == 0));
2579        /// ```
2580        #[must_use = "handle decode errors; use decode_slice_staged_clear_tail for shared-memory or HSM-style threat models"]
2581        pub fn decode_slice_clear_tail(
2582            &self,
2583            input: &[u8],
2584            output: &mut [u8],
2585        ) -> Result<usize, DecodeError> {
2586            let written = match ct_decode_slice::<A, PAD>(input, output) {
2587                Ok(written) => written,
2588                Err(err) => {
2589                    crate::wipe_bytes(output);
2590                    return Err(err);
2591                }
2592            };
2593            crate::wipe_tail(output, written);
2594            Ok(written)
2595        }
2596
2597        /// Decodes through caller-provided private staging before copying into
2598        /// `output`.
2599        ///
2600        /// This variant is for shared-memory or sandboxed deployments where
2601        /// the caller-owned `output` buffer must not contain transient decoded
2602        /// bytes from malformed input. The `staging` buffer must be at least
2603        /// the decoded length of `input` and must not be shared with
2604        /// untrusted concurrent observers. On success, decoded bytes are
2605        /// copied from `staging` into `output`; on error, both buffers are
2606        /// cleared before returning.
2607        ///
2608        /// Input length, final success or failure, and decoded length remain
2609        /// public.
2610        pub fn decode_slice_staged_clear_tail(
2611            &self,
2612            input: &[u8],
2613            output: &mut [u8],
2614            staging: &mut [u8],
2615        ) -> Result<usize, DecodeError> {
2616            ct_decode_slice_staged_clear_tail::<A, PAD>(input, output, staging)
2617        }
2618
2619        /// Decodes `input` into a stack-backed buffer.
2620        ///
2621        /// This uses the same constant-time-oriented scalar decoder as
2622        /// [`Self::decode_slice_clear_tail`] and clears the internal backing
2623        /// array before returning an error.
2624        ///
2625        /// # Examples
2626        ///
2627        /// ```
2628        /// use base64_ng::ct;
2629        ///
2630        /// let decoded = ct::STANDARD.decode_buffer::<5>(b"aGVsbG8=").unwrap();
2631        ///
2632        /// assert_eq!(decoded.as_bytes(), b"hello");
2633        /// ```
2634        pub fn decode_buffer<const CAP: usize>(
2635            &self,
2636            input: &[u8],
2637        ) -> Result<DecodedBuffer<CAP>, DecodeError> {
2638            let mut output = DecodedBuffer::new();
2639            let written = match self.decode_slice_clear_tail(input, &mut output.bytes) {
2640                Ok(written) => written,
2641                Err(err) => {
2642                    output.clear();
2643                    return Err(err);
2644                }
2645            };
2646            output.len = written;
2647            Ok(output)
2648        }
2649
2650        /// Decodes `buffer` in place and clears all bytes after the decoded
2651        /// prefix.
2652        ///
2653        /// If decoding fails, the entire buffer is cleared before the error is
2654        /// returned.
2655        ///
2656        /// # Security: Transient Plaintext Window
2657        ///
2658        /// This in-place API writes decoded bytes into `buffer` during the
2659        /// fixed-shape decode loop before malformed-input detection is
2660        /// complete. On error, the entire buffer is wiped before returning,
2661        /// but concurrent same-process observers with access to the same memory
2662        /// can observe transient partial plaintext. Use
2663        /// [`Self::decode_slice_staged_clear_tail`] with a private staging
2664        /// buffer when shared-memory or enclave-adjacent deployments cannot
2665        /// tolerate that window.
2666        ///
2667        /// # Examples
2668        ///
2669        /// ```
2670        /// use base64_ng::ct;
2671        ///
2672        /// let mut buffer = *b"aGk=";
2673        /// let decoded = ct::STANDARD.decode_in_place_clear_tail(&mut buffer).unwrap();
2674        ///
2675        /// assert_eq!(decoded, b"hi");
2676        /// ```
2677        pub fn decode_in_place_clear_tail<'a>(
2678            &self,
2679            buffer: &'a mut [u8],
2680        ) -> Result<&'a mut [u8], DecodeError> {
2681            let len = match ct_decode_in_place::<A, PAD>(buffer) {
2682                Ok(len) => len,
2683                Err(err) => {
2684                    crate::wipe_bytes(buffer);
2685                    return Err(err);
2686                }
2687            };
2688            crate::wipe_tail(buffer, len);
2689            Ok(&mut buffer[..len])
2690        }
2691    }
2692
2693    impl<A, const PAD: bool> core::fmt::Display for CtEngine<A, PAD> {
2694        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2695            write!(formatter, "ct padded={PAD}")
2696        }
2697    }
2698}
2699
2700/// Standard Base64 engine with padding.
2701///
2702/// This default strict engine is not a constant-time token validator or
2703/// key-material decoder. Use [`ct::STANDARD`] or [`Engine::ct_decoder`] for the
2704/// matching constant-time-oriented decoder when timing posture matters.
2705#[doc(alias = "ct")]
2706#[doc(alias = "constant_time")]
2707#[doc(alias = "sensitive")]
2708pub const STANDARD: Engine<Standard, true> = Engine::new();
2709
2710/// Standard Base64 engine without padding.
2711///
2712/// This default strict engine is not a constant-time token validator or
2713/// key-material decoder. Use [`ct::STANDARD_NO_PAD`] or [`Engine::ct_decoder`]
2714/// for the matching constant-time-oriented decoder when timing posture
2715/// matters.
2716#[doc(alias = "ct")]
2717#[doc(alias = "constant_time")]
2718#[doc(alias = "sensitive")]
2719pub const STANDARD_NO_PAD: Engine<Standard, false> = Engine::new();
2720
2721/// URL-safe Base64 engine with padding.
2722///
2723/// This default strict engine is not a constant-time token validator or
2724/// key-material decoder. Use [`ct::URL_SAFE`] or [`Engine::ct_decoder`] for the
2725/// matching constant-time-oriented decoder when timing posture matters.
2726#[doc(alias = "ct")]
2727#[doc(alias = "constant_time")]
2728#[doc(alias = "sensitive")]
2729pub const URL_SAFE: Engine<UrlSafe, true> = Engine::new();
2730
2731/// URL-safe Base64 engine without padding.
2732///
2733/// This default strict engine is not a constant-time token validator or
2734/// key-material decoder. Use [`ct::URL_SAFE_NO_PAD`] or [`Engine::ct_decoder`]
2735/// for the matching constant-time-oriented decoder when timing posture
2736/// matters.
2737#[doc(alias = "ct")]
2738#[doc(alias = "constant_time")]
2739#[doc(alias = "sensitive")]
2740pub const URL_SAFE_NO_PAD: Engine<UrlSafe, false> = Engine::new();
2741
2742/// bcrypt-style Base64 engine without padding.
2743///
2744/// This uses the bcrypt alphabet with the crate's normal Base64 bit packing.
2745/// It does not parse complete bcrypt password-hash strings. This default strict
2746/// engine is not a constant-time token validator or key-material decoder; use
2747/// [`Engine::ct_decoder`] for the matching constant-time-oriented decoder when
2748/// timing posture matters.
2749#[doc(alias = "ct")]
2750#[doc(alias = "constant_time")]
2751#[doc(alias = "sensitive")]
2752pub const BCRYPT_NO_PAD: Engine<Bcrypt, false> = Engine::new();
2753
2754/// Unix `crypt(3)`-style Base64 engine without padding.
2755///
2756/// This uses the `crypt(3)` alphabet with the crate's normal Base64 bit
2757/// packing. It does not parse complete password-hash strings. This default
2758/// strict engine is not a constant-time token validator or key-material
2759/// decoder; use [`Engine::ct_decoder`] for the matching constant-time-oriented
2760/// decoder when timing posture matters.
2761#[doc(alias = "ct")]
2762#[doc(alias = "constant_time")]
2763#[doc(alias = "sensitive")]
2764pub const CRYPT_NO_PAD: Engine<Crypt, false> = Engine::new();
2765
2766/// Line ending used by wrapped Base64 output.
2767#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2768pub enum LineEnding {
2769    /// Line feed (`\n`).
2770    Lf,
2771    /// Carriage return followed by line feed (`\r\n`).
2772    CrLf,
2773}
2774
2775impl LineEnding {
2776    /// Returns a stable printable identifier for this line ending.
2777    #[must_use]
2778    pub const fn name(self) -> &'static str {
2779        match self {
2780            Self::Lf => "LF",
2781            Self::CrLf => "CRLF",
2782        }
2783    }
2784
2785    /// Returns the text representation of this line ending.
2786    #[must_use]
2787    pub const fn as_str(self) -> &'static str {
2788        match self {
2789            Self::Lf => "\n",
2790            Self::CrLf => "\r\n",
2791        }
2792    }
2793
2794    /// Returns the byte representation of this line ending.
2795    #[must_use]
2796    pub const fn as_bytes(self) -> &'static [u8] {
2797        self.as_str().as_bytes()
2798    }
2799
2800    /// Returns the byte length of this line ending.
2801    #[must_use]
2802    pub const fn byte_len(self) -> usize {
2803        match self {
2804            Self::Lf => 1,
2805            Self::CrLf => 2,
2806        }
2807    }
2808}
2809
2810impl core::fmt::Display for LineEnding {
2811    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2812        formatter.write_str(self.name())
2813    }
2814}
2815
2816/// Base64 line wrapping policy.
2817///
2818/// `line_len` is measured in encoded Base64 bytes, not source input bytes.
2819/// Encoders insert line endings between lines and do not append a trailing line
2820/// ending after the final line.
2821#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2822pub struct LineWrap {
2823    /// Maximum encoded bytes per line.
2824    pub line_len: usize,
2825    /// Line ending inserted between wrapped lines.
2826    pub line_ending: LineEnding,
2827}
2828
2829impl LineWrap {
2830    /// MIME-style wrapping: 76 columns with CRLF endings.
2831    pub const MIME: Self = Self::new(76, LineEnding::CrLf);
2832    /// PEM-style wrapping: 64 columns with LF endings.
2833    pub const PEM: Self = Self::new(64, LineEnding::Lf);
2834    /// PEM-style wrapping: 64 columns with CRLF endings.
2835    pub const PEM_CRLF: Self = Self::new(64, LineEnding::CrLf);
2836
2837    /// Creates a wrapping policy.
2838    ///
2839    /// This constructor is intended for fixed, trusted values such as
2840    /// compile-time MIME or PEM profile constants. Use [`Self::checked_new`]
2841    /// when the line length comes from configuration, network input, file
2842    /// metadata, or another untrusted runtime source.
2843    ///
2844    /// # Panics
2845    ///
2846    /// Panics when `line_len` is zero. Base64 wrapping requires a non-zero
2847    /// encoded line length; accepting zero would make progress impossible for
2848    /// wrapped encoders.
2849    #[must_use]
2850    pub const fn new(line_len: usize, line_ending: LineEnding) -> Self {
2851        assert!(line_len != 0, "base64 line wrap length must be non-zero");
2852        Self {
2853            line_len,
2854            line_ending,
2855        }
2856    }
2857
2858    /// Creates a wrapping policy, returning `None` when the line length is
2859    /// invalid.
2860    ///
2861    /// Base64 line-wrapping requires a non-zero encoded line length. This
2862    /// helper is useful when accepting a wrapping policy from configuration or
2863    /// another untrusted source.
2864    #[must_use]
2865    pub const fn checked_new(line_len: usize, line_ending: LineEnding) -> Option<Self> {
2866        if line_len == 0 {
2867            None
2868        } else {
2869            Some(Self::new(line_len, line_ending))
2870        }
2871    }
2872
2873    /// Returns the maximum encoded bytes per line.
2874    #[must_use]
2875    pub const fn line_len(self) -> usize {
2876        self.line_len
2877    }
2878
2879    /// Returns the line ending inserted between wrapped lines.
2880    #[must_use]
2881    pub const fn line_ending(self) -> LineEnding {
2882        self.line_ending
2883    }
2884
2885    /// Returns whether this wrapping policy can be used by the encoder.
2886    #[must_use]
2887    pub const fn is_valid(self) -> bool {
2888        self.line_len != 0
2889    }
2890}
2891
2892impl core::fmt::Display for LineWrap {
2893    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2894        write!(formatter, "{}:{}", self.line_len, self.line_ending.name())
2895    }
2896}
2897
2898#[inline(never)]
2899#[allow(unsafe_code)]
2900fn wipe_bytes(bytes: &mut [u8]) {
2901    for byte in bytes.iter_mut() {
2902        // SAFETY: `byte` comes from a unique mutable slice iterator, so the
2903        // pointer is non-null, aligned, valid for one `u8` write, and does not
2904        // alias another live mutable reference during this iteration.
2905        unsafe {
2906            core::ptr::write_volatile(byte, 0);
2907        }
2908    }
2909    wipe_barrier(bytes.as_mut_ptr(), bytes.len());
2910}
2911
2912#[inline(never)]
2913#[allow(unsafe_code)]
2914fn wipe_barrier(ptr: *mut u8, len: usize) {
2915    let _ = (ptr, len);
2916
2917    #[cfg(all(not(miri), any(target_arch = "x86", target_arch = "x86_64")))]
2918    {
2919        // `mfence` orders prior stores before later memory operations on
2920        // x86/x86_64, while the pointer and length are opaque optimizer inputs.
2921        // SAFETY: the assembly block does not read or write through the pointer.
2922        unsafe {
2923            core::arch::asm!(
2924                "mfence",
2925                "/* {0} {1} */",
2926                in(reg) ptr,
2927                in(reg) len,
2928                options(nostack, preserves_flags)
2929            );
2930        }
2931    }
2932
2933    #[cfg(all(not(miri), target_arch = "aarch64"))]
2934    {
2935        // `dsb sy` completes prior explicit memory accesses before later
2936        // instructions, and `isb sy` flushes subsequent instruction context.
2937        // SAFETY: the assembly block does not read or write through the pointer.
2938        unsafe {
2939            core::arch::asm!(
2940                "dsb sy",
2941                "isb sy",
2942                "hint #20",
2943                "/* {0} {1} */",
2944                in(reg) ptr,
2945                in(reg) len,
2946                options(nostack, preserves_flags)
2947            );
2948        }
2949    }
2950
2951    #[cfg(all(not(miri), target_arch = "arm"))]
2952    {
2953        // `dsb sy` completes prior explicit memory accesses before later
2954        // instructions, and `isb sy` flushes subsequent instruction context.
2955        // SAFETY: the assembly block does not read or write through the pointer.
2956        unsafe {
2957            core::arch::asm!(
2958                "dsb sy",
2959                "isb sy",
2960                "/* {0} {1} */",
2961                in(reg) ptr,
2962                in(reg) len,
2963                options(nostack, preserves_flags)
2964            );
2965        }
2966    }
2967
2968    #[cfg(all(not(miri), any(target_arch = "riscv32", target_arch = "riscv64")))]
2969    {
2970        // `fence rw, rw` orders prior reads/writes before later reads/writes.
2971        // SAFETY: the assembly block does not read or write through the pointer.
2972        unsafe {
2973            core::arch::asm!(
2974                "fence rw, rw",
2975                "/* {0} {1} */",
2976                in(reg) ptr,
2977                in(reg) len,
2978                options(nostack, preserves_flags)
2979            );
2980        }
2981    }
2982
2983    core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
2984}
2985
2986fn wipe_tail(bytes: &mut [u8], start: usize) {
2987    wipe_bytes(&mut bytes[start..]);
2988}
2989
2990#[cfg(feature = "alloc")]
2991#[allow(unsafe_code)]
2992fn wipe_vec_spare_capacity(bytes: &mut alloc::vec::Vec<u8>) {
2993    let ptr = bytes.as_mut_ptr();
2994    let len = bytes.len();
2995    let capacity = bytes.capacity();
2996    let spare = capacity - len;
2997    if spare == 0 {
2998        return;
2999    }
3000
3001    let mut offset = len;
3002    while offset < capacity {
3003        // SAFETY: `offset` is within the vector allocation's spare capacity, so
3004        // the pointer is valid, aligned, and writable for one `u8`. This writes
3005        // a zero byte without reading the prior uninitialized value.
3006        unsafe {
3007            core::ptr::write_volatile(ptr.add(offset), 0);
3008        }
3009        offset += 1;
3010    }
3011    // SAFETY: `spare > 0`, so `len < capacity` and `ptr.add(len)` points
3012    // inside the vector allocation at the first spare-capacity byte.
3013    let spare_ptr = unsafe { ptr.add(len) };
3014    wipe_barrier(spare_ptr, spare);
3015}
3016
3017#[cfg(feature = "alloc")]
3018fn wipe_vec_all(bytes: &mut alloc::vec::Vec<u8>) {
3019    wipe_bytes(bytes);
3020    wipe_vec_spare_capacity(bytes);
3021}
3022
3023/// Stack-backed encoded Base64 output.
3024///
3025/// This type is intended for short values where heap allocation would be
3026/// unnecessary but manually sizing and passing a separate output slice is
3027/// noisy. Its visible bytes are produced by crate encoders, so [`Self::as_str`]
3028/// can return `&str` without exposing a fallible UTF-8 conversion to callers.
3029///
3030/// The backing array is cleared when the value is dropped. This is best-effort
3031/// data-retention reduction and is not a formal zeroization guarantee.
3032///
3033/// On `wasm32` targets, the wipe barrier uses only a compiler fence. The wasm
3034/// runtime JIT may still optimize or retain cleared bytes in ways this crate
3035/// cannot control. `wasm32` builds fail closed by default; enable
3036/// `allow-wasm32-best-effort-wipe` only when the deployment explicitly accepts
3037/// this limitation and applies its own memory strategy around stack-backed
3038/// buffers.
3039pub struct EncodedBuffer<const CAP: usize> {
3040    bytes: [u8; CAP],
3041    len: usize,
3042}
3043
3044/// Owned stack array extracted from [`EncodedBuffer`].
3045///
3046/// This wrapper keeps the extracted encoded bytes on the crate's best-effort
3047/// drop-time cleanup path. Use
3048/// [`Self::into_exposed_unprotected_array_caller_must_zeroize`] only when a
3049/// bare array is unavoidable and the caller will handle cleanup.
3050pub struct ExposedEncodedArray<const CAP: usize> {
3051    bytes: [u8; CAP],
3052    len: usize,
3053}
3054
3055impl<const CAP: usize> ExposedEncodedArray<CAP> {
3056    /// Wraps an encoded backing array and visible length.
3057    ///
3058    /// # Panics
3059    ///
3060    /// Panics if `len` is greater than `CAP`.
3061    #[must_use]
3062    pub const fn from_array(bytes: [u8; CAP], len: usize) -> Self {
3063        assert!(len <= CAP, "visible length exceeds array capacity");
3064        Self { bytes, len }
3065    }
3066
3067    /// Returns the visible encoded bytes.
3068    #[must_use]
3069    pub fn as_bytes(&self) -> &[u8] {
3070        &self.bytes[..self.len]
3071    }
3072
3073    /// Returns the number of visible encoded bytes.
3074    #[must_use]
3075    pub const fn len(&self) -> usize {
3076        self.len
3077    }
3078
3079    /// Returns whether there are no visible encoded bytes.
3080    #[must_use]
3081    pub const fn is_empty(&self) -> bool {
3082        self.len == 0
3083    }
3084
3085    /// Returns the backing array capacity.
3086    #[must_use]
3087    pub const fn capacity(&self) -> usize {
3088        CAP
3089    }
3090
3091    /// Consumes the wrapper and returns a bare array plus visible length.
3092    ///
3093    /// This is an unprotected escape hatch. The returned array will not be
3094    /// cleared by this crate on drop. Callers must clear it with their own
3095    /// approved zeroization policy.
3096    #[must_use = "caller must zeroize the returned array"]
3097    pub fn into_exposed_unprotected_array_caller_must_zeroize(mut self) -> ([u8; CAP], usize) {
3098        let len = self.len;
3099        self.len = 0;
3100        (core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
3101    }
3102}
3103
3104impl<const CAP: usize> Drop for ExposedEncodedArray<CAP> {
3105    fn drop(&mut self) {
3106        wipe_bytes(&mut self.bytes);
3107        self.len = 0;
3108    }
3109}
3110
3111impl<const CAP: usize> core::fmt::Debug for ExposedEncodedArray<CAP> {
3112    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3113        formatter
3114            .debug_struct("ExposedEncodedArray")
3115            .field("bytes", &"<redacted>")
3116            .field("len", &self.len)
3117            .field("capacity", &CAP)
3118            .finish()
3119    }
3120}
3121
3122impl<const CAP: usize> EncodedBuffer<CAP> {
3123    /// Creates an empty encoded buffer.
3124    #[must_use]
3125    pub const fn new() -> Self {
3126        Self {
3127            bytes: [0u8; CAP],
3128            len: 0,
3129        }
3130    }
3131
3132    /// Returns the number of visible encoded bytes.
3133    #[must_use]
3134    pub const fn len(&self) -> usize {
3135        self.len
3136    }
3137
3138    /// Returns whether the buffer has no visible encoded bytes.
3139    #[must_use]
3140    pub const fn is_empty(&self) -> bool {
3141        self.len == 0
3142    }
3143
3144    /// Returns whether the visible encoded bytes fill the stack backing array.
3145    #[must_use]
3146    pub const fn is_full(&self) -> bool {
3147        self.len == CAP
3148    }
3149
3150    /// Returns the stack capacity in bytes.
3151    #[must_use]
3152    pub const fn capacity(&self) -> usize {
3153        CAP
3154    }
3155
3156    /// Returns the number of unused bytes in the stack backing array.
3157    #[must_use]
3158    pub const fn remaining_capacity(&self) -> usize {
3159        CAP - self.len
3160    }
3161
3162    /// Returns the visible encoded bytes.
3163    #[must_use]
3164    pub fn as_bytes(&self) -> &[u8] {
3165        &self.bytes[..self.len]
3166    }
3167
3168    /// Returns the visible encoded bytes as UTF-8 text.
3169    ///
3170    /// Encoded Base64 output is produced as ASCII by this crate, so this
3171    /// method should not fail unless an internal invariant has been broken.
3172    /// It is provided for callers that prefer a fallible accessor over
3173    /// [`Self::as_str`].
3174    pub fn as_utf8(&self) -> Result<&str, core::str::Utf8Error> {
3175        core::str::from_utf8(self.as_bytes())
3176    }
3177
3178    /// Returns the visible encoded bytes as UTF-8.
3179    ///
3180    /// # Panics
3181    ///
3182    /// Panics only if the crate's internal invariant is broken and the buffer
3183    /// contains non-UTF-8 bytes.
3184    #[must_use]
3185    pub fn as_str(&self) -> &str {
3186        match self.as_utf8() {
3187            Ok(output) => output,
3188            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
3189        }
3190    }
3191
3192    /// Compares this encoded output to `other` without short-circuiting on the
3193    /// first differing byte.
3194    ///
3195    /// Length and the final equality result remain public. Different lengths
3196    /// return `false` immediately; use this helper only when the compared
3197    /// lengths are public protocol facts or have been normalized by the
3198    /// caller. For equal-length inputs, this helper scans every byte before
3199    /// returning. It is constant-time-oriented best effort, not a formal
3200    /// cryptographic constant-time guarantee. This comparison is deliberately
3201    /// explicit: redacted buffer types do not implement [`PartialEq`] because
3202    /// `==` would make a best-effort helper look like a formal token/MAC
3203    /// comparison primitive.
3204    ///
3205    /// Do not use this helper as the sole MAC, bearer-token, password-hash, or
3206    /// authentication-secret comparison primitive in high-assurance systems.
3207    /// Applications that can admit dependencies should use a reviewed
3208    /// constant-time comparison primitive, such as `subtle`, at the protocol
3209    /// boundary.
3210    #[doc(alias = "constant_time_eq")]
3211    #[must_use]
3212    pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
3213        constant_time_eq_public_len(self.as_bytes(), other)
3214    }
3215
3216    /// Consumes the wrapper and returns the backing array plus visible length
3217    /// inside a drop-wiping exposed wrapper.
3218    ///
3219    /// This is an explicit escape hatch for no-alloc interop with APIs that
3220    /// require ownership of a fixed array. The returned
3221    /// [`ExposedEncodedArray`] remains redacted by formatting and clears its
3222    /// backing array on drop.
3223    #[must_use]
3224    pub fn into_exposed_array(mut self) -> ExposedEncodedArray<CAP> {
3225        let len = self.len;
3226        self.len = 0;
3227        ExposedEncodedArray::from_array(core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
3228    }
3229
3230    /// Clears the visible bytes and the full backing array.
3231    pub fn clear(&mut self) {
3232        wipe_bytes(&mut self.bytes);
3233        self.len = 0;
3234    }
3235
3236    /// Clears bytes after the visible prefix.
3237    pub fn clear_tail(&mut self) {
3238        wipe_tail(&mut self.bytes, self.len);
3239    }
3240}
3241
3242impl<const CAP: usize> AsRef<[u8]> for EncodedBuffer<CAP> {
3243    fn as_ref(&self) -> &[u8] {
3244        self.as_bytes()
3245    }
3246}
3247
3248impl<const CAP: usize> Clone for EncodedBuffer<CAP> {
3249    /// Clones the visible encoded bytes into a second stack-backed buffer.
3250    ///
3251    /// Security note: cloning duplicates the visible bytes in memory. Both the
3252    /// original and the clone must be dropped or explicitly cleared before the
3253    /// duplicated bytes are gone on the crate's best-effort cleanup path. The
3254    /// compiler may also create temporary stack copies while performing the
3255    /// copy; those intermediates are outside this crate's cleanup boundary.
3256    /// Avoid cloning encoded secret material; use `SecretBuffer` when redacted
3257    /// formatting and heap-owned secret handling are required.
3258    fn clone(&self) -> Self {
3259        let mut output = Self::new();
3260        output.bytes[..self.len].copy_from_slice(self.as_bytes());
3261        output.len = self.len;
3262        output
3263    }
3264}
3265
3266impl<const CAP: usize> core::fmt::Debug for EncodedBuffer<CAP> {
3267    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3268        formatter
3269            .debug_struct("EncodedBuffer")
3270            .field("bytes", &"<redacted>")
3271            .field("len", &self.len)
3272            .field("capacity", &CAP)
3273            .finish()
3274    }
3275}
3276
3277impl<const CAP: usize> core::fmt::Display for EncodedBuffer<CAP> {
3278    /// Writes the full Base64 text.
3279    ///
3280    /// Security note: this is intentionally not redacted. Do not use
3281    /// `EncodedBuffer` for encoded secrets that may reach logs or error
3282    /// messages; use `SecretBuffer` for redacted formatting.
3283    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3284        formatter.write_str(self.as_str())
3285    }
3286}
3287
3288impl<const CAP: usize> Default for EncodedBuffer<CAP> {
3289    fn default() -> Self {
3290        Self::new()
3291    }
3292}
3293
3294impl<const CAP: usize> Drop for EncodedBuffer<CAP> {
3295    fn drop(&mut self) {
3296        self.clear();
3297    }
3298}
3299
3300impl<const CAP: usize> TryFrom<&[u8]> for EncodedBuffer<CAP> {
3301    type Error = EncodeError;
3302
3303    /// Encodes bytes into strict standard padded Base64 in a stack-backed
3304    /// buffer.
3305    ///
3306    /// Use [`Engine::encode_buffer`] or [`Profile::encode_buffer`] when a
3307    /// different alphabet, padding mode, or line-wrapping profile is required.
3308    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
3309        STANDARD.encode_buffer(input)
3310    }
3311}
3312
3313impl<const CAP: usize, const N: usize> TryFrom<&[u8; N]> for EncodedBuffer<CAP> {
3314    type Error = EncodeError;
3315
3316    /// Encodes a byte array into strict standard padded Base64 in a
3317    /// stack-backed buffer.
3318    ///
3319    /// Use [`Engine::encode_buffer`] or [`Profile::encode_buffer`] when a
3320    /// different alphabet, padding mode, or line-wrapping profile is required.
3321    fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
3322        Self::try_from(&input[..])
3323    }
3324}
3325
3326impl<const CAP: usize> TryFrom<&str> for EncodedBuffer<CAP> {
3327    type Error = EncodeError;
3328
3329    /// Encodes UTF-8 text bytes into strict standard padded Base64 in a
3330    /// stack-backed buffer.
3331    ///
3332    /// This treats the string as raw input bytes. Use
3333    /// [`Engine::encode_buffer`] or [`Profile::encode_buffer`] when a
3334    /// different alphabet, padding mode, or line-wrapping profile is required.
3335    fn try_from(input: &str) -> Result<Self, Self::Error> {
3336        Self::try_from(input.as_bytes())
3337    }
3338}
3339
3340/// Stack-backed decoded Base64 output.
3341///
3342/// This type is intended for short decoded values where heap allocation would
3343/// be unnecessary but manually sizing and passing a separate output slice is
3344/// noisy. Decoded data may be binary or secret-bearing, so formatting is
3345/// redacted and contents are exposed only through explicit byte accessors.
3346///
3347/// The backing array is cleared when the value is dropped. This is best-effort
3348/// data-retention reduction and is not a formal zeroization guarantee.
3349///
3350/// On `wasm32` targets, the wipe barrier uses only a compiler fence. The wasm
3351/// runtime JIT may still optimize or retain cleared bytes in ways this crate
3352/// cannot control. `wasm32` builds fail closed by default; enable
3353/// `allow-wasm32-best-effort-wipe` only when the deployment explicitly accepts
3354/// this limitation and applies its own memory strategy around stack-backed
3355/// buffers.
3356pub struct DecodedBuffer<const CAP: usize> {
3357    bytes: [u8; CAP],
3358    len: usize,
3359}
3360
3361/// Owned stack array extracted from [`DecodedBuffer`].
3362///
3363/// This wrapper keeps the extracted decoded bytes on the crate's best-effort
3364/// drop-time cleanup path. Use
3365/// [`Self::into_exposed_unprotected_array_caller_must_zeroize`] only when a
3366/// bare array is unavoidable and the caller will handle cleanup.
3367pub struct ExposedDecodedArray<const CAP: usize> {
3368    bytes: [u8; CAP],
3369    len: usize,
3370}
3371
3372impl<const CAP: usize> ExposedDecodedArray<CAP> {
3373    /// Wraps a decoded backing array and visible length.
3374    ///
3375    /// # Panics
3376    ///
3377    /// Panics if `len` is greater than `CAP`.
3378    #[must_use]
3379    pub const fn from_array(bytes: [u8; CAP], len: usize) -> Self {
3380        assert!(len <= CAP, "visible length exceeds array capacity");
3381        Self { bytes, len }
3382    }
3383
3384    /// Returns the visible decoded bytes.
3385    #[must_use]
3386    pub fn as_bytes(&self) -> &[u8] {
3387        &self.bytes[..self.len]
3388    }
3389
3390    /// Returns the number of visible decoded bytes.
3391    #[must_use]
3392    pub const fn len(&self) -> usize {
3393        self.len
3394    }
3395
3396    /// Returns whether there are no visible decoded bytes.
3397    #[must_use]
3398    pub const fn is_empty(&self) -> bool {
3399        self.len == 0
3400    }
3401
3402    /// Returns the backing array capacity.
3403    #[must_use]
3404    pub const fn capacity(&self) -> usize {
3405        CAP
3406    }
3407
3408    /// Consumes the wrapper and returns a bare array plus visible length.
3409    ///
3410    /// This is an unprotected escape hatch. The returned array will not be
3411    /// cleared by this crate on drop. Callers must clear it with their own
3412    /// approved zeroization policy.
3413    #[must_use = "caller must zeroize the returned array"]
3414    pub fn into_exposed_unprotected_array_caller_must_zeroize(mut self) -> ([u8; CAP], usize) {
3415        let len = self.len;
3416        self.len = 0;
3417        (core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
3418    }
3419}
3420
3421impl<const CAP: usize> Drop for ExposedDecodedArray<CAP> {
3422    fn drop(&mut self) {
3423        wipe_bytes(&mut self.bytes);
3424        self.len = 0;
3425    }
3426}
3427
3428impl<const CAP: usize> core::fmt::Debug for ExposedDecodedArray<CAP> {
3429    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3430        formatter
3431            .debug_struct("ExposedDecodedArray")
3432            .field("bytes", &"<redacted>")
3433            .field("len", &self.len)
3434            .field("capacity", &CAP)
3435            .finish()
3436    }
3437}
3438
3439impl<const CAP: usize> DecodedBuffer<CAP> {
3440    /// Creates an empty decoded buffer.
3441    #[must_use]
3442    pub const fn new() -> Self {
3443        Self {
3444            bytes: [0u8; CAP],
3445            len: 0,
3446        }
3447    }
3448
3449    /// Returns the number of visible decoded bytes.
3450    #[must_use]
3451    pub const fn len(&self) -> usize {
3452        self.len
3453    }
3454
3455    /// Returns whether the buffer has no visible decoded bytes.
3456    #[must_use]
3457    pub const fn is_empty(&self) -> bool {
3458        self.len == 0
3459    }
3460
3461    /// Returns whether the visible decoded bytes fill the stack backing array.
3462    #[must_use]
3463    pub const fn is_full(&self) -> bool {
3464        self.len == CAP
3465    }
3466
3467    /// Returns the stack capacity in bytes.
3468    #[must_use]
3469    pub const fn capacity(&self) -> usize {
3470        CAP
3471    }
3472
3473    /// Returns the number of unused bytes in the stack backing array.
3474    #[must_use]
3475    pub const fn remaining_capacity(&self) -> usize {
3476        CAP - self.len
3477    }
3478
3479    /// Returns the visible decoded bytes.
3480    #[must_use]
3481    pub fn as_bytes(&self) -> &[u8] {
3482        &self.bytes[..self.len]
3483    }
3484
3485    /// Returns the visible decoded bytes as UTF-8 text.
3486    ///
3487    /// Decoded Base64 output is arbitrary bytes, so this method is fallible.
3488    /// Use [`Self::as_bytes`] when the decoded payload is binary or when text
3489    /// validation belongs to a higher protocol layer.
3490    pub fn as_utf8(&self) -> Result<&str, core::str::Utf8Error> {
3491        core::str::from_utf8(self.as_bytes())
3492    }
3493
3494    /// Compares this decoded output to `other` without short-circuiting on the
3495    /// first differing byte.
3496    ///
3497    /// Length and the final equality result remain public. Different lengths
3498    /// return `false` immediately; use this helper only when the compared
3499    /// lengths are public protocol facts or have been normalized by the
3500    /// caller. For equal-length inputs, this helper scans every byte before
3501    /// returning. It is constant-time-oriented best effort, not a formal
3502    /// cryptographic constant-time guarantee. This comparison is deliberately
3503    /// explicit: redacted buffer types do not implement [`PartialEq`] because
3504    /// `==` would make a best-effort helper look like a formal token/MAC
3505    /// comparison primitive.
3506    ///
3507    /// Do not use this helper as the sole MAC, bearer-token, password-hash, or
3508    /// authentication-secret comparison primitive in high-assurance systems.
3509    /// Applications that can admit dependencies should use a reviewed
3510    /// constant-time comparison primitive, such as `subtle`, at the protocol
3511    /// boundary.
3512    #[doc(alias = "constant_time_eq")]
3513    #[must_use]
3514    pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
3515        constant_time_eq_public_len(self.as_bytes(), other)
3516    }
3517
3518    /// Consumes the wrapper and returns the backing array plus visible length
3519    /// inside a drop-wiping exposed wrapper.
3520    ///
3521    /// This is an explicit escape hatch for no-alloc interop with APIs that
3522    /// require ownership of a fixed array. The returned
3523    /// [`ExposedDecodedArray`] remains redacted by formatting and clears its
3524    /// backing array on drop.
3525    #[must_use]
3526    pub fn into_exposed_array(mut self) -> ExposedDecodedArray<CAP> {
3527        let len = self.len;
3528        self.len = 0;
3529        ExposedDecodedArray::from_array(core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
3530    }
3531
3532    /// Clears the visible bytes and the full backing array.
3533    pub fn clear(&mut self) {
3534        wipe_bytes(&mut self.bytes);
3535        self.len = 0;
3536    }
3537
3538    /// Clears bytes after the visible prefix.
3539    pub fn clear_tail(&mut self) {
3540        wipe_tail(&mut self.bytes, self.len);
3541    }
3542}
3543
3544impl<const CAP: usize> AsRef<[u8]> for DecodedBuffer<CAP> {
3545    fn as_ref(&self) -> &[u8] {
3546        self.as_bytes()
3547    }
3548}
3549
3550impl<const CAP: usize> Clone for DecodedBuffer<CAP> {
3551    /// Clones the visible decoded bytes into a second stack-backed buffer.
3552    ///
3553    /// Security note: cloning duplicates decoded bytes in memory. Both the
3554    /// original and the clone must be dropped or explicitly cleared before the
3555    /// duplicated bytes are gone on the crate's best-effort cleanup path. The
3556    /// compiler may also create temporary stack copies while performing the
3557    /// copy; those intermediates are outside this crate's cleanup boundary. For
3558    /// high-assurance applications, avoid cloning decoded key material and use
3559    /// `SecretBuffer` for heap-owned secrets without a `Clone` implementation.
3560    fn clone(&self) -> Self {
3561        let mut output = Self::new();
3562        output.bytes[..self.len].copy_from_slice(self.as_bytes());
3563        output.len = self.len;
3564        output
3565    }
3566}
3567
3568impl<const CAP: usize> core::fmt::Debug for DecodedBuffer<CAP> {
3569    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3570        formatter
3571            .debug_struct("DecodedBuffer")
3572            .field("bytes", &"<redacted>")
3573            .field("len", &self.len)
3574            .field("capacity", &CAP)
3575            .finish()
3576    }
3577}
3578
3579impl<const CAP: usize> Default for DecodedBuffer<CAP> {
3580    fn default() -> Self {
3581        Self::new()
3582    }
3583}
3584
3585impl<const CAP: usize> Drop for DecodedBuffer<CAP> {
3586    fn drop(&mut self) {
3587        self.clear();
3588    }
3589}
3590
3591impl<const CAP: usize> TryFrom<&[u8]> for DecodedBuffer<CAP> {
3592    type Error = DecodeError;
3593
3594    /// Decodes strict standard padded Base64 into a stack-backed buffer.
3595    ///
3596    /// Use [`Engine::decode_buffer`] or [`Profile::decode_buffer`] when a
3597    /// different alphabet, padding mode, or line-wrapping profile is required.
3598    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
3599        STANDARD.decode_buffer(input)
3600    }
3601}
3602
3603impl<const CAP: usize, const N: usize> TryFrom<&[u8; N]> for DecodedBuffer<CAP> {
3604    type Error = DecodeError;
3605
3606    /// Decodes a strict standard padded Base64 byte array into a stack-backed
3607    /// buffer.
3608    ///
3609    /// Use [`Engine::decode_buffer`] or [`Profile::decode_buffer`] when a
3610    /// different alphabet, padding mode, or line-wrapping profile is required.
3611    fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
3612        Self::try_from(&input[..])
3613    }
3614}
3615
3616impl<const CAP: usize> TryFrom<&str> for DecodedBuffer<CAP> {
3617    type Error = DecodeError;
3618
3619    /// Decodes strict standard padded Base64 text into a stack-backed buffer.
3620    ///
3621    /// Use [`Engine::decode_buffer`] or [`Profile::decode_buffer`] when a
3622    /// different alphabet, padding mode, or line-wrapping profile is required.
3623    fn try_from(input: &str) -> Result<Self, Self::Error> {
3624        Self::try_from(input.as_bytes())
3625    }
3626}
3627
3628impl<const CAP: usize> core::str::FromStr for DecodedBuffer<CAP> {
3629    type Err = DecodeError;
3630
3631    /// Decodes strict standard padded Base64 text into a stack-backed buffer.
3632    ///
3633    /// Use [`Engine::decode_buffer`] or [`Profile::decode_buffer`] when a
3634    /// different alphabet, padding mode, or line-wrapping profile is required.
3635    fn from_str(input: &str) -> Result<Self, Self::Err> {
3636        Self::try_from(input)
3637    }
3638}
3639
3640/// Owned sensitive bytes with redacted formatting and drop-time cleanup.
3641///
3642/// `SecretBuffer` is available with the `alloc` feature. It is intended for
3643/// decoded keys, tokens, and other values that should not be accidentally
3644/// logged. The buffer exposes contents only through explicit reveal methods.
3645///
3646/// Spare vector capacity is cleared when wrapping owned bytes. On drop,
3647/// initialized bytes and vector spare capacity are cleared with the crate's
3648/// internal best-effort wipe helpers. This is data-retention reduction, not a
3649/// formal zeroization guarantee, and it cannot make claims about allocator
3650/// behavior or historical copies outside the wrapper.
3651///
3652/// # Platform Memory Controls
3653///
3654/// `SecretBuffer` does not lock its allocation into physical memory. The OS
3655/// may page its contents to disk, include them in hibernation images, or expose
3656/// them through crash dumps. High-assurance deployments must combine
3657/// `SecretBuffer` with platform memory-locking where available, encrypted or
3658/// disabled swap, crash-dump suppression, and allocator isolation appropriate
3659/// for their environment.
3660///
3661/// On `wasm32` targets, the wipe barrier uses only a compiler fence. The wasm
3662/// runtime JIT may still optimize or retain cleared bytes in ways this crate
3663/// cannot control. `wasm32` builds fail closed by default; enable
3664/// `allow-wasm32-best-effort-wipe` only when the deployment explicitly accepts
3665/// this limitation and applies its own memory strategy around owned secret
3666/// buffers.
3667#[cfg(feature = "alloc")]
3668pub struct SecretBuffer {
3669    bytes: alloc::vec::Vec<u8>,
3670}
3671
3672/// Owned secret bytes extracted from [`SecretBuffer`].
3673///
3674/// This wrapper keeps redacted formatting, best-effort spare-capacity clearing
3675/// at construction time, and best-effort full wipe on drop after a
3676/// [`SecretBuffer`] is consumed for owned interop. Use
3677/// [`Self::into_exposed_unprotected_vec_caller_must_zeroize`] only when a raw
3678/// `Vec<u8>` is unavoidable and the caller will handle cleanup.
3679#[cfg(feature = "alloc")]
3680pub struct ExposedSecretVec {
3681    bytes: alloc::vec::Vec<u8>,
3682}
3683
3684#[cfg(feature = "alloc")]
3685impl ExposedSecretVec {
3686    /// Wraps an owned vector as exposed secret material.
3687    #[must_use]
3688    pub fn from_vec(mut bytes: alloc::vec::Vec<u8>) -> Self {
3689        wipe_vec_spare_capacity(&mut bytes);
3690        Self { bytes }
3691    }
3692
3693    /// Returns the number of initialized secret bytes.
3694    #[must_use]
3695    pub fn len(&self) -> usize {
3696        self.bytes.len()
3697    }
3698
3699    /// Returns whether the buffer contains no initialized secret bytes.
3700    #[must_use]
3701    pub fn is_empty(&self) -> bool {
3702        self.bytes.is_empty()
3703    }
3704
3705    /// Reveals the secret bytes.
3706    ///
3707    /// This method is intentionally named to make secret access explicit at the
3708    /// call site.
3709    #[must_use]
3710    pub fn expose_secret(&self) -> &[u8] {
3711        &self.bytes
3712    }
3713
3714    /// Reveals the secret bytes mutably.
3715    ///
3716    /// This method is intentionally named to make secret access explicit at the
3717    /// call site.
3718    #[must_use]
3719    pub fn expose_secret_mut(&mut self) -> &mut [u8] {
3720        &mut self.bytes
3721    }
3722
3723    /// Consumes the wrapper and returns a raw `Vec<u8>`.
3724    ///
3725    /// This is an unprotected escape hatch. The returned vector is no longer
3726    /// redacted by formatting and will not be cleared by this crate on drop.
3727    /// Callers must clear it with their own approved zeroization policy.
3728    #[must_use = "caller must zeroize the returned Vec"]
3729    pub fn into_exposed_unprotected_vec_caller_must_zeroize(mut self) -> alloc::vec::Vec<u8> {
3730        core::mem::take(&mut self.bytes)
3731    }
3732}
3733
3734#[cfg(feature = "alloc")]
3735impl core::fmt::Debug for ExposedSecretVec {
3736    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3737        formatter
3738            .debug_struct("ExposedSecretVec")
3739            .field("bytes", &"<redacted>")
3740            .field("len", &self.len())
3741            .finish()
3742    }
3743}
3744
3745#[cfg(feature = "alloc")]
3746impl core::fmt::Display for ExposedSecretVec {
3747    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3748        formatter.write_str("<redacted>")
3749    }
3750}
3751
3752#[cfg(feature = "alloc")]
3753impl Drop for ExposedSecretVec {
3754    fn drop(&mut self) {
3755        wipe_vec_all(&mut self.bytes);
3756    }
3757}
3758
3759#[cfg(feature = "alloc")]
3760impl AsRef<[u8]> for ExposedSecretVec {
3761    fn as_ref(&self) -> &[u8] {
3762        self.expose_secret()
3763    }
3764}
3765
3766#[cfg(feature = "alloc")]
3767impl AsMut<[u8]> for ExposedSecretVec {
3768    fn as_mut(&mut self) -> &mut [u8] {
3769        self.expose_secret_mut()
3770    }
3771}
3772
3773/// Owned secret UTF-8 text extracted from [`SecretBuffer`].
3774///
3775/// This wrapper keeps redacted formatting, best-effort spare-capacity clearing
3776/// at construction time, and best-effort full wipe on drop after a
3777/// [`SecretBuffer`] is consumed for string interop. Use
3778/// [`Self::into_exposed_unprotected_string_caller_must_zeroize`] only when a
3779/// raw `String` is unavoidable and the caller will handle cleanup.
3780#[cfg(feature = "alloc")]
3781pub struct ExposedSecretString {
3782    text: alloc::string::String,
3783}
3784
3785#[cfg(feature = "alloc")]
3786impl ExposedSecretString {
3787    /// Wraps an owned UTF-8 string as exposed secret text.
3788    #[must_use]
3789    pub fn from_string(text: alloc::string::String) -> Self {
3790        let mut bytes = text.into_bytes();
3791        wipe_vec_spare_capacity(&mut bytes);
3792        let text = string_from_validated_secret_bytes(bytes);
3793        Self { text }
3794    }
3795
3796    /// Returns the length of the secret text in bytes.
3797    #[must_use]
3798    pub fn len(&self) -> usize {
3799        self.text.len()
3800    }
3801
3802    /// Returns whether the secret text is empty.
3803    #[must_use]
3804    pub fn is_empty(&self) -> bool {
3805        self.text.is_empty()
3806    }
3807
3808    /// Reveals the secret text.
3809    ///
3810    /// This method is intentionally named to make secret access explicit at
3811    /// the call site.
3812    #[must_use]
3813    pub fn expose_secret(&self) -> &str {
3814        &self.text
3815    }
3816
3817    /// Reveals the secret text as bytes.
3818    ///
3819    /// This method is intentionally named to make secret access explicit at
3820    /// the call site.
3821    #[must_use]
3822    pub fn expose_secret_bytes(&self) -> &[u8] {
3823        self.text.as_bytes()
3824    }
3825
3826    /// Consumes the wrapper and returns a raw `String`.
3827    ///
3828    /// This is an unprotected escape hatch. The returned string is no longer
3829    /// redacted by formatting and will not be cleared by this crate on drop.
3830    /// Callers must clear it with their own approved zeroization policy.
3831    #[must_use = "caller must zeroize the returned String"]
3832    pub fn into_exposed_unprotected_string_caller_must_zeroize(mut self) -> alloc::string::String {
3833        core::mem::take(&mut self.text)
3834    }
3835}
3836
3837#[cfg(feature = "alloc")]
3838impl core::fmt::Debug for ExposedSecretString {
3839    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3840        formatter
3841            .debug_struct("ExposedSecretString")
3842            .field("text", &"<redacted>")
3843            .field("len", &self.len())
3844            .finish()
3845    }
3846}
3847
3848#[cfg(feature = "alloc")]
3849impl core::fmt::Display for ExposedSecretString {
3850    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3851        formatter.write_str("<redacted>")
3852    }
3853}
3854
3855#[cfg(feature = "alloc")]
3856impl Drop for ExposedSecretString {
3857    fn drop(&mut self) {
3858        let mut bytes = core::mem::take(&mut self.text).into_bytes();
3859        wipe_vec_all(&mut bytes);
3860    }
3861}
3862
3863#[cfg(feature = "alloc")]
3864impl AsRef<str> for ExposedSecretString {
3865    fn as_ref(&self) -> &str {
3866        self.expose_secret()
3867    }
3868}
3869
3870#[cfg(feature = "alloc")]
3871impl SecretBuffer {
3872    /// Wraps an existing vector as sensitive material.
3873    #[must_use]
3874    pub fn from_vec(mut bytes: alloc::vec::Vec<u8>) -> Self {
3875        wipe_vec_spare_capacity(&mut bytes);
3876        Self { bytes }
3877    }
3878
3879    /// Copies a slice into an owned sensitive buffer.
3880    #[must_use]
3881    pub fn from_slice(bytes: &[u8]) -> Self {
3882        Self::from_vec(bytes.to_vec())
3883    }
3884
3885    /// Returns the number of initialized secret bytes.
3886    #[must_use]
3887    pub fn len(&self) -> usize {
3888        self.bytes.len()
3889    }
3890
3891    /// Returns whether the buffer contains no initialized secret bytes.
3892    #[must_use]
3893    pub fn is_empty(&self) -> bool {
3894        self.bytes.is_empty()
3895    }
3896
3897    /// Reveals the secret bytes.
3898    ///
3899    /// This method is intentionally named to make secret access explicit at the
3900    /// call site.
3901    #[must_use]
3902    pub fn expose_secret(&self) -> &[u8] {
3903        &self.bytes
3904    }
3905
3906    /// Reveals the secret bytes as UTF-8 text.
3907    ///
3908    /// This method is intentionally named to make secret access explicit at the
3909    /// call site. Secret material may be arbitrary binary data, so this method
3910    /// is fallible.
3911    pub fn expose_secret_utf8(&self) -> Result<&str, core::str::Utf8Error> {
3912        core::str::from_utf8(self.expose_secret())
3913    }
3914
3915    /// Reveals the secret bytes mutably.
3916    ///
3917    /// This method is intentionally named to make secret access explicit at the
3918    /// call site.
3919    #[must_use]
3920    pub fn expose_secret_mut(&mut self) -> &mut [u8] {
3921        &mut self.bytes
3922    }
3923
3924    /// Consumes the wrapper and returns owned secret bytes.
3925    ///
3926    /// This is an explicit escape hatch for interop with APIs that require an
3927    /// owned vector-like value. The returned [`ExposedSecretVec`] remains
3928    /// redacted by formatting and clears its vector on drop.
3929    #[must_use]
3930    pub fn into_exposed_vec(mut self) -> ExposedSecretVec {
3931        ExposedSecretVec::from_vec(core::mem::take(&mut self.bytes))
3932    }
3933
3934    /// Consumes the wrapper and returns the owned secret bytes as UTF-8 text.
3935    ///
3936    /// This is an explicit escape hatch for interop with APIs that require an
3937    /// owned string-like value. The returned [`ExposedSecretString`] remains
3938    /// redacted by formatting and clears its heap allocation on drop.
3939    ///
3940    /// If the secret bytes are not valid UTF-8, the original redacted wrapper
3941    /// is returned unchanged.
3942    #[must_use = "handle invalid UTF-8 errors and keep the returned wrapper protected"]
3943    pub fn try_into_exposed_string(self) -> Result<ExposedSecretString, Self> {
3944        if core::str::from_utf8(self.expose_secret()).is_err() {
3945            return Err(self);
3946        }
3947
3948        // Security invariant: do not add fallible or allocating work between
3949        // taking `exposed.bytes` and wrapping it as `ExposedSecretString`.
3950        // During that narrow move-only window the bytes are temporarily in a
3951        // plain `Vec<u8>`.
3952        let mut exposed = self.into_exposed_vec();
3953        let bytes = core::mem::take(&mut exposed.bytes);
3954        drop(exposed);
3955        Ok(ExposedSecretString::from_string(
3956            string_from_validated_secret_bytes(bytes),
3957        ))
3958    }
3959
3960    /// Compares this secret to `other` without short-circuiting on the first
3961    /// differing byte.
3962    ///
3963    /// Length and the final equality result remain public. Different lengths
3964    /// return `false` immediately; use this helper only when the compared
3965    /// lengths are public protocol facts or have been normalized by the
3966    /// caller. For equal-length inputs, this helper scans every byte before
3967    /// returning. It is constant-time-oriented best effort, not a formal
3968    /// cryptographic constant-time guarantee. This comparison is deliberately
3969    /// explicit: redacted buffer types do not implement [`PartialEq`] because
3970    /// `==` would make a best-effort helper look like a formal token/MAC
3971    /// comparison primitive.
3972    ///
3973    /// Do not use this helper as the sole MAC, bearer-token, password-hash, or
3974    /// authentication-secret comparison primitive in high-assurance systems.
3975    /// Applications that can admit dependencies should use a reviewed
3976    /// constant-time comparison primitive, such as `subtle`, at the protocol
3977    /// boundary.
3978    #[doc(alias = "constant_time_eq")]
3979    #[must_use]
3980    pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
3981        constant_time_eq_public_len(self.expose_secret(), other)
3982    }
3983
3984    /// Clears the initialized bytes and makes the buffer empty.
3985    pub fn clear(&mut self) {
3986        wipe_vec_all(&mut self.bytes);
3987        self.bytes.clear();
3988    }
3989}
3990
3991#[cfg(feature = "alloc")]
3992impl core::fmt::Debug for SecretBuffer {
3993    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3994        formatter
3995            .debug_struct("SecretBuffer")
3996            .field("bytes", &"<redacted>")
3997            .field("len", &self.len())
3998            .finish()
3999    }
4000}
4001
4002#[cfg(feature = "alloc")]
4003impl core::fmt::Display for SecretBuffer {
4004    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
4005        formatter.write_str("<redacted>")
4006    }
4007}
4008
4009#[cfg(feature = "alloc")]
4010impl Drop for SecretBuffer {
4011    fn drop(&mut self) {
4012        wipe_vec_all(&mut self.bytes);
4013    }
4014}
4015
4016#[cfg(feature = "alloc")]
4017impl From<alloc::vec::Vec<u8>> for SecretBuffer {
4018    /// Wraps an owned vector as sensitive material.
4019    ///
4020    /// Spare capacity is cleared immediately before the vector is stored.
4021    /// Use [`SecretBuffer::from_slice`] when the source data is borrowed.
4022    fn from(bytes: alloc::vec::Vec<u8>) -> Self {
4023        Self::from_vec(bytes)
4024    }
4025}
4026
4027#[cfg(feature = "alloc")]
4028impl From<alloc::string::String> for SecretBuffer {
4029    /// Wraps an owned UTF-8 string as sensitive material.
4030    ///
4031    /// The string is consumed without copying its initialized bytes. Spare
4032    /// vector capacity is cleared immediately before the bytes are stored.
4033    fn from(text: alloc::string::String) -> Self {
4034        Self::from_vec(text.into_bytes())
4035    }
4036}
4037
4038#[cfg(feature = "alloc")]
4039impl<const CAP: usize> From<EncodedBuffer<CAP>> for SecretBuffer {
4040    /// Copies visible encoded bytes from a stack-backed buffer into an owned
4041    /// redacted buffer.
4042    ///
4043    /// The consumed stack-backed buffer clears its backing array when it is
4044    /// dropped at the end of the conversion.
4045    fn from(buffer: EncodedBuffer<CAP>) -> Self {
4046        Self::from_slice(buffer.as_bytes())
4047    }
4048}
4049
4050#[cfg(feature = "alloc")]
4051impl<const CAP: usize> From<DecodedBuffer<CAP>> for SecretBuffer {
4052    /// Copies visible decoded bytes from a stack-backed buffer into an owned
4053    /// redacted buffer.
4054    ///
4055    /// The consumed stack-backed buffer clears its backing array when it is
4056    /// dropped at the end of the conversion.
4057    fn from(buffer: DecodedBuffer<CAP>) -> Self {
4058        Self::from_slice(buffer.as_bytes())
4059    }
4060}
4061
4062#[cfg(feature = "alloc")]
4063impl TryFrom<&[u8]> for SecretBuffer {
4064    type Error = DecodeError;
4065
4066    /// Decodes strict standard padded Base64 into a redacted owned buffer.
4067    ///
4068    /// Use [`Engine::decode_secret`] or [`Profile::decode_secret`] when a
4069    /// different alphabet, padding mode, or line-wrapping profile is required.
4070    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
4071        STANDARD.decode_secret(input)
4072    }
4073}
4074
4075#[cfg(feature = "alloc")]
4076impl<const N: usize> TryFrom<&[u8; N]> for SecretBuffer {
4077    type Error = DecodeError;
4078
4079    /// Decodes a strict standard padded Base64 byte array into a redacted
4080    /// owned buffer.
4081    ///
4082    /// Use [`Engine::decode_secret`] or [`Profile::decode_secret`] when a
4083    /// different alphabet, padding mode, or line-wrapping profile is required.
4084    fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
4085        Self::try_from(&input[..])
4086    }
4087}
4088
4089#[cfg(feature = "alloc")]
4090impl TryFrom<&str> for SecretBuffer {
4091    type Error = DecodeError;
4092
4093    /// Decodes strict standard padded Base64 text into a redacted owned buffer.
4094    ///
4095    /// Use [`Engine::decode_secret`] or [`Profile::decode_secret`] when a
4096    /// different alphabet, padding mode, or line-wrapping profile is required.
4097    fn try_from(input: &str) -> Result<Self, Self::Error> {
4098        Self::try_from(input.as_bytes())
4099    }
4100}
4101
4102#[cfg(feature = "alloc")]
4103impl core::str::FromStr for SecretBuffer {
4104    type Err = DecodeError;
4105
4106    /// Decodes strict standard padded Base64 text into a redacted owned buffer.
4107    ///
4108    /// Use [`Engine::decode_secret`] or [`Profile::decode_secret`] when a
4109    /// different alphabet, padding mode, or line-wrapping profile is required.
4110    fn from_str(input: &str) -> Result<Self, Self::Err> {
4111        Self::try_from(input)
4112    }
4113}
4114
4115/// A named Base64 profile with an engine and optional strict line wrapping.
4116///
4117/// Profiles are convenience values for protocol-shaped Base64. They keep the
4118/// same strict alphabet, padding, canonical-bit, and output-buffer rules as
4119/// [`Engine`], while carrying the wrapping policy for MIME/PEM-like formats.
4120#[derive(Clone, Copy, Debug, Eq, PartialEq)]
4121pub struct Profile<A, const PAD: bool> {
4122    engine: Engine<A, PAD>,
4123    wrap: Option<LineWrap>,
4124}
4125
4126impl<A, const PAD: bool> Profile<A, PAD>
4127where
4128    A: Alphabet,
4129{
4130    /// Creates a profile from an engine and optional strict line wrapping.
4131    #[must_use]
4132    pub const fn new(engine: Engine<A, PAD>, wrap: Option<LineWrap>) -> Self {
4133        Self { engine, wrap }
4134    }
4135
4136    /// Creates a profile, returning `None` when the wrapping policy is invalid.
4137    ///
4138    /// This is useful when a profile is assembled from configuration or other
4139    /// untrusted metadata. Use [`Self::new`] for compile-time constants where
4140    /// the wrapping policy is known to be valid.
4141    #[must_use]
4142    pub const fn checked_new(engine: Engine<A, PAD>, wrap: Option<LineWrap>) -> Option<Self> {
4143        match wrap {
4144            Some(wrap) if !wrap.is_valid() => None,
4145            _ => Some(Self::new(engine, wrap)),
4146        }
4147    }
4148
4149    /// Returns whether this profile can be used by encoders and decoders.
4150    #[must_use]
4151    pub const fn is_valid(&self) -> bool {
4152        match self.wrap {
4153            Some(wrap) => wrap.is_valid(),
4154            None => true,
4155        }
4156    }
4157
4158    /// Returns the underlying engine.
4159    #[must_use]
4160    pub const fn engine(&self) -> Engine<A, PAD> {
4161        self.engine
4162    }
4163
4164    /// Returns whether this profile uses padded Base64.
4165    #[must_use]
4166    pub const fn is_padded(&self) -> bool {
4167        PAD
4168    }
4169
4170    /// Returns whether this profile carries a strict line-wrapping policy.
4171    #[must_use]
4172    pub const fn is_wrapped(&self) -> bool {
4173        self.wrap.is_some()
4174    }
4175
4176    /// Returns the strict wrapping policy carried by this profile, if any.
4177    #[must_use]
4178    pub const fn line_wrap(&self) -> Option<LineWrap> {
4179        self.wrap
4180    }
4181
4182    /// Returns the encoded line length for wrapped profiles.
4183    #[must_use]
4184    pub const fn line_len(&self) -> Option<usize> {
4185        match self.wrap {
4186            Some(wrap) => Some(wrap.line_len()),
4187            None => None,
4188        }
4189    }
4190
4191    /// Returns the line ending for wrapped profiles.
4192    #[must_use]
4193    pub const fn line_ending(&self) -> Option<LineEnding> {
4194        match self.wrap {
4195            Some(wrap) => Some(wrap.line_ending()),
4196            None => None,
4197        }
4198    }
4199
4200    /// Returns the encoded length for this profile.
4201    pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
4202        match self.wrap {
4203            Some(wrap) => wrapped_encoded_len(input_len, PAD, wrap),
4204            None => encoded_len(input_len, PAD),
4205        }
4206    }
4207
4208    /// Returns the encoded length for this profile, or `None` on overflow or
4209    /// invalid line wrapping.
4210    #[must_use]
4211    pub const fn checked_encoded_len(&self, input_len: usize) -> Option<usize> {
4212        match self.wrap {
4213            Some(wrap) => checked_wrapped_encoded_len(input_len, PAD, wrap),
4214            None => checked_encoded_len(input_len, PAD),
4215        }
4216    }
4217
4218    /// Returns the exact decoded length for this profile.
4219    pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
4220        match self.wrap {
4221            Some(wrap) => self.engine.decoded_len_wrapped(input, wrap),
4222            None => self.engine.decoded_len(input),
4223        }
4224    }
4225
4226    /// Validates input according to this profile without writing decoded bytes.
4227    pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
4228        match self.wrap {
4229            Some(wrap) => self.engine.validate_wrapped_result(input, wrap),
4230            None => self.engine.validate_result(input),
4231        }
4232    }
4233
4234    /// Returns whether `input` is valid for this profile.
4235    #[must_use]
4236    pub fn validate(&self, input: &[u8]) -> bool {
4237        self.validate_result(input).is_ok()
4238    }
4239
4240    /// Encodes `input` into `output` according to this profile.
4241    pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
4242        match self.wrap {
4243            Some(wrap) => self.engine.encode_slice_wrapped(input, output, wrap),
4244            None => self.engine.encode_slice(input, output),
4245        }
4246    }
4247
4248    /// Encodes `input` into `output` and clears all bytes after the encoded
4249    /// prefix.
4250    pub fn encode_slice_clear_tail(
4251        &self,
4252        input: &[u8],
4253        output: &mut [u8],
4254    ) -> Result<usize, EncodeError> {
4255        match self.wrap {
4256            Some(wrap) => self
4257                .engine
4258                .encode_slice_wrapped_clear_tail(input, output, wrap),
4259            None => self.engine.encode_slice_clear_tail(input, output),
4260        }
4261    }
4262
4263    /// Encodes `input` into a stack-backed buffer.
4264    ///
4265    /// This is useful for short values where heap allocation is unnecessary.
4266    /// If encoding fails, the internal backing array is cleared before the
4267    /// error is returned.
4268    pub fn encode_buffer<const CAP: usize>(
4269        &self,
4270        input: &[u8],
4271    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
4272        let mut output = EncodedBuffer::new();
4273        let written = match self.encode_slice_clear_tail(input, &mut output.bytes) {
4274            Ok(written) => written,
4275            Err(err) => {
4276                output.clear();
4277                return Err(err);
4278            }
4279        };
4280        output.len = written;
4281        Ok(output)
4282    }
4283
4284    /// Decodes `input` into `output` according to this profile.
4285    ///
4286    /// # Security
4287    ///
4288    /// Profile decoders use the normal strict decode path. They may branch or
4289    /// return early based on malformed input, padding position, wrapping, and
4290    /// output capacity in order to return precise [`DecodeError`] diagnostics,
4291    /// including exact invalid-byte values and positions.
4292    /// Do not use this method for token comparison, key-material decoding, or
4293    /// secret-bearing validation where malformed-input timing matters. Use
4294    /// [`crate::ct`] with a matching unwrapped engine for constant-time-oriented
4295    /// secret decoding.
4296    #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
4297    pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
4298        match self.wrap {
4299            Some(wrap) => self.engine.decode_slice_wrapped(input, output, wrap),
4300            None => self.engine.decode_slice(input, output),
4301        }
4302    }
4303
4304    /// Decodes `input` into `output` and clears all bytes after the decoded
4305    /// prefix.
4306    pub fn decode_slice_clear_tail(
4307        &self,
4308        input: &[u8],
4309        output: &mut [u8],
4310    ) -> Result<usize, DecodeError> {
4311        match self.wrap {
4312            Some(wrap) => self
4313                .engine
4314                .decode_slice_wrapped_clear_tail(input, output, wrap),
4315            None => self.engine.decode_slice_clear_tail(input, output),
4316        }
4317    }
4318
4319    /// Decodes `input` into a stack-backed buffer according to this profile.
4320    ///
4321    /// This is useful for short decoded values where heap allocation is
4322    /// unnecessary. If decoding fails, the internal backing array is cleared
4323    /// before the error is returned.
4324    pub fn decode_buffer<const CAP: usize>(
4325        &self,
4326        input: &[u8],
4327    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
4328        let mut output = DecodedBuffer::new();
4329        let written = match self.decode_slice_clear_tail(input, &mut output.bytes) {
4330            Ok(written) => written,
4331            Err(err) => {
4332                output.clear();
4333                return Err(err);
4334            }
4335        };
4336        output.len = written;
4337        Ok(output)
4338    }
4339
4340    /// Decodes `buffer` in place according to this profile.
4341    ///
4342    /// For wrapped profiles, configured line endings are compacted out before
4343    /// decoding. If validation fails, the buffer contents are unspecified.
4344    /// On success, bytes after the returned decoded prefix may retain compacted
4345    /// encoded input. Use [`Self::decode_in_place_clear_tail`] when the buffer
4346    /// may be reused or freed without a caller-managed wipe.
4347    ///
4348    /// # Security
4349    ///
4350    /// Profile in-place decoders use the normal strict decode path. They may
4351    /// branch or return early based on malformed input, padding position,
4352    /// wrapping, and output capacity in order to return precise
4353    /// [`DecodeError`] diagnostics. Do not use this method for token
4354    /// comparison, key-material decoding, or secret-bearing validation where
4355    /// malformed-input timing matters.
4356    ///
4357    /// # Examples
4358    ///
4359    /// ```
4360    /// use base64_ng::{LineEnding, LineWrap, Profile, STANDARD};
4361    ///
4362    /// let profile = Profile::new(STANDARD, Some(LineWrap::new(4, LineEnding::Lf)));
4363    /// let mut buffer = *b"aGVs\nbG8=";
4364    /// let decoded = profile.decode_in_place(&mut buffer).unwrap();
4365    ///
4366    /// assert_eq!(decoded, b"hello");
4367    /// ```
4368    pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
4369        match self.wrap {
4370            Some(wrap) => self.engine.decode_in_place_wrapped(buffer, wrap),
4371            None => self.engine.decode_in_place(buffer),
4372        }
4373    }
4374
4375    /// Decodes `buffer` in place according to this profile and clears all
4376    /// bytes after the decoded prefix.
4377    ///
4378    /// If validation or decoding fails, the entire buffer is cleared before the
4379    /// error is returned.
4380    ///
4381    /// # Examples
4382    ///
4383    /// ```
4384    /// use base64_ng::{LineEnding, LineWrap, Profile, STANDARD};
4385    ///
4386    /// let profile = Profile::new(STANDARD, Some(LineWrap::new(4, LineEnding::Lf)));
4387    /// let mut buffer = *b"aGVs\nbG8=";
4388    /// let len = profile.decode_in_place_clear_tail(&mut buffer).unwrap().len();
4389    ///
4390    /// assert_eq!(&buffer[..len], b"hello");
4391    /// assert!(buffer[len..].iter().all(|byte| *byte == 0));
4392    /// ```
4393    pub fn decode_in_place_clear_tail<'a>(
4394        &self,
4395        buffer: &'a mut [u8],
4396    ) -> Result<&'a mut [u8], DecodeError> {
4397        match self.wrap {
4398            Some(wrap) => self.engine.decode_in_place_wrapped_clear_tail(buffer, wrap),
4399            None => self.engine.decode_in_place_clear_tail(buffer),
4400        }
4401    }
4402
4403    /// Encodes `input` into a newly allocated byte vector.
4404    #[cfg(feature = "alloc")]
4405    #[must_use = "for secret-bearing payloads use encode_secret, which returns a redacted buffer with drop-time cleanup"]
4406    pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
4407        match self.wrap {
4408            Some(wrap) => self.engine.encode_wrapped_vec(input, wrap),
4409            None => self.engine.encode_vec(input),
4410        }
4411    }
4412
4413    /// Encodes `input` into a redacted owned secret buffer.
4414    #[cfg(feature = "alloc")]
4415    pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
4416        self.encode_vec(input).map(SecretBuffer::from_vec)
4417    }
4418
4419    /// Encodes `input` into a newly allocated UTF-8 string.
4420    #[cfg(feature = "alloc")]
4421    pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
4422        match self.wrap {
4423            Some(wrap) => self.engine.encode_wrapped_string(input, wrap),
4424            None => self.engine.encode_string(input),
4425        }
4426    }
4427
4428    /// Decodes `input` into a newly allocated byte vector.
4429    #[cfg(feature = "alloc")]
4430    #[must_use = "for secret-bearing payloads use decode_secret, which returns a redacted buffer with drop-time cleanup"]
4431    pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
4432        match self.wrap {
4433            Some(wrap) => self.engine.decode_wrapped_vec(input, wrap),
4434            None => self.engine.decode_vec(input),
4435        }
4436    }
4437
4438    /// Decodes `input` into a redacted owned secret buffer.
4439    #[cfg(feature = "alloc")]
4440    pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
4441        self.decode_vec(input).map(SecretBuffer::from_vec)
4442    }
4443}
4444
4445impl<A, const PAD: bool> Default for Profile<A, PAD>
4446where
4447    A: Alphabet,
4448{
4449    fn default() -> Self {
4450        Self::new(Engine::new(), None)
4451    }
4452}
4453
4454impl<A, const PAD: bool> core::fmt::Display for Profile<A, PAD>
4455where
4456    A: Alphabet,
4457{
4458    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
4459        match self.wrap {
4460            Some(wrap) => write!(formatter, "padded={PAD} wrap={wrap}"),
4461            None => write!(formatter, "padded={PAD} wrap=none"),
4462        }
4463    }
4464}
4465
4466impl<A, const PAD: bool> From<Engine<A, PAD>> for Profile<A, PAD>
4467where
4468    A: Alphabet,
4469{
4470    fn from(engine: Engine<A, PAD>) -> Self {
4471        Self::new(engine, None)
4472    }
4473}
4474
4475/// MIME Base64 profile: standard alphabet, padding, 76-column CRLF wrapping.
4476///
4477/// This profile uses the default strict decoder and is not a constant-time
4478/// token validator or key-material decoder. Use [`ct::STANDARD`] with an
4479/// application-level wrapping policy for sensitive fixed-shape protocols.
4480#[doc(alias = "ct")]
4481#[doc(alias = "constant_time")]
4482#[doc(alias = "sensitive")]
4483pub const MIME: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::MIME));
4484
4485/// PEM Base64 profile: standard alphabet, padding, 64-column LF wrapping.
4486///
4487/// This profile uses the default strict decoder and is not a constant-time
4488/// token validator or key-material decoder. Use [`ct::STANDARD`] with an
4489/// application-level wrapping policy for sensitive fixed-shape protocols.
4490#[doc(alias = "ct")]
4491#[doc(alias = "constant_time")]
4492#[doc(alias = "sensitive")]
4493pub const PEM: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::PEM));
4494
4495/// PEM Base64 profile with CRLF line endings.
4496///
4497/// This profile uses the default strict decoder and is not a constant-time
4498/// token validator or key-material decoder. Use [`ct::STANDARD`] with an
4499/// application-level wrapping policy for sensitive fixed-shape protocols.
4500#[doc(alias = "ct")]
4501#[doc(alias = "constant_time")]
4502#[doc(alias = "sensitive")]
4503pub const PEM_CRLF: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::PEM_CRLF));
4504
4505/// bcrypt-style no-padding Base64 profile.
4506///
4507/// This profile carries the bcrypt alphabet and no padding. It does not parse
4508/// complete bcrypt password-hash strings. Its default strict decoder is not a
4509/// constant-time token validator or key-material decoder; use
4510/// [`Profile::engine`] with [`Engine::ct_decoder`] for the matching
4511/// constant-time-oriented decoder when timing posture matters.
4512#[doc(alias = "ct")]
4513#[doc(alias = "constant_time")]
4514#[doc(alias = "sensitive")]
4515pub const BCRYPT: Profile<Bcrypt, false> = Profile::new(BCRYPT_NO_PAD, None);
4516
4517/// Unix `crypt(3)`-style no-padding Base64 profile.
4518///
4519/// This profile carries the `crypt(3)` alphabet and no padding. It does not
4520/// parse complete password-hash strings. Its default strict decoder is not a
4521/// constant-time token validator or key-material decoder; use
4522/// [`Profile::engine`] with [`Engine::ct_decoder`] for the matching
4523/// constant-time-oriented decoder when timing posture matters.
4524#[doc(alias = "ct")]
4525#[doc(alias = "constant_time")]
4526#[doc(alias = "sensitive")]
4527pub const CRYPT: Profile<Crypt, false> = Profile::new(CRYPT_NO_PAD, None);
4528
4529/// Returns the encoded length for an input length and padding policy.
4530///
4531/// This function returns [`EncodeError::LengthOverflow`] instead of panicking.
4532/// Use [`checked_encoded_len`] when an `Option<usize>` is more convenient.
4533///
4534/// # Examples
4535///
4536/// ```
4537/// use base64_ng::encoded_len;
4538///
4539/// assert_eq!(encoded_len(5, true).unwrap(), 8);
4540/// assert_eq!(encoded_len(5, false).unwrap(), 7);
4541/// assert!(encoded_len(usize::MAX, true).is_err());
4542/// ```
4543pub const fn encoded_len(input_len: usize, padded: bool) -> Result<usize, EncodeError> {
4544    match checked_encoded_len(input_len, padded) {
4545        Some(len) => Ok(len),
4546        None => Err(EncodeError::LengthOverflow),
4547    }
4548}
4549
4550/// Returns the encoded length after applying a line wrapping policy.
4551///
4552/// The returned length includes inserted line endings but does not include a
4553/// trailing line ending after the final encoded line.
4554///
4555/// # Examples
4556///
4557/// ```
4558/// use base64_ng::{LineEnding, LineWrap, wrapped_encoded_len};
4559///
4560/// let wrap = LineWrap::new(4, LineEnding::Lf);
4561/// assert_eq!(wrapped_encoded_len(5, true, wrap).unwrap(), 9);
4562/// ```
4563pub const fn wrapped_encoded_len(
4564    input_len: usize,
4565    padded: bool,
4566    wrap: LineWrap,
4567) -> Result<usize, EncodeError> {
4568    if wrap.line_len == 0 {
4569        return Err(EncodeError::InvalidLineWrap { line_len: 0 });
4570    }
4571
4572    let Some(encoded) = checked_encoded_len(input_len, padded) else {
4573        return Err(EncodeError::LengthOverflow);
4574    };
4575    if encoded == 0 {
4576        return Ok(0);
4577    }
4578
4579    let breaks = (encoded - 1) / wrap.line_len;
4580    let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
4581        return Err(EncodeError::LengthOverflow);
4582    };
4583    match encoded.checked_add(line_ending_bytes) {
4584        Some(len) => Ok(len),
4585        None => Err(EncodeError::LengthOverflow),
4586    }
4587}
4588
4589/// Returns the encoded length after line wrapping, or `None` on overflow or
4590/// invalid line wrapping.
4591///
4592/// The returned length includes inserted line endings but does not include a
4593/// trailing line ending after the final encoded line.
4594///
4595/// # Examples
4596///
4597/// ```
4598/// use base64_ng::{LineEnding, LineWrap, checked_wrapped_encoded_len};
4599///
4600/// let wrap = LineWrap::new(4, LineEnding::Lf);
4601/// assert_eq!(checked_wrapped_encoded_len(5, true, wrap), Some(9));
4602/// assert_eq!(LineWrap::checked_new(0, LineEnding::Lf), None);
4603/// ```
4604#[must_use]
4605pub const fn checked_wrapped_encoded_len(
4606    input_len: usize,
4607    padded: bool,
4608    wrap: LineWrap,
4609) -> Option<usize> {
4610    if wrap.line_len == 0 {
4611        return None;
4612    }
4613
4614    let Some(encoded) = checked_encoded_len(input_len, padded) else {
4615        return None;
4616    };
4617    if encoded == 0 {
4618        return Some(0);
4619    }
4620
4621    let breaks = (encoded - 1) / wrap.line_len;
4622    let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
4623        return None;
4624    };
4625    encoded.checked_add(line_ending_bytes)
4626}
4627
4628/// Returns the encoded length, or `None` if it would overflow `usize`.
4629///
4630/// # Examples
4631///
4632/// ```
4633/// use base64_ng::checked_encoded_len;
4634///
4635/// assert_eq!(checked_encoded_len(5, true), Some(8));
4636/// assert_eq!(checked_encoded_len(usize::MAX, true), None);
4637/// ```
4638#[must_use]
4639pub const fn checked_encoded_len(input_len: usize, padded: bool) -> Option<usize> {
4640    let groups = input_len / 3;
4641    if groups > usize::MAX / 4 {
4642        return None;
4643    }
4644    let full = groups * 4;
4645    let rem = input_len % 3;
4646    if rem == 0 {
4647        Some(full)
4648    } else if padded {
4649        full.checked_add(4)
4650    } else {
4651        full.checked_add(rem + 1)
4652    }
4653}
4654
4655/// Compares two fixed-width byte arrays without a length-mismatch branch.
4656///
4657/// Use this helper when the value length itself should not be represented as a
4658/// timing-distinct branch in the comparison API. The array length `N` is a
4659/// compile-time public type fact, and the helper scans exactly `N` bytes before
4660/// returning. The final equality result remains public. This is still a
4661/// dependency-free, constant-time-oriented best-effort helper, not a formally
4662/// verified cryptographic comparison primitive.
4663///
4664/// # Examples
4665///
4666/// ```
4667/// use base64_ng::constant_time_eq_fixed_width;
4668///
4669/// assert!(constant_time_eq_fixed_width(b"token", b"token"));
4670/// assert!(!constant_time_eq_fixed_width(b"token", b"Token"));
4671/// ```
4672#[must_use]
4673pub fn constant_time_eq_fixed_width<const N: usize>(left: &[u8; N], right: &[u8; N]) -> bool {
4674    constant_time_eq_fixed_width_array(left, right)
4675}
4676
4677/// Returns the maximum decoded length for an encoded input length.
4678///
4679/// # Examples
4680///
4681/// ```
4682/// use base64_ng::decoded_capacity;
4683///
4684/// assert_eq!(decoded_capacity(8), 6);
4685/// assert_eq!(decoded_capacity(7), 5);
4686/// ```
4687#[must_use]
4688pub const fn decoded_capacity(encoded_len: usize) -> usize {
4689    let rem = encoded_len % 4;
4690    encoded_len / 4 * 3
4691        + if rem == 2 {
4692            1
4693        } else if rem == 3 {
4694            2
4695        } else {
4696            0
4697        }
4698}
4699
4700/// Returns the exact decoded length implied by input length and padding.
4701///
4702/// This validates padding placement and impossible lengths, but it does not
4703/// validate alphabet membership or non-canonical trailing bits.
4704///
4705/// # Examples
4706///
4707/// ```
4708/// use base64_ng::decoded_len;
4709///
4710/// assert_eq!(decoded_len(b"aGVsbG8=", true).unwrap(), 5);
4711/// assert_eq!(decoded_len(b"aGVsbG8", false).unwrap(), 5);
4712/// ```
4713pub fn decoded_len(input: &[u8], padded: bool) -> Result<usize, DecodeError> {
4714    if padded {
4715        decoded_len_padded(input)
4716    } else {
4717        decoded_len_unpadded(input)
4718    }
4719}
4720
4721/// Defines a custom [`Alphabet`] from a 64-byte string literal.
4722///
4723/// The generated alphabet is validated at compile time with
4724/// [`validate_alphabet`]. Invalid, duplicate, or padding bytes fail the build
4725/// instead of creating a malformed runtime profile.
4726///
4727/// The generated implementation uses the conservative default
4728/// [`Alphabet::encode`] behavior: every emitted Base64 byte performs a fixed
4729/// 64-entry scan to avoid secret-indexed table lookups. Built-in alphabets use
4730/// optimized arithmetic mappers.
4731///
4732/// The generated [`Alphabet::decode`] implementation delegates to
4733/// [`decode_alphabet_byte`]. The constant-time-oriented [`ct`] module scans the
4734/// generated `ENCODE` table directly and does not call the generated `decode`
4735/// method.
4736///
4737/// # Examples
4738///
4739/// ```
4740/// base64_ng::define_alphabet! {
4741///     struct DotSlash = b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
4742/// }
4743///
4744/// let engine = base64_ng::Engine::<DotSlash, false>::new();
4745/// let mut encoded = [0u8; 4];
4746/// let written = engine.encode_slice(&[0xff, 0xff, 0xff], &mut encoded).unwrap();
4747/// assert_eq!(&encoded[..written], b"9999");
4748/// ```
4749///
4750/// Invalid alphabets fail during compilation:
4751///
4752/// ```compile_fail
4753/// base64_ng::define_alphabet! {
4754///     struct Bad = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
4755/// }
4756/// ```
4757#[macro_export]
4758macro_rules! define_alphabet {
4759    ($(#[$meta:meta])* $vis:vis struct $name:ident = $encode:expr;) => {
4760        $(#[$meta])*
4761        #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
4762        $vis struct $name;
4763
4764        impl $crate::Alphabet for $name {
4765            const ENCODE: [u8; 64] = *$encode;
4766
4767            #[inline]
4768            fn decode(byte: u8) -> Option<u8> {
4769                $crate::decode_alphabet_byte(byte, &Self::ENCODE)
4770            }
4771        }
4772
4773        const _: [(); 1] = [(); match $crate::validate_alphabet(
4774            &<$name as $crate::Alphabet>::ENCODE,
4775        ) {
4776            Ok(()) => 1,
4777            Err(_) => 0,
4778        }];
4779    };
4780}
4781
4782/// Validates a 64-byte Base64 alphabet table.
4783///
4784/// A valid alphabet must contain exactly 64 unique visible ASCII bytes and must
4785/// not contain the padding byte `=`.
4786///
4787/// # Examples
4788///
4789/// ```
4790/// use base64_ng::{Alphabet, Standard, validate_alphabet};
4791///
4792/// validate_alphabet(&Standard::ENCODE).unwrap();
4793/// ```
4794pub const fn validate_alphabet(encode: &[u8; 64]) -> Result<(), AlphabetError> {
4795    let mut index = 0;
4796    while index < encode.len() {
4797        let byte = encode[index];
4798        if !is_visible_ascii(byte) {
4799            return Err(AlphabetError::InvalidByte { index, byte });
4800        }
4801        if byte == b'=' {
4802            return Err(AlphabetError::PaddingByte { index });
4803        }
4804
4805        let mut duplicate = index + 1;
4806        while duplicate < encode.len() {
4807            if encode[duplicate] == byte {
4808                return Err(AlphabetError::DuplicateByte {
4809                    first: index,
4810                    second: duplicate,
4811                    byte,
4812                });
4813            }
4814            duplicate += 1;
4815        }
4816
4817        index += 1;
4818    }
4819
4820    Ok(())
4821}
4822
4823/// Decodes one byte by scanning a caller-provided alphabet table.
4824///
4825/// This helper is intended for custom [`Alphabet`] implementations. Validate
4826/// the table with [`validate_alphabet`] before trusting the alphabet in a
4827/// protocol or public API. The scan always visits all 64 entries before
4828/// returning so the match position does not create an early-return timing
4829/// signal in custom alphabet decoders.
4830///
4831/// # Examples
4832///
4833/// ```
4834/// use base64_ng::{Alphabet, decode_alphabet_byte};
4835///
4836/// struct DotSlash;
4837///
4838/// impl Alphabet for DotSlash {
4839///     const ENCODE: [u8; 64] =
4840///         *b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
4841///
4842///     fn decode(byte: u8) -> Option<u8> {
4843///         decode_alphabet_byte(byte, &Self::ENCODE)
4844///     }
4845/// }
4846///
4847/// assert_eq!(DotSlash::decode(b'.'), Some(0));
4848/// assert_eq!(DotSlash::decode(b'9'), Some(63));
4849/// ```
4850#[must_use]
4851pub const fn decode_alphabet_byte(byte: u8, encode: &[u8; 64]) -> Option<u8> {
4852    let mut index = 0;
4853    let mut candidate = 0;
4854    let mut decoded = 0;
4855    let mut valid = 0;
4856    while index < encode.len() {
4857        let matches = ct_mask_eq_u8(byte, encode[index]);
4858        decoded |= candidate & matches;
4859        valid |= matches;
4860        index += 1;
4861        candidate += 1;
4862    }
4863
4864    if valid == 0 { None } else { Some(decoded) }
4865}
4866
4867/// A Base64 alphabet.
4868///
4869/// # Security
4870///
4871/// The default [`Alphabet::encode`] implementation is constant-time-oriented:
4872/// it scans all 64 alphabet entries instead of using `ENCODE[value as usize]`.
4873/// If an implementation overrides `encode` with a direct table lookup, normal
4874/// [`Engine`] encoding becomes timing-sensitive with respect to the emitted
4875/// 6-bit value.
4876///
4877/// The normal strict decode path calls [`Alphabet::decode`] and is not a
4878/// constant-time decoder. The [`ct`] module does not call
4879/// [`Alphabet::decode`]; it scans [`Alphabet::ENCODE`] directly with its own
4880/// fixed 64-entry mapper. A custom non-constant-time `decode` implementation
4881/// therefore affects normal strict decode diagnostics and timing, but not the
4882/// `ct` module's symbol-mapping loop.
4883pub trait Alphabet {
4884    /// Encoding table indexed by 6-bit values.
4885    const ENCODE: [u8; 64];
4886
4887    /// Encode one 6-bit value into an alphabet byte.
4888    ///
4889    /// The default implementation scans the alphabet table instead of using a
4890    /// secret-indexed table lookup. Built-in alphabets override this with the
4891    /// branch-minimized ASCII arithmetic mapper. Custom alphabets that keep the
4892    /// default method prioritize timing posture over throughput: every emitted
4893    /// Base64 byte performs a fixed 64-entry scan. For massive payloads with
4894    /// user-defined alphabets, profile this cost and consider an audited custom
4895    /// override only if the alphabet has a structure that can be mapped without
4896    /// secret-indexed table access.
4897    #[must_use]
4898    fn encode(value: u8) -> u8 {
4899        encode_alphabet_value(value, &Self::ENCODE)
4900    }
4901
4902    /// Decode one byte into a 6-bit value.
4903    ///
4904    /// Implementations that want conservative custom-alphabet timing posture
4905    /// should delegate to [`decode_alphabet_byte`], which scans all 64 entries
4906    /// before returning. The `ct` module ignores this method and scans
4907    /// [`Self::ENCODE`] directly.
4908    fn decode(byte: u8) -> Option<u8>;
4909}
4910
4911const fn is_visible_ascii(byte: u8) -> bool {
4912    byte >= 0x21 && byte <= 0x7e
4913}
4914
4915/// The RFC 4648 standard Base64 alphabet.
4916#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
4917pub struct Standard;
4918
4919impl Alphabet for Standard {
4920    const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4921
4922    #[inline]
4923    fn encode(value: u8) -> u8 {
4924        encode_ascii_base64(value, Self::ENCODE[62], Self::ENCODE[63])
4925    }
4926
4927    #[inline]
4928    fn decode(byte: u8) -> Option<u8> {
4929        decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
4930    }
4931}
4932
4933/// The RFC 4648 URL-safe Base64 alphabet.
4934#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
4935pub struct UrlSafe;
4936
4937impl Alphabet for UrlSafe {
4938    const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
4939
4940    #[inline]
4941    fn encode(value: u8) -> u8 {
4942        encode_ascii_base64(value, Self::ENCODE[62], Self::ENCODE[63])
4943    }
4944
4945    #[inline]
4946    fn decode(byte: u8) -> Option<u8> {
4947        decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
4948    }
4949}
4950
4951/// The bcrypt Base64 alphabet.
4952///
4953/// This alphabet is commonly used by bcrypt hash strings. It is provided as an
4954/// alphabet/profile building block; `base64-ng` does not parse or verify full
4955/// bcrypt password-hash records.
4956#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
4957pub struct Bcrypt;
4958
4959impl Alphabet for Bcrypt {
4960    const ENCODE: [u8; 64] = *b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
4961
4962    #[inline]
4963    fn decode(byte: u8) -> Option<u8> {
4964        decode_alphabet_byte(byte, &Self::ENCODE)
4965    }
4966}
4967
4968/// The Unix `crypt(3)` Base64 alphabet.
4969///
4970/// This alphabet is provided as an explicit legacy interoperability profile.
4971/// `base64-ng` does not parse or verify complete password-hash records.
4972#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
4973pub struct Crypt;
4974
4975impl Alphabet for Crypt {
4976    const ENCODE: [u8; 64] = *b"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
4977
4978    #[inline]
4979    fn decode(byte: u8) -> Option<u8> {
4980        decode_alphabet_byte(byte, &Self::ENCODE)
4981    }
4982}
4983
4984#[inline]
4985const fn encode_base64_value<A: Alphabet>(value: u8) -> u8 {
4986    encode_alphabet_value(value, &A::ENCODE)
4987}
4988
4989#[inline]
4990fn encode_base64_value_runtime<A: Alphabet>(value: u8) -> u8 {
4991    A::encode(value)
4992}
4993
4994#[inline]
4995const fn encode_alphabet_value(value: u8, encode: &[u8; 64]) -> u8 {
4996    let mut output = 0;
4997    let mut index = 0;
4998    let mut candidate = 0;
4999    while index < encode.len() {
5000        output |= encode[index] & ct_mask_eq_u8(value, candidate);
5001        index += 1;
5002        candidate += 1;
5003    }
5004    output
5005}
5006
5007#[inline]
5008const fn encode_ascii_base64(value: u8, value_62_byte: u8, value_63_byte: u8) -> u8 {
5009    let upper = ct_mask_lt_u8(value, 26);
5010    let lower = ct_mask_lt_u8(value.wrapping_sub(26), 26);
5011    let digit = ct_mask_lt_u8(value.wrapping_sub(52), 10);
5012    let value_62 = ct_mask_eq_u8(value, 0x3e);
5013    let value_63 = ct_mask_eq_u8(value, 0x3f);
5014
5015    (value.wrapping_add(b'A') & upper)
5016        | (value.wrapping_sub(26).wrapping_add(b'a') & lower)
5017        | (value.wrapping_sub(52).wrapping_add(b'0') & digit)
5018        | (value_62_byte & value_62)
5019        | (value_63_byte & value_63)
5020}
5021
5022#[inline]
5023fn decode_ascii_base64(byte: u8, value_62_byte: u8, value_63_byte: u8) -> Option<u8> {
5024    let upper = ct_mask_lt_u8(byte.wrapping_sub(b'A'), 26);
5025    let lower = ct_mask_lt_u8(byte.wrapping_sub(b'a'), 26);
5026    let digit = ct_mask_lt_u8(byte.wrapping_sub(b'0'), 10);
5027    let value_62 = ct_mask_eq_u8(byte, value_62_byte);
5028    let value_63 = ct_mask_eq_u8(byte, value_63_byte);
5029    let valid = upper | lower | digit | value_62 | value_63;
5030
5031    let decoded = (byte.wrapping_sub(b'A') & upper)
5032        | (byte.wrapping_sub(b'a').wrapping_add(26) & lower)
5033        | (byte.wrapping_sub(b'0').wrapping_add(52) & digit)
5034        | (0x3e & value_62)
5035        | (0x3f & value_63);
5036
5037    if valid == 0 { None } else { Some(decoded) }
5038}
5039
5040#[inline]
5041const fn ct_mask_bit(bit: u8) -> u8 {
5042    0u8.wrapping_sub(bit & 1)
5043}
5044
5045#[inline]
5046const fn ct_mask_nonzero_u8(value: u8) -> u8 {
5047    let wide = value as u16;
5048    let negative = 0u16.wrapping_sub(wide);
5049    let nonzero = ((wide | negative) >> 8) as u8;
5050    ct_mask_bit(nonzero)
5051}
5052
5053#[inline]
5054const fn ct_mask_eq_u8(left: u8, right: u8) -> u8 {
5055    !ct_mask_nonzero_u8(left ^ right)
5056}
5057
5058#[inline]
5059const fn ct_mask_lt_u8(left: u8, right: u8) -> u8 {
5060    let diff = (left as u16).wrapping_sub(right as u16);
5061    ct_mask_bit((diff >> 8) as u8)
5062}
5063
5064#[inline(never)]
5065fn constant_time_eq_public_len(left: &[u8], right: &[u8]) -> bool {
5066    if left.len() != right.len() {
5067        return false;
5068    }
5069
5070    constant_time_eq_same_len(left, right)
5071}
5072
5073#[inline(never)]
5074fn constant_time_eq_fixed_width_array<const N: usize>(left: &[u8; N], right: &[u8; N]) -> bool {
5075    constant_time_eq_same_len(left, right)
5076}
5077
5078#[inline(never)]
5079#[allow(unsafe_code)]
5080fn constant_time_eq_same_len(left: &[u8], right: &[u8]) -> bool {
5081    let mut diff = 0u8;
5082    for (left, right) in left.iter().zip(right) {
5083        diff = core::hint::black_box(
5084            core::hint::black_box(diff) | core::hint::black_box(*left ^ *right),
5085        );
5086        // SAFETY: `diff` is an initialized local `u8`; the volatile read is a
5087        // dependency-free optimizer barrier for the accumulation value and does
5088        // not access caller memory.
5089        diff = unsafe { core::ptr::read_volatile(&raw const diff) };
5090    }
5091    ct_error_gate_barrier(diff, 0);
5092    // SAFETY: `diff` is an initialized local `u8`; this final volatile read
5093    // keeps the public equality comparison dependent on a post-barrier load of
5094    // the accumulated value.
5095    let result = unsafe { core::ptr::read_volatile(&raw const diff) };
5096    result == 0
5097}
5098
5099#[cfg(feature = "alloc")]
5100#[allow(unsafe_code)]
5101fn string_from_validated_secret_bytes(bytes: alloc::vec::Vec<u8>) -> alloc::string::String {
5102    // SAFETY: Callers validate the same byte vector as UTF-8 immediately before
5103    // handing ownership to this helper, and the bytes are not modified between
5104    // validation and conversion. Using the unchecked conversion avoids a
5105    // second fallible conversion while the bytes are outside `SecretBuffer`.
5106    unsafe { alloc::string::String::from_utf8_unchecked(bytes) }
5107}
5108
5109mod backend {
5110    use super::{
5111        Alphabet, DecodeError, EncodeError, checked_encoded_len, decode_padded, decode_unpadded,
5112        encode_base64_value_runtime,
5113    };
5114
5115    pub(super) fn encode_slice<A, const PAD: bool>(
5116        input: &[u8],
5117        output: &mut [u8],
5118    ) -> Result<usize, EncodeError>
5119    where
5120        A: Alphabet,
5121    {
5122        #[cfg(feature = "simd")]
5123        match super::simd::active_backend() {
5124            super::simd::ActiveBackend::Scalar => {}
5125        }
5126
5127        scalar_encode_slice::<A, PAD>(input, output)
5128    }
5129
5130    pub(super) fn decode_slice<A, const PAD: bool>(
5131        input: &[u8],
5132        output: &mut [u8],
5133    ) -> Result<usize, DecodeError>
5134    where
5135        A: Alphabet,
5136    {
5137        #[cfg(feature = "simd")]
5138        match super::simd::active_backend() {
5139            super::simd::ActiveBackend::Scalar => {}
5140        }
5141
5142        scalar_decode_slice::<A, PAD>(input, output)
5143    }
5144
5145    #[cfg(test)]
5146    pub(super) fn scalar_reference_encode_slice<A, const PAD: bool>(
5147        input: &[u8],
5148        output: &mut [u8],
5149    ) -> Result<usize, EncodeError>
5150    where
5151        A: Alphabet,
5152    {
5153        scalar_encode_slice::<A, PAD>(input, output)
5154    }
5155
5156    #[cfg(test)]
5157    pub(super) fn scalar_reference_decode_slice<A, const PAD: bool>(
5158        input: &[u8],
5159        output: &mut [u8],
5160    ) -> Result<usize, DecodeError>
5161    where
5162        A: Alphabet,
5163    {
5164        scalar_decode_slice::<A, PAD>(input, output)
5165    }
5166
5167    fn scalar_encode_slice<A, const PAD: bool>(
5168        input: &[u8],
5169        output: &mut [u8],
5170    ) -> Result<usize, EncodeError>
5171    where
5172        A: Alphabet,
5173    {
5174        let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
5175        if output.len() < required {
5176            return Err(EncodeError::OutputTooSmall {
5177                required,
5178                available: output.len(),
5179            });
5180        }
5181
5182        let mut read = 0;
5183        let mut write = 0;
5184        while read + 3 <= input.len() {
5185            let b0 = input[read];
5186            let b1 = input[read + 1];
5187            let b2 = input[read + 2];
5188
5189            output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
5190            output[write + 1] =
5191                encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
5192            output[write + 2] =
5193                encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
5194            output[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
5195
5196            read += 3;
5197            write += 4;
5198        }
5199
5200        match input.len() - read {
5201            0 => {}
5202            1 => {
5203                let b0 = input[read];
5204                output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
5205                output[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
5206                write += 2;
5207                if PAD {
5208                    output[write] = b'=';
5209                    output[write + 1] = b'=';
5210                    write += 2;
5211                }
5212            }
5213            2 => {
5214                let b0 = input[read];
5215                let b1 = input[read + 1];
5216                output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
5217                output[write + 1] =
5218                    encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
5219                output[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
5220                write += 3;
5221                if PAD {
5222                    output[write] = b'=';
5223                    write += 1;
5224                }
5225            }
5226            _ => unreachable!(),
5227        }
5228
5229        Ok(write)
5230    }
5231
5232    fn scalar_decode_slice<A, const PAD: bool>(
5233        input: &[u8],
5234        output: &mut [u8],
5235    ) -> Result<usize, DecodeError>
5236    where
5237        A: Alphabet,
5238    {
5239        if input.is_empty() {
5240            return Ok(0);
5241        }
5242
5243        if PAD {
5244            decode_padded::<A>(input, output)
5245        } else {
5246            decode_unpadded::<A>(input, output)
5247        }
5248    }
5249}
5250
5251/// A zero-sized Base64 engine parameterized by alphabet and padding policy.
5252pub struct Engine<A, const PAD: bool> {
5253    alphabet: core::marker::PhantomData<A>,
5254}
5255
5256impl<A, const PAD: bool> Clone for Engine<A, PAD> {
5257    fn clone(&self) -> Self {
5258        *self
5259    }
5260}
5261
5262impl<A, const PAD: bool> Copy for Engine<A, PAD> {}
5263
5264impl<A, const PAD: bool> core::fmt::Debug for Engine<A, PAD> {
5265    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
5266        formatter
5267            .debug_struct("Engine")
5268            .field("padded", &PAD)
5269            .finish()
5270    }
5271}
5272
5273impl<A, const PAD: bool> core::fmt::Display for Engine<A, PAD> {
5274    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
5275        write!(formatter, "padded={PAD}")
5276    }
5277}
5278
5279impl<A, const PAD: bool> Default for Engine<A, PAD> {
5280    fn default() -> Self {
5281        Self {
5282            alphabet: core::marker::PhantomData,
5283        }
5284    }
5285}
5286
5287impl<A, const PAD: bool> Eq for Engine<A, PAD> {}
5288
5289impl<A, const PAD: bool> PartialEq for Engine<A, PAD> {
5290    fn eq(&self, _other: &Self) -> bool {
5291        true
5292    }
5293}
5294
5295impl<A, const PAD: bool> Engine<A, PAD>
5296where
5297    A: Alphabet,
5298{
5299    /// Creates a new engine value.
5300    #[must_use]
5301    pub const fn new() -> Self {
5302        Self {
5303            alphabet: core::marker::PhantomData,
5304        }
5305    }
5306
5307    /// Returns whether this engine uses padded Base64.
5308    #[must_use]
5309    pub const fn is_padded(&self) -> bool {
5310        PAD
5311    }
5312
5313    /// Returns this engine as an unwrapped profile.
5314    ///
5315    /// Use [`Profile::new`] or [`Profile::checked_new`] when a strict
5316    /// line-wrapping policy should travel with the profile.
5317    #[must_use]
5318    pub const fn profile(&self) -> Profile<A, PAD> {
5319        Profile::new(*self, None)
5320    }
5321
5322    /// Returns the matching constant-time-oriented decoder for this engine's
5323    /// alphabet and padding policy.
5324    ///
5325    /// The returned decoder is still an explicit opt-in to the [`ct`] module's
5326    /// slower, opaque-error, constant-time-oriented scalar path.
5327    #[must_use]
5328    pub const fn ct_decoder(&self) -> ct::CtEngine<A, PAD> {
5329        ct::CtEngine::new()
5330    }
5331
5332    /// Wraps a `std::io::Write` value in a streaming Base64 encoder.
5333    ///
5334    /// This is a convenience constructor for [`stream::Encoder::new`] that
5335    /// keeps the selected engine attached to the call site.
5336    ///
5337    /// ```
5338    /// use std::io::Write;
5339    /// use base64_ng::STANDARD;
5340    ///
5341    /// let mut encoder = STANDARD.encoder_writer(Vec::new());
5342    /// encoder.write_all(b"hello").unwrap();
5343    /// assert_eq!(encoder.finish().unwrap(), b"aGVsbG8=");
5344    /// ```
5345    #[cfg(feature = "stream")]
5346    #[must_use]
5347    pub fn encoder_writer<W>(&self, inner: W) -> stream::Encoder<W, A, PAD> {
5348        stream::Encoder::new(inner, *self)
5349    }
5350
5351    /// Wraps a `std::io::Write` value in a streaming Base64 decoder.
5352    ///
5353    /// This is a convenience constructor for [`stream::Decoder::new`] that
5354    /// keeps the selected engine attached to the call site.
5355    ///
5356    /// ```
5357    /// use std::io::Write;
5358    /// use base64_ng::STANDARD;
5359    ///
5360    /// let mut decoder = STANDARD.decoder_writer(Vec::new());
5361    /// decoder.write_all(b"aGVsbG8=").unwrap();
5362    /// assert_eq!(decoder.finish().unwrap(), b"hello");
5363    /// ```
5364    ///
5365    /// # Security
5366    ///
5367    /// Streaming decoders use the normal strict decode path, not the
5368    /// [`crate::ct`] module. Do not use this adapter for secret-bearing
5369    /// payloads when malformed-input timing matters.
5370    #[cfg(feature = "stream")]
5371    #[must_use]
5372    pub fn decoder_writer<W>(&self, inner: W) -> stream::Decoder<W, A, PAD> {
5373        stream::Decoder::new(inner, *self)
5374    }
5375
5376    /// Wraps a `std::io::Read` value in a streaming Base64 encoder.
5377    ///
5378    /// This is a convenience constructor for [`stream::EncoderReader::new`]
5379    /// that keeps the selected engine attached to the call site.
5380    ///
5381    /// ```
5382    /// use std::io::Read;
5383    /// use base64_ng::STANDARD;
5384    ///
5385    /// let mut reader = STANDARD.encoder_reader(&b"hello"[..]);
5386    /// let mut encoded = String::new();
5387    /// reader.read_to_string(&mut encoded).unwrap();
5388    /// assert_eq!(encoded, "aGVsbG8=");
5389    /// ```
5390    #[cfg(feature = "stream")]
5391    #[must_use]
5392    pub fn encoder_reader<R>(&self, inner: R) -> stream::EncoderReader<R, A, PAD> {
5393        stream::EncoderReader::new(inner, *self)
5394    }
5395
5396    /// Wraps a `std::io::Read` value in a streaming Base64 decoder.
5397    ///
5398    /// This is a convenience constructor for [`stream::DecoderReader::new`]
5399    /// that keeps the selected engine attached to the call site.
5400    ///
5401    /// ```
5402    /// use std::io::Read;
5403    /// use base64_ng::STANDARD;
5404    ///
5405    /// let mut reader = STANDARD.decoder_reader(&b"aGVsbG8="[..]);
5406    /// let mut decoded = Vec::new();
5407    /// reader.read_to_end(&mut decoded).unwrap();
5408    /// assert_eq!(decoded, b"hello");
5409    /// ```
5410    ///
5411    /// # Security
5412    ///
5413    /// Streaming decoder readers use the normal strict decode path, not the
5414    /// [`crate::ct`] module. Do not use this adapter for secret-bearing
5415    /// payloads when malformed-input timing matters.
5416    #[cfg(feature = "stream")]
5417    #[must_use]
5418    pub fn decoder_reader<R>(&self, inner: R) -> stream::DecoderReader<R, A, PAD> {
5419        stream::DecoderReader::new(inner, *self)
5420    }
5421
5422    /// Returns the encoded length for this engine's padding policy.
5423    pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
5424        encoded_len(input_len, PAD)
5425    }
5426
5427    /// Returns the encoded length for this engine, or `None` on overflow.
5428    #[must_use]
5429    pub const fn checked_encoded_len(&self, input_len: usize) -> Option<usize> {
5430        checked_encoded_len(input_len, PAD)
5431    }
5432
5433    /// Returns the encoded length after applying a line wrapping policy.
5434    ///
5435    /// The returned length includes inserted line endings but does not include
5436    /// a trailing line ending after the final encoded line.
5437    pub const fn wrapped_encoded_len(
5438        &self,
5439        input_len: usize,
5440        wrap: LineWrap,
5441    ) -> Result<usize, EncodeError> {
5442        wrapped_encoded_len(input_len, PAD, wrap)
5443    }
5444
5445    /// Returns the encoded length after line wrapping, or `None` on overflow or
5446    /// invalid line wrapping.
5447    #[must_use]
5448    pub const fn checked_wrapped_encoded_len(
5449        &self,
5450        input_len: usize,
5451        wrap: LineWrap,
5452    ) -> Option<usize> {
5453        checked_wrapped_encoded_len(input_len, PAD, wrap)
5454    }
5455
5456    /// Returns the exact decoded length implied by input length and padding.
5457    ///
5458    /// This validates padding placement and impossible lengths, but it does not
5459    /// validate alphabet membership or non-canonical trailing bits.
5460    pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
5461        decoded_len(input, PAD)
5462    }
5463
5464    /// Returns the exact decoded length for the explicit legacy profile.
5465    ///
5466    /// The legacy profile ignores ASCII space, tab, carriage return, and line
5467    /// feed bytes before applying the same alphabet, padding, and canonical-bit
5468    /// checks as strict decoding.
5469    pub fn decoded_len_legacy(&self, input: &[u8]) -> Result<usize, DecodeError> {
5470        validate_legacy_decode::<A, PAD>(input)
5471    }
5472
5473    /// Returns the exact decoded length for a line-wrapped profile.
5474    ///
5475    /// The wrapped profile accepts only the configured line ending. Non-final
5476    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
5477    /// may be shorter. A single trailing line ending after the final line is
5478    /// accepted.
5479    pub fn decoded_len_wrapped(&self, input: &[u8], wrap: LineWrap) -> Result<usize, DecodeError> {
5480        validate_wrapped_decode::<A, PAD>(input, wrap)
5481    }
5482
5483    /// Validates strict Base64 input without writing decoded bytes.
5484    ///
5485    /// This applies the same alphabet, padding, and canonical-bit checks as
5486    /// [`Self::decode_slice`]. Use this method when malformed-input
5487    /// diagnostics matter; use [`Self::validate`] when a boolean is enough.
5488    /// This default validator is not constant-time; use
5489    /// [`crate::ct::CtEngine::validate_result`] through [`Self::ct_decoder`]
5490    /// for secret-bearing payloads where timing posture matters.
5491    ///
5492    /// # Examples
5493    ///
5494    /// ```
5495    /// use base64_ng::STANDARD;
5496    ///
5497    /// STANDARD.validate_result(b"aGVsbG8=").unwrap();
5498    /// assert!(STANDARD.validate_result(b"aGVsbG8").is_err());
5499    /// ```
5500    pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
5501        validate_decode::<A, PAD>(input).map(|_| ())
5502    }
5503
5504    /// Returns whether `input` is valid strict Base64 for this engine.
5505    ///
5506    /// This is a convenience wrapper around [`Self::validate_result`] and is
5507    /// not constant-time. Use [`crate::ct::CtEngine::validate`] through
5508    /// [`Self::ct_decoder`] for secret-bearing payloads where timing posture
5509    /// matters.
5510    ///
5511    /// # Examples
5512    ///
5513    /// ```
5514    /// use base64_ng::URL_SAFE_NO_PAD;
5515    ///
5516    /// assert!(URL_SAFE_NO_PAD.validate(b"-_8"));
5517    /// assert!(!URL_SAFE_NO_PAD.validate(b"+/8"));
5518    /// ```
5519    #[must_use]
5520    pub fn validate(&self, input: &[u8]) -> bool {
5521        self.validate_result(input).is_ok()
5522    }
5523
5524    /// Validates input using the explicit legacy whitespace profile.
5525    ///
5526    /// ASCII space, tab, carriage return, and line feed bytes are ignored
5527    /// before applying the same alphabet, padding, and canonical-bit checks as
5528    /// strict decoding.
5529    ///
5530    /// # Examples
5531    ///
5532    /// ```
5533    /// use base64_ng::STANDARD;
5534    ///
5535    /// STANDARD.validate_legacy_result(b" aG\r\nVsbG8= ").unwrap();
5536    /// assert!(STANDARD.validate_legacy_result(b" aG-=").is_err());
5537    /// ```
5538    pub fn validate_legacy_result(&self, input: &[u8]) -> Result<(), DecodeError> {
5539        validate_legacy_decode::<A, PAD>(input).map(|_| ())
5540    }
5541
5542    /// Returns whether `input` is valid for the explicit legacy whitespace
5543    /// profile.
5544    ///
5545    /// This is a convenience wrapper around [`Self::validate_legacy_result`].
5546    ///
5547    /// # Examples
5548    ///
5549    /// ```
5550    /// use base64_ng::STANDARD;
5551    ///
5552    /// assert!(STANDARD.validate_legacy(b" aG\r\nVsbG8= "));
5553    /// assert!(!STANDARD.validate_legacy(b"aG-V"));
5554    /// ```
5555    #[must_use]
5556    pub fn validate_legacy(&self, input: &[u8]) -> bool {
5557        self.validate_legacy_result(input).is_ok()
5558    }
5559
5560    /// Validates input using a strict line-wrapped profile.
5561    ///
5562    /// This is stricter than [`Self::validate_legacy_result`]: it accepts only
5563    /// the configured line ending and enforces the configured line length for
5564    /// every non-final line.
5565    ///
5566    /// # Examples
5567    ///
5568    /// ```
5569    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
5570    ///
5571    /// let wrap = LineWrap::new(4, LineEnding::Lf);
5572    /// STANDARD.validate_wrapped_result(b"aGVs\nbG8=", wrap).unwrap();
5573    /// assert!(STANDARD.validate_wrapped_result(b"aG\nVsbG8=", wrap).is_err());
5574    /// ```
5575    pub fn validate_wrapped_result(&self, input: &[u8], wrap: LineWrap) -> Result<(), DecodeError> {
5576        validate_wrapped_decode::<A, PAD>(input, wrap).map(|_| ())
5577    }
5578
5579    /// Returns whether `input` is valid for a strict line-wrapped profile.
5580    ///
5581    /// This is a convenience wrapper around [`Self::validate_wrapped_result`].
5582    ///
5583    /// # Examples
5584    ///
5585    /// ```
5586    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
5587    ///
5588    /// let wrap = LineWrap::new(4, LineEnding::Lf);
5589    /// assert!(STANDARD.validate_wrapped(b"aGVs\nbG8=", wrap));
5590    /// assert!(!STANDARD.validate_wrapped(b"aG\nVsbG8=", wrap));
5591    /// ```
5592    #[must_use]
5593    pub fn validate_wrapped(&self, input: &[u8], wrap: LineWrap) -> bool {
5594        self.validate_wrapped_result(input, wrap).is_ok()
5595    }
5596
5597    /// Encodes a fixed-size input into a fixed-size output array in const contexts.
5598    ///
5599    /// Stable Rust does not yet allow this API to return an array whose length
5600    /// is computed from `INPUT_LEN` directly. Instead, the caller supplies the
5601    /// output length through the destination type and this function panics
5602    /// during const evaluation if the length is wrong.
5603    ///
5604    /// # Panics
5605    ///
5606    /// Panics if `OUTPUT_LEN` is not exactly the encoded length for `INPUT_LEN`
5607    /// and this engine's padding policy, or if that length overflows `usize`.
5608    ///
5609    /// # Examples
5610    ///
5611    /// ```
5612    /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
5613    ///
5614    /// const HELLO: [u8; 8] = STANDARD.encode_array(b"hello");
5615    /// const URL_SAFE: [u8; 3] = URL_SAFE_NO_PAD.encode_array(b"\xfb\xff");
5616    ///
5617    /// assert_eq!(&HELLO, b"aGVsbG8=");
5618    /// assert_eq!(&URL_SAFE, b"-_8");
5619    /// ```
5620    ///
5621    /// Incorrect output lengths fail during const evaluation:
5622    ///
5623    /// ```compile_fail
5624    /// use base64_ng::STANDARD;
5625    ///
5626    /// const TOO_SHORT: [u8; 7] = STANDARD.encode_array(b"hello");
5627    /// ```
5628    #[must_use]
5629    pub const fn encode_array<const INPUT_LEN: usize, const OUTPUT_LEN: usize>(
5630        &self,
5631        input: &[u8; INPUT_LEN],
5632    ) -> [u8; OUTPUT_LEN] {
5633        let Some(required) = checked_encoded_len(INPUT_LEN, PAD) else {
5634            panic!("encoded base64 length overflows usize");
5635        };
5636        assert!(
5637            required == OUTPUT_LEN,
5638            "base64 output array has incorrect length"
5639        );
5640
5641        let mut output = [0u8; OUTPUT_LEN];
5642        let mut read = 0;
5643        let mut write = 0;
5644        while INPUT_LEN - read >= 3 {
5645            let b0 = input[read];
5646            let b1 = input[read + 1];
5647            let b2 = input[read + 2];
5648
5649            output[write] = encode_base64_value::<A>(b0 >> 2);
5650            output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
5651            output[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
5652            output[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
5653
5654            read += 3;
5655            write += 4;
5656        }
5657
5658        match INPUT_LEN - read {
5659            0 => {}
5660            1 => {
5661                let b0 = input[read];
5662                output[write] = encode_base64_value::<A>(b0 >> 2);
5663                output[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
5664                write += 2;
5665                if PAD {
5666                    output[write] = b'=';
5667                    output[write + 1] = b'=';
5668                }
5669            }
5670            2 => {
5671                let b0 = input[read];
5672                let b1 = input[read + 1];
5673                output[write] = encode_base64_value::<A>(b0 >> 2);
5674                output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
5675                output[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
5676                if PAD {
5677                    output[write + 3] = b'=';
5678                }
5679            }
5680            _ => unreachable!(),
5681        }
5682
5683        output
5684    }
5685
5686    /// Encodes `input` into `output`, returning the number of bytes written.
5687    pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
5688        backend::encode_slice::<A, PAD>(input, output)
5689    }
5690
5691    /// Encodes `input` into `output` with line wrapping.
5692    ///
5693    /// The wrapping policy inserts line endings between encoded lines and does
5694    /// not append a trailing line ending after the final line.
5695    ///
5696    /// # Examples
5697    ///
5698    /// ```
5699    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
5700    ///
5701    /// let wrap = LineWrap::new(4, LineEnding::Lf);
5702    /// let mut output = [0u8; 9];
5703    /// let written = STANDARD
5704    ///     .encode_slice_wrapped(b"hello", &mut output, wrap)
5705    ///     .unwrap();
5706    ///
5707    /// assert_eq!(&output[..written], b"aGVs\nbG8=");
5708    /// ```
5709    pub fn encode_slice_wrapped(
5710        &self,
5711        input: &[u8],
5712        output: &mut [u8],
5713        wrap: LineWrap,
5714    ) -> Result<usize, EncodeError> {
5715        let required = self.wrapped_encoded_len(input.len(), wrap)?;
5716        if output.len() < required {
5717            return Err(EncodeError::OutputTooSmall {
5718                required,
5719                available: output.len(),
5720            });
5721        }
5722
5723        let encoded_len =
5724            checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
5725        if encoded_len == 0 {
5726            return Ok(0);
5727        }
5728
5729        // If the temporary in-buffer layout size overflows, fall back to the
5730        // fixed scratch buffer path rather than relying on saturated arithmetic.
5731        let combined_required = match required.checked_add(encoded_len) {
5732            Some(len) => len,
5733            None => usize::MAX,
5734        };
5735        if output.len() < combined_required {
5736            let mut scratch = [0u8; 1024];
5737            let mut input_offset = 0;
5738            let mut output_offset = 0;
5739            let mut column = 0;
5740
5741            while input_offset < input.len() {
5742                let remaining = input.len() - input_offset;
5743                let mut take = remaining.min(768);
5744                if remaining > take {
5745                    take -= take % 3;
5746                }
5747                if take == 0 {
5748                    take = remaining;
5749                }
5750
5751                let encoded = match self
5752                    .encode_slice(&input[input_offset..input_offset + take], &mut scratch)
5753                {
5754                    Ok(encoded) => encoded,
5755                    Err(err) => {
5756                        wipe_bytes(&mut scratch);
5757                        return Err(err);
5758                    }
5759                };
5760                if let Err(err) = write_wrapped_bytes(
5761                    &scratch[..encoded],
5762                    output,
5763                    &mut output_offset,
5764                    &mut column,
5765                    wrap,
5766                ) {
5767                    wipe_bytes(&mut scratch);
5768                    return Err(err);
5769                }
5770                wipe_bytes(&mut scratch[..encoded]);
5771                input_offset += take;
5772            }
5773
5774            Ok(output_offset)
5775        } else {
5776            let encoded =
5777                self.encode_slice(input, &mut output[required..required + encoded_len])?;
5778            let mut output_offset = 0;
5779            let mut column = 0;
5780            let mut read = required;
5781            while read < required + encoded {
5782                let byte = output[read];
5783                write_wrapped_byte(byte, output, &mut output_offset, &mut column, wrap)?;
5784                read += 1;
5785            }
5786            wipe_bytes(&mut output[required..required + encoded]);
5787            Ok(output_offset)
5788        }
5789    }
5790
5791    /// Encodes `input` with line wrapping and clears all bytes after the
5792    /// encoded prefix.
5793    ///
5794    /// If encoding fails, the entire output buffer is cleared before the error
5795    /// is returned.
5796    pub fn encode_slice_wrapped_clear_tail(
5797        &self,
5798        input: &[u8],
5799        output: &mut [u8],
5800        wrap: LineWrap,
5801    ) -> Result<usize, EncodeError> {
5802        let written = match self.encode_slice_wrapped(input, output, wrap) {
5803            Ok(written) => written,
5804            Err(err) => {
5805                wipe_bytes(output);
5806                return Err(err);
5807            }
5808        };
5809        wipe_tail(output, written);
5810        Ok(written)
5811    }
5812
5813    /// Encodes `input` with line wrapping into a stack-backed buffer.
5814    ///
5815    /// This is useful for MIME/PEM-style protocols where heap allocation is
5816    /// unnecessary. If encoding fails, the internal backing array is cleared
5817    /// before the error is returned.
5818    pub fn encode_wrapped_buffer<const CAP: usize>(
5819        &self,
5820        input: &[u8],
5821        wrap: LineWrap,
5822    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
5823        let mut output = EncodedBuffer::new();
5824        let written = match self.encode_slice_wrapped_clear_tail(input, &mut output.bytes, wrap) {
5825            Ok(written) => written,
5826            Err(err) => {
5827                output.clear();
5828                return Err(err);
5829            }
5830        };
5831        output.len = written;
5832        Ok(output)
5833    }
5834
5835    /// Encodes `input` with line wrapping into a newly allocated byte vector.
5836    #[cfg(feature = "alloc")]
5837    #[must_use = "for secret-bearing payloads use encode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
5838    pub fn encode_wrapped_vec(
5839        &self,
5840        input: &[u8],
5841        wrap: LineWrap,
5842    ) -> Result<alloc::vec::Vec<u8>, EncodeError> {
5843        let required = self.wrapped_encoded_len(input.len(), wrap)?;
5844        let mut output = alloc::vec![0; required];
5845        let written = self.encode_slice_wrapped(input, &mut output, wrap)?;
5846        output.truncate(written);
5847        Ok(output)
5848    }
5849
5850    /// Encodes `input` with line wrapping into a newly allocated UTF-8 string.
5851    #[cfg(feature = "alloc")]
5852    pub fn encode_wrapped_string(
5853        &self,
5854        input: &[u8],
5855        wrap: LineWrap,
5856    ) -> Result<alloc::string::String, EncodeError> {
5857        let output = self.encode_wrapped_vec(input, wrap)?;
5858        match alloc::string::String::from_utf8(output) {
5859            Ok(output) => Ok(output),
5860            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
5861        }
5862    }
5863
5864    /// Encodes `input` with line wrapping into a redacted owned secret buffer.
5865    ///
5866    /// This is useful when the wrapped encoded representation itself is
5867    /// sensitive and should not be accidentally logged through formatting.
5868    #[cfg(feature = "alloc")]
5869    pub fn encode_wrapped_secret(
5870        &self,
5871        input: &[u8],
5872        wrap: LineWrap,
5873    ) -> Result<SecretBuffer, EncodeError> {
5874        self.encode_wrapped_vec(input, wrap)
5875            .map(SecretBuffer::from_vec)
5876    }
5877
5878    /// Encodes `input` into `output` and clears all bytes after the encoded
5879    /// prefix.
5880    ///
5881    /// If encoding fails, the entire output buffer is cleared before the error
5882    /// is returned.
5883    ///
5884    /// # Examples
5885    ///
5886    /// ```
5887    /// use base64_ng::STANDARD;
5888    ///
5889    /// let mut output = [0xff; 12];
5890    /// let written = STANDARD
5891    ///     .encode_slice_clear_tail(b"hello", &mut output)
5892    ///     .unwrap();
5893    ///
5894    /// assert_eq!(&output[..written], b"aGVsbG8=");
5895    /// assert!(output[written..].iter().all(|byte| *byte == 0));
5896    /// ```
5897    pub fn encode_slice_clear_tail(
5898        &self,
5899        input: &[u8],
5900        output: &mut [u8],
5901    ) -> Result<usize, EncodeError> {
5902        let written = match self.encode_slice(input, output) {
5903            Ok(written) => written,
5904            Err(err) => {
5905                wipe_bytes(output);
5906                return Err(err);
5907            }
5908        };
5909        wipe_tail(output, written);
5910        Ok(written)
5911    }
5912
5913    /// Encodes `input` into a stack-backed buffer.
5914    ///
5915    /// This helper is useful for short values where callers want the
5916    /// convenience of an owned result without enabling `alloc`.
5917    ///
5918    /// # Examples
5919    ///
5920    /// ```
5921    /// use base64_ng::STANDARD;
5922    ///
5923    /// let encoded = STANDARD.encode_buffer::<8>(b"hello").unwrap();
5924    ///
5925    /// assert_eq!(encoded.as_str(), "aGVsbG8=");
5926    /// ```
5927    pub fn encode_buffer<const CAP: usize>(
5928        &self,
5929        input: &[u8],
5930    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
5931        let mut output = EncodedBuffer::new();
5932        let written = match self.encode_slice_clear_tail(input, &mut output.bytes) {
5933            Ok(written) => written,
5934            Err(err) => {
5935                output.clear();
5936                return Err(err);
5937            }
5938        };
5939        output.len = written;
5940        Ok(output)
5941    }
5942
5943    /// Encodes `input` into a newly allocated byte vector.
5944    #[cfg(feature = "alloc")]
5945    #[must_use = "for secret-bearing payloads use encode_secret, which returns a redacted buffer with drop-time cleanup"]
5946    pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
5947        let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
5948        let mut output = alloc::vec![0; required];
5949        let written = self.encode_slice(input, &mut output)?;
5950        output.truncate(written);
5951        Ok(output)
5952    }
5953
5954    /// Encodes `input` into a redacted owned secret buffer.
5955    ///
5956    /// This is useful when the encoded representation itself is sensitive and
5957    /// should not be accidentally logged through formatting.
5958    #[cfg(feature = "alloc")]
5959    pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
5960        self.encode_vec(input).map(SecretBuffer::from_vec)
5961    }
5962
5963    /// Encodes `input` into a newly allocated UTF-8 string.
5964    ///
5965    /// Base64 output is ASCII by construction. This helper is available with
5966    /// the `alloc` feature and has the same encoding semantics as
5967    /// [`Self::encode_slice`].
5968    ///
5969    /// # Examples
5970    ///
5971    /// ```
5972    /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
5973    ///
5974    /// assert_eq!(STANDARD.encode_string(b"hello").unwrap(), "aGVsbG8=");
5975    /// assert_eq!(URL_SAFE_NO_PAD.encode_string(b"\xfb\xff").unwrap(), "-_8");
5976    /// ```
5977    #[cfg(feature = "alloc")]
5978    pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
5979        let output = self.encode_vec(input)?;
5980        match alloc::string::String::from_utf8(output) {
5981            Ok(output) => Ok(output),
5982            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
5983        }
5984    }
5985
5986    /// Encodes the first `input_len` bytes of `buffer` in place.
5987    ///
5988    /// The buffer must have enough spare capacity for the encoded output. The
5989    /// implementation writes from right to left, so unread input bytes are not
5990    /// overwritten before they are encoded.
5991    ///
5992    /// # Examples
5993    ///
5994    /// ```
5995    /// use base64_ng::STANDARD;
5996    ///
5997    /// let mut buffer = [0u8; 8];
5998    /// buffer[..5].copy_from_slice(b"hello");
5999    /// let encoded = STANDARD.encode_in_place(&mut buffer, 5).unwrap();
6000    /// assert_eq!(encoded, b"aGVsbG8=");
6001    /// ```
6002    pub fn encode_in_place<'a>(
6003        &self,
6004        buffer: &'a mut [u8],
6005        input_len: usize,
6006    ) -> Result<&'a mut [u8], EncodeError> {
6007        if input_len > buffer.len() {
6008            return Err(EncodeError::InputTooLarge {
6009                input_len,
6010                buffer_len: buffer.len(),
6011            });
6012        }
6013
6014        let required = checked_encoded_len(input_len, PAD).ok_or(EncodeError::LengthOverflow)?;
6015        if buffer.len() < required {
6016            return Err(EncodeError::OutputTooSmall {
6017                required,
6018                available: buffer.len(),
6019            });
6020        }
6021
6022        let mut read = input_len;
6023        let mut write = required;
6024
6025        match input_len % 3 {
6026            0 => {}
6027            1 => {
6028                read -= 1;
6029                let b0 = buffer[read];
6030                if PAD {
6031                    write -= 4;
6032                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
6033                    buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
6034                    buffer[write + 2] = b'=';
6035                    buffer[write + 3] = b'=';
6036                } else {
6037                    write -= 2;
6038                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
6039                    buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
6040                }
6041            }
6042            2 => {
6043                read -= 2;
6044                let b0 = buffer[read];
6045                let b1 = buffer[read + 1];
6046                if PAD {
6047                    write -= 4;
6048                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
6049                    buffer[write + 1] =
6050                        encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
6051                    buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
6052                    buffer[write + 3] = b'=';
6053                } else {
6054                    write -= 3;
6055                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
6056                    buffer[write + 1] =
6057                        encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
6058                    buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
6059                }
6060            }
6061            _ => unreachable!(),
6062        }
6063
6064        while read > 0 {
6065            read -= 3;
6066            write -= 4;
6067            let b0 = buffer[read];
6068            let b1 = buffer[read + 1];
6069            let b2 = buffer[read + 2];
6070
6071            buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
6072            buffer[write + 1] =
6073                encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
6074            buffer[write + 2] =
6075                encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
6076            buffer[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
6077        }
6078
6079        // The right-to-left loop consumes exactly three input bytes for every
6080        // four output bytes. If this invariant changes, returning a shifted
6081        // slice would silently corrupt the in-place output.
6082        debug_assert_eq!(write, 0);
6083        Ok(&mut buffer[..required])
6084    }
6085
6086    /// Encodes the first `input_len` bytes of `buffer` in place and clears all
6087    /// bytes after the encoded prefix.
6088    ///
6089    /// If encoding fails because `input_len` is too large, the output buffer is
6090    /// too small, or the encoded length overflows `usize`, the entire buffer is
6091    /// cleared before the error is returned.
6092    ///
6093    /// # Examples
6094    ///
6095    /// ```
6096    /// use base64_ng::STANDARD;
6097    ///
6098    /// let mut buffer = [0xff; 12];
6099    /// buffer[..5].copy_from_slice(b"hello");
6100    /// let encoded = STANDARD.encode_in_place_clear_tail(&mut buffer, 5).unwrap();
6101    /// assert_eq!(encoded, b"aGVsbG8=");
6102    /// ```
6103    pub fn encode_in_place_clear_tail<'a>(
6104        &self,
6105        buffer: &'a mut [u8],
6106        input_len: usize,
6107    ) -> Result<&'a mut [u8], EncodeError> {
6108        let len = match self.encode_in_place(buffer, input_len) {
6109            Ok(encoded) => encoded.len(),
6110            Err(err) => {
6111                wipe_bytes(buffer);
6112                return Err(err);
6113            }
6114        };
6115        wipe_tail(buffer, len);
6116        Ok(&mut buffer[..len])
6117    }
6118
6119    /// Decodes `input` into `output`, returning the number of bytes written.
6120    ///
6121    /// This is strict decoding. Whitespace, mixed alphabets, malformed padding,
6122    /// and trailing non-padding data are rejected.
6123    ///
6124    /// # Security
6125    ///
6126    /// This default scalar decoder prioritizes strict validation, exact error
6127    /// reporting, and ordinary throughput. It may branch or return early based
6128    /// on byte validity, malformed input, padding position, and output
6129    /// capacity. It also reports exact failure positions and invalid byte
6130    /// values through [`DecodeError`]. Do not use this method for token
6131    /// comparison, key-material decoding, or secret-bearing validation where
6132    /// malformed-input timing matters. Use [`crate::ct`],
6133    /// [`crate::ct::STANDARD`], [`crate::ct::URL_SAFE_NO_PAD`], or
6134    /// [`Self::ct_decoder`] with `decode_slice_clear_tail` for
6135    /// constant-time-oriented secret decoding.
6136    #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
6137    pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
6138        backend::decode_slice::<A, PAD>(input, output)
6139    }
6140
6141    /// Decodes `input` into `output` and clears all bytes after the decoded
6142    /// prefix.
6143    ///
6144    /// If decoding fails, the entire output buffer is cleared before the error
6145    /// is returned.
6146    ///
6147    /// # Examples
6148    ///
6149    /// ```
6150    /// use base64_ng::STANDARD;
6151    ///
6152    /// let mut output = [0xff; 8];
6153    /// let written = STANDARD
6154    ///     .decode_slice_clear_tail(b"aGk=", &mut output)
6155    ///     .unwrap();
6156    ///
6157    /// assert_eq!(&output[..written], b"hi");
6158    /// assert!(output[written..].iter().all(|byte| *byte == 0));
6159    /// ```
6160    pub fn decode_slice_clear_tail(
6161        &self,
6162        input: &[u8],
6163        output: &mut [u8],
6164    ) -> Result<usize, DecodeError> {
6165        let written = match self.decode_slice(input, output) {
6166            Ok(written) => written,
6167            Err(err) => {
6168                wipe_bytes(output);
6169                return Err(err);
6170            }
6171        };
6172        wipe_tail(output, written);
6173        Ok(written)
6174    }
6175
6176    /// Decodes `input` into a stack-backed buffer.
6177    ///
6178    /// This helper is useful for short decoded values where callers want the
6179    /// convenience of an owned result without enabling `alloc`.
6180    ///
6181    /// # Examples
6182    ///
6183    /// ```
6184    /// use base64_ng::STANDARD;
6185    ///
6186    /// let decoded = STANDARD.decode_buffer::<5>(b"aGVsbG8=").unwrap();
6187    ///
6188    /// assert_eq!(decoded.as_bytes(), b"hello");
6189    /// ```
6190    pub fn decode_buffer<const CAP: usize>(
6191        &self,
6192        input: &[u8],
6193    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
6194        let mut output = DecodedBuffer::new();
6195        let written = match self.decode_slice_clear_tail(input, &mut output.bytes) {
6196            Ok(written) => written,
6197            Err(err) => {
6198                output.clear();
6199                return Err(err);
6200            }
6201        };
6202        output.len = written;
6203        Ok(output)
6204    }
6205
6206    /// Decodes `input` using the explicit legacy whitespace profile.
6207    ///
6208    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
6209    /// Alphabet selection, padding placement, trailing data after padding, and
6210    /// non-canonical trailing bits remain strict.
6211    ///
6212    /// # Security
6213    ///
6214    /// This method uses the normal strict decode path after legacy whitespace
6215    /// handling. It may branch or return early based on malformed input and is
6216    /// not a constant-time token validator or key-material decoder. Use
6217    /// [`crate::ct`] for secret-bearing payloads.
6218    #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
6219    pub fn decode_slice_legacy(
6220        &self,
6221        input: &[u8],
6222        output: &mut [u8],
6223    ) -> Result<usize, DecodeError> {
6224        let required = validate_legacy_decode::<A, PAD>(input)?;
6225        if output.len() < required {
6226            return Err(DecodeError::OutputTooSmall {
6227                required,
6228                available: output.len(),
6229            });
6230        }
6231        decode_legacy_to_slice::<A, PAD>(input, output)
6232    }
6233
6234    /// Decodes `input` using the explicit legacy whitespace profile and clears
6235    /// all bytes after the decoded prefix.
6236    ///
6237    /// If validation or decoding fails, the entire output buffer is cleared
6238    /// before the error is returned.
6239    ///
6240    /// # Examples
6241    ///
6242    /// ```
6243    /// use base64_ng::STANDARD;
6244    ///
6245    /// let mut output = [0xff; 8];
6246    /// let written = STANDARD
6247    ///     .decode_slice_legacy_clear_tail(b" aG\r\nk= ", &mut output)
6248    ///     .unwrap();
6249    ///
6250    /// assert_eq!(&output[..written], b"hi");
6251    /// assert!(output[written..].iter().all(|byte| *byte == 0));
6252    /// ```
6253    pub fn decode_slice_legacy_clear_tail(
6254        &self,
6255        input: &[u8],
6256        output: &mut [u8],
6257    ) -> Result<usize, DecodeError> {
6258        let written = match self.decode_slice_legacy(input, output) {
6259            Ok(written) => written,
6260            Err(err) => {
6261                wipe_bytes(output);
6262                return Err(err);
6263            }
6264        };
6265        wipe_tail(output, written);
6266        Ok(written)
6267    }
6268
6269    /// Decodes `input` into a stack-backed buffer using the explicit legacy
6270    /// whitespace profile.
6271    ///
6272    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
6273    /// Alphabet selection, padding placement, trailing data after padding, and
6274    /// non-canonical trailing bits remain strict. If decoding fails, the
6275    /// internal backing array is cleared before the error is returned.
6276    pub fn decode_buffer_legacy<const CAP: usize>(
6277        &self,
6278        input: &[u8],
6279    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
6280        let mut output = DecodedBuffer::new();
6281        let written = match self.decode_slice_legacy_clear_tail(input, &mut output.bytes) {
6282            Ok(written) => written,
6283            Err(err) => {
6284                output.clear();
6285                return Err(err);
6286            }
6287        };
6288        output.len = written;
6289        Ok(output)
6290    }
6291
6292    /// Decodes `input` using a strict line-wrapped profile.
6293    ///
6294    /// The wrapped profile accepts only the configured line ending. Non-final
6295    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
6296    /// may be shorter. A single trailing line ending after the final line is
6297    /// accepted.
6298    ///
6299    /// # Security
6300    ///
6301    /// This method uses the normal strict decode path after line-profile
6302    /// validation. It may branch or return early based on malformed input and
6303    /// is not a constant-time token validator or key-material decoder. Use
6304    /// [`crate::ct`] for secret-bearing payloads.
6305    #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
6306    pub fn decode_slice_wrapped(
6307        &self,
6308        input: &[u8],
6309        output: &mut [u8],
6310        wrap: LineWrap,
6311    ) -> Result<usize, DecodeError> {
6312        let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
6313        if output.len() < required {
6314            return Err(DecodeError::OutputTooSmall {
6315                required,
6316                available: output.len(),
6317            });
6318        }
6319        decode_wrapped_to_slice::<A, PAD>(input, output, wrap)
6320    }
6321
6322    /// Decodes `input` using a strict line-wrapped profile and clears all bytes
6323    /// after the decoded prefix.
6324    ///
6325    /// If validation or decoding fails, the entire output buffer is cleared
6326    /// before the error is returned.
6327    pub fn decode_slice_wrapped_clear_tail(
6328        &self,
6329        input: &[u8],
6330        output: &mut [u8],
6331        wrap: LineWrap,
6332    ) -> Result<usize, DecodeError> {
6333        let written = match self.decode_slice_wrapped(input, output, wrap) {
6334            Ok(written) => written,
6335            Err(err) => {
6336                wipe_bytes(output);
6337                return Err(err);
6338            }
6339        };
6340        wipe_tail(output, written);
6341        Ok(written)
6342    }
6343
6344    /// Decodes `input` using a strict line-wrapped profile into a stack-backed
6345    /// buffer.
6346    ///
6347    /// The wrapped profile accepts only the configured line ending. Non-final
6348    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
6349    /// may be shorter. A single trailing line ending after the final line is
6350    /// accepted. If decoding fails, the internal backing array is cleared
6351    /// before the error is returned.
6352    pub fn decode_wrapped_buffer<const CAP: usize>(
6353        &self,
6354        input: &[u8],
6355        wrap: LineWrap,
6356    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
6357        let mut output = DecodedBuffer::new();
6358        let written = match self.decode_slice_wrapped_clear_tail(input, &mut output.bytes, wrap) {
6359            Ok(written) => written,
6360            Err(err) => {
6361                output.clear();
6362                return Err(err);
6363            }
6364        };
6365        output.len = written;
6366        Ok(output)
6367    }
6368
6369    /// Decodes `input` into a newly allocated byte vector.
6370    ///
6371    /// This is strict decoding with the same semantics as [`Self::decode_slice`].
6372    #[cfg(feature = "alloc")]
6373    #[must_use = "for secret-bearing payloads use decode_secret, which returns a redacted buffer with drop-time cleanup"]
6374    pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
6375        let required = validate_decode::<A, PAD>(input)?;
6376        let mut output = alloc::vec![0; required];
6377        let written = match self.decode_slice(input, &mut output) {
6378            Ok(written) => written,
6379            Err(err) => {
6380                wipe_bytes(&mut output);
6381                return Err(err);
6382            }
6383        };
6384        output.truncate(written);
6385        Ok(output)
6386    }
6387
6388    /// Decodes `input` into a redacted owned secret buffer.
6389    ///
6390    /// On malformed input, the intermediate output buffer is cleared before the
6391    /// error is returned by [`Self::decode_vec`].
6392    #[cfg(feature = "alloc")]
6393    pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
6394        self.decode_vec(input).map(SecretBuffer::from_vec)
6395    }
6396
6397    /// Decodes `input` into a newly allocated byte vector using the explicit
6398    /// legacy whitespace profile.
6399    #[cfg(feature = "alloc")]
6400    #[must_use = "for secret-bearing payloads use decode_secret_legacy, which returns a redacted buffer with drop-time cleanup"]
6401    pub fn decode_vec_legacy(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
6402        let required = validate_legacy_decode::<A, PAD>(input)?;
6403        let mut output = alloc::vec![0; required];
6404        let written = match self.decode_slice_legacy(input, &mut output) {
6405            Ok(written) => written,
6406            Err(err) => {
6407                wipe_bytes(&mut output);
6408                return Err(err);
6409            }
6410        };
6411        output.truncate(written);
6412        Ok(output)
6413    }
6414
6415    /// Decodes `input` into a redacted owned secret buffer using the explicit
6416    /// legacy whitespace profile.
6417    ///
6418    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
6419    /// Alphabet selection, padding placement, trailing data after padding, and
6420    /// non-canonical trailing bits remain strict.
6421    #[cfg(feature = "alloc")]
6422    pub fn decode_secret_legacy(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
6423        self.decode_vec_legacy(input).map(SecretBuffer::from_vec)
6424    }
6425
6426    /// Decodes line-wrapped input into a newly allocated byte vector.
6427    #[cfg(feature = "alloc")]
6428    #[must_use = "for secret-bearing payloads use decode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
6429    pub fn decode_wrapped_vec(
6430        &self,
6431        input: &[u8],
6432        wrap: LineWrap,
6433    ) -> Result<alloc::vec::Vec<u8>, DecodeError> {
6434        let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
6435        let mut output = alloc::vec![0; required];
6436        let written = match self.decode_slice_wrapped(input, &mut output, wrap) {
6437            Ok(written) => written,
6438            Err(err) => {
6439                wipe_bytes(&mut output);
6440                return Err(err);
6441            }
6442        };
6443        output.truncate(written);
6444        Ok(output)
6445    }
6446
6447    /// Decodes line-wrapped input into a redacted owned secret buffer.
6448    ///
6449    /// The wrapped profile accepts only the configured line ending. Non-final
6450    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
6451    /// may be shorter. A single trailing line ending after the final line is
6452    /// accepted.
6453    #[cfg(feature = "alloc")]
6454    pub fn decode_wrapped_secret(
6455        &self,
6456        input: &[u8],
6457        wrap: LineWrap,
6458    ) -> Result<SecretBuffer, DecodeError> {
6459        self.decode_wrapped_vec(input, wrap)
6460            .map(SecretBuffer::from_vec)
6461    }
6462
6463    /// Decodes `buffer` in place using a strict line-wrapped profile.
6464    ///
6465    /// The wrapped profile accepts only the configured line ending. Non-final
6466    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
6467    /// may be shorter. A single trailing line ending after the final line is
6468    /// accepted. If validation fails, the buffer contents are unspecified.
6469    /// On success, bytes after the returned decoded prefix may retain the
6470    /// compacted encoded representation. Use
6471    /// [`Self::decode_in_place_wrapped_clear_tail`] when the buffer may be
6472    /// reused or freed without a caller-managed wipe.
6473    ///
6474    /// # Examples
6475    ///
6476    /// ```
6477    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
6478    ///
6479    /// let mut buffer = *b"aGVs\nbG8=";
6480    /// let decoded = STANDARD
6481    ///     .decode_in_place_wrapped(&mut buffer, LineWrap::new(4, LineEnding::Lf))
6482    ///     .unwrap();
6483    ///
6484    /// assert_eq!(decoded, b"hello");
6485    /// ```
6486    pub fn decode_in_place_wrapped<'a>(
6487        &self,
6488        buffer: &'a mut [u8],
6489        wrap: LineWrap,
6490    ) -> Result<&'a mut [u8], DecodeError> {
6491        let _required = validate_wrapped_decode::<A, PAD>(buffer, wrap)?;
6492        let compacted = compact_wrapped_input(buffer, wrap)?;
6493        let len = Self::decode_slice_to_start(&mut buffer[..compacted])?;
6494        Ok(&mut buffer[..len])
6495    }
6496
6497    /// Decodes `buffer` in place using a strict line-wrapped profile and clears
6498    /// all bytes after the decoded prefix.
6499    ///
6500    /// If validation or decoding fails, the entire buffer is cleared before the
6501    /// error is returned.
6502    ///
6503    /// # Examples
6504    ///
6505    /// ```
6506    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
6507    ///
6508    /// let mut buffer = *b"aGVs\nbG8=";
6509    /// let len = STANDARD
6510    ///     .decode_in_place_wrapped_clear_tail(&mut buffer, LineWrap::new(4, LineEnding::Lf))
6511    ///     .unwrap()
6512    ///     .len();
6513    ///
6514    /// assert_eq!(&buffer[..len], b"hello");
6515    /// assert!(buffer[len..].iter().all(|byte| *byte == 0));
6516    /// ```
6517    pub fn decode_in_place_wrapped_clear_tail<'a>(
6518        &self,
6519        buffer: &'a mut [u8],
6520        wrap: LineWrap,
6521    ) -> Result<&'a mut [u8], DecodeError> {
6522        if let Err(err) = validate_wrapped_decode::<A, PAD>(buffer, wrap) {
6523            wipe_bytes(buffer);
6524            return Err(err);
6525        }
6526
6527        let compacted = match compact_wrapped_input(buffer, wrap) {
6528            Ok(compacted) => compacted,
6529            Err(err) => {
6530                wipe_bytes(buffer);
6531                return Err(err);
6532            }
6533        };
6534
6535        let len = match Self::decode_slice_to_start(&mut buffer[..compacted]) {
6536            Ok(len) => len,
6537            Err(err) => {
6538                wipe_bytes(buffer);
6539                return Err(err);
6540            }
6541        };
6542        wipe_tail(buffer, len);
6543        Ok(&mut buffer[..len])
6544    }
6545
6546    /// Decodes the buffer in place and returns the decoded prefix.
6547    ///
6548    /// On success, bytes after the returned decoded prefix may retain encoded
6549    /// input bytes. Use [`Self::decode_in_place_clear_tail`] when the buffer
6550    /// may be reused or freed without a caller-managed wipe.
6551    ///
6552    /// # Security
6553    ///
6554    /// This default scalar decoder prioritizes strict validation, exact error
6555    /// reporting, and ordinary throughput. It may branch or return early based
6556    /// on malformed input and reports exact failure positions and invalid byte
6557    /// values through [`DecodeError`]. Do not use this method for token
6558    /// comparison, key-material decoding, or secret-bearing validation where
6559    /// malformed-input timing matters.
6560    ///
6561    /// # Examples
6562    ///
6563    /// ```
6564    /// use base64_ng::STANDARD_NO_PAD;
6565    ///
6566    /// let mut buffer = *b"Zm9vYmFy";
6567    /// let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
6568    /// assert_eq!(decoded, b"foobar");
6569    /// ```
6570    pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
6571        let len = Self::decode_slice_to_start(buffer)?;
6572        Ok(&mut buffer[..len])
6573    }
6574
6575    /// Decodes the buffer in place and clears all bytes after the decoded prefix.
6576    ///
6577    /// If decoding fails, the entire buffer is cleared before the error is
6578    /// returned. Use this variant when the encoded or partially decoded data is
6579    /// sensitive and the caller wants best-effort cleanup without adding a
6580    /// dependency.
6581    ///
6582    /// # Examples
6583    ///
6584    /// ```
6585    /// use base64_ng::STANDARD;
6586    ///
6587    /// let mut buffer = *b"aGk=";
6588    /// let decoded = STANDARD.decode_in_place_clear_tail(&mut buffer).unwrap();
6589    /// assert_eq!(decoded, b"hi");
6590    /// ```
6591    pub fn decode_in_place_clear_tail<'a>(
6592        &self,
6593        buffer: &'a mut [u8],
6594    ) -> Result<&'a mut [u8], DecodeError> {
6595        let len = match Self::decode_slice_to_start(buffer) {
6596            Ok(len) => len,
6597            Err(err) => {
6598                wipe_bytes(buffer);
6599                return Err(err);
6600            }
6601        };
6602        wipe_tail(buffer, len);
6603        Ok(&mut buffer[..len])
6604    }
6605
6606    /// Decodes `buffer` in place using the explicit legacy whitespace profile.
6607    ///
6608    /// Ignored whitespace is compacted out before decoding. If validation
6609    /// fails, the buffer contents are unspecified. On success, bytes after the
6610    /// returned decoded prefix may retain the compacted encoded
6611    /// representation. Use [`Self::decode_in_place_legacy_clear_tail`] when the
6612    /// buffer may be reused or freed without a caller-managed wipe.
6613    pub fn decode_in_place_legacy<'a>(
6614        &self,
6615        buffer: &'a mut [u8],
6616    ) -> Result<&'a mut [u8], DecodeError> {
6617        let _required = validate_legacy_decode::<A, PAD>(buffer)?;
6618        let mut write = 0;
6619        let mut read = 0;
6620        while read < buffer.len() {
6621            let byte = buffer[read];
6622            if !is_legacy_whitespace(byte) {
6623                buffer[write] = byte;
6624                write += 1;
6625            }
6626            read += 1;
6627        }
6628        let len = Self::decode_slice_to_start(&mut buffer[..write])?;
6629        Ok(&mut buffer[..len])
6630    }
6631
6632    /// Decodes `buffer` in place using the explicit legacy whitespace profile
6633    /// and clears all bytes after the decoded prefix.
6634    ///
6635    /// If validation or decoding fails, the entire buffer is cleared before the
6636    /// error is returned.
6637    pub fn decode_in_place_legacy_clear_tail<'a>(
6638        &self,
6639        buffer: &'a mut [u8],
6640    ) -> Result<&'a mut [u8], DecodeError> {
6641        if let Err(err) = validate_legacy_decode::<A, PAD>(buffer) {
6642            wipe_bytes(buffer);
6643            return Err(err);
6644        }
6645
6646        let mut write = 0;
6647        let mut read = 0;
6648        while read < buffer.len() {
6649            let byte = buffer[read];
6650            if !is_legacy_whitespace(byte) {
6651                buffer[write] = byte;
6652                write += 1;
6653            }
6654            read += 1;
6655        }
6656
6657        let len = match Self::decode_slice_to_start(&mut buffer[..write]) {
6658            Ok(len) => len,
6659            Err(err) => {
6660                wipe_bytes(buffer);
6661                return Err(err);
6662            }
6663        };
6664        wipe_tail(buffer, len);
6665        Ok(&mut buffer[..len])
6666    }
6667
6668    fn decode_slice_to_start(buffer: &mut [u8]) -> Result<usize, DecodeError> {
6669        let _required = validate_decode::<A, PAD>(buffer)?;
6670        let input_len = buffer.len();
6671        let mut read = 0;
6672        let mut write = 0;
6673        while read + 4 <= input_len {
6674            let chunk = read_quad(buffer, read)?;
6675            let available = buffer.len();
6676            let output_tail = buffer.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
6677                required: write,
6678                available,
6679            })?;
6680            let written = decode_chunk::<A, PAD>(chunk, output_tail)
6681                .map_err(|err| err.with_index_offset(read))?;
6682            read += 4;
6683            write += written;
6684            if written < 3 {
6685                if read != input_len {
6686                    return Err(DecodeError::InvalidPadding { index: read - 4 });
6687                }
6688                return Ok(write);
6689            }
6690        }
6691
6692        let rem = input_len - read;
6693        if rem == 0 {
6694            return Ok(write);
6695        }
6696        if PAD {
6697            return Err(DecodeError::InvalidLength);
6698        }
6699        let mut tail = [0u8; 3];
6700        tail[..rem].copy_from_slice(&buffer[read..input_len]);
6701        decode_tail_unpadded::<A>(&tail[..rem], &mut buffer[write..])
6702            .map_err(|err| err.with_index_offset(read))
6703            .map(|n| write + n)
6704    }
6705}
6706
6707fn write_wrapped_bytes(
6708    input: &[u8],
6709    output: &mut [u8],
6710    output_offset: &mut usize,
6711    column: &mut usize,
6712    wrap: LineWrap,
6713) -> Result<(), EncodeError> {
6714    for byte in input {
6715        write_wrapped_byte(*byte, output, output_offset, column, wrap)?;
6716    }
6717    Ok(())
6718}
6719
6720fn write_wrapped_byte(
6721    byte: u8,
6722    output: &mut [u8],
6723    output_offset: &mut usize,
6724    column: &mut usize,
6725    wrap: LineWrap,
6726) -> Result<(), EncodeError> {
6727    if *column == wrap.line_len {
6728        let line_ending = wrap.line_ending.as_bytes();
6729        let mut index = 0;
6730        while index < line_ending.len() {
6731            if *output_offset >= output.len() {
6732                return Err(EncodeError::OutputTooSmall {
6733                    required: *output_offset + 1,
6734                    available: output.len(),
6735                });
6736            }
6737            output[*output_offset] = line_ending[index];
6738            *output_offset += 1;
6739            index += 1;
6740        }
6741        *column = 0;
6742    }
6743
6744    if *output_offset >= output.len() {
6745        return Err(EncodeError::OutputTooSmall {
6746            required: *output_offset + 1,
6747            available: output.len(),
6748        });
6749    }
6750    output[*output_offset] = byte;
6751    *output_offset += 1;
6752    *column += 1;
6753    Ok(())
6754}
6755
6756/// Encoding error.
6757#[derive(Clone, Copy, Debug, Eq, PartialEq)]
6758pub enum EncodeError {
6759    /// The encoded output length would overflow `usize`.
6760    LengthOverflow,
6761    /// The requested line wrapping policy is invalid.
6762    InvalidLineWrap {
6763        /// Requested line length.
6764        line_len: usize,
6765    },
6766    /// The caller-provided input length exceeds the provided buffer.
6767    InputTooLarge {
6768        /// Requested input bytes.
6769        input_len: usize,
6770        /// Available buffer bytes.
6771        buffer_len: usize,
6772    },
6773    /// The output buffer is too small.
6774    OutputTooSmall {
6775        /// Required output bytes.
6776        required: usize,
6777        /// Available output bytes.
6778        available: usize,
6779    },
6780}
6781
6782impl core::fmt::Display for EncodeError {
6783    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
6784        match self {
6785            Self::LengthOverflow => f.write_str("base64 output length overflows usize"),
6786            Self::InvalidLineWrap { line_len } => {
6787                write!(f, "base64 line wrap length {line_len} is invalid")
6788            }
6789            Self::InputTooLarge {
6790                input_len,
6791                buffer_len,
6792            } => write!(
6793                f,
6794                "base64 input length {input_len} exceeds buffer length {buffer_len}"
6795            ),
6796            Self::OutputTooSmall {
6797                required,
6798                available,
6799            } => write!(
6800                f,
6801                "base64 output buffer too small: required {required}, available {available}"
6802            ),
6803        }
6804    }
6805}
6806
6807#[cfg(feature = "std")]
6808impl std::error::Error for EncodeError {}
6809
6810/// Alphabet validation error.
6811#[derive(Clone, Copy, Debug, Eq, PartialEq)]
6812pub enum AlphabetError {
6813    /// The alphabet contains a non-visible-ASCII byte.
6814    InvalidByte {
6815        /// Byte index in the alphabet table.
6816        index: usize,
6817        /// Invalid byte value.
6818        byte: u8,
6819    },
6820    /// The alphabet contains the padding byte `=`.
6821    PaddingByte {
6822        /// Byte index in the alphabet table.
6823        index: usize,
6824    },
6825    /// The alphabet maps more than one value to the same byte.
6826    DuplicateByte {
6827        /// First byte index.
6828        first: usize,
6829        /// Second byte index.
6830        second: usize,
6831        /// Duplicated byte value.
6832        byte: u8,
6833    },
6834}
6835
6836impl core::fmt::Display for AlphabetError {
6837    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
6838        match self {
6839            Self::InvalidByte { index, byte } => {
6840                write!(
6841                    f,
6842                    "invalid base64 alphabet byte 0x{byte:02x} at index {index}"
6843                )
6844            }
6845            Self::PaddingByte { index } => {
6846                write!(f, "base64 alphabet contains padding byte at index {index}")
6847            }
6848            Self::DuplicateByte {
6849                first,
6850                second,
6851                byte,
6852            } => write!(
6853                f,
6854                "base64 alphabet byte 0x{byte:02x} is duplicated at indexes {first} and {second}"
6855            ),
6856        }
6857    }
6858}
6859
6860#[cfg(feature = "std")]
6861impl std::error::Error for AlphabetError {}
6862
6863/// Decoding error.
6864#[derive(Clone, Copy, Debug, Eq, PartialEq)]
6865pub enum DecodeError {
6866    /// The encoded input is malformed, but the decoder intentionally does not
6867    /// disclose a more specific error class.
6868    InvalidInput,
6869    /// The encoded input length is impossible for the selected padding policy.
6870    InvalidLength,
6871    /// A byte is not valid for the selected alphabet.
6872    InvalidByte {
6873        /// Byte index in the input.
6874        index: usize,
6875        /// Invalid byte value.
6876        byte: u8,
6877    },
6878    /// Padding is missing, misplaced, or non-canonical.
6879    InvalidPadding {
6880        /// Byte index where padding became invalid.
6881        index: usize,
6882    },
6883    /// Line wrapping is missing, misplaced, or uses the wrong line ending.
6884    InvalidLineWrap {
6885        /// Byte index where line wrapping became invalid.
6886        index: usize,
6887    },
6888    /// The output buffer is too small.
6889    OutputTooSmall {
6890        /// Required output bytes.
6891        required: usize,
6892        /// Available output bytes.
6893        available: usize,
6894    },
6895}
6896
6897impl core::fmt::Display for DecodeError {
6898    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
6899        match self {
6900            Self::InvalidInput => f.write_str("malformed base64 input"),
6901            Self::InvalidLength => f.write_str("invalid base64 input length"),
6902            Self::InvalidByte { index, byte } => {
6903                write!(f, "invalid base64 byte 0x{byte:02x} at index {index}")
6904            }
6905            Self::InvalidPadding { index } => write!(f, "invalid base64 padding at index {index}"),
6906            Self::InvalidLineWrap { index } => {
6907                write!(f, "invalid base64 line wrapping at index {index}")
6908            }
6909            Self::OutputTooSmall {
6910                required,
6911                available,
6912            } => write!(
6913                f,
6914                "base64 decode output buffer too small: required {required}, available {available}"
6915            ),
6916        }
6917    }
6918}
6919
6920impl DecodeError {
6921    fn with_index_offset(self, offset: usize) -> Self {
6922        match self {
6923            Self::InvalidByte { index, byte } => Self::InvalidByte {
6924                index: index + offset,
6925                byte,
6926            },
6927            Self::InvalidPadding { index } => Self::InvalidPadding {
6928                index: index + offset,
6929            },
6930            Self::InvalidLineWrap { index } => Self::InvalidLineWrap {
6931                index: index + offset,
6932            },
6933            Self::InvalidInput | Self::InvalidLength | Self::OutputTooSmall { .. } => self,
6934        }
6935    }
6936}
6937
6938#[cfg(feature = "std")]
6939impl std::error::Error for DecodeError {}
6940
6941fn validate_legacy_decode<A: Alphabet, const PAD: bool>(
6942    input: &[u8],
6943) -> Result<usize, DecodeError> {
6944    let mut chunk = [0u8; 4];
6945    let mut indexes = [0usize; 4];
6946    let mut chunk_len = 0;
6947    let mut required = 0;
6948    let mut terminal_seen = false;
6949
6950    for (index, byte) in input.iter().copied().enumerate() {
6951        if is_legacy_whitespace(byte) {
6952            continue;
6953        }
6954        if terminal_seen {
6955            return Err(DecodeError::InvalidPadding { index });
6956        }
6957
6958        chunk[chunk_len] = byte;
6959        indexes[chunk_len] = index;
6960        chunk_len += 1;
6961
6962        if chunk_len == 4 {
6963            let written =
6964                validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
6965            required += written;
6966            terminal_seen = written < 3;
6967            chunk_len = 0;
6968        }
6969    }
6970
6971    if chunk_len == 0 {
6972        return Ok(required);
6973    }
6974    if PAD {
6975        return Err(DecodeError::InvalidLength);
6976    }
6977
6978    validate_tail_unpadded::<A>(&chunk[..chunk_len])
6979        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
6980    Ok(required + decoded_capacity(chunk_len))
6981}
6982
6983fn decode_legacy_to_slice<A: Alphabet, const PAD: bool>(
6984    input: &[u8],
6985    output: &mut [u8],
6986) -> Result<usize, DecodeError> {
6987    let mut chunk = [0u8; 4];
6988    let mut indexes = [0usize; 4];
6989    let mut chunk_len = 0;
6990    let mut write = 0;
6991    let mut terminal_seen = false;
6992
6993    for (index, byte) in input.iter().copied().enumerate() {
6994        if is_legacy_whitespace(byte) {
6995            continue;
6996        }
6997        if terminal_seen {
6998            return Err(DecodeError::InvalidPadding { index });
6999        }
7000
7001        chunk[chunk_len] = byte;
7002        indexes[chunk_len] = index;
7003        chunk_len += 1;
7004
7005        if chunk_len == 4 {
7006            let available = output.len();
7007            let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
7008                required: write,
7009                available,
7010            })?;
7011            let written = decode_chunk::<A, PAD>(chunk, output_tail)
7012                .map_err(|err| map_chunk_error(err, &indexes))?;
7013            write += written;
7014            terminal_seen = written < 3;
7015            chunk_len = 0;
7016        }
7017    }
7018
7019    if chunk_len == 0 {
7020        return Ok(write);
7021    }
7022    if PAD {
7023        return Err(DecodeError::InvalidLength);
7024    }
7025
7026    decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
7027        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
7028        .map(|n| write + n)
7029}
7030
7031struct WrappedBytes<'a> {
7032    input: &'a [u8],
7033    wrap: LineWrap,
7034    index: usize,
7035    line_len: usize,
7036}
7037
7038impl<'a> WrappedBytes<'a> {
7039    const fn new(input: &'a [u8], wrap: LineWrap) -> Result<Self, DecodeError> {
7040        if wrap.line_len == 0 {
7041            return Err(DecodeError::InvalidLineWrap { index: 0 });
7042        }
7043        Ok(Self {
7044            input,
7045            wrap,
7046            index: 0,
7047            line_len: 0,
7048        })
7049    }
7050
7051    fn next_byte(&mut self) -> Result<Option<(usize, u8)>, DecodeError> {
7052        loop {
7053            if self.index == self.input.len() {
7054                return Ok(None);
7055            }
7056
7057            if self.starts_with_line_ending() {
7058                let line_end_index = self.index;
7059                if self.line_len == 0 {
7060                    return Err(DecodeError::InvalidLineWrap {
7061                        index: line_end_index,
7062                    });
7063                }
7064
7065                self.index += self.wrap.line_ending.byte_len();
7066                if self.index == self.input.len() {
7067                    self.line_len = 0;
7068                    return Ok(None);
7069                }
7070
7071                if self.line_len != self.wrap.line_len {
7072                    return Err(DecodeError::InvalidLineWrap {
7073                        index: line_end_index,
7074                    });
7075                }
7076                self.line_len = 0;
7077                continue;
7078            }
7079
7080            let byte = self.input[self.index];
7081            if matches!(byte, b'\r' | b'\n') {
7082                return Err(DecodeError::InvalidLineWrap { index: self.index });
7083            }
7084
7085            self.line_len += 1;
7086            if self.line_len > self.wrap.line_len {
7087                return Err(DecodeError::InvalidLineWrap { index: self.index });
7088            }
7089
7090            let index = self.index;
7091            self.index += 1;
7092            return Ok(Some((index, byte)));
7093        }
7094    }
7095
7096    fn starts_with_line_ending(&self) -> bool {
7097        let line_ending = self.wrap.line_ending.as_bytes();
7098        let end = self.index + line_ending.len();
7099        end <= self.input.len() && &self.input[self.index..end] == line_ending
7100    }
7101}
7102
7103fn validate_wrapped_decode<A: Alphabet, const PAD: bool>(
7104    input: &[u8],
7105    wrap: LineWrap,
7106) -> Result<usize, DecodeError> {
7107    let mut bytes = WrappedBytes::new(input, wrap)?;
7108    let mut chunk = [0u8; 4];
7109    let mut indexes = [0usize; 4];
7110    let mut chunk_len = 0;
7111    let mut required = 0;
7112    let mut terminal_seen = false;
7113
7114    while let Some((index, byte)) = bytes.next_byte()? {
7115        if terminal_seen {
7116            return Err(DecodeError::InvalidPadding { index });
7117        }
7118
7119        chunk[chunk_len] = byte;
7120        indexes[chunk_len] = index;
7121        chunk_len += 1;
7122
7123        if chunk_len == 4 {
7124            let written =
7125                validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
7126            required += written;
7127            terminal_seen = written < 3;
7128            chunk_len = 0;
7129        }
7130    }
7131
7132    if chunk_len == 0 {
7133        return Ok(required);
7134    }
7135    if PAD {
7136        return Err(DecodeError::InvalidLength);
7137    }
7138
7139    validate_tail_unpadded::<A>(&chunk[..chunk_len])
7140        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
7141    Ok(required + decoded_capacity(chunk_len))
7142}
7143
7144fn decode_wrapped_to_slice<A: Alphabet, const PAD: bool>(
7145    input: &[u8],
7146    output: &mut [u8],
7147    wrap: LineWrap,
7148) -> Result<usize, DecodeError> {
7149    let mut bytes = WrappedBytes::new(input, wrap)?;
7150    let mut chunk = [0u8; 4];
7151    let mut indexes = [0usize; 4];
7152    let mut chunk_len = 0;
7153    let mut write = 0;
7154    let mut terminal_seen = false;
7155
7156    while let Some((index, byte)) = bytes.next_byte()? {
7157        if terminal_seen {
7158            return Err(DecodeError::InvalidPadding { index });
7159        }
7160
7161        chunk[chunk_len] = byte;
7162        indexes[chunk_len] = index;
7163        chunk_len += 1;
7164
7165        if chunk_len == 4 {
7166            let available = output.len();
7167            let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
7168                required: write,
7169                available,
7170            })?;
7171            let written = decode_chunk::<A, PAD>(chunk, output_tail)
7172                .map_err(|err| map_chunk_error(err, &indexes))?;
7173            write += written;
7174            terminal_seen = written < 3;
7175            chunk_len = 0;
7176        }
7177    }
7178
7179    if chunk_len == 0 {
7180        return Ok(write);
7181    }
7182    if PAD {
7183        return Err(DecodeError::InvalidLength);
7184    }
7185
7186    decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
7187        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
7188        .map(|n| write + n)
7189}
7190
7191fn compact_wrapped_input(buffer: &mut [u8], wrap: LineWrap) -> Result<usize, DecodeError> {
7192    if !wrap.is_valid() {
7193        return Err(DecodeError::InvalidLineWrap { index: 0 });
7194    }
7195
7196    let line_ending = wrap.line_ending.as_bytes();
7197    let line_ending_len = line_ending.len();
7198    let mut read = 0;
7199    let mut write = 0;
7200
7201    while read < buffer.len() {
7202        let line_end = read + line_ending_len;
7203        if buffer.get(read..line_end) == Some(line_ending) {
7204            read = line_end;
7205            continue;
7206        }
7207
7208        buffer[write] = buffer[read];
7209        write += 1;
7210        read += 1;
7211    }
7212
7213    Ok(write)
7214}
7215
7216#[inline]
7217const fn is_legacy_whitespace(byte: u8) -> bool {
7218    matches!(byte, b' ' | b'\t' | b'\r' | b'\n')
7219}
7220
7221fn map_chunk_error(err: DecodeError, indexes: &[usize; 4]) -> DecodeError {
7222    match err {
7223        DecodeError::InvalidByte { index, byte } => DecodeError::InvalidByte {
7224            index: indexes[index],
7225            byte,
7226        },
7227        DecodeError::InvalidPadding { index } => DecodeError::InvalidPadding {
7228            index: indexes[index],
7229        },
7230        DecodeError::InvalidInput
7231        | DecodeError::InvalidLineWrap { .. }
7232        | DecodeError::InvalidLength
7233        | DecodeError::OutputTooSmall { .. } => err,
7234    }
7235}
7236
7237fn map_partial_chunk_error(err: DecodeError, indexes: &[usize; 4], len: usize) -> DecodeError {
7238    match err {
7239        DecodeError::InvalidByte { index, byte } if index < len => DecodeError::InvalidByte {
7240            index: indexes[index],
7241            byte,
7242        },
7243        DecodeError::InvalidPadding { index } if index < len => DecodeError::InvalidPadding {
7244            index: indexes[index],
7245        },
7246        DecodeError::InvalidByte { .. }
7247        | DecodeError::InvalidPadding { .. }
7248        | DecodeError::InvalidLineWrap { .. }
7249        | DecodeError::InvalidInput
7250        | DecodeError::InvalidLength
7251        | DecodeError::OutputTooSmall { .. } => err,
7252    }
7253}
7254
7255fn decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
7256    if !input.len().is_multiple_of(4) {
7257        return Err(DecodeError::InvalidLength);
7258    }
7259    let required = decoded_len_padded(input)?;
7260    if output.len() < required {
7261        return Err(DecodeError::OutputTooSmall {
7262            required,
7263            available: output.len(),
7264        });
7265    }
7266
7267    let mut read = 0;
7268    let mut write = 0;
7269    while read < input.len() {
7270        let chunk = read_quad(input, read)?;
7271        let available = output.len();
7272        let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
7273            required: write,
7274            available,
7275        })?;
7276        let written = decode_chunk::<A, true>(chunk, output_tail)
7277            .map_err(|err| err.with_index_offset(read))?;
7278        read += 4;
7279        write += written;
7280        if written < 3 && read != input.len() {
7281            return Err(DecodeError::InvalidPadding { index: read - 4 });
7282        }
7283    }
7284    Ok(write)
7285}
7286
7287fn validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
7288    if input.is_empty() {
7289        return Ok(0);
7290    }
7291
7292    if PAD {
7293        validate_padded::<A>(input)
7294    } else {
7295        validate_unpadded::<A>(input)
7296    }
7297}
7298
7299fn validate_padded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
7300    if !input.len().is_multiple_of(4) {
7301        return Err(DecodeError::InvalidLength);
7302    }
7303    let required = decoded_len_padded(input)?;
7304
7305    let mut read = 0;
7306    while read < input.len() {
7307        let chunk = read_quad(input, read)?;
7308        let written =
7309            validate_chunk::<A, true>(chunk).map_err(|err| err.with_index_offset(read))?;
7310        read += 4;
7311        if written < 3 && read != input.len() {
7312            return Err(DecodeError::InvalidPadding { index: read - 4 });
7313        }
7314    }
7315
7316    Ok(required)
7317}
7318
7319fn validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
7320    let required = decoded_len_unpadded(input)?;
7321
7322    let mut read = 0;
7323    while read + 4 <= input.len() {
7324        let chunk = read_quad(input, read)?;
7325        validate_chunk::<A, false>(chunk).map_err(|err| err.with_index_offset(read))?;
7326        read += 4;
7327    }
7328    validate_tail_unpadded::<A>(&input[read..]).map_err(|err| err.with_index_offset(read))?;
7329
7330    Ok(required)
7331}
7332
7333fn decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
7334    let required = decoded_len_unpadded(input)?;
7335    if output.len() < required {
7336        return Err(DecodeError::OutputTooSmall {
7337            required,
7338            available: output.len(),
7339        });
7340    }
7341
7342    let mut read = 0;
7343    let mut write = 0;
7344    while read + 4 <= input.len() {
7345        let chunk = read_quad(input, read)?;
7346        let available = output.len();
7347        let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
7348            required: write,
7349            available,
7350        })?;
7351        let written = decode_chunk::<A, false>(chunk, output_tail)
7352            .map_err(|err| err.with_index_offset(read))?;
7353        read += 4;
7354        write += written;
7355    }
7356    decode_tail_unpadded::<A>(&input[read..], &mut output[write..])
7357        .map_err(|err| err.with_index_offset(read))
7358        .map(|n| write + n)
7359}
7360
7361fn decoded_len_padded(input: &[u8]) -> Result<usize, DecodeError> {
7362    if input.is_empty() {
7363        return Ok(0);
7364    }
7365    if !input.len().is_multiple_of(4) {
7366        return Err(DecodeError::InvalidLength);
7367    }
7368
7369    let Some((&last, before_last_prefix)) = input.split_last() else {
7370        return Ok(0);
7371    };
7372    let Some(&before_last) = before_last_prefix.last() else {
7373        return Err(DecodeError::InvalidLength);
7374    };
7375
7376    let mut padding = 0;
7377    if last == b'=' {
7378        padding += 1;
7379    }
7380    if before_last == b'=' {
7381        padding += 1;
7382    }
7383    if padding == 0
7384        && let Some(index) = input.iter().position(|byte| *byte == b'=')
7385    {
7386        return Err(DecodeError::InvalidPadding { index });
7387    }
7388    if padding > 0 {
7389        let first_pad = input.len() - padding;
7390        if let Some(index) = input[..first_pad].iter().position(|byte| *byte == b'=') {
7391            return Err(DecodeError::InvalidPadding { index });
7392        }
7393    }
7394    Ok(input.len() / 4 * 3 - padding)
7395}
7396
7397fn decoded_len_unpadded(input: &[u8]) -> Result<usize, DecodeError> {
7398    if input.len() % 4 == 1 {
7399        return Err(DecodeError::InvalidLength);
7400    }
7401    if let Some(index) = input.iter().position(|byte| *byte == b'=') {
7402        return Err(DecodeError::InvalidPadding { index });
7403    }
7404    Ok(decoded_capacity(input.len()))
7405}
7406
7407fn read_quad(input: &[u8], offset: usize) -> Result<[u8; 4], DecodeError> {
7408    let end = offset.checked_add(4).ok_or(DecodeError::InvalidLength)?;
7409    match input.get(offset..end) {
7410        Some([b0, b1, b2, b3]) => Ok([*b0, *b1, *b2, *b3]),
7411        _ => Err(DecodeError::InvalidLength),
7412    }
7413}
7414
7415fn first_padding_index_unchecked(input: [u8; 4]) -> usize {
7416    let [b0, b1, b2, b3] = input;
7417    if b0 == b'=' {
7418        0
7419    } else if b1 == b'=' {
7420        1
7421    } else if b2 == b'=' {
7422        2
7423    } else if b3 == b'=' {
7424        3
7425    } else {
7426        debug_assert!(
7427            false,
7428            "first_padding_index_unchecked called with no padding"
7429        );
7430        4
7431    }
7432}
7433
7434fn validate_chunk<A: Alphabet, const PAD: bool>(input: [u8; 4]) -> Result<usize, DecodeError> {
7435    let [b0, b1, b2, b3] = input;
7436    let _v0 = decode_byte::<A>(b0, 0)?;
7437    let v1 = decode_byte::<A>(b1, 1)?;
7438
7439    match (b2, b3) {
7440        (b'=', b'=') if PAD => {
7441            if v1 & 0b0000_1111 != 0 {
7442                return Err(DecodeError::InvalidPadding { index: 1 });
7443            }
7444            Ok(1)
7445        }
7446        (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
7447        (_, b'=') if PAD => {
7448            let v2 = decode_byte::<A>(b2, 2)?;
7449            if v2 & 0b0000_0011 != 0 {
7450                return Err(DecodeError::InvalidPadding { index: 2 });
7451            }
7452            Ok(2)
7453        }
7454        (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
7455            index: first_padding_index_unchecked(input),
7456        }),
7457        _ => {
7458            decode_byte::<A>(b2, 2)?;
7459            decode_byte::<A>(b3, 3)?;
7460            Ok(3)
7461        }
7462    }
7463}
7464
7465fn decode_chunk<A: Alphabet, const PAD: bool>(
7466    input: [u8; 4],
7467    output: &mut [u8],
7468) -> Result<usize, DecodeError> {
7469    let [b0, b1, b2, b3] = input;
7470    let v0 = decode_byte::<A>(b0, 0)?;
7471    let v1 = decode_byte::<A>(b1, 1)?;
7472
7473    match (b2, b3) {
7474        (b'=', b'=') if PAD => {
7475            if output.is_empty() {
7476                return Err(DecodeError::OutputTooSmall {
7477                    required: 1,
7478                    available: output.len(),
7479                });
7480            }
7481            if v1 & 0b0000_1111 != 0 {
7482                return Err(DecodeError::InvalidPadding { index: 1 });
7483            }
7484            output[0] = (v0 << 2) | (v1 >> 4);
7485            Ok(1)
7486        }
7487        (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
7488        (_, b'=') if PAD => {
7489            if output.len() < 2 {
7490                return Err(DecodeError::OutputTooSmall {
7491                    required: 2,
7492                    available: output.len(),
7493                });
7494            }
7495            let v2 = decode_byte::<A>(b2, 2)?;
7496            if v2 & 0b0000_0011 != 0 {
7497                return Err(DecodeError::InvalidPadding { index: 2 });
7498            }
7499            output[0] = (v0 << 2) | (v1 >> 4);
7500            output[1] = (v1 << 4) | (v2 >> 2);
7501            Ok(2)
7502        }
7503        (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
7504            index: first_padding_index_unchecked(input),
7505        }),
7506        _ => {
7507            if output.len() < 3 {
7508                return Err(DecodeError::OutputTooSmall {
7509                    required: 3,
7510                    available: output.len(),
7511                });
7512            }
7513            let v2 = decode_byte::<A>(b2, 2)?;
7514            let v3 = decode_byte::<A>(b3, 3)?;
7515            output[0] = (v0 << 2) | (v1 >> 4);
7516            output[1] = (v1 << 4) | (v2 >> 2);
7517            output[2] = (v2 << 6) | v3;
7518            Ok(3)
7519        }
7520    }
7521}
7522
7523fn validate_tail_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
7524    match input {
7525        [] => Ok(()),
7526        [b0, b1] => {
7527            decode_byte::<A>(*b0, 0)?;
7528            let v1 = decode_byte::<A>(*b1, 1)?;
7529            if v1 & 0b0000_1111 != 0 {
7530                return Err(DecodeError::InvalidPadding { index: 1 });
7531            }
7532            Ok(())
7533        }
7534        [b0, b1, b2] => {
7535            decode_byte::<A>(*b0, 0)?;
7536            decode_byte::<A>(*b1, 1)?;
7537            let v2 = decode_byte::<A>(*b2, 2)?;
7538            if v2 & 0b0000_0011 != 0 {
7539                return Err(DecodeError::InvalidPadding { index: 2 });
7540            }
7541            Ok(())
7542        }
7543        _ => Err(DecodeError::InvalidLength),
7544    }
7545}
7546
7547fn decode_tail_unpadded<A: Alphabet>(
7548    input: &[u8],
7549    output: &mut [u8],
7550) -> Result<usize, DecodeError> {
7551    match input {
7552        [] => Ok(0),
7553        [b0, b1] => {
7554            let Some(out0) = output.first_mut() else {
7555                return Err(DecodeError::OutputTooSmall {
7556                    required: 1,
7557                    available: output.len(),
7558                });
7559            };
7560            let v0 = decode_byte::<A>(*b0, 0)?;
7561            let v1 = decode_byte::<A>(*b1, 1)?;
7562            if v1 & 0b0000_1111 != 0 {
7563                return Err(DecodeError::InvalidPadding { index: 1 });
7564            }
7565            *out0 = (v0 << 2) | (v1 >> 4);
7566            Ok(1)
7567        }
7568        [b0, b1, b2] => {
7569            let available = output.len();
7570            let Some([out0, out1]) = output.get_mut(..2) else {
7571                return Err(DecodeError::OutputTooSmall {
7572                    required: 2,
7573                    available,
7574                });
7575            };
7576            let v0 = decode_byte::<A>(*b0, 0)?;
7577            let v1 = decode_byte::<A>(*b1, 1)?;
7578            let v2 = decode_byte::<A>(*b2, 2)?;
7579            if v2 & 0b0000_0011 != 0 {
7580                return Err(DecodeError::InvalidPadding { index: 2 });
7581            }
7582            *out0 = (v0 << 2) | (v1 >> 4);
7583            *out1 = (v1 << 4) | (v2 >> 2);
7584            Ok(2)
7585        }
7586        _ => Err(DecodeError::InvalidLength),
7587    }
7588}
7589
7590fn decode_byte<A: Alphabet>(byte: u8, index: usize) -> Result<u8, DecodeError> {
7591    A::decode(byte).ok_or(DecodeError::InvalidByte { index, byte })
7592}
7593
7594fn ct_decode_slice<A: Alphabet, const PAD: bool>(
7595    input: &[u8],
7596    output: &mut [u8],
7597) -> Result<usize, DecodeError> {
7598    if input.is_empty() {
7599        return Ok(0);
7600    }
7601
7602    if PAD {
7603        ct_decode_padded::<A>(input, output)
7604    } else {
7605        ct_decode_unpadded::<A>(input, output)
7606    }
7607}
7608
7609fn ct_decode_slice_staged_clear_tail<A: Alphabet, const PAD: bool>(
7610    input: &[u8],
7611    output: &mut [u8],
7612    staging: &mut [u8],
7613) -> Result<usize, DecodeError> {
7614    let required = match ct_decoded_len::<A, PAD>(input) {
7615        Ok(required) => required,
7616        Err(err) => {
7617            wipe_bytes(output);
7618            wipe_bytes(staging);
7619            return Err(err);
7620        }
7621    };
7622
7623    if output.len() < required {
7624        wipe_bytes(output);
7625        wipe_bytes(staging);
7626        return Err(DecodeError::OutputTooSmall {
7627            required,
7628            available: output.len(),
7629        });
7630    }
7631
7632    if staging.len() < required {
7633        wipe_bytes(output);
7634        wipe_bytes(staging);
7635        return Err(DecodeError::OutputTooSmall {
7636            required,
7637            available: staging.len(),
7638        });
7639    }
7640
7641    let written = match ct_decode_slice::<A, PAD>(input, &mut staging[..required]) {
7642        Ok(written) => written,
7643        Err(err) => {
7644            wipe_bytes(output);
7645            wipe_bytes(staging);
7646            return Err(err);
7647        }
7648    };
7649
7650    output[..written].copy_from_slice(&staging[..written]);
7651    wipe_bytes(staging);
7652    wipe_tail(output, written);
7653    Ok(written)
7654}
7655
7656fn ct_decode_in_place<A: Alphabet, const PAD: bool>(
7657    buffer: &mut [u8],
7658) -> Result<usize, DecodeError> {
7659    if buffer.is_empty() {
7660        return Ok(0);
7661    }
7662
7663    if PAD {
7664        ct_decode_padded_in_place::<A>(buffer)
7665    } else {
7666        ct_decode_unpadded_in_place::<A>(buffer)
7667    }
7668}
7669
7670#[inline(never)]
7671#[allow(unsafe_code)]
7672fn ct_error_gate_barrier(invalid_byte: u8, invalid_padding: u8) {
7673    core::hint::black_box(invalid_byte | invalid_padding);
7674    core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
7675
7676    #[cfg(all(not(miri), any(target_arch = "x86", target_arch = "x86_64")))]
7677    {
7678        // SAFETY: `lfence` does not access memory and is used as a speculation
7679        // barrier before the public success/failure branch is observed.
7680        unsafe {
7681            core::arch::asm!("lfence", options(nostack, preserves_flags, nomem));
7682        }
7683    }
7684
7685    #[cfg(all(not(miri), target_arch = "aarch64"))]
7686    {
7687        // SAFETY: these barriers do not access memory. `isb sy` is a
7688        // best-effort speculation boundary and `hint #20` is the architectural
7689        // CSDB hint encoding on AArch64.
7690        unsafe {
7691            core::arch::asm!("isb sy", "hint #20", options(nostack, preserves_flags));
7692        }
7693    }
7694
7695    #[cfg(all(not(miri), target_arch = "arm"))]
7696    {
7697        // SAFETY: `isb sy` does not access memory and is used as the best
7698        // available stable ARM speculation boundary for this crate.
7699        unsafe {
7700            core::arch::asm!("isb sy", options(nostack, preserves_flags));
7701        }
7702    }
7703
7704    #[cfg(all(not(miri), any(target_arch = "riscv32", target_arch = "riscv64")))]
7705    {
7706        // RISC-V base ISA does not provide a canonical speculation barrier.
7707        // `fence rw, rw` is the available ordering primitive for the CT public
7708        // result gate and is reported separately as `ordering-fence`.
7709        // SAFETY: the assembly block does not access memory.
7710        unsafe {
7711            core::arch::asm!("fence rw, rw", options(nostack, preserves_flags));
7712        }
7713    }
7714}
7715
7716fn ct_validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<(), DecodeError> {
7717    if input.is_empty() {
7718        return Ok(());
7719    }
7720
7721    if PAD {
7722        ct_validate_padded::<A>(input)
7723    } else {
7724        ct_validate_unpadded::<A>(input)
7725    }
7726}
7727
7728fn ct_decoded_len<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
7729    ct_validate_decode::<A, PAD>(input)?;
7730    if input.is_empty() {
7731        return Ok(0);
7732    }
7733
7734    if PAD {
7735        Ok(input.len() / 4 * 3 - ct_padding_len(input))
7736    } else {
7737        let full_quads = input.len() / 4 * 3;
7738        match input.len() % 4 {
7739            0 => Ok(full_quads),
7740            2 => Ok(full_quads + 1),
7741            3 => Ok(full_quads + 2),
7742            _ => Err(DecodeError::InvalidLength),
7743        }
7744    }
7745}
7746
7747fn ct_validate_padded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
7748    if !input.len().is_multiple_of(4) {
7749        return Err(DecodeError::InvalidLength);
7750    }
7751
7752    let padding = ct_padding_len(input);
7753    let mut invalid_byte = 0u8;
7754    let mut invalid_padding = 0u8;
7755    let mut read = 0;
7756
7757    while read + 4 < input.len() {
7758        let [b0, b1, b2, b3] =
7759            read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
7760        let (_, valid0) = ct_decode_alphabet_byte::<A>(b0);
7761        let (_, valid1) = ct_decode_alphabet_byte::<A>(b1);
7762        let (_, valid2) = ct_decode_alphabet_byte::<A>(b2);
7763        let (_, valid3) = ct_decode_alphabet_byte::<A>(b3);
7764
7765        invalid_byte |= !valid0;
7766        invalid_byte |= !valid1;
7767        invalid_byte |= !valid2;
7768        invalid_byte |= !valid3;
7769        invalid_padding |= ct_mask_eq_u8(b2, b'=');
7770        invalid_padding |= ct_mask_eq_u8(b3, b'=');
7771        read += 4;
7772    }
7773
7774    let final_chunk =
7775        read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
7776    let (_, final_invalid_byte, final_invalid_padding, _) =
7777        ct_padded_final_quantum::<A>(final_chunk, padding);
7778    invalid_byte |= final_invalid_byte;
7779    invalid_padding |= final_invalid_padding;
7780
7781    report_ct_error(invalid_byte, invalid_padding)
7782}
7783
7784fn ct_validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
7785    if input.len() % 4 == 1 {
7786        return Err(DecodeError::InvalidLength);
7787    }
7788
7789    let mut invalid_byte = 0u8;
7790    let mut invalid_padding = 0u8;
7791    let mut read = 0;
7792
7793    while read + 4 <= input.len() {
7794        let [b0, b1, b2, b3] =
7795            read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
7796        let (_, valid0) = ct_decode_alphabet_byte::<A>(b0);
7797        let (_, valid1) = ct_decode_alphabet_byte::<A>(b1);
7798        let (_, valid2) = ct_decode_alphabet_byte::<A>(b2);
7799        let (_, valid3) = ct_decode_alphabet_byte::<A>(b3);
7800
7801        invalid_byte |= !valid0;
7802        invalid_byte |= !valid1;
7803        invalid_byte |= !valid2;
7804        invalid_byte |= !valid3;
7805        invalid_padding |= ct_mask_eq_u8(b0, b'=');
7806        invalid_padding |= ct_mask_eq_u8(b1, b'=');
7807        invalid_padding |= ct_mask_eq_u8(b2, b'=');
7808        invalid_padding |= ct_mask_eq_u8(b3, b'=');
7809
7810        read += 4;
7811    }
7812
7813    match read_tail_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding) {
7814        [] => {}
7815        [b0, b1] => {
7816            let (_, valid0) = ct_decode_alphabet_byte::<A>(*b0);
7817            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
7818            invalid_byte |= !valid0;
7819            invalid_byte |= !valid1;
7820            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
7821            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
7822            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
7823        }
7824        [b0, b1, b2] => {
7825            let (_, valid0) = ct_decode_alphabet_byte::<A>(*b0);
7826            let (_, valid1) = ct_decode_alphabet_byte::<A>(*b1);
7827            let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
7828            invalid_byte |= !valid0;
7829            invalid_byte |= !valid1;
7830            invalid_byte |= !valid2;
7831            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
7832            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
7833            invalid_padding |= ct_mask_eq_u8(*b2, b'=');
7834            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
7835        }
7836        _ => {
7837            invalid_byte = 0xff;
7838            invalid_padding = 0xff;
7839        }
7840    }
7841
7842    report_ct_error(invalid_byte, invalid_padding)
7843}
7844
7845fn ct_padded_final_quantum<A: Alphabet>(
7846    input: [u8; 4],
7847    padding: usize,
7848) -> ([u8; 3], u8, u8, usize) {
7849    let [b0, b1, b2, b3] = input;
7850    let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
7851    let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
7852    let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
7853    let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
7854
7855    let padding_byte = match padding {
7856        0 => 0,
7857        1 => 1,
7858        2 => 2,
7859        _ => return ([0; 3], 0xff, 0xff, 0),
7860    };
7861    let no_padding = ct_mask_eq_u8(padding_byte, 0);
7862    let one_padding = ct_mask_eq_u8(padding_byte, 1);
7863    let two_padding = ct_mask_eq_u8(padding_byte, 2);
7864    let require_v2 = no_padding | one_padding;
7865    let require_v3 = no_padding;
7866
7867    let invalid_byte = !valid0 | !valid1 | (!valid2 & require_v2) | (!valid3 & require_v3);
7868    let invalid_padding = (ct_mask_nonzero_u8(v1 & 0b0000_1111) & two_padding)
7869        | ((ct_mask_eq_u8(b2, b'=') | ct_mask_nonzero_u8(v2 & 0b0000_0011)) & one_padding)
7870        | ((ct_mask_eq_u8(b2, b'=') | ct_mask_eq_u8(b3, b'=')) & no_padding);
7871
7872    (
7873        [(v0 << 2) | (v1 >> 4), (v1 << 4) | (v2 >> 2), (v2 << 6) | v3],
7874        invalid_byte,
7875        invalid_padding,
7876        3 - padding,
7877    )
7878}
7879
7880fn ct_decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
7881    if !input.len().is_multiple_of(4) {
7882        return Err(DecodeError::InvalidLength);
7883    }
7884
7885    let padding = ct_padding_len(input);
7886    let required = input.len() / 4 * 3 - padding;
7887    if output.len() < required {
7888        return Err(DecodeError::OutputTooSmall {
7889            required,
7890            available: output.len(),
7891        });
7892    }
7893
7894    let mut invalid_byte = 0u8;
7895    let mut invalid_padding = 0u8;
7896    let mut write = 0;
7897    let mut read = 0;
7898
7899    while read + 4 < input.len() {
7900        let [b0, b1, b2, b3] =
7901            read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
7902        let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
7903        let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
7904        let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
7905        let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
7906
7907        invalid_byte |= !valid0;
7908        invalid_byte |= !valid1;
7909        invalid_byte |= !valid2;
7910        invalid_byte |= !valid3;
7911        invalid_padding |= ct_mask_eq_u8(b2, b'=');
7912        invalid_padding |= ct_mask_eq_u8(b3, b'=');
7913        output[write] = (v0 << 2) | (v1 >> 4);
7914        output[write + 1] = (v1 << 4) | (v2 >> 2);
7915        output[write + 2] = (v2 << 6) | v3;
7916        write += 3;
7917        read += 4;
7918    }
7919
7920    let final_chunk =
7921        read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
7922    let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
7923        ct_padded_final_quantum::<A>(final_chunk, padding);
7924    invalid_byte |= final_invalid_byte;
7925    invalid_padding |= final_invalid_padding;
7926    output[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
7927    write += final_written;
7928
7929    report_ct_error(invalid_byte, invalid_padding)?;
7930    Ok(write)
7931}
7932
7933fn ct_decode_padded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
7934    if !buffer.len().is_multiple_of(4) {
7935        return Err(DecodeError::InvalidLength);
7936    }
7937
7938    let padding = ct_padding_len(buffer);
7939    let required = buffer.len() / 4 * 3 - padding;
7940    if required > buffer.len() {
7941        wipe_bytes(buffer);
7942        return Err(DecodeError::InvalidInput);
7943    }
7944
7945    let mut invalid_byte = 0u8;
7946    let mut invalid_padding = 0u8;
7947    let mut write = 0;
7948    let mut read = 0;
7949
7950    while read + 4 < buffer.len() {
7951        let [b0, b1, b2, b3] =
7952            read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
7953        let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
7954        let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
7955        let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
7956        let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
7957
7958        invalid_byte |= !valid0;
7959        invalid_byte |= !valid1;
7960        invalid_byte |= !valid2;
7961        invalid_byte |= !valid3;
7962        invalid_padding |= ct_mask_eq_u8(b2, b'=');
7963        invalid_padding |= ct_mask_eq_u8(b3, b'=');
7964        buffer[write] = (v0 << 2) | (v1 >> 4);
7965        buffer[write + 1] = (v1 << 4) | (v2 >> 2);
7966        buffer[write + 2] = (v2 << 6) | v3;
7967        write += 3;
7968        read += 4;
7969    }
7970
7971    let final_chunk =
7972        read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
7973    let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
7974        ct_padded_final_quantum::<A>(final_chunk, padding);
7975    invalid_byte |= final_invalid_byte;
7976    invalid_padding |= final_invalid_padding;
7977    buffer[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
7978    write += final_written;
7979
7980    if write != required {
7981        ct_error_gate_barrier(invalid_byte, invalid_padding);
7982        wipe_bytes(buffer);
7983        return Err(DecodeError::InvalidInput);
7984    }
7985    if let Err(err) = report_ct_error(invalid_byte, invalid_padding) {
7986        wipe_bytes(buffer);
7987        return Err(err);
7988    }
7989    Ok(write)
7990}
7991
7992fn ct_decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
7993    if input.len() % 4 == 1 {
7994        return Err(DecodeError::InvalidLength);
7995    }
7996
7997    let required = decoded_capacity(input.len());
7998    if output.len() < required {
7999        return Err(DecodeError::OutputTooSmall {
8000            required,
8001            available: output.len(),
8002        });
8003    }
8004
8005    let mut invalid_byte = 0u8;
8006    let mut invalid_padding = 0u8;
8007    let mut write = 0;
8008    let mut read = 0;
8009
8010    while read + 4 <= input.len() {
8011        let [b0, b1, b2, b3] =
8012            read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
8013        let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
8014        let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
8015        let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
8016        let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
8017
8018        invalid_byte |= !valid0;
8019        invalid_byte |= !valid1;
8020        invalid_byte |= !valid2;
8021        invalid_byte |= !valid3;
8022        invalid_padding |= ct_mask_eq_u8(b0, b'=');
8023        invalid_padding |= ct_mask_eq_u8(b1, b'=');
8024        invalid_padding |= ct_mask_eq_u8(b2, b'=');
8025        invalid_padding |= ct_mask_eq_u8(b3, b'=');
8026
8027        output[write] = (v0 << 2) | (v1 >> 4);
8028        output[write + 1] = (v1 << 4) | (v2 >> 2);
8029        output[write + 2] = (v2 << 6) | v3;
8030        read += 4;
8031        write += 3;
8032    }
8033
8034    match read_tail_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding) {
8035        [] => {}
8036        [b0, b1] => {
8037            let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
8038            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
8039            invalid_byte |= !valid0;
8040            invalid_byte |= !valid1;
8041            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
8042            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
8043            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
8044            output[write] = (v0 << 2) | (v1 >> 4);
8045            write += 1;
8046        }
8047        [b0, b1, b2] => {
8048            let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
8049            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
8050            let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
8051            invalid_byte |= !valid0;
8052            invalid_byte |= !valid1;
8053            invalid_byte |= !valid2;
8054            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
8055            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
8056            invalid_padding |= ct_mask_eq_u8(*b2, b'=');
8057            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
8058            output[write] = (v0 << 2) | (v1 >> 4);
8059            output[write + 1] = (v1 << 4) | (v2 >> 2);
8060            write += 2;
8061        }
8062        _ => {
8063            invalid_byte = 0xff;
8064            invalid_padding = 0xff;
8065        }
8066    }
8067
8068    report_ct_error(invalid_byte, invalid_padding)?;
8069    Ok(write)
8070}
8071
8072fn ct_decode_unpadded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
8073    if buffer.len() % 4 == 1 {
8074        return Err(DecodeError::InvalidLength);
8075    }
8076
8077    let required = decoded_capacity(buffer.len());
8078    if required > buffer.len() {
8079        wipe_bytes(buffer);
8080        return Err(DecodeError::InvalidInput);
8081    }
8082
8083    let mut invalid_byte = 0u8;
8084    let mut invalid_padding = 0u8;
8085    let mut write = 0;
8086    let mut read = 0;
8087
8088    while read + 4 <= buffer.len() {
8089        let [b0, b1, b2, b3] =
8090            read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
8091        let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
8092        let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
8093        let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
8094        let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
8095
8096        invalid_byte |= !valid0;
8097        invalid_byte |= !valid1;
8098        invalid_byte |= !valid2;
8099        invalid_byte |= !valid3;
8100        invalid_padding |= ct_mask_eq_u8(b0, b'=');
8101        invalid_padding |= ct_mask_eq_u8(b1, b'=');
8102        invalid_padding |= ct_mask_eq_u8(b2, b'=');
8103        invalid_padding |= ct_mask_eq_u8(b3, b'=');
8104
8105        buffer[write] = (v0 << 2) | (v1 >> 4);
8106        buffer[write + 1] = (v1 << 4) | (v2 >> 2);
8107        buffer[write + 2] = (v2 << 6) | v3;
8108        read += 4;
8109        write += 3;
8110    }
8111
8112    let tail = read_tail_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
8113    match tail {
8114        [] => {}
8115        [b0, b1] => {
8116            let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
8117            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
8118            invalid_byte |= !valid0;
8119            invalid_byte |= !valid1;
8120            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
8121            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
8122            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
8123            buffer[write] = (v0 << 2) | (v1 >> 4);
8124            write += 1;
8125        }
8126        [b0, b1, b2] => {
8127            let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
8128            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
8129            let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
8130            invalid_byte |= !valid0;
8131            invalid_byte |= !valid1;
8132            invalid_byte |= !valid2;
8133            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
8134            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
8135            invalid_padding |= ct_mask_eq_u8(*b2, b'=');
8136            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
8137            buffer[write] = (v0 << 2) | (v1 >> 4);
8138            buffer[write + 1] = (v1 << 4) | (v2 >> 2);
8139            write += 2;
8140        }
8141        _ => {
8142            invalid_byte = 0xff;
8143            invalid_padding = 0xff;
8144        }
8145    }
8146
8147    if write != required {
8148        ct_error_gate_barrier(invalid_byte, invalid_padding);
8149        wipe_bytes(buffer);
8150        return Err(DecodeError::InvalidInput);
8151    }
8152    if let Err(err) = report_ct_error(invalid_byte, invalid_padding) {
8153        wipe_bytes(buffer);
8154        return Err(err);
8155    }
8156    Ok(write)
8157}
8158
8159fn read_tail(input: &[u8], offset: usize) -> Result<&[u8], DecodeError> {
8160    input.get(offset..).ok_or(DecodeError::InvalidLength)
8161}
8162
8163fn read_quad_or_mark_invalid(
8164    input: &[u8],
8165    offset: usize,
8166    invalid_byte: &mut u8,
8167    invalid_padding: &mut u8,
8168) -> [u8; 4] {
8169    if let Ok(quad) = read_quad(input, offset) {
8170        quad
8171    } else {
8172        debug_assert!(
8173            false,
8174            "read_quad failed inside length-validated constant-time decode loop"
8175        );
8176        *invalid_byte = 0xff;
8177        *invalid_padding = 0xff;
8178        [0; 4]
8179    }
8180}
8181
8182fn read_tail_or_mark_invalid<'a>(
8183    input: &'a [u8],
8184    offset: usize,
8185    invalid_byte: &mut u8,
8186    invalid_padding: &mut u8,
8187) -> &'a [u8] {
8188    if let Ok(tail) = read_tail(input, offset) {
8189        tail
8190    } else {
8191        debug_assert!(
8192            false,
8193            "read_tail failed inside length-validated constant-time decode loop"
8194        );
8195        *invalid_byte = 0xff;
8196        *invalid_padding = 0xff;
8197        &[]
8198    }
8199}
8200
8201#[inline(never)]
8202#[allow(unsafe_code)]
8203fn ct_decode_alphabet_byte<A: Alphabet>(byte: u8) -> (u8, u8) {
8204    let mut decoded = 0u8;
8205    let mut valid = 0u8;
8206    let mut candidate = 0u8;
8207
8208    while candidate < 64 {
8209        let matches = core::hint::black_box(ct_mask_eq_u8(
8210            core::hint::black_box(byte),
8211            core::hint::black_box(A::ENCODE[candidate as usize]),
8212        ));
8213        decoded = core::hint::black_box(
8214            core::hint::black_box(decoded) | core::hint::black_box(candidate & matches),
8215        );
8216        // SAFETY: `decoded` is an initialized local `u8`; the volatile read is
8217        // an optimizer barrier for the fixed 64-iteration alphabet scan and
8218        // does not access caller memory.
8219        decoded = unsafe { core::ptr::read_volatile(&raw const decoded) };
8220        valid =
8221            core::hint::black_box(core::hint::black_box(valid) | core::hint::black_box(matches));
8222        // SAFETY: `valid` is an initialized local `u8`; the volatile read is an
8223        // optimizer barrier for the fixed 64-iteration alphabet scan and does
8224        // not access caller memory.
8225        valid = unsafe { core::ptr::read_volatile(&raw const valid) };
8226        candidate += 1;
8227    }
8228
8229    (decoded, valid)
8230}
8231
8232fn ct_padding_len(input: &[u8]) -> usize {
8233    let Some((&last, before_last_prefix)) = input.split_last() else {
8234        return 0;
8235    };
8236    let Some(&before_last) = before_last_prefix.last() else {
8237        return 0;
8238    };
8239    usize::from(ct_mask_eq_u8(last, b'=') & 1) + usize::from(ct_mask_eq_u8(before_last, b'=') & 1)
8240}
8241
8242fn report_ct_error(invalid_byte: u8, invalid_padding: u8) -> Result<(), DecodeError> {
8243    ct_error_gate_barrier(invalid_byte, invalid_padding);
8244
8245    if (invalid_byte | invalid_padding) != 0 {
8246        Err(DecodeError::InvalidInput)
8247    } else {
8248        Ok(())
8249    }
8250}
8251
8252#[cfg(kani)]
8253mod kani_proofs {
8254    use super::{
8255        STANDARD, Standard, checked_encoded_len, ct, decode_byte, decode_chunk,
8256        decode_tail_unpadded, decoded_capacity, validate_tail_unpadded,
8257    };
8258
8259    #[kani::proof]
8260    fn checked_encoded_len_is_bounded_for_small_inputs() {
8261        let len = usize::from(kani::any::<u8>());
8262        let padded = kani::any::<bool>();
8263        let encoded = checked_encoded_len(len, padded).expect("u8 input length cannot overflow");
8264
8265        assert!(encoded >= len);
8266        assert!(encoded <= len / 3 * 4 + 4);
8267    }
8268
8269    #[kani::proof]
8270    fn decoded_capacity_is_bounded_for_small_inputs() {
8271        let len = usize::from(kani::any::<u8>());
8272        let capacity = decoded_capacity(len);
8273
8274        assert!(capacity <= len / 4 * 3 + 2);
8275    }
8276
8277    #[kani::proof]
8278    #[kani::unwind(3)]
8279    fn standard_in_place_decode_returns_prefix_within_buffer() {
8280        let mut buffer = kani::any::<[u8; 8]>();
8281        let result = STANDARD.decode_in_place(&mut buffer);
8282
8283        if let Ok(decoded) = result {
8284            assert!(decoded.len() <= 8);
8285        }
8286    }
8287
8288    #[kani::proof]
8289    #[kani::unwind(3)]
8290    fn standard_decode_slice_returns_written_within_output() {
8291        let input = kani::any::<[u8; 4]>();
8292        let mut output = kani::any::<[u8; 3]>();
8293        let result = STANDARD.decode_slice(&input, &mut output);
8294
8295        if let Ok(written) = result {
8296            assert!(written <= output.len());
8297        }
8298    }
8299
8300    #[kani::proof]
8301    #[kani::unwind(3)]
8302    fn standard_decode_chunk_returns_written_within_output() {
8303        let input = kani::any::<[u8; 4]>();
8304        let mut output = kani::any::<[u8; 3]>();
8305        let result = decode_chunk::<Standard, true>(input, &mut output);
8306
8307        if let Ok(written) = result {
8308            assert!(written <= output.len());
8309            assert!(written <= 3);
8310        }
8311    }
8312
8313    #[kani::proof]
8314    #[kani::unwind(3)]
8315    fn standard_decode_chunk_bit_packing_matches_decoded_values() {
8316        let input = kani::any::<[u8; 4]>();
8317        let mut output = kani::any::<[u8; 3]>();
8318        let result = decode_chunk::<Standard, true>(input, &mut output);
8319
8320        if let Ok(written) = result {
8321            let v0 = decode_byte::<Standard>(input[0], 0).expect("successful chunk has v0");
8322            let v1 = decode_byte::<Standard>(input[1], 1).expect("successful chunk has v1");
8323
8324            assert!(output[0] == ((v0 << 2) | (v1 >> 4)));
8325
8326            if written >= 2 {
8327                let v2 = decode_byte::<Standard>(input[2], 2).expect("successful chunk has v2");
8328                assert!(output[1] == ((v1 << 4) | (v2 >> 2)));
8329            }
8330
8331            if written == 3 {
8332                let v2 = decode_byte::<Standard>(input[2], 2).expect("successful chunk has v2");
8333                let v3 = decode_byte::<Standard>(input[3], 3).expect("successful chunk has v3");
8334                assert!(output[2] == ((v2 << 6) | v3));
8335            }
8336        }
8337    }
8338
8339    #[kani::proof]
8340    #[kani::unwind(3)]
8341    fn standard_validate_tail_unpadded_accepts_or_rejects_without_panic() {
8342        let input = kani::any::<[u8; 3]>();
8343        let len = usize::from(kani::any::<u8>() % 4);
8344        let result = validate_tail_unpadded::<Standard>(&input[..len]);
8345
8346        if result.is_ok() {
8347            assert!(len == 0 || len == 2 || len == 3);
8348        }
8349    }
8350
8351    #[kani::proof]
8352    #[kani::unwind(3)]
8353    fn standard_decode_two_byte_tail_returns_written_within_output() {
8354        let input = kani::any::<[u8; 2]>();
8355        let mut output = kani::any::<[u8; 1]>();
8356        let result = decode_tail_unpadded::<Standard>(&input, &mut output);
8357
8358        if let Ok(written) = result {
8359            assert!(written <= output.len());
8360            assert!(written == 1);
8361        }
8362    }
8363
8364    #[kani::proof]
8365    #[kani::unwind(3)]
8366    fn standard_decode_three_byte_tail_returns_written_within_output() {
8367        let input = kani::any::<[u8; 3]>();
8368        let mut output = kani::any::<[u8; 2]>();
8369        let result = decode_tail_unpadded::<Standard>(&input, &mut output);
8370
8371        if let Ok(written) = result {
8372            assert!(written <= output.len());
8373            assert!(written == 2);
8374        }
8375    }
8376
8377    #[kani::proof]
8378    #[kani::unwind(3)]
8379    fn standard_decode_slice_clear_tail_clears_output_on_error() {
8380        let input = kani::any::<[u8; 4]>();
8381        let mut output = kani::any::<[u8; 3]>();
8382        let result = STANDARD.decode_slice_clear_tail(&input, &mut output);
8383
8384        if result.is_err() {
8385            assert!(output.iter().all(|byte| *byte == 0));
8386        }
8387    }
8388
8389    #[kani::proof]
8390    #[kani::unwind(3)]
8391    fn standard_encode_slice_returns_written_within_output() {
8392        let input = kani::any::<[u8; 3]>();
8393        let mut output = kani::any::<[u8; 4]>();
8394        let result = STANDARD.encode_slice(&input, &mut output);
8395
8396        if let Ok(written) = result {
8397            assert!(written <= output.len());
8398        }
8399    }
8400
8401    #[kani::proof]
8402    #[kani::unwind(4)]
8403    fn standard_encode_in_place_returns_prefix_within_buffer() {
8404        let mut buffer = kani::any::<[u8; 8]>();
8405        let input_len = usize::from(kani::any::<u8>() % 9);
8406        let result = STANDARD.encode_in_place(&mut buffer, input_len);
8407
8408        if let Ok(encoded) = result {
8409            assert!(encoded.len() <= 8);
8410        }
8411    }
8412
8413    #[kani::proof]
8414    #[kani::unwind(3)]
8415    fn standard_clear_tail_decode_clears_buffer_on_error() {
8416        let mut buffer = kani::any::<[u8; 4]>();
8417        let result = STANDARD.decode_in_place_clear_tail(&mut buffer);
8418
8419        if result.is_err() {
8420            assert!(buffer.iter().all(|byte| *byte == 0));
8421        }
8422    }
8423
8424    #[kani::proof]
8425    #[kani::unwind(3)]
8426    fn ct_standard_decode_slice_returns_written_within_output() {
8427        let input = kani::any::<[u8; 4]>();
8428        let mut output = kani::any::<[u8; 3]>();
8429        let result = ct::STANDARD.decode_slice_clear_tail(&input, &mut output);
8430
8431        if let Ok(written) = result {
8432            assert!(written <= output.len());
8433        }
8434    }
8435
8436    #[kani::proof]
8437    #[kani::unwind(3)]
8438    fn ct_standard_decode_slice_clear_tail_clears_output_on_error() {
8439        let input = kani::any::<[u8; 4]>();
8440        let mut output = kani::any::<[u8; 3]>();
8441        let result = ct::STANDARD.decode_slice_clear_tail(&input, &mut output);
8442
8443        if result.is_err() {
8444            assert!(output.iter().all(|byte| *byte == 0));
8445        }
8446    }
8447
8448    #[kani::proof]
8449    #[kani::unwind(3)]
8450    fn ct_standard_decode_in_place_clear_tail_clears_buffer_on_error() {
8451        let mut buffer = kani::any::<[u8; 4]>();
8452        let result = ct::STANDARD.decode_in_place_clear_tail(&mut buffer);
8453
8454        if result.is_err() {
8455            assert!(buffer.iter().all(|byte| *byte == 0));
8456        }
8457    }
8458
8459    #[kani::proof]
8460    #[kani::unwind(3)]
8461    fn ct_standard_validate_matches_decode_for_one_quantum() {
8462        let input = kani::any::<[u8; 4]>();
8463        let mut output = kani::any::<[u8; 3]>();
8464
8465        let validate_ok = ct::STANDARD.validate_result(&input).is_ok();
8466        let decode_ok = ct::STANDARD
8467            .decode_slice_clear_tail(&input, &mut output)
8468            .is_ok();
8469
8470        assert!(validate_ok == decode_ok);
8471    }
8472}
8473
8474#[cfg(test)]
8475mod tests {
8476    use super::*;
8477
8478    fn fill_pattern(output: &mut [u8], seed: usize) {
8479        for (index, byte) in output.iter_mut().enumerate() {
8480            let value = (index * 73 + seed * 19) % 256;
8481            *byte = u8::try_from(value).unwrap();
8482        }
8483    }
8484
8485    fn assert_encode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
8486    where
8487        A: Alphabet,
8488    {
8489        let engine = Engine::<A, PAD>::new();
8490        let mut dispatched = [0x55; 256];
8491        let mut scalar = [0xaa; 256];
8492
8493        let dispatched_result = engine.encode_slice(input, &mut dispatched);
8494        let scalar_result = backend::scalar_reference_encode_slice::<A, PAD>(input, &mut scalar);
8495
8496        assert_eq!(dispatched_result, scalar_result);
8497        if let Ok(written) = dispatched_result {
8498            assert_eq!(&dispatched[..written], &scalar[..written]);
8499        }
8500
8501        let required = checked_encoded_len(input.len(), PAD).unwrap();
8502        if required > 0 {
8503            let mut dispatched_short = [0x55; 256];
8504            let mut scalar_short = [0xaa; 256];
8505            let available = required - 1;
8506
8507            assert_eq!(
8508                engine.encode_slice(input, &mut dispatched_short[..available]),
8509                backend::scalar_reference_encode_slice::<A, PAD>(
8510                    input,
8511                    &mut scalar_short[..available],
8512                )
8513            );
8514        }
8515    }
8516
8517    fn assert_decode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
8518    where
8519        A: Alphabet,
8520    {
8521        let engine = Engine::<A, PAD>::new();
8522        let mut dispatched = [0x55; 128];
8523        let mut scalar = [0xaa; 128];
8524
8525        let dispatched_result = engine.decode_slice(input, &mut dispatched);
8526        let scalar_result = backend::scalar_reference_decode_slice::<A, PAD>(input, &mut scalar);
8527
8528        assert_eq!(dispatched_result, scalar_result);
8529        if let Ok(written) = dispatched_result {
8530            assert_eq!(&dispatched[..written], &scalar[..written]);
8531
8532            if written > 0 {
8533                let mut dispatched_short = [0x55; 128];
8534                let mut scalar_short = [0xaa; 128];
8535                let available = written - 1;
8536
8537                assert_eq!(
8538                    engine.decode_slice(input, &mut dispatched_short[..available]),
8539                    backend::scalar_reference_decode_slice::<A, PAD>(
8540                        input,
8541                        &mut scalar_short[..available],
8542                    )
8543                );
8544            }
8545        }
8546    }
8547
8548    fn assert_backend_round_trip_matches_scalar<A, const PAD: bool>(input: &[u8])
8549    where
8550        A: Alphabet,
8551    {
8552        assert_encode_backend_matches_scalar::<A, PAD>(input);
8553
8554        let mut encoded = [0; 256];
8555        let encoded_len =
8556            backend::scalar_reference_encode_slice::<A, PAD>(input, &mut encoded).unwrap();
8557        assert_decode_backend_matches_scalar::<A, PAD>(&encoded[..encoded_len]);
8558    }
8559
8560    fn assert_standard_decode_chunk_matches_input(input: &[u8]) {
8561        let mut encoded = [0u8; 4];
8562        let encoded_len = STANDARD.encode_slice(input, &mut encoded).unwrap();
8563        assert_eq!(encoded_len, 4);
8564
8565        let chunk = [encoded[0], encoded[1], encoded[2], encoded[3]];
8566        let mut decoded = [0u8; 3];
8567        let decoded_len = decode_chunk::<Standard, true>(chunk, &mut decoded).unwrap();
8568
8569        assert_eq!(decoded_len, input.len());
8570        assert_eq!(&decoded[..decoded_len], input);
8571    }
8572
8573    #[test]
8574    fn backend_dispatch_matches_scalar_reference_for_canonical_inputs() {
8575        let mut input = [0; 128];
8576
8577        for input_len in 0..=input.len() {
8578            fill_pattern(&mut input[..input_len], input_len);
8579            let input = &input[..input_len];
8580
8581            assert_backend_round_trip_matches_scalar::<Standard, true>(input);
8582            assert_backend_round_trip_matches_scalar::<Standard, false>(input);
8583            assert_backend_round_trip_matches_scalar::<UrlSafe, true>(input);
8584            assert_backend_round_trip_matches_scalar::<UrlSafe, false>(input);
8585        }
8586    }
8587
8588    #[test]
8589    fn backend_dispatch_matches_scalar_reference_for_malformed_inputs() {
8590        for input in [
8591            &b"Z"[..],
8592            b"====",
8593            b"AA=A",
8594            b"Zh==",
8595            b"Zm9=",
8596            b"Zm9v$g==",
8597            b"Zm9vZh==",
8598        ] {
8599            assert_decode_backend_matches_scalar::<Standard, true>(input);
8600        }
8601
8602        for input in [&b"Z"[..], b"AA=A", b"Zh", b"Zm9", b"Zm9vYg$"] {
8603            assert_decode_backend_matches_scalar::<Standard, false>(input);
8604        }
8605
8606        assert_decode_backend_matches_scalar::<UrlSafe, true>(b"AA+A");
8607        assert_decode_backend_matches_scalar::<UrlSafe, false>(b"AA/A");
8608        assert_decode_backend_matches_scalar::<Standard, true>(b"AA-A");
8609        assert_decode_backend_matches_scalar::<Standard, false>(b"AA_A");
8610    }
8611
8612    #[test]
8613    fn decode_chunk_bit_packing_matches_exhaustive_small_inputs() {
8614        for byte in u8::MIN..=u8::MAX {
8615            assert_standard_decode_chunk_matches_input(&[byte]);
8616        }
8617
8618        for first in u8::MIN..=u8::MAX {
8619            for second in u8::MIN..=u8::MAX {
8620                assert_standard_decode_chunk_matches_input(&[first, second]);
8621            }
8622        }
8623    }
8624
8625    #[test]
8626    fn decode_chunk_bit_packing_matches_representative_full_quanta() {
8627        const SAMPLES: [u8; 16] = [
8628            0, 1, 2, 15, 16, 31, 32, 63, 64, 95, 127, 128, 191, 192, 254, 255,
8629        ];
8630
8631        for first in SAMPLES {
8632            for second in SAMPLES {
8633                for third in SAMPLES {
8634                    assert_standard_decode_chunk_matches_input(&[first, second, third]);
8635                }
8636            }
8637        }
8638    }
8639
8640    #[test]
8641    fn ct_padded_final_quantum_fails_closed_for_invalid_padding_count() {
8642        let (_, invalid_byte, invalid_padding, written) =
8643            ct_padded_final_quantum::<Standard>(*b"ABCD", 3);
8644
8645        assert_ne!(invalid_byte, 0);
8646        assert_ne!(invalid_padding, 0);
8647        assert_eq!(written, 0);
8648        assert_eq!(
8649            report_ct_error(invalid_byte, invalid_padding),
8650            Err(DecodeError::InvalidInput)
8651        );
8652    }
8653
8654    #[cfg(feature = "simd")]
8655    #[test]
8656    fn simd_dispatch_scaffold_keeps_scalar_active() {
8657        assert_eq!(simd::active_backend(), simd::ActiveBackend::Scalar);
8658        let _candidate = simd::detected_candidate();
8659    }
8660
8661    #[test]
8662    fn encodes_standard_vectors() {
8663        let vectors = [
8664            (&b""[..], &b""[..]),
8665            (&b"f"[..], &b"Zg=="[..]),
8666            (&b"fo"[..], &b"Zm8="[..]),
8667            (&b"foo"[..], &b"Zm9v"[..]),
8668            (&b"foob"[..], &b"Zm9vYg=="[..]),
8669            (&b"fooba"[..], &b"Zm9vYmE="[..]),
8670            (&b"foobar"[..], &b"Zm9vYmFy"[..]),
8671        ];
8672        for (input, expected) in vectors {
8673            let mut output = [0u8; 16];
8674            let written = STANDARD.encode_slice(input, &mut output).unwrap();
8675            assert_eq!(&output[..written], expected);
8676        }
8677    }
8678
8679    #[test]
8680    fn decodes_standard_vectors() {
8681        let vectors = [
8682            (&b""[..], &b""[..]),
8683            (&b"Zg=="[..], &b"f"[..]),
8684            (&b"Zm8="[..], &b"fo"[..]),
8685            (&b"Zm9v"[..], &b"foo"[..]),
8686            (&b"Zm9vYg=="[..], &b"foob"[..]),
8687            (&b"Zm9vYmE="[..], &b"fooba"[..]),
8688            (&b"Zm9vYmFy"[..], &b"foobar"[..]),
8689        ];
8690        for (input, expected) in vectors {
8691            let mut output = [0u8; 16];
8692            let written = STANDARD.decode_slice(input, &mut output).unwrap();
8693            assert_eq!(&output[..written], expected);
8694        }
8695    }
8696
8697    #[test]
8698    fn supports_unpadded_url_safe() {
8699        let mut encoded = [0u8; 16];
8700        let written = URL_SAFE_NO_PAD
8701            .encode_slice(b"\xfb\xff", &mut encoded)
8702            .unwrap();
8703        assert_eq!(&encoded[..written], b"-_8");
8704
8705        let mut decoded = [0u8; 2];
8706        let written = URL_SAFE_NO_PAD
8707            .decode_slice(&encoded[..written], &mut decoded)
8708            .unwrap();
8709        assert_eq!(&decoded[..written], b"\xfb\xff");
8710    }
8711
8712    #[test]
8713    fn decodes_in_place() {
8714        let mut buffer = *b"Zm9vYmFy";
8715        let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
8716        assert_eq!(decoded, b"foobar");
8717    }
8718
8719    #[test]
8720    fn rejects_non_canonical_padding_bits() {
8721        let mut output = [0u8; 4];
8722        assert_eq!(
8723            STANDARD.decode_slice(b"Zh==", &mut output),
8724            Err(DecodeError::InvalidPadding { index: 1 })
8725        );
8726        assert_eq!(
8727            STANDARD.decode_slice(b"Zm9=", &mut output),
8728            Err(DecodeError::InvalidPadding { index: 2 })
8729        );
8730    }
8731}