chromium/chrome/services/sharing/nearby/platform/ble_v2_gatt_server_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_gatt_server.h"

#include <memory>

#include "base/containers/span.h"
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "chrome/services/sharing/nearby/platform/bluetooth_adapter.h"
#include "chrome/services/sharing/nearby/platform/bluetooth_utils.h"
#include "chrome/services/sharing/nearby/test_support/fake_adapter.h"
#include "chrome/services/sharing/nearby/test_support/fake_gatt_service.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/bindings/shared_remote.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/nearby/src/internal/platform/uuid.h"

namespace {

const device::BluetoothUUID kServiceId =
    device::BluetoothUUID("12345678-1234-5678-9abc-def123456789");
const device::BluetoothUUID kCharacteristicUuid1 =
    device::BluetoothUUID("00001101-0000-1000-8000-00805f9b34fb");
const device::BluetoothUUID kCharacteristicUuid2 =
    device::BluetoothUUID("00001102-0000-1000-8000-00805f9b34fc");
const std::string kNewCharacteristicValue = "123456";
const int kPartialBufferOffset = 2;

class FakeGattService : public nearby::chrome::BleV2GattServer::GattService {
 public:
  explicit FakeGattService(base::OnceClosure on_destroyed_callback)
      : on_destroyed_callback_(std::move(on_destroyed_callback)) {}

  ~FakeGattService() override {
    if (on_destroyed_callback_) {
      std::move(on_destroyed_callback_).Run();
    }
  }

 private:
  base::OnceClosure on_destroyed_callback_;
};

class FakeGattServiceFactory
    : public nearby::chrome::BleV2GattServer::GattService::Factory {
 public:
  std::unique_ptr<nearby::chrome::BleV2GattServer::GattService> Create()
      override {
    return std::make_unique<FakeGattService>(
        std::move(next_fake_gatt_service_destroyed_callback_));
  }

  void SetNextFakeGattServiceDestroyedCallback(
      base::OnceClosure on_destroyed_callback) {
    next_fake_gatt_service_destroyed_callback_ =
        std::move(on_destroyed_callback);
  }

 private:
  base::OnceClosure next_fake_gatt_service_destroyed_callback_;
};

}  // namespace

namespace nearby::chrome {

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

  void SetUp() override {
    auto fake_adapter = std::make_unique<bluetooth::FakeAdapter>();
    fake_adapter_ = fake_adapter.get();
    mojo::PendingRemote<bluetooth::mojom::Adapter> pending_adapter;
    mojo::MakeSelfOwnedReceiver(std::move(fake_adapter),
                                remote_adapter_.BindNewPipeAndPassReceiver());

    auto fake_gatt_service_factory = std::make_unique<FakeGattServiceFactory>();
    fake_gatt_service_factory_ = fake_gatt_service_factory.get();
    ble_v2_gatt_server_ = std::make_unique<BleV2GattServer>(
        remote_adapter_, std::move(fake_gatt_service_factory));
  }

  void CallCreateCharacteristic(
      device::BluetoothUUID characteristic_uuid,
      bool expected_success,
      api::ble_v2::GattCharacteristic::Permission permission =
          api::ble_v2::GattCharacteristic::Permission::kRead,
      api::ble_v2::GattCharacteristic::Property property =
          api::ble_v2::GattCharacteristic::Property::kRead) {
    gatt_characteristic_ = ble_v2_gatt_server_->CreateCharacteristic(
        /*service_uuid=*/BluetoothUuidToNearbyUuid(kServiceId),
        /*characteristic_uuid=*/BluetoothUuidToNearbyUuid(characteristic_uuid),
        /*permission=*/permission,
        /*property=*/property);
    EXPECT_EQ(expected_success, gatt_characteristic_.has_value());
  }

  void CallUpdateCharacteristic(
      device::BluetoothUUID characteristic_uuid,
      bool expected_success,
      api::ble_v2::GattCharacteristic::Permission permission =
          api::ble_v2::GattCharacteristic::Permission::kRead,
      api::ble_v2::GattCharacteristic::Property property =
          api::ble_v2::GattCharacteristic::Property::kRead) {
    api::ble_v2::GattCharacteristic gatt_characteristic = {
        BluetoothUuidToNearbyUuid(characteristic_uuid),
        BluetoothUuidToNearbyUuid(kServiceId), permission, property};
    bool result = ble_v2_gatt_server_->UpdateCharacteristic(
        /*characteristic=*/gatt_characteristic,
        /*value=*/nearby::ByteArray(kNewCharacteristicValue));
    EXPECT_EQ(expected_success, result);
  }

 protected:
  base::test::TaskEnvironment task_environment_;
  std::optional<api::ble_v2::GattCharacteristic> gatt_characteristic_;
  raw_ptr<bluetooth::FakeAdapter> fake_adapter_;
  mojo::SharedRemote<bluetooth::mojom::Adapter> remote_adapter_;
  std::unique_ptr<BleV2GattServer> ble_v2_gatt_server_;
  raw_ptr<FakeGattServiceFactory> fake_gatt_service_factory_;
  base::HistogramTester histogram_tester_;
};

TEST_F(BleV2GattServerTest, GetBlePeripheral) {
  BluetoothAdapter& ble_v2_periphral = ble_v2_gatt_server_->GetBlePeripheral();
  EXPECT_EQ(fake_adapter_->address_, ble_v2_periphral.GetAddress());
}

TEST_F(BleV2GattServerTest,
       CreateCharacteristic_CreateGattService_AlreadyExists) {
  auto fake_gatt_service = std::make_unique<bluetooth::FakeGattService>();
  fake_gatt_service->SetCreateCharacteristicResult(/*success=*/true);
  auto* fake_gatt_service_ptr = fake_gatt_service.get();
  fake_adapter_->SetCreateLocalGattServiceResult(
      /*gatt_service=*/std::move(fake_gatt_service));

  // First time, expect a call to the browser process to get or create
  // a `GattService` since it doesn't exist yet.
  {
    base::MockCallback<base::OnceClosure> callback;
    EXPECT_CALL(callback, Run).Times(1);
    fake_adapter_->SetCreateLocalGattServiceCallback(callback.Get());
    CallCreateCharacteristic(/*characteristic_uuid=*/kCharacteristicUuid1,
                             /*expected_success=*/true);
    histogram_tester_.ExpectBucketCount(
        "Nearby.Connections.BleV2.GattServer.CreateLocalGattService.Result",
        /*bucket: success=*/1, 1);
    histogram_tester_.ExpectBucketCount(
        "Nearby.Connections.BleV2.GattServer.CreateCharacteristic.Result",
        /*bucket: success=*/1, 1);
  }

  // Second time, expect no call to browser process since it already
  // exists for the same service id.
  {
    base::MockCallback<base::OnceClosure> callback;
    EXPECT_CALL(callback, Run).Times(0);
    fake_adapter_->SetCreateLocalGattServiceCallback(callback.Get());
    CallCreateCharacteristic(
        /*characteristic_uuid=*/kCharacteristicUuid2,
        /*expected_success=*/true);
    histogram_tester_.ExpectBucketCount(
        "Nearby.Connections.BleV2.GattServer.CreateLocalGattService.Result",
        /*bucket: success=*/1, 1);
    histogram_tester_.ExpectBucketCount(
        "Nearby.Connections.BleV2.GattServer.CreateCharacteristic.Result",
        /*bucket: success=*/1, 2);
  }

  EXPECT_EQ(2, fake_gatt_service_ptr->GetNumCharacteristicUuids());
}

TEST_F(BleV2GattServerTest, CreateCharacteristic_Success) {
  auto fake_gatt_service = std::make_unique<bluetooth::FakeGattService>();
  fake_gatt_service->SetCreateCharacteristicResult(/*success=*/true);
  fake_adapter_->SetCreateLocalGattServiceResult(
      /*gatt_service=*/std::move(fake_gatt_service));

  CallCreateCharacteristic(/*characteristic_uuid=*/kCharacteristicUuid1,
                           /*expected_success=*/true);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.GattServer.CreateLocalGattService.Result",
      /*bucket: success=*/1, 1);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.GattServer.CreateCharacteristic.Result",
      /*bucket: success=*/1, 1);
}

TEST_F(BleV2GattServerTest, CreateCharacteristic_Failure) {
  auto fake_gatt_service = std::make_unique<bluetooth::FakeGattService>();
  fake_gatt_service->SetCreateCharacteristicResult(/*success=*/false);
  fake_adapter_->SetCreateLocalGattServiceResult(
      /*gatt_service=*/std::move(fake_gatt_service));

  CallCreateCharacteristic(/*characteristic_uuid=*/kCharacteristicUuid1,
                           /*expected_success=*/false);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.GattServer.CreateLocalGattService.Result",
      /*bucket: success=*/1, 1);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.GattServer.CreateCharacteristic.Result",
      /*bucket: success=*/0, 1);
}

TEST_F(BleV2GattServerTest,
       CreateCharacteristic_CreateCharacteristic_AlreadyExists) {
  auto fake_gatt_service = std::make_unique<bluetooth::FakeGattService>();
  fake_gatt_service->SetCreateCharacteristicResult(/*success=*/true);
  auto* fake_gatt_service_ptr = fake_gatt_service.get();
  fake_adapter_->SetCreateLocalGattServiceResult(
      /*gatt_service=*/std::move(fake_gatt_service));

  // First time, expect a call to the browser process to get or create
  // a GATT characteristic since it isn't in the map yet.
  {
    CallCreateCharacteristic(
        /*characteristic_uuid=*/kCharacteristicUuid1,
        /*expected_success=*/true);
    EXPECT_EQ(1, fake_gatt_service_ptr->GetNumCharacteristicUuids());
    histogram_tester_.ExpectBucketCount(
        "Nearby.Connections.BleV2.GattServer.CreateLocalGattService.Result",
        /*bucket: success=*/1, 1);
    histogram_tester_.ExpectBucketCount(
        "Nearby.Connections.BleV2.GattServer.CreateCharacteristic.Result",
        /*bucket: success=*/1, 1);
  }

  // Second time, expect no call to browser process since it already
  // exists for the same characteristic id.
  {
    CallCreateCharacteristic(
        /*characteristic_uuid=*/kCharacteristicUuid1,
        /*expected_success=*/true);
    EXPECT_EQ(1, fake_gatt_service_ptr->GetNumCharacteristicUuids());
    histogram_tester_.ExpectBucketCount(
        "Nearby.Connections.BleV2.GattServer.CreateLocalGattService.Result",
        /*bucket: success=*/1, 1);
    histogram_tester_.ExpectBucketCount(
        "Nearby.Connections.BleV2.GattServer.CreateCharacteristic.Result",
        /*bucket: success=*/1, 1);
  }
}

TEST_F(BleV2GattServerTest,
       UpdateCharacteristic_FailureIfCharacteristicDoesntExist) {
  CallUpdateCharacteristic(
      /*characteristic_uuid=*/kCharacteristicUuid1,
      /*expected_success=*/false);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.GattServer.UpdateCharacteristic.Result",
      /*bucket: failure=*/0, 1);
}

TEST_F(BleV2GattServerTest,
       UpdateCharacteristic_ReadCharacteristicRequest_Success) {
  auto fake_gatt_service = std::make_unique<bluetooth::FakeGattService>();
  fake_gatt_service->SetCreateCharacteristicResult(/*success=*/true);
  auto* fake_gatt_service_ptr = fake_gatt_service.get();
  fake_adapter_->SetCreateLocalGattServiceResult(
      /*gatt_service=*/std::move(fake_gatt_service));
  CallCreateCharacteristic(
      /*characteristic_uuid=*/kCharacteristicUuid1,
      /*expected_success=*/true);

  CallUpdateCharacteristic(
      /*characteristic_uuid=*/kCharacteristicUuid1,
      /*expected_success=*/true);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.GattServer.UpdateCharacteristic.Result",
      /*bucket: success=*/1, 1);

  base::test::TestFuture<bluetooth::mojom::LocalCharacteristicReadResultPtr>
      future;
  fake_gatt_service_ptr->TriggerReadCharacteristicRequest(
      device::BluetoothUUID(kServiceId),
      device::BluetoothUUID(kCharacteristicUuid1), future.GetCallback());
  auto read_result = future.Take();
  EXPECT_FALSE(read_result->is_error_code());
  EXPECT_TRUE(read_result->is_data());
  EXPECT_EQ(kNewCharacteristicValue,
            base::as_string_view(
                base::as_chars(base::make_span(read_result->get_data()))));
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.GattServer.OnLocalCharacteristicRead.Result",
      /*bucket: success=*/1, 1);
}

TEST_F(
    BleV2GattServerTest,
    UpdateCharacteristic_ReadCharacteristicRequest_FailureIfCharacteristicDoesntSupportRead) {
  auto fake_gatt_service = std::make_unique<bluetooth::FakeGattService>();
  fake_gatt_service->SetCreateCharacteristicResult(/*success=*/true);
  auto* fake_gatt_service_ptr = fake_gatt_service.get();
  fake_adapter_->SetCreateLocalGattServiceResult(
      /*gatt_service=*/std::move(fake_gatt_service));
  CallCreateCharacteristic(
      /*characteristic_uuid=*/kCharacteristicUuid1,
      /*expected_success=*/true,
      /*permission=*/api::ble_v2::GattCharacteristic::Permission::kWrite,
      /*property=*/api::ble_v2::GattCharacteristic::Property::kWrite);

  CallUpdateCharacteristic(
      /*characteristic_uuid=*/kCharacteristicUuid1,
      /*expected_success=*/true,
      /*permission=*/api::ble_v2::GattCharacteristic::Permission::kWrite,
      /*property=*/api::ble_v2::GattCharacteristic::Property::kWrite);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.GattServer.UpdateCharacteristic.Result",
      /*bucket: success=*/1, 1);

  base::test::TestFuture<bluetooth::mojom::LocalCharacteristicReadResultPtr>
      future;
  fake_gatt_service_ptr->TriggerReadCharacteristicRequest(
      device::BluetoothUUID(kServiceId),
      device::BluetoothUUID(kCharacteristicUuid1), future.GetCallback());
  auto read_result = future.Take();
  EXPECT_TRUE(read_result->is_error_code());
  EXPECT_FALSE(read_result->is_data());
  EXPECT_EQ(device::BluetoothGattService::GattErrorCode::kNotPermitted,
            read_result->get_error_code());
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.GattServer.OnLocalCharacteristicRead.Result",
      /*bucket: failure=*/0, 1);
}

TEST_F(
    BleV2GattServerTest,
    UpdateCharacteristic_ReadCharacteristicRequest_FailureIfReadRequestForCharacteristicValueDoesntExist) {
  auto fake_gatt_service = std::make_unique<bluetooth::FakeGattService>();
  fake_gatt_service->SetCreateCharacteristicResult(/*success=*/true);
  auto* fake_gatt_service_ptr = fake_gatt_service.get();
  fake_adapter_->SetCreateLocalGattServiceResult(
      /*gatt_service=*/std::move(fake_gatt_service));
  CallCreateCharacteristic(
      /*characteristic_uuid=*/kCharacteristicUuid1,
      /*expected_success=*/true);

  base::test::TestFuture<bluetooth::mojom::LocalCharacteristicReadResultPtr>
      future;
  fake_gatt_service_ptr->TriggerReadCharacteristicRequest(
      device::BluetoothUUID(kServiceId),
      device::BluetoothUUID(kCharacteristicUuid1), future.GetCallback());
  auto read_result = future.Take();
  EXPECT_TRUE(read_result->is_error_code());
  EXPECT_FALSE(read_result->is_data());
  EXPECT_EQ(device::BluetoothGattService::GattErrorCode::kNotSupported,
            read_result->get_error_code());
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.GattServer.OnLocalCharacteristicRead.Result",
      /*bucket: failure=*/0, 1);
}

TEST_F(BleV2GattServerTest, Stop) {
  auto fake_gatt_service = std::make_unique<bluetooth::FakeGattService>();
  fake_gatt_service->SetCreateCharacteristicResult(/*success=*/true);

  base::RunLoop run_loop;
  bool fake_gatt_service_destroyed = false;
  fake_gatt_service->SetOnDestroyedCallback(base::BindLambdaForTesting([&]() {
    fake_gatt_service_destroyed = true;
    run_loop.Quit();
  }));

  fake_adapter_->SetCreateLocalGattServiceResult(
      /*gatt_service=*/std::move(fake_gatt_service));
  CallCreateCharacteristic(
      /*characteristic_uuid=*/kCharacteristicUuid1,
      /*expected_success=*/true);

  // Expect the underlying objects to have been destroyed.
  ble_v2_gatt_server_->Stop();
  run_loop.Run();
  EXPECT_TRUE(fake_gatt_service_destroyed);
}

TEST_F(BleV2GattServerTest, Register_Success) {
  auto fake_gatt_service = std::make_unique<bluetooth::FakeGattService>();
  fake_gatt_service->SetShouldRegisterSucceed(true);
  fake_gatt_service->SetCreateCharacteristicResult(/*success=*/true);
  fake_adapter_->SetCreateLocalGattServiceResult(
      /*gatt_service=*/std::move(fake_gatt_service));
  CallCreateCharacteristic(
      /*characteristic_uuid=*/kCharacteristicUuid1,
      /*expected_success=*/true);

  base::test::TestFuture<bool> future;
  ble_v2_gatt_server_->RegisterGattServices(future.GetCallback());
  EXPECT_TRUE(future.Take());
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.GattServer.RegisterGattServices.Result",
      /*bucket: success=*/1, 1);
}

TEST_F(BleV2GattServerTest, Register_Failure) {
  auto fake_gatt_service = std::make_unique<bluetooth::FakeGattService>();
  fake_gatt_service->SetShouldRegisterSucceed(false);
  fake_gatt_service->SetCreateCharacteristicResult(/*success=*/true);
  fake_adapter_->SetCreateLocalGattServiceResult(
      /*gatt_service=*/std::move(fake_gatt_service));
  CallCreateCharacteristic(
      /*characteristic_uuid=*/kCharacteristicUuid1,
      /*expected_success=*/true);

  base::test::TestFuture<bool> future;
  ble_v2_gatt_server_->RegisterGattServices(future.GetCallback());
  EXPECT_FALSE(future.Take());
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.GattServer.RegisterGattServices.Result",
      /*bucket: failure=*/0, 1);
  histogram_tester_.ExpectBucketCount(
      "Nearby.Connections.BleV2.GattServer.RegisterGattService.FailureReason",
      /*bucket: Failed=*/1, 1);
}

TEST_F(BleV2GattServerTest, MojoGattServiceDisconnect) {
  auto fake_gatt_service = std::make_unique<bluetooth::FakeGattService>();
  fake_gatt_service->SetShouldRegisterSucceed(false);
  fake_gatt_service->SetCreateCharacteristicResult(/*success=*/true);
  fake_adapter_->SetCreateLocalGattServiceResult(
      /*gatt_service=*/std::move(fake_gatt_service));

  base::RunLoop run_loop;
  bool fake_gatt_service_destroyed = false;
  fake_gatt_service_factory_->SetNextFakeGattServiceDestroyedCallback(
      base::BindLambdaForTesting([&]() {
        fake_gatt_service_destroyed = true;
        run_loop.Quit();
      }));

  CallCreateCharacteristic(
      /*characteristic_uuid=*/kCharacteristicUuid1,
      /*expected_success=*/true);

  // Close the Mojo pipe.
  fake_adapter_->gatt_service_receiver_->Close();

  run_loop.Run();
  EXPECT_TRUE(fake_gatt_service_destroyed);
}

TEST_F(
    BleV2GattServerTest,
    UpdateCharacteristic_ReadCharacteristicRequest_Success_PartialBufferReads) {
  auto fake_gatt_service = std::make_unique<bluetooth::FakeGattService>();
  fake_gatt_service->SetCreateCharacteristicResult(/*success=*/true);
  auto* fake_gatt_service_ptr = fake_gatt_service.get();
  fake_adapter_->SetCreateLocalGattServiceResult(
      /*gatt_service=*/std::move(fake_gatt_service));
  CallCreateCharacteristic(
      /*characteristic_uuid=*/kCharacteristicUuid1,
      /*expected_success=*/true);

  CallUpdateCharacteristic(
      /*characteristic_uuid=*/kCharacteristicUuid1,
      /*expected_success=*/true);

  base::test::TestFuture<bluetooth::mojom::LocalCharacteristicReadResultPtr>
      future;
  fake_gatt_service_ptr->TriggerReadCharacteristicRequest(
      device::BluetoothUUID(kServiceId),
      device::BluetoothUUID(kCharacteristicUuid1), future.GetCallback(),
      /*offset=*/kPartialBufferOffset);
  auto read_result = future.Take();
  EXPECT_FALSE(read_result->is_error_code());
  EXPECT_TRUE(read_result->is_data());
  EXPECT_EQ(kNewCharacteristicValue.substr(kPartialBufferOffset),
            base::as_string_view(
                base::as_chars(base::make_span(read_result->get_data()))));
}

TEST_F(BleV2GattServerTest,
       UpdateCharacteristic_ReadCharacteristicRequest_FailureIfInvalidOffset) {
  auto fake_gatt_service = std::make_unique<bluetooth::FakeGattService>();
  fake_gatt_service->SetCreateCharacteristicResult(/*success=*/true);
  auto* fake_gatt_service_ptr = fake_gatt_service.get();
  fake_adapter_->SetCreateLocalGattServiceResult(
      /*gatt_service=*/std::move(fake_gatt_service));
  CallCreateCharacteristic(
      /*characteristic_uuid=*/kCharacteristicUuid1,
      /*expected_success=*/true);

  CallUpdateCharacteristic(
      /*characteristic_uuid=*/kCharacteristicUuid1,
      /*expected_success=*/true);

  base::test::TestFuture<bluetooth::mojom::LocalCharacteristicReadResultPtr>
      future;
  fake_gatt_service_ptr->TriggerReadCharacteristicRequest(
      device::BluetoothUUID(kServiceId),
      device::BluetoothUUID(kCharacteristicUuid1), future.GetCallback(),
      /*offset=*/10);
  auto read_result = future.Take();
  EXPECT_TRUE(read_result->is_error_code());
  EXPECT_FALSE(read_result->is_data());
  EXPECT_EQ(device::BluetoothGattService::GattErrorCode::kInvalidLength,
            read_result->get_error_code());
}

}  // namespace nearby::chrome