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