• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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