1use crate::config::ConfigError;
4use crate::security::compression_bomb::CompressionBombConfig;
5use serde::{Deserialize, Serialize};
6use std::time::Duration;
7
8#[derive(Debug, Clone, Serialize, Deserialize, Default)]
10pub struct SecurityConfig {
11 pub json: JsonLimits,
13
14 pub buffers: BufferLimits,
16
17 pub network: NetworkLimits,
19
20 pub sessions: SessionLimits,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct JsonLimits {
27 pub max_input_size: usize,
29
30 pub max_depth: usize,
32
33 pub max_object_keys: usize,
35
36 pub max_array_length: usize,
38
39 pub max_string_length: usize,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct BufferLimits {
46 pub max_buffer_size: usize,
48
49 pub max_pool_size: usize,
51
52 pub max_total_memory: usize,
54
55 pub buffer_ttl_secs: u64,
57
58 pub max_buffers_per_bucket: usize,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct NetworkLimits {
65 pub max_websocket_frame_size: usize,
67
68 pub max_concurrent_connections: usize,
70
71 pub connection_timeout_secs: u64,
73
74 pub max_requests_per_second: u32,
76
77 pub max_http_payload_size: usize,
79
80 pub rate_limiting: RateLimitingConfig,
82
83 pub compression_bomb: CompressionBombConfig,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct RateLimitingConfig {
90 pub max_requests_per_window: u32,
92
93 pub window_duration_secs: u64,
95
96 pub max_connections_per_ip: usize,
98
99 pub max_messages_per_second: u32,
101
102 pub burst_allowance: u32,
104
105 pub enabled: bool,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct SessionLimits {
112 pub max_session_id_length: usize,
114
115 pub min_session_id_length: usize,
117
118 pub max_streams_per_session: usize,
120
121 pub session_timeout_secs: u64,
123
124 pub max_session_data_size: usize,
126}
127
128impl Default for JsonLimits {
129 fn default() -> Self {
130 Self {
131 max_input_size: 100 * 1024 * 1024, max_depth: 64,
133 max_object_keys: 10_000,
134 max_array_length: 1_000_000,
135 max_string_length: 10 * 1024 * 1024, }
137 }
138}
139
140impl Default for BufferLimits {
141 fn default() -> Self {
142 Self {
143 max_buffer_size: 256 * 1024 * 1024, max_pool_size: 1000,
145 max_total_memory: 512 * 1024 * 1024, buffer_ttl_secs: 300, max_buffers_per_bucket: 50,
148 }
149 }
150}
151
152impl Default for NetworkLimits {
153 fn default() -> Self {
154 Self {
155 max_websocket_frame_size: 16 * 1024 * 1024, max_concurrent_connections: 10_000,
157 connection_timeout_secs: 30,
158 max_requests_per_second: 100,
159 max_http_payload_size: 50 * 1024 * 1024, rate_limiting: RateLimitingConfig::default(),
161 compression_bomb: CompressionBombConfig::default(),
162 }
163 }
164}
165
166impl Default for RateLimitingConfig {
167 fn default() -> Self {
168 Self {
169 max_requests_per_window: 100,
170 window_duration_secs: 60,
171 max_connections_per_ip: 10,
172 max_messages_per_second: 30,
173 burst_allowance: 5,
174 enabled: true,
175 }
176 }
177}
178
179impl Default for SessionLimits {
180 fn default() -> Self {
181 Self {
182 max_session_id_length: 128,
183 min_session_id_length: 8,
184 max_streams_per_session: 100,
185 session_timeout_secs: 3600, max_session_data_size: 100 * 1024 * 1024, }
188 }
189}
190
191impl SecurityConfig {
192 pub fn validate(&self) -> Result<(), ConfigError> {
211 let s = "security.sessions";
212
213 if self.sessions.min_session_id_length > self.sessions.max_session_id_length {
214 return Err(ConfigError::InconsistentBounds {
215 section: s,
216 message: "min_session_id_length must be <= max_session_id_length",
217 });
218 }
219
220 macro_rules! must_be_positive {
221 ($section:expr, $field:expr, $value:expr) => {
222 if $value == 0 {
223 return Err(ConfigError::MustBePositive {
224 section: $section,
225 field: $field,
226 });
227 }
228 };
229 }
230
231 must_be_positive!("security.json", "max_input_size", self.json.max_input_size);
232 must_be_positive!("security.json", "max_depth", self.json.max_depth);
233 must_be_positive!(
234 "security.json",
235 "max_string_length",
236 self.json.max_string_length
237 );
238 must_be_positive!(
239 "security.buffers",
240 "max_buffer_size",
241 self.buffers.max_buffer_size
242 );
243 must_be_positive!(
244 "security.buffers",
245 "max_total_memory",
246 self.buffers.max_total_memory
247 );
248 must_be_positive!(
249 "security.network",
250 "max_websocket_frame_size",
251 self.network.max_websocket_frame_size
252 );
253 must_be_positive!(
254 "security.network",
255 "max_http_payload_size",
256 self.network.max_http_payload_size
257 );
258 must_be_positive!(
259 "security.sessions",
260 "max_session_id_length",
261 self.sessions.max_session_id_length
262 );
263 must_be_positive!(
264 "security.sessions",
265 "min_session_id_length",
266 self.sessions.min_session_id_length
267 );
268 must_be_positive!(
269 "security.sessions",
270 "max_session_data_size",
271 self.sessions.max_session_data_size
272 );
273
274 Ok(())
275 }
276
277 pub fn high_throughput() -> Self {
279 Self {
280 json: JsonLimits {
281 max_input_size: 500 * 1024 * 1024, max_depth: 128,
283 max_object_keys: 50_000,
284 max_array_length: 5_000_000,
285 max_string_length: 50 * 1024 * 1024, },
287 buffers: BufferLimits {
288 max_buffer_size: 1024 * 1024 * 1024, max_pool_size: 5000,
290 max_total_memory: 2 * 1024 * 1024 * 1024, buffer_ttl_secs: 600, max_buffers_per_bucket: 200,
293 },
294 network: NetworkLimits {
295 max_websocket_frame_size: 100 * 1024 * 1024, max_concurrent_connections: 50_000,
297 connection_timeout_secs: 60,
298 max_requests_per_second: 1000,
299 max_http_payload_size: 200 * 1024 * 1024, rate_limiting: RateLimitingConfig {
301 max_requests_per_window: 1000,
302 window_duration_secs: 60,
303 max_connections_per_ip: 50,
304 max_messages_per_second: 100,
305 burst_allowance: 20,
306 enabled: true,
307 },
308 compression_bomb: CompressionBombConfig::high_throughput(),
309 },
310 sessions: SessionLimits {
311 max_session_id_length: 256,
312 min_session_id_length: 16,
313 max_streams_per_session: 1000,
314 session_timeout_secs: 7200, max_session_data_size: 500 * 1024 * 1024, },
317 }
318 }
319
320 pub fn low_memory() -> Self {
322 Self {
323 json: JsonLimits {
324 max_input_size: 10 * 1024 * 1024, max_depth: 32,
326 max_object_keys: 1_000,
327 max_array_length: 100_000,
328 max_string_length: 1024 * 1024, },
330 buffers: BufferLimits {
331 max_buffer_size: 10 * 1024 * 1024, max_pool_size: 100,
333 max_total_memory: 50 * 1024 * 1024, buffer_ttl_secs: 60, max_buffers_per_bucket: 10,
336 },
337 network: NetworkLimits {
338 max_websocket_frame_size: 1024 * 1024, max_concurrent_connections: 1_000,
340 connection_timeout_secs: 15,
341 max_requests_per_second: 10,
342 max_http_payload_size: 5 * 1024 * 1024, rate_limiting: RateLimitingConfig {
344 max_requests_per_window: 20,
345 window_duration_secs: 60,
346 max_connections_per_ip: 2,
347 max_messages_per_second: 5,
348 burst_allowance: 2,
349 enabled: true,
350 },
351 compression_bomb: CompressionBombConfig::low_memory(),
352 },
353 sessions: SessionLimits {
354 max_session_id_length: 64,
355 min_session_id_length: 8,
356 max_streams_per_session: 10,
357 session_timeout_secs: 900, max_session_data_size: 10 * 1024 * 1024, },
360 }
361 }
362
363 pub fn development() -> Self {
365 Self {
366 json: JsonLimits {
367 max_input_size: 50 * 1024 * 1024, max_depth: 64,
369 max_object_keys: 5_000,
370 max_array_length: 500_000,
371 max_string_length: 5 * 1024 * 1024, },
373 buffers: BufferLimits {
374 max_buffer_size: 100 * 1024 * 1024, max_pool_size: 500,
376 max_total_memory: 200 * 1024 * 1024, buffer_ttl_secs: 120, max_buffers_per_bucket: 25,
379 },
380 network: NetworkLimits {
381 max_websocket_frame_size: 10 * 1024 * 1024, max_concurrent_connections: 1_000,
383 connection_timeout_secs: 30,
384 max_requests_per_second: 50,
385 max_http_payload_size: 25 * 1024 * 1024, rate_limiting: RateLimitingConfig {
387 max_requests_per_window: 200,
388 window_duration_secs: 60,
389 max_connections_per_ip: 20,
390 max_messages_per_second: 50,
391 burst_allowance: 10,
392 enabled: true,
393 },
394 compression_bomb: CompressionBombConfig::default(),
395 },
396 sessions: SessionLimits {
397 max_session_id_length: 128,
398 min_session_id_length: 8,
399 max_streams_per_session: 50,
400 session_timeout_secs: 1800, max_session_data_size: 50 * 1024 * 1024, },
403 }
404 }
405
406 pub fn buffer_ttl(&self) -> Duration {
408 Duration::from_secs(self.buffers.buffer_ttl_secs)
409 }
410
411 pub fn connection_timeout(&self) -> Duration {
413 Duration::from_secs(self.network.connection_timeout_secs)
414 }
415
416 pub fn session_timeout(&self) -> Duration {
418 Duration::from_secs(self.sessions.session_timeout_secs)
419 }
420}
421
422#[cfg(test)]
423mod tests {
424 use super::*;
425 use crate::config::ConfigError;
426
427 #[test]
428 fn test_default_security_config() {
429 let config = SecurityConfig::default();
430
431 assert!(config.json.max_input_size > 0);
433 assert!(config.buffers.max_buffer_size > 0);
434 assert!(config.network.max_concurrent_connections > 0);
435 assert!(config.sessions.max_session_id_length >= config.sessions.min_session_id_length);
436 }
437
438 #[test]
439 fn test_security_config_default_validates() {
440 SecurityConfig::default()
441 .validate()
442 .expect("SecurityConfig::default() must be valid");
443 }
444
445 #[test]
446 fn test_rejects_min_session_id_length_greater_than_max() {
447 let mut config = SecurityConfig::default();
448 config.sessions.min_session_id_length = 200;
449 config.sessions.max_session_id_length = 100;
450 let err = config.validate().unwrap_err();
451 assert!(matches!(
452 err,
453 ConfigError::InconsistentBounds {
454 section: "security.sessions",
455 ..
456 }
457 ));
458 }
459
460 #[test]
461 fn test_high_throughput_config() {
462 let config = SecurityConfig::high_throughput();
463 let default = SecurityConfig::default();
464
465 assert!(config.json.max_input_size >= default.json.max_input_size);
467 assert!(config.buffers.max_total_memory >= default.buffers.max_total_memory);
468 assert!(
469 config.network.max_concurrent_connections >= default.network.max_concurrent_connections
470 );
471 }
472
473 #[test]
474 fn test_low_memory_config() {
475 let config = SecurityConfig::low_memory();
476 let default = SecurityConfig::default();
477
478 assert!(config.json.max_input_size <= default.json.max_input_size);
480 assert!(config.buffers.max_total_memory <= default.buffers.max_total_memory);
481 assert!(config.buffers.max_buffers_per_bucket <= default.buffers.max_buffers_per_bucket);
482 }
483
484 #[test]
485 fn test_duration_conversions() {
486 let config = SecurityConfig::default();
487
488 assert!(config.buffer_ttl().as_secs() > 0);
489 assert!(config.connection_timeout().as_secs() > 0);
490 assert!(config.session_timeout().as_secs() > 0);
491 }
492}