Skip to main content

palisade_config/
timing.rs

1//! Centralized timing-profile controls for constant-time floor normalization.
2
3use std::sync::atomic::{AtomicU8, Ordering};
4use std::time::{Duration, Instant};
5
6/// Runtime timing profile.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[repr(u8)]
9pub enum TimingProfile {
10    /// Lower latency with moderate timing smoothing.
11    Balanced = 0,
12    /// Higher latency with stronger timing smoothing.
13    Hardened = 1,
14}
15
16/// Internal operation kinds with dedicated timing floors.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub(crate) enum TimingOperation {
19    ConfigLoad,
20    ConfigValidateStandard,
21    ConfigValidateStrict,
22    ConfigHostname,
23    ConfigDiff,
24    PolicyLoad,
25    PolicyValidate,
26    PolicyDiff,
27    PolicySuspiciousCheckLegacy,
28    RuntimeConfigBuild,
29    RuntimePolicyBuild,
30    RootTagNew,
31    RootTagGenerate,
32    RootTagDeriveHost,
33    RootTagDeriveArtifact,
34    RootTagHashCompare,
35    PolicySuspiciousCheck,
36    PolicyCustomConditionCheck,
37}
38
39static TIMING_PROFILE: AtomicU8 = AtomicU8::new(TimingProfile::Balanced as u8);
40
41/// Set global timing profile for constant-time floor normalization.
42pub fn set_timing_profile(profile: TimingProfile) {
43    TIMING_PROFILE.store(profile as u8, Ordering::Relaxed);
44}
45
46/// Get current global timing profile.
47#[must_use]
48pub fn get_timing_profile() -> TimingProfile {
49    match TIMING_PROFILE.load(Ordering::Relaxed) {
50        1 => TimingProfile::Hardened,
51        _ => TimingProfile::Balanced,
52    }
53}
54
55#[inline]
56const fn timing_floor(profile: TimingProfile, op: TimingOperation) -> Duration {
57    match profile {
58        TimingProfile::Balanced => match op {
59            TimingOperation::ConfigLoad => Duration::from_micros(30),
60            TimingOperation::ConfigValidateStandard => Duration::from_micros(12),
61            TimingOperation::ConfigValidateStrict => Duration::from_micros(16),
62            TimingOperation::ConfigHostname => Duration::from_micros(2),
63            TimingOperation::ConfigDiff => Duration::from_micros(8),
64            TimingOperation::PolicyLoad => Duration::from_micros(30),
65            TimingOperation::PolicyValidate => Duration::from_micros(12),
66            TimingOperation::PolicyDiff => Duration::from_micros(8),
67            TimingOperation::PolicySuspiciousCheckLegacy => Duration::from_micros(8),
68            TimingOperation::RuntimeConfigBuild => Duration::from_micros(10),
69            TimingOperation::RuntimePolicyBuild => Duration::from_micros(10),
70            TimingOperation::RootTagNew => Duration::from_micros(18),
71            TimingOperation::RootTagGenerate => Duration::from_micros(18),
72            TimingOperation::RootTagDeriveHost => Duration::from_micros(8),
73            TimingOperation::RootTagDeriveArtifact => Duration::from_micros(12),
74            TimingOperation::RootTagHashCompare => Duration::from_micros(1),
75            TimingOperation::PolicySuspiciousCheck => Duration::from_micros(8),
76            TimingOperation::PolicyCustomConditionCheck => Duration::from_micros(4),
77        },
78        TimingProfile::Hardened => match op {
79            TimingOperation::ConfigLoad => Duration::from_micros(45),
80            TimingOperation::ConfigValidateStandard => Duration::from_micros(18),
81            TimingOperation::ConfigValidateStrict => Duration::from_micros(24),
82            TimingOperation::ConfigHostname => Duration::from_micros(4),
83            TimingOperation::ConfigDiff => Duration::from_micros(12),
84            TimingOperation::PolicyLoad => Duration::from_micros(45),
85            TimingOperation::PolicyValidate => Duration::from_micros(18),
86            TimingOperation::PolicyDiff => Duration::from_micros(12),
87            TimingOperation::PolicySuspiciousCheckLegacy => Duration::from_micros(12),
88            TimingOperation::RuntimeConfigBuild => Duration::from_micros(16),
89            TimingOperation::RuntimePolicyBuild => Duration::from_micros(16),
90            TimingOperation::RootTagNew => Duration::from_micros(28),
91            TimingOperation::RootTagGenerate => Duration::from_micros(28),
92            TimingOperation::RootTagDeriveHost => Duration::from_micros(12),
93            TimingOperation::RootTagDeriveArtifact => Duration::from_micros(18),
94            TimingOperation::RootTagHashCompare => Duration::from_micros(2),
95            TimingOperation::PolicySuspiciousCheck => Duration::from_micros(12),
96            TimingOperation::PolicyCustomConditionCheck => Duration::from_micros(6),
97        },
98    }
99}
100
101#[inline]
102pub(crate) fn enforce_operation_min_timing(started: Instant, op: TimingOperation) {
103    let target = started + timing_floor(get_timing_profile(), op);
104    while Instant::now() < target {
105        std::hint::spin_loop();
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn profile_roundtrip() {
115        set_timing_profile(TimingProfile::Balanced);
116        assert_eq!(get_timing_profile(), TimingProfile::Balanced);
117        set_timing_profile(TimingProfile::Hardened);
118        assert_eq!(get_timing_profile(), TimingProfile::Hardened);
119    }
120}