use std::time::{Duration, SystemTime};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[allow(clippy::exhaustive_enums)]
pub enum ClockSkew {
Slow(Duration),
None,
Fast(Duration),
}
const MIN: Duration = Duration::from_secs(2);
impl ClockSkew {
pub(crate) fn from_handshake_timestamps(
ours_at_start: SystemTime,
theirs: SystemTime,
delay: Duration,
) -> Self {
let theirs_at_start_min = theirs - delay;
let theirs_at_start_max = theirs;
if let Ok(skew) = theirs_at_start_min.duration_since(ours_at_start) {
ClockSkew::Slow(skew).if_above(MIN)
} else if let Ok(skew) = ours_at_start.duration_since(theirs_at_start_max) {
ClockSkew::Fast(skew).if_above(MIN)
} else {
ClockSkew::None
}
}
pub fn magnitude(&self) -> Duration {
match self {
ClockSkew::Slow(d) => *d,
ClockSkew::None => Duration::from_secs(0),
ClockSkew::Fast(d) => *d,
}
}
pub fn as_secs_f64(&self) -> f64 {
match self {
ClockSkew::Slow(d) => -d.as_secs_f64(),
ClockSkew::None => 0.0,
ClockSkew::Fast(d) => d.as_secs_f64(),
}
}
pub fn from_secs_f64(seconds: f64) -> Option<Self> {
use std::num::FpCategory;
let max_seconds = Duration::MAX.as_secs_f64();
match seconds.classify() {
FpCategory::Nan => None,
FpCategory::Zero | FpCategory::Subnormal => Some(ClockSkew::None),
FpCategory::Normal | FpCategory::Infinite => Some(if seconds <= -max_seconds {
ClockSkew::Slow(Duration::MAX)
} else if seconds < 0.0 {
ClockSkew::Slow(Duration::from_secs_f64(-seconds)).if_above(MIN)
} else if seconds < max_seconds {
ClockSkew::Fast(Duration::from_secs_f64(seconds)).if_above(MIN)
} else {
ClockSkew::Fast(Duration::MAX)
}),
}
}
pub fn if_above(self, min: Duration) -> Self {
if self.magnitude() > min {
self
} else {
ClockSkew::None
}
}
pub fn is_skewed(&self) -> bool {
!matches!(self, ClockSkew::None)
}
}
impl Ord for ClockSkew {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
use std::cmp::Ordering::*;
use ClockSkew::*;
match (self, other) {
(Slow(a), Slow(b)) => a.cmp(b).reverse(),
(Slow(_), _) => Less,
(None, None) => Equal,
(None, Slow(_)) => Greater,
(None, Fast(_)) => Less,
(Fast(a), Fast(b)) => a.cmp(b),
(Fast(_), _) => Greater,
}
}
}
impl PartialOrd for ClockSkew {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[cfg(test)]
mod test {
#![allow(clippy::unwrap_used)]
use super::*;
use tor_basic_utils::test_rng::testing_rng;
#[test]
fn make_skew() {
let now = SystemTime::now();
let later = now + Duration::from_secs(777);
let earlier = now - Duration::from_secs(333);
let window = Duration::from_secs(30);
let skew = ClockSkew::from_handshake_timestamps(now, later, window);
assert_eq!(skew, ClockSkew::Slow(Duration::from_secs(747)));
let skew = ClockSkew::from_handshake_timestamps(now, earlier, window);
assert_eq!(skew, ClockSkew::Fast(Duration::from_secs(333)));
let skew = ClockSkew::from_handshake_timestamps(now, now + Duration::from_secs(20), window);
assert_eq!(skew, ClockSkew::None);
let skew = ClockSkew::from_handshake_timestamps(
now,
now + Duration::from_millis(500),
Duration::from_secs(0),
);
assert_eq!(skew, ClockSkew::None);
}
#[test]
fn from_f64() {
use ClockSkew as CS;
use Duration as D;
assert_eq!(CS::from_secs_f64(0.0), Some(CS::None));
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));
assert_eq!(CS::from_secs_f64(-1.0), Some(CS::None));
assert_eq!(CS::from_secs_f64(3.0), Some(CS::Fast(D::from_secs(3))));
assert_eq!(CS::from_secs_f64(-3.0), Some(CS::Slow(D::from_secs(3))));
assert_eq!(CS::from_secs_f64(1.0e100), Some(CS::Fast(D::MAX)));
assert_eq!(CS::from_secs_f64(-1.0e100), Some(CS::Slow(D::MAX)));
assert_eq!(CS::from_secs_f64(f64::NAN), None);
assert_eq!(CS::from_secs_f64(f64::INFINITY), Some(CS::Fast(D::MAX)));
assert_eq!(CS::from_secs_f64(f64::NEG_INFINITY), Some(CS::Slow(D::MAX)));
}
#[test]
fn order() {
use rand::seq::SliceRandom as _;
use ClockSkew as CS;
let sorted: Vec<ClockSkew> = vec![-10.0, -5.0, 0.0, 0.0, 10.0, 20.0]
.into_iter()
.map(|n| CS::from_secs_f64(n).unwrap())
.collect();
let mut rng = testing_rng();
let mut v = sorted.clone();
for _ in 0..100 {
v.shuffle(&mut rng);
v.sort();
assert_eq!(v, sorted);
}
}
}