// file: crates/common/game-realtime-websocket-lib/src/config.rs // version: 1 const DEFAULT_CLOSE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(2); const DEFAULT_CONNECT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); const DEFAULT_MAX_FRAME_SIZE: usize = 1024 * 1024; const DEFAULT_MAX_MESSAGE_SIZE: usize = 1024 * 1024; const DEFAULT_MAX_WRITE_BUFFER_SIZE: usize = 2 * 1024 * 1024; const DEFAULT_SEND_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5); const DEFAULT_WRITE_BUFFER_SIZE: usize = 64 * 1024; /// Product-facing limits and operation deadlines for one WebSocket connection. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct WebSocketConfig { max_message_size: usize, max_frame_size: usize, write_buffer_size: usize, max_write_buffer_size: usize, connect_timeout: std::time::Duration, send_timeout: std::time::Duration, close_timeout: std::time::Duration, } impl Default for WebSocketConfig { fn default() -> Self { return Self { max_message_size: DEFAULT_MAX_MESSAGE_SIZE, max_frame_size: DEFAULT_MAX_FRAME_SIZE, write_buffer_size: DEFAULT_WRITE_BUFFER_SIZE, max_write_buffer_size: DEFAULT_MAX_WRITE_BUFFER_SIZE, connect_timeout: DEFAULT_CONNECT_TIMEOUT, send_timeout: DEFAULT_SEND_TIMEOUT, close_timeout: DEFAULT_CLOSE_TIMEOUT, }; } } impl WebSocketConfig { /// Returns a copy with a different maximum binary message size. #[must_use] pub fn with_max_message_size(mut self, value: usize) -> Self { self.max_message_size = value; return self; } /// Returns a copy with a different maximum WebSocket frame payload size. #[must_use] pub fn with_max_frame_size(mut self, value: usize) -> Self { self.max_frame_size = value; return self; } /// Returns a copy with a different Tungstenite write-buffer target. #[must_use] pub fn with_write_buffer_size(mut self, value: usize) -> Self { self.write_buffer_size = value; return self; } /// Returns a copy with a different hard maximum for the Tungstenite write buffer. #[must_use] pub fn with_max_write_buffer_size(mut self, value: usize) -> Self { self.max_write_buffer_size = value; return self; } /// Returns a copy with a different connection or handshake deadline. #[must_use] pub fn with_connect_timeout(mut self, value: std::time::Duration) -> Self { self.connect_timeout = value; return self; } /// Returns a copy with a different deadline for one send operation. #[must_use] pub fn with_send_timeout(mut self, value: std::time::Duration) -> Self { self.send_timeout = value; return self; } /// Returns a copy with a different deadline for a clean local close operation. #[must_use] pub fn with_close_timeout(mut self, value: std::time::Duration) -> Self { self.close_timeout = value; return self; } /// Returns the configured maximum binary message size. #[must_use] pub fn max_message_size(&self) -> usize { return self.max_message_size; } /// Returns the configured maximum WebSocket frame payload size. #[must_use] pub fn max_frame_size(&self) -> usize { return self.max_frame_size; } /// Returns the configured Tungstenite write-buffer target. #[must_use] pub fn write_buffer_size(&self) -> usize { return self.write_buffer_size; } /// Returns the configured hard maximum for the Tungstenite write buffer. #[must_use] pub fn max_write_buffer_size(&self) -> usize { return self.max_write_buffer_size; } /// Returns the configured connection or handshake deadline. #[must_use] pub fn connect_timeout(&self) -> std::time::Duration { return self.connect_timeout; } /// Returns the configured deadline for one send operation. #[must_use] pub fn send_timeout(&self) -> std::time::Duration { return self.send_timeout; } /// Returns the configured deadline for a clean local close operation. #[must_use] pub fn close_timeout(&self) -> std::time::Duration { return self.close_timeout; } /// Validates all invariants required before creating a WebSocket endpoint or connection. pub fn validate(&self) -> Result<(), game_realtime_transport_lib::TransportError> { if self.max_message_size == 0 { return Err(invalid_configuration("max_message_size must be greater than zero")); } if self.max_frame_size == 0 { return Err(invalid_configuration("max_frame_size must be greater than zero")); } if self.max_frame_size > self.max_message_size { return Err(invalid_configuration("max_frame_size must not exceed max_message_size")); } let minimum_max_write_buffer_size = match self.write_buffer_size.checked_add(self.max_message_size) { Some(value) => value, None => return Err(invalid_configuration("write_buffer_size + max_message_size overflows usize")), }; if self.max_write_buffer_size < minimum_max_write_buffer_size { return Err(invalid_configuration("max_write_buffer_size must fit write_buffer_size plus one maximum-sized message")); } if self.connect_timeout.is_zero() { return Err(invalid_configuration("connect_timeout must be greater than zero")); } if self.send_timeout.is_zero() { return Err(invalid_configuration("send_timeout must be greater than zero")); } if self.close_timeout.is_zero() { return Err(invalid_configuration("close_timeout must be greater than zero")); } return Ok(()); } } fn invalid_configuration(detail: &str) -> game_realtime_transport_lib::TransportError { return game_realtime_transport_lib::TransportError::new(game_realtime_transport_lib::TransportErrorKind::InvalidConfiguration, detail); } #[cfg(test)] #[path = "../unit_tests/config.rs"] mod tests;