use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use std::time::Duration;
use serde::{Deserialize, Serialize};
use crate::address::IndividualAddress;
use crate::error_tracker::SendErrorTrackerConfig;
use crate::filter::FilterChainConfig;
use crate::group_cache::GroupValueCacheConfig;
use crate::heartbeat::HeartbeatSchedulerConfig;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KnxServerConfig {
#[serde(default = "default_bind_addr")]
pub bind_addr: SocketAddr,
#[serde(default = "default_multicast_addr")]
pub multicast_addr: SocketAddr,
#[serde(default = "default_individual_address")]
pub individual_address: IndividualAddress,
#[serde(default = "default_device_name")]
pub device_name: String,
#[serde(default = "default_serial_number")]
pub serial_number: [u8; 6],
#[serde(default = "default_mac_address")]
pub mac_address: [u8; 6],
#[serde(default = "default_max_connections")]
pub max_connections: usize,
#[serde(default = "default_heartbeat_interval_secs")]
pub heartbeat_interval_secs: u64,
#[serde(default = "default_connection_timeout_secs")]
pub connection_timeout_secs: u64,
#[serde(default)]
pub routing_enabled: bool,
#[serde(default = "default_true")]
pub tunneling_enabled: bool,
#[serde(default)]
pub device_management_enabled: bool,
#[serde(default)]
pub tunnel_behavior: TunnelBehaviorConfig,
}
fn default_bind_addr() -> SocketAddr {
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 3671))
}
fn default_multicast_addr() -> SocketAddr {
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(224, 0, 23, 12), 3671))
}
fn default_individual_address() -> IndividualAddress {
IndividualAddress::new(1, 1, 1)
}
fn default_device_name() -> String {
"OTSim KNX Simulator".to_string()
}
fn default_serial_number() -> [u8; 6] {
[0x00, 0x00, 0x00, 0x00, 0x00, 0x01]
}
fn default_mac_address() -> [u8; 6] {
[0x00, 0x00, 0x00, 0x00, 0x00, 0x01]
}
fn default_max_connections() -> usize {
256
}
fn default_heartbeat_interval_secs() -> u64 {
60
}
fn default_connection_timeout_secs() -> u64 {
120
}
fn default_true() -> bool {
true
}
impl Default for KnxServerConfig {
fn default() -> Self {
Self {
bind_addr: default_bind_addr(),
multicast_addr: default_multicast_addr(),
individual_address: default_individual_address(),
device_name: default_device_name(),
serial_number: default_serial_number(),
mac_address: default_mac_address(),
max_connections: default_max_connections(),
heartbeat_interval_secs: default_heartbeat_interval_secs(),
connection_timeout_secs: default_connection_timeout_secs(),
routing_enabled: false,
tunneling_enabled: true,
device_management_enabled: false,
tunnel_behavior: TunnelBehaviorConfig::default(),
}
}
}
impl KnxServerConfig {
pub fn with_bind_addr(mut self, addr: SocketAddr) -> Self {
self.bind_addr = addr;
self
}
pub fn with_individual_address(mut self, addr: IndividualAddress) -> Self {
self.individual_address = addr;
self
}
pub fn with_device_name(mut self, name: impl Into<String>) -> Self {
self.device_name = name.into();
self
}
pub fn with_max_connections(mut self, max: usize) -> Self {
self.max_connections = max;
self
}
pub fn with_routing(mut self, enabled: bool) -> Self {
self.routing_enabled = enabled;
self
}
pub fn heartbeat_interval(&self) -> Duration {
Duration::from_secs(self.heartbeat_interval_secs)
}
pub fn connection_timeout(&self) -> Duration {
Duration::from_secs(self.connection_timeout_secs)
}
pub fn validate(&self) -> Result<(), String> {
if self.max_connections == 0 {
return Err("max_connections must be greater than 0".to_string());
}
if self.heartbeat_interval_secs == 0 {
return Err("heartbeat_interval_secs must be greater than 0".to_string());
}
if self.device_name.is_empty() {
return Err("device_name cannot be empty".to_string());
}
if self.device_name.len() > 30 {
return Err("device_name cannot exceed 30 characters".to_string());
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KnxDeviceConfig {
pub id: String,
pub name: String,
#[serde(default)]
pub description: String,
pub individual_address: IndividualAddress,
#[serde(default)]
pub group_objects: Vec<GroupObjectConfig>,
#[serde(default = "default_tick_interval_ms")]
pub tick_interval_ms: u64,
}
fn default_tick_interval_ms() -> u64 {
100
}
impl Default for KnxDeviceConfig {
fn default() -> Self {
Self {
id: "knx-device-1".to_string(),
name: "KNX Device".to_string(),
description: String::new(),
individual_address: IndividualAddress::new(1, 1, 1),
group_objects: Vec::new(),
tick_interval_ms: default_tick_interval_ms(),
}
}
}
impl KnxDeviceConfig {
pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
Self {
id: id.into(),
name: name.into(),
..Default::default()
}
}
pub fn with_individual_address(mut self, addr: IndividualAddress) -> Self {
self.individual_address = addr;
self
}
pub fn with_group_object(mut self, obj: GroupObjectConfig) -> Self {
self.group_objects.push(obj);
self
}
pub fn tick_interval(&self) -> Duration {
Duration::from_millis(self.tick_interval_ms)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GroupObjectConfig {
pub address: String,
pub name: String,
pub dpt: String,
#[serde(default)]
pub flags: GroupObjectFlagsConfig,
#[serde(default)]
pub initial_value: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GroupObjectFlagsConfig {
#[serde(default = "default_true")]
pub communication: bool,
#[serde(default = "default_true")]
pub read: bool,
#[serde(default = "default_true")]
pub write: bool,
#[serde(default = "default_true")]
pub transmit: bool,
#[serde(default = "default_true")]
pub update: bool,
}
impl Default for GroupObjectFlagsConfig {
fn default() -> Self {
Self {
communication: true,
read: true,
write: true,
transmit: true,
update: true,
}
}
}
impl GroupObjectFlagsConfig {
pub fn read_only() -> Self {
Self {
communication: true,
read: true,
write: false,
transmit: true,
update: false,
}
}
pub fn write_only() -> Self {
Self {
communication: true,
read: false,
write: true,
transmit: false,
update: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TunnelConfig {
#[serde(default)]
pub layer: KnxLayerConfig,
#[serde(default = "default_request_timeout_ms")]
pub request_timeout_ms: u64,
#[serde(default = "default_max_retries")]
pub max_retries: u8,
#[serde(default = "default_ack_timeout_ms")]
pub ack_timeout_ms: u64,
#[serde(default = "default_confirmation_timeout_ms")]
pub confirmation_timeout_ms: u64,
#[serde(default = "default_send_error_threshold")]
pub send_error_threshold: u32,
#[serde(default = "default_fatal_desync_threshold")]
pub fatal_desync_threshold: u8,
}
fn default_request_timeout_ms() -> u64 {
1000
}
fn default_max_retries() -> u8 {
3
}
fn default_ack_timeout_ms() -> u64 {
1000
}
fn default_confirmation_timeout_ms() -> u64 {
3000
}
fn default_send_error_threshold() -> u32 {
5
}
fn default_fatal_desync_threshold() -> u8 {
5
}
impl Default for TunnelConfig {
fn default() -> Self {
Self {
layer: KnxLayerConfig::default(),
request_timeout_ms: default_request_timeout_ms(),
max_retries: default_max_retries(),
ack_timeout_ms: default_ack_timeout_ms(),
confirmation_timeout_ms: default_confirmation_timeout_ms(),
send_error_threshold: default_send_error_threshold(),
fatal_desync_threshold: default_fatal_desync_threshold(),
}
}
}
impl TunnelConfig {
pub fn request_timeout(&self) -> Duration {
Duration::from_millis(self.request_timeout_ms)
}
pub fn ack_timeout(&self) -> Duration {
Duration::from_millis(self.ack_timeout_ms)
}
pub fn confirmation_timeout(&self) -> Duration {
Duration::from_millis(self.confirmation_timeout_ms)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TunnelBehaviorConfig {
#[serde(default = "default_true")]
pub ldata_con_enabled: bool,
#[serde(default = "default_confirmation_success_rate")]
pub confirmation_success_rate: f64,
#[serde(default = "default_bus_delivery_delay_ms")]
pub bus_delivery_delay_ms: u64,
#[serde(default = "default_true")]
pub sequence_validation_enabled: bool,
#[serde(default = "default_true")]
pub ack_tracking_enabled: bool,
#[serde(default)]
pub heartbeat_status_override: Option<u8>,
#[serde(default = "default_server_ack_timeout_ms")]
pub server_ack_timeout_ms: u64,
#[serde(default = "default_max_retries")]
pub server_max_retries: u8,
#[serde(default)]
pub bus_monitor_enabled: bool,
#[serde(default = "default_true")]
pub ldata_ind_broadcast_enabled: bool,
#[serde(default = "default_true")]
pub property_service_enabled: bool,
#[serde(default = "default_true")]
pub reset_service_enabled: bool,
#[serde(default)]
pub flow_control: FilterChainConfig,
#[serde(default)]
pub heartbeat_scheduler: HeartbeatSchedulerConfig,
#[serde(default)]
pub group_value_cache: GroupValueCacheConfig,
#[serde(default)]
pub send_error_tracker: SendErrorTrackerConfig,
}
fn default_confirmation_success_rate() -> f64 {
1.0
}
fn default_bus_delivery_delay_ms() -> u64 {
0
}
fn default_server_ack_timeout_ms() -> u64 {
1000
}
impl Default for TunnelBehaviorConfig {
fn default() -> Self {
Self {
ldata_con_enabled: true,
confirmation_success_rate: default_confirmation_success_rate(),
bus_delivery_delay_ms: default_bus_delivery_delay_ms(),
sequence_validation_enabled: true,
ack_tracking_enabled: true,
heartbeat_status_override: None,
server_ack_timeout_ms: default_server_ack_timeout_ms(),
server_max_retries: default_max_retries(),
bus_monitor_enabled: false,
ldata_ind_broadcast_enabled: true,
property_service_enabled: true,
reset_service_enabled: true,
flow_control: FilterChainConfig::default(),
heartbeat_scheduler: HeartbeatSchedulerConfig::default(),
group_value_cache: GroupValueCacheConfig::default(),
send_error_tracker: SendErrorTrackerConfig::default(),
}
}
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum KnxLayerConfig {
#[default]
LinkLayer,
Raw,
BusMonitor,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_server_config_default() {
let config = KnxServerConfig::default();
assert_eq!(config.bind_addr.port(), 3671);
assert_eq!(config.max_connections, 256);
assert!(config.tunneling_enabled);
}
#[test]
fn test_server_config_builder() {
let config = KnxServerConfig::default()
.with_bind_addr("192.168.1.100:3671".parse().unwrap())
.with_max_connections(100)
.with_device_name("Test Server");
assert_eq!(config.device_name, "Test Server");
assert_eq!(config.max_connections, 100);
}
#[test]
fn test_server_config_validation() {
let config = KnxServerConfig::default();
assert!(config.validate().is_ok());
let invalid = KnxServerConfig {
max_connections: 0,
..Default::default()
};
assert!(invalid.validate().is_err());
}
#[test]
fn test_device_config() {
let config = KnxDeviceConfig::new("dev-1", "Living Room Controller")
.with_individual_address(IndividualAddress::new(1, 2, 3));
assert_eq!(config.id, "dev-1");
assert_eq!(config.individual_address.to_string(), "1.2.3");
}
#[test]
fn test_group_object_flags() {
let ro = GroupObjectFlagsConfig::read_only();
assert!(ro.read);
assert!(!ro.write);
let wo = GroupObjectFlagsConfig::write_only();
assert!(!wo.read);
assert!(wo.write);
}
}