xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/hci/legacy_low_energy_scanner_test.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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_bluetooth/hci_events.emb.h"
18 #include "pw_bluetooth_sapphire/internal/host/hci/fake_local_address_delegate.h"
19 #include "pw_bluetooth_sapphire/internal/host/testing/controller_test.h"
20 #include "pw_bluetooth_sapphire/internal/host/testing/fake_controller.h"
21 #include "pw_bluetooth_sapphire/internal/host/testing/fake_peer.h"
22 #include "pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
23 
24 namespace bt::hci {
25 
26 using bt::testing::FakeController;
27 using bt::testing::FakePeer;
28 using TestingBase = bt::testing::FakeDispatcherControllerTest<FakeController>;
29 
30 constexpr pw::chrono::SystemClock::duration kPwScanResponseTimeout =
31     std::chrono::seconds(2);
32 
33 const StaticByteBuffer kPlainAdvDataBytes('T', 'e', 's', 't');
34 const StaticByteBuffer kPlainScanRspBytes('D', 'a', 't', 'a');
35 
36 const DeviceAddress kPublicAddr(DeviceAddress::Type::kLEPublic, {1});
37 
38 class LegacyLowEnergyScannerTest : public TestingBase,
39                                    public LowEnergyScanner::Delegate {
40  public:
41   LegacyLowEnergyScannerTest() = default;
42   ~LegacyLowEnergyScannerTest() override = default;
43 
44  protected:
SetUp()45   void SetUp() override {
46     TestingBase::SetUp();
47 
48     FakeController::Settings settings;
49     settings.ApplyLegacyLEConfig();
50     test_device()->set_settings(settings);
51 
52     scanner_ = std::make_unique<LegacyLowEnergyScanner>(
53         fake_address_delegate(), transport()->GetWeakPtr(), dispatcher());
54     scanner_->set_delegate(this);
55 
56     auto p = std::make_unique<FakePeer>(kPublicAddr,
57                                         dispatcher(),
58                                         /*scannable=*/true,
59                                         /*send_advertising_report=*/true);
60     p->set_use_extended_advertising_pdus(true);
61     p->set_advertising_data(kPlainAdvDataBytes);
62     p->set_scan_response(kPlainScanRspBytes);
63     peers_.push_back(std::move(p));
64   }
65 
TearDown()66   void TearDown() override {
67     scanner_ = nullptr;
68     test_device()->Stop();
69     TestingBase::TearDown();
70   }
71 
StartScan(bool active,pw::chrono::SystemClock::duration period=LowEnergyScanner::kPeriodInfinite)72   bool StartScan(bool active,
73                  pw::chrono::SystemClock::duration period =
74                      LowEnergyScanner::kPeriodInfinite) {
75     LowEnergyScanner::ScanOptions options{
76         .active = active,
77         .filter_duplicates = true,
78         .period = period,
79         .scan_response_timeout = kPwScanResponseTimeout};
80     return scanner()->StartScan(options, [](auto) {});
81   }
82 
83   using PeerFoundCallback = fit::function<void(const LowEnergyScanResult&)>;
set_peer_found_callback(PeerFoundCallback cb)84   void set_peer_found_callback(PeerFoundCallback cb) {
85     peer_found_cb_ = std::move(cb);
86   }
87 
88   // LowEnergyScanner::Delegate override:
OnPeerFound(const LowEnergyScanResult & result)89   void OnPeerFound(const LowEnergyScanResult& result) override {
90     if (peer_found_cb_) {
91       peer_found_cb_(result);
92     }
93   }
94 
scanner() const95   LowEnergyScanner* scanner() const { return scanner_.get(); }
fake_address_delegate()96   FakeLocalAddressDelegate* fake_address_delegate() {
97     return &fake_address_delegate_;
98   }
99 
100   static constexpr size_t event_prefix_size =
101       pw::bluetooth::emboss::LEAdvertisingReportSubevent::MinSizeInBytes();
102   static constexpr size_t report_prefix_size =
103       pw::bluetooth::emboss::LEAdvertisingReportData::MinSizeInBytes();
104 
105  private:
106   std::unique_ptr<LowEnergyScanner> scanner_;
107   PeerFoundCallback peer_found_cb_;
108   std::vector<std::unique_ptr<FakePeer>> peers_;
109   FakeLocalAddressDelegate fake_address_delegate_{dispatcher()};
110 };
111 
112 // Ensure we can parse a single advertising report correctly
TEST_F(LegacyLowEnergyScannerTest,ParseAdvertisingReportsSingleReport)113 TEST_F(LegacyLowEnergyScannerTest, ParseAdvertisingReportsSingleReport) {
114   {
115     auto peer = std::make_unique<FakePeer>(kPublicAddr,
116                                            dispatcher(),
117                                            /*connectable=*/false,
118                                            /*scannable=*/false,
119                                            /*send_advertising_report=*/false);
120     peer->set_advertising_data(kPlainAdvDataBytes);
121     test_device()->AddPeer(std::move(peer));
122   }
123 
124   bool peer_found_callback_called = false;
125   std::unordered_map<DeviceAddress, std::unique_ptr<DynamicByteBuffer>> map;
126 
127   set_peer_found_callback([&](const LowEnergyScanResult& result) {
128     peer_found_callback_called = true;
129     map[result.address()] =
130         std::make_unique<DynamicByteBuffer>(result.data().size());
131     result.data().Copy(&*map[result.address()]);
132   });
133 
134   ASSERT_TRUE(StartScan(true));
135   RunUntilIdle();
136 
137   auto peer = test_device()->FindPeer(kPublicAddr);
138   DynamicByteBuffer buffer = peer->BuildLegacyAdvertisingReportEvent(false);
139   test_device()->SendCommandChannelPacket(buffer);
140 
141   RunUntilIdle();
142   ASSERT_TRUE(peer_found_callback_called);
143   ASSERT_EQ(1u, map.count(peer->address()));
144   ASSERT_TRUE(ContainersEqual(kPlainAdvDataBytes, *map[peer->address()]));
145 }
146 
147 // Ensure we can parse multiple extended advertising reports correctly
TEST_F(LegacyLowEnergyScannerTest,ParseAdvertisingReportsMultipleReports)148 TEST_F(LegacyLowEnergyScannerTest, ParseAdvertisingReportsMultipleReports) {
149   {
150     auto peer = std::make_unique<FakePeer>(kPublicAddr,
151                                            dispatcher(),
152                                            /*connectable=*/true,
153                                            /*scannable=*/true,
154                                            /*send_advertising_report=*/false);
155     peer->set_advertising_data(kPlainAdvDataBytes);
156     peer->set_scan_response(kPlainScanRspBytes);
157     test_device()->AddPeer(std::move(peer));
158   }
159 
160   bool peer_found_callback_called = false;
161   std::unordered_map<DeviceAddress, std::unique_ptr<DynamicByteBuffer>> map;
162 
163   set_peer_found_callback([&](const LowEnergyScanResult& result) {
164     peer_found_callback_called = true;
165     map[result.address()] =
166         std::make_unique<DynamicByteBuffer>(result.data().size());
167     result.data().Copy(&*map[result.address()]);
168   });
169 
170   ASSERT_TRUE(StartScan(true));
171   RunUntilIdle();
172 
173   auto peer = test_device()->FindPeer(kPublicAddr);
174   DynamicByteBuffer buffer = peer->BuildLegacyAdvertisingReportEvent(true);
175   test_device()->SendCommandChannelPacket(buffer);
176 
177   RunUntilIdle();
178   ASSERT_TRUE(peer_found_callback_called);
179   ASSERT_EQ(1u, map.count(peer->address()));
180   ASSERT_EQ(kPlainAdvDataBytes.ToString() + kPlainScanRspBytes.ToString(),
181             map[peer->address()]->ToString());
182 }
183 
184 // Test that we check for enough data being present before constructing a view
185 // on top of it. This case hopefully should never happen since the
186 // Controller should always send back valid data but it's better to be
187 // careful and avoid a crash.
TEST_F(LegacyLowEnergyScannerTest,ParseAdvertisingReportsNotEnoughData)188 TEST_F(LegacyLowEnergyScannerTest, ParseAdvertisingReportsNotEnoughData) {
189   {
190     auto peer = std::make_unique<FakePeer>(kPublicAddr,
191                                            dispatcher(),
192                                            /*connectable=*/true,
193                                            /*scannable=*/true,
194                                            /*send_advertising_report=*/false);
195     peer->set_advertising_data(kPlainAdvDataBytes);
196     test_device()->AddPeer(std::move(peer));
197   }
198 
199   ASSERT_TRUE(StartScan(true));
200   RunUntilIdle();
201 
202   auto peer = test_device()->FindPeer(kPublicAddr);
203   DynamicByteBuffer buffer = peer->BuildLegacyAdvertisingReportEvent(false);
204   auto params = pw::bluetooth::emboss::LEAdvertisingReportSubeventWriter(
205       buffer.mutable_data(), buffer.size());
206   auto report = pw::bluetooth::emboss::LEAdvertisingReportDataWriter(
207       params.BackingStorage().data(), params.BackingStorage().SizeInBytes());
208   report.data_length().Write(report.data_length().Read() + 1);
209   test_device()->SendCommandChannelPacket(buffer);
210 
211   // there wasn't enough data available so we shouldn't have parsed out any
212   // advertising reports
213   set_peer_found_callback([&](const LowEnergyScanResult&) { FAIL(); });
214 
215   RunUntilIdle();
216 }
217 
218 }  // namespace bt::hci
219