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