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"
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"
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.
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
;
131 ChromotingInstance::ChromotingInstance(PP_Instance pp_instance
)
132 : pp::Instance(pp_instance
),
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_
));
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.
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.
198 bool ChromotingInstance::Init(uint32_t argc
,
200 const char* argv
[]) {
201 CHECK(!initialized_
);
204 VLOG(1) << "Started ChromotingInstance::Init";
206 // Start all the threads.
212 void ChromotingInstance::HandleMessage(const pp::Var
& message
) {
213 if (!message
.is_string()) {
214 LOG(ERROR
) << "Received a message that is not a string.";
218 scoped_ptr
<base::Value
> json
= base::JSONReader::Read(
219 message
.AsString(), base::JSON_ALLOW_TRAILING_COMMAS
);
220 base::DictionaryValue
* message_dict
= nullptr;
222 base::DictionaryValue
* data
= nullptr;
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();
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());
282 input_handler_
.DidChangeFocus(has_focus
);
284 mouse_locker_
->DidChangeFocus(has_focus
);
287 void ChromotingInstance::DidChangeView(const pp::View
& view
) {
288 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
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
);
297 video_renderer_
->OnViewChanged(view
);
300 bool ChromotingInstance::HandleInputEvent(const pp::InputEvent
& event
) {
301 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
306 return input_handler_
.HandleInputEvent(event
);
309 void ChromotingInstance::OnVideoDecodeError() {
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());
334 data
->SetInteger("x_dpi", dpi
.x());
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_
)) {
346 scoped_ptr
<base::DictionaryValue
> shape_message(new base::DictionaryValue());
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();
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);
390 case protocol::ConnectionToHost::INITIALIZING
:
393 case protocol::ConnectionToHost::CONNECTING
:
394 connection_started_time
= base::TimeTicks::Now();
396 case protocol::ConnectionToHost::AUTHENTICATED
:
397 connection_authenticated_time_
= base::TimeTicks::Now();
398 uma
.HistogramCustomTimes(
399 kTimeToAuthenticateHistogram
,
400 (connection_authenticated_time_
- connection_started_time
)
402 kConnectionTimesHistogramMinMs
, kConnectionTimesHistogramMaxMs
,
403 kConnectionTimesHistogramBuckets
);
405 case protocol::ConnectionToHost::CONNECTED
:
406 connection_connected_time_
= base::TimeTicks::Now();
407 uma
.HistogramCustomTimes(
408 kTimeToConnectHistogram
,
409 (connection_connected_time_
- connection_authenticated_time_
)
411 kConnectionTimesHistogramMinMs
, kConnectionTimesHistogramMaxMs
,
412 kConnectionTimesHistogramBuckets
);
414 case protocol::ConnectionToHost::CLOSED
:
415 if (!connection_connected_time_
.is_null()) {
416 uma
.HistogramCustomTimes(
417 kClosedSessionDurationHistogram
,
418 (base::TimeTicks::Now() - connection_connected_time_
)
420 kConnectionDurationHistogramMinMinutes
,
421 kConnectionDurationHistogramMaxMinutes
,
422 kConnectionDurationHistogramBuckets
);
425 case protocol::ConnectionToHost::FAILED
:
426 if (!connection_connected_time_
.is_null()) {
427 uma
.HistogramCustomTimes(
428 kFailedSessionDurationHistogram
,
429 (base::TimeTicks::Now() - connection_connected_time_
)
431 kConnectionDurationHistogramMinMinutes
,
432 kConnectionDurationHistogramMaxMinutes
,
433 kConnectionDurationHistogramBuckets
);
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.
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());
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.";
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());
586 std::string shared_secret
;
587 if (!data
.GetString("sharedSecret", &shared_secret
)) {
588 LOG(ERROR
) << "sharedSecret not specified in connect().";
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.";
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
)) {
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_
));
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_
) {
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()),
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());
732 void ChromotingInstance::HandleOnIncomingIq(const base::DictionaryValue
& data
) {
734 if (!data
.GetString("iq", &iq
)) {
735 LOG(ERROR
) << "Invalid incomingIq() data.";
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
) {
748 input_tracker_
.ReleaseAll();
751 void ChromotingInstance::HandleInjectKeyEvent(
752 const base::DictionaryValue
& data
) {
754 bool is_pressed
= false;
755 if (!data
.GetInteger("usbKeycode", &usb_keycode
) ||
756 !data
.GetBoolean("pressed", &is_pressed
)) {
757 LOG(ERROR
) << "Invalid injectKeyEvent.";
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.
767 input_tracker_
.InjectKeyEvent(event
);
770 void ChromotingInstance::HandleRemapKey(const base::DictionaryValue
& data
) {
771 int from_keycode
= 0;
773 if (!data
.GetInteger("fromKeycode", &from_keycode
) ||
774 !data
.GetInteger("toKeycode", &to_keycode
)) {
775 LOG(ERROR
) << "Invalid remapKey.";
779 key_mapper_
.RemapKey(from_keycode
, to_keycode
);
782 void ChromotingInstance::HandleTrapKey(const base::DictionaryValue
& data
) {
785 if (!data
.GetInteger("keycode", &keycode
) ||
786 !data
.GetBoolean("trap", &trap
)) {
787 LOG(ERROR
) << "Invalid trapKey.";
791 key_mapper_
.TrapKey(keycode
, trap
);
794 void ChromotingInstance::HandleSendClipboardItem(
795 const base::DictionaryValue
& data
) {
796 std::string mime_type
;
798 if (!data
.GetString("mimeType", &mime_type
) ||
799 !data
.GetString("item", &item
)) {
800 LOG(ERROR
) << "Invalid sendClipboardItem data.";
803 if (!IsConnected()) {
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
) {
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.";
828 if (!IsConnected()) {
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()) {
863 client_
->host_stub()->ControlVideo(video_control
);
866 void ChromotingInstance::HandlePauseAudio(const base::DictionaryValue
& data
) {
868 if (!data
.GetBoolean("pause", &pause
)) {
869 LOG(ERROR
) << "Invalid pauseAudio.";
872 if (!IsConnected()) {
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
) {
881 if (!data
.GetString("pin", &pin
)) {
882 LOG(ERROR
) << "Invalid onPinFetched.";
885 if (!secret_fetched_callback_
.is_null()) {
886 base::ResetAndReturn(&secret_fetched_callback_
).Run(pin
);
888 LOG(WARNING
) << "Ignored OnPinFetched received without a pending fetch.";
892 void ChromotingInstance::HandleOnThirdPartyTokenFetched(
893 const base::DictionaryValue
& data
) {
895 std::string shared_secret
;
896 if (!data
.GetString("token", &token
) ||
897 !data
.GetString("sharedSecret", &shared_secret
)) {
898 LOG(ERROR
) << "Invalid onThirdPartyTokenFetched data.";
901 if (token_fetcher_proxy_
.get()) {
902 token_fetcher_proxy_
->OnTokenFetched(token
, shared_secret
);
903 token_fetcher_proxy_
.reset();
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";
916 if (!IsConnected()) {
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
) {
927 std::string message_data
;
928 if (!data
.GetString("type", &type
) ||
929 !data
.GetString("data", &message_data
)) {
930 LOG(ERROR
) << "Invalid extensionMessage.";
933 if (!IsConnected()) {
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_
)),
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
) {
962 if (!data
.GetBoolean("enable", &enable
)) {
963 LOG(ERROR
) << "Invalid enableDebugRegion.";
967 video_renderer_
->EnableDebugDirtyRegion(enable
);
970 void ChromotingInstance::HandleEnableTouchEvents(
971 const base::DictionaryValue
& data
) {
973 if (!data
.GetBoolean("enable", &enable
)) {
974 LOG(ERROR
) << "Invalid handleTouchEvents.";
979 RequestInputEvents(PP_INPUTEVENT_CLASS_TOUCH
);
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);
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());
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
)
1065 // Unregister this instance for logging.
1066 g_logging_instance
= 0;
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
;
1075 case logging::LOG_INFO
:
1076 log_level
= PP_LOGLEVEL_TIP
;
1078 case logging::LOG_WARNING
:
1079 log_level
= PP_LOGLEVEL_WARNING
;
1081 case logging::LOG_ERROR
:
1082 case logging::LOG_FATAL
:
1083 log_level
= PP_LOGLEVEL_ERROR
;
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
;
1094 const PPB_Console
* console
= reinterpret_cast<const PPB_Console
*>(
1095 pp::Module::Get()->GetBrowserInterface(PPB_CONSOLE_INTERFACE
));
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));
1109 bool ChromotingInstance::IsConnected() {
1111 (client_
->connection_state() == protocol::ConnectionToHost::CONNECTED
);
1114 void ChromotingInstance::UpdateUmaEnumHistogram(
1115 const std::string
& histogram_name
,
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
,
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
);
1135 uma
.HistogramCustomTimes(histogram_name
, value
, histogram_min
,
1136 histogram_max
, histogram_buckets
);
1139 } // namespace remoting