Skip to main content

reinhardt_websockets/
connection.rs

1#![allow(deprecated)] // `ConnectionConfig` is deprecated but still used internally during the compatibility window.
2
3use std::collections::HashMap;
4use std::sync::Arc;
5use std::time::{Duration, Instant};
6use tokio::sync::RwLock;
7use tokio::sync::mpsc;
8
9/// Ping/pong keepalive configuration for WebSocket connections.
10///
11/// Controls how frequently ping frames are sent and how long
12/// the server waits for a pong response before considering
13/// the connection dead.
14///
15/// # Examples
16///
17/// ```
18/// use reinhardt_websockets::connection::PingPongConfig;
19/// use std::time::Duration;
20///
21/// // Use defaults (30s ping interval, 10s pong timeout)
22/// let config = PingPongConfig::default();
23/// assert_eq!(config.ping_interval(), Duration::from_secs(30));
24/// assert_eq!(config.pong_timeout(), Duration::from_secs(10));
25///
26/// // Custom configuration
27/// let config = PingPongConfig::new(
28///     Duration::from_secs(15),
29///     Duration::from_secs(5),
30/// );
31/// assert_eq!(config.ping_interval(), Duration::from_secs(15));
32/// assert_eq!(config.pong_timeout(), Duration::from_secs(5));
33/// ```
34#[derive(Debug, Clone)]
35pub struct PingPongConfig {
36	/// Interval between ping frames sent to the client
37	ping_interval: Duration,
38	/// Maximum time to wait for a pong response before closing
39	pong_timeout: Duration,
40}
41
42impl Default for PingPongConfig {
43	fn default() -> Self {
44		Self {
45			ping_interval: Duration::from_secs(30),
46			pong_timeout: Duration::from_secs(10),
47		}
48	}
49}
50
51impl PingPongConfig {
52	/// Creates a new ping/pong configuration with the given intervals.
53	///
54	/// # Arguments
55	///
56	/// * `ping_interval` - How often to send ping frames
57	/// * `pong_timeout` - How long to wait for a pong response
58	///
59	/// # Examples
60	///
61	/// ```
62	/// use reinhardt_websockets::connection::PingPongConfig;
63	/// use std::time::Duration;
64	///
65	/// let config = PingPongConfig::new(
66	///     Duration::from_secs(20),
67	///     Duration::from_secs(8),
68	/// );
69	/// assert_eq!(config.ping_interval(), Duration::from_secs(20));
70	/// assert_eq!(config.pong_timeout(), Duration::from_secs(8));
71	/// ```
72	pub fn new(ping_interval: Duration, pong_timeout: Duration) -> Self {
73		Self {
74			ping_interval,
75			pong_timeout,
76		}
77	}
78
79	/// Returns the ping interval duration.
80	pub fn ping_interval(&self) -> Duration {
81		self.ping_interval
82	}
83
84	/// Returns the pong timeout duration.
85	pub fn pong_timeout(&self) -> Duration {
86		self.pong_timeout
87	}
88
89	/// Sets the ping interval duration.
90	///
91	/// # Examples
92	///
93	/// ```
94	/// use reinhardt_websockets::connection::PingPongConfig;
95	/// use std::time::Duration;
96	///
97	/// let config = PingPongConfig::default()
98	///     .with_ping_interval(Duration::from_secs(60));
99	/// assert_eq!(config.ping_interval(), Duration::from_secs(60));
100	/// ```
101	pub fn with_ping_interval(mut self, interval: Duration) -> Self {
102		self.ping_interval = interval;
103		self
104	}
105
106	/// Sets the pong timeout duration.
107	///
108	/// # Examples
109	///
110	/// ```
111	/// use reinhardt_websockets::connection::PingPongConfig;
112	/// use std::time::Duration;
113	///
114	/// let config = PingPongConfig::default()
115	///     .with_pong_timeout(Duration::from_secs(15));
116	/// assert_eq!(config.pong_timeout(), Duration::from_secs(15));
117	/// ```
118	pub fn with_pong_timeout(mut self, timeout: Duration) -> Self {
119		self.pong_timeout = timeout;
120		self
121	}
122}
123
124/// Connection timeout configuration
125///
126/// This struct defines the timeout settings for WebSocket connections
127/// to prevent resource exhaustion from idle connections.
128///
129/// # Fields
130///
131/// - `idle_timeout` - Maximum duration a connection can be idle before being closed (default: 5 minutes)
132/// - `handshake_timeout` - Maximum duration for the WebSocket handshake to complete (default: 10 seconds)
133/// - `cleanup_interval` - Interval for checking idle connections (default: 30 seconds)
134///
135/// # Examples
136///
137/// ```
138/// use reinhardt_websockets::connection::ConnectionConfig;
139/// use std::time::Duration;
140///
141/// let config = ConnectionConfig::new()
142///     .with_idle_timeout(Duration::from_secs(300))
143///     .with_handshake_timeout(Duration::from_secs(10))
144///     .with_cleanup_interval(Duration::from_secs(30));
145///
146/// assert_eq!(config.idle_timeout(), Duration::from_secs(300));
147/// assert_eq!(config.handshake_timeout(), Duration::from_secs(10));
148/// assert_eq!(config.cleanup_interval(), Duration::from_secs(30));
149/// ```
150#[deprecated(
151	since = "0.2.0",
152	note = "Use `ConnectionSettings` with the `#[settings]` macro instead."
153)]
154#[derive(Debug, Clone)]
155pub struct ConnectionConfig {
156	idle_timeout: Duration,
157	handshake_timeout: Duration,
158	cleanup_interval: Duration,
159	/// Maximum number of concurrent connections allowed (None for unlimited)
160	max_connections: Option<usize>,
161	/// Ping/pong keepalive configuration
162	ping_config: PingPongConfig,
163}
164
165impl Default for ConnectionConfig {
166	fn default() -> Self {
167		Self {
168			idle_timeout: Duration::from_secs(300), // 5 minutes default
169			handshake_timeout: Duration::from_secs(10), // 10 seconds default
170			cleanup_interval: Duration::from_secs(30), // 30 seconds default
171			max_connections: None,                  // Unlimited by default
172			ping_config: PingPongConfig::default(),
173		}
174	}
175}
176
177impl ConnectionConfig {
178	/// Create a new connection configuration with default values
179	///
180	/// # Examples
181	///
182	/// ```
183	/// use reinhardt_websockets::connection::ConnectionConfig;
184	/// use std::time::Duration;
185	///
186	/// let config = ConnectionConfig::new();
187	/// assert_eq!(config.idle_timeout(), Duration::from_secs(300));
188	/// assert_eq!(config.handshake_timeout(), Duration::from_secs(10));
189	/// assert_eq!(config.cleanup_interval(), Duration::from_secs(30));
190	/// ```
191	pub fn new() -> Self {
192		Self::default()
193	}
194
195	/// Set the idle timeout duration
196	///
197	/// # Arguments
198	///
199	/// * `timeout` - Maximum duration a connection can be idle before being closed
200	///
201	/// # Examples
202	///
203	/// ```
204	/// use reinhardt_websockets::connection::ConnectionConfig;
205	/// use std::time::Duration;
206	///
207	/// let config = ConnectionConfig::new()
208	///     .with_idle_timeout(Duration::from_secs(60));
209	///
210	/// assert_eq!(config.idle_timeout(), Duration::from_secs(60));
211	/// ```
212	pub fn with_idle_timeout(mut self, timeout: Duration) -> Self {
213		self.idle_timeout = timeout;
214		self
215	}
216
217	/// Set the handshake timeout duration
218	///
219	/// # Arguments
220	///
221	/// * `timeout` - Maximum duration for the WebSocket handshake to complete
222	///
223	/// # Examples
224	///
225	/// ```
226	/// use reinhardt_websockets::connection::ConnectionConfig;
227	/// use std::time::Duration;
228	///
229	/// let config = ConnectionConfig::new()
230	///     .with_handshake_timeout(Duration::from_secs(5));
231	///
232	/// assert_eq!(config.handshake_timeout(), Duration::from_secs(5));
233	/// ```
234	pub fn with_handshake_timeout(mut self, timeout: Duration) -> Self {
235		self.handshake_timeout = timeout;
236		self
237	}
238
239	/// Set the cleanup interval for checking idle connections
240	///
241	/// # Arguments
242	///
243	/// * `interval` - How often to check for idle connections
244	///
245	/// # Examples
246	///
247	/// ```
248	/// use reinhardt_websockets::connection::ConnectionConfig;
249	/// use std::time::Duration;
250	///
251	/// let config = ConnectionConfig::new()
252	///     .with_cleanup_interval(Duration::from_secs(15));
253	///
254	/// assert_eq!(config.cleanup_interval(), Duration::from_secs(15));
255	/// ```
256	pub fn with_cleanup_interval(mut self, interval: Duration) -> Self {
257		self.cleanup_interval = interval;
258		self
259	}
260
261	/// Get the idle timeout duration
262	pub fn idle_timeout(&self) -> Duration {
263		self.idle_timeout
264	}
265
266	/// Get the handshake timeout duration
267	pub fn handshake_timeout(&self) -> Duration {
268		self.handshake_timeout
269	}
270
271	/// Get the cleanup interval duration
272	pub fn cleanup_interval(&self) -> Duration {
273		self.cleanup_interval
274	}
275
276	/// Set the maximum number of concurrent connections
277	///
278	/// # Arguments
279	///
280	/// * `max` - Maximum number of connections allowed. Use `None` for unlimited.
281	///
282	/// # Examples
283	///
284	/// ```
285	/// use reinhardt_websockets::connection::ConnectionConfig;
286	///
287	/// let config = ConnectionConfig::new()
288	///     .with_max_connections(Some(1000));
289	///
290	/// assert_eq!(config.max_connections(), Some(1000));
291	/// ```
292	pub fn with_max_connections(mut self, max: Option<usize>) -> Self {
293		self.max_connections = max;
294		self
295	}
296
297	/// Get the maximum number of concurrent connections
298	pub fn max_connections(&self) -> Option<usize> {
299		self.max_connections
300	}
301
302	/// Set the ping/pong keepalive configuration.
303	///
304	/// # Arguments
305	///
306	/// * `config` - The ping/pong configuration to use
307	///
308	/// # Examples
309	///
310	/// ```
311	/// use reinhardt_websockets::connection::{ConnectionConfig, PingPongConfig};
312	/// use std::time::Duration;
313	///
314	/// let ping_config = PingPongConfig::new(
315	///     Duration::from_secs(15),
316	///     Duration::from_secs(5),
317	/// );
318	/// let config = ConnectionConfig::new()
319	///     .with_ping_config(ping_config);
320	///
321	/// assert_eq!(config.ping_config().ping_interval(), Duration::from_secs(15));
322	/// assert_eq!(config.ping_config().pong_timeout(), Duration::from_secs(5));
323	/// ```
324	pub fn with_ping_config(mut self, config: PingPongConfig) -> Self {
325		self.ping_config = config;
326		self
327	}
328
329	/// Get the ping/pong keepalive configuration.
330	pub fn ping_config(&self) -> &PingPongConfig {
331		&self.ping_config
332	}
333
334	/// Create a configuration with no idle timeout (connections never time out)
335	///
336	/// # Examples
337	///
338	/// ```
339	/// use reinhardt_websockets::connection::ConnectionConfig;
340	///
341	/// let config = ConnectionConfig::no_timeout();
342	/// assert_eq!(config.idle_timeout(), std::time::Duration::MAX);
343	/// assert_eq!(config.handshake_timeout(), std::time::Duration::MAX);
344	/// ```
345	pub fn no_timeout() -> Self {
346		Self {
347			idle_timeout: Duration::MAX,
348			handshake_timeout: Duration::MAX,
349			cleanup_interval: Duration::from_secs(30),
350			max_connections: None,
351			ping_config: PingPongConfig::default(),
352		}
353	}
354
355	/// Create a strict configuration with short timeouts
356	///
357	/// - Idle timeout: 30 seconds
358	/// - Handshake timeout: 5 seconds
359	/// - Cleanup interval: 10 seconds
360	///
361	/// # Examples
362	///
363	/// ```
364	/// use reinhardt_websockets::connection::ConnectionConfig;
365	/// use std::time::Duration;
366	///
367	/// let config = ConnectionConfig::strict();
368	/// assert_eq!(config.idle_timeout(), Duration::from_secs(30));
369	/// assert_eq!(config.handshake_timeout(), Duration::from_secs(5));
370	/// assert_eq!(config.cleanup_interval(), Duration::from_secs(10));
371	/// ```
372	pub fn strict() -> Self {
373		Self {
374			idle_timeout: Duration::from_secs(30),
375			handshake_timeout: Duration::from_secs(5),
376			cleanup_interval: Duration::from_secs(10),
377			max_connections: None,
378			ping_config: PingPongConfig::new(Duration::from_secs(10), Duration::from_secs(5)),
379		}
380	}
381
382	/// Create a permissive configuration with long timeouts
383	///
384	/// - Idle timeout: 1 hour
385	/// - Handshake timeout: 30 seconds
386	/// - Cleanup interval: 60 seconds
387	///
388	/// # Examples
389	///
390	/// ```
391	/// use reinhardt_websockets::connection::ConnectionConfig;
392	/// use std::time::Duration;
393	///
394	/// let config = ConnectionConfig::permissive();
395	/// assert_eq!(config.idle_timeout(), Duration::from_secs(3600));
396	/// assert_eq!(config.handshake_timeout(), Duration::from_secs(30));
397	/// assert_eq!(config.cleanup_interval(), Duration::from_secs(60));
398	/// ```
399	pub fn permissive() -> Self {
400		Self {
401			idle_timeout: Duration::from_secs(3600),
402			handshake_timeout: Duration::from_secs(30),
403			cleanup_interval: Duration::from_secs(60),
404			max_connections: None,
405			ping_config: PingPongConfig::new(Duration::from_secs(60), Duration::from_secs(30)),
406		}
407	}
408}
409
410/// Errors that can occur during WebSocket operations.
411#[derive(Debug, thiserror::Error)]
412pub enum WebSocketError {
413	/// A connection-level error occurred.
414	#[error("Connection error")]
415	Connection(String),
416	/// Failed to send a message to the peer.
417	#[error("Send failed")]
418	Send(String),
419	/// Failed to receive a message from the peer.
420	#[error("Receive failed")]
421	Receive(String),
422	/// A WebSocket protocol violation was detected.
423	#[error("Protocol error")]
424	Protocol(String),
425	/// An internal server error occurred.
426	#[error("Internal error")]
427	Internal(String),
428	/// The connection timed out after the given duration of inactivity.
429	#[error("Connection timed out")]
430	Timeout(Duration),
431	/// Reconnection failed after the specified number of attempts.
432	#[error("Reconnection failed")]
433	ReconnectFailed(u32),
434	/// The binary payload was invalid or could not be decoded.
435	#[error("Invalid binary payload: {0}")]
436	BinaryPayload(String),
437	/// No pong response was received within the heartbeat timeout.
438	#[error("Heartbeat timeout: no pong received within {0:?}")]
439	HeartbeatTimeout(Duration),
440	/// The consumer could not keep up with the message rate.
441	#[error("Slow consumer: send timed out after {0:?}")]
442	SlowConsumer(Duration),
443}
444
445impl WebSocketError {
446	/// Returns a sanitized error message safe for client-facing communication.
447	///
448	/// Internal details (buffer sizes, queue depths, connection state, type names)
449	/// are stripped to prevent information leakage.
450	pub fn client_message(&self) -> &'static str {
451		match self {
452			Self::Connection(_) => "Connection error",
453			Self::Send(_) => "Failed to send message",
454			Self::Receive(_) => "Failed to receive message",
455			Self::Protocol(_) => "Protocol error",
456			Self::Internal(_) => "Internal server error",
457			Self::Timeout(_) => "Connection timed out",
458			Self::ReconnectFailed(_) => "Reconnection failed",
459			Self::BinaryPayload(_) => "Invalid message format",
460			Self::HeartbeatTimeout(_) => "Connection timed out",
461			Self::SlowConsumer(_) => "Server overloaded",
462		}
463	}
464
465	/// Returns the internal detail message for logging purposes.
466	///
467	/// This MUST NOT be sent to clients as it may contain sensitive
468	/// internal state information.
469	pub fn internal_detail(&self) -> String {
470		match self {
471			Self::Connection(msg) => format!("Connection error: {}", msg),
472			Self::Send(msg) => format!("Send error: {}", msg),
473			Self::Receive(msg) => format!("Receive error: {}", msg),
474			Self::Protocol(msg) => format!("Protocol error: {}", msg),
475			Self::Internal(msg) => format!("Internal error: {}", msg),
476			Self::Timeout(d) => format!("Connection timeout: idle for {:?}", d),
477			Self::ReconnectFailed(n) => format!("Reconnection failed after {} attempts", n),
478			Self::BinaryPayload(msg) => format!("Invalid binary payload: {}", msg),
479			Self::HeartbeatTimeout(d) => format!("Heartbeat timeout: no pong within {:?}", d),
480			Self::SlowConsumer(d) => format!("Slow consumer: send timed out after {:?}", d),
481		}
482	}
483}
484
485/// A specialized `Result` type for WebSocket operations.
486pub type WebSocketResult<T> = Result<T, WebSocketError>;
487
488/// WebSocket message types
489#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
490#[serde(tag = "type")]
491pub enum Message {
492	/// A UTF-8 text message.
493	Text {
494		/// The text content of the message.
495		data: String,
496	},
497	/// A binary message.
498	Binary {
499		/// The raw bytes of the message.
500		data: Vec<u8>,
501	},
502	/// A ping control frame.
503	Ping,
504	/// A pong control frame (response to ping).
505	Pong,
506	/// A close control frame with status code and reason.
507	Close {
508		/// The WebSocket close status code.
509		code: u16,
510		/// A human-readable reason for closing.
511		reason: String,
512	},
513}
514
515impl Message {
516	/// Creates a new text message.
517	///
518	/// # Examples
519	///
520	/// ```
521	/// use reinhardt_websockets::Message;
522	///
523	/// let msg = Message::text("Hello, World!".to_string());
524	/// match msg {
525	///     Message::Text { data } => assert_eq!(data, "Hello, World!"),
526	///     _ => panic!("Expected text message"),
527	/// }
528	/// ```
529	pub fn text(data: String) -> Self {
530		Self::Text { data }
531	}
532	/// Creates a new binary message.
533	///
534	/// # Examples
535	///
536	/// ```
537	/// use reinhardt_websockets::Message;
538	///
539	/// let data = vec![0x48, 0x65, 0x6c, 0x6c, 0x6f]; // "Hello" in bytes
540	/// let msg = Message::binary(data.clone());
541	/// match msg {
542	///     Message::Binary { data: d } => assert_eq!(d, data),
543	///     _ => panic!("Expected binary message"),
544	/// }
545	/// ```
546	pub fn binary(data: Vec<u8>) -> Self {
547		Self::Binary { data }
548	}
549	/// Creates a text message containing JSON-serialized data.
550	///
551	/// # Examples
552	///
553	/// ```
554	/// use reinhardt_websockets::Message;
555	/// use serde::Serialize;
556	///
557	/// #[derive(Serialize)]
558	/// struct User {
559	///     name: String,
560	///     age: u32,
561	/// }
562	///
563	/// let user = User {
564	///     name: "Alice".to_string(),
565	///     age: 30,
566	/// };
567	///
568	/// let msg = Message::json(&user).unwrap();
569	/// match msg {
570	///     Message::Text { data } => {
571	///         assert!(data.contains("Alice"));
572	///         assert!(data.contains("30"));
573	///     },
574	///     _ => panic!("Expected text message"),
575	/// }
576	/// ```
577	pub fn json<T: serde::Serialize>(data: &T) -> WebSocketResult<Self> {
578		let json =
579			serde_json::to_string(data).map_err(|e| WebSocketError::Protocol(e.to_string()))?;
580		Ok(Self::text(json))
581	}
582	/// Parses the message content as JSON into the target type.
583	///
584	/// # Examples
585	///
586	/// ```
587	/// use reinhardt_websockets::Message;
588	/// use serde::Deserialize;
589	///
590	/// #[derive(Deserialize, Debug, PartialEq)]
591	/// struct User {
592	///     name: String,
593	///     age: u32,
594	/// }
595	///
596	/// let msg = Message::text(r#"{"name":"Bob","age":25}"#.to_string());
597	/// let user: User = msg.parse_json().unwrap();
598	/// assert_eq!(user.name, "Bob");
599	/// assert_eq!(user.age, 25);
600	/// ```
601	pub fn parse_json<T: serde::de::DeserializeOwned>(&self) -> WebSocketResult<T> {
602		match self {
603			Message::Text { data } => {
604				serde_json::from_str(data).map_err(|e| WebSocketError::Protocol(e.to_string()))
605			}
606			_ => Err(WebSocketError::Protocol("Not a text message".to_string())),
607		}
608	}
609}
610
611/// WebSocket connection with activity tracking and timeout support
612pub struct WebSocketConnection {
613	id: String,
614	tx: mpsc::UnboundedSender<Message>,
615	closed: Arc<RwLock<bool>>,
616	/// Subprotocol (negotiated protocol during WebSocket handshake)
617	subprotocol: Option<String>,
618	/// Timestamp of last activity on this connection
619	last_activity: Arc<RwLock<Instant>>,
620	/// Connection timeout configuration
621	config: ConnectionConfig,
622}
623
624impl WebSocketConnection {
625	/// Creates a new WebSocket connection with the given ID and sender.
626	///
627	/// Uses default [`ConnectionConfig`] for timeout settings.
628	///
629	/// # Examples
630	///
631	/// ```
632	/// use reinhardt_websockets::{WebSocketConnection, Message};
633	/// use tokio::sync::mpsc;
634	///
635	/// let (tx, _rx) = mpsc::unbounded_channel();
636	/// let conn = WebSocketConnection::new("connection_1".to_string(), tx);
637	/// assert_eq!(conn.id(), "connection_1");
638	/// ```
639	pub fn new(id: String, tx: mpsc::UnboundedSender<Message>) -> Self {
640		Self {
641			id,
642			tx,
643			closed: Arc::new(RwLock::new(false)),
644			subprotocol: None,
645			last_activity: Arc::new(RwLock::new(Instant::now())),
646			config: ConnectionConfig::default(),
647		}
648	}
649
650	/// Creates a new WebSocket connection with the given ID, sender, and configuration.
651	///
652	/// # Examples
653	///
654	/// ```
655	/// use reinhardt_websockets::{WebSocketConnection, Message};
656	/// use reinhardt_websockets::connection::ConnectionConfig;
657	/// use tokio::sync::mpsc;
658	/// use std::time::Duration;
659	///
660	/// let (tx, _rx) = mpsc::unbounded_channel();
661	/// let config = ConnectionConfig::new()
662	///     .with_idle_timeout(Duration::from_secs(60));
663	/// let conn = WebSocketConnection::with_config("conn_1".to_string(), tx, config);
664	/// assert_eq!(conn.id(), "conn_1");
665	/// assert_eq!(conn.config().idle_timeout(), Duration::from_secs(60));
666	/// ```
667	pub fn with_config(
668		id: String,
669		tx: mpsc::UnboundedSender<Message>,
670		config: ConnectionConfig,
671	) -> Self {
672		Self {
673			id,
674			tx,
675			closed: Arc::new(RwLock::new(false)),
676			subprotocol: None,
677			last_activity: Arc::new(RwLock::new(Instant::now())),
678			config,
679		}
680	}
681
682	/// Creates a new WebSocket connection with the given ID, sender, and subprotocol.
683	///
684	/// # Examples
685	///
686	/// ```
687	/// use reinhardt_websockets::{WebSocketConnection, Message};
688	/// use tokio::sync::mpsc;
689	///
690	/// let (tx, _rx) = mpsc::unbounded_channel();
691	/// let conn = WebSocketConnection::with_subprotocol(
692	///     "connection_1".to_string(),
693	///     tx,
694	///     Some("chat".to_string())
695	/// );
696	/// assert_eq!(conn.id(), "connection_1");
697	/// assert_eq!(conn.subprotocol(), Some("chat"));
698	/// ```
699	pub fn with_subprotocol(
700		id: String,
701		tx: mpsc::UnboundedSender<Message>,
702		subprotocol: Option<String>,
703	) -> Self {
704		Self {
705			id,
706			tx,
707			closed: Arc::new(RwLock::new(false)),
708			subprotocol,
709			last_activity: Arc::new(RwLock::new(Instant::now())),
710			config: ConnectionConfig::default(),
711		}
712	}
713
714	/// Gets the negotiated subprotocol, if any.
715	///
716	/// # Examples
717	///
718	/// ```
719	/// use reinhardt_websockets::{WebSocketConnection, Message};
720	/// use tokio::sync::mpsc;
721	///
722	/// let (tx, _rx) = mpsc::unbounded_channel();
723	/// let conn = WebSocketConnection::with_subprotocol(
724	///     "test".to_string(),
725	///     tx,
726	///     Some("chat".to_string())
727	/// );
728	/// assert_eq!(conn.subprotocol(), Some("chat"));
729	/// ```
730	pub fn subprotocol(&self) -> Option<&str> {
731		self.subprotocol.as_deref()
732	}
733
734	/// Gets the connection ID.
735	///
736	/// # Examples
737	///
738	/// ```
739	/// use reinhardt_websockets::{WebSocketConnection, Message};
740	/// use tokio::sync::mpsc;
741	///
742	/// let (tx, _rx) = mpsc::unbounded_channel();
743	/// let conn = WebSocketConnection::new("test_id".to_string(), tx);
744	/// assert_eq!(conn.id(), "test_id");
745	/// ```
746	pub fn id(&self) -> &str {
747		&self.id
748	}
749
750	/// Gets the connection timeout configuration.
751	pub fn config(&self) -> &ConnectionConfig {
752		&self.config
753	}
754
755	/// Records activity on the connection, resetting the idle timer.
756	///
757	/// This is called automatically when sending messages, but can also be called
758	/// manually to indicate that the connection is still active (e.g., when
759	/// receiving messages from the client).
760	///
761	/// # Examples
762	///
763	/// ```
764	/// use reinhardt_websockets::WebSocketConnection;
765	/// use tokio::sync::mpsc;
766	///
767	/// # tokio_test::block_on(async {
768	/// let (tx, _rx) = mpsc::unbounded_channel();
769	/// let conn = WebSocketConnection::new("test".to_string(), tx);
770	///
771	/// conn.record_activity().await;
772	/// assert!(!conn.is_idle().await);
773	/// # });
774	/// ```
775	pub async fn record_activity(&self) {
776		*self.last_activity.write().await = Instant::now();
777	}
778
779	/// Returns the duration since the last activity on this connection.
780	///
781	/// # Examples
782	///
783	/// ```
784	/// use reinhardt_websockets::WebSocketConnection;
785	/// use tokio::sync::mpsc;
786	/// use std::time::Duration;
787	///
788	/// # tokio_test::block_on(async {
789	/// let (tx, _rx) = mpsc::unbounded_channel();
790	/// let conn = WebSocketConnection::new("test".to_string(), tx);
791	///
792	/// let idle = conn.idle_duration().await;
793	/// assert!(idle < Duration::from_secs(1));
794	/// # });
795	/// ```
796	pub async fn idle_duration(&self) -> Duration {
797		self.last_activity.read().await.elapsed()
798	}
799
800	/// Checks whether this connection has exceeded its idle timeout.
801	///
802	/// # Examples
803	///
804	/// ```
805	/// use reinhardt_websockets::WebSocketConnection;
806	/// use tokio::sync::mpsc;
807	///
808	/// # tokio_test::block_on(async {
809	/// let (tx, _rx) = mpsc::unbounded_channel();
810	/// let conn = WebSocketConnection::new("test".to_string(), tx);
811	///
812	/// // A freshly created connection is not idle
813	/// assert!(!conn.is_idle().await);
814	/// # });
815	/// ```
816	pub async fn is_idle(&self) -> bool {
817		self.idle_duration().await > self.config.idle_timeout
818	}
819
820	/// Sends a message through the WebSocket connection.
821	///
822	/// Records activity on the connection when a message is sent successfully.
823	///
824	/// # Examples
825	///
826	/// ```
827	/// use reinhardt_websockets::{WebSocketConnection, Message};
828	/// use tokio::sync::mpsc;
829	///
830	/// # tokio_test::block_on(async {
831	/// let (tx, mut rx) = mpsc::unbounded_channel();
832	/// let conn = WebSocketConnection::new("test".to_string(), tx);
833	///
834	/// let message = Message::text("Hello".to_string());
835	/// conn.send(message).await.unwrap();
836	///
837	/// let received = rx.recv().await.unwrap();
838	/// assert!(matches!(received, Message::Text { .. }));
839	/// # });
840	/// ```
841	pub async fn send(&self, message: Message) -> WebSocketResult<()> {
842		if *self.closed.read().await {
843			return Err(WebSocketError::Send("Connection closed".to_string()));
844		}
845
846		let result = self
847			.tx
848			.send(message)
849			.map_err(|e| WebSocketError::Send(e.to_string()));
850
851		if result.is_ok() {
852			self.record_activity().await;
853		}
854
855		result
856	}
857	/// Sends a text message through the WebSocket connection.
858	///
859	/// # Examples
860	///
861	/// ```
862	/// use reinhardt_websockets::{WebSocketConnection, Message};
863	/// use tokio::sync::mpsc;
864	///
865	/// # tokio_test::block_on(async {
866	/// let (tx, mut rx) = mpsc::unbounded_channel();
867	/// let conn = WebSocketConnection::new("test".to_string(), tx);
868	///
869	/// conn.send_text("Hello World".to_string()).await.unwrap();
870	///
871	/// let received = rx.recv().await.unwrap();
872	/// match received {
873	///     Message::Text { data } => assert_eq!(data, "Hello World"),
874	///     _ => panic!("Expected text message"),
875	/// }
876	/// # });
877	/// ```
878	pub async fn send_text(&self, text: String) -> WebSocketResult<()> {
879		self.send(Message::text(text)).await
880	}
881	/// Sends a binary message through the WebSocket connection.
882	///
883	/// # Examples
884	///
885	/// ```
886	/// use reinhardt_websockets::{WebSocketConnection, Message};
887	/// use tokio::sync::mpsc;
888	///
889	/// # tokio_test::block_on(async {
890	/// let (tx, mut rx) = mpsc::unbounded_channel();
891	/// let conn = WebSocketConnection::new("test".to_string(), tx);
892	///
893	/// let binary_data = vec![0x48, 0x65, 0x6c, 0x6c, 0x6f]; // "Hello"
894	/// conn.send_binary(binary_data.clone()).await.unwrap();
895	///
896	/// let received = rx.recv().await.unwrap();
897	/// match received {
898	///     Message::Binary { data } => assert_eq!(data, binary_data),
899	///     _ => panic!("Expected binary message"),
900	/// }
901	/// # });
902	/// ```
903	pub async fn send_binary(&self, data: Vec<u8>) -> WebSocketResult<()> {
904		self.send(Message::binary(data)).await
905	}
906	/// Sends a JSON message through the WebSocket connection.
907	///
908	/// # Examples
909	///
910	/// ```
911	/// use reinhardt_websockets::{WebSocketConnection, Message};
912	/// use tokio::sync::mpsc;
913	/// use serde::Serialize;
914	///
915	/// #[derive(Serialize)]
916	/// struct User {
917	///     name: String,
918	///     age: u32,
919	/// }
920	///
921	/// # tokio_test::block_on(async {
922	/// let (tx, mut rx) = mpsc::unbounded_channel();
923	/// let conn = WebSocketConnection::new("test".to_string(), tx);
924	///
925	/// let user = User { name: "Alice".to_string(), age: 30 };
926	/// conn.send_json(&user).await.unwrap();
927	///
928	/// let received = rx.recv().await.unwrap();
929	/// match received {
930	///     Message::Text { data } => assert!(data.contains("Alice")),
931	///     _ => panic!("Expected text message"),
932	/// }
933	/// # });
934	/// ```
935	pub async fn send_json<T: serde::Serialize>(&self, data: &T) -> WebSocketResult<()> {
936		let message = Message::json(data)?;
937		self.send(message).await
938	}
939	/// Closes the WebSocket connection.
940	///
941	/// The connection is always marked as closed regardless of whether the
942	/// close frame could be sent. This ensures resource cleanup even when
943	/// the underlying channel is already broken.
944	///
945	/// # Examples
946	///
947	/// ```
948	/// use reinhardt_websockets::{WebSocketConnection, Message};
949	/// use tokio::sync::mpsc;
950	///
951	/// # tokio_test::block_on(async {
952	/// let (tx, mut rx) = mpsc::unbounded_channel();
953	/// let conn = WebSocketConnection::new("test".to_string(), tx);
954	///
955	/// conn.close().await.unwrap();
956	/// assert!(conn.is_closed().await);
957	/// # });
958	/// ```
959	pub async fn close(&self) -> WebSocketResult<()> {
960		// Mark as closed first to prevent new sends
961		*self.closed.write().await = true;
962
963		// Best-effort send of close frame; connection is closed regardless
964		self.tx
965			.send(Message::Close {
966				code: 1000,
967				reason: "Normal closure".to_string(),
968			})
969			.map_err(|e| WebSocketError::Send(e.to_string()))
970	}
971	/// Closes the connection with a custom close code and reason.
972	///
973	/// The connection is always marked as closed regardless of whether the
974	/// close frame could be sent. This ensures resource cleanup even when
975	/// the underlying channel is already broken.
976	///
977	/// # Examples
978	///
979	/// ```
980	/// use reinhardt_websockets::{WebSocketConnection, Message};
981	/// use tokio::sync::mpsc;
982	///
983	/// # tokio_test::block_on(async {
984	/// let (tx, mut rx) = mpsc::unbounded_channel();
985	/// let conn = WebSocketConnection::new("test".to_string(), tx);
986	///
987	/// conn.close_with_reason(1001, "Idle timeout".to_string()).await.unwrap();
988	/// assert!(conn.is_closed().await);
989	///
990	/// let msg = rx.recv().await.unwrap();
991	/// match msg {
992	///     Message::Close { code, reason } => {
993	///         assert_eq!(code, 1001);
994	///         assert_eq!(reason, "Idle timeout");
995	///     },
996	///     _ => panic!("Expected close message"),
997	/// }
998	/// # });
999	/// ```
1000	pub async fn close_with_reason(&self, code: u16, reason: String) -> WebSocketResult<()> {
1001		// Mark as closed first to prevent new sends
1002		*self.closed.write().await = true;
1003
1004		// Best-effort send of close frame; connection is closed regardless
1005		self.tx
1006			.send(Message::Close { code, reason })
1007			.map_err(|e| WebSocketError::Send(e.to_string()))
1008	}
1009
1010	/// Forces the connection closed without sending a close frame.
1011	///
1012	/// Use this for abnormal close paths where the underlying transport is
1013	/// already broken and sending a close frame would fail.
1014	///
1015	/// # Examples
1016	///
1017	/// ```
1018	/// use reinhardt_websockets::WebSocketConnection;
1019	/// use tokio::sync::mpsc;
1020	///
1021	/// # tokio_test::block_on(async {
1022	/// let (tx, _rx) = mpsc::unbounded_channel();
1023	/// let conn = WebSocketConnection::new("test".to_string(), tx);
1024	///
1025	/// conn.force_close().await;
1026	/// assert!(conn.is_closed().await);
1027	/// # });
1028	/// ```
1029	pub async fn force_close(&self) {
1030		*self.closed.write().await = true;
1031	}
1032
1033	/// Checks if the WebSocket connection is closed.
1034	///
1035	/// # Examples
1036	///
1037	/// ```
1038	/// use reinhardt_websockets::{WebSocketConnection, Message};
1039	/// use tokio::sync::mpsc;
1040	///
1041	/// # tokio_test::block_on(async {
1042	/// let (tx, _rx) = mpsc::unbounded_channel();
1043	/// let conn = WebSocketConnection::new("test".to_string(), tx);
1044	///
1045	/// assert!(!conn.is_closed().await);
1046	/// # });
1047	/// ```
1048	pub async fn is_closed(&self) -> bool {
1049		*self.closed.read().await
1050	}
1051}
1052
1053/// Monitors WebSocket connections for idle timeouts and cleans them up.
1054///
1055/// The monitor periodically checks all registered connections and closes
1056/// those that have exceeded their idle timeout. This prevents resource
1057/// exhaustion from idle connection holding attacks.
1058///
1059/// # Examples
1060///
1061/// ```
1062/// use reinhardt_websockets::connection::{ConnectionConfig, ConnectionTimeoutMonitor};
1063/// use reinhardt_websockets::WebSocketConnection;
1064/// use tokio::sync::mpsc;
1065/// use std::sync::Arc;
1066/// use std::time::Duration;
1067///
1068/// # tokio_test::block_on(async {
1069/// let config = ConnectionConfig::new()
1070///     .with_idle_timeout(Duration::from_secs(60))
1071///     .with_cleanup_interval(Duration::from_secs(10));
1072///
1073/// let monitor = ConnectionTimeoutMonitor::new(config);
1074///
1075/// let (tx, _rx) = mpsc::unbounded_channel();
1076/// let conn = Arc::new(WebSocketConnection::new("conn_1".to_string(), tx));
1077/// monitor.register(conn).await.unwrap();
1078///
1079/// assert_eq!(monitor.connection_count().await, 1);
1080/// # });
1081/// ```
1082pub struct ConnectionTimeoutMonitor {
1083	connections: Arc<RwLock<HashMap<String, Arc<WebSocketConnection>>>>,
1084	config: ConnectionConfig,
1085}
1086
1087impl ConnectionTimeoutMonitor {
1088	/// Creates a new connection timeout monitor with the given configuration.
1089	pub fn new(config: ConnectionConfig) -> Self {
1090		Self {
1091			connections: Arc::new(RwLock::new(HashMap::new())),
1092			config,
1093		}
1094	}
1095
1096	/// Registers a connection for timeout monitoring.
1097	///
1098	/// Returns `Ok(())` if the connection was registered, or `Err` if the
1099	/// maximum connection limit has been reached.
1100	pub async fn register(
1101		&self,
1102		connection: Arc<WebSocketConnection>,
1103	) -> Result<(), WebSocketError> {
1104		let mut connections = self.connections.write().await;
1105
1106		if let Some(max) = self.config.max_connections
1107			&& connections.len() >= max
1108		{
1109			return Err(WebSocketError::Connection(format!(
1110				"maximum connection limit reached ({})",
1111				max
1112			)));
1113		}
1114
1115		connections.insert(connection.id().to_string(), connection);
1116		Ok(())
1117	}
1118
1119	/// Unregisters a connection from timeout monitoring.
1120	pub async fn unregister(&self, connection_id: &str) {
1121		self.connections.write().await.remove(connection_id);
1122	}
1123
1124	/// Returns the number of currently monitored connections.
1125	pub async fn connection_count(&self) -> usize {
1126		self.connections.read().await.len()
1127	}
1128
1129	/// Checks all connections and closes those that have exceeded their idle timeout.
1130	///
1131	/// Returns the IDs of connections that were closed due to timeout.
1132	pub async fn check_idle_connections(&self) -> Vec<String> {
1133		let connections = self.connections.read().await;
1134		let mut timed_out = Vec::new();
1135
1136		for (id, conn) in connections.iter() {
1137			if conn.is_closed().await {
1138				timed_out.push(id.clone());
1139				continue;
1140			}
1141
1142			let idle_duration = conn.idle_duration().await;
1143			if idle_duration > self.config.idle_timeout {
1144				let reason = format!(
1145					"Idle timeout: connection idle for {}s (limit: {}s)",
1146					idle_duration.as_secs(),
1147					self.config.idle_timeout.as_secs()
1148				);
1149				// Close with 1001 (Going Away) as per RFC 6455
1150				let _ = conn.close_with_reason(1001, reason).await;
1151				timed_out.push(id.clone());
1152			}
1153		}
1154
1155		drop(connections);
1156
1157		// Remove timed-out connections
1158		if !timed_out.is_empty() {
1159			let mut connections = self.connections.write().await;
1160			for id in &timed_out {
1161				connections.remove(id);
1162			}
1163		}
1164
1165		timed_out
1166	}
1167
1168	/// Gracefully shuts down all monitored connections.
1169	///
1170	/// Sends a Close frame (code 1001, "Going Away") to each active connection
1171	/// and removes it from monitoring. Already-closed connections are silently
1172	/// removed.
1173	///
1174	/// Returns the IDs of all connections that were shut down.
1175	pub async fn shutdown_all(&self) -> Vec<String> {
1176		let mut connections = self.connections.write().await;
1177		let mut shut_down = Vec::with_capacity(connections.len());
1178
1179		for (id, conn) in connections.drain() {
1180			if !conn.is_closed().await {
1181				let _ = conn
1182					.close_with_reason(1001, "Server shutting down".to_string())
1183					.await;
1184			}
1185			shut_down.push(id);
1186		}
1187
1188		shut_down
1189	}
1190
1191	/// Starts the background monitoring task.
1192	///
1193	/// Returns a [`tokio::task::JoinHandle`] that can be used to abort the monitor.
1194	/// The monitor runs until the handle is aborted or the process exits.
1195	///
1196	/// # Examples
1197	///
1198	/// ```
1199	/// use reinhardt_websockets::connection::{ConnectionConfig, ConnectionTimeoutMonitor};
1200	/// use std::time::Duration;
1201	///
1202	/// # tokio_test::block_on(async {
1203	/// let config = ConnectionConfig::new()
1204	///     .with_cleanup_interval(Duration::from_millis(100));
1205	/// let monitor = std::sync::Arc::new(ConnectionTimeoutMonitor::new(config));
1206	///
1207	/// let handle = monitor.start();
1208	///
1209	/// // Monitor is running in background...
1210	/// tokio::time::sleep(Duration::from_millis(50)).await;
1211	///
1212	/// // Stop the monitor
1213	/// handle.abort();
1214	/// # });
1215	/// ```
1216	pub fn start(self: &Arc<Self>) -> tokio::task::JoinHandle<()> {
1217		let monitor = Arc::clone(self);
1218		tokio::spawn(async move {
1219			let mut interval = tokio::time::interval(monitor.config.cleanup_interval);
1220			loop {
1221				interval.tick().await;
1222				monitor.check_idle_connections().await;
1223			}
1224		})
1225	}
1226}
1227
1228/// Configuration for heartbeat (ping/pong) monitoring.
1229///
1230/// Defines how often pings are sent and how long to wait for a pong
1231/// response before considering the connection dead.
1232///
1233/// # Examples
1234///
1235/// ```
1236/// use reinhardt_websockets::connection::HeartbeatConfig;
1237/// use std::time::Duration;
1238///
1239/// let config = HeartbeatConfig::new(
1240///     Duration::from_secs(30),
1241///     Duration::from_secs(10),
1242/// );
1243///
1244/// assert_eq!(config.ping_interval(), Duration::from_secs(30));
1245/// assert_eq!(config.pong_timeout(), Duration::from_secs(10));
1246/// ```
1247#[derive(Debug, Clone)]
1248pub struct HeartbeatConfig {
1249	/// Interval between outgoing pings
1250	ping_interval: Duration,
1251	/// Maximum time to wait for a pong response before closing
1252	pong_timeout: Duration,
1253}
1254
1255impl HeartbeatConfig {
1256	/// Creates a new heartbeat configuration.
1257	pub fn new(ping_interval: Duration, pong_timeout: Duration) -> Self {
1258		Self {
1259			ping_interval,
1260			pong_timeout,
1261		}
1262	}
1263
1264	/// Returns the ping interval.
1265	pub fn ping_interval(&self) -> Duration {
1266		self.ping_interval
1267	}
1268
1269	/// Returns the pong timeout.
1270	pub fn pong_timeout(&self) -> Duration {
1271		self.pong_timeout
1272	}
1273}
1274
1275impl Default for HeartbeatConfig {
1276	fn default() -> Self {
1277		Self {
1278			ping_interval: Duration::from_secs(30),
1279			pong_timeout: Duration::from_secs(10),
1280		}
1281	}
1282}
1283
1284/// Monitors a WebSocket connection's heartbeat via ping/pong.
1285///
1286/// Sends periodic pings and tracks when the last pong was received.
1287/// When no pong arrives within the configured timeout, the connection
1288/// is force-closed and the monitor signals a heartbeat failure.
1289///
1290/// # Examples
1291///
1292/// ```
1293/// use reinhardt_websockets::connection::{HeartbeatConfig, HeartbeatMonitor};
1294/// use reinhardt_websockets::WebSocketConnection;
1295/// use tokio::sync::mpsc;
1296/// use std::sync::Arc;
1297/// use std::time::Duration;
1298///
1299/// # tokio_test::block_on(async {
1300/// let (tx, _rx) = mpsc::unbounded_channel();
1301/// let conn = Arc::new(WebSocketConnection::new("hb_test".to_string(), tx));
1302/// let config = HeartbeatConfig::default();
1303///
1304/// let monitor = HeartbeatMonitor::new(conn, config);
1305/// assert!(!monitor.is_timed_out().await);
1306/// # });
1307/// ```
1308pub struct HeartbeatMonitor {
1309	connection: Arc<WebSocketConnection>,
1310	config: HeartbeatConfig,
1311	last_pong: Arc<RwLock<Instant>>,
1312	timed_out: Arc<RwLock<bool>>,
1313	pong_notify: Arc<tokio::sync::Notify>,
1314}
1315
1316impl HeartbeatMonitor {
1317	/// Creates a new heartbeat monitor for the given connection.
1318	pub fn new(connection: Arc<WebSocketConnection>, config: HeartbeatConfig) -> Self {
1319		Self {
1320			connection,
1321			config,
1322			last_pong: Arc::new(RwLock::new(Instant::now())),
1323			timed_out: Arc::new(RwLock::new(false)),
1324			pong_notify: Arc::new(tokio::sync::Notify::new()),
1325		}
1326	}
1327
1328	/// Records a pong response, resetting the timeout tracker.
1329	///
1330	/// This also wakes up the heartbeat monitor's sleep so it can
1331	/// proceed immediately instead of waiting for the full pong timeout.
1332	pub async fn record_pong(&self) {
1333		*self.last_pong.write().await = Instant::now();
1334		self.pong_notify.notify_one();
1335	}
1336
1337	/// Returns the duration since the last pong was received.
1338	pub async fn time_since_last_pong(&self) -> Duration {
1339		self.last_pong.read().await.elapsed()
1340	}
1341
1342	/// Returns whether the heartbeat has timed out.
1343	pub async fn is_timed_out(&self) -> bool {
1344		*self.timed_out.read().await
1345	}
1346
1347	/// Checks whether the pong timeout has been exceeded.
1348	///
1349	/// If the timeout is exceeded, the connection is force-closed and
1350	/// the method returns `true`.
1351	pub async fn check_heartbeat(&self) -> bool {
1352		let since_pong = self.time_since_last_pong().await;
1353
1354		if since_pong > self.config.pong_timeout {
1355			self.connection.force_close().await;
1356			*self.timed_out.write().await = true;
1357			return true;
1358		}
1359
1360		false
1361	}
1362
1363	/// Sends a ping message through the connection.
1364	///
1365	/// Returns `Ok(())` if the ping was sent, or an error if the
1366	/// connection is already closed.
1367	pub async fn send_ping(&self) -> WebSocketResult<()> {
1368		self.connection.send(Message::Ping).await
1369	}
1370
1371	/// Returns a reference to the heartbeat configuration.
1372	pub fn config(&self) -> &HeartbeatConfig {
1373		&self.config
1374	}
1375
1376	/// Returns a reference to the monitored connection.
1377	pub fn connection(&self) -> &Arc<WebSocketConnection> {
1378		&self.connection
1379	}
1380
1381	/// Starts a background task that periodically sends pings and checks
1382	/// for pong timeouts.
1383	///
1384	/// Returns a [`tokio::task::JoinHandle`] that can be aborted to stop
1385	/// the monitor. The task ends automatically when a heartbeat timeout
1386	/// occurs.
1387	pub fn start(self: &Arc<Self>) -> tokio::task::JoinHandle<()> {
1388		let monitor = Arc::clone(self);
1389		tokio::spawn(async move {
1390			let mut interval = tokio::time::interval(monitor.config.ping_interval);
1391			loop {
1392				interval.tick().await;
1393
1394				if monitor.connection.is_closed().await {
1395					break;
1396				}
1397
1398				// Best-effort ping; if send fails, check_heartbeat will catch it
1399				let _ = monitor.send_ping().await;
1400
1401				// Wait for pong or timeout, whichever comes first.
1402				// If pong arrives early, we skip the remaining sleep and
1403				// proceed to the next ping interval immediately.
1404				tokio::select! {
1405					() = tokio::time::sleep(monitor.config.pong_timeout) => {
1406						// Timeout elapsed without pong notification
1407						if monitor.check_heartbeat().await {
1408							break;
1409						}
1410					}
1411					() = monitor.pong_notify.notified() => {
1412						// Pong received early; no need to wait further
1413					}
1414				}
1415			}
1416		})
1417	}
1418}
1419
1420#[cfg(test)]
1421mod tests {
1422	use super::*;
1423	use rstest::rstest;
1424
1425	#[rstest]
1426	fn test_message_text() {
1427		// Arrange
1428		let text = "Hello".to_string();
1429
1430		// Act
1431		let msg = Message::text(text);
1432
1433		// Assert
1434		match msg {
1435			Message::Text { data } => assert_eq!(data, "Hello"),
1436			_ => panic!("Expected text message"),
1437		}
1438	}
1439
1440	#[rstest]
1441	fn test_message_json() {
1442		// Arrange
1443		#[derive(serde::Serialize)]
1444		struct TestData {
1445			value: i32,
1446		}
1447		let data = TestData { value: 42 };
1448
1449		// Act
1450		let msg = Message::json(&data).unwrap();
1451
1452		// Assert
1453		match msg {
1454			Message::Text { data } => assert!(data.contains("42")),
1455			_ => panic!("Expected text message"),
1456		}
1457	}
1458
1459	#[rstest]
1460	#[tokio::test]
1461	async fn test_connection_send() {
1462		// Arrange
1463		let (tx, mut rx) = mpsc::unbounded_channel();
1464		let conn = WebSocketConnection::new("test".to_string(), tx);
1465
1466		// Act
1467		conn.send_text("Hello".to_string()).await.unwrap();
1468
1469		// Assert
1470		let received = rx.recv().await.unwrap();
1471		match received {
1472			Message::Text { data } => assert_eq!(data, "Hello"),
1473			_ => panic!("Expected text message"),
1474		}
1475	}
1476
1477	#[rstest]
1478	fn test_connection_config_default() {
1479		// Arrange & Act
1480		let config = ConnectionConfig::new();
1481
1482		// Assert
1483		assert_eq!(config.idle_timeout(), Duration::from_secs(300));
1484		assert_eq!(config.handshake_timeout(), Duration::from_secs(10));
1485		assert_eq!(config.cleanup_interval(), Duration::from_secs(30));
1486	}
1487
1488	#[rstest]
1489	fn test_connection_config_strict() {
1490		// Arrange & Act
1491		let config = ConnectionConfig::strict();
1492
1493		// Assert
1494		assert_eq!(config.idle_timeout(), Duration::from_secs(30));
1495		assert_eq!(config.handshake_timeout(), Duration::from_secs(5));
1496		assert_eq!(config.cleanup_interval(), Duration::from_secs(10));
1497	}
1498
1499	#[rstest]
1500	fn test_connection_config_permissive() {
1501		// Arrange & Act
1502		let config = ConnectionConfig::permissive();
1503
1504		// Assert
1505		assert_eq!(config.idle_timeout(), Duration::from_secs(3600));
1506		assert_eq!(config.handshake_timeout(), Duration::from_secs(30));
1507		assert_eq!(config.cleanup_interval(), Duration::from_secs(60));
1508	}
1509
1510	#[rstest]
1511	fn test_connection_config_no_timeout() {
1512		// Arrange & Act
1513		let config = ConnectionConfig::no_timeout();
1514
1515		// Assert
1516		assert_eq!(config.idle_timeout(), Duration::MAX);
1517		assert_eq!(config.handshake_timeout(), Duration::MAX);
1518	}
1519
1520	#[rstest]
1521	fn test_connection_config_builder() {
1522		// Arrange & Act
1523		let config = ConnectionConfig::new()
1524			.with_idle_timeout(Duration::from_secs(120))
1525			.with_handshake_timeout(Duration::from_secs(15))
1526			.with_cleanup_interval(Duration::from_secs(20));
1527
1528		// Assert
1529		assert_eq!(config.idle_timeout(), Duration::from_secs(120));
1530		assert_eq!(config.handshake_timeout(), Duration::from_secs(15));
1531		assert_eq!(config.cleanup_interval(), Duration::from_secs(20));
1532	}
1533
1534	#[rstest]
1535	#[tokio::test]
1536	async fn test_connection_with_config() {
1537		// Arrange
1538		let config = ConnectionConfig::new().with_idle_timeout(Duration::from_secs(60));
1539		let (tx, _rx) = mpsc::unbounded_channel();
1540
1541		// Act
1542		let conn = WebSocketConnection::with_config("test".to_string(), tx, config);
1543
1544		// Assert
1545		assert_eq!(conn.config().idle_timeout(), Duration::from_secs(60));
1546		assert!(!conn.is_idle().await);
1547	}
1548
1549	#[rstest]
1550	#[tokio::test]
1551	async fn test_connection_record_activity_resets_idle() {
1552		// Arrange
1553		let config = ConnectionConfig::new().with_idle_timeout(Duration::from_millis(50));
1554		let (tx, _rx) = mpsc::unbounded_channel();
1555		let conn = WebSocketConnection::with_config("test".to_string(), tx, config);
1556
1557		// Act - wait for connection to become idle
1558		tokio::time::sleep(Duration::from_millis(60)).await;
1559		assert!(conn.is_idle().await);
1560
1561		// Act - record activity to reset idle timer
1562		conn.record_activity().await;
1563
1564		// Assert - connection should no longer be idle
1565		assert!(!conn.is_idle().await);
1566	}
1567
1568	#[rstest]
1569	#[tokio::test]
1570	async fn test_connection_becomes_idle_after_timeout() {
1571		// Arrange
1572		let config = ConnectionConfig::new().with_idle_timeout(Duration::from_millis(50));
1573		let (tx, _rx) = mpsc::unbounded_channel();
1574		let conn = WebSocketConnection::with_config("test".to_string(), tx, config);
1575
1576		// Act - wait for connection to exceed idle timeout
1577		tokio::time::sleep(Duration::from_millis(60)).await;
1578
1579		// Assert
1580		assert!(conn.is_idle().await);
1581		assert!(conn.idle_duration().await >= Duration::from_millis(50));
1582	}
1583
1584	#[rstest]
1585	#[tokio::test]
1586	async fn test_send_resets_activity() {
1587		// Arrange
1588		let config = ConnectionConfig::new().with_idle_timeout(Duration::from_millis(100));
1589		let (tx, mut _rx) = mpsc::unbounded_channel();
1590		let conn = WebSocketConnection::with_config("test".to_string(), tx, config);
1591
1592		// Act - wait a bit then send
1593		tokio::time::sleep(Duration::from_millis(50)).await;
1594		conn.send_text("ping".to_string()).await.unwrap();
1595
1596		// Assert - activity should be recent
1597		assert!(conn.idle_duration().await < Duration::from_millis(30));
1598		assert!(!conn.is_idle().await);
1599	}
1600
1601	#[rstest]
1602	#[tokio::test]
1603	async fn test_close_with_reason() {
1604		// Arrange
1605		let (tx, mut rx) = mpsc::unbounded_channel();
1606		let conn = WebSocketConnection::new("test".to_string(), tx);
1607
1608		// Act
1609		conn.close_with_reason(1001, "Idle timeout".to_string())
1610			.await
1611			.unwrap();
1612
1613		// Assert
1614		assert!(conn.is_closed().await);
1615		let msg = rx.recv().await.unwrap();
1616		match msg {
1617			Message::Close { code, reason } => {
1618				assert_eq!(code, 1001);
1619				assert_eq!(reason, "Idle timeout");
1620			}
1621			_ => panic!("Expected close message"),
1622		}
1623	}
1624
1625	#[rstest]
1626	#[tokio::test]
1627	async fn test_timeout_monitor_register_and_count() {
1628		// Arrange
1629		let config = ConnectionConfig::new();
1630		let monitor = ConnectionTimeoutMonitor::new(config);
1631		let (tx, _rx) = mpsc::unbounded_channel();
1632		let conn = Arc::new(WebSocketConnection::new("conn_1".to_string(), tx));
1633
1634		// Act
1635		monitor.register(conn).await.unwrap();
1636
1637		// Assert
1638		assert_eq!(monitor.connection_count().await, 1);
1639	}
1640
1641	#[rstest]
1642	#[tokio::test]
1643	async fn test_timeout_monitor_unregister() {
1644		// Arrange
1645		let config = ConnectionConfig::new();
1646		let monitor = ConnectionTimeoutMonitor::new(config);
1647		let (tx, _rx) = mpsc::unbounded_channel();
1648		let conn = Arc::new(WebSocketConnection::new("conn_1".to_string(), tx));
1649		monitor.register(conn).await.unwrap();
1650
1651		// Act
1652		monitor.unregister("conn_1").await;
1653
1654		// Assert
1655		assert_eq!(monitor.connection_count().await, 0);
1656	}
1657
1658	#[rstest]
1659	#[tokio::test]
1660	async fn test_timeout_monitor_closes_idle_connections() {
1661		// Arrange
1662		let config = ConnectionConfig::new().with_idle_timeout(Duration::from_millis(50));
1663		let monitor = ConnectionTimeoutMonitor::new(config);
1664
1665		let (tx1, mut rx1) = mpsc::unbounded_channel();
1666		let conn1 = Arc::new(WebSocketConnection::with_config(
1667			"idle_conn".to_string(),
1668			tx1,
1669			ConnectionConfig::new().with_idle_timeout(Duration::from_millis(50)),
1670		));
1671
1672		let (tx2, _rx2) = mpsc::unbounded_channel();
1673		let conn2 = Arc::new(WebSocketConnection::with_config(
1674			"active_conn".to_string(),
1675			tx2,
1676			ConnectionConfig::new().with_idle_timeout(Duration::from_secs(300)),
1677		));
1678
1679		monitor.register(conn1).await.unwrap();
1680		monitor.register(conn2.clone()).await.unwrap();
1681
1682		// Act - wait for idle timeout to expire
1683		tokio::time::sleep(Duration::from_millis(60)).await;
1684		// Keep active connection alive
1685		conn2.record_activity().await;
1686
1687		let timed_out = monitor.check_idle_connections().await;
1688
1689		// Assert
1690		assert_eq!(timed_out.len(), 1);
1691		assert_eq!(timed_out[0], "idle_conn");
1692		assert_eq!(monitor.connection_count().await, 1);
1693
1694		// Verify the idle connection received a close message
1695		let msg = rx1.recv().await.unwrap();
1696		match msg {
1697			Message::Close { code, reason } => {
1698				assert_eq!(code, 1001);
1699				assert!(reason.contains("Idle timeout"));
1700			}
1701			_ => panic!("Expected close message for idle connection"),
1702		}
1703	}
1704
1705	#[rstest]
1706	#[tokio::test]
1707	async fn test_timeout_monitor_removes_already_closed_connections() {
1708		// Arrange
1709		let config = ConnectionConfig::new();
1710		let monitor = ConnectionTimeoutMonitor::new(config);
1711		let (tx, _rx) = mpsc::unbounded_channel();
1712		let conn = Arc::new(WebSocketConnection::new("conn_1".to_string(), tx));
1713		conn.close().await.unwrap();
1714		monitor.register(conn).await.unwrap();
1715
1716		// Act
1717		let timed_out = monitor.check_idle_connections().await;
1718
1719		// Assert
1720		assert_eq!(timed_out.len(), 1);
1721		assert_eq!(timed_out[0], "conn_1");
1722		assert_eq!(monitor.connection_count().await, 0);
1723	}
1724
1725	#[rstest]
1726	#[tokio::test]
1727	async fn test_timeout_monitor_background_task() {
1728		// Arrange
1729		let config = ConnectionConfig::new()
1730			.with_idle_timeout(Duration::from_millis(30))
1731			.with_cleanup_interval(Duration::from_millis(20));
1732		let monitor = Arc::new(ConnectionTimeoutMonitor::new(config));
1733
1734		let (tx, mut rx) = mpsc::unbounded_channel();
1735		let conn = Arc::new(WebSocketConnection::with_config(
1736			"bg_conn".to_string(),
1737			tx,
1738			ConnectionConfig::new().with_idle_timeout(Duration::from_millis(30)),
1739		));
1740		monitor.register(conn).await.unwrap();
1741
1742		// Act - start background monitor
1743		let handle = monitor.start();
1744
1745		// Wait for the monitor to detect and close the idle connection
1746		tokio::time::sleep(Duration::from_millis(120)).await;
1747
1748		// Assert
1749		assert_eq!(monitor.connection_count().await, 0);
1750
1751		// Verify close message was sent
1752		let msg = rx.recv().await.unwrap();
1753		assert!(matches!(msg, Message::Close { .. }));
1754
1755		// Cleanup
1756		handle.abort();
1757	}
1758
1759	#[rstest]
1760	fn test_ping_pong_config_default() {
1761		// Arrange & Act
1762		let config = PingPongConfig::default();
1763
1764		// Assert
1765		assert_eq!(config.ping_interval(), Duration::from_secs(30));
1766		assert_eq!(config.pong_timeout(), Duration::from_secs(10));
1767	}
1768
1769	#[rstest]
1770	fn test_ping_pong_config_custom() {
1771		// Arrange & Act
1772		let config = PingPongConfig::new(Duration::from_secs(15), Duration::from_secs(5));
1773
1774		// Assert
1775		assert_eq!(config.ping_interval(), Duration::from_secs(15));
1776		assert_eq!(config.pong_timeout(), Duration::from_secs(5));
1777	}
1778
1779	#[rstest]
1780	fn test_ping_pong_config_builder() {
1781		// Arrange & Act
1782		let config = PingPongConfig::default()
1783			.with_ping_interval(Duration::from_secs(60))
1784			.with_pong_timeout(Duration::from_secs(20));
1785
1786		// Assert
1787		assert_eq!(config.ping_interval(), Duration::from_secs(60));
1788		assert_eq!(config.pong_timeout(), Duration::from_secs(20));
1789	}
1790
1791	#[rstest]
1792	fn test_connection_config_has_default_ping_config() {
1793		// Arrange & Act
1794		let config = ConnectionConfig::new();
1795
1796		// Assert
1797		assert_eq!(
1798			config.ping_config().ping_interval(),
1799			Duration::from_secs(30)
1800		);
1801		assert_eq!(config.ping_config().pong_timeout(), Duration::from_secs(10));
1802	}
1803
1804	#[rstest]
1805	fn test_connection_config_with_custom_ping_config() {
1806		// Arrange
1807		let ping_config = PingPongConfig::new(Duration::from_secs(15), Duration::from_secs(5));
1808
1809		// Act
1810		let config = ConnectionConfig::new().with_ping_config(ping_config);
1811
1812		// Assert
1813		assert_eq!(
1814			config.ping_config().ping_interval(),
1815			Duration::from_secs(15)
1816		);
1817		assert_eq!(config.ping_config().pong_timeout(), Duration::from_secs(5));
1818	}
1819
1820	#[rstest]
1821	fn test_strict_config_has_aggressive_ping() {
1822		// Arrange & Act
1823		let config = ConnectionConfig::strict();
1824
1825		// Assert
1826		assert_eq!(
1827			config.ping_config().ping_interval(),
1828			Duration::from_secs(10)
1829		);
1830		assert_eq!(config.ping_config().pong_timeout(), Duration::from_secs(5));
1831	}
1832
1833	#[rstest]
1834	fn test_permissive_config_has_relaxed_ping() {
1835		// Arrange & Act
1836		let config = ConnectionConfig::permissive();
1837
1838		// Assert
1839		assert_eq!(
1840			config.ping_config().ping_interval(),
1841			Duration::from_secs(60)
1842		);
1843		assert_eq!(config.ping_config().pong_timeout(), Duration::from_secs(30));
1844	}
1845
1846	#[rstest]
1847	#[tokio::test]
1848	async fn test_timeout_monitor_rejects_when_max_connections_reached() {
1849		// Arrange
1850		let config = ConnectionConfig::new().with_max_connections(Some(1));
1851		let monitor = ConnectionTimeoutMonitor::new(config);
1852
1853		let (tx1, _rx1) = mpsc::unbounded_channel();
1854		let conn1 = Arc::new(WebSocketConnection::new("conn_1".to_string(), tx1));
1855
1856		let (tx2, _rx2) = mpsc::unbounded_channel();
1857		let conn2 = Arc::new(WebSocketConnection::new("conn_2".to_string(), tx2));
1858
1859		// Act
1860		monitor.register(conn1).await.unwrap();
1861		let result = monitor.register(conn2).await;
1862
1863		// Assert
1864		assert!(result.is_err());
1865		assert_eq!(monitor.connection_count().await, 1);
1866	}
1867
1868	#[rstest]
1869	#[tokio::test]
1870	async fn test_force_close_marks_connection_closed() {
1871		// Arrange
1872		let (tx, _rx) = mpsc::unbounded_channel();
1873		let conn = WebSocketConnection::new("test".to_string(), tx);
1874
1875		// Act
1876		conn.force_close().await;
1877
1878		// Assert
1879		assert!(conn.is_closed().await);
1880	}
1881
1882	#[rstest]
1883	#[tokio::test]
1884	async fn test_close_marks_closed_even_when_channel_dropped() {
1885		// Arrange
1886		let (tx, rx) = mpsc::unbounded_channel();
1887		let conn = WebSocketConnection::new("test".to_string(), tx);
1888
1889		// Drop receiver to simulate broken channel
1890		drop(rx);
1891
1892		// Act - close should still mark the connection as closed
1893		let result = conn.close().await;
1894
1895		// Assert
1896		assert!(result.is_err()); // send fails because receiver is dropped
1897		assert!(conn.is_closed().await); // but connection is still marked closed
1898	}
1899
1900	#[rstest]
1901	#[tokio::test]
1902	async fn test_close_with_reason_marks_closed_even_when_channel_dropped() {
1903		// Arrange
1904		let (tx, rx) = mpsc::unbounded_channel();
1905		let conn = WebSocketConnection::new("test".to_string(), tx);
1906
1907		// Drop receiver to simulate broken channel
1908		drop(rx);
1909
1910		// Act
1911		let result = conn
1912			.close_with_reason(1006, "Abnormal close".to_string())
1913			.await;
1914
1915		// Assert
1916		assert!(result.is_err());
1917		assert!(conn.is_closed().await);
1918	}
1919
1920	#[rstest]
1921	#[tokio::test]
1922	async fn test_send_after_force_close_returns_error() {
1923		// Arrange
1924		let (tx, _rx) = mpsc::unbounded_channel();
1925		let conn = WebSocketConnection::new("test".to_string(), tx);
1926		conn.force_close().await;
1927
1928		// Act
1929		let result = conn.send_text("should fail".to_string()).await;
1930
1931		// Assert
1932		assert!(result.is_err());
1933		assert!(matches!(result.unwrap_err(), WebSocketError::Send(_)));
1934	}
1935
1936	#[rstest]
1937	fn test_heartbeat_config_default() {
1938		// Arrange & Act
1939		let config = HeartbeatConfig::default();
1940
1941		// Assert
1942		assert_eq!(config.ping_interval(), Duration::from_secs(30));
1943		assert_eq!(config.pong_timeout(), Duration::from_secs(10));
1944	}
1945
1946	#[rstest]
1947	fn test_heartbeat_config_custom() {
1948		// Arrange & Act
1949		let config = HeartbeatConfig::new(Duration::from_secs(15), Duration::from_secs(5));
1950
1951		// Assert
1952		assert_eq!(config.ping_interval(), Duration::from_secs(15));
1953		assert_eq!(config.pong_timeout(), Duration::from_secs(5));
1954	}
1955
1956	#[rstest]
1957	#[tokio::test]
1958	async fn test_heartbeat_monitor_initial_state() {
1959		// Arrange
1960		let (tx, _rx) = mpsc::unbounded_channel();
1961		let conn = Arc::new(WebSocketConnection::new("hb_test".to_string(), tx));
1962		let config = HeartbeatConfig::default();
1963
1964		// Act
1965		let monitor = HeartbeatMonitor::new(conn, config);
1966
1967		// Assert
1968		assert!(!monitor.is_timed_out().await);
1969		assert!(monitor.time_since_last_pong().await < Duration::from_secs(1));
1970	}
1971
1972	#[rstest]
1973	#[tokio::test]
1974	async fn test_heartbeat_monitor_record_pong_resets_timer() {
1975		// Arrange
1976		let (tx, _rx) = mpsc::unbounded_channel();
1977		let conn = Arc::new(WebSocketConnection::new("hb_pong".to_string(), tx));
1978		let config = HeartbeatConfig::new(Duration::from_millis(50), Duration::from_millis(30));
1979		let monitor = HeartbeatMonitor::new(conn, config);
1980
1981		// Act - wait then record pong
1982		tokio::time::sleep(Duration::from_millis(20)).await;
1983		monitor.record_pong().await;
1984
1985		// Assert
1986		assert!(monitor.time_since_last_pong().await < Duration::from_millis(10));
1987	}
1988
1989	#[rstest]
1990	#[tokio::test]
1991	async fn test_heartbeat_monitor_timeout_closes_connection() {
1992		// Arrange
1993		let (tx, _rx) = mpsc::unbounded_channel();
1994		let conn = Arc::new(WebSocketConnection::new("hb_timeout".to_string(), tx));
1995		let config = HeartbeatConfig::new(Duration::from_millis(50), Duration::from_millis(30));
1996		let monitor = HeartbeatMonitor::new(conn.clone(), config);
1997
1998		// Act - wait past the pong timeout
1999		tokio::time::sleep(Duration::from_millis(40)).await;
2000		let timed_out = monitor.check_heartbeat().await;
2001
2002		// Assert
2003		assert!(timed_out);
2004		assert!(monitor.is_timed_out().await);
2005		assert!(conn.is_closed().await);
2006	}
2007
2008	#[rstest]
2009	#[tokio::test]
2010	async fn test_heartbeat_monitor_no_timeout_when_pong_received() {
2011		// Arrange
2012		let (tx, _rx) = mpsc::unbounded_channel();
2013		let conn = Arc::new(WebSocketConnection::new("hb_ok".to_string(), tx));
2014		let config = HeartbeatConfig::new(Duration::from_millis(100), Duration::from_millis(50));
2015		let monitor = HeartbeatMonitor::new(conn.clone(), config);
2016
2017		// Act - record pong within timeout window
2018		tokio::time::sleep(Duration::from_millis(20)).await;
2019		monitor.record_pong().await;
2020		let timed_out = monitor.check_heartbeat().await;
2021
2022		// Assert
2023		assert!(!timed_out);
2024		assert!(!monitor.is_timed_out().await);
2025		assert!(!conn.is_closed().await);
2026	}
2027
2028	#[rstest]
2029	#[tokio::test]
2030	async fn test_heartbeat_monitor_send_ping() {
2031		// Arrange
2032		let (tx, mut rx) = mpsc::unbounded_channel();
2033		let conn = Arc::new(WebSocketConnection::new("hb_ping".to_string(), tx));
2034		let config = HeartbeatConfig::default();
2035		let monitor = HeartbeatMonitor::new(conn, config);
2036
2037		// Act
2038		monitor.send_ping().await.unwrap();
2039
2040		// Assert
2041		let msg = rx.recv().await.unwrap();
2042		assert!(matches!(msg, Message::Ping));
2043	}
2044
2045	#[rstest]
2046	#[tokio::test]
2047	async fn test_heartbeat_monitor_early_pong_skips_full_sleep() {
2048		// Arrange
2049		let (tx, _rx) = mpsc::unbounded_channel();
2050		let conn = Arc::new(WebSocketConnection::new("hb_early".to_string(), tx));
2051		// Use a long pong_timeout to make the test obvious
2052		let config = HeartbeatConfig {
2053			ping_interval: Duration::from_secs(60),
2054			pong_timeout: Duration::from_secs(10),
2055		};
2056		let monitor = Arc::new(HeartbeatMonitor::new(conn, config));
2057
2058		// Act: simulate pong arriving after a short delay
2059		let monitor_clone = Arc::clone(&monitor);
2060		tokio::spawn(async move {
2061			tokio::time::sleep(Duration::from_millis(50)).await;
2062			monitor_clone.record_pong().await;
2063		});
2064
2065		// Send ping and wait for pong or timeout via select!
2066		let _ = monitor.send_ping().await;
2067		let start = Instant::now();
2068
2069		tokio::select! {
2070			() = tokio::time::sleep(monitor.config.pong_timeout) => {
2071				panic!("Should not reach full timeout");
2072			}
2073			() = monitor.pong_notify.notified() => {
2074				// Pong received early
2075			}
2076		}
2077
2078		// Assert: should complete well before the 10s pong_timeout
2079		let elapsed = start.elapsed();
2080		assert!(
2081			elapsed < Duration::from_secs(2),
2082			"Expected early wakeup but elapsed {:?}",
2083			elapsed
2084		);
2085	}
2086
2087	#[rstest]
2088	fn test_websocket_error_binary_payload_variant() {
2089		// Arrange & Act
2090		let err = WebSocketError::BinaryPayload("invalid data".to_string());
2091
2092		// Assert
2093		assert_eq!(err.to_string(), "Invalid binary payload: invalid data");
2094	}
2095
2096	#[rstest]
2097	fn test_websocket_error_heartbeat_timeout_variant() {
2098		// Arrange & Act
2099		let err = WebSocketError::HeartbeatTimeout(Duration::from_secs(10));
2100
2101		// Assert
2102		assert_eq!(
2103			err.to_string(),
2104			"Heartbeat timeout: no pong received within 10s"
2105		);
2106	}
2107
2108	#[rstest]
2109	fn test_websocket_error_slow_consumer_variant() {
2110		// Arrange & Act
2111		let err = WebSocketError::SlowConsumer(Duration::from_secs(5));
2112
2113		// Assert
2114		assert_eq!(err.to_string(), "Slow consumer: send timed out after 5s");
2115	}
2116}