diff options
author | sergeyu <sergeyu@chromium.org> | 2015-11-18 18:24:01 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-11-19 02:24:50 +0000 |
commit | c661e6bbc53491be1e227b92ca39dfab65107039 (patch) | |
tree | 619812ff866d1f03af049af1b93952773ed0438a /remoting | |
parent | 36fbcc9a3108e3a76e4304fe44a79d5a86134bd1 (diff) | |
download | chromium_src-c661e6bbc53491be1e227b92ca39dfab65107039.zip chromium_src-c661e6bbc53491be1e227b92ca39dfab65107039.tar.gz chromium_src-c661e6bbc53491be1e227b92ca39dfab65107039.tar.bz2 |
Implement WebrtcTransport
This is an initial portion of the new WebRTC-based protocol. The
new Transport implementation can establish connection but doesn't
send any data yet.
BUG=547158
Review URL: https://codereview.chromium.org/1427003009
Cr-Commit-Position: refs/heads/master@{#360491}
Diffstat (limited to 'remoting')
-rw-r--r-- | remoting/host/BUILD.gn | 9 | ||||
-rw-r--r-- | remoting/protocol/BUILD.gn | 14 | ||||
-rw-r--r-- | remoting/protocol/jingle_session.cc | 18 | ||||
-rw-r--r-- | remoting/protocol/jingle_session.h | 1 | ||||
-rw-r--r-- | remoting/protocol/transport.h | 3 | ||||
-rw-r--r-- | remoting/protocol/webrtc_transport.cc | 497 | ||||
-rw-r--r-- | remoting/protocol/webrtc_transport.h | 124 | ||||
-rw-r--r-- | remoting/protocol/webrtc_transport_unittest.cc | 171 | ||||
-rw-r--r-- | remoting/remoting.gyp | 2 | ||||
-rw-r--r-- | remoting/remoting_host.gypi | 1 | ||||
-rw-r--r-- | remoting/remoting_nacl.gyp | 1 | ||||
-rw-r--r-- | remoting/remoting_srcs.gypi | 2 | ||||
-rw-r--r-- | remoting/remoting_test.gypi | 1 |
13 files changed, 835 insertions, 9 deletions
diff --git a/remoting/host/BUILD.gn b/remoting/host/BUILD.gn index f62a03f..5489890 100644 --- a/remoting/host/BUILD.gn +++ b/remoting/host/BUILD.gn @@ -193,14 +193,7 @@ if (is_mac) { # TODO(GYP) Mac build of remoting host. } if (enable_webrtc) { - deps += [ - # TODO(GYP): crbug.com/481633. We should probably not have to depend on - # libjingle_webrtc; that should be pulled in automatically by - # libpeerconnection instead. - "//third_party/libjingle:libjingle_webrtc", - "//third_party/libjingle:libpeerconnection", - "//third_party/webrtc/modules/desktop_capture", - ] + deps += [ "//third_party/webrtc/modules/desktop_capture" ] sources += rebase_path(remoting_host_srcs_gypi_values.remoting_cast_sources, diff --git a/remoting/protocol/BUILD.gn b/remoting/protocol/BUILD.gn index f5d79ca..8a81dd2 100644 --- a/remoting/protocol/BUILD.gn +++ b/remoting/protocol/BUILD.gn @@ -2,6 +2,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//build/config/features.gni") import("//remoting/remoting_srcs.gni") source_set("protocol") { @@ -36,6 +37,18 @@ source_set("protocol") { "chromium_socket_factory.cc", ] } + + if (!is_nacl && enable_webrtc) { + deps += [ + # TODO(GYP): crbug.com/481633. We should probably not have to depend on + # libjingle_webrtc; that should be pulled in automatically by + # libpeerconnection instead. + "//third_party/libjingle:libjingle_webrtc", + "//third_party/libjingle:libpeerconnection", + ] + } else { + sources -= [ "webrtc_transport.cc" ] + } } source_set("test_support") { @@ -95,6 +108,7 @@ source_set("unit_tests") { "ssl_hmac_channel_authenticator_unittest.cc", "third_party_authenticator_unittest.cc", "v2_authenticator_unittest.cc", + "webrtc_transport_unittest.cc", ] deps = [ diff --git a/remoting/protocol/jingle_session.cc b/remoting/protocol/jingle_session.cc index 46cc311..fe0dc7e 100644 --- a/remoting/protocol/jingle_session.cc +++ b/remoting/protocol/jingle_session.cc @@ -277,6 +277,8 @@ void JingleSession::Close(protocol::ErrorCode error) { } void JingleSession::SendMessage(const JingleMessage& message) { + DCHECK(CalledOnValidThread()); + scoped_ptr<IqRequest> request = session_manager_->iq_sender()->SendIq( message.ToXml(), base::Bind(&JingleSession::OnMessageResponse, @@ -301,6 +303,8 @@ void JingleSession::OnMessageResponse( JingleMessage::ActionType request_type, IqRequest* request, const buzz::XmlElement* response) { + DCHECK(CalledOnValidThread()); + // Delete the request from the list of pending requests. pending_requests_.erase(request); delete request; @@ -333,6 +337,8 @@ void JingleSession::OnMessageResponse( void JingleSession::OnOutgoingTransportInfo( scoped_ptr<XmlElement> transport_info) { + DCHECK(CalledOnValidThread()); + JingleMessage message(peer_jid_, JingleMessage::TRANSPORT_INFO, session_id_); message.transport_info = transport_info.Pass(); @@ -349,15 +355,27 @@ void JingleSession::OnOutgoingTransportInfo( void JingleSession::OnTransportRouteChange(const std::string& channel_name, const TransportRoute& route) { + DCHECK(CalledOnValidThread()); + event_handler_->OnSessionRouteChange(channel_name, route); } +void JingleSession::OnTransportConnected() { + DCHECK(CalledOnValidThread()); + + // TODO(sergeyu): Add Session::State value to indicate that the transport has + // been connected. +} + void JingleSession::OnTransportError(ErrorCode error) { + DCHECK(CalledOnValidThread()); + Close(error); } void JingleSession::OnTransportInfoResponse(IqRequest* request, const buzz::XmlElement* response) { + DCHECK(CalledOnValidThread()); DCHECK(!transport_info_requests_.empty()); // Consider transport-info requests sent before this one lost and delete diff --git a/remoting/protocol/jingle_session.h b/remoting/protocol/jingle_session.h index cb8fd96..149aeb5 100644 --- a/remoting/protocol/jingle_session.h +++ b/remoting/protocol/jingle_session.h @@ -77,6 +77,7 @@ class JingleSession : public base::NonThreadSafe, scoped_ptr<buzz::XmlElement> transport_info) override; void OnTransportRouteChange(const std::string& component, const TransportRoute& route) override; + void OnTransportConnected() override; void OnTransportError(ErrorCode error) override; // Response handler for transport-info responses. Transport-info timeouts are diff --git a/remoting/protocol/transport.h b/remoting/protocol/transport.h index 08d0aa3..00170c5 100644 --- a/remoting/protocol/transport.h +++ b/remoting/protocol/transport.h @@ -67,6 +67,9 @@ class Transport { virtual void OnTransportRouteChange(const std::string& channel_name, const TransportRoute& route) = 0; + // Called when the transport is connected. + virtual void OnTransportConnected() = 0; + // Called when there is an error connecting the session. virtual void OnTransportError(ErrorCode error) = 0; }; diff --git a/remoting/protocol/webrtc_transport.cc b/remoting/protocol/webrtc_transport.cc new file mode 100644 index 0000000..4bc629b --- /dev/null +++ b/remoting/protocol/webrtc_transport.cc @@ -0,0 +1,497 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/protocol/webrtc_transport.h" + +#include "base/callback_helpers.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/string_number_conversions.h" +#include "base/task_runner_util.h" +#include "jingle/glue/thread_wrapper.h" +#include "third_party/libjingle/source/talk/app/webrtc/test/fakeconstraints.h" +#include "third_party/webrtc/libjingle/xmllite/xmlelement.h" +#include "third_party/webrtc/modules/audio_device/include/fake_audio_device.h" + +using buzz::QName; +using buzz::XmlElement; + +namespace remoting { +namespace protocol { + +namespace { + +// Delay after candidate creation before sending transport-info message to +// accumulate multiple candidates. This is an optimization to reduce number of +// transport-info messages. +const int kTransportInfoSendDelayMs = 20; + +// XML namespace for the transport elements. +const char kTransportNamespace[] = "google:remoting:webrtc"; + +rtc::Thread* InitAndGetRtcThread() { + jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop(); + + // TODO(sergeyu): Investigate if it's possible to avoid Send(). + jingle_glue::JingleThreadWrapper::current()->set_send_allowed(true); + + return jingle_glue::JingleThreadWrapper::current(); +} + +// A webrtc::CreateSessionDescriptionObserver implementation used to receive the +// results of creating descriptions for this end of the PeerConnection. +class CreateSessionDescriptionObserver + : public webrtc::CreateSessionDescriptionObserver { + public: + typedef base::Callback<void( + scoped_ptr<webrtc::SessionDescriptionInterface> description, + const std::string& error)> ResultCallback; + + static CreateSessionDescriptionObserver* Create( + const ResultCallback& result_callback) { + return new rtc::RefCountedObject<CreateSessionDescriptionObserver>( + result_callback); + } + void OnSuccess(webrtc::SessionDescriptionInterface* desc) override { + base::ResetAndReturn(&result_callback_) + .Run(make_scoped_ptr(desc), std::string()); + } + void OnFailure(const std::string& error) override { + base::ResetAndReturn(&result_callback_).Run(nullptr, error); + } + + protected: + explicit CreateSessionDescriptionObserver( + const ResultCallback& result_callback) + : result_callback_(result_callback) {} + ~CreateSessionDescriptionObserver() override {} + + private: + ResultCallback result_callback_; + + DISALLOW_COPY_AND_ASSIGN(CreateSessionDescriptionObserver); +}; + +// A webrtc::SetSessionDescriptionObserver implementation used to receive the +// results of setting local and remote descriptions of the PeerConnection. +class SetSessionDescriptionObserver + : public webrtc::SetSessionDescriptionObserver { + public: + typedef base::Callback<void(bool success, const std::string& error)> + ResultCallback; + + static SetSessionDescriptionObserver* Create( + const ResultCallback& result_callback) { + return new rtc::RefCountedObject<SetSessionDescriptionObserver>( + result_callback); + } + + void OnSuccess() override { + base::ResetAndReturn(&result_callback_).Run(true, std::string()); + } + + void OnFailure(const std::string& error) override { + base::ResetAndReturn(&result_callback_).Run(false, error); + } + + protected: + SetSessionDescriptionObserver(const ResultCallback& result_callback) + : result_callback_(result_callback) {} + ~SetSessionDescriptionObserver() override {} + + private: + ResultCallback result_callback_; + + DISALLOW_COPY_AND_ASSIGN(SetSessionDescriptionObserver); +}; + +} // namespace + +WebrtcTransport::WebrtcTransport( + rtc::scoped_refptr<webrtc::PortAllocatorFactoryInterface> + port_allocator_factory, + TransportRole role, + scoped_refptr<base::SingleThreadTaskRunner> worker_task_runner) + : port_allocator_factory_(port_allocator_factory), + role_(role), + worker_task_runner_(worker_task_runner), + weak_factory_(this) {} + +WebrtcTransport::~WebrtcTransport() {} + +void WebrtcTransport::Start(EventHandler* event_handler, + Authenticator* authenticator) { + DCHECK(thread_checker_.CalledOnValidThread()); + + event_handler_ = event_handler; + + // TODO(sergeyu): Use the |authenticator| to authenticate PeerConnection. + + base::PostTaskAndReplyWithResult( + worker_task_runner_.get(), FROM_HERE, base::Bind(&InitAndGetRtcThread), + base::Bind(&WebrtcTransport::DoStart, weak_factory_.GetWeakPtr())); +} + +bool WebrtcTransport::ProcessTransportInfo(XmlElement* transport_info) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (transport_info->Name() != QName(kTransportNamespace, "transport")) + return false; + + if (!peer_connection_) + return false; + + XmlElement* session_description = transport_info->FirstNamed( + QName(kTransportNamespace, "session-description")); + if (session_description) { + webrtc::PeerConnectionInterface::SignalingState expected_state = + role_ == TransportRole::SERVER + ? webrtc::PeerConnectionInterface::kStable + : webrtc::PeerConnectionInterface::kHaveLocalOffer; + if (peer_connection_->signaling_state() != expected_state) { + LOG(ERROR) << "Received unexpected WebRTC session_description. "; + return false; + } + + std::string type = session_description->Attr(QName(std::string(), "type")); + std::string sdp = session_description->BodyText(); + if (type.empty() || sdp.empty()) { + LOG(ERROR) << "Incorrect session_description format."; + return false; + } + + webrtc::SdpParseError error; + scoped_ptr<webrtc::SessionDescriptionInterface> session_description( + webrtc::CreateSessionDescription(type, sdp, &error)); + if (!session_description) { + LOG(ERROR) << "Failed to parse the offer: " << error.description + << " line: " << error.line; + return false; + } + + peer_connection_->SetRemoteDescription( + SetSessionDescriptionObserver::Create( + base::Bind(&WebrtcTransport::OnRemoteDescriptionSet, + weak_factory_.GetWeakPtr())), + session_description.release()); + } + + XmlElement* candidate_element; + QName candidate_qname(kTransportNamespace, "candidate"); + for (candidate_element = transport_info->FirstNamed(candidate_qname); + candidate_element; + candidate_element = candidate_element->NextNamed(candidate_qname)) { + std::string candidate_str = candidate_element->BodyText(); + std::string sdp_mid = + candidate_element->Attr(QName(std::string(), "sdpMid")); + std::string sdp_mlineindex_str = + candidate_element->Attr(QName(std::string(), "sdpMLineIndex")); + int sdp_mlineindex; + if (candidate_str.empty() || sdp_mid.empty() || + !base::StringToInt(sdp_mlineindex_str, &sdp_mlineindex)) { + LOG(ERROR) << "Failed to parse incoming candidates."; + return false; + } + + webrtc::SdpParseError error; + scoped_ptr<webrtc::IceCandidateInterface> candidate( + webrtc::CreateIceCandidate(sdp_mid, sdp_mlineindex, candidate_str, + &error)); + if (!candidate) { + LOG(ERROR) << "Failed to parse incoming candidate: " << error.description + << " line: " << error.line; + return false; + } + + if (peer_connection_->signaling_state() == + webrtc::PeerConnectionInterface::kStable) { + if (!peer_connection_->AddIceCandidate(candidate.get())) { + LOG(ERROR) << "Failed to add incoming ICE candidate."; + return false; + } + } else { + pending_incoming_candidates_.push_back(candidate.Pass()); + } + } + + return true; +} + +DatagramChannelFactory* WebrtcTransport::GetDatagramChannelFactory() { + DCHECK(thread_checker_.CalledOnValidThread()); + NOTIMPLEMENTED(); + return nullptr; +} + +StreamChannelFactory* WebrtcTransport::GetStreamChannelFactory() { + DCHECK(thread_checker_.CalledOnValidThread()); + // TODO(sergeyu): Implement data stream support. + NOTIMPLEMENTED(); + return nullptr; +} + +StreamChannelFactory* WebrtcTransport::GetMultiplexedChannelFactory() { + DCHECK(thread_checker_.CalledOnValidThread()); + return GetStreamChannelFactory(); +} + +void WebrtcTransport::DoStart(rtc::Thread* worker_thread) { + DCHECK(thread_checker_.CalledOnValidThread()); + + jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop(); + + // TODO(sergeyu): Investigate if it's possible to avoid Send(). + jingle_glue::JingleThreadWrapper::current()->set_send_allowed(true); + + fake_audio_device_module_.reset(new webrtc::FakeAudioDeviceModule()); + + peer_connection_factory_ = webrtc::CreatePeerConnectionFactory( + worker_thread, rtc::Thread::Current(), + fake_audio_device_module_.get(), nullptr, nullptr); + + webrtc::PeerConnectionInterface::IceServer stun_server; + stun_server.urls.push_back("stun:stun.l.google.com:19302"); + webrtc::PeerConnectionInterface::RTCConfiguration rtc_config; + rtc_config.servers.push_back(stun_server); + + webrtc::FakeConstraints constraints; + constraints.AddMandatory(webrtc::MediaConstraintsInterface::kEnableDtlsSrtp, + webrtc::MediaConstraintsInterface::kValueTrue); + + peer_connection_ = peer_connection_factory_->CreatePeerConnection( + rtc_config, &constraints, port_allocator_factory_, nullptr, this); + + if (role_ == TransportRole::CLIENT) { + webrtc::FakeConstraints offer_config; + offer_config.AddMandatory( + webrtc::MediaConstraintsInterface::kOfferToReceiveVideo, + webrtc::MediaConstraintsInterface::kValueTrue); + offer_config.AddMandatory( + webrtc::MediaConstraintsInterface::kOfferToReceiveAudio, + webrtc::MediaConstraintsInterface::kValueFalse); + peer_connection_->CreateOffer( + CreateSessionDescriptionObserver::Create( + base::Bind(&WebrtcTransport::OnLocalSessionDescriptionCreated, + weak_factory_.GetWeakPtr())), + &offer_config); + } +} + +void WebrtcTransport::OnLocalSessionDescriptionCreated( + scoped_ptr<webrtc::SessionDescriptionInterface> description, + const std::string& error) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (!peer_connection_) + return; + + if (!description) { + LOG(ERROR) << "PeerConnection offer creation failed: " << error; + Close(CHANNEL_CONNECTION_ERROR); + return; + } + + std::string description_sdp; + if (!description->ToString(&description_sdp)) { + LOG(ERROR) << "Failed to serialize description."; + Close(CHANNEL_CONNECTION_ERROR); + return; + } + + // Format and send the session description to the peer. + scoped_ptr<XmlElement> transport_info( + new XmlElement(QName(kTransportNamespace, "transport"), true)); + XmlElement* offer_tag = + new XmlElement(QName(kTransportNamespace, "session-description")); + transport_info->AddElement(offer_tag); + offer_tag->SetAttr(QName(std::string(), "type"), description->type()); + offer_tag->SetBodyText(description_sdp); + + event_handler_->OnOutgoingTransportInfo(transport_info.Pass()); + + peer_connection_->SetLocalDescription( + SetSessionDescriptionObserver::Create(base::Bind( + &WebrtcTransport::OnLocalDescriptionSet, weak_factory_.GetWeakPtr())), + description.release()); +} + +void WebrtcTransport::OnLocalDescriptionSet(bool success, + const std::string& error) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (!peer_connection_) + return; + + if (!success) { + LOG(ERROR) << "Failed to set local description: " << error; + Close(CHANNEL_CONNECTION_ERROR); + return; + } + + AddPendingCandidatesIfPossible(); +} + +void WebrtcTransport::OnRemoteDescriptionSet(bool success, + const std::string& error) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (!peer_connection_) + return; + + if (!success) { + LOG(ERROR) << "Failed to set local description: " << error; + Close(CHANNEL_CONNECTION_ERROR); + return; + } + + // Create and send answer on the server. + if (role_ == TransportRole::SERVER) { + peer_connection_->CreateAnswer( + CreateSessionDescriptionObserver::Create( + base::Bind(&WebrtcTransport::OnLocalSessionDescriptionCreated, + weak_factory_.GetWeakPtr())), + nullptr); + } + + AddPendingCandidatesIfPossible(); +} + +void WebrtcTransport::Close(ErrorCode error) { + DCHECK(thread_checker_.CalledOnValidThread()); + + weak_factory_.InvalidateWeakPtrs(); + peer_connection_->Close(); + peer_connection_ = nullptr; + peer_connection_factory_ = nullptr; + + if (error != OK) + event_handler_->OnTransportError(error); +} + +void WebrtcTransport::OnSignalingChange( + webrtc::PeerConnectionInterface::SignalingState new_state) { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void WebrtcTransport::OnAddStream(webrtc::MediaStreamInterface* stream) { + DCHECK(thread_checker_.CalledOnValidThread()); + LOG(ERROR) << "Stream added " << stream->label(); +} + +void WebrtcTransport::OnRemoveStream(webrtc::MediaStreamInterface* stream) { + DCHECK(thread_checker_.CalledOnValidThread()); + LOG(ERROR) << "Stream removed " << stream->label(); +} + +void WebrtcTransport::OnDataChannel( + webrtc::DataChannelInterface* data_channel) { + DCHECK(thread_checker_.CalledOnValidThread()); + // TODO(sergeyu): Use the data channel. +} + +void WebrtcTransport::OnRenegotiationNeeded() { + DCHECK(thread_checker_.CalledOnValidThread()); + // TODO(sergeyu): Figure out what needs to happen here. +} + +void WebrtcTransport::OnIceConnectionChange( + webrtc::PeerConnectionInterface::IceConnectionState new_state) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (new_state == webrtc::PeerConnectionInterface::kIceConnectionConnected) + event_handler_->OnTransportConnected(); +} + +void WebrtcTransport::OnIceGatheringChange( + webrtc::PeerConnectionInterface::IceGatheringState new_state) { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void WebrtcTransport::OnIceCandidate( + const webrtc::IceCandidateInterface* candidate) { + DCHECK(thread_checker_.CalledOnValidThread()); + + scoped_ptr<XmlElement> candidate_element( + new XmlElement(QName(kTransportNamespace, "candidate"))); + std::string candidate_str; + if (!candidate->ToString(&candidate_str)) { + LOG(ERROR) << "Failed to serialize local candidate."; + return; + } + candidate_element->SetBodyText(candidate_str); + candidate_element->SetAttr(QName(std::string(), "sdpMid"), + candidate->sdp_mid()); + candidate_element->SetAttr(QName(std::string(), "sdpMLineIndex"), + base::IntToString(candidate->sdp_mline_index())); + + EnsurePendingTransportInfoMessage(); + pending_transport_info_message_->AddElement(candidate_element.release()); +} + +void WebrtcTransport::EnsurePendingTransportInfoMessage() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // |transport_info_timer_| must be running iff + // |pending_transport_info_message_| exists. + DCHECK_EQ(pending_transport_info_message_ != nullptr, + transport_info_timer_.IsRunning()); + + if (!pending_transport_info_message_) { + pending_transport_info_message_.reset( + new XmlElement(QName(kTransportNamespace, "transport"), true)); + + // Delay sending the new candidates in case we get more candidates + // that we can send in one message. + transport_info_timer_.Start( + FROM_HERE, base::TimeDelta::FromMilliseconds(kTransportInfoSendDelayMs), + this, &WebrtcTransport::SendTransportInfo); + } +} + +void WebrtcTransport::SendTransportInfo() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(pending_transport_info_message_); + + event_handler_->OnOutgoingTransportInfo( + pending_transport_info_message_.Pass()); + pending_transport_info_message_.reset(); +} + +void WebrtcTransport::AddPendingCandidatesIfPossible() { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (peer_connection_->signaling_state() == + webrtc::PeerConnectionInterface::kStable) { + for (auto candidate : pending_incoming_candidates_) { + if (!peer_connection_->AddIceCandidate(candidate)) { + LOG(ERROR) << "Failed to add incoming candidate"; + Close(INCOMPATIBLE_PROTOCOL); + return; + } + } + pending_incoming_candidates_.clear(); + } +} + +WebrtcTransportFactory::WebrtcTransportFactory( + SignalStrategy* signal_strategy, + rtc::scoped_refptr<webrtc::PortAllocatorFactoryInterface> + port_allocator_factory, + TransportRole role) + : signal_strategy_(signal_strategy), + port_allocator_factory_(port_allocator_factory), + role_(role), + worker_thread_("ChromotingWebrtcWorkerThread") { + worker_thread_.StartWithOptions( + base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); +} + +WebrtcTransportFactory::~WebrtcTransportFactory() {} + +scoped_ptr<Transport> WebrtcTransportFactory::CreateTransport() { + return make_scoped_ptr(new WebrtcTransport(port_allocator_factory_, role_, + worker_thread_.task_runner())); +} + +} // namespace protocol +} // namespace remoting diff --git a/remoting/protocol/webrtc_transport.h b/remoting/protocol/webrtc_transport.h new file mode 100644 index 0000000..cbc6458 --- /dev/null +++ b/remoting/protocol/webrtc_transport.h @@ -0,0 +1,124 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_PROTOCOL_WEBRTC_TRANSPORT_H_ +#define REMOTING_PROTOCOL_WEBRTC_TRANSPORT_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread.h" +#include "base/threading/thread_checker.h" +#include "base/timer/timer.h" +#include "remoting/protocol/transport.h" +#include "remoting/signaling/signal_strategy.h" +#include "third_party/libjingle/source/talk/app/webrtc/peerconnectioninterface.h" + +namespace webrtc { +class FakeAudioDeviceModule; +} // namespace webrtc + +namespace remoting { +namespace protocol { + +class WebrtcTransport : public Transport, + public webrtc::PeerConnectionObserver { + public: + WebrtcTransport( + rtc::scoped_refptr<webrtc::PortAllocatorFactoryInterface> + port_allocator_factory, + TransportRole role, + scoped_refptr<base::SingleThreadTaskRunner> worker_task_runner); + ~WebrtcTransport() override; + + // Transport interface. + void Start(EventHandler* event_handler, + Authenticator* authenticator) override; + bool ProcessTransportInfo(buzz::XmlElement* transport_info) override; + DatagramChannelFactory* GetDatagramChannelFactory() override; + StreamChannelFactory* GetStreamChannelFactory() override; + StreamChannelFactory* GetMultiplexedChannelFactory() override; + + private: + void DoStart(rtc::Thread* worker_thread); + void OnLocalSessionDescriptionCreated( + scoped_ptr<webrtc::SessionDescriptionInterface> description, + const std::string& error); + void OnLocalDescriptionSet(bool success, const std::string& error); + void OnRemoteDescriptionSet(bool success, const std::string& error); + + // webrtc::PeerConnectionObserver interface. + void OnSignalingChange( + webrtc::PeerConnectionInterface::SignalingState new_state) override; + void OnAddStream(webrtc::MediaStreamInterface* stream) override; + void OnRemoveStream(webrtc::MediaStreamInterface* stream) override; + void OnDataChannel(webrtc::DataChannelInterface* data_channel) override; + void OnRenegotiationNeeded() override; + void OnIceConnectionChange( + webrtc::PeerConnectionInterface::IceConnectionState new_state) override; + void OnIceGatheringChange( + webrtc::PeerConnectionInterface::IceGatheringState new_state) override; + void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override; + + void EnsurePendingTransportInfoMessage(); + void SendTransportInfo(); + void AddPendingCandidatesIfPossible(); + + void Close(ErrorCode error); + + base::ThreadChecker thread_checker_; + + rtc::scoped_refptr<webrtc::PortAllocatorFactoryInterface> + port_allocator_factory_; + TransportRole role_; + EventHandler* event_handler_ = nullptr; + scoped_refptr<base::SingleThreadTaskRunner> worker_task_runner_; + + scoped_ptr<webrtc::FakeAudioDeviceModule> fake_audio_device_module_; + + rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> + peer_connection_factory_; + rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_; + + scoped_ptr<buzz::XmlElement> pending_transport_info_message_; + base::OneShotTimer transport_info_timer_; + + ScopedVector<webrtc::IceCandidateInterface> pending_incoming_candidates_; + + std::list<rtc::scoped_refptr<webrtc::MediaStreamInterface>> + unclaimed_streams_; + + base::WeakPtrFactory<WebrtcTransport> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(WebrtcTransport); +}; + +class WebrtcTransportFactory : public TransportFactory { + public: + WebrtcTransportFactory( + SignalStrategy* signal_strategy, + rtc::scoped_refptr<webrtc::PortAllocatorFactoryInterface> + port_allocator_factory, + TransportRole role); + ~WebrtcTransportFactory() override; + + // TransportFactory interface. + scoped_ptr<Transport> CreateTransport() override; + + private: + SignalStrategy* signal_strategy_; + rtc::scoped_refptr<webrtc::PortAllocatorFactoryInterface> + port_allocator_factory_; + TransportRole role_; + + base::Thread worker_thread_; + + DISALLOW_COPY_AND_ASSIGN(WebrtcTransportFactory); +}; + +} // namespace protocol +} // namespace remoting + +#endif // REMOTING_PROTOCOL_WEBRTC_TRANSPORT_H_ diff --git a/remoting/protocol/webrtc_transport_unittest.cc b/remoting/protocol/webrtc_transport_unittest.cc new file mode 100644 index 0000000..8f21cca --- /dev/null +++ b/remoting/protocol/webrtc_transport_unittest.cc @@ -0,0 +1,171 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/protocol/webrtc_transport.h" + +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "jingle/glue/thread_wrapper.h" +#include "net/url_request/url_request_context_getter.h" +#include "remoting/protocol/chromium_port_allocator_factory.h" +#include "remoting/protocol/fake_authenticator.h" +#include "remoting/protocol/network_settings.h" +#include "remoting/signaling/fake_signal_strategy.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/webrtc/libjingle/xmllite/xmlelement.h" + +namespace remoting { +namespace protocol { + +namespace { + +const char kTestJid[] = "client@gmail.com/321"; + +class TestTransportEventHandler : public Transport::EventHandler { + public: + typedef base::Callback<void(scoped_ptr<buzz::XmlElement> message)> + TransportInfoCallback; + typedef base::Callback<void(ErrorCode error)> ErrorCallback; + + TestTransportEventHandler() {} + ~TestTransportEventHandler() {} + + // Both callback must be set before the test handler is passed to a Transport + // object. + void set_transport_info_callback(const TransportInfoCallback& callback) { + transport_info_callback_ = callback; + } + void set_connected_callback(const base::Closure& callback) { + connected_callback_ = callback; + } + void set_error_callback(const ErrorCallback& callback) { + error_callback_ = callback; + } + + // Transport::EventHandler interface. + void OnOutgoingTransportInfo(scoped_ptr<buzz::XmlElement> message) override { + transport_info_callback_.Run(message.Pass()); + } + void OnTransportRouteChange(const std::string& channel_name, + const TransportRoute& route) override {} + void OnTransportConnected() override { + connected_callback_.Run(); + } + void OnTransportError(ErrorCode error) override { + error_callback_.Run(error); + } + + private: + TransportInfoCallback transport_info_callback_; + base::Closure connected_callback_; + ErrorCallback error_callback_; + + DISALLOW_COPY_AND_ASSIGN(TestTransportEventHandler); +}; + +} // namespace + +class WebrtcTransportTest : public testing::Test { + public: + WebrtcTransportTest() { + jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop(); + network_settings_ = + NetworkSettings(NetworkSettings::NAT_TRAVERSAL_OUTGOING); + } + + void ProcessTransportInfo(scoped_ptr<Transport>* target_transport, + scoped_ptr<buzz::XmlElement> transport_info) { + ASSERT_TRUE(target_transport); + EXPECT_TRUE((*target_transport) + ->ProcessTransportInfo(transport_info.get())); + } + + protected: + void WaitUntilConnected() { + host_event_handler_.set_error_callback(base::Bind( + &WebrtcTransportTest::QuitRunLoopOnError, base::Unretained(this))); + client_event_handler_.set_error_callback(base::Bind( + &WebrtcTransportTest::QuitRunLoopOnError, base::Unretained(this))); + + int counter = 2; + host_event_handler_.set_connected_callback( + base::Bind(&WebrtcTransportTest::QuitRunLoopOnCounter, + base::Unretained(this), &counter)); + client_event_handler_.set_connected_callback( + base::Bind(&WebrtcTransportTest::QuitRunLoopOnCounter, + base::Unretained(this), &counter)); + + run_loop_.reset(new base::RunLoop()); + run_loop_->Run(); + + EXPECT_EQ(OK, error_); + } + + void QuitRunLoopOnError(ErrorCode error) { + error_ = error; + run_loop_->Quit(); + } + + void QuitRunLoopOnCounter(int* counter) { + --(*counter); + if (*counter == 0) + run_loop_->Quit(); + } + + protected: + base::MessageLoopForIO message_loop_; + scoped_ptr<base::RunLoop> run_loop_; + + NetworkSettings network_settings_; + + scoped_ptr< FakeSignalStrategy> signal_strategy_; + + scoped_ptr<WebrtcTransportFactory> host_transport_factory_; + scoped_ptr<Transport> host_transport_; + TestTransportEventHandler host_event_handler_; + scoped_ptr<FakeAuthenticator> host_authenticator_; + + scoped_ptr<WebrtcTransportFactory> client_transport_factory_; + scoped_ptr<Transport> client_transport_; + TestTransportEventHandler client_event_handler_; + scoped_ptr<FakeAuthenticator> client_authenticator_; + + ErrorCode error_ = OK; +}; + +TEST_F(WebrtcTransportTest, Connects) { + signal_strategy_.reset(new FakeSignalStrategy(kTestJid)); + + host_transport_factory_.reset(new WebrtcTransportFactory( + signal_strategy_.get(), + ChromiumPortAllocatorFactory::Create(network_settings_, nullptr), + TransportRole::SERVER)); + host_transport_ = host_transport_factory_->CreateTransport(); + host_authenticator_.reset(new FakeAuthenticator( + FakeAuthenticator::HOST, 0, FakeAuthenticator::ACCEPT, false)); + + client_transport_factory_.reset(new WebrtcTransportFactory( + signal_strategy_.get(), + ChromiumPortAllocatorFactory::Create(network_settings_, nullptr), + TransportRole::CLIENT)); + client_transport_ = client_transport_factory_->CreateTransport(); + host_authenticator_.reset(new FakeAuthenticator( + FakeAuthenticator::CLIENT, 0, FakeAuthenticator::ACCEPT, false)); + + // Connect signaling between the two WebrtcTransport objects. + host_event_handler_.set_transport_info_callback( + base::Bind(&WebrtcTransportTest::ProcessTransportInfo, + base::Unretained(this), &client_transport_)); + client_event_handler_.set_transport_info_callback( + base::Bind(&WebrtcTransportTest::ProcessTransportInfo, + base::Unretained(this), &host_transport_)); + + host_transport_->Start(&host_event_handler_, host_authenticator_.get()); + client_transport_->Start(&client_event_handler_, client_authenticator_.get()); + + WaitUntilConnected(); +} + +} // namespace protocol +} // namespace remoting diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index 7d7b992..005823c 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -237,6 +237,8 @@ '../net/net.gyp:net', '../third_party/expat/expat.gyp:expat', '../third_party/libjingle/libjingle.gyp:libjingle', + '../third_party/libjingle/libjingle.gyp:libjingle_webrtc', + '../third_party/libjingle/libjingle.gyp:libpeerconnection', 'remoting_base', ], 'export_dependent_settings': [ diff --git a/remoting/remoting_host.gypi b/remoting/remoting_host.gypi index 99794b22..4ec28b1 100644 --- a/remoting/remoting_host.gypi +++ b/remoting/remoting_host.gypi @@ -182,7 +182,6 @@ ['enable_webrtc==1', { 'dependencies': [ '../third_party/webrtc/modules/modules.gyp:desktop_capture', - '../third_party/libjingle/libjingle.gyp:libpeerconnection', ], 'sources': [ '<@(remoting_cast_sources)', diff --git a/remoting/remoting_nacl.gyp b/remoting/remoting_nacl.gyp index d5cb7be..7ea8e1a 100644 --- a/remoting/remoting_nacl.gyp +++ b/remoting/remoting_nacl.gyp @@ -125,6 +125,7 @@ 'base/url_request_context_getter.cc', 'protocol/chromium_socket_factory.cc', 'protocol/chromium_port_allocator_factory.cc', + 'protocol/webrtc_transport.cc', ], # Include normalizing_input_filter_*.cc excluded by the filename diff --git a/remoting/remoting_srcs.gypi b/remoting/remoting_srcs.gypi index 04d3738..928a1b8 100644 --- a/remoting/remoting_srcs.gypi +++ b/remoting/remoting_srcs.gypi @@ -207,6 +207,8 @@ 'protocol/v2_authenticator.cc', 'protocol/v2_authenticator.h', 'protocol/video_stub.h', + 'protocol/webrtc_transport.cc', + 'protocol/webrtc_transport.h', ], 'remoting_signaling_sources': [ diff --git a/remoting/remoting_test.gypi b/remoting/remoting_test.gypi index 6702053..33e60ac 100644 --- a/remoting/remoting_test.gypi +++ b/remoting/remoting_test.gypi @@ -330,6 +330,7 @@ 'protocol/ssl_hmac_channel_authenticator_unittest.cc', 'protocol/third_party_authenticator_unittest.cc', 'protocol/v2_authenticator_unittest.cc', + 'protocol/webrtc_transport_unittest.cc', 'signaling/iq_sender_unittest.cc', 'signaling/jid_util_unittest.cc', 'signaling/log_to_server_unittest.cc', |