// Copyright 2018 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 <memory>

#include "base/bind.h"
#include "base/callback.h"
#include "base/containers/flat_map.h"
#include "base/memory/ptr_util.h"
#include "base/optional.h"
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "base/test/test_simple_task_runner.h"
#include "chromeos/services/secure_channel/active_connection_manager_impl.h"
#include "chromeos/services/secure_channel/ble_connection_manager_impl.h"
#include "chromeos/services/secure_channel/ble_service_data_helper_impl.h"
#include "chromeos/services/secure_channel/client_connection_parameters_impl.h"
#include "chromeos/services/secure_channel/fake_active_connection_manager.h"
#include "chromeos/services/secure_channel/fake_authenticated_channel.h"
#include "chromeos/services/secure_channel/fake_ble_connection_manager.h"
#include "chromeos/services/secure_channel/fake_ble_service_data_helper.h"
#include "chromeos/services/secure_channel/fake_client_connection_parameters.h"
#include "chromeos/services/secure_channel/fake_connection_delegate.h"
#include "chromeos/services/secure_channel/fake_pending_connection_manager.h"
#include "chromeos/services/secure_channel/fake_timer_factory.h"
#include "chromeos/services/secure_channel/pending_connection_manager_impl.h"
#include "chromeos/services/secure_channel/public/cpp/shared/connection_priority.h"
#include "chromeos/services/secure_channel/public/mojom/constants.mojom.h"
#include "chromeos/services/secure_channel/public/mojom/secure_channel.mojom.h"
#include "chromeos/services/secure_channel/secure_channel_initializer.h"
#include "chromeos/services/secure_channel/secure_channel_service.h"
#include "chromeos/services/secure_channel/timer_factory_impl.h"
#include "components/cryptauth/remote_device_cache.h"
#include "components/cryptauth/remote_device_test_util.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "services/service_manager/public/cpp/test/test_connector_factory.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace chromeos {

namespace secure_channel {

namespace {

const size_t kNumTestDevices = 6;

class FakeTimerFactoryFactory : public TimerFactoryImpl::Factory {
 public:
  FakeTimerFactoryFactory() = default;
  ~FakeTimerFactoryFactory() override = default;

  FakeTimerFactory* instance() { return instance_; }

 private:
  // TimerFactoryImpl::Factory:
  std::unique_ptr<TimerFactory> BuildInstance() override {
    EXPECT_FALSE(instance_);
    auto instance = std::make_unique<FakeTimerFactory>();
    instance_ = instance.get();
    return instance;
  }

  FakeTimerFactory* instance_ = nullptr;

  DISALLOW_COPY_AND_ASSIGN(FakeTimerFactoryFactory);
};

class TestRemoteDeviceCacheFactory
    : public cryptauth::RemoteDeviceCache::Factory {
 public:
  TestRemoteDeviceCacheFactory() = default;
  ~TestRemoteDeviceCacheFactory() override = default;

  cryptauth::RemoteDeviceCache* instance() { return instance_; }

 private:
  // cryptauth::RemoteDeviceCache::Factory:
  std::unique_ptr<cryptauth::RemoteDeviceCache> BuildInstance() override {
    EXPECT_FALSE(instance_);
    auto instance = cryptauth::RemoteDeviceCache::Factory::BuildInstance();
    instance_ = instance.get();
    return instance;
  }

  cryptauth::RemoteDeviceCache* instance_ = nullptr;

  DISALLOW_COPY_AND_ASSIGN(TestRemoteDeviceCacheFactory);
};

class FakeBleServiceDataHelperFactory
    : public BleServiceDataHelperImpl::Factory {
 public:
  FakeBleServiceDataHelperFactory(
      TestRemoteDeviceCacheFactory* test_remote_device_cache_factory)
      : test_remote_device_cache_factory_(test_remote_device_cache_factory) {}

  ~FakeBleServiceDataHelperFactory() override = default;

  FakeBleServiceDataHelper* instance() { return instance_; }

 private:
  // BleServiceDataHelperImpl::Factory:
  std::unique_ptr<BleServiceDataHelper> BuildInstance(
      cryptauth::RemoteDeviceCache* remote_device_cache) override {
    EXPECT_FALSE(instance_);
    EXPECT_EQ(test_remote_device_cache_factory_->instance(),
              remote_device_cache);

    auto instance = std::make_unique<FakeBleServiceDataHelper>();
    instance_ = instance.get();
    return instance;
  }

  TestRemoteDeviceCacheFactory* test_remote_device_cache_factory_;

  FakeBleServiceDataHelper* instance_ = nullptr;

  DISALLOW_COPY_AND_ASSIGN(FakeBleServiceDataHelperFactory);
};

class FakeBleConnectionManagerFactory
    : public BleConnectionManagerImpl::Factory {
 public:
  FakeBleConnectionManagerFactory(
      device::BluetoothAdapter* expected_bluetooth_adapter,
      FakeBleServiceDataHelperFactory* fake_ble_service_data_helper_factory,
      FakeTimerFactoryFactory* fake_timer_factory_factory)
      : expected_bluetooth_adapter_(expected_bluetooth_adapter),
        fake_ble_service_data_helper_factory_(
            fake_ble_service_data_helper_factory),
        fake_timer_factory_factory_(fake_timer_factory_factory) {}

  ~FakeBleConnectionManagerFactory() override = default;

  FakeBleConnectionManager* instance() { return instance_; }

 private:
  // BleConnectionManagerImpl::Factory:
  std::unique_ptr<BleConnectionManager> BuildInstance(
      scoped_refptr<device::BluetoothAdapter> bluetooth_adapter,
      BleServiceDataHelper* ble_service_data_helper,
      TimerFactory* timer_factory) override {
    EXPECT_FALSE(instance_);
    EXPECT_EQ(expected_bluetooth_adapter_, bluetooth_adapter.get());
    EXPECT_EQ(fake_ble_service_data_helper_factory_->instance(),
              ble_service_data_helper);
    EXPECT_EQ(fake_timer_factory_factory_->instance(), timer_factory);

    auto instance = std::make_unique<FakeBleConnectionManager>();
    instance_ = instance.get();
    return instance;
  }

  device::BluetoothAdapter* expected_bluetooth_adapter_;
  FakeBleServiceDataHelperFactory* fake_ble_service_data_helper_factory_;
  FakeTimerFactoryFactory* fake_timer_factory_factory_;

  FakeBleConnectionManager* instance_ = nullptr;

  DISALLOW_COPY_AND_ASSIGN(FakeBleConnectionManagerFactory);
};

class FakePendingConnectionManagerFactory
    : public PendingConnectionManagerImpl::Factory {
 public:
  FakePendingConnectionManagerFactory(
      FakeBleConnectionManagerFactory* fake_ble_connection_manager_factory)
      : fake_ble_connection_manager_factory_(
            fake_ble_connection_manager_factory) {}

  ~FakePendingConnectionManagerFactory() override = default;

  FakePendingConnectionManager* instance() { return instance_; }

 private:
  // PendingConnectionManagerImpl::Factory:
  std::unique_ptr<PendingConnectionManager> BuildInstance(
      PendingConnectionManager::Delegate* delegate,
      BleConnectionManager* ble_connection_manager) override {
    EXPECT_FALSE(instance_);
    EXPECT_EQ(fake_ble_connection_manager_factory_->instance(),
              ble_connection_manager);

    auto instance = std::make_unique<FakePendingConnectionManager>(delegate);
    instance_ = instance.get();
    return instance;
  }

  FakeBleConnectionManagerFactory* fake_ble_connection_manager_factory_;

  FakePendingConnectionManager* instance_ = nullptr;

  DISALLOW_COPY_AND_ASSIGN(FakePendingConnectionManagerFactory);
};

class FakeActiveConnectionManagerFactory
    : public ActiveConnectionManagerImpl::Factory {
 public:
  FakeActiveConnectionManagerFactory() = default;
  ~FakeActiveConnectionManagerFactory() override = default;

  FakeActiveConnectionManager* instance() { return instance_; }

 private:
  // ActiveConnectionManagerImpl::Factory:
  std::unique_ptr<ActiveConnectionManager> BuildInstance(
      ActiveConnectionManager::Delegate* delegate) override {
    EXPECT_FALSE(instance_);
    auto instance = std::make_unique<FakeActiveConnectionManager>(delegate);
    instance_ = instance.get();
    return instance;
  }

  FakeActiveConnectionManager* instance_ = nullptr;

  DISALLOW_COPY_AND_ASSIGN(FakeActiveConnectionManagerFactory);
};

class TestSecureChannelInitializerFactory
    : public SecureChannelInitializer::Factory {
 public:
  TestSecureChannelInitializerFactory(
      scoped_refptr<base::TestSimpleTaskRunner> test_task_runner)
      : test_task_runner_(test_task_runner) {}

  ~TestSecureChannelInitializerFactory() override = default;

 private:
  // SecureChannelInitializer::Factory:
  std::unique_ptr<SecureChannelBase> BuildInstance(
      scoped_refptr<base::TaskRunner> task_runner) override {
    EXPECT_FALSE(instance_);
    auto instance =
        SecureChannelInitializer::Factory::BuildInstance(test_task_runner_);
    instance_ = instance.get();
    return instance;
  }

  scoped_refptr<base::TestSimpleTaskRunner> test_task_runner_;

  SecureChannelBase* instance_ = nullptr;

  DISALLOW_COPY_AND_ASSIGN(TestSecureChannelInitializerFactory);
};

class FakeClientConnectionParametersFactory
    : public ClientConnectionParametersImpl::Factory {
 public:
  FakeClientConnectionParametersFactory() = default;
  ~FakeClientConnectionParametersFactory() override = default;

  const base::UnguessableToken& last_created_instance_id() {
    return last_created_instance_id_;
  }

  std::unordered_map<base::UnguessableToken,
                     FakeClientConnectionParameters*,
                     base::UnguessableTokenHash>&
  id_to_active_client_parameters_map() {
    return id_to_active_client_parameters_map_;
  }

  const std::unordered_map<
      base::UnguessableToken,
      base::Optional<mojom::ConnectionAttemptFailureReason>,
      base::UnguessableTokenHash>&
  id_to_failure_reason_when_deleted_map() {
    return id_to_failure_reason_when_deleted_map_;
  }

 private:
  // ClientConnectionParametersImpl::Factory:
  std::unique_ptr<ClientConnectionParameters> BuildInstance(
      const std::string& feature,
      mojom::ConnectionDelegatePtr connection_delegate_ptr) override {
    auto instance = std::make_unique<FakeClientConnectionParameters>(
        feature, base::BindOnce(
                     &FakeClientConnectionParametersFactory::OnInstanceDeleted,
                     base::Unretained(this)));
    last_created_instance_id_ = instance->id();
    id_to_active_client_parameters_map_[instance->id()] = instance.get();
    return instance;
  }

  void OnInstanceDeleted(const base::UnguessableToken& instance_id) {
    // Store failure reason before deleting.
    id_to_failure_reason_when_deleted_map_[instance_id] =
        id_to_active_client_parameters_map_[instance_id]->failure_reason();

    size_t num_deleted = id_to_active_client_parameters_map_.erase(instance_id);
    EXPECT_EQ(1u, num_deleted);
  }

  base::UnguessableToken last_created_instance_id_;

  std::unordered_map<base::UnguessableToken,
                     FakeClientConnectionParameters*,
                     base::UnguessableTokenHash>
      id_to_active_client_parameters_map_;

  std::unordered_map<base::UnguessableToken,
                     base::Optional<mojom::ConnectionAttemptFailureReason>,
                     base::UnguessableTokenHash>
      id_to_failure_reason_when_deleted_map_;

  DISALLOW_COPY_AND_ASSIGN(FakeClientConnectionParametersFactory);
};

}  // namespace

class SecureChannelServiceTest : public testing::Test {
 protected:
  SecureChannelServiceTest()
      : test_devices_(
            cryptauth::CreateRemoteDeviceListForTest(kNumTestDevices)) {}
  ~SecureChannelServiceTest() override = default;

  // testing::Test:
  void SetUp() override {
    mock_adapter_ =
        base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
    device::BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_);

    test_task_runner_ = base::MakeRefCounted<base::TestSimpleTaskRunner>();

    fake_timer_factory_factory_ = std::make_unique<FakeTimerFactoryFactory>();
    TimerFactoryImpl::Factory::SetFactoryForTesting(
        fake_timer_factory_factory_.get());

    test_remote_device_cache_factory_ =
        std::make_unique<TestRemoteDeviceCacheFactory>();
    cryptauth::RemoteDeviceCache::Factory::SetFactoryForTesting(
        test_remote_device_cache_factory_.get());

    fake_ble_service_data_helper_factory_ =
        std::make_unique<FakeBleServiceDataHelperFactory>(
            test_remote_device_cache_factory_.get());
    BleServiceDataHelperImpl::Factory::SetFactoryForTesting(
        fake_ble_service_data_helper_factory_.get());

    fake_ble_connection_manager_factory_ =
        std::make_unique<FakeBleConnectionManagerFactory>(
            mock_adapter_.get(), fake_ble_service_data_helper_factory_.get(),
            fake_timer_factory_factory_.get());
    BleConnectionManagerImpl::Factory::SetFactoryForTesting(
        fake_ble_connection_manager_factory_.get());

    fake_pending_connection_manager_factory_ =
        std::make_unique<FakePendingConnectionManagerFactory>(
            fake_ble_connection_manager_factory_.get());
    PendingConnectionManagerImpl::Factory::SetFactoryForTesting(
        fake_pending_connection_manager_factory_.get());

    fake_active_connection_manager_factory_ =
        std::make_unique<FakeActiveConnectionManagerFactory>();
    ActiveConnectionManagerImpl::Factory::SetFactoryForTesting(
        fake_active_connection_manager_factory_.get());

    test_secure_channel_initializer_factory_ =
        std::make_unique<TestSecureChannelInitializerFactory>(
            test_task_runner_);
    SecureChannelInitializer::Factory::SetFactoryForTesting(
        test_secure_channel_initializer_factory_.get());

    fake_client_connection_parameters_factory_ =
        std::make_unique<FakeClientConnectionParametersFactory>();
    ClientConnectionParametersImpl::Factory::SetFactoryForTesting(
        fake_client_connection_parameters_factory_.get());

    connector_factory_ =
        service_manager::TestConnectorFactory::CreateForUniqueService(
            std::make_unique<SecureChannelService>());

    auto connector = connector_factory_->CreateConnector();
    connector->BindInterface(mojom::kServiceName, &secure_channel_ptr_);
    secure_channel_ptr_.FlushForTesting();
  }

  void TearDown() override {
    TimerFactoryImpl::Factory::SetFactoryForTesting(nullptr);
    cryptauth::RemoteDeviceCache::Factory::SetFactoryForTesting(nullptr);
    BleServiceDataHelperImpl::Factory::SetFactoryForTesting(nullptr);
    BleConnectionManagerImpl::Factory::SetFactoryForTesting(nullptr);
    PendingConnectionManagerImpl::Factory::SetFactoryForTesting(nullptr);
    ActiveConnectionManagerImpl::Factory::SetFactoryForTesting(nullptr);
    SecureChannelInitializer::Factory::SetFactoryForTesting(nullptr);
    ClientConnectionParametersImpl::Factory::SetFactoryForTesting(nullptr);
  }

  void CallListenForConnectionFromDeviceAndVerifyInitializationNotComplete(
      const cryptauth::RemoteDevice& device_to_connect,
      const cryptauth::RemoteDevice& local_device,
      const std::string& feature,
      ConnectionPriority connection_priority) {
    AttemptConnectionPreInitialization(device_to_connect, local_device, feature,
                                       connection_priority,
                                       true /* is_listener */);
  }

  void CallInitiateConnectionToDeviceAndVerifyInitializationNotComplete(
      const cryptauth::RemoteDevice& device_to_connect,
      const cryptauth::RemoteDevice& local_device,
      const std::string& feature,
      ConnectionPriority connection_priority) {
    AttemptConnectionPreInitialization(device_to_connect, local_device, feature,
                                       connection_priority,
                                       false /* is_listener */);
  }

  void CallListenForConnectionFromDeviceAndVerifyRejection(
      const cryptauth::RemoteDevice& device_to_connect,
      const cryptauth::RemoteDevice& local_device,
      const std::string& feature,
      ConnectionPriority connection_priority,
      mojom::ConnectionAttemptFailureReason expected_failure_reason) {
    AttemptConnectionAndVerifyRejection(
        device_to_connect, local_device, feature, connection_priority,
        expected_failure_reason, true /* is_listener */);
  }

  void CallInitiateConnectionToDeviceAndVerifyRejection(
      const cryptauth::RemoteDevice& device_to_connect,
      const cryptauth::RemoteDevice& local_device,
      const std::string& feature,
      ConnectionPriority connection_priority,
      mojom::ConnectionAttemptFailureReason expected_failure_reason) {
    AttemptConnectionAndVerifyRejection(
        device_to_connect, local_device, feature, connection_priority,
        expected_failure_reason, false /* is_listener */);
  }

  void CallListenForConnectionFromDeviceAndVerifyPendingConnection(
      const cryptauth::RemoteDevice& device_to_connect,
      const cryptauth::RemoteDevice& local_device,
      const std::string& feature,
      ConnectionPriority connection_priority) {
    AttemptConnectionAndVerifyPendingConnection(device_to_connect, local_device,
                                                feature, connection_priority,
                                                true /* is_listener */);
  }

  void CallInitiateConnectionToDeviceAndVerifyPendingConnection(
      const cryptauth::RemoteDevice& device_to_connect,
      const cryptauth::RemoteDevice& local_device,
      const std::string& feature,
      ConnectionPriority connection_priority) {
    AttemptConnectionAndVerifyPendingConnection(device_to_connect, local_device,
                                                feature, connection_priority,
                                                false /* is_listener */);
  }

  void CallListenForConnectionFromDeviceAndVerifyActiveConnection(
      const cryptauth::RemoteDevice& device_to_connect,
      const cryptauth::RemoteDevice& local_device,
      const std::string& feature,
      ConnectionPriority connection_priority) {
    AttemptConnectionAndVerifyActiveConnection(device_to_connect, local_device,
                                               feature, connection_priority,
                                               true /* is_listener */);
  }

  void CallInitiateConnectionToDeviceAndVerifyActiveConnection(
      const cryptauth::RemoteDevice& device_to_connect,
      const cryptauth::RemoteDevice& local_device,
      const std::string& feature,
      ConnectionPriority connection_priority) {
    AttemptConnectionAndVerifyActiveConnection(device_to_connect, local_device,
                                               feature, connection_priority,
                                               false /* is_listener */);
  }

  base::UnguessableToken
  CallListenForConnectionFromDeviceAndVerifyStillDisconnecting(
      const cryptauth::RemoteDevice& device_to_connect,
      const cryptauth::RemoteDevice& local_device,
      const std::string& feature,
      ConnectionPriority connection_priority) {
    return AttemptConnectionAndVerifyStillDisconnecting(
        device_to_connect, local_device, feature, connection_priority,
        true /* is_listener */);
  }

  base::UnguessableToken
  CallInitiateConnectionToDeviceAndVerifyStillDisconnecting(
      const cryptauth::RemoteDevice& device_to_connect,
      const cryptauth::RemoteDevice& local_device,
      const std::string& feature,
      ConnectionPriority connection_priority) {
    return AttemptConnectionAndVerifyStillDisconnecting(
        device_to_connect, local_device, feature, connection_priority,
        false /* is_listener */);
  }

  void SimulateSuccessfulConnection(const std::string& device_id) {
    ConnectionDetails connection_details(device_id,
                                         ConnectionMedium::kBluetoothLowEnergy);

    auto fake_authenticated_channel =
        std::make_unique<FakeAuthenticatedChannel>();
    auto* fake_authenticated_channel_raw = fake_authenticated_channel.get();

    std::vector<ClientConnectionParameters*> moved_client_list =
        fake_pending_connection_manager()->NotifyConnectionForHandledRequests(
            std::move(fake_authenticated_channel), connection_details);

    // Now, verify that ActiveConnectionManager has received the moved data.
    const auto& metadata = fake_active_connection_manager()
                               ->connection_details_to_active_metadata_map()
                               .find(connection_details)
                               ->second;
    EXPECT_EQ(ActiveConnectionManager::ConnectionState::kActiveConnectionExists,
              std::get<0>(metadata));
    EXPECT_EQ(fake_authenticated_channel_raw, std::get<1>(metadata).get());
    for (size_t i = 0; i < moved_client_list.size(); ++i)
      EXPECT_EQ(moved_client_list[i], std::get<2>(metadata)[i].get());
  }

  void SimulateConnectionStartingDisconnecting(const std::string& device_id) {
    fake_active_connection_manager()->SetDisconnecting(
        ConnectionDetails(device_id, ConnectionMedium::kBluetoothLowEnergy));
  }

  void SimulateConnectionBecomingDisconnected(const std::string& device_id) {
    ConnectionDetails connection_details(device_id,
                                         ConnectionMedium::kBluetoothLowEnergy);

    // If the connection was previously disconnected, there may have been
    // pending metadata corresponding to any connection attempts which were
    // triggered while the previous connection was in the disconnecting state.
    std::vector<std::tuple<base::UnguessableToken, std::string, ConnectionRole,
                           ConnectionPriority>>
        pending_metadata_list;

    auto it = disconnecting_details_to_requests_map_.find(connection_details);
    if (it != disconnecting_details_to_requests_map_.end()) {
      pending_metadata_list = it->second;
      disconnecting_details_to_requests_map_.erase(it);
    }

    fake_active_connection_manager()->SetDisconnected(
        ConnectionDetails(device_id, ConnectionMedium::kBluetoothLowEnergy));

    // If there were no pending metadata, there is no need to make additional
    // verifications.
    if (pending_metadata_list.empty())
      return;

    size_t num_handled_requests_start_index =
        fake_pending_connection_manager()->handled_requests().size() -
        pending_metadata_list.size();

    for (size_t i = 0; i < pending_metadata_list.size(); ++i) {
      VerifyHandledRequest(
          DeviceIdPair(device_id, std::get<1>(pending_metadata_list[i])),
          std::get<0>(pending_metadata_list[i]),
          std::get<2>(pending_metadata_list[i]),
          std::get<3>(pending_metadata_list[i]),
          connection_details.connection_medium(),
          num_handled_requests_start_index + i);
    }
  }

  void CancelPendingRequest(const base::UnguessableToken& request_id) {
    fake_client_connection_parameters_factory_
        ->id_to_active_client_parameters_map()
        .at(request_id)
        ->CancelClientRequest();
  }

  void FinishInitialization() {
    // The PendingConnectionManager should not have yet been created.
    EXPECT_FALSE(fake_pending_connection_manager());

    EXPECT_TRUE(test_task_runner_->HasPendingTask());
    test_task_runner_->RunUntilIdle();

    // The PendingConnectionManager should have been created, and all pending
    // requests should have been passed to it.
    EXPECT_EQ(num_queued_requests_before_initialization_,
              fake_pending_connection_manager()->handled_requests().size());
  }

  const cryptauth::RemoteDeviceList& test_devices() { return test_devices_; }

 private:
  void AttemptConnectionAndVerifyPendingConnection(
      const cryptauth::RemoteDevice& device_to_connect,
      const cryptauth::RemoteDevice& local_device,
      const std::string& feature,
      ConnectionPriority connection_priority,
      bool is_listener) {
    // If this is the first time the Mojo service will be accessed,
    // |fake_pending_connection_manager_factory_| will not yet have created an
    // instance, so fake_pending_connection_manager() will be null.
    size_t num_handled_requests_before_call =
        fake_pending_connection_manager()
            ? fake_pending_connection_manager()->handled_requests().size()
            : 0;

    auto id = AttemptConnectionWithoutRejection(device_to_connect, local_device,
                                                feature, connection_priority,
                                                is_listener);

    FakePendingConnectionManager::HandledRequestsList& handled_requests =
        fake_pending_connection_manager()->handled_requests();
    EXPECT_EQ(num_handled_requests_before_call + 1u, handled_requests.size());

    VerifyHandledRequest(DeviceIdPair(device_to_connect.GetDeviceId(),
                                      local_device.GetDeviceId()),
                         id,
                         is_listener ? ConnectionRole::kListenerRole
                                     : ConnectionRole::kInitiatorRole,
                         connection_priority,
                         ConnectionMedium::kBluetoothLowEnergy,
                         handled_requests.size() - 1);
  }

  void AttemptConnectionAndVerifyActiveConnection(
      const cryptauth::RemoteDevice& device_to_connect,
      const cryptauth::RemoteDevice& local_device,
      const std::string& feature,
      ConnectionPriority connection_priority,
      bool is_listener) {
    ConnectionDetails connection_details(device_to_connect.GetDeviceId(),
                                         ConnectionMedium::kBluetoothLowEnergy);

    const std::vector<std::unique_ptr<ClientConnectionParameters>>&
        clients_for_active_connection =
            std::get<2>(fake_active_connection_manager()
                            ->connection_details_to_active_metadata_map()
                            .find(connection_details)
                            ->second);
    size_t num_clients_before_call = clients_for_active_connection.size();

    auto id = AttemptConnectionWithoutRejection(device_to_connect, local_device,
                                                feature, connection_priority,
                                                true /* is_listener */);

    EXPECT_EQ(num_clients_before_call + 1u,
              clients_for_active_connection.size());
    EXPECT_EQ(id, clients_for_active_connection.back()->id());
  }

  base::UnguessableToken AttemptConnectionAndVerifyStillDisconnecting(
      const cryptauth::RemoteDevice& device_to_connect,
      const cryptauth::RemoteDevice& local_device,
      const std::string& feature,
      ConnectionPriority connection_priority,
      bool is_listener) {
    FakePendingConnectionManager::HandledRequestsList& handled_requests =
        fake_pending_connection_manager()->handled_requests();
    size_t num_handled_requests_before_call = handled_requests.size();

    auto id = AttemptConnectionWithoutRejection(device_to_connect, local_device,
                                                feature, connection_priority,
                                                is_listener);

    // Since the channel is expected to be disconnecting, no additional
    // pending request should have been sent.
    EXPECT_EQ(num_handled_requests_before_call, handled_requests.size());

    // Store the metadata associated with this attempt in
    // |disconnecting_details_to_requests_map_|. When the connection becomes
    // fully disconnected, this entry will be verified in
    // SimulateConnectionBecomingDisconnected().
    ConnectionDetails connection_details(device_to_connect.GetDeviceId(),
                                         ConnectionMedium::kBluetoothLowEnergy);
    disconnecting_details_to_requests_map_[connection_details].push_back(
        std::make_tuple(id, local_device.GetDeviceId(),
                        is_listener ? ConnectionRole::kListenerRole
                                    : ConnectionRole::kInitiatorRole,
                        connection_priority));

    return id;
  }

  void VerifyHandledRequest(
      const DeviceIdPair& expected_device_id_pair,
      const base::UnguessableToken& expected_client_parameters_id,
      ConnectionRole expected_connection_role,
      ConnectionPriority expected_connection_priority,
      ConnectionMedium expected_connection_medium,
      size_t expected_pending_connection_manager_index) {
    const auto& request =
        fake_pending_connection_manager()->handled_requests().at(
            expected_pending_connection_manager_index);

    EXPECT_EQ(ConnectionAttemptDetails(expected_device_id_pair,
                                       expected_connection_medium,
                                       expected_connection_role),
              std::get<0>(request));
    EXPECT_EQ(expected_client_parameters_id, std::get<1>(request)->id());
    EXPECT_EQ(expected_connection_priority, std::get<2>(request));
  }

  void AttemptConnectionAndVerifyRejection(
      const cryptauth::RemoteDevice& device_to_connect,
      const cryptauth::RemoteDevice& local_device,
      const std::string& feature,
      ConnectionPriority connection_priority,
      mojom::ConnectionAttemptFailureReason expected_failure_reason,
      bool is_listener) {
    auto id = AttemptConnectionPostInitialization(
        device_to_connect, local_device, feature, connection_priority,
        is_listener);
    EXPECT_EQ(expected_failure_reason, GetFailureReasonForRequest(id));
  }

  const base::Optional<mojom::ConnectionAttemptFailureReason>&
  GetFailureReasonForRequest(const base::UnguessableToken& id) {
    return fake_client_connection_parameters_factory_
        ->id_to_failure_reason_when_deleted_map()
        .at(id);
  }

  // Attempt a connection that is not expected to be rejected. This function
  // verifies that devices were correctly set in the RemoteDeviceCache after the
  // request completed.
  base::UnguessableToken AttemptConnectionWithoutRejection(
      const cryptauth::RemoteDevice& device_to_connect,
      const cryptauth::RemoteDevice& local_device,
      const std::string& feature,
      ConnectionPriority connection_priority,
      bool is_listener) {
    auto id = AttemptConnectionPostInitialization(
        device_to_connect, local_device, feature, connection_priority,
        is_listener);

    // |device_to_connect| should be in the cache.
    EXPECT_TRUE(cryptauth::IsSameDevice(device_to_connect,
                                        *remote_device_cache()->GetRemoteDevice(
                                            device_to_connect.GetDeviceId())));

    // |local_device| should also be in the cache.
    EXPECT_TRUE(cryptauth::IsSameDevice(
        local_device,
        *remote_device_cache()->GetRemoteDevice(local_device.GetDeviceId())));

    return id;
  }

  base::UnguessableToken AttemptConnectionPostInitialization(
      const cryptauth::RemoteDevice& device_to_connect,
      const cryptauth::RemoteDevice& local_device,
      const std::string& feature,
      ConnectionPriority connection_priority,
      bool is_listener) {
    base::UnguessableToken last_id_before_call =
        fake_client_connection_parameters_factory_->last_created_instance_id();

    AttemptConnection(device_to_connect, local_device, feature,
                      connection_priority, is_listener);

    base::UnguessableToken id_generated_by_call =
        fake_client_connection_parameters_factory_->last_created_instance_id();

    // The request should have caused a FakeClientConnectionParameters to be
    // created.
    EXPECT_NE(last_id_before_call, id_generated_by_call);

    return id_generated_by_call;
  }

  void AttemptConnectionPreInitialization(
      const cryptauth::RemoteDevice& device_to_connect,
      const cryptauth::RemoteDevice& local_device,
      const std::string& feature,
      ConnectionPriority connection_priority,
      bool is_listener) {
    // Should not have been any ClientConnectionParameters before the attempt.
    EXPECT_TRUE(
        fake_client_connection_parameters_factory_->last_created_instance_id()
            .is_empty());

    AttemptConnection(device_to_connect, local_device, feature,
                      connection_priority, is_listener);

    // Should still not have been any after the attempt.
    EXPECT_TRUE(
        fake_client_connection_parameters_factory_->last_created_instance_id()
            .is_empty());

    ++num_queued_requests_before_initialization_;
  }

  void AttemptConnection(const cryptauth::RemoteDevice& device_to_connect,
                         const cryptauth::RemoteDevice& local_device,
                         const std::string& feature,
                         ConnectionPriority connection_priority,
                         bool is_listener) {
    FakeConnectionDelegate fake_connection_delegate;

    if (is_listener) {
      secure_channel_ptr_->ListenForConnectionFromDevice(
          device_to_connect, local_device, feature, connection_priority,
          fake_connection_delegate.GenerateInterfacePtr());
    } else {
      secure_channel_ptr_->InitiateConnectionToDevice(
          device_to_connect, local_device, feature, connection_priority,
          fake_connection_delegate.GenerateInterfacePtr());
    }

    secure_channel_ptr_.FlushForTesting();
  }

  FakeActiveConnectionManager* fake_active_connection_manager() {
    return fake_active_connection_manager_factory_->instance();
  }

  FakePendingConnectionManager* fake_pending_connection_manager() {
    return fake_pending_connection_manager_factory_->instance();
  }

  cryptauth::RemoteDeviceCache* remote_device_cache() {
    return test_remote_device_cache_factory_->instance();
  }

  const base::test::ScopedTaskEnvironment scoped_task_environment_;
  const cryptauth::RemoteDeviceList test_devices_;

  scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>> mock_adapter_;
  scoped_refptr<base::TestSimpleTaskRunner> test_task_runner_;

  std::unique_ptr<FakeTimerFactoryFactory> fake_timer_factory_factory_;
  std::unique_ptr<TestRemoteDeviceCacheFactory>
      test_remote_device_cache_factory_;
  std::unique_ptr<FakeBleServiceDataHelperFactory>
      fake_ble_service_data_helper_factory_;
  std::unique_ptr<FakeBleConnectionManagerFactory>
      fake_ble_connection_manager_factory_;
  std::unique_ptr<FakePendingConnectionManagerFactory>
      fake_pending_connection_manager_factory_;
  std::unique_ptr<FakeActiveConnectionManagerFactory>
      fake_active_connection_manager_factory_;
  std::unique_ptr<TestSecureChannelInitializerFactory>
      test_secure_channel_initializer_factory_;
  std::unique_ptr<FakeClientConnectionParametersFactory>
      fake_client_connection_parameters_factory_;

  // Stores metadata which is expected to be pending when a connection attempt
  // is made while an ongoing connection is in the process of disconnecting.
  base::flat_map<ConnectionDetails,
                 std::vector<std::tuple<base::UnguessableToken,
                                        std::string,  // Local device ID.
                                        ConnectionRole,
                                        ConnectionPriority>>>
      disconnecting_details_to_requests_map_;

  size_t num_queued_requests_before_initialization_ = 0u;

  std::unique_ptr<service_manager::TestConnectorFactory> connector_factory_;
  std::unique_ptr<service_manager::Connector> connector_;

  mojom::SecureChannelPtr secure_channel_ptr_;

  DISALLOW_COPY_AND_ASSIGN(SecureChannelServiceTest);
};

TEST_F(SecureChannelServiceTest, ListenForConnection_MissingPublicKey) {
  FinishInitialization();

  cryptauth::RemoteDevice device_to_connect = test_devices()[0];
  device_to_connect.public_key.clear();

  CallListenForConnectionFromDeviceAndVerifyRejection(
      device_to_connect, test_devices()[1], "feature", ConnectionPriority::kLow,
      mojom::ConnectionAttemptFailureReason::REMOTE_DEVICE_INVALID_PUBLIC_KEY);
}

TEST_F(SecureChannelServiceTest, InitiateConnection_MissingPublicKey) {
  FinishInitialization();

  cryptauth::RemoteDevice device_to_connect = test_devices()[0];
  device_to_connect.public_key.clear();

  CallInitiateConnectionToDeviceAndVerifyRejection(
      device_to_connect, test_devices()[1], "feature", ConnectionPriority::kLow,
      mojom::ConnectionAttemptFailureReason::REMOTE_DEVICE_INVALID_PUBLIC_KEY);
}

TEST_F(SecureChannelServiceTest, ListenForConnection_MissingPsk) {
  FinishInitialization();

  cryptauth::RemoteDevice device_to_connect = test_devices()[0];
  device_to_connect.persistent_symmetric_key.clear();

  CallListenForConnectionFromDeviceAndVerifyRejection(
      device_to_connect, test_devices()[1], "feature", ConnectionPriority::kLow,
      mojom::ConnectionAttemptFailureReason::REMOTE_DEVICE_INVALID_PSK);
}

TEST_F(SecureChannelServiceTest, InitiateConnection_MissingPsk) {
  FinishInitialization();

  cryptauth::RemoteDevice device_to_connect = test_devices()[0];
  device_to_connect.persistent_symmetric_key.clear();

  CallInitiateConnectionToDeviceAndVerifyRejection(
      device_to_connect, test_devices()[1], "feature", ConnectionPriority::kLow,
      mojom::ConnectionAttemptFailureReason::REMOTE_DEVICE_INVALID_PSK);
}

TEST_F(SecureChannelServiceTest,
       ListenForConnection_MissingLocalDevicePublicKey) {
  FinishInitialization();

  cryptauth::RemoteDevice local_device = test_devices()[1];
  local_device.public_key.clear();

  CallListenForConnectionFromDeviceAndVerifyRejection(
      test_devices()[0], local_device, "feature", ConnectionPriority::kLow,
      mojom::ConnectionAttemptFailureReason::LOCAL_DEVICE_INVALID_PUBLIC_KEY);
}

TEST_F(SecureChannelServiceTest,
       InitiateConnection_MissingLocalDevicePublicKey) {
  FinishInitialization();

  cryptauth::RemoteDevice local_device = test_devices()[1];
  local_device.public_key.clear();

  CallInitiateConnectionToDeviceAndVerifyRejection(
      test_devices()[0], local_device, "feature", ConnectionPriority::kLow,
      mojom::ConnectionAttemptFailureReason::LOCAL_DEVICE_INVALID_PUBLIC_KEY);
}

TEST_F(SecureChannelServiceTest, ListenForConnection_MissingLocalDevicePsk) {
  FinishInitialization();

  cryptauth::RemoteDevice local_device = test_devices()[1];
  local_device.persistent_symmetric_key.clear();

  CallListenForConnectionFromDeviceAndVerifyRejection(
      test_devices()[0], local_device, "feature", ConnectionPriority::kLow,
      mojom::ConnectionAttemptFailureReason::LOCAL_DEVICE_INVALID_PSK);
}

TEST_F(SecureChannelServiceTest, InitiateConnection_MissingLocalDevicePsk) {
  FinishInitialization();

  cryptauth::RemoteDevice local_device = test_devices()[1];
  local_device.persistent_symmetric_key.clear();

  CallInitiateConnectionToDeviceAndVerifyRejection(
      test_devices()[0], local_device, "feature", ConnectionPriority::kLow,
      mojom::ConnectionAttemptFailureReason::LOCAL_DEVICE_INVALID_PSK);
}

TEST_F(SecureChannelServiceTest, CallsQueuedBeforeInitializationComplete) {
  CallInitiateConnectionToDeviceAndVerifyInitializationNotComplete(
      test_devices()[4], test_devices()[5], "feature",
      ConnectionPriority::kLow);
  CallListenForConnectionFromDeviceAndVerifyInitializationNotComplete(
      test_devices()[4], test_devices()[5], "feature",
      ConnectionPriority::kLow);
  FinishInitialization();
}

TEST_F(SecureChannelServiceTest, ListenForConnection_OneDevice) {
  FinishInitialization();

  CallListenForConnectionFromDeviceAndVerifyPendingConnection(
      test_devices()[0], test_devices()[1], "feature",
      ConnectionPriority::kLow);
  SimulateSuccessfulConnection(test_devices()[0].GetDeviceId());
  SimulateConnectionStartingDisconnecting(test_devices()[0].GetDeviceId());
  SimulateConnectionBecomingDisconnected(test_devices()[0].GetDeviceId());
}

TEST_F(SecureChannelServiceTest, InitiateConnection_OneDevice) {
  FinishInitialization();

  CallInitiateConnectionToDeviceAndVerifyPendingConnection(
      test_devices()[0], test_devices()[1], "feature",
      ConnectionPriority::kLow);
  SimulateSuccessfulConnection(test_devices()[0].GetDeviceId());
  SimulateConnectionStartingDisconnecting(test_devices()[0].GetDeviceId());
  SimulateConnectionBecomingDisconnected(test_devices()[0].GetDeviceId());
}

TEST_F(SecureChannelServiceTest,
       ListenForConnection_OneDevice_RequestSpecificLocalDevice) {
  FinishInitialization();

  CallListenForConnectionFromDeviceAndVerifyPendingConnection(
      test_devices()[0], test_devices()[1], "feature",
      ConnectionPriority::kLow);
  SimulateSuccessfulConnection(test_devices()[0].GetDeviceId());
  SimulateConnectionStartingDisconnecting(test_devices()[0].GetDeviceId());
  SimulateConnectionBecomingDisconnected(test_devices()[0].GetDeviceId());
}

TEST_F(SecureChannelServiceTest,
       InitiateConnection_OneDevice_RequestSpecificLocalDevice) {
  FinishInitialization();

  CallInitiateConnectionToDeviceAndVerifyPendingConnection(
      test_devices()[0], test_devices()[1], "feature",
      ConnectionPriority::kLow);
  SimulateSuccessfulConnection(test_devices()[0].GetDeviceId());
  SimulateConnectionStartingDisconnecting(test_devices()[0].GetDeviceId());
  SimulateConnectionBecomingDisconnected(test_devices()[0].GetDeviceId());
}

TEST_F(SecureChannelServiceTest, OneDevice_TwoConnectionRequests) {
  FinishInitialization();

  // Two pending connection requests for the same device.
  CallListenForConnectionFromDeviceAndVerifyPendingConnection(
      test_devices()[0], test_devices()[1], "feature1",
      ConnectionPriority::kLow);
  CallInitiateConnectionToDeviceAndVerifyPendingConnection(
      test_devices()[0], test_devices()[1], "feature2",
      ConnectionPriority::kMedium);

  SimulateSuccessfulConnection(test_devices()[0].GetDeviceId());
  SimulateConnectionStartingDisconnecting(test_devices()[0].GetDeviceId());
  SimulateConnectionBecomingDisconnected(test_devices()[0].GetDeviceId());
}

TEST_F(SecureChannelServiceTest,
       OneDevice_TwoConnectionRequests_OneAfterConnection) {
  FinishInitialization();

  // First request is successful.
  CallListenForConnectionFromDeviceAndVerifyPendingConnection(
      test_devices()[0], test_devices()[1], "feature1",
      ConnectionPriority::kLow);
  SimulateSuccessfulConnection(test_devices()[0].GetDeviceId());

  // Second request is added to the existing channel.
  CallInitiateConnectionToDeviceAndVerifyActiveConnection(
      test_devices()[0], test_devices()[1], "feature2",
      ConnectionPriority::kMedium);

  SimulateConnectionStartingDisconnecting(test_devices()[0].GetDeviceId());
  SimulateConnectionBecomingDisconnected(test_devices()[0].GetDeviceId());
}

TEST_F(SecureChannelServiceTest,
       OneDevice_TwoConnectionRequests_OneWhileDisconnecting) {
  FinishInitialization();

  // First request is successful.
  CallListenForConnectionFromDeviceAndVerifyPendingConnection(
      test_devices()[0], test_devices()[1], "feature1",
      ConnectionPriority::kLow);
  SimulateSuccessfulConnection(test_devices()[0].GetDeviceId());

  // Connection starts disconnecting.
  SimulateConnectionStartingDisconnecting(test_devices()[0].GetDeviceId());

  // Second request is added before disconnecting is complete.
  CallInitiateConnectionToDeviceAndVerifyStillDisconnecting(
      test_devices()[0], test_devices()[1], "feature2",
      ConnectionPriority::kMedium);

  // Complete the disconnection; this should cause the second request to be
  // delivered to PendingConnectionManager.
  SimulateConnectionBecomingDisconnected(test_devices()[0].GetDeviceId());

  // The second attempt succeeds.
  SimulateSuccessfulConnection(test_devices()[0].GetDeviceId());

  SimulateConnectionStartingDisconnecting(test_devices()[0].GetDeviceId());
  SimulateConnectionBecomingDisconnected(test_devices()[0].GetDeviceId());
}

TEST_F(SecureChannelServiceTest,
       OneDevice_TwoConnectionRequests_OneWhileDisconnecting_Canceled) {
  FinishInitialization();

  // First request is successful.
  CallListenForConnectionFromDeviceAndVerifyPendingConnection(
      test_devices()[0], test_devices()[1], "feature1",
      ConnectionPriority::kLow);
  SimulateSuccessfulConnection(test_devices()[0].GetDeviceId());

  // Connection starts disconnecting.
  SimulateConnectionStartingDisconnecting(test_devices()[0].GetDeviceId());

  // Second request is added before disconnecting is complete, but the request
  // is canceled before the disconnection completes.
  auto id = CallInitiateConnectionToDeviceAndVerifyStillDisconnecting(
      test_devices()[0], test_devices()[1], "feature2",
      ConnectionPriority::kMedium);
  CancelPendingRequest(id);

  // Complete the disconnection; even though the request was canceled, it should
  // still have been added to PendingConnectionManager.
  SimulateConnectionBecomingDisconnected(test_devices()[0].GetDeviceId());
}

TEST_F(SecureChannelServiceTest, ThreeDevices) {
  FinishInitialization();

  // Two requests for each device.
  CallListenForConnectionFromDeviceAndVerifyPendingConnection(
      test_devices()[0], test_devices()[1], "feature1",
      ConnectionPriority::kLow);
  CallInitiateConnectionToDeviceAndVerifyPendingConnection(
      test_devices()[0], test_devices()[1], "feature2",
      ConnectionPriority::kMedium);
  CallListenForConnectionFromDeviceAndVerifyPendingConnection(
      test_devices()[2], test_devices()[1], "feature3",
      ConnectionPriority::kHigh);
  CallInitiateConnectionToDeviceAndVerifyPendingConnection(
      test_devices()[2], test_devices()[1], "feature4",
      ConnectionPriority::kLow);
  CallListenForConnectionFromDeviceAndVerifyPendingConnection(
      test_devices()[3], test_devices()[1], "feature5",
      ConnectionPriority::kMedium);
  CallInitiateConnectionToDeviceAndVerifyPendingConnection(
      test_devices()[3], test_devices()[1], "feature6",
      ConnectionPriority::kHigh);

  SimulateSuccessfulConnection(test_devices()[0].GetDeviceId());
  SimulateSuccessfulConnection(test_devices()[2].GetDeviceId());
  SimulateSuccessfulConnection(test_devices()[3].GetDeviceId());

  SimulateConnectionStartingDisconnecting(test_devices()[0].GetDeviceId());
  SimulateConnectionStartingDisconnecting(test_devices()[2].GetDeviceId());
  SimulateConnectionStartingDisconnecting(test_devices()[3].GetDeviceId());

  SimulateConnectionBecomingDisconnected(test_devices()[0].GetDeviceId());
  SimulateConnectionBecomingDisconnected(test_devices()[2].GetDeviceId());
  SimulateConnectionBecomingDisconnected(test_devices()[3].GetDeviceId());
}

}  // namespace secure_channel

}  // namespace chromeos
