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 <unordered_map>
18
19 #include "pw_bluetooth_sapphire/internal/host/hci/fake_local_address_delegate.h"
20 #include "pw_bluetooth_sapphire/internal/host/testing/controller_test.h"
21 #include "pw_bluetooth_sapphire/internal/host/testing/fake_controller.h"
22
23 namespace bt::hci {
24
25 using bt::testing::FakeController;
26 using TestingBase = bt::testing::FakeDispatcherControllerTest<FakeController>;
27 using LEAdvertisingState = FakeController::LEAdvertisingState;
28
29 using pw::bluetooth::emboss::LEAdvertisingDataStatus;
30 using pw::bluetooth::emboss::LEExtendedAdvertisingReportDataView;
31 using pw::bluetooth::emboss::LEExtendedAdvertisingReportDataWriter;
32 using pw::bluetooth::emboss::LEExtendedAdvertisingReportSubeventWriter;
33 using testing::FakePeer;
34
35 constexpr pw::chrono::SystemClock::duration kPwScanResponseTimeout =
36 std::chrono::seconds(2);
37
38 const StaticByteBuffer kPlainAdvDataBytes('T', 'e', 's', 't');
39 const StaticByteBuffer kPlainScanRspBytes('D', 'a', 't', 'a');
40
41 const DeviceAddress kPublicAddr1(DeviceAddress::Type::kLEPublic, {1});
42 const DeviceAddress kPublicAddr2(DeviceAddress::Type::kLEPublic, {2});
43 const DeviceAddress kPublicAddr3(DeviceAddress::Type::kLEPublic, {3});
44
45 class ExtendedLowEnergyScannerTest : public TestingBase,
46 public LowEnergyScanner::Delegate {
47 public:
48 ExtendedLowEnergyScannerTest() = default;
49 ~ExtendedLowEnergyScannerTest() override = default;
50
51 protected:
SetUp()52 void SetUp() override {
53 TestingBase::SetUp();
54
55 FakeController::Settings settings;
56 settings.ApplyExtendedLEConfig();
57 test_device()->set_settings(settings);
58
59 scanner_ = std::make_unique<ExtendedLowEnergyScanner>(
60 &fake_address_delegate_, transport()->GetWeakPtr(), dispatcher());
61 scanner_->set_delegate(this);
62
63 auto p = std::make_unique<FakePeer>(kPublicAddr1, dispatcher(), true, true);
64 p->set_use_extended_advertising_pdus(true);
65 p->set_advertising_data(kPlainAdvDataBytes);
66 p->set_scan_response(kPlainScanRspBytes);
67 peers_.push_back(std::move(p));
68
69 p = std::make_unique<FakePeer>(kPublicAddr2, dispatcher(), true, false);
70 p->set_use_extended_advertising_pdus(true);
71 p->set_advertising_data(kPlainAdvDataBytes);
72 peers_.push_back(std::move(p));
73
74 p = std::make_unique<FakePeer>(kPublicAddr3, dispatcher(), true, false);
75 p->set_use_extended_advertising_pdus(true);
76 p->set_advertising_data(kPlainAdvDataBytes);
77 peers_.push_back(std::move(p));
78
79 StartScan(/*active=*/true);
80 RunUntilIdle();
81 }
82
TearDown()83 void TearDown() override {
84 scanner_ = nullptr;
85 TestingBase::TearDown();
86 }
87
OnPeerFound(const LowEnergyScanResult & result)88 void OnPeerFound(const LowEnergyScanResult& result) override {
89 if (peer_found_cb_) {
90 peer_found_cb_(result);
91 }
92 }
93
94 using PeerFoundCallback = fit::function<void(const LowEnergyScanResult&)>;
set_peer_found_callback(PeerFoundCallback cb)95 void set_peer_found_callback(PeerFoundCallback cb) {
96 peer_found_cb_ = std::move(cb);
97 }
98
StartScan(bool active,pw::chrono::SystemClock::duration period=LowEnergyScanner::kPeriodInfinite)99 bool StartScan(bool active,
100 pw::chrono::SystemClock::duration period =
101 LowEnergyScanner::kPeriodInfinite) {
102 LowEnergyScanner::ScanOptions options{
103 .active = active,
104 .filter_duplicates = true,
105 .period = period,
106 .scan_response_timeout = kPwScanResponseTimeout};
107 return scanner_->StartScan(options, [](auto) {});
108 }
109
peer(int i) const110 const std::unique_ptr<FakePeer>& peer(int i) const { return peers_[i]; }
111
112 static constexpr size_t event_prefix_size = pw::bluetooth::emboss::
113 LEExtendedAdvertisingReportSubevent::MinSizeInBytes();
114 static constexpr size_t report_prefix_size =
115 pw::bluetooth::emboss::LEExtendedAdvertisingReportData::MinSizeInBytes();
116
117 private:
118 std::unique_ptr<ExtendedLowEnergyScanner> scanner_;
119 PeerFoundCallback peer_found_cb_;
120 std::vector<std::unique_ptr<FakePeer>> peers_;
121 FakeLocalAddressDelegate fake_address_delegate_{dispatcher()};
122
123 BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(ExtendedLowEnergyScannerTest);
124 };
125
126 // Ensure we can parse a single advertising report correctly
TEST_F(ExtendedLowEnergyScannerTest,ParseAdvertisingReportsSingleReport)127 TEST_F(ExtendedLowEnergyScannerTest, ParseAdvertisingReportsSingleReport) {
128 size_t data_size = peer(1)->advertising_data().size();
129 size_t reports_size = report_prefix_size + data_size;
130 size_t packet_size = event_prefix_size + reports_size;
131
132 auto event = hci::EventPacket::New<LEExtendedAdvertisingReportSubeventWriter>(
133 hci_spec::kLEMetaEventCode, packet_size);
134 auto packet = event.view_t(static_cast<int32_t>(reports_size));
135 packet.le_meta_event().subevent_code().Write(
136 hci_spec::kLEExtendedAdvertisingReportSubeventCode);
137 packet.num_reports().Write(1);
138
139 LEExtendedAdvertisingReportDataWriter report(
140 packet.reports().BackingStorage().begin(), reports_size);
141 peer(1)->FillExtendedAdvertisingReport(report,
142 peer(1)->advertising_data(),
143 /*is_fragmented=*/false,
144 /*is_scan_response=*/false);
145
146 test_device()->SendCommandChannelPacket(event.data());
147
148 bool peer_found_callback_called = false;
149 set_peer_found_callback([&](const LowEnergyScanResult& result) {
150 peer_found_callback_called = true;
151 EXPECT_EQ(peer(1)->address(), result.address());
152 EXPECT_EQ(peer(1)->advertising_data().size(), result.data().size());
153 EXPECT_EQ(peer(1)->advertising_data(), result.data());
154 });
155
156 RunUntilIdle();
157 EXPECT_TRUE(peer_found_callback_called);
158 }
159
160 // Ensure we can parse multiple extended advertising reports correctly
TEST_F(ExtendedLowEnergyScannerTest,ParseAdvertisingReportsMultipleReports)161 TEST_F(ExtendedLowEnergyScannerTest, ParseAdvertisingReportsMultipleReports) {
162 size_t data_size = peer(1)->advertising_data().size();
163 uint8_t num_reports = 2;
164 size_t single_report_size = report_prefix_size + data_size;
165 size_t reports_size = num_reports * single_report_size;
166 size_t packet_size = event_prefix_size + reports_size;
167
168 auto event = hci::EventPacket::New<LEExtendedAdvertisingReportSubeventWriter>(
169 hci_spec::kLEMetaEventCode, packet_size);
170 auto packet = event.view_t(static_cast<int32_t>(reports_size));
171 packet.le_meta_event().subevent_code().Write(
172 hci_spec::kLEExtendedAdvertisingReportSubeventCode);
173 packet.num_reports().Write(num_reports);
174
175 LEExtendedAdvertisingReportDataWriter report_a(
176 packet.reports().BackingStorage().begin(), single_report_size);
177 peer(1)->FillExtendedAdvertisingReport(report_a,
178 peer(1)->advertising_data(),
179 /*is_fragmented=*/false,
180 /*is_scan_response=*/false);
181
182 LEExtendedAdvertisingReportDataWriter report_b(
183 packet.reports().BackingStorage().begin() + single_report_size,
184 single_report_size);
185 peer(2)->FillExtendedAdvertisingReport(report_b,
186 peer(2)->advertising_data(),
187 /*is_fragmented=*/false,
188 /*is_scan_response=*/false);
189
190 test_device()->SendCommandChannelPacket(event.data());
191
192 bool peer_found_callback_called = false;
193 std::unordered_map<DeviceAddress, std::unique_ptr<DynamicByteBuffer>> map;
194
195 set_peer_found_callback([&](const LowEnergyScanResult& result) {
196 peer_found_callback_called = true;
197 map[result.address()] =
198 std::make_unique<DynamicByteBuffer>(result.data().size());
199 result.data().Copy(&*map[result.address()]);
200 });
201
202 RunUntilIdle();
203 EXPECT_TRUE(peer_found_callback_called);
204
205 EXPECT_EQ(2u, map.size());
206 EXPECT_EQ(1u, map.count(peer(1)->address()));
207 EXPECT_EQ(*map[peer(1)->address()], peer(1)->advertising_data());
208
209 EXPECT_EQ(1u, map.count(peer(2)->address()));
210 EXPECT_EQ(*map[peer(2)->address()], peer(2)->advertising_data());
211 }
212
213 // Test that we check for enough data being present before constructing a view
214 // on top of it. This case hopefully should never happen since the
215 // Controller should always send back valid data but it's better to be
216 // careful and avoid a crash.
TEST_F(ExtendedLowEnergyScannerTest,ParseAdvertisingReportsNotEnoughData)217 TEST_F(ExtendedLowEnergyScannerTest, ParseAdvertisingReportsNotEnoughData) {
218 size_t data_size = peer(1)->advertising_data().size();
219 size_t reports_size = report_prefix_size + data_size;
220 size_t packet_size = event_prefix_size + reports_size;
221
222 auto event = hci::EventPacket::New<LEExtendedAdvertisingReportSubeventWriter>(
223 hci_spec::kLEMetaEventCode, packet_size);
224 auto packet = event.view_t(static_cast<int32_t>(reports_size));
225 packet.le_meta_event().subevent_code().Write(
226 hci_spec::kLEExtendedAdvertisingReportSubeventCode);
227 packet.num_reports().Write(1);
228
229 LEExtendedAdvertisingReportDataWriter report(
230 packet.reports().BackingStorage().begin(), reports_size);
231 peer(1)->FillExtendedAdvertisingReport(report,
232 peer(1)->advertising_data(),
233 /*is_fragmented=*/false,
234 /*is_scan_response=*/false);
235
236 // claim we need more data than we actually provided to trigger the edge case
237 report.data_length().Write(report.data_length().Read() + 1);
238 test_device()->SendCommandChannelPacket(event.data());
239
240 // there wasn't enough data available so we shouldn't have parsed out any
241 // advertising reports
242 set_peer_found_callback([&](const LowEnergyScanResult&) { FAIL(); });
243
244 RunUntilIdle();
245 }
246
247 // If a series of advertising reports claim to have more than
248 // hci_spec::kMaxLEExtendedAdvertisingDataLength, we should truncate the excess.
249 // This case hopefully should never happen since the Controller should always
250 // send back valid data but it's better to be careful and avoid a bug.
TEST_F(ExtendedLowEnergyScannerTest,TruncateToMax)251 TEST_F(ExtendedLowEnergyScannerTest, TruncateToMax) {
252 size_t data_size = std::numeric_limits<uint8_t>::max() - report_prefix_size -
253 event_prefix_size;
254 size_t reports_size = report_prefix_size + data_size;
255 size_t packet_size = event_prefix_size + reports_size;
256 size_t num_full_reports =
257 hci_spec::kMaxLEExtendedAdvertisingDataLength / data_size;
258
259 for (size_t i = 0; i < num_full_reports; i++) {
260 auto event =
261 hci::EventPacket::New<LEExtendedAdvertisingReportSubeventWriter>(
262 hci_spec::kLEMetaEventCode, packet_size);
263 auto packet = event.view_t(static_cast<int32_t>(reports_size));
264 packet.le_meta_event().subevent_code().Write(
265 hci_spec::kLEExtendedAdvertisingReportSubeventCode);
266 packet.num_reports().Write(1);
267
268 LEExtendedAdvertisingReportDataWriter report(
269 packet.reports().BackingStorage().begin(), reports_size);
270 peer(1)->FillExtendedAdvertisingReport(report,
271 peer(1)->advertising_data(),
272 /*is_fragmented=*/true,
273 /*is_scan_response=*/false);
274 report.data_length().Write(static_cast<uint8_t>(data_size));
275
276 test_device()->SendCommandChannelPacket(event.data());
277 }
278
279 // the final report we send has an extra byte to trigger the edge case
280 data_size = hci_spec::kMaxLEExtendedAdvertisingDataLength % data_size + 1;
281 reports_size = report_prefix_size + data_size;
282 packet_size = event_prefix_size + reports_size;
283
284 auto event = hci::EventPacket::New<LEExtendedAdvertisingReportSubeventWriter>(
285 hci_spec::kLEMetaEventCode, packet_size);
286 auto packet = event.view_t(static_cast<int32_t>(reports_size));
287 packet.le_meta_event().subevent_code().Write(
288 hci_spec::kLEExtendedAdvertisingReportSubeventCode);
289 packet.num_reports().Write(1);
290
291 LEExtendedAdvertisingReportDataWriter report(
292 packet.reports().BackingStorage().begin(), reports_size);
293 peer(1)->FillExtendedAdvertisingReport(report,
294 peer(1)->advertising_data(),
295 /*is_fragmented=*/false,
296 /*is_scan_response=*/false);
297 report.data_length().Write(static_cast<int32_t>(data_size));
298
299 size_t result_data_length = 0;
300 set_peer_found_callback([&](const LowEnergyScanResult& result) {
301 result_data_length = result.data().size();
302 });
303
304 test_device()->SendCommandChannelPacket(event.data());
305
306 RunUntilIdle();
307 EXPECT_EQ(hci_spec::kMaxLEExtendedAdvertisingDataLength, result_data_length);
308 }
309
310 // If we receive an event marked as incomplete, there is more data coming in
311 // another extended advertising report. We should wait for that data and not
312 // call the peer found callback.
TEST_F(ExtendedLowEnergyScannerTest,Incomplete)313 TEST_F(ExtendedLowEnergyScannerTest, Incomplete) {
314 size_t data_size = peer(1)->advertising_data().size();
315 size_t reports_size = report_prefix_size + data_size;
316 size_t packet_size = event_prefix_size + reports_size;
317
318 auto event = hci::EventPacket::New<LEExtendedAdvertisingReportSubeventWriter>(
319 hci_spec::kLEMetaEventCode, packet_size);
320 auto packet = event.view_t(static_cast<int32_t>(reports_size));
321 packet.le_meta_event().subevent_code().Write(
322 hci_spec::kLEExtendedAdvertisingReportSubeventCode);
323 packet.num_reports().Write(1);
324
325 LEExtendedAdvertisingReportDataWriter report(
326 packet.reports().BackingStorage().begin(), reports_size);
327 peer(1)->FillExtendedAdvertisingReport(report,
328 peer(1)->advertising_data(),
329 /*is_fragmented=*/true,
330 /*is_scan_response=*/false);
331 test_device()->SendCommandChannelPacket(event.data());
332
333 bool callback_called = false;
334 set_peer_found_callback(
335 [&](const LowEnergyScanResult&) { callback_called = true; });
336
337 RunUntilIdle();
338 EXPECT_FALSE(callback_called);
339 }
340
341 // If we receive an event marked as incomplete truncated, the data was truncated
342 // but we won't be receiving any more advertising reports for this particular
343 // peer. We can go ahead and notify a peer was found with the data we do
344 // currently have. Ensure that we do.
345 //
346 // We specifically use peer(0) here because it is set to be scannable. We want
347 // to make sure that we continue to scan for a scan response, even if the
348 // advertising data got truncated.
TEST_F(ExtendedLowEnergyScannerTest,IncompleteTruncated)349 TEST_F(ExtendedLowEnergyScannerTest, IncompleteTruncated) {
350 size_t data_size = peer(0)->advertising_data().size();
351 size_t reports_size = report_prefix_size + data_size;
352 size_t packet_size = event_prefix_size + reports_size;
353
354 auto event = hci::EventPacket::New<LEExtendedAdvertisingReportSubeventWriter>(
355 hci_spec::kLEMetaEventCode, packet_size);
356 auto packet = event.view_t(static_cast<int32_t>(reports_size));
357 packet.le_meta_event().subevent_code().Write(
358 hci_spec::kLEExtendedAdvertisingReportSubeventCode);
359 packet.num_reports().Write(1);
360
361 LEExtendedAdvertisingReportDataWriter report(
362 packet.reports().BackingStorage().begin(), reports_size);
363 peer(0)->FillExtendedAdvertisingReport(report,
364 peer(0)->advertising_data(),
365 /*is_fragmented=*/false,
366 /*is_scan_response=*/false);
367 report.event_type().data_status().Write(
368 LEAdvertisingDataStatus::INCOMPLETE_TRUNCATED);
369 test_device()->SendCommandChannelPacket(event.data());
370
371 bool callback_called = false;
372 set_peer_found_callback(
373 [&](const LowEnergyScanResult&) { callback_called = true; });
374
375 RunUntilIdle();
376 EXPECT_FALSE(callback_called);
377 }
378
379 // If we receive an event marked as incomplete truncated, the data was truncated
380 // but we won't be receiving any more advertising reports for this particular
381 // peer. We can go ahead and notify a peer was found with the data we do
382 // currently have.
383 //
384 // We specifically use peer(1) here because it is not set to be scannable. We
385 // want to make sure that we report the peer found right away if the peer isn't
386 // scannable, essentially treating this event as if the advertising data was
387 // complete.
TEST_F(ExtendedLowEnergyScannerTest,IncompleteTruncatedNonScannable)388 TEST_F(ExtendedLowEnergyScannerTest, IncompleteTruncatedNonScannable) {
389 size_t data_size = peer(1)->advertising_data().size();
390 size_t reports_size = report_prefix_size + data_size;
391 size_t packet_size = event_prefix_size + reports_size;
392
393 auto event = hci::EventPacket::New<LEExtendedAdvertisingReportSubeventWriter>(
394 hci_spec::kLEMetaEventCode, packet_size);
395 auto packet = event.view_t(static_cast<int32_t>(reports_size));
396 packet.le_meta_event().subevent_code().Write(
397 hci_spec::kLEExtendedAdvertisingReportSubeventCode);
398 packet.num_reports().Write(1);
399
400 LEExtendedAdvertisingReportDataWriter report(
401 packet.reports().BackingStorage().begin(), reports_size);
402 peer(1)->FillExtendedAdvertisingReport(report,
403 peer(1)->advertising_data(),
404 /*is_fragmented=*/false,
405 /*is_scan_response=*/false);
406 report.event_type().data_status().Write(
407 LEAdvertisingDataStatus::INCOMPLETE_TRUNCATED);
408 test_device()->SendCommandChannelPacket(event.data());
409
410 bool callback_called = false;
411 set_peer_found_callback(
412 [&](const LowEnergyScanResult&) { callback_called = true; });
413
414 RunUntilIdle();
415 EXPECT_TRUE(callback_called);
416 }
417
418 } // namespace bt::hci
419