#[cfg(feature = "websocket")]
use std::collections::HashMap;
#[cfg(feature = "websocket")]
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
#[cfg(feature = "websocket")]
use std::sync::{Arc, RwLock};
#[cfg(feature = "websocket")]
use crate::websocket::message::WebSocketMessage;
#[cfg(feature = "websocket")]
#[derive(Clone)]
pub struct WebSocketConnection {
id: String,
sender: tokio::sync::mpsc::UnboundedSender<WebSocketMessage>,
}
#[cfg(feature = "websocket")]
impl WebSocketConnection {
pub fn new(id: String) -> (Self, tokio::sync::mpsc::UnboundedReceiver<WebSocketMessage>) {
let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
(Self { id, sender }, receiver)
}
pub fn id(&self) -> &str {
&self.id
}
pub async fn send(&self, message: WebSocketMessage) -> Result<(), Box<dyn std::error::Error>> {
self.sender.send(message).map_err(|e| e.into())
}
}
#[cfg(feature = "websocket")]
pub struct ConnectionManager {
pub(crate) connections: Arc<RwLock<HashMap<String, WebSocketConnection>>>,
pub(crate) message_counts: Arc<RwLock<HashMap<String, AtomicU64>>>,
pub(crate) connection_count: Arc<AtomicUsize>,
pub(crate) last_message_time: Arc<RwLock<HashMap<String, AtomicU64>>>,
}
#[derive(Debug, Clone)]
pub struct RateLimitConfig {
pub max_messages_per_second: u64,
pub max_message_size: usize,
pub max_connections: usize,
pub rate_limit_window_seconds: u64,
}
impl RateLimitConfig {
pub fn validate(&self) -> Result<(), String> {
if self.max_connections == 0 {
return Err("max_connections must be greater than 0".to_string());
}
if self.max_connections > 100_000 {
return Err("max_connections exceeds reasonable limit of 100,000".to_string());
}
if self.max_messages_per_second == 0 {
return Err("max_messages_per_second must be greater than 0".to_string());
}
if self.max_messages_per_second > 1_000_000 {
return Err(
"max_messages_per_second exceeds reasonable limit of 1,000,000".to_string(),
);
}
if self.max_message_size == 0 {
return Err("max_message_size must be greater than 0".to_string());
}
if self.max_message_size > 100_000_000 {
return Err("max_message_size exceeds reasonable limit of 100MB".to_string());
}
if self.rate_limit_window_seconds == 0 {
return Err("rate_limit_window_seconds must be greater than 0".to_string());
}
if self.rate_limit_window_seconds > 86400 {
return Err(
"rate_limit_window_seconds exceeds reasonable limit of 86400 (24 hours)"
.to_string(),
);
}
Ok(())
}
}
impl Default for RateLimitConfig {
fn default() -> Self {
Self {
max_messages_per_second: 100,
max_message_size: 1_048_576, max_connections: 1000,
rate_limit_window_seconds: 1,
}
}
}
#[derive(Clone, Default)]
pub struct WebSocketConfig {
#[cfg(feature = "security")]
pub auth: Option<crate::security::BearerAuth>,
pub rate_limit: RateLimitConfig,
}
#[cfg(feature = "websocket")]
#[derive(Clone)]
pub struct AppState {
pub config: Arc<WebSocketConfig>,
pub manager: Arc<ConnectionManager>,
}
#[cfg(feature = "websocket")]
impl AppState {
pub fn new(manager: Arc<ConnectionManager>) -> Self {
Self {
config: Arc::new(WebSocketConfig::default()),
manager,
}
}
pub fn with_config(config: WebSocketConfig, manager: Arc<ConnectionManager>) -> Self {
Self {
config: Arc::new(config),
manager,
}
}
}
#[cfg(feature = "websocket")]
impl ConnectionManager {
pub fn new() -> Self {
Self {
connections: Arc::new(RwLock::new(HashMap::new())),
message_counts: Arc::new(RwLock::new(HashMap::new())),
connection_count: Arc::new(AtomicUsize::new(0)),
last_message_time: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn check_and_record(&self, conn_id: &str, config: &RateLimitConfig) -> bool {
let current_time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
if self.connection_count.load(Ordering::SeqCst) >= config.max_connections {
return true;
}
let mut should_disconnect = false;
let mut counts = match self.message_counts.write() {
Ok(g) => g,
Err(_) => {
log::warn!(
"websocket message_counts lock poisoned; rate-limit check skipped for conn={}",
conn_id
);
return false;
}
};
let mut times = match self.last_message_time.write() {
Ok(g) => g,
Err(_) => {
log::warn!(
"websocket last_message_time lock poisoned; rate-limit check skipped for conn={}",
conn_id
);
return false;
}
};
if counts.contains_key(conn_id) {
let last_time = times
.get(conn_id)
.map(|t| t.load(Ordering::Relaxed))
.unwrap_or(0);
if current_time.saturating_sub(last_time) >= config.rate_limit_window_seconds {
if let Some(count) = counts.get(conn_id) {
count.store(1, Ordering::Relaxed);
}
if let Some(time_entry) = times.get_mut(conn_id) {
time_entry.store(current_time, Ordering::Relaxed);
}
} else {
let count_val = counts
.get(conn_id)
.map(|c| c.load(Ordering::Relaxed))
.unwrap_or(0);
if count_val >= config.max_messages_per_second {
should_disconnect = true;
} else if let Some(count) = counts.get(conn_id) {
count.fetch_add(1, Ordering::Relaxed);
}
}
} else {
counts.insert(conn_id.to_string(), AtomicU64::new(1));
times.insert(conn_id.to_string(), AtomicU64::new(current_time));
}
should_disconnect
}
pub async fn add_connection(&self, id: String, conn: WebSocketConnection) {
{
if let Ok(mut map) = self.connections.write() {
map.insert(id, conn);
} else {
log::warn!("websocket connections lock poisoned; add_connection skipped");
return;
}
}
self.connection_count.fetch_add(1, Ordering::Relaxed);
}
pub async fn remove_connection(&self, id: &str) {
let existed = {
if let Ok(mut map) = self.connections.write() {
map.remove(id).is_some()
} else {
log::warn!("websocket connections lock poisoned; remove_connection skipped");
return;
}
};
if !existed {
return;
}
self.connection_count.fetch_sub(1, Ordering::Relaxed);
if let Ok(mut counts) = self.message_counts.write() {
counts.remove(id);
}
if let Ok(mut times) = self.last_message_time.write() {
times.remove(id);
}
}
pub async fn get_connection(&self, id: &str) -> Option<WebSocketConnection> {
let map = match self.connections.read() {
Ok(g) => g,
Err(_) => {
log::warn!("websocket connections lock poisoned; get_connection returned None");
return None;
}
};
map.get(id).cloned()
}
pub async fn connection_count(&self) -> usize {
self.connection_count.load(Ordering::Relaxed)
}
}
#[cfg(feature = "websocket")]
impl Default for ConnectionManager {
fn default() -> Self {
Self::new()
}
}