tuple: update to make use of C++11
[chromium-blink-merge.git] / remoting / client / plugin / chromoting_instance.cc
bloba56955278e0eefbcbec13dee594ab4ff3fce0d4b
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "remoting/client/plugin/chromoting_instance.h"
7 #include <string>
8 #include <vector>
10 #if defined(OS_NACL)
11 #include <nacl_io/nacl_io.h>
12 #include <sys/mount.h>
13 #endif
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/frame_consumer_proxy.h"
41 #include "remoting/client/plugin/delegating_signal_strategy.h"
42 #include "remoting/client/plugin/media_source_video_renderer.h"
43 #include "remoting/client/plugin/normalizing_input_filter_cros.h"
44 #include "remoting/client/plugin/normalizing_input_filter_mac.h"
45 #include "remoting/client/plugin/pepper_audio_player.h"
46 #include "remoting/client/plugin/pepper_mouse_locker.h"
47 #include "remoting/client/plugin/pepper_port_allocator.h"
48 #include "remoting/client/plugin/pepper_view.h"
49 #include "remoting/client/software_video_renderer.h"
50 #include "remoting/client/token_fetcher_proxy.h"
51 #include "remoting/protocol/connection_to_host.h"
52 #include "remoting/protocol/host_stub.h"
53 #include "remoting/protocol/libjingle_transport_factory.h"
54 #include "third_party/webrtc/base/helpers.h"
55 #include "third_party/webrtc/base/ssladapter.h"
56 #include "url/gurl.h"
58 // Windows defines 'PostMessage', so we have to undef it.
59 #if defined(PostMessage)
60 #undef PostMessage
61 #endif
63 namespace remoting {
65 namespace {
67 // Default DPI to assume for old clients that use notifyClientResolution.
68 const int kDefaultDPI = 96;
70 // Interval at which to sample performance statistics.
71 const int kPerfStatsIntervalMs = 1000;
73 // URL scheme used by Chrome apps and extensions.
74 const char kChromeExtensionUrlScheme[] = "chrome-extension";
76 #if defined(USE_OPENSSL)
77 // Size of the random seed blob used to initialize RNG in libjingle. Libjingle
78 // uses the seed only for OpenSSL builds. OpenSSL needs at least 32 bytes of
79 // entropy (see http://wiki.openssl.org/index.php/Random_Numbers), but stores
80 // 1039 bytes of state, so we initialize it with 1k or random data.
81 const int kRandomSeedSize = 1024;
82 #endif // defined(USE_OPENSSL)
84 std::string ConnectionStateToString(protocol::ConnectionToHost::State state) {
85 // Values returned by this function must match the
86 // remoting.ClientSession.State enum in JS code.
87 switch (state) {
88 case protocol::ConnectionToHost::INITIALIZING:
89 return "INITIALIZING";
90 case protocol::ConnectionToHost::CONNECTING:
91 return "CONNECTING";
92 case protocol::ConnectionToHost::AUTHENTICATED:
93 // Report the authenticated state as 'CONNECTING' to avoid changing
94 // the interface between the plugin and webapp.
95 return "CONNECTING";
96 case protocol::ConnectionToHost::CONNECTED:
97 return "CONNECTED";
98 case protocol::ConnectionToHost::CLOSED:
99 return "CLOSED";
100 case protocol::ConnectionToHost::FAILED:
101 return "FAILED";
103 NOTREACHED();
104 return std::string();
107 // TODO(sergeyu): Ideally we should just pass ErrorCode to the webapp
108 // and let it handle it, but it would be hard to fix it now because
109 // client plugin and webapp versions may not be in sync. It should be
110 // easy to do after we are finished moving the client plugin to NaCl.
111 std::string ConnectionErrorToString(protocol::ErrorCode error) {
112 // Values returned by this function must match the
113 // remoting.ClientSession.Error enum in JS code.
114 switch (error) {
115 case protocol::OK:
116 return "NONE";
118 case protocol::PEER_IS_OFFLINE:
119 return "HOST_IS_OFFLINE";
121 case protocol::SESSION_REJECTED:
122 case protocol::AUTHENTICATION_FAILED:
123 return "SESSION_REJECTED";
125 case protocol::INCOMPATIBLE_PROTOCOL:
126 return "INCOMPATIBLE_PROTOCOL";
128 case protocol::HOST_OVERLOAD:
129 return "HOST_OVERLOAD";
131 case protocol::CHANNEL_CONNECTION_ERROR:
132 case protocol::SIGNALING_ERROR:
133 case protocol::SIGNALING_TIMEOUT:
134 case protocol::UNKNOWN_ERROR:
135 return "NETWORK_FAILURE";
137 DLOG(FATAL) << "Unknown error code" << error;
138 return std::string();
141 bool ParseAuthMethods(
142 const std::string& auth_methods_str,
143 std::vector<protocol::AuthenticationMethod>* auth_methods) {
144 std::vector<std::string> parts;
145 base::SplitString(auth_methods_str, ',', &parts);
146 for (std::vector<std::string>::iterator it = parts.begin();
147 it != parts.end(); ++it) {
148 protocol::AuthenticationMethod authentication_method =
149 protocol::AuthenticationMethod::FromString(*it);
150 if (authentication_method.is_valid())
151 auth_methods->push_back(authentication_method);
153 if (auth_methods->empty()) {
154 LOG(ERROR) << "No valid authentication methods specified.";
155 return false;
158 return true;
161 // This flag blocks LOGs to the UI if we're already in the middle of logging
162 // to the UI. This prevents a potential infinite loop if we encounter an error
163 // while sending the log message to the UI.
164 bool g_logging_to_plugin = false;
165 bool g_has_logging_instance = false;
166 base::LazyInstance<scoped_refptr<base::SingleThreadTaskRunner> >::Leaky
167 g_logging_task_runner = LAZY_INSTANCE_INITIALIZER;
168 base::LazyInstance<base::WeakPtr<ChromotingInstance> >::Leaky
169 g_logging_instance = LAZY_INSTANCE_INITIALIZER;
170 base::LazyInstance<base::Lock>::Leaky
171 g_logging_lock = LAZY_INSTANCE_INITIALIZER;
172 logging::LogMessageHandlerFunction g_logging_old_handler = NULL;
174 } // namespace
176 // String sent in the "hello" message to the webapp to describe features.
177 const char ChromotingInstance::kApiFeatures[] =
178 "highQualityScaling injectKeyEvent sendClipboardItem remapKey trapKey "
179 "notifyClientResolution pauseVideo pauseAudio asyncPin thirdPartyAuth "
180 "pinlessAuth extensionMessage allowMouseLock mediaSourceRendering "
181 "videoControl";
183 const char ChromotingInstance::kRequestedCapabilities[] = "";
184 const char ChromotingInstance::kSupportedCapabilities[] = "desktopShape";
186 ChromotingInstance::ChromotingInstance(PP_Instance pp_instance)
187 : pp::Instance(pp_instance),
188 initialized_(false),
189 plugin_task_runner_(new PluginThreadTaskRunner(&plugin_thread_delegate_)),
190 context_(plugin_task_runner_.get()),
191 input_tracker_(&mouse_input_filter_),
192 key_mapper_(&input_tracker_),
193 cursor_setter_(this),
194 empty_cursor_filter_(&cursor_setter_),
195 text_input_controller_(this),
196 use_async_pin_dialog_(false),
197 use_media_source_rendering_(false),
198 weak_factory_(this) {
199 #if defined(OS_NACL)
200 // In NaCl global resources need to be initialized differently because they
201 // are not shared with Chrome.
202 thread_task_runner_handle_.reset(
203 new base::ThreadTaskRunnerHandle(plugin_task_runner_));
204 thread_wrapper_.reset(
205 new jingle_glue::JingleThreadWrapper(plugin_task_runner_));
206 media::InitializeCPUSpecificYUVConversions();
208 // Register a global log handler.
209 ChromotingInstance::RegisterLogMessageHandler();
210 #else
211 jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
212 #endif
214 #if defined(OS_NACL)
215 nacl_io_init_ppapi(pp_instance, pp::Module::Get()->get_browser_interface());
216 mount("", "/etc", "memfs", 0, "");
217 mount("", "/usr", "memfs", 0, "");
218 #endif
220 // Register for mouse, wheel and keyboard events.
221 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL);
222 RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
224 // Disable the client-side IME in Chrome.
225 text_input_controller_.SetTextInputType(PP_TEXTINPUT_TYPE_NONE);
227 // Resister this instance to handle debug log messsages.
228 RegisterLoggingInstance();
230 #if defined(USE_OPENSSL)
231 // Initialize random seed for libjingle. It's necessary only with OpenSSL.
232 char random_seed[kRandomSeedSize];
233 crypto::RandBytes(random_seed, sizeof(random_seed));
234 rtc::InitRandom(random_seed, sizeof(random_seed));
235 #else
236 // Libjingle's SSL implementation is not really used, but it has to be
237 // initialized for NSS builds to make sure that RNG is initialized in NSS,
238 // because libjingle uses it.
239 rtc::InitializeSSL();
240 #endif // !defined(USE_OPENSSL)
242 // Send hello message.
243 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
244 data->SetInteger("apiVersion", kApiVersion);
245 data->SetString("apiFeatures", kApiFeatures);
246 data->SetInteger("apiMinVersion", kApiMinMessagingVersion);
247 data->SetString("requestedCapabilities", kRequestedCapabilities);
248 data->SetString("supportedCapabilities", kSupportedCapabilities);
250 PostLegacyJsonMessage("hello", data.Pass());
253 ChromotingInstance::~ChromotingInstance() {
254 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
256 // Unregister this instance so that debug log messages will no longer be sent
257 // to it. This will stop all logging in all Chromoting instances.
258 UnregisterLoggingInstance();
260 // PepperView must be destroyed before the client.
261 view_weak_factory_.reset();
262 view_.reset();
264 client_.reset();
266 plugin_task_runner_->Quit();
268 // Ensure that nothing touches the plugin thread delegate after this point.
269 plugin_task_runner_->DetachAndRunShutdownLoop();
271 // Stopping the context shuts down all chromoting threads.
272 context_.Stop();
275 bool ChromotingInstance::Init(uint32_t argc,
276 const char* argn[],
277 const char* argv[]) {
278 CHECK(!initialized_);
279 initialized_ = true;
281 VLOG(1) << "Started ChromotingInstance::Init";
283 // Check that the calling content is part of an app or extension. This is only
284 // necessary for non-PNaCl version of the plugin. Also PPB_URLUtil_Dev doesn't
285 // work in NaCl at the moment so the check fails in NaCl builds.
286 #if !defined(OS_NACL)
287 if (!IsCallerAppOrExtension()) {
288 LOG(ERROR) << "Not an app or extension";
289 return false;
291 #endif
293 // Start all the threads.
294 context_.Start();
296 return true;
299 void ChromotingInstance::HandleMessage(const pp::Var& message) {
300 if (!message.is_string()) {
301 LOG(ERROR) << "Received a message that is not a string.";
302 return;
305 scoped_ptr<base::Value> json(
306 base::JSONReader::Read(message.AsString(),
307 base::JSON_ALLOW_TRAILING_COMMAS));
308 base::DictionaryValue* message_dict = NULL;
309 std::string method;
310 base::DictionaryValue* data = NULL;
311 if (!json.get() ||
312 !json->GetAsDictionary(&message_dict) ||
313 !message_dict->GetString("method", &method) ||
314 !message_dict->GetDictionary("data", &data)) {
315 LOG(ERROR) << "Received invalid message:" << message.AsString();
316 return;
319 if (method == "connect") {
320 HandleConnect(*data);
321 } else if (method == "disconnect") {
322 HandleDisconnect(*data);
323 } else if (method == "incomingIq") {
324 HandleOnIncomingIq(*data);
325 } else if (method == "releaseAllKeys") {
326 HandleReleaseAllKeys(*data);
327 } else if (method == "injectKeyEvent") {
328 HandleInjectKeyEvent(*data);
329 } else if (method == "remapKey") {
330 HandleRemapKey(*data);
331 } else if (method == "trapKey") {
332 HandleTrapKey(*data);
333 } else if (method == "sendClipboardItem") {
334 HandleSendClipboardItem(*data);
335 } else if (method == "notifyClientResolution") {
336 HandleNotifyClientResolution(*data);
337 } else if (method == "pauseVideo") {
338 HandlePauseVideo(*data);
339 } else if (method == "videoControl") {
340 HandleVideoControl(*data);
341 } else if (method == "pauseAudio") {
342 HandlePauseAudio(*data);
343 } else if (method == "useAsyncPinDialog") {
344 use_async_pin_dialog_ = true;
345 } else if (method == "onPinFetched") {
346 HandleOnPinFetched(*data);
347 } else if (method == "onThirdPartyTokenFetched") {
348 HandleOnThirdPartyTokenFetched(*data);
349 } else if (method == "requestPairing") {
350 HandleRequestPairing(*data);
351 } else if (method == "extensionMessage") {
352 HandleExtensionMessage(*data);
353 } else if (method == "allowMouseLock") {
354 HandleAllowMouseLockMessage();
355 } else if (method == "enableMediaSourceRendering") {
356 HandleEnableMediaSourceRendering();
357 } else if (method == "sendMouseInputWhenUnfocused") {
358 HandleSendMouseInputWhenUnfocused();
359 } else if (method == "delegateLargeCursors") {
360 HandleDelegateLargeCursors();
364 void ChromotingInstance::DidChangeFocus(bool has_focus) {
365 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
367 if (!IsConnected())
368 return;
370 input_handler_.DidChangeFocus(has_focus);
371 if (mouse_locker_)
372 mouse_locker_->DidChangeFocus(has_focus);
375 void ChromotingInstance::DidChangeView(const pp::View& view) {
376 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
378 plugin_view_ = view;
379 mouse_input_filter_.set_input_size(
380 webrtc::DesktopSize(view.GetRect().width(), view.GetRect().height()));
382 if (view_)
383 view_->SetView(view);
386 bool ChromotingInstance::HandleInputEvent(const pp::InputEvent& event) {
387 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
389 if (!IsConnected())
390 return false;
392 return input_handler_.HandleInputEvent(event);
395 void ChromotingInstance::SetDesktopSize(const webrtc::DesktopSize& size,
396 const webrtc::DesktopVector& dpi) {
397 mouse_input_filter_.set_output_size(size);
399 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
400 data->SetInteger("width", size.width());
401 data->SetInteger("height", size.height());
402 if (dpi.x())
403 data->SetInteger("x_dpi", dpi.x());
404 if (dpi.y())
405 data->SetInteger("y_dpi", dpi.y());
406 PostLegacyJsonMessage("onDesktopSize", data.Pass());
409 void ChromotingInstance::SetDesktopShape(const webrtc::DesktopRegion& shape) {
410 if (desktop_shape_ && shape.Equals(*desktop_shape_))
411 return;
413 desktop_shape_.reset(new webrtc::DesktopRegion(shape));
415 scoped_ptr<base::ListValue> rects_value(new base::ListValue());
416 for (webrtc::DesktopRegion::Iterator i(shape); !i.IsAtEnd(); i.Advance()) {
417 const webrtc::DesktopRect& rect = i.rect();
418 scoped_ptr<base::ListValue> rect_value(new base::ListValue());
419 rect_value->AppendInteger(rect.left());
420 rect_value->AppendInteger(rect.top());
421 rect_value->AppendInteger(rect.width());
422 rect_value->AppendInteger(rect.height());
423 rects_value->Append(rect_value.release());
426 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
427 data->Set("rects", rects_value.release());
428 PostLegacyJsonMessage("onDesktopShape", data.Pass());
431 void ChromotingInstance::OnConnectionState(
432 protocol::ConnectionToHost::State state,
433 protocol::ErrorCode error) {
434 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
435 data->SetString("state", ConnectionStateToString(state));
436 data->SetString("error", ConnectionErrorToString(error));
437 PostLegacyJsonMessage("onConnectionStatus", data.Pass());
440 void ChromotingInstance::FetchThirdPartyToken(
441 const GURL& token_url,
442 const std::string& host_public_key,
443 const std::string& scope,
444 base::WeakPtr<TokenFetcherProxy> token_fetcher_proxy) {
445 // Once the Session object calls this function, it won't continue the
446 // authentication until the callback is called (or connection is canceled).
447 // So, it's impossible to reach this with a callback already registered.
448 DCHECK(!token_fetcher_proxy_.get());
449 token_fetcher_proxy_ = token_fetcher_proxy;
450 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
451 data->SetString("tokenUrl", token_url.spec());
452 data->SetString("hostPublicKey", host_public_key);
453 data->SetString("scope", scope);
454 PostLegacyJsonMessage("fetchThirdPartyToken", data.Pass());
457 void ChromotingInstance::OnConnectionReady(bool ready) {
458 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
459 data->SetBoolean("ready", ready);
460 PostLegacyJsonMessage("onConnectionReady", data.Pass());
463 void ChromotingInstance::OnRouteChanged(const std::string& channel_name,
464 const protocol::TransportRoute& route) {
465 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
466 data->SetString("channel", channel_name);
467 data->SetString("connectionType",
468 protocol::TransportRoute::GetTypeString(route.type));
469 PostLegacyJsonMessage("onRouteChanged", data.Pass());
472 void ChromotingInstance::SetCapabilities(const std::string& capabilities) {
473 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
474 data->SetString("capabilities", capabilities);
475 PostLegacyJsonMessage("setCapabilities", data.Pass());
478 void ChromotingInstance::SetPairingResponse(
479 const protocol::PairingResponse& pairing_response) {
480 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
481 data->SetString("clientId", pairing_response.client_id());
482 data->SetString("sharedSecret", pairing_response.shared_secret());
483 PostLegacyJsonMessage("pairingResponse", data.Pass());
486 void ChromotingInstance::DeliverHostMessage(
487 const protocol::ExtensionMessage& message) {
488 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
489 data->SetString("type", message.type());
490 data->SetString("data", message.data());
491 PostLegacyJsonMessage("extensionMessage", data.Pass());
494 void ChromotingInstance::FetchSecretFromDialog(
495 bool pairing_supported,
496 const protocol::SecretFetchedCallback& secret_fetched_callback) {
497 // Once the Session object calls this function, it won't continue the
498 // authentication until the callback is called (or connection is canceled).
499 // So, it's impossible to reach this with a callback already registered.
500 DCHECK(secret_fetched_callback_.is_null());
501 secret_fetched_callback_ = secret_fetched_callback;
502 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
503 data->SetBoolean("pairingSupported", pairing_supported);
504 PostLegacyJsonMessage("fetchPin", data.Pass());
507 void ChromotingInstance::FetchSecretFromString(
508 const std::string& shared_secret,
509 bool pairing_supported,
510 const protocol::SecretFetchedCallback& secret_fetched_callback) {
511 secret_fetched_callback.Run(shared_secret);
514 protocol::ClipboardStub* ChromotingInstance::GetClipboardStub() {
515 // TODO(sergeyu): Move clipboard handling to a separate class.
516 // crbug.com/138108
517 return this;
520 protocol::CursorShapeStub* ChromotingInstance::GetCursorShapeStub() {
521 return &empty_cursor_filter_;
524 void ChromotingInstance::InjectClipboardEvent(
525 const protocol::ClipboardEvent& event) {
526 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
527 data->SetString("mimeType", event.mime_type());
528 data->SetString("item", event.data());
529 PostLegacyJsonMessage("injectClipboardItem", data.Pass());
532 void ChromotingInstance::SetCursorShape(
533 const protocol::CursorShapeInfo& cursor_shape) {
534 // If the delegated cursor is empty then stop rendering a DOM cursor.
535 if (IsCursorShapeEmpty(cursor_shape)) {
536 PostChromotingMessage("unsetCursorShape", pp::VarDictionary());
537 return;
540 // Cursor is not empty, so pass it to JS to render.
541 const int kBytesPerPixel = sizeof(uint32_t);
542 const size_t buffer_size =
543 cursor_shape.height() * cursor_shape.width() * kBytesPerPixel;
545 pp::VarArrayBuffer array_buffer(buffer_size);
546 void* dst = array_buffer.Map();
547 memcpy(dst, cursor_shape.data().data(), buffer_size);
548 array_buffer.Unmap();
550 pp::VarDictionary dictionary;
551 dictionary.Set(pp::Var("width"), cursor_shape.width());
552 dictionary.Set(pp::Var("height"), cursor_shape.height());
553 dictionary.Set(pp::Var("hotspotX"), cursor_shape.hotspot_x());
554 dictionary.Set(pp::Var("hotspotY"), cursor_shape.hotspot_y());
555 dictionary.Set(pp::Var("data"), array_buffer);
556 PostChromotingMessage("setCursorShape", dictionary);
559 void ChromotingInstance::OnFirstFrameReceived() {
560 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
561 PostLegacyJsonMessage("onFirstFrameReceived", data.Pass());
564 void ChromotingInstance::HandleConnect(const base::DictionaryValue& data) {
565 std::string local_jid;
566 std::string host_jid;
567 std::string host_public_key;
568 std::string auth_methods_str;
569 std::string authentication_tag;
570 std::vector<protocol::AuthenticationMethod> auth_methods;
571 if (!data.GetString("hostJid", &host_jid) ||
572 !data.GetString("hostPublicKey", &host_public_key) ||
573 !data.GetString("localJid", &local_jid) ||
574 !data.GetString("authenticationMethods", &auth_methods_str) ||
575 !ParseAuthMethods(auth_methods_str, &auth_methods) ||
576 !data.GetString("authenticationTag", &authentication_tag)) {
577 LOG(ERROR) << "Invalid connect() data.";
578 return;
581 std::string client_pairing_id;
582 data.GetString("clientPairingId", &client_pairing_id);
583 std::string client_paired_secret;
584 data.GetString("clientPairedSecret", &client_paired_secret);
586 protocol::FetchSecretCallback fetch_secret_callback;
587 if (use_async_pin_dialog_) {
588 fetch_secret_callback = base::Bind(
589 &ChromotingInstance::FetchSecretFromDialog, weak_factory_.GetWeakPtr());
590 } else {
591 std::string shared_secret;
592 if (!data.GetString("sharedSecret", &shared_secret)) {
593 LOG(ERROR) << "sharedSecret not specified in connect().";
594 return;
596 fetch_secret_callback =
597 base::Bind(&ChromotingInstance::FetchSecretFromString, shared_secret);
600 // Read the list of capabilities, if any.
601 std::string capabilities;
602 if (data.HasKey("capabilities")) {
603 if (!data.GetString("capabilities", &capabilities)) {
604 LOG(ERROR) << "Invalid connect() data.";
605 return;
609 VLOG(0) << "Connecting to " << host_jid
610 << ". Local jid: " << local_jid << ".";
612 #if defined(OS_NACL)
613 std::string key_filter;
614 if (!data.GetString("keyFilter", &key_filter)) {
615 NOTREACHED();
616 normalizing_input_filter_.reset(new protocol::InputFilter(&key_mapper_));
617 } else if (key_filter == "mac") {
618 normalizing_input_filter_.reset(
619 new NormalizingInputFilterMac(&key_mapper_));
620 } else if (key_filter == "cros") {
621 normalizing_input_filter_.reset(
622 new NormalizingInputFilterCros(&key_mapper_));
623 } else {
624 DCHECK(key_filter.empty());
625 normalizing_input_filter_.reset(new protocol::InputFilter(&key_mapper_));
627 #elif defined(OS_MACOSX)
628 normalizing_input_filter_.reset(new NormalizingInputFilterMac(&key_mapper_));
629 #elif defined(OS_CHROMEOS)
630 normalizing_input_filter_.reset(new NormalizingInputFilterCros(&key_mapper_));
631 #else
632 normalizing_input_filter_.reset(new protocol::InputFilter(&key_mapper_));
633 #endif
634 input_handler_.set_input_stub(normalizing_input_filter_.get());
636 if (use_media_source_rendering_) {
637 video_renderer_.reset(new MediaSourceVideoRenderer(this));
638 } else {
639 view_.reset(new PepperView(this, &context_));
640 view_weak_factory_.reset(
641 new base::WeakPtrFactory<FrameConsumer>(view_.get()));
643 // SoftwareVideoRenderer runs on a separate thread so for now we wrap
644 // PepperView with a ref-counted proxy object.
645 scoped_refptr<FrameConsumerProxy> consumer_proxy =
646 new FrameConsumerProxy(plugin_task_runner_,
647 view_weak_factory_->GetWeakPtr());
649 SoftwareVideoRenderer* renderer =
650 new SoftwareVideoRenderer(context_.main_task_runner(),
651 context_.decode_task_runner(),
652 consumer_proxy);
653 view_->Initialize(renderer);
654 if (!plugin_view_.is_null())
655 view_->SetView(plugin_view_);
656 video_renderer_.reset(renderer);
659 scoped_ptr<AudioPlayer> audio_player(new PepperAudioPlayer(this));
660 client_.reset(new ChromotingClient(&context_, this, video_renderer_.get(),
661 audio_player.Pass()));
663 // Connect the input pipeline to the protocol stub & initialize components.
664 mouse_input_filter_.set_input_stub(client_->input_stub());
665 if (!plugin_view_.is_null()) {
666 mouse_input_filter_.set_input_size(webrtc::DesktopSize(
667 plugin_view_.GetRect().width(), plugin_view_.GetRect().height()));
670 // Setup the signal strategy.
671 signal_strategy_.reset(new DelegatingSignalStrategy(
672 local_jid, base::Bind(&ChromotingInstance::SendOutgoingIq,
673 weak_factory_.GetWeakPtr())));
675 // Create TransportFactory.
676 scoped_ptr<protocol::TransportFactory> transport_factory(
677 new protocol::LibjingleTransportFactory(
678 signal_strategy_.get(),
679 PepperPortAllocator::Create(this).Pass(),
680 protocol::NetworkSettings(
681 protocol::NetworkSettings::NAT_TRAVERSAL_FULL)));
683 // Create Authenticator.
684 scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher>
685 token_fetcher(new TokenFetcherProxy(
686 base::Bind(&ChromotingInstance::FetchThirdPartyToken,
687 weak_factory_.GetWeakPtr()),
688 host_public_key));
689 scoped_ptr<protocol::Authenticator> authenticator(
690 new protocol::NegotiatingClientAuthenticator(
691 client_pairing_id, client_paired_secret, authentication_tag,
692 fetch_secret_callback, token_fetcher.Pass(), auth_methods));
694 // Kick off the connection.
695 client_->Start(signal_strategy_.get(), authenticator.Pass(),
696 transport_factory.Pass(), host_jid, capabilities);
698 // Start timer that periodically sends perf stats.
699 plugin_task_runner_->PostDelayedTask(
700 FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats,
701 weak_factory_.GetWeakPtr()),
702 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
705 void ChromotingInstance::HandleDisconnect(const base::DictionaryValue& data) {
706 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
708 // PepperView must be destroyed before the client.
709 view_weak_factory_.reset();
710 view_.reset();
712 VLOG(0) << "Disconnecting from host.";
714 // Disconnect the input pipeline and teardown the connection.
715 mouse_input_filter_.set_input_stub(NULL);
716 client_.reset();
719 void ChromotingInstance::HandleOnIncomingIq(const base::DictionaryValue& data) {
720 std::string iq;
721 if (!data.GetString("iq", &iq)) {
722 LOG(ERROR) << "Invalid incomingIq() data.";
723 return;
726 // Just ignore the message if it's received before Connect() is called. It's
727 // likely to be a leftover from a previous session, so it's safe to ignore it.
728 if (signal_strategy_)
729 signal_strategy_->OnIncomingMessage(iq);
732 void ChromotingInstance::HandleReleaseAllKeys(
733 const base::DictionaryValue& data) {
734 if (IsConnected())
735 input_tracker_.ReleaseAll();
738 void ChromotingInstance::HandleInjectKeyEvent(
739 const base::DictionaryValue& data) {
740 int usb_keycode = 0;
741 bool is_pressed = false;
742 if (!data.GetInteger("usbKeycode", &usb_keycode) ||
743 !data.GetBoolean("pressed", &is_pressed)) {
744 LOG(ERROR) << "Invalid injectKeyEvent.";
745 return;
748 protocol::KeyEvent event;
749 event.set_usb_keycode(usb_keycode);
750 event.set_pressed(is_pressed);
752 // Inject after the KeyEventMapper, so the event won't get mapped or trapped.
753 if (IsConnected())
754 input_tracker_.InjectKeyEvent(event);
757 void ChromotingInstance::HandleRemapKey(const base::DictionaryValue& data) {
758 int from_keycode = 0;
759 int to_keycode = 0;
760 if (!data.GetInteger("fromKeycode", &from_keycode) ||
761 !data.GetInteger("toKeycode", &to_keycode)) {
762 LOG(ERROR) << "Invalid remapKey.";
763 return;
766 key_mapper_.RemapKey(from_keycode, to_keycode);
769 void ChromotingInstance::HandleTrapKey(const base::DictionaryValue& data) {
770 int keycode = 0;
771 bool trap = false;
772 if (!data.GetInteger("keycode", &keycode) ||
773 !data.GetBoolean("trap", &trap)) {
774 LOG(ERROR) << "Invalid trapKey.";
775 return;
778 key_mapper_.TrapKey(keycode, trap);
781 void ChromotingInstance::HandleSendClipboardItem(
782 const base::DictionaryValue& data) {
783 std::string mime_type;
784 std::string item;
785 if (!data.GetString("mimeType", &mime_type) ||
786 !data.GetString("item", &item)) {
787 LOG(ERROR) << "Invalid sendClipboardItem data.";
788 return;
790 if (!IsConnected()) {
791 return;
793 protocol::ClipboardEvent event;
794 event.set_mime_type(mime_type);
795 event.set_data(item);
796 client_->clipboard_forwarder()->InjectClipboardEvent(event);
799 void ChromotingInstance::HandleNotifyClientResolution(
800 const base::DictionaryValue& data) {
801 int width = 0;
802 int height = 0;
803 int x_dpi = kDefaultDPI;
804 int y_dpi = kDefaultDPI;
805 if (!data.GetInteger("width", &width) ||
806 !data.GetInteger("height", &height) ||
807 !data.GetInteger("x_dpi", &x_dpi) ||
808 !data.GetInteger("y_dpi", &y_dpi) ||
809 width <= 0 || height <= 0 ||
810 x_dpi <= 0 || y_dpi <= 0) {
811 LOG(ERROR) << "Invalid notifyClientResolution.";
812 return;
815 if (!IsConnected()) {
816 return;
819 protocol::ClientResolution client_resolution;
820 client_resolution.set_width(width);
821 client_resolution.set_height(height);
822 client_resolution.set_x_dpi(x_dpi);
823 client_resolution.set_y_dpi(y_dpi);
825 // Include the legacy width & height in DIPs for use by older hosts.
826 client_resolution.set_dips_width((width * kDefaultDPI) / x_dpi);
827 client_resolution.set_dips_height((height * kDefaultDPI) / y_dpi);
829 client_->host_stub()->NotifyClientResolution(client_resolution);
832 void ChromotingInstance::HandlePauseVideo(const base::DictionaryValue& data) {
833 if (!data.HasKey("pause")) {
834 LOG(ERROR) << "Invalid pauseVideo.";
835 return;
837 HandleVideoControl(data);
840 void ChromotingInstance::HandleVideoControl(const base::DictionaryValue& data) {
841 protocol::VideoControl video_control;
842 bool pause_video = false;
843 if (data.GetBoolean("pause", &pause_video)) {
844 video_control.set_enable(!pause_video);
846 bool lossless_encode = false;
847 if (data.GetBoolean("losslessEncode", &lossless_encode)) {
848 video_control.set_lossless_encode(lossless_encode);
850 bool lossless_color = false;
851 if (data.GetBoolean("losslessColor", &lossless_color)) {
852 video_control.set_lossless_color(lossless_color);
854 if (!IsConnected()) {
855 return;
857 client_->host_stub()->ControlVideo(video_control);
860 void ChromotingInstance::HandlePauseAudio(const base::DictionaryValue& data) {
861 bool pause = false;
862 if (!data.GetBoolean("pause", &pause)) {
863 LOG(ERROR) << "Invalid pauseAudio.";
864 return;
866 if (!IsConnected()) {
867 return;
869 protocol::AudioControl audio_control;
870 audio_control.set_enable(!pause);
871 client_->host_stub()->ControlAudio(audio_control);
873 void ChromotingInstance::HandleOnPinFetched(const base::DictionaryValue& data) {
874 std::string pin;
875 if (!data.GetString("pin", &pin)) {
876 LOG(ERROR) << "Invalid onPinFetched.";
877 return;
879 if (!secret_fetched_callback_.is_null()) {
880 secret_fetched_callback_.Run(pin);
881 secret_fetched_callback_.Reset();
882 } else {
883 LOG(WARNING) << "Ignored OnPinFetched received without a pending fetch.";
887 void ChromotingInstance::HandleOnThirdPartyTokenFetched(
888 const base::DictionaryValue& data) {
889 std::string token;
890 std::string shared_secret;
891 if (!data.GetString("token", &token) ||
892 !data.GetString("sharedSecret", &shared_secret)) {
893 LOG(ERROR) << "Invalid onThirdPartyTokenFetched data.";
894 return;
896 if (token_fetcher_proxy_.get()) {
897 token_fetcher_proxy_->OnTokenFetched(token, shared_secret);
898 token_fetcher_proxy_.reset();
899 } else {
900 LOG(WARNING) << "Ignored OnThirdPartyTokenFetched without a pending fetch.";
904 void ChromotingInstance::HandleRequestPairing(
905 const base::DictionaryValue& data) {
906 std::string client_name;
907 if (!data.GetString("clientName", &client_name)) {
908 LOG(ERROR) << "Invalid requestPairing";
909 return;
911 if (!IsConnected()) {
912 return;
914 protocol::PairingRequest pairing_request;
915 pairing_request.set_client_name(client_name);
916 client_->host_stub()->RequestPairing(pairing_request);
919 void ChromotingInstance::HandleExtensionMessage(
920 const base::DictionaryValue& data) {
921 std::string type;
922 std::string message_data;
923 if (!data.GetString("type", &type) ||
924 !data.GetString("data", &message_data)) {
925 LOG(ERROR) << "Invalid extensionMessage.";
926 return;
928 if (!IsConnected()) {
929 return;
931 protocol::ExtensionMessage message;
932 message.set_type(type);
933 message.set_data(message_data);
934 client_->host_stub()->DeliverClientMessage(message);
937 void ChromotingInstance::HandleAllowMouseLockMessage() {
938 // Create the mouse lock handler and route cursor shape messages through it.
939 mouse_locker_.reset(new PepperMouseLocker(
940 this, base::Bind(&PepperInputHandler::set_send_mouse_move_deltas,
941 base::Unretained(&input_handler_)),
942 &cursor_setter_));
943 empty_cursor_filter_.set_cursor_stub(mouse_locker_.get());
946 void ChromotingInstance::HandleEnableMediaSourceRendering() {
947 use_media_source_rendering_ = true;
950 void ChromotingInstance::HandleSendMouseInputWhenUnfocused() {
951 input_handler_.set_send_mouse_input_when_unfocused(true);
954 void ChromotingInstance::HandleDelegateLargeCursors() {
955 cursor_setter_.set_delegate_stub(this);
958 ChromotingStats* ChromotingInstance::GetStats() {
959 if (!video_renderer_.get())
960 return NULL;
961 return video_renderer_->GetStats();
964 void ChromotingInstance::PostChromotingMessage(const std::string& method,
965 const pp::VarDictionary& data) {
966 pp::VarDictionary message;
967 message.Set(pp::Var("method"), pp::Var(method));
968 message.Set(pp::Var("data"), data);
969 PostMessage(message);
972 void ChromotingInstance::PostLegacyJsonMessage(
973 const std::string& method,
974 scoped_ptr<base::DictionaryValue> data) {
975 scoped_ptr<base::DictionaryValue> message(new base::DictionaryValue());
976 message->SetString("method", method);
977 message->Set("data", data.release());
979 std::string message_json;
980 base::JSONWriter::Write(message.get(), &message_json);
981 PostMessage(pp::Var(message_json));
984 void ChromotingInstance::SendTrappedKey(uint32 usb_keycode, bool pressed) {
985 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
986 data->SetInteger("usbKeycode", usb_keycode);
987 data->SetBoolean("pressed", pressed);
988 PostLegacyJsonMessage("trappedKeyEvent", data.Pass());
991 void ChromotingInstance::SendOutgoingIq(const std::string& iq) {
992 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
993 data->SetString("iq", iq);
994 PostLegacyJsonMessage("sendOutgoingIq", data.Pass());
997 void ChromotingInstance::SendPerfStats() {
998 if (!video_renderer_.get()) {
999 return;
1002 plugin_task_runner_->PostDelayedTask(
1003 FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats,
1004 weak_factory_.GetWeakPtr()),
1005 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
1007 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
1008 ChromotingStats* stats = video_renderer_->GetStats();
1009 data->SetDouble("videoBandwidth", stats->video_bandwidth()->Rate());
1010 data->SetDouble("videoFrameRate", stats->video_frame_rate()->Rate());
1011 data->SetDouble("captureLatency", stats->video_capture_ms()->Average());
1012 data->SetDouble("encodeLatency", stats->video_encode_ms()->Average());
1013 data->SetDouble("decodeLatency", stats->video_decode_ms()->Average());
1014 data->SetDouble("renderLatency", stats->video_paint_ms()->Average());
1015 data->SetDouble("roundtripLatency", stats->round_trip_ms()->Average());
1016 PostLegacyJsonMessage("onPerfStats", data.Pass());
1019 // static
1020 void ChromotingInstance::RegisterLogMessageHandler() {
1021 base::AutoLock lock(g_logging_lock.Get());
1023 VLOG(1) << "Registering global log handler";
1025 // Record previous handler so we can call it in a chain.
1026 g_logging_old_handler = logging::GetLogMessageHandler();
1028 // Set up log message handler.
1029 // This is not thread-safe so we need it within our lock.
1030 logging::SetLogMessageHandler(&LogToUI);
1033 void ChromotingInstance::RegisterLoggingInstance() {
1034 base::AutoLock lock(g_logging_lock.Get());
1036 // Register this instance as the one that will handle all logging calls
1037 // and display them to the user.
1038 // If multiple plugins are run, then the last one registered will handle all
1039 // logging for all instances.
1040 g_logging_instance.Get() = weak_factory_.GetWeakPtr();
1041 g_logging_task_runner.Get() = plugin_task_runner_;
1042 g_has_logging_instance = true;
1045 void ChromotingInstance::UnregisterLoggingInstance() {
1046 base::AutoLock lock(g_logging_lock.Get());
1048 // Don't unregister unless we're the currently registered instance.
1049 if (this != g_logging_instance.Get().get())
1050 return;
1052 // Unregister this instance for logging.
1053 g_has_logging_instance = false;
1054 g_logging_instance.Get().reset();
1055 g_logging_task_runner.Get() = NULL;
1057 VLOG(1) << "Unregistering global log handler";
1060 // static
1061 bool ChromotingInstance::LogToUI(int severity, const char* file, int line,
1062 size_t message_start,
1063 const std::string& str) {
1064 // Note that we're reading |g_has_logging_instance| outside of a lock.
1065 // This lockless read is done so that we don't needlessly slow down global
1066 // logging with a lock for each log message.
1068 // This lockless read is safe because:
1070 // Misreading a false value (when it should be true) means that we'll simply
1071 // skip processing a few log messages.
1073 // Misreading a true value (when it should be false) means that we'll take
1074 // the lock and check |g_logging_instance| unnecessarily. This is not
1075 // problematic because we always set |g_logging_instance| inside a lock.
1076 if (g_has_logging_instance) {
1077 scoped_refptr<base::SingleThreadTaskRunner> logging_task_runner;
1078 base::WeakPtr<ChromotingInstance> logging_instance;
1081 base::AutoLock lock(g_logging_lock.Get());
1082 // If we're on the logging thread and |g_logging_to_plugin| is set then
1083 // this LOG message came from handling a previous LOG message and we
1084 // should skip it to avoid an infinite loop of LOG messages.
1085 if (!g_logging_task_runner.Get()->BelongsToCurrentThread() ||
1086 !g_logging_to_plugin) {
1087 logging_task_runner = g_logging_task_runner.Get();
1088 logging_instance = g_logging_instance.Get();
1092 if (logging_task_runner.get()) {
1093 std::string message = remoting::GetTimestampString();
1094 message += (str.c_str() + message_start);
1096 logging_task_runner->PostTask(
1097 FROM_HERE, base::Bind(&ChromotingInstance::ProcessLogToUI,
1098 logging_instance, message));
1102 if (g_logging_old_handler)
1103 return (g_logging_old_handler)(severity, file, line, message_start, str);
1104 return false;
1107 void ChromotingInstance::ProcessLogToUI(const std::string& message) {
1108 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
1110 // This flag (which is set only here) is used to prevent LogToUI from posting
1111 // new tasks while we're in the middle of servicing a LOG call. This can
1112 // happen if the call to LogDebugInfo tries to LOG anything.
1113 // Since it is read on the plugin thread, we don't need to lock to set it.
1114 g_logging_to_plugin = true;
1115 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
1116 data->SetString("message", message);
1117 PostLegacyJsonMessage("logDebugMessage", data.Pass());
1118 g_logging_to_plugin = false;
1121 bool ChromotingInstance::IsCallerAppOrExtension() {
1122 const pp::URLUtil_Dev* url_util = pp::URLUtil_Dev::Get();
1123 if (!url_util)
1124 return false;
1126 PP_URLComponents_Dev url_components;
1127 pp::Var url_var = url_util->GetDocumentURL(this, &url_components);
1128 if (!url_var.is_string())
1129 return false;
1131 std::string url = url_var.AsString();
1132 std::string url_scheme = url.substr(url_components.scheme.begin,
1133 url_components.scheme.len);
1134 return url_scheme == kChromeExtensionUrlScheme;
1137 bool ChromotingInstance::IsConnected() {
1138 return client_ &&
1139 (client_->connection_state() == protocol::ConnectionToHost::CONNECTED);
1142 void ChromotingInstance::OnMediaSourceSize(const webrtc::DesktopSize& size,
1143 const webrtc::DesktopVector& dpi) {
1144 SetDesktopSize(size, dpi);
1147 void ChromotingInstance::OnMediaSourceShape(
1148 const webrtc::DesktopRegion& shape) {
1149 SetDesktopShape(shape);
1152 void ChromotingInstance::OnMediaSourceReset(const std::string& format) {
1153 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
1154 data->SetString("format", format);
1155 PostLegacyJsonMessage("mediaSourceReset", data.Pass());
1158 void ChromotingInstance::OnMediaSourceData(uint8_t* buffer, size_t buffer_size,
1159 bool keyframe) {
1160 pp::VarArrayBuffer array_buffer(buffer_size);
1161 void* data_ptr = array_buffer.Map();
1162 memcpy(data_ptr, buffer, buffer_size);
1163 array_buffer.Unmap();
1164 pp::VarDictionary data_dictionary;
1165 data_dictionary.Set(pp::Var("buffer"), array_buffer);
1166 data_dictionary.Set(pp::Var("keyframe"), keyframe);
1167 PostChromotingMessage("mediaSourceData", data_dictionary);
1170 } // namespace remoting