blob: fb9b0b492fcdf7a6a195e2756cf9952d3ff49730 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gatt/generic_attribute_service.h"
#include <gtest/gtest.h>
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/assert.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gatt/gatt_defs.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gatt/persisted_data.h"
namespace bt::gatt {
namespace {
// Handles for the third attribute (Service Changed characteristic) and fourth
// attribute (corresponding client config).
constexpr att::Handle kChrcHandle = 0x0003;
constexpr att::Handle kCCCHandle = 0x0004;
constexpr PeerId kTestPeerId(1);
constexpr uint16_t kEnableInd = 0x0002;
class GenericAttributeServiceTest : public ::testing::Test {
protected:
bool WriteServiceChangedCcc(PeerId peer_id,
uint16_t ccc_value,
fit::result<att::ErrorCode>* out_status) {
BT_ASSERT(out_status);
auto* attr = mgr.database()->FindAttribute(kCCCHandle);
BT_ASSERT(attr);
auto result_cb = [&out_status](auto cb_status) { *out_status = cb_status; };
uint16_t value = htole16(ccc_value);
return attr->WriteAsync(
peer_id, 0u, BufferView(&value, sizeof(value)), result_cb);
}
LocalServiceManager mgr;
};
// Test registration and unregistration of the local GATT service itself.
TEST_F(GenericAttributeServiceTest, RegisterUnregister) {
{
GenericAttributeService gatt_service(mgr.GetWeakPtr(), NopSendIndication);
// Check that the local attribute database has a grouping for the GATT GATT
// service with four attributes.
auto iter = mgr.database()->groupings().begin();
EXPECT_TRUE(iter->complete());
EXPECT_EQ(4u, iter->attributes().size());
EXPECT_TRUE(iter->active());
EXPECT_EQ(0x0001, iter->start_handle());
EXPECT_EQ(0x0004, iter->end_handle());
EXPECT_EQ(types::kPrimaryService, iter->group_type());
auto const* ccc_attr = mgr.database()->FindAttribute(kCCCHandle);
ASSERT_TRUE(ccc_attr != nullptr);
EXPECT_EQ(types::kClientCharacteristicConfig, ccc_attr->type());
}
// The service should now be unregistered, so no characeteristic attributes
// should be active.
auto const* chrc_attr = mgr.database()->FindAttribute(kChrcHandle);
ASSERT_TRUE(chrc_attr == nullptr);
}
// Tests that registering the GATT service, enabling indication on its Service
// Changed characteristic, then registering a different service invokes the
// callback to send an indication to the "client."
TEST_F(GenericAttributeServiceTest, IndicateOnRegister) {
std::optional<IdType> indicated_svc_id;
auto send_indication =
[&](IdType service_id, IdType chrc_id, PeerId peer_id, BufferView value) {
EXPECT_EQ(kTestPeerId, peer_id);
EXPECT_EQ(kServiceChangedChrcId, chrc_id);
indicated_svc_id = service_id;
ASSERT_EQ(4u, value.size());
// The second service following the four-attribute GATT service should
// span the subsequent three handles.
EXPECT_EQ(0x05, value[0]);
EXPECT_EQ(0x00, value[1]);
EXPECT_EQ(0x07, value[2]);
EXPECT_EQ(0x00, value[3]);
};
// Register the GATT service.
GenericAttributeService gatt_service(mgr.GetWeakPtr(),
std::move(send_indication));
// Enable Service Changed indications for the test client.
fit::result<att::ErrorCode> status = fit::ok();
WriteServiceChangedCcc(kTestPeerId, kEnableInd, &status);
EXPECT_EQ(std::nullopt, indicated_svc_id);
constexpr UUID kTestSvcType(uint32_t{0xdeadbeef});
constexpr IdType kChrcId = 12;
constexpr uint8_t kChrcProps = Property::kRead;
constexpr UUID kTestChrcType(uint32_t{0xdeadbeef});
const att::AccessRequirements kReadReqs(/*encryption=*/true,
/*authentication=*/true,
/*authorization=*/true);
const att::AccessRequirements kWriteReqs, kUpdateReqs;
auto service = std::make_unique<Service>(/*primary=*/false, kTestSvcType);
service->AddCharacteristic(std::make_unique<Characteristic>(kChrcId,
kTestChrcType,
kChrcProps,
0,
kReadReqs,
kWriteReqs,
kUpdateReqs));
auto service_id = mgr.RegisterService(
std::move(service), NopReadHandler, NopWriteHandler, NopCCCallback);
// Verify that service registration succeeded
EXPECT_NE(kInvalidId, service_id);
// Verify that |send_indication| was invoked to indicate the Service Changed
// chrc within the gatt_service.
EXPECT_EQ(gatt_service.service_id(), indicated_svc_id);
}
// Same test as above, but the indication is enabled just prior unregistering
// the latter test service.
TEST_F(GenericAttributeServiceTest, IndicateOnUnregister) {
std::optional<IdType> indicated_svc_id;
auto send_indication =
[&](IdType service_id, IdType chrc_id, PeerId peer_id, BufferView value) {
EXPECT_EQ(kTestPeerId, peer_id);
EXPECT_EQ(kServiceChangedChrcId, chrc_id);
indicated_svc_id = service_id;
ASSERT_EQ(4u, value.size());
// The second service following the four-attribute GATT service should
// span the subsequent four handles (update enabled).
EXPECT_EQ(0x05, value[0]);
EXPECT_EQ(0x00, value[1]);
EXPECT_EQ(0x08, value[2]);
EXPECT_EQ(0x00, value[3]);
};
// Register the GATT service.
GenericAttributeService gatt_service(mgr.GetWeakPtr(),
std::move(send_indication));
constexpr UUID kTestSvcType(uint32_t{0xdeadbeef});
constexpr IdType kChrcId = 0;
constexpr uint8_t kChrcProps = Property::kNotify;
constexpr UUID kTestChrcType(uint32_t{0xdeadbeef});
const att::AccessRequirements kReadReqs, kWriteReqs;
const att::AccessRequirements kUpdateReqs(/*encryption=*/true,
/*authentication=*/true,
/*authorization=*/true);
auto service = std::make_unique<Service>(/*primary=*/false, kTestSvcType);
service->AddCharacteristic(std::make_unique<Characteristic>(kChrcId,
kTestChrcType,
kChrcProps,
0,
kReadReqs,
kWriteReqs,
kUpdateReqs));
auto service_id = mgr.RegisterService(
std::move(service), NopReadHandler, NopWriteHandler, NopCCCallback);
// Verify that service registration succeeded
EXPECT_NE(kInvalidId, service_id);
// Enable Service Changed indications for the test client.
fit::result<att::ErrorCode> status = fit::ok();
WriteServiceChangedCcc(kTestPeerId, kEnableInd, &status);
EXPECT_EQ(std::nullopt, indicated_svc_id);
mgr.UnregisterService(service_id);
// Verify that |send_indication| was invoked to indicate the Service Changed
// chrc within the gatt_service.
EXPECT_EQ(gatt_service.service_id(), indicated_svc_id);
}
// Tests that registering the GATT service reads a persisted value for the
// service changed characteristic's ccc, and that enabling indication on its
// service changed characteristic writes a persisted value.
TEST_F(GenericAttributeServiceTest, PersistIndicate) {
int persist_callback_count = 0;
auto persist_callback = [&persist_callback_count](
PeerId peer_id,
ServiceChangedCCCPersistedData gatt_data) {
EXPECT_EQ(peer_id, kTestPeerId);
EXPECT_EQ(gatt_data.indicate, true);
persist_callback_count++;
};
// Register the GATT service.
GenericAttributeService gatt_service(mgr.GetWeakPtr(), NopSendIndication);
gatt_service.SetPersistServiceChangedCCCCallback(std::move(persist_callback));
EXPECT_EQ(persist_callback_count, 0);
// Enable Service Changed indications for the test client.
fit::result<att::ErrorCode> status = fit::ok();
WriteServiceChangedCcc(kTestPeerId, kEnableInd, &status);
EXPECT_EQ(persist_callback_count, 1);
}
} // namespace
} // namespace bt::gatt