use std::net::Ipv4Addr;
use std::time::{Duration, Instant};
use str0m::media::{Direction, MediaKind};
use str0m::{Event, RtcConfig, RtcError};
use tracing::info_span;
mod common;
use common::{Peer, TestRtc, init_crypto_default, init_log, negotiate, progress};
#[test]
fn config_reordering_size_custom() -> Result<(), RtcError> {
init_log();
init_crypto_default();
let config = RtcConfig::new()
.set_reordering_size_audio(20)
.set_reordering_size_video(50);
assert_eq!(
config.reordering_size_audio(),
20,
"Audio reordering size should be 20"
);
assert_eq!(
config.reordering_size_video(),
50,
"Video reordering size should be 50"
);
let default_config = RtcConfig::new();
assert_eq!(
default_config.reordering_size_audio(),
15,
"Default audio reordering should be 15"
);
assert_eq!(
default_config.reordering_size_video(),
30,
"Default video reordering should be 30"
);
let rtc = config.build(Instant::now());
let mut l = TestRtc::new_with_rtc(info_span!("L"), rtc);
let mut r = TestRtc::new(Peer::Right);
l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into());
r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into());
let mid = negotiate(&mut l, &mut r, |change| {
change.add_media(MediaKind::Audio, Direction::SendRecv, None, None, None)
});
loop {
if l.is_connected() && r.is_connected() {
break;
}
if l.duration() > Duration::from_secs(5) {
panic!("Failed to connect");
}
progress(&mut l, &mut r)?;
}
let params = l.params_opus();
let pt = params.pt();
let data = vec![1_u8; 80];
for _ in 0..20 {
let wallclock = l.start + l.duration();
let time = l.duration().into();
l.writer(mid)
.unwrap()
.write(pt, wallclock, time, data.clone())?;
progress(&mut l, &mut r)?;
}
let received_count = r
.events
.iter()
.filter(|(_, e)| matches!(e, Event::MediaData(_)))
.count();
assert!(
received_count > 10,
"Should receive media data with custom reordering config, got {}",
received_count
);
Ok(())
}
#[test]
fn config_raw_packets_enabled() -> Result<(), RtcError> {
init_log();
init_crypto_default();
let rtc = RtcConfig::new()
.enable_raw_packets(true)
.build(Instant::now());
let mut l = TestRtc::new_with_rtc(info_span!("L"), rtc);
let rtc_r = RtcConfig::new()
.enable_raw_packets(true)
.build(Instant::now());
let mut r = TestRtc::new_with_rtc(info_span!("R"), rtc_r);
l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into());
r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into());
let mid = negotiate(&mut l, &mut r, |change| {
change.add_media(MediaKind::Audio, Direction::SendRecv, None, None, None)
});
loop {
if l.is_connected() && r.is_connected() {
break;
}
if l.duration() > Duration::from_secs(5) {
panic!("Failed to connect");
}
progress(&mut l, &mut r)?;
}
l.events.clear();
r.events.clear();
let params = l.params_opus();
let pt = params.pt();
let data = vec![1_u8; 80];
for _ in 0..20 {
let wallclock = l.start + l.duration();
let time = l.duration().into();
l.writer(mid)
.unwrap()
.write(pt, wallclock, time, data.clone())?;
progress(&mut l, &mut r)?;
}
let raw_packet_count = r
.events
.iter()
.filter(|(_, e)| matches!(e, Event::RawPacket(_)))
.count();
assert!(
raw_packet_count > 0,
"Should have RawPacket events with enable_raw_packets(true)"
);
Ok(())
}
#[test]
fn config_stats_interval_custom() -> Result<(), RtcError> {
init_log();
init_crypto_default();
let stats_interval = Duration::from_secs(1);
let rtc = RtcConfig::new()
.set_stats_interval(Some(stats_interval))
.build(Instant::now());
let mut l = TestRtc::new_with_rtc(info_span!("L"), rtc);
let rtc_r = RtcConfig::new()
.set_stats_interval(Some(stats_interval))
.build(Instant::now());
let mut r = TestRtc::new_with_rtc(info_span!("R"), rtc_r);
l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into());
r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into());
let mid = negotiate(&mut l, &mut r, |change| {
change.add_media(MediaKind::Audio, Direction::SendRecv, None, None, None)
});
loop {
if l.is_connected() && r.is_connected() {
break;
}
progress(&mut l, &mut r)?;
}
let max = l.last.max(r.last);
l.last = max;
r.last = max;
let params = l.params_opus();
let pt = params.pt();
let data = vec![1_u8; 80];
l.set_forced_time_advance(Duration::from_millis(1));
r.set_forced_time_advance(Duration::from_millis(1));
loop {
let wallclock = l.start + l.duration();
let time = l.duration().into();
l.writer(mid)
.unwrap()
.write(pt, wallclock, time, data.clone())?;
progress(&mut l, &mut r)?;
if l.duration() > Duration::from_secs(5) {
break;
}
}
let peer_stats_count = l
.events
.iter()
.filter(|(_, e)| matches!(e, Event::PeerStats(_)))
.count();
assert!(
peer_stats_count >= 3,
"Expected at least 3 PeerStats events with 1s interval over 5s, got {}",
peer_stats_count
);
Ok(())
}
#[test]
fn config_stats_disabled() -> Result<(), RtcError> {
init_log();
init_crypto_default();
let rtc = RtcConfig::new()
.set_stats_interval(None)
.build(Instant::now());
let mut l = TestRtc::new_with_rtc(info_span!("L"), rtc);
let rtc_r = RtcConfig::new()
.set_stats_interval(None)
.build(Instant::now());
let mut r = TestRtc::new_with_rtc(info_span!("R"), rtc_r);
l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into());
r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into());
let mid = negotiate(&mut l, &mut r, |change| {
change.add_media(MediaKind::Audio, Direction::SendRecv, None, None, None)
});
loop {
if l.is_connected() && r.is_connected() {
break;
}
progress(&mut l, &mut r)?;
}
let max = l.last.max(r.last);
l.last = max;
r.last = max;
let params = l.params_opus();
let pt = params.pt();
let data = vec![1_u8; 80];
l.set_forced_time_advance(Duration::from_millis(1));
r.set_forced_time_advance(Duration::from_millis(1));
loop {
let wallclock = l.start + l.duration();
let time = l.duration().into();
l.writer(mid)
.unwrap()
.write(pt, wallclock, time, data.clone())?;
progress(&mut l, &mut r)?;
if l.duration() > Duration::from_secs(3) {
break;
}
}
let peer_stats_count = l
.events
.iter()
.filter(|(_, e)| matches!(e, Event::PeerStats(_)))
.count();
assert_eq!(
peer_stats_count, 0,
"Should have no PeerStats events when stats disabled"
);
Ok(())
}
#[test]
fn config_fingerprint_verification_disabled() -> Result<(), RtcError> {
init_log();
init_crypto_default();
use str0m::Candidate;
use str0m::crypto::Fingerprint;
let rtc_l = RtcConfig::new()
.set_fingerprint_verification(false)
.set_rtp_mode(true)
.build(Instant::now());
let rtc_r = RtcConfig::new()
.set_fingerprint_verification(false)
.set_rtp_mode(true)
.build(Instant::now());
let mut l = TestRtc::new_with_rtc(info_span!("L"), rtc_l);
let mut r = TestRtc::new_with_rtc(info_span!("R"), rtc_r);
let host1 = Candidate::host((Ipv4Addr::new(1, 1, 1, 1), 1000).into(), "udp").unwrap();
let host2 = Candidate::host((Ipv4Addr::new(2, 2, 2, 2), 2000).into(), "udp").unwrap();
l.add_local_candidate(host1.clone()).unwrap();
l.add_remote_candidate(host2.clone());
r.add_local_candidate(host2).unwrap();
r.add_remote_candidate(host1);
let corrupted_fingerprint = Fingerprint {
hash_func: "sha-256".to_string(),
bytes: vec![0u8; 32], };
l.direct_api()
.set_remote_fingerprint(corrupted_fingerprint.clone());
r.direct_api().set_remote_fingerprint(corrupted_fingerprint);
let creds_l = l.direct_api().local_ice_credentials();
let creds_r = r.direct_api().local_ice_credentials();
l.direct_api().set_remote_ice_credentials(creds_r);
r.direct_api().set_remote_ice_credentials(creds_l);
l.direct_api().set_ice_controlling(true);
r.direct_api().set_ice_controlling(false);
l.direct_api().start_dtls(true).unwrap();
r.direct_api().start_dtls(false).unwrap();
l.direct_api().start_sctp(true);
r.direct_api().start_sctp(false);
loop {
if l.is_connected() && r.is_connected() {
break;
}
if l.duration() > Duration::from_secs(5) {
panic!("Failed to connect - fingerprint verification should be disabled");
}
progress(&mut l, &mut r)?;
}
assert!(l.is_connected(), "L should be connected");
assert!(r.is_connected(), "R should be connected");
Ok(())
}
#[test]
fn config_clone_multiple_instances() -> Result<(), RtcError> {
init_log();
init_crypto_default();
let config = RtcConfig::new()
.set_reordering_size_audio(20)
.set_reordering_size_video(40);
let rtc1 = config.clone().build(Instant::now());
let rtc2 = config.build(Instant::now());
let mut l = TestRtc::new_with_rtc(info_span!("L"), rtc1);
let mut r = TestRtc::new_with_rtc(info_span!("R"), rtc2);
l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into());
r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into());
let (offer, pending) = l.span.in_scope(|| {
let mut change = l.rtc.sdp_api();
let _ = change.add_channel("test".into());
change.apply().unwrap()
});
let answer = r.span.in_scope(|| r.rtc.sdp_api().accept_offer(offer))?;
l.span
.in_scope(|| l.rtc.sdp_api().accept_answer(pending, answer))?;
loop {
if l.is_connected() && r.is_connected() {
break;
}
if l.duration() > Duration::from_secs(5) {
panic!("Failed to connect with cloned config");
}
progress(&mut l, &mut r)?;
}
Ok(())
}
#[test]
fn config_send_buffer_sizes() -> Result<(), RtcError> {
init_log();
init_crypto_default();
let config = RtcConfig::new()
.set_send_buffer_audio(100)
.set_send_buffer_video(2000);
assert_eq!(
config.send_buffer_audio(),
100,
"Audio send buffer should be 100"
);
assert_eq!(
config.send_buffer_video(),
2000,
"Video send buffer should be 2000"
);
let default_config = RtcConfig::new();
assert_eq!(
default_config.send_buffer_audio(),
50,
"Default audio send buffer should be 50"
);
assert_eq!(
default_config.send_buffer_video(),
1000,
"Default video send buffer should be 1000"
);
let rtc = config.build(Instant::now());
let mut l = TestRtc::new_with_rtc(info_span!("L"), rtc);
let mut r = TestRtc::new(Peer::Right);
l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into());
r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into());
let mid = negotiate(&mut l, &mut r, |change| {
change.add_media(MediaKind::Audio, Direction::SendRecv, None, None, None)
});
loop {
if l.is_connected() && r.is_connected() {
break;
}
if l.duration() > Duration::from_secs(5) {
panic!("Failed to connect");
}
progress(&mut l, &mut r)?;
}
let params = l.params_opus();
let pt = params.pt();
let data = vec![1_u8; 80];
for _ in 0..20 {
let wallclock = l.start + l.duration();
let time = l.duration().into();
l.writer(mid)
.unwrap()
.write(pt, wallclock, time, data.clone())?;
progress(&mut l, &mut r)?;
}
let received_count = r
.events
.iter()
.filter(|(_, e)| matches!(e, Event::MediaData(_)))
.count();
assert!(
received_count > 10,
"Should receive media data with custom send buffer config, got {}",
received_count
);
Ok(())
}