use crate::config::ConfigError;
use crate::security::compression_bomb::CompressionBombConfig;
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SecurityConfig {
pub json: JsonLimits,
pub buffers: BufferLimits,
pub network: NetworkLimits,
pub sessions: SessionLimits,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonLimits {
pub max_input_size: usize,
pub max_depth: usize,
pub max_object_keys: usize,
pub max_array_length: usize,
pub max_string_length: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BufferLimits {
pub max_buffer_size: usize,
pub max_pool_size: usize,
pub max_total_memory: usize,
pub buffer_ttl_secs: u64,
pub max_buffers_per_bucket: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkLimits {
pub max_websocket_frame_size: usize,
pub max_concurrent_connections: usize,
pub connection_timeout_secs: u64,
pub max_requests_per_second: u32,
pub max_http_payload_size: usize,
pub rate_limiting: RateLimitingConfig,
pub compression_bomb: CompressionBombConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RateLimitingConfig {
pub max_requests_per_window: u32,
pub window_duration_secs: u64,
pub max_connections_per_ip: usize,
pub max_messages_per_second: u32,
pub burst_allowance: u32,
pub enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionLimits {
pub max_session_id_length: usize,
pub min_session_id_length: usize,
pub max_streams_per_session: usize,
pub session_timeout_secs: u64,
pub max_session_data_size: usize,
}
impl Default for JsonLimits {
fn default() -> Self {
Self {
max_input_size: 100 * 1024 * 1024, max_depth: 64,
max_object_keys: 10_000,
max_array_length: 1_000_000,
max_string_length: 10 * 1024 * 1024, }
}
}
impl Default for BufferLimits {
fn default() -> Self {
Self {
max_buffer_size: 256 * 1024 * 1024, max_pool_size: 1000,
max_total_memory: 512 * 1024 * 1024, buffer_ttl_secs: 300, max_buffers_per_bucket: 50,
}
}
}
impl Default for NetworkLimits {
fn default() -> Self {
Self {
max_websocket_frame_size: 16 * 1024 * 1024, max_concurrent_connections: 10_000,
connection_timeout_secs: 30,
max_requests_per_second: 100,
max_http_payload_size: 50 * 1024 * 1024, rate_limiting: RateLimitingConfig::default(),
compression_bomb: CompressionBombConfig::default(),
}
}
}
impl Default for RateLimitingConfig {
fn default() -> Self {
Self {
max_requests_per_window: 100,
window_duration_secs: 60,
max_connections_per_ip: 10,
max_messages_per_second: 30,
burst_allowance: 5,
enabled: true,
}
}
}
impl Default for SessionLimits {
fn default() -> Self {
Self {
max_session_id_length: 128,
min_session_id_length: 8,
max_streams_per_session: 100,
session_timeout_secs: 3600, max_session_data_size: 100 * 1024 * 1024, }
}
}
impl SecurityConfig {
pub fn validate(&self) -> Result<(), ConfigError> {
let s = "security.sessions";
if self.sessions.min_session_id_length > self.sessions.max_session_id_length {
return Err(ConfigError::InconsistentBounds {
section: s,
message: "min_session_id_length must be <= max_session_id_length",
});
}
macro_rules! must_be_positive {
($section:expr, $field:expr, $value:expr) => {
if $value == 0 {
return Err(ConfigError::MustBePositive {
section: $section,
field: $field,
});
}
};
}
must_be_positive!("security.json", "max_input_size", self.json.max_input_size);
must_be_positive!("security.json", "max_depth", self.json.max_depth);
must_be_positive!(
"security.json",
"max_string_length",
self.json.max_string_length
);
must_be_positive!(
"security.buffers",
"max_buffer_size",
self.buffers.max_buffer_size
);
must_be_positive!(
"security.buffers",
"max_total_memory",
self.buffers.max_total_memory
);
must_be_positive!(
"security.network",
"max_websocket_frame_size",
self.network.max_websocket_frame_size
);
must_be_positive!(
"security.network",
"max_http_payload_size",
self.network.max_http_payload_size
);
must_be_positive!(
"security.sessions",
"max_session_id_length",
self.sessions.max_session_id_length
);
must_be_positive!(
"security.sessions",
"min_session_id_length",
self.sessions.min_session_id_length
);
must_be_positive!(
"security.sessions",
"max_session_data_size",
self.sessions.max_session_data_size
);
Ok(())
}
pub fn high_throughput() -> Self {
Self {
json: JsonLimits {
max_input_size: 500 * 1024 * 1024, max_depth: 128,
max_object_keys: 50_000,
max_array_length: 5_000_000,
max_string_length: 50 * 1024 * 1024, },
buffers: BufferLimits {
max_buffer_size: 1024 * 1024 * 1024, max_pool_size: 5000,
max_total_memory: 2 * 1024 * 1024 * 1024, buffer_ttl_secs: 600, max_buffers_per_bucket: 200,
},
network: NetworkLimits {
max_websocket_frame_size: 100 * 1024 * 1024, max_concurrent_connections: 50_000,
connection_timeout_secs: 60,
max_requests_per_second: 1000,
max_http_payload_size: 200 * 1024 * 1024, rate_limiting: RateLimitingConfig {
max_requests_per_window: 1000,
window_duration_secs: 60,
max_connections_per_ip: 50,
max_messages_per_second: 100,
burst_allowance: 20,
enabled: true,
},
compression_bomb: CompressionBombConfig::high_throughput(),
},
sessions: SessionLimits {
max_session_id_length: 256,
min_session_id_length: 16,
max_streams_per_session: 1000,
session_timeout_secs: 7200, max_session_data_size: 500 * 1024 * 1024, },
}
}
pub fn low_memory() -> Self {
Self {
json: JsonLimits {
max_input_size: 10 * 1024 * 1024, max_depth: 32,
max_object_keys: 1_000,
max_array_length: 100_000,
max_string_length: 1024 * 1024, },
buffers: BufferLimits {
max_buffer_size: 10 * 1024 * 1024, max_pool_size: 100,
max_total_memory: 50 * 1024 * 1024, buffer_ttl_secs: 60, max_buffers_per_bucket: 10,
},
network: NetworkLimits {
max_websocket_frame_size: 1024 * 1024, max_concurrent_connections: 1_000,
connection_timeout_secs: 15,
max_requests_per_second: 10,
max_http_payload_size: 5 * 1024 * 1024, rate_limiting: RateLimitingConfig {
max_requests_per_window: 20,
window_duration_secs: 60,
max_connections_per_ip: 2,
max_messages_per_second: 5,
burst_allowance: 2,
enabled: true,
},
compression_bomb: CompressionBombConfig::low_memory(),
},
sessions: SessionLimits {
max_session_id_length: 64,
min_session_id_length: 8,
max_streams_per_session: 10,
session_timeout_secs: 900, max_session_data_size: 10 * 1024 * 1024, },
}
}
pub fn development() -> Self {
Self {
json: JsonLimits {
max_input_size: 50 * 1024 * 1024, max_depth: 64,
max_object_keys: 5_000,
max_array_length: 500_000,
max_string_length: 5 * 1024 * 1024, },
buffers: BufferLimits {
max_buffer_size: 100 * 1024 * 1024, max_pool_size: 500,
max_total_memory: 200 * 1024 * 1024, buffer_ttl_secs: 120, max_buffers_per_bucket: 25,
},
network: NetworkLimits {
max_websocket_frame_size: 10 * 1024 * 1024, max_concurrent_connections: 1_000,
connection_timeout_secs: 30,
max_requests_per_second: 50,
max_http_payload_size: 25 * 1024 * 1024, rate_limiting: RateLimitingConfig {
max_requests_per_window: 200,
window_duration_secs: 60,
max_connections_per_ip: 20,
max_messages_per_second: 50,
burst_allowance: 10,
enabled: true,
},
compression_bomb: CompressionBombConfig::default(),
},
sessions: SessionLimits {
max_session_id_length: 128,
min_session_id_length: 8,
max_streams_per_session: 50,
session_timeout_secs: 1800, max_session_data_size: 50 * 1024 * 1024, },
}
}
pub fn buffer_ttl(&self) -> Duration {
Duration::from_secs(self.buffers.buffer_ttl_secs)
}
pub fn connection_timeout(&self) -> Duration {
Duration::from_secs(self.network.connection_timeout_secs)
}
pub fn session_timeout(&self) -> Duration {
Duration::from_secs(self.sessions.session_timeout_secs)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::ConfigError;
#[test]
fn test_default_security_config() {
let config = SecurityConfig::default();
assert!(config.json.max_input_size > 0);
assert!(config.buffers.max_buffer_size > 0);
assert!(config.network.max_concurrent_connections > 0);
assert!(config.sessions.max_session_id_length >= config.sessions.min_session_id_length);
}
#[test]
fn test_security_config_default_validates() {
SecurityConfig::default()
.validate()
.expect("SecurityConfig::default() must be valid");
}
#[test]
fn test_rejects_min_session_id_length_greater_than_max() {
let mut config = SecurityConfig::default();
config.sessions.min_session_id_length = 200;
config.sessions.max_session_id_length = 100;
let err = config.validate().unwrap_err();
assert!(matches!(
err,
ConfigError::InconsistentBounds {
section: "security.sessions",
..
}
));
}
#[test]
fn test_high_throughput_config() {
let config = SecurityConfig::high_throughput();
let default = SecurityConfig::default();
assert!(config.json.max_input_size >= default.json.max_input_size);
assert!(config.buffers.max_total_memory >= default.buffers.max_total_memory);
assert!(
config.network.max_concurrent_connections >= default.network.max_concurrent_connections
);
}
#[test]
fn test_low_memory_config() {
let config = SecurityConfig::low_memory();
let default = SecurityConfig::default();
assert!(config.json.max_input_size <= default.json.max_input_size);
assert!(config.buffers.max_total_memory <= default.buffers.max_total_memory);
assert!(config.buffers.max_buffers_per_bucket <= default.buffers.max_buffers_per_bucket);
}
#[test]
fn test_duration_conversions() {
let config = SecurityConfig::default();
assert!(config.buffer_ttl().as_secs() > 0);
assert!(config.connection_timeout().as_secs() > 0);
assert!(config.session_timeout().as_secs() > 0);
}
}