diff --git a/webrtc/pc/mediasession.cc b/webrtc/pc/mediasession.cc index 34ccdce775..fc142c1777 100644 --- a/webrtc/pc/mediasession.cc +++ b/webrtc/pc/mediasession.cc @@ -67,6 +67,36 @@ const char kMediaProtocolDtlsSctp[] = "DTLS/SCTP"; const char kMediaProtocolUdpDtlsSctp[] = "UDP/DTLS/SCTP"; const char kMediaProtocolTcpDtlsSctp[] = "TCP/DTLS/SCTP"; +// Note that the below functions support some protocol strings purely for +// legacy compatibility, as required by JSEP in Section 5.1.2, Profile Names +// and Interoperability. + +static bool IsDtlsRtp(const std::string& protocol) { + // Most-likely values first. + return protocol == "UDP/TLS/RTP/SAVPF" || protocol == "TCP/TLS/RTP/SAVPF" || + protocol == "UDP/TLS/RTP/SAVP" || protocol == "TCP/TLS/RTP/SAVP"; +} + +static bool IsPlainRtp(const std::string& protocol) { + // Most-likely values first. + return protocol == "RTP/SAVPF" || protocol == "RTP/AVPF" || + protocol == "RTP/SAVP" || protocol == "RTP/AVP"; +} + +static bool IsDtlsSctp(const std::string& protocol) { + return protocol == kMediaProtocolDtlsSctp || + protocol == kMediaProtocolUdpDtlsSctp || + protocol == kMediaProtocolTcpDtlsSctp; +} + +static bool IsPlainSctp(const std::string& protocol) { + return protocol == kMediaProtocolSctp; +} + +static bool IsSctp(const std::string& protocol) { + return IsPlainSctp(protocol) || IsDtlsSctp(protocol); +} + RtpTransceiverDirection RtpTransceiverDirection::FromMediaContentDirection( MediaContentDirection md) { const bool send = (md == MD_SENDRECV || md == MD_SENDONLY); @@ -398,11 +428,6 @@ class UsedRtpHeaderExtensionIds : public UsedIds { private: }; -static bool IsSctp(const MediaContentDescription* desc) { - return ((desc->protocol() == kMediaProtocolSctp) || - (desc->protocol() == kMediaProtocolDtlsSctp)); -} - // Adds a StreamParams for each Stream in Streams with media type // media_type to content_description. // |current_params| - All currently known StreamParams of any media type. @@ -413,7 +438,7 @@ static bool AddStreamParams(MediaType media_type, MediaContentDescriptionImpl* content_description, const bool add_legacy_stream) { // SCTP streams are not negotiated using SDP/ContentDescriptions. - if (IsSctp(content_description)) { + if (IsSctp(content_description->protocol())) { return true; } @@ -1085,26 +1110,6 @@ static bool CreateMediaContentAnswer( return true; } -static bool IsDtlsRtp(const std::string& protocol) { - // Most-likely values first. - return protocol == "UDP/TLS/RTP/SAVPF" || protocol == "TCP/TLS/RTP/SAVPF" || - protocol == "UDP/TLS/RTP/SAVP" || protocol == "TCP/TLS/RTP/SAVP"; -} - -static bool IsPlainRtp(const std::string& protocol) { - // Most-likely values first. - return protocol == "RTP/SAVPF" || protocol == "RTP/AVPF" || - protocol == "RTP/SAVP" || protocol == "RTP/AVP"; -} - -static bool IsDtlsSctp(const std::string& protocol) { - return protocol == "DTLS/SCTP"; -} - -static bool IsPlainSctp(const std::string& protocol) { - return protocol == "SCTP"; -} - static bool IsMediaProtocolSupported(MediaType type, const std::string& protocol, bool secure_transport) { @@ -1357,8 +1362,8 @@ SessionDescription* MediaSessionDescriptionFactory::CreateOffer( video_added = true; } else if (IsMediaContentOfType(&*it, MEDIA_TYPE_DATA)) { MediaSessionOptions options_copy(options); - if (IsSctp(static_cast( - it->description))) { + if (IsSctp(static_cast(it->description) + ->protocol())) { options_copy.data_channel_type = DCT_SCTP; } if (!AddDataContentForOffer(options_copy, current_description, @@ -1797,6 +1802,9 @@ bool MediaSessionDescriptionFactory::AddDataContentForOffer( // before we call CreateMediaContentOffer. Otherwise, // CreateMediaContentOffer won't know this is SCTP and will // generate SSRCs rather than SIDs. + // TODO(deadbeef): Offer kMediaProtocolUdpDtlsSctp (or TcpDtlsSctp), once + // it's safe to do so. Older versions of webrtc would reject these + // protocols; see https://bugs.chromium.org/p/webrtc/issues/detail?id=7706. data->set_protocol( secure_transport ? kMediaProtocolDtlsSctp : kMediaProtocolSctp); } else { diff --git a/webrtc/pc/mediasession_unittest.cc b/webrtc/pc/mediasession_unittest.cc index a1d6a6d0c3..fc2da881f6 100644 --- a/webrtc/pc/mediasession_unittest.cc +++ b/webrtc/pc/mediasession_unittest.cc @@ -1008,6 +1008,41 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswerWithoutSctpmap) { EXPECT_FALSE(dcd_answer->use_sctpmap()); } +// Test that a valid answer will be created for "DTLS/SCTP", "UDP/DTLS/SCTP" +// and "TCP/DTLS/SCTP" offers. +TEST_F(MediaSessionDescriptionFactoryTest, + TestCreateDataAnswerToDifferentOfferedProtos) { + // Need to enable DTLS offer/answer generation (disabled by default in this + // test). + f1_.set_secure(SEC_ENABLED); + f2_.set_secure(SEC_ENABLED); + tdf1_.set_secure(SEC_ENABLED); + tdf2_.set_secure(SEC_ENABLED); + + MediaSessionOptions opts; + opts.data_channel_type = cricket::DCT_SCTP; + std::unique_ptr offer(f1_.CreateOffer(opts, nullptr)); + ASSERT_TRUE(offer.get() != nullptr); + ContentInfo* dc_offer = offer->GetContentByName("data"); + ASSERT_TRUE(dc_offer != nullptr); + DataContentDescription* dcd_offer = + static_cast(dc_offer->description); + + std::vector protos = {"DTLS/SCTP", "UDP/DTLS/SCTP", + "TCP/DTLS/SCTP"}; + for (const std::string& proto : protos) { + dcd_offer->set_protocol(proto); + std::unique_ptr answer( + f2_.CreateAnswer(offer.get(), opts, nullptr)); + const ContentInfo* dc_answer = answer->GetContentByName("data"); + ASSERT_TRUE(dc_answer != nullptr); + const DataContentDescription* dcd_answer = + static_cast(dc_answer->description); + EXPECT_FALSE(dc_answer->rejected); + EXPECT_EQ(proto, dcd_answer->protocol()); + } +} + // Verifies that the order of the media contents in the offer is preserved in // the answer. TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAnswerContentOrder) { diff --git a/webrtc/pc/peerconnection_integrationtest.cc b/webrtc/pc/peerconnection_integrationtest.cc index e6c3cf1f57..12b99b4902 100644 --- a/webrtc/pc/peerconnection_integrationtest.cc +++ b/webrtc/pc/peerconnection_integrationtest.cc @@ -2482,6 +2482,40 @@ TEST_F(PeerConnectionIntegrationTest, SctpDataChannelToAudioVideoUpgrade) { kMaxWaitForFramesMs); } +static void MakeSpecCompliantSctpOffer(cricket::SessionDescription* desc) { + const ContentInfo* dc_offer = GetFirstDataContent(desc); + ASSERT_NE(nullptr, dc_offer); + cricket::DataContentDescription* dcd_offer = + static_cast(dc_offer->description); + dcd_offer->set_use_sctpmap(false); + dcd_offer->set_protocol("UDP/DTLS/SCTP"); +} + +// Test that the data channel works when a spec-compliant SCTP m= section is +// offered (using "a=sctp-port" instead of "a=sctpmap", and using +// "UDP/DTLS/SCTP" as the protocol). +TEST_F(PeerConnectionIntegrationTest, + DataChannelWorksWhenSpecCompliantSctpOfferReceived) { + ASSERT_TRUE(CreatePeerConnectionWrappers()); + ConnectFakeSignaling(); + caller()->CreateDataChannel(); + caller()->SetGeneratedSdpMunger(MakeSpecCompliantSctpOffer); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + ASSERT_TRUE_WAIT(callee()->data_channel() != nullptr, kDefaultTimeout); + EXPECT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout); + EXPECT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout); + + // 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); +} + #endif // HAVE_SCTP // Test that the ICE connection and gathering states eventually reach