blob: 72cbffbbc58817f71cb00f861940bb02384676ff [file] [log] [blame]
/*
* 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.
*/
// This file contains tests that check the PeerConnection's signaling state
// machine, as well as tests that check basic, media-agnostic aspects of SDP.
#include <memory>
#include <tuple>
#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/video_codecs/builtin_video_decoder_factory.h"
#include "api/video_codecs/builtin_video_encoder_factory.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 "pc/test/fake_audio_capture_module.h"
#include "pc/test/fake_rtc_certificate_generator.h"
#include "rtc_base/gunit.h"
#include "rtc_base/virtual_socket_server.h"
#include "test/gmock.h"
namespace webrtc {
using SignalingState = PeerConnectionInterface::SignalingState;
using RTCConfiguration = PeerConnectionInterface::RTCConfiguration;
using RTCOfferAnswerOptions = PeerConnectionInterface::RTCOfferAnswerOptions;
using ::testing::Bool;
using ::testing::Combine;
using ::testing::Values;
namespace {
const int64_t kWaitTimeout = 10000;
} // namespace
class PeerConnectionWrapperForSignalingTest : public PeerConnectionWrapper {
public:
using PeerConnectionWrapper::PeerConnectionWrapper;
bool initial_offerer() {
return GetInternalPeerConnection()->initial_offerer();
}
PeerConnection* GetInternalPeerConnection() {
auto* pci =
static_cast<PeerConnectionProxyWithInternal<PeerConnectionInterface>*>(
pc());
return static_cast<PeerConnection*>(pci->internal());
}
};
class ExecuteFunctionOnCreateSessionDescriptionObserver
: public CreateSessionDescriptionObserver {
public:
ExecuteFunctionOnCreateSessionDescriptionObserver(
std::function<void(SessionDescriptionInterface*)> function)
: function_(std::move(function)) {}
~ExecuteFunctionOnCreateSessionDescriptionObserver() override {
RTC_DCHECK(was_called_);
}
bool was_called() const { return was_called_; }
void OnSuccess(SessionDescriptionInterface* desc) override {
RTC_DCHECK(!was_called_);
was_called_ = true;
function_(desc);
}
void OnFailure(RTCError error) override { RTC_NOTREACHED(); }
private:
bool was_called_ = false;
std::function<void(SessionDescriptionInterface*)> function_;
};
class PeerConnectionSignalingBaseTest : public ::testing::Test {
protected:
typedef std::unique_ptr<PeerConnectionWrapperForSignalingTest> WrapperPtr;
explicit PeerConnectionSignalingBaseTest(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 observer = std::make_unique<MockPeerConnectionObserver>();
RTCConfiguration modified_config = config;
modified_config.sdp_semantics = sdp_semantics_;
auto pc = pc_factory_->CreatePeerConnection(modified_config, nullptr,
nullptr, observer.get());
if (!pc) {
return nullptr;
}
observer->SetPeerConnectionInterface(pc.get());
return std::make_unique<PeerConnectionWrapperForSignalingTest>(
pc_factory_, pc, std::move(observer));
}
// 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;
}
int NumberOfDtlsTransports(const WrapperPtr& pc_wrapper) {
std::set<DtlsTransportInterface*> transports;
auto transceivers = pc_wrapper->pc()->GetTransceivers();
for (auto& transceiver : transceivers) {
if (transceiver->sender()->dtls_transport()) {
EXPECT_TRUE(transceiver->receiver()->dtls_transport());
EXPECT_EQ(transceiver->sender()->dtls_transport().get(),
transceiver->receiver()->dtls_transport().get());
transports.insert(transceiver->sender()->dtls_transport().get());
} else {
// If one transceiver is missing, they all should be.
EXPECT_EQ(0UL, transports.size());
}
}
return transports.size();
}
bool HasDtlsTransport(const WrapperPtr& pc_wrapper) {
return NumberOfDtlsTransports(pc_wrapper) > 0;
}
std::unique_ptr<rtc::VirtualSocketServer> vss_;
rtc::AutoSocketServerThread main_;
rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_;
const SdpSemantics sdp_semantics_;
};
class PeerConnectionSignalingTest
: public PeerConnectionSignalingBaseTest,
public ::testing::WithParamInterface<SdpSemantics> {
protected:
PeerConnectionSignalingTest() : PeerConnectionSignalingBaseTest(GetParam()) {}
};
TEST_P(PeerConnectionSignalingTest, SetLocalOfferTwiceWorks) {
auto caller = CreatePeerConnection();
EXPECT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));
EXPECT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));
}
TEST_P(PeerConnectionSignalingTest, SetRemoteOfferTwiceWorks) {
auto caller = CreatePeerConnection();
auto callee = CreatePeerConnection();
EXPECT_TRUE(callee->SetRemoteDescription(caller->CreateOffer()));
EXPECT_TRUE(callee->SetRemoteDescription(caller->CreateOffer()));
}
TEST_P(PeerConnectionSignalingTest, FailToSetNullLocalDescription) {
auto caller = CreatePeerConnection();
std::string error;
ASSERT_FALSE(caller->SetLocalDescription(nullptr, &error));
EXPECT_EQ("SessionDescription is NULL.", error);
}
TEST_P(PeerConnectionSignalingTest, FailToSetNullRemoteDescription) {
auto caller = CreatePeerConnection();
std::string error;
ASSERT_FALSE(caller->SetRemoteDescription(nullptr, &error));
EXPECT_EQ("SessionDescription is NULL.", error);
}
// The following parameterized test verifies that calls to various signaling
// methods on PeerConnection will succeed/fail depending on what is the
// PeerConnection's signaling state. Note that the test tries many different
// forms of SignalingState::kClosed by arriving at a valid state then calling
// |Close()|. This is intended to catch cases where the PeerConnection signaling
// method ignores the closed flag but may work/not work because of the single
// state the PeerConnection was created in before it was closed.
class PeerConnectionSignalingStateTest
: public PeerConnectionSignalingBaseTest,
public ::testing::WithParamInterface<
std::tuple<SdpSemantics, SignalingState, bool>> {
protected:
PeerConnectionSignalingStateTest()
: PeerConnectionSignalingBaseTest(std::get<0>(GetParam())),
state_under_test_(std::make_tuple(std::get<1>(GetParam()),
std::get<2>(GetParam()))) {}
RTCConfiguration GetConfig() {
RTCConfiguration config;
config.certificates.push_back(
FakeRTCCertificateGenerator::GenerateCertificate());
return config;
}
WrapperPtr CreatePeerConnectionUnderTest() {
return CreatePeerConnectionInState(state_under_test_);
}
WrapperPtr CreatePeerConnectionInState(SignalingState state) {
return CreatePeerConnectionInState(std::make_tuple(state, false));
}
WrapperPtr CreatePeerConnectionInState(
std::tuple<SignalingState, bool> state_tuple) {
SignalingState state = std::get<0>(state_tuple);
bool closed = std::get<1>(state_tuple);
auto wrapper = CreatePeerConnectionWithAudioVideo(GetConfig());
switch (state) {
case SignalingState::kStable: {
break;
}
case SignalingState::kHaveLocalOffer: {
wrapper->SetLocalDescription(wrapper->CreateOffer());
break;
}
case SignalingState::kHaveLocalPrAnswer: {
auto caller = CreatePeerConnectionWithAudioVideo(GetConfig());
wrapper->SetRemoteDescription(caller->CreateOffer());
auto answer = wrapper->CreateAnswer();
wrapper->SetLocalDescription(
CloneSessionDescriptionAsType(answer.get(), SdpType::kPrAnswer));
break;
}
case SignalingState::kHaveRemoteOffer: {
auto caller = CreatePeerConnectionWithAudioVideo(GetConfig());
wrapper->SetRemoteDescription(caller->CreateOffer());
break;
}
case SignalingState::kHaveRemotePrAnswer: {
auto callee = CreatePeerConnectionWithAudioVideo(GetConfig());
callee->SetRemoteDescription(wrapper->CreateOfferAndSetAsLocal());
auto answer = callee->CreateAnswer();
wrapper->SetRemoteDescription(
CloneSessionDescriptionAsType(answer.get(), SdpType::kPrAnswer));
break;
}
case SignalingState::kClosed: {
RTC_NOTREACHED() << "Set the second member of the tuple to true to "
"achieve a closed state from an existing, valid "
"state.";
}
}
RTC_DCHECK_EQ(state, wrapper->pc()->signaling_state());
if (closed) {
wrapper->pc()->Close();
RTC_DCHECK_EQ(SignalingState::kClosed, wrapper->signaling_state());
}
return wrapper;
}
std::tuple<SignalingState, bool> state_under_test_;
};
TEST_P(PeerConnectionSignalingStateTest, CreateOffer) {
auto wrapper = CreatePeerConnectionUnderTest();
if (wrapper->signaling_state() != SignalingState::kClosed) {
EXPECT_TRUE(wrapper->CreateOffer());
} else {
std::string error;
ASSERT_FALSE(wrapper->CreateOffer(RTCOfferAnswerOptions(), &error));
EXPECT_PRED_FORMAT2(AssertStartsWith, error,
"CreateOffer called when PeerConnection is closed.");
}
}
TEST_P(PeerConnectionSignalingStateTest, CreateAnswer) {
auto wrapper = CreatePeerConnectionUnderTest();
if (wrapper->signaling_state() == SignalingState::kHaveLocalPrAnswer ||
wrapper->signaling_state() == SignalingState::kHaveRemoteOffer) {
EXPECT_TRUE(wrapper->CreateAnswer());
} else {
std::string error;
ASSERT_FALSE(wrapper->CreateAnswer(RTCOfferAnswerOptions(), &error));
EXPECT_EQ(error,
"PeerConnection cannot create an answer in a state other than "
"have-remote-offer or have-local-pranswer.");
}
}
TEST_P(PeerConnectionSignalingStateTest, SetLocalOffer) {
auto wrapper = CreatePeerConnectionUnderTest();
if (wrapper->signaling_state() == SignalingState::kStable ||
wrapper->signaling_state() == SignalingState::kHaveLocalOffer) {
// Need to call CreateOffer on the PeerConnection under test, otherwise when
// setting the local offer it will want to verify the DTLS fingerprint
// against the locally generated certificate, but without a call to
// CreateOffer the certificate will never be generated.
EXPECT_TRUE(wrapper->SetLocalDescription(wrapper->CreateOffer()));
} else {
auto wrapper_for_offer =
CreatePeerConnectionInState(SignalingState::kHaveLocalOffer);
auto offer =
CloneSessionDescription(wrapper_for_offer->pc()->local_description());
std::string error;
ASSERT_FALSE(wrapper->SetLocalDescription(std::move(offer), &error));
EXPECT_PRED_FORMAT2(
AssertStartsWith, error,
"Failed to set local offer sdp: Called in wrong state:");
}
}
TEST_P(PeerConnectionSignalingStateTest, SetLocalPrAnswer) {
auto wrapper_for_pranswer =
CreatePeerConnectionInState(SignalingState::kHaveLocalPrAnswer);
auto pranswer =
CloneSessionDescription(wrapper_for_pranswer->pc()->local_description());
auto wrapper = CreatePeerConnectionUnderTest();
if (wrapper->signaling_state() == SignalingState::kHaveLocalPrAnswer ||
wrapper->signaling_state() == SignalingState::kHaveRemoteOffer) {
EXPECT_TRUE(wrapper->SetLocalDescription(std::move(pranswer)));
} else {
std::string error;
ASSERT_FALSE(wrapper->SetLocalDescription(std::move(pranswer), &error));
EXPECT_PRED_FORMAT2(
AssertStartsWith, error,
"Failed to set local pranswer sdp: Called in wrong state:");
}
}
TEST_P(PeerConnectionSignalingStateTest, SetLocalAnswer) {
auto wrapper_for_answer =
CreatePeerConnectionInState(SignalingState::kHaveRemoteOffer);
auto answer = wrapper_for_answer->CreateAnswer();
auto wrapper = CreatePeerConnectionUnderTest();
if (wrapper->signaling_state() == SignalingState::kHaveLocalPrAnswer ||
wrapper->signaling_state() == SignalingState::kHaveRemoteOffer) {
EXPECT_TRUE(wrapper->SetLocalDescription(std::move(answer)));
} else {
std::string error;
ASSERT_FALSE(wrapper->SetLocalDescription(std::move(answer), &error));
EXPECT_PRED_FORMAT2(
AssertStartsWith, error,
"Failed to set local answer sdp: Called in wrong state:");
}
}
TEST_P(PeerConnectionSignalingStateTest, SetRemoteOffer) {
auto wrapper_for_offer =
CreatePeerConnectionInState(SignalingState::kHaveRemoteOffer);
auto offer =
CloneSessionDescription(wrapper_for_offer->pc()->remote_description());
auto wrapper = CreatePeerConnectionUnderTest();
if (wrapper->signaling_state() == SignalingState::kStable ||
wrapper->signaling_state() == SignalingState::kHaveRemoteOffer) {
EXPECT_TRUE(wrapper->SetRemoteDescription(std::move(offer)));
} else {
std::string error;
ASSERT_FALSE(wrapper->SetRemoteDescription(std::move(offer), &error));
EXPECT_PRED_FORMAT2(
AssertStartsWith, error,
"Failed to set remote offer sdp: Called in wrong state:");
}
}
TEST_P(PeerConnectionSignalingStateTest, SetRemotePrAnswer) {
auto wrapper_for_pranswer =
CreatePeerConnectionInState(SignalingState::kHaveRemotePrAnswer);
auto pranswer =
CloneSessionDescription(wrapper_for_pranswer->pc()->remote_description());
auto wrapper = CreatePeerConnectionUnderTest();
if (wrapper->signaling_state() == SignalingState::kHaveLocalOffer ||
wrapper->signaling_state() == SignalingState::kHaveRemotePrAnswer) {
EXPECT_TRUE(wrapper->SetRemoteDescription(std::move(pranswer)));
} else {
std::string error;
ASSERT_FALSE(wrapper->SetRemoteDescription(std::move(pranswer), &error));
EXPECT_PRED_FORMAT2(
AssertStartsWith, error,
"Failed to set remote pranswer sdp: Called in wrong state:");
}
}
TEST_P(PeerConnectionSignalingStateTest, SetRemoteAnswer) {
auto wrapper_for_answer =
CreatePeerConnectionInState(SignalingState::kHaveRemoteOffer);
auto answer = wrapper_for_answer->CreateAnswer();
auto wrapper = CreatePeerConnectionUnderTest();
if (wrapper->signaling_state() == SignalingState::kHaveLocalOffer ||
wrapper->signaling_state() == SignalingState::kHaveRemotePrAnswer) {
EXPECT_TRUE(wrapper->SetRemoteDescription(std::move(answer)));
} else {
std::string error;
ASSERT_FALSE(wrapper->SetRemoteDescription(std::move(answer), &error));
EXPECT_PRED_FORMAT2(
AssertStartsWith, error,
"Failed to set remote answer sdp: Called in wrong state:");
}
}
INSTANTIATE_TEST_SUITE_P(PeerConnectionSignalingTest,
PeerConnectionSignalingStateTest,
Combine(Values(SdpSemantics::kPlanB,
SdpSemantics::kUnifiedPlan),
Values(SignalingState::kStable,
SignalingState::kHaveLocalOffer,
SignalingState::kHaveLocalPrAnswer,
SignalingState::kHaveRemoteOffer,
SignalingState::kHaveRemotePrAnswer),
Bool()));
// Test that CreateAnswer fails if a round of offer/answer has been done and
// the PeerConnection is in the stable state.
TEST_P(PeerConnectionSignalingTest, CreateAnswerFailsIfStable) {
auto caller = CreatePeerConnection();
auto callee = CreatePeerConnection();
ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
ASSERT_EQ(SignalingState::kStable, caller->signaling_state());
EXPECT_FALSE(caller->CreateAnswer());
ASSERT_EQ(SignalingState::kStable, callee->signaling_state());
EXPECT_FALSE(callee->CreateAnswer());
}
// According to https://tools.ietf.org/html/rfc3264#section-8, the session id
// stays the same but the version must be incremented if a later, different
// session description is generated. These two tests verify that is the case for
// both offers and answers.
TEST_P(PeerConnectionSignalingTest,
SessionVersionIncrementedInSubsequentDifferentOffer) {
auto caller = CreatePeerConnection();
auto callee = CreatePeerConnection();
auto original_offer = caller->CreateOfferAndSetAsLocal();
const std::string original_id = original_offer->session_id();
const std::string original_version = original_offer->session_version();
ASSERT_TRUE(callee->SetRemoteDescription(std::move(original_offer)));
ASSERT_TRUE(caller->SetRemoteDescription(callee->CreateAnswer()));
// Add track to get a different offer.
caller->AddAudioTrack("a");
auto later_offer = caller->CreateOffer();
EXPECT_EQ(original_id, later_offer->session_id());
EXPECT_LT(rtc::FromString<uint64_t>(original_version),
rtc::FromString<uint64_t>(later_offer->session_version()));
}
TEST_P(PeerConnectionSignalingTest,
SessionVersionIncrementedInSubsequentDifferentAnswer) {
auto caller = CreatePeerConnection();
auto callee = CreatePeerConnection();
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
auto original_answer = callee->CreateAnswer();
const std::string original_id = original_answer->session_id();
const std::string original_version = original_answer->session_version();
// Add track to get a different answer.
callee->AddAudioTrack("a");
auto later_answer = callee->CreateAnswer();
EXPECT_EQ(original_id, later_answer->session_id());
EXPECT_LT(rtc::FromString<uint64_t>(original_version),
rtc::FromString<uint64_t>(later_answer->session_version()));
}
TEST_P(PeerConnectionSignalingTest, InitiatorFlagSetOnCallerAndNotOnCallee) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnectionWithAudioVideo();
EXPECT_FALSE(caller->initial_offerer());
EXPECT_FALSE(callee->initial_offerer());
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
EXPECT_TRUE(caller->initial_offerer());
EXPECT_FALSE(callee->initial_offerer());
ASSERT_TRUE(
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
EXPECT_TRUE(caller->initial_offerer());
EXPECT_FALSE(callee->initial_offerer());
}
// Test creating a PeerConnection, request multiple offers, destroy the
// PeerConnection and make sure we get success/failure callbacks for all of the
// requests.
// Background: crbug.com/507307
TEST_P(PeerConnectionSignalingTest, CreateOffersAndShutdown) {
auto caller = CreatePeerConnection();
RTCOfferAnswerOptions options;
options.offer_to_receive_audio =
RTCOfferAnswerOptions::kOfferToReceiveMediaTrue;
rtc::scoped_refptr<MockCreateSessionDescriptionObserver> observers[100];
for (auto& observer : observers) {
observer =
new rtc::RefCountedObject<MockCreateSessionDescriptionObserver>();
caller->pc()->CreateOffer(observer, options);
}
// Destroy the PeerConnection.
caller.reset(nullptr);
for (auto& observer : observers) {
// We expect to have received a notification now even if the PeerConnection
// was terminated. The offer creation may or may not have succeeded, but we
// must have received a notification.
EXPECT_TRUE(observer->called());
}
}
// Similar to the above test, but by closing the PC first the CreateOffer() will
// fail "early", which triggers a codepath where the PeerConnection is
// reponsible for invoking the observer, instead of the normal codepath where
// the WebRtcSessionDescriptionFactory is responsible for it.
TEST_P(PeerConnectionSignalingTest, CloseCreateOfferAndShutdown) {
auto caller = CreatePeerConnection();
rtc::scoped_refptr<MockCreateSessionDescriptionObserver> observer =
new rtc::RefCountedObject<MockCreateSessionDescriptionObserver>();
caller->pc()->Close();
caller->pc()->CreateOffer(observer, RTCOfferAnswerOptions());
caller.reset(nullptr);
EXPECT_TRUE(observer->called());
}
TEST_P(PeerConnectionSignalingTest,
ImplicitCreateOfferAndShutdownWithOldObserver) {
auto caller = CreatePeerConnection();
auto observer = MockSetSessionDescriptionObserver::Create();
caller->pc()->SetLocalDescription(observer.get());
caller.reset(nullptr);
// The old observer does not get invoked because posted messages are lost.
EXPECT_FALSE(observer->called());
}
TEST_P(PeerConnectionSignalingTest, ImplicitCreateOfferAndShutdown) {
auto caller = CreatePeerConnection();
rtc::scoped_refptr<FakeSetLocalDescriptionObserver> observer(
new FakeSetLocalDescriptionObserver());
caller->pc()->SetLocalDescription(observer);
caller.reset(nullptr);
// The new observer gets invoked because it is called immediately.
EXPECT_TRUE(observer->called());
EXPECT_FALSE(observer->error().ok());
}
TEST_P(PeerConnectionSignalingTest,
CloseBeforeImplicitCreateOfferAndShutdownWithOldObserver) {
auto caller = CreatePeerConnection();
auto observer = MockSetSessionDescriptionObserver::Create();
caller->pc()->Close();
caller->pc()->SetLocalDescription(observer.get());
caller.reset(nullptr);
// The old observer does not get invoked because posted messages are lost.
EXPECT_FALSE(observer->called());
}
TEST_P(PeerConnectionSignalingTest, CloseBeforeImplicitCreateOfferAndShutdown) {
auto caller = CreatePeerConnection();
rtc::scoped_refptr<FakeSetLocalDescriptionObserver> observer(
new FakeSetLocalDescriptionObserver());
caller->pc()->Close();
caller->pc()->SetLocalDescription(observer);
caller.reset(nullptr);
// The new observer gets invoked because it is called immediately.
EXPECT_TRUE(observer->called());
EXPECT_FALSE(observer->error().ok());
}
TEST_P(PeerConnectionSignalingTest,
CloseAfterImplicitCreateOfferAndShutdownWithOldObserver) {
auto caller = CreatePeerConnection();
auto observer = MockSetSessionDescriptionObserver::Create();
caller->pc()->SetLocalDescription(observer.get());
caller->pc()->Close();
caller.reset(nullptr);
// The old observer does not get invoked because posted messages are lost.
EXPECT_FALSE(observer->called());
}
TEST_P(PeerConnectionSignalingTest, CloseAfterImplicitCreateOfferAndShutdown) {
auto caller = CreatePeerConnection();
rtc::scoped_refptr<FakeSetLocalDescriptionObserver> observer(
new FakeSetLocalDescriptionObserver());
caller->pc()->SetLocalDescription(observer);
caller->pc()->Close();
caller.reset(nullptr);
// The new observer gets invoked because it is called immediately.
EXPECT_TRUE(observer->called());
EXPECT_FALSE(observer->error().ok());
}
TEST_P(PeerConnectionSignalingTest,
SetLocalDescriptionNewObserverIsInvokedImmediately) {
auto caller = CreatePeerConnection();
auto offer = caller->CreateOffer(RTCOfferAnswerOptions());
rtc::scoped_refptr<FakeSetLocalDescriptionObserver> observer(
new FakeSetLocalDescriptionObserver());
caller->pc()->SetLocalDescription(std::move(offer), observer);
// The new observer is invoked immediately.
EXPECT_TRUE(observer->called());
EXPECT_TRUE(observer->error().ok());
}
TEST_P(PeerConnectionSignalingTest,
SetLocalDescriptionOldObserverIsInvokedInAPostedMessage) {
auto caller = CreatePeerConnection();
auto offer = caller->CreateOffer(RTCOfferAnswerOptions());
auto observer = MockSetSessionDescriptionObserver::Create();
caller->pc()->SetLocalDescription(observer, offer.release());
// The old observer is not invoked immediately.
EXPECT_FALSE(observer->called());
// Process all currently pending messages by waiting for a posted task to run.
bool checkpoint_reached = false;
rtc::Thread::Current()->PostTask(
RTC_FROM_HERE, [&checkpoint_reached] { checkpoint_reached = true; });
EXPECT_TRUE_WAIT(checkpoint_reached, kWaitTimeout);
// If resolving the observer was pending, it must now have been called.
EXPECT_TRUE(observer->called());
}
TEST_P(PeerConnectionSignalingTest, SetRemoteDescriptionExecutesImmediately) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnection();
// This offer will cause receivers to be created.
auto offer = caller->CreateOffer(RTCOfferAnswerOptions());
// By not waiting for the observer's callback we can verify that the operation
// executed immediately.
callee->pc()->SetRemoteDescription(std::move(offer),
new FakeSetRemoteDescriptionObserver());
EXPECT_EQ(2u, callee->pc()->GetReceivers().size());
}
TEST_P(PeerConnectionSignalingTest, CreateOfferBlocksSetRemoteDescription) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnection();
// This offer will cause receivers to be created.
auto offer = caller->CreateOffer(RTCOfferAnswerOptions());
EXPECT_EQ(0u, callee->pc()->GetReceivers().size());
rtc::scoped_refptr<MockCreateSessionDescriptionObserver> offer_observer(
new rtc::RefCountedObject<MockCreateSessionDescriptionObserver>());
// Synchronously invoke CreateOffer() and SetRemoteDescription(). The
// SetRemoteDescription() operation should be chained to be executed
// asynchronously, when CreateOffer() completes.
callee->pc()->CreateOffer(offer_observer, RTCOfferAnswerOptions());
callee->pc()->SetRemoteDescription(std::move(offer),
new FakeSetRemoteDescriptionObserver());
// CreateOffer() is asynchronous; without message processing this operation
// should not have completed.
EXPECT_FALSE(offer_observer->called());
// Due to chaining, the receivers should not have been created by the offer
// yet.
EXPECT_EQ(0u, callee->pc()->GetReceivers().size());
// EXPECT_TRUE_WAIT causes messages to be processed...
EXPECT_TRUE_WAIT(offer_observer->called(), kWaitTimeout);
// Now that the offer has been completed, SetRemoteDescription() will have
// been executed next in the chain.
EXPECT_EQ(2u, callee->pc()->GetReceivers().size());
}
TEST_P(PeerConnectionSignalingTest,
ParameterlessSetLocalDescriptionCreatesOffer) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto observer = MockSetSessionDescriptionObserver::Create();
caller->pc()->SetLocalDescription(observer.get());
// The offer is created asynchronously; message processing is needed for it to
// complete.
EXPECT_FALSE(observer->called());
EXPECT_FALSE(caller->pc()->pending_local_description());
EXPECT_EQ(PeerConnection::kStable, caller->signaling_state());
// Wait for messages to be processed.
EXPECT_TRUE_WAIT(observer->called(), kWaitTimeout);
EXPECT_TRUE(observer->result());
EXPECT_TRUE(caller->pc()->pending_local_description());
EXPECT_EQ(SdpType::kOffer,
caller->pc()->pending_local_description()->GetType());
EXPECT_EQ(PeerConnection::kHaveLocalOffer, caller->signaling_state());
}
TEST_P(PeerConnectionSignalingTest,
ParameterlessSetLocalDescriptionCreatesAnswer) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnectionWithAudioVideo();
callee->SetRemoteDescription(caller->CreateOffer());
EXPECT_EQ(PeerConnection::kHaveRemoteOffer, callee->signaling_state());
auto observer = MockSetSessionDescriptionObserver::Create();
callee->pc()->SetLocalDescription(observer.get());
// The answer is created asynchronously; message processing is needed for it
// to complete.
EXPECT_FALSE(observer->called());
EXPECT_FALSE(callee->pc()->current_local_description());
// Wait for messages to be processed.
EXPECT_TRUE_WAIT(observer->called(), kWaitTimeout);
EXPECT_TRUE(observer->result());
EXPECT_TRUE(callee->pc()->current_local_description());
EXPECT_EQ(SdpType::kAnswer,
callee->pc()->current_local_description()->GetType());
EXPECT_EQ(PeerConnection::kStable, callee->signaling_state());
}
TEST_P(PeerConnectionSignalingTest,
ParameterlessSetLocalDescriptionFullExchange) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnectionWithAudioVideo();
// SetLocalDescription(), implicitly creating an offer.
auto caller_set_local_description_observer =
MockSetSessionDescriptionObserver::Create();
caller->pc()->SetLocalDescription(
caller_set_local_description_observer.get());
EXPECT_TRUE_WAIT(caller_set_local_description_observer->called(),
kWaitTimeout);
ASSERT_TRUE(caller->pc()->pending_local_description());
// SetRemoteDescription(offer)
auto callee_set_remote_description_observer =
MockSetSessionDescriptionObserver::Create();
callee->pc()->SetRemoteDescription(
callee_set_remote_description_observer,
CloneSessionDescription(caller->pc()->pending_local_description())
.release());
// SetLocalDescription(), implicitly creating an answer.
auto callee_set_local_description_observer =
MockSetSessionDescriptionObserver::Create();
callee->pc()->SetLocalDescription(
callee_set_local_description_observer.get());
EXPECT_TRUE_WAIT(callee_set_local_description_observer->called(),
kWaitTimeout);
// Chaining guarantees SetRemoteDescription() happened before
// SetLocalDescription().
EXPECT_TRUE(callee_set_remote_description_observer->called());
EXPECT_TRUE(callee->pc()->current_local_description());
// SetRemoteDescription(answer)
auto caller_set_remote_description_observer =
MockSetSessionDescriptionObserver::Create();
caller->pc()->SetRemoteDescription(
caller_set_remote_description_observer,
CloneSessionDescription(callee->pc()->current_local_description())
.release());
EXPECT_TRUE_WAIT(caller_set_remote_description_observer->called(),
kWaitTimeout);
EXPECT_EQ(PeerConnection::kStable, caller->signaling_state());
EXPECT_EQ(PeerConnection::kStable, callee->signaling_state());
}
TEST_P(PeerConnectionSignalingTest,
ParameterlessSetLocalDescriptionCloseBeforeCreatingOffer) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto observer = MockSetSessionDescriptionObserver::Create();
caller->pc()->Close();
caller->pc()->SetLocalDescription(observer.get());
// The operation should fail asynchronously.
EXPECT_FALSE(observer->called());
EXPECT_TRUE_WAIT(observer->called(), kWaitTimeout);
EXPECT_FALSE(observer->result());
// This did not affect the signaling state.
EXPECT_EQ(PeerConnection::kClosed, caller->pc()->signaling_state());
EXPECT_EQ(
"SetLocalDescription failed to create session description - "
"SetLocalDescription called when PeerConnection is closed.",
observer->error());
}
TEST_P(PeerConnectionSignalingTest,
ParameterlessSetLocalDescriptionCloseWhileCreatingOffer) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto observer = MockSetSessionDescriptionObserver::Create();
caller->pc()->SetLocalDescription(observer.get());
caller->pc()->Close();
// The operation should fail asynchronously.
EXPECT_FALSE(observer->called());
EXPECT_TRUE_WAIT(observer->called(), kWaitTimeout);
EXPECT_FALSE(observer->result());
// This did not affect the signaling state.
EXPECT_EQ(PeerConnection::kClosed, caller->pc()->signaling_state());
EXPECT_EQ(
"SetLocalDescription failed to create session description - "
"CreateOffer failed because the session was shut down",
observer->error());
}
INSTANTIATE_TEST_SUITE_P(PeerConnectionSignalingTest,
PeerConnectionSignalingTest,
Values(SdpSemantics::kPlanB,
SdpSemantics::kUnifiedPlan));
class PeerConnectionSignalingUnifiedPlanTest
: public PeerConnectionSignalingBaseTest {
protected:
PeerConnectionSignalingUnifiedPlanTest()
: PeerConnectionSignalingBaseTest(SdpSemantics::kUnifiedPlan) {}
};
// We verify that SetLocalDescription() executed immediately by verifying that
// the transceiver mid values got assigned. SLD executing immeditately is not
// unique to Unified Plan, but the transceivers used to verify this are only
// available in Unified Plan.
TEST_F(PeerConnectionSignalingUnifiedPlanTest,
SetLocalDescriptionExecutesImmediatelyUsingOldObserver) {
auto caller = CreatePeerConnectionWithAudioVideo();
// This offer will cause transceiver mids to get assigned.
auto offer = caller->CreateOffer(RTCOfferAnswerOptions());
// By not waiting for the observer's callback we can verify that the operation
// executed immediately. The old observer is invoked in a posted message, so
// waiting for it would not ensure synchronicity.
RTC_DCHECK(!caller->pc()->GetTransceivers()[0]->mid().has_value());
caller->pc()->SetLocalDescription(
new rtc::RefCountedObject<MockSetSessionDescriptionObserver>(),
offer.release());
EXPECT_TRUE(caller->pc()->GetTransceivers()[0]->mid().has_value());
}
TEST_F(PeerConnectionSignalingUnifiedPlanTest,
SetLocalDescriptionExecutesImmediatelyUsingNewObserver) {
auto caller = CreatePeerConnectionWithAudioVideo();
// This offer will cause transceiver mids to get assigned.
auto offer = caller->CreateOffer(RTCOfferAnswerOptions());
// Verify that mids were assigned without waiting for the observer. (However,
// the new observer should also be invoked synchronously - as is ensured by
// other tests.)
RTC_DCHECK(!caller->pc()->GetTransceivers()[0]->mid().has_value());
caller->pc()->SetLocalDescription(std::move(offer),
new FakeSetLocalDescriptionObserver());
EXPECT_TRUE(caller->pc()->GetTransceivers()[0]->mid().has_value());
}
TEST_F(PeerConnectionSignalingUnifiedPlanTest,
SetLocalDescriptionExecutesImmediatelyInsideCreateOfferCallback) {
auto caller = CreatePeerConnectionWithAudioVideo();
// This offer will cause transceiver mids to get assigned.
auto offer = caller->CreateOffer(RTCOfferAnswerOptions());
rtc::scoped_refptr<ExecuteFunctionOnCreateSessionDescriptionObserver>
offer_observer(new rtc::RefCountedObject<
ExecuteFunctionOnCreateSessionDescriptionObserver>(
[pc = caller->pc()](SessionDescriptionInterface* desc) {
// By not waiting for the observer's callback we can verify that the
// operation executed immediately.
RTC_DCHECK(!pc->GetTransceivers()[0]->mid().has_value());
pc->SetLocalDescription(
new rtc::RefCountedObject<MockSetSessionDescriptionObserver>(),
desc);
EXPECT_TRUE(pc->GetTransceivers()[0]->mid().has_value());
}));
caller->pc()->CreateOffer(offer_observer, RTCOfferAnswerOptions());
EXPECT_TRUE_WAIT(offer_observer->was_called(), kWaitTimeout);
}
// Test that transports are shown in the sender/receiver API after offer/answer.
// This only works in Unified Plan.
TEST_F(PeerConnectionSignalingUnifiedPlanTest,
DtlsTransportsInstantiateInOfferAnswer) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnection();
EXPECT_FALSE(HasDtlsTransport(caller));
EXPECT_FALSE(HasDtlsTransport(callee));
auto offer = caller->CreateOffer(RTCOfferAnswerOptions());
caller->SetLocalDescription(CloneSessionDescription(offer.get()));
EXPECT_TRUE(HasDtlsTransport(caller));
callee->SetRemoteDescription(std::move(offer));
EXPECT_FALSE(HasDtlsTransport(callee));
auto answer = callee->CreateAnswer(RTCOfferAnswerOptions());
callee->SetLocalDescription(CloneSessionDescription(answer.get()));
EXPECT_TRUE(HasDtlsTransport(callee));
caller->SetRemoteDescription(std::move(answer));
EXPECT_TRUE(HasDtlsTransport(caller));
ASSERT_EQ(SignalingState::kStable, caller->signaling_state());
}
TEST_F(PeerConnectionSignalingUnifiedPlanTest, DtlsTransportsMergeWhenBundled) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnection();
EXPECT_FALSE(HasDtlsTransport(caller));
EXPECT_FALSE(HasDtlsTransport(callee));
auto offer = caller->CreateOffer(RTCOfferAnswerOptions());
caller->SetLocalDescription(CloneSessionDescription(offer.get()));
EXPECT_EQ(2, NumberOfDtlsTransports(caller));
callee->SetRemoteDescription(std::move(offer));
auto answer = callee->CreateAnswer(RTCOfferAnswerOptions());
callee->SetLocalDescription(CloneSessionDescription(answer.get()));
caller->SetRemoteDescription(std::move(answer));
EXPECT_EQ(1, NumberOfDtlsTransports(caller));
ASSERT_EQ(SignalingState::kStable, caller->signaling_state());
}
TEST_F(PeerConnectionSignalingUnifiedPlanTest,
DtlsTransportsAreSeparateeWhenUnbundled) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnection();
EXPECT_FALSE(HasDtlsTransport(caller));
EXPECT_FALSE(HasDtlsTransport(callee));
RTCOfferAnswerOptions unbundle_options;
unbundle_options.use_rtp_mux = false;
auto offer = caller->CreateOffer(unbundle_options);
caller->SetLocalDescription(CloneSessionDescription(offer.get()));
EXPECT_EQ(2, NumberOfDtlsTransports(caller));
callee->SetRemoteDescription(std::move(offer));
auto answer = callee->CreateAnswer(RTCOfferAnswerOptions());
callee->SetLocalDescription(CloneSessionDescription(answer.get()));
EXPECT_EQ(2, NumberOfDtlsTransports(callee));
caller->SetRemoteDescription(std::move(answer));
EXPECT_EQ(2, NumberOfDtlsTransports(caller));
ASSERT_EQ(SignalingState::kStable, caller->signaling_state());
}
} // namespace webrtc