| /* | 
 |  *  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. | 
 |  */ | 
 |  | 
 | #include "api/audio_codecs/builtin_audio_decoder_factory.h" | 
 | #include "api/audio_codecs/builtin_audio_encoder_factory.h" | 
 | #include "media/engine/webrtcmediaengine.h" | 
 | #include "modules/audio_processing/include/audio_processing.h" | 
 | #include "pc/mediasession.h" | 
 | #include "pc/peerconnectionfactory.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/fakesctptransport.h" | 
 | #include "rtc_base/gunit.h" | 
 | #include "rtc_base/ptr_util.h" | 
 | #include "rtc_base/virtualsocketserver.h" | 
 | #include "test/gmock.h" | 
 |  | 
 | // This file contains tests that ensure the PeerConnection's implementation of | 
 | // CreateOffer/CreateAnswer/SetLocalDescription/SetRemoteDescription conform | 
 | // to the JavaScript Session Establishment Protocol (JSEP). | 
 | // For now these semantics are only available when configuring the | 
 | // PeerConnection with Unified Plan, but eventually that will be the default. | 
 |  | 
 | namespace webrtc { | 
 |  | 
 | using cricket::MediaContentDescription; | 
 | using RTCConfiguration = PeerConnectionInterface::RTCConfiguration; | 
 | using ::testing::Values; | 
 | using ::testing::Combine; | 
 | using ::testing::ElementsAre; | 
 |  | 
 | class PeerConnectionFactoryForJsepTest : public PeerConnectionFactory { | 
 |  public: | 
 |   PeerConnectionFactoryForJsepTest() | 
 |       : PeerConnectionFactory( | 
 |             rtc::Thread::Current(), | 
 |             rtc::Thread::Current(), | 
 |             rtc::Thread::Current(), | 
 |             rtc::WrapUnique(cricket::WebRtcMediaEngineFactory::Create( | 
 |                 FakeAudioCaptureModule::Create(), | 
 |                 CreateBuiltinAudioEncoderFactory(), | 
 |                 CreateBuiltinAudioDecoderFactory(), | 
 |                 nullptr, | 
 |                 nullptr, | 
 |                 nullptr, | 
 |                 AudioProcessing::Create())), | 
 |             CreateCallFactory(), | 
 |             nullptr) {} | 
 |  | 
 |   std::unique_ptr<cricket::SctpTransportInternalFactory> | 
 |   CreateSctpTransportInternalFactory() { | 
 |     return rtc::MakeUnique<FakeSctpTransportFactory>(); | 
 |   } | 
 | }; | 
 |  | 
 | class PeerConnectionJsepTest : public ::testing::Test { | 
 |  protected: | 
 |   typedef std::unique_ptr<PeerConnectionWrapper> WrapperPtr; | 
 |  | 
 |   PeerConnectionJsepTest() | 
 |       : vss_(new rtc::VirtualSocketServer()), main_(vss_.get()) { | 
 | #ifdef WEBRTC_ANDROID | 
 |     InitializeAndroidObjects(); | 
 | #endif | 
 |   } | 
 |  | 
 |   WrapperPtr CreatePeerConnection() { | 
 |     RTCConfiguration config; | 
 |     config.sdp_semantics = SdpSemantics::kUnifiedPlan; | 
 |     return CreatePeerConnection(config); | 
 |   } | 
 |  | 
 |   WrapperPtr CreatePeerConnection(const RTCConfiguration& config) { | 
 |     rtc::scoped_refptr<PeerConnectionFactory> pc_factory( | 
 |         new rtc::RefCountedObject<PeerConnectionFactoryForJsepTest>()); | 
 |     RTC_CHECK(pc_factory->Initialize()); | 
 |     auto observer = rtc::MakeUnique<MockPeerConnectionObserver>(); | 
 |     auto pc = pc_factory->CreatePeerConnection(config, nullptr, nullptr, | 
 |                                                observer.get()); | 
 |     if (!pc) { | 
 |       return nullptr; | 
 |     } | 
 |  | 
 |     return rtc::MakeUnique<PeerConnectionWrapper>(pc_factory, pc, | 
 |                                                   std::move(observer)); | 
 |   } | 
 |  | 
 |   std::unique_ptr<rtc::VirtualSocketServer> vss_; | 
 |   rtc::AutoSocketServerThread main_; | 
 | }; | 
 |  | 
 | // Tests for JSEP initial offer generation. | 
 |  | 
 | // Test that an offer created by a PeerConnection with no transceivers generates | 
 | // no media sections. | 
 | TEST_F(PeerConnectionJsepTest, EmptyInitialOffer) { | 
 |   auto caller = CreatePeerConnection(); | 
 |  | 
 |   auto offer = caller->CreateOffer(); | 
 |   ASSERT_EQ(0u, offer->description()->contents().size()); | 
 | } | 
 |  | 
 | // Test that an initial offer with one audio track generates one audio media | 
 | // section. | 
 | TEST_F(PeerConnectionJsepTest, AudioOnlyInitialOffer) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); | 
 |  | 
 |   auto offer = caller->CreateOffer(); | 
 |   auto contents = offer->description()->contents(); | 
 |   ASSERT_EQ(1u, contents.size()); | 
 |   EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, contents[0].media_description()->type()); | 
 | } | 
 |  | 
 | // Test than an initial offer with one video track generates one video media | 
 | // section | 
 | TEST_F(PeerConnectionJsepTest, VideoOnlyInitialOffer) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO); | 
 |  | 
 |   auto offer = caller->CreateOffer(); | 
 |   auto contents = offer->description()->contents(); | 
 |   ASSERT_EQ(1u, contents.size()); | 
 |   EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, contents[0].media_description()->type()); | 
 | } | 
 |  | 
 | // Test that an initial offer with one data channel generates one data media | 
 | // section. | 
 | TEST_F(PeerConnectionJsepTest, DataOnlyInitialOffer) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->CreateDataChannel("dc"); | 
 |  | 
 |   auto offer = caller->CreateOffer(); | 
 |   auto contents = offer->description()->contents(); | 
 |   ASSERT_EQ(1u, contents.size()); | 
 |   EXPECT_EQ(cricket::MEDIA_TYPE_DATA, contents[0].media_description()->type()); | 
 | } | 
 |  | 
 | // Test that creating multiple data channels only results in one data section | 
 | // generated in the offer. | 
 | TEST_F(PeerConnectionJsepTest, MultipleDataChannelsCreateOnlyOneDataSection) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->CreateDataChannel("first"); | 
 |   caller->CreateDataChannel("second"); | 
 |   caller->CreateDataChannel("third"); | 
 |  | 
 |   auto offer = caller->CreateOffer(); | 
 |   ASSERT_EQ(1u, offer->description()->contents().size()); | 
 | } | 
 |  | 
 | // Test that multiple media sections in the initial offer are ordered in the | 
 | // order the transceivers were added to the PeerConnection. This is required by | 
 | // JSEP section 5.2.1. | 
 | TEST_F(PeerConnectionJsepTest, MediaSectionsInInitialOfferOrderedCorrectly) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO); | 
 |   caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); | 
 |   RtpTransceiverInit init; | 
 |   init.direction = RtpTransceiverDirection::kSendOnly; | 
 |   caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init); | 
 |  | 
 |   auto offer = caller->CreateOffer(); | 
 |   auto contents = offer->description()->contents(); | 
 |   ASSERT_EQ(3u, contents.size()); | 
 |  | 
 |   const MediaContentDescription* media_description1 = | 
 |       contents[0].media_description(); | 
 |   EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, media_description1->type()); | 
 |   EXPECT_EQ(RtpTransceiverDirection::kSendRecv, | 
 |             media_description1->direction()); | 
 |  | 
 |   const MediaContentDescription* media_description2 = | 
 |       contents[1].media_description(); | 
 |   EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, media_description2->type()); | 
 |   EXPECT_EQ(RtpTransceiverDirection::kSendRecv, | 
 |             media_description2->direction()); | 
 |  | 
 |   const MediaContentDescription* media_description3 = | 
 |       contents[2].media_description(); | 
 |   EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, media_description3->type()); | 
 |   EXPECT_EQ(RtpTransceiverDirection::kSendOnly, | 
 |             media_description3->direction()); | 
 | } | 
 |  | 
 | // Test that media sections in the initial offer have different mids. | 
 | TEST_F(PeerConnectionJsepTest, MediaSectionsInInitialOfferHaveDifferentMids) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); | 
 |   caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); | 
 |  | 
 |   auto offer = caller->CreateOffer(); | 
 |   auto contents = offer->description()->contents(); | 
 |   ASSERT_EQ(2u, contents.size()); | 
 |   EXPECT_NE(contents[0].name, contents[1].name); | 
 | } | 
 |  | 
 | TEST_F(PeerConnectionJsepTest, | 
 |        StoppedTransceiverHasNoMediaSectionInInitialOffer) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); | 
 |   transceiver->Stop(); | 
 |  | 
 |   auto offer = caller->CreateOffer(); | 
 |   EXPECT_EQ(0u, offer->description()->contents().size()); | 
 | } | 
 |  | 
 | // Tests for JSEP SetLocalDescription with a local offer. | 
 |  | 
 | TEST_F(PeerConnectionJsepTest, SetLocalEmptyOfferCreatesNoTransceivers) { | 
 |   auto caller = CreatePeerConnection(); | 
 |  | 
 |   ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer())); | 
 |  | 
 |   EXPECT_THAT(caller->pc()->GetTransceivers(), ElementsAre()); | 
 |   EXPECT_THAT(caller->pc()->GetSenders(), ElementsAre()); | 
 |   EXPECT_THAT(caller->pc()->GetReceivers(), ElementsAre()); | 
 | } | 
 |  | 
 | TEST_F(PeerConnectionJsepTest, SetLocalOfferSetsTransceiverMid) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   auto audio_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); | 
 |   auto video_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO); | 
 |  | 
 |   auto offer = caller->CreateOffer(); | 
 |   std::string audio_mid = offer->description()->contents()[0].name; | 
 |   std::string video_mid = offer->description()->contents()[1].name; | 
 |  | 
 |   ASSERT_TRUE(caller->SetLocalDescription(std::move(offer))); | 
 |  | 
 |   EXPECT_EQ(audio_mid, audio_transceiver->mid()); | 
 |   EXPECT_EQ(video_mid, video_transceiver->mid()); | 
 | } | 
 |  | 
 | // Tests for JSEP SetRemoteDescription with a remote offer. | 
 |  | 
 | // Test that setting a remote offer with sendrecv audio and video creates two | 
 | // transceivers, one for receiving audio and one for receiving video. | 
 | TEST_F(PeerConnectionJsepTest, SetRemoteOfferCreatesTransceivers) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   auto caller_audio = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); | 
 |   auto caller_video = caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO); | 
 |   auto callee = CreatePeerConnection(); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |  | 
 |   auto transceivers = callee->pc()->GetTransceivers(); | 
 |   ASSERT_EQ(2u, transceivers.size()); | 
 |   EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, | 
 |             transceivers[0]->receiver()->media_type()); | 
 |   EXPECT_EQ(caller_audio->mid(), transceivers[0]->mid()); | 
 |   EXPECT_EQ(RtpTransceiverDirection::kRecvOnly, transceivers[0]->direction()); | 
 |   EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, | 
 |             transceivers[1]->receiver()->media_type()); | 
 |   EXPECT_EQ(caller_video->mid(), transceivers[1]->mid()); | 
 |   EXPECT_EQ(RtpTransceiverDirection::kRecvOnly, transceivers[1]->direction()); | 
 | } | 
 |  | 
 | // Test that setting a remote offer with an audio track will reuse the | 
 | // transceiver created for a local audio track added by AddTrack. | 
 | // This is specified in JSEP section 5.10 (Applying a Remote Description). The | 
 | // intent is to preserve backwards compatibility with clients who only use the | 
 | // AddTrack API. | 
 | TEST_F(PeerConnectionJsepTest, SetRemoteOfferReusesTransceiverFromAddTrack) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddAudioTrack("a"); | 
 |   auto caller_audio = caller->pc()->GetTransceivers()[0]; | 
 |   auto callee = CreatePeerConnection(); | 
 |   callee->AddAudioTrack("a"); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |  | 
 |   auto transceivers = callee->pc()->GetTransceivers(); | 
 |   ASSERT_EQ(1u, transceivers.size()); | 
 |   EXPECT_EQ(MediaStreamTrackInterface::kAudioKind, | 
 |             transceivers[0]->receiver()->track()->kind()); | 
 |   EXPECT_EQ(caller_audio->mid(), transceivers[0]->mid()); | 
 | } | 
 |  | 
 | // Test that setting a remote offer with an audio track marked sendonly will not | 
 | // reuse a transceiver created by AddTrack. JSEP only allows the transceiver to | 
 | // be reused if the offer direction is sendrecv or recvonly. | 
 | TEST_F(PeerConnectionJsepTest, | 
 |        SetRemoteOfferDoesNotReuseTransceiverIfDirectionSendOnly) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddAudioTrack("a"); | 
 |   auto caller_audio = caller->pc()->GetTransceivers()[0]; | 
 |   caller_audio->SetDirection(RtpTransceiverDirection::kSendOnly); | 
 |   auto callee = CreatePeerConnection(); | 
 |   callee->AddAudioTrack("a"); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |  | 
 |   auto transceivers = callee->pc()->GetTransceivers(); | 
 |   ASSERT_EQ(2u, transceivers.size()); | 
 |   EXPECT_EQ(rtc::nullopt, transceivers[0]->mid()); | 
 |   EXPECT_EQ(caller_audio->mid(), transceivers[1]->mid()); | 
 | } | 
 |  | 
 | // Test that setting a remote offer with an audio track will not reuse a | 
 | // transceiver added by AddTransceiver. The logic for reusing a transceiver is | 
 | // specific to those added by AddTrack and is tested above. | 
 | TEST_F(PeerConnectionJsepTest, | 
 |        SetRemoteOfferDoesNotReuseTransceiverFromAddTransceiver) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddAudioTrack("a"); | 
 |   auto callee = CreatePeerConnection(); | 
 |   auto transceiver = callee->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |  | 
 |   auto transceivers = callee->pc()->GetTransceivers(); | 
 |   ASSERT_EQ(2u, transceivers.size()); | 
 |   EXPECT_EQ(rtc::nullopt, transceivers[0]->mid()); | 
 |   EXPECT_EQ(caller->pc()->GetTransceivers()[0]->mid(), transceivers[1]->mid()); | 
 |   EXPECT_EQ(MediaStreamTrackInterface::kAudioKind, | 
 |             transceivers[1]->receiver()->track()->kind()); | 
 | } | 
 |  | 
 | // Test that setting a remote offer with an audio track will not reuse a | 
 | // transceiver created for a local video track added by AddTrack. | 
 | TEST_F(PeerConnectionJsepTest, | 
 |        SetRemoteOfferDoesNotReuseTransceiverOfWrongType) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddAudioTrack("a"); | 
 |   auto callee = CreatePeerConnection(); | 
 |   auto video_sender = callee->AddVideoTrack("v"); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |  | 
 |   auto transceivers = callee->pc()->GetTransceivers(); | 
 |   ASSERT_EQ(2u, transceivers.size()); | 
 |   EXPECT_EQ(rtc::nullopt, transceivers[0]->mid()); | 
 |   EXPECT_EQ(caller->pc()->GetTransceivers()[0]->mid(), transceivers[1]->mid()); | 
 |   EXPECT_EQ(MediaStreamTrackInterface::kAudioKind, | 
 |             transceivers[1]->receiver()->track()->kind()); | 
 | } | 
 |  | 
 | // Test that setting a remote offer with an audio track will not reuse a | 
 | // stopped transceiver. | 
 | TEST_F(PeerConnectionJsepTest, SetRemoteOfferDoesNotReuseStoppedTransceiver) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddAudioTrack("a"); | 
 |   auto callee = CreatePeerConnection(); | 
 |   callee->AddAudioTrack("a"); | 
 |   callee->pc()->GetTransceivers()[0]->Stop(); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |  | 
 |   auto transceivers = callee->pc()->GetTransceivers(); | 
 |   ASSERT_EQ(2u, transceivers.size()); | 
 |   EXPECT_EQ(rtc::nullopt, transceivers[0]->mid()); | 
 |   EXPECT_TRUE(transceivers[0]->stopped()); | 
 |   EXPECT_EQ(caller->pc()->GetTransceivers()[0]->mid(), transceivers[1]->mid()); | 
 |   EXPECT_FALSE(transceivers[1]->stopped()); | 
 | } | 
 |  | 
 | // Test that audio and video transceivers created on the remote side with | 
 | // AddTrack will all be reused if there is the same number of audio/video tracks | 
 | // in the remote offer. Additionally, this tests that transceivers are | 
 | // successfully matched even if they are in a different order on the remote | 
 | // side. | 
 | TEST_F(PeerConnectionJsepTest, SetRemoteOfferReusesTransceiversOfBothTypes) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddVideoTrack("v"); | 
 |   caller->AddAudioTrack("a"); | 
 |   auto callee = CreatePeerConnection(); | 
 |   callee->AddAudioTrack("a"); | 
 |   callee->AddVideoTrack("v"); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |  | 
 |   auto caller_transceivers = caller->pc()->GetTransceivers(); | 
 |   auto callee_transceivers = callee->pc()->GetTransceivers(); | 
 |   ASSERT_EQ(2u, callee_transceivers.size()); | 
 |   EXPECT_EQ(caller_transceivers[0]->mid(), callee_transceivers[1]->mid()); | 
 |   EXPECT_EQ(caller_transceivers[1]->mid(), callee_transceivers[0]->mid()); | 
 | } | 
 |  | 
 | // Tests for JSEP initial CreateAnswer. | 
 |  | 
 | // Test that the answer to a remote offer creates media sections for each | 
 | // offered media in the same order and with the same mids. | 
 | TEST_F(PeerConnectionJsepTest, CreateAnswerHasSameMidsAsOffer) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   auto first_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO); | 
 |   auto second_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); | 
 |   auto third_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO); | 
 |   caller->CreateDataChannel("dc"); | 
 |   auto callee = CreatePeerConnection(); | 
 |  | 
 |   auto offer = caller->CreateOffer(); | 
 |   const auto* offer_data = cricket::GetFirstDataContent(offer->description()); | 
 |   ASSERT_TRUE( | 
 |       caller->SetLocalDescription(CloneSessionDescription(offer.get()))); | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer))); | 
 |  | 
 |   auto answer = callee->CreateAnswer(); | 
 |   auto contents = answer->description()->contents(); | 
 |   ASSERT_EQ(4u, contents.size()); | 
 |   EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, contents[0].media_description()->type()); | 
 |   EXPECT_EQ(first_transceiver->mid(), contents[0].name); | 
 |   EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, contents[1].media_description()->type()); | 
 |   EXPECT_EQ(second_transceiver->mid(), contents[1].name); | 
 |   EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, contents[2].media_description()->type()); | 
 |   EXPECT_EQ(third_transceiver->mid(), contents[2].name); | 
 |   EXPECT_EQ(cricket::MEDIA_TYPE_DATA, contents[3].media_description()->type()); | 
 |   EXPECT_EQ(offer_data->name, contents[3].name); | 
 | } | 
 |  | 
 | // Test that an answering media section is marked as rejected if the underlying | 
 | // transceiver has been stopped. | 
 | TEST_F(PeerConnectionJsepTest, CreateAnswerRejectsStoppedTransceiver) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddAudioTrack("a"); | 
 |   auto callee = CreatePeerConnection(); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |  | 
 |   callee->pc()->GetTransceivers()[0]->Stop(); | 
 |  | 
 |   auto answer = callee->CreateAnswer(); | 
 |   auto contents = answer->description()->contents(); | 
 |   ASSERT_EQ(1u, contents.size()); | 
 |   EXPECT_TRUE(contents[0].rejected); | 
 | } | 
 |  | 
 | // Test that CreateAnswer will generate media sections which will only send or | 
 | // receive if the offer indicates it can do the reciprocating direction. | 
 | // The full matrix is tested more extensively in MediaSession. | 
 | TEST_F(PeerConnectionJsepTest, CreateAnswerNegotiatesDirection) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   RtpTransceiverInit init; | 
 |   init.direction = RtpTransceiverDirection::kSendOnly; | 
 |   caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO, init); | 
 |   auto callee = CreatePeerConnection(); | 
 |   callee->AddAudioTrack("a"); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |  | 
 |   auto answer = callee->CreateAnswer(); | 
 |   auto contents = answer->description()->contents(); | 
 |   ASSERT_EQ(1u, contents.size()); | 
 |   EXPECT_EQ(RtpTransceiverDirection::kRecvOnly, | 
 |             contents[0].media_description()->direction()); | 
 | } | 
 |  | 
 | // Tests for JSEP SetLocalDescription with a local answer. | 
 | // Note that these test only the additional behaviors not covered by | 
 | // SetLocalDescription with a local offer. | 
 |  | 
 | // Test that SetLocalDescription with an answer sets the current_direction | 
 | // property of the transceivers mentioned in the session description. | 
 | TEST_F(PeerConnectionJsepTest, SetLocalAnswerUpdatesCurrentDirection) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   auto caller_audio = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); | 
 |   caller_audio->SetDirection(RtpTransceiverDirection::kRecvOnly); | 
 |   auto callee = CreatePeerConnection(); | 
 |   callee->AddAudioTrack("a"); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |   ASSERT_TRUE(callee->SetLocalDescription(callee->CreateAnswer())); | 
 |  | 
 |   auto transceivers = callee->pc()->GetTransceivers(); | 
 |   ASSERT_EQ(1u, transceivers.size()); | 
 |   // Since the offer was recvonly and the transceiver direction is sendrecv, | 
 |   // the negotiated direction will be sendonly. | 
 |   EXPECT_EQ(RtpTransceiverDirection::kSendOnly, | 
 |             transceivers[0]->current_direction()); | 
 | } | 
 |  | 
 | // Tests for JSEP SetRemoteDescription with a remote answer. | 
 | // Note that these test only the additional behaviors not covered by | 
 | // SetRemoteDescription with a remote offer. | 
 |  | 
 | TEST_F(PeerConnectionJsepTest, SetRemoteAnswerUpdatesCurrentDirection) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddAudioTrack("a"); | 
 |   auto callee = CreatePeerConnection(); | 
 |   callee->AddAudioTrack("a"); | 
 |   auto callee_audio = callee->pc()->GetTransceivers()[0]; | 
 |   callee_audio->SetDirection(RtpTransceiverDirection::kSendOnly); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |   ASSERT_TRUE( | 
 |       caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); | 
 |  | 
 |   auto transceivers = caller->pc()->GetTransceivers(); | 
 |   ASSERT_EQ(1u, transceivers.size()); | 
 |   // Since the remote transceiver was set to sendonly, the negotiated direction | 
 |   // in the answer would be sendonly which we apply as recvonly to the local | 
 |   // transceiver. | 
 |   EXPECT_EQ(RtpTransceiverDirection::kRecvOnly, | 
 |             transceivers[0]->current_direction()); | 
 | } | 
 |  | 
 | // Tests for multiple round trips. | 
 |  | 
 | // Test that setting a transceiver with the inactive direction does not stop it | 
 | // on either the caller or the callee. | 
 | TEST_F(PeerConnectionJsepTest, SettingTransceiverInactiveDoesNotStopIt) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddAudioTrack("a"); | 
 |   auto callee = CreatePeerConnection(); | 
 |   callee->AddAudioTrack("a"); | 
 |   callee->pc()->GetTransceivers()[0]->SetDirection( | 
 |       RtpTransceiverDirection::kInactive); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |   ASSERT_TRUE( | 
 |       caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); | 
 |  | 
 |   EXPECT_FALSE(caller->pc()->GetTransceivers()[0]->stopped()); | 
 |   EXPECT_FALSE(callee->pc()->GetTransceivers()[0]->stopped()); | 
 | } | 
 |  | 
 | // Test that if a transceiver had been associated and later stopped, then a | 
 | // media section is still generated for it and the media section is marked as | 
 | // rejected. | 
 | TEST_F(PeerConnectionJsepTest, | 
 |        ReOfferMediaSectionForAssociatedStoppedTransceiverIsRejected) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); | 
 |   auto callee = CreatePeerConnection(); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |   ASSERT_TRUE( | 
 |       caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); | 
 |  | 
 |   ASSERT_TRUE(transceiver->mid()); | 
 |   transceiver->Stop(); | 
 |  | 
 |   auto reoffer = caller->CreateOffer(); | 
 |   auto contents = reoffer->description()->contents(); | 
 |   ASSERT_EQ(1u, contents.size()); | 
 |   EXPECT_TRUE(contents[0].rejected); | 
 | } | 
 |  | 
 | // Test that stopping an associated transceiver on the caller side will stop the | 
 | // corresponding transceiver on the remote side when the remote offer is | 
 | // applied. | 
 | TEST_F(PeerConnectionJsepTest, | 
 |        StoppingTransceiverInOfferStopsTransceiverOnRemoteSide) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); | 
 |   auto callee = CreatePeerConnection(); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |   ASSERT_TRUE( | 
 |       caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); | 
 |  | 
 |   transceiver->Stop(); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |  | 
 |   auto transceivers = callee->pc()->GetTransceivers(); | 
 |   EXPECT_TRUE(transceivers[0]->stopped()); | 
 |   EXPECT_TRUE(transceivers[0]->mid()); | 
 | } | 
 |  | 
 | // Test that CreateOffer will only generate a recycled media section if the | 
 | // transceiver to be recycled has been seen stopped by the other side first. | 
 | TEST_F(PeerConnectionJsepTest, | 
 |        CreateOfferDoesNotRecycleMediaSectionIfFirstStopped) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   auto first_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); | 
 |   auto callee = CreatePeerConnection(); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |   ASSERT_TRUE( | 
 |       caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); | 
 |  | 
 |   auto second_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); | 
 |   first_transceiver->Stop(); | 
 |  | 
 |   auto reoffer = caller->CreateOffer(); | 
 |   auto contents = reoffer->description()->contents(); | 
 |   ASSERT_EQ(2u, contents.size()); | 
 |   EXPECT_TRUE(contents[0].rejected); | 
 |   EXPECT_FALSE(contents[1].rejected); | 
 | } | 
 |  | 
 | // Test that the offer/answer and transceivers for both the caller and callee | 
 | // side are generated/updated correctly when recycling an audio/video media | 
 | // section as a media section of either the same or opposite type. | 
 | class RecycleMediaSectionTest | 
 |     : public PeerConnectionJsepTest, | 
 |       public testing::WithParamInterface< | 
 |           std::tuple<cricket::MediaType, cricket::MediaType>> { | 
 |  protected: | 
 |   RecycleMediaSectionTest() { | 
 |     first_type_ = std::get<0>(GetParam()); | 
 |     second_type_ = std::get<1>(GetParam()); | 
 |   } | 
 |  | 
 |   cricket::MediaType first_type_; | 
 |   cricket::MediaType second_type_; | 
 | }; | 
 |  | 
 | TEST_P(RecycleMediaSectionTest, VerifyOfferAnswerAndTransceivers) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   auto first_transceiver = caller->AddTransceiver(first_type_); | 
 |   auto callee = CreatePeerConnection(); | 
 |  | 
 |   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); | 
 |  | 
 |   std::string first_mid = *first_transceiver->mid(); | 
 |   first_transceiver->Stop(); | 
 |  | 
 |   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); | 
 |  | 
 |   auto second_transceiver = caller->AddTransceiver(second_type_); | 
 |  | 
 |   // The offer should reuse the previous media section but allocate a new MID | 
 |   // and change the media type. | 
 |   auto offer = caller->CreateOffer(); | 
 |   auto offer_contents = offer->description()->contents(); | 
 |   ASSERT_EQ(1u, offer_contents.size()); | 
 |   EXPECT_FALSE(offer_contents[0].rejected); | 
 |   EXPECT_EQ(second_type_, offer_contents[0].media_description()->type()); | 
 |   std::string second_mid = offer_contents[0].name; | 
 |   EXPECT_NE(first_mid, second_mid); | 
 |  | 
 |   // Setting the local offer will dissociate the previous transceiver and set | 
 |   // the MID for the new transceiver. | 
 |   ASSERT_TRUE( | 
 |       caller->SetLocalDescription(CloneSessionDescription(offer.get()))); | 
 |   EXPECT_EQ(rtc::nullopt, first_transceiver->mid()); | 
 |   EXPECT_EQ(second_mid, second_transceiver->mid()); | 
 |  | 
 |   // Setting the remote offer will dissociate the previous transceiver and | 
 |   // create a new transceiver for the media section. | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer))); | 
 |   auto callee_transceivers = callee->pc()->GetTransceivers(); | 
 |   ASSERT_EQ(2u, callee_transceivers.size()); | 
 |   EXPECT_EQ(rtc::nullopt, callee_transceivers[0]->mid()); | 
 |   EXPECT_EQ(first_type_, callee_transceivers[0]->receiver()->media_type()); | 
 |   EXPECT_EQ(second_mid, callee_transceivers[1]->mid()); | 
 |   EXPECT_EQ(second_type_, callee_transceivers[1]->receiver()->media_type()); | 
 |  | 
 |   // The answer should have only one media section for the new transceiver. | 
 |   auto answer = callee->CreateAnswer(); | 
 |   auto answer_contents = answer->description()->contents(); | 
 |   ASSERT_EQ(1u, answer_contents.size()); | 
 |   EXPECT_FALSE(answer_contents[0].rejected); | 
 |   EXPECT_EQ(second_mid, answer_contents[0].name); | 
 |   EXPECT_EQ(second_type_, answer_contents[0].media_description()->type()); | 
 |  | 
 |   // Setting the local answer should succeed. | 
 |   ASSERT_TRUE( | 
 |       callee->SetLocalDescription(CloneSessionDescription(answer.get()))); | 
 |  | 
 |   // Setting the remote answer should succeed. | 
 |   ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer))); | 
 | } | 
 |  | 
 | // Test all combinations of audio and video as the first and second media type | 
 | // for the media section. This is needed for full test coverage because | 
 | // MediaSession has separate functions for processing audio and video media | 
 | // sections. | 
 | INSTANTIATE_TEST_CASE_P( | 
 |     PeerConnectionJsepTest, | 
 |     RecycleMediaSectionTest, | 
 |     Combine(Values(cricket::MEDIA_TYPE_AUDIO, cricket::MEDIA_TYPE_VIDEO), | 
 |             Values(cricket::MEDIA_TYPE_AUDIO, cricket::MEDIA_TYPE_VIDEO))); | 
 |  | 
 | // Test that a new data channel section will not reuse a recycleable audio or | 
 | // video media section. Additionally, tests that the new section is added to the | 
 | // end of the session description. | 
 | TEST_F(PeerConnectionJsepTest, DataChannelDoesNotRecycleMediaSection) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); | 
 |   auto callee = CreatePeerConnection(); | 
 |  | 
 |   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); | 
 |  | 
 |   transceiver->Stop(); | 
 |  | 
 |   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); | 
 |  | 
 |   caller->CreateDataChannel("dc"); | 
 |  | 
 |   auto offer = caller->CreateOffer(); | 
 |   auto offer_contents = offer->description()->contents(); | 
 |   ASSERT_EQ(2u, offer_contents.size()); | 
 |   EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, | 
 |             offer_contents[0].media_description()->type()); | 
 |   EXPECT_EQ(cricket::MEDIA_TYPE_DATA, | 
 |             offer_contents[1].media_description()->type()); | 
 |  | 
 |   ASSERT_TRUE( | 
 |       caller->SetLocalDescription(CloneSessionDescription(offer.get()))); | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer))); | 
 |  | 
 |   auto answer = callee->CreateAnswer(); | 
 |   auto answer_contents = answer->description()->contents(); | 
 |   ASSERT_EQ(2u, answer_contents.size()); | 
 |   EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, | 
 |             answer_contents[0].media_description()->type()); | 
 |   EXPECT_EQ(cricket::MEDIA_TYPE_DATA, | 
 |             answer_contents[1].media_description()->type()); | 
 | } | 
 |  | 
 | // Test that if a new track is added to an existing session that has a data, | 
 | // the new section comes at the end of the new offer, after the existing data | 
 | // section. | 
 | TEST_F(PeerConnectionJsepTest, AudioTrackAddedAfterDataSectionInReoffer) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->CreateDataChannel("dc"); | 
 |   auto callee = CreatePeerConnection(); | 
 |  | 
 |   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); | 
 |  | 
 |   caller->AddAudioTrack("a"); | 
 |  | 
 |   auto offer = caller->CreateOffer(); | 
 |   auto contents = offer->description()->contents(); | 
 |   ASSERT_EQ(2u, contents.size()); | 
 |   EXPECT_EQ(cricket::MEDIA_TYPE_DATA, contents[0].media_description()->type()); | 
 |   EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, contents[1].media_description()->type()); | 
 | } | 
 |  | 
 | // Tests for MID properties. | 
 |  | 
 | static void RenameSection(size_t mline_index, | 
 |                           const std::string& new_mid, | 
 |                           SessionDescriptionInterface* sdesc) { | 
 |   cricket::SessionDescription* desc = sdesc->description(); | 
 |   std::string old_mid = desc->contents()[mline_index].name; | 
 |   desc->contents()[mline_index].name = new_mid; | 
 |   desc->transport_infos()[mline_index].content_name = new_mid; | 
 |   const cricket::ContentGroup* bundle = | 
 |       desc->GetGroupByName(cricket::GROUP_TYPE_BUNDLE); | 
 |   if (bundle) { | 
 |     cricket::ContentGroup new_bundle = *bundle; | 
 |     if (new_bundle.RemoveContentName(old_mid)) { | 
 |       new_bundle.AddContentName(new_mid); | 
 |     } | 
 |     desc->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE); | 
 |     desc->AddGroup(new_bundle); | 
 |   } | 
 | } | 
 |  | 
 | // Test that two PeerConnections can have a successful offer/answer exchange if | 
 | // the MIDs are changed from the defaults. | 
 | TEST_F(PeerConnectionJsepTest, OfferAnswerWithChangedMids) { | 
 |   constexpr char kFirstMid[] = "nondefaultmid"; | 
 |   constexpr char kSecondMid[] = "randommid"; | 
 |  | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddAudioTrack("a"); | 
 |   caller->AddAudioTrack("b"); | 
 |   auto callee = CreatePeerConnection(); | 
 |  | 
 |   auto offer = caller->CreateOffer(); | 
 |   RenameSection(0, kFirstMid, offer.get()); | 
 |   RenameSection(1, kSecondMid, offer.get()); | 
 |  | 
 |   ASSERT_TRUE( | 
 |       caller->SetLocalDescription(CloneSessionDescription(offer.get()))); | 
 |   auto caller_transceivers = caller->pc()->GetTransceivers(); | 
 |   EXPECT_EQ(kFirstMid, caller_transceivers[0]->mid()); | 
 |   EXPECT_EQ(kSecondMid, caller_transceivers[1]->mid()); | 
 |  | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer))); | 
 |   auto callee_transceivers = callee->pc()->GetTransceivers(); | 
 |   EXPECT_EQ(kFirstMid, callee_transceivers[0]->mid()); | 
 |   EXPECT_EQ(kSecondMid, callee_transceivers[1]->mid()); | 
 |  | 
 |   auto answer = callee->CreateAnswer(); | 
 |   auto answer_contents = answer->description()->contents(); | 
 |   EXPECT_EQ(kFirstMid, answer_contents[0].name); | 
 |   EXPECT_EQ(kSecondMid, answer_contents[1].name); | 
 |  | 
 |   ASSERT_TRUE( | 
 |       callee->SetLocalDescription(CloneSessionDescription(answer.get()))); | 
 |   ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer))); | 
 | } | 
 |  | 
 | // Test that CreateOffer will generate a MID that is not already used if the | 
 | // default it would have picked is already taken. This is tested by using a | 
 | // third PeerConnection to determine what the default would be for the second | 
 | // media section then setting that as the first media section's MID. | 
 | TEST_F(PeerConnectionJsepTest, CreateOfferGeneratesUniqueMidIfAlreadyTaken) { | 
 |   // First, find what the default MID is for the second media section. | 
 |   auto pc = CreatePeerConnection(); | 
 |   pc->AddAudioTrack("a"); | 
 |   pc->AddAudioTrack("b"); | 
 |   auto default_offer = pc->CreateOffer(); | 
 |   std::string default_second_mid = | 
 |       default_offer->description()->contents()[1].name; | 
 |  | 
 |   // Now, do an offer/answer with one track which has the MID set to the default | 
 |   // second MID. | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddAudioTrack("a"); | 
 |   auto callee = CreatePeerConnection(); | 
 |  | 
 |   auto offer = caller->CreateOffer(); | 
 |   RenameSection(0, default_second_mid, offer.get()); | 
 |  | 
 |   ASSERT_TRUE( | 
 |       caller->SetLocalDescription(CloneSessionDescription(offer.get()))); | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer))); | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |  | 
 |   // Add a second track and ensure that the MID is different. | 
 |   caller->AddAudioTrack("b"); | 
 |  | 
 |   auto reoffer = caller->CreateOffer(); | 
 |   auto reoffer_contents = reoffer->description()->contents(); | 
 |   EXPECT_EQ(default_second_mid, reoffer_contents[0].name); | 
 |   EXPECT_NE(reoffer_contents[0].name, reoffer_contents[1].name); | 
 | } | 
 |  | 
 | // Test that if an audio or video section has the default data section MID, then | 
 | // CreateOffer will generate a unique MID for the newly added data section. | 
 | TEST_F(PeerConnectionJsepTest, | 
 |        CreateOfferGeneratesUniqueMidForDataSectionIfAlreadyTaken) { | 
 |   // First, find what the default MID is for the data channel. | 
 |   auto pc = CreatePeerConnection(); | 
 |   pc->CreateDataChannel("dc"); | 
 |   auto default_offer = pc->CreateOffer(); | 
 |   std::string default_data_mid = | 
 |       default_offer->description()->contents()[0].name; | 
 |  | 
 |   // Now do an offer/answer with one audio track which has a MID set to the | 
 |   // default data MID. | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddAudioTrack("a"); | 
 |   auto callee = CreatePeerConnection(); | 
 |  | 
 |   auto offer = caller->CreateOffer(); | 
 |   RenameSection(0, default_data_mid, offer.get()); | 
 |  | 
 |   ASSERT_TRUE( | 
 |       caller->SetLocalDescription(CloneSessionDescription(offer.get()))); | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer))); | 
 |   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); | 
 |  | 
 |   // Add a data channel and ensure that the MID is different. | 
 |   caller->CreateDataChannel("dc"); | 
 |  | 
 |   auto reoffer = caller->CreateOffer(); | 
 |   auto reoffer_contents = reoffer->description()->contents(); | 
 |   EXPECT_EQ(default_data_mid, reoffer_contents[0].name); | 
 |   EXPECT_NE(reoffer_contents[0].name, reoffer_contents[1].name); | 
 | } | 
 |  | 
 | // Test that a reoffer initiated by the callee adds a new track to the caller. | 
 | TEST_F(PeerConnectionJsepTest, CalleeDoesReoffer) { | 
 |   auto caller = CreatePeerConnection(); | 
 |   caller->AddAudioTrack("a"); | 
 |   auto callee = CreatePeerConnection(); | 
 |   callee->AddAudioTrack("a"); | 
 |   callee->AddVideoTrack("v"); | 
 |  | 
 |   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); | 
 |  | 
 |   EXPECT_EQ(1u, caller->pc()->GetTransceivers().size()); | 
 |   EXPECT_EQ(2u, callee->pc()->GetTransceivers().size()); | 
 |  | 
 |   ASSERT_TRUE(callee->ExchangeOfferAnswerWith(caller.get())); | 
 |  | 
 |   EXPECT_EQ(2u, caller->pc()->GetTransceivers().size()); | 
 |   EXPECT_EQ(2u, callee->pc()->GetTransceivers().size()); | 
 | } | 
 |  | 
 | }  // namespace webrtc |