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