1use crate::compression::CompressedPayload;
2use crate::websocket::auth::{AuthContext, AuthDeny};
3use crate::websocket::rate_limiter::{RateLimitResult, WebSocketRateLimiter};
4use arete_auth::Limits;
5use bytes::Bytes;
6use dashmap::DashMap;
7use futures_util::stream::SplitSink;
8use futures_util::SinkExt;
9use std::collections::HashMap;
10use std::net::SocketAddr;
11use std::sync::Arc;
12use std::time::{Duration, SystemTime};
13use tokio::net::TcpStream;
14use tokio::sync::{mpsc, RwLock};
15use tokio_tungstenite::{tungstenite::Message, WebSocketStream};
16use tokio_util::sync::CancellationToken;
17use tracing::{debug, info, warn};
18use uuid::Uuid;
19
20pub type WebSocketSender = SplitSink<WebSocketStream<TcpStream>, Message>;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum SendError {
25 ClientNotFound,
27 ClientBackpressured,
29 ClientDisconnected,
31}
32
33impl std::fmt::Display for SendError {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 match self {
36 SendError::ClientNotFound => write!(f, "client not found"),
37 SendError::ClientBackpressured => write!(f, "client backpressured and disconnected"),
38 SendError::ClientDisconnected => write!(f, "client disconnected"),
39 }
40 }
41}
42
43impl std::error::Error for SendError {}
44
45#[derive(Debug)]
47struct EgressTracker {
48 bytes_this_minute: u64,
50 window_start: SystemTime,
52}
53
54#[derive(Debug)]
56struct MessageRateTracker {
57 messages_this_minute: u32,
58 window_start: SystemTime,
59}
60
61impl MessageRateTracker {
62 fn new() -> Self {
63 Self {
64 messages_this_minute: 0,
65 window_start: SystemTime::now(),
66 }
67 }
68
69 fn maybe_reset_window(&mut self) {
70 let now = SystemTime::now();
71 if now.duration_since(self.window_start).unwrap_or_default() >= Duration::from_secs(60) {
72 self.messages_this_minute = 0;
73 self.window_start = now;
74 }
75 }
76
77 fn record_message(&mut self, limit: u32) -> bool {
78 self.maybe_reset_window();
79 if self.messages_this_minute + 1 > limit {
80 false
81 } else {
82 self.messages_this_minute += 1;
83 true
84 }
85 }
86
87 fn current_usage(&mut self) -> u32 {
88 self.maybe_reset_window();
89 self.messages_this_minute
90 }
91}
92
93impl EgressTracker {
94 fn new() -> Self {
95 Self {
96 bytes_this_minute: 0,
97 window_start: SystemTime::now(),
98 }
99 }
100
101 fn maybe_reset_window(&mut self) {
103 let now = SystemTime::now();
104 if now.duration_since(self.window_start).unwrap_or_default() >= Duration::from_secs(60) {
105 self.bytes_this_minute = 0;
106 self.window_start = now;
107 }
108 }
109
110 fn record_bytes(&mut self, bytes: usize, limit: u64) -> bool {
112 self.maybe_reset_window();
113 let bytes_u64 = bytes as u64;
114 if self.bytes_this_minute + bytes_u64 > limit {
115 false
116 } else {
117 self.bytes_this_minute += bytes_u64;
118 true
119 }
120 }
121
122 fn current_usage(&mut self) -> u64 {
124 self.maybe_reset_window();
125 self.bytes_this_minute
126 }
127}
128
129#[derive(Debug)]
131pub struct ClientInfo {
132 pub id: Uuid,
133 pub last_seen: SystemTime,
134 pub sender: mpsc::Sender<Message>,
135 subscriptions: Arc<RwLock<HashMap<String, CancellationToken>>>,
136 pub auth_context: Option<AuthContext>,
138 pub remote_addr: SocketAddr,
140 egress_tracker: std::sync::Mutex<EgressTracker>,
142 message_rate_tracker: std::sync::Mutex<MessageRateTracker>,
144}
145
146impl ClientInfo {
147 pub fn new(
148 id: Uuid,
149 sender: mpsc::Sender<Message>,
150 auth_context: Option<AuthContext>,
151 remote_addr: SocketAddr,
152 ) -> Self {
153 Self {
154 id,
155 last_seen: SystemTime::now(),
156 sender,
157 subscriptions: Arc::new(RwLock::new(HashMap::new())),
158 auth_context,
159 remote_addr,
160 egress_tracker: std::sync::Mutex::new(EgressTracker::new()),
161 message_rate_tracker: std::sync::Mutex::new(MessageRateTracker::new()),
162 }
163 }
164
165 pub fn record_egress(&self, bytes: usize) -> Option<u64> {
167 if let Ok(mut tracker) = self.egress_tracker.lock() {
168 if let Some(ref ctx) = self.auth_context {
169 if let Some(limit) = ctx.limits.max_bytes_per_minute {
170 if tracker.record_bytes(bytes, limit) {
171 return Some(tracker.current_usage());
172 } else {
173 return None; }
175 }
176 }
177 return Some(tracker.current_usage());
179 }
180 None
181 }
182
183 pub fn record_inbound_message(&self) -> Option<u32> {
185 if let Ok(mut tracker) = self.message_rate_tracker.lock() {
186 if let Some(ref ctx) = self.auth_context {
187 if let Some(limit) = ctx.limits.max_messages_per_minute {
188 if tracker.record_message(limit) {
189 return Some(tracker.current_usage());
190 } else {
191 return None;
192 }
193 }
194 }
195
196 return Some(tracker.current_usage());
197 }
198
199 None
200 }
201
202 pub fn update_last_seen(&mut self) {
203 self.last_seen = SystemTime::now();
204 }
205
206 pub fn is_stale(&self, timeout: Duration) -> bool {
207 self.last_seen.elapsed().unwrap_or(Duration::MAX) > timeout
208 }
209
210 pub async fn add_subscription(
211 &self,
212 subscription_id: String,
213 token: CancellationToken,
214 ) -> bool {
215 let mut subs = self.subscriptions.write().await;
216 match subs.entry(subscription_id) {
217 std::collections::hash_map::Entry::Vacant(entry) => {
218 entry.insert(token);
219 true
220 }
221 std::collections::hash_map::Entry::Occupied(_) => false,
222 }
223 }
224
225 pub async fn remove_subscription(&self, subscription_id: &str) -> bool {
226 let mut subs = self.subscriptions.write().await;
227 if let Some(token) = subs.remove(subscription_id) {
228 token.cancel();
229 debug!("Cancelled subscription: {}", subscription_id);
230 true
231 } else {
232 debug!(
233 "Subscription not found for cancellation: {}",
234 subscription_id
235 );
236 false
237 }
238 }
239
240 pub async fn cancel_all_subscriptions(&self) {
241 let subs = self.subscriptions.read().await;
242 for (subscription_id, token) in subs.iter() {
243 token.cancel();
244 debug!("Cancelled subscription on disconnect: {}", subscription_id);
245 }
246 }
247
248 pub async fn subscription_count(&self) -> usize {
249 self.subscriptions.read().await.len()
250 }
251}
252
253#[derive(Debug, Clone)]
258pub struct RateLimitConfig {
259 pub max_connections_per_ip: Option<usize>,
261 pub max_connections_per_metering_key: Option<usize>,
263 pub max_connections_per_origin: Option<usize>,
265 pub client_timeout: Duration,
267 pub message_queue_size: usize,
269 pub max_reconnect_attempts: Option<u32>,
271 pub message_rate_window: Duration,
273 pub egress_rate_window: Duration,
275 pub default_limits: Option<Limits>,
278}
279
280impl Default for RateLimitConfig {
281 fn default() -> Self {
282 Self {
283 max_connections_per_ip: None,
284 max_connections_per_metering_key: None,
285 max_connections_per_origin: None,
286 client_timeout: Duration::from_secs(300),
287 message_queue_size: 512,
288 max_reconnect_attempts: None,
289 message_rate_window: Duration::from_secs(60),
290 egress_rate_window: Duration::from_secs(60),
291 default_limits: None,
292 }
293 }
294}
295
296impl RateLimitConfig {
297 pub fn from_env() -> Self {
312 let mut config = Self::default();
313
314 if let Ok(val) = std::env::var("ARETE_WS_MAX_CONNECTIONS_PER_IP") {
315 if let Ok(max) = val.parse() {
316 config.max_connections_per_ip = Some(max);
317 }
318 }
319
320 if let Ok(val) = std::env::var("ARETE_WS_MAX_CONNECTIONS_PER_METERING_KEY") {
321 if let Ok(max) = val.parse() {
322 config.max_connections_per_metering_key = Some(max);
323 }
324 }
325
326 if let Ok(val) = std::env::var("ARETE_WS_MAX_CONNECTIONS_PER_ORIGIN") {
327 if let Ok(max) = val.parse() {
328 config.max_connections_per_origin = Some(max);
329 }
330 }
331
332 if let Ok(val) = std::env::var("ARETE_WS_CLIENT_TIMEOUT_SECS") {
333 if let Ok(secs) = val.parse() {
334 config.client_timeout = Duration::from_secs(secs);
335 }
336 }
337
338 if let Ok(val) = std::env::var("ARETE_WS_MESSAGE_QUEUE_SIZE") {
339 if let Ok(size) = val.parse() {
340 config.message_queue_size = size;
341 }
342 }
343
344 if let Ok(val) = std::env::var("ARETE_WS_RATE_LIMIT_WINDOW_SECS") {
345 if let Ok(secs) = val.parse() {
346 config.message_rate_window = Duration::from_secs(secs);
347 config.egress_rate_window = Duration::from_secs(secs);
348 }
349 }
350
351 let mut default_limits = Limits::default();
353 let mut has_default_limits = false;
354
355 if let Ok(val) = std::env::var("ARETE_WS_DEFAULT_MAX_CONNECTIONS") {
356 if let Ok(max) = val.parse() {
357 default_limits.max_connections = Some(max);
358 has_default_limits = true;
359 }
360 }
361
362 if let Ok(val) = std::env::var("ARETE_WS_DEFAULT_MAX_SUBSCRIPTIONS") {
363 if let Ok(max) = val.parse() {
364 default_limits.max_subscriptions = Some(max);
365 has_default_limits = true;
366 }
367 }
368
369 if let Ok(val) = std::env::var("ARETE_WS_DEFAULT_MAX_SNAPSHOT_ROWS") {
370 if let Ok(max) = val.parse() {
371 default_limits.max_snapshot_rows = Some(max);
372 has_default_limits = true;
373 }
374 }
375
376 if let Ok(val) = std::env::var("ARETE_WS_DEFAULT_MAX_MESSAGES_PER_MINUTE") {
377 if let Ok(max) = val.parse() {
378 default_limits.max_messages_per_minute = Some(max);
379 has_default_limits = true;
380 }
381 }
382
383 if let Ok(val) = std::env::var("ARETE_WS_DEFAULT_MAX_BYTES_PER_MINUTE") {
384 if let Ok(max) = val.parse() {
385 default_limits.max_bytes_per_minute = Some(max);
386 has_default_limits = true;
387 }
388 }
389
390 if has_default_limits {
391 config.default_limits = Some(default_limits);
392 }
393
394 config
395 }
396
397 pub fn with_max_connections_per_ip(mut self, max: usize) -> Self {
399 self.max_connections_per_ip = Some(max);
400 self
401 }
402
403 pub fn with_timeout(mut self, timeout: Duration) -> Self {
405 self.client_timeout = timeout;
406 self
407 }
408
409 pub fn with_message_queue_size(mut self, size: usize) -> Self {
411 self.message_queue_size = size;
412 self
413 }
414
415 pub fn with_rate_limit_window(mut self, window: Duration) -> Self {
417 self.message_rate_window = window;
418 self.egress_rate_window = window;
419 self
420 }
421
422 pub fn with_default_limits(mut self, limits: Limits) -> Self {
427 self.default_limits = Some(limits);
428 self
429 }
430}
431
432#[derive(Clone)]
441pub struct ClientManager {
442 clients: Arc<DashMap<Uuid, ClientInfo>>,
443 rate_limit_config: RateLimitConfig,
444 rate_limiter: Option<Arc<WebSocketRateLimiter>>,
446}
447
448impl ClientManager {
449 pub fn new() -> Self {
450 Self::with_config(RateLimitConfig::default())
451 }
452
453 pub fn with_config(config: RateLimitConfig) -> Self {
455 Self {
456 clients: Arc::new(DashMap::new()),
457 rate_limit_config: config,
458 rate_limiter: None,
459 }
460 }
461
462 pub fn from_env() -> Self {
466 Self::with_config(RateLimitConfig::from_env())
467 }
468
469 pub fn with_timeout(mut self, timeout: Duration) -> Self {
471 self.rate_limit_config.client_timeout = timeout;
472 self
473 }
474
475 pub fn with_message_queue_size(mut self, queue_size: usize) -> Self {
477 self.rate_limit_config.message_queue_size = queue_size;
478 self
479 }
480
481 pub fn with_max_connections_per_ip(mut self, max: usize) -> Self {
483 self.rate_limit_config.max_connections_per_ip = Some(max);
484 self
485 }
486
487 pub fn with_rate_limit_window(mut self, window: Duration) -> Self {
489 self.rate_limit_config.message_rate_window = window;
490 self.rate_limit_config.egress_rate_window = window;
491 self
492 }
493
494 pub fn with_default_limits(mut self, limits: Limits) -> Self {
499 self.rate_limit_config.default_limits = Some(limits);
500 self
501 }
502
503 pub fn with_rate_limiter(mut self, rate_limiter: Arc<WebSocketRateLimiter>) -> Self {
505 self.rate_limiter = Some(rate_limiter);
506 self
507 }
508
509 pub fn rate_limiter(&self) -> Option<&WebSocketRateLimiter> {
511 self.rate_limiter.as_ref().map(|r| r.as_ref())
512 }
513
514 pub fn rate_limit_config(&self) -> &RateLimitConfig {
516 &self.rate_limit_config
517 }
518
519 pub fn add_client(
525 &self,
526 client_id: Uuid,
527 mut ws_sender: WebSocketSender,
528 auth_context: Option<AuthContext>,
529 remote_addr: SocketAddr,
530 ) {
531 let (client_tx, mut client_rx) =
532 mpsc::channel::<Message>(self.rate_limit_config.message_queue_size);
533 let client_info = ClientInfo::new(client_id, client_tx, auth_context, remote_addr);
534
535 let clients_ref = self.clients.clone();
536 tokio::spawn(async move {
537 while let Some(message) = client_rx.recv().await {
538 if let Err(e) = ws_sender.send(message).await {
539 warn!("Failed to send message to client {}: {}", client_id, e);
540 break;
541 }
542 }
543 clients_ref.remove(&client_id);
544 debug!("WebSocket sender task for client {} stopped", client_id);
545 });
546
547 self.clients.insert(client_id, client_info);
548 info!("Client {} registered from {}", client_id, remote_addr);
549 }
550
551 pub fn remove_client(&self, client_id: Uuid) {
553 if self.clients.remove(&client_id).is_some() {
554 info!("Client {} removed", client_id);
555 }
556 }
557
558 pub fn update_client_auth(&self, client_id: Uuid, auth_context: AuthContext) -> bool {
562 if let Some(mut client) = self.clients.get_mut(&client_id) {
563 client.auth_context = Some(auth_context);
564 debug!("Updated auth context for client {}", client_id);
565 true
566 } else {
567 false
568 }
569 }
570
571 pub fn check_and_remove_expired(&self, client_id: Uuid) -> bool {
576 if let Some(client) = self.clients.get(&client_id) {
577 if let Some(ref ctx) = client.auth_context {
578 let now = std::time::SystemTime::now()
579 .duration_since(std::time::UNIX_EPOCH)
580 .unwrap_or_default()
581 .as_secs();
582 if ctx.expires_at <= now {
583 warn!(
584 "Client {} token expired (expired at {}), disconnecting",
585 client_id, ctx.expires_at
586 );
587 self.clients.remove(&client_id);
588 return true;
589 }
590 }
591 }
592 false
593 }
594
595 pub fn client_count(&self) -> usize {
600 self.clients.len()
601 }
602
603 pub fn send_to_client(&self, client_id: Uuid, data: Arc<Bytes>) -> Result<(), SendError> {
612 if self.check_and_remove_expired(client_id) {
614 return Err(SendError::ClientDisconnected);
615 }
616
617 if let Some(client) = self.clients.get(&client_id) {
619 if client.record_egress(data.len()).is_none() {
620 warn!("Client {} exceeded egress limit, disconnecting", client_id);
621 self.clients.remove(&client_id);
622 return Err(SendError::ClientDisconnected);
623 }
624 } else {
625 return Err(SendError::ClientNotFound);
626 }
627
628 let sender = {
629 let client = self
630 .clients
631 .get(&client_id)
632 .ok_or(SendError::ClientNotFound)?;
633 client.sender.clone()
634 };
635
636 let msg = Message::Binary((*data).clone());
637 match sender.try_send(msg) {
638 Ok(()) => Ok(()),
639 Err(mpsc::error::TrySendError::Full(_)) => {
640 warn!(
641 "Client {} backpressured (queue full), disconnecting",
642 client_id
643 );
644 self.clients.remove(&client_id);
645 Err(SendError::ClientBackpressured)
646 }
647 Err(mpsc::error::TrySendError::Closed(_)) => {
648 debug!("Client {} channel closed", client_id);
649 self.clients.remove(&client_id);
650 Err(SendError::ClientDisconnected)
651 }
652 }
653 }
654
655 pub async fn send_to_client_async(
664 &self,
665 client_id: Uuid,
666 data: Arc<Bytes>,
667 ) -> Result<(), SendError> {
668 if self.check_and_remove_expired(client_id) {
670 return Err(SendError::ClientDisconnected);
671 }
672
673 if let Some(client) = self.clients.get(&client_id) {
675 if client.record_egress(data.len()).is_none() {
676 warn!("Client {} exceeded egress limit, disconnecting", client_id);
677 self.clients.remove(&client_id);
678 return Err(SendError::ClientDisconnected);
679 }
680 } else {
681 return Err(SendError::ClientNotFound);
682 }
683
684 let sender = {
685 let client = self
686 .clients
687 .get(&client_id)
688 .ok_or(SendError::ClientNotFound)?;
689 client.sender.clone()
690 };
691
692 let msg = Message::Binary((*data).clone());
693 sender
694 .send(msg)
695 .await
696 .map_err(|_| SendError::ClientDisconnected)
697 }
698
699 pub async fn send_text_to_client(
704 &self,
705 client_id: Uuid,
706 text: String,
707 ) -> Result<(), SendError> {
708 if self.check_and_remove_expired(client_id) {
710 return Err(SendError::ClientDisconnected);
711 }
712
713 let sender = {
714 let client = self
715 .clients
716 .get(&client_id)
717 .ok_or(SendError::ClientNotFound)?;
718 client.sender.clone()
719 };
720
721 let msg = Message::Text(text.into());
722 sender
723 .send(msg)
724 .await
725 .map_err(|_| SendError::ClientDisconnected)
726 }
727
728 pub async fn send_compressed_async(
733 &self,
734 client_id: Uuid,
735 payload: CompressedPayload,
736 ) -> Result<(), SendError> {
737 if self.check_and_remove_expired(client_id) {
739 return Err(SendError::ClientDisconnected);
740 }
741
742 let (sender, bytes_to_record) = {
743 let client = self
744 .clients
745 .get(&client_id)
746 .ok_or(SendError::ClientNotFound)?;
747
748 let bytes = match &payload {
749 CompressedPayload::Compressed(bytes) => bytes.len(),
750 CompressedPayload::Uncompressed(bytes) => bytes.len(),
751 };
752
753 (client.sender.clone(), bytes)
754 };
755
756 if let Some(client) = self.clients.get(&client_id) {
758 if client.record_egress(bytes_to_record).is_none() {
759 warn!("Client {} exceeded egress limit, disconnecting", client_id);
760 self.clients.remove(&client_id);
761 return Err(SendError::ClientDisconnected);
762 }
763 }
764
765 let msg = match payload {
766 CompressedPayload::Compressed(bytes) => Message::Binary(bytes),
767 CompressedPayload::Uncompressed(bytes) => Message::Binary(bytes),
768 };
769 sender
770 .send(msg)
771 .await
772 .map_err(|_| SendError::ClientDisconnected)
773 }
774
775 pub fn update_client_last_seen(&self, client_id: Uuid) {
777 if let Some(mut client) = self.clients.get_mut(&client_id) {
778 client.update_last_seen();
779 }
780 }
781
782 #[allow(clippy::result_large_err)]
784 pub fn check_inbound_message_allowed(&self, client_id: Uuid) -> Result<(), AuthDeny> {
785 if self.check_and_remove_expired(client_id) {
786 return Err(AuthDeny::new(
787 crate::websocket::auth::AuthErrorCode::TokenExpired,
788 "Authentication token expired",
789 ));
790 }
791
792 let Some(client) = self.clients.get(&client_id) else {
793 return Err(AuthDeny::new(
794 crate::websocket::auth::AuthErrorCode::InternalError,
795 "Client not found",
796 ));
797 };
798
799 if client.record_inbound_message().is_some() {
800 Ok(())
801 } else {
802 self.clients.remove(&client_id);
803 Err(AuthDeny::rate_limited(
804 self.rate_limit_config.message_rate_window,
805 "inbound websocket messages",
806 )
807 .with_context(format!(
808 "client {} exceeded the inbound message budget",
809 client_id
810 )))
811 }
812 }
813
814 pub fn has_client(&self, client_id: Uuid) -> bool {
816 self.clients.contains_key(&client_id)
817 }
818
819 pub async fn add_client_subscription(
820 &self,
821 client_id: Uuid,
822 subscription_id: String,
823 token: CancellationToken,
824 ) -> bool {
825 if let Some(client) = self.clients.get(&client_id) {
826 client.add_subscription(subscription_id, token).await
827 } else {
828 false
829 }
830 }
831
832 pub async fn remove_client_subscription(&self, client_id: Uuid, subscription_id: &str) -> bool {
833 if let Some(client) = self.clients.get(&client_id) {
834 client.remove_subscription(subscription_id).await
835 } else {
836 false
837 }
838 }
839
840 pub async fn cancel_all_client_subscriptions(&self, client_id: Uuid) {
841 if let Some(client) = self.clients.get(&client_id) {
842 client.cancel_all_subscriptions().await;
843 }
844 }
845
846 pub fn cleanup_stale_clients(&self) -> usize {
848 let timeout = self.rate_limit_config.client_timeout;
849 let mut stale_clients = Vec::new();
850
851 for entry in self.clients.iter() {
852 if entry.value().is_stale(timeout) {
853 stale_clients.push(*entry.key());
854 }
855 }
856
857 let removed_count = stale_clients.len();
858 for client_id in stale_clients {
859 self.clients.remove(&client_id);
860 info!("Removed stale client {}", client_id);
861 }
862
863 removed_count
864 }
865
866 pub fn start_cleanup_task(&self) {
868 let client_manager = self.clone();
869
870 tokio::spawn(async move {
871 let mut interval = tokio::time::interval(Duration::from_secs(30));
872
873 loop {
874 interval.tick().await;
875 let removed = client_manager.cleanup_stale_clients();
876 if removed > 0 {
877 info!("Cleaned up {} stale clients", removed);
878 }
879 }
880 });
881 }
882
883 pub async fn check_connection_allowed(
891 &self,
892 remote_addr: SocketAddr,
893 auth_context: &Option<AuthContext>,
894 ) -> Result<(), AuthDeny> {
895 if let Some(ref rate_limiter) = self.rate_limiter {
897 match rate_limiter.check_handshake(remote_addr).await {
899 RateLimitResult::Allowed { .. } => {}
900 RateLimitResult::Denied { retry_after, limit } => {
901 return Err(AuthDeny::rate_limited(retry_after, "websocket handshakes")
902 .with_context(format!(
903 "handshake rate limit of {} per minute exceeded for {}",
904 limit, remote_addr
905 )));
906 }
907 }
908
909 if let Some(ref ctx) = auth_context {
911 match rate_limiter
912 .check_connection_for_subject(&ctx.subject)
913 .await
914 {
915 RateLimitResult::Allowed { .. } => {}
916 RateLimitResult::Denied { retry_after, limit } => {
917 return Err(AuthDeny::rate_limited(retry_after, "websocket connections")
918 .with_context(format!(
919 "connection rate limit for subject {} of {} per minute exceeded",
920 ctx.subject, limit
921 )));
922 }
923 }
924
925 match rate_limiter
927 .check_connection_for_metering_key(&ctx.metering_key)
928 .await
929 {
930 RateLimitResult::Allowed { .. } => {}
931 RateLimitResult::Denied { retry_after, limit } => {
932 return Err(AuthDeny::rate_limited(
933 retry_after,
934 "metered websocket connections",
935 )
936 .with_context(format!(
937 "connection rate limit for metering key {} of {} per minute exceeded",
938 ctx.metering_key, limit
939 )));
940 }
941 }
942 }
943 }
944
945 if let Some(max_per_ip) = self.rate_limit_config.max_connections_per_ip {
947 let current_ip_connections = self.count_connections_for_ip(&remote_addr);
948 if current_ip_connections >= max_per_ip {
949 return Err(AuthDeny::connection_limit_exceeded(
950 &format!("ip {}", remote_addr.ip()),
951 current_ip_connections,
952 max_per_ip,
953 ));
954 }
955 }
956
957 if let Some(ctx) = auth_context {
958 let max_connections = ctx.limits.max_connections.or_else(|| {
960 self.rate_limit_config
961 .default_limits
962 .as_ref()
963 .and_then(|l| l.max_connections)
964 });
965 if let Some(max_connections) = max_connections {
966 let current_connections = self.count_connections_for_subject(&ctx.subject);
967 if current_connections >= max_connections as usize {
968 return Err(AuthDeny::connection_limit_exceeded(
969 &format!("subject {}", ctx.subject),
970 current_connections,
971 max_connections as usize,
972 ));
973 }
974 }
975
976 if let Some(max_per_metering_key) =
978 self.rate_limit_config.max_connections_per_metering_key
979 {
980 let current_metering_connections =
981 self.count_connections_for_metering_key(&ctx.metering_key);
982 if current_metering_connections >= max_per_metering_key {
983 return Err(AuthDeny::connection_limit_exceeded(
984 &format!("metering key {}", ctx.metering_key),
985 current_metering_connections,
986 max_per_metering_key,
987 ));
988 }
989 }
990
991 if let Some(max_per_origin) = self.rate_limit_config.max_connections_per_origin {
993 if let Some(ref origin) = ctx.origin {
994 let current_origin_connections = self.count_connections_for_origin(origin);
995 if current_origin_connections >= max_per_origin {
996 return Err(AuthDeny::connection_limit_exceeded(
997 &format!("origin {}", origin),
998 current_origin_connections,
999 max_per_origin,
1000 ));
1001 }
1002 }
1003 }
1004 }
1005 Ok(())
1006 }
1007
1008 fn count_connections_for_ip(&self, remote_addr: &SocketAddr) -> usize {
1010 let ip = remote_addr.ip();
1011 self.clients
1012 .iter()
1013 .filter(|entry| entry.value().remote_addr.ip() == ip)
1014 .count()
1015 }
1016
1017 fn count_connections_for_subject(&self, subject: &str) -> usize {
1019 self.clients
1020 .iter()
1021 .filter(|entry| {
1022 entry
1023 .value()
1024 .auth_context
1025 .as_ref()
1026 .map(|ctx| ctx.subject == subject)
1027 .unwrap_or(false)
1028 })
1029 .count()
1030 }
1031
1032 fn count_connections_for_metering_key(&self, metering_key: &str) -> usize {
1034 self.clients
1035 .iter()
1036 .filter(|entry| {
1037 entry
1038 .value()
1039 .auth_context
1040 .as_ref()
1041 .map(|ctx| ctx.metering_key == metering_key)
1042 .unwrap_or(false)
1043 })
1044 .count()
1045 }
1046
1047 fn count_connections_for_origin(&self, origin: &str) -> usize {
1049 self.clients
1050 .iter()
1051 .filter(|entry| {
1052 entry
1053 .value()
1054 .auth_context
1055 .as_ref()
1056 .and_then(|ctx| ctx.origin.as_ref())
1057 .map(|o| o == origin)
1058 .unwrap_or(false)
1059 })
1060 .count()
1061 }
1062
1063 pub async fn check_subscription_allowed(&self, client_id: Uuid) -> Result<(), AuthDeny> {
1067 if let Some(client) = self.clients.get(&client_id) {
1068 let current_subs = client.subscription_count().await;
1069
1070 if let Some(ref ctx) = client.auth_context {
1072 let max_subs = ctx.limits.max_subscriptions.or_else(|| {
1073 self.rate_limit_config
1074 .default_limits
1075 .as_ref()
1076 .and_then(|l| l.max_subscriptions)
1077 });
1078 if let Some(max_subs) = max_subs {
1079 if current_subs >= max_subs as usize {
1080 return Err(AuthDeny::new(
1081 crate::websocket::auth::AuthErrorCode::SubscriptionLimitExceeded,
1082 format!(
1083 "Subscription limit exceeded: {} of {} subscriptions for client {}",
1084 current_subs, max_subs, client_id
1085 ),
1086 )
1087 .with_suggested_action(
1088 "Unsubscribe from an existing view before creating another subscription",
1089 ));
1090 }
1091 }
1092 }
1093 }
1094 Ok(())
1095 }
1096
1097 pub fn get_metering_key(&self, client_id: Uuid) -> Option<String> {
1099 self.clients.get(&client_id).and_then(|client| {
1100 client
1101 .auth_context
1102 .as_ref()
1103 .map(|ctx| ctx.metering_key.clone())
1104 })
1105 }
1106
1107 pub fn get_auth_context(&self, client_id: Uuid) -> Option<AuthContext> {
1109 self.clients
1110 .get(&client_id)
1111 .and_then(|client| client.auth_context.clone())
1112 }
1113
1114 #[allow(clippy::result_large_err)]
1118 pub fn check_snapshot_allowed(
1119 &self,
1120 client_id: Uuid,
1121 requested_rows: u32,
1122 ) -> Result<(), AuthDeny> {
1123 if let Some(client) = self.clients.get(&client_id) {
1124 if let Some(ref ctx) = client.auth_context {
1125 let max_rows = ctx.limits.max_snapshot_rows.or_else(|| {
1126 self.rate_limit_config
1127 .default_limits
1128 .as_ref()
1129 .and_then(|l| l.max_snapshot_rows)
1130 });
1131 if let Some(max_rows) = max_rows {
1132 if requested_rows > max_rows {
1133 return Err(AuthDeny::new(
1134 crate::websocket::auth::AuthErrorCode::SnapshotLimitExceeded,
1135 format!(
1136 "Snapshot limit exceeded: requested {} rows, max allowed is {} for client {}",
1137 requested_rows, max_rows, client_id
1138 ),
1139 )
1140 .with_suggested_action(
1141 "Request fewer rows or lower the snapshotLimit on the subscription",
1142 ));
1143 }
1144 }
1145 }
1146 }
1147 Ok(())
1148 }
1149}
1150
1151impl Default for ClientManager {
1152 fn default() -> Self {
1153 Self::new()
1154 }
1155}
1156
1157#[cfg(test)]
1158mod tests {
1159 use super::*;
1160 use crate::websocket::auth::AuthContext;
1161 use arete_auth::{KeyClass, Limits};
1162 use std::net::{IpAddr, Ipv4Addr, SocketAddr};
1163
1164 fn create_test_auth_context(subject: &str, limits: Limits) -> AuthContext {
1165 AuthContext {
1166 subject: subject.to_string(),
1167 issuer: "test-issuer".to_string(),
1168 key_class: KeyClass::Publishable,
1169 metering_key: format!("meter-{}", subject),
1170 deployment_id: None,
1171 expires_at: u64::MAX,
1172 scope: "read".to_string(),
1173 limits,
1174 plan: None,
1175 origin: None,
1176 client_ip: None,
1177 jti: uuid::Uuid::new_v4().to_string(),
1178 }
1179 }
1180
1181 fn create_test_socket_addr(ip: &str) -> SocketAddr {
1182 SocketAddr::new(
1183 ip.parse::<IpAddr>()
1184 .unwrap_or(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
1185 12345,
1186 )
1187 }
1188
1189 #[test]
1190 fn test_egress_tracker_basic() {
1191 let mut tracker = EgressTracker::new();
1192
1193 assert!(tracker.record_bytes(500, 1000));
1195 assert_eq!(tracker.current_usage(), 500);
1196
1197 assert!(tracker.record_bytes(400, 1000));
1199 assert_eq!(tracker.current_usage(), 900);
1200
1201 assert!(!tracker.record_bytes(200, 1000));
1203 assert_eq!(tracker.current_usage(), 900); }
1205
1206 #[test]
1207 fn test_egress_tracker_window_reset() {
1208 let mut tracker = EgressTracker::new();
1209
1210 assert!(tracker.record_bytes(100, 100));
1212 assert!(!tracker.record_bytes(1, 100));
1213
1214 tracker.bytes_this_minute = 0;
1216 tracker.window_start = SystemTime::now() - Duration::from_secs(61);
1217
1218 assert!(tracker.record_bytes(50, 100));
1220 }
1221
1222 #[test]
1223 fn test_message_rate_tracker_basic() {
1224 let mut tracker = MessageRateTracker::new();
1225
1226 assert!(tracker.record_message(2));
1227 assert_eq!(tracker.current_usage(), 1);
1228
1229 assert!(tracker.record_message(2));
1230 assert_eq!(tracker.current_usage(), 2);
1231
1232 assert!(!tracker.record_message(2));
1233 assert_eq!(tracker.current_usage(), 2);
1234 }
1235
1236 #[tokio::test]
1237 async fn test_client_inbound_message_limit() {
1238 let (tx, _rx) = mpsc::channel(1);
1239 let client = ClientInfo::new(
1240 Uuid::new_v4(),
1241 tx,
1242 Some(create_test_auth_context(
1243 "user-1",
1244 Limits {
1245 max_messages_per_minute: Some(2),
1246 ..Default::default()
1247 },
1248 )),
1249 create_test_socket_addr("127.0.0.1"),
1250 );
1251
1252 assert_eq!(client.record_inbound_message(), Some(1));
1253 assert_eq!(client.record_inbound_message(), Some(2));
1254 assert_eq!(client.record_inbound_message(), None);
1255 }
1256
1257 #[tokio::test]
1258 async fn duplicate_subscription_id_is_rejected_without_replacement() {
1259 let (tx, _rx) = mpsc::channel(1);
1260 let client = ClientInfo::new(
1261 Uuid::new_v4(),
1262 tx,
1263 None,
1264 create_test_socket_addr("127.0.0.1"),
1265 );
1266 let first = CancellationToken::new();
1267 let duplicate = CancellationToken::new();
1268
1269 assert!(
1270 client
1271 .add_subscription("opaque-id".to_string(), first.clone())
1272 .await
1273 );
1274 assert!(
1275 !client
1276 .add_subscription("opaque-id".to_string(), duplicate.clone())
1277 .await
1278 );
1279 assert!(!first.is_cancelled());
1280 assert!(!duplicate.is_cancelled());
1281
1282 assert!(client.remove_subscription("opaque-id").await);
1283 assert!(first.is_cancelled());
1284 assert!(!duplicate.is_cancelled());
1285 }
1286
1287 #[tokio::test]
1288 async fn test_no_limits() {
1289 let manager = ClientManager::new();
1290 let addr = create_test_socket_addr("127.0.0.1");
1291
1292 assert!(manager.check_connection_allowed(addr, &None).await.is_ok());
1294
1295 let auth_context = create_test_auth_context("test", Limits::default());
1297 assert!(manager
1298 .check_connection_allowed(addr, &Some(auth_context))
1299 .await
1300 .is_ok());
1301 }
1302
1303 #[tokio::test]
1304 async fn test_per_subject_connection_limit() {
1305 let manager = ClientManager::new();
1306
1307 let limits = Limits {
1308 max_connections: Some(2),
1309 ..Default::default()
1310 };
1311
1312 let auth_context = create_test_auth_context("user-1", limits);
1313 let addr = create_test_socket_addr("127.0.0.1");
1314
1315 assert!(manager
1317 .check_connection_allowed(addr, &Some(auth_context.clone()))
1318 .await
1319 .is_ok());
1320 }
1321
1322 #[tokio::test]
1323 async fn test_per_ip_connection_limit() {
1324 let manager = ClientManager::new().with_max_connections_per_ip(2);
1325 let addr = create_test_socket_addr("192.168.1.1");
1326
1327 assert!(manager.check_connection_allowed(addr, &None).await.is_ok());
1329 }
1330
1331 #[test]
1333 fn rate_limit_config_default() {
1334 let config = RateLimitConfig::default();
1335 assert!(config.max_connections_per_ip.is_none());
1336 assert_eq!(config.client_timeout, Duration::from_secs(300));
1337 assert_eq!(config.message_queue_size, 512);
1338 assert!(config.max_reconnect_attempts.is_none());
1339 assert_eq!(config.message_rate_window, Duration::from_secs(60));
1340 assert_eq!(config.egress_rate_window, Duration::from_secs(60));
1341 }
1342
1343 #[test]
1344 fn rate_limit_config_builder_methods() {
1345 let config = RateLimitConfig::default()
1346 .with_max_connections_per_ip(10)
1347 .with_timeout(Duration::from_secs(600))
1348 .with_message_queue_size(1024)
1349 .with_rate_limit_window(Duration::from_secs(120));
1350
1351 assert_eq!(config.max_connections_per_ip, Some(10));
1352 assert_eq!(config.client_timeout, Duration::from_secs(600));
1353 assert_eq!(config.message_queue_size, 1024);
1354 assert_eq!(config.message_rate_window, Duration::from_secs(120));
1355 assert_eq!(config.egress_rate_window, Duration::from_secs(120));
1356 }
1357
1358 #[tokio::test]
1359 async fn client_manager_with_config() {
1360 let config = RateLimitConfig::default()
1361 .with_max_connections_per_ip(5)
1362 .with_timeout(Duration::from_secs(120))
1363 .with_message_queue_size(256);
1364
1365 let manager = ClientManager::with_config(config);
1366 let addr = create_test_socket_addr("10.0.0.1");
1367
1368 assert_eq!(manager.rate_limit_config().max_connections_per_ip, Some(5));
1370 assert_eq!(
1371 manager.rate_limit_config().client_timeout,
1372 Duration::from_secs(120)
1373 );
1374 assert_eq!(manager.rate_limit_config().message_queue_size, 256);
1375
1376 assert!(manager.check_connection_allowed(addr, &None).await.is_ok());
1378 }
1379
1380 #[tokio::test]
1381 async fn client_manager_builder_pattern() {
1382 let manager = ClientManager::new()
1383 .with_max_connections_per_ip(10)
1384 .with_timeout(Duration::from_secs(180))
1385 .with_message_queue_size(1024)
1386 .with_rate_limit_window(Duration::from_secs(90));
1387
1388 assert_eq!(manager.rate_limit_config().max_connections_per_ip, Some(10));
1389 assert_eq!(
1390 manager.rate_limit_config().client_timeout,
1391 Duration::from_secs(180)
1392 );
1393 assert_eq!(manager.rate_limit_config().message_queue_size, 1024);
1394 assert_eq!(
1395 manager.rate_limit_config().message_rate_window,
1396 Duration::from_secs(90)
1397 );
1398 }
1399
1400 #[tokio::test]
1402 async fn connection_limit_enforcement_with_actual_clients() {
1403 let manager = ClientManager::new().with_max_connections_per_ip(2);
1404 let addr1 = create_test_socket_addr("192.168.1.1");
1405 let addr2 = create_test_socket_addr("192.168.1.2");
1406
1407 let auth1 = create_test_auth_context("user-1", Limits::default());
1409 assert!(manager
1410 .check_connection_allowed(addr1, &Some(auth1.clone()))
1411 .await
1412 .is_ok());
1413
1414 let auth2 = create_test_auth_context("user-2", Limits::default());
1419 assert!(manager
1420 .check_connection_allowed(addr1, &Some(auth2.clone()))
1421 .await
1422 .is_ok());
1423
1424 let auth3 = create_test_auth_context("user-3", Limits::default());
1426 assert!(manager
1427 .check_connection_allowed(addr2, &Some(auth3.clone()))
1428 .await
1429 .is_ok());
1430 }
1431
1432 #[tokio::test]
1434 async fn subscription_limit_enforcement() {
1435 let manager = ClientManager::new();
1436 let addr = create_test_socket_addr("127.0.0.1");
1437
1438 let auth = create_test_auth_context(
1440 "user-1",
1441 Limits {
1442 max_subscriptions: Some(2),
1443 ..Default::default()
1444 },
1445 );
1446
1447 assert!(manager
1449 .check_connection_allowed(addr, &Some(auth.clone()))
1450 .await
1451 .is_ok());
1452
1453 assert_eq!(auth.limits.max_subscriptions, Some(2));
1456 }
1457
1458 #[tokio::test]
1460 async fn snapshot_limit_enforcement() {
1461 let manager = ClientManager::new();
1462 let addr = create_test_socket_addr("127.0.0.1");
1463
1464 let auth = create_test_auth_context(
1465 "user-1",
1466 Limits {
1467 max_snapshot_rows: Some(1000),
1468 ..Default::default()
1469 },
1470 );
1471
1472 assert!(manager
1473 .check_connection_allowed(addr, &Some(auth.clone()))
1474 .await
1475 .is_ok());
1476
1477 }
1480
1481 #[tokio::test]
1483 async fn test_rate_limiter_integration() {
1484 use crate::websocket::rate_limiter::{RateLimiterConfig, WebSocketRateLimiter};
1485
1486 let rate_limiter = Arc::new(WebSocketRateLimiter::new(RateLimiterConfig::default()));
1487 let manager = ClientManager::new().with_rate_limiter(rate_limiter);
1488 let addr = create_test_socket_addr("127.0.0.1");
1489
1490 let auth = create_test_auth_context("user-1", Limits::default());
1492 assert!(manager
1493 .check_connection_allowed(addr, &Some(auth))
1494 .await
1495 .is_ok());
1496 }
1497}