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"
11 #include <nacl_io/nacl_io.h>
12 #include <sys/mount.h>
15 #include "base/bind.h"
16 #include "base/callback.h"
17 #include "base/json/json_reader.h"
18 #include "base/json/json_writer.h"
19 #include "base/lazy_instance.h"
20 #include "base/logging.h"
21 #include "base/strings/string_split.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/synchronization/lock.h"
24 #include "base/threading/thread.h"
25 #include "base/values.h"
26 #include "crypto/random.h"
27 #include "jingle/glue/thread_wrapper.h"
28 #include "media/base/yuv_convert.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/rect.h"
35 #include "ppapi/cpp/var_array_buffer.h"
36 #include "ppapi/cpp/var_dictionary.h"
37 #include "remoting/base/constants.h"
38 #include "remoting/base/util.h"
39 #include "remoting/client/chromoting_client.h"
40 #include "remoting/client/plugin/delegating_signal_strategy.h"
41 #include "remoting/client/plugin/normalizing_input_filter_cros.h"
42 #include "remoting/client/plugin/normalizing_input_filter_mac.h"
43 #include "remoting/client/plugin/pepper_audio_player.h"
44 #include "remoting/client/plugin/pepper_mouse_locker.h"
45 #include "remoting/client/plugin/pepper_port_allocator.h"
46 #include "remoting/client/plugin/pepper_video_renderer_2d.h"
47 #include "remoting/client/plugin/pepper_video_renderer_3d.h"
48 #include "remoting/client/software_video_renderer.h"
49 #include "remoting/client/token_fetcher_proxy.h"
50 #include "remoting/protocol/connection_to_host.h"
51 #include "remoting/protocol/host_stub.h"
52 #include "remoting/protocol/libjingle_transport_factory.h"
53 #include "third_party/webrtc/base/helpers.h"
54 #include "third_party/webrtc/base/ssladapter.h"
57 // Windows defines 'PostMessage', so we have to undef it.
58 #if defined(PostMessage)
66 // Default DPI to assume for old clients that use notifyClientResolution.
67 const int kDefaultDPI
= 96;
69 // Interval at which to sample performance statistics.
70 const int kPerfStatsIntervalMs
= 1000;
72 // URL scheme used by Chrome apps and extensions.
73 const char kChromeExtensionUrlScheme
[] = "chrome-extension";
75 #if defined(USE_OPENSSL)
76 // Size of the random seed blob used to initialize RNG in libjingle. Libjingle
77 // uses the seed only for OpenSSL builds. OpenSSL needs at least 32 bytes of
78 // entropy (see http://wiki.openssl.org/index.php/Random_Numbers), but stores
79 // 1039 bytes of state, so we initialize it with 1k or random data.
80 const int kRandomSeedSize
= 1024;
81 #endif // defined(USE_OPENSSL)
83 std::string
ConnectionStateToString(protocol::ConnectionToHost::State state
) {
84 // Values returned by this function must match the
85 // remoting.ClientSession.State enum in JS code.
87 case protocol::ConnectionToHost::INITIALIZING
:
88 return "INITIALIZING";
89 case protocol::ConnectionToHost::CONNECTING
:
91 case protocol::ConnectionToHost::AUTHENTICATED
:
92 // Report the authenticated state as 'CONNECTING' to avoid changing
93 // the interface between the plugin and webapp.
94 // TODO(garykac) Change to 'AUTHENTICATED' in M44 or once we've switched
95 // the client to NaCl.
97 case protocol::ConnectionToHost::CONNECTED
:
99 case protocol::ConnectionToHost::CLOSED
:
101 case protocol::ConnectionToHost::FAILED
:
105 return std::string();
108 // TODO(sergeyu): Ideally we should just pass ErrorCode to the webapp
109 // and let it handle it, but it would be hard to fix it now because
110 // client plugin and webapp versions may not be in sync. It should be
111 // easy to do after we are finished moving the client plugin to NaCl.
112 std::string
ConnectionErrorToString(protocol::ErrorCode error
) {
113 // Values returned by this function must match the
114 // remoting.ClientSession.Error enum in JS code.
119 case protocol::PEER_IS_OFFLINE
:
120 return "HOST_IS_OFFLINE";
122 case protocol::SESSION_REJECTED
:
123 case protocol::AUTHENTICATION_FAILED
:
124 return "SESSION_REJECTED";
126 case protocol::INCOMPATIBLE_PROTOCOL
:
127 return "INCOMPATIBLE_PROTOCOL";
129 case protocol::HOST_OVERLOAD
:
130 return "HOST_OVERLOAD";
132 case protocol::CHANNEL_CONNECTION_ERROR
:
133 case protocol::SIGNALING_ERROR
:
134 case protocol::SIGNALING_TIMEOUT
:
135 case protocol::UNKNOWN_ERROR
:
136 return "NETWORK_FAILURE";
138 DLOG(FATAL
) << "Unknown error code" << error
;
139 return std::string();
142 bool ParseAuthMethods(
143 const std::string
& auth_methods_str
,
144 std::vector
<protocol::AuthenticationMethod
>* auth_methods
) {
145 std::vector
<std::string
> parts
;
146 base::SplitString(auth_methods_str
, ',', &parts
);
147 for (std::vector
<std::string
>::iterator it
= parts
.begin();
148 it
!= parts
.end(); ++it
) {
149 protocol::AuthenticationMethod authentication_method
=
150 protocol::AuthenticationMethod::FromString(*it
);
151 if (authentication_method
.is_valid())
152 auth_methods
->push_back(authentication_method
);
154 if (auth_methods
->empty()) {
155 LOG(ERROR
) << "No valid authentication methods specified.";
162 // This flag blocks LOGs to the UI if we're already in the middle of logging
163 // to the UI. This prevents a potential infinite loop if we encounter an error
164 // while sending the log message to the UI.
165 bool g_logging_to_plugin
= false;
166 bool g_has_logging_instance
= false;
167 base::LazyInstance
<scoped_refptr
<base::SingleThreadTaskRunner
> >::Leaky
168 g_logging_task_runner
= LAZY_INSTANCE_INITIALIZER
;
169 base::LazyInstance
<base::WeakPtr
<ChromotingInstance
> >::Leaky
170 g_logging_instance
= LAZY_INSTANCE_INITIALIZER
;
171 base::LazyInstance
<base::Lock
>::Leaky
172 g_logging_lock
= LAZY_INSTANCE_INITIALIZER
;
173 logging::LogMessageHandlerFunction g_logging_old_handler
= nullptr;
177 // String sent in the "hello" message to the webapp to describe features.
178 const char ChromotingInstance::kApiFeatures
[] =
179 "highQualityScaling injectKeyEvent sendClipboardItem remapKey trapKey "
180 "notifyClientResolution pauseVideo pauseAudio asyncPin thirdPartyAuth "
181 "pinlessAuth extensionMessage allowMouseLock videoControl";
183 const char ChromotingInstance::kRequestedCapabilities
[] = "";
184 const char ChromotingInstance::kSupportedCapabilities
[] = "desktopShape";
186 ChromotingInstance::ChromotingInstance(PP_Instance pp_instance
)
187 : pp::Instance(pp_instance
),
189 plugin_task_runner_(new PluginThreadTaskRunner(&plugin_thread_delegate_
)),
190 context_(plugin_task_runner_
.get()),
191 input_tracker_(&mouse_input_filter_
),
192 touch_input_scaler_(&input_tracker_
),
193 key_mapper_(&touch_input_scaler_
),
194 input_handler_(&input_tracker_
),
195 cursor_setter_(this),
196 empty_cursor_filter_(&cursor_setter_
),
197 text_input_controller_(this),
198 use_async_pin_dialog_(false),
199 weak_factory_(this) {
201 // In NaCl global resources need to be initialized differently because they
202 // are not shared with Chrome.
203 thread_task_runner_handle_
.reset(
204 new base::ThreadTaskRunnerHandle(plugin_task_runner_
));
206 jingle_glue::JingleThreadWrapper::WrapTaskRunner(plugin_task_runner_
);
207 media::InitializeCPUSpecificYUVConversions();
209 // Register a global log handler.
210 ChromotingInstance::RegisterLogMessageHandler();
212 jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
216 nacl_io_init_ppapi(pp_instance
, pp::Module::Get()->get_browser_interface());
217 mount("", "/etc", "memfs", 0, "");
218 mount("", "/usr", "memfs", 0, "");
221 // Register for mouse, wheel and keyboard events.
222 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE
| PP_INPUTEVENT_CLASS_WHEEL
);
223 RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD
);
225 // Disable the client-side IME in Chrome.
226 text_input_controller_
.SetTextInputType(PP_TEXTINPUT_TYPE_NONE
);
228 // Resister this instance to handle debug log messsages.
229 RegisterLoggingInstance();
231 #if defined(USE_OPENSSL)
232 // Initialize random seed for libjingle. It's necessary only with OpenSSL.
233 char random_seed
[kRandomSeedSize
];
234 crypto::RandBytes(random_seed
, sizeof(random_seed
));
235 rtc::InitRandom(random_seed
, sizeof(random_seed
));
237 // Libjingle's SSL implementation is not really used, but it has to be
238 // initialized for NSS builds to make sure that RNG is initialized in NSS,
239 // because libjingle uses it.
240 rtc::InitializeSSL();
241 #endif // !defined(USE_OPENSSL)
243 // Send hello message.
244 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
245 data
->SetInteger("apiVersion", kApiVersion
);
246 data
->SetString("apiFeatures", kApiFeatures
);
247 data
->SetInteger("apiMinVersion", kApiMinMessagingVersion
);
248 data
->SetString("requestedCapabilities", kRequestedCapabilities
);
249 data
->SetString("supportedCapabilities", kSupportedCapabilities
);
251 PostLegacyJsonMessage("hello", data
.Pass());
254 ChromotingInstance::~ChromotingInstance() {
255 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
257 // Disconnect the client.
260 // Unregister this instance so that debug log messages will no longer be sent
261 // to it. This will stop all logging in all Chromoting instances.
262 UnregisterLoggingInstance();
264 plugin_task_runner_
->Quit();
266 // Ensure that nothing touches the plugin thread delegate after this point.
267 plugin_task_runner_
->DetachAndRunShutdownLoop();
269 // Stopping the context shuts down all chromoting threads.
273 bool ChromotingInstance::Init(uint32_t argc
,
275 const char* argv
[]) {
276 CHECK(!initialized_
);
279 VLOG(1) << "Started ChromotingInstance::Init";
281 // Check that the calling content is part of an app or extension. This is only
282 // necessary for non-PNaCl version of the plugin. Also PPB_URLUtil_Dev doesn't
283 // work in NaCl at the moment so the check fails in NaCl builds.
284 #if !defined(OS_NACL)
285 if (!IsCallerAppOrExtension()) {
286 LOG(ERROR
) << "Not an app or extension";
291 // Start all the threads.
297 void ChromotingInstance::HandleMessage(const pp::Var
& message
) {
298 if (!message
.is_string()) {
299 LOG(ERROR
) << "Received a message that is not a string.";
303 scoped_ptr
<base::Value
> json(
304 base::JSONReader::Read(message
.AsString(),
305 base::JSON_ALLOW_TRAILING_COMMAS
));
306 base::DictionaryValue
* message_dict
= nullptr;
308 base::DictionaryValue
* data
= nullptr;
310 !json
->GetAsDictionary(&message_dict
) ||
311 !message_dict
->GetString("method", &method
) ||
312 !message_dict
->GetDictionary("data", &data
)) {
313 LOG(ERROR
) << "Received invalid message:" << message
.AsString();
317 if (method
== "connect") {
318 HandleConnect(*data
);
319 } else if (method
== "disconnect") {
320 HandleDisconnect(*data
);
321 } else if (method
== "incomingIq") {
322 HandleOnIncomingIq(*data
);
323 } else if (method
== "releaseAllKeys") {
324 HandleReleaseAllKeys(*data
);
325 } else if (method
== "injectKeyEvent") {
326 HandleInjectKeyEvent(*data
);
327 } else if (method
== "remapKey") {
328 HandleRemapKey(*data
);
329 } else if (method
== "trapKey") {
330 HandleTrapKey(*data
);
331 } else if (method
== "sendClipboardItem") {
332 HandleSendClipboardItem(*data
);
333 } else if (method
== "notifyClientResolution") {
334 HandleNotifyClientResolution(*data
);
335 } else if (method
== "pauseVideo") {
336 HandlePauseVideo(*data
);
337 } else if (method
== "videoControl") {
338 HandleVideoControl(*data
);
339 } else if (method
== "pauseAudio") {
340 HandlePauseAudio(*data
);
341 } else if (method
== "useAsyncPinDialog") {
342 use_async_pin_dialog_
= true;
343 } else if (method
== "onPinFetched") {
344 HandleOnPinFetched(*data
);
345 } else if (method
== "onThirdPartyTokenFetched") {
346 HandleOnThirdPartyTokenFetched(*data
);
347 } else if (method
== "requestPairing") {
348 HandleRequestPairing(*data
);
349 } else if (method
== "extensionMessage") {
350 HandleExtensionMessage(*data
);
351 } else if (method
== "allowMouseLock") {
352 HandleAllowMouseLockMessage();
353 } else if (method
== "sendMouseInputWhenUnfocused") {
354 HandleSendMouseInputWhenUnfocused();
355 } else if (method
== "delegateLargeCursors") {
356 HandleDelegateLargeCursors();
357 } else if (method
== "enableDebugRegion") {
358 HandleEnableDebugRegion(*data
);
359 } else if (method
== "enableTouchEvents") {
360 HandleEnableTouchEvents();
364 void ChromotingInstance::DidChangeFocus(bool has_focus
) {
365 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
370 input_handler_
.DidChangeFocus(has_focus
);
372 mouse_locker_
->DidChangeFocus(has_focus
);
375 void ChromotingInstance::DidChangeView(const pp::View
& view
) {
376 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
379 webrtc::DesktopSize
size(
380 webrtc::DesktopSize(view
.GetRect().width(), view
.GetRect().height()));
381 mouse_input_filter_
.set_input_size(size
);
382 touch_input_scaler_
.set_input_size(size
);
385 video_renderer_
->OnViewChanged(view
);
388 bool ChromotingInstance::HandleInputEvent(const pp::InputEvent
& event
) {
389 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
394 return input_handler_
.HandleInputEvent(event
);
397 void ChromotingInstance::OnVideoDecodeError() {
400 // Assume that the decoder failure was caused by the host not encoding video
401 // correctly and report it as a protocol error.
402 // TODO(sergeyu): Consider using a different error code in case the decoder
403 // error was caused by some other problem.
404 OnConnectionState(protocol::ConnectionToHost::FAILED
,
405 protocol::INCOMPATIBLE_PROTOCOL
);
408 void ChromotingInstance::OnVideoFirstFrameReceived() {
409 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
410 PostLegacyJsonMessage("onFirstFrameReceived", data
.Pass());
413 void ChromotingInstance::OnVideoSize(const webrtc::DesktopSize
& size
,
414 const webrtc::DesktopVector
& dpi
) {
415 mouse_input_filter_
.set_output_size(size
);
416 touch_input_scaler_
.set_output_size(size
);
418 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
419 data
->SetInteger("width", size
.width());
420 data
->SetInteger("height", size
.height());
422 data
->SetInteger("x_dpi", dpi
.x());
424 data
->SetInteger("y_dpi", dpi
.y());
425 PostLegacyJsonMessage("onDesktopSize", data
.Pass());
428 void ChromotingInstance::OnVideoShape(const webrtc::DesktopRegion
& shape
) {
429 if (desktop_shape_
&& shape
.Equals(*desktop_shape_
))
432 desktop_shape_
.reset(new webrtc::DesktopRegion(shape
));
434 scoped_ptr
<base::ListValue
> rects_value(new base::ListValue());
435 for (webrtc::DesktopRegion::Iterator
i(shape
); !i
.IsAtEnd(); i
.Advance()) {
436 const webrtc::DesktopRect
& rect
= i
.rect();
437 scoped_ptr
<base::ListValue
> rect_value(new base::ListValue());
438 rect_value
->AppendInteger(rect
.left());
439 rect_value
->AppendInteger(rect
.top());
440 rect_value
->AppendInteger(rect
.width());
441 rect_value
->AppendInteger(rect
.height());
442 rects_value
->Append(rect_value
.release());
445 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
446 data
->Set("rects", rects_value
.release());
447 PostLegacyJsonMessage("onDesktopShape", data
.Pass());
450 void ChromotingInstance::OnVideoFrameDirtyRegion(
451 const webrtc::DesktopRegion
& dirty_region
) {
452 scoped_ptr
<base::ListValue
> rects_value(new base::ListValue());
453 for (webrtc::DesktopRegion::Iterator
i(dirty_region
); !i
.IsAtEnd();
455 const webrtc::DesktopRect
& rect
= i
.rect();
456 scoped_ptr
<base::ListValue
> rect_value(new base::ListValue());
457 rect_value
->AppendInteger(rect
.left());
458 rect_value
->AppendInteger(rect
.top());
459 rect_value
->AppendInteger(rect
.width());
460 rect_value
->AppendInteger(rect
.height());
461 rects_value
->Append(rect_value
.release());
464 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
465 data
->Set("rects", rects_value
.release());
466 PostLegacyJsonMessage("onDebugRegion", data
.Pass());
469 void ChromotingInstance::OnConnectionState(
470 protocol::ConnectionToHost::State state
,
471 protocol::ErrorCode error
) {
472 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
473 data
->SetString("state", ConnectionStateToString(state
));
474 data
->SetString("error", ConnectionErrorToString(error
));
475 PostLegacyJsonMessage("onConnectionStatus", data
.Pass());
478 void ChromotingInstance::FetchThirdPartyToken(
479 const GURL
& token_url
,
480 const std::string
& host_public_key
,
481 const std::string
& scope
,
482 base::WeakPtr
<TokenFetcherProxy
> token_fetcher_proxy
) {
483 // Once the Session object calls this function, it won't continue the
484 // authentication until the callback is called (or connection is canceled).
485 // So, it's impossible to reach this with a callback already registered.
486 DCHECK(!token_fetcher_proxy_
.get());
487 token_fetcher_proxy_
= token_fetcher_proxy
;
488 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
489 data
->SetString("tokenUrl", token_url
.spec());
490 data
->SetString("hostPublicKey", host_public_key
);
491 data
->SetString("scope", scope
);
492 PostLegacyJsonMessage("fetchThirdPartyToken", data
.Pass());
495 void ChromotingInstance::OnConnectionReady(bool ready
) {
496 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
497 data
->SetBoolean("ready", ready
);
498 PostLegacyJsonMessage("onConnectionReady", data
.Pass());
501 void ChromotingInstance::OnRouteChanged(const std::string
& channel_name
,
502 const protocol::TransportRoute
& route
) {
503 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
504 data
->SetString("channel", channel_name
);
505 data
->SetString("connectionType",
506 protocol::TransportRoute::GetTypeString(route
.type
));
507 PostLegacyJsonMessage("onRouteChanged", data
.Pass());
510 void ChromotingInstance::SetCapabilities(const std::string
& capabilities
) {
511 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
512 data
->SetString("capabilities", capabilities
);
513 PostLegacyJsonMessage("setCapabilities", data
.Pass());
516 void ChromotingInstance::SetPairingResponse(
517 const protocol::PairingResponse
& pairing_response
) {
518 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
519 data
->SetString("clientId", pairing_response
.client_id());
520 data
->SetString("sharedSecret", pairing_response
.shared_secret());
521 PostLegacyJsonMessage("pairingResponse", data
.Pass());
524 void ChromotingInstance::DeliverHostMessage(
525 const protocol::ExtensionMessage
& message
) {
526 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
527 data
->SetString("type", message
.type());
528 data
->SetString("data", message
.data());
529 PostLegacyJsonMessage("extensionMessage", data
.Pass());
532 void ChromotingInstance::FetchSecretFromDialog(
533 bool pairing_supported
,
534 const protocol::SecretFetchedCallback
& secret_fetched_callback
) {
535 // Once the Session object calls this function, it won't continue the
536 // authentication until the callback is called (or connection is canceled).
537 // So, it's impossible to reach this with a callback already registered.
538 DCHECK(secret_fetched_callback_
.is_null());
539 secret_fetched_callback_
= secret_fetched_callback
;
540 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
541 data
->SetBoolean("pairingSupported", pairing_supported
);
542 PostLegacyJsonMessage("fetchPin", data
.Pass());
545 void ChromotingInstance::FetchSecretFromString(
546 const std::string
& shared_secret
,
547 bool pairing_supported
,
548 const protocol::SecretFetchedCallback
& secret_fetched_callback
) {
549 secret_fetched_callback
.Run(shared_secret
);
552 protocol::ClipboardStub
* ChromotingInstance::GetClipboardStub() {
553 // TODO(sergeyu): Move clipboard handling to a separate class.
558 protocol::CursorShapeStub
* ChromotingInstance::GetCursorShapeStub() {
559 return &empty_cursor_filter_
;
562 void ChromotingInstance::InjectClipboardEvent(
563 const protocol::ClipboardEvent
& event
) {
564 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
565 data
->SetString("mimeType", event
.mime_type());
566 data
->SetString("item", event
.data());
567 PostLegacyJsonMessage("injectClipboardItem", data
.Pass());
570 void ChromotingInstance::SetCursorShape(
571 const protocol::CursorShapeInfo
& cursor_shape
) {
572 // If the delegated cursor is empty then stop rendering a DOM cursor.
573 if (IsCursorShapeEmpty(cursor_shape
)) {
574 PostChromotingMessage("unsetCursorShape", pp::VarDictionary());
578 // Cursor is not empty, so pass it to JS to render.
579 const int kBytesPerPixel
= sizeof(uint32_t);
580 const size_t buffer_size
=
581 cursor_shape
.height() * cursor_shape
.width() * kBytesPerPixel
;
583 pp::VarArrayBuffer
array_buffer(buffer_size
);
584 void* dst
= array_buffer
.Map();
585 memcpy(dst
, cursor_shape
.data().data(), buffer_size
);
586 array_buffer
.Unmap();
588 pp::VarDictionary dictionary
;
589 dictionary
.Set(pp::Var("width"), cursor_shape
.width());
590 dictionary
.Set(pp::Var("height"), cursor_shape
.height());
591 dictionary
.Set(pp::Var("hotspotX"), cursor_shape
.hotspot_x());
592 dictionary
.Set(pp::Var("hotspotY"), cursor_shape
.hotspot_y());
593 dictionary
.Set(pp::Var("data"), array_buffer
);
594 PostChromotingMessage("setCursorShape", dictionary
);
597 void ChromotingInstance::HandleConnect(const base::DictionaryValue
& data
) {
598 std::string local_jid
;
599 std::string host_jid
;
600 std::string host_public_key
;
601 std::string auth_methods_str
;
602 std::string authentication_tag
;
603 std::vector
<protocol::AuthenticationMethod
> auth_methods
;
604 if (!data
.GetString("hostJid", &host_jid
) ||
605 !data
.GetString("hostPublicKey", &host_public_key
) ||
606 !data
.GetString("localJid", &local_jid
) ||
607 !data
.GetString("authenticationMethods", &auth_methods_str
) ||
608 !ParseAuthMethods(auth_methods_str
, &auth_methods
) ||
609 !data
.GetString("authenticationTag", &authentication_tag
)) {
610 LOG(ERROR
) << "Invalid connect() data.";
614 std::string client_pairing_id
;
615 data
.GetString("clientPairingId", &client_pairing_id
);
616 std::string client_paired_secret
;
617 data
.GetString("clientPairedSecret", &client_paired_secret
);
619 protocol::FetchSecretCallback fetch_secret_callback
;
620 if (use_async_pin_dialog_
) {
621 fetch_secret_callback
= base::Bind(
622 &ChromotingInstance::FetchSecretFromDialog
, weak_factory_
.GetWeakPtr());
624 std::string shared_secret
;
625 if (!data
.GetString("sharedSecret", &shared_secret
)) {
626 LOG(ERROR
) << "sharedSecret not specified in connect().";
629 fetch_secret_callback
=
630 base::Bind(&ChromotingInstance::FetchSecretFromString
, shared_secret
);
633 // Read the list of capabilities, if any.
634 std::string capabilities
;
635 if (data
.HasKey("capabilities")) {
636 if (!data
.GetString("capabilities", &capabilities
)) {
637 LOG(ERROR
) << "Invalid connect() data.";
642 VLOG(0) << "Connecting to " << host_jid
643 << ". Local jid: " << local_jid
<< ".";
646 std::string key_filter
;
647 if (!data
.GetString("keyFilter", &key_filter
)) {
649 normalizing_input_filter_
.reset(new protocol::InputFilter(&key_mapper_
));
650 } else if (key_filter
== "mac") {
651 normalizing_input_filter_
.reset(
652 new NormalizingInputFilterMac(&key_mapper_
));
653 } else if (key_filter
== "cros") {
654 normalizing_input_filter_
.reset(
655 new NormalizingInputFilterCros(&key_mapper_
));
657 DCHECK(key_filter
.empty());
658 normalizing_input_filter_
.reset(new protocol::InputFilter(&key_mapper_
));
660 #elif defined(OS_MACOSX)
661 normalizing_input_filter_
.reset(new NormalizingInputFilterMac(&key_mapper_
));
662 #elif defined(OS_CHROMEOS)
663 normalizing_input_filter_
.reset(new NormalizingInputFilterCros(&key_mapper_
));
665 normalizing_input_filter_
.reset(new protocol::InputFilter(&key_mapper_
));
667 input_handler_
.set_input_stub(normalizing_input_filter_
.get());
669 // PPB_VideoDecoder is not always enabled because it's broken in some versions
670 // of Chrome. See crbug.com/447403 .
671 bool enable_video_decode_renderer
= false;
672 if (data
.GetBoolean("enableVideoDecodeRenderer",
673 &enable_video_decode_renderer
) &&
674 enable_video_decode_renderer
) {
675 LogToWebapp("Initializing 3D renderer.");
676 video_renderer_
.reset(new PepperVideoRenderer3D());
677 if (!video_renderer_
->Initialize(this, context_
, this))
678 video_renderer_
.reset();
681 // If we didn't initialize 3D renderer then use the 2D renderer.
682 if (!video_renderer_
) {
683 LogToWebapp("Initializing 2D renderer.");
684 video_renderer_
.reset(new PepperVideoRenderer2D());
685 if (!video_renderer_
->Initialize(this, context_
, this))
686 video_renderer_
.reset();
689 CHECK(video_renderer_
);
691 if (!plugin_view_
.is_null())
692 video_renderer_
->OnViewChanged(plugin_view_
);
694 scoped_ptr
<AudioPlayer
> audio_player(new PepperAudioPlayer(this));
695 client_
.reset(new ChromotingClient(&context_
, this, video_renderer_
.get(),
696 audio_player
.Pass()));
698 // Connect the input pipeline to the protocol stub & initialize components.
699 mouse_input_filter_
.set_input_stub(client_
->input_stub());
700 if (!plugin_view_
.is_null()) {
701 webrtc::DesktopSize
size(plugin_view_
.GetRect().width(),
702 plugin_view_
.GetRect().height());
703 mouse_input_filter_
.set_input_size(size
);
704 touch_input_scaler_
.set_input_size(size
);
707 // Setup the signal strategy.
708 signal_strategy_
.reset(new DelegatingSignalStrategy(
709 local_jid
, base::Bind(&ChromotingInstance::SendOutgoingIq
,
710 weak_factory_
.GetWeakPtr())));
712 // Create TransportFactory.
713 scoped_ptr
<protocol::TransportFactory
> transport_factory(
714 new protocol::LibjingleTransportFactory(
715 signal_strategy_
.get(),
716 PepperPortAllocator::Create(this).Pass(),
717 protocol::NetworkSettings(
718 protocol::NetworkSettings::NAT_TRAVERSAL_FULL
)));
720 // Create Authenticator.
721 scoped_ptr
<protocol::ThirdPartyClientAuthenticator::TokenFetcher
>
722 token_fetcher(new TokenFetcherProxy(
723 base::Bind(&ChromotingInstance::FetchThirdPartyToken
,
724 weak_factory_
.GetWeakPtr()),
726 scoped_ptr
<protocol::Authenticator
> authenticator(
727 new protocol::NegotiatingClientAuthenticator(
728 client_pairing_id
, client_paired_secret
, authentication_tag
,
729 fetch_secret_callback
, token_fetcher
.Pass(), auth_methods
));
731 // Kick off the connection.
732 client_
->Start(signal_strategy_
.get(), authenticator
.Pass(),
733 transport_factory
.Pass(), host_jid
, capabilities
);
735 // Start timer that periodically sends perf stats.
736 plugin_task_runner_
->PostDelayedTask(
737 FROM_HERE
, base::Bind(&ChromotingInstance::SendPerfStats
,
738 weak_factory_
.GetWeakPtr()),
739 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs
));
742 void ChromotingInstance::HandleDisconnect(const base::DictionaryValue
& data
) {
743 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
747 void ChromotingInstance::HandleOnIncomingIq(const base::DictionaryValue
& data
) {
749 if (!data
.GetString("iq", &iq
)) {
750 LOG(ERROR
) << "Invalid incomingIq() data.";
754 // Just ignore the message if it's received before Connect() is called. It's
755 // likely to be a leftover from a previous session, so it's safe to ignore it.
756 if (signal_strategy_
)
757 signal_strategy_
->OnIncomingMessage(iq
);
760 void ChromotingInstance::HandleReleaseAllKeys(
761 const base::DictionaryValue
& data
) {
763 input_tracker_
.ReleaseAll();
766 void ChromotingInstance::HandleInjectKeyEvent(
767 const base::DictionaryValue
& data
) {
769 bool is_pressed
= false;
770 if (!data
.GetInteger("usbKeycode", &usb_keycode
) ||
771 !data
.GetBoolean("pressed", &is_pressed
)) {
772 LOG(ERROR
) << "Invalid injectKeyEvent.";
776 protocol::KeyEvent event
;
777 event
.set_usb_keycode(usb_keycode
);
778 event
.set_pressed(is_pressed
);
780 // Inject after the KeyEventMapper, so the event won't get mapped or trapped.
782 input_tracker_
.InjectKeyEvent(event
);
785 void ChromotingInstance::HandleRemapKey(const base::DictionaryValue
& data
) {
786 int from_keycode
= 0;
788 if (!data
.GetInteger("fromKeycode", &from_keycode
) ||
789 !data
.GetInteger("toKeycode", &to_keycode
)) {
790 LOG(ERROR
) << "Invalid remapKey.";
794 key_mapper_
.RemapKey(from_keycode
, to_keycode
);
797 void ChromotingInstance::HandleTrapKey(const base::DictionaryValue
& data
) {
800 if (!data
.GetInteger("keycode", &keycode
) ||
801 !data
.GetBoolean("trap", &trap
)) {
802 LOG(ERROR
) << "Invalid trapKey.";
806 key_mapper_
.TrapKey(keycode
, trap
);
809 void ChromotingInstance::HandleSendClipboardItem(
810 const base::DictionaryValue
& data
) {
811 std::string mime_type
;
813 if (!data
.GetString("mimeType", &mime_type
) ||
814 !data
.GetString("item", &item
)) {
815 LOG(ERROR
) << "Invalid sendClipboardItem data.";
818 if (!IsConnected()) {
821 protocol::ClipboardEvent event
;
822 event
.set_mime_type(mime_type
);
823 event
.set_data(item
);
824 client_
->clipboard_forwarder()->InjectClipboardEvent(event
);
827 void ChromotingInstance::HandleNotifyClientResolution(
828 const base::DictionaryValue
& data
) {
831 int x_dpi
= kDefaultDPI
;
832 int y_dpi
= kDefaultDPI
;
833 if (!data
.GetInteger("width", &width
) ||
834 !data
.GetInteger("height", &height
) ||
835 !data
.GetInteger("x_dpi", &x_dpi
) ||
836 !data
.GetInteger("y_dpi", &y_dpi
) ||
837 width
<= 0 || height
<= 0 ||
838 x_dpi
<= 0 || y_dpi
<= 0) {
839 LOG(ERROR
) << "Invalid notifyClientResolution.";
843 if (!IsConnected()) {
847 protocol::ClientResolution client_resolution
;
848 client_resolution
.set_width(width
);
849 client_resolution
.set_height(height
);
850 client_resolution
.set_x_dpi(x_dpi
);
851 client_resolution
.set_y_dpi(y_dpi
);
853 // Include the legacy width & height in DIPs for use by older hosts.
854 client_resolution
.set_dips_width((width
* kDefaultDPI
) / x_dpi
);
855 client_resolution
.set_dips_height((height
* kDefaultDPI
) / y_dpi
);
857 client_
->host_stub()->NotifyClientResolution(client_resolution
);
860 void ChromotingInstance::HandlePauseVideo(const base::DictionaryValue
& data
) {
861 if (!data
.HasKey("pause")) {
862 LOG(ERROR
) << "Invalid pauseVideo.";
865 HandleVideoControl(data
);
868 void ChromotingInstance::HandleVideoControl(const base::DictionaryValue
& data
) {
869 protocol::VideoControl video_control
;
870 bool pause_video
= false;
871 if (data
.GetBoolean("pause", &pause_video
)) {
872 video_control
.set_enable(!pause_video
);
874 bool lossless_encode
= false;
875 if (data
.GetBoolean("losslessEncode", &lossless_encode
)) {
876 video_control
.set_lossless_encode(lossless_encode
);
878 bool lossless_color
= false;
879 if (data
.GetBoolean("losslessColor", &lossless_color
)) {
880 video_control
.set_lossless_color(lossless_color
);
882 if (!IsConnected()) {
885 client_
->host_stub()->ControlVideo(video_control
);
888 void ChromotingInstance::HandlePauseAudio(const base::DictionaryValue
& data
) {
890 if (!data
.GetBoolean("pause", &pause
)) {
891 LOG(ERROR
) << "Invalid pauseAudio.";
894 if (!IsConnected()) {
897 protocol::AudioControl audio_control
;
898 audio_control
.set_enable(!pause
);
899 client_
->host_stub()->ControlAudio(audio_control
);
901 void ChromotingInstance::HandleOnPinFetched(const base::DictionaryValue
& data
) {
903 if (!data
.GetString("pin", &pin
)) {
904 LOG(ERROR
) << "Invalid onPinFetched.";
907 if (!secret_fetched_callback_
.is_null()) {
908 secret_fetched_callback_
.Run(pin
);
909 secret_fetched_callback_
.Reset();
911 LOG(WARNING
) << "Ignored OnPinFetched received without a pending fetch.";
915 void ChromotingInstance::HandleOnThirdPartyTokenFetched(
916 const base::DictionaryValue
& data
) {
918 std::string shared_secret
;
919 if (!data
.GetString("token", &token
) ||
920 !data
.GetString("sharedSecret", &shared_secret
)) {
921 LOG(ERROR
) << "Invalid onThirdPartyTokenFetched data.";
924 if (token_fetcher_proxy_
.get()) {
925 token_fetcher_proxy_
->OnTokenFetched(token
, shared_secret
);
926 token_fetcher_proxy_
.reset();
928 LOG(WARNING
) << "Ignored OnThirdPartyTokenFetched without a pending fetch.";
932 void ChromotingInstance::HandleRequestPairing(
933 const base::DictionaryValue
& data
) {
934 std::string client_name
;
935 if (!data
.GetString("clientName", &client_name
)) {
936 LOG(ERROR
) << "Invalid requestPairing";
939 if (!IsConnected()) {
942 protocol::PairingRequest pairing_request
;
943 pairing_request
.set_client_name(client_name
);
944 client_
->host_stub()->RequestPairing(pairing_request
);
947 void ChromotingInstance::HandleExtensionMessage(
948 const base::DictionaryValue
& data
) {
950 std::string message_data
;
951 if (!data
.GetString("type", &type
) ||
952 !data
.GetString("data", &message_data
)) {
953 LOG(ERROR
) << "Invalid extensionMessage.";
956 if (!IsConnected()) {
959 protocol::ExtensionMessage message
;
960 message
.set_type(type
);
961 message
.set_data(message_data
);
962 client_
->host_stub()->DeliverClientMessage(message
);
965 void ChromotingInstance::HandleAllowMouseLockMessage() {
966 // Create the mouse lock handler and route cursor shape messages through it.
967 mouse_locker_
.reset(new PepperMouseLocker(
968 this, base::Bind(&PepperInputHandler::set_send_mouse_move_deltas
,
969 base::Unretained(&input_handler_
)),
971 empty_cursor_filter_
.set_cursor_stub(mouse_locker_
.get());
974 void ChromotingInstance::HandleSendMouseInputWhenUnfocused() {
975 input_handler_
.set_send_mouse_input_when_unfocused(true);
978 void ChromotingInstance::HandleDelegateLargeCursors() {
979 cursor_setter_
.set_delegate_stub(this);
982 void ChromotingInstance::HandleEnableDebugRegion(
983 const base::DictionaryValue
& data
) {
985 if (!data
.GetBoolean("enable", &enable
)) {
986 LOG(ERROR
) << "Invalid enableDebugRegion.";
990 video_renderer_
->EnableDebugDirtyRegion(enable
);
993 void ChromotingInstance::HandleEnableTouchEvents() {
994 RequestInputEvents(PP_INPUTEVENT_CLASS_TOUCH
);
997 void ChromotingInstance::Disconnect() {
998 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
1000 VLOG(0) << "Disconnecting from host.";
1002 // Disconnect the input pipeline and teardown the connection.
1003 mouse_input_filter_
.set_input_stub(nullptr);
1005 video_renderer_
.reset();
1008 void ChromotingInstance::PostChromotingMessage(const std::string
& method
,
1009 const pp::VarDictionary
& data
) {
1010 pp::VarDictionary message
;
1011 message
.Set(pp::Var("method"), pp::Var(method
));
1012 message
.Set(pp::Var("data"), data
);
1013 PostMessage(message
);
1016 void ChromotingInstance::PostLegacyJsonMessage(
1017 const std::string
& method
,
1018 scoped_ptr
<base::DictionaryValue
> data
) {
1019 scoped_ptr
<base::DictionaryValue
> message(new base::DictionaryValue());
1020 message
->SetString("method", method
);
1021 message
->Set("data", data
.release());
1023 std::string message_json
;
1024 base::JSONWriter::Write(message
.get(), &message_json
);
1025 PostMessage(pp::Var(message_json
));
1028 void ChromotingInstance::SendTrappedKey(uint32 usb_keycode
, bool pressed
) {
1029 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
1030 data
->SetInteger("usbKeycode", usb_keycode
);
1031 data
->SetBoolean("pressed", pressed
);
1032 PostLegacyJsonMessage("trappedKeyEvent", data
.Pass());
1035 void ChromotingInstance::SendOutgoingIq(const std::string
& iq
) {
1036 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
1037 data
->SetString("iq", iq
);
1038 PostLegacyJsonMessage("sendOutgoingIq", data
.Pass());
1041 void ChromotingInstance::SendPerfStats() {
1042 if (!video_renderer_
.get()) {
1046 plugin_task_runner_
->PostDelayedTask(
1047 FROM_HERE
, base::Bind(&ChromotingInstance::SendPerfStats
,
1048 weak_factory_
.GetWeakPtr()),
1049 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs
));
1051 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
1052 ChromotingStats
* stats
= video_renderer_
->GetStats();
1053 data
->SetDouble("videoBandwidth", stats
->video_bandwidth()->Rate());
1054 data
->SetDouble("videoFrameRate", stats
->video_frame_rate()->Rate());
1055 data
->SetDouble("captureLatency", stats
->video_capture_ms()->Average());
1056 data
->SetDouble("encodeLatency", stats
->video_encode_ms()->Average());
1057 data
->SetDouble("decodeLatency", stats
->video_decode_ms()->Average());
1058 data
->SetDouble("renderLatency", stats
->video_paint_ms()->Average());
1059 data
->SetDouble("roundtripLatency", stats
->round_trip_ms()->Average());
1060 PostLegacyJsonMessage("onPerfStats", data
.Pass());
1064 void ChromotingInstance::RegisterLogMessageHandler() {
1065 base::AutoLock
lock(g_logging_lock
.Get());
1067 VLOG(1) << "Registering global log handler";
1069 // Record previous handler so we can call it in a chain.
1070 g_logging_old_handler
= logging::GetLogMessageHandler();
1072 // Set up log message handler.
1073 // This is not thread-safe so we need it within our lock.
1074 logging::SetLogMessageHandler(&LogToUI
);
1077 void ChromotingInstance::RegisterLoggingInstance() {
1078 base::AutoLock
lock(g_logging_lock
.Get());
1080 // Register this instance as the one that will handle all logging calls
1081 // and display them to the user.
1082 // If multiple plugins are run, then the last one registered will handle all
1083 // logging for all instances.
1084 g_logging_instance
.Get() = weak_factory_
.GetWeakPtr();
1085 g_logging_task_runner
.Get() = plugin_task_runner_
;
1086 g_has_logging_instance
= true;
1089 void ChromotingInstance::UnregisterLoggingInstance() {
1090 base::AutoLock
lock(g_logging_lock
.Get());
1092 // Don't unregister unless we're the currently registered instance.
1093 if (this != g_logging_instance
.Get().get())
1096 // Unregister this instance for logging.
1097 g_has_logging_instance
= false;
1098 g_logging_instance
.Get().reset();
1099 g_logging_task_runner
.Get() = nullptr;
1101 VLOG(1) << "Unregistering global log handler";
1105 bool ChromotingInstance::LogToUI(int severity
, const char* file
, int line
,
1106 size_t message_start
,
1107 const std::string
& str
) {
1108 // Note that we're reading |g_has_logging_instance| outside of a lock.
1109 // This lockless read is done so that we don't needlessly slow down global
1110 // logging with a lock for each log message.
1112 // This lockless read is safe because:
1114 // Misreading a false value (when it should be true) means that we'll simply
1115 // skip processing a few log messages.
1117 // Misreading a true value (when it should be false) means that we'll take
1118 // the lock and check |g_logging_instance| unnecessarily. This is not
1119 // problematic because we always set |g_logging_instance| inside a lock.
1120 if (g_has_logging_instance
) {
1121 scoped_refptr
<base::SingleThreadTaskRunner
> logging_task_runner
;
1122 base::WeakPtr
<ChromotingInstance
> logging_instance
;
1125 base::AutoLock
lock(g_logging_lock
.Get());
1126 // If we're on the logging thread and |g_logging_to_plugin| is set then
1127 // this LOG message came from handling a previous LOG message and we
1128 // should skip it to avoid an infinite loop of LOG messages.
1129 if (!g_logging_task_runner
.Get()->BelongsToCurrentThread() ||
1130 !g_logging_to_plugin
) {
1131 logging_task_runner
= g_logging_task_runner
.Get();
1132 logging_instance
= g_logging_instance
.Get();
1136 if (logging_task_runner
.get()) {
1137 std::string message
= remoting::GetTimestampString();
1138 message
+= (str
.c_str() + message_start
);
1140 logging_task_runner
->PostTask(
1141 FROM_HERE
, base::Bind(&ChromotingInstance::ProcessLogToUI
,
1142 logging_instance
, message
));
1146 if (g_logging_old_handler
)
1147 return (g_logging_old_handler
)(severity
, file
, line
, message_start
, str
);
1151 void ChromotingInstance::ProcessLogToUI(const std::string
& message
) {
1152 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
1154 // This flag (which is set only here) is used to prevent LogToUI from posting
1155 // new tasks while we're in the middle of servicing a LOG call. This can
1156 // happen if the call to LogDebugInfo tries to LOG anything.
1157 // Since it is read on the plugin thread, we don't need to lock to set it.
1158 g_logging_to_plugin
= true;
1159 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
1160 data
->SetString("message", message
);
1161 PostLegacyJsonMessage("logDebugMessage", data
.Pass());
1162 g_logging_to_plugin
= false;
1165 bool ChromotingInstance::IsCallerAppOrExtension() {
1166 const pp::URLUtil_Dev
* url_util
= pp::URLUtil_Dev::Get();
1170 PP_URLComponents_Dev url_components
;
1171 pp::Var url_var
= url_util
->GetDocumentURL(this, &url_components
);
1172 if (!url_var
.is_string())
1175 std::string url
= url_var
.AsString();
1176 std::string url_scheme
= url
.substr(url_components
.scheme
.begin
,
1177 url_components
.scheme
.len
);
1178 return url_scheme
== kChromeExtensionUrlScheme
;
1181 bool ChromotingInstance::IsConnected() {
1183 (client_
->connection_state() == protocol::ConnectionToHost::CONNECTED
);
1186 void ChromotingInstance::LogToWebapp(const std::string
& message
) {
1187 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
1189 LOG(ERROR
) << message
;
1191 #if !defined(OS_NACL)
1192 // Log messages are forwarded to the webapp only in PNaCl version of the
1193 // plugin, so ProcessLogToUI() needs to be called explicitly in the non-PNaCl
1195 ProcessLogToUI(message
);
1196 #endif // !defined(OS_NACL)
1199 } // namespace remoting