Skip to main content

mabi_knx/
config.rs

1//! KNX configuration types.
2//!
3//! This module provides configuration structures for KNXnet/IP server and devices.
4
5use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
6use std::time::Duration;
7
8use serde::{Deserialize, Serialize};
9
10use crate::address::IndividualAddress;
11use crate::error_tracker::SendErrorTrackerConfig;
12use crate::filter::FilterChainConfig;
13use crate::group_cache::GroupValueCacheConfig;
14use crate::heartbeat::HeartbeatSchedulerConfig;
15
16// ============================================================================
17// Server Configuration
18// ============================================================================
19
20/// KNXnet/IP server configuration.
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct KnxServerConfig {
23    /// UDP bind address for KNXnet/IP.
24    #[serde(default = "default_bind_addr")]
25    pub bind_addr: SocketAddr,
26
27    /// Multicast address for KNX routing.
28    #[serde(default = "default_multicast_addr")]
29    pub multicast_addr: SocketAddr,
30
31    /// Individual address of this KNX device.
32    #[serde(default = "default_individual_address")]
33    pub individual_address: IndividualAddress,
34
35    /// Device name for discovery.
36    #[serde(default = "default_device_name")]
37    pub device_name: String,
38
39    /// Device serial number.
40    #[serde(default = "default_serial_number")]
41    pub serial_number: [u8; 6],
42
43    /// MAC address (for HPAI).
44    #[serde(default = "default_mac_address")]
45    pub mac_address: [u8; 6],
46
47    /// Maximum number of simultaneous tunnel connections.
48    #[serde(default = "default_max_connections")]
49    pub max_connections: usize,
50
51    /// Connection heartbeat interval in seconds.
52    #[serde(default = "default_heartbeat_interval_secs")]
53    pub heartbeat_interval_secs: u64,
54
55    /// Connection timeout in seconds.
56    #[serde(default = "default_connection_timeout_secs")]
57    pub connection_timeout_secs: u64,
58
59    /// Enable routing mode.
60    #[serde(default)]
61    pub routing_enabled: bool,
62
63    /// Enable tunneling mode.
64    #[serde(default = "default_true")]
65    pub tunneling_enabled: bool,
66
67    /// Enable device management mode.
68    #[serde(default)]
69    pub device_management_enabled: bool,
70
71    /// Tunnel behavior configuration for protocol simulation fidelity.
72    #[serde(default)]
73    pub tunnel_behavior: TunnelBehaviorConfig,
74}
75
76fn default_bind_addr() -> SocketAddr {
77    SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 3671))
78}
79
80fn default_multicast_addr() -> SocketAddr {
81    // KNX standard multicast address: 224.0.23.12:3671
82    SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(224, 0, 23, 12), 3671))
83}
84
85fn default_individual_address() -> IndividualAddress {
86    IndividualAddress::new(1, 1, 1)
87}
88
89fn default_device_name() -> String {
90    "OTSim KNX Simulator".to_string()
91}
92
93fn default_serial_number() -> [u8; 6] {
94    [0x00, 0x00, 0x00, 0x00, 0x00, 0x01]
95}
96
97fn default_mac_address() -> [u8; 6] {
98    [0x00, 0x00, 0x00, 0x00, 0x00, 0x01]
99}
100
101fn default_max_connections() -> usize {
102    256
103}
104
105fn default_heartbeat_interval_secs() -> u64 {
106    60
107}
108
109fn default_connection_timeout_secs() -> u64 {
110    120
111}
112
113fn default_true() -> bool {
114    true
115}
116
117impl Default for KnxServerConfig {
118    fn default() -> Self {
119        Self {
120            bind_addr: default_bind_addr(),
121            multicast_addr: default_multicast_addr(),
122            individual_address: default_individual_address(),
123            device_name: default_device_name(),
124            serial_number: default_serial_number(),
125            mac_address: default_mac_address(),
126            max_connections: default_max_connections(),
127            heartbeat_interval_secs: default_heartbeat_interval_secs(),
128            connection_timeout_secs: default_connection_timeout_secs(),
129            routing_enabled: false,
130            tunneling_enabled: true,
131            device_management_enabled: false,
132            tunnel_behavior: TunnelBehaviorConfig::default(),
133        }
134    }
135}
136
137impl KnxServerConfig {
138    /// Create a new server config with the specified bind address.
139    pub fn with_bind_addr(mut self, addr: SocketAddr) -> Self {
140        self.bind_addr = addr;
141        self
142    }
143
144    /// Set the individual address.
145    pub fn with_individual_address(mut self, addr: IndividualAddress) -> Self {
146        self.individual_address = addr;
147        self
148    }
149
150    /// Set the device name.
151    pub fn with_device_name(mut self, name: impl Into<String>) -> Self {
152        self.device_name = name.into();
153        self
154    }
155
156    /// Set maximum connections.
157    pub fn with_max_connections(mut self, max: usize) -> Self {
158        self.max_connections = max;
159        self
160    }
161
162    /// Enable routing mode.
163    pub fn with_routing(mut self, enabled: bool) -> Self {
164        self.routing_enabled = enabled;
165        self
166    }
167
168    /// Get heartbeat interval as Duration.
169    pub fn heartbeat_interval(&self) -> Duration {
170        Duration::from_secs(self.heartbeat_interval_secs)
171    }
172
173    /// Get connection timeout as Duration.
174    pub fn connection_timeout(&self) -> Duration {
175        Duration::from_secs(self.connection_timeout_secs)
176    }
177
178    /// Validate configuration.
179    pub fn validate(&self) -> Result<(), String> {
180        if self.max_connections == 0 {
181            return Err("max_connections must be greater than 0".to_string());
182        }
183        if self.heartbeat_interval_secs == 0 {
184            return Err("heartbeat_interval_secs must be greater than 0".to_string());
185        }
186        if self.device_name.is_empty() {
187            return Err("device_name cannot be empty".to_string());
188        }
189        if self.device_name.len() > 30 {
190            return Err("device_name cannot exceed 30 characters".to_string());
191        }
192        Ok(())
193    }
194}
195
196// ============================================================================
197// Device Configuration
198// ============================================================================
199
200/// KNX device configuration.
201#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct KnxDeviceConfig {
203    /// Device ID.
204    pub id: String,
205
206    /// Device name.
207    pub name: String,
208
209    /// Device description.
210    #[serde(default)]
211    pub description: String,
212
213    /// Individual address.
214    pub individual_address: IndividualAddress,
215
216    /// Group objects configuration.
217    #[serde(default)]
218    pub group_objects: Vec<GroupObjectConfig>,
219
220    /// Simulation tick interval in milliseconds.
221    #[serde(default = "default_tick_interval_ms")]
222    pub tick_interval_ms: u64,
223}
224
225fn default_tick_interval_ms() -> u64 {
226    100
227}
228
229impl Default for KnxDeviceConfig {
230    fn default() -> Self {
231        Self {
232            id: "knx-device-1".to_string(),
233            name: "KNX Device".to_string(),
234            description: String::new(),
235            individual_address: IndividualAddress::new(1, 1, 1),
236            group_objects: Vec::new(),
237            tick_interval_ms: default_tick_interval_ms(),
238        }
239    }
240}
241
242impl KnxDeviceConfig {
243    /// Create a new device config.
244    pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
245        Self {
246            id: id.into(),
247            name: name.into(),
248            ..Default::default()
249        }
250    }
251
252    /// Set the individual address.
253    pub fn with_individual_address(mut self, addr: IndividualAddress) -> Self {
254        self.individual_address = addr;
255        self
256    }
257
258    /// Add a group object.
259    pub fn with_group_object(mut self, obj: GroupObjectConfig) -> Self {
260        self.group_objects.push(obj);
261        self
262    }
263
264    /// Get tick interval as Duration.
265    pub fn tick_interval(&self) -> Duration {
266        Duration::from_millis(self.tick_interval_ms)
267    }
268}
269
270// ============================================================================
271// Group Object Configuration
272// ============================================================================
273
274/// Group object configuration.
275#[derive(Debug, Clone, Serialize, Deserialize)]
276pub struct GroupObjectConfig {
277    /// Group address (e.g., "1/2/3").
278    pub address: String,
279
280    /// Object name.
281    pub name: String,
282
283    /// Datapoint type (e.g., "DPT1.001", "DPT9.001").
284    pub dpt: String,
285
286    /// Object flags.
287    #[serde(default)]
288    pub flags: GroupObjectFlagsConfig,
289
290    /// Initial value (JSON).
291    #[serde(default)]
292    pub initial_value: Option<serde_json::Value>,
293}
294
295/// Group object flags configuration.
296#[derive(Debug, Clone, Serialize, Deserialize)]
297pub struct GroupObjectFlagsConfig {
298    /// Communication enabled.
299    #[serde(default = "default_true")]
300    pub communication: bool,
301
302    /// Read enabled.
303    #[serde(default = "default_true")]
304    pub read: bool,
305
306    /// Write enabled.
307    #[serde(default = "default_true")]
308    pub write: bool,
309
310    /// Transmit on change.
311    #[serde(default = "default_true")]
312    pub transmit: bool,
313
314    /// Update on receive.
315    #[serde(default = "default_true")]
316    pub update: bool,
317}
318
319impl Default for GroupObjectFlagsConfig {
320    fn default() -> Self {
321        Self {
322            communication: true,
323            read: true,
324            write: true,
325            transmit: true,
326            update: true,
327        }
328    }
329}
330
331impl GroupObjectFlagsConfig {
332    /// Create read-only flags.
333    pub fn read_only() -> Self {
334        Self {
335            communication: true,
336            read: true,
337            write: false,
338            transmit: true,
339            update: false,
340        }
341    }
342
343    /// Create write-only flags.
344    pub fn write_only() -> Self {
345        Self {
346            communication: true,
347            read: false,
348            write: true,
349            transmit: false,
350            update: true,
351        }
352    }
353}
354
355// ============================================================================
356// Connection Configuration
357// ============================================================================
358
359/// Tunnel connection configuration.
360#[derive(Debug, Clone, Serialize, Deserialize)]
361pub struct TunnelConfig {
362    /// KNX layer type.
363    #[serde(default)]
364    pub layer: KnxLayerConfig,
365
366    /// Request timeout in milliseconds.
367    #[serde(default = "default_request_timeout_ms")]
368    pub request_timeout_ms: u64,
369
370    /// Maximum retry count.
371    #[serde(default = "default_max_retries")]
372    pub max_retries: u8,
373
374    /// ACK timeout in milliseconds (knxd default: 1000ms).
375    #[serde(default = "default_ack_timeout_ms")]
376    pub ack_timeout_ms: u64,
377
378    /// L_Data.con confirmation timeout in milliseconds (default: 3000ms).
379    #[serde(default = "default_confirmation_timeout_ms")]
380    pub confirmation_timeout_ms: u64,
381
382    /// Consecutive send error threshold for tunnel restart (knxd: 5).
383    #[serde(default = "default_send_error_threshold")]
384    pub send_error_threshold: u32,
385
386    /// Fatal desync threshold — sequence distance triggering tunnel restart (knxd: 5).
387    #[serde(default = "default_fatal_desync_threshold")]
388    pub fatal_desync_threshold: u8,
389}
390
391fn default_request_timeout_ms() -> u64 {
392    1000
393}
394
395fn default_max_retries() -> u8 {
396    3
397}
398
399fn default_ack_timeout_ms() -> u64 {
400    1000
401}
402
403fn default_confirmation_timeout_ms() -> u64 {
404    3000
405}
406
407fn default_send_error_threshold() -> u32 {
408    5
409}
410
411fn default_fatal_desync_threshold() -> u8 {
412    5
413}
414
415impl Default for TunnelConfig {
416    fn default() -> Self {
417        Self {
418            layer: KnxLayerConfig::default(),
419            request_timeout_ms: default_request_timeout_ms(),
420            max_retries: default_max_retries(),
421            ack_timeout_ms: default_ack_timeout_ms(),
422            confirmation_timeout_ms: default_confirmation_timeout_ms(),
423            send_error_threshold: default_send_error_threshold(),
424            fatal_desync_threshold: default_fatal_desync_threshold(),
425        }
426    }
427}
428
429impl TunnelConfig {
430    /// Get request timeout as Duration.
431    pub fn request_timeout(&self) -> Duration {
432        Duration::from_millis(self.request_timeout_ms)
433    }
434
435    /// Get ACK timeout as Duration.
436    pub fn ack_timeout(&self) -> Duration {
437        Duration::from_millis(self.ack_timeout_ms)
438    }
439
440    /// Get confirmation timeout as Duration.
441    pub fn confirmation_timeout(&self) -> Duration {
442        Duration::from_millis(self.confirmation_timeout_ms)
443    }
444}
445
446/// Server-side tunnel behavior configuration.
447///
448/// Controls how the simulator behaves from the gateway perspective,
449/// enabling realistic protocol testing for trap-knx clients.
450#[derive(Debug, Clone, Serialize, Deserialize)]
451pub struct TunnelBehaviorConfig {
452    /// Enable L_Data.con confirmation flow (MC=0x2E).
453    /// When true, the server sends L_Data.con after processing L_Data.req.
454    #[serde(default = "default_true")]
455    pub ldata_con_enabled: bool,
456
457    /// Base probability of L_Data.con success (0.0 - 1.0).
458    /// 1.0 = always succeed, 0.0 = always NACK.
459    #[serde(default = "default_confirmation_success_rate")]
460    pub confirmation_success_rate: f64,
461
462    /// Simulated bus delivery delay in milliseconds before sending L_Data.con.
463    #[serde(default = "default_bus_delivery_delay_ms")]
464    pub bus_delivery_delay_ms: u64,
465
466    /// Enable sequence validation with duplicate/out-of-order detection.
467    #[serde(default = "default_true")]
468    pub sequence_validation_enabled: bool,
469
470    /// Enable ACK timeout and retry tracking for server→client frames.
471    #[serde(default = "default_true")]
472    pub ack_tracking_enabled: bool,
473
474    /// Heartbeat response status override.
475    /// None = use normal behavior, Some(status) = always respond with this status.
476    #[serde(default)]
477    pub heartbeat_status_override: Option<u8>,
478
479    /// Per-connection ACK timeout in milliseconds for server→client frames.
480    #[serde(default = "default_server_ack_timeout_ms")]
481    pub server_ack_timeout_ms: u64,
482
483    /// Maximum retries for server→client frame delivery.
484    #[serde(default = "default_max_retries")]
485    pub server_max_retries: u8,
486
487    /// Enable Bus Monitor frame generation (MC=0x2B).
488    /// When true, L_Busmon.ind frames with Additional Info TLV are sent
489    /// for every bus frame to connections in BusMonitor mode.
490    #[serde(default)]
491    pub bus_monitor_enabled: bool,
492
493    /// Enable L_Data.ind broadcast to other tunnel connections.
494    /// When true, group value writes are forwarded to all other connected tunnels.
495    #[serde(default = "default_true")]
496    pub ldata_ind_broadcast_enabled: bool,
497
498    /// Enable cEMI property service handling (M_PropRead, M_PropWrite).
499    /// When true, the server processes property read/write requests and
500    /// sends appropriate confirmations.
501    #[serde(default = "default_true")]
502    pub property_service_enabled: bool,
503
504    /// Enable M_Reset handling.
505    /// When true, the server responds to M_Reset.req with M_Reset.ind.
506    #[serde(default = "default_true")]
507    pub reset_service_enabled: bool,
508
509    /// Flow control filter chain configuration.
510    /// Enables bus timing simulation, priority queuing, and circuit breaker
511    /// for realistic KNX bus behavior testing.
512    #[serde(default)]
513    pub flow_control: FilterChainConfig,
514
515    /// Heartbeat scheduler configuration.
516    /// When enabled, the scheduler determines the heartbeat response action
517    /// for each connection state request, supporting 5 action types.
518    #[serde(default)]
519    pub heartbeat_scheduler: HeartbeatSchedulerConfig,
520
521    /// Group value cache configuration.
522    /// Caches group values with TTL/LRU eviction and auto-updates on
523    /// L_Data.ind indications for consistent multi-client simulation.
524    #[serde(default)]
525    pub group_value_cache: GroupValueCacheConfig,
526
527    /// Send error tracker configuration.
528    /// Monitors consecutive and sliding window error rates for each channel,
529    /// triggering tunnel restart when thresholds are exceeded.
530    #[serde(default)]
531    pub send_error_tracker: SendErrorTrackerConfig,
532}
533
534fn default_confirmation_success_rate() -> f64 {
535    1.0
536}
537
538fn default_bus_delivery_delay_ms() -> u64 {
539    0
540}
541
542fn default_server_ack_timeout_ms() -> u64 {
543    1000
544}
545
546impl Default for TunnelBehaviorConfig {
547    fn default() -> Self {
548        Self {
549            ldata_con_enabled: true,
550            confirmation_success_rate: default_confirmation_success_rate(),
551            bus_delivery_delay_ms: default_bus_delivery_delay_ms(),
552            sequence_validation_enabled: true,
553            ack_tracking_enabled: true,
554            heartbeat_status_override: None,
555            server_ack_timeout_ms: default_server_ack_timeout_ms(),
556            server_max_retries: default_max_retries(),
557            bus_monitor_enabled: false,
558            ldata_ind_broadcast_enabled: true,
559            property_service_enabled: true,
560            reset_service_enabled: true,
561            flow_control: FilterChainConfig::default(),
562            heartbeat_scheduler: HeartbeatSchedulerConfig::default(),
563            group_value_cache: GroupValueCacheConfig::default(),
564            send_error_tracker: SendErrorTrackerConfig::default(),
565        }
566    }
567}
568
569/// KNX layer configuration.
570#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
571#[serde(rename_all = "lowercase")]
572pub enum KnxLayerConfig {
573    /// Link layer (default for tunneling).
574    #[default]
575    LinkLayer,
576    /// Raw layer.
577    Raw,
578    /// Bus monitor layer.
579    BusMonitor,
580}
581
582#[cfg(test)]
583mod tests {
584    use super::*;
585
586    #[test]
587    fn test_server_config_default() {
588        let config = KnxServerConfig::default();
589        assert_eq!(config.bind_addr.port(), 3671);
590        assert_eq!(config.max_connections, 256);
591        assert!(config.tunneling_enabled);
592    }
593
594    #[test]
595    fn test_server_config_builder() {
596        let config = KnxServerConfig::default()
597            .with_bind_addr("192.168.1.100:3671".parse().unwrap())
598            .with_max_connections(100)
599            .with_device_name("Test Server");
600
601        assert_eq!(config.device_name, "Test Server");
602        assert_eq!(config.max_connections, 100);
603    }
604
605    #[test]
606    fn test_server_config_validation() {
607        let config = KnxServerConfig::default();
608        assert!(config.validate().is_ok());
609
610        let invalid = KnxServerConfig {
611            max_connections: 0,
612            ..Default::default()
613        };
614        assert!(invalid.validate().is_err());
615    }
616
617    #[test]
618    fn test_device_config() {
619        let config = KnxDeviceConfig::new("dev-1", "Living Room Controller")
620            .with_individual_address(IndividualAddress::new(1, 2, 3));
621
622        assert_eq!(config.id, "dev-1");
623        assert_eq!(config.individual_address.to_string(), "1.2.3");
624    }
625
626    #[test]
627    fn test_group_object_flags() {
628        let ro = GroupObjectFlagsConfig::read_only();
629        assert!(ro.read);
630        assert!(!ro.write);
631
632        let wo = GroupObjectFlagsConfig::write_only();
633        assert!(!wo.read);
634        assert!(wo.write);
635    }
636}