Skip to main content

base64_ng/v2/
backend_health.rs

1//! Backend health, self-test, quarantine, and fallback ownership boundary.
2
3use crate::runtime::{Backend, OperationKind};
4
5use super::contracts::BackendFault;
6
7#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
8use core::sync::atomic::{AtomicUsize, Ordering};
9
10#[cfg(feature = "simd")]
11mod kat;
12
13#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
14const NEVER_RUN: usize = 0;
15#[cfg(any(test, all(feature = "simd", target_has_atomic = "ptr")))]
16const TESTING: usize = 1;
17#[cfg(any(test, all(feature = "simd", target_has_atomic = "ptr")))]
18const HEALTHY: usize = 2;
19#[cfg(any(test, all(feature = "simd", target_has_atomic = "ptr")))]
20const QUARANTINED: usize = 3;
21
22/// Runtime state of an ordinary accelerated backend.
23#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
24#[non_exhaustive]
25pub enum BackendHealthState {
26    /// No known-answer test has completed.
27    NeverRun,
28    /// One thread is running the known-answer test.
29    Testing,
30    /// The known-answer test passed and the backend may execute.
31    Healthy,
32    /// The backend failed an integrity check and is disabled for this process.
33    Quarantined,
34}
35
36impl BackendHealthState {
37    /// Returns the stable lowercase identifier.
38    #[must_use]
39    pub const fn as_str(self) -> &'static str {
40        match self {
41            Self::NeverRun => "never-run",
42            Self::Testing => "testing",
43            Self::Healthy => "healthy",
44            Self::Quarantined => "quarantined",
45        }
46    }
47}
48
49/// Atomic snapshot of one backend's health latch.
50#[derive(Clone, Copy, Debug, Eq, PartialEq)]
51pub struct BackendHealthSnapshot {
52    /// Operation whose implementation is covered by the latch.
53    pub operation: OperationKind,
54    /// Backend covered by the latch.
55    pub backend: Backend,
56    /// Current process-local health state.
57    pub state: BackendHealthState,
58    /// Monotonic process-local state generation.
59    pub generation: usize,
60    /// Most recent integrity fault, when quarantined.
61    pub fault: Option<BackendFault>,
62}
63
64/// Summary returned by explicit startup backend initialization.
65#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
66pub struct BackendInitializationReport {
67    /// Operation/backend pairs whose KAT was requested.
68    pub tested: usize,
69    /// Pairs that are healthy after initialization.
70    pub healthy: usize,
71    /// Pairs that are permanently quarantined.
72    pub quarantined: usize,
73    /// Pairs unavailable on this CPU, build, or synchronization target.
74    pub unavailable: usize,
75}
76
77impl BackendInitializationReport {
78    fn record(&mut self, result: InitializationResult) {
79        match result {
80            InitializationResult::Healthy => {
81                self.tested += 1;
82                self.healthy += 1;
83            }
84            InitializationResult::Quarantined => {
85                self.tested += 1;
86                self.quarantined += 1;
87            }
88            InitializationResult::Unavailable => self.unavailable += 1,
89        }
90    }
91}
92
93#[derive(Clone, Copy)]
94enum InitializationResult {
95    Healthy,
96    Quarantined,
97    Unavailable,
98}
99
100#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
101struct HealthCell {
102    state: AtomicUsize,
103    generation: AtomicUsize,
104    fault: AtomicUsize,
105    #[cfg(all(feature = "std", unix))]
106    process_id: AtomicUsize,
107}
108
109#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
110struct KatTransitionGuard<'a> {
111    cell: &'a HealthCell,
112    armed: bool,
113}
114
115#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
116impl<'a> KatTransitionGuard<'a> {
117    const fn new(cell: &'a HealthCell) -> Self {
118        Self { cell, armed: true }
119    }
120
121    fn complete(mut self, passed: bool) -> bool {
122        if passed {
123            self.cell.state.store(HEALTHY, Ordering::Release);
124            bump_generation(&self.cell.generation);
125        } else {
126            self.cell.quarantine(BackendFault::SelfTestFailed);
127        }
128        self.armed = false;
129        passed
130    }
131}
132
133#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
134impl Drop for KatTransitionGuard<'_> {
135    fn drop(&mut self) {
136        if self.armed {
137            self.cell.quarantine(BackendFault::SelfTestFailed);
138        }
139    }
140}
141
142#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
143impl HealthCell {
144    const fn new() -> Self {
145        Self {
146            state: AtomicUsize::new(NEVER_RUN),
147            generation: AtomicUsize::new(1),
148            fault: AtomicUsize::new(0),
149            #[cfg(all(feature = "std", unix))]
150            process_id: AtomicUsize::new(0),
151        }
152    }
153
154    fn snapshot(&self, operation: OperationKind, backend: Backend) -> BackendHealthSnapshot {
155        self.refresh_after_fork();
156        BackendHealthSnapshot {
157            operation,
158            backend,
159            state: decode_state(self.state.load(Ordering::Acquire)),
160            generation: self.generation.load(Ordering::Acquire),
161            fault: decode_fault(self.fault.load(Ordering::Acquire)),
162        }
163    }
164
165    fn ensure(&self, operation: OperationKind, backend: Backend) -> bool {
166        self.ensure_with(|| kat::run(operation, backend))
167    }
168
169    fn ensure_with(&self, run: impl FnOnce() -> bool) -> bool {
170        self.refresh_after_fork();
171        let mut run = Some(run);
172        loop {
173            match self.state.load(Ordering::Acquire) {
174                HEALTHY => return true,
175                QUARANTINED | TESTING => return false,
176                NEVER_RUN => {
177                    if self
178                        .state
179                        .compare_exchange(NEVER_RUN, TESTING, Ordering::AcqRel, Ordering::Acquire)
180                        .is_err()
181                    {
182                        continue;
183                    }
184                    bump_generation(&self.generation);
185                    self.remember_process();
186                    let Some(initializer) = run.take() else {
187                        self.quarantine(BackendFault::ImpossibleState);
188                        return false;
189                    };
190                    let transition = KatTransitionGuard::new(self);
191                    let passed = run_catching_panics(initializer);
192                    return transition.complete(passed);
193                }
194                _ => {
195                    self.quarantine(BackendFault::ImpossibleState);
196                    return false;
197                }
198            }
199        }
200    }
201
202    fn quarantine(&self, fault: BackendFault) {
203        self.fault.store(encode_fault(fault), Ordering::Release);
204        if self.state.swap(QUARANTINED, Ordering::AcqRel) != QUARANTINED {
205            bump_generation(&self.generation);
206        }
207    }
208
209    #[cfg(all(feature = "std", unix))]
210    fn remember_process(&self) {
211        self.process_id
212            .store(std::process::id() as usize, Ordering::Release);
213    }
214
215    #[cfg(not(all(feature = "std", unix)))]
216    const fn remember_process(&self) {
217        let _ = self;
218    }
219
220    #[cfg(all(feature = "std", unix))]
221    fn refresh_after_fork(&self) {
222        let current = std::process::id() as usize;
223        let recorded = self.process_id.load(Ordering::Acquire);
224        if recorded != 0
225            && recorded != current
226            && self
227                .state
228                .compare_exchange(TESTING, NEVER_RUN, Ordering::AcqRel, Ordering::Acquire)
229                .is_ok()
230        {
231            self.process_id.store(current, Ordering::Release);
232            bump_generation(&self.generation);
233        }
234    }
235
236    #[cfg(not(all(feature = "std", unix)))]
237    const fn refresh_after_fork(&self) {
238        let _ = self;
239    }
240}
241
242#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
243fn bump_generation(generation: &AtomicUsize) {
244    let mut current = generation.load(Ordering::Relaxed);
245    while current != usize::MAX {
246        match generation.compare_exchange_weak(
247            current,
248            current + 1,
249            Ordering::AcqRel,
250            Ordering::Relaxed,
251        ) {
252            Ok(_) => return,
253            Err(observed) => current = observed,
254        }
255    }
256}
257
258#[cfg(any(test, all(feature = "simd", target_has_atomic = "ptr")))]
259const fn decode_state(state: usize) -> BackendHealthState {
260    match state {
261        TESTING => BackendHealthState::Testing,
262        HEALTHY => BackendHealthState::Healthy,
263        QUARANTINED => BackendHealthState::Quarantined,
264        _ => BackendHealthState::NeverRun,
265    }
266}
267
268#[cfg(any(test, all(feature = "simd", target_has_atomic = "ptr")))]
269const fn encode_fault(fault: BackendFault) -> usize {
270    match fault {
271        BackendFault::SelfTestFailed => 1,
272        BackendFault::OutputMismatch => 2,
273        BackendFault::ImpossibleState => 3,
274        BackendFault::ScalarRetryFailed => 4,
275    }
276}
277
278#[cfg(any(test, all(feature = "simd", target_has_atomic = "ptr")))]
279const fn decode_fault(fault: usize) -> Option<BackendFault> {
280    match fault {
281        1 => Some(BackendFault::SelfTestFailed),
282        2 => Some(BackendFault::OutputMismatch),
283        3 => Some(BackendFault::ImpossibleState),
284        4 => Some(BackendFault::ScalarRetryFailed),
285        _ => None,
286    }
287}
288
289#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
290fn run_catching_panics(run: impl FnOnce() -> bool) -> bool {
291    #[cfg(feature = "std")]
292    {
293        std::panic::catch_unwind(std::panic::AssertUnwindSafe(run)).unwrap_or(false)
294    }
295    #[cfg(not(feature = "std"))]
296    {
297        run()
298    }
299}
300
301macro_rules! health_cells {
302    ($($name:ident),+ $(,)?) => {
303        $(
304            #[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
305            static $name: HealthCell = HealthCell::new();
306        )+
307    };
308}
309
310health_cells!(
311    ENCODE_AVX512,
312    DECODE_AVX512,
313    ENCODE_AVX2,
314    DECODE_AVX2,
315    ENCODE_SSSE3,
316    DECODE_SSSE3,
317    ENCODE_NEON,
318    DECODE_NEON,
319    ENCODE_WASM,
320    DECODE_WASM,
321    ENCODE_RVV,
322    DECODE_RVV,
323);
324
325#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
326fn cell(operation: OperationKind, backend: Backend) -> Option<&'static HealthCell> {
327    match (operation, backend) {
328        (OperationKind::Encode, Backend::Avx512Vbmi) => Some(&ENCODE_AVX512),
329        (OperationKind::StrictDecode, Backend::Avx512Vbmi) => Some(&DECODE_AVX512),
330        (OperationKind::Encode, Backend::Avx2) => Some(&ENCODE_AVX2),
331        (OperationKind::StrictDecode, Backend::Avx2) => Some(&DECODE_AVX2),
332        (OperationKind::Encode, Backend::Ssse3Sse41) => Some(&ENCODE_SSSE3),
333        (OperationKind::StrictDecode, Backend::Ssse3Sse41) => Some(&DECODE_SSSE3),
334        (OperationKind::Encode, Backend::Neon) => Some(&ENCODE_NEON),
335        (OperationKind::StrictDecode, Backend::Neon) => Some(&DECODE_NEON),
336        (OperationKind::Encode, Backend::WasmSimd128) => Some(&ENCODE_WASM),
337        (OperationKind::StrictDecode, Backend::WasmSimd128) => Some(&DECODE_WASM),
338        (OperationKind::Encode, Backend::Rvv) => Some(&ENCODE_RVV),
339        (OperationKind::StrictDecode, Backend::Rvv) => Some(&DECODE_RVV),
340        _ => None,
341    }
342}
343
344pub(crate) fn admit(operation: OperationKind, backend: Backend) -> bool {
345    if backend == Backend::Scalar {
346        return true;
347    }
348    #[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
349    if kat::available(backend)
350        && let Some(cell) = cell(operation, backend)
351    {
352        return cell.ensure(operation, backend);
353    }
354    let _ = operation;
355    false
356}
357
358/// Admits a target-compatible backend after an unsafe deployment attestation.
359///
360/// The only caller is `StaticBackendToken::assume_supported`, whose public
361/// unsafe contract owns the CPU and OS evidence for executing the direct KAT.
362#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
363pub(crate) fn admit_deployment_attested(operation: OperationKind, backend: Backend) -> bool {
364    cell(operation, backend).is_some_and(|cell| cell.ensure(operation, backend))
365}
366
367#[cfg(all(feature = "simd", not(target_has_atomic = "ptr")))]
368pub(crate) const fn admit_deployment_attested(
369    _operation: OperationKind,
370    _backend: Backend,
371) -> bool {
372    false
373}
374
375#[cfg(feature = "checked-backend")]
376pub(crate) fn quarantine(operation: OperationKind, backend: Backend, fault: BackendFault) {
377    #[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
378    if let Some(cell) = cell(operation, backend) {
379        cell.quarantine(fault);
380    }
381    let _ = (operation, backend, fault);
382}
383
384#[cfg(feature = "checked-backend")]
385pub(crate) fn direct_encode<A: crate::Alphabet, const PAD: bool>(
386    backend: Backend,
387    input: &[u8],
388    output: &mut [u8],
389) -> Option<usize> {
390    kat::direct_encode::<A, PAD>(backend, input, output)
391}
392
393#[cfg(feature = "checked-backend")]
394pub(crate) fn direct_decode<A: crate::Alphabet, const PAD: bool>(
395    backend: Backend,
396    input: &[u8],
397    output: &mut [u8],
398) -> Option<usize> {
399    kat::direct_decode::<A, PAD>(backend, input, output)
400}
401
402pub(crate) fn snapshot(operation: OperationKind, backend: Backend) -> BackendHealthSnapshot {
403    #[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
404    if let Some(cell) = cell(operation, backend) {
405        return cell.snapshot(operation, backend);
406    }
407    BackendHealthSnapshot {
408        operation,
409        backend,
410        state: if backend == Backend::Scalar {
411            BackendHealthState::Healthy
412        } else {
413            BackendHealthState::NeverRun
414        },
415        generation: 1,
416        fault: None,
417    }
418}
419
420/// Runs KAT initialization for every accelerated backend available now.
421#[must_use]
422pub fn initialize_backends() -> BackendInitializationReport {
423    let mut report = BackendInitializationReport::default();
424    for backend in candidate_backends() {
425        for operation in [OperationKind::Encode, OperationKind::StrictDecode] {
426            let result = if !backend_available(*backend) {
427                InitializationResult::Unavailable
428            } else if admit(operation, *backend) {
429                InitializationResult::Healthy
430            } else if snapshot(operation, *backend).state == BackendHealthState::Quarantined {
431                InitializationResult::Quarantined
432            } else {
433                InitializationResult::Unavailable
434            };
435            report.record(result);
436        }
437    }
438    report
439}
440
441fn backend_available(backend: Backend) -> bool {
442    #[cfg(feature = "simd")]
443    {
444        kat::available(backend)
445    }
446    #[cfg(not(feature = "simd"))]
447    {
448        let _ = backend;
449        false
450    }
451}
452
453const fn candidate_backends() -> &'static [Backend] {
454    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
455    return &[Backend::Avx512Vbmi, Backend::Avx2, Backend::Ssse3Sse41];
456    #[cfg(all(target_arch = "aarch64", target_endian = "little"))]
457    return &[Backend::Neon];
458    #[cfg(target_arch = "wasm32")]
459    return &[Backend::WasmSimd128];
460    #[cfg(all(feature = "std", target_arch = "riscv64", target_os = "linux"))]
461    return &[Backend::Rvv];
462    #[allow(unreachable_code)]
463    &[]
464}
465
466#[cfg(test)]
467mod tests;