chromium/device/bluetooth/adapter_unittest.cc

// Copyright 2020 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/adapter.h"

#include "base/containers/contains.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "build/build_config.h"
#include "device/bluetooth/bluetooth_advertisement.h"
#include "device/bluetooth/gatt_service.h"
#include "device/bluetooth/public/mojom/adapter.mojom.h"
#include "device/bluetooth/test/fake_local_gatt_service.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_advertisement.h"
#include "device/bluetooth/test/mock_bluetooth_device.h"
#include "device/bluetooth/test/mock_bluetooth_socket.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "testing/gtest/include/gtest/gtest.h"

RunOnceCallback;

_;
DoAll;
InvokeWithoutArgs;
NiceMock;
Return;

namespace {

const char kKnownDeviceAddress[] =;
const char kUnknownDeviceAddress[] =;
const char kServiceName[] =;
const char kServiceId[] =;
const char kDeviceServiceDataStr[] =;

std::vector<uint8_t> GetByteVector(const std::string& str) {}

class MockBluetoothAdapterWithAdvertisements
    : public device::MockBluetoothAdapter {};

}  // namespace

namespace bluetooth {

class AdapterTest : public testing::Test, public mojom::GattServiceObserver {};

TEST_F(AdapterTest, TestRegisterAdvertisement_Success) {}

TEST_F(AdapterTest, TestRegisterAdvertisement_Error) {}

TEST_F(AdapterTest, TestRegisterAdvertisement_ScanResponseData) {}

TEST_F(AdapterTest, TestRegisterAdvertisement_NotConnectable) {}

TEST_F(AdapterTest, TestRegisterAdvertisement_Connectable) {}

TEST_F(AdapterTest, TestConnectToServiceInsecurely_DisallowedUuid) {}

TEST_F(AdapterTest, TestConnectToServiceInsecurely_KnownDevice_Success) {}

TEST_F(AdapterTest, TestConnectToServiceInsecurely_KnownDevice_Error) {}

#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
TEST_F(
    AdapterTest,
    TestConnectToServiceInsecurely_UnknownDevice_Success_ServicesAlreadyResolved) {}

TEST_F(
    AdapterTest,
    TestConnectToServiceInsecurely_UnknownDevice_Success_WaitForServicesToResolve) {}

TEST_F(
    AdapterTest,
    TestConnectToServiceInsecurely_UnknownDevice_Failure_WaitForServicesToResolve_DeviceRemoved) {}

TEST_F(
    AdapterTest,
    TestConnectToServiceInsecurely_UnknownDevice_Failure_WaitForServicesToResolve_DeviceChangedWithNoRssi) {}

TEST_F(AdapterTest, TestConnectToServiceInsecurely_UnknownDevice_Error) {}
#else
TEST_F(AdapterTest, TestConnectToServiceInsecurely_UnknownDevice) {
  adapter_->AllowConnectionsForUuid(device::BluetoothUUID(kServiceId));

  base::RunLoop run_loop;
  adapter_->ConnectToServiceInsecurely(
      kUnknownDeviceAddress, device::BluetoothUUID(kServiceId),
      /*should_unbond_on_error=*/false,
      base::BindLambdaForTesting(
          [&](mojom::ConnectToServiceResultPtr connect_to_service_result) {
            EXPECT_FALSE(connect_to_service_result);
            run_loop.Quit();
          }));
  run_loop.Run();
}
#endif

#if BUILDFLAG(IS_CHROMEOS)
TEST_F(AdapterTest, TestConnectToServiceInsecurely_HalfPaired) {
  EXPECT_CALL(*mock_known_bluetooth_device_, IsBonded).WillOnce(Return(true));

  EXPECT_CALL(*mock_known_bluetooth_device_,
              ConnectToServiceInsecurely(_, _, _))
      .WillOnce(RunOnceCallback<2>("br-connection-canceled"));

  EXPECT_CALL(*mock_known_bluetooth_device_, Forget).Times(1);

  adapter_->AllowConnectionsForUuid(device::BluetoothUUID(kServiceId));

  adapter_->ConnectToServiceInsecurely(
      kKnownDeviceAddress, device::BluetoothUUID(kServiceId),
      /*should_unbond_on_error=*/true,
      base::BindLambdaForTesting(
          [&](mojom::ConnectToServiceResultPtr connect_to_service_result) {}));
}

TEST_F(AdapterTest, CreateLocalGattService) {
  mojo::PendingRemote<mojom::GattServiceObserver> observer =
      observer_.BindNewPipeAndPassRemote();
  base::test::TestFuture<mojo::PendingRemote<mojom::GattService>> future;
  adapter_->CreateLocalGattService(bluetooth_service_id_, std::move(observer),
                                   future.GetCallback());
  EXPECT_TRUE(future.Take());
}

TEST_F(AdapterTest, MemoryCleanUp_ResetGattServiceMojoRemote) {
  mojo::Remote<mojom::GattService> gatt_service_remote;

  // Create `GattService` and set the Mojo remote
  {
    mojo::PendingRemote<mojom::GattServiceObserver> observer =
        observer_.BindNewPipeAndPassRemote();
    base::test::TestFuture<mojo::PendingRemote<mojom::GattService>> future;
    adapter_->CreateLocalGattService(bluetooth_service_id_, std::move(observer),
                                     future.GetCallback());
    gatt_service_remote.Bind(future.Take());
  }

  // Simulate that the GATT service is created successfully, and is never
  // destroyed during the lifetime of this test.
  ON_CALL(*mock_bluetooth_adapter_, GetGattService)
      .WillByDefault(testing::Return(fake_local_gatt_service_.get()));

  {
    // Delete the GATT service remote, which triggers memory clean up.
    base::RunLoop run_loop;
    fake_local_gatt_service_->set_on_deleted_callback(run_loop.QuitClosure());
    gatt_service_remote.reset();
    run_loop.Run();
  }

  // Since the GATT service is deleted, the Adapter no longer returns a
  // pointer to the GATT service when it is called.
  ON_CALL(*mock_bluetooth_adapter_, GetGattService)
      .WillByDefault(testing::Return(nullptr));
  observer_.reset();

  // Expect a new call to `CreateLocalGattService` to be successful since the
  // old `GattService` was deleted. The memory cleanup is enforced by a CHECK in
  // `CreateLocalGattService()` that an existing `GattService` does not already
  // exist with that UUID.
  {
    mojo::PendingRemote<mojom::GattServiceObserver> observer =
        observer_.BindNewPipeAndPassRemote();
    base::test::TestFuture<mojo::PendingRemote<mojom::GattService>> future;
    adapter_->CreateLocalGattService(bluetooth_service_id_, std::move(observer),
                                     future.GetCallback());
    EXPECT_TRUE(future.Take());
  }
}

#endif

TEST_F(AdapterTest, TestCreateRfcommServiceInsecurely_DisallowedUuid) {}

TEST_F(AdapterTest, TestCreateRfcommServiceInsecurely_Error) {}

TEST_F(AdapterTest, TestCreateRfcommServiceInsecurely_Success) {}

#if BUILDFLAG(IS_CHROMEOS)
TEST_F(AdapterTest, TestIsLeScatternetDualRoleSupported_Suppported) {
  std::vector<device::BluetoothAdapter::BluetoothRole> roles{
      device::BluetoothAdapter::BluetoothRole::kCentralPeripheral};
  ON_CALL(*mock_bluetooth_adapter_, GetSupportedRoles())
      .WillByDefault(Return(roles));

  base::test::TestFuture<bool> future;
  adapter_->IsLeScatternetDualRoleSupported(future.GetCallback());
  EXPECT_TRUE(future.Get());
}

TEST_F(AdapterTest, TestIsLeScatternetDualRoleSupported_NotSupported) {
  std::vector<device::BluetoothAdapter::BluetoothRole> roles{
      device::BluetoothAdapter::BluetoothRole::kCentral,
      device::BluetoothAdapter::BluetoothRole::kPeripheral};
  ON_CALL(*mock_bluetooth_adapter_, GetSupportedRoles())
      .WillByDefault(Return(roles));

  base::test::TestFuture<bool> future;
  adapter_->IsLeScatternetDualRoleSupported(future.GetCallback());
  EXPECT_FALSE(future.Get());
}

TEST_F(AdapterTest, TestMetricsOnShutdown_NoPendingConnects) {
  base::HistogramTester histogram_tester;
  adapter_.reset();

  EXPECT_EQ(0u,
            histogram_tester
                .GetAllSamples(
                    "Bluetooth.Mojo.PendingConnectAtShutdown.DurationWaiting")
                .size());
  histogram_tester.ExpectUniqueSample(
      "Bluetooth.Mojo.PendingConnectAtShutdown."
      "NumberOfServiceDiscoveriesInProgress",
      /*sample=*/0, /*expected_bucket_count=*/1);
}

TEST_F(AdapterTest, TestMetricsOnShutdown_PendingConnects) {
  base::HistogramTester histogram_tester;
  EXPECT_CALL(*mock_bluetooth_adapter_,
              ConnectDevice(kUnknownDeviceAddress, _, _, _))
      .WillOnce(RunOnceCallback<2>(mock_unknown_bluetooth_device_.get()));

  EXPECT_CALL(*mock_unknown_bluetooth_device_,
              IsGattServicesDiscoveryComplete())
      .WillRepeatedly(Return(false));

  adapter_->AllowConnectionsForUuid(device::BluetoothUUID(kServiceId));
  adapter_->ConnectToServiceInsecurely(
      kUnknownDeviceAddress, device::BluetoothUUID(kServiceId),
      /*should_unbond_on_error=*/false, base::DoNothing());
  base::RunLoop().RunUntilIdle();

  adapter_.reset();

  histogram_tester.ExpectUniqueSample(
      "Bluetooth.Mojo.PendingConnectAtShutdown."
      "NumberOfServiceDiscoveriesInProgress",
      /*sample=*/1, /*expected_bucket_count=*/1);
  EXPECT_EQ(1u, histogram_tester
                    .GetAllSamples("Bluetooth.Mojo.PendingConnectAtShutdown."
                                   "NumberOfServiceDiscoveriesInProgress")
                    .size());
}

TEST_F(AdapterTest,
       TestConnectToServiceInsecurely_UnknownDevice_Error_DeviceRemoved) {
  EXPECT_CALL(*mock_bluetooth_adapter_,
              ConnectDevice(kUnknownDeviceAddress, _, _, _))
      .WillOnce(
          [&](const std::string& address,
              const std::optional<device::BluetoothDevice::AddressType>&
                  address_type,
              MockBluetoothAdapterWithAdvertisements::ConnectDeviceCallback
                  callback,
              MockBluetoothAdapterWithAdvertisements::ConnectDeviceErrorCallback
                  error_callback) {
            // Device is removed before `ConnectToService()` error, which
            // removes all entries from the map.
            adapter_->DeviceRemoved(mock_bluetooth_adapter_.get(),
                                    mock_unknown_bluetooth_device_.get());

            // Trigger error callback verify Adapter doesn't crash.
            std::move(error_callback).Run("error_message");
          });

  adapter_->AllowConnectionsForUuid(device::BluetoothUUID(kServiceId));

  base::test::TestFuture<mojom::ConnectToServiceResultPtr> future;
  adapter_->ConnectToServiceInsecurely(
      kUnknownDeviceAddress, device::BluetoothUUID(kServiceId),
      /*should_unbond_on_error=*/false, future.GetCallback());
  EXPECT_FALSE(future.Take());
}
#endif
}  // namespace bluetooth