use std::future::Future;
use std::time::{Duration, Instant};
use keyhog_core::VerificationResult;
use crate::verify::request::TIMEOUT_ERROR;
#[derive(Debug, Clone, Copy)]
pub struct VerificationDeadline {
start: Instant,
deadline: Instant,
timeout: Duration,
}
impl VerificationDeadline {
pub fn new(timeout: Duration) -> Self {
let start = Instant::now();
let deadline = start.checked_add(timeout).unwrap_or(start);
Self {
start,
deadline,
timeout,
}
}
pub fn for_attempts(
per_attempt_timeout: Duration,
max_attempts: usize,
max_backoff: Duration,
) -> Self {
let start = Instant::now();
let total_duration = per_attempt_timeout
.saturating_mul(max_attempts as u32)
.saturating_add(max_backoff)
.max(Duration::from_secs(1));
let deadline = start.checked_add(total_duration).unwrap_or(start);
Self {
start,
deadline,
timeout: per_attempt_timeout,
}
}
pub fn remaining(&self) -> Result<Duration, VerificationResult> {
let now = Instant::now();
if now >= self.deadline {
Err(VerificationResult::Error(TIMEOUT_ERROR.into()))
} else {
Ok(self.deadline.saturating_duration_since(now))
}
}
pub fn check(&self) -> Result<(), VerificationResult> {
if self.is_expired() {
Err(VerificationResult::Error(TIMEOUT_ERROR.into()))
} else {
Ok(())
}
}
pub fn is_expired(&self) -> bool {
Instant::now() >= self.deadline
}
pub fn elapsed(&self) -> Duration {
self.start.elapsed()
}
pub fn timeout(&self) -> Duration {
self.timeout
}
pub async fn run_bounded<F, T>(&self, fut: F) -> Result<T, VerificationResult>
where
F: Future<Output = T>,
{
let remaining = self.remaining()?;
match tokio::time::timeout(remaining, fut).await {
Ok(val) => Ok(val),
Err(_) => Err(VerificationResult::Error(TIMEOUT_ERROR.into())),
}
}
}