1use std::time::{SystemTime, UNIX_EPOCH};
2
3#[derive(Debug, Clone)]
5pub struct ClockSkewWarning {
6 pub event_ts: u64,
8 pub wall_ts: u64,
10 pub skew_secs: i64,
13 pub threshold_secs: u64,
15 pub message: String,
17}
18
19pub const DEFAULT_SKEW_THRESHOLD_SECS: u64 = 300;
21
22#[must_use]
28pub fn wall_clock_now() -> u64 {
29 SystemTime::now()
30 .duration_since(UNIX_EPOCH)
31 .expect("Time went backwards")
32 .as_secs()
33}
34
35#[must_use]
41pub fn check_clock_skew(
42 event_ts: u64,
43 wall_ts: u64,
44 threshold_secs: u64,
45) -> Option<ClockSkewWarning> {
46 let skew_secs = event_ts.cast_signed() - wall_ts.cast_signed();
47 let abs_skew = skew_secs.unsigned_abs();
48
49 if abs_skew > threshold_secs {
50 let direction = if skew_secs > 0 { "future" } else { "past" };
51 let message = format!(
52 "Clock skew detected: event is {abs_skew} seconds in the {direction}, threshold is {threshold_secs} seconds"
53 );
54
55 Some(ClockSkewWarning {
56 event_ts,
57 wall_ts,
58 skew_secs,
59 threshold_secs,
60 message,
61 })
62 } else {
63 None
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn test_no_skew() {
73 let wall = 1000;
74 let event = 1050;
75 assert!(check_clock_skew(event, wall, 100).is_none());
76 }
77
78 #[test]
79 fn test_future_skew() {
80 let wall = 1000;
81 let event = 1200;
82 let warning = check_clock_skew(event, wall, 100).unwrap();
83 assert_eq!(warning.skew_secs, 200);
84 assert!(warning.message.contains("future"));
85 }
86
87 #[test]
88 fn test_past_skew() {
89 let wall = 1000;
90 let event = 800;
91 let warning = check_clock_skew(event, wall, 100).unwrap();
92 assert_eq!(warning.skew_secs, -200);
93 assert!(warning.message.contains("past"));
94 }
95}