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