// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/host/win/rdp_client.h"
#include <windows.h>
#include <cstdint>
#include <memory>
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/task/current_thread.h"
#include "base/task/single_thread_task_runner.h"
#include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h"
#include "remoting/base/typed_buffer.h"
#include "remoting/host/base/screen_resolution.h"
#include "remoting/host/win/rdp_client_window.h"
namespace remoting {
namespace {
// 127.0.0.1 is explicitly blocked by the RDP ActiveX control, so we use
// 127.0.0.2 instead.
const unsigned char kRdpLoopbackAddress[] = {127, 0, 0, 2};
} // namespace
// The core of RdpClient is ref-counted since it services calls and notifies
// events on the caller task runner, but runs the ActiveX control on the UI
// task runner.
class RdpClient::Core : public base::RefCountedThreadSafe<Core>,
public RdpClientWindow::EventHandler {
public:
Core(scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
RdpClient::EventHandler* event_handler);
Core(const Core&) = delete;
Core& operator=(const Core&) = delete;
// Initiates a loopback RDP connection.
void Connect(const ScreenResolution& resolution,
const std::string& terminal_id,
DWORD port_number);
// Initiates a graceful shutdown of the RDP connection.
void Disconnect();
// Sends Secure Attention Sequence to the session.
void InjectSas();
// Change the resolution of the desktop.
void ChangeResolution(const ScreenResolution& resolution);
// RdpClientWindow::EventHandler interface.
void OnConnected() override;
void OnDisconnected() override;
private:
friend class base::RefCountedThreadSafe<Core>;
~Core() override;
// Helpers for the event handler's methods that make sure that OnRdpClosed()
// is the last notification delivered and is delevered only once.
void NotifyConnected();
void NotifyClosed();
// Task runner on which the caller expects |event_handler_| to be notified.
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_;
// Task runner on which |rdp_client_window_| is running.
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;
// Event handler receiving notification about connection state. The pointer is
// cleared when Disconnect() methods is called, stopping any further updates.
raw_ptr<RdpClient::EventHandler> event_handler_;
// Hosts the RDP ActiveX control.
std::unique_ptr<RdpClientWindow> rdp_client_window_;
// A self-reference to keep the object alive during connection shutdown.
scoped_refptr<Core> self_;
};
RdpClient::RdpClient(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
const ScreenResolution& resolution,
const std::string& terminal_id,
DWORD port_number,
EventHandler* event_handler) {
DCHECK(caller_task_runner->BelongsToCurrentThread());
core_ = new Core(caller_task_runner, ui_task_runner, event_handler);
core_->Connect(resolution, terminal_id, port_number);
}
RdpClient::~RdpClient() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
core_->Disconnect();
}
void RdpClient::InjectSas() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
core_->InjectSas();
}
void RdpClient::ChangeResolution(const ScreenResolution& resolution) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
core_->ChangeResolution(resolution);
}
RdpClient::Core::Core(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
RdpClient::EventHandler* event_handler)
: caller_task_runner_(caller_task_runner),
ui_task_runner_(ui_task_runner),
event_handler_(event_handler) {}
void RdpClient::Core::Connect(const ScreenResolution& resolution,
const std::string& terminal_id,
DWORD port_number) {
if (!ui_task_runner_->BelongsToCurrentThread()) {
ui_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Core::Connect, this, resolution, terminal_id,
port_number));
return;
}
DCHECK(base::CurrentUIThread::IsSet());
DCHECK(!rdp_client_window_);
DCHECK(!self_.get());
net::IPEndPoint server_endpoint(net::IPAddress(kRdpLoopbackAddress),
base::checked_cast<uint16_t>(port_number));
// Create the ActiveX control window.
rdp_client_window_ =
std::make_unique<RdpClientWindow>(server_endpoint, terminal_id, this);
if (!rdp_client_window_->Connect(resolution)) {
rdp_client_window_.reset();
// Notify the caller that connection attempt failed.
NotifyClosed();
}
}
void RdpClient::Core::Disconnect() {
if (!ui_task_runner_->BelongsToCurrentThread()) {
ui_task_runner_->PostTask(FROM_HERE,
base::BindOnce(&Core::Disconnect, this));
return;
}
// The caller does not expect any notifications to be delivered after this
// point.
event_handler_ = nullptr;
// Gracefully shutdown the RDP connection.
if (rdp_client_window_) {
self_ = this;
rdp_client_window_->Disconnect();
}
}
void RdpClient::Core::InjectSas() {
if (!ui_task_runner_->BelongsToCurrentThread()) {
ui_task_runner_->PostTask(FROM_HERE,
base::BindOnce(&Core::InjectSas, this));
return;
}
if (rdp_client_window_) {
rdp_client_window_->InjectSas();
}
}
void RdpClient::Core::ChangeResolution(const ScreenResolution& resolution) {
if (!ui_task_runner_->BelongsToCurrentThread()) {
ui_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Core::ChangeResolution, this, resolution));
return;
}
if (rdp_client_window_) {
rdp_client_window_->ChangeResolution(resolution);
}
}
void RdpClient::Core::OnConnected() {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
DCHECK(rdp_client_window_);
NotifyConnected();
}
void RdpClient::Core::OnDisconnected() {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
DCHECK(rdp_client_window_);
NotifyClosed();
// Delay window destruction until no ActiveX control's code is on the stack.
ui_task_runner_->DeleteSoon(FROM_HERE, rdp_client_window_.release());
self_ = nullptr;
}
RdpClient::Core::~Core() {
DCHECK(!event_handler_);
DCHECK(!rdp_client_window_);
}
void RdpClient::Core::NotifyConnected() {
if (!caller_task_runner_->BelongsToCurrentThread()) {
caller_task_runner_->PostTask(FROM_HERE,
base::BindOnce(&Core::NotifyConnected, this));
return;
}
if (event_handler_) {
event_handler_->OnRdpConnected();
}
}
void RdpClient::Core::NotifyClosed() {
if (!caller_task_runner_->BelongsToCurrentThread()) {
caller_task_runner_->PostTask(FROM_HERE,
base::BindOnce(&Core::NotifyClosed, this));
return;
}
if (event_handler_) {
RdpClient::EventHandler* event_handler = event_handler_;
event_handler_ = nullptr;
event_handler->OnRdpClosed();
}
}
} // namespace remoting