From 8e1343aeda4b1a3ad1b34864b39a5458123e82fa Mon Sep 17 00:00:00 2001 From: Bjorn A Mellem Date: Mon, 30 Sep 2019 15:12:47 -0700 Subject: [PATCH] Add an alt-protocol to SDP to indicate which m= sections use a plugin transport. The plugin transport parameters (a=x-opaque: lines) relate to how to create and set up a plugin transport. When SDP bundle is used, the x-opaque line needs to be copied into the bundled m= section. This means x-opaque can appear on a section even if the offerer does not intend to use the transport for the media described by that section. Consequently, the answerer cannot currently tell whether the caller is offering an alternate transport for media, data, or both. This change adds an a=x-alt-protocol: line to SDP. The value following this line matches the part of the x-opaque:: line. However, alt-protocol is not bundled--it only ever applies to the m= section that contains the line. This allows the offerer to express which m= sections should actually use an alternate transport, even in the case of bundle. Note that this is still limited by the available configuration options: datagram transport can be used for media (audio + video) and/or data. It is still not possible to use it for audio but not video, or vice versa. PeerConnection places an alt-protocol line in each media (audio/video) m= section if it is configured to use a datagram transport for media. It places an alt-protocol line in each data m= section if it is configured to use a datagram transport for data channels. PeerConnection leaves alt-protocol in media (audio/video) m= sections of the answer if it is configured to use a datagram transport for media, and in data m= sections of the answer if it is configured to use a datagram transport for data channels. JsepTransport now negotiates use of the datagram transport independently for media and data channels. It only uses it for media if the m= sections for bundled audio/video have an alt-protocol line matching the x-opaque protocol, and only uses it for data channels if a bundled m= section for data has an alt-protocol line matching the x-opaque protocol. Bug: webrtc:9719 Change-Id: I773e4fc10c57d815afcd76a2a74da38dd0c52b3b Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/154763 Reviewed-by: Steve Anton Reviewed-by: Seth Hampson Commit-Queue: Bjorn Mellem Cr-Commit-Position: refs/heads/master@{#29351} --- api/test/loopback_media_transport.cc | 48 +++- api/test/loopback_media_transport.h | 13 +- pc/jsep_transport.cc | 80 ++++-- pc/jsep_transport.h | 12 +- pc/jsep_transport_controller.cc | 81 +++++- pc/jsep_transport_controller.h | 10 +- pc/jsep_transport_controller_unittest.cc | 63 ++++- pc/media_session.cc | 6 + pc/media_session.h | 1 + pc/media_session_unittest.cc | 118 +++++++++ pc/peer_connection.cc | 26 +- pc/peer_connection_integrationtest.cc | 304 +++++++++++++++++++++++ pc/session_description.h | 9 + pc/webrtc_sdp.cc | 27 ++ pc/webrtc_sdp_unittest.cc | 44 ++++ 15 files changed, 803 insertions(+), 39 deletions(-) diff --git a/api/test/loopback_media_transport.cc b/api/test/loopback_media_transport.cc index e341a38876..f1bce1c937 100644 --- a/api/test/loopback_media_transport.cc +++ b/api/test/loopback_media_transport.cc @@ -19,6 +19,8 @@ namespace webrtc { namespace { +constexpr size_t kLoopbackMaxDatagramSize = 1200; + // Wrapper used to hand out unique_ptrs to loopback media transports without // ownership changes. class WrapperMediaTransport : public MediaTransportInterface { @@ -611,10 +613,11 @@ void MediaTransportPair::LoopbackMediaTransport::SetAllocatedBitrateLimits( MediaTransportPair::LoopbackDatagramTransport::LoopbackDatagramTransport( rtc::Thread* thread) - : dc_transport_(thread) {} + : thread_(thread), dc_transport_(thread) {} void MediaTransportPair::LoopbackDatagramTransport::Connect( LoopbackDatagramTransport* other) { + other_ = other; dc_transport_.Connect(&other->dc_transport_); } @@ -631,21 +634,43 @@ MediaTransportPair::LoopbackDatagramTransport::congestion_control() { } void MediaTransportPair::LoopbackDatagramTransport::SetTransportStateCallback( - MediaTransportStateCallback* callback) {} + MediaTransportStateCallback* callback) { + RTC_DCHECK_RUN_ON(thread_); + state_callback_ = callback; + if (state_callback_) { + state_callback_->OnStateChanged(state_); + } +} RTCError MediaTransportPair::LoopbackDatagramTransport::SendDatagram( rtc::ArrayView data, DatagramId datagram_id) { + rtc::CopyOnWriteBuffer buffer; + buffer.SetData(data.data(), data.size()); + invoker_.AsyncInvoke( + RTC_FROM_HERE, thread_, [this, datagram_id, buffer = std::move(buffer)] { + RTC_DCHECK_RUN_ON(thread_); + other_->DeliverDatagram(std::move(buffer)); + if (sink_) { + DatagramAck ack; + ack.datagram_id = datagram_id; + ack.receive_timestamp = Timestamp::us(rtc::TimeMicros()); + sink_->OnDatagramAcked(ack); + } + }); return RTCError::OK(); } size_t MediaTransportPair::LoopbackDatagramTransport::GetLargestDatagramSize() const { - return 0; + return kLoopbackMaxDatagramSize; } void MediaTransportPair::LoopbackDatagramTransport::SetDatagramSink( - DatagramSinkInterface* sink) {} + DatagramSinkInterface* sink) { + RTC_DCHECK_RUN_ON(thread_); + sink_ = sink; +} std::string MediaTransportPair::LoopbackDatagramTransport::GetTransportParameters() const { @@ -680,6 +705,13 @@ bool MediaTransportPair::LoopbackDatagramTransport::IsReadyToSend() const { void MediaTransportPair::LoopbackDatagramTransport::SetState( MediaTransportState state) { + invoker_.AsyncInvoke(RTC_FROM_HERE, thread_, [this, state] { + RTC_DCHECK_RUN_ON(thread_); + state_ = state; + if (state_callback_) { + state_callback_->OnStateChanged(state_); + } + }); dc_transport_.OnReadyToSend(state == MediaTransportState::kWritable); } @@ -692,4 +724,12 @@ void MediaTransportPair::LoopbackDatagramTransport::FlushAsyncInvokes() { dc_transport_.FlushAsyncInvokes(); } +void MediaTransportPair::LoopbackDatagramTransport::DeliverDatagram( + rtc::CopyOnWriteBuffer buffer) { + RTC_DCHECK_RUN_ON(thread_); + if (sink_) { + sink_->OnDatagramReceived(buffer); + } +} + } // namespace webrtc diff --git a/api/test/loopback_media_transport.h b/api/test/loopback_media_transport.h index bacfd5ea38..475c58665d 100644 --- a/api/test/loopback_media_transport.h +++ b/api/test/loopback_media_transport.h @@ -300,7 +300,6 @@ class MediaTransportPair { void Connect(LoopbackDatagramTransport* other); // Datagram transport overrides. - // TODO(mellem): Implement these when tests actually need to use them. void Connect(rtc::PacketTransportInternal* packet_transport) override; CongestionControlInterface* congestion_control() override; void SetTransportStateCallback( @@ -333,11 +332,23 @@ class MediaTransportPair { } private: + void DeliverDatagram(rtc::CopyOnWriteBuffer buffer); + + rtc::Thread* thread_; LoopbackDataChannelTransport dc_transport_; + MediaTransportState state_ RTC_GUARDED_BY(thread_) = + MediaTransportState::kPending; + DatagramSinkInterface* sink_ RTC_GUARDED_BY(thread_) = nullptr; + MediaTransportStateCallback* state_callback_ RTC_GUARDED_BY(thread_) = + nullptr; + LoopbackDatagramTransport* other_; + std::string transport_parameters_; absl::optional state_after_connect_; + + rtc::AsyncInvoker invoker_; }; LoopbackMediaTransport first_; diff --git a/pc/jsep_transport.cc b/pc/jsep_transport.cc index 7c83e85e87..ca44ec8b65 100644 --- a/pc/jsep_transport.cc +++ b/pc/jsep_transport.cc @@ -60,12 +60,16 @@ JsepTransportDescription::JsepTransportDescription( const std::vector& cryptos, const std::vector& encrypted_header_extension_ids, int rtp_abs_sendtime_extn_id, - const TransportDescription& transport_desc) + const TransportDescription& transport_desc, + absl::optional media_alt_protocol, + absl::optional data_alt_protocol) : rtcp_mux_enabled(rtcp_mux_enabled), cryptos(cryptos), encrypted_header_extension_ids(encrypted_header_extension_ids), rtp_abs_sendtime_extn_id(rtp_abs_sendtime_extn_id), - transport_desc(transport_desc) {} + transport_desc(transport_desc), + media_alt_protocol(media_alt_protocol), + data_alt_protocol(data_alt_protocol) {} JsepTransportDescription::JsepTransportDescription( const JsepTransportDescription& from) @@ -73,7 +77,9 @@ JsepTransportDescription::JsepTransportDescription( cryptos(from.cryptos), encrypted_header_extension_ids(from.encrypted_header_extension_ids), rtp_abs_sendtime_extn_id(from.rtp_abs_sendtime_extn_id), - transport_desc(from.transport_desc) {} + transport_desc(from.transport_desc), + media_alt_protocol(from.media_alt_protocol), + data_alt_protocol(from.data_alt_protocol) {} JsepTransportDescription::~JsepTransportDescription() = default; @@ -87,6 +93,8 @@ JsepTransportDescription& JsepTransportDescription::operator=( encrypted_header_extension_ids = from.encrypted_header_extension_ids; rtp_abs_sendtime_extn_id = from.rtp_abs_sendtime_extn_id; transport_desc = from.transport_desc; + media_alt_protocol = from.media_alt_protocol; + data_alt_protocol = from.data_alt_protocol; return *this; } @@ -794,34 +802,50 @@ void JsepTransport::NegotiateDatagramTransport(SdpType type) { return; // No need to negotiate the use of datagram transport. } - bool use_datagram_transport = + bool compatible_datagram_transport = remote_description_->transport_desc.opaque_parameters && remote_description_->transport_desc.opaque_parameters == local_description_->transport_desc.opaque_parameters; - RTC_LOG(LS_INFO) << "Negotiating datagram transport, use_datagram_transport=" - << use_datagram_transport << " answer type=" - << (type == SdpType::kAnswer ? "answer" : "pr_answer"); + bool use_datagram_transport_for_media = + compatible_datagram_transport && + remote_description_->media_alt_protocol == + remote_description_->transport_desc.opaque_parameters->protocol && + remote_description_->media_alt_protocol == + local_description_->media_alt_protocol; + + bool use_datagram_transport_for_data = + compatible_datagram_transport && + remote_description_->data_alt_protocol == + remote_description_->transport_desc.opaque_parameters->protocol && + remote_description_->data_alt_protocol == + local_description_->data_alt_protocol; + + RTC_LOG(LS_INFO) + << "Negotiating datagram transport, use_datagram_transport_for_media=" + << use_datagram_transport_for_media + << ", use_datagram_transport_for_data=" << use_datagram_transport_for_data + << " answer type=" << (type == SdpType::kAnswer ? "answer" : "pr_answer"); // A provisional or full or answer lets the peer start sending on one of the // transports. if (composite_rtp_transport_) { composite_rtp_transport_->SetSendTransport( - use_datagram_transport ? datagram_rtp_transport_.get() - : default_rtp_transport()); + use_datagram_transport_for_media ? datagram_rtp_transport_.get() + : default_rtp_transport()); } if (composite_data_channel_transport_) { composite_data_channel_transport_->SetSendTransport( - use_datagram_transport ? data_channel_transport_ - : sctp_data_channel_transport_.get()); + use_datagram_transport_for_data ? data_channel_transport_ + : sctp_data_channel_transport_.get()); } if (type != SdpType::kAnswer) { return; } - if (use_datagram_transport) { - if (composite_rtp_transport_) { + if (composite_rtp_transport_) { + if (use_datagram_transport_for_media) { // Negotiated use of datagram transport for RTP, so remove the // non-datagram RTP transport. composite_rtp_transport_->RemoveTransport(default_rtp_transport()); @@ -832,30 +856,34 @@ void JsepTransport::NegotiateDatagramTransport(SdpType type) { } else { dtls_srtp_transport_ = nullptr; } + } else { + composite_rtp_transport_->RemoveTransport(datagram_rtp_transport_.get()); + datagram_rtp_transport_ = nullptr; } - if (composite_data_channel_transport_) { + } + + if (composite_data_channel_transport_) { + if (use_datagram_transport_for_data) { // Negotiated use of datagram transport for data channels, so remove the // non-datagram data channel transport. composite_data_channel_transport_->RemoveTransport( sctp_data_channel_transport_.get()); sctp_data_channel_transport_ = nullptr; sctp_transport_ = nullptr; - } - } else { - // Remove and delete the datagram transport. - if (composite_rtp_transport_) { - composite_rtp_transport_->RemoveTransport(datagram_rtp_transport_.get()); - } - if (composite_data_channel_transport_) { + } else { composite_data_channel_transport_->RemoveTransport( data_channel_transport_); - } else { - // If there's no composite data channel transport, we need to signal that - // the data channel is about to be deleted. - SignalDataChannelTransportNegotiated(this, nullptr); + data_channel_transport_ = nullptr; } - datagram_rtp_transport_ = nullptr; + } else if (data_channel_transport_ && !use_datagram_transport_for_data) { + // The datagram transport has been rejected without a fallback. We still + // need to inform the application and delete it. + SignalDataChannelTransportNegotiated(this, nullptr); data_channel_transport_ = nullptr; + } + + if (!use_datagram_transport_for_media && !use_datagram_transport_for_data) { + // Datagram transport is not being used for anything, so clean it up. datagram_transport_ = nullptr; } } diff --git a/pc/jsep_transport.h b/pc/jsep_transport.h index b6199f8d0f..3c63c47ba4 100644 --- a/pc/jsep_transport.h +++ b/pc/jsep_transport.h @@ -55,7 +55,9 @@ struct JsepTransportDescription { const std::vector& cryptos, const std::vector& encrypted_header_extension_ids, int rtp_abs_sendtime_extn_id, - const TransportDescription& transport_description); + const TransportDescription& transport_description, + absl::optional media_alt_protocol, + absl::optional data_alt_protocol); JsepTransportDescription(const JsepTransportDescription& from); ~JsepTransportDescription(); @@ -68,6 +70,14 @@ struct JsepTransportDescription { // TODO(zhihuang): Add the ICE and DTLS related variables and methods from // TransportDescription and remove this extra layer of abstraction. TransportDescription transport_desc; + + // Alt-protocols that apply to this JsepTransport. Presence indicates a + // request to use an alternative protocol for media and/or data. The + // alt-protocol is handled by a datagram transport. If one or both of these + // values are present, JsepTransport will attempt to negotiate use of the + // datagram transport for media and/or data. + absl::optional media_alt_protocol; + absl::optional data_alt_protocol; }; // Helper class used by JsepTransportController that processes diff --git a/pc/jsep_transport_controller.cc b/pc/jsep_transport_controller.cc index c9ff0a7dce..52ae53c4f7 100644 --- a/pc/jsep_transport_controller.cc +++ b/pc/jsep_transport_controller.cc @@ -644,9 +644,16 @@ RTCError JsepTransportController::ApplyDescription_n( } std::vector merged_encrypted_extension_ids; + absl::optional bundle_media_alt_protocol; + absl::optional bundle_data_alt_protocol; if (bundle_group_) { merged_encrypted_extension_ids = MergeEncryptedHeaderExtensionIdsForBundle(description); + error = GetAltProtocolsForBundle(description, &bundle_media_alt_protocol, + &bundle_data_alt_protocol); + if (!error.ok()) { + return error; + } } for (const cricket::ContentInfo& content_info : description->contents()) { @@ -665,6 +672,8 @@ RTCError JsepTransportController::ApplyDescription_n( description->transport_infos().size()); for (size_t i = 0; i < description->contents().size(); ++i) { const cricket::ContentInfo& content_info = description->contents()[i]; + const cricket::MediaContentDescription* media_description = + content_info.media_description(); const cricket::TransportInfo& transport_info = description->transport_infos()[i]; if (content_info.rejected) { @@ -686,10 +695,23 @@ RTCError JsepTransportController::ApplyDescription_n( } std::vector extension_ids; + absl::optional media_alt_protocol; + absl::optional data_alt_protocol; if (bundled_mid() && content_info.name == *bundled_mid()) { extension_ids = merged_encrypted_extension_ids; + media_alt_protocol = bundle_media_alt_protocol; + data_alt_protocol = bundle_data_alt_protocol; } else { extension_ids = GetEncryptedHeaderExtensionIds(content_info); + switch (media_description->type()) { + case cricket::MEDIA_TYPE_AUDIO: + case cricket::MEDIA_TYPE_VIDEO: + media_alt_protocol = media_description->alt_protocol(); + break; + case cricket::MEDIA_TYPE_DATA: + data_alt_protocol = media_description->alt_protocol(); + break; + } } int rtp_abs_sendtime_extn_id = @@ -703,7 +725,8 @@ RTCError JsepTransportController::ApplyDescription_n( cricket::JsepTransportDescription jsep_description = CreateJsepTransportDescription(content_info, transport_info, - extension_ids, rtp_abs_sendtime_extn_id); + extension_ids, rtp_abs_sendtime_extn_id, + media_alt_protocol, data_alt_protocol); if (local) { error = transport->SetLocalJsepTransportDescription(jsep_description, type); @@ -896,7 +919,9 @@ JsepTransportController::CreateJsepTransportDescription( const cricket::ContentInfo& content_info, const cricket::TransportInfo& transport_info, const std::vector& encrypted_extension_ids, - int rtp_abs_sendtime_extn_id) { + int rtp_abs_sendtime_extn_id, + absl::optional media_alt_protocol, + absl::optional data_alt_protocol) { const cricket::MediaContentDescription* content_desc = content_info.media_description(); RTC_DCHECK(content_desc); @@ -906,7 +931,8 @@ JsepTransportController::CreateJsepTransportDescription( return cricket::JsepTransportDescription( rtcp_mux_enabled, content_desc->cryptos(), encrypted_extension_ids, - rtp_abs_sendtime_extn_id, transport_info.description); + rtp_abs_sendtime_extn_id, transport_info.description, media_alt_protocol, + data_alt_protocol); } bool JsepTransportController::ShouldUpdateBundleGroup( @@ -972,6 +998,55 @@ JsepTransportController::MergeEncryptedHeaderExtensionIdsForBundle( return merged_ids; } +RTCError JsepTransportController::GetAltProtocolsForBundle( + const cricket::SessionDescription* description, + absl::optional* media_alt_protocol, + absl::optional* data_alt_protocol) { + RTC_DCHECK(description); + RTC_DCHECK(bundle_group_); + RTC_DCHECK(media_alt_protocol); + RTC_DCHECK(data_alt_protocol); + + bool found_media = false; + bool found_data = false; + for (const cricket::ContentInfo& content : description->contents()) { + if (bundle_group_->HasContentName(content.name)) { + const cricket::MediaContentDescription* media_description = + content.media_description(); + switch (media_description->type()) { + case cricket::MEDIA_TYPE_AUDIO: + case cricket::MEDIA_TYPE_VIDEO: + if (found_media && + *media_alt_protocol != media_description->alt_protocol()) { + return RTCError(RTCErrorType::INVALID_PARAMETER, + "The BUNDLE group contains conflicting " + "alt-protocols for media ('" + + media_alt_protocol->value_or("") + "' and '" + + media_description->alt_protocol().value_or("") + + "')"); + } + found_media = true; + *media_alt_protocol = media_description->alt_protocol(); + break; + case cricket::MEDIA_TYPE_DATA: + if (found_data && + *data_alt_protocol != media_description->alt_protocol()) { + return RTCError(RTCErrorType::INVALID_PARAMETER, + "The BUNDLE group contains conflicting " + "alt-protocols for data ('" + + data_alt_protocol->value_or("") + "' and '" + + media_description->alt_protocol().value_or("") + + "')"); + } + found_data = true; + *data_alt_protocol = media_description->alt_protocol(); + break; + } + } + } + return RTCError::OK(); +} + int JsepTransportController::GetRtpAbsSendTimeHeaderExtensionId( const cricket::ContentInfo& content_info) { if (!config_.enable_external_auth) { diff --git a/pc/jsep_transport_controller.h b/pc/jsep_transport_controller.h index af3c82ce47..a46a71efbb 100644 --- a/pc/jsep_transport_controller.h +++ b/pc/jsep_transport_controller.h @@ -308,7 +308,9 @@ class JsepTransportController : public sigslot::has_slots<> { const cricket::ContentInfo& content_info, const cricket::TransportInfo& transport_info, const std::vector& encrypted_extension_ids, - int rtp_abs_sendtime_extn_id); + int rtp_abs_sendtime_extn_id, + absl::optional media_alt_protocol, + absl::optional data_alt_protocol); absl::optional bundled_mid() const { absl::optional bundled_mid; @@ -330,6 +332,12 @@ class JsepTransportController : public sigslot::has_slots<> { std::vector GetEncryptedHeaderExtensionIds( const cricket::ContentInfo& content_info); + // Extracts the alt-protocol settings that apply to the bundle group. + RTCError GetAltProtocolsForBundle( + const cricket::SessionDescription* description, + absl::optional* media_alt_protocol, + absl::optional* data_alt_protocol); + int GetRtpAbsSendTimeHeaderExtensionId( const cricket::ContentInfo& content_info); diff --git a/pc/jsep_transport_controller_unittest.cc b/pc/jsep_transport_controller_unittest.cc index 8461e86b00..408cb019b8 100644 --- a/pc/jsep_transport_controller_unittest.cc +++ b/pc/jsep_transport_controller_unittest.cc @@ -136,6 +136,21 @@ class JsepTransportControllerTest : public JsepTransportController::Observer, return description; } + std::unique_ptr + CreateSessionDescriptionWithBundledData() { + auto description = CreateSessionDescriptionWithoutBundle(); + AddDataSection(description.get(), kDataMid1, + cricket::MediaProtocolType::kSctp, kIceUfrag1, kIcePwd1, + cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS, + nullptr); + cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE); + bundle_group.AddContentName(kAudioMid1); + bundle_group.AddContentName(kVideoMid1); + bundle_group.AddContentName(kDataMid1); + description->AddGroup(bundle_group); + return description; + } + void AddAudioSection(cricket::SessionDescription* description, const std::string& mid, const std::string& ufrag, @@ -474,13 +489,19 @@ TEST_F(JsepTransportControllerTest, config.use_datagram_transport_for_data_channels = true; CreateJsepTransportController(config); - auto description = CreateSessionDescriptionWithBundleGroup(); + auto description = CreateSessionDescriptionWithBundledData(); AddCryptoSettings(description.get()); + absl::optional params = transport_controller_->GetTransportParameters(kAudioMid1); for (auto& info : description->transport_infos()) { info.description.opaque_parameters = params; } + for (cricket::ContentInfo& content_info : description->contents()) { + if (content_info.media_description()->type() == cricket::MEDIA_TYPE_DATA) { + content_info.media_description()->set_alt_protocol(params->protocol); + } + } EXPECT_TRUE(transport_controller_ ->SetLocalDescription(SdpType::kOffer, description.get()) @@ -513,6 +534,40 @@ TEST_F(JsepTransportControllerTest, ->IsWritable(/*rtcp=*/false)); } +// An offer that bundles different alt-protocols should be rejected. +TEST_F(JsepTransportControllerTest, CannotBundleDifferentAltProtocols) { + FakeMediaTransportFactory fake_media_transport_factory("transport_params"); + JsepTransportController::Config config; + config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyRequire; + config.bundle_policy = PeerConnectionInterface::kBundlePolicyMaxBundle; + config.media_transport_factory = &fake_media_transport_factory; + config.use_datagram_transport = true; + config.use_datagram_transport_for_data_channels = true; + CreateJsepTransportController(config); + + auto description = CreateSessionDescriptionWithBundledData(); + AddCryptoSettings(description.get()); + + absl::optional params = + transport_controller_->GetTransportParameters(kAudioMid1); + for (auto& info : description->transport_infos()) { + info.description.opaque_parameters = params; + } + + // Append a different alt-protocol to each of the sections. + for (cricket::ContentInfo& content_info : description->contents()) { + content_info.media_description()->set_alt_protocol(params->protocol + "-" + + content_info.name); + } + + EXPECT_FALSE(transport_controller_ + ->SetLocalDescription(SdpType::kOffer, description.get()) + .ok()); + EXPECT_FALSE(transport_controller_ + ->SetRemoteDescription(SdpType::kAnswer, description.get()) + .ok()); +} + TEST_F(JsepTransportControllerTest, GetMediaTransportInCaller) { FakeMediaTransportFactory fake_media_transport_factory; JsepTransportController::Config config; @@ -2214,6 +2269,12 @@ class JsepTransportControllerDatagramTest for (auto& info : description->transport_infos()) { info.description.opaque_parameters = transport_params; } + if (transport_params) { + for (auto& content_info : description->contents()) { + content_info.media_description()->set_alt_protocol( + transport_params->protocol); + } + } return description; } diff --git a/pc/media_session.cc b/pc/media_session.cc index ff9c17b27c..dd5a814865 100644 --- a/pc/media_session.cc +++ b/pc/media_session.cc @@ -661,6 +661,8 @@ static bool CreateContentOffer( } } + offer->set_alt_protocol(media_description_options.alt_protocol); + if (secure_policy == SEC_REQUIRED && offer->cryptos().empty()) { return false; } @@ -1179,6 +1181,10 @@ static bool CreateMediaContentAnswer( answer->set_direction(NegotiateRtpTransceiverDirection( offer->direction(), media_description_options.direction)); + + if (offer->alt_protocol() == media_description_options.alt_protocol) { + answer->set_alt_protocol(media_description_options.alt_protocol); + } return true; } diff --git a/pc/media_session.h b/pc/media_session.h index 1de8ed4e1a..f91729aa28 100644 --- a/pc/media_session.h +++ b/pc/media_session.h @@ -78,6 +78,7 @@ struct MediaDescriptionOptions { // stream information goes in the local descriptions. std::vector sender_options; std::vector codec_preferences; + absl::optional alt_protocol; private: // Doesn't DCHECK on |type|. diff --git a/pc/media_session_unittest.cc b/pc/media_session_unittest.cc index e3778d6964..d2feb1fe04 100644 --- a/pc/media_session_unittest.cc +++ b/pc/media_session_unittest.cc @@ -3486,6 +3486,124 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfo(/*offer=*/false, options, /*has_current_desc=*/false); } +TEST_F(MediaSessionDescriptionFactoryTest, AltProtocolAddedToOffer) { + MediaSessionOptions options; + AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &options); + AddDataSection(cricket::DCT_RTP, RtpTransceiverDirection::kRecvOnly, + &options); + + FindFirstMediaDescriptionByMid("audio", &options)->alt_protocol = "foo"; + FindFirstMediaDescriptionByMid("video", &options)->alt_protocol = "bar"; + FindFirstMediaDescriptionByMid("data", &options)->alt_protocol = "baz"; + + std::unique_ptr offer = f1_.CreateOffer(options, nullptr); + + EXPECT_EQ(offer->GetContentDescriptionByName("audio")->alt_protocol(), "foo"); + EXPECT_EQ(offer->GetContentDescriptionByName("video")->alt_protocol(), "bar"); + EXPECT_EQ(offer->GetContentDescriptionByName("data")->alt_protocol(), "baz"); +} + +TEST_F(MediaSessionDescriptionFactoryTest, AltProtocolAddedToAnswer) { + MediaSessionOptions options; + AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &options); + AddDataSection(cricket::DCT_SCTP, RtpTransceiverDirection::kRecvOnly, + &options); + + FindFirstMediaDescriptionByMid("audio", &options)->alt_protocol = "foo"; + FindFirstMediaDescriptionByMid("video", &options)->alt_protocol = "bar"; + FindFirstMediaDescriptionByMid("data", &options)->alt_protocol = "baz"; + + std::unique_ptr offer = f1_.CreateOffer(options, nullptr); + std::unique_ptr answer = + f1_.CreateAnswer(offer.get(), options, nullptr); + + EXPECT_EQ(answer->GetContentDescriptionByName("audio")->alt_protocol(), + "foo"); + EXPECT_EQ(answer->GetContentDescriptionByName("video")->alt_protocol(), + "bar"); + EXPECT_EQ(answer->GetContentDescriptionByName("data")->alt_protocol(), "baz"); +} + +TEST_F(MediaSessionDescriptionFactoryTest, AltProtocolNotInOffer) { + MediaSessionOptions options; + AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &options); + AddDataSection(cricket::DCT_SCTP, RtpTransceiverDirection::kRecvOnly, + &options); + + std::unique_ptr offer = f1_.CreateOffer(options, nullptr); + + FindFirstMediaDescriptionByMid("audio", &options)->alt_protocol = "foo"; + FindFirstMediaDescriptionByMid("video", &options)->alt_protocol = "bar"; + FindFirstMediaDescriptionByMid("data", &options)->alt_protocol = "baz"; + + std::unique_ptr answer = + f1_.CreateAnswer(offer.get(), options, nullptr); + + EXPECT_EQ(answer->GetContentDescriptionByName("audio")->alt_protocol(), + absl::nullopt); + EXPECT_EQ(answer->GetContentDescriptionByName("video")->alt_protocol(), + absl::nullopt); + EXPECT_EQ(answer->GetContentDescriptionByName("data")->alt_protocol(), + absl::nullopt); +} + +TEST_F(MediaSessionDescriptionFactoryTest, AltProtocolDifferentInOffer) { + MediaSessionOptions options; + AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &options); + AddDataSection(cricket::DCT_SCTP, RtpTransceiverDirection::kRecvOnly, + &options); + + FindFirstMediaDescriptionByMid("audio", &options)->alt_protocol = "not-foo"; + FindFirstMediaDescriptionByMid("video", &options)->alt_protocol = "not-bar"; + FindFirstMediaDescriptionByMid("data", &options)->alt_protocol = "not-baz"; + + std::unique_ptr offer = f1_.CreateOffer(options, nullptr); + + FindFirstMediaDescriptionByMid("audio", &options)->alt_protocol = "foo"; + FindFirstMediaDescriptionByMid("video", &options)->alt_protocol = "bar"; + FindFirstMediaDescriptionByMid("data", &options)->alt_protocol = "baz"; + + std::unique_ptr answer = + f1_.CreateAnswer(offer.get(), options, nullptr); + + EXPECT_EQ(answer->GetContentDescriptionByName("audio")->alt_protocol(), + absl::nullopt); + EXPECT_EQ(answer->GetContentDescriptionByName("video")->alt_protocol(), + absl::nullopt); + EXPECT_EQ(answer->GetContentDescriptionByName("data")->alt_protocol(), + absl::nullopt); +} + +TEST_F(MediaSessionDescriptionFactoryTest, AltProtocolNotInAnswer) { + MediaSessionOptions options; + AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &options); + AddDataSection(cricket::DCT_SCTP, RtpTransceiverDirection::kRecvOnly, + &options); + + FindFirstMediaDescriptionByMid("audio", &options)->alt_protocol = "foo"; + FindFirstMediaDescriptionByMid("video", &options)->alt_protocol = "bar"; + FindFirstMediaDescriptionByMid("data", &options)->alt_protocol = "baz"; + + std::unique_ptr offer = f1_.CreateOffer(options, nullptr); + + FindFirstMediaDescriptionByMid("audio", &options)->alt_protocol = + absl::nullopt; + FindFirstMediaDescriptionByMid("video", &options)->alt_protocol = + absl::nullopt; + FindFirstMediaDescriptionByMid("data", &options)->alt_protocol = + absl::nullopt; + + std::unique_ptr answer = + f1_.CreateAnswer(offer.get(), options, nullptr); + + EXPECT_EQ(answer->GetContentDescriptionByName("audio")->alt_protocol(), + absl::nullopt); + EXPECT_EQ(answer->GetContentDescriptionByName("video")->alt_protocol(), + absl::nullopt); + EXPECT_EQ(answer->GetContentDescriptionByName("data")->alt_protocol(), + absl::nullopt); +} + // Create an offer with bundle enabled and verify the crypto parameters are // the common set of the available cryptos. TEST_F(MediaSessionDescriptionFactoryTest, TestCryptoWithOfferBundle) { diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc index 2679800020..c2723e7f8e 100644 --- a/pc/peer_connection.cc +++ b/pc/peer_connection.cc @@ -4501,8 +4501,19 @@ void PeerConnection::GetOptionsForOffer( // If datagram transport is in use, add opaque transport parameters. if (use_datagram_transport_ || use_datagram_transport_for_data_channels_) { for (auto& options : session_options->media_description_options) { - options.transport_options.opaque_parameters = + absl::optional params = transport_controller_->GetTransportParameters(options.mid); + if (!params) { + continue; + } + options.transport_options.opaque_parameters = params; + if ((use_datagram_transport_ && + (options.type == cricket::MEDIA_TYPE_AUDIO || + options.type == cricket::MEDIA_TYPE_VIDEO)) || + (use_datagram_transport_for_data_channels_ && + options.type == cricket::MEDIA_TYPE_DATA)) { + options.alt_protocol = params->protocol; + } } } @@ -4807,8 +4818,19 @@ void PeerConnection::GetOptionsForAnswer( // If datagram transport is in use, add opaque transport parameters. if (use_datagram_transport_ || use_datagram_transport_for_data_channels_) { for (auto& options : session_options->media_description_options) { - options.transport_options.opaque_parameters = + absl::optional params = transport_controller_->GetTransportParameters(options.mid); + if (!params) { + continue; + } + options.transport_options.opaque_parameters = params; + if ((use_datagram_transport_ && + (options.type == cricket::MEDIA_TYPE_AUDIO || + options.type == cricket::MEDIA_TYPE_VIDEO)) || + (use_datagram_transport_for_data_channels_ && + options.type == cricket::MEDIA_TYPE_DATA)) { + options.alt_protocol = params->protocol; + } } } } diff --git a/pc/peer_connection_integrationtest.cc b/pc/peer_connection_integrationtest.cc index 6b2d8303d1..3a0ef0f9be 100644 --- a/pc/peer_connection_integrationtest.cc +++ b/pc/peer_connection_integrationtest.cc @@ -3647,6 +3647,310 @@ TEST_P(PeerConnectionIntegrationTest, ASSERT_TRUE(ExpectNewFrames(media_expectations)); } +TEST_P(PeerConnectionIntegrationTest, + DatagramTransportDataChannelWithMediaOnCaller) { + // Configure the caller to attempt use of datagram transport for media and + // data channels. + PeerConnectionInterface::RTCConfiguration offerer_config; + offerer_config.rtcp_mux_policy = + PeerConnectionInterface::kRtcpMuxPolicyRequire; + offerer_config.bundle_policy = + PeerConnectionInterface::kBundlePolicyMaxBundle; + offerer_config.use_datagram_transport_for_data_channels = true; + offerer_config.use_datagram_transport = true; + + // Configure the callee to only use datagram transport for data channels. + PeerConnectionInterface::RTCConfiguration answerer_config; + answerer_config.rtcp_mux_policy = + PeerConnectionInterface::kRtcpMuxPolicyRequire; + answerer_config.bundle_policy = + PeerConnectionInterface::kBundlePolicyMaxBundle; + answerer_config.use_datagram_transport_for_data_channels = true; + + ASSERT_TRUE(CreatePeerConnectionWrappersWithConfigAndMediaTransportFactory( + offerer_config, answerer_config, + loopback_media_transports()->first_factory(), + loopback_media_transports()->second_factory())); + ConnectFakeSignaling(); + + // Offer both media and data. + caller()->AddAudioVideoTracks(); + callee()->AddAudioVideoTracks(); + caller()->CreateDataChannel(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + + // Ensure that the data channel transport is ready. + loopback_media_transports()->SetState(webrtc::MediaTransportState::kWritable); + loopback_media_transports()->FlushAsyncInvokes(); + + ASSERT_NE(nullptr, caller()->data_channel()); + ASSERT_TRUE_WAIT(callee()->data_channel() != nullptr, kDefaultTimeout); + EXPECT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout); + EXPECT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout); + + // Both endpoints should agree to use datagram transport for data channels. + EXPECT_EQ(nullptr, caller()->pc()->GetSctpTransport()); + EXPECT_EQ(nullptr, callee()->pc()->GetSctpTransport()); + + // Ensure data can be sent in both directions. + std::string data = "hello world"; + caller()->data_channel()->Send(DataBuffer(data)); + EXPECT_EQ_WAIT(data, callee()->data_observer()->last_message(), + kDefaultTimeout); + callee()->data_channel()->Send(DataBuffer(data)); + EXPECT_EQ_WAIT(data, caller()->data_observer()->last_message(), + kDefaultTimeout); + + // Media flow should not be impacted. + MediaExpectations media_expectations; + media_expectations.ExpectBidirectionalAudioAndVideo(); + ASSERT_TRUE(ExpectNewFrames(media_expectations)); +} + +TEST_P(PeerConnectionIntegrationTest, + DatagramTransportMediaWithDataChannelOnCaller) { + // Configure the caller to attempt use of datagram transport for media and + // data channels. + PeerConnectionInterface::RTCConfiguration offerer_config; + offerer_config.rtcp_mux_policy = + PeerConnectionInterface::kRtcpMuxPolicyRequire; + offerer_config.bundle_policy = + PeerConnectionInterface::kBundlePolicyMaxBundle; + offerer_config.use_datagram_transport_for_data_channels = true; + offerer_config.use_datagram_transport = true; + + // Configure the callee to only use datagram transport for media. + PeerConnectionInterface::RTCConfiguration answerer_config; + answerer_config.rtcp_mux_policy = + PeerConnectionInterface::kRtcpMuxPolicyRequire; + answerer_config.bundle_policy = + PeerConnectionInterface::kBundlePolicyMaxBundle; + answerer_config.use_datagram_transport = true; + + ASSERT_TRUE(CreatePeerConnectionWrappersWithConfigAndMediaTransportFactory( + offerer_config, answerer_config, + loopback_media_transports()->first_factory(), + loopback_media_transports()->second_factory())); + ConnectFakeSignaling(); + + // Offer both media and data. + caller()->AddAudioVideoTracks(); + callee()->AddAudioVideoTracks(); + caller()->CreateDataChannel(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + + // Ensure that the data channel transport is ready. + loopback_media_transports()->SetState(webrtc::MediaTransportState::kWritable); + loopback_media_transports()->FlushAsyncInvokes(); + + ASSERT_NE(nullptr, caller()->data_channel()); + ASSERT_TRUE_WAIT(callee()->data_channel() != nullptr, kDefaultTimeout); + EXPECT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout); + EXPECT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout); + + // Both endpoints should agree to use SCTP for data channels. + EXPECT_NE(nullptr, caller()->pc()->GetSctpTransport()); + EXPECT_NE(nullptr, callee()->pc()->GetSctpTransport()); + + // Ensure data can be sent in both directions. + std::string data = "hello world"; + caller()->data_channel()->Send(DataBuffer(data)); + EXPECT_EQ_WAIT(data, callee()->data_observer()->last_message(), + kDefaultTimeout); + callee()->data_channel()->Send(DataBuffer(data)); + EXPECT_EQ_WAIT(data, caller()->data_observer()->last_message(), + kDefaultTimeout); + + // Media flow should not be impacted. + MediaExpectations media_expectations; + media_expectations.ExpectBidirectionalAudioAndVideo(); + ASSERT_TRUE(ExpectNewFrames(media_expectations)); +} + +TEST_P(PeerConnectionIntegrationTest, + DatagramTransportDataChannelWithMediaOnCallee) { + // Configure the caller to attempt use of datagram transport for data + // channels. + PeerConnectionInterface::RTCConfiguration offerer_config; + offerer_config.rtcp_mux_policy = + PeerConnectionInterface::kRtcpMuxPolicyRequire; + offerer_config.bundle_policy = + PeerConnectionInterface::kBundlePolicyMaxBundle; + offerer_config.use_datagram_transport_for_data_channels = true; + + // Configure the callee to use datagram transport for data channels and media. + PeerConnectionInterface::RTCConfiguration answerer_config; + answerer_config.rtcp_mux_policy = + PeerConnectionInterface::kRtcpMuxPolicyRequire; + answerer_config.bundle_policy = + PeerConnectionInterface::kBundlePolicyMaxBundle; + answerer_config.use_datagram_transport_for_data_channels = true; + answerer_config.use_datagram_transport = true; + + ASSERT_TRUE(CreatePeerConnectionWrappersWithConfigAndMediaTransportFactory( + offerer_config, answerer_config, + loopback_media_transports()->first_factory(), + loopback_media_transports()->second_factory())); + ConnectFakeSignaling(); + + // Offer both media and data. + caller()->AddAudioVideoTracks(); + callee()->AddAudioVideoTracks(); + caller()->CreateDataChannel(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + + // Ensure that the data channel transport is ready. + loopback_media_transports()->SetState(webrtc::MediaTransportState::kWritable); + loopback_media_transports()->FlushAsyncInvokes(); + + ASSERT_NE(nullptr, caller()->data_channel()); + ASSERT_TRUE_WAIT(callee()->data_channel() != nullptr, kDefaultTimeout); + EXPECT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout); + EXPECT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout); + + // Both endpoints should agree to use datagram transport for data channels. + EXPECT_EQ(nullptr, caller()->pc()->GetSctpTransport()); + EXPECT_EQ(nullptr, callee()->pc()->GetSctpTransport()); + + // Ensure data can be sent in both directions. + std::string data = "hello world"; + caller()->data_channel()->Send(DataBuffer(data)); + EXPECT_EQ_WAIT(data, callee()->data_observer()->last_message(), + kDefaultTimeout); + callee()->data_channel()->Send(DataBuffer(data)); + EXPECT_EQ_WAIT(data, caller()->data_observer()->last_message(), + kDefaultTimeout); + + // Media flow should not be impacted. + MediaExpectations media_expectations; + media_expectations.ExpectBidirectionalAudioAndVideo(); + ASSERT_TRUE(ExpectNewFrames(media_expectations)); +} + +TEST_P(PeerConnectionIntegrationTest, + DatagramTransportMediaWithDataChannelOnCallee) { + // Configure the caller to attempt use of datagram transport for media. + PeerConnectionInterface::RTCConfiguration offerer_config; + offerer_config.rtcp_mux_policy = + PeerConnectionInterface::kRtcpMuxPolicyRequire; + offerer_config.bundle_policy = + PeerConnectionInterface::kBundlePolicyMaxBundle; + offerer_config.use_datagram_transport = true; + + // Configure the callee to only use datagram transport for media and data + // channels. + PeerConnectionInterface::RTCConfiguration answerer_config; + answerer_config.rtcp_mux_policy = + PeerConnectionInterface::kRtcpMuxPolicyRequire; + answerer_config.bundle_policy = + PeerConnectionInterface::kBundlePolicyMaxBundle; + answerer_config.use_datagram_transport = true; + answerer_config.use_datagram_transport_for_data_channels = true; + + ASSERT_TRUE(CreatePeerConnectionWrappersWithConfigAndMediaTransportFactory( + offerer_config, answerer_config, + loopback_media_transports()->first_factory(), + loopback_media_transports()->second_factory())); + ConnectFakeSignaling(); + + // Offer both media and data. + caller()->AddAudioVideoTracks(); + callee()->AddAudioVideoTracks(); + caller()->CreateDataChannel(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + + // Ensure that the data channel transport is ready. + loopback_media_transports()->SetState(webrtc::MediaTransportState::kWritable); + loopback_media_transports()->FlushAsyncInvokes(); + + ASSERT_NE(nullptr, caller()->data_channel()); + ASSERT_TRUE_WAIT(callee()->data_channel() != nullptr, kDefaultTimeout); + EXPECT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout); + EXPECT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout); + + // Both endpoints should agree to use SCTP for data channels. + EXPECT_NE(nullptr, caller()->pc()->GetSctpTransport()); + EXPECT_NE(nullptr, callee()->pc()->GetSctpTransport()); + + // Ensure data can be sent in both directions. + std::string data = "hello world"; + caller()->data_channel()->Send(DataBuffer(data)); + EXPECT_EQ_WAIT(data, callee()->data_observer()->last_message(), + kDefaultTimeout); + callee()->data_channel()->Send(DataBuffer(data)); + EXPECT_EQ_WAIT(data, caller()->data_observer()->last_message(), + kDefaultTimeout); + + // Media flow should not be impacted. + MediaExpectations media_expectations; + media_expectations.ExpectBidirectionalAudioAndVideo(); + ASSERT_TRUE(ExpectNewFrames(media_expectations)); +} + +TEST_P(PeerConnectionIntegrationTest, DatagramTransportDataChannelAndMedia) { + // Configure the caller to use datagram transport for data channels and media. + PeerConnectionInterface::RTCConfiguration offerer_config; + offerer_config.rtcp_mux_policy = + PeerConnectionInterface::kRtcpMuxPolicyRequire; + offerer_config.bundle_policy = + PeerConnectionInterface::kBundlePolicyMaxBundle; + offerer_config.use_datagram_transport_for_data_channels = true; + offerer_config.use_datagram_transport = true; + + // Configure the callee to use datagram transport for data channels and media. + PeerConnectionInterface::RTCConfiguration answerer_config; + answerer_config.rtcp_mux_policy = + PeerConnectionInterface::kRtcpMuxPolicyRequire; + answerer_config.bundle_policy = + PeerConnectionInterface::kBundlePolicyMaxBundle; + answerer_config.use_datagram_transport_for_data_channels = true; + answerer_config.use_datagram_transport = true; + + ASSERT_TRUE(CreatePeerConnectionWrappersWithConfigAndMediaTransportFactory( + offerer_config, answerer_config, + loopback_media_transports()->first_factory(), + loopback_media_transports()->second_factory())); + ConnectFakeSignaling(); + + // Offer both media and data. + caller()->AddAudioVideoTracks(); + callee()->AddAudioVideoTracks(); + caller()->CreateDataChannel(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + + // Ensure that the data channel transport is ready. + loopback_media_transports()->SetState(webrtc::MediaTransportState::kWritable); + loopback_media_transports()->FlushAsyncInvokes(); + + ASSERT_NE(nullptr, caller()->data_channel()); + ASSERT_TRUE_WAIT(callee()->data_channel() != nullptr, kDefaultTimeout); + EXPECT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout); + EXPECT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout); + + // Both endpoints should agree to use datagram transport for data channels. + EXPECT_EQ(nullptr, caller()->pc()->GetSctpTransport()); + EXPECT_EQ(nullptr, callee()->pc()->GetSctpTransport()); + + // Ensure data can be sent in both directions. + std::string data = "hello world"; + caller()->data_channel()->Send(DataBuffer(data)); + EXPECT_EQ_WAIT(data, callee()->data_observer()->last_message(), + kDefaultTimeout); + callee()->data_channel()->Send(DataBuffer(data)); + EXPECT_EQ_WAIT(data, caller()->data_observer()->last_message(), + kDefaultTimeout); + + // Media flow should not be impacted. + MediaExpectations media_expectations; + media_expectations.ExpectBidirectionalAudioAndVideo(); + ASSERT_TRUE(ExpectNewFrames(media_expectations)); +} + // Tests that data channels use SCTP instead of datagram transport if datagram // transport is configured in receive-only mode on the caller. TEST_P(PeerConnectionIntegrationTest, diff --git a/pc/session_description.h b/pc/session_description.h index 99e78d8d28..9856cd6534 100644 --- a/pc/session_description.h +++ b/pc/session_description.h @@ -249,6 +249,13 @@ class MediaContentDescription { receive_rids_ = rids; } + virtual const absl::optional& alt_protocol() const { + return alt_protocol_; + } + virtual void set_alt_protocol(const absl::optional& protocol) { + alt_protocol_ = protocol; + } + protected: bool rtcp_mux_ = false; bool rtcp_reduced_size_ = false; @@ -270,6 +277,8 @@ class MediaContentDescription { SimulcastDescription simulcast_; std::vector receive_rids_; + + absl::optional alt_protocol_; }; // TODO(bugs.webrtc.org/8620): Remove this alias once downstream projects have diff --git a/pc/webrtc_sdp.cc b/pc/webrtc_sdp.cc index ad8fb7ed7b..7a42dcaa0a 100644 --- a/pc/webrtc_sdp.cc +++ b/pc/webrtc_sdp.cc @@ -239,6 +239,9 @@ static const char kMediaTransportSettingLine[] = "x-mt"; // This is a non-standardized setting for plugin transports. static const char kOpaqueTransportParametersLine[] = "x-opaque"; +// This is a non-standardized setting for plugin transports. +static const char kAltProtocolLine[] = "x-alt-protocol"; + // RTP payload type is in the 0-127 range. Use -1 to indicate "all" payload // types. const int kWildcardPayloadType = -1; @@ -549,6 +552,14 @@ static void AddOpaqueTransportLine( AddLine(os.str(), message); } +static void AddAltProtocolLine(const std::string& protocol, + std::string* message) { + rtc::StringBuilder os; + InitAttrLine(kAltProtocolLine, &os); + os << kSdpDelimiterColon << protocol; + AddLine(os.str(), message); +} + // Writes a SDP attribute line based on |attribute| and |value| to |message|. static void AddAttributeLine(const std::string& attribute, int value, @@ -1540,6 +1551,10 @@ void BuildMediaDescription(const ContentInfo* content_info, } } + if (media_desc->alt_protocol()) { + AddAltProtocolLine(*media_desc->alt_protocol(), message); + } + // RFC 3388 // mid-attribute = "a=mid:" identification-tag // identification-tag = token @@ -2149,6 +2164,12 @@ bool ParseOpaqueTransportLine(const std::string& line, return true; } +bool ParseAltProtocolLine(const std::string& line, + std::string* protocol, + SdpParseError* error) { + return GetValue(line, kAltProtocolLine, protocol, error); +} + bool ParseSessionDescription(const std::string& message, size_t* pos, std::string* session_id, @@ -3180,6 +3201,12 @@ bool ParseContent(const std::string& message, &transport->opaque_parameters->parameters, error)) { return false; } + } else if (HasAttribute(line, kAltProtocolLine)) { + std::string alt_protocol; + if (!ParseAltProtocolLine(line, &alt_protocol, error)) { + return false; + } + media_desc->set_alt_protocol(alt_protocol); } else if (HasAttribute(line, kAttributeFmtp)) { if (!ParseFmtpAttributes(line, media_type, media_desc, error)) { return false; diff --git a/pc/webrtc_sdp_unittest.cc b/pc/webrtc_sdp_unittest.cc index a6182c52a7..5c7e7836fb 100644 --- a/pc/webrtc_sdp_unittest.cc +++ b/pc/webrtc_sdp_unittest.cc @@ -1524,6 +1524,8 @@ class WebRtcSdpTest : public ::testing::Test { CompareSimulcastDescription( c1.media_description()->simulcast_description(), c2.media_description()->simulcast_description()); + EXPECT_EQ(c1.media_description()->alt_protocol(), + c2.media_description()->alt_protocol()); } // group @@ -1682,6 +1684,14 @@ class WebRtcSdpTest : public ::testing::Test { desc_.AddTransportInfo(info); } + void AddAltProtocol(const std::string& content_name, + const std::string& alt_protocol) { + ASSERT_TRUE(desc_.GetTransportInfoByName(content_name) != NULL); + cricket::MediaContentDescription* description = + desc_.GetContentDescriptionByName(content_name); + description->set_alt_protocol(alt_protocol); + } + void AddFingerprint() { desc_.RemoveTransportInfoByName(kAudioContentName); desc_.RemoveTransportInfoByName(kVideoContentName); @@ -2234,6 +2244,22 @@ TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithOpaqueTransportParams) { EXPECT_EQ(message, sdp_with_transport_parameters); } +TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithAltProtocol) { + AddAltProtocol(kAudioContentName, "foo"); + AddAltProtocol(kVideoContentName, "bar"); + + ASSERT_TRUE(jdesc_.Initialize(desc_.Clone(), jdesc_.session_id(), + jdesc_.session_version())); + std::string message = webrtc::SdpSerialize(jdesc_); + + std::string sdp_with_alt_protocol = kSdpFullString; + InjectAfter(kAttributeIcePwdVoice, "a=x-alt-protocol:foo\r\n", + &sdp_with_alt_protocol); + InjectAfter(kAttributeIcePwdVideo, "a=x-alt-protocol:bar\r\n", + &sdp_with_alt_protocol); + EXPECT_EQ(message, sdp_with_alt_protocol); +} + TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithRecvOnlyContent) { EXPECT_TRUE(TestSerializeDirection(RtpTransceiverDirection::kRecvOnly)); } @@ -2646,6 +2672,24 @@ TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithOpaqueTransportParams) { CompareSessionDescription(jdesc_, jdesc_with_transport_parameters)); } +TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithAltProtocol) { + std::string sdp_with_alt_protocol = kSdpFullString; + InjectAfter(kAttributeIcePwdVoice, "a=x-alt-protocol:foo\r\n", + &sdp_with_alt_protocol); + InjectAfter(kAttributeIcePwdVideo, "a=x-alt-protocol:bar\r\n", + &sdp_with_alt_protocol); + + JsepSessionDescription jdesc_with_alt_protocol(kDummyType); + EXPECT_TRUE(SdpDeserialize(sdp_with_alt_protocol, &jdesc_with_alt_protocol)); + + AddAltProtocol(kAudioContentName, "foo"); + AddAltProtocol(kVideoContentName, "bar"); + + ASSERT_TRUE(jdesc_.Initialize(desc_.Clone(), jdesc_.session_id(), + jdesc_.session_version())); + EXPECT_TRUE(CompareSessionDescription(jdesc_, jdesc_with_alt_protocol)); +} + TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithUfragPwd) { // Remove the original ice-ufrag and ice-pwd JsepSessionDescription jdesc_with_ufrag_pwd(kDummyType);