#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing,
clippy::cast_possible_truncation
)]
#![allow(clippy::similar_names)]
mod interop_media;
use std::net::SocketAddr;
use std::time::Duration;
use bytes::Bytes;
use sipx_call::{DialOptions, answer, dial};
use sipx_sip::{Method, Uri};
use sipx_transport::{Config as TransportConfig, Target, bind};
use interop_media::{addr_in, assert_echo, echo_round_trip, echo_uri, loopback};
fn answering_port() -> u16 {
std::env::var("SIPX_INTEROP_UA_PORT")
.ok()
.and_then(|port| port.parse().ok())
.unwrap_or(5080)
}
fn peer_originates() -> std::process::Output {
let container = std::env::var("SIPX_INTEROP_CONTAINER").unwrap_or_else(|_| {
panic!("SIPX_INTEROP_CONTAINER is unset; run this through tests/interop/run.sh")
});
let channel =
std::env::var("SIPX_INTEROP_ORIGINATE").unwrap_or_else(|_| "PJSIP/sipx-ua".to_owned());
std::process::Command::new("docker")
.args([
"exec",
&container,
"asterisk",
"-rx",
&format!("channel originate {channel} application Echo"),
])
.output()
.expect("docker exec runs")
}
#[tokio::test]
#[ignore = "needs a user agent peer; see tests/interop/README.md"]
async fn an_independent_user_agent_answers_a_call_sipx_placed() {
let mut config = TransportConfig::new("127.0.0.1:0".parse().expect("valid"));
config.sent_by = loopback().to_string();
let (handle, _incoming) = bind(config).await.expect("binds");
let to = Uri::parse(Bytes::from(echo_uri())).expect("a SIP URI");
let options =
DialOptions::new("<sip:sipx@127.0.0.1>", loopback()).with_timeout(Duration::from_secs(15));
let mut call = tokio::time::timeout(
Duration::from_secs(20),
dial(&handle, Target::udp(addr_in(&echo_uri())), &to, &options),
)
.await
.expect("the peer answers rather than leaving us ringing")
.expect("the peer accepts the call");
let codec = call.media().codec();
assert_eq!(
codec.payload_type(),
0,
"the negotiation chose {codec:?}; the offer's first and the peer's configured codec is µ-law"
);
let (sent, echoed) = echo_round_trip(&call).await;
assert_echo(&sent, &echoed, codec.payload_type());
call.hang_up().await.expect("the BYE is accepted");
assert!(call.is_ended(), "the call is over on our side too");
}
#[tokio::test]
#[ignore = "needs a user agent peer; see tests/interop/README.md"]
async fn an_independent_user_agent_places_a_call_sipx_answers() {
let local: SocketAddr = format!("127.0.0.1:{}", answering_port())
.parse()
.expect("valid");
let mut config = TransportConfig::new(local);
config.sent_by = loopback().to_string();
let (handle, mut incoming) = bind(config)
.await
.unwrap_or_else(|e| panic!("binding {local}: {e}; the peer's contact names this port"));
let originate = std::thread::spawn(peer_originates);
let request = tokio::time::timeout(Duration::from_secs(20), incoming.recv())
.await
.expect("the peer places the call within twenty seconds")
.expect("an incoming request");
assert_eq!(request.request.method, Method::Invite);
let mut call = answer(&handle, &request, loopback())
.await
.expect("sipx answers the foreign offer");
let codec = call.media().codec();
assert_eq!(
codec.payload_type(),
0,
"sipx answered a foreign offer with {codec:?}"
);
let (sent, echoed) = echo_round_trip(&call).await;
assert_echo(&sent, &echoed, codec.payload_type());
call.hang_up().await.expect("the BYE is accepted");
let output = originate.join().expect("the originate command finishes");
assert!(
output.status.success(),
"the peer refused to place the call: {}{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}