Skip to main content

bones_core/clock/
skew.rs

1use std::time::{SystemTime, UNIX_EPOCH};
2
3/// Warning emitted when clock skew is detected.
4#[derive(Debug, Clone)]
5pub struct ClockSkewWarning {
6    /// The event timestamp that triggered the warning.
7    pub event_ts: u64,
8    /// The current wall-clock time.
9    pub wall_ts: u64,
10    /// The detected skew in seconds (positive = event is in the future,
11    /// negative = event is significantly in the past).
12    pub skew_secs: i64,
13    /// The threshold that was exceeded.
14    pub threshold_secs: u64,
15    /// Human-readable warning message.
16    pub message: String,
17}
18
19/// Default skew threshold in seconds (5 minutes).
20pub const DEFAULT_SKEW_THRESHOLD_SECS: u64 = 300;
21
22/// Get the current wall-clock time as Unix epoch seconds.
23///
24/// # Panics
25///
26/// Panics if the system clock is before the Unix epoch.
27#[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/// Check for clock skew between an event's timestamp and the current wall time.
36/// Returns Some(warning) if the absolute difference exceeds `threshold_secs`.
37///
38/// Called during event write (before appending to shard) to warn the user.
39/// Events are NEVER rejected due to clock skew — ITC ordering is authoritative.
40#[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}