xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/hci/extended_low_energy_scanner_test.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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