nxtquic-sim 0.1.3

Deterministic network simulator for NxtQuic testing
Documentation
//! Network simulation channel and datagram queue.

use std::collections::VecDeque;
use std::net::SocketAddr;
use std::time::Instant;

/// Represents a datagram that has been queued for simulated delivery.
#[derive(Debug, Clone)]
pub struct QueuedDatagram {
    /// The instant at which this datagram should be delivered.
    pub delivery_time: Instant,
    /// The raw payload of the datagram.
    pub payload: Vec<u8>,
    /// The source address of the datagram.
    pub src: SocketAddr,
    /// The destination address of the datagram.
    pub dst: SocketAddr,
}

/// A simulated network channel that holds queued datagrams and delivers them at the scheduled `Instant`.
#[derive(Debug, Default)]
pub struct SimChannel {
    queue: VecDeque<QueuedDatagram>,
}

impl SimChannel {
    /// Creates a new `SimChannel`.
    pub fn new() -> Self {
        Self {
            queue: VecDeque::new(),
        }
    }

    /// Pushes a new datagram into the channel, maintaining delivery order.
    pub fn push(&mut self, datagram: QueuedDatagram) {
        // Keep the queue sorted by delivery_time
        let idx = self
            .queue
            .binary_search_by_key(&datagram.delivery_time, |d| d.delivery_time)
            .unwrap_or_else(|x| x);
        self.queue.insert(idx, datagram);
    }

    /// Pops the next ready datagram from the channel, if its delivery time is <= `now`.
    pub fn pop_ready(&mut self, now: Instant) -> Option<QueuedDatagram> {
        if let Some(front) = self.queue.front() {
            if front.delivery_time <= now {
                return self.queue.pop_front();
            }
        }
        None
    }
}

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

    #[test]
    fn test_sim_channel_ordering() {
        let mut channel = SimChannel::new();
        let now = Instant::now();
        let src: SocketAddr = "127.0.0.1:1000".parse().unwrap();
        let dst: SocketAddr = "127.0.0.1:2000".parse().unwrap();

        channel.push(QueuedDatagram {
            delivery_time: now + Duration::from_millis(20),
            payload: vec![2],
            src,
            dst,
        });

        channel.push(QueuedDatagram {
            delivery_time: now + Duration::from_millis(10),
            payload: vec![1],
            src,
            dst,
        });

        // The one with 10ms delay should be popped first
        assert!(channel.pop_ready(now).is_none());
        assert!(channel.pop_ready(now + Duration::from_millis(15)).is_some());
        assert!(channel.pop_ready(now + Duration::from_millis(15)).is_none());

        let next = channel.pop_ready(now + Duration::from_millis(25)).unwrap();
        assert_eq!(next.payload, vec![2]);
    }
}