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/host/client_session.h" 6 7 #include <algorithm> 8 9 #include "base/message_loop/message_loop_proxy.h" 10 #include "remoting/base/capabilities.h" 11 #include "remoting/base/logging.h" 12 #include "remoting/codec/audio_encoder.h" 13 #include "remoting/codec/audio_encoder_opus.h" 14 #include "remoting/codec/audio_encoder_verbatim.h" 15 #include "remoting/codec/video_encoder.h" 16 #include "remoting/codec/video_encoder_verbatim.h" 17 #include "remoting/codec/video_encoder_vpx.h" 18 #include "remoting/host/audio_capturer.h" 19 #include "remoting/host/audio_scheduler.h" 20 #include "remoting/host/desktop_environment.h" 21 #include "remoting/host/host_extension_session.h" 22 #include "remoting/host/input_injector.h" 23 #include "remoting/host/screen_controls.h" 24 #include "remoting/host/screen_resolution.h" 25 #include "remoting/host/video_scheduler.h" 26 #include "remoting/proto/control.pb.h" 27 #include "remoting/proto/event.pb.h" 28 #include "remoting/protocol/client_stub.h" 29 #include "remoting/protocol/clipboard_thread_proxy.h" 30 #include "remoting/protocol/pairing_registry.h" 31 #include "third_party/webrtc/modules/desktop_capture/screen_capturer.h" 32 33 // Default DPI to assume for old clients that use notifyClientDimensions. 34 const int kDefaultDPI = 96; 35 36 namespace remoting { 37 38 ClientSession::ClientSession( 39 EventHandler* event_handler, 40 scoped_refptr<base::SingleThreadTaskRunner> audio_task_runner, 41 scoped_refptr<base::SingleThreadTaskRunner> input_task_runner, 42 scoped_refptr<base::SingleThreadTaskRunner> video_capture_task_runner, 43 scoped_refptr<base::SingleThreadTaskRunner> video_encode_task_runner, 44 scoped_refptr<base::SingleThreadTaskRunner> network_task_runner, 45 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner, 46 scoped_ptr<protocol::ConnectionToClient> connection, 47 DesktopEnvironmentFactory* desktop_environment_factory, 48 const base::TimeDelta& max_duration, 49 scoped_refptr<protocol::PairingRegistry> pairing_registry) 50 : event_handler_(event_handler), 51 connection_(connection.Pass()), 52 client_jid_(connection_->session()->jid()), 53 control_factory_(this), 54 desktop_environment_factory_(desktop_environment_factory), 55 input_tracker_(&host_input_filter_), 56 remote_input_filter_(&input_tracker_), 57 mouse_clamping_filter_(&remote_input_filter_), 58 disable_input_filter_(mouse_clamping_filter_.input_filter()), 59 disable_clipboard_filter_(clipboard_echo_filter_.host_filter()), 60 auth_input_filter_(&disable_input_filter_), 61 auth_clipboard_filter_(&disable_clipboard_filter_), 62 client_clipboard_factory_(clipboard_echo_filter_.client_filter()), 63 max_duration_(max_duration), 64 audio_task_runner_(audio_task_runner), 65 input_task_runner_(input_task_runner), 66 video_capture_task_runner_(video_capture_task_runner), 67 video_encode_task_runner_(video_encode_task_runner), 68 network_task_runner_(network_task_runner), 69 ui_task_runner_(ui_task_runner), 70 pairing_registry_(pairing_registry) { 71 connection_->SetEventHandler(this); 72 73 // TODO(sergeyu): Currently ConnectionToClient expects stubs to be 74 // set before channels are connected. Make it possible to set stubs 75 // later and set them only when connection is authenticated. 76 connection_->set_clipboard_stub(&auth_clipboard_filter_); 77 connection_->set_host_stub(this); 78 connection_->set_input_stub(&auth_input_filter_); 79 80 // |auth_*_filter_|'s states reflect whether the session is authenticated. 81 auth_input_filter_.set_enabled(false); 82 auth_clipboard_filter_.set_enabled(false); 83 84 #if defined(OS_WIN) 85 // LocalInputMonitorWin filters out an echo of the injected input before it 86 // reaches |remote_input_filter_|. 87 remote_input_filter_.SetExpectLocalEcho(false); 88 #endif // defined(OS_WIN) 89 } 90 91 ClientSession::~ClientSession() { 92 DCHECK(CalledOnValidThread()); 93 DCHECK(!audio_scheduler_.get()); 94 DCHECK(!desktop_environment_); 95 DCHECK(!input_injector_); 96 DCHECK(!screen_controls_); 97 DCHECK(!video_scheduler_.get()); 98 99 connection_.reset(); 100 } 101 102 void ClientSession::AddExtensionSession( 103 scoped_ptr<HostExtensionSession> extension_session) { 104 DCHECK(CalledOnValidThread()); 105 106 extension_sessions_.push_back(extension_session.release()); 107 } 108 109 void ClientSession::AddHostCapabilities(const std::string& capabilities) { 110 DCHECK(CalledOnValidThread()); 111 112 if (capabilities.empty()) 113 return; 114 115 if (!host_capabilities_.empty()) 116 host_capabilities_.append(" "); 117 118 host_capabilities_.append(capabilities); 119 } 120 121 void ClientSession::NotifyClientResolution( 122 const protocol::ClientResolution& resolution) { 123 DCHECK(CalledOnValidThread()); 124 125 // TODO(sergeyu): Move these checks to protocol layer. 126 if (!resolution.has_dips_width() || !resolution.has_dips_height() || 127 resolution.dips_width() < 0 || resolution.dips_height() < 0 || 128 resolution.width() <= 0 || resolution.height() <= 0) { 129 LOG(ERROR) << "Received invalid ClientResolution message."; 130 return; 131 } 132 133 VLOG(1) << "Received ClientResolution (dips_width=" 134 << resolution.dips_width() << ", dips_height=" 135 << resolution.dips_height() << ")"; 136 137 if (!screen_controls_) 138 return; 139 140 ScreenResolution client_resolution( 141 webrtc::DesktopSize(resolution.dips_width(), resolution.dips_height()), 142 webrtc::DesktopVector(kDefaultDPI, kDefaultDPI)); 143 144 // Try to match the client's resolution. 145 screen_controls_->SetScreenResolution(client_resolution); 146 } 147 148 void ClientSession::ControlVideo(const protocol::VideoControl& video_control) { 149 DCHECK(CalledOnValidThread()); 150 151 if (video_control.has_enable()) { 152 VLOG(1) << "Received VideoControl (enable=" 153 << video_control.enable() << ")"; 154 video_scheduler_->Pause(!video_control.enable()); 155 } 156 if (video_control.has_lossless_encode()) { 157 VLOG(1) << "Received VideoControl (lossless_encode=" 158 << video_control.lossless_encode() << ")"; 159 video_scheduler_->SetLosslessEncode(video_control.lossless_encode()); 160 } 161 if (video_control.has_lossless_color()) { 162 VLOG(1) << "Received VideoControl (lossless_color=" 163 << video_control.lossless_color() << ")"; 164 video_scheduler_->SetLosslessColor(video_control.lossless_color()); 165 } 166 } 167 168 void ClientSession::ControlAudio(const protocol::AudioControl& audio_control) { 169 DCHECK(CalledOnValidThread()); 170 171 if (audio_control.has_enable()) { 172 VLOG(1) << "Received AudioControl (enable=" 173 << audio_control.enable() << ")"; 174 if (audio_scheduler_.get()) 175 audio_scheduler_->Pause(!audio_control.enable()); 176 } 177 } 178 179 void ClientSession::SetCapabilities( 180 const protocol::Capabilities& capabilities) { 181 DCHECK(CalledOnValidThread()); 182 183 // The client should not send protocol::Capabilities if it is not supported by 184 // the config channel. 185 if (!connection_->session()->config().SupportsCapabilities()) { 186 LOG(ERROR) << "Unexpected protocol::Capabilities has been received."; 187 return; 188 } 189 190 // Ignore all the messages but the 1st one. 191 if (client_capabilities_) { 192 LOG(WARNING) << "protocol::Capabilities has been received already."; 193 return; 194 } 195 196 client_capabilities_ = make_scoped_ptr(new std::string()); 197 if (capabilities.has_capabilities()) 198 *client_capabilities_ = capabilities.capabilities(); 199 200 VLOG(1) << "Client capabilities: " << *client_capabilities_; 201 event_handler_->OnSessionClientCapabilities(this); 202 203 // Calculate the set of capabilities enabled by both client and host and 204 // pass it to the desktop environment if it is available. 205 desktop_environment_->SetCapabilities( 206 IntersectCapabilities(*client_capabilities_, host_capabilities_)); 207 } 208 209 void ClientSession::RequestPairing( 210 const protocol::PairingRequest& pairing_request) { 211 if (pairing_registry_ && pairing_request.has_client_name()) { 212 protocol::PairingRegistry::Pairing pairing = 213 pairing_registry_->CreatePairing(pairing_request.client_name()); 214 protocol::PairingResponse pairing_response; 215 pairing_response.set_client_id(pairing.client_id()); 216 pairing_response.set_shared_secret(pairing.shared_secret()); 217 connection_->client_stub()->SetPairingResponse(pairing_response); 218 } 219 } 220 221 void ClientSession::DeliverClientMessage( 222 const protocol::ExtensionMessage& message) { 223 if (message.has_type()) { 224 if (message.type() == "test-echo") { 225 protocol::ExtensionMessage reply; 226 reply.set_type("test-echo-reply"); 227 if (message.has_data()) 228 reply.set_data(message.data().substr(0, 16)); 229 connection_->client_stub()->DeliverHostMessage(reply); 230 return; 231 } else if (message.type() == "gnubby-auth") { 232 if (gnubby_auth_handler_) { 233 gnubby_auth_handler_->DeliverClientMessage(message.data()); 234 } else { 235 HOST_LOG << "gnubby auth is not enabled"; 236 } 237 return; 238 } else { 239 for(HostExtensionSessionList::iterator it = extension_sessions_.begin(); 240 it != extension_sessions_.end(); ++it) { 241 // Extension returns |true| to indicate that the message was handled. 242 if ((*it)->OnExtensionMessage(this, message)) 243 return; 244 } 245 } 246 } 247 HOST_LOG << "Unexpected message received: " 248 << message.type() << ": " << message.data(); 249 } 250 251 void ClientSession::OnConnectionAuthenticating( 252 protocol::ConnectionToClient* connection) { 253 event_handler_->OnSessionAuthenticating(this); 254 } 255 256 void ClientSession::OnConnectionAuthenticated( 257 protocol::ConnectionToClient* connection) { 258 DCHECK(CalledOnValidThread()); 259 DCHECK_EQ(connection_.get(), connection); 260 DCHECK(!audio_scheduler_.get()); 261 DCHECK(!desktop_environment_); 262 DCHECK(!input_injector_); 263 DCHECK(!screen_controls_); 264 DCHECK(!video_scheduler_.get()); 265 266 auth_input_filter_.set_enabled(true); 267 auth_clipboard_filter_.set_enabled(true); 268 269 clipboard_echo_filter_.set_client_stub(connection_->client_stub()); 270 mouse_clamping_filter_.set_video_stub(connection_->video_stub()); 271 272 if (max_duration_ > base::TimeDelta()) { 273 // TODO(simonmorris): Let Disconnect() tell the client that the 274 // disconnection was caused by the session exceeding its maximum duration. 275 max_duration_timer_.Start(FROM_HERE, max_duration_, 276 this, &ClientSession::DisconnectSession); 277 } 278 279 // Disconnect the session if the connection was rejected by the host. 280 if (!event_handler_->OnSessionAuthenticated(this)) { 281 DisconnectSession(); 282 return; 283 } 284 285 // Create the desktop environment. Drop the connection if it could not be 286 // created for any reason (for instance the curtain could not initialize). 287 desktop_environment_ = 288 desktop_environment_factory_->Create(control_factory_.GetWeakPtr()); 289 if (!desktop_environment_) { 290 DisconnectSession(); 291 return; 292 } 293 294 AddHostCapabilities(desktop_environment_->GetCapabilities()); 295 296 // Ignore protocol::Capabilities messages from the client if it does not 297 // support any capabilities. 298 if (!connection_->session()->config().SupportsCapabilities()) { 299 VLOG(1) << "The client does not support any capabilities."; 300 301 client_capabilities_ = make_scoped_ptr(new std::string()); 302 event_handler_->OnSessionClientCapabilities(this); 303 304 desktop_environment_->SetCapabilities(*client_capabilities_); 305 } 306 307 // Create the object that controls the screen resolution. 308 screen_controls_ = desktop_environment_->CreateScreenControls(); 309 310 // Create the event executor. 311 input_injector_ = desktop_environment_->CreateInputInjector(); 312 313 // Connect the host clipboard and input stubs. 314 host_input_filter_.set_input_stub(input_injector_.get()); 315 clipboard_echo_filter_.set_host_stub(input_injector_.get()); 316 317 // Create a VideoEncoder based on the session's video channel configuration. 318 scoped_ptr<VideoEncoder> video_encoder = 319 CreateVideoEncoder(connection_->session()->config()); 320 321 // Create a VideoScheduler to pump frames from the capturer to the client. 322 video_scheduler_ = new VideoScheduler( 323 video_capture_task_runner_, 324 video_encode_task_runner_, 325 network_task_runner_, 326 desktop_environment_->CreateVideoCapturer(), 327 video_encoder.Pass(), 328 connection_->client_stub(), 329 &mouse_clamping_filter_); 330 331 // Create an AudioScheduler if audio is enabled, to pump audio samples. 332 if (connection_->session()->config().is_audio_enabled()) { 333 scoped_ptr<AudioEncoder> audio_encoder = 334 CreateAudioEncoder(connection_->session()->config()); 335 audio_scheduler_ = new AudioScheduler( 336 audio_task_runner_, 337 network_task_runner_, 338 desktop_environment_->CreateAudioCapturer(), 339 audio_encoder.Pass(), 340 connection_->audio_stub()); 341 } 342 343 // Create a GnubbyAuthHandler to proxy gnubbyd messages. 344 gnubby_auth_handler_ = desktop_environment_->CreateGnubbyAuthHandler( 345 connection_->client_stub()); 346 } 347 348 void ClientSession::OnConnectionChannelsConnected( 349 protocol::ConnectionToClient* connection) { 350 DCHECK(CalledOnValidThread()); 351 DCHECK_EQ(connection_.get(), connection); 352 353 // Negotiate capabilities with the client. 354 if (connection_->session()->config().SupportsCapabilities()) { 355 VLOG(1) << "Host capabilities: " << host_capabilities_; 356 357 protocol::Capabilities capabilities; 358 capabilities.set_capabilities(host_capabilities_); 359 connection_->client_stub()->SetCapabilities(capabilities); 360 } 361 362 // Start the event executor. 363 input_injector_->Start(CreateClipboardProxy()); 364 SetDisableInputs(false); 365 366 // Start capturing the screen. 367 video_scheduler_->Start(); 368 369 // Start recording audio. 370 if (connection_->session()->config().is_audio_enabled()) 371 audio_scheduler_->Start(); 372 373 // Notify the event handler that all our channels are now connected. 374 event_handler_->OnSessionChannelsConnected(this); 375 } 376 377 void ClientSession::OnConnectionClosed( 378 protocol::ConnectionToClient* connection, 379 protocol::ErrorCode error) { 380 DCHECK(CalledOnValidThread()); 381 DCHECK_EQ(connection_.get(), connection); 382 383 // Ignore any further callbacks. 384 control_factory_.InvalidateWeakPtrs(); 385 386 // If the client never authenticated then the session failed. 387 if (!auth_input_filter_.enabled()) 388 event_handler_->OnSessionAuthenticationFailed(this); 389 390 // Block any further input events from the client. 391 // TODO(wez): Fix ChromotingHost::OnSessionClosed not to check our 392 // is_authenticated(), so that we can disable |auth_*_filter_| here. 393 disable_input_filter_.set_enabled(false); 394 disable_clipboard_filter_.set_enabled(false); 395 396 // Ensure that any pressed keys or buttons are released. 397 input_tracker_.ReleaseAll(); 398 399 // Stop components access the client, audio or video stubs, which are no 400 // longer valid once ConnectionToClient calls OnConnectionClosed(). 401 if (audio_scheduler_.get()) { 402 audio_scheduler_->Stop(); 403 audio_scheduler_ = NULL; 404 } 405 if (video_scheduler_.get()) { 406 video_scheduler_->Stop(); 407 video_scheduler_ = NULL; 408 } 409 410 client_clipboard_factory_.InvalidateWeakPtrs(); 411 input_injector_.reset(); 412 screen_controls_.reset(); 413 desktop_environment_.reset(); 414 415 // Notify the ChromotingHost that this client is disconnected. 416 // TODO(sergeyu): Log failure reason? 417 event_handler_->OnSessionClosed(this); 418 } 419 420 void ClientSession::OnSequenceNumberUpdated( 421 protocol::ConnectionToClient* connection, int64 sequence_number) { 422 DCHECK(CalledOnValidThread()); 423 DCHECK_EQ(connection_.get(), connection); 424 425 if (video_scheduler_.get()) 426 video_scheduler_->UpdateSequenceNumber(sequence_number); 427 428 event_handler_->OnSessionSequenceNumber(this, sequence_number); 429 } 430 431 void ClientSession::OnRouteChange( 432 protocol::ConnectionToClient* connection, 433 const std::string& channel_name, 434 const protocol::TransportRoute& route) { 435 DCHECK(CalledOnValidThread()); 436 DCHECK_EQ(connection_.get(), connection); 437 event_handler_->OnSessionRouteChange(this, channel_name, route); 438 } 439 440 const std::string& ClientSession::client_jid() const { 441 return client_jid_; 442 } 443 444 void ClientSession::DisconnectSession() { 445 DCHECK(CalledOnValidThread()); 446 DCHECK(connection_.get()); 447 448 max_duration_timer_.Stop(); 449 450 // This triggers OnConnectionClosed(), and the session may be destroyed 451 // as the result, so this call must be the last in this method. 452 connection_->Disconnect(); 453 } 454 455 void ClientSession::OnLocalMouseMoved(const webrtc::DesktopVector& position) { 456 DCHECK(CalledOnValidThread()); 457 remote_input_filter_.LocalMouseMoved(position); 458 } 459 460 void ClientSession::SetDisableInputs(bool disable_inputs) { 461 DCHECK(CalledOnValidThread()); 462 463 if (disable_inputs) 464 input_tracker_.ReleaseAll(); 465 466 disable_input_filter_.set_enabled(!disable_inputs); 467 disable_clipboard_filter_.set_enabled(!disable_inputs); 468 } 469 470 void ClientSession::SetGnubbyAuthHandlerForTesting( 471 GnubbyAuthHandler* gnubby_auth_handler) { 472 gnubby_auth_handler_.reset(gnubby_auth_handler); 473 } 474 475 scoped_ptr<protocol::ClipboardStub> ClientSession::CreateClipboardProxy() { 476 DCHECK(CalledOnValidThread()); 477 478 return scoped_ptr<protocol::ClipboardStub>( 479 new protocol::ClipboardThreadProxy( 480 client_clipboard_factory_.GetWeakPtr(), 481 base::MessageLoopProxy::current())); 482 } 483 484 // TODO(sergeyu): Move this to SessionManager? 485 // static 486 scoped_ptr<VideoEncoder> ClientSession::CreateVideoEncoder( 487 const protocol::SessionConfig& config) { 488 const protocol::ChannelConfig& video_config = config.video_config(); 489 490 if (video_config.codec == protocol::ChannelConfig::CODEC_VP8) { 491 return remoting::VideoEncoderVpx::CreateForVP8().PassAs<VideoEncoder>(); 492 } else if (video_config.codec == protocol::ChannelConfig::CODEC_VP9) { 493 return remoting::VideoEncoderVpx::CreateForVP9().PassAs<VideoEncoder>(); 494 } 495 496 NOTREACHED(); 497 return scoped_ptr<VideoEncoder>(); 498 } 499 500 // static 501 scoped_ptr<AudioEncoder> ClientSession::CreateAudioEncoder( 502 const protocol::SessionConfig& config) { 503 const protocol::ChannelConfig& audio_config = config.audio_config(); 504 505 if (audio_config.codec == protocol::ChannelConfig::CODEC_VERBATIM) { 506 return scoped_ptr<AudioEncoder>(new AudioEncoderVerbatim()); 507 } else if (audio_config.codec == protocol::ChannelConfig::CODEC_OPUS) { 508 return scoped_ptr<AudioEncoder>(new AudioEncoderOpus()); 509 } 510 511 NOTREACHED(); 512 return scoped_ptr<AudioEncoder>(); 513 } 514 515 } // namespace remoting 516