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