/* * * Copyright (c) 2021 Project CHIP Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include "lib/dnssd/platform/Dnssd.h" #include "platform/CHIPDeviceLayer.h" #include "platform/ConnectivityManager.h" #include "platform/PlatformManager.h" #include #include #include #include #include #include #include #include #include #include #include #include using chip::Dnssd::DnssdService; using chip::Dnssd::DnssdServiceProtocol; using chip::Dnssd::TextEntry; namespace { class TestDnssdResolveServerDelegate : public mdns::Minimal::ServerDelegate, public mdns::Minimal::ParserDelegate { public: TestDnssdResolveServerDelegate(mdns::Minimal::ResponseSender * responder) : mResponder(responder) {} virtual ~TestDnssdResolveServerDelegate() = default; // Implementation of mdns::Minimal::ServerDelegate void OnResponse(const mdns::Minimal::BytesRange & data, const chip::Inet::IPPacketInfo * info) override {} void OnQuery(const mdns::Minimal::BytesRange & data, const chip::Inet::IPPacketInfo * info) override { mCurSrc = info; mdns::Minimal::ParsePacket(data, this); mCurSrc = nullptr; } // Implementation of mdns::Minimal::ParserDelegate void OnHeader(mdns::Minimal::ConstHeaderRef & header) override { mMsgId = header.GetMessageId(); } void OnQuery(const mdns::Minimal::QueryData & data) override { mResponder->Respond(mMsgId, data, mCurSrc, mRespConfig); } void OnResource(mdns::Minimal::ResourceType type, const mdns::Minimal::ResourceData & data) override {} private: mdns::Minimal::ResponseSender * mResponder; mdns::Minimal::ResponseConfiguration mRespConfig; const chip::Inet::IPPacketInfo * mCurSrc = nullptr; uint16_t mMsgId = 0; }; } // namespace class TestDnssd : public ::testing::Test { public: // protected static void SetUpTestSuite() { EXPECT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); EXPECT_EQ(chip::DeviceLayer::PlatformMgr().InitChipStack(), CHIP_NO_ERROR); } static void TearDownTestSuite() { chip::DeviceLayer::PlatformMgr().Shutdown(); chip::Platform::MemoryShutdown(); } std::atomic mTimeoutExpired{ false }; intptr_t mBrowseIdentifier = 0; unsigned int mBrowsedServicesCount = 0; unsigned int mResolvedServicesCount = 0; bool mEndOfInput = false; }; static void Timeout(chip::System::Layer * systemLayer, void * context) { auto * ctx = static_cast(context); ChipLogError(DeviceLayer, "mDNS test timeout, is avahi daemon running?"); ctx->mTimeoutExpired = true; chip::DeviceLayer::PlatformMgr().StopEventLoopTask(); } static void HandleResolve(void * context, DnssdService * result, const chip::Span & addresses, CHIP_ERROR error) { auto * ctx = static_cast(context); char addrBuf[100]; EXPECT_NE(result, nullptr); EXPECT_EQ(error, CHIP_NO_ERROR); if (!addresses.empty()) { addresses.data()[0].ToString(addrBuf, sizeof(addrBuf)); printf("Service[%u] at [%s]:%u\n", ctx->mResolvedServicesCount, addrBuf, result->mPort); } EXPECT_EQ(result->mTextEntrySize, 1u); EXPECT_STREQ(result->mTextEntries[0].mKey, "key"); EXPECT_STREQ(reinterpret_cast(result->mTextEntries[0].mData), "val"); if (ctx->mBrowsedServicesCount == ++ctx->mResolvedServicesCount) { chip::DeviceLayer::SystemLayer().CancelTimer(Timeout, context); // StopEventLoopTask can be called from any thread, but when called from // non-Matter one it will lock the Matter stack. The same locking rules // are required when the resolve callback (this one) is called. In order // to avoid deadlocks, we need to call the StopEventLoopTask from inside // the Matter event loop by scheduling a lambda. chip::DeviceLayer::SystemLayer().ScheduleLambda([]() { // After last service is resolved, stop the event loop, // so the test case can gracefully exit. chip::DeviceLayer::PlatformMgr().StopEventLoopTask(); }); } } static void HandleBrowse(void * context, DnssdService * services, size_t servicesSize, bool finalBrowse, CHIP_ERROR error) { auto * ctx = static_cast(context); // Make sure that we will not be called again after end-of-input is set EXPECT_FALSE(ctx->mEndOfInput); // Cancelled error is expected when the browse is stopped with // ChipDnssdStopBrowse(), so we will not assert on it. EXPECT_TRUE(error == CHIP_NO_ERROR || error == CHIP_ERROR_CANCELLED); ctx->mBrowsedServicesCount += servicesSize; ctx->mEndOfInput = finalBrowse; if (servicesSize > 0) { printf("Browse mDNS service size %u\n", static_cast(servicesSize)); for (unsigned int i = 0; i < servicesSize; i++) { printf("Service[%u] name %s\n", i, services[i].mName); printf("Service[%u] type %s\n", i, services[i].mType); EXPECT_EQ(ChipDnssdResolve(&services[i], services[i].mInterface, HandleResolve, context), CHIP_NO_ERROR); } } } static void DnssdErrorCallback(void * context, CHIP_ERROR error) { EXPECT_EQ(error, CHIP_NO_ERROR); } void TestDnssdBrowse_DnssdInitCallback(void * context, CHIP_ERROR error) { auto * ctx = static_cast(context); EXPECT_EQ(error, CHIP_NO_ERROR); EXPECT_EQ(ChipDnssdBrowse("_mock", DnssdServiceProtocol::kDnssdProtocolUdp, chip::Inet::IPAddressType::kAny, chip::Inet::InterfaceId::Null(), HandleBrowse, context, &ctx->mBrowseIdentifier), CHIP_NO_ERROR); } // Verify that platform DNS-SD implementation can browse and resolve services. // // This test case uses platform-independent mDNS server implementation based on // minimal mdns library. The server is configured to respond to PTR, SRV, TXT, // A and AAAA queries without additional records. In order to pass this test, // the platform DNS-SD client implementation must be able to browse and resolve // services by querying for all of these records separately. TEST_F(TestDnssd, TestDnssdBrowse) { mdns::Minimal::SetDefaultAddressPolicy(); mdns::Minimal::Server<10> server; mdns::Minimal::QNamePart serverName[] = { "resolve-tester", "_mock", chip::Dnssd::kCommissionProtocol, chip::Dnssd::kLocalDomain }; mdns::Minimal::ResponseSender responseSender(&server); mdns::Minimal::QueryResponder<16> queryResponder; responseSender.AddQueryResponder(&queryResponder); // Respond to PTR queries for _mock._udp.local mdns::Minimal::QNamePart serviceName[] = { "_mock", chip::Dnssd::kCommissionProtocol, chip::Dnssd::kLocalDomain }; mdns::Minimal::QNamePart serverServiceName[] = { "INSTANCE", chip::Dnssd::kCommissionableServiceName, chip::Dnssd::kCommissionProtocol, chip::Dnssd::kLocalDomain }; mdns::Minimal::PtrResponder ptrUdpResponder(serviceName, serverServiceName); queryResponder.AddResponder(&ptrUdpResponder); // Respond to SRV queries for INSTANCE._matterc._udp.local mdns::Minimal::SrvResponder srvResponder(mdns::Minimal::SrvResourceRecord(serverServiceName, serverName, CHIP_PORT)); queryResponder.AddResponder(&srvResponder); // Respond to TXT queries for INSTANCE._matterc._udp.local const char * txtEntries[] = { "key=val" }; mdns::Minimal::TxtResponder txtResponder(mdns::Minimal::TxtResourceRecord(serverServiceName, txtEntries)); queryResponder.AddResponder(&txtResponder); // Respond to A queries mdns::Minimal::IPv4Responder ipv4Responder(serverName); queryResponder.AddResponder(&ipv4Responder); // Respond to AAAA queries mdns::Minimal::IPv6Responder ipv6Responder(serverName); queryResponder.AddResponder(&ipv6Responder); TestDnssdResolveServerDelegate delegate(&responseSender); server.SetDelegate(&delegate); auto endpoints = mdns::Minimal::GetAddressPolicy()->GetListenEndpoints(); EXPECT_EQ(server.Listen(chip::DeviceLayer::UDPEndPointManager(), endpoints.get(), 5353), CHIP_NO_ERROR); EXPECT_EQ(chip::Dnssd::ChipDnssdInit(TestDnssdBrowse_DnssdInitCallback, DnssdErrorCallback, this), CHIP_NO_ERROR); EXPECT_EQ(chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds32(5), Timeout, this), CHIP_NO_ERROR); ChipLogProgress(DeviceLayer, "Start EventLoop"); chip::DeviceLayer::PlatformMgr().RunEventLoop(); ChipLogProgress(DeviceLayer, "End EventLoop"); EXPECT_GT(mResolvedServicesCount, 0u); EXPECT_FALSE(mTimeoutExpired); // Stop browsing so we can safely shutdown DNS-SD chip::Dnssd::ChipDnssdStopBrowse(mBrowseIdentifier); chip::Dnssd::ChipDnssdShutdown(); } static void HandlePublish(void * context, const char * type, const char * instanceName, CHIP_ERROR error) { EXPECT_EQ(error, CHIP_NO_ERROR); } static void TestDnssdPublishService_DnssdInitCallback(void * context, CHIP_ERROR error) { auto * ctx = static_cast(context); EXPECT_EQ(error, CHIP_NO_ERROR); DnssdService service{}; TextEntry entry{ "key", reinterpret_cast("val"), 3 }; service.mInterface = chip::Inet::InterfaceId::Null(); service.mPort = 80; strcpy(service.mHostName, "MatterTest"); strcpy(service.mName, "test"); strcpy(service.mType, "_mock"); service.mAddressType = chip::Inet::IPAddressType::kAny; service.mProtocol = DnssdServiceProtocol::kDnssdProtocolTcp; service.mTextEntries = &entry; service.mTextEntrySize = 1; service.mSubTypes = nullptr; service.mSubTypeSize = 0; EXPECT_EQ(ChipDnssdPublishService(&service, HandlePublish, nullptr), CHIP_NO_ERROR); EXPECT_EQ(ChipDnssdBrowse("_mock", DnssdServiceProtocol::kDnssdProtocolTcp, chip::Inet::IPAddressType::kAny, chip::Inet::InterfaceId::Null(), HandleBrowse, context, &ctx->mBrowseIdentifier), CHIP_NO_ERROR); } // Verify that the platform DNS-SD implementation can publish services. // // This test uses platform implementation of DNS-SD server and client. Since // client implementation should be verified by the TestDnssdBrowse test case, // here we only verify that the server implementation can publish services. TEST_F(TestDnssd, TestDnssdPublishService) { EXPECT_EQ(chip::Dnssd::ChipDnssdInit(TestDnssdPublishService_DnssdInitCallback, DnssdErrorCallback, this), CHIP_NO_ERROR); EXPECT_EQ(chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds32(5), Timeout, this), CHIP_NO_ERROR); ChipLogProgress(DeviceLayer, "Start EventLoop"); chip::DeviceLayer::PlatformMgr().RunEventLoop(); ChipLogProgress(DeviceLayer, "End EventLoop"); EXPECT_GT(mResolvedServicesCount, 0u); EXPECT_FALSE(mTimeoutExpired); // Stop browsing so we can safely shutdown DNS-SD chip::Dnssd::ChipDnssdStopBrowse(mBrowseIdentifier); chip::Dnssd::ChipDnssdShutdown(); }