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