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)]
30pub struct ConnectionConfig {
31 pub connection_timeout: Duration,
33 pub read_timeout: Duration,
35 pub write_timeout: Duration,
37 pub max_connections: u32,
39 pub retry_attempts: u32,
41 pub retry_delay: Duration,
43 pub keep_alive_interval: Duration,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct PerformanceConfig {
49 pub max_packet_size: usize,
51 pub batch_config: BatchConfig,
53 pub connection_pool: ConnectionPoolConfig,
55 pub memory_limits: MemoryLimits,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct BatchConfig {
61 pub max_operations_per_batch: usize,
63 pub batch_timeout: Duration,
65 pub continue_on_error: bool,
67 pub optimize_packet_packing: bool,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct ConnectionPoolConfig {
73 pub initial_size: u32,
75 pub max_size: u32,
77 pub growth_increment: u32,
79 pub idle_timeout: Duration,
81 pub cleanup_interval: Duration,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct MemoryLimits {
87 pub max_memory_mb: usize,
89 pub warning_threshold_mb: usize,
91 pub enable_monitoring: bool,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct MonitoringConfig {
97 pub enabled: bool,
99 pub collection_interval: Duration,
101 pub health_check_interval: Duration,
103 pub retention_period: Duration,
105 pub enable_profiling: bool,
107 pub alert_thresholds: AlertThresholds,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct AlertThresholds {
113 pub error_rate_threshold: f64,
115 pub latency_threshold_ms: f64,
117 pub memory_threshold_mb: usize,
119 pub connection_failure_threshold: u32,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct SecurityConfig {
125 pub enable_encryption: bool,
127 pub validate_connections: bool,
129 pub validate_inputs: bool,
131 pub rate_limiting: RateLimitingConfig,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct RateLimitingConfig {
137 pub enabled: bool,
139 pub max_requests_per_second: u32,
141 pub burst_capacity: u32,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct LoggingConfig {
147 pub level: LogLevel,
149 pub format: LogFormat,
151 pub file_path: Option<String>,
153 pub enable_console: bool,
155 pub enable_structured: bool,
157 pub rotation: LogRotationConfig,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct LogRotationConfig {
163 pub enabled: bool,
165 pub max_file_size_mb: usize,
167 pub max_files: usize,
169 pub schedule: LogRotationSchedule,
171}
172
173#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
174#[serde(rename_all = "lowercase")]
175pub enum LogLevel {
176 Trace,
177 Debug,
178 Info,
179 Warn,
180 Error,
181}
182
183#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
184#[serde(rename_all = "lowercase")]
185pub enum LogFormat {
186 Json,
187 Text,
188}
189
190#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
191#[serde(rename_all = "lowercase")]
192pub enum LogRotationSchedule {
193 Daily,
194 Weekly,
195 Monthly,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
199pub struct PlcSpecificConfig {
200 pub model: String,
202 pub connection_settings: HashMap<String, String>,
204 pub tag_discovery: TagDiscoveryConfig,
206 pub performance_tuning: HashMap<String, String>,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct TagDiscoveryConfig {
212 pub enabled: bool,
214 pub interval: Duration,
216 pub cache_tags: bool,
218 pub max_tags: usize,
220}
221
222#[expect(
223 deprecated,
224 reason = "CODEX-AQ keeps ProductionConfig compatibility until 2.0 removal"
225)]
226impl Default for ProductionConfig {
227 fn default() -> Self {
228 Self {
229 connection: ConnectionConfig {
230 connection_timeout: Duration::from_secs(10),
231 read_timeout: Duration::from_secs(5),
232 write_timeout: Duration::from_secs(5),
233 max_connections: 10,
234 retry_attempts: 3,
235 retry_delay: Duration::from_secs(1),
236 keep_alive_interval: Duration::from_secs(30),
237 },
238 performance: PerformanceConfig {
239 max_packet_size: 4000,
240 batch_config: BatchConfig {
241 max_operations_per_batch: 50,
242 batch_timeout: Duration::from_secs(10),
243 continue_on_error: true,
244 optimize_packet_packing: true,
245 },
246 connection_pool: ConnectionPoolConfig {
247 initial_size: 2,
248 max_size: 10,
249 growth_increment: 2,
250 idle_timeout: Duration::from_secs(300),
251 cleanup_interval: Duration::from_secs(60),
252 },
253 memory_limits: MemoryLimits {
254 max_memory_mb: 100,
255 warning_threshold_mb: 80,
256 enable_monitoring: true,
257 },
258 },
259 monitoring: MonitoringConfig {
260 enabled: true,
261 collection_interval: Duration::from_secs(30),
262 health_check_interval: Duration::from_secs(60),
263 retention_period: Duration::from_secs(86400), enable_profiling: false,
265 alert_thresholds: AlertThresholds {
266 error_rate_threshold: 0.05,
267 latency_threshold_ms: 1000.0,
268 memory_threshold_mb: 80,
269 connection_failure_threshold: 5,
270 },
271 },
272 security: SecurityConfig {
273 enable_encryption: false,
274 validate_connections: true,
275 validate_inputs: true,
276 rate_limiting: RateLimitingConfig {
277 enabled: true,
278 max_requests_per_second: 100,
279 burst_capacity: 200,
280 },
281 },
282 logging: LoggingConfig {
283 level: LogLevel::Info,
284 format: LogFormat::Json,
285 file_path: Some("logs/ethernet_ip.log".to_string()),
286 enable_console: true,
287 enable_structured: true,
288 rotation: LogRotationConfig {
289 enabled: true,
290 max_file_size_mb: 100,
291 max_files: 10,
292 schedule: LogRotationSchedule::Daily,
293 },
294 },
295 plc_settings: HashMap::new(),
296 }
297 }
298}
299
300#[expect(
301 deprecated,
302 reason = "CODEX-AQ keeps ProductionConfig compatibility until 2.0 removal"
303)]
304impl ProductionConfig {
305 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
307 let content = fs::read_to_string(path)?;
308 let config: ProductionConfig =
309 toml::from_str(&content).map_err(|e| EtherNetIpError::Other(e.to_string()))?;
310 Ok(config)
311 }
312
313 pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
315 let content =
316 toml::to_string_pretty(self).map_err(|e| EtherNetIpError::Other(e.to_string()))?;
317 fs::write(path, content)?;
318 Ok(())
319 }
320
321 pub fn validate(&self) -> std::result::Result<(), Vec<String>> {
323 let mut errors = Vec::new();
324
325 if self.connection.connection_timeout.as_secs() == 0 {
327 errors.push("Connection timeout must be greater than 0".to_string());
328 }
329
330 if self.connection.max_connections == 0 {
331 errors.push("Maximum connections must be greater than 0".to_string());
332 }
333
334 if self.performance.max_packet_size < 100 {
336 errors.push("Maximum packet size must be at least 100 bytes".to_string());
337 }
338
339 if self.performance.batch_config.max_operations_per_batch == 0 {
340 errors.push("Maximum operations per batch must be greater than 0".to_string());
341 }
342
343 if self.monitoring.collection_interval.as_secs() == 0 {
345 errors.push("Collection interval must be greater than 0".to_string());
346 }
347
348 if self.security.rate_limiting.enabled
350 && self.security.rate_limiting.max_requests_per_second == 0
351 {
352 errors.push(
353 "Max requests per second must be greater than 0 when rate limiting is enabled"
354 .to_string(),
355 );
356 }
357
358 if errors.is_empty() {
359 Ok(())
360 } else {
361 Err(errors)
362 }
363 }
364
365 pub fn get_plc_config(&self, plc_address: &str) -> Option<&PlcSpecificConfig> {
367 self.plc_settings.get(plc_address)
368 }
369
370 pub fn set_plc_config(&mut self, plc_address: String, config: PlcSpecificConfig) {
372 self.plc_settings.insert(plc_address, config);
373 }
374
375 pub fn development() -> Self {
377 let mut config = Self::default();
378 config.logging.level = LogLevel::Debug;
379 config.monitoring.enabled = false;
380 config.security.rate_limiting.enabled = false;
381 config.performance.memory_limits.enable_monitoring = false;
382 config
383 }
384
385 pub fn production() -> Self {
387 let mut config = Self::default();
388 config.logging.level = LogLevel::Info;
389 config.monitoring.enabled = true;
390 config.security.rate_limiting.enabled = true;
391 config.performance.memory_limits.enable_monitoring = true;
392 config.performance.memory_limits.max_memory_mb = 500;
393 config
394 }
395}