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