use std::collections::HashMap;
use std::net::Ipv4Addr;
use std::time::{Duration, Instant};
pub struct LeasePool {
start: u32,
end: u32,
server_ip: u32,
ttl: Duration,
leases: HashMap<u32, Instant>,
cursor: u32,
}
impl LeasePool {
pub fn new(server_ip: Ipv4Addr, netmask: Ipv4Addr, ttl: Duration) -> Self {
let ip = u32::from(server_ip);
let mask = u32::from(netmask);
let network = ip & mask;
let broadcast = network | !mask;
let start = network.saturating_add(1);
let end = broadcast.saturating_sub(1);
Self {
start,
end,
server_ip: ip,
ttl,
leases: HashMap::new(),
cursor: start,
}
}
pub fn capacity(&self) -> usize {
if self.end < self.start {
return 0;
}
let span = (self.end - self.start + 1) as usize;
if (self.start..=self.end).contains(&self.server_ip) {
span - 1
} else {
span
}
}
pub fn allocate(&mut self, now: Instant) -> Option<Ipv4Addr> {
if self.end < self.start {
return None;
}
let span = self.end - self.start + 1;
for _ in 0..span {
let cand = self.cursor;
self.cursor = if cand >= self.end {
self.start
} else {
cand + 1
};
if cand == self.server_ip || self.leases.contains_key(&cand) {
continue;
}
self.leases.insert(cand, now);
return Some(Ipv4Addr::from(cand));
}
None
}
pub fn refresh(&mut self, ip: Ipv4Addr, now: Instant) -> bool {
if let Some(slot) = self.leases.get_mut(&u32::from(ip)) {
*slot = now;
true
} else {
false
}
}
pub fn is_leased(&self, ip: Ipv4Addr) -> bool {
self.leases.contains_key(&u32::from(ip))
}
pub fn reap(&mut self, now: Instant) -> Vec<Ipv4Addr> {
let ttl = self.ttl;
let expired: Vec<u32> = self
.leases
.iter()
.filter(|(_, &seen)| now.duration_since(seen) > ttl)
.map(|(&ip, _)| ip)
.collect();
for ip in &expired {
self.leases.remove(ip);
}
expired.into_iter().map(Ipv4Addr::from).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pool() -> LeasePool {
LeasePool::new(
Ipv4Addr::new(10, 9, 0, 1),
Ipv4Addr::new(255, 255, 255, 0),
Duration::from_secs(120),
)
}
#[test]
fn allocates_distinct_addresses_skipping_the_server() {
let mut p = pool();
let now = Instant::now();
let a = p.allocate(now).unwrap();
let b = p.allocate(now).unwrap();
assert_ne!(a, b);
assert_ne!(a, Ipv4Addr::new(10, 9, 0, 1));
assert_ne!(b, Ipv4Addr::new(10, 9, 0, 1));
for ip in [a, b] {
assert!(ip > Ipv4Addr::new(10, 9, 0, 1) || ip < Ipv4Addr::new(10, 9, 0, 1));
assert!(p.is_leased(ip));
}
}
#[test]
fn capacity_excludes_network_broadcast_and_server() {
assert_eq!(pool().capacity(), 253);
}
#[test]
fn exhaustion_returns_none() {
let mut p = LeasePool::new(
Ipv4Addr::new(10, 0, 0, 1),
Ipv4Addr::new(255, 255, 255, 252),
Duration::from_secs(60),
);
let now = Instant::now();
assert_eq!(p.capacity(), 1);
assert!(p.allocate(now).is_some());
assert!(p.allocate(now).is_none());
}
#[test]
fn refresh_only_known_leases() {
let mut p = pool();
let now = Instant::now();
let ip = p.allocate(now).unwrap();
assert!(p.refresh(ip, now));
assert!(!p.refresh(Ipv4Addr::new(10, 9, 0, 200), now));
}
#[test]
fn reap_reclaims_idle_leases_and_frees_them() {
let mut p = LeasePool::new(
Ipv4Addr::new(10, 9, 0, 1),
Ipv4Addr::new(255, 255, 255, 0),
Duration::from_secs(120),
);
let t0 = Instant::now();
let ip = p.allocate(t0).unwrap();
assert!(p.reap(t0 + Duration::from_secs(60)).is_empty());
assert!(p.is_leased(ip));
let freed = p.reap(t0 + Duration::from_secs(121));
assert_eq!(freed, vec![ip]);
assert!(!p.is_leased(ip));
}
}