use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Strategy {
Timestamp,
Sequence,
}
const PLAUSIBLE_WINDOW: Duration = Duration::from_secs(2 * 24 * 60 * 60);
pub fn classify(payload: &[u8], now: SystemTime) -> Strategy {
if payload.len() < 12 {
return Strategy::Sequence;
}
let secs = u64::from_le_bytes(payload[0..8].try_into().unwrap());
let now_secs = now.duration_since(UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0);
let window = PLAUSIBLE_WINDOW.as_secs();
let diff = secs.abs_diff(now_secs);
if diff <= window {
Strategy::Timestamp
} else {
Strategy::Sequence
}
}
#[cfg(test)]
mod tests {
use super::*;
fn now_at(secs: u64) -> SystemTime {
UNIX_EPOCH + Duration::from_secs(secs)
}
fn payload_with_secs(secs: u64) -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(&secs.to_le_bytes());
v.extend_from_slice(&0u32.to_le_bytes());
v.extend_from_slice(&[0u8; 40]); v
}
#[test]
fn recent_timestamp_classified_as_timestamp() {
let now = now_at(1_800_000_000);
let p = payload_with_secs(1_800_000_000);
assert_eq!(classify(&p, now), Strategy::Timestamp);
}
#[test]
fn slightly_off_timestamp_still_classified_as_timestamp() {
let now = now_at(1_800_000_000);
let p = payload_with_secs(1_800_000_000 - 60); assert_eq!(classify(&p, now), Strategy::Timestamp);
}
#[test]
fn far_past_classified_as_sequence() {
let now = now_at(1_800_000_000);
let p = payload_with_secs(0);
assert_eq!(classify(&p, now), Strategy::Sequence);
}
#[test]
fn far_future_classified_as_sequence() {
let now = now_at(1_800_000_000);
let p = payload_with_secs(9_000_000_000);
assert_eq!(classify(&p, now), Strategy::Sequence);
}
#[test]
fn short_payload_classified_as_sequence() {
let now = now_at(1_800_000_000);
let p = vec![0u8; 8];
assert_eq!(classify(&p, now), Strategy::Sequence);
}
}