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