use std::net::SocketAddr;
use crate::AnyConnection;
use crate::error::KnxIpError;
use crate::router::RouterConnection;
use crate::tunnel::TunnelConnection;
#[derive(Debug, Clone)]
pub enum ConnectionSpec {
Tunnel(SocketAddr),
Router(SocketAddr),
}
pub fn parse_url(url: &str) -> Result<ConnectionSpec, KnxIpError> {
let (scheme, rest) = url
.split_once("://")
.ok_or_else(|| KnxIpError::InvalidUrl("missing ://".into()))?;
let addr_str = rest;
let sock_addr: SocketAddr = addr_str
.parse()
.map_err(|e| KnxIpError::InvalidUrl(format!("invalid address '{addr_str}': {e}")))?;
match scheme {
"tunnel" => Ok(ConnectionSpec::Tunnel(sock_addr)),
"router" => Ok(ConnectionSpec::Router(sock_addr)),
"udp" => {
if sock_addr.ip().is_multicast() {
return Ok(ConnectionSpec::Router(sock_addr));
}
Ok(ConnectionSpec::Tunnel(sock_addr))
}
_ => Err(KnxIpError::InvalidUrl(format!(
"unsupported scheme '{scheme}'"
))),
}
}
pub async fn connect(spec: ConnectionSpec) -> Result<AnyConnection, KnxIpError> {
match spec {
ConnectionSpec::Tunnel(addr) => {
let conn = TunnelConnection::connect(addr).await?;
Ok(AnyConnection::Tunnel(conn))
}
ConnectionSpec::Router(multicast) => {
let conn = RouterConnection::connect_multicast(multicast).await?;
Ok(AnyConnection::Router(conn))
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn parse_tunnel_url() {
let spec = parse_url("udp://192.168.1.50:3671").unwrap();
assert!(matches!(spec, ConnectionSpec::Tunnel(_)));
}
#[test]
fn parse_router_url_auto() {
let spec = parse_url("udp://224.0.23.12:3671").unwrap();
assert!(matches!(spec, ConnectionSpec::Router(_)));
}
#[test]
fn parse_explicit_tunnel() {
let spec = parse_url("tunnel://192.168.1.50:3671").unwrap();
assert!(matches!(spec, ConnectionSpec::Tunnel(_)));
}
#[test]
fn parse_explicit_router() {
let spec = parse_url("router://224.0.23.12:3671").unwrap();
assert!(matches!(spec, ConnectionSpec::Router(_)));
}
#[test]
fn parse_ipv6_tunnel() {
let spec = parse_url("tunnel://[::1]:3671").unwrap();
assert!(matches!(spec, ConnectionSpec::Tunnel(_)));
}
#[test]
fn parse_ipv6_router() {
let spec = parse_url("router://[ff02::1]:3671").unwrap();
assert!(matches!(spec, ConnectionSpec::Router(_)));
}
#[test]
fn parse_udp_ipv6_multicast_as_router() {
let spec = parse_url("udp://[ff02::1]:3671").unwrap();
assert!(matches!(spec, ConnectionSpec::Router(_)));
}
#[test]
fn parse_invalid_url() {
assert!(parse_url("192.168.1.50:3671").is_err());
assert!(parse_url("http://192.168.1.50:3671").is_err());
assert!(parse_url("udp://not-an-address").is_err());
}
}