|  | /* | 
|  | *  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 <algorithm> | 
|  | #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/jsep_session_description.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" | 
|  | #include "pc/webrtc_sdp.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 = rtc::make_ref_counted<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(); | 
|  | auto observer = rtc::make_ref_counted<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()); | 
|  | auto offer_observer = | 
|  | rtc::make_ref_counted<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()); | 
|  | } | 
|  |  | 
|  | TEST_P(PeerConnectionSignalingTest, UnsupportedContentType) { | 
|  | auto caller = CreatePeerConnection(); | 
|  |  | 
|  | // Call setRemoteDescription with a m= line we don't understand. | 
|  | std::string sdp = | 
|  | "v=0\r\n" | 
|  | "o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1\r\n" | 
|  | "s=-\r\n" | 
|  | "t=0 0\r\n" | 
|  | "m=bogus 9 FOO 0 8\r\n" | 
|  | "c=IN IP4 0.0.0.0\r\n" | 
|  | "a=mid:bogusmid\r\n"; | 
|  | std::unique_ptr<webrtc::SessionDescriptionInterface> remote_description = | 
|  | webrtc::CreateSessionDescription(SdpType::kOffer, sdp, nullptr); | 
|  |  | 
|  | EXPECT_TRUE(caller->SetRemoteDescription(std::move(remote_description))); | 
|  |  | 
|  | // Assert we respond back with something meaningful. | 
|  | auto answer = caller->CreateAnswer(); | 
|  | ASSERT_EQ(answer->description()->contents().size(), 1u); | 
|  | EXPECT_NE(answer->description() | 
|  | ->contents()[0] | 
|  | .media_description() | 
|  | ->as_unsupported(), | 
|  | nullptr); | 
|  | EXPECT_EQ(answer->description() | 
|  | ->contents()[0] | 
|  | .media_description() | 
|  | ->as_unsupported() | 
|  | ->media_type(), | 
|  | "bogus"); | 
|  | EXPECT_TRUE(answer->description()->contents()[0].rejected); | 
|  | EXPECT_EQ(answer->description()->contents()[0].mid(), "bogusmid"); | 
|  | EXPECT_EQ( | 
|  | answer->description()->contents()[0].media_description()->protocol(), | 
|  | "FOO"); | 
|  | EXPECT_FALSE( | 
|  | answer->description()->contents()[0].media_description()->has_codecs()); | 
|  |  | 
|  | EXPECT_TRUE(caller->SetLocalDescription(std::move(answer))); | 
|  |  | 
|  | // Assert we keep this in susequent offers. | 
|  | auto offer = caller->CreateOffer(); | 
|  | EXPECT_EQ(offer->description() | 
|  | ->contents()[0] | 
|  | .media_description() | 
|  | ->as_unsupported() | 
|  | ->media_type(), | 
|  | "bogus"); | 
|  | EXPECT_TRUE(offer->description()->contents()[0].rejected); | 
|  | EXPECT_EQ(offer->description()->contents()[0].media_description()->protocol(), | 
|  | "FOO"); | 
|  | EXPECT_EQ(offer->description()->contents()[0].mid(), "bogusmid"); | 
|  | EXPECT_FALSE( | 
|  | offer->description()->contents()[0].media_description()->has_codecs()); | 
|  | EXPECT_TRUE(caller->SetLocalDescription(std::move(offer))); | 
|  | } | 
|  |  | 
|  | TEST_P(PeerConnectionSignalingTest, ReceiveFlexFec) { | 
|  | auto caller = CreatePeerConnection(); | 
|  |  | 
|  | std::string sdp = | 
|  | "v=0\r\n" | 
|  | "o=- 8403615332048243445 2 IN IP4 127.0.0.1\r\n" | 
|  | "s=-\r\n" | 
|  | "t=0 0\r\n" | 
|  | "a=group:BUNDLE 0\r\n" | 
|  | "m=video 9 UDP/TLS/RTP/SAVPF 102 122\r\n" | 
|  | "c=IN IP4 0.0.0.0\r\n" | 
|  | "a=rtcp:9 IN IP4 0.0.0.0\r\n" | 
|  | "a=ice-ufrag:IZeV\r\n" | 
|  | "a=ice-pwd:uaZhQD4rYM/Tta2qWBT1Bbt4\r\n" | 
|  | "a=ice-options:trickle\r\n" | 
|  | "a=fingerprint:sha-256 " | 
|  | "D8:6C:3D:FA:23:E2:2C:63:11:2D:D0:86:BE:C4:D0:65:F9:42:F7:1C:06:04:27:E6:" | 
|  | "1C:2C:74:01:8D:50:67:23\r\n" | 
|  | "a=setup:actpass\r\n" | 
|  | "a=mid:0\r\n" | 
|  | "a=sendrecv\r\n" | 
|  | "a=msid:stream track\r\n" | 
|  | "a=rtcp-mux\r\n" | 
|  | "a=rtcp-rsize\r\n" | 
|  | "a=rtpmap:102 VP8/90000\r\n" | 
|  | "a=rtcp-fb:102 goog-remb\r\n" | 
|  | "a=rtcp-fb:102 transport-cc\r\n" | 
|  | "a=rtcp-fb:102 ccm fir\r\n" | 
|  | "a=rtcp-fb:102 nack\r\n" | 
|  | "a=rtcp-fb:102 nack pli\r\n" | 
|  | "a=rtpmap:122 flexfec-03/90000\r\n" | 
|  | "a=fmtp:122 repair-window=10000000\r\n" | 
|  | "a=ssrc-group:FEC-FR 1224551896 1953032773\r\n" | 
|  | "a=ssrc:1224551896 cname:/exJcmhSLpyu9FgV\r\n" | 
|  | "a=ssrc:1953032773 cname:/exJcmhSLpyu9FgV\r\n"; | 
|  | std::unique_ptr<webrtc::SessionDescriptionInterface> remote_description = | 
|  | webrtc::CreateSessionDescription(SdpType::kOffer, sdp, nullptr); | 
|  |  | 
|  | EXPECT_TRUE(caller->SetRemoteDescription(std::move(remote_description))); | 
|  |  | 
|  | auto answer = caller->CreateAnswer(); | 
|  | ASSERT_EQ(answer->description()->contents().size(), 1u); | 
|  | ASSERT_NE( | 
|  | answer->description()->contents()[0].media_description()->as_video(), | 
|  | nullptr); | 
|  | auto codecs = answer->description() | 
|  | ->contents()[0] | 
|  | .media_description() | 
|  | ->as_video() | 
|  | ->codecs(); | 
|  | ASSERT_EQ(codecs.size(), 2u); | 
|  | EXPECT_EQ(codecs[1].name, "flexfec-03"); | 
|  |  | 
|  | EXPECT_TRUE(caller->SetLocalDescription(std::move(answer))); | 
|  | } | 
|  |  | 
|  | TEST_P(PeerConnectionSignalingTest, ReceiveFlexFecReoffer) { | 
|  | auto caller = CreatePeerConnection(); | 
|  |  | 
|  | std::string sdp = | 
|  | "v=0\r\n" | 
|  | "o=- 8403615332048243445 2 IN IP4 127.0.0.1\r\n" | 
|  | "s=-\r\n" | 
|  | "t=0 0\r\n" | 
|  | "a=group:BUNDLE 0\r\n" | 
|  | "m=video 9 UDP/TLS/RTP/SAVPF 102 35\r\n" | 
|  | "c=IN IP4 0.0.0.0\r\n" | 
|  | "a=rtcp:9 IN IP4 0.0.0.0\r\n" | 
|  | "a=ice-ufrag:IZeV\r\n" | 
|  | "a=ice-pwd:uaZhQD4rYM/Tta2qWBT1Bbt4\r\n" | 
|  | "a=ice-options:trickle\r\n" | 
|  | "a=fingerprint:sha-256 " | 
|  | "D8:6C:3D:FA:23:E2:2C:63:11:2D:D0:86:BE:C4:D0:65:F9:42:F7:1C:06:04:27:E6:" | 
|  | "1C:2C:74:01:8D:50:67:23\r\n" | 
|  | "a=setup:actpass\r\n" | 
|  | "a=mid:0\r\n" | 
|  | "a=sendrecv\r\n" | 
|  | "a=msid:stream track\r\n" | 
|  | "a=rtcp-mux\r\n" | 
|  | "a=rtcp-rsize\r\n" | 
|  | "a=rtpmap:102 VP8/90000\r\n" | 
|  | "a=rtcp-fb:102 goog-remb\r\n" | 
|  | "a=rtcp-fb:102 transport-cc\r\n" | 
|  | "a=rtcp-fb:102 ccm fir\r\n" | 
|  | "a=rtcp-fb:102 nack\r\n" | 
|  | "a=rtcp-fb:102 nack pli\r\n" | 
|  | "a=rtpmap:35 flexfec-03/90000\r\n" | 
|  | "a=fmtp:35 repair-window=10000000\r\n" | 
|  | "a=ssrc-group:FEC-FR 1224551896 1953032773\r\n" | 
|  | "a=ssrc:1224551896 cname:/exJcmhSLpyu9FgV\r\n" | 
|  | "a=ssrc:1953032773 cname:/exJcmhSLpyu9FgV\r\n"; | 
|  | std::unique_ptr<webrtc::SessionDescriptionInterface> remote_description = | 
|  | webrtc::CreateSessionDescription(SdpType::kOffer, sdp, nullptr); | 
|  |  | 
|  | EXPECT_TRUE(caller->SetRemoteDescription(std::move(remote_description))); | 
|  |  | 
|  | auto answer = caller->CreateAnswer(); | 
|  | ASSERT_EQ(answer->description()->contents().size(), 1u); | 
|  | ASSERT_NE( | 
|  | answer->description()->contents()[0].media_description()->as_video(), | 
|  | nullptr); | 
|  | auto codecs = answer->description() | 
|  | ->contents()[0] | 
|  | .media_description() | 
|  | ->as_video() | 
|  | ->codecs(); | 
|  | ASSERT_EQ(codecs.size(), 2u); | 
|  | EXPECT_EQ(codecs[1].name, "flexfec-03"); | 
|  | EXPECT_EQ(codecs[1].id, 35); | 
|  |  | 
|  | EXPECT_TRUE(caller->SetLocalDescription(std::move(answer))); | 
|  |  | 
|  | // This generates a collision for AV1 which needs to be remapped. | 
|  | auto offer = caller->CreateOffer(RTCOfferAnswerOptions()); | 
|  | auto offer_codecs = offer->description() | 
|  | ->contents()[0] | 
|  | .media_description() | 
|  | ->as_video() | 
|  | ->codecs(); | 
|  | auto flexfec_it = std::find_if( | 
|  | offer_codecs.begin(), offer_codecs.end(), | 
|  | [](const cricket::Codec& codec) { return codec.name == "flexfec-03"; }); | 
|  | ASSERT_EQ(flexfec_it->id, 35); | 
|  | auto av1_it = std::find_if( | 
|  | offer_codecs.begin(), offer_codecs.end(), | 
|  | [](const cricket::Codec& codec) { return codec.name == "AV1X"; }); | 
|  | if (av1_it != offer_codecs.end()) { | 
|  | ASSERT_NE(av1_it->id, 35); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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( | 
|  | rtc::make_ref_counted<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()); | 
|  |  | 
|  | auto offer_observer = | 
|  | rtc::make_ref_counted<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()); | 
|  | } | 
|  |  | 
|  | TEST_F(PeerConnectionSignalingUnifiedPlanTest, | 
|  | ShouldFireNegotiationNeededWhenNoChangesArePending) { | 
|  | auto caller = CreatePeerConnection(); | 
|  | EXPECT_FALSE(caller->observer()->has_negotiation_needed_event()); | 
|  | auto transceiver = | 
|  | caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO, RtpTransceiverInit()); | 
|  | EXPECT_TRUE(caller->observer()->has_negotiation_needed_event()); | 
|  | EXPECT_TRUE(caller->pc()->ShouldFireNegotiationNeededEvent( | 
|  | caller->observer()->latest_negotiation_needed_event())); | 
|  | } | 
|  |  | 
|  | TEST_F(PeerConnectionSignalingUnifiedPlanTest, | 
|  | SuppressNegotiationNeededWhenOperationChainIsNotEmpty) { | 
|  | auto caller = CreatePeerConnection(); | 
|  | EXPECT_FALSE(caller->observer()->has_negotiation_needed_event()); | 
|  | auto transceiver = | 
|  | caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO, RtpTransceiverInit()); | 
|  | EXPECT_TRUE(caller->observer()->has_negotiation_needed_event()); | 
|  |  | 
|  | auto observer = rtc::make_ref_counted<MockCreateSessionDescriptionObserver>(); | 
|  | caller->pc()->CreateOffer(observer, RTCOfferAnswerOptions()); | 
|  | // For this test to work, the operation has to be pending, i.e. the observer | 
|  | // has not yet been invoked. | 
|  | EXPECT_FALSE(observer->called()); | 
|  | // Because the Operations Chain is not empty, the event is now suppressed. | 
|  | EXPECT_FALSE(caller->pc()->ShouldFireNegotiationNeededEvent( | 
|  | caller->observer()->latest_negotiation_needed_event())); | 
|  | caller->observer()->clear_latest_negotiation_needed_event(); | 
|  |  | 
|  | // When the Operations Chain becomes empty again, a new negotiation needed | 
|  | // event will be generated that is not suppressed. | 
|  | EXPECT_TRUE_WAIT(observer->called(), kWaitTimeout); | 
|  | EXPECT_TRUE(caller->observer()->has_negotiation_needed_event()); | 
|  | EXPECT_TRUE(caller->pc()->ShouldFireNegotiationNeededEvent( | 
|  | caller->observer()->latest_negotiation_needed_event())); | 
|  | } | 
|  |  | 
|  | TEST_F(PeerConnectionSignalingUnifiedPlanTest, | 
|  | SuppressNegotiationNeededWhenSignalingStateIsNotStable) { | 
|  | auto caller = CreatePeerConnection(); | 
|  | auto callee = CreatePeerConnection(); | 
|  | auto offer = caller->CreateOffer(RTCOfferAnswerOptions()); | 
|  |  | 
|  | EXPECT_FALSE(caller->observer()->has_negotiation_needed_event()); | 
|  | auto transceiver = | 
|  | callee->AddTransceiver(cricket::MEDIA_TYPE_AUDIO, RtpTransceiverInit()); | 
|  | EXPECT_TRUE(callee->observer()->has_negotiation_needed_event()); | 
|  |  | 
|  | // Change signaling state (to "have-remote-offer") by setting a remote offer. | 
|  | callee->SetRemoteDescription(std::move(offer)); | 
|  | // Because the signaling state is not "stable", the event is now suppressed. | 
|  | EXPECT_FALSE(callee->pc()->ShouldFireNegotiationNeededEvent( | 
|  | callee->observer()->latest_negotiation_needed_event())); | 
|  | callee->observer()->clear_latest_negotiation_needed_event(); | 
|  |  | 
|  | // Upon rolling back to "stable", a new negotiation needed event will be | 
|  | // generated that is not suppressed. | 
|  | callee->SetLocalDescription(CreateSessionDescription(SdpType::kRollback, "")); | 
|  | EXPECT_TRUE(callee->observer()->has_negotiation_needed_event()); | 
|  | EXPECT_TRUE(callee->pc()->ShouldFireNegotiationNeededEvent( | 
|  | callee->observer()->latest_negotiation_needed_event())); | 
|  | } | 
|  |  | 
|  | }  // namespace webrtc |