• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <netinet/in.h>
30 #include <signal.h>
31 
32 #include <set>
33 #include <vector>
34 
35 #include "common/mainloop.hpp"
36 #include "common/mainloop_manager.hpp"
37 #include "mdns/mdns.hpp"
38 
39 #include <CppUTest/CommandLineTestRunner.h>
40 #include <CppUTest/TestHarness.h>
41 
42 using namespace otbr;
43 using namespace otbr::Mdns;
44 
TEST_GROUP(Mdns)45 TEST_GROUP(Mdns){};
46 
47 static constexpr int kTimeoutSeconds = 3;
48 
StringFrom(const std::set<Ip6Address> & aAddresses)49 SimpleString StringFrom(const std::set<Ip6Address> &aAddresses)
50 {
51     std::string result = "[";
52 
53     for (const auto &address : aAddresses)
54     {
55         result += address.ToString() + ",";
56     }
57     result.back() = ']';
58 
59     return SimpleString(result.c_str());
60 }
61 
RunMainloopUntilTimeout(int aSeconds)62 int RunMainloopUntilTimeout(int aSeconds)
63 {
64     using namespace otbr;
65 
66     int  rval      = 0;
67     auto beginTime = Clock::now();
68 
69     while (true)
70     {
71         MainloopContext mainloop;
72 
73         mainloop.mMaxFd   = -1;
74         mainloop.mTimeout = {1, 0};
75         FD_ZERO(&mainloop.mReadFdSet);
76         FD_ZERO(&mainloop.mWriteFdSet);
77         FD_ZERO(&mainloop.mErrorFdSet);
78 
79         MainloopManager::GetInstance().Update(mainloop);
80         rval = select(mainloop.mMaxFd + 1, &mainloop.mReadFdSet, &mainloop.mWriteFdSet, &mainloop.mErrorFdSet,
81                       (mainloop.mTimeout.tv_sec == INT_MAX ? nullptr : &mainloop.mTimeout));
82 
83         if (rval < 0)
84         {
85             perror("select");
86             break;
87         }
88 
89         MainloopManager::GetInstance().Process(mainloop);
90 
91         if (Clock::now() - beginTime >= std::chrono::seconds(aSeconds))
92         {
93             break;
94         }
95     }
96 
97     return rval;
98 }
99 
AsSet(const Container & aContainer)100 template <typename Container> std::set<typename Container::value_type> AsSet(const Container &aContainer)
101 {
102     return std::set<typename Container::value_type>(aContainer.begin(), aContainer.end());
103 }
104 
NoOpCallback(void)105 Publisher::ResultCallback NoOpCallback(void)
106 {
107     return [](otbrError aError) { OTBR_UNUSED_VARIABLE(aError); };
108 }
109 
AsTxtMap(const Publisher::TxtData & aTxtData)110 std::map<std::string, std::vector<uint8_t>> AsTxtMap(const Publisher::TxtData &aTxtData)
111 {
112     Publisher::TxtList                          txtList;
113     std::map<std::string, std::vector<uint8_t>> map;
114 
115     Publisher::DecodeTxtData(txtList, aTxtData.data(), aTxtData.size());
116     for (const auto &entry : txtList)
117     {
118         map[entry.mKey] = entry.mValue;
119     }
120 
121     return map;
122 }
123 
124 Publisher::TxtList sTxtList1{{"a", "1"}, {"b", "2"}};
125 Publisher::TxtData sTxtData1;
126 Ip6Address         sAddr1;
127 Ip6Address         sAddr2;
128 Ip6Address         sAddr3;
129 Ip6Address         sAddr4;
130 
SetUp(void)131 void SetUp(void)
132 {
133     otbrLogInit("test-mdns-subscriber", OTBR_LOG_INFO, true, false);
134     SuccessOrDie(Ip6Address::FromString("2002::1", sAddr1), "");
135     SuccessOrDie(Ip6Address::FromString("2002::2", sAddr2), "");
136     SuccessOrDie(Ip6Address::FromString("2002::3", sAddr3), "");
137     SuccessOrDie(Ip6Address::FromString("2002::4", sAddr4), "");
138     SuccessOrDie(Publisher::EncodeTxtData(sTxtList1, sTxtData1), "");
139 }
140 
CreatePublisher(void)141 std::unique_ptr<Publisher> CreatePublisher(void)
142 {
143     bool                       ready = false;
144     std::unique_ptr<Publisher> publisher{Publisher::Create([&publisher, &ready](Mdns::Publisher::State aState) {
145         if (aState == Publisher::State::kReady)
146         {
147             ready = true;
148         }
149     })};
150 
151     publisher->Start();
152     RunMainloopUntilTimeout(kTimeoutSeconds);
153     CHECK_TRUE(ready);
154 
155     return publisher;
156 }
157 
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)158 void CheckServiceInstance(const Publisher::DiscoveredInstanceInfo aInstanceInfo,
159                           bool                                    aRemoved,
160                           const std::string                      &aHostName,
161                           const std::vector<Ip6Address>          &aAddresses,
162                           const std::string                      &aServiceName,
163                           uint16_t                                aPort,
164                           const Publisher::TxtData                aTxtData)
165 {
166     CHECK_EQUAL(aRemoved, aInstanceInfo.mRemoved);
167     CHECK_EQUAL(aServiceName, aInstanceInfo.mName);
168     if (!aRemoved)
169     {
170         CHECK_EQUAL(aHostName, aInstanceInfo.mHostName);
171         CHECK_EQUAL(AsSet(aAddresses), AsSet(aInstanceInfo.mAddresses));
172         CHECK_EQUAL(aPort, aInstanceInfo.mPort);
173         CHECK(AsTxtMap(aTxtData) == AsTxtMap(aInstanceInfo.mTxtData));
174     }
175 }
176 
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)177 void CheckServiceInstanceAdded(const Publisher::DiscoveredInstanceInfo aInstanceInfo,
178                                const std::string                      &aHostName,
179                                const std::vector<Ip6Address>          &aAddresses,
180                                const std::string                      &aServiceName,
181                                uint16_t                                aPort,
182                                const Publisher::TxtData                aTxtData)
183 {
184     CheckServiceInstance(aInstanceInfo, false, aHostName, aAddresses, aServiceName, aPort, aTxtData);
185 }
186 
CheckServiceInstanceRemoved(const Publisher::DiscoveredInstanceInfo aInstanceInfo,const std::string & aServiceName)187 void CheckServiceInstanceRemoved(const Publisher::DiscoveredInstanceInfo aInstanceInfo, const std::string &aServiceName)
188 {
189     CheckServiceInstance(aInstanceInfo, true, "", {}, aServiceName, 0, {});
190 }
191 
CheckHostAdded(const Publisher::DiscoveredHostInfo & aHostInfo,const std::string & aHostName,const std::vector<Ip6Address> & aAddresses)192 void CheckHostAdded(const Publisher::DiscoveredHostInfo &aHostInfo,
193                     const std::string                   &aHostName,
194                     const std::vector<Ip6Address>       &aAddresses)
195 {
196     CHECK_EQUAL(aHostName, aHostInfo.mHostName);
197     CHECK_EQUAL(AsSet(aAddresses), AsSet(aHostInfo.mAddresses));
198 }
199 
TEST(Mdns,SubscribeHost)200 TEST(Mdns, SubscribeHost)
201 {
202     std::unique_ptr<Publisher>    pub = CreatePublisher();
203     std::string                   lastHostName;
204     Publisher::DiscoveredHostInfo lastHostInfo{};
205 
206     auto clearLastHost = [&lastHostName, &lastHostInfo] {
207         lastHostName = "";
208         lastHostInfo = {};
209     };
210 
211     pub->AddSubscriptionCallbacks(
212         nullptr,
213         [&lastHostName, &lastHostInfo](const std::string &aHostName, const Publisher::DiscoveredHostInfo &aHostInfo) {
214             lastHostName = aHostName;
215             lastHostInfo = aHostInfo;
216         });
217     pub->SubscribeHost("host1");
218 
219     pub->PublishHost("host1", Publisher::AddressList{sAddr1, sAddr2}, NoOpCallback());
220     pub->PublishService("host1", "service1", "_test._tcp", Publisher::SubTypeList{"_sub1", "_sub2"}, 11111, sTxtData1,
221                         NoOpCallback());
222     RunMainloopUntilTimeout(kTimeoutSeconds);
223     CHECK_EQUAL("host1", lastHostName);
224     CheckHostAdded(lastHostInfo, "host1.local.", {sAddr1, sAddr2});
225     clearLastHost();
226 
227     pub->PublishService("host1", "service2", "_test._tcp", {}, 22222, {}, NoOpCallback());
228     RunMainloopUntilTimeout(kTimeoutSeconds);
229     CHECK_EQUAL("", lastHostName);
230     clearLastHost();
231 
232     pub->PublishHost("host2", Publisher::AddressList{sAddr3}, NoOpCallback());
233     pub->PublishService("host2", "service3", "_test._tcp", {}, 33333, {}, NoOpCallback());
234     RunMainloopUntilTimeout(kTimeoutSeconds);
235     CHECK_EQUAL("", lastHostName);
236     clearLastHost();
237 }
238 
TEST(Mdns,SubscribeServiceInstance)239 TEST(Mdns, SubscribeServiceInstance)
240 {
241     std::unique_ptr<Publisher>        pub = CreatePublisher();
242     std::string                       lastServiceType;
243     Publisher::DiscoveredInstanceInfo lastInstanceInfo{};
244 
245     auto clearLastInstance = [&lastServiceType, &lastInstanceInfo] {
246         lastServiceType  = "";
247         lastInstanceInfo = {};
248     };
249 
250     pub->AddSubscriptionCallbacks(
251         [&lastServiceType, &lastInstanceInfo](const std::string                &aType,
252                                               Publisher::DiscoveredInstanceInfo aInstanceInfo) {
253             lastServiceType  = aType;
254             lastInstanceInfo = aInstanceInfo;
255         },
256         nullptr);
257     pub->SubscribeService("_test._tcp", "service1");
258 
259     pub->PublishHost("host1", Publisher::AddressList{sAddr1, sAddr2}, NoOpCallback());
260     pub->PublishService("host1", "service1", "_test._tcp", Publisher::SubTypeList{"_sub1", "_sub2"}, 11111, sTxtData1,
261                         NoOpCallback());
262     RunMainloopUntilTimeout(kTimeoutSeconds);
263     CHECK_EQUAL("_test._tcp", lastServiceType);
264     CheckServiceInstanceAdded(lastInstanceInfo, "host1.local.", {sAddr1, sAddr2}, "service1", 11111, sTxtData1);
265     clearLastInstance();
266 
267     pub->PublishService("host1", "service2", "_test._tcp", {}, 22222, {}, NoOpCallback());
268     RunMainloopUntilTimeout(kTimeoutSeconds);
269     CHECK_EQUAL("", lastServiceType);
270     clearLastInstance();
271 
272     pub->PublishHost("host2", Publisher::AddressList{sAddr3}, NoOpCallback());
273     pub->PublishService("host2", "service3", "_test._tcp", {}, 33333, {}, NoOpCallback());
274     RunMainloopUntilTimeout(kTimeoutSeconds);
275     CHECK_EQUAL("", lastServiceType);
276     clearLastInstance();
277 }
278 
TEST(Mdns,SubscribeServiceType)279 TEST(Mdns, SubscribeServiceType)
280 {
281     std::unique_ptr<Publisher>        pub = CreatePublisher();
282     std::string                       lastServiceType;
283     Publisher::DiscoveredInstanceInfo lastInstanceInfo{};
284 
285     auto clearLastInstance = [&lastServiceType, &lastInstanceInfo] {
286         lastServiceType  = "";
287         lastInstanceInfo = {};
288     };
289 
290     pub->AddSubscriptionCallbacks(
291         [&lastServiceType, &lastInstanceInfo](const std::string                &aType,
292                                               Publisher::DiscoveredInstanceInfo aInstanceInfo) {
293             lastServiceType  = aType;
294             lastInstanceInfo = aInstanceInfo;
295         },
296         nullptr);
297     pub->SubscribeService("_test._tcp", "");
298 
299     pub->PublishHost("host1", Publisher::AddressList{sAddr1, sAddr2}, NoOpCallback());
300     pub->PublishService("host1", "service1", "_test._tcp", Publisher::SubTypeList{"_sub1", "_sub2"}, 11111, sTxtData1,
301                         NoOpCallback());
302     RunMainloopUntilTimeout(kTimeoutSeconds);
303     CHECK_EQUAL("_test._tcp", lastServiceType);
304     CheckServiceInstanceAdded(lastInstanceInfo, "host1.local.", {sAddr1, sAddr2}, "service1", 11111, sTxtData1);
305     clearLastInstance();
306 
307     pub->PublishService("host1", "service2", "_test._tcp", {}, 22222, {}, NoOpCallback());
308     RunMainloopUntilTimeout(kTimeoutSeconds);
309     CHECK_EQUAL("_test._tcp", lastServiceType);
310     CheckServiceInstanceAdded(lastInstanceInfo, "host1.local.", {sAddr1, sAddr2}, "service2", 22222, {});
311     clearLastInstance();
312 
313     pub->PublishHost("host2", Publisher::AddressList{sAddr3}, NoOpCallback());
314     pub->PublishService("host2", "service3", "_test._tcp", {}, 33333, {}, NoOpCallback());
315     RunMainloopUntilTimeout(kTimeoutSeconds);
316     CHECK_EQUAL("_test._tcp", lastServiceType);
317     CheckServiceInstanceAdded(lastInstanceInfo, "host2.local.", {sAddr3}, "service3", 33333, {});
318     clearLastInstance();
319 
320     pub->UnpublishHost("host2", NoOpCallback());
321     pub->UnpublishService("service3", "_test._tcp", NoOpCallback());
322     RunMainloopUntilTimeout(kTimeoutSeconds);
323     CHECK_EQUAL("_test._tcp", lastServiceType);
324     CheckServiceInstanceRemoved(lastInstanceInfo, "service3");
325     clearLastInstance();
326 
327     pub->PublishHost("host2", {sAddr3}, NoOpCallback());
328     pub->PublishService("host2", "service3", "_test._tcp", {}, 44444, {}, NoOpCallback());
329     pub->PublishHost("host2", {sAddr3, sAddr4}, NoOpCallback());
330     RunMainloopUntilTimeout(kTimeoutSeconds);
331     CHECK_EQUAL("_test._tcp", lastServiceType);
332     CheckServiceInstanceAdded(lastInstanceInfo, "host2.local.", {sAddr3, sAddr4}, "service3", 44444, {});
333     clearLastInstance();
334 
335     pub->PublishHost("host2", {sAddr4}, NoOpCallback());
336     RunMainloopUntilTimeout(kTimeoutSeconds);
337     CHECK_EQUAL("_test._tcp", lastServiceType);
338     CheckServiceInstanceAdded(lastInstanceInfo, "host2.local.", {sAddr4}, "service3", 44444, {});
339     clearLastInstance();
340 }
341 
main(int argc,const char * argv[])342 int main(int argc, const char *argv[])
343 {
344     SetUp();
345 
346     return RUN_ALL_TESTS(argc, argv);
347 }
348