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);