/* * 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 #include "api/audio_codecs/builtin_audio_decoder_factory.h" #include "api/audio_codecs/builtin_audio_encoder_factory.h" #include "api/peerconnectionproxy.h" #include "api/video_codecs/builtin_video_decoder_factory.h" #include "api/video_codecs/builtin_video_encoder_factory.h" #include "pc/peerconnection.h" #include "pc/peerconnectionwrapper.h" #include "pc/sdputils.h" #ifdef WEBRTC_ANDROID #include "pc/test/androidtestinitializer.h" #endif #include "absl/memory/memory.h" #include "pc/test/fakeaudiocapturemodule.h" #include "pc/test/fakertccertificategenerator.h" #include "rtc_base/gunit.h" #include "rtc_base/stringutils.h" #include "rtc_base/virtualsocketserver.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; class PeerConnectionWrapperForSignalingTest : public PeerConnectionWrapper { public: using PeerConnectionWrapper::PeerConnectionWrapper; bool initial_offerer() { return GetInternalPeerConnection()->initial_offerer(); } PeerConnection* GetInternalPeerConnection() { auto* pci = static_cast*>( pc()); return static_cast(pci->internal()); } }; class PeerConnectionSignalingBaseTest : public ::testing::Test { protected: typedef std::unique_ptr 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(FakeAudioCaptureModule::Create()), CreateBuiltinAudioEncoderFactory(), CreateBuiltinAudioDecoderFactory(), CreateBuiltinVideoEncoderFactory(), CreateBuiltinVideoDecoderFactory(), nullptr /* audio_mixer */, nullptr /* audio_processing */); } WrapperPtr CreatePeerConnection() { return CreatePeerConnection(RTCConfiguration()); } WrapperPtr CreatePeerConnection(const RTCConfiguration& config) { auto observer = absl::make_unique(); 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; } return absl::make_unique( pc_factory_, pc, std::move(observer)); } // Accepts the same arguments as CreatePeerConnection and adds default audio // and video tracks. template WrapperPtr CreatePeerConnectionWithAudioVideo(Args&&... args) { auto wrapper = CreatePeerConnection(std::forward(args)...); if (!wrapper) { return nullptr; } wrapper->AddAudioTrack("a"); wrapper->AddVideoTrack("v"); return wrapper; } std::unique_ptr vss_; rtc::AutoSocketServerThread main_; rtc::scoped_refptr pc_factory_; const SdpSemantics sdp_semantics_; }; class PeerConnectionSignalingTest : public PeerConnectionSignalingBaseTest, public ::testing::WithParamInterface { 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> { 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 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 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_CASE_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(original_version), rtc::FromString(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(original_version), rtc::FromString(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 observers[100]; for (auto& observer : observers) { observer = new rtc::RefCountedObject(); 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()); } } INSTANTIATE_TEST_CASE_P(PeerConnectionSignalingTest, PeerConnectionSignalingTest, Values(SdpSemantics::kPlanB, SdpSemantics::kUnifiedPlan)); } // namespace webrtc