blob: 8194238467f13862a79e97b69e7d3c47c65680e1 [file] [log] [blame]
// 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 "base/bind.h"
#include "base/callback_helpers.h"
#include "base/format_macros.h"
#include "base/logging.h"
#include "jingle/glue/thread_wrapper.h"
#include "net/socket/client_socket_factory.h"
#include "remoting/base/chromium_url_request.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/transport_context.h"
#include "remoting/protocol/video_renderer.h"
#include "remoting/signaling/server_log_entry.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
namespace remoting {
namespace {
const char* const kXmppServer = "talk.google.com";
const int kXmppPort = 5222;
const bool kXmppUseTls = true;
// Interval at which to log performance statistics, if enabled.
const int kPerfStatsIntervalMs = 60000;
// Default DPI to assume for old clients that use notifyClientResolution.
const int kDefaultDPI = 96;
// Used by NormalizeclientResolution. See comment below.
const int kMinDimension = 640;
// 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);
}
} // namespace
ChromotingSession::ChromotingSession(
base::WeakPtr<ChromotingSession::Delegate> delegate,
std::unique_ptr<protocol::CursorShapeStub> cursor_shape_stub,
std::unique_ptr<protocol::VideoRenderer> video_renderer,
base::WeakPtr<protocol::AudioStub> audio_player,
const ConnectToHostInfo& info,
const protocol::ClientAuthenticationConfig& client_auth_config)
: delegate_(delegate),
connection_info_(info),
client_auth_config_(client_auth_config),
cursor_shape_stub_(std::move(cursor_shape_stub)),
video_renderer_(std::move(video_renderer)),
audio_player_(audio_player),
capabilities_(info.capabilities),
weak_factory_(this) {
runtime_ = ChromotingClientRuntime::GetInstance();
DCHECK(runtime_->ui_task_runner()->BelongsToCurrentThread());
weak_ptr_ = weak_factory_.GetWeakPtr();
// Initialize XMPP config.
xmpp_config_.host = kXmppServer;
xmpp_config_.port = kXmppPort;
xmpp_config_.use_tls = kXmppUseTls;
xmpp_config_.username = info.username;
xmpp_config_.auth_token = info.auth_token;
client_auth_config_.fetch_third_party_token_callback = base::Bind(
&ChromotingSession::FetchThirdPartyToken, GetWeakPtr(), info.host_pubkey);
}
ChromotingSession::~ChromotingSession() {
DCHECK(runtime_->network_task_runner()->BelongsToCurrentThread());
ReleaseResources();
}
void ChromotingSession::Connect() {
if (runtime_->network_task_runner()->BelongsToCurrentThread()) {
ConnectToHostOnNetworkThread();
} else {
runtime_->network_task_runner()->PostTask(
FROM_HERE, base::Bind(&ChromotingSession::ConnectToHostOnNetworkThread,
GetWeakPtr()));
}
}
void ChromotingSession::Disconnect() {
DisconnectForReason(protocol::ErrorCode::OK);
}
void ChromotingSession::DisconnectForReason(protocol::ErrorCode error) {
if (!runtime_->network_task_runner()->BelongsToCurrentThread()) {
runtime_->network_task_runner()->PostTask(
FROM_HERE, base::BindOnce(&ChromotingSession::DisconnectForReason,
GetWeakPtr(), error));
return;
}
EnableStatsLogging(false);
// Do not log session state change if the connection is never started or 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 (error != protocol::ErrorCode::OK) {
session_state_to_log = ChromotingEvent::SessionState::CONNECTION_FAILED;
} else 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, ClientTelemetryLogger::TranslateError(error));
session_state_ = (error == protocol::ErrorCode::OK)
? protocol::ConnectionToHost::CLOSED
: protocol::ConnectionToHost::FAILED;
}
ReleaseResources();
}
void ChromotingSession::FetchThirdPartyToken(
const std::string& host_public_key,
const std::string& token_url,
const std::string& scope,
const protocol::ThirdPartyTokenFetchedCallback& token_fetched_callback) {
DCHECK(runtime_->network_task_runner()->BelongsToCurrentThread());
DCHECK(third_party_token_fetched_callback_.is_null());
third_party_token_fetched_callback_ = token_fetched_callback;
runtime_->ui_task_runner()->PostTask(
FROM_HERE, base::Bind(&ChromotingSession::Delegate::FetchThirdPartyToken,
delegate_, token_url, host_public_key, scope));
}
void ChromotingSession::HandleOnThirdPartyTokenFetched(
const std::string& token,
const std::string& shared_secret) {
DCHECK(runtime_->network_task_runner()->BelongsToCurrentThread());
if (!third_party_token_fetched_callback_.is_null()) {
base::ResetAndReturn(&third_party_token_fetched_callback_)
.Run(token, shared_secret);
} else {
LOG(WARNING) << "ThirdPartyAuth: Ignored OnThirdPartyTokenFetched()"
" without a pending fetch.";
}
}
void ChromotingSession::ProvideSecret(const std::string& pin,
bool create_pairing,
const std::string& device_name) {
DCHECK(runtime_->ui_task_runner()->BelongsToCurrentThread());
// TODO(nicholss): |pin| here is not used. Maybe there was an api refactor and
// this was not cleaned up. The auth pin providing mechanism seems to be call
// ProvideSecret, and then call the auth callback. When session moves to
// Connected state, this chromoing session calls RequestPairing based on
// create_pairing.
create_pairing_ = create_pairing;
if (create_pairing)
SetDeviceName(device_name);
}
void ChromotingSession::SendMouseEvent(int x,
int y,
protocol::MouseEvent_MouseButton button,
bool button_down) {
if (!runtime_->network_task_runner()->BelongsToCurrentThread()) {
runtime_->network_task_runner()->PostTask(
FROM_HERE, base::Bind(&ChromotingSession::SendMouseEvent, GetWeakPtr(),
x, y, button, button_down));
return;
}
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::SendMouseWheelEvent(int delta_x, int delta_y) {
if (!runtime_->network_task_runner()->BelongsToCurrentThread()) {
runtime_->network_task_runner()->PostTask(
FROM_HERE, base::Bind(&ChromotingSession::SendMouseWheelEvent,
GetWeakPtr(), delta_x, delta_y));
return;
}
protocol::MouseEvent event;
event.set_wheel_delta_x(delta_x);
event.set_wheel_delta_y(delta_y);
client_->input_stub()->InjectMouseEvent(event);
}
bool ChromotingSession::SendKeyEvent(int scan_code,
int key_code,
bool key_down) {
// 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;
}
SendKeyEventInternal(usb_key_code, key_down);
return true;
}
void ChromotingSession::SendTextEvent(const std::string& text) {
if (!runtime_->network_task_runner()->BelongsToCurrentThread()) {
runtime_->network_task_runner()->PostTask(
FROM_HERE,
base::Bind(&ChromotingSession::SendTextEvent, GetWeakPtr(), text));
return;
}
protocol::TextEvent event;
event.set_text(text);
client_->input_stub()->InjectTextEvent(event);
}
void ChromotingSession::SendTouchEvent(
const protocol::TouchEvent& touch_event) {
if (!runtime_->network_task_runner()->BelongsToCurrentThread()) {
runtime_->network_task_runner()->PostTask(
FROM_HERE, base::Bind(&ChromotingSession::SendTouchEvent, GetWeakPtr(),
touch_event));
return;
}
client_->input_stub()->InjectTouchEvent(touch_event);
}
void ChromotingSession::SendClientResolution(int dips_width,
int dips_height,
int scale) {
if (!runtime_->network_task_runner()->BelongsToCurrentThread()) {
runtime_->network_task_runner()->PostTask(
FROM_HERE, base::Bind(&ChromotingSession::SendClientResolution,
GetWeakPtr(), dips_width, dips_height, scale));
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::EnableVideoChannel(bool enable) {
if (!runtime_->network_task_runner()->BelongsToCurrentThread()) {
runtime_->network_task_runner()->PostTask(
FROM_HERE, base::Bind(&ChromotingSession::EnableVideoChannel,
GetWeakPtr(), enable));
return;
}
protocol::VideoControl video_control;
video_control.set_enable(enable);
client_->host_stub()->ControlVideo(video_control);
}
void ChromotingSession::SendClientMessage(const std::string& type,
const std::string& data) {
if (!runtime_->network_task_runner()->BelongsToCurrentThread()) {
runtime_->network_task_runner()->PostTask(
FROM_HERE, base::Bind(&ChromotingSession::SendClientMessage,
GetWeakPtr(), type, data));
return;
}
protocol::ExtensionMessage extension_message;
extension_message.set_type(type);
extension_message.set_data(data);
client_->host_stub()->DeliverClientMessage(extension_message);
}
void ChromotingSession::OnConnectionState(
protocol::ConnectionToHost::State state,
protocol::ErrorCode error) {
DCHECK(runtime_->network_task_runner()->BelongsToCurrentThread());
// This code assumes no intermediate connection state between CONNECTED and
// CLOSED/FAILED.
session_state_ = state;
EnableStatsLogging(session_state_ == protocol::ConnectionToHost::CONNECTED);
logger_->LogSessionStateChange(ClientTelemetryLogger::TranslateState(state),
ClientTelemetryLogger::TranslateError(error));
if (create_pairing_ && state == protocol::ConnectionToHost::CONNECTED) {
protocol::PairingRequest request;
DCHECK(!device_name_.empty());
request.set_client_name(device_name_);
client_->host_stub()->RequestPairing(request);
}
runtime_->ui_task_runner()->PostTask(
FROM_HERE, base::Bind(&ChromotingSession::Delegate::OnConnectionState,
delegate_, state, error));
if (state == protocol::ConnectionToHost::CLOSED ||
state == protocol::ConnectionToHost::FAILED) {
ReleaseResources();
}
}
void ChromotingSession::OnConnectionReady(bool ready) {
// We ignore this message, since OnConnectionState tells us the same thing.
}
void ChromotingSession::OnRouteChanged(const std::string& channel_name,
const protocol::TransportRoute& route) {
std::string message = "Channel " + channel_name + " using " +
protocol::TransportRoute::GetTypeString(route.type) +
" connection.";
VLOG(1) << "Route: " << message;
}
void ChromotingSession::SetCapabilities(const std::string& capabilities) {
runtime_->ui_task_runner()->PostTask(
FROM_HERE, base::Bind(&ChromotingSession::Delegate::SetCapabilities,
delegate_, capabilities));
}
void ChromotingSession::SetPairingResponse(
const protocol::PairingResponse& response) {
runtime_->ui_task_runner()->PostTask(
FROM_HERE,
base::Bind(&ChromotingSession::Delegate::CommitPairingCredentials,
delegate_, client_auth_config_.host_id, response.client_id(),
response.shared_secret()));
}
void ChromotingSession::DeliverHostMessage(
const protocol::ExtensionMessage& message) {
runtime_->ui_task_runner()->PostTask(
FROM_HERE,
base::Bind(&ChromotingSession::Delegate::HandleExtensionMessage,
delegate_, message.type(), message.data()));
}
void ChromotingSession::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::GetClipboardStub() {
return this;
}
protocol::CursorShapeStub* ChromotingSession::GetCursorShapeStub() {
return cursor_shape_stub_.get();
}
void ChromotingSession::InjectClipboardEvent(
const protocol::ClipboardEvent& event) {
NOTIMPLEMENTED();
}
base::WeakPtr<ChromotingSession> ChromotingSession::GetWeakPtr() {
return weak_ptr_;
}
void ChromotingSession::ConnectToHostOnNetworkThread() {
DCHECK(runtime_->network_task_runner()->BelongsToCurrentThread());
jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
client_context_.reset(new ClientContext(runtime_->network_task_runner()));
client_context_->Start();
perf_tracker_.reset(new protocol::PerformanceTracker());
video_renderer_->Initialize(*client_context_, perf_tracker_.get());
logger_.reset(new ClientTelemetryLogger(runtime_->log_writer(),
ChromotingEvent::Mode::ME2ME));
logger_->SetHostInfo(
connection_info_.host_version,
ChromotingEvent::ParseOsFromString(connection_info_.host_os),
connection_info_.host_os_version);
client_.reset(new ChromotingClient(client_context_.get(), this,
video_renderer_.get(), audio_player_));
signaling_.reset(
new XmppSignalStrategy(net::ClientSocketFactory::GetDefaultFactory(),
runtime_->url_requester(), xmpp_config_));
scoped_refptr<protocol::TransportContext> transport_context =
new protocol::TransportContext(
signaling_.get(),
std::make_unique<protocol::ChromiumPortAllocatorFactory>(),
std::make_unique<ChromiumUrlRequestFactory>(
runtime_->url_requester()),
protocol::NetworkSettings(
protocol::NetworkSettings::NAT_TRAVERSAL_FULL),
protocol::TransportRole::CLIENT);
transport_context->set_ice_config_url(
ServiceUrls::GetInstance()->ice_config_url(), runtime_->token_getter());
#if defined(ENABLE_WEBRTC_REMOTING_CLIENT)
if (connection_info_.flags.find("useWebrtc") != std::string::npos) {
VLOG(0) << "Attempting to connect using WebRTC.";
std::unique_ptr<protocol::CandidateSessionConfig> protocol_config =
protocol::CandidateSessionConfig::CreateEmpty();
protocol_config->set_webrtc_supported(true);
protocol_config->set_ice_supported(false);
client_->set_protocol_config(std::move(protocol_config));
}
#endif // defined(ENABLE_WEBRTC_REMOTING_CLIENT)
client_->Start(signaling_.get(), client_auth_config_, transport_context,
connection_info_.host_jid, capabilities_);
}
void ChromotingSession::SetDeviceName(const std::string& device_name) {
if (!runtime_->network_task_runner()->BelongsToCurrentThread()) {
runtime_->network_task_runner()->PostTask(
FROM_HERE, base::Bind(&ChromotingSession::SetDeviceName, GetWeakPtr(),
device_name));
return;
}
device_name_ = device_name;
}
void ChromotingSession::SendKeyEventInternal(int usb_key_code, bool key_down) {
if (!runtime_->network_task_runner()->BelongsToCurrentThread()) {
runtime_->network_task_runner()->PostTask(
FROM_HERE, base::Bind(&ChromotingSession::SendKeyEventInternal,
GetWeakPtr(), usb_key_code, key_down));
return;
}
protocol::KeyEvent event;
event.set_usb_keycode(usb_key_code);
event.set_pressed(key_down);
client_->input_stub()->InjectKeyEvent(event);
}
void ChromotingSession::EnableStatsLogging(bool enabled) {
DCHECK(runtime_->network_task_runner()->BelongsToCurrentThread());
if (enabled && !stats_logging_enabled_) {
runtime_->network_task_runner()->PostDelayedTask(
FROM_HERE, base::Bind(&ChromotingSession::LogPerfStats, GetWeakPtr()),
base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
}
stats_logging_enabled_ = enabled;
}
void ChromotingSession::LogPerfStats() {
DCHECK(runtime_->network_task_runner()->BelongsToCurrentThread());
if (!stats_logging_enabled_)
return;
logger_->LogStatistics(perf_tracker_.get());
runtime_->network_task_runner()->PostDelayedTask(
FROM_HERE, base::Bind(&ChromotingSession::LogPerfStats, GetWeakPtr()),
base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
}
void ChromotingSession::ReleaseResources() {
logger_.reset();
// |client_| must be torn down before |signaling_|.
client_.reset();
delegate_.reset();
audio_player_.reset();
video_renderer_.reset();
signaling_.reset();
perf_tracker_.reset();
client_context_.reset();
cursor_shape_stub_.reset();
// Weak factory can only be invalidated once (due to DCHECK in it). After that
// the instance will no longer be usable. This is a design flaw that makes the
// instance no longer reusable after the caller calls Disconnect. Ideally we
// should factor out a Core that lives between (Connect, Disconnect).
if (weak_ptr_) {
weak_factory_.InvalidateWeakPtrs();
}
}
} // namespace remoting