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