1use std::time::{Duration, SystemTime};
4
5#[derive(Copy, Clone, Debug, Eq, PartialEq)]
13#[allow(clippy::exhaustive_enums)]
14pub enum ClockSkew {
15 Slow(Duration),
18 None,
20 Fast(Duration),
23}
24
25const MIN: Duration = Duration::from_secs(2);
31
32impl ClockSkew {
33 pub(crate) fn from_handshake_timestamps(
40 ours_at_start: SystemTime,
41 theirs: SystemTime,
42 delay: Duration,
43 ) -> Self {
44 let theirs_at_start_min = theirs - delay;
48 let theirs_at_start_max = theirs;
49
50 if let Ok(skew) = theirs_at_start_min.duration_since(ours_at_start) {
51 ClockSkew::Slow(skew).if_above(MIN)
52 } else if let Ok(skew) = ours_at_start.duration_since(theirs_at_start_max) {
53 ClockSkew::Fast(skew).if_above(MIN)
54 } else {
55 ClockSkew::None
57 }
58 }
59
60 pub fn magnitude(&self) -> Duration {
62 match self {
63 ClockSkew::Slow(d) => *d,
64 ClockSkew::None => Duration::from_secs(0),
65 ClockSkew::Fast(d) => *d,
66 }
67 }
68
69 pub fn as_secs_f64(&self) -> f64 {
72 match self {
73 ClockSkew::Slow(d) => -d.as_secs_f64(),
74 ClockSkew::None => 0.0,
75 ClockSkew::Fast(d) => d.as_secs_f64(),
76 }
77 }
78
79 pub fn from_secs_f64(seconds: f64) -> Option<Self> {
83 use std::num::FpCategory;
84 let max_seconds = Duration::MAX.as_secs_f64();
85
86 match seconds.classify() {
90 FpCategory::Nan => None,
91 FpCategory::Zero | FpCategory::Subnormal => Some(ClockSkew::None),
92 FpCategory::Normal | FpCategory::Infinite => Some(if seconds <= -max_seconds {
93 ClockSkew::Slow(Duration::MAX)
94 } else if seconds < 0.0 {
95 ClockSkew::Slow(Duration::from_secs_f64(-seconds)).if_above(MIN)
96 } else if seconds < max_seconds {
97 ClockSkew::Fast(Duration::from_secs_f64(seconds)).if_above(MIN)
98 } else {
99 ClockSkew::Fast(Duration::MAX)
100 }),
101 }
102 }
103
104 pub fn if_above(self, min: Duration) -> Self {
106 if self.magnitude() > min {
107 self
108 } else {
109 ClockSkew::None
110 }
111 }
112
113 pub fn is_skewed(&self) -> bool {
115 !matches!(self, ClockSkew::None)
116 }
117}
118
119impl Ord for ClockSkew {
120 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
121 use std::cmp::Ordering::*;
122 use ClockSkew::*;
123 match (self, other) {
124 (Slow(a), Slow(b)) => a.cmp(b).reverse(),
128 (Slow(_), _) => Less,
129
130 (None, None) => Equal,
131 (None, Slow(_)) => Greater,
132 (None, Fast(_)) => Less,
133
134 (Fast(a), Fast(b)) => a.cmp(b),
135 (Fast(_), _) => Greater,
136 }
137 }
138}
139
140impl PartialOrd for ClockSkew {
141 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
142 Some(self.cmp(other))
143 }
144}
145
146#[cfg(test)]
147mod test {
148 #![allow(clippy::bool_assert_comparison)]
150 #![allow(clippy::clone_on_copy)]
151 #![allow(clippy::dbg_macro)]
152 #![allow(clippy::mixed_attributes_style)]
153 #![allow(clippy::print_stderr)]
154 #![allow(clippy::print_stdout)]
155 #![allow(clippy::single_char_pattern)]
156 #![allow(clippy::unwrap_used)]
157 #![allow(clippy::unchecked_duration_subtraction)]
158 #![allow(clippy::useless_vec)]
159 #![allow(clippy::needless_pass_by_value)]
160 use super::*;
163 use tor_basic_utils::test_rng::testing_rng;
164
165 #[test]
166 fn make_skew() {
167 let now = SystemTime::now();
168 let later = now + Duration::from_secs(777);
169 let earlier = now - Duration::from_secs(333);
170 let window = Duration::from_secs(30);
171
172 let skew = ClockSkew::from_handshake_timestamps(now, later, window);
174 assert_eq!(skew, ClockSkew::Slow(Duration::from_secs(747)));
176
177 let skew = ClockSkew::from_handshake_timestamps(now, earlier, window);
179 assert_eq!(skew, ClockSkew::Fast(Duration::from_secs(333)));
180
181 let skew = ClockSkew::from_handshake_timestamps(now, now + Duration::from_secs(20), window);
183 assert_eq!(skew, ClockSkew::None);
184
185 let skew = ClockSkew::from_handshake_timestamps(
187 now,
188 now + Duration::from_millis(500),
189 Duration::from_secs(0),
190 );
191 assert_eq!(skew, ClockSkew::None);
192 }
193
194 #[test]
195 fn from_f64() {
196 use ClockSkew as CS;
197 use Duration as D;
198
199 assert_eq!(CS::from_secs_f64(0.0), Some(CS::None));
200 assert_eq!(CS::from_secs_f64(f64::MIN_POSITIVE / 2.0), Some(CS::None)); assert_eq!(CS::from_secs_f64(1.0), Some(CS::None));
202 assert_eq!(CS::from_secs_f64(-1.0), Some(CS::None));
203 assert_eq!(CS::from_secs_f64(3.0), Some(CS::Fast(D::from_secs(3))));
204 assert_eq!(CS::from_secs_f64(-3.0), Some(CS::Slow(D::from_secs(3))));
205
206 assert_eq!(CS::from_secs_f64(1.0e100), Some(CS::Fast(D::MAX)));
207 assert_eq!(CS::from_secs_f64(-1.0e100), Some(CS::Slow(D::MAX)));
208
209 assert_eq!(CS::from_secs_f64(f64::NAN), None);
210 assert_eq!(CS::from_secs_f64(f64::INFINITY), Some(CS::Fast(D::MAX)));
211 assert_eq!(CS::from_secs_f64(f64::NEG_INFINITY), Some(CS::Slow(D::MAX)));
212 }
213
214 #[test]
215 fn order() {
216 use rand::seq::SliceRandom as _;
217 use ClockSkew as CS;
218 let sorted: Vec<ClockSkew> = vec![-10.0, -5.0, 0.0, 0.0, 10.0, 20.0]
219 .into_iter()
220 .map(|n| CS::from_secs_f64(n).unwrap())
221 .collect();
222
223 let mut rng = testing_rng();
224 let mut v = sorted.clone();
225 for _ in 0..100 {
226 v.shuffle(&mut rng);
227 v.sort();
228 assert_eq!(v, sorted);
229 }
230 }
231}