Skip to main content

base64_ng/runtime/
report.rs

1use super::{
2    Backend, BackendPolicy, CandidateDetectionMode, CtGatePosture, MemoryLockPosture,
3    SecurityPosture, WipePosture,
4};
5
6/// Runtime backend policy failure.
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub struct BackendPolicyError {
9    /// Policy that was requested.
10    pub policy: BackendPolicy,
11    /// Backend report observed when the policy failed.
12    pub report: BackendReport,
13}
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
28/// Backend report for the current build and target.
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30pub struct BackendReport {
31    /// Backend currently used for admitted runtime dispatch.
32    ///
33    /// This field reports the primary active backend used by the established
34    /// encode dispatch boundary. Decode has its own narrower admission path;
35    /// use [`Self::active_decode_backend`] to inspect decode dispatch.
36    pub active: Backend,
37    /// Strongest backend candidate visible to the current build.
38    pub candidate: Backend,
39    /// Whether candidate visibility came from runtime CPU probing,
40    /// compile-time target features, or a disabled SIMD feature.
41    pub candidate_detection_mode: CandidateDetectionMode,
42    /// Whether the `simd` feature is enabled in this build.
43    pub simd_feature_enabled: bool,
44    /// Whether an accelerated SIMD backend is active.
45    pub accelerated_backend_active: bool,
46    /// Whether this build keeps the high-assurance scalar unsafe boundary.
47    ///
48    /// This is a conservative compile-time posture signal. It is `true`
49    /// only when the reserved `simd` feature is disabled; `simd` builds
50    /// expose additional private prototype boundaries and must use the
51    /// release evidence scripts for boundary validation.
52    pub unsafe_boundary_enforced: bool,
53    /// Current security posture.
54    pub security_posture: SecurityPosture,
55    /// Current wipe-barrier posture.
56    pub wipe_posture: WipePosture,
57    /// Current constant-time result-gate barrier posture.
58    pub ct_gate_posture: CtGatePosture,
59}
60
61/// Compact structured backend snapshot for logging and policy evidence.
62#[derive(Clone, Copy, Debug, Eq, PartialEq)]
63pub struct BackendSnapshot {
64    /// Stable active backend identifier.
65    ///
66    /// Non-scalar active values describe the primary admitted encode backend.
67    /// Decode dispatch can be queried separately through
68    /// [`BackendReport::active_decode_backend`].
69    pub active: &'static str,
70    /// Stable detected candidate identifier.
71    pub candidate: &'static str,
72    /// Stable SIMD candidate detection-mode identifier.
73    pub candidate_detection_mode: &'static str,
74    /// CPU features required by the detected candidate.
75    pub candidate_required_cpu_features: &'static [&'static str],
76    /// Whether the `simd` feature is enabled in this build.
77    pub simd_feature_enabled: bool,
78    /// Whether an accelerated SIMD backend is active.
79    pub accelerated_backend_active: bool,
80    /// Whether this build keeps the high-assurance scalar unsafe boundary.
81    ///
82    /// This is `false` for `simd` builds even while execution remains
83    /// scalar-only, because those builds include additional private
84    /// prototype boundaries.
85    pub unsafe_boundary_enforced: bool,
86    /// Stable security posture identifier.
87    pub security_posture: &'static str,
88    /// Stable wipe-barrier posture identifier.
89    pub wipe_posture: &'static str,
90    /// Stable constant-time result-gate barrier posture identifier.
91    pub ct_gate_posture: &'static str,
92}
93
94impl core::fmt::Display for BackendReport {
95    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
96        write!(
97            formatter,
98            "active={} candidate={} candidate_detection_mode={} candidate_required_cpu_features=",
99            self.active, self.candidate, self.candidate_detection_mode,
100        )?;
101        write_feature_list(formatter, self.candidate_required_cpu_features())?;
102        write!(
103            formatter,
104            " simd_feature_enabled={} accelerated_backend_active={} unsafe_boundary_enforced={} security_posture={} wipe_posture={} ct_gate_posture={}",
105            self.simd_feature_enabled,
106            self.accelerated_backend_active,
107            self.unsafe_boundary_enforced,
108            self.security_posture,
109            self.wipe_posture,
110            self.ct_gate_posture,
111        )
112    }
113}
114
115impl BackendReport {
116    /// Returns whether this report satisfies `policy`.
117    ///
118    /// ```
119    /// let report = base64_ng::runtime::backend_report();
120    ///
121    /// let scalar_only =
122    ///     report.satisfies(base64_ng::runtime::BackendPolicy::ScalarExecutionOnly);
123    /// assert_eq!(scalar_only, !report.accelerated_backend_active);
124    /// ```
125    #[must_use]
126    pub const fn satisfies(self, policy: BackendPolicy) -> bool {
127        match policy {
128            BackendPolicy::ScalarExecutionOnly => {
129                matches!(self.active, Backend::Scalar) && !self.accelerated_backend_active
130            }
131            BackendPolicy::SimdFeatureDisabled => !self.simd_feature_enabled,
132            BackendPolicy::NoDetectedSimdCandidate => matches!(self.candidate, Backend::Scalar),
133            BackendPolicy::HighAssuranceScalarOnly => {
134                matches!(self.active, Backend::Scalar)
135                    && matches!(self.candidate, Backend::Scalar)
136                    && !self.simd_feature_enabled
137                    && !self.accelerated_backend_active
138                    && self.unsafe_boundary_enforced
139                    && matches!(
140                        self.ct_gate_posture,
141                        CtGatePosture::HardwareSpeculationBarrier
142                            | CtGatePosture::HardwareSpeculationBarrierBuildAsserted
143                    )
144            }
145        }
146    }
147
148    /// Returns the CPU features required by the detected candidate.
149    ///
150    /// ```
151    /// let report = base64_ng::runtime::backend_report();
152    ///
153    /// assert_eq!(
154    ///     report.candidate_required_cpu_features(),
155    ///     report.candidate.required_cpu_features(),
156    /// );
157    /// ```
158    #[must_use]
159    pub const fn candidate_required_cpu_features(self) -> &'static [&'static str] {
160        self.candidate.required_cpu_features()
161    }
162
163    /// Returns the active backend for the normal strict decode boundary.
164    ///
165    /// This is intentionally separate from [`Self::active`]. In the `1.3.0`
166    /// decode line, strict decode may admit a narrower backend than encode;
167    /// unsupported decode surfaces still return scalar here through the public
168    /// API fallback rules.
169    #[must_use]
170    pub fn active_decode_backend(self) -> Backend {
171        let _ = self;
172        active_decode_backend()
173    }
174
175    /// Returns whether `base64-ng` itself locks secret buffers into physical
176    /// memory.
177    ///
178    /// This crate intentionally has no OS-specific `mlock`/`VirtualLock`
179    /// integration. High-assurance deployments should pair secret buffers with
180    /// their own platform-approved memory-locking, swap, hibernation, and
181    /// crash-dump controls.
182    #[must_use]
183    pub const fn memory_lock_posture(self) -> MemoryLockPosture {
184        let _ = self;
185        MemoryLockPosture::NotProvided
186    }
187
188    /// Returns a compact structured snapshot with stable string values.
189    ///
190    /// ```
191    /// let snapshot = base64_ng::runtime::backend_report().snapshot();
192    ///
193    /// assert_eq!(
194    ///     snapshot.accelerated_backend_active,
195    ///     snapshot.active != "scalar",
196    /// );
197    /// ```
198    #[must_use]
199    pub const fn snapshot(self) -> BackendSnapshot {
200        BackendSnapshot {
201            active: self.active.as_str(),
202            candidate: self.candidate.as_str(),
203            candidate_detection_mode: self.candidate_detection_mode.as_str(),
204            candidate_required_cpu_features: self.candidate_required_cpu_features(),
205            simd_feature_enabled: self.simd_feature_enabled,
206            accelerated_backend_active: self.accelerated_backend_active,
207            unsafe_boundary_enforced: self.unsafe_boundary_enforced,
208            security_posture: self.security_posture.as_str(),
209            wipe_posture: self.wipe_posture.as_str(),
210            ct_gate_posture: self.ct_gate_posture.as_str(),
211        }
212    }
213}
214
215/// Returns the runtime backend report for this build and target.
216///
217/// ```
218/// let report = base64_ng::runtime::backend_report();
219///
220/// if report.accelerated_backend_active {
221///     assert_ne!(report.active, base64_ng::runtime::Backend::Scalar);
222/// }
223/// ```
224#[must_use]
225pub fn backend_report() -> BackendReport {
226    let active = active_backend();
227    let candidate = detected_candidate();
228    let candidate_detection_mode = candidate_detection_mode();
229    let accelerated_backend_active = active != Backend::Scalar;
230    let unsafe_boundary_enforced = !cfg!(feature = "simd");
231    let security_posture = if accelerated_backend_active {
232        SecurityPosture::Accelerated
233    } else if candidate != Backend::Scalar {
234        SecurityPosture::SimdCandidateScalarActive
235    } else {
236        SecurityPosture::ScalarOnly
237    };
238
239    BackendReport {
240        active,
241        candidate,
242        candidate_detection_mode,
243        simd_feature_enabled: cfg!(feature = "simd"),
244        accelerated_backend_active,
245        unsafe_boundary_enforced,
246        security_posture,
247        wipe_posture: wipe_posture(),
248        ct_gate_posture: ct_gate_posture(),
249    }
250}
251
252const fn wipe_posture() -> WipePosture {
253    if cfg!(any(
254        target_arch = "aarch64",
255        target_arch = "arm",
256        target_arch = "riscv32",
257        target_arch = "riscv64",
258        target_arch = "x86",
259        target_arch = "x86_64",
260    )) {
261        WipePosture::HardwareFence
262    } else {
263        WipePosture::CompilerFenceOnly
264    }
265}
266
267const fn ct_gate_posture() -> CtGatePosture {
268    if cfg!(any(target_arch = "x86", target_arch = "x86_64")) {
269        CtGatePosture::HardwareSpeculationBarrier
270    } else if cfg!(all(
271        target_arch = "aarch64",
272        base64_ng_aarch64_csdb_attested
273    )) {
274        CtGatePosture::HardwareSpeculationBarrierBuildAsserted
275    } else if cfg!(target_arch = "aarch64") {
276        CtGatePosture::HardwareSpeculationBarrierUnattested
277    } else if cfg!(any(
278        target_arch = "arm",
279        target_arch = "riscv32",
280        target_arch = "riscv64"
281    )) {
282        CtGatePosture::OrderingFence
283    } else {
284        CtGatePosture::CompilerFenceOnly
285    }
286}
287
288/// Requires the current runtime backend report to satisfy `policy`.
289///
290/// ```
291/// let result = base64_ng::runtime::require_backend_policy(
292///     base64_ng::runtime::BackendPolicy::ScalarExecutionOnly,
293/// );
294///
295/// if base64_ng::runtime::backend_report().accelerated_backend_active {
296///     assert!(result.is_err());
297/// } else {
298///     assert!(result.is_ok());
299/// }
300/// ```
301pub fn require_backend_policy(policy: BackendPolicy) -> Result<(), BackendPolicyError> {
302    let report = backend_report();
303    if report.satisfies(policy) {
304        Ok(())
305    } else {
306        Err(BackendPolicyError { policy, report })
307    }
308}
309
310fn write_feature_list(
311    formatter: &mut core::fmt::Formatter<'_>,
312    features: &[&str],
313) -> core::fmt::Result {
314    formatter.write_str("[")?;
315    let mut index = 0;
316    while index < features.len() {
317        if index != 0 {
318            formatter.write_str(",")?;
319        }
320        formatter.write_str(features[index])?;
321        index += 1;
322    }
323    formatter.write_str("]")
324}
325
326#[cfg(feature = "simd")]
327fn active_backend() -> Backend {
328    match crate::simd::active_backend() {
329        crate::simd::ActiveBackend::Scalar => Backend::Scalar,
330        #[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
331        crate::simd::ActiveBackend::Avx512Vbmi => Backend::Avx512Vbmi,
332        #[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
333        crate::simd::ActiveBackend::Avx2 => Backend::Avx2,
334        #[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
335        crate::simd::ActiveBackend::Ssse3Sse41 => Backend::Ssse3Sse41,
336        #[cfg(all(feature = "std", target_arch = "aarch64", target_endian = "little"))]
337        crate::simd::ActiveBackend::Neon => Backend::Neon,
338        #[cfg(all(feature = "simd", target_arch = "wasm32"))]
339        crate::simd::ActiveBackend::WasmSimd128 => Backend::WasmSimd128,
340    }
341}
342
343#[cfg(not(feature = "simd"))]
344const fn active_backend() -> Backend {
345    Backend::Scalar
346}
347
348#[cfg(feature = "simd")]
349fn active_decode_backend() -> Backend {
350    match crate::decode_backend::active_decode_backend() {
351        crate::decode_backend::DecodeBackend::Scalar => Backend::Scalar,
352        #[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
353        crate::decode_backend::DecodeBackend::Avx512Vbmi => Backend::Avx512Vbmi,
354        #[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
355        crate::decode_backend::DecodeBackend::Avx2 => Backend::Avx2,
356        #[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
357        crate::decode_backend::DecodeBackend::Ssse3Sse41 => Backend::Ssse3Sse41,
358        #[cfg(all(feature = "std", target_arch = "aarch64", target_endian = "little"))]
359        crate::decode_backend::DecodeBackend::Neon => Backend::Neon,
360        #[cfg(all(feature = "simd", target_arch = "wasm32"))]
361        crate::decode_backend::DecodeBackend::WasmSimd128 => Backend::WasmSimd128,
362    }
363}
364
365#[cfg(not(feature = "simd"))]
366const fn active_decode_backend() -> Backend {
367    Backend::Scalar
368}
369
370#[cfg(feature = "simd")]
371fn detected_candidate() -> Backend {
372    match crate::simd::detected_candidate() {
373        crate::simd::Candidate::Scalar => Backend::Scalar,
374        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
375        crate::simd::Candidate::Avx512Vbmi => Backend::Avx512Vbmi,
376        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
377        crate::simd::Candidate::Avx2 => Backend::Avx2,
378        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
379        crate::simd::Candidate::Ssse3Sse41 => Backend::Ssse3Sse41,
380        #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
381        crate::simd::Candidate::Neon => Backend::Neon,
382        #[cfg(target_arch = "wasm32")]
383        crate::simd::Candidate::WasmSimd128 => Backend::WasmSimd128,
384    }
385}
386
387#[cfg(not(feature = "simd"))]
388const fn detected_candidate() -> Backend {
389    Backend::Scalar
390}
391
392#[cfg(all(
393    feature = "simd",
394    feature = "std",
395    any(target_arch = "x86", target_arch = "x86_64")
396))]
397const fn candidate_detection_mode() -> CandidateDetectionMode {
398    CandidateDetectionMode::RuntimeCpuFeatures
399}
400
401#[cfg(all(
402    feature = "simd",
403    not(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))
404))]
405const fn candidate_detection_mode() -> CandidateDetectionMode {
406    CandidateDetectionMode::CompileTimeTargetFeatures
407}
408
409#[cfg(not(feature = "simd"))]
410const fn candidate_detection_mode() -> CandidateDetectionMode {
411    CandidateDetectionMode::SimdFeatureDisabled
412}