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/gap/generic_access_client.h"
16
17 #include <pw_assert/check.h>
18 #include <pw_bytes/endian.h>
19
20 #include "pw_bluetooth_sapphire/internal/host/gap/gap.h"
21
22 namespace bt::gap::internal {
23
GenericAccessClient(PeerId peer_id,gatt::RemoteService::WeakPtr service)24 GenericAccessClient::GenericAccessClient(PeerId peer_id,
25 gatt::RemoteService::WeakPtr service)
26 : WeakSelf(this), service_(std::move(service)), peer_id_(peer_id) {
27 PW_CHECK(service_.is_alive());
28 PW_CHECK(service_->uuid() == kGenericAccessService);
29 }
30
ReadDeviceName(DeviceNameCallback callback)31 void GenericAccessClient::ReadDeviceName(DeviceNameCallback callback) {
32 service_->DiscoverCharacteristics(
33 [self = GetWeakPtr(), cb = std::move(callback)](
34 att::Result<> result, const gatt::CharacteristicMap& chars) mutable {
35 if (!self.is_alive()) {
36 return;
37 }
38
39 if (result.is_error()) {
40 cb(result.take_error());
41 return;
42 }
43
44 std::optional<gatt::CharacteristicHandle> device_name_value_handle;
45 for (auto& [handle, chr] : chars) {
46 auto& data = chr.first;
47 if (data.type == kDeviceNameCharacteristic) {
48 device_name_value_handle.emplace(data.value_handle);
49 break;
50 }
51 }
52
53 if (!device_name_value_handle) {
54 bt_log(DEBUG,
55 "gap-le",
56 "GAP service does not have device name characteristic "
57 "(peer: %s)",
58 bt_str(self->peer_id_));
59 cb(ToResult(HostError::kNotFound).take_error());
60 return;
61 }
62
63 // according to Core Spec v5.3, Vol 3, Part C, 12.1: "0 to 248 octets in
64 // length"
65 self->service_->ReadLongCharacteristic(
66 *device_name_value_handle,
67 /*offset=*/0,
68 att::kMaxAttributeValueLength,
69 [self, device_name_cb = std::move(cb)](
70 att::Result<> discover_result,
71 const ByteBuffer& buffer,
72 bool /*maybe_truncated*/) mutable {
73 if (!self.is_alive()) {
74 return;
75 }
76
77 if (bt_is_error(
78 discover_result,
79 DEBUG,
80 "gap-le",
81 "error reading device name characteristic (peer: %s)",
82 bt_str(self->peer_id_))) {
83 device_name_cb(discover_result.take_error());
84 return;
85 }
86
87 const auto device_name_end =
88 std::find(buffer.begin(), buffer.end(), '\0');
89 device_name_cb(
90 fit::ok(std::string(buffer.begin(), device_name_end)));
91 });
92 });
93 }
94
ReadAppearance(AppearanceCallback callback)95 void GenericAccessClient::ReadAppearance(AppearanceCallback callback) {
96 service_->DiscoverCharacteristics([self = GetWeakPtr(),
97 cb = std::move(callback)](
98 att::Result<> result,
99 const gatt::CharacteristicMap&
100 chars) mutable {
101 if (!self.is_alive()) {
102 return;
103 }
104
105 if (result.is_error()) {
106 cb(result.take_error());
107 return;
108 }
109
110 std::optional<gatt::CharacteristicHandle> appearance_value_handle;
111 for (auto& [handle, chr] : chars) {
112 auto& data = chr.first;
113 if (data.type == kAppearanceCharacteristic) {
114 appearance_value_handle.emplace(data.value_handle);
115 break;
116 }
117 }
118
119 if (!appearance_value_handle) {
120 bt_log(DEBUG,
121 "gap-le",
122 "GAP service does not have appearance characteristic "
123 "(peer: %s)",
124 bt_str(self->peer_id_));
125 cb(ToResult(HostError::kNotFound).take_error());
126 return;
127 }
128
129 // according to Core Spec v5.3, Vol 3, Part C, 12.2: "2 octets in length"
130 self->service_->ReadCharacteristic(
131 *appearance_value_handle,
132 [self, appearance_cb = std::move(cb)](
133 att::Result<> discover_result,
134 const ByteBuffer& buffer,
135 bool /*maybe_truncated*/) mutable {
136 if (!self.is_alive()) {
137 return;
138 }
139
140 if (bt_is_error(discover_result,
141 DEBUG,
142 "gap-le",
143 "error reading appearance characteristic (peer: %s)",
144 bt_str(self->peer_id_))) {
145 appearance_cb(discover_result.take_error());
146 return;
147 }
148
149 if (buffer.size() != sizeof(uint16_t)) {
150 bt_log(
151 DEBUG,
152 "gap-le",
153 "appearance characteristic has invalid value size (peer: %s)",
154 bt_str(self->peer_id_));
155 appearance_cb(ToResult(HostError::kPacketMalformed).take_error());
156 return;
157 }
158
159 uint16_t char_value = pw::bytes::ConvertOrderFrom(
160 cpp20::endian::little, buffer.template To<uint16_t>());
161 appearance_cb(fit::ok(char_value));
162 });
163 });
164 }
165
ReadPeripheralPreferredConnectionParameters(ConnectionParametersCallback callback)166 void GenericAccessClient::ReadPeripheralPreferredConnectionParameters(
167 ConnectionParametersCallback callback) {
168 service_->DiscoverCharacteristics([self = GetWeakPtr(),
169 cb = std::move(callback)](
170 att::Result<> result,
171 const gatt::CharacteristicMap&
172 chars) mutable {
173 if (!self.is_alive()) {
174 return;
175 }
176
177 if (result.is_error()) {
178 cb(result.take_error());
179 return;
180 }
181
182 std::optional<gatt::CharacteristicHandle> conn_params_value_handle;
183 for (auto& [handle, chr] : chars) {
184 auto& data = chr.first;
185 if (data.type == kPeripheralPreferredConnectionParametersCharacteristic) {
186 conn_params_value_handle.emplace(data.value_handle);
187 break;
188 }
189 }
190
191 if (!conn_params_value_handle) {
192 bt_log(DEBUG,
193 "gap-le",
194 "GAP service does not have peripheral preferred connection "
195 "parameters characteristic "
196 "(peer: %s)",
197 bt_str(self->peer_id_));
198 cb(ToResult(HostError::kNotFound).take_error());
199 return;
200 }
201
202 self->service_->ReadCharacteristic(
203 *conn_params_value_handle,
204 [self, connection_params_cb = std::move(cb)](
205 att::Result<> discover_result,
206 const ByteBuffer& buffer,
207 bool /*maybe_truncated*/) mutable {
208 if (!self.is_alive()) {
209 return;
210 }
211
212 if (bt_is_error(discover_result,
213 DEBUG,
214 "gap-le",
215 "error reading peripheral preferred connection "
216 "parameters characteristic "
217 "(peer: %s)",
218 bt_str(self->peer_id_))) {
219 connection_params_cb(discover_result.take_error());
220 return;
221 }
222
223 if (buffer.size() !=
224 sizeof(
225 PeripheralPreferredConnectionParametersCharacteristicValue)) {
226 bt_log(DEBUG,
227 "gap-le",
228 "peripheral preferred connection parameters characteristic "
229 "has invalid value size "
230 "(peer: %s)",
231 bt_str(self->peer_id_));
232 connection_params_cb(
233 ToResult(HostError::kPacketMalformed).take_error());
234 return;
235 }
236
237 auto char_value = buffer.template To<
238 PeripheralPreferredConnectionParametersCharacteristicValue>();
239 hci_spec::LEPreferredConnectionParameters params(
240 pw::bytes::ConvertOrderFrom(cpp20::endian::little,
241 char_value.min_interval),
242 pw::bytes::ConvertOrderFrom(cpp20::endian::little,
243 char_value.max_interval),
244 pw::bytes::ConvertOrderFrom(cpp20::endian::little,
245 char_value.max_latency),
246 pw::bytes::ConvertOrderFrom(cpp20::endian::little,
247 char_value.supervision_timeout));
248
249 connection_params_cb(fit::ok(params));
250 });
251 });
252 }
253
254 } // namespace bt::gap::internal
255