use std::collections::HashMap;
use std::net::Ipv6Addr;
use std::time::{Duration, Instant};
pub struct IcmpRateLimiter {
last_sent: HashMap<Ipv6Addr, Instant>,
min_interval: Duration,
max_age: Duration,
}
impl IcmpRateLimiter {
pub fn new() -> Self {
Self {
last_sent: HashMap::new(),
min_interval: Duration::from_millis(100),
max_age: Duration::from_secs(10),
}
}
pub fn with_interval(min_interval: Duration) -> Self {
Self {
last_sent: HashMap::new(),
min_interval,
max_age: Duration::from_secs(10),
}
}
pub fn should_send(&mut self, src_addr: Ipv6Addr) -> bool {
let now = Instant::now();
if let Some(&last) = self.last_sent.get(&src_addr)
&& now.duration_since(last) < self.min_interval
{
return false; }
self.last_sent.insert(src_addr, now);
self.cleanup(now);
true
}
fn cleanup(&mut self, now: Instant) {
self.last_sent
.retain(|_, &mut last| now.duration_since(last) < self.max_age);
}
#[cfg(test)]
pub fn len(&self) -> usize {
self.last_sent.len()
}
#[cfg(test)]
pub fn is_empty(&self) -> bool {
self.last_sent.is_empty()
}
}
impl Default for IcmpRateLimiter {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_first_send_allowed() {
let mut limiter = IcmpRateLimiter::new();
let addr: Ipv6Addr = "fd00::1".parse().unwrap();
assert!(limiter.should_send(addr));
}
#[test]
fn test_rapid_sends_rate_limited() {
let mut limiter = IcmpRateLimiter::new();
let addr: Ipv6Addr = "fd00::1".parse().unwrap();
assert!(limiter.should_send(addr));
assert!(!limiter.should_send(addr));
assert!(!limiter.should_send(addr));
}
#[test]
fn test_different_sources_independent() {
let mut limiter = IcmpRateLimiter::new();
let addr1: Ipv6Addr = "fd00::1".parse().unwrap();
let addr2: Ipv6Addr = "fd00::2".parse().unwrap();
assert!(limiter.should_send(addr1));
assert!(limiter.should_send(addr2));
assert!(!limiter.should_send(addr1));
assert!(!limiter.should_send(addr2));
}
#[test]
fn test_send_allowed_after_interval() {
let mut limiter = IcmpRateLimiter::with_interval(Duration::from_millis(50));
let addr: Ipv6Addr = "fd00::1".parse().unwrap();
assert!(limiter.should_send(addr));
thread::sleep(Duration::from_millis(60));
assert!(limiter.should_send(addr));
}
#[test]
fn test_cleanup_removes_old_entries() {
let mut limiter = IcmpRateLimiter::new();
let addr1: Ipv6Addr = "fd00::1".parse().unwrap();
let addr2: Ipv6Addr = "fd00::2".parse().unwrap();
assert!(limiter.should_send(addr1));
assert!(limiter.should_send(addr2));
assert_eq!(limiter.len(), 2);
let future = Instant::now() + Duration::from_secs(11);
limiter.cleanup(future);
assert_eq!(limiter.len(), 0);
}
#[test]
fn test_cleanup_preserves_recent_entries() {
let mut limiter = IcmpRateLimiter::new();
let addr: Ipv6Addr = "fd00::1".parse().unwrap();
assert!(limiter.should_send(addr));
assert_eq!(limiter.len(), 1);
limiter.cleanup(Instant::now());
assert_eq!(limiter.len(), 1);
}
}