xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/peripheral_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/peripheral.h"
16 
17 #include "pw_async/fake_dispatcher.h"
18 #include "pw_async2/pend_func_task.h"
19 #include "pw_bluetooth_sapphire/internal/host/gap/fake_adapter.h"
20 #include "pw_unit_test/framework.h"
21 
22 using Peripheral2 = pw::bluetooth::low_energy::Peripheral2;
23 using AdvertisedPeripheral2 = pw::bluetooth::low_energy::AdvertisedPeripheral2;
24 using ManufacturerData = pw::bluetooth::low_energy::ManufacturerData;
25 using AdvertiseError = pw::bluetooth::low_energy::Peripheral2::AdvertiseError;
26 
27 template <typename T>
28 class ReceiverTask final : public pw::async2::Task {
29  public:
ReceiverTask(pw::async2::OnceReceiver<T> receiver)30   ReceiverTask(pw::async2::OnceReceiver<T> receiver)
31       : receiver_(std::move(receiver)) {}
32 
DoPend(pw::async2::Context & cx)33   pw::async2::Poll<> DoPend(pw::async2::Context& cx) override {
34     pw::async2::Poll<pw::Result<T>> pend = receiver_.Pend(cx);
35     if (pend.IsPending()) {
36       return pw::async2::Pending();
37     }
38     result_ = std::move(pend.value());
39     return pw::async2::Ready();
40   }
41 
result()42   pw::Result<T>& result() { return result_; }
43 
44  private:
45   pw::async2::OnceReceiver<T> receiver_;
46   pw::Result<T> result_;
47 };
48 
49 class PeripheralTest : public ::testing::Test {
50  public:
SetUp()51   void SetUp() override {}
52 
TearDown()53   void TearDown() override {}
54 
55   // Returns nullopt if OnceReceiver received no result or a OnceReceiver error.
Advertise(Peripheral2::AdvertisingParameters & parameters)56   std::optional<Peripheral2::AdvertiseResult> Advertise(
57       Peripheral2::AdvertisingParameters& parameters) {
58     pw::async2::OnceReceiver<Peripheral2::AdvertiseResult> receiver =
59         peripheral().Advertise(parameters);
60 
61     ReceiverTask<Peripheral2::AdvertiseResult> task(std::move(receiver));
62     dispatcher2().Post(task);
63     EXPECT_TRUE(task.result().status().IsUnknown());
64 
65     dispatcher().RunUntilIdle();
66     EXPECT_TRUE(dispatcher2().RunUntilStalled().IsReady());
67     if (!task.result().status().ok()) {
68       return std::nullopt;
69     }
70     return std::move(task.result().value());
71   }
72 
AdvertiseExpectSuccess(Peripheral2::AdvertisingParameters & parameters)73   AdvertisedPeripheral2::Ptr AdvertiseExpectSuccess(
74       Peripheral2::AdvertisingParameters& parameters) {
75     std::optional<Peripheral2::AdvertiseResult> result = Advertise(parameters);
76     if (!result.has_value()) {
77       ADD_FAILURE();
78       return nullptr;
79     }
80     if (!result.value().has_value()) {
81       ADD_FAILURE();
82       return nullptr;
83     }
84     return std::move(result.value().value());
85   }
86 
peripheral()87   pw::bluetooth_sapphire::Peripheral& peripheral() { return peripheral_; }
88 
adapter()89   bt::gap::testing::FakeAdapter& adapter() { return adapter_; }
90 
dispatcher()91   pw::async::test::FakeDispatcher& dispatcher() { return async_dispatcher_; }
dispatcher2()92   pw::async2::Dispatcher& dispatcher2() { return async2_dispatcher_; }
93 
94  private:
95   pw::async::test::FakeDispatcher async_dispatcher_;
96   pw::async2::Dispatcher async2_dispatcher_;
97   bt::gap::testing::FakeAdapter adapter_{async_dispatcher_};
98   pw::bluetooth_sapphire::Peripheral peripheral_{adapter_.AsWeakPtr(),
99                                                  async_dispatcher_};
100 };
101 
TEST_F(PeripheralTest,StartAdvertisingWithNameAndDestroyAdvertisedPeripheralStopsAdvertising)102 TEST_F(PeripheralTest,
103        StartAdvertisingWithNameAndDestroyAdvertisedPeripheralStopsAdvertising) {
104   Peripheral2::AdvertisingParameters parameters;
105   parameters.data.name = "pigweed";
106 
107   AdvertisedPeripheral2::Ptr advertised_peripheral =
108       AdvertiseExpectSuccess(parameters);
109 
110   ASSERT_EQ(adapter().fake_le()->registered_advertisements().size(), 1u);
111   auto& advertisement =
112       adapter().fake_le()->registered_advertisements().begin()->second;
113   EXPECT_EQ(advertisement.data.local_name()->name, parameters.data.name);
114   EXPECT_EQ(advertisement.data.appearance(),
115             static_cast<uint16_t>(pw::bluetooth::Appearance::kUnknown));
116   EXPECT_FALSE(advertisement.extended_pdu);
117   EXPECT_FALSE(advertisement.include_tx_power_level);
118   EXPECT_FALSE(advertisement.connectable);
119   EXPECT_FALSE(advertisement.anonymous);
120 
121   advertised_peripheral.reset();
122   dispatcher().RunUntilIdle();
123   ASSERT_EQ(adapter().fake_le()->registered_advertisements().size(), 0u);
124 }
125 
TEST_F(PeripheralTest,StartAdvertisingWithTooLongName)126 TEST_F(PeripheralTest, StartAdvertisingWithTooLongName) {
127   std::string name(300, 'A');
128   Peripheral2::AdvertisingParameters parameters;
129   parameters.data.name = name;
130   std::optional<Peripheral2::AdvertiseResult> result = Advertise(parameters);
131   ASSERT_TRUE(result.has_value());
132   ASSERT_FALSE(result.value().has_value());
133   EXPECT_EQ(result.value().error(),
134             pw::bluetooth_sapphire::Peripheral::AdvertiseError::
135                 kAdvertisingDataTooLong);
136 }
137 
TEST_F(PeripheralTest,StartAdvertisingWithServiceData)138 TEST_F(PeripheralTest, StartAdvertisingWithServiceData) {
139   const uint16_t uuid_0 = 42, uuid_1 = 43;
140   pw::bluetooth::low_energy::ServiceData service_data_0;
141   service_data_0.uuid = pw::bluetooth::Uuid(uuid_0);
142   std::array<std::byte, 3> service_data_0_data = {
143       std::byte{0x00}, std::byte{0x01}, std::byte{0x02}};
144   service_data_0.data = pw::span(service_data_0_data);
145 
146   pw::bluetooth::low_energy::ServiceData service_data_1;
147   service_data_1.uuid = pw::bluetooth::Uuid(uuid_1);
148   std::array<std::byte, 3> service_data_1_data = {
149       std::byte{0x10}, std::byte{0x11}, std::byte{0x12}};
150   service_data_1.data = pw::span(service_data_1_data);
151 
152   std::array<pw::bluetooth::low_energy::ServiceData, 2> service_data = {
153       service_data_0, service_data_1};
154   Peripheral2::AdvertisingParameters parameters;
155   parameters.data.service_data = service_data;
156 
157   AdvertisedPeripheral2::Ptr advertised_peripheral =
158       AdvertiseExpectSuccess(parameters);
159 
160   ASSERT_EQ(adapter().fake_le()->registered_advertisements().size(), 1u);
161   auto& advertisement =
162       adapter().fake_le()->registered_advertisements().begin()->second;
163   EXPECT_EQ(advertisement.data.service_data(bt::UUID(uuid_0)),
164             bt::BufferView(service_data_0.data));
165   EXPECT_EQ(advertisement.data.service_data(bt::UUID(uuid_1)),
166             bt::BufferView(service_data_1.data));
167 }
168 
TEST_F(PeripheralTest,StartAdvertisingWithServiceUuids)169 TEST_F(PeripheralTest, StartAdvertisingWithServiceUuids) {
170   const uint16_t uuid_0 = 42, uuid_1 = 43;
171   std::array<pw::bluetooth::Uuid, 2> service_uuids = {
172       pw::bluetooth::Uuid(uuid_0), pw::bluetooth::Uuid(uuid_1)};
173   std::unordered_set<bt::UUID> expected_uuids{bt::UUID(uuid_0),
174                                               bt::UUID(uuid_1)};
175   Peripheral2::AdvertisingParameters parameters;
176   parameters.data.service_uuids = service_uuids;
177 
178   AdvertisedPeripheral2::Ptr advertised_peripheral =
179       AdvertiseExpectSuccess(parameters);
180 
181   ASSERT_EQ(adapter().fake_le()->registered_advertisements().size(), 1u);
182   auto& advertisement =
183       adapter().fake_le()->registered_advertisements().begin()->second;
184   EXPECT_EQ(advertisement.data.service_uuids(), expected_uuids);
185 }
186 
TEST_F(PeripheralTest,StartAdvertisingWithManufacturerData)187 TEST_F(PeripheralTest, StartAdvertisingWithManufacturerData) {
188   std::array<std::byte, 3> data_0 = {
189       std::byte{0x00}, std::byte{0x01}, std::byte{0x02}};
190   std::array<std::byte, 3> data_1 = {
191       std::byte{0x03}, std::byte{0x04}, std::byte{0x05}};
192   std::array<ManufacturerData, 2> manufacturer_data{
193       ManufacturerData{.company_id = 0, .data = data_0},
194       ManufacturerData{.company_id = 1, .data = data_1}};
195   Peripheral2::AdvertisingParameters parameters;
196   parameters.data.manufacturer_data = manufacturer_data;
197 
198   AdvertisedPeripheral2::Ptr advertised_peripheral =
199       AdvertiseExpectSuccess(parameters);
200 
201   ASSERT_EQ(adapter().fake_le()->registered_advertisements().size(), 1u);
202   auto& advertisement =
203       adapter().fake_le()->registered_advertisements().begin()->second;
204   EXPECT_EQ(advertisement.data.manufacturer_data(0), bt::BufferView(data_0));
205   EXPECT_EQ(advertisement.data.manufacturer_data(1), bt::BufferView(data_1));
206 }
207 
TEST_F(PeripheralTest,StartAdvertisingWithUris)208 TEST_F(PeripheralTest, StartAdvertisingWithUris) {
209   std::string uri_0("https://abc.xyz");
210   std::string uri_1("https://pigweed.dev");
211   std::array<std::string_view, 2> uris = {uri_0, uri_1};
212   std::unordered_set<std::string> uri_set = {uri_0, uri_1};
213   Peripheral2::AdvertisingParameters parameters;
214   parameters.data.uris = uris;
215 
216   AdvertisedPeripheral2::Ptr advertised_peripheral =
217       AdvertiseExpectSuccess(parameters);
218 
219   ASSERT_EQ(adapter().fake_le()->registered_advertisements().size(), 1u);
220   auto& advertisement =
221       adapter().fake_le()->registered_advertisements().begin()->second;
222   EXPECT_EQ(advertisement.data.uris(), uri_set);
223 }
224 
TEST_F(PeripheralTest,StartAdvertisingWithPublicAddressType)225 TEST_F(PeripheralTest, StartAdvertisingWithPublicAddressType) {
226   Peripheral2::AdvertisingParameters parameters;
227   parameters.address_type = pw::bluetooth::Address::Type::kPublic;
228 
229   AdvertisedPeripheral2::Ptr advertised_peripheral =
230       AdvertiseExpectSuccess(parameters);
231 
232   ASSERT_EQ(adapter().fake_le()->registered_advertisements().size(), 1u);
233   auto& advertisement =
234       adapter().fake_le()->registered_advertisements().begin()->second;
235   EXPECT_EQ(advertisement.addr_type, bt::DeviceAddress::Type::kLEPublic);
236 }
237 
TEST_F(PeripheralTest,StartAdvertisingWithRandomAddressType)238 TEST_F(PeripheralTest, StartAdvertisingWithRandomAddressType) {
239   adapter().fake_le()->EnablePrivacy(true);
240 
241   Peripheral2::AdvertisingParameters parameters;
242   parameters.address_type =
243       pw::bluetooth::Address::Type::kRandomResolvablePrivate;
244 
245   AdvertisedPeripheral2::Ptr advertised_peripheral =
246       AdvertiseExpectSuccess(parameters);
247 
248   ASSERT_EQ(adapter().fake_le()->registered_advertisements().size(), 1u);
249   auto& advertisement =
250       adapter().fake_le()->registered_advertisements().begin()->second;
251   EXPECT_EQ(advertisement.addr_type, bt::DeviceAddress::Type::kLERandom);
252 }
253 
TEST_F(PeripheralTest,StartAdvertisingWithLegacyProcedureWithScanResponse)254 TEST_F(PeripheralTest, StartAdvertisingWithLegacyProcedureWithScanResponse) {
255   pw::bluetooth::low_energy::AdvertisingData scan_rsp;
256   scan_rsp.name = "robot";
257   Peripheral2::AdvertisingParameters parameters;
258   parameters.procedure = Peripheral2::LegacyAdvertising{
259       .scan_response = std::move(scan_rsp), .connection_options = std::nullopt};
260 
261   AdvertisedPeripheral2::Ptr advertised_peripheral =
262       AdvertiseExpectSuccess(parameters);
263 
264   ASSERT_EQ(adapter().fake_le()->registered_advertisements().size(), 1u);
265   auto& advertisement =
266       adapter().fake_le()->registered_advertisements().begin()->second;
267   EXPECT_EQ(advertisement.scan_response.local_name()->name, "robot");
268 }
269 
TEST_F(PeripheralTest,StartAdvertisingWithLegacyProcedureWithConnectionOptionsNonBondable)270 TEST_F(PeripheralTest,
271        StartAdvertisingWithLegacyProcedureWithConnectionOptionsNonBondable) {
272   Peripheral2::AdvertisingParameters parameters;
273   Peripheral2::ConnectionOptions connection_options{
274       .bondable_mode = false,
275       .service_filter = std::nullopt,
276       .parameters = std::nullopt,
277       .att_mtu = std::nullopt};
278   parameters.procedure = Peripheral2::LegacyAdvertising{
279       .scan_response = std::nullopt, .connection_options = connection_options};
280 
281   AdvertisedPeripheral2::Ptr advertised_peripheral =
282       AdvertiseExpectSuccess(parameters);
283 
284   ASSERT_EQ(adapter().fake_le()->registered_advertisements().size(), 1u);
285   auto& advertisement =
286       adapter().fake_le()->registered_advertisements().begin()->second;
287   ASSERT_TRUE(advertisement.connectable.has_value());
288   EXPECT_EQ(advertisement.connectable->bondable_mode,
289             bt::sm::BondableMode::NonBondable);
290 }
291 
TEST_F(PeripheralTest,StartAdvertisingWithLegacyProcedureWithConnectionOptionsBondable)292 TEST_F(PeripheralTest,
293        StartAdvertisingWithLegacyProcedureWithConnectionOptionsBondable) {
294   Peripheral2::AdvertisingParameters parameters;
295   Peripheral2::ConnectionOptions connection_options{
296       .bondable_mode = true,
297       .service_filter = std::nullopt,
298       .parameters = std::nullopt,
299       .att_mtu = std::nullopt};
300   parameters.procedure = Peripheral2::LegacyAdvertising{
301       .scan_response = std::nullopt, .connection_options = connection_options};
302 
303   AdvertisedPeripheral2::Ptr advertised_peripheral =
304       AdvertiseExpectSuccess(parameters);
305 
306   ASSERT_EQ(adapter().fake_le()->registered_advertisements().size(), 1u);
307   auto& advertisement =
308       adapter().fake_le()->registered_advertisements().begin()->second;
309   ASSERT_TRUE(advertisement.connectable.has_value());
310   EXPECT_EQ(advertisement.connectable->bondable_mode,
311             bt::sm::BondableMode::Bondable);
312 }
313 
TEST_F(PeripheralTest,StartAdvertisingAnonymous)314 TEST_F(PeripheralTest, StartAdvertisingAnonymous) {
315   Peripheral2::AdvertisingParameters parameters;
316   parameters.procedure = Peripheral2::ExtendedAdvertising{
317       .configuration = pw::bluetooth::low_energy::Peripheral2::
318           ExtendedAdvertising::Anonymous(),
319       .tx_power = std::nullopt,
320       .primary_phy = pw::bluetooth::low_energy::Phy::k1Megabit,
321       .secondary_phy = pw::bluetooth::low_energy::Phy::k1Megabit};
322 
323   AdvertisedPeripheral2::Ptr advertised_peripheral =
324       AdvertiseExpectSuccess(parameters);
325 
326   ASSERT_EQ(adapter().fake_le()->registered_advertisements().size(), 1u);
327   auto& advertisement =
328       adapter().fake_le()->registered_advertisements().begin()->second;
329   EXPECT_TRUE(advertisement.anonymous);
330 }
331 
TEST_F(PeripheralTest,StartAdvertisingWithExtendedProcedureWithScanResponse)332 TEST_F(PeripheralTest, StartAdvertisingWithExtendedProcedureWithScanResponse) {
333   Peripheral2::ScanResponse scan_rsp;
334   scan_rsp.name = "robot";
335   Peripheral2::AdvertisingParameters parameters;
336   parameters.procedure = Peripheral2::ExtendedAdvertising{
337       .configuration = scan_rsp,
338       .tx_power = std::nullopt,
339       .primary_phy = pw::bluetooth::low_energy::Phy::k1Megabit,
340       .secondary_phy = pw::bluetooth::low_energy::Phy::k1Megabit};
341 
342   AdvertisedPeripheral2::Ptr advertised_peripheral =
343       AdvertiseExpectSuccess(parameters);
344 
345   ASSERT_EQ(adapter().fake_le()->registered_advertisements().size(), 1u);
346   auto& advertisement =
347       adapter().fake_le()->registered_advertisements().begin()->second;
348   EXPECT_EQ(advertisement.scan_response.local_name()->name, "robot");
349   EXPECT_FALSE(advertisement.anonymous);
350 }
351 
TEST_F(PeripheralTest,StartAdvertisingWithExtendedProcedureWithConnectionOptionsNonBondable)352 TEST_F(PeripheralTest,
353        StartAdvertisingWithExtendedProcedureWithConnectionOptionsNonBondable) {
354   Peripheral2::AdvertisingParameters parameters;
355   Peripheral2::ConnectionOptions connection_options{
356       .bondable_mode = false,
357       .service_filter = std::nullopt,
358       .parameters = std::nullopt,
359       .att_mtu = std::nullopt};
360   parameters.procedure = Peripheral2::ExtendedAdvertising{
361       .configuration = connection_options,
362       .tx_power = std::nullopt,
363       .primary_phy = pw::bluetooth::low_energy::Phy::k1Megabit,
364       .secondary_phy = pw::bluetooth::low_energy::Phy::k1Megabit};
365 
366   AdvertisedPeripheral2::Ptr advertised_peripheral =
367       AdvertiseExpectSuccess(parameters);
368 
369   ASSERT_EQ(adapter().fake_le()->registered_advertisements().size(), 1u);
370   auto& advertisement =
371       adapter().fake_le()->registered_advertisements().begin()->second;
372   ASSERT_TRUE(advertisement.connectable.has_value());
373   EXPECT_EQ(advertisement.connectable->bondable_mode,
374             bt::sm::BondableMode::NonBondable);
375 }
376 
TEST_F(PeripheralTest,StartAdvertisingWithExtendedProcedureWithConnectionOptionsBondable)377 TEST_F(PeripheralTest,
378        StartAdvertisingWithExtendedProcedureWithConnectionOptionsBondable) {
379   Peripheral2::AdvertisingParameters parameters;
380   Peripheral2::ConnectionOptions connection_options{
381       .bondable_mode = true,
382       .service_filter = std::nullopt,
383       .parameters = std::nullopt,
384       .att_mtu = std::nullopt};
385   parameters.procedure = Peripheral2::ExtendedAdvertising{
386       .configuration = connection_options,
387       .tx_power = std::nullopt,
388       .primary_phy = pw::bluetooth::low_energy::Phy::k1Megabit,
389       .secondary_phy = pw::bluetooth::low_energy::Phy::k1Megabit};
390 
391   AdvertisedPeripheral2::Ptr advertised_peripheral =
392       AdvertiseExpectSuccess(parameters);
393 
394   ASSERT_EQ(adapter().fake_le()->registered_advertisements().size(), 1u);
395   auto& advertisement =
396       adapter().fake_le()->registered_advertisements().begin()->second;
397   ASSERT_TRUE(advertisement.connectable.has_value());
398   EXPECT_EQ(advertisement.connectable->bondable_mode,
399             bt::sm::BondableMode::Bondable);
400 }
401 
TEST_F(PeripheralTest,StartAdvertisingFailureInternalError)402 TEST_F(PeripheralTest, StartAdvertisingFailureInternalError) {
403   adapter().fake_le()->set_advertising_result(
404       ToResult(bt::HostError::kScanResponseTooLong));
405   Peripheral2::AdvertisingParameters parameters;
406   std::optional<Peripheral2::AdvertiseResult> result = Advertise(parameters);
407   ASSERT_TRUE(result.has_value());
408   ASSERT_FALSE(result.value().has_value());
409   EXPECT_EQ(result.value().error(), AdvertiseError::kScanResponseDataTooLong);
410 }
411 
TEST_F(PeripheralTest,StartAdvertisingAndCallAdvertisedPeripheralStopAdvertising)412 TEST_F(PeripheralTest,
413        StartAdvertisingAndCallAdvertisedPeripheralStopAdvertising) {
414   Peripheral2::AdvertisingParameters parameters;
415 
416   AdvertisedPeripheral2::Ptr advertised_peripheral =
417       AdvertiseExpectSuccess(parameters);
418   ASSERT_EQ(adapter().fake_le()->registered_advertisements().size(), 1u);
419 
420   advertised_peripheral->StopAdvertising();
421 
422   pw::async2::PendFuncTask stop_task(
423       [&advertised_peripheral](pw::async2::Context& cx) -> pw::async2::Poll<> {
424         pw::async2::Poll<pw::Status> pend = advertised_peripheral->PendStop(cx);
425         if (pend.IsReady()) {
426           EXPECT_TRUE(pend->ok());
427           return pw::async2::Ready();
428         }
429         return pw::async2::Pending();
430       });
431   dispatcher2().Post(stop_task);
432 
433   EXPECT_EQ(dispatcher2().RunUntilStalled(stop_task), pw::async2::Pending());
434   ASSERT_EQ(adapter().fake_le()->registered_advertisements().size(), 1u);
435 
436   // Process the stop request.
437   dispatcher().RunUntilIdle();
438   // Process the waker wake.
439   EXPECT_EQ(dispatcher2().RunUntilStalled(stop_task), pw::async2::Ready());
440   ASSERT_EQ(adapter().fake_le()->registered_advertisements().size(), 0u);
441 }
442