summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/pc/peer_connection_ice_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/pc/peer_connection_ice_unittest.cc')
-rw-r--r--third_party/libwebrtc/pc/peer_connection_ice_unittest.cc1570
1 files changed, 1570 insertions, 0 deletions
diff --git a/third_party/libwebrtc/pc/peer_connection_ice_unittest.cc b/third_party/libwebrtc/pc/peer_connection_ice_unittest.cc
new file mode 100644
index 0000000000..fc0448bcef
--- /dev/null
+++ b/third_party/libwebrtc/pc/peer_connection_ice_unittest.cc
@@ -0,0 +1,1570 @@
+/*
+ * 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