diff --git a/pc/BUILD.gn b/pc/BUILD.gn index 02a8e9a727..3e552c9bc7 100644 --- a/pc/BUILD.gn +++ b/pc/BUILD.gn @@ -391,6 +391,7 @@ if (rtc_include_tests) { "localaudiosource_unittest.cc", "mediaconstraintsinterface_unittest.cc", "mediastream_unittest.cc", + "peerconnection_bundle_unittest.cc", "peerconnection_crypto_unittest.cc", "peerconnection_ice_unittest.cc", "peerconnection_integrationtest.cc", diff --git a/pc/peerconnection_bundle_unittest.cc b/pc/peerconnection_bundle_unittest.cc new file mode 100644 index 0000000000..6bc177df1f --- /dev/null +++ b/pc/peerconnection_bundle_unittest.cc @@ -0,0 +1,616 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "api/peerconnectionproxy.h" +#include "p2p/base/fakeportallocator.h" +#include "p2p/base/teststunserver.h" +#include "p2p/client/basicportallocator.h" +#include "pc/mediasession.h" +#include "pc/peerconnection.h" +#include "pc/peerconnectionwrapper.h" +#include "pc/sdputils.h" +#ifdef WEBRTC_ANDROID +#include "pc/test/androidtestinitializer.h" +#endif +#include "pc/test/fakeaudiocapturemodule.h" +#include "rtc_base/fakenetwork.h" +#include "rtc_base/gunit.h" +#include "rtc_base/ptr_util.h" +#include "rtc_base/virtualsocketserver.h" +#include "test/gmock.h" + +namespace webrtc { + +using BundlePolicy = PeerConnectionInterface::BundlePolicy; +using RTCConfiguration = PeerConnectionInterface::RTCConfiguration; +using RTCOfferAnswerOptions = PeerConnectionInterface::RTCOfferAnswerOptions; +using RtcpMuxPolicy = PeerConnectionInterface::RtcpMuxPolicy; +using rtc::SocketAddress; +using ::testing::ElementsAre; +using ::testing::UnorderedElementsAre; +using ::testing::Values; + +constexpr int kDefaultTimeout = 10000; + +// TODO(steveanton): These tests should be rewritten to use the standard +// RtpSenderInterface/DtlsTransportInterface objects once they're available in +// the API. The RtpSender can be used to determine which transport a given media +// will use: https://www.w3.org/TR/webrtc/#dom-rtcrtpsender-transport + +class PeerConnectionWrapperForBundleTest : public PeerConnectionWrapper { + public: + using PeerConnectionWrapper::PeerConnectionWrapper; + + bool AddIceCandidateToMedia(cricket::Candidate* candidate, + cricket::MediaType media_type) { + auto* desc = pc()->remote_description()->description(); + for (size_t i = 0; i < desc->contents().size(); i++) { + const auto& content = desc->contents()[i]; + auto* media_desc = + static_cast(content.description); + if (media_desc->type() == media_type) { + candidate->set_transport_name(content.name); + JsepIceCandidate jsep_candidate(content.name, i, *candidate); + return pc()->AddIceCandidate(&jsep_candidate); + } + } + RTC_NOTREACHED(); + return false; + } + + rtc::PacketTransportInternal* voice_rtp_transport_channel() { + return (voice_channel() ? voice_channel()->rtp_dtls_transport() : nullptr); + } + + rtc::PacketTransportInternal* voice_rtcp_transport_channel() { + return (voice_channel() ? voice_channel()->rtcp_dtls_transport() : nullptr); + } + + cricket::VoiceChannel* voice_channel() { + return GetInternalPeerConnection()->voice_channel(); + } + + rtc::PacketTransportInternal* video_rtp_transport_channel() { + return (video_channel() ? video_channel()->rtp_dtls_transport() : nullptr); + } + + rtc::PacketTransportInternal* video_rtcp_transport_channel() { + return (video_channel() ? video_channel()->rtcp_dtls_transport() : nullptr); + } + + cricket::VideoChannel* video_channel() { + return GetInternalPeerConnection()->video_channel(); + } + + PeerConnection* GetInternalPeerConnection() { + auto* pci = reinterpret_cast< + PeerConnectionProxyWithInternal*>(pc()); + return reinterpret_cast(pci->internal()); + } + + // Returns true if the stats indicate that an ICE connection is either in + // progress or established with the given remote address. + bool HasConnectionWithRemoteAddress(const SocketAddress& address) { + auto report = GetStats(); + if (!report) { + return false; + } + std::string matching_candidate_id; + for (auto* ice_candidate_stats : + report->GetStatsOfType()) { + if (*ice_candidate_stats->ip == address.HostAsURIString() && + *ice_candidate_stats->port == address.port()) { + matching_candidate_id = ice_candidate_stats->id(); + break; + } + } + if (matching_candidate_id.empty()) { + return false; + } + for (auto* pair_stats : + report->GetStatsOfType()) { + if (*pair_stats->remote_candidate_id == matching_candidate_id) { + if (*pair_stats->state == RTCStatsIceCandidatePairState::kInProgress || + *pair_stats->state == RTCStatsIceCandidatePairState::kSucceeded) { + return true; + } + } + } + return false; + } + + rtc::FakeNetworkManager* network() { return network_; } + + void set_network(rtc::FakeNetworkManager* network) { network_ = network; } + + private: + rtc::FakeNetworkManager* network_; +}; + +class PeerConnectionBundleTest : public ::testing::Test { + protected: + typedef std::unique_ptr WrapperPtr; + + PeerConnectionBundleTest() + : vss_(new rtc::VirtualSocketServer()), main_(vss_.get()) { +#ifdef WEBRTC_ANDROID + InitializeAndroidObjects(); +#endif + pc_factory_ = CreatePeerConnectionFactory( + rtc::Thread::Current(), rtc::Thread::Current(), rtc::Thread::Current(), + FakeAudioCaptureModule::Create(), nullptr, nullptr); + } + + WrapperPtr CreatePeerConnection() { + return CreatePeerConnection(RTCConfiguration()); + } + + WrapperPtr CreatePeerConnection(const RTCConfiguration& config) { + auto* fake_network = NewFakeNetwork(); + auto port_allocator = + rtc::MakeUnique(fake_network); + port_allocator->set_flags(cricket::PORTALLOCATOR_DISABLE_TCP | + cricket::PORTALLOCATOR_DISABLE_RELAY); + port_allocator->set_step_delay(cricket::kMinimumStepDelay); + auto observer = rtc::MakeUnique(); + auto pc = pc_factory_->CreatePeerConnection( + config, std::move(port_allocator), nullptr, observer.get()); + if (!pc) { + return nullptr; + } + + auto wrapper = rtc::MakeUnique( + pc_factory_, pc, std::move(observer)); + wrapper->set_network(fake_network); + return wrapper; + } + + // Accepts the same arguments as CreatePeerConnection and adds default audio + // and video tracks. + template + WrapperPtr CreatePeerConnectionWithAudioVideo(Args&&... args) { + auto wrapper = CreatePeerConnection(std::forward(args)...); + if (!wrapper) { + return nullptr; + } + wrapper->AddAudioTrack("a"); + wrapper->AddVideoTrack("v"); + return wrapper; + } + + cricket::Candidate CreateLocalUdpCandidate( + const rtc::SocketAddress& address) { + cricket::Candidate candidate; + candidate.set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT); + candidate.set_protocol(cricket::UDP_PROTOCOL_NAME); + candidate.set_address(address); + candidate.set_type(cricket::LOCAL_PORT_TYPE); + return candidate; + } + + rtc::FakeNetworkManager* NewFakeNetwork() { + // The PeerConnection's port allocator is tied to the PeerConnection's + // lifetime and expects the underlying NetworkManager to outlive it. If + // PeerConnectionWrapper owned the NetworkManager, it would be destroyed + // before the PeerConnection (since subclass members are destroyed before + // base class members). Therefore, the test fixture will own all the fake + // networks even though tests should access the fake network through the + // PeerConnectionWrapper. + auto* fake_network = new rtc::FakeNetworkManager(); + fake_networks_.emplace_back(fake_network); + return fake_network; + } + + std::unique_ptr vss_; + rtc::AutoSocketServerThread main_; + rtc::scoped_refptr pc_factory_; + std::vector> fake_networks_; +}; + +SdpContentMutator RemoveRtcpMux() { + return [](cricket::ContentInfo* content, cricket::TransportInfo* transport) { + auto* media_desc = + static_cast(content->description); + media_desc->set_rtcp_mux(false); + }; +} + +std::vector GetCandidateComponents( + const std::vector candidates) { + std::vector components; + for (auto* candidate : candidates) { + components.push_back(candidate->candidate().component()); + } + return components; +} + +// Test that there are 2 local UDP candidates (1 RTP and 1 RTCP candidate) for +// each media section when disabling bundling and disabling RTCP multiplexing. +TEST_F(PeerConnectionBundleTest, + TwoCandidatesForEachTransportWhenNoBundleNoRtcpMux) { + const SocketAddress kCallerAddress("1.1.1.1", 0); + const SocketAddress kCalleeAddress("2.2.2.2", 0); + + RTCConfiguration config; + config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyNegotiate; + auto caller = CreatePeerConnectionWithAudioVideo(config); + caller->network()->AddInterface(kCallerAddress); + auto callee = CreatePeerConnectionWithAudioVideo(config); + callee->network()->AddInterface(kCalleeAddress); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + RTCOfferAnswerOptions options_no_bundle; + options_no_bundle.use_rtp_mux = false; + auto answer = callee->CreateAnswer(options_no_bundle); + SdpContentsForEach(RemoveRtcpMux(), answer->description()); + ASSERT_TRUE( + callee->SetLocalDescription(CloneSessionDescription(answer.get()))); + ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer))); + + // Check that caller has separate RTP and RTCP candidates for each media. + EXPECT_TRUE_WAIT(caller->IsIceGatheringDone(), kDefaultTimeout); + EXPECT_THAT( + GetCandidateComponents(caller->observer()->GetCandidatesByMline(0)), + UnorderedElementsAre(cricket::ICE_CANDIDATE_COMPONENT_RTP, + cricket::ICE_CANDIDATE_COMPONENT_RTCP)); + EXPECT_THAT( + GetCandidateComponents(caller->observer()->GetCandidatesByMline(1)), + UnorderedElementsAre(cricket::ICE_CANDIDATE_COMPONENT_RTP, + cricket::ICE_CANDIDATE_COMPONENT_RTCP)); + + // Check that callee has separate RTP and RTCP candidates for each media. + EXPECT_TRUE_WAIT(callee->IsIceGatheringDone(), kDefaultTimeout); + EXPECT_THAT( + GetCandidateComponents(callee->observer()->GetCandidatesByMline(0)), + UnorderedElementsAre(cricket::ICE_CANDIDATE_COMPONENT_RTP, + cricket::ICE_CANDIDATE_COMPONENT_RTCP)); + EXPECT_THAT( + GetCandidateComponents(callee->observer()->GetCandidatesByMline(1)), + UnorderedElementsAre(cricket::ICE_CANDIDATE_COMPONENT_RTP, + cricket::ICE_CANDIDATE_COMPONENT_RTCP)); +} + +// Test that there is 1 local UDP candidate for both RTP and RTCP for each media +// section when disabling bundle but enabling RTCP multiplexing. +TEST_F(PeerConnectionBundleTest, + OneCandidateForEachTransportWhenNoBundleButRtcpMux) { + const SocketAddress kCallerAddress("1.1.1.1", 0); + + auto caller = CreatePeerConnectionWithAudioVideo(); + caller->network()->AddInterface(kCallerAddress); + auto callee = CreatePeerConnectionWithAudioVideo(); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + RTCOfferAnswerOptions options_no_bundle; + options_no_bundle.use_rtp_mux = false; + ASSERT_TRUE( + caller->SetRemoteDescription(callee->CreateAnswer(options_no_bundle))); + + EXPECT_TRUE_WAIT(caller->IsIceGatheringDone(), kDefaultTimeout); + + EXPECT_EQ(1u, caller->observer()->GetCandidatesByMline(0).size()); + EXPECT_EQ(1u, caller->observer()->GetCandidatesByMline(1).size()); +} + +// Test that there is 1 local UDP candidate in only the first media section when +// bundling and enabling RTCP multiplexing. +TEST_F(PeerConnectionBundleTest, + OneCandidateOnlyOnFirstTransportWhenBundleAndRtcpMux) { + const SocketAddress kCallerAddress("1.1.1.1", 0); + + RTCConfiguration config; + config.bundle_policy = BundlePolicy::kBundlePolicyMaxBundle; + auto caller = CreatePeerConnectionWithAudioVideo(config); + caller->network()->AddInterface(kCallerAddress); + auto callee = CreatePeerConnectionWithAudioVideo(config); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + ASSERT_TRUE(caller->SetRemoteDescription(callee->CreateAnswer())); + + EXPECT_TRUE_WAIT(caller->IsIceGatheringDone(), kDefaultTimeout); + + EXPECT_EQ(1u, caller->observer()->GetCandidatesByMline(0).size()); + EXPECT_EQ(0u, caller->observer()->GetCandidatesByMline(1).size()); +} + +// The following parameterized test verifies that an offer/answer with varying +// bundle policies and either bundle in the answer or not will produce the +// expected RTP transports for audio and video. In particular, for bundling we +// care about whether they are separate transports or the same. + +enum class BundleIncluded { kBundleInAnswer, kBundleNotInAnswer }; +std::ostream& operator<<(std::ostream& out, BundleIncluded value) { + switch (value) { + case BundleIncluded::kBundleInAnswer: + return out << "bundle in answer"; + case BundleIncluded::kBundleNotInAnswer: + return out << "bundle not in answer"; + } + return out << "unknown"; +} + +class PeerConnectionBundleMatrixTest + : public PeerConnectionBundleTest, + public ::testing::WithParamInterface< + std::tuple> { + protected: + PeerConnectionBundleMatrixTest() { + bundle_policy_ = std::get<0>(GetParam()); + bundle_included_ = std::get<1>(GetParam()); + expected_same_before_ = std::get<2>(GetParam()); + expected_same_after_ = std::get<3>(GetParam()); + } + + PeerConnectionInterface::BundlePolicy bundle_policy_; + BundleIncluded bundle_included_; + bool expected_same_before_; + bool expected_same_after_; +}; + +TEST_P(PeerConnectionBundleMatrixTest, + VerifyTransportsBeforeAndAfterSettingRemoteAnswer) { + RTCConfiguration config; + config.bundle_policy = bundle_policy_; + auto caller = CreatePeerConnectionWithAudioVideo(config); + auto callee = CreatePeerConnectionWithAudioVideo(); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + bool equal_before = (caller->voice_rtp_transport_channel() == + caller->video_rtp_transport_channel()); + EXPECT_EQ(expected_same_before_, equal_before); + + RTCOfferAnswerOptions options; + options.use_rtp_mux = (bundle_included_ == BundleIncluded::kBundleInAnswer); + ASSERT_TRUE( + caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal(options))); + bool equal_after = (caller->voice_rtp_transport_channel() == + caller->video_rtp_transport_channel()); + EXPECT_EQ(expected_same_after_, equal_after); +} + +// The max-bundle policy means we should anticipate bundling being negotiated, +// and multiplex audio/video from the start. +// For all other policies, bundling should only be enabled if negotiated by the +// answer. +INSTANTIATE_TEST_CASE_P( + PeerConnectionBundleTest, + PeerConnectionBundleMatrixTest, + Values(std::make_tuple(BundlePolicy::kBundlePolicyBalanced, + BundleIncluded::kBundleInAnswer, + false, + true), + std::make_tuple(BundlePolicy::kBundlePolicyBalanced, + BundleIncluded::kBundleNotInAnswer, + false, + false), + std::make_tuple(BundlePolicy::kBundlePolicyMaxBundle, + BundleIncluded::kBundleInAnswer, + true, + true), + std::make_tuple(BundlePolicy::kBundlePolicyMaxBundle, + BundleIncluded::kBundleNotInAnswer, + true, + true), + std::make_tuple(BundlePolicy::kBundlePolicyMaxCompat, + BundleIncluded::kBundleInAnswer, + false, + true), + std::make_tuple(BundlePolicy::kBundlePolicyMaxCompat, + BundleIncluded::kBundleNotInAnswer, + false, + false))); + +// Test that the audio/video transports on the callee side are the same before +// and after setting a local answer when max BUNDLE is enabled and an offer with +// BUNDLE is received. +TEST_F(PeerConnectionBundleTest, + TransportsSameForMaxBundleWithBundleInRemoteOffer) { + auto caller = CreatePeerConnectionWithAudioVideo(); + RTCConfiguration config; + config.bundle_policy = BundlePolicy::kBundlePolicyMaxBundle; + auto callee = CreatePeerConnectionWithAudioVideo(config); + + RTCOfferAnswerOptions options_with_bundle; + options_with_bundle.use_rtp_mux = true; + ASSERT_TRUE(callee->SetRemoteDescription( + caller->CreateOfferAndSetAsLocal(options_with_bundle))); + + EXPECT_EQ(callee->voice_rtp_transport_channel(), + callee->video_rtp_transport_channel()); + + ASSERT_TRUE(callee->SetLocalDescription(callee->CreateAnswer())); + + EXPECT_EQ(callee->voice_rtp_transport_channel(), + callee->video_rtp_transport_channel()); +} + +TEST_F(PeerConnectionBundleTest, + FailToSetRemoteOfferWithNoBundleWhenBundlePolicyMaxBundle) { + auto caller = CreatePeerConnectionWithAudioVideo(); + RTCConfiguration config; + config.bundle_policy = BundlePolicy::kBundlePolicyMaxBundle; + auto callee = CreatePeerConnectionWithAudioVideo(config); + + RTCOfferAnswerOptions options_no_bundle; + options_no_bundle.use_rtp_mux = false; + EXPECT_FALSE(callee->SetRemoteDescription( + caller->CreateOfferAndSetAsLocal(options_no_bundle))); +} + +// Test that if the media section which has the bundled transport is rejected, +// then the peers still connect and the bundled transport switches to the other +// media section. +// Note: This is currently failing because of the following bug: +// https://bugs.chromium.org/p/webrtc/issues/detail?id=6280 +TEST_F(PeerConnectionBundleTest, + DISABLED_SuccessfullyNegotiateMaxBundleIfBundleTransportMediaRejected) { + RTCConfiguration config; + config.bundle_policy = BundlePolicy::kBundlePolicyMaxBundle; + auto caller = CreatePeerConnectionWithAudioVideo(config); + auto callee = CreatePeerConnection(); + callee->AddVideoTrack("v"); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + + RTCOfferAnswerOptions options; + options.offer_to_receive_audio = 0; + ASSERT_TRUE( + caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal(options))); + + EXPECT_FALSE(caller->voice_rtp_transport_channel()); + EXPECT_TRUE(caller->video_rtp_transport_channel()); +} + +// When requiring RTCP multiplexing, the PeerConnection never makes RTCP +// transport channels. +TEST_F(PeerConnectionBundleTest, NeverCreateRtcpTransportWithRtcpMuxRequired) { + RTCConfiguration config; + config.rtcp_mux_policy = RtcpMuxPolicy::kRtcpMuxPolicyRequire; + auto caller = CreatePeerConnectionWithAudioVideo(config); + auto callee = CreatePeerConnectionWithAudioVideo(); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + + EXPECT_FALSE(caller->voice_rtcp_transport_channel()); + EXPECT_FALSE(caller->video_rtcp_transport_channel()); + + ASSERT_TRUE( + caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); + + EXPECT_FALSE(caller->voice_rtcp_transport_channel()); + EXPECT_FALSE(caller->video_rtcp_transport_channel()); +} + +// When negotiating RTCP multiplexing, the PeerConnection makes RTCP transport +// channels when the offer is sent, but will destroy them once the remote answer +// is set. +TEST_F(PeerConnectionBundleTest, + CreateRtcpTransportOnlyBeforeAnswerWithRtcpMuxNegotiate) { + RTCConfiguration config; + config.rtcp_mux_policy = RtcpMuxPolicy::kRtcpMuxPolicyNegotiate; + auto caller = CreatePeerConnectionWithAudioVideo(config); + auto callee = CreatePeerConnectionWithAudioVideo(); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + + EXPECT_TRUE(caller->voice_rtcp_transport_channel()); + EXPECT_TRUE(caller->video_rtcp_transport_channel()); + + ASSERT_TRUE( + caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); + + EXPECT_FALSE(caller->voice_rtcp_transport_channel()); + EXPECT_FALSE(caller->video_rtcp_transport_channel()); +} + +TEST_F(PeerConnectionBundleTest, FailToSetDescriptionWithBundleAndNoRtcpMux) { + auto caller = CreatePeerConnectionWithAudioVideo(); + auto callee = CreatePeerConnectionWithAudioVideo(); + + RTCOfferAnswerOptions options; + options.use_rtp_mux = true; + + auto offer = caller->CreateOffer(options); + SdpContentsForEach(RemoveRtcpMux(), offer->description()); + + std::string error; + EXPECT_FALSE(caller->SetLocalDescription(CloneSessionDescription(offer.get()), + &error)); + EXPECT_EQ( + "Failed to set local offer SDP: rtcp-mux must be enabled when BUNDLE is " + "enabled.", + error); + + EXPECT_FALSE(callee->SetRemoteDescription(std::move(offer), &error)); + EXPECT_EQ( + "Failed to set remote offer SDP: rtcp-mux must be enabled when BUNDLE is " + "enabled.", + error); +} + +// Test that candidates sent to the "video" transport do not get pushed down to +// the "audio" transport channel when bundling. +TEST_F(PeerConnectionBundleTest, + IgnoreCandidatesForUnusedTransportWhenBundling) { + const SocketAddress kAudioAddress1("1.1.1.1", 1111); + const SocketAddress kAudioAddress2("2.2.2.2", 2222); + const SocketAddress kVideoAddress("3.3.3.3", 3333); + const SocketAddress kCallerAddress("4.4.4.4", 0); + const SocketAddress kCalleeAddress("5.5.5.5", 0); + + auto caller = CreatePeerConnectionWithAudioVideo(); + auto callee = CreatePeerConnectionWithAudioVideo(); + + caller->network()->AddInterface(kCallerAddress); + callee->network()->AddInterface(kCalleeAddress); + + RTCOfferAnswerOptions options; + options.use_rtp_mux = true; + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + ASSERT_TRUE( + caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); + + // The way the *_WAIT checks work is they only wait if the condition fails, + // which does not help in the case where state is not changing. This is + // problematic in this test since we want to verify that adding a video + // candidate does _not_ change state. So we interleave candidates and assume + // that messages are executed in the order they were posted. + + cricket::Candidate audio_candidate1 = CreateLocalUdpCandidate(kAudioAddress1); + ASSERT_TRUE(caller->AddIceCandidateToMedia(&audio_candidate1, + cricket::MEDIA_TYPE_AUDIO)); + + cricket::Candidate video_candidate = CreateLocalUdpCandidate(kVideoAddress); + ASSERT_TRUE(caller->AddIceCandidateToMedia(&video_candidate, + cricket::MEDIA_TYPE_VIDEO)); + + cricket::Candidate audio_candidate2 = CreateLocalUdpCandidate(kAudioAddress2); + ASSERT_TRUE(caller->AddIceCandidateToMedia(&audio_candidate2, + cricket::MEDIA_TYPE_AUDIO)); + + EXPECT_TRUE_WAIT(caller->HasConnectionWithRemoteAddress(kAudioAddress1), + kDefaultTimeout); + EXPECT_TRUE_WAIT(caller->HasConnectionWithRemoteAddress(kAudioAddress2), + kDefaultTimeout); + EXPECT_FALSE(caller->HasConnectionWithRemoteAddress(kVideoAddress)); +} + +// Test that the transport used by both audio and video is the transport +// associated with the first MID in the answer BUNDLE group, even if it's in a +// different order from the offer. +TEST_F(PeerConnectionBundleTest, BundleOnFirstMidInAnswer) { + auto caller = CreatePeerConnectionWithAudioVideo(); + auto callee = CreatePeerConnectionWithAudioVideo(); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + + auto* old_video_transport = caller->video_rtp_transport_channel(); + + auto answer = callee->CreateAnswer(); + auto* old_bundle_group = + answer->description()->GetGroupByName(cricket::GROUP_TYPE_BUNDLE); + ASSERT_THAT(old_bundle_group->content_names(), + ElementsAre(cricket::CN_AUDIO, cricket::CN_VIDEO)); + answer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE); + + cricket::ContentGroup new_bundle_group(cricket::GROUP_TYPE_BUNDLE); + new_bundle_group.AddContentName(cricket::CN_VIDEO); + new_bundle_group.AddContentName(cricket::CN_AUDIO); + answer->description()->AddGroup(new_bundle_group); + + ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer))); + + EXPECT_EQ(old_video_transport, caller->video_rtp_transport_channel()); + EXPECT_EQ(caller->voice_rtp_transport_channel(), + caller->video_rtp_transport_channel()); +} + +} // namespace webrtc diff --git a/pc/peerconnection_integrationtest.cc b/pc/peerconnection_integrationtest.cc index 9453567cb0..d38433c18e 100644 --- a/pc/peerconnection_integrationtest.cc +++ b/pc/peerconnection_integrationtest.cc @@ -291,6 +291,9 @@ class PeerConnectionWrapper : public webrtc::PeerConnectionObserver, ice_connection_state_history() const { return ice_connection_state_history_; } + void clear_ice_connection_state_history() { + ice_connection_state_history_.clear(); + } // Every ICE gathering state in order that has been seen by the observer. std::vector @@ -3082,6 +3085,31 @@ TEST_F(PeerConnectionIntegrationTest, EndToEndCallWithIceRenomination) { kMaxWaitForFramesMs); } +// With a max bundle policy and RTCP muxing, adding a new media description to +// the connection should not affect ICE at all because the new media will use +// the existing connection. +TEST_F(PeerConnectionIntegrationTest, + AddMediaToConnectedBundleDoesNotRestartIce) { + PeerConnectionInterface::RTCConfiguration config; + config.bundle_policy = PeerConnectionInterface::kBundlePolicyMaxBundle; + config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyRequire; + ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig( + config, PeerConnectionInterface::RTCConfiguration())); + ConnectFakeSignaling(); + + caller()->AddAudioOnlyMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + + caller()->clear_ice_connection_state_history(); + + caller()->AddVideoOnlyMediaStream(); + caller()->CreateAndSetAndSignalOffer(); + ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout); + + EXPECT_EQ(0u, caller()->ice_connection_state_history().size()); +} + // This test sets up a call between two parties with audio and video. It then // renegotiates setting the video m-line to "port 0", then later renegotiates // again, enabling video. diff --git a/pc/peerconnectioninterface_unittest.cc b/pc/peerconnectioninterface_unittest.cc index a8b4f724af..d8fa486e18 100644 --- a/pc/peerconnectioninterface_unittest.cc +++ b/pc/peerconnectioninterface_unittest.cc @@ -944,7 +944,7 @@ class PeerConnectionInterfaceTest : public testing::Test { EXPECT_TRUE(DoSetLocalDescription(std::move(new_offer))); EXPECT_EQ(PeerConnectionInterface::kHaveLocalOffer, observer_.state_); // Wait for the ice_complete message, so that SDP will have candidates. - EXPECT_TRUE_WAIT(observer_.ice_complete_, kTimeout); + EXPECT_TRUE_WAIT(observer_.ice_gathering_complete_, kTimeout); } void CreateAnswerAsRemoteDescription(const std::string& sdp) { @@ -1598,7 +1598,7 @@ TEST_F(PeerConnectionInterfaceTest, IceCandidates) { EXPECT_TRUE(DoSetLocalDescription(std::move(answer))); EXPECT_TRUE_WAIT(observer_.last_candidate() != nullptr, kTimeout); - EXPECT_TRUE_WAIT(observer_.ice_complete_, kTimeout); + EXPECT_TRUE_WAIT(observer_.ice_gathering_complete_, kTimeout); EXPECT_TRUE(pc_->AddIceCandidate(observer_.last_candidate())); } diff --git a/pc/peerconnectionwrapper.cc b/pc/peerconnectionwrapper.cc index 9be93096a3..070deb9ddb 100644 --- a/pc/peerconnectionwrapper.cc +++ b/pc/peerconnectionwrapper.cc @@ -23,7 +23,7 @@ namespace webrtc { namespace { -const uint32_t kWaitTimeout = 10000U; +const uint32_t kDefaultTimeout = 10000U; } PeerConnectionWrapper::PeerConnectionWrapper( @@ -122,7 +122,7 @@ std::unique_ptr PeerConnectionWrapper::CreateSdp( rtc::scoped_refptr observer( new rtc::RefCountedObject()); fn(observer); - EXPECT_EQ_WAIT(true, observer->called(), kWaitTimeout); + EXPECT_EQ_WAIT(true, observer->called(), kDefaultTimeout); if (error_out && !observer->result()) { *error_out = observer->error(); } @@ -155,7 +155,7 @@ bool PeerConnectionWrapper::SetSdp( rtc::scoped_refptr observer( new rtc::RefCountedObject()); fn(observer); - EXPECT_EQ_WAIT(true, observer->called(), kWaitTimeout); + EXPECT_EQ_WAIT(true, observer->called(), kDefaultTimeout); if (error_out && !observer->result()) { *error_out = observer->error(); } @@ -186,7 +186,20 @@ PeerConnectionWrapper::signaling_state() { } bool PeerConnectionWrapper::IsIceGatheringDone() { - return observer()->ice_complete_; + return observer()->ice_gathering_complete_; +} + +bool PeerConnectionWrapper::IsIceConnected() { + return observer()->ice_connected_; +} + +rtc::scoped_refptr +PeerConnectionWrapper::GetStats() { + rtc::scoped_refptr callback( + new rtc::RefCountedObject()); + pc()->GetStats(callback); + EXPECT_TRUE_WAIT(callback->called(), kDefaultTimeout); + return callback->report(); } } // namespace webrtc diff --git a/pc/peerconnectionwrapper.h b/pc/peerconnectionwrapper.h index f74fcdb85a..88d2f07f57 100644 --- a/pc/peerconnectionwrapper.h +++ b/pc/peerconnectionwrapper.h @@ -107,6 +107,13 @@ class PeerConnectionWrapper { // Returns true if ICE has finished gathering candidates. bool IsIceGatheringDone(); + // Returns true if ICE has established a connection. + bool IsIceConnected(); + + // Calls GetStats() on the underlying PeerConnection and returns the resulting + // report. If GetStats() fails, this method returns null and fails the test. + rtc::scoped_refptr GetStats(); + private: std::unique_ptr CreateSdp( std::function fn, diff --git a/pc/test/mockpeerconnectionobservers.h b/pc/test/mockpeerconnectionobservers.h index 82098cac6d..845dbc7ce7 100644 --- a/pc/test/mockpeerconnectionobservers.h +++ b/pc/test/mockpeerconnectionobservers.h @@ -73,12 +73,15 @@ class MockPeerConnectionObserver : public PeerConnectionObserver { void OnIceConnectionChange( PeerConnectionInterface::IceConnectionState new_state) override { RTC_DCHECK(pc_->ice_connection_state() == new_state); + ice_connected_ = + (new_state == PeerConnectionInterface::kIceConnectionConnected); callback_triggered_ = true; } void OnIceGatheringChange( PeerConnectionInterface::IceGatheringState new_state) override { RTC_DCHECK(pc_->ice_gathering_state() == new_state); - ice_complete_ = new_state == PeerConnectionInterface::kIceGatheringComplete; + ice_gathering_complete_ = + new_state == PeerConnectionInterface::kIceGatheringComplete; callback_triggered_ = true; } void OnIceCandidate(const IceCandidateInterface* candidate) override { @@ -159,7 +162,8 @@ class MockPeerConnectionObserver : public PeerConnectionObserver { rtc::scoped_refptr last_datachannel_; rtc::scoped_refptr remote_streams_; bool renegotiation_needed_ = false; - bool ice_complete_ = false; + bool ice_gathering_complete_ = false; + bool ice_connected_ = false; bool callback_triggered_ = false; int num_added_tracks_ = 0; std::string last_added_track_label_; diff --git a/pc/webrtcsession.cc b/pc/webrtcsession.cc index 2e0ae50d4c..4fd1e32762 100644 --- a/pc/webrtcsession.cc +++ b/pc/webrtcsession.cc @@ -56,8 +56,9 @@ using cricket::PRFLX_PORT_TYPE; namespace webrtc { // Error messages -const char kBundleWithoutRtcpMux[] = "RTCP-MUX must be enabled when BUNDLE " - "is enabled."; +const char kBundleWithoutRtcpMux[] = + "rtcp-mux must be enabled when BUNDLE " + "is enabled."; const char kCreateChannelFailed[] = "Failed to create channels."; const char kInvalidCandidates[] = "Description contains invalid candidates."; const char kInvalidSdp[] = "Invalid session description."; diff --git a/pc/webrtcsession_unittest.cc b/pc/webrtcsession_unittest.cc index 0c54abc9cb..a6fda21d27 100644 --- a/pc/webrtcsession_unittest.cc +++ b/pc/webrtcsession_unittest.cc @@ -83,11 +83,9 @@ static const char kSessionVersion[] = "1"; // Media index of candidates belonging to the first media content. static const int kMediaContentIndex0 = 0; -static const char kMediaContentName0[] = "audio"; // Media index of candidates belonging to the second media content. static const int kMediaContentIndex1 = 1; -static const char kMediaContentName1[] = "video"; static const int kDefaultTimeout = 10000; // 10 seconds. static const int kIceCandidatesTimeout = 10000; @@ -400,12 +398,6 @@ class WebRtcSessionTest Init(); } - void InitWithRtcpMuxPolicy( - PeerConnectionInterface::RtcpMuxPolicy rtcp_mux_policy) { - PeerConnectionInterface::RTCConfiguration configuration; - Init(nullptr, rtcp_mux_policy, rtc::CryptoOptions()); - } - // Successfully init with DTLS; with a certificate generated and supplied or // with a store that generates it for us. void InitWithDtls(RTCCertificateGenerationMethod cert_gen_method) { @@ -901,51 +893,6 @@ class WebRtcSessionTest return CreateRemoteAnswer(offer, options, cricket::SEC_REQUIRED); } - void TestSessionCandidatesWithBundleRtcpMux(bool bundle, bool rtcp_mux) { - AddInterface(rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); - Init(); - SendAudioVideoStream1(); - - PeerConnectionInterface::RTCOfferAnswerOptions options; - options.use_rtp_mux = bundle; - - SessionDescriptionInterface* offer = CreateOffer(options); - // SetLocalDescription and SetRemoteDescriptions takes ownership of offer - // and answer. - SetLocalDescriptionWithoutError(offer); - - std::unique_ptr answer( - CreateRemoteAnswer(session_->local_description())); - std::string sdp; - EXPECT_TRUE(answer->ToString(&sdp)); - - size_t expected_candidate_num = 2; - if (!rtcp_mux) { - // If rtcp_mux is enabled we should expect 4 candidates - host and srflex - // for rtp and rtcp. - expected_candidate_num = 4; - // Disable rtcp-mux from the answer - const std::string kRtcpMux = "a=rtcp-mux"; - const std::string kXRtcpMux = "a=xrtcp-mux"; - rtc::replace_substrs(kRtcpMux.c_str(), kRtcpMux.length(), - kXRtcpMux.c_str(), kXRtcpMux.length(), - &sdp); - } - - SessionDescriptionInterface* new_answer = CreateSessionDescription( - JsepSessionDescription::kAnswer, sdp, NULL); - - // SetRemoteDescription to enable rtcp mux. - SetRemoteDescriptionWithoutError(new_answer); - EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout); - EXPECT_EQ(expected_candidate_num, observer_.mline_0_candidates_.size()); - if (bundle) { - EXPECT_EQ(0, observer_.mline_1_candidates_.size()); - } else { - EXPECT_EQ(expected_candidate_num, observer_.mline_1_candidates_.size()); - } - } - // The method sets up a call from the session to itself, in a loopback // arrangement. It also uses a firewall rule to create a temporary // disconnection, and then a permanent disconnection. @@ -1067,20 +1014,6 @@ class WebRtcSessionTest rtc::CryptoOptions crypto_options_; }; -TEST_F(WebRtcSessionTest, TestSessionCandidates) { - TestSessionCandidatesWithBundleRtcpMux(false, false); -} - -// Below test cases (TestSessionCandidatesWith*) verify the candidates gathered -// with rtcp-mux and/or bundle. -TEST_F(WebRtcSessionTest, TestSessionCandidatesWithRtcpMux) { - TestSessionCandidatesWithBundleRtcpMux(false, true); -} - -TEST_F(WebRtcSessionTest, TestSessionCandidatesWithBundleRtcpMux) { - TestSessionCandidatesWithBundleRtcpMux(true, true); -} - // Test that we can create and set an answer correctly when different // SSL roles have been negotiated for different transports. // See: https://bugs.chromium.org/p/webrtc/issues/detail?id=4525 @@ -1144,466 +1077,6 @@ TEST_P(WebRtcSessionTest, TestCreateAnswerWithDifferentSslRoles) { SetLocalDescriptionWithoutError(answer); } -// Test that candidates sent to the "video" transport do not get pushed down to -// the "audio" transport channel when bundling. -TEST_F(WebRtcSessionTest, TestIgnoreCandidatesForUnusedTransportWhenBundling) { - AddInterface(rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); - - InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyBalanced); - SendAudioVideoStream1(); - - cricket::MediaSessionOptions offer_options; - GetOptionsForRemoteOffer(&offer_options); - offer_options.bundle_enabled = true; - - SessionDescriptionInterface* offer = CreateRemoteOffer(offer_options); - SetRemoteDescriptionWithoutError(offer); - - cricket::MediaSessionOptions answer_options; - answer_options.bundle_enabled = true; - SessionDescriptionInterface* answer = CreateAnswer(answer_options); - SetLocalDescriptionWithoutError(answer); - - EXPECT_EQ(session_->voice_rtp_transport_channel(), - session_->video_rtp_transport_channel()); - - cricket::BaseChannel* voice_channel = session_->voice_channel(); - ASSERT_TRUE(voice_channel != NULL); - - // Checks if one of the transport channels contains a connection using a given - // port. - auto connection_with_remote_port = [this](int port) { - std::unique_ptr stats = session_->GetStats_s(); - for (auto& kv : stats->transport_stats) { - for (auto& chan_stat : kv.second.channel_stats) { - for (auto& conn_info : chan_stat.connection_infos) { - if (conn_info.remote_candidate.address().port() == port) { - return true; - } - } - } - } - return false; - }; - - EXPECT_FALSE(connection_with_remote_port(5000)); - EXPECT_FALSE(connection_with_remote_port(5001)); - EXPECT_FALSE(connection_with_remote_port(6000)); - - // The way the *_WAIT checks work is they only wait if the condition fails, - // which does not help in the case where state is not changing. This is - // problematic in this test since we want to verify that adding a video - // candidate does _not_ change state. So we interleave candidates and assume - // that messages are executed in the order they were posted. - - // First audio candidate. - cricket::Candidate candidate0; - candidate0.set_address(rtc::SocketAddress("1.1.1.1", 5000)); - candidate0.set_component(1); - candidate0.set_protocol("udp"); - candidate0.set_type("local"); - JsepIceCandidate ice_candidate0(kMediaContentName0, kMediaContentIndex0, - candidate0); - EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate0)); - - // Video candidate. - cricket::Candidate candidate1; - candidate1.set_address(rtc::SocketAddress("1.1.1.1", 6000)); - candidate1.set_component(1); - candidate1.set_protocol("udp"); - candidate1.set_type("local"); - JsepIceCandidate ice_candidate1(kMediaContentName1, kMediaContentIndex1, - candidate1); - EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate1)); - - // Second audio candidate. - cricket::Candidate candidate2; - candidate2.set_address(rtc::SocketAddress("1.1.1.1", 5001)); - candidate2.set_component(1); - candidate2.set_protocol("udp"); - candidate2.set_type("local"); - JsepIceCandidate ice_candidate2(kMediaContentName0, kMediaContentIndex0, - candidate2); - EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate2)); - - EXPECT_TRUE_WAIT(connection_with_remote_port(5000), 1000); - EXPECT_TRUE_WAIT(connection_with_remote_port(5001), 1000); - - // No need here for a _WAIT check since we are checking that state hasn't - // changed: if this is false we would be doing waits for nothing and if this - // is true then there will be no messages processed anyways. - EXPECT_FALSE(connection_with_remote_port(6000)); -} - -// kBundlePolicyBalanced BUNDLE policy and answer contains BUNDLE. -TEST_F(WebRtcSessionTest, TestBalancedBundleInAnswer) { - InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyBalanced); - SendAudioVideoStream1(); - - PeerConnectionInterface::RTCOfferAnswerOptions options; - options.use_rtp_mux = true; - - SessionDescriptionInterface* offer = CreateOffer(options); - SetLocalDescriptionWithoutError(offer); - - EXPECT_NE(session_->voice_rtp_transport_channel(), - session_->video_rtp_transport_channel()); - - SendAudioVideoStream2(); - SessionDescriptionInterface* answer = - CreateRemoteAnswer(session_->local_description()); - SetRemoteDescriptionWithoutError(answer); - - EXPECT_EQ(session_->voice_rtp_transport_channel(), - session_->video_rtp_transport_channel()); -} - -// kBundlePolicyBalanced BUNDLE policy but no BUNDLE in the answer. -TEST_F(WebRtcSessionTest, TestBalancedNoBundleInAnswer) { - InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyBalanced); - SendAudioVideoStream1(); - - PeerConnectionInterface::RTCOfferAnswerOptions options; - options.use_rtp_mux = true; - - SessionDescriptionInterface* offer = CreateOffer(options); - SetLocalDescriptionWithoutError(offer); - - EXPECT_NE(session_->voice_rtp_transport_channel(), - session_->video_rtp_transport_channel()); - - SendAudioVideoStream2(); - - // Remove BUNDLE from the answer. - std::unique_ptr answer( - CreateRemoteAnswer(session_->local_description())); - cricket::SessionDescription* answer_copy = answer->description()->Copy(); - answer_copy->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE); - JsepSessionDescription* modified_answer = - new JsepSessionDescription(JsepSessionDescription::kAnswer); - modified_answer->Initialize(answer_copy, "1", "1"); - SetRemoteDescriptionWithoutError(modified_answer); // - - EXPECT_NE(session_->voice_rtp_transport_channel(), - session_->video_rtp_transport_channel()); -} - -// kBundlePolicyMaxBundle policy with BUNDLE in the answer. -TEST_F(WebRtcSessionTest, TestMaxBundleBundleInAnswer) { - InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyMaxBundle); - SendAudioVideoStream1(); - - PeerConnectionInterface::RTCOfferAnswerOptions options; - options.use_rtp_mux = true; - - SessionDescriptionInterface* offer = CreateOffer(options); - SetLocalDescriptionWithoutError(offer); - - EXPECT_EQ(session_->voice_rtp_transport_channel(), - session_->video_rtp_transport_channel()); - - SendAudioVideoStream2(); - SessionDescriptionInterface* answer = - CreateRemoteAnswer(session_->local_description()); - SetRemoteDescriptionWithoutError(answer); - - EXPECT_EQ(session_->voice_rtp_transport_channel(), - session_->video_rtp_transport_channel()); -} - -// kBundlePolicyMaxBundle policy with BUNDLE in the answer, but no -// audio content in the answer. -TEST_F(WebRtcSessionTest, TestMaxBundleRejectAudio) { - InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyMaxBundle); - SendAudioVideoStream1(); - - PeerConnectionInterface::RTCOfferAnswerOptions options; - options.use_rtp_mux = true; - - SessionDescriptionInterface* offer = CreateOffer(options); - SetLocalDescriptionWithoutError(offer); - - EXPECT_EQ(session_->voice_rtp_transport_channel(), - session_->video_rtp_transport_channel()); - - SendVideoOnlyStream2(); - local_send_audio_ = false; - remote_recv_audio_ = false; - cricket::MediaSessionOptions recv_options; - GetOptionsForRemoteAnswer(&recv_options); - SessionDescriptionInterface* answer = - CreateRemoteAnswer(session_->local_description(), recv_options); - SetRemoteDescriptionWithoutError(answer); - - EXPECT_TRUE(nullptr == session_->voice_channel()); - EXPECT_TRUE(nullptr != session_->video_rtp_transport_channel()); - - session_->Close(); - EXPECT_TRUE(nullptr == session_->voice_rtp_transport_channel()); - EXPECT_TRUE(nullptr == session_->voice_rtcp_transport_channel()); - EXPECT_TRUE(nullptr == session_->video_rtp_transport_channel()); - EXPECT_TRUE(nullptr == session_->video_rtcp_transport_channel()); -} - -// kBundlePolicyMaxBundle policy but no BUNDLE in the answer. -TEST_F(WebRtcSessionTest, TestMaxBundleNoBundleInAnswer) { - InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyMaxBundle); - SendAudioVideoStream1(); - - PeerConnectionInterface::RTCOfferAnswerOptions options; - options.use_rtp_mux = true; - - SessionDescriptionInterface* offer = CreateOffer(options); - SetLocalDescriptionWithoutError(offer); - - EXPECT_EQ(session_->voice_rtp_transport_channel(), - session_->video_rtp_transport_channel()); - - SendAudioVideoStream2(); - - // Remove BUNDLE from the answer. - std::unique_ptr answer( - CreateRemoteAnswer(session_->local_description())); - cricket::SessionDescription* answer_copy = answer->description()->Copy(); - answer_copy->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE); - JsepSessionDescription* modified_answer = - new JsepSessionDescription(JsepSessionDescription::kAnswer); - modified_answer->Initialize(answer_copy, "1", "1"); - SetRemoteDescriptionWithoutError(modified_answer); - - EXPECT_EQ(session_->voice_rtp_transport_channel(), - session_->video_rtp_transport_channel()); -} - -// kBundlePolicyMaxBundle policy with BUNDLE in the remote offer. -TEST_F(WebRtcSessionTest, TestMaxBundleBundleInRemoteOffer) { - InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyMaxBundle); - SendAudioVideoStream1(); - - SessionDescriptionInterface* offer = CreateRemoteOffer(); - SetRemoteDescriptionWithoutError(offer); - - EXPECT_EQ(session_->voice_rtp_transport_channel(), - session_->video_rtp_transport_channel()); - - SendAudioVideoStream2(); - SessionDescriptionInterface* answer = CreateAnswer(); - SetLocalDescriptionWithoutError(answer); - - EXPECT_EQ(session_->voice_rtp_transport_channel(), - session_->video_rtp_transport_channel()); -} - -// kBundlePolicyMaxBundle policy but no BUNDLE in the remote offer. -TEST_F(WebRtcSessionTest, TestMaxBundleNoBundleInRemoteOffer) { - InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyMaxBundle); - SendAudioVideoStream1(); - - // Remove BUNDLE from the offer. - std::unique_ptr offer(CreateRemoteOffer()); - cricket::SessionDescription* offer_copy = offer->description()->Copy(); - offer_copy->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE); - JsepSessionDescription* modified_offer = - new JsepSessionDescription(JsepSessionDescription::kOffer); - modified_offer->Initialize(offer_copy, "1", "1"); - - // Expect an error when applying the remote description - SetRemoteDescriptionExpectError(JsepSessionDescription::kOffer, - kCreateChannelFailed, modified_offer); -} - -// kBundlePolicyMaxCompat bundle policy and answer contains BUNDLE. -TEST_F(WebRtcSessionTest, TestMaxCompatBundleInAnswer) { - InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyMaxCompat); - SendAudioVideoStream1(); - - PeerConnectionInterface::RTCOfferAnswerOptions rtc_options; - rtc_options.use_rtp_mux = true; - - SessionDescriptionInterface* offer = CreateOffer(rtc_options); - SetLocalDescriptionWithoutError(offer); - - EXPECT_NE(session_->voice_rtp_transport_channel(), - session_->video_rtp_transport_channel()); - - SendAudioVideoStream2(); - SessionDescriptionInterface* answer = - CreateRemoteAnswer(session_->local_description()); - SetRemoteDescriptionWithoutError(answer); - - // This should lead to an audio-only call but isn't implemented - // correctly yet. - EXPECT_EQ(session_->voice_rtp_transport_channel(), - session_->video_rtp_transport_channel()); -} - -// kBundlePolicyMaxCompat BUNDLE policy but no BUNDLE in the answer. -TEST_F(WebRtcSessionTest, TestMaxCompatNoBundleInAnswer) { - InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyMaxCompat); - SendAudioVideoStream1(); - PeerConnectionInterface::RTCOfferAnswerOptions options; - options.use_rtp_mux = true; - - SessionDescriptionInterface* offer = CreateOffer(options); - SetLocalDescriptionWithoutError(offer); - - EXPECT_NE(session_->voice_rtp_transport_channel(), - session_->video_rtp_transport_channel()); - - SendAudioVideoStream2(); - - // Remove BUNDLE from the answer. - std::unique_ptr answer( - CreateRemoteAnswer(session_->local_description())); - cricket::SessionDescription* answer_copy = answer->description()->Copy(); - answer_copy->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE); - JsepSessionDescription* modified_answer = - new JsepSessionDescription(JsepSessionDescription::kAnswer); - modified_answer->Initialize(answer_copy, "1", "1"); - SetRemoteDescriptionWithoutError(modified_answer); // - - EXPECT_NE(session_->voice_rtp_transport_channel(), - session_->video_rtp_transport_channel()); -} - -// kBundlePolicyMaxbundle and then we call SetRemoteDescription first. -TEST_F(WebRtcSessionTest, TestMaxBundleWithSetRemoteDescriptionFirst) { - InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyMaxBundle); - SendAudioVideoStream1(); - - PeerConnectionInterface::RTCOfferAnswerOptions options; - options.use_rtp_mux = true; - - SessionDescriptionInterface* offer = CreateOffer(options); - SetRemoteDescriptionWithoutError(offer); - - EXPECT_EQ(session_->voice_rtp_transport_channel(), - session_->video_rtp_transport_channel()); -} - -// Adding a new channel to a BUNDLE which is already connected should directly -// assign the bundle transport to the channel, without first setting a -// disconnected non-bundle transport and then replacing it. The application -// should not receive any changes in the ICE state. -TEST_F(WebRtcSessionTest, TestAddChannelToConnectedBundle) { - AddInterface(rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); - // Both BUNDLE and RTCP-mux need to be enabled for the ICE state to remain - // connected. Disabling either of these two means that we need to wait for the - // answer to find out if more transports are needed. - configuration_.bundle_policy = - PeerConnectionInterface::kBundlePolicyMaxBundle; - options_.disable_encryption = true; - InitWithRtcpMuxPolicy(PeerConnectionInterface::kRtcpMuxPolicyRequire); - - // Negotiate an audio channel with MAX_BUNDLE enabled. - SendAudioOnlyStream2(); - SessionDescriptionInterface* offer = CreateOffer(); - SetLocalDescriptionWithoutError(offer); - EXPECT_EQ_WAIT(PeerConnectionInterface::kIceGatheringComplete, - observer_.ice_gathering_state_, kIceCandidatesTimeout); - std::string sdp; - offer->ToString(&sdp); - SessionDescriptionInterface* answer = webrtc::CreateSessionDescription( - JsepSessionDescription::kAnswer, sdp, nullptr); - ASSERT_TRUE(answer != NULL); - SetRemoteDescriptionWithoutError(answer); - - // Wait for the ICE state to stabilize. - EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionCompleted, - observer_.ice_connection_state_, kIceCandidatesTimeout); - observer_.ice_connection_state_history_.clear(); - - // Now add a video channel which should be using the same bundle transport. - SendAudioVideoStream2(); - offer = CreateOffer(); - offer->ToString(&sdp); - SetLocalDescriptionWithoutError(offer); - answer = webrtc::CreateSessionDescription(JsepSessionDescription::kAnswer, - sdp, nullptr); - ASSERT_TRUE(answer != NULL); - SetRemoteDescriptionWithoutError(answer); - - // Wait for ICE state to stabilize - rtc::Thread::Current()->ProcessMessages(0); - EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionCompleted, - observer_.ice_connection_state_, kIceCandidatesTimeout); - - // No ICE state changes are expected to happen. - EXPECT_EQ(0, observer_.ice_connection_state_history_.size()); -} - -TEST_F(WebRtcSessionTest, TestRequireRtcpMux) { - InitWithRtcpMuxPolicy(PeerConnectionInterface::kRtcpMuxPolicyRequire); - SendAudioVideoStream1(); - - PeerConnectionInterface::RTCOfferAnswerOptions options; - SessionDescriptionInterface* offer = CreateOffer(options); - SetLocalDescriptionWithoutError(offer); - - EXPECT_TRUE(session_->voice_rtcp_transport_channel() == NULL); - EXPECT_TRUE(session_->video_rtcp_transport_channel() == NULL); - - SendAudioVideoStream2(); - SessionDescriptionInterface* answer = - CreateRemoteAnswer(session_->local_description()); - SetRemoteDescriptionWithoutError(answer); - - EXPECT_TRUE(session_->voice_rtcp_transport_channel() == NULL); - EXPECT_TRUE(session_->video_rtcp_transport_channel() == NULL); -} - -TEST_F(WebRtcSessionTest, TestNegotiateRtcpMux) { - InitWithRtcpMuxPolicy(PeerConnectionInterface::kRtcpMuxPolicyNegotiate); - SendAudioVideoStream1(); - - PeerConnectionInterface::RTCOfferAnswerOptions options; - SessionDescriptionInterface* offer = CreateOffer(options); - SetLocalDescriptionWithoutError(offer); - - EXPECT_TRUE(session_->voice_rtcp_transport_channel() != NULL); - EXPECT_TRUE(session_->video_rtcp_transport_channel() != NULL); - - SendAudioVideoStream2(); - SessionDescriptionInterface* answer = - CreateRemoteAnswer(session_->local_description()); - SetRemoteDescriptionWithoutError(answer); - - EXPECT_TRUE(session_->voice_rtcp_transport_channel() == NULL); - EXPECT_TRUE(session_->video_rtcp_transport_channel() == NULL); -} - -// This test verifies that SetLocalDescription and SetRemoteDescription fails -// if BUNDLE is enabled but rtcp-mux is disabled in m-lines. -TEST_F(WebRtcSessionTest, TestDisabledRtcpMuxWithBundleEnabled) { - Init(); - SendAudioVideoStream1(); - - PeerConnectionInterface::RTCOfferAnswerOptions options; - options.use_rtp_mux = true; - - SessionDescriptionInterface* offer = CreateOffer(options); - std::string offer_str; - offer->ToString(&offer_str); - // Disable rtcp-mux - const std::string rtcp_mux = "rtcp-mux"; - const std::string xrtcp_mux = "xrtcp-mux"; - rtc::replace_substrs(rtcp_mux.c_str(), rtcp_mux.length(), - xrtcp_mux.c_str(), xrtcp_mux.length(), - &offer_str); - SessionDescriptionInterface* local_offer = CreateSessionDescription( - SessionDescriptionInterface::kOffer, offer_str, nullptr); - ASSERT_TRUE(local_offer); - SetLocalDescriptionOfferExpectError(kBundleWithoutRtcpMux, local_offer); - - SessionDescriptionInterface* remote_offer = CreateSessionDescription( - SessionDescriptionInterface::kOffer, offer_str, nullptr); - ASSERT_TRUE(remote_offer); - SetRemoteDescriptionOfferExpectError(kBundleWithoutRtcpMux, remote_offer); - - // Trying unmodified SDP. - SetLocalDescriptionWithoutError(offer); -} - TEST_F(WebRtcSessionTest, TestRtpDataChannel) { configuration_.enable_rtp_data_channel = true; Init();