910e62b5创建于 1月15日历史提交
// Copyright 2012 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/client_session.h"

#include <array>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/strings/string_split.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "remoting/base/auto_thread_task_runner.h"
#include "remoting/base/constants.h"
#include "remoting/base/errors.h"
#include "remoting/base/local_session_policies_provider.h"
#include "remoting/base/session_policies.h"
#include "remoting/host/base/desktop_environment_options.h"
#include "remoting/host/desktop_display_info.h"
#include "remoting/host/fake_desktop_environment.h"
#include "remoting/host/fake_host_extension.h"
#include "remoting/host/host_extension.h"
#include "remoting/host/host_extension_session.h"
#include "remoting/host/host_mock_objects.h"
#include "remoting/proto/control.pb.h"
#include "remoting/proto/event.pb.h"
#include "remoting/protocol/capability_names.h"
#include "remoting/protocol/fake_connection_to_client.h"
#include "remoting/protocol/fake_desktop_capturer.h"
#include "remoting/protocol/fake_message_pipe.h"
#include "remoting/protocol/fake_session.h"
#include "remoting/protocol/message_pipe.h"
#include "remoting/protocol/protocol_mock_objects.h"
#include "remoting/protocol/session_config.h"
#include "remoting/protocol/test_event_matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/libjingle_xmpp/xmllite/qname.h"
#include "third_party/libjingle_xmpp/xmllite/xmlelement.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
#include "ui/events/types/event_type.h"

namespace remoting {

using protocol::FakeSession;
using protocol::MockClientStub;
using protocol::MockHostStub;
using protocol::MockInputStub;
using protocol::MockVideoStub;
using protocol::SessionConfig;
using protocol::test::EqualsClipboardEvent;
using protocol::test::EqualsKeyEvent;
using protocol::test::EqualsMouseButtonEvent;
using protocol::test::EqualsMouseMoveEvent;

using testing::_;
using testing::AtLeast;
using testing::Eq;
using testing::Not;
using testing::Return;
using testing::ReturnRef;
using testing::StrictMock;

namespace {

constexpr char kTestDataChannelCallbackName[] = "test_channel_name";

// Use large fake screen-ids on 64-bit systems, to detect errors caused by
// inadvertent casts to 32-bits.
constexpr bool kUse64BitDisplayId = (sizeof(webrtc::ScreenId) >= 8);

// Matches a |protocol::Capabilities| argument against a list of capabilities
// formatted as a space-separated string.
MATCHER_P(IncludesCapabilities, expected_capabilities, "") {
  if (!arg.has_capabilities()) {
    return false;
  }

  std::vector<std::string> words_args =
      base::SplitString(arg.capabilities(), " ", base::KEEP_WHITESPACE,
                        base::SPLIT_WANT_NONEMPTY);
  std::vector<std::string> words_expected =
      base::SplitString(expected_capabilities, " ", base::KEEP_WHITESPACE,
                        base::SPLIT_WANT_NONEMPTY);

  for (const auto& word : words_expected) {
    if (!base::Contains(words_args, word)) {
      return false;
    }
  }
  return true;
}

MATCHER_P(ScreenIdMatches, expected_id, "") {
  return arg.screen_id() == expected_id;
}

protocol::MouseEvent MakeMouseMoveEvent(int x, int y) {
  protocol::MouseEvent result;
  result.set_x(x);
  result.set_y(y);
  return result;
}

protocol::KeyEvent MakeKeyEvent(bool pressed, std::uint32_t keycode) {
  protocol::KeyEvent result;
  result.set_pressed(pressed);
  result.set_usb_keycode(keycode);
  return result;
}

protocol::ClipboardEvent MakeClipboardEvent(const std::string& text) {
  protocol::ClipboardEvent result;
  result.set_mime_type(kMimeTypeTextUtf8);
  result.set_data(text);
  return result;
}

}  // namespace

class ClientSessionTest : public testing::Test {
 public:
  ClientSessionTest() = default;

  void SetUp() override;
  void TearDown() override;

 protected:
  // Fake multi-monitor setup.
  static const int kDisplay1Width =
      protocol::FakeDesktopCapturer::kWidth;  // 800
  static const int kDisplay1Height =
      protocol::FakeDesktopCapturer::kHeight;  // 600
  static const std::int64_t kDisplay1Id =
      kUse64BitDisplayId ? 1111111111111111 : 11111111;
  static const int kDisplay2Width = 1024;
  static const int kDisplay2Height = 768;
  static const int kDisplay2YOffset = 35;
  static const std::int64_t kDisplay2Id =
      kUse64BitDisplayId ? 2222222222222222 : 22222222;

  // Creates the client session from a FakeSession instance.
  void CreateClientSession(std::unique_ptr<protocol::FakeSession> session);

  // Creates the client session.
  void CreateClientSession();

  // Notifies the client session that the client connection has been
  // authenticated and channels have been connected. This effectively enables
  // the input pipe line and starts video capturing.
  void ConnectClientSession(const SessionPolicies* session_policies = nullptr);

  // Add a fake display to the layout list. Used in conjunction with
  // NotifyDesktopDisplaySize.
  void AddDisplayToLayout(protocol::VideoLayout* displays,
                          int x,
                          int y,
                          int width,
                          int height,
                          int dpi_x,
                          int dpi_y,
                          std::int64_t display_id);

  // Fakes desktop display size notification from Webrtc.
  void NotifyDesktopDisplaySize(
      std::unique_ptr<protocol::VideoLayout> displays);

  // Fakes display select request from user.
  void NotifySelectDesktopDisplay(std::string id);

  // Convenience methods to setup a single- or double-monitor setup.
  void ResetDisplayInfo();
  void SetupSingleDisplay();
  void SetupMultiDisplay();
  void SetupMultiDisplay_SameSize();

  // When using a multi-mon setup, this fakes the user selecting which display
  // to show.
  void MultiMon_SelectFirstDisplay();
  void MultiMon_SelectSecondDisplay();
  void MultiMon_SelectAllDisplays();
  void MultiMon_SelectDisplay(std::string display_id);

  // Return the identifier of the display that's currently selected.
  webrtc::ScreenId GetSelectedSourceDisplayId();

  // Geometry info for displays being tested.
  DesktopDisplayInfo displays_;
  int curr_display_;

  // Message loop that will process all ClientSession tasks.
  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};

  // AutoThreadTaskRunner on which |client_session_| will be run.
  scoped_refptr<AutoThreadTaskRunner> task_runner_;

  // Used to run |message_loop_| after each test, until no objects remain that
  // require it.
  base::RunLoop run_loop_;

  // HostExtensions to pass when creating the ClientSession. Caller retains
  // ownership of the HostExtensions themselves.
  std::vector<raw_ptr<HostExtension, VectorExperimental>> extensions_;

  // Vectors of events to bind to `client_sessions_`, must outlive it.
  std::vector<protocol::KeyEvent> key_events_;
  std::vector<protocol::MouseEvent> mouse_events_;
  std::vector<protocol::ClipboardEvent> clipboard_events_;

  SessionPolicies initial_local_policies_;

  LocalSessionPoliciesProvider local_session_policies_provider_;

  // ClientSession instance under test.
  std::unique_ptr<ClientSession> client_session_;

  // ClientSession::EventHandler mock for use in tests.
  MockClientSessionEventHandler session_event_handler_;

  // Stubs returned to |client_session_| components by |connection_|.
  MockClientStub client_stub_;

  // ClientSession owns |connection_| but tests need it to inject fake events.
  raw_ptr<protocol::FakeConnectionToClient, DanglingUntriaged> connection_;

  std::unique_ptr<FakeDesktopEnvironmentFactory> desktop_environment_factory_;

  DesktopEnvironmentOptions desktop_environment_options_;
};

void ClientSessionTest::SetUp() {
  // Arrange to run |task_environment_| until no components depend on it.
  task_runner_ = new AutoThreadTaskRunner(
      task_environment_.GetMainThreadTaskRunner(), run_loop_.QuitClosure());

  desktop_environment_factory_ =
      std::make_unique<FakeDesktopEnvironmentFactory>(
          task_environment_.GetMainThreadTaskRunner());
  desktop_environment_options_ = DesktopEnvironmentOptions::CreateDefault();

  initial_local_policies_.maximum_session_duration = base::Hours(10);
  local_session_policies_provider_.set_local_policies(initial_local_policies_);

  // Suppress spammy "uninteresting call" logs.
  EXPECT_CALL(client_stub_, SetCursorShape(_)).Times(testing::AnyNumber());
}

void ClientSessionTest::TearDown() {
  if (client_session_) {
    if (connection_->is_connected()) {
      client_session_->DisconnectSession(ErrorCode::OK, {}, FROM_HERE);
    }
    client_session_.reset();
    desktop_environment_factory_.reset();
  }

  // Clear out |task_runner_| reference so the loop can quit, and run it until
  // it does.
  task_runner_ = nullptr;
  run_loop_.Run();
}

void ClientSessionTest::CreateClientSession(
    std::unique_ptr<protocol::FakeSession> session) {
  DCHECK(session);

  // Mock protocol::ConnectionToClient APIs called directly by ClientSession.
  // HostStub is not touched by ClientSession, so we can safely pass nullptr.
  std::unique_ptr<protocol::FakeConnectionToClient> connection(
      new protocol::FakeConnectionToClient(std::move(session)));
  connection->set_client_stub(&client_stub_);
  connection_ = connection.get();

  client_session_ = std::make_unique<ClientSession>(
      &session_event_handler_, std::move(connection),
      desktop_environment_factory_.get(), desktop_environment_options_, nullptr,
      extensions_, &local_session_policies_provider_);
}

void ClientSessionTest::CreateClientSession() {
  CreateClientSession(std::make_unique<protocol::FakeSession>());
}

void ClientSessionTest::ConnectClientSession(
    const SessionPolicies* session_policies) {
  EXPECT_CALL(session_event_handler_, OnSessionPoliciesReceived(_))
      .WillOnce(Return(std::nullopt));
  EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_));

  base::test::TestFuture<void> future;
  EXPECT_CALL(session_event_handler_, OnSessionChannelsConnected(_))
      .WillOnce([&future] { future.SetValue(); });

  // Stubs should be set only after connection is authenticated.
  EXPECT_FALSE(connection_->clipboard_stub());
  EXPECT_FALSE(connection_->input_stub());

  client_session_->OnConnectionAuthenticated(session_policies);
  client_session_->CreateMediaStreams();
  client_session_->OnConnectionChannelsConnected();
  future.Get();

  EXPECT_TRUE(connection_->clipboard_stub());
  EXPECT_TRUE(connection_->input_stub());
}

void ClientSessionTest::AddDisplayToLayout(protocol::VideoLayout* displays,
                                           int x,
                                           int y,
                                           int width,
                                           int height,
                                           int dpi_x,
                                           int dpi_y,
                                           std::int64_t display_id) {
  protocol::VideoTrackLayout* video_track = displays->add_video_track();
  video_track->set_position_x(x);
  video_track->set_position_y(y);
  video_track->set_width(width);
  video_track->set_height(height);
  video_track->set_x_dpi(dpi_x);
  video_track->set_y_dpi(dpi_y);
  video_track->set_screen_id(display_id);
  displays_.AddDisplayFrom(*video_track);
}

void ClientSessionTest::NotifyDesktopDisplaySize(
    std::unique_ptr<protocol::VideoLayout> displays) {
  client_session_->OnDesktopDisplayChanged(std::move(displays));
}

void ClientSessionTest::NotifySelectDesktopDisplay(std::string id) {
  protocol::SelectDesktopDisplayRequest req;
  req.set_id(id);
  client_session_->SelectDesktopDisplay(req);
}

void ClientSessionTest::ResetDisplayInfo() {
  displays_.Reset();
  curr_display_ = webrtc::kInvalidScreenId;
}

// Set up a single display (default size).
void ClientSessionTest::SetupSingleDisplay() {
  ResetDisplayInfo();
  auto displays = std::make_unique<protocol::VideoLayout>();
  AddDisplayToLayout(displays.get(), 0, 0, kDisplay1Width, kDisplay1Height,
                     kDefaultDpi, kDefaultDpi, kDisplay1Id);
  NotifyDesktopDisplaySize(std::move(displays));
}

// Set up multiple displays:
// +-----------+
// |  800x600  |---------------+
// |     0     |   1024x768    |
// +-----------+       1       |
//             |               |
//             +---------------+
void ClientSessionTest::SetupMultiDisplay() {
  ResetDisplayInfo();
  auto displays = std::make_unique<protocol::VideoLayout>();
  AddDisplayToLayout(displays.get(), 0, 0, kDisplay1Width, kDisplay1Height,
                     kDefaultDpi, kDefaultDpi, kDisplay1Id);
  AddDisplayToLayout(displays.get(), kDisplay1Width, kDisplay2YOffset,
                     kDisplay2Width, kDisplay2Height, kDefaultDpi, kDefaultDpi,
                     kDisplay2Id);
  NotifyDesktopDisplaySize(std::move(displays));
}

// Set up multiple displays that are the same size:
// +-----------+
// |  800x600  |-----------+
// |     0     |  800x600  |
// +-----------+     1     |
//             +-----------+
void ClientSessionTest::SetupMultiDisplay_SameSize() {
  ResetDisplayInfo();
  auto displays = std::make_unique<protocol::VideoLayout>();
  AddDisplayToLayout(displays.get(), 0, 0, kDisplay1Width, kDisplay1Height,
                     kDefaultDpi, kDefaultDpi, kDisplay1Id);
  AddDisplayToLayout(displays.get(), kDisplay1Width, kDisplay2YOffset,
                     kDisplay1Width, kDisplay1Height, kDefaultDpi, kDefaultDpi,
                     kDisplay2Id);
  NotifyDesktopDisplaySize(std::move(displays));
}

void ClientSessionTest::MultiMon_SelectFirstDisplay() {
  NotifySelectDesktopDisplay("0");
}

void ClientSessionTest::MultiMon_SelectSecondDisplay() {
  NotifySelectDesktopDisplay("1");
}

void ClientSessionTest::MultiMon_SelectAllDisplays() {
  NotifySelectDesktopDisplay("all");
}

void ClientSessionTest::MultiMon_SelectDisplay(std::string display_id) {
  NotifySelectDesktopDisplay(display_id);
}

webrtc::ScreenId ClientSessionTest::GetSelectedSourceDisplayId() {
  return connection_->last_video_stream()->selected_source();
}

TEST_F(
    ClientSessionTest,
    OnLocalPoliciesChanged_DoesNotDisconnectIfEffectivePoliciesComeFromRemotePolicies) {
  SessionPolicies remote_policies;
  remote_policies.maximum_session_duration = base::Hours(8);
  CreateClientSession();
  ConnectClientSession(&remote_policies);

  EXPECT_TRUE(connection_->is_connected());
  SessionPolicies new_policies;
  new_policies.maximum_session_duration = base::Hours(23);
  local_session_policies_provider_.set_local_policies(new_policies);
  EXPECT_TRUE(connection_->is_connected());
}

TEST_F(ClientSessionTest,
       OnLocalPoliciesChanged_DoesNotDisconnectIfEffectivePoliciesNotChanged) {
  CreateClientSession();
  ConnectClientSession();

  EXPECT_TRUE(connection_->is_connected());
  local_session_policies_provider_.set_local_policies(initial_local_policies_);
  EXPECT_TRUE(connection_->is_connected());
}

TEST_F(ClientSessionTest,
       OnLocalPoliciesChanged_DisconnectsIfEffectivePoliciesChanged) {
  CreateClientSession();
  ConnectClientSession();

  EXPECT_TRUE(connection_->is_connected());
  SessionPolicies local_policies;
  local_policies.maximum_session_duration = base::Hours(23);
  local_session_policies_provider_.set_local_policies(local_policies);
  EXPECT_FALSE(connection_->is_connected());
}

TEST_F(ClientSessionTest, DisconnectsAfterMaxSessionDurationIsReached) {
  CreateClientSession();
  ConnectClientSession();

  EXPECT_TRUE(connection_->is_connected());
  // Calling FastForwardBy() would result in a livelock, so we just advance the
  // clock and run all the scheduled tasks, which includes the max duration
  // timer.
  task_environment_.AdvanceClock(
      *initial_local_policies_.maximum_session_duration + base::Minutes(1));
  task_environment_.RunUntilIdle();
  EXPECT_FALSE(connection_->is_connected());
}

TEST_F(ClientSessionTest, DisconnectsIfOnSessionPoliciesReceivedReturnsError) {
  EXPECT_CALL(session_event_handler_,
              OnSessionPoliciesReceived(initial_local_policies_))
      .WillOnce(Return(ErrorCode::DISALLOWED_BY_POLICY));

  CreateClientSession();
  client_session_->OnConnectionAuthenticated(nullptr);

  EXPECT_FALSE(connection_->is_connected());
  EXPECT_EQ(connection_->disconnect_error(), ErrorCode::DISALLOWED_BY_POLICY);
}

TEST_F(ClientSessionTest,
       EffectivePoliciesImplicitlyAllowFileTransfer_HasCapability) {
  local_session_policies_provider_.set_local_policies({});
  EXPECT_CALL(
      client_stub_,
      SetCapabilities(IncludesCapabilities(protocol::kFileTransferCapability)));

  CreateClientSession();
  ConnectClientSession();
}

TEST_F(ClientSessionTest,
       EffectivePoliciesExplicitlyAllowFileTransfer_HasCapability) {
  SessionPolicies local_policies;
  local_policies.allow_file_transfer = true;
  local_session_policies_provider_.set_local_policies(local_policies);
  EXPECT_CALL(
      client_stub_,
      SetCapabilities(IncludesCapabilities(protocol::kFileTransferCapability)));

  CreateClientSession();
  ConnectClientSession();
}

TEST_F(ClientSessionTest,
       EffectivePoliciesDisallowFileTransfer_DoesNotHaveCapability) {
  SessionPolicies local_policies;
  local_policies.allow_file_transfer = false;
  local_session_policies_provider_.set_local_policies(local_policies);
  EXPECT_CALL(client_stub_, SetCapabilities(Not(IncludesCapabilities(
                                protocol::kFileTransferCapability))));

  CreateClientSession();
  ConnectClientSession();
}

TEST_F(ClientSessionTest, ApplyPoliciesFromRemotePolicies) {
  SessionPolicies local_policies;
  local_policies.allow_file_transfer = true;
  local_policies.allow_uri_forwarding = true;
  local_session_policies_provider_.set_local_policies(local_policies);
  SessionPolicies remote_policies;
  remote_policies.allow_file_transfer = false;
  remote_policies.allow_uri_forwarding = false;
  EXPECT_CALL(client_stub_,
              SetCapabilities(Not(IncludesCapabilities(
                  std::string() + protocol::kFileTransferCapability + " " +
                  protocol::kRemoteOpenUrlCapability))));

  CreateClientSession();
  ConnectClientSession(&remote_policies);
}

// TODO(lambroslambrou): Re-implement the deleted MultiMonMouseMove
// and MultiMonMouseMove_SameSize tests in a way that makes sense for
// multi-stream mode.

TEST_F(ClientSessionTest, DisableInputs) {
  CreateClientSession();
  ConnectClientSession();
  SetupSingleDisplay();

  FakeInputInjector* input_injector =
      desktop_environment_factory_->last_desktop_environment()
          ->last_input_injector()
          .get();
  input_injector->set_key_events(&key_events_);
  input_injector->set_mouse_events(&mouse_events_);
  input_injector->set_clipboard_events(&clipboard_events_);

  // Inject test events that are expected to be injected.
  connection_->clipboard_stub()->InjectClipboardEvent(MakeClipboardEvent("a"));
  connection_->input_stub()->InjectKeyEvent(MakeKeyEvent(true, 1));
  connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(100, 101));

  // Disable input.
  client_session_->SetDisableInputs(true);

  // These events shouldn't get though to the input injector.
  connection_->clipboard_stub()->InjectClipboardEvent(MakeClipboardEvent("b"));
  connection_->input_stub()->InjectKeyEvent(MakeKeyEvent(true, 2));
  connection_->input_stub()->InjectKeyEvent(MakeKeyEvent(false, 2));
  connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(200, 201));

  // Enable input again.
  client_session_->SetDisableInputs(false);
  connection_->clipboard_stub()->InjectClipboardEvent(MakeClipboardEvent("c"));
  connection_->input_stub()->InjectKeyEvent(MakeKeyEvent(true, 3));
  connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(300, 301));

  client_session_->DisconnectSession(ErrorCode::OK, {}, FROM_HERE);
  client_session_.reset();

  EXPECT_EQ(2U, mouse_events_.size());
  EXPECT_THAT(mouse_events_[0], EqualsMouseMoveEvent(100, 101));
  EXPECT_THAT(mouse_events_[1], EqualsMouseMoveEvent(300, 301));

  EXPECT_EQ(4U, key_events_.size());
  EXPECT_THAT(key_events_[0], EqualsKeyEvent(1, true));
  EXPECT_THAT(key_events_[1], EqualsKeyEvent(1, false));
  EXPECT_THAT(key_events_[2], EqualsKeyEvent(3, true));
  EXPECT_THAT(key_events_[3], EqualsKeyEvent(3, false));

  EXPECT_EQ(2U, clipboard_events_.size());
  EXPECT_THAT(clipboard_events_[0],
              EqualsClipboardEvent(kMimeTypeTextUtf8, "a"));
  EXPECT_THAT(clipboard_events_[1],
              EqualsClipboardEvent(kMimeTypeTextUtf8, "c"));
}

TEST_F(ClientSessionTest, InputAllowedFromRemotePolicy) {
  SessionPolicies remote_policies;
  remote_policies.allow_remote_input = true;
  CreateClientSession();
  ConnectClientSession(&remote_policies);
  SetupSingleDisplay();

  FakeInputInjector* input_injector =
      desktop_environment_factory_->last_desktop_environment()
          ->last_input_injector()
          .get();
  input_injector->set_key_events(&key_events_);
  input_injector->set_mouse_events(&mouse_events_);
  input_injector->set_clipboard_events(&clipboard_events_);

  connection_->clipboard_stub()->InjectClipboardEvent(MakeClipboardEvent("a"));
  connection_->input_stub()->InjectKeyEvent(MakeKeyEvent(true, 1));
  connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(100, 101));

  client_session_->DisconnectSession(ErrorCode::OK, {}, FROM_HERE);
  client_session_.reset();

  EXPECT_EQ(1U, mouse_events_.size());
  EXPECT_THAT(mouse_events_[0], EqualsMouseMoveEvent(100, 101));

  EXPECT_EQ(2U, key_events_.size());
  EXPECT_THAT(key_events_[0], EqualsKeyEvent(1, true));
  EXPECT_THAT(key_events_[1], EqualsKeyEvent(1, false));

  EXPECT_EQ(1U, clipboard_events_.size());
  EXPECT_THAT(clipboard_events_[0],
              EqualsClipboardEvent(kMimeTypeTextUtf8, "a"));
}

TEST_F(ClientSessionTest, InputDisabledFromRemotePolicy) {
  SessionPolicies remote_policies;
  remote_policies.allow_remote_input = false;
  CreateClientSession();
  ConnectClientSession(&remote_policies);
  SetupSingleDisplay();

  FakeInputInjector* input_injector =
      desktop_environment_factory_->last_desktop_environment()
          ->last_input_injector()
          .get();
  input_injector->set_key_events(&key_events_);
  input_injector->set_mouse_events(&mouse_events_);
  input_injector->set_clipboard_events(&clipboard_events_);

  connection_->clipboard_stub()->InjectClipboardEvent(MakeClipboardEvent("a"));
  connection_->input_stub()->InjectKeyEvent(MakeKeyEvent(true, 1));
  connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(100, 101));

  client_session_->DisconnectSession(ErrorCode::OK, {}, FROM_HERE);
  client_session_.reset();

  EXPECT_EQ(0U, mouse_events_.size());
  EXPECT_EQ(0U, key_events_.size());
  EXPECT_EQ(0U, clipboard_events_.size());
}

TEST_F(ClientSessionTest, LocalInputTest) {
  CreateClientSession();
  ConnectClientSession();
  SetupSingleDisplay();

  desktop_environment_factory_->last_desktop_environment()
      ->last_input_injector()
      ->set_mouse_events(&mouse_events_);

  connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(100, 101));

#if !BUILDFLAG(IS_WIN) && !BUILDFLAG(IS_CHROMEOS)
  // The OS echoes the injected event back.
  client_session_->OnLocalPointerMoved(webrtc::DesktopVector(100, 101),
                                       ui::EventType::kMouseMoved);
#endif  // !BUILDFLAG(IS_WIN)

  // This one should get throught as well.
  connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(200, 201));

  // Now this is a genuine local event.
  client_session_->OnLocalPointerMoved(webrtc::DesktopVector(100, 101),
                                       ui::EventType::kMouseMoved);

  // This one should be blocked because of the previous local input event.
  connection_->input_stub()->InjectMouseEvent(MakeMouseMoveEvent(300, 301));

  // Verify that we've received correct set of mouse events.
  ASSERT_EQ(2U, mouse_events_.size());
  EXPECT_THAT(mouse_events_[0], EqualsMouseMoveEvent(100, 101));
  EXPECT_THAT(mouse_events_[1], EqualsMouseMoveEvent(200, 201));

  // Verify that we're still connected.
  EXPECT_TRUE(connection_->is_connected());

  // TODO(jamiewalch): Verify that remote inputs are re-enabled
  // eventually (via dependency injection, not sleep!)
}

TEST_F(ClientSessionTest, DisconnectOnLocalInputTest) {
  desktop_environment_options_.set_terminate_upon_input(true);
  CreateClientSession();
  ConnectClientSession();
  SetupSingleDisplay();

  client_session_->OnLocalPointerMoved(webrtc::DesktopVector(100, 101),
                                       ui::EventType::kMouseMoved);
  EXPECT_FALSE(connection_->is_connected());
}

TEST_F(ClientSessionTest, RestoreEventState) {
  CreateClientSession();
  ConnectClientSession();
  SetupSingleDisplay();

  FakeInputInjector* input_injector =
      desktop_environment_factory_->last_desktop_environment()
          ->last_input_injector()
          .get();
  input_injector->set_key_events(&key_events_);
  input_injector->set_mouse_events(&mouse_events_);

  connection_->input_stub()->InjectKeyEvent(MakeKeyEvent(true, 1));
  connection_->input_stub()->InjectKeyEvent(MakeKeyEvent(true, 2));

  protocol::MouseEvent mousedown;
  mousedown.set_button(protocol::MouseEvent::BUTTON_LEFT);
  mousedown.set_button_down(true);
  connection_->input_stub()->InjectMouseEvent(mousedown);

  client_session_->DisconnectSession(ErrorCode::OK, {}, FROM_HERE);
  client_session_.reset();

  EXPECT_EQ(2U, mouse_events_.size());
  EXPECT_THAT(mouse_events_[0],
              EqualsMouseButtonEvent(protocol::MouseEvent::BUTTON_LEFT, true));
  EXPECT_THAT(mouse_events_[1],
              EqualsMouseButtonEvent(protocol::MouseEvent::BUTTON_LEFT, false));

  EXPECT_EQ(4U, key_events_.size());
  EXPECT_THAT(key_events_[0], EqualsKeyEvent(1, true));
  EXPECT_THAT(key_events_[1], EqualsKeyEvent(2, true));
  EXPECT_THAT(key_events_[2], EqualsKeyEvent(1, false));
  EXPECT_THAT(key_events_[3], EqualsKeyEvent(2, false));
}

TEST_F(ClientSessionTest, ClampMouseEvents) {
  CreateClientSession();
  ConnectClientSession();
  SetupSingleDisplay();

  desktop_environment_factory_->last_desktop_environment()
      ->last_input_injector()
      ->set_mouse_events(&mouse_events_);

  std::array<int, 3> input_x = {-999, 100, 999};
  std::array<int, 3> expected_x = {
      0,
      100,
      protocol::FakeDesktopCapturer::kWidth - 1,
  };
  std::array<int, 3> input_y = {-999, 50, 999};
  std::array<int, 3> expected_y = {
      0,
      50,
      protocol::FakeDesktopCapturer::kHeight - 1,
  };

  protocol::MouseEvent expected_event;
  for (int j = 0; j < 3; j++) {
    for (int i = 0; i < 3; i++) {
      mouse_events_.clear();
      connection_->input_stub()->InjectMouseEvent(
          MakeMouseMoveEvent(input_x[i], input_y[j]));

      EXPECT_EQ(1U, mouse_events_.size());
      EXPECT_THAT(mouse_events_[0],
                  EqualsMouseMoveEvent(expected_x[i], expected_y[j]));
    }
  }
}

// Verifies that clients can have extensions registered, resulting in the
// correct capabilities being reported, and messages delivered correctly.
// The extension system is tested more extensively in the
// HostExtensionSessionManager unit-tests.
TEST_F(ClientSessionTest, Extensions) {
  // Configure fake extensions for testing.
  FakeExtension extension1("ext1", "cap1");
  extensions_.push_back(&extension1);
  FakeExtension extension2("ext2", "");
  extensions_.push_back(&extension2);
  FakeExtension extension3("ext3", "cap3");
  extensions_.push_back(&extension3);

  // Verify that the ClientSession reports the correct capabilities.
  EXPECT_CALL(client_stub_, SetCapabilities(IncludesCapabilities("cap1 cap3")));

  CreateClientSession();
  ConnectClientSession();

  testing::Mock::VerifyAndClearExpectations(&client_stub_);

  // Mimic the client reporting an overlapping set of capabilities.
  protocol::Capabilities capabilities_message;
  capabilities_message.set_capabilities("cap1 cap4 default");
  client_session_->SetCapabilities(capabilities_message);

  // Verify that the correct extension messages are delivered, and dropped.
  protocol::ExtensionMessage message1;
  message1.set_type("ext1");
  message1.set_data("data");
  client_session_->DeliverClientMessage(message1);
  protocol::ExtensionMessage message3;
  message3.set_type("ext3");
  message3.set_data("data");
  client_session_->DeliverClientMessage(message3);
  protocol::ExtensionMessage message4;
  message4.set_type("ext4");
  message4.set_data("data");
  client_session_->DeliverClientMessage(message4);

  base::RunLoop().RunUntilIdle();

  // ext1 was instantiated and sent a message, and did not wrap anything.
  EXPECT_TRUE(extension1.was_instantiated());
  EXPECT_TRUE(extension1.has_handled_message());

  // ext2 was instantiated but not sent a message, and wrapped video encoder.
  EXPECT_TRUE(extension2.was_instantiated());
  EXPECT_FALSE(extension2.has_handled_message());

  // ext3 was sent a message but not instantiated.
  EXPECT_FALSE(extension3.was_instantiated());

  // Drop references to locals before they go out of scope.
  extensions_.clear();
}

TEST_F(ClientSessionTest, DataChannelCallbackIsCalled) {
  bool callback_called = false;

  CreateClientSession();
  client_session_->RegisterCreateHandlerCallbackForTesting(
      kTestDataChannelCallbackName,
      base::BindRepeating([](bool* callback_was_called, const std::string& name,
                             std::unique_ptr<protocol::MessagePipe> pipe)
                              -> void { *callback_was_called = true; },
                          &callback_called));
  ConnectClientSession();

  std::unique_ptr<protocol::MessagePipe> pipe =
      base::WrapUnique(new protocol::FakeMessagePipe(false));

  client_session_->OnIncomingDataChannel(kTestDataChannelCallbackName,
                                         std::move(pipe));

  ASSERT_TRUE(callback_called);
}

TEST_F(ClientSessionTest, ForwardHostSessionOptions1) {
  auto session = std::make_unique<protocol::FakeSession>();
  auto configuration = std::make_unique<jingle_xmpp::XmlElement>(
      jingle_xmpp::QName(kChromotingXmlNamespace, "host-configuration"));
  configuration->SetBodyText("Detect-Updated-Region:true");
  session->SetAttachment(0, std::move(configuration));
  CreateClientSession(std::move(session));
  ConnectClientSession();
  ASSERT_TRUE(desktop_environment_factory_->last_desktop_environment()
                  ->options()
                  .desktop_capture_options()
                  ->detect_updated_region());
}

TEST_F(ClientSessionTest, ForwardHostSessionOptions2) {
  auto session = std::make_unique<protocol::FakeSession>();
  auto configuration = std::make_unique<jingle_xmpp::XmlElement>(
      jingle_xmpp::QName(kChromotingXmlNamespace, "host-configuration"));
  configuration->SetBodyText("Detect-Updated-Region:false");
  session->SetAttachment(0, std::move(configuration));
  CreateClientSession(std::move(session));
  ConnectClientSession();
  ASSERT_FALSE(desktop_environment_factory_->last_desktop_environment()
                   ->options()
                   .desktop_capture_options()
                   ->detect_updated_region());
}

TEST_F(ClientSessionTest, ActiveDisplayMessageSent) {
  EXPECT_CALL(client_stub_, SetActiveDisplay(ScreenIdMatches(kDisplay1Id)));

  // The ActiveDisplayMonitor only gets created after negotiating this
  // capability with the client.
  desktop_environment_factory_->set_capabilities(
      protocol::kMultiStreamCapability);
  CreateClientSession();
  ConnectClientSession();

  protocol::Capabilities client_capabilities;
  client_capabilities.set_capabilities(protocol::kMultiStreamCapability);
  client_session_->SetCapabilities(client_capabilities);

  auto monitor = desktop_environment_factory_->last_desktop_environment()
                     ->last_active_display_monitor();
  ASSERT_TRUE(monitor);
  monitor->SetActiveDisplay(static_cast<webrtc::ScreenId>(kDisplay1Id));
}

// Display selection behaves quite differently if capturing of the full desktop
// is enabled or not. To simplify things these tests only handle the ChromeOS
// situation, where full desktop capturing is disabled.
#if BUILDFLAG(IS_CHROMEOS)
TEST_F(ClientSessionTest, ShouldSelectFirstDesktopByDefault) {
  CreateClientSession();
  ConnectClientSession();

  SetupMultiDisplay();

  EXPECT_THAT(GetSelectedSourceDisplayId(), Eq(kDisplay1Id));
}

TEST_F(ClientSessionTest,
       ShouldChangeSelectedSourceDisplayWhenSwitchingDisplay) {
  CreateClientSession();
  ConnectClientSession();
  SetupMultiDisplay();

  MultiMon_SelectSecondDisplay();
  EXPECT_THAT(GetSelectedSourceDisplayId(), Eq(kDisplay2Id));

  MultiMon_SelectFirstDisplay();
  EXPECT_THAT(GetSelectedSourceDisplayId(), Eq(kDisplay1Id));
}

TEST_F(ClientSessionTest,
       ShouldFallBackToPrimaryDisplayWhenSwitchingToInvalidDisplay) {
  CreateClientSession();
  ConnectClientSession();
  SetupMultiDisplay();

  MultiMon_SelectDisplay("Not an integer");
  EXPECT_THAT(GetSelectedSourceDisplayId(), Eq(kDisplay1Id));

  MultiMon_SelectDisplay("123456");  // There is no display with this id.
  EXPECT_THAT(GetSelectedSourceDisplayId(), Eq(kDisplay1Id));

  // Full desktop capturing is not supported on ChromeOS.
  MultiMon_SelectDisplay("all");
  EXPECT_THAT(GetSelectedSourceDisplayId(), Eq(kDisplay1Id));
}

TEST_F(ClientSessionTest,
       ShouldFallBackToPrimaryDisplayWhenSelectedDisplayIsDisconnected) {
  CreateClientSession();
  ConnectClientSession();
  SetupMultiDisplay();
  MultiMon_SelectSecondDisplay();

  SetupSingleDisplay();
  EXPECT_THAT(GetSelectedSourceDisplayId(), Eq(kDisplay1Id));
}
#endif  // if BUILDFLAG(IS_CHROMEOS)

}  // namespace remoting