use std::sync::Arc;
use crate::helpers::sync::Lock;
pub struct GateState {
pub per_ip: std::collections::HashMap<std::net::IpAddr, u32>,
pub history: std::collections::HashMap<std::net::IpAddr, std::collections::VecDeque<std::time::Instant>>,
}
pub struct Gate {
pub max_connections: u32,
pub max_connections_per_ip: u32,
pub max_connection_rate: Vec<(f64, u32)>,
pub max_connection_history: usize,
pub window: f64,
pub connections: std::sync::atomic::AtomicU32,
pub state: std::sync::Mutex<GateState>,
}
impl Gate {
pub fn new(max_connections: u32, max_connections_per_ip: u32, max_connection_rate: Vec<(f64, u32)>, max_connection_history: usize) -> Arc<Self> {
Arc::new(Self {
window: max_connection_rate.iter().map(|(period, _)| *period).fold(0.0, f64::max),
max_connections,
max_connections_per_ip,
max_connection_rate,
max_connection_history,
connections: std::sync::atomic::AtomicU32::new(0),
state: std::sync::Mutex::new(GateState {
per_ip: std::collections::HashMap::new(),
history: std::collections::HashMap::new(),
}),
})
}
pub fn count(&self) -> u32 {
self.connections.load(std::sync::atomic::Ordering::Acquire)
}
pub fn window(&self) -> f64 {
self.window
}
pub fn admit(self: &Arc<Self>, ip: Option<std::net::IpAddr>, now: std::time::Instant) -> Option<Permit> {
use std::sync::atomic::Ordering;
loop {
let current = self.connections.load(Ordering::Acquire);
if self.max_connections != 0 && current >= self.max_connections {
return None;
}
if self
.connections
.compare_exchange_weak(current, current + 1, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
break;
}
}
if let Some(ip) = ip {
let mut state = Lock::on(&self.state);
let count = state.per_ip.get(&ip).copied().unwrap_or(0);
let over_ip = self.max_connections_per_ip != 0 && count >= self.max_connections_per_ip;
if over_ip || !self.rate(&mut state, ip, now) {
drop(state);
self.connections.fetch_sub(1, Ordering::AcqRel);
return None;
}
self.bound_history(&mut state, ip);
*state.per_ip.entry(ip).or_insert(0) += 1;
}
Some(Permit { gate: Arc::clone(self), ip })
}
pub fn rate(&self, state: &mut GateState, ip: std::net::IpAddr, now: std::time::Instant) -> bool {
let window = self.window();
let record = state.history.entry(ip).or_default();
while record.front().is_some_and(|front| now.duration_since(*front).as_secs_f64() > window) {
record.pop_front();
}
for &(period, count) in &self.max_connection_rate {
let recent = record.iter().filter(|at| now.duration_since(**at).as_secs_f64() <= period).count() as u32;
if recent >= count {
return false;
}
}
record.push_back(now);
true
}
pub fn bound_history(&self, state: &mut GateState, keep: std::net::IpAddr) {
let cap = self.max_connection_history.max(self.max_connections as usize);
while state.history.len() > cap {
let victim = state
.history
.iter()
.filter(|(address, _)| **address != keep)
.min_by_key(|(_, record)| record.front().copied())
.map(|(address, _)| *address);
let Some(victim) = victim else {
break;
};
state.history.remove(&victim);
}
}
pub fn release(&self, ip: Option<std::net::IpAddr>) {
self.connections.fetch_sub(1, std::sync::atomic::Ordering::AcqRel);
if let Some(ip) = ip {
let mut state = Lock::on(&self.state);
if let Some(count) = state.per_ip.get_mut(&ip) {
*count = count.saturating_sub(1);
if *count == 0 {
state.per_ip.remove(&ip);
}
}
}
}
pub fn sweep(&self, now: std::time::Instant) {
let window = self.window();
let mut state = Lock::on(&self.state);
state.history.retain(|_, record| {
while record.front().is_some_and(|front| now.duration_since(*front).as_secs_f64() > window) {
record.pop_front();
}
!record.is_empty()
});
}
}
pub struct Permit {
pub gate: Arc<Gate>,
pub ip: Option<std::net::IpAddr>,
}
impl Drop for Permit {
fn drop(&mut self) {
self.gate.release(self.ip);
}
}