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