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    /// In the staged `1.2.0` line, AVX-512 VBMI, AVX2, SSSE3/SSE4.1, and NEON
34    /// admission covers encode dispatch only. Decode remains scalar until a
35    /// separate decode admission package is complete.
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    /// In the staged `1.2.0` line, non-scalar active values describe admitted
67    /// encode dispatch; decode remains scalar.
68    pub active: &'static str,
69    /// Stable detected candidate identifier.
70    pub candidate: &'static str,
71    /// Stable SIMD candidate detection-mode identifier.
72    pub candidate_detection_mode: &'static str,
73    /// CPU features required by the detected candidate.
74    pub candidate_required_cpu_features: &'static [&'static str],
75    /// Whether the `simd` feature is enabled in this build.
76    pub simd_feature_enabled: bool,
77    /// Whether an accelerated SIMD backend is active.
78    pub accelerated_backend_active: bool,
79    /// Whether this build keeps the high-assurance scalar unsafe boundary.
80    ///
81    /// This is `false` for `simd` builds even while execution remains
82    /// scalar-only, because those builds include additional private
83    /// prototype boundaries.
84    pub unsafe_boundary_enforced: bool,
85    /// Stable security posture identifier.
86    pub security_posture: &'static str,
87    /// Stable wipe-barrier posture identifier.
88    pub wipe_posture: &'static str,
89    /// Stable constant-time result-gate barrier posture identifier.
90    pub ct_gate_posture: &'static str,
91}
92
93impl core::fmt::Display for BackendReport {
94    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
95        write!(
96            formatter,
97            "active={} candidate={} candidate_detection_mode={} candidate_required_cpu_features=",
98            self.active, self.candidate, self.candidate_detection_mode,
99        )?;
100        write_feature_list(formatter, self.candidate_required_cpu_features())?;
101        write!(
102            formatter,
103            " simd_feature_enabled={} accelerated_backend_active={} unsafe_boundary_enforced={} security_posture={} wipe_posture={} ct_gate_posture={}",
104            self.simd_feature_enabled,
105            self.accelerated_backend_active,
106            self.unsafe_boundary_enforced,
107            self.security_posture,
108            self.wipe_posture,
109            self.ct_gate_posture,
110        )
111    }
112}
113
114impl BackendReport {
115    /// Returns whether this report satisfies `policy`.
116    ///
117    /// ```
118    /// let report = base64_ng::runtime::backend_report();
119    ///
120    /// let scalar_only =
121    ///     report.satisfies(base64_ng::runtime::BackendPolicy::ScalarExecutionOnly);
122    /// assert_eq!(scalar_only, !report.accelerated_backend_active);
123    /// ```
124    #[must_use]
125    pub const fn satisfies(self, policy: BackendPolicy) -> bool {
126        match policy {
127            BackendPolicy::ScalarExecutionOnly => {
128                matches!(self.active, Backend::Scalar) && !self.accelerated_backend_active
129            }
130            BackendPolicy::SimdFeatureDisabled => !self.simd_feature_enabled,
131            BackendPolicy::NoDetectedSimdCandidate => matches!(self.candidate, Backend::Scalar),
132            BackendPolicy::HighAssuranceScalarOnly => {
133                matches!(self.active, Backend::Scalar)
134                    && matches!(self.candidate, Backend::Scalar)
135                    && !self.simd_feature_enabled
136                    && !self.accelerated_backend_active
137                    && self.unsafe_boundary_enforced
138                    && matches!(
139                        self.ct_gate_posture,
140                        CtGatePosture::HardwareSpeculationBarrier
141                            | CtGatePosture::HardwareSpeculationBarrierBuildAsserted
142                    )
143            }
144        }
145    }
146
147    /// Returns the CPU features required by the detected candidate.
148    ///
149    /// ```
150    /// let report = base64_ng::runtime::backend_report();
151    ///
152    /// assert_eq!(
153    ///     report.candidate_required_cpu_features(),
154    ///     report.candidate.required_cpu_features(),
155    /// );
156    /// ```
157    #[must_use]
158    pub const fn candidate_required_cpu_features(self) -> &'static [&'static str] {
159        self.candidate.required_cpu_features()
160    }
161
162    /// Returns whether `base64-ng` itself locks secret buffers into physical
163    /// memory.
164    ///
165    /// This crate intentionally has no OS-specific `mlock`/`VirtualLock`
166    /// integration. High-assurance deployments should pair secret buffers with
167    /// their own platform-approved memory-locking, swap, hibernation, and
168    /// crash-dump controls.
169    #[must_use]
170    pub const fn memory_lock_posture(self) -> MemoryLockPosture {
171        let _ = self;
172        MemoryLockPosture::NotProvided
173    }
174
175    /// Returns a compact structured snapshot with stable string values.
176    ///
177    /// ```
178    /// let snapshot = base64_ng::runtime::backend_report().snapshot();
179    ///
180    /// assert_eq!(
181    ///     snapshot.accelerated_backend_active,
182    ///     snapshot.active != "scalar",
183    /// );
184    /// ```
185    #[must_use]
186    pub const fn snapshot(self) -> BackendSnapshot {
187        BackendSnapshot {
188            active: self.active.as_str(),
189            candidate: self.candidate.as_str(),
190            candidate_detection_mode: self.candidate_detection_mode.as_str(),
191            candidate_required_cpu_features: self.candidate_required_cpu_features(),
192            simd_feature_enabled: self.simd_feature_enabled,
193            accelerated_backend_active: self.accelerated_backend_active,
194            unsafe_boundary_enforced: self.unsafe_boundary_enforced,
195            security_posture: self.security_posture.as_str(),
196            wipe_posture: self.wipe_posture.as_str(),
197            ct_gate_posture: self.ct_gate_posture.as_str(),
198        }
199    }
200}
201
202/// Returns the runtime backend report for this build and target.
203///
204/// ```
205/// let report = base64_ng::runtime::backend_report();
206///
207/// if report.accelerated_backend_active {
208///     assert_ne!(report.active, base64_ng::runtime::Backend::Scalar);
209/// }
210/// ```
211#[must_use]
212pub fn backend_report() -> BackendReport {
213    let active = active_backend();
214    let candidate = detected_candidate();
215    let candidate_detection_mode = candidate_detection_mode();
216    let accelerated_backend_active = active != Backend::Scalar;
217    let unsafe_boundary_enforced = !cfg!(feature = "simd");
218    let security_posture = if accelerated_backend_active {
219        SecurityPosture::Accelerated
220    } else if candidate != Backend::Scalar {
221        SecurityPosture::SimdCandidateScalarActive
222    } else {
223        SecurityPosture::ScalarOnly
224    };
225
226    BackendReport {
227        active,
228        candidate,
229        candidate_detection_mode,
230        simd_feature_enabled: cfg!(feature = "simd"),
231        accelerated_backend_active,
232        unsafe_boundary_enforced,
233        security_posture,
234        wipe_posture: wipe_posture(),
235        ct_gate_posture: ct_gate_posture(),
236    }
237}
238
239const fn wipe_posture() -> WipePosture {
240    if cfg!(any(
241        target_arch = "aarch64",
242        target_arch = "arm",
243        target_arch = "riscv32",
244        target_arch = "riscv64",
245        target_arch = "x86",
246        target_arch = "x86_64",
247    )) {
248        WipePosture::HardwareFence
249    } else {
250        WipePosture::CompilerFenceOnly
251    }
252}
253
254const fn ct_gate_posture() -> CtGatePosture {
255    if cfg!(any(target_arch = "x86", target_arch = "x86_64")) {
256        CtGatePosture::HardwareSpeculationBarrier
257    } else if cfg!(all(
258        target_arch = "aarch64",
259        base64_ng_aarch64_csdb_attested
260    )) {
261        CtGatePosture::HardwareSpeculationBarrierBuildAsserted
262    } else if cfg!(target_arch = "aarch64") {
263        CtGatePosture::HardwareSpeculationBarrierUnattested
264    } else if cfg!(any(
265        target_arch = "arm",
266        target_arch = "riscv32",
267        target_arch = "riscv64"
268    )) {
269        CtGatePosture::OrderingFence
270    } else {
271        CtGatePosture::CompilerFenceOnly
272    }
273}
274
275/// Requires the current runtime backend report to satisfy `policy`.
276///
277/// ```
278/// let result = base64_ng::runtime::require_backend_policy(
279///     base64_ng::runtime::BackendPolicy::ScalarExecutionOnly,
280/// );
281///
282/// if base64_ng::runtime::backend_report().accelerated_backend_active {
283///     assert!(result.is_err());
284/// } else {
285///     assert!(result.is_ok());
286/// }
287/// ```
288pub fn require_backend_policy(policy: BackendPolicy) -> Result<(), BackendPolicyError> {
289    let report = backend_report();
290    if report.satisfies(policy) {
291        Ok(())
292    } else {
293        Err(BackendPolicyError { policy, report })
294    }
295}
296
297fn write_feature_list(
298    formatter: &mut core::fmt::Formatter<'_>,
299    features: &[&str],
300) -> core::fmt::Result {
301    formatter.write_str("[")?;
302    let mut index = 0;
303    while index < features.len() {
304        if index != 0 {
305            formatter.write_str(",")?;
306        }
307        formatter.write_str(features[index])?;
308        index += 1;
309    }
310    formatter.write_str("]")
311}
312
313#[cfg(feature = "simd")]
314fn active_backend() -> Backend {
315    match crate::simd::active_backend() {
316        crate::simd::ActiveBackend::Scalar => Backend::Scalar,
317        #[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
318        crate::simd::ActiveBackend::Avx512Vbmi => Backend::Avx512Vbmi,
319        #[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
320        crate::simd::ActiveBackend::Avx2 => Backend::Avx2,
321        #[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
322        crate::simd::ActiveBackend::Ssse3Sse41 => Backend::Ssse3Sse41,
323        #[cfg(all(feature = "std", target_arch = "aarch64"))]
324        crate::simd::ActiveBackend::Neon => Backend::Neon,
325    }
326}
327
328#[cfg(not(feature = "simd"))]
329const fn active_backend() -> Backend {
330    Backend::Scalar
331}
332
333#[cfg(feature = "simd")]
334fn detected_candidate() -> Backend {
335    match crate::simd::detected_candidate() {
336        crate::simd::Candidate::Scalar => Backend::Scalar,
337        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
338        crate::simd::Candidate::Avx512Vbmi => Backend::Avx512Vbmi,
339        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
340        crate::simd::Candidate::Avx2 => Backend::Avx2,
341        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
342        crate::simd::Candidate::Ssse3Sse41 => Backend::Ssse3Sse41,
343        #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
344        crate::simd::Candidate::Neon => Backend::Neon,
345        #[cfg(target_arch = "wasm32")]
346        crate::simd::Candidate::WasmSimd128 => Backend::WasmSimd128,
347    }
348}
349
350#[cfg(not(feature = "simd"))]
351const fn detected_candidate() -> Backend {
352    Backend::Scalar
353}
354
355#[cfg(all(
356    feature = "simd",
357    feature = "std",
358    any(target_arch = "x86", target_arch = "x86_64")
359))]
360const fn candidate_detection_mode() -> CandidateDetectionMode {
361    CandidateDetectionMode::RuntimeCpuFeatures
362}
363
364#[cfg(all(
365    feature = "simd",
366    not(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))
367))]
368const fn candidate_detection_mode() -> CandidateDetectionMode {
369    CandidateDetectionMode::CompileTimeTargetFeatures
370}
371
372#[cfg(not(feature = "simd"))]
373const fn candidate_detection_mode() -> CandidateDetectionMode {
374    CandidateDetectionMode::SimdFeatureDisabled
375}