use std::sync::Arc;
use std::time::Duration;
use rustrtc::{
transports::sctp::{DataChannelConfig, DataChannelEvent},
IceCandidateType, IceServer, PeerConnection, PeerConnectionEvent, RtcConfiguration,
};
use tokio::time::timeout;
use turn::relay::relay_static::RelayAddressGeneratorStatic;
use turn::server::config::{ConnConfig, ServerConfig};
use turn::server::Server;
use webrtc_util::vnet::net::Net;
struct TestTurnServer {
_server: Server,
}
impl TestTurnServer {
async fn start() -> anyhow::Result<(Self, String, String, String)> {
let socket = tokio::net::UdpSocket::bind("127.0.0.1:0").await?;
let addr = socket.local_addr()?;
let conn: Arc<tokio::net::UdpSocket> = Arc::new(socket);
let secret = format!("test-secret-{}", std::process::id());
let relay_addr_generator = Box::new(RelayAddressGeneratorStatic {
relay_address: "127.0.0.1".parse()?,
address: "0.0.0.0".to_owned(),
net: Arc::new(Net::new(None)),
});
let auth_handler = turn::auth::LongTermAuthHandler::new(secret.clone());
let config = ServerConfig {
conn_configs: vec![ConnConfig {
conn,
relay_addr_generator,
}],
realm: "test.turn".to_owned(),
auth_handler: Arc::new(auth_handler),
channel_bind_timeout: Duration::from_secs(600),
alloc_close_notify: None,
};
let server = Server::new(config).await?;
let (username, password) =
turn::auth::generate_long_term_credentials(&secret, Duration::from_secs(3600))?;
let url = format!("turn:127.0.0.1:{}", addr.port());
Ok((Self { _server: server }, url, username, password))
}
}
fn turn_ice_servers(url: &str, username: &str, password: &str) -> Vec<IceServer> {
let mut s = IceServer::new(vec![url.to_string()]);
s = s.with_credential(username, password);
vec![s]
}
fn test_config(turn_url: &str, username: &str, password: &str) -> RtcConfiguration {
RtcConfiguration {
ice_servers: turn_ice_servers(turn_url, username, password),
bind_ip: Some("127.0.0.1".to_string()),
ice_connection_timeout: Duration::from_secs(20),
ice_disconnect_threshold: Duration::from_secs(10),
..Default::default()
}
}
#[tokio::test]
async fn test_controlling_side_selects_highest_priority_pair() -> anyhow::Result<()> {
let _ = env_logger::builder().is_test(true).try_init();
unsafe {
std::env::set_var("RUSTRTC_STUN_RESPOND_DELAY_MS", "100");
}
let (_turn, turn_url, username, password) = TestTurnServer::start().await?;
println!("TURN server at {}", turn_url);
let config_a = test_config(&turn_url, &username, &password);
let config_b = config_a.clone();
let pc_a = PeerConnection::new(config_a);
let dc_a = pc_a.create_data_channel(
"ice-priority",
Some(DataChannelConfig {
ordered: true,
..Default::default()
}),
)?;
let pc_b = PeerConnection::new(config_b);
let offer = pc_a.create_offer().await?;
pc_a.set_local_description(offer)?;
pc_a.wait_for_gathering_complete().await;
let offer = pc_a.local_description().unwrap();
pc_b.set_remote_description(offer).await?;
let answer = pc_b.create_answer().await?;
pc_b.set_local_description(answer)?;
pc_b.wait_for_gathering_complete().await;
let answer = pc_b.local_description().unwrap();
pc_a.set_remote_description(answer).await?;
timeout(Duration::from_secs(15), pc_a.wait_for_connected()).await??;
timeout(Duration::from_secs(15), pc_b.wait_for_connected()).await??;
println!("Both peers connected");
let pair_a = pc_a
.ice_transport()
.get_selected_pair()
.expect("controlling side must have a selected pair");
let pair_b = pc_b
.ice_transport()
.get_selected_pair()
.expect("controlled side must have a selected pair");
println!(
"Controlling selected: local {} {:?} -> remote {} {:?}",
pair_a.local.address, pair_a.local.typ, pair_a.remote.address, pair_a.remote.typ
);
println!(
"Controlled selected: local {} {:?} -> remote {} {:?}",
pair_b.local.address, pair_b.local.typ, pair_b.remote.address, pair_b.remote.typ
);
assert!(
matches!(pair_a.local.typ, IceCandidateType::Host)
&& matches!(pair_a.remote.typ, IceCandidateType::Host),
"controlling side selected a lower-priority pair ({} {:?} -> {} {:?}); \
the host->host pair should win the nomination even when it responds \
more slowly than the relay path",
pair_a.local.address, pair_a.local.typ, pair_a.remote.address, pair_a.remote.typ,
);
assert_eq!(
pair_a.remote.address,
pair_b.local.address,
"controlling and controlled sides selected incompatible pairs"
);
let dc_b = {
let mut received = None;
while let Ok(Some(event)) =
timeout(Duration::from_secs(5), pc_b.recv()).await
{
if let PeerConnectionEvent::DataChannel(dc) = event {
received = Some(dc);
break;
}
}
received.expect("controlled side should receive the incoming data channel")
};
let mut dc_a_open = false;
while let Some(event) = dc_a.recv().await {
if let DataChannelEvent::Open = event {
dc_a_open = true;
break;
}
}
assert!(dc_a_open, "data channel should open on the controlling side");
pc_a.send_data(dc_a.id, b"hello-over-host-path").await?;
let mut echoed = false;
let start = std::time::Instant::now();
while start.elapsed() < Duration::from_secs(10) {
if let Ok(Some(DataChannelEvent::Message(msg))) =
timeout(Duration::from_millis(200), dc_b.recv()).await
{
assert_eq!(msg.as_ref(), b"hello-over-host-path");
echoed = true;
break;
}
}
assert!(echoed, "data should be delivered over the data channel");
pc_a.close();
pc_b.close();
Ok(())
}