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