| /* |
| * 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/video_decoder_factory_template.h" |
| #include "api/video_codecs/video_decoder_factory_template_dav1d_adapter.h" |
| #include "api/video_codecs/video_decoder_factory_template_libvpx_vp8_adapter.h" |
| #include "api/video_codecs/video_decoder_factory_template_libvpx_vp9_adapter.h" |
| #include "api/video_codecs/video_decoder_factory_template_open_h264_adapter.h" |
| #include "api/video_codecs/video_encoder_factory_template.h" |
| #include "api/video_codecs/video_encoder_factory_template_libaom_av1_adapter.h" |
| #include "api/video_codecs/video_encoder_factory_template_libvpx_vp8_adapter.h" |
| #include "api/video_codecs/video_encoder_factory_template_libvpx_vp9_adapter.h" |
| #include "api/video_codecs/video_encoder_factory_template_open_h264_adapter.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(), |
| std::make_unique<VideoEncoderFactoryTemplate< |
| LibvpxVp8EncoderTemplateAdapter, LibvpxVp9EncoderTemplateAdapter, |
| OpenH264EncoderTemplateAdapter, LibaomAv1EncoderTemplateAdapter>>(), |
| std::make_unique<VideoDecoderFactoryTemplate< |
| LibvpxVp8DecoderTemplateAdapter, LibvpxVp9DecoderTemplateAdapter, |
| OpenH264DecoderTemplateAdapter, Dav1dDecoderTemplateAdapter>>(), |
| 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<SessionDescriptionInterface> remote_description = |
| 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<SessionDescriptionInterface> remote_description = |
| 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<SessionDescriptionInterface> remote_description = |
| 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<SessionDescriptionInterface> remote_description = |
| 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<SessionDescriptionInterface> remote_description = |
| 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 |