diff options
Diffstat (limited to 'third_party/libwebrtc/pc/peer_connection_signaling_unittest.cc')
-rw-r--r-- | third_party/libwebrtc/pc/peer_connection_signaling_unittest.cc | 1355 |
1 files changed, 1355 insertions, 0 deletions
diff --git a/third_party/libwebrtc/pc/peer_connection_signaling_unittest.cc b/third_party/libwebrtc/pc/peer_connection_signaling_unittest.cc new file mode 100644 index 0000000000..3ff890ab6a --- /dev/null +++ b/third_party/libwebrtc/pc/peer_connection_signaling_unittest.cc @@ -0,0 +1,1355 @@ +/* + * 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 <cstdint> +#include <functional> +#include <map> +#include <memory> +#include <set> +#include <string> +#include <tuple> +#include <type_traits> +#include <utility> +#include <vector> + +#include "absl/types/optional.h" +#include "api/audio/audio_mixer.h" +#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/dtls_transport_interface.h" +#include "api/jsep.h" +#include "api/media_types.h" +#include "api/peer_connection_interface.h" +#include "api/rtc_error.h" +#include "api/rtp_receiver_interface.h" +#include "api/rtp_sender_interface.h" +#include "api/rtp_transceiver_interface.h" +#include "api/scoped_refptr.h" +#include "api/set_local_description_observer_interface.h" +#include "api/set_remote_description_observer_interface.h" +#include "api/video_codecs/builtin_video_decoder_factory.h" +#include "api/video_codecs/builtin_video_encoder_factory.h" +#include "media/base/codec.h" +#include "modules/audio_device/include/audio_device.h" +#include "modules/audio_processing/include/audio_processing.h" +#include "p2p/base/port_allocator.h" +#include "pc/peer_connection.h" +#include "pc/peer_connection_proxy.h" +#include "pc/peer_connection_wrapper.h" +#include "pc/sdp_utils.h" +#include "pc/session_description.h" +#include "pc/test/mock_peer_connection_observers.h" +#include "rtc_base/checks.h" +#include "rtc_base/rtc_certificate.h" +#include "rtc_base/rtc_certificate_generator.h" +#include "rtc_base/string_encode.h" +#include "rtc_base/thread.h" +#include "test/gtest.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" + +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_DCHECK_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 result = pc_factory_->CreatePeerConnectionOrError( + modified_config, PeerConnectionDependencies(observer.get())); + if (!result.ok()) { + return nullptr; + } + + observer->SetPeerConnectionInterface(result.value().get()); + return std::make_unique<PeerConnectionWrapperForSignalingTest>( + pc_factory_, result.MoveValue(), 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_DCHECK_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_DEPRECATED, + 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.get(), 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_WAIT(observer->called(), kWaitTimeout); + } +} + +// 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.get(), RTCOfferAnswerOptions()); + caller.reset(nullptr); + EXPECT_TRUE_WAIT(observer->called(), kWaitTimeout); +} + +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(); + auto observer = rtc::make_ref_counted<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(); + auto observer = rtc::make_ref_counted<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(); + auto observer = rtc::make_ref_counted<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()); + + auto observer = rtc::make_ref_counted<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.get(), 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( + [&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), + rtc::make_ref_counted<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.get(), RTCOfferAnswerOptions()); + callee->pc()->SetRemoteDescription( + std::move(offer), + rtc::make_ref_counted<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.get(), + 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.get(), + 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 == "AV1"; }); + if (av1_it != offer_codecs.end()) { + ASSERT_NE(av1_it->id, 35); + } +} + +TEST_P(PeerConnectionSignalingTest, MidAttributeMaxLength) { + 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" + "m=video 9 UDP/TLS/RTP/SAVPF 102\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" + // Too long mid attribute. + "a=mid:0123456789012345678901234567890123\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=ssrc:1224551896 cname:/exJcmhSLpyu9FgV\r\n"; + std::unique_ptr<webrtc::SessionDescriptionInterface> remote_description = + webrtc::CreateSessionDescription(SdpType::kOffer, sdp, nullptr); + + EXPECT_FALSE(caller->SetRemoteDescription(std::move(remote_description))); +} + +INSTANTIATE_TEST_SUITE_P(PeerConnectionSignalingTest, + PeerConnectionSignalingTest, + Values(SdpSemantics::kPlanB_DEPRECATED, + 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>().get(), + 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), + rtc::make_ref_counted<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( + rtc::make_ref_counted<MockSetSessionDescriptionObserver>() + .get(), + desc); + EXPECT_TRUE(pc->GetTransceivers()[0]->mid().has_value()); + }); + caller->pc()->CreateOffer(offer_observer.get(), 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.get(), 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())); +} + +TEST_F(PeerConnectionSignalingUnifiedPlanTest, RtxReofferApt) { + auto callee = 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" + "m=video 9 UDP/TLS/RTP/SAVPF 102\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=ssrc:1224551896 cname:/exJcmhSLpyu9FgV\r\n"; + std::unique_ptr<webrtc::SessionDescriptionInterface> remote_description = + webrtc::CreateSessionDescription(SdpType::kOffer, sdp, nullptr); + + EXPECT_TRUE(callee->SetRemoteDescription(std::move(remote_description))); + + auto answer = callee->CreateAnswer(RTCOfferAnswerOptions()); + EXPECT_TRUE( + callee->SetLocalDescription(CloneSessionDescription(answer.get()))); + + callee->pc()->GetTransceivers()[0]->StopStandard(); + auto reoffer = callee->CreateOffer(RTCOfferAnswerOptions()); + auto codecs = reoffer->description() + ->contents()[0] + .media_description() + ->as_video() + ->codecs(); + ASSERT_GT(codecs.size(), 2u); + EXPECT_EQ(codecs[0].name, "VP8"); + EXPECT_EQ(codecs[1].name, "rtx"); + auto apt_it = codecs[1].params.find("apt"); + ASSERT_NE(apt_it, codecs[1].params.end()); + // The apt should match the id from the remote offer. + EXPECT_EQ(apt_it->second, rtc::ToString(codecs[0].id)); + EXPECT_EQ(apt_it->second, "102"); +} + +} // namespace webrtc |