text: /*
   *  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 <memory>
  
  #include "api/audio_codecs/builtin_audio_decoder_factory.h"
  #include "api/audio_codecs/builtin_audio_encoder_factory.h"
  #include "api/create_peerconnection_factory.h"
  #include "api/video_codecs/builtin_video_decoder_factory.h"
  #include "api/video_codecs/builtin_video_encoder_factory.h"
  #include "p2p/base/fake_port_allocator.h"
  #include "p2p/base/test_stun_server.h"
  #include "p2p/client/basic_port_allocator.h"
  #include "pc/media_session.h"
  #include "pc/peer_connection.h"
  #include "pc/peer_connection_proxy.h"
  #include "pc/peer_connection_wrapper.h"
  #include "pc/sdp_utils.h"
  #ifdef WEBRTC_ANDROID
  #include "pc/test/android_test_initializer.h"
  #endif
  #include "pc/test/fake_audio_capture_module.h"
  #include "rtc_base/fake_network.h"
  #include "rtc_base/gunit.h"
  #include "rtc_base/virtual_socket_server.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::Combine;
  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
  // Should also be able to remove GetTransceiversForTesting at that point.
  
  class FakeNetworkManagerWithNoAnyNetwork : public rtc::FakeNetworkManager {
   public:
    void GetAnyAddressNetworks(NetworkList* networks) override {
      // This function allocates networks that are owned by the
      // NetworkManager. But some tests assume that they can release
      // all networks independent of the network manager.
      // In order to prevent use-after-free issues, don't allow this
      // function to have any effect when run in tests.
      RTC_LOG(LS_INFO) << "FakeNetworkManager::GetAnyAddressNetworks ignored";
    }
  };
  
  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];
        if (content.media_description()->type() == media_type) {
          candidate->set_transport_name(content.name);
          std::unique_ptr<IceCandidateInterface> jsep_candidate =
              CreateIceCandidate(content.name, i, *candidate);
          return pc()->AddIceCandidate(jsep_candidate.get());
        }
      }
      RTC_NOTREACHED();
      return false;
    }
  
    RtpTransportInternal* voice_rtp_transport() {
      return (voice_channel() ? voice_channel()->rtp_transport() : nullptr);
    }
  
    cricket::VoiceChannel* voice_channel() {
      auto transceivers = GetInternalPeerConnection()->GetTransceiversInternal();
      for (const auto& transceiver : transceivers) {
        if (transceiver->media_type() == cricket::MEDIA_TYPE_AUDIO) {
          return static_cast<cricket::VoiceChannel*>(
              transceiver->internal()->channel());
        }
      }
      return nullptr;
    }
  
    RtpTransportInternal* video_rtp_transport() {
      return (video_channel() ? video_channel()->rtp_transport() : nullptr);
    }
  
    cricket::VideoChannel* video_channel() {
      auto transceivers = GetInternalPeerConnection()->GetTransceiversInternal();
      for (const auto& transceiver : transceivers) {
        if (transceiver->media_type() == cricket::MEDIA_TYPE_VIDEO) {
          return static_cast<cricket::VideoChannel*>(
              transceiver->internal()->channel());
        }
      }
      return nullptr;
    }
  
    PeerConnection* GetInternalPeerConnection() {
      auto* pci =
          static_cast<PeerConnectionProxyWithInternal<PeerConnectionInterface>*>(
              pc());
      return static_cast<PeerConnection*>(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<RTCRemoteIceCandidateStats>()) {
        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<RTCIceCandidatePairStats>()) {
        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 PeerConnectionBundleBaseTest : public ::testing::Test {
   protected:
    typedef std::unique_ptr<PeerConnectionWrapperForBundleTest> WrapperPtr;
  
    explicit PeerConnectionBundleBaseTest(SdpSemantics sdp_semantics)
        : vss_(new rtc::VirtualSocketServer()),
          main_(vss_.get()),
          sdp_semantics_(sdp_semantics) {
  #ifdef WEBRTC_ANDROID
      InitializeAndroidObjects();
  #endif
      pc_factory_ = CreatePeerConnectionFactory(
          rtc::Thread::Current(), rtc::Thread::Current(), rtc::Thread::Current(),
          rtc::scoped_refptr<AudioDeviceModule>(FakeAudioCaptureModule::Create()),
          CreateBuiltinAudioEncoderFactory(), CreateBuiltinAudioDecoderFactory(),
          CreateBuiltinVideoEncoderFactory(), CreateBuiltinVideoDecoderFactory(),
          nullptr /* audio_mixer */, nullptr /* audio_processing */);
    }
  
    WrapperPtr CreatePeerConnection() {
      return CreatePeerConnection(RTCConfiguration());
    }
  
    WrapperPtr CreatePeerConnection(const RTCConfiguration& config) {
      auto* fake_network = NewFakeNetwork();
      auto port_allocator =
          std::make_unique<cricket::BasicPortAllocator>(fake_network);
      port_allocator->set_flags(cricket::PORTALLOCATOR_DISABLE_TCP |
                                cricket::PORTALLOCATOR_DISABLE_RELAY);
      port_allocator->set_step_delay(cricket::kMinimumStepDelay);
      auto observer = std::make_unique<MockPeerConnectionObserver>();
      RTCConfiguration modified_config = config;
      modified_config.sdp_semantics = sdp_semantics_;
      auto pc = pc_factory_->CreatePeerConnection(
          modified_config, std::move(port_allocator), nullptr, observer.get());
      if (!pc) {
        return nullptr;
      }
  
      auto wrapper = std::make_unique<PeerConnectionWrapperForBundleTest>(
          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 <typename... Args>
    WrapperPtr CreatePeerConnectionWithAudioVideo(Args&&... args) {
      auto wrapper = CreatePeerConnection(std::forward<Args>(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 FakeNetworkManagerWithNoAnyNetwork();
      fake_networks_.emplace_back(fake_network);
      return fake_network;
    }
  
    std::unique_ptr<rtc::VirtualSocketServer> vss_;
    rtc::AutoSocketServerThread main_;
    rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_;
    std::vector<std::unique_ptr<rtc::FakeNetworkManager>> fake_networks_;
    const SdpSemantics sdp_semantics_;
  };
  
  class PeerConnectionBundleTest
      : public PeerConnectionBundleBaseTest,
        public ::testing::WithParamInterface<SdpSemantics> {
   protected:
    PeerConnectionBundleTest() : PeerConnectionBundleBaseTest(GetParam()) {}
  };
  
  class PeerConnectionBundleTestUnifiedPlan
      : public PeerConnectionBundleBaseTest {
   protected:
    PeerConnectionBundleTestUnifiedPlan()
        : PeerConnectionBundleBaseTest(SdpSemantics::kUnifiedPlan) {}
  };
  
  SdpContentMutator RemoveRtcpMux() {
    return [](cricket::ContentInfo* content, cricket::TransportInfo* transport) {
      content->media_description()->set_rtcp_mux(false);
    };
  }
  
  std::vector<int> GetCandidateComponents(
      const std::vector<IceCandidateInterface*> candidates) {
    std::vector<int> components;
    components.reserve(candidates.size());
    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_P(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_P(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_P(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());
  }
  
  // It will fail if the offerer uses the mux-BUNDLE policy but the answerer
  // doesn't support BUNDLE.
  TEST_P(PeerConnectionBundleTest, MaxBundleNotSupportedInAnswer) {
    RTCConfiguration config;
    config.bundle_policy = BundlePolicy::kBundlePolicyMaxBundle;
    auto caller = CreatePeerConnectionWithAudioVideo(config);
    auto callee = CreatePeerConnectionWithAudioVideo();
  
    ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
    bool equal_before =
        (caller->voice_rtp_transport() == caller->video_rtp_transport());
    EXPECT_EQ(true, equal_before);
    RTCOfferAnswerOptions options;
    options.use_rtp_mux = false;
    EXPECT_FALSE(
        caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal(options)));
  }
  
  // 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 PeerConnectionBundleBaseTest,
        public ::testing::WithParamInterface<
            std::tuple<SdpSemantics,
                       std::tuple<BundlePolicy, BundleIncluded, bool, bool>>> {
   protected:
    PeerConnectionBundleMatrixTest()
        : PeerConnectionBundleBaseTest(std::get<0>(GetParam())) {
      auto param = std::get<1>(GetParam());
      bundle_policy_ = std::get<0>(param);
      bundle_included_ = std::get<1>(param);
      expected_same_before_ = std::get<2>(param);
      expected_same_after_ = std::get<3>(param);
    }
  
    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() == caller->video_rtp_transport());
    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() == caller->video_rtp_transport());
    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_SUITE_P(
      PeerConnectionBundleTest,
      PeerConnectionBundleMatrixTest,
      Combine(Values(SdpSemantics::kPlanB, SdpSemantics::kUnifiedPlan),
              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::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_P(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(), callee->video_rtp_transport());
  
    ASSERT_TRUE(callee->SetLocalDescription(callee->CreateAnswer()));
  
    EXPECT_EQ(callee->voice_rtp_transport(), callee->video_rtp_transport());
  }
  
  TEST_P(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_P(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());
    EXPECT_TRUE(caller->video_rtp_transport());
  }
  
  // When requiring RTCP multiplexing, the PeerConnection never makes RTCP
  // transport channels.
  TEST_P(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_rtp_transport()->rtcp_mux_enabled());
    EXPECT_FALSE(caller->video_rtp_transport()->rtcp_mux_enabled());
  
    ASSERT_TRUE(
        caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
  
    EXPECT_TRUE(caller->voice_rtp_transport()->rtcp_mux_enabled());
    EXPECT_TRUE(caller->video_rtp_transport()->rtcp_mux_enabled());
  }
  
  // When negotiating RTCP multiplexing, the PeerConnection makes RTCP transports
  // when the offer is sent, but will destroy them once the remote answer is set.
  TEST_P(PeerConnectionBundleTest,
         CreateRtcpTransportOnlyBeforeAnswerWithRtcpMuxNegotiate) {
    RTCConfiguration config;
    config.rtcp_mux_policy = RtcpMuxPolicy::kRtcpMuxPolicyNegotiate;
    auto caller = CreatePeerConnectionWithAudioVideo(config);
    auto callee = CreatePeerConnectionWithAudioVideo();
  
    ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
  
    EXPECT_FALSE(caller->voice_rtp_transport()->rtcp_mux_enabled());
    EXPECT_FALSE(caller->video_rtp_transport()->rtcp_mux_enabled());
  
    ASSERT_TRUE(
        caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
  
    EXPECT_TRUE(caller->voice_rtp_transport()->rtcp_mux_enabled());
    EXPECT_TRUE(caller->video_rtp_transport()->rtcp_mux_enabled());
  }
  
  TEST_P(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_P(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(options)));
  
    // 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_P(PeerConnectionBundleTest, BundleOnFirstMidInAnswer) {
    auto caller = CreatePeerConnectionWithAudioVideo();
    auto callee = CreatePeerConnectionWithAudioVideo();
  
    ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
  
    auto* old_video_transport = caller->video_rtp_transport();
  
    auto answer = callee->CreateAnswer();
    auto* old_bundle_group =
        answer->description()->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
    std::string first_mid = old_bundle_group->content_names()[0];
    std::string second_mid = old_bundle_group->content_names()[1];
    answer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
  
    cricket::ContentGroup new_bundle_group(cricket::GROUP_TYPE_BUNDLE);
    new_bundle_group.AddContentName(second_mid);
    new_bundle_group.AddContentName(first_mid);
    answer->description()->AddGroup(new_bundle_group);
  
    ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer)));
  
    EXPECT_EQ(old_video_transport, caller->video_rtp_transport());
    EXPECT_EQ(caller->voice_rtp_transport(), caller->video_rtp_transport());
  }
  
  // This tests that applying description with conflicted RTP demuxing criteria
  // will fail.
  TEST_P(PeerConnectionBundleTest,
         ApplyDescriptionWithConflictedDemuxCriteriaFail) {
    auto caller = CreatePeerConnectionWithAudioVideo();
    auto callee = CreatePeerConnectionWithAudioVideo();
  
    RTCOfferAnswerOptions options;
    options.use_rtp_mux = false;
    auto offer = caller->CreateOffer(options);
    // Modified the SDP to make two m= sections have the same SSRC.
    ASSERT_GE(offer->description()->contents().size(), 2U);
    offer->description()
        ->contents()[0]
        .media_description()
        ->mutable_streams()[0]
        .ssrcs[0] = 1111222;
    offer->description()
        ->contents()[1]
        .media_description()
        ->mutable_streams()[0]
        .ssrcs[0] = 1111222;
    EXPECT_TRUE(
        caller->SetLocalDescription(CloneSessionDescription(offer.get())));
    EXPECT_TRUE(callee->SetRemoteDescription(std::move(offer)));
    EXPECT_TRUE(callee->CreateAnswerAndSetAsLocal(options));
  
    // Enable BUNDLE in subsequent offer/answer exchange and two m= sections are
    // expectd to use one RtpTransport underneath.
    options.use_rtp_mux = true;
    EXPECT_TRUE(
        callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal(options)));
    auto answer = callee->CreateAnswer(options);
    // When BUNDLE is enabled, applying the description is expected to fail
    // because the demuxing criteria is conflicted.
    EXPECT_FALSE(callee->SetLocalDescription(std::move(answer)));
  }
  
  // This tests that changing the pre-negotiated BUNDLE tag is not supported.
  TEST_P(PeerConnectionBundleTest, RejectDescriptionChangingBundleTag) {
    RTCConfiguration config;
    config.bundle_policy = BundlePolicy::kBundlePolicyMaxBundle;
    auto caller = CreatePeerConnectionWithAudioVideo(config);
    auto callee = CreatePeerConnectionWithAudioVideo(config);
  
    RTCOfferAnswerOptions options;
    options.use_rtp_mux = true;
    auto offer = caller->CreateOfferAndSetAsLocal(options);
  
    // Create a new bundle-group with different bundled_mid.
    auto* old_bundle_group =
        offer->description()->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
    std::string first_mid = old_bundle_group->content_names()[0];
    std::string second_mid = old_bundle_group->content_names()[1];
    cricket::ContentGroup new_bundle_group(cricket::GROUP_TYPE_BUNDLE);
    new_bundle_group.AddContentName(second_mid);
  
    auto re_offer = CloneSessionDescription(offer.get());
    callee->SetRemoteDescription(std::move(offer));
    auto answer = callee->CreateAnswer(options);
    // Reject the first MID.
    answer->description()->contents()[0].rejected = true;
    // Remove the first MID from the bundle group.
    answer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
    answer->description()->AddGroup(new_bundle_group);
    // The answer is expected to be rejected.
    EXPECT_FALSE(caller->SetRemoteDescription(std::move(answer)));
  
    // Do the same thing for re-offer.
    re_offer->description()->contents()[0].rejected = true;
    re_offer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
    re_offer->description()->AddGroup(new_bundle_group);
    // The re-offer is expected to be rejected.
    EXPECT_FALSE(caller->SetLocalDescription(std::move(re_offer)));
  }
  
  // This tests that removing contents from BUNDLE group and reject the whole
  // BUNDLE group could work. This is a regression test for
  // (https://bugs.chromium.org/p/chromium/issues/detail?id=827917)
  #ifdef HAVE_SCTP
  TEST_P(PeerConnectionBundleTest, RemovingContentAndRejectBundleGroup) {
    RTCConfiguration config;
    config.bundle_policy = BundlePolicy::kBundlePolicyMaxBundle;
    auto caller = CreatePeerConnectionWithAudioVideo(config);
    caller->CreateDataChannel("dc");
  
    auto offer = caller->CreateOfferAndSetAsLocal();
    auto re_offer = CloneSessionDescription(offer.get());
  
    // Removing the second MID from the BUNDLE group.
    auto* old_bundle_group =
        offer->description()->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
    std::string first_mid = old_bundle_group->content_names()[0];
    std::string third_mid = old_bundle_group->content_names()[2];
    cricket::ContentGroup new_bundle_group(cricket::GROUP_TYPE_BUNDLE);
    new_bundle_group.AddContentName(first_mid);
    new_bundle_group.AddContentName(third_mid);
  
    // Reject the entire new bundle group.
    re_offer->description()->contents()[0].rejected = true;
    re_offer->description()->contents()[2].rejected = true;
    re_offer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
    re_offer->description()->AddGroup(new_bundle_group);
  
    EXPECT_TRUE(caller->SetLocalDescription(std::move(re_offer)));
  }
  #endif
  
  // This tests that the BUNDLE group in answer should be a subset of the offered
  // group.
  TEST_P(PeerConnectionBundleTest, AddContentToBundleGroupInAnswerNotSupported) {
    auto caller = CreatePeerConnectionWithAudioVideo();
    auto callee = CreatePeerConnectionWithAudioVideo();
  
    auto offer = caller->CreateOffer();
    std::string first_mid = offer->description()->contents()[0].name;
    std::string second_mid = offer->description()->contents()[1].name;
  
    cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE);
    bundle_group.AddContentName(first_mid);
    offer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
    offer->description()->AddGroup(bundle_group);
    EXPECT_TRUE(
        caller->SetLocalDescription(CloneSessionDescription(offer.get())));
    EXPECT_TRUE(callee->SetRemoteDescription(std::move(offer)));
  
    auto answer = callee->CreateAnswer();
    bundle_group.AddContentName(second_mid);
    answer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
    answer->description()->AddGroup(bundle_group);
  
    // The answer is expected to be rejected because second mid is not in the
    // offered BUNDLE group.
    EXPECT_FALSE(callee->SetLocalDescription(std::move(answer)));
  }
  
  // This tests that the BUNDLE group with non-existing MID should be rejectd.
  TEST_P(PeerConnectionBundleTest, RejectBundleGroupWithNonExistingMid) {
    auto caller = CreatePeerConnectionWithAudioVideo();
    auto callee = CreatePeerConnectionWithAudioVideo();
  
    auto offer = caller->CreateOffer();
    auto invalid_bundle_group =
        *offer->description()->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
    invalid_bundle_group.AddContentName("non-existing-MID");
    offer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
    offer->description()->AddGroup(invalid_bundle_group);
  
    EXPECT_FALSE(
        caller->SetLocalDescription(CloneSessionDescription(offer.get())));
    EXPECT_FALSE(callee->SetRemoteDescription(std::move(offer)));
  }
  
  // This tests that an answer shouldn't be able to remove an m= section from an
  // established group without rejecting it.
  TEST_P(PeerConnectionBundleTest, RemoveContentFromBundleGroup) {
    auto caller = CreatePeerConnectionWithAudioVideo();
    auto callee = CreatePeerConnectionWithAudioVideo();
  
    EXPECT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
    EXPECT_TRUE(
        caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
  
    EXPECT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
    auto answer = callee->CreateAnswer();
    std::string second_mid = answer->description()->contents()[1].name;
  
    auto invalid_bundle_group =
        *answer->description()->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
    invalid_bundle_group.RemoveContentName(second_mid);
    answer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
    answer->description()->AddGroup(invalid_bundle_group);
  
    EXPECT_FALSE(
        callee->SetLocalDescription(CloneSessionDescription(answer.get())));
  }
  
  INSTANTIATE_TEST_SUITE_P(PeerConnectionBundleTest,
                           PeerConnectionBundleTest,
                           Values(SdpSemantics::kPlanB,
                                  SdpSemantics::kUnifiedPlan));
  
  // According to RFC5888, if an endpoint understands the semantics of an
  // "a=group", it MUST return an answer with that group. So, an empty BUNDLE
  // group is valid when the answerer rejects all m= sections (by stopping all
  // transceivers), meaning there's nothing to bundle.
  //
  // Only writing this test for Unified Plan mode, since there's no way to reject
  // m= sections in answers for Plan B without SDP munging.
  TEST_F(PeerConnectionBundleTestUnifiedPlan,
         EmptyBundleGroupCreatedInAnswerWhenAppropriate) {
    auto caller = CreatePeerConnectionWithAudioVideo();
    auto callee = CreatePeerConnection();
  
    EXPECT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
  
    // Stop all transceivers, causing all m= sections to be rejected.
    for (const auto& transceiver : callee->pc()->GetTransceivers()) {
      transceiver->StopInternal();
    }
    EXPECT_TRUE(
        caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
  
    // Verify that the answer actually contained an empty bundle group.
    const SessionDescriptionInterface* desc = callee->pc()->local_description();
    ASSERT_NE(nullptr, desc);
    const cricket::ContentGroup* bundle_group =
        desc->description()->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
    ASSERT_NE(nullptr, bundle_group);
    EXPECT_TRUE(bundle_group->content_names().empty());
  }
  
  TEST_F(PeerConnectionBundleTestUnifiedPlan, MultipleBundleGroups) {
    auto caller = CreatePeerConnection();
    caller->AddAudioTrack("0_audio");
    caller->AddAudioTrack("1_audio");
    caller->AddVideoTrack("2_audio");
    caller->AddVideoTrack("3_audio");
    auto callee = CreatePeerConnection();
  
    auto offer = caller->CreateOffer(RTCOfferAnswerOptions());
    // Modify the GROUP to have two BUNDLEs. We know that the MIDs will be 0,1,2,4
    // because our implementation has predictable MIDs.
    offer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
    cricket::ContentGroup bundle_group1(cricket::GROUP_TYPE_BUNDLE);
    bundle_group1.AddContentName("0");
    bundle_group1.AddContentName("1");
    cricket::ContentGroup bundle_group2(cricket::GROUP_TYPE_BUNDLE);
    bundle_group2.AddContentName("2");
    bundle_group2.AddContentName("3");
    offer->description()->AddGroup(bundle_group1);
    offer->description()->AddGroup(bundle_group2);
  
    EXPECT_TRUE(
        caller->SetLocalDescription(CloneSessionDescription(offer.get())));
    EXPECT_TRUE(callee->SetRemoteDescription(std::move(offer)));
    auto answer = callee->CreateAnswer();
    EXPECT_TRUE(
        callee->SetLocalDescription(CloneSessionDescription(answer.get())));
    EXPECT_TRUE(caller->SetRemoteDescription(std::move(answer)));
  
    // Verify bundling on sender side.
    auto senders = caller->pc()->GetSenders();
    ASSERT_EQ(senders.size(), 4u);
    auto sender0_transport = senders[0]->dtls_transport();
    auto sender1_transport = senders[1]->dtls_transport();
    auto sender2_transport = senders[2]->dtls_transport();
    auto sender3_transport = senders[3]->dtls_transport();
    EXPECT_EQ(sender0_transport, sender1_transport);
    EXPECT_EQ(sender2_transport, sender3_transport);
    EXPECT_NE(sender0_transport, sender2_transport);
  
    // Verify bundling on receiver side.
    auto receivers = callee->pc()->GetReceivers();
    ASSERT_EQ(receivers.size(), 4u);
    auto receiver0_transport = receivers[0]->dtls_transport();
    auto receiver1_transport = receivers[1]->dtls_transport();
    auto receiver2_transport = receivers[2]->dtls_transport();
    auto receiver3_transport = receivers[3]->dtls_transport();
    EXPECT_EQ(receiver0_transport, receiver1_transport);
    EXPECT_EQ(receiver2_transport, receiver3_transport);
    EXPECT_NE(receiver0_transport, receiver2_transport);
  }
  
  // Test that, with the "max-compat" bundle policy, it's possible to add an m=
  // section that's not part of an existing bundle group.
  TEST_F(PeerConnectionBundleTestUnifiedPlan, AddNonBundledSection) {
    RTCConfiguration config;
    config.bundle_policy = PeerConnectionInterface::kBundlePolicyMaxCompat;
    auto caller = CreatePeerConnection(config);
    caller->AddAudioTrack("0_audio");
    caller->AddAudioTrack("1_audio");
    auto callee = CreatePeerConnection(config);
  
    // Establish an existing BUNDLE group.
    auto offer = caller->CreateOffer(RTCOfferAnswerOptions());
    EXPECT_TRUE(
        caller->SetLocalDescription(CloneSessionDescription(offer.get())));
    EXPECT_TRUE(callee->SetRemoteDescription(std::move(offer)));
    auto answer = callee->CreateAnswer();
    EXPECT_TRUE(
        callee->SetLocalDescription(CloneSessionDescription(answer.get())));
    EXPECT_TRUE(caller->SetRemoteDescription(std::move(answer)));
  
    // Add a track but munge SDP so it's not part of the bundle group.
    caller->AddAudioTrack("3_audio");
    offer = caller->CreateOffer(RTCOfferAnswerOptions());
    offer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
    cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE);
    bundle_group.AddContentName("0");
    bundle_group.AddContentName("1");
    offer->description()->AddGroup(bundle_group);
    EXPECT_TRUE(
        caller->SetLocalDescription(CloneSessionDescription(offer.get())));
    EXPECT_TRUE(callee->SetRemoteDescription(std::move(offer)));
    answer = callee->CreateAnswer();
    EXPECT_TRUE(
        callee->SetLocalDescription(CloneSessionDescription(answer.get())));
    EXPECT_TRUE(caller->SetRemoteDescription(std::move(answer)));
  
    // Verify bundling on the sender side.
    auto senders = caller->pc()->GetSenders();
    ASSERT_EQ(senders.size(), 3u);
    auto sender0_transport = senders[0]->dtls_transport();
    auto sender1_transport = senders[1]->dtls_transport();
    auto sender2_transport = senders[2]->dtls_transport();
    EXPECT_EQ(sender0_transport, sender1_transport);
    EXPECT_NE(sender0_transport, sender2_transport);
  
    // Verify bundling on receiver side.
    auto receivers = callee->pc()->GetReceivers();
    ASSERT_EQ(receivers.size(), 3u);
    auto receiver0_transport = receivers[0]->dtls_transport();
    auto receiver1_transport = receivers[1]->dtls_transport();
    auto receiver2_transport = receivers[2]->dtls_transport();
    EXPECT_EQ(receiver0_transport, receiver1_transport);
    EXPECT_NE(receiver0_transport, receiver2_transport);
  }
  
  }  // namespace webrtc
  