| // Copyright 2013 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "remoting/client/chromoting_session.h" | 
 |  | 
 | #include <stdint.h> | 
 |  | 
 | #include <algorithm> | 
 | #include <memory> | 
 | #include <utility> | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/callback.h" | 
 | #include "base/callback_helpers.h" | 
 | #include "base/format_macros.h" | 
 | #include "base/logging.h" | 
 | #include "base/memory/raw_ptr.h" | 
 | #include "base/task/single_thread_task_runner.h" | 
 | #include "base/task/task_runner_util.h" | 
 | #include "base/timer/timer.h" | 
 | #include "components/webrtc/thread_wrapper.h" | 
 | #include "net/socket/client_socket_factory.h" | 
 | #include "remoting/base/chromoting_event.h" | 
 | #include "remoting/base/service_urls.h" | 
 | #include "remoting/client/audio/audio_player.h" | 
 | #include "remoting/client/chromoting_client_runtime.h" | 
 | #include "remoting/client/client_telemetry_logger.h" | 
 | #include "remoting/client/input/native_device_keymap.h" | 
 | #include "remoting/protocol/chromium_port_allocator_factory.h" | 
 | #include "remoting/protocol/chromium_socket_factory.h" | 
 | #include "remoting/protocol/client_authentication_config.h" | 
 | #include "remoting/protocol/frame_consumer.h" | 
 | #include "remoting/protocol/host_stub.h" | 
 | #include "remoting/protocol/network_settings.h" | 
 | #include "remoting/protocol/performance_tracker.h" | 
 | #include "remoting/protocol/token_validator.h" | 
 | #include "remoting/protocol/transport_context.h" | 
 | #include "remoting/protocol/video_renderer.h" | 
 | #include "remoting/signaling/ftl_client_uuid_device_id_provider.h" | 
 | #include "remoting/signaling/ftl_signal_strategy.h" | 
 | #include "remoting/signaling/server_log_entry.h" | 
 | #include "services/network/public/cpp/shared_url_loader_factory.h" | 
 | #include "ui/events/keycodes/dom/keycode_converter.h" | 
 |  | 
 | namespace remoting { | 
 |  | 
 | namespace { | 
 |  | 
 | // Default DPI to assume for old clients that use notifyClientResolution. | 
 | const int kDefaultDPI = 96; | 
 |  | 
 | // Used by NormalizeclientResolution. See comment below. | 
 | const int kMinDimension = 640; | 
 |  | 
 | // Interval at which to log performance statistics, if enabled. | 
 | constexpr base::TimeDelta kPerfStatsInterval = base::Minutes(1); | 
 |  | 
 | // Delay to destroy the signal strategy, so that the session-terminate event can | 
 | // still be sent out. | 
 | constexpr base::TimeDelta kDestroySignalingDelay = base::Seconds(2); | 
 |  | 
 | bool IsClientResolutionValid(int dips_width, int dips_height) { | 
 |   // This prevents sending resolution on a portrait mode small phone screen | 
 |   // because resizing the remote desktop to portrait will mess with icons and | 
 |   // such on the desktop and it probably isn't what the user wants. | 
 |   return (dips_width >= dips_height) || (dips_width >= kMinDimension); | 
 | } | 
 |  | 
 | // Normalizes the resolution so that both dimensions are not smaller than | 
 | // kMinDimension. | 
 | void NormalizeClientResolution(protocol::ClientResolution* resolution) { | 
 |   int min_dimension = | 
 |       std::min(resolution->dips_width(), resolution->dips_height()); | 
 |   if (min_dimension >= kMinDimension) { | 
 |     return; | 
 |   } | 
 |  | 
 |   // Always scale by integer to prevent blurry interpolation. | 
 |   int scale = std::ceil(((float)kMinDimension) / min_dimension); | 
 |   resolution->set_dips_width(resolution->dips_width() * scale); | 
 |   resolution->set_dips_height(resolution->dips_height() * scale); | 
 | } | 
 |  | 
 | struct SessionContext { | 
 |   base::WeakPtr<ChromotingSession::Delegate> delegate; | 
 |   std::unique_ptr<protocol::AudioStub> audio_player; | 
 |   std::unique_ptr<base::WeakPtrFactory<protocol::AudioStub>> | 
 |       audio_player_weak_factory; | 
 |   std::unique_ptr<protocol::CursorShapeStub> cursor_shape_stub; | 
 |   std::unique_ptr<protocol::VideoRenderer> video_renderer; | 
 |  | 
 |   ConnectToHostInfo info; | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | class ChromotingSession::Core : public ClientUserInterface, | 
 |                                 public protocol::ClipboardStub, | 
 |                                 public protocol::KeyboardLayoutStub { | 
 |  public: | 
 |   Core(ChromotingClientRuntime* runtime, | 
 |        std::unique_ptr<ClientTelemetryLogger> logger, | 
 |        std::unique_ptr<SessionContext> session_context); | 
 |  | 
 |   Core(const Core&) = delete; | 
 |   Core& operator=(const Core&) = delete; | 
 |  | 
 |   ~Core() override; | 
 |  | 
 |   void RequestPairing(const std::string& device_name); | 
 |   void SendMouseEvent(int x, | 
 |                       int y, | 
 |                       protocol::MouseEvent_MouseButton button, | 
 |                       bool button_down); | 
 |   void SendMouseWheelEvent(int delta_x, int delta_y); | 
 |   void SendKeyEvent(int usb_key_code, bool key_down); | 
 |   void SendTextEvent(const std::string& text); | 
 |   void SendTouchEvent(const protocol::TouchEvent& touch_event); | 
 |   void SendClientResolution(int dips_width, int dips_height, float scale); | 
 |   void EnableVideoChannel(bool enable); | 
 |   void SendClientMessage(const std::string& type, const std::string& data); | 
 |  | 
 |   // This function is still valid after Invalidate() is called. | 
 |   std::unique_ptr<FeedbackData> GetFeedbackData(); | 
 |  | 
 |   // Logs the disconnect event and invalidates the instance. | 
 |   void Disconnect(); | 
 |  | 
 |   // ClientUserInterface implementation. | 
 |   void OnConnectionState(protocol::ConnectionToHost::State state, | 
 |                          protocol::ErrorCode error) override; | 
 |   void OnConnectionReady(bool ready) override; | 
 |   void OnRouteChanged(const std::string& channel_name, | 
 |                       const protocol::TransportRoute& route) override; | 
 |   void SetCapabilities(const std::string& capabilities) override; | 
 |   void SetPairingResponse(const protocol::PairingResponse& response) override; | 
 |   void DeliverHostMessage(const protocol::ExtensionMessage& message) override; | 
 |   void SetDesktopSize(const webrtc::DesktopSize& size, | 
 |                       const webrtc::DesktopVector& dpi) override; | 
 |   protocol::ClipboardStub* GetClipboardStub() override; | 
 |   protocol::CursorShapeStub* GetCursorShapeStub() override; | 
 |   protocol::KeyboardLayoutStub* GetKeyboardLayoutStub() override; | 
 |  | 
 |   // ClipboardStub implementation. | 
 |   void InjectClipboardEvent(const protocol::ClipboardEvent& event) override; | 
 |  | 
 |   // KeyboardLayoutStub implementation. | 
 |   void SetKeyboardLayout(const protocol::KeyboardLayout& layout) override; | 
 |  | 
 |   base::WeakPtr<Core> GetWeakPtr(); | 
 |  | 
 |  private: | 
 |   // Destroys the client and invalidates weak pointers. This doesn't destroy the | 
 |   // instance itself. | 
 |   void Invalidate(); | 
 |  | 
 |   void ConnectOnNetworkThread(); | 
 |   void LogPerfStats(); | 
 |  | 
 |   // Pops up a UI to fetch the PIN. | 
 |   void FetchSecret( | 
 |       bool pairing_supported, | 
 |       const protocol::SecretFetchedCallback& secret_fetched_callback); | 
 |   void HandleOnSecretFetched(const protocol::SecretFetchedCallback& callback, | 
 |                              const std::string secret); | 
 |  | 
 |   // Pops up a UI to fetch the third party token. | 
 |   void FetchThirdPartyToken( | 
 |       const std::string& host_public_key, | 
 |       const std::string& token_url, | 
 |       const std::string& scopes, | 
 |       const protocol::ThirdPartyTokenFetchedCallback& token_fetched_callback); | 
 |   void HandleOnThirdPartyTokenFetched( | 
 |       const protocol::ThirdPartyTokenFetchedCallback& callback, | 
 |       const std::string& token, | 
 |       const protocol::TokenValidator::ValidationResult& validation_result); | 
 |  | 
 |   scoped_refptr<AutoThreadTaskRunner> ui_task_runner() { | 
 |     return runtime_->ui_task_runner(); | 
 |   } | 
 |  | 
 |   scoped_refptr<AutoThreadTaskRunner> network_task_runner() { | 
 |     return runtime_->network_task_runner(); | 
 |   } | 
 |  | 
 |   // |runtime_| and |logger_| are stored separately from |session_context_| so | 
 |   // that they won't be destroyed after the core is invalidated. | 
 |   const raw_ptr<ChromotingClientRuntime> runtime_; | 
 |   std::unique_ptr<ClientTelemetryLogger> logger_; | 
 |  | 
 |   std::unique_ptr<SessionContext> session_context_; | 
 |  | 
 |   std::unique_ptr<ClientContext> client_context_; | 
 |   std::unique_ptr<protocol::PerformanceTracker> perf_tracker_; | 
 |  | 
 |   // |signaling_| must outlive |client_|. | 
 |   std::unique_ptr<SignalStrategy> signaling_; | 
 |   std::unique_ptr<OAuthTokenGetter> token_getter_; | 
 |   std::unique_ptr<ChromotingClient> client_; | 
 |  | 
 |   // Empty string if client doesn't request for pairing. | 
 |   std::string device_name_for_pairing_; | 
 |  | 
 |   // The current session state. | 
 |   protocol::ConnectionToHost::State session_state_ = | 
 |       protocol::ConnectionToHost::INITIALIZING; | 
 |  | 
 |   base::RepeatingTimer perf_stats_logging_timer_; | 
 |  | 
 |   // weak_factory_.GetWeakPtr() creates new valid WeakPtrs after | 
 |   // weak_factory_.InvalidateWeakPtrs() is called. We store and return | 
 |   // |weak_ptr_| in GetWeakPtr() so that its copies are still invalidated once | 
 |   // InvalidateWeakPtrs() is called. | 
 |   base::WeakPtr<Core> weak_ptr_; | 
 |   base::WeakPtrFactory<Core> weak_factory_{this}; | 
 | }; | 
 |  | 
 | ChromotingSession::Core::Core(ChromotingClientRuntime* runtime, | 
 |                               std::unique_ptr<ClientTelemetryLogger> logger, | 
 |                               std::unique_ptr<SessionContext> session_context) | 
 |     : runtime_(runtime), | 
 |       logger_(std::move(logger)), | 
 |       session_context_(std::move(session_context)) { | 
 |   DCHECK(ui_task_runner()->BelongsToCurrentThread()); | 
 |   DCHECK(runtime_); | 
 |   DCHECK(logger_); | 
 |   DCHECK(session_context_); | 
 |  | 
 |   weak_ptr_ = weak_factory_.GetWeakPtr(); | 
 |  | 
 |   network_task_runner()->PostTask( | 
 |       FROM_HERE, base::BindOnce(&Core::ConnectOnNetworkThread, GetWeakPtr())); | 
 | } | 
 |  | 
 | ChromotingSession::Core::~Core() { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |  | 
 |   // Make sure we log a close event if the session has not been disconnected | 
 |   // yet. | 
 |   Disconnect(); | 
 | } | 
 |  | 
 | void ChromotingSession::Core::RequestPairing(const std::string& device_name) { | 
 |   DCHECK(!device_name.empty()); | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |   device_name_for_pairing_ = device_name; | 
 | } | 
 |  | 
 | void ChromotingSession::Core::SendMouseEvent( | 
 |     int x, | 
 |     int y, | 
 |     protocol::MouseEvent_MouseButton button, | 
 |     bool button_down) { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |   protocol::MouseEvent event; | 
 |   event.set_x(x); | 
 |   event.set_y(y); | 
 |   event.set_button(button); | 
 |   if (button != protocol::MouseEvent::BUTTON_UNDEFINED) | 
 |     event.set_button_down(button_down); | 
 |  | 
 |   client_->input_stub()->InjectMouseEvent(event); | 
 | } | 
 |  | 
 | void ChromotingSession::Core::SendMouseWheelEvent(int delta_x, int delta_y) { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |   protocol::MouseEvent event; | 
 |   event.set_wheel_delta_x(delta_x); | 
 |   event.set_wheel_delta_y(delta_y); | 
 |   client_->input_stub()->InjectMouseEvent(event); | 
 | } | 
 |  | 
 | void ChromotingSession::Core::SendKeyEvent(int usb_key_code, bool key_down) { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |   protocol::KeyEvent event; | 
 |   event.set_usb_keycode(usb_key_code); | 
 |   event.set_pressed(key_down); | 
 |   client_->input_stub()->InjectKeyEvent(event); | 
 | } | 
 |  | 
 | void ChromotingSession::Core::SendTextEvent(const std::string& text) { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |   protocol::TextEvent event; | 
 |   event.set_text(text); | 
 |   client_->input_stub()->InjectTextEvent(event); | 
 | } | 
 |  | 
 | void ChromotingSession::Core::SendTouchEvent( | 
 |     const protocol::TouchEvent& touch_event) { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |   client_->input_stub()->InjectTouchEvent(touch_event); | 
 | } | 
 |  | 
 | void ChromotingSession::Core::SendClientResolution(int dips_width, | 
 |                                                    int dips_height, | 
 |                                                    float scale) { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |   if (!IsClientResolutionValid(dips_width, dips_height)) { | 
 |     return; | 
 |   } | 
 |  | 
 |   protocol::ClientResolution client_resolution; | 
 |   client_resolution.set_dips_width(dips_width); | 
 |   client_resolution.set_dips_height(dips_height); | 
 |   client_resolution.set_x_dpi(scale * kDefaultDPI); | 
 |   client_resolution.set_y_dpi(scale * kDefaultDPI); | 
 |   NormalizeClientResolution(&client_resolution); | 
 |  | 
 |   // Include the legacy width & height in physical pixels for use by older | 
 |   // hosts. | 
 |   client_resolution.set_width_deprecated(dips_width * scale); | 
 |   client_resolution.set_height_deprecated(dips_height * scale); | 
 |  | 
 |   client_->host_stub()->NotifyClientResolution(client_resolution); | 
 | } | 
 |  | 
 | void ChromotingSession::Core::EnableVideoChannel(bool enable) { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |   protocol::VideoControl video_control; | 
 |   video_control.set_enable(enable); | 
 |   client_->host_stub()->ControlVideo(video_control); | 
 | } | 
 |  | 
 | void ChromotingSession::Core::SendClientMessage(const std::string& type, | 
 |                                                 const std::string& data) { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |   protocol::ExtensionMessage extension_message; | 
 |   extension_message.set_type(type); | 
 |   extension_message.set_data(data); | 
 |   client_->host_stub()->DeliverClientMessage(extension_message); | 
 | } | 
 |  | 
 | std::unique_ptr<FeedbackData> ChromotingSession::Core::GetFeedbackData() { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |  | 
 |   auto data = std::make_unique<FeedbackData>(); | 
 |   data->FillWithChromotingEvent(logger_->current_session_state_event()); | 
 |   return data; | 
 | } | 
 |  | 
 | void ChromotingSession::Core::Disconnect() { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |  | 
 |   // Do not log session state change if the connection is already closed. | 
 |   if (session_state_ != protocol::ConnectionToHost::INITIALIZING && | 
 |       session_state_ != protocol::ConnectionToHost::FAILED && | 
 |       session_state_ != protocol::ConnectionToHost::CLOSED) { | 
 |     ChromotingEvent::SessionState session_state_to_log; | 
 |     if (session_state_ == protocol::ConnectionToHost::CONNECTED) { | 
 |       session_state_to_log = ChromotingEvent::SessionState::CLOSED; | 
 |     } else { | 
 |       session_state_to_log = ChromotingEvent::SessionState::CONNECTION_CANCELED; | 
 |     } | 
 |     logger_->LogSessionStateChange(session_state_to_log, | 
 |                                    ChromotingEvent::ConnectionError::NONE); | 
 |     session_state_ = protocol::ConnectionToHost::CLOSED; | 
 |  | 
 |     // Make sure we send a session-terminate to the host. | 
 |     client_->Close(); | 
 |  | 
 |     Invalidate(); | 
 |   } | 
 | } | 
 |  | 
 | void ChromotingSession::Core::OnConnectionState( | 
 |     protocol::ConnectionToHost::State state, | 
 |     protocol::ErrorCode error) { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |  | 
 |   if (state == protocol::ConnectionToHost::CONNECTED) { | 
 |     perf_stats_logging_timer_.Start( | 
 |         FROM_HERE, kPerfStatsInterval, | 
 |         base::BindRepeating(&Core::LogPerfStats, GetWeakPtr())); | 
 |  | 
 |     if (!device_name_for_pairing_.empty()) { | 
 |       protocol::PairingRequest request; | 
 |       request.set_client_name(device_name_for_pairing_); | 
 |       client_->host_stub()->RequestPairing(request); | 
 |     } | 
 |   } else if (perf_stats_logging_timer_.IsRunning()) { | 
 |     perf_stats_logging_timer_.Stop(); | 
 |   } | 
 |  | 
 |   logger_->LogSessionStateChange( | 
 |       ClientTelemetryLogger::TranslateState(state, session_state_), | 
 |       ClientTelemetryLogger::TranslateError(error)); | 
 |  | 
 |   session_state_ = state; | 
 |  | 
 |   ui_task_runner()->PostTask( | 
 |       FROM_HERE, base::BindOnce(&ChromotingSession::Delegate::OnConnectionState, | 
 |                                 session_context_->delegate, state, error)); | 
 |  | 
 |   if (state == protocol::ConnectionToHost::CLOSED || | 
 |       state == protocol::ConnectionToHost::FAILED) { | 
 |     Invalidate(); | 
 |   } | 
 | } | 
 |  | 
 | void ChromotingSession::Core::OnConnectionReady(bool ready) { | 
 |   // We ignore this message, since OnConnectionState tells us the same thing. | 
 | } | 
 |  | 
 | void ChromotingSession::Core::OnRouteChanged( | 
 |     const std::string& channel_name, | 
 |     const protocol::TransportRoute& route) { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |   std::string message = "Channel " + channel_name + " using " + | 
 |                         protocol::TransportRoute::GetTypeString(route.type) + | 
 |                         " connection."; | 
 |   VLOG(1) << "Route: " << message; | 
 |   logger_->SetTransportRoute(route); | 
 | } | 
 |  | 
 | void ChromotingSession::Core::SetCapabilities(const std::string& capabilities) { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |   ui_task_runner()->PostTask( | 
 |       FROM_HERE, base::BindOnce(&ChromotingSession::Delegate::SetCapabilities, | 
 |                                 session_context_->delegate, capabilities)); | 
 | } | 
 |  | 
 | void ChromotingSession::Core::SetPairingResponse( | 
 |     const protocol::PairingResponse& response) { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |   ui_task_runner()->PostTask( | 
 |       FROM_HERE, | 
 |       base::BindOnce(&ChromotingSession::Delegate::CommitPairingCredentials, | 
 |                      session_context_->delegate, session_context_->info.host_id, | 
 |                      response.client_id(), response.shared_secret())); | 
 | } | 
 |  | 
 | void ChromotingSession::Core::DeliverHostMessage( | 
 |     const protocol::ExtensionMessage& message) { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |   ui_task_runner()->PostTask( | 
 |       FROM_HERE, | 
 |       base::BindOnce(&ChromotingSession::Delegate::HandleExtensionMessage, | 
 |                      session_context_->delegate, message.type(), | 
 |                      message.data())); | 
 | } | 
 |  | 
 | void ChromotingSession::Core::SetDesktopSize(const webrtc::DesktopSize& size, | 
 |                                              const webrtc::DesktopVector& dpi) { | 
 |   // ChromotingSession's VideoRenderer gets size from the frames and it doesn't | 
 |   // use DPI, so this call can be ignored. | 
 | } | 
 |  | 
 | protocol::ClipboardStub* ChromotingSession::Core::GetClipboardStub() { | 
 |   return this; | 
 | } | 
 |  | 
 | protocol::CursorShapeStub* ChromotingSession::Core::GetCursorShapeStub() { | 
 |   return session_context_->cursor_shape_stub.get(); | 
 | } | 
 |  | 
 | protocol::KeyboardLayoutStub* ChromotingSession::Core::GetKeyboardLayoutStub() { | 
 |   return this; | 
 | } | 
 |  | 
 | void ChromotingSession::Core::InjectClipboardEvent( | 
 |     const protocol::ClipboardEvent& event) { | 
 |   NOTIMPLEMENTED(); | 
 | } | 
 |  | 
 | void ChromotingSession::Core::SetKeyboardLayout( | 
 |     const protocol::KeyboardLayout& layout) { | 
 |   NOTIMPLEMENTED(); | 
 | } | 
 |  | 
 | base::WeakPtr<ChromotingSession::Core> ChromotingSession::Core::GetWeakPtr() { | 
 |   return weak_ptr_; | 
 | } | 
 |  | 
 | void ChromotingSession::Core::Invalidate() { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |  | 
 |   // Prevent all pending and future calls from ChromotingSession. | 
 |   weak_factory_.InvalidateWeakPtrs(); | 
 |  | 
 |   client_.reset(); | 
 |   token_getter_.reset(); | 
 |   perf_tracker_.reset(); | 
 |   client_context_.reset(); | 
 |   session_context_.reset(); | 
 |  | 
 |   // Dirty hack to make sure session-terminate message is sent before | 
 |   // |signaling_| gets deleted. W/o the message being sent, the other side will | 
 |   // believe an error has occurred. | 
 |   if (signaling_) { | 
 |     signaling_->Disconnect(); | 
 |     network_task_runner()->PostDelayedTask( | 
 |         FROM_HERE, | 
 |         base::BindOnce( | 
 |             [](std::unique_ptr<SignalStrategy> signaling) { | 
 |               signaling.reset(); | 
 |             }, | 
 |             std::move(signaling_)), | 
 |         kDestroySignalingDelay); | 
 |   } | 
 | } | 
 |  | 
 | void ChromotingSession::Core::ConnectOnNetworkThread() { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |  | 
 |   if (session_context_->info.host_ftl_id.empty()) { | 
 |     // Simulate a CONNECTING state to make sure it doesn't skew telemetry. | 
 |     OnConnectionState(protocol::ConnectionToHost::State::CONNECTING, | 
 |                       protocol::OK); | 
 |     OnConnectionState(protocol::ConnectionToHost::State::FAILED, | 
 |                       protocol::INCOMPATIBLE_PROTOCOL); | 
 |     return; | 
 |   } | 
 |  | 
 |   webrtc::ThreadWrapper::EnsureForCurrentMessageLoop(); | 
 |  | 
 |   client_context_ = std::make_unique<ClientContext>(network_task_runner()); | 
 |   client_context_->Start(); | 
 |  | 
 |   perf_tracker_ = std::make_unique<protocol::PerformanceTracker>(); | 
 |  | 
 |   session_context_->video_renderer->Initialize(*client_context_, | 
 |                                                perf_tracker_.get()); | 
 |   logger_->SetHostInfo( | 
 |       session_context_->info.host_version, | 
 |       ChromotingEvent::ParseOsFromString(session_context_->info.host_os), | 
 |       session_context_->info.host_os_version); | 
 |  | 
 |   // TODO(yuweih): Ideally we should make ChromotingClient and all its | 
 |   // sub-components (e.g. ConnectionToHost) take raw pointer instead of WeakPtr. | 
 |   client_ = std::make_unique<ChromotingClient>( | 
 |       client_context_.get(), this, session_context_->video_renderer.get(), | 
 |       session_context_->audio_player_weak_factory->GetWeakPtr()); | 
 |  | 
 |   signaling_ = std::make_unique<FtlSignalStrategy>( | 
 |       runtime_->CreateOAuthTokenGetter(), runtime_->url_loader_factory(), | 
 |       std::make_unique<FtlClientUuidDeviceIdProvider>()); | 
 |   logger_->SetSignalStrategyType(ChromotingEvent::SignalStrategyType::FTL); | 
 |  | 
 |   token_getter_ = runtime_->CreateOAuthTokenGetter(); | 
 |  | 
 |   scoped_refptr<protocol::TransportContext> transport_context = | 
 |       new protocol::TransportContext( | 
 |           std::make_unique<protocol::ChromiumPortAllocatorFactory>(), | 
 |           webrtc::ThreadWrapper::current()->SocketServer(), | 
 |           runtime_->url_loader_factory(), | 
 |           /* oauth_token_getter= */ nullptr, | 
 |           protocol::NetworkSettings( | 
 |               protocol::NetworkSettings::NAT_TRAVERSAL_FULL), | 
 |           protocol::TransportRole::CLIENT); | 
 |  | 
 |   if (session_context_->info.pairing_id.length() && | 
 |       session_context_->info.pairing_secret.length()) { | 
 |     logger_->SetAuthMethod(ChromotingEvent::AuthMethod::PINLESS); | 
 |   } | 
 |  | 
 |   protocol::ClientAuthenticationConfig client_auth_config; | 
 |   client_auth_config.host_id = session_context_->info.host_id; | 
 |   client_auth_config.pairing_client_id = session_context_->info.pairing_id; | 
 |   client_auth_config.pairing_secret = session_context_->info.pairing_secret; | 
 |   client_auth_config.fetch_third_party_token_callback = | 
 |       base::BindRepeating(&Core::FetchThirdPartyToken, GetWeakPtr(), | 
 |                           session_context_->info.host_pubkey); | 
 |   client_auth_config.fetch_secret_callback = | 
 |       base::BindRepeating(&Core::FetchSecret, GetWeakPtr()); | 
 |  | 
 |   client_->Start(signaling_.get(), client_auth_config, transport_context, | 
 |                  session_context_->info.host_ftl_id, | 
 |                  session_context_->info.capabilities); | 
 | } | 
 |  | 
 | void ChromotingSession::Core::LogPerfStats() { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |  | 
 |   logger_->LogStatistics(*perf_tracker_); | 
 | } | 
 |  | 
 | void ChromotingSession::Core::FetchSecret( | 
 |     bool pairing_supported, | 
 |     const protocol::SecretFetchedCallback& secret_fetched_callback) { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |  | 
 |   // TODO(yuweih): Use bindOnce once SecretFetchedCallback becomes OnceCallback. | 
 |   auto secret_fetched_callback_for_ui_thread = base::BindRepeating( | 
 |       [](scoped_refptr<AutoThreadTaskRunner> network_task_runner, | 
 |          base::WeakPtr<ChromotingSession::Core> core, | 
 |          const protocol::SecretFetchedCallback& callback, | 
 |          const std::string& secret) { | 
 |         DCHECK(!network_task_runner->BelongsToCurrentThread()); | 
 |         network_task_runner->PostTask( | 
 |             FROM_HERE, | 
 |             base::BindOnce(&ChromotingSession::Core::HandleOnSecretFetched, | 
 |                            core, callback, secret)); | 
 |       }, | 
 |       network_task_runner(), GetWeakPtr(), secret_fetched_callback); | 
 |   ui_task_runner()->PostTask( | 
 |       FROM_HERE, base::BindOnce(&ChromotingSession::Delegate::FetchSecret, | 
 |                                 session_context_->delegate, pairing_supported, | 
 |                                 secret_fetched_callback_for_ui_thread)); | 
 | } | 
 |  | 
 | void ChromotingSession::Core::HandleOnSecretFetched( | 
 |     const protocol::SecretFetchedCallback& callback, | 
 |     const std::string secret) { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |  | 
 |   logger_->SetAuthMethod(ChromotingEvent::AuthMethod::PIN); | 
 |  | 
 |   callback.Run(secret); | 
 | } | 
 |  | 
 | void ChromotingSession::Core::FetchThirdPartyToken( | 
 |     const std::string& host_public_key, | 
 |     const std::string& token_url, | 
 |     const std::string& scopes, | 
 |     const protocol::ThirdPartyTokenFetchedCallback& token_fetched_callback) { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |  | 
 |   // TODO(yuweih): Use bindOnce once SecretFetchedCallback becomes OnceCallback. | 
 |   auto token_fetched_callback_for_ui_thread = base::BindRepeating( | 
 |       [](scoped_refptr<AutoThreadTaskRunner> network_task_runner, | 
 |          base::WeakPtr<ChromotingSession::Core> core, | 
 |          const protocol::ThirdPartyTokenFetchedCallback& callback, | 
 |          const std::string& token, | 
 |          const protocol::TokenValidator::ValidationResult& validation_result) { | 
 |         DCHECK(!network_task_runner->BelongsToCurrentThread()); | 
 |         network_task_runner->PostTask( | 
 |             FROM_HERE, | 
 |             base::BindOnce( | 
 |                 &ChromotingSession::Core::HandleOnThirdPartyTokenFetched, core, | 
 |                 callback, token, validation_result)); | 
 |       }, | 
 |       network_task_runner(), GetWeakPtr(), token_fetched_callback); | 
 |  | 
 |   ui_task_runner()->PostTask( | 
 |       FROM_HERE, | 
 |       base::BindOnce(&ChromotingSession::Delegate::FetchThirdPartyToken, | 
 |                      session_context_->delegate, token_url, host_public_key, | 
 |                      scopes, token_fetched_callback_for_ui_thread)); | 
 | } | 
 |  | 
 | void ChromotingSession::Core::HandleOnThirdPartyTokenFetched( | 
 |     const protocol::ThirdPartyTokenFetchedCallback& callback, | 
 |     const std::string& token, | 
 |     const protocol::TokenValidator::ValidationResult& validation_result) { | 
 |   DCHECK(network_task_runner()->BelongsToCurrentThread()); | 
 |  | 
 |   logger_->SetAuthMethod(ChromotingEvent::AuthMethod::THIRD_PARTY); | 
 |  | 
 |   callback.Run(token, validation_result); | 
 | } | 
 |  | 
 | // ChromotingSession implementation. | 
 |  | 
 | ChromotingSession::ChromotingSession( | 
 |     base::WeakPtr<ChromotingSession::Delegate> delegate, | 
 |     std::unique_ptr<protocol::CursorShapeStub> cursor_shape_stub, | 
 |     std::unique_ptr<protocol::VideoRenderer> video_renderer, | 
 |     std::unique_ptr<protocol::AudioStub> audio_player, | 
 |     const ConnectToHostInfo& info) | 
 |     : runtime_(ChromotingClientRuntime::GetInstance()) { | 
 |   DCHECK(delegate); | 
 |   DCHECK(cursor_shape_stub); | 
 |   DCHECK(video_renderer); | 
 |   DCHECK(audio_player); | 
 |   DCHECK(runtime_->ui_task_runner()->BelongsToCurrentThread()); | 
 |  | 
 |   // logger is set when connection is started. | 
 |   auto session_context = std::make_unique<SessionContext>(); | 
 |   session_context->delegate = delegate; | 
 |   session_context->audio_player = std::move(audio_player); | 
 |   session_context->audio_player_weak_factory = | 
 |       std::make_unique<base::WeakPtrFactory<protocol::AudioStub>>( | 
 |           session_context->audio_player.get()); | 
 |   session_context->cursor_shape_stub = std::move(cursor_shape_stub); | 
 |   session_context->video_renderer = std::move(video_renderer); | 
 |   session_context->info = info; | 
 |  | 
 |   auto logger = std::make_unique<ClientTelemetryLogger>( | 
 |       runtime_->log_writer(), ChromotingEvent::Mode::ME2ME, | 
 |       info.session_entry_point); | 
 |  | 
 |   core_ = std::make_unique<Core>(runtime_, std::move(logger), | 
 |                                  std::move(session_context)); | 
 | } | 
 |  | 
 | ChromotingSession::~ChromotingSession() { | 
 |   DCHECK(runtime_->ui_task_runner()->BelongsToCurrentThread()); | 
 |  | 
 |   runtime_->network_task_runner()->DeleteSoon(FROM_HERE, core_.release()); | 
 | } | 
 |  | 
 | void ChromotingSession::GetFeedbackData( | 
 |     GetFeedbackDataCallback callback) const { | 
 |   DCHECK(runtime_->ui_task_runner()->BelongsToCurrentThread()); | 
 |  | 
 |   // Bind to base::Unretained(core) instead of the WeakPtr so that we can still | 
 |   // get the feedback data after the session is remotely disconnected. | 
 |   base::PostTaskAndReplyWithResult( | 
 |       runtime_->network_task_runner().get(), FROM_HERE, | 
 |       base::BindOnce(&Core::GetFeedbackData, base::Unretained(core_.get())), | 
 |       std::move(callback)); | 
 | } | 
 |  | 
 | void ChromotingSession::RequestPairing(const std::string& device_name) { | 
 |   RunCoreTaskOnNetworkThread(FROM_HERE, &Core::RequestPairing, device_name); | 
 | } | 
 |  | 
 | void ChromotingSession::SendMouseEvent(int x, | 
 |                                        int y, | 
 |                                        protocol::MouseEvent_MouseButton button, | 
 |                                        bool button_down) { | 
 |   RunCoreTaskOnNetworkThread(FROM_HERE, &Core::SendMouseEvent, x, y, button, | 
 |                              button_down); | 
 | } | 
 |  | 
 | void ChromotingSession::SendMouseWheelEvent(int delta_x, int delta_y) { | 
 |   RunCoreTaskOnNetworkThread(FROM_HERE, &Core::SendMouseWheelEvent, delta_x, | 
 |                              delta_y); | 
 | } | 
 |  | 
 | bool ChromotingSession::SendKeyEvent(int scan_code, | 
 |                                      int key_code, | 
 |                                      bool key_down) { | 
 |   DCHECK(runtime_->ui_task_runner()->BelongsToCurrentThread()); | 
 |  | 
 |   // For software keyboards |scan_code| is set to 0, in which case the | 
 |   // |key_code| is used instead. | 
 |   uint32_t usb_key_code = | 
 |       scan_code ? ui::KeycodeConverter::NativeKeycodeToUsbKeycode(scan_code) | 
 |                 : NativeDeviceKeycodeToUsbKeycode(key_code); | 
 |   if (!usb_key_code) { | 
 |     LOG(WARNING) << "Ignoring unknown key code: " << key_code | 
 |                  << " scan code: " << scan_code; | 
 |     return false; | 
 |   } | 
 |   RunCoreTaskOnNetworkThread(FROM_HERE, &Core::SendKeyEvent, usb_key_code, | 
 |                              key_down); | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | void ChromotingSession::SendTextEvent(const std::string& text) { | 
 |   RunCoreTaskOnNetworkThread(FROM_HERE, &Core::SendTextEvent, text); | 
 | } | 
 |  | 
 | void ChromotingSession::SendTouchEvent( | 
 |     const protocol::TouchEvent& touch_event) { | 
 |   RunCoreTaskOnNetworkThread(FROM_HERE, &Core::SendTouchEvent, touch_event); | 
 | } | 
 |  | 
 | void ChromotingSession::SendClientResolution(int dips_width, | 
 |                                              int dips_height, | 
 |                                              float scale) { | 
 |   RunCoreTaskOnNetworkThread(FROM_HERE, &Core::SendClientResolution, dips_width, | 
 |                              dips_height, scale); | 
 | } | 
 |  | 
 | void ChromotingSession::EnableVideoChannel(bool enable) { | 
 |   RunCoreTaskOnNetworkThread(FROM_HERE, &Core::EnableVideoChannel, enable); | 
 | } | 
 |  | 
 | void ChromotingSession::SendClientMessage(const std::string& type, | 
 |                                           const std::string& data) { | 
 |   RunCoreTaskOnNetworkThread(FROM_HERE, &Core::SendClientMessage, type, data); | 
 | } | 
 |  | 
 | template <typename Functor, typename... Args> | 
 | void ChromotingSession::RunCoreTaskOnNetworkThread( | 
 |     const base::Location& from_here, | 
 |     Functor&& core_functor, | 
 |     Args&&... args) { | 
 |   DCHECK(runtime_->ui_task_runner()->BelongsToCurrentThread()); | 
 |  | 
 |   runtime_->network_task_runner()->PostTask( | 
 |       FROM_HERE, | 
 |       base::BindOnce(std::forward<Functor>(core_functor), core_->GetWeakPtr(), | 
 |                      std::forward<Args>(args)...)); | 
 | } | 
 |  | 
 | }  // namespace remoting |