chromoting_instance.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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.
4
5#include "remoting/client/plugin/chromoting_instance.h"
6
7#include <algorithm>
8#include <string>
9#include <vector>
10
11#include "base/bind.h"
12#include "base/callback.h"
13#include "base/json/json_reader.h"
14#include "base/json/json_writer.h"
15#include "base/lazy_instance.h"
16#include "base/logging.h"
17#include "base/strings/string_split.h"
18#include "base/strings/stringprintf.h"
19#include "base/synchronization/lock.h"
20#include "base/threading/thread.h"
21#include "base/values.h"
22#include "crypto/random.h"
23#include "jingle/glue/thread_wrapper.h"
24#include "media/base/media.h"
25#include "net/socket/ssl_server_socket.h"
26#include "ppapi/cpp/completion_callback.h"
27#include "ppapi/cpp/dev/url_util_dev.h"
28#include "ppapi/cpp/image_data.h"
29#include "ppapi/cpp/input_event.h"
30#include "ppapi/cpp/rect.h"
31#include "ppapi/cpp/var_array_buffer.h"
32#include "ppapi/cpp/var_dictionary.h"
33#include "remoting/base/constants.h"
34#include "remoting/base/util.h"
35#include "remoting/client/chromoting_client.h"
36#include "remoting/client/client_config.h"
37#include "remoting/client/frame_consumer_proxy.h"
38#include "remoting/client/plugin/delegating_signal_strategy.h"
39#include "remoting/client/plugin/media_source_video_renderer.h"
40#include "remoting/client/plugin/pepper_audio_player.h"
41#include "remoting/client/plugin/pepper_input_handler.h"
42#include "remoting/client/plugin/pepper_port_allocator.h"
43#include "remoting/client/plugin/pepper_token_fetcher.h"
44#include "remoting/client/plugin/pepper_view.h"
45#include "remoting/client/software_video_renderer.h"
46#include "remoting/protocol/connection_to_host.h"
47#include "remoting/protocol/host_stub.h"
48#include "remoting/protocol/libjingle_transport_factory.h"
49#include "third_party/libjingle/source/talk/base/helpers.h"
50#include "url/gurl.h"
51
52// Windows defines 'PostMessage', so we have to undef it.
53#if defined(PostMessage)
54#undef PostMessage
55#endif
56
57namespace remoting {
58
59namespace {
60
61// 32-bit BGRA is 4 bytes per pixel.
62const int kBytesPerPixel = 4;
63
64#if defined(ARCH_CPU_LITTLE_ENDIAN)
65const uint32_t kPixelAlphaMask = 0xff000000;
66#else  // !defined(ARCH_CPU_LITTLE_ENDIAN)
67const uint32_t kPixelAlphaMask = 0x000000ff;
68#endif  // !defined(ARCH_CPU_LITTLE_ENDIAN)
69
70// Default DPI to assume for old clients that use notifyClientResolution.
71const int kDefaultDPI = 96;
72
73// Interval at which to sample performance statistics.
74const int kPerfStatsIntervalMs = 1000;
75
76// URL scheme used by Chrome apps and extensions.
77const char kChromeExtensionUrlScheme[] = "chrome-extension";
78
79// Maximum width and height of a mouse cursor supported by PPAPI.
80const int kMaxCursorWidth = 32;
81const int kMaxCursorHeight = 32;
82
83#if defined(USE_OPENSSL)
84// Size of the random seed blob used to initialize RNG in libjingle. Libjingle
85// uses the seed only for OpenSSL builds. OpenSSL needs at least 32 bytes of
86// entropy (see http://wiki.openssl.org/index.php/Random_Numbers), but stores
87// 1039 bytes of state, so we initialize it with 1k or random data.
88const int kRandomSeedSize = 1024;
89#endif  // defined(USE_OPENSSL)
90
91std::string ConnectionStateToString(protocol::ConnectionToHost::State state) {
92  // Values returned by this function must match the
93  // remoting.ClientSession.State enum in JS code.
94  switch (state) {
95    case protocol::ConnectionToHost::INITIALIZING:
96      return "INITIALIZING";
97    case protocol::ConnectionToHost::CONNECTING:
98      return "CONNECTING";
99    case protocol::ConnectionToHost::AUTHENTICATED:
100      // Report the authenticated state as 'CONNECTING' to avoid changing
101      // the interface between the plugin and webapp.
102      return "CONNECTING";
103    case protocol::ConnectionToHost::CONNECTED:
104      return "CONNECTED";
105    case protocol::ConnectionToHost::CLOSED:
106      return "CLOSED";
107    case protocol::ConnectionToHost::FAILED:
108      return "FAILED";
109  }
110  NOTREACHED();
111  return std::string();
112}
113
114// TODO(sergeyu): Ideally we should just pass ErrorCode to the webapp
115// and let it handle it, but it would be hard to fix it now because
116// client plugin and webapp versions may not be in sync. It should be
117// easy to do after we are finished moving the client plugin to NaCl.
118std::string ConnectionErrorToString(protocol::ErrorCode error) {
119  // Values returned by this function must match the
120  // remoting.ClientSession.Error enum in JS code.
121  switch (error) {
122    case protocol::OK:
123      return "NONE";
124
125    case protocol::PEER_IS_OFFLINE:
126      return "HOST_IS_OFFLINE";
127
128    case protocol::SESSION_REJECTED:
129    case protocol::AUTHENTICATION_FAILED:
130      return "SESSION_REJECTED";
131
132    case protocol::INCOMPATIBLE_PROTOCOL:
133      return "INCOMPATIBLE_PROTOCOL";
134
135    case protocol::HOST_OVERLOAD:
136      return "HOST_OVERLOAD";
137
138    case protocol::CHANNEL_CONNECTION_ERROR:
139    case protocol::SIGNALING_ERROR:
140    case protocol::SIGNALING_TIMEOUT:
141    case protocol::UNKNOWN_ERROR:
142      return "NETWORK_FAILURE";
143  }
144  DLOG(FATAL) << "Unknown error code" << error;
145  return std::string();
146}
147
148// Returns true if |pixel| is not completely transparent.
149bool IsVisiblePixel(uint32_t pixel) {
150  return (pixel & kPixelAlphaMask) != 0;
151}
152
153// Returns true if there is at least one visible pixel in the given range.
154bool IsVisibleRow(const uint32_t* begin, const uint32_t* end) {
155  return std::find_if(begin, end, &IsVisiblePixel) != end;
156}
157
158// This flag blocks LOGs to the UI if we're already in the middle of logging
159// to the UI. This prevents a potential infinite loop if we encounter an error
160// while sending the log message to the UI.
161bool g_logging_to_plugin = false;
162bool g_has_logging_instance = false;
163base::LazyInstance<scoped_refptr<base::SingleThreadTaskRunner> >::Leaky
164    g_logging_task_runner = LAZY_INSTANCE_INITIALIZER;
165base::LazyInstance<base::WeakPtr<ChromotingInstance> >::Leaky
166    g_logging_instance = LAZY_INSTANCE_INITIALIZER;
167base::LazyInstance<base::Lock>::Leaky
168    g_logging_lock = LAZY_INSTANCE_INITIALIZER;
169logging::LogMessageHandlerFunction g_logging_old_handler = NULL;
170
171}  // namespace
172
173// String sent in the "hello" message to the webapp to describe features.
174const char ChromotingInstance::kApiFeatures[] =
175    "highQualityScaling injectKeyEvent sendClipboardItem remapKey trapKey "
176    "notifyClientResolution pauseVideo pauseAudio asyncPin thirdPartyAuth "
177    "pinlessAuth extensionMessage allowMouseLock mediaSourceRendering";
178
179const char ChromotingInstance::kRequestedCapabilities[] = "";
180const char ChromotingInstance::kSupportedCapabilities[] = "desktopShape";
181
182bool ChromotingInstance::ParseAuthMethods(const std::string& auth_methods_str,
183                                          ClientConfig* config) {
184  std::vector<std::string> auth_methods;
185  base::SplitString(auth_methods_str, ',', &auth_methods);
186  for (std::vector<std::string>::iterator it = auth_methods.begin();
187       it != auth_methods.end(); ++it) {
188    protocol::AuthenticationMethod authentication_method =
189        protocol::AuthenticationMethod::FromString(*it);
190    if (authentication_method.is_valid())
191      config->authentication_methods.push_back(authentication_method);
192  }
193  if (config->authentication_methods.empty()) {
194    LOG(ERROR) << "No valid authentication methods specified.";
195    return false;
196  }
197
198  return true;
199}
200
201ChromotingInstance::ChromotingInstance(PP_Instance pp_instance)
202    : pp::Instance(pp_instance),
203      initialized_(false),
204      plugin_task_runner_(new PluginThreadTaskRunner(&plugin_thread_delegate_)),
205      context_(plugin_task_runner_.get()),
206      input_tracker_(&mouse_input_filter_),
207      key_mapper_(&input_tracker_),
208      normalizing_input_filter_(CreateNormalizingInputFilter(&key_mapper_)),
209      input_handler_(this, normalizing_input_filter_.get()),
210      use_async_pin_dialog_(false),
211      use_media_source_rendering_(false),
212      weak_factory_(this) {
213  RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL);
214  RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
215
216  // Resister this instance to handle debug log messsages.
217  RegisterLoggingInstance();
218
219#if defined(USE_OPENSSL)
220  // Initialize random seed for libjingle. It's necessary only with OpenSSL.
221  char random_seed[kRandomSeedSize];
222  crypto::RandBytes(random_seed, sizeof(random_seed));
223  talk_base::InitRandom(random_seed, sizeof(random_seed));
224#endif  // defined(USE_OPENSSL)
225
226  // Send hello message.
227  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
228  data->SetInteger("apiVersion", kApiVersion);
229  data->SetString("apiFeatures", kApiFeatures);
230  data->SetInteger("apiMinVersion", kApiMinMessagingVersion);
231  data->SetString("requestedCapabilities", kRequestedCapabilities);
232  data->SetString("supportedCapabilities", kSupportedCapabilities);
233
234  PostLegacyJsonMessage("hello", data.Pass());
235}
236
237ChromotingInstance::~ChromotingInstance() {
238  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
239
240  // Unregister this instance so that debug log messages will no longer be sent
241  // to it. This will stop all logging in all Chromoting instances.
242  UnregisterLoggingInstance();
243
244  // PepperView must be destroyed before the client.
245  view_weak_factory_.reset();
246  view_.reset();
247
248  client_.reset();
249
250  plugin_task_runner_->Quit();
251
252  // Ensure that nothing touches the plugin thread delegate after this point.
253  plugin_task_runner_->DetachAndRunShutdownLoop();
254
255  // Stopping the context shuts down all chromoting threads.
256  context_.Stop();
257}
258
259bool ChromotingInstance::Init(uint32_t argc,
260                              const char* argn[],
261                              const char* argv[]) {
262  CHECK(!initialized_);
263  initialized_ = true;
264
265  VLOG(1) << "Started ChromotingInstance::Init";
266
267  // Check to make sure the media library is initialized.
268  // http://crbug.com/91521.
269  if (!media::IsMediaLibraryInitialized()) {
270    LOG(ERROR) << "Media library not initialized.";
271    return false;
272  }
273
274  // Check that the calling content is part of an app or extension.
275  if (!IsCallerAppOrExtension()) {
276    LOG(ERROR) << "Not an app or extension";
277    return false;
278  }
279
280  // Start all the threads.
281  context_.Start();
282
283  return true;
284}
285
286void ChromotingInstance::HandleMessage(const pp::Var& message) {
287  if (!message.is_string()) {
288    LOG(ERROR) << "Received a message that is not a string.";
289    return;
290  }
291
292  scoped_ptr<base::Value> json(
293      base::JSONReader::Read(message.AsString(),
294                             base::JSON_ALLOW_TRAILING_COMMAS));
295  base::DictionaryValue* message_dict = NULL;
296  std::string method;
297  base::DictionaryValue* data = NULL;
298  if (!json.get() ||
299      !json->GetAsDictionary(&message_dict) ||
300      !message_dict->GetString("method", &method) ||
301      !message_dict->GetDictionary("data", &data)) {
302    LOG(ERROR) << "Received invalid message:" << message.AsString();
303    return;
304  }
305
306  if (method == "connect") {
307    HandleConnect(*data);
308  } else if (method == "disconnect") {
309    HandleDisconnect(*data);
310  } else if (method == "incomingIq") {
311    HandleOnIncomingIq(*data);
312  } else if (method == "releaseAllKeys") {
313    HandleReleaseAllKeys(*data);
314  } else if (method == "injectKeyEvent") {
315    HandleInjectKeyEvent(*data);
316  } else if (method == "remapKey") {
317    HandleRemapKey(*data);
318  } else if (method == "trapKey") {
319    HandleTrapKey(*data);
320  } else if (method == "sendClipboardItem") {
321    HandleSendClipboardItem(*data);
322  } else if (method == "notifyClientResolution") {
323    HandleNotifyClientResolution(*data);
324  } else if (method == "pauseVideo") {
325    HandlePauseVideo(*data);
326  } else if (method == "pauseAudio") {
327    HandlePauseAudio(*data);
328  } else if (method == "useAsyncPinDialog") {
329    use_async_pin_dialog_ = true;
330  } else if (method == "onPinFetched") {
331    HandleOnPinFetched(*data);
332  } else if (method == "onThirdPartyTokenFetched") {
333    HandleOnThirdPartyTokenFetched(*data);
334  } else if (method == "requestPairing") {
335    HandleRequestPairing(*data);
336  } else if (method == "extensionMessage") {
337    HandleExtensionMessage(*data);
338  } else if (method == "allowMouseLock") {
339    HandleAllowMouseLockMessage();
340  } else if (method == "enableMediaSourceRendering") {
341    HandleEnableMediaSourceRendering();
342  }
343}
344
345void ChromotingInstance::DidChangeFocus(bool has_focus) {
346  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
347
348  input_handler_.DidChangeFocus(has_focus);
349}
350
351void ChromotingInstance::DidChangeView(const pp::View& view) {
352  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
353
354  plugin_view_ = view;
355  mouse_input_filter_.set_input_size(
356      webrtc::DesktopSize(view.GetRect().width(), view.GetRect().height()));
357
358  if (view_)
359    view_->SetView(view);
360}
361
362bool ChromotingInstance::HandleInputEvent(const pp::InputEvent& event) {
363  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
364
365  if (!IsConnected())
366    return false;
367
368  return input_handler_.HandleInputEvent(event);
369}
370
371void ChromotingInstance::SetDesktopSize(const webrtc::DesktopSize& size,
372                                        const webrtc::DesktopVector& dpi) {
373  mouse_input_filter_.set_output_size(size);
374
375  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
376  data->SetInteger("width", size.width());
377  data->SetInteger("height", size.height());
378  if (dpi.x())
379    data->SetInteger("x_dpi", dpi.x());
380  if (dpi.y())
381    data->SetInteger("y_dpi", dpi.y());
382  PostLegacyJsonMessage("onDesktopSize", data.Pass());
383}
384
385void ChromotingInstance::SetDesktopShape(const webrtc::DesktopRegion& shape) {
386  if (desktop_shape_ && shape.Equals(*desktop_shape_))
387    return;
388
389  desktop_shape_.reset(new webrtc::DesktopRegion(shape));
390
391  scoped_ptr<base::ListValue> rects_value(new base::ListValue());
392  for (webrtc::DesktopRegion::Iterator i(shape); !i.IsAtEnd(); i.Advance()) {
393    const webrtc::DesktopRect& rect = i.rect();
394    scoped_ptr<base::ListValue> rect_value(new base::ListValue());
395    rect_value->AppendInteger(rect.left());
396    rect_value->AppendInteger(rect.top());
397    rect_value->AppendInteger(rect.width());
398    rect_value->AppendInteger(rect.height());
399    rects_value->Append(rect_value.release());
400  }
401
402  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
403  data->Set("rects", rects_value.release());
404  PostLegacyJsonMessage("onDesktopShape", data.Pass());
405}
406
407void ChromotingInstance::OnConnectionState(
408    protocol::ConnectionToHost::State state,
409    protocol::ErrorCode error) {
410  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
411  data->SetString("state", ConnectionStateToString(state));
412  data->SetString("error", ConnectionErrorToString(error));
413  PostLegacyJsonMessage("onConnectionStatus", data.Pass());
414}
415
416void ChromotingInstance::FetchThirdPartyToken(
417    const GURL& token_url,
418    const std::string& host_public_key,
419    const std::string& scope,
420    base::WeakPtr<PepperTokenFetcher> pepper_token_fetcher) {
421  // Once the Session object calls this function, it won't continue the
422  // authentication until the callback is called (or connection is canceled).
423  // So, it's impossible to reach this with a callback already registered.
424  DCHECK(!pepper_token_fetcher_.get());
425  pepper_token_fetcher_ = pepper_token_fetcher;
426  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
427  data->SetString("tokenUrl", token_url.spec());
428  data->SetString("hostPublicKey", host_public_key);
429  data->SetString("scope", scope);
430  PostLegacyJsonMessage("fetchThirdPartyToken", data.Pass());
431}
432
433void ChromotingInstance::OnConnectionReady(bool ready) {
434  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
435  data->SetBoolean("ready", ready);
436  PostLegacyJsonMessage("onConnectionReady", data.Pass());
437}
438
439void ChromotingInstance::OnRouteChanged(const std::string& channel_name,
440                                        const protocol::TransportRoute& route) {
441  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
442  std::string message = "Channel " + channel_name + " using " +
443      protocol::TransportRoute::GetTypeString(route.type) + " connection.";
444  data->SetString("message", message);
445  PostLegacyJsonMessage("logDebugMessage", data.Pass());
446}
447
448void ChromotingInstance::SetCapabilities(const std::string& capabilities) {
449  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
450  data->SetString("capabilities", capabilities);
451  PostLegacyJsonMessage("setCapabilities", data.Pass());
452}
453
454void ChromotingInstance::SetPairingResponse(
455    const protocol::PairingResponse& pairing_response) {
456  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
457  data->SetString("clientId", pairing_response.client_id());
458  data->SetString("sharedSecret", pairing_response.shared_secret());
459  PostLegacyJsonMessage("pairingResponse", data.Pass());
460}
461
462void ChromotingInstance::DeliverHostMessage(
463    const protocol::ExtensionMessage& message) {
464  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
465  data->SetString("type", message.type());
466  data->SetString("data", message.data());
467  PostLegacyJsonMessage("extensionMessage", data.Pass());
468}
469
470void ChromotingInstance::FetchSecretFromDialog(
471    bool pairing_supported,
472    const protocol::SecretFetchedCallback& secret_fetched_callback) {
473  // Once the Session object calls this function, it won't continue the
474  // authentication until the callback is called (or connection is canceled).
475  // So, it's impossible to reach this with a callback already registered.
476  DCHECK(secret_fetched_callback_.is_null());
477  secret_fetched_callback_ = secret_fetched_callback;
478  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
479  data->SetBoolean("pairingSupported", pairing_supported);
480  PostLegacyJsonMessage("fetchPin", data.Pass());
481}
482
483void ChromotingInstance::FetchSecretFromString(
484    const std::string& shared_secret,
485    bool pairing_supported,
486    const protocol::SecretFetchedCallback& secret_fetched_callback) {
487  secret_fetched_callback.Run(shared_secret);
488}
489
490protocol::ClipboardStub* ChromotingInstance::GetClipboardStub() {
491  // TODO(sergeyu): Move clipboard handling to a separate class.
492  // crbug.com/138108
493  return this;
494}
495
496protocol::CursorShapeStub* ChromotingInstance::GetCursorShapeStub() {
497  // TODO(sergeyu): Move cursor shape code to a separate class.
498  // crbug.com/138108
499  return this;
500}
501
502scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher>
503ChromotingInstance::GetTokenFetcher(const std::string& host_public_key) {
504  return scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher>(
505      new PepperTokenFetcher(weak_factory_.GetWeakPtr(), host_public_key));
506}
507
508void ChromotingInstance::InjectClipboardEvent(
509    const protocol::ClipboardEvent& event) {
510  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
511  data->SetString("mimeType", event.mime_type());
512  data->SetString("item", event.data());
513  PostLegacyJsonMessage("injectClipboardItem", data.Pass());
514}
515
516void ChromotingInstance::SetCursorShape(
517    const protocol::CursorShapeInfo& cursor_shape) {
518  COMPILE_ASSERT(sizeof(uint32_t) == kBytesPerPixel, rgba_pixels_are_32bit);
519
520  // pp::MouseCursor requires image to be in the native format.
521  if (pp::ImageData::GetNativeImageDataFormat() !=
522      PP_IMAGEDATAFORMAT_BGRA_PREMUL) {
523    LOG(WARNING) << "Unable to set cursor shape - native image format is not"
524                    " premultiplied BGRA";
525    return;
526  }
527
528  int width = cursor_shape.width();
529  int height = cursor_shape.height();
530
531  int hotspot_x = cursor_shape.hotspot_x();
532  int hotspot_y = cursor_shape.hotspot_y();
533  int bytes_per_row = width * kBytesPerPixel;
534  int src_stride = width;
535  const uint32_t* src_row_data = reinterpret_cast<const uint32_t*>(
536      cursor_shape.data().data());
537  const uint32_t* src_row_data_end = src_row_data + src_stride * height;
538
539  scoped_ptr<pp::ImageData> cursor_image;
540  pp::Point cursor_hotspot;
541
542  // Check if the cursor is visible.
543  if (IsVisibleRow(src_row_data, src_row_data_end)) {
544    // If the cursor exceeds the size permitted by PPAPI then crop it, keeping
545    // the hotspot as close to the center of the new cursor shape as possible.
546    if (height > kMaxCursorHeight) {
547      int y = hotspot_y - (kMaxCursorHeight / 2);
548      y = std::max(y, 0);
549      y = std::min(y, height - kMaxCursorHeight);
550
551      src_row_data += src_stride * y;
552      height = kMaxCursorHeight;
553      hotspot_y -= y;
554    }
555    if (width > kMaxCursorWidth) {
556      int x = hotspot_x - (kMaxCursorWidth / 2);
557      x = std::max(x, 0);
558      x = std::min(x, height - kMaxCursorWidth);
559
560      src_row_data += x;
561      width = kMaxCursorWidth;
562      bytes_per_row = width * kBytesPerPixel;
563      hotspot_x -= x;
564    }
565
566    cursor_image.reset(new pp::ImageData(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
567                                          pp::Size(width, height), false));
568    cursor_hotspot = pp::Point(hotspot_x, hotspot_y);
569
570    uint8* dst_row_data = reinterpret_cast<uint8*>(cursor_image->data());
571    for (int row = 0; row < height; row++) {
572      memcpy(dst_row_data, src_row_data, bytes_per_row);
573      src_row_data += src_stride;
574      dst_row_data += cursor_image->stride();
575    }
576  }
577
578  input_handler_.SetMouseCursor(cursor_image.Pass(), cursor_hotspot);
579}
580
581void ChromotingInstance::OnFirstFrameReceived() {
582  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
583  PostLegacyJsonMessage("onFirstFrameReceived", data.Pass());
584}
585
586void ChromotingInstance::HandleConnect(const base::DictionaryValue& data) {
587  ClientConfig config;
588  std::string local_jid;
589  std::string auth_methods;
590  if (!data.GetString("hostJid", &config.host_jid) ||
591      !data.GetString("hostPublicKey", &config.host_public_key) ||
592      !data.GetString("localJid", &local_jid) ||
593      !data.GetString("authenticationMethods", &auth_methods) ||
594      !ParseAuthMethods(auth_methods, &config) ||
595      !data.GetString("authenticationTag", &config.authentication_tag)) {
596    LOG(ERROR) << "Invalid connect() data.";
597    return;
598  }
599  data.GetString("clientPairingId", &config.client_pairing_id);
600  data.GetString("clientPairedSecret", &config.client_paired_secret);
601  if (use_async_pin_dialog_) {
602    config.fetch_secret_callback =
603        base::Bind(&ChromotingInstance::FetchSecretFromDialog,
604                   weak_factory_.GetWeakPtr());
605  } else {
606    std::string shared_secret;
607    if (!data.GetString("sharedSecret", &shared_secret)) {
608      LOG(ERROR) << "sharedSecret not specified in connect().";
609      return;
610    }
611    config.fetch_secret_callback =
612        base::Bind(&ChromotingInstance::FetchSecretFromString, shared_secret);
613  }
614
615  // Read the list of capabilities, if any.
616  if (data.HasKey("capabilities")) {
617    if (!data.GetString("capabilities", &config.capabilities)) {
618      LOG(ERROR) << "Invalid connect() data.";
619      return;
620    }
621  }
622
623  ConnectWithConfig(config, local_jid);
624}
625
626void ChromotingInstance::ConnectWithConfig(const ClientConfig& config,
627                                           const std::string& local_jid) {
628  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
629
630  jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
631
632  if (use_media_source_rendering_) {
633    video_renderer_.reset(new MediaSourceVideoRenderer(this));
634  } else {
635    view_.reset(new PepperView(this, &context_));
636    view_weak_factory_.reset(
637        new base::WeakPtrFactory<FrameConsumer>(view_.get()));
638
639    // SoftwareVideoRenderer runs on a separate thread so for now we wrap
640    // PepperView with a ref-counted proxy object.
641    scoped_refptr<FrameConsumerProxy> consumer_proxy =
642        new FrameConsumerProxy(plugin_task_runner_,
643                               view_weak_factory_->GetWeakPtr());
644
645    SoftwareVideoRenderer* renderer =
646        new SoftwareVideoRenderer(context_.main_task_runner(),
647                                  context_.decode_task_runner(),
648                                  consumer_proxy);
649    view_->Initialize(renderer);
650    if (!plugin_view_.is_null())
651      view_->SetView(plugin_view_);
652    video_renderer_.reset(renderer);
653  }
654
655  host_connection_.reset(new protocol::ConnectionToHost(true));
656  scoped_ptr<AudioPlayer> audio_player(new PepperAudioPlayer(this));
657  client_.reset(new ChromotingClient(config, &context_, host_connection_.get(),
658                                     this, video_renderer_.get(),
659                                     audio_player.Pass()));
660
661  // Connect the input pipeline to the protocol stub & initialize components.
662  mouse_input_filter_.set_input_stub(host_connection_->input_stub());
663  if (!plugin_view_.is_null()) {
664    mouse_input_filter_.set_input_size(webrtc::DesktopSize(
665        plugin_view_.GetRect().width(), plugin_view_.GetRect().height()));
666  }
667
668  VLOG(0) << "Connecting to " << config.host_jid
669          << ". Local jid: " << local_jid << ".";
670
671  // Setup the signal strategy.
672  signal_strategy_.reset(new DelegatingSignalStrategy(
673      local_jid, base::Bind(&ChromotingInstance::SendOutgoingIq,
674                            weak_factory_.GetWeakPtr())));
675
676  scoped_ptr<cricket::HttpPortAllocatorBase> port_allocator(
677      PepperPortAllocator::Create(this));
678  scoped_ptr<protocol::TransportFactory> transport_factory(
679      new protocol::LibjingleTransportFactory(
680          signal_strategy_.get(), port_allocator.Pass(),
681          NetworkSettings(NetworkSettings::NAT_TRAVERSAL_FULL)));
682
683  // Kick off the connection.
684  client_->Start(signal_strategy_.get(), transport_factory.Pass());
685
686  // Start timer that periodically sends perf stats.
687  plugin_task_runner_->PostDelayedTask(
688      FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats,
689                            weak_factory_.GetWeakPtr()),
690      base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
691}
692
693void ChromotingInstance::HandleDisconnect(const base::DictionaryValue& data) {
694  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
695
696  // PepperView must be destroyed before the client.
697  view_weak_factory_.reset();
698  view_.reset();
699
700  VLOG(0) << "Disconnecting from host.";
701
702  client_.reset();
703
704  // Disconnect the input pipeline and teardown the connection.
705  mouse_input_filter_.set_input_stub(NULL);
706  host_connection_.reset();
707}
708
709void ChromotingInstance::HandleOnIncomingIq(const base::DictionaryValue& data) {
710  std::string iq;
711  if (!data.GetString("iq", &iq)) {
712    LOG(ERROR) << "Invalid incomingIq() data.";
713    return;
714  }
715
716  // Just ignore the message if it's received before Connect() is called. It's
717  // likely to be a leftover from a previous session, so it's safe to ignore it.
718  if (signal_strategy_)
719    signal_strategy_->OnIncomingMessage(iq);
720}
721
722void ChromotingInstance::HandleReleaseAllKeys(
723    const base::DictionaryValue& data) {
724  if (IsConnected())
725    input_tracker_.ReleaseAll();
726}
727
728void ChromotingInstance::HandleInjectKeyEvent(
729      const base::DictionaryValue& data) {
730  int usb_keycode = 0;
731  bool is_pressed = false;
732  if (!data.GetInteger("usbKeycode", &usb_keycode) ||
733      !data.GetBoolean("pressed", &is_pressed)) {
734    LOG(ERROR) << "Invalid injectKeyEvent.";
735    return;
736  }
737
738  protocol::KeyEvent event;
739  event.set_usb_keycode(usb_keycode);
740  event.set_pressed(is_pressed);
741
742  // Inject after the KeyEventMapper, so the event won't get mapped or trapped.
743  if (IsConnected())
744    input_tracker_.InjectKeyEvent(event);
745}
746
747void ChromotingInstance::HandleRemapKey(const base::DictionaryValue& data) {
748  int from_keycode = 0;
749  int to_keycode = 0;
750  if (!data.GetInteger("fromKeycode", &from_keycode) ||
751      !data.GetInteger("toKeycode", &to_keycode)) {
752    LOG(ERROR) << "Invalid remapKey.";
753    return;
754  }
755
756  key_mapper_.RemapKey(from_keycode, to_keycode);
757}
758
759void ChromotingInstance::HandleTrapKey(const base::DictionaryValue& data) {
760  int keycode = 0;
761  bool trap = false;
762  if (!data.GetInteger("keycode", &keycode) ||
763      !data.GetBoolean("trap", &trap)) {
764    LOG(ERROR) << "Invalid trapKey.";
765    return;
766  }
767
768  key_mapper_.TrapKey(keycode, trap);
769}
770
771void ChromotingInstance::HandleSendClipboardItem(
772    const base::DictionaryValue& data) {
773  std::string mime_type;
774  std::string item;
775  if (!data.GetString("mimeType", &mime_type) ||
776      !data.GetString("item", &item)) {
777    LOG(ERROR) << "Invalid sendClipboardItem data.";
778    return;
779  }
780  if (!IsConnected()) {
781    return;
782  }
783  protocol::ClipboardEvent event;
784  event.set_mime_type(mime_type);
785  event.set_data(item);
786  host_connection_->clipboard_stub()->InjectClipboardEvent(event);
787}
788
789void ChromotingInstance::HandleNotifyClientResolution(
790    const base::DictionaryValue& data) {
791  int width = 0;
792  int height = 0;
793  int x_dpi = kDefaultDPI;
794  int y_dpi = kDefaultDPI;
795  if (!data.GetInteger("width", &width) ||
796      !data.GetInteger("height", &height) ||
797      !data.GetInteger("x_dpi", &x_dpi) ||
798      !data.GetInteger("y_dpi", &y_dpi) ||
799      width <= 0 || height <= 0 ||
800      x_dpi <= 0 || y_dpi <= 0) {
801    LOG(ERROR) << "Invalid notifyClientResolution.";
802    return;
803  }
804
805  if (!IsConnected()) {
806    return;
807  }
808
809  protocol::ClientResolution client_resolution;
810  client_resolution.set_width(width);
811  client_resolution.set_height(height);
812  client_resolution.set_x_dpi(x_dpi);
813  client_resolution.set_y_dpi(y_dpi);
814
815  // Include the legacy width & height in DIPs for use by older hosts.
816  client_resolution.set_dips_width((width * kDefaultDPI) / x_dpi);
817  client_resolution.set_dips_height((height * kDefaultDPI) / y_dpi);
818
819  host_connection_->host_stub()->NotifyClientResolution(client_resolution);
820}
821
822void ChromotingInstance::HandlePauseVideo(const base::DictionaryValue& data) {
823  bool pause = false;
824  if (!data.GetBoolean("pause", &pause)) {
825    LOG(ERROR) << "Invalid pauseVideo.";
826    return;
827  }
828  if (!IsConnected()) {
829    return;
830  }
831  protocol::VideoControl video_control;
832  video_control.set_enable(!pause);
833  host_connection_->host_stub()->ControlVideo(video_control);
834}
835
836void ChromotingInstance::HandlePauseAudio(const base::DictionaryValue& data) {
837  bool pause = false;
838  if (!data.GetBoolean("pause", &pause)) {
839    LOG(ERROR) << "Invalid pauseAudio.";
840    return;
841  }
842  if (!IsConnected()) {
843    return;
844  }
845  protocol::AudioControl audio_control;
846  audio_control.set_enable(!pause);
847  host_connection_->host_stub()->ControlAudio(audio_control);
848}
849void ChromotingInstance::HandleOnPinFetched(const base::DictionaryValue& data) {
850  std::string pin;
851  if (!data.GetString("pin", &pin)) {
852    LOG(ERROR) << "Invalid onPinFetched.";
853    return;
854  }
855  if (!secret_fetched_callback_.is_null()) {
856    secret_fetched_callback_.Run(pin);
857    secret_fetched_callback_.Reset();
858  } else {
859    LOG(WARNING) << "Ignored OnPinFetched received without a pending fetch.";
860  }
861}
862
863void ChromotingInstance::HandleOnThirdPartyTokenFetched(
864    const base::DictionaryValue& data) {
865  std::string token;
866  std::string shared_secret;
867  if (!data.GetString("token", &token) ||
868      !data.GetString("sharedSecret", &shared_secret)) {
869    LOG(ERROR) << "Invalid onThirdPartyTokenFetched data.";
870    return;
871  }
872  if (pepper_token_fetcher_.get()) {
873    pepper_token_fetcher_->OnTokenFetched(token, shared_secret);
874    pepper_token_fetcher_.reset();
875  } else {
876    LOG(WARNING) << "Ignored OnThirdPartyTokenFetched without a pending fetch.";
877  }
878}
879
880void ChromotingInstance::HandleRequestPairing(
881    const base::DictionaryValue& data) {
882  std::string client_name;
883  if (!data.GetString("clientName", &client_name)) {
884    LOG(ERROR) << "Invalid requestPairing";
885    return;
886  }
887  if (!IsConnected()) {
888    return;
889  }
890  protocol::PairingRequest pairing_request;
891  pairing_request.set_client_name(client_name);
892  host_connection_->host_stub()->RequestPairing(pairing_request);
893}
894
895void ChromotingInstance::HandleExtensionMessage(
896    const base::DictionaryValue& data) {
897  std::string type;
898  std::string message_data;
899  if (!data.GetString("type", &type) ||
900      !data.GetString("data", &message_data)) {
901    LOG(ERROR) << "Invalid extensionMessage.";
902    return;
903  }
904  if (!IsConnected()) {
905    return;
906  }
907  protocol::ExtensionMessage message;
908  message.set_type(type);
909  message.set_data(message_data);
910  host_connection_->host_stub()->DeliverClientMessage(message);
911}
912
913void ChromotingInstance::HandleAllowMouseLockMessage() {
914  input_handler_.AllowMouseLock();
915}
916
917void ChromotingInstance::HandleEnableMediaSourceRendering() {
918  use_media_source_rendering_ = true;
919}
920
921ChromotingStats* ChromotingInstance::GetStats() {
922  if (!video_renderer_.get())
923    return NULL;
924  return video_renderer_->GetStats();
925}
926
927void ChromotingInstance::PostChromotingMessage(const std::string& method,
928                                               const pp::VarDictionary& data) {
929  pp::VarDictionary message;
930  message.Set(pp::Var("method"), pp::Var(method));
931  message.Set(pp::Var("data"), data);
932  PostMessage(message);
933}
934
935void ChromotingInstance::PostLegacyJsonMessage(
936    const std::string& method,
937    scoped_ptr<base::DictionaryValue> data) {
938  scoped_ptr<base::DictionaryValue> message(new base::DictionaryValue());
939  message->SetString("method", method);
940  message->Set("data", data.release());
941
942  std::string message_json;
943  base::JSONWriter::Write(message.get(), &message_json);
944  PostMessage(pp::Var(message_json));
945}
946
947void ChromotingInstance::SendTrappedKey(uint32 usb_keycode, bool pressed) {
948  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
949  data->SetInteger("usbKeycode", usb_keycode);
950  data->SetBoolean("pressed", pressed);
951  PostLegacyJsonMessage("trappedKeyEvent", data.Pass());
952}
953
954void ChromotingInstance::SendOutgoingIq(const std::string& iq) {
955  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
956  data->SetString("iq", iq);
957  PostLegacyJsonMessage("sendOutgoingIq", data.Pass());
958}
959
960void ChromotingInstance::SendPerfStats() {
961  if (!video_renderer_.get()) {
962    return;
963  }
964
965  plugin_task_runner_->PostDelayedTask(
966      FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats,
967                            weak_factory_.GetWeakPtr()),
968      base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
969
970  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
971  ChromotingStats* stats = video_renderer_->GetStats();
972  data->SetDouble("videoBandwidth", stats->video_bandwidth()->Rate());
973  data->SetDouble("videoFrameRate", stats->video_frame_rate()->Rate());
974  data->SetDouble("captureLatency", stats->video_capture_ms()->Average());
975  data->SetDouble("encodeLatency", stats->video_encode_ms()->Average());
976  data->SetDouble("decodeLatency", stats->video_decode_ms()->Average());
977  data->SetDouble("renderLatency", stats->video_paint_ms()->Average());
978  data->SetDouble("roundtripLatency", stats->round_trip_ms()->Average());
979  PostLegacyJsonMessage("onPerfStats", data.Pass());
980}
981
982// static
983void ChromotingInstance::RegisterLogMessageHandler() {
984  base::AutoLock lock(g_logging_lock.Get());
985
986  VLOG(1) << "Registering global log handler";
987
988  // Record previous handler so we can call it in a chain.
989  g_logging_old_handler = logging::GetLogMessageHandler();
990
991  // Set up log message handler.
992  // This is not thread-safe so we need it within our lock.
993  logging::SetLogMessageHandler(&LogToUI);
994}
995
996void ChromotingInstance::RegisterLoggingInstance() {
997  base::AutoLock lock(g_logging_lock.Get());
998
999  // Register this instance as the one that will handle all logging calls
1000  // and display them to the user.
1001  // If multiple plugins are run, then the last one registered will handle all
1002  // logging for all instances.
1003  g_logging_instance.Get() = weak_factory_.GetWeakPtr();
1004  g_logging_task_runner.Get() = plugin_task_runner_;
1005  g_has_logging_instance = true;
1006}
1007
1008void ChromotingInstance::UnregisterLoggingInstance() {
1009  base::AutoLock lock(g_logging_lock.Get());
1010
1011  // Don't unregister unless we're the currently registered instance.
1012  if (this != g_logging_instance.Get().get())
1013    return;
1014
1015  // Unregister this instance for logging.
1016  g_has_logging_instance = false;
1017  g_logging_instance.Get().reset();
1018  g_logging_task_runner.Get() = NULL;
1019
1020  VLOG(1) << "Unregistering global log handler";
1021}
1022
1023// static
1024bool ChromotingInstance::LogToUI(int severity, const char* file, int line,
1025                                 size_t message_start,
1026                                 const std::string& str) {
1027  // Note that we're reading |g_has_logging_instance| outside of a lock.
1028  // This lockless read is done so that we don't needlessly slow down global
1029  // logging with a lock for each log message.
1030  //
1031  // This lockless read is safe because:
1032  //
1033  // Misreading a false value (when it should be true) means that we'll simply
1034  // skip processing a few log messages.
1035  //
1036  // Misreading a true value (when it should be false) means that we'll take
1037  // the lock and check |g_logging_instance| unnecessarily. This is not
1038  // problematic because we always set |g_logging_instance| inside a lock.
1039  if (g_has_logging_instance) {
1040    scoped_refptr<base::SingleThreadTaskRunner> logging_task_runner;
1041    base::WeakPtr<ChromotingInstance> logging_instance;
1042
1043    {
1044      base::AutoLock lock(g_logging_lock.Get());
1045      // If we're on the logging thread and |g_logging_to_plugin| is set then
1046      // this LOG message came from handling a previous LOG message and we
1047      // should skip it to avoid an infinite loop of LOG messages.
1048      if (!g_logging_task_runner.Get()->BelongsToCurrentThread() ||
1049          !g_logging_to_plugin) {
1050        logging_task_runner = g_logging_task_runner.Get();
1051        logging_instance = g_logging_instance.Get();
1052      }
1053    }
1054
1055    if (logging_task_runner.get()) {
1056      std::string message = remoting::GetTimestampString();
1057      message += (str.c_str() + message_start);
1058
1059      logging_task_runner->PostTask(
1060          FROM_HERE, base::Bind(&ChromotingInstance::ProcessLogToUI,
1061                                logging_instance, message));
1062    }
1063  }
1064
1065  if (g_logging_old_handler)
1066    return (g_logging_old_handler)(severity, file, line, message_start, str);
1067  return false;
1068}
1069
1070void ChromotingInstance::ProcessLogToUI(const std::string& message) {
1071  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
1072
1073  // This flag (which is set only here) is used to prevent LogToUI from posting
1074  // new tasks while we're in the middle of servicing a LOG call. This can
1075  // happen if the call to LogDebugInfo tries to LOG anything.
1076  // Since it is read on the plugin thread, we don't need to lock to set it.
1077  g_logging_to_plugin = true;
1078  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
1079  data->SetString("message", message);
1080  PostLegacyJsonMessage("logDebugMessage", data.Pass());
1081  g_logging_to_plugin = false;
1082}
1083
1084bool ChromotingInstance::IsCallerAppOrExtension() {
1085  const pp::URLUtil_Dev* url_util = pp::URLUtil_Dev::Get();
1086  if (!url_util)
1087    return false;
1088
1089  PP_URLComponents_Dev url_components;
1090  pp::Var url_var = url_util->GetDocumentURL(this, &url_components);
1091  if (!url_var.is_string())
1092    return false;
1093
1094  std::string url = url_var.AsString();
1095  std::string url_scheme = url.substr(url_components.scheme.begin,
1096                                      url_components.scheme.len);
1097  return url_scheme == kChromeExtensionUrlScheme;
1098}
1099
1100bool ChromotingInstance::IsConnected() {
1101  return host_connection_.get() &&
1102    (host_connection_->state() == protocol::ConnectionToHost::CONNECTED);
1103}
1104
1105void ChromotingInstance::OnMediaSourceSize(const webrtc::DesktopSize& size,
1106                                           const webrtc::DesktopVector& dpi) {
1107  SetDesktopSize(size, dpi);
1108}
1109
1110void ChromotingInstance::OnMediaSourceShape(
1111    const webrtc::DesktopRegion& shape) {
1112  SetDesktopShape(shape);
1113}
1114
1115void ChromotingInstance::OnMediaSourceReset(const std::string& format) {
1116  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
1117  data->SetString("format", format);
1118  PostLegacyJsonMessage("mediaSourceReset", data.Pass());
1119}
1120
1121void ChromotingInstance::OnMediaSourceData(uint8_t* buffer,
1122                                           size_t buffer_size) {
1123  pp::VarArrayBuffer array_buffer(buffer_size);
1124  void* data_ptr = array_buffer.Map();
1125  memcpy(data_ptr, buffer, buffer_size);
1126  array_buffer.Unmap();
1127  pp::VarDictionary data_dictionary;
1128  data_dictionary.Set(pp::Var("buffer"), array_buffer);
1129  PostChromotingMessage("mediaSourceData", data_dictionary);
1130}
1131
1132}  // namespace remoting
1133