| /* |
| * libjingle |
| * Copyright 2011, Google Inc. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO |
| * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
| * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
| * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "talk/app/webrtc/webrtcsession.h" |
| #include "talk/base/thread.h" |
| #include "talk/base/gunit.h" |
| #include "talk/session/phone/fakedevicemanager.h" |
| #include "talk/session/phone/fakemediaengine.h" |
| #include "talk/p2p/client/fakeportallocator.h" |
| #include "talk/session/phone/channelmanager.h" |
| #include "talk/session/phone/mediasession.h" |
| |
| static const char kStream1[] = "stream1"; |
| static const char kVideoTrack1[] = "video1"; |
| static const char kAudioTrack1[] = "audio1"; |
| |
| static const char kStream2[] = "stream2"; |
| static const char kVideoTrack2[] = "video2"; |
| static const char kAudioTrack2[] = "audio2"; |
| |
| |
| class MockWebRtcSessionObserver : public webrtc::WebRtcSessionObserver { |
| public: |
| virtual void OnCandidatesReady( |
| const std::vector<cricket::Candidate>& candidates) { |
| for (cricket::Candidates::const_iterator iter = candidates.begin(); |
| iter != candidates.end(); ++iter) { |
| candidates_.push_back(*iter); |
| } |
| } |
| std::vector<cricket::Candidate> candidates_; |
| }; |
| |
| class WebRtcSessionForTest : public webrtc::WebRtcSession { |
| public: |
| WebRtcSessionForTest(cricket::ChannelManager* cmgr, |
| talk_base::Thread* signaling_thread, |
| talk_base::Thread* worker_thread, |
| cricket::PortAllocator* port_allocator) |
| : WebRtcSession(cmgr, signaling_thread, worker_thread, port_allocator) { |
| } |
| virtual ~WebRtcSessionForTest() {} |
| |
| using webrtc::WebRtcSession::CreateOffer; |
| using webrtc::WebRtcSession::CreateAnswer; |
| using webrtc::WebRtcSession::SetLocalDescription; |
| using webrtc::WebRtcSession::SetRemoteDescription; |
| using webrtc::WebRtcSession::SetRemoteCandidates; |
| }; |
| |
| class WebRtcSessionTest : public testing::Test { |
| protected: |
| virtual void SetUp() { |
| cricket::FakeMediaEngine* media_engine(new cricket::FakeMediaEngine()); |
| cricket::FakeDeviceManager* device_manager( |
| new cricket::FakeDeviceManager()); |
| |
| channel_manager_.reset(new cricket::ChannelManager( |
| media_engine, device_manager, talk_base::Thread::Current())); |
| port_allocator_.reset(new cricket::FakePortAllocator( |
| talk_base::Thread::Current(), NULL)); |
| desc_factory_.reset( |
| new cricket::MediaSessionDescriptionFactory(channel_manager_.get())); |
| |
| ASSERT_TRUE(channel_manager_.get() != NULL); |
| ASSERT_TRUE(session_.get() == NULL); |
| ASSERT_TRUE(channel_manager_.get()->Init()); |
| |
| session_.reset(new WebRtcSessionForTest( |
| channel_manager_.get(), talk_base::Thread::Current(), |
| talk_base::Thread::Current(), port_allocator_.get())); |
| session_->RegisterObserver(&observer_); |
| |
| EXPECT_TRUE(session_->Initialize()); |
| |
| video_channel_ = media_engine->GetVideoChannel(0); |
| voice_channel_ = media_engine->GetVoiceChannel(0); |
| } |
| |
| void PopulateFakeCandidates() { |
| const int num_of_channels = 4; |
| const char* const channel_names[num_of_channels] = { |
| "rtp", "rtcp", "video_rtp", "video_rtcp" |
| }; |
| |
| // max 4 transport channels; |
| candidates_.clear(); |
| for (int i = 0; i < num_of_channels; ++i) { |
| cricket::Candidate candidate; |
| candidate.set_name(channel_names[i]); |
| candidates_.push_back(candidate); |
| } |
| } |
| |
| // Create a session description based on options. Used for testing but don't |
| // test WebRtcSession. |
| cricket::SessionDescription* CreateTestOffer( |
| const cricket::MediaSessionOptions& options) { |
| desc_factory_->set_secure(cricket::SEC_REQUIRED); |
| return desc_factory_->CreateOffer(options, NULL); |
| } |
| |
| // Create a session description based on options. Used for testing but don't |
| // test WebRtcSession. |
| cricket::SessionDescription* CreateTestAnswer( |
| const cricket::SessionDescription* offer, |
| const cricket::MediaSessionOptions& options) { |
| desc_factory_->set_secure(cricket::SEC_REQUIRED); |
| return desc_factory_->CreateAnswer(offer, options, NULL); |
| } |
| |
| cricket::MediaSessionOptions OptionsWithStream1() { |
| cricket::MediaSessionOptions options; |
| options.AddStream(cricket::MEDIA_TYPE_VIDEO, kVideoTrack1, kStream1); |
| options.AddStream(cricket::MEDIA_TYPE_AUDIO, kAudioTrack1, kStream1); |
| return options; |
| } |
| |
| cricket::MediaSessionOptions OptionsWithStream2() { |
| cricket::MediaSessionOptions options; |
| options.AddStream(cricket::MEDIA_TYPE_VIDEO, kVideoTrack2, kStream2); |
| options.AddStream(cricket::MEDIA_TYPE_AUDIO, kAudioTrack2, kStream2); |
| return options; |
| } |
| |
| cricket::MediaSessionOptions OptionsWithStream1And2() { |
| cricket::MediaSessionOptions options; |
| options.AddStream(cricket::MEDIA_TYPE_VIDEO, kVideoTrack1, kStream1); |
| options.AddStream(cricket::MEDIA_TYPE_AUDIO, kAudioTrack1, kStream1); |
| options.AddStream(cricket::MEDIA_TYPE_VIDEO, kVideoTrack2, kStream2); |
| options.AddStream(cricket::MEDIA_TYPE_AUDIO, kAudioTrack2, kStream2); |
| return options; |
| } |
| |
| cricket::MediaSessionOptions OptionsReceiveOnly() { |
| cricket::MediaSessionOptions options; |
| options.has_video = true; |
| return options; |
| } |
| |
| bool ChannelsExist() { |
| return (session_->voice_channel() != NULL && |
| session_->video_channel() != NULL); |
| } |
| |
| void CheckTransportChannels() { |
| EXPECT_TRUE(session_->GetChannel(cricket::CN_AUDIO, "rtp") != NULL); |
| EXPECT_TRUE(session_->GetChannel(cricket::CN_AUDIO, "rtcp") != NULL); |
| EXPECT_TRUE(session_->GetChannel(cricket::CN_VIDEO, "video_rtp") != NULL); |
| EXPECT_TRUE(session_->GetChannel(cricket::CN_VIDEO, "video_rtcp") != NULL); |
| } |
| |
| void VerifyCryptoParams(const cricket::SessionDescription* sdp, |
| bool offer) { |
| ASSERT_TRUE(session_.get() != NULL); |
| const cricket::ContentInfo* content = cricket::GetFirstAudioContent(sdp); |
| ASSERT_TRUE(content != NULL); |
| const cricket::AudioContentDescription* audio_content = |
| static_cast<const cricket::AudioContentDescription*>( |
| content->description); |
| ASSERT_TRUE(audio_content != NULL); |
| if (offer) { |
| ASSERT_EQ(2U, audio_content->cryptos().size()); |
| // key(40) + inline string |
| ASSERT_EQ(47U, audio_content->cryptos()[0].key_params.size()); |
| ASSERT_EQ("AES_CM_128_HMAC_SHA1_32", |
| audio_content->cryptos()[0].cipher_suite); |
| ASSERT_EQ("AES_CM_128_HMAC_SHA1_80", |
| audio_content->cryptos()[1].cipher_suite); |
| ASSERT_EQ(47U, audio_content->cryptos()[1].key_params.size()); |
| } else { |
| ASSERT_EQ(1U, audio_content->cryptos().size()); |
| // key(40) + inline string |
| ASSERT_EQ(47U, audio_content->cryptos()[0].key_params.size()); |
| ASSERT_EQ("AES_CM_128_HMAC_SHA1_32", |
| audio_content->cryptos()[0].cipher_suite); |
| } |
| |
| content = cricket::GetFirstVideoContent(sdp); |
| ASSERT_TRUE(content != NULL); |
| const cricket::VideoContentDescription* video_content = |
| static_cast<const cricket::VideoContentDescription*>( |
| content->description); |
| ASSERT_TRUE(video_content != NULL); |
| ASSERT_EQ(1U, video_content->cryptos().size()); |
| ASSERT_EQ("AES_CM_128_HMAC_SHA1_80", |
| video_content->cryptos()[0].cipher_suite); |
| ASSERT_EQ(47U, video_content->cryptos()[0].key_params.size()); |
| } |
| |
| void VerifyNoCryptoParams(const cricket::SessionDescription* sdp) { |
| const cricket::ContentInfo* content = cricket::GetFirstAudioContent(sdp); |
| ASSERT_TRUE(content != NULL); |
| const cricket::AudioContentDescription* audio_content = |
| static_cast<const cricket::AudioContentDescription*>( |
| content->description); |
| ASSERT_TRUE(audio_content != NULL); |
| ASSERT_EQ(0U, audio_content->cryptos().size()); |
| |
| content = cricket::GetFirstVideoContent(sdp); |
| ASSERT_TRUE(content != NULL); |
| const cricket::VideoContentDescription* video_content = |
| static_cast<const cricket::VideoContentDescription*>( |
| content->description); |
| ASSERT_TRUE(video_content != NULL); |
| ASSERT_EQ(0U, video_content->cryptos().size()); |
| } |
| |
| void VerifyAnswerFromNonCryptoOffer() { |
| // Create a SDP without Crypto. |
| desc_factory_->set_secure(cricket::SEC_DISABLED); |
| cricket::MediaSessionOptions options; |
| options.has_video = true; |
| cricket::SessionDescription* offer = |
| desc_factory_->CreateOffer(options, NULL); |
| ASSERT_TRUE(offer != NULL); |
| VerifyNoCryptoParams(offer); |
| const cricket::SessionDescription* answer = |
| session_->CreateAnswer(offer, options); |
| // Answer should be NULL as no crypto params in offer. |
| ASSERT_TRUE(answer == NULL); |
| } |
| |
| void VerifyAnswerFromCryptoOffer() { |
| desc_factory_->set_secure(cricket::SEC_REQUIRED); |
| cricket::MediaSessionOptions options; |
| options.has_video = true; |
| cricket::SessionDescription* offer = |
| desc_factory_->CreateOffer(options, NULL); |
| ASSERT_TRUE(offer != NULL); |
| VerifyCryptoParams(offer, true); |
| const cricket::SessionDescription* answer = |
| session_->CreateAnswer(offer, options); |
| ASSERT_TRUE(answer != NULL); |
| VerifyCryptoParams(answer, false); |
| } |
| |
| talk_base::scoped_ptr<cricket::PortAllocator> port_allocator_; |
| talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_; |
| talk_base::scoped_ptr<cricket::MediaSessionDescriptionFactory> desc_factory_; |
| talk_base::scoped_ptr<WebRtcSessionForTest> session_; |
| MockWebRtcSessionObserver observer_; |
| std::vector<cricket::Candidate> candidates_; |
| cricket::FakeVideoMediaChannel* video_channel_; |
| cricket::FakeVoiceMediaChannel* voice_channel_; |
| }; |
| |
| TEST_F(WebRtcSessionTest, TestInitialize) { |
| EXPECT_TRUE(ChannelsExist()); |
| CheckTransportChannels(); |
| talk_base::Thread::Current()->ProcessMessages(1000); |
| EXPECT_EQ(4u, observer_.candidates_.size()); |
| } |
| |
| // Test creating offers and receive answers and make sure the |
| // media engine creates the expected send and receive streams. |
| TEST_F(WebRtcSessionTest, TestCreateOfferReceiveAnswer) { |
| cricket::MediaSessionOptions options = OptionsWithStream1(); |
| cricket::SessionDescription* offer = session_->CreateOffer(options); |
| |
| cricket::MediaSessionOptions options2 = OptionsWithStream2(); |
| cricket::SessionDescription* answer = CreateTestAnswer(offer, options2); |
| |
| session_->SetLocalDescription(offer, cricket::CA_OFFER); |
| session_->SetRemoteDescription(answer, cricket::CA_ANSWER); |
| |
| ASSERT_EQ(1u, video_channel_->recv_streams().size()); |
| cricket::StreamParams recv_video_stream = |
| video_channel_->recv_streams()[0]; |
| EXPECT_TRUE(kVideoTrack2 == recv_video_stream.name); |
| |
| ASSERT_EQ(1u, voice_channel_->recv_streams().size()); |
| cricket::StreamParams recv_audio_stream = |
| voice_channel_->recv_streams()[0]; |
| EXPECT_TRUE(kAudioTrack2 == recv_audio_stream.name); |
| |
| ASSERT_EQ(1u, video_channel_->send_streams().size()); |
| EXPECT_TRUE(kVideoTrack1 == video_channel_->send_streams()[0].name); |
| ASSERT_EQ(1u, voice_channel_->send_streams().size()); |
| EXPECT_TRUE(kAudioTrack1 == voice_channel_->send_streams()[0].name); |
| |
| // Create new offer without send streams. |
| offer = session_->CreateOffer(OptionsReceiveOnly()); |
| // Test with same answer. |
| session_->SetLocalDescription(offer, cricket::CA_OFFER); |
| session_->SetRemoteDescription(answer, cricket::CA_ANSWER); |
| |
| EXPECT_EQ(0u, video_channel_->send_streams().size()); |
| EXPECT_EQ(0u, voice_channel_->send_streams().size()); |
| |
| // Make sure the receive streams have not changed. |
| ASSERT_EQ(1u, video_channel_->recv_streams().size()); |
| EXPECT_EQ(recv_video_stream, video_channel_->recv_streams()[0]); |
| ASSERT_EQ(1u, voice_channel_->recv_streams().size()); |
| EXPECT_EQ(recv_audio_stream, voice_channel_->recv_streams()[0]); |
| } |
| |
| // Test receiving offers and creating answers and make sure the |
| // media engine creates the expected send and receive streams. |
| TEST_F(WebRtcSessionTest, TestReceiveOfferCreateAnswer) { |
| cricket::SessionDescription* offer = CreateTestOffer(OptionsWithStream2()); |
| |
| cricket::MediaSessionOptions answer_options = OptionsWithStream1(); |
| cricket::SessionDescription* answer = |
| session_->CreateAnswer(offer, answer_options); |
| session_->SetRemoteDescription(offer, cricket::CA_OFFER); |
| session_->SetLocalDescription(answer, cricket::CA_ANSWER); |
| |
| ASSERT_EQ(1u, video_channel_->recv_streams().size()); |
| EXPECT_TRUE(kVideoTrack2 == video_channel_->recv_streams()[0].name); |
| |
| ASSERT_EQ(1u, voice_channel_->recv_streams().size()); |
| EXPECT_TRUE(kAudioTrack2 == voice_channel_->recv_streams()[0].name); |
| |
| ASSERT_EQ(1u, video_channel_->send_streams().size()); |
| EXPECT_TRUE(kVideoTrack1 == video_channel_->send_streams()[0].name); |
| ASSERT_EQ(1u, voice_channel_->send_streams().size()); |
| EXPECT_TRUE(kAudioTrack1 == voice_channel_->send_streams()[0].name); |
| |
| offer = CreateTestOffer(OptionsWithStream1And2()); |
| |
| // Answer by turning off all send streams. |
| answer = session_->CreateAnswer(offer, OptionsReceiveOnly()); |
| session_->SetRemoteDescription(offer, cricket::CA_OFFER); |
| session_->SetLocalDescription(answer, cricket::CA_ANSWER); |
| |
| ASSERT_EQ(2u, video_channel_->recv_streams().size()); |
| EXPECT_TRUE(kVideoTrack1 == video_channel_->recv_streams()[0].name); |
| EXPECT_TRUE(kVideoTrack2 == video_channel_->recv_streams()[1].name); |
| ASSERT_EQ(2u, voice_channel_->recv_streams().size()); |
| EXPECT_TRUE(kAudioTrack1 == voice_channel_->recv_streams()[0].name); |
| EXPECT_TRUE(kAudioTrack2 == voice_channel_->recv_streams()[1].name); |
| |
| // Make we have no send streams. |
| EXPECT_EQ(0u, video_channel_->send_streams().size()); |
| EXPECT_EQ(0u, voice_channel_->send_streams().size()); |
| } |
| |
| TEST_F(WebRtcSessionTest, TestDefaultSetSecurePolicy) { |
| EXPECT_EQ(cricket::SEC_REQUIRED, session_->secure_policy()); |
| } |
| |
| TEST_F(WebRtcSessionTest, VerifyCryptoParamsInSDP) { |
| VerifyCryptoParams(session_->CreateOffer(OptionsWithStream1()), true); |
| } |
| |
| TEST_F(WebRtcSessionTest, VerifyNoCryptoParamsInSDP) { |
| session_->set_secure_policy(cricket::SEC_DISABLED); |
| VerifyNoCryptoParams(session_->CreateOffer(OptionsWithStream1())); |
| } |
| |
| TEST_F(WebRtcSessionTest, VerifyAnswerFromNonCryptoOffer) { |
| VerifyAnswerFromNonCryptoOffer(); |
| } |
| |
| TEST_F(WebRtcSessionTest, VerifyAnswerFromCryptoOffer) { |
| VerifyAnswerFromCryptoOffer(); |
| } |