diff --git a/media/base/fakemediaengine.h b/media/base/fakemediaengine.h index 7b09dd4f0c..29a129f781 100644 --- a/media/base/fakemediaengine.h +++ b/media/base/fakemediaengine.h @@ -488,11 +488,12 @@ class FakeVoiceMediaChannel : public RtpHelper { if (it != local_sinks_.end()) { RTC_CHECK(it->second->source() == source); } else { - local_sinks_.insert(std::make_pair( - ssrc, rtc::MakeUnique(source))); + local_sinks_.insert( + std::make_pair(ssrc, new VoiceChannelAudioSink(source))); } } else { if (it != local_sinks_.end()) { + delete it->second; local_sinks_.erase(it); } } @@ -505,7 +506,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 02a8e9a727..c77a638b39 100644 --- a/pc/BUILD.gn +++ b/pc/BUILD.gn @@ -394,9 +394,7 @@ 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", @@ -465,9 +463,7 @@ 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 7cad0e1121..9eb12d694c 100644 --- a/pc/peerconnection.cc +++ b/pc/peerconnection.cc @@ -828,7 +828,10 @@ 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| @@ -845,19 +848,11 @@ 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; @@ -874,12 +869,20 @@ 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)) { @@ -889,7 +892,9 @@ void PeerConnection::CreateAnswer( return; } - CreateAnswer(observer, offer_answer_options); + cricket::MediaSessionOptions session_options; + GetOptionsForAnswer(offer_answer_options, &session_options); + session_->CreateAnswer(observer, session_options); } void PeerConnection::CreateAnswer(CreateSessionDescriptionObserver* observer, @@ -900,22 +905,6 @@ 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); @@ -926,6 +915,9 @@ 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; @@ -934,23 +926,11 @@ 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(std::move(desc_temp), &error)) { + if (!session_->SetLocalDescription(desc, &error)) { PostSetSessionDescriptionFailure(observer, error); return; } @@ -1031,6 +1011,9 @@ 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; @@ -1039,23 +1022,11 @@ 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(std::move(desc_temp), &error)) { + if (!session_->SetRemoteDescription(desc, &error)) { PostSetSessionDescriptionFailure(observer, error); return; } @@ -1090,15 +1061,6 @@ 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 68eec08df9..081e11ac6e 100644 --- a/pc/peerconnection_crypto_unittest.cc +++ b/pc/peerconnection_crypto_unittest.cc @@ -75,8 +75,7 @@ class PeerConnectionCryptoUnitTest : public ::testing::Test { if (!wrapper) { return nullptr; } - wrapper->AddAudioTrack("a"); - wrapper->AddVideoTrack("v"); + wrapper->AddAudioVideoStream("s", "a", "v"); return wrapper; } diff --git a/pc/peerconnection_ice_unittest.cc b/pc/peerconnection_ice_unittest.cc index 3ab9acb267..088001841d 100644 --- a/pc/peerconnection_ice_unittest.cc +++ b/pc/peerconnection_ice_unittest.cc @@ -120,8 +120,7 @@ class PeerConnectionIceUnitTest : public ::testing::Test { if (!wrapper) { return nullptr; } - wrapper->AddAudioTrack("a"); - wrapper->AddVideoTrack("v"); + wrapper->AddAudioVideoStream("s", "a", "v"); return wrapper; } diff --git a/pc/peerconnection_media_unittest.cc b/pc/peerconnection_media_unittest.cc deleted file mode 100644 index f106bbe3ea..0000000000 --- a/pc/peerconnection_media_unittest.cc +++ /dev/null @@ -1,889 +0,0 @@ -/* - * 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 deleted file mode 100644 index caaac4c0ba..0000000000 --- a/pc/peerconnection_signaling_unittest.cc +++ /dev/null @@ -1,501 +0,0 @@ -/* - * 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 a8b4f724af..99f3301f82 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_FALSE(DoCreateOffer(&offer, nullptr)); + EXPECT_TRUE(DoCreateOffer(&offer, nullptr)); std::unique_ptr answer; - EXPECT_FALSE(DoCreateAnswer(&answer, nullptr)); + EXPECT_TRUE(DoCreateAnswer(&answer, nullptr)); std::string sdp; ASSERT_TRUE(pc_->remote_description()->ToString(&sdp)); @@ -3558,6 +3558,32 @@ 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) { @@ -3638,6 +3664,42 @@ 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. @@ -3672,6 +3734,52 @@ 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 9be93096a3..dd114607d0 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), observer_(std::move(observer)), pc_(pc) { + : pc_factory_(pc_factory), pc_(pc), observer_(std::move(observer)) { RTC_DCHECK(pc_factory_); RTC_DCHECK(pc_); RTC_DCHECK(observer_); @@ -57,25 +57,15 @@ PeerConnectionWrapper::CreateOffer() { } std::unique_ptr PeerConnectionWrapper::CreateOffer( - const PeerConnectionInterface::RTCOfferAnswerOptions& options, - std::string* error_out) { - return CreateSdp( - [this, options](CreateSessionDescriptionObserver* observer) { - pc()->CreateOffer(observer, options); - }, - error_out); + const PeerConnectionInterface::RTCOfferAnswerOptions& options) { + return CreateSdp([this, options](CreateSessionDescriptionObserver* observer) { + pc()->CreateOffer(observer, options); + }); } std::unique_ptr PeerConnectionWrapper::CreateOfferAndSetAsLocal() { - return CreateOfferAndSetAsLocal( - PeerConnectionInterface::RTCOfferAnswerOptions()); -} - -std::unique_ptr -PeerConnectionWrapper::CreateOfferAndSetAsLocal( - const PeerConnectionInterface::RTCOfferAnswerOptions& options) { - auto offer = CreateOffer(options); + auto offer = CreateOffer(); if (!offer) { return nullptr; } @@ -90,25 +80,15 @@ PeerConnectionWrapper::CreateAnswer() { std::unique_ptr PeerConnectionWrapper::CreateAnswer( - const PeerConnectionInterface::RTCOfferAnswerOptions& options, - std::string* error_out) { - return CreateSdp( - [this, options](CreateSessionDescriptionObserver* observer) { - pc()->CreateAnswer(observer, options); - }, - error_out); + const PeerConnectionInterface::RTCOfferAnswerOptions& options) { + return CreateSdp([this, options](CreateSessionDescriptionObserver* observer) { + pc()->CreateAnswer(observer, options); + }); } std::unique_ptr PeerConnectionWrapper::CreateAnswerAndSetAsLocal() { - return CreateAnswerAndSetAsLocal( - PeerConnectionInterface::RTCOfferAnswerOptions()); -} - -std::unique_ptr -PeerConnectionWrapper::CreateAnswerAndSetAsLocal( - const PeerConnectionInterface::RTCOfferAnswerOptions& options) { - auto answer = CreateAnswer(options); + auto answer = CreateAnswer(); if (!answer) { return nullptr; } @@ -117,72 +97,73 @@ PeerConnectionWrapper::CreateAnswerAndSetAsLocal( } std::unique_ptr PeerConnectionWrapper::CreateSdp( - std::function fn, - std::string* error_out) { + std::function fn) { 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, - std::string* error_out) { - return SetSdp( - [this, &desc](SetSessionDescriptionObserver* observer) { - pc()->SetLocalDescription(observer, desc.release()); - }, - error_out); + std::unique_ptr desc) { + return SetSdp([this, &desc](SetSessionDescriptionObserver* observer) { + pc()->SetLocalDescription(observer, desc.release()); + }); } bool PeerConnectionWrapper::SetRemoteDescription( - std::unique_ptr desc, - std::string* error_out) { - return SetSdp( - [this, &desc](SetSessionDescriptionObserver* observer) { - pc()->SetRemoteDescription(observer, desc.release()); - }, - error_out); + std::unique_ptr desc) { + return SetSdp([this, &desc](SetSessionDescriptionObserver* observer) { + pc()->SetRemoteDescription(observer, desc.release()); + }); } bool PeerConnectionWrapper::SetSdp( - std::function fn, - std::string* error_out) { + std::function fn) { 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(); + if (pc()->signaling_state() != PeerConnectionInterface::kClosed) { + EXPECT_EQ_WAIT(true, observer->called(), kWaitTimeout); } return observer->result(); } -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::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::AddVideoTrack( - const std::string& track_label, - std::vector streams) { +void PeerConnectionWrapper::AddVideoStream(const std::string& stream_label, + const std::string& track_label) { + auto stream = pc_factory()->CreateLocalMediaStream(stream_label); auto video_source = pc_factory()->CreateVideoSource( rtc::MakeUnique()); - auto media_stream_track = - pc_factory()->CreateVideoTrack(track_label, video_source); - return pc()->AddTrack(media_stream_track, streams); + 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; } -PeerConnectionInterface::SignalingState -PeerConnectionWrapper::signaling_state() { - return pc()->signaling_state(); +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; } bool PeerConnectionWrapper::IsIceGatheringDone() { diff --git a/pc/peerconnectionwrapper.h b/pc/peerconnectionwrapper.h index f74fcdb85a..783ae3827b 100644 --- a/pc/peerconnectionwrapper.h +++ b/pc/peerconnectionwrapper.h @@ -54,69 +54,54 @@ class PeerConnectionWrapper { // resulting SessionDescription once it is available. If the method call // failed, null is returned. std::unique_ptr CreateOffer( - const PeerConnectionInterface::RTCOfferAnswerOptions& options, - std::string* error_out = nullptr); + const PeerConnectionInterface::RTCOfferAnswerOptions& options); // 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, - std::string* error_out = nullptr); + const PeerConnectionInterface::RTCOfferAnswerOptions& options); // 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, - std::string* error_out = nullptr); + bool SetLocalDescription(std::unique_ptr desc); // 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, - std::string* error_out = nullptr); + bool SetRemoteDescription(std::unique_ptr desc); - // 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(); + // 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); // Returns true if ICE has finished gathering candidates. bool IsIceGatheringDone(); private: std::unique_ptr CreateSdp( - std::function fn, - std::string* error_out); - bool SetSdp(std::function fn, - std::string* error_out); + std::function fn); + bool SetSdp(std::function fn); rtc::scoped_refptr pc_factory_; - std::unique_ptr observer_; rtc::scoped_refptr pc_; + std::unique_ptr observer_; }; } // namespace webrtc diff --git a/pc/sdputils.cc b/pc/sdputils.cc index 8932bea033..9339fdb874 100644 --- a/pc/sdputils.cc +++ b/pc/sdputils.cc @@ -20,14 +20,7 @@ namespace webrtc { std::unique_ptr CloneSessionDescription( const SessionDescriptionInterface* sdesc) { RTC_DCHECK(sdesc); - return CloneSessionDescriptionAsType(sdesc, sdesc->type()); -} - -std::unique_ptr CloneSessionDescriptionAsType( - const SessionDescriptionInterface* sdesc, - const std::string& type) { - RTC_DCHECK(sdesc); - auto clone = rtc::MakeUnique(type); + auto clone = rtc::MakeUnique(sdesc->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 3a53a41756..7d67fd8d72 100644 --- a/pc/sdputils.h +++ b/pc/sdputils.h @@ -23,11 +23,6 @@ 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_; - std::string error_; + bool result_; std::unique_ptr desc_; }; @@ -206,23 +205,22 @@ class MockSetSessionDescriptionObserver public: MockSetSessionDescriptionObserver() : called_(false), - error_("MockSetSessionDescriptionObserver not called") {} + result_(false) {} virtual ~MockSetSessionDescriptionObserver() {} virtual void OnSuccess() { called_ = true; - error_ = ""; + result_ = true; } virtual void OnFailure(const std::string& error) { called_ = true; - error_ = error; + result_ = false; } bool called() const { return called_; } - bool result() const { return error_.empty(); } - const std::string& error() const { return error_; } + bool result() const { return result_; } private: bool called_; - std::string error_; + bool result_; }; class MockDataChannelObserver : public webrtc::DataChannelObserver { diff --git a/pc/webrtcsession.cc b/pc/webrtcsession.cc index 2e0ae50d4c..f556204c6c 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,13 +707,15 @@ void WebRtcSession::CreateAnswer( webrtc_session_desc_factory_->CreateAnswer(observer, session_options); } -bool WebRtcSession::SetLocalDescription( - std::unique_ptr desc, - std::string* err_desc) { +bool WebRtcSession::SetLocalDescription(SessionDescriptionInterface* 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.get(), cricket::CS_LOCAL, err_desc)) { + if (!ValidateSessionDescription(desc, cricket::CS_LOCAL, err_desc)) { return false; } @@ -725,19 +727,18 @@ bool WebRtcSession::SetLocalDescription( } if (action == kAnswer) { - current_local_description_ = std::move(desc); - pending_local_description_ = nullptr; - current_remote_description_ = std::move(pending_remote_description_); + current_local_description_.reset(desc_temp.release()); + pending_local_description_.reset(nullptr); + current_remote_description_.reset(pending_remote_description_.release()); } else { - pending_local_description_ = std::move(desc); + pending_local_description_.reset(desc_temp.release()); } // 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(local_description()->type(), kCreateChannelFailed, - err_desc); + return BadLocalSdp(desc->type(), kCreateChannelFailed, err_desc); } // Remove unused channels if MediaContentDescription is rejected. @@ -753,54 +754,50 @@ bool WebRtcSession::SetLocalDescription( pending_ice_restarts_.clear(); if (error() != ERROR_NONE) { - return BadLocalSdp(local_description()->type(), GetSessionErrorMsg(), - err_desc); + return BadLocalSdp(desc->type(), GetSessionErrorMsg(), err_desc); } return true; } -bool WebRtcSession::SetRemoteDescription( - std::unique_ptr desc, - std::string* err_desc) { +bool WebRtcSession::SetRemoteDescription(SessionDescriptionInterface* 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.get(), cricket::CS_REMOTE, err_desc)) { + if (!ValidateSessionDescription(desc, 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 as |old_remote_description|. + // method, since it's used below. std::unique_ptr replaced_remote_description; Action action = GetAction(desc->type()); if (action == kAnswer) { - 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_); + 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()); } else { - replaced_remote_description = std::move(pending_remote_description_); - pending_remote_description_ = std::move(desc); + replaced_remote_description.reset(pending_remote_description_.release()); + pending_remote_description_.reset(desc_temp.release()); } // Transport and Media channels will be created only when offer is set. - if (action == kOffer && - !CreateChannels(remote_description()->description())) { + if (action == kOffer && !CreateChannels(desc->description())) { // TODO(mallinath) - Handle CreateChannel failure, as new local description // is applied. Restore back to old description. - return BadRemoteSdp(remote_description()->type(), kCreateChannelFailed, - err_desc); + return BadRemoteSdp(desc->type(), kCreateChannelFailed, err_desc); } // Remove unused channels if MediaContentDescription is rejected. - RemoveUnusedChannels(remote_description()->description()); + RemoveUnusedChannels(desc->description()); // NOTE: Candidates allocation will be initiated only when SetLocalDescription // is called. @@ -808,10 +805,8 @@ bool WebRtcSession::SetRemoteDescription( return false; } - if (local_description() && - !UseCandidatesInSessionDescription(remote_description())) { - return BadRemoteSdp(remote_description()->type(), kInvalidCandidates, - err_desc); + if (local_description() && !UseCandidatesInSessionDescription(desc)) { + return BadRemoteSdp(desc->type(), kInvalidCandidates, err_desc); } if (old_remote_description) { @@ -822,7 +817,7 @@ bool WebRtcSession::SetRemoteDescription( // 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, remote_description(), + if (CheckForRemoteIceRestart(old_remote_description, desc, content.name)) { if (action == kOffer) { pending_ice_restarts_.insert(content.name); @@ -836,14 +831,13 @@ bool WebRtcSession::SetRemoteDescription( // 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_ptr); + old_remote_description, content.name, desc); } } } if (error() != ERROR_NONE) { - return BadRemoteSdp(remote_description()->type(), GetSessionErrorMsg(), - err_desc); + return BadRemoteSdp(desc->type(), GetSessionErrorMsg(), err_desc); } // Set the the ICE connection state to connecting since the connection may @@ -854,7 +848,7 @@ bool WebRtcSession::SetRemoteDescription( // 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 (remote_description()->type() != SessionDescriptionInterface::kOffer && + if (desc->type() != SessionDescriptionInterface::kOffer && ice_connection_state_ == PeerConnectionInterface::kIceConnectionNew) { SetIceConnectionState(PeerConnectionInterface::kIceConnectionChecking); } diff --git a/pc/webrtcsession.h b/pc/webrtcsession.h index 185fa05ed4..16c3931b35 100644 --- a/pc/webrtcsession.h +++ b/pc/webrtcsession.h @@ -254,9 +254,11 @@ class WebRtcSession : const cricket::MediaSessionOptions& session_options); void CreateAnswer(CreateSessionDescriptionObserver* observer, const cricket::MediaSessionOptions& session_options); - bool SetLocalDescription(std::unique_ptr desc, + // The ownership of |desc| will be transferred after this call. + bool SetLocalDescription(SessionDescriptionInterface* desc, std::string* err_desc); - bool SetRemoteDescription(std::unique_ptr desc, + // The ownership of |desc| will be transferred after this call. + bool SetRemoteDescription(SessionDescriptionInterface* desc, std::string* err_desc); bool ProcessIceMessage(const IceCandidateInterface* ice_candidate); diff --git a/pc/webrtcsession_unittest.cc b/pc/webrtcsession_unittest.cc index 0c54abc9cb..25cb35a6a9 100644 --- a/pc/webrtcsession_unittest.cc +++ b/pc/webrtcsession_unittest.cc @@ -92,6 +92,26 @@ 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"; @@ -100,6 +120,7 @@ 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 }; @@ -444,6 +465,24 @@ 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; @@ -462,6 +501,19 @@ 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 @@ -636,6 +688,13 @@ 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_, @@ -752,15 +811,30 @@ 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(rtc::WrapUnique(desc), nullptr)); + ASSERT_TRUE(session_->SetLocalDescription(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(rtc::WrapUnique(desc), &error)); + EXPECT_FALSE(session_->SetLocalDescription(desc, &error)); std::string sdp_type = "local "; sdp_type.append(action); EXPECT_NE(std::string::npos, error.find(sdp_type)); @@ -771,14 +845,24 @@ 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(rtc::WrapUnique(desc), nullptr)); + ASSERT_TRUE(session_->SetRemoteDescription(desc, nullptr)); + } + void SetRemoteDescriptionExpectState(SessionDescriptionInterface* desc, + WebRtcSession::State expected_state) { + SetRemoteDescriptionWithoutError(desc); + EXPECT_EQ(expected_state, session_->state()); } void SetRemoteDescriptionExpectError(const std::string& action, const std::string& expected_error, SessionDescriptionInterface* desc) { std::string error; - EXPECT_FALSE(session_->SetRemoteDescription(rtc::WrapUnique(desc), &error)); + EXPECT_FALSE(session_->SetRemoteDescription(desc, &error)); std::string sdp_type = "remote "; sdp_type.append(action); EXPECT_NE(std::string::npos, error.find(sdp_type)); @@ -789,6 +873,11 @@ 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, @@ -946,6 +1035,23 @@ 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. @@ -1009,6 +1115,33 @@ 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()); @@ -1081,6 +1214,141 @@ 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 @@ -1144,6 +1412,592 @@ 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) { @@ -1604,6 +2458,127 @@ 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(); @@ -1782,6 +2757,21 @@ 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; @@ -1801,6 +2791,33 @@ 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) { @@ -1849,6 +2866,34 @@ 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(); }