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 // 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.
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
;
128 ChromotingInstance::ChromotingInstance(PP_Instance pp_instance
)
129 : pp::Instance(pp_instance
),
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_
));
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.
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.
195 bool ChromotingInstance::Init(uint32_t argc
,
197 const char* argv
[]) {
198 CHECK(!initialized_
);
201 VLOG(1) << "Started ChromotingInstance::Init";
203 // Start all the threads.
209 void ChromotingInstance::HandleMessage(const pp::Var
& message
) {
210 if (!message
.is_string()) {
211 LOG(ERROR
) << "Received a message that is not a string.";
215 scoped_ptr
<base::Value
> json
= base::JSONReader::Read(
216 message
.AsString(), base::JSON_ALLOW_TRAILING_COMMAS
);
217 base::DictionaryValue
* message_dict
= nullptr;
219 base::DictionaryValue
* data
= nullptr;
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();
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());
279 input_handler_
.DidChangeFocus(has_focus
);
281 mouse_locker_
->DidChangeFocus(has_focus
);
284 void ChromotingInstance::DidChangeView(const pp::View
& view
) {
285 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
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
);
294 video_renderer_
->OnViewChanged(view
);
297 bool ChromotingInstance::HandleInputEvent(const pp::InputEvent
& event
) {
298 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
303 return input_handler_
.HandleInputEvent(event
);
306 void ChromotingInstance::OnVideoDecodeError() {
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());
331 data
->SetInteger("x_dpi", dpi
.x());
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_
)) {
343 scoped_ptr
<base::DictionaryValue
> shape_message(new base::DictionaryValue());
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();
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);
387 case protocol::ConnectionToHost::INITIALIZING
:
390 case protocol::ConnectionToHost::CONNECTING
:
391 connection_started_time
= base::TimeTicks::Now();
393 case protocol::ConnectionToHost::AUTHENTICATED
:
394 connection_authenticated_time_
= base::TimeTicks::Now();
395 uma
.HistogramCustomTimes(
396 kTimeToAuthenticateHistogram
,
397 (connection_authenticated_time_
- connection_started_time
)
399 kConnectionTimesHistogramMinMs
, kConnectionTimesHistogramMaxMs
,
400 kConnectionTimesHistogramBuckets
);
402 case protocol::ConnectionToHost::CONNECTED
:
403 connection_connected_time_
= base::TimeTicks::Now();
404 uma
.HistogramCustomTimes(
405 kTimeToConnectHistogram
,
406 (connection_connected_time_
- connection_authenticated_time_
)
408 kConnectionTimesHistogramMinMs
, kConnectionTimesHistogramMaxMs
,
409 kConnectionTimesHistogramBuckets
);
411 case protocol::ConnectionToHost::CLOSED
:
412 if (!connection_connected_time_
.is_null()) {
413 uma
.HistogramCustomTimes(
414 kClosedSessionDurationHistogram
,
415 (base::TimeTicks::Now() - connection_connected_time_
)
417 kConnectionDurationHistogramMinMinutes
,
418 kConnectionDurationHistogramMaxMinutes
,
419 kConnectionDurationHistogramBuckets
);
422 case protocol::ConnectionToHost::FAILED
:
423 if (!connection_connected_time_
.is_null()) {
424 uma
.HistogramCustomTimes(
425 kFailedSessionDurationHistogram
,
426 (base::TimeTicks::Now() - connection_connected_time_
)
428 kConnectionDurationHistogramMinMinutes
,
429 kConnectionDurationHistogramMaxMinutes
,
430 kConnectionDurationHistogramBuckets
);
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.
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());
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.";
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());
583 std::string shared_secret
;
584 if (!data
.GetString("sharedSecret", &shared_secret
)) {
585 LOG(ERROR
) << "sharedSecret not specified in connect().";
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.";
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
)) {
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_
));
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_
) {
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()),
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());
730 void ChromotingInstance::HandleOnIncomingIq(const base::DictionaryValue
& data
) {
732 if (!data
.GetString("iq", &iq
)) {
733 LOG(ERROR
) << "Invalid incomingIq() data.";
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
) {
746 input_tracker_
.ReleaseAll();
749 void ChromotingInstance::HandleInjectKeyEvent(
750 const base::DictionaryValue
& data
) {
752 bool is_pressed
= false;
753 if (!data
.GetInteger("usbKeycode", &usb_keycode
) ||
754 !data
.GetBoolean("pressed", &is_pressed
)) {
755 LOG(ERROR
) << "Invalid injectKeyEvent.";
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.
765 input_tracker_
.InjectKeyEvent(event
);
768 void ChromotingInstance::HandleRemapKey(const base::DictionaryValue
& data
) {
769 int from_keycode
= 0;
771 if (!data
.GetInteger("fromKeycode", &from_keycode
) ||
772 !data
.GetInteger("toKeycode", &to_keycode
)) {
773 LOG(ERROR
) << "Invalid remapKey.";
777 key_mapper_
.RemapKey(from_keycode
, to_keycode
);
780 void ChromotingInstance::HandleTrapKey(const base::DictionaryValue
& data
) {
783 if (!data
.GetInteger("keycode", &keycode
) ||
784 !data
.GetBoolean("trap", &trap
)) {
785 LOG(ERROR
) << "Invalid trapKey.";
789 key_mapper_
.TrapKey(keycode
, trap
);
792 void ChromotingInstance::HandleSendClipboardItem(
793 const base::DictionaryValue
& data
) {
794 std::string mime_type
;
796 if (!data
.GetString("mimeType", &mime_type
) ||
797 !data
.GetString("item", &item
)) {
798 LOG(ERROR
) << "Invalid sendClipboardItem data.";
801 if (!IsConnected()) {
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
) {
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.";
826 if (!IsConnected()) {
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()) {
860 client_
->host_stub()->ControlVideo(video_control
);
863 void ChromotingInstance::HandlePauseAudio(const base::DictionaryValue
& data
) {
865 if (!data
.GetBoolean("pause", &pause
)) {
866 LOG(ERROR
) << "Invalid pauseAudio.";
869 if (!IsConnected()) {
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
) {
878 if (!data
.GetString("pin", &pin
)) {
879 LOG(ERROR
) << "Invalid onPinFetched.";
882 if (!secret_fetched_callback_
.is_null()) {
883 base::ResetAndReturn(&secret_fetched_callback_
).Run(pin
);
885 LOG(WARNING
) << "Ignored OnPinFetched received without a pending fetch.";
889 void ChromotingInstance::HandleOnThirdPartyTokenFetched(
890 const base::DictionaryValue
& data
) {
892 std::string shared_secret
;
893 if (!data
.GetString("token", &token
) ||
894 !data
.GetString("sharedSecret", &shared_secret
)) {
895 LOG(ERROR
) << "Invalid onThirdPartyTokenFetched data.";
898 if (token_fetcher_proxy_
.get()) {
899 token_fetcher_proxy_
->OnTokenFetched(token
, shared_secret
);
900 token_fetcher_proxy_
.reset();
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";
913 if (!IsConnected()) {
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
) {
924 std::string message_data
;
925 if (!data
.GetString("type", &type
) ||
926 !data
.GetString("data", &message_data
)) {
927 LOG(ERROR
) << "Invalid extensionMessage.";
930 if (!IsConnected()) {
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_
)),
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
) {
959 if (!data
.GetBoolean("enable", &enable
)) {
960 LOG(ERROR
) << "Invalid enableDebugRegion.";
964 video_renderer_
->EnableDebugDirtyRegion(enable
);
967 void ChromotingInstance::HandleEnableTouchEvents(
968 const base::DictionaryValue
& data
) {
970 if (!data
.GetBoolean("enable", &enable
)) {
971 LOG(ERROR
) << "Invalid handleTouchEvents.";
976 RequestInputEvents(PP_INPUTEVENT_CLASS_TOUCH
);
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);
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()) {
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();
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
)
1078 // Unregister this instance for logging.
1079 g_logging_instance
= 0;
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
;
1088 case logging::LOG_INFO
:
1089 log_level
= PP_LOGLEVEL_TIP
;
1091 case logging::LOG_WARNING
:
1092 log_level
= PP_LOGLEVEL_WARNING
;
1094 case logging::LOG_ERROR
:
1095 case logging::LOG_FATAL
:
1096 log_level
= PP_LOGLEVEL_ERROR
;
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
;
1107 const PPB_Console
* console
= reinterpret_cast<const PPB_Console
*>(
1108 pp::Module::Get()->GetBrowserInterface(PPB_CONSOLE_INTERFACE
));
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));
1122 bool ChromotingInstance::IsConnected() {
1124 (client_
->connection_state() == protocol::ConnectionToHost::CONNECTED
);
1127 void ChromotingInstance::UpdateUmaEnumHistogram(
1128 const std::string
& histogram_name
,
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
,
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
);
1148 uma
.HistogramCustomTimes(histogram_name
, value
, histogram_min
,
1149 histogram_max
, histogram_buckets
);
1152 } // namespace remoting