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