Skip to main content

keyhog_verifier/
engine.rs

1//! Verification engine deadline bounds and sensitive intermediate state management.
2//!
3//! Enforces strict bounded deadline checks across verification execution and
4//! ensures intermediate auth header strings and credentials are automatically
5//! zeroized on completion or error.
6
7use std::future::Future;
8use std::time::{Duration, Instant};
9
10use keyhog_core::VerificationResult;
11
12use crate::verify::request::TIMEOUT_ERROR;
13
14/// Enforces a strict bounded deadline for verification tasks.
15#[derive(Debug, Clone, Copy)]
16pub struct VerificationDeadline {
17    start: Instant,
18    deadline: Instant,
19    timeout: Duration,
20}
21
22impl VerificationDeadline {
23    /// Create a new verification deadline from the given timeout duration.
24    pub fn new(timeout: Duration) -> Self {
25        let start = Instant::now();
26        let deadline = start.checked_add(timeout).unwrap_or(start);
27        Self {
28            start,
29            deadline,
30            timeout,
31        }
32    }
33
34    /// Create a bounded deadline for a multi-attempt task, accounting for per-attempt timeouts and retry backoff.
35    pub fn for_attempts(
36        per_attempt_timeout: Duration,
37        max_attempts: usize,
38        max_backoff: Duration,
39    ) -> Self {
40        let start = Instant::now();
41        let total_duration = per_attempt_timeout
42            .saturating_mul(max_attempts as u32)
43            .saturating_add(max_backoff)
44            .max(Duration::from_secs(1));
45        let deadline = start.checked_add(total_duration).unwrap_or(start);
46        Self {
47            start,
48            deadline,
49            timeout: per_attempt_timeout,
50        }
51    }
52
53    /// Return the remaining duration before the deadline expires.
54    ///
55    /// Fails closed with [`TIMEOUT_ERROR`] if the deadline has already passed.
56    pub fn remaining(&self) -> Result<Duration, VerificationResult> {
57        let now = Instant::now();
58        if now >= self.deadline {
59            Err(VerificationResult::Error(TIMEOUT_ERROR.into()))
60        } else {
61            Ok(self.deadline.saturating_duration_since(now))
62        }
63    }
64
65    /// Check whether the deadline has passed, returning an error if expired.
66    pub fn check(&self) -> Result<(), VerificationResult> {
67        if self.is_expired() {
68            Err(VerificationResult::Error(TIMEOUT_ERROR.into()))
69        } else {
70            Ok(())
71        }
72    }
73
74    /// Returns `true` if the deadline has elapsed.
75    pub fn is_expired(&self) -> bool {
76        Instant::now() >= self.deadline
77    }
78
79    /// Elapsed time since this deadline was constructed.
80    pub fn elapsed(&self) -> Duration {
81        self.start.elapsed()
82    }
83
84    /// The base timeout configured for this deadline.
85    pub fn timeout(&self) -> Duration {
86        self.timeout
87    }
88
89    /// Execute a future bounded strictly by the remaining deadline.
90    pub async fn run_bounded<F, T>(&self, fut: F) -> Result<T, VerificationResult>
91    where
92        F: Future<Output = T>,
93    {
94        let remaining = self.remaining()?;
95        match tokio::time::timeout(remaining, fut).await {
96            Ok(val) => Ok(val),
97            Err(_) => Err(VerificationResult::Error(TIMEOUT_ERROR.into())),
98        }
99    }
100}