use rustrtc::sdp::{
Attribute, Direction, MediaSection, SdpType, SessionDescription, SessionSection,
};
use rustrtc::*;
fn create_minimal_sdp(sdp_type: SdpType, mid: &str, direction: Direction) -> SessionDescription {
let mut desc = SessionDescription::new(sdp_type);
desc.session = SessionSection::default();
let mut section = MediaSection::new(MediaKind::Audio, mid);
section.direction = direction;
section.attributes.push(Attribute::new(
"rtpmap",
Some("111 opus/48000/2".to_string()),
));
section.attributes.push(Attribute::new(
"extmap",
Some("1 urn:ietf:params:rtp-hdrext:ssrc-audio-level".to_string()),
));
section
.attributes
.push(Attribute::new("ssrc", Some("12345 cname:test".to_string())));
desc.media_sections.push(section);
desc
}
#[tokio::test]
async fn test_reinvite_offerer_timing() {
let mut config = RtcConfiguration::default();
config.transport_mode = TransportMode::Rtp;
let pc = PeerConnection::new(config);
pc.add_transceiver(
MediaKind::Audio,
peer_connection::TransceiverDirection::SendRecv,
);
let initial_offer = create_minimal_sdp(SdpType::Offer, "0", Direction::SendRecv);
pc.set_local_description(initial_offer.clone()).unwrap();
let initial_answer = create_minimal_sdp(SdpType::Answer, "0", Direction::SendRecv);
pc.set_remote_description(initial_answer).await.unwrap();
let mut reinvite_offer = create_minimal_sdp(SdpType::Offer, "0", Direction::SendRecv);
reinvite_offer.media_sections[0].attributes.clear();
reinvite_offer.media_sections[0]
.attributes
.push(Attribute::new(
"rtpmap",
Some("120 opus/48000/2".to_string()),
));
reinvite_offer.media_sections[0]
.attributes
.push(Attribute::new("ssrc", Some("12345 cname:test".to_string())));
pc.set_local_description(reinvite_offer.clone()).unwrap();
let transceivers = pc.get_transceivers();
let payload_map_after_offer = transceivers[0].get_payload_map();
assert!(
payload_map_after_offer.contains_key(&120),
"Payload map should contain PT 120 after sending offer"
);
let mut reinvite_answer = create_minimal_sdp(SdpType::Answer, "0", Direction::SendRecv);
reinvite_answer.media_sections[0].attributes.clear();
reinvite_answer.media_sections[0]
.attributes
.push(Attribute::new(
"rtpmap",
Some("120 opus/48000/2".to_string()),
));
reinvite_answer.media_sections[0]
.attributes
.push(Attribute::new("ssrc", Some("12345 cname:test".to_string())));
pc.set_remote_description(reinvite_answer).await.unwrap();
let transceivers = pc.get_transceivers();
assert_eq!(transceivers.len(), 1);
let payload_map = transceivers[0].get_payload_map();
assert!(
payload_map.contains_key(&120),
"Payload map should still contain PT 120 after answer"
);
}
#[tokio::test]
async fn test_reinvite_answerer_timing() {
let mut config = RtcConfiguration::default();
config.transport_mode = TransportMode::Rtp;
let pc = PeerConnection::new(config);
let initial_offer = create_minimal_sdp(SdpType::Offer, "0", Direction::SendRecv);
pc.set_remote_description(initial_offer).await.unwrap();
let initial_answer = pc.create_answer().await.unwrap();
pc.set_local_description(initial_answer).unwrap();
let mut reinvite_offer = create_minimal_sdp(SdpType::Offer, "0", Direction::SendRecv);
reinvite_offer.media_sections[0].attributes.clear();
reinvite_offer.media_sections[0]
.attributes
.push(Attribute::new(
"rtpmap",
Some("120 opus/48000/2".to_string()),
));
reinvite_offer.media_sections[0]
.attributes
.push(Attribute::new("ssrc", Some("12345 cname:test".to_string())));
pc.set_remote_description(reinvite_offer).await.unwrap();
let transceivers = pc.get_transceivers();
assert_eq!(transceivers.len(), 1);
let payload_map = transceivers[0].get_payload_map();
assert!(payload_map.contains_key(&120));
assert!(!payload_map.contains_key(&111));
}
#[tokio::test]
async fn test_ssrc_change_detection() {
let mut config = RtcConfiguration::default();
config.transport_mode = TransportMode::Rtp;
let pc = PeerConnection::new(config);
let initial_offer = create_minimal_sdp(SdpType::Offer, "0", Direction::SendRecv);
pc.set_remote_description(initial_offer).await.unwrap();
let initial_answer = pc.create_answer().await.unwrap();
pc.set_local_description(initial_answer).unwrap();
let mut reinvite_offer = create_minimal_sdp(SdpType::Offer, "0", Direction::SendRecv);
reinvite_offer.media_sections[0].attributes.clear();
reinvite_offer.media_sections[0]
.attributes
.push(Attribute::new(
"rtpmap",
Some("111 opus/48000/2".to_string()),
));
reinvite_offer.media_sections[0]
.attributes
.push(Attribute::new("ssrc", Some("99999 cname:test".to_string())));
let result = pc.set_remote_description(reinvite_offer).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_direction_change_hold() {
let mut config = RtcConfiguration::default();
config.transport_mode = TransportMode::Rtp;
let pc = PeerConnection::new(config);
pc.add_transceiver(
MediaKind::Audio,
peer_connection::TransceiverDirection::SendRecv,
);
let initial_offer = create_minimal_sdp(SdpType::Offer, "0", Direction::SendRecv);
pc.set_local_description(initial_offer).unwrap();
let initial_answer = create_minimal_sdp(SdpType::Answer, "0", Direction::SendRecv);
pc.set_remote_description(initial_answer).await.unwrap();
let reinvite_offer = create_minimal_sdp(SdpType::Offer, "0", Direction::SendOnly);
pc.set_remote_description(reinvite_offer).await.unwrap();
let answer = pc.create_answer().await.unwrap();
pc.set_local_description(answer).unwrap();
let transceivers = pc.get_transceivers();
assert_eq!(
transceivers[0].direction(),
peer_connection::TransceiverDirection::SendOnly
);
}
#[tokio::test]
async fn test_direction_change_unhold() {
let mut config = RtcConfiguration::default();
config.transport_mode = TransportMode::Rtp;
let pc = PeerConnection::new(config);
let initial_offer = create_minimal_sdp(SdpType::Offer, "0", Direction::SendOnly);
pc.set_remote_description(initial_offer).await.unwrap();
let initial_answer = pc.create_answer().await.unwrap();
pc.set_local_description(initial_answer).unwrap();
let reinvite_offer = create_minimal_sdp(SdpType::Offer, "0", Direction::SendRecv);
pc.set_remote_description(reinvite_offer).await.unwrap();
let answer = pc.create_answer().await.unwrap();
pc.set_local_description(answer).unwrap();
let transceivers = pc.get_transceivers();
assert_eq!(
transceivers[0].direction(),
peer_connection::TransceiverDirection::SendRecv
);
}
#[tokio::test]
async fn test_direction_change_inactive() {
let mut config = RtcConfiguration::default();
config.transport_mode = TransportMode::Rtp;
let pc = PeerConnection::new(config);
let initial_offer = create_minimal_sdp(SdpType::Offer, "0", Direction::SendRecv);
pc.set_remote_description(initial_offer).await.unwrap();
let initial_answer = pc.create_answer().await.unwrap();
pc.set_local_description(initial_answer).unwrap();
let reinvite_offer = create_minimal_sdp(SdpType::Offer, "0", Direction::Inactive);
pc.set_remote_description(reinvite_offer).await.unwrap();
let transceivers = pc.get_transceivers();
assert_eq!(
transceivers[0].direction(),
peer_connection::TransceiverDirection::Inactive
);
}
#[tokio::test]
async fn test_combined_parameter_changes() {
let mut config = RtcConfiguration::default();
config.transport_mode = TransportMode::Rtp;
let pc = PeerConnection::new(config);
let initial_offer = create_minimal_sdp(SdpType::Offer, "0", Direction::SendRecv);
pc.set_remote_description(initial_offer).await.unwrap();
let initial_answer = pc.create_answer().await.unwrap();
pc.set_local_description(initial_answer).unwrap();
let mut reinvite_offer = create_minimal_sdp(SdpType::Offer, "0", Direction::SendOnly);
reinvite_offer.media_sections[0].attributes.clear();
reinvite_offer.media_sections[0]
.attributes
.push(Attribute::new(
"rtpmap",
Some("120 opus/48000/2".to_string()),
));
reinvite_offer.media_sections[0]
.attributes
.push(Attribute::new(
"extmap",
Some("5 urn:ietf:params:rtp-hdrext:ssrc-audio-level".to_string()),
));
reinvite_offer.media_sections[0]
.attributes
.push(Attribute::new("ssrc", Some("12345 cname:test".to_string())));
pc.set_remote_description(reinvite_offer).await.unwrap();
let transceivers = pc.get_transceivers();
let t = &transceivers[0];
assert_eq!(
t.direction(),
peer_connection::TransceiverDirection::SendOnly
);
let payload_map = t.get_payload_map();
assert!(payload_map.contains_key(&120));
assert!(!payload_map.contains_key(&111));
let extmap = t.get_extmap();
assert!(extmap.contains_key(&5));
assert!(!extmap.contains_key(&1));
}
#[tokio::test]
async fn test_glare_detection() {
let mut config = RtcConfiguration::default();
config.transport_mode = TransportMode::Rtp;
let pc = PeerConnection::new(config);
pc.add_transceiver(
MediaKind::Audio,
peer_connection::TransceiverDirection::SendRecv,
);
let initial_offer = create_minimal_sdp(SdpType::Offer, "0", Direction::SendRecv);
pc.set_local_description(initial_offer).unwrap();
let initial_answer = create_minimal_sdp(SdpType::Answer, "0", Direction::SendRecv);
pc.set_remote_description(initial_answer).await.unwrap();
let local_reinvite = create_minimal_sdp(SdpType::Offer, "0", Direction::SendRecv);
pc.set_local_description(local_reinvite).unwrap();
let remote_reinvite = create_minimal_sdp(SdpType::Offer, "0", Direction::SendOnly);
let result = pc.set_remote_description(remote_reinvite).await;
assert!(result.is_err());
if let Err(e) = result {
assert!(matches!(e, RtcError::InvalidState(_)));
}
}
#[tokio::test]
async fn test_sequential_reinvites() {
let mut config = RtcConfiguration::default();
config.transport_mode = TransportMode::Rtp;
let pc = PeerConnection::new(config);
let initial_offer = create_minimal_sdp(SdpType::Offer, "0", Direction::SendRecv);
pc.set_remote_description(initial_offer).await.unwrap();
let initial_answer = pc.create_answer().await.unwrap();
pc.set_local_description(initial_answer).unwrap();
let mut reinvite1 = create_minimal_sdp(SdpType::Offer, "0", Direction::SendRecv);
reinvite1.media_sections[0].attributes.clear();
reinvite1.media_sections[0].attributes.push(Attribute::new(
"rtpmap",
Some("120 opus/48000/2".to_string()),
));
reinvite1.media_sections[0]
.attributes
.push(Attribute::new("ssrc", Some("12345 cname:test".to_string())));
pc.set_remote_description(reinvite1).await.unwrap();
let answer1 = pc.create_answer().await.unwrap();
pc.set_local_description(answer1).unwrap();
let transceivers = pc.get_transceivers();
assert!(transceivers[0].get_payload_map().contains_key(&120));
let mut reinvite2 = create_minimal_sdp(SdpType::Offer, "0", Direction::SendOnly);
reinvite2.media_sections[0].attributes.clear();
reinvite2.media_sections[0].attributes.push(Attribute::new(
"rtpmap",
Some("96 opus/48000/2".to_string()),
));
reinvite2.media_sections[0]
.attributes
.push(Attribute::new("ssrc", Some("12345 cname:test".to_string())));
pc.set_remote_description(reinvite2).await.unwrap();
let answer2 = pc.create_answer().await.unwrap();
pc.set_local_description(answer2).unwrap();
let transceivers = pc.get_transceivers();
let payload_map = transceivers[0].get_payload_map();
assert!(payload_map.contains_key(&96));
assert!(!payload_map.contains_key(&120));
assert_eq!(
transceivers[0].direction(),
peer_connection::TransceiverDirection::SendOnly
);
}
#[tokio::test]
async fn test_extmap_changes_in_reinvite() {
let mut config = RtcConfiguration::default();
config.transport_mode = TransportMode::Rtp;
let pc = PeerConnection::new(config);
pc.add_transceiver(
MediaKind::Audio,
peer_connection::TransceiverDirection::SendRecv,
);
let mut initial_offer = create_minimal_sdp(SdpType::Offer, "0", Direction::SendRecv);
initial_offer.media_sections[0]
.attributes
.push(Attribute::new(
"extmap",
Some("3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time".to_string()),
));
pc.set_remote_description(initial_offer).await.unwrap();
let initial_answer = pc.create_answer().await.unwrap();
pc.set_local_description(initial_answer).unwrap();
let transceivers = pc.get_transceivers();
let initial_extmap = transceivers[0].get_extmap();
assert!(
initial_extmap.len() >= 1,
"Should have at least one extmap entry"
);
let mut reinvite_offer = create_minimal_sdp(SdpType::Offer, "0", Direction::SendRecv);
reinvite_offer.media_sections[0].attributes.clear();
reinvite_offer.media_sections[0]
.attributes
.push(Attribute::new(
"rtpmap",
Some("111 opus/48000/2".to_string()),
));
reinvite_offer.media_sections[0]
.attributes
.push(Attribute::new(
"extmap",
Some("2 urn:ietf:params:rtp-hdrext:ssrc-audio-level".to_string()),
));
reinvite_offer.media_sections[0]
.attributes
.push(Attribute::new(
"extmap",
Some("7 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time".to_string()),
));
reinvite_offer.media_sections[0]
.attributes
.push(Attribute::new("ssrc", Some("12345 cname:test".to_string())));
pc.set_remote_description(reinvite_offer).await.unwrap();
let answer = pc.create_answer().await.unwrap();
pc.set_local_description(answer).unwrap();
let transceivers = pc.get_transceivers();
let new_extmap = transceivers[0].get_extmap();
assert!(
new_extmap.contains_key(&2),
"Should contain new extmap ID 2"
);
assert!(
new_extmap.contains_key(&7),
"Should contain new extmap ID 7"
);
}
#[tokio::test]
async fn test_reinvite_updates_remote_addr() {
let mut config = RtcConfiguration::default();
config.transport_mode = TransportMode::Rtp;
let pc = PeerConnection::new(config);
let initial_offer = "v=0\r\n\
o=- 1 1 IN IP4 10.0.0.1\r\n\
s=-\r\n\
t=0 0\r\n\
c=IN IP4 10.0.0.1\r\n\
m=audio 8000 RTP/AVP 0\r\n\
a=rtpmap:0 PCMU/8000\r\n\
a=sendrecv\r\n";
let initial_offer_desc =
SessionDescription::parse(SdpType::Offer, initial_offer).unwrap();
pc.set_remote_description(initial_offer_desc).await.unwrap();
let initial_answer = pc.create_answer().await.unwrap();
pc.set_local_description(initial_answer).unwrap();
let initial_pair: Option<rustrtc::transports::ice::IceCandidatePair> =
pc.ice_transport().get_selected_pair().await;
assert!(initial_pair.is_some());
assert_eq!(
initial_pair.unwrap().remote.address,
std::net::SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::new(10, 0, 0, 1)), 8000)
);
let reinvite_offer = "v=0\r\n\
o=- 1 2 IN IP4 192.168.1.50\r\n\
s=-\r\n\
t=0 0\r\n\
c=IN IP4 192.168.1.50\r\n\
m=audio 9000 RTP/AVP 0\r\n\
a=rtpmap:0 PCMU/8000\r\n\
a=sendrecv\r\n";
let reinvite_desc = SessionDescription::parse(SdpType::Offer, reinvite_offer).unwrap();
pc.set_remote_description(reinvite_desc).await.unwrap();
let answer = pc.create_answer().await.unwrap();
pc.set_local_description(answer).unwrap();
let updated_pair: Option<rustrtc::transports::ice::IceCandidatePair> =
pc.ice_transport().get_selected_pair().await;
assert!(updated_pair.is_some());
assert_eq!(
updated_pair.unwrap().remote.address,
std::net::SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::new(192, 168, 1, 50)), 9000),
"reinvite should update RTP remote address"
);
}