|  | /* | 
|  | *  Copyright 2012 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 <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "webrtc/api/audiotrack.h" | 
|  | #include "webrtc/api/fakemediacontroller.h" | 
|  | #include "webrtc/api/fakemetricsobserver.h" | 
|  | #include "webrtc/api/jsepicecandidate.h" | 
|  | #include "webrtc/api/jsepsessiondescription.h" | 
|  | #include "webrtc/api/peerconnection.h" | 
|  | #include "webrtc/api/sctputils.h" | 
|  | #include "webrtc/api/test/fakedtlsidentitystore.h" | 
|  | #include "webrtc/api/videotrack.h" | 
|  | #include "webrtc/api/webrtcsession.h" | 
|  | #include "webrtc/api/webrtcsessiondescriptionfactory.h" | 
|  | #include "webrtc/base/fakenetwork.h" | 
|  | #include "webrtc/base/firewallsocketserver.h" | 
|  | #include "webrtc/base/gunit.h" | 
|  | #include "webrtc/base/logging.h" | 
|  | #include "webrtc/base/network.h" | 
|  | #include "webrtc/base/physicalsocketserver.h" | 
|  | #include "webrtc/base/ssladapter.h" | 
|  | #include "webrtc/base/sslidentity.h" | 
|  | #include "webrtc/base/sslstreamadapter.h" | 
|  | #include "webrtc/base/stringutils.h" | 
|  | #include "webrtc/base/thread.h" | 
|  | #include "webrtc/base/virtualsocketserver.h" | 
|  | #include "webrtc/media/base/fakemediaengine.h" | 
|  | #include "webrtc/media/base/fakevideorenderer.h" | 
|  | #include "webrtc/media/base/mediachannel.h" | 
|  | #include "webrtc/media/engine/fakewebrtccall.h" | 
|  | #include "webrtc/p2p/base/stunserver.h" | 
|  | #include "webrtc/p2p/base/teststunserver.h" | 
|  | #include "webrtc/p2p/base/testturnserver.h" | 
|  | #include "webrtc/p2p/base/transportchannel.h" | 
|  | #include "webrtc/p2p/client/basicportallocator.h" | 
|  | #include "webrtc/pc/channelmanager.h" | 
|  | #include "webrtc/pc/mediasession.h" | 
|  |  | 
|  | #define MAYBE_SKIP_TEST(feature)                    \ | 
|  | if (!(feature())) {                               \ | 
|  | LOG(LS_INFO) << "Feature disabled... skipping"; \ | 
|  | return;                                         \ | 
|  | } | 
|  |  | 
|  | using cricket::FakeVoiceMediaChannel; | 
|  | using cricket::TransportInfo; | 
|  | using rtc::SocketAddress; | 
|  | using rtc::scoped_ptr; | 
|  | using rtc::Thread; | 
|  | using webrtc::CreateSessionDescription; | 
|  | using webrtc::CreateSessionDescriptionObserver; | 
|  | using webrtc::CreateSessionDescriptionRequest; | 
|  | using webrtc::DataChannel; | 
|  | using webrtc::DtlsIdentityStoreInterface; | 
|  | using webrtc::FakeMetricsObserver; | 
|  | using webrtc::IceCandidateCollection; | 
|  | using webrtc::InternalDataChannelInit; | 
|  | using webrtc::JsepIceCandidate; | 
|  | using webrtc::JsepSessionDescription; | 
|  | using webrtc::PeerConnectionFactoryInterface; | 
|  | using webrtc::PeerConnectionInterface; | 
|  | using webrtc::SessionDescriptionInterface; | 
|  | using webrtc::SessionStats; | 
|  | using webrtc::StreamCollection; | 
|  | using webrtc::WebRtcSession; | 
|  | using webrtc::kBundleWithoutRtcpMux; | 
|  | using webrtc::kCreateChannelFailed; | 
|  | using webrtc::kInvalidSdp; | 
|  | using webrtc::kMlineMismatch; | 
|  | using webrtc::kPushDownTDFailed; | 
|  | using webrtc::kSdpWithoutIceUfragPwd; | 
|  | using webrtc::kSdpWithoutDtlsFingerprint; | 
|  | using webrtc::kSdpWithoutSdesCrypto; | 
|  | using webrtc::kSessionError; | 
|  | using webrtc::kSessionErrorDesc; | 
|  | using webrtc::kMaxUnsignalledRecvStreams; | 
|  |  | 
|  | typedef PeerConnectionInterface::RTCOfferAnswerOptions RTCOfferAnswerOptions; | 
|  |  | 
|  | static const int kClientAddrPort = 0; | 
|  | static const char kClientAddrHost1[] = "11.11.11.11"; | 
|  | static const char kClientIPv6AddrHost1[] = | 
|  | "2620:0:aaaa:bbbb:cccc:dddd:eeee:ffff"; | 
|  | static const char kClientAddrHost2[] = "22.22.22.22"; | 
|  | static const char kStunAddrHost[] = "99.99.99.1"; | 
|  | static const SocketAddress kTurnUdpIntAddr("99.99.99.4", 3478); | 
|  | static const SocketAddress kTurnUdpExtAddr("99.99.99.6", 0); | 
|  | static const char kTurnUsername[] = "test"; | 
|  | static const char kTurnPassword[] = "test"; | 
|  |  | 
|  | static const char kSessionVersion[] = "1"; | 
|  |  | 
|  | // Media index of candidates belonging to the first media content. | 
|  | static const int kMediaContentIndex0 = 0; | 
|  | static const char kMediaContentName0[] = "audio"; | 
|  |  | 
|  | // Media index of candidates belonging to the second media content. | 
|  | static const int kMediaContentIndex1 = 1; | 
|  | static const char kMediaContentName1[] = "video"; | 
|  |  | 
|  | static const int kIceCandidatesTimeout = 10000; | 
|  |  | 
|  | static const char kFakeDtlsFingerprint[] = | 
|  | "BB:CD:72:F7:2F:D0:BA:43:F3:68:B1:0C:23:72:B6:4A:" | 
|  | "0F:DE:34:06:BC:E0:FE:01:BC:73:C8:6D:F4:65:D5:24"; | 
|  |  | 
|  | static const char kTooLongIceUfragPwd[] = | 
|  | "IceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfrag" | 
|  | "IceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfrag" | 
|  | "IceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfrag" | 
|  | "IceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfragIceUfrag"; | 
|  |  | 
|  | 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"; | 
|  |  | 
|  | static const char kStream2[] = "stream2"; | 
|  | static const char kVideoTrack2[] = "video2"; | 
|  | static const char kAudioTrack2[] = "audio2"; | 
|  |  | 
|  | enum RTCCertificateGenerationMethod { ALREADY_GENERATED, DTLS_IDENTITY_STORE }; | 
|  |  | 
|  | class MockIceObserver : public webrtc::IceObserver { | 
|  | public: | 
|  | MockIceObserver() | 
|  | : oncandidatesready_(false), | 
|  | ice_connection_state_(PeerConnectionInterface::kIceConnectionNew), | 
|  | ice_gathering_state_(PeerConnectionInterface::kIceGatheringNew) { | 
|  | } | 
|  |  | 
|  | void OnIceConnectionChange( | 
|  | PeerConnectionInterface::IceConnectionState new_state) override { | 
|  | ice_connection_state_ = new_state; | 
|  | } | 
|  | void OnIceGatheringChange( | 
|  | PeerConnectionInterface::IceGatheringState new_state) override { | 
|  | // We can never transition back to "new". | 
|  | EXPECT_NE(PeerConnectionInterface::kIceGatheringNew, new_state); | 
|  | ice_gathering_state_ = new_state; | 
|  | oncandidatesready_ = | 
|  | new_state == PeerConnectionInterface::kIceGatheringComplete; | 
|  | } | 
|  |  | 
|  | // Found a new candidate. | 
|  | void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override { | 
|  | switch (candidate->sdp_mline_index()) { | 
|  | case kMediaContentIndex0: | 
|  | mline_0_candidates_.push_back(candidate->candidate()); | 
|  | break; | 
|  | case kMediaContentIndex1: | 
|  | mline_1_candidates_.push_back(candidate->candidate()); | 
|  | break; | 
|  | default: | 
|  | ASSERT(false); | 
|  | } | 
|  |  | 
|  | // The ICE gathering state should always be Gathering when a candidate is | 
|  | // received (or possibly Completed in the case of the final candidate). | 
|  | EXPECT_NE(PeerConnectionInterface::kIceGatheringNew, ice_gathering_state_); | 
|  | } | 
|  |  | 
|  | // Some local candidates are removed. | 
|  | void OnIceCandidatesRemoved( | 
|  | const std::vector<cricket::Candidate>& candidates) { | 
|  | num_candidates_removed_ += candidates.size(); | 
|  | } | 
|  |  | 
|  | bool oncandidatesready_; | 
|  | std::vector<cricket::Candidate> mline_0_candidates_; | 
|  | std::vector<cricket::Candidate> mline_1_candidates_; | 
|  | PeerConnectionInterface::IceConnectionState ice_connection_state_; | 
|  | PeerConnectionInterface::IceGatheringState ice_gathering_state_; | 
|  | size_t num_candidates_removed_ = 0; | 
|  | }; | 
|  |  | 
|  | class WebRtcSessionForTest : public webrtc::WebRtcSession { | 
|  | public: | 
|  | WebRtcSessionForTest(webrtc::MediaControllerInterface* media_controller, | 
|  | rtc::Thread* signaling_thread, | 
|  | rtc::Thread* worker_thread, | 
|  | cricket::PortAllocator* port_allocator, | 
|  | webrtc::IceObserver* ice_observer) | 
|  | : WebRtcSession(media_controller, | 
|  | signaling_thread, | 
|  | worker_thread, | 
|  | port_allocator) { | 
|  | RegisterIceObserver(ice_observer); | 
|  | } | 
|  | virtual ~WebRtcSessionForTest() {} | 
|  |  | 
|  | // Note that these methods are only safe to use if the signaling thread | 
|  | // is the same as the worker thread | 
|  | cricket::TransportChannel* voice_rtp_transport_channel() { | 
|  | return rtp_transport_channel(voice_channel()); | 
|  | } | 
|  |  | 
|  | cricket::TransportChannel* voice_rtcp_transport_channel() { | 
|  | return rtcp_transport_channel(voice_channel()); | 
|  | } | 
|  |  | 
|  | cricket::TransportChannel* video_rtp_transport_channel() { | 
|  | return rtp_transport_channel(video_channel()); | 
|  | } | 
|  |  | 
|  | cricket::TransportChannel* video_rtcp_transport_channel() { | 
|  | return rtcp_transport_channel(video_channel()); | 
|  | } | 
|  |  | 
|  | cricket::TransportChannel* data_rtp_transport_channel() { | 
|  | return rtp_transport_channel(data_channel()); | 
|  | } | 
|  |  | 
|  | cricket::TransportChannel* data_rtcp_transport_channel() { | 
|  | return rtcp_transport_channel(data_channel()); | 
|  | } | 
|  |  | 
|  | using webrtc::WebRtcSession::SetAudioPlayout; | 
|  | using webrtc::WebRtcSession::SetAudioSend; | 
|  | using webrtc::WebRtcSession::SetSource; | 
|  | using webrtc::WebRtcSession::SetVideoPlayout; | 
|  | using webrtc::WebRtcSession::SetVideoSend; | 
|  |  | 
|  | private: | 
|  | cricket::TransportChannel* rtp_transport_channel(cricket::BaseChannel* ch) { | 
|  | if (!ch) { | 
|  | return nullptr; | 
|  | } | 
|  | return ch->transport_channel(); | 
|  | } | 
|  |  | 
|  | cricket::TransportChannel* rtcp_transport_channel(cricket::BaseChannel* ch) { | 
|  | if (!ch) { | 
|  | return nullptr; | 
|  | } | 
|  | return ch->rtcp_transport_channel(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | class WebRtcSessionCreateSDPObserverForTest | 
|  | : public rtc::RefCountedObject<CreateSessionDescriptionObserver> { | 
|  | public: | 
|  | enum State { | 
|  | kInit, | 
|  | kFailed, | 
|  | kSucceeded, | 
|  | }; | 
|  | WebRtcSessionCreateSDPObserverForTest() : state_(kInit) {} | 
|  |  | 
|  | // CreateSessionDescriptionObserver implementation. | 
|  | virtual void OnSuccess(SessionDescriptionInterface* desc) { | 
|  | description_.reset(desc); | 
|  | state_ = kSucceeded; | 
|  | } | 
|  | virtual void OnFailure(const std::string& error) { | 
|  | state_ = kFailed; | 
|  | } | 
|  |  | 
|  | SessionDescriptionInterface* description() { return description_.get(); } | 
|  |  | 
|  | SessionDescriptionInterface* ReleaseDescription() { | 
|  | return description_.release(); | 
|  | } | 
|  |  | 
|  | State state() const { return state_; } | 
|  |  | 
|  | protected: | 
|  | ~WebRtcSessionCreateSDPObserverForTest() {} | 
|  |  | 
|  | private: | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> description_; | 
|  | State state_; | 
|  | }; | 
|  |  | 
|  | class FakeAudioSource : public cricket::AudioSource { | 
|  | public: | 
|  | FakeAudioSource() : sink_(NULL) {} | 
|  | virtual ~FakeAudioSource() { | 
|  | if (sink_) | 
|  | sink_->OnClose(); | 
|  | } | 
|  |  | 
|  | void SetSink(Sink* sink) override { sink_ = sink; } | 
|  |  | 
|  | const cricket::AudioSource::Sink* sink() const { return sink_; } | 
|  |  | 
|  | private: | 
|  | cricket::AudioSource::Sink* sink_; | 
|  | }; | 
|  |  | 
|  | class WebRtcSessionTest | 
|  | : public testing::TestWithParam<RTCCertificateGenerationMethod>, | 
|  | public sigslot::has_slots<> { | 
|  | protected: | 
|  | // TODO Investigate why ChannelManager crashes, if it's created | 
|  | // after stun_server. | 
|  | WebRtcSessionTest() | 
|  | : media_engine_(new cricket::FakeMediaEngine()), | 
|  | data_engine_(new cricket::FakeDataEngine()), | 
|  | channel_manager_( | 
|  | new cricket::ChannelManager(media_engine_, | 
|  | data_engine_, | 
|  | rtc::Thread::Current())), | 
|  | fake_call_(webrtc::Call::Config()), | 
|  | media_controller_( | 
|  | webrtc::MediaControllerInterface::Create(cricket::MediaConfig(), | 
|  | rtc::Thread::Current(), | 
|  | channel_manager_.get())), | 
|  | tdesc_factory_(new cricket::TransportDescriptionFactory()), | 
|  | desc_factory_( | 
|  | new cricket::MediaSessionDescriptionFactory(channel_manager_.get(), | 
|  | tdesc_factory_.get())), | 
|  | pss_(new rtc::PhysicalSocketServer), | 
|  | vss_(new rtc::VirtualSocketServer(pss_.get())), | 
|  | fss_(new rtc::FirewallSocketServer(vss_.get())), | 
|  | ss_scope_(fss_.get()), | 
|  | stun_socket_addr_( | 
|  | rtc::SocketAddress(kStunAddrHost, cricket::STUN_SERVER_PORT)), | 
|  | stun_server_(cricket::TestStunServer::Create(Thread::Current(), | 
|  | stun_socket_addr_)), | 
|  | turn_server_(Thread::Current(), kTurnUdpIntAddr, kTurnUdpExtAddr), | 
|  | metrics_observer_(new rtc::RefCountedObject<FakeMetricsObserver>()) { | 
|  | cricket::ServerAddresses stun_servers; | 
|  | stun_servers.insert(stun_socket_addr_); | 
|  | allocator_.reset(new cricket::BasicPortAllocator( | 
|  | &network_manager_, | 
|  | stun_servers, | 
|  | SocketAddress(), SocketAddress(), SocketAddress())); | 
|  | allocator_->set_flags(cricket::PORTALLOCATOR_DISABLE_TCP | | 
|  | cricket::PORTALLOCATOR_DISABLE_RELAY); | 
|  | EXPECT_TRUE(channel_manager_->Init()); | 
|  | desc_factory_->set_add_legacy_streams(false); | 
|  | allocator_->set_step_delay(cricket::kMinimumStepDelay); | 
|  | } | 
|  |  | 
|  | void AddInterface(const SocketAddress& addr) { | 
|  | network_manager_.AddInterface(addr); | 
|  | } | 
|  | void RemoveInterface(const SocketAddress& addr) { | 
|  | network_manager_.RemoveInterface(addr); | 
|  | } | 
|  |  | 
|  | // If |dtls_identity_store| != null or |rtc_configuration| contains | 
|  | // |certificates| then DTLS will be enabled unless explicitly disabled by | 
|  | // |rtc_configuration| options. When DTLS is enabled a certificate will be | 
|  | // used if provided, otherwise one will be generated using the | 
|  | // |dtls_identity_store|. | 
|  | void Init( | 
|  | rtc::scoped_ptr<webrtc::DtlsIdentityStoreInterface> dtls_identity_store) { | 
|  | ASSERT_TRUE(session_.get() == NULL); | 
|  | session_.reset(new WebRtcSessionForTest( | 
|  | media_controller_.get(), rtc::Thread::Current(), rtc::Thread::Current(), | 
|  | allocator_.get(), &observer_)); | 
|  | session_->SignalDataChannelOpenMessage.connect( | 
|  | this, &WebRtcSessionTest::OnDataChannelOpenMessage); | 
|  | session_->GetOnDestroyedSignal()->connect( | 
|  | this, &WebRtcSessionTest::OnSessionDestroyed); | 
|  |  | 
|  | EXPECT_EQ(PeerConnectionInterface::kIceConnectionNew, | 
|  | observer_.ice_connection_state_); | 
|  | EXPECT_EQ(PeerConnectionInterface::kIceGatheringNew, | 
|  | observer_.ice_gathering_state_); | 
|  |  | 
|  | EXPECT_TRUE(session_->Initialize(options_, std::move(dtls_identity_store), | 
|  | configuration_)); | 
|  | session_->set_metrics_observer(metrics_observer_); | 
|  | } | 
|  |  | 
|  | void OnDataChannelOpenMessage(const std::string& label, | 
|  | const InternalDataChannelInit& config) { | 
|  | last_data_channel_label_ = label; | 
|  | last_data_channel_config_ = config; | 
|  | } | 
|  |  | 
|  | void OnSessionDestroyed() { session_destroyed_ = true; } | 
|  |  | 
|  | void Init() { Init(nullptr); } | 
|  |  | 
|  | void InitWithIceTransport( | 
|  | PeerConnectionInterface::IceTransportsType ice_transport_type) { | 
|  | configuration_.type = ice_transport_type; | 
|  | Init(); | 
|  | } | 
|  |  | 
|  | void InitWithBundlePolicy( | 
|  | PeerConnectionInterface::BundlePolicy bundle_policy) { | 
|  | configuration_.bundle_policy = bundle_policy; | 
|  | Init(); | 
|  | } | 
|  |  | 
|  | void InitWithRtcpMuxPolicy( | 
|  | PeerConnectionInterface::RtcpMuxPolicy rtcp_mux_policy) { | 
|  | PeerConnectionInterface::RTCConfiguration configuration; | 
|  | configuration_.rtcp_mux_policy = rtcp_mux_policy; | 
|  | Init(); | 
|  | } | 
|  |  | 
|  | // Successfully init with DTLS; with a certificate generated and supplied or | 
|  | // with a store that generates it for us. | 
|  | void InitWithDtls(RTCCertificateGenerationMethod cert_gen_method) { | 
|  | rtc::scoped_ptr<FakeDtlsIdentityStore> dtls_identity_store; | 
|  | if (cert_gen_method == ALREADY_GENERATED) { | 
|  | configuration_.certificates.push_back( | 
|  | FakeDtlsIdentityStore::GenerateCertificate()); | 
|  | } else if (cert_gen_method == DTLS_IDENTITY_STORE) { | 
|  | dtls_identity_store.reset(new FakeDtlsIdentityStore()); | 
|  | dtls_identity_store->set_should_fail(false); | 
|  | } else { | 
|  | RTC_CHECK(false); | 
|  | } | 
|  | Init(std::move(dtls_identity_store)); | 
|  | } | 
|  |  | 
|  | // Init with DTLS with a store that will fail to generate a certificate. | 
|  | void InitWithDtlsIdentityGenFail() { | 
|  | rtc::scoped_ptr<FakeDtlsIdentityStore> dtls_identity_store( | 
|  | new FakeDtlsIdentityStore()); | 
|  | dtls_identity_store->set_should_fail(true); | 
|  | Init(std::move(dtls_identity_store)); | 
|  | } | 
|  |  | 
|  | void InitWithDtmfCodec() { | 
|  | // Add kTelephoneEventCodec for dtmf test. | 
|  | const cricket::AudioCodec kTelephoneEventCodec(106, "telephone-event", 8000, | 
|  | 0, 1); | 
|  | std::vector<cricket::AudioCodec> codecs; | 
|  | codecs.push_back(kTelephoneEventCodec); | 
|  | media_engine_->SetAudioCodecs(codecs); | 
|  | desc_factory_->set_audio_codecs(codecs); | 
|  | Init(); | 
|  | } | 
|  |  | 
|  | void SendAudioVideoStream1() { | 
|  | send_stream_1_ = true; | 
|  | send_stream_2_ = false; | 
|  | send_audio_ = true; | 
|  | send_video_ = true; | 
|  | } | 
|  |  | 
|  | void SendAudioVideoStream2() { | 
|  | send_stream_1_ = false; | 
|  | send_stream_2_ = true; | 
|  | send_audio_ = true; | 
|  | send_video_ = true; | 
|  | } | 
|  |  | 
|  | void SendAudioVideoStream1And2() { | 
|  | send_stream_1_ = true; | 
|  | send_stream_2_ = true; | 
|  | send_audio_ = true; | 
|  | send_video_ = true; | 
|  | } | 
|  |  | 
|  | void SendNothing() { | 
|  | send_stream_1_ = false; | 
|  | send_stream_2_ = false; | 
|  | send_audio_ = false; | 
|  | send_video_ = false; | 
|  | } | 
|  |  | 
|  | void SendAudioOnlyStream2() { | 
|  | send_stream_1_ = false; | 
|  | send_stream_2_ = true; | 
|  | send_audio_ = true; | 
|  | send_video_ = false; | 
|  | } | 
|  |  | 
|  | void SendVideoOnlyStream2() { | 
|  | send_stream_1_ = false; | 
|  | send_stream_2_ = true; | 
|  | send_audio_ = false; | 
|  | send_video_ = true; | 
|  | } | 
|  |  | 
|  | void AddStreamsToOptions(cricket::MediaSessionOptions* session_options) { | 
|  | if (send_stream_1_ && send_audio_) { | 
|  | session_options->AddSendStream(cricket::MEDIA_TYPE_AUDIO, kAudioTrack1, | 
|  | kStream1); | 
|  | } | 
|  | if (send_stream_1_ && send_video_) { | 
|  | session_options->AddSendStream(cricket::MEDIA_TYPE_VIDEO, kVideoTrack1, | 
|  | kStream1); | 
|  | } | 
|  | if (send_stream_2_ && send_audio_) { | 
|  | session_options->AddSendStream(cricket::MEDIA_TYPE_AUDIO, kAudioTrack2, | 
|  | kStream2); | 
|  | } | 
|  | if (send_stream_2_ && send_video_) { | 
|  | session_options->AddSendStream(cricket::MEDIA_TYPE_VIDEO, kVideoTrack2, | 
|  | kStream2); | 
|  | } | 
|  | if (data_channel_ && session_->data_channel_type() == cricket::DCT_RTP) { | 
|  | session_options->AddSendStream(cricket::MEDIA_TYPE_DATA, | 
|  | data_channel_->label(), | 
|  | data_channel_->label()); | 
|  | } | 
|  | } | 
|  |  | 
|  | void GetOptionsForOffer( | 
|  | const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options, | 
|  | cricket::MediaSessionOptions* session_options) { | 
|  | ASSERT_TRUE(ExtractMediaSessionOptions(rtc_options, true, session_options)); | 
|  |  | 
|  | AddStreamsToOptions(session_options); | 
|  | if (rtc_options.offer_to_receive_audio == | 
|  | RTCOfferAnswerOptions::kUndefined) { | 
|  | session_options->recv_audio = | 
|  | session_options->HasSendMediaStream(cricket::MEDIA_TYPE_AUDIO); | 
|  | } | 
|  | if (rtc_options.offer_to_receive_video == | 
|  | RTCOfferAnswerOptions::kUndefined) { | 
|  | session_options->recv_video = | 
|  | session_options->HasSendMediaStream(cricket::MEDIA_TYPE_VIDEO); | 
|  | } | 
|  | session_options->bundle_enabled = | 
|  | session_options->bundle_enabled && | 
|  | (session_options->has_audio() || session_options->has_video() || | 
|  | session_options->has_data()); | 
|  |  | 
|  | if (session_->data_channel_type() == cricket::DCT_SCTP && data_channel_) { | 
|  | session_options->data_channel_type = cricket::DCT_SCTP; | 
|  | } | 
|  | } | 
|  |  | 
|  | void GetOptionsForAnswer(cricket::MediaSessionOptions* session_options) { | 
|  | // ParseConstraintsForAnswer is used to set some defaults. | 
|  | ASSERT_TRUE(webrtc::ParseConstraintsForAnswer(nullptr, session_options)); | 
|  |  | 
|  | AddStreamsToOptions(session_options); | 
|  | session_options->bundle_enabled = | 
|  | session_options->bundle_enabled && | 
|  | (session_options->has_audio() || session_options->has_video() || | 
|  | session_options->has_data()); | 
|  |  | 
|  | if (session_->data_channel_type() == cricket::DCT_SCTP) { | 
|  | session_options->data_channel_type = cricket::DCT_SCTP; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Creates a local offer and applies it. Starts ICE. | 
|  | // Call SendAudioVideoStreamX() before this function | 
|  | // to decide which streams to create. | 
|  | void InitiateCall() { | 
|  | SessionDescriptionInterface* offer = CreateOffer(); | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  | EXPECT_TRUE_WAIT(PeerConnectionInterface::kIceGatheringNew != | 
|  | observer_.ice_gathering_state_, | 
|  | kIceCandidatesTimeout); | 
|  | } | 
|  |  | 
|  | SessionDescriptionInterface* CreateOffer() { | 
|  | PeerConnectionInterface::RTCOfferAnswerOptions options; | 
|  | options.offer_to_receive_audio = | 
|  | RTCOfferAnswerOptions::kOfferToReceiveMediaTrue; | 
|  |  | 
|  | return CreateOffer(options); | 
|  | } | 
|  |  | 
|  | SessionDescriptionInterface* CreateOffer( | 
|  | const PeerConnectionInterface::RTCOfferAnswerOptions options) { | 
|  | rtc::scoped_refptr<WebRtcSessionCreateSDPObserverForTest> | 
|  | observer = new WebRtcSessionCreateSDPObserverForTest(); | 
|  | cricket::MediaSessionOptions session_options; | 
|  | GetOptionsForOffer(options, &session_options); | 
|  | session_->CreateOffer(observer, options, session_options); | 
|  | EXPECT_TRUE_WAIT( | 
|  | observer->state() != WebRtcSessionCreateSDPObserverForTest::kInit, | 
|  | 2000); | 
|  | return observer->ReleaseDescription(); | 
|  | } | 
|  |  | 
|  | SessionDescriptionInterface* CreateAnswer( | 
|  | const cricket::MediaSessionOptions& options) { | 
|  | rtc::scoped_refptr<WebRtcSessionCreateSDPObserverForTest> observer | 
|  | = new WebRtcSessionCreateSDPObserverForTest(); | 
|  | cricket::MediaSessionOptions session_options = options; | 
|  | GetOptionsForAnswer(&session_options); | 
|  | // Overwrite recv_audio and recv_video with passed-in values. | 
|  | session_options.recv_video = options.recv_video; | 
|  | session_options.recv_audio = options.recv_audio; | 
|  | session_->CreateAnswer(observer, session_options); | 
|  | EXPECT_TRUE_WAIT( | 
|  | observer->state() != WebRtcSessionCreateSDPObserverForTest::kInit, | 
|  | 2000); | 
|  | return observer->ReleaseDescription(); | 
|  | } | 
|  |  | 
|  | SessionDescriptionInterface* CreateAnswer() { | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_video = true; | 
|  | options.recv_audio = true; | 
|  | return CreateAnswer(options); | 
|  | } | 
|  |  | 
|  | bool ChannelsExist() const { | 
|  | return (session_->voice_channel() != NULL && | 
|  | session_->video_channel() != NULL); | 
|  | } | 
|  |  | 
|  | void VerifyCryptoParams(const cricket::SessionDescription* sdp) { | 
|  | 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); | 
|  | ASSERT_EQ(1U, audio_content->cryptos().size()); | 
|  | ASSERT_EQ(47U, audio_content->cryptos()[0].key_params.size()); | 
|  | ASSERT_EQ("AES_CM_128_HMAC_SHA1_80", | 
|  | audio_content->cryptos()[0].cipher_suite); | 
|  | EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), | 
|  | audio_content->protocol()); | 
|  |  | 
|  | 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()); | 
|  | EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), | 
|  | video_content->protocol()); | 
|  | } | 
|  |  | 
|  | void VerifyNoCryptoParams(const cricket::SessionDescription* sdp, bool dtls) { | 
|  | 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()); | 
|  |  | 
|  | if (dtls) { | 
|  | EXPECT_EQ(std::string(cricket::kMediaProtocolDtlsSavpf), | 
|  | audio_content->protocol()); | 
|  | EXPECT_EQ(std::string(cricket::kMediaProtocolDtlsSavpf), | 
|  | video_content->protocol()); | 
|  | } else { | 
|  | EXPECT_EQ(std::string(cricket::kMediaProtocolAvpf), | 
|  | audio_content->protocol()); | 
|  | EXPECT_EQ(std::string(cricket::kMediaProtocolAvpf), | 
|  | video_content->protocol()); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Set the internal fake description factories to do DTLS-SRTP. | 
|  | void SetFactoryDtlsSrtp() { | 
|  | desc_factory_->set_secure(cricket::SEC_DISABLED); | 
|  | std::string identity_name = "WebRTC" + | 
|  | rtc::ToString(rtc::CreateRandomId()); | 
|  | // Confirmed to work with KT_RSA and KT_ECDSA. | 
|  | tdesc_factory_->set_certificate( | 
|  | rtc::RTCCertificate::Create(rtc::scoped_ptr<rtc::SSLIdentity>( | 
|  | rtc::SSLIdentity::Generate(identity_name, rtc::KT_DEFAULT)))); | 
|  | tdesc_factory_->set_secure(cricket::SEC_REQUIRED); | 
|  | } | 
|  |  | 
|  | void VerifyFingerprintStatus(const cricket::SessionDescription* sdp, | 
|  | bool expected) { | 
|  | const TransportInfo* audio = sdp->GetTransportInfoByName("audio"); | 
|  | ASSERT_TRUE(audio != NULL); | 
|  | ASSERT_EQ(expected, audio->description.identity_fingerprint.get() != NULL); | 
|  | const TransportInfo* video = sdp->GetTransportInfoByName("video"); | 
|  | ASSERT_TRUE(video != NULL); | 
|  | ASSERT_EQ(expected, video->description.identity_fingerprint.get() != NULL); | 
|  | } | 
|  |  | 
|  | void VerifyAnswerFromNonCryptoOffer() { | 
|  | // Create an SDP without Crypto. | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_video = true; | 
|  | JsepSessionDescription* offer( | 
|  | CreateRemoteOffer(options, cricket::SEC_DISABLED)); | 
|  | ASSERT_TRUE(offer != NULL); | 
|  | VerifyNoCryptoParams(offer->description(), false); | 
|  | SetRemoteDescriptionOfferExpectError(kSdpWithoutSdesCrypto, | 
|  | offer); | 
|  | const webrtc::SessionDescriptionInterface* answer = CreateAnswer(); | 
|  | // Answer should be NULL as no crypto params in offer. | 
|  | ASSERT_TRUE(answer == NULL); | 
|  | } | 
|  |  | 
|  | void VerifyAnswerFromCryptoOffer() { | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_video = true; | 
|  | options.bundle_enabled = true; | 
|  | scoped_ptr<JsepSessionDescription> offer( | 
|  | CreateRemoteOffer(options, cricket::SEC_REQUIRED)); | 
|  | ASSERT_TRUE(offer.get() != NULL); | 
|  | VerifyCryptoParams(offer->description()); | 
|  | SetRemoteDescriptionWithoutError(offer.release()); | 
|  | scoped_ptr<SessionDescriptionInterface> answer(CreateAnswer()); | 
|  | ASSERT_TRUE(answer.get() != NULL); | 
|  | VerifyCryptoParams(answer->description()); | 
|  | } | 
|  |  | 
|  | bool IceUfragPwdEqual(const cricket::SessionDescription* desc1, | 
|  | const cricket::SessionDescription* desc2) { | 
|  | if (desc1->contents().size() != desc2->contents().size()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const cricket::ContentInfos& contents = desc1->contents(); | 
|  | cricket::ContentInfos::const_iterator it = contents.begin(); | 
|  |  | 
|  | for (; it != contents.end(); ++it) { | 
|  | const cricket::TransportDescription* transport_desc1 = | 
|  | desc1->GetTransportDescriptionByName(it->name); | 
|  | const cricket::TransportDescription* transport_desc2 = | 
|  | desc2->GetTransportDescriptionByName(it->name); | 
|  | if (!transport_desc1 || !transport_desc2) { | 
|  | return false; | 
|  | } | 
|  | if (transport_desc1->ice_pwd != transport_desc2->ice_pwd || | 
|  | transport_desc1->ice_ufrag != transport_desc2->ice_ufrag) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Compares ufrag/password only for the specified |media_type|. | 
|  | bool IceUfragPwdEqual(const cricket::SessionDescription* desc1, | 
|  | const cricket::SessionDescription* desc2, | 
|  | cricket::MediaType media_type) { | 
|  | if (desc1->contents().size() != desc2->contents().size()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const cricket::ContentInfo* cinfo = | 
|  | cricket::GetFirstMediaContent(desc1->contents(), media_type); | 
|  | const cricket::TransportDescription* transport_desc1 = | 
|  | desc1->GetTransportDescriptionByName(cinfo->name); | 
|  | const cricket::TransportDescription* transport_desc2 = | 
|  | desc2->GetTransportDescriptionByName(cinfo->name); | 
|  | if (!transport_desc1 || !transport_desc2) { | 
|  | return false; | 
|  | } | 
|  | if (transport_desc1->ice_pwd != transport_desc2->ice_pwd || | 
|  | transport_desc1->ice_ufrag != transport_desc2->ice_ufrag) { | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void RemoveIceUfragPwdLines(const SessionDescriptionInterface* current_desc, | 
|  | std::string *sdp) { | 
|  | const cricket::SessionDescription* desc = current_desc->description(); | 
|  | EXPECT_TRUE(current_desc->ToString(sdp)); | 
|  |  | 
|  | const cricket::ContentInfos& contents = desc->contents(); | 
|  | cricket::ContentInfos::const_iterator it = contents.begin(); | 
|  | // Replace ufrag and pwd lines with empty strings. | 
|  | for (; it != contents.end(); ++it) { | 
|  | const cricket::TransportDescription* transport_desc = | 
|  | desc->GetTransportDescriptionByName(it->name); | 
|  | std::string ufrag_line = "a=ice-ufrag:" + transport_desc->ice_ufrag | 
|  | + "\r\n"; | 
|  | std::string pwd_line = "a=ice-pwd:" + transport_desc->ice_pwd | 
|  | + "\r\n"; | 
|  | rtc::replace_substrs(ufrag_line.c_str(), ufrag_line.length(), | 
|  | "", 0, | 
|  | sdp); | 
|  | rtc::replace_substrs(pwd_line.c_str(), pwd_line.length(), | 
|  | "", 0, | 
|  | sdp); | 
|  | } | 
|  | } | 
|  |  | 
|  | void SetIceUfragPwd(SessionDescriptionInterface* current_desc, | 
|  | const std::string& ufrag, | 
|  | const std::string& pwd) { | 
|  | cricket::SessionDescription* desc = current_desc->description(); | 
|  | for (TransportInfo& transport_info : desc->transport_infos()) { | 
|  | cricket::TransportDescription& transport_desc = | 
|  | transport_info.description; | 
|  | transport_desc.ice_ufrag = ufrag; | 
|  | transport_desc.ice_pwd = pwd; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Sets ufrag/pwd for specified |media_type|. | 
|  | void SetIceUfragPwd(SessionDescriptionInterface* current_desc, | 
|  | cricket::MediaType media_type, | 
|  | const std::string& ufrag, | 
|  | const std::string& pwd) { | 
|  | cricket::SessionDescription* desc = current_desc->description(); | 
|  | const cricket::ContentInfo* cinfo = | 
|  | cricket::GetFirstMediaContent(desc->contents(), media_type); | 
|  | TransportInfo* transport_info = desc->GetTransportInfoByName(cinfo->name); | 
|  | cricket::TransportDescription* transport_desc = | 
|  | &transport_info->description; | 
|  | transport_desc->ice_ufrag = ufrag; | 
|  | 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) { | 
|  | EXPECT_TRUE(session_->SetLocalDescription(desc, NULL)); | 
|  | session_->MaybeStartGathering(); | 
|  | } | 
|  | void SetLocalDescriptionExpectState(SessionDescriptionInterface* desc, | 
|  | WebRtcSession::State expected_state) { | 
|  | SetLocalDescriptionWithoutError(desc); | 
|  | EXPECT_EQ(expected_state, session_->state()); | 
|  | } | 
|  | void SetLocalDescriptionExpectError(const std::string& action, | 
|  | const std::string& expected_error, | 
|  | SessionDescriptionInterface* desc) { | 
|  | std::string error; | 
|  | EXPECT_FALSE(session_->SetLocalDescription(desc, &error)); | 
|  | std::string sdp_type = "local "; | 
|  | sdp_type.append(action); | 
|  | EXPECT_NE(std::string::npos, error.find(sdp_type)); | 
|  | EXPECT_NE(std::string::npos, error.find(expected_error)); | 
|  | } | 
|  | void SetLocalDescriptionOfferExpectError(const std::string& expected_error, | 
|  | SessionDescriptionInterface* desc) { | 
|  | 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) { | 
|  | EXPECT_TRUE(session_->SetRemoteDescription(desc, NULL)); | 
|  | } | 
|  | 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(desc, &error)); | 
|  | std::string sdp_type = "remote "; | 
|  | sdp_type.append(action); | 
|  | EXPECT_NE(std::string::npos, error.find(sdp_type)); | 
|  | EXPECT_NE(std::string::npos, error.find(expected_error)); | 
|  | } | 
|  | void SetRemoteDescriptionOfferExpectError( | 
|  | const std::string& expected_error, SessionDescriptionInterface* desc) { | 
|  | SetRemoteDescriptionExpectError(SessionDescriptionInterface::kOffer, | 
|  | expected_error, desc); | 
|  | } | 
|  | void SetRemoteDescriptionPranswerExpectError( | 
|  | const std::string& expected_error, SessionDescriptionInterface* desc) { | 
|  | SetRemoteDescriptionExpectError(SessionDescriptionInterface::kPrAnswer, | 
|  | expected_error, desc); | 
|  | } | 
|  | void SetRemoteDescriptionAnswerExpectError( | 
|  | const std::string& expected_error, SessionDescriptionInterface* desc) { | 
|  | SetRemoteDescriptionExpectError(SessionDescriptionInterface::kAnswer, | 
|  | expected_error, desc); | 
|  | } | 
|  |  | 
|  | void CreateCryptoOfferAndNonCryptoAnswer(SessionDescriptionInterface** offer, | 
|  | SessionDescriptionInterface** nocrypto_answer) { | 
|  | // Create a SDP without Crypto. | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_video = true; | 
|  | options.bundle_enabled = true; | 
|  | *offer = CreateRemoteOffer(options, cricket::SEC_ENABLED); | 
|  | ASSERT_TRUE(*offer != NULL); | 
|  | VerifyCryptoParams((*offer)->description()); | 
|  |  | 
|  | *nocrypto_answer = CreateRemoteAnswer(*offer, options, | 
|  | cricket::SEC_DISABLED); | 
|  | EXPECT_TRUE(*nocrypto_answer != NULL); | 
|  | } | 
|  |  | 
|  | void CreateDtlsOfferAndNonDtlsAnswer(SessionDescriptionInterface** offer, | 
|  | SessionDescriptionInterface** nodtls_answer) { | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_video = true; | 
|  | options.bundle_enabled = true; | 
|  |  | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> temp_offer( | 
|  | CreateRemoteOffer(options, cricket::SEC_ENABLED)); | 
|  |  | 
|  | *nodtls_answer = | 
|  | CreateRemoteAnswer(temp_offer.get(), options, cricket::SEC_ENABLED); | 
|  | EXPECT_TRUE(*nodtls_answer != NULL); | 
|  | VerifyFingerprintStatus((*nodtls_answer)->description(), false); | 
|  | VerifyCryptoParams((*nodtls_answer)->description()); | 
|  |  | 
|  | SetFactoryDtlsSrtp(); | 
|  | *offer = CreateRemoteOffer(options, cricket::SEC_ENABLED); | 
|  | ASSERT_TRUE(*offer != NULL); | 
|  | VerifyFingerprintStatus((*offer)->description(), true); | 
|  | VerifyCryptoParams((*offer)->description()); | 
|  | } | 
|  |  | 
|  | JsepSessionDescription* CreateRemoteOfferWithVersion( | 
|  | cricket::MediaSessionOptions options, | 
|  | cricket::SecurePolicy secure_policy, | 
|  | const std::string& session_version, | 
|  | const SessionDescriptionInterface* current_desc) { | 
|  | std::string session_id = rtc::ToString(rtc::CreateRandomId64()); | 
|  | const cricket::SessionDescription* cricket_desc = NULL; | 
|  | if (current_desc) { | 
|  | cricket_desc = current_desc->description(); | 
|  | session_id = current_desc->session_id(); | 
|  | } | 
|  |  | 
|  | desc_factory_->set_secure(secure_policy); | 
|  | JsepSessionDescription* offer( | 
|  | new JsepSessionDescription(JsepSessionDescription::kOffer)); | 
|  | if (!offer->Initialize(desc_factory_->CreateOffer(options, cricket_desc), | 
|  | session_id, session_version)) { | 
|  | delete offer; | 
|  | offer = NULL; | 
|  | } | 
|  | return offer; | 
|  | } | 
|  | JsepSessionDescription* CreateRemoteOffer( | 
|  | cricket::MediaSessionOptions options) { | 
|  | return CreateRemoteOfferWithVersion(options, cricket::SEC_ENABLED, | 
|  | kSessionVersion, NULL); | 
|  | } | 
|  | JsepSessionDescription* CreateRemoteOffer( | 
|  | cricket::MediaSessionOptions options, cricket::SecurePolicy sdes_policy) { | 
|  | return CreateRemoteOfferWithVersion( | 
|  | options, sdes_policy, kSessionVersion, NULL); | 
|  | } | 
|  | JsepSessionDescription* CreateRemoteOffer( | 
|  | cricket::MediaSessionOptions options, | 
|  | const SessionDescriptionInterface* current_desc) { | 
|  | return CreateRemoteOfferWithVersion(options, cricket::SEC_ENABLED, | 
|  | kSessionVersion, current_desc); | 
|  | } | 
|  |  | 
|  | JsepSessionDescription* CreateRemoteOfferWithSctpPort( | 
|  | const char* sctp_stream_name, int new_port, | 
|  | cricket::MediaSessionOptions options) { | 
|  | options.data_channel_type = cricket::DCT_SCTP; | 
|  | options.AddSendStream(cricket::MEDIA_TYPE_DATA, "datachannel", | 
|  | sctp_stream_name); | 
|  | return ChangeSDPSctpPort(new_port, CreateRemoteOffer(options)); | 
|  | } | 
|  |  | 
|  | // Takes ownership of offer_basis (and deletes it). | 
|  | JsepSessionDescription* ChangeSDPSctpPort( | 
|  | int new_port, webrtc::SessionDescriptionInterface *offer_basis) { | 
|  | // Stringify the input SDP, swap the 5000 for 'new_port' and create a new | 
|  | // SessionDescription from the mutated string. | 
|  | const char* default_port_str = "5000"; | 
|  | char new_port_str[16]; | 
|  | rtc::sprintfn(new_port_str, sizeof(new_port_str), "%d", new_port); | 
|  | std::string offer_str; | 
|  | offer_basis->ToString(&offer_str); | 
|  | rtc::replace_substrs(default_port_str, strlen(default_port_str), | 
|  | new_port_str, strlen(new_port_str), | 
|  | &offer_str); | 
|  | JsepSessionDescription* offer = new JsepSessionDescription( | 
|  | offer_basis->type()); | 
|  | delete offer_basis; | 
|  | offer->Initialize(offer_str, NULL); | 
|  | return offer; | 
|  | } | 
|  |  | 
|  | // Create a remote offer. Call SendAudioVideoStreamX() | 
|  | // before this function to decide which streams to create. | 
|  | JsepSessionDescription* CreateRemoteOffer() { | 
|  | cricket::MediaSessionOptions options; | 
|  | GetOptionsForAnswer(&options); | 
|  | return CreateRemoteOffer(options, session_->remote_description()); | 
|  | } | 
|  |  | 
|  | JsepSessionDescription* CreateRemoteAnswer( | 
|  | const SessionDescriptionInterface* offer, | 
|  | cricket::MediaSessionOptions options, | 
|  | cricket::SecurePolicy policy) { | 
|  | desc_factory_->set_secure(policy); | 
|  | const std::string session_id = | 
|  | rtc::ToString(rtc::CreateRandomId64()); | 
|  | JsepSessionDescription* answer( | 
|  | new JsepSessionDescription(JsepSessionDescription::kAnswer)); | 
|  | if (!answer->Initialize(desc_factory_->CreateAnswer(offer->description(), | 
|  | options, NULL), | 
|  | session_id, kSessionVersion)) { | 
|  | delete answer; | 
|  | answer = NULL; | 
|  | } | 
|  | return answer; | 
|  | } | 
|  |  | 
|  | JsepSessionDescription* CreateRemoteAnswer( | 
|  | const SessionDescriptionInterface* offer, | 
|  | cricket::MediaSessionOptions options) { | 
|  | return CreateRemoteAnswer(offer, options, cricket::SEC_REQUIRED); | 
|  | } | 
|  |  | 
|  | // Creates an answer session description. | 
|  | // Call SendAudioVideoStreamX() before this function | 
|  | // to decide which streams to create. | 
|  | JsepSessionDescription* CreateRemoteAnswer( | 
|  | const SessionDescriptionInterface* offer) { | 
|  | cricket::MediaSessionOptions options; | 
|  | GetOptionsForAnswer(&options); | 
|  | return CreateRemoteAnswer(offer, options, cricket::SEC_REQUIRED); | 
|  | } | 
|  |  | 
|  | void TestSessionCandidatesWithBundleRtcpMux(bool bundle, bool rtcp_mux) { | 
|  | AddInterface(rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  |  | 
|  | PeerConnectionInterface::RTCOfferAnswerOptions options; | 
|  | options.use_rtp_mux = bundle; | 
|  |  | 
|  | SessionDescriptionInterface* offer = CreateOffer(options); | 
|  | // SetLocalDescription and SetRemoteDescriptions takes ownership of offer | 
|  | // and answer. | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  |  | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> answer( | 
|  | CreateRemoteAnswer(session_->local_description())); | 
|  | std::string sdp; | 
|  | EXPECT_TRUE(answer->ToString(&sdp)); | 
|  |  | 
|  | size_t expected_candidate_num = 2; | 
|  | if (!rtcp_mux) { | 
|  | // If rtcp_mux is enabled we should expect 4 candidates - host and srflex | 
|  | // for rtp and rtcp. | 
|  | expected_candidate_num = 4; | 
|  | // Disable rtcp-mux from the answer | 
|  | const std::string kRtcpMux = "a=rtcp-mux"; | 
|  | const std::string kXRtcpMux = "a=xrtcp-mux"; | 
|  | rtc::replace_substrs(kRtcpMux.c_str(), kRtcpMux.length(), | 
|  | kXRtcpMux.c_str(), kXRtcpMux.length(), | 
|  | &sdp); | 
|  | } | 
|  |  | 
|  | SessionDescriptionInterface* new_answer = CreateSessionDescription( | 
|  | JsepSessionDescription::kAnswer, sdp, NULL); | 
|  |  | 
|  | // SetRemoteDescription to enable rtcp mux. | 
|  | SetRemoteDescriptionWithoutError(new_answer); | 
|  | EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout); | 
|  | EXPECT_EQ(expected_candidate_num, observer_.mline_0_candidates_.size()); | 
|  | if (bundle) { | 
|  | EXPECT_EQ(0, observer_.mline_1_candidates_.size()); | 
|  | } else { | 
|  | EXPECT_EQ(expected_candidate_num, observer_.mline_1_candidates_.size()); | 
|  | } | 
|  | } | 
|  | // Tests that we can only send DTMF when the dtmf codec is supported. | 
|  | void TestCanInsertDtmf(bool can) { | 
|  | if (can) { | 
|  | InitWithDtmfCodec(); | 
|  | } else { | 
|  | Init(); | 
|  | } | 
|  | SendAudioVideoStream1(); | 
|  | CreateAndSetRemoteOfferAndLocalAnswer(); | 
|  | EXPECT_FALSE(session_->CanInsertDtmf("")); | 
|  | EXPECT_EQ(can, session_->CanInsertDtmf(kAudioTrack1)); | 
|  | } | 
|  |  | 
|  | // Helper class to configure loopback network and verify Best | 
|  | // Connection using right IP protocol for TestLoopbackCall | 
|  | // method. LoopbackNetworkManager applies firewall rules to block | 
|  | // all ping traffic once ICE completed, and remove them to observe | 
|  | // ICE reconnected again. This LoopbackNetworkConfiguration struct | 
|  | // verifies the best connection is using the right IP protocol after | 
|  | // initial ICE convergences. | 
|  |  | 
|  | class LoopbackNetworkConfiguration { | 
|  | public: | 
|  | LoopbackNetworkConfiguration() | 
|  | : test_ipv6_network_(false), | 
|  | test_extra_ipv4_network_(false), | 
|  | best_connection_after_initial_ice_converged_(1, 0) {} | 
|  |  | 
|  | // Used to track the expected best connection count in each IP protocol. | 
|  | struct ExpectedBestConnection { | 
|  | ExpectedBestConnection(int ipv4_count, int ipv6_count) | 
|  | : ipv4_count_(ipv4_count), | 
|  | ipv6_count_(ipv6_count) {} | 
|  |  | 
|  | int ipv4_count_; | 
|  | int ipv6_count_; | 
|  | }; | 
|  |  | 
|  | bool test_ipv6_network_; | 
|  | bool test_extra_ipv4_network_; | 
|  | ExpectedBestConnection best_connection_after_initial_ice_converged_; | 
|  |  | 
|  | void VerifyBestConnectionAfterIceConverge( | 
|  | const rtc::scoped_refptr<FakeMetricsObserver> metrics_observer) const { | 
|  | Verify(metrics_observer, best_connection_after_initial_ice_converged_); | 
|  | } | 
|  |  | 
|  | private: | 
|  | void Verify(const rtc::scoped_refptr<FakeMetricsObserver> metrics_observer, | 
|  | const ExpectedBestConnection& expected) const { | 
|  | EXPECT_EQ( | 
|  | metrics_observer->GetEnumCounter(webrtc::kEnumCounterAddressFamily, | 
|  | webrtc::kBestConnections_IPv4), | 
|  | expected.ipv4_count_); | 
|  | EXPECT_EQ( | 
|  | metrics_observer->GetEnumCounter(webrtc::kEnumCounterAddressFamily, | 
|  | webrtc::kBestConnections_IPv6), | 
|  | expected.ipv6_count_); | 
|  | // This is used in the loopback call so there is only single host to host | 
|  | // candidate pair. | 
|  | EXPECT_EQ(metrics_observer->GetEnumCounter( | 
|  | webrtc::kEnumCounterIceCandidatePairTypeUdp, | 
|  | webrtc::kIceCandidatePairHostHost), | 
|  | 0); | 
|  | EXPECT_EQ(metrics_observer->GetEnumCounter( | 
|  | webrtc::kEnumCounterIceCandidatePairTypeUdp, | 
|  | webrtc::kIceCandidatePairHostPublicHostPublic), | 
|  | 1); | 
|  | } | 
|  | }; | 
|  |  | 
|  | class LoopbackNetworkManager { | 
|  | public: | 
|  | LoopbackNetworkManager(WebRtcSessionTest* session, | 
|  | const LoopbackNetworkConfiguration& config) | 
|  | : config_(config) { | 
|  | session->AddInterface( | 
|  | rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); | 
|  | if (config_.test_extra_ipv4_network_) { | 
|  | session->AddInterface( | 
|  | rtc::SocketAddress(kClientAddrHost2, kClientAddrPort)); | 
|  | } | 
|  | if (config_.test_ipv6_network_) { | 
|  | session->AddInterface( | 
|  | rtc::SocketAddress(kClientIPv6AddrHost1, kClientAddrPort)); | 
|  | } | 
|  | } | 
|  |  | 
|  | void ApplyFirewallRules(rtc::FirewallSocketServer* fss) { | 
|  | fss->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, | 
|  | rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); | 
|  | if (config_.test_extra_ipv4_network_) { | 
|  | fss->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, | 
|  | rtc::SocketAddress(kClientAddrHost2, kClientAddrPort)); | 
|  | } | 
|  | if (config_.test_ipv6_network_) { | 
|  | fss->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, | 
|  | rtc::SocketAddress(kClientIPv6AddrHost1, kClientAddrPort)); | 
|  | } | 
|  | } | 
|  |  | 
|  | void ClearRules(rtc::FirewallSocketServer* fss) { fss->ClearRules(); } | 
|  |  | 
|  | private: | 
|  | LoopbackNetworkConfiguration config_; | 
|  | }; | 
|  |  | 
|  | // 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. | 
|  | // This code is placed in a method so that it can be invoked | 
|  | // by multiple tests with different allocators (e.g. with and without BUNDLE). | 
|  | // While running the call, this method also checks if the session goes through | 
|  | // the correct sequence of ICE states when a connection is established, | 
|  | // broken, and re-established. | 
|  | // The Connection state should go: | 
|  | // New -> Checking -> (Connected) -> Completed -> Disconnected -> Completed | 
|  | //     -> Failed. | 
|  | // The Gathering state should go: New -> Gathering -> Completed. | 
|  |  | 
|  | void SetupLoopbackCall() { | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | SessionDescriptionInterface* offer = CreateOffer(); | 
|  |  | 
|  | EXPECT_EQ(PeerConnectionInterface::kIceGatheringNew, | 
|  | observer_.ice_gathering_state_); | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  | EXPECT_EQ(PeerConnectionInterface::kIceConnectionNew, | 
|  | observer_.ice_connection_state_); | 
|  | EXPECT_EQ_WAIT(PeerConnectionInterface::kIceGatheringGathering, | 
|  | observer_.ice_gathering_state_, kIceCandidatesTimeout); | 
|  | EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout); | 
|  | EXPECT_EQ_WAIT(PeerConnectionInterface::kIceGatheringComplete, | 
|  | observer_.ice_gathering_state_, kIceCandidatesTimeout); | 
|  |  | 
|  | std::string sdp; | 
|  | offer->ToString(&sdp); | 
|  | SessionDescriptionInterface* desc = webrtc::CreateSessionDescription( | 
|  | JsepSessionDescription::kAnswer, sdp, nullptr); | 
|  | ASSERT_TRUE(desc != NULL); | 
|  | SetRemoteDescriptionWithoutError(desc); | 
|  |  | 
|  | EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionChecking, | 
|  | observer_.ice_connection_state_, kIceCandidatesTimeout); | 
|  |  | 
|  | // The ice connection state is "Connected" too briefly to catch in a test. | 
|  | EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionCompleted, | 
|  | observer_.ice_connection_state_, kIceCandidatesTimeout); | 
|  | } | 
|  |  | 
|  | void TestLoopbackCall(const LoopbackNetworkConfiguration& config) { | 
|  | LoopbackNetworkManager loopback_network_manager(this, config); | 
|  | SetupLoopbackCall(); | 
|  | config.VerifyBestConnectionAfterIceConverge(metrics_observer_); | 
|  | // Adding firewall rule to block ping requests, which should cause | 
|  | // transport channel failure. | 
|  |  | 
|  | loopback_network_manager.ApplyFirewallRules(fss_.get()); | 
|  |  | 
|  | LOG(LS_INFO) << "Firewall Rules applied"; | 
|  | EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionDisconnected, | 
|  | observer_.ice_connection_state_, | 
|  | kIceCandidatesTimeout); | 
|  |  | 
|  | metrics_observer_->Reset(); | 
|  |  | 
|  | // Clearing the rules, session should move back to completed state. | 
|  | loopback_network_manager.ClearRules(fss_.get()); | 
|  |  | 
|  | LOG(LS_INFO) << "Firewall Rules cleared"; | 
|  | EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionCompleted, | 
|  | observer_.ice_connection_state_, | 
|  | kIceCandidatesTimeout); | 
|  |  | 
|  | // Now we block ping requests and wait until the ICE connection transitions | 
|  | // to the Failed state.  This will take at least 30 seconds because it must | 
|  | // wait for the Port to timeout. | 
|  | int port_timeout = 30000; | 
|  |  | 
|  | loopback_network_manager.ApplyFirewallRules(fss_.get()); | 
|  | LOG(LS_INFO) << "Firewall Rules applied again"; | 
|  | EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionDisconnected, | 
|  | observer_.ice_connection_state_, | 
|  | kIceCandidatesTimeout + port_timeout); | 
|  | } | 
|  |  | 
|  | void TestLoopbackCall() { | 
|  | LoopbackNetworkConfiguration config; | 
|  | TestLoopbackCall(config); | 
|  | } | 
|  |  | 
|  | void TestPacketOptions() { | 
|  | media_controller_.reset( | 
|  | new cricket::FakeMediaController(channel_manager_.get(), &fake_call_)); | 
|  | LoopbackNetworkConfiguration config; | 
|  | LoopbackNetworkManager loopback_network_manager(this, config); | 
|  |  | 
|  | SetupLoopbackCall(); | 
|  |  | 
|  | uint8_t test_packet[15] = {0}; | 
|  | rtc::PacketOptions options; | 
|  | options.packet_id = 10; | 
|  | media_engine_->GetVideoChannel(0) | 
|  | ->SendRtp(test_packet, sizeof(test_packet), options); | 
|  |  | 
|  | const int kPacketTimeout = 2000; | 
|  | EXPECT_EQ_WAIT(fake_call_.last_sent_packet().packet_id, 10, kPacketTimeout); | 
|  | 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<cricket::AudioCodec> codecs = media_engine_->audio_codecs();; | 
|  | codecs.push_back(kCNCodec1); | 
|  | codecs.push_back(kCNCodec2); | 
|  | media_engine_->SetAudioCodecs(codecs); | 
|  | desc_factory_->set_audio_codecs(codecs); | 
|  | } | 
|  |  | 
|  | bool VerifyNoCNCodecs(const cricket::ContentInfo* content) { | 
|  | const cricket::ContentDescription* description = content->description; | 
|  | ASSERT(description != NULL); | 
|  | const cricket::AudioContentDescription* audio_content_desc = | 
|  | static_cast<const cricket::AudioContentDescription*>(description); | 
|  | ASSERT(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; | 
|  | ASSERT(session_.get()); | 
|  | dci.reliable = session_->data_channel_type() == cricket::DCT_SCTP; | 
|  | data_channel_ = DataChannel::Create( | 
|  | session_.get(), session_->data_channel_type(), "datachannel", dci); | 
|  | } | 
|  |  | 
|  | void SetLocalDescriptionWithDataChannel() { | 
|  | CreateDataChannel(); | 
|  | SessionDescriptionInterface* offer = CreateOffer(); | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  | } | 
|  |  | 
|  | void VerifyMultipleAsyncCreateDescription( | 
|  | RTCCertificateGenerationMethod cert_gen_method, | 
|  | CreateSessionDescriptionRequest::Type type) { | 
|  | InitWithDtls(cert_gen_method); | 
|  | VerifyMultipleAsyncCreateDescriptionAfterInit(true, type); | 
|  | } | 
|  |  | 
|  | void VerifyMultipleAsyncCreateDescriptionIdentityGenFailure( | 
|  | CreateSessionDescriptionRequest::Type type) { | 
|  | InitWithDtlsIdentityGenFail(); | 
|  | VerifyMultipleAsyncCreateDescriptionAfterInit(false, type); | 
|  | } | 
|  |  | 
|  | void VerifyMultipleAsyncCreateDescriptionAfterInit( | 
|  | bool success, CreateSessionDescriptionRequest::Type type) { | 
|  | RTC_CHECK(session_); | 
|  | SetFactoryDtlsSrtp(); | 
|  | if (type == CreateSessionDescriptionRequest::kAnswer) { | 
|  | cricket::MediaSessionOptions options; | 
|  | scoped_ptr<JsepSessionDescription> offer( | 
|  | CreateRemoteOffer(options, cricket::SEC_DISABLED)); | 
|  | ASSERT_TRUE(offer.get() != NULL); | 
|  | SetRemoteDescriptionWithoutError(offer.release()); | 
|  | } | 
|  |  | 
|  | PeerConnectionInterface::RTCOfferAnswerOptions options; | 
|  | cricket::MediaSessionOptions session_options; | 
|  | const int kNumber = 3; | 
|  | rtc::scoped_refptr<WebRtcSessionCreateSDPObserverForTest> | 
|  | observers[kNumber]; | 
|  | for (int i = 0; i < kNumber; ++i) { | 
|  | observers[i] = new WebRtcSessionCreateSDPObserverForTest(); | 
|  | if (type == CreateSessionDescriptionRequest::kOffer) { | 
|  | session_->CreateOffer(observers[i], options, session_options); | 
|  | } else { | 
|  | session_->CreateAnswer(observers[i], session_options); | 
|  | } | 
|  | } | 
|  |  | 
|  | WebRtcSessionCreateSDPObserverForTest::State expected_state = | 
|  | success ? WebRtcSessionCreateSDPObserverForTest::kSucceeded : | 
|  | WebRtcSessionCreateSDPObserverForTest::kFailed; | 
|  |  | 
|  | for (int i = 0; i < kNumber; ++i) { | 
|  | EXPECT_EQ_WAIT(expected_state, observers[i]->state(), 1000); | 
|  | if (success) { | 
|  | EXPECT_TRUE(observers[i]->description() != NULL); | 
|  | } else { | 
|  | EXPECT_TRUE(observers[i]->description() == NULL); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void ConfigureAllocatorWithTurn() { | 
|  | cricket::RelayServerConfig turn_server(cricket::RELAY_TURN); | 
|  | cricket::RelayCredentials credentials(kTurnUsername, kTurnPassword); | 
|  | turn_server.credentials = credentials; | 
|  | turn_server.ports.push_back( | 
|  | cricket::ProtocolAddress(kTurnUdpIntAddr, cricket::PROTO_UDP, false)); | 
|  | allocator_->AddTurnServer(turn_server); | 
|  | allocator_->set_step_delay(cricket::kMinimumStepDelay); | 
|  | allocator_->set_flags(cricket::PORTALLOCATOR_DISABLE_TCP); | 
|  | } | 
|  |  | 
|  | cricket::FakeMediaEngine* media_engine_; | 
|  | cricket::FakeDataEngine* data_engine_; | 
|  | rtc::scoped_ptr<cricket::ChannelManager> channel_manager_; | 
|  | cricket::FakeCall fake_call_; | 
|  | rtc::scoped_ptr<webrtc::MediaControllerInterface> media_controller_; | 
|  | rtc::scoped_ptr<cricket::TransportDescriptionFactory> tdesc_factory_; | 
|  | rtc::scoped_ptr<cricket::MediaSessionDescriptionFactory> desc_factory_; | 
|  | rtc::scoped_ptr<rtc::PhysicalSocketServer> pss_; | 
|  | rtc::scoped_ptr<rtc::VirtualSocketServer> vss_; | 
|  | rtc::scoped_ptr<rtc::FirewallSocketServer> fss_; | 
|  | rtc::SocketServerScope ss_scope_; | 
|  | rtc::SocketAddress stun_socket_addr_; | 
|  | rtc::scoped_ptr<cricket::TestStunServer> stun_server_; | 
|  | cricket::TestTurnServer turn_server_; | 
|  | rtc::FakeNetworkManager network_manager_; | 
|  | rtc::scoped_ptr<cricket::BasicPortAllocator> allocator_; | 
|  | PeerConnectionFactoryInterface::Options options_; | 
|  | PeerConnectionInterface::RTCConfiguration configuration_; | 
|  | rtc::scoped_ptr<WebRtcSessionForTest> session_; | 
|  | MockIceObserver observer_; | 
|  | cricket::FakeVideoMediaChannel* video_channel_; | 
|  | cricket::FakeVoiceMediaChannel* voice_channel_; | 
|  | rtc::scoped_refptr<FakeMetricsObserver> metrics_observer_; | 
|  | // The following flags affect options created for CreateOffer/CreateAnswer. | 
|  | bool send_stream_1_ = false; | 
|  | bool send_stream_2_ = false; | 
|  | bool send_audio_ = false; | 
|  | bool send_video_ = false; | 
|  | rtc::scoped_refptr<DataChannel> data_channel_; | 
|  | // Last values received from data channel creation signal. | 
|  | std::string last_data_channel_label_; | 
|  | InternalDataChannelInit last_data_channel_config_; | 
|  | bool session_destroyed_ = false; | 
|  | }; | 
|  |  | 
|  | TEST_P(WebRtcSessionTest, TestInitializeWithDtls) { | 
|  | InitWithDtls(GetParam()); | 
|  | // SDES is disabled when DTLS is on. | 
|  | EXPECT_EQ(cricket::SEC_DISABLED, session_->SdesPolicy()); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, TestInitializeWithoutDtls) { | 
|  | Init(); | 
|  | // SDES is required if DTLS is off. | 
|  | EXPECT_EQ(cricket::SEC_REQUIRED, session_->SdesPolicy()); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, TestSessionCandidates) { | 
|  | TestSessionCandidatesWithBundleRtcpMux(false, false); | 
|  | } | 
|  |  | 
|  | // Below test cases (TestSessionCandidatesWith*) verify the candidates gathered | 
|  | // with rtcp-mux and/or bundle. | 
|  | TEST_F(WebRtcSessionTest, TestSessionCandidatesWithRtcpMux) { | 
|  | TestSessionCandidatesWithBundleRtcpMux(false, true); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, TestSessionCandidatesWithBundleRtcpMux) { | 
|  | TestSessionCandidatesWithBundleRtcpMux(true, true); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, TestMultihomeCandidates) { | 
|  | AddInterface(rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); | 
|  | AddInterface(rtc::SocketAddress(kClientAddrHost2, kClientAddrPort)); | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | InitiateCall(); | 
|  | EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout); | 
|  | EXPECT_EQ(8u, observer_.mline_0_candidates_.size()); | 
|  | EXPECT_EQ(8u, observer_.mline_1_candidates_.size()); | 
|  | } | 
|  |  | 
|  | // Crashes on Win only. See webrtc:5411. | 
|  | #if defined(WEBRTC_WIN) | 
|  | #define MAYBE_TestStunError DISABLED_TestStunError | 
|  | #else | 
|  | #define MAYBE_TestStunError TestStunError | 
|  | #endif | 
|  | TEST_F(WebRtcSessionTest, MAYBE_TestStunError) { | 
|  | AddInterface(rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); | 
|  | AddInterface(rtc::SocketAddress(kClientAddrHost2, kClientAddrPort)); | 
|  | fss_->AddRule(false, | 
|  | rtc::FP_UDP, | 
|  | rtc::FD_ANY, | 
|  | rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | InitiateCall(); | 
|  | // Since kClientAddrHost1 is blocked, not expecting stun candidates for it. | 
|  | EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout); | 
|  | EXPECT_EQ(6u, observer_.mline_0_candidates_.size()); | 
|  | EXPECT_EQ(6u, observer_.mline_1_candidates_.size()); | 
|  | } | 
|  |  | 
|  | // Test session delivers no candidates gathered when constraint set to "none". | 
|  | TEST_F(WebRtcSessionTest, TestIceTransportsNone) { | 
|  | AddInterface(rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); | 
|  | InitWithIceTransport(PeerConnectionInterface::kNone); | 
|  | SendAudioVideoStream1(); | 
|  | InitiateCall(); | 
|  | EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout); | 
|  | EXPECT_EQ(0u, observer_.mline_0_candidates_.size()); | 
|  | EXPECT_EQ(0u, observer_.mline_1_candidates_.size()); | 
|  | } | 
|  |  | 
|  | // Test session delivers only relay candidates gathered when constaint set to | 
|  | // "relay". | 
|  | TEST_F(WebRtcSessionTest, TestIceTransportsRelay) { | 
|  | AddInterface(rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); | 
|  | ConfigureAllocatorWithTurn(); | 
|  | InitWithIceTransport(PeerConnectionInterface::kRelay); | 
|  | SendAudioVideoStream1(); | 
|  | InitiateCall(); | 
|  | EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout); | 
|  | EXPECT_EQ(2u, observer_.mline_0_candidates_.size()); | 
|  | EXPECT_EQ(2u, observer_.mline_1_candidates_.size()); | 
|  | for (size_t i = 0; i < observer_.mline_0_candidates_.size(); ++i) { | 
|  | EXPECT_EQ(cricket::RELAY_PORT_TYPE, | 
|  | observer_.mline_0_candidates_[i].type()); | 
|  | } | 
|  | for (size_t i = 0; i < observer_.mline_1_candidates_.size(); ++i) { | 
|  | EXPECT_EQ(cricket::RELAY_PORT_TYPE, | 
|  | observer_.mline_1_candidates_[i].type()); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Test session delivers all candidates gathered when constaint set to "all". | 
|  | TEST_F(WebRtcSessionTest, TestIceTransportsAll) { | 
|  | AddInterface(rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); | 
|  | InitWithIceTransport(PeerConnectionInterface::kAll); | 
|  | SendAudioVideoStream1(); | 
|  | InitiateCall(); | 
|  | EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout); | 
|  | // Host + STUN. By default allocator is disabled to gather relay candidates. | 
|  | EXPECT_EQ(4u, observer_.mline_0_candidates_.size()); | 
|  | EXPECT_EQ(4u, observer_.mline_1_candidates_.size()); | 
|  | } | 
|  |  | 
|  | 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<uint64_t>(session_version_orig), | 
|  | rtc::FromString<uint64_t>(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(); | 
|  | VerifyCryptoParams(offer->description()); | 
|  | SetRemoteDescriptionWithoutError(offer); | 
|  |  | 
|  | SendAudioVideoStream1(); | 
|  | SessionDescriptionInterface* answer = CreateAnswer(); | 
|  | VerifyCryptoParams(answer->description()); | 
|  | 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<uint64_t>(session_version_orig), | 
|  | rtc::FromString<uint64_t>(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); | 
|  | } | 
|  |  | 
|  | // | 
|  | // Tests for creating/setting SDP under different SDES/DTLS polices: | 
|  | // | 
|  | // --DTLS off and SDES on | 
|  | // TestCreateSdesOfferReceiveSdesAnswer/TestReceiveSdesOfferCreateSdesAnswer: | 
|  | //     set local/remote offer/answer with crypto --> success | 
|  | // TestSetNonSdesOfferWhenSdesOn: set local/remote offer without crypto ---> | 
|  | //     failure | 
|  | // TestSetLocalNonSdesAnswerWhenSdesOn: set local answer without crypto --> | 
|  | //     failure | 
|  | // TestSetRemoteNonSdesAnswerWhenSdesOn: set remote answer without crypto --> | 
|  | //     failure | 
|  | // | 
|  | // --DTLS on and SDES off | 
|  | // TestCreateDtlsOfferReceiveDtlsAnswer/TestReceiveDtlsOfferCreateDtlsAnswer: | 
|  | //     set local/remote offer/answer with DTLS fingerprint --> success | 
|  | // TestReceiveNonDtlsOfferWhenDtlsOn: set local/remote offer without DTLS | 
|  | //     fingerprint --> failure | 
|  | // TestSetLocalNonDtlsAnswerWhenDtlsOn: set local answer without fingerprint | 
|  | //     --> failure | 
|  | // TestSetRemoteNonDtlsAnswerWhenDtlsOn: set remote answer without fingerprint | 
|  | //     --> failure | 
|  | // | 
|  | // --Encryption disabled: DTLS off and SDES off | 
|  | // TestCreateOfferReceiveAnswerWithoutEncryption: set local offer and remote | 
|  | //     answer without SDES or DTLS --> success | 
|  | // TestCreateAnswerReceiveOfferWithoutEncryption: set remote offer and local | 
|  | //     answer without SDES or DTLS --> success | 
|  | // | 
|  |  | 
|  | // Test that we return a failure when applying a remote/local offer that doesn't | 
|  | // have cryptos enabled when DTLS is off. | 
|  | TEST_F(WebRtcSessionTest, TestSetNonSdesOfferWhenSdesOn) { | 
|  | Init(); | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_video = true; | 
|  | JsepSessionDescription* offer = CreateRemoteOffer( | 
|  | options, cricket::SEC_DISABLED); | 
|  | ASSERT_TRUE(offer != NULL); | 
|  | VerifyNoCryptoParams(offer->description(), false); | 
|  | // SetRemoteDescription and SetLocalDescription will take the ownership of | 
|  | // the offer. | 
|  | SetRemoteDescriptionOfferExpectError(kSdpWithoutSdesCrypto, offer); | 
|  | offer = CreateRemoteOffer(options, cricket::SEC_DISABLED); | 
|  | ASSERT_TRUE(offer != NULL); | 
|  | SetLocalDescriptionOfferExpectError(kSdpWithoutSdesCrypto, offer); | 
|  | } | 
|  |  | 
|  | // Test that we return a failure when applying a local answer that doesn't have | 
|  | // cryptos enabled when DTLS is off. | 
|  | TEST_F(WebRtcSessionTest, TestSetLocalNonSdesAnswerWhenSdesOn) { | 
|  | Init(); | 
|  | SessionDescriptionInterface* offer = NULL; | 
|  | SessionDescriptionInterface* answer = NULL; | 
|  | CreateCryptoOfferAndNonCryptoAnswer(&offer, &answer); | 
|  | // SetRemoteDescription and SetLocalDescription will take the ownership of | 
|  | // the offer. | 
|  | SetRemoteDescriptionWithoutError(offer); | 
|  | SetLocalDescriptionAnswerExpectError(kSdpWithoutSdesCrypto, answer); | 
|  | } | 
|  |  | 
|  | // Test we will return fail when apply an remote answer that doesn't have | 
|  | // crypto enabled when DTLS is off. | 
|  | TEST_F(WebRtcSessionTest, TestSetRemoteNonSdesAnswerWhenSdesOn) { | 
|  | Init(); | 
|  | SessionDescriptionInterface* offer = NULL; | 
|  | SessionDescriptionInterface* answer = NULL; | 
|  | CreateCryptoOfferAndNonCryptoAnswer(&offer, &answer); | 
|  | // SetRemoteDescription and SetLocalDescription will take the ownership of | 
|  | // the offer. | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  | SetRemoteDescriptionAnswerExpectError(kSdpWithoutSdesCrypto, answer); | 
|  | } | 
|  |  | 
|  | // Test that we accept an offer with a DTLS fingerprint when DTLS is on | 
|  | // and that we return an answer with a DTLS fingerprint. | 
|  | TEST_P(WebRtcSessionTest, TestReceiveDtlsOfferCreateDtlsAnswer) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  | SendAudioVideoStream1(); | 
|  | InitWithDtls(GetParam()); | 
|  | SetFactoryDtlsSrtp(); | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_video = true; | 
|  | JsepSessionDescription* offer = | 
|  | CreateRemoteOffer(options, cricket::SEC_DISABLED); | 
|  | ASSERT_TRUE(offer != NULL); | 
|  | VerifyFingerprintStatus(offer->description(), true); | 
|  | VerifyNoCryptoParams(offer->description(), true); | 
|  |  | 
|  | // SetRemoteDescription will take the ownership of the offer. | 
|  | SetRemoteDescriptionWithoutError(offer); | 
|  |  | 
|  | // Verify that we get a crypto fingerprint in the answer. | 
|  | SessionDescriptionInterface* answer = CreateAnswer(); | 
|  | ASSERT_TRUE(answer != NULL); | 
|  | VerifyFingerprintStatus(answer->description(), true); | 
|  | // Check that we don't have an a=crypto line in the answer. | 
|  | VerifyNoCryptoParams(answer->description(), true); | 
|  |  | 
|  | // Now set the local description, which should work, even without a=crypto. | 
|  | SetLocalDescriptionWithoutError(answer); | 
|  | } | 
|  |  | 
|  | // Test that we set a local offer with a DTLS fingerprint when DTLS is on | 
|  | // and then we accept a remote answer with a DTLS fingerprint successfully. | 
|  | TEST_P(WebRtcSessionTest, TestCreateDtlsOfferReceiveDtlsAnswer) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  | SendAudioVideoStream1(); | 
|  | InitWithDtls(GetParam()); | 
|  | SetFactoryDtlsSrtp(); | 
|  |  | 
|  | // Verify that we get a crypto fingerprint in the answer. | 
|  | SessionDescriptionInterface* offer = CreateOffer(); | 
|  | ASSERT_TRUE(offer != NULL); | 
|  | VerifyFingerprintStatus(offer->description(), true); | 
|  | // Check that we don't have an a=crypto line in the offer. | 
|  | VerifyNoCryptoParams(offer->description(), true); | 
|  |  | 
|  | // Now set the local description, which should work, even without a=crypto. | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  |  | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_video = true; | 
|  | JsepSessionDescription* answer = | 
|  | CreateRemoteAnswer(offer, options, cricket::SEC_DISABLED); | 
|  | ASSERT_TRUE(answer != NULL); | 
|  | VerifyFingerprintStatus(answer->description(), true); | 
|  | VerifyNoCryptoParams(answer->description(), true); | 
|  |  | 
|  | // SetRemoteDescription will take the ownership of the answer. | 
|  | SetRemoteDescriptionWithoutError(answer); | 
|  | } | 
|  |  | 
|  | // Test that if we support DTLS and the other side didn't offer a fingerprint, | 
|  | // we will fail to set the remote description. | 
|  | TEST_P(WebRtcSessionTest, TestReceiveNonDtlsOfferWhenDtlsOn) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  | InitWithDtls(GetParam()); | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_video = true; | 
|  | options.bundle_enabled = true; | 
|  | JsepSessionDescription* offer = CreateRemoteOffer( | 
|  | options, cricket::SEC_REQUIRED); | 
|  | ASSERT_TRUE(offer != NULL); | 
|  | VerifyFingerprintStatus(offer->description(), false); | 
|  | VerifyCryptoParams(offer->description()); | 
|  |  | 
|  | // SetRemoteDescription will take the ownership of the offer. | 
|  | SetRemoteDescriptionOfferExpectError( | 
|  | kSdpWithoutDtlsFingerprint, offer); | 
|  |  | 
|  | offer = CreateRemoteOffer(options, cricket::SEC_REQUIRED); | 
|  | // SetLocalDescription will take the ownership of the offer. | 
|  | SetLocalDescriptionOfferExpectError( | 
|  | kSdpWithoutDtlsFingerprint, offer); | 
|  | } | 
|  |  | 
|  | // Test that we return a failure when applying a local answer that doesn't have | 
|  | // a DTLS fingerprint when DTLS is required. | 
|  | TEST_P(WebRtcSessionTest, TestSetLocalNonDtlsAnswerWhenDtlsOn) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  | InitWithDtls(GetParam()); | 
|  | SessionDescriptionInterface* offer = NULL; | 
|  | SessionDescriptionInterface* answer = NULL; | 
|  | CreateDtlsOfferAndNonDtlsAnswer(&offer, &answer); | 
|  |  | 
|  | // SetRemoteDescription and SetLocalDescription will take the ownership of | 
|  | // the offer and answer. | 
|  | SetRemoteDescriptionWithoutError(offer); | 
|  | SetLocalDescriptionAnswerExpectError( | 
|  | kSdpWithoutDtlsFingerprint, answer); | 
|  | } | 
|  |  | 
|  | // Test that we return a failure when applying a remote answer that doesn't have | 
|  | // a DTLS fingerprint when DTLS is required. | 
|  | TEST_P(WebRtcSessionTest, TestSetRemoteNonDtlsAnswerWhenDtlsOn) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  | InitWithDtls(GetParam()); | 
|  | SessionDescriptionInterface* offer = CreateOffer(); | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_video = true; | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> temp_offer( | 
|  | CreateRemoteOffer(options, cricket::SEC_ENABLED)); | 
|  | JsepSessionDescription* answer = | 
|  | CreateRemoteAnswer(temp_offer.get(), options, cricket::SEC_ENABLED); | 
|  |  | 
|  | // SetRemoteDescription and SetLocalDescription will take the ownership of | 
|  | // the offer and answer. | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  | SetRemoteDescriptionAnswerExpectError( | 
|  | kSdpWithoutDtlsFingerprint, answer); | 
|  | } | 
|  |  | 
|  | // Test that we create a local offer without SDES or DTLS and accept a remote | 
|  | // answer without SDES or DTLS when encryption is disabled. | 
|  | TEST_P(WebRtcSessionTest, TestCreateOfferReceiveAnswerWithoutEncryption) { | 
|  | SendAudioVideoStream1(); | 
|  | options_.disable_encryption = true; | 
|  | InitWithDtls(GetParam()); | 
|  |  | 
|  | // Verify that we get a crypto fingerprint in the answer. | 
|  | SessionDescriptionInterface* offer = CreateOffer(); | 
|  | ASSERT_TRUE(offer != NULL); | 
|  | VerifyFingerprintStatus(offer->description(), false); | 
|  | // Check that we don't have an a=crypto line in the offer. | 
|  | VerifyNoCryptoParams(offer->description(), false); | 
|  |  | 
|  | // Now set the local description, which should work, even without a=crypto. | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  |  | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_video = true; | 
|  | JsepSessionDescription* answer = | 
|  | CreateRemoteAnswer(offer, options, cricket::SEC_DISABLED); | 
|  | ASSERT_TRUE(answer != NULL); | 
|  | VerifyFingerprintStatus(answer->description(), false); | 
|  | VerifyNoCryptoParams(answer->description(), false); | 
|  |  | 
|  | // SetRemoteDescription will take the ownership of the answer. | 
|  | SetRemoteDescriptionWithoutError(answer); | 
|  | } | 
|  |  | 
|  | // Test that we create a local answer without SDES or DTLS and accept a remote | 
|  | // offer without SDES or DTLS when encryption is disabled. | 
|  | TEST_P(WebRtcSessionTest, TestCreateAnswerReceiveOfferWithoutEncryption) { | 
|  | options_.disable_encryption = true; | 
|  | InitWithDtls(GetParam()); | 
|  |  | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_video = true; | 
|  | JsepSessionDescription* offer = | 
|  | CreateRemoteOffer(options, cricket::SEC_DISABLED); | 
|  | ASSERT_TRUE(offer != NULL); | 
|  | VerifyFingerprintStatus(offer->description(), false); | 
|  | VerifyNoCryptoParams(offer->description(), false); | 
|  |  | 
|  | // SetRemoteDescription will take the ownership of the offer. | 
|  | SetRemoteDescriptionWithoutError(offer); | 
|  |  | 
|  | // Verify that we get a crypto fingerprint in the answer. | 
|  | SessionDescriptionInterface* answer = CreateAnswer(); | 
|  | ASSERT_TRUE(answer != NULL); | 
|  | VerifyFingerprintStatus(answer->description(), false); | 
|  | // Check that we don't have an a=crypto line in the answer. | 
|  | VerifyNoCryptoParams(answer->description(), false); | 
|  |  | 
|  | // Now set the local description, which should work, even without a=crypto. | 
|  | SetLocalDescriptionWithoutError(answer); | 
|  | } | 
|  |  | 
|  | // 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 | 
|  | TEST_P(WebRtcSessionTest, TestCreateAnswerWithDifferentSslRoles) { | 
|  | SendAudioVideoStream1(); | 
|  | InitWithDtls(GetParam()); | 
|  | SetFactoryDtlsSrtp(); | 
|  |  | 
|  | SessionDescriptionInterface* offer = CreateOffer(); | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  |  | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_video = true; | 
|  |  | 
|  | // First, negotiate different SSL roles. | 
|  | SessionDescriptionInterface* answer = | 
|  | CreateRemoteAnswer(offer, options, cricket::SEC_DISABLED); | 
|  | TransportInfo* audio_transport_info = | 
|  | answer->description()->GetTransportInfoByName("audio"); | 
|  | audio_transport_info->description.connection_role = | 
|  | cricket::CONNECTIONROLE_ACTIVE; | 
|  | TransportInfo* video_transport_info = | 
|  | answer->description()->GetTransportInfoByName("video"); | 
|  | video_transport_info->description.connection_role = | 
|  | cricket::CONNECTIONROLE_PASSIVE; | 
|  | SetRemoteDescriptionWithoutError(answer); | 
|  |  | 
|  | // Now create an offer in the reverse direction, and ensure the initial | 
|  | // offerer responds with an answer with correct SSL roles. | 
|  | offer = CreateRemoteOfferWithVersion(options, cricket::SEC_DISABLED, | 
|  | kSessionVersion, | 
|  | session_->remote_description()); | 
|  | SetRemoteDescriptionWithoutError(offer); | 
|  |  | 
|  | answer = CreateAnswer(); | 
|  | audio_transport_info = answer->description()->GetTransportInfoByName("audio"); | 
|  | EXPECT_EQ(cricket::CONNECTIONROLE_PASSIVE, | 
|  | audio_transport_info->description.connection_role); | 
|  | video_transport_info = answer->description()->GetTransportInfoByName("video"); | 
|  | EXPECT_EQ(cricket::CONNECTIONROLE_ACTIVE, | 
|  | video_transport_info->description.connection_role); | 
|  | SetLocalDescriptionWithoutError(answer); | 
|  |  | 
|  | // Lastly, start BUNDLE-ing on "audio", expecting that the "passive" role of | 
|  | // audio is transferred over to video in the answer that completes the BUNDLE | 
|  | // negotiation. | 
|  | options.bundle_enabled = true; | 
|  | offer = CreateRemoteOfferWithVersion(options, cricket::SEC_DISABLED, | 
|  | kSessionVersion, | 
|  | session_->remote_description()); | 
|  | SetRemoteDescriptionWithoutError(offer); | 
|  | answer = CreateAnswer(); | 
|  | audio_transport_info = answer->description()->GetTransportInfoByName("audio"); | 
|  | EXPECT_EQ(cricket::CONNECTIONROLE_PASSIVE, | 
|  | audio_transport_info->description.connection_role); | 
|  | video_transport_info = answer->description()->GetTransportInfoByName("video"); | 
|  | EXPECT_EQ(cricket::CONNECTIONROLE_PASSIVE, | 
|  | video_transport_info->description.connection_role); | 
|  | 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<JsepSessionDescription*>(CreateAnswer()); | 
|  | pranswer->set_type(SessionDescriptionInterface::kPrAnswer); | 
|  | SetLocalDescriptionExpectState(pranswer, WebRtcSession::STATE_SENTPRANSWER); | 
|  |  | 
|  | SendAudioVideoStream1(); | 
|  | JsepSessionDescription* pranswer2 = | 
|  | static_cast<JsepSessionDescription*>(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(); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> offer(CreateOffer()); | 
|  |  | 
|  | SessionDescriptionInterface* answer = | 
|  | CreateRemoteAnswer(offer.get()); | 
|  | SetLocalDescriptionAnswerExpectError("Called in wrong state: STATE_INIT", | 
|  | answer); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, TestSetRemoteAnswerWithoutOffer) { | 
|  | Init(); | 
|  | SendNothing(); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> offer(CreateOffer()); | 
|  |  | 
|  | SessionDescriptionInterface* answer = | 
|  | CreateRemoteAnswer(offer.get()); | 
|  | SetRemoteDescriptionAnswerExpectError( | 
|  | "Called in wrong state: STATE_INIT", answer); | 
|  | } | 
|  |  | 
|  | // Tests that the remote candidates are added and removed successfully. | 
|  | TEST_F(WebRtcSessionTest, TestAddAndRemoveRemoteCandidates) { | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  |  | 
|  | cricket::Candidate candidate(1, "udp", rtc::SocketAddress("1.1.1.1", 5000), 0, | 
|  | "", "", "host", 0, ""); | 
|  | candidate.set_transport_name("audio"); | 
|  | JsepIceCandidate ice_candidate1(kMediaContentName0, 0, candidate); | 
|  |  | 
|  | // Fail since we have not set a remote description. | 
|  | EXPECT_FALSE(session_->ProcessIceMessage(&ice_candidate1)); | 
|  |  | 
|  | SessionDescriptionInterface* offer = CreateOffer(); | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  |  | 
|  | // Fail since we have not set a remote description. | 
|  | EXPECT_FALSE(session_->ProcessIceMessage(&ice_candidate1)); | 
|  |  | 
|  | SessionDescriptionInterface* answer = CreateRemoteAnswer( | 
|  | session_->local_description()); | 
|  | SetRemoteDescriptionWithoutError(answer); | 
|  |  | 
|  | EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate1)); | 
|  | candidate.set_component(2); | 
|  | candidate.set_address(rtc::SocketAddress("2.2.2.2", 6000)); | 
|  | JsepIceCandidate ice_candidate2(kMediaContentName0, 0, candidate); | 
|  | EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate2)); | 
|  |  | 
|  | // Verifying the candidates are copied properly from internal vector. | 
|  | const SessionDescriptionInterface* remote_desc = | 
|  | session_->remote_description(); | 
|  | ASSERT_TRUE(remote_desc != NULL); | 
|  | ASSERT_EQ(2u, remote_desc->number_of_mediasections()); | 
|  | const IceCandidateCollection* candidates = | 
|  | remote_desc->candidates(kMediaContentIndex0); | 
|  | ASSERT_EQ(2u, candidates->count()); | 
|  | EXPECT_EQ(kMediaContentIndex0, candidates->at(0)->sdp_mline_index()); | 
|  | EXPECT_EQ(kMediaContentName0, candidates->at(0)->sdp_mid()); | 
|  | EXPECT_EQ(1, candidates->at(0)->candidate().component()); | 
|  | EXPECT_EQ(2, candidates->at(1)->candidate().component()); | 
|  |  | 
|  | // |ice_candidate3| is identical to |ice_candidate2|.  It can be added | 
|  | // successfully, but the total count of candidates will not increase. | 
|  | candidate.set_component(2); | 
|  | JsepIceCandidate ice_candidate3(kMediaContentName0, 0, candidate); | 
|  | EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate3)); | 
|  | ASSERT_EQ(2u, candidates->count()); | 
|  |  | 
|  | JsepIceCandidate bad_ice_candidate("bad content name", 99, candidate); | 
|  | EXPECT_FALSE(session_->ProcessIceMessage(&bad_ice_candidate)); | 
|  |  | 
|  | // Remove candidate1 and candidate2 | 
|  | std::vector<cricket::Candidate> remote_candidates; | 
|  | remote_candidates.push_back(ice_candidate1.candidate()); | 
|  | remote_candidates.push_back(ice_candidate2.candidate()); | 
|  | EXPECT_TRUE(session_->RemoveRemoteIceCandidates(remote_candidates)); | 
|  | EXPECT_EQ(0u, candidates->count()); | 
|  | } | 
|  |  | 
|  | // Tests that a remote candidate is added to the remote session description and | 
|  | // that it is retained if the remote session description is changed. | 
|  | TEST_F(WebRtcSessionTest, TestRemoteCandidatesAddedToSessionDescription) { | 
|  | Init(); | 
|  | cricket::Candidate candidate1; | 
|  | candidate1.set_component(1); | 
|  | JsepIceCandidate ice_candidate1(kMediaContentName0, kMediaContentIndex0, | 
|  | candidate1); | 
|  | SendAudioVideoStream1(); | 
|  | CreateAndSetRemoteOfferAndLocalAnswer(); | 
|  |  | 
|  | EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate1)); | 
|  | const SessionDescriptionInterface* remote_desc = | 
|  | session_->remote_description(); | 
|  | ASSERT_TRUE(remote_desc != NULL); | 
|  | ASSERT_EQ(2u, remote_desc->number_of_mediasections()); | 
|  | const IceCandidateCollection* candidates = | 
|  | remote_desc->candidates(kMediaContentIndex0); | 
|  | ASSERT_EQ(1u, candidates->count()); | 
|  | EXPECT_EQ(kMediaContentIndex0, candidates->at(0)->sdp_mline_index()); | 
|  |  | 
|  | // Update the RemoteSessionDescription with a new session description and | 
|  | // a candidate and check that the new remote session description contains both | 
|  | // candidates. | 
|  | SessionDescriptionInterface* offer = CreateRemoteOffer(); | 
|  | cricket::Candidate candidate2; | 
|  | JsepIceCandidate ice_candidate2(kMediaContentName0, kMediaContentIndex0, | 
|  | candidate2); | 
|  | EXPECT_TRUE(offer->AddCandidate(&ice_candidate2)); | 
|  | SetRemoteDescriptionWithoutError(offer); | 
|  |  | 
|  | remote_desc = session_->remote_description(); | 
|  | ASSERT_TRUE(remote_desc != NULL); | 
|  | ASSERT_EQ(2u, remote_desc->number_of_mediasections()); | 
|  | candidates = remote_desc->candidates(kMediaContentIndex0); | 
|  | ASSERT_EQ(2u, candidates->count()); | 
|  | EXPECT_EQ(kMediaContentIndex0, candidates->at(0)->sdp_mline_index()); | 
|  | // Username and password have be updated with the TransportInfo of the | 
|  | // SessionDescription, won't be equal to the original one. | 
|  | candidate2.set_username(candidates->at(0)->candidate().username()); | 
|  | candidate2.set_password(candidates->at(0)->candidate().password()); | 
|  | EXPECT_TRUE(candidate2.IsEquivalent(candidates->at(0)->candidate())); | 
|  | EXPECT_EQ(kMediaContentIndex0, candidates->at(1)->sdp_mline_index()); | 
|  | // No need to verify the username and password. | 
|  | candidate1.set_username(candidates->at(1)->candidate().username()); | 
|  | candidate1.set_password(candidates->at(1)->candidate().password()); | 
|  | EXPECT_TRUE(candidate1.IsEquivalent(candidates->at(1)->candidate())); | 
|  |  | 
|  | // Test that the candidate is ignored if we can add the same candidate again. | 
|  | EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate2)); | 
|  | } | 
|  |  | 
|  | // Test that local candidates are added to the local session description and | 
|  | // that they are retained if the local session description is changed. And if | 
|  | // continual gathering is enabled, they are removed from the local session | 
|  | // description when the network is down. | 
|  | TEST_F(WebRtcSessionTest, | 
|  | TestLocalCandidatesAddedAndRemovedIfGatherContinually) { | 
|  | AddInterface(rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | CreateAndSetRemoteOfferAndLocalAnswer(); | 
|  |  | 
|  | const SessionDescriptionInterface* local_desc = session_->local_description(); | 
|  | const IceCandidateCollection* candidates = | 
|  | local_desc->candidates(kMediaContentIndex0); | 
|  | ASSERT_TRUE(candidates != NULL); | 
|  | EXPECT_EQ(0u, candidates->count()); | 
|  |  | 
|  | EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout); | 
|  |  | 
|  | local_desc = session_->local_description(); | 
|  | candidates = local_desc->candidates(kMediaContentIndex0); | 
|  | ASSERT_TRUE(candidates != NULL); | 
|  | EXPECT_LT(0u, candidates->count()); | 
|  | candidates = local_desc->candidates(1); | 
|  | ASSERT_TRUE(candidates != NULL); | 
|  | EXPECT_EQ(0u, candidates->count()); | 
|  |  | 
|  | // Update the session descriptions. | 
|  | SendAudioVideoStream1(); | 
|  | CreateAndSetRemoteOfferAndLocalAnswer(); | 
|  |  | 
|  | local_desc = session_->local_description(); | 
|  | candidates = local_desc->candidates(kMediaContentIndex0); | 
|  | ASSERT_TRUE(candidates != NULL); | 
|  | EXPECT_LT(0u, candidates->count()); | 
|  | candidates = local_desc->candidates(1); | 
|  | ASSERT_TRUE(candidates != NULL); | 
|  | EXPECT_EQ(0u, candidates->count()); | 
|  |  | 
|  | candidates = local_desc->candidates(kMediaContentIndex0); | 
|  | size_t num_local_candidates = candidates->count(); | 
|  | // Enable Continual Gathering | 
|  | session_->SetIceConfig(cricket::IceConfig(-1, -1, true, false, -1)); | 
|  | // Bring down the network interface to trigger candidate removals. | 
|  | RemoveInterface(rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); | 
|  | // Verify that all local candidates are removed. | 
|  | EXPECT_EQ(0, observer_.num_candidates_removed_); | 
|  | EXPECT_EQ_WAIT(num_local_candidates, observer_.num_candidates_removed_, | 
|  | kIceCandidatesTimeout); | 
|  | EXPECT_EQ_WAIT(0u, candidates->count(), kIceCandidatesTimeout); | 
|  | } | 
|  |  | 
|  | // Tests that if continual gathering is disabled, local candidates won't be | 
|  | // removed when the interface is turned down. | 
|  | TEST_F(WebRtcSessionTest, TestLocalCandidatesNotRemovedIfNotGatherContinually) { | 
|  | AddInterface(rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | CreateAndSetRemoteOfferAndLocalAnswer(); | 
|  |  | 
|  | const SessionDescriptionInterface* local_desc = session_->local_description(); | 
|  | const IceCandidateCollection* candidates = | 
|  | local_desc->candidates(kMediaContentIndex0); | 
|  | ASSERT_TRUE(candidates != NULL); | 
|  | EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout); | 
|  |  | 
|  | size_t num_local_candidates = candidates->count(); | 
|  | EXPECT_LT(0u, num_local_candidates); | 
|  | // By default, Continual Gathering is disabled. | 
|  | // Bring down the network interface. | 
|  | RemoveInterface(rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); | 
|  | // Verify that the local candidates are not removed. | 
|  | rtc::Thread::Current()->ProcessMessages(1000); | 
|  | EXPECT_EQ(0, observer_.num_candidates_removed_); | 
|  | EXPECT_EQ(num_local_candidates, candidates->count()); | 
|  | } | 
|  |  | 
|  | // Test that we can set a remote session description with remote candidates. | 
|  | TEST_F(WebRtcSessionTest, TestSetRemoteSessionDescriptionWithCandidates) { | 
|  | Init(); | 
|  |  | 
|  | cricket::Candidate candidate1; | 
|  | candidate1.set_component(1); | 
|  | JsepIceCandidate ice_candidate(kMediaContentName0, kMediaContentIndex0, | 
|  | candidate1); | 
|  | SendAudioVideoStream1(); | 
|  | SessionDescriptionInterface* offer = CreateOffer(); | 
|  |  | 
|  | EXPECT_TRUE(offer->AddCandidate(&ice_candidate)); | 
|  | SetRemoteDescriptionWithoutError(offer); | 
|  |  | 
|  | const SessionDescriptionInterface* remote_desc = | 
|  | session_->remote_description(); | 
|  | ASSERT_TRUE(remote_desc != NULL); | 
|  | ASSERT_EQ(2u, remote_desc->number_of_mediasections()); | 
|  | const IceCandidateCollection* candidates = | 
|  | remote_desc->candidates(kMediaContentIndex0); | 
|  | ASSERT_EQ(1u, candidates->count()); | 
|  | EXPECT_EQ(kMediaContentIndex0, candidates->at(0)->sdp_mline_index()); | 
|  |  | 
|  | SessionDescriptionInterface* answer = CreateAnswer(); | 
|  | SetLocalDescriptionWithoutError(answer); | 
|  | } | 
|  |  | 
|  | // Test that offers and answers contains ice candidates when Ice candidates have | 
|  | // been gathered. | 
|  | TEST_F(WebRtcSessionTest, TestSetLocalAndRemoteDescriptionWithCandidates) { | 
|  | AddInterface(rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | // Ice is started but candidates are not provided until SetLocalDescription | 
|  | // is called. | 
|  | EXPECT_EQ(0u, observer_.mline_0_candidates_.size()); | 
|  | EXPECT_EQ(0u, observer_.mline_1_candidates_.size()); | 
|  | CreateAndSetRemoteOfferAndLocalAnswer(); | 
|  | // Wait until at least one local candidate has been collected. | 
|  | EXPECT_TRUE_WAIT(0u < observer_.mline_0_candidates_.size(), | 
|  | kIceCandidatesTimeout); | 
|  |  | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> local_offer(CreateOffer()); | 
|  |  | 
|  | ASSERT_TRUE(local_offer->candidates(kMediaContentIndex0) != NULL); | 
|  | EXPECT_LT(0u, local_offer->candidates(kMediaContentIndex0)->count()); | 
|  |  | 
|  | SessionDescriptionInterface* remote_offer(CreateRemoteOffer()); | 
|  | SetRemoteDescriptionWithoutError(remote_offer); | 
|  | SessionDescriptionInterface* answer = CreateAnswer(); | 
|  | ASSERT_TRUE(answer->candidates(kMediaContentIndex0) != NULL); | 
|  | EXPECT_LT(0u, answer->candidates(kMediaContentIndex0)->count()); | 
|  | SetLocalDescriptionWithoutError(answer); | 
|  | } | 
|  |  | 
|  | // Verifies TransportProxy and media channels are created with content names | 
|  | // present in the SessionDescription. | 
|  | TEST_F(WebRtcSessionTest, TestChannelCreationsWithContentNames) { | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> 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)); | 
|  | const std::string kAudioMid = "a=mid:audio"; | 
|  | const std::string kAudioMidReplaceStr = "a=mid:audio_content_name"; | 
|  | const std::string kVideoMid = "a=mid:video"; | 
|  | const std::string kVideoMidReplaceStr = "a=mid:video_content_name"; | 
|  |  | 
|  | // Replacing |audio| with |audio_content_name|. | 
|  | rtc::replace_substrs(kAudioMid.c_str(), kAudioMid.length(), | 
|  | kAudioMidReplaceStr.c_str(), | 
|  | kAudioMidReplaceStr.length(), | 
|  | &sdp); | 
|  | // Replacing |video| with |video_content_name|. | 
|  | rtc::replace_substrs(kVideoMid.c_str(), kVideoMid.length(), | 
|  | kVideoMidReplaceStr.c_str(), | 
|  | kVideoMidReplaceStr.length(), | 
|  | &sdp); | 
|  |  | 
|  | SessionDescriptionInterface* modified_offer = | 
|  | CreateSessionDescription(JsepSessionDescription::kOffer, sdp, NULL); | 
|  |  | 
|  | SetRemoteDescriptionWithoutError(modified_offer); | 
|  |  | 
|  | SessionDescriptionInterface* answer = CreateAnswer(); | 
|  | SetLocalDescriptionWithoutError(answer); | 
|  |  | 
|  | cricket::TransportChannel* voice_transport_channel = | 
|  | session_->voice_rtp_transport_channel(); | 
|  | EXPECT_TRUE(voice_transport_channel != NULL); | 
|  | EXPECT_EQ(voice_transport_channel->transport_name(), "audio_content_name"); | 
|  | cricket::TransportChannel* video_transport_channel = | 
|  | session_->video_rtp_transport_channel(); | 
|  | ASSERT_TRUE(video_transport_channel != NULL); | 
|  | EXPECT_EQ(video_transport_channel->transport_name(), "video_content_name"); | 
|  | 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(); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> offer(CreateOffer()); | 
|  |  | 
|  | 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 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(); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> offer(CreateOffer()); | 
|  |  | 
|  | const cricket::ContentInfo* content = | 
|  | cricket::GetFirstAudioContent(offer->description()); | 
|  | EXPECT_TRUE(content != NULL); | 
|  | content = cricket::GetFirstVideoContent(offer->description()); | 
|  | EXPECT_TRUE(content == NULL); | 
|  |  | 
|  | // Test Audio / Video offer. | 
|  | SendAudioVideoStream1(); | 
|  | offer.reset(CreateOffer()); | 
|  | content = cricket::GetFirstAudioContent(offer->description()); | 
|  | EXPECT_TRUE(content != NULL); | 
|  | content = cricket::GetFirstVideoContent(offer->description()); | 
|  | EXPECT_TRUE(content != NULL); | 
|  | } | 
|  |  | 
|  | // 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; | 
|  |  | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> 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; | 
|  |  | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> 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; | 
|  |  | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> 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; | 
|  | 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. | 
|  | rtc::scoped_ptr<JsepSessionDescription> offer(CreateRemoteOffer()); | 
|  | SetRemoteDescriptionWithoutError(offer.release()); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> 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; | 
|  |  | 
|  | rtc::scoped_ptr<JsepSessionDescription> offer( | 
|  | CreateRemoteOffer(options)); | 
|  | ASSERT_TRUE(cricket::GetFirstVideoContent(offer->description()) == NULL); | 
|  | ASSERT_TRUE(cricket::GetFirstAudioContent(offer->description()) != NULL); | 
|  |  | 
|  | SetRemoteDescriptionWithoutError(offer.release()); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> 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. | 
|  | rtc::scoped_ptr<JsepSessionDescription> offer(CreateRemoteOffer()); | 
|  | SetRemoteDescriptionWithoutError(offer.release()); | 
|  | // Test with a stream with tracks. | 
|  | SendAudioVideoStream1(); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> 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. | 
|  | rtc::scoped_ptr<JsepSessionDescription> offer(CreateRemoteOffer()); | 
|  | SetRemoteDescriptionWithoutError(offer.release()); | 
|  |  | 
|  | cricket::MediaSessionOptions session_options; | 
|  | session_options.recv_audio = false; | 
|  | session_options.recv_video = false; | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> 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. | 
|  | rtc::scoped_ptr<JsepSessionDescription> offer(CreateRemoteOffer()); | 
|  | SetRemoteDescriptionWithoutError(offer.release()); | 
|  |  | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_audio = false; | 
|  | options.recv_video = false; | 
|  |  | 
|  | // Test with a stream with tracks. | 
|  | SendAudioVideoStream1(); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> 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; | 
|  |  | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> 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. | 
|  | rtc::scoped_ptr<JsepSessionDescription> offer(CreateRemoteOffer()); | 
|  | SetRemoteDescriptionWithoutError(offer.release()); | 
|  |  | 
|  | cricket::MediaSessionOptions options; | 
|  | options.vad_enabled = false; | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> 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; | 
|  | 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_ == NULL); | 
|  |  | 
|  | 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(); | 
|  | CreateAndSetRemoteOfferAndLocalAnswer(); | 
|  |  | 
|  | video_channel_ = media_engine_->GetVideoChannel(0); | 
|  | voice_channel_ = media_engine_->GetVoiceChannel(0); | 
|  |  | 
|  | ASSERT_TRUE(video_channel_ != NULL); | 
|  | ASSERT_TRUE(voice_channel_ != NULL); | 
|  |  | 
|  | 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. | 
|  | SendAudioOnlyStream2(); | 
|  | CreateAndSetRemoteOfferAndLocalAnswer(); | 
|  |  | 
|  | EXPECT_EQ(0u, video_channel_->recv_streams().size()); | 
|  | 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; | 
|  | options.recv_audio = false; | 
|  | options.recv_video = true; | 
|  | 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(); | 
|  | CreateAndSetRemoteOfferAndLocalAnswer(); | 
|  |  | 
|  | 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. | 
|  | SendVideoOnlyStream2(); | 
|  | CreateAndSetRemoteOfferAndLocalAnswer(); | 
|  |  | 
|  | video_channel_ = media_engine_->GetVideoChannel(0); | 
|  | voice_channel_ = media_engine_->GetVoiceChannel(0); | 
|  |  | 
|  | 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_F(WebRtcSessionTest, VerifyCryptoParamsInSDP) { | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | scoped_ptr<SessionDescriptionInterface> offer(CreateOffer()); | 
|  | VerifyCryptoParams(offer->description()); | 
|  | SetRemoteDescriptionWithoutError(offer.release()); | 
|  | scoped_ptr<SessionDescriptionInterface> answer(CreateAnswer()); | 
|  | VerifyCryptoParams(answer->description()); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, VerifyNoCryptoParamsInSDP) { | 
|  | options_.disable_encryption = true; | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | scoped_ptr<SessionDescriptionInterface> offer(CreateOffer()); | 
|  | VerifyNoCryptoParams(offer->description(), false); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, VerifyAnswerFromNonCryptoOffer) { | 
|  | Init(); | 
|  | VerifyAnswerFromNonCryptoOffer(); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, VerifyAnswerFromCryptoOffer) { | 
|  | Init(); | 
|  | VerifyAnswerFromCryptoOffer(); | 
|  | } | 
|  |  | 
|  | // This test verifies that setLocalDescription fails if | 
|  | // no a=ice-ufrag and a=ice-pwd lines are present in the SDP. | 
|  | TEST_F(WebRtcSessionTest, TestSetLocalDescriptionWithoutIce) { | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> offer(CreateOffer()); | 
|  |  | 
|  | std::string sdp; | 
|  | RemoveIceUfragPwdLines(offer.get(), &sdp); | 
|  | SessionDescriptionInterface* modified_offer = | 
|  | CreateSessionDescription(JsepSessionDescription::kOffer, sdp, NULL); | 
|  | SetLocalDescriptionOfferExpectError(kSdpWithoutIceUfragPwd, modified_offer); | 
|  | } | 
|  |  | 
|  | // This test verifies that setRemoteDescription fails if | 
|  | // no a=ice-ufrag and a=ice-pwd lines are present in the SDP. | 
|  | TEST_F(WebRtcSessionTest, TestSetRemoteDescriptionWithoutIce) { | 
|  | Init(); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> offer(CreateRemoteOffer()); | 
|  | std::string sdp; | 
|  | RemoveIceUfragPwdLines(offer.get(), &sdp); | 
|  | SessionDescriptionInterface* modified_offer = | 
|  | CreateSessionDescription(JsepSessionDescription::kOffer, sdp, NULL); | 
|  | SetRemoteDescriptionOfferExpectError(kSdpWithoutIceUfragPwd, modified_offer); | 
|  | } | 
|  |  | 
|  | // This test verifies that setLocalDescription fails if local offer has | 
|  | // too short ice ufrag and pwd strings. | 
|  | TEST_F(WebRtcSessionTest, TestSetLocalDescriptionInvalidIceCredentials) { | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> offer(CreateOffer()); | 
|  | // Modifying ice ufrag and pwd in local offer with strings smaller than the | 
|  | // recommended values of 4 and 22 bytes respectively. | 
|  | SetIceUfragPwd(offer.get(), "ice", "icepwd"); | 
|  | std::string error; | 
|  | EXPECT_FALSE(session_->SetLocalDescription(offer.release(), &error)); | 
|  |  | 
|  | // Test with string greater than 256. | 
|  | offer.reset(CreateOffer()); | 
|  | SetIceUfragPwd(offer.get(), kTooLongIceUfragPwd, kTooLongIceUfragPwd); | 
|  | EXPECT_FALSE(session_->SetLocalDescription(offer.release(), &error)); | 
|  | } | 
|  |  | 
|  | // This test verifies that setRemoteDescription fails if remote offer has | 
|  | // too short ice ufrag and pwd strings. | 
|  | TEST_F(WebRtcSessionTest, TestSetRemoteDescriptionInvalidIceCredentials) { | 
|  | Init(); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> offer(CreateRemoteOffer()); | 
|  | // Modifying ice ufrag and pwd in remote offer with strings smaller than the | 
|  | // recommended values of 4 and 22 bytes respectively. | 
|  | SetIceUfragPwd(offer.get(), "ice", "icepwd"); | 
|  | std::string error; | 
|  | EXPECT_FALSE(session_->SetRemoteDescription(offer.release(), &error)); | 
|  |  | 
|  | offer.reset(CreateRemoteOffer()); | 
|  | SetIceUfragPwd(offer.get(), kTooLongIceUfragPwd, kTooLongIceUfragPwd); | 
|  | EXPECT_FALSE(session_->SetRemoteDescription(offer.release(), &error)); | 
|  | } | 
|  |  | 
|  | // Test that if the remote offer indicates the peer requested ICE restart (via | 
|  | // a new ufrag or pwd), the old ICE candidates are not copied, and vice versa. | 
|  | TEST_F(WebRtcSessionTest, TestSetRemoteOfferWithIceRestart) { | 
|  | Init(); | 
|  |  | 
|  | // Create the first offer. | 
|  | scoped_ptr<SessionDescriptionInterface> offer(CreateRemoteOffer()); | 
|  | SetIceUfragPwd(offer.get(), "0123456789012345", "abcdefghijklmnopqrstuvwx"); | 
|  | cricket::Candidate candidate1(1, "udp", rtc::SocketAddress("1.1.1.1", 5000), | 
|  | 0, "", "", "relay", 0, ""); | 
|  | JsepIceCandidate ice_candidate1(kMediaContentName0, kMediaContentIndex0, | 
|  | candidate1); | 
|  | EXPECT_TRUE(offer->AddCandidate(&ice_candidate1)); | 
|  | SetRemoteDescriptionWithoutError(offer.release()); | 
|  | EXPECT_EQ(1, session_->remote_description()->candidates(0)->count()); | 
|  |  | 
|  | // The second offer has the same ufrag and pwd but different address. | 
|  | offer.reset(CreateRemoteOffer()); | 
|  | SetIceUfragPwd(offer.get(), "0123456789012345", "abcdefghijklmnopqrstuvwx"); | 
|  | candidate1.set_address(rtc::SocketAddress("1.1.1.1", 6000)); | 
|  | JsepIceCandidate ice_candidate2(kMediaContentName0, kMediaContentIndex0, | 
|  | candidate1); | 
|  | EXPECT_TRUE(offer->AddCandidate(&ice_candidate2)); | 
|  | SetRemoteDescriptionWithoutError(offer.release()); | 
|  | EXPECT_EQ(2, session_->remote_description()->candidates(0)->count()); | 
|  |  | 
|  | // The third offer has a different ufrag and different address. | 
|  | offer.reset(CreateRemoteOffer()); | 
|  | SetIceUfragPwd(offer.get(), "0123456789012333", "abcdefghijklmnopqrstuvwx"); | 
|  | candidate1.set_address(rtc::SocketAddress("1.1.1.1", 7000)); | 
|  | JsepIceCandidate ice_candidate3(kMediaContentName0, kMediaContentIndex0, | 
|  | candidate1); | 
|  | EXPECT_TRUE(offer->AddCandidate(&ice_candidate3)); | 
|  | SetRemoteDescriptionWithoutError(offer.release()); | 
|  | EXPECT_EQ(1, session_->remote_description()->candidates(0)->count()); | 
|  |  | 
|  | // The fourth offer has no candidate but a different ufrag/pwd. | 
|  | offer.reset(CreateRemoteOffer()); | 
|  | SetIceUfragPwd(offer.get(), "0123456789012444", "abcdefghijklmnopqrstuvyz"); | 
|  | SetRemoteDescriptionWithoutError(offer.release()); | 
|  | EXPECT_EQ(0, session_->remote_description()->candidates(0)->count()); | 
|  | } | 
|  |  | 
|  | // Test that if the remote answer indicates the peer requested ICE restart (via | 
|  | // a new ufrag or pwd), the old ICE candidates are not copied, and vice versa. | 
|  | TEST_F(WebRtcSessionTest, TestSetRemoteAnswerWithIceRestart) { | 
|  | Init(); | 
|  | SessionDescriptionInterface* offer = CreateOffer(); | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  |  | 
|  | // Create the first answer. | 
|  | scoped_ptr<JsepSessionDescription> answer(CreateRemoteAnswer(offer)); | 
|  | answer->set_type(JsepSessionDescription::kPrAnswer); | 
|  | SetIceUfragPwd(answer.get(), "0123456789012345", "abcdefghijklmnopqrstuvwx"); | 
|  | cricket::Candidate candidate1(1, "udp", rtc::SocketAddress("1.1.1.1", 5000), | 
|  | 0, "", "", "relay", 0, ""); | 
|  | JsepIceCandidate ice_candidate1(kMediaContentName0, kMediaContentIndex0, | 
|  | candidate1); | 
|  | EXPECT_TRUE(answer->AddCandidate(&ice_candidate1)); | 
|  | SetRemoteDescriptionWithoutError(answer.release()); | 
|  | EXPECT_EQ(1, session_->remote_description()->candidates(0)->count()); | 
|  |  | 
|  | // The second answer has the same ufrag and pwd but different address. | 
|  | answer.reset(CreateRemoteAnswer(offer)); | 
|  | answer->set_type(JsepSessionDescription::kPrAnswer); | 
|  | SetIceUfragPwd(answer.get(), "0123456789012345", "abcdefghijklmnopqrstuvwx"); | 
|  | candidate1.set_address(rtc::SocketAddress("1.1.1.1", 6000)); | 
|  | JsepIceCandidate ice_candidate2(kMediaContentName0, kMediaContentIndex0, | 
|  | candidate1); | 
|  | EXPECT_TRUE(answer->AddCandidate(&ice_candidate2)); | 
|  | SetRemoteDescriptionWithoutError(answer.release()); | 
|  | EXPECT_EQ(2, session_->remote_description()->candidates(0)->count()); | 
|  |  | 
|  | // The third answer has a different ufrag and different address. | 
|  | answer.reset(CreateRemoteAnswer(offer)); | 
|  | answer->set_type(JsepSessionDescription::kPrAnswer); | 
|  | SetIceUfragPwd(answer.get(), "0123456789012333", "abcdefghijklmnopqrstuvwx"); | 
|  | candidate1.set_address(rtc::SocketAddress("1.1.1.1", 7000)); | 
|  | JsepIceCandidate ice_candidate3(kMediaContentName0, kMediaContentIndex0, | 
|  | candidate1); | 
|  | EXPECT_TRUE(answer->AddCandidate(&ice_candidate3)); | 
|  | SetRemoteDescriptionWithoutError(answer.release()); | 
|  | EXPECT_EQ(1, session_->remote_description()->candidates(0)->count()); | 
|  |  | 
|  | // The fourth answer has no candidate but a different ufrag/pwd. | 
|  | answer.reset(CreateRemoteAnswer(offer)); | 
|  | answer->set_type(JsepSessionDescription::kPrAnswer); | 
|  | SetIceUfragPwd(answer.get(), "0123456789012444", "abcdefghijklmnopqrstuvyz"); | 
|  | SetRemoteDescriptionWithoutError(answer.release()); | 
|  | EXPECT_EQ(0, session_->remote_description()->candidates(0)->count()); | 
|  | } | 
|  |  | 
|  | // Test that candidates sent to the "video" transport do not get pushed down to | 
|  | // the "audio" transport channel when bundling. | 
|  | TEST_F(WebRtcSessionTest, TestIgnoreCandidatesForUnusedTransportWhenBundling) { | 
|  | AddInterface(rtc::SocketAddress(kClientAddrHost1, kClientAddrPort)); | 
|  |  | 
|  | InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyBalanced); | 
|  | SendAudioVideoStream1(); | 
|  |  | 
|  | PeerConnectionInterface::RTCOfferAnswerOptions options; | 
|  | options.use_rtp_mux = true; | 
|  |  | 
|  | SessionDescriptionInterface* offer = CreateRemoteOffer(); | 
|  | SetRemoteDescriptionWithoutError(offer); | 
|  |  | 
|  | SessionDescriptionInterface* answer = CreateAnswer(); | 
|  | SetLocalDescriptionWithoutError(answer); | 
|  |  | 
|  | EXPECT_EQ(session_->voice_rtp_transport_channel(), | 
|  | session_->video_rtp_transport_channel()); | 
|  |  | 
|  | cricket::BaseChannel* voice_channel = session_->voice_channel(); | 
|  | ASSERT(voice_channel != NULL); | 
|  |  | 
|  | // Checks if one of the transport channels contains a connection using a given | 
|  | // port. | 
|  | auto connection_with_remote_port = [this, voice_channel](int port) { | 
|  | SessionStats stats; | 
|  | session_->GetChannelTransportStats(voice_channel, &stats); | 
|  | for (auto& kv : stats.transport_stats) { | 
|  | for (auto& chan_stat : kv.second.channel_stats) { | 
|  | for (auto& conn_info : chan_stat.connection_infos) { | 
|  | if (conn_info.remote_candidate.address().port() == port) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | return false; | 
|  | }; | 
|  |  | 
|  | EXPECT_FALSE(connection_with_remote_port(5000)); | 
|  | EXPECT_FALSE(connection_with_remote_port(5001)); | 
|  | EXPECT_FALSE(connection_with_remote_port(6000)); | 
|  |  | 
|  | // The way the *_WAIT checks work is they only wait if the condition fails, | 
|  | // which does not help in the case where state is not changing. This is | 
|  | // problematic in this test since we want to verify that adding a video | 
|  | // candidate does _not_ change state. So we interleave candidates and assume | 
|  | // that messages are executed in the order they were posted. | 
|  |  | 
|  | // First audio candidate. | 
|  | cricket::Candidate candidate0; | 
|  | candidate0.set_address(rtc::SocketAddress("1.1.1.1", 5000)); | 
|  | candidate0.set_component(1); | 
|  | candidate0.set_protocol("udp"); | 
|  | JsepIceCandidate ice_candidate0(kMediaContentName0, kMediaContentIndex0, | 
|  | candidate0); | 
|  | EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate0)); | 
|  |  | 
|  | // Video candidate. | 
|  | cricket::Candidate candidate1; | 
|  | candidate1.set_address(rtc::SocketAddress("1.1.1.1", 6000)); | 
|  | candidate1.set_component(1); | 
|  | candidate1.set_protocol("udp"); | 
|  | JsepIceCandidate ice_candidate1(kMediaContentName1, kMediaContentIndex1, | 
|  | candidate1); | 
|  | EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate1)); | 
|  |  | 
|  | // Second audio candidate. | 
|  | cricket::Candidate candidate2; | 
|  | candidate2.set_address(rtc::SocketAddress("1.1.1.1", 5001)); | 
|  | candidate2.set_component(1); | 
|  | candidate2.set_protocol("udp"); | 
|  | JsepIceCandidate ice_candidate2(kMediaContentName0, kMediaContentIndex0, | 
|  | candidate2); | 
|  | EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate2)); | 
|  |  | 
|  | EXPECT_TRUE_WAIT(connection_with_remote_port(5000), 1000); | 
|  | EXPECT_TRUE_WAIT(connection_with_remote_port(5001), 1000); | 
|  |  | 
|  | // No need here for a _WAIT check since we are checking that state hasn't | 
|  | // changed: if this is false we would be doing waits for nothing and if this | 
|  | // is true then there will be no messages processed anyways. | 
|  | EXPECT_FALSE(connection_with_remote_port(6000)); | 
|  | } | 
|  |  | 
|  | // kBundlePolicyBalanced BUNDLE policy and answer contains BUNDLE. | 
|  | TEST_F(WebRtcSessionTest, TestBalancedBundleInAnswer) { | 
|  | InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyBalanced); | 
|  | SendAudioVideoStream1(); | 
|  |  | 
|  | PeerConnectionInterface::RTCOfferAnswerOptions options; | 
|  | options.use_rtp_mux = true; | 
|  |  | 
|  | SessionDescriptionInterface* offer = CreateOffer(options); | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  |  | 
|  | EXPECT_NE(session_->voice_rtp_transport_channel(), | 
|  | session_->video_rtp_transport_channel()); | 
|  |  | 
|  | SendAudioVideoStream2(); | 
|  | SessionDescriptionInterface* answer = | 
|  | CreateRemoteAnswer(session_->local_description()); | 
|  | SetRemoteDescriptionWithoutError(answer); | 
|  |  | 
|  | EXPECT_EQ(session_->voice_rtp_transport_channel(), | 
|  | session_->video_rtp_transport_channel()); | 
|  | } | 
|  |  | 
|  | // kBundlePolicyBalanced BUNDLE policy but no BUNDLE in the answer. | 
|  | TEST_F(WebRtcSessionTest, TestBalancedNoBundleInAnswer) { | 
|  | InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyBalanced); | 
|  | SendAudioVideoStream1(); | 
|  |  | 
|  | PeerConnectionInterface::RTCOfferAnswerOptions options; | 
|  | options.use_rtp_mux = true; | 
|  |  | 
|  | SessionDescriptionInterface* offer = CreateOffer(options); | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  |  | 
|  | EXPECT_NE(session_->voice_rtp_transport_channel(), | 
|  | session_->video_rtp_transport_channel()); | 
|  |  | 
|  | SendAudioVideoStream2(); | 
|  |  | 
|  | // Remove BUNDLE from the answer. | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> answer( | 
|  | CreateRemoteAnswer(session_->local_description())); | 
|  | cricket::SessionDescription* answer_copy = answer->description()->Copy(); | 
|  | answer_copy->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE); | 
|  | JsepSessionDescription* modified_answer = | 
|  | new JsepSessionDescription(JsepSessionDescription::kAnswer); | 
|  | modified_answer->Initialize(answer_copy, "1", "1"); | 
|  | SetRemoteDescriptionWithoutError(modified_answer);  // | 
|  |  | 
|  | EXPECT_NE(session_->voice_rtp_transport_channel(), | 
|  | session_->video_rtp_transport_channel()); | 
|  | } | 
|  |  | 
|  | // kBundlePolicyMaxBundle policy with BUNDLE in the answer. | 
|  | TEST_F(WebRtcSessionTest, TestMaxBundleBundleInAnswer) { | 
|  | InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyMaxBundle); | 
|  | SendAudioVideoStream1(); | 
|  |  | 
|  | PeerConnectionInterface::RTCOfferAnswerOptions options; | 
|  | options.use_rtp_mux = true; | 
|  |  | 
|  | SessionDescriptionInterface* offer = CreateOffer(options); | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  |  | 
|  | EXPECT_EQ(session_->voice_rtp_transport_channel(), | 
|  | session_->video_rtp_transport_channel()); | 
|  |  | 
|  | SendAudioVideoStream2(); | 
|  | SessionDescriptionInterface* answer = | 
|  | CreateRemoteAnswer(session_->local_description()); | 
|  | SetRemoteDescriptionWithoutError(answer); | 
|  |  | 
|  | EXPECT_EQ(session_->voice_rtp_transport_channel(), | 
|  | session_->video_rtp_transport_channel()); | 
|  | } | 
|  |  | 
|  | // kBundlePolicyMaxBundle policy with BUNDLE in the answer, but no | 
|  | // audio content in the answer. | 
|  | TEST_F(WebRtcSessionTest, TestMaxBundleRejectAudio) { | 
|  | InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyMaxBundle); | 
|  | SendAudioVideoStream1(); | 
|  |  | 
|  | PeerConnectionInterface::RTCOfferAnswerOptions options; | 
|  | options.use_rtp_mux = true; | 
|  |  | 
|  | SessionDescriptionInterface* offer = CreateOffer(options); | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  |  | 
|  | EXPECT_EQ(session_->voice_rtp_transport_channel(), | 
|  | session_->video_rtp_transport_channel()); | 
|  |  | 
|  | SendAudioVideoStream2(); | 
|  | cricket::MediaSessionOptions recv_options; | 
|  | recv_options.recv_audio = false; | 
|  | recv_options.recv_video = true; | 
|  | SessionDescriptionInterface* answer = | 
|  | CreateRemoteAnswer(session_->local_description(), recv_options); | 
|  | SetRemoteDescriptionWithoutError(answer); | 
|  |  | 
|  | EXPECT_TRUE(nullptr == session_->voice_channel()); | 
|  | EXPECT_TRUE(nullptr != session_->video_rtp_transport_channel()); | 
|  |  | 
|  | session_->Close(); | 
|  | EXPECT_TRUE(nullptr == session_->voice_rtp_transport_channel()); | 
|  | EXPECT_TRUE(nullptr == session_->voice_rtcp_transport_channel()); | 
|  | EXPECT_TRUE(nullptr == session_->video_rtp_transport_channel()); | 
|  | EXPECT_TRUE(nullptr == session_->video_rtcp_transport_channel()); | 
|  | } | 
|  |  | 
|  | // kBundlePolicyMaxBundle policy but no BUNDLE in the answer. | 
|  | TEST_F(WebRtcSessionTest, TestMaxBundleNoBundleInAnswer) { | 
|  | InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyMaxBundle); | 
|  | SendAudioVideoStream1(); | 
|  |  | 
|  | PeerConnectionInterface::RTCOfferAnswerOptions options; | 
|  | options.use_rtp_mux = true; | 
|  |  | 
|  | SessionDescriptionInterface* offer = CreateOffer(options); | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  |  | 
|  | EXPECT_EQ(session_->voice_rtp_transport_channel(), | 
|  | session_->video_rtp_transport_channel()); | 
|  |  | 
|  | SendAudioVideoStream2(); | 
|  |  | 
|  | // Remove BUNDLE from the answer. | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> answer( | 
|  | CreateRemoteAnswer(session_->local_description())); | 
|  | cricket::SessionDescription* answer_copy = answer->description()->Copy(); | 
|  | answer_copy->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE); | 
|  | JsepSessionDescription* modified_answer = | 
|  | new JsepSessionDescription(JsepSessionDescription::kAnswer); | 
|  | modified_answer->Initialize(answer_copy, "1", "1"); | 
|  | SetRemoteDescriptionWithoutError(modified_answer); | 
|  |  | 
|  | EXPECT_EQ(session_->voice_rtp_transport_channel(), | 
|  | session_->video_rtp_transport_channel()); | 
|  | } | 
|  |  | 
|  | // kBundlePolicyMaxBundle policy with BUNDLE in the remote offer. | 
|  | TEST_F(WebRtcSessionTest, TestMaxBundleBundleInRemoteOffer) { | 
|  | InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyMaxBundle); | 
|  | SendAudioVideoStream1(); | 
|  |  | 
|  | SessionDescriptionInterface* offer = CreateRemoteOffer(); | 
|  | SetRemoteDescriptionWithoutError(offer); | 
|  |  | 
|  | EXPECT_EQ(session_->voice_rtp_transport_channel(), | 
|  | session_->video_rtp_transport_channel()); | 
|  |  | 
|  | SendAudioVideoStream2(); | 
|  | SessionDescriptionInterface* answer = CreateAnswer(); | 
|  | SetLocalDescriptionWithoutError(answer); | 
|  |  | 
|  | EXPECT_EQ(session_->voice_rtp_transport_channel(), | 
|  | session_->video_rtp_transport_channel()); | 
|  | } | 
|  |  | 
|  | // kBundlePolicyMaxBundle policy but no BUNDLE in the remote offer. | 
|  | TEST_F(WebRtcSessionTest, TestMaxBundleNoBundleInRemoteOffer) { | 
|  | InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyMaxBundle); | 
|  | SendAudioVideoStream1(); | 
|  |  | 
|  | // Remove BUNDLE from the offer. | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> offer(CreateRemoteOffer()); | 
|  | cricket::SessionDescription* offer_copy = offer->description()->Copy(); | 
|  | offer_copy->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE); | 
|  | JsepSessionDescription* modified_offer = | 
|  | new JsepSessionDescription(JsepSessionDescription::kOffer); | 
|  | modified_offer->Initialize(offer_copy, "1", "1"); | 
|  |  | 
|  | // Expect an error when applying the remote description | 
|  | SetRemoteDescriptionExpectError(JsepSessionDescription::kOffer, | 
|  | kCreateChannelFailed, modified_offer); | 
|  | } | 
|  |  | 
|  | // kBundlePolicyMaxCompat bundle policy and answer contains BUNDLE. | 
|  | TEST_F(WebRtcSessionTest, TestMaxCompatBundleInAnswer) { | 
|  | InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyMaxCompat); | 
|  | SendAudioVideoStream1(); | 
|  |  | 
|  | PeerConnectionInterface::RTCOfferAnswerOptions options; | 
|  | options.use_rtp_mux = true; | 
|  |  | 
|  | SessionDescriptionInterface* offer = CreateOffer(options); | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  |  | 
|  | EXPECT_NE(session_->voice_rtp_transport_channel(), | 
|  | session_->video_rtp_transport_channel()); | 
|  |  | 
|  | SendAudioVideoStream2(); | 
|  | SessionDescriptionInterface* answer = | 
|  | CreateRemoteAnswer(session_->local_description()); | 
|  | SetRemoteDescriptionWithoutError(answer); | 
|  |  | 
|  | // This should lead to an audio-only call but isn't implemented | 
|  | // correctly yet. | 
|  | EXPECT_EQ(session_->voice_rtp_transport_channel(), | 
|  | session_->video_rtp_transport_channel()); | 
|  | } | 
|  |  | 
|  | // kBundlePolicyMaxCompat BUNDLE policy but no BUNDLE in the answer. | 
|  | TEST_F(WebRtcSessionTest, TestMaxCompatNoBundleInAnswer) { | 
|  | InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyMaxCompat); | 
|  | SendAudioVideoStream1(); | 
|  | PeerConnectionInterface::RTCOfferAnswerOptions options; | 
|  | options.use_rtp_mux = true; | 
|  |  | 
|  | SessionDescriptionInterface* offer = CreateOffer(options); | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  |  | 
|  | EXPECT_NE(session_->voice_rtp_transport_channel(), | 
|  | session_->video_rtp_transport_channel()); | 
|  |  | 
|  | SendAudioVideoStream2(); | 
|  |  | 
|  | // Remove BUNDLE from the answer. | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> answer( | 
|  | CreateRemoteAnswer(session_->local_description())); | 
|  | cricket::SessionDescription* answer_copy = answer->description()->Copy(); | 
|  | answer_copy->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE); | 
|  | JsepSessionDescription* modified_answer = | 
|  | new JsepSessionDescription(JsepSessionDescription::kAnswer); | 
|  | modified_answer->Initialize(answer_copy, "1", "1"); | 
|  | SetRemoteDescriptionWithoutError(modified_answer);  // | 
|  |  | 
|  | EXPECT_NE(session_->voice_rtp_transport_channel(), | 
|  | session_->video_rtp_transport_channel()); | 
|  | } | 
|  |  | 
|  | // kBundlePolicyMaxbundle and then we call SetRemoteDescription first. | 
|  | TEST_F(WebRtcSessionTest, TestMaxBundleWithSetRemoteDescriptionFirst) { | 
|  | InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyMaxBundle); | 
|  | SendAudioVideoStream1(); | 
|  |  | 
|  | PeerConnectionInterface::RTCOfferAnswerOptions options; | 
|  | options.use_rtp_mux = true; | 
|  |  | 
|  | SessionDescriptionInterface* offer = CreateOffer(options); | 
|  | SetRemoteDescriptionWithoutError(offer); | 
|  |  | 
|  | EXPECT_EQ(session_->voice_rtp_transport_channel(), | 
|  | session_->video_rtp_transport_channel()); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, TestRequireRtcpMux) { | 
|  | InitWithRtcpMuxPolicy(PeerConnectionInterface::kRtcpMuxPolicyRequire); | 
|  | SendAudioVideoStream1(); | 
|  |  | 
|  | PeerConnectionInterface::RTCOfferAnswerOptions options; | 
|  | SessionDescriptionInterface* offer = CreateOffer(options); | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  |  | 
|  | EXPECT_TRUE(session_->voice_rtcp_transport_channel() == NULL); | 
|  | EXPECT_TRUE(session_->video_rtcp_transport_channel() == NULL); | 
|  |  | 
|  | SendAudioVideoStream2(); | 
|  | SessionDescriptionInterface* answer = | 
|  | CreateRemoteAnswer(session_->local_description()); | 
|  | SetRemoteDescriptionWithoutError(answer); | 
|  |  | 
|  | EXPECT_TRUE(session_->voice_rtcp_transport_channel() == NULL); | 
|  | EXPECT_TRUE(session_->video_rtcp_transport_channel() == NULL); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, TestNegotiateRtcpMux) { | 
|  | InitWithRtcpMuxPolicy(PeerConnectionInterface::kRtcpMuxPolicyNegotiate); | 
|  | SendAudioVideoStream1(); | 
|  |  | 
|  | PeerConnectionInterface::RTCOfferAnswerOptions options; | 
|  | SessionDescriptionInterface* offer = CreateOffer(options); | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  |  | 
|  | EXPECT_TRUE(session_->voice_rtcp_transport_channel() != NULL); | 
|  | EXPECT_TRUE(session_->video_rtcp_transport_channel() != NULL); | 
|  |  | 
|  | SendAudioVideoStream2(); | 
|  | SessionDescriptionInterface* answer = | 
|  | CreateRemoteAnswer(session_->local_description()); | 
|  | SetRemoteDescriptionWithoutError(answer); | 
|  |  | 
|  | EXPECT_TRUE(session_->voice_rtcp_transport_channel() == NULL); | 
|  | EXPECT_TRUE(session_->video_rtcp_transport_channel() == NULL); | 
|  | } | 
|  |  | 
|  | // This test verifies that SetLocalDescription and SetRemoteDescription fails | 
|  | // if BUNDLE is enabled but rtcp-mux is disabled in m-lines. | 
|  | TEST_F(WebRtcSessionTest, TestDisabledRtcpMuxWithBundleEnabled) { | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  |  | 
|  | PeerConnectionInterface::RTCOfferAnswerOptions options; | 
|  | options.use_rtp_mux = true; | 
|  |  | 
|  | SessionDescriptionInterface* offer = CreateOffer(options); | 
|  | std::string offer_str; | 
|  | offer->ToString(&offer_str); | 
|  | // Disable rtcp-mux | 
|  | const std::string rtcp_mux = "rtcp-mux"; | 
|  | const std::string xrtcp_mux = "xrtcp-mux"; | 
|  | rtc::replace_substrs(rtcp_mux.c_str(), rtcp_mux.length(), | 
|  | xrtcp_mux.c_str(), xrtcp_mux.length(), | 
|  | &offer_str); | 
|  | JsepSessionDescription* local_offer = | 
|  | new JsepSessionDescription(JsepSessionDescription::kOffer); | 
|  | EXPECT_TRUE((local_offer)->Initialize(offer_str, NULL)); | 
|  | SetLocalDescriptionOfferExpectError(kBundleWithoutRtcpMux, local_offer); | 
|  | JsepSessionDescription* remote_offer = | 
|  | new JsepSessionDescription(JsepSessionDescription::kOffer); | 
|  | EXPECT_TRUE((remote_offer)->Initialize(offer_str, NULL)); | 
|  | SetRemoteDescriptionOfferExpectError(kBundleWithoutRtcpMux, remote_offer); | 
|  | // Trying unmodified SDP. | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, SetAudioPlayout) { | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | CreateAndSetRemoteOfferAndLocalAnswer(); | 
|  | cricket::FakeVoiceMediaChannel* channel = media_engine_->GetVoiceChannel(0); | 
|  | ASSERT_TRUE(channel != NULL); | 
|  | ASSERT_EQ(1u, channel->recv_streams().size()); | 
|  | uint32_t receive_ssrc = channel->recv_streams()[0].first_ssrc(); | 
|  | double volume; | 
|  | EXPECT_TRUE(channel->GetOutputVolume(receive_ssrc, &volume)); | 
|  | EXPECT_EQ(1, volume); | 
|  | session_->SetAudioPlayout(receive_ssrc, false); | 
|  | EXPECT_TRUE(channel->GetOutputVolume(receive_ssrc, &volume)); | 
|  | EXPECT_EQ(0, volume); | 
|  | session_->SetAudioPlayout(receive_ssrc, true); | 
|  | EXPECT_TRUE(channel->GetOutputVolume(receive_ssrc, &volume)); | 
|  | EXPECT_EQ(1, volume); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, SetAudioMaxSendBitrate) { | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | CreateAndSetRemoteOfferAndLocalAnswer(); | 
|  | cricket::FakeVoiceMediaChannel* channel = media_engine_->GetVoiceChannel(0); | 
|  | ASSERT_TRUE(channel != NULL); | 
|  | uint32_t send_ssrc = channel->send_streams()[0].first_ssrc(); | 
|  | EXPECT_EQ(-1, channel->max_bps()); | 
|  | webrtc::RtpParameters params = session_->GetAudioRtpParameters(send_ssrc); | 
|  | EXPECT_EQ(1, params.encodings.size()); | 
|  | EXPECT_EQ(-1, params.encodings[0].max_bitrate_bps); | 
|  | params.encodings[0].max_bitrate_bps = 1000; | 
|  | EXPECT_TRUE(session_->SetAudioRtpParameters(send_ssrc, params)); | 
|  |  | 
|  | // Read back the parameters and verify they have been changed. | 
|  | params = session_->GetAudioRtpParameters(send_ssrc); | 
|  | EXPECT_EQ(1, params.encodings.size()); | 
|  | EXPECT_EQ(1000, params.encodings[0].max_bitrate_bps); | 
|  |  | 
|  | // Verify that the audio channel received the new parameters. | 
|  | params = channel->GetRtpParameters(send_ssrc); | 
|  | EXPECT_EQ(1, params.encodings.size()); | 
|  | EXPECT_EQ(1000, params.encodings[0].max_bitrate_bps); | 
|  |  | 
|  | // Verify that the global bitrate limit has not been changed. | 
|  | EXPECT_EQ(-1, channel->max_bps()); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, SetAudioSend) { | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | CreateAndSetRemoteOfferAndLocalAnswer(); | 
|  | cricket::FakeVoiceMediaChannel* channel = media_engine_->GetVoiceChannel(0); | 
|  | ASSERT_TRUE(channel != NULL); | 
|  | ASSERT_EQ(1u, channel->send_streams().size()); | 
|  | uint32_t send_ssrc = channel->send_streams()[0].first_ssrc(); | 
|  | EXPECT_FALSE(channel->IsStreamMuted(send_ssrc)); | 
|  |  | 
|  | cricket::AudioOptions options; | 
|  | options.echo_cancellation = rtc::Optional<bool>(true); | 
|  |  | 
|  | rtc::scoped_ptr<FakeAudioSource> source(new FakeAudioSource()); | 
|  | session_->SetAudioSend(send_ssrc, false, options, source.get()); | 
|  | EXPECT_TRUE(channel->IsStreamMuted(send_ssrc)); | 
|  | EXPECT_EQ(rtc::Optional<bool>(), channel->options().echo_cancellation); | 
|  | EXPECT_TRUE(source->sink() != nullptr); | 
|  |  | 
|  | // This will trigger SetSink(nullptr) to the |source|. | 
|  | session_->SetAudioSend(send_ssrc, true, options, nullptr); | 
|  | EXPECT_FALSE(channel->IsStreamMuted(send_ssrc)); | 
|  | EXPECT_EQ(rtc::Optional<bool>(true), channel->options().echo_cancellation); | 
|  | EXPECT_TRUE(source->sink() == nullptr); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, AudioSourceForLocalStream) { | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | CreateAndSetRemoteOfferAndLocalAnswer(); | 
|  | cricket::FakeVoiceMediaChannel* channel = media_engine_->GetVoiceChannel(0); | 
|  | ASSERT_TRUE(channel != NULL); | 
|  | ASSERT_EQ(1u, channel->send_streams().size()); | 
|  | uint32_t send_ssrc = channel->send_streams()[0].first_ssrc(); | 
|  |  | 
|  | rtc::scoped_ptr<FakeAudioSource> source(new FakeAudioSource()); | 
|  | cricket::AudioOptions options; | 
|  | session_->SetAudioSend(send_ssrc, true, options, source.get()); | 
|  | EXPECT_TRUE(source->sink() != nullptr); | 
|  |  | 
|  | // Delete the |source| and it will trigger OnClose() to the sink, and this | 
|  | // will invalidate the |source_| pointer in the sink and prevent getting a | 
|  | // SetSink(nullptr) callback afterwards. | 
|  | source.reset(); | 
|  |  | 
|  | // This will trigger SetSink(nullptr) if no OnClose() callback. | 
|  | session_->SetAudioSend(send_ssrc, true, options, nullptr); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, SetVideoPlayout) { | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | CreateAndSetRemoteOfferAndLocalAnswer(); | 
|  | cricket::FakeVideoMediaChannel* channel = media_engine_->GetVideoChannel(0); | 
|  | ASSERT_TRUE(channel != NULL); | 
|  | ASSERT_LT(0u, channel->sinks().size()); | 
|  | EXPECT_TRUE(channel->sinks().begin()->second == NULL); | 
|  | ASSERT_EQ(1u, channel->recv_streams().size()); | 
|  | uint32_t receive_ssrc = channel->recv_streams()[0].first_ssrc(); | 
|  | cricket::FakeVideoRenderer renderer; | 
|  | session_->SetVideoPlayout(receive_ssrc, true, &renderer); | 
|  | EXPECT_TRUE(channel->sinks().begin()->second == &renderer); | 
|  | session_->SetVideoPlayout(receive_ssrc, false, &renderer); | 
|  | EXPECT_TRUE(channel->sinks().begin()->second == NULL); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, SetVideoMaxSendBitrate) { | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | CreateAndSetRemoteOfferAndLocalAnswer(); | 
|  | cricket::FakeVideoMediaChannel* channel = media_engine_->GetVideoChannel(0); | 
|  | ASSERT_TRUE(channel != NULL); | 
|  | uint32_t send_ssrc = channel->send_streams()[0].first_ssrc(); | 
|  | EXPECT_EQ(-1, channel->max_bps()); | 
|  | webrtc::RtpParameters params = session_->GetVideoRtpParameters(send_ssrc); | 
|  | EXPECT_EQ(1, params.encodings.size()); | 
|  | EXPECT_EQ(-1, params.encodings[0].max_bitrate_bps); | 
|  | params.encodings[0].max_bitrate_bps = 1000; | 
|  | EXPECT_TRUE(session_->SetVideoRtpParameters(send_ssrc, params)); | 
|  |  | 
|  | // Read back the parameters and verify they have been changed. | 
|  | params = session_->GetVideoRtpParameters(send_ssrc); | 
|  | EXPECT_EQ(1, params.encodings.size()); | 
|  | EXPECT_EQ(1000, params.encodings[0].max_bitrate_bps); | 
|  |  | 
|  | // Verify that the video channel received the new parameters. | 
|  | params = channel->GetRtpParameters(send_ssrc); | 
|  | EXPECT_EQ(1, params.encodings.size()); | 
|  | EXPECT_EQ(1000, params.encodings[0].max_bitrate_bps); | 
|  |  | 
|  | // Verify that the global bitrate limit has not been changed. | 
|  | EXPECT_EQ(-1, channel->max_bps()); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, SetVideoSend) { | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | CreateAndSetRemoteOfferAndLocalAnswer(); | 
|  | cricket::FakeVideoMediaChannel* channel = media_engine_->GetVideoChannel(0); | 
|  | ASSERT_TRUE(channel != NULL); | 
|  | ASSERT_EQ(1u, channel->send_streams().size()); | 
|  | uint32_t send_ssrc = channel->send_streams()[0].first_ssrc(); | 
|  | EXPECT_FALSE(channel->IsStreamMuted(send_ssrc)); | 
|  | cricket::VideoOptions* options = NULL; | 
|  | session_->SetVideoSend(send_ssrc, false, options); | 
|  | EXPECT_TRUE(channel->IsStreamMuted(send_ssrc)); | 
|  | session_->SetVideoSend(send_ssrc, true, options); | 
|  | EXPECT_FALSE(channel->IsStreamMuted(send_ssrc)); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, CanNotInsertDtmf) { | 
|  | TestCanInsertDtmf(false); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, CanInsertDtmf) { | 
|  | TestCanInsertDtmf(true); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, InsertDtmf) { | 
|  | // Setup | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | CreateAndSetRemoteOfferAndLocalAnswer(); | 
|  | FakeVoiceMediaChannel* channel = media_engine_->GetVoiceChannel(0); | 
|  | EXPECT_EQ(0U, channel->dtmf_info_queue().size()); | 
|  |  | 
|  | // Insert DTMF | 
|  | const int expected_duration = 90; | 
|  | session_->InsertDtmf(kAudioTrack1, 0, expected_duration); | 
|  | session_->InsertDtmf(kAudioTrack1, 1, expected_duration); | 
|  | session_->InsertDtmf(kAudioTrack1, 2, expected_duration); | 
|  |  | 
|  | // Verify | 
|  | ASSERT_EQ(3U, channel->dtmf_info_queue().size()); | 
|  | const uint32_t send_ssrc = channel->send_streams()[0].first_ssrc(); | 
|  | EXPECT_TRUE(CompareDtmfInfo(channel->dtmf_info_queue()[0], send_ssrc, 0, | 
|  | expected_duration)); | 
|  | EXPECT_TRUE(CompareDtmfInfo(channel->dtmf_info_queue()[1], send_ssrc, 1, | 
|  | expected_duration)); | 
|  | EXPECT_TRUE(CompareDtmfInfo(channel->dtmf_info_queue()[2], send_ssrc, 2, | 
|  | expected_duration)); | 
|  | } | 
|  |  | 
|  | // 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); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> 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(kMlineMismatch, 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(kMlineMismatch, 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(kMlineMismatch, 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(kMlineMismatch, modified_answer); | 
|  | SetLocalDescriptionWithoutError(answer); | 
|  | } | 
|  |  | 
|  | // This test verifies that WebRtcSession does not start candidate allocation | 
|  | // before SetLocalDescription is called. | 
|  | TEST_F(WebRtcSessionTest, TestIceStartAfterSetLocalDescriptionOnly) { | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | SessionDescriptionInterface* offer = CreateRemoteOffer(); | 
|  | cricket::Candidate candidate; | 
|  | candidate.set_component(1); | 
|  | JsepIceCandidate ice_candidate(kMediaContentName0, kMediaContentIndex0, | 
|  | candidate); | 
|  | EXPECT_TRUE(offer->AddCandidate(&ice_candidate)); | 
|  | cricket::Candidate candidate1; | 
|  | candidate1.set_component(1); | 
|  | JsepIceCandidate ice_candidate1(kMediaContentName1, kMediaContentIndex1, | 
|  | candidate1); | 
|  | EXPECT_TRUE(offer->AddCandidate(&ice_candidate1)); | 
|  | SetRemoteDescriptionWithoutError(offer); | 
|  | ASSERT_TRUE(session_->voice_rtp_transport_channel() != NULL); | 
|  | ASSERT_TRUE(session_->video_rtp_transport_channel() != NULL); | 
|  |  | 
|  | // Pump for 1 second and verify that no candidates are generated. | 
|  | rtc::Thread::Current()->ProcessMessages(1000); | 
|  | EXPECT_TRUE(observer_.mline_0_candidates_.empty()); | 
|  | EXPECT_TRUE(observer_.mline_1_candidates_.empty()); | 
|  |  | 
|  | SessionDescriptionInterface* answer = CreateAnswer(); | 
|  | SetLocalDescriptionWithoutError(answer); | 
|  | EXPECT_TRUE_WAIT(observer_.oncandidatesready_, kIceCandidatesTimeout); | 
|  | } | 
|  |  | 
|  | // This test verifies that crypto parameter is updated in local session | 
|  | // description as per security policy set in MediaSessionDescriptionFactory. | 
|  | TEST_F(WebRtcSessionTest, TestCryptoAfterSetLocalDescription) { | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> offer(CreateOffer()); | 
|  |  | 
|  | // Making sure SetLocalDescription correctly sets crypto value in | 
|  | // SessionDescription object after de-serialization of sdp string. The value | 
|  | // will be set as per MediaSessionDescriptionFactory. | 
|  | std::string offer_str; | 
|  | offer->ToString(&offer_str); | 
|  | SessionDescriptionInterface* jsep_offer_str = | 
|  | CreateSessionDescription(JsepSessionDescription::kOffer, offer_str, NULL); | 
|  | SetLocalDescriptionWithoutError(jsep_offer_str); | 
|  | EXPECT_TRUE(session_->voice_channel()->secure_required()); | 
|  | EXPECT_TRUE(session_->video_channel()->secure_required()); | 
|  | } | 
|  |  | 
|  | // This test verifies the crypto parameter when security is disabled. | 
|  | TEST_F(WebRtcSessionTest, TestCryptoAfterSetLocalDescriptionWithDisabled) { | 
|  | options_.disable_encryption = true; | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> offer(CreateOffer()); | 
|  |  | 
|  | // Making sure SetLocalDescription correctly sets crypto value in | 
|  | // SessionDescription object after de-serialization of sdp string. The value | 
|  | // will be set as per MediaSessionDescriptionFactory. | 
|  | std::string offer_str; | 
|  | offer->ToString(&offer_str); | 
|  | SessionDescriptionInterface* jsep_offer_str = | 
|  | CreateSessionDescription(JsepSessionDescription::kOffer, offer_str, NULL); | 
|  | SetLocalDescriptionWithoutError(jsep_offer_str); | 
|  | EXPECT_FALSE(session_->voice_channel()->secure_required()); | 
|  | EXPECT_FALSE(session_->video_channel()->secure_required()); | 
|  | } | 
|  |  | 
|  | // This test verifies that an answer contains new ufrag and password if an offer | 
|  | // with new ufrag and password is received. | 
|  | TEST_F(WebRtcSessionTest, TestCreateAnswerWithNewUfragAndPassword) { | 
|  | Init(); | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_video = true; | 
|  | rtc::scoped_ptr<JsepSessionDescription> offer( | 
|  | CreateRemoteOffer(options)); | 
|  | SetRemoteDescriptionWithoutError(offer.release()); | 
|  |  | 
|  | SendAudioVideoStream1(); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> answer(CreateAnswer()); | 
|  | SetLocalDescriptionWithoutError(answer.release()); | 
|  |  | 
|  | // Receive an offer with new ufrag and password. | 
|  | for (const cricket::ContentInfo& content : | 
|  | session_->local_description()->description()->contents()) { | 
|  | options.transport_options[content.name].ice_restart = true; | 
|  | } | 
|  | rtc::scoped_ptr<JsepSessionDescription> updated_offer1( | 
|  | CreateRemoteOffer(options, session_->remote_description())); | 
|  | SetRemoteDescriptionWithoutError(updated_offer1.release()); | 
|  |  | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> updated_answer1(CreateAnswer()); | 
|  |  | 
|  | EXPECT_FALSE(IceUfragPwdEqual(updated_answer1->description(), | 
|  | session_->local_description()->description())); | 
|  |  | 
|  | // Even a second answer (created before the description is set) should have | 
|  | // a new ufrag/password. | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> updated_answer2(CreateAnswer()); | 
|  |  | 
|  | EXPECT_FALSE(IceUfragPwdEqual(updated_answer2->description(), | 
|  | session_->local_description()->description())); | 
|  |  | 
|  | SetLocalDescriptionWithoutError(updated_answer2.release()); | 
|  | } | 
|  |  | 
|  | // This test verifies that an answer contains new ufrag and password if an offer | 
|  | // that changes either the ufrag or password (but not both) is received. | 
|  | // RFC 5245 says: "If the offer contained a change in the a=ice-ufrag or | 
|  | // a=ice-pwd attributes compared to the previous SDP from the peer, it | 
|  | // indicates that ICE is restarting for this media stream." | 
|  | TEST_F(WebRtcSessionTest, TestOfferChangingOnlyUfragOrPassword) { | 
|  | Init(); | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_audio = true; | 
|  | options.recv_video = true; | 
|  | // Create an offer with audio and video. | 
|  | rtc::scoped_ptr<JsepSessionDescription> offer(CreateRemoteOffer(options)); | 
|  | SetIceUfragPwd(offer.get(), "original_ufrag", "original_password12345"); | 
|  | SetRemoteDescriptionWithoutError(offer.release()); | 
|  |  | 
|  | SendAudioVideoStream1(); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> answer(CreateAnswer()); | 
|  | SetLocalDescriptionWithoutError(answer.release()); | 
|  |  | 
|  | // Receive an offer with a new ufrag but stale password. | 
|  | rtc::scoped_ptr<JsepSessionDescription> ufrag_changed_offer( | 
|  | CreateRemoteOffer(options, session_->remote_description())); | 
|  | SetIceUfragPwd(ufrag_changed_offer.get(), "modified_ufrag", | 
|  | "original_password12345"); | 
|  | SetRemoteDescriptionWithoutError(ufrag_changed_offer.release()); | 
|  |  | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> updated_answer1(CreateAnswer()); | 
|  | EXPECT_FALSE(IceUfragPwdEqual(updated_answer1->description(), | 
|  | session_->local_description()->description())); | 
|  | SetLocalDescriptionWithoutError(updated_answer1.release()); | 
|  |  | 
|  | // Receive an offer with a new password but stale ufrag. | 
|  | rtc::scoped_ptr<JsepSessionDescription> password_changed_offer( | 
|  | CreateRemoteOffer(options, session_->remote_description())); | 
|  | SetIceUfragPwd(password_changed_offer.get(), "modified_ufrag", | 
|  | "modified_password12345"); | 
|  | SetRemoteDescriptionWithoutError(password_changed_offer.release()); | 
|  |  | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> updated_answer2(CreateAnswer()); | 
|  | EXPECT_FALSE(IceUfragPwdEqual(updated_answer2->description(), | 
|  | session_->local_description()->description())); | 
|  | SetLocalDescriptionWithoutError(updated_answer2.release()); | 
|  | } | 
|  |  | 
|  | // This test verifies that an answer contains old ufrag and password if an offer | 
|  | // with old ufrag and password is received. | 
|  | TEST_F(WebRtcSessionTest, TestCreateAnswerWithOldUfragAndPassword) { | 
|  | Init(); | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_video = true; | 
|  | rtc::scoped_ptr<JsepSessionDescription> offer( | 
|  | CreateRemoteOffer(options)); | 
|  | SetRemoteDescriptionWithoutError(offer.release()); | 
|  |  | 
|  | SendAudioVideoStream1(); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> answer(CreateAnswer()); | 
|  | SetLocalDescriptionWithoutError(answer.release()); | 
|  |  | 
|  | // Receive an offer without changed ufrag or password. | 
|  | rtc::scoped_ptr<JsepSessionDescription> updated_offer2( | 
|  | CreateRemoteOffer(options, session_->remote_description())); | 
|  | SetRemoteDescriptionWithoutError(updated_offer2.release()); | 
|  |  | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> updated_answer2(CreateAnswer()); | 
|  |  | 
|  | EXPECT_TRUE(IceUfragPwdEqual(updated_answer2->description(), | 
|  | session_->local_description()->description())); | 
|  |  | 
|  | SetLocalDescriptionWithoutError(updated_answer2.release()); | 
|  | } | 
|  |  | 
|  | // This test verifies that if an offer does an ICE restart on some, but not all | 
|  | // media sections, the answer will change the ufrag/password in the correct | 
|  | // media sections. | 
|  | TEST_F(WebRtcSessionTest, TestCreateAnswerWithNewAndOldUfragAndPassword) { | 
|  | Init(); | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_video = true; | 
|  | options.recv_audio = true; | 
|  | options.bundle_enabled = false; | 
|  | rtc::scoped_ptr<JsepSessionDescription> offer(CreateRemoteOffer(options)); | 
|  |  | 
|  | SetIceUfragPwd(offer.get(), cricket::MEDIA_TYPE_AUDIO, "aaaa", | 
|  | "aaaaaaaaaaaaaaaaaaaaaa"); | 
|  | SetIceUfragPwd(offer.get(), cricket::MEDIA_TYPE_VIDEO, "bbbb", | 
|  | "bbbbbbbbbbbbbbbbbbbbbb"); | 
|  | SetRemoteDescriptionWithoutError(offer.release()); | 
|  |  | 
|  | SendAudioVideoStream1(); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> answer(CreateAnswer()); | 
|  | SetLocalDescriptionWithoutError(answer.release()); | 
|  |  | 
|  | // Receive an offer with new ufrag and password, but only for the video media | 
|  | // section. | 
|  | rtc::scoped_ptr<JsepSessionDescription> updated_offer( | 
|  | CreateRemoteOffer(options, session_->remote_description())); | 
|  | SetIceUfragPwd(updated_offer.get(), cricket::MEDIA_TYPE_VIDEO, "cccc", | 
|  | "cccccccccccccccccccccc"); | 
|  | SetRemoteDescriptionWithoutError(updated_offer.release()); | 
|  |  | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> updated_answer(CreateAnswer()); | 
|  |  | 
|  | EXPECT_TRUE(IceUfragPwdEqual(updated_answer->description(), | 
|  | session_->local_description()->description(), | 
|  | cricket::MEDIA_TYPE_AUDIO)); | 
|  |  | 
|  | EXPECT_FALSE(IceUfragPwdEqual(updated_answer->description(), | 
|  | session_->local_description()->description(), | 
|  | cricket::MEDIA_TYPE_VIDEO)); | 
|  |  | 
|  | SetLocalDescriptionWithoutError(updated_answer.release()); | 
|  | } | 
|  |  | 
|  | 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); | 
|  | } | 
|  |  | 
|  | // Runs the loopback call test with BUNDLE and STUN disabled. | 
|  | TEST_F(WebRtcSessionTest, TestIceStatesBasic) { | 
|  | // Lets try with only UDP ports. | 
|  | allocator_->set_flags(cricket::PORTALLOCATOR_DISABLE_TCP | | 
|  | cricket::PORTALLOCATOR_DISABLE_STUN | | 
|  | cricket::PORTALLOCATOR_DISABLE_RELAY); | 
|  | TestLoopbackCall(); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, TestIceStatesBasicIPv6) { | 
|  | allocator_->set_flags(cricket::PORTALLOCATOR_DISABLE_TCP | | 
|  | cricket::PORTALLOCATOR_DISABLE_STUN | | 
|  | cricket::PORTALLOCATOR_ENABLE_IPV6 | | 
|  | cricket::PORTALLOCATOR_DISABLE_RELAY); | 
|  |  | 
|  | // best connection is IPv6 since it has higher network preference. | 
|  | LoopbackNetworkConfiguration config; | 
|  | config.test_ipv6_network_ = true; | 
|  | config.best_connection_after_initial_ice_converged_ = | 
|  | LoopbackNetworkConfiguration::ExpectedBestConnection(0, 1); | 
|  |  | 
|  | TestLoopbackCall(config); | 
|  | } | 
|  |  | 
|  | // Runs the loopback call test with BUNDLE and STUN enabled. | 
|  | TEST_F(WebRtcSessionTest, TestIceStatesBundle) { | 
|  | allocator_->set_flags(cricket::PORTALLOCATOR_DISABLE_TCP | | 
|  | cricket::PORTALLOCATOR_DISABLE_RELAY); | 
|  | TestLoopbackCall(); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, TestRtpDataChannel) { | 
|  | configuration_.enable_rtp_data_channel = true; | 
|  | Init(); | 
|  | SetLocalDescriptionWithDataChannel(); | 
|  | ASSERT_TRUE(data_engine_); | 
|  | EXPECT_EQ(cricket::DCT_RTP, data_engine_->last_channel_type()); | 
|  | } | 
|  |  | 
|  | TEST_P(WebRtcSessionTest, TestRtpDataChannelConstraintTakesPrecedence) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  |  | 
|  | configuration_.enable_rtp_data_channel = true; | 
|  | options_.disable_sctp_data_channels = false; | 
|  |  | 
|  | InitWithDtls(GetParam()); | 
|  |  | 
|  | SetLocalDescriptionWithDataChannel(); | 
|  | EXPECT_EQ(cricket::DCT_RTP, data_engine_->last_channel_type()); | 
|  | } | 
|  |  | 
|  | TEST_P(WebRtcSessionTest, TestCreateOfferWithSctpEnabledWithoutStreams) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  |  | 
|  | InitWithDtls(GetParam()); | 
|  |  | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> offer(CreateOffer()); | 
|  | EXPECT_TRUE(offer->description()->GetContentByName("data") == NULL); | 
|  | EXPECT_TRUE(offer->description()->GetTransportInfoByName("data") == NULL); | 
|  | } | 
|  |  | 
|  | TEST_P(WebRtcSessionTest, TestCreateAnswerWithSctpInOfferAndNoStreams) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  | SetFactoryDtlsSrtp(); | 
|  | InitWithDtls(GetParam()); | 
|  |  | 
|  | // Create remote offer with SCTP. | 
|  | cricket::MediaSessionOptions options; | 
|  | options.data_channel_type = cricket::DCT_SCTP; | 
|  | JsepSessionDescription* offer = | 
|  | CreateRemoteOffer(options, cricket::SEC_DISABLED); | 
|  | SetRemoteDescriptionWithoutError(offer); | 
|  |  | 
|  | // Verifies the answer contains SCTP. | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> answer(CreateAnswer()); | 
|  | EXPECT_TRUE(answer != NULL); | 
|  | EXPECT_TRUE(answer->description()->GetContentByName("data") != NULL); | 
|  | EXPECT_TRUE(answer->description()->GetTransportInfoByName("data") != NULL); | 
|  | } | 
|  |  | 
|  | TEST_P(WebRtcSessionTest, TestSctpDataChannelWithoutDtls) { | 
|  | configuration_.enable_dtls_srtp = rtc::Optional<bool>(false); | 
|  | InitWithDtls(GetParam()); | 
|  |  | 
|  | SetLocalDescriptionWithDataChannel(); | 
|  | EXPECT_EQ(cricket::DCT_NONE, data_engine_->last_channel_type()); | 
|  | } | 
|  |  | 
|  | TEST_P(WebRtcSessionTest, TestSctpDataChannelWithDtls) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  |  | 
|  | InitWithDtls(GetParam()); | 
|  |  | 
|  | SetLocalDescriptionWithDataChannel(); | 
|  | EXPECT_EQ(cricket::DCT_SCTP, data_engine_->last_channel_type()); | 
|  | } | 
|  |  | 
|  | TEST_P(WebRtcSessionTest, TestDisableSctpDataChannels) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  | options_.disable_sctp_data_channels = true; | 
|  | InitWithDtls(GetParam()); | 
|  |  | 
|  | SetLocalDescriptionWithDataChannel(); | 
|  | EXPECT_EQ(cricket::DCT_NONE, data_engine_->last_channel_type()); | 
|  | } | 
|  |  | 
|  | TEST_P(WebRtcSessionTest, TestSctpDataChannelSendPortParsing) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  | const int new_send_port = 9998; | 
|  | const int new_recv_port = 7775; | 
|  |  | 
|  | InitWithDtls(GetParam()); | 
|  | SetFactoryDtlsSrtp(); | 
|  |  | 
|  | // By default, don't actually add the codecs to desc_factory_; they don't | 
|  | // actually get serialized for SCTP in BuildMediaDescription().  Instead, | 
|  | // let the session description get parsed.  That'll get the proper codecs | 
|  | // into the stream. | 
|  | cricket::MediaSessionOptions options; | 
|  | JsepSessionDescription* offer = CreateRemoteOfferWithSctpPort( | 
|  | "stream1", new_send_port, options); | 
|  |  | 
|  | // SetRemoteDescription will take the ownership of the offer. | 
|  | SetRemoteDescriptionWithoutError(offer); | 
|  |  | 
|  | SessionDescriptionInterface* answer = | 
|  | ChangeSDPSctpPort(new_recv_port, CreateAnswer()); | 
|  | ASSERT_TRUE(answer != NULL); | 
|  |  | 
|  | // Now set the local description, which'll take ownership of the answer. | 
|  | SetLocalDescriptionWithoutError(answer); | 
|  |  | 
|  | // TEST PLAN: Set the port number to something new, set it in the SDP, | 
|  | // and pass it all the way down. | 
|  | EXPECT_EQ(cricket::DCT_SCTP, data_engine_->last_channel_type()); | 
|  | CreateDataChannel(); | 
|  |  | 
|  | cricket::FakeDataMediaChannel* ch = data_engine_->GetChannel(0); | 
|  | int portnum = -1; | 
|  | ASSERT_TRUE(ch != NULL); | 
|  | ASSERT_EQ(1UL, ch->send_codecs().size()); | 
|  | EXPECT_EQ(cricket::kGoogleSctpDataCodecId, ch->send_codecs()[0].id); | 
|  | EXPECT_EQ(0, strcmp(cricket::kGoogleSctpDataCodecName, | 
|  | ch->send_codecs()[0].name.c_str())); | 
|  | EXPECT_TRUE(ch->send_codecs()[0].GetParam(cricket::kCodecParamPort, | 
|  | &portnum)); | 
|  | EXPECT_EQ(new_send_port, portnum); | 
|  |  | 
|  | ASSERT_EQ(1UL, ch->recv_codecs().size()); | 
|  | EXPECT_EQ(cricket::kGoogleSctpDataCodecId, ch->recv_codecs()[0].id); | 
|  | EXPECT_EQ(0, strcmp(cricket::kGoogleSctpDataCodecName, | 
|  | ch->recv_codecs()[0].name.c_str())); | 
|  | EXPECT_TRUE(ch->recv_codecs()[0].GetParam(cricket::kCodecParamPort, | 
|  | &portnum)); | 
|  | EXPECT_EQ(new_recv_port, portnum); | 
|  | } | 
|  |  | 
|  | // Verifies that when a session's DataChannel receives an OPEN message, | 
|  | // WebRtcSession signals the DataChannel creation request with the expected | 
|  | // config. | 
|  | TEST_P(WebRtcSessionTest, TestSctpDataChannelOpenMessage) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  |  | 
|  | InitWithDtls(GetParam()); | 
|  |  | 
|  | SetLocalDescriptionWithDataChannel(); | 
|  | EXPECT_EQ(cricket::DCT_SCTP, data_engine_->last_channel_type()); | 
|  |  | 
|  | webrtc::DataChannelInit config; | 
|  | config.id = 1; | 
|  | rtc::CopyOnWriteBuffer payload; | 
|  | webrtc::WriteDataChannelOpenMessage("a", config, &payload); | 
|  | cricket::ReceiveDataParams params; | 
|  | params.ssrc = config.id; | 
|  | params.type = cricket::DMT_CONTROL; | 
|  |  | 
|  | cricket::DataChannel* data_channel = session_->data_channel(); | 
|  | data_channel->SignalDataReceived(data_channel, params, payload); | 
|  |  | 
|  | EXPECT_EQ("a", last_data_channel_label_); | 
|  | EXPECT_EQ(config.id, last_data_channel_config_.id); | 
|  | EXPECT_FALSE(last_data_channel_config_.negotiated); | 
|  | EXPECT_EQ(webrtc::InternalDataChannelInit::kAcker, | 
|  | last_data_channel_config_.open_handshake_role); | 
|  | } | 
|  |  | 
|  | TEST_P(WebRtcSessionTest, TestUsesProvidedCertificate) { | 
|  | rtc::scoped_refptr<rtc::RTCCertificate> certificate = | 
|  | FakeDtlsIdentityStore::GenerateCertificate(); | 
|  |  | 
|  | configuration_.certificates.push_back(certificate); | 
|  | Init(); | 
|  | EXPECT_TRUE_WAIT(!session_->waiting_for_certificate_for_testing(), 1000); | 
|  |  | 
|  | EXPECT_EQ(session_->certificate_for_testing(), certificate); | 
|  | } | 
|  |  | 
|  | // Verifies that CreateOffer succeeds when CreateOffer is called before async | 
|  | // identity generation is finished (even if a certificate is provided this is | 
|  | // an async op). | 
|  | TEST_P(WebRtcSessionTest, TestCreateOfferBeforeIdentityRequestReturnSuccess) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  | InitWithDtls(GetParam()); | 
|  |  | 
|  | EXPECT_TRUE(session_->waiting_for_certificate_for_testing()); | 
|  | SendAudioVideoStream1(); | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> offer(CreateOffer()); | 
|  |  | 
|  | EXPECT_TRUE(offer != NULL); | 
|  | VerifyNoCryptoParams(offer->description(), true); | 
|  | VerifyFingerprintStatus(offer->description(), true); | 
|  | } | 
|  |  | 
|  | // Verifies that CreateAnswer succeeds when CreateOffer is called before async | 
|  | // identity generation is finished (even if a certificate is provided this is | 
|  | // an async op). | 
|  | TEST_P(WebRtcSessionTest, TestCreateAnswerBeforeIdentityRequestReturnSuccess) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  | InitWithDtls(GetParam()); | 
|  | SetFactoryDtlsSrtp(); | 
|  |  | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_video = true; | 
|  | scoped_ptr<JsepSessionDescription> offer( | 
|  | CreateRemoteOffer(options, cricket::SEC_DISABLED)); | 
|  | ASSERT_TRUE(offer.get() != NULL); | 
|  | SetRemoteDescriptionWithoutError(offer.release()); | 
|  |  | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> answer(CreateAnswer()); | 
|  | EXPECT_TRUE(answer != NULL); | 
|  | VerifyNoCryptoParams(answer->description(), true); | 
|  | VerifyFingerprintStatus(answer->description(), true); | 
|  | } | 
|  |  | 
|  | // Verifies that CreateOffer succeeds when CreateOffer is called after async | 
|  | // identity generation is finished (even if a certificate is provided this is | 
|  | // an async op). | 
|  | TEST_P(WebRtcSessionTest, TestCreateOfferAfterIdentityRequestReturnSuccess) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  | InitWithDtls(GetParam()); | 
|  |  | 
|  | EXPECT_TRUE_WAIT(!session_->waiting_for_certificate_for_testing(), 1000); | 
|  |  | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> offer(CreateOffer()); | 
|  | EXPECT_TRUE(offer != NULL); | 
|  | } | 
|  |  | 
|  | // Verifies that CreateOffer fails when CreateOffer is called after async | 
|  | // identity generation fails. | 
|  | TEST_F(WebRtcSessionTest, TestCreateOfferAfterIdentityRequestReturnFailure) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  | InitWithDtlsIdentityGenFail(); | 
|  |  | 
|  | EXPECT_TRUE_WAIT(!session_->waiting_for_certificate_for_testing(), 1000); | 
|  |  | 
|  | rtc::scoped_ptr<SessionDescriptionInterface> offer(CreateOffer()); | 
|  | EXPECT_TRUE(offer == NULL); | 
|  | } | 
|  |  | 
|  | // Verifies that CreateOffer succeeds when Multiple CreateOffer calls are made | 
|  | // before async identity generation is finished. | 
|  | TEST_P(WebRtcSessionTest, | 
|  | TestMultipleCreateOfferBeforeIdentityRequestReturnSuccess) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  | VerifyMultipleAsyncCreateDescription(GetParam(), | 
|  | CreateSessionDescriptionRequest::kOffer); | 
|  | } | 
|  |  | 
|  | // Verifies that CreateOffer fails when Multiple CreateOffer calls are made | 
|  | // before async identity generation fails. | 
|  | TEST_F(WebRtcSessionTest, | 
|  | TestMultipleCreateOfferBeforeIdentityRequestReturnFailure) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  | VerifyMultipleAsyncCreateDescriptionIdentityGenFailure( | 
|  | CreateSessionDescriptionRequest::kOffer); | 
|  | } | 
|  |  | 
|  | // Verifies that CreateAnswer succeeds when Multiple CreateAnswer calls are made | 
|  | // before async identity generation is finished. | 
|  | TEST_P(WebRtcSessionTest, | 
|  | TestMultipleCreateAnswerBeforeIdentityRequestReturnSuccess) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  | VerifyMultipleAsyncCreateDescription( | 
|  | GetParam(), CreateSessionDescriptionRequest::kAnswer); | 
|  | } | 
|  |  | 
|  | // Verifies that CreateAnswer fails when Multiple CreateAnswer calls are made | 
|  | // before async identity generation fails. | 
|  | TEST_F(WebRtcSessionTest, | 
|  | TestMultipleCreateAnswerBeforeIdentityRequestReturnFailure) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  | VerifyMultipleAsyncCreateDescriptionIdentityGenFailure( | 
|  | CreateSessionDescriptionRequest::kAnswer); | 
|  | } | 
|  |  | 
|  | // Verifies that setRemoteDescription fails when DTLS is disabled and the remote | 
|  | // offer has no SDES crypto but only DTLS fingerprint. | 
|  | TEST_F(WebRtcSessionTest, TestSetRemoteOfferFailIfDtlsDisabledAndNoCrypto) { | 
|  | // Init without DTLS. | 
|  | Init(); | 
|  | // Create a remote offer with secured transport disabled. | 
|  | cricket::MediaSessionOptions options; | 
|  | JsepSessionDescription* offer(CreateRemoteOffer( | 
|  | options, cricket::SEC_DISABLED)); | 
|  | // Adds a DTLS fingerprint to the remote offer. | 
|  | cricket::SessionDescription* sdp = offer->description(); | 
|  | TransportInfo* audio = sdp->GetTransportInfoByName("audio"); | 
|  | ASSERT_TRUE(audio != NULL); | 
|  | ASSERT_TRUE(audio->description.identity_fingerprint.get() == NULL); | 
|  | audio->description.identity_fingerprint.reset( | 
|  | rtc::SSLFingerprint::CreateFromRfc4572( | 
|  | rtc::DIGEST_SHA_256, kFakeDtlsFingerprint)); | 
|  | SetRemoteDescriptionOfferExpectError(kSdpWithoutSdesCrypto, | 
|  | offer); | 
|  | } | 
|  |  | 
|  | TEST_F(WebRtcSessionTest, TestCombinedAudioVideoBweConstraint) { | 
|  | configuration_.combined_audio_video_bwe = rtc::Optional<bool>(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<bool>(true), audio_options.combined_audio_video_bwe); | 
|  | } | 
|  |  | 
|  | // Tests that we can renegotiate new media content with ICE candidates in the | 
|  | // new remote SDP. | 
|  | TEST_P(WebRtcSessionTest, TestRenegotiateNewMediaWithCandidatesInSdp) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  | InitWithDtls(GetParam()); | 
|  | SetFactoryDtlsSrtp(); | 
|  |  | 
|  | SendAudioOnlyStream2(); | 
|  | SessionDescriptionInterface* offer = CreateOffer(); | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  |  | 
|  | SessionDescriptionInterface* answer = CreateRemoteAnswer(offer); | 
|  | SetRemoteDescriptionWithoutError(answer); | 
|  |  | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_video = true; | 
|  | offer = CreateRemoteOffer(options, cricket::SEC_DISABLED); | 
|  |  | 
|  | cricket::Candidate candidate1; | 
|  | candidate1.set_address(rtc::SocketAddress("1.1.1.1", 5000)); | 
|  | candidate1.set_component(1); | 
|  | JsepIceCandidate ice_candidate(kMediaContentName1, kMediaContentIndex1, | 
|  | candidate1); | 
|  | EXPECT_TRUE(offer->AddCandidate(&ice_candidate)); | 
|  | SetRemoteDescriptionWithoutError(offer); | 
|  |  | 
|  | answer = CreateAnswer(); | 
|  | SetLocalDescriptionWithoutError(answer); | 
|  | } | 
|  |  | 
|  | // Tests that we can renegotiate new media content with ICE candidates separated | 
|  | // from the remote SDP. | 
|  | TEST_P(WebRtcSessionTest, TestRenegotiateNewMediaWithCandidatesSeparated) { | 
|  | MAYBE_SKIP_TEST(rtc::SSLStreamAdapter::HaveDtlsSrtp); | 
|  | InitWithDtls(GetParam()); | 
|  | SetFactoryDtlsSrtp(); | 
|  |  | 
|  | SendAudioOnlyStream2(); | 
|  | SessionDescriptionInterface* offer = CreateOffer(); | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  |  | 
|  | SessionDescriptionInterface* answer = CreateRemoteAnswer(offer); | 
|  | SetRemoteDescriptionWithoutError(answer); | 
|  |  | 
|  | cricket::MediaSessionOptions options; | 
|  | options.recv_video = true; | 
|  | offer = CreateRemoteOffer(options, cricket::SEC_DISABLED); | 
|  | SetRemoteDescriptionWithoutError(offer); | 
|  |  | 
|  | cricket::Candidate candidate1; | 
|  | candidate1.set_address(rtc::SocketAddress("1.1.1.1", 5000)); | 
|  | candidate1.set_component(1); | 
|  | JsepIceCandidate ice_candidate(kMediaContentName1, kMediaContentIndex1, | 
|  | candidate1); | 
|  | EXPECT_TRUE(session_->ProcessIceMessage(&ice_candidate)); | 
|  |  | 
|  | answer = CreateAnswer(); | 
|  | SetLocalDescriptionWithoutError(answer); | 
|  | } | 
|  |  | 
|  | // Flaky on Win and Mac only. See webrtc:4943 | 
|  | #if defined(WEBRTC_WIN) || defined(WEBRTC_MAC) | 
|  | #define MAYBE_TestRtxRemovedByCreateAnswer DISABLED_TestRtxRemovedByCreateAnswer | 
|  | #else | 
|  | #define MAYBE_TestRtxRemovedByCreateAnswer TestRtxRemovedByCreateAnswer | 
|  | #endif | 
|  | // Tests that RTX codec is removed from the answer when it isn't supported | 
|  | // by local side. | 
|  | TEST_F(WebRtcSessionTest, MAYBE_TestRtxRemovedByCreateAnswer) { | 
|  | Init(); | 
|  | SendAudioVideoStream1(); | 
|  | 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(offer_sdp.find("rtx") != std::string::npos); | 
|  | SetRemoteDescriptionWithoutError(offer); | 
|  |  | 
|  | SessionDescriptionInterface* answer = CreateAnswer(); | 
|  | std::string answer_sdp; | 
|  | answer->ToString(&answer_sdp); | 
|  | // Answer SDP removes the unsupported RTX codec. | 
|  | EXPECT_TRUE(answer_sdp.find("rtx") == std::string::npos); | 
|  | SetLocalDescriptionWithoutError(answer); | 
|  | } | 
|  |  | 
|  | // This verifies that the voice channel after bundle has both options from video | 
|  | // and voice channels. | 
|  | TEST_F(WebRtcSessionTest, TestSetSocketOptionBeforeBundle) { | 
|  | InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyBalanced); | 
|  | SendAudioVideoStream1(); | 
|  |  | 
|  | PeerConnectionInterface::RTCOfferAnswerOptions options; | 
|  | options.use_rtp_mux = true; | 
|  |  | 
|  | SessionDescriptionInterface* offer = CreateOffer(options); | 
|  | SetLocalDescriptionWithoutError(offer); | 
|  |  | 
|  | session_->video_channel()->SetOption(cricket::BaseChannel::ST_RTP, | 
|  | rtc::Socket::Option::OPT_SNDBUF, 4000); | 
|  |  | 
|  | session_->voice_channel()->SetOption(cricket::BaseChannel::ST_RTP, | 
|  | rtc::Socket::Option::OPT_RCVBUF, 8000); | 
|  |  | 
|  | int option_val; | 
|  | EXPECT_TRUE(session_->video_rtp_transport_channel()->GetOption( | 
|  | rtc::Socket::Option::OPT_SNDBUF, &option_val)); | 
|  | EXPECT_EQ(4000, option_val); | 
|  | EXPECT_FALSE(session_->voice_rtp_transport_channel()->GetOption( | 
|  | rtc::Socket::Option::OPT_SNDBUF, &option_val)); | 
|  |  | 
|  | EXPECT_TRUE(session_->voice_rtp_transport_channel()->GetOption( | 
|  | rtc::Socket::Option::OPT_RCVBUF, &option_val)); | 
|  | EXPECT_EQ(8000, option_val); | 
|  | EXPECT_FALSE(session_->video_rtp_transport_channel()->GetOption( | 
|  | rtc::Socket::Option::OPT_RCVBUF, &option_val)); | 
|  |  | 
|  | EXPECT_NE(session_->voice_rtp_transport_channel(), | 
|  | session_->video_rtp_transport_channel()); | 
|  |  | 
|  | SendAudioVideoStream2(); | 
|  | SessionDescriptionInterface* answer = | 
|  | CreateRemoteAnswer(session_->local_description()); | 
|  | SetRemoteDescriptionWithoutError(answer); | 
|  |  | 
|  | EXPECT_TRUE(session_->voice_rtp_transport_channel()->GetOption( | 
|  | rtc::Socket::Option::OPT_SNDBUF, &option_val)); | 
|  | EXPECT_EQ(4000, option_val); | 
|  |  | 
|  | EXPECT_TRUE(session_->voice_rtp_transport_channel()->GetOption( | 
|  | rtc::Socket::Option::OPT_RCVBUF, &option_val)); | 
|  | 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<WebRtcSessionCreateSDPObserverForTest> observers[100]; | 
|  | PeerConnectionInterface::RTCOfferAnswerOptions options; | 
|  | options.offer_to_receive_audio = | 
|  | RTCOfferAnswerOptions::kOfferToReceiveMediaTrue; | 
|  | cricket::MediaSessionOptions session_options; | 
|  | session_options.recv_audio = true; | 
|  |  | 
|  | 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(); | 
|  | } | 
|  |  | 
|  | // Make sure the signal from "GetOnDestroyedSignal()" fires when the session | 
|  | // is destroyed. | 
|  | TEST_F(WebRtcSessionTest, TestOnDestroyedSignal) { | 
|  | Init(); | 
|  | session_.reset(); | 
|  | EXPECT_TRUE(session_destroyed_); | 
|  | } | 
|  |  | 
|  | // TODO(bemasc): Add a TestIceStatesBundle with BUNDLE enabled.  That test | 
|  | // currently fails because upon disconnection and reconnection OnIceComplete is | 
|  | // called more than once without returning to IceGatheringGathering. | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(WebRtcSessionTests, | 
|  | WebRtcSessionTest, | 
|  | testing::Values(ALREADY_GENERATED, | 
|  | DTLS_IDENTITY_STORE)); |