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