#![allow(clippy::unwrap_used, clippy::expect_used)]
use ant_quic::nat_traversal_api::{NatTraversalConfig, NatTraversalEndpoint, NatTraversalError};
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
sync::Arc,
time::Duration,
};
fn test_peer_config() -> NatTraversalConfig {
NatTraversalConfig {
known_peers: vec!["127.0.0.1:9000".parse().unwrap()],
max_candidates: 10,
coordination_timeout: Duration::from_secs(5),
enable_symmetric_nat: true,
enable_relay_fallback: false,
max_concurrent_attempts: 5,
bind_addr: None, prefer_rfc_nat_traversal: true,
pqc: None,
timeouts: Default::default(),
identity_key: None,
relay_nodes: vec![],
enable_relay_service: true,
allow_ipv4_mapped: true,
transport_registry: None,
max_message_size: ant_quic::P2pConfig::DEFAULT_MAX_MESSAGE_SIZE,
max_concurrent_uni_streams: 100,
additional_bind_addrs: Vec::new(),
}
}
fn test_server_config() -> NatTraversalConfig {
NatTraversalConfig {
known_peers: vec![],
max_candidates: 20,
coordination_timeout: Duration::from_secs(10),
enable_symmetric_nat: true,
enable_relay_fallback: false,
max_concurrent_attempts: 10,
bind_addr: Some("127.0.0.1:0".parse().unwrap()),
prefer_rfc_nat_traversal: true,
pqc: None,
timeouts: Default::default(),
identity_key: None,
relay_nodes: vec![],
enable_relay_service: true,
allow_ipv4_mapped: true,
transport_registry: None,
max_message_size: ant_quic::P2pConfig::DEFAULT_MAX_MESSAGE_SIZE,
max_concurrent_uni_streams: 100,
additional_bind_addrs: Vec::new(),
}
}
fn assert_endpoint_bound(endpoint: &NatTraversalEndpoint, expected_ip: IpAddr) -> SocketAddr {
let quic_ep = endpoint
.get_endpoint()
.expect("endpoint should expose inner QUIC endpoint");
let addr = quic_ep
.local_addr()
.expect("endpoint should have a local socket address");
assert_ne!(addr.port(), 0, "endpoint should bind a non-zero port");
assert_eq!(addr.ip(), expected_ip, "endpoint should bind expected IP");
addr
}
fn is_udp_bind_blocked(error: &NatTraversalError) -> bool {
matches!(
error,
NatTraversalError::NetworkError(message)
if message.contains("Failed to bind UDP socket")
&& (message.contains("Operation not permitted")
|| message.contains("Permission denied"))
)
}
fn endpoint_or_skip_udp_blocked(
result: Result<NatTraversalEndpoint, NatTraversalError>,
context: &str,
) -> Option<NatTraversalEndpoint> {
match result {
Ok(endpoint) => Some(endpoint),
Err(error) => {
assert!(
is_udp_bind_blocked(&error),
"{context} failed before endpoint construction: {error}"
);
println!("Skipping {context}: UDP bind blocked by test environment: {error}");
None
}
}
}
fn assert_config_error_contains(
result: &Result<NatTraversalEndpoint, NatTraversalError>,
expected_fragment: &str,
) {
assert!(
matches!(
result,
Err(NatTraversalError::ConfigError(message)) if message.contains(expected_fragment)
),
"invalid config should fail with ConfigError containing {expected_fragment}"
);
}
#[tokio::test]
async fn test_random_port_binding_no_panic() {
let config = test_peer_config();
if let Some(endpoint) = endpoint_or_skip_udp_blocked(
NatTraversalEndpoint::new(config, None, None).await,
"random port binding",
) {
let addr = assert_endpoint_bound(&endpoint, IpAddr::V4(Ipv4Addr::UNSPECIFIED));
println!("✓ Random port binding succeeded: {addr}");
}
}
#[tokio::test]
async fn test_error_handling_no_panic() {
let config1 = NatTraversalConfig {
known_peers: vec!["127.0.0.1:9000".parse().unwrap()],
max_candidates: 10,
coordination_timeout: Duration::from_secs(0), enable_symmetric_nat: true,
enable_relay_fallback: false,
max_concurrent_attempts: 5,
bind_addr: Some("127.0.0.1:0".parse().unwrap()),
prefer_rfc_nat_traversal: true,
pqc: None,
timeouts: Default::default(),
identity_key: None,
relay_nodes: vec![],
enable_relay_service: true,
allow_ipv4_mapped: true,
transport_registry: None,
max_message_size: ant_quic::P2pConfig::DEFAULT_MAX_MESSAGE_SIZE,
max_concurrent_uni_streams: 100,
additional_bind_addrs: Vec::new(),
};
let result1 = NatTraversalEndpoint::new(config1, None, None).await;
assert_config_error_contains(&result1, "coordination_timeout");
let config2 = NatTraversalConfig {
known_peers: vec!["127.0.0.1:9000".parse().unwrap()],
max_candidates: 0, coordination_timeout: Duration::from_secs(10),
enable_symmetric_nat: true,
enable_relay_fallback: false,
max_concurrent_attempts: 5,
bind_addr: Some("127.0.0.1:0".parse().unwrap()),
prefer_rfc_nat_traversal: true,
pqc: None,
timeouts: Default::default(),
identity_key: None,
relay_nodes: vec![],
enable_relay_service: true,
allow_ipv4_mapped: true,
transport_registry: None,
max_message_size: ant_quic::P2pConfig::DEFAULT_MAX_MESSAGE_SIZE,
max_concurrent_uni_streams: 100,
additional_bind_addrs: Vec::new(),
};
let result2 = NatTraversalEndpoint::new(config2, None, None).await;
assert_config_error_contains(&result2, "max_candidates");
}
#[tokio::test]
async fn test_concurrent_creation_safety() {
const NUM_CONCURRENT: usize = 10;
let handles: Vec<_> = (0..NUM_CONCURRENT)
.map(|i| {
tokio::spawn(async move {
let mut config = test_peer_config();
config.bind_addr = Some(format!("127.0.0.1:{}", 10000 + i).parse().unwrap());
let result = NatTraversalEndpoint::new(config, None, None).await;
(i, result.is_ok())
})
})
.collect();
let results: Vec<_> = futures_util::future::join_all(handles)
.await
.into_iter()
.map(|r| r.expect("Task should not panic"))
.collect();
assert_eq!(results.len(), NUM_CONCURRENT, "All tasks should complete");
let successful = results.iter().filter(|(_, success)| *success).count();
println!("✓ Concurrent creation test: {successful}/{NUM_CONCURRENT} succeeded");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_statistics_concurrent_access() {
const NUM_CONCURRENT: usize = 20;
let config = test_server_config();
let endpoint_result = NatTraversalEndpoint::new(config, None, None).await;
if let Ok(endpoint) = endpoint_result {
let endpoint = Arc::new(endpoint);
let barrier = Arc::new(tokio::sync::Barrier::new(NUM_CONCURRENT));
let handles: Vec<_> = (0..NUM_CONCURRENT)
.map(|_| {
let ep = Arc::clone(&endpoint);
let barrier = Arc::clone(&barrier);
tokio::spawn(async move {
barrier.wait().await;
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| ep.get_statistics()))
})
})
.collect();
let results = tokio::time::timeout(
Duration::from_secs(5),
futures_util::future::join_all(handles),
)
.await
.expect("concurrent statistics calls should complete");
for (i, result) in results.into_iter().enumerate() {
let result = result.expect("statistics task should join");
assert!(result.is_ok(), "Statistics call {i} should not panic");
}
println!("✓ Concurrent statistics access completed safely");
}
}
#[tokio::test]
async fn test_malformed_config_handling() {
let no_peers_config = NatTraversalConfig {
known_peers: vec![], max_candidates: 10,
coordination_timeout: Duration::from_secs(10),
enable_symmetric_nat: true,
enable_relay_fallback: false,
max_concurrent_attempts: 5,
bind_addr: Some("127.0.0.1:0".parse().unwrap()),
prefer_rfc_nat_traversal: true,
pqc: None,
timeouts: Default::default(),
identity_key: None,
relay_nodes: vec![],
enable_relay_service: true,
allow_ipv4_mapped: true,
transport_registry: None,
max_message_size: ant_quic::P2pConfig::DEFAULT_MAX_MESSAGE_SIZE,
max_concurrent_uni_streams: 100,
additional_bind_addrs: Vec::new(),
};
if let Some(endpoint) = endpoint_or_skip_udp_blocked(
NatTraversalEndpoint::new(no_peers_config, None, None).await,
"no peers config",
) {
let addr = assert_endpoint_bound(&endpoint, IpAddr::V4(Ipv4Addr::LOCALHOST));
println!("✓ No peers config accepted: {addr}");
}
let extreme_config = NatTraversalConfig {
known_peers: vec!["127.0.0.1:9000".parse().unwrap()],
max_candidates: usize::MAX, coordination_timeout: Duration::from_secs(u64::MAX / 1000), enable_symmetric_nat: true,
enable_relay_fallback: false,
max_concurrent_attempts: usize::MAX,
bind_addr: Some("127.0.0.1:0".parse().unwrap()),
prefer_rfc_nat_traversal: true,
pqc: None,
timeouts: Default::default(),
identity_key: None,
relay_nodes: vec![],
enable_relay_service: true,
allow_ipv4_mapped: true,
transport_registry: None,
max_message_size: ant_quic::P2pConfig::DEFAULT_MAX_MESSAGE_SIZE,
max_concurrent_uni_streams: 100,
additional_bind_addrs: Vec::new(),
};
let result2 = NatTraversalEndpoint::new(extreme_config, None, None).await;
assert_config_error_contains(&result2, "max_candidates");
}
#[tokio::test]
async fn test_input_sanitization() {
let many_peers: Vec<_> = (9000..9200)
.map(|port| format!("127.0.0.1:{port}").parse().unwrap())
.collect();
let large_peer_config = NatTraversalConfig {
known_peers: many_peers, max_candidates: 10,
coordination_timeout: Duration::from_secs(10),
enable_symmetric_nat: true,
enable_relay_fallback: false,
max_concurrent_attempts: 5,
bind_addr: Some("127.0.0.1:0".parse().unwrap()),
prefer_rfc_nat_traversal: true,
pqc: None,
timeouts: Default::default(),
identity_key: None,
relay_nodes: vec![],
enable_relay_service: true,
allow_ipv4_mapped: true,
transport_registry: None,
max_message_size: ant_quic::P2pConfig::DEFAULT_MAX_MESSAGE_SIZE,
max_concurrent_uni_streams: 100,
additional_bind_addrs: Vec::new(),
};
let start_time = std::time::Instant::now();
let result = NatTraversalEndpoint::new(large_peer_config, None, None).await;
let duration = start_time.elapsed();
assert!(
duration < Duration::from_secs(30),
"Large config processing took too long"
);
match result {
Ok(_) => println!("✓ Large peer list handled successfully in {duration:?}"),
Err(e) => println!("✓ Large peer list rejected safely in {duration:?}: {e}"),
}
}
#[tokio::test]
async fn test_resource_cleanup() {
for i in 0..20 {
let mut config = test_peer_config();
config.bind_addr = Some(format!("127.0.0.1:{}", 11000 + i).parse().unwrap());
let endpoint_result = NatTraversalEndpoint::new(config, None, None).await;
if let Ok(endpoint) = endpoint_result {
let _stats = endpoint.get_statistics();
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
println!("✓ Resource cleanup test completed - no obvious leaks");
}
#[cfg(test)]
mod specific_regression_tests {
use super::*;
#[tokio::test]
async fn test_commit_6e633cd9_protocol_obfuscation() {
let config_with_none = NatTraversalConfig {
known_peers: vec!["127.0.0.1:9000".parse().unwrap()],
max_candidates: 10,
coordination_timeout: Duration::from_secs(10),
enable_symmetric_nat: true,
enable_relay_fallback: false,
max_concurrent_attempts: 5,
bind_addr: None, prefer_rfc_nat_traversal: true,
pqc: None,
timeouts: Default::default(),
identity_key: None,
relay_nodes: vec![],
enable_relay_service: true,
allow_ipv4_mapped: true,
transport_registry: None,
max_message_size: ant_quic::P2pConfig::DEFAULT_MAX_MESSAGE_SIZE,
max_concurrent_uni_streams: 100,
additional_bind_addrs: Vec::new(),
};
if let Some(endpoint) = endpoint_or_skip_udp_blocked(
NatTraversalEndpoint::new(config_with_none, None, None).await,
"protocol obfuscation random port binding",
) {
let addr = assert_endpoint_bound(&endpoint, IpAddr::V4(Ipv4Addr::UNSPECIFIED));
println!("✓ Random port binding successful: {addr}");
}
}
#[tokio::test]
async fn test_commit_a7d1de11_robust_error_handling() {
let problematic_config = NatTraversalConfig {
known_peers: vec!["127.0.0.1:9000".parse().unwrap()],
max_candidates: 0,
coordination_timeout: Duration::from_secs(0),
enable_symmetric_nat: false,
enable_relay_fallback: false,
max_concurrent_attempts: 0,
bind_addr: None,
prefer_rfc_nat_traversal: true,
pqc: None,
timeouts: Default::default(),
identity_key: None,
relay_nodes: vec![],
enable_relay_service: true,
allow_ipv4_mapped: true,
transport_registry: None,
max_message_size: ant_quic::P2pConfig::DEFAULT_MAX_MESSAGE_SIZE,
max_concurrent_uni_streams: 100,
additional_bind_addrs: Vec::new(),
};
let result = NatTraversalEndpoint::new(problematic_config, None, None).await;
assert_config_error_contains(&result, "max_candidates");
}
}