xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/hci/legacy_low_energy_scanner.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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/legacy_low_energy_scanner.h"
16 
17 #include <pw_preprocessor/compiler.h>
18 
19 #include "pw_bluetooth_sapphire/internal/host/hci/advertising_report_parser.h"
20 
21 namespace bt::hci {
22 namespace pwemb = pw::bluetooth::emboss;
23 
LegacyLowEnergyScanner(LocalAddressDelegate * local_addr_delegate,Transport::WeakPtr transport,pw::async::Dispatcher & pw_dispatcher)24 LegacyLowEnergyScanner::LegacyLowEnergyScanner(
25     LocalAddressDelegate* local_addr_delegate,
26     Transport::WeakPtr transport,
27     pw::async::Dispatcher& pw_dispatcher)
28     : LowEnergyScanner(
29           local_addr_delegate, std::move(transport), pw_dispatcher),
30       weak_self_(this) {
31   auto self = weak_self_.GetWeakPtr();
32   event_handler_id_ = hci()->command_channel()->AddLEMetaEventHandler(
33       hci_spec::kLEAdvertisingReportSubeventCode,
34       [self](const EventPacket& event) {
35         if (!self.is_alive()) {
36           return hci::CommandChannel::EventCallbackResult::kRemove;
37         }
38 
39         self->OnAdvertisingReportEvent(event);
40         return hci::CommandChannel::EventCallbackResult::kContinue;
41       });
42 }
43 
~LegacyLowEnergyScanner()44 LegacyLowEnergyScanner::~LegacyLowEnergyScanner() {
45   // This object is probably being destroyed because the stack is shutting down,
46   // in which case the HCI layer may have already been destroyed.
47   if (!hci().is_alive() || !hci()->command_channel()) {
48     return;
49   }
50 
51   hci()->command_channel()->RemoveEventHandler(event_handler_id_);
52   StopScan();
53 }
54 
StartScan(const ScanOptions & options,ScanStatusCallback callback)55 bool LegacyLowEnergyScanner::StartScan(const ScanOptions& options,
56                                        ScanStatusCallback callback) {
57   PW_CHECK(options.interval >= hci_spec::kLEScanIntervalMin);
58   PW_CHECK(options.interval <= hci_spec::kLEScanIntervalMax);
59   PW_CHECK(options.window >= hci_spec::kLEScanIntervalMin);
60   PW_CHECK(options.window <= hci_spec::kLEScanIntervalMax);
61   return LowEnergyScanner::StartScan(options, std::move(callback));
62 }
63 
BuildSetScanParametersPacket(const DeviceAddress & local_address,const ScanOptions & options)64 CommandPacket LegacyLowEnergyScanner::BuildSetScanParametersPacket(
65     const DeviceAddress& local_address, const ScanOptions& options) {
66   auto packet = hci::CommandPacket::New<
67       pw::bluetooth::emboss::LESetScanParametersCommandWriter>(
68       hci_spec::kLESetScanParameters);
69   auto params = packet.view_t();
70 
71   params.le_scan_type().Write(pw::bluetooth::emboss::LEScanType::PASSIVE);
72   if (options.active) {
73     params.le_scan_type().Write(pw::bluetooth::emboss::LEScanType::ACTIVE);
74   }
75 
76   params.le_scan_interval().Write(options.interval);
77   params.le_scan_window().Write(options.window);
78   params.scanning_filter_policy().Write(options.filter_policy);
79 
80   if (local_address.type() == DeviceAddress::Type::kLERandom) {
81     params.own_address_type().Write(
82         pw::bluetooth::emboss::LEOwnAddressType::RANDOM);
83   } else {
84     params.own_address_type().Write(
85         pw::bluetooth::emboss::LEOwnAddressType::PUBLIC);
86   }
87 
88   return packet;
89 }
90 
BuildEnablePacket(const ScanOptions & options,pw::bluetooth::emboss::GenericEnableParam enable)91 CommandPacket LegacyLowEnergyScanner::BuildEnablePacket(
92     const ScanOptions& options,
93     pw::bluetooth::emboss::GenericEnableParam enable) {
94   auto packet =
95       CommandPacket::New<pw::bluetooth::emboss::LESetScanEnableCommandWriter>(
96           hci_spec::kLESetScanEnable);
97   auto params = packet.view_t();
98   params.le_scan_enable().Write(enable);
99 
100   params.filter_duplicates().Write(
101       pw::bluetooth::emboss::GenericEnableParam::DISABLE);
102   if (options.filter_duplicates) {
103     params.filter_duplicates().Write(
104         pw::bluetooth::emboss::GenericEnableParam::ENABLE);
105   }
106 
107   return packet;
108 }
109 
HandleScanResponse(const DeviceAddress & address,bool resolved,int8_t rssi,const ByteBuffer & data)110 void LegacyLowEnergyScanner::HandleScanResponse(const DeviceAddress& address,
111                                                 bool resolved,
112                                                 int8_t rssi,
113                                                 const ByteBuffer& data) {
114   std::unique_ptr<PendingScanResult> pending = RemovePendingResult(address);
115   if (!pending) {
116     bt_log(DEBUG, "hci-le", "dropping unmatched scan response");
117     return;
118   }
119 
120   PW_DCHECK(address == pending->result().address());
121   pending->result().AppendData(data);
122   pending->result().set_resolved(resolved);
123   pending->result().set_rssi(rssi);
124 
125   delegate()->OnPeerFound(pending->result());
126 
127   // The callback handler may stop the scan, destroying objects within the
128   // LowEnergyScanner. Avoid doing anything more to prevent use after free
129   // bugs.
130 }
131 
132 // Extract all advertising reports from a given HCI LE Advertising Report event
133 std::vector<pw::bluetooth::emboss::LEAdvertisingReportDataView>
ParseAdvertisingReports(const EventPacket & event)134 LegacyLowEnergyScanner::ParseAdvertisingReports(const EventPacket& event) {
135   PW_DCHECK(event.event_code() == hci_spec::kLEMetaEventCode);
136   PW_DCHECK(event.view<pw::bluetooth::emboss::LEMetaEventView>()
137                 .subevent_code()
138                 .Read() == hci_spec::kLEAdvertisingReportSubeventCode);
139 
140   auto params =
141       event.view<pw::bluetooth::emboss::LEAdvertisingReportSubeventView>();
142   uint8_t num_reports = params.num_reports().Read();
143   std::vector<pw::bluetooth::emboss::LEAdvertisingReportDataView> reports;
144   reports.reserve(num_reports);
145 
146   size_t bytes_read = 0;
147   while (bytes_read < params.reports().BackingStorage().SizeInBytes()) {
148     size_t min_size =
149         pw::bluetooth::emboss::LEAdvertisingReportData::MinSizeInBytes();
150     auto report_prefix = pw::bluetooth::emboss::MakeLEAdvertisingReportDataView(
151         params.reports().BackingStorage().begin() + bytes_read, min_size);
152 
153     uint8_t data_length = report_prefix.data_length().Read();
154     size_t actual_size = min_size + data_length;
155 
156     size_t bytes_left =
157         params.reports().BackingStorage().SizeInBytes() - bytes_read;
158     if (actual_size > bytes_left) {
159       bt_log(WARN,
160              "hci-le",
161              "parsing advertising reports, next report size %zu bytes, but "
162              "only %zu bytes left",
163              actual_size,
164              bytes_left);
165       break;
166     }
167 
168     auto report = pw::bluetooth::emboss::MakeLEAdvertisingReportDataView(
169         params.reports().BackingStorage().begin() + bytes_read, actual_size);
170     reports.push_back(report);
171 
172     bytes_read += actual_size;
173   }
174 
175   return reports;
176 }
177 
178 // Returns a DeviceAddress and whether or not that DeviceAddress has been
179 // resolved
BuildDeviceAddress(pw::bluetooth::emboss::LEAddressType report_type,pw::bluetooth::emboss::BdAddrView address_view)180 static std::tuple<DeviceAddress, bool> BuildDeviceAddress(
181     pw::bluetooth::emboss::LEAddressType report_type,
182     pw::bluetooth::emboss::BdAddrView address_view) {
183   std::optional<DeviceAddress::Type> address_type =
184       DeviceAddress::LeAddrToDeviceAddr(report_type);
185   PW_DCHECK(address_type);
186 
187   bool resolved = false;
188   switch (report_type) {
189     case pw::bluetooth::emboss::LEAddressType::PUBLIC_IDENTITY:
190     case pw::bluetooth::emboss::LEAddressType::RANDOM_IDENTITY:
191       resolved = true;
192       break;
193     case pw::bluetooth::emboss::LEAddressType::PUBLIC:
194     case pw::bluetooth::emboss::LEAddressType::RANDOM:
195       resolved = false;
196       break;
197   }
198 
199   DeviceAddress address =
200       DeviceAddress(*address_type, DeviceAddressBytes(address_view));
201   return std::make_tuple(address, resolved);
202 }
203 
OnAdvertisingReportEvent(const EventPacket & event)204 void LegacyLowEnergyScanner::OnAdvertisingReportEvent(
205     const EventPacket& event) {
206   if (!IsScanning()) {
207     return;
208   }
209 
210   std::vector<pw::bluetooth::emboss::LEAdvertisingReportDataView> reports =
211       ParseAdvertisingReports(event);
212 
213   for (pw::bluetooth::emboss::LEAdvertisingReportDataView report : reports) {
214     if (report.data_length().Read() > hci_spec::kMaxLEAdvertisingDataLength) {
215       bt_log(WARN, "hci-le", "advertising data too long! Ignoring");
216       continue;
217     }
218 
219     const auto& [address, resolved] =
220         BuildDeviceAddress(report.address_type().Read(), report.address());
221 
222     bool needs_scan_rsp = false;
223     bool connectable = false;
224     bool directed = false;
225 
226     PW_MODIFY_DIAGNOSTICS_PUSH();
227     PW_MODIFY_DIAGNOSTIC(ignored, "-Wswitch-enum");
228     switch (report.event_type().Read()) {
229       case pwemb::LEAdvertisingEventType::CONNECTABLE_DIRECTED: {
230         directed = true;
231         break;
232       }
233       case pwemb::LEAdvertisingEventType::
234           CONNECTABLE_AND_SCANNABLE_UNDIRECTED: {
235         connectable = true;
236         [[fallthrough]];
237       }
238       case pwemb::LEAdvertisingEventType::SCANNABLE_UNDIRECTED: {
239         if (IsActiveScanning()) {
240           needs_scan_rsp = true;
241         }
242         break;
243       }
244       case pwemb::LEAdvertisingEventType::SCAN_RESPONSE: {
245         if (IsActiveScanning()) {
246           BufferView data = BufferView(report.data().BackingStorage().data(),
247                                        report.data_length().Read());
248           HandleScanResponse(address, resolved, report.rssi().Read(), data);
249         }
250         continue;
251       }
252       default: {
253         break;
254       }
255     }
256     PW_MODIFY_DIAGNOSTICS_POP();
257 
258     LowEnergyScanResult result(address, resolved, connectable);
259     result.AppendData(BufferView(report.data().BackingStorage().data(),
260                                  report.data_length().Read()));
261     result.set_rssi(report.rssi().Read());
262 
263     if (directed) {
264       delegate()->OnDirectedAdvertisement(result);
265       continue;
266     }
267 
268     if (!needs_scan_rsp) {
269       delegate()->OnPeerFound(result);
270       continue;
271     }
272 
273     AddPendingResult(std::move(result));
274   }
275 }
276 
277 }  // namespace bt::hci
278