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