use std::collections::HashMap;
use std::net::Ipv4Addr;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy)]
pub struct SenderState {
pub last_seq: u16,
pub last_seen: Instant,
}
#[derive(Default)]
pub struct SenderTable {
inner: HashMap<(Ipv4Addr, u16), SenderState>,
}
const RESET_THRESHOLD: u16 = 100;
impl SenderTable {
pub fn new() -> Self { Self::default() }
pub fn touch(&mut self, key: (Ipv4Addr, u16), seq: u16, now: Instant) -> bool {
match self.inner.get_mut(&key) {
None => {
self.inner.insert(key, SenderState { last_seq: seq, last_seen: now });
true
}
Some(s) => {
let reset = seq < s.last_seq && (s.last_seq - seq) >= RESET_THRESHOLD;
s.last_seq = seq;
s.last_seen = now;
reset
}
}
}
pub fn evict_idle(&mut self, older_than: Duration, now: Instant) {
self.inner.retain(|_, s| now.duration_since(s.last_seen) < older_than);
}
pub fn len(&self) -> usize { self.inner.len() }
pub fn is_empty(&self) -> bool { self.inner.is_empty() }
}
#[cfg(test)]
mod tests {
use super::*;
fn key() -> (Ipv4Addr, u16) { (Ipv4Addr::new(10, 0, 0, 1), 0x1234) }
#[test]
fn first_touch_is_first() {
let mut t = SenderTable::new();
assert!(t.touch(key(), 1, Instant::now()));
}
#[test]
fn second_touch_is_not_first() {
let mut t = SenderTable::new();
let now = Instant::now();
t.touch(key(), 1, now);
assert!(!t.touch(key(), 2, now + Duration::from_millis(100)));
}
#[test]
fn sequence_reset_counts_as_first() {
let mut t = SenderTable::new();
let now = Instant::now();
t.touch(key(), 5_000, now);
assert!(t.touch(key(), 1, now + Duration::from_secs(1)));
}
#[test]
fn small_backward_jump_is_not_reset() {
let mut t = SenderTable::new();
let now = Instant::now();
t.touch(key(), 50, now);
assert!(!t.touch(key(), 49, now + Duration::from_millis(10)));
}
#[test]
fn evict_idle_drops_old_entries() {
let mut t = SenderTable::new();
let now = Instant::now();
t.touch(key(), 1, now);
t.evict_idle(Duration::from_secs(60), now + Duration::from_secs(120));
assert_eq!(t.len(), 0);
}
#[test]
fn evict_idle_keeps_recent_entries() {
let mut t = SenderTable::new();
let now = Instant::now();
t.touch(key(), 1, now);
t.evict_idle(Duration::from_secs(60), now + Duration::from_secs(10));
assert_eq!(t.len(), 1);
}
}