use std::net::Ipv4Addr;
use std::time::{Duration, Instant};
use str0m::media::{Direction, MediaKind};
use str0m::rtp::RawPacket;
use str0m::rtp::rtcp::{ReportBlock, Rtcp};
use str0m::{Rtc, RtcError};
use tracing::info_span;
mod common;
use common::{TestRtc, init_crypto_default, init_log, negotiate, progress};
#[test]
pub fn dlrr_response_to_rrtr() -> Result<(), RtcError> {
init_log();
init_crypto_default();
let now = Instant::now();
let l_rtc = Rtc::builder().enable_raw_packets(true).build(now);
let r_rtc = Rtc::builder().enable_raw_packets(true).build(now);
let mut l = TestRtc::new_with_rtc(info_span!("L"), l_rtc);
let mut r = TestRtc::new_with_rtc(info_span!("R"), r_rtc);
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::SendOnly, 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 = [1_u8; 80];
loop {
let wallclock = l.start + l.duration();
let time = l.duration().into();
l.writer(mid).unwrap().write(pt, wallclock, time, data)?;
progress(&mut l, &mut r)?;
if l.duration() > Duration::from_secs(15) {
break;
}
}
let media_ssrc = l
.events
.iter()
.find_map(|(_, e)| match e.as_raw_packet() {
Some(RawPacket::RtpTx(header, _)) => Some(header.ssrc),
_ => None,
})
.expect("L should have sent RTP");
let rrtr_ssrc = r
.events
.iter()
.find_map(|(_, e)| {
let Some(RawPacket::RtcpTx(Rtcp::ExtendedReport(xr))) = e.as_raw_packet() else {
return None;
};
xr.blocks
.iter()
.any(|b| matches!(b, ReportBlock::Rrtr(_)))
.then_some(xr.ssrc)
})
.expect("R should have sent at least one RRTR");
assert_ne!(rrtr_ssrc, media_ssrc);
let dlrr_reports: Vec<_> = l
.events
.iter()
.filter_map(|(_, e)| {
if let Some(RawPacket::RtcpTx(Rtcp::ExtendedReport(xr))) = e.as_raw_packet() {
if xr.blocks.iter().any(|b| matches!(b, ReportBlock::Dlrr(_))) {
return Some(xr);
}
}
None
})
.collect();
assert!(
!dlrr_reports.is_empty(),
"L should have sent DLRR in response to RRTR"
);
let dlrr = dlrr_reports[0]
.blocks
.iter()
.find_map(|b| match b {
ReportBlock::Dlrr(d) => Some(d),
_ => None,
})
.unwrap();
assert!(!dlrr.items.is_empty(), "DLRR should have at least one item");
let item = &dlrr.items[0];
assert_eq!(item.ssrc, rrtr_ssrc);
assert!(
item.last_rr_time != 0,
"DLRR last_rr_time should be non-zero"
);
let r_received_dlrr = r.events.iter().any(|(_, e)| {
if let Some(RawPacket::RtcpRx(Rtcp::ExtendedReport(xr))) = e.as_raw_packet() {
xr.blocks.iter().any(|b| {
if let ReportBlock::Dlrr(d) = b {
d.items.iter().any(|i| i.last_rr_time == item.last_rr_time)
} else {
false
}
})
} else {
false
}
});
assert!(
r_received_dlrr,
"R should have received the DLRR with matching last_rr_time"
);
Ok(())
}