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/chromoting_host.h"

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

#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/process/process_handle.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "build/build_config.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/network_change_notifier.h"
#include "remoting/base/auto_thread_task_runner.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/chromoting_host_context.h"
#include "remoting/host/client_session.h"
#include "remoting/host/fake_desktop_environment.h"
#include "remoting/host/host_extension.h"
#include "remoting/host/host_mock_objects.h"
#include "remoting/host/mojom/chromoting_host_services.mojom.h"
#include "remoting/protocol/connection_to_client.h"
#include "remoting/protocol/fake_connection_to_client.h"
#include "remoting/protocol/protocol_mock_objects.h"
#include "remoting/protocol/session.h"
#include "remoting/protocol/session_config.h"
#include "remoting/protocol/session_manager.h"
#include "remoting/protocol/transport.h"
#include "remoting/protocol/transport_context.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(IS_WIN)
#include <windows.h>
#endif

using ::remoting::protocol::MockClientStub;
using ::remoting::protocol::MockConnectionToClientEventHandler;
using ::remoting::protocol::MockHostStub;
using ::remoting::protocol::MockSession;
using ::remoting::protocol::MockVideoStub;
using ::remoting::protocol::Session;
using ::remoting::protocol::SessionConfig;

using testing::_;
using testing::AnyNumber;
using testing::AtLeast;
using testing::AtMost;
using testing::DeleteArg;
using testing::DoAll;
using testing::Expectation;
using testing::InSequence;
using testing::InvokeArgument;
using testing::InvokeWithoutArgs;
using testing::Return;
using testing::ReturnRef;
using testing::SaveArg;
using testing::Sequence;

namespace remoting {

const std::size_t kNumFailuresIgnored = 5;

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

  void SetUp() override {
    network_change_notifier_ = net::NetworkChangeNotifier::CreateIfNeeded();

    task_runner_ = new AutoThreadTaskRunner(
        base::SingleThreadTaskRunner::GetCurrentDefault(), base::DoNothing());

    desktop_environment_factory_ =
        std::make_unique<FakeDesktopEnvironmentFactory>(
            base::SingleThreadTaskRunner::GetCurrentDefault());
    session_manager_ = new protocol::MockSessionManager();

    host_ = std::make_unique<ChromotingHost>(
        desktop_environment_factory_.get(),
        base::WrapUnique(session_manager_.get()),
        protocol::TransportContext::ForTests(protocol::TransportRole::SERVER),
        task_runner_,  // Audio
        task_runner_,  // Video encode
        DesktopEnvironmentOptions::CreateDefault(), base::NullCallback(),
        &local_session_policies_provider_);
    host_->status_monitor()->AddStatusObserver(&host_status_observer_);

    owner_email_ = "host@domain";
    session1_ = new MockSession();
    session2_ = new MockSession();
    session_unowned1_ = std::make_unique<MockSession>();
    session_unowned2_ = std::make_unique<MockSession>();
    session_config1_ = SessionConfig::ForTest();
    session_jid1_ = "user@domain/rest-of-jid";
    session_config2_ = SessionConfig::ForTest();
    session_jid2_ = "user2@domain/rest-of-jid";
    session_unowned_jid1_ = "user3@doman/rest-of-jid";
    session_unowned_jid2_ = "user4@doman/rest-of-jid";

    EXPECT_CALL(*session1_, jid()).WillRepeatedly(ReturnRef(session_jid1_));
    EXPECT_CALL(*session2_, jid()).WillRepeatedly(ReturnRef(session_jid2_));
    EXPECT_CALL(*session_unowned1_, jid())
        .WillRepeatedly(ReturnRef(session_unowned_jid1_));
    EXPECT_CALL(*session_unowned2_, jid())
        .WillRepeatedly(ReturnRef(session_unowned_jid2_));
    EXPECT_CALL(*session_unowned1_, SetEventHandler(_))
        .Times(AnyNumber())
        .WillRepeatedly(SaveArg<0>(&session_unowned1_event_handler_));
    EXPECT_CALL(*session_unowned2_, SetEventHandler(_))
        .Times(AnyNumber())
        .WillRepeatedly(SaveArg<0>(&session_unowned2_event_handler_));
    EXPECT_CALL(*session1_, config())
        .WillRepeatedly(ReturnRef(*session_config1_));
    EXPECT_CALL(*session2_, config())
        .WillRepeatedly(ReturnRef(*session_config2_));
    EXPECT_CALL(*session_unowned1_, config())
        .WillRepeatedly(ReturnRef(*session_config1_));
    EXPECT_CALL(*session_unowned2_, config())
        .WillRepeatedly(ReturnRef(*session_config2_));

    connection1_ = std::make_unique<protocol::FakeConnectionToClient>(
        base::WrapUnique(session1_.get()));
    connection1_->set_host_stub(&host_stub1_);
    connection1_->set_client_stub(&client_stub1_);

    connection2_ = std::make_unique<protocol::FakeConnectionToClient>(
        base::WrapUnique(session2_.get()));
    connection2_->set_host_stub(&host_stub2_);
    connection2_->set_client_stub(&client_stub2_);
  }

  // Helper method to pretend a client is connected to ChromotingHost.
  void SimulateClientConnection(int connection_index,
                                bool authenticate,
                                bool reject) {
    std::unique_ptr<protocol::ConnectionToClient> connection =
        std::move((connection_index == 0) ? connection1_ : connection2_);
    protocol::ConnectionToClient* connection_ptr = connection.get();
    auto client = std::make_unique<ClientSession>(
        host_.get(), std::move(connection), desktop_environment_factory_.get(),
        DesktopEnvironmentOptions::CreateDefault(), nullptr,
        std::vector<raw_ptr<HostExtension, VectorExperimental>>(),
        &local_session_policies_provider_);
    ClientSession* client_ptr = client.get();

    connection_ptr->set_host_stub(client.get());
    get_client(connection_index) = client_ptr;

    // |host| is responsible for deleting |client| from now on.
    host_->clients_.push_back(std::move(client));

    if (authenticate) {
      if (reject) {
        // Free the corresponding client pointer to prevent a dangling pointer
        // crash.
        PrepareForClientDisconnection(connection_index);
      }
      client_ptr->OnConnectionAuthenticated(nullptr);
      if (!reject) {
        client_ptr->OnConnectionChannelsConnected();
      }
    } else {
      PrepareForClientDisconnection(connection_index);
      client_ptr->OnConnectionClosed(ErrorCode::AUTHENTICATION_FAILED);
    }
  }

  void TearDown() override {
    if (host_) {
      ShutdownHost();
    }
    task_runner_ = nullptr;

    base::RunLoop().RunUntilIdle();
  }

  void NotifyConnectionClosed1() {
    if (session_unowned1_event_handler_) {
      protocol::Session::EventHandler* handler =
          std::exchange(session_unowned1_event_handler_, nullptr);
      handler->OnSessionStateChange(Session::CLOSED);
    }
  }

  void NotifyConnectionClosed2() {
    if (session_unowned2_event_handler_) {
      protocol::Session::EventHandler* handler =
          std::exchange(session_unowned2_event_handler_, nullptr);
      handler->OnSessionStateChange(Session::CLOSED);
    }
  }

  void ShutdownHost() {
    EXPECT_CALL(host_status_observer_, OnHostShutdown());
    session_manager_ = nullptr;
    client1_ = nullptr;
    session1_ = nullptr;
    client2_ = nullptr;
    session2_ = nullptr;
    host_.reset();
    desktop_environment_factory_.reset();
  }

  // Starts the host.
  void StartHost() {
    EXPECT_CALL(host_status_observer_, OnHostStarted(owner_email_));
    EXPECT_CALL(*session_manager_, AcceptIncoming(_));
    host_->Start(owner_email_);
  }

  // Expect a client to connect.
  // Return an expectation that a session has started.
  std::unique_ptr<base::test::TestFuture<void>> ExpectClientConnected(
      int connection_index) {
    const std::string& session_jid = get_session_jid(connection_index);
    auto future = std::make_unique<base::test::TestFuture<void>>();

    Expectation client_authenticated =
        EXPECT_CALL(host_status_observer_, OnClientAuthenticated(session_jid));
    EXPECT_CALL(host_status_observer_, OnClientConnected(session_jid))
        .After(client_authenticated)
        .WillOnce([callback = future->GetCallback()]() mutable {
          std::move(callback).Run();
        });
    return future;
  }

  ClientSession* PrepareForClientDisconnection(int connection_index) {
    // A client disconnecting will destroy the session and client.
    // Clear both the session and client and return the client to the caller.
    switch (connection_index) {
      case 0:
        session1_ = nullptr;
        return std::exchange(client1_, nullptr);
      case 1:
        session2_ = nullptr;
        return std::exchange(client2_, nullptr);
      default:
        NOTREACHED();
    }
  }

  // Expect that a client is disconnected. The given action will be done after
  // the status observer is notified that the session has finished.
  // A pointer to the client is returned.
  ClientSession* ExpectClientDisconnected(int connection_index) {
    EXPECT_CALL(host_status_observer_,
                OnClientDisconnected(get_session_jid(connection_index)))
        .RetiresOnSaturation();
    return PrepareForClientDisconnection(connection_index);
  }

  void SetPerSessionPoliciesValidator(
      const ChromotingHost::SessionPoliciesValidator& validator) {
    host_->per_session_policies_validator_ = validator;
  }

  mojo::Remote<mojom::ChromotingHostServices> BindChromotingHostServices() {
    mojo::Remote<mojom::ChromotingHostServices> remote;
    // ChromotingHost::BindSessionServices calls ProcessIdToSessionId() on the
    // IPC client's PID. The PID we know that always works is the current
    // process' PID.
    auto current_pid = base::GetCurrentProcId();
    host_->BindChromotingHostServices(remote.BindNewPipeAndPassReceiver(),
                                      current_pid);
    return remote;
  }

#if BUILDFLAG(IS_WIN)
  // Simulates the IPC client's session ID for the session ID check in
  // ChromotingHost::BindSessionServices.
  //
  // |is_remote_desktop_session_id|: True if the simulated session ID should be
  // exactly the session ID of the fake desktop environment. If false, the
  // simulated session ID is guaranteed to be different from the desktop
  // environment's session ID.
  void SimulateIpcClientSessionId(bool is_remote_desktop_session_id) {
    // ChromotingHost::BindSessionServices calls ProcessIdToSessionId() on the
    // IPC client's PID. The PID we know that always works is the current
    // process' PID.
    auto current_pid = base::GetCurrentProcId();
    DWORD current_session_id;
    bool success = ProcessIdToSessionId(current_pid, &current_session_id);
    ASSERT_TRUE(success);
    // The IPC client's session ID is exactly the current process' session ID
    // at this point, so we change the fake desktop environment's session ID
    // here.
    if (is_remote_desktop_session_id) {
      desktop_environment_factory_->set_desktop_session_id(current_session_id);
    } else {
      desktop_environment_factory_->set_desktop_session_id(current_session_id +
                                                           1);
    }
  }
#endif

 protected:
  base::test::TaskEnvironment task_environment_;
  std::unique_ptr<net::NetworkChangeNotifier> network_change_notifier_;
  scoped_refptr<AutoThreadTaskRunner> task_runner_;
  MockConnectionToClientEventHandler handler_;
  std::unique_ptr<FakeDesktopEnvironmentFactory> desktop_environment_factory_;
  MockHostStatusObserver host_status_observer_;
  LocalSessionPoliciesProvider local_session_policies_provider_;
  std::unique_ptr<ChromotingHost> host_;
  raw_ptr<protocol::MockSessionManager> session_manager_;
  std::string owner_email_;
  std::unique_ptr<protocol::FakeConnectionToClient> connection1_;
  raw_ptr<ClientSession> client1_;  // Owned by |host_|.
  std::string session_jid1_;
  raw_ptr<MockSession> session1_;  // Owned by |connection1_|.
  std::unique_ptr<SessionConfig> session_config1_;
  MockClientStub client_stub1_;
  MockHostStub host_stub1_;
  std::unique_ptr<protocol::FakeConnectionToClient> connection2_;
  raw_ptr<ClientSession> client2_;  // Owned by |host_|.
  std::string session_jid2_;
  raw_ptr<MockSession> session2_;  // Owned by |connection2_|.
  std::unique_ptr<SessionConfig> session_config2_;
  MockClientStub client_stub2_;
  MockHostStub host_stub2_;
  std::unique_ptr<MockSession> session_unowned1_;  // Not owned by a connection.
  std::string session_unowned_jid1_;
  std::unique_ptr<MockSession> session_unowned2_;  // Not owned by a connection.
  std::string session_unowned_jid2_;
  raw_ptr<protocol::Session::EventHandler> session_unowned1_event_handler_;
  raw_ptr<protocol::Session::EventHandler> session_unowned2_event_handler_;

  // Returns the cached client pointers client1_ or client2_.
  raw_ptr<ClientSession>& get_client(int connection_index) {
    return (connection_index == 0) ? client1_ : client2_;
  }

  const std::string& get_session_jid(int connection_index) {
    return (connection_index == 0) ? session_jid1_ : session_jid2_;
  }
};

TEST_F(ChromotingHostTest, StartAndShutdown) {
  StartHost();
}

TEST_F(ChromotingHostTest, Connect) {
  StartHost();

  // Shut down the host when the first video packet is received.
  auto future = ExpectClientConnected(0);
  SimulateClientConnection(0, true, false);
  future->Get();
}

TEST_F(ChromotingHostTest, AuthenticationFailed) {
  StartHost();

  EXPECT_CALL(host_status_observer_, OnClientAccessDenied(session_jid1_));
  SimulateClientConnection(0, false, false);
}

TEST_F(ChromotingHostTest, Reconnect) {
  StartHost();

  // Connect first client.
  auto future = ExpectClientConnected(0);
  SimulateClientConnection(0, true, false);
  future->Get();

  // Disconnect first client.
  ClientSession* client1 = ExpectClientDisconnected(0);
  client1->OnConnectionClosed(ErrorCode::OK);

  // Connect second client.
  future = ExpectClientConnected(1);
  SimulateClientConnection(1, true, false);
  future->Get();

  // Disconnect second client.
  ClientSession* client2 = ExpectClientDisconnected(1);
  client2->OnConnectionClosed(ErrorCode::OK);
}

TEST_F(ChromotingHostTest, ConnectWhenAnotherClientIsConnected) {
  StartHost();

  // Connect first client.
  auto future = ExpectClientConnected(0);
  SimulateClientConnection(0, true, false);
  future->Get();

  // Connect second client. First client should be disconnected automatically.
  {
    InSequence s;
    ExpectClientDisconnected(0);
    future = ExpectClientConnected(1);
  }
  SimulateClientConnection(1, true, false);
  future->Get();

  // Disconnect second client.
  ClientSession* client2 = ExpectClientDisconnected(1);
  client2->OnConnectionClosed(ErrorCode::OK);
}

TEST_F(ChromotingHostTest, IncomingSessionAccepted) {
  StartHost();

  MockSession* session = session_unowned1_.get();
  protocol::SessionManager::IncomingSessionResponse response =
      protocol::SessionManager::DECLINE;
  std::string rejection_reason;
  base::Location rejection_location;
  host_->OnIncomingSession(session_unowned1_.release(), &response,
                           &rejection_reason, &rejection_location);
  EXPECT_EQ(protocol::SessionManager::ACCEPT, response);
  EXPECT_TRUE(rejection_reason.empty());
  EXPECT_EQ(nullptr, rejection_location.program_counter());

  EXPECT_CALL(*session, Close(_, _, _))
      .WillOnce(InvokeWithoutArgs(
          this, &ChromotingHostTest::NotifyConnectionClosed1));
  ShutdownHost();
}

TEST_F(ChromotingHostTest, LoginBackOffTriggersIfClientsDoNotAuthenticate) {
  StartHost();

  protocol::SessionManager::IncomingSessionResponse response =
      protocol::SessionManager::DECLINE;
  std::string rejection_reason;
  base::Location rejection_location;
  std::array<protocol::Session::EventHandler*, kNumFailuresIgnored + 1>
      session_event_handlers;
  for (auto*& session_event_handler : session_event_handlers) {
    // Set expectations and responses for the new session.
    auto session = std::make_unique<MockSession>();
    EXPECT_CALL(*session, jid()).WillRepeatedly(ReturnRef(session_jid1_));
    EXPECT_CALL(*session, config())
        .WillRepeatedly(ReturnRef(*session_config1_));
    EXPECT_CALL(*session, SetEventHandler(_))
        .Times(AnyNumber())
        .WillRepeatedly(SaveArg<0>(&session_event_handler));
    EXPECT_CALL(*session, Close(_, _, _))
        .WillOnce(InvokeWithoutArgs([&session_event_handler]() {
          session_event_handler->OnSessionStateChange(Session::CLOSED);
        }));
    // Simulate the incoming connection.
    host_->OnIncomingSession(session.release(), &response, &rejection_reason,
                             &rejection_location);
    EXPECT_EQ(protocol::SessionManager::ACCEPT, response);
    EXPECT_TRUE(rejection_reason.empty());
    EXPECT_EQ(nullptr, rejection_location.program_counter());
    // Begin authentication; this will increase the backoff count, and since
    // OnSessionAuthenticated is never called, the host should only allow
    // kNumFailuresIgnored + 1 connections before beginning the backoff.
    host_->OnSessionAuthenticating(
        host_->client_sessions_for_tests().front().get());
  }

  // As this is connection kNumFailuresIgnored + 2, it should be rejected.
  host_->OnIncomingSession(session_unowned2_.get(), &response,
                           &rejection_reason, &rejection_location);
  EXPECT_EQ(protocol::SessionManager::OVERLOAD, response);
  EXPECT_FALSE(rejection_reason.empty());
  EXPECT_NE(nullptr, rejection_location.program_counter());
  EXPECT_EQ(host_->client_sessions_for_tests().size(), kNumFailuresIgnored + 1);

  // Shut down host while objects owned by this test are still in scope.
  ShutdownHost();
}

TEST_F(ChromotingHostTest, LoginBackOffResetsIfClientsAuthenticate) {
  StartHost();

  protocol::SessionManager::IncomingSessionResponse response =
      protocol::SessionManager::DECLINE;
  std::string rejection_reason;
  base::Location rejection_location;
  std::array<protocol::Session::EventHandler*, kNumFailuresIgnored + 1>
      session_event_handlers;
  for (auto*& session_event_handler : session_event_handlers) {
    // Set expectations and responses for the new session.
    auto session = std::make_unique<MockSession>();
    EXPECT_CALL(*session, jid()).WillRepeatedly(ReturnRef(session_jid1_));
    EXPECT_CALL(*session, config())
        .WillRepeatedly(ReturnRef(*session_config1_));
    EXPECT_CALL(*session, SetEventHandler(_))
        .Times(AnyNumber())
        .WillRepeatedly(SaveArg<0>(&session_event_handler));
    EXPECT_CALL(*session, Close(_, _, _))
        .WillOnce(InvokeWithoutArgs([&session_event_handler]() {
          session_event_handler->OnSessionStateChange(Session::CLOSED);
        }));
    // Simulate the incoming connection.
    host_->OnIncomingSession(session.release(), &response, &rejection_reason,
                             &rejection_location);
    EXPECT_EQ(protocol::SessionManager::ACCEPT, response);
    EXPECT_TRUE(rejection_reason.empty());
    EXPECT_EQ(nullptr, rejection_location.program_counter());
    // Begin authentication; this will increase the backoff count
    host_->OnSessionAuthenticating(
        host_->client_sessions_for_tests().front().get());
  }

  // Simulate successful authentication for one of the previous connections.
  // This should reset the backoff and disconnect all the other connections.
  host_->OnSessionAuthenticated(
      host_->client_sessions_for_tests().front().get());
  EXPECT_EQ(host_->client_sessions_for_tests().size(), 1U);

  // This is connection kNumFailuresIgnored + 2, but since we now have a
  // successful authentication it should not be rejected.
  auto session = std::make_unique<MockSession>();
  protocol::Session::EventHandler* session_event_handler;
  EXPECT_CALL(*session, jid()).WillRepeatedly(ReturnRef(session_jid1_));
  EXPECT_CALL(*session, config()).WillRepeatedly(ReturnRef(*session_config1_));
  EXPECT_CALL(*session, SetEventHandler(_))
      .Times(AnyNumber())
      .WillRepeatedly(SaveArg<0>(&session_event_handler));
  EXPECT_CALL(*session, Close(_, _, _))
      .WillOnce(InvokeWithoutArgs([&session_event_handler]() {
        session_event_handler->OnSessionStateChange(Session::CLOSED);
      }));
  host_->OnIncomingSession(session.release(), &response, &rejection_reason,
                           &rejection_location);
  EXPECT_EQ(protocol::SessionManager::ACCEPT, response);
  EXPECT_TRUE(rejection_reason.empty());
  EXPECT_EQ(nullptr, rejection_location.program_counter());

  // Shut down host while objects owned by this test are still in scope.
  ShutdownHost();
}

// Flaky on all platforms.  http://crbug.com/1265894
TEST_F(ChromotingHostTest, DISABLED_OnSessionRouteChange) {
  StartHost();

  ExpectClientConnected(0);
  SimulateClientConnection(0, true, false);

  std::string channel_name("ChannelName");
  protocol::TransportRoute route;
  EXPECT_CALL(host_status_observer_,
              OnClientRouteChange(session_jid1_, channel_name, _));
  host_->OnSessionRouteChange(get_client(0), channel_name, route);
}

TEST_F(ChromotingHostTest, ExtraSessionPoliciesValidator) {
  SessionPolicies session_policies;
  session_policies.host_username_match_required = true;
  local_session_policies_provider_.set_local_policies(session_policies);
  base::MockCallback<ChromotingHost::SessionPoliciesValidator> mock_validator;
  EXPECT_CALL(mock_validator, Run(session_policies))
      .WillOnce(Return(ErrorCode::DISALLOWED_BY_POLICY));
  SetPerSessionPoliciesValidator(mock_validator.Get());

  StartHost();

  EXPECT_CALL(host_status_observer_, OnClientDisconnected(get_session_jid(0)));

  SimulateClientConnection(0, /* authenticate= */ true, /* reject= */ true);
}

TEST_F(ChromotingHostTest, BindSessionServicesWithNoConnectedSession_Rejected) {
  StartHost();

  mojo::Remote<mojom::ChromotingSessionServices> remote;
  auto receiver = remote.BindNewPipeAndPassReceiver();
  base::RunLoop wait_for_disconnect_run_loop;
  remote.set_disconnect_handler(wait_for_disconnect_run_loop.QuitClosure());
  host_->BindSessionServices(std::move(receiver));
  wait_for_disconnect_run_loop.Run();
}

TEST_F(ChromotingHostTest, BindSessionServicesWithConnectedSession_Accepted) {
  StartHost();
  auto host_services_remote = BindChromotingHostServices();
#if BUILDFLAG(IS_WIN)
  SimulateIpcClientSessionId(/* is_remote_desktop_session_id= */ true);
#endif
  auto future = ExpectClientConnected(0);
  SimulateClientConnection(0, true, false);
  future->Get();

  mojo::Remote<mojom::ChromotingSessionServices> remote;
  auto receiver = remote.BindNewPipeAndPassReceiver();
  base::RunLoop wait_for_version_run_loop;
  remote.set_disconnect_handler(base::BindLambdaForTesting([&]() {
    wait_for_version_run_loop.Quit();
    FAIL() << "Disconnect handler should not be called.";
  }));
  // QueryVersion() is used to determine whether the server accepts the bind
  // request; if it doesn't, the callback won't be called, and the disconnect
  // handler will be called instead.
  remote.QueryVersion(base::BindLambdaForTesting(
      [&](std::uint32_t version) { wait_for_version_run_loop.Quit(); }));
  // Note that we can't just call host_->BindSessionServices(), since that
  // doesn't have the peer PID context.
  host_services_remote->BindSessionServices(std::move(receiver));
  wait_for_version_run_loop.Run();
}

#if BUILDFLAG(IS_WIN)
TEST_F(ChromotingHostTest, BindSessionServicesWithWrongSession_Rejected) {
  StartHost();
  auto host_services_remote = BindChromotingHostServices();
  SimulateIpcClientSessionId(/* is_remote_desktop_session_id= */ false);
  auto future = ExpectClientConnected(0);
  SimulateClientConnection(0, true, false);
  future->Get();

  mojo::Remote<mojom::ChromotingSessionServices> remote;
  auto receiver = remote.BindNewPipeAndPassReceiver();
  base::RunLoop wait_for_disconnect_run_loop;
  remote.set_disconnect_handler(wait_for_disconnect_run_loop.QuitClosure());
  // Note that we can't just call host_->BindSessionServices(), since that
  // doesn't have the peer PID context.
  host_services_remote->BindSessionServices(std::move(receiver));
  wait_for_disconnect_run_loop.Run();
}
#endif

}  // namespace remoting