• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/service_discoverer.h"
16 
17 #include "pw_bluetooth_sapphire/internal/host/l2cap/fake_channel.h"
18 #include "pw_bluetooth_sapphire/internal/host/l2cap/fake_channel_test.h"
19 #include "pw_bluetooth_sapphire/internal/host/testing/gtest_helpers.h"
20 
21 namespace bt::sdp {
22 namespace {
23 
24 constexpr PeerId kDeviceOne(1), kDeviceTwo(2), kDeviceThree(3);
25 
26 class FakeClient : public Client {
27  public:
28   // |destroyed_cb| will be called when this client is destroyed, with true if
29   // there are outstanding expected requests.
FakeClient(fit::closure destroyed_cb)30   FakeClient(fit::closure destroyed_cb)
31       : destroyed_cb_(std::move(destroyed_cb)) {}
32 
~FakeClient()33   virtual ~FakeClient() override { destroyed_cb_(); }
34 
ServiceSearchAttributes(std::unordered_set<UUID> search_pattern,const std::unordered_set<AttributeId> & req_attributes,SearchResultFunction result_cb)35   virtual void ServiceSearchAttributes(
36       std::unordered_set<UUID> search_pattern,
37       const std::unordered_set<AttributeId>& req_attributes,
38       SearchResultFunction result_cb) override {
39     if (!service_search_attributes_cb_) {
40       FAIL() << "ServiceSearchAttributes with no callback set";
41     }
42 
43     service_search_attributes_cb_(std::move(search_pattern),
44                                   std::move(req_attributes),
45                                   std::move(result_cb));
46   }
47 
48   using ServiceSearchAttributesCallback =
49       fit::function<void(std::unordered_set<UUID>,
50                          std::unordered_set<AttributeId>,
51                          SearchResultFunction)>;
SetServiceSearchAttributesCallback(ServiceSearchAttributesCallback callback)52   void SetServiceSearchAttributesCallback(
53       ServiceSearchAttributesCallback callback) {
54     service_search_attributes_cb_ = std::move(callback);
55   }
56 
57  private:
58   ServiceSearchAttributesCallback service_search_attributes_cb_;
59   fit::closure destroyed_cb_;
60 };
61 
62 class ServiceDiscovererTest : public pw::async::test::FakeDispatcherFixture {
63  public:
64   ServiceDiscovererTest() = default;
65   ~ServiceDiscovererTest() = default;
66 
heap_dispatcher()67   pw::async::HeapDispatcher& heap_dispatcher() { return heap_dispatcher_; }
68 
69  protected:
SetUp()70   void SetUp() override {
71     clients_created_ = 0;
72     clients_destroyed_ = 0;
73   }
74 
TearDown()75   void TearDown() override {}
76 
77   // Connect an SDP client to a fake channel, which is available in channel_
GetFakeClient()78   std::unique_ptr<FakeClient> GetFakeClient() {
79     SCOPED_TRACE("Connect Client");
80     clients_created_++;
81     return std::make_unique<FakeClient>([this]() { clients_destroyed_++; });
82   }
83 
clients_created() const84   size_t clients_created() const { return clients_created_; }
clients_destroyed() const85   size_t clients_destroyed() const { return clients_destroyed_; }
86 
87  private:
88   size_t clients_created_, clients_destroyed_;
89   pw::async::HeapDispatcher heap_dispatcher_{dispatcher()};
90 };
91 
92 // When there are no searches registered, it just disconnects the client.
TEST_F(ServiceDiscovererTest,NoSearches)93 TEST_F(ServiceDiscovererTest, NoSearches) {
94   ServiceDiscoverer discoverer;
95   EXPECT_EQ(0u, discoverer.search_count());
96 
97   discoverer.StartServiceDiscovery(kDeviceOne, GetFakeClient());
98 
99   RETURN_IF_FATAL(RunUntilIdle());
100 
101   EXPECT_EQ(1u, clients_destroyed());
102 }
103 
104 // Happy path test with one registered service and no results.
TEST_F(ServiceDiscovererTest,NoResults)105 TEST_F(ServiceDiscovererTest, NoResults) {
106   ServiceDiscoverer discoverer;
107 
108   size_t cb_count = 0;
109 
110   auto result_cb = [&cb_count](auto, const auto&) { cb_count++; };
111 
112   ServiceDiscoverer::SearchId id = discoverer.AddSearch(
113       profile::kSerialPort,
114       {kServiceId, kProtocolDescriptorList, kBluetoothProfileDescriptorList},
115       std::move(result_cb));
116   ASSERT_NE(ServiceDiscoverer::kInvalidSearchId, id);
117   EXPECT_EQ(1u, discoverer.search_count());
118 
119   auto client = GetFakeClient();
120 
121   std::vector<std::unordered_set<UUID>> searches;
122 
123   client->SetServiceSearchAttributesCallback(
124       [dispatcher = heap_dispatcher(), &searches](
125           auto pattern, auto /*attributes*/, auto callback) mutable {
126         searches.emplace_back(std::move(pattern));
127         (void)dispatcher.Post(
128             [cb = std::move(callback)](pw::async::Context /*ctx*/,
129                                        pw::Status status) {
130               if (status.ok()) {
131                 cb(fit::error(Error(HostError::kNotFound)));
132               }
133             });
134       });
135 
136   discoverer.StartServiceDiscovery(kDeviceOne, std::move(client));
137 
138   RETURN_IF_FATAL(RunUntilIdle());
139 
140   EXPECT_EQ(1u, searches.size());
141   ASSERT_EQ(0u, cb_count);
142   ASSERT_EQ(1u, clients_destroyed());
143 }
144 
TEST_F(ServiceDiscovererTest,SynchronousErrorResult)145 TEST_F(ServiceDiscovererTest, SynchronousErrorResult) {
146   ServiceDiscoverer discoverer;
147 
148   size_t cb_count = 0;
149   auto result_cb = [&cb_count](auto, const auto&) { cb_count++; };
150   ServiceDiscoverer::SearchId id = discoverer.AddSearch(
151       profile::kSerialPort,
152       {kServiceId, kProtocolDescriptorList, kBluetoothProfileDescriptorList},
153       std::move(result_cb));
154   ASSERT_NE(ServiceDiscoverer::kInvalidSearchId, id);
155   EXPECT_EQ(1u, discoverer.search_count());
156 
157   auto client = GetFakeClient();
158   std::vector<std::unordered_set<UUID>> searches;
159   client->SetServiceSearchAttributesCallback(
160       [&searches](auto pattern, auto /*attributes*/, auto callback) {
161         searches.emplace_back(std::move(pattern));
162         callback(fit::error(Error(HostError::kLinkDisconnected)));
163       });
164 
165   discoverer.StartServiceDiscovery(kDeviceOne, std::move(client));
166   RETURN_IF_FATAL(RunUntilIdle());
167   EXPECT_EQ(1u, searches.size());
168   ASSERT_EQ(0u, cb_count);
169   ASSERT_EQ(1u, clients_destroyed());
170 }
171 
172 // Happy path test with two registered searches.
173 // No results, then two results.
174 // Unregister one search.
175 // Then one result are searched for and two returned.
TEST_F(ServiceDiscovererTest,SomeResults)176 TEST_F(ServiceDiscovererTest, SomeResults) {
177   ServiceDiscoverer discoverer;
178 
179   std::vector<std::pair<PeerId, std::map<AttributeId, DataElement>>> results;
180 
181   ServiceDiscoverer::ResultCallback result_cb =
182       [&results](PeerId id, const auto& attributes) {
183         std::map<AttributeId, DataElement> attributes_clone;
184         for (const auto& it : attributes) {
185           auto [inserted_it, added] =
186               attributes_clone.try_emplace(it.first, it.second.Clone());
187           ASSERT_TRUE(added);
188         }
189         results.emplace_back(id, std::move(attributes_clone));
190       };
191 
192   ServiceDiscoverer::SearchId one = discoverer.AddSearch(
193       profile::kSerialPort,
194       {kServiceId, kProtocolDescriptorList, kBluetoothProfileDescriptorList},
195       result_cb.share());
196   ASSERT_NE(ServiceDiscoverer::kInvalidSearchId, one);
197   EXPECT_EQ(1u, discoverer.search_count());
198   ServiceDiscoverer::SearchId two = discoverer.AddSearch(
199       profile::kAudioSink,
200       {kProtocolDescriptorList, kBluetoothProfileDescriptorList},
201       result_cb.share());
202   ASSERT_NE(ServiceDiscoverer::kInvalidSearchId, two);
203   EXPECT_EQ(2u, discoverer.search_count());
204 
205   auto client = GetFakeClient();
206 
207   std::vector<std::unordered_set<UUID>> searches;
208 
209   client->SetServiceSearchAttributesCallback(
210       [dispatcher = heap_dispatcher(), &searches](
211           auto pattern, auto /*attributes*/, auto callback) mutable {
212         searches.emplace_back(std::move(pattern));
213         (void)dispatcher.Post(
214             [cb = std::move(callback)](pw::async::Context /*ctx*/,
215                                        pw::Status status) {
216               if (status.ok()) {
217                 cb(fit::error(Error(HostError::kNotFound)));
218               }
219             });
220       });
221 
222   discoverer.StartServiceDiscovery(kDeviceOne, std::move(client));
223 
224   RETURN_IF_FATAL(RunUntilIdle());
225 
226   EXPECT_EQ(2u, searches.size());
227   ASSERT_EQ(0u, results.size());
228   ASSERT_EQ(1u, clients_destroyed());
229 
230   client = GetFakeClient();
231 
232   searches.clear();
233 
234   client->SetServiceSearchAttributesCallback(
235       [cb_dispatcher = heap_dispatcher(), &searches](
236           auto pattern, auto /*attributes*/, auto callback) mutable {
237         searches.emplace_back(pattern);
238         if (pattern.count(profile::kSerialPort)) {
239           (void)cb_dispatcher.Post([cb = std::move(callback)](
240                                        pw::async::Context /*ctx*/,
241                                        pw::Status status) {
242             if (!status.ok()) {
243               return;
244             }
245             ServiceSearchAttributeResponse rsp;
246             rsp.SetAttribute(0, kServiceId, DataElement(UUID(uint16_t{1})));
247             // This would normally be a element list. uint32_t for Testing.
248             rsp.SetAttribute(
249                 0, kBluetoothProfileDescriptorList, DataElement(uint32_t{1}));
250 
251             if (!cb(fit::ok(std::cref(rsp.attributes(0))))) {
252               return;
253             }
254             cb(fit::error(Error(HostError::kNotFound)));
255           });
256         } else if (pattern.count(profile::kAudioSink)) {
257           (void)cb_dispatcher.Post([cb = std::move(callback)](
258                                        pw::async::Context /*ctx*/,
259                                        pw::Status status) {
260             if (!status.ok()) {
261               return;
262             }
263             ServiceSearchAttributeResponse rsp;
264             // This would normally be a element list. uint32_t for Testing.
265             rsp.SetAttribute(
266                 0, kBluetoothProfileDescriptorList, DataElement(uint32_t{1}));
267 
268             if (!cb(fit::ok(std::cref(rsp.attributes(0))))) {
269               return;
270             }
271             cb(fit::error(Error(HostError::kNotFound)));
272           });
273         } else {
274           std::cerr << "Searched for " << pattern.size() << std::endl;
275           for (auto it : pattern) {
276             std::cerr << it.ToString() << std::endl;
277           }
278           FAIL() << "Unexpected search called";
279         }
280       });
281 
282   discoverer.StartServiceDiscovery(kDeviceTwo, std::move(client));
283 
284   RETURN_IF_FATAL(RunUntilIdle());
285 
286   EXPECT_EQ(2u, searches.size());
287   ASSERT_EQ(2u, results.size());
288   ASSERT_EQ(2u, clients_destroyed());
289 
290   results.clear();
291   searches.clear();
292 
293   ASSERT_TRUE(discoverer.RemoveSearch(one));
294   ASSERT_FALSE(discoverer.RemoveSearch(one));
295   EXPECT_EQ(1u, discoverer.search_count());
296 
297   client = GetFakeClient();
298 
299   client->SetServiceSearchAttributesCallback(
300       [cb_dispatcher = heap_dispatcher(), &searches](
301           auto pattern, auto /*attributes*/, auto callback) mutable {
302         searches.emplace_back(pattern);
303         if (pattern.count(profile::kAudioSink)) {
304           (void)cb_dispatcher.Post([cb = std::move(callback)](
305                                        pw::async::Context /*ctx*/,
306                                        pw::Status status) {
307             if (!status.ok()) {
308               return;
309             }
310             ServiceSearchAttributeResponse rsp;
311             // This would normally be a element list. uint32_t for Testing.
312             rsp.SetAttribute(
313                 0, kBluetoothProfileDescriptorList, DataElement(uint32_t{1}));
314             rsp.SetAttribute(
315                 1, kProtocolDescriptorList, DataElement(uint32_t{2}));
316 
317             if (!cb(fit::ok(std::cref(rsp.attributes(0))))) {
318               return;
319             }
320             if (!cb(fit::ok(std::cref(rsp.attributes(1))))) {
321               return;
322             }
323             cb(fit::error(Error(HostError::kNotFound)));
324           });
325         } else {
326           std::cerr << "Searched for " << pattern.size() << std::endl;
327           for (auto it : pattern) {
328             std::cerr << it.ToString() << std::endl;
329           }
330           FAIL() << "Unexpected search called";
331         }
332       });
333 
334   discoverer.StartServiceDiscovery(kDeviceThree, std::move(client));
335 
336   RETURN_IF_FATAL(RunUntilIdle());
337 
338   EXPECT_EQ(1u, searches.size());
339   ASSERT_EQ(2u, results.size());
340   ASSERT_EQ(3u, clients_destroyed());
341 }
342 
343 // Single search behavior when there is a discovery not running (2 clients
344 // simultaneously)
TEST_F(ServiceDiscovererTest,SingleSearchDifferentPeers)345 TEST_F(ServiceDiscovererTest, SingleSearchDifferentPeers) {
346   ServiceDiscoverer discoverer;
347 
348   size_t cb_count = 0;
349 
350   auto result_cb = [&cb_count](auto, const auto&) { cb_count++; };
351 
352   ServiceDiscoverer::SearchId search_id = discoverer.AddSearch(
353       profile::kSerialPort, {kServiceId}, std::move(result_cb));
354   ASSERT_NE(ServiceDiscoverer::kInvalidSearchId, search_id);
355   EXPECT_EQ(1u, discoverer.search_count());
356 
357   auto client = GetFakeClient();
358   auto client2 = GetFakeClient();
359 
360   std::vector<std::unordered_set<UUID>> searches;
361 
362   auto search_attributes_cb =
363       [cb_dispatcher = heap_dispatcher(), &searches](
364           auto pattern, auto /*attributes*/, auto callback) mutable {
365         searches.emplace_back(pattern);
366         if (pattern.count(profile::kSerialPort)) {
367           (void)cb_dispatcher.Post(
368               [cb = std::move(callback)](pw::async::Context /*ctx*/,
369                                          pw::Status status) {
370                 if (!status.ok()) {
371                   return;
372                 }
373                 ServiceSearchAttributeResponse rsp;
374                 rsp.SetAttribute(0, kServiceId, DataElement(UUID(uint16_t{1})));
375                 if (!cb(fit::ok(std::cref(rsp.attributes(0))))) {
376                   return;
377                 }
378                 cb(fit::error(Error(HostError::kNotFound)));
379               });
380         } else {
381           std::cerr << "Searched for " << pattern.size() << std::endl;
382           for (auto it : pattern) {
383             std::cerr << it.ToString() << std::endl;
384           }
385           FAIL() << "Unexpected search called";
386         }
387       };
388 
389   client->SetServiceSearchAttributesCallback(search_attributes_cb);
390   client2->SetServiceSearchAttributesCallback(search_attributes_cb);
391 
392   discoverer.SingleSearch(search_id, PeerId(1), std::move(client));
393   discoverer.SingleSearch(search_id, PeerId(2), std::move(client2));
394 
395   RETURN_IF_FATAL(RunUntilIdle());
396 
397   EXPECT_EQ(2u, searches.size());
398   ASSERT_EQ(2u, cb_count);
399   ASSERT_EQ(2u, clients_destroyed());
400 }
401 
402 // Single search behavior when there is a discovery running (2 searches
403 // simultaneously)
TEST_F(ServiceDiscovererTest,SingleSearchSamePeer)404 TEST_F(ServiceDiscovererTest, SingleSearchSamePeer) {
405   ServiceDiscoverer discoverer;
406 
407   size_t cb_count = 0;
408 
409   auto result_cb = [&cb_count](auto, const auto&) { cb_count++; };
410 
411   ServiceDiscoverer::SearchId search_id = discoverer.AddSearch(
412       profile::kSerialPort, {kServiceId}, std::move(result_cb));
413   ASSERT_NE(ServiceDiscoverer::kInvalidSearchId, search_id);
414   EXPECT_EQ(1u, discoverer.search_count());
415 
416   auto client = GetFakeClient();
417   std::vector<std::unordered_set<UUID>> searches;
418 
419   auto search_attributes_cb =
420       [cb_dispatcher = heap_dispatcher(), &searches](
421           auto pattern, auto /*attributes*/, auto callback) mutable {
422         searches.emplace_back(pattern);
423         if (pattern.count(profile::kSerialPort)) {
424           (void)cb_dispatcher.Post(
425               [cb = std::move(callback)](pw::async::Context /*ctx*/,
426                                          pw::Status status) {
427                 if (!status.ok()) {
428                   return;
429                 }
430                 ServiceSearchAttributeResponse rsp;
431                 rsp.SetAttribute(0, kServiceId, DataElement(UUID(uint16_t{1})));
432                 if (!cb(fit::ok(std::cref(rsp.attributes(0))))) {
433                   return;
434                 }
435                 cb(fit::error(Error(HostError::kNotFound)));
436               });
437         } else {
438           std::cerr << "Searched for " << pattern.size() << std::endl;
439           for (auto it : pattern) {
440             std::cerr << it.ToString() << std::endl;
441           }
442           FAIL() << "Unexpected search called";
443         }
444       };
445 
446   client->SetServiceSearchAttributesCallback(search_attributes_cb);
447 
448   discoverer.SingleSearch(search_id, PeerId(1), std::move(client));
449   discoverer.SingleSearch(search_id, PeerId(1), nullptr);
450 
451   RETURN_IF_FATAL(RunUntilIdle());
452 
453   EXPECT_EQ(2u, searches.size());
454   ASSERT_EQ(2u, cb_count);
455   ASSERT_EQ(1u, clients_destroyed());
456 }
457 
458 // Disconnected on the other end before the discovery completes
TEST_F(ServiceDiscovererTest,Disconnected)459 TEST_F(ServiceDiscovererTest, Disconnected) {
460   ServiceDiscoverer discoverer;
461 
462   size_t cb_count = 0;
463 
464   auto result_cb = [&cb_count](auto, const auto&) { cb_count++; };
465 
466   ServiceDiscoverer::SearchId id = discoverer.AddSearch(
467       profile::kSerialPort,
468       {kServiceId, kProtocolDescriptorList, kBluetoothProfileDescriptorList},
469       std::move(result_cb));
470   ASSERT_NE(ServiceDiscoverer::kInvalidSearchId, id);
471   EXPECT_EQ(1u, discoverer.search_count());
472 
473   auto client = GetFakeClient();
474 
475   std::vector<std::unordered_set<UUID>> searches;
476 
477   client->SetServiceSearchAttributesCallback(
478       [cb_dispatcher = heap_dispatcher(), &searches](
479           auto pattern, auto /*attributes*/, auto callback) mutable {
480         searches.emplace_back(pattern);
481         if (pattern.count(profile::kSerialPort)) {
482           (void)cb_dispatcher.Post(
483               [cb = std::move(callback)](pw::async::Context /*ctx*/,
484                                          pw::Status status) {
485                 if (status.ok()) {
486                   cb(fit::error(Error(HostError::kLinkDisconnected)));
487                 }
488               });
489         } else {
490           std::cerr << "Searched for " << pattern.size() << std::endl;
491           for (auto it : pattern) {
492             std::cerr << it.ToString() << std::endl;
493           }
494           FAIL() << "Unexpected search called";
495         }
496       });
497 
498   discoverer.StartServiceDiscovery(kDeviceOne, std::move(client));
499 
500   RETURN_IF_FATAL(RunUntilIdle());
501 
502   EXPECT_EQ(1u, searches.size());
503   ASSERT_EQ(0u, cb_count);
504   ASSERT_EQ(1u, clients_destroyed());
505 }
506 
507 // Unregistered Search when partway through the discovery
TEST_F(ServiceDiscovererTest,UnregisterInProgress)508 TEST_F(ServiceDiscovererTest, UnregisterInProgress) {
509   ServiceDiscoverer discoverer;
510 
511   std::optional<std::pair<PeerId, std::map<AttributeId, DataElement>>> result;
512 
513   ServiceDiscoverer::SearchId id = ServiceDiscoverer::kInvalidSearchId;
514 
515   ServiceDiscoverer::ResultCallback one_result_cb =
516       [&discoverer, &result, &id](auto peer_id, const auto& attributes) {
517         // We should only be called once
518         ASSERT_TRUE(!result.has_value());
519         std::map<AttributeId, DataElement> attributes_clone;
520         for (const auto& it : attributes) {
521           auto [inserted_it, added] =
522               attributes_clone.try_emplace(it.first, it.second.Clone());
523           ASSERT_TRUE(added);
524         }
525         result.emplace(peer_id, std::move(attributes_clone));
526         discoverer.RemoveSearch(id);
527       };
528 
529   id = discoverer.AddSearch(
530       profile::kAudioSink,
531       {kProtocolDescriptorList, kBluetoothProfileDescriptorList},
532       one_result_cb.share());
533   ASSERT_NE(ServiceDiscoverer::kInvalidSearchId, id);
534   EXPECT_EQ(1u, discoverer.search_count());
535 
536   auto client = GetFakeClient();
537 
538   std::vector<std::unordered_set<UUID>> searches;
539 
540   client->SetServiceSearchAttributesCallback(
541       [cb_dispatcher = heap_dispatcher(), &searches](
542           auto pattern, auto /*attributes*/, auto callback) mutable {
543         searches.emplace_back(pattern);
544         if (pattern.count(profile::kAudioSink)) {
545           (void)cb_dispatcher.Post([cb = std::move(callback)](
546                                        pw::async::Context /*ctx*/,
547                                        pw::Status status) {
548             if (!status.ok()) {
549               return;
550             }
551             ServiceSearchAttributeResponse rsp;
552             // This would normally be a element list. uint32_t for Testing.
553             rsp.SetAttribute(
554                 0, kBluetoothProfileDescriptorList, DataElement(uint32_t{1}));
555             rsp.SetAttribute(
556                 1, kProtocolDescriptorList, DataElement(uint32_t{2}));
557 
558             if (!cb(fit::ok(std::cref(rsp.attributes(0))))) {
559               return;
560             }
561             if (!cb(fit::ok(std::cref(rsp.attributes(1))))) {
562               return;
563             }
564             cb(fit::error(Error(HostError::kNotFound)));
565           });
566         } else {
567           std::cerr << "Searched for " << pattern.size() << std::endl;
568           for (auto it : pattern) {
569             std::cerr << it.ToString() << std::endl;
570           }
571           FAIL() << "Unexpected search called";
572         }
573       });
574 
575   discoverer.StartServiceDiscovery(kDeviceOne, std::move(client));
576 
577   RETURN_IF_FATAL(RunUntilIdle());
578 
579   EXPECT_EQ(1u, searches.size());
580 
581   ASSERT_TRUE(result.has_value());
582   ASSERT_EQ(kDeviceOne, result->first);
583   auto value = result->second[kBluetoothProfileDescriptorList].Get<uint32_t>();
584   ASSERT_TRUE(value);
585   ASSERT_EQ(1u, *value);
586 
587   ASSERT_EQ(1u, clients_destroyed());
588   EXPECT_EQ(0u, discoverer.search_count());
589 }
590 
591 }  // namespace
592 }  // namespace bt::sdp
593