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