use hickory_resolver::config::{ResolverConfig, ResolverOpts};
use hickory_resolver::proto::rr::RecordType;
use hickory_resolver::TokioAsyncResolver;
use std::future::Future;
use std::net::{IpAddr, SocketAddr};
use std::pin::Pin;
use thiserror::Error;
use tracing::{debug, trace};
use crate::transport::TransportProtocol;
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
type LookupNaptrFn<'a> =
dyn FnMut(String) -> BoxFuture<'a, Result<Vec<(String, TransportProtocol)>>> + 'a;
type LookupTargetsFn<'a> =
dyn FnMut(String, TransportProtocol) -> BoxFuture<'a, Result<Vec<ResolvedTarget>>> + 'a;
fn boxed<'a, T>(fut: impl Future<Output = T> + 'a) -> BoxFuture<'a, T> {
Box::pin(fut)
}
#[derive(Debug, Error)]
pub enum ResolverError {
#[error("DNS lookup failed: {0}")]
LookupFailed(#[from] hickory_resolver::error::ResolveError),
#[error("no DNS records found for {0}")]
NoRecords(String),
#[error("invalid domain: {0}")]
InvalidDomain(String),
}
pub type Result<T> = std::result::Result<T, ResolverError>;
#[derive(Debug, Clone)]
pub struct ResolvedTarget {
pub host: String,
pub port: u16,
pub transport: TransportProtocol,
pub priority: u16,
pub weight: u16,
pub addresses: Vec<IpAddr>,
}
impl ResolvedTarget {
pub fn socket_addrs(&self) -> Vec<SocketAddr> {
self.addresses
.iter()
.map(|ip| SocketAddr::new(*ip, self.port))
.collect()
}
}
pub struct SipResolver {
resolver: TokioAsyncResolver,
}
impl SipResolver {
pub async fn new() -> Result<Self> {
let resolver =
TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default());
Ok(Self { resolver })
}
pub fn with_config(config: ResolverConfig, opts: ResolverOpts) -> Self {
let resolver = TokioAsyncResolver::tokio(config, opts);
Self { resolver }
}
pub async fn resolve(
&self,
domain: &str,
preferred_transport: Option<TransportProtocol>,
) -> Result<Vec<ResolvedTarget>> {
let mut lookup_naptr =
|lookup_domain: String| -> BoxFuture<'_, Result<Vec<(String, TransportProtocol)>>> {
let resolver = self;
boxed(async move { resolver.lookup_naptr(&lookup_domain).await })
};
let mut lookup_srv = |srv_name: String,
transport: TransportProtocol|
-> BoxFuture<'_, Result<Vec<ResolvedTarget>>> {
let resolver = self;
boxed(async move { resolver.lookup_srv(&srv_name, transport).await })
};
let mut lookup_address = |addr_domain: String,
transport: TransportProtocol|
-> BoxFuture<'_, Result<Vec<ResolvedTarget>>> {
let resolver = self;
boxed(async move { resolver.lookup_address(&addr_domain, transport).await })
};
resolve_with(
domain,
preferred_transport,
&mut lookup_naptr,
&mut lookup_srv,
&mut lookup_address,
)
.await
}
async fn lookup_naptr(&self, domain: &str) -> Result<Vec<(String, TransportProtocol)>> {
let lookup = self.resolver.lookup(domain, RecordType::NAPTR).await?;
Ok(collect_naptr_records(lookup.record_iter()))
}
async fn lookup_srv(
&self,
srv_name: &str,
transport: TransportProtocol,
) -> Result<Vec<ResolvedTarget>> {
let lookup = self.resolver.srv_lookup(srv_name).await?;
let mut targets: Vec<ResolvedTarget> = Vec::new();
for record in lookup.iter() {
let host = record
.target()
.to_string()
.trim_end_matches('.')
.to_string();
let port = record.port();
let priority = record.priority();
let weight = record.weight();
trace!(
"SRV: {} -> {}:{} (pri={}, wt={})",
srv_name,
host,
port,
priority,
weight
);
let addresses = self.resolve_addresses(&host).await.unwrap_or_default();
targets.push(ResolvedTarget {
host,
port,
transport,
priority,
weight,
addresses,
});
}
targets.sort_by(|a, b| a.priority.cmp(&b.priority).then(b.weight.cmp(&a.weight)));
Ok(targets)
}
async fn lookup_address(
&self,
domain: &str,
transport: TransportProtocol,
) -> Result<Vec<ResolvedTarget>> {
let addresses = self.resolve_addresses(domain).await?;
build_address_targets(domain, transport, addresses)
}
async fn resolve_addresses(&self, host: &str) -> Result<Vec<IpAddr>> {
if let Ok(ip) = host.parse::<IpAddr>() {
return Ok(vec![ip]);
}
let lookup = self.resolver.lookup_ip(host).await?;
Ok(lookup.iter().collect())
}
pub async fn resolve_uri(&self, uri: &str) -> Result<Vec<ResolvedTarget>> {
let (host, port, transport) = parse_sip_uri_internal(uri);
if let Some(port) = port {
let transport = transport.unwrap_or(TransportProtocol::Udp);
let lookup_host = host
.strip_prefix('[')
.and_then(|value| value.strip_suffix(']'))
.unwrap_or(host.as_str());
let addresses = self
.resolve_addresses(lookup_host)
.await
.unwrap_or_default();
return Ok(vec![ResolvedTarget {
host: host.to_string(),
port,
transport,
priority: 0,
weight: 0,
addresses,
}]);
}
self.resolve(&host, transport).await
}
}
fn parse_naptr_transport(service: &str) -> Option<TransportProtocol> {
match service {
"SIP+D2U" | "sip+d2u" => Some(TransportProtocol::Udp),
"SIP+D2T" | "sip+d2t" => Some(TransportProtocol::Tcp),
"SIPS+D2T" | "sips+d2t" => Some(TransportProtocol::Tls),
_ => None,
}
}
fn collect_naptr_services(
entries: Vec<(u16, u16, String, String)>,
) -> Vec<(String, TransportProtocol)> {
let mut services: Vec<(u16, u16, String, TransportProtocol)> = Vec::new();
for (order, preference, replacement, service) in entries {
let transport = parse_naptr_transport(&service);
if let Some(t) = transport {
trace!("NAPTR: {} -> {} ({:?})", service, replacement, t);
services.push((order, preference, replacement, t));
}
}
services.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
services.into_iter().map(|(_, _, r, t)| (r, t)).collect()
}
fn collect_naptr_records<'a, I>(records: I) -> Vec<(String, TransportProtocol)>
where
I: IntoIterator<Item = &'a hickory_resolver::proto::rr::Record>,
{
use hickory_resolver::proto::rr::RData;
let mut entries: Vec<(u16, u16, String, String)> = Vec::new();
for record in records {
if let Some(RData::NAPTR(naptr)) = record.data() {
let service = String::from_utf8_lossy(naptr.services()).to_string();
let replacement = naptr.replacement().to_string();
trace!("NAPTR: {} -> {}", service, replacement);
entries.push((naptr.order(), naptr.preference(), replacement, service));
}
}
collect_naptr_services(entries)
}
fn build_address_targets(
domain: &str,
transport: TransportProtocol,
addresses: Vec<IpAddr>,
) -> Result<Vec<ResolvedTarget>> {
if addresses.is_empty() {
return Err(ResolverError::NoRecords(domain.to_string()));
}
let port = match transport {
TransportProtocol::Udp | TransportProtocol::Tcp => 5060,
TransportProtocol::Tls => 5061,
};
Ok(vec![ResolvedTarget {
host: domain.to_string(),
port,
transport,
priority: 0,
weight: 0,
addresses,
}])
}
async fn resolve_with<'a>(
domain: &str,
preferred_transport: Option<TransportProtocol>,
lookup_naptr: &mut LookupNaptrFn<'a>,
lookup_srv: &mut LookupTargetsFn<'a>,
lookup_address: &mut LookupTargetsFn<'a>,
) -> Result<Vec<ResolvedTarget>> {
debug!("Resolving SIP domain: {}", domain);
let naptr_results = lookup_naptr(domain.to_string()).await;
if let Ok(services) = naptr_results {
if !services.is_empty() {
debug!("Found {} NAPTR records", services.len());
return resolve_from_naptr_inner(services, preferred_transport, lookup_srv).await;
}
}
let transports = match preferred_transport {
Some(t) => vec![t],
None => vec![
TransportProtocol::Tls,
TransportProtocol::Tcp,
TransportProtocol::Udp,
],
};
for transport in transports {
let srv_name = match transport {
TransportProtocol::Udp => format!("_sip._udp.{}", domain),
TransportProtocol::Tcp => format!("_sip._tcp.{}", domain),
TransportProtocol::Tls => format!("_sips._tcp.{}", domain),
};
let srv_name_for_log = srv_name.clone();
if let Ok(targets) = lookup_srv(srv_name, transport).await {
if !targets.is_empty() {
debug!(
"Found {} SRV records for {}",
targets.len(),
srv_name_for_log
);
return Ok(targets);
}
}
}
debug!("Falling back to A/AAAA lookup for {}", domain);
lookup_address(
domain.to_string(),
preferred_transport.unwrap_or(TransportProtocol::Udp),
)
.await
}
async fn resolve_from_naptr_inner<'a>(
naptr_results: Vec<(String, TransportProtocol)>,
preferred_transport: Option<TransportProtocol>,
lookup_srv: &mut dyn FnMut(
String,
TransportProtocol,
) -> BoxFuture<'a, Result<Vec<ResolvedTarget>>>,
) -> Result<Vec<ResolvedTarget>> {
let mut all_targets = Vec::new();
for (srv_name, transport) in naptr_results {
if let Some(pref) = preferred_transport {
if transport != pref {
continue;
}
}
if let Ok(mut targets) = lookup_srv(srv_name, transport).await {
all_targets.append(&mut targets);
}
}
if all_targets.is_empty() {
return Err(ResolverError::NoRecords("NAPTR targets".to_string()));
}
Ok(all_targets)
}
fn parse_sip_uri_internal(uri: &str) -> (String, Option<u16>, Option<TransportProtocol>) {
let uri = uri
.trim_start_matches("sip:")
.trim_start_matches("sips:")
.trim_start_matches("SIP:")
.trim_start_matches("SIPS:");
let domain_part = uri.split('@').next_back().unwrap_or(uri);
let (host_port, params) = domain_part
.split_once(';')
.map(|(h, p)| (h, Some(p)))
.unwrap_or((domain_part, None));
let (host, explicit_port) = if host_port.starts_with('[') {
if let Some(end_bracket) = host_port.find(']') {
if host_port.len() > end_bracket + 1 && host_port.as_bytes()[end_bracket + 1] == b':' {
let h = &host_port[..=end_bracket];
let p = &host_port[end_bracket + 2..];
(h, p.parse().ok())
} else {
(host_port, None)
}
} else {
(host_port, None)
}
} else {
host_port
.split_once(':')
.map(|(h, p)| (h, p.parse().ok()))
.unwrap_or((host_port, None))
};
let transport = params.and_then(|p| {
p.split(';').find_map(|param| {
let (k, v) = param.split_once('=')?;
if k.eq_ignore_ascii_case("transport") {
match v.to_lowercase().as_str() {
"udp" => Some(TransportProtocol::Udp),
"tcp" => Some(TransportProtocol::Tcp),
"tls" => Some(TransportProtocol::Tls),
_ => None,
}
} else {
None
}
})
});
(host.to_string(), explicit_port, transport)
}
#[cfg(test)]
mod tests {
use super::*;
use hickory_resolver::config::{LookupIpStrategy, NameServerConfigGroup};
use hickory_resolver::proto::op::{Message, MessageType, OpCode, ResponseCode};
use hickory_resolver::proto::rr::rdata::{A, NAPTR, SRV};
use hickory_resolver::proto::rr::{Name, RData, Record, RecordType};
use std::net::Ipv4Addr;
use std::sync::Once;
use std::time::Duration;
use tokio::net::UdpSocket;
use tokio::sync::oneshot;
fn init_tracing() {
static INIT: Once = Once::new();
INIT.call_once(|| {
let _ = tracing_subscriber::fmt()
.with_max_level(tracing::Level::TRACE)
.with_test_writer()
.try_init();
});
}
fn build_test_dns_response(request: &Message) -> Message {
let mut response = Message::new();
response.set_id(request.id());
response.set_message_type(MessageType::Response);
response.set_op_code(OpCode::Query);
response.set_response_code(ResponseCode::NoError);
response.set_authoritative(true);
response.add_queries(request.queries().to_vec());
for query in request.queries() {
let name = query.name().to_ascii().to_lowercase();
match query.query_type() {
RecordType::NAPTR if name == "example.test." => {
let replacement = Name::from_ascii("_sip._udp.example.test.").unwrap();
let naptr = NAPTR::new(
10,
10,
b"S".to_vec().into_boxed_slice(),
b"SIP+D2U".to_vec().into_boxed_slice(),
Vec::<u8>::new().into_boxed_slice(),
replacement,
);
let record = Record::from_rdata(query.name().clone(), 60, RData::NAPTR(naptr));
response.add_answer(record);
}
RecordType::SRV if name == "_sip._udp.example.test." => {
let target = Name::from_ascii("srv.example.test.").unwrap();
let srv = SRV::new(10, 5, 5060, target);
let record = Record::from_rdata(query.name().clone(), 60, RData::SRV(srv));
response.add_answer(record);
}
RecordType::SRV if name == "_sip._udp.multi.test." => {
let target1 = Name::from_ascii("srv1.multi.test.").unwrap();
let target2 = Name::from_ascii("srv2.multi.test.").unwrap();
let srv1 = SRV::new(10, 5, 5060, target1);
let srv2 = SRV::new(10, 20, 5070, target2);
response.add_answer(Record::from_rdata(
query.name().clone(),
60,
RData::SRV(srv1),
));
response.add_answer(Record::from_rdata(
query.name().clone(),
60,
RData::SRV(srv2),
));
}
RecordType::A if name == "srv.example.test." => {
let record = Record::from_rdata(
query.name().clone(),
60,
RData::A(A(Ipv4Addr::new(203, 0, 113, 10))),
);
response.add_answer(record);
}
RecordType::A if name == "srv1.multi.test." => {
let record = Record::from_rdata(
query.name().clone(),
60,
RData::A(A(Ipv4Addr::new(203, 0, 113, 11))),
);
response.add_answer(record);
}
RecordType::A if name == "srv2.multi.test." => {
let record = Record::from_rdata(
query.name().clone(),
60,
RData::A(A(Ipv4Addr::new(203, 0, 113, 12))),
);
response.add_answer(record);
}
_ => {}
}
}
response
}
async fn spawn_test_dns_server(simulate_recv_error: bool) -> (SocketAddr, oneshot::Sender<()>) {
let socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let addr = socket.local_addr().unwrap();
let (shutdown_tx, mut shutdown_rx) = oneshot::channel();
tokio::spawn(async move {
let mut simulate_recv_error = simulate_recv_error;
let mut buf = [0u8; 512];
loop {
tokio::select! {
_ = &mut shutdown_rx => break,
result = async {
if simulate_recv_error {
simulate_recv_error = false;
Err(std::io::Error::other("simulated recv error"))
} else {
socket.recv_from(&mut buf).await
}
} => {
let (len, peer) = match result {
Ok(value) => value,
Err(_) => break,
};
let request = match Message::from_vec(&buf[..len]) {
Ok(msg) => msg,
Err(_) => continue,
};
let response = build_test_dns_response(&request);
let bytes = response.to_vec().unwrap();
let _ = socket.send_to(&bytes, peer).await;
}
}
}
});
(addr, shutdown_tx)
}
#[test]
fn test_build_test_dns_response_matching_queries() {
let mut request = Message::new();
request.add_query(hickory_resolver::proto::op::Query::query(
Name::from_ascii("example.test.").unwrap(),
RecordType::NAPTR,
));
request.add_query(hickory_resolver::proto::op::Query::query(
Name::from_ascii("_sip._udp.example.test.").unwrap(),
RecordType::SRV,
));
request.add_query(hickory_resolver::proto::op::Query::query(
Name::from_ascii("srv.example.test.").unwrap(),
RecordType::A,
));
let response = build_test_dns_response(&request);
assert_eq!(response.answers().len(), 3);
}
#[test]
fn test_build_test_dns_response_non_matching_queries() {
let mut request = Message::new();
request.add_query(hickory_resolver::proto::op::Query::query(
Name::from_ascii("other.test.").unwrap(),
RecordType::NAPTR,
));
request.add_query(hickory_resolver::proto::op::Query::query(
Name::from_ascii("_sip._tcp.other.test.").unwrap(),
RecordType::SRV,
));
request.add_query(hickory_resolver::proto::op::Query::query(
Name::from_ascii("host.other.test.").unwrap(),
RecordType::A,
));
request.add_query(hickory_resolver::proto::op::Query::query(
Name::from_ascii("other.test.").unwrap(),
RecordType::TXT,
));
let response = build_test_dns_response(&request);
assert!(response.answers().is_empty());
}
#[tokio::test]
async fn test_spawn_test_dns_server_simulated_recv_error() {
let (_dns_addr, shutdown_tx) = spawn_test_dns_server(true).await;
tokio::time::sleep(Duration::from_millis(10)).await;
let _ = shutdown_tx.send(());
}
#[tokio::test]
async fn test_spawn_test_dns_server_ignores_invalid_message() {
let (dns_addr, shutdown_tx) = spawn_test_dns_server(false).await;
let socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let _ = socket.send_to(&[0xde, 0xad, 0xbe, 0xef], dns_addr).await;
tokio::time::sleep(Duration::from_millis(10)).await;
let _ = shutdown_tx.send(());
}
#[tokio::test]
async fn test_spawn_test_dns_server_shutdown_breaks_loop() {
let (_dns_addr, shutdown_tx) = spawn_test_dns_server(false).await;
tokio::time::sleep(Duration::from_millis(10)).await;
let _ = shutdown_tx.send(());
tokio::time::sleep(Duration::from_millis(10)).await;
}
#[test]
fn test_resolved_target_socket_addrs() {
let target = ResolvedTarget {
host: "sip.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 10,
weight: 100,
addresses: vec![
"192.168.1.1".parse().unwrap(),
"192.168.1.2".parse().unwrap(),
],
};
let addrs = target.socket_addrs();
assert_eq!(addrs.len(), 2);
assert_eq!(addrs[0], "192.168.1.1:5060".parse().unwrap());
assert_eq!(addrs[1], "192.168.1.2:5060".parse().unwrap());
}
#[test]
fn test_resolved_target_socket_addrs_ipv6() {
let target = ResolvedTarget {
host: "sip.example.com".to_string(),
port: 5061,
transport: TransportProtocol::Tls,
priority: 5,
weight: 50,
addresses: vec!["2001:db8::1".parse().unwrap(), "::1".parse().unwrap()],
};
let addrs = target.socket_addrs();
assert_eq!(addrs.len(), 2);
assert_eq!(addrs[0], "[2001:db8::1]:5061".parse().unwrap());
assert_eq!(addrs[1], "[::1]:5061".parse().unwrap());
}
#[test]
fn test_resolved_target_socket_addrs_mixed() {
let target = ResolvedTarget {
host: "dual.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Tcp,
priority: 0,
weight: 100,
addresses: vec![
"192.168.1.100".parse().unwrap(),
"2001:db8::100".parse().unwrap(),
],
};
let addrs = target.socket_addrs();
assert_eq!(addrs.len(), 2);
assert!(addrs[0].is_ipv4());
assert!(addrs[1].is_ipv6());
}
#[test]
fn test_resolved_target_socket_addrs_empty() {
let target = ResolvedTarget {
host: "unresolved.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 0,
weight: 0,
addresses: vec![],
};
let addrs = target.socket_addrs();
assert!(addrs.is_empty());
}
#[test]
fn test_resolved_target_clone() {
let target = ResolvedTarget {
host: "sip.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 10,
weight: 100,
addresses: vec!["192.168.1.1".parse().unwrap()],
};
let cloned = target.clone();
assert_eq!(cloned.host, target.host);
assert_eq!(cloned.port, target.port);
assert_eq!(cloned.transport, target.transport);
assert_eq!(cloned.priority, target.priority);
assert_eq!(cloned.weight, target.weight);
assert_eq!(cloned.addresses.len(), target.addresses.len());
}
#[test]
fn test_resolved_target_debug() {
let target = ResolvedTarget {
host: "test.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 0,
weight: 0,
addresses: vec![],
};
let debug = format!("{:?}", target);
assert!(debug.contains("ResolvedTarget"));
assert!(debug.contains("test.com"));
}
#[test]
fn test_transport_protocol_priority() {
let transports = [
TransportProtocol::Tls,
TransportProtocol::Tcp,
TransportProtocol::Udp,
];
assert_eq!(transports[0], TransportProtocol::Tls);
}
#[test]
fn test_resolver_error_no_records() {
let err = ResolverError::NoRecords("example.com".to_string());
let msg = err.to_string();
assert!(msg.contains("no DNS records found"));
assert!(msg.contains("example.com"));
}
#[test]
fn test_resolver_error_invalid_domain() {
let err = ResolverError::InvalidDomain("bad..domain".to_string());
let msg = err.to_string();
assert!(msg.contains("invalid domain"));
assert!(msg.contains("bad..domain"));
}
#[test]
fn test_resolver_error_debug() {
let err = ResolverError::NoRecords("test.com".to_string());
let debug = format!("{:?}", err);
assert!(debug.contains("NoRecords"));
}
#[tokio::test]
async fn test_resolve_uri_with_ip_address() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve_uri("sip:user@192.168.1.1:5060").await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets.len(), 1);
assert_eq!(targets[0].host, "192.168.1.1");
assert_eq!(targets[0].port, 5060);
assert_eq!(targets[0].transport, TransportProtocol::Udp);
}
#[tokio::test]
async fn test_resolve_uri_with_explicit_port_and_transport() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver
.resolve_uri("sip:user@10.0.0.1:5080;transport=tcp")
.await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets.len(), 1);
assert_eq!(targets[0].host, "10.0.0.1");
assert_eq!(targets[0].port, 5080);
assert_eq!(targets[0].transport, TransportProtocol::Tcp);
}
#[tokio::test]
async fn test_resolve_uri_with_tls_transport() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver
.resolve_uri("sips:user@172.16.0.1:5061;transport=tls")
.await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].port, 5061);
assert_eq!(targets[0].transport, TransportProtocol::Tls);
}
#[tokio::test]
async fn test_resolve_uri_ip_without_port() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve_uri("sip:user@127.0.0.1").await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].host, "127.0.0.1");
assert_eq!(targets[0].port, 5060);
}
#[tokio::test]
async fn test_resolve_uri_sips_scheme() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve_uri("sips:user@10.10.10.10:5061").await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].host, "10.10.10.10");
assert_eq!(targets[0].port, 5061);
}
#[tokio::test]
async fn test_resolve_uri_no_user_part() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve_uri("sip:192.168.0.1:5060").await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].host, "192.168.0.1");
}
#[tokio::test]
async fn test_resolver_new() {
let resolver = SipResolver::new().await;
assert!(resolver.is_ok());
}
#[tokio::test]
async fn test_resolver_with_config() {
let config = ResolverConfig::default();
let opts = ResolverOpts::default();
let _resolver = SipResolver::with_config(config, opts);
}
#[tokio::test]
async fn test_resolve_addresses_ip_passthrough() {
let resolver = SipResolver::new().await.unwrap();
let addrs = resolver.resolve_addresses("192.168.1.1").await;
assert!(addrs.is_ok());
let addrs = addrs.unwrap();
assert_eq!(addrs.len(), 1);
assert_eq!(addrs[0], "192.168.1.1".parse::<IpAddr>().unwrap());
}
#[tokio::test]
async fn test_resolve_addresses_ipv6_passthrough() {
let resolver = SipResolver::new().await.unwrap();
let addrs = resolver.resolve_addresses("::1").await;
assert!(addrs.is_ok());
let addrs = addrs.unwrap();
assert_eq!(addrs.len(), 1);
assert_eq!(addrs[0], "::1".parse::<IpAddr>().unwrap());
}
#[tokio::test]
async fn test_lookup_address_default_ports() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver
.lookup_address("127.0.0.1", TransportProtocol::Udp)
.await;
assert!(targets.is_ok());
assert_eq!(targets.unwrap()[0].port, 5060);
let targets = resolver
.lookup_address("127.0.0.1", TransportProtocol::Tcp)
.await;
assert!(targets.is_ok());
assert_eq!(targets.unwrap()[0].port, 5060);
let targets = resolver
.lookup_address("127.0.0.1", TransportProtocol::Tls)
.await;
assert!(targets.is_ok());
assert_eq!(targets.unwrap()[0].port, 5061);
}
#[tokio::test]
async fn test_resolve_with_preferred_transport() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver
.resolve("127.0.0.1", Some(TransportProtocol::Tcp))
.await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].transport, TransportProtocol::Tcp);
}
#[tokio::test]
async fn test_resolve_uri_multiple_params() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver
.resolve_uri("sip:user@10.0.0.1:5070;transport=udp;lr;maddr=10.0.0.2")
.await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].port, 5070);
assert_eq!(targets[0].transport, TransportProtocol::Udp);
}
#[tokio::test]
async fn test_resolve_uri_unknown_transport() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver
.resolve_uri("sip:user@192.168.1.1:5060;transport=sctp")
.await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].transport, TransportProtocol::Udp);
}
#[tokio::test]
async fn test_resolve_localhost() {
let mut lookup_naptr =
|_domain: String| boxed(async { Err(ResolverError::NoRecords("naptr".to_string())) });
let mut lookup_srv =
|_srv_name: String, _transport: TransportProtocol| boxed(async { Ok(Vec::new()) });
let mut lookup_addr = |domain: String, transport: TransportProtocol| {
boxed(async move {
Ok(vec![ResolvedTarget {
host: domain,
port: 5060,
transport,
priority: 0,
weight: 0,
addresses: vec!["127.0.0.1".parse().unwrap()],
}])
})
};
let targets = resolve_with(
"localhost",
Some(TransportProtocol::Udp),
&mut lookup_naptr,
&mut lookup_srv,
&mut lookup_addr,
)
.await
.unwrap();
assert_eq!(targets.len(), 1);
assert_eq!(targets[0].port, 5060);
}
#[tokio::test]
async fn test_resolve_real_domain() {
let mut lookup_naptr =
|_domain: String| boxed(async { Err(ResolverError::NoRecords("naptr".to_string())) });
let mut lookup_srv = |srv_name: String, transport: TransportProtocol| {
boxed(async move { Ok(vec![sample_target(&srv_name, transport)]) })
};
let mut lookup_addr = |_domain: String, _transport: TransportProtocol| {
boxed(async { Err(ResolverError::NoRecords("addr".to_string())) })
};
let _ = lookup_addr("example.com".to_string(), TransportProtocol::Udp).await;
let targets = resolve_with(
"example.com",
Some(TransportProtocol::Udp),
&mut lookup_naptr,
&mut lookup_srv,
&mut lookup_addr,
)
.await
.unwrap();
assert_eq!(targets.len(), 1);
assert_eq!(targets[0].host, "_sip._udp.example.com");
}
#[test]
fn test_srv_sorting_by_priority() {
let mut targets = [
ResolvedTarget {
host: "low-priority.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 20,
weight: 100,
addresses: vec![],
},
ResolvedTarget {
host: "high-priority.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 10,
weight: 100,
addresses: vec![],
},
ResolvedTarget {
host: "medium-priority.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 15,
weight: 100,
addresses: vec![],
},
];
targets.sort_by(|a, b| a.priority.cmp(&b.priority).then(b.weight.cmp(&a.weight)));
assert_eq!(targets[0].priority, 10);
assert_eq!(targets[0].host, "high-priority.example.com");
assert_eq!(targets[1].priority, 15);
assert_eq!(targets[2].priority, 20);
}
#[test]
fn test_srv_sorting_by_weight_when_priority_equal() {
let mut targets = [
ResolvedTarget {
host: "low-weight.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 10,
weight: 50,
addresses: vec![],
},
ResolvedTarget {
host: "high-weight.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 10,
weight: 200,
addresses: vec![],
},
ResolvedTarget {
host: "medium-weight.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 10,
weight: 100,
addresses: vec![],
},
];
targets.sort_by(|a, b| a.priority.cmp(&b.priority).then(b.weight.cmp(&a.weight)));
assert_eq!(targets[0].weight, 200);
assert_eq!(targets[0].host, "high-weight.example.com");
assert_eq!(targets[1].weight, 100);
assert_eq!(targets[2].weight, 50);
}
#[test]
fn test_srv_sorting_combined_priority_and_weight() {
let mut targets = [
ResolvedTarget {
host: "server1.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 10,
weight: 100,
addresses: vec![],
},
ResolvedTarget {
host: "server2.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 10,
weight: 200,
addresses: vec![],
},
ResolvedTarget {
host: "server3.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 5,
weight: 50,
addresses: vec![],
},
ResolvedTarget {
host: "server4.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 5,
weight: 150,
addresses: vec![],
},
];
targets.sort_by(|a, b| a.priority.cmp(&b.priority).then(b.weight.cmp(&a.weight)));
assert_eq!(targets[0].priority, 5);
assert_eq!(targets[0].weight, 150); assert_eq!(targets[1].priority, 5);
assert_eq!(targets[1].weight, 50); assert_eq!(targets[2].priority, 10);
assert_eq!(targets[2].weight, 200);
assert_eq!(targets[3].priority, 10);
assert_eq!(targets[3].weight, 100);
}
#[test]
fn test_srv_sorting_zero_weight() {
let mut targets = [
ResolvedTarget {
host: "zero-weight.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 10,
weight: 0,
addresses: vec![],
},
ResolvedTarget {
host: "nonzero-weight.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 10,
weight: 100,
addresses: vec![],
},
];
targets.sort_by(|a, b| a.priority.cmp(&b.priority).then(b.weight.cmp(&a.weight)));
assert_eq!(targets[0].weight, 100);
assert_eq!(targets[1].weight, 0);
}
#[test]
fn test_srv_sorting_max_values() {
let mut targets = [
ResolvedTarget {
host: "max-priority.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: u16::MAX,
weight: 100,
addresses: vec![],
},
ResolvedTarget {
host: "min-priority.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 0,
weight: u16::MAX,
addresses: vec![],
},
];
targets.sort_by(|a, b| a.priority.cmp(&b.priority).then(b.weight.cmp(&a.weight)));
assert_eq!(targets[0].priority, 0);
assert_eq!(targets[1].priority, u16::MAX);
}
#[test]
fn test_naptr_sorting_by_order() {
let mut services = [
(
30u16,
50u16,
"srv3.example.com".to_string(),
TransportProtocol::Tcp,
),
(
10u16,
50u16,
"srv1.example.com".to_string(),
TransportProtocol::Udp,
),
(
20u16,
50u16,
"srv2.example.com".to_string(),
TransportProtocol::Tls,
),
];
services.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
assert_eq!(services[0].0, 10);
assert_eq!(services[0].2, "srv1.example.com");
assert_eq!(services[1].0, 20);
assert_eq!(services[2].0, 30);
}
#[test]
fn test_naptr_sorting_by_preference_when_order_equal() {
let mut services = [
(
10u16,
100u16,
"srv3.example.com".to_string(),
TransportProtocol::Tcp,
),
(
10u16,
50u16,
"srv1.example.com".to_string(),
TransportProtocol::Udp,
),
(
10u16,
75u16,
"srv2.example.com".to_string(),
TransportProtocol::Tls,
),
];
services.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
assert_eq!(services[0].1, 50);
assert_eq!(services[0].2, "srv1.example.com");
assert_eq!(services[1].1, 75);
assert_eq!(services[2].1, 100);
}
#[test]
fn test_naptr_sorting_combined_order_and_preference() {
let mut services = [
(
20u16,
50u16,
"srv4.example.com".to_string(),
TransportProtocol::Tcp,
),
(
10u16,
100u16,
"srv2.example.com".to_string(),
TransportProtocol::Udp,
),
(
10u16,
50u16,
"srv1.example.com".to_string(),
TransportProtocol::Tls,
),
(
20u16,
25u16,
"srv3.example.com".to_string(),
TransportProtocol::Tcp,
),
];
services.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
assert_eq!(services[0].0, 10);
assert_eq!(services[0].1, 50);
assert_eq!(services[1].0, 10);
assert_eq!(services[1].1, 100);
assert_eq!(services[2].0, 20);
assert_eq!(services[2].1, 25);
assert_eq!(services[3].0, 20);
assert_eq!(services[3].1, 50);
}
#[test]
fn test_collect_naptr_services_filters_and_sorts() {
let entries = vec![
(
20u16,
10u16,
"srv2.example.com".to_string(),
"SIP+D2T".to_string(),
),
(
10u16,
20u16,
"srv1.example.com".to_string(),
"SIP+D2U".to_string(),
),
(
10u16,
5u16,
"srv0.example.com".to_string(),
"SIPS+D2T".to_string(),
),
(
30u16,
5u16,
"ignored.example.com".to_string(),
"SIP+UNKNOWN".to_string(),
),
];
let services = collect_naptr_services(entries);
assert_eq!(services.len(), 3);
assert_eq!(services[0].0, "srv0.example.com");
assert_eq!(services[0].1, TransportProtocol::Tls);
assert_eq!(services[1].0, "srv1.example.com");
assert_eq!(services[1].1, TransportProtocol::Udp);
assert_eq!(services[2].0, "srv2.example.com");
assert_eq!(services[2].1, TransportProtocol::Tcp);
}
#[test]
fn test_collect_naptr_records_filters_non_naptr() {
init_tracing();
let name = Name::from_ascii("example.com").unwrap();
let replacement = Name::from_ascii("_sip._udp.example.com").unwrap();
let naptr = NAPTR::new(
10,
5,
b"s".to_vec().into_boxed_slice(),
b"SIP+D2U".to_vec().into_boxed_slice(),
Vec::new().into_boxed_slice(),
replacement,
);
let naptr_record = Record::from_rdata(name.clone(), 60, RData::NAPTR(naptr));
let a_record = Record::from_rdata(name.clone(), 60, RData::A(A::new(127, 0, 0, 1)));
let empty_record = Record::with(name, RecordType::A, 60);
let services = collect_naptr_records([naptr_record, a_record, empty_record].iter());
assert_eq!(services.len(), 1);
assert_eq!(services[0].0.trim_end_matches('.'), "_sip._udp.example.com");
assert_eq!(services[0].1, TransportProtocol::Udp);
}
#[test]
fn test_build_address_targets_empty() {
let err =
build_address_targets("example.com", TransportProtocol::Udp, Vec::new()).unwrap_err();
assert!(format!("{err:?}").contains("NoRecords"));
}
#[test]
fn test_build_address_targets_tls_port() {
let targets = build_address_targets(
"example.com",
TransportProtocol::Tls,
vec!["::1".parse().unwrap()],
)
.unwrap();
assert_eq!(targets.len(), 1);
assert_eq!(targets[0].port, 5061);
assert_eq!(targets[0].transport, TransportProtocol::Tls);
}
#[tokio::test]
async fn test_resolve_uri_case_insensitive_transport() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver
.resolve_uri("sip:user@192.168.1.1:5060;transport=TCP")
.await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].transport, TransportProtocol::Tcp);
let targets = resolver
.resolve_uri("sip:user@192.168.1.1:5060;transport=TlS")
.await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].transport, TransportProtocol::Tls);
}
#[tokio::test]
async fn test_resolve_uri_with_ipv6_address() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve_uri("sip:user@[::1]:5060").await.unwrap();
assert_eq!(targets[0].host, "[::1]");
assert_eq!(targets[0].port, 5060);
assert_eq!(targets[0].transport, TransportProtocol::Udp);
}
#[test]
fn test_parse_sip_uri_internal_bracketed_ipv6_with_port() {
let (host, port, transport) =
parse_sip_uri_internal("sip:user@[2001:db8::1]:5070;transport=tls");
assert_eq!(host, "[2001:db8::1]");
assert_eq!(port, Some(5070));
assert_eq!(transport, Some(TransportProtocol::Tls));
}
#[test]
fn test_parse_sip_uri_internal_bracketed_ipv6_without_port() {
let (host, port, transport) =
parse_sip_uri_internal("sip:user@[2001:db8::1];transport=udp");
assert_eq!(host, "[2001:db8::1]");
assert_eq!(port, None);
assert_eq!(transport, Some(TransportProtocol::Udp));
}
#[test]
fn test_parse_sip_uri_internal_bracketed_ipv6_extra_suffix() {
let (host, port, transport) =
parse_sip_uri_internal("sip:user@[2001:db8::1]extra;transport=udp");
assert_eq!(host, "[2001:db8::1]extra");
assert_eq!(port, None);
assert_eq!(transport, Some(TransportProtocol::Udp));
}
#[test]
fn test_parse_sip_uri_internal_bracketed_ipv6_malformed() {
let (host, port, transport) = parse_sip_uri_internal("sip:user@[2001:db8::1");
assert_eq!(host, "[2001:db8::1");
assert_eq!(port, None);
assert_eq!(transport, None);
}
#[test]
fn test_parse_sip_uri_internal_unknown_transport() {
let (host, port, transport) =
parse_sip_uri_internal("sip:user@127.0.0.1:5060;transport=sctp");
assert_eq!(host, "127.0.0.1");
assert_eq!(port, Some(5060));
assert_eq!(transport, None);
}
#[tokio::test]
async fn test_resolve_uri_trailing_semicolon() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve_uri("sip:user@192.168.1.1:5060;").await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].host, "192.168.1.1");
assert_eq!(targets[0].port, 5060);
}
fn sample_target(host: &str, transport: TransportProtocol) -> ResolvedTarget {
ResolvedTarget {
host: host.to_string(),
port: 5060,
transport,
priority: 0,
weight: 0,
addresses: vec![],
}
}
#[tokio::test]
async fn test_resolve_with_naptr_success() {
let mut lookup_naptr = |_domain: String| {
boxed(async {
Ok(vec![(
"_sip._udp.example.com".to_string(),
TransportProtocol::Udp,
)])
})
};
let mut lookup_srv = |srv_name: String, transport: TransportProtocol| {
boxed(async move {
assert_eq!(srv_name, "_sip._udp.example.com");
Ok(vec![sample_target("udp.example.com", transport)])
})
};
let mut lookup_addr = |_domain: String, _transport: TransportProtocol| {
boxed(async { Err(ResolverError::NoRecords("addr".to_string())) })
};
let _ = lookup_addr("example.com".to_string(), TransportProtocol::Udp).await;
let targets = resolve_with(
"example.com",
None,
&mut lookup_naptr,
&mut lookup_srv,
&mut lookup_addr,
)
.await
.unwrap();
assert_eq!(targets.len(), 1);
assert_eq!(targets[0].transport, TransportProtocol::Udp);
}
#[tokio::test]
async fn test_resolve_with_naptr_preferred_transport_skips() {
let mut lookup_naptr = |_domain: String| {
boxed(async {
Ok(vec![
("_sip._udp.example.com".to_string(), TransportProtocol::Udp),
("_sip._tcp.example.com".to_string(), TransportProtocol::Tcp),
])
})
};
let mut lookup_srv = |srv_name: String, _transport: TransportProtocol| {
boxed(async move { Ok(vec![sample_target(&srv_name, TransportProtocol::Tcp)]) })
};
let mut lookup_addr = |_domain: String, _transport: TransportProtocol| {
boxed(async { Err(ResolverError::NoRecords("addr".to_string())) })
};
let _ = lookup_addr("example.com".to_string(), TransportProtocol::Udp).await;
let targets = resolve_with(
"example.com",
Some(TransportProtocol::Tcp),
&mut lookup_naptr,
&mut lookup_srv,
&mut lookup_addr,
)
.await
.unwrap();
assert_eq!(targets.len(), 1);
assert_eq!(targets[0].transport, TransportProtocol::Tcp);
}
#[tokio::test]
async fn test_resolve_from_naptr_inner_errors_when_empty() {
let naptr_results = vec![("_sip._udp.example.com".to_string(), TransportProtocol::Udp)];
let mut lookup_srv = |srv_name: String, _transport: TransportProtocol| {
boxed(async move { Err(ResolverError::InvalidDomain(srv_name)) })
};
let err = resolve_from_naptr_inner(naptr_results, None, &mut lookup_srv)
.await
.err()
.unwrap();
assert!(format!("{err:?}").contains("NoRecords"));
}
#[tokio::test]
async fn test_resolve_from_naptr_inner_ignores_lookup_errors() {
let naptr_results = vec![
("_sip._udp.example.com".to_string(), TransportProtocol::Udp),
("_sip._tcp.example.com".to_string(), TransportProtocol::Tcp),
];
let mut lookup_srv = |srv_name: String, transport: TransportProtocol| {
boxed(async move {
if transport == TransportProtocol::Udp {
Err(ResolverError::InvalidDomain(srv_name))
} else {
Ok(vec![sample_target(&srv_name, transport)])
}
})
};
let targets = resolve_from_naptr_inner(naptr_results, None, &mut lookup_srv)
.await
.unwrap();
assert_eq!(targets.len(), 1);
assert_eq!(targets[0].transport, TransportProtocol::Tcp);
}
#[tokio::test]
async fn test_resolve_with_srv_fallback_order() {
let mut lookup_naptr = |_domain: String| {
boxed(async { Ok::<Vec<(String, TransportProtocol)>, ResolverError>(Vec::new()) })
};
let mut lookup_srv = |srv_name: String, transport: TransportProtocol| {
boxed(async move {
if transport == TransportProtocol::Udp {
Ok(vec![sample_target(&srv_name, transport)])
} else {
Ok(Vec::new())
}
})
};
let mut lookup_addr = |_domain: String, _transport: TransportProtocol| {
boxed(async { Err(ResolverError::NoRecords("addr".to_string())) })
};
let _ = lookup_addr("example.com".to_string(), TransportProtocol::Udp).await;
let targets = resolve_with(
"example.com",
None,
&mut lookup_naptr,
&mut lookup_srv,
&mut lookup_addr,
)
.await
.unwrap();
assert_eq!(targets.len(), 1);
assert_eq!(targets[0].transport, TransportProtocol::Udp);
}
#[tokio::test]
async fn test_resolve_with_address_fallback() {
let mut lookup_naptr = |_domain: String| {
boxed(async { Err(ResolverError::InvalidDomain("fail".to_string())) })
};
let mut lookup_srv = |_srv_name: String, _transport: TransportProtocol| {
boxed(async { Ok::<Vec<ResolvedTarget>, ResolverError>(Vec::new()) })
};
let mut lookup_addr = |domain: String, transport: TransportProtocol| {
boxed(async move {
Ok(vec![ResolvedTarget {
host: domain,
port: 5060,
transport,
priority: 0,
weight: 0,
addresses: vec!["127.0.0.1".parse().unwrap()],
}])
})
};
let targets = resolve_with(
"example.com",
Some(TransportProtocol::Udp),
&mut lookup_naptr,
&mut lookup_srv,
&mut lookup_addr,
)
.await
.unwrap();
assert_eq!(targets.len(), 1);
assert_eq!(targets[0].host, "example.com");
assert_eq!(targets[0].transport, TransportProtocol::Udp);
}
#[tokio::test]
async fn test_lookup_naptr_and_srv_with_local_dns() {
init_tracing();
let (dns_addr, shutdown_tx) = spawn_test_dns_server(false).await;
let mut opts = ResolverOpts::default();
opts.timeout = Duration::from_millis(200);
opts.attempts = 1;
opts.try_tcp_on_error = false;
opts.ip_strategy = LookupIpStrategy::Ipv4Only;
let name_servers =
NameServerConfigGroup::from_ips_clear(&[dns_addr.ip()], dns_addr.port(), true);
let config = ResolverConfig::from_parts(None, vec![], name_servers);
let resolver = SipResolver::with_config(config, opts);
let naptr = resolver.lookup_naptr("example.test").await.unwrap();
assert_eq!(naptr.len(), 1);
assert_eq!(naptr[0].0, "_sip._udp.example.test.");
assert_eq!(naptr[0].1, TransportProtocol::Udp);
let targets = resolver
.lookup_srv("_sip._udp.example.test", TransportProtocol::Udp)
.await
.unwrap();
assert_eq!(targets.len(), 1);
assert_eq!(targets[0].host, "srv.example.test");
assert_eq!(targets[0].port, 5060);
assert_eq!(targets[0].addresses.len(), 1);
let socket_addrs = targets[0].socket_addrs();
assert_eq!(socket_addrs[0].port(), 5060);
let _ = shutdown_tx.send(());
}
#[tokio::test]
async fn test_lookup_srv_sorting_by_weight() {
init_tracing();
let (dns_addr, shutdown_tx) = spawn_test_dns_server(false).await;
let mut opts = ResolverOpts::default();
opts.timeout = Duration::from_millis(200);
opts.attempts = 1;
opts.try_tcp_on_error = false;
opts.ip_strategy = LookupIpStrategy::Ipv4Only;
let name_servers =
NameServerConfigGroup::from_ips_clear(&[dns_addr.ip()], dns_addr.port(), true);
let config = ResolverConfig::from_parts(None, vec![], name_servers);
let resolver = SipResolver::with_config(config, opts);
let targets = resolver
.lookup_srv("_sip._udp.multi.test", TransportProtocol::Udp)
.await
.unwrap();
assert_eq!(targets.len(), 2);
assert_eq!(targets[0].host, "srv2.multi.test");
assert_eq!(targets[1].host, "srv1.multi.test");
let _ = shutdown_tx.send(());
}
#[tokio::test]
async fn test_resolve_uri_empty_transport_param() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver
.resolve_uri("sip:user@192.168.1.1:5060;transport=")
.await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].transport, TransportProtocol::Udp);
}
#[tokio::test]
async fn test_resolve_uri_other_params_ignored() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver
.resolve_uri("sip:user@192.168.1.1:5060;lr;maddr=10.0.0.1;ttl=1")
.await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].host, "192.168.1.1");
assert_eq!(targets[0].port, 5060);
}
#[tokio::test]
async fn test_resolve_uri_no_scheme() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve_uri("user@192.168.1.1:5060").await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].host, "192.168.1.1");
assert_eq!(targets[0].port, 5060);
}
#[tokio::test]
async fn test_resolve_uri_complex_username() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve_uri("sip:user+name@192.168.1.1:5060").await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].host, "192.168.1.1");
}
#[tokio::test]
async fn test_lookup_address_empty_addresses() {
let resolver = SipResolver::new().await.unwrap();
let result = resolver
.lookup_address(
"not.a.valid.ip.address.that.does.not.exist.example",
TransportProtocol::Udp,
)
.await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_resolve_addresses_invalid_hostname() {
let resolver = SipResolver::new().await.unwrap();
let result = resolver
.resolve_addresses("this-domain-definitely-does-not-exist-12345.invalid")
.await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_resolve_with_all_transports_fallback() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve("127.0.0.1", None).await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].port, 5060);
}
#[tokio::test]
async fn test_resolve_ipv6_loopback() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve("::1", Some(TransportProtocol::Tcp)).await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].transport, TransportProtocol::Tcp);
assert_eq!(targets[0].addresses[0], "::1".parse::<IpAddr>().unwrap());
}
#[tokio::test]
async fn test_lookup_address_sets_correct_metadata() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver
.lookup_address("192.168.1.1", TransportProtocol::Udp)
.await
.unwrap();
assert_eq!(targets.len(), 1);
assert_eq!(targets[0].host, "192.168.1.1");
assert_eq!(targets[0].port, 5060);
assert_eq!(targets[0].transport, TransportProtocol::Udp);
assert_eq!(targets[0].priority, 0);
assert_eq!(targets[0].weight, 0);
assert_eq!(targets[0].addresses.len(), 1);
}
#[tokio::test]
async fn test_lookup_address_tls_port() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver
.lookup_address("10.0.0.1", TransportProtocol::Tls)
.await
.unwrap();
assert_eq!(targets[0].port, 5061);
assert_eq!(targets[0].transport, TransportProtocol::Tls);
}
#[test]
fn test_resolved_target_with_multiple_addresses() {
let target = ResolvedTarget {
host: "multi.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 10,
weight: 100,
addresses: vec![
"192.168.1.1".parse().unwrap(),
"192.168.1.2".parse().unwrap(),
"192.168.1.3".parse().unwrap(),
"2001:db8::1".parse().unwrap(),
],
};
let addrs = target.socket_addrs();
assert_eq!(addrs.len(), 4);
assert_eq!(addrs[0].port(), 5060);
assert_eq!(addrs[1].port(), 5060);
assert_eq!(addrs[2].port(), 5060);
assert_eq!(addrs[3].port(), 5060);
}
#[test]
fn test_resolved_target_fields() {
let target = ResolvedTarget {
host: "test.example.com".to_string(),
port: 5070,
transport: TransportProtocol::Tcp,
priority: 25,
weight: 75,
addresses: vec!["10.0.0.1".parse().unwrap()],
};
assert_eq!(target.host, "test.example.com");
assert_eq!(target.port, 5070);
assert_eq!(target.transport, TransportProtocol::Tcp);
assert_eq!(target.priority, 25);
assert_eq!(target.weight, 75);
assert_eq!(target.addresses.len(), 1);
}
#[test]
fn test_resolver_with_custom_config() {
let config = ResolverConfig::default();
let mut opts = ResolverOpts::default();
opts.timeout = std::time::Duration::from_secs(5);
let _resolver = SipResolver::with_config(config.clone(), opts.clone());
}
#[tokio::test]
async fn test_resolve_uri_bare_domain() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve_uri("sip:192.168.1.1:5060").await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].host, "192.168.1.1");
assert_eq!(targets[0].port, 5060);
}
#[tokio::test]
async fn test_resolve_uri_default_transport() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve_uri("sip:user@192.168.1.1:5060").await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].transport, TransportProtocol::Udp);
}
#[tokio::test]
async fn test_resolve_uri_invalid_port_ignored() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve_uri("sip:user@127.0.0.1:99999").await;
assert!(targets.is_ok());
}
#[tokio::test]
async fn test_resolve_uri_zero_port() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve_uri("sip:user@192.168.1.1:0").await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].port, 0);
}
#[tokio::test]
async fn test_resolve_uri_max_port() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve_uri("sip:user@192.168.1.1:65535").await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].port, 65535);
}
#[tokio::test]
async fn test_resolve_uri_transport_case_variations() {
let resolver = SipResolver::new().await.unwrap();
let test_cases = vec![
("transport=udp", TransportProtocol::Udp),
("transport=UDP", TransportProtocol::Udp),
("transport=tcp", TransportProtocol::Tcp),
("transport=TCP", TransportProtocol::Tcp),
("transport=tls", TransportProtocol::Tls),
("transport=TLS", TransportProtocol::Tls),
];
for (param, expected) in test_cases {
let uri = format!("sip:user@192.168.1.1:5060;{}", param);
let targets = resolver.resolve_uri(&uri).await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].transport, expected);
}
}
#[tokio::test]
async fn test_resolve_transport_preference_order() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve("127.0.0.1", None).await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert!(!targets.is_empty());
}
#[test]
fn test_resolver_error_display_formats() {
let err = ResolverError::NoRecords("test.example.com".to_string());
assert_eq!(err.to_string(), "no DNS records found for test.example.com");
let err = ResolverError::InvalidDomain("bad..domain..example".to_string());
assert_eq!(err.to_string(), "invalid domain: bad..domain..example");
}
#[test]
fn test_resolved_target_clone_independence() {
let target = ResolvedTarget {
host: "original.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 10,
weight: 100,
addresses: vec!["192.168.1.1".parse().unwrap()],
};
let mut cloned = target.clone();
cloned.host = "modified.example.com".to_string();
cloned.port = 5070;
assert_eq!(target.host, "original.example.com");
assert_eq!(target.port, 5060);
}
#[test]
fn test_socket_addrs_ipv4_only() {
let target = ResolvedTarget {
host: "ipv4.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 0,
weight: 0,
addresses: vec!["10.0.0.1".parse().unwrap(), "10.0.0.2".parse().unwrap()],
};
let addrs = target.socket_addrs();
assert!(addrs.iter().all(|a| a.is_ipv4()));
}
#[test]
fn test_socket_addrs_ipv6_only() {
let target = ResolvedTarget {
host: "ipv6.example.com".to_string(),
port: 5061,
transport: TransportProtocol::Tls,
priority: 0,
weight: 0,
addresses: vec![
"2001:db8::1".parse().unwrap(),
"2001:db8::2".parse().unwrap(),
],
};
let addrs = target.socket_addrs();
assert!(addrs.iter().all(|a| a.is_ipv6()));
}
#[tokio::test]
async fn test_lookup_address_with_invalid_domain() {
let resolver = SipResolver::new().await.unwrap();
let result = resolver
.lookup_address(
"this.is.definitely.an.invalid.nonexistent.test.domain.12345",
TransportProtocol::Udp,
)
.await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_resolve_without_preferred_transport_ip() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve("127.0.0.1", None).await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert!(!targets.is_empty());
assert_eq!(targets[0].transport, TransportProtocol::Udp);
}
#[tokio::test]
async fn test_resolve_uri_without_port_uses_resolve_path() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver
.resolve_uri("sip:user@127.0.0.1;transport=tcp")
.await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert!(!targets.is_empty());
assert_eq!(targets[0].transport, TransportProtocol::Tcp);
assert_eq!(targets[0].port, 5060); }
#[tokio::test]
async fn test_resolve_uri_tls_without_port() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver
.resolve_uri("sip:user@127.0.0.1;transport=tls")
.await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].transport, TransportProtocol::Tls);
assert_eq!(targets[0].port, 5061); }
#[tokio::test]
async fn test_resolve_uri_param_with_equals() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver
.resolve_uri("sip:user@192.168.1.1:5060;foo=bar=baz;transport=tcp;other=val")
.await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].transport, TransportProtocol::Tcp);
}
#[tokio::test]
async fn test_resolve_with_udp_transport_preference() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver
.resolve("127.0.0.1", Some(TransportProtocol::Udp))
.await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].transport, TransportProtocol::Udp);
assert_eq!(targets[0].port, 5060);
}
#[tokio::test]
async fn test_resolve_with_tls_transport_preference() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver
.resolve("127.0.0.1", Some(TransportProtocol::Tls))
.await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].transport, TransportProtocol::Tls);
assert_eq!(targets[0].port, 5061);
}
#[test]
fn test_resolver_error_from_hickory_error() {
use hickory_resolver::error::ResolveErrorKind;
let hickory_err =
hickory_resolver::error::ResolveError::from(ResolveErrorKind::NoRecordsFound {
query: Box::new(hickory_resolver::proto::op::Query::new()),
soa: None,
negative_ttl: None,
response_code: hickory_resolver::proto::op::ResponseCode::NXDomain,
trusted: false,
});
let resolver_err: ResolverError = hickory_err.into();
assert!(format!("{resolver_err:?}").contains("LookupFailed"));
}
#[tokio::test]
async fn test_lookup_address_port_selection() {
let resolver = SipResolver::new().await.unwrap();
let targets_udp = resolver
.lookup_address("127.0.0.1", TransportProtocol::Udp)
.await
.unwrap();
assert_eq!(targets_udp[0].port, 5060);
let targets_tcp = resolver
.lookup_address("127.0.0.1", TransportProtocol::Tcp)
.await
.unwrap();
assert_eq!(targets_tcp[0].port, 5060);
let targets_tls = resolver
.lookup_address("127.0.0.1", TransportProtocol::Tls)
.await
.unwrap();
assert_eq!(targets_tls[0].port, 5061);
}
#[tokio::test]
async fn test_resolve_addresses_with_ipv6() {
let resolver = SipResolver::new().await.unwrap();
let addrs = resolver.resolve_addresses("::1").await.unwrap();
assert_eq!(addrs.len(), 1);
assert!(addrs[0].is_ipv6());
assert_eq!(addrs[0], "::1".parse::<IpAddr>().unwrap());
}
#[tokio::test]
async fn test_resolve_addresses_with_ipv4() {
let resolver = SipResolver::new().await.unwrap();
let addrs = resolver.resolve_addresses("127.0.0.1").await.unwrap();
assert_eq!(addrs.len(), 1);
assert!(addrs[0].is_ipv4());
assert_eq!(addrs[0], "127.0.0.1".parse::<IpAddr>().unwrap());
}
#[tokio::test]
async fn test_resolve_uri_just_ip_with_port() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve_uri("192.168.1.1:5090").await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].host, "192.168.1.1");
assert_eq!(targets[0].port, 5090);
}
#[tokio::test]
async fn test_lookup_address_no_addresses_returns_error() {
let resolver = SipResolver::new().await.unwrap();
let result = resolver
.lookup_address(
"nonexistent-domain-that-definitely-does-not-exist-12345.invalid",
TransportProtocol::Udp,
)
.await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_resolve_uri_mixed_case_transport_param_key() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver
.resolve_uri("sip:user@192.168.1.1:5060;Transport=tcp")
.await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].transport, TransportProtocol::Tcp);
}
#[tokio::test]
async fn test_resolve_uri_uppercase_transport_param_key() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver
.resolve_uri("sip:user@192.168.1.1:5060;TRANSPORT=UDP")
.await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].transport, TransportProtocol::Udp);
}
#[test]
fn test_resolved_target_with_all_transports() {
let transports = vec![
TransportProtocol::Udp,
TransportProtocol::Tcp,
TransportProtocol::Tls,
];
for transport in transports {
let target = ResolvedTarget {
host: "test.com".to_string(),
port: 5060,
transport,
priority: 0,
weight: 0,
addresses: vec![],
};
assert_eq!(target.transport, transport);
}
}
#[test]
fn test_all_resolver_error_variants_display() {
let err1 = ResolverError::NoRecords("test.com".to_string());
assert!(err1.to_string().contains("test.com"));
assert!(err1.to_string().contains("no DNS records"));
let err2 = ResolverError::InvalidDomain("bad.domain".to_string());
assert!(err2.to_string().contains("bad.domain"));
assert!(err2.to_string().contains("invalid domain"));
}
#[tokio::test]
async fn test_resolve_uri_no_transport_defaults_to_udp() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve_uri("sip:user@192.168.1.1:5060").await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].transport, TransportProtocol::Udp);
}
#[test]
fn test_socket_addrs_various_ports() {
let ports = vec![5060, 5061, 5080, 8080, 65535];
for port in ports {
let target = ResolvedTarget {
host: "test.com".to_string(),
port,
transport: TransportProtocol::Udp,
priority: 0,
weight: 0,
addresses: vec!["192.168.1.1".parse().unwrap()],
};
let addrs = target.socket_addrs();
assert_eq!(addrs[0].port(), port);
}
}
#[test]
fn test_resolved_target_field_access() {
let target = ResolvedTarget {
host: "sip.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Tcp,
priority: 10,
weight: 100,
addresses: vec!["192.168.1.1".parse().unwrap()],
};
let _ = &target.host;
let _ = target.port;
let _ = target.transport;
let _ = target.priority;
let _ = target.weight;
let _ = &target.addresses;
assert_eq!(target.host, "sip.example.com");
assert_eq!(target.port, 5060);
}
#[tokio::test]
async fn test_resolve_uri_port_no_transport() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve_uri("sip:user@192.168.1.1:5070").await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].port, 5070);
assert_eq!(targets[0].transport, TransportProtocol::Udp); }
#[test]
fn test_resolved_target_clone_with_addresses() {
let original = ResolvedTarget {
host: "original.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 10,
weight: 100,
addresses: vec![
"192.168.1.1".parse().unwrap(),
"192.168.1.2".parse().unwrap(),
],
};
let cloned = original.clone();
assert_eq!(cloned.addresses.len(), 2);
assert_eq!(cloned.addresses[0], original.addresses[0]);
assert_eq!(cloned.addresses[1], original.addresses[1]);
}
#[test]
fn test_custom_resolver_config_creation() {
let config = ResolverConfig::new();
let mut opts = ResolverOpts::default();
opts.timeout = std::time::Duration::from_secs(10);
opts.attempts = 3;
let _resolver = SipResolver::with_config(config, opts);
}
#[tokio::test]
async fn test_resolve_uri_parsing_variations() {
let resolver = SipResolver::new().await.unwrap();
let test_cases = vec![
(
"sip:alice@127.0.0.1:5060",
"127.0.0.1",
5060,
TransportProtocol::Udp,
),
(
"sips:bob@[::1]:5061;transport=tls",
"[::1]",
5061,
TransportProtocol::Tls,
),
(
"sip:carol@192.0.2.10:5070;transport=tcp",
"192.0.2.10",
5070,
TransportProtocol::Tcp,
),
];
for (uri, expected_host, expected_port, expected_transport) in test_cases {
let targets = resolver.resolve_uri(uri).await.unwrap();
assert_eq!(targets[0].host, expected_host);
assert_eq!(targets[0].port, expected_port);
assert_eq!(targets[0].transport, expected_transport);
}
}
#[tokio::test]
async fn test_resolve_addresses_with_trailing_dot() {
let resolver = SipResolver::new().await.unwrap();
let result = resolver.resolve_addresses("127.0.0.1.").await;
let _ = result;
}
#[tokio::test]
async fn test_resolve_srv_name_generation() {
let resolver = SipResolver::new().await.unwrap();
let result = resolver
.resolve("nonexistent-test-12345.invalid", None)
.await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_resolve_srv_names_for_each_transport() {
let resolver = SipResolver::new().await.unwrap();
let _ = resolver
.resolve("test-udp.invalid", Some(TransportProtocol::Udp))
.await;
let _ = resolver
.resolve("test-tcp.invalid", Some(TransportProtocol::Tcp))
.await;
let _ = resolver
.resolve("test-tls.invalid", Some(TransportProtocol::Tls))
.await;
}
#[tokio::test]
async fn test_resolve_uri_with_at_symbol() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver
.resolve_uri("sip:user@domain@192.168.1.1:5060")
.await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].host, "192.168.1.1");
}
#[tokio::test]
async fn test_resolve_uri_without_at_symbol() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve_uri("sip:192.168.1.1:5060").await;
assert!(targets.is_ok());
let targets = targets.unwrap();
assert_eq!(targets[0].host, "192.168.1.1");
}
#[tokio::test]
async fn test_resolve_uri_parameter_parsing() {
let resolver = SipResolver::new().await.unwrap();
let targets = resolver.resolve_uri("sip:user@192.168.1.1:5060").await;
assert!(targets.is_ok());
let targets = resolver.resolve_uri("sip:user@192.168.1.1:5060;lr").await;
assert!(targets.is_ok());
let targets = resolver
.resolve_uri("sip:user@192.168.1.1:5060;lr;transport=tcp;maddr=10.0.0.1")
.await;
assert!(targets.is_ok());
assert_eq!(targets.unwrap()[0].transport, TransportProtocol::Tcp);
}
#[test]
fn test_resolver_error_from_trait() {
use hickory_resolver::error::{ResolveError, ResolveErrorKind};
let hickory_err = ResolveError::from(ResolveErrorKind::NoRecordsFound {
query: Box::new(hickory_resolver::proto::op::Query::new()),
soa: None,
negative_ttl: None,
response_code: hickory_resolver::proto::op::ResponseCode::NXDomain,
trusted: false,
});
let resolver_err: ResolverError = hickory_err.into();
assert!(format!("{resolver_err:?}").contains("LookupFailed"));
}
#[test]
fn test_resolved_target_debug_format() {
let target = ResolvedTarget {
host: "test.example.com".to_string(),
port: 5060,
transport: TransportProtocol::Tcp,
priority: 10,
weight: 100,
addresses: vec!["192.168.1.1".parse().unwrap()],
};
let debug_str = format!("{:?}", target);
assert!(debug_str.contains("ResolvedTarget"));
assert!(debug_str.contains("test.example.com"));
}
#[test]
fn test_resolver_error_debug_format() {
let err = ResolverError::NoRecords("test.invalid".to_string());
let debug_str = format!("{:?}", err);
assert!(debug_str.contains("NoRecords"));
assert!(debug_str.contains("test.invalid"));
}
#[test]
fn test_resolved_target_clone_deep_copy() {
let original = ResolvedTarget {
host: "original.com".to_string(),
port: 5060,
transport: TransportProtocol::Udp,
priority: 10,
weight: 100,
addresses: vec!["192.168.1.1".parse().unwrap()],
};
let mut cloned = original.clone();
cloned.host = "modified.com".to_string();
cloned.addresses.push("192.168.1.2".parse().unwrap());
assert_eq!(original.host, "original.com");
assert_eq!(original.addresses.len(), 1);
assert_eq!(cloned.host, "modified.com");
assert_eq!(cloned.addresses.len(), 2);
}
#[test]
fn test_parse_naptr_transport() {
assert_eq!(
parse_naptr_transport("SIP+D2U"),
Some(TransportProtocol::Udp)
);
assert_eq!(
parse_naptr_transport("sip+d2u"),
Some(TransportProtocol::Udp)
);
assert_eq!(
parse_naptr_transport("SIP+D2T"),
Some(TransportProtocol::Tcp)
);
assert_eq!(
parse_naptr_transport("sip+d2t"),
Some(TransportProtocol::Tcp)
);
assert_eq!(
parse_naptr_transport("SIPS+D2T"),
Some(TransportProtocol::Tls)
);
assert_eq!(
parse_naptr_transport("sips+d2t"),
Some(TransportProtocol::Tls)
);
assert_eq!(parse_naptr_transport("SIP+D2X"), None);
assert_eq!(parse_naptr_transport(""), None);
assert_eq!(parse_naptr_transport("unknown"), None);
}
#[test]
fn test_parse_sip_uri_internal() {
let (host, port, transport) = parse_sip_uri_internal("sip:example.com");
assert_eq!(host, "example.com");
assert_eq!(port, None);
assert_eq!(transport, None);
let (host, port, transport) = parse_sip_uri_internal("sip:example.com:5060");
assert_eq!(host, "example.com");
assert_eq!(port, Some(5060));
assert_eq!(transport, None);
let (host, port, transport) = parse_sip_uri_internal("sip:example.com;transport=tcp");
assert_eq!(host, "example.com");
assert_eq!(port, None);
assert_eq!(transport, Some(TransportProtocol::Tcp));
let (host, port, transport) = parse_sip_uri_internal("sip:example.com:5060;transport=tls");
assert_eq!(host, "example.com");
assert_eq!(port, Some(5060));
assert_eq!(transport, Some(TransportProtocol::Tls));
let (host, port, transport) = parse_sip_uri_internal("sips:example.com");
assert_eq!(host, "example.com");
assert_eq!(port, None);
assert_eq!(transport, None);
let (host, port, transport) = parse_sip_uri_internal("sip:example.com:invalid");
assert_eq!(host, "example.com");
assert_eq!(port, None);
assert_eq!(transport, None);
let (host, port, transport) =
parse_sip_uri_internal("sip:example.com;foo=bar;transport=udp;baz");
assert_eq!(host, "example.com");
assert_eq!(port, None);
assert_eq!(transport, Some(TransportProtocol::Udp));
let (host, port, transport) = parse_sip_uri_internal("SIP:example.com;TRANSPORT=TCP");
assert_eq!(host, "example.com");
assert_eq!(port, None);
assert_eq!(transport, Some(TransportProtocol::Tcp));
let (host, port, transport) = parse_sip_uri_internal("sip:[::1]:5060");
assert_eq!(host, "[::1]");
assert_eq!(port, Some(5060));
assert_eq!(transport, None);
}
}