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/low_energy_advertiser.h"
16
17 #include "pw_bluetooth_sapphire/internal/host/hci/sequential_command_runner.h"
18
19 namespace bt::hci {
20
LowEnergyAdvertiser(hci::Transport::WeakPtr hci)21 LowEnergyAdvertiser::LowEnergyAdvertiser(hci::Transport::WeakPtr hci)
22 : hci_(std::move(hci)),
23 hci_cmd_runner_(std::make_unique<SequentialCommandRunner>(
24 hci_->command_channel()->AsWeakPtr())) {}
25
CanStartAdvertising(const DeviceAddress & address,const AdvertisingData & data,const AdvertisingData & scan_rsp,const AdvertisingOptions & options) const26 fit::result<HostError> LowEnergyAdvertiser::CanStartAdvertising(
27 const DeviceAddress& address,
28 const AdvertisingData& data,
29 const AdvertisingData& scan_rsp,
30 const AdvertisingOptions& options) const {
31 BT_ASSERT(address.type() != DeviceAddress::Type::kBREDR);
32
33 if (options.anonymous) {
34 bt_log(WARN, "hci-le", "anonymous advertising not supported");
35 return fit::error(HostError::kNotSupported);
36 }
37
38 // If the TX Power Level is requested, ensure both buffers have enough space.
39 size_t size_limit = GetSizeLimit();
40 if (options.include_tx_power_level) {
41 size_limit -= kTLVTxPowerLevelSize;
42 }
43
44 if (size_t size = data.CalculateBlockSize(/*include_flags=*/true);
45 size > size_limit) {
46 bt_log(WARN,
47 "hci-le",
48 "advertising data too large (actual: %zu, max: %zu)",
49 size,
50 size_limit);
51 return fit::error(HostError::kAdvertisingDataTooLong);
52 }
53
54 if (size_t size = scan_rsp.CalculateBlockSize(/*include_flags=*/false);
55 size > size_limit) {
56 bt_log(WARN,
57 "hci-le",
58 "scan response too large (actual: %zu, max: %zu)",
59 size,
60 size_limit);
61 return fit::error(HostError::kScanResponseTooLong);
62 }
63
64 return fit::ok();
65 }
66
StartAdvertisingInternal(const DeviceAddress & address,const AdvertisingData & data,const AdvertisingData & scan_rsp,AdvertisingIntervalRange interval,AdvFlags flags,ConnectionCallback connect_callback,hci::ResultFunction<> result_callback)67 void LowEnergyAdvertiser::StartAdvertisingInternal(
68 const DeviceAddress& address,
69 const AdvertisingData& data,
70 const AdvertisingData& scan_rsp,
71 AdvertisingIntervalRange interval,
72 AdvFlags flags,
73 ConnectionCallback connect_callback,
74 hci::ResultFunction<> result_callback) {
75 if (IsAdvertising(address)) {
76 // Temporarily disable advertising so we can tweak the parameters
77 EmbossCommandPacket packet = BuildEnablePacket(
78 address, pw::bluetooth::emboss::GenericEnableParam::DISABLE);
79 hci_cmd_runner_->QueueCommand(packet);
80 }
81
82 // Set advertising parameters
83 pw::bluetooth::emboss::LEAdvertisingType type =
84 pw::bluetooth::emboss::LEAdvertisingType::NOT_CONNECTABLE_UNDIRECTED;
85 if (connect_callback) {
86 type = pw::bluetooth::emboss::LEAdvertisingType::
87 CONNECTABLE_AND_SCANNABLE_UNDIRECTED;
88 } else if (scan_rsp.CalculateBlockSize() > 0) {
89 type = pw::bluetooth::emboss::LEAdvertisingType::SCANNABLE_UNDIRECTED;
90 }
91
92 pw::bluetooth::emboss::LEOwnAddressType own_addr_type;
93 if (address.type() == DeviceAddress::Type::kLEPublic) {
94 own_addr_type = pw::bluetooth::emboss::LEOwnAddressType::PUBLIC;
95 } else {
96 own_addr_type = pw::bluetooth::emboss::LEOwnAddressType::RANDOM;
97 }
98
99 data.Copy(&staged_parameters_.data);
100 scan_rsp.Copy(&staged_parameters_.scan_rsp);
101
102 using PacketPtr = std::unique_ptr<CommandPacket>;
103
104 CommandChannel::CommandPacketVariant set_adv_params_packet =
105 BuildSetAdvertisingParams(address, type, own_addr_type, interval);
106 if (std::holds_alternative<PacketPtr>(set_adv_params_packet) &&
107 !std::get<PacketPtr>(set_adv_params_packet)) {
108 bt_log(WARN,
109 "hci-le",
110 "cannot build HCI set params packet for %s",
111 bt_str(address));
112 result_callback(ToResult(HostError::kCanceled));
113 return;
114 }
115
116 hci_cmd_runner_->QueueCommand(
117 std::move(set_adv_params_packet),
118 fit::bind_member<&LowEnergyAdvertiser::OnSetAdvertisingParamsComplete>(
119 this));
120
121 // In order to support use cases where advertisers use the return parameters
122 // of the SetAdvertisingParams HCI command, we place the remaining advertising
123 // setup HCI commands in the result callback here. SequentialCommandRunner
124 // doesn't allow enqueuing commands within a callback (during a run).
125 hci_cmd_runner_->RunCommands([this,
126 address,
127 flags,
128 result_callback = std::move(result_callback),
129 connect_callback = std::move(connect_callback)](
130 hci::Result<> result) mutable {
131 if (bt_is_error(result,
132 WARN,
133 "hci-le",
134 "failed to start advertising for %s",
135 bt_str(address))) {
136 result_callback(result);
137 return;
138 }
139
140 bool success = StartAdvertisingInternalStep2(address,
141 flags,
142 std::move(connect_callback),
143 std::move(result_callback));
144 if (!success) {
145 result_callback(ToResult(HostError::kCanceled));
146 }
147 });
148 }
149
StartAdvertisingInternalStep2(const DeviceAddress & address,AdvFlags flags,ConnectionCallback connect_callback,hci::ResultFunction<> result_callback)150 bool LowEnergyAdvertiser::StartAdvertisingInternalStep2(
151 const DeviceAddress& address,
152 AdvFlags flags,
153 ConnectionCallback connect_callback,
154 hci::ResultFunction<> result_callback) {
155 using PacketPtr = std::unique_ptr<CommandPacket>;
156
157 CommandChannel::CommandPacketVariant set_adv_data_packet =
158 BuildSetAdvertisingData(address, staged_parameters_.data, flags);
159 if (std::holds_alternative<PacketPtr>(set_adv_data_packet) &&
160 !std::get<PacketPtr>(set_adv_data_packet)) {
161 bt_log(WARN,
162 "hci-le",
163 "cannot build HCI set advertising data packet for %s",
164 bt_str(address));
165 return false;
166 }
167
168 CommandChannel::CommandPacketVariant set_scan_rsp_packet =
169 BuildSetScanResponse(address, staged_parameters_.scan_rsp);
170 if (std::holds_alternative<PacketPtr>(set_scan_rsp_packet) &&
171 !std::get<PacketPtr>(set_scan_rsp_packet)) {
172 bt_log(WARN,
173 "hci-le",
174 "cannot build HCI set scan response data packet for %s",
175 bt_str(address));
176 return false;
177 }
178
179 EmbossCommandPacket enable_packet = BuildEnablePacket(
180 address, pw::bluetooth::emboss::GenericEnableParam::ENABLE);
181
182 hci_cmd_runner_->QueueCommand(std::move(set_adv_data_packet));
183 hci_cmd_runner_->QueueCommand(std::move(set_scan_rsp_packet));
184 hci_cmd_runner_->QueueCommand(enable_packet);
185
186 staged_parameters_.reset();
187 hci_cmd_runner_->RunCommands([this,
188 address,
189 result_callback = std::move(result_callback),
190 connect_callback = std::move(connect_callback)](
191 Result<> result) mutable {
192 if (bt_is_error(result,
193 WARN,
194 "hci-le",
195 "failed to start advertising for %s",
196 bt_str(address))) {
197 } else {
198 bt_log(INFO, "hci-le", "advertising enabled for %s", bt_str(address));
199 connection_callbacks_.emplace(address, std::move(connect_callback));
200 }
201
202 result_callback(result);
203 OnCurrentOperationComplete();
204 });
205
206 return true;
207 }
208
209 // We have StopAdvertising(address) so one would naturally think to implement
210 // StopAdvertising() by iterating through all addresses and calling
211 // StopAdvertising(address) on each iteration. However, such an implementation
212 // won't work. Each call to StopAdvertising(address) checks if the command
213 // runner is running, cancels any pending commands if it is, and then issues new
214 // ones. Called in quick succession, StopAdvertising(address) won't have a
215 // chance to finish its previous HCI commands before being cancelled. Instead,
216 // we must enqueue them all at once and then run them together.
StopAdvertising()217 void LowEnergyAdvertiser::StopAdvertising() {
218 if (!hci_cmd_runner_->IsReady()) {
219 hci_cmd_runner_->Cancel();
220 }
221
222 for (auto itr = connection_callbacks_.begin();
223 itr != connection_callbacks_.end();) {
224 const DeviceAddress& address = itr->first;
225
226 bool success = EnqueueStopAdvertisingCommands(address);
227 if (success) {
228 itr = connection_callbacks_.erase(itr);
229 } else {
230 bt_log(WARN, "hci-le", "cannot stop advertising for %s", bt_str(address));
231 itr++;
232 }
233 }
234
235 if (hci_cmd_runner_->HasQueuedCommands()) {
236 hci_cmd_runner_->RunCommands([this](hci::Result<> result) {
237 bt_log(INFO, "hci-le", "advertising stopped: %s", bt_str(result));
238 OnCurrentOperationComplete();
239 });
240 }
241 }
242
StopAdvertisingInternal(const DeviceAddress & address)243 void LowEnergyAdvertiser::StopAdvertisingInternal(
244 const DeviceAddress& address) {
245 if (!IsAdvertising(address)) {
246 return;
247 }
248
249 bool success = EnqueueStopAdvertisingCommands(address);
250 if (!success) {
251 bt_log(WARN, "hci-le", "cannot stop advertising for %s", bt_str(address));
252 return;
253 }
254
255 hci_cmd_runner_->RunCommands([this, address](Result<> result) {
256 bt_log(INFO,
257 "hci-le",
258 "advertising stopped for %s: %s",
259 bt_str(address),
260 bt_str(result));
261 OnCurrentOperationComplete();
262 });
263
264 connection_callbacks_.erase(address);
265 }
266
EnqueueStopAdvertisingCommands(const DeviceAddress & address)267 bool LowEnergyAdvertiser::EnqueueStopAdvertisingCommands(
268 const DeviceAddress& address) {
269 EmbossCommandPacket disable_packet = BuildEnablePacket(
270 address, pw::bluetooth::emboss::GenericEnableParam::DISABLE);
271
272 using PacketPtr = std::unique_ptr<hci::CommandPacket>;
273
274 hci::CommandChannel::CommandPacketVariant unset_scan_rsp_packet =
275 BuildUnsetScanResponse(address);
276 if (std::holds_alternative<PacketPtr>(unset_scan_rsp_packet) &&
277 !std::get<PacketPtr>(unset_scan_rsp_packet)) {
278 bt_log(WARN,
279 "hci-le",
280 "cannot build HCI unset scan rsp packet for %s",
281 bt_str(address));
282 return false;
283 }
284
285 hci::CommandChannel::CommandPacketVariant unset_adv_data_packet =
286 BuildUnsetAdvertisingData(address);
287 if (std::holds_alternative<PacketPtr>(unset_adv_data_packet) &&
288 !std::get<PacketPtr>(unset_adv_data_packet)) {
289 bt_log(WARN,
290 "hci-le",
291 "cannot build HCI unset advertising data packet for %s",
292 bt_str(address));
293 return false;
294 }
295
296 EmbossCommandPacket remove_packet = BuildRemoveAdvertisingSet(address);
297
298 hci_cmd_runner_->QueueCommand(disable_packet);
299 hci_cmd_runner_->QueueCommand(std::move(unset_scan_rsp_packet));
300 hci_cmd_runner_->QueueCommand(std::move(unset_adv_data_packet));
301 hci_cmd_runner_->QueueCommand(remove_packet);
302
303 return true;
304 }
305
CompleteIncomingConnection(hci_spec::ConnectionHandle handle,pw::bluetooth::emboss::ConnectionRole role,const DeviceAddress & local_address,const DeviceAddress & peer_address,const hci_spec::LEConnectionParameters & conn_params)306 void LowEnergyAdvertiser::CompleteIncomingConnection(
307 hci_spec::ConnectionHandle handle,
308 pw::bluetooth::emboss::ConnectionRole role,
309 const DeviceAddress& local_address,
310 const DeviceAddress& peer_address,
311 const hci_spec::LEConnectionParameters& conn_params) {
312 // Immediately construct a Connection object. If this object goes out of scope
313 // following the error checks below, it will send the a command to disconnect
314 // the link.
315 std::unique_ptr<LowEnergyConnection> link =
316 std::make_unique<LowEnergyConnection>(
317 handle, local_address, peer_address, conn_params, role, hci());
318
319 if (!IsAdvertising(local_address)) {
320 bt_log(DEBUG,
321 "hci-le",
322 "connection received without advertising address (role: %d, local "
323 "address: %s, peer "
324 "address: %s, connection parameters: %s)",
325 static_cast<uint8_t>(role),
326 bt_str(local_address),
327 bt_str(peer_address),
328 bt_str(conn_params));
329 return;
330 }
331
332 if (!connection_callbacks_[local_address]) {
333 bt_log(WARN,
334 "hci-le",
335 "connection received when not connectable (role: %d, local address: "
336 "%s, peer "
337 "address: %s, connection parameters: %s)",
338 static_cast<uint8_t>(role),
339 bt_str(local_address),
340 bt_str(peer_address),
341 bt_str(conn_params));
342 return;
343 }
344
345 ConnectionCallback connect_callback =
346 std::move(connection_callbacks_[local_address]);
347 StopAdvertising(local_address);
348 connect_callback(std::move(link));
349 }
350
351 } // namespace bt::hci
352