use chrono::{DateTime, Duration, Utc};
use super::APPROVAL_TTL_SECS;
pub const HITL_CLOCK_SKEW_SECS: i64 = 60;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimeFault {
FromTheFuture,
BeforeRequest,
AfterExpiry,
}
pub fn check_answer_time(
asked: DateTime<Utc>,
answered: DateTime<Utc>,
now: DateTime<Utc>,
) -> Result<(), TimeFault> {
let skew = Duration::seconds(HITL_CLOCK_SKEW_SECS);
if asked > now + skew || answered > now + skew {
return Err(TimeFault::FromTheFuture);
}
if answered < asked - skew {
return Err(TimeFault::BeforeRequest);
}
if answered - asked > Duration::seconds(APPROVAL_TTL_SECS) + skew {
return Err(TimeFault::AfterExpiry);
}
Ok(())
}
pub fn check_fresh(issued_at: DateTime<Utc>, now: DateTime<Utc>) -> Result<(), TimeFault> {
check_answer_time(issued_at, now, now)
}
#[cfg(test)]
mod tests {
use super::*;
fn secs(s: i64) -> Duration {
Duration::seconds(s)
}
const SKEW: i64 = HITL_CLOCK_SKEW_SECS;
#[test]
fn an_answer_a_little_before_its_request_is_clock_noise() {
let now = Utc::now();
let asked = now - secs(10);
assert_eq!(check_answer_time(asked, asked - secs(SKEW), now), Ok(()));
assert_eq!(
check_answer_time(asked, asked - secs(SKEW + 1), now),
Err(TimeFault::BeforeRequest)
);
}
#[test]
fn expiry_adds_the_skew_to_the_ttl() {
let now = Utc::now();
let asked = now - secs(APPROVAL_TTL_SECS + SKEW + 10);
let edge = asked + secs(APPROVAL_TTL_SECS + SKEW);
assert_eq!(check_answer_time(asked, edge, now), Ok(()));
assert_eq!(
check_answer_time(asked, edge + secs(1), now),
Err(TimeFault::AfterExpiry)
);
}
#[test]
fn a_fast_clock_is_bounded_by_the_skew() {
let now = Utc::now();
assert_eq!(check_answer_time(now, now + secs(SKEW), now), Ok(()));
assert_eq!(
check_answer_time(now, now + secs(SKEW + 1), now),
Err(TimeFault::FromTheFuture)
);
assert_eq!(
check_answer_time(now + secs(SKEW + 1), now + secs(SKEW + 1), now),
Err(TimeFault::FromTheFuture),
"a future-dated request is refused too"
);
assert_eq!(
check_answer_time(now, now + Duration::days(30), now),
Err(TimeFault::FromTheFuture)
);
}
#[test]
fn fresh_until_ttl_plus_skew_and_never_from_the_future() {
let now = Utc::now();
assert_eq!(
check_fresh(now - secs(APPROVAL_TTL_SECS + SKEW), now),
Ok(())
);
assert_eq!(
check_fresh(now - secs(APPROVAL_TTL_SECS + SKEW + 1), now),
Err(TimeFault::AfterExpiry)
);
assert_eq!(
check_fresh(now + secs(SKEW + 1), now),
Err(TimeFault::FromTheFuture)
);
}
}