Skip to main content

base64_ng/runtime/
report.rs

1use super::{
2    Backend, BackendHealthPosture, BackendPolicy, CandidateDetectionMode, CtGatePosture,
3    MemoryLockPosture, OperationBackendReport, OperationKind, OperationSecurityPosture,
4    SecurityPosture, WasmArtifactPosture, WasmRuntimePosture, WipePosture,
5    operation::{wasm_artifact_posture, wasm_runtime_posture},
6};
7/// Runtime backend policy failure.
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub struct BackendPolicyError {
10    /// Policy that was requested.
11    pub policy: BackendPolicy,
12    /// Backend report observed when the policy failed.
13    pub report: BackendReport,
14}
15impl core::fmt::Display for BackendPolicyError {
16    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
17        write!(
18            formatter,
19            "runtime backend policy `{}` was not satisfied ({})",
20            self.policy, self.report,
21        )
22    }
23}
24
25#[cfg(feature = "std")]
26impl std::error::Error for BackendPolicyError {}
27/// Backend report for the current build and target.
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29#[allow(clippy::struct_excessive_bools)]
30pub struct BackendReport {
31    /// Compatibility alias for ordinary encode. New code should use
32    /// [`Self::encode_backend`] and inspect the separate decode reports.
33    pub active: Backend,
34    /// Whether ordinary encode is accelerated; this does not describe strict
35    /// decode. New code should inspect the operation-specific reports.
36    pub accelerated_backend_active: bool,
37    /// Compatibility posture for ordinary encode and its candidate. New code
38    /// should use the operation-specific security postures.
39    pub security_posture: SecurityPosture,
40    /// Strongest healthy ordinary encode backend for qualifying inputs.
41    ///
42    /// Per-call length and alphabet policy can select a narrower backend or
43    /// scalar fallback without changing this capability-oriented report.
44    pub encode_backend: OperationBackendReport,
45    /// Selected ordinary strict-decode backend.
46    pub strict_decode_backend: OperationBackendReport,
47    /// Secret decode backend, independently fixed to the scalar
48    /// constant-time-oriented boundary.
49    pub secret_decode_backend: OperationBackendReport,
50    /// Strongest backend candidate visible to the current build.
51    pub candidate: Backend,
52    /// Whether candidate visibility came from runtime CPU probing,
53    /// compile-time target features, or a disabled SIMD feature.
54    pub candidate_detection_mode: CandidateDetectionMode,
55    /// Whether the `simd` feature is enabled in this build.
56    pub simd_feature_enabled: bool,
57    /// Whether either ordinary operation selects acceleration.
58    pub ordinary_acceleration_active: bool,
59    /// Whether this build keeps the high-assurance scalar unsafe boundary.
60    ///
61    /// This is a conservative compile-time posture signal. It is `true`
62    /// only when the reserved `simd` feature is disabled; `simd` builds
63    /// expose additional private prototype boundaries and must use the
64    /// release evidence scripts for boundary validation.
65    pub unsafe_boundary_enforced: bool,
66    /// Wasm artifact selection, distinct from native runtime CPU dispatch.
67    pub wasm_artifact_posture: WasmArtifactPosture,
68    /// Wasm host-runtime identification posture.
69    pub wasm_runtime_posture: WasmRuntimePosture,
70    /// Current wipe-barrier posture.
71    pub wipe_posture: WipePosture,
72    /// Current constant-time result-gate barrier posture.
73    pub ct_gate_posture: CtGatePosture,
74}
75/// Compact structured backend snapshot for logging and policy evidence.
76#[derive(Clone, Copy, Debug, Eq, PartialEq)]
77#[allow(clippy::struct_excessive_bools)]
78pub struct BackendSnapshot {
79    /// Stable compatibility alias for the ordinary encode backend.
80    pub active: &'static str,
81    /// Compatibility alias reporting whether ordinary encode is accelerated.
82    pub accelerated_backend_active: bool,
83    /// Stable compatibility posture for ordinary encode and its candidate.
84    pub security_posture: &'static str,
85    /// Stable ordinary encode report.
86    pub encode_backend: super::OperationBackendSnapshot,
87    /// Stable ordinary strict-decode report.
88    pub strict_decode_backend: super::OperationBackendSnapshot,
89    /// Stable secret decode report.
90    pub secret_decode_backend: super::OperationBackendSnapshot,
91    /// Stable detected candidate identifier.
92    pub candidate: &'static str,
93    /// Stable SIMD candidate detection-mode identifier.
94    pub candidate_detection_mode: &'static str,
95    /// CPU features required by the detected candidate.
96    pub candidate_required_cpu_features: &'static [&'static str],
97    /// Whether the `simd` feature is enabled in this build.
98    pub simd_feature_enabled: bool,
99    /// Whether either ordinary operation selects acceleration.
100    pub ordinary_acceleration_active: bool,
101    /// Whether this build keeps the high-assurance scalar unsafe boundary.
102    ///
103    /// This is `false` for `simd` builds even while execution remains
104    /// scalar-only, because those builds include additional private
105    /// prototype boundaries.
106    pub unsafe_boundary_enforced: bool,
107    /// Stable Wasm artifact posture identifier.
108    pub wasm_artifact_posture: &'static str,
109    /// Stable Wasm host-runtime posture identifier.
110    pub wasm_runtime_posture: &'static str,
111    /// Stable wipe-barrier posture identifier.
112    pub wipe_posture: &'static str,
113    /// Stable constant-time result-gate barrier posture identifier.
114    pub ct_gate_posture: &'static str,
115}
116
117impl core::fmt::Display for BackendReport {
118    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
119        write!(
120            formatter,
121            "active={} accelerated_backend_active={} security_posture={} encode_backend={} strict_decode_backend={} secret_decode_backend={} candidate={} candidate_detection_mode={} candidate_required_cpu_features=",
122            self.active,
123            self.accelerated_backend_active,
124            self.security_posture,
125            self.encode_backend.backend,
126            self.strict_decode_backend.backend,
127            self.secret_decode_backend.backend,
128            self.candidate,
129            self.candidate_detection_mode,
130        )?;
131        write_feature_list(formatter, self.candidate_required_cpu_features())?;
132        write!(
133            formatter,
134            " simd_feature_enabled={} ordinary_acceleration_active={} unsafe_boundary_enforced={} wasm_artifact_posture={} wasm_runtime_posture={} wipe_posture={} ct_gate_posture={}",
135            self.simd_feature_enabled,
136            self.ordinary_acceleration_active,
137            self.unsafe_boundary_enforced,
138            self.wasm_artifact_posture.as_str(),
139            self.wasm_runtime_posture.as_str(),
140            self.wipe_posture,
141            self.ct_gate_posture,
142        )
143    }
144}
145
146impl BackendReport {
147    /// Returns whether this report satisfies `policy`.
148    ///
149    /// ```
150    /// let report = base64_ng::runtime::backend_report();
151    ///
152    /// let scalar_only =
153    ///     report.satisfies(base64_ng::runtime::BackendPolicy::ScalarExecutionOnly);
154    /// assert!(!scalar_only || !report.ordinary_acceleration_active);
155    /// ```
156    #[must_use]
157    pub const fn satisfies(self, policy: BackendPolicy) -> bool {
158        match policy {
159            BackendPolicy::ScalarExecutionOnly => {
160                stably_scalar(self.encode_backend)
161                    && stably_scalar(self.strict_decode_backend)
162                    && !self.ordinary_acceleration_active
163            }
164            BackendPolicy::SimdFeatureDisabled => !self.simd_feature_enabled,
165            BackendPolicy::NoDetectedSimdCandidate => matches!(self.candidate, Backend::Scalar),
166            BackendPolicy::HighAssuranceScalarOnly => {
167                matches!(
168                    self.encode_backend.security_posture,
169                    OperationSecurityPosture::OrdinaryScalar
170                ) && matches!(
171                    self.strict_decode_backend.security_posture,
172                    OperationSecurityPosture::OrdinaryScalar
173                ) && matches!(
174                    self.secret_decode_backend.security_posture,
175                    OperationSecurityPosture::ScalarConstantTimeOriented
176                ) && matches!(self.candidate, Backend::Scalar)
177                    && !self.simd_feature_enabled
178                    && !self.ordinary_acceleration_active
179                    && self.unsafe_boundary_enforced
180                    && matches!(
181                        self.ct_gate_posture,
182                        CtGatePosture::HardwareSpeculationBarrier
183                            | CtGatePosture::HardwareSpeculationBarrierBuildAsserted
184                    )
185            }
186        }
187    }
188
189    /// Returns the CPU features required by the detected candidate.
190    ///
191    /// ```
192    /// let report = base64_ng::runtime::backend_report();
193    ///
194    /// assert_eq!(
195    ///     report.candidate_required_cpu_features(),
196    ///     report.candidate.required_cpu_features(),
197    /// );
198    /// ```
199    #[must_use]
200    pub const fn candidate_required_cpu_features(self) -> &'static [&'static str] {
201        self.candidate.required_cpu_features()
202    }
203
204    /// Returns the typed backend selected by ordinary strict decode.
205    ///
206    /// Prefer [`Self::strict_decode_backend`] for stable logging.
207    #[must_use]
208    pub fn active_decode_backend(self) -> Backend {
209        let _ = self;
210        active_decode_backend()
211    }
212
213    /// Returns whether `base64-ng` itself locks secret buffers into physical
214    /// memory.
215    ///
216    /// This crate intentionally has no OS-specific `mlock`/`VirtualLock`
217    /// integration. High-assurance deployments should pair secret buffers with
218    /// their own platform-approved memory-locking, swap, hibernation, and
219    /// crash-dump controls.
220    #[must_use]
221    pub const fn memory_lock_posture(self) -> MemoryLockPosture {
222        let _ = self;
223        MemoryLockPosture::NotProvided
224    }
225
226    /// Returns a compact structured snapshot with stable string values.
227    ///
228    /// ```
229    /// let snapshot = base64_ng::runtime::backend_report().snapshot();
230    ///
231    /// assert_eq!(
232    ///     snapshot.ordinary_acceleration_active,
233    ///     snapshot.encode_backend.backend != "scalar"
234    ///         || snapshot.strict_decode_backend.backend != "scalar",
235    /// );
236    /// ```
237    #[must_use]
238    pub const fn snapshot(self) -> BackendSnapshot {
239        BackendSnapshot {
240            active: self.active.as_str(),
241            accelerated_backend_active: self.accelerated_backend_active,
242            security_posture: self.security_posture.as_str(),
243            encode_backend: self.encode_backend.snapshot(),
244            strict_decode_backend: self.strict_decode_backend.snapshot(),
245            secret_decode_backend: self.secret_decode_backend.snapshot(),
246            candidate: self.candidate.as_str(),
247            candidate_detection_mode: self.candidate_detection_mode.as_str(),
248            candidate_required_cpu_features: self.candidate_required_cpu_features(),
249            simd_feature_enabled: self.simd_feature_enabled,
250            ordinary_acceleration_active: self.ordinary_acceleration_active,
251            unsafe_boundary_enforced: self.unsafe_boundary_enforced,
252            wasm_artifact_posture: self.wasm_artifact_posture.as_str(),
253            wasm_runtime_posture: self.wasm_runtime_posture.as_str(),
254            wipe_posture: self.wipe_posture.as_str(),
255            ct_gate_posture: self.ct_gate_posture.as_str(),
256        }
257    }
258}
259
260const fn stably_scalar(report: OperationBackendReport) -> bool {
261    matches!(
262        report.security_posture,
263        OperationSecurityPosture::OrdinaryScalar
264    ) && matches!(
265        report.health_posture,
266        BackendHealthPosture::ScalarFixed
267            | BackendHealthPosture::Quarantined
268            | BackendHealthPosture::SynchronizationUnavailable
269    )
270}
271
272/// Returns the runtime backend report for this build and target.
273///
274/// ```
275/// let report = base64_ng::runtime::backend_report();
276///
277/// assert_eq!(
278///     report.secret_decode_backend.backend.as_str(),
279///     "scalar-constant-time-oriented",
280/// );
281/// ```
282#[must_use]
283pub fn backend_report() -> BackendReport {
284    let encode = active_backend();
285    let strict_decode = active_decode_backend();
286    let encode_candidate = encode_candidate_backend();
287    let decode_candidate = decode_candidate_backend();
288    let candidate = detected_candidate();
289    let candidate_detection_mode = candidate_detection_mode();
290    let accelerated_backend_active = encode != Backend::Scalar;
291    let ordinary_acceleration_active =
292        encode != Backend::Scalar || strict_decode != Backend::Scalar;
293    let unsafe_boundary_enforced = !cfg!(feature = "simd");
294    let security_posture = if accelerated_backend_active {
295        SecurityPosture::Accelerated
296    } else if candidate == Backend::Scalar {
297        SecurityPosture::ScalarOnly
298    } else {
299        SecurityPosture::SimdCandidateScalarActive
300    };
301
302    BackendReport {
303        active: encode,
304        accelerated_backend_active,
305        security_posture,
306        encode_backend: OperationBackendReport::ordinary(
307            OperationKind::Encode,
308            encode,
309            encode_candidate,
310        ),
311        strict_decode_backend: OperationBackendReport::ordinary(
312            OperationKind::StrictDecode,
313            strict_decode,
314            decode_candidate,
315        ),
316        secret_decode_backend: OperationBackendReport::secret_decode(),
317        candidate,
318        candidate_detection_mode,
319        simd_feature_enabled: cfg!(feature = "simd"),
320        ordinary_acceleration_active,
321        unsafe_boundary_enforced,
322        wasm_artifact_posture: wasm_artifact_posture(),
323        wasm_runtime_posture: wasm_runtime_posture(),
324        wipe_posture: wipe_posture(),
325        ct_gate_posture: ct_gate_posture(),
326    }
327}
328
329const fn wipe_posture() -> WipePosture {
330    if cfg!(any(
331        target_arch = "aarch64",
332        target_arch = "arm",
333        target_arch = "riscv32",
334        target_arch = "riscv64",
335        target_arch = "x86",
336        target_arch = "x86_64",
337    )) {
338        WipePosture::HardwareFence
339    } else {
340        WipePosture::CompilerFenceOnly
341    }
342}
343
344const fn ct_gate_posture() -> CtGatePosture {
345    if cfg!(any(target_arch = "x86", target_arch = "x86_64")) {
346        CtGatePosture::HardwareSpeculationBarrier
347    } else if cfg!(all(
348        target_arch = "aarch64",
349        base64_ng_aarch64_csdb_attested
350    )) {
351        CtGatePosture::HardwareSpeculationBarrierBuildAsserted
352    } else if cfg!(target_arch = "aarch64") {
353        CtGatePosture::HardwareSpeculationBarrierUnattested
354    } else if cfg!(any(
355        target_arch = "arm",
356        target_arch = "riscv32",
357        target_arch = "riscv64"
358    )) {
359        CtGatePosture::OrderingFence
360    } else {
361        CtGatePosture::CompilerFenceOnly
362    }
363}
364
365/// Requires the current runtime backend report to satisfy `policy`.
366///
367/// ```
368/// let result = base64_ng::runtime::require_backend_policy(
369///     base64_ng::runtime::BackendPolicy::ScalarExecutionOnly,
370/// );
371///
372/// if result.is_ok() {
373///     assert!(!base64_ng::runtime::backend_report().ordinary_acceleration_active);
374/// }
375/// ```
376pub fn require_backend_policy(policy: BackendPolicy) -> Result<(), BackendPolicyError> {
377    let report = backend_report();
378    if report.satisfies(policy) {
379        Ok(())
380    } else {
381        Err(BackendPolicyError { policy, report })
382    }
383}
384
385fn write_feature_list(
386    formatter: &mut core::fmt::Formatter<'_>,
387    features: &[&str],
388) -> core::fmt::Result {
389    formatter.write_str("[")?;
390    let mut index = 0;
391    while index < features.len() {
392        if index != 0 {
393            formatter.write_str(",")?;
394        }
395        formatter.write_str(features[index])?;
396        index += 1;
397    }
398    formatter.write_str("]")
399}
400
401#[cfg(feature = "simd")]
402fn active_backend() -> Backend {
403    crate::encode_backend::active_encode_backend().reported()
404}
405
406#[cfg(not(feature = "simd"))]
407const fn active_backend() -> Backend {
408    Backend::Scalar
409}
410
411#[cfg(feature = "simd")]
412fn active_decode_backend() -> Backend {
413    crate::decode_backend::active_decode_backend().reported()
414}
415
416#[cfg(not(feature = "simd"))]
417const fn active_decode_backend() -> Backend {
418    Backend::Scalar
419}
420
421#[cfg(feature = "simd")]
422fn encode_candidate_backend() -> Backend {
423    crate::encode_backend::candidate_encode_backend().reported()
424}
425
426#[cfg(not(feature = "simd"))]
427const fn encode_candidate_backend() -> Backend {
428    Backend::Scalar
429}
430
431#[cfg(feature = "simd")]
432fn decode_candidate_backend() -> Backend {
433    crate::decode_backend::candidate_decode_backend().reported()
434}
435
436#[cfg(not(feature = "simd"))]
437const fn decode_candidate_backend() -> Backend {
438    Backend::Scalar
439}
440
441#[cfg(feature = "simd")]
442fn detected_candidate() -> Backend {
443    match crate::simd::detected_candidate() {
444        crate::simd::Candidate::Scalar => Backend::Scalar,
445        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
446        crate::simd::Candidate::Avx512Vbmi => Backend::Avx512Vbmi,
447        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
448        crate::simd::Candidate::Avx2 => Backend::Avx2,
449        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
450        crate::simd::Candidate::Ssse3Sse41 => Backend::Ssse3Sse41,
451        #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
452        crate::simd::Candidate::Neon => Backend::Neon,
453        #[cfg(target_arch = "wasm32")]
454        crate::simd::Candidate::WasmSimd128 => Backend::WasmSimd128,
455        #[cfg(target_arch = "riscv64")]
456        crate::simd::Candidate::Rvv => Backend::Rvv,
457        #[cfg(all(target_arch = "aarch64", base64_ng_sve_candidate))]
458        crate::simd::Candidate::Sve => Backend::Sve,
459    }
460}
461
462#[cfg(not(feature = "simd"))]
463const fn detected_candidate() -> Backend {
464    Backend::Scalar
465}
466
467#[cfg(all(
468    feature = "simd",
469    feature = "std",
470    any(
471        target_arch = "x86",
472        target_arch = "x86_64",
473        all(target_arch = "aarch64", base64_ng_sve_candidate),
474        target_arch = "riscv64"
475    )
476))]
477const fn candidate_detection_mode() -> CandidateDetectionMode {
478    CandidateDetectionMode::RuntimeCpuFeatures
479}
480
481#[cfg(all(
482    feature = "simd",
483    not(all(
484        feature = "std",
485        any(
486            target_arch = "x86",
487            target_arch = "x86_64",
488            all(target_arch = "aarch64", base64_ng_sve_candidate),
489            target_arch = "riscv64"
490        )
491    ))
492))]
493const fn candidate_detection_mode() -> CandidateDetectionMode {
494    CandidateDetectionMode::CompileTimeTargetFeatures
495}
496
497#[cfg(not(feature = "simd"))]
498const fn candidate_detection_mode() -> CandidateDetectionMode {
499    CandidateDetectionMode::SimdFeatureDisabled
500}