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