1use crate::error::{EtherNetIpError, Result};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fs;
5use std::path::Path;
6use std::time::Duration;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10#[deprecated(
11 since = "1.2.0",
12 note = "ProductionConfig is not consumed by EipClient and implies unsupported enforcement; configure EipClient/Client/Fleet directly. The type will be removed in 2.0."
13)]
14pub struct ProductionConfig {
15 pub connection: ConnectionConfig,
17 pub performance: PerformanceConfig,
19 pub monitoring: MonitoringConfig,
21 pub security: SecurityConfig,
23 pub logging: LoggingConfig,
25 pub plc_settings: HashMap<String, PlcSpecificConfig>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct ConnectionConfig {
32 pub connection_timeout: Duration,
34 pub read_timeout: Duration,
36 pub write_timeout: Duration,
38 pub max_connections: u32,
40 pub retry_attempts: u32,
42 pub retry_delay: Duration,
44 pub keep_alive_interval: Duration,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct PerformanceConfig {
51 pub max_packet_size: usize,
53 pub batch_config: BatchConfig,
55 pub connection_pool: ConnectionPoolConfig,
57 pub memory_limits: MemoryLimits,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct BatchConfig {
64 pub max_operations_per_batch: usize,
66 pub batch_timeout: Duration,
68 pub continue_on_error: bool,
70 pub optimize_packet_packing: bool,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct ConnectionPoolConfig {
77 pub initial_size: u32,
79 pub max_size: u32,
81 pub growth_increment: u32,
83 pub idle_timeout: Duration,
85 pub cleanup_interval: Duration,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct MemoryLimits {
92 pub max_memory_mb: usize,
94 pub warning_threshold_mb: usize,
96 pub enable_monitoring: bool,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct MonitoringConfig {
103 pub enabled: bool,
105 pub collection_interval: Duration,
107 pub health_check_interval: Duration,
109 pub retention_period: Duration,
111 pub enable_profiling: bool,
113 pub alert_thresholds: AlertThresholds,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct AlertThresholds {
120 pub error_rate_threshold: f64,
122 pub latency_threshold_ms: f64,
124 pub memory_threshold_mb: usize,
126 pub connection_failure_threshold: u32,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct SecurityConfig {
133 pub enable_encryption: bool,
135 pub validate_connections: bool,
137 pub validate_inputs: bool,
139 pub rate_limiting: RateLimitingConfig,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct RateLimitingConfig {
146 pub enabled: bool,
148 pub max_requests_per_second: u32,
150 pub burst_capacity: u32,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct LoggingConfig {
157 pub level: LogLevel,
159 pub format: LogFormat,
161 pub file_path: Option<String>,
163 pub enable_console: bool,
165 pub enable_structured: bool,
167 pub rotation: LogRotationConfig,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct LogRotationConfig {
174 pub enabled: bool,
176 pub max_file_size_mb: usize,
178 pub max_files: usize,
180 pub schedule: LogRotationSchedule,
182}
183
184#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
186#[serde(rename_all = "lowercase")]
187pub enum LogLevel {
188 Trace,
190 Debug,
192 Info,
194 Warn,
196 Error,
198}
199
200#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
202#[serde(rename_all = "lowercase")]
203pub enum LogFormat {
204 Json,
206 Text,
208}
209
210#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
212#[serde(rename_all = "lowercase")]
213pub enum LogRotationSchedule {
214 Daily,
216 Weekly,
218 Monthly,
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize)]
224pub struct PlcSpecificConfig {
225 pub model: String,
227 pub connection_settings: HashMap<String, String>,
229 pub tag_discovery: TagDiscoveryConfig,
231 pub performance_tuning: HashMap<String, String>,
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct TagDiscoveryConfig {
238 pub enabled: bool,
240 pub interval: Duration,
242 pub cache_tags: bool,
244 pub max_tags: usize,
246}
247
248#[expect(
249 deprecated,
250 reason = "CODEX-AQ keeps ProductionConfig compatibility until 2.0 removal"
251)]
252impl Default for ProductionConfig {
253 fn default() -> Self {
254 Self {
255 connection: ConnectionConfig {
256 connection_timeout: Duration::from_secs(10),
257 read_timeout: Duration::from_secs(5),
258 write_timeout: Duration::from_secs(5),
259 max_connections: 10,
260 retry_attempts: 3,
261 retry_delay: Duration::from_secs(1),
262 keep_alive_interval: Duration::from_secs(30),
263 },
264 performance: PerformanceConfig {
265 max_packet_size: 4000,
266 batch_config: BatchConfig {
267 max_operations_per_batch: 50,
268 batch_timeout: Duration::from_secs(10),
269 continue_on_error: true,
270 optimize_packet_packing: true,
271 },
272 connection_pool: ConnectionPoolConfig {
273 initial_size: 2,
274 max_size: 10,
275 growth_increment: 2,
276 idle_timeout: Duration::from_secs(300),
277 cleanup_interval: Duration::from_secs(60),
278 },
279 memory_limits: MemoryLimits {
280 max_memory_mb: 100,
281 warning_threshold_mb: 80,
282 enable_monitoring: true,
283 },
284 },
285 monitoring: MonitoringConfig {
286 enabled: true,
287 collection_interval: Duration::from_secs(30),
288 health_check_interval: Duration::from_secs(60),
289 retention_period: Duration::from_secs(86400), enable_profiling: false,
291 alert_thresholds: AlertThresholds {
292 error_rate_threshold: 0.05,
293 latency_threshold_ms: 1000.0,
294 memory_threshold_mb: 80,
295 connection_failure_threshold: 5,
296 },
297 },
298 security: SecurityConfig {
299 enable_encryption: false,
300 validate_connections: true,
301 validate_inputs: true,
302 rate_limiting: RateLimitingConfig {
303 enabled: true,
304 max_requests_per_second: 100,
305 burst_capacity: 200,
306 },
307 },
308 logging: LoggingConfig {
309 level: LogLevel::Info,
310 format: LogFormat::Json,
311 file_path: Some("logs/ethernet_ip.log".to_string()),
312 enable_console: true,
313 enable_structured: true,
314 rotation: LogRotationConfig {
315 enabled: true,
316 max_file_size_mb: 100,
317 max_files: 10,
318 schedule: LogRotationSchedule::Daily,
319 },
320 },
321 plc_settings: HashMap::new(),
322 }
323 }
324}
325
326#[expect(
327 deprecated,
328 reason = "CODEX-AQ keeps ProductionConfig compatibility until 2.0 removal"
329)]
330impl ProductionConfig {
331 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
333 let content = fs::read_to_string(path)?;
334 let config: ProductionConfig =
335 toml::from_str(&content).map_err(|e| EtherNetIpError::Other(e.to_string()))?;
336 Ok(config)
337 }
338
339 pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
341 let content =
342 toml::to_string_pretty(self).map_err(|e| EtherNetIpError::Other(e.to_string()))?;
343 fs::write(path, content)?;
344 Ok(())
345 }
346
347 pub fn validate(&self) -> std::result::Result<(), Vec<String>> {
349 let mut errors = Vec::new();
350
351 if self.connection.connection_timeout.as_secs() == 0 {
353 errors.push("Connection timeout must be greater than 0".to_string());
354 }
355
356 if self.connection.max_connections == 0 {
357 errors.push("Maximum connections must be greater than 0".to_string());
358 }
359
360 if self.performance.max_packet_size < 100 {
362 errors.push("Maximum packet size must be at least 100 bytes".to_string());
363 }
364
365 if self.performance.batch_config.max_operations_per_batch == 0 {
366 errors.push("Maximum operations per batch must be greater than 0".to_string());
367 }
368
369 if self.monitoring.collection_interval.as_secs() == 0 {
371 errors.push("Collection interval must be greater than 0".to_string());
372 }
373
374 if self.security.rate_limiting.enabled
376 && self.security.rate_limiting.max_requests_per_second == 0
377 {
378 errors.push(
379 "Max requests per second must be greater than 0 when rate limiting is enabled"
380 .to_string(),
381 );
382 }
383
384 if errors.is_empty() {
385 Ok(())
386 } else {
387 Err(errors)
388 }
389 }
390
391 pub fn get_plc_config(&self, plc_address: &str) -> Option<&PlcSpecificConfig> {
393 self.plc_settings.get(plc_address)
394 }
395
396 pub fn set_plc_config(&mut self, plc_address: String, config: PlcSpecificConfig) {
398 self.plc_settings.insert(plc_address, config);
399 }
400
401 pub fn development() -> Self {
403 let mut config = Self::default();
404 config.logging.level = LogLevel::Debug;
405 config.monitoring.enabled = false;
406 config.security.rate_limiting.enabled = false;
407 config.performance.memory_limits.enable_monitoring = false;
408 config
409 }
410
411 pub fn production() -> Self {
413 let mut config = Self::default();
414 config.logging.level = LogLevel::Info;
415 config.monitoring.enabled = true;
416 config.security.rate_limiting.enabled = true;
417 config.performance.memory_limits.enable_monitoring = true;
418 config.performance.memory_limits.max_memory_mb = 500;
419 config
420 }
421}