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