diff --git a/pc/BUILD.gn b/pc/BUILD.gn index 3e552c9bc7..02a8e9a727 100644 --- a/pc/BUILD.gn +++ b/pc/BUILD.gn @@ -391,7 +391,6 @@ 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 deleted file mode 100644 index 6bc177df1f..0000000000 --- a/pc/peerconnection_bundle_unittest.cc +++ /dev/null @@ -1,616 +0,0 @@ -/* - * 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 d38433c18e..9453567cb0 100644 --- a/pc/peerconnection_integrationtest.cc +++ b/pc/peerconnection_integrationtest.cc @@ -291,9 +291,6 @@ 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 @@ -3085,31 +3082,6 @@ 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 d8fa486e18..a8b4f724af 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_gathering_complete_, kTimeout); + EXPECT_TRUE_WAIT(observer_.ice_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_gathering_complete_, kTimeout); + EXPECT_TRUE_WAIT(observer_.ice_complete_, kTimeout); EXPECT_TRUE(pc_->AddIceCandidate(observer_.last_candidate())); } diff --git a/pc/peerconnectionwrapper.cc b/pc/peerconnectionwrapper.cc index 070deb9ddb..9be93096a3 100644 --- a/pc/peerconnectionwrapper.cc +++ b/pc/peerconnectionwrapper.cc @@ -23,7 +23,7 @@ namespace webrtc { namespace { -const uint32_t kDefaultTimeout = 10000U; +const uint32_t kWaitTimeout = 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(), kDefaultTimeout); + EXPECT_EQ_WAIT(true, observer->called(), kWaitTimeout); 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(), kDefaultTimeout); + EXPECT_EQ_WAIT(true, observer->called(), kWaitTimeout); if (error_out && !observer->result()) { *error_out = observer->error(); } @@ -186,20 +186,7 @@ PeerConnectionWrapper::signaling_state() { } bool PeerConnectionWrapper::IsIceGatheringDone() { - 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(); + return observer()->ice_complete_; } } // namespace webrtc diff --git a/pc/peerconnectionwrapper.h b/pc/peerconnectionwrapper.h index 88d2f07f57..f74fcdb85a 100644 --- a/pc/peerconnectionwrapper.h +++ b/pc/peerconnectionwrapper.h @@ -107,13 +107,6 @@ 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 845dbc7ce7..82098cac6d 100644 --- a/pc/test/mockpeerconnectionobservers.h +++ b/pc/test/mockpeerconnectionobservers.h @@ -73,15 +73,12 @@ 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_gathering_complete_ = - new_state == PeerConnectionInterface::kIceGatheringComplete; + ice_complete_ = new_state == PeerConnectionInterface::kIceGatheringComplete; callback_triggered_ = true; } void OnIceCandidate(const IceCandidateInterface* candidate) override { @@ -162,8 +159,7 @@ class MockPeerConnectionObserver : public PeerConnectionObserver { rtc::scoped_refptr last_datachannel_; rtc::scoped_refptr remote_streams_; bool renegotiation_needed_ = false; - bool ice_gathering_complete_ = false; - bool ice_connected_ = false; + bool ice_complete_ = 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 4fd1e32762..2e0ae50d4c 100644 --- a/pc/webrtcsession.cc +++ b/pc/webrtcsession.cc @@ -56,9 +56,8 @@ 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 a6fda21d27..0c54abc9cb 100644 --- a/pc/webrtcsession_unittest.cc +++ b/pc/webrtcsession_unittest.cc @@ -83,9 +83,11 @@ 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; @@ -398,6 +400,12 @@ 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) { @@ -893,6 +901,51 @@ 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. @@ -1014,6 +1067,20 @@ 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 @@ -1077,6 +1144,466 @@ 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();