1use crate::error::{ProtocolError, Result};
19use crate::utils::timeout;
20use serde::{Deserialize, Serialize};
21use std::fs::File;
22use std::io::Read;
23use std::path::Path;
24use std::time::Duration;
25use tracing::Level;
26
27pub const PROTOCOL_VERSION: u8 = 1;
29
30pub const MAGIC_BYTES: [u8; 4] = [0x4E, 0x50, 0x52, 0x4F];
32
33pub const MAX_PAYLOAD_SIZE: usize = 16 * 1024 * 1024;
35
36pub const ENABLE_COMPRESSION: bool = false;
38
39pub const ENABLE_ENCRYPTION: bool = true;
41
42#[derive(Debug, Clone, Deserialize, Serialize, Default)]
44pub struct NetworkConfig {
45 #[serde(default)]
47 pub server: ServerConfig,
48
49 #[serde(default)]
51 pub client: ClientConfig,
52
53 #[serde(default)]
55 pub transport: TransportConfig,
56
57 #[serde(default)]
59 pub logging: LoggingConfig,
60}
61
62impl NetworkConfig {
65 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
67 let mut file = File::open(path)
68 .map_err(|e| ProtocolError::ConfigError(format!("Failed to open config file: {e}")))?;
69
70 let mut contents = String::new();
71 file.read_to_string(&mut contents)
72 .map_err(|e| ProtocolError::ConfigError(format!("Failed to read config file: {e}")))?;
73
74 Self::from_toml(&contents)
75 }
76
77 pub fn from_toml(content: &str) -> Result<Self> {
79 toml::from_str::<Self>(content)
80 .map_err(|e| ProtocolError::ConfigError(format!("Failed to parse TOML: {e}")))
81 }
82
83 pub fn from_env() -> Result<Self> {
85 let mut config = Self::default();
87
88 if let Ok(addr) = std::env::var("NETWORK_PROTOCOL_SERVER_ADDRESS") {
90 config.server.address = addr;
91 }
92
93 if let Ok(capacity) = std::env::var("NETWORK_PROTOCOL_BACKPRESSURE_LIMIT") {
94 if let Ok(val) = capacity.parse::<usize>() {
95 config.server.backpressure_limit = val;
96 }
97 }
98
99 if let Ok(timeout) = std::env::var("NETWORK_PROTOCOL_CONNECTION_TIMEOUT_MS") {
100 if let Ok(val) = timeout.parse::<u64>() {
101 config.server.connection_timeout = Duration::from_millis(val);
102 config.client.connection_timeout = Duration::from_millis(val);
103 }
104 }
105
106 if let Ok(heartbeat) = std::env::var("NETWORK_PROTOCOL_HEARTBEAT_INTERVAL_MS") {
107 if let Ok(val) = heartbeat.parse::<u64>() {
108 config.server.heartbeat_interval = Duration::from_millis(val);
109 }
110 }
111
112 Ok(config)
115 }
116
117 pub fn default_with_overrides<F>(mutator: F) -> Self
119 where
120 F: FnOnce(&mut Self),
121 {
122 let mut config = Self::default();
123 mutator(&mut config);
124 config
125 }
126
127 pub fn example_config() -> String {
129 toml::to_string_pretty(&Self::default())
130 .unwrap_or_else(|_| String::from("# Failed to generate example config"))
131 }
132
133 pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
135 let content = toml::to_string_pretty(self)
136 .map_err(|e| ProtocolError::ConfigError(format!("Failed to serialize config: {e}")))?;
137
138 std::fs::write(path, content)
139 .map_err(|e| ProtocolError::ConfigError(format!("Failed to write config file: {e}")))?;
140
141 Ok(())
142 }
143
144 pub fn validate(&self) -> Vec<String> {
148 let mut errors = Vec::new();
149
150 errors.extend(self.server.validate());
152
153 errors.extend(self.client.validate());
155
156 errors.extend(self.transport.validate());
158
159 errors.extend(self.logging.validate());
161
162 errors
163 }
164
165 pub fn validate_strict(&self) -> Result<()> {
167 let errors = self.validate();
168 if errors.is_empty() {
169 Ok(())
170 } else {
171 Err(ProtocolError::ConfigError(format!(
172 "Configuration validation failed:\n - {}",
173 errors.join("\n - ")
174 )))
175 }
176 }
177}
178
179#[derive(Debug, Clone, Deserialize, Serialize)]
181pub struct ServerConfig {
182 pub address: String,
184
185 pub backpressure_limit: usize,
188
189 #[serde(with = "duration_serde")]
192 pub connection_timeout: Duration,
193
194 #[serde(with = "duration_serde")]
196 pub heartbeat_interval: Duration,
197
198 #[serde(with = "duration_serde")]
200 pub shutdown_timeout: Duration,
201
202 pub max_connections: usize,
205}
206
207impl Default for ServerConfig {
208 fn default() -> Self {
209 Self {
210 address: String::from("127.0.0.1:9000"),
211 backpressure_limit: 32,
212 connection_timeout: timeout::DEFAULT_TIMEOUT,
213 heartbeat_interval: timeout::KEEPALIVE_INTERVAL,
214 shutdown_timeout: timeout::SHUTDOWN_TIMEOUT,
215 max_connections: 1000,
216 }
217 }
218}
219
220impl ServerConfig {
221 pub fn validate(&self) -> Vec<String> {
223 let mut errors = Vec::new();
224
225 if self.address.is_empty() {
227 errors.push("Server address cannot be empty".to_string());
228 } else if self.address.parse::<std::net::SocketAddr>().is_err() {
229 errors.push(format!(
230 "Invalid server address format: '{}' (expected format: '0.0.0.0:8080')",
231 self.address
232 ));
233 }
234
235 if self.backpressure_limit == 0 {
237 errors.push("Backpressure limit must be greater than 0".to_string());
238 } else if self.backpressure_limit > 1_000_000 {
239 errors.push(format!(
240 "Backpressure limit too large: {} (max recommended: 1,000,000)",
241 self.backpressure_limit
242 ));
243 }
244
245 if self.connection_timeout.as_millis() < 100 {
247 errors.push("Connection timeout too short (minimum: 100ms)".to_string());
248 } else if self.connection_timeout.as_secs() > 300 {
249 errors.push("Connection timeout too long (maximum: 300s)".to_string());
250 }
251
252 if self.heartbeat_interval.as_millis() < 100 {
254 errors.push("Heartbeat interval too short (minimum: 100ms)".to_string());
255 } else if self.heartbeat_interval.as_secs() > 3600 {
256 errors.push("Heartbeat interval too long (maximum: 1 hour)".to_string());
257 }
258
259 if self.shutdown_timeout.as_secs() < 1 {
261 errors.push("Shutdown timeout too short (minimum: 1s)".to_string());
262 } else if self.shutdown_timeout.as_secs() > 60 {
263 errors.push("Shutdown timeout too long (maximum: 60s)".to_string());
264 }
265
266 if self.max_connections == 0 {
268 errors.push("Max connections must be greater than 0".to_string());
269 } else if self.max_connections > 100_000 {
270 errors.push(format!(
271 "Max connections very high: {} (ensure system resources can support this)",
272 self.max_connections
273 ));
274 }
275
276 errors
277 }
278}
279
280#[derive(Debug, Clone, Deserialize, Serialize)]
282pub struct ClientConfig {
283 pub address: String,
285
286 #[serde(with = "duration_serde")]
288 pub connection_timeout: Duration,
289
290 #[serde(with = "duration_serde")]
292 pub operation_timeout: Duration,
293
294 #[serde(with = "duration_serde")]
296 pub response_timeout: Duration,
297
298 #[serde(with = "duration_serde")]
300 pub heartbeat_interval: Duration,
301
302 #[deprecated(
306 since = "1.3.0",
307 note = "not applied: Client never reconnects; reconnect in your own code with Client::connect_with_config()"
308 )]
309 #[serde(default)]
310 pub auto_reconnect: bool,
311
312 #[deprecated(since = "1.3.0", note = "not applied: Client never reconnects")]
316 #[serde(default)]
317 pub max_reconnect_attempts: u32,
318
319 #[deprecated(since = "1.3.0", note = "not applied: Client never reconnects")]
323 #[serde(default, with = "duration_serde")]
324 pub reconnect_delay: Duration,
325}
326
327#[allow(deprecated)] impl Default for ClientConfig {
329 fn default() -> Self {
330 Self {
331 address: String::from("127.0.0.1:9000"),
332 connection_timeout: timeout::DEFAULT_TIMEOUT,
333 operation_timeout: Duration::from_secs(3),
334 response_timeout: Duration::from_secs(30),
335 heartbeat_interval: timeout::KEEPALIVE_INTERVAL,
336 auto_reconnect: true,
337 max_reconnect_attempts: 3,
338 reconnect_delay: Duration::from_secs(1),
339 }
340 }
341}
342
343impl ClientConfig {
344 #[allow(deprecated)] pub fn validate(&self) -> Vec<String> {
347 let mut errors = Vec::new();
348
349 if self.address.is_empty() {
351 errors.push("Client address cannot be empty".to_string());
352 } else if self.address.parse::<std::net::SocketAddr>().is_err() {
353 errors.push(format!(
354 "Invalid client address format: '{}' (expected format: 'example.com:8080')",
355 self.address
356 ));
357 }
358
359 if self.connection_timeout.as_millis() < 100 {
361 errors.push("Connection timeout too short (minimum: 100ms)".to_string());
362 }
363
364 if self.operation_timeout.as_millis() < 10 {
365 errors.push("Operation timeout too short (minimum: 10ms)".to_string());
366 }
367
368 if self.response_timeout.as_millis() < 100 {
369 errors.push("Response timeout too short (minimum: 100ms)".to_string());
370 }
371
372 if self.auto_reconnect && self.max_reconnect_attempts == 0 {
374 errors.push(
375 "Max reconnect attempts must be greater than 0 when auto_reconnect is enabled"
376 .to_string(),
377 );
378 }
379
380 if self.reconnect_delay.as_millis() < 10 {
381 errors.push("Reconnect delay too short (minimum: 10ms)".to_string());
382 } else if self.reconnect_delay.as_secs() > 60 {
383 errors.push("Reconnect delay too long (maximum: 60s)".to_string());
384 }
385
386 errors
387 }
388}
389
390#[derive(Debug, Clone, Deserialize, Serialize)]
397pub struct TransportConfig {
398 #[deprecated(
400 since = "1.3.0",
401 note = "not applied by any transport; call utils::compression directly"
402 )]
403 #[serde(default)]
404 pub compression_enabled: bool,
405
406 #[deprecated(
408 since = "1.3.0",
409 note = "not applied: the built-in services always encrypt"
410 )]
411 #[serde(default)]
412 pub encryption_enabled: bool,
413
414 #[deprecated(
416 since = "1.3.0",
417 note = "not applied: the packet codec always enforces MAX_PAYLOAD_SIZE"
418 )]
419 #[serde(default)]
420 pub max_payload_size: usize,
421
422 #[deprecated(
424 since = "1.3.0",
425 note = "not applied by any transport; call utils::compression directly"
426 )]
427 #[serde(default)]
428 pub compression_level: i32,
429
430 #[deprecated(
433 since = "1.3.0",
434 note = "not applied by any transport; call utils::compression directly"
435 )]
436 #[serde(default)]
437 pub compression_threshold_bytes: usize,
438}
439
440#[allow(deprecated)] impl Default for TransportConfig {
442 fn default() -> Self {
443 Self {
444 compression_enabled: ENABLE_COMPRESSION,
445 encryption_enabled: ENABLE_ENCRYPTION,
446 max_payload_size: MAX_PAYLOAD_SIZE,
447 compression_level: 6, compression_threshold_bytes: 512,
449 }
450 }
451}
452
453impl TransportConfig {
454 #[allow(deprecated)] pub fn validate(&self) -> Vec<String> {
457 let mut errors = Vec::new();
458
459 if self.max_payload_size == 0 {
461 errors.push("Max payload size cannot be 0".to_string());
462 } else if self.max_payload_size < 1024 {
463 errors.push("Max payload size too small (minimum: 1 KB)".to_string());
464 } else if self.max_payload_size > 100 * 1024 * 1024 {
465 errors.push(format!(
466 "Max payload size too large: {} bytes (maximum recommended: 100 MB)",
467 self.max_payload_size
468 ));
469 }
470
471 if self.compression_enabled {
473 if self.compression_level < 1 || self.compression_level > 22 {
474 errors.push(format!(
475 "Invalid compression level: {} (valid range: 1-22)",
476 self.compression_level
477 ));
478 }
479
480 if self.compression_threshold_bytes > self.max_payload_size {
481 errors.push(
482 "Compression threshold cannot be larger than max payload size".to_string(),
483 );
484 }
485 }
486
487 if !self.encryption_enabled {
489 errors.push(
490 "WARNING: Encryption is disabled - not recommended for production".to_string(),
491 );
492 }
493
494 errors
495 }
496}
497
498#[derive(Debug, Clone, Deserialize, Serialize)]
500pub struct LoggingConfig {
501 pub app_name: String,
503
504 #[serde(with = "log_level_serde")]
506 pub log_level: Level,
507
508 pub log_to_console: bool,
510
511 pub log_to_file: bool,
513
514 pub log_file_path: Option<String>,
516
517 pub json_format: bool,
519}
520
521impl Default for LoggingConfig {
522 fn default() -> Self {
523 Self {
524 app_name: String::from("network-protocol"),
525 log_level: Level::INFO,
526 log_to_console: true,
527 log_to_file: false,
528 log_file_path: None,
529 json_format: false,
530 }
531 }
532}
533
534impl LoggingConfig {
535 pub fn validate(&self) -> Vec<String> {
537 let mut errors = Vec::new();
538
539 if self.app_name.is_empty() {
541 errors.push("Application name cannot be empty".to_string());
542 } else if self.app_name.len() > 64 {
543 errors.push(format!(
544 "Application name too long: {} characters (maximum: 64)",
545 self.app_name.len()
546 ));
547 }
548
549 if self.log_to_file {
551 if let Some(ref path) = self.log_file_path {
552 if let Some(parent) = std::path::Path::new(path).parent() {
554 if !parent.as_os_str().is_empty() && !parent.exists() {
555 errors.push(format!(
556 "Log file directory does not exist: {}",
557 parent.display()
558 ));
559 }
560 }
561 } else {
562 errors.push("log_file_path must be specified when log_to_file is true".to_string());
563 }
564 }
565
566 if !self.log_to_console && !self.log_to_file {
568 errors
569 .push("At least one logging output (console or file) must be enabled".to_string());
570 }
571
572 errors
573 }
574}
575
576mod duration_serde {
578 use serde::{Deserialize, Deserializer, Serialize, Serializer};
579 use std::time::Duration;
580
581 pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
582 where
583 S: Serializer,
584 {
585 let millis = duration.as_millis() as u64;
586 millis.serialize(serializer)
587 }
588
589 pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
590 where
591 D: Deserializer<'de>,
592 {
593 let millis = u64::deserialize(deserializer)?;
594 Ok(Duration::from_millis(millis))
595 }
596}
597
598mod log_level_serde {
600 use serde::{Deserialize, Deserializer, Serialize, Serializer};
601 use std::str::FromStr;
602 use tracing::Level;
603
604 pub fn serialize<S>(level: &Level, serializer: S) -> Result<S::Ok, S::Error>
605 where
606 S: Serializer,
607 {
608 let level_str = match *level {
609 Level::TRACE => "trace",
610 Level::DEBUG => "debug",
611 Level::INFO => "info",
612 Level::WARN => "warn",
613 Level::ERROR => "error",
614 };
615 level_str.serialize(serializer)
616 }
617
618 pub fn deserialize<'de, D>(deserializer: D) -> Result<Level, D::Error>
619 where
620 D: Deserializer<'de>,
621 {
622 let level_str = String::deserialize(deserializer)?;
623 Level::from_str(&level_str)
624 .map_err(|_| serde::de::Error::custom(format!("Invalid log level: {level_str}")))
625 }
626}