liarsping 0.1.0

A ping server which attempts to manipulate the ping times seen by the client
Documentation
use std::cmp::Ordering;
use std::collections::BinaryHeap;
use std::net::Ipv4Addr;
use std::time::Instant;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PendingReply {
    pub deadline: Instant,
    pub dest: Ipv4Addr,
    pub identifier: u16,
    pub sequence: u16,
    pub payload: Vec<u8>,
}

// Reverse ordering by deadline so BinaryHeap acts as a min-heap.
impl PartialOrd for PendingReply { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) } }
impl Ord for PendingReply { fn cmp(&self, other: &Self) -> Ordering { other.deadline.cmp(&self.deadline) } }

#[derive(Default)]
pub struct Scheduler {
    heap: BinaryHeap<PendingReply>,
}

impl Scheduler {
    pub fn new() -> Self { Self::default() }

    pub fn push(&mut self, reply: PendingReply) {
        self.heap.push(reply);
    }

    pub fn pop_ready(&mut self, now: Instant) -> Vec<PendingReply> {
        let mut out = Vec::new();
        while let Some(top) = self.heap.peek() {
            if top.deadline <= now {
                out.push(self.heap.pop().unwrap());
            } else {
                break;
            }
        }
        out
    }

    pub fn next_deadline(&self) -> Option<Instant> {
        self.heap.peek().map(|r| r.deadline)
    }

    pub fn len(&self) -> usize { self.heap.len() }

    pub fn is_empty(&self) -> bool { self.heap.is_empty() }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::time::Duration;

    fn reply(deadline: Instant, seq: u16) -> PendingReply {
        PendingReply { deadline, dest: Ipv4Addr::LOCALHOST, identifier: 1, sequence: seq, payload: vec![] }
    }

    #[test]
    fn next_deadline_empty_is_none() {
        assert!(Scheduler::new().next_deadline().is_none());
    }

    #[test]
    fn next_deadline_returns_earliest() {
        let mut s = Scheduler::new();
        let t = Instant::now();
        s.push(reply(t + Duration::from_millis(500), 2));
        s.push(reply(t + Duration::from_millis(100), 1));
        assert_eq!(s.next_deadline(), Some(t + Duration::from_millis(100)));
    }

    #[test]
    fn pop_ready_returns_only_due_in_order() {
        let mut s = Scheduler::new();
        let t = Instant::now();
        s.push(reply(t + Duration::from_millis(300), 3));
        s.push(reply(t + Duration::from_millis(100), 1));
        s.push(reply(t + Duration::from_millis(200), 2));
        let due = s.pop_ready(t + Duration::from_millis(250));
        assert_eq!(due.len(), 2);
        assert_eq!(due[0].sequence, 1);
        assert_eq!(due[1].sequence, 2);
        assert_eq!(s.len(), 1);
    }

    #[test]
    fn pop_ready_nothing_due() {
        let mut s = Scheduler::new();
        let t = Instant::now();
        s.push(reply(t + Duration::from_millis(100), 1));
        assert!(s.pop_ready(t).is_empty());
        assert_eq!(s.len(), 1);
    }
}