Linux: Depend on liberation-fonts package for RPMs.
[chromium-blink-merge.git] / remoting / client / plugin / chromoting_instance.cc
blob05a153113b1af945115e125401e9c3a48055583c
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "remoting/client/plugin/chromoting_instance.h"
7 #include <string>
8 #include <vector>
10 #include <nacl_io/nacl_io.h>
11 #include <sys/mount.h>
13 #include "base/bind.h"
14 #include "base/callback.h"
15 #include "base/callback_helpers.h"
16 #include "base/json/json_reader.h"
17 #include "base/json/json_writer.h"
18 #include "base/lazy_instance.h"
19 #include "base/logging.h"
20 #include "base/strings/string_number_conversions.h"
21 #include "base/strings/string_split.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/synchronization/lock.h"
24 #include "base/threading/platform_thread.h"
25 #include "base/threading/thread.h"
26 #include "base/values.h"
27 #include "crypto/random.h"
28 #include "jingle/glue/thread_wrapper.h"
29 #include "net/socket/ssl_server_socket.h"
30 #include "ppapi/cpp/completion_callback.h"
31 #include "ppapi/cpp/dev/url_util_dev.h"
32 #include "ppapi/cpp/image_data.h"
33 #include "ppapi/cpp/input_event.h"
34 #include "ppapi/cpp/private/uma_private.h"
35 #include "ppapi/cpp/rect.h"
36 #include "ppapi/cpp/var_array_buffer.h"
37 #include "ppapi/cpp/var_dictionary.h"
38 #include "remoting/base/constants.h"
39 #include "remoting/base/util.h"
40 #include "remoting/client/chromoting_client.h"
41 #include "remoting/client/normalizing_input_filter_cros.h"
42 #include "remoting/client/normalizing_input_filter_mac.h"
43 #include "remoting/client/plugin/delegating_signal_strategy.h"
44 #include "remoting/client/plugin/pepper_audio_player.h"
45 #include "remoting/client/plugin/pepper_mouse_locker.h"
46 #include "remoting/client/plugin/pepper_port_allocator.h"
47 #include "remoting/client/plugin/pepper_video_renderer_2d.h"
48 #include "remoting/client/plugin/pepper_video_renderer_3d.h"
49 #include "remoting/client/software_video_renderer.h"
50 #include "remoting/client/token_fetcher_proxy.h"
51 #include "remoting/protocol/connection_to_host.h"
52 #include "remoting/protocol/host_stub.h"
53 #include "remoting/protocol/libjingle_transport_factory.h"
54 #include "url/gurl.h"
56 namespace remoting {
58 namespace {
60 // Default DPI to assume for old clients that use notifyClientResolution.
61 const int kDefaultDPI = 96;
63 // Size of the random seed blob used to initialize RNG in libjingle. OpenSSL
64 // needs at least 32 bytes of entropy (see
65 // http://wiki.openssl.org/index.php/Random_Numbers), but stores 1039 bytes of
66 // state, so we initialize it with 1k or random data.
67 const int kRandomSeedSize = 1024;
69 // The connection times and duration values are stored in UMA custom-time
70 // histograms, that are log-scaled by default. The histogram specifications are
71 // based off values seen over a recent 7-day period.
72 // The connection times histograms are in milliseconds and the connection
73 // duration histograms are in minutes.
74 const char kTimeToAuthenticateHistogram[] =
75 "Chromoting.Connections.Times.ToAuthenticate";
76 const char kTimeToConnectHistogram[] = "Chromoting.Connections.Times.ToConnect";
77 const char kClosedSessionDurationHistogram[] =
78 "Chromoting.Connections.Durations.Closed";
79 const char kFailedSessionDurationHistogram[] =
80 "Chromoting.Connections.Durations.Failed";
81 const int kConnectionTimesHistogramMinMs = 1;
82 const int kConnectionTimesHistogramMaxMs = 30000;
83 const int kConnectionTimesHistogramBuckets = 50;
84 const int kConnectionDurationHistogramMinMinutes = 1;
85 const int kConnectionDurationHistogramMaxMinutes = 24 * 60;
86 const int kConnectionDurationHistogramBuckets = 50;
88 // Update perf stats in the UI every second.
89 const int kUIStatsUpdatePeriodSeconds = 1;
91 // TODO(sergeyu): Ideally we should just pass ErrorCode to the webapp
92 // and let it handle it, but it would be hard to fix it now because
93 // client plugin and webapp versions may not be in sync. It should be
94 // easy to do after we are finished moving the client plugin to NaCl.
95 std::string ConnectionErrorToString(protocol::ErrorCode error) {
96 // Values returned by this function must match the
97 // remoting.ClientSession.Error enum in JS code.
98 switch (error) {
99 case protocol::OK:
100 return "NONE";
102 case protocol::PEER_IS_OFFLINE:
103 return "HOST_IS_OFFLINE";
105 case protocol::SESSION_REJECTED:
106 case protocol::AUTHENTICATION_FAILED:
107 return "SESSION_REJECTED";
109 case protocol::INCOMPATIBLE_PROTOCOL:
110 return "INCOMPATIBLE_PROTOCOL";
112 case protocol::HOST_OVERLOAD:
113 return "HOST_OVERLOAD";
115 case protocol::CHANNEL_CONNECTION_ERROR:
116 case protocol::SIGNALING_ERROR:
117 case protocol::SIGNALING_TIMEOUT:
118 case protocol::UNKNOWN_ERROR:
119 return "NETWORK_FAILURE";
121 DLOG(FATAL) << "Unknown error code" << error;
122 return std::string();
125 PP_Instance g_logging_instance = 0;
126 base::LazyInstance<base::Lock>::Leaky g_logging_lock =
127 LAZY_INSTANCE_INITIALIZER;
129 } // namespace
131 ChromotingInstance::ChromotingInstance(PP_Instance pp_instance)
132 : pp::Instance(pp_instance),
133 initialized_(false),
134 plugin_task_runner_(new PluginThreadTaskRunner(&plugin_thread_delegate_)),
135 context_(plugin_task_runner_.get()),
136 input_tracker_(&mouse_input_filter_),
137 touch_input_scaler_(&input_tracker_),
138 key_mapper_(&touch_input_scaler_),
139 input_handler_(&input_tracker_),
140 cursor_setter_(this),
141 empty_cursor_filter_(&cursor_setter_),
142 text_input_controller_(this),
143 use_async_pin_dialog_(false),
144 weak_factory_(this) {
145 // In NaCl global resources need to be initialized differently because they
146 // are not shared with Chrome.
147 thread_task_runner_handle_.reset(
148 new base::ThreadTaskRunnerHandle(plugin_task_runner_));
149 thread_wrapper_ =
150 jingle_glue::JingleThreadWrapper::WrapTaskRunner(plugin_task_runner_);
152 // Register a global log handler.
153 ChromotingInstance::RegisterLogMessageHandler();
155 nacl_io_init_ppapi(pp_instance, pp::Module::Get()->get_browser_interface());
156 mount("", "/etc", "memfs", 0, "");
157 mount("", "/usr", "memfs", 0, "");
159 // Register for mouse, wheel and keyboard events.
160 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL);
161 RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
163 // Disable the client-side IME in Chrome.
164 text_input_controller_.SetTextInputType(PP_TEXTINPUT_TYPE_NONE);
166 // Resister this instance to handle debug log messsages.
167 RegisterLoggingInstance();
169 // Initialize random seed for libjingle. It's necessary only with OpenSSL.
170 char random_seed[kRandomSeedSize];
171 crypto::RandBytes(random_seed, sizeof(random_seed));
172 rtc::InitRandom(random_seed, sizeof(random_seed));
174 // Send hello message.
175 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
176 PostLegacyJsonMessage("hello", data.Pass());
179 ChromotingInstance::~ChromotingInstance() {
180 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
182 // Disconnect the client.
183 Disconnect();
185 // Unregister this instance so that debug log messages will no longer be sent
186 // to it. This will stop all logging in all Chromoting instances.
187 UnregisterLoggingInstance();
189 plugin_task_runner_->Quit();
191 // Ensure that nothing touches the plugin thread delegate after this point.
192 plugin_task_runner_->DetachAndRunShutdownLoop();
194 // Stopping the context shuts down all chromoting threads.
195 context_.Stop();
198 bool ChromotingInstance::Init(uint32_t argc,
199 const char* argn[],
200 const char* argv[]) {
201 CHECK(!initialized_);
202 initialized_ = true;
204 VLOG(1) << "Started ChromotingInstance::Init";
206 // Start all the threads.
207 context_.Start();
209 return true;
212 void ChromotingInstance::HandleMessage(const pp::Var& message) {
213 if (!message.is_string()) {
214 LOG(ERROR) << "Received a message that is not a string.";
215 return;
218 scoped_ptr<base::Value> json = base::JSONReader::Read(
219 message.AsString(), base::JSON_ALLOW_TRAILING_COMMAS);
220 base::DictionaryValue* message_dict = nullptr;
221 std::string method;
222 base::DictionaryValue* data = nullptr;
223 if (!json.get() ||
224 !json->GetAsDictionary(&message_dict) ||
225 !message_dict->GetString("method", &method) ||
226 !message_dict->GetDictionary("data", &data)) {
227 LOG(ERROR) << "Received invalid message:" << message.AsString();
228 return;
231 if (method == "connect") {
232 HandleConnect(*data);
233 } else if (method == "disconnect") {
234 HandleDisconnect(*data);
235 } else if (method == "incomingIq") {
236 HandleOnIncomingIq(*data);
237 } else if (method == "releaseAllKeys") {
238 HandleReleaseAllKeys(*data);
239 } else if (method == "injectKeyEvent") {
240 HandleInjectKeyEvent(*data);
241 } else if (method == "remapKey") {
242 HandleRemapKey(*data);
243 } else if (method == "trapKey") {
244 HandleTrapKey(*data);
245 } else if (method == "sendClipboardItem") {
246 HandleSendClipboardItem(*data);
247 } else if (method == "notifyClientResolution") {
248 HandleNotifyClientResolution(*data);
249 } else if (method == "videoControl") {
250 HandleVideoControl(*data);
251 } else if (method == "pauseAudio") {
252 HandlePauseAudio(*data);
253 } else if (method == "useAsyncPinDialog") {
254 use_async_pin_dialog_ = true;
255 } else if (method == "onPinFetched") {
256 HandleOnPinFetched(*data);
257 } else if (method == "onThirdPartyTokenFetched") {
258 HandleOnThirdPartyTokenFetched(*data);
259 } else if (method == "requestPairing") {
260 HandleRequestPairing(*data);
261 } else if (method == "extensionMessage") {
262 HandleExtensionMessage(*data);
263 } else if (method == "allowMouseLock") {
264 HandleAllowMouseLockMessage();
265 } else if (method == "sendMouseInputWhenUnfocused") {
266 HandleSendMouseInputWhenUnfocused();
267 } else if (method == "delegateLargeCursors") {
268 HandleDelegateLargeCursors();
269 } else if (method == "enableDebugRegion") {
270 HandleEnableDebugRegion(*data);
271 } else if (method == "enableTouchEvents") {
272 HandleEnableTouchEvents(*data);
276 void ChromotingInstance::DidChangeFocus(bool has_focus) {
277 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
279 if (!IsConnected())
280 return;
282 input_handler_.DidChangeFocus(has_focus);
283 if (mouse_locker_)
284 mouse_locker_->DidChangeFocus(has_focus);
287 void ChromotingInstance::DidChangeView(const pp::View& view) {
288 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
290 plugin_view_ = view;
291 webrtc::DesktopSize size(
292 webrtc::DesktopSize(view.GetRect().width(), view.GetRect().height()));
293 mouse_input_filter_.set_input_size(size);
294 touch_input_scaler_.set_input_size(size);
296 if (video_renderer_)
297 video_renderer_->OnViewChanged(view);
300 bool ChromotingInstance::HandleInputEvent(const pp::InputEvent& event) {
301 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
303 if (!IsConnected())
304 return false;
306 return input_handler_.HandleInputEvent(event);
309 void ChromotingInstance::OnVideoDecodeError() {
310 Disconnect();
312 // Assume that the decoder failure was caused by the host not encoding video
313 // correctly and report it as a protocol error.
314 // TODO(sergeyu): Consider using a different error code in case the decoder
315 // error was caused by some other problem.
316 OnConnectionState(protocol::ConnectionToHost::FAILED,
317 protocol::INCOMPATIBLE_PROTOCOL);
320 void ChromotingInstance::OnVideoFirstFrameReceived() {
321 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
322 PostLegacyJsonMessage("onFirstFrameReceived", data.Pass());
325 void ChromotingInstance::OnVideoSize(const webrtc::DesktopSize& size,
326 const webrtc::DesktopVector& dpi) {
327 mouse_input_filter_.set_output_size(size);
328 touch_input_scaler_.set_output_size(size);
330 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
331 data->SetInteger("width", size.width());
332 data->SetInteger("height", size.height());
333 if (dpi.x())
334 data->SetInteger("x_dpi", dpi.x());
335 if (dpi.y())
336 data->SetInteger("y_dpi", dpi.y());
337 PostLegacyJsonMessage("onDesktopSize", data.Pass());
340 void ChromotingInstance::OnVideoShape(const webrtc::DesktopRegion* shape) {
341 if ((shape && desktop_shape_ && shape->Equals(*desktop_shape_)) ||
342 (!shape && !desktop_shape_)) {
343 return;
346 scoped_ptr<base::DictionaryValue> shape_message(new base::DictionaryValue());
347 if (shape) {
348 desktop_shape_ = make_scoped_ptr(new webrtc::DesktopRegion(*shape));
349 scoped_ptr<base::ListValue> rects_value(new base::ListValue());
350 for (webrtc::DesktopRegion::Iterator i(*shape); !i.IsAtEnd(); i.Advance()) {
351 const webrtc::DesktopRect& rect = i.rect();
352 scoped_ptr<base::ListValue> rect_value(new base::ListValue());
353 rect_value->AppendInteger(rect.left());
354 rect_value->AppendInteger(rect.top());
355 rect_value->AppendInteger(rect.width());
356 rect_value->AppendInteger(rect.height());
357 rects_value->Append(rect_value.release());
359 shape_message->Set("rects", rects_value.release());
362 PostLegacyJsonMessage("onDesktopShape", shape_message.Pass());
365 void ChromotingInstance::OnVideoFrameDirtyRegion(
366 const webrtc::DesktopRegion& dirty_region) {
367 scoped_ptr<base::ListValue> rects_value(new base::ListValue());
368 for (webrtc::DesktopRegion::Iterator i(dirty_region); !i.IsAtEnd();
369 i.Advance()) {
370 const webrtc::DesktopRect& rect = i.rect();
371 scoped_ptr<base::ListValue> rect_value(new base::ListValue());
372 rect_value->AppendInteger(rect.left());
373 rect_value->AppendInteger(rect.top());
374 rect_value->AppendInteger(rect.width());
375 rect_value->AppendInteger(rect.height());
376 rects_value->Append(rect_value.release());
379 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
380 data->Set("rects", rects_value.release());
381 PostLegacyJsonMessage("onDebugRegion", data.Pass());
384 void ChromotingInstance::OnConnectionState(
385 protocol::ConnectionToHost::State state,
386 protocol::ErrorCode error) {
387 pp::UMAPrivate uma(this);
389 switch (state) {
390 case protocol::ConnectionToHost::INITIALIZING:
391 NOTREACHED();
392 break;
393 case protocol::ConnectionToHost::CONNECTING:
394 connection_started_time = base::TimeTicks::Now();
395 break;
396 case protocol::ConnectionToHost::AUTHENTICATED:
397 connection_authenticated_time_ = base::TimeTicks::Now();
398 uma.HistogramCustomTimes(
399 kTimeToAuthenticateHistogram,
400 (connection_authenticated_time_ - connection_started_time)
401 .InMilliseconds(),
402 kConnectionTimesHistogramMinMs, kConnectionTimesHistogramMaxMs,
403 kConnectionTimesHistogramBuckets);
404 break;
405 case protocol::ConnectionToHost::CONNECTED:
406 connection_connected_time_ = base::TimeTicks::Now();
407 uma.HistogramCustomTimes(
408 kTimeToConnectHistogram,
409 (connection_connected_time_ - connection_authenticated_time_)
410 .InMilliseconds(),
411 kConnectionTimesHistogramMinMs, kConnectionTimesHistogramMaxMs,
412 kConnectionTimesHistogramBuckets);
413 break;
414 case protocol::ConnectionToHost::CLOSED:
415 if (!connection_connected_time_.is_null()) {
416 uma.HistogramCustomTimes(
417 kClosedSessionDurationHistogram,
418 (base::TimeTicks::Now() - connection_connected_time_)
419 .InMilliseconds(),
420 kConnectionDurationHistogramMinMinutes,
421 kConnectionDurationHistogramMaxMinutes,
422 kConnectionDurationHistogramBuckets);
424 break;
425 case protocol::ConnectionToHost::FAILED:
426 if (!connection_connected_time_.is_null()) {
427 uma.HistogramCustomTimes(
428 kFailedSessionDurationHistogram,
429 (base::TimeTicks::Now() - connection_connected_time_)
430 .InMilliseconds(),
431 kConnectionDurationHistogramMinMinutes,
432 kConnectionDurationHistogramMaxMinutes,
433 kConnectionDurationHistogramBuckets);
435 break;
438 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
439 data->SetString("state", protocol::ConnectionToHost::StateToString(state));
440 data->SetString("error", ConnectionErrorToString(error));
441 PostLegacyJsonMessage("onConnectionStatus", data.Pass());
444 void ChromotingInstance::FetchThirdPartyToken(
445 const GURL& token_url,
446 const std::string& host_public_key,
447 const std::string& scope,
448 base::WeakPtr<TokenFetcherProxy> token_fetcher_proxy) {
449 // Once the Session object calls this function, it won't continue the
450 // authentication until the callback is called (or connection is canceled).
451 // So, it's impossible to reach this with a callback already registered.
452 DCHECK(!token_fetcher_proxy_.get());
453 token_fetcher_proxy_ = token_fetcher_proxy;
454 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
455 data->SetString("tokenUrl", token_url.spec());
456 data->SetString("hostPublicKey", host_public_key);
457 data->SetString("scope", scope);
458 PostLegacyJsonMessage("fetchThirdPartyToken", data.Pass());
461 void ChromotingInstance::OnConnectionReady(bool ready) {
462 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
463 data->SetBoolean("ready", ready);
464 PostLegacyJsonMessage("onConnectionReady", data.Pass());
467 void ChromotingInstance::OnRouteChanged(const std::string& channel_name,
468 const protocol::TransportRoute& route) {
469 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
470 data->SetString("channel", channel_name);
471 data->SetString("connectionType",
472 protocol::TransportRoute::GetTypeString(route.type));
473 PostLegacyJsonMessage("onRouteChanged", data.Pass());
476 void ChromotingInstance::SetCapabilities(const std::string& capabilities) {
477 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
478 data->SetString("capabilities", capabilities);
479 PostLegacyJsonMessage("setCapabilities", data.Pass());
482 void ChromotingInstance::SetPairingResponse(
483 const protocol::PairingResponse& pairing_response) {
484 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
485 data->SetString("clientId", pairing_response.client_id());
486 data->SetString("sharedSecret", pairing_response.shared_secret());
487 PostLegacyJsonMessage("pairingResponse", data.Pass());
490 void ChromotingInstance::DeliverHostMessage(
491 const protocol::ExtensionMessage& message) {
492 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
493 data->SetString("type", message.type());
494 data->SetString("data", message.data());
495 PostLegacyJsonMessage("extensionMessage", data.Pass());
498 void ChromotingInstance::FetchSecretFromDialog(
499 bool pairing_supported,
500 const protocol::SecretFetchedCallback& secret_fetched_callback) {
501 // Once the Session object calls this function, it won't continue the
502 // authentication until the callback is called (or connection is canceled).
503 // So, it's impossible to reach this with a callback already registered.
504 DCHECK(secret_fetched_callback_.is_null());
505 secret_fetched_callback_ = secret_fetched_callback;
506 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
507 data->SetBoolean("pairingSupported", pairing_supported);
508 PostLegacyJsonMessage("fetchPin", data.Pass());
511 void ChromotingInstance::FetchSecretFromString(
512 const std::string& shared_secret,
513 bool pairing_supported,
514 const protocol::SecretFetchedCallback& secret_fetched_callback) {
515 secret_fetched_callback.Run(shared_secret);
518 protocol::ClipboardStub* ChromotingInstance::GetClipboardStub() {
519 // TODO(sergeyu): Move clipboard handling to a separate class.
520 // crbug.com/138108
521 return this;
524 protocol::CursorShapeStub* ChromotingInstance::GetCursorShapeStub() {
525 return &empty_cursor_filter_;
528 void ChromotingInstance::InjectClipboardEvent(
529 const protocol::ClipboardEvent& event) {
530 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
531 data->SetString("mimeType", event.mime_type());
532 data->SetString("item", event.data());
533 PostLegacyJsonMessage("injectClipboardItem", data.Pass());
536 void ChromotingInstance::SetCursorShape(
537 const protocol::CursorShapeInfo& cursor_shape) {
538 // If the delegated cursor is empty then stop rendering a DOM cursor.
539 if (IsCursorShapeEmpty(cursor_shape)) {
540 PostChromotingMessage("unsetCursorShape", pp::VarDictionary());
541 return;
544 // Cursor is not empty, so pass it to JS to render.
545 const int kBytesPerPixel = sizeof(uint32_t);
546 const size_t buffer_size =
547 cursor_shape.height() * cursor_shape.width() * kBytesPerPixel;
549 pp::VarArrayBuffer array_buffer(buffer_size);
550 void* dst = array_buffer.Map();
551 memcpy(dst, cursor_shape.data().data(), buffer_size);
552 array_buffer.Unmap();
554 pp::VarDictionary dictionary;
555 dictionary.Set(pp::Var("width"), cursor_shape.width());
556 dictionary.Set(pp::Var("height"), cursor_shape.height());
557 dictionary.Set(pp::Var("hotspotX"), cursor_shape.hotspot_x());
558 dictionary.Set(pp::Var("hotspotY"), cursor_shape.hotspot_y());
559 dictionary.Set(pp::Var("data"), array_buffer);
560 PostChromotingMessage("setCursorShape", dictionary);
563 void ChromotingInstance::HandleConnect(const base::DictionaryValue& data) {
564 std::string local_jid;
565 std::string host_jid;
566 std::string host_public_key;
567 std::string authentication_tag;
568 if (!data.GetString("hostJid", &host_jid) ||
569 !data.GetString("hostPublicKey", &host_public_key) ||
570 !data.GetString("localJid", &local_jid) ||
571 !data.GetString("authenticationTag", &authentication_tag)) {
572 LOG(ERROR) << "Invalid connect() data.";
573 return;
576 std::string client_pairing_id;
577 data.GetString("clientPairingId", &client_pairing_id);
578 std::string client_paired_secret;
579 data.GetString("clientPairedSecret", &client_paired_secret);
581 protocol::FetchSecretCallback fetch_secret_callback;
582 if (use_async_pin_dialog_) {
583 fetch_secret_callback = base::Bind(
584 &ChromotingInstance::FetchSecretFromDialog, weak_factory_.GetWeakPtr());
585 } else {
586 std::string shared_secret;
587 if (!data.GetString("sharedSecret", &shared_secret)) {
588 LOG(ERROR) << "sharedSecret not specified in connect().";
589 return;
591 fetch_secret_callback =
592 base::Bind(&ChromotingInstance::FetchSecretFromString, shared_secret);
595 // Read the list of capabilities, if any.
596 std::string capabilities;
597 if (data.HasKey("capabilities")) {
598 if (!data.GetString("capabilities", &capabilities)) {
599 LOG(ERROR) << "Invalid connect() data.";
600 return;
604 // Read and parse list of experiments.
605 std::string experiments;
606 std::vector<std::string> experiments_list;
607 if (data.GetString("experiments", &experiments)) {
608 experiments_list = base::SplitString(
609 experiments, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
612 VLOG(0) << "Connecting to " << host_jid
613 << ". Local jid: " << local_jid << ".";
615 std::string key_filter;
616 if (!data.GetString("keyFilter", &key_filter)) {
617 NOTREACHED();
618 normalizing_input_filter_.reset(new protocol::InputFilter(&key_mapper_));
619 } else if (key_filter == "mac") {
620 normalizing_input_filter_.reset(
621 new NormalizingInputFilterMac(&key_mapper_));
622 } else if (key_filter == "cros") {
623 normalizing_input_filter_.reset(
624 new NormalizingInputFilterCros(&key_mapper_));
625 } else {
626 DCHECK(key_filter.empty());
627 normalizing_input_filter_.reset(new protocol::InputFilter(&key_mapper_));
629 input_handler_.set_input_stub(normalizing_input_filter_.get());
631 // Try initializing 3D video renderer.
632 video_renderer_.reset(new PepperVideoRenderer3D());
633 if (!video_renderer_->Initialize(this, context_, this, &perf_tracker_))
634 video_renderer_.reset();
636 // If we didn't initialize 3D renderer then use the 2D renderer.
637 if (!video_renderer_) {
638 LOG(WARNING)
639 << "Failed to initialize 3D renderer. Using 2D renderer instead.";
640 video_renderer_.reset(new PepperVideoRenderer2D());
641 if (!video_renderer_->Initialize(this, context_, this, &perf_tracker_))
642 video_renderer_.reset();
645 CHECK(video_renderer_);
647 perf_tracker_.SetUpdateUmaCallbacks(
648 base::Bind(&ChromotingInstance::UpdateUmaCustomHistogram,
649 weak_factory_.GetWeakPtr(), true),
650 base::Bind(&ChromotingInstance::UpdateUmaCustomHistogram,
651 weak_factory_.GetWeakPtr(), false),
652 base::Bind(&ChromotingInstance::UpdateUmaEnumHistogram,
653 weak_factory_.GetWeakPtr()));
655 if (!plugin_view_.is_null())
656 video_renderer_->OnViewChanged(plugin_view_);
658 scoped_ptr<AudioPlayer> audio_player(new PepperAudioPlayer(this));
659 client_.reset(new ChromotingClient(&context_, this, video_renderer_.get(),
660 audio_player.Pass()));
662 // Connect the input pipeline to the protocol stub & initialize components.
663 mouse_input_filter_.set_input_stub(client_->input_stub());
664 if (!plugin_view_.is_null()) {
665 webrtc::DesktopSize size(plugin_view_.GetRect().width(),
666 plugin_view_.GetRect().height());
667 mouse_input_filter_.set_input_size(size);
668 touch_input_scaler_.set_input_size(size);
671 // Setup the signal strategy.
672 signal_strategy_.reset(new DelegatingSignalStrategy(
673 local_jid, base::Bind(&ChromotingInstance::SendOutgoingIq,
674 weak_factory_.GetWeakPtr())));
676 // Create TransportFactory.
677 scoped_ptr<protocol::TransportFactory> transport_factory(
678 new protocol::LibjingleTransportFactory(
679 signal_strategy_.get(), PepperPortAllocator::Create(this).Pass(),
680 protocol::NetworkSettings(
681 protocol::NetworkSettings::NAT_TRAVERSAL_FULL),
682 protocol::TransportRole::CLIENT));
684 // Create Authenticator.
685 scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher>
686 token_fetcher(new TokenFetcherProxy(
687 base::Bind(&ChromotingInstance::FetchThirdPartyToken,
688 weak_factory_.GetWeakPtr()),
689 host_public_key));
691 std::vector<protocol::AuthenticationMethod> auth_methods;
692 auth_methods.push_back(protocol::AuthenticationMethod::ThirdParty());
693 auth_methods.push_back(protocol::AuthenticationMethod::Spake2Pair());
694 auth_methods.push_back(protocol::AuthenticationMethod::Spake2(
695 protocol::AuthenticationMethod::HMAC_SHA256));
696 auth_methods.push_back(protocol::AuthenticationMethod::Spake2(
697 protocol::AuthenticationMethod::NONE));
699 scoped_ptr<protocol::Authenticator> authenticator(
700 new protocol::NegotiatingClientAuthenticator(
701 client_pairing_id, client_paired_secret, authentication_tag,
702 fetch_secret_callback, token_fetcher.Pass(), auth_methods));
704 scoped_ptr<protocol::CandidateSessionConfig> config =
705 protocol::CandidateSessionConfig::CreateDefault();
706 if (std::find(experiments_list.begin(), experiments_list.end(), "vp9") !=
707 experiments_list.end()) {
708 config->set_vp9_experiment_enabled(true);
710 if (std::find(experiments_list.begin(), experiments_list.end(), "quic") !=
711 experiments_list.end()) {
712 config->PreferTransport(protocol::ChannelConfig::TRANSPORT_QUIC_STREAM);
714 client_->set_protocol_config(config.Pass());
716 // Kick off the connection.
717 client_->Start(signal_strategy_.get(), authenticator.Pass(),
718 transport_factory.Pass(), host_jid, capabilities);
720 // Start timer that periodically sends perf stats.
721 stats_update_timer_.Start(
722 FROM_HERE, base::TimeDelta::FromSeconds(kUIStatsUpdatePeriodSeconds),
723 base::Bind(&ChromotingInstance::UpdatePerfStatsInUI,
724 base::Unretained(this)));
727 void ChromotingInstance::HandleDisconnect(const base::DictionaryValue& data) {
728 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
729 Disconnect();
732 void ChromotingInstance::HandleOnIncomingIq(const base::DictionaryValue& data) {
733 std::string iq;
734 if (!data.GetString("iq", &iq)) {
735 LOG(ERROR) << "Invalid incomingIq() data.";
736 return;
739 // Just ignore the message if it's received before Connect() is called. It's
740 // likely to be a leftover from a previous session, so it's safe to ignore it.
741 if (signal_strategy_)
742 signal_strategy_->OnIncomingMessage(iq);
745 void ChromotingInstance::HandleReleaseAllKeys(
746 const base::DictionaryValue& data) {
747 if (IsConnected())
748 input_tracker_.ReleaseAll();
751 void ChromotingInstance::HandleInjectKeyEvent(
752 const base::DictionaryValue& data) {
753 int usb_keycode = 0;
754 bool is_pressed = false;
755 if (!data.GetInteger("usbKeycode", &usb_keycode) ||
756 !data.GetBoolean("pressed", &is_pressed)) {
757 LOG(ERROR) << "Invalid injectKeyEvent.";
758 return;
761 protocol::KeyEvent event;
762 event.set_usb_keycode(usb_keycode);
763 event.set_pressed(is_pressed);
765 // Inject after the KeyEventMapper, so the event won't get mapped or trapped.
766 if (IsConnected())
767 input_tracker_.InjectKeyEvent(event);
770 void ChromotingInstance::HandleRemapKey(const base::DictionaryValue& data) {
771 int from_keycode = 0;
772 int to_keycode = 0;
773 if (!data.GetInteger("fromKeycode", &from_keycode) ||
774 !data.GetInteger("toKeycode", &to_keycode)) {
775 LOG(ERROR) << "Invalid remapKey.";
776 return;
779 key_mapper_.RemapKey(from_keycode, to_keycode);
782 void ChromotingInstance::HandleTrapKey(const base::DictionaryValue& data) {
783 int keycode = 0;
784 bool trap = false;
785 if (!data.GetInteger("keycode", &keycode) ||
786 !data.GetBoolean("trap", &trap)) {
787 LOG(ERROR) << "Invalid trapKey.";
788 return;
791 key_mapper_.TrapKey(keycode, trap);
794 void ChromotingInstance::HandleSendClipboardItem(
795 const base::DictionaryValue& data) {
796 std::string mime_type;
797 std::string item;
798 if (!data.GetString("mimeType", &mime_type) ||
799 !data.GetString("item", &item)) {
800 LOG(ERROR) << "Invalid sendClipboardItem data.";
801 return;
803 if (!IsConnected()) {
804 return;
806 protocol::ClipboardEvent event;
807 event.set_mime_type(mime_type);
808 event.set_data(item);
809 client_->clipboard_forwarder()->InjectClipboardEvent(event);
812 void ChromotingInstance::HandleNotifyClientResolution(
813 const base::DictionaryValue& data) {
814 int width = 0;
815 int height = 0;
816 int x_dpi = kDefaultDPI;
817 int y_dpi = kDefaultDPI;
818 if (!data.GetInteger("width", &width) ||
819 !data.GetInteger("height", &height) ||
820 !data.GetInteger("x_dpi", &x_dpi) ||
821 !data.GetInteger("y_dpi", &y_dpi) ||
822 width <= 0 || height <= 0 ||
823 x_dpi <= 0 || y_dpi <= 0) {
824 LOG(ERROR) << "Invalid notifyClientResolution.";
825 return;
828 if (!IsConnected()) {
829 return;
832 protocol::ClientResolution client_resolution;
833 client_resolution.set_width(width);
834 client_resolution.set_height(height);
835 client_resolution.set_x_dpi(x_dpi);
836 client_resolution.set_y_dpi(y_dpi);
838 // Include the legacy width & height in DIPs for use by older hosts.
839 client_resolution.set_dips_width((width * kDefaultDPI) / x_dpi);
840 client_resolution.set_dips_height((height * kDefaultDPI) / y_dpi);
842 client_->host_stub()->NotifyClientResolution(client_resolution);
845 void ChromotingInstance::HandleVideoControl(const base::DictionaryValue& data) {
846 protocol::VideoControl video_control;
847 bool pause_video = false;
848 if (data.GetBoolean("pause", &pause_video)) {
849 video_control.set_enable(!pause_video);
850 perf_tracker_.OnPauseStateChanged(pause_video);
852 bool lossless_encode = false;
853 if (data.GetBoolean("losslessEncode", &lossless_encode)) {
854 video_control.set_lossless_encode(lossless_encode);
856 bool lossless_color = false;
857 if (data.GetBoolean("losslessColor", &lossless_color)) {
858 video_control.set_lossless_color(lossless_color);
860 if (!IsConnected()) {
861 return;
863 client_->host_stub()->ControlVideo(video_control);
866 void ChromotingInstance::HandlePauseAudio(const base::DictionaryValue& data) {
867 bool pause = false;
868 if (!data.GetBoolean("pause", &pause)) {
869 LOG(ERROR) << "Invalid pauseAudio.";
870 return;
872 if (!IsConnected()) {
873 return;
875 protocol::AudioControl audio_control;
876 audio_control.set_enable(!pause);
877 client_->host_stub()->ControlAudio(audio_control);
879 void ChromotingInstance::HandleOnPinFetched(const base::DictionaryValue& data) {
880 std::string pin;
881 if (!data.GetString("pin", &pin)) {
882 LOG(ERROR) << "Invalid onPinFetched.";
883 return;
885 if (!secret_fetched_callback_.is_null()) {
886 base::ResetAndReturn(&secret_fetched_callback_).Run(pin);
887 } else {
888 LOG(WARNING) << "Ignored OnPinFetched received without a pending fetch.";
892 void ChromotingInstance::HandleOnThirdPartyTokenFetched(
893 const base::DictionaryValue& data) {
894 std::string token;
895 std::string shared_secret;
896 if (!data.GetString("token", &token) ||
897 !data.GetString("sharedSecret", &shared_secret)) {
898 LOG(ERROR) << "Invalid onThirdPartyTokenFetched data.";
899 return;
901 if (token_fetcher_proxy_.get()) {
902 token_fetcher_proxy_->OnTokenFetched(token, shared_secret);
903 token_fetcher_proxy_.reset();
904 } else {
905 LOG(WARNING) << "Ignored OnThirdPartyTokenFetched without a pending fetch.";
909 void ChromotingInstance::HandleRequestPairing(
910 const base::DictionaryValue& data) {
911 std::string client_name;
912 if (!data.GetString("clientName", &client_name)) {
913 LOG(ERROR) << "Invalid requestPairing";
914 return;
916 if (!IsConnected()) {
917 return;
919 protocol::PairingRequest pairing_request;
920 pairing_request.set_client_name(client_name);
921 client_->host_stub()->RequestPairing(pairing_request);
924 void ChromotingInstance::HandleExtensionMessage(
925 const base::DictionaryValue& data) {
926 std::string type;
927 std::string message_data;
928 if (!data.GetString("type", &type) ||
929 !data.GetString("data", &message_data)) {
930 LOG(ERROR) << "Invalid extensionMessage.";
931 return;
933 if (!IsConnected()) {
934 return;
936 protocol::ExtensionMessage message;
937 message.set_type(type);
938 message.set_data(message_data);
939 client_->host_stub()->DeliverClientMessage(message);
942 void ChromotingInstance::HandleAllowMouseLockMessage() {
943 // Create the mouse lock handler and route cursor shape messages through it.
944 mouse_locker_.reset(new PepperMouseLocker(
945 this, base::Bind(&PepperInputHandler::set_send_mouse_move_deltas,
946 base::Unretained(&input_handler_)),
947 &cursor_setter_));
948 empty_cursor_filter_.set_cursor_stub(mouse_locker_.get());
951 void ChromotingInstance::HandleSendMouseInputWhenUnfocused() {
952 input_handler_.set_send_mouse_input_when_unfocused(true);
955 void ChromotingInstance::HandleDelegateLargeCursors() {
956 cursor_setter_.set_delegate_stub(this);
959 void ChromotingInstance::HandleEnableDebugRegion(
960 const base::DictionaryValue& data) {
961 bool enable = false;
962 if (!data.GetBoolean("enable", &enable)) {
963 LOG(ERROR) << "Invalid enableDebugRegion.";
964 return;
967 video_renderer_->EnableDebugDirtyRegion(enable);
970 void ChromotingInstance::HandleEnableTouchEvents(
971 const base::DictionaryValue& data) {
972 bool enable = false;
973 if (!data.GetBoolean("enable", &enable)) {
974 LOG(ERROR) << "Invalid handleTouchEvents.";
975 return;
978 if (enable) {
979 RequestInputEvents(PP_INPUTEVENT_CLASS_TOUCH);
980 } else {
981 ClearInputEventRequest(PP_INPUTEVENT_CLASS_TOUCH);
985 void ChromotingInstance::Disconnect() {
986 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
988 VLOG(0) << "Disconnecting from host.";
990 // Disconnect the input pipeline and teardown the connection.
991 mouse_input_filter_.set_input_stub(nullptr);
992 client_.reset();
993 video_renderer_.reset();
994 stats_update_timer_.Stop();
997 void ChromotingInstance::PostChromotingMessage(const std::string& method,
998 const pp::VarDictionary& data) {
999 pp::VarDictionary message;
1000 message.Set(pp::Var("method"), pp::Var(method));
1001 message.Set(pp::Var("data"), data);
1002 PostMessage(message);
1005 void ChromotingInstance::PostLegacyJsonMessage(
1006 const std::string& method,
1007 scoped_ptr<base::DictionaryValue> data) {
1008 base::DictionaryValue message;
1009 message.SetString("method", method);
1010 message.Set("data", data.release());
1012 std::string message_json;
1013 base::JSONWriter::Write(message, &message_json);
1014 PostMessage(pp::Var(message_json));
1017 void ChromotingInstance::SendTrappedKey(uint32 usb_keycode, bool pressed) {
1018 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
1019 data->SetInteger("usbKeycode", usb_keycode);
1020 data->SetBoolean("pressed", pressed);
1021 PostLegacyJsonMessage("trappedKeyEvent", data.Pass());
1024 void ChromotingInstance::SendOutgoingIq(const std::string& iq) {
1025 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
1026 data->SetString("iq", iq);
1027 PostLegacyJsonMessage("sendOutgoingIq", data.Pass());
1030 void ChromotingInstance::UpdatePerfStatsInUI() {
1031 // Fetch performance stats from the VideoRenderer and send them to the client
1032 // for display to users.
1033 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
1034 data->SetDouble("videoBandwidth", perf_tracker_.video_bandwidth());
1035 data->SetDouble("videoFrameRate", perf_tracker_.video_frame_rate());
1036 data->SetDouble("captureLatency", perf_tracker_.video_capture_ms());
1037 data->SetDouble("encodeLatency", perf_tracker_.video_encode_ms());
1038 data->SetDouble("decodeLatency", perf_tracker_.video_decode_ms());
1039 data->SetDouble("renderLatency", perf_tracker_.video_paint_ms());
1040 data->SetDouble("roundtripLatency", perf_tracker_.round_trip_ms());
1041 PostLegacyJsonMessage("onPerfStats", data.Pass());
1044 // static
1045 void ChromotingInstance::RegisterLogMessageHandler() {
1046 base::AutoLock lock(g_logging_lock.Get());
1048 // Set up log message handler.
1049 // This is not thread-safe so we need it within our lock.
1050 logging::SetLogMessageHandler(&LogToUI);
1053 void ChromotingInstance::RegisterLoggingInstance() {
1054 base::AutoLock lock(g_logging_lock.Get());
1055 g_logging_instance = pp_instance();
1058 void ChromotingInstance::UnregisterLoggingInstance() {
1059 base::AutoLock lock(g_logging_lock.Get());
1061 // Don't unregister unless we're the currently registered instance.
1062 if (pp_instance() != g_logging_instance)
1063 return;
1065 // Unregister this instance for logging.
1066 g_logging_instance = 0;
1069 // static
1070 bool ChromotingInstance::LogToUI(int severity, const char* file, int line,
1071 size_t message_start,
1072 const std::string& str) {
1073 PP_LogLevel log_level = PP_LOGLEVEL_ERROR;
1074 switch(severity) {
1075 case logging::LOG_INFO:
1076 log_level = PP_LOGLEVEL_TIP;
1077 break;
1078 case logging::LOG_WARNING:
1079 log_level = PP_LOGLEVEL_WARNING;
1080 break;
1081 case logging::LOG_ERROR:
1082 case logging::LOG_FATAL:
1083 log_level = PP_LOGLEVEL_ERROR;
1084 break;
1087 PP_Instance pp_instance = 0;
1089 base::AutoLock lock(g_logging_lock.Get());
1090 if (g_logging_instance)
1091 pp_instance = g_logging_instance;
1093 if (pp_instance) {
1094 const PPB_Console* console = reinterpret_cast<const PPB_Console*>(
1095 pp::Module::Get()->GetBrowserInterface(PPB_CONSOLE_INTERFACE));
1096 if (console)
1097 console->Log(pp_instance, log_level, pp::Var(str).pp_var());
1100 // If this is a fatal message the log handler is going to crash after this
1101 // function returns. In that case sleep for 1 second, Otherwise the plugin
1102 // may crash before the message is delivered to the console.
1103 if (severity == logging::LOG_FATAL)
1104 base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1));
1106 return false;
1109 bool ChromotingInstance::IsConnected() {
1110 return client_ &&
1111 (client_->connection_state() == protocol::ConnectionToHost::CONNECTED);
1114 void ChromotingInstance::UpdateUmaEnumHistogram(
1115 const std::string& histogram_name,
1116 int64 value,
1117 int histogram_max) {
1118 pp::UMAPrivate uma(this);
1119 uma.HistogramEnumeration(histogram_name, value, histogram_max);
1122 void ChromotingInstance::UpdateUmaCustomHistogram(
1123 bool is_custom_counts_histogram,
1124 const std::string& histogram_name,
1125 int64 value,
1126 int histogram_min,
1127 int histogram_max,
1128 int histogram_buckets) {
1129 pp::UMAPrivate uma(this);
1131 if (is_custom_counts_histogram)
1132 uma.HistogramCustomCounts(histogram_name, value, histogram_min,
1133 histogram_max, histogram_buckets);
1134 else
1135 uma.HistogramCustomTimes(histogram_name, value, histogram_min,
1136 histogram_max, histogram_buckets);
1139 } // namespace remoting