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