summaryrefslogtreecommitdiffstats
path: root/remoting
diff options
context:
space:
mode:
authorsergeyu <sergeyu@chromium.org>2015-11-18 18:24:01 -0800
committerCommit bot <commit-bot@chromium.org>2015-11-19 02:24:50 +0000
commitc661e6bbc53491be1e227b92ca39dfab65107039 (patch)
tree619812ff866d1f03af049af1b93952773ed0438a /remoting
parent36fbcc9a3108e3a76e4304fe44a79d5a86134bd1 (diff)
downloadchromium_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.gn9
-rw-r--r--remoting/protocol/BUILD.gn14
-rw-r--r--remoting/protocol/jingle_session.cc18
-rw-r--r--remoting/protocol/jingle_session.h1
-rw-r--r--remoting/protocol/transport.h3
-rw-r--r--remoting/protocol/webrtc_transport.cc497
-rw-r--r--remoting/protocol/webrtc_transport.h124
-rw-r--r--remoting/protocol/webrtc_transport_unittest.cc171
-rw-r--r--remoting/remoting.gyp2
-rw-r--r--remoting/remoting_host.gypi1
-rw-r--r--remoting/remoting_nacl.gyp1
-rw-r--r--remoting/remoting_srcs.gypi2
-rw-r--r--remoting/remoting_test.gypi1
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',