// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "device/bluetooth/bluez/bluetooth_adapter_profile_bluez.h"

#include <memory>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/bluez/bluetooth_adapter_bluez.h"
#include "device/bluetooth/dbus/bluetooth_profile_service_provider.h"
#include "device/bluetooth/dbus/bluez_dbus_manager.h"
#include "device/bluetooth/dbus/fake_bluetooth_adapter_client.h"
#include "device/bluetooth/dbus/fake_bluetooth_agent_manager_client.h"
#include "device/bluetooth/dbus/fake_bluetooth_device_client.h"
#include "device/bluetooth/dbus/fake_bluetooth_profile_manager_client.h"
#include "device/bluetooth/floss/floss_features.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
#include "testing/gtest/include/gtest/gtest.h"

using device::BluetoothAdapter;
using device::BluetoothUUID;

namespace bluez {

class BluetoothAdapterProfileBlueZTest : public testing::Test {
 public:
  BluetoothAdapterProfileBlueZTest()
      : success_callback_count_(0),
        error_callback_count_(0),
        fake_delegate_paired_(
            bluez::FakeBluetoothDeviceClient::kPairedDevicePath),
        fake_delegate_autopair_(
            bluez::FakeBluetoothDeviceClient::kLegacyAutopairPath),
        fake_delegate_listen_(""),
        profile_user_ptr_(nullptr) {}

  void SetUp() override {
    // TODO(b/266989920) Remove when Floss fake implementation is completed.
    if (floss::features::IsFlossEnabled()) {
      GTEST_SKIP();
    }
    std::unique_ptr<bluez::BluezDBusManagerSetter> dbus_setter =
        bluez::BluezDBusManager::GetSetterForTesting();

    dbus_setter->SetBluetoothAdapterClient(
        std::make_unique<bluez::FakeBluetoothAdapterClient>());
    dbus_setter->SetBluetoothAgentManagerClient(
        std::make_unique<bluez::FakeBluetoothAgentManagerClient>());
    dbus_setter->SetBluetoothDeviceClient(
        std::make_unique<bluez::FakeBluetoothDeviceClient>());
    dbus_setter->SetBluetoothProfileManagerClient(
        std::make_unique<bluez::FakeBluetoothProfileManagerClient>());

    // Grab a pointer to the adapter.
    base::RunLoop run_loop;
    device::BluetoothAdapterFactory::Get()->GetAdapter(
        base::BindLambdaForTesting(
            [&](scoped_refptr<BluetoothAdapter> adapter) {
              adapter_ = std::move(adapter);
              run_loop.Quit();
            }));
    run_loop.Run();
    ASSERT_TRUE(adapter_);
    ASSERT_TRUE(adapter_->IsInitialized());
    ASSERT_TRUE(adapter_->IsPresent());

    // Turn on the adapter.
    adapter_->SetPowered(true, base::DoNothing(), base::DoNothing());
    ASSERT_TRUE(adapter_->IsPowered());
  }

  void TearDown() override {
    if (floss::features::IsFlossEnabled()) {
      return;
    }
    profile_.reset();
    adapter_ = nullptr;
    bluez::BluezDBusManager::Shutdown();
  }

  class FakeDelegate : public bluez::BluetoothProfileServiceProvider::Delegate {
   public:
    FakeDelegate(const std::string& device_path) : connections_(0) {
      device_path_ = dbus::ObjectPath(device_path);
    }

    // bluez::BluetoothProfileServiceProvider::Delegate:
    void Released() override {
      // noop
    }

    void NewConnection(
        const dbus::ObjectPath& device_path,
        base::ScopedFD fd,
        const bluez::BluetoothProfileServiceProvider::Delegate::Options&
            options,
        ConfirmationCallback callback) override {
      ++connections_;
      fd.reset();
      std::move(callback).Run(SUCCESS);
      if (device_path_.value() != "")
        ASSERT_EQ(device_path_, device_path);
    }

    void RequestDisconnection(const dbus::ObjectPath& device_path,
                              ConfirmationCallback callback) override {
      ++disconnections_;
    }

    void Cancel() override {
      // noop
    }

    unsigned int connections_;
    unsigned int disconnections_;
    dbus::ObjectPath device_path_;
  };

  void ProfileSuccessCallback(
      std::unique_ptr<BluetoothAdapterProfileBlueZ> profile) {
    profile_.swap(profile);
    ++success_callback_count_;
  }

  void ProfileUserSuccessCallback(BluetoothAdapterProfileBlueZ* profile) {
    profile_user_ptr_ = profile;
    ++success_callback_count_;
  }

  void MatchedProfileCallback(BluetoothAdapterProfileBlueZ* profile) {
    ASSERT_EQ(profile_user_ptr_, profile);
    ++success_callback_count_;
  }

  void DBusConnectSuccessCallback() { ++success_callback_count_; }

  void DBusErrorCallback(const std::string& error_name,
                         const std::string& error_message) {
    ++error_callback_count_;
  }

  void BasicErrorCallback(const std::string& error_message) {
    ++error_callback_count_;
  }

 protected:
  base::test::TaskEnvironment task_environment_;

  scoped_refptr<BluetoothAdapter> adapter_;

  unsigned int success_callback_count_;
  unsigned int error_callback_count_;

  FakeDelegate fake_delegate_paired_;
  FakeDelegate fake_delegate_autopair_;
  FakeDelegate fake_delegate_listen_;

  std::unique_ptr<BluetoothAdapterProfileBlueZ> profile_;

  // unowned pointer as expected to be used by clients of
  // BluetoothAdapterBlueZ::UseProfile like BluetoothSocketBlueZ
  raw_ptr<BluetoothAdapterProfileBlueZ, DanglingUntriaged> profile_user_ptr_;
};

TEST_F(BluetoothAdapterProfileBlueZTest, DelegateCount) {
  BluetoothUUID uuid(bluez::FakeBluetoothProfileManagerClient::kRfcommUuid);
  bluez::BluetoothProfileManagerClient::Options options;

  options.require_authentication = std::make_unique<bool>(false);

  BluetoothAdapterProfileBlueZ::Register(
      uuid, options,
      base::BindOnce(&BluetoothAdapterProfileBlueZTest::ProfileSuccessCallback,
                     base::Unretained(this)),
      base::BindOnce(&BluetoothAdapterProfileBlueZTest::DBusErrorCallback,
                     base::Unretained(this)));

  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(profile_);
  EXPECT_EQ(1U, success_callback_count_);
  EXPECT_EQ(0U, error_callback_count_);

  EXPECT_EQ(0U, profile_->DelegateCount());

  profile_->SetDelegate(fake_delegate_paired_.device_path_,
                        &fake_delegate_paired_);

  EXPECT_EQ(1U, profile_->DelegateCount());

  profile_->RemoveDelegate(fake_delegate_autopair_.device_path_,
                           base::DoNothing());

  EXPECT_EQ(1U, profile_->DelegateCount());

  profile_->RemoveDelegate(fake_delegate_paired_.device_path_,
                           base::DoNothing());

  EXPECT_EQ(0U, profile_->DelegateCount());
}

TEST_F(BluetoothAdapterProfileBlueZTest, BlackHole) {
  BluetoothUUID uuid(bluez::FakeBluetoothProfileManagerClient::kRfcommUuid);
  bluez::BluetoothProfileManagerClient::Options options;

  options.require_authentication = std::make_unique<bool>(false);

  BluetoothAdapterProfileBlueZ::Register(
      uuid, options,
      base::BindOnce(&BluetoothAdapterProfileBlueZTest::ProfileSuccessCallback,
                     base::Unretained(this)),
      base::BindOnce(&BluetoothAdapterProfileBlueZTest::DBusErrorCallback,
                     base::Unretained(this)));

  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(profile_);
  EXPECT_EQ(1U, success_callback_count_);
  EXPECT_EQ(0U, error_callback_count_);

  bluez::BluezDBusManager::Get()->GetBluetoothDeviceClient()->ConnectProfile(
      dbus::ObjectPath(bluez::FakeBluetoothDeviceClient::kPairedDevicePath),
      bluez::FakeBluetoothProfileManagerClient::kRfcommUuid,
      base::BindOnce(
          &BluetoothAdapterProfileBlueZTest::DBusConnectSuccessCallback,
          base::Unretained(this)),
      base::BindOnce(&BluetoothAdapterProfileBlueZTest::DBusErrorCallback,
                     base::Unretained(this)));

  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(1U, success_callback_count_);
  EXPECT_EQ(1U, error_callback_count_);

  EXPECT_EQ(0U, fake_delegate_paired_.connections_);
}

TEST_F(BluetoothAdapterProfileBlueZTest, Routing) {
  BluetoothUUID uuid(bluez::FakeBluetoothProfileManagerClient::kRfcommUuid);
  bluez::BluetoothProfileManagerClient::Options options;

  options.require_authentication = std::make_unique<bool>(false);

  BluetoothAdapterProfileBlueZ::Register(
      uuid, options,
      base::BindOnce(&BluetoothAdapterProfileBlueZTest::ProfileSuccessCallback,
                     base::Unretained(this)),
      base::BindOnce(&BluetoothAdapterProfileBlueZTest::DBusErrorCallback,
                     base::Unretained(this)));

  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE(profile_);
  ASSERT_EQ(1U, success_callback_count_);
  ASSERT_EQ(0U, error_callback_count_);

  profile_->SetDelegate(fake_delegate_paired_.device_path_,
                        &fake_delegate_paired_);
  profile_->SetDelegate(fake_delegate_autopair_.device_path_,
                        &fake_delegate_autopair_);
  profile_->SetDelegate(fake_delegate_listen_.device_path_,
                        &fake_delegate_listen_);

  bluez::BluezDBusManager::Get()->GetBluetoothDeviceClient()->ConnectProfile(
      dbus::ObjectPath(bluez::FakeBluetoothDeviceClient::kPairedDevicePath),
      bluez::FakeBluetoothProfileManagerClient::kRfcommUuid,
      base::BindOnce(
          &BluetoothAdapterProfileBlueZTest::DBusConnectSuccessCallback,
          base::Unretained(this)),
      base::BindOnce(&BluetoothAdapterProfileBlueZTest::DBusErrorCallback,
                     base::Unretained(this)));

  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(2U, success_callback_count_);
  EXPECT_EQ(0U, error_callback_count_);

  EXPECT_EQ(1U, fake_delegate_paired_.connections_);

  bluez::BluezDBusManager::Get()->GetBluetoothDeviceClient()->ConnectProfile(
      dbus::ObjectPath(bluez::FakeBluetoothDeviceClient::kLegacyAutopairPath),
      bluez::FakeBluetoothProfileManagerClient::kRfcommUuid,
      base::BindOnce(
          &BluetoothAdapterProfileBlueZTest::DBusConnectSuccessCallback,
          base::Unretained(this)),
      base::BindOnce(&BluetoothAdapterProfileBlueZTest::DBusErrorCallback,
                     base::Unretained(this)));

  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(3U, success_callback_count_);
  EXPECT_EQ(0U, error_callback_count_);

  EXPECT_EQ(1U, fake_delegate_autopair_.connections_);

  // Incoming connections look the same from BlueZ.
  bluez::BluezDBusManager::Get()->GetBluetoothDeviceClient()->ConnectProfile(
      dbus::ObjectPath(bluez::FakeBluetoothDeviceClient::kDisplayPinCodePath),
      bluez::FakeBluetoothProfileManagerClient::kRfcommUuid,
      base::BindOnce(
          &BluetoothAdapterProfileBlueZTest::DBusConnectSuccessCallback,
          base::Unretained(this)),
      base::BindOnce(&BluetoothAdapterProfileBlueZTest::DBusErrorCallback,
                     base::Unretained(this)));

  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(4U, success_callback_count_);
  EXPECT_EQ(0U, error_callback_count_);

  EXPECT_EQ(1U, fake_delegate_listen_.connections_);
}

TEST_F(BluetoothAdapterProfileBlueZTest, SimultaneousRegister) {
  BluetoothUUID uuid(bluez::FakeBluetoothProfileManagerClient::kRfcommUuid);
  bluez::BluetoothProfileManagerClient::Options options;
  BluetoothAdapterBlueZ* adapter =
      static_cast<BluetoothAdapterBlueZ*>(adapter_.get());

  options.require_authentication = std::make_unique<bool>(false);

  success_callback_count_ = 0;
  error_callback_count_ = 0;

  adapter->UseProfile(
      uuid, fake_delegate_paired_.device_path_, options, &fake_delegate_paired_,
      base::BindOnce(
          &BluetoothAdapterProfileBlueZTest::ProfileUserSuccessCallback,
          base::Unretained(this)),
      base::BindOnce(&BluetoothAdapterProfileBlueZTest::BasicErrorCallback,
                     base::Unretained(this)));

  adapter->UseProfile(
      uuid, fake_delegate_autopair_.device_path_, options,
      &fake_delegate_autopair_,
      base::BindOnce(&BluetoothAdapterProfileBlueZTest::MatchedProfileCallback,
                     base::Unretained(this)),
      base::BindOnce(&BluetoothAdapterProfileBlueZTest::BasicErrorCallback,
                     base::Unretained(this)));

  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(profile_user_ptr_);
  EXPECT_EQ(2U, success_callback_count_);
  EXPECT_EQ(0U, error_callback_count_);

  adapter->ReleaseProfile(fake_delegate_paired_.device_path_,
                          profile_user_ptr_);
  adapter->ReleaseProfile(fake_delegate_autopair_.device_path_,
                          profile_user_ptr_);

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

TEST_F(BluetoothAdapterProfileBlueZTest, SimultaneousRegisterFail) {
  BluetoothUUID uuid(
      bluez::FakeBluetoothProfileManagerClient::kUnregisterableUuid);
  bluez::BluetoothProfileManagerClient::Options options;
  BluetoothAdapterBlueZ* adapter =
      static_cast<BluetoothAdapterBlueZ*>(adapter_.get());

  options.require_authentication = std::make_unique<bool>(false);

  success_callback_count_ = 0;
  error_callback_count_ = 0;

  adapter->UseProfile(
      uuid, fake_delegate_paired_.device_path_, options, &fake_delegate_paired_,
      base::BindOnce(
          &BluetoothAdapterProfileBlueZTest::ProfileUserSuccessCallback,
          base::Unretained(this)),
      base::BindOnce(&BluetoothAdapterProfileBlueZTest::BasicErrorCallback,
                     base::Unretained(this)));

  adapter->UseProfile(
      uuid, fake_delegate_autopair_.device_path_, options,
      &fake_delegate_autopair_,
      base::BindOnce(&BluetoothAdapterProfileBlueZTest::MatchedProfileCallback,
                     base::Unretained(this)),
      base::BindOnce(&BluetoothAdapterProfileBlueZTest::BasicErrorCallback,
                     base::Unretained(this)));

  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(profile_user_ptr_);
  EXPECT_EQ(0U, success_callback_count_);
  EXPECT_EQ(2U, error_callback_count_);
}

}  // namespace bluez
