1 // Copyright 2024 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/extended_low_energy_scanner.h"
16
17 #include <pw_bluetooth/hci_common.emb.h>
18
19 namespace bt::hci {
20
21 using pw::bluetooth::emboss::BdAddrView;
22 using pw::bluetooth::emboss::GenericEnableParam;
23 using pw::bluetooth::emboss::LEAdvertisingDataStatus;
24 using pw::bluetooth::emboss::LEExtendedAddressType;
25 using pw::bluetooth::emboss::LEExtendedAdvertisingReportDataView;
26 using pw::bluetooth::emboss::LEExtendedAdvertisingReportSubeventView;
27 using pw::bluetooth::emboss::LEExtendedDuplicateFilteringOption;
28 using pw::bluetooth::emboss::LEMetaEventView;
29 using pw::bluetooth::emboss::LEScanType;
30 using pw::bluetooth::emboss::LESetExtendedScanEnableCommandWriter;
31 using pw::bluetooth::emboss::LESetExtendedScanParametersCommandWriter;
32 using pw::bluetooth::emboss::MakeLEExtendedAdvertisingReportDataView;
33
ExtendedLowEnergyScanner(LocalAddressDelegate * local_addr_delegate,Transport::WeakPtr transport,pw::async::Dispatcher & pw_dispatcher)34 ExtendedLowEnergyScanner::ExtendedLowEnergyScanner(
35 LocalAddressDelegate* local_addr_delegate,
36 Transport::WeakPtr transport,
37 pw::async::Dispatcher& pw_dispatcher)
38 : LowEnergyScanner(
39 local_addr_delegate, std::move(transport), pw_dispatcher) {
40 event_handler_id_ = hci()->command_channel()->AddLEMetaEventHandler(
41 hci_spec::kLEExtendedAdvertisingReportSubeventCode,
42 [this](const EventPacket& event) {
43 OnExtendedAdvertisingReportEvent(event);
44 return hci::CommandChannel::EventCallbackResult::kContinue;
45 });
46 }
~ExtendedLowEnergyScanner()47 ExtendedLowEnergyScanner::~ExtendedLowEnergyScanner() {
48 // This object is probably being destroyed because the stack is shutting down,
49 // in which case the HCI layer may have already been destroyed.
50 if (!hci().is_alive() || !hci()->command_channel()) {
51 return;
52 }
53
54 hci()->command_channel()->RemoveEventHandler(event_handler_id_);
55 StopScan();
56 }
57
StartScan(const ScanOptions & options,ScanStatusCallback callback)58 bool ExtendedLowEnergyScanner::StartScan(const ScanOptions& options,
59 ScanStatusCallback callback) {
60 PW_CHECK(options.interval >= hci_spec::kLEExtendedScanIntervalMin);
61 PW_CHECK(options.interval <= hci_spec::kLEExtendedScanIntervalMax);
62 PW_CHECK(options.window >= hci_spec::kLEExtendedScanIntervalMin);
63 PW_CHECK(options.window <= hci_spec::kLEExtendedScanIntervalMax);
64
65 return LowEnergyScanner::StartScan(options, std::move(callback));
66 }
67
BuildSetScanParametersPacket(const DeviceAddress & local_address,const ScanOptions & options)68 CommandPacket ExtendedLowEnergyScanner::BuildSetScanParametersPacket(
69 const DeviceAddress& local_address, const ScanOptions& options) {
70 // LESetExtendedScanParametersCommand contains a variable amount of data,
71 // depending on how many bits are set within the scanning_phys parameter. As
72 // such, we must first calculate the size of the variable data before
73 // allocating the packet.
74
75 // we scan on the LE 1M PHY and the LE Coded PHY
76 constexpr size_t num_phys = 2;
77 constexpr size_t fixed_size = pw::bluetooth::emboss::
78 LESetExtendedScanParametersCommand::MinSizeInBytes();
79 constexpr size_t variable_size = pw::bluetooth::emboss::
80 LESetExtendedScanParametersData::IntrinsicSizeInBytes();
81 constexpr size_t packet_size = fixed_size + (num_phys * variable_size);
82
83 auto packet =
84 hci::CommandPacket::New<LESetExtendedScanParametersCommandWriter>(
85 hci_spec::kLESetExtendedScanParameters, packet_size);
86 auto params = packet.view_t();
87
88 params.scanning_filter_policy().Write(options.filter_policy);
89 params.own_address_type().Write(
90 DeviceAddress::DeviceAddrToLEOwnAddr(local_address.type()));
91
92 // For maximum compatibility, Sapphire scans on all available PHYs.
93 params.scanning_phys().le_1m().Write(true);
94 params.scanning_phys().le_coded().Write(true);
95
96 for (size_t i = 0; i < num_phys; i++) {
97 params.data()[i].scan_type().Write(LEScanType::PASSIVE);
98 if (options.active) {
99 params.data()[i].scan_type().Write(LEScanType::ACTIVE);
100 }
101
102 params.data()[i].scan_interval().Write(options.interval);
103 params.data()[i].scan_window().Write(options.window);
104 }
105
106 return packet;
107 }
108
BuildEnablePacket(const ScanOptions & options,GenericEnableParam enable)109 CommandPacket ExtendedLowEnergyScanner::BuildEnablePacket(
110 const ScanOptions& options, GenericEnableParam enable) {
111 auto packet = CommandPacket::New<LESetExtendedScanEnableCommandWriter>(
112 hci_spec::kLESetExtendedScanEnable);
113 auto params = packet.view_t();
114
115 params.scanning_enabled().Write(enable);
116 params.filter_duplicates().Write(
117 LEExtendedDuplicateFilteringOption::DISABLED);
118 if (options.filter_duplicates) {
119 params.filter_duplicates().Write(
120 LEExtendedDuplicateFilteringOption::ENABLED);
121 }
122
123 // The scan duration and period parameters control how long the scan
124 // continues. Setting these values to hci_spec::kNoScanningDuration and
125 // hci_spec::kNoScanningPeriod, respectively, means that scanning continues
126 // indefinitely until the client requests it to stop.
127 params.duration().Write(hci_spec::kNoScanningDuration);
128 params.period().Write(hci_spec::kNoScanningPeriod);
129
130 return packet;
131 }
132
133 // Extract all advertising reports from a given HCI LE Extended Advertising
134 // Report event
135 std::vector<LEExtendedAdvertisingReportDataView>
ParseAdvertisingReports(const EventPacket & event)136 ExtendedLowEnergyScanner::ParseAdvertisingReports(const EventPacket& event) {
137 PW_DCHECK(event.event_code() == hci_spec::kLEMetaEventCode);
138 PW_DCHECK(event.view<LEMetaEventView>().subevent_code().Read() ==
139 hci_spec::kLEExtendedAdvertisingReportSubeventCode);
140 size_t reports_size =
141 event.size() -
142 pw::bluetooth::emboss::LEExtendedAdvertisingReportSubeventView::
143 MinSizeInBytes()
144 .Read();
145 auto params = event.view<LEExtendedAdvertisingReportSubeventView>(
146 static_cast<int32_t>(reports_size));
147
148 uint8_t num_reports = params.num_reports().Read();
149 std::vector<LEExtendedAdvertisingReportDataView> reports;
150 reports.reserve(num_reports);
151
152 size_t bytes_read = 0;
153 while (bytes_read < params.reports().BackingStorage().SizeInBytes()) {
154 size_t min_size = pw::bluetooth::emboss::LEExtendedAdvertisingReportData::
155 MinSizeInBytes();
156 auto report_prefix = MakeLEExtendedAdvertisingReportDataView(
157 params.reports().BackingStorage().begin() + bytes_read, min_size);
158
159 uint8_t data_length = report_prefix.data_length().Read();
160 size_t actual_size = min_size + data_length;
161
162 size_t bytes_left =
163 params.reports().BackingStorage().SizeInBytes() - bytes_read;
164 if (actual_size > bytes_left) {
165 bt_log(WARN,
166 "hci-le",
167 "parsing advertising reports, next report size %zu bytes, but "
168 "only %zu bytes left",
169 actual_size,
170 bytes_left);
171 break;
172 }
173
174 auto report = MakeLEExtendedAdvertisingReportDataView(
175 params.reports().BackingStorage().begin() + bytes_read, actual_size);
176 reports.push_back(report);
177
178 bytes_read += actual_size;
179 }
180
181 return reports;
182 }
183
BuildDeviceAddress(LEExtendedAddressType report_type,BdAddrView address_view)184 static std::tuple<DeviceAddress, bool> BuildDeviceAddress(
185 LEExtendedAddressType report_type, BdAddrView address_view) {
186 std::optional<DeviceAddress::Type> address_type =
187 DeviceAddress::LeAddrToDeviceAddr(report_type);
188 PW_DCHECK(address_type);
189
190 bool resolved = false;
191 switch (report_type) {
192 case LEExtendedAddressType::PUBLIC_IDENTITY:
193 case LEExtendedAddressType::RANDOM_IDENTITY:
194 resolved = true;
195 break;
196 case LEExtendedAddressType::PUBLIC:
197 case LEExtendedAddressType::RANDOM:
198 case LEExtendedAddressType::ANONYMOUS:
199 default:
200 resolved = false;
201 break;
202 }
203
204 DeviceAddress address =
205 DeviceAddress(*address_type, DeviceAddressBytes(address_view));
206 return std::make_tuple(address, resolved);
207 }
208
OnExtendedAdvertisingReportEvent(const EventPacket & event)209 void ExtendedLowEnergyScanner::OnExtendedAdvertisingReportEvent(
210 const EventPacket& event) {
211 if (!IsScanning()) {
212 return;
213 }
214
215 std::vector<LEExtendedAdvertisingReportDataView> reports =
216 ParseAdvertisingReports(event);
217 for (LEExtendedAdvertisingReportDataView report : reports) {
218 const auto& [address, resolved] =
219 BuildDeviceAddress(report.address_type().Read(), report.address());
220
221 bool is_directed = report.event_type().directed().Read();
222 bool is_connectable = report.event_type().connectable().Read();
223 bool is_scannable = report.event_type().scannable().Read();
224 bool is_scan_response = report.event_type().scan_response().Read();
225
226 // scan responses without a pending result from an advertising data result
227 // mean they are too late and the timer waiting for them has expired. The
228 // delegate has already been notified and we unfortunately need to drop this
229 // result.
230 if (is_scan_response && !HasPendingResult(address)) {
231 bt_log(DEBUG, "hci-le", "dropping unmatched scan response");
232 return;
233 }
234
235 int8_t rssi = report.rssi().Read();
236 BufferView data(report.data().BackingStorage().begin(),
237 report.data_length().Read());
238
239 LowEnergyScanResult result;
240 std::unique_ptr<PendingScanResult> pending = RemovePendingResult(address);
241 if (pending) {
242 result = pending->result();
243 } else {
244 result = LowEnergyScanResult(address, resolved, is_connectable);
245 }
246
247 result.set_resolved(resolved);
248 result.set_rssi(rssi);
249 result.set_tx_power(report.tx_power().Read());
250 result.set_advertising_sid(report.advertising_sid().Read());
251
252 // If the next set of data exceeds the maximum allowed in an extended
253 // advertising data payload, take as much as we can and report it back.
254 size_t size_after_add = result.data().size() + data.size();
255 if (size_after_add > hci_spec::kMaxLEExtendedAdvertisingDataLength) {
256 bt_log(WARN,
257 "hci-le",
258 "advertising data for (%s) too long (actual: %zu, max: %zu)! "
259 "Ignoring rest.",
260 bt_str(address),
261 size_after_add,
262 hci_spec::kMaxLEExtendedAdvertisingDataLength);
263
264 size_t bytes_allowed =
265 hci_spec::kMaxLEExtendedAdvertisingDataLength - result.data().size();
266 BufferView truncated_data =
267 BufferView(report.data().BackingStorage().begin(), bytes_allowed);
268 result.AppendData(truncated_data);
269
270 delegate()->OnPeerFound(result);
271 continue;
272 }
273
274 result.AppendData(data);
275
276 LEAdvertisingDataStatus data_status =
277 report.event_type().data_status().Read();
278 if (data_status == LEAdvertisingDataStatus::INCOMPLETE) {
279 // There is more data coming in another extended advertising PDU so we
280 // just wait for it
281 AddPendingResult(std::move(result));
282 continue;
283 }
284
285 // Incoming data was truncated and we won't receive the rest. Nothing we can
286 // do about that so just notify the delegate with the data we currently
287 // have.
288 if (data_status == LEAdvertisingDataStatus::INCOMPLETE_TRUNCATED) {
289 bt_log(WARN,
290 "hci-le",
291 "data for %s truncated to %zu bytes",
292 bt_str(address),
293 result.data().size());
294 }
295
296 if (is_directed) {
297 delegate()->OnDirectedAdvertisement(result);
298 continue;
299 }
300
301 if (IsActiveScanning() && is_scan_response) {
302 delegate()->OnPeerFound(result);
303 continue;
304 }
305
306 if (IsActiveScanning() && is_scannable) {
307 // We need to wait for a scan response. Scan responses have the
308 // scannable bit set so it's important that this if statement comes
309 // after the one checking for a scan response.
310 AddPendingResult(std::move(result));
311 continue;
312 }
313
314 delegate()->OnPeerFound(result);
315 }
316 }
317
318 } // namespace bt::hci
319