From 23bbc1fb7eb9962e19e03cd4b8645e7aee8926c4 Mon Sep 17 00:00:00 2001 From: Haakon Sporsheim Date: Sun, 18 Aug 2024 14:27:07 +0200 Subject: [PATCH] sdp: Negotiate extmap-allow-mixed (#604) This allows both one-byte and two-byte headers --- sdp/src/description/media.rs | 5 +++++ sdp/src/description/session.rs | 6 ++++++ .../src/peer_connection/peer_connection_internal.rs | 9 +++++++++ webrtc/src/peer_connection/sdp/mod.rs | 11 +++++++++++ webrtc/src/peer_connection/sdp/sdp_test.rs | 9 +++++++++ 5 files changed, 40 insertions(+) diff --git a/sdp/src/description/media.rs b/sdp/src/description/media.rs index 775987353..53f0b037f 100644 --- a/sdp/src/description/media.rs +++ b/sdp/src/description/media.rs @@ -65,6 +65,11 @@ pub struct MediaDescription { } impl MediaDescription { + /// Returns whether an attribute exists + pub fn has_attribute(&self, key: &str) -> bool { + self.attributes.iter().any(|a| a.key == key) + } + /// attribute returns the value of an attribute and if it exists pub fn attribute(&self, key: &str) -> Option> { for a in &self.attributes { diff --git a/sdp/src/description/session.rs b/sdp/src/description/session.rs index 23bedf2bd..c1f39b0d4 100644 --- a/sdp/src/description/session.rs +++ b/sdp/src/description/session.rs @@ -30,6 +30,7 @@ pub const ATTR_KEY_RECV_ONLY: &str = "recvonly"; pub const ATTR_KEY_SEND_ONLY: &str = "sendonly"; pub const ATTR_KEY_SEND_RECV: &str = "sendrecv"; pub const ATTR_KEY_EXT_MAP: &str = "extmap"; +pub const ATTR_KEY_EXTMAP_ALLOW_MIXED: &str = "extmap-allow-mixed"; /// Constants for semantic tokens used in JSEP pub const SEMANTIC_TOKEN_LIP_SYNCHRONIZATION: &str = "LS"; @@ -409,6 +410,11 @@ impl SessionDescription { Err(Error::CodecNotFound) } + /// Returns whether an attribute exists + pub fn has_attribute(&self, key: &str) -> bool { + self.attributes.iter().any(|a| a.key == key) + } + /// Attribute returns the value of an attribute and if it exists pub fn attribute(&self, key: &str) -> Option<&String> { for a in &self.attributes { diff --git a/webrtc/src/peer_connection/peer_connection_internal.rs b/webrtc/src/peer_connection/peer_connection_internal.rs index 6f877bb9f..24e07b1bd 100644 --- a/webrtc/src/peer_connection/peer_connection_internal.rs +++ b/webrtc/src/peer_connection/peer_connection_internal.rs @@ -771,6 +771,7 @@ impl PeerConnectionInternal { let params = PopulateSdpParams { media_description_fingerprint: self.setting_engine.sdp_media_level_fingerprints, is_icelite: self.setting_engine.candidates.ice_lite, + extmap_allow_mixed: true, connection_role: DEFAULT_DTLS_ROLE_OFFER.to_connection_role(), ice_gathering_state: self.ice_gathering_state(), match_bundle_group: None, @@ -804,8 +805,12 @@ impl PeerConnectionInternal { let remote_description = self.remote_description().await; let mut media_sections = vec![]; let mut already_have_application_media_section = false; + let mut extmap_allow_mixed = false; + if let Some(remote_description) = remote_description.as_ref() { if let Some(parsed) = &remote_description.parsed { + extmap_allow_mixed = parsed.has_attribute(ATTR_KEY_EXTMAP_ALLOW_MIXED); + for media in &parsed.media_descriptions { if let Some(mid_value) = get_mid_value(media) { if mid_value.is_empty() { @@ -830,6 +835,8 @@ impl PeerConnectionInternal { continue; } + let extmap_allow_mixed = media.has_attribute(ATTR_KEY_EXTMAP_ALLOW_MIXED); + if let Some(t) = find_by_mid(mid_value, &mut local_transceivers).await { t.sender().await.set_negotiated(); let media_transceivers = vec![t]; @@ -843,6 +850,7 @@ impl PeerConnectionInternal { transceivers: media_transceivers, rid_map: get_rids(media), offered_direction: (!include_unmatched).then(|| direction), + extmap_allow_mixed, ..Default::default() }); } else { @@ -896,6 +904,7 @@ impl PeerConnectionInternal { let params = PopulateSdpParams { media_description_fingerprint: self.setting_engine.sdp_media_level_fingerprints, is_icelite: self.setting_engine.candidates.ice_lite, + extmap_allow_mixed, connection_role, ice_gathering_state: self.ice_gathering_state(), match_bundle_group, diff --git a/webrtc/src/peer_connection/sdp/mod.rs b/webrtc/src/peer_connection/sdp/mod.rs index c507c3c45..34983cc59 100644 --- a/webrtc/src/peer_connection/sdp/mod.rs +++ b/webrtc/src/peer_connection/sdp/mod.rs @@ -462,6 +462,10 @@ pub(crate) async fn add_transceiver_sdp( .with_property_attribute(ATTR_KEY_RTCPMUX.to_owned()) .with_property_attribute(ATTR_KEY_RTCPRSIZE.to_owned()); + if media_section.extmap_allow_mixed { + media = media.with_property_attribute(ATTR_KEY_EXTMAP_ALLOW_MIXED.to_owned()); + } + let codecs = t.get_codecs().await; for codec in &codecs { let name = codec @@ -772,11 +776,13 @@ pub(crate) struct MediaSection { pub(crate) data: bool, pub(crate) rid_map: Vec, pub(crate) offered_direction: Option, + pub(crate) extmap_allow_mixed: bool, } pub(crate) struct PopulateSdpParams { pub(crate) media_description_fingerprint: bool, pub(crate) is_icelite: bool, + pub(crate) extmap_allow_mixed: bool, pub(crate) connection_role: ConnectionRole, pub(crate) ice_gathering_state: RTCIceGatheringState, pub(crate) match_bundle_group: Option, @@ -876,6 +882,11 @@ pub(crate) async fn populate_sdp( d = d.with_value_attribute(ATTR_KEY_GROUP.to_owned(), bundle_value); } + if params.extmap_allow_mixed { + // RFC 8285 6. + d = d.with_property_attribute(ATTR_KEY_EXTMAP_ALLOW_MIXED.to_owned()); + } + Ok(d) } diff --git a/webrtc/src/peer_connection/sdp/sdp_test.rs b/webrtc/src/peer_connection/sdp/sdp_test.rs index 947c38de6..9fc3cf9f8 100644 --- a/webrtc/src/peer_connection/sdp/sdp_test.rs +++ b/webrtc/src/peer_connection/sdp/sdp_test.rs @@ -581,6 +581,7 @@ async fn fingerprint_test( let params = PopulateSdpParams { media_description_fingerprint: sdpmedia_description_fingerprints, is_icelite: false, + extmap_allow_mixed: false, connection_role: ConnectionRole::Active, ice_gathering_state: RTCIceGatheringState::New, match_bundle_group: None, @@ -777,6 +778,7 @@ async fn test_populate_sdp() -> Result<()> { let params = PopulateSdpParams { media_description_fingerprint: se.sdp_media_level_fingerprints, is_icelite: se.candidates.ice_lite, + extmap_allow_mixed: true, connection_role: DEFAULT_DTLS_ROLE_OFFER.to_connection_role(), ice_gathering_state: RTCIceGatheringState::Complete, match_bundle_group: None, @@ -882,6 +884,7 @@ async fn test_populate_sdp() -> Result<()> { let params = PopulateSdpParams { media_description_fingerprint: se.sdp_media_level_fingerprints, is_icelite: se.candidates.ice_lite, + extmap_allow_mixed: true, connection_role: DEFAULT_DTLS_ROLE_OFFER.to_connection_role(), ice_gathering_state: RTCIceGatheringState::Complete, match_bundle_group: None, @@ -962,6 +965,7 @@ async fn test_populate_sdp() -> Result<()> { let params = PopulateSdpParams { media_description_fingerprint: se.sdp_media_level_fingerprints, is_icelite: se.candidates.ice_lite, + extmap_allow_mixed: true, connection_role: DEFAULT_DTLS_ROLE_OFFER.to_connection_role(), ice_gathering_state: RTCIceGatheringState::Complete, match_bundle_group: None, @@ -981,6 +985,8 @@ async fn test_populate_sdp() -> Result<()> { offer_sdp.attribute(ATTR_KEY_GROUP), Some(&"BUNDLE video".to_owned()) ); + + assert!(offer_sdp.has_attribute(ATTR_KEY_EXTMAP_ALLOW_MIXED)); } //"Bundle matched" @@ -1057,6 +1063,7 @@ async fn test_populate_sdp() -> Result<()> { let params = PopulateSdpParams { media_description_fingerprint: se.sdp_media_level_fingerprints, is_icelite: se.candidates.ice_lite, + extmap_allow_mixed: true, connection_role: DEFAULT_DTLS_ROLE_OFFER.to_connection_role(), ice_gathering_state: RTCIceGatheringState::Complete, match_bundle_group: Some("audio".to_owned()), @@ -1122,6 +1129,7 @@ async fn test_populate_sdp() -> Result<()> { let params = PopulateSdpParams { media_description_fingerprint: se.sdp_media_level_fingerprints, is_icelite: se.candidates.ice_lite, + extmap_allow_mixed: true, connection_role: DEFAULT_DTLS_ROLE_OFFER.to_connection_role(), ice_gathering_state: RTCIceGatheringState::Complete, match_bundle_group: Some("".to_owned()), @@ -1231,6 +1239,7 @@ async fn test_populate_sdp_reject() -> Result<()> { let params = PopulateSdpParams { media_description_fingerprint: se.sdp_media_level_fingerprints, is_icelite: se.candidates.ice_lite, + extmap_allow_mixed: true, connection_role: DEFAULT_DTLS_ROLE_OFFER.to_connection_role(), ice_gathering_state: RTCIceGatheringState::Complete, match_bundle_group: None,