From 3df5dcac9b339ba4d3f4969602f094c2c8035b51 Mon Sep 17 00:00:00 2001 From: Steve Anton Date: Thu, 19 Oct 2017 16:11:30 -0700 Subject: [PATCH] Rewrite WebRtcSession media tests as PeerConnection tests Bug: webrtc:8222 Change-Id: I782a3227e30de70eb8f6c26a48723cb3510a84ad Reviewed-on: https://webrtc-review.googlesource.com/6640 Commit-Queue: Steve Anton Reviewed-by: Taylor Brandstetter Cr-Commit-Position: refs/heads/master@{#20364} --- media/base/fakemediaengine.h | 7 +- pc/BUILD.gn | 4 + pc/peerconnection.cc | 86 +- pc/peerconnection_crypto_unittest.cc | 3 +- pc/peerconnection_ice_unittest.cc | 3 +- pc/peerconnection_media_unittest.cc | 889 +++++++++++++++++++ pc/peerconnection_signaling_unittest.cc | 501 +++++++++++ pc/peerconnectioninterface_unittest.cc | 112 +-- pc/peerconnectionwrapper.cc | 121 +-- pc/peerconnectionwrapper.h | 51 +- pc/sdputils.cc | 9 +- pc/sdputils.h | 5 + pc/test/mockpeerconnectionobservers.h | 22 +- pc/webrtcsession.cc | 80 +- pc/webrtcsession.h | 6 +- pc/webrtcsession_unittest.cc | 1053 +---------------------- 16 files changed, 1642 insertions(+), 1310 deletions(-) create mode 100644 pc/peerconnection_media_unittest.cc create mode 100644 pc/peerconnection_signaling_unittest.cc diff --git a/media/base/fakemediaengine.h b/media/base/fakemediaengine.h index 29a129f781..7b09dd4f0c 100644 --- a/media/base/fakemediaengine.h +++ b/media/base/fakemediaengine.h @@ -488,12 +488,11 @@ class FakeVoiceMediaChannel : public RtpHelper { if (it != local_sinks_.end()) { RTC_CHECK(it->second->source() == source); } else { - local_sinks_.insert( - std::make_pair(ssrc, new VoiceChannelAudioSink(source))); + local_sinks_.insert(std::make_pair( + ssrc, rtc::MakeUnique(source))); } } else { if (it != local_sinks_.end()) { - delete it->second; local_sinks_.erase(it); } } @@ -506,7 +505,7 @@ class FakeVoiceMediaChannel : public RtpHelper { std::map output_scalings_; std::vector dtmf_info_queue_; AudioOptions options_; - std::map local_sinks_; + std::map> local_sinks_; std::unique_ptr sink_; int max_bps_; }; diff --git a/pc/BUILD.gn b/pc/BUILD.gn index c77a638b39..02a8e9a727 100644 --- a/pc/BUILD.gn +++ b/pc/BUILD.gn @@ -394,7 +394,9 @@ if (rtc_include_tests) { "peerconnection_crypto_unittest.cc", "peerconnection_ice_unittest.cc", "peerconnection_integrationtest.cc", + "peerconnection_media_unittest.cc", "peerconnection_rtp_unittest.cc", + "peerconnection_signaling_unittest.cc", "peerconnectionendtoend_unittest.cc", "peerconnectionfactory_unittest.cc", "peerconnectioninterface_unittest.cc", @@ -463,7 +465,9 @@ if (rtc_include_tests) { "../api/audio_codecs:builtin_audio_encoder_factory", "../api/audio_codecs/L16:audio_decoder_L16", "../api/audio_codecs/L16:audio_encoder_L16", + "../call:call_interfaces", "../logging:rtc_event_log_api", + "../logging:rtc_event_log_impl", "../media:rtc_audio_video", "../media:rtc_data", # TODO(phoglund): AFAIK only used for one sctp constant. "../media:rtc_media_base", diff --git a/pc/peerconnection.cc b/pc/peerconnection.cc index a78c29045f..bec13a5886 100644 --- a/pc/peerconnection.cc +++ b/pc/peerconnection.cc @@ -828,10 +828,7 @@ PeerConnection::CreateDataChannel( void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer, const MediaConstraintsInterface* constraints) { TRACE_EVENT0("webrtc", "PeerConnection::CreateOffer"); - if (!observer) { - LOG(LS_ERROR) << "CreateOffer - observer is NULL."; - return; - } + PeerConnectionInterface::RTCOfferAnswerOptions offer_answer_options; // Always create an offer even if |ConvertConstraintsToOfferAnswerOptions| // returns false for now. Because |ConvertConstraintsToOfferAnswerOptions| @@ -848,11 +845,19 @@ void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer, void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer, const RTCOfferAnswerOptions& options) { TRACE_EVENT0("webrtc", "PeerConnection::CreateOffer"); + if (!observer) { LOG(LS_ERROR) << "CreateOffer - observer is NULL."; return; } + if (IsClosed()) { + std::string error = "CreateOffer called when PeerConnection is closed."; + LOG(LS_ERROR) << error; + PostCreateSessionDescriptionFailure(observer, error); + return; + } + if (!ValidateOfferAnswerOptions(options)) { std::string error = "CreateOffer called with invalid options."; LOG(LS_ERROR) << error; @@ -869,20 +874,12 @@ void PeerConnection::CreateAnswer( CreateSessionDescriptionObserver* observer, const MediaConstraintsInterface* constraints) { TRACE_EVENT0("webrtc", "PeerConnection::CreateAnswer"); + if (!observer) { LOG(LS_ERROR) << "CreateAnswer - observer is NULL."; return; } - if (!session_->remote_description() || - session_->remote_description()->type() != - SessionDescriptionInterface::kOffer) { - std::string error = "CreateAnswer called without remote offer."; - LOG(LS_ERROR) << error; - PostCreateSessionDescriptionFailure(observer, error); - return; - } - PeerConnectionInterface::RTCOfferAnswerOptions offer_answer_options; if (!ConvertConstraintsToOfferAnswerOptions(constraints, &offer_answer_options)) { @@ -892,9 +889,7 @@ void PeerConnection::CreateAnswer( return; } - cricket::MediaSessionOptions session_options; - GetOptionsForAnswer(offer_answer_options, &session_options); - session_->CreateAnswer(observer, session_options); + CreateAnswer(observer, offer_answer_options); } void PeerConnection::CreateAnswer(CreateSessionDescriptionObserver* observer, @@ -905,6 +900,22 @@ void PeerConnection::CreateAnswer(CreateSessionDescriptionObserver* observer, return; } + if (IsClosed()) { + std::string error = "CreateAnswer called when PeerConnection is closed."; + LOG(LS_ERROR) << error; + PostCreateSessionDescriptionFailure(observer, error); + return; + } + + if (!session_->remote_description() || + session_->remote_description()->type() != + SessionDescriptionInterface::kOffer) { + std::string error = "CreateAnswer called without remote offer."; + LOG(LS_ERROR) << error; + PostCreateSessionDescriptionFailure(observer, error); + return; + } + cricket::MediaSessionOptions session_options; GetOptionsForAnswer(options, &session_options); @@ -915,9 +926,6 @@ void PeerConnection::SetLocalDescription( SetSessionDescriptionObserver* observer, SessionDescriptionInterface* desc) { TRACE_EVENT0("webrtc", "PeerConnection::SetLocalDescription"); - if (IsClosed()) { - return; - } if (!observer) { LOG(LS_ERROR) << "SetLocalDescription - observer is NULL."; return; @@ -926,11 +934,23 @@ void PeerConnection::SetLocalDescription( PostSetSessionDescriptionFailure(observer, "SessionDescription is NULL."); return; } + + // Takes the ownership of |desc| regardless of the result. + std::unique_ptr desc_temp(desc); + + if (IsClosed()) { + std::string error = "Failed to set local " + desc->type() + + " SDP: Called in wrong state: STATE_CLOSED"; + LOG(LS_ERROR) << error; + PostSetSessionDescriptionFailure(observer, error); + return; + } + // Update stats here so that we have the most recent stats for tracks and // streams that might be removed by updating the session description. stats_->UpdateStats(kStatsOutputLevelStandard); std::string error; - if (!session_->SetLocalDescription(desc, &error)) { + if (!session_->SetLocalDescription(std::move(desc_temp), &error)) { PostSetSessionDescriptionFailure(observer, error); return; } @@ -1011,9 +1031,6 @@ void PeerConnection::SetRemoteDescription( SetSessionDescriptionObserver* observer, SessionDescriptionInterface* desc) { TRACE_EVENT0("webrtc", "PeerConnection::SetRemoteDescription"); - if (IsClosed()) { - return; - } if (!observer) { LOG(LS_ERROR) << "SetRemoteDescription - observer is NULL."; return; @@ -1022,11 +1039,23 @@ void PeerConnection::SetRemoteDescription( PostSetSessionDescriptionFailure(observer, "SessionDescription is NULL."); return; } + + // Takes the ownership of |desc| regardless of the result. + std::unique_ptr desc_temp(desc); + + if (IsClosed()) { + std::string error = "Failed to set remote " + desc->type() + + " SDP: Called in wrong state: STATE_CLOSED"; + LOG(LS_ERROR) << error; + PostSetSessionDescriptionFailure(observer, error); + return; + } + // Update stats here so that we have the most recent stats for tracks and // streams that might be removed by updating the session description. stats_->UpdateStats(kStatsOutputLevelStandard); std::string error; - if (!session_->SetRemoteDescription(desc, &error)) { + if (!session_->SetRemoteDescription(std::move(desc_temp), &error)) { PostSetSessionDescriptionFailure(observer, error); return; } @@ -1061,6 +1090,15 @@ void PeerConnection::SetRemoteDescription( // since only at that point will new streams have all their tracks. rtc::scoped_refptr new_streams(StreamCollection::Create()); + // TODO(steveanton): When removing RTP senders/receivers in response to a + // rejected media section, there is some cleanup logic that expects the voice/ + // video channel to still be set. But in this method the voice/video channel + // would have been destroyed by WebRtcSession's SetRemoteDescription method + // above, so the cleanup that relies on them fails to run. This is hard to fix + // with WebRtcSession and PeerConnection separated, but once the classes are + // merged it will be easy to call RemoveTracks right before destroying the + // voice/video channels. + // Find all audio rtp streams and create corresponding remote AudioTracks // and MediaStreams. if (audio_content) { diff --git a/pc/peerconnection_crypto_unittest.cc b/pc/peerconnection_crypto_unittest.cc index 081e11ac6e..68eec08df9 100644 --- a/pc/peerconnection_crypto_unittest.cc +++ b/pc/peerconnection_crypto_unittest.cc @@ -75,7 +75,8 @@ class PeerConnectionCryptoUnitTest : public ::testing::Test { if (!wrapper) { return nullptr; } - wrapper->AddAudioVideoStream("s", "a", "v"); + wrapper->AddAudioTrack("a"); + wrapper->AddVideoTrack("v"); return wrapper; } diff --git a/pc/peerconnection_ice_unittest.cc b/pc/peerconnection_ice_unittest.cc index 088001841d..3ab9acb267 100644 --- a/pc/peerconnection_ice_unittest.cc +++ b/pc/peerconnection_ice_unittest.cc @@ -120,7 +120,8 @@ class PeerConnectionIceUnitTest : public ::testing::Test { if (!wrapper) { return nullptr; } - wrapper->AddAudioVideoStream("s", "a", "v"); + wrapper->AddAudioTrack("a"); + wrapper->AddVideoTrack("v"); return wrapper; } diff --git a/pc/peerconnection_media_unittest.cc b/pc/peerconnection_media_unittest.cc new file mode 100644 index 0000000000..f106bbe3ea --- /dev/null +++ b/pc/peerconnection_media_unittest.cc @@ -0,0 +1,889 @@ +/* + * 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 interaction between the +// PeerConnection and the underlying media engine, as well as tests that check +// the media-related aspects of SDP. + +#include + +#include "call/callfactoryinterface.h" +#include "logging/rtc_event_log/rtc_event_log_factory.h" +#include "media/base/fakemediaengine.h" +#include "p2p/base/fakeportallocator.h" +#include "pc/mediasession.h" +#include "pc/peerconnectionwrapper.h" +#include "pc/sdputils.h" +#ifdef WEBRTC_ANDROID +#include "pc/test/androidtestinitializer.h" +#endif +#include "pc/test/fakertccertificategenerator.h" +#include "rtc_base/gunit.h" +#include "rtc_base/ptr_util.h" +#include "rtc_base/virtualsocketserver.h" +#include "test/gmock.h" + +namespace webrtc { + +using cricket::FakeMediaEngine; +using RTCConfiguration = PeerConnectionInterface::RTCConfiguration; +using RTCOfferAnswerOptions = PeerConnectionInterface::RTCOfferAnswerOptions; +using ::testing::Bool; +using ::testing::Combine; +using ::testing::Values; +using ::testing::ElementsAre; + +class PeerConnectionWrapperForMediaTest : public PeerConnectionWrapper { + public: + using PeerConnectionWrapper::PeerConnectionWrapper; + + FakeMediaEngine* media_engine() { return media_engine_; } + void set_media_engine(FakeMediaEngine* media_engine) { + media_engine_ = media_engine; + } + + private: + FakeMediaEngine* media_engine_; +}; + +class PeerConnectionMediaTest : public ::testing::Test { + protected: + typedef std::unique_ptr WrapperPtr; + + PeerConnectionMediaTest() + : vss_(new rtc::VirtualSocketServer()), main_(vss_.get()) { +#ifdef WEBRTC_ANDROID + InitializeAndroidObjects(); +#endif + } + + WrapperPtr CreatePeerConnection() { + return CreatePeerConnection(RTCConfiguration()); + } + + WrapperPtr CreatePeerConnection(const RTCConfiguration& config) { + auto media_engine = rtc::MakeUnique(); + auto* media_engine_ptr = media_engine.get(); + auto pc_factory = CreateModularPeerConnectionFactory( + rtc::Thread::Current(), rtc::Thread::Current(), rtc::Thread::Current(), + std::move(media_engine), CreateCallFactory(), + CreateRtcEventLogFactory()); + + auto fake_port_allocator = rtc::MakeUnique( + rtc::Thread::Current(), nullptr); + auto observer = rtc::MakeUnique(); + auto pc = pc_factory->CreatePeerConnection( + config, std::move(fake_port_allocator), nullptr, observer.get()); + if (!pc) { + return nullptr; + } + + auto wrapper = rtc::MakeUnique( + pc_factory, pc, std::move(observer)); + wrapper->set_media_engine(media_engine_ptr); + return wrapper; + } + + // Accepts the same arguments as CreatePeerConnection and adds default audio + // and video tracks. + template + WrapperPtr CreatePeerConnectionWithAudioVideo(Args&&... args) { + auto wrapper = CreatePeerConnection(std::forward(args)...); + if (!wrapper) { + return nullptr; + } + wrapper->AddAudioTrack("a"); + wrapper->AddVideoTrack("v"); + return wrapper; + } + + const cricket::MediaContentDescription* GetMediaContent( + const SessionDescriptionInterface* sdesc, + const std::string& mid) { + const auto* content_desc = + sdesc->description()->GetContentDescriptionByName(mid); + return static_cast(content_desc); + } + + cricket::MediaContentDirection GetMediaContentDirection( + const SessionDescriptionInterface* sdesc, + const std::string& mid) { + auto* media_content = GetMediaContent(sdesc, mid); + RTC_DCHECK(media_content); + return media_content->direction(); + } + + std::unique_ptr vss_; + rtc::AutoSocketServerThread main_; +}; + +TEST_F(PeerConnectionMediaTest, + FailToSetRemoteDescriptionIfCreateMediaChannelFails) { + auto caller = CreatePeerConnectionWithAudioVideo(); + auto callee = CreatePeerConnectionWithAudioVideo(); + callee->media_engine()->set_fail_create_channel(true); + + std::string error; + ASSERT_FALSE(callee->SetRemoteDescription(caller->CreateOffer(), &error)); + EXPECT_EQ("Failed to set remote offer SDP: Failed to create channels.", + error); +} + +TEST_F(PeerConnectionMediaTest, + FailToSetLocalDescriptionIfCreateMediaChannelFails) { + auto caller = CreatePeerConnectionWithAudioVideo(); + caller->media_engine()->set_fail_create_channel(true); + + std::string error; + ASSERT_FALSE(caller->SetLocalDescription(caller->CreateOffer(), &error)); + EXPECT_EQ("Failed to set local offer SDP: Failed to create channels.", error); +} + +std::vector GetIds( + const std::vector& streams) { + std::vector ids; + for (const auto& stream : streams) { + ids.push_back(stream.id); + } + return ids; +} + +// Test that exchanging an offer and answer with each side having an audio and +// video stream creates the appropriate send/recv streams in the underlying +// media engine on both sides. +TEST_F(PeerConnectionMediaTest, AudioVideoOfferAnswerCreateSendRecvStreams) { + const std::string kCallerAudioId = "caller_a"; + const std::string kCallerVideoId = "caller_v"; + const std::string kCalleeAudioId = "callee_a"; + const std::string kCalleeVideoId = "callee_v"; + + auto caller = CreatePeerConnection(); + caller->AddAudioTrack(kCallerAudioId); + caller->AddVideoTrack(kCallerVideoId); + + auto callee = CreatePeerConnection(); + callee->AddAudioTrack(kCalleeAudioId); + callee->AddVideoTrack(kCalleeVideoId); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + ASSERT_TRUE( + caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); + + auto* caller_voice = caller->media_engine()->GetVoiceChannel(0); + EXPECT_THAT(GetIds(caller_voice->recv_streams()), + ElementsAre(kCalleeAudioId)); + EXPECT_THAT(GetIds(caller_voice->send_streams()), + ElementsAre(kCallerAudioId)); + + auto* caller_video = caller->media_engine()->GetVideoChannel(0); + EXPECT_THAT(GetIds(caller_video->recv_streams()), + ElementsAre(kCalleeVideoId)); + EXPECT_THAT(GetIds(caller_video->send_streams()), + ElementsAre(kCallerVideoId)); + + auto* callee_voice = callee->media_engine()->GetVoiceChannel(0); + EXPECT_THAT(GetIds(callee_voice->recv_streams()), + ElementsAre(kCallerAudioId)); + EXPECT_THAT(GetIds(callee_voice->send_streams()), + ElementsAre(kCalleeAudioId)); + + auto* callee_video = callee->media_engine()->GetVideoChannel(0); + EXPECT_THAT(GetIds(callee_video->recv_streams()), + ElementsAre(kCallerVideoId)); + EXPECT_THAT(GetIds(callee_video->send_streams()), + ElementsAre(kCalleeVideoId)); +} + +// Test that removing streams from a subsequent offer causes the receive streams +// on the callee to be removed. +TEST_F(PeerConnectionMediaTest, EmptyRemoteOfferRemovesRecvStreams) { + auto caller = CreatePeerConnection(); + auto caller_audio_track = caller->AddAudioTrack("a"); + auto caller_video_track = caller->AddVideoTrack("v"); + auto callee = CreatePeerConnectionWithAudioVideo(); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + ASSERT_TRUE( + caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); + + // Remove both tracks from caller. + caller->pc()->RemoveTrack(caller_audio_track); + caller->pc()->RemoveTrack(caller_video_track); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + ASSERT_TRUE( + caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); + + auto callee_voice = callee->media_engine()->GetVoiceChannel(0); + EXPECT_EQ(1u, callee_voice->send_streams().size()); + EXPECT_EQ(0u, callee_voice->recv_streams().size()); + + auto callee_video = callee->media_engine()->GetVideoChannel(0); + EXPECT_EQ(1u, callee_video->send_streams().size()); + EXPECT_EQ(0u, callee_video->recv_streams().size()); +} + +// Test that removing streams from a subsequent answer causes the send streams +// on the callee to be removed when applied locally. +TEST_F(PeerConnectionMediaTest, EmptyLocalAnswerRemovesSendStreams) { + auto caller = CreatePeerConnectionWithAudioVideo(); + auto callee = CreatePeerConnection(); + auto callee_audio_track = callee->AddAudioTrack("a"); + auto callee_video_track = callee->AddVideoTrack("v"); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + ASSERT_TRUE( + caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); + + // Remove both tracks from callee. + callee->pc()->RemoveTrack(callee_audio_track); + callee->pc()->RemoveTrack(callee_video_track); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + ASSERT_TRUE( + caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); + + auto callee_voice = callee->media_engine()->GetVoiceChannel(0); + EXPECT_EQ(0u, callee_voice->send_streams().size()); + EXPECT_EQ(1u, callee_voice->recv_streams().size()); + + auto callee_video = callee->media_engine()->GetVideoChannel(0); + EXPECT_EQ(0u, callee_video->send_streams().size()); + EXPECT_EQ(1u, callee_video->recv_streams().size()); +} + +// Test that a new stream in a subsequent offer causes a new receive stream to +// be created on the callee. +TEST_F(PeerConnectionMediaTest, NewStreamInRemoteOfferAddsRecvStreams) { + auto caller = CreatePeerConnectionWithAudioVideo(); + auto callee = CreatePeerConnection(); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + ASSERT_TRUE( + caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); + + // Add second set of tracks to the caller. + caller->AddAudioTrack("a2"); + caller->AddVideoTrack("v2"); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + ASSERT_TRUE( + caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); + + auto callee_voice = callee->media_engine()->GetVoiceChannel(0); + EXPECT_EQ(2u, callee_voice->recv_streams().size()); + auto callee_video = callee->media_engine()->GetVideoChannel(0); + EXPECT_EQ(2u, callee_video->recv_streams().size()); +} + +// Test that a new stream in a subsequent answer causes a new send stream to be +// created on the callee when added locally. +TEST_F(PeerConnectionMediaTest, NewStreamInLocalAnswerAddsSendStreams) { + auto caller = CreatePeerConnection(); + auto callee = CreatePeerConnectionWithAudioVideo(); + + RTCOfferAnswerOptions options; + options.offer_to_receive_audio = + RTCOfferAnswerOptions::kOfferToReceiveMediaTrue; + options.offer_to_receive_video = + RTCOfferAnswerOptions::kOfferToReceiveMediaTrue; + + ASSERT_TRUE( + callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal(options))); + ASSERT_TRUE( + caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); + + // Add second set of tracks to the callee. + callee->AddAudioTrack("a2"); + callee->AddVideoTrack("v2"); + + ASSERT_TRUE( + callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal(options))); + ASSERT_TRUE( + caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); + + auto callee_voice = callee->media_engine()->GetVoiceChannel(0); + EXPECT_EQ(2u, callee_voice->send_streams().size()); + auto callee_video = callee->media_engine()->GetVideoChannel(0); + EXPECT_EQ(2u, callee_video->send_streams().size()); +} + +// A PeerConnection with no local streams and no explicit answer constraints +// should not reject any offered media sections. +TEST_F(PeerConnectionMediaTest, + CreateAnswerWithNoStreamsAndDefaultOptionsDoesNotReject) { + auto caller = CreatePeerConnectionWithAudioVideo(); + auto callee = CreatePeerConnection(); + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + auto answer = callee->CreateAnswer(); + + const auto* audio_content = + cricket::GetFirstAudioContent(answer->description()); + ASSERT_TRUE(audio_content); + EXPECT_FALSE(audio_content->rejected); + + const auto* video_content = + cricket::GetFirstVideoContent(answer->description()); + ASSERT_TRUE(video_content); + EXPECT_FALSE(video_content->rejected); +} + +class PeerConnectionMediaOfferDirectionTest + : public PeerConnectionMediaTest, + public ::testing::WithParamInterface< + std::tuple> { + protected: + PeerConnectionMediaOfferDirectionTest() { + send_media_ = std::get<0>(GetParam()); + offer_to_receive_ = std::get<1>(GetParam()); + expected_direction_ = std::get<2>(GetParam()); + } + + bool send_media_; + int offer_to_receive_; + cricket::MediaContentDirection expected_direction_; +}; + +// Tests that the correct direction is set on the media description according +// to the presence of a local media track and the offer_to_receive setting. +TEST_P(PeerConnectionMediaOfferDirectionTest, VerifyDirection) { + auto caller = CreatePeerConnection(); + if (send_media_) { + caller->AddAudioTrack("a"); + } + + RTCOfferAnswerOptions options; + options.offer_to_receive_audio = offer_to_receive_; + auto offer = caller->CreateOffer(options); + + auto* media_content = GetMediaContent(offer.get(), cricket::CN_AUDIO); + if (expected_direction_ == cricket::MD_INACTIVE) { + EXPECT_FALSE(media_content); + } else { + EXPECT_EQ(expected_direction_, media_content->direction()); + } +} + +// Note that in these tests, MD_INACTIVE indicates that no media section is +// included in the offer, not that the media direction is inactive. +INSTANTIATE_TEST_CASE_P(PeerConnectionMediaTest, + PeerConnectionMediaOfferDirectionTest, + Values(std::make_tuple(false, -1, cricket::MD_INACTIVE), + std::make_tuple(false, 0, cricket::MD_INACTIVE), + std::make_tuple(false, 1, cricket::MD_RECVONLY), + std::make_tuple(true, -1, cricket::MD_SENDRECV), + std::make_tuple(true, 0, cricket::MD_SENDONLY), + std::make_tuple(true, 1, cricket::MD_SENDRECV))); + +class PeerConnectionMediaAnswerDirectionTest + : public PeerConnectionMediaTest, + public ::testing::WithParamInterface< + std::tuple> { + protected: + PeerConnectionMediaAnswerDirectionTest() { + offer_direction_ = std::get<0>(GetParam()); + send_media_ = std::get<1>(GetParam()); + offer_to_receive_ = std::get<2>(GetParam()); + } + + cricket::MediaContentDirection offer_direction_; + bool send_media_; + int offer_to_receive_; +}; + +// Tests that the direction in an answer is correct according to direction sent +// in the offer, the presence of a local media track on the receive side and the +// offer_to_receive setting. +TEST_P(PeerConnectionMediaAnswerDirectionTest, VerifyDirection) { + auto caller = CreatePeerConnection(); + caller->AddAudioTrack("a"); + + // Create the offer with an audio section and set its direction. + auto offer = caller->CreateOffer(); + cricket::GetFirstAudioContentDescription(offer->description()) + ->set_direction(offer_direction_); + + auto callee = CreatePeerConnection(); + if (send_media_) { + callee->AddAudioTrack("a"); + } + ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer))); + + // Create the answer according to the test parameters. + RTCOfferAnswerOptions options; + options.offer_to_receive_audio = offer_to_receive_; + auto answer = callee->CreateAnswer(options); + + // The expected direction in the answer is the intersection of each side's + // capability to send/recv media. + // For the offerer, the direction is given in the offer (offer_direction_). + // For the answerer, the direction has two components: + // 1. Send if the answerer has a local track to send. + // 2. Receive if the answerer has explicitly set the offer_to_receive to 1 or + // if it has been left as default. + auto offer_direction = + cricket::RtpTransceiverDirection::FromMediaContentDirection( + offer_direction_); + + // The negotiated components determine the direction set in the answer. + bool negotiate_send = (send_media_ && offer_direction.recv); + bool negotiate_recv = ((offer_to_receive_ != 0) && offer_direction.send); + + auto expected_direction = + cricket::RtpTransceiverDirection(negotiate_send, negotiate_recv) + .ToMediaContentDirection(); + EXPECT_EQ(expected_direction, + GetMediaContentDirection(answer.get(), cricket::CN_AUDIO)); +} + +// Tests that the media section is rejected if and only if the callee has no +// local media track and has set offer_to_receive to 0, no matter which +// direction the caller indicated in the offer. +TEST_P(PeerConnectionMediaAnswerDirectionTest, VerifyRejected) { + auto caller = CreatePeerConnection(); + caller->AddAudioTrack("a"); + + // Create the offer with an audio section and set its direction. + auto offer = caller->CreateOffer(); + cricket::GetFirstAudioContentDescription(offer->description()) + ->set_direction(offer_direction_); + + auto callee = CreatePeerConnection(); + if (send_media_) { + callee->AddAudioTrack("a"); + } + ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer))); + + // Create the answer according to the test parameters. + RTCOfferAnswerOptions options; + options.offer_to_receive_audio = offer_to_receive_; + auto answer = callee->CreateAnswer(options); + + // The media section is rejected if and only if offer_to_receive is explicitly + // set to 0 and there is no media to send. + auto* audio_content = cricket::GetFirstAudioContent(answer->description()); + ASSERT_TRUE(audio_content); + EXPECT_EQ((offer_to_receive_ == 0 && !send_media_), audio_content->rejected); +} + +INSTANTIATE_TEST_CASE_P(PeerConnectionMediaTest, + PeerConnectionMediaAnswerDirectionTest, + Combine(Values(cricket::MD_INACTIVE, + cricket::MD_SENDONLY, + cricket::MD_RECVONLY, + cricket::MD_SENDRECV), + Bool(), + Values(-1, 0, 1))); + +TEST_F(PeerConnectionMediaTest, OfferHasDifferentDirectionForAudioVideo) { + auto caller = CreatePeerConnection(); + caller->AddVideoTrack("v"); + + RTCOfferAnswerOptions options; + options.offer_to_receive_audio = 1; + options.offer_to_receive_video = 0; + auto offer = caller->CreateOffer(options); + + EXPECT_EQ(cricket::MD_RECVONLY, + GetMediaContentDirection(offer.get(), cricket::CN_AUDIO)); + EXPECT_EQ(cricket::MD_SENDONLY, + GetMediaContentDirection(offer.get(), cricket::CN_VIDEO)); +} + +TEST_F(PeerConnectionMediaTest, AnswerHasDifferentDirectionsForAudioVideo) { + auto caller = CreatePeerConnectionWithAudioVideo(); + auto callee = CreatePeerConnection(); + callee->AddVideoTrack("v"); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + + RTCOfferAnswerOptions options; + options.offer_to_receive_audio = 1; + options.offer_to_receive_video = 0; + auto answer = callee->CreateAnswer(options); + + EXPECT_EQ(cricket::MD_RECVONLY, + GetMediaContentDirection(answer.get(), cricket::CN_AUDIO)); + EXPECT_EQ(cricket::MD_SENDONLY, + GetMediaContentDirection(answer.get(), cricket::CN_VIDEO)); +} + +void AddComfortNoiseCodecsToSend(cricket::FakeMediaEngine* media_engine) { + const cricket::AudioCodec kComfortNoiseCodec8k(102, "CN", 8000, 0, 1); + const cricket::AudioCodec kComfortNoiseCodec16k(103, "CN", 16000, 0, 1); + + auto codecs = media_engine->audio_send_codecs(); + codecs.push_back(kComfortNoiseCodec8k); + codecs.push_back(kComfortNoiseCodec16k); + media_engine->SetAudioCodecs(codecs); +} + +bool HasAnyComfortNoiseCodecs(const cricket::SessionDescription* desc) { + const auto* audio_desc = cricket::GetFirstAudioContentDescription(desc); + for (const auto& codec : audio_desc->codecs()) { + if (codec.name == "CN") { + return true; + } + } + return false; +} + +TEST_F(PeerConnectionMediaTest, + CreateOfferWithNoVoiceActivityDetectionIncludesNoComfortNoiseCodecs) { + auto caller = CreatePeerConnectionWithAudioVideo(); + AddComfortNoiseCodecsToSend(caller->media_engine()); + + RTCOfferAnswerOptions options; + options.voice_activity_detection = false; + auto offer = caller->CreateOffer(options); + + EXPECT_FALSE(HasAnyComfortNoiseCodecs(offer->description())); +} + +TEST_F(PeerConnectionMediaTest, + CreateAnswerWithNoVoiceActivityDetectionIncludesNoComfortNoiseCodecs) { + auto caller = CreatePeerConnectionWithAudioVideo(); + AddComfortNoiseCodecsToSend(caller->media_engine()); + auto callee = CreatePeerConnectionWithAudioVideo(); + AddComfortNoiseCodecsToSend(callee->media_engine()); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + + RTCOfferAnswerOptions options; + options.voice_activity_detection = false; + auto answer = callee->CreateAnswer(options); + + EXPECT_FALSE(HasAnyComfortNoiseCodecs(answer->description())); +} + +// The following test group verifies that we reject answers with invalid media +// sections as per RFC 3264. + +class PeerConnectionMediaInvalidMediaTest + : public PeerConnectionMediaTest, + public ::testing::WithParamInterface< + std::tuple, + std::string>> { + protected: + PeerConnectionMediaInvalidMediaTest() { + mutator_ = std::get<1>(GetParam()); + expected_error_ = std::get<2>(GetParam()); + } + + std::function mutator_; + std::string expected_error_; +}; + +TEST_P(PeerConnectionMediaInvalidMediaTest, FailToSetRemoteAnswer) { + auto caller = CreatePeerConnectionWithAudioVideo(); + auto callee = CreatePeerConnectionWithAudioVideo(); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + + auto answer = callee->CreateAnswer(); + mutator_(answer->description()); + + std::string error; + ASSERT_FALSE(caller->SetRemoteDescription(std::move(answer), &error)); + EXPECT_EQ("Failed to set remote answer SDP: " + expected_error_, error); +} + +TEST_P(PeerConnectionMediaInvalidMediaTest, FailToSetLocalAnswer) { + auto caller = CreatePeerConnectionWithAudioVideo(); + auto callee = CreatePeerConnectionWithAudioVideo(); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + + auto answer = callee->CreateAnswer(); + mutator_(answer->description()); + + std::string error; + ASSERT_FALSE(callee->SetLocalDescription(std::move(answer), &error)); + EXPECT_EQ("Failed to set local answer SDP: " + expected_error_, error); +} + +void RemoveVideoContent(cricket::SessionDescription* desc) { + auto content_name = cricket::GetFirstVideoContent(desc)->name; + desc->RemoveContentByName(content_name); + desc->RemoveTransportInfoByName(content_name); +} + +void RenameVideoContent(cricket::SessionDescription* desc) { + auto* video_content = cricket::GetFirstVideoContent(desc); + auto* transport_info = desc->GetTransportInfoByName(video_content->name); + video_content->name = "video_renamed"; + transport_info->content_name = video_content->name; +} + +void ReverseMediaContent(cricket::SessionDescription* desc) { + std::reverse(desc->contents().begin(), desc->contents().end()); + std::reverse(desc->transport_infos().begin(), desc->transport_infos().end()); +} + +void ChangeMediaTypeAudioToVideo(cricket::SessionDescription* desc) { + desc->RemoveContentByName(cricket::CN_AUDIO); + auto* video_content = desc->GetContentByName(cricket::CN_VIDEO); + desc->AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP, + video_content->description->Copy()); +} + +constexpr char kMLinesOutOfOrder[] = + "The order of m-lines in answer doesn't match order in offer. Rejecting " + "answer."; + +INSTANTIATE_TEST_CASE_P( + PeerConnectionMediaTest, + PeerConnectionMediaInvalidMediaTest, + Values( + std::make_tuple("remove video", RemoveVideoContent, kMLinesOutOfOrder), + std::make_tuple("rename video", RenameVideoContent, kMLinesOutOfOrder), + std::make_tuple("reverse media sections", + ReverseMediaContent, + kMLinesOutOfOrder), + std::make_tuple("change audio type to video type", + ChangeMediaTypeAudioToVideo, + kMLinesOutOfOrder))); + +// Test that the correct media engine send/recv streams are created when doing +// a series of offer/answers where audio/video are both sent, then audio is +// rejected, then both audio/video sent again. +TEST_F(PeerConnectionMediaTest, TestAVOfferWithAudioOnlyAnswer) { + RTCOfferAnswerOptions options_reject_video; + options_reject_video.offer_to_receive_audio = + RTCOfferAnswerOptions::kOfferToReceiveMediaTrue; + options_reject_video.offer_to_receive_video = 0; + + auto caller = CreatePeerConnection(); + caller->AddAudioTrack("a"); + caller->AddVideoTrack("v"); + auto callee = CreatePeerConnection(); + + // Caller initially offers to send/recv audio and video. + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + // Callee accepts the audio as recv only but rejects the video. + ASSERT_TRUE(caller->SetRemoteDescription( + callee->CreateAnswerAndSetAsLocal(options_reject_video))); + + auto caller_voice = caller->media_engine()->GetVoiceChannel(0); + ASSERT_TRUE(caller_voice); + EXPECT_EQ(0u, caller_voice->recv_streams().size()); + EXPECT_EQ(1u, caller_voice->send_streams().size()); + auto caller_video = caller->media_engine()->GetVideoChannel(0); + EXPECT_FALSE(caller_video); + + // Callee adds its own audio/video stream and offers to receive audio/video + // too. + callee->AddAudioTrack("a"); + auto callee_video_track = callee->AddVideoTrack("v"); + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + ASSERT_TRUE( + caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); + + auto callee_voice = callee->media_engine()->GetVoiceChannel(0); + ASSERT_TRUE(callee_voice); + EXPECT_EQ(1u, callee_voice->recv_streams().size()); + EXPECT_EQ(1u, callee_voice->send_streams().size()); + auto callee_video = callee->media_engine()->GetVideoChannel(0); + ASSERT_TRUE(callee_video); + EXPECT_EQ(1u, callee_video->recv_streams().size()); + EXPECT_EQ(1u, callee_video->send_streams().size()); + + // Callee removes video but keeps audio and rejects the video once again. + callee->pc()->RemoveTrack(callee_video_track); + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + ASSERT_TRUE( + callee->SetLocalDescription(callee->CreateAnswer(options_reject_video))); + + callee_voice = callee->media_engine()->GetVoiceChannel(0); + ASSERT_TRUE(callee_voice); + EXPECT_EQ(1u, callee_voice->recv_streams().size()); + EXPECT_EQ(1u, callee_voice->send_streams().size()); + callee_video = callee->media_engine()->GetVideoChannel(0); + EXPECT_FALSE(callee_video); +} + +// Test that the correct media engine send/recv streams are created when doing +// a series of offer/answers where audio/video are both sent, then video is +// rejected, then both audio/video sent again. +TEST_F(PeerConnectionMediaTest, TestAVOfferWithVideoOnlyAnswer) { + // Disable the bundling here. If the media is bundled on audio + // transport, then we can't reject the audio because switching the bundled + // transport is not currently supported. + // (https://bugs.chromium.org/p/webrtc/issues/detail?id=6704) + RTCOfferAnswerOptions options_no_bundle; + options_no_bundle.use_rtp_mux = false; + RTCOfferAnswerOptions options_reject_audio = options_no_bundle; + options_reject_audio.offer_to_receive_audio = 0; + options_reject_audio.offer_to_receive_video = + RTCOfferAnswerOptions::kMaxOfferToReceiveMedia; + + auto caller = CreatePeerConnection(); + caller->AddAudioTrack("a"); + caller->AddVideoTrack("v"); + auto callee = CreatePeerConnection(); + + // Caller initially offers to send/recv audio and video. + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + // Callee accepts the video as recv only but rejects the audio. + ASSERT_TRUE(caller->SetRemoteDescription( + callee->CreateAnswerAndSetAsLocal(options_reject_audio))); + + auto caller_voice = caller->media_engine()->GetVoiceChannel(0); + EXPECT_FALSE(caller_voice); + auto caller_video = caller->media_engine()->GetVideoChannel(0); + ASSERT_TRUE(caller_video); + EXPECT_EQ(0u, caller_video->recv_streams().size()); + EXPECT_EQ(1u, caller_video->send_streams().size()); + + // Callee adds its own audio/video stream and offers to receive audio/video + // too. + auto callee_audio_track = callee->AddAudioTrack("a"); + callee->AddVideoTrack("v"); + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + ASSERT_TRUE(caller->SetRemoteDescription( + callee->CreateAnswerAndSetAsLocal(options_no_bundle))); + + auto callee_voice = callee->media_engine()->GetVoiceChannel(0); + ASSERT_TRUE(callee_voice); + EXPECT_EQ(1u, callee_voice->recv_streams().size()); + EXPECT_EQ(1u, callee_voice->send_streams().size()); + auto callee_video = callee->media_engine()->GetVideoChannel(0); + ASSERT_TRUE(callee_video); + EXPECT_EQ(1u, callee_video->recv_streams().size()); + EXPECT_EQ(1u, callee_video->send_streams().size()); + + // Callee removes audio but keeps video and rejects the audio once again. + callee->pc()->RemoveTrack(callee_audio_track); + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + ASSERT_TRUE( + callee->SetLocalDescription(callee->CreateAnswer(options_reject_audio))); + + callee_voice = callee->media_engine()->GetVoiceChannel(0); + EXPECT_FALSE(callee_voice); + callee_video = callee->media_engine()->GetVideoChannel(0); + ASSERT_TRUE(callee_video); + EXPECT_EQ(1u, callee_video->recv_streams().size()); + EXPECT_EQ(1u, callee_video->send_streams().size()); +} + +// Tests that if the underlying video encoder fails to be initialized (signaled +// by failing to set send codecs), the PeerConnection signals the error to the +// client. +TEST_F(PeerConnectionMediaTest, MediaEngineErrorPropagatedToClients) { + auto caller = CreatePeerConnectionWithAudioVideo(); + auto callee = CreatePeerConnectionWithAudioVideo(); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + + auto video_channel = caller->media_engine()->GetVideoChannel(0); + video_channel->set_fail_set_send_codecs(true); + + std::string error; + ASSERT_FALSE(caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal(), + &error)); + EXPECT_EQ( + "Failed to set remote answer SDP: Session error code: ERROR_CONTENT. " + "Session error description: Failed to set remote video description send " + "parameters..", + error); +} + +// Tests that if the underlying video encoder fails once then subsequent +// attempts at setting the local/remote description will also fail, even if +// SetSendCodecs no longer fails. +TEST_F(PeerConnectionMediaTest, + FailToApplyDescriptionIfVideoEncoderHasEverFailed) { + auto caller = CreatePeerConnectionWithAudioVideo(); + auto callee = CreatePeerConnectionWithAudioVideo(); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + + auto video_channel = caller->media_engine()->GetVideoChannel(0); + video_channel->set_fail_set_send_codecs(true); + + EXPECT_FALSE( + caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); + + video_channel->set_fail_set_send_codecs(false); + + EXPECT_FALSE(caller->SetRemoteDescription(callee->CreateAnswer())); + EXPECT_FALSE(caller->SetLocalDescription(caller->CreateOffer())); +} + +void RenameContent(cricket::SessionDescription* desc, + const std::string& old_name, + const std::string& new_name) { + auto* content = desc->GetContentByName(old_name); + RTC_DCHECK(content); + content->name = new_name; + auto* transport = desc->GetTransportInfoByName(old_name); + RTC_DCHECK(transport); + transport->content_name = new_name; +} + +// Tests that an answer responds with the same MIDs as the offer. +TEST_F(PeerConnectionMediaTest, AnswerHasSameMidsAsOffer) { + const std::string kAudioMid = "not default1"; + const std::string kVideoMid = "not default2"; + + auto caller = CreatePeerConnectionWithAudioVideo(); + auto callee = CreatePeerConnectionWithAudioVideo(); + + auto offer = caller->CreateOffer(); + RenameContent(offer->description(), cricket::CN_AUDIO, kAudioMid); + RenameContent(offer->description(), cricket::CN_VIDEO, kVideoMid); + ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer))); + + auto answer = callee->CreateAnswer(); + EXPECT_EQ(kAudioMid, + cricket::GetFirstAudioContent(answer->description())->name); + EXPECT_EQ(kVideoMid, + cricket::GetFirstVideoContent(answer->description())->name); +} + +// Test that if the callee creates a re-offer, the MIDs are the same as the +// original offer. +TEST_F(PeerConnectionMediaTest, ReOfferHasSameMidsAsFirstOffer) { + const std::string kAudioMid = "not default1"; + const std::string kVideoMid = "not default2"; + + auto caller = CreatePeerConnectionWithAudioVideo(); + auto callee = CreatePeerConnectionWithAudioVideo(); + + auto offer = caller->CreateOffer(); + RenameContent(offer->description(), cricket::CN_AUDIO, kAudioMid); + RenameContent(offer->description(), cricket::CN_VIDEO, kVideoMid); + ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer))); + ASSERT_TRUE(callee->SetLocalDescription(callee->CreateAnswer())); + + auto reoffer = callee->CreateOffer(); + EXPECT_EQ(kAudioMid, + cricket::GetFirstAudioContent(reoffer->description())->name); + EXPECT_EQ(kVideoMid, + cricket::GetFirstVideoContent(reoffer->description())->name); +} + +TEST_F(PeerConnectionMediaTest, + CombinedAudioVideoBweConfigPropagatedToMediaEngine) { + RTCConfiguration config; + config.combined_audio_video_bwe.emplace(true); + auto caller = CreatePeerConnectionWithAudioVideo(config); + + ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer())); + + auto caller_voice = caller->media_engine()->GetVoiceChannel(0); + ASSERT_TRUE(caller_voice); + const cricket::AudioOptions& audio_options = caller_voice->options(); + EXPECT_EQ(config.combined_audio_video_bwe, + audio_options.combined_audio_video_bwe); +} + +} // namespace webrtc diff --git a/pc/peerconnection_signaling_unittest.cc b/pc/peerconnection_signaling_unittest.cc new file mode 100644 index 0000000000..caaac4c0ba --- /dev/null +++ b/pc/peerconnection_signaling_unittest.cc @@ -0,0 +1,501 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +// This file contains tests that check the PeerConnection's signaling state +// machine, as well as tests that check basic, media-agnostic aspects of SDP. + +#include + +#include "api/audio_codecs/builtin_audio_decoder_factory.h" +#include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "api/peerconnectionproxy.h" +#include "pc/peerconnection.h" +#include "pc/peerconnectionwrapper.h" +#include "pc/sdputils.h" +#ifdef WEBRTC_ANDROID +#include "pc/test/androidtestinitializer.h" +#endif +#include "pc/test/fakeaudiocapturemodule.h" +#include "pc/test/fakertccertificategenerator.h" +#include "rtc_base/gunit.h" +#include "rtc_base/ptr_util.h" +#include "rtc_base/stringutils.h" +#include "rtc_base/virtualsocketserver.h" +#include "test/gmock.h" + +namespace webrtc { + +using SignalingState = PeerConnectionInterface::SignalingState; +using RTCConfiguration = PeerConnectionInterface::RTCConfiguration; +using RTCOfferAnswerOptions = PeerConnectionInterface::RTCOfferAnswerOptions; +using ::testing::Bool; +using ::testing::Combine; +using ::testing::Values; + +class PeerConnectionWrapperForSignalingTest : public PeerConnectionWrapper { + public: + using PeerConnectionWrapper::PeerConnectionWrapper; + + bool initial_offerer() { + return GetInternalPeerConnection()->initial_offerer(); + } + + PeerConnection* GetInternalPeerConnection() { + auto* pci = reinterpret_cast< + PeerConnectionProxyWithInternal*>(pc()); + return reinterpret_cast(pci->internal()); + } +}; + +class PeerConnectionSignalingTest : public ::testing::Test { + protected: + typedef std::unique_ptr WrapperPtr; + + PeerConnectionSignalingTest() + : vss_(new rtc::VirtualSocketServer()), main_(vss_.get()) { +#ifdef WEBRTC_ANDROID + InitializeAndroidObjects(); +#endif + pc_factory_ = CreatePeerConnectionFactory( + rtc::Thread::Current(), rtc::Thread::Current(), rtc::Thread::Current(), + FakeAudioCaptureModule::Create(), CreateBuiltinAudioEncoderFactory(), + CreateBuiltinAudioDecoderFactory(), nullptr, nullptr); + } + + WrapperPtr CreatePeerConnection() { + return CreatePeerConnection(RTCConfiguration()); + } + + WrapperPtr CreatePeerConnection(const RTCConfiguration& config) { + auto observer = rtc::MakeUnique(); + auto pc = pc_factory_->CreatePeerConnection(config, nullptr, nullptr, + observer.get()); + if (!pc) { + return nullptr; + } + + return rtc::MakeUnique( + pc_factory_, pc, std::move(observer)); + } + + // Accepts the same arguments as CreatePeerConnection and adds default audio + // and video tracks. + template + WrapperPtr CreatePeerConnectionWithAudioVideo(Args&&... args) { + auto wrapper = CreatePeerConnection(std::forward(args)...); + if (!wrapper) { + return nullptr; + } + wrapper->AddAudioTrack("a"); + wrapper->AddVideoTrack("v"); + return wrapper; + } + + std::unique_ptr vss_; + rtc::AutoSocketServerThread main_; + rtc::scoped_refptr pc_factory_; +}; + +TEST_F(PeerConnectionSignalingTest, SetLocalOfferTwiceWorks) { + auto caller = CreatePeerConnection(); + + EXPECT_TRUE(caller->SetLocalDescription(caller->CreateOffer())); + EXPECT_TRUE(caller->SetLocalDescription(caller->CreateOffer())); +} + +TEST_F(PeerConnectionSignalingTest, SetRemoteOfferTwiceWorks) { + auto caller = CreatePeerConnection(); + auto callee = CreatePeerConnection(); + + EXPECT_TRUE(callee->SetRemoteDescription(caller->CreateOffer())); + EXPECT_TRUE(callee->SetRemoteDescription(caller->CreateOffer())); +} + +TEST_F(PeerConnectionSignalingTest, FailToSetNullLocalDescription) { + auto caller = CreatePeerConnection(); + std::string error; + ASSERT_FALSE(caller->SetLocalDescription(nullptr, &error)); + EXPECT_EQ("SessionDescription is NULL.", error); +} + +TEST_F(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 PeerConnectionSignalingTest, + public ::testing::WithParamInterface> { + protected: + RTCConfiguration GetConfig() { + RTCConfiguration config; + config.certificates.push_back( + FakeRTCCertificateGenerator::GenerateCertificate()); + return config; + } + + WrapperPtr CreatePeerConnectionInState(SignalingState state) { + return CreatePeerConnectionInState(std::make_tuple(state, false)); + } + + WrapperPtr CreatePeerConnectionInState( + std::tuple state_tuple) { + SignalingState state = std::get<0>(state_tuple); + bool closed = std::get<1>(state_tuple); + + auto wrapper = CreatePeerConnectionWithAudioVideo(GetConfig()); + switch (state) { + case SignalingState::kStable: { + break; + } + case SignalingState::kHaveLocalOffer: { + wrapper->SetLocalDescription(wrapper->CreateOffer()); + break; + } + case SignalingState::kHaveLocalPrAnswer: { + auto caller = CreatePeerConnectionWithAudioVideo(GetConfig()); + wrapper->SetRemoteDescription(caller->CreateOffer()); + auto answer = wrapper->CreateAnswer(); + wrapper->SetLocalDescription(CloneSessionDescriptionAsType( + answer.get(), SessionDescriptionInterface::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(), SessionDescriptionInterface::kPrAnswer)); + break; + } + case SignalingState::kClosed: { + RTC_NOTREACHED() << "Set the second member of the tuple to true to " + "achieve a closed state from an existing, valid " + "state."; + } + } + + RTC_DCHECK_EQ(state, wrapper->pc()->signaling_state()); + + if (closed) { + wrapper->pc()->Close(); + RTC_DCHECK_EQ(SignalingState::kClosed, wrapper->signaling_state()); + } + + return wrapper; + } +}; + +::testing::AssertionResult AssertStartsWith(const char* str_expr, + const char* prefix_expr, + const std::string& str, + const std::string& prefix) { + if (rtc::starts_with(str.c_str(), prefix.c_str())) { + return ::testing::AssertionSuccess(); + } else { + return ::testing::AssertionFailure() + << str_expr << "\nwhich is\n\"" << str << "\"\ndoes not start with\n" + << prefix_expr << "\nwhich is\n\"" << prefix << "\""; + } +} + +TEST_P(PeerConnectionSignalingStateTest, CreateOffer) { + auto wrapper = CreatePeerConnectionInState(GetParam()); + 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 = CreatePeerConnectionInState(GetParam()); + 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)); + if (wrapper->signaling_state() == SignalingState::kClosed) { + EXPECT_PRED_FORMAT2(AssertStartsWith, error, + "CreateAnswer called when PeerConnection is closed."); + } else { + EXPECT_PRED_FORMAT2(AssertStartsWith, error, + "CreateAnswer called without remote offer."); + } + } +} + +TEST_P(PeerConnectionSignalingStateTest, SetLocalOffer) { + auto wrapper = CreatePeerConnectionInState(GetParam()); + 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 = CreatePeerConnectionInState(GetParam()); + 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 = CreatePeerConnectionInState(GetParam()); + 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 = CreatePeerConnectionInState(GetParam()); + 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 = CreatePeerConnectionInState(GetParam()); + 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 = CreatePeerConnectionInState(GetParam()); + if (wrapper->signaling_state() == SignalingState::kHaveLocalOffer || + wrapper->signaling_state() == SignalingState::kHaveRemotePrAnswer) { + EXPECT_TRUE(wrapper->SetRemoteDescription(std::move(answer))); + } else { + std::string error; + ASSERT_FALSE(wrapper->SetRemoteDescription(std::move(answer), &error)); + EXPECT_PRED_FORMAT2( + AssertStartsWith, error, + "Failed to set remote answer SDP: Called in wrong state:"); + } +} + +INSTANTIATE_TEST_CASE_P(PeerConnectionSignalingTest, + PeerConnectionSignalingStateTest, + Combine(Values(SignalingState::kStable, + SignalingState::kHaveLocalOffer, + SignalingState::kHaveLocalPrAnswer, + SignalingState::kHaveRemoteOffer, + SignalingState::kHaveRemotePrAnswer), + Bool())); + +TEST_F(PeerConnectionSignalingTest, + CreateAnswerSucceedsIfStableAndRemoteDescriptionIsOffer) { + auto caller = CreatePeerConnection(); + auto callee = CreatePeerConnection(); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + ASSERT_TRUE( + caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); + + ASSERT_EQ(SignalingState::kStable, callee->signaling_state()); + EXPECT_TRUE(callee->CreateAnswer()); +} + +TEST_F(PeerConnectionSignalingTest, + CreateAnswerFailsIfStableButRemoteDescriptionIsAnswer) { + auto caller = CreatePeerConnection(); + auto callee = CreatePeerConnection(); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + ASSERT_TRUE( + caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); + + ASSERT_EQ(SignalingState::kStable, caller->signaling_state()); + std::string error; + ASSERT_FALSE(caller->CreateAnswer(RTCOfferAnswerOptions(), &error)); + EXPECT_EQ("CreateAnswer called without remote offer.", error); +} + +// 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_F(PeerConnectionSignalingTest, + SessionVersionIncrementedInSubsequentDifferentOffer) { + auto caller = CreatePeerConnection(); + auto callee = CreatePeerConnection(); + + auto original_offer = caller->CreateOfferAndSetAsLocal(); + const std::string original_id = original_offer->session_id(); + const std::string original_version = original_offer->session_version(); + + ASSERT_TRUE(callee->SetRemoteDescription(std::move(original_offer))); + ASSERT_TRUE(caller->SetRemoteDescription(callee->CreateAnswer())); + + // Add track to get a different offer. + caller->AddAudioTrack("a"); + + auto later_offer = caller->CreateOffer(); + + EXPECT_EQ(original_id, later_offer->session_id()); + EXPECT_LT(rtc::FromString(original_version), + rtc::FromString(later_offer->session_version())); +} +TEST_F(PeerConnectionSignalingTest, + SessionVersionIncrementedInSubsequentDifferentAnswer) { + auto caller = CreatePeerConnection(); + auto callee = CreatePeerConnection(); + + ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); + + auto original_answer = callee->CreateAnswerAndSetAsLocal(); + const std::string original_id = original_answer->session_id(); + const std::string original_version = original_answer->session_version(); + + // Add track to get a different answer. + callee->AddAudioTrack("a"); + + auto later_answer = callee->CreateAnswer(); + + EXPECT_EQ(original_id, later_answer->session_id()); + EXPECT_LT(rtc::FromString(original_version), + rtc::FromString(later_answer->session_version())); +} + +TEST_F(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_F(PeerConnectionSignalingTest, CreateOffersAndShutdown) { + auto caller = CreatePeerConnection(); + + RTCOfferAnswerOptions options; + options.offer_to_receive_audio = + RTCOfferAnswerOptions::kOfferToReceiveMediaTrue; + + rtc::scoped_refptr observers[100]; + for (auto& observer : observers) { + observer = + new rtc::RefCountedObject(); + caller->pc()->CreateOffer(observer, options); + } + + // Destroy the PeerConnection. + caller.reset(nullptr); + + for (auto& observer : observers) { + // We expect to have received a notification now even if the PeerConnection + // was terminated. The offer creation may or may not have succeeded, but we + // must have received a notification. + EXPECT_TRUE(observer->called()); + } +} + +} // namespace webrtc diff --git a/pc/peerconnectioninterface_unittest.cc b/pc/peerconnectioninterface_unittest.cc index 99f3301f82..a8b4f724af 100644 --- a/pc/peerconnectioninterface_unittest.cc +++ b/pc/peerconnectioninterface_unittest.cc @@ -2517,9 +2517,9 @@ TEST_F(PeerConnectionInterfaceTest, CloseAndTestMethods) { EXPECT_TRUE(pc_->remote_description() != NULL); std::unique_ptr offer; - EXPECT_TRUE(DoCreateOffer(&offer, nullptr)); + EXPECT_FALSE(DoCreateOffer(&offer, nullptr)); std::unique_ptr answer; - EXPECT_TRUE(DoCreateAnswer(&answer, nullptr)); + EXPECT_FALSE(DoCreateAnswer(&answer, nullptr)); std::string sdp; ASSERT_TRUE(pc_->remote_description()->ToString(&sdp)); @@ -3558,32 +3558,6 @@ TEST_F(PeerConnectionInterfaceTest, CreateOfferWithVideoOnlyOptions) { EXPECT_NE(nullptr, GetFirstVideoContent(offer->description())); } -// Test that if |voice_activity_detection| is false, no CN codec is added to the -// offer. -TEST_F(PeerConnectionInterfaceTest, CreateOfferWithVADOptions) { - RTCOfferAnswerOptions rtc_options; - rtc_options.offer_to_receive_audio = 1; - rtc_options.offer_to_receive_video = 0; - - std::unique_ptr offer; - CreatePeerConnection(); - offer = CreateOfferWithOptions(rtc_options); - ASSERT_TRUE(offer); - const cricket::ContentInfo* audio_content = - offer->description()->GetContentByName(cricket::CN_AUDIO); - ASSERT_TRUE(audio_content); - // |voice_activity_detection| is true by default. - EXPECT_TRUE(HasCNCodecs(audio_content)); - - rtc_options.voice_activity_detection = false; - CreatePeerConnection(); - offer = CreateOfferWithOptions(rtc_options); - ASSERT_TRUE(offer); - audio_content = offer->description()->GetContentByName(cricket::CN_AUDIO); - ASSERT_TRUE(audio_content); - EXPECT_FALSE(HasCNCodecs(audio_content)); -} - // Test that no media content will be added to the offer if using default // RTCOfferAnswerOptions. TEST_F(PeerConnectionInterfaceTest, CreateOfferWithDefaultOfferAnswerOptions) { @@ -3664,42 +3638,6 @@ TEST_F(PeerConnectionInterfaceTest, CreateOfferWithRtpMux) { EXPECT_FALSE(offer->description()->HasGroup(cricket::GROUP_TYPE_BUNDLE)); } -// If SetMandatoryReceiveAudio(false) and SetMandatoryReceiveVideo(false) are -// called for the answer constraints, but an audio and a video section were -// offered, there will still be an audio and a video section in the answer. -TEST_F(PeerConnectionInterfaceTest, - RejectAudioAndVideoInAnswerWithConstraints) { - // Offer both audio and video. - RTCOfferAnswerOptions rtc_offer_options; - rtc_offer_options.offer_to_receive_audio = 1; - rtc_offer_options.offer_to_receive_video = 1; - - CreatePeerConnection(); - std::unique_ptr offer; - CreateOfferWithOptionsAsRemoteDescription(&offer, rtc_offer_options); - EXPECT_NE(nullptr, GetFirstAudioContent(offer->description())); - EXPECT_NE(nullptr, GetFirstVideoContent(offer->description())); - - // Since an offer has been created with both audio and video, - // Answers will contain the media types that exist in the offer regardless of - // the value of |answer_options.has_audio| and |answer_options.has_video|. - FakeConstraints answer_c; - // Reject both audio and video. - answer_c.SetMandatoryReceiveAudio(false); - answer_c.SetMandatoryReceiveVideo(false); - - std::unique_ptr answer; - ASSERT_TRUE(DoCreateAnswer(&answer, &answer_c)); - const cricket::ContentInfo* audio_content = - GetFirstAudioContent(answer->description()); - const cricket::ContentInfo* video_content = - GetFirstVideoContent(answer->description()); - ASSERT_NE(nullptr, audio_content); - ASSERT_NE(nullptr, video_content); - EXPECT_TRUE(audio_content->rejected); - EXPECT_TRUE(video_content->rejected); -} - // This test ensures OnRenegotiationNeeded is called when we add track with // MediaStream -> AddTrack in the same way it is called when we add track with // PeerConnection -> AddTrack. @@ -3734,52 +3672,6 @@ TEST_F(PeerConnectionInterfaceTest, MediaStreamAddTrackRemoveTrackRenegotiate) { observer_.renegotiation_needed_ = false; } -// Tests that creating answer would fail gracefully without being crashed if the -// remote description is unset. -TEST_F(PeerConnectionInterfaceTest, CreateAnswerWithoutRemoteDescription) { - CreatePeerConnection(); - // Creating answer fails because the remote description is unset. - std::unique_ptr answer; - EXPECT_FALSE(DoCreateAnswer(&answer, nullptr)); - - // Createing answer succeeds when the remote description is set. - CreateOfferAsRemoteDescription(); - EXPECT_TRUE(DoCreateAnswer(&answer, nullptr)); -} - -// Test that an error is returned if a description is applied that doesn't -// respect the order of existing media sections. -TEST_F(PeerConnectionInterfaceTest, - MediaSectionOrderEnforcedForSubsequentOffers) { - CreatePeerConnection(); - FakeConstraints constraints; - constraints.SetMandatoryReceiveAudio(true); - constraints.SetMandatoryReceiveVideo(true); - std::unique_ptr offer; - ASSERT_TRUE(DoCreateOffer(&offer, &constraints)); - EXPECT_TRUE(DoSetRemoteDescription(std::move(offer))); - - std::unique_ptr answer; - ASSERT_TRUE(DoCreateAnswer(&answer, nullptr)); - EXPECT_TRUE(DoSetLocalDescription(std::move(answer))); - - // A remote offer with different m=line order should be rejected. - ASSERT_TRUE(DoCreateOffer(&offer, &constraints)); - std::reverse(offer->description()->contents().begin(), - offer->description()->contents().end()); - std::reverse(offer->description()->transport_infos().begin(), - offer->description()->transport_infos().end()); - EXPECT_FALSE(DoSetRemoteDescription(std::move(offer))); - - // A subsequent local offer with different m=line order should be rejected. - ASSERT_TRUE(DoCreateOffer(&offer, &constraints)); - std::reverse(offer->description()->contents().begin(), - offer->description()->contents().end()); - std::reverse(offer->description()->transport_infos().begin(), - offer->description()->transport_infos().end()); - EXPECT_FALSE(DoSetLocalDescription(std::move(offer))); -} - class PeerConnectionMediaConfigTest : public testing::Test { protected: void SetUp() override { diff --git a/pc/peerconnectionwrapper.cc b/pc/peerconnectionwrapper.cc index dd114607d0..9be93096a3 100644 --- a/pc/peerconnectionwrapper.cc +++ b/pc/peerconnectionwrapper.cc @@ -30,7 +30,7 @@ PeerConnectionWrapper::PeerConnectionWrapper( rtc::scoped_refptr pc_factory, rtc::scoped_refptr pc, std::unique_ptr observer) - : pc_factory_(pc_factory), pc_(pc), observer_(std::move(observer)) { + : pc_factory_(pc_factory), observer_(std::move(observer)), pc_(pc) { RTC_DCHECK(pc_factory_); RTC_DCHECK(pc_); RTC_DCHECK(observer_); @@ -57,15 +57,25 @@ PeerConnectionWrapper::CreateOffer() { } std::unique_ptr PeerConnectionWrapper::CreateOffer( - const PeerConnectionInterface::RTCOfferAnswerOptions& options) { - return CreateSdp([this, options](CreateSessionDescriptionObserver* observer) { - pc()->CreateOffer(observer, options); - }); + const PeerConnectionInterface::RTCOfferAnswerOptions& options, + std::string* error_out) { + return CreateSdp( + [this, options](CreateSessionDescriptionObserver* observer) { + pc()->CreateOffer(observer, options); + }, + error_out); } std::unique_ptr PeerConnectionWrapper::CreateOfferAndSetAsLocal() { - auto offer = CreateOffer(); + return CreateOfferAndSetAsLocal( + PeerConnectionInterface::RTCOfferAnswerOptions()); +} + +std::unique_ptr +PeerConnectionWrapper::CreateOfferAndSetAsLocal( + const PeerConnectionInterface::RTCOfferAnswerOptions& options) { + auto offer = CreateOffer(options); if (!offer) { return nullptr; } @@ -80,15 +90,25 @@ PeerConnectionWrapper::CreateAnswer() { std::unique_ptr PeerConnectionWrapper::CreateAnswer( - const PeerConnectionInterface::RTCOfferAnswerOptions& options) { - return CreateSdp([this, options](CreateSessionDescriptionObserver* observer) { - pc()->CreateAnswer(observer, options); - }); + const PeerConnectionInterface::RTCOfferAnswerOptions& options, + std::string* error_out) { + return CreateSdp( + [this, options](CreateSessionDescriptionObserver* observer) { + pc()->CreateAnswer(observer, options); + }, + error_out); } std::unique_ptr PeerConnectionWrapper::CreateAnswerAndSetAsLocal() { - auto answer = CreateAnswer(); + return CreateAnswerAndSetAsLocal( + PeerConnectionInterface::RTCOfferAnswerOptions()); +} + +std::unique_ptr +PeerConnectionWrapper::CreateAnswerAndSetAsLocal( + const PeerConnectionInterface::RTCOfferAnswerOptions& options) { + auto answer = CreateAnswer(options); if (!answer) { return nullptr; } @@ -97,73 +117,72 @@ PeerConnectionWrapper::CreateAnswerAndSetAsLocal() { } std::unique_ptr PeerConnectionWrapper::CreateSdp( - std::function fn) { + std::function fn, + std::string* error_out) { rtc::scoped_refptr observer( new rtc::RefCountedObject()); fn(observer); EXPECT_EQ_WAIT(true, observer->called(), kWaitTimeout); + if (error_out && !observer->result()) { + *error_out = observer->error(); + } return observer->MoveDescription(); } bool PeerConnectionWrapper::SetLocalDescription( - std::unique_ptr desc) { - return SetSdp([this, &desc](SetSessionDescriptionObserver* observer) { - pc()->SetLocalDescription(observer, desc.release()); - }); + std::unique_ptr desc, + std::string* error_out) { + return SetSdp( + [this, &desc](SetSessionDescriptionObserver* observer) { + pc()->SetLocalDescription(observer, desc.release()); + }, + error_out); } bool PeerConnectionWrapper::SetRemoteDescription( - std::unique_ptr desc) { - return SetSdp([this, &desc](SetSessionDescriptionObserver* observer) { - pc()->SetRemoteDescription(observer, desc.release()); - }); + std::unique_ptr desc, + std::string* error_out) { + return SetSdp( + [this, &desc](SetSessionDescriptionObserver* observer) { + pc()->SetRemoteDescription(observer, desc.release()); + }, + error_out); } bool PeerConnectionWrapper::SetSdp( - std::function fn) { + std::function fn, + std::string* error_out) { rtc::scoped_refptr observer( new rtc::RefCountedObject()); fn(observer); - if (pc()->signaling_state() != PeerConnectionInterface::kClosed) { - EXPECT_EQ_WAIT(true, observer->called(), kWaitTimeout); + EXPECT_EQ_WAIT(true, observer->called(), kWaitTimeout); + if (error_out && !observer->result()) { + *error_out = observer->error(); } return observer->result(); } -void PeerConnectionWrapper::AddAudioStream(const std::string& stream_label, - const std::string& track_label) { - auto stream = pc_factory()->CreateLocalMediaStream(stream_label); - auto audio_track = pc_factory()->CreateAudioTrack(track_label, nullptr); - EXPECT_TRUE(pc()->AddTrack(audio_track, {stream})); - EXPECT_TRUE_WAIT(observer()->renegotiation_needed_, kWaitTimeout); - observer()->renegotiation_needed_ = false; +rtc::scoped_refptr PeerConnectionWrapper::AddAudioTrack( + const std::string& track_label, + std::vector streams) { + auto media_stream_track = + pc_factory()->CreateAudioTrack(track_label, nullptr); + return pc()->AddTrack(media_stream_track, streams); } -void PeerConnectionWrapper::AddVideoStream(const std::string& stream_label, - const std::string& track_label) { - auto stream = pc_factory()->CreateLocalMediaStream(stream_label); +rtc::scoped_refptr PeerConnectionWrapper::AddVideoTrack( + const std::string& track_label, + std::vector streams) { auto video_source = pc_factory()->CreateVideoSource( rtc::MakeUnique()); - auto video_track = pc_factory()->CreateVideoTrack(track_label, video_source); - EXPECT_TRUE(pc()->AddTrack(video_track, {stream})); - EXPECT_TRUE_WAIT(observer()->renegotiation_needed_, kWaitTimeout); - observer()->renegotiation_needed_ = false; + auto media_stream_track = + pc_factory()->CreateVideoTrack(track_label, video_source); + return pc()->AddTrack(media_stream_track, streams); } -void PeerConnectionWrapper::AddAudioVideoStream( - const std::string& stream_label, - const std::string& audio_track_label, - const std::string& video_track_label) { - auto stream = pc_factory()->CreateLocalMediaStream(stream_label); - auto audio_track = pc_factory()->CreateAudioTrack(audio_track_label, nullptr); - EXPECT_TRUE(pc()->AddTrack(audio_track, {stream})); - auto video_source = pc_factory()->CreateVideoSource( - rtc::MakeUnique()); - auto video_track = - pc_factory()->CreateVideoTrack(video_track_label, video_source); - EXPECT_TRUE(pc()->AddTrack(video_track, {stream})); - EXPECT_TRUE_WAIT(observer()->renegotiation_needed_, kWaitTimeout); - observer()->renegotiation_needed_ = false; +PeerConnectionInterface::SignalingState +PeerConnectionWrapper::signaling_state() { + return pc()->signaling_state(); } bool PeerConnectionWrapper::IsIceGatheringDone() { diff --git a/pc/peerconnectionwrapper.h b/pc/peerconnectionwrapper.h index 783ae3827b..f74fcdb85a 100644 --- a/pc/peerconnectionwrapper.h +++ b/pc/peerconnectionwrapper.h @@ -54,54 +54,69 @@ class PeerConnectionWrapper { // resulting SessionDescription once it is available. If the method call // failed, null is returned. std::unique_ptr CreateOffer( - const PeerConnectionInterface::RTCOfferAnswerOptions& options); + const PeerConnectionInterface::RTCOfferAnswerOptions& options, + std::string* error_out = nullptr); // Calls CreateOffer with default options. std::unique_ptr CreateOffer(); // Calls CreateOffer and sets a copy of the offer as the local description. + std::unique_ptr CreateOfferAndSetAsLocal( + const PeerConnectionInterface::RTCOfferAnswerOptions& options); + // Calls CreateOfferAndSetAsLocal with default options. std::unique_ptr CreateOfferAndSetAsLocal(); // Calls the underlying PeerConnection's CreateAnswer method and returns the // resulting SessionDescription once it is available. If the method call // failed, null is returned. std::unique_ptr CreateAnswer( - const PeerConnectionInterface::RTCOfferAnswerOptions& options); + const PeerConnectionInterface::RTCOfferAnswerOptions& options, + std::string* error_out = nullptr); // Calls CreateAnswer with the default options. std::unique_ptr CreateAnswer(); // Calls CreateAnswer and sets a copy of the offer as the local description. + std::unique_ptr CreateAnswerAndSetAsLocal( + const PeerConnectionInterface::RTCOfferAnswerOptions& options); + // Calls CreateAnswerAndSetAsLocal with default options. std::unique_ptr CreateAnswerAndSetAsLocal(); // Calls the underlying PeerConnection's SetLocalDescription method with the // given session description and waits for the success/failure response. // Returns true if the description was successfully set. - bool SetLocalDescription(std::unique_ptr desc); + bool SetLocalDescription(std::unique_ptr desc, + std::string* error_out = nullptr); // Calls the underlying PeerConnection's SetRemoteDescription method with the // given session description and waits for the success/failure response. // Returns true if the description was successfully set. - bool SetRemoteDescription(std::unique_ptr desc); + bool SetRemoteDescription(std::unique_ptr desc, + std::string* error_out = nullptr); - // Adds a new stream with one audio track to the underlying PeerConnection. - void AddAudioStream(const std::string& stream_label, - const std::string& track_label); - // Adds a new stream with one video track to the underlying PeerConnection. - void AddVideoStream(const std::string& stream_label, - const std::string& track_label); - // Adds a new stream with one audio and one video track to the underlying - // PeerConnection. - void AddAudioVideoStream(const std::string& stream_label, - const std::string& audio_track_label, - const std::string& video_track_label); + // Calls the underlying PeerConnection's AddTrack method with an audio media + // stream track not bound to any source. + rtc::scoped_refptr AddAudioTrack( + const std::string& track_label, + std::vector streams = {}); + + // Calls the underlying PeerConnection's AddTrack method with a video media + // stream track fed by a fake video capturer. + rtc::scoped_refptr AddVideoTrack( + const std::string& track_label, + std::vector streams = {}); + + // Returns the signaling state of the underlying PeerConnection. + PeerConnectionInterface::SignalingState signaling_state(); // Returns true if ICE has finished gathering candidates. bool IsIceGatheringDone(); private: std::unique_ptr CreateSdp( - std::function fn); - bool SetSdp(std::function fn); + std::function fn, + std::string* error_out); + bool SetSdp(std::function fn, + std::string* error_out); rtc::scoped_refptr pc_factory_; - rtc::scoped_refptr pc_; std::unique_ptr observer_; + rtc::scoped_refptr pc_; }; } // namespace webrtc diff --git a/pc/sdputils.cc b/pc/sdputils.cc index 9339fdb874..8932bea033 100644 --- a/pc/sdputils.cc +++ b/pc/sdputils.cc @@ -20,7 +20,14 @@ namespace webrtc { std::unique_ptr CloneSessionDescription( const SessionDescriptionInterface* sdesc) { RTC_DCHECK(sdesc); - auto clone = rtc::MakeUnique(sdesc->type()); + return CloneSessionDescriptionAsType(sdesc, sdesc->type()); +} + +std::unique_ptr CloneSessionDescriptionAsType( + const SessionDescriptionInterface* sdesc, + const std::string& type) { + RTC_DCHECK(sdesc); + auto clone = rtc::MakeUnique(type); clone->Initialize(sdesc->description()->Copy(), sdesc->session_id(), sdesc->session_version()); // As of writing, our version of GCC does not allow returning a unique_ptr of diff --git a/pc/sdputils.h b/pc/sdputils.h index 7d67fd8d72..3a53a41756 100644 --- a/pc/sdputils.h +++ b/pc/sdputils.h @@ -23,6 +23,11 @@ namespace webrtc { std::unique_ptr CloneSessionDescription( const SessionDescriptionInterface* sdesc); +// Returns a copy of the given session description with the type changed. +std::unique_ptr CloneSessionDescriptionAsType( + const SessionDescriptionInterface* sdesc, + const std::string& type); + // Function that takes a single session description content with its // corresponding transport and produces a boolean. typedef std::function MoveDescription() { return std::move(desc_); } private: bool called_; - bool result_; + std::string error_; std::unique_ptr desc_; }; @@ -205,22 +206,23 @@ class MockSetSessionDescriptionObserver public: MockSetSessionDescriptionObserver() : called_(false), - result_(false) {} + error_("MockSetSessionDescriptionObserver not called") {} virtual ~MockSetSessionDescriptionObserver() {} virtual void OnSuccess() { called_ = true; - result_ = true; + error_ = ""; } virtual void OnFailure(const std::string& error) { called_ = true; - result_ = false; + error_ = error; } bool called() const { return called_; } - bool result() const { return result_; } + bool result() const { return error_.empty(); } + const std::string& error() const { return error_; } private: bool called_; - bool result_; + std::string error_; }; class MockDataChannelObserver : public webrtc::DataChannelObserver { diff --git a/pc/webrtcsession.cc b/pc/webrtcsession.cc index f556204c6c..2e0ae50d4c 100644 --- a/pc/webrtcsession.cc +++ b/pc/webrtcsession.cc @@ -337,7 +337,7 @@ static bool BadSdp(const std::string& source, if (!type.empty()) { desc << " " << type; } - desc << " sdp: " << reason; + desc << " SDP: " << reason; if (err_desc) { *err_desc = desc.str(); @@ -707,15 +707,13 @@ void WebRtcSession::CreateAnswer( webrtc_session_desc_factory_->CreateAnswer(observer, session_options); } -bool WebRtcSession::SetLocalDescription(SessionDescriptionInterface* desc, - std::string* err_desc) { +bool WebRtcSession::SetLocalDescription( + std::unique_ptr desc, + std::string* err_desc) { RTC_DCHECK(signaling_thread()->IsCurrent()); - // Takes the ownership of |desc| regardless of the result. - std::unique_ptr desc_temp(desc); - // Validate SDP. - if (!ValidateSessionDescription(desc, cricket::CS_LOCAL, err_desc)) { + if (!ValidateSessionDescription(desc.get(), cricket::CS_LOCAL, err_desc)) { return false; } @@ -727,18 +725,19 @@ bool WebRtcSession::SetLocalDescription(SessionDescriptionInterface* desc, } if (action == kAnswer) { - current_local_description_.reset(desc_temp.release()); - pending_local_description_.reset(nullptr); - current_remote_description_.reset(pending_remote_description_.release()); + current_local_description_ = std::move(desc); + pending_local_description_ = nullptr; + current_remote_description_ = std::move(pending_remote_description_); } else { - pending_local_description_.reset(desc_temp.release()); + pending_local_description_ = std::move(desc); } // Transport and Media channels will be created only when offer is set. if (action == kOffer && !CreateChannels(local_description()->description())) { // TODO(mallinath) - Handle CreateChannel failure, as new local description // is applied. Restore back to old description. - return BadLocalSdp(desc->type(), kCreateChannelFailed, err_desc); + return BadLocalSdp(local_description()->type(), kCreateChannelFailed, + err_desc); } // Remove unused channels if MediaContentDescription is rejected. @@ -754,50 +753,54 @@ bool WebRtcSession::SetLocalDescription(SessionDescriptionInterface* desc, pending_ice_restarts_.clear(); if (error() != ERROR_NONE) { - return BadLocalSdp(desc->type(), GetSessionErrorMsg(), err_desc); + return BadLocalSdp(local_description()->type(), GetSessionErrorMsg(), + err_desc); } return true; } -bool WebRtcSession::SetRemoteDescription(SessionDescriptionInterface* desc, - std::string* err_desc) { +bool WebRtcSession::SetRemoteDescription( + std::unique_ptr desc, + std::string* err_desc) { RTC_DCHECK(signaling_thread()->IsCurrent()); - // Takes the ownership of |desc| regardless of the result. - std::unique_ptr desc_temp(desc); - // Validate SDP. - if (!ValidateSessionDescription(desc, cricket::CS_REMOTE, err_desc)) { + if (!ValidateSessionDescription(desc.get(), cricket::CS_REMOTE, err_desc)) { return false; } + // Hold this pointer so candidates can be copied to it later in the method. + SessionDescriptionInterface* desc_ptr = desc.get(); + const SessionDescriptionInterface* old_remote_description = remote_description(); // Grab ownership of the description being replaced for the remainder of this - // method, since it's used below. + // method, since it's used below as |old_remote_description|. std::unique_ptr replaced_remote_description; Action action = GetAction(desc->type()); if (action == kAnswer) { - replaced_remote_description.reset( - pending_remote_description_ ? pending_remote_description_.release() - : current_remote_description_.release()); - current_remote_description_.reset(desc_temp.release()); - pending_remote_description_.reset(nullptr); - current_local_description_.reset(pending_local_description_.release()); + replaced_remote_description = pending_remote_description_ + ? std::move(pending_remote_description_) + : std::move(current_remote_description_); + current_remote_description_ = std::move(desc); + pending_remote_description_ = nullptr; + current_local_description_ = std::move(pending_local_description_); } else { - replaced_remote_description.reset(pending_remote_description_.release()); - pending_remote_description_.reset(desc_temp.release()); + replaced_remote_description = std::move(pending_remote_description_); + pending_remote_description_ = std::move(desc); } // Transport and Media channels will be created only when offer is set. - if (action == kOffer && !CreateChannels(desc->description())) { + if (action == kOffer && + !CreateChannels(remote_description()->description())) { // TODO(mallinath) - Handle CreateChannel failure, as new local description // is applied. Restore back to old description. - return BadRemoteSdp(desc->type(), kCreateChannelFailed, err_desc); + return BadRemoteSdp(remote_description()->type(), kCreateChannelFailed, + err_desc); } // Remove unused channels if MediaContentDescription is rejected. - RemoveUnusedChannels(desc->description()); + RemoveUnusedChannels(remote_description()->description()); // NOTE: Candidates allocation will be initiated only when SetLocalDescription // is called. @@ -805,8 +808,10 @@ bool WebRtcSession::SetRemoteDescription(SessionDescriptionInterface* desc, return false; } - if (local_description() && !UseCandidatesInSessionDescription(desc)) { - return BadRemoteSdp(desc->type(), kInvalidCandidates, err_desc); + if (local_description() && + !UseCandidatesInSessionDescription(remote_description())) { + return BadRemoteSdp(remote_description()->type(), kInvalidCandidates, + err_desc); } if (old_remote_description) { @@ -817,7 +822,7 @@ bool WebRtcSession::SetRemoteDescription(SessionDescriptionInterface* desc, // TODO(deadbeef): When we start storing both the current and pending // remote description, this should reset pending_ice_restarts and compare // against the current description. - if (CheckForRemoteIceRestart(old_remote_description, desc, + if (CheckForRemoteIceRestart(old_remote_description, remote_description(), content.name)) { if (action == kOffer) { pending_ice_restarts_.insert(content.name); @@ -831,13 +836,14 @@ bool WebRtcSession::SetRemoteDescription(SessionDescriptionInterface* desc, // description plus any candidates added since then. We should remove // this once we're sure it won't break anything. WebRtcSessionDescriptionFactory::CopyCandidatesFromSessionDescription( - old_remote_description, content.name, desc); + old_remote_description, content.name, desc_ptr); } } } if (error() != ERROR_NONE) { - return BadRemoteSdp(desc->type(), GetSessionErrorMsg(), err_desc); + return BadRemoteSdp(remote_description()->type(), GetSessionErrorMsg(), + err_desc); } // Set the the ICE connection state to connecting since the connection may @@ -848,7 +854,7 @@ bool WebRtcSession::SetRemoteDescription(SessionDescriptionInterface* desc, // transport and expose a new checking() member from transport that can be // read to determine the current checking state. The existing SignalConnecting // actually means "gathering candidates", so cannot be be used here. - if (desc->type() != SessionDescriptionInterface::kOffer && + if (remote_description()->type() != SessionDescriptionInterface::kOffer && ice_connection_state_ == PeerConnectionInterface::kIceConnectionNew) { SetIceConnectionState(PeerConnectionInterface::kIceConnectionChecking); } diff --git a/pc/webrtcsession.h b/pc/webrtcsession.h index 16c3931b35..185fa05ed4 100644 --- a/pc/webrtcsession.h +++ b/pc/webrtcsession.h @@ -254,11 +254,9 @@ class WebRtcSession : const cricket::MediaSessionOptions& session_options); void CreateAnswer(CreateSessionDescriptionObserver* observer, const cricket::MediaSessionOptions& session_options); - // The ownership of |desc| will be transferred after this call. - bool SetLocalDescription(SessionDescriptionInterface* desc, + bool SetLocalDescription(std::unique_ptr desc, std::string* err_desc); - // The ownership of |desc| will be transferred after this call. - bool SetRemoteDescription(SessionDescriptionInterface* desc, + bool SetRemoteDescription(std::unique_ptr desc, std::string* err_desc); bool ProcessIceMessage(const IceCandidateInterface* ice_candidate); diff --git a/pc/webrtcsession_unittest.cc b/pc/webrtcsession_unittest.cc index 25cb35a6a9..0c54abc9cb 100644 --- a/pc/webrtcsession_unittest.cc +++ b/pc/webrtcsession_unittest.cc @@ -92,26 +92,6 @@ static const char kMediaContentName1[] = "video"; static const int kDefaultTimeout = 10000; // 10 seconds. static const int kIceCandidatesTimeout = 10000; -static const char kSdpWithRtx[] = - "v=0\r\n" - "o=- 4104004319237231850 2 IN IP4 127.0.0.1\r\n" - "s=-\r\n" - "t=0 0\r\n" - "a=msid-semantic: WMS stream1\r\n" - "m=video 9 RTP/SAVPF 0 96\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:CerjGp19G7wpXwl7\r\n" - "a=ice-pwd:cMvOlFvQ6ochez1ZOoC2uBEC\r\n" - "a=mid:video\r\n" - "a=sendrecv\r\n" - "a=rtcp-mux\r\n" - "a=crypto:1 AES_CM_128_HMAC_SHA1_80 " - "inline:5/4N5CDvMiyDArHtBByUM71VIkguH17ZNoX60GrA\r\n" - "a=rtpmap:0 fake_video_codec/90000\r\n" - "a=rtpmap:96 rtx/90000\r\n" - "a=fmtp:96 apt=0\r\n"; - static const char kStream1[] = "stream1"; static const char kVideoTrack1[] = "video1"; static const char kAudioTrack1[] = "audio1"; @@ -120,7 +100,6 @@ static const char kStream2[] = "stream2"; static const char kVideoTrack2[] = "video2"; static const char kAudioTrack2[] = "audio2"; -static constexpr bool kStopped = true; static constexpr bool kActive = false; enum RTCCertificateGenerationMethod { ALREADY_GENERATED, DTLS_IDENTITY_STORE }; @@ -465,24 +444,6 @@ class WebRtcSessionTest remote_send_video_ = true; } - void SendAudioVideoStream1And2() { - send_stream_1_ = true; - send_stream_2_ = true; - local_send_audio_ = true; - local_send_video_ = true; - remote_send_audio_ = true; - remote_send_video_ = true; - } - - void SendNothing() { - send_stream_1_ = false; - send_stream_2_ = false; - local_send_audio_ = false; - local_send_video_ = false; - remote_send_audio_ = false; - remote_send_video_ = false; - } - void SendAudioOnlyStream2() { send_stream_1_ = false; send_stream_2_ = true; @@ -501,19 +462,6 @@ class WebRtcSessionTest remote_send_video_ = true; } - // Helper function used to add a specific media section to the - // |session_options|. - void AddMediaSection(cricket::MediaType type, - const std::string& mid, - cricket::MediaContentDirection direction, - bool stopped, - cricket::MediaSessionOptions* opts) { - opts->media_description_options.push_back(cricket::MediaDescriptionOptions( - type, mid, - cricket::RtpTransceiverDirection::FromMediaContentDirection(direction), - stopped)); - } - // Add the media sections to the options from |offered_media_sections_| when // creating an answer or a new offer. // This duplicates a lot of logic from PeerConnection but this can be fixed @@ -688,13 +636,6 @@ class WebRtcSessionTest session_options->crypto_options = crypto_options_; } - void GetOptionsForAudioOnlyRemoteOffer( - cricket::MediaSessionOptions* session_options) { - remote_recv_audio_ = true; - remote_recv_video_ = false; - GetOptionsForRemoteOffer(session_options); - } - void GetOptionsForRemoteOffer(cricket::MediaSessionOptions* session_options) { AddMediaSectionsAndSendersToOptions(session_options, remote_send_audio_, remote_recv_audio_, remote_send_video_, @@ -811,30 +752,15 @@ class WebRtcSessionTest transport_desc->ice_pwd = pwd; } - // Creates a remote offer and and applies it as a remote description, - // creates a local answer and applies is as a local description. - // Call SendAudioVideoStreamX() before this function - // to decide which local and remote streams to create. - void CreateAndSetRemoteOfferAndLocalAnswer() { - SessionDescriptionInterface* offer = CreateRemoteOffer(); - SetRemoteDescriptionWithoutError(offer); - SessionDescriptionInterface* answer = CreateAnswer(); - SetLocalDescriptionWithoutError(answer); - } void SetLocalDescriptionWithoutError(SessionDescriptionInterface* desc) { - ASSERT_TRUE(session_->SetLocalDescription(desc, nullptr)); + ASSERT_TRUE(session_->SetLocalDescription(rtc::WrapUnique(desc), nullptr)); session_->MaybeStartGathering(); } - void SetLocalDescriptionExpectState(SessionDescriptionInterface* desc, - WebRtcSession::State expected_state) { - SetLocalDescriptionWithoutError(desc); - EXPECT_EQ(expected_state, session_->state()); - } void SetLocalDescriptionExpectError(const std::string& action, const std::string& expected_error, SessionDescriptionInterface* desc) { std::string error; - EXPECT_FALSE(session_->SetLocalDescription(desc, &error)); + EXPECT_FALSE(session_->SetLocalDescription(rtc::WrapUnique(desc), &error)); std::string sdp_type = "local "; sdp_type.append(action); EXPECT_NE(std::string::npos, error.find(sdp_type)); @@ -845,24 +771,14 @@ class WebRtcSessionTest SetLocalDescriptionExpectError(SessionDescriptionInterface::kOffer, expected_error, desc); } - void SetLocalDescriptionAnswerExpectError(const std::string& expected_error, - SessionDescriptionInterface* desc) { - SetLocalDescriptionExpectError(SessionDescriptionInterface::kAnswer, - expected_error, desc); - } void SetRemoteDescriptionWithoutError(SessionDescriptionInterface* desc) { - ASSERT_TRUE(session_->SetRemoteDescription(desc, nullptr)); - } - void SetRemoteDescriptionExpectState(SessionDescriptionInterface* desc, - WebRtcSession::State expected_state) { - SetRemoteDescriptionWithoutError(desc); - EXPECT_EQ(expected_state, session_->state()); + ASSERT_TRUE(session_->SetRemoteDescription(rtc::WrapUnique(desc), nullptr)); } void SetRemoteDescriptionExpectError(const std::string& action, const std::string& expected_error, SessionDescriptionInterface* desc) { std::string error; - EXPECT_FALSE(session_->SetRemoteDescription(desc, &error)); + EXPECT_FALSE(session_->SetRemoteDescription(rtc::WrapUnique(desc), &error)); std::string sdp_type = "remote "; sdp_type.append(action); EXPECT_NE(std::string::npos, error.find(sdp_type)); @@ -873,11 +789,6 @@ class WebRtcSessionTest SetRemoteDescriptionExpectError(SessionDescriptionInterface::kOffer, expected_error, desc); } - void SetRemoteDescriptionAnswerExpectError( - const std::string& expected_error, SessionDescriptionInterface* desc) { - SetRemoteDescriptionExpectError(SessionDescriptionInterface::kAnswer, - expected_error, desc); - } JsepSessionDescription* CreateRemoteOfferWithVersion( cricket::MediaSessionOptions options, @@ -1035,23 +946,6 @@ class WebRtcSessionTest } } - bool ContainsVideoCodecWithName(const SessionDescriptionInterface* desc, - const std::string& codec_name) { - for (const auto& content : desc->description()->contents()) { - if (static_cast(content.description) - ->type() == cricket::MEDIA_TYPE_VIDEO) { - const auto* mdesc = - static_cast(content.description); - for (const auto& codec : mdesc->codecs()) { - if (codec.name == codec_name) { - return true; - } - } - } - } - return false; - } - // The method sets up a call from the session to itself, in a loopback // arrangement. It also uses a firewall rule to create a temporary // disconnection, and then a permanent disconnection. @@ -1115,33 +1009,6 @@ class WebRtcSessionTest EXPECT_GT(fake_call_.last_sent_packet().send_time_ms, -1); } - // Adds CN codecs to FakeMediaEngine and MediaDescriptionFactory. - void AddCNCodecs() { - const cricket::AudioCodec kCNCodec1(102, "CN", 8000, 0, 1); - const cricket::AudioCodec kCNCodec2(103, "CN", 16000, 0, 1); - - // Add kCNCodec for dtmf test. - std::vector codecs = - media_engine_->audio_send_codecs(); - codecs.push_back(kCNCodec1); - codecs.push_back(kCNCodec2); - media_engine_->SetAudioCodecs(codecs); - desc_factory_->set_audio_codecs(codecs, codecs); - } - - bool VerifyNoCNCodecs(const cricket::ContentInfo* content) { - const cricket::ContentDescription* description = content->description; - RTC_CHECK(description != NULL); - const cricket::AudioContentDescription* audio_content_desc = - static_cast(description); - RTC_CHECK(audio_content_desc != NULL); - for (size_t i = 0; i < audio_content_desc->codecs().size(); ++i) { - if (audio_content_desc->codecs()[i].name == "CN") - return false; - } - return true; - } - void CreateDataChannel() { webrtc::InternalDataChannelInit dci; RTC_CHECK(session_.get()); @@ -1214,141 +1081,6 @@ TEST_F(WebRtcSessionTest, TestSessionCandidatesWithBundleRtcpMux) { TestSessionCandidatesWithBundleRtcpMux(true, true); } -TEST_F(WebRtcSessionTest, SetSdpFailedOnInvalidSdp) { - Init(); - SessionDescriptionInterface* offer = NULL; - // Since |offer| is NULL, there's no way to tell if it's an offer or answer. - std::string unknown_action; - SetLocalDescriptionExpectError(unknown_action, kInvalidSdp, offer); - SetRemoteDescriptionExpectError(unknown_action, kInvalidSdp, offer); -} - -// Test creating offers and receive answers and make sure the -// media engine creates the expected send and receive streams. -TEST_F(WebRtcSessionTest, TestCreateSdesOfferReceiveSdesAnswer) { - Init(); - SendAudioVideoStream1(); - SessionDescriptionInterface* offer = CreateOffer(); - const std::string session_id_orig = offer->session_id(); - const std::string session_version_orig = offer->session_version(); - SetLocalDescriptionWithoutError(offer); - - SendAudioVideoStream2(); - SessionDescriptionInterface* answer = - CreateRemoteAnswer(session_->local_description()); - SetRemoteDescriptionWithoutError(answer); - - video_channel_ = media_engine_->GetVideoChannel(0); - voice_channel_ = media_engine_->GetVoiceChannel(0); - - ASSERT_EQ(1u, video_channel_->recv_streams().size()); - EXPECT_TRUE(kVideoTrack2 == video_channel_->recv_streams()[0].id); - - ASSERT_EQ(1u, voice_channel_->recv_streams().size()); - EXPECT_TRUE(kAudioTrack2 == voice_channel_->recv_streams()[0].id); - - ASSERT_EQ(1u, video_channel_->send_streams().size()); - EXPECT_TRUE(kVideoTrack1 == video_channel_->send_streams()[0].id); - ASSERT_EQ(1u, voice_channel_->send_streams().size()); - EXPECT_TRUE(kAudioTrack1 == voice_channel_->send_streams()[0].id); - - // Create new offer without send streams. - SendNothing(); - offer = CreateOffer(); - - // Verify the session id is the same and the session version is - // increased. - EXPECT_EQ(session_id_orig, offer->session_id()); - EXPECT_LT(rtc::FromString(session_version_orig), - rtc::FromString(offer->session_version())); - - SetLocalDescriptionWithoutError(offer); - EXPECT_EQ(0u, video_channel_->send_streams().size()); - EXPECT_EQ(0u, voice_channel_->send_streams().size()); - - SendAudioVideoStream2(); - answer = CreateRemoteAnswer(session_->local_description()); - SetRemoteDescriptionWithoutError(answer); - - // Make sure the receive streams have not changed. - ASSERT_EQ(1u, video_channel_->recv_streams().size()); - EXPECT_TRUE(kVideoTrack2 == video_channel_->recv_streams()[0].id); - ASSERT_EQ(1u, voice_channel_->recv_streams().size()); - EXPECT_TRUE(kAudioTrack2 == voice_channel_->recv_streams()[0].id); -} - -// Test receiving offers and creating answers and make sure the -// media engine creates the expected send and receive streams. -TEST_F(WebRtcSessionTest, TestReceiveSdesOfferCreateSdesAnswer) { - Init(); - SendAudioVideoStream2(); - SessionDescriptionInterface* offer = CreateOffer(); - SetRemoteDescriptionWithoutError(offer); - - SendAudioVideoStream1(); - SessionDescriptionInterface* answer = CreateAnswer(); - SetLocalDescriptionWithoutError(answer); - - const std::string session_id_orig = answer->session_id(); - const std::string session_version_orig = answer->session_version(); - - video_channel_ = media_engine_->GetVideoChannel(0); - voice_channel_ = media_engine_->GetVoiceChannel(0); - - ASSERT_TRUE(video_channel_); - ASSERT_TRUE(voice_channel_); - ASSERT_EQ(1u, video_channel_->recv_streams().size()); - EXPECT_TRUE(kVideoTrack2 == video_channel_->recv_streams()[0].id); - - ASSERT_EQ(1u, voice_channel_->recv_streams().size()); - EXPECT_TRUE(kAudioTrack2 == voice_channel_->recv_streams()[0].id); - - ASSERT_EQ(1u, video_channel_->send_streams().size()); - EXPECT_TRUE(kVideoTrack1 == video_channel_->send_streams()[0].id); - ASSERT_EQ(1u, voice_channel_->send_streams().size()); - EXPECT_TRUE(kAudioTrack1 == voice_channel_->send_streams()[0].id); - - SendAudioVideoStream1And2(); - offer = CreateOffer(); - SetRemoteDescriptionWithoutError(offer); - - // Answer by turning off all send streams. - SendNothing(); - answer = CreateAnswer(); - - // Verify the session id is the same and the session version is - // increased. - EXPECT_EQ(session_id_orig, answer->session_id()); - EXPECT_LT(rtc::FromString(session_version_orig), - rtc::FromString(answer->session_version())); - SetLocalDescriptionWithoutError(answer); - - ASSERT_EQ(2u, video_channel_->recv_streams().size()); - EXPECT_TRUE(kVideoTrack1 == video_channel_->recv_streams()[0].id); - EXPECT_TRUE(kVideoTrack2 == video_channel_->recv_streams()[1].id); - ASSERT_EQ(2u, voice_channel_->recv_streams().size()); - EXPECT_TRUE(kAudioTrack1 == voice_channel_->recv_streams()[0].id); - EXPECT_TRUE(kAudioTrack2 == voice_channel_->recv_streams()[1].id); - - // Make sure we have no send streams. - EXPECT_EQ(0u, video_channel_->send_streams().size()); - EXPECT_EQ(0u, voice_channel_->send_streams().size()); -} - -TEST_F(WebRtcSessionTest, SetLocalSdpFailedOnCreateChannel) { - Init(); - media_engine_->set_fail_create_channel(true); - - SessionDescriptionInterface* offer = CreateOffer(); - ASSERT_TRUE(offer != NULL); - // SetRemoteDescription and SetLocalDescription will take the ownership of - // the offer. - SetRemoteDescriptionOfferExpectError(kCreateChannelFailed, offer); - offer = CreateOffer(); - ASSERT_TRUE(offer != NULL); - SetLocalDescriptionOfferExpectError(kCreateChannelFailed, offer); -} - // Test that we can create and set an answer correctly when different // SSL roles have been negotiated for different transports. // See: https://bugs.chromium.org/p/webrtc/issues/detail?id=4525 @@ -1412,592 +1144,6 @@ TEST_P(WebRtcSessionTest, TestCreateAnswerWithDifferentSslRoles) { SetLocalDescriptionWithoutError(answer); } -TEST_F(WebRtcSessionTest, TestSetLocalOfferTwice) { - Init(); - SendNothing(); - // SetLocalDescription take ownership of offer. - SessionDescriptionInterface* offer = CreateOffer(); - SetLocalDescriptionWithoutError(offer); - - // SetLocalDescription take ownership of offer. - SessionDescriptionInterface* offer2 = CreateOffer(); - SetLocalDescriptionWithoutError(offer2); -} - -TEST_F(WebRtcSessionTest, TestSetRemoteOfferTwice) { - Init(); - SendNothing(); - // SetLocalDescription take ownership of offer. - SessionDescriptionInterface* offer = CreateOffer(); - SetRemoteDescriptionWithoutError(offer); - - SessionDescriptionInterface* offer2 = CreateOffer(); - SetRemoteDescriptionWithoutError(offer2); -} - -TEST_F(WebRtcSessionTest, TestSetLocalAndRemoteOffer) { - Init(); - SendNothing(); - SessionDescriptionInterface* offer = CreateOffer(); - SetLocalDescriptionWithoutError(offer); - offer = CreateOffer(); - SetRemoteDescriptionOfferExpectError("Called in wrong state: STATE_SENTOFFER", - offer); -} - -TEST_F(WebRtcSessionTest, TestSetRemoteAndLocalOffer) { - Init(); - SendNothing(); - SessionDescriptionInterface* offer = CreateOffer(); - SetRemoteDescriptionWithoutError(offer); - offer = CreateOffer(); - SetLocalDescriptionOfferExpectError( - "Called in wrong state: STATE_RECEIVEDOFFER", offer); -} - -TEST_F(WebRtcSessionTest, TestSetLocalPrAnswer) { - Init(); - SendNothing(); - SessionDescriptionInterface* offer = CreateRemoteOffer(); - SetRemoteDescriptionExpectState(offer, WebRtcSession::STATE_RECEIVEDOFFER); - - JsepSessionDescription* pranswer = - static_cast(CreateAnswer()); - pranswer->set_type(SessionDescriptionInterface::kPrAnswer); - SetLocalDescriptionExpectState(pranswer, WebRtcSession::STATE_SENTPRANSWER); - - SendAudioVideoStream1(); - JsepSessionDescription* pranswer2 = - static_cast(CreateAnswer()); - pranswer2->set_type(SessionDescriptionInterface::kPrAnswer); - - SetLocalDescriptionExpectState(pranswer2, WebRtcSession::STATE_SENTPRANSWER); - - SendAudioVideoStream2(); - SessionDescriptionInterface* answer = CreateAnswer(); - SetLocalDescriptionExpectState(answer, WebRtcSession::STATE_INPROGRESS); -} - -TEST_F(WebRtcSessionTest, TestSetRemotePrAnswer) { - Init(); - SendNothing(); - SessionDescriptionInterface* offer = CreateOffer(); - SetLocalDescriptionExpectState(offer, WebRtcSession::STATE_SENTOFFER); - - JsepSessionDescription* pranswer = - CreateRemoteAnswer(session_->local_description()); - pranswer->set_type(SessionDescriptionInterface::kPrAnswer); - - SetRemoteDescriptionExpectState(pranswer, - WebRtcSession::STATE_RECEIVEDPRANSWER); - - SendAudioVideoStream1(); - JsepSessionDescription* pranswer2 = - CreateRemoteAnswer(session_->local_description()); - pranswer2->set_type(SessionDescriptionInterface::kPrAnswer); - - SetRemoteDescriptionExpectState(pranswer2, - WebRtcSession::STATE_RECEIVEDPRANSWER); - - SendAudioVideoStream2(); - SessionDescriptionInterface* answer = - CreateRemoteAnswer(session_->local_description()); - SetRemoteDescriptionExpectState(answer, WebRtcSession::STATE_INPROGRESS); -} - -TEST_F(WebRtcSessionTest, TestSetLocalAnswerWithoutOffer) { - Init(); - SendNothing(); - std::unique_ptr offer(CreateOffer()); - - SessionDescriptionInterface* answer = - CreateRemoteAnswer(offer.get()); - SetLocalDescriptionAnswerExpectError("Called in wrong state: STATE_INIT", - answer); -} - -TEST_F(WebRtcSessionTest, TestSetRemoteAnswerWithoutOffer) { - Init(); - SendNothing(); - std::unique_ptr offer(CreateOffer()); - - SessionDescriptionInterface* answer = - CreateRemoteAnswer(offer.get()); - SetRemoteDescriptionAnswerExpectError( - "Called in wrong state: STATE_INIT", answer); -} - -// Verifies TransportProxy and media channels are created with content names -// present in the SessionDescription. -TEST_F(WebRtcSessionTest, TestChannelCreationsWithContentNames) { - Init(); - SendAudioVideoStream1(); - std::unique_ptr offer(CreateOffer()); - - // CreateOffer creates session description with the content names "audio" and - // "video". Goal is to modify these content names and verify transport - // channels - // in the WebRtcSession, as channels are created with the content names - // present in SDP. - std::string sdp; - EXPECT_TRUE(offer->ToString(&sdp)); - - SessionDescriptionInterface* modified_offer = - CreateSessionDescription(JsepSessionDescription::kOffer, sdp, NULL); - - SetRemoteDescriptionWithoutError(modified_offer); - - cricket::MediaSessionOptions answer_options; - answer_options.bundle_enabled = false; - SessionDescriptionInterface* answer = CreateAnswer(answer_options); - SetLocalDescriptionWithoutError(answer); - - rtc::PacketTransportInternal* voice_transport_channel = - session_->voice_rtp_transport_channel(); - EXPECT_TRUE(voice_transport_channel != NULL); - EXPECT_EQ(voice_transport_channel->debug_name(), - "audio " + std::to_string(cricket::ICE_CANDIDATE_COMPONENT_RTP)); - rtc::PacketTransportInternal* video_transport_channel = - session_->video_rtp_transport_channel(); - ASSERT_TRUE(video_transport_channel != NULL); - EXPECT_EQ(video_transport_channel->debug_name(), - "video " + std::to_string(cricket::ICE_CANDIDATE_COMPONENT_RTP)); - EXPECT_TRUE((video_channel_ = media_engine_->GetVideoChannel(0)) != NULL); - EXPECT_TRUE((voice_channel_ = media_engine_->GetVoiceChannel(0)) != NULL); -} - -// Test that an offer contains the correct media content descriptions based on -// the send streams when no constraints have been set. -TEST_F(WebRtcSessionTest, CreateOfferWithoutConstraintsOrStreams) { - Init(); - std::unique_ptr offer(CreateOffer()); - - ASSERT_TRUE(offer != NULL); - const cricket::ContentInfo* content = - cricket::GetFirstAudioContent(offer->description()); - ASSERT_TRUE(content != NULL); - EXPECT_EQ( - cricket::MD_RECVONLY, - static_cast(content->description) - ->direction()); - content = cricket::GetFirstVideoContent(offer->description()); - ASSERT_TRUE(content != NULL); - EXPECT_EQ( - cricket::MD_RECVONLY, - static_cast(content->description) - ->direction()); -} - -// Test that an offer contains the correct media content descriptions based on -// the send streams when no constraints have been set. -TEST_F(WebRtcSessionTest, CreateOfferWithoutConstraints) { - Init(); - // Test Audio only offer. - SendAudioOnlyStream2(); - std::unique_ptr offer(CreateOffer()); - - const cricket::ContentInfo* content = - cricket::GetFirstAudioContent(offer->description()); - ASSERT_TRUE(content != NULL); - EXPECT_EQ( - cricket::MD_SENDRECV, - static_cast(content->description) - ->direction()); - content = cricket::GetFirstVideoContent(offer->description()); - ASSERT_TRUE(content != NULL); - EXPECT_EQ( - cricket::MD_RECVONLY, - static_cast(content->description) - ->direction()); - - // Test Audio / Video offer. - SendAudioVideoStream1(); - offer.reset(CreateOffer()); - content = cricket::GetFirstAudioContent(offer->description()); - ASSERT_TRUE(content != NULL); - EXPECT_EQ( - cricket::MD_SENDRECV, - static_cast(content->description) - ->direction()); - - content = cricket::GetFirstVideoContent(offer->description()); - ASSERT_TRUE(content != NULL); - EXPECT_EQ( - cricket::MD_SENDRECV, - static_cast(content->description) - ->direction()); -} - -// Test that an offer contains no media content descriptions if -// kOfferToReceiveVideo and kOfferToReceiveAudio constraints are set to false. -TEST_F(WebRtcSessionTest, CreateOfferWithConstraintsWithoutStreams) { - Init(); - PeerConnectionInterface::RTCOfferAnswerOptions options; - options.offer_to_receive_audio = 0; - options.offer_to_receive_video = 0; - - std::unique_ptr offer(CreateOffer(options)); - - ASSERT_TRUE(offer != NULL); - const cricket::ContentInfo* content = - cricket::GetFirstAudioContent(offer->description()); - EXPECT_TRUE(content == NULL); - content = cricket::GetFirstVideoContent(offer->description()); - EXPECT_TRUE(content == NULL); -} - -// Test that an offer contains only audio media content descriptions if -// kOfferToReceiveAudio constraints are set to true. -TEST_F(WebRtcSessionTest, CreateAudioOnlyOfferWithConstraints) { - Init(); - PeerConnectionInterface::RTCOfferAnswerOptions options; - options.offer_to_receive_audio = - RTCOfferAnswerOptions::kOfferToReceiveMediaTrue; - options.offer_to_receive_video = 0; - - std::unique_ptr offer(CreateOffer(options)); - - const cricket::ContentInfo* content = - cricket::GetFirstAudioContent(offer->description()); - EXPECT_TRUE(content != NULL); - content = cricket::GetFirstVideoContent(offer->description()); - EXPECT_TRUE(content == NULL); -} - -// Test that an offer contains audio and video media content descriptions if -// kOfferToReceiveAudio and kOfferToReceiveVideo constraints are set to true. -TEST_F(WebRtcSessionTest, CreateOfferWithConstraints) { - Init(); - // Test Audio / Video offer. - PeerConnectionInterface::RTCOfferAnswerOptions options; - options.offer_to_receive_audio = - RTCOfferAnswerOptions::kOfferToReceiveMediaTrue; - options.offer_to_receive_video = - RTCOfferAnswerOptions::kOfferToReceiveMediaTrue; - - std::unique_ptr offer(CreateOffer(options)); - - const cricket::ContentInfo* content = - cricket::GetFirstAudioContent(offer->description()); - EXPECT_TRUE(content != NULL); - - content = cricket::GetFirstVideoContent(offer->description()); - EXPECT_TRUE(content != NULL); - - // Sets constraints to false and verifies that audio/video contents are - // removed. - options.offer_to_receive_audio = 0; - options.offer_to_receive_video = 0; - // Remove the media sections added in previous offer. - offered_media_sections_.clear(); - offer.reset(CreateOffer(options)); - - content = cricket::GetFirstAudioContent(offer->description()); - EXPECT_TRUE(content == NULL); - content = cricket::GetFirstVideoContent(offer->description()); - EXPECT_TRUE(content == NULL); -} - -// Test that an answer can not be created if the last remote description is not -// an offer. -TEST_F(WebRtcSessionTest, CreateAnswerWithoutAnOffer) { - Init(); - SessionDescriptionInterface* offer = CreateOffer(); - SetLocalDescriptionWithoutError(offer); - SessionDescriptionInterface* answer = CreateRemoteAnswer(offer); - SetRemoteDescriptionWithoutError(answer); - EXPECT_TRUE(CreateAnswer() == NULL); -} - -// Test that an answer contains the correct media content descriptions when no -// constraints have been set. -TEST_F(WebRtcSessionTest, CreateAnswerWithoutConstraintsOrStreams) { - Init(); - // Create a remote offer with audio and video content. - std::unique_ptr offer(CreateRemoteOffer()); - SetRemoteDescriptionWithoutError(offer.release()); - std::unique_ptr answer(CreateAnswer()); - const cricket::ContentInfo* content = - cricket::GetFirstAudioContent(answer->description()); - ASSERT_TRUE(content != NULL); - EXPECT_FALSE(content->rejected); - - content = cricket::GetFirstVideoContent(answer->description()); - ASSERT_TRUE(content != NULL); - EXPECT_FALSE(content->rejected); -} - -// Test that an answer contains the correct media content descriptions when no -// constraints have been set and the offer only contain audio. -TEST_F(WebRtcSessionTest, CreateAudioAnswerWithoutConstraintsOrStreams) { - Init(); - // Create a remote offer with audio only. - cricket::MediaSessionOptions options; - GetOptionsForAudioOnlyRemoteOffer(&options); - - std::unique_ptr offer(CreateRemoteOffer(options)); - ASSERT_TRUE(cricket::GetFirstVideoContent(offer->description()) == NULL); - ASSERT_TRUE(cricket::GetFirstAudioContent(offer->description()) != NULL); - - SetRemoteDescriptionWithoutError(offer.release()); - std::unique_ptr answer(CreateAnswer()); - const cricket::ContentInfo* content = - cricket::GetFirstAudioContent(answer->description()); - ASSERT_TRUE(content != NULL); - EXPECT_FALSE(content->rejected); - - EXPECT_TRUE(cricket::GetFirstVideoContent(answer->description()) == NULL); -} - -// Test that an answer contains the correct media content descriptions when no -// constraints have been set. -TEST_F(WebRtcSessionTest, CreateAnswerWithoutConstraints) { - Init(); - // Create a remote offer with audio and video content. - std::unique_ptr offer(CreateRemoteOffer()); - SetRemoteDescriptionWithoutError(offer.release()); - // Test with a stream with tracks. - SendAudioVideoStream1(); - std::unique_ptr answer(CreateAnswer()); - const cricket::ContentInfo* content = - cricket::GetFirstAudioContent(answer->description()); - ASSERT_TRUE(content != NULL); - EXPECT_FALSE(content->rejected); - - content = cricket::GetFirstVideoContent(answer->description()); - ASSERT_TRUE(content != NULL); - EXPECT_FALSE(content->rejected); -} - -// Test that an answer contains the correct media content descriptions when -// constraints have been set but no stream is sent. -TEST_F(WebRtcSessionTest, CreateAnswerWithConstraintsWithoutStreams) { - Init(); - // Create a remote offer with audio and video content. - std::unique_ptr offer(CreateRemoteOffer()); - SetRemoteDescriptionWithoutError(offer.release()); - - cricket::MediaSessionOptions session_options; - remote_send_audio_ = false; - remote_send_video_ = false; - local_recv_audio_ = false; - local_recv_video_ = false; - std::unique_ptr answer( - CreateAnswer(session_options)); - - const cricket::ContentInfo* content = - cricket::GetFirstAudioContent(answer->description()); - ASSERT_TRUE(content != NULL); - EXPECT_TRUE(content->rejected); - - content = cricket::GetFirstVideoContent(answer->description()); - ASSERT_TRUE(content != NULL); - EXPECT_TRUE(content->rejected); -} - -// Test that an answer contains the correct media content descriptions when -// constraints have been set and streams are sent. -TEST_F(WebRtcSessionTest, CreateAnswerWithConstraints) { - Init(); - // Create a remote offer with audio and video content. - std::unique_ptr offer(CreateRemoteOffer()); - SetRemoteDescriptionWithoutError(offer.release()); - - cricket::MediaSessionOptions options; - // Test with a stream with tracks. - SendAudioVideoStream1(); - std::unique_ptr answer(CreateAnswer(options)); - - // TODO(perkj): Should the direction be set to SEND_ONLY? - const cricket::ContentInfo* content = - cricket::GetFirstAudioContent(answer->description()); - ASSERT_TRUE(content != NULL); - EXPECT_FALSE(content->rejected); - - // TODO(perkj): Should the direction be set to SEND_ONLY? - content = cricket::GetFirstVideoContent(answer->description()); - ASSERT_TRUE(content != NULL); - EXPECT_FALSE(content->rejected); -} - -TEST_F(WebRtcSessionTest, CreateOfferWithoutCNCodecs) { - AddCNCodecs(); - Init(); - PeerConnectionInterface::RTCOfferAnswerOptions options; - options.offer_to_receive_audio = - RTCOfferAnswerOptions::kOfferToReceiveMediaTrue; - options.voice_activity_detection = false; - - std::unique_ptr offer(CreateOffer(options)); - - const cricket::ContentInfo* content = - cricket::GetFirstAudioContent(offer->description()); - EXPECT_TRUE(content != NULL); - EXPECT_TRUE(VerifyNoCNCodecs(content)); -} - -TEST_F(WebRtcSessionTest, CreateAnswerWithoutCNCodecs) { - AddCNCodecs(); - Init(); - // Create a remote offer with audio and video content. - std::unique_ptr offer(CreateRemoteOffer()); - SetRemoteDescriptionWithoutError(offer.release()); - - cricket::MediaSessionOptions options; - options.vad_enabled = false; - std::unique_ptr answer(CreateAnswer(options)); - const cricket::ContentInfo* content = - cricket::GetFirstAudioContent(answer->description()); - ASSERT_TRUE(content != NULL); - EXPECT_TRUE(VerifyNoCNCodecs(content)); -} - -// This test verifies the call setup when remote answer with audio only and -// later updates with video. -TEST_F(WebRtcSessionTest, TestAVOfferWithAudioOnlyAnswer) { - Init(); - EXPECT_TRUE(media_engine_->GetVideoChannel(0) == NULL); - EXPECT_TRUE(media_engine_->GetVoiceChannel(0) == NULL); - - SendAudioVideoStream1(); - SessionDescriptionInterface* offer = CreateOffer(); - - cricket::MediaSessionOptions options; - AddMediaSection(cricket::MEDIA_TYPE_AUDIO, cricket::CN_AUDIO, - cricket::MD_RECVONLY, kActive, &options); - AddMediaSection(cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO, - cricket::MD_INACTIVE, kStopped, &options); - local_recv_video_ = false; - SessionDescriptionInterface* answer = CreateRemoteAnswer(offer, options); - - // SetLocalDescription and SetRemoteDescriptions takes ownership of offer - // and answer; - SetLocalDescriptionWithoutError(offer); - SetRemoteDescriptionWithoutError(answer); - - video_channel_ = media_engine_->GetVideoChannel(0); - voice_channel_ = media_engine_->GetVoiceChannel(0); - - ASSERT_TRUE(video_channel_ == nullptr); - - ASSERT_EQ(0u, voice_channel_->recv_streams().size()); - ASSERT_EQ(1u, voice_channel_->send_streams().size()); - EXPECT_EQ(kAudioTrack1, voice_channel_->send_streams()[0].id); - - // Let the remote end update the session descriptions, with Audio and Video. - SendAudioVideoStream2(); - local_recv_video_ = true; - CreateAndSetRemoteOfferAndLocalAnswer(); - - video_channel_ = media_engine_->GetVideoChannel(0); - voice_channel_ = media_engine_->GetVoiceChannel(0); - - ASSERT_TRUE(video_channel_ != nullptr); - ASSERT_TRUE(voice_channel_ != nullptr); - - ASSERT_EQ(1u, video_channel_->recv_streams().size()); - ASSERT_EQ(1u, video_channel_->send_streams().size()); - EXPECT_EQ(kVideoTrack2, video_channel_->recv_streams()[0].id); - EXPECT_EQ(kVideoTrack2, video_channel_->send_streams()[0].id); - ASSERT_EQ(1u, voice_channel_->recv_streams().size()); - ASSERT_EQ(1u, voice_channel_->send_streams().size()); - EXPECT_EQ(kAudioTrack2, voice_channel_->recv_streams()[0].id); - EXPECT_EQ(kAudioTrack2, voice_channel_->send_streams()[0].id); - - // Change session back to audio only. - // The remote side doesn't send and recv video. - SendAudioOnlyStream2(); - remote_recv_video_ = false; - CreateAndSetRemoteOfferAndLocalAnswer(); - - video_channel_ = media_engine_->GetVideoChannel(0); - voice_channel_ = media_engine_->GetVoiceChannel(0); - - // The audio is expected to be rejected. - EXPECT_TRUE(video_channel_ == nullptr); - - ASSERT_EQ(1u, voice_channel_->recv_streams().size()); - EXPECT_EQ(kAudioTrack2, voice_channel_->recv_streams()[0].id); - ASSERT_EQ(1u, voice_channel_->send_streams().size()); - EXPECT_EQ(kAudioTrack2, voice_channel_->send_streams()[0].id); -} - -// This test verifies the call setup when remote answer with video only and -// later updates with audio. -TEST_F(WebRtcSessionTest, TestAVOfferWithVideoOnlyAnswer) { - Init(); - EXPECT_TRUE(media_engine_->GetVideoChannel(0) == NULL); - EXPECT_TRUE(media_engine_->GetVoiceChannel(0) == NULL); - SendAudioVideoStream1(); - SessionDescriptionInterface* offer = CreateOffer(); - - cricket::MediaSessionOptions options; - AddMediaSection(cricket::MEDIA_TYPE_AUDIO, cricket::CN_AUDIO, - cricket::MD_INACTIVE, kStopped, &options); - AddMediaSection(cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO, - cricket::MD_RECVONLY, kActive, &options); - local_recv_audio_ = false; - SessionDescriptionInterface* answer = - CreateRemoteAnswer(offer, options, cricket::SEC_ENABLED); - - // SetLocalDescription and SetRemoteDescriptions takes ownership of offer - // and answer. - SetLocalDescriptionWithoutError(offer); - SetRemoteDescriptionWithoutError(answer); - - video_channel_ = media_engine_->GetVideoChannel(0); - voice_channel_ = media_engine_->GetVoiceChannel(0); - - ASSERT_TRUE(voice_channel_ == NULL); - ASSERT_TRUE(video_channel_ != NULL); - - EXPECT_EQ(0u, video_channel_->recv_streams().size()); - ASSERT_EQ(1u, video_channel_->send_streams().size()); - EXPECT_EQ(kVideoTrack1, video_channel_->send_streams()[0].id); - - // Update the session descriptions, with Audio and Video. - SendAudioVideoStream2(); - local_recv_audio_ = true; - SessionDescriptionInterface* offer2 = CreateRemoteOffer(); - SetRemoteDescriptionWithoutError(offer2); - cricket::MediaSessionOptions answer_options; - // Disable the bundling here. If the media is bundled on audio - // transport, then we can't reject the audio because switching the bundled - // transport is not currently supported. - // (https://bugs.chromium.org/p/webrtc/issues/detail?id=6704) - answer_options.bundle_enabled = false; - SessionDescriptionInterface* answer2 = CreateAnswer(answer_options); - SetLocalDescriptionWithoutError(answer2); - - voice_channel_ = media_engine_->GetVoiceChannel(0); - - ASSERT_TRUE(voice_channel_ != NULL); - ASSERT_EQ(1u, voice_channel_->recv_streams().size()); - ASSERT_EQ(1u, voice_channel_->send_streams().size()); - EXPECT_EQ(kAudioTrack2, voice_channel_->recv_streams()[0].id); - EXPECT_EQ(kAudioTrack2, voice_channel_->send_streams()[0].id); - - // Change session back to video only. - // The remote side doesn't send and recv audio. - SendVideoOnlyStream2(); - remote_recv_audio_ = false; - SessionDescriptionInterface* offer3 = CreateRemoteOffer(); - SetRemoteDescriptionWithoutError(offer3); - SessionDescriptionInterface* answer3 = CreateAnswer(answer_options); - SetLocalDescriptionWithoutError(answer3); - - video_channel_ = media_engine_->GetVideoChannel(0); - voice_channel_ = media_engine_->GetVoiceChannel(0); - - // The video is expected to be rejected. - EXPECT_TRUE(voice_channel_ == nullptr); - - ASSERT_EQ(1u, video_channel_->recv_streams().size()); - EXPECT_EQ(kVideoTrack2, video_channel_->recv_streams()[0].id); - ASSERT_EQ(1u, video_channel_->send_streams().size()); - EXPECT_EQ(kVideoTrack2, video_channel_->send_streams()[0].id); -} - // Test that candidates sent to the "video" transport do not get pushed down to // the "audio" transport channel when bundling. TEST_F(WebRtcSessionTest, TestIgnoreCandidatesForUnusedTransportWhenBundling) { @@ -2458,127 +1604,6 @@ TEST_F(WebRtcSessionTest, TestDisabledRtcpMuxWithBundleEnabled) { SetLocalDescriptionWithoutError(offer); } -// This test verifies the |initial_offerer| flag when session initiates the -// call. -TEST_F(WebRtcSessionTest, TestInitiatorFlagAsOriginator) { - Init(); - EXPECT_FALSE(session_->initial_offerer()); - SessionDescriptionInterface* offer = CreateOffer(); - SessionDescriptionInterface* answer = CreateRemoteAnswer(offer); - SetLocalDescriptionWithoutError(offer); - EXPECT_TRUE(session_->initial_offerer()); - SetRemoteDescriptionWithoutError(answer); - EXPECT_TRUE(session_->initial_offerer()); -} - -// This test verifies the |initial_offerer| flag when session receives the call. -TEST_F(WebRtcSessionTest, TestInitiatorFlagAsReceiver) { - Init(); - EXPECT_FALSE(session_->initial_offerer()); - SessionDescriptionInterface* offer = CreateRemoteOffer(); - SetRemoteDescriptionWithoutError(offer); - SessionDescriptionInterface* answer = CreateAnswer(); - - EXPECT_FALSE(session_->initial_offerer()); - SetLocalDescriptionWithoutError(answer); - EXPECT_FALSE(session_->initial_offerer()); -} - -// Verifing local offer and remote answer have matching m-lines as per RFC 3264. -TEST_F(WebRtcSessionTest, TestIncorrectMLinesInRemoteAnswer) { - Init(); - SendAudioVideoStream1(); - SessionDescriptionInterface* offer = CreateOffer(); - SetLocalDescriptionWithoutError(offer); - std::unique_ptr answer( - CreateRemoteAnswer(session_->local_description())); - - cricket::SessionDescription* answer_copy = answer->description()->Copy(); - answer_copy->RemoveContentByName("video"); - JsepSessionDescription* modified_answer = - new JsepSessionDescription(JsepSessionDescription::kAnswer); - - EXPECT_TRUE(modified_answer->Initialize(answer_copy, - answer->session_id(), - answer->session_version())); - SetRemoteDescriptionAnswerExpectError(kMlineMismatchInAnswer, - modified_answer); - - // Different content names. - std::string sdp; - EXPECT_TRUE(answer->ToString(&sdp)); - const std::string kAudioMid = "a=mid:audio"; - const std::string kAudioMidReplaceStr = "a=mid:audio_content_name"; - rtc::replace_substrs(kAudioMid.c_str(), kAudioMid.length(), - kAudioMidReplaceStr.c_str(), - kAudioMidReplaceStr.length(), - &sdp); - SessionDescriptionInterface* modified_answer1 = - CreateSessionDescription(JsepSessionDescription::kAnswer, sdp, NULL); - SetRemoteDescriptionAnswerExpectError(kMlineMismatchInAnswer, - modified_answer1); - - // Different media types. - EXPECT_TRUE(answer->ToString(&sdp)); - const std::string kAudioMline = "m=audio"; - const std::string kAudioMlineReplaceStr = "m=video"; - rtc::replace_substrs(kAudioMline.c_str(), kAudioMline.length(), - kAudioMlineReplaceStr.c_str(), - kAudioMlineReplaceStr.length(), - &sdp); - SessionDescriptionInterface* modified_answer2 = - CreateSessionDescription(JsepSessionDescription::kAnswer, sdp, NULL); - SetRemoteDescriptionAnswerExpectError(kMlineMismatchInAnswer, - modified_answer2); - - SetRemoteDescriptionWithoutError(answer.release()); -} - -// Verifying remote offer and local answer have matching m-lines as per -// RFC 3264. -TEST_F(WebRtcSessionTest, TestIncorrectMLinesInLocalAnswer) { - Init(); - SendAudioVideoStream1(); - SessionDescriptionInterface* offer = CreateRemoteOffer(); - SetRemoteDescriptionWithoutError(offer); - SessionDescriptionInterface* answer = CreateAnswer(); - - cricket::SessionDescription* answer_copy = answer->description()->Copy(); - answer_copy->RemoveContentByName("video"); - JsepSessionDescription* modified_answer = - new JsepSessionDescription(JsepSessionDescription::kAnswer); - - EXPECT_TRUE(modified_answer->Initialize(answer_copy, - answer->session_id(), - answer->session_version())); - SetLocalDescriptionAnswerExpectError(kMlineMismatchInAnswer, modified_answer); - SetLocalDescriptionWithoutError(answer); -} - -TEST_F(WebRtcSessionTest, TestSessionContentError) { - Init(); - SendAudioVideoStream1(); - SessionDescriptionInterface* offer = CreateOffer(); - const std::string session_id_orig = offer->session_id(); - const std::string session_version_orig = offer->session_version(); - SetLocalDescriptionWithoutError(offer); - - video_channel_ = media_engine_->GetVideoChannel(0); - video_channel_->set_fail_set_send_codecs(true); - - SessionDescriptionInterface* answer = - CreateRemoteAnswer(session_->local_description()); - SetRemoteDescriptionAnswerExpectError("ERROR_CONTENT", answer); - - // Test that after a content error, setting any description will - // result in an error. - video_channel_->set_fail_set_send_codecs(false); - answer = CreateRemoteAnswer(session_->local_description()); - SetRemoteDescriptionExpectError("", "ERROR_CONTENT", answer); - offer = CreateRemoteOffer(); - SetLocalDescriptionExpectError("", "ERROR_CONTENT", offer); -} - TEST_F(WebRtcSessionTest, TestRtpDataChannel) { configuration_.enable_rtp_data_channel = true; Init(); @@ -2757,21 +1782,6 @@ TEST_P(WebRtcSessionTest, TestSctpDataChannelOpenMessage) { last_data_channel_config_.open_handshake_role); } -TEST_F(WebRtcSessionTest, TestCombinedAudioVideoBweConstraint) { - configuration_.combined_audio_video_bwe = rtc::Optional(true); - Init(); - SendAudioVideoStream1(); - SessionDescriptionInterface* offer = CreateOffer(); - - SetLocalDescriptionWithoutError(offer); - - voice_channel_ = media_engine_->GetVoiceChannel(0); - - ASSERT_TRUE(voice_channel_ != NULL); - const cricket::AudioOptions& audio_options = voice_channel_->options(); - EXPECT_EQ(rtc::Optional(true), audio_options.combined_audio_video_bwe); -} - #ifdef HAVE_QUIC TEST_P(WebRtcSessionTest, TestNegotiateQuic) { configuration_.enable_quic = true; @@ -2791,33 +1801,6 @@ TEST_P(WebRtcSessionTest, TestNegotiateQuic) { } #endif // HAVE_QUIC -// Tests that RTX codec is removed from the answer when it isn't supported -// by local side. -TEST_F(WebRtcSessionTest, TestRtxRemovedByCreateAnswer) { - Init(); - // Send video only to match the |kSdpWithRtx|. - SendVideoOnlyStream2(); - std::string offer_sdp(kSdpWithRtx); - - SessionDescriptionInterface* offer = - CreateSessionDescription(JsepSessionDescription::kOffer, offer_sdp, NULL); - EXPECT_TRUE(offer->ToString(&offer_sdp)); - - // Offer SDP contains the RTX codec. - EXPECT_TRUE(ContainsVideoCodecWithName(offer, "rtx")); - SetRemoteDescriptionWithoutError(offer); - - // |offered_media_sections_| is used when creating answer. - offered_media_sections_.push_back(cricket::MediaDescriptionOptions( - cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO, - cricket::RtpTransceiverDirection(true, true), false)); - // Don't create media section for audio in the answer. - SessionDescriptionInterface* answer = CreateAnswer(); - // Answer SDP does not contain the RTX codec. - EXPECT_FALSE(ContainsVideoCodecWithName(answer, "rtx")); - SetLocalDescriptionWithoutError(answer); -} - // This verifies that the voice channel after bundle has both options from video // and voice channels. TEST_F(WebRtcSessionTest, TestSetSocketOptionBeforeBundle) { @@ -2866,34 +1849,6 @@ TEST_F(WebRtcSessionTest, TestSetSocketOptionBeforeBundle) { EXPECT_EQ(8000, option_val); } -// Test creating a session, request multiple offers, destroy the session -// and make sure we got success/failure callbacks for all of the requests. -// Background: crbug.com/507307 -TEST_F(WebRtcSessionTest, CreateOffersAndShutdown) { - Init(); - - rtc::scoped_refptr observers[100]; - PeerConnectionInterface::RTCOfferAnswerOptions options; - options.offer_to_receive_audio = - RTCOfferAnswerOptions::kOfferToReceiveMediaTrue; - cricket::MediaSessionOptions session_options; - GetOptionsForOffer(options, &session_options); - for (auto& o : observers) { - o = new WebRtcSessionCreateSDPObserverForTest(); - session_->CreateOffer(o, options, session_options); - } - - session_.reset(); - - for (auto& o : observers) { - // We expect to have received a notification now even if the session was - // terminated. The offer creation may or may not have succeeded, but we - // must have received a notification which, so the only invalid state - // is kInit. - EXPECT_NE(WebRtcSessionCreateSDPObserverForTest::kInit, o->state()); - } -} - TEST_F(WebRtcSessionTest, TestPacketOptionsAndOnPacketSent) { TestPacketOptions(); }