/* * 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 #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*>( pc()); return static_cast(pci->internal()); } }; class ExecuteFunctionOnCreateSessionDescriptionObserver : public CreateSessionDescriptionObserver { public: ExecuteFunctionOnCreateSessionDescriptionObserver( std::function 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 function_; }; 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 = std::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; } observer->SetPeerConnectionInterface(pc.get()); return std::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; } int NumberOfDtlsTransports(const WrapperPtr& pc_wrapper) { std::set 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 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_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(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()); } } // 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 observer = new rtc::RefCountedObject(); caller->pc()->Close(); caller->pc()->CreateOffer(observer, RTCOfferAnswerOptions()); caller.reset(nullptr); EXPECT_TRUE(observer->called()); } TEST_P(PeerConnectionSignalingTest, ImplicitCreateOfferAndShutdown) { auto caller = CreatePeerConnection(); auto observer = MockSetSessionDescriptionObserver::Create(); caller->pc()->SetLocalDescription(observer); caller.reset(nullptr); EXPECT_FALSE(observer->called()); } TEST_P(PeerConnectionSignalingTest, CloseBeforeImplicitCreateOfferAndShutdown) { auto caller = CreatePeerConnection(); auto observer = MockSetSessionDescriptionObserver::Create(); caller->pc()->Close(); caller->pc()->SetLocalDescription(observer); caller.reset(nullptr); EXPECT_FALSE(observer->called()); } TEST_P(PeerConnectionSignalingTest, CloseAfterImplicitCreateOfferAndShutdown) { auto caller = CreatePeerConnection(); auto observer = MockSetSessionDescriptionObserver::Create(); caller->pc()->SetLocalDescription(observer); caller->pc()->Close(); caller.reset(nullptr); EXPECT_FALSE(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 MockSetRemoteDescriptionObserver()); 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 offer_observer( new rtc::RefCountedObject()); // 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 MockSetRemoteDescriptionObserver()); // 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); // 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); // 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. rtc::scoped_refptr caller_set_local_description_observer( new rtc::RefCountedObject()); caller->pc()->SetLocalDescription(caller_set_local_description_observer); EXPECT_TRUE_WAIT(caller_set_local_description_observer->called(), kWaitTimeout); ASSERT_TRUE(caller->pc()->pending_local_description()); // SetRemoteDescription(offer) rtc::scoped_refptr callee_set_remote_description_observer( new rtc::RefCountedObject()); callee->pc()->SetRemoteDescription( callee_set_remote_description_observer.get(), CloneSessionDescription(caller->pc()->pending_local_description()) .release()); // SetLocalDescription(), implicitly creating an answer. rtc::scoped_refptr callee_set_local_description_observer( new rtc::RefCountedObject()); callee->pc()->SetLocalDescription(callee_set_local_description_observer); 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) rtc::scoped_refptr caller_set_remote_description_observer( new rtc::RefCountedObject()); 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); // 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); 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, SetLocalDescriptionExecutesImmediately) { 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. RTC_DCHECK(!caller->pc()->GetTransceivers()[0]->mid().has_value()); caller->pc()->SetLocalDescription( new rtc::RefCountedObject(), offer.release()); 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 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(), 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