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/sdp/client.h"
16
17 #include <pw_async/dispatcher.h>
18
19 #include <chrono>
20 #include <ratio>
21
22 #include "pw_bluetooth_sapphire/internal/host/l2cap/fake_channel.h"
23 #include "pw_bluetooth_sapphire/internal/host/l2cap/fake_channel_test.h"
24 #include "pw_bluetooth_sapphire/internal/host/sdp/service_record.h"
25 #include "pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
26 #include "pw_unit_test/framework.h"
27
28 namespace bt::sdp {
29 namespace {
30
31 using TestingBase = bt::l2cap::testing::FakeChannelTest;
32 constexpr l2cap::ChannelId kTestChannelId = 0x0041;
33 constexpr uint16_t kResponseMaxSize = 672;
34
35 class ClientTest : public TestingBase {
36 public:
37 ClientTest() = default;
38
39 protected:
SetUp()40 void SetUp() override {
41 ChannelOptions options(kTestChannelId);
42 options.link_type = bt::LinkType::kACL;
43 channel_ = CreateFakeChannel(options);
44 }
45
TearDown()46 void TearDown() override { channel_ = nullptr; }
47
48 private:
49 std::unique_ptr<l2cap::testing::FakeChannel> channel_;
50 };
51
52 // Flower Path Test:
53 // - sends correctly formatted request
54 // - receives response in the callback
55 // - receives kNotFound at the end of the callbacks
56 // - closes SDP channel when client is deallocated
TEST_F(ClientTest,ConnectAndQuery)57 TEST_F(ClientTest, ConnectAndQuery) {
58 {
59 auto client = Client::Create(fake_chan()->GetWeakPtr(), dispatcher());
60
61 EXPECT_TRUE(fake_chan()->activated());
62
63 size_t cb_count = 0;
64 auto result_cb =
65 [&](fit::result<
66 Error<>,
67 std::reference_wrapper<const std::map<AttributeId, DataElement>>>
68 attrs_result) {
69 cb_count++;
70 if (cb_count == 3) {
71 EXPECT_EQ(Error(HostError::kNotFound), attrs_result);
72 return true;
73 }
74 const std::map<AttributeId, DataElement>& attrs =
75 attrs_result.value();
76 // All results should have the ServiceClassIdList.
77 EXPECT_EQ(1u, attrs.count(kServiceClassIdList));
78 // The first result has a kProtocolDescriptorList and the second has a
79 // kBluetoothProfileDescriptorList
80 if (cb_count == 1) {
81 EXPECT_EQ(1u, attrs.count(kProtocolDescriptorList));
82 EXPECT_EQ(0u, attrs.count(kBluetoothProfileDescriptorList));
83 } else if (cb_count == 2) {
84 EXPECT_EQ(0u, attrs.count(kProtocolDescriptorList));
85 EXPECT_EQ(1u, attrs.count(kBluetoothProfileDescriptorList));
86 }
87 return true;
88 };
89
90 const StaticByteBuffer kSearchExpectedParams(
91 // ServiceSearchPattern
92 0x35,
93 0x03, // Sequence uint8 3 bytes
94 0x19,
95 0x11,
96 0x0B, // UUID (kAudioSink)
97 0xFF,
98 0xFF, // MaxAttributeByteCount (no max)
99 // Attribute ID list
100 0x35,
101 0x09, // Sequence uint8 9 bytes
102 0x09,
103 0x00,
104 0x01, // uint16_t (kServiceClassIdList)
105 0x09,
106 0x00,
107 0x04, // uint16_t (kProtocolDescriptorList)
108 0x09,
109 0x00,
110 0x09, // uint16_t (kBluetoothProfileDescriptorList)
111 0x00 // No continuation state
112 );
113
114 uint16_t request_tid;
115 bool success = false;
116
117 fake_chan()->SetSendCallback(
118 [&request_tid, &success, &kSearchExpectedParams](auto packet) {
119 // First byte should be type.
120 ASSERT_LE(3u, packet->size());
121 ASSERT_EQ(kServiceSearchAttributeRequest, (*packet)[0]);
122 ASSERT_EQ(kSearchExpectedParams, packet->view(5));
123 request_tid = ((*packet)[1] << 8) != 0 || (*packet)[2];
124 success = true;
125 },
126 dispatcher());
127
128 // Search for all A2DP sinks, get the:
129 // - Service Class ID list
130 // - Descriptor List
131 // - Bluetooth Profile Descriptor List
132 client->ServiceSearchAttributes({profile::kAudioSink},
133 {kServiceClassIdList,
134 kProtocolDescriptorList,
135 kBluetoothProfileDescriptorList},
136 result_cb);
137 RunUntilIdle();
138 EXPECT_TRUE(success);
139
140 // Receive the response
141 // Record makes building the response easier.
142 ServiceRecord rec;
143 rec.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList,
144 protocol::kL2CAP,
145 DataElement(l2cap::kAVDTP));
146 // The second element here indicates version 1.3 (specified in A2DP spec)
147 rec.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList,
148 protocol::kAVDTP,
149 DataElement(uint16_t{0x0103}));
150 rec.AddProfile(profile::kAudioSink, 1, 3);
151 ServiceSearchAttributeResponse rsp;
152 rsp.SetAttribute(0,
153 kServiceClassIdList,
154 DataElement({DataElement(profile::kAudioSink)}));
155 rsp.SetAttribute(0,
156 kProtocolDescriptorList,
157 rec.GetAttribute(kProtocolDescriptorList).Clone());
158
159 rsp.SetAttribute(1,
160 kServiceClassIdList,
161 DataElement({DataElement(profile::kAudioSink)}));
162 rsp.SetAttribute(1,
163 kBluetoothProfileDescriptorList,
164 rec.GetAttribute(kBluetoothProfileDescriptorList).Clone());
165
166 auto rsp_ptr = rsp.GetPDU(0xFFFF /* Max attribute bytes */,
167 request_tid,
168 kResponseMaxSize,
169 BufferView());
170 fake_chan()->Receive(*rsp_ptr);
171
172 RunUntilIdle();
173
174 EXPECT_EQ(3u, cb_count);
175 }
176 EXPECT_FALSE(fake_chan()->activated());
177 }
178
TEST_F(ClientTest,TwoQueriesSubsequent)179 TEST_F(ClientTest, TwoQueriesSubsequent) {
180 {
181 auto client = Client::Create(fake_chan()->GetWeakPtr(), dispatcher());
182
183 EXPECT_TRUE(fake_chan()->activated());
184
185 size_t cb_count = 0;
186 auto result_cb =
187 [&](fit::result<
188 Error<>,
189 std::reference_wrapper<const std::map<AttributeId, DataElement>>>
190 attrs_result) {
191 cb_count++;
192 // We return no results for both queries.
193 EXPECT_EQ(Error(HostError::kNotFound), attrs_result);
194 return true;
195 };
196
197 const StaticByteBuffer kSearchExpectedParams(
198 // ServiceSearchPattern
199 0x35,
200 0x03, // Sequence uint8 3 bytes
201 0x19,
202 0x11,
203 0x0B, // UUID (kAudioSink)
204 0xFF,
205 0xFF, // MaxAttributeByteCount (no max)
206 // Attribute ID list
207 0x35,
208 0x03, // Sequence uint8 3 bytes
209 0x09,
210 0x00,
211 0x01, // uint16_t (kServiceClassIdList)
212 0x00 // No continuation state
213 );
214
215 uint16_t request_tid;
216 bool success = false;
217
218 fake_chan()->SetSendCallback(
219 [&request_tid, &success, &kSearchExpectedParams](auto packet) {
220 // First byte should be type.
221 ASSERT_LE(3u, packet->size());
222 ASSERT_EQ(kServiceSearchAttributeRequest, (*packet)[0]);
223 ASSERT_EQ(kSearchExpectedParams, packet->view(5));
224 request_tid = ((*packet)[1] << 8) != 0 || (*packet)[2];
225 success = true;
226 },
227 dispatcher());
228
229 // Search for all A2DP sinks, get the:
230 // - Service Class ID list
231 client->ServiceSearchAttributes(
232 {profile::kAudioSink}, {kServiceClassIdList}, result_cb);
233 RunUntilIdle();
234 EXPECT_TRUE(success);
235
236 // Receive the response (empty response)
237 // Record makes building the response easier.
238 ServiceSearchAttributeResponse rsp;
239 auto rsp_ptr = rsp.GetPDU(0xFFFF /* Max attribute bytes */,
240 request_tid,
241 kResponseMaxSize,
242 BufferView());
243 fake_chan()->Receive(*rsp_ptr);
244
245 RunUntilIdle();
246
247 EXPECT_EQ(1u, cb_count);
248
249 // Twice
250 success = false;
251 client->ServiceSearchAttributes(
252 {profile::kAudioSink}, {kServiceClassIdList}, result_cb);
253 RunUntilIdle();
254 EXPECT_TRUE(success);
255
256 rsp_ptr = rsp.GetPDU(0xFFFF /* Max attribute bytes */,
257 request_tid,
258 kResponseMaxSize,
259 BufferView());
260 fake_chan()->Receive(*rsp_ptr);
261
262 RunUntilIdle();
263
264 EXPECT_EQ(2u, cb_count);
265 }
266 EXPECT_FALSE(fake_chan()->activated());
267 }
268
TEST_F(ClientTest,TwoQueriesQueued)269 TEST_F(ClientTest, TwoQueriesQueued) {
270 {
271 auto client = Client::Create(fake_chan()->GetWeakPtr(), dispatcher());
272
273 EXPECT_TRUE(fake_chan()->activated());
274
275 size_t cb_count = 0;
276 auto result_cb =
277 [&](fit::result<
278 Error<>,
279 std::reference_wrapper<const std::map<AttributeId, DataElement>>>
280 attrs_result) {
281 cb_count++;
282 // We return no results for both queries.
283 EXPECT_EQ(Error(HostError::kNotFound), attrs_result);
284 return true;
285 };
286
287 const StaticByteBuffer kSearchExpectedParams(
288 // ServiceSearchPattern
289 0x35,
290 0x03, // Sequence uint8 3 bytes
291 0x19,
292 0x11,
293 0x0B, // UUID (kAudioSink)
294 0xFF,
295 0xFF, // MaxAttributeByteCount (no max)
296 // Attribute ID list
297 0x35,
298 0x03, // Sequence uint8 3 bytes
299 0x09,
300 0x00,
301 0x01, // uint16_t (kServiceClassIdList)
302 0x00 // No continuation state
303 );
304
305 uint16_t request_tid;
306 size_t sent_packets = 0;
307
308 fake_chan()->SetSendCallback(
309 [&request_tid, &sent_packets, &kSearchExpectedParams](auto packet) {
310 // First byte should be type.
311 ASSERT_LE(3u, packet->size());
312 ASSERT_EQ(kServiceSearchAttributeRequest, (*packet)[0]);
313 ASSERT_EQ(kSearchExpectedParams, packet->view(5));
314 request_tid = ((*packet)[1] << 8) != 0 || (*packet)[2];
315 sent_packets++;
316 },
317 dispatcher());
318
319 // Search for all A2DP sinks, get the:
320 // - Service Class ID list
321 client->ServiceSearchAttributes(
322 {profile::kAudioSink}, {kServiceClassIdList}, result_cb);
323 // Twice (without waiting)
324 client->ServiceSearchAttributes(
325 {profile::kAudioSink}, {kServiceClassIdList}, result_cb);
326 RunUntilIdle();
327 // Only one request should have been sent.
328 EXPECT_EQ(1u, sent_packets);
329
330 // Receive the response (empty response)
331 // Record makes building the response easier.
332 ServiceSearchAttributeResponse rsp;
333 auto rsp_ptr = rsp.GetPDU(0xFFFF /* Max attribute bytes */,
334 request_tid,
335 kResponseMaxSize,
336 BufferView());
337 fake_chan()->Receive(*rsp_ptr);
338
339 RunUntilIdle();
340
341 EXPECT_EQ(1u, cb_count);
342 // The second request should have been sent when the first completed.
343 EXPECT_EQ(2u, sent_packets);
344
345 // Respond to the second request.
346 rsp_ptr = rsp.GetPDU(0xFFFF /* Max attribute bytes */,
347 request_tid,
348 kResponseMaxSize,
349 BufferView());
350 fake_chan()->Receive(*rsp_ptr);
351
352 RunUntilIdle();
353
354 EXPECT_EQ(2u, cb_count);
355 EXPECT_EQ(2u, sent_packets);
356 }
357 EXPECT_FALSE(fake_chan()->activated());
358 }
359
360 // Continuing response test:
361 // - send correctly formatted request
362 // - receives a response with a continuing response
363 // - sends a second request to get the rest of the response
364 // - receives the continued response
365 // - responds with the results
366 // - gives up when callback returns false
TEST_F(ClientTest,ContinuingResponseRequested)367 TEST_F(ClientTest, ContinuingResponseRequested) {
368 auto client = Client::Create(fake_chan()->GetWeakPtr(), dispatcher());
369
370 size_t cb_count = 0;
371 auto result_cb =
372 [&](fit::result<
373 Error<>,
374 std::reference_wrapper<const std::map<AttributeId, DataElement>>>
375 attrs_result) {
376 cb_count++;
377 if (cb_count == 3) {
378 EXPECT_EQ(Error(HostError::kNotFound), attrs_result);
379 return true;
380 }
381 const std::map<AttributeId, DataElement>& attrs = attrs_result.value();
382 // All results should have the ServiceClassIdList.
383 EXPECT_EQ(1u, attrs.count(kServiceClassIdList));
384 EXPECT_EQ(1u, attrs.count(kProtocolDescriptorList));
385 return true;
386 };
387
388 const StaticByteBuffer kSearchExpectedParams(
389 // ServiceSearchPattern
390 0x35,
391 0x03, // Sequence uint8 3 bytes
392 0x19,
393 0x11,
394 0x0B, // UUID (kAudioSink)
395 0xFF,
396 0xFF, // MaxAttributeByteCount (no max)
397 // Attribute ID list
398 0x35,
399 0x06, // Sequence uint8 6 bytes
400 0x09,
401 0x00,
402 0x01, // uint16_t (0x0001 = kServiceClassIdList)
403 0x09,
404 0x00,
405 0x04 // uint16_t (0x0004 = kProtocolDescriptorList)
406 );
407
408 size_t requests_made = 0;
409
410 // Record makes building the response easier.
411 ServiceRecord rec;
412 rec.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList,
413 protocol::kL2CAP,
414 DataElement(l2cap::kAVDTP));
415 // The second element here indicates version 1.3 (specified in A2DP spec)
416 rec.AddProtocolDescriptor(ServiceRecord::kPrimaryProtocolList,
417 protocol::kAVDTP,
418 DataElement(uint16_t{0x0103}));
419 rec.AddProfile(profile::kAudioSink, 1, 3);
420 ServiceSearchAttributeResponse rsp;
421 rsp.SetAttribute(
422 0, kServiceClassIdList, DataElement({DataElement(profile::kAudioSink)}));
423 rsp.SetAttribute(0,
424 kProtocolDescriptorList,
425 rec.GetAttribute(kProtocolDescriptorList).Clone());
426 rsp.SetAttribute(
427 1, kServiceClassIdList, DataElement({DataElement(profile::kAudioSink)}));
428 rsp.SetAttribute(1,
429 kProtocolDescriptorList,
430 rec.GetAttribute(kProtocolDescriptorList).Clone());
431
432 fake_chan()->SetSendCallback(
433 [&](auto packet) {
434 requests_made++;
435 // First byte should be type.
436 ASSERT_LE(5u, packet->size());
437 ASSERT_EQ(kServiceSearchAttributeRequest, (*packet)[0]);
438 uint16_t request_tid = ((*packet)[1] << 8) != 0 || (*packet)[2];
439 ASSERT_EQ(kSearchExpectedParams,
440 packet->view(5, kSearchExpectedParams.size()));
441 // The stuff after the params is the continuation state.
442 auto rsp_ptr =
443 rsp.GetPDU(16 /* Max attribute bytes */,
444 request_tid,
445 kResponseMaxSize,
446 packet->view(5 + kSearchExpectedParams.size() + 1));
447 fake_chan()->Receive(*rsp_ptr);
448 },
449 dispatcher());
450
451 // Search for all A2DP sinks, get the:
452 // - Service Class ID list
453 // - Descriptor List
454 // - Bluetooth Profile Descriptor List
455 client->ServiceSearchAttributes(
456 {profile::kAudioSink},
457 {kServiceClassIdList, kProtocolDescriptorList},
458 result_cb);
459 RunUntilIdle();
460 EXPECT_EQ(3u, cb_count);
461 EXPECT_EQ(4u, requests_made);
462 }
463
464 // No results test:
465 // - send correctly formatted request
466 // - receives response with no results
467 // - callback with no results (kNotFound right away)
TEST_F(ClientTest,NoResults)468 TEST_F(ClientTest, NoResults) {
469 auto client = Client::Create(fake_chan()->GetWeakPtr(), dispatcher());
470
471 size_t cb_count = 0;
472 auto result_cb =
473 [&](fit::result<
474 Error<>,
475 std::reference_wrapper<const std::map<AttributeId, DataElement>>>
476 attrs_result) {
477 cb_count++;
478 EXPECT_EQ(Error(HostError::kNotFound), attrs_result);
479 return true;
480 };
481
482 const StaticByteBuffer kSearchExpectedParams(
483 // ServiceSearchPattern
484 0x35,
485 0x03, // Sequence uint8 3 bytes
486 0x19,
487 0x11,
488 0x0B, // UUID (kAudioSink)
489 0xFF,
490 0xFF, // MaxAttributeByteCount (no max)
491 // Attribute ID list
492 0x35,
493 0x06, // Sequence uint8 6 bytes
494 0x09,
495 0x00,
496 0x01, // uint16_t (0x0001 = kServiceClassIdList)
497 0x09,
498 0x00,
499 0x04, // uint16_t (0x0004 = kProtocolDescriptorList)
500 0x00 // No continuation state
501 );
502
503 uint16_t request_tid;
504 bool success = false;
505
506 fake_chan()->SetSendCallback(
507 [&request_tid, &success, &kSearchExpectedParams](auto packet) {
508 // First byte should be type.
509 ASSERT_LE(3u, packet->size());
510 ASSERT_EQ(kServiceSearchAttributeRequest, (*packet)[0]);
511 ASSERT_EQ(kSearchExpectedParams, packet->view(5));
512 request_tid = ((*packet)[1] << 8) != 0 || (*packet)[2];
513 success = true;
514 },
515 dispatcher());
516
517 // Search for all A2DP sinks, get the:
518 // - Service Class ID list
519 // - Descriptor List
520 // - Bluetooth Profile Descriptor List
521 client->ServiceSearchAttributes(
522 {profile::kAudioSink},
523 {kServiceClassIdList, kProtocolDescriptorList},
524 result_cb);
525 RunUntilIdle();
526 EXPECT_TRUE(success);
527
528 // Receive an empty response
529 ServiceSearchAttributeResponse rsp;
530 auto rsp_ptr = rsp.GetPDU(0xFFFF /* Max attribute bytes */,
531 request_tid,
532 kResponseMaxSize,
533 BufferView());
534 fake_chan()->Receive(*rsp_ptr);
535
536 RunUntilIdle();
537
538 EXPECT_EQ(1u, cb_count);
539 }
540
541 // Disconnect early test:
542 // - send request
543 // - remote end disconnects
544 // - result should be called with kLinkDisconnected
TEST_F(ClientTest,Disconnected)545 TEST_F(ClientTest, Disconnected) {
546 auto client = Client::Create(fake_chan()->GetWeakPtr(), dispatcher());
547
548 size_t cb_count = 0;
549 auto result_cb =
550 [&](fit::result<
551 Error<>,
552 std::reference_wrapper<const std::map<AttributeId, DataElement>>>
553 attrs_result) {
554 cb_count++;
555 EXPECT_EQ(Error(HostError::kLinkDisconnected), attrs_result);
556 return true;
557 };
558
559 const StaticByteBuffer kSearchExpectedParams(
560 // ServiceSearchPattern
561 0x35,
562 0x03, // Sequence uint8 3 bytes
563 0x19,
564 0x11,
565 0x0B, // UUID (kAudioSink)
566 0xFF,
567 0xFF, // MaxAttributeByteCount (no max)
568 // Attribute ID list
569 0x35,
570 0x06, // Sequence uint8 6 bytes
571 0x09,
572 0x00,
573 0x01, // uint16_t (0x0001 = kServiceClassIdList)
574 0x09,
575 0x00,
576 0x04, // uint16_t (0x0004 = kProtocolDescriptorList)
577 0x00 // No continuation state
578 );
579
580 bool requested = false;
581
582 fake_chan()->SetSendCallback(
583 [&](auto packet) {
584 // First byte should be type.
585 ASSERT_LE(3u, packet->size());
586 ASSERT_EQ(kServiceSearchAttributeRequest, (*packet)[0]);
587 ASSERT_EQ(kSearchExpectedParams, packet->view(5));
588 requested = true;
589 },
590 dispatcher());
591
592 // Search for all A2DP sinks, get the:
593 // - Service Class ID list
594 // - Descriptor List
595 // - Bluetooth Profile Descriptor List
596 client->ServiceSearchAttributes(
597 {profile::kAudioSink},
598 {kServiceClassIdList, kProtocolDescriptorList},
599 result_cb);
600 RunUntilIdle();
601 EXPECT_TRUE(requested);
602 EXPECT_EQ(0u, cb_count);
603
604 // Remote end closes the channel.
605 fake_chan()->Close();
606
607 RunUntilIdle();
608
609 EXPECT_EQ(1u, cb_count);
610 }
611
612 // Malformed reply test:
613 // - remote end sends wrong packet type in response (dropped)
614 // - remote end sends invalid response
615 // - callback receives no response with a malformed packet error
TEST_F(ClientTest,InvalidResponse)616 TEST_F(ClientTest, InvalidResponse) {
617 auto client = Client::Create(fake_chan()->GetWeakPtr(), dispatcher());
618
619 size_t cb_count = 0;
620 auto result_cb =
621 [&](fit::result<
622 Error<>,
623 std::reference_wrapper<const std::map<AttributeId, DataElement>>>
624 attrs_result) {
625 cb_count++;
626 EXPECT_EQ(Error(HostError::kPacketMalformed), attrs_result);
627 return true;
628 };
629
630 const StaticByteBuffer kSearchExpectedParams(
631 // ServiceSearchPattern
632 0x35,
633 0x03, // Sequence uint8 3 bytes
634 0x19,
635 0x11,
636 0x0B, // UUID (kAudioSink)
637 0xFF,
638 0xFF, // MaxAttributeByteCount (no max)
639 // Attribute ID list
640 0x35,
641 0x06, // Sequence uint8 6 bytes
642 0x09,
643 0x00,
644 0x01, // uint16_t (0x0001 = kServiceClassIdList)
645 0x09,
646 0x00,
647 0x04, // uint16_t (0x0004 = kProtocolDescriptorList)
648 0x00 // No continuation state
649 );
650
651 uint16_t request_tid;
652 bool requested = false;
653
654 fake_chan()->SetSendCallback(
655 [&](auto packet) {
656 // First byte should be type.
657 ASSERT_LE(3u, packet->size());
658 ASSERT_EQ(kServiceSearchAttributeRequest, (*packet)[0]);
659 ASSERT_EQ(kSearchExpectedParams, packet->view(5));
660 request_tid = ((*packet)[1] << 8) != 0 || (*packet)[2];
661 requested = true;
662 },
663 dispatcher());
664
665 // Search for all A2DP sinks, get the:
666 // - Service Class ID list
667 // - Descriptor List
668 // - Bluetooth Profile Descriptor List
669 client->ServiceSearchAttributes(
670 {profile::kAudioSink},
671 {kServiceClassIdList, kProtocolDescriptorList},
672 result_cb);
673 RunUntilIdle();
674 EXPECT_TRUE(requested);
675 EXPECT_EQ(0u, cb_count);
676
677 // Remote end sends some unparsable stuff for the packet.
678 fake_chan()->Receive(StaticByteBuffer(0x07,
679 UpperBits(request_tid),
680 LowerBits(request_tid),
681 0x00,
682 0x03,
683 0x05,
684 0x06,
685 0x07));
686
687 RunUntilIdle();
688
689 EXPECT_EQ(1u, cb_count);
690 }
691
692 // Time out (or possibly dropped packets that were malformed)
TEST_F(ClientTest,Timeout)693 TEST_F(ClientTest, Timeout) {
694 constexpr uint32_t kTimeoutMs = 10000;
695 auto client = Client::Create(fake_chan()->GetWeakPtr(), dispatcher());
696
697 size_t cb_count = 0;
698 auto result_cb =
699 [&](fit::result<
700 Error<>,
701 std::reference_wrapper<const std::map<AttributeId, DataElement>>>
702 attrs_result) {
703 cb_count++;
704 EXPECT_EQ(Error(HostError::kTimedOut), attrs_result);
705 return true;
706 };
707
708 const StaticByteBuffer kSearchExpectedParams(
709 // ServiceSearchPattern
710 0x35,
711 0x03, // Sequence uint8 3 bytes
712 0x19,
713 0x11,
714 0x0B, // UUID (kAudioSink)
715 0xFF,
716 0xFF, // MaxAttributeByteCount (no max)
717 // Attribute ID list
718 0x35,
719 0x06, // Sequence uint8 6 bytes
720 0x09,
721 0x00,
722 0x01, // uint16_t (0x0001 = kServiceClassIdList)
723 0x09,
724 0x00,
725 0x04, // uint16_t (0x0004 = kProtocolDescriptorList)
726 0x00 // No continuation state
727 );
728
729 bool requested = false;
730
731 fake_chan()->SetSendCallback(
732 [&](auto packet) {
733 // First byte should be type.
734 ASSERT_LE(3u, packet->size());
735 ASSERT_EQ(kServiceSearchAttributeRequest, (*packet)[0]);
736 ASSERT_EQ(kSearchExpectedParams, packet->view(5));
737 requested = true;
738 },
739 dispatcher());
740
741 // Search for all A2DP sinks, get the:
742 // - Service Class ID list
743 // - Descriptor List
744 // - Bluetooth Profile Descriptor List
745 client->ServiceSearchAttributes(
746 {profile::kAudioSink},
747 {kServiceClassIdList, kProtocolDescriptorList},
748 result_cb);
749 RunUntilIdle();
750 EXPECT_TRUE(requested);
751 EXPECT_EQ(0u, cb_count);
752
753 // Wait until the timeout happens
754 RunFor(std::chrono::milliseconds(kTimeoutMs + 1));
755
756 EXPECT_EQ(1u, cb_count);
757 }
758
TEST_F(ClientTest,DestroyClientInErrorResultCallbackDoesNotCrash)759 TEST_F(ClientTest, DestroyClientInErrorResultCallbackDoesNotCrash) {
760 constexpr uint32_t kTimeoutMs = 10000;
761 auto client = Client::Create(fake_chan()->GetWeakPtr(), dispatcher());
762
763 size_t cb_count = 0;
764 auto result_cb =
765 [&](fit::result<
766 Error<>,
767 std::reference_wrapper<const std::map<AttributeId, DataElement>>>
768 attrs_result) {
769 cb_count++;
770 EXPECT_TRUE(attrs_result.is_error());
771 client.reset();
772 return true;
773 };
774
775 bool requested = false;
776 fake_chan()->SetSendCallback([&](auto /*packet*/) { requested = true; },
777 dispatcher());
778
779 client->ServiceSearchAttributes(
780 {profile::kAudioSink},
781 {kServiceClassIdList, kProtocolDescriptorList},
782 result_cb);
783 RunUntilIdle();
784 EXPECT_TRUE(requested);
785 EXPECT_EQ(0u, cb_count);
786
787 // Wait until the timeout happens
788 RunFor(std::chrono::milliseconds(kTimeoutMs + 1));
789
790 EXPECT_EQ(1u, cb_count);
791 }
792
TEST_F(ClientTest,DestroyClientInDisconnectedResultCallback)793 TEST_F(ClientTest, DestroyClientInDisconnectedResultCallback) {
794 auto client = Client::Create(fake_chan()->GetWeakPtr(), dispatcher());
795
796 size_t cb_count = 0;
797 auto result_cb =
798 [&](fit::result<
799 Error<>,
800 std::reference_wrapper<const std::map<AttributeId, DataElement>>>
801 attrs_result) {
802 cb_count++;
803 EXPECT_EQ(Error(HostError::kLinkDisconnected), attrs_result);
804 client.reset();
805 return true;
806 };
807
808 bool requested = false;
809 fake_chan()->SetSendCallback([&](auto /*packet*/) { requested = true; },
810 dispatcher());
811
812 client->ServiceSearchAttributes(
813 {profile::kAudioSink},
814 {kServiceClassIdList, kProtocolDescriptorList},
815 result_cb);
816 RunUntilIdle();
817 EXPECT_TRUE(requested);
818 EXPECT_EQ(0u, cb_count);
819
820 // Remote end closes the channel.
821 fake_chan()->Close();
822
823 RunUntilIdle();
824
825 EXPECT_EQ(1u, cb_count);
826 }
827
828 } // namespace
829 } // namespace bt::sdp
830