chromium/remoting/host/security_key/security_key_ipc_client_unittest.cc

// Copyright 2016 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/security_key/security_key_ipc_client.h"

#include <memory>
#include <string>

#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/platform/named_platform_channel.h"
#include "mojo/public/cpp/system/message_pipe.h"
#include "remoting/host/host_mock_objects.h"
#include "remoting/host/mojom/remote_security_key.mojom.h"
#include "remoting/host/security_key/security_key_ipc_constants.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {
const int kLargeMessageSizeBytes = 256 * 1024;

using testing::_;
using testing::Return;
}  // namespace

namespace remoting {

class SecurityKeyIpcClientTest : public testing::Test,
                                 public mojom::SecurityKeyForwarder {
 public:
  SecurityKeyIpcClientTest();

  SecurityKeyIpcClientTest(const SecurityKeyIpcClientTest&) = delete;
  SecurityKeyIpcClientTest& operator=(const SecurityKeyIpcClientTest&) = delete;

  ~SecurityKeyIpcClientTest() override;

  // mojom::SecurityKeyForwarder interface.
  void OnSecurityKeyRequest(const std::string& request_data,
                            OnSecurityKeyRequestCallback callback) override;

  // Passed to the object used for testing to be called back to signal
  // completion of an IPC channel state change or reception of an IPC message.
  void OperationComplete(bool failed);

  // Callback used to signal when the IPC channel is ready for messages.
  void ConnectionStateHandler();

  // Used as a callback given to the object under test, expected to be called
  // back when a security key response is sent.
  void ClientMessageReceived(const std::string& response_payload);

 protected:
  // Waits until the current |run_loop_| instance is signaled, then resets it.
  void WaitForOperationComplete();

  // Sets up an active IPC connection between |security_key_ipc_client_|
  // and |fake_ipc_server_|. |expect_error| defines whether the the error
  // callback should be invoked during the connection process.
  void EstablishConnection(bool expect_error = false);

  // Sends a security key request from |security_key_ipc_client_| and
  // a response from |fake_ipc_server_| and verifies the payloads for both.
  void SendRequestAndResponse(const std::string& request_data,
                              const std::string& response_data);

  // IPC tests require a valid MessageLoop to run.
  base::test::SingleThreadTaskEnvironment task_environment_{
      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};

  // Used to allow |message_loop_| to run during tests.  The instance is reset
  // after each stage of the tests has been completed.
  std::unique_ptr<base::RunLoop> run_loop_;

  raw_ptr<MockChromotingHostServicesProvider, DanglingUntriaged> api_provider_;

  // The object under test.
  std::unique_ptr<SecurityKeyIpcClient> security_key_ipc_client_;

  MockChromotingSessionServices mock_api_;

  mojo::Receiver<mojom::SecurityKeyForwarder> receiver_{this};

  // Stores the current session ID on supported platforms.
  uint32_t session_id_ = 0;

  // Tracks the success/failure of the last async operation.
  bool operation_failed_ = false;

  // Tracks whether the IPC channel connection has been established.
  bool connection_established_ = false;

  // Used to drive invalid session behavior for testing the client.
  bool simulate_invalid_session_ = false;

  // Stores the contents of the last IPC message received for validation.
  std::string last_message_received_;

  OnSecurityKeyRequestCallback response_callback_;
};

SecurityKeyIpcClientTest::SecurityKeyIpcClientTest()
    : run_loop_(new base::RunLoop()) {
  auto api_provider = std::make_unique<MockChromotingHostServicesProvider>();
  api_provider_ = api_provider.get();
  security_key_ipc_client_ =
      base::WrapUnique(new SecurityKeyIpcClient(std::move(api_provider)));
}

SecurityKeyIpcClientTest::~SecurityKeyIpcClientTest() = default;

void SecurityKeyIpcClientTest::OnSecurityKeyRequest(
    const std::string& request_data,
    OnSecurityKeyRequestCallback callback) {
  last_message_received_ = request_data;
  response_callback_ = std::move(callback);
  OperationComplete(/*failed=*/false);
}

void SecurityKeyIpcClientTest::OperationComplete(bool failed) {
  operation_failed_ |= failed;
  run_loop_->Quit();
}

void SecurityKeyIpcClientTest::ConnectionStateHandler() {
  connection_established_ = true;
  OperationComplete(/*failed=*/false);
}

void SecurityKeyIpcClientTest::WaitForOperationComplete() {
  run_loop_->Run();
  run_loop_ = std::make_unique<base::RunLoop>();

  // Run until there are no pending work items in the queue.
  base::RunLoop().RunUntilIdle();
}

void SecurityKeyIpcClientTest::ClientMessageReceived(
    const std::string& response_payload) {
  last_message_received_ = response_payload;
  OperationComplete(/*failed=*/false);
}

void SecurityKeyIpcClientTest::EstablishConnection(bool expect_error) {
  EXPECT_CALL(*api_provider_, GetSessionServices())
      .WillRepeatedly(Return(&mock_api_));

  EXPECT_CALL(mock_api_, BindSecurityKeyForwarder(_))
      .WillOnce([&](mojo::PendingReceiver<mojom::SecurityKeyForwarder>
                        pending_receiver) {
        if (!simulate_invalid_session_) {
          receiver_.Bind(std::move(pending_receiver));
        }
        // Otherwise drop the receiver.
      });

  ASSERT_TRUE(security_key_ipc_client_->CheckForSecurityKeyIpcServerChannel());

  // Establish the IPC channel so we can begin sending and receiving security
  // key messages.
  security_key_ipc_client_->EstablishIpcConnection(
      base::BindOnce(&SecurityKeyIpcClientTest::ConnectionStateHandler,
                     base::Unretained(this)),
      base::BindOnce(&SecurityKeyIpcClientTest::OperationComplete,
                     base::Unretained(this), /*failed=*/true));
  WaitForOperationComplete();

  ASSERT_EQ(!expect_error, connection_established_);
  ASSERT_EQ(expect_error, operation_failed_);
}

void SecurityKeyIpcClientTest::SendRequestAndResponse(
    const std::string& request_data,
    const std::string& response_data) {
  ASSERT_TRUE(security_key_ipc_client_->SendSecurityKeyRequest(
      request_data,
      base::BindRepeating(&SecurityKeyIpcClientTest::ClientMessageReceived,
                          base::Unretained(this))));
  WaitForOperationComplete();

  ASSERT_FALSE(operation_failed_);
  ASSERT_EQ(request_data, last_message_received_);

  std::move(response_callback_).Run(response_data);
  WaitForOperationComplete();

  ASSERT_FALSE(operation_failed_);
  ASSERT_EQ(response_data, last_message_received_);
}

TEST_F(SecurityKeyIpcClientTest, GenerateSingleSecurityKeyRequest) {
  EstablishConnection();

  SendRequestAndResponse("Auth me!", "You've been authed!");

  security_key_ipc_client_->CloseIpcConnection();
}

TEST_F(SecurityKeyIpcClientTest, GenerateLargeSecurityKeyRequest) {
  EstablishConnection();

  SendRequestAndResponse(std::string(kLargeMessageSizeBytes, 'Y'),
                         std::string(kLargeMessageSizeBytes, 'Z'));

  security_key_ipc_client_->CloseIpcConnection();
}

TEST_F(SecurityKeyIpcClientTest, GenerateReallyLargeSecurityKeyRequest) {
  EstablishConnection();

  SendRequestAndResponse(std::string(kLargeMessageSizeBytes * 2, 'Y'),
                         std::string(kLargeMessageSizeBytes * 2, 'Z'));

  security_key_ipc_client_->CloseIpcConnection();
}

TEST_F(SecurityKeyIpcClientTest, GenerateMultipleSecurityKeyRequest) {
  EstablishConnection();

  SendRequestAndResponse("Auth me 1!", "You've been authed once!");
  SendRequestAndResponse("Auth me 2!", "You've been authed twice!");
  SendRequestAndResponse("Auth me 3!", "You've been authed thrice!");

  security_key_ipc_client_->CloseIpcConnection();
}

TEST_F(SecurityKeyIpcClientTest, ServerClosesConnectionAfterRequestTimeout) {
  EstablishConnection();
  receiver_.reset();
  WaitForOperationComplete();
  ASSERT_TRUE(operation_failed_);
}

TEST_F(SecurityKeyIpcClientTest,
       SecondSecurityKeyRequestBeforeFirstResponseReceived) {
  EstablishConnection();

  ASSERT_TRUE(security_key_ipc_client_->SendSecurityKeyRequest(
      "First Request",
      base::BindRepeating(&SecurityKeyIpcClientTest::ClientMessageReceived,
                          base::Unretained(this))));
  WaitForOperationComplete();
  ASSERT_FALSE(operation_failed_);

  ASSERT_FALSE(security_key_ipc_client_->SendSecurityKeyRequest(
      "Second Request",
      base::BindRepeating(&SecurityKeyIpcClientTest::ClientMessageReceived,
                          base::Unretained(this))));

  // This is the response callback for the first request. Mojo callbacks must be
  // run before they are dropped.
  std::move(response_callback_).Run("");
}

TEST_F(SecurityKeyIpcClientTest, ReceiveSecurityKeyResponseWithEmptyPayload) {
  EstablishConnection();

  ASSERT_TRUE(security_key_ipc_client_->SendSecurityKeyRequest(
      "Valid request",
      base::BindRepeating(&SecurityKeyIpcClientTest::ClientMessageReceived,
                          base::Unretained(this))));
  WaitForOperationComplete();
  ASSERT_FALSE(operation_failed_);

  std::move(response_callback_).Run("");
  WaitForOperationComplete();
  ASSERT_TRUE(operation_failed_);
}

TEST_F(SecurityKeyIpcClientTest, SendRequestBeforeEstablishingConnection) {
  // Sending a request will fail since the IPC connection has not been
  // established.
  ASSERT_FALSE(security_key_ipc_client_->SendSecurityKeyRequest(
      "Too soon!!",
      base::BindRepeating(&SecurityKeyIpcClientTest::ClientMessageReceived,
                          base::Unretained(this))));
}

TEST_F(SecurityKeyIpcClientTest, NonExistentIpcServerChannel) {
  EXPECT_CALL(*api_provider_, GetSessionServices())
      .WillRepeatedly(Return(nullptr));

  // Attempt to establish the connection (should fail since the IPC channel does
  // not exist).
  security_key_ipc_client_->EstablishIpcConnection(
      base::BindOnce(&SecurityKeyIpcClientTest::ConnectionStateHandler,
                     base::Unretained(this)),
      base::BindOnce(&SecurityKeyIpcClientTest::OperationComplete,
                     base::Unretained(this), /*failed=*/true));
  ASSERT_TRUE(operation_failed_);
}

TEST_F(SecurityKeyIpcClientTest, SecurityKeyIpcClientRunningInWrongSession) {
  // Attempting to establish a connection should fail here since the IPC Client
  // is 'running' in the non-remoted session.
  simulate_invalid_session_ = true;
  EstablishConnection(/*expect_error=*/true);
}

}  // namespace remoting