use anyhow::Result;
use rtc::peer_connection::RTCPeerConnectionBuilder;
use rtc::peer_connection::configuration::RTCConfigurationBuilder;
use rtc::peer_connection::configuration::interceptor_registry::register_default_interceptors;
use rtc::peer_connection::configuration::media_engine::MediaEngine;
use rtc::peer_connection::configuration::setting_engine::SettingEngine;
use rtc::peer_connection::sdp::RTCSessionDescription;
use rtc::rtp_transceiver::rtp_sender::{RTCRtpHeaderExtensionCapability, RtpCodecKind};
const FIREFOX_OFFER: &str = include_str!("testdata/firefox_152_simulcast_offer.sdp");
fn build_answerer()
-> Result<rtc::peer_connection::RTCPeerConnection<impl rtc::interceptor::Interceptor + use<>>> {
let mut media_engine = MediaEngine::default();
media_engine.register_default_codecs()?;
for uri in [
"urn:ietf:params:rtp-hdrext:sdes:mid",
"urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id",
"urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id",
] {
media_engine.register_header_extension(
RTCRtpHeaderExtensionCapability {
uri: uri.to_owned(),
},
RtpCodecKind::Video,
None,
)?;
}
let registry = rtc::interceptor::Registry::new();
let registry = register_default_interceptors(registry, &mut media_engine)?;
let config = RTCConfigurationBuilder::new().build();
Ok(RTCPeerConnectionBuilder::new()
.with_configuration(config)
.with_setting_engine(SettingEngine::default())
.with_media_engine(media_engine)
.with_interceptor_registry(registry)
.build()?)
}
fn crlf(s: &str) -> String {
s.replace("\r\n", "\n").replace('\n', "\r\n")
}
#[test]
fn firefox_simulcast_offer_answer() -> Result<()> {
let mut pc = build_answerer()?;
let offer = RTCSessionDescription::offer(crlf(FIREFOX_OFFER))?;
pc.set_remote_description(offer)?;
let answer = pc.create_answer(None)?;
let ans = &answer.sdp;
for rid in ["h", "m", "l"] {
assert!(
ans.contains(&format!("a=rid:{rid} recv")),
"answer missing `a=rid:{rid} recv`\n{ans}"
);
}
assert!(
ans.contains("a=simulcast:recv h;m;l"),
"answer missing `a=simulcast:recv h;m;l`\n{ans}"
);
assert!(
ans.contains("urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id"),
"answer did not negotiate the RID header extension\n{ans}"
);
Ok(())
}
#[test]
fn simulcast_recv_order_follows_simulcast_attribute_not_rid_lines() -> Result<()> {
let lf = FIREFOX_OFFER.replace("\r\n", "\n");
let reordered = lf.replace(
"a=rid:h send\na=rid:m send\na=rid:l send",
"a=rid:l send\na=rid:m send\na=rid:h send",
);
assert!(
reordered.contains("a=rid:l send\na=rid:m send\na=rid:h send"),
"fixture rid lines not reordered as expected"
);
assert!(reordered.contains("a=simulcast:send h;m;l"));
let mut pc = build_answerer()?;
pc.set_remote_description(RTCSessionDescription::offer(crlf(&reordered))?)?;
let answer = pc.create_answer(None)?;
let ans = &answer.sdp;
assert!(
ans.contains("a=simulcast:recv h;m;l"),
"answer should follow the offer's a=simulcast preference order (h;m;l), \
not the a=rid line order (RFC 8853 §5.2). Got:\n{ans}"
);
let ans_lf = ans.replace("\r\n", "\n");
assert!(
ans_lf.contains("a=rid:h recv\na=rid:m recv\na=rid:l recv"),
"a=rid recv lines should follow the a=simulcast order. Got:\n{ans}"
);
Ok(())
}