liarsping 0.1.0

A ping server which attempts to manipulate the ping times seen by the client
Documentation
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);

/// Classify a payload. Returns `Timestamp` iff the payload is at least 12 bytes
/// and bytes 0..8 parse as a Unix `secs` value within `PLAUSIBLE_WINDOW` of `now`.
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]); // typical Linux ping payload size
        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); // 1 min ago
        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);
    }
}