/*
 *  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 <stddef.h>
#include <stdint.h>

#include <memory>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>

#include "absl/types/optional.h"
#include "api/audio/audio_mixer.h"
#include "api/candidate.h"
#include "api/ice_transport_interface.h"
#include "api/jsep.h"
#include "api/media_types.h"
#include "api/peer_connection_interface.h"
#include "api/rtc_error.h"
#include "api/scoped_refptr.h"
#include "modules/audio_device/include/audio_device.h"
#include "modules/audio_processing/include/audio_processing.h"
#include "p2p/base/fake_port_allocator.h"
#include "p2p/base/ice_transport_internal.h"
#include "p2p/base/p2p_constants.h"
#include "p2p/base/port.h"
#include "p2p/base/port_allocator.h"
#include "p2p/base/transport_description.h"
#include "p2p/base/transport_info.h"
#include "p2p/client/basic_port_allocator.h"
#include "pc/channel_interface.h"
#include "pc/dtls_transport.h"
#include "pc/media_session.h"
#include "pc/peer_connection.h"
#include "pc/peer_connection_wrapper.h"
#include "pc/rtp_transceiver.h"
#include "pc/sdp_utils.h"
#include "pc/session_description.h"
#include "rtc_base/checks.h"
#include "rtc_base/internal/default_socket_server.h"
#include "rtc_base/ip_address.h"
#include "rtc_base/logging.h"
#include "rtc_base/net_helper.h"
#include "rtc_base/rtc_certificate_generator.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/thread.h"
#include "test/gtest.h"
#include "test/scoped_key_value_config.h"
#ifdef WEBRTC_ANDROID
#include "pc/test/android_test_initializer.h"
#endif
#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/uma_metrics.h"
#include "api/video_codecs/builtin_video_decoder_factory.h"
#include "api/video_codecs/builtin_video_encoder_factory.h"
#include "pc/peer_connection_proxy.h"
#include "pc/test/fake_audio_capture_module.h"
#include "pc/test/mock_peer_connection_observers.h"
#include "rtc_base/fake_network.h"
#include "rtc_base/gunit.h"
#include "rtc_base/strings/string_builder.h"
#include "rtc_base/virtual_socket_server.h"
#include "system_wrappers/include/metrics.h"
#include "test/gmock.h"

namespace webrtc {

using RTCConfiguration = PeerConnectionInterface::RTCConfiguration;
using RTCOfferAnswerOptions = PeerConnectionInterface::RTCOfferAnswerOptions;
using rtc::SocketAddress;
using ::testing::Combine;
using ::testing::ElementsAre;
using ::testing::Pair;
using ::testing::Values;

constexpr int kIceCandidatesTimeout = 10000;
constexpr int64_t kWaitTimeout = 10000;
constexpr uint64_t kTiebreakerDefault = 44444;

class PeerConnectionWrapperForIceTest : public PeerConnectionWrapper {
 public:
  using PeerConnectionWrapper::PeerConnectionWrapper;

  std::unique_ptr<IceCandidateInterface> CreateJsepCandidateForFirstTransport(
      cricket::Candidate* candidate) {
    RTC_DCHECK(pc()->remote_description());
    const auto* desc = pc()->remote_description()->description();
    RTC_DCHECK(desc->contents().size() > 0);
    const auto& first_content = desc->contents()[0];
    candidate->set_transport_name(first_content.name);
    return CreateIceCandidate(first_content.name, -1, *candidate);
  }

  // Adds a new ICE candidate to the first transport.
  bool AddIceCandidate(cricket::Candidate* candidate) {
    return pc()->AddIceCandidate(
        CreateJsepCandidateForFirstTransport(candidate).get());
  }

  // Returns ICE candidates from the remote session description.
  std::vector<const IceCandidateInterface*>
  GetIceCandidatesFromRemoteDescription() {
    const SessionDescriptionInterface* sdesc = pc()->remote_description();
    RTC_DCHECK(sdesc);
    std::vector<const IceCandidateInterface*> candidates;
    for (size_t mline_index = 0; mline_index < sdesc->number_of_mediasections();
         mline_index++) {
      const auto* candidate_collection = sdesc->candidates(mline_index);
      for (size_t i = 0; i < candidate_collection->count(); i++) {
        candidates.push_back(candidate_collection->at(i));
      }
    }
    return candidates;
  }

  rtc::FakeNetworkManager* network() { return network_; }

  void set_network(rtc::FakeNetworkManager* network) { network_ = network; }

  // The port allocator used by this PC.
  cricket::PortAllocator* port_allocator_;

 private:
  rtc::FakeNetworkManager* network_;
};

class PeerConnectionIceBaseTest : public ::testing::Test {
 protected:
  typedef std::unique_ptr<PeerConnectionWrapperForIceTest> WrapperPtr;

  explicit PeerConnectionIceBaseTest(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,
        std::make_unique<rtc::BasicPacketSocketFactory>(vss_.get()));
    port_allocator->set_flags(cricket::PORTALLOCATOR_DISABLE_TCP |
                              cricket::PORTALLOCATOR_DISABLE_RELAY);
    port_allocator->set_step_delay(cricket::kMinimumStepDelay);
    RTCConfiguration modified_config = config;
    modified_config.sdp_semantics = sdp_semantics_;
    auto observer = std::make_unique<MockPeerConnectionObserver>();
    auto port_allocator_copy = port_allocator.get();
    PeerConnectionDependencies pc_dependencies(observer.get());
    pc_dependencies.allocator = std::move(port_allocator);
    auto result = pc_factory_->CreatePeerConnectionOrError(
        modified_config, std::move(pc_dependencies));
    if (!result.ok()) {
      return nullptr;
    }

    observer->SetPeerConnectionInterface(result.value().get());
    auto wrapper = std::make_unique<PeerConnectionWrapperForIceTest>(
        pc_factory_, result.MoveValue(), std::move(observer));
    wrapper->set_network(fake_network);
    wrapper->port_allocator_ = port_allocator_copy;
    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;
  }

  // Remove all ICE ufrag/pwd lines from the given session description.
  void RemoveIceUfragPwd(SessionDescriptionInterface* sdesc) {
    SetIceUfragPwd(sdesc, "", "");
  }

  // Sets all ICE ufrag/pwds on the given session description.
  void SetIceUfragPwd(SessionDescriptionInterface* sdesc,
                      const std::string& ufrag,
                      const std::string& pwd) {
    auto* desc = sdesc->description();
    for (const auto& content : desc->contents()) {
      auto* transport_info = desc->GetTransportInfoByName(content.name);
      transport_info->description.ice_ufrag = ufrag;
      transport_info->description.ice_pwd = pwd;
    }
  }

  // Set ICE mode on the given session description.
  void SetIceMode(SessionDescriptionInterface* sdesc,
                  const cricket::IceMode ice_mode) {
    auto* desc = sdesc->description();
    for (const auto& content : desc->contents()) {
      auto* transport_info = desc->GetTransportInfoByName(content.name);
      transport_info->description.ice_mode = ice_mode;
    }
  }

  cricket::TransportDescription* GetFirstTransportDescription(
      SessionDescriptionInterface* sdesc) {
    auto* desc = sdesc->description();
    RTC_DCHECK(desc->contents().size() > 0);
    auto* transport_info =
        desc->GetTransportInfoByName(desc->contents()[0].name);
    RTC_DCHECK(transport_info);
    return &transport_info->description;
  }

  const cricket::TransportDescription* GetFirstTransportDescription(
      const SessionDescriptionInterface* sdesc) {
    auto* desc = sdesc->description();
    RTC_DCHECK(desc->contents().size() > 0);
    auto* transport_info =
        desc->GetTransportInfoByName(desc->contents()[0].name);
    RTC_DCHECK(transport_info);
    return &transport_info->description;
  }

  // TODO(qingsi): Rewrite this method in terms of the standard IceTransport
  // after it is implemented.
  cricket::IceRole GetIceRole(const WrapperPtr& pc_wrapper_ptr) {
    auto* pc_proxy =
        static_cast<PeerConnectionProxyWithInternal<PeerConnectionInterface>*>(
            pc_wrapper_ptr->pc());
    PeerConnection* pc = static_cast<PeerConnection*>(pc_proxy->internal());
    for (const auto& transceiver : pc->GetTransceiversInternal()) {
      if (transceiver->media_type() == cricket::MEDIA_TYPE_AUDIO) {
        auto dtls_transport = pc->LookupDtlsTransportByMidInternal(
            transceiver->internal()->channel()->mid());
        return dtls_transport->ice_transport()->internal()->GetIceRole();
      }
    }
    RTC_DCHECK_NOTREACHED();
    return cricket::ICEROLE_UNKNOWN;
  }

  // Returns a list of (ufrag, pwd) pairs in the order that they appear in
  // `description`, or the empty list if `description` is null.
  std::vector<std::pair<std::string, std::string>> GetIceCredentials(
      const SessionDescriptionInterface* description) {
    std::vector<std::pair<std::string, std::string>> ice_credentials;
    if (!description)
      return ice_credentials;
    const auto* desc = description->description();
    for (const auto& content_info : desc->contents()) {
      const auto* transport_info =
          desc->GetTransportInfoByName(content_info.name);
      if (transport_info) {
        ice_credentials.push_back(
            std::make_pair(transport_info->description.ice_ufrag,
                           transport_info->description.ice_pwd));
      }
    }
    return ice_credentials;
  }

  bool AddCandidateToFirstTransport(cricket::Candidate* candidate,
                                    SessionDescriptionInterface* sdesc) {
    auto* desc = sdesc->description();
    RTC_DCHECK(desc->contents().size() > 0);
    const auto& first_content = desc->contents()[0];
    candidate->set_transport_name(first_content.name);
    std::unique_ptr<IceCandidateInterface> jsep_candidate =
        CreateIceCandidate(first_content.name, 0, *candidate);
    return sdesc->AddCandidate(jsep_candidate.get());
  }

  rtc::FakeNetworkManager* NewFakeNetwork() {
    // The PeerConnection's port allocator is tied to the PeerConnection's
    // lifetime and expects the underlying NetworkManager to outlive it. That
    // prevents us from having the PeerConnectionWrapper own the fake network.
    // 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<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 PeerConnectionIceTest
    : public PeerConnectionIceBaseTest,
      public ::testing::WithParamInterface<SdpSemantics> {
 protected:
  PeerConnectionIceTest() : PeerConnectionIceBaseTest(GetParam()) {
    webrtc::metrics::Reset();
  }
};

::testing::AssertionResult AssertCandidatesEqual(const char* a_expr,
                                                 const char* b_expr,
                                                 const cricket::Candidate& a,
                                                 const cricket::Candidate& b) {
  rtc::StringBuilder failure_info;
  if (a.component() != b.component()) {
    failure_info << "\ncomponent: " << a.component() << " != " << b.component();
  }
  if (a.protocol() != b.protocol()) {
    failure_info << "\nprotocol: " << a.protocol() << " != " << b.protocol();
  }
  if (a.address() != b.address()) {
    failure_info << "\naddress: " << a.address().ToString()
                 << " != " << b.address().ToString();
  }
  if (a.type() != b.type()) {
    failure_info << "\ntype: " << a.type() << " != " << b.type();
  }
  std::string failure_info_str = failure_info.str();
  if (failure_info_str.empty()) {
    return ::testing::AssertionSuccess();
  } else {
    return ::testing::AssertionFailure()
           << a_expr << " and " << b_expr << " are not equal"
           << failure_info_str;
  }
}

TEST_P(PeerConnectionIceTest, OfferContainsGatheredCandidates) {
  const SocketAddress kLocalAddress("1.1.1.1", 0);

  auto caller = CreatePeerConnectionWithAudioVideo();
  caller->network()->AddInterface(kLocalAddress);

  // Start ICE candidate gathering by setting the local offer.
  ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));

  EXPECT_TRUE_WAIT(caller->IsIceGatheringDone(), kIceCandidatesTimeout);

  auto offer = caller->CreateOffer();
  EXPECT_LT(0u, caller->observer()->GetCandidatesByMline(0).size());
  EXPECT_EQ(caller->observer()->GetCandidatesByMline(0).size(),
            offer->candidates(0)->count());
  EXPECT_LT(0u, caller->observer()->GetCandidatesByMline(1).size());
  EXPECT_EQ(caller->observer()->GetCandidatesByMline(1).size(),
            offer->candidates(1)->count());
}

TEST_P(PeerConnectionIceTest, AnswerContainsGatheredCandidates) {
  const SocketAddress kCallerAddress("1.1.1.1", 0);

  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();
  caller->network()->AddInterface(kCallerAddress);

  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
  ASSERT_TRUE(callee->SetLocalDescription(callee->CreateAnswer()));

  EXPECT_TRUE_WAIT(callee->IsIceGatheringDone(), kIceCandidatesTimeout);

  auto* answer = callee->pc()->local_description();
  EXPECT_LT(0u, caller->observer()->GetCandidatesByMline(0).size());
  EXPECT_EQ(callee->observer()->GetCandidatesByMline(0).size(),
            answer->candidates(0)->count());
  EXPECT_LT(0u, caller->observer()->GetCandidatesByMline(1).size());
  EXPECT_EQ(callee->observer()->GetCandidatesByMline(1).size(),
            answer->candidates(1)->count());
}

TEST_P(PeerConnectionIceTest,
       CanSetRemoteSessionDescriptionWithRemoteCandidates) {
  const SocketAddress kCallerAddress("1.1.1.1", 1111);

  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  auto offer = caller->CreateOfferAndSetAsLocal();
  cricket::Candidate candidate = CreateLocalUdpCandidate(kCallerAddress);
  AddCandidateToFirstTransport(&candidate, offer.get());

  ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
  auto remote_candidates = callee->GetIceCandidatesFromRemoteDescription();
  ASSERT_EQ(1u, remote_candidates.size());
  EXPECT_PRED_FORMAT2(AssertCandidatesEqual, candidate,
                      remote_candidates[0]->candidate());
}

TEST_P(PeerConnectionIceTest, SetLocalDescriptionFailsIfNoIceCredentials) {
  auto caller = CreatePeerConnectionWithAudioVideo();

  auto offer = caller->CreateOffer();
  RemoveIceUfragPwd(offer.get());

  EXPECT_FALSE(caller->SetLocalDescription(std::move(offer)));
}

TEST_P(PeerConnectionIceTest, SetRemoteDescriptionFailsIfNoIceCredentials) {
  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  auto offer = caller->CreateOfferAndSetAsLocal();
  RemoveIceUfragPwd(offer.get());

  EXPECT_FALSE(callee->SetRemoteDescription(std::move(offer)));
}

// Test that doing an offer/answer exchange with no transport (i.e., no data
// channel or media) results in the ICE connection state staying at New.
TEST_P(PeerConnectionIceTest,
       OfferAnswerWithNoTransportsDoesNotChangeIceConnectionState) {
  auto caller = CreatePeerConnection();
  auto callee = CreatePeerConnection();

  ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));

  EXPECT_EQ(PeerConnectionInterface::kIceConnectionNew,
            caller->pc()->ice_connection_state());
  EXPECT_EQ(PeerConnectionInterface::kIceConnectionNew,
            callee->pc()->ice_connection_state());
}

// The following group tests that ICE candidates are not generated before
// SetLocalDescription is called on a PeerConnection.

TEST_P(PeerConnectionIceTest, NoIceCandidatesBeforeSetLocalDescription) {
  const SocketAddress kLocalAddress("1.1.1.1", 0);

  auto caller = CreatePeerConnectionWithAudioVideo();
  caller->network()->AddInterface(kLocalAddress);

  // Pump for 1 second and verify that no candidates are generated.
  rtc::Thread::Current()->ProcessMessages(1000);

  EXPECT_EQ(0u, caller->observer()->candidates_.size());
}
TEST_P(PeerConnectionIceTest,
       NoIceCandidatesBeforeAnswerSetAsLocalDescription) {
  const SocketAddress kCallerAddress("1.1.1.1", 1111);

  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();
  caller->network()->AddInterface(kCallerAddress);

  auto offer = caller->CreateOfferAndSetAsLocal();
  cricket::Candidate candidate = CreateLocalUdpCandidate(kCallerAddress);
  AddCandidateToFirstTransport(&candidate, offer.get());
  ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));

  // Pump for 1 second and verify that no candidates are generated.
  rtc::Thread::Current()->ProcessMessages(1000);

  EXPECT_EQ(0u, callee->observer()->candidates_.size());
}

TEST_P(PeerConnectionIceTest, CannotAddCandidateWhenRemoteDescriptionNotSet) {
  const SocketAddress kCalleeAddress("1.1.1.1", 1111);

  auto caller = CreatePeerConnectionWithAudioVideo();
  cricket::Candidate candidate = CreateLocalUdpCandidate(kCalleeAddress);
  std::unique_ptr<IceCandidateInterface> jsep_candidate =
      CreateIceCandidate(cricket::CN_AUDIO, 0, candidate);

  EXPECT_FALSE(caller->pc()->AddIceCandidate(jsep_candidate.get()));

  caller->CreateOfferAndSetAsLocal();

  EXPECT_FALSE(caller->pc()->AddIceCandidate(jsep_candidate.get()));
  EXPECT_METRIC_THAT(
      webrtc::metrics::Samples("WebRTC.PeerConnection.AddIceCandidate"),
      ElementsAre(Pair(kAddIceCandidateFailNoRemoteDescription, 2)));
}

TEST_P(PeerConnectionIceTest, CannotAddCandidateWhenPeerConnectionClosed) {
  const SocketAddress kCalleeAddress("1.1.1.1", 1111);

  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));

  cricket::Candidate candidate = CreateLocalUdpCandidate(kCalleeAddress);
  auto* audio_content = cricket::GetFirstAudioContent(
      caller->pc()->local_description()->description());
  std::unique_ptr<IceCandidateInterface> jsep_candidate =
      CreateIceCandidate(audio_content->name, 0, candidate);

  caller->pc()->Close();

  EXPECT_FALSE(caller->pc()->AddIceCandidate(jsep_candidate.get()));
}

TEST_P(PeerConnectionIceTest, DuplicateIceCandidateIgnoredWhenAdded) {
  const SocketAddress kCalleeAddress("1.1.1.1", 1111);

  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
  ASSERT_TRUE(
      caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));

  cricket::Candidate candidate = CreateLocalUdpCandidate(kCalleeAddress);
  caller->AddIceCandidate(&candidate);
  EXPECT_TRUE(caller->AddIceCandidate(&candidate));
  EXPECT_EQ(1u, caller->GetIceCandidatesFromRemoteDescription().size());
}

// TODO(tommi): Re-enable after updating RTCPeerConnection-blockedPorts.html in
// Chromium (the test needs setRemoteDescription to succeed for an invalid
// candidate).
TEST_P(PeerConnectionIceTest, DISABLED_ErrorOnInvalidRemoteIceCandidateAdded) {
  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();
  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
  // Add a candidate to the remote description with a candidate that has an
  // invalid address (port number == 2).
  auto answer = callee->CreateAnswerAndSetAsLocal();
  cricket::Candidate bad_candidate =
      CreateLocalUdpCandidate(SocketAddress("2.2.2.2", 2));
  RTC_LOG(LS_INFO) << "Bad candidate: " << bad_candidate.ToString();
  AddCandidateToFirstTransport(&bad_candidate, answer.get());
  // Now the call to SetRemoteDescription should fail.
  EXPECT_FALSE(caller->SetRemoteDescription(std::move(answer)));
}

TEST_P(PeerConnectionIceTest,
       CannotRemoveIceCandidatesWhenPeerConnectionClosed) {
  const SocketAddress kCalleeAddress("1.1.1.1", 1111);

  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));

  cricket::Candidate candidate = CreateLocalUdpCandidate(kCalleeAddress);
  auto* audio_content = cricket::GetFirstAudioContent(
      caller->pc()->local_description()->description());
  std::unique_ptr<IceCandidateInterface> ice_candidate =
      CreateIceCandidate(audio_content->name, 0, candidate);

  ASSERT_TRUE(caller->pc()->AddIceCandidate(ice_candidate.get()));

  caller->pc()->Close();

  EXPECT_FALSE(caller->pc()->RemoveIceCandidates({candidate}));
}

TEST_P(PeerConnectionIceTest,
       AddRemoveCandidateWithEmptyTransportDoesNotCrash) {
  const SocketAddress kCalleeAddress("1.1.1.1", 1111);

  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
  ASSERT_TRUE(
      caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));

  // `candidate.transport_name()` is empty.
  cricket::Candidate candidate = CreateLocalUdpCandidate(kCalleeAddress);
  auto* audio_content = cricket::GetFirstAudioContent(
      caller->pc()->local_description()->description());
  std::unique_ptr<IceCandidateInterface> ice_candidate =
      CreateIceCandidate(audio_content->name, 0, candidate);
  EXPECT_TRUE(caller->pc()->AddIceCandidate(ice_candidate.get()));
  EXPECT_TRUE(caller->pc()->RemoveIceCandidates({candidate}));
}

TEST_P(PeerConnectionIceTest, RemoveCandidateRemovesFromRemoteDescription) {
  const SocketAddress kCalleeAddress("1.1.1.1", 1111);

  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
  ASSERT_TRUE(
      caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));

  cricket::Candidate candidate = CreateLocalUdpCandidate(kCalleeAddress);
  ASSERT_TRUE(caller->AddIceCandidate(&candidate));
  EXPECT_TRUE(caller->pc()->RemoveIceCandidates({candidate}));
  EXPECT_EQ(0u, caller->GetIceCandidatesFromRemoteDescription().size());
}

// Test that if a candidate is added via AddIceCandidate and via an updated
// remote description, then both candidates appear in the stored remote
// description.
TEST_P(PeerConnectionIceTest,
       CandidateInSubsequentOfferIsAddedToRemoteDescription) {
  const SocketAddress kCallerAddress1("1.1.1.1", 1111);
  const SocketAddress kCallerAddress2("2.2.2.2", 2222);

  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
  ASSERT_TRUE(
      caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));

  // Add one candidate via `AddIceCandidate`.
  cricket::Candidate candidate1 = CreateLocalUdpCandidate(kCallerAddress1);
  ASSERT_TRUE(callee->AddIceCandidate(&candidate1));

  // Add the second candidate via a reoffer.
  auto offer = caller->CreateOffer();
  cricket::Candidate candidate2 = CreateLocalUdpCandidate(kCallerAddress2);
  AddCandidateToFirstTransport(&candidate2, offer.get());

  // Expect both candidates to appear in the callee's remote description.
  ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
  EXPECT_EQ(2u, callee->GetIceCandidatesFromRemoteDescription().size());
}

// The follow test verifies that SetLocal/RemoteDescription fails when an offer
// has either ICE ufrag/pwd too short or too long and succeeds otherwise.
// The standard (https://tools.ietf.org/html/rfc5245#section-15.4) says that
// pwd must be 22-256 characters and ufrag must be 4-256 characters.
TEST_P(PeerConnectionIceTest, VerifyUfragPwdLength) {
  auto set_local_description_with_ufrag_pwd_length = [this](int ufrag_len,
                                                            int pwd_len) {
    auto pc = CreatePeerConnectionWithAudioVideo();
    auto offer = pc->CreateOffer();
    SetIceUfragPwd(offer.get(), std::string(ufrag_len, 'x'),
                   std::string(pwd_len, 'x'));
    return pc->SetLocalDescription(std::move(offer));
  };

  auto set_remote_description_with_ufrag_pwd_length = [this](int ufrag_len,
                                                             int pwd_len) {
    auto pc = CreatePeerConnectionWithAudioVideo();
    auto offer = pc->CreateOffer();
    SetIceUfragPwd(offer.get(), std::string(ufrag_len, 'x'),
                   std::string(pwd_len, 'x'));
    return pc->SetRemoteDescription(std::move(offer));
  };

  EXPECT_FALSE(set_local_description_with_ufrag_pwd_length(3, 22));
  EXPECT_FALSE(set_remote_description_with_ufrag_pwd_length(3, 22));
  EXPECT_FALSE(set_local_description_with_ufrag_pwd_length(257, 22));
  EXPECT_FALSE(set_remote_description_with_ufrag_pwd_length(257, 22));
  EXPECT_FALSE(set_local_description_with_ufrag_pwd_length(4, 21));
  EXPECT_FALSE(set_remote_description_with_ufrag_pwd_length(4, 21));
  EXPECT_FALSE(set_local_description_with_ufrag_pwd_length(4, 257));
  EXPECT_FALSE(set_remote_description_with_ufrag_pwd_length(4, 257));
  EXPECT_TRUE(set_local_description_with_ufrag_pwd_length(4, 22));
  EXPECT_TRUE(set_remote_description_with_ufrag_pwd_length(4, 22));
  EXPECT_TRUE(set_local_description_with_ufrag_pwd_length(256, 256));
  EXPECT_TRUE(set_remote_description_with_ufrag_pwd_length(256, 256));
}

::testing::AssertionResult AssertIpInCandidates(
    const char* address_expr,
    const char* candidates_expr,
    const SocketAddress& address,
    const std::vector<IceCandidateInterface*> candidates) {
  rtc::StringBuilder candidate_hosts;
  for (const auto* candidate : candidates) {
    const auto& candidate_ip = candidate->candidate().address().ipaddr();
    if (candidate_ip == address.ipaddr()) {
      return ::testing::AssertionSuccess();
    }
    candidate_hosts << "\n" << candidate_ip.ToString();
  }
  return ::testing::AssertionFailure()
         << address_expr << " (host " << address.HostAsURIString()
         << ") not in " << candidates_expr
         << " which have the following address hosts:" << candidate_hosts.str();
}

TEST_P(PeerConnectionIceTest, CandidatesGeneratedForEachLocalInterface) {
  const SocketAddress kLocalAddress1("1.1.1.1", 0);
  const SocketAddress kLocalAddress2("2.2.2.2", 0);

  auto caller = CreatePeerConnectionWithAudioVideo();
  caller->network()->AddInterface(kLocalAddress1);
  caller->network()->AddInterface(kLocalAddress2);

  caller->CreateOfferAndSetAsLocal();
  EXPECT_TRUE_WAIT(caller->IsIceGatheringDone(), kIceCandidatesTimeout);

  auto candidates = caller->observer()->GetCandidatesByMline(0);
  EXPECT_PRED_FORMAT2(AssertIpInCandidates, kLocalAddress1, candidates);
  EXPECT_PRED_FORMAT2(AssertIpInCandidates, kLocalAddress2, candidates);
}

TEST_P(PeerConnectionIceTest, TrickledSingleCandidateAddedToRemoteDescription) {
  const SocketAddress kCallerAddress("1.1.1.1", 1111);

  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));

  cricket::Candidate candidate = CreateLocalUdpCandidate(kCallerAddress);
  callee->AddIceCandidate(&candidate);
  auto candidates = callee->GetIceCandidatesFromRemoteDescription();
  ASSERT_EQ(1u, candidates.size());
  EXPECT_PRED_FORMAT2(AssertCandidatesEqual, candidate,
                      candidates[0]->candidate());
}

TEST_P(PeerConnectionIceTest, TwoTrickledCandidatesAddedToRemoteDescription) {
  const SocketAddress kCalleeAddress1("1.1.1.1", 1111);
  const SocketAddress kCalleeAddress2("2.2.2.2", 2222);

  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
  ASSERT_TRUE(
      caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));

  cricket::Candidate candidate1 = CreateLocalUdpCandidate(kCalleeAddress1);
  caller->AddIceCandidate(&candidate1);

  cricket::Candidate candidate2 = CreateLocalUdpCandidate(kCalleeAddress2);
  caller->AddIceCandidate(&candidate2);

  auto candidates = caller->GetIceCandidatesFromRemoteDescription();
  ASSERT_EQ(2u, candidates.size());
  EXPECT_PRED_FORMAT2(AssertCandidatesEqual, candidate1,
                      candidates[0]->candidate());
  EXPECT_PRED_FORMAT2(AssertCandidatesEqual, candidate2,
                      candidates[1]->candidate());
}

TEST_P(PeerConnectionIceTest, AsyncAddIceCandidateIsAddedToRemoteDescription) {
  auto candidate = CreateLocalUdpCandidate(SocketAddress("1.1.1.1", 1111));

  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));

  auto jsep_candidate =
      callee->CreateJsepCandidateForFirstTransport(&candidate);
  bool operation_completed = false;
  callee->pc()->AddIceCandidate(std::move(jsep_candidate),
                                [&operation_completed](RTCError result) {
                                  EXPECT_TRUE(result.ok());
                                  operation_completed = true;
                                });
  EXPECT_TRUE_WAIT(operation_completed, kWaitTimeout);

  auto candidates = callee->GetIceCandidatesFromRemoteDescription();
  ASSERT_EQ(1u, candidates.size());
  EXPECT_PRED_FORMAT2(AssertCandidatesEqual, candidate,
                      candidates[0]->candidate());
}

TEST_P(PeerConnectionIceTest,
       AsyncAddIceCandidateCompletesImmediatelyIfNoPendingOperation) {
  auto candidate = CreateLocalUdpCandidate(SocketAddress("1.1.1.1", 1111));

  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));

  auto jsep_candidate =
      callee->CreateJsepCandidateForFirstTransport(&candidate);
  bool operation_completed = false;
  callee->pc()->AddIceCandidate(
      std::move(jsep_candidate),
      [&operation_completed](RTCError result) { operation_completed = true; });
  EXPECT_TRUE(operation_completed);
}

TEST_P(PeerConnectionIceTest,
       AsyncAddIceCandidateCompletesWhenPendingOperationCompletes) {
  auto candidate = CreateLocalUdpCandidate(SocketAddress("1.1.1.1", 1111));

  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));

  // Chain an operation that will block AddIceCandidate() from executing.
  auto answer_observer =
      rtc::make_ref_counted<MockCreateSessionDescriptionObserver>();
  callee->pc()->CreateAnswer(answer_observer.get(), RTCOfferAnswerOptions());

  auto jsep_candidate =
      callee->CreateJsepCandidateForFirstTransport(&candidate);
  bool operation_completed = false;
  callee->pc()->AddIceCandidate(
      std::move(jsep_candidate),
      [&operation_completed](RTCError result) { operation_completed = true; });
  // The operation will not be able to complete until we EXPECT_TRUE_WAIT()
  // allowing CreateAnswer() to complete.
  EXPECT_FALSE(operation_completed);
  EXPECT_TRUE_WAIT(answer_observer->called(), kWaitTimeout);
  // As soon as it does, AddIceCandidate() will execute without delay, so it
  // must also have completed.
  EXPECT_TRUE(operation_completed);
}

TEST_P(PeerConnectionIceTest,
       AsyncAddIceCandidateFailsBeforeSetRemoteDescription) {
  auto candidate = CreateLocalUdpCandidate(SocketAddress("1.1.1.1", 1111));

  auto caller = CreatePeerConnectionWithAudioVideo();
  std::unique_ptr<IceCandidateInterface> jsep_candidate =
      CreateIceCandidate(cricket::CN_AUDIO, 0, candidate);

  bool operation_completed = false;
  caller->pc()->AddIceCandidate(
      std::move(jsep_candidate), [&operation_completed](RTCError result) {
        EXPECT_FALSE(result.ok());
        EXPECT_EQ(result.message(),
                  std::string("The remote description was null"));
        operation_completed = true;
      });
  EXPECT_TRUE_WAIT(operation_completed, kWaitTimeout);
}

TEST_P(PeerConnectionIceTest,
       AsyncAddIceCandidateFailsIfPeerConnectionDestroyed) {
  auto candidate = CreateLocalUdpCandidate(SocketAddress("1.1.1.1", 1111));

  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));

  // Chain an operation that will block AddIceCandidate() from executing.
  auto answer_observer =
      rtc::make_ref_counted<MockCreateSessionDescriptionObserver>();
  callee->pc()->CreateAnswer(answer_observer.get(), RTCOfferAnswerOptions());

  auto jsep_candidate =
      callee->CreateJsepCandidateForFirstTransport(&candidate);
  bool operation_completed = false;
  callee->pc()->AddIceCandidate(
      std::move(jsep_candidate), [&operation_completed](RTCError result) {
        EXPECT_FALSE(result.ok());
        EXPECT_EQ(
            result.message(),
            std::string(
                "AddIceCandidate failed because the session was shut down"));
        operation_completed = true;
      });
  // The operation will not be able to run until EXPECT_TRUE_WAIT(), giving us
  // time to remove all references to the PeerConnection.
  EXPECT_FALSE(operation_completed);
  // This should delete the callee PC.
  callee = nullptr;
  EXPECT_TRUE_WAIT(operation_completed, kWaitTimeout);
}

TEST_P(PeerConnectionIceTest, LocalDescriptionUpdatedWhenContinualGathering) {
  const SocketAddress kLocalAddress("1.1.1.1", 0);

  RTCConfiguration config;
  config.sdp_semantics = SdpSemantics::kUnifiedPlan;
  config.continual_gathering_policy =
      PeerConnectionInterface::GATHER_CONTINUALLY;
  auto caller = CreatePeerConnectionWithAudioVideo(config);
  caller->network()->AddInterface(kLocalAddress);

  // Start ICE candidate gathering by setting the local offer.
  ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));

  // Since we're using continual gathering, we won't get "gathering done".
  EXPECT_TRUE_WAIT(
      caller->pc()->local_description()->candidates(0)->count() > 0,
      kIceCandidatesTimeout);
}

// Test that when continual gathering is enabled, and a network interface goes
// down, the candidate is signaled as removed and removed from the local
// description.
TEST_P(PeerConnectionIceTest,
       LocalCandidatesRemovedWhenNetworkDownIfGatheringContinually) {
  const SocketAddress kLocalAddress("1.1.1.1", 0);

  RTCConfiguration config;
  config.sdp_semantics = SdpSemantics::kUnifiedPlan;
  config.continual_gathering_policy =
      PeerConnectionInterface::GATHER_CONTINUALLY;
  auto caller = CreatePeerConnectionWithAudioVideo(config);
  caller->network()->AddInterface(kLocalAddress);

  // Start ICE candidate gathering by setting the local offer.
  ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));

  EXPECT_TRUE_WAIT(
      caller->pc()->local_description()->candidates(0)->count() > 0,
      kIceCandidatesTimeout);

  // Remove the only network interface, causing the PeerConnection to signal
  // the removal of all candidates derived from this interface.
  caller->network()->RemoveInterface(kLocalAddress);

  EXPECT_EQ_WAIT(0u, caller->pc()->local_description()->candidates(0)->count(),
                 kIceCandidatesTimeout);
  EXPECT_LT(0, caller->observer()->num_candidates_removed_);
}

TEST_P(PeerConnectionIceTest,
       LocalCandidatesNotRemovedWhenNetworkDownIfGatheringOnce) {
  const SocketAddress kLocalAddress("1.1.1.1", 0);

  RTCConfiguration config;
  config.sdp_semantics = SdpSemantics::kUnifiedPlan;
  config.continual_gathering_policy = PeerConnectionInterface::GATHER_ONCE;
  auto caller = CreatePeerConnectionWithAudioVideo(config);
  caller->network()->AddInterface(kLocalAddress);

  // Start ICE candidate gathering by setting the local offer.
  ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));

  EXPECT_TRUE_WAIT(caller->IsIceGatheringDone(), kIceCandidatesTimeout);

  caller->network()->RemoveInterface(kLocalAddress);

  // Verify that the local candidates are not removed;
  rtc::Thread::Current()->ProcessMessages(1000);
  EXPECT_EQ(0, caller->observer()->num_candidates_removed_);
}

// The following group tests that when an offer includes a new ufrag or pwd
// (indicating an ICE restart) the old candidates are removed and new candidates
// added to the remote description.

TEST_P(PeerConnectionIceTest, IceRestartOfferClearsExistingCandidate) {
  const SocketAddress kCallerAddress("1.1.1.1", 1111);

  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  auto offer = caller->CreateOfferAndSetAsLocal();
  cricket::Candidate candidate = CreateLocalUdpCandidate(kCallerAddress);
  AddCandidateToFirstTransport(&candidate, offer.get());

  ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));

  RTCOfferAnswerOptions options;
  options.ice_restart = true;
  ASSERT_TRUE(
      callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal(options)));

  EXPECT_EQ(0u, callee->GetIceCandidatesFromRemoteDescription().size());
}
TEST_P(PeerConnectionIceTest,
       IceRestartOfferCandidateReplacesExistingCandidate) {
  const SocketAddress kFirstCallerAddress("1.1.1.1", 1111);
  const SocketAddress kRestartedCallerAddress("2.2.2.2", 2222);

  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  auto offer = caller->CreateOfferAndSetAsLocal();
  cricket::Candidate old_candidate =
      CreateLocalUdpCandidate(kFirstCallerAddress);
  AddCandidateToFirstTransport(&old_candidate, offer.get());

  ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));

  RTCOfferAnswerOptions options;
  options.ice_restart = true;
  auto restart_offer = caller->CreateOfferAndSetAsLocal(options);
  cricket::Candidate new_candidate =
      CreateLocalUdpCandidate(kRestartedCallerAddress);
  AddCandidateToFirstTransport(&new_candidate, restart_offer.get());

  ASSERT_TRUE(callee->SetRemoteDescription(std::move(restart_offer)));

  auto remote_candidates = callee->GetIceCandidatesFromRemoteDescription();
  ASSERT_EQ(1u, remote_candidates.size());
  EXPECT_PRED_FORMAT2(AssertCandidatesEqual, new_candidate,
                      remote_candidates[0]->candidate());
}

// Test that if there is not an ICE restart (i.e., nothing changes), then the
// answer to a later offer should have the same ufrag/pwd as the first answer.
TEST_P(PeerConnectionIceTest, LaterAnswerHasSameIceCredentialsIfNoIceRestart) {
  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
  ASSERT_TRUE(
      caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));

  // Re-offer.
  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));

  auto answer = callee->CreateAnswer();
  auto* answer_transport_desc = GetFirstTransportDescription(answer.get());
  auto* local_transport_desc =
      GetFirstTransportDescription(callee->pc()->local_description());

  EXPECT_EQ(answer_transport_desc->ice_ufrag, local_transport_desc->ice_ufrag);
  EXPECT_EQ(answer_transport_desc->ice_pwd, local_transport_desc->ice_pwd);
}

TEST_P(PeerConnectionIceTest, RestartIceGeneratesNewCredentials) {
  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
  auto initial_ice_credentials =
      GetIceCredentials(caller->pc()->local_description());
  caller->pc()->RestartIce();
  ASSERT_TRUE(caller->CreateOfferAndSetAsLocal());
  auto restarted_ice_credentials =
      GetIceCredentials(caller->pc()->local_description());
  EXPECT_NE(initial_ice_credentials, restarted_ice_credentials);
}

TEST_P(PeerConnectionIceTest,
       RestartIceWhileLocalOfferIsPendingGeneratesNewCredentialsInNextOffer) {
  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
  auto initial_ice_credentials =
      GetIceCredentials(caller->pc()->local_description());
  // ICE restart becomes needed while an O/A is pending and `caller` is the
  // offerer.
  caller->pc()->RestartIce();
  ASSERT_TRUE(
      caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
  ASSERT_TRUE(caller->CreateOfferAndSetAsLocal());
  auto restarted_ice_credentials =
      GetIceCredentials(caller->pc()->local_description());
  EXPECT_NE(initial_ice_credentials, restarted_ice_credentials);
}

TEST_P(PeerConnectionIceTest,
       RestartIceWhileRemoteOfferIsPendingGeneratesNewCredentialsInNextOffer) {
  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
  auto initial_ice_credentials =
      GetIceCredentials(caller->pc()->local_description());
  ASSERT_TRUE(caller->SetRemoteDescription(callee->CreateOfferAndSetAsLocal()));
  // ICE restart becomes needed while an O/A is pending and `caller` is the
  // answerer.
  caller->pc()->RestartIce();
  ASSERT_TRUE(
      callee->SetRemoteDescription(caller->CreateAnswerAndSetAsLocal()));
  ASSERT_TRUE(caller->CreateOfferAndSetAsLocal());
  auto restarted_ice_credentials =
      GetIceCredentials(caller->pc()->local_description());
  EXPECT_NE(initial_ice_credentials, restarted_ice_credentials);
}

TEST_P(PeerConnectionIceTest, RestartIceTriggeredByRemoteSide) {
  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
  auto initial_ice_credentials =
      GetIceCredentials(caller->pc()->local_description());

  // Remote restart and O/A exchange with `caller` as the answerer should
  // restart ICE locally as well.
  callee->pc()->RestartIce();
  ASSERT_TRUE(callee->ExchangeOfferAnswerWith(caller.get()));

  auto restarted_ice_credentials =
      GetIceCredentials(caller->pc()->local_description());
  EXPECT_NE(initial_ice_credentials, restarted_ice_credentials);
}

TEST_P(PeerConnectionIceTest, RestartIceCausesNegotiationNeeded) {
  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
  caller->observer()->clear_legacy_renegotiation_needed();
  caller->observer()->clear_latest_negotiation_needed_event();
  caller->pc()->RestartIce();
  EXPECT_TRUE(caller->observer()->legacy_renegotiation_needed());
  EXPECT_TRUE(caller->observer()->has_negotiation_needed_event());
}

// In Unified Plan, "onnegotiationneeded" is spec-compliant, including not
// firing multipe times in a row, or firing when returning to the stable
// signaling state if negotiation is still needed. In Plan B it fires any time
// something changes. As such, some tests are SdpSemantics-specific.
class PeerConnectionIceTestUnifiedPlan : public PeerConnectionIceBaseTest {
 protected:
  PeerConnectionIceTestUnifiedPlan()
      : PeerConnectionIceBaseTest(SdpSemantics::kUnifiedPlan) {}
};

TEST_F(PeerConnectionIceTestUnifiedPlan,
       RestartIceWhileLocalOfferIsPendingCausesNegotiationNeededWhenStable) {
  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
  // ICE restart becomes needed while an O/A is pending and `caller` is the
  // offerer.
  caller->observer()->clear_legacy_renegotiation_needed();
  caller->observer()->clear_latest_negotiation_needed_event();
  caller->pc()->RestartIce();
  // In Unified Plan, the event should not fire until we are back in the stable
  // signaling state.
  EXPECT_FALSE(caller->observer()->legacy_renegotiation_needed());
  EXPECT_FALSE(caller->observer()->has_negotiation_needed_event());
  ASSERT_TRUE(
      caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
  EXPECT_TRUE(caller->observer()->legacy_renegotiation_needed());
  EXPECT_TRUE(caller->observer()->has_negotiation_needed_event());
}

TEST_F(PeerConnectionIceTestUnifiedPlan,
       RestartIceWhileRemoteOfferIsPendingCausesNegotiationNeededWhenStable) {
  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  // Establish initial credentials as the caller.
  ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
  ASSERT_TRUE(caller->SetRemoteDescription(callee->CreateOfferAndSetAsLocal()));
  // ICE restart becomes needed while an O/A is pending and `caller` is the
  // answerer.
  caller->observer()->clear_legacy_renegotiation_needed();
  caller->observer()->clear_latest_negotiation_needed_event();
  caller->pc()->RestartIce();
  // In Unified Plan, the event should not fire until we are back in the stable
  // signaling state.
  EXPECT_FALSE(caller->observer()->legacy_renegotiation_needed());
  EXPECT_FALSE(caller->observer()->has_negotiation_needed_event());
  ASSERT_TRUE(
      callee->SetRemoteDescription(caller->CreateAnswerAndSetAsLocal()));
  EXPECT_TRUE(caller->observer()->legacy_renegotiation_needed());
  EXPECT_TRUE(caller->observer()->has_negotiation_needed_event());
}

TEST_F(PeerConnectionIceTestUnifiedPlan,
       RestartIceTriggeredByRemoteSideCauseNegotiationNotNeeded) {
  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
  // Local restart.
  caller->pc()->RestartIce();
  caller->observer()->clear_legacy_renegotiation_needed();
  caller->observer()->clear_latest_negotiation_needed_event();
  // Remote restart and O/A exchange with `caller` as the answerer should
  // restart ICE locally as well.
  callee->pc()->RestartIce();
  ASSERT_TRUE(callee->ExchangeOfferAnswerWith(caller.get()));
  // Having restarted ICE by the remote offer, we do not need to renegotiate ICE
  // credentials when back in the stable signaling state.
  EXPECT_FALSE(caller->observer()->legacy_renegotiation_needed());
  EXPECT_FALSE(caller->observer()->has_negotiation_needed_event());
}

TEST_F(PeerConnectionIceTestUnifiedPlan,
       RestartIceTwiceDoesNotFireNegotiationNeededTwice) {
  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
  caller->pc()->RestartIce();
  EXPECT_TRUE(caller->observer()->legacy_renegotiation_needed());
  EXPECT_TRUE(caller->observer()->has_negotiation_needed_event());
  caller->observer()->clear_legacy_renegotiation_needed();
  caller->observer()->clear_latest_negotiation_needed_event();
  caller->pc()->RestartIce();
  EXPECT_FALSE(caller->observer()->legacy_renegotiation_needed());
  EXPECT_FALSE(caller->observer()->has_negotiation_needed_event());
}

// In Plan B, "onnegotiationneeded" is not spec-compliant, firing based on if
// something changed rather than if negotiation is needed. In Unified Plan it
// fires according to spec. As such, some tests are SdpSemantics-specific.
class PeerConnectionIceTestPlanB : public PeerConnectionIceBaseTest {
 protected:
  PeerConnectionIceTestPlanB()
      : PeerConnectionIceBaseTest(SdpSemantics::kPlanB_DEPRECATED) {}
};

TEST_F(PeerConnectionIceTestPlanB,
       RestartIceWhileOfferIsPendingCausesNegotiationNeededImmediately) {
  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
  caller->observer()->clear_legacy_renegotiation_needed();
  caller->observer()->clear_latest_negotiation_needed_event();
  caller->pc()->RestartIce();
  EXPECT_TRUE(caller->observer()->legacy_renegotiation_needed());
  EXPECT_TRUE(caller->observer()->has_negotiation_needed_event());
  caller->observer()->clear_legacy_renegotiation_needed();
  caller->observer()->clear_latest_negotiation_needed_event();
  ASSERT_TRUE(
      caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
  // In Plan B, the event fired early so we don't expect it to fire now. This is
  // not spec-compliant but follows the pattern of existing Plan B behavior.
  EXPECT_FALSE(caller->observer()->legacy_renegotiation_needed());
  EXPECT_FALSE(caller->observer()->has_negotiation_needed_event());
}

TEST_F(PeerConnectionIceTestPlanB,
       RestartIceTwiceDoesFireNegotiationNeededTwice) {
  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
  caller->observer()->clear_legacy_renegotiation_needed();
  caller->observer()->clear_latest_negotiation_needed_event();
  caller->pc()->RestartIce();
  EXPECT_TRUE(caller->observer()->legacy_renegotiation_needed());
  EXPECT_TRUE(caller->observer()->has_negotiation_needed_event());
  caller->observer()->clear_legacy_renegotiation_needed();
  caller->observer()->clear_latest_negotiation_needed_event();
  caller->pc()->RestartIce();
  // In Plan B, the event fires every time something changed, even if we have
  // already fired the event. This is not spec-compliant but follows the same
  // pattern of existing Plan B behavior.
  EXPECT_TRUE(caller->observer()->legacy_renegotiation_needed());
  EXPECT_TRUE(caller->observer()->has_negotiation_needed_event());
}

// The following parameterized test verifies that if an offer is sent with a
// modified ICE ufrag and/or ICE pwd, then the answer should identify that the
// other side has initiated an ICE restart and generate a new ufrag and pwd.
// RFC 5245 says: "If the offer contained a change in the a=ice-ufrag or
// a=ice-pwd attributes compared to the previous SDP from the peer, it
// indicates that ICE is restarting for this media stream."

class PeerConnectionIceUfragPwdAnswerTest
    : public PeerConnectionIceBaseTest,
      public ::testing::WithParamInterface<
          std::tuple<SdpSemantics, std::tuple<bool, bool>>> {
 protected:
  PeerConnectionIceUfragPwdAnswerTest()
      : PeerConnectionIceBaseTest(std::get<0>(GetParam())) {
    auto param = std::get<1>(GetParam());
    offer_new_ufrag_ = std::get<0>(param);
    offer_new_pwd_ = std::get<1>(param);
  }

  bool offer_new_ufrag_;
  bool offer_new_pwd_;
};

TEST_P(PeerConnectionIceUfragPwdAnswerTest, TestIncludedInAnswer) {
  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
  ASSERT_TRUE(
      caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));

  auto offer = caller->CreateOffer();
  auto* offer_transport_desc = GetFirstTransportDescription(offer.get());
  if (offer_new_ufrag_) {
    offer_transport_desc->ice_ufrag += "+new";
  }
  if (offer_new_pwd_) {
    offer_transport_desc->ice_pwd += "+new";
  }

  ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));

  auto answer = callee->CreateAnswer();
  auto* answer_transport_desc = GetFirstTransportDescription(answer.get());
  auto* local_transport_desc =
      GetFirstTransportDescription(callee->pc()->local_description());

  EXPECT_NE(answer_transport_desc->ice_ufrag, local_transport_desc->ice_ufrag);
  EXPECT_NE(answer_transport_desc->ice_pwd, local_transport_desc->ice_pwd);
}

INSTANTIATE_TEST_SUITE_P(
    PeerConnectionIceTest,
    PeerConnectionIceUfragPwdAnswerTest,
    Combine(Values(SdpSemantics::kPlanB_DEPRECATED, SdpSemantics::kUnifiedPlan),
            Values(std::make_pair(true, true),      // Both changed.
                   std::make_pair(true, false),     // Only ufrag changed.
                   std::make_pair(false, true))));  // Only pwd changed.

// Test that if an ICE restart is offered on one media section, then the answer
// will only change ICE ufrag/pwd for that section and keep the other sections
// the same.
// Note that this only works if we have disabled BUNDLE, otherwise all media
// sections will share the same transport.
TEST_P(PeerConnectionIceTest,
       CreateAnswerHasNewUfragPwdForOnlyMediaSectionWhichRestarted) {
  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
  ASSERT_TRUE(
      caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));

  RTCOfferAnswerOptions disable_bundle_options;
  disable_bundle_options.use_rtp_mux = false;

  auto offer = caller->CreateOffer(disable_bundle_options);

  // Signal ICE restart on the first media section.
  auto* offer_transport_desc = GetFirstTransportDescription(offer.get());
  offer_transport_desc->ice_ufrag += "+new";
  offer_transport_desc->ice_pwd += "+new";

  ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));

  auto answer = callee->CreateAnswer(disable_bundle_options);
  const auto& answer_transports = answer->description()->transport_infos();
  const auto& local_transports =
      callee->pc()->local_description()->description()->transport_infos();

  EXPECT_NE(answer_transports[0].description.ice_ufrag,
            local_transports[0].description.ice_ufrag);
  EXPECT_NE(answer_transports[0].description.ice_pwd,
            local_transports[0].description.ice_pwd);
  EXPECT_EQ(answer_transports[1].description.ice_ufrag,
            local_transports[1].description.ice_ufrag);
  EXPECT_EQ(answer_transports[1].description.ice_pwd,
            local_transports[1].description.ice_pwd);
}

// Test that when the initial offerer (caller) uses the lite implementation of
// ICE and the callee uses the full implementation, the caller takes the
// CONTROLLED role and the callee takes the CONTROLLING role. This is specified
// in RFC5245 Section 5.1.1.
TEST_P(PeerConnectionIceTest,
       OfferFromLiteIceControlledAndAnswerFromFullIceControlling) {
  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  auto offer = caller->CreateOffer();
  SetIceMode(offer.get(), cricket::IceMode::ICEMODE_LITE);
  ASSERT_TRUE(
      caller->SetLocalDescription(CloneSessionDescription(offer.get())));
  ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));

  auto answer = callee->CreateAnswer();
  SetIceMode(answer.get(), cricket::IceMode::ICEMODE_FULL);
  ASSERT_TRUE(
      callee->SetLocalDescription(CloneSessionDescription(answer.get())));
  ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer)));

  EXPECT_EQ(cricket::ICEROLE_CONTROLLED, GetIceRole(caller));
  EXPECT_EQ(cricket::ICEROLE_CONTROLLING, GetIceRole(callee));
}

// Test that when the caller and the callee both use the lite implementation of
// ICE, the initial offerer (caller) takes the CONTROLLING role and the callee
// takes the CONTROLLED role. This is specified in RFC5245 Section 5.1.1.
TEST_P(PeerConnectionIceTest,
       OfferFromLiteIceControllingAndAnswerFromLiteIceControlled) {
  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  auto offer = caller->CreateOffer();
  SetIceMode(offer.get(), cricket::IceMode::ICEMODE_LITE);
  ASSERT_TRUE(
      caller->SetLocalDescription(CloneSessionDescription(offer.get())));
  ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));

  auto answer = callee->CreateAnswer();
  SetIceMode(answer.get(), cricket::IceMode::ICEMODE_LITE);
  ASSERT_TRUE(
      callee->SetLocalDescription(CloneSessionDescription(answer.get())));
  ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer)));

  EXPECT_EQ(cricket::ICEROLE_CONTROLLING, GetIceRole(caller));
  EXPECT_EQ(cricket::ICEROLE_CONTROLLED, GetIceRole(callee));
}

INSTANTIATE_TEST_SUITE_P(PeerConnectionIceTest,
                         PeerConnectionIceTest,
                         Values(SdpSemantics::kPlanB_DEPRECATED,
                                SdpSemantics::kUnifiedPlan));

class PeerConnectionIceConfigTest : public ::testing::Test {
 public:
  PeerConnectionIceConfigTest()
      : socket_server_(rtc::CreateDefaultSocketServer()),
        main_thread_(socket_server_.get()) {}

 protected:
  void SetUp() override {
    pc_factory_ = CreatePeerConnectionFactory(
        rtc::Thread::Current(), rtc::Thread::Current(), rtc::Thread::Current(),
        FakeAudioCaptureModule::Create(), CreateBuiltinAudioEncoderFactory(),
        CreateBuiltinAudioDecoderFactory(), CreateBuiltinVideoEncoderFactory(),
        CreateBuiltinVideoDecoderFactory(), nullptr /* audio_mixer */,
        nullptr /* audio_processing */);
  }
  void CreatePeerConnection(const RTCConfiguration& config) {
    packet_socket_factory_.reset(
        new rtc::BasicPacketSocketFactory(socket_server_.get()));
    std::unique_ptr<cricket::FakePortAllocator> port_allocator(
        new cricket::FakePortAllocator(rtc::Thread::Current(),
                                       packet_socket_factory_.get(),
                                       &field_trials_));
    port_allocator_ = port_allocator.get();
    port_allocator_->SetIceTiebreaker(kTiebreakerDefault);
    PeerConnectionDependencies pc_dependencies(&observer_);
    pc_dependencies.allocator = std::move(port_allocator);
    auto result = pc_factory_->CreatePeerConnectionOrError(
        config, std::move(pc_dependencies));
    EXPECT_TRUE(result.ok());
    pc_ = result.MoveValue();
  }

  webrtc::test::ScopedKeyValueConfig field_trials_;
  std::unique_ptr<rtc::SocketServer> socket_server_;
  rtc::AutoSocketServerThread main_thread_;
  rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_ = nullptr;
  rtc::scoped_refptr<PeerConnectionInterface> pc_ = nullptr;
  std::unique_ptr<rtc::PacketSocketFactory> packet_socket_factory_;
  cricket::FakePortAllocator* port_allocator_ = nullptr;

  MockPeerConnectionObserver observer_;
};

TEST_F(PeerConnectionIceConfigTest, SetStunCandidateKeepaliveInterval) {
  RTCConfiguration config;
  config.sdp_semantics = SdpSemantics::kUnifiedPlan;
  config.stun_candidate_keepalive_interval = 123;
  config.ice_candidate_pool_size = 1;
  CreatePeerConnection(config);
  ASSERT_NE(port_allocator_, nullptr);
  absl::optional<int> actual_stun_keepalive_interval =
      port_allocator_->stun_candidate_keepalive_interval();
  EXPECT_EQ(actual_stun_keepalive_interval.value_or(-1), 123);
  config.stun_candidate_keepalive_interval = 321;
  ASSERT_TRUE(pc_->SetConfiguration(config).ok());
  actual_stun_keepalive_interval =
      port_allocator_->stun_candidate_keepalive_interval();
  EXPECT_EQ(actual_stun_keepalive_interval.value_or(-1), 321);
}

TEST_F(PeerConnectionIceConfigTest, SetStableWritableConnectionInterval) {
  RTCConfiguration config;
  config.sdp_semantics = SdpSemantics::kUnifiedPlan;
  config.stable_writable_connection_ping_interval_ms = 3500;
  CreatePeerConnection(config);
  EXPECT_TRUE(pc_->SetConfiguration(config).ok());
  EXPECT_EQ(pc_->GetConfiguration().stable_writable_connection_ping_interval_ms,
            config.stable_writable_connection_ping_interval_ms);
}

TEST_F(PeerConnectionIceConfigTest,
       SetStableWritableConnectionInterval_FailsValidation) {
  RTCConfiguration config;
  config.sdp_semantics = SdpSemantics::kUnifiedPlan;
  CreatePeerConnection(config);
  ASSERT_TRUE(pc_->SetConfiguration(config).ok());
  config.stable_writable_connection_ping_interval_ms = 5000;
  config.ice_check_interval_strong_connectivity = 7500;
  EXPECT_FALSE(pc_->SetConfiguration(config).ok());
}

TEST_F(PeerConnectionIceConfigTest,
       SetStableWritableConnectionInterval_DefaultValue_FailsValidation) {
  RTCConfiguration config;
  config.sdp_semantics = SdpSemantics::kUnifiedPlan;
  CreatePeerConnection(config);
  ASSERT_TRUE(pc_->SetConfiguration(config).ok());
  config.ice_check_interval_strong_connectivity = 2500;
  EXPECT_TRUE(pc_->SetConfiguration(config).ok());
  config.ice_check_interval_strong_connectivity = 2501;
  EXPECT_FALSE(pc_->SetConfiguration(config).ok());
}

TEST_P(PeerConnectionIceTest, IceCredentialsCreateOffer) {
  RTCConfiguration config;
  config.sdp_semantics = SdpSemantics::kUnifiedPlan;
  config.ice_candidate_pool_size = 1;
  auto pc = CreatePeerConnectionWithAudioVideo(config);
  ASSERT_NE(pc->port_allocator_, nullptr);
  auto offer = pc->CreateOffer();
  auto credentials = pc->port_allocator_->GetPooledIceCredentials();
  ASSERT_EQ(1u, credentials.size());

  auto* desc = offer->description();
  for (const auto& content : desc->contents()) {
    auto* transport_info = desc->GetTransportInfoByName(content.name);
    EXPECT_EQ(transport_info->description.ice_ufrag, credentials[0].ufrag);
    EXPECT_EQ(transport_info->description.ice_pwd, credentials[0].pwd);
  }
}

TEST_P(PeerConnectionIceTest, IceCredentialsCreateAnswer) {
  RTCConfiguration config;
  config.sdp_semantics = SdpSemantics::kUnifiedPlan;
  config.ice_candidate_pool_size = 1;
  auto pc = CreatePeerConnectionWithAudioVideo(config);
  ASSERT_NE(pc->port_allocator_, nullptr);
  auto offer = pc->CreateOffer();
  ASSERT_TRUE(pc->SetRemoteDescription(std::move(offer)));
  auto answer = pc->CreateAnswer();

  auto credentials = pc->port_allocator_->GetPooledIceCredentials();
  ASSERT_EQ(1u, credentials.size());

  auto* desc = answer->description();
  for (const auto& content : desc->contents()) {
    auto* transport_info = desc->GetTransportInfoByName(content.name);
    EXPECT_EQ(transport_info->description.ice_ufrag, credentials[0].ufrag);
    EXPECT_EQ(transport_info->description.ice_pwd, credentials[0].pwd);
  }
}

// Regression test for https://bugs.chromium.org/p/webrtc/issues/detail?id=4728
TEST_P(PeerConnectionIceTest, CloseDoesNotTransitionGatheringStateToComplete) {
  auto pc = CreatePeerConnectionWithAudioVideo();
  pc->pc()->Close();
  EXPECT_FALSE(pc->IsIceGatheringDone());
  EXPECT_EQ(PeerConnectionInterface::kIceGatheringNew,
            pc->pc()->ice_gathering_state());
}

TEST_P(PeerConnectionIceTest, PrefersMidOverMLineIndex) {
  const SocketAddress kCalleeAddress("1.1.1.1", 1111);

  auto caller = CreatePeerConnectionWithAudioVideo();
  auto callee = CreatePeerConnectionWithAudioVideo();

  ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
  ASSERT_TRUE(
      caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));

  // `candidate.transport_name()` is empty.
  cricket::Candidate candidate = CreateLocalUdpCandidate(kCalleeAddress);
  auto* audio_content = cricket::GetFirstAudioContent(
      caller->pc()->local_description()->description());
  std::unique_ptr<IceCandidateInterface> ice_candidate =
      CreateIceCandidate(audio_content->name, 65535, candidate);
  EXPECT_TRUE(caller->pc()->AddIceCandidate(ice_candidate.get()));
  EXPECT_TRUE(caller->pc()->RemoveIceCandidates({candidate}));
}

}  // namespace webrtc
