chromium/chrome/services/sharing/nearby/platform/ble_v2_medium_unittest.cc

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

#include "chrome/services/sharing/nearby/platform/ble_v2_medium.h"

#include <memory>

#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/task/thread_pool.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/services/sharing/nearby/platform/count_down_latch.h"
#include "chrome/services/sharing/nearby/platform/nearby_platform_metrics.h"
#include "chrome/services/sharing/nearby/test_support/fake_adapter.h"
#include "chrome/services/sharing/nearby/test_support/fake_device.h"
#include "components/cross_device/nearby/nearby_features.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace nearby::chrome {

namespace {

const char kDeviceAddress[] = "DeviceAddress";
const char kDeviceServiceData1Str[] = "Device_Advertisement1";
const char kDeviceServiceData2Str[] = "Device_Advertisement2";
const ByteArray kDeviceServiceData1ByteArray{
    std::string{kDeviceServiceData1Str}};
const ByteArray kDeviceServiceData2ByteArray{
    std::string{kDeviceServiceData2Str}};
const Uuid kFastAdvertisementServiceUuid1{0x0000FEF300001000,
                                          0x800000805F9B34FB};
const Uuid kTestServiceUuid2{0x0000FEF300001000, 0xA0000060ABCDEF12};
const device::BluetoothUUID kService1BluetoothUuid{
    base::span<const uint8_t>(reinterpret_cast<const uint8_t*>(
                                  kFastAdvertisementServiceUuid1.data().data()),
                              kFastAdvertisementServiceUuid1.data().size())};
const device::BluetoothUUID kService2BluetoothUuid{base::span<const uint8_t>(
    reinterpret_cast<const uint8_t*>(kTestServiceUuid2.data().data()),
    kTestServiceUuid2.data().size())};
const char kServiceId[] = "TestServiceId";
const char kCharacteristicUuid[] = "1234";
const uint64_t kUniqueId = 1053256082272529;

std::vector<uint8_t> GetByteVector(const std::string& str) {
  return std::vector<uint8_t>(str.begin(), str.end());
}

class FakeBleV2RemotePeripheral : public api::ble_v2::BlePeripheral {
 public:
  FakeBleV2RemotePeripheral() = default;
  ~FakeBleV2RemotePeripheral() override = default;

  std::string GetAddress() const override { return kDeviceAddress; }

  UniqueId GetUniqueId() const override { return kUniqueId; }
};

}  // namespace

class BleV2MediumTest : public testing::Test {
 public:
  BleV2MediumTest() = default;
  ~BleV2MediumTest() override = default;
  BleV2MediumTest(const BleV2MediumTest&) = delete;
  BleV2MediumTest& operator=(const BleV2MediumTest&) = delete;

  void SetUp() override {
    auto fake_adapter = std::make_unique<bluetooth::FakeAdapter>();
    fake_adapter_ = fake_adapter.get();
    fake_adapter_->SetExtendedAdvertisementSupport(true);

    mojo::PendingRemote<bluetooth::mojom::Adapter> pending_adapter;

    mojo::MakeSelfOwnedReceiver(
        std::move(fake_adapter),
        pending_adapter.InitWithNewPipeAndPassReceiver());

    remote_adapter_.Bind(std::move(pending_adapter),
                         /*bind_task_runner=*/nullptr);

    ble_v2_medium_ = std::make_unique<BleV2Medium>(remote_adapter_);
  }

  void OnPeripheralDiscovered() {
    if (on_expected_peripherals_discovered_callback_) {
      std::move(on_expected_peripherals_discovered_callback_).Run();
    }
  }

  void SetOnExpectedPeripheralsDiscoveredCallback(base::OnceClosure callback) {
    on_expected_peripherals_discovered_callback_ = std::move(callback);
  }

  bluetooth::mojom::DeviceInfoPtr CreateDeviceInfo(
      const std::string& address,
      const base::flat_map<device::BluetoothUUID, std::vector<uint8_t>>&
          service_data_map) {
    // Do not set |name|. This reflects Chrome's usual representation of a BLE
    // advertisement.
    auto device_info = bluetooth::mojom::DeviceInfo::New();
    device_info->address = address;
    device_info->name_for_display = address;
    device_info->service_data_map = service_data_map;

    // TODO(b/285637726): Once the bug is resolved, test whether the
    // service_uuids are being populated correctly.
    for (auto pair : service_data_map) {
      device_info->service_uuids.push_back(pair.first);
    }

    return device_info;
  }

  void SetUpGattServerForAdvertising(bool should_register_succeed) {
    base::ScopedAllowBaseSyncPrimitivesForTesting allow_sync_primitives;
    fake_adapter_->is_dual_role_supported_ = true;
    gatt_server_ = ble_v2_medium_->StartGattServer({});
    EXPECT_TRUE(gatt_server_);

    // Set up a `GattService` that will succeed/fail registration when
    // `RegisterGattServices()` is called by `BleV2Medium`.
    auto fake_gatt_service = std::make_unique<bluetooth::FakeGattService>();
    fake_gatt_service->SetShouldRegisterSucceed(should_register_succeed);
    fake_gatt_service->SetCreateCharacteristicResult(/*success=*/true);
    fake_adapter_->SetCreateLocalGattServiceResult(
        /*gatt_service=*/std::move(fake_gatt_service));
    auto gatt_characteristic = gatt_server_->CreateCharacteristic(
        /*service_uuid=*/Uuid(/*data=*/kServiceId),
        /*characteristic_uuid=*/Uuid(/*data=*/kCharacteristicUuid),
        /*permission=*/api::ble_v2::GattCharacteristic::Permission::kRead,
        /*property=*/api::ble_v2::GattCharacteristic::Property::kRead);
    EXPECT_TRUE(gatt_characteristic);
  }

  void CallStartAdvertisingForGattService(bool expected_success) {
    api::ble_v2::BleAdvertisementData advertising_data;
    advertising_data.is_extended_advertisement = true;
    advertising_data.service_data.insert(
        {kFastAdvertisementServiceUuid1, kDeviceServiceData1ByteArray});

    base::ScopedAllowBaseSyncPrimitivesForTesting allow_sync_primitives;
    EXPECT_EQ(expected_success,
              ble_v2_medium_->StartAdvertising(
                  advertising_data,
                  {.tx_power_level = api::ble_v2::TxPowerLevel::kHigh,
                   .is_connectable = true}));
  }

  void CallConnectToGattServer(bool expected_success) {
    base::ScopedAllowBaseSyncPrimitivesForTesting allow_sync_primitives;
    FakeBleV2RemotePeripheral peripheral;
    auto gatt_client = ble_v2_medium_->ConnectToGattServer(
        peripheral, api::ble_v2::TxPowerLevel::kHigh,
        /*callback=*/{});
    EXPECT_EQ(expected_success, (gatt_client != nullptr));
  }

 protected:
  base::test::TaskEnvironment task_environment_;
  base::HistogramTester histogram_tester_;
  mojo::SharedRemote<bluetooth::mojom::Adapter> remote_adapter_;
  std::unique_ptr<api::ble_v2::GattServer> gatt_server_;
  std::unique_ptr<BleV2Medium::ScanningSession> scanning_session_;
  base::OnceClosure on_expected_peripherals_discovered_callback_;
  base::OnceClosure on_discovery_session_destroyed_callback_;
  raw_ptr<bluetooth::FakeAdapter> fake_adapter_;
  std::unique_ptr<BleV2Medium> ble_v2_medium_;
};

TEST_F(BleV2MediumTest, TestScanning_OneService) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2},
      /*disabled_features=*/{});

  CountDownLatch scanning_started_latch(1);
  CountDownLatch found_advertisement_latch(1);
  api::ble_v2::BleMedium::ScanningCallback scanning_callback = {
      .start_scanning_result =
          [&scanning_started_latch](absl::Status status) {
            scanning_started_latch.CountDown();
          },
      .advertisement_found_cb =
          [this, &found_advertisement_latch](
              api::ble_v2::BlePeripheral& peripheral,
              const api::ble_v2::BleAdvertisementData& advertisement_data) {
            EXPECT_EQ(peripheral.GetAddress(), kDeviceAddress);
            EXPECT_EQ(advertisement_data.service_data
                          .find(kFastAdvertisementServiceUuid1)
                          ->second,
                      kDeviceServiceData1ByteArray);
            found_advertisement_latch.CountDown();
            OnPeripheralDiscovered();

            EXPECT_TRUE(ble_v2_medium_->GetRemotePeripheral(
                peripheral.GetUniqueId(),
                [&](api::ble_v2::BlePeripheral& device) {
                  EXPECT_EQ(kDeviceAddress, device.GetAddress());
                }));
          }};

  auto scanning_session = ble_v2_medium_->StartScanning(
      kFastAdvertisementServiceUuid1, {}, std::move(scanning_callback));
  EXPECT_NE(scanning_session, nullptr);

  base::flat_map<device::BluetoothUUID, std::vector<uint8_t>> service_data_map;
  service_data_map.insert_or_assign(kService1BluetoothUuid,
                                    GetByteVector(kDeviceServiceData1Str));

  EXPECT_TRUE(scanning_started_latch.Await().Ok());
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartScanning.Result",
      /*bucket: Success=*/1, 1);

  base::RunLoop run_loop;
  SetOnExpectedPeripheralsDiscoveredCallback(run_loop.QuitClosure());
  fake_adapter_->NotifyDeviceAdded(
      CreateDeviceInfo(kDeviceAddress, service_data_map));
  run_loop.Run();

  EXPECT_TRUE(found_advertisement_latch.Await().Ok());
  EXPECT_TRUE(scanning_session->stop_scanning().ok());
}

TEST_F(BleV2MediumTest, TestScanning_MultipleSessions) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2},
      /*disabled_features=*/{});

  // Expects session 1 found one advertisement.
  CountDownLatch session_1_found_advertisement_latch(1);
  // Expects session 2 found two advertisement.
  CountDownLatch session_2_found_advertisement_latch(2);
  // Expects both sessions succeeded to start scanning.
  CountDownLatch scanning_started_latch(2);

  api::ble_v2::BleMedium::ScanningCallback scanning_callback_1 = {
      .start_scanning_result =
          [&scanning_started_latch](absl::Status status) {
            scanning_started_latch.CountDown();
          },
      .advertisement_found_cb =
          [this, &session_1_found_advertisement_latch](
              api::ble_v2::BlePeripheral& peripheral,
              const api::ble_v2::BleAdvertisementData& advertisement_data) {
            session_1_found_advertisement_latch.CountDown();
            OnPeripheralDiscovered();

            EXPECT_TRUE(ble_v2_medium_->GetRemotePeripheral(
                peripheral.GetUniqueId(),
                [&](api::ble_v2::BlePeripheral& device) {
                  EXPECT_EQ(kDeviceAddress, device.GetAddress());
                }));
          }};
  api::ble_v2::BleMedium::ScanningCallback scanning_callback_2 = {
      .start_scanning_result =
          [&scanning_started_latch](absl::Status status) {
            scanning_started_latch.CountDown();
          },
      .advertisement_found_cb =
          [this, &session_2_found_advertisement_latch](
              api::ble_v2::BlePeripheral& peripheral,
              const api::ble_v2::BleAdvertisementData& advertisement_data) {
            session_2_found_advertisement_latch.CountDown();
            OnPeripheralDiscovered();

            EXPECT_TRUE(ble_v2_medium_->GetRemotePeripheral(
                peripheral.GetUniqueId(),
                [&](api::ble_v2::BlePeripheral& device) {
                  EXPECT_EQ(kDeviceAddress, device.GetAddress());
                }));
          }};

  auto scanning_session_1 = ble_v2_medium_->StartScanning(
      kFastAdvertisementServiceUuid1, {}, std::move(scanning_callback_1));
  auto scanning_session_2 = ble_v2_medium_->StartScanning(
      kFastAdvertisementServiceUuid1, {}, std::move(scanning_callback_2));
  // Verify both sessions start successfully.
  EXPECT_NE(scanning_session_1, nullptr);
  EXPECT_NE(scanning_session_2, nullptr);
  EXPECT_TRUE(scanning_started_latch.Await().Ok());
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartScanning.Result",
      /*bucket: Success=*/1, 2);

  base::flat_map<device::BluetoothUUID, std::vector<uint8_t>> service_data_map;
  service_data_map.insert_or_assign(kService1BluetoothUuid,
                                    GetByteVector(kDeviceServiceData1Str));
  base::RunLoop run_loop;
  SetOnExpectedPeripheralsDiscoveredCallback(run_loop.QuitClosure());
  // Both session should see the advertisement.
  fake_adapter_->NotifyDeviceAdded(
      CreateDeviceInfo(kDeviceAddress, service_data_map));
  run_loop.Run();

  // Verify session 1 did see one advertisement and stop successfully.
  EXPECT_TRUE(session_1_found_advertisement_latch.Await().Ok());
  EXPECT_TRUE(scanning_session_1->stop_scanning().ok());

  base::RunLoop run_loop_2;
  SetOnExpectedPeripheralsDiscoveredCallback(run_loop_2.QuitClosure());
  // Only session 2 should see the advertisement.
  fake_adapter_->NotifyDeviceAdded(
      CreateDeviceInfo(kDeviceAddress, service_data_map));
  run_loop_2.Run();

  // Verify session 2 did see two advertisements and stop successfully.
  EXPECT_TRUE(session_2_found_advertisement_latch.Await().Ok());
  EXPECT_TRUE(scanning_session_2->stop_scanning().ok());
}

TEST_F(BleV2MediumTest, TestScanning_IgnoreIrrelevantAdvertisement) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2},
      /*disabled_features=*/{});

  CountDownLatch scanning_started_latch(1);
  api::ble_v2::BleMedium::ScanningCallback scanning_callback = {
      .start_scanning_result =
          [&scanning_started_latch](absl::Status status) {
            scanning_started_latch.CountDown();
          },
      .advertisement_found_cb =
          [](api::ble_v2::BlePeripheral& peripheral,
             const api::ble_v2::BleAdvertisementData& advertisement_data) {
            // should not reached here for irrelavant advertisement.
            EXPECT_TRUE(false);
          }};

  auto scanning_session = ble_v2_medium_->StartScanning(
      kFastAdvertisementServiceUuid1, {}, std::move(scanning_callback));
  EXPECT_NE(scanning_session, nullptr);

  base::flat_map<device::BluetoothUUID, std::vector<uint8_t>> service_data_map;
  // Scan for kService1BluetoothUuid but notify with kService2BluetoothUuid.
  service_data_map.insert_or_assign(kService2BluetoothUuid,
                                    GetByteVector(kDeviceServiceData1Str));

  EXPECT_TRUE(scanning_started_latch.Await().Ok());
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartScanning.Result",
      /*bucket: Success=*/1, 1);

  base::RunLoop run_loop;
  fake_adapter_->NotifyDeviceAdded(
      CreateDeviceInfo(kDeviceAddress, service_data_map));
  fake_adapter_->SetDiscoverySessionDestroyedCallback(run_loop.QuitClosure());

  EXPECT_TRUE(scanning_session->stop_scanning().ok());
  run_loop.Run();
}

TEST_F(BleV2MediumTest, TestAdvertising_AdapterFails) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2},
      /*disabled_features=*/{});

  fake_adapter_->SetShouldAdvertisementRegistrationSucceed(false);
  api::ble_v2::BleAdvertisementData advertising_data;
  advertising_data.is_extended_advertisement = false;
  advertising_data.service_data.insert(
      {kFastAdvertisementServiceUuid1, kDeviceServiceData1ByteArray});
  EXPECT_FALSE(ble_v2_medium_->StartAdvertising(
      advertising_data, {.tx_power_level = api::ble_v2::TxPowerLevel::kLow,
                         .is_connectable = true}));
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result",
      /*bucket: Failure=*/0, 1);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result.RegularAdvertisement",
      /*bucket: Failure=*/0, 1);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.FailureReason",
      metrics::StartAdvertisingFailureReason::
          kAdapterRegisterAdvertisementFailed,
      1);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.FailureReason."
      "RegularAdvertisement",
      metrics::StartAdvertisingFailureReason::
          kAdapterRegisterAdvertisementFailed,
      1);
}

TEST_F(BleV2MediumTest, TestAdvertising_AdapterFailsInAsyncStartAdvertising) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2},
      /*disabled_features=*/{});

  fake_adapter_->SetShouldAdvertisementRegistrationSucceed(false);
  api::ble_v2::BleAdvertisementData advertising_data;
  advertising_data.is_extended_advertisement = false;
  advertising_data.service_data.insert(
      {kFastAdvertisementServiceUuid1, kDeviceServiceData1ByteArray});
  auto advertising_session = ble_v2_medium_->StartAdvertising(
      advertising_data,
      {.tx_power_level = api::ble_v2::TxPowerLevel::kLow,
       .is_connectable = true},
      api::ble_v2::BleMedium::AdvertisingCallback{
          .start_advertising_result =
              [this](absl::Status status) {
                EXPECT_FALSE(status.ok());
                histogram_tester_.ExpectBucketCount(
                    "Nearby.Connections.BleV2.StartAdvertising.Result",
                    /*bucket: Failure=*/0, 1);
                histogram_tester_.ExpectBucketCount(
                    "Nearby.Connections.BleV2.StartAdvertising.Result."
                    "RegularAdvertisement",
                    /*bucket: Failure=*/0, 1);
                histogram_tester_.ExpectBucketCount(
                    "Nearby.Connections.BleV2.StartAdvertising.FailureReason",
                    metrics::StartAdvertisingFailureReason::
                        kAdapterRegisterAdvertisementFailed,
                    1);
                histogram_tester_.ExpectBucketCount(
                    "Nearby.Connections.BleV2.StartAdvertising.FailureReason."
                    "RegularAdvertisement",
                    metrics::StartAdvertisingFailureReason::
                        kAdapterRegisterAdvertisementFailed,
                    1);
              },
      });
  EXPECT_EQ(advertising_session, nullptr);
}

TEST_F(BleV2MediumTest, TestAdvertising_FastAdvertisementSuccess) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2},
      /*disabled_features=*/{});

  fake_adapter_->SetShouldAdvertisementRegistrationSucceed(true);
  api::ble_v2::BleAdvertisementData advertising_data;
  advertising_data.is_extended_advertisement = false;
  advertising_data.service_data.insert(
      {kFastAdvertisementServiceUuid1, kDeviceServiceData1ByteArray});
  EXPECT_TRUE(ble_v2_medium_->StartAdvertising(
      advertising_data, {.tx_power_level = api::ble_v2::TxPowerLevel::kLow,
                         .is_connectable = true}));
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result",
      /*bucket: Success=*/1, 1);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result.RegularAdvertisement",
      /*bucket: Success=*/1, 1);
}

TEST_F(BleV2MediumTest, TestAdvertising_ExtendedAdvertisementNotSupported) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2},
      /*disabled_features=*/{
          ::features::kEnableNearbyBleV2ExtendedAdvertising});
  EXPECT_FALSE(ble_v2_medium_->IsExtendedAdvertisementsAvailable());

  fake_adapter_->SetShouldAdvertisementRegistrationSucceed(true);
  api::ble_v2::BleAdvertisementData advertising_data;
  advertising_data.is_extended_advertisement = true;
  advertising_data.service_data.insert(
      {kFastAdvertisementServiceUuid1, kDeviceServiceData1ByteArray});
  EXPECT_FALSE(ble_v2_medium_->StartAdvertising(
      advertising_data, {.tx_power_level = api::ble_v2::TxPowerLevel::kHigh,
                         .is_connectable = true}));
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result",
      /*bucket: Failure=*/0, 1);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result.ExtendedAdvertisement",
      /*bucket: Failure=*/0, 1);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.FailureReason",
      metrics::StartAdvertisingFailureReason::kNoExtendedAdvertisementSupport,
      1);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.FailureReason."
      "ExtendedAdvertisement",
      metrics::StartAdvertisingFailureReason::kNoExtendedAdvertisementSupport,
      1);
}

TEST_F(BleV2MediumTest, TestAdvertising_ExtendedAdvertisementSupported) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2,
                            ::features::kEnableNearbyBleV2ExtendedAdvertising},
      /*disabled_features=*/{});
  EXPECT_TRUE(ble_v2_medium_->IsExtendedAdvertisementsAvailable());

  fake_adapter_->SetShouldAdvertisementRegistrationSucceed(true);
  api::ble_v2::BleAdvertisementData advertising_data;
  advertising_data.is_extended_advertisement = true;
  advertising_data.service_data.insert(
      {kFastAdvertisementServiceUuid1, kDeviceServiceData1ByteArray});
  EXPECT_TRUE(ble_v2_medium_->StartAdvertising(
      advertising_data, {.tx_power_level = api::ble_v2::TxPowerLevel::kHigh,
                         .is_connectable = true}));
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result",
      /*bucket: Success=*/1, 1);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result.ExtendedAdvertisement",
      /*bucket: Success=*/1, 1);
}

TEST_F(BleV2MediumTest, TestAdvertising_EmptyAdvertisingData) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2},
      /*disabled_features=*/{});

  fake_adapter_->SetShouldAdvertisementRegistrationSucceed(true);
  api::ble_v2::BleAdvertisementData advertising_data = {};
  // Passing in empty advertisement data is unexpected, but is still
  // expected to pass.
  EXPECT_TRUE(ble_v2_medium_->StartAdvertising(advertising_data, {}));
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result",
      /*bucket: Success=*/1, 1);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result.RegularAdvertisement",
      /*bucket: Success=*/1, 1);
}

TEST_F(BleV2MediumTest, TestAdvertising_MultipleStartAdvertisingSuccess) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2,
                            ::features::kEnableNearbyBleV2ExtendedAdvertising},
      /*disabled_features=*/{});
  EXPECT_TRUE(ble_v2_medium_->IsExtendedAdvertisementsAvailable());
  EXPECT_FALSE(fake_adapter_->GetRegisteredAdvertisementServiceData(
      kService1BluetoothUuid));

  fake_adapter_->SetShouldAdvertisementRegistrationSucceed(true);
  api::ble_v2::BleAdvertisementData advertising_data1;
  advertising_data1.is_extended_advertisement = false;
  advertising_data1.service_data.insert(
      {kFastAdvertisementServiceUuid1, kDeviceServiceData1ByteArray});
  EXPECT_TRUE(ble_v2_medium_->StartAdvertising(
      advertising_data1, {.tx_power_level = api::ble_v2::TxPowerLevel::kHigh,
                          .is_connectable = true}));
  EXPECT_TRUE(fake_adapter_->GetRegisteredAdvertisementServiceData(
      kService1BluetoothUuid));
  // TODO(b/330759317): Refactor FakeAdapter to hold multiple advertisements per
  // Bluetooth UUID, and remove private variable access here.
  EXPECT_EQ(1u, ble_v2_medium_->registered_advertisements_map_
                    .at(kService1BluetoothUuid)
                    .size());
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result",
      /*bucket: Success=*/1, 1);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result.RegularAdvertisement",
      /*bucket: Success=*/1, 1);

  // We are expected to be able to concurrently advertise multiple
  // advertisements registered to the same service UUID.
  api::ble_v2::BleAdvertisementData advertising_data2;
  advertising_data2.is_extended_advertisement = true;
  advertising_data2.service_data.insert(
      {kFastAdvertisementServiceUuid1, kDeviceServiceData1ByteArray});
  EXPECT_TRUE(ble_v2_medium_->StartAdvertising(
      advertising_data2, {.tx_power_level = api::ble_v2::TxPowerLevel::kHigh,
                          .is_connectable = true}));
  EXPECT_TRUE(fake_adapter_->GetRegisteredAdvertisementServiceData(
      kService1BluetoothUuid));
  // TODO(b/330759317): Refactor FakeAdapter to hold multiple advertisements per
  // Bluetooth UUID, and remove private variable access here.
  EXPECT_EQ(2u, ble_v2_medium_->registered_advertisements_map_
                    .at(kService1BluetoothUuid)
                    .size());
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result",
      /*bucket: Success=*/1, 2);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result.ExtendedAdvertisement",
      /*bucket: Success=*/1, 1);
}

TEST_F(BleV2MediumTest, TestAdvertising_MultipleAdvertisementDataSuccess) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2},
      /*disabled_features=*/{});

  fake_adapter_->SetShouldAdvertisementRegistrationSucceed(true);
  api::ble_v2::BleAdvertisementData advertising_data;
  advertising_data.is_extended_advertisement = false;
  EXPECT_FALSE(fake_adapter_->GetRegisteredAdvertisementServiceData(
      kService1BluetoothUuid));
  EXPECT_FALSE(fake_adapter_->GetRegisteredAdvertisementServiceData(
      kService2BluetoothUuid));

  // Currently, NC does not pass in multiple advertisement data per call
  // to StartAdvertising. However, we are expected to support that
  // capability and start advertising for each one. This is a map, so
  // service UUIDs will be different in this case.
  advertising_data.service_data = {
      {kFastAdvertisementServiceUuid1, kDeviceServiceData1ByteArray},
      {kTestServiceUuid2, kDeviceServiceData2ByteArray}};
  EXPECT_TRUE(ble_v2_medium_->StartAdvertising(
      advertising_data, {.tx_power_level = api::ble_v2::TxPowerLevel::kLow,
                         .is_connectable = true}));
  EXPECT_TRUE(fake_adapter_->GetRegisteredAdvertisementServiceData(
      kService1BluetoothUuid));
  EXPECT_TRUE(fake_adapter_->GetRegisteredAdvertisementServiceData(
      kService2BluetoothUuid));
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result",
      /*bucket: Success=*/1, 1);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result.RegularAdvertisement",
      /*bucket: Success=*/1, 1);
}

TEST_F(BleV2MediumTest, TestAdvertising_StopAdvertisingClearsRegistrationMap) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2},
      /*disabled_features=*/{});

  fake_adapter_->SetShouldAdvertisementRegistrationSucceed(true);
  api::ble_v2::BleAdvertisementData advertising_data;
  advertising_data.is_extended_advertisement = false;
  advertising_data.service_data.insert(
      {kFastAdvertisementServiceUuid1, kDeviceServiceData1ByteArray});
  EXPECT_FALSE(fake_adapter_->GetRegisteredAdvertisementServiceData(
      kService1BluetoothUuid));

  EXPECT_TRUE(ble_v2_medium_->StartAdvertising(
      advertising_data, {.tx_power_level = api::ble_v2::TxPowerLevel::kLow,
                         .is_connectable = true}));
  EXPECT_TRUE(fake_adapter_->GetRegisteredAdvertisementServiceData(
      kService1BluetoothUuid));
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result",
      /*bucket: Success=*/1, 1);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result.RegularAdvertisement",
      /*bucket: Success=*/1, 1);

  {
    base::RunLoop run_loop;
    fake_adapter_->SetAdvertisementDestroyedCallback(run_loop.QuitClosure());
    EXPECT_TRUE(ble_v2_medium_->StopAdvertising());
    run_loop.Run();
  }
  EXPECT_FALSE(fake_adapter_->GetRegisteredAdvertisementServiceData(
      kService1BluetoothUuid));
}

TEST_F(BleV2MediumTest, TestAdvertising_StartAndStopAsyncAdvertising) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2},
      /*disabled_features=*/{});

  fake_adapter_->SetShouldAdvertisementRegistrationSucceed(true);
  api::ble_v2::BleAdvertisementData advertising_data;
  advertising_data.is_extended_advertisement = false;
  advertising_data.service_data.insert(
      {kFastAdvertisementServiceUuid1, kDeviceServiceData1ByteArray});
  EXPECT_FALSE(fake_adapter_->GetRegisteredAdvertisementServiceData(
      kService1BluetoothUuid));

  auto advertising_session = ble_v2_medium_->StartAdvertising(
      advertising_data,
      {.tx_power_level = api::ble_v2::TxPowerLevel::kLow,
       .is_connectable = true},
      api::ble_v2::BleMedium::AdvertisingCallback{
          .start_advertising_result =
              [this](absl::Status status) {
                EXPECT_TRUE(status.ok());
                histogram_tester_.ExpectBucketCount(
                    "Nearby.Connections.BleV2.StartAdvertising.Result",
                    /*bucket: Success=*/1, 1);
                histogram_tester_.ExpectBucketCount(
                    "Nearby.Connections.BleV2.StartAdvertising.Result."
                    "RegularAdvertisement",
                    /*bucket: Success=*/1, 1);
              },
      });
  EXPECT_NE(advertising_session, nullptr);

  EXPECT_TRUE(fake_adapter_->GetRegisteredAdvertisementServiceData(
      kService1BluetoothUuid));

  {
    base::RunLoop run_loop;
    fake_adapter_->SetAdvertisementDestroyedCallback(run_loop.QuitClosure());
    auto status = advertising_session->stop_advertising();
    EXPECT_TRUE(status.ok());
    run_loop.Run();
  }
  EXPECT_FALSE(fake_adapter_->GetRegisteredAdvertisementServiceData(
      kService1BluetoothUuid));
}

TEST_F(BleV2MediumTest, IsExtendedAdvertisementsAvailable_FlagDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2},
      /*disabled_features=*/{
          ::features::kEnableNearbyBleV2ExtendedAdvertising});

  // If the flag is disabled, always return false.
  fake_adapter_->SetExtendedAdvertisementSupport(true);
  EXPECT_FALSE(ble_v2_medium_->IsExtendedAdvertisementsAvailable());

  fake_adapter_->SetExtendedAdvertisementSupport(false);
  EXPECT_FALSE(ble_v2_medium_->IsExtendedAdvertisementsAvailable());
}

TEST_F(BleV2MediumTest, IsExtendedAdvertisementsAvailable_FlagEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2,
                            ::features::kEnableNearbyBleV2ExtendedAdvertising},
      /*disabled_features=*/{});

  // If the flag is enabled, return whether the device has hardware support.
  fake_adapter_->SetExtendedAdvertisementSupport(true);
  EXPECT_TRUE(ble_v2_medium_->IsExtendedAdvertisementsAvailable());

  fake_adapter_->SetExtendedAdvertisementSupport(false);
  EXPECT_FALSE(ble_v2_medium_->IsExtendedAdvertisementsAvailable());
}

TEST_F(BleV2MediumTest, StartGattServer_DualRoleSupported_FlagDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2},
      /*disabled_features=*/{::features::kEnableNearbyBleV2GattServer});

  fake_adapter_->is_dual_role_supported_ = true;
  auto gatt_server = ble_v2_medium_->StartGattServer({});
  EXPECT_FALSE(gatt_server);
}

TEST_F(BleV2MediumTest, StartGattServer_DualRoleSupported_FlagEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2,
                            ::features::kEnableNearbyBleV2GattServer},
      /*disabled_features=*/{});

  fake_adapter_->is_dual_role_supported_ = true;
  auto gatt_server = ble_v2_medium_->StartGattServer({});
  EXPECT_TRUE(gatt_server);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.ScatternetDualRoleSupported",
      /*bucket: DualRole is supported=*/1, 1);

  // Clean up ble_v2_medium_ to prevent dangling raw_ptr in unit tests.
  ble_v2_medium_.reset();
}

TEST_F(BleV2MediumTest, StartGattServer_DualRoleNotSupported) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2,
                            ::features::kEnableNearbyBleV2GattServer},
      /*disabled_features=*/{});

  fake_adapter_->is_dual_role_supported_ = false;
  auto gatt_server = ble_v2_medium_->StartGattServer({});
  EXPECT_FALSE(gatt_server);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.ScatternetDualRoleSupported",
      /*bucket: DualRole is not supported=*/0, 1);
}

TEST_F(BleV2MediumTest, StartAdvertising_RegisterGattServer_Success) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2,
                            ::features::kEnableNearbyBleV2GattServer},
      /*disabled_features=*/{});

  SetUpGattServerForAdvertising(/*should_register_succeed=*/true);

  base::RunLoop run_loop;
  base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})
      ->PostTaskAndReply(
          FROM_HERE,
          base::BindOnce(&BleV2MediumTest::CallStartAdvertisingForGattService,
                         base::Unretained(this), /*expected_result=*/true),
          run_loop.QuitClosure());
  run_loop.Run();

  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result",
      /*bucket: Success=*/1, 1);
  histogram_tester_.ExpectTotalCount(
      "Nearby.Connections.BleV2.StartAdvertising.FailureReason", 0);
}

TEST_F(BleV2MediumTest, StartAdvertising_RegisterGattServer_Failure) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2,
                            ::features::kEnableNearbyBleV2GattServer},
      /*disabled_features=*/{});

  SetUpGattServerForAdvertising(/*should_register_succeed=*/false);

  base::RunLoop run_loop;
  base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})
      ->PostTaskAndReply(
          FROM_HERE,
          base::BindOnce(&BleV2MediumTest::CallStartAdvertisingForGattService,
                         base::Unretained(this), /*expected_result=*/false),
          run_loop.QuitClosure());
  run_loop.Run();

  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result",
      /*bucket: Failure=*/0, 1);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.Result."
      "RegularAdvertisement",
      /*bucket: Failure=*/0, 1);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.StartAdvertising.FailureReason",
      metrics::StartAdvertisingFailureReason::kFailedToRegisterGattServices, 1);
}

TEST_F(BleV2MediumTest, ConnectToGattServer_Success) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2},
      /*disabled_features=*/{});

  fake_adapter_->SetConnectToDeviceResult(
      bluetooth::mojom::ConnectResult::SUCCESS,
      std::make_unique<bluetooth::FakeDevice>());
  base::RunLoop run_loop;
  base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})
      ->PostTaskAndReply(
          FROM_HERE,
          base::BindOnce(&BleV2MediumTest::CallConnectToGattServer,
                         base::Unretained(this), /*expected_result=*/true),
          run_loop.QuitClosure());
  run_loop.Run();
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.ConnectToGattServer.Result",
      /*bucket: success=*/1, 1);
  histogram_tester_.ExpectTotalCount(
      "Nearby.Connections.BleV2.ConnectToGattServer.Duration", 1);
}

TEST_F(BleV2MediumTest, ConnectToGattServer_Failure) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{::features::kEnableNearbyBleV2},
      /*disabled_features=*/{});

  fake_adapter_->SetConnectToDeviceResult(
      bluetooth::mojom::ConnectResult::FAILED, /*fake_device=*/nullptr);
  base::RunLoop run_loop;
  base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})
      ->PostTaskAndReply(
          FROM_HERE,
          base::BindOnce(&BleV2MediumTest::CallConnectToGattServer,
                         base::Unretained(this), /*expected_result=*/false),
          run_loop.QuitClosure());
  run_loop.Run();
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.ConnectToGattServer.Result",
      /*bucket: failure=*/0, 1);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.ConnectToGattServer.FailureReason",
      /*bucket: FAILED=*/5, 1);
  histogram_tester_.ExpectTotalCount(
      "Nearby.Connections.BleV2.ConnectToGattServer.Duration", 0);
}

}  // namespace nearby::chrome