You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1417 lines
57 KiB
1417 lines
57 KiB
/*
|
|
* Copyright 2017 The WebRTC project authors. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*/
|
|
|
|
#include <memory>
|
|
|
|
#include "p2p/base/fake_port_allocator.h"
|
|
#include "p2p/base/test_stun_server.h"
|
|
#include "p2p/client/basic_port_allocator.h"
|
|
#include "pc/media_session.h"
|
|
#include "pc/peer_connection.h"
|
|
#include "pc/peer_connection_wrapper.h"
|
|
#include "pc/sdp_utils.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/peer_connection_proxy.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/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;
|
|
|
|
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);
|
|
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();
|
|
auto pc = pc_factory_->CreatePeerConnection(
|
|
modified_config, std::move(port_allocator), nullptr, observer.get());
|
|
if (!pc) {
|
|
return nullptr;
|
|
}
|
|
|
|
observer->SetPeerConnectionInterface(pc.get());
|
|
auto wrapper = std::make_unique<PeerConnectionWrapperForIceTest>(
|
|
pc_factory_, pc, 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()->content_name());
|
|
return dtls_transport->ice_transport()->internal()->GetIceRole();
|
|
}
|
|
}
|
|
RTC_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());
|
|
}
|
|
|
|
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.
|
|
rtc::scoped_refptr<MockCreateSessionDescriptionObserver> answer_observer(
|
|
new rtc::RefCountedObject<MockCreateSessionDescriptionObserver>());
|
|
callee->pc()->CreateAnswer(answer_observer, 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("Error processing ICE candidate"));
|
|
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.
|
|
rtc::scoped_refptr<MockCreateSessionDescriptionObserver> answer_observer(
|
|
new rtc::RefCountedObject<MockCreateSessionDescriptionObserver>());
|
|
callee->pc()->CreateAnswer(answer_observer, 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.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.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.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_negotiation_needed();
|
|
caller->pc()->RestartIce();
|
|
EXPECT_TRUE(caller->observer()->negotiation_needed());
|
|
}
|
|
|
|
// 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_negotiation_needed();
|
|
caller->pc()->RestartIce();
|
|
// In Unified Plan, the event should not fire until we are back in the stable
|
|
// signaling state.
|
|
EXPECT_FALSE(caller->observer()->negotiation_needed());
|
|
ASSERT_TRUE(
|
|
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
|
|
EXPECT_TRUE(caller->observer()->negotiation_needed());
|
|
}
|
|
|
|
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_negotiation_needed();
|
|
caller->pc()->RestartIce();
|
|
// In Unified Plan, the event should not fire until we are back in the stable
|
|
// signaling state.
|
|
EXPECT_FALSE(caller->observer()->negotiation_needed());
|
|
ASSERT_TRUE(
|
|
callee->SetRemoteDescription(caller->CreateAnswerAndSetAsLocal()));
|
|
EXPECT_TRUE(caller->observer()->negotiation_needed());
|
|
}
|
|
|
|
TEST_F(PeerConnectionIceTestUnifiedPlan,
|
|
RestartIceTriggeredByRemoteSideCauseNegotiationNotNeeded) {
|
|
auto caller = CreatePeerConnectionWithAudioVideo();
|
|
auto callee = CreatePeerConnectionWithAudioVideo();
|
|
|
|
ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
|
|
// Local restart.
|
|
caller->pc()->RestartIce();
|
|
caller->observer()->clear_negotiation_needed();
|
|
// 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()->negotiation_needed());
|
|
}
|
|
|
|
TEST_F(PeerConnectionIceTestUnifiedPlan,
|
|
RestartIceTwiceDoesNotFireNegotiationNeededTwice) {
|
|
auto caller = CreatePeerConnectionWithAudioVideo();
|
|
auto callee = CreatePeerConnectionWithAudioVideo();
|
|
|
|
ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
|
|
caller->pc()->RestartIce();
|
|
EXPECT_TRUE(caller->observer()->negotiation_needed());
|
|
caller->observer()->clear_negotiation_needed();
|
|
caller->pc()->RestartIce();
|
|
EXPECT_FALSE(caller->observer()->negotiation_needed());
|
|
}
|
|
|
|
// 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) {}
|
|
};
|
|
|
|
TEST_F(PeerConnectionIceTestPlanB,
|
|
RestartIceWhileOfferIsPendingCausesNegotiationNeededImmediately) {
|
|
auto caller = CreatePeerConnectionWithAudioVideo();
|
|
auto callee = CreatePeerConnectionWithAudioVideo();
|
|
|
|
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
|
|
caller->observer()->clear_negotiation_needed();
|
|
caller->pc()->RestartIce();
|
|
EXPECT_TRUE(caller->observer()->negotiation_needed());
|
|
caller->observer()->clear_negotiation_needed();
|
|
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()->negotiation_needed());
|
|
}
|
|
|
|
TEST_F(PeerConnectionIceTestPlanB,
|
|
RestartIceTwiceDoesFireNegotiationNeededTwice) {
|
|
auto caller = CreatePeerConnectionWithAudioVideo();
|
|
auto callee = CreatePeerConnectionWithAudioVideo();
|
|
|
|
ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
|
|
caller->observer()->clear_negotiation_needed();
|
|
caller->pc()->RestartIce();
|
|
EXPECT_TRUE(caller->observer()->negotiation_needed());
|
|
caller->observer()->clear_negotiation_needed();
|
|
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()->negotiation_needed());
|
|
}
|
|
|
|
// 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, 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,
|
|
SdpSemantics::kUnifiedPlan));
|
|
|
|
class PeerConnectionIceConfigTest : public ::testing::Test {
|
|
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) {
|
|
std::unique_ptr<cricket::FakePortAllocator> port_allocator(
|
|
new cricket::FakePortAllocator(rtc::Thread::Current(), nullptr));
|
|
port_allocator_ = port_allocator.get();
|
|
rtc::scoped_refptr<PeerConnectionInterface> pc(
|
|
pc_factory_->CreatePeerConnection(config, std::move(port_allocator),
|
|
nullptr /* cert_generator */,
|
|
&observer_));
|
|
EXPECT_TRUE(pc.get());
|
|
pc_ = std::move(pc);
|
|
}
|
|
|
|
rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_ = nullptr;
|
|
rtc::scoped_refptr<PeerConnectionInterface> pc_ = nullptr;
|
|
cricket::FakePortAllocator* port_allocator_ = nullptr;
|
|
|
|
MockPeerConnectionObserver observer_;
|
|
};
|
|
|
|
TEST_F(PeerConnectionIceConfigTest, SetStunCandidateKeepaliveInterval) {
|
|
RTCConfiguration config;
|
|
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_P(PeerConnectionIceTest, IceCredentialsCreateOffer) {
|
|
RTCConfiguration config;
|
|
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.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());
|
|
}
|
|
|
|
} // namespace webrtc
|