Skip to main content

network_protocol/
config.rs

1//! # Configuration Management
2//!
3//! Centralized configuration for the network protocol library.
4//!
5//! This module provides structured configuration for servers and clients,
6//! including connection parameters, timeouts, compression settings, and security options.
7//!
8//! ## Configuration Sources
9//! - TOML files via `from_toml_file()`
10//! - Direct instantiation with defaults
11//! - Environment-specific overrides
12//!
13//! ## Security Considerations
14//! - Default compression threshold (512 bytes) balances performance and CPU
15//! - Recommended timeout values prevent slowloris attacks
16//! - TLS settings enforce modern cryptography (TLS 1.2+)
17
18use 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
27/// Current supported protocol version
28pub const PROTOCOL_VERSION: u8 = 1;
29
30/// Magic bytes to identify protocol packets (e.g., 0x4E50524F → "NPRO")
31pub const MAGIC_BYTES: [u8; 4] = [0x4E, 0x50, 0x52, 0x4F];
32
33/// Max allowed payload size (e.g. 16 MB)
34pub const MAX_PAYLOAD_SIZE: usize = 16 * 1024 * 1024;
35
36/// Whether to enable compression by default
37pub const ENABLE_COMPRESSION: bool = false;
38
39/// Whether to enable encryption by default
40pub const ENABLE_ENCRYPTION: bool = true;
41
42/// Main network configuration structure that contains all configurable settings
43#[derive(Debug, Clone, Deserialize, Serialize, Default)]
44pub struct NetworkConfig {
45    /// Server-specific configuration
46    #[serde(default)]
47    pub server: ServerConfig,
48
49    /// Client-specific configuration
50    #[serde(default)]
51    pub client: ClientConfig,
52
53    /// Transport configuration
54    #[serde(default)]
55    pub transport: TransportConfig,
56
57    /// Logging configuration
58    #[serde(default)]
59    pub logging: LoggingConfig,
60}
61
62// Default implementation is now derived
63
64impl NetworkConfig {
65    /// Load configuration from a TOML file
66    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    /// Load configuration from TOML string
78    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    /// Load configuration from environment variables
84    pub fn from_env() -> Result<Self> {
85        // Start with defaults
86        let mut config = Self::default();
87
88        // Override with environment variables
89        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        // Add more environment variables as needed
113
114        Ok(config)
115    }
116
117    /// Apply overrides to the default configuration
118    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    /// Generate example configuration file content
128    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    /// Save configuration to a file
134    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    /// Validate the configuration for common issues and misconfigurations
145    ///
146    /// Returns a list of validation errors. Empty list means configuration is valid.
147    pub fn validate(&self) -> Vec<String> {
148        let mut errors = Vec::new();
149
150        // Validate server configuration
151        errors.extend(self.server.validate());
152
153        // Validate client configuration
154        errors.extend(self.client.validate());
155
156        // Validate transport configuration
157        errors.extend(self.transport.validate());
158
159        // Validate logging configuration
160        errors.extend(self.logging.validate());
161
162        errors
163    }
164
165    /// Validate and return Result - convenience method
166    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/// Server-specific configuration
180#[derive(Debug, Clone, Deserialize, Serialize)]
181pub struct ServerConfig {
182    /// Server listen address (e.g., "127.0.0.1:9000")
183    pub address: String,
184
185    /// Maximum number of messages queued per connection before the server stops
186    /// reading from that client (backpressure)
187    pub backpressure_limit: usize,
188
189    /// Timeout for each step of a client's handshake (the server waits at most this
190    /// long for each handshake message). An established session is not limited by it.
191    #[serde(with = "duration_serde")]
192    pub connection_timeout: Duration,
193
194    /// Interval for sending heartbeat messages
195    #[serde(with = "duration_serde")]
196    pub heartbeat_interval: Duration,
197
198    /// Timeout for graceful server shutdown
199    #[serde(with = "duration_serde")]
200    pub shutdown_timeout: Duration,
201
202    /// Maximum number of concurrent connections. Connections accepted beyond this
203    /// limit are closed immediately.
204    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    /// Validate server configuration
222    pub fn validate(&self) -> Vec<String> {
223        let mut errors = Vec::new();
224
225        // Validate address format
226        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        // Validate backpressure limit
236        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        // Validate connection timeout
246        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        // Validate heartbeat interval
253        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        // Validate shutdown timeout
260        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        // Validate max connections
267        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/// Client-specific configuration
281#[derive(Debug, Clone, Deserialize, Serialize)]
282pub struct ClientConfig {
283    /// Target server address
284    pub address: String,
285
286    /// Timeout for connection attempts
287    #[serde(with = "duration_serde")]
288    pub connection_timeout: Duration,
289
290    /// Timeout for sending a single message with `Client::send`
291    #[serde(with = "duration_serde")]
292    pub operation_timeout: Duration,
293
294    /// Timeout for waiting for response messages
295    #[serde(with = "duration_serde")]
296    pub response_timeout: Duration,
297
298    /// Interval for sending heartbeat messages
299    #[serde(with = "duration_serde")]
300    pub heartbeat_interval: Duration,
301
302    /// Whether to automatically reconnect on connection loss
303    ///
304    /// Not implemented: `Client` never reconnects, whatever this is set to.
305    #[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    /// Maximum number of reconnect attempts before giving up
313    ///
314    /// Not implemented, see `auto_reconnect`.
315    #[deprecated(since = "1.3.0", note = "not applied: Client never reconnects")]
316    #[serde(default)]
317    pub max_reconnect_attempts: u32,
318
319    /// Delay between reconnect attempts
320    ///
321    /// Not implemented, see `auto_reconnect`.
322    #[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)] // still fills in the deprecated fields
328impl 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    /// Validate client configuration
345    #[allow(deprecated)] // still checks the deprecated reconnect fields
346    pub fn validate(&self) -> Vec<String> {
347        let mut errors = Vec::new();
348
349        // Validate address format
350        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        // Validate timeouts
360        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        // Validate reconnect settings
373        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/// Transport configuration
391///
392/// None of these settings is read by the built-in transports, clients or servers, so
393/// every field is deprecated. The packet codec always enforces `MAX_PAYLOAD_SIZE`, the
394/// built-in services always encrypt, and compression is applied only where you call
395/// `utils::compression` yourself.
396#[derive(Debug, Clone, Deserialize, Serialize)]
397pub struct TransportConfig {
398    /// Whether to enable compression
399    #[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    /// Whether to enable encryption
407    #[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    /// Maximum allowed payload size in bytes
415    #[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    /// Compression level (when compression is enabled)
423    #[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    /// Minimum payload size (bytes) before compression is applied
431    /// Payloads smaller than this threshold should bypass compression to reduce overhead
432    #[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)] // still fills in the deprecated fields
441impl 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, // Default compression level (medium)
448            compression_threshold_bytes: 512,
449        }
450    }
451}
452
453impl TransportConfig {
454    /// Validate transport configuration
455    #[allow(deprecated)] // still checks the deprecated fields
456    pub fn validate(&self) -> Vec<String> {
457        let mut errors = Vec::new();
458
459        // Validate max payload size
460        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        // Validate compression settings
472        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        // Warn if encryption is disabled
488        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/// Logging configuration
499#[derive(Debug, Clone, Deserialize, Serialize)]
500pub struct LoggingConfig {
501    /// Application name for logs
502    pub app_name: String,
503
504    /// Log level
505    #[serde(with = "log_level_serde")]
506    pub log_level: Level,
507
508    /// Whether to log to console
509    pub log_to_console: bool,
510
511    /// Whether to log to file
512    pub log_to_file: bool,
513
514    /// Path to log file (if log_to_file is true)
515    pub log_file_path: Option<String>,
516
517    /// Whether to use JSON formatting for logs
518    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    /// Validate logging configuration
536    pub fn validate(&self) -> Vec<String> {
537        let mut errors = Vec::new();
538
539        // Validate app name
540        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        // Validate file logging configuration
550        if self.log_to_file {
551            if let Some(ref path) = self.log_file_path {
552                // Check if parent directory exists (if path is absolute)
553                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        // Validate at least one output is enabled
567        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
576/// Helper module for Duration serialization/deserialization
577mod 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
598/// Helper module for tracing::Level serialization/deserialization
599mod 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}