• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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