mur_common/hitl/
signed_time.rs1use chrono::{DateTime, Duration, Utc};
10
11use super::APPROVAL_TTL_SECS;
12
13pub const HITL_CLOCK_SKEW_SECS: i64 = 60;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum TimeFault {
34 FromTheFuture,
36 BeforeRequest,
38 AfterExpiry,
40}
41
42pub fn check_answer_time(
44 asked: DateTime<Utc>,
45 answered: DateTime<Utc>,
46 now: DateTime<Utc>,
47) -> Result<(), TimeFault> {
48 let skew = Duration::seconds(HITL_CLOCK_SKEW_SECS);
49 if asked > now + skew || answered > now + skew {
50 return Err(TimeFault::FromTheFuture);
51 }
52 if answered < asked - skew {
53 return Err(TimeFault::BeforeRequest);
54 }
55 if answered - asked > Duration::seconds(APPROVAL_TTL_SECS) + skew {
56 return Err(TimeFault::AfterExpiry);
57 }
58 Ok(())
59}
60
61pub fn check_fresh(issued_at: DateTime<Utc>, now: DateTime<Utc>) -> Result<(), TimeFault> {
67 check_answer_time(issued_at, now, now)
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 fn secs(s: i64) -> Duration {
75 Duration::seconds(s)
76 }
77
78 const SKEW: i64 = HITL_CLOCK_SKEW_SECS;
79
80 #[test]
81 fn an_answer_a_little_before_its_request_is_clock_noise() {
82 let now = Utc::now();
83 let asked = now - secs(10);
84 assert_eq!(check_answer_time(asked, asked - secs(SKEW), now), Ok(()));
85 assert_eq!(
86 check_answer_time(asked, asked - secs(SKEW + 1), now),
87 Err(TimeFault::BeforeRequest)
88 );
89 }
90
91 #[test]
92 fn expiry_adds_the_skew_to_the_ttl() {
93 let now = Utc::now();
94 let asked = now - secs(APPROVAL_TTL_SECS + SKEW + 10);
95 let edge = asked + secs(APPROVAL_TTL_SECS + SKEW);
96 assert_eq!(check_answer_time(asked, edge, now), Ok(()));
97 assert_eq!(
98 check_answer_time(asked, edge + secs(1), now),
99 Err(TimeFault::AfterExpiry)
100 );
101 }
102
103 #[test]
104 fn a_fast_clock_is_bounded_by_the_skew() {
105 let now = Utc::now();
106 assert_eq!(check_answer_time(now, now + secs(SKEW), now), Ok(()));
107 assert_eq!(
108 check_answer_time(now, now + secs(SKEW + 1), now),
109 Err(TimeFault::FromTheFuture)
110 );
111 assert_eq!(
112 check_answer_time(now + secs(SKEW + 1), now + secs(SKEW + 1), now),
113 Err(TimeFault::FromTheFuture),
114 "a future-dated request is refused too"
115 );
116 assert_eq!(
118 check_answer_time(now, now + Duration::days(30), now),
119 Err(TimeFault::FromTheFuture)
120 );
121 }
122
123 #[test]
124 fn fresh_until_ttl_plus_skew_and_never_from_the_future() {
125 let now = Utc::now();
126 assert_eq!(
127 check_fresh(now - secs(APPROVAL_TTL_SECS + SKEW), now),
128 Ok(())
129 );
130 assert_eq!(
131 check_fresh(now - secs(APPROVAL_TTL_SECS + SKEW + 1), now),
132 Err(TimeFault::AfterExpiry)
133 );
134 assert_eq!(
135 check_fresh(now + secs(SKEW + 1), now),
136 Err(TimeFault::FromTheFuture)
137 );
138 }
139}