Ensure low-memory renderers retry failed loads correctly.
[chromium-blink-merge.git] / remoting / client / plugin / chromoting_instance.cc
blob82f1e40382b8f487afeaf953e5ce2bc2dcfb7128
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 // TODO(sergeyu): Ideally we should just pass ErrorCode to the webapp
89 // and let it handle it, but it would be hard to fix it now because
90 // client plugin and webapp versions may not be in sync. It should be
91 // easy to do after we are finished moving the client plugin to NaCl.
92 std::string ConnectionErrorToString(protocol::ErrorCode error) {
93 // Values returned by this function must match the
94 // remoting.ClientSession.Error enum in JS code.
95 switch (error) {
96 case protocol::OK:
97 return "NONE";
99 case protocol::PEER_IS_OFFLINE:
100 return "HOST_IS_OFFLINE";
102 case protocol::SESSION_REJECTED:
103 case protocol::AUTHENTICATION_FAILED:
104 return "SESSION_REJECTED";
106 case protocol::INCOMPATIBLE_PROTOCOL:
107 return "INCOMPATIBLE_PROTOCOL";
109 case protocol::HOST_OVERLOAD:
110 return "HOST_OVERLOAD";
112 case protocol::CHANNEL_CONNECTION_ERROR:
113 case protocol::SIGNALING_ERROR:
114 case protocol::SIGNALING_TIMEOUT:
115 case protocol::UNKNOWN_ERROR:
116 return "NETWORK_FAILURE";
118 DLOG(FATAL) << "Unknown error code" << error;
119 return std::string();
122 PP_Instance g_logging_instance = 0;
123 base::LazyInstance<base::Lock>::Leaky g_logging_lock =
124 LAZY_INSTANCE_INITIALIZER;
126 } // namespace
128 ChromotingInstance::ChromotingInstance(PP_Instance pp_instance)
129 : pp::Instance(pp_instance),
130 initialized_(false),
131 plugin_task_runner_(new PluginThreadTaskRunner(&plugin_thread_delegate_)),
132 context_(plugin_task_runner_.get()),
133 input_tracker_(&mouse_input_filter_),
134 touch_input_scaler_(&input_tracker_),
135 key_mapper_(&touch_input_scaler_),
136 input_handler_(&input_tracker_),
137 cursor_setter_(this),
138 empty_cursor_filter_(&cursor_setter_),
139 text_input_controller_(this),
140 use_async_pin_dialog_(false),
141 weak_factory_(this) {
142 // In NaCl global resources need to be initialized differently because they
143 // are not shared with Chrome.
144 thread_task_runner_handle_.reset(
145 new base::ThreadTaskRunnerHandle(plugin_task_runner_));
146 thread_wrapper_ =
147 jingle_glue::JingleThreadWrapper::WrapTaskRunner(plugin_task_runner_);
149 // Register a global log handler.
150 ChromotingInstance::RegisterLogMessageHandler();
152 nacl_io_init_ppapi(pp_instance, pp::Module::Get()->get_browser_interface());
153 mount("", "/etc", "memfs", 0, "");
154 mount("", "/usr", "memfs", 0, "");
156 // Register for mouse, wheel and keyboard events.
157 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL);
158 RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
160 // Disable the client-side IME in Chrome.
161 text_input_controller_.SetTextInputType(PP_TEXTINPUT_TYPE_NONE);
163 // Resister this instance to handle debug log messsages.
164 RegisterLoggingInstance();
166 // Initialize random seed for libjingle. It's necessary only with OpenSSL.
167 char random_seed[kRandomSeedSize];
168 crypto::RandBytes(random_seed, sizeof(random_seed));
169 rtc::InitRandom(random_seed, sizeof(random_seed));
171 // Send hello message.
172 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
173 PostLegacyJsonMessage("hello", data.Pass());
176 ChromotingInstance::~ChromotingInstance() {
177 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
179 // Disconnect the client.
180 Disconnect();
182 // Unregister this instance so that debug log messages will no longer be sent
183 // to it. This will stop all logging in all Chromoting instances.
184 UnregisterLoggingInstance();
186 plugin_task_runner_->Quit();
188 // Ensure that nothing touches the plugin thread delegate after this point.
189 plugin_task_runner_->DetachAndRunShutdownLoop();
191 // Stopping the context shuts down all chromoting threads.
192 context_.Stop();
195 bool ChromotingInstance::Init(uint32_t argc,
196 const char* argn[],
197 const char* argv[]) {
198 CHECK(!initialized_);
199 initialized_ = true;
201 VLOG(1) << "Started ChromotingInstance::Init";
203 // Start all the threads.
204 context_.Start();
206 return true;
209 void ChromotingInstance::HandleMessage(const pp::Var& message) {
210 if (!message.is_string()) {
211 LOG(ERROR) << "Received a message that is not a string.";
212 return;
215 scoped_ptr<base::Value> json = base::JSONReader::Read(
216 message.AsString(), base::JSON_ALLOW_TRAILING_COMMAS);
217 base::DictionaryValue* message_dict = nullptr;
218 std::string method;
219 base::DictionaryValue* data = nullptr;
220 if (!json.get() ||
221 !json->GetAsDictionary(&message_dict) ||
222 !message_dict->GetString("method", &method) ||
223 !message_dict->GetDictionary("data", &data)) {
224 LOG(ERROR) << "Received invalid message:" << message.AsString();
225 return;
228 if (method == "connect") {
229 HandleConnect(*data);
230 } else if (method == "disconnect") {
231 HandleDisconnect(*data);
232 } else if (method == "incomingIq") {
233 HandleOnIncomingIq(*data);
234 } else if (method == "releaseAllKeys") {
235 HandleReleaseAllKeys(*data);
236 } else if (method == "injectKeyEvent") {
237 HandleInjectKeyEvent(*data);
238 } else if (method == "remapKey") {
239 HandleRemapKey(*data);
240 } else if (method == "trapKey") {
241 HandleTrapKey(*data);
242 } else if (method == "sendClipboardItem") {
243 HandleSendClipboardItem(*data);
244 } else if (method == "notifyClientResolution") {
245 HandleNotifyClientResolution(*data);
246 } else if (method == "videoControl") {
247 HandleVideoControl(*data);
248 } else if (method == "pauseAudio") {
249 HandlePauseAudio(*data);
250 } else if (method == "useAsyncPinDialog") {
251 use_async_pin_dialog_ = true;
252 } else if (method == "onPinFetched") {
253 HandleOnPinFetched(*data);
254 } else if (method == "onThirdPartyTokenFetched") {
255 HandleOnThirdPartyTokenFetched(*data);
256 } else if (method == "requestPairing") {
257 HandleRequestPairing(*data);
258 } else if (method == "extensionMessage") {
259 HandleExtensionMessage(*data);
260 } else if (method == "allowMouseLock") {
261 HandleAllowMouseLockMessage();
262 } else if (method == "sendMouseInputWhenUnfocused") {
263 HandleSendMouseInputWhenUnfocused();
264 } else if (method == "delegateLargeCursors") {
265 HandleDelegateLargeCursors();
266 } else if (method == "enableDebugRegion") {
267 HandleEnableDebugRegion(*data);
268 } else if (method == "enableTouchEvents") {
269 HandleEnableTouchEvents(*data);
273 void ChromotingInstance::DidChangeFocus(bool has_focus) {
274 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
276 if (!IsConnected())
277 return;
279 input_handler_.DidChangeFocus(has_focus);
280 if (mouse_locker_)
281 mouse_locker_->DidChangeFocus(has_focus);
284 void ChromotingInstance::DidChangeView(const pp::View& view) {
285 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
287 plugin_view_ = view;
288 webrtc::DesktopSize size(
289 webrtc::DesktopSize(view.GetRect().width(), view.GetRect().height()));
290 mouse_input_filter_.set_input_size(size);
291 touch_input_scaler_.set_input_size(size);
293 if (video_renderer_)
294 video_renderer_->OnViewChanged(view);
297 bool ChromotingInstance::HandleInputEvent(const pp::InputEvent& event) {
298 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
300 if (!IsConnected())
301 return false;
303 return input_handler_.HandleInputEvent(event);
306 void ChromotingInstance::OnVideoDecodeError() {
307 Disconnect();
309 // Assume that the decoder failure was caused by the host not encoding video
310 // correctly and report it as a protocol error.
311 // TODO(sergeyu): Consider using a different error code in case the decoder
312 // error was caused by some other problem.
313 OnConnectionState(protocol::ConnectionToHost::FAILED,
314 protocol::INCOMPATIBLE_PROTOCOL);
317 void ChromotingInstance::OnVideoFirstFrameReceived() {
318 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
319 PostLegacyJsonMessage("onFirstFrameReceived", data.Pass());
322 void ChromotingInstance::OnVideoSize(const webrtc::DesktopSize& size,
323 const webrtc::DesktopVector& dpi) {
324 mouse_input_filter_.set_output_size(size);
325 touch_input_scaler_.set_output_size(size);
327 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
328 data->SetInteger("width", size.width());
329 data->SetInteger("height", size.height());
330 if (dpi.x())
331 data->SetInteger("x_dpi", dpi.x());
332 if (dpi.y())
333 data->SetInteger("y_dpi", dpi.y());
334 PostLegacyJsonMessage("onDesktopSize", data.Pass());
337 void ChromotingInstance::OnVideoShape(const webrtc::DesktopRegion* shape) {
338 if ((shape && desktop_shape_ && shape->Equals(*desktop_shape_)) ||
339 (!shape && !desktop_shape_)) {
340 return;
343 scoped_ptr<base::DictionaryValue> shape_message(new base::DictionaryValue());
344 if (shape) {
345 desktop_shape_ = make_scoped_ptr(new webrtc::DesktopRegion(*shape));
346 scoped_ptr<base::ListValue> rects_value(new base::ListValue());
347 for (webrtc::DesktopRegion::Iterator i(*shape); !i.IsAtEnd(); i.Advance()) {
348 const webrtc::DesktopRect& rect = i.rect();
349 scoped_ptr<base::ListValue> rect_value(new base::ListValue());
350 rect_value->AppendInteger(rect.left());
351 rect_value->AppendInteger(rect.top());
352 rect_value->AppendInteger(rect.width());
353 rect_value->AppendInteger(rect.height());
354 rects_value->Append(rect_value.release());
356 shape_message->Set("rects", rects_value.release());
359 PostLegacyJsonMessage("onDesktopShape", shape_message.Pass());
362 void ChromotingInstance::OnVideoFrameDirtyRegion(
363 const webrtc::DesktopRegion& dirty_region) {
364 scoped_ptr<base::ListValue> rects_value(new base::ListValue());
365 for (webrtc::DesktopRegion::Iterator i(dirty_region); !i.IsAtEnd();
366 i.Advance()) {
367 const webrtc::DesktopRect& rect = i.rect();
368 scoped_ptr<base::ListValue> rect_value(new base::ListValue());
369 rect_value->AppendInteger(rect.left());
370 rect_value->AppendInteger(rect.top());
371 rect_value->AppendInteger(rect.width());
372 rect_value->AppendInteger(rect.height());
373 rects_value->Append(rect_value.release());
376 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
377 data->Set("rects", rects_value.release());
378 PostLegacyJsonMessage("onDebugRegion", data.Pass());
381 void ChromotingInstance::OnConnectionState(
382 protocol::ConnectionToHost::State state,
383 protocol::ErrorCode error) {
384 pp::UMAPrivate uma(this);
386 switch (state) {
387 case protocol::ConnectionToHost::INITIALIZING:
388 NOTREACHED();
389 break;
390 case protocol::ConnectionToHost::CONNECTING:
391 connection_started_time = base::TimeTicks::Now();
392 break;
393 case protocol::ConnectionToHost::AUTHENTICATED:
394 connection_authenticated_time_ = base::TimeTicks::Now();
395 uma.HistogramCustomTimes(
396 kTimeToAuthenticateHistogram,
397 (connection_authenticated_time_ - connection_started_time)
398 .InMilliseconds(),
399 kConnectionTimesHistogramMinMs, kConnectionTimesHistogramMaxMs,
400 kConnectionTimesHistogramBuckets);
401 break;
402 case protocol::ConnectionToHost::CONNECTED:
403 connection_connected_time_ = base::TimeTicks::Now();
404 uma.HistogramCustomTimes(
405 kTimeToConnectHistogram,
406 (connection_connected_time_ - connection_authenticated_time_)
407 .InMilliseconds(),
408 kConnectionTimesHistogramMinMs, kConnectionTimesHistogramMaxMs,
409 kConnectionTimesHistogramBuckets);
410 break;
411 case protocol::ConnectionToHost::CLOSED:
412 if (!connection_connected_time_.is_null()) {
413 uma.HistogramCustomTimes(
414 kClosedSessionDurationHistogram,
415 (base::TimeTicks::Now() - connection_connected_time_)
416 .InMilliseconds(),
417 kConnectionDurationHistogramMinMinutes,
418 kConnectionDurationHistogramMaxMinutes,
419 kConnectionDurationHistogramBuckets);
421 break;
422 case protocol::ConnectionToHost::FAILED:
423 if (!connection_connected_time_.is_null()) {
424 uma.HistogramCustomTimes(
425 kFailedSessionDurationHistogram,
426 (base::TimeTicks::Now() - connection_connected_time_)
427 .InMilliseconds(),
428 kConnectionDurationHistogramMinMinutes,
429 kConnectionDurationHistogramMaxMinutes,
430 kConnectionDurationHistogramBuckets);
432 break;
435 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
436 data->SetString("state", protocol::ConnectionToHost::StateToString(state));
437 data->SetString("error", ConnectionErrorToString(error));
438 PostLegacyJsonMessage("onConnectionStatus", data.Pass());
441 void ChromotingInstance::FetchThirdPartyToken(
442 const GURL& token_url,
443 const std::string& host_public_key,
444 const std::string& scope,
445 base::WeakPtr<TokenFetcherProxy> token_fetcher_proxy) {
446 // Once the Session object calls this function, it won't continue the
447 // authentication until the callback is called (or connection is canceled).
448 // So, it's impossible to reach this with a callback already registered.
449 DCHECK(!token_fetcher_proxy_.get());
450 token_fetcher_proxy_ = token_fetcher_proxy;
451 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
452 data->SetString("tokenUrl", token_url.spec());
453 data->SetString("hostPublicKey", host_public_key);
454 data->SetString("scope", scope);
455 PostLegacyJsonMessage("fetchThirdPartyToken", data.Pass());
458 void ChromotingInstance::OnConnectionReady(bool ready) {
459 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
460 data->SetBoolean("ready", ready);
461 PostLegacyJsonMessage("onConnectionReady", data.Pass());
464 void ChromotingInstance::OnRouteChanged(const std::string& channel_name,
465 const protocol::TransportRoute& route) {
466 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
467 data->SetString("channel", channel_name);
468 data->SetString("connectionType",
469 protocol::TransportRoute::GetTypeString(route.type));
470 PostLegacyJsonMessage("onRouteChanged", data.Pass());
473 void ChromotingInstance::SetCapabilities(const std::string& capabilities) {
474 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
475 data->SetString("capabilities", capabilities);
476 PostLegacyJsonMessage("setCapabilities", data.Pass());
479 void ChromotingInstance::SetPairingResponse(
480 const protocol::PairingResponse& pairing_response) {
481 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
482 data->SetString("clientId", pairing_response.client_id());
483 data->SetString("sharedSecret", pairing_response.shared_secret());
484 PostLegacyJsonMessage("pairingResponse", data.Pass());
487 void ChromotingInstance::DeliverHostMessage(
488 const protocol::ExtensionMessage& message) {
489 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
490 data->SetString("type", message.type());
491 data->SetString("data", message.data());
492 PostLegacyJsonMessage("extensionMessage", data.Pass());
495 void ChromotingInstance::FetchSecretFromDialog(
496 bool pairing_supported,
497 const protocol::SecretFetchedCallback& secret_fetched_callback) {
498 // Once the Session object calls this function, it won't continue the
499 // authentication until the callback is called (or connection is canceled).
500 // So, it's impossible to reach this with a callback already registered.
501 DCHECK(secret_fetched_callback_.is_null());
502 secret_fetched_callback_ = secret_fetched_callback;
503 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
504 data->SetBoolean("pairingSupported", pairing_supported);
505 PostLegacyJsonMessage("fetchPin", data.Pass());
508 void ChromotingInstance::FetchSecretFromString(
509 const std::string& shared_secret,
510 bool pairing_supported,
511 const protocol::SecretFetchedCallback& secret_fetched_callback) {
512 secret_fetched_callback.Run(shared_secret);
515 protocol::ClipboardStub* ChromotingInstance::GetClipboardStub() {
516 // TODO(sergeyu): Move clipboard handling to a separate class.
517 // crbug.com/138108
518 return this;
521 protocol::CursorShapeStub* ChromotingInstance::GetCursorShapeStub() {
522 return &empty_cursor_filter_;
525 void ChromotingInstance::InjectClipboardEvent(
526 const protocol::ClipboardEvent& event) {
527 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
528 data->SetString("mimeType", event.mime_type());
529 data->SetString("item", event.data());
530 PostLegacyJsonMessage("injectClipboardItem", data.Pass());
533 void ChromotingInstance::SetCursorShape(
534 const protocol::CursorShapeInfo& cursor_shape) {
535 // If the delegated cursor is empty then stop rendering a DOM cursor.
536 if (IsCursorShapeEmpty(cursor_shape)) {
537 PostChromotingMessage("unsetCursorShape", pp::VarDictionary());
538 return;
541 // Cursor is not empty, so pass it to JS to render.
542 const int kBytesPerPixel = sizeof(uint32_t);
543 const size_t buffer_size =
544 cursor_shape.height() * cursor_shape.width() * kBytesPerPixel;
546 pp::VarArrayBuffer array_buffer(buffer_size);
547 void* dst = array_buffer.Map();
548 memcpy(dst, cursor_shape.data().data(), buffer_size);
549 array_buffer.Unmap();
551 pp::VarDictionary dictionary;
552 dictionary.Set(pp::Var("width"), cursor_shape.width());
553 dictionary.Set(pp::Var("height"), cursor_shape.height());
554 dictionary.Set(pp::Var("hotspotX"), cursor_shape.hotspot_x());
555 dictionary.Set(pp::Var("hotspotY"), cursor_shape.hotspot_y());
556 dictionary.Set(pp::Var("data"), array_buffer);
557 PostChromotingMessage("setCursorShape", dictionary);
560 void ChromotingInstance::HandleConnect(const base::DictionaryValue& data) {
561 std::string local_jid;
562 std::string host_jid;
563 std::string host_public_key;
564 std::string authentication_tag;
565 if (!data.GetString("hostJid", &host_jid) ||
566 !data.GetString("hostPublicKey", &host_public_key) ||
567 !data.GetString("localJid", &local_jid) ||
568 !data.GetString("authenticationTag", &authentication_tag)) {
569 LOG(ERROR) << "Invalid connect() data.";
570 return;
573 std::string client_pairing_id;
574 data.GetString("clientPairingId", &client_pairing_id);
575 std::string client_paired_secret;
576 data.GetString("clientPairedSecret", &client_paired_secret);
578 protocol::FetchSecretCallback fetch_secret_callback;
579 if (use_async_pin_dialog_) {
580 fetch_secret_callback = base::Bind(
581 &ChromotingInstance::FetchSecretFromDialog, weak_factory_.GetWeakPtr());
582 } else {
583 std::string shared_secret;
584 if (!data.GetString("sharedSecret", &shared_secret)) {
585 LOG(ERROR) << "sharedSecret not specified in connect().";
586 return;
588 fetch_secret_callback =
589 base::Bind(&ChromotingInstance::FetchSecretFromString, shared_secret);
592 // Read the list of capabilities, if any.
593 std::string capabilities;
594 if (data.HasKey("capabilities")) {
595 if (!data.GetString("capabilities", &capabilities)) {
596 LOG(ERROR) << "Invalid connect() data.";
597 return;
601 // Read and parse list of experiments.
602 std::string experiments;
603 std::vector<std::string> experiments_list;
604 if (data.GetString("experiments", &experiments)) {
605 experiments_list = base::SplitString(
606 experiments, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
609 VLOG(0) << "Connecting to " << host_jid
610 << ". Local jid: " << local_jid << ".";
612 std::string key_filter;
613 if (!data.GetString("keyFilter", &key_filter)) {
614 NOTREACHED();
615 normalizing_input_filter_.reset(new protocol::InputFilter(&key_mapper_));
616 } else if (key_filter == "mac") {
617 normalizing_input_filter_.reset(
618 new NormalizingInputFilterMac(&key_mapper_));
619 } else if (key_filter == "cros") {
620 normalizing_input_filter_.reset(
621 new NormalizingInputFilterCros(&key_mapper_));
622 } else {
623 DCHECK(key_filter.empty());
624 normalizing_input_filter_.reset(new protocol::InputFilter(&key_mapper_));
626 input_handler_.set_input_stub(normalizing_input_filter_.get());
628 // Try initializing 3D video renderer.
629 video_renderer_.reset(new PepperVideoRenderer3D());
630 if (!video_renderer_->Initialize(this, context_, this))
631 video_renderer_.reset();
633 // If we didn't initialize 3D renderer then use the 2D renderer.
634 if (!video_renderer_) {
635 LOG(WARNING)
636 << "Failed to initialize 3D renderer. Using 2D renderer instead.";
637 video_renderer_.reset(new PepperVideoRenderer2D());
638 if (!video_renderer_->Initialize(this, context_, this))
639 video_renderer_.reset();
642 CHECK(video_renderer_);
644 video_renderer_->GetPerformanceTracker()->SetUpdateUmaCallbacks(
645 base::Bind(&ChromotingInstance::UpdateUmaCustomHistogram,
646 weak_factory_.GetWeakPtr(), true),
647 base::Bind(&ChromotingInstance::UpdateUmaCustomHistogram,
648 weak_factory_.GetWeakPtr(), false),
649 base::Bind(&ChromotingInstance::UpdateUmaEnumHistogram,
650 weak_factory_.GetWeakPtr()));
652 if (!plugin_view_.is_null())
653 video_renderer_->OnViewChanged(plugin_view_);
655 scoped_ptr<AudioPlayer> audio_player(new PepperAudioPlayer(this));
656 client_.reset(new ChromotingClient(&context_, this, video_renderer_.get(),
657 audio_player.Pass()));
659 // Connect the input pipeline to the protocol stub & initialize components.
660 mouse_input_filter_.set_input_stub(client_->input_stub());
661 if (!plugin_view_.is_null()) {
662 webrtc::DesktopSize size(plugin_view_.GetRect().width(),
663 plugin_view_.GetRect().height());
664 mouse_input_filter_.set_input_size(size);
665 touch_input_scaler_.set_input_size(size);
668 // Setup the signal strategy.
669 signal_strategy_.reset(new DelegatingSignalStrategy(
670 local_jid, base::Bind(&ChromotingInstance::SendOutgoingIq,
671 weak_factory_.GetWeakPtr())));
673 // Create TransportFactory.
674 scoped_ptr<protocol::TransportFactory> transport_factory(
675 new protocol::LibjingleTransportFactory(
676 signal_strategy_.get(), PepperPortAllocator::Create(this).Pass(),
677 protocol::NetworkSettings(
678 protocol::NetworkSettings::NAT_TRAVERSAL_FULL),
679 protocol::TransportRole::CLIENT));
681 // Create Authenticator.
682 scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher>
683 token_fetcher(new TokenFetcherProxy(
684 base::Bind(&ChromotingInstance::FetchThirdPartyToken,
685 weak_factory_.GetWeakPtr()),
686 host_public_key));
688 std::vector<protocol::AuthenticationMethod> auth_methods;
689 auth_methods.push_back(protocol::AuthenticationMethod::ThirdParty());
690 auth_methods.push_back(protocol::AuthenticationMethod::Spake2Pair());
691 auth_methods.push_back(protocol::AuthenticationMethod::Spake2(
692 protocol::AuthenticationMethod::HMAC_SHA256));
693 auth_methods.push_back(protocol::AuthenticationMethod::Spake2(
694 protocol::AuthenticationMethod::NONE));
696 scoped_ptr<protocol::Authenticator> authenticator(
697 new protocol::NegotiatingClientAuthenticator(
698 client_pairing_id, client_paired_secret, authentication_tag,
699 fetch_secret_callback, token_fetcher.Pass(), auth_methods));
701 scoped_ptr<protocol::CandidateSessionConfig> config =
702 protocol::CandidateSessionConfig::CreateDefault();
703 if (std::find(experiments_list.begin(), experiments_list.end(), "vp9") !=
704 experiments_list.end()) {
705 config->set_vp9_experiment_enabled(true);
707 if (std::find(experiments_list.begin(), experiments_list.end(), "quic") !=
708 experiments_list.end()) {
709 config->PreferTransport(protocol::ChannelConfig::TRANSPORT_QUIC_STREAM);
711 client_->set_protocol_config(config.Pass());
713 // Kick off the connection.
714 client_->Start(signal_strategy_.get(), authenticator.Pass(),
715 transport_factory.Pass(), host_jid, capabilities);
717 // Start timer that periodically sends perf stats.
718 plugin_task_runner_->PostDelayedTask(
719 FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats,
720 weak_factory_.GetWeakPtr()),
721 base::TimeDelta::FromSeconds(
722 protocol::PerformanceTracker::kStatsUpdateFrequencyInSeconds));
725 void ChromotingInstance::HandleDisconnect(const base::DictionaryValue& data) {
726 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
727 Disconnect();
730 void ChromotingInstance::HandleOnIncomingIq(const base::DictionaryValue& data) {
731 std::string iq;
732 if (!data.GetString("iq", &iq)) {
733 LOG(ERROR) << "Invalid incomingIq() data.";
734 return;
737 // Just ignore the message if it's received before Connect() is called. It's
738 // likely to be a leftover from a previous session, so it's safe to ignore it.
739 if (signal_strategy_)
740 signal_strategy_->OnIncomingMessage(iq);
743 void ChromotingInstance::HandleReleaseAllKeys(
744 const base::DictionaryValue& data) {
745 if (IsConnected())
746 input_tracker_.ReleaseAll();
749 void ChromotingInstance::HandleInjectKeyEvent(
750 const base::DictionaryValue& data) {
751 int usb_keycode = 0;
752 bool is_pressed = false;
753 if (!data.GetInteger("usbKeycode", &usb_keycode) ||
754 !data.GetBoolean("pressed", &is_pressed)) {
755 LOG(ERROR) << "Invalid injectKeyEvent.";
756 return;
759 protocol::KeyEvent event;
760 event.set_usb_keycode(usb_keycode);
761 event.set_pressed(is_pressed);
763 // Inject after the KeyEventMapper, so the event won't get mapped or trapped.
764 if (IsConnected())
765 input_tracker_.InjectKeyEvent(event);
768 void ChromotingInstance::HandleRemapKey(const base::DictionaryValue& data) {
769 int from_keycode = 0;
770 int to_keycode = 0;
771 if (!data.GetInteger("fromKeycode", &from_keycode) ||
772 !data.GetInteger("toKeycode", &to_keycode)) {
773 LOG(ERROR) << "Invalid remapKey.";
774 return;
777 key_mapper_.RemapKey(from_keycode, to_keycode);
780 void ChromotingInstance::HandleTrapKey(const base::DictionaryValue& data) {
781 int keycode = 0;
782 bool trap = false;
783 if (!data.GetInteger("keycode", &keycode) ||
784 !data.GetBoolean("trap", &trap)) {
785 LOG(ERROR) << "Invalid trapKey.";
786 return;
789 key_mapper_.TrapKey(keycode, trap);
792 void ChromotingInstance::HandleSendClipboardItem(
793 const base::DictionaryValue& data) {
794 std::string mime_type;
795 std::string item;
796 if (!data.GetString("mimeType", &mime_type) ||
797 !data.GetString("item", &item)) {
798 LOG(ERROR) << "Invalid sendClipboardItem data.";
799 return;
801 if (!IsConnected()) {
802 return;
804 protocol::ClipboardEvent event;
805 event.set_mime_type(mime_type);
806 event.set_data(item);
807 client_->clipboard_forwarder()->InjectClipboardEvent(event);
810 void ChromotingInstance::HandleNotifyClientResolution(
811 const base::DictionaryValue& data) {
812 int width = 0;
813 int height = 0;
814 int x_dpi = kDefaultDPI;
815 int y_dpi = kDefaultDPI;
816 if (!data.GetInteger("width", &width) ||
817 !data.GetInteger("height", &height) ||
818 !data.GetInteger("x_dpi", &x_dpi) ||
819 !data.GetInteger("y_dpi", &y_dpi) ||
820 width <= 0 || height <= 0 ||
821 x_dpi <= 0 || y_dpi <= 0) {
822 LOG(ERROR) << "Invalid notifyClientResolution.";
823 return;
826 if (!IsConnected()) {
827 return;
830 protocol::ClientResolution client_resolution;
831 client_resolution.set_width(width);
832 client_resolution.set_height(height);
833 client_resolution.set_x_dpi(x_dpi);
834 client_resolution.set_y_dpi(y_dpi);
836 // Include the legacy width & height in DIPs for use by older hosts.
837 client_resolution.set_dips_width((width * kDefaultDPI) / x_dpi);
838 client_resolution.set_dips_height((height * kDefaultDPI) / y_dpi);
840 client_->host_stub()->NotifyClientResolution(client_resolution);
843 void ChromotingInstance::HandleVideoControl(const base::DictionaryValue& data) {
844 protocol::VideoControl video_control;
845 bool pause_video = false;
846 if (data.GetBoolean("pause", &pause_video)) {
847 video_control.set_enable(!pause_video);
849 bool lossless_encode = false;
850 if (data.GetBoolean("losslessEncode", &lossless_encode)) {
851 video_control.set_lossless_encode(lossless_encode);
853 bool lossless_color = false;
854 if (data.GetBoolean("losslessColor", &lossless_color)) {
855 video_control.set_lossless_color(lossless_color);
857 if (!IsConnected()) {
858 return;
860 client_->host_stub()->ControlVideo(video_control);
863 void ChromotingInstance::HandlePauseAudio(const base::DictionaryValue& data) {
864 bool pause = false;
865 if (!data.GetBoolean("pause", &pause)) {
866 LOG(ERROR) << "Invalid pauseAudio.";
867 return;
869 if (!IsConnected()) {
870 return;
872 protocol::AudioControl audio_control;
873 audio_control.set_enable(!pause);
874 client_->host_stub()->ControlAudio(audio_control);
876 void ChromotingInstance::HandleOnPinFetched(const base::DictionaryValue& data) {
877 std::string pin;
878 if (!data.GetString("pin", &pin)) {
879 LOG(ERROR) << "Invalid onPinFetched.";
880 return;
882 if (!secret_fetched_callback_.is_null()) {
883 base::ResetAndReturn(&secret_fetched_callback_).Run(pin);
884 } else {
885 LOG(WARNING) << "Ignored OnPinFetched received without a pending fetch.";
889 void ChromotingInstance::HandleOnThirdPartyTokenFetched(
890 const base::DictionaryValue& data) {
891 std::string token;
892 std::string shared_secret;
893 if (!data.GetString("token", &token) ||
894 !data.GetString("sharedSecret", &shared_secret)) {
895 LOG(ERROR) << "Invalid onThirdPartyTokenFetched data.";
896 return;
898 if (token_fetcher_proxy_.get()) {
899 token_fetcher_proxy_->OnTokenFetched(token, shared_secret);
900 token_fetcher_proxy_.reset();
901 } else {
902 LOG(WARNING) << "Ignored OnThirdPartyTokenFetched without a pending fetch.";
906 void ChromotingInstance::HandleRequestPairing(
907 const base::DictionaryValue& data) {
908 std::string client_name;
909 if (!data.GetString("clientName", &client_name)) {
910 LOG(ERROR) << "Invalid requestPairing";
911 return;
913 if (!IsConnected()) {
914 return;
916 protocol::PairingRequest pairing_request;
917 pairing_request.set_client_name(client_name);
918 client_->host_stub()->RequestPairing(pairing_request);
921 void ChromotingInstance::HandleExtensionMessage(
922 const base::DictionaryValue& data) {
923 std::string type;
924 std::string message_data;
925 if (!data.GetString("type", &type) ||
926 !data.GetString("data", &message_data)) {
927 LOG(ERROR) << "Invalid extensionMessage.";
928 return;
930 if (!IsConnected()) {
931 return;
933 protocol::ExtensionMessage message;
934 message.set_type(type);
935 message.set_data(message_data);
936 client_->host_stub()->DeliverClientMessage(message);
939 void ChromotingInstance::HandleAllowMouseLockMessage() {
940 // Create the mouse lock handler and route cursor shape messages through it.
941 mouse_locker_.reset(new PepperMouseLocker(
942 this, base::Bind(&PepperInputHandler::set_send_mouse_move_deltas,
943 base::Unretained(&input_handler_)),
944 &cursor_setter_));
945 empty_cursor_filter_.set_cursor_stub(mouse_locker_.get());
948 void ChromotingInstance::HandleSendMouseInputWhenUnfocused() {
949 input_handler_.set_send_mouse_input_when_unfocused(true);
952 void ChromotingInstance::HandleDelegateLargeCursors() {
953 cursor_setter_.set_delegate_stub(this);
956 void ChromotingInstance::HandleEnableDebugRegion(
957 const base::DictionaryValue& data) {
958 bool enable = false;
959 if (!data.GetBoolean("enable", &enable)) {
960 LOG(ERROR) << "Invalid enableDebugRegion.";
961 return;
964 video_renderer_->EnableDebugDirtyRegion(enable);
967 void ChromotingInstance::HandleEnableTouchEvents(
968 const base::DictionaryValue& data) {
969 bool enable = false;
970 if (!data.GetBoolean("enable", &enable)) {
971 LOG(ERROR) << "Invalid handleTouchEvents.";
972 return;
975 if (enable) {
976 RequestInputEvents(PP_INPUTEVENT_CLASS_TOUCH);
977 } else {
978 ClearInputEventRequest(PP_INPUTEVENT_CLASS_TOUCH);
982 void ChromotingInstance::Disconnect() {
983 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
985 VLOG(0) << "Disconnecting from host.";
987 // Disconnect the input pipeline and teardown the connection.
988 mouse_input_filter_.set_input_stub(nullptr);
989 client_.reset();
990 video_renderer_.reset();
993 void ChromotingInstance::PostChromotingMessage(const std::string& method,
994 const pp::VarDictionary& data) {
995 pp::VarDictionary message;
996 message.Set(pp::Var("method"), pp::Var(method));
997 message.Set(pp::Var("data"), data);
998 PostMessage(message);
1001 void ChromotingInstance::PostLegacyJsonMessage(
1002 const std::string& method,
1003 scoped_ptr<base::DictionaryValue> data) {
1004 base::DictionaryValue message;
1005 message.SetString("method", method);
1006 message.Set("data", data.release());
1008 std::string message_json;
1009 base::JSONWriter::Write(message, &message_json);
1010 PostMessage(pp::Var(message_json));
1013 void ChromotingInstance::SendTrappedKey(uint32 usb_keycode, bool pressed) {
1014 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
1015 data->SetInteger("usbKeycode", usb_keycode);
1016 data->SetBoolean("pressed", pressed);
1017 PostLegacyJsonMessage("trappedKeyEvent", data.Pass());
1020 void ChromotingInstance::SendOutgoingIq(const std::string& iq) {
1021 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
1022 data->SetString("iq", iq);
1023 PostLegacyJsonMessage("sendOutgoingIq", data.Pass());
1026 void ChromotingInstance::SendPerfStats() {
1027 if (!video_renderer_.get()) {
1028 return;
1031 plugin_task_runner_->PostDelayedTask(
1032 FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats,
1033 weak_factory_.GetWeakPtr()),
1034 base::TimeDelta::FromSeconds(
1035 protocol::PerformanceTracker::kStatsUpdateFrequencyInSeconds));
1037 // Fetch performance stats from the VideoRenderer and send them to the client
1038 // for display to users.
1039 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
1040 protocol::PerformanceTracker* perf_tracker =
1041 video_renderer_->GetPerformanceTracker();
1042 data->SetDouble("videoBandwidth", perf_tracker->video_bandwidth());
1043 data->SetDouble("videoFrameRate", perf_tracker->video_frame_rate());
1044 data->SetDouble("captureLatency", perf_tracker->video_capture_ms());
1045 data->SetDouble("encodeLatency", perf_tracker->video_encode_ms());
1046 data->SetDouble("decodeLatency", perf_tracker->video_decode_ms());
1047 data->SetDouble("renderLatency", perf_tracker->video_paint_ms());
1048 data->SetDouble("roundtripLatency", perf_tracker->round_trip_ms());
1049 PostLegacyJsonMessage("onPerfStats", data.Pass());
1051 // Record the video frame-rate, packet-rate and bandwidth stats to UMA.
1052 // TODO(anandc): Create a timer in PerformanceTracker to do this work.
1053 // See http://crbug/508602.
1054 perf_tracker->UploadRateStatsToUma();
1057 // static
1058 void ChromotingInstance::RegisterLogMessageHandler() {
1059 base::AutoLock lock(g_logging_lock.Get());
1061 // Set up log message handler.
1062 // This is not thread-safe so we need it within our lock.
1063 logging::SetLogMessageHandler(&LogToUI);
1066 void ChromotingInstance::RegisterLoggingInstance() {
1067 base::AutoLock lock(g_logging_lock.Get());
1068 g_logging_instance = pp_instance();
1071 void ChromotingInstance::UnregisterLoggingInstance() {
1072 base::AutoLock lock(g_logging_lock.Get());
1074 // Don't unregister unless we're the currently registered instance.
1075 if (pp_instance() != g_logging_instance)
1076 return;
1078 // Unregister this instance for logging.
1079 g_logging_instance = 0;
1082 // static
1083 bool ChromotingInstance::LogToUI(int severity, const char* file, int line,
1084 size_t message_start,
1085 const std::string& str) {
1086 PP_LogLevel log_level = PP_LOGLEVEL_ERROR;
1087 switch(severity) {
1088 case logging::LOG_INFO:
1089 log_level = PP_LOGLEVEL_TIP;
1090 break;
1091 case logging::LOG_WARNING:
1092 log_level = PP_LOGLEVEL_WARNING;
1093 break;
1094 case logging::LOG_ERROR:
1095 case logging::LOG_FATAL:
1096 log_level = PP_LOGLEVEL_ERROR;
1097 break;
1100 PP_Instance pp_instance = 0;
1102 base::AutoLock lock(g_logging_lock.Get());
1103 if (g_logging_instance)
1104 pp_instance = g_logging_instance;
1106 if (pp_instance) {
1107 const PPB_Console* console = reinterpret_cast<const PPB_Console*>(
1108 pp::Module::Get()->GetBrowserInterface(PPB_CONSOLE_INTERFACE));
1109 if (console)
1110 console->Log(pp_instance, log_level, pp::Var(str).pp_var());
1113 // If this is a fatal message the log handler is going to crash after this
1114 // function returns. In that case sleep for 1 second, Otherwise the plugin
1115 // may crash before the message is delivered to the console.
1116 if (severity == logging::LOG_FATAL)
1117 base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1));
1119 return false;
1122 bool ChromotingInstance::IsConnected() {
1123 return client_ &&
1124 (client_->connection_state() == protocol::ConnectionToHost::CONNECTED);
1127 void ChromotingInstance::UpdateUmaEnumHistogram(
1128 const std::string& histogram_name,
1129 int64 value,
1130 int histogram_max) {
1131 pp::UMAPrivate uma(this);
1132 uma.HistogramEnumeration(histogram_name, value, histogram_max);
1135 void ChromotingInstance::UpdateUmaCustomHistogram(
1136 bool is_custom_counts_histogram,
1137 const std::string& histogram_name,
1138 int64 value,
1139 int histogram_min,
1140 int histogram_max,
1141 int histogram_buckets) {
1142 pp::UMAPrivate uma(this);
1144 if (is_custom_counts_histogram)
1145 uma.HistogramCustomCounts(histogram_name, value, histogram_min,
1146 histogram_max, histogram_buckets);
1147 else
1148 uma.HistogramCustomTimes(histogram_name, value, histogram_min,
1149 histogram_max, histogram_buckets);
1152 } // namespace remoting