use std::{collections::HashMap, net::IpAddr, sync::Arc};
use tokio::time::Instant;
use crate::{constants, protocol::external::connection_limit_key};
#[cfg(test)]
mod tests;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct BanList {
banned_at: Arc<HashMap<IpAddr, Instant>>,
}
impl BanList {
pub fn is_banned(&self, ip: IpAddr) -> bool {
self.banned_at
.get(&connection_limit_key(ip))
.is_some_and(|banned_at| !Self::has_lapsed(*banned_at, Instant::now()))
}
pub(crate) fn ban(&mut self, ip: IpAddr) {
let now = Instant::now();
let banned_at = Arc::make_mut(&mut self.banned_at);
banned_at.retain(|_group, entry| !Self::has_lapsed(*entry, now));
banned_at.insert(connection_limit_key(ip), now);
while banned_at.len() > constants::MAX_BANNED_IPS {
let oldest = banned_at
.iter()
.min_by_key(|(_group, entry)| **entry)
.map(|(group, _entry)| *group)
.expect("the map is over the limit, so it is not empty");
banned_at.remove(&oldest);
}
}
fn has_lapsed(banned_at: Instant, now: Instant) -> bool {
now.saturating_duration_since(banned_at) >= constants::BAN_DURATION
}
pub fn len(&self) -> usize {
self.banned_at.len()
}
pub fn is_empty(&self) -> bool {
self.banned_at.is_empty()
}
}