blob: 1ea6694b0f08412837f3aab771eb214836a8f2cb [file] [log] [blame]
// Copyright 2024 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/gatt_service.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
#include "device/bluetooth/test/fake_local_gatt_characteristic.h"
#include "device/bluetooth/test/fake_local_gatt_service.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_device.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const char kServiceId[] = "12345678-1234-5678-9abc-def123456789";
const char kCharacteristicUuid[] = "00001101-0000-1000-8000-00805f9b34fb";
const char kTestDeviceName[] = "TestDeviceName";
const char kTestDeviceAddress[] = "TestDeviceAddress";
const std::vector<uint8_t> kReadCharacteristicValue = {0x01, 0x02, 0x03};
const int kReadCharacteristicOffset = 0;
std::unique_ptr<bluetooth::FakeLocalGattCharacteristic>
CreateFakeCharacteristic(device::BluetoothLocalGattService* service) {
return std::make_unique<bluetooth::FakeLocalGattCharacteristic>(
/*characteristic_id=*/kCharacteristicUuid,
/*characteristic_uuid=*/device::BluetoothUUID(kCharacteristicUuid),
/*service=*/service,
/*properties=*/
device::BluetoothGattCharacteristic::Permission::PERMISSION_READ,
/*property=*/
device::BluetoothGattCharacteristic::Property::PROPERTY_READ);
}
std::unique_ptr<testing::NiceMock<device::MockBluetoothDevice>>
CreateMockBluetoothDevice() {
return std::make_unique<testing::NiceMock<device::MockBluetoothDevice>>(
/*adapter=*/nullptr,
/*class=*/0, kTestDeviceName, kTestDeviceAddress,
/*paired=*/false,
/*connected=*/false);
}
} // namespace
namespace bluetooth {
class GattServiceTest : public testing::Test,
public mojom::GattServiceObserver {
public:
GattServiceTest() = default;
~GattServiceTest() override = default;
GattServiceTest(const GattServiceTest&) = delete;
GattServiceTest& operator=(const GattServiceTest&) = delete;
void SetUp() override {
fake_local_gatt_service_ = std::make_unique<FakeLocalGattService>(
/*service_id=*/kServiceId,
/*service_uuid=*/device::BluetoothUUID(kServiceId),
/*is_primary=*/false);
mock_bluetooth_adapter_ =
base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
ON_CALL(*mock_bluetooth_adapter_, GetGattService)
.WillByDefault(testing::Return(nullptr));
ON_CALL(*mock_bluetooth_adapter_, CreateLocalGattService)
.WillByDefault(Invoke(this, &GattServiceTest::CreateLocalGattService));
mojo::PendingReceiver<mojom::GattService> pending_gatt_service_receiver =
remote_.BindNewPipeAndPassReceiver();
mojo::PendingRemote<mojom::GattServiceObserver> pending_observer_remote =
observer_.BindNewPipeAndPassRemote();
gatt_service_ = std::make_unique<GattService>(
std::move(pending_gatt_service_receiver),
std::move(pending_observer_remote), device::BluetoothUUID(kServiceId),
mock_bluetooth_adapter_.get(),
base::BindOnce(&GattServiceTest::OnGattServiceInvalidated,
base::Unretained(this)));
}
base::WeakPtr<device::BluetoothLocalGattService> CreateLocalGattService(
const device::BluetoothUUID& uuid,
bool is_primary,
device::BluetoothLocalGattService::Delegate* delegate) {
// Although this method mocks the BluetoothAdapter's creation of
// `BluetoothLocalGattService`s, and therefore there is no limit on
// the number created, `GattService` is expected to only create and own
// a single `BluetoothLocalGattService` instance, which we enforce in the
// test with the CHECK below.
CHECK_EQ(0, create_local_gatt_service_calls_);
create_local_gatt_service_calls_++;
delegate_ = delegate;
return fake_local_gatt_service_->GetWeakPtr();
}
// mojo::GattServiceObserver:
void OnLocalCharacteristicRead(
bluetooth::mojom::DeviceInfoPtr remote_device,
const device::BluetoothUUID& characteristic_uuid,
const device::BluetoothUUID& service_uuid,
uint32_t offset,
OnLocalCharacteristicReadCallback callback) override {
mojom::LocalCharacteristicReadResultPtr read_result;
if (local_characteristic_read_error_code_.has_value()) {
read_result = mojom::LocalCharacteristicReadResult::NewErrorCode(
local_characteristic_read_error_code_.value());
} else {
CHECK(local_characteristic_read_value_.has_value());
read_result = mojom::LocalCharacteristicReadResult::NewData(
local_characteristic_read_value_.value());
}
std::move(callback).Run(std::move(read_result));
std::move(on_local_characteristic_read_callback_).Run();
}
void OnGattServiceInvalidated(device::BluetoothUUID service_id) {
EXPECT_EQ(device::BluetoothUUID(kServiceId), service_id);
gatt_service_invalidated_ = true;
std::move(on_gatt_service_invalidated_callback_).Run();
}
void SetGattServiceInvalidatedCallback(base::OnceClosure callback) {
on_gatt_service_invalidated_callback_ = std::move(callback);
}
void CallCreateCharacteristic(
bool gatt_service_exists,
bool expected_success,
device::BluetoothGattCharacteristic::Permissions permissions =
device::BluetoothGattCharacteristic::Permission::PERMISSION_READ,
device::BluetoothGattCharacteristic::Properties properties =
device::BluetoothGattCharacteristic::Property::PROPERTY_READ) {
if (gatt_service_exists) {
// 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()));
} else {
// Simulate the underlying platform GattService destruction.
EXPECT_CALL(*mock_bluetooth_adapter_, GetGattService)
.WillOnce(testing::Return(nullptr));
}
base::test::TestFuture<bool> future;
remote_->CreateCharacteristic(
/*characteristic_uuid=*/device::BluetoothUUID(kCharacteristicUuid),
/*permissions=*/permissions,
/*properties=*/properties, future.GetCallback());
EXPECT_EQ(expected_success, future.Take());
EXPECT_EQ(expected_success,
(nullptr != fake_local_gatt_service_->GetCharacteristic(
kCharacteristicUuid)));
}
void SetOnLocalCharacteristicReadCallback(base::OnceClosure callback) {
on_local_characteristic_read_callback_ = std::move(callback);
}
void SetOnLocalCharacteristicReadResult(
std::optional<device::BluetoothGattService::GattErrorCode> error_code,
std::optional<std::vector<uint8_t>> value) {
local_characteristic_read_error_code_ = error_code;
local_characteristic_read_value_ = value;
}
protected:
bool gatt_service_invalidated_ = false;
std::optional<device::BluetoothGattService::GattErrorCode>
local_characteristic_read_error_code_;
std::optional<std::vector<uint8_t>> local_characteristic_read_value_;
base::OnceClosure on_local_characteristic_read_callback_;
base::OnceClosure on_gatt_service_invalidated_callback_;
int create_local_gatt_service_calls_ = 0;
base::test::SingleThreadTaskEnvironment task_environment_;
mojo::Remote<mojom::GattService> remote_;
std::unique_ptr<FakeLocalGattService> fake_local_gatt_service_;
scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>>
mock_bluetooth_adapter_;
std::unique_ptr<GattService> gatt_service_;
raw_ptr<device::BluetoothLocalGattService::Delegate> delegate_;
mojo::Receiver<mojom::GattServiceObserver> observer_{this};
};
TEST_F(GattServiceTest, CreateGattServiceOnConstruction) {
EXPECT_TRUE(gatt_service_);
EXPECT_EQ(1, create_local_gatt_service_calls_);
}
TEST_F(GattServiceTest, CreateCharacteristic_FailureIfGattServiceIsDestroyed) {
// Simulate the underlying platform GattService destruction.
EXPECT_CALL(*mock_bluetooth_adapter_, GetGattService)
.WillOnce(testing::Return(nullptr));
base::test::TestFuture<bool> future;
remote_->CreateCharacteristic(
/*characteristic_uuid=*/device::BluetoothUUID(kCharacteristicUuid),
/*permissions=*/
device::BluetoothGattCharacteristic::Permission::PERMISSION_READ,
/*properties=*/
device::BluetoothGattCharacteristic::Property::PROPERTY_READ,
future.GetCallback());
EXPECT_FALSE(future.Take());
}
TEST_F(GattServiceTest,
CreateCharacteristic_FailureIfCreateGattCharacteristicFails) {
CallCreateCharacteristic(/*gatt_service_exists=*/false,
/*expected_success=*/false);
}
TEST_F(GattServiceTest, CreateCharacteristic_SuccessIfCreated) {
CallCreateCharacteristic(/*gatt_service_exists=*/true,
/*expected_success=*/true);
}
TEST_F(GattServiceTest,
CreateCharacteristic_Success_MultiplePermissionsAndProperties) {
device::BluetoothGattCharacteristic::Permissions permissions =
device::BluetoothGattCharacteristic::Permission::PERMISSION_READ |
device::BluetoothGattCharacteristic::Permission::PERMISSION_WRITE;
device::BluetoothGattCharacteristic::Properties properties =
device::BluetoothGattCharacteristic::Property::PROPERTY_READ |
device::BluetoothGattCharacteristic::Property::PROPERTY_WRITE;
CallCreateCharacteristic(/*gatt_service_exists=*/true,
/*expected_success=*/true, permissions, properties);
auto* fake_characteristic =
fake_local_gatt_service_->GetCharacteristic(kCharacteristicUuid);
EXPECT_TRUE(fake_characteristic);
EXPECT_EQ(properties, fake_characteristic->GetProperties());
EXPECT_EQ(permissions, fake_characteristic->GetPermissions());
}
TEST_F(GattServiceTest, OnReadCharacteristic_Success) {
// Simulate that the GATT service is created successfully, and is never
// destroyed during the lifetime of this test. Simulate a successful
// characteristic being added to the GATT service.
CallCreateCharacteristic(/*gatt_service_exists=*/true,
/*expected_success=*/true);
base::MockCallback<base::OnceClosure> mock_observer_callback;
EXPECT_CALL(mock_observer_callback, Run).Times(1);
SetOnLocalCharacteristicReadCallback(mock_observer_callback.Get());
SetOnLocalCharacteristicReadResult(
/*error_code=*/std::nullopt,
/*value=*/kReadCharacteristicValue);
base::RunLoop run_loop;
auto mock_device = CreateMockBluetoothDevice();
auto fake_characteristic =
CreateFakeCharacteristic(fake_local_gatt_service_.get());
delegate_->OnCharacteristicReadRequest(
/*device=*/mock_device.get(),
/*characteristic=*/fake_characteristic.get(),
/*offset=*/kReadCharacteristicOffset,
/*callback=*/
base::BindLambdaForTesting(
[&](std::optional<::device::BluetoothGattService::GattErrorCode>
error_code,
const std::vector<uint8_t>& value) {
EXPECT_FALSE(error_code.has_value());
EXPECT_FALSE(value.empty());
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(GattServiceTest, OnReadCharacteristic_Failure) {
// Simulate that the GATT service is created successfully, and is never
// destroyed during the lifetime of this test. Simulate a successful
// characteristic being added to the GATT service.
CallCreateCharacteristic(/*gatt_service_exists=*/true,
/*expected_success=*/true);
base::MockCallback<base::OnceClosure> mock_observer_callback;
EXPECT_CALL(mock_observer_callback, Run).Times(1);
SetOnLocalCharacteristicReadCallback(mock_observer_callback.Get());
SetOnLocalCharacteristicReadResult(
/*error_code=*/device::BluetoothGattService::GattErrorCode::kFailed,
/*value=*/std::nullopt);
base::RunLoop run_loop;
auto mock_device = CreateMockBluetoothDevice();
auto fake_characteristic =
CreateFakeCharacteristic(fake_local_gatt_service_.get());
delegate_->OnCharacteristicReadRequest(
/*device=*/mock_device.get(),
/*characteristic=*/fake_characteristic.get(),
/*offset=*/kReadCharacteristicOffset,
/*callback=*/
base::BindLambdaForTesting(
[&](std::optional<::device::BluetoothGattService::GattErrorCode>
error_code,
const std::vector<uint8_t>& value) {
EXPECT_TRUE(error_code.has_value());
EXPECT_TRUE(value.empty());
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(GattServiceTest, MojoDisconnect_GattServiceRemote) {
// Simulate that the GATT service is created successfully, and thus is always
// accessible via `GetGattService()` calls.
ON_CALL(*mock_bluetooth_adapter_, GetGattService)
.WillByDefault(testing::Return(fake_local_gatt_service_.get()));
base::RunLoop run_loop;
SetGattServiceInvalidatedCallback(run_loop.QuitClosure());
remote_.reset();
run_loop.Run();
EXPECT_TRUE(gatt_service_invalidated_);
EXPECT_TRUE(fake_local_gatt_service_->WasDeleted());
}
TEST_F(GattServiceTest, MojoDisconnect_GattServiceObserverRemote) {
// Simulate that the GATT service is created successfully, and thus is always
// accessible via `GetGattService()` calls.
ON_CALL(*mock_bluetooth_adapter_, GetGattService)
.WillByDefault(testing::Return(fake_local_gatt_service_.get()));
base::RunLoop run_loop;
SetGattServiceInvalidatedCallback(run_loop.QuitClosure());
observer_.reset();
run_loop.Run();
EXPECT_TRUE(gatt_service_invalidated_);
EXPECT_TRUE(fake_local_gatt_service_->WasDeleted());
}
TEST_F(GattServiceTest, Register_Success) {
// 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()));
fake_local_gatt_service_->set_should_registration_succeed(true);
base::test::TestFuture<
std::optional<device::BluetoothGattService::GattErrorCode>>
future;
remote_->Register(future.GetCallback());
EXPECT_FALSE(future.Take());
}
TEST_F(GattServiceTest, Register_Failure) {
// 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()));
fake_local_gatt_service_->set_should_registration_succeed(false);
base::test::TestFuture<
std::optional<device::BluetoothGattService::GattErrorCode>>
future;
remote_->Register(future.GetCallback());
EXPECT_TRUE(future.Take());
}
} // namespace bluetooth