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