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
712/// Constant-time-oriented scalar decoding APIs.
713///
714/// This module is separate from the default decoder so callers can opt into a
715/// slower path with a narrower timing target. It avoids lookup tables indexed
716/// by secret input bytes while mapping Base64 symbols and reports malformed
717/// content through one opaque error. It is not documented as a formally
718/// verified cryptographic constant-time API.
719///
720/// # Security
721///
722/// Input length, decoded length, selected alphabet, and final success or
723/// failure remain public. The clear-tail methods wipe caller-owned output on
724/// error, but decoded bytes are written during the fixed-shape decode loop
725/// before final validation is reported. In shared-memory, enclave, or HSM-style
726/// threat models where another component can observe the output buffer during
727/// the call, prefer [`crate::ct::CtEngine::decode_slice_staged_clear_tail`]
728/// with a private staging buffer. In those deployments,
729/// [`crate::ct::CtEngine::decode_slice_clear_tail`] is not sufficient by
730/// itself because it wipes caller-owned output only after the internal decode
731/// loop reaches the final error gate.
732///
733/// The dependency-free comparison helpers on redacted buffers are
734/// constant-time-oriented best effort, not formally audited MAC or token
735/// comparison primitives. Applications that can admit dependencies and need a
736/// reviewed comparison primitive should use one at the protocol boundary.
737///
738/// The CT decoder exposes only clear-tail and stack-backed decode APIs. The
739/// former non-clear-tail methods were removed before the `1.0` stable boundary
740/// because they could leave decoded plaintext in caller-owned buffers after
741/// malformed input errors.
742///
743/// ```compile_fail
744/// use base64_ng::ct;
745///
746/// let mut output = [0u8; 8];
747/// let _ = ct::STANDARD.decode_slice(b"aGk=", &mut output);
748/// ```
749///
750/// ```compile_fail
751/// use base64_ng::ct;
752///
753/// let mut buffer = *b"aGk=";
754/// let _ = ct::STANDARD.decode_in_place(&mut buffer);
755/// ```
756pub mod ct {
757    use super::{
758        Alphabet, DecodeError, DecodedBuffer, Standard, UrlSafe, ct_decode_in_place,
759        ct_decode_slice, ct_decode_slice_staged_clear_tail, ct_decoded_len, ct_validate_decode,
760    };
761    use core::marker::PhantomData;
762
763    /// Standard Base64 constant-time-oriented decoder with padding.
764    pub const STANDARD: CtEngine<Standard, true> = CtEngine::new();
765
766    /// Standard Base64 constant-time-oriented decoder without padding.
767    pub const STANDARD_NO_PAD: CtEngine<Standard, false> = CtEngine::new();
768
769    /// URL-safe Base64 constant-time-oriented decoder with padding.
770    pub const URL_SAFE: CtEngine<UrlSafe, true> = CtEngine::new();
771
772    /// URL-safe Base64 constant-time-oriented decoder without padding.
773    pub const URL_SAFE_NO_PAD: CtEngine<UrlSafe, false> = CtEngine::new();
774
775    /// A zero-sized constant-time-oriented Base64 decoder.
776    ///
777    /// # Security
778    ///
779    /// For ordinary secret-bearing inputs, prefer
780    /// [`Self::decode_slice_clear_tail`], [`Self::decode_buffer`], or
781    /// [`Self::decode_in_place_clear_tail`]. For shared-memory,
782    /// enclave-adjacent, HSM-style, or multi-principal deployments where
783    /// another component can observe caller-owned output during the call, use
784    /// [`Self::decode_slice_staged_clear_tail`] with a private staging buffer
785    /// so malformed input cannot transiently write decoded bytes into the
786    /// public output buffer before the final error gate.
787    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
788    pub struct CtEngine<A, const PAD: bool> {
789        alphabet: PhantomData<A>,
790    }
791
792    impl<A, const PAD: bool> CtEngine<A, PAD>
793    where
794        A: Alphabet,
795    {
796        /// Creates a new constant-time-oriented decoder engine.
797        #[must_use]
798        pub const fn new() -> Self {
799            Self {
800                alphabet: PhantomData,
801            }
802        }
803
804        /// Returns whether this constant-time-oriented decoder expects padded
805        /// input.
806        #[must_use]
807        pub const fn is_padded(&self) -> bool {
808            PAD
809        }
810
811        /// Validates `input` without writing decoded bytes.
812        ///
813        /// This uses the same constant-time-oriented symbol mapping and opaque
814        /// malformed-input error behavior as
815        /// [`Self::decode_slice_clear_tail`]. Input length, padding length, and
816        /// final success or failure remain public.
817        ///
818        /// # Examples
819        ///
820        /// ```
821        /// use base64_ng::ct;
822        ///
823        /// ct::STANDARD.validate_result(b"aGVsbG8=").unwrap();
824        /// assert!(ct::STANDARD.validate_result(b"aGVsbG8").is_err());
825        /// ```
826        pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
827            ct_validate_decode::<A, PAD>(input)
828        }
829
830        /// Returns whether `input` is valid for this constant-time-oriented
831        /// decoder.
832        ///
833        /// This is a convenience wrapper around [`Self::validate_result`].
834        ///
835        /// # Examples
836        ///
837        /// ```
838        /// use base64_ng::ct;
839        ///
840        /// assert!(ct::URL_SAFE_NO_PAD.validate(b"-_8"));
841        /// assert!(!ct::URL_SAFE_NO_PAD.validate(b"+/8"));
842        /// ```
843        #[must_use]
844        pub fn validate(&self, input: &[u8]) -> bool {
845            self.validate_result(input).is_ok()
846        }
847
848        /// Returns the exact decoded length for valid input.
849        ///
850        /// This uses the same constant-time-oriented validation policy as
851        /// [`Self::validate_result`] before returning a length. Input length,
852        /// padding length, and final success or failure remain public.
853        pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
854            ct_decoded_len::<A, PAD>(input)
855        }
856
857        /// Decodes `input` into `output` and clears all bytes after the
858        /// decoded prefix.
859        ///
860        /// If decoding fails, the entire output buffer is cleared before the
861        /// error is returned. Use this variant for sensitive payloads where
862        /// partially decoded bytes from rejected input should not remain in the
863        /// caller-owned output buffer.
864        ///
865        /// # Security: Transient Plaintext Window
866        ///
867        /// Decoded bytes are written to `output` progressively during the
868        /// fixed-shape decode loop before malformed-input detection is
869        /// complete. On error, the entire `output` is wiped before returning,
870        /// but a concurrent same-process observer with access to `output`
871        /// during the call may observe transient partial plaintext from valid
872        /// leading quanta. For shared-memory, enclave-adjacent, HSM-style, or
873        /// multi-principal deployments where even transient writes are
874        /// unacceptable, use [`Self::decode_slice_staged_clear_tail`] with a
875        /// private staging buffer.
876        ///
877        /// # Examples
878        ///
879        /// ```
880        /// use base64_ng::ct;
881        ///
882        /// let mut output = [0xff; 8];
883        /// let written = ct::STANDARD
884        ///     .decode_slice_clear_tail(b"aGk=", &mut output)
885        ///     .unwrap();
886        ///
887        /// assert_eq!(&output[..written], b"hi");
888        /// assert!(output[written..].iter().all(|byte| *byte == 0));
889        /// ```
890        #[must_use = "handle decode errors; use decode_slice_staged_clear_tail for shared-memory or HSM-style threat models"]
891        pub fn decode_slice_clear_tail(
892            &self,
893            input: &[u8],
894            output: &mut [u8],
895        ) -> Result<usize, DecodeError> {
896            let written = match ct_decode_slice::<A, PAD>(input, output) {
897                Ok(written) => written,
898                Err(err) => {
899                    crate::wipe_bytes(output);
900                    return Err(err);
901                }
902            };
903            crate::wipe_tail(output, written);
904            Ok(written)
905        }
906
907        /// Decodes through caller-provided private staging before copying into
908        /// `output`.
909        ///
910        /// This variant is for shared-memory or sandboxed deployments where
911        /// the caller-owned `output` buffer must not contain transient decoded
912        /// bytes from malformed input. The `staging` buffer must be at least
913        /// the decoded length of `input` and must not be shared with
914        /// untrusted concurrent observers. On success, decoded bytes are
915        /// copied from `staging` into `output`; on error, both buffers are
916        /// cleared before returning.
917        ///
918        /// Input length, final success or failure, and decoded length remain
919        /// public.
920        #[must_use = "handle decode errors; staged decode is for shared-memory or HSM-style threat models"]
921        pub fn decode_slice_staged_clear_tail(
922            &self,
923            input: &[u8],
924            output: &mut [u8],
925            staging: &mut [u8],
926        ) -> Result<usize, DecodeError> {
927            ct_decode_slice_staged_clear_tail::<A, PAD>(input, output, staging)
928        }
929
930        /// Decodes `input` into a stack-backed buffer.
931        ///
932        /// This uses the same constant-time-oriented scalar decoder as
933        /// [`Self::decode_slice_clear_tail`] and clears the internal backing
934        /// array before returning an error.
935        ///
936        /// # Examples
937        ///
938        /// ```
939        /// use base64_ng::ct;
940        ///
941        /// let decoded = ct::STANDARD.decode_buffer::<5>(b"aGVsbG8=").unwrap();
942        ///
943        /// assert_eq!(decoded.as_bytes(), b"hello");
944        /// ```
945        pub fn decode_buffer<const CAP: usize>(
946            &self,
947            input: &[u8],
948        ) -> Result<DecodedBuffer<CAP>, DecodeError> {
949            let mut output = DecodedBuffer::new();
950            let written = match self.decode_slice_clear_tail(input, &mut output.bytes) {
951                Ok(written) => written,
952                Err(err) => {
953                    output.clear();
954                    return Err(err);
955                }
956            };
957            output.len = written;
958            Ok(output)
959        }
960
961        /// Decodes `buffer` in place and clears all bytes after the decoded
962        /// prefix.
963        ///
964        /// If decoding fails, the entire buffer is cleared before the error is
965        /// returned.
966        ///
967        /// # Security: Transient Plaintext Window
968        ///
969        /// This in-place API writes decoded bytes into `buffer` during the
970        /// fixed-shape decode loop before malformed-input detection is
971        /// complete. On error, the entire buffer is wiped before returning,
972        /// but concurrent same-process observers with access to the same memory
973        /// can observe transient partial plaintext. Use
974        /// [`Self::decode_slice_staged_clear_tail`] with a private staging
975        /// buffer when shared-memory or enclave-adjacent deployments cannot
976        /// tolerate that window.
977        ///
978        /// # Examples
979        ///
980        /// ```
981        /// use base64_ng::ct;
982        ///
983        /// let mut buffer = *b"aGk=";
984        /// let decoded = ct::STANDARD.decode_in_place_clear_tail(&mut buffer).unwrap();
985        ///
986        /// assert_eq!(decoded, b"hi");
987        /// ```
988        pub fn decode_in_place_clear_tail<'a>(
989            &self,
990            buffer: &'a mut [u8],
991        ) -> Result<&'a mut [u8], DecodeError> {
992            let len = match ct_decode_in_place::<A, PAD>(buffer) {
993                Ok(len) => len,
994                Err(err) => {
995                    crate::wipe_bytes(buffer);
996                    return Err(err);
997                }
998            };
999            crate::wipe_tail(buffer, len);
1000            Ok(&mut buffer[..len])
1001        }
1002    }
1003
1004    impl<A, const PAD: bool> core::fmt::Display for CtEngine<A, PAD> {
1005        fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1006            write!(formatter, "ct padded={PAD}")
1007        }
1008    }
1009}
1010
1011/// Standard Base64 engine with padding.
1012///
1013/// This default strict engine is not a constant-time token validator or
1014/// key-material decoder. Use [`ct::STANDARD`] or [`Engine::ct_decoder`] for the
1015/// matching constant-time-oriented decoder when timing posture matters.
1016#[doc(alias = "ct")]
1017#[doc(alias = "constant_time")]
1018#[doc(alias = "sensitive")]
1019pub const STANDARD: Engine<Standard, true> = Engine::new();
1020
1021/// Standard Base64 engine without padding.
1022///
1023/// This default strict engine is not a constant-time token validator or
1024/// key-material decoder. Use [`ct::STANDARD_NO_PAD`] or [`Engine::ct_decoder`]
1025/// for the matching constant-time-oriented decoder when timing posture
1026/// matters.
1027#[doc(alias = "ct")]
1028#[doc(alias = "constant_time")]
1029#[doc(alias = "sensitive")]
1030pub const STANDARD_NO_PAD: Engine<Standard, false> = Engine::new();
1031
1032/// URL-safe Base64 engine with padding.
1033///
1034/// This default strict engine is not a constant-time token validator or
1035/// key-material decoder. Use [`ct::URL_SAFE`] or [`Engine::ct_decoder`] for the
1036/// matching constant-time-oriented decoder when timing posture matters.
1037#[doc(alias = "ct")]
1038#[doc(alias = "constant_time")]
1039#[doc(alias = "sensitive")]
1040pub const URL_SAFE: Engine<UrlSafe, true> = Engine::new();
1041
1042/// URL-safe Base64 engine without padding.
1043///
1044/// This default strict engine is not a constant-time token validator or
1045/// key-material decoder. Use [`ct::URL_SAFE_NO_PAD`] or [`Engine::ct_decoder`]
1046/// for the matching constant-time-oriented decoder when timing posture
1047/// matters.
1048#[doc(alias = "ct")]
1049#[doc(alias = "constant_time")]
1050#[doc(alias = "sensitive")]
1051pub const URL_SAFE_NO_PAD: Engine<UrlSafe, false> = Engine::new();
1052
1053/// bcrypt-style Base64 engine without padding.
1054///
1055/// This uses the bcrypt alphabet with the crate's normal Base64 bit packing.
1056/// It does not parse complete bcrypt password-hash strings. This default strict
1057/// engine is not a constant-time token validator or key-material decoder; use
1058/// [`Engine::ct_decoder`] for the matching constant-time-oriented decoder when
1059/// timing posture matters.
1060#[doc(alias = "ct")]
1061#[doc(alias = "constant_time")]
1062#[doc(alias = "sensitive")]
1063pub const BCRYPT_NO_PAD: Engine<Bcrypt, false> = Engine::new();
1064
1065/// Unix `crypt(3)`-style Base64 engine without padding.
1066///
1067/// This uses the `crypt(3)` alphabet with the crate's normal Base64 bit
1068/// packing. It does not parse complete password-hash strings. This default
1069/// strict engine is not a constant-time token validator or key-material
1070/// decoder; use [`Engine::ct_decoder`] for the matching constant-time-oriented
1071/// decoder when timing posture matters.
1072#[doc(alias = "ct")]
1073#[doc(alias = "constant_time")]
1074#[doc(alias = "sensitive")]
1075pub const CRYPT_NO_PAD: Engine<Crypt, false> = Engine::new();
1076
1077/// Line ending used by wrapped Base64 output.
1078#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1079pub enum LineEnding {
1080    /// Line feed (`\n`).
1081    Lf,
1082    /// Carriage return followed by line feed (`\r\n`).
1083    CrLf,
1084}
1085
1086impl LineEnding {
1087    /// Returns a stable printable identifier for this line ending.
1088    #[must_use]
1089    pub const fn name(self) -> &'static str {
1090        match self {
1091            Self::Lf => "LF",
1092            Self::CrLf => "CRLF",
1093        }
1094    }
1095
1096    /// Returns the text representation of this line ending.
1097    #[must_use]
1098    pub const fn as_str(self) -> &'static str {
1099        match self {
1100            Self::Lf => "\n",
1101            Self::CrLf => "\r\n",
1102        }
1103    }
1104
1105    /// Returns the byte representation of this line ending.
1106    #[must_use]
1107    pub const fn as_bytes(self) -> &'static [u8] {
1108        self.as_str().as_bytes()
1109    }
1110
1111    /// Returns the byte length of this line ending.
1112    #[must_use]
1113    pub const fn byte_len(self) -> usize {
1114        match self {
1115            Self::Lf => 1,
1116            Self::CrLf => 2,
1117        }
1118    }
1119}
1120
1121impl core::fmt::Display for LineEnding {
1122    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1123        formatter.write_str(self.name())
1124    }
1125}
1126
1127/// Base64 line wrapping policy.
1128///
1129/// `line_len` is measured in encoded Base64 bytes, not source input bytes.
1130/// Encoders insert line endings between lines and do not append a trailing line
1131/// ending after the final line.
1132#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1133pub struct LineWrap {
1134    /// Maximum encoded bytes per line.
1135    pub line_len: usize,
1136    /// Line ending inserted between wrapped lines.
1137    pub line_ending: LineEnding,
1138}
1139
1140impl LineWrap {
1141    /// MIME-style wrapping: 76 columns with CRLF endings.
1142    pub const MIME: Self = Self::new(76, LineEnding::CrLf);
1143    /// PEM-style wrapping: 64 columns with LF endings.
1144    pub const PEM: Self = Self::new(64, LineEnding::Lf);
1145    /// PEM-style wrapping: 64 columns with CRLF endings.
1146    pub const PEM_CRLF: Self = Self::new(64, LineEnding::CrLf);
1147
1148    /// Creates a wrapping policy.
1149    ///
1150    /// This constructor is intended for fixed, trusted values such as
1151    /// compile-time MIME or PEM profile constants. Use [`Self::checked_new`]
1152    /// when the line length comes from configuration, network input, file
1153    /// metadata, or another untrusted runtime source.
1154    ///
1155    /// # Panics
1156    ///
1157    /// Panics when `line_len` is zero. Base64 wrapping requires a non-zero
1158    /// encoded line length; accepting zero would make progress impossible for
1159    /// wrapped encoders. This constructor is callable at runtime, so do not
1160    /// pass attacker-controlled or externally configured values here; use
1161    /// [`Self::checked_new`] for those cases.
1162    #[must_use]
1163    pub const fn new(line_len: usize, line_ending: LineEnding) -> Self {
1164        assert!(line_len != 0, "base64 line wrap length must be non-zero");
1165        Self {
1166            line_len,
1167            line_ending,
1168        }
1169    }
1170
1171    /// Creates a wrapping policy, returning `None` when the line length is
1172    /// invalid.
1173    ///
1174    /// Base64 line-wrapping requires a non-zero encoded line length. This
1175    /// helper is useful when accepting a wrapping policy from configuration or
1176    /// another untrusted source.
1177    #[must_use]
1178    pub const fn checked_new(line_len: usize, line_ending: LineEnding) -> Option<Self> {
1179        if line_len == 0 {
1180            None
1181        } else {
1182            Some(Self::new(line_len, line_ending))
1183        }
1184    }
1185
1186    /// Returns the maximum encoded bytes per line.
1187    #[must_use]
1188    pub const fn line_len(self) -> usize {
1189        self.line_len
1190    }
1191
1192    /// Returns the line ending inserted between wrapped lines.
1193    #[must_use]
1194    pub const fn line_ending(self) -> LineEnding {
1195        self.line_ending
1196    }
1197
1198    /// Returns whether this wrapping policy can be used by the encoder.
1199    #[must_use]
1200    pub const fn is_valid(self) -> bool {
1201        self.line_len != 0
1202    }
1203}
1204
1205impl core::fmt::Display for LineWrap {
1206    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1207        write!(formatter, "{}:{}", self.line_len, self.line_ending.name())
1208    }
1209}
1210
1211#[inline(never)]
1212#[allow(unsafe_code)]
1213fn wipe_bytes(bytes: &mut [u8]) {
1214    for byte in bytes.iter_mut() {
1215        // SAFETY: `byte` comes from a unique mutable slice iterator, so the
1216        // pointer is non-null, aligned, valid for one `u8` write, and does not
1217        // alias another live mutable reference during this iteration.
1218        unsafe {
1219            core::ptr::write_volatile(byte, 0);
1220        }
1221    }
1222    wipe_barrier(bytes.as_mut_ptr(), bytes.len());
1223}
1224
1225#[inline(never)]
1226#[allow(unsafe_code)]
1227fn wipe_barrier(ptr: *mut u8, len: usize) {
1228    let _ = (ptr, len);
1229
1230    #[cfg(all(not(miri), any(target_arch = "x86", target_arch = "x86_64")))]
1231    {
1232        // `mfence` orders prior stores before later memory operations on
1233        // x86/x86_64, while the pointer and length are opaque optimizer inputs.
1234        // SAFETY: the assembly block does not read or write through the pointer.
1235        unsafe {
1236            core::arch::asm!(
1237                "mfence",
1238                "/* {0} {1} */",
1239                in(reg) ptr,
1240                in(reg) len,
1241                options(nostack, preserves_flags)
1242            );
1243        }
1244    }
1245
1246    #[cfg(all(not(miri), target_arch = "aarch64"))]
1247    {
1248        // `dsb sy` completes prior explicit memory accesses before later
1249        // instructions, and `isb sy` flushes subsequent instruction context.
1250        // SAFETY: the assembly block does not read or write through the pointer.
1251        unsafe {
1252            core::arch::asm!(
1253                "dsb sy",
1254                "isb sy",
1255                "hint #20",
1256                "/* {0} {1} */",
1257                in(reg) ptr,
1258                in(reg) len,
1259                options(nostack, preserves_flags)
1260            );
1261        }
1262    }
1263
1264    #[cfg(all(not(miri), target_arch = "arm"))]
1265    {
1266        // `dsb sy` completes prior explicit memory accesses before later
1267        // instructions, and `isb sy` flushes subsequent instruction context.
1268        // SAFETY: the assembly block does not read or write through the pointer.
1269        unsafe {
1270            core::arch::asm!(
1271                "dsb sy",
1272                "isb sy",
1273                "/* {0} {1} */",
1274                in(reg) ptr,
1275                in(reg) len,
1276                options(nostack, preserves_flags)
1277            );
1278        }
1279    }
1280
1281    #[cfg(all(not(miri), any(target_arch = "riscv32", target_arch = "riscv64")))]
1282    {
1283        // `fence rw, rw` orders prior reads/writes before later reads/writes.
1284        // SAFETY: the assembly block does not read or write through the pointer.
1285        unsafe {
1286            core::arch::asm!(
1287                "fence rw, rw",
1288                "/* {0} {1} */",
1289                in(reg) ptr,
1290                in(reg) len,
1291                options(nostack, preserves_flags)
1292            );
1293        }
1294    }
1295
1296    core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
1297}
1298
1299fn wipe_tail(bytes: &mut [u8], start: usize) {
1300    wipe_bytes(&mut bytes[start..]);
1301}
1302
1303#[cfg(feature = "alloc")]
1304#[allow(unsafe_code)]
1305fn wipe_vec_spare_capacity(bytes: &mut alloc::vec::Vec<u8>) {
1306    let ptr = bytes.as_mut_ptr();
1307    let len = bytes.len();
1308    let capacity = bytes.capacity();
1309    let spare = capacity - len;
1310    if spare == 0 {
1311        return;
1312    }
1313
1314    let mut offset = len;
1315    while offset < capacity {
1316        // SAFETY: `offset` is within the vector allocation's spare capacity, so
1317        // the pointer is valid, aligned, and writable for one `u8`. This writes
1318        // a zero byte without reading the prior uninitialized value.
1319        unsafe {
1320            core::ptr::write_volatile(ptr.add(offset), 0);
1321        }
1322        offset += 1;
1323    }
1324    // SAFETY: `spare > 0`, so `len < capacity` and `ptr.add(len)` points
1325    // inside the vector allocation at the first spare-capacity byte.
1326    let spare_ptr = unsafe { ptr.add(len) };
1327    wipe_barrier(spare_ptr, spare);
1328}
1329
1330#[cfg(feature = "alloc")]
1331fn wipe_vec_all(bytes: &mut alloc::vec::Vec<u8>) {
1332    wipe_bytes(bytes);
1333    wipe_vec_spare_capacity(bytes);
1334}
1335
1336/// Stack-backed encoded Base64 output.
1337///
1338/// This type is intended for short values where heap allocation would be
1339/// unnecessary but manually sizing and passing a separate output slice is
1340/// noisy. Its visible bytes are produced by crate encoders, so [`Self::as_str`]
1341/// can return `&str` without exposing a fallible UTF-8 conversion to callers.
1342///
1343/// The backing array is cleared when the value is dropped. This is best-effort
1344/// data-retention reduction and is not a formal zeroization guarantee.
1345///
1346/// On `wasm32` targets, the wipe barrier uses only a compiler fence. The wasm
1347/// runtime JIT may still optimize or retain cleared bytes in ways this crate
1348/// cannot control. `wasm32` builds fail closed by default; enable
1349/// `allow-wasm32-best-effort-wipe` only when the deployment explicitly accepts
1350/// this limitation and applies its own memory strategy around stack-backed
1351/// buffers.
1352pub struct EncodedBuffer<const CAP: usize> {
1353    bytes: [u8; CAP],
1354    len: usize,
1355}
1356
1357/// Owned stack array extracted from [`EncodedBuffer`].
1358///
1359/// This wrapper keeps the extracted encoded bytes on the crate's best-effort
1360/// drop-time cleanup path. Use
1361/// [`Self::into_exposed_unprotected_array_caller_must_zeroize`] only when a
1362/// bare array is unavoidable and the caller will handle cleanup.
1363pub struct ExposedEncodedArray<const CAP: usize> {
1364    bytes: [u8; CAP],
1365    len: usize,
1366}
1367
1368impl<const CAP: usize> ExposedEncodedArray<CAP> {
1369    /// Wraps an encoded backing array and visible length.
1370    ///
1371    /// # Panics
1372    ///
1373    /// Panics if `len` is greater than `CAP`.
1374    #[must_use]
1375    pub const fn from_array(bytes: [u8; CAP], len: usize) -> Self {
1376        assert!(len <= CAP, "visible length exceeds array capacity");
1377        Self { bytes, len }
1378    }
1379
1380    /// Returns the visible encoded bytes.
1381    #[must_use]
1382    pub fn as_bytes(&self) -> &[u8] {
1383        &self.bytes[..self.len]
1384    }
1385
1386    /// Returns the number of visible encoded bytes.
1387    #[must_use]
1388    pub const fn len(&self) -> usize {
1389        self.len
1390    }
1391
1392    /// Returns whether there are no visible encoded bytes.
1393    #[must_use]
1394    pub const fn is_empty(&self) -> bool {
1395        self.len == 0
1396    }
1397
1398    /// Returns the backing array capacity.
1399    #[must_use]
1400    pub const fn capacity(&self) -> usize {
1401        CAP
1402    }
1403
1404    /// Consumes the wrapper and returns a bare array plus visible length.
1405    ///
1406    /// This is an unprotected escape hatch. The returned array will not be
1407    /// cleared by this crate on drop. Callers must clear it with their own
1408    /// approved zeroization policy.
1409    ///
1410    /// # Security
1411    ///
1412    /// Treat this as a cleanup-boundary API. Failing to clear the returned
1413    /// array leaves the encoded bytes in ordinary caller-owned memory until
1414    /// overwritten by later stack or heap activity.
1415    #[must_use = "caller must zeroize the returned array"]
1416    pub fn into_exposed_unprotected_array_caller_must_zeroize(mut self) -> ([u8; CAP], usize) {
1417        let len = self.len;
1418        self.len = 0;
1419        (core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
1420    }
1421}
1422
1423impl<const CAP: usize> Drop for ExposedEncodedArray<CAP> {
1424    fn drop(&mut self) {
1425        wipe_bytes(&mut self.bytes);
1426        self.len = 0;
1427    }
1428}
1429
1430impl<const CAP: usize> core::fmt::Debug for ExposedEncodedArray<CAP> {
1431    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1432        formatter
1433            .debug_struct("ExposedEncodedArray")
1434            .field("bytes", &"<redacted>")
1435            .field("len", &self.len)
1436            .field("capacity", &CAP)
1437            .finish()
1438    }
1439}
1440
1441impl<const CAP: usize> EncodedBuffer<CAP> {
1442    /// Creates an empty encoded buffer.
1443    #[must_use]
1444    pub const fn new() -> Self {
1445        Self {
1446            bytes: [0u8; CAP],
1447            len: 0,
1448        }
1449    }
1450
1451    /// Returns the number of visible encoded bytes.
1452    #[must_use]
1453    pub const fn len(&self) -> usize {
1454        self.len
1455    }
1456
1457    /// Returns whether the buffer has no visible encoded bytes.
1458    #[must_use]
1459    pub const fn is_empty(&self) -> bool {
1460        self.len == 0
1461    }
1462
1463    /// Returns whether the visible encoded bytes fill the stack backing array.
1464    #[must_use]
1465    pub const fn is_full(&self) -> bool {
1466        self.len == CAP
1467    }
1468
1469    /// Returns the stack capacity in bytes.
1470    #[must_use]
1471    pub const fn capacity(&self) -> usize {
1472        CAP
1473    }
1474
1475    /// Returns the number of unused bytes in the stack backing array.
1476    #[must_use]
1477    pub const fn remaining_capacity(&self) -> usize {
1478        CAP - self.len
1479    }
1480
1481    /// Returns the visible encoded bytes.
1482    #[must_use]
1483    pub fn as_bytes(&self) -> &[u8] {
1484        &self.bytes[..self.len]
1485    }
1486
1487    /// Returns the visible encoded bytes as UTF-8 text.
1488    ///
1489    /// Encoded Base64 output is produced as ASCII by this crate, so this
1490    /// method should not fail unless an internal invariant has been broken.
1491    /// It is provided for callers that prefer a fallible accessor over
1492    /// [`Self::as_str`].
1493    pub fn as_utf8(&self) -> Result<&str, core::str::Utf8Error> {
1494        core::str::from_utf8(self.as_bytes())
1495    }
1496
1497    /// Returns the visible encoded bytes as UTF-8.
1498    ///
1499    /// # Panics
1500    ///
1501    /// Panics only if the crate's internal invariant is broken and the buffer
1502    /// contains non-UTF-8 bytes.
1503    #[must_use]
1504    pub fn as_str(&self) -> &str {
1505        match self.as_utf8() {
1506            Ok(output) => output,
1507            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
1508        }
1509    }
1510
1511    /// Compares this encoded output to `other` without short-circuiting on the
1512    /// first differing byte.
1513    ///
1514    /// Length and the final equality result remain public. Different lengths
1515    /// return `false` immediately; use this helper only when the compared
1516    /// lengths are public protocol facts or have been normalized by the
1517    /// caller. For equal-length inputs, this helper scans every byte before
1518    /// returning. It is constant-time-oriented best effort, not a formal
1519    /// cryptographic constant-time guarantee. This comparison is deliberately
1520    /// explicit: redacted buffer types do not implement [`PartialEq`] because
1521    /// `==` would make a best-effort helper look like a formal token/MAC
1522    /// comparison primitive.
1523    ///
1524    /// Do not use this helper as the sole MAC, bearer-token, password-hash, or
1525    /// authentication-secret comparison primitive in high-assurance systems.
1526    /// Applications that can admit dependencies should use a reviewed
1527    /// constant-time comparison primitive, such as `subtle`, at the protocol
1528    /// boundary.
1529    #[doc(alias = "constant_time_eq")]
1530    #[must_use]
1531    pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
1532        constant_time_eq_public_len(self.as_bytes(), other)
1533    }
1534
1535    /// Consumes the wrapper and returns the backing array plus visible length
1536    /// inside a drop-wiping exposed wrapper.
1537    ///
1538    /// This is an explicit escape hatch for no-alloc interop with APIs that
1539    /// require ownership of a fixed array. The returned
1540    /// [`ExposedEncodedArray`] remains redacted by formatting and clears its
1541    /// backing array on drop.
1542    #[must_use]
1543    pub fn into_exposed_array(mut self) -> ExposedEncodedArray<CAP> {
1544        let len = self.len;
1545        self.len = 0;
1546        ExposedEncodedArray::from_array(core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
1547    }
1548
1549    /// Clears the visible bytes and the full backing array.
1550    pub fn clear(&mut self) {
1551        wipe_bytes(&mut self.bytes);
1552        self.len = 0;
1553    }
1554
1555    /// Clears bytes after the visible prefix.
1556    pub fn clear_tail(&mut self) {
1557        wipe_tail(&mut self.bytes, self.len);
1558    }
1559}
1560
1561impl<const CAP: usize> AsRef<[u8]> for EncodedBuffer<CAP> {
1562    fn as_ref(&self) -> &[u8] {
1563        self.as_bytes()
1564    }
1565}
1566
1567impl<const CAP: usize> Clone for EncodedBuffer<CAP> {
1568    /// Clones the visible encoded bytes into a second stack-backed buffer.
1569    ///
1570    /// Security note: cloning duplicates the visible bytes in memory. Both the
1571    /// original and the clone must be dropped or explicitly cleared before the
1572    /// duplicated bytes are gone on the crate's best-effort cleanup path. The
1573    /// compiler may also create temporary stack copies while performing the
1574    /// copy; those intermediates are outside this crate's cleanup boundary.
1575    /// Avoid cloning encoded secret material; use `SecretBuffer` when redacted
1576    /// formatting and heap-owned secret handling are required.
1577    fn clone(&self) -> Self {
1578        let mut output = Self::new();
1579        output.bytes[..self.len].copy_from_slice(self.as_bytes());
1580        output.len = self.len;
1581        output
1582    }
1583}
1584
1585impl<const CAP: usize> core::fmt::Debug for EncodedBuffer<CAP> {
1586    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1587        formatter
1588            .debug_struct("EncodedBuffer")
1589            .field("bytes", &"<redacted>")
1590            .field("len", &self.len)
1591            .field("capacity", &CAP)
1592            .finish()
1593    }
1594}
1595
1596impl<const CAP: usize> core::fmt::Display for EncodedBuffer<CAP> {
1597    /// Writes the full Base64 text.
1598    ///
1599    /// Security note: this is intentionally not redacted. Do not use
1600    /// `EncodedBuffer` for encoded secrets that may reach logs or error
1601    /// messages; use `SecretBuffer` for redacted formatting.
1602    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1603        formatter.write_str(self.as_str())
1604    }
1605}
1606
1607impl<const CAP: usize> Default for EncodedBuffer<CAP> {
1608    fn default() -> Self {
1609        Self::new()
1610    }
1611}
1612
1613impl<const CAP: usize> Drop for EncodedBuffer<CAP> {
1614    fn drop(&mut self) {
1615        self.clear();
1616    }
1617}
1618
1619impl<const CAP: usize> TryFrom<&[u8]> for EncodedBuffer<CAP> {
1620    type Error = EncodeError;
1621
1622    /// Encodes bytes into strict standard padded Base64 in a stack-backed
1623    /// buffer.
1624    ///
1625    /// Use [`Engine::encode_buffer`] or [`Profile::encode_buffer`] when a
1626    /// different alphabet, padding mode, or line-wrapping profile is required.
1627    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
1628        STANDARD.encode_buffer(input)
1629    }
1630}
1631
1632impl<const CAP: usize, const N: usize> TryFrom<&[u8; N]> for EncodedBuffer<CAP> {
1633    type Error = EncodeError;
1634
1635    /// Encodes a byte array into strict standard padded Base64 in a
1636    /// stack-backed buffer.
1637    ///
1638    /// Use [`Engine::encode_buffer`] or [`Profile::encode_buffer`] when a
1639    /// different alphabet, padding mode, or line-wrapping profile is required.
1640    fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
1641        Self::try_from(&input[..])
1642    }
1643}
1644
1645impl<const CAP: usize> TryFrom<&str> for EncodedBuffer<CAP> {
1646    type Error = EncodeError;
1647
1648    /// Encodes UTF-8 text bytes into strict standard padded Base64 in a
1649    /// stack-backed buffer.
1650    ///
1651    /// This treats the string as raw input bytes. Use
1652    /// [`Engine::encode_buffer`] or [`Profile::encode_buffer`] when a
1653    /// different alphabet, padding mode, or line-wrapping profile is required.
1654    fn try_from(input: &str) -> Result<Self, Self::Error> {
1655        Self::try_from(input.as_bytes())
1656    }
1657}
1658
1659/// Stack-backed decoded Base64 output.
1660///
1661/// This type is intended for short decoded values where heap allocation would
1662/// be unnecessary but manually sizing and passing a separate output slice is
1663/// noisy. Decoded data may be binary or secret-bearing, so formatting is
1664/// redacted and contents are exposed only through explicit byte accessors.
1665///
1666/// The backing array is cleared when the value is dropped. This is best-effort
1667/// data-retention reduction and is not a formal zeroization guarantee.
1668///
1669/// On `wasm32` targets, the wipe barrier uses only a compiler fence. The wasm
1670/// runtime JIT may still optimize or retain cleared bytes in ways this crate
1671/// cannot control. `wasm32` builds fail closed by default; enable
1672/// `allow-wasm32-best-effort-wipe` only when the deployment explicitly accepts
1673/// this limitation and applies its own memory strategy around stack-backed
1674/// buffers.
1675pub struct DecodedBuffer<const CAP: usize> {
1676    bytes: [u8; CAP],
1677    len: usize,
1678}
1679
1680/// Owned stack array extracted from [`DecodedBuffer`].
1681///
1682/// This wrapper keeps the extracted decoded bytes on the crate's best-effort
1683/// drop-time cleanup path. Use
1684/// [`Self::into_exposed_unprotected_array_caller_must_zeroize`] only when a
1685/// bare array is unavoidable and the caller will handle cleanup.
1686pub struct ExposedDecodedArray<const CAP: usize> {
1687    bytes: [u8; CAP],
1688    len: usize,
1689}
1690
1691impl<const CAP: usize> ExposedDecodedArray<CAP> {
1692    /// Wraps a decoded backing array and visible length.
1693    ///
1694    /// # Panics
1695    ///
1696    /// Panics if `len` is greater than `CAP`.
1697    #[must_use]
1698    pub const fn from_array(bytes: [u8; CAP], len: usize) -> Self {
1699        assert!(len <= CAP, "visible length exceeds array capacity");
1700        Self { bytes, len }
1701    }
1702
1703    /// Returns the visible decoded bytes.
1704    #[must_use]
1705    pub fn as_bytes(&self) -> &[u8] {
1706        &self.bytes[..self.len]
1707    }
1708
1709    /// Returns the number of visible decoded bytes.
1710    #[must_use]
1711    pub const fn len(&self) -> usize {
1712        self.len
1713    }
1714
1715    /// Returns whether there are no visible decoded bytes.
1716    #[must_use]
1717    pub const fn is_empty(&self) -> bool {
1718        self.len == 0
1719    }
1720
1721    /// Returns the backing array capacity.
1722    #[must_use]
1723    pub const fn capacity(&self) -> usize {
1724        CAP
1725    }
1726
1727    /// Consumes the wrapper and returns a bare array plus visible length.
1728    ///
1729    /// This is an unprotected escape hatch. The returned array will not be
1730    /// cleared by this crate on drop. Callers must clear it with their own
1731    /// approved zeroization policy.
1732    ///
1733    /// # Security
1734    ///
1735    /// Treat this as a cleanup-boundary API. Failing to clear the returned
1736    /// array leaves decoded bytes, which may be secret-bearing, in ordinary
1737    /// caller-owned memory until overwritten by later stack or heap activity.
1738    #[must_use = "caller must zeroize the returned array"]
1739    pub fn into_exposed_unprotected_array_caller_must_zeroize(mut self) -> ([u8; CAP], usize) {
1740        let len = self.len;
1741        self.len = 0;
1742        (core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
1743    }
1744}
1745
1746impl<const CAP: usize> Drop for ExposedDecodedArray<CAP> {
1747    fn drop(&mut self) {
1748        wipe_bytes(&mut self.bytes);
1749        self.len = 0;
1750    }
1751}
1752
1753impl<const CAP: usize> core::fmt::Debug for ExposedDecodedArray<CAP> {
1754    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1755        formatter
1756            .debug_struct("ExposedDecodedArray")
1757            .field("bytes", &"<redacted>")
1758            .field("len", &self.len)
1759            .field("capacity", &CAP)
1760            .finish()
1761    }
1762}
1763
1764impl<const CAP: usize> DecodedBuffer<CAP> {
1765    /// Creates an empty decoded buffer.
1766    #[must_use]
1767    pub const fn new() -> Self {
1768        Self {
1769            bytes: [0u8; CAP],
1770            len: 0,
1771        }
1772    }
1773
1774    /// Returns the number of visible decoded bytes.
1775    #[must_use]
1776    pub const fn len(&self) -> usize {
1777        self.len
1778    }
1779
1780    /// Returns whether the buffer has no visible decoded bytes.
1781    #[must_use]
1782    pub const fn is_empty(&self) -> bool {
1783        self.len == 0
1784    }
1785
1786    /// Returns whether the visible decoded bytes fill the stack backing array.
1787    #[must_use]
1788    pub const fn is_full(&self) -> bool {
1789        self.len == CAP
1790    }
1791
1792    /// Returns the stack capacity in bytes.
1793    #[must_use]
1794    pub const fn capacity(&self) -> usize {
1795        CAP
1796    }
1797
1798    /// Returns the number of unused bytes in the stack backing array.
1799    #[must_use]
1800    pub const fn remaining_capacity(&self) -> usize {
1801        CAP - self.len
1802    }
1803
1804    /// Returns the visible decoded bytes.
1805    #[must_use]
1806    pub fn as_bytes(&self) -> &[u8] {
1807        &self.bytes[..self.len]
1808    }
1809
1810    /// Returns the visible decoded bytes as UTF-8 text.
1811    ///
1812    /// Decoded Base64 output is arbitrary bytes, so this method is fallible.
1813    /// Use [`Self::as_bytes`] when the decoded payload is binary or when text
1814    /// validation belongs to a higher protocol layer.
1815    pub fn as_utf8(&self) -> Result<&str, core::str::Utf8Error> {
1816        core::str::from_utf8(self.as_bytes())
1817    }
1818
1819    /// Compares this decoded output to `other` without short-circuiting on the
1820    /// first differing byte.
1821    ///
1822    /// Length and the final equality result remain public. Different lengths
1823    /// return `false` immediately; use this helper only when the compared
1824    /// lengths are public protocol facts or have been normalized by the
1825    /// caller. For equal-length inputs, this helper scans every byte before
1826    /// returning. It is constant-time-oriented best effort, not a formal
1827    /// cryptographic constant-time guarantee. This comparison is deliberately
1828    /// explicit: redacted buffer types do not implement [`PartialEq`] because
1829    /// `==` would make a best-effort helper look like a formal token/MAC
1830    /// comparison primitive.
1831    ///
1832    /// Do not use this helper as the sole MAC, bearer-token, password-hash, or
1833    /// authentication-secret comparison primitive in high-assurance systems.
1834    /// Applications that can admit dependencies should use a reviewed
1835    /// constant-time comparison primitive, such as `subtle`, at the protocol
1836    /// boundary.
1837    #[doc(alias = "constant_time_eq")]
1838    #[must_use]
1839    pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
1840        constant_time_eq_public_len(self.as_bytes(), other)
1841    }
1842
1843    /// Consumes the wrapper and returns the backing array plus visible length
1844    /// inside a drop-wiping exposed wrapper.
1845    ///
1846    /// This is an explicit escape hatch for no-alloc interop with APIs that
1847    /// require ownership of a fixed array. The returned
1848    /// [`ExposedDecodedArray`] remains redacted by formatting and clears its
1849    /// backing array on drop.
1850    #[must_use]
1851    pub fn into_exposed_array(mut self) -> ExposedDecodedArray<CAP> {
1852        let len = self.len;
1853        self.len = 0;
1854        ExposedDecodedArray::from_array(core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
1855    }
1856
1857    /// Clears the visible bytes and the full backing array.
1858    pub fn clear(&mut self) {
1859        wipe_bytes(&mut self.bytes);
1860        self.len = 0;
1861    }
1862
1863    /// Clears bytes after the visible prefix.
1864    pub fn clear_tail(&mut self) {
1865        wipe_tail(&mut self.bytes, self.len);
1866    }
1867}
1868
1869impl<const CAP: usize> AsRef<[u8]> for DecodedBuffer<CAP> {
1870    fn as_ref(&self) -> &[u8] {
1871        self.as_bytes()
1872    }
1873}
1874
1875impl<const CAP: usize> Clone for DecodedBuffer<CAP> {
1876    /// Clones the visible decoded bytes into a second stack-backed buffer.
1877    ///
1878    /// Security note: cloning duplicates decoded bytes in memory. Both the
1879    /// original and the clone must be dropped or explicitly cleared before the
1880    /// duplicated bytes are gone on the crate's best-effort cleanup path. The
1881    /// compiler may also create temporary stack copies while performing the
1882    /// copy; those intermediates are outside this crate's cleanup boundary. For
1883    /// high-assurance applications, avoid cloning decoded key material and use
1884    /// `SecretBuffer` for heap-owned secrets without a `Clone` implementation.
1885    fn clone(&self) -> Self {
1886        let mut output = Self::new();
1887        output.bytes[..self.len].copy_from_slice(self.as_bytes());
1888        output.len = self.len;
1889        output
1890    }
1891}
1892
1893impl<const CAP: usize> core::fmt::Debug for DecodedBuffer<CAP> {
1894    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1895        formatter
1896            .debug_struct("DecodedBuffer")
1897            .field("bytes", &"<redacted>")
1898            .field("len", &self.len)
1899            .field("capacity", &CAP)
1900            .finish()
1901    }
1902}
1903
1904impl<const CAP: usize> Default for DecodedBuffer<CAP> {
1905    fn default() -> Self {
1906        Self::new()
1907    }
1908}
1909
1910impl<const CAP: usize> Drop for DecodedBuffer<CAP> {
1911    fn drop(&mut self) {
1912        self.clear();
1913    }
1914}
1915
1916impl<const CAP: usize> TryFrom<&[u8]> for DecodedBuffer<CAP> {
1917    type Error = DecodeError;
1918
1919    /// Decodes strict standard padded Base64 into a stack-backed buffer.
1920    ///
1921    /// Use [`Engine::decode_buffer`] or [`Profile::decode_buffer`] when a
1922    /// different alphabet, padding mode, or line-wrapping profile is required.
1923    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
1924        STANDARD.decode_buffer(input)
1925    }
1926}
1927
1928impl<const CAP: usize, const N: usize> TryFrom<&[u8; N]> for DecodedBuffer<CAP> {
1929    type Error = DecodeError;
1930
1931    /// Decodes a strict standard padded Base64 byte array into a stack-backed
1932    /// buffer.
1933    ///
1934    /// Use [`Engine::decode_buffer`] or [`Profile::decode_buffer`] when a
1935    /// different alphabet, padding mode, or line-wrapping profile is required.
1936    fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
1937        Self::try_from(&input[..])
1938    }
1939}
1940
1941impl<const CAP: usize> TryFrom<&str> for DecodedBuffer<CAP> {
1942    type Error = DecodeError;
1943
1944    /// Decodes strict standard padded Base64 text into a stack-backed buffer.
1945    ///
1946    /// Use [`Engine::decode_buffer`] or [`Profile::decode_buffer`] when a
1947    /// different alphabet, padding mode, or line-wrapping profile is required.
1948    fn try_from(input: &str) -> Result<Self, Self::Error> {
1949        Self::try_from(input.as_bytes())
1950    }
1951}
1952
1953impl<const CAP: usize> core::str::FromStr for DecodedBuffer<CAP> {
1954    type Err = DecodeError;
1955
1956    /// Decodes strict standard padded Base64 text into a stack-backed buffer.
1957    ///
1958    /// Use [`Engine::decode_buffer`] or [`Profile::decode_buffer`] when a
1959    /// different alphabet, padding mode, or line-wrapping profile is required.
1960    fn from_str(input: &str) -> Result<Self, Self::Err> {
1961        Self::try_from(input)
1962    }
1963}
1964
1965/// Owned sensitive bytes with redacted formatting and drop-time cleanup.
1966///
1967/// `SecretBuffer` is available with the `alloc` feature. It is intended for
1968/// decoded keys, tokens, and other values that should not be accidentally
1969/// logged. The buffer exposes contents only through explicit reveal methods.
1970///
1971/// Spare vector capacity is cleared when wrapping owned bytes. On drop,
1972/// initialized bytes and vector spare capacity are cleared with the crate's
1973/// internal best-effort wipe helpers. This is data-retention reduction, not a
1974/// formal zeroization guarantee, and it cannot make claims about allocator
1975/// behavior or historical copies outside the wrapper.
1976///
1977/// # Platform Memory Controls
1978///
1979/// `SecretBuffer` does not lock its allocation into physical memory. The OS
1980/// may page its contents to disk, include them in hibernation images, or expose
1981/// them through crash dumps. High-assurance deployments must combine
1982/// `SecretBuffer` with platform memory-locking where available, encrypted or
1983/// disabled swap, crash-dump suppression, and allocator isolation appropriate
1984/// for their environment.
1985///
1986/// On `wasm32` targets, the wipe barrier uses only a compiler fence. The wasm
1987/// runtime JIT may still optimize or retain cleared bytes in ways this crate
1988/// cannot control. `wasm32` builds fail closed by default; enable
1989/// `allow-wasm32-best-effort-wipe` only when the deployment explicitly accepts
1990/// this limitation and applies its own memory strategy around owned secret
1991/// buffers.
1992#[cfg(feature = "alloc")]
1993pub struct SecretBuffer {
1994    bytes: alloc::vec::Vec<u8>,
1995}
1996
1997/// Owned secret bytes extracted from [`SecretBuffer`].
1998///
1999/// This wrapper keeps redacted formatting, best-effort spare-capacity clearing
2000/// at construction time, and best-effort full wipe on drop after a
2001/// [`SecretBuffer`] is consumed for owned interop. Use
2002/// [`Self::into_exposed_unprotected_vec_caller_must_zeroize`] only when a raw
2003/// `Vec<u8>` is unavoidable and the caller will handle cleanup.
2004#[cfg(feature = "alloc")]
2005pub struct ExposedSecretVec {
2006    bytes: alloc::vec::Vec<u8>,
2007}
2008
2009#[cfg(feature = "alloc")]
2010impl ExposedSecretVec {
2011    /// Wraps an owned vector as exposed secret material.
2012    #[must_use]
2013    pub fn from_vec(mut bytes: alloc::vec::Vec<u8>) -> Self {
2014        wipe_vec_spare_capacity(&mut bytes);
2015        Self { bytes }
2016    }
2017
2018    /// Returns the number of initialized secret bytes.
2019    #[must_use]
2020    pub fn len(&self) -> usize {
2021        self.bytes.len()
2022    }
2023
2024    /// Returns whether the buffer contains no initialized secret bytes.
2025    #[must_use]
2026    pub fn is_empty(&self) -> bool {
2027        self.bytes.is_empty()
2028    }
2029
2030    /// Reveals the secret bytes.
2031    ///
2032    /// This method is intentionally named to make secret access explicit at the
2033    /// call site.
2034    #[must_use]
2035    pub fn expose_secret(&self) -> &[u8] {
2036        &self.bytes
2037    }
2038
2039    /// Reveals the secret bytes mutably.
2040    ///
2041    /// This method is intentionally named to make secret access explicit at the
2042    /// call site.
2043    #[must_use]
2044    pub fn expose_secret_mut(&mut self) -> &mut [u8] {
2045        &mut self.bytes
2046    }
2047
2048    /// Consumes the wrapper and returns a raw `Vec<u8>`.
2049    ///
2050    /// This is an unprotected escape hatch. The returned vector is no longer
2051    /// redacted by formatting and will not be cleared by this crate on drop.
2052    /// Callers must clear it with their own approved zeroization policy.
2053    #[must_use = "caller must zeroize the returned Vec"]
2054    pub fn into_exposed_unprotected_vec_caller_must_zeroize(mut self) -> alloc::vec::Vec<u8> {
2055        core::mem::take(&mut self.bytes)
2056    }
2057}
2058
2059#[cfg(feature = "alloc")]
2060impl core::fmt::Debug for ExposedSecretVec {
2061    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2062        formatter
2063            .debug_struct("ExposedSecretVec")
2064            .field("bytes", &"<redacted>")
2065            .field("len", &self.len())
2066            .finish()
2067    }
2068}
2069
2070#[cfg(feature = "alloc")]
2071impl core::fmt::Display for ExposedSecretVec {
2072    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2073        formatter.write_str("<redacted>")
2074    }
2075}
2076
2077#[cfg(feature = "alloc")]
2078impl Drop for ExposedSecretVec {
2079    fn drop(&mut self) {
2080        wipe_vec_all(&mut self.bytes);
2081    }
2082}
2083
2084#[cfg(feature = "alloc")]
2085struct WipeVecGuard {
2086    bytes: alloc::vec::Vec<u8>,
2087}
2088
2089#[cfg(feature = "alloc")]
2090impl WipeVecGuard {
2091    fn from_vec(bytes: alloc::vec::Vec<u8>) -> Self {
2092        Self { bytes }
2093    }
2094
2095    fn into_validated_secret_string(mut self) -> alloc::string::String {
2096        wipe_vec_spare_capacity(&mut self.bytes);
2097        let bytes = core::mem::take(&mut self.bytes);
2098        core::mem::forget(self);
2099        string_from_validated_secret_bytes(bytes)
2100    }
2101}
2102
2103#[cfg(feature = "alloc")]
2104impl Drop for WipeVecGuard {
2105    fn drop(&mut self) {
2106        wipe_vec_all(&mut self.bytes);
2107    }
2108}
2109
2110#[cfg(feature = "alloc")]
2111impl AsRef<[u8]> for ExposedSecretVec {
2112    fn as_ref(&self) -> &[u8] {
2113        self.expose_secret()
2114    }
2115}
2116
2117#[cfg(feature = "alloc")]
2118impl AsMut<[u8]> for ExposedSecretVec {
2119    fn as_mut(&mut self) -> &mut [u8] {
2120        self.expose_secret_mut()
2121    }
2122}
2123
2124/// Owned secret UTF-8 text extracted from [`SecretBuffer`].
2125///
2126/// This wrapper keeps redacted formatting, best-effort spare-capacity clearing
2127/// at construction time, and best-effort full wipe on drop after a
2128/// [`SecretBuffer`] is consumed for string interop. Use
2129/// [`Self::into_exposed_unprotected_string_caller_must_zeroize`] only when a
2130/// raw `String` is unavoidable and the caller will handle cleanup.
2131#[cfg(feature = "alloc")]
2132pub struct ExposedSecretString {
2133    text: alloc::string::String,
2134}
2135
2136#[cfg(feature = "alloc")]
2137impl ExposedSecretString {
2138    /// Wraps an owned UTF-8 string as exposed secret text.
2139    #[must_use]
2140    pub fn from_string(text: alloc::string::String) -> Self {
2141        let mut bytes = text.into_bytes();
2142        wipe_vec_spare_capacity(&mut bytes);
2143        let text = string_from_validated_secret_bytes(bytes);
2144        Self { text }
2145    }
2146
2147    /// Returns the length of the secret text in bytes.
2148    #[must_use]
2149    pub fn len(&self) -> usize {
2150        self.text.len()
2151    }
2152
2153    /// Returns whether the secret text is empty.
2154    #[must_use]
2155    pub fn is_empty(&self) -> bool {
2156        self.text.is_empty()
2157    }
2158
2159    /// Reveals the secret text.
2160    ///
2161    /// This method is intentionally named to make secret access explicit at
2162    /// the call site.
2163    #[must_use]
2164    pub fn expose_secret(&self) -> &str {
2165        &self.text
2166    }
2167
2168    /// Reveals the secret text as bytes.
2169    ///
2170    /// This method is intentionally named to make secret access explicit at
2171    /// the call site.
2172    #[must_use]
2173    pub fn expose_secret_bytes(&self) -> &[u8] {
2174        self.text.as_bytes()
2175    }
2176
2177    /// Consumes the wrapper and returns a raw `String`.
2178    ///
2179    /// This is an unprotected escape hatch. The returned string is no longer
2180    /// redacted by formatting and will not be cleared by this crate on drop.
2181    /// Callers must clear it with their own approved zeroization policy.
2182    #[must_use = "caller must zeroize the returned String"]
2183    pub fn into_exposed_unprotected_string_caller_must_zeroize(mut self) -> alloc::string::String {
2184        core::mem::take(&mut self.text)
2185    }
2186}
2187
2188#[cfg(feature = "alloc")]
2189impl core::fmt::Debug for ExposedSecretString {
2190    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2191        formatter
2192            .debug_struct("ExposedSecretString")
2193            .field("text", &"<redacted>")
2194            .field("len", &self.len())
2195            .finish()
2196    }
2197}
2198
2199#[cfg(feature = "alloc")]
2200impl core::fmt::Display for ExposedSecretString {
2201    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2202        formatter.write_str("<redacted>")
2203    }
2204}
2205
2206#[cfg(feature = "alloc")]
2207impl Drop for ExposedSecretString {
2208    fn drop(&mut self) {
2209        let mut bytes = core::mem::take(&mut self.text).into_bytes();
2210        wipe_vec_all(&mut bytes);
2211    }
2212}
2213
2214#[cfg(feature = "alloc")]
2215impl AsRef<str> for ExposedSecretString {
2216    fn as_ref(&self) -> &str {
2217        self.expose_secret()
2218    }
2219}
2220
2221#[cfg(feature = "alloc")]
2222impl SecretBuffer {
2223    /// Wraps an existing vector as sensitive material.
2224    #[must_use]
2225    pub fn from_vec(mut bytes: alloc::vec::Vec<u8>) -> Self {
2226        wipe_vec_spare_capacity(&mut bytes);
2227        Self { bytes }
2228    }
2229
2230    /// Copies a slice into an owned sensitive buffer.
2231    #[must_use]
2232    pub fn from_slice(bytes: &[u8]) -> Self {
2233        Self::from_vec(bytes.to_vec())
2234    }
2235
2236    /// Returns the number of initialized secret bytes.
2237    #[must_use]
2238    pub fn len(&self) -> usize {
2239        self.bytes.len()
2240    }
2241
2242    /// Returns whether the buffer contains no initialized secret bytes.
2243    #[must_use]
2244    pub fn is_empty(&self) -> bool {
2245        self.bytes.is_empty()
2246    }
2247
2248    /// Reveals the secret bytes.
2249    ///
2250    /// This method is intentionally named to make secret access explicit at the
2251    /// call site.
2252    #[must_use]
2253    pub fn expose_secret(&self) -> &[u8] {
2254        &self.bytes
2255    }
2256
2257    /// Reveals the secret bytes as UTF-8 text.
2258    ///
2259    /// This method is intentionally named to make secret access explicit at the
2260    /// call site. Secret material may be arbitrary binary data, so this method
2261    /// is fallible.
2262    pub fn expose_secret_utf8(&self) -> Result<&str, core::str::Utf8Error> {
2263        core::str::from_utf8(self.expose_secret())
2264    }
2265
2266    /// Reveals the secret bytes mutably.
2267    ///
2268    /// This method is intentionally named to make secret access explicit at the
2269    /// call site.
2270    #[must_use]
2271    pub fn expose_secret_mut(&mut self) -> &mut [u8] {
2272        &mut self.bytes
2273    }
2274
2275    /// Consumes the wrapper and returns owned secret bytes.
2276    ///
2277    /// This is an explicit escape hatch for interop with APIs that require an
2278    /// owned vector-like value. The returned [`ExposedSecretVec`] remains
2279    /// redacted by formatting and clears its vector on drop.
2280    #[must_use]
2281    pub fn into_exposed_vec(mut self) -> ExposedSecretVec {
2282        ExposedSecretVec::from_vec(core::mem::take(&mut self.bytes))
2283    }
2284
2285    /// Consumes the wrapper and returns the owned secret bytes as UTF-8 text.
2286    ///
2287    /// This is an explicit escape hatch for interop with APIs that require an
2288    /// owned string-like value. The returned [`ExposedSecretString`] remains
2289    /// redacted by formatting and clears its heap allocation on drop.
2290    ///
2291    /// If the secret bytes are not valid UTF-8, the original redacted wrapper
2292    /// is returned unchanged.
2293    #[must_use = "handle invalid UTF-8 errors and keep the returned wrapper protected"]
2294    pub fn try_into_exposed_string(self) -> Result<ExposedSecretString, Self> {
2295        if core::str::from_utf8(self.expose_secret()).is_err() {
2296            return Err(self);
2297        }
2298
2299        // Keep the bytes behind a wiping guard until the final infallible
2300        // ownership transfer into `String`.
2301        let mut exposed = self.into_exposed_vec();
2302        let guard = WipeVecGuard::from_vec(core::mem::take(&mut exposed.bytes));
2303        drop(exposed);
2304        Ok(ExposedSecretString::from_string(
2305            guard.into_validated_secret_string(),
2306        ))
2307    }
2308
2309    /// Compares this secret to `other` without short-circuiting on the first
2310    /// differing byte.
2311    ///
2312    /// Length and the final equality result remain public. Different lengths
2313    /// return `false` immediately; use this helper only when the compared
2314    /// lengths are public protocol facts or have been normalized by the
2315    /// caller. For equal-length inputs, this helper scans every byte before
2316    /// returning. It is constant-time-oriented best effort, not a formal
2317    /// cryptographic constant-time guarantee. This comparison is deliberately
2318    /// explicit: redacted buffer types do not implement [`PartialEq`] because
2319    /// `==` would make a best-effort helper look like a formal token/MAC
2320    /// comparison primitive.
2321    ///
2322    /// Do not use this helper as the sole MAC, bearer-token, password-hash, or
2323    /// authentication-secret comparison primitive in high-assurance systems.
2324    /// Applications that can admit dependencies should use a reviewed
2325    /// constant-time comparison primitive, such as `subtle`, at the protocol
2326    /// boundary.
2327    #[doc(alias = "constant_time_eq")]
2328    #[must_use]
2329    pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
2330        constant_time_eq_public_len(self.expose_secret(), other)
2331    }
2332
2333    /// Clears the initialized bytes and makes the buffer empty.
2334    pub fn clear(&mut self) {
2335        self.bytes.clear();
2336        wipe_vec_all(&mut self.bytes);
2337    }
2338}
2339
2340#[cfg(feature = "alloc")]
2341impl core::fmt::Debug for SecretBuffer {
2342    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2343        formatter
2344            .debug_struct("SecretBuffer")
2345            .field("bytes", &"<redacted>")
2346            .field("len", &self.len())
2347            .finish()
2348    }
2349}
2350
2351#[cfg(feature = "alloc")]
2352impl core::fmt::Display for SecretBuffer {
2353    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2354        formatter.write_str("<redacted>")
2355    }
2356}
2357
2358#[cfg(feature = "alloc")]
2359impl Drop for SecretBuffer {
2360    fn drop(&mut self) {
2361        wipe_vec_all(&mut self.bytes);
2362    }
2363}
2364
2365#[cfg(feature = "alloc")]
2366impl From<alloc::vec::Vec<u8>> for SecretBuffer {
2367    /// Wraps an owned vector as sensitive material.
2368    ///
2369    /// Spare capacity is cleared immediately before the vector is stored.
2370    /// Use [`SecretBuffer::from_slice`] when the source data is borrowed.
2371    fn from(bytes: alloc::vec::Vec<u8>) -> Self {
2372        Self::from_vec(bytes)
2373    }
2374}
2375
2376#[cfg(feature = "alloc")]
2377impl From<alloc::string::String> for SecretBuffer {
2378    /// Wraps an owned UTF-8 string as sensitive material.
2379    ///
2380    /// The string is consumed without copying its initialized bytes. Spare
2381    /// vector capacity is cleared immediately before the bytes are stored.
2382    fn from(text: alloc::string::String) -> Self {
2383        Self::from_vec(text.into_bytes())
2384    }
2385}
2386
2387#[cfg(feature = "alloc")]
2388impl<const CAP: usize> From<EncodedBuffer<CAP>> for SecretBuffer {
2389    /// Copies visible encoded bytes from a stack-backed buffer into an owned
2390    /// redacted buffer.
2391    ///
2392    /// The consumed stack-backed buffer clears its backing array when it is
2393    /// dropped at the end of the conversion.
2394    fn from(buffer: EncodedBuffer<CAP>) -> Self {
2395        Self::from_slice(buffer.as_bytes())
2396    }
2397}
2398
2399#[cfg(feature = "alloc")]
2400impl<const CAP: usize> From<DecodedBuffer<CAP>> for SecretBuffer {
2401    /// Copies visible decoded bytes from a stack-backed buffer into an owned
2402    /// redacted buffer.
2403    ///
2404    /// The consumed stack-backed buffer clears its backing array when it is
2405    /// dropped at the end of the conversion.
2406    fn from(buffer: DecodedBuffer<CAP>) -> Self {
2407        Self::from_slice(buffer.as_bytes())
2408    }
2409}
2410
2411#[cfg(feature = "alloc")]
2412impl TryFrom<&[u8]> for SecretBuffer {
2413    type Error = DecodeError;
2414
2415    /// Decodes strict standard padded Base64 into a redacted owned buffer.
2416    ///
2417    /// Use [`Engine::decode_secret`] or [`Profile::decode_secret`] when a
2418    /// different alphabet, padding mode, or line-wrapping profile is required.
2419    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
2420        STANDARD.decode_secret(input)
2421    }
2422}
2423
2424#[cfg(feature = "alloc")]
2425impl<const N: usize> TryFrom<&[u8; N]> for SecretBuffer {
2426    type Error = DecodeError;
2427
2428    /// Decodes a strict standard padded Base64 byte array into a redacted
2429    /// owned buffer.
2430    ///
2431    /// Use [`Engine::decode_secret`] or [`Profile::decode_secret`] when a
2432    /// different alphabet, padding mode, or line-wrapping profile is required.
2433    fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
2434        Self::try_from(&input[..])
2435    }
2436}
2437
2438#[cfg(feature = "alloc")]
2439impl TryFrom<&str> for SecretBuffer {
2440    type Error = DecodeError;
2441
2442    /// Decodes strict standard padded Base64 text into a redacted owned buffer.
2443    ///
2444    /// Use [`Engine::decode_secret`] or [`Profile::decode_secret`] when a
2445    /// different alphabet, padding mode, or line-wrapping profile is required.
2446    fn try_from(input: &str) -> Result<Self, Self::Error> {
2447        Self::try_from(input.as_bytes())
2448    }
2449}
2450
2451#[cfg(feature = "alloc")]
2452impl core::str::FromStr for SecretBuffer {
2453    type Err = DecodeError;
2454
2455    /// Decodes strict standard padded Base64 text into a redacted owned buffer.
2456    ///
2457    /// Use [`Engine::decode_secret`] or [`Profile::decode_secret`] when a
2458    /// different alphabet, padding mode, or line-wrapping profile is required.
2459    fn from_str(input: &str) -> Result<Self, Self::Err> {
2460        Self::try_from(input)
2461    }
2462}
2463
2464/// A named Base64 profile with an engine and optional strict line wrapping.
2465///
2466/// Profiles are convenience values for protocol-shaped Base64. They keep the
2467/// same strict alphabet, padding, canonical-bit, and output-buffer rules as
2468/// [`Engine`], while carrying the wrapping policy for MIME/PEM-like formats.
2469#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2470pub struct Profile<A, const PAD: bool> {
2471    engine: Engine<A, PAD>,
2472    wrap: Option<LineWrap>,
2473}
2474
2475impl<A, const PAD: bool> Profile<A, PAD>
2476where
2477    A: Alphabet,
2478{
2479    /// Creates a profile from an engine and optional strict line wrapping.
2480    #[must_use]
2481    pub const fn new(engine: Engine<A, PAD>, wrap: Option<LineWrap>) -> Self {
2482        Self { engine, wrap }
2483    }
2484
2485    /// Creates a profile, returning `None` when the wrapping policy is invalid.
2486    ///
2487    /// This is useful when a profile is assembled from configuration or other
2488    /// untrusted metadata. Use [`Self::new`] for compile-time constants where
2489    /// the wrapping policy is known to be valid.
2490    #[must_use]
2491    pub const fn checked_new(engine: Engine<A, PAD>, wrap: Option<LineWrap>) -> Option<Self> {
2492        match wrap {
2493            Some(wrap) if !wrap.is_valid() => None,
2494            _ => Some(Self::new(engine, wrap)),
2495        }
2496    }
2497
2498    /// Returns whether this profile can be used by encoders and decoders.
2499    #[must_use]
2500    pub const fn is_valid(&self) -> bool {
2501        match self.wrap {
2502            Some(wrap) => wrap.is_valid(),
2503            None => true,
2504        }
2505    }
2506
2507    /// Returns the underlying engine.
2508    #[must_use]
2509    pub const fn engine(&self) -> Engine<A, PAD> {
2510        self.engine
2511    }
2512
2513    /// Returns whether this profile uses padded Base64.
2514    #[must_use]
2515    pub const fn is_padded(&self) -> bool {
2516        PAD
2517    }
2518
2519    /// Returns whether this profile carries a strict line-wrapping policy.
2520    #[must_use]
2521    pub const fn is_wrapped(&self) -> bool {
2522        self.wrap.is_some()
2523    }
2524
2525    /// Returns the strict wrapping policy carried by this profile, if any.
2526    #[must_use]
2527    pub const fn line_wrap(&self) -> Option<LineWrap> {
2528        self.wrap
2529    }
2530
2531    /// Returns the encoded line length for wrapped profiles.
2532    #[must_use]
2533    pub const fn line_len(&self) -> Option<usize> {
2534        match self.wrap {
2535            Some(wrap) => Some(wrap.line_len()),
2536            None => None,
2537        }
2538    }
2539
2540    /// Returns the line ending for wrapped profiles.
2541    #[must_use]
2542    pub const fn line_ending(&self) -> Option<LineEnding> {
2543        match self.wrap {
2544            Some(wrap) => Some(wrap.line_ending()),
2545            None => None,
2546        }
2547    }
2548
2549    /// Returns the encoded length for this profile.
2550    pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
2551        match self.wrap {
2552            Some(wrap) => wrapped_encoded_len(input_len, PAD, wrap),
2553            None => encoded_len(input_len, PAD),
2554        }
2555    }
2556
2557    /// Returns the encoded length for this profile, or `None` on overflow or
2558    /// invalid line wrapping.
2559    #[must_use]
2560    pub const fn checked_encoded_len(&self, input_len: usize) -> Option<usize> {
2561        match self.wrap {
2562            Some(wrap) => checked_wrapped_encoded_len(input_len, PAD, wrap),
2563            None => checked_encoded_len(input_len, PAD),
2564        }
2565    }
2566
2567    /// Returns the exact decoded length for this profile.
2568    pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
2569        match self.wrap {
2570            Some(wrap) => self.engine.decoded_len_wrapped(input, wrap),
2571            None => self.engine.decoded_len(input),
2572        }
2573    }
2574
2575    /// Validates input according to this profile without writing decoded bytes.
2576    pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
2577        match self.wrap {
2578            Some(wrap) => self.engine.validate_wrapped_result(input, wrap),
2579            None => self.engine.validate_result(input),
2580        }
2581    }
2582
2583    /// Returns whether `input` is valid for this profile.
2584    #[must_use]
2585    pub fn validate(&self, input: &[u8]) -> bool {
2586        self.validate_result(input).is_ok()
2587    }
2588
2589    /// Encodes `input` into `output` according to this profile.
2590    pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
2591        match self.wrap {
2592            Some(wrap) => self.engine.encode_slice_wrapped(input, output, wrap),
2593            None => self.engine.encode_slice(input, output),
2594        }
2595    }
2596
2597    /// Encodes `input` into `output` and clears all bytes after the encoded
2598    /// prefix.
2599    pub fn encode_slice_clear_tail(
2600        &self,
2601        input: &[u8],
2602        output: &mut [u8],
2603    ) -> Result<usize, EncodeError> {
2604        match self.wrap {
2605            Some(wrap) => self
2606                .engine
2607                .encode_slice_wrapped_clear_tail(input, output, wrap),
2608            None => self.engine.encode_slice_clear_tail(input, output),
2609        }
2610    }
2611
2612    /// Encodes `input` into a stack-backed buffer.
2613    ///
2614    /// This is useful for short values where heap allocation is unnecessary.
2615    /// If encoding fails, the internal backing array is cleared before the
2616    /// error is returned.
2617    pub fn encode_buffer<const CAP: usize>(
2618        &self,
2619        input: &[u8],
2620    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
2621        let mut output = EncodedBuffer::new();
2622        let written = match self.encode_slice_clear_tail(input, &mut output.bytes) {
2623            Ok(written) => written,
2624            Err(err) => {
2625                output.clear();
2626                return Err(err);
2627            }
2628        };
2629        output.len = written;
2630        Ok(output)
2631    }
2632
2633    /// Decodes `input` into `output` according to this profile.
2634    ///
2635    /// # Security
2636    ///
2637    /// Profile decoders use the normal strict decode path. They may branch or
2638    /// return early based on malformed input, padding position, wrapping, and
2639    /// output capacity in order to return precise [`DecodeError`] diagnostics,
2640    /// including exact invalid-byte values and positions.
2641    /// Do not use this method for token comparison, key-material decoding, or
2642    /// secret-bearing validation where malformed-input timing matters. Use
2643    /// [`crate::ct`] with a matching unwrapped engine for constant-time-oriented
2644    /// secret decoding.
2645    #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
2646    pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
2647        match self.wrap {
2648            Some(wrap) => self.engine.decode_slice_wrapped(input, output, wrap),
2649            None => self.engine.decode_slice(input, output),
2650        }
2651    }
2652
2653    /// Decodes `input` into `output` and clears all bytes after the decoded
2654    /// prefix.
2655    pub fn decode_slice_clear_tail(
2656        &self,
2657        input: &[u8],
2658        output: &mut [u8],
2659    ) -> Result<usize, DecodeError> {
2660        match self.wrap {
2661            Some(wrap) => self
2662                .engine
2663                .decode_slice_wrapped_clear_tail(input, output, wrap),
2664            None => self.engine.decode_slice_clear_tail(input, output),
2665        }
2666    }
2667
2668    /// Decodes `input` into a stack-backed buffer according to this profile.
2669    ///
2670    /// This is useful for short decoded values where heap allocation is
2671    /// unnecessary. If decoding fails, the internal backing array is cleared
2672    /// before the error is returned.
2673    pub fn decode_buffer<const CAP: usize>(
2674        &self,
2675        input: &[u8],
2676    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
2677        let mut output = DecodedBuffer::new();
2678        let written = match self.decode_slice_clear_tail(input, &mut output.bytes) {
2679            Ok(written) => written,
2680            Err(err) => {
2681                output.clear();
2682                return Err(err);
2683            }
2684        };
2685        output.len = written;
2686        Ok(output)
2687    }
2688
2689    /// Decodes `buffer` in place according to this profile.
2690    ///
2691    /// For wrapped profiles, configured line endings are compacted out before
2692    /// decoding. If validation fails, the buffer contents are unspecified.
2693    /// On success, bytes after the returned decoded prefix may retain compacted
2694    /// encoded input. Use [`Self::decode_in_place_clear_tail`] when the buffer
2695    /// may be reused or freed without a caller-managed wipe.
2696    ///
2697    /// # Security
2698    ///
2699    /// Profile in-place decoders use the normal strict decode path. They may
2700    /// branch or return early based on malformed input, padding position,
2701    /// wrapping, and output capacity in order to return precise
2702    /// [`DecodeError`] diagnostics. Do not use this method for token
2703    /// comparison, key-material decoding, or secret-bearing validation where
2704    /// malformed-input timing matters.
2705    ///
2706    /// # Examples
2707    ///
2708    /// ```
2709    /// use base64_ng::{LineEnding, LineWrap, Profile, STANDARD};
2710    ///
2711    /// let profile = Profile::new(STANDARD, Some(LineWrap::new(4, LineEnding::Lf)));
2712    /// let mut buffer = *b"aGVs\nbG8=";
2713    /// let decoded = profile.decode_in_place(&mut buffer).unwrap();
2714    ///
2715    /// assert_eq!(decoded, b"hello");
2716    /// ```
2717    pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
2718        match self.wrap {
2719            Some(wrap) => self.engine.decode_in_place_wrapped(buffer, wrap),
2720            None => self.engine.decode_in_place(buffer),
2721        }
2722    }
2723
2724    /// Decodes `buffer` in place according to this profile and clears all
2725    /// bytes after the decoded prefix.
2726    ///
2727    /// If validation or decoding fails, the entire buffer is cleared before the
2728    /// error is returned.
2729    ///
2730    /// # Examples
2731    ///
2732    /// ```
2733    /// use base64_ng::{LineEnding, LineWrap, Profile, STANDARD};
2734    ///
2735    /// let profile = Profile::new(STANDARD, Some(LineWrap::new(4, LineEnding::Lf)));
2736    /// let mut buffer = *b"aGVs\nbG8=";
2737    /// let len = profile.decode_in_place_clear_tail(&mut buffer).unwrap().len();
2738    ///
2739    /// assert_eq!(&buffer[..len], b"hello");
2740    /// assert!(buffer[len..].iter().all(|byte| *byte == 0));
2741    /// ```
2742    pub fn decode_in_place_clear_tail<'a>(
2743        &self,
2744        buffer: &'a mut [u8],
2745    ) -> Result<&'a mut [u8], DecodeError> {
2746        match self.wrap {
2747            Some(wrap) => self.engine.decode_in_place_wrapped_clear_tail(buffer, wrap),
2748            None => self.engine.decode_in_place_clear_tail(buffer),
2749        }
2750    }
2751
2752    /// Encodes `input` into a newly allocated byte vector.
2753    #[cfg(feature = "alloc")]
2754    #[must_use = "for secret-bearing payloads use encode_secret, which returns a redacted buffer with drop-time cleanup"]
2755    pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
2756        match self.wrap {
2757            Some(wrap) => self.engine.encode_wrapped_vec(input, wrap),
2758            None => self.engine.encode_vec(input),
2759        }
2760    }
2761
2762    /// Encodes `input` into a redacted owned secret buffer.
2763    #[cfg(feature = "alloc")]
2764    pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
2765        self.encode_vec(input).map(SecretBuffer::from_vec)
2766    }
2767
2768    /// Encodes `input` into a newly allocated UTF-8 string.
2769    #[cfg(feature = "alloc")]
2770    pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
2771        match self.wrap {
2772            Some(wrap) => self.engine.encode_wrapped_string(input, wrap),
2773            None => self.engine.encode_string(input),
2774        }
2775    }
2776
2777    /// Decodes `input` into a newly allocated byte vector.
2778    #[cfg(feature = "alloc")]
2779    #[must_use = "for secret-bearing payloads use decode_secret, which returns a redacted buffer with drop-time cleanup"]
2780    pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
2781        match self.wrap {
2782            Some(wrap) => self.engine.decode_wrapped_vec(input, wrap),
2783            None => self.engine.decode_vec(input),
2784        }
2785    }
2786
2787    /// Decodes `input` into a redacted owned secret buffer.
2788    #[cfg(feature = "alloc")]
2789    pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
2790        self.decode_vec(input).map(SecretBuffer::from_vec)
2791    }
2792}
2793
2794impl<A, const PAD: bool> Default for Profile<A, PAD>
2795where
2796    A: Alphabet,
2797{
2798    fn default() -> Self {
2799        Self::new(Engine::new(), None)
2800    }
2801}
2802
2803impl<A, const PAD: bool> core::fmt::Display for Profile<A, PAD>
2804where
2805    A: Alphabet,
2806{
2807    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2808        match self.wrap {
2809            Some(wrap) => write!(formatter, "padded={PAD} wrap={wrap}"),
2810            None => write!(formatter, "padded={PAD} wrap=none"),
2811        }
2812    }
2813}
2814
2815impl<A, const PAD: bool> From<Engine<A, PAD>> for Profile<A, PAD>
2816where
2817    A: Alphabet,
2818{
2819    fn from(engine: Engine<A, PAD>) -> Self {
2820        Self::new(engine, None)
2821    }
2822}
2823
2824/// MIME Base64 profile: standard alphabet, padding, 76-column CRLF wrapping.
2825///
2826/// This profile uses the default strict decoder and is not a constant-time
2827/// token validator or key-material decoder. Use [`ct::STANDARD`] with an
2828/// application-level wrapping policy for sensitive fixed-shape protocols.
2829#[doc(alias = "ct")]
2830#[doc(alias = "constant_time")]
2831#[doc(alias = "sensitive")]
2832pub const MIME: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::MIME));
2833
2834/// PEM Base64 profile: standard alphabet, padding, 64-column LF wrapping.
2835///
2836/// This profile uses the default strict decoder and is not a constant-time
2837/// token validator or key-material decoder. Use [`ct::STANDARD`] with an
2838/// application-level wrapping policy for sensitive fixed-shape protocols.
2839#[doc(alias = "ct")]
2840#[doc(alias = "constant_time")]
2841#[doc(alias = "sensitive")]
2842pub const PEM: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::PEM));
2843
2844/// PEM Base64 profile with CRLF line endings.
2845///
2846/// This profile uses the default strict decoder and is not a constant-time
2847/// token validator or key-material decoder. Use [`ct::STANDARD`] with an
2848/// application-level wrapping policy for sensitive fixed-shape protocols.
2849#[doc(alias = "ct")]
2850#[doc(alias = "constant_time")]
2851#[doc(alias = "sensitive")]
2852pub const PEM_CRLF: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::PEM_CRLF));
2853
2854/// bcrypt-style no-padding Base64 profile.
2855///
2856/// This profile carries the bcrypt alphabet and no padding. It does not parse
2857/// complete bcrypt password-hash strings. Its default strict decoder is not a
2858/// constant-time token validator or key-material decoder; use
2859/// [`Profile::engine`] with [`Engine::ct_decoder`] for the matching
2860/// constant-time-oriented decoder when timing posture matters.
2861#[doc(alias = "ct")]
2862#[doc(alias = "constant_time")]
2863#[doc(alias = "sensitive")]
2864pub const BCRYPT: Profile<Bcrypt, false> = Profile::new(BCRYPT_NO_PAD, None);
2865
2866/// Unix `crypt(3)`-style no-padding Base64 profile.
2867///
2868/// This profile carries the `crypt(3)` alphabet and no padding. It does not
2869/// parse complete password-hash strings. Its default strict decoder is not a
2870/// constant-time token validator or key-material decoder; use
2871/// [`Profile::engine`] with [`Engine::ct_decoder`] for the matching
2872/// constant-time-oriented decoder when timing posture matters.
2873#[doc(alias = "ct")]
2874#[doc(alias = "constant_time")]
2875#[doc(alias = "sensitive")]
2876pub const CRYPT: Profile<Crypt, false> = Profile::new(CRYPT_NO_PAD, None);
2877
2878/// Returns the encoded length for an input length and padding policy.
2879///
2880/// This function returns [`EncodeError::LengthOverflow`] instead of panicking.
2881/// Use [`checked_encoded_len`] when an `Option<usize>` is more convenient.
2882///
2883/// # Examples
2884///
2885/// ```
2886/// use base64_ng::encoded_len;
2887///
2888/// assert_eq!(encoded_len(5, true).unwrap(), 8);
2889/// assert_eq!(encoded_len(5, false).unwrap(), 7);
2890/// assert!(encoded_len(usize::MAX, true).is_err());
2891/// ```
2892pub const fn encoded_len(input_len: usize, padded: bool) -> Result<usize, EncodeError> {
2893    match checked_encoded_len(input_len, padded) {
2894        Some(len) => Ok(len),
2895        None => Err(EncodeError::LengthOverflow),
2896    }
2897}
2898
2899/// Returns the encoded length after applying a line wrapping policy.
2900///
2901/// The returned length includes inserted line endings but does not include a
2902/// trailing line ending after the final encoded line.
2903///
2904/// # Examples
2905///
2906/// ```
2907/// use base64_ng::{LineEnding, LineWrap, wrapped_encoded_len};
2908///
2909/// let wrap = LineWrap::new(4, LineEnding::Lf);
2910/// assert_eq!(wrapped_encoded_len(5, true, wrap).unwrap(), 9);
2911/// ```
2912pub const fn wrapped_encoded_len(
2913    input_len: usize,
2914    padded: bool,
2915    wrap: LineWrap,
2916) -> Result<usize, EncodeError> {
2917    if wrap.line_len == 0 {
2918        return Err(EncodeError::InvalidLineWrap { line_len: 0 });
2919    }
2920
2921    let Some(encoded) = checked_encoded_len(input_len, padded) else {
2922        return Err(EncodeError::LengthOverflow);
2923    };
2924    if encoded == 0 {
2925        return Ok(0);
2926    }
2927
2928    let breaks = (encoded - 1) / wrap.line_len;
2929    let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
2930        return Err(EncodeError::LengthOverflow);
2931    };
2932    match encoded.checked_add(line_ending_bytes) {
2933        Some(len) => Ok(len),
2934        None => Err(EncodeError::LengthOverflow),
2935    }
2936}
2937
2938/// Returns the encoded length after line wrapping, or `None` on overflow or
2939/// invalid line wrapping.
2940///
2941/// The returned length includes inserted line endings but does not include a
2942/// trailing line ending after the final encoded line.
2943///
2944/// # Examples
2945///
2946/// ```
2947/// use base64_ng::{LineEnding, LineWrap, checked_wrapped_encoded_len};
2948///
2949/// let wrap = LineWrap::new(4, LineEnding::Lf);
2950/// assert_eq!(checked_wrapped_encoded_len(5, true, wrap), Some(9));
2951/// assert_eq!(LineWrap::checked_new(0, LineEnding::Lf), None);
2952/// ```
2953#[must_use]
2954pub const fn checked_wrapped_encoded_len(
2955    input_len: usize,
2956    padded: bool,
2957    wrap: LineWrap,
2958) -> Option<usize> {
2959    if wrap.line_len == 0 {
2960        return None;
2961    }
2962
2963    let Some(encoded) = checked_encoded_len(input_len, padded) else {
2964        return None;
2965    };
2966    if encoded == 0 {
2967        return Some(0);
2968    }
2969
2970    let breaks = (encoded - 1) / wrap.line_len;
2971    let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
2972        return None;
2973    };
2974    encoded.checked_add(line_ending_bytes)
2975}
2976
2977/// Returns the encoded length, or `None` if it would overflow `usize`.
2978///
2979/// # Examples
2980///
2981/// ```
2982/// use base64_ng::checked_encoded_len;
2983///
2984/// assert_eq!(checked_encoded_len(5, true), Some(8));
2985/// assert_eq!(checked_encoded_len(usize::MAX, true), None);
2986/// ```
2987#[must_use]
2988pub const fn checked_encoded_len(input_len: usize, padded: bool) -> Option<usize> {
2989    let groups = input_len / 3;
2990    if groups > usize::MAX / 4 {
2991        return None;
2992    }
2993    let full = groups * 4;
2994    let rem = input_len % 3;
2995    if rem == 0 {
2996        Some(full)
2997    } else if padded {
2998        full.checked_add(4)
2999    } else {
3000        full.checked_add(rem + 1)
3001    }
3002}
3003
3004/// Compares two fixed-width byte arrays without a length-mismatch branch.
3005///
3006/// Use this helper when the value length itself should not be represented as a
3007/// timing-distinct branch in the comparison API. The array length `N` is a
3008/// compile-time public type fact, and the helper scans exactly `N` bytes before
3009/// returning. The final equality result remains public. This is still a
3010/// dependency-free, constant-time-oriented best-effort helper, not a formally
3011/// verified cryptographic comparison primitive.
3012///
3013/// # Examples
3014///
3015/// ```
3016/// use base64_ng::constant_time_eq_fixed_width;
3017///
3018/// assert!(constant_time_eq_fixed_width(b"token", b"token"));
3019/// assert!(!constant_time_eq_fixed_width(b"token", b"Token"));
3020/// ```
3021#[must_use]
3022pub fn constant_time_eq_fixed_width<const N: usize>(left: &[u8; N], right: &[u8; N]) -> bool {
3023    constant_time_eq_fixed_width_array(left, right)
3024}
3025
3026/// Returns the maximum decoded length for an encoded input length.
3027///
3028/// # Examples
3029///
3030/// ```
3031/// use base64_ng::decoded_capacity;
3032///
3033/// assert_eq!(decoded_capacity(8), 6);
3034/// assert_eq!(decoded_capacity(7), 5);
3035/// ```
3036#[must_use]
3037pub const fn decoded_capacity(encoded_len: usize) -> usize {
3038    let rem = encoded_len % 4;
3039    encoded_len / 4 * 3
3040        + if rem == 2 {
3041            1
3042        } else if rem == 3 {
3043            2
3044        } else {
3045            0
3046        }
3047}
3048
3049/// Returns the exact decoded length implied by input length and padding.
3050///
3051/// This validates padding placement and impossible lengths, but it does not
3052/// validate alphabet membership or non-canonical trailing bits.
3053///
3054/// # Examples
3055///
3056/// ```
3057/// use base64_ng::decoded_len;
3058///
3059/// assert_eq!(decoded_len(b"aGVsbG8=", true).unwrap(), 5);
3060/// assert_eq!(decoded_len(b"aGVsbG8", false).unwrap(), 5);
3061/// ```
3062pub fn decoded_len(input: &[u8], padded: bool) -> Result<usize, DecodeError> {
3063    if padded {
3064        decoded_len_padded(input)
3065    } else {
3066        decoded_len_unpadded(input)
3067    }
3068}
3069
3070/// Defines a custom [`Alphabet`] from a 64-byte string literal.
3071///
3072/// The generated alphabet is validated at compile time with
3073/// [`validate_alphabet`]. Invalid, duplicate, or padding bytes fail the build
3074/// instead of creating a malformed runtime profile.
3075///
3076/// The generated implementation uses the conservative default
3077/// [`Alphabet::encode`] behavior: every emitted Base64 byte performs a fixed
3078/// 64-entry scan to avoid secret-indexed table lookups. Built-in alphabets use
3079/// optimized arithmetic mappers.
3080///
3081/// The generated [`Alphabet::decode`] implementation delegates to
3082/// [`decode_alphabet_byte`]. The constant-time-oriented [`ct`] module scans the
3083/// generated `ENCODE` table directly and does not call the generated `decode`
3084/// method.
3085///
3086/// # Examples
3087///
3088/// ```
3089/// base64_ng::define_alphabet! {
3090///     struct DotSlash = b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
3091/// }
3092///
3093/// let engine = base64_ng::Engine::<DotSlash, false>::new();
3094/// let mut encoded = [0u8; 4];
3095/// let written = engine.encode_slice(&[0xff, 0xff, 0xff], &mut encoded).unwrap();
3096/// assert_eq!(&encoded[..written], b"9999");
3097/// ```
3098///
3099/// Invalid alphabets fail during compilation:
3100///
3101/// ```compile_fail
3102/// base64_ng::define_alphabet! {
3103///     struct Bad = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
3104/// }
3105/// ```
3106#[macro_export]
3107macro_rules! define_alphabet {
3108    ($(#[$meta:meta])* $vis:vis struct $name:ident = $encode:expr;) => {
3109        $(#[$meta])*
3110        #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
3111        $vis struct $name;
3112
3113        impl $crate::Alphabet for $name {
3114            const ENCODE: [u8; 64] = *$encode;
3115
3116            #[inline]
3117            fn decode(byte: u8) -> Option<u8> {
3118                $crate::decode_alphabet_byte(byte, &Self::ENCODE)
3119            }
3120        }
3121
3122        const _: [(); 1] = [(); match $crate::validate_alphabet(
3123            &<$name as $crate::Alphabet>::ENCODE,
3124        ) {
3125            Ok(()) => 1,
3126            Err(_) => 0,
3127        }];
3128    };
3129}
3130
3131/// Validates a 64-byte Base64 alphabet table.
3132///
3133/// A valid alphabet must contain exactly 64 unique visible ASCII bytes and must
3134/// not contain the padding byte `=`.
3135///
3136/// # Examples
3137///
3138/// ```
3139/// use base64_ng::{Alphabet, Standard, validate_alphabet};
3140///
3141/// validate_alphabet(&Standard::ENCODE).unwrap();
3142/// ```
3143pub const fn validate_alphabet(encode: &[u8; 64]) -> Result<(), AlphabetError> {
3144    let mut index = 0;
3145    while index < encode.len() {
3146        let byte = encode[index];
3147        if !is_visible_ascii(byte) {
3148            return Err(AlphabetError::InvalidByte { index, byte });
3149        }
3150        if byte == b'=' {
3151            return Err(AlphabetError::PaddingByte { index });
3152        }
3153
3154        let mut duplicate = index + 1;
3155        while duplicate < encode.len() {
3156            if encode[duplicate] == byte {
3157                return Err(AlphabetError::DuplicateByte {
3158                    first: index,
3159                    second: duplicate,
3160                    byte,
3161                });
3162            }
3163            duplicate += 1;
3164        }
3165
3166        index += 1;
3167    }
3168
3169    Ok(())
3170}
3171
3172/// Decodes one byte by scanning a caller-provided alphabet table.
3173///
3174/// This helper is intended for custom [`Alphabet`] implementations. Validate
3175/// the table with [`validate_alphabet`] before trusting the alphabet in a
3176/// protocol or public API. The scan always visits all 64 entries before
3177/// returning so the match position does not create an early-return timing
3178/// signal in custom alphabet decoders.
3179///
3180/// # Examples
3181///
3182/// ```
3183/// use base64_ng::{Alphabet, decode_alphabet_byte};
3184///
3185/// struct DotSlash;
3186///
3187/// impl Alphabet for DotSlash {
3188///     const ENCODE: [u8; 64] =
3189///         *b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
3190///
3191///     fn decode(byte: u8) -> Option<u8> {
3192///         decode_alphabet_byte(byte, &Self::ENCODE)
3193///     }
3194/// }
3195///
3196/// assert_eq!(DotSlash::decode(b'.'), Some(0));
3197/// assert_eq!(DotSlash::decode(b'9'), Some(63));
3198/// ```
3199#[must_use]
3200pub const fn decode_alphabet_byte(byte: u8, encode: &[u8; 64]) -> Option<u8> {
3201    let mut index = 0;
3202    let mut candidate = 0;
3203    let mut decoded = 0;
3204    let mut valid = 0;
3205    while index < encode.len() {
3206        let matches = ct_mask_eq_u8(byte, encode[index]);
3207        decoded |= candidate & matches;
3208        valid |= matches;
3209        index += 1;
3210        candidate += 1;
3211    }
3212
3213    if valid == 0 { None } else { Some(decoded) }
3214}
3215
3216/// A Base64 alphabet.
3217///
3218/// # Security
3219///
3220/// The default [`Alphabet::encode`] implementation is constant-time-oriented:
3221/// it scans all 64 alphabet entries instead of using `ENCODE[value as usize]`.
3222/// If an implementation overrides `encode` with a direct table lookup, normal
3223/// [`Engine`] encoding becomes timing-sensitive with respect to the emitted
3224/// 6-bit value.
3225///
3226/// The normal strict decode path calls [`Alphabet::decode`] and is not a
3227/// constant-time decoder. The [`ct`] module does not call
3228/// [`Alphabet::decode`]; it scans [`Alphabet::ENCODE`] directly with its own
3229/// fixed 64-entry mapper. A custom non-constant-time `decode` implementation
3230/// therefore affects normal strict decode diagnostics and timing, but not the
3231/// `ct` module's symbol-mapping loop.
3232pub trait Alphabet {
3233    /// Encoding table indexed by 6-bit values.
3234    const ENCODE: [u8; 64];
3235
3236    /// Encode one 6-bit value into an alphabet byte.
3237    ///
3238    /// The default implementation scans the alphabet table instead of using a
3239    /// secret-indexed table lookup. Built-in alphabets override this with the
3240    /// branch-minimized ASCII arithmetic mapper. Custom alphabets that keep the
3241    /// default method prioritize timing posture over throughput: every emitted
3242    /// Base64 byte performs a fixed 64-entry scan. For massive payloads with
3243    /// user-defined alphabets, profile this cost and consider an audited custom
3244    /// override only if the alphabet has a structure that can be mapped without
3245    /// secret-indexed table access.
3246    #[must_use]
3247    fn encode(value: u8) -> u8 {
3248        encode_alphabet_value(value, &Self::ENCODE)
3249    }
3250
3251    /// Decode one byte into a 6-bit value.
3252    ///
3253    /// Implementations that want conservative custom-alphabet timing posture
3254    /// should delegate to [`decode_alphabet_byte`], which scans all 64 entries
3255    /// before returning. The `ct` module ignores this method and scans
3256    /// [`Self::ENCODE`] directly.
3257    fn decode(byte: u8) -> Option<u8>;
3258}
3259
3260const fn is_visible_ascii(byte: u8) -> bool {
3261    byte >= 0x21 && byte <= 0x7e
3262}
3263
3264/// The RFC 4648 standard Base64 alphabet.
3265#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
3266pub struct Standard;
3267
3268impl Alphabet for Standard {
3269    const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3270
3271    #[inline]
3272    fn encode(value: u8) -> u8 {
3273        encode_ascii_base64(value, Self::ENCODE[62], Self::ENCODE[63])
3274    }
3275
3276    #[inline]
3277    fn decode(byte: u8) -> Option<u8> {
3278        decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
3279    }
3280}
3281
3282/// The RFC 4648 URL-safe Base64 alphabet.
3283#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
3284pub struct UrlSafe;
3285
3286impl Alphabet for UrlSafe {
3287    const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
3288
3289    #[inline]
3290    fn encode(value: u8) -> u8 {
3291        encode_ascii_base64(value, Self::ENCODE[62], Self::ENCODE[63])
3292    }
3293
3294    #[inline]
3295    fn decode(byte: u8) -> Option<u8> {
3296        decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
3297    }
3298}
3299
3300/// The bcrypt Base64 alphabet.
3301///
3302/// This alphabet is commonly used by bcrypt hash strings. It is provided as an
3303/// alphabet/profile building block; `base64-ng` does not parse or verify full
3304/// bcrypt password-hash records.
3305#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
3306pub struct Bcrypt;
3307
3308impl Alphabet for Bcrypt {
3309    const ENCODE: [u8; 64] = *b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
3310
3311    #[inline]
3312    fn decode(byte: u8) -> Option<u8> {
3313        decode_alphabet_byte(byte, &Self::ENCODE)
3314    }
3315}
3316
3317/// The Unix `crypt(3)` Base64 alphabet.
3318///
3319/// This alphabet is provided as an explicit legacy interoperability profile.
3320/// `base64-ng` does not parse or verify complete password-hash records.
3321#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
3322pub struct Crypt;
3323
3324impl Alphabet for Crypt {
3325    const ENCODE: [u8; 64] = *b"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
3326
3327    #[inline]
3328    fn decode(byte: u8) -> Option<u8> {
3329        decode_alphabet_byte(byte, &Self::ENCODE)
3330    }
3331}
3332
3333#[inline]
3334const fn encode_base64_value<A: Alphabet>(value: u8) -> u8 {
3335    encode_alphabet_value(value, &A::ENCODE)
3336}
3337
3338#[inline]
3339fn encode_base64_value_runtime<A: Alphabet>(value: u8) -> u8 {
3340    A::encode(value)
3341}
3342
3343#[inline]
3344const fn encode_alphabet_value(value: u8, encode: &[u8; 64]) -> u8 {
3345    let mut output = 0;
3346    let mut index = 0;
3347    let mut candidate = 0;
3348    while index < encode.len() {
3349        output |= encode[index] & ct_mask_eq_u8(value, candidate);
3350        index += 1;
3351        candidate += 1;
3352    }
3353    output
3354}
3355
3356#[inline]
3357const fn encode_ascii_base64(value: u8, value_62_byte: u8, value_63_byte: u8) -> u8 {
3358    let upper = ct_mask_lt_u8(value, 26);
3359    let lower = ct_mask_lt_u8(value.wrapping_sub(26), 26);
3360    let digit = ct_mask_lt_u8(value.wrapping_sub(52), 10);
3361    let value_62 = ct_mask_eq_u8(value, 0x3e);
3362    let value_63 = ct_mask_eq_u8(value, 0x3f);
3363
3364    (value.wrapping_add(b'A') & upper)
3365        | (value.wrapping_sub(26).wrapping_add(b'a') & lower)
3366        | (value.wrapping_sub(52).wrapping_add(b'0') & digit)
3367        | (value_62_byte & value_62)
3368        | (value_63_byte & value_63)
3369}
3370
3371#[inline]
3372fn decode_ascii_base64(byte: u8, value_62_byte: u8, value_63_byte: u8) -> Option<u8> {
3373    let upper = ct_mask_lt_u8(byte.wrapping_sub(b'A'), 26);
3374    let lower = ct_mask_lt_u8(byte.wrapping_sub(b'a'), 26);
3375    let digit = ct_mask_lt_u8(byte.wrapping_sub(b'0'), 10);
3376    let value_62 = ct_mask_eq_u8(byte, value_62_byte);
3377    let value_63 = ct_mask_eq_u8(byte, value_63_byte);
3378    let valid = upper | lower | digit | value_62 | value_63;
3379
3380    let decoded = (byte.wrapping_sub(b'A') & upper)
3381        | (byte.wrapping_sub(b'a').wrapping_add(26) & lower)
3382        | (byte.wrapping_sub(b'0').wrapping_add(52) & digit)
3383        | (0x3e & value_62)
3384        | (0x3f & value_63);
3385
3386    if valid == 0 { None } else { Some(decoded) }
3387}
3388
3389#[inline]
3390const fn ct_mask_bit(bit: u8) -> u8 {
3391    0u8.wrapping_sub(bit & 1)
3392}
3393
3394#[inline]
3395const fn ct_mask_nonzero_u8(value: u8) -> u8 {
3396    let wide = value as u16;
3397    let negative = 0u16.wrapping_sub(wide);
3398    let nonzero = ((wide | negative) >> 8) as u8;
3399    ct_mask_bit(nonzero)
3400}
3401
3402#[inline]
3403const fn ct_mask_eq_u8(left: u8, right: u8) -> u8 {
3404    !ct_mask_nonzero_u8(left ^ right)
3405}
3406
3407#[inline]
3408const fn ct_mask_lt_u8(left: u8, right: u8) -> u8 {
3409    let diff = (left as u16).wrapping_sub(right as u16);
3410    ct_mask_bit((diff >> 8) as u8)
3411}
3412
3413#[inline(never)]
3414fn constant_time_eq_public_len(left: &[u8], right: &[u8]) -> bool {
3415    if left.len() != right.len() {
3416        return false;
3417    }
3418
3419    constant_time_eq_same_len(left, right)
3420}
3421
3422#[inline(never)]
3423fn constant_time_eq_fixed_width_array<const N: usize>(left: &[u8; N], right: &[u8; N]) -> bool {
3424    constant_time_eq_same_len(left, right)
3425}
3426
3427#[inline(never)]
3428#[allow(unsafe_code)]
3429fn constant_time_eq_same_len(left: &[u8], right: &[u8]) -> bool {
3430    let mut diff = 0u8;
3431    for (left, right) in left.iter().zip(right) {
3432        diff = core::hint::black_box(
3433            core::hint::black_box(diff) | core::hint::black_box(*left ^ *right),
3434        );
3435        // SAFETY: `diff` is an initialized local `u8`; the volatile read is a
3436        // dependency-free optimizer barrier for the accumulation value and does
3437        // not access caller memory.
3438        diff = unsafe { core::ptr::read_volatile(&raw const diff) };
3439    }
3440    ct_error_gate_barrier(diff, 0);
3441    // SAFETY: `diff` is an initialized local `u8`; this final volatile read
3442    // keeps the public equality comparison dependent on a post-barrier load of
3443    // the accumulated value.
3444    let result = unsafe { core::ptr::read_volatile(&raw const diff) };
3445    result == 0
3446}
3447
3448#[cfg(feature = "alloc")]
3449#[allow(unsafe_code)]
3450fn string_from_validated_secret_bytes(bytes: alloc::vec::Vec<u8>) -> alloc::string::String {
3451    debug_assert!(
3452        core::str::from_utf8(&bytes).is_ok(),
3453        "string_from_validated_secret_bytes called with invalid UTF-8",
3454    );
3455    // SAFETY: Callers validate the same byte vector as UTF-8 immediately before
3456    // handing ownership to this helper, and the bytes are not modified between
3457    // validation and conversion. Using the unchecked conversion avoids a
3458    // second fallible conversion while the bytes are outside `SecretBuffer`.
3459    unsafe { alloc::string::String::from_utf8_unchecked(bytes) }
3460}
3461
3462mod backend {
3463    use super::{
3464        Alphabet, DecodeError, EncodeError, checked_encoded_len, decode_padded, decode_unpadded,
3465        encode_base64_value_runtime,
3466    };
3467
3468    pub(super) fn encode_slice<A, const PAD: bool>(
3469        input: &[u8],
3470        output: &mut [u8],
3471    ) -> Result<usize, EncodeError>
3472    where
3473        A: Alphabet,
3474    {
3475        #[cfg(feature = "simd")]
3476        match super::simd::active_backend() {
3477            super::simd::ActiveBackend::Scalar => {}
3478        }
3479
3480        scalar_encode_slice::<A, PAD>(input, output)
3481    }
3482
3483    pub(super) fn decode_slice<A, const PAD: bool>(
3484        input: &[u8],
3485        output: &mut [u8],
3486    ) -> Result<usize, DecodeError>
3487    where
3488        A: Alphabet,
3489    {
3490        #[cfg(feature = "simd")]
3491        match super::simd::active_backend() {
3492            super::simd::ActiveBackend::Scalar => {}
3493        }
3494
3495        scalar_decode_slice::<A, PAD>(input, output)
3496    }
3497
3498    #[cfg(test)]
3499    pub(super) fn scalar_reference_encode_slice<A, const PAD: bool>(
3500        input: &[u8],
3501        output: &mut [u8],
3502    ) -> Result<usize, EncodeError>
3503    where
3504        A: Alphabet,
3505    {
3506        scalar_encode_slice::<A, PAD>(input, output)
3507    }
3508
3509    #[cfg(test)]
3510    pub(super) fn scalar_reference_decode_slice<A, const PAD: bool>(
3511        input: &[u8],
3512        output: &mut [u8],
3513    ) -> Result<usize, DecodeError>
3514    where
3515        A: Alphabet,
3516    {
3517        scalar_decode_slice::<A, PAD>(input, output)
3518    }
3519
3520    fn scalar_encode_slice<A, const PAD: bool>(
3521        input: &[u8],
3522        output: &mut [u8],
3523    ) -> Result<usize, EncodeError>
3524    where
3525        A: Alphabet,
3526    {
3527        let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
3528        if output.len() < required {
3529            return Err(EncodeError::OutputTooSmall {
3530                required,
3531                available: output.len(),
3532            });
3533        }
3534
3535        let mut read = 0;
3536        let mut write = 0;
3537        while read + 3 <= input.len() {
3538            let b0 = input[read];
3539            let b1 = input[read + 1];
3540            let b2 = input[read + 2];
3541
3542            output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
3543            output[write + 1] =
3544                encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
3545            output[write + 2] =
3546                encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
3547            output[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
3548
3549            read += 3;
3550            write += 4;
3551        }
3552
3553        match input.len() - read {
3554            0 => {}
3555            1 => {
3556                let b0 = input[read];
3557                output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
3558                output[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
3559                write += 2;
3560                if PAD {
3561                    output[write] = b'=';
3562                    output[write + 1] = b'=';
3563                    write += 2;
3564                }
3565            }
3566            2 => {
3567                let b0 = input[read];
3568                let b1 = input[read + 1];
3569                output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
3570                output[write + 1] =
3571                    encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
3572                output[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
3573                write += 3;
3574                if PAD {
3575                    output[write] = b'=';
3576                    write += 1;
3577                }
3578            }
3579            _ => unreachable!(),
3580        }
3581
3582        Ok(write)
3583    }
3584
3585    fn scalar_decode_slice<A, const PAD: bool>(
3586        input: &[u8],
3587        output: &mut [u8],
3588    ) -> Result<usize, DecodeError>
3589    where
3590        A: Alphabet,
3591    {
3592        if input.is_empty() {
3593            return Ok(0);
3594        }
3595
3596        if PAD {
3597            decode_padded::<A>(input, output)
3598        } else {
3599            decode_unpadded::<A>(input, output)
3600        }
3601    }
3602}
3603
3604/// A zero-sized Base64 engine parameterized by alphabet and padding policy.
3605pub struct Engine<A, const PAD: bool> {
3606    alphabet: core::marker::PhantomData<A>,
3607}
3608
3609impl<A, const PAD: bool> Clone for Engine<A, PAD> {
3610    fn clone(&self) -> Self {
3611        *self
3612    }
3613}
3614
3615impl<A, const PAD: bool> Copy for Engine<A, PAD> {}
3616
3617impl<A, const PAD: bool> core::fmt::Debug for Engine<A, PAD> {
3618    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3619        formatter
3620            .debug_struct("Engine")
3621            .field("padded", &PAD)
3622            .finish()
3623    }
3624}
3625
3626impl<A, const PAD: bool> core::fmt::Display for Engine<A, PAD> {
3627    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3628        write!(formatter, "padded={PAD}")
3629    }
3630}
3631
3632impl<A, const PAD: bool> Default for Engine<A, PAD> {
3633    fn default() -> Self {
3634        Self {
3635            alphabet: core::marker::PhantomData,
3636        }
3637    }
3638}
3639
3640impl<A, const PAD: bool> Eq for Engine<A, PAD> {}
3641
3642impl<A, const PAD: bool> PartialEq for Engine<A, PAD> {
3643    fn eq(&self, _other: &Self) -> bool {
3644        true
3645    }
3646}
3647
3648impl<A, const PAD: bool> Engine<A, PAD>
3649where
3650    A: Alphabet,
3651{
3652    /// Creates a new engine value.
3653    #[must_use]
3654    pub const fn new() -> Self {
3655        Self {
3656            alphabet: core::marker::PhantomData,
3657        }
3658    }
3659
3660    /// Returns whether this engine uses padded Base64.
3661    #[must_use]
3662    pub const fn is_padded(&self) -> bool {
3663        PAD
3664    }
3665
3666    /// Returns this engine as an unwrapped profile.
3667    ///
3668    /// Use [`Profile::new`] or [`Profile::checked_new`] when a strict
3669    /// line-wrapping policy should travel with the profile.
3670    #[must_use]
3671    pub const fn profile(&self) -> Profile<A, PAD> {
3672        Profile::new(*self, None)
3673    }
3674
3675    /// Returns the matching constant-time-oriented decoder for this engine's
3676    /// alphabet and padding policy.
3677    ///
3678    /// The returned decoder is still an explicit opt-in to the [`ct`] module's
3679    /// slower, opaque-error, constant-time-oriented scalar path.
3680    #[must_use]
3681    pub const fn ct_decoder(&self) -> ct::CtEngine<A, PAD> {
3682        ct::CtEngine::new()
3683    }
3684
3685    /// Wraps a `std::io::Write` value in a streaming Base64 encoder.
3686    ///
3687    /// This is a convenience constructor for [`stream::Encoder::new`] that
3688    /// keeps the selected engine attached to the call site.
3689    ///
3690    /// ```
3691    /// use std::io::Write;
3692    /// use base64_ng::STANDARD;
3693    ///
3694    /// let mut encoder = STANDARD.encoder_writer(Vec::new());
3695    /// encoder.write_all(b"hello").unwrap();
3696    /// assert_eq!(encoder.finish().unwrap(), b"aGVsbG8=");
3697    /// ```
3698    #[cfg(feature = "stream")]
3699    #[must_use]
3700    pub fn encoder_writer<W>(&self, inner: W) -> stream::Encoder<W, A, PAD> {
3701        stream::Encoder::new(inner, *self)
3702    }
3703
3704    /// Wraps a `std::io::Write` value in a streaming Base64 decoder.
3705    ///
3706    /// This is a convenience constructor for [`stream::Decoder::new`] that
3707    /// keeps the selected engine attached to the call site.
3708    ///
3709    /// ```
3710    /// use std::io::Write;
3711    /// use base64_ng::STANDARD;
3712    ///
3713    /// let mut decoder = STANDARD.decoder_writer(Vec::new());
3714    /// decoder.write_all(b"aGVsbG8=").unwrap();
3715    /// assert_eq!(decoder.finish().unwrap(), b"hello");
3716    /// ```
3717    ///
3718    /// # Security
3719    ///
3720    /// Streaming decoders use the normal strict decode path, not the
3721    /// [`crate::ct`] module. Do not use this adapter for secret-bearing
3722    /// payloads when malformed-input timing matters.
3723    #[cfg(feature = "stream")]
3724    #[must_use]
3725    pub fn decoder_writer<W>(&self, inner: W) -> stream::Decoder<W, A, PAD> {
3726        stream::Decoder::new(inner, *self)
3727    }
3728
3729    /// Wraps a `std::io::Read` value in a streaming Base64 encoder.
3730    ///
3731    /// This is a convenience constructor for [`stream::EncoderReader::new`]
3732    /// that keeps the selected engine attached to the call site.
3733    ///
3734    /// ```
3735    /// use std::io::Read;
3736    /// use base64_ng::STANDARD;
3737    ///
3738    /// let mut reader = STANDARD.encoder_reader(&b"hello"[..]);
3739    /// let mut encoded = String::new();
3740    /// reader.read_to_string(&mut encoded).unwrap();
3741    /// assert_eq!(encoded, "aGVsbG8=");
3742    /// ```
3743    #[cfg(feature = "stream")]
3744    #[must_use]
3745    pub fn encoder_reader<R>(&self, inner: R) -> stream::EncoderReader<R, A, PAD> {
3746        stream::EncoderReader::new(inner, *self)
3747    }
3748
3749    /// Wraps a `std::io::Read` value in a streaming Base64 decoder.
3750    ///
3751    /// This is a convenience constructor for [`stream::DecoderReader::new`]
3752    /// that keeps the selected engine attached to the call site.
3753    ///
3754    /// ```
3755    /// use std::io::Read;
3756    /// use base64_ng::STANDARD;
3757    ///
3758    /// let mut reader = STANDARD.decoder_reader(&b"aGVsbG8="[..]);
3759    /// let mut decoded = Vec::new();
3760    /// reader.read_to_end(&mut decoded).unwrap();
3761    /// assert_eq!(decoded, b"hello");
3762    /// ```
3763    ///
3764    /// # Security
3765    ///
3766    /// Streaming decoder readers use the normal strict decode path, not the
3767    /// [`crate::ct`] module. Do not use this adapter for secret-bearing
3768    /// payloads when malformed-input timing matters.
3769    #[cfg(feature = "stream")]
3770    #[must_use]
3771    pub fn decoder_reader<R>(&self, inner: R) -> stream::DecoderReader<R, A, PAD> {
3772        stream::DecoderReader::new(inner, *self)
3773    }
3774
3775    /// Returns the encoded length for this engine's padding policy.
3776    pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
3777        encoded_len(input_len, PAD)
3778    }
3779
3780    /// Returns the encoded length for this engine, or `None` on overflow.
3781    #[must_use]
3782    pub const fn checked_encoded_len(&self, input_len: usize) -> Option<usize> {
3783        checked_encoded_len(input_len, PAD)
3784    }
3785
3786    /// Returns the encoded length after applying a line wrapping policy.
3787    ///
3788    /// The returned length includes inserted line endings but does not include
3789    /// a trailing line ending after the final encoded line.
3790    pub const fn wrapped_encoded_len(
3791        &self,
3792        input_len: usize,
3793        wrap: LineWrap,
3794    ) -> Result<usize, EncodeError> {
3795        wrapped_encoded_len(input_len, PAD, wrap)
3796    }
3797
3798    /// Returns the encoded length after line wrapping, or `None` on overflow or
3799    /// invalid line wrapping.
3800    #[must_use]
3801    pub const fn checked_wrapped_encoded_len(
3802        &self,
3803        input_len: usize,
3804        wrap: LineWrap,
3805    ) -> Option<usize> {
3806        checked_wrapped_encoded_len(input_len, PAD, wrap)
3807    }
3808
3809    /// Returns the exact decoded length implied by input length and padding.
3810    ///
3811    /// This validates padding placement and impossible lengths, but it does not
3812    /// validate alphabet membership or non-canonical trailing bits.
3813    pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
3814        decoded_len(input, PAD)
3815    }
3816
3817    /// Returns the exact decoded length for the explicit legacy profile.
3818    ///
3819    /// The legacy profile ignores ASCII space, tab, carriage return, and line
3820    /// feed bytes before applying the same alphabet, padding, and canonical-bit
3821    /// checks as strict decoding.
3822    pub fn decoded_len_legacy(&self, input: &[u8]) -> Result<usize, DecodeError> {
3823        validate_legacy_decode::<A, PAD>(input)
3824    }
3825
3826    /// Returns the exact decoded length for a line-wrapped profile.
3827    ///
3828    /// The wrapped profile accepts only the configured line ending. Non-final
3829    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
3830    /// may be shorter. A single trailing line ending after the final line is
3831    /// accepted.
3832    pub fn decoded_len_wrapped(&self, input: &[u8], wrap: LineWrap) -> Result<usize, DecodeError> {
3833        validate_wrapped_decode::<A, PAD>(input, wrap)
3834    }
3835
3836    /// Validates strict Base64 input without writing decoded bytes.
3837    ///
3838    /// This applies the same alphabet, padding, and canonical-bit checks as
3839    /// [`Self::decode_slice`]. Use this method when malformed-input
3840    /// diagnostics matter; use [`Self::validate`] when a boolean is enough.
3841    /// This default validator is not constant-time; use
3842    /// [`crate::ct::CtEngine::validate_result`] through [`Self::ct_decoder`]
3843    /// for secret-bearing payloads where timing posture matters.
3844    ///
3845    /// # Examples
3846    ///
3847    /// ```
3848    /// use base64_ng::STANDARD;
3849    ///
3850    /// STANDARD.validate_result(b"aGVsbG8=").unwrap();
3851    /// assert!(STANDARD.validate_result(b"aGVsbG8").is_err());
3852    /// ```
3853    pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
3854        validate_decode::<A, PAD>(input).map(|_| ())
3855    }
3856
3857    /// Returns whether `input` is valid strict Base64 for this engine.
3858    ///
3859    /// This is a convenience wrapper around [`Self::validate_result`] and is
3860    /// not constant-time. Use [`crate::ct::CtEngine::validate`] through
3861    /// [`Self::ct_decoder`] for secret-bearing payloads where timing posture
3862    /// matters.
3863    ///
3864    /// # Examples
3865    ///
3866    /// ```
3867    /// use base64_ng::URL_SAFE_NO_PAD;
3868    ///
3869    /// assert!(URL_SAFE_NO_PAD.validate(b"-_8"));
3870    /// assert!(!URL_SAFE_NO_PAD.validate(b"+/8"));
3871    /// ```
3872    #[must_use]
3873    pub fn validate(&self, input: &[u8]) -> bool {
3874        self.validate_result(input).is_ok()
3875    }
3876
3877    /// Validates input using the explicit legacy whitespace profile.
3878    ///
3879    /// ASCII space, tab, carriage return, and line feed bytes are ignored
3880    /// before applying the same alphabet, padding, and canonical-bit checks as
3881    /// strict decoding.
3882    ///
3883    /// # Examples
3884    ///
3885    /// ```
3886    /// use base64_ng::STANDARD;
3887    ///
3888    /// STANDARD.validate_legacy_result(b" aG\r\nVsbG8= ").unwrap();
3889    /// assert!(STANDARD.validate_legacy_result(b" aG-=").is_err());
3890    /// ```
3891    pub fn validate_legacy_result(&self, input: &[u8]) -> Result<(), DecodeError> {
3892        validate_legacy_decode::<A, PAD>(input).map(|_| ())
3893    }
3894
3895    /// Returns whether `input` is valid for the explicit legacy whitespace
3896    /// profile.
3897    ///
3898    /// This is a convenience wrapper around [`Self::validate_legacy_result`].
3899    ///
3900    /// # Examples
3901    ///
3902    /// ```
3903    /// use base64_ng::STANDARD;
3904    ///
3905    /// assert!(STANDARD.validate_legacy(b" aG\r\nVsbG8= "));
3906    /// assert!(!STANDARD.validate_legacy(b"aG-V"));
3907    /// ```
3908    #[must_use]
3909    pub fn validate_legacy(&self, input: &[u8]) -> bool {
3910        self.validate_legacy_result(input).is_ok()
3911    }
3912
3913    /// Validates input using a strict line-wrapped profile.
3914    ///
3915    /// This is stricter than [`Self::validate_legacy_result`]: it accepts only
3916    /// the configured line ending and enforces the configured line length for
3917    /// every non-final line.
3918    ///
3919    /// # Examples
3920    ///
3921    /// ```
3922    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
3923    ///
3924    /// let wrap = LineWrap::new(4, LineEnding::Lf);
3925    /// STANDARD.validate_wrapped_result(b"aGVs\nbG8=", wrap).unwrap();
3926    /// assert!(STANDARD.validate_wrapped_result(b"aG\nVsbG8=", wrap).is_err());
3927    /// ```
3928    pub fn validate_wrapped_result(&self, input: &[u8], wrap: LineWrap) -> Result<(), DecodeError> {
3929        validate_wrapped_decode::<A, PAD>(input, wrap).map(|_| ())
3930    }
3931
3932    /// Returns whether `input` is valid for a strict line-wrapped profile.
3933    ///
3934    /// This is a convenience wrapper around [`Self::validate_wrapped_result`].
3935    ///
3936    /// # Examples
3937    ///
3938    /// ```
3939    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
3940    ///
3941    /// let wrap = LineWrap::new(4, LineEnding::Lf);
3942    /// assert!(STANDARD.validate_wrapped(b"aGVs\nbG8=", wrap));
3943    /// assert!(!STANDARD.validate_wrapped(b"aG\nVsbG8=", wrap));
3944    /// ```
3945    #[must_use]
3946    pub fn validate_wrapped(&self, input: &[u8], wrap: LineWrap) -> bool {
3947        self.validate_wrapped_result(input, wrap).is_ok()
3948    }
3949
3950    /// Encodes a fixed-size input into a fixed-size output array in const contexts.
3951    ///
3952    /// Stable Rust does not yet allow this API to return an array whose length
3953    /// is computed from `INPUT_LEN` directly. Instead, the caller supplies the
3954    /// output length through the destination type and this function panics
3955    /// during const evaluation if the length is wrong.
3956    ///
3957    /// # Panics
3958    ///
3959    /// Panics if `OUTPUT_LEN` is not exactly the encoded length for `INPUT_LEN`
3960    /// and this engine's padding policy, or if that length overflows `usize`.
3961    ///
3962    /// # Examples
3963    ///
3964    /// ```
3965    /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
3966    ///
3967    /// const HELLO: [u8; 8] = STANDARD.encode_array(b"hello");
3968    /// const URL_SAFE: [u8; 3] = URL_SAFE_NO_PAD.encode_array(b"\xfb\xff");
3969    ///
3970    /// assert_eq!(&HELLO, b"aGVsbG8=");
3971    /// assert_eq!(&URL_SAFE, b"-_8");
3972    /// ```
3973    ///
3974    /// Incorrect output lengths fail during const evaluation:
3975    ///
3976    /// ```compile_fail
3977    /// use base64_ng::STANDARD;
3978    ///
3979    /// const TOO_SHORT: [u8; 7] = STANDARD.encode_array(b"hello");
3980    /// ```
3981    #[must_use]
3982    pub const fn encode_array<const INPUT_LEN: usize, const OUTPUT_LEN: usize>(
3983        &self,
3984        input: &[u8; INPUT_LEN],
3985    ) -> [u8; OUTPUT_LEN] {
3986        let Some(required) = checked_encoded_len(INPUT_LEN, PAD) else {
3987            panic!("encoded base64 length overflows usize");
3988        };
3989        assert!(
3990            required == OUTPUT_LEN,
3991            "base64 output array has incorrect length"
3992        );
3993
3994        let mut output = [0u8; OUTPUT_LEN];
3995        let mut read = 0;
3996        let mut write = 0;
3997        while INPUT_LEN - read >= 3 {
3998            let b0 = input[read];
3999            let b1 = input[read + 1];
4000            let b2 = input[read + 2];
4001
4002            output[write] = encode_base64_value::<A>(b0 >> 2);
4003            output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
4004            output[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
4005            output[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
4006
4007            read += 3;
4008            write += 4;
4009        }
4010
4011        match INPUT_LEN - read {
4012            0 => {}
4013            1 => {
4014                let b0 = input[read];
4015                output[write] = encode_base64_value::<A>(b0 >> 2);
4016                output[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
4017                write += 2;
4018                if PAD {
4019                    output[write] = b'=';
4020                    output[write + 1] = b'=';
4021                }
4022            }
4023            2 => {
4024                let b0 = input[read];
4025                let b1 = input[read + 1];
4026                output[write] = encode_base64_value::<A>(b0 >> 2);
4027                output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
4028                output[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
4029                if PAD {
4030                    output[write + 3] = b'=';
4031                }
4032            }
4033            _ => unreachable!(),
4034        }
4035
4036        output
4037    }
4038
4039    /// Encodes `input` into `output`, returning the number of bytes written.
4040    pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
4041        backend::encode_slice::<A, PAD>(input, output)
4042    }
4043
4044    /// Encodes `input` into `output` with line wrapping.
4045    ///
4046    /// The wrapping policy inserts line endings between encoded lines and does
4047    /// not append a trailing line ending after the final line.
4048    ///
4049    /// # Examples
4050    ///
4051    /// ```
4052    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
4053    ///
4054    /// let wrap = LineWrap::new(4, LineEnding::Lf);
4055    /// let mut output = [0u8; 9];
4056    /// let written = STANDARD
4057    ///     .encode_slice_wrapped(b"hello", &mut output, wrap)
4058    ///     .unwrap();
4059    ///
4060    /// assert_eq!(&output[..written], b"aGVs\nbG8=");
4061    /// ```
4062    pub fn encode_slice_wrapped(
4063        &self,
4064        input: &[u8],
4065        output: &mut [u8],
4066        wrap: LineWrap,
4067    ) -> Result<usize, EncodeError> {
4068        let required = self.wrapped_encoded_len(input.len(), wrap)?;
4069        if output.len() < required {
4070            return Err(EncodeError::OutputTooSmall {
4071                required,
4072                available: output.len(),
4073            });
4074        }
4075
4076        let encoded_len =
4077            checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
4078        if encoded_len == 0 {
4079            return Ok(0);
4080        }
4081
4082        // If the temporary in-buffer layout size overflows, fall back to the
4083        // fixed scratch buffer path rather than relying on saturated arithmetic.
4084        let combined_required = match required.checked_add(encoded_len) {
4085            Some(len) => len,
4086            None => usize::MAX,
4087        };
4088        if output.len() < combined_required {
4089            let mut scratch = [0u8; 1024];
4090            let mut input_offset = 0;
4091            let mut output_offset = 0;
4092            let mut column = 0;
4093
4094            while input_offset < input.len() {
4095                let remaining = input.len() - input_offset;
4096                let mut take = remaining.min(768);
4097                if remaining > take {
4098                    take -= take % 3;
4099                }
4100                if take == 0 {
4101                    take = remaining;
4102                }
4103
4104                let encoded = match self
4105                    .encode_slice(&input[input_offset..input_offset + take], &mut scratch)
4106                {
4107                    Ok(encoded) => encoded,
4108                    Err(err) => {
4109                        wipe_bytes(&mut scratch);
4110                        return Err(err);
4111                    }
4112                };
4113                if let Err(err) = write_wrapped_bytes(
4114                    &scratch[..encoded],
4115                    output,
4116                    &mut output_offset,
4117                    &mut column,
4118                    wrap,
4119                ) {
4120                    wipe_bytes(&mut scratch);
4121                    return Err(err);
4122                }
4123                wipe_bytes(&mut scratch[..encoded]);
4124                input_offset += take;
4125            }
4126
4127            Ok(output_offset)
4128        } else {
4129            let encoded =
4130                self.encode_slice(input, &mut output[required..required + encoded_len])?;
4131            let mut output_offset = 0;
4132            let mut column = 0;
4133            let mut read = required;
4134            while read < required + encoded {
4135                let byte = output[read];
4136                write_wrapped_byte(byte, output, &mut output_offset, &mut column, wrap)?;
4137                read += 1;
4138            }
4139            wipe_bytes(&mut output[required..required + encoded]);
4140            Ok(output_offset)
4141        }
4142    }
4143
4144    /// Encodes `input` with line wrapping and clears all bytes after the
4145    /// encoded prefix.
4146    ///
4147    /// If encoding fails, the entire output buffer is cleared before the error
4148    /// is returned.
4149    pub fn encode_slice_wrapped_clear_tail(
4150        &self,
4151        input: &[u8],
4152        output: &mut [u8],
4153        wrap: LineWrap,
4154    ) -> Result<usize, EncodeError> {
4155        let written = match self.encode_slice_wrapped(input, output, wrap) {
4156            Ok(written) => written,
4157            Err(err) => {
4158                wipe_bytes(output);
4159                return Err(err);
4160            }
4161        };
4162        wipe_tail(output, written);
4163        Ok(written)
4164    }
4165
4166    /// Encodes `input` with line wrapping into a stack-backed buffer.
4167    ///
4168    /// This is useful for MIME/PEM-style protocols where heap allocation is
4169    /// unnecessary. If encoding fails, the internal backing array is cleared
4170    /// before the error is returned.
4171    pub fn encode_wrapped_buffer<const CAP: usize>(
4172        &self,
4173        input: &[u8],
4174        wrap: LineWrap,
4175    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
4176        let mut output = EncodedBuffer::new();
4177        let written = match self.encode_slice_wrapped_clear_tail(input, &mut output.bytes, wrap) {
4178            Ok(written) => written,
4179            Err(err) => {
4180                output.clear();
4181                return Err(err);
4182            }
4183        };
4184        output.len = written;
4185        Ok(output)
4186    }
4187
4188    /// Encodes `input` with line wrapping into a newly allocated byte vector.
4189    #[cfg(feature = "alloc")]
4190    #[must_use = "for secret-bearing payloads use encode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
4191    pub fn encode_wrapped_vec(
4192        &self,
4193        input: &[u8],
4194        wrap: LineWrap,
4195    ) -> Result<alloc::vec::Vec<u8>, EncodeError> {
4196        let required = self.wrapped_encoded_len(input.len(), wrap)?;
4197        let mut output = alloc::vec![0; required];
4198        let written = self.encode_slice_wrapped(input, &mut output, wrap)?;
4199        output.truncate(written);
4200        Ok(output)
4201    }
4202
4203    /// Encodes `input` with line wrapping into a newly allocated UTF-8 string.
4204    #[cfg(feature = "alloc")]
4205    pub fn encode_wrapped_string(
4206        &self,
4207        input: &[u8],
4208        wrap: LineWrap,
4209    ) -> Result<alloc::string::String, EncodeError> {
4210        let output = self.encode_wrapped_vec(input, wrap)?;
4211        match alloc::string::String::from_utf8(output) {
4212            Ok(output) => Ok(output),
4213            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
4214        }
4215    }
4216
4217    /// Encodes `input` with line wrapping into a redacted owned secret buffer.
4218    ///
4219    /// This is useful when the wrapped encoded representation itself is
4220    /// sensitive and should not be accidentally logged through formatting.
4221    #[cfg(feature = "alloc")]
4222    pub fn encode_wrapped_secret(
4223        &self,
4224        input: &[u8],
4225        wrap: LineWrap,
4226    ) -> Result<SecretBuffer, EncodeError> {
4227        self.encode_wrapped_vec(input, wrap)
4228            .map(SecretBuffer::from_vec)
4229    }
4230
4231    /// Encodes `input` into `output` and clears all bytes after the encoded
4232    /// prefix.
4233    ///
4234    /// If encoding fails, the entire output buffer is cleared before the error
4235    /// is returned.
4236    ///
4237    /// # Examples
4238    ///
4239    /// ```
4240    /// use base64_ng::STANDARD;
4241    ///
4242    /// let mut output = [0xff; 12];
4243    /// let written = STANDARD
4244    ///     .encode_slice_clear_tail(b"hello", &mut output)
4245    ///     .unwrap();
4246    ///
4247    /// assert_eq!(&output[..written], b"aGVsbG8=");
4248    /// assert!(output[written..].iter().all(|byte| *byte == 0));
4249    /// ```
4250    pub fn encode_slice_clear_tail(
4251        &self,
4252        input: &[u8],
4253        output: &mut [u8],
4254    ) -> Result<usize, EncodeError> {
4255        let written = match self.encode_slice(input, output) {
4256            Ok(written) => written,
4257            Err(err) => {
4258                wipe_bytes(output);
4259                return Err(err);
4260            }
4261        };
4262        wipe_tail(output, written);
4263        Ok(written)
4264    }
4265
4266    /// Encodes `input` into a stack-backed buffer.
4267    ///
4268    /// This helper is useful for short values where callers want the
4269    /// convenience of an owned result without enabling `alloc`.
4270    ///
4271    /// # Examples
4272    ///
4273    /// ```
4274    /// use base64_ng::STANDARD;
4275    ///
4276    /// let encoded = STANDARD.encode_buffer::<8>(b"hello").unwrap();
4277    ///
4278    /// assert_eq!(encoded.as_str(), "aGVsbG8=");
4279    /// ```
4280    pub fn encode_buffer<const CAP: usize>(
4281        &self,
4282        input: &[u8],
4283    ) -> Result<EncodedBuffer<CAP>, EncodeError> {
4284        let mut output = EncodedBuffer::new();
4285        let written = match self.encode_slice_clear_tail(input, &mut output.bytes) {
4286            Ok(written) => written,
4287            Err(err) => {
4288                output.clear();
4289                return Err(err);
4290            }
4291        };
4292        output.len = written;
4293        Ok(output)
4294    }
4295
4296    /// Encodes `input` into a newly allocated byte vector.
4297    #[cfg(feature = "alloc")]
4298    #[must_use = "for secret-bearing payloads use encode_secret, which returns a redacted buffer with drop-time cleanup"]
4299    pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
4300        let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
4301        let mut output = alloc::vec![0; required];
4302        let written = self.encode_slice(input, &mut output)?;
4303        output.truncate(written);
4304        Ok(output)
4305    }
4306
4307    /// Encodes `input` into a redacted owned secret buffer.
4308    ///
4309    /// This is useful when the encoded representation itself is sensitive and
4310    /// should not be accidentally logged through formatting.
4311    #[cfg(feature = "alloc")]
4312    pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
4313        self.encode_vec(input).map(SecretBuffer::from_vec)
4314    }
4315
4316    /// Encodes `input` into a newly allocated UTF-8 string.
4317    ///
4318    /// Base64 output is ASCII by construction. This helper is available with
4319    /// the `alloc` feature and has the same encoding semantics as
4320    /// [`Self::encode_slice`].
4321    ///
4322    /// # Examples
4323    ///
4324    /// ```
4325    /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
4326    ///
4327    /// assert_eq!(STANDARD.encode_string(b"hello").unwrap(), "aGVsbG8=");
4328    /// assert_eq!(URL_SAFE_NO_PAD.encode_string(b"\xfb\xff").unwrap(), "-_8");
4329    /// ```
4330    #[cfg(feature = "alloc")]
4331    pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
4332        let output = self.encode_vec(input)?;
4333        match alloc::string::String::from_utf8(output) {
4334            Ok(output) => Ok(output),
4335            Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
4336        }
4337    }
4338
4339    /// Encodes the first `input_len` bytes of `buffer` in place.
4340    ///
4341    /// The buffer must have enough spare capacity for the encoded output. The
4342    /// implementation writes from right to left, so unread input bytes are not
4343    /// overwritten before they are encoded.
4344    ///
4345    /// # Examples
4346    ///
4347    /// ```
4348    /// use base64_ng::STANDARD;
4349    ///
4350    /// let mut buffer = [0u8; 8];
4351    /// buffer[..5].copy_from_slice(b"hello");
4352    /// let encoded = STANDARD.encode_in_place(&mut buffer, 5).unwrap();
4353    /// assert_eq!(encoded, b"aGVsbG8=");
4354    /// ```
4355    pub fn encode_in_place<'a>(
4356        &self,
4357        buffer: &'a mut [u8],
4358        input_len: usize,
4359    ) -> Result<&'a mut [u8], EncodeError> {
4360        if input_len > buffer.len() {
4361            return Err(EncodeError::InputTooLarge {
4362                input_len,
4363                buffer_len: buffer.len(),
4364            });
4365        }
4366
4367        let required = checked_encoded_len(input_len, PAD).ok_or(EncodeError::LengthOverflow)?;
4368        if buffer.len() < required {
4369            return Err(EncodeError::OutputTooSmall {
4370                required,
4371                available: buffer.len(),
4372            });
4373        }
4374
4375        let mut read = input_len;
4376        let mut write = required;
4377
4378        match input_len % 3 {
4379            0 => {}
4380            1 => {
4381                read -= 1;
4382                let b0 = buffer[read];
4383                if PAD {
4384                    write -= 4;
4385                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
4386                    buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
4387                    buffer[write + 2] = b'=';
4388                    buffer[write + 3] = b'=';
4389                } else {
4390                    write -= 2;
4391                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
4392                    buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
4393                }
4394            }
4395            2 => {
4396                read -= 2;
4397                let b0 = buffer[read];
4398                let b1 = buffer[read + 1];
4399                if PAD {
4400                    write -= 4;
4401                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
4402                    buffer[write + 1] =
4403                        encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
4404                    buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
4405                    buffer[write + 3] = b'=';
4406                } else {
4407                    write -= 3;
4408                    buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
4409                    buffer[write + 1] =
4410                        encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
4411                    buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
4412                }
4413            }
4414            _ => unreachable!(),
4415        }
4416
4417        while read > 0 {
4418            read -= 3;
4419            write -= 4;
4420            let b0 = buffer[read];
4421            let b1 = buffer[read + 1];
4422            let b2 = buffer[read + 2];
4423
4424            buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
4425            buffer[write + 1] =
4426                encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
4427            buffer[write + 2] =
4428                encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
4429            buffer[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
4430        }
4431
4432        // The right-to-left loop consumes exactly three input bytes for every
4433        // four output bytes. If this invariant changes, returning a shifted
4434        // slice would silently corrupt the in-place output.
4435        debug_assert_eq!(write, 0);
4436        Ok(&mut buffer[..required])
4437    }
4438
4439    /// Encodes the first `input_len` bytes of `buffer` in place and clears all
4440    /// bytes after the encoded prefix.
4441    ///
4442    /// If encoding fails because `input_len` is too large, the output buffer is
4443    /// too small, or the encoded length overflows `usize`, the entire buffer is
4444    /// cleared before the error is returned.
4445    ///
4446    /// # Examples
4447    ///
4448    /// ```
4449    /// use base64_ng::STANDARD;
4450    ///
4451    /// let mut buffer = [0xff; 12];
4452    /// buffer[..5].copy_from_slice(b"hello");
4453    /// let encoded = STANDARD.encode_in_place_clear_tail(&mut buffer, 5).unwrap();
4454    /// assert_eq!(encoded, b"aGVsbG8=");
4455    /// ```
4456    pub fn encode_in_place_clear_tail<'a>(
4457        &self,
4458        buffer: &'a mut [u8],
4459        input_len: usize,
4460    ) -> Result<&'a mut [u8], EncodeError> {
4461        let len = match self.encode_in_place(buffer, input_len) {
4462            Ok(encoded) => encoded.len(),
4463            Err(err) => {
4464                wipe_bytes(buffer);
4465                return Err(err);
4466            }
4467        };
4468        wipe_tail(buffer, len);
4469        Ok(&mut buffer[..len])
4470    }
4471
4472    /// Decodes `input` into `output`, returning the number of bytes written.
4473    ///
4474    /// This is strict decoding. Whitespace, mixed alphabets, malformed padding,
4475    /// and trailing non-padding data are rejected.
4476    ///
4477    /// # Security
4478    ///
4479    /// This default scalar decoder prioritizes strict validation, exact error
4480    /// reporting, and ordinary throughput. It may branch or return early based
4481    /// on byte validity, malformed input, padding position, and output
4482    /// capacity. It also reports exact failure positions and invalid byte
4483    /// values through [`DecodeError`]. Do not use this method for token
4484    /// comparison, key-material decoding, or secret-bearing validation where
4485    /// malformed-input timing matters. Use [`crate::ct`],
4486    /// [`crate::ct::STANDARD`], [`crate::ct::URL_SAFE_NO_PAD`], or
4487    /// [`Self::ct_decoder`] with `decode_slice_clear_tail` for
4488    /// constant-time-oriented secret decoding.
4489    #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
4490    pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
4491        backend::decode_slice::<A, PAD>(input, output)
4492    }
4493
4494    /// Decodes `input` into `output` and clears all bytes after the decoded
4495    /// prefix.
4496    ///
4497    /// If decoding fails, the entire output buffer is cleared before the error
4498    /// is returned.
4499    ///
4500    /// # Examples
4501    ///
4502    /// ```
4503    /// use base64_ng::STANDARD;
4504    ///
4505    /// let mut output = [0xff; 8];
4506    /// let written = STANDARD
4507    ///     .decode_slice_clear_tail(b"aGk=", &mut output)
4508    ///     .unwrap();
4509    ///
4510    /// assert_eq!(&output[..written], b"hi");
4511    /// assert!(output[written..].iter().all(|byte| *byte == 0));
4512    /// ```
4513    pub fn decode_slice_clear_tail(
4514        &self,
4515        input: &[u8],
4516        output: &mut [u8],
4517    ) -> Result<usize, DecodeError> {
4518        let written = match self.decode_slice(input, output) {
4519            Ok(written) => written,
4520            Err(err) => {
4521                wipe_bytes(output);
4522                return Err(err);
4523            }
4524        };
4525        wipe_tail(output, written);
4526        Ok(written)
4527    }
4528
4529    /// Decodes `input` into a stack-backed buffer.
4530    ///
4531    /// This helper is useful for short decoded values where callers want the
4532    /// convenience of an owned result without enabling `alloc`.
4533    ///
4534    /// # Examples
4535    ///
4536    /// ```
4537    /// use base64_ng::STANDARD;
4538    ///
4539    /// let decoded = STANDARD.decode_buffer::<5>(b"aGVsbG8=").unwrap();
4540    ///
4541    /// assert_eq!(decoded.as_bytes(), b"hello");
4542    /// ```
4543    pub fn decode_buffer<const CAP: usize>(
4544        &self,
4545        input: &[u8],
4546    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
4547        let mut output = DecodedBuffer::new();
4548        let written = match self.decode_slice_clear_tail(input, &mut output.bytes) {
4549            Ok(written) => written,
4550            Err(err) => {
4551                output.clear();
4552                return Err(err);
4553            }
4554        };
4555        output.len = written;
4556        Ok(output)
4557    }
4558
4559    /// Decodes `input` using the explicit legacy whitespace profile.
4560    ///
4561    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
4562    /// Alphabet selection, padding placement, trailing data after padding, and
4563    /// non-canonical trailing bits remain strict.
4564    ///
4565    /// # Security
4566    ///
4567    /// This method uses the normal strict decode path after legacy whitespace
4568    /// handling. It may branch or return early based on malformed input and is
4569    /// not a constant-time token validator or key-material decoder. Use
4570    /// [`crate::ct`] for secret-bearing payloads.
4571    #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
4572    pub fn decode_slice_legacy(
4573        &self,
4574        input: &[u8],
4575        output: &mut [u8],
4576    ) -> Result<usize, DecodeError> {
4577        let required = validate_legacy_decode::<A, PAD>(input)?;
4578        if output.len() < required {
4579            return Err(DecodeError::OutputTooSmall {
4580                required,
4581                available: output.len(),
4582            });
4583        }
4584        decode_legacy_to_slice::<A, PAD>(input, output)
4585    }
4586
4587    /// Decodes `input` using the explicit legacy whitespace profile and clears
4588    /// all bytes after the decoded prefix.
4589    ///
4590    /// If validation or decoding fails, the entire output buffer is cleared
4591    /// before the error is returned.
4592    ///
4593    /// # Examples
4594    ///
4595    /// ```
4596    /// use base64_ng::STANDARD;
4597    ///
4598    /// let mut output = [0xff; 8];
4599    /// let written = STANDARD
4600    ///     .decode_slice_legacy_clear_tail(b" aG\r\nk= ", &mut output)
4601    ///     .unwrap();
4602    ///
4603    /// assert_eq!(&output[..written], b"hi");
4604    /// assert!(output[written..].iter().all(|byte| *byte == 0));
4605    /// ```
4606    pub fn decode_slice_legacy_clear_tail(
4607        &self,
4608        input: &[u8],
4609        output: &mut [u8],
4610    ) -> Result<usize, DecodeError> {
4611        let written = match self.decode_slice_legacy(input, output) {
4612            Ok(written) => written,
4613            Err(err) => {
4614                wipe_bytes(output);
4615                return Err(err);
4616            }
4617        };
4618        wipe_tail(output, written);
4619        Ok(written)
4620    }
4621
4622    /// Decodes `input` into a stack-backed buffer using the explicit legacy
4623    /// whitespace profile.
4624    ///
4625    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
4626    /// Alphabet selection, padding placement, trailing data after padding, and
4627    /// non-canonical trailing bits remain strict. If decoding fails, the
4628    /// internal backing array is cleared before the error is returned.
4629    pub fn decode_buffer_legacy<const CAP: usize>(
4630        &self,
4631        input: &[u8],
4632    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
4633        let mut output = DecodedBuffer::new();
4634        let written = match self.decode_slice_legacy_clear_tail(input, &mut output.bytes) {
4635            Ok(written) => written,
4636            Err(err) => {
4637                output.clear();
4638                return Err(err);
4639            }
4640        };
4641        output.len = written;
4642        Ok(output)
4643    }
4644
4645    /// Decodes `input` using a strict line-wrapped profile.
4646    ///
4647    /// The wrapped profile accepts only the configured line ending. Non-final
4648    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
4649    /// may be shorter. A single trailing line ending after the final line is
4650    /// accepted.
4651    ///
4652    /// # Security
4653    ///
4654    /// This method uses the normal strict decode path after line-profile
4655    /// validation. It may branch or return early based on malformed input and
4656    /// is not a constant-time token validator or key-material decoder. Use
4657    /// [`crate::ct`] for secret-bearing payloads.
4658    #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
4659    pub fn decode_slice_wrapped(
4660        &self,
4661        input: &[u8],
4662        output: &mut [u8],
4663        wrap: LineWrap,
4664    ) -> Result<usize, DecodeError> {
4665        let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
4666        if output.len() < required {
4667            return Err(DecodeError::OutputTooSmall {
4668                required,
4669                available: output.len(),
4670            });
4671        }
4672        decode_wrapped_to_slice::<A, PAD>(input, output, wrap)
4673    }
4674
4675    /// Decodes `input` using a strict line-wrapped profile and clears all bytes
4676    /// after the decoded prefix.
4677    ///
4678    /// If validation or decoding fails, the entire output buffer is cleared
4679    /// before the error is returned.
4680    pub fn decode_slice_wrapped_clear_tail(
4681        &self,
4682        input: &[u8],
4683        output: &mut [u8],
4684        wrap: LineWrap,
4685    ) -> Result<usize, DecodeError> {
4686        let written = match self.decode_slice_wrapped(input, output, wrap) {
4687            Ok(written) => written,
4688            Err(err) => {
4689                wipe_bytes(output);
4690                return Err(err);
4691            }
4692        };
4693        wipe_tail(output, written);
4694        Ok(written)
4695    }
4696
4697    /// Decodes `input` using a strict line-wrapped profile into a stack-backed
4698    /// buffer.
4699    ///
4700    /// The wrapped profile accepts only the configured line ending. Non-final
4701    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
4702    /// may be shorter. A single trailing line ending after the final line is
4703    /// accepted. If decoding fails, the internal backing array is cleared
4704    /// before the error is returned.
4705    pub fn decode_wrapped_buffer<const CAP: usize>(
4706        &self,
4707        input: &[u8],
4708        wrap: LineWrap,
4709    ) -> Result<DecodedBuffer<CAP>, DecodeError> {
4710        let mut output = DecodedBuffer::new();
4711        let written = match self.decode_slice_wrapped_clear_tail(input, &mut output.bytes, wrap) {
4712            Ok(written) => written,
4713            Err(err) => {
4714                output.clear();
4715                return Err(err);
4716            }
4717        };
4718        output.len = written;
4719        Ok(output)
4720    }
4721
4722    /// Decodes `input` into a newly allocated byte vector.
4723    ///
4724    /// This is strict decoding with the same semantics as [`Self::decode_slice`].
4725    #[cfg(feature = "alloc")]
4726    #[must_use = "for secret-bearing payloads use decode_secret, which returns a redacted buffer with drop-time cleanup"]
4727    pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
4728        let required = validate_decode::<A, PAD>(input)?;
4729        let mut output = alloc::vec![0; required];
4730        let written = match self.decode_slice(input, &mut output) {
4731            Ok(written) => written,
4732            Err(err) => {
4733                wipe_bytes(&mut output);
4734                return Err(err);
4735            }
4736        };
4737        output.truncate(written);
4738        Ok(output)
4739    }
4740
4741    /// Decodes `input` into a redacted owned secret buffer.
4742    ///
4743    /// On malformed input, the intermediate output buffer is cleared before the
4744    /// error is returned by [`Self::decode_vec`].
4745    #[cfg(feature = "alloc")]
4746    pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
4747        self.decode_vec(input).map(SecretBuffer::from_vec)
4748    }
4749
4750    /// Decodes `input` into a newly allocated byte vector using the explicit
4751    /// legacy whitespace profile.
4752    #[cfg(feature = "alloc")]
4753    #[must_use = "for secret-bearing payloads use decode_secret_legacy, which returns a redacted buffer with drop-time cleanup"]
4754    pub fn decode_vec_legacy(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
4755        let required = validate_legacy_decode::<A, PAD>(input)?;
4756        let mut output = alloc::vec![0; required];
4757        let written = match self.decode_slice_legacy(input, &mut output) {
4758            Ok(written) => written,
4759            Err(err) => {
4760                wipe_bytes(&mut output);
4761                return Err(err);
4762            }
4763        };
4764        output.truncate(written);
4765        Ok(output)
4766    }
4767
4768    /// Decodes `input` into a redacted owned secret buffer using the explicit
4769    /// legacy whitespace profile.
4770    ///
4771    /// ASCII space, tab, carriage return, and line feed bytes are ignored.
4772    /// Alphabet selection, padding placement, trailing data after padding, and
4773    /// non-canonical trailing bits remain strict.
4774    #[cfg(feature = "alloc")]
4775    pub fn decode_secret_legacy(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
4776        self.decode_vec_legacy(input).map(SecretBuffer::from_vec)
4777    }
4778
4779    /// Decodes line-wrapped input into a newly allocated byte vector.
4780    #[cfg(feature = "alloc")]
4781    #[must_use = "for secret-bearing payloads use decode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
4782    pub fn decode_wrapped_vec(
4783        &self,
4784        input: &[u8],
4785        wrap: LineWrap,
4786    ) -> Result<alloc::vec::Vec<u8>, DecodeError> {
4787        let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
4788        let mut output = alloc::vec![0; required];
4789        let written = match self.decode_slice_wrapped(input, &mut output, wrap) {
4790            Ok(written) => written,
4791            Err(err) => {
4792                wipe_bytes(&mut output);
4793                return Err(err);
4794            }
4795        };
4796        output.truncate(written);
4797        Ok(output)
4798    }
4799
4800    /// Decodes line-wrapped input into a redacted owned secret buffer.
4801    ///
4802    /// The wrapped profile accepts only the configured line ending. Non-final
4803    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
4804    /// may be shorter. A single trailing line ending after the final line is
4805    /// accepted.
4806    #[cfg(feature = "alloc")]
4807    pub fn decode_wrapped_secret(
4808        &self,
4809        input: &[u8],
4810        wrap: LineWrap,
4811    ) -> Result<SecretBuffer, DecodeError> {
4812        self.decode_wrapped_vec(input, wrap)
4813            .map(SecretBuffer::from_vec)
4814    }
4815
4816    /// Decodes `buffer` in place using a strict line-wrapped profile.
4817    ///
4818    /// The wrapped profile accepts only the configured line ending. Non-final
4819    /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
4820    /// may be shorter. A single trailing line ending after the final line is
4821    /// accepted. If validation fails, the buffer contents are unspecified.
4822    /// On success, bytes after the returned decoded prefix may retain the
4823    /// compacted encoded representation. Use
4824    /// [`Self::decode_in_place_wrapped_clear_tail`] when the buffer may be
4825    /// reused or freed without a caller-managed wipe.
4826    ///
4827    /// # Examples
4828    ///
4829    /// ```
4830    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
4831    ///
4832    /// let mut buffer = *b"aGVs\nbG8=";
4833    /// let decoded = STANDARD
4834    ///     .decode_in_place_wrapped(&mut buffer, LineWrap::new(4, LineEnding::Lf))
4835    ///     .unwrap();
4836    ///
4837    /// assert_eq!(decoded, b"hello");
4838    /// ```
4839    pub fn decode_in_place_wrapped<'a>(
4840        &self,
4841        buffer: &'a mut [u8],
4842        wrap: LineWrap,
4843    ) -> Result<&'a mut [u8], DecodeError> {
4844        let _required = validate_wrapped_decode::<A, PAD>(buffer, wrap)?;
4845        let compacted = compact_wrapped_input(buffer, wrap)?;
4846        let len = Self::decode_slice_to_start(&mut buffer[..compacted])?;
4847        Ok(&mut buffer[..len])
4848    }
4849
4850    /// Decodes `buffer` in place using a strict line-wrapped profile and clears
4851    /// all bytes after the decoded prefix.
4852    ///
4853    /// If validation or decoding fails, the entire buffer is cleared before the
4854    /// error is returned.
4855    ///
4856    /// # Examples
4857    ///
4858    /// ```
4859    /// use base64_ng::{LineEnding, LineWrap, STANDARD};
4860    ///
4861    /// let mut buffer = *b"aGVs\nbG8=";
4862    /// let len = STANDARD
4863    ///     .decode_in_place_wrapped_clear_tail(&mut buffer, LineWrap::new(4, LineEnding::Lf))
4864    ///     .unwrap()
4865    ///     .len();
4866    ///
4867    /// assert_eq!(&buffer[..len], b"hello");
4868    /// assert!(buffer[len..].iter().all(|byte| *byte == 0));
4869    /// ```
4870    pub fn decode_in_place_wrapped_clear_tail<'a>(
4871        &self,
4872        buffer: &'a mut [u8],
4873        wrap: LineWrap,
4874    ) -> Result<&'a mut [u8], DecodeError> {
4875        if let Err(err) = validate_wrapped_decode::<A, PAD>(buffer, wrap) {
4876            wipe_bytes(buffer);
4877            return Err(err);
4878        }
4879
4880        let compacted = match compact_wrapped_input(buffer, wrap) {
4881            Ok(compacted) => compacted,
4882            Err(err) => {
4883                wipe_bytes(buffer);
4884                return Err(err);
4885            }
4886        };
4887
4888        let len = match Self::decode_slice_to_start(&mut buffer[..compacted]) {
4889            Ok(len) => len,
4890            Err(err) => {
4891                wipe_bytes(buffer);
4892                return Err(err);
4893            }
4894        };
4895        wipe_tail(buffer, len);
4896        Ok(&mut buffer[..len])
4897    }
4898
4899    /// Decodes the buffer in place and returns the decoded prefix.
4900    ///
4901    /// On success, bytes after the returned decoded prefix may retain encoded
4902    /// input bytes. Use [`Self::decode_in_place_clear_tail`] when the buffer
4903    /// may be reused or freed without a caller-managed wipe.
4904    ///
4905    /// # Security
4906    ///
4907    /// This default scalar decoder prioritizes strict validation, exact error
4908    /// reporting, and ordinary throughput. It may branch or return early based
4909    /// on malformed input and reports exact failure positions and invalid byte
4910    /// values through [`DecodeError`]. Do not use this method for token
4911    /// comparison, key-material decoding, or secret-bearing validation where
4912    /// malformed-input timing matters.
4913    ///
4914    /// # Examples
4915    ///
4916    /// ```
4917    /// use base64_ng::STANDARD_NO_PAD;
4918    ///
4919    /// let mut buffer = *b"Zm9vYmFy";
4920    /// let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
4921    /// assert_eq!(decoded, b"foobar");
4922    /// ```
4923    pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
4924        let len = Self::decode_slice_to_start(buffer)?;
4925        Ok(&mut buffer[..len])
4926    }
4927
4928    /// Decodes the buffer in place and clears all bytes after the decoded prefix.
4929    ///
4930    /// If decoding fails, the entire buffer is cleared before the error is
4931    /// returned. Use this variant when the encoded or partially decoded data is
4932    /// sensitive and the caller wants best-effort cleanup without adding a
4933    /// dependency.
4934    ///
4935    /// # Examples
4936    ///
4937    /// ```
4938    /// use base64_ng::STANDARD;
4939    ///
4940    /// let mut buffer = *b"aGk=";
4941    /// let decoded = STANDARD.decode_in_place_clear_tail(&mut buffer).unwrap();
4942    /// assert_eq!(decoded, b"hi");
4943    /// ```
4944    pub fn decode_in_place_clear_tail<'a>(
4945        &self,
4946        buffer: &'a mut [u8],
4947    ) -> Result<&'a mut [u8], DecodeError> {
4948        let len = match Self::decode_slice_to_start(buffer) {
4949            Ok(len) => len,
4950            Err(err) => {
4951                wipe_bytes(buffer);
4952                return Err(err);
4953            }
4954        };
4955        wipe_tail(buffer, len);
4956        Ok(&mut buffer[..len])
4957    }
4958
4959    /// Decodes `buffer` in place using the explicit legacy whitespace profile.
4960    ///
4961    /// Ignored whitespace is compacted out before decoding. If validation
4962    /// fails, the buffer contents are unspecified. On success, bytes after the
4963    /// returned decoded prefix may retain the compacted encoded
4964    /// representation. Use [`Self::decode_in_place_legacy_clear_tail`] when the
4965    /// buffer may be reused or freed without a caller-managed wipe.
4966    pub fn decode_in_place_legacy<'a>(
4967        &self,
4968        buffer: &'a mut [u8],
4969    ) -> Result<&'a mut [u8], DecodeError> {
4970        let _required = validate_legacy_decode::<A, PAD>(buffer)?;
4971        let mut write = 0;
4972        let mut read = 0;
4973        while read < buffer.len() {
4974            let byte = buffer[read];
4975            if !is_legacy_whitespace(byte) {
4976                buffer[write] = byte;
4977                write += 1;
4978            }
4979            read += 1;
4980        }
4981        let len = Self::decode_slice_to_start(&mut buffer[..write])?;
4982        Ok(&mut buffer[..len])
4983    }
4984
4985    /// Decodes `buffer` in place using the explicit legacy whitespace profile
4986    /// and clears all bytes after the decoded prefix.
4987    ///
4988    /// If validation or decoding fails, the entire buffer is cleared before the
4989    /// error is returned.
4990    pub fn decode_in_place_legacy_clear_tail<'a>(
4991        &self,
4992        buffer: &'a mut [u8],
4993    ) -> Result<&'a mut [u8], DecodeError> {
4994        if let Err(err) = validate_legacy_decode::<A, PAD>(buffer) {
4995            wipe_bytes(buffer);
4996            return Err(err);
4997        }
4998
4999        let mut write = 0;
5000        let mut read = 0;
5001        while read < buffer.len() {
5002            let byte = buffer[read];
5003            if !is_legacy_whitespace(byte) {
5004                buffer[write] = byte;
5005                write += 1;
5006            }
5007            read += 1;
5008        }
5009
5010        let len = match Self::decode_slice_to_start(&mut buffer[..write]) {
5011            Ok(len) => len,
5012            Err(err) => {
5013                wipe_bytes(buffer);
5014                return Err(err);
5015            }
5016        };
5017        wipe_tail(buffer, len);
5018        Ok(&mut buffer[..len])
5019    }
5020
5021    fn decode_slice_to_start(buffer: &mut [u8]) -> Result<usize, DecodeError> {
5022        let _required = validate_decode::<A, PAD>(buffer)?;
5023        let input_len = buffer.len();
5024        let mut read = 0;
5025        let mut write = 0;
5026        while read + 4 <= input_len {
5027            let chunk = read_quad(buffer, read)?;
5028            let available = buffer.len();
5029            let output_tail = buffer.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
5030                required: write,
5031                available,
5032            })?;
5033            let written = decode_chunk::<A, PAD>(chunk, output_tail)
5034                .map_err(|err| err.with_index_offset(read))?;
5035            read += 4;
5036            write += written;
5037            if written < 3 {
5038                if read != input_len {
5039                    return Err(DecodeError::InvalidPadding { index: read - 4 });
5040                }
5041                return Ok(write);
5042            }
5043        }
5044
5045        let rem = input_len - read;
5046        if rem == 0 {
5047            return Ok(write);
5048        }
5049        if PAD {
5050            return Err(DecodeError::InvalidLength);
5051        }
5052        let mut tail = [0u8; 3];
5053        tail[..rem].copy_from_slice(&buffer[read..input_len]);
5054        decode_tail_unpadded::<A>(&tail[..rem], &mut buffer[write..])
5055            .map_err(|err| err.with_index_offset(read))
5056            .map(|n| write + n)
5057    }
5058}
5059
5060fn write_wrapped_bytes(
5061    input: &[u8],
5062    output: &mut [u8],
5063    output_offset: &mut usize,
5064    column: &mut usize,
5065    wrap: LineWrap,
5066) -> Result<(), EncodeError> {
5067    for byte in input {
5068        write_wrapped_byte(*byte, output, output_offset, column, wrap)?;
5069    }
5070    Ok(())
5071}
5072
5073fn write_wrapped_byte(
5074    byte: u8,
5075    output: &mut [u8],
5076    output_offset: &mut usize,
5077    column: &mut usize,
5078    wrap: LineWrap,
5079) -> Result<(), EncodeError> {
5080    if *column == wrap.line_len {
5081        let line_ending = wrap.line_ending.as_bytes();
5082        let mut index = 0;
5083        while index < line_ending.len() {
5084            if *output_offset >= output.len() {
5085                return Err(EncodeError::OutputTooSmall {
5086                    required: *output_offset + 1,
5087                    available: output.len(),
5088                });
5089            }
5090            output[*output_offset] = line_ending[index];
5091            *output_offset += 1;
5092            index += 1;
5093        }
5094        *column = 0;
5095    }
5096
5097    if *output_offset >= output.len() {
5098        return Err(EncodeError::OutputTooSmall {
5099            required: *output_offset + 1,
5100            available: output.len(),
5101        });
5102    }
5103    output[*output_offset] = byte;
5104    *output_offset += 1;
5105    *column += 1;
5106    Ok(())
5107}
5108
5109/// Encoding error.
5110#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5111pub enum EncodeError {
5112    /// The encoded output length would overflow `usize`.
5113    LengthOverflow,
5114    /// The requested line wrapping policy is invalid.
5115    InvalidLineWrap {
5116        /// Requested line length.
5117        line_len: usize,
5118    },
5119    /// The caller-provided input length exceeds the provided buffer.
5120    InputTooLarge {
5121        /// Requested input bytes.
5122        input_len: usize,
5123        /// Available buffer bytes.
5124        buffer_len: usize,
5125    },
5126    /// The output buffer is too small.
5127    OutputTooSmall {
5128        /// Required output bytes.
5129        required: usize,
5130        /// Available output bytes.
5131        available: usize,
5132    },
5133}
5134
5135impl core::fmt::Display for EncodeError {
5136    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
5137        match self {
5138            Self::LengthOverflow => f.write_str("base64 output length overflows usize"),
5139            Self::InvalidLineWrap { line_len } => {
5140                write!(f, "base64 line wrap length {line_len} is invalid")
5141            }
5142            Self::InputTooLarge {
5143                input_len,
5144                buffer_len,
5145            } => write!(
5146                f,
5147                "base64 input length {input_len} exceeds buffer length {buffer_len}"
5148            ),
5149            Self::OutputTooSmall {
5150                required,
5151                available,
5152            } => write!(
5153                f,
5154                "base64 output buffer too small: required {required}, available {available}"
5155            ),
5156        }
5157    }
5158}
5159
5160#[cfg(feature = "std")]
5161impl std::error::Error for EncodeError {}
5162
5163/// Alphabet validation error.
5164#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5165pub enum AlphabetError {
5166    /// The alphabet contains a non-visible-ASCII byte.
5167    InvalidByte {
5168        /// Byte index in the alphabet table.
5169        index: usize,
5170        /// Invalid byte value.
5171        byte: u8,
5172    },
5173    /// The alphabet contains the padding byte `=`.
5174    PaddingByte {
5175        /// Byte index in the alphabet table.
5176        index: usize,
5177    },
5178    /// The alphabet maps more than one value to the same byte.
5179    DuplicateByte {
5180        /// First byte index.
5181        first: usize,
5182        /// Second byte index.
5183        second: usize,
5184        /// Duplicated byte value.
5185        byte: u8,
5186    },
5187}
5188
5189impl core::fmt::Display for AlphabetError {
5190    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
5191        match self {
5192            Self::InvalidByte { index, byte } => {
5193                write!(
5194                    f,
5195                    "invalid base64 alphabet byte 0x{byte:02x} at index {index}"
5196                )
5197            }
5198            Self::PaddingByte { index } => {
5199                write!(f, "base64 alphabet contains padding byte at index {index}")
5200            }
5201            Self::DuplicateByte {
5202                first,
5203                second,
5204                byte,
5205            } => write!(
5206                f,
5207                "base64 alphabet byte 0x{byte:02x} is duplicated at indexes {first} and {second}"
5208            ),
5209        }
5210    }
5211}
5212
5213#[cfg(feature = "std")]
5214impl std::error::Error for AlphabetError {}
5215
5216/// Decoding error.
5217#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5218pub enum DecodeError {
5219    /// The encoded input is malformed, but the decoder intentionally does not
5220    /// disclose a more specific error class.
5221    InvalidInput,
5222    /// The encoded input length is impossible for the selected padding policy.
5223    InvalidLength,
5224    /// A byte is not valid for the selected alphabet.
5225    InvalidByte {
5226        /// Byte index in the input.
5227        index: usize,
5228        /// Invalid byte value.
5229        byte: u8,
5230    },
5231    /// Padding is missing, misplaced, or non-canonical.
5232    InvalidPadding {
5233        /// Byte index where padding became invalid.
5234        index: usize,
5235    },
5236    /// Line wrapping is missing, misplaced, or uses the wrong line ending.
5237    InvalidLineWrap {
5238        /// Byte index where line wrapping became invalid.
5239        index: usize,
5240    },
5241    /// The output buffer is too small.
5242    OutputTooSmall {
5243        /// Required output bytes.
5244        required: usize,
5245        /// Available output bytes.
5246        available: usize,
5247    },
5248    /// The caller-provided constant-time staging buffer is too small.
5249    StagingTooSmall {
5250        /// Required staging bytes.
5251        required: usize,
5252        /// Available staging bytes.
5253        available: usize,
5254    },
5255}
5256
5257impl core::fmt::Display for DecodeError {
5258    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
5259        match self {
5260            Self::InvalidInput => f.write_str("malformed base64 input"),
5261            Self::InvalidLength => f.write_str("invalid base64 input length"),
5262            Self::InvalidByte { index, byte } => {
5263                write!(f, "invalid base64 byte 0x{byte:02x} at index {index}")
5264            }
5265            Self::InvalidPadding { index } => write!(f, "invalid base64 padding at index {index}"),
5266            Self::InvalidLineWrap { index } => {
5267                write!(f, "invalid base64 line wrapping at index {index}")
5268            }
5269            Self::OutputTooSmall {
5270                required,
5271                available,
5272            } => write!(
5273                f,
5274                "base64 decode output buffer too small: required {required}, available {available}"
5275            ),
5276            Self::StagingTooSmall {
5277                required,
5278                available,
5279            } => write!(
5280                f,
5281                "base64 decode staging buffer too small: required {required}, available {available}"
5282            ),
5283        }
5284    }
5285}
5286
5287impl DecodeError {
5288    fn with_index_offset(self, offset: usize) -> Self {
5289        match self {
5290            Self::InvalidByte { index, byte } => Self::InvalidByte {
5291                index: index + offset,
5292                byte,
5293            },
5294            Self::InvalidPadding { index } => Self::InvalidPadding {
5295                index: index + offset,
5296            },
5297            Self::InvalidLineWrap { index } => Self::InvalidLineWrap {
5298                index: index + offset,
5299            },
5300            Self::InvalidInput
5301            | Self::InvalidLength
5302            | Self::OutputTooSmall { .. }
5303            | Self::StagingTooSmall { .. } => self,
5304        }
5305    }
5306}
5307
5308#[cfg(feature = "std")]
5309impl std::error::Error for DecodeError {}
5310
5311struct LegacyBytes<'a> {
5312    input: &'a [u8],
5313    index: usize,
5314}
5315
5316impl<'a> LegacyBytes<'a> {
5317    const fn new(input: &'a [u8]) -> Self {
5318        Self { input, index: 0 }
5319    }
5320
5321    fn next_byte(&mut self) -> Option<(usize, u8)> {
5322        while self.index < self.input.len() {
5323            let index = self.index;
5324            let byte = self.input[index];
5325            self.index += 1;
5326            if !is_legacy_whitespace(byte) {
5327                return Some((index, byte));
5328            }
5329        }
5330        None
5331    }
5332}
5333
5334fn validate_legacy_decode<A: Alphabet, const PAD: bool>(
5335    input: &[u8],
5336) -> Result<usize, DecodeError> {
5337    let mut bytes = LegacyBytes::new(input);
5338    let mut chunk = [0u8; 4];
5339    let mut indexes = [0usize; 4];
5340    let mut chunk_len = 0;
5341    let mut required = 0;
5342    let mut terminal_seen = false;
5343
5344    while let Some((index, byte)) = bytes.next_byte() {
5345        if terminal_seen {
5346            return Err(DecodeError::InvalidPadding { index });
5347        }
5348
5349        chunk[chunk_len] = byte;
5350        indexes[chunk_len] = index;
5351        chunk_len += 1;
5352
5353        if chunk_len == 4 {
5354            let written =
5355                validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
5356            required += written;
5357            terminal_seen = written < 3;
5358            chunk_len = 0;
5359        }
5360    }
5361
5362    if chunk_len == 0 {
5363        return Ok(required);
5364    }
5365    if PAD {
5366        return Err(DecodeError::InvalidLength);
5367    }
5368
5369    validate_tail_unpadded::<A>(&chunk[..chunk_len])
5370        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
5371    Ok(required + decoded_capacity(chunk_len))
5372}
5373
5374fn decode_legacy_to_slice<A: Alphabet, const PAD: bool>(
5375    input: &[u8],
5376    output: &mut [u8],
5377) -> Result<usize, DecodeError> {
5378    let mut bytes = LegacyBytes::new(input);
5379    let mut chunk = [0u8; 4];
5380    let mut indexes = [0usize; 4];
5381    let mut chunk_len = 0;
5382    let mut write = 0;
5383    let mut terminal_seen = false;
5384
5385    while let Some((index, byte)) = bytes.next_byte() {
5386        if terminal_seen {
5387            return Err(DecodeError::InvalidPadding { index });
5388        }
5389
5390        chunk[chunk_len] = byte;
5391        indexes[chunk_len] = index;
5392        chunk_len += 1;
5393
5394        if chunk_len == 4 {
5395            let available = output.len();
5396            let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
5397                required: write,
5398                available,
5399            })?;
5400            let written = decode_chunk::<A, PAD>(chunk, output_tail)
5401                .map_err(|err| map_chunk_error(err, &indexes))?;
5402            write += written;
5403            terminal_seen = written < 3;
5404            chunk_len = 0;
5405        }
5406    }
5407
5408    if chunk_len == 0 {
5409        return Ok(write);
5410    }
5411    if PAD {
5412        return Err(DecodeError::InvalidLength);
5413    }
5414
5415    decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
5416        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
5417        .map(|n| write + n)
5418}
5419
5420struct WrappedBytes<'a> {
5421    input: &'a [u8],
5422    wrap: LineWrap,
5423    index: usize,
5424    line_len: usize,
5425}
5426
5427impl<'a> WrappedBytes<'a> {
5428    const fn new(input: &'a [u8], wrap: LineWrap) -> Result<Self, DecodeError> {
5429        if wrap.line_len == 0 {
5430            return Err(DecodeError::InvalidLineWrap { index: 0 });
5431        }
5432        Ok(Self {
5433            input,
5434            wrap,
5435            index: 0,
5436            line_len: 0,
5437        })
5438    }
5439
5440    fn next_byte(&mut self) -> Result<Option<(usize, u8)>, DecodeError> {
5441        loop {
5442            if self.index == self.input.len() {
5443                return Ok(None);
5444            }
5445
5446            if self.starts_with_line_ending() {
5447                let line_end_index = self.index;
5448                if self.line_len == 0 {
5449                    return Err(DecodeError::InvalidLineWrap {
5450                        index: line_end_index,
5451                    });
5452                }
5453
5454                self.index += self.wrap.line_ending.byte_len();
5455                if self.index == self.input.len() {
5456                    self.line_len = 0;
5457                    return Ok(None);
5458                }
5459
5460                if self.line_len != self.wrap.line_len {
5461                    return Err(DecodeError::InvalidLineWrap {
5462                        index: line_end_index,
5463                    });
5464                }
5465                self.line_len = 0;
5466                continue;
5467            }
5468
5469            let byte = self.input[self.index];
5470            if matches!(byte, b'\r' | b'\n') {
5471                return Err(DecodeError::InvalidLineWrap { index: self.index });
5472            }
5473
5474            self.line_len += 1;
5475            if self.line_len > self.wrap.line_len {
5476                return Err(DecodeError::InvalidLineWrap { index: self.index });
5477            }
5478
5479            let index = self.index;
5480            self.index += 1;
5481            return Ok(Some((index, byte)));
5482        }
5483    }
5484
5485    fn starts_with_line_ending(&self) -> bool {
5486        let line_ending = self.wrap.line_ending.as_bytes();
5487        let Some(end) = self.index.checked_add(line_ending.len()) else {
5488            return false;
5489        };
5490        end <= self.input.len() && &self.input[self.index..end] == line_ending
5491    }
5492}
5493
5494fn validate_wrapped_decode<A: Alphabet, const PAD: bool>(
5495    input: &[u8],
5496    wrap: LineWrap,
5497) -> Result<usize, DecodeError> {
5498    let mut bytes = WrappedBytes::new(input, wrap)?;
5499    let mut chunk = [0u8; 4];
5500    let mut indexes = [0usize; 4];
5501    let mut chunk_len = 0;
5502    let mut required = 0;
5503    let mut terminal_seen = false;
5504
5505    while let Some((index, byte)) = bytes.next_byte()? {
5506        if terminal_seen {
5507            return Err(DecodeError::InvalidPadding { index });
5508        }
5509
5510        chunk[chunk_len] = byte;
5511        indexes[chunk_len] = index;
5512        chunk_len += 1;
5513
5514        if chunk_len == 4 {
5515            let written =
5516                validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
5517            required += written;
5518            terminal_seen = written < 3;
5519            chunk_len = 0;
5520        }
5521    }
5522
5523    if chunk_len == 0 {
5524        return Ok(required);
5525    }
5526    if PAD {
5527        return Err(DecodeError::InvalidLength);
5528    }
5529
5530    validate_tail_unpadded::<A>(&chunk[..chunk_len])
5531        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
5532    Ok(required + decoded_capacity(chunk_len))
5533}
5534
5535fn decode_wrapped_to_slice<A: Alphabet, const PAD: bool>(
5536    input: &[u8],
5537    output: &mut [u8],
5538    wrap: LineWrap,
5539) -> Result<usize, DecodeError> {
5540    let mut bytes = WrappedBytes::new(input, wrap)?;
5541    let mut chunk = [0u8; 4];
5542    let mut indexes = [0usize; 4];
5543    let mut chunk_len = 0;
5544    let mut write = 0;
5545    let mut terminal_seen = false;
5546
5547    while let Some((index, byte)) = bytes.next_byte()? {
5548        if terminal_seen {
5549            return Err(DecodeError::InvalidPadding { index });
5550        }
5551
5552        chunk[chunk_len] = byte;
5553        indexes[chunk_len] = index;
5554        chunk_len += 1;
5555
5556        if chunk_len == 4 {
5557            let available = output.len();
5558            let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
5559                required: write,
5560                available,
5561            })?;
5562            let written = decode_chunk::<A, PAD>(chunk, output_tail)
5563                .map_err(|err| map_chunk_error(err, &indexes))?;
5564            write += written;
5565            terminal_seen = written < 3;
5566            chunk_len = 0;
5567        }
5568    }
5569
5570    if chunk_len == 0 {
5571        return Ok(write);
5572    }
5573    if PAD {
5574        return Err(DecodeError::InvalidLength);
5575    }
5576
5577    decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
5578        .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
5579        .map(|n| write + n)
5580}
5581
5582fn compact_wrapped_input(buffer: &mut [u8], wrap: LineWrap) -> Result<usize, DecodeError> {
5583    if !wrap.is_valid() {
5584        return Err(DecodeError::InvalidLineWrap { index: 0 });
5585    }
5586
5587    let line_ending = wrap.line_ending.as_bytes();
5588    let line_ending_len = line_ending.len();
5589    let mut read = 0;
5590    let mut write = 0;
5591
5592    while read < buffer.len() {
5593        let line_end = read + line_ending_len;
5594        if buffer.get(read..line_end) == Some(line_ending) {
5595            read = line_end;
5596            continue;
5597        }
5598
5599        buffer[write] = buffer[read];
5600        write += 1;
5601        read += 1;
5602    }
5603
5604    Ok(write)
5605}
5606
5607#[inline]
5608const fn is_legacy_whitespace(byte: u8) -> bool {
5609    matches!(byte, b' ' | b'\t' | b'\r' | b'\n')
5610}
5611
5612fn map_chunk_error(err: DecodeError, indexes: &[usize; 4]) -> DecodeError {
5613    match err {
5614        DecodeError::InvalidByte { index, byte } => DecodeError::InvalidByte {
5615            index: indexes[index],
5616            byte,
5617        },
5618        DecodeError::InvalidPadding { index } => DecodeError::InvalidPadding {
5619            index: indexes[index],
5620        },
5621        DecodeError::InvalidInput
5622        | DecodeError::InvalidLineWrap { .. }
5623        | DecodeError::InvalidLength
5624        | DecodeError::OutputTooSmall { .. }
5625        | DecodeError::StagingTooSmall { .. } => err,
5626    }
5627}
5628
5629fn map_partial_chunk_error(err: DecodeError, indexes: &[usize; 4], len: usize) -> DecodeError {
5630    match err {
5631        DecodeError::InvalidByte { index, byte } if index < len => DecodeError::InvalidByte {
5632            index: indexes[index],
5633            byte,
5634        },
5635        DecodeError::InvalidPadding { index } if index < len => DecodeError::InvalidPadding {
5636            index: indexes[index],
5637        },
5638        DecodeError::InvalidByte { .. }
5639        | DecodeError::InvalidPadding { .. }
5640        | DecodeError::InvalidLineWrap { .. }
5641        | DecodeError::InvalidInput
5642        | DecodeError::InvalidLength
5643        | DecodeError::OutputTooSmall { .. }
5644        | DecodeError::StagingTooSmall { .. } => err,
5645    }
5646}
5647
5648fn decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
5649    if !input.len().is_multiple_of(4) {
5650        return Err(DecodeError::InvalidLength);
5651    }
5652    let required = decoded_len_padded(input)?;
5653    if output.len() < required {
5654        return Err(DecodeError::OutputTooSmall {
5655            required,
5656            available: output.len(),
5657        });
5658    }
5659
5660    let mut read = 0;
5661    let mut write = 0;
5662    while read < input.len() {
5663        let chunk = read_quad(input, read)?;
5664        let available = output.len();
5665        let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
5666            required: write,
5667            available,
5668        })?;
5669        let written = decode_chunk::<A, true>(chunk, output_tail)
5670            .map_err(|err| err.with_index_offset(read))?;
5671        read += 4;
5672        write += written;
5673        if written < 3 && read != input.len() {
5674            return Err(DecodeError::InvalidPadding { index: read - 4 });
5675        }
5676    }
5677    Ok(write)
5678}
5679
5680fn validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
5681    if input.is_empty() {
5682        return Ok(0);
5683    }
5684
5685    if PAD {
5686        validate_padded::<A>(input)
5687    } else {
5688        validate_unpadded::<A>(input)
5689    }
5690}
5691
5692fn validate_padded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
5693    if !input.len().is_multiple_of(4) {
5694        return Err(DecodeError::InvalidLength);
5695    }
5696    let required = decoded_len_padded(input)?;
5697
5698    let mut read = 0;
5699    while read < input.len() {
5700        let chunk = read_quad(input, read)?;
5701        let written =
5702            validate_chunk::<A, true>(chunk).map_err(|err| err.with_index_offset(read))?;
5703        read += 4;
5704        if written < 3 && read != input.len() {
5705            return Err(DecodeError::InvalidPadding { index: read - 4 });
5706        }
5707    }
5708
5709    Ok(required)
5710}
5711
5712fn validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
5713    let required = decoded_len_unpadded(input)?;
5714
5715    let mut read = 0;
5716    while read + 4 <= input.len() {
5717        let chunk = read_quad(input, read)?;
5718        validate_chunk::<A, false>(chunk).map_err(|err| err.with_index_offset(read))?;
5719        read += 4;
5720    }
5721    validate_tail_unpadded::<A>(&input[read..]).map_err(|err| err.with_index_offset(read))?;
5722
5723    Ok(required)
5724}
5725
5726fn decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
5727    let required = decoded_len_unpadded(input)?;
5728    if output.len() < required {
5729        return Err(DecodeError::OutputTooSmall {
5730            required,
5731            available: output.len(),
5732        });
5733    }
5734
5735    let mut read = 0;
5736    let mut write = 0;
5737    while read + 4 <= input.len() {
5738        let chunk = read_quad(input, read)?;
5739        let available = output.len();
5740        let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
5741            required: write,
5742            available,
5743        })?;
5744        let written = decode_chunk::<A, false>(chunk, output_tail)
5745            .map_err(|err| err.with_index_offset(read))?;
5746        read += 4;
5747        write += written;
5748    }
5749    decode_tail_unpadded::<A>(&input[read..], &mut output[write..])
5750        .map_err(|err| err.with_index_offset(read))
5751        .map(|n| write + n)
5752}
5753
5754fn decoded_len_padded(input: &[u8]) -> Result<usize, DecodeError> {
5755    if input.is_empty() {
5756        return Ok(0);
5757    }
5758    if !input.len().is_multiple_of(4) {
5759        return Err(DecodeError::InvalidLength);
5760    }
5761
5762    let Some((&last, before_last_prefix)) = input.split_last() else {
5763        return Ok(0);
5764    };
5765    let Some(&before_last) = before_last_prefix.last() else {
5766        return Err(DecodeError::InvalidLength);
5767    };
5768
5769    let mut padding = 0;
5770    if last == b'=' {
5771        padding += 1;
5772    }
5773    if before_last == b'=' {
5774        padding += 1;
5775    }
5776    if padding == 0
5777        && let Some(index) = input.iter().position(|byte| *byte == b'=')
5778    {
5779        return Err(DecodeError::InvalidPadding { index });
5780    }
5781    if padding > 0 {
5782        let first_pad = input.len() - padding;
5783        if let Some(index) = input[..first_pad].iter().position(|byte| *byte == b'=') {
5784            return Err(DecodeError::InvalidPadding { index });
5785        }
5786    }
5787    Ok(input.len() / 4 * 3 - padding)
5788}
5789
5790fn decoded_len_unpadded(input: &[u8]) -> Result<usize, DecodeError> {
5791    if input.len() % 4 == 1 {
5792        return Err(DecodeError::InvalidLength);
5793    }
5794    if let Some(index) = input.iter().position(|byte| *byte == b'=') {
5795        return Err(DecodeError::InvalidPadding { index });
5796    }
5797    Ok(decoded_capacity(input.len()))
5798}
5799
5800fn read_quad(input: &[u8], offset: usize) -> Result<[u8; 4], DecodeError> {
5801    let end = offset.checked_add(4).ok_or(DecodeError::InvalidLength)?;
5802    match input.get(offset..end) {
5803        Some([b0, b1, b2, b3]) => Ok([*b0, *b1, *b2, *b3]),
5804        _ => Err(DecodeError::InvalidLength),
5805    }
5806}
5807
5808fn first_padding_index_unchecked(input: [u8; 4]) -> usize {
5809    let [b0, b1, b2, b3] = input;
5810    if b0 == b'=' {
5811        0
5812    } else if b1 == b'=' {
5813        1
5814    } else if b2 == b'=' {
5815        2
5816    } else if b3 == b'=' {
5817        3
5818    } else {
5819        debug_assert!(
5820            false,
5821            "first_padding_index_unchecked called with no padding"
5822        );
5823        4
5824    }
5825}
5826
5827fn validate_chunk<A: Alphabet, const PAD: bool>(input: [u8; 4]) -> Result<usize, DecodeError> {
5828    let [b0, b1, b2, b3] = input;
5829    let _v0 = decode_byte::<A>(b0, 0)?;
5830    let v1 = decode_byte::<A>(b1, 1)?;
5831
5832    match (b2, b3) {
5833        (b'=', b'=') if PAD => {
5834            if v1 & 0b0000_1111 != 0 {
5835                return Err(DecodeError::InvalidPadding { index: 1 });
5836            }
5837            Ok(1)
5838        }
5839        (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
5840        (_, b'=') if PAD => {
5841            let v2 = decode_byte::<A>(b2, 2)?;
5842            if v2 & 0b0000_0011 != 0 {
5843                return Err(DecodeError::InvalidPadding { index: 2 });
5844            }
5845            Ok(2)
5846        }
5847        (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
5848            index: first_padding_index_unchecked(input),
5849        }),
5850        _ => {
5851            decode_byte::<A>(b2, 2)?;
5852            decode_byte::<A>(b3, 3)?;
5853            Ok(3)
5854        }
5855    }
5856}
5857
5858fn decode_chunk<A: Alphabet, const PAD: bool>(
5859    input: [u8; 4],
5860    output: &mut [u8],
5861) -> Result<usize, DecodeError> {
5862    let [b0, b1, b2, b3] = input;
5863    let v0 = decode_byte::<A>(b0, 0)?;
5864    let v1 = decode_byte::<A>(b1, 1)?;
5865
5866    match (b2, b3) {
5867        (b'=', b'=') if PAD => {
5868            if output.is_empty() {
5869                return Err(DecodeError::OutputTooSmall {
5870                    required: 1,
5871                    available: output.len(),
5872                });
5873            }
5874            if v1 & 0b0000_1111 != 0 {
5875                return Err(DecodeError::InvalidPadding { index: 1 });
5876            }
5877            output[0] = (v0 << 2) | (v1 >> 4);
5878            Ok(1)
5879        }
5880        (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
5881        (_, b'=') if PAD => {
5882            if output.len() < 2 {
5883                return Err(DecodeError::OutputTooSmall {
5884                    required: 2,
5885                    available: output.len(),
5886                });
5887            }
5888            let v2 = decode_byte::<A>(b2, 2)?;
5889            if v2 & 0b0000_0011 != 0 {
5890                return Err(DecodeError::InvalidPadding { index: 2 });
5891            }
5892            output[0] = (v0 << 2) | (v1 >> 4);
5893            output[1] = (v1 << 4) | (v2 >> 2);
5894            Ok(2)
5895        }
5896        (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
5897            index: first_padding_index_unchecked(input),
5898        }),
5899        _ => {
5900            if output.len() < 3 {
5901                return Err(DecodeError::OutputTooSmall {
5902                    required: 3,
5903                    available: output.len(),
5904                });
5905            }
5906            let v2 = decode_byte::<A>(b2, 2)?;
5907            let v3 = decode_byte::<A>(b3, 3)?;
5908            output[0] = (v0 << 2) | (v1 >> 4);
5909            output[1] = (v1 << 4) | (v2 >> 2);
5910            output[2] = (v2 << 6) | v3;
5911            Ok(3)
5912        }
5913    }
5914}
5915
5916fn validate_tail_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
5917    match input {
5918        [] => Ok(()),
5919        [b0, b1] => {
5920            decode_byte::<A>(*b0, 0)?;
5921            let v1 = decode_byte::<A>(*b1, 1)?;
5922            if v1 & 0b0000_1111 != 0 {
5923                return Err(DecodeError::InvalidPadding { index: 1 });
5924            }
5925            Ok(())
5926        }
5927        [b0, b1, b2] => {
5928            decode_byte::<A>(*b0, 0)?;
5929            decode_byte::<A>(*b1, 1)?;
5930            let v2 = decode_byte::<A>(*b2, 2)?;
5931            if v2 & 0b0000_0011 != 0 {
5932                return Err(DecodeError::InvalidPadding { index: 2 });
5933            }
5934            Ok(())
5935        }
5936        _ => Err(DecodeError::InvalidLength),
5937    }
5938}
5939
5940fn decode_tail_unpadded<A: Alphabet>(
5941    input: &[u8],
5942    output: &mut [u8],
5943) -> Result<usize, DecodeError> {
5944    match input {
5945        [] => Ok(0),
5946        [b0, b1] => {
5947            let Some(out0) = output.first_mut() else {
5948                return Err(DecodeError::OutputTooSmall {
5949                    required: 1,
5950                    available: output.len(),
5951                });
5952            };
5953            let v0 = decode_byte::<A>(*b0, 0)?;
5954            let v1 = decode_byte::<A>(*b1, 1)?;
5955            if v1 & 0b0000_1111 != 0 {
5956                return Err(DecodeError::InvalidPadding { index: 1 });
5957            }
5958            *out0 = (v0 << 2) | (v1 >> 4);
5959            Ok(1)
5960        }
5961        [b0, b1, b2] => {
5962            let available = output.len();
5963            let Some([out0, out1]) = output.get_mut(..2) else {
5964                return Err(DecodeError::OutputTooSmall {
5965                    required: 2,
5966                    available,
5967                });
5968            };
5969            let v0 = decode_byte::<A>(*b0, 0)?;
5970            let v1 = decode_byte::<A>(*b1, 1)?;
5971            let v2 = decode_byte::<A>(*b2, 2)?;
5972            if v2 & 0b0000_0011 != 0 {
5973                return Err(DecodeError::InvalidPadding { index: 2 });
5974            }
5975            *out0 = (v0 << 2) | (v1 >> 4);
5976            *out1 = (v1 << 4) | (v2 >> 2);
5977            Ok(2)
5978        }
5979        _ => Err(DecodeError::InvalidLength),
5980    }
5981}
5982
5983fn decode_byte<A: Alphabet>(byte: u8, index: usize) -> Result<u8, DecodeError> {
5984    A::decode(byte).ok_or(DecodeError::InvalidByte { index, byte })
5985}
5986
5987fn ct_decode_slice<A: Alphabet, const PAD: bool>(
5988    input: &[u8],
5989    output: &mut [u8],
5990) -> Result<usize, DecodeError> {
5991    if input.is_empty() {
5992        return Ok(0);
5993    }
5994
5995    if PAD {
5996        ct_decode_padded::<A>(input, output)
5997    } else {
5998        ct_decode_unpadded::<A>(input, output)
5999    }
6000}
6001
6002fn ct_decode_slice_staged_clear_tail<A: Alphabet, const PAD: bool>(
6003    input: &[u8],
6004    output: &mut [u8],
6005    staging: &mut [u8],
6006) -> Result<usize, DecodeError> {
6007    let required = match ct_decoded_len::<A, PAD>(input) {
6008        Ok(required) => required,
6009        Err(err) => {
6010            wipe_bytes(output);
6011            wipe_bytes(staging);
6012            return Err(err);
6013        }
6014    };
6015
6016    if output.len() < required {
6017        wipe_bytes(output);
6018        wipe_bytes(staging);
6019        return Err(DecodeError::OutputTooSmall {
6020            required,
6021            available: output.len(),
6022        });
6023    }
6024
6025    if staging.len() < required {
6026        wipe_bytes(output);
6027        wipe_bytes(staging);
6028        return Err(DecodeError::StagingTooSmall {
6029            required,
6030            available: staging.len(),
6031        });
6032    }
6033
6034    let written = match ct_decode_slice::<A, PAD>(input, &mut staging[..required]) {
6035        Ok(written) => written,
6036        Err(err) => {
6037            wipe_bytes(output);
6038            wipe_bytes(staging);
6039            return Err(err);
6040        }
6041    };
6042
6043    output[..written].copy_from_slice(&staging[..written]);
6044    wipe_bytes(staging);
6045    wipe_tail(output, written);
6046    Ok(written)
6047}
6048
6049fn ct_decode_in_place<A: Alphabet, const PAD: bool>(
6050    buffer: &mut [u8],
6051) -> Result<usize, DecodeError> {
6052    if buffer.is_empty() {
6053        return Ok(0);
6054    }
6055
6056    if PAD {
6057        ct_decode_padded_in_place::<A>(buffer)
6058    } else {
6059        ct_decode_unpadded_in_place::<A>(buffer)
6060    }
6061}
6062
6063#[inline(never)]
6064#[allow(unsafe_code)]
6065fn ct_error_gate_barrier(invalid_byte: u8, invalid_padding: u8) {
6066    core::hint::black_box(invalid_byte | invalid_padding);
6067    core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
6068
6069    #[cfg(all(not(miri), any(target_arch = "x86", target_arch = "x86_64")))]
6070    {
6071        // SAFETY: `lfence` does not access memory and is used as a speculation
6072        // barrier before the public success/failure branch is observed.
6073        unsafe {
6074            core::arch::asm!("lfence", options(nostack, preserves_flags, nomem));
6075        }
6076    }
6077
6078    #[cfg(all(not(miri), target_arch = "aarch64"))]
6079    {
6080        // SAFETY: these barriers do not access memory. `isb sy` is a
6081        // best-effort speculation boundary and `hint #20` is the architectural
6082        // CSDB hint encoding on AArch64.
6083        unsafe {
6084            core::arch::asm!("isb sy", "hint #20", options(nostack, preserves_flags));
6085        }
6086    }
6087
6088    #[cfg(all(not(miri), target_arch = "arm"))]
6089    {
6090        // SAFETY: `isb sy` does not access memory and is used as the best
6091        // available stable ARM speculation boundary for this crate.
6092        unsafe {
6093            core::arch::asm!("isb sy", options(nostack, preserves_flags));
6094        }
6095    }
6096
6097    #[cfg(all(not(miri), any(target_arch = "riscv32", target_arch = "riscv64")))]
6098    {
6099        // RISC-V base ISA does not provide a canonical speculation barrier.
6100        // `fence rw, rw` is the available ordering primitive for the CT public
6101        // result gate and is reported separately as `ordering-fence`.
6102        // SAFETY: the assembly block does not access memory.
6103        unsafe {
6104            core::arch::asm!("fence rw, rw", options(nostack, preserves_flags));
6105        }
6106    }
6107}
6108
6109fn ct_validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<(), DecodeError> {
6110    if input.is_empty() {
6111        return Ok(());
6112    }
6113
6114    if PAD {
6115        ct_validate_padded::<A>(input)
6116    } else {
6117        ct_validate_unpadded::<A>(input)
6118    }
6119}
6120
6121fn ct_decoded_len<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
6122    ct_validate_decode::<A, PAD>(input)?;
6123    if input.is_empty() {
6124        return Ok(0);
6125    }
6126
6127    if PAD {
6128        Ok(input.len() / 4 * 3 - ct_padding_len(input))
6129    } else {
6130        let full_quads = input.len() / 4 * 3;
6131        match input.len() % 4 {
6132            0 => Ok(full_quads),
6133            2 => Ok(full_quads + 1),
6134            3 => Ok(full_quads + 2),
6135            _ => Err(DecodeError::InvalidLength),
6136        }
6137    }
6138}
6139
6140fn ct_validate_padded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
6141    if !input.len().is_multiple_of(4) {
6142        return Err(DecodeError::InvalidLength);
6143    }
6144
6145    let padding = ct_padding_len(input);
6146    let mut invalid_byte = 0u8;
6147    let mut invalid_padding = 0u8;
6148    let mut read = 0;
6149
6150    while read + 4 < input.len() {
6151        let [b0, b1, b2, b3] =
6152            read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
6153        let (_, valid0) = ct_decode_alphabet_byte::<A>(b0);
6154        let (_, valid1) = ct_decode_alphabet_byte::<A>(b1);
6155        let (_, valid2) = ct_decode_alphabet_byte::<A>(b2);
6156        let (_, valid3) = ct_decode_alphabet_byte::<A>(b3);
6157
6158        invalid_byte |= !valid0;
6159        invalid_byte |= !valid1;
6160        invalid_byte |= !valid2;
6161        invalid_byte |= !valid3;
6162        invalid_padding |= ct_mask_eq_u8(b2, b'=');
6163        invalid_padding |= ct_mask_eq_u8(b3, b'=');
6164        read += 4;
6165    }
6166
6167    let final_chunk =
6168        read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
6169    let (_, final_invalid_byte, final_invalid_padding, _) =
6170        ct_padded_final_quantum::<A>(final_chunk, padding);
6171    invalid_byte |= final_invalid_byte;
6172    invalid_padding |= final_invalid_padding;
6173
6174    report_ct_error(invalid_byte, invalid_padding)
6175}
6176
6177fn ct_validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
6178    if input.len() % 4 == 1 {
6179        return Err(DecodeError::InvalidLength);
6180    }
6181
6182    let mut invalid_byte = 0u8;
6183    let mut invalid_padding = 0u8;
6184    let mut read = 0;
6185
6186    while read + 4 <= input.len() {
6187        let [b0, b1, b2, b3] =
6188            read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
6189        let (_, valid0) = ct_decode_alphabet_byte::<A>(b0);
6190        let (_, valid1) = ct_decode_alphabet_byte::<A>(b1);
6191        let (_, valid2) = ct_decode_alphabet_byte::<A>(b2);
6192        let (_, valid3) = ct_decode_alphabet_byte::<A>(b3);
6193
6194        invalid_byte |= !valid0;
6195        invalid_byte |= !valid1;
6196        invalid_byte |= !valid2;
6197        invalid_byte |= !valid3;
6198        invalid_padding |= ct_mask_eq_u8(b0, b'=');
6199        invalid_padding |= ct_mask_eq_u8(b1, b'=');
6200        invalid_padding |= ct_mask_eq_u8(b2, b'=');
6201        invalid_padding |= ct_mask_eq_u8(b3, b'=');
6202
6203        read += 4;
6204    }
6205
6206    match read_tail_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding) {
6207        [] => {}
6208        [b0, b1] => {
6209            let (_, valid0) = ct_decode_alphabet_byte::<A>(*b0);
6210            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
6211            invalid_byte |= !valid0;
6212            invalid_byte |= !valid1;
6213            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
6214            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
6215            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
6216        }
6217        [b0, b1, b2] => {
6218            let (_, valid0) = ct_decode_alphabet_byte::<A>(*b0);
6219            let (_, valid1) = ct_decode_alphabet_byte::<A>(*b1);
6220            let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
6221            invalid_byte |= !valid0;
6222            invalid_byte |= !valid1;
6223            invalid_byte |= !valid2;
6224            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
6225            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
6226            invalid_padding |= ct_mask_eq_u8(*b2, b'=');
6227            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
6228        }
6229        _ => {
6230            invalid_byte = 0xff;
6231            invalid_padding = 0xff;
6232        }
6233    }
6234
6235    report_ct_error(invalid_byte, invalid_padding)
6236}
6237
6238fn ct_padded_final_quantum<A: Alphabet>(
6239    input: [u8; 4],
6240    padding: usize,
6241) -> ([u8; 3], u8, u8, usize) {
6242    let [b0, b1, b2, b3] = input;
6243    let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
6244    let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
6245    let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
6246    let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
6247
6248    let padding_byte = match padding {
6249        0 => 0,
6250        1 => 1,
6251        2 => 2,
6252        _ => return ([0; 3], 0xff, 0xff, 0),
6253    };
6254    let no_padding = ct_mask_eq_u8(padding_byte, 0);
6255    let one_padding = ct_mask_eq_u8(padding_byte, 1);
6256    let two_padding = ct_mask_eq_u8(padding_byte, 2);
6257    let require_v2 = no_padding | one_padding;
6258    let require_v3 = no_padding;
6259
6260    let invalid_byte = !valid0 | !valid1 | (!valid2 & require_v2) | (!valid3 & require_v3);
6261    let invalid_padding = (ct_mask_nonzero_u8(v1 & 0b0000_1111) & two_padding)
6262        | ((ct_mask_eq_u8(b2, b'=') | ct_mask_nonzero_u8(v2 & 0b0000_0011)) & one_padding)
6263        | ((ct_mask_eq_u8(b2, b'=') | ct_mask_eq_u8(b3, b'=')) & no_padding);
6264
6265    (
6266        [(v0 << 2) | (v1 >> 4), (v1 << 4) | (v2 >> 2), (v2 << 6) | v3],
6267        invalid_byte,
6268        invalid_padding,
6269        3 - padding,
6270    )
6271}
6272
6273fn ct_decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
6274    if !input.len().is_multiple_of(4) {
6275        return Err(DecodeError::InvalidLength);
6276    }
6277
6278    let padding = ct_padding_len(input);
6279    let required = input.len() / 4 * 3 - padding;
6280    if output.len() < required {
6281        return Err(DecodeError::OutputTooSmall {
6282            required,
6283            available: output.len(),
6284        });
6285    }
6286
6287    let mut invalid_byte = 0u8;
6288    let mut invalid_padding = 0u8;
6289    let mut write = 0;
6290    let mut read = 0;
6291
6292    while read + 4 < input.len() {
6293        let [b0, b1, b2, b3] =
6294            read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
6295        let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
6296        let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
6297        let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
6298        let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
6299
6300        invalid_byte |= !valid0;
6301        invalid_byte |= !valid1;
6302        invalid_byte |= !valid2;
6303        invalid_byte |= !valid3;
6304        invalid_padding |= ct_mask_eq_u8(b2, b'=');
6305        invalid_padding |= ct_mask_eq_u8(b3, b'=');
6306        output[write] = (v0 << 2) | (v1 >> 4);
6307        output[write + 1] = (v1 << 4) | (v2 >> 2);
6308        output[write + 2] = (v2 << 6) | v3;
6309        write += 3;
6310        read += 4;
6311    }
6312
6313    let final_chunk =
6314        read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
6315    let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
6316        ct_padded_final_quantum::<A>(final_chunk, padding);
6317    invalid_byte |= final_invalid_byte;
6318    invalid_padding |= final_invalid_padding;
6319    output[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
6320    write += final_written;
6321
6322    report_ct_error(invalid_byte, invalid_padding)?;
6323    Ok(write)
6324}
6325
6326fn ct_decode_padded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
6327    if !buffer.len().is_multiple_of(4) {
6328        return Err(DecodeError::InvalidLength);
6329    }
6330
6331    let padding = ct_padding_len(buffer);
6332    let required = buffer.len() / 4 * 3 - padding;
6333    if required > buffer.len() {
6334        wipe_bytes(buffer);
6335        return Err(DecodeError::InvalidInput);
6336    }
6337
6338    let mut invalid_byte = 0u8;
6339    let mut invalid_padding = 0u8;
6340    let mut write = 0;
6341    let mut read = 0;
6342
6343    while read + 4 < buffer.len() {
6344        let [b0, b1, b2, b3] =
6345            read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
6346        let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
6347        let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
6348        let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
6349        let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
6350
6351        invalid_byte |= !valid0;
6352        invalid_byte |= !valid1;
6353        invalid_byte |= !valid2;
6354        invalid_byte |= !valid3;
6355        invalid_padding |= ct_mask_eq_u8(b2, b'=');
6356        invalid_padding |= ct_mask_eq_u8(b3, b'=');
6357        buffer[write] = (v0 << 2) | (v1 >> 4);
6358        buffer[write + 1] = (v1 << 4) | (v2 >> 2);
6359        buffer[write + 2] = (v2 << 6) | v3;
6360        write += 3;
6361        read += 4;
6362    }
6363
6364    let final_chunk =
6365        read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
6366    let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
6367        ct_padded_final_quantum::<A>(final_chunk, padding);
6368    invalid_byte |= final_invalid_byte;
6369    invalid_padding |= final_invalid_padding;
6370    buffer[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
6371    write += final_written;
6372
6373    if write != required {
6374        ct_error_gate_barrier(invalid_byte, invalid_padding);
6375        wipe_bytes(buffer);
6376        return Err(DecodeError::InvalidInput);
6377    }
6378    if let Err(err) = report_ct_error(invalid_byte, invalid_padding) {
6379        wipe_bytes(buffer);
6380        return Err(err);
6381    }
6382    Ok(write)
6383}
6384
6385fn ct_decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
6386    if input.len() % 4 == 1 {
6387        return Err(DecodeError::InvalidLength);
6388    }
6389
6390    let required = decoded_capacity(input.len());
6391    if output.len() < required {
6392        return Err(DecodeError::OutputTooSmall {
6393            required,
6394            available: output.len(),
6395        });
6396    }
6397
6398    let mut invalid_byte = 0u8;
6399    let mut invalid_padding = 0u8;
6400    let mut write = 0;
6401    let mut read = 0;
6402
6403    while read + 4 <= input.len() {
6404        let [b0, b1, b2, b3] =
6405            read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
6406        let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
6407        let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
6408        let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
6409        let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
6410
6411        invalid_byte |= !valid0;
6412        invalid_byte |= !valid1;
6413        invalid_byte |= !valid2;
6414        invalid_byte |= !valid3;
6415        invalid_padding |= ct_mask_eq_u8(b0, b'=');
6416        invalid_padding |= ct_mask_eq_u8(b1, b'=');
6417        invalid_padding |= ct_mask_eq_u8(b2, b'=');
6418        invalid_padding |= ct_mask_eq_u8(b3, b'=');
6419
6420        output[write] = (v0 << 2) | (v1 >> 4);
6421        output[write + 1] = (v1 << 4) | (v2 >> 2);
6422        output[write + 2] = (v2 << 6) | v3;
6423        read += 4;
6424        write += 3;
6425    }
6426
6427    match read_tail_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding) {
6428        [] => {}
6429        [b0, b1] => {
6430            let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
6431            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
6432            invalid_byte |= !valid0;
6433            invalid_byte |= !valid1;
6434            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
6435            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
6436            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
6437            output[write] = (v0 << 2) | (v1 >> 4);
6438            write += 1;
6439        }
6440        [b0, b1, b2] => {
6441            let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
6442            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
6443            let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
6444            invalid_byte |= !valid0;
6445            invalid_byte |= !valid1;
6446            invalid_byte |= !valid2;
6447            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
6448            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
6449            invalid_padding |= ct_mask_eq_u8(*b2, b'=');
6450            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
6451            output[write] = (v0 << 2) | (v1 >> 4);
6452            output[write + 1] = (v1 << 4) | (v2 >> 2);
6453            write += 2;
6454        }
6455        _ => {
6456            invalid_byte = 0xff;
6457            invalid_padding = 0xff;
6458        }
6459    }
6460
6461    report_ct_error(invalid_byte, invalid_padding)?;
6462    Ok(write)
6463}
6464
6465fn ct_decode_unpadded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
6466    if buffer.len() % 4 == 1 {
6467        return Err(DecodeError::InvalidLength);
6468    }
6469
6470    let required = decoded_capacity(buffer.len());
6471    if required > buffer.len() {
6472        wipe_bytes(buffer);
6473        return Err(DecodeError::InvalidInput);
6474    }
6475
6476    let mut invalid_byte = 0u8;
6477    let mut invalid_padding = 0u8;
6478    let mut write = 0;
6479    let mut read = 0;
6480
6481    while read + 4 <= buffer.len() {
6482        let [b0, b1, b2, b3] =
6483            read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
6484        let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
6485        let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
6486        let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
6487        let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
6488
6489        invalid_byte |= !valid0;
6490        invalid_byte |= !valid1;
6491        invalid_byte |= !valid2;
6492        invalid_byte |= !valid3;
6493        invalid_padding |= ct_mask_eq_u8(b0, b'=');
6494        invalid_padding |= ct_mask_eq_u8(b1, b'=');
6495        invalid_padding |= ct_mask_eq_u8(b2, b'=');
6496        invalid_padding |= ct_mask_eq_u8(b3, b'=');
6497
6498        buffer[write] = (v0 << 2) | (v1 >> 4);
6499        buffer[write + 1] = (v1 << 4) | (v2 >> 2);
6500        buffer[write + 2] = (v2 << 6) | v3;
6501        read += 4;
6502        write += 3;
6503    }
6504
6505    let tail = read_tail_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
6506    match tail {
6507        [] => {}
6508        [b0, b1] => {
6509            let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
6510            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
6511            invalid_byte |= !valid0;
6512            invalid_byte |= !valid1;
6513            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
6514            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
6515            invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
6516            buffer[write] = (v0 << 2) | (v1 >> 4);
6517            write += 1;
6518        }
6519        [b0, b1, b2] => {
6520            let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
6521            let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
6522            let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
6523            invalid_byte |= !valid0;
6524            invalid_byte |= !valid1;
6525            invalid_byte |= !valid2;
6526            invalid_padding |= ct_mask_eq_u8(*b0, b'=');
6527            invalid_padding |= ct_mask_eq_u8(*b1, b'=');
6528            invalid_padding |= ct_mask_eq_u8(*b2, b'=');
6529            invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
6530            buffer[write] = (v0 << 2) | (v1 >> 4);
6531            buffer[write + 1] = (v1 << 4) | (v2 >> 2);
6532            write += 2;
6533        }
6534        _ => {
6535            invalid_byte = 0xff;
6536            invalid_padding = 0xff;
6537        }
6538    }
6539
6540    if write != required {
6541        ct_error_gate_barrier(invalid_byte, invalid_padding);
6542        wipe_bytes(buffer);
6543        return Err(DecodeError::InvalidInput);
6544    }
6545    if let Err(err) = report_ct_error(invalid_byte, invalid_padding) {
6546        wipe_bytes(buffer);
6547        return Err(err);
6548    }
6549    Ok(write)
6550}
6551
6552fn read_tail(input: &[u8], offset: usize) -> Result<&[u8], DecodeError> {
6553    input.get(offset..).ok_or(DecodeError::InvalidLength)
6554}
6555
6556fn read_quad_or_mark_invalid(
6557    input: &[u8],
6558    offset: usize,
6559    invalid_byte: &mut u8,
6560    invalid_padding: &mut u8,
6561) -> [u8; 4] {
6562    if let Ok(quad) = read_quad(input, offset) {
6563        quad
6564    } else {
6565        debug_assert!(
6566            false,
6567            "read_quad failed inside length-validated constant-time decode loop"
6568        );
6569        *invalid_byte = 0xff;
6570        *invalid_padding = 0xff;
6571        [0; 4]
6572    }
6573}
6574
6575fn read_tail_or_mark_invalid<'a>(
6576    input: &'a [u8],
6577    offset: usize,
6578    invalid_byte: &mut u8,
6579    invalid_padding: &mut u8,
6580) -> &'a [u8] {
6581    if let Ok(tail) = read_tail(input, offset) {
6582        tail
6583    } else {
6584        debug_assert!(
6585            false,
6586            "read_tail failed inside length-validated constant-time decode loop"
6587        );
6588        *invalid_byte = 0xff;
6589        *invalid_padding = 0xff;
6590        &[]
6591    }
6592}
6593
6594#[inline(never)]
6595#[allow(unsafe_code)]
6596fn ct_decode_alphabet_byte<A: Alphabet>(byte: u8) -> (u8, u8) {
6597    let mut decoded = 0u8;
6598    let mut valid = 0u8;
6599    let mut candidate = 0u8;
6600
6601    while candidate < 64 {
6602        let matches = core::hint::black_box(ct_mask_eq_u8(
6603            core::hint::black_box(byte),
6604            core::hint::black_box(A::ENCODE[candidate as usize]),
6605        ));
6606        decoded = core::hint::black_box(
6607            core::hint::black_box(decoded) | core::hint::black_box(candidate & matches),
6608        );
6609        // SAFETY: `decoded` is an initialized local `u8`; the volatile read is
6610        // an optimizer barrier for the fixed 64-iteration alphabet scan and
6611        // does not access caller memory.
6612        decoded = unsafe { core::ptr::read_volatile(&raw const decoded) };
6613        valid =
6614            core::hint::black_box(core::hint::black_box(valid) | core::hint::black_box(matches));
6615        // SAFETY: `valid` is an initialized local `u8`; the volatile read is an
6616        // optimizer barrier for the fixed 64-iteration alphabet scan and does
6617        // not access caller memory.
6618        valid = unsafe { core::ptr::read_volatile(&raw const valid) };
6619        candidate += 1;
6620    }
6621
6622    (decoded, valid)
6623}
6624
6625fn ct_padding_len(input: &[u8]) -> usize {
6626    let Some((&last, before_last_prefix)) = input.split_last() else {
6627        return 0;
6628    };
6629    let Some(&before_last) = before_last_prefix.last() else {
6630        return 0;
6631    };
6632    usize::from(ct_mask_eq_u8(last, b'=') & 1) + usize::from(ct_mask_eq_u8(before_last, b'=') & 1)
6633}
6634
6635fn report_ct_error(invalid_byte: u8, invalid_padding: u8) -> Result<(), DecodeError> {
6636    ct_error_gate_barrier(invalid_byte, invalid_padding);
6637
6638    if (invalid_byte | invalid_padding) != 0 {
6639        Err(DecodeError::InvalidInput)
6640    } else {
6641        Ok(())
6642    }
6643}
6644
6645#[cfg(kani)]
6646mod kani_proofs {
6647    use super::{
6648        STANDARD, Standard, checked_encoded_len, ct, decode_byte, decode_chunk,
6649        decode_tail_unpadded, decoded_capacity, validate_tail_unpadded,
6650    };
6651
6652    #[kani::proof]
6653    fn checked_encoded_len_is_bounded_for_small_inputs() {
6654        let len = usize::from(kani::any::<u8>());
6655        let padded = kani::any::<bool>();
6656        let encoded = checked_encoded_len(len, padded).expect("u8 input length cannot overflow");
6657
6658        assert!(encoded >= len);
6659        assert!(encoded <= len / 3 * 4 + 4);
6660    }
6661
6662    #[kani::proof]
6663    fn decoded_capacity_is_bounded_for_small_inputs() {
6664        let len = usize::from(kani::any::<u8>());
6665        let capacity = decoded_capacity(len);
6666
6667        assert!(capacity <= len / 4 * 3 + 2);
6668    }
6669
6670    #[kani::proof]
6671    #[kani::unwind(3)]
6672    fn standard_in_place_decode_returns_prefix_within_buffer() {
6673        let mut buffer = kani::any::<[u8; 8]>();
6674        let result = STANDARD.decode_in_place(&mut buffer);
6675
6676        if let Ok(decoded) = result {
6677            assert!(decoded.len() <= 8);
6678        }
6679    }
6680
6681    #[kani::proof]
6682    #[kani::unwind(3)]
6683    fn standard_decode_slice_returns_written_within_output() {
6684        let input = kani::any::<[u8; 4]>();
6685        let mut output = kani::any::<[u8; 3]>();
6686        let result = STANDARD.decode_slice(&input, &mut output);
6687
6688        if let Ok(written) = result {
6689            assert!(written <= output.len());
6690        }
6691    }
6692
6693    #[kani::proof]
6694    #[kani::unwind(3)]
6695    fn standard_decode_chunk_returns_written_within_output() {
6696        let input = kani::any::<[u8; 4]>();
6697        let mut output = kani::any::<[u8; 3]>();
6698        let result = decode_chunk::<Standard, true>(input, &mut output);
6699
6700        if let Ok(written) = result {
6701            assert!(written <= output.len());
6702            assert!(written <= 3);
6703        }
6704    }
6705
6706    #[kani::proof]
6707    #[kani::unwind(3)]
6708    fn standard_decode_chunk_bit_packing_matches_decoded_values() {
6709        let input = kani::any::<[u8; 4]>();
6710        let mut output = kani::any::<[u8; 3]>();
6711        let result = decode_chunk::<Standard, true>(input, &mut output);
6712
6713        if let Ok(written) = result {
6714            let v0 = decode_byte::<Standard>(input[0], 0).expect("successful chunk has v0");
6715            let v1 = decode_byte::<Standard>(input[1], 1).expect("successful chunk has v1");
6716
6717            assert!(output[0] == ((v0 << 2) | (v1 >> 4)));
6718
6719            if written >= 2 {
6720                let v2 = decode_byte::<Standard>(input[2], 2).expect("successful chunk has v2");
6721                assert!(output[1] == ((v1 << 4) | (v2 >> 2)));
6722            }
6723
6724            if written == 3 {
6725                let v2 = decode_byte::<Standard>(input[2], 2).expect("successful chunk has v2");
6726                let v3 = decode_byte::<Standard>(input[3], 3).expect("successful chunk has v3");
6727                assert!(output[2] == ((v2 << 6) | v3));
6728            }
6729        }
6730    }
6731
6732    #[kani::proof]
6733    #[kani::unwind(3)]
6734    fn standard_validate_tail_unpadded_accepts_or_rejects_without_panic() {
6735        let input = kani::any::<[u8; 3]>();
6736        let len = usize::from(kani::any::<u8>() % 4);
6737        let result = validate_tail_unpadded::<Standard>(&input[..len]);
6738
6739        if result.is_ok() {
6740            assert!(len == 0 || len == 2 || len == 3);
6741        }
6742    }
6743
6744    #[kani::proof]
6745    #[kani::unwind(3)]
6746    fn standard_decode_two_byte_tail_returns_written_within_output() {
6747        let input = kani::any::<[u8; 2]>();
6748        let mut output = kani::any::<[u8; 1]>();
6749        let result = decode_tail_unpadded::<Standard>(&input, &mut output);
6750
6751        if let Ok(written) = result {
6752            assert!(written <= output.len());
6753            assert!(written == 1);
6754        }
6755    }
6756
6757    #[kani::proof]
6758    #[kani::unwind(3)]
6759    fn standard_decode_three_byte_tail_returns_written_within_output() {
6760        let input = kani::any::<[u8; 3]>();
6761        let mut output = kani::any::<[u8; 2]>();
6762        let result = decode_tail_unpadded::<Standard>(&input, &mut output);
6763
6764        if let Ok(written) = result {
6765            assert!(written <= output.len());
6766            assert!(written == 2);
6767        }
6768    }
6769
6770    #[kani::proof]
6771    #[kani::unwind(3)]
6772    fn standard_decode_slice_clear_tail_clears_output_on_error() {
6773        let input = kani::any::<[u8; 4]>();
6774        let mut output = kani::any::<[u8; 3]>();
6775        let result = STANDARD.decode_slice_clear_tail(&input, &mut output);
6776
6777        if result.is_err() {
6778            assert!(output.iter().all(|byte| *byte == 0));
6779        }
6780    }
6781
6782    #[kani::proof]
6783    #[kani::unwind(3)]
6784    fn standard_encode_slice_returns_written_within_output() {
6785        let input = kani::any::<[u8; 3]>();
6786        let mut output = kani::any::<[u8; 4]>();
6787        let result = STANDARD.encode_slice(&input, &mut output);
6788
6789        if let Ok(written) = result {
6790            assert!(written <= output.len());
6791        }
6792    }
6793
6794    #[kani::proof]
6795    #[kani::unwind(4)]
6796    fn standard_encode_in_place_returns_prefix_within_buffer() {
6797        let mut buffer = kani::any::<[u8; 8]>();
6798        let input_len = usize::from(kani::any::<u8>() % 9);
6799        let result = STANDARD.encode_in_place(&mut buffer, input_len);
6800
6801        if let Ok(encoded) = result {
6802            assert!(encoded.len() <= 8);
6803        }
6804    }
6805
6806    #[kani::proof]
6807    #[kani::unwind(3)]
6808    fn standard_clear_tail_decode_clears_buffer_on_error() {
6809        let mut buffer = kani::any::<[u8; 4]>();
6810        let result = STANDARD.decode_in_place_clear_tail(&mut buffer);
6811
6812        if result.is_err() {
6813            assert!(buffer.iter().all(|byte| *byte == 0));
6814        }
6815    }
6816
6817    #[kani::proof]
6818    #[kani::unwind(3)]
6819    fn ct_standard_decode_slice_returns_written_within_output() {
6820        let input = kani::any::<[u8; 4]>();
6821        let mut output = kani::any::<[u8; 3]>();
6822        let result = ct::STANDARD.decode_slice_clear_tail(&input, &mut output);
6823
6824        if let Ok(written) = result {
6825            assert!(written <= output.len());
6826        }
6827    }
6828
6829    #[kani::proof]
6830    #[kani::unwind(3)]
6831    fn ct_standard_decode_slice_clear_tail_clears_output_on_error() {
6832        let input = kani::any::<[u8; 4]>();
6833        let mut output = kani::any::<[u8; 3]>();
6834        let result = ct::STANDARD.decode_slice_clear_tail(&input, &mut output);
6835
6836        if result.is_err() {
6837            assert!(output.iter().all(|byte| *byte == 0));
6838        }
6839    }
6840
6841    #[kani::proof]
6842    #[kani::unwind(3)]
6843    fn ct_standard_decode_in_place_clear_tail_clears_buffer_on_error() {
6844        let mut buffer = kani::any::<[u8; 4]>();
6845        let result = ct::STANDARD.decode_in_place_clear_tail(&mut buffer);
6846
6847        if result.is_err() {
6848            assert!(buffer.iter().all(|byte| *byte == 0));
6849        }
6850    }
6851
6852    #[kani::proof]
6853    #[kani::unwind(3)]
6854    fn ct_standard_validate_matches_decode_for_one_quantum() {
6855        let input = kani::any::<[u8; 4]>();
6856        let mut output = kani::any::<[u8; 3]>();
6857
6858        let validate_ok = ct::STANDARD.validate_result(&input).is_ok();
6859        let decode_ok = ct::STANDARD
6860            .decode_slice_clear_tail(&input, &mut output)
6861            .is_ok();
6862
6863        assert!(validate_ok == decode_ok);
6864    }
6865}
6866
6867#[cfg(test)]
6868mod tests {
6869    use super::*;
6870
6871    fn fill_pattern(output: &mut [u8], seed: usize) {
6872        for (index, byte) in output.iter_mut().enumerate() {
6873            let value = (index * 73 + seed * 19) % 256;
6874            *byte = u8::try_from(value).unwrap();
6875        }
6876    }
6877
6878    fn assert_encode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
6879    where
6880        A: Alphabet,
6881    {
6882        let engine = Engine::<A, PAD>::new();
6883        let mut dispatched = [0x55; 256];
6884        let mut scalar = [0xaa; 256];
6885
6886        let dispatched_result = engine.encode_slice(input, &mut dispatched);
6887        let scalar_result = backend::scalar_reference_encode_slice::<A, PAD>(input, &mut scalar);
6888
6889        assert_eq!(dispatched_result, scalar_result);
6890        if let Ok(written) = dispatched_result {
6891            assert_eq!(&dispatched[..written], &scalar[..written]);
6892        }
6893
6894        let required = checked_encoded_len(input.len(), PAD).unwrap();
6895        if required > 0 {
6896            let mut dispatched_short = [0x55; 256];
6897            let mut scalar_short = [0xaa; 256];
6898            let available = required - 1;
6899
6900            assert_eq!(
6901                engine.encode_slice(input, &mut dispatched_short[..available]),
6902                backend::scalar_reference_encode_slice::<A, PAD>(
6903                    input,
6904                    &mut scalar_short[..available],
6905                )
6906            );
6907        }
6908    }
6909
6910    fn assert_decode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
6911    where
6912        A: Alphabet,
6913    {
6914        let engine = Engine::<A, PAD>::new();
6915        let mut dispatched = [0x55; 128];
6916        let mut scalar = [0xaa; 128];
6917
6918        let dispatched_result = engine.decode_slice(input, &mut dispatched);
6919        let scalar_result = backend::scalar_reference_decode_slice::<A, PAD>(input, &mut scalar);
6920
6921        assert_eq!(dispatched_result, scalar_result);
6922        if let Ok(written) = dispatched_result {
6923            assert_eq!(&dispatched[..written], &scalar[..written]);
6924
6925            if written > 0 {
6926                let mut dispatched_short = [0x55; 128];
6927                let mut scalar_short = [0xaa; 128];
6928                let available = written - 1;
6929
6930                assert_eq!(
6931                    engine.decode_slice(input, &mut dispatched_short[..available]),
6932                    backend::scalar_reference_decode_slice::<A, PAD>(
6933                        input,
6934                        &mut scalar_short[..available],
6935                    )
6936                );
6937            }
6938        }
6939    }
6940
6941    fn assert_backend_round_trip_matches_scalar<A, const PAD: bool>(input: &[u8])
6942    where
6943        A: Alphabet,
6944    {
6945        assert_encode_backend_matches_scalar::<A, PAD>(input);
6946
6947        let mut encoded = [0; 256];
6948        let encoded_len =
6949            backend::scalar_reference_encode_slice::<A, PAD>(input, &mut encoded).unwrap();
6950        assert_decode_backend_matches_scalar::<A, PAD>(&encoded[..encoded_len]);
6951    }
6952
6953    fn assert_standard_decode_chunk_matches_input(input: &[u8]) {
6954        let mut encoded = [0u8; 4];
6955        let encoded_len = STANDARD.encode_slice(input, &mut encoded).unwrap();
6956        assert_eq!(encoded_len, 4);
6957
6958        let chunk = [encoded[0], encoded[1], encoded[2], encoded[3]];
6959        let mut decoded = [0u8; 3];
6960        let decoded_len = decode_chunk::<Standard, true>(chunk, &mut decoded).unwrap();
6961
6962        assert_eq!(decoded_len, input.len());
6963        assert_eq!(&decoded[..decoded_len], input);
6964    }
6965
6966    #[test]
6967    fn backend_dispatch_matches_scalar_reference_for_canonical_inputs() {
6968        let mut input = [0; 128];
6969
6970        for input_len in 0..=input.len() {
6971            fill_pattern(&mut input[..input_len], input_len);
6972            let input = &input[..input_len];
6973
6974            assert_backend_round_trip_matches_scalar::<Standard, true>(input);
6975            assert_backend_round_trip_matches_scalar::<Standard, false>(input);
6976            assert_backend_round_trip_matches_scalar::<UrlSafe, true>(input);
6977            assert_backend_round_trip_matches_scalar::<UrlSafe, false>(input);
6978        }
6979    }
6980
6981    #[test]
6982    fn backend_dispatch_matches_scalar_reference_for_malformed_inputs() {
6983        for input in [
6984            &b"Z"[..],
6985            b"====",
6986            b"AA=A",
6987            b"Zh==",
6988            b"Zm9=",
6989            b"Zm9v$g==",
6990            b"Zm9vZh==",
6991        ] {
6992            assert_decode_backend_matches_scalar::<Standard, true>(input);
6993        }
6994
6995        for input in [&b"Z"[..], b"AA=A", b"Zh", b"Zm9", b"Zm9vYg$"] {
6996            assert_decode_backend_matches_scalar::<Standard, false>(input);
6997        }
6998
6999        assert_decode_backend_matches_scalar::<UrlSafe, true>(b"AA+A");
7000        assert_decode_backend_matches_scalar::<UrlSafe, false>(b"AA/A");
7001        assert_decode_backend_matches_scalar::<Standard, true>(b"AA-A");
7002        assert_decode_backend_matches_scalar::<Standard, false>(b"AA_A");
7003    }
7004
7005    #[test]
7006    fn decode_chunk_bit_packing_matches_exhaustive_small_inputs() {
7007        for byte in u8::MIN..=u8::MAX {
7008            assert_standard_decode_chunk_matches_input(&[byte]);
7009        }
7010
7011        for first in u8::MIN..=u8::MAX {
7012            for second in u8::MIN..=u8::MAX {
7013                assert_standard_decode_chunk_matches_input(&[first, second]);
7014            }
7015        }
7016    }
7017
7018    #[test]
7019    fn decode_chunk_bit_packing_matches_representative_full_quanta() {
7020        const SAMPLES: [u8; 16] = [
7021            0, 1, 2, 15, 16, 31, 32, 63, 64, 95, 127, 128, 191, 192, 254, 255,
7022        ];
7023
7024        for first in SAMPLES {
7025            for second in SAMPLES {
7026                for third in SAMPLES {
7027                    assert_standard_decode_chunk_matches_input(&[first, second, third]);
7028                }
7029            }
7030        }
7031    }
7032
7033    #[test]
7034    fn ct_padded_final_quantum_fails_closed_for_invalid_padding_count() {
7035        let (_, invalid_byte, invalid_padding, written) =
7036            ct_padded_final_quantum::<Standard>(*b"ABCD", 3);
7037
7038        assert_ne!(invalid_byte, 0);
7039        assert_ne!(invalid_padding, 0);
7040        assert_eq!(written, 0);
7041        assert_eq!(
7042            report_ct_error(invalid_byte, invalid_padding),
7043            Err(DecodeError::InvalidInput)
7044        );
7045    }
7046
7047    #[cfg(feature = "simd")]
7048    #[test]
7049    fn simd_dispatch_scaffold_keeps_scalar_active() {
7050        assert_eq!(simd::active_backend(), simd::ActiveBackend::Scalar);
7051        let _candidate = simd::detected_candidate();
7052    }
7053
7054    #[test]
7055    fn encodes_standard_vectors() {
7056        let vectors = [
7057            (&b""[..], &b""[..]),
7058            (&b"f"[..], &b"Zg=="[..]),
7059            (&b"fo"[..], &b"Zm8="[..]),
7060            (&b"foo"[..], &b"Zm9v"[..]),
7061            (&b"foob"[..], &b"Zm9vYg=="[..]),
7062            (&b"fooba"[..], &b"Zm9vYmE="[..]),
7063            (&b"foobar"[..], &b"Zm9vYmFy"[..]),
7064        ];
7065        for (input, expected) in vectors {
7066            let mut output = [0u8; 16];
7067            let written = STANDARD.encode_slice(input, &mut output).unwrap();
7068            assert_eq!(&output[..written], expected);
7069        }
7070    }
7071
7072    #[test]
7073    fn decodes_standard_vectors() {
7074        let vectors = [
7075            (&b""[..], &b""[..]),
7076            (&b"Zg=="[..], &b"f"[..]),
7077            (&b"Zm8="[..], &b"fo"[..]),
7078            (&b"Zm9v"[..], &b"foo"[..]),
7079            (&b"Zm9vYg=="[..], &b"foob"[..]),
7080            (&b"Zm9vYmE="[..], &b"fooba"[..]),
7081            (&b"Zm9vYmFy"[..], &b"foobar"[..]),
7082        ];
7083        for (input, expected) in vectors {
7084            let mut output = [0u8; 16];
7085            let written = STANDARD.decode_slice(input, &mut output).unwrap();
7086            assert_eq!(&output[..written], expected);
7087        }
7088    }
7089
7090    #[test]
7091    fn supports_unpadded_url_safe() {
7092        let mut encoded = [0u8; 16];
7093        let written = URL_SAFE_NO_PAD
7094            .encode_slice(b"\xfb\xff", &mut encoded)
7095            .unwrap();
7096        assert_eq!(&encoded[..written], b"-_8");
7097
7098        let mut decoded = [0u8; 2];
7099        let written = URL_SAFE_NO_PAD
7100            .decode_slice(&encoded[..written], &mut decoded)
7101            .unwrap();
7102        assert_eq!(&decoded[..written], b"\xfb\xff");
7103    }
7104
7105    #[test]
7106    fn decodes_in_place() {
7107        let mut buffer = *b"Zm9vYmFy";
7108        let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
7109        assert_eq!(decoded, b"foobar");
7110    }
7111
7112    #[test]
7113    fn rejects_non_canonical_padding_bits() {
7114        let mut output = [0u8; 4];
7115        assert_eq!(
7116            STANDARD.decode_slice(b"Zh==", &mut output),
7117            Err(DecodeError::InvalidPadding { index: 1 })
7118        );
7119        assert_eq!(
7120            STANDARD.decode_slice(b"Zm9=", &mut output),
7121            Err(DecodeError::InvalidPadding { index: 2 })
7122        );
7123    }
7124}