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