use super::connection::{Connection, ConnectionState, ConnectionStats};
use crate::error::{Result, TransportError};
use crate::log_transport;
use crate::logging::LogLevel;
use crate::protocol::knxip::{KnxIpFrame, ServiceType};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::net::UdpSocket;
pub struct RoutingConnection {
socket: Arc<UdpSocket>,
multicast_addr: SocketAddr,
local_addr: SocketAddr,
state: Arc<std::sync::RwLock<ConnectionState>>,
stats: Arc<std::sync::RwLock<ConnectionStats>>,
established_at: Option<Instant>,
routing_busy_count: Arc<std::sync::RwLock<u32>>,
lost_message_count: Arc<std::sync::RwLock<u32>>,
}
impl RoutingConnection {
pub const MULTICAST_ADDR: Ipv4Addr = Ipv4Addr::new(224, 0, 23, 12);
pub const MULTICAST_PORT: u16 = 3671;
pub async fn new(local_addr: Option<IpAddr>) -> Result<Self> {
let bind_addr = match local_addr {
Some(addr) => SocketAddr::new(addr, Self::MULTICAST_PORT),
None => SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), Self::MULTICAST_PORT),
};
let socket = UdpSocket::bind(bind_addr)
.await
.map_err(|e| TransportError::SocketError {
operation: "bind".to_string(),
source: e,
})?;
let actual_local_addr = socket
.local_addr()
.map_err(|e| TransportError::SocketError {
operation: "get_local_addr".to_string(),
source: e,
})?;
let multicast_addr =
SocketAddr::new(IpAddr::V4(Self::MULTICAST_ADDR), Self::MULTICAST_PORT);
socket
.join_multicast_v4(Self::MULTICAST_ADDR, Ipv4Addr::UNSPECIFIED)
.map_err(|e| TransportError::SocketError {
operation: "join_multicast".to_string(),
source: e,
})?;
Ok(Self {
socket: Arc::new(socket),
multicast_addr,
local_addr: actual_local_addr,
state: Arc::new(std::sync::RwLock::new(ConnectionState::Connected)),
stats: Arc::new(std::sync::RwLock::new(ConnectionStats::default())),
established_at: Some(Instant::now()),
routing_busy_count: Arc::new(std::sync::RwLock::new(0)),
lost_message_count: Arc::new(std::sync::RwLock::new(0)),
})
}
pub async fn new_with_interface(interface_addr: Ipv4Addr) -> Result<Self> {
let bind_addr = SocketAddr::new(IpAddr::V4(interface_addr), Self::MULTICAST_PORT);
let socket = UdpSocket::bind(bind_addr)
.await
.map_err(|e| TransportError::SocketError {
operation: "bind".to_string(),
source: e,
})?;
let actual_local_addr = socket
.local_addr()
.map_err(|e| TransportError::SocketError {
operation: "get_local_addr".to_string(),
source: e,
})?;
let multicast_addr =
SocketAddr::new(IpAddr::V4(Self::MULTICAST_ADDR), Self::MULTICAST_PORT);
socket
.join_multicast_v4(Self::MULTICAST_ADDR, interface_addr)
.map_err(|e| TransportError::SocketError {
operation: "join_multicast_with_interface".to_string(),
source: e,
})?;
Ok(Self {
socket: Arc::new(socket),
multicast_addr,
local_addr: actual_local_addr,
state: Arc::new(std::sync::RwLock::new(ConnectionState::Connected)),
stats: Arc::new(std::sync::RwLock::new(ConnectionStats::default())),
established_at: Some(Instant::now()),
routing_busy_count: Arc::new(std::sync::RwLock::new(0)),
lost_message_count: Arc::new(std::sync::RwLock::new(0)),
})
}
pub async fn process_routing_message(&self, frame_data: &[u8]) -> Result<Vec<u8>> {
let frame =
KnxIpFrame::parse(frame_data).map_err(|e| TransportError::InvalidConfiguration {
details: format!("Failed to parse routing frame: {e}"),
})?;
match frame.header.service_type {
ServiceType::RoutingIndication => {
Ok(frame.body)
}
ServiceType::RoutingLostMessage => {
self.handle_routing_lost_message(&frame.body);
Ok(Vec::new())
}
ServiceType::RoutingBusy => {
self.handle_routing_busy(&frame.body).await?;
Ok(Vec::new())
}
_ => {
Ok(frame.body)
}
}
}
fn handle_routing_lost_message(&self, _body: &[u8]) {
if let Ok(mut count) = self.lost_message_count.write() {
*count += 1;
}
if let Ok(mut stats) = self.stats.write() {
stats.recv_errors += 1;
stats.last_error = Some("Routing lost message received".to_string());
}
log_transport!(
LogLevel::Warn,
"KNX/IP routing lost message indication received"
);
}
async fn handle_routing_busy(&self, body: &[u8]) -> Result<()> {
let device_state = if body.len() >= 2 {
u16::from_be_bytes([body[0], body[1]])
} else {
0
};
let wait_time = if body.len() >= 4 {
u16::from_be_bytes([body[2], body[3]])
} else {
100 };
if let Ok(mut count) = self.routing_busy_count.write() {
*count += 1;
}
if let Ok(mut stats) = self.stats.write() {
stats.recv_errors += 1;
stats.last_error = Some(format!(
"Routing busy: device_state=0x{device_state:04X}, wait_time={wait_time}ms"
));
}
log_transport!(
LogLevel::Warn,
"KNX/IP routing busy indication received (device_state=0x{device_state:04X}, wait_time={wait_time}ms)"
);
if wait_time > 0 {
tokio::time::sleep(Duration::from_millis(u64::from(wait_time))).await;
}
Ok(())
}
pub async fn send_routing_indication(&self, cemi_data: &[u8]) -> Result<()> {
let frame = KnxIpFrame::new(ServiceType::RoutingIndication, cemi_data.to_vec());
let frame_data = frame.serialize();
self.socket
.send_to(&frame_data, self.multicast_addr)
.await
.map_err(|e| {
if let Ok(mut stats) = self.stats.write() {
stats.send_errors += 1;
stats.last_error = Some(e.to_string());
}
TransportError::SocketError {
operation: "send_routing_indication".to_string(),
source: e,
}
})?;
if let Ok(mut stats) = self.stats.write() {
stats.frames_sent += 1;
}
Ok(())
}
#[must_use]
pub fn routing_stats(&self) -> RoutingStats {
let busy_count = self.routing_busy_count.read().map_or(0, |c| *c);
let lost_count = self.lost_message_count.read().map_or(0, |c| *c);
RoutingStats {
routing_busy_count: busy_count,
lost_message_count: lost_count,
uptime: self.uptime(),
}
}
#[must_use]
pub fn uptime(&self) -> Option<Duration> {
self.established_at.map(|start| start.elapsed())
}
#[must_use]
pub fn is_connected(&self) -> bool {
matches!(self.state(), ConnectionState::Connected)
}
#[must_use]
pub fn local_addr(&self) -> SocketAddr {
self.local_addr
}
#[must_use]
pub fn multicast_addr(&self) -> SocketAddr {
self.multicast_addr
}
}
#[async_trait::async_trait]
impl Connection for RoutingConnection {
async fn send(&self, frame: &[u8]) -> Result<()> {
if !self.is_connected() {
return Err(TransportError::ConnectionClosed.into());
}
self.send_routing_indication(frame).await
}
async fn recv(&self) -> Result<Vec<u8>> {
if !self.is_connected() {
return Err(TransportError::ConnectionClosed.into());
}
let mut buf = vec![0u8; 1024];
let (len, _addr) = self.socket.recv_from(&mut buf).await.map_err(|e| {
if let Ok(mut stats) = self.stats.write() {
stats.recv_errors += 1;
stats.last_error = Some(e.to_string());
}
TransportError::SocketError {
operation: "recv_from".to_string(),
source: e,
}
})?;
buf.truncate(len);
let cemi_data = self.process_routing_message(&buf).await?;
if !cemi_data.is_empty()
&& let Ok(mut stats) = self.stats.write()
{
stats.frames_received += 1;
}
Ok(cemi_data)
}
async fn close(&self) -> Result<()> {
if let Ok(mut state) = self.state.write() {
*state = ConnectionState::Disconnecting;
}
let result = self
.socket
.leave_multicast_v4(Self::MULTICAST_ADDR, Ipv4Addr::UNSPECIFIED);
if let Ok(mut state) = self.state.write() {
*state = ConnectionState::Disconnected;
}
result.map_err(|e| TransportError::SocketError {
operation: "leave_multicast".to_string(),
source: e,
})?;
Ok(())
}
fn state(&self) -> ConnectionState {
*self.state.read().unwrap()
}
fn stats(&self) -> ConnectionStats {
let mut stats = self.stats.read().unwrap().clone();
if let Some(uptime) = self.uptime() {
stats.uptime_seconds = uptime.as_secs();
}
stats
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
#[derive(Debug, Clone, Default)]
pub struct RoutingStats {
pub routing_busy_count: u32,
pub lost_message_count: u32,
pub uptime: Option<Duration>,
}