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/central.h"
16
17 #include <array>
18
19 #include "pw_async/fake_dispatcher.h"
20 #include "pw_async2/pend_func_task.h"
21 #include "pw_async2/poll.h"
22 #include "pw_bluetooth_sapphire/internal/host/gap/fake_adapter.h"
23 #include "pw_multibuf/simple_allocator_for_test.h"
24 #include "pw_unit_test/framework.h"
25
26 namespace {
27
28 using pw::bluetooth_sapphire::Central;
29 using ScanStartResult = Central::ScanStartResult;
30 using Pending = pw::async2::PendingType;
31 using Ready = pw::async2::ReadyType;
32 using Context = pw::async2::Context;
33 using ScanHandle = Central::ScanHandle;
34 using ScanResult = Central::ScanResult;
35 using pw::async2::PendFuncTask;
36 using pw::async2::Poll;
37 using pw::chrono::SystemClock;
38 using ScanFilter = Central::ScanFilter;
39 using DisconnectReason =
40 pw::bluetooth::low_energy::Connection2::DisconnectReason;
41
42 const bt::DeviceAddress kAddress0(bt::DeviceAddress::Type::kLEPublic, {0});
43 const bt::StaticByteBuffer kAdvDataWithName(0x05, // length
44 0x09, // type (name)
45 'T',
46 'e',
47 's',
48 't');
49
50 const pw::bluetooth::Uuid kUuid1(1);
51 const bt::StaticByteBuffer kAdvDataWithUuid1(
52 0x05, bt::DataType::kIncomplete16BitServiceUuids, 0x01, 0x00, 0x00, 0x00);
53
MakePendResultTask(ScanHandle::Ptr & scan_handle,std::optional<pw::Result<ScanResult>> & scan_result_out)54 auto MakePendResultTask(
55 ScanHandle::Ptr& scan_handle,
56 std::optional<pw::Result<ScanResult>>& scan_result_out) {
57 return PendFuncTask([&scan_handle, &scan_result_out](Context& cx) -> Poll<> {
58 Poll<pw::Result<ScanResult>> pend = scan_handle->PendResult(cx);
59 if (pend.IsPending()) {
60 return Pending();
61 }
62 scan_result_out = std::move(pend.value());
63 return Ready();
64 });
65 }
66
67 class CentralTest : public ::testing::Test {
68 public:
SetUp()69 void SetUp() override {
70 central_.emplace(
71 adapter_.AsWeakPtr(), async_dispatcher_, multibuf_allocator_);
72 }
73
Scan(Central::ScanOptions & options)74 ScanHandle::Ptr Scan(Central::ScanOptions& options) {
75 pw::async2::OnceReceiver<ScanStartResult> scan_receiver =
76 central().Scan(options);
77
78 std::optional<pw::Result<ScanStartResult>> scan_pend_result;
79 PendFuncTask scan_receiver_task(
80 [&scan_receiver, &scan_pend_result](Context& cx) -> Poll<> {
81 Poll<pw::Result<ScanStartResult>> scan_pend = scan_receiver.Pend(cx);
82 if (scan_pend.IsPending()) {
83 return Pending();
84 }
85 scan_pend_result = std::move(scan_pend.value());
86 return Ready();
87 });
88 async2_dispatcher().Post(scan_receiver_task);
89 EXPECT_FALSE(scan_pend_result.has_value());
90
91 async_dispatcher().RunUntilIdle();
92 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsReady());
93
94 if (!scan_pend_result.has_value()) {
95 ADD_FAILURE();
96 return nullptr;
97 }
98 if (!scan_pend_result.value().ok()) {
99 ADD_FAILURE();
100 return nullptr;
101 }
102 ScanStartResult scan_start_result =
103 std::move(scan_pend_result.value().value());
104 if (!scan_start_result.has_value()) {
105 ADD_FAILURE();
106 return nullptr;
107 }
108 return std::move(scan_start_result.value());
109 }
110
DestroyCentral()111 void DestroyCentral() { central_.reset(); }
112
connections()113 auto connections() { return adapter().fake_le()->connections(); }
114
adapter()115 bt::gap::testing::FakeAdapter& adapter() { return adapter_; }
peer_cache()116 bt::gap::PeerCache& peer_cache() { return *adapter_.peer_cache(); }
central()117 pw::bluetooth_sapphire::Central& central() { return central_.value(); }
async_dispatcher()118 pw::async::test::FakeDispatcher& async_dispatcher() {
119 return async_dispatcher_;
120 }
async2_dispatcher()121 pw::async2::Dispatcher& async2_dispatcher() { return async2_dispatcher_; }
122
123 private:
124 pw::async::test::FakeDispatcher async_dispatcher_;
125 pw::async2::Dispatcher async2_dispatcher_;
126 bt::gap::testing::FakeAdapter adapter_{async_dispatcher_};
127
128 pw::multibuf::test::SimpleAllocatorForTest</*kDataSizeBytes=*/2024,
129 /*kMetaSizeBytes=*/3000>
130 multibuf_allocator_;
131 std::optional<Central> central_;
132 };
133
TEST_F(CentralTest,ScanOneResultAndStopScanSuccess)134 TEST_F(CentralTest, ScanOneResultAndStopScanSuccess) {
135 Central::ScanOptions options;
136 options.scan_type = Central::ScanType::kActiveUsePublicAddress;
137 // Don't filter results.
138 std::array<Central::ScanFilter, 1> filters{Central::ScanFilter{}};
139 options.filters = filters;
140
141 ScanHandle::Ptr scan_handle = Scan(options);
142 ASSERT_TRUE(scan_handle);
143 ASSERT_EQ(adapter().fake_le()->discovery_sessions().size(), 1u);
144 EXPECT_TRUE((*adapter().fake_le()->discovery_sessions().cbegin())->active());
145
146 std::optional<pw::Result<ScanResult>> scan_result_result;
147 PendFuncTask scan_handle_task =
148 MakePendResultTask(scan_handle, scan_result_result);
149 async2_dispatcher().Post(scan_handle_task);
150 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsPending());
151
152 const bool connectable = true;
153 bt::gap::Peer* peer = peer_cache().NewPeer(kAddress0, connectable);
154 const int rssi = 5;
155 SystemClock::time_point timestamp(SystemClock::duration(5));
156 peer->MutLe().SetAdvertisingData(rssi, kAdvDataWithName, timestamp);
157
158 adapter().fake_le()->NotifyScanResult(*peer);
159
160 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsReady());
161 ASSERT_TRUE(scan_result_result.has_value());
162 ASSERT_TRUE(scan_result_result.value().ok());
163
164 ScanResult scan_result = std::move(scan_result_result.value().value());
165 scan_result_result.reset();
166 EXPECT_EQ(scan_result.peer_id, peer->identifier().value());
167 EXPECT_EQ(scan_result.connectable, connectable);
168 EXPECT_EQ(scan_result.rssi, rssi);
169 EXPECT_EQ(scan_result.last_updated, timestamp);
170 ASSERT_EQ(scan_result.data.size(), kAdvDataWithName.size());
171 ASSERT_TRUE(scan_result.data.IsContiguous());
172 for (size_t i = 0; i < kAdvDataWithName.size(); i++) {
173 EXPECT_EQ(scan_result.data.ContiguousSpan().value()[i],
174 kAdvDataWithName.subspan()[i]);
175 }
176 ASSERT_TRUE(scan_result.name.has_value());
177 EXPECT_EQ(scan_result.name.value(), "Test");
178
179 // No more scan results should be received.
180 async2_dispatcher().Post(scan_handle_task);
181 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsPending());
182 EXPECT_FALSE(scan_result_result.has_value());
183 scan_handle_task.Deregister();
184
185 // Stop scan
186 scan_handle.reset();
187 // The scan should stop asynchronously.
188 EXPECT_EQ(adapter().fake_le()->discovery_sessions().size(), 1u);
189 async_dispatcher().RunUntilIdle();
190 EXPECT_EQ(adapter().fake_le()->discovery_sessions().size(), 0u);
191 }
192
TEST_F(CentralTest,ScanResultDoesNotMatchFilter)193 TEST_F(CentralTest, ScanResultDoesNotMatchFilter) {
194 Central::ScanOptions options;
195 options.scan_type = Central::ScanType::kActiveUsePublicAddress;
196 Central::ScanFilter filter;
197 filter.name = "different-name";
198 std::array<Central::ScanFilter, 1> filters{filter};
199 options.filters = filters;
200
201 ScanHandle::Ptr scan_handle = Scan(options);
202 ASSERT_TRUE(scan_handle);
203
204 std::optional<pw::Result<ScanResult>> scan_result_result;
205 PendFuncTask scan_handle_task =
206 MakePendResultTask(scan_handle, scan_result_result);
207 async2_dispatcher().Post(scan_handle_task);
208
209 const bool connectable = true;
210 bt::gap::Peer* peer = peer_cache().NewPeer(kAddress0, connectable);
211 const int rssi = 5;
212 SystemClock::time_point timestamp(SystemClock::duration(5));
213 peer->MutLe().SetAdvertisingData(rssi, kAdvDataWithName, timestamp);
214
215 adapter().fake_le()->NotifyScanResult(*peer);
216
217 async_dispatcher().RunUntilIdle();
218 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsPending());
219 EXPECT_FALSE(scan_result_result.has_value());
220 scan_handle_task.Deregister();
221 }
222
TEST_F(CentralTest,ScanResultMatchesSecondFilterOnly)223 TEST_F(CentralTest, ScanResultMatchesSecondFilterOnly) {
224 Central::ScanOptions options;
225 options.scan_type = Central::ScanType::kActiveUsePublicAddress;
226 ScanFilter filter_0;
227 filter_0.service_uuid = pw::bluetooth::Uuid(2);
228 ScanFilter filter_1;
229 filter_1.service_uuid = kUuid1;
230 std::array<ScanFilter, 2> filters{filter_0, filter_1};
231 options.filters = filters;
232
233 ScanHandle::Ptr scan_handle = Scan(options);
234 ASSERT_TRUE(scan_handle);
235 ASSERT_EQ(adapter().fake_le()->discovery_sessions().size(), 1u);
236 EXPECT_TRUE((*adapter().fake_le()->discovery_sessions().cbegin())->active());
237
238 std::optional<pw::Result<ScanResult>> scan_result_result;
239 PendFuncTask scan_handle_task =
240 MakePendResultTask(scan_handle, scan_result_result);
241 async2_dispatcher().Post(scan_handle_task);
242 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsPending());
243
244 const bool connectable = false;
245 bt::gap::Peer* peer = peer_cache().NewPeer(kAddress0, connectable);
246 const int rssi = 6;
247 SystemClock::time_point timestamp(SystemClock::duration(6));
248 peer->MutLe().SetAdvertisingData(rssi, kAdvDataWithUuid1, timestamp);
249
250 adapter().fake_le()->NotifyScanResult(*peer);
251
252 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsReady());
253 ASSERT_TRUE(scan_result_result.has_value());
254 ASSERT_TRUE(scan_result_result.value().ok());
255
256 ScanResult scan_result = std::move(scan_result_result.value().value());
257 scan_result_result.reset();
258 EXPECT_EQ(scan_result.peer_id, peer->identifier().value());
259 EXPECT_EQ(scan_result.connectable, connectable);
260 EXPECT_EQ(scan_result.rssi, rssi);
261 EXPECT_EQ(scan_result.last_updated, timestamp);
262 ASSERT_EQ(scan_result.data.size(), kAdvDataWithName.size());
263 ASSERT_TRUE(scan_result.data.IsContiguous());
264 for (size_t i = 0; i < kAdvDataWithUuid1.size(); i++) {
265 EXPECT_EQ(scan_result.data.ContiguousSpan().value()[i],
266 kAdvDataWithUuid1.subspan()[i]);
267 }
268 EXPECT_FALSE(scan_result.name.has_value());
269 }
270
TEST_F(CentralTest,ScanResultMatchesSolicitationUUID)271 TEST_F(CentralTest, ScanResultMatchesSolicitationUUID) {
272 Central::ScanOptions options;
273 options.scan_type = Central::ScanType::kActiveUsePublicAddress;
274
275 ScanFilter filter;
276 filter.solicitation_uuid = kUuid1;
277 std::array<Central::ScanFilter, 1> filters{filter};
278 options.filters = filters;
279
280 ScanHandle::Ptr scan_handle = Scan(options);
281 ASSERT_TRUE(scan_handle);
282
283 std::optional<pw::Result<ScanResult>> scan_result_result;
284 PendFuncTask scan_handle_task =
285 MakePendResultTask(scan_handle, scan_result_result);
286 async2_dispatcher().Post(scan_handle_task);
287
288 const bool connectable = false;
289 bt::gap::Peer* peer = peer_cache().NewPeer(kAddress0, connectable);
290 SystemClock::time_point timestamp(SystemClock::duration(6));
291
292 const int rssi = 6;
293 bt::StaticByteBuffer adv_data(
294 0x05, bt::DataType::kSolicitationUuid16Bit, 0x01, 0x00, 0x00, 0x00);
295 peer->MutLe().SetAdvertisingData(rssi, adv_data, timestamp);
296
297 adapter().fake_le()->NotifyScanResult(*peer);
298 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsReady());
299
300 ASSERT_TRUE(scan_result_result.has_value());
301 ASSERT_TRUE(scan_result_result.value().ok());
302
303 ScanResult scan_result = std::move(scan_result_result.value().value());
304 scan_result_result.reset();
305 EXPECT_EQ(scan_result.peer_id, peer->identifier().value());
306 EXPECT_EQ(scan_result.connectable, connectable);
307 EXPECT_EQ(scan_result.rssi, rssi);
308
309 ASSERT_TRUE(scan_result.data.IsContiguous());
310 for (size_t i = 0; i < adv_data.size(); i++) {
311 EXPECT_EQ(scan_result.data.ContiguousSpan().value()[i],
312 adv_data.subspan()[i]);
313 }
314 }
315
TEST_F(CentralTest,CachedScanResult)316 TEST_F(CentralTest, CachedScanResult) {
317 const bool connectable = true;
318 bt::gap::Peer* peer = peer_cache().NewPeer(kAddress0, connectable);
319 const int rssi = 5;
320 SystemClock::time_point timestamp(SystemClock::duration(5));
321 peer->MutLe().SetAdvertisingData(rssi, kAdvDataWithName, timestamp);
322 adapter().fake_le()->AddCachedScanResult(peer->identifier());
323
324 Central::ScanOptions options;
325 options.scan_type = Central::ScanType::kActiveUsePublicAddress;
326 // Don't filter results.
327 std::array<ScanFilter, 1> filters{ScanFilter{}};
328 options.filters = filters;
329
330 ScanHandle::Ptr scan_handle = Scan(options);
331 ASSERT_TRUE(scan_handle);
332 ASSERT_EQ(adapter().fake_le()->discovery_sessions().size(), 1u);
333 EXPECT_TRUE((*adapter().fake_le()->discovery_sessions().cbegin())->active());
334
335 std::optional<pw::Result<ScanResult>> scan_result_result;
336 PendFuncTask scan_handle_task =
337 MakePendResultTask(scan_handle, scan_result_result);
338 async2_dispatcher().Post(scan_handle_task);
339
340 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsReady());
341 ASSERT_TRUE(scan_result_result.has_value());
342 ASSERT_TRUE(scan_result_result.value().ok());
343
344 ScanResult scan_result = std::move(scan_result_result.value().value());
345 EXPECT_EQ(scan_result.peer_id, peer->identifier().value());
346 EXPECT_EQ(scan_result.connectable, connectable);
347 EXPECT_EQ(scan_result.rssi, rssi);
348 EXPECT_EQ(scan_result.last_updated, timestamp);
349 ASSERT_EQ(scan_result.data.size(), kAdvDataWithName.size());
350 ASSERT_TRUE(scan_result.data.IsContiguous());
351 for (size_t i = 0; i < kAdvDataWithName.size(); i++) {
352 EXPECT_EQ(scan_result.data.ContiguousSpan().value()[i],
353 kAdvDataWithName.subspan()[i]);
354 }
355 ASSERT_TRUE(scan_result.name.has_value());
356 EXPECT_EQ(scan_result.name.value(), "Test");
357 }
358
TEST_F(CentralTest,ScanErrorReceivedByScanHandle)359 TEST_F(CentralTest, ScanErrorReceivedByScanHandle) {
360 Central::ScanOptions options;
361 options.scan_type = Central::ScanType::kActiveUsePublicAddress;
362 // Don't filter results.
363 std::array<Central::ScanFilter, 1> filters{Central::ScanFilter{}};
364 options.filters = filters;
365
366 ScanHandle::Ptr scan_handle = Scan(options);
367 ASSERT_TRUE(scan_handle);
368 ASSERT_EQ(adapter().fake_le()->discovery_sessions().size(), 1u);
369 EXPECT_TRUE((*adapter().fake_le()->discovery_sessions().cbegin())->active());
370
371 std::optional<pw::Result<ScanResult>> scan_result_result;
372 PendFuncTask scan_handle_task =
373 MakePendResultTask(scan_handle, scan_result_result);
374 async2_dispatcher().Post(scan_handle_task);
375 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsPending());
376
377 (*adapter().fake_le()->discovery_sessions().cbegin())->NotifyError();
378
379 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsReady());
380 ASSERT_TRUE(scan_result_result.has_value());
381 EXPECT_TRUE(scan_result_result.value().status().IsCancelled());
382 }
383
TEST_F(CentralTest,ScanWithoutFiltersFails)384 TEST_F(CentralTest, ScanWithoutFiltersFails) {
385 Central::ScanOptions options;
386 options.scan_type = Central::ScanType::kActiveUsePublicAddress;
387 options.filters = {};
388
389 pw::async2::OnceReceiver<ScanStartResult> scan_receiver =
390 central().Scan(options);
391
392 std::optional<pw::Result<ScanStartResult>> scan_pend_result;
393 PendFuncTask scan_receiver_task(
394 [&scan_receiver, &scan_pend_result](Context& cx) -> Poll<> {
395 Poll<pw::Result<ScanStartResult>> scan_pend = scan_receiver.Pend(cx);
396 if (scan_pend.IsPending()) {
397 return Pending();
398 }
399 scan_pend_result = std::move(scan_pend.value());
400 return Ready();
401 });
402 async2_dispatcher().Post(scan_receiver_task);
403 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsReady());
404 ASSERT_TRUE(scan_pend_result.has_value());
405 ASSERT_TRUE(scan_pend_result.value().ok());
406 ScanStartResult scan_start_result =
407 std::move(scan_pend_result.value().value());
408 ASSERT_FALSE(scan_start_result.has_value());
409 EXPECT_EQ(scan_start_result.error(),
410 Central::StartScanError::kInvalidParameters);
411 }
412
TEST_F(CentralTest,QueueMoreThanMaxScanResultsInScanHandleDropsOldest)413 TEST_F(CentralTest, QueueMoreThanMaxScanResultsInScanHandleDropsOldest) {
414 Central::ScanOptions options;
415 options.scan_type = Central::ScanType::kActiveUsePublicAddress;
416 // Don't filter results.
417 std::array<Central::ScanFilter, 1> filters{Central::ScanFilter{}};
418 options.filters = filters;
419
420 ScanHandle::Ptr scan_handle = Scan(options);
421 ASSERT_TRUE(scan_handle);
422 ASSERT_EQ(adapter().fake_le()->discovery_sessions().size(), 1u);
423 EXPECT_TRUE((*adapter().fake_le()->discovery_sessions().cbegin())->active());
424
425 std::vector<pw::Result<ScanResult>> scan_result_results;
426 PendFuncTask scan_handle_task =
427 PendFuncTask([&scan_handle, &scan_result_results](Context& cx) -> Poll<> {
428 while (true) {
429 Poll<pw::Result<ScanResult>> pend = scan_handle->PendResult(cx);
430 if (pend.IsPending()) {
431 return Pending();
432 }
433 scan_result_results.emplace_back(std::move(pend.value()));
434 if (!scan_result_results.back().ok()) {
435 return Ready();
436 }
437 }
438 });
439 async2_dispatcher().Post(scan_handle_task);
440 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsPending());
441
442 const bool connectable = true;
443 bt::gap::Peer* peer = peer_cache().NewPeer(kAddress0, connectable);
444 SystemClock::time_point timestamp(SystemClock::duration(5));
445
446 // Queue 1 more than the max queue size. Put the index in the rssi field.
447 for (int i = 0; i < Central::kMaxScanResultsQueueSize + 1; i++) {
448 peer->MutLe().SetAdvertisingData(/*rssi=*/i, kAdvDataWithName, timestamp);
449 adapter().fake_le()->NotifyScanResult(*peer);
450 }
451
452 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsPending());
453 scan_handle_task.Deregister();
454 ASSERT_EQ(scan_result_results.size(), Central::kMaxScanResultsQueueSize);
455 // The first scan result should have been dropped.
456 for (int i = 0; i < Central::kMaxScanResultsQueueSize; i++) {
457 ASSERT_TRUE(scan_result_results[i].ok());
458 EXPECT_EQ(scan_result_results[i].value().rssi, i + 1);
459 }
460 }
461
TEST_F(CentralTest,CentralDestroyedBeforeScanHandle)462 TEST_F(CentralTest, CentralDestroyedBeforeScanHandle) {
463 Central::ScanOptions options;
464 options.scan_type = Central::ScanType::kActiveUsePublicAddress;
465 std::array<Central::ScanFilter, 1> filters{Central::ScanFilter{}};
466 options.filters = filters;
467
468 ScanHandle::Ptr scan_handle = Scan(options);
469 ASSERT_TRUE(scan_handle);
470 ASSERT_EQ(adapter().fake_le()->discovery_sessions().size(), 1u);
471
472 std::optional<pw::Result<ScanResult>> scan_result_result;
473 PendFuncTask scan_handle_task =
474 MakePendResultTask(scan_handle, scan_result_result);
475 async2_dispatcher().Post(scan_handle_task);
476 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsPending());
477
478 DestroyCentral();
479
480 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsReady());
481 ASSERT_TRUE(scan_result_result.has_value());
482 EXPECT_TRUE(scan_result_result.value().status().IsCancelled());
483
484 scan_handle.reset();
485 }
486
TEST_F(CentralTest,ConnectAndDisconnectSuccess)487 TEST_F(CentralTest, ConnectAndDisconnectSuccess) {
488 bt::gap::Peer* peer = peer_cache().NewPeer(kAddress0, /*connectable=*/true);
489 pw::bluetooth::low_energy::Connection2::ConnectionOptions options;
490 std::optional<pw::Result<Central::ConnectResult>> connect_result;
491 pw::async2::OnceReceiver<Central::ConnectResult> receiver =
492 central().Connect(peer->identifier().value(), options);
493 PendFuncTask connect_task =
494 PendFuncTask([&connect_result, &receiver](Context& cx) -> Poll<> {
495 Poll<pw::Result<Central::ConnectResult>> poll = receiver.Pend(cx);
496 if (poll.IsPending()) {
497 return Pending();
498 }
499 connect_result = std::move(poll->value());
500 return Ready();
501 });
502 async2_dispatcher().Post(connect_task);
503 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsPending());
504 async_dispatcher().RunUntilIdle();
505 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsReady());
506 ASSERT_TRUE(connect_result.has_value());
507 ASSERT_TRUE(connect_result->ok());
508 ASSERT_TRUE(connect_result->value());
509 ASSERT_EQ(adapter().fake_le()->connections().count(peer->identifier()), 1u);
510 pw::bluetooth::low_energy::Connection2::Ptr connection =
511 std::move(connect_result->value().value());
512
513 // Disconnect
514 connection.reset();
515 ASSERT_EQ(connections().count(peer->identifier()), 1u);
516 async_dispatcher().RunUntilIdle();
517 ASSERT_EQ(connections().count(peer->identifier()), 0u);
518 }
519
TEST_F(CentralTest,PendDisconnect)520 TEST_F(CentralTest, PendDisconnect) {
521 bt::gap::Peer* peer = peer_cache().NewPeer(kAddress0, /*connectable=*/true);
522 pw::bluetooth::low_energy::Connection2::ConnectionOptions options;
523 std::optional<pw::Result<Central::ConnectResult>> connect_result;
524 pw::async2::OnceReceiver<Central::ConnectResult> receiver =
525 central().Connect(peer->identifier().value(), options);
526 PendFuncTask connect_task =
527 PendFuncTask([&connect_result, &receiver](Context& cx) -> Poll<> {
528 Poll<pw::Result<Central::ConnectResult>> poll = receiver.Pend(cx);
529 if (poll.IsPending()) {
530 return Pending();
531 }
532 connect_result = std::move(poll->value());
533 return Ready();
534 });
535 async2_dispatcher().Post(connect_task);
536 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsPending());
537 async_dispatcher().RunUntilIdle();
538 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsReady());
539 ASSERT_TRUE(connect_result.has_value());
540 ASSERT_TRUE(connect_result->ok());
541 ASSERT_TRUE(connect_result->value());
542 ASSERT_EQ(adapter().fake_le()->connections().count(peer->identifier()), 1u);
543 pw::bluetooth::low_energy::Connection2::Ptr connection =
544 std::move(connect_result->value().value());
545
546 std::optional<DisconnectReason> disconnect_reason;
547 PendFuncTask disconnect_task =
548 PendFuncTask([&connection, &disconnect_reason](Context& cx) -> Poll<> {
549 Poll<DisconnectReason> poll = connection->PendDisconnect(cx);
550 if (poll.IsPending()) {
551 return Pending();
552 }
553 disconnect_reason = poll.value();
554 return Ready();
555 });
556 async2_dispatcher().Post(disconnect_task);
557 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsPending());
558 ASSERT_FALSE(disconnect_reason.has_value());
559
560 ASSERT_TRUE(adapter().fake_le()->Disconnect(peer->identifier()));
561 ASSERT_EQ(adapter().fake_le()->connections().count(peer->identifier()), 0u);
562 EXPECT_TRUE(async2_dispatcher().RunUntilStalled().IsReady());
563 ASSERT_TRUE(disconnect_reason.has_value());
564 EXPECT_EQ(disconnect_reason.value(), DisconnectReason::kFailure);
565
566 connection.reset();
567 async_dispatcher().RunUntilIdle();
568 }
569
570 } // namespace
571