use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone)]
pub struct ClockSkewWarning {
pub event_ts: u64,
pub wall_ts: u64,
pub skew_secs: i64,
pub threshold_secs: u64,
pub message: String,
}
pub const DEFAULT_SKEW_THRESHOLD_SECS: u64 = 300;
#[must_use]
pub fn wall_clock_now() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs()
}
#[must_use]
pub fn check_clock_skew(
event_ts: u64,
wall_ts: u64,
threshold_secs: u64,
) -> Option<ClockSkewWarning> {
let skew_secs = event_ts.cast_signed() - wall_ts.cast_signed();
let abs_skew = skew_secs.unsigned_abs();
if abs_skew > threshold_secs {
let direction = if skew_secs > 0 { "future" } else { "past" };
let message = format!(
"Clock skew detected: event is {abs_skew} seconds in the {direction}, threshold is {threshold_secs} seconds"
);
Some(ClockSkewWarning {
event_ts,
wall_ts,
skew_secs,
threshold_secs,
message,
})
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_skew() {
let wall = 1000;
let event = 1050;
assert!(check_clock_skew(event, wall, 100).is_none());
}
#[test]
fn test_future_skew() {
let wall = 1000;
let event = 1200;
let warning = check_clock_skew(event, wall, 100).unwrap();
assert_eq!(warning.skew_secs, 200);
assert!(warning.message.contains("future"));
}
#[test]
fn test_past_skew() {
let wall = 1000;
let event = 800;
let warning = check_clock_skew(event, wall, 100).unwrap();
assert_eq!(warning.skew_secs, -200);
assert!(warning.message.contains("past"));
}
}