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/hci/android_extended_low_energy_advertiser.h"
16
17 #include <pw_assert/check.h>
18 #include <pw_bluetooth/hci_android.emb.h>
19 #include <pw_bluetooth/hci_common.emb.h>
20
21 #include "pw_bluetooth_sapphire/internal/host/hci-spec/vendor_protocol.h"
22 #include "pw_bluetooth_sapphire/internal/host/transport/transport.h"
23
24 namespace bt::hci {
25 namespace pwemb = pw::bluetooth::emboss;
26
27 // Android range -70 to +20, select the middle for now
28 constexpr int8_t kTransmitPower = -25;
29
30 // AndroidExtendedLowEnergyAdvertiser doesn't support extended advertising PDUs
31 constexpr bool kUseExtendedPdu = false;
32
33 namespace android_hci = hci_spec::vendor::android;
34 namespace android_emb = pw::bluetooth::vendor::android_hci;
35
AndroidExtendedLowEnergyAdvertiser(hci::Transport::WeakPtr hci_ptr,uint8_t max_advertisements)36 AndroidExtendedLowEnergyAdvertiser::AndroidExtendedLowEnergyAdvertiser(
37 hci::Transport::WeakPtr hci_ptr, uint8_t max_advertisements)
38 : LowEnergyAdvertiser(std::move(hci_ptr),
39 hci_spec::kMaxLEAdvertisingDataLength),
40 advertising_handle_map_(max_advertisements) {
41 state_changed_event_handler_id_ =
42 hci()->command_channel()->AddVendorEventHandler(
43 android_hci::kLEMultiAdvtStateChangeSubeventCode,
44 [this](const EventPacket& event_packet) {
45 return OnAdvertisingStateChangedSubevent(event_packet);
46 });
47 }
48
~AndroidExtendedLowEnergyAdvertiser()49 AndroidExtendedLowEnergyAdvertiser::~AndroidExtendedLowEnergyAdvertiser() {
50 // This object is probably being destroyed because the stack is shutting down,
51 // in which case the HCI layer may have already been destroyed.
52 if (!hci().is_alive() || !hci()->command_channel()) {
53 return;
54 }
55
56 hci()->command_channel()->RemoveEventHandler(state_changed_event_handler_id_);
57 // TODO(fxbug.dev/42063496): This will only cancel one advertisement, after
58 // which the SequentialCommandRunner will have been destroyed and no further
59 // commands will be sent.
60 StopAdvertising();
61 }
62
BuildEnablePacket(const DeviceAddress & address,pwemb::GenericEnableParam enable,bool extended_pdu)63 CommandPacket AndroidExtendedLowEnergyAdvertiser::BuildEnablePacket(
64 const DeviceAddress& address,
65 pwemb::GenericEnableParam enable,
66 bool extended_pdu) {
67 std::optional<hci_spec::AdvertisingHandle> handle =
68 advertising_handle_map_.GetHandle(address, extended_pdu);
69 PW_CHECK(handle);
70
71 auto packet =
72 hci::CommandPacket::New<android_emb::LEMultiAdvtEnableCommandWriter>(
73 android_hci::kLEMultiAdvt);
74 auto packet_view = packet.view_t();
75 packet_view.vendor_command().sub_opcode().Write(
76 android_hci::kLEMultiAdvtEnableSubopcode);
77 packet_view.enable().Write(enable);
78 packet_view.advertising_handle().Write(handle.value());
79 return packet;
80 }
81
82 std::optional<CommandPacket>
BuildSetAdvertisingParams(const DeviceAddress & address,const AdvertisingEventProperties & properties,pwemb::LEOwnAddressType own_address_type,const AdvertisingIntervalRange & interval,bool extended_pdu)83 AndroidExtendedLowEnergyAdvertiser::BuildSetAdvertisingParams(
84 const DeviceAddress& address,
85 const AdvertisingEventProperties& properties,
86 pwemb::LEOwnAddressType own_address_type,
87 const AdvertisingIntervalRange& interval,
88 bool extended_pdu) {
89 std::optional<hci_spec::AdvertisingHandle> handle =
90 advertising_handle_map_.MapHandle(address, extended_pdu);
91 if (!handle) {
92 bt_log(WARN,
93 "hci-le",
94 "could not allocate advertising handle for address: %s",
95 bt_str(address));
96 return std::nullopt;
97 }
98
99 auto packet = hci::CommandPacket::New<
100 android_emb::LEMultiAdvtSetAdvtParamCommandWriter>(
101 android_hci::kLEMultiAdvt);
102 auto view = packet.view_t();
103
104 view.vendor_command().sub_opcode().Write(
105 android_hci::kLEMultiAdvtSetAdvtParamSubopcode);
106 view.adv_interval_min().Write(interval.min());
107 view.adv_interval_max().Write(interval.max());
108 view.adv_type().Write(
109 AdvertisingEventPropertiesToLEAdvertisingType(properties));
110 view.own_addr_type().Write(own_address_type);
111 view.adv_channel_map().channel_37().Write(true);
112 view.adv_channel_map().channel_38().Write(true);
113 view.adv_channel_map().channel_39().Write(true);
114 view.adv_filter_policy().Write(pwemb::LEAdvertisingFilterPolicy::ALLOW_ALL);
115 view.adv_handle().Write(handle.value());
116 view.adv_tx_power().Write(hci_spec::kLEAdvertisingTxPowerMax);
117
118 // We don't support directed advertising yet, so leave peer_address and
119 // peer_address_type as 0x00
120 // (|packet| parameters are initialized to zero above).
121
122 return packet;
123 }
124
125 std::vector<CommandPacket>
BuildSetAdvertisingData(const DeviceAddress & address,const AdvertisingData & data,AdvFlags flags,bool extended_pdu)126 AndroidExtendedLowEnergyAdvertiser::BuildSetAdvertisingData(
127 const DeviceAddress& address,
128 const AdvertisingData& data,
129 AdvFlags flags,
130 bool extended_pdu) {
131 if (data.CalculateBlockSize() == 0) {
132 std::vector<CommandPacket> packets;
133 return packets;
134 }
135
136 std::optional<hci_spec::AdvertisingHandle> handle =
137 advertising_handle_map_.GetHandle(address, extended_pdu);
138 PW_CHECK(handle);
139
140 uint8_t adv_data_length =
141 static_cast<uint8_t>(data.CalculateBlockSize(/*include_flags=*/true));
142 size_t packet_size =
143 android_emb::LEMultiAdvtSetAdvtDataCommandWriter::MinSizeInBytes()
144 .Read() +
145 adv_data_length;
146
147 auto packet =
148 hci::CommandPacket::New<android_emb::LEMultiAdvtSetAdvtDataCommandWriter>(
149 android_hci::kLEMultiAdvt, packet_size);
150 auto view = packet.view_t();
151
152 view.vendor_command().sub_opcode().Write(
153 android_hci::kLEMultiAdvtSetAdvtDataSubopcode);
154 view.adv_data_length().Write(adv_data_length);
155 view.adv_handle().Write(handle.value());
156
157 MutableBufferView data_view(view.adv_data().BackingStorage().data(),
158 adv_data_length);
159 data.WriteBlock(&data_view, flags);
160
161 std::vector<CommandPacket> packets;
162 packets.reserve(1);
163 packets.emplace_back(std::move(packet));
164 return packets;
165 }
166
BuildUnsetAdvertisingData(const DeviceAddress & address,bool extended_pdu)167 CommandPacket AndroidExtendedLowEnergyAdvertiser::BuildUnsetAdvertisingData(
168 const DeviceAddress& address, bool extended_pdu) {
169 std::optional<hci_spec::AdvertisingHandle> handle =
170 advertising_handle_map_.GetHandle(address, extended_pdu);
171 PW_CHECK(handle);
172
173 size_t packet_size =
174 android_emb::LEMultiAdvtSetAdvtDataCommandWriter::MinSizeInBytes().Read();
175 auto packet =
176 hci::CommandPacket::New<android_emb::LEMultiAdvtSetAdvtDataCommandWriter>(
177 android_hci::kLEMultiAdvt, packet_size);
178 auto view = packet.view_t();
179
180 view.vendor_command().sub_opcode().Write(
181 android_hci::kLEMultiAdvtSetAdvtDataSubopcode);
182 view.adv_data_length().Write(0);
183 view.adv_handle().Write(handle.value());
184
185 return packet;
186 }
187
188 std::vector<CommandPacket>
BuildSetScanResponse(const DeviceAddress & address,const AdvertisingData & data,bool extended_pdu)189 AndroidExtendedLowEnergyAdvertiser::BuildSetScanResponse(
190 const DeviceAddress& address,
191 const AdvertisingData& data,
192 bool extended_pdu) {
193 if (data.CalculateBlockSize() == 0) {
194 std::vector<CommandPacket> packets;
195 return packets;
196 }
197
198 std::optional<hci_spec::AdvertisingHandle> handle =
199 advertising_handle_map_.GetHandle(address, extended_pdu);
200 PW_CHECK(handle);
201
202 uint8_t scan_rsp_length = static_cast<uint8_t>(data.CalculateBlockSize());
203 size_t packet_size =
204 android_emb::LEMultiAdvtSetScanRespDataCommandWriter::MinSizeInBytes()
205 .Read() +
206 scan_rsp_length;
207 auto packet = hci::CommandPacket::New<
208 android_emb::LEMultiAdvtSetScanRespDataCommandWriter>(
209 android_hci::kLEMultiAdvt, packet_size);
210 auto view = packet.view_t();
211
212 view.vendor_command().sub_opcode().Write(
213 android_hci::kLEMultiAdvtSetScanRespSubopcode);
214 view.scan_resp_length().Write(scan_rsp_length);
215 view.adv_handle().Write(handle.value());
216
217 MutableBufferView data_view(view.scan_resp_data().BackingStorage().data(),
218 scan_rsp_length);
219 data.WriteBlock(&data_view, std::nullopt);
220
221 std::vector<CommandPacket> packets;
222 packets.reserve(1);
223 packets.emplace_back(std::move(packet));
224 return packets;
225 }
226
BuildUnsetScanResponse(const DeviceAddress & address,bool extended_pdu)227 CommandPacket AndroidExtendedLowEnergyAdvertiser::BuildUnsetScanResponse(
228 const DeviceAddress& address, bool extended_pdu) {
229 std::optional<hci_spec::AdvertisingHandle> handle =
230 advertising_handle_map_.GetHandle(address, extended_pdu);
231 PW_CHECK(handle);
232
233 size_t packet_size =
234 android_emb::LEMultiAdvtSetScanRespDataCommandWriter::MinSizeInBytes()
235 .Read();
236 auto packet = hci::CommandPacket::New<
237 android_emb::LEMultiAdvtSetScanRespDataCommandWriter>(
238 android_hci::kLEMultiAdvt, packet_size);
239 auto view = packet.view_t();
240
241 view.vendor_command().sub_opcode().Write(
242 android_hci::kLEMultiAdvtSetScanRespSubopcode);
243 view.scan_resp_length().Write(0);
244 view.adv_handle().Write(handle.value());
245
246 return packet;
247 }
248
BuildRemoveAdvertisingSet(const DeviceAddress & address,bool extended_pdu)249 CommandPacket AndroidExtendedLowEnergyAdvertiser::BuildRemoveAdvertisingSet(
250 const DeviceAddress& address, bool extended_pdu) {
251 std::optional<hci_spec::AdvertisingHandle> handle =
252 advertising_handle_map_.GetHandle(address, extended_pdu);
253 PW_CHECK(handle);
254
255 auto packet =
256 hci::CommandPacket::New<android_emb::LEMultiAdvtEnableCommandWriter>(
257 android_hci::kLEMultiAdvt);
258 auto packet_view = packet.view_t();
259 packet_view.vendor_command().sub_opcode().Write(
260 android_hci::kLEMultiAdvtEnableSubopcode);
261 packet_view.enable().Write(pwemb::GenericEnableParam::DISABLE);
262 packet_view.advertising_handle().Write(handle.value());
263 return packet;
264 }
265
StartAdvertising(const DeviceAddress & address,const AdvertisingData & data,const AdvertisingData & scan_rsp,const AdvertisingOptions & options,ConnectionCallback connect_callback,ResultFunction<> result_callback)266 void AndroidExtendedLowEnergyAdvertiser::StartAdvertising(
267 const DeviceAddress& address,
268 const AdvertisingData& data,
269 const AdvertisingData& scan_rsp,
270 const AdvertisingOptions& options,
271 ConnectionCallback connect_callback,
272 ResultFunction<> result_callback) {
273 if (options.extended_pdu) {
274 bt_log(WARN,
275 "hci-le",
276 "android vendor extensions cannot use extended advertising PDUs");
277 result_callback(ToResult(HostError::kNotSupported));
278 return;
279 }
280
281 fit::result<HostError> result =
282 CanStartAdvertising(address, data, scan_rsp, options, connect_callback);
283 if (result.is_error()) {
284 result_callback(ToResult(result.error_value()));
285 return;
286 }
287
288 AdvertisingData copied_data;
289 data.Copy(&copied_data);
290
291 AdvertisingData copied_scan_rsp;
292 scan_rsp.Copy(&copied_scan_rsp);
293
294 // if there is an operation currently in progress, enqueue this operation and
295 // we will get to it the next time we have a chance
296 if (!hci_cmd_runner().IsReady()) {
297 bt_log(INFO,
298 "hci-le",
299 "hci cmd runner not ready, queuing advertisement commands for now");
300
301 op_queue_.push([this,
302 address,
303 data_copy = std::move(copied_data),
304 scan_rsp_copy = std::move(copied_scan_rsp),
305 options_copy = options,
306 conn_cb = std::move(connect_callback),
307 result_cb = std::move(result_callback)]() mutable {
308 StartAdvertising(address,
309 data_copy,
310 scan_rsp_copy,
311 options_copy,
312 std::move(conn_cb),
313 std::move(result_cb));
314 });
315
316 return;
317 }
318
319 if (IsAdvertising(address, options.extended_pdu)) {
320 bt_log(DEBUG,
321 "hci-le",
322 "updating existing advertisement for %s",
323 bt_str(address));
324 }
325
326 if (options.include_tx_power_level) {
327 copied_data.SetTxPower(kTransmitPower);
328 copied_scan_rsp.SetTxPower(kTransmitPower);
329 }
330
331 StartAdvertisingInternal(address,
332 copied_data,
333 copied_scan_rsp,
334 options,
335 std::move(connect_callback),
336 std::move(result_callback));
337 }
338
StopAdvertising()339 void AndroidExtendedLowEnergyAdvertiser::StopAdvertising() {
340 LowEnergyAdvertiser::StopAdvertising();
341 advertising_handle_map_.Clear();
342
343 // std::queue doesn't have a clear method so we have to resort to this
344 // tomfoolery :(
345 decltype(op_queue_) empty;
346 std::swap(op_queue_, empty);
347 }
348
StopAdvertising(const DeviceAddress & address,bool extended_pdu)349 void AndroidExtendedLowEnergyAdvertiser::StopAdvertising(
350 const DeviceAddress& address, bool extended_pdu) {
351 // if there is an operation currently in progress, enqueue this operation and
352 // we will get to it the next time we have a chance
353 if (!hci_cmd_runner().IsReady()) {
354 bt_log(
355 INFO,
356 "hci-le",
357 "hci cmd runner not ready, queueing stop advertising command for now");
358 op_queue_.push([this, address, extended_pdu]() {
359 StopAdvertising(address, extended_pdu);
360 });
361 return;
362 }
363
364 LowEnergyAdvertiser::StopAdvertisingInternal(address, kUseExtendedPdu);
365 advertising_handle_map_.RemoveAddress(address, kUseExtendedPdu);
366 }
367
OnIncomingConnection(hci_spec::ConnectionHandle handle,pwemb::ConnectionRole role,const DeviceAddress & peer_address,const hci_spec::LEConnectionParameters & conn_params)368 void AndroidExtendedLowEnergyAdvertiser::OnIncomingConnection(
369 hci_spec::ConnectionHandle handle,
370 pwemb::ConnectionRole role,
371 const DeviceAddress& peer_address,
372 const hci_spec::LEConnectionParameters& conn_params) {
373 staged_connections_map_[handle] = {role, peer_address, conn_params};
374 }
375
376 // The LE multi-advertising state change subevent contains the mapping between
377 // connection handle and advertising handle. After the LE multi-advertising
378 // state change subevent, we have all the information necessary to create a
379 // connection object within the Host layer.
380 CommandChannel::EventCallbackResult
OnAdvertisingStateChangedSubevent(const EventPacket & event)381 AndroidExtendedLowEnergyAdvertiser::OnAdvertisingStateChangedSubevent(
382 const EventPacket& event) {
383 PW_CHECK(event.event_code() == hci_spec::kVendorDebugEventCode);
384 PW_CHECK(event.view<pwemb::VendorDebugEventView>().subevent_code().Read() ==
385 android_hci::kLEMultiAdvtStateChangeSubeventCode);
386
387 Result<> result = event.ToResult();
388 if (bt_is_error(result,
389 ERROR,
390 "hci-le",
391 "advertising state change event, error received %s",
392 bt_str(result))) {
393 return CommandChannel::EventCallbackResult::kContinue;
394 }
395
396 auto view = event.view<android_emb::LEMultiAdvtStateChangeSubeventView>();
397 hci_spec::AdvertisingHandle adv_handle = view.advertising_handle().Read();
398 std::optional<DeviceAddress> opt_local_address =
399 advertising_handle_map_.GetAddress(adv_handle);
400
401 // We use the identity address as the local address if we aren't advertising
402 // or otherwise don't know about this advertising set. This is obviously
403 // wrong. However, the link will be disconnected in that case before it can
404 // propagate to higher layers.
405 static DeviceAddress identity_address =
406 DeviceAddress(DeviceAddress::Type::kLEPublic, {0});
407 DeviceAddress local_address = identity_address;
408 if (opt_local_address) {
409 local_address = opt_local_address.value();
410 }
411
412 hci_spec::ConnectionHandle connection_handle =
413 view.connection_handle().Read();
414 auto staged_node = staged_connections_map_.extract(connection_handle);
415 if (staged_node.empty()) {
416 bt_log(ERROR,
417 "hci-le",
418 "advertising state change event, staged params not available "
419 "(handle: %d)",
420 view.advertising_handle().Read());
421 return CommandChannel::EventCallbackResult::kContinue;
422 }
423
424 StagedConnectionParameters staged = staged_node.mapped();
425 CompleteIncomingConnection(connection_handle,
426 staged.role,
427 local_address,
428 staged.peer_address,
429 staged.conn_params,
430 kUseExtendedPdu);
431
432 return CommandChannel::EventCallbackResult::kContinue;
433 }
434
OnCurrentOperationComplete()435 void AndroidExtendedLowEnergyAdvertiser::OnCurrentOperationComplete() {
436 if (op_queue_.empty()) {
437 return; // no more queued operations so nothing to do
438 }
439
440 fit::closure closure = std::move(op_queue_.front());
441 op_queue_.pop();
442 closure();
443 }
444
445 } // namespace bt::hci
446