1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_bluetooth_sapphire/internal/host/gatt/generic_attribute_service.h"
16
17 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
18 #include "pw_bluetooth_sapphire/internal/host/gatt/gatt_defs.h"
19 #include "pw_bluetooth_sapphire/internal/host/gatt/persisted_data.h"
20 #include "pw_unit_test/framework.h"
21
22 namespace bt::gatt {
23 namespace {
24 // Handles for the third attribute (Service Changed characteristic) and fourth
25 // attribute (corresponding client config).
26 constexpr att::Handle kChrcHandle = 0x0003;
27 constexpr att::Handle kCCCHandle = 0x0004;
28 constexpr PeerId kTestPeerId(1);
29 constexpr uint16_t kEnableInd = 0x0002;
30
31 class GenericAttributeServiceTest : public ::testing::Test {
32 protected:
WriteServiceChangedCcc(PeerId peer_id,uint16_t ccc_value,fit::result<att::ErrorCode> * out_status)33 bool WriteServiceChangedCcc(PeerId peer_id,
34 uint16_t ccc_value,
35 fit::result<att::ErrorCode>* out_status) {
36 BT_ASSERT(out_status);
37
38 auto* attr = mgr.database()->FindAttribute(kCCCHandle);
39 BT_ASSERT(attr);
40 auto result_cb = [&out_status](auto cb_status) { *out_status = cb_status; };
41 uint16_t value = htole16(ccc_value);
42 return attr->WriteAsync(
43 peer_id, 0u, BufferView(&value, sizeof(value)), result_cb);
44 }
45
46 LocalServiceManager mgr;
47 };
48
49 // Test registration and unregistration of the local GATT service itself.
TEST_F(GenericAttributeServiceTest,RegisterUnregister)50 TEST_F(GenericAttributeServiceTest, RegisterUnregister) {
51 {
52 GenericAttributeService gatt_service(mgr.GetWeakPtr(), NopSendIndication);
53
54 // Check that the local attribute database has a grouping for the GATT GATT
55 // service with four attributes.
56 auto iter = mgr.database()->groupings().begin();
57 EXPECT_TRUE(iter->complete());
58 EXPECT_EQ(4u, iter->attributes().size());
59 EXPECT_TRUE(iter->active());
60 EXPECT_EQ(0x0001, iter->start_handle());
61 EXPECT_EQ(0x0004, iter->end_handle());
62 EXPECT_EQ(types::kPrimaryService, iter->group_type());
63
64 auto const* ccc_attr = mgr.database()->FindAttribute(kCCCHandle);
65 ASSERT_TRUE(ccc_attr != nullptr);
66 EXPECT_EQ(types::kClientCharacteristicConfig, ccc_attr->type());
67 }
68
69 // The service should now be unregistered, so no characeteristic attributes
70 // should be active.
71 auto const* chrc_attr = mgr.database()->FindAttribute(kChrcHandle);
72 ASSERT_TRUE(chrc_attr == nullptr);
73 }
74
75 // Tests that registering the GATT service, enabling indication on its Service
76 // Changed characteristic, then registering a different service invokes the
77 // callback to send an indication to the "client."
TEST_F(GenericAttributeServiceTest,IndicateOnRegister)78 TEST_F(GenericAttributeServiceTest, IndicateOnRegister) {
79 std::optional<IdType> indicated_svc_id;
80 auto send_indication =
81 [&](IdType service_id, IdType chrc_id, PeerId peer_id, BufferView value) {
82 EXPECT_EQ(kTestPeerId, peer_id);
83 EXPECT_EQ(kServiceChangedChrcId, chrc_id);
84 indicated_svc_id = service_id;
85
86 ASSERT_EQ(4u, value.size());
87 // The second service following the four-attribute GATT service should
88 // span the subsequent three handles.
89 EXPECT_EQ(0x05, value[0]);
90 EXPECT_EQ(0x00, value[1]);
91 EXPECT_EQ(0x07, value[2]);
92 EXPECT_EQ(0x00, value[3]);
93 };
94
95 // Register the GATT service.
96 GenericAttributeService gatt_service(mgr.GetWeakPtr(),
97 std::move(send_indication));
98
99 // Enable Service Changed indications for the test client.
100 fit::result<att::ErrorCode> status = fit::ok();
101 WriteServiceChangedCcc(kTestPeerId, kEnableInd, &status);
102 EXPECT_EQ(std::nullopt, indicated_svc_id);
103
104 constexpr UUID kTestSvcType(uint32_t{0xdeadbeef});
105 constexpr IdType kChrcId = 12;
106 constexpr uint8_t kChrcProps = Property::kRead;
107 constexpr UUID kTestChrcType(uint32_t{0xdeadbeef});
108 const att::AccessRequirements kReadReqs(/*encryption=*/true,
109 /*authentication=*/true,
110 /*authorization=*/true);
111 const att::AccessRequirements kWriteReqs, kUpdateReqs;
112 auto service = std::make_unique<Service>(/*primary=*/false, kTestSvcType);
113 service->AddCharacteristic(std::make_unique<Characteristic>(kChrcId,
114 kTestChrcType,
115 kChrcProps,
116 0,
117 kReadReqs,
118 kWriteReqs,
119 kUpdateReqs));
120 auto service_id = mgr.RegisterService(
121 std::move(service), NopReadHandler, NopWriteHandler, NopCCCallback);
122 // Verify that service registration succeeded
123 EXPECT_NE(kInvalidId, service_id);
124 // Verify that |send_indication| was invoked to indicate the Service Changed
125 // chrc within the gatt_service.
126 EXPECT_EQ(gatt_service.service_id(), indicated_svc_id);
127 }
128
129 // Same test as above, but the indication is enabled just prior unregistering
130 // the latter test service.
TEST_F(GenericAttributeServiceTest,IndicateOnUnregister)131 TEST_F(GenericAttributeServiceTest, IndicateOnUnregister) {
132 std::optional<IdType> indicated_svc_id;
133 auto send_indication =
134 [&](IdType service_id, IdType chrc_id, PeerId peer_id, BufferView value) {
135 EXPECT_EQ(kTestPeerId, peer_id);
136 EXPECT_EQ(kServiceChangedChrcId, chrc_id);
137 indicated_svc_id = service_id;
138
139 ASSERT_EQ(4u, value.size());
140 // The second service following the four-attribute GATT service should
141 // span the subsequent four handles (update enabled).
142 EXPECT_EQ(0x05, value[0]);
143 EXPECT_EQ(0x00, value[1]);
144 EXPECT_EQ(0x08, value[2]);
145 EXPECT_EQ(0x00, value[3]);
146 };
147
148 // Register the GATT service.
149 GenericAttributeService gatt_service(mgr.GetWeakPtr(),
150 std::move(send_indication));
151
152 constexpr UUID kTestSvcType(uint32_t{0xdeadbeef});
153 constexpr IdType kChrcId = 0;
154 constexpr uint8_t kChrcProps = Property::kNotify;
155 constexpr UUID kTestChrcType(uint32_t{0xdeadbeef});
156 const att::AccessRequirements kReadReqs, kWriteReqs;
157 const att::AccessRequirements kUpdateReqs(/*encryption=*/true,
158 /*authentication=*/true,
159 /*authorization=*/true);
160 auto service = std::make_unique<Service>(/*primary=*/false, kTestSvcType);
161 service->AddCharacteristic(std::make_unique<Characteristic>(kChrcId,
162 kTestChrcType,
163 kChrcProps,
164 0,
165 kReadReqs,
166 kWriteReqs,
167 kUpdateReqs));
168 auto service_id = mgr.RegisterService(
169 std::move(service), NopReadHandler, NopWriteHandler, NopCCCallback);
170 // Verify that service registration succeeded
171 EXPECT_NE(kInvalidId, service_id);
172
173 // Enable Service Changed indications for the test client.
174 fit::result<att::ErrorCode> status = fit::ok();
175 WriteServiceChangedCcc(kTestPeerId, kEnableInd, &status);
176 EXPECT_EQ(std::nullopt, indicated_svc_id);
177
178 mgr.UnregisterService(service_id);
179 // Verify that |send_indication| was invoked to indicate the Service Changed
180 // chrc within the gatt_service.
181 EXPECT_EQ(gatt_service.service_id(), indicated_svc_id);
182 }
183
184 // Tests that registering the GATT service reads a persisted value for the
185 // service changed characteristic's ccc, and that enabling indication on its
186 // service changed characteristic writes a persisted value.
TEST_F(GenericAttributeServiceTest,PersistIndicate)187 TEST_F(GenericAttributeServiceTest, PersistIndicate) {
188 int persist_callback_count = 0;
189
190 auto persist_callback = [&persist_callback_count](
191 PeerId peer_id,
192 ServiceChangedCCCPersistedData gatt_data) {
193 EXPECT_EQ(peer_id, kTestPeerId);
194 EXPECT_EQ(gatt_data.indicate, true);
195 persist_callback_count++;
196 };
197
198 // Register the GATT service.
199 GenericAttributeService gatt_service(mgr.GetWeakPtr(), NopSendIndication);
200 gatt_service.SetPersistServiceChangedCCCCallback(std::move(persist_callback));
201 EXPECT_EQ(persist_callback_count, 0);
202
203 // Enable Service Changed indications for the test client.
204 fit::result<att::ErrorCode> status = fit::ok();
205 WriteServiceChangedCcc(kTestPeerId, kEnableInd, &status);
206 EXPECT_EQ(persist_callback_count, 1);
207 }
208 } // namespace
209 } // namespace bt::gatt
210