xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/hci/low_energy_scanner.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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_scanner.h"
16 
17 namespace bt::hci {
18 
ScanStateToString(LowEnergyScanner::State state)19 static std::string ScanStateToString(LowEnergyScanner::State state) {
20   switch (state) {
21     case LowEnergyScanner::State::kIdle:
22       return "(idle)";
23     case LowEnergyScanner::State::kStopping:
24       return "(stopping)";
25     case LowEnergyScanner::State::kInitiating:
26       return "(initiating)";
27     case LowEnergyScanner::State::kActiveScanning:
28       return "(active scanning)";
29     case LowEnergyScanner::State::kPassiveScanning:
30       return "(passive scanning)";
31     default:
32       break;
33   }
34 
35   BT_PANIC("invalid scanner state: %u", static_cast<unsigned int>(state));
36   return "(unknown)";
37 }
38 
AppendData(const ByteBuffer & data)39 void LowEnergyScanResult::AppendData(const ByteBuffer& data) {
40   size_t bytes_needed = data_size_ + data.size();
41   if (buffer_.size() < bytes_needed) {
42     buffer_.expand(bytes_needed);
43   }
44 
45   buffer_.Write(data, data_size_);
46   data_size_ += data.size();
47 }
48 
operator =(const LowEnergyScanResult & other)49 LowEnergyScanResult& LowEnergyScanResult::operator=(
50     const LowEnergyScanResult& other) {
51   address_ = other.address_;
52   resolved_ = other.resolved_;
53   connectable_ = other.connectable_;
54   rssi_ = other.rssi_;
55   data_size_ = other.data_size_;
56 
57   if (buffer_.size() < other.buffer_.size()) {
58     buffer_.expand(other.buffer_.size());
59   }
60   other.buffer_.Copy(&buffer_);
61 
62   return *this;
63 }
64 
PendingScanResult(LowEnergyScanResult && result,pw::async::Dispatcher & dispatcher,pw::chrono::SystemClock::duration timeout,fit::closure timeout_handler)65 LowEnergyScanner::PendingScanResult::PendingScanResult(
66     LowEnergyScanResult&& result,
67     pw::async::Dispatcher& dispatcher,
68     pw::chrono::SystemClock::duration timeout,
69     fit::closure timeout_handler)
70     : result_(result), timeout_(timeout), timeout_task_(dispatcher) {
71   timeout_task_.set_function(
72       [timeout_handler_cb = std::move(timeout_handler)](
73           pw::async::Context /*ctx*/, pw::Status status) {
74         if (status.ok()) {
75           timeout_handler_cb();
76         }
77       });
78   StartTimer();
79 }
80 
LowEnergyScanner(LocalAddressDelegate * local_addr_delegate,hci::Transport::WeakPtr hci,pw::async::Dispatcher & pw_dispatcher)81 LowEnergyScanner::LowEnergyScanner(LocalAddressDelegate* local_addr_delegate,
82                                    hci::Transport::WeakPtr hci,
83                                    pw::async::Dispatcher& pw_dispatcher)
84     : pw_dispatcher_(pw_dispatcher),
85       scan_timeout_task_(pw_dispatcher_),
86       local_addr_delegate_(local_addr_delegate),
87       hci_(std::move(hci)) {
88   PW_DCHECK(local_addr_delegate_);
89   PW_DCHECK(hci_.is_alive());
90   hci_cmd_runner_ = std::make_unique<SequentialCommandRunner>(
91       hci_->command_channel()->AsWeakPtr());
92 
93   scan_timeout_task_.set_function(
94       [this](pw::async::Context /*ctx*/, pw::Status status) {
95         if (status.ok() && IsScanning()) {
96           StopScanInternal(false);
97         }
98       });
99 }
100 
AddPendingResult(LowEnergyScanResult && scan_result)101 void LowEnergyScanner::AddPendingResult(LowEnergyScanResult&& scan_result) {
102   auto pending = std::make_unique<PendingScanResult>(
103       std::move(scan_result),
104       pw_dispatcher_,
105       scan_response_timeout_,
106       [this, address = scan_result.address()] {
107         std::unique_ptr<PendingScanResult> result =
108             RemovePendingResult(address);
109         delegate()->OnPeerFound(result->result());
110       });
111   pending_results_.emplace(scan_result.address(), std::move(pending));
112 }
113 
114 std::unique_ptr<LowEnergyScanner::PendingScanResult>
RemovePendingResult(const DeviceAddress & address)115 LowEnergyScanner::RemovePendingResult(const DeviceAddress& address) {
116   auto node = pending_results_.extract(address);
117   if (node.empty()) {
118     return nullptr;
119   }
120 
121   node.mapped()->CancelTimeout();
122   return std::move(node.mapped());
123 }
124 
StartScan(const ScanOptions & options,ScanStatusCallback callback)125 bool LowEnergyScanner::StartScan(const ScanOptions& options,
126                                  ScanStatusCallback callback) {
127   PW_CHECK(callback);
128   PW_CHECK(options.window < options.interval);
129 
130   if (state_ != State::kIdle) {
131     bt_log(ERROR,
132            "hci-le",
133            "cannot start scan while in state: %s",
134            ScanStateToString(state_).c_str());
135     return false;
136   }
137 
138   state_ = State::kInitiating;
139   scan_response_timeout_ = options.scan_response_timeout;
140   scan_cb_ = std::move(callback);
141 
142   // Obtain the local address type.
143   local_addr_delegate_->EnsureLocalAddress(
144       /*address_type=*/std::nullopt,
145       [this, options, cb = std::move(callback)](
146           fit::result<HostError, const DeviceAddress> result) mutable {
147         if (result.is_error()) {
148           cb(ScanStatus::kFailed);
149           return;
150         }
151         StartScanInternal(result.value(), options, std::move(cb));
152       });
153 
154   return true;
155 }
156 
StartScanInternal(const DeviceAddress & local_address,const ScanOptions & options,ScanStatusCallback)157 void LowEnergyScanner::StartScanInternal(const DeviceAddress& local_address,
158                                          const ScanOptions& options,
159                                          ScanStatusCallback) {
160   // Check if the scan request was canceled by StopScan() while we were waiting
161   // for the local address.
162   if (state_ != State::kInitiating) {
163     bt_log(DEBUG,
164            "hci-le",
165            "scan request was canceled while obtaining local address");
166     return;
167   }
168 
169   bt_log(DEBUG,
170          "hci-le",
171          "requesting scan (%s, address: %s, interval: %#.4x, window: %#.4x)",
172          (options.active ? "active" : "passive"),
173          local_address.ToString().c_str(),
174          options.interval,
175          options.window);
176 
177   CommandPacket scan_params_command =
178       BuildSetScanParametersPacket(local_address, options);
179   CommandPacket scan_enable_command = BuildEnablePacket(
180       options, pw::bluetooth::emboss::GenericEnableParam::ENABLE);
181 
182   hci_cmd_runner_->QueueCommand(std::move(scan_params_command));
183   hci_cmd_runner_->QueueCommand(std::move(scan_enable_command));
184   hci_cmd_runner_->RunCommands([this,
185                                 active = options.active,
186                                 period = options.period](Result<> status) {
187     PW_DCHECK(scan_cb_);
188     PW_DCHECK(state_ == State::kInitiating);
189 
190     if (status.is_error()) {
191       if (status == ToResult(HostError::kCanceled)) {
192         bt_log(DEBUG, "hci-le", "scan canceled");
193         return;
194       }
195 
196       bt_log(ERROR, "hci-le", "failed to start scan: %s", bt_str(status));
197       state_ = State::kIdle;
198       scan_cb_(ScanStatus::kFailed);
199       return;
200     }
201 
202     // Schedule the timeout.
203     if (period != kPeriodInfinite) {
204       scan_timeout_task_.PostAfter(period);
205     }
206 
207     if (active) {
208       state_ = State::kActiveScanning;
209       scan_cb_(ScanStatus::kActive);
210     } else {
211       state_ = State::kPassiveScanning;
212       scan_cb_(ScanStatus::kPassive);
213     }
214   });
215 }
216 
StopScan()217 bool LowEnergyScanner::StopScan() {
218   if (state_ == State::kStopping || state_ == State::kIdle) {
219     bt_log(DEBUG,
220            "hci-le",
221            "cannot stop scan while in state: %s",
222            ScanStateToString(state_).c_str());
223     return false;
224   }
225 
226   // Scan is either being initiated or already running. Cancel any in-flight HCI
227   // command sequence.
228   if (!hci_cmd_runner_->IsReady()) {
229     hci_cmd_runner_->Cancel();
230   }
231 
232   // We'll tell the controller to stop scanning even if it is not (this is OK
233   // because the command will have no effect; see Core Spec v5.0, Vol 2, Part E,
234   // Section 7.8.11, paragraph 4).
235   StopScanInternal(true);
236   return true;
237 }
238 
StopScanInternal(bool stopped_by_user)239 void LowEnergyScanner::StopScanInternal(bool stopped_by_user) {
240   PW_DCHECK(scan_cb_);
241 
242   scan_timeout_task_.Cancel();
243   state_ = State::kStopping;
244 
245   // Notify any pending scan results unless the scan was terminated by the user.
246   if (!stopped_by_user) {
247     for (auto& result : pending_results_) {
248       const std::unique_ptr<PendingScanResult>& pending = result.second;
249       delegate_->OnPeerFound(pending->result());
250     }
251   }
252 
253   // Either way clear all results from the previous scan period.
254   pending_results_.clear();
255 
256   PW_DCHECK(hci_cmd_runner_->IsReady());
257 
258   // Tell the controller to stop scanning.
259   ScanOptions options;
260   CommandPacket command = BuildEnablePacket(
261       options, pw::bluetooth::emboss::GenericEnableParam::DISABLE);
262 
263   hci_cmd_runner_->QueueCommand(std::move(command));
264   hci_cmd_runner_->RunCommands([this, stopped_by_user](Result<> status) {
265     PW_DCHECK(scan_cb_);
266     PW_DCHECK(state_ == State::kStopping);
267     state_ = State::kIdle;
268 
269     // Something went wrong but there isn't really a meaningful way to recover,
270     // so we just fall through and notify the caller with ScanStatus::kFailed
271     // instead.
272     bt_is_error(
273         status, WARN, "hci-le", "failed to stop scan: %s", bt_str(status));
274 
275     ScanStatus scan_status = ScanStatus::kFailed;
276     if (status.is_error()) {
277       scan_status = ScanStatus::kFailed;
278     } else if (stopped_by_user) {
279       scan_status = ScanStatus::kStopped;
280     } else {
281       scan_status = ScanStatus::kComplete;
282     }
283 
284     scan_cb_(scan_status);
285   });
286 }
287 }  // namespace bt::hci
288