#![allow(dead_code)]
#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing,
clippy::cast_possible_truncation
)]
use std::net::{IpAddr, SocketAddr};
use std::time::Duration;
use sipx_audio::g711;
use sipx_call::Call;
pub(crate) fn echo_uri() -> String {
std::env::var("SIPX_INTEROP_ECHO_URI").unwrap_or_else(|_| "sip:echo@127.0.0.1:5060".to_owned())
}
pub(crate) fn addr_in(uri: &str) -> SocketAddr {
let host = uri.rsplit('@').next().unwrap_or_default();
host.parse()
.unwrap_or_else(|e| panic!("{host}: not an address and port ({e}); check the peer profile"))
}
pub(crate) fn loopback() -> IpAddr {
"127.0.0.1".parse().expect("valid")
}
pub(crate) fn tone(milliseconds: usize) -> Vec<i16> {
let samples = milliseconds * 8;
(0..samples)
.map(|i| {
let t = f64::from(u32::try_from(i).unwrap_or(0)) / 8000.0;
let envelope = (t * 4.0).min(1.0);
let value = (t * 440.0 * 2.0 * std::f64::consts::PI).sin() * 12000.0 * envelope;
i16::try_from(value.round() as i32).unwrap_or(0)
})
.collect()
}
pub(crate) fn longest_aligned_match(sent: &[u8], received: &[u8]) -> usize {
let window = 320; if sent.len() < window || received.len() < window {
return 0;
}
let probe = &received[..window];
let Some(offset) = sent.windows(window).position(|w| w == probe) else {
return 0;
};
sent[offset..]
.iter()
.zip(received.iter())
.take_while(|(a, b)| a == b)
.count()
}
pub(crate) struct Echoed {
pub payload_types: Vec<u8>,
pub payload: Vec<u8>,
}
pub(crate) async fn echo_round_trip(call: &Call) -> (Vec<u8>, Echoed) {
let source = tone(600);
let media = call.media();
media.set_relay(true);
let played = source.clone();
let collected = tokio::join!(async { media.play(&played, 160).await }, async {
let mut echoed = Echoed {
payload_types: Vec::new(),
payload: Vec::new(),
};
let mut window = Duration::from_secs(10);
while let Ok(Some(packet)) = tokio::time::timeout(window, media.recv_encoded()).await {
echoed.payload_types.push(packet.payload_type);
echoed.payload.extend_from_slice(&packet.payload);
window = Duration::from_millis(600);
}
echoed
})
.1;
media.set_relay(false);
(g711::ulaw_encode_all(&source), collected)
}
pub(crate) fn assert_echo(sent: &[u8], echoed: &Echoed, expected_payload_type: u8) {
assert!(
!echoed.payload.is_empty(),
"the call connected and no audio came back; a session was set up and nothing was heard"
);
let unexpected: Vec<u8> = echoed
.payload_types
.iter()
.copied()
.filter(|pt| *pt != expected_payload_type)
.collect();
assert!(
unexpected.is_empty(),
"the peer sent payload types {unexpected:?} where the negotiation chose \
{expected_payload_type}"
);
let floor = sent.len().saturating_sub(3 * 160);
let matched = longest_aligned_match(sent, &echoed.payload);
assert!(
matched >= floor,
"only {matched} of the {} bytes sent came back unchanged; µ-law in and µ-law out \
means nothing on the path should have transcoded it",
sent.len()
);
}