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