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