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_assert/check.h>
18 #include <pw_bytes/endian.h>
19
20 #include "pw_bluetooth_sapphire/internal/host/gatt/gatt_defs.h"
21 #include "pw_bluetooth_sapphire/internal/host/gatt/persisted_data.h"
22 #include "pw_unit_test/framework.h"
23
24 namespace bt::gatt {
25 namespace {
26 // Handles for the third attribute (Service Changed characteristic) and fourth
27 // attribute (corresponding client config).
28 constexpr att::Handle kChrcHandle = 0x0003;
29 constexpr att::Handle kCCCHandle = 0x0004;
30 // Handle for the ServerSupportedFeatures Chrc
31 constexpr att::Handle kSupportedFeaturesChrcHandle = 0x0005;
32 constexpr PeerId kTestPeerId(1);
33 constexpr uint16_t kEnableInd = 0x0002;
34
35 class GenericAttributeServiceTest : public ::testing::Test {
36 protected:
WriteServiceChangedCcc(PeerId peer_id,uint16_t ccc_value,fit::result<att::ErrorCode> * out_status)37 bool WriteServiceChangedCcc(PeerId peer_id,
38 uint16_t ccc_value,
39 fit::result<att::ErrorCode>* out_status) {
40 PW_CHECK(out_status);
41
42 auto* attr = mgr.database()->FindAttribute(kCCCHandle);
43 PW_CHECK(attr);
44 auto result_cb = [&out_status](auto cb_status) { *out_status = cb_status; };
45 uint16_t value =
46 pw::bytes::ConvertOrderTo(cpp20::endian::little, ccc_value);
47 return attr->WriteAsync(
48 peer_id, 0u, BufferView(&value, sizeof(value)), result_cb);
49 }
50
51 LocalServiceManager mgr;
52 };
53
54 // Test registration and unregistration of the local GATT service itself.
TEST_F(GenericAttributeServiceTest,RegisterUnregister)55 TEST_F(GenericAttributeServiceTest, RegisterUnregister) {
56 {
57 GenericAttributeService gatt_service(mgr.GetWeakPtr(), NopSendIndication);
58
59 // Check that the local attribute database has a grouping for the GATT GATT
60 // service with four attributes.
61 auto iter = mgr.database()->groupings().begin();
62 EXPECT_TRUE(iter->complete());
63 EXPECT_EQ(6u, iter->attributes().size());
64 EXPECT_TRUE(iter->active());
65 EXPECT_EQ(0x0001, iter->start_handle());
66 EXPECT_EQ(0x0006, iter->end_handle());
67 EXPECT_EQ(types::kPrimaryService, iter->group_type());
68
69 auto const* ccc_attr = mgr.database()->FindAttribute(kCCCHandle);
70 ASSERT_TRUE(ccc_attr != nullptr);
71 EXPECT_EQ(types::kClientCharacteristicConfig, ccc_attr->type());
72 }
73
74 // The service should now be unregistered, so no characeteristic attributes
75 // should be active.
76 auto const* chrc_attr = mgr.database()->FindAttribute(kChrcHandle);
77 ASSERT_TRUE(chrc_attr == nullptr);
78 }
79
80 // Tests that registering the GATT service, enabling indication on its Service
81 // Changed characteristic, then registering a different service invokes the
82 // callback to send an indication to the "client."
TEST_F(GenericAttributeServiceTest,IndicateOnRegister)83 TEST_F(GenericAttributeServiceTest, IndicateOnRegister) {
84 std::optional<IdType> indicated_svc_id;
85 auto send_indication =
86 [&](IdType service_id, IdType chrc_id, PeerId peer_id, BufferView value) {
87 EXPECT_EQ(kTestPeerId, peer_id);
88 EXPECT_EQ(kServiceChangedChrcId, chrc_id);
89 indicated_svc_id = service_id;
90
91 ASSERT_EQ(4u, value.size());
92 // The second service following the six-handle GATT service should
93 // span the subsequent three handles.
94 EXPECT_EQ(0x07, value[0]);
95 EXPECT_EQ(0x00, value[1]);
96 EXPECT_EQ(0x09, value[2]);
97 EXPECT_EQ(0x00, value[3]);
98 };
99
100 // Register the GATT service.
101 GenericAttributeService gatt_service(mgr.GetWeakPtr(),
102 std::move(send_indication));
103
104 // Enable Service Changed indications for the test client.
105 fit::result<att::ErrorCode> status = fit::ok();
106 WriteServiceChangedCcc(kTestPeerId, kEnableInd, &status);
107 EXPECT_EQ(std::nullopt, indicated_svc_id);
108
109 constexpr UUID kTestSvcType(uint32_t{0xdeadbeef});
110 constexpr IdType kChrcId = 12;
111 constexpr uint8_t kChrcProps = Property::kRead;
112 constexpr UUID kTestChrcType(uint32_t{0xdeadbeef});
113 const att::AccessRequirements kReadReqs(/*encryption=*/true,
114 /*authentication=*/true,
115 /*authorization=*/true);
116 const att::AccessRequirements kWriteReqs, kUpdateReqs;
117 auto service = std::make_unique<Service>(/*primary=*/false, kTestSvcType);
118 service->AddCharacteristic(std::make_unique<Characteristic>(kChrcId,
119 kTestChrcType,
120 kChrcProps,
121 0,
122 kReadReqs,
123 kWriteReqs,
124 kUpdateReqs));
125 auto service_id = mgr.RegisterService(
126 std::move(service), NopReadHandler, NopWriteHandler, NopCCCallback);
127 // Verify that service registration succeeded
128 EXPECT_NE(kInvalidId, service_id);
129 // Verify that |send_indication| was invoked to indicate the Service Changed
130 // chrc within the gatt_service.
131 EXPECT_EQ(gatt_service.service_id(), indicated_svc_id);
132 }
133
TEST_F(GenericAttributeServiceTest,ReadSupportedFeatures)134 TEST_F(GenericAttributeServiceTest, ReadSupportedFeatures) {
135 // Register the GATT service.
136 GenericAttributeService gatt_service(mgr.GetWeakPtr(), NopSendIndication);
137 auto const* ssf_attr =
138 mgr.database()->FindAttribute(kSupportedFeaturesChrcHandle);
139 ASSERT_TRUE(ssf_attr != nullptr);
140
141 auto result_callback = [&](auto status, const ByteBuffer& value) {
142 ASSERT_TRUE(status.is_ok());
143 ASSERT_EQ(1u, value.size());
144 ASSERT_EQ(0x00, value[0]);
145 };
146
147 ssf_attr->ReadAsync(PeerId(1), 0, result_callback);
148 }
149
150 // Same test as above, but the indication is enabled just prior unregistering
151 // the latter test service.
TEST_F(GenericAttributeServiceTest,IndicateOnUnregister)152 TEST_F(GenericAttributeServiceTest, IndicateOnUnregister) {
153 std::optional<IdType> indicated_svc_id;
154 auto send_indication =
155 [&](IdType service_id, IdType chrc_id, PeerId peer_id, BufferView value) {
156 EXPECT_EQ(kTestPeerId, peer_id);
157 EXPECT_EQ(kServiceChangedChrcId, chrc_id);
158 indicated_svc_id = service_id;
159
160 ASSERT_EQ(4u, value.size());
161 // The second service following the six-handle GATT service should
162 // span the subsequent four handles (update enabled).
163 EXPECT_EQ(0x07, value[0]);
164 EXPECT_EQ(0x00, value[1]);
165 EXPECT_EQ(0x0A, value[2]);
166 EXPECT_EQ(0x00, value[3]);
167 };
168
169 // Register the GATT service.
170 GenericAttributeService gatt_service(mgr.GetWeakPtr(),
171 std::move(send_indication));
172
173 constexpr UUID kTestSvcType(uint32_t{0xdeadbeef});
174 constexpr IdType kChrcId = 0;
175 constexpr uint8_t kChrcProps = Property::kNotify;
176 constexpr UUID kTestChrcType(uint32_t{0xdeadbeef});
177 const att::AccessRequirements kReadReqs, kWriteReqs;
178 const att::AccessRequirements kUpdateReqs(/*encryption=*/true,
179 /*authentication=*/true,
180 /*authorization=*/true);
181 auto service = std::make_unique<Service>(/*primary=*/false, kTestSvcType);
182 service->AddCharacteristic(std::make_unique<Characteristic>(kChrcId,
183 kTestChrcType,
184 kChrcProps,
185 0,
186 kReadReqs,
187 kWriteReqs,
188 kUpdateReqs));
189 auto service_id = mgr.RegisterService(
190 std::move(service), NopReadHandler, NopWriteHandler, NopCCCallback);
191 // Verify that service registration succeeded
192 EXPECT_NE(kInvalidId, service_id);
193
194 // Enable Service Changed indications for the test client.
195 fit::result<att::ErrorCode> status = fit::ok();
196 WriteServiceChangedCcc(kTestPeerId, kEnableInd, &status);
197 EXPECT_EQ(std::nullopt, indicated_svc_id);
198
199 mgr.UnregisterService(service_id);
200 // Verify that |send_indication| was invoked to indicate the Service Changed
201 // chrc within the gatt_service.
202 EXPECT_EQ(gatt_service.service_id(), indicated_svc_id);
203 }
204
205 // Tests that registering the GATT service reads a persisted value for the
206 // service changed characteristic's ccc, and that enabling indication on its
207 // service changed characteristic writes a persisted value.
TEST_F(GenericAttributeServiceTest,PersistIndicate)208 TEST_F(GenericAttributeServiceTest, PersistIndicate) {
209 int persist_callback_count = 0;
210
211 auto persist_callback = [&persist_callback_count](
212 PeerId peer_id,
213 ServiceChangedCCCPersistedData gatt_data) {
214 EXPECT_EQ(peer_id, kTestPeerId);
215 EXPECT_EQ(gatt_data.indicate, true);
216 persist_callback_count++;
217 };
218
219 // Register the GATT service.
220 GenericAttributeService gatt_service(mgr.GetWeakPtr(), NopSendIndication);
221 gatt_service.SetPersistServiceChangedCCCCallback(std::move(persist_callback));
222 EXPECT_EQ(persist_callback_count, 0);
223
224 // Enable Service Changed indications for the test client.
225 fit::result<att::ErrorCode> status = fit::ok();
226 WriteServiceChangedCcc(kTestPeerId, kEnableInd, &status);
227 EXPECT_EQ(persist_callback_count, 1);
228 }
229 } // namespace
230 } // namespace bt::gatt
231