xref: /aosp_15_r20/external/ot-br-posix/tests/gtest/test_mdns_subscribe.cpp (revision 4a64e381480ef79f0532b2421e44e6ee336b8e0d)
1 /*
2  *    Copyright (c) 2023, The OpenThread Authors.
3  *    All rights reserved.
4  *
5  *    Redistribution and use in source and binary forms, with or without
6  *    modification, are permitted provided that the following conditions are met:
7  *    1. Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *    2. Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *    3. Neither the name of the copyright holder nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *    POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include <gtest/gtest.h>
30 #include <limits.h>
31 #include <netinet/in.h>
32 #include <signal.h>
33 
34 #include <set>
35 #include <vector>
36 
37 #include "common/mainloop.hpp"
38 #include "common/mainloop_manager.hpp"
39 #include "mdns/mdns.hpp"
40 
41 using namespace otbr;
42 using namespace otbr::Mdns;
43 
44 static constexpr int kTimeoutSeconds = 3;
45 
RunMainloopUntilTimeout(int aSeconds)46 int RunMainloopUntilTimeout(int aSeconds)
47 {
48     using namespace otbr;
49 
50     int  rval      = 0;
51     auto beginTime = Clock::now();
52 
53     while (true)
54     {
55         MainloopContext mainloop;
56 
57         mainloop.mMaxFd   = -1;
58         mainloop.mTimeout = {1, 0};
59         FD_ZERO(&mainloop.mReadFdSet);
60         FD_ZERO(&mainloop.mWriteFdSet);
61         FD_ZERO(&mainloop.mErrorFdSet);
62 
63         MainloopManager::GetInstance().Update(mainloop);
64         rval = select(mainloop.mMaxFd + 1, &mainloop.mReadFdSet, &mainloop.mWriteFdSet, &mainloop.mErrorFdSet,
65                       (mainloop.mTimeout.tv_sec == INT_MAX ? nullptr : &mainloop.mTimeout));
66 
67         if (rval < 0)
68         {
69             perror("select");
70             break;
71         }
72 
73         MainloopManager::GetInstance().Process(mainloop);
74 
75         if (Clock::now() - beginTime >= std::chrono::seconds(aSeconds))
76         {
77             break;
78         }
79     }
80 
81     return rval;
82 }
83 
AsSet(const Container & aContainer)84 template <typename Container> std::set<typename Container::value_type> AsSet(const Container &aContainer)
85 {
86     return std::set<typename Container::value_type>(aContainer.begin(), aContainer.end());
87 }
88 
NoOpCallback(void)89 Publisher::ResultCallback NoOpCallback(void)
90 {
91     return [](otbrError aError) { OTBR_UNUSED_VARIABLE(aError); };
92 }
93 
AsTxtMap(const Publisher::TxtData & aTxtData)94 std::map<std::string, std::vector<uint8_t>> AsTxtMap(const Publisher::TxtData &aTxtData)
95 {
96     Publisher::TxtList                          txtList;
97     std::map<std::string, std::vector<uint8_t>> map;
98 
99     Publisher::DecodeTxtData(txtList, aTxtData.data(), aTxtData.size());
100     for (const auto &entry : txtList)
101     {
102         map[entry.mKey] = entry.mValue;
103     }
104 
105     return map;
106 }
107 
108 Publisher::TxtList sTxtList1{{"a", "1"}, {"b", "2"}};
109 Publisher::TxtData sTxtData1;
110 Ip6Address         sAddr1;
111 Ip6Address         sAddr2;
112 Ip6Address         sAddr3;
113 Ip6Address         sAddr4;
114 
115 class MdnsTest : public ::testing::Test
116 {
117 protected:
MdnsTest()118     MdnsTest()
119     {
120         SuccessOrDie(Ip6Address::FromString("2002::1", sAddr1), "");
121         SuccessOrDie(Ip6Address::FromString("2002::2", sAddr2), "");
122         SuccessOrDie(Ip6Address::FromString("2002::3", sAddr3), "");
123         SuccessOrDie(Ip6Address::FromString("2002::4", sAddr4), "");
124         SuccessOrDie(Publisher::EncodeTxtData(sTxtList1, sTxtData1), "");
125     }
126 };
127 
CreatePublisher(void)128 std::unique_ptr<Publisher> CreatePublisher(void)
129 {
130     bool                       ready = false;
131     std::unique_ptr<Publisher> publisher{Publisher::Create([&ready](Mdns::Publisher::State aState) {
132         if (aState == Publisher::State::kReady)
133         {
134             ready = true;
135         }
136     })};
137 
138     publisher->Start();
139     RunMainloopUntilTimeout(kTimeoutSeconds);
140     EXPECT_TRUE(ready);
141 
142     return publisher;
143 }
144 
CheckServiceInstance(const Publisher::DiscoveredInstanceInfo aInstanceInfo,bool aRemoved,const std::string & aHostName,const std::vector<Ip6Address> & aAddresses,const std::string & aServiceName,uint16_t aPort,const Publisher::TxtData aTxtData)145 void CheckServiceInstance(const Publisher::DiscoveredInstanceInfo aInstanceInfo,
146                           bool                                    aRemoved,
147                           const std::string                      &aHostName,
148                           const std::vector<Ip6Address>          &aAddresses,
149                           const std::string                      &aServiceName,
150                           uint16_t                                aPort,
151                           const Publisher::TxtData                aTxtData)
152 {
153     EXPECT_EQ(aRemoved, aInstanceInfo.mRemoved);
154     EXPECT_EQ(aServiceName, aInstanceInfo.mName);
155     if (!aRemoved)
156     {
157         EXPECT_EQ(aHostName, aInstanceInfo.mHostName);
158         EXPECT_EQ(AsSet(aAddresses), AsSet(aInstanceInfo.mAddresses));
159         EXPECT_EQ(aPort, aInstanceInfo.mPort);
160         EXPECT_TRUE(AsTxtMap(aTxtData) == AsTxtMap(aInstanceInfo.mTxtData));
161     }
162 }
163 
CheckServiceInstanceAdded(const Publisher::DiscoveredInstanceInfo aInstanceInfo,const std::string & aHostName,const std::vector<Ip6Address> & aAddresses,const std::string & aServiceName,uint16_t aPort,const Publisher::TxtData aTxtData)164 void CheckServiceInstanceAdded(const Publisher::DiscoveredInstanceInfo aInstanceInfo,
165                                const std::string                      &aHostName,
166                                const std::vector<Ip6Address>          &aAddresses,
167                                const std::string                      &aServiceName,
168                                uint16_t                                aPort,
169                                const Publisher::TxtData                aTxtData)
170 {
171     CheckServiceInstance(aInstanceInfo, false, aHostName, aAddresses, aServiceName, aPort, aTxtData);
172 }
173 
CheckServiceInstanceRemoved(const Publisher::DiscoveredInstanceInfo aInstanceInfo,const std::string & aServiceName)174 void CheckServiceInstanceRemoved(const Publisher::DiscoveredInstanceInfo aInstanceInfo, const std::string &aServiceName)
175 {
176     CheckServiceInstance(aInstanceInfo, true, "", {}, aServiceName, 0, {});
177 }
178 
CheckHostAdded(const Publisher::DiscoveredHostInfo & aHostInfo,const std::string & aHostName,const std::vector<Ip6Address> & aAddresses)179 void CheckHostAdded(const Publisher::DiscoveredHostInfo &aHostInfo,
180                     const std::string                   &aHostName,
181                     const std::vector<Ip6Address>       &aAddresses)
182 {
183     EXPECT_EQ(aHostName, aHostInfo.mHostName);
184     EXPECT_EQ(AsSet(aAddresses), AsSet(aHostInfo.mAddresses));
185 }
186 
TEST_F(MdnsTest,SubscribeHost)187 TEST_F(MdnsTest, SubscribeHost)
188 {
189     std::unique_ptr<Publisher>    pub = CreatePublisher();
190     std::string                   lastHostName;
191     Publisher::DiscoveredHostInfo lastHostInfo{};
192 
193     auto clearLastHost = [&lastHostName, &lastHostInfo] {
194         lastHostName = "";
195         lastHostInfo = {};
196     };
197 
198     pub->AddSubscriptionCallbacks(
199         nullptr,
200         [&lastHostName, &lastHostInfo](const std::string &aHostName, const Publisher::DiscoveredHostInfo &aHostInfo) {
201             lastHostName = aHostName;
202             lastHostInfo = aHostInfo;
203         });
204     pub->SubscribeHost("host1");
205 
206     pub->PublishHost("host1", Publisher::AddressList{sAddr1, sAddr2}, NoOpCallback());
207     pub->PublishService("host1", "service1", "_test._tcp", Publisher::SubTypeList{"_sub1", "_sub2"}, 11111, sTxtData1,
208                         NoOpCallback());
209     RunMainloopUntilTimeout(kTimeoutSeconds);
210     EXPECT_EQ("host1", lastHostName);
211     CheckHostAdded(lastHostInfo, "host1.local.", {sAddr1, sAddr2});
212     clearLastHost();
213 
214     pub->PublishService("host1", "service2", "_test._tcp", {}, 22222, {}, NoOpCallback());
215     RunMainloopUntilTimeout(kTimeoutSeconds);
216     EXPECT_EQ("", lastHostName);
217     clearLastHost();
218 
219     pub->PublishHost("host2", Publisher::AddressList{sAddr3}, NoOpCallback());
220     pub->PublishService("host2", "service3", "_test._tcp", {}, 33333, {}, NoOpCallback());
221     RunMainloopUntilTimeout(kTimeoutSeconds);
222     EXPECT_EQ("", lastHostName);
223     clearLastHost();
224 }
225 
TEST_F(MdnsTest,SubscribeServiceInstance)226 TEST_F(MdnsTest, SubscribeServiceInstance)
227 {
228     std::unique_ptr<Publisher>        pub = CreatePublisher();
229     std::string                       lastServiceType;
230     Publisher::DiscoveredInstanceInfo lastInstanceInfo{};
231 
232     auto clearLastInstance = [&lastServiceType, &lastInstanceInfo] {
233         lastServiceType  = "";
234         lastInstanceInfo = {};
235     };
236 
237     pub->AddSubscriptionCallbacks(
238         [&lastServiceType, &lastInstanceInfo](const std::string                &aType,
239                                               Publisher::DiscoveredInstanceInfo aInstanceInfo) {
240             lastServiceType  = aType;
241             lastInstanceInfo = aInstanceInfo;
242         },
243         nullptr);
244     pub->SubscribeService("_test._tcp", "service1");
245 
246     pub->PublishHost("host1", Publisher::AddressList{sAddr1, sAddr2}, NoOpCallback());
247     pub->PublishService("host1", "service1", "_test._tcp", Publisher::SubTypeList{"_sub1", "_sub2"}, 11111, sTxtData1,
248                         NoOpCallback());
249     RunMainloopUntilTimeout(kTimeoutSeconds);
250     EXPECT_EQ("_test._tcp", lastServiceType);
251     CheckServiceInstanceAdded(lastInstanceInfo, "host1.local.", {sAddr1, sAddr2}, "service1", 11111, sTxtData1);
252     clearLastInstance();
253 
254     pub->PublishService("host1", "service2", "_test._tcp", {}, 22222, {}, NoOpCallback());
255     RunMainloopUntilTimeout(kTimeoutSeconds);
256     EXPECT_EQ("", lastServiceType);
257     clearLastInstance();
258 
259     pub->PublishHost("host2", Publisher::AddressList{sAddr3}, NoOpCallback());
260     pub->PublishService("host2", "service3", "_test._tcp", {}, 33333, {}, NoOpCallback());
261     RunMainloopUntilTimeout(kTimeoutSeconds);
262     EXPECT_EQ("", lastServiceType);
263     clearLastInstance();
264 }
265 
TEST_F(MdnsTest,SubscribeServiceType)266 TEST_F(MdnsTest, SubscribeServiceType)
267 {
268     std::unique_ptr<Publisher>        pub = CreatePublisher();
269     std::string                       lastServiceType;
270     Publisher::DiscoveredInstanceInfo lastInstanceInfo{};
271 
272     auto clearLastInstance = [&lastServiceType, &lastInstanceInfo] {
273         lastServiceType  = "";
274         lastInstanceInfo = {};
275     };
276 
277     pub->AddSubscriptionCallbacks(
278         [&lastServiceType, &lastInstanceInfo](const std::string                &aType,
279                                               Publisher::DiscoveredInstanceInfo aInstanceInfo) {
280             lastServiceType  = aType;
281             lastInstanceInfo = aInstanceInfo;
282         },
283         nullptr);
284     pub->SubscribeService("_test._tcp", "");
285 
286     pub->PublishHost("host1", Publisher::AddressList{sAddr1, sAddr2}, NoOpCallback());
287     pub->PublishService("host1", "service1", "_test._tcp", Publisher::SubTypeList{"_sub1", "_sub2"}, 11111, sTxtData1,
288                         NoOpCallback());
289     RunMainloopUntilTimeout(kTimeoutSeconds);
290     EXPECT_EQ("_test._tcp", lastServiceType);
291     CheckServiceInstanceAdded(lastInstanceInfo, "host1.local.", {sAddr1, sAddr2}, "service1", 11111, sTxtData1);
292     clearLastInstance();
293 
294     pub->PublishService("host1", "service2", "_test._tcp", {}, 22222, {}, NoOpCallback());
295     RunMainloopUntilTimeout(kTimeoutSeconds);
296     EXPECT_EQ("_test._tcp", lastServiceType);
297     CheckServiceInstanceAdded(lastInstanceInfo, "host1.local.", {sAddr1, sAddr2}, "service2", 22222, {});
298     clearLastInstance();
299 
300     pub->PublishHost("host2", Publisher::AddressList{sAddr3}, NoOpCallback());
301     pub->PublishService("host2", "service3", "_test._tcp", {}, 33333, {}, NoOpCallback());
302     RunMainloopUntilTimeout(kTimeoutSeconds);
303     EXPECT_EQ("_test._tcp", lastServiceType);
304     CheckServiceInstanceAdded(lastInstanceInfo, "host2.local.", {sAddr3}, "service3", 33333, {});
305     clearLastInstance();
306 
307     pub->UnpublishHost("host2", NoOpCallback());
308     pub->UnpublishService("service3", "_test._tcp", NoOpCallback());
309     RunMainloopUntilTimeout(kTimeoutSeconds);
310     EXPECT_EQ("_test._tcp", lastServiceType);
311     CheckServiceInstanceRemoved(lastInstanceInfo, "service3");
312     clearLastInstance();
313 
314     pub->PublishHost("host2", {sAddr3}, NoOpCallback());
315     pub->PublishService("host2", "service3", "_test._tcp", {}, 44444, {}, NoOpCallback());
316     pub->PublishHost("host2", {sAddr3, sAddr4}, NoOpCallback());
317     RunMainloopUntilTimeout(kTimeoutSeconds);
318     EXPECT_EQ("_test._tcp", lastServiceType);
319     CheckServiceInstanceAdded(lastInstanceInfo, "host2.local.", {sAddr3, sAddr4}, "service3", 44444, {});
320     clearLastInstance();
321 
322     pub->PublishHost("host2", {sAddr4}, NoOpCallback());
323     RunMainloopUntilTimeout(kTimeoutSeconds);
324     EXPECT_EQ("_test._tcp", lastServiceType);
325     CheckServiceInstanceAdded(lastInstanceInfo, "host2.local.", {sAddr4}, "service3", 44444, {});
326     clearLastInstance();
327 }
328