xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/hci/extended_low_energy_scanner.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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