use super::*;
use crate::transport::packet_channel;
use tokio::time::{Duration, timeout};
fn make_config(port: u16) -> UdpConfig {
UdpConfig {
bind_addr: Some(format!("127.0.0.1:{}", port)),
mtu: Some(1280),
..Default::default()
}
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[test]
fn udp_receive_batch_width_matches_dataplane_reference() {
assert_eq!(UDP_RECV_BATCH_SIZE, 128);
}
#[test]
fn proc_net_snmp_udp_parser_reads_rcvbuf_errors() {
let contents = "\
Ip: Forwarding DefaultTTL InReceives\n\
Ip: 1 64 123\n\
Udp: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors InCsumErrors IgnoredMulti MemErrors\n\
Udp: 10 0 7 12 42 0 0 0 0\n\
";
assert_eq!(parse_proc_net_snmp_udp_rcvbuf_errors(contents), Some(42));
assert_eq!(
parse_proc_net_snmp_udp_rcvbuf_errors("Udp: InDatagrams\n"),
None
);
}
#[tokio::test]
async fn test_start_stop() {
let (tx, _rx) = packet_channel(100);
let mut transport = UdpTransport::new(TransportId::new(1), None, make_config(0), tx);
assert_eq!(transport.state(), TransportState::Configured);
transport.start_async().await.unwrap();
assert_eq!(transport.state(), TransportState::Up);
assert!(transport.local_addr().is_some());
transport.stop_async().await.unwrap();
assert_eq!(transport.state(), TransportState::Down);
}
#[tokio::test]
async fn test_double_start_fails() {
let (tx, _rx) = packet_channel(100);
let mut transport = UdpTransport::new(TransportId::new(1), None, make_config(0), tx);
transport.start_async().await.unwrap();
let result = transport.start_async().await;
assert!(matches!(result, Err(TransportError::AlreadyStarted)));
transport.stop_async().await.unwrap();
}
#[tokio::test]
async fn test_stop_not_started_fails() {
let (tx, _rx) = packet_channel(100);
let mut transport = UdpTransport::new(TransportId::new(1), None, make_config(0), tx);
let result = transport.stop_async().await;
assert!(matches!(result, Err(TransportError::NotStarted)));
}
#[tokio::test]
async fn test_send_recv() {
let (tx1, _rx1) = packet_channel(100);
let (tx2, mut rx2) = packet_channel(100);
let mut t1 = UdpTransport::new(TransportId::new(1), None, make_config(0), tx1);
let mut t2 = UdpTransport::new(TransportId::new(2), None, make_config(0), tx2);
t1.start_async().await.unwrap();
t2.start_async().await.unwrap();
let addr1 = t1.local_addr().unwrap();
let addr2 = t2.local_addr().unwrap();
let data = b"hello world";
let bytes_sent = t1
.send_async(&TransportAddr::from_string(&addr2.to_string()), data)
.await
.unwrap();
assert_eq!(bytes_sent, data.len());
let packet = timeout(Duration::from_secs(1), rx2.recv())
.await
.expect("timeout")
.expect("channel closed");
assert_eq!(packet.data.as_slice(), &data[..]);
assert_eq!(
packet.remote_addr.as_str(),
Some(addr1.to_string().as_str())
);
t1.stop_async().await.unwrap();
t2.stop_async().await.unwrap();
}
#[tokio::test]
async fn test_bidirectional() {
let (tx1, mut rx1) = packet_channel(100);
let (tx2, mut rx2) = packet_channel(100);
let mut t1 = UdpTransport::new(TransportId::new(1), None, make_config(0), tx1);
let mut t2 = UdpTransport::new(TransportId::new(2), None, make_config(0), tx2);
t1.start_async().await.unwrap();
t2.start_async().await.unwrap();
let addr1 = TransportAddr::from_string(&t1.local_addr().unwrap().to_string());
let addr2 = TransportAddr::from_string(&t2.local_addr().unwrap().to_string());
t1.send_async(&addr2, b"ping").await.unwrap();
let packet = timeout(Duration::from_secs(1), rx2.recv())
.await
.expect("timeout")
.expect("channel closed");
assert_eq!(packet.data.as_slice(), &b"ping"[..]);
t2.send_async(&addr1, b"pong").await.unwrap();
let packet = timeout(Duration::from_secs(1), rx1.recv())
.await
.expect("timeout")
.expect("channel closed");
assert_eq!(packet.data.as_slice(), &b"pong"[..]);
t1.stop_async().await.unwrap();
t2.stop_async().await.unwrap();
}
#[tokio::test]
async fn test_mtu_exceeded() {
let (tx, _rx) = packet_channel(100);
let mut transport = UdpTransport::new(
TransportId::new(1),
None,
UdpConfig {
mtu: Some(100),
..make_config(0)
},
tx,
);
transport.start_async().await.unwrap();
let oversized = vec![0u8; 200];
let result = transport
.send_async(&TransportAddr::from_string("127.0.0.1:9999"), &oversized)
.await;
assert!(matches!(result, Err(TransportError::MtuExceeded { .. })));
transport.stop_async().await.unwrap();
}
#[tokio::test]
async fn test_send_not_started() {
let (tx, _rx) = packet_channel(100);
let transport = UdpTransport::new(TransportId::new(1), None, make_config(0), tx);
let result = transport
.send_async(&TransportAddr::from_string("127.0.0.1:9999"), b"test")
.await;
assert!(matches!(result, Err(TransportError::NotStarted)));
}
#[tokio::test]
async fn test_discover_returns_empty() {
let (tx, _rx) = packet_channel(100);
let transport = UdpTransport::new(TransportId::new(1), None, make_config(0), tx);
let peers = transport.discover().unwrap();
assert!(peers.is_empty());
}
#[test]
fn test_transport_type() {
let (tx, _rx) = packet_channel(100);
let transport = UdpTransport::new(TransportId::new(1), None, make_config(0), tx);
assert_eq!(transport.transport_type().name, "udp");
assert!(!transport.transport_type().connection_oriented);
assert!(!transport.transport_type().reliable);
}
#[test]
fn test_sync_methods_return_not_supported() {
let (tx, _rx) = packet_channel(100);
let mut transport = UdpTransport::new(TransportId::new(1), None, make_config(0), tx);
assert!(matches!(
transport.start(),
Err(TransportError::NotSupported(_))
));
assert!(matches!(
transport.stop(),
Err(TransportError::NotSupported(_))
));
assert!(matches!(
transport.send(&TransportAddr::from_string("test"), b"data"),
Err(TransportError::NotSupported(_))
));
}
#[tokio::test]
async fn test_resolve_socket_addr_ip() {
let addr = TransportAddr::from_string("192.168.1.1:2121");
let result = resolve_socket_addr(&addr).await.unwrap();
assert_eq!(result.to_string(), "192.168.1.1:2121");
}
#[tokio::test]
async fn test_resolve_socket_addr_invalid() {
let invalid = TransportAddr::from_string("nonexistent.invalid:2121");
assert!(resolve_socket_addr(&invalid).await.is_err());
let binary = TransportAddr::new(vec![0xff, 0x80]);
assert!(resolve_socket_addr(&binary).await.is_err());
}
#[tokio::test]
async fn test_resolve_socket_addr_hostname() {
let addr = TransportAddr::from_string("localhost:2121");
let result = resolve_socket_addr(&addr).await.unwrap();
assert!(result.ip().is_loopback());
assert_eq!(result.port(), 2121);
}
#[tokio::test]
async fn test_congestion_reports_kernel_drops() {
let (tx, _rx) = packet_channel(100);
let transport = UdpTransport::new(TransportId::new(1), None, make_config(0), tx);
let cong = transport.congestion();
assert_eq!(cong.recv_drops, Some(0));
assert_eq!(cong.socket_recv_drops, Some(0));
#[cfg(target_os = "linux")]
assert_eq!(cong.namespace_recv_drops, Some(0));
#[cfg(not(target_os = "linux"))]
assert_eq!(cong.namespace_recv_drops, None);
}
#[test]
fn test_accept_connections_default_true() {
let (tx, _rx) = packet_channel(100);
let transport = UdpTransport::new(TransportId::new(1), None, make_config(0), tx);
assert!(transport.accept_connections());
}
#[test]
fn test_accept_connections_false_when_configured() {
let (tx, _rx) = packet_channel(100);
let transport = UdpTransport::new(
TransportId::new(1),
None,
UdpConfig {
bind_addr: Some("127.0.0.1:0".to_string()),
accept_connections: Some(false),
..Default::default()
},
tx,
);
assert!(!transport.accept_connections());
}
#[test]
fn test_accept_connections_forced_false_in_outbound_only() {
let (tx, _rx) = packet_channel(100);
let transport = UdpTransport::new(
TransportId::new(1),
None,
UdpConfig {
outbound_only: Some(true),
accept_connections: Some(true), ..Default::default()
},
tx,
);
assert!(!transport.accept_connections());
}
#[tokio::test]
async fn test_outbound_only_binds_ephemeral() {
let (tx, _rx) = packet_channel(100);
let mut transport = UdpTransport::new(
TransportId::new(1),
None,
UdpConfig {
bind_addr: Some("127.0.0.1:65535".to_string()),
outbound_only: Some(true),
..Default::default()
},
tx,
);
transport.start_async().await.unwrap();
let local = transport.local_addr().unwrap();
assert_ne!(local.port(), 65535);
assert!(local.port() > 0);
assert!(local.ip().is_unspecified());
transport.stop_async().await.unwrap();
}
#[tokio::test]
async fn test_punch_probe_dropped() {
let (tx_recv, mut rx_recv) = packet_channel(100);
let (tx_send, _rx_send) = packet_channel(100);
let mut t_recv = UdpTransport::new(TransportId::new(1), None, make_config(0), tx_recv);
let mut t_send = UdpTransport::new(TransportId::new(2), None, make_config(0), tx_send);
t_recv.start_async().await.unwrap();
t_send.start_async().await.unwrap();
let recv_addr = t_recv.local_addr().unwrap();
let recv_addr_str = TransportAddr::from_string(&recv_addr.to_string());
let mut probe = vec![0u8; 16];
probe[..4].copy_from_slice(&0x4E505443u32.to_be_bytes());
t_send.send_async(&recv_addr_str, &probe).await.unwrap();
let mut ack = vec![0u8; 16];
ack[..4].copy_from_slice(&0x4E505441u32.to_be_bytes());
t_send.send_async(&recv_addr_str, &ack).await.unwrap();
let real = b"valid-fmp-frame";
t_send.send_async(&recv_addr_str, real).await.unwrap();
let packet = timeout(Duration::from_secs(1), rx_recv.recv())
.await
.expect("timeout waiting for real packet")
.expect("channel closed");
assert_eq!(packet.data.as_slice(), &real[..]);
let no_more = timeout(Duration::from_millis(200), rx_recv.recv()).await;
assert!(no_more.is_err(), "punch probe/ack leaked through filter");
t_recv.stop_async().await.unwrap();
t_send.stop_async().await.unwrap();
}
#[test]
fn test_is_punch_packet_helper() {
use crate::discovery::is_punch_packet;
assert!(is_punch_packet(&[0x4E, 0x50, 0x54, 0x43, 0xAA, 0xBB]));
assert!(is_punch_packet(&[0x4E, 0x50, 0x54, 0x41]));
assert!(!is_punch_packet(&[0x01, 0x02, 0x03, 0x04]));
assert!(!is_punch_packet(&[0x4E, 0x50, 0x54]));
assert!(!is_punch_packet(&[]));
}
#[tokio::test]
async fn test_send_recv_ip_string() {
let (tx1, _rx1) = packet_channel(100);
let (tx2, mut rx2) = packet_channel(100);
let mut t1 = UdpTransport::new(TransportId::new(1), None, make_config(0), tx1);
let mut t2 = UdpTransport::new(TransportId::new(2), None, make_config(0), tx2);
t1.start_async().await.unwrap();
t2.start_async().await.unwrap();
let port2 = t2.local_addr().unwrap().port();
let data = b"hello via ip string";
let bytes_sent = t1
.send_async(
&TransportAddr::from_string(&format!("127.0.0.1:{}", port2)),
data,
)
.await
.unwrap();
assert_eq!(bytes_sent, data.len());
let packet = timeout(Duration::from_secs(1), rx2.recv())
.await
.expect("timeout")
.expect("channel closed");
assert_eq!(packet.data.as_slice(), &data[..]);
t1.stop_async().await.unwrap();
t2.stop_async().await.unwrap();
}