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/common/byte_buffer.h"
19 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
20 #include "pw_bluetooth_sapphire/internal/host/gatt/gatt_defs.h"
21
22 namespace bt::gatt {
23
GenericAttributeService(LocalServiceManager::WeakPtr local_service_manager,SendIndicationCallback send_indication_callback)24 GenericAttributeService::GenericAttributeService(
25 LocalServiceManager::WeakPtr local_service_manager,
26 SendIndicationCallback send_indication_callback)
27 : local_service_manager_(std::move(local_service_manager)),
28 send_indication_callback_(std::move(send_indication_callback)) {
29 PW_CHECK(local_service_manager_.is_alive());
30 PW_DCHECK(send_indication_callback_);
31
32 Register();
33 }
34
~GenericAttributeService()35 GenericAttributeService::~GenericAttributeService() {
36 if (local_service_manager_.is_alive() && service_id_ != kInvalidId) {
37 local_service_manager_->UnregisterService(service_id_);
38 }
39 }
40
Register()41 void GenericAttributeService::Register() {
42 const att::AccessRequirements kDisallowed;
43 const att::AccessRequirements kAllowedNoSecurity(/*encryption=*/false,
44 /*authentication=*/false,
45 /*authorization=*/false);
46 CharacteristicPtr service_changed_chr = std::make_unique<Characteristic>(
47 kServiceChangedChrcId, // id
48 types::kServiceChangedCharacteristic, // type
49 Property::kIndicate, // properties
50 0u, // extended_properties
51 kDisallowed, // read
52 kDisallowed, // write
53 kAllowedNoSecurity); // update
54 auto service =
55 std::make_unique<Service>(true, types::kGenericAttributeService);
56 service->AddCharacteristic(std::move(service_changed_chr));
57
58 ClientConfigCallback ccc_callback = [this](IdType,
59 IdType chrc_id,
60 PeerId peer_id,
61 bool notify,
62 bool indicate) {
63 PW_DCHECK(chrc_id == 0u);
64
65 SetServiceChangedIndicationSubscription(peer_id, indicate);
66 if (persist_service_changed_ccc_callback_) {
67 ServiceChangedCCCPersistedData persisted = {.notify = notify,
68 .indicate = indicate};
69 persist_service_changed_ccc_callback_(peer_id, persisted);
70 } else {
71 bt_log(WARN,
72 "gatt",
73 "Attempted to persist service changed ccc but no callback found.");
74 }
75 };
76
77 // Server Supported Features (Vol 3, Part G, Section 7.4)
78 CharacteristicPtr server_features_chr = std::make_unique<Characteristic>(
79 kServerSupportedFeaturesChrcId, // id
80 types::kServerSupportedFeaturesCharacteristic, // type
81 Property::kRead, // properties
82 0u, // extended_properties
83 kAllowedNoSecurity, // read
84 kDisallowed, // write
85 kDisallowed); // update
86 service->AddCharacteristic(std::move(server_features_chr));
87
88 ReadHandler read_handler =
89 [](PeerId, IdType, IdType chrc_id, uint16_t, ReadResponder responder) {
90 // The stack shouldn't send us any read requests other than this id,
91 // none of the other characteristics or descriptors support it.
92 PW_DCHECK(chrc_id == kServerSupportedFeaturesChrcId);
93
94 // The only octet is the first octet. The only bit is the EATT
95 // supported bit.
96 // TODO(fxbug.dev/364660604): Support EATT, then flip this bit to 1.
97 responder(fit::ok(), StaticByteBuffer(0x00));
98 };
99
100 service_id_ =
101 local_service_manager_->RegisterService(std::move(service),
102 std::move(read_handler),
103 NopWriteHandler,
104 std::move(ccc_callback));
105 PW_DCHECK(service_id_ != kInvalidId);
106
107 local_service_manager_->set_service_changed_callback(
108 fit::bind_member<&GenericAttributeService::OnServiceChanged>(this));
109 }
110
SetServiceChangedIndicationSubscription(PeerId peer_id,bool indicate)111 void GenericAttributeService::SetServiceChangedIndicationSubscription(
112 PeerId peer_id, bool indicate) {
113 if (indicate) {
114 subscribed_peers_.insert(peer_id);
115 bt_log(DEBUG,
116 "gatt",
117 "service: Service Changed enabled for peer %s",
118 bt_str(peer_id));
119 } else {
120 subscribed_peers_.erase(peer_id);
121 bt_log(DEBUG,
122 "gatt",
123 "service: Service Changed disabled for peer %s",
124 bt_str(peer_id));
125 }
126 }
127
OnServiceChanged(IdType service_id,att::Handle start,att::Handle end)128 void GenericAttributeService::OnServiceChanged(IdType service_id,
129 att::Handle start,
130 att::Handle end) {
131 // Don't send indications for this service's removal.
132 if (service_id_ == service_id) {
133 return;
134 }
135
136 StaticByteBuffer<2 * sizeof(uint16_t)> value;
137
138 value[0] = static_cast<uint8_t>(start);
139 value[1] = static_cast<uint8_t>(start >> 8);
140 value[2] = static_cast<uint8_t>(end);
141 value[3] = static_cast<uint8_t>(end >> 8);
142
143 for (auto peer_id : subscribed_peers_) {
144 bt_log(TRACE,
145 "gatt",
146 "service: indicating peer %s of service(s) changed "
147 "(start: %#.4x, end: %#.4x)",
148 bt_str(peer_id),
149 start,
150 end);
151 send_indication_callback_(
152 service_id_, kServiceChangedChrcId, peer_id, value.view());
153 }
154 }
155
156 } // namespace bt::gatt
157