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