| // Copyright (c) 2012 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/host/desktop_process.h" |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/location.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/message_loop/message_pump_type.h" |
| #include "base/run_loop.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/test/task_environment.h" |
| #include "build/build_config.h" |
| #include "ipc/ipc_channel.h" |
| #include "ipc/ipc_channel_proxy.h" |
| #include "ipc/ipc_listener.h" |
| #include "mojo/public/cpp/bindings/associated_remote.h" |
| #include "remoting/base/auto_thread.h" |
| #include "remoting/base/auto_thread_task_runner.h" |
| #include "remoting/host/base/host_exit_codes.h" |
| #include "remoting/host/base/screen_resolution.h" |
| #include "remoting/host/chromoting_messages.h" |
| #include "remoting/host/desktop_process.h" |
| #include "remoting/host/fake_keyboard_layout_monitor.h" |
| #include "remoting/host/fake_mouse_cursor_monitor.h" |
| #include "remoting/host/host_mock_objects.h" |
| #include "remoting/host/mojom/desktop_session.mojom.h" |
| #include "remoting/host/remote_open_url/fake_url_forwarder_configurator.h" |
| #include "remoting/protocol/fake_desktop_capturer.h" |
| #include "remoting/protocol/protocol_mock_objects.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using testing::_; |
| using testing::AnyNumber; |
| using testing::AtMost; |
| using testing::ByMove; |
| using testing::InSequence; |
| using testing::Return; |
| |
| namespace remoting { |
| |
| namespace { |
| |
| class MockDaemonListener : public IPC::Listener, |
| public mojom::DesktopSessionRequestHandler { |
| public: |
| MockDaemonListener() = default; |
| |
| MockDaemonListener(const MockDaemonListener&) = delete; |
| MockDaemonListener& operator=(const MockDaemonListener&) = delete; |
| |
| ~MockDaemonListener() override = default; |
| |
| bool OnMessageReceived(const IPC::Message& message) override; |
| void OnAssociatedInterfaceRequest( |
| const std::string& interface_name, |
| mojo::ScopedInterfaceEndpointHandle handle) override; |
| |
| MOCK_METHOD(void, |
| ConnectDesktopChannel, |
| (mojo::ScopedMessagePipeHandle handle), |
| (override)); |
| MOCK_METHOD(void, InjectSecureAttentionSequence, (), (override)); |
| MOCK_METHOD(void, CrashNetworkProcess, (), (override)); |
| MOCK_METHOD(void, OnChannelConnected, (int32_t), (override)); |
| MOCK_METHOD(void, OnChannelError, (), (override)); |
| |
| void Disconnect(); |
| |
| private: |
| mojo::AssociatedReceiver<mojom::DesktopSessionRequestHandler> |
| desktop_session_request_handler_{this}; |
| }; |
| |
| class MockNetworkListener : public IPC::Listener { |
| public: |
| MockNetworkListener() = default; |
| |
| MockNetworkListener(const MockNetworkListener&) = delete; |
| MockNetworkListener& operator=(const MockNetworkListener&) = delete; |
| |
| ~MockNetworkListener() override = default; |
| |
| bool OnMessageReceived(const IPC::Message& message) override; |
| |
| MOCK_METHOD(void, OnChannelConnected, (int32_t), (override)); |
| MOCK_METHOD(void, OnChannelError, (), (override)); |
| |
| MOCK_METHOD0(OnDesktopEnvironmentCreated, void()); |
| }; |
| |
| bool MockDaemonListener::OnMessageReceived(const IPC::Message& message) { |
| ADD_FAILURE() << "Unexpected call to OnMessageReceived()"; |
| return false; |
| } |
| |
| void MockDaemonListener::OnAssociatedInterfaceRequest( |
| const std::string& interface_name, |
| mojo::ScopedInterfaceEndpointHandle handle) { |
| EXPECT_EQ(mojom::DesktopSessionRequestHandler::Name_, interface_name); |
| mojo::PendingAssociatedReceiver<mojom::DesktopSessionRequestHandler> |
| pending_receiver(std::move(handle)); |
| desktop_session_request_handler_.Bind(std::move(pending_receiver)); |
| } |
| |
| void MockDaemonListener::Disconnect() { |
| desktop_session_request_handler_.reset(); |
| } |
| |
| bool MockNetworkListener::OnMessageReceived(const IPC::Message& message) { |
| bool handled = true; |
| |
| // TODO(alexeypa): handle received messages here. |
| |
| EXPECT_TRUE(handled); |
| return handled; |
| } |
| |
| } // namespace |
| |
| class DesktopProcessTest : public testing::Test { |
| public: |
| DesktopProcessTest(); |
| ~DesktopProcessTest() override; |
| |
| // Methods invoked when MockDaemonListener::ConnectDesktopChannel is called. |
| void CreateNetworkChannel(mojo::ScopedMessagePipeHandle desktop_pipe); |
| void StoreDesktopHandle(mojo::ScopedMessagePipeHandle desktop_pipe); |
| |
| // Creates a DesktopEnvironment with a fake webrtc::DesktopCapturer, to mock |
| // DesktopEnvironmentFactory::Create(). |
| std::unique_ptr<DesktopEnvironment> CreateDesktopEnvironment(); |
| |
| // Creates a fake InputInjector, to mock |
| // DesktopEnvironment::CreateInputInjector(). |
| std::unique_ptr<InputInjector> CreateInputInjector(); |
| |
| // Disconnects the daemon-to-desktop channel causing the desktop process to |
| // exit. |
| void DisconnectChannels(); |
| |
| // Posts DisconnectChannels() to |message_loop_|. |
| void PostDisconnectChannels(); |
| |
| // Runs the desktop process code in a separate thread. |
| void RunDesktopProcess(); |
| |
| // Creates the desktop process and sends a crash request to it. |
| void RunDeathTest(); |
| |
| // Sends a crash request to the desktop process. |
| void SendCrashRequest(); |
| |
| // Requests the desktop process to start the desktop session agent. |
| void SendStartSessionAgent(); |
| |
| // Receives the DesktopSessionControl remote used to inject input and control |
| // A/V capture in the test. |
| void OnDesktopSessionAgentStarted( |
| mojo::PendingAssociatedRemote<mojom::DesktopSessionControl> |
| pending_remote); |
| |
| protected: |
| // The daemon's end of the daemon-to-desktop channel. |
| std::unique_ptr<IPC::ChannelProxy> daemon_channel_; |
| |
| // Delegate that is passed to |daemon_channel_|. |
| MockDaemonListener daemon_listener_; |
| |
| mojo::AssociatedRemote<mojom::DesktopSessionAgent> desktop_session_agent_; |
| mojo::AssociatedRemote<mojom::DesktopSessionControl> desktop_session_control_; |
| |
| // Runs the daemon's end of the channel. |
| base::test::SingleThreadTaskEnvironment task_environment_{ |
| base::test::SingleThreadTaskEnvironment::MainThreadType::UI}; |
| |
| scoped_refptr<AutoThreadTaskRunner> io_task_runner_; |
| |
| // The network's end of the network-to-desktop channel. |
| std::unique_ptr<IPC::ChannelProxy> network_channel_; |
| |
| // Delegate that is passed to |network_channel_|. |
| MockNetworkListener network_listener_; |
| |
| mojo::ScopedMessagePipeHandle desktop_pipe_handle_; |
| }; |
| |
| DesktopProcessTest::DesktopProcessTest() = default; |
| |
| DesktopProcessTest::~DesktopProcessTest() = default; |
| |
| void DesktopProcessTest::CreateNetworkChannel( |
| mojo::ScopedMessagePipeHandle desktop_pipe) { |
| network_channel_ = IPC::ChannelProxy::Create( |
| desktop_pipe.release(), IPC::Channel::MODE_CLIENT, &network_listener_, |
| io_task_runner_.get(), base::ThreadTaskRunnerHandle::Get()); |
| } |
| |
| void DesktopProcessTest::StoreDesktopHandle( |
| mojo::ScopedMessagePipeHandle desktop_pipe) { |
| desktop_pipe_handle_ = std::move(desktop_pipe); |
| } |
| |
| std::unique_ptr<DesktopEnvironment> |
| DesktopProcessTest::CreateDesktopEnvironment() { |
| auto desktop_environment = std::make_unique<MockDesktopEnvironment>(); |
| EXPECT_CALL(*desktop_environment, CreateAudioCapturer()).Times(0); |
| EXPECT_CALL(*desktop_environment, CreateInputInjector()) |
| .Times(AtMost(1)) |
| .WillOnce(Invoke(this, &DesktopProcessTest::CreateInputInjector)); |
| EXPECT_CALL(*desktop_environment, CreateActionExecutor()).Times(AtMost(1)); |
| EXPECT_CALL(*desktop_environment, CreateScreenControls()).Times(AtMost(1)); |
| EXPECT_CALL(*desktop_environment, CreateVideoCapturer(_)) |
| .Times(AtMost(1)) |
| .WillOnce( |
| Return(ByMove(std::make_unique<protocol::FakeDesktopCapturer>()))); |
| EXPECT_CALL(*desktop_environment, CreateMouseCursorMonitor()) |
| .Times(AtMost(1)) |
| .WillOnce(Return(ByMove(std::make_unique<FakeMouseCursorMonitor>()))); |
| EXPECT_CALL(*desktop_environment, CreateKeyboardLayoutMonitor(_)) |
| .Times(AtMost(1)) |
| .WillOnce(Return(ByMove(std::make_unique<FakeKeyboardLayoutMonitor>()))); |
| EXPECT_CALL(*desktop_environment, CreateUrlForwarderConfigurator()) |
| .Times(AtMost(1)) |
| .WillOnce( |
| Return(ByMove(std::make_unique<FakeUrlForwarderConfigurator>()))); |
| EXPECT_CALL(*desktop_environment, CreateFileOperations()).Times(AtMost(1)); |
| EXPECT_CALL(*desktop_environment, GetCapabilities()) |
| .Times(AtMost(1)); |
| EXPECT_CALL(*desktop_environment, SetCapabilities(_)) |
| .Times(AtMost(1)); |
| |
| // Notify the test that the desktop environment has been created. |
| network_listener_.OnDesktopEnvironmentCreated(); |
| return desktop_environment; |
| } |
| |
| std::unique_ptr<InputInjector> DesktopProcessTest::CreateInputInjector() { |
| auto input_injector = std::make_unique<MockInputInjector>(); |
| EXPECT_CALL(*input_injector, Start(_)); |
| return input_injector; |
| } |
| |
| void DesktopProcessTest::DisconnectChannels() { |
| daemon_channel_.reset(); |
| desktop_pipe_handle_.reset(); |
| daemon_listener_.Disconnect(); |
| desktop_session_agent_.reset(); |
| desktop_session_control_.reset(); |
| |
| network_channel_.reset(); |
| io_task_runner_ = nullptr; |
| } |
| |
| void DesktopProcessTest::PostDisconnectChannels() { |
| task_environment_.GetMainThreadTaskRunner()->PostTask( |
| FROM_HERE, base::BindOnce(&DesktopProcessTest::DisconnectChannels, |
| base::Unretained(this))); |
| } |
| |
| void DesktopProcessTest::RunDesktopProcess() { |
| base::RunLoop run_loop; |
| base::OnceClosure quit_ui_task_runner = base::BindOnce( |
| base::IgnoreResult(&base::SingleThreadTaskRunner::PostTask), |
| task_environment_.GetMainThreadTaskRunner(), FROM_HERE, |
| run_loop.QuitClosure()); |
| scoped_refptr<AutoThreadTaskRunner> ui_task_runner = |
| new AutoThreadTaskRunner(task_environment_.GetMainThreadTaskRunner(), |
| std::move(quit_ui_task_runner)); |
| |
| io_task_runner_ = AutoThread::CreateWithType("IPC thread", ui_task_runner, |
| base::MessagePumpType::IO); |
| |
| mojo::MessagePipe pipe; |
| daemon_channel_ = IPC::ChannelProxy::Create( |
| pipe.handle0.release(), IPC::Channel::MODE_SERVER, &daemon_listener_, |
| io_task_runner_.get(), base::ThreadTaskRunnerHandle::Get()); |
| |
| std::unique_ptr<MockDesktopEnvironmentFactory> desktop_environment_factory( |
| new MockDesktopEnvironmentFactory()); |
| EXPECT_CALL(*desktop_environment_factory, Create(_, _, _)) |
| .Times(AnyNumber()) |
| .WillRepeatedly( |
| Invoke(this, &DesktopProcessTest::CreateDesktopEnvironment)); |
| EXPECT_CALL(*desktop_environment_factory, SupportsAudioCapture()) |
| .Times(AnyNumber()) |
| .WillRepeatedly(Return(false)); |
| |
| DesktopProcess desktop_process(ui_task_runner, io_task_runner_, |
| io_task_runner_, std::move(pipe.handle1)); |
| EXPECT_TRUE(desktop_process.Start(std::move(desktop_environment_factory))); |
| |
| ui_task_runner = nullptr; |
| run_loop.Run(); |
| } |
| |
| void DesktopProcessTest::RunDeathTest() { |
| InSequence s; |
| EXPECT_CALL(daemon_listener_, OnChannelConnected(_)); |
| EXPECT_CALL(daemon_listener_, ConnectDesktopChannel(_)) |
| .WillOnce([&](mojo::ScopedMessagePipeHandle desktop_pipe) { |
| StoreDesktopHandle(std::move(desktop_pipe)); |
| SendCrashRequest(); |
| }); |
| |
| RunDesktopProcess(); |
| } |
| |
| void DesktopProcessTest::SendCrashRequest() { |
| base::Location location = FROM_HERE; |
| daemon_channel_->Send(new ChromotingDaemonMsg_Crash( |
| location.function_name(), location.file_name(), location.line_number())); |
| } |
| |
| void DesktopProcessTest::SendStartSessionAgent() { |
| desktop_session_agent_.reset(); |
| network_channel_->GetRemoteAssociatedInterface(&desktop_session_agent_); |
| |
| desktop_session_agent_->Start( |
| "user@domain/rest-of-jid", ScreenResolution(), |
| DesktopEnvironmentOptions(), |
| base::BindOnce(&DesktopProcessTest::OnDesktopSessionAgentStarted, |
| base::Unretained(this))); |
| task_environment_.RunUntilIdle(); |
| } |
| |
| void DesktopProcessTest::OnDesktopSessionAgentStarted( |
| mojo::PendingAssociatedRemote<mojom::DesktopSessionControl> |
| pending_remote) { |
| desktop_session_control_.reset(); |
| desktop_session_control_.Bind(std::move(pending_remote)); |
| } |
| |
| // Launches the desktop process and then disconnects immediately. |
| TEST_F(DesktopProcessTest, Basic) { |
| InSequence s; |
| EXPECT_CALL(daemon_listener_, OnChannelConnected(_)); |
| EXPECT_CALL(daemon_listener_, ConnectDesktopChannel(_)) |
| .WillOnce([&](mojo::ScopedMessagePipeHandle desktop_pipe) { |
| StoreDesktopHandle(std::move(desktop_pipe)); |
| DisconnectChannels(); |
| }); |
| |
| RunDesktopProcess(); |
| } |
| |
| // Launches the desktop process and waits until the IPC channel is established. |
| TEST_F(DesktopProcessTest, CreateNetworkChannel) { |
| InSequence s; |
| EXPECT_CALL(daemon_listener_, OnChannelConnected(_)); |
| EXPECT_CALL(daemon_listener_, ConnectDesktopChannel(_)) |
| .WillOnce([&](mojo::ScopedMessagePipeHandle desktop_pipe) { |
| CreateNetworkChannel(std::move(desktop_pipe)); |
| }); |
| EXPECT_CALL(network_listener_, OnChannelConnected(_)) |
| .WillOnce(InvokeWithoutArgs( |
| this, &DesktopProcessTest::DisconnectChannels)); |
| |
| RunDesktopProcess(); |
| } |
| |
| // Launches the desktop process, waits until the IPC channel is established, |
| // then starts the desktop session agent. |
| TEST_F(DesktopProcessTest, StartSessionAgent) { |
| { |
| InSequence s; |
| EXPECT_CALL(daemon_listener_, OnChannelConnected(_)); |
| EXPECT_CALL(daemon_listener_, ConnectDesktopChannel(_)) |
| .WillOnce([&](mojo::ScopedMessagePipeHandle desktop_pipe) { |
| CreateNetworkChannel(std::move(desktop_pipe)); |
| }); |
| EXPECT_CALL(network_listener_, OnChannelConnected(_)) |
| .WillOnce(InvokeWithoutArgs( |
| this, &DesktopProcessTest::SendStartSessionAgent)); |
| } |
| |
| EXPECT_CALL(network_listener_, OnDesktopEnvironmentCreated()) |
| .WillOnce(InvokeWithoutArgs( |
| this, &DesktopProcessTest::PostDisconnectChannels)); |
| |
| RunDesktopProcess(); |
| } |
| |
| // Run the desktop process and ask it to crash. |
| TEST_F(DesktopProcessTest, DeathTest) { |
| testing::GTEST_FLAG(death_test_style) = "threadsafe"; |
| |
| EXPECT_DEATH(RunDeathTest(), ""); |
| } |
| |
| } // namespace remoting |