blob: b01be3eb4340b7a42e7e0eedd070e8ba5760d717 [file] [edit]
/*
* Copyright (c) 2023, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <netinet/in.h>
#include <signal.h>
#include <set>
#include <vector>
#include "common/mainloop.hpp"
#include "common/mainloop_manager.hpp"
#include "mdns/mdns.hpp"
#include <CppUTest/CommandLineTestRunner.h>
#include <CppUTest/TestHarness.h>
using namespace otbr;
using namespace otbr::Mdns;
TEST_GROUP(Mdns){};
static constexpr int kTimeoutSeconds = 3;
SimpleString StringFrom(const std::set<Ip6Address> &aAddresses)
{
std::string result = "[";
for (const auto &address : aAddresses)
{
result += address.ToString() + ",";
}
result.back() = ']';
return SimpleString(result.c_str());
}
int RunMainloopUntilTimeout(int aSeconds)
{
using namespace otbr;
int rval = 0;
auto beginTime = Clock::now();
while (true)
{
MainloopContext mainloop;
mainloop.mMaxFd = -1;
mainloop.mTimeout = {1, 0};
FD_ZERO(&mainloop.mReadFdSet);
FD_ZERO(&mainloop.mWriteFdSet);
FD_ZERO(&mainloop.mErrorFdSet);
MainloopManager::GetInstance().Update(mainloop);
rval = select(mainloop.mMaxFd + 1, &mainloop.mReadFdSet, &mainloop.mWriteFdSet, &mainloop.mErrorFdSet,
(mainloop.mTimeout.tv_sec == INT_MAX ? nullptr : &mainloop.mTimeout));
if (rval < 0)
{
perror("select");
break;
}
MainloopManager::GetInstance().Process(mainloop);
if (Clock::now() - beginTime >= std::chrono::seconds(aSeconds))
{
break;
}
}
return rval;
}
template <typename Container> std::set<typename Container::value_type> AsSet(const Container &aContainer)
{
return std::set<typename Container::value_type>(aContainer.begin(), aContainer.end());
}
Publisher::ResultCallback NoOpCallback(void)
{
return [](otbrError aError) { OTBR_UNUSED_VARIABLE(aError); };
}
std::map<std::string, std::vector<uint8_t>> AsTxtMap(const Publisher::TxtData &aTxtData)
{
Publisher::TxtList txtList;
std::map<std::string, std::vector<uint8_t>> map;
Publisher::DecodeTxtData(txtList, aTxtData.data(), aTxtData.size());
for (const auto &entry : txtList)
{
map[entry.mKey] = entry.mValue;
}
return map;
}
Publisher::TxtList sTxtList1{{"a", "1"}, {"b", "2"}};
Publisher::TxtData sTxtData1;
Ip6Address sAddr1;
Ip6Address sAddr2;
Ip6Address sAddr3;
Ip6Address sAddr4;
void SetUp(void)
{
otbrLogInit("test-mdns-subscriber", OTBR_LOG_INFO, true, false);
SuccessOrDie(Ip6Address::FromString("2002::1", sAddr1), "");
SuccessOrDie(Ip6Address::FromString("2002::2", sAddr2), "");
SuccessOrDie(Ip6Address::FromString("2002::3", sAddr3), "");
SuccessOrDie(Ip6Address::FromString("2002::4", sAddr4), "");
SuccessOrDie(Publisher::EncodeTxtData(sTxtList1, sTxtData1), "");
}
std::unique_ptr<Publisher> CreatePublisher(void)
{
bool ready = false;
std::unique_ptr<Publisher> publisher{Publisher::Create([&publisher, &ready](Mdns::Publisher::State aState) {
if (aState == Publisher::State::kReady)
{
ready = true;
}
})};
publisher->Start();
RunMainloopUntilTimeout(kTimeoutSeconds);
CHECK_TRUE(ready);
return publisher;
}
void 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)
{
CHECK_EQUAL(aRemoved, aInstanceInfo.mRemoved);
CHECK_EQUAL(aServiceName, aInstanceInfo.mName);
if (!aRemoved)
{
CHECK_EQUAL(aHostName, aInstanceInfo.mHostName);
CHECK_EQUAL(AsSet(aAddresses), AsSet(aInstanceInfo.mAddresses));
CHECK_EQUAL(aPort, aInstanceInfo.mPort);
CHECK(AsTxtMap(aTxtData) == AsTxtMap(aInstanceInfo.mTxtData));
}
}
void 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)
{
CheckServiceInstance(aInstanceInfo, false, aHostName, aAddresses, aServiceName, aPort, aTxtData);
}
void CheckServiceInstanceRemoved(const Publisher::DiscoveredInstanceInfo aInstanceInfo, const std::string &aServiceName)
{
CheckServiceInstance(aInstanceInfo, true, "", {}, aServiceName, 0, {});
}
void CheckHostAdded(const Publisher::DiscoveredHostInfo &aHostInfo,
const std::string &aHostName,
const std::vector<Ip6Address> &aAddresses)
{
CHECK_EQUAL(aHostName, aHostInfo.mHostName);
CHECK_EQUAL(AsSet(aAddresses), AsSet(aHostInfo.mAddresses));
}
TEST(Mdns, SubscribeHost)
{
std::unique_ptr<Publisher> pub = CreatePublisher();
std::string lastHostName;
Publisher::DiscoveredHostInfo lastHostInfo{};
auto clearLastHost = [&lastHostName, &lastHostInfo] {
lastHostName = "";
lastHostInfo = {};
};
pub->AddSubscriptionCallbacks(
nullptr,
[&lastHostName, &lastHostInfo](const std::string &aHostName, const Publisher::DiscoveredHostInfo &aHostInfo) {
lastHostName = aHostName;
lastHostInfo = aHostInfo;
});
pub->SubscribeHost("host1");
pub->PublishHost("host1", Publisher::AddressList{sAddr1, sAddr2}, NoOpCallback());
pub->PublishService("host1", "service1", "_test._tcp", Publisher::SubTypeList{"_sub1", "_sub2"}, 11111, sTxtData1,
NoOpCallback());
RunMainloopUntilTimeout(kTimeoutSeconds);
CHECK_EQUAL("host1", lastHostName);
CheckHostAdded(lastHostInfo, "host1.local.", {sAddr1, sAddr2});
clearLastHost();
pub->PublishService("host1", "service2", "_test._tcp", {}, 22222, {}, NoOpCallback());
RunMainloopUntilTimeout(kTimeoutSeconds);
CHECK_EQUAL("", lastHostName);
clearLastHost();
pub->PublishHost("host2", Publisher::AddressList{sAddr3}, NoOpCallback());
pub->PublishService("host2", "service3", "_test._tcp", {}, 33333, {}, NoOpCallback());
RunMainloopUntilTimeout(kTimeoutSeconds);
CHECK_EQUAL("", lastHostName);
clearLastHost();
}
TEST(Mdns, SubscribeServiceInstance)
{
std::unique_ptr<Publisher> pub = CreatePublisher();
std::string lastServiceType;
Publisher::DiscoveredInstanceInfo lastInstanceInfo{};
auto clearLastInstance = [&lastServiceType, &lastInstanceInfo] {
lastServiceType = "";
lastInstanceInfo = {};
};
pub->AddSubscriptionCallbacks(
[&lastServiceType, &lastInstanceInfo](const std::string &aType,
Publisher::DiscoveredInstanceInfo aInstanceInfo) {
lastServiceType = aType;
lastInstanceInfo = aInstanceInfo;
},
nullptr);
pub->SubscribeService("_test._tcp", "service1");
pub->PublishHost("host1", Publisher::AddressList{sAddr1, sAddr2}, NoOpCallback());
pub->PublishService("host1", "service1", "_test._tcp", Publisher::SubTypeList{"_sub1", "_sub2"}, 11111, sTxtData1,
NoOpCallback());
RunMainloopUntilTimeout(kTimeoutSeconds);
CHECK_EQUAL("_test._tcp", lastServiceType);
CheckServiceInstanceAdded(lastInstanceInfo, "host1.local.", {sAddr1, sAddr2}, "service1", 11111, sTxtData1);
clearLastInstance();
pub->PublishService("host1", "service2", "_test._tcp", {}, 22222, {}, NoOpCallback());
RunMainloopUntilTimeout(kTimeoutSeconds);
CHECK_EQUAL("", lastServiceType);
clearLastInstance();
pub->PublishHost("host2", Publisher::AddressList{sAddr3}, NoOpCallback());
pub->PublishService("host2", "service3", "_test._tcp", {}, 33333, {}, NoOpCallback());
RunMainloopUntilTimeout(kTimeoutSeconds);
CHECK_EQUAL("", lastServiceType);
clearLastInstance();
}
TEST(Mdns, SubscribeServiceType)
{
std::unique_ptr<Publisher> pub = CreatePublisher();
std::string lastServiceType;
Publisher::DiscoveredInstanceInfo lastInstanceInfo{};
auto clearLastInstance = [&lastServiceType, &lastInstanceInfo] {
lastServiceType = "";
lastInstanceInfo = {};
};
pub->AddSubscriptionCallbacks(
[&lastServiceType, &lastInstanceInfo](const std::string &aType,
Publisher::DiscoveredInstanceInfo aInstanceInfo) {
lastServiceType = aType;
lastInstanceInfo = aInstanceInfo;
},
nullptr);
pub->SubscribeService("_test._tcp", "");
pub->PublishHost("host1", Publisher::AddressList{sAddr1, sAddr2}, NoOpCallback());
pub->PublishService("host1", "service1", "_test._tcp", Publisher::SubTypeList{"_sub1", "_sub2"}, 11111, sTxtData1,
NoOpCallback());
RunMainloopUntilTimeout(kTimeoutSeconds);
CHECK_EQUAL("_test._tcp", lastServiceType);
CheckServiceInstanceAdded(lastInstanceInfo, "host1.local.", {sAddr1, sAddr2}, "service1", 11111, sTxtData1);
clearLastInstance();
pub->PublishService("host1", "service2", "_test._tcp", {}, 22222, {}, NoOpCallback());
RunMainloopUntilTimeout(kTimeoutSeconds);
CHECK_EQUAL("_test._tcp", lastServiceType);
CheckServiceInstanceAdded(lastInstanceInfo, "host1.local.", {sAddr1, sAddr2}, "service2", 22222, {});
clearLastInstance();
pub->PublishHost("host2", Publisher::AddressList{sAddr3}, NoOpCallback());
pub->PublishService("host2", "service3", "_test._tcp", {}, 33333, {}, NoOpCallback());
RunMainloopUntilTimeout(kTimeoutSeconds);
CHECK_EQUAL("_test._tcp", lastServiceType);
CheckServiceInstanceAdded(lastInstanceInfo, "host2.local.", {sAddr3}, "service3", 33333, {});
clearLastInstance();
pub->UnpublishHost("host2", NoOpCallback());
pub->UnpublishService("service3", "_test._tcp", NoOpCallback());
RunMainloopUntilTimeout(kTimeoutSeconds);
CHECK_EQUAL("_test._tcp", lastServiceType);
CheckServiceInstanceRemoved(lastInstanceInfo, "service3");
clearLastInstance();
pub->PublishHost("host2", {sAddr3}, NoOpCallback());
pub->PublishService("host2", "service3", "_test._tcp", {}, 44444, {}, NoOpCallback());
pub->PublishHost("host2", {sAddr3, sAddr4}, NoOpCallback());
RunMainloopUntilTimeout(kTimeoutSeconds);
CHECK_EQUAL("_test._tcp", lastServiceType);
CheckServiceInstanceAdded(lastInstanceInfo, "host2.local.", {sAddr3, sAddr4}, "service3", 44444, {});
clearLastInstance();
pub->PublishHost("host2", {sAddr4}, NoOpCallback());
RunMainloopUntilTimeout(kTimeoutSeconds);
CHECK_EQUAL("_test._tcp", lastServiceType);
CheckServiceInstanceAdded(lastInstanceInfo, "host2.local.", {sAddr4}, "service3", 44444, {});
clearLastInstance();
}
int main(int argc, const char *argv[])
{
SetUp();
return RUN_ALL_TESTS(argc, argv);
}