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