use knust::protocol::telegram::{Direction, Priority, Telegram, TelegramType};
use knust::protocol::{Address, GroupAddress, IndividualAddress};
#[cfg(feature = "secure")]
use knust::security::SessionConfig;
#[cfg(feature = "secure")]
use knust::transport::SecurityConfig;
use knust::transport::{BackoffConfig, TcpConfig};
use knust::{ConnectionConfig, ConnectionType, Knx, KnxError};
use std::time::Duration;
use tokio::time::timeout;
fn write_telegram(
source: IndividualAddress,
destination: GroupAddress,
payload: Vec<u8>,
) -> Telegram {
Telegram {
source,
destination: Address::Group(destination),
payload,
priority: Priority::Normal,
direction: Direction::Outgoing,
telegram_type: TelegramType::GroupValueWrite,
gateway_id: None,
timestamp: std::time::SystemTime::now(),
}
}
#[tokio::test]
async fn test_end_to_end_group_communication() -> Result<(), KnxError> {
let config = ConnectionConfig {
connection_type: ConnectionType::Tunneling,
gateway_ip: Some("127.0.0.1".parse().unwrap()),
gateway_port: Some(3671),
local_ip: Some("127.0.0.1".parse().unwrap()),
individual_address: IndividualAddress::new(1, 1, 1),
security: None,
timeout_ms: 5000,
auto_reconnect: false,
reconnect_backoff: BackoffConfig::default(),
tcp_config: TcpConfig::default(),
};
let knx = Knx::new(config).await?;
let source_addr = IndividualAddress::new(1, 1, 240);
let result = timeout(
Duration::from_secs(5),
knx.send_telegram(&write_telegram(
source_addr,
GroupAddress::from_parts(1, 2, 1)?,
vec![0x01],
)),
)
.await;
match result {
Ok(Ok(())) => println!("✓ Living room light switch write sent successfully"),
Ok(Err(e)) => println!("⚠ Living room light switch write failed (expected in test): {e}"),
Err(_) => println!("⚠ Living room light switch write timed out (expected in test)"),
}
let result = timeout(
Duration::from_secs(5),
knx.send_telegram(&write_telegram(
source_addr,
GroupAddress::from_parts(1, 2, 2)?,
vec![128],
)),
)
.await;
match result {
Ok(Ok(())) => println!("✓ Living room light brightness write sent successfully"),
Ok(Err(e)) => {
println!("⚠ Living room light brightness write failed (expected in test): {e}");
}
Err(_) => println!("⚠ Living room light brightness write timed out (expected in test)"),
}
let result = timeout(
Duration::from_secs(5),
knx.send_telegram(&write_telegram(
source_addr,
GroupAddress::from_parts(1, 3, 1)?,
vec![0x01],
)),
)
.await;
match result {
Ok(Ok(())) => println!("✓ Kitchen light switch write sent successfully"),
Ok(Err(e)) => println!("⚠ Kitchen light switch write failed (expected in test): {e}"),
Err(_) => println!("⚠ Kitchen light switch write timed out (expected in test)"),
}
let result = knx
.read_group_value(GroupAddress::from_parts(2, 1, 1)?, Duration::from_secs(5))
.await;
match result {
Ok(_) => println!("✓ Temperature sensor read successfully"),
Err(e) => println!("⚠ Temperature sensor read failed (expected in test): {e}"),
}
println!("✓ End-to-end group communication test completed");
Ok(())
}
#[tokio::test]
async fn test_multi_target_coordination() -> Result<(), KnxError> {
let config = ConnectionConfig {
connection_type: ConnectionType::Routing,
timeout_ms: 5000,
auto_reconnect: false,
reconnect_backoff: BackoffConfig::default(),
tcp_config: TcpConfig::default(),
..Default::default()
};
let knx = Knx::new(config).await?;
let source_addr = IndividualAddress::new(1, 1, 240);
let addresses = vec![
GroupAddress::from_parts(1, 1, 1)?,
GroupAddress::from_parts(1, 1, 2)?,
GroupAddress::from_parts(1, 1, 3)?,
];
let mut tasks = Vec::new();
for addr in &addresses {
let knx_clone = knx.clone();
let addr = *addr;
tasks.push(tokio::spawn(async move {
timeout(
Duration::from_secs(5),
knx_clone.send_telegram(&write_telegram(source_addr, addr, vec![0x01])),
)
.await
}));
}
for (i, task) in tasks.into_iter().enumerate() {
match task.await {
Ok(Ok(Ok(()))) => println!("✓ Group address {} switched on successfully", i + 1),
Ok(Ok(Err(e))) => println!("⚠ Group address {} failed (expected): {}", i + 1, e),
Ok(Err(_)) => println!("⚠ Group address {} timed out (expected)", i + 1),
Err(e) => println!("⚠ Group address {} task failed: {}", i + 1, e),
}
}
let brightness_levels = [64u8, 128, 192];
let mut brightness_tasks = Vec::new();
for (addr, &brightness) in addresses.iter().zip(brightness_levels.iter()) {
let knx_clone = knx.clone();
let addr = *addr;
brightness_tasks.push(tokio::spawn(async move {
timeout(
Duration::from_secs(5),
knx_clone.send_telegram(&write_telegram(source_addr, addr, vec![brightness])),
)
.await
}));
}
for (i, task) in brightness_tasks.into_iter().enumerate() {
match task.await {
Ok(Ok(Ok(()))) => println!(
"✓ Group address {} brightness set to {}",
i + 1,
brightness_levels[i]
),
Ok(Ok(Err(e))) => println!(
"⚠ Group address {} brightness failed (expected): {}",
i + 1,
e
),
Ok(Err(_)) => println!("⚠ Group address {} brightness timed out (expected)", i + 1),
Err(e) => println!("⚠ Group address {} brightness task failed: {}", i + 1, e),
}
}
assert_eq!(addresses.len(), 3);
println!("✓ Multi-target coordination test completed");
Ok(())
}
#[cfg(feature = "secure")]
#[tokio::test]
async fn test_secure_communication_workflow() -> Result<(), KnxError> {
let _session_config = SessionConfig {
user_id: 1,
user_password: "test_password".to_string(),
device_auth_password: Some("device_auth".to_string()),
keepalive_interval: 60,
};
let security_config = SecurityConfig {
device_auth_password: "device_auth".to_string(),
user_password: Some("test_password".to_string()),
keyring_path: None,
session_timeout: 300,
};
let config = ConnectionConfig {
connection_type: ConnectionType::SecureTunneling,
gateway_ip: Some("127.0.0.1".parse().unwrap()),
security: Some(security_config),
timeout_ms: 10000,
auto_reconnect: false,
reconnect_backoff: BackoffConfig::default(),
tcp_config: TcpConfig::default(),
..Default::default()
};
let knx = Knx::new(config).await?;
let source_addr = IndividualAddress::new(1, 1, 240);
let secure_addr = GroupAddress::from_parts(1, 5, 1)?;
let result = timeout(Duration::from_secs(10), async {
knx.send_telegram(&write_telegram(source_addr, secure_addr, vec![0x01]))
.await?;
tokio::time::sleep(Duration::from_millis(100)).await;
knx.send_telegram(&write_telegram(source_addr, secure_addr, vec![0x00]))
.await?;
Ok::<(), KnxError>(())
})
.await;
match result {
Ok(Ok(())) => println!("✓ Secure communication workflow completed successfully"),
Ok(Err(e)) => println!("⚠ Secure communication failed (expected in test): {e}"),
Err(_) => println!("⚠ Secure communication timed out (expected in test)"),
}
println!("✓ Secure communication workflow test completed");
Ok(())
}
#[tokio::test]
async fn test_error_handling_and_recovery() -> Result<(), KnxError> {
let config = ConnectionConfig {
connection_type: ConnectionType::Tunneling,
gateway_ip: Some("192.168.255.255".parse().unwrap()), timeout_ms: 5000,
auto_reconnect: false,
reconnect_backoff: BackoffConfig::default(),
tcp_config: TcpConfig::default(),
..Default::default()
};
let result = timeout(Duration::from_secs(5), Knx::new(config)).await;
match result {
Ok(Ok(_)) => println!("⚠ Unexpected success with invalid gateway"),
Ok(Err(e)) => {
println!("✓ Expected error with invalid gateway: {e}");
let error_string = format!("{e}");
assert!(
error_string.contains("192.168.255.255")
|| error_string.contains("connection")
|| error_string.contains("transport")
);
}
Err(_) => println!("✓ Connection attempt timed out as expected"),
}
let config = ConnectionConfig {
connection_type: ConnectionType::Routing,
timeout_ms: 2000,
auto_reconnect: false,
reconnect_backoff: BackoffConfig::default(),
tcp_config: TcpConfig::default(),
..Default::default()
};
let knx = Knx::new(config).await?;
let source_addr = IndividualAddress::new(1, 1, 240);
let addr = GroupAddress::from_parts(1, 1, 1)?;
let result = timeout(
Duration::from_secs(2),
knx.send_telegram(&write_telegram(source_addr, addr, vec![0x01])),
)
.await;
match result {
Ok(Ok(())) => println!("⚠ Unexpected success without connection"),
Ok(Err(e)) => println!("✓ Expected error without connection: {e}"),
Err(_) => println!("✓ Operation timed out as expected without connection"),
}
println!("✓ Error handling and recovery test completed");
Ok(())
}
#[tokio::test]
async fn test_configuration_validation() {
let result = GroupAddress::from_parts(32, 8, 255); assert!(result.is_err(), "Should reject invalid group address");
let result = GroupAddress::from_parts(1, 2, 3);
assert!(result.is_ok(), "Should accept valid group address");
let config = ConnectionConfig {
connection_type: ConnectionType::Tunneling,
gateway_ip: None, timeout_ms: 2000,
auto_reconnect: false,
reconnect_backoff: BackoffConfig::default(),
tcp_config: TcpConfig::default(),
..Default::default()
};
let result = timeout(Duration::from_secs(2), Knx::new(config)).await;
match result {
Ok(Ok(_)) => println!("⚠ Unexpected success with incomplete config"),
Ok(Err(e)) => println!("✓ Expected error with incomplete config: {e}"),
Err(_) => println!("✓ Configuration validation timed out as expected"),
}
println!("✓ Configuration validation test completed");
}
#[tokio::test]
async fn test_resource_cleanup() -> Result<(), KnxError> {
let config = ConnectionConfig {
connection_type: ConnectionType::Routing,
timeout_ms: 1000,
auto_reconnect: false,
reconnect_backoff: BackoffConfig::default(),
tcp_config: TcpConfig::default(),
..Default::default()
};
for i in 0u8..5 {
let knx = Knx::new(config.clone()).await?;
let source_addr = IndividualAddress::new(1, 1, 240);
let addr = GroupAddress::from_parts(1, 1, i + 1)?;
let _ = timeout(
Duration::from_millis(100),
knx.send_telegram(&write_telegram(source_addr, addr, vec![0x01])),
)
.await;
drop(knx);
println!("✓ Knx instance {} created and cleaned up", i + 1);
}
println!("✓ Resource cleanup test completed");
Ok(())
}