/* * 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 #include "test_platform.h" #include "test_util.hpp" #include #include #include #include #include #include "common/arg_macros.hpp" #include "common/array.hpp" #include "common/string.hpp" #include "common/time.hpp" #include "instance/instance.hpp" #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE && OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE && \ OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_ADDRESS_AUTO_SET_ENABLE && OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE && \ OPENTHREAD_CONFIG_SRP_SERVER_ENABLE && OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE && \ !OPENTHREAD_CONFIG_TIME_SYNC_ENABLE && !OPENTHREAD_PLATFORM_POSIX #define ENABLE_DNS_TEST 1 #else #define ENABLE_DNS_TEST 0 #endif #if ENABLE_DNS_TEST using namespace ot; // Logs a message and adds current time (sNow) as "::." #define Log(...) \ printf("%02u:%02u:%02u.%03u " OT_FIRST_ARG(__VA_ARGS__) "\n", (sNow / 36000000), (sNow / 60000) % 60, \ (sNow / 1000) % 60, sNow % 1000 OT_REST_ARGS(__VA_ARGS__)) static constexpr uint16_t kMaxRaSize = 800; static ot::Instance *sInstance; static uint32_t sNow = 0; static uint32_t sAlarmTime; static bool sAlarmOn = false; static otRadioFrame sRadioTxFrame; static uint8_t sRadioTxFramePsdu[OT_RADIO_FRAME_MAX_SIZE]; static bool sRadioTxOngoing = false; //---------------------------------------------------------------------------------------------------------------------- // Function prototypes void ProcessRadioTxAndTasklets(void); void AdvanceTime(uint32_t aDuration); //---------------------------------------------------------------------------------------------------------------------- // `otPlatRadio` extern "C" { otRadioCaps otPlatRadioGetCaps(otInstance *) { return OT_RADIO_CAPS_ACK_TIMEOUT | OT_RADIO_CAPS_CSMA_BACKOFF; } otError otPlatRadioTransmit(otInstance *, otRadioFrame *) { sRadioTxOngoing = true; return OT_ERROR_NONE; } otRadioFrame *otPlatRadioGetTransmitBuffer(otInstance *) { return &sRadioTxFrame; } //---------------------------------------------------------------------------------------------------------------------- // `otPlatAlaram` void otPlatAlarmMilliStop(otInstance *) { sAlarmOn = false; } void otPlatAlarmMilliStartAt(otInstance *, uint32_t aT0, uint32_t aDt) { sAlarmOn = true; sAlarmTime = aT0 + aDt; } uint32_t otPlatAlarmMilliGetNow(void) { return sNow; } //---------------------------------------------------------------------------------------------------------------------- Array sHeapAllocatedPtrs; #if OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE void *otPlatCAlloc(size_t aNum, size_t aSize) { void *ptr = calloc(aNum, aSize); SuccessOrQuit(sHeapAllocatedPtrs.PushBack(ptr)); return ptr; } void otPlatFree(void *aPtr) { if (aPtr != nullptr) { void **entry = sHeapAllocatedPtrs.Find(aPtr); VerifyOrQuit(entry != nullptr, "A heap allocated item is freed twice"); sHeapAllocatedPtrs.Remove(*entry); } free(aPtr); } #endif #if OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_PLATFORM_DEFINED void otPlatLog(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aFormat, ...) { OT_UNUSED_VARIABLE(aLogLevel); OT_UNUSED_VARIABLE(aLogRegion); va_list args; printf(" "); va_start(args, aFormat); vprintf(aFormat, args); va_end(args); printf("\n"); } #endif } // extern "C" //--------------------------------------------------------------------------------------------------------------------- void ProcessRadioTxAndTasklets(void) { do { if (sRadioTxOngoing) { sRadioTxOngoing = false; otPlatRadioTxStarted(sInstance, &sRadioTxFrame); otPlatRadioTxDone(sInstance, &sRadioTxFrame, nullptr, OT_ERROR_NONE); } otTaskletsProcess(sInstance); } while (otTaskletsArePending(sInstance)); } void AdvanceTime(uint32_t aDuration) { uint32_t time = sNow + aDuration; Log("AdvanceTime for %u.%03u", aDuration / 1000, aDuration % 1000); while (TimeMilli(sAlarmTime) <= TimeMilli(time)) { ProcessRadioTxAndTasklets(); sNow = sAlarmTime; otPlatAlarmMilliFired(sInstance); } ProcessRadioTxAndTasklets(); sNow = time; } void InitTest(void) { //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Initialize OT instance. sNow = 0; sAlarmOn = false; sInstance = static_cast(testInitInstance()); memset(&sRadioTxFrame, 0, sizeof(sRadioTxFrame)); sRadioTxFrame.mPsdu = sRadioTxFramePsdu; sRadioTxOngoing = false; //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Initialize Border Router and start Thread operation. otOperationalDataset dataset; otOperationalDatasetTlvs datasetTlvs; SuccessOrQuit(otDatasetCreateNewNetwork(sInstance, &dataset)); otDatasetConvertToTlvs(&dataset, &datasetTlvs); SuccessOrQuit(otDatasetSetActiveTlvs(sInstance, &datasetTlvs)); SuccessOrQuit(otIp6SetEnabled(sInstance, true)); SuccessOrQuit(otThreadSetEnabled(sInstance, true)); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Ensure device starts as leader. AdvanceTime(10000); VerifyOrQuit(otThreadGetDeviceRole(sInstance) == OT_DEVICE_ROLE_LEADER); } void FinalizeTest(void) { SuccessOrQuit(otIp6SetEnabled(sInstance, false)); SuccessOrQuit(otThreadSetEnabled(sInstance, false)); // Make sure there is no message/buffer leak VerifyOrQuit(sInstance->Get().GetFreeBufferCount() == sInstance->Get().GetTotalBufferCount()); SuccessOrQuit(otInstanceErasePersistentInfo(sInstance)); testFreeInstance(sInstance); } //--------------------------------------------------------------------------------------------------------------------- static const char kHostName[] = "elden"; static const char kHostFullName[] = "elden.default.service.arpa."; static const char kService1Name[] = "_srv._udp"; static const char kService1FullName[] = "_srv._udp.default.service.arpa."; static const char kInstance1Label[] = "srv-instance"; static const char kInstance1FullName[] = "srv-instance._srv._udp.default.service.arpa."; static const char kService2Name[] = "_game._udp"; static const char kService2FullName[] = "_game._udp.default.service.arpa."; static const char kService2SubTypeFullName[] = "_best._sub._game._udp.default.service.arpa."; static const char kInstance2Label[] = "last-ninja"; static const char kInstance2FullName[] = "last-ninja._game._udp.default.service.arpa."; void PrepareService1(Srp::Client::Service &aService) { static const char kSub1[] = "_sub1"; static const char kSub2[] = "_V1234567"; static const char kSub3[] = "_XYZWS"; static const char *kSubLabels[] = {kSub1, kSub2, kSub3, nullptr}; static const char kTxtKey1[] = "ABCD"; static const uint8_t kTxtValue1[] = {'a', '0'}; static const char kTxtKey2[] = "Z0"; static const uint8_t kTxtValue2[] = {'1', '2', '3'}; static const char kTxtKey3[] = "D"; static const uint8_t kTxtValue3[] = {0}; static const otDnsTxtEntry kTxtEntries[] = { {kTxtKey1, kTxtValue1, sizeof(kTxtValue1)}, {kTxtKey2, kTxtValue2, sizeof(kTxtValue2)}, {kTxtKey3, kTxtValue3, sizeof(kTxtValue3)}, }; memset(&aService, 0, sizeof(aService)); aService.mName = kService1Name; aService.mInstanceName = kInstance1Label; aService.mSubTypeLabels = kSubLabels; aService.mTxtEntries = kTxtEntries; aService.mNumTxtEntries = 3; aService.mPort = 777; aService.mWeight = 1; aService.mPriority = 2; } void PrepareService2(Srp::Client::Service &aService) { static const char kSub4[] = "_best"; static const char *kSubLabels2[] = {kSub4, nullptr}; memset(&aService, 0, sizeof(aService)); aService.mName = kService2Name; aService.mInstanceName = kInstance2Label; aService.mSubTypeLabels = kSubLabels2; aService.mTxtEntries = nullptr; aService.mNumTxtEntries = 0; aService.mPort = 555; aService.mWeight = 0; aService.mPriority = 3; } void ValidateHost(Srp::Server &aServer, const char *aHostName) { // Validate that only a host with `aHostName` is // registered on SRP server. const Srp::Server::Host *host; const char *name; Log("ValidateHost()"); host = aServer.GetNextHost(nullptr); VerifyOrQuit(host != nullptr); name = host->GetFullName(); Log("Hostname: %s", name); VerifyOrQuit(StringStartsWith(name, aHostName, kStringCaseInsensitiveMatch)); VerifyOrQuit(name[strlen(aHostName)] == '.'); // Only one host on server VerifyOrQuit(aServer.GetNextHost(host) == nullptr); } //--------------------------------------------------------------------------------------------------------------------- void LogServiceInfo(const Dns::Client::ServiceInfo &aInfo) { Log(" TTL: %u", aInfo.mTtl); Log(" Port: %u", aInfo.mPort); Log(" Weight: %u", aInfo.mWeight); Log(" HostName: %s", aInfo.mHostNameBuffer); Log(" HostAddr: %s", AsCoreType(&aInfo.mHostAddress).ToString().AsCString()); Log(" TxtDataLength: %u", aInfo.mTxtDataSize); Log(" TxtDataTTL: %u", aInfo.mTxtDataTtl); } const char *ServiceModeToString(Dns::Client::QueryConfig::ServiceMode aMode) { static const char *const kServiceModeStrings[] = { "unspec", // kServiceModeUnspecified (0) "srv", // kServiceModeSrv (1) "txt", // kServiceModeTxt (2) "srv_txt", // kServiceModeSrvTxt (3) "srv_txt_sep", // kServiceModeSrvTxtSeparate (4) "srv_txt_opt", // kServiceModeSrvTxtOptimize (5) }; static_assert(Dns::Client::QueryConfig::kServiceModeUnspecified == 0, "Unspecified value is incorrect"); static_assert(Dns::Client::QueryConfig::kServiceModeSrv == 1, "Srv value is incorrect"); static_assert(Dns::Client::QueryConfig::kServiceModeTxt == 2, "Txt value is incorrect"); static_assert(Dns::Client::QueryConfig::kServiceModeSrvTxt == 3, "SrvTxt value is incorrect"); static_assert(Dns::Client::QueryConfig::kServiceModeSrvTxtSeparate == 4, "SrvTxtSeparate value is incorrect"); static_assert(Dns::Client::QueryConfig::kServiceModeSrvTxtOptimize == 5, "SrvTxtOptimize value is incorrect"); return kServiceModeStrings[aMode]; } //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - struct BrowseInfo { void Reset(void) { mCallbackCount = 0; } uint16_t mCallbackCount; Error mError; Dns::Name::Buffer mServiceName; uint16_t mNumInstances; }; static BrowseInfo sBrowseInfo; void BrowseCallback(otError aError, const otDnsBrowseResponse *aResponse, void *aContext) { const Dns::Client::BrowseResponse &response = AsCoreType(aResponse); Log("BrowseCallback"); Log(" Error: %s", ErrorToString(aError)); VerifyOrQuit(aContext == sInstance); sBrowseInfo.mCallbackCount++; sBrowseInfo.mError = aError; SuccessOrExit(aError); SuccessOrQuit(response.GetServiceName(sBrowseInfo.mServiceName, sizeof(sBrowseInfo.mServiceName))); Log(" ServiceName: %s", sBrowseInfo.mServiceName); for (uint16_t index = 0;; index++) { Dns::Name::LabelBuffer instLabel; Error error; error = response.GetServiceInstance(index, instLabel, sizeof(instLabel)); if (error == kErrorNotFound) { sBrowseInfo.mNumInstances = index; break; } SuccessOrQuit(error); Log(" %2u) %s", index + 1, instLabel); } exit: return; } //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static constexpr uint8_t kMaxHostAddresses = 10; static constexpr uint16_t kMaxTxtBuffer = 256; struct ResolveServiceInfo { void Reset(void) { memset(this, 0, sizeof(*this)); mInfo.mHostNameBuffer = mNameBuffer; mInfo.mHostNameBufferSize = sizeof(mNameBuffer); mInfo.mTxtData = mTxtBuffer; mInfo.mTxtDataSize = sizeof(mTxtBuffer); }; uint16_t mCallbackCount; Error mError; Dns::Client::ServiceInfo mInfo; Dns::Name::Buffer mNameBuffer; uint8_t mTxtBuffer[kMaxTxtBuffer]; Ip6::Address mHostAddresses[kMaxHostAddresses]; uint8_t mNumHostAddresses; }; static ResolveServiceInfo sResolveServiceInfo; void ServiceCallback(otError aError, const otDnsServiceResponse *aResponse, void *aContext) { const Dns::Client::ServiceResponse &response = AsCoreType(aResponse); Dns::Name::LabelBuffer instLabel; Dns::Name::Buffer serviceName; Log("ServiceCallback"); Log(" Error: %s", ErrorToString(aError)); VerifyOrQuit(aContext == sInstance); SuccessOrQuit(response.GetServiceName(instLabel, sizeof(instLabel), serviceName, sizeof(serviceName))); Log(" InstLabel: %s", instLabel); Log(" ServiceName: %s", serviceName); sResolveServiceInfo.mCallbackCount++; sResolveServiceInfo.mError = aError; SuccessOrExit(aError); SuccessOrQuit(response.GetServiceInfo(sResolveServiceInfo.mInfo)); for (uint8_t index = 0; index < kMaxHostAddresses; index++) { Error error; uint32_t ttl; error = response.GetHostAddress(sResolveServiceInfo.mInfo.mHostNameBuffer, index, sResolveServiceInfo.mHostAddresses[index], ttl); if (error == kErrorNotFound) { sResolveServiceInfo.mNumHostAddresses = index; break; } SuccessOrQuit(error); } LogServiceInfo(sResolveServiceInfo.mInfo); Log(" NumHostAddresses: %u", sResolveServiceInfo.mNumHostAddresses); for (uint8_t index = 0; index < sResolveServiceInfo.mNumHostAddresses; index++) { Log(" %s", sResolveServiceInfo.mHostAddresses[index].ToString().AsCString()); } exit: return; } //---------------------------------------------------------------------------------------------------------------------- void TestDnsClient(void) { static constexpr uint8_t kNumAddresses = 2; static const char *const kAddresses[kNumAddresses] = {"2001::beef:cafe", "fd00:1234:5678:9abc::1"}; const Dns::Client::QueryConfig::ServiceMode kServiceModes[] = { Dns::Client::QueryConfig::kServiceModeSrv, Dns::Client::QueryConfig::kServiceModeTxt, Dns::Client::QueryConfig::kServiceModeSrvTxt, Dns::Client::QueryConfig::kServiceModeSrvTxtSeparate, Dns::Client::QueryConfig::kServiceModeSrvTxtOptimize, }; Array addresses; Srp::Server *srpServer; Srp::Client *srpClient; Srp::Client::Service service1; Srp::Client::Service service2; Dns::Client *dnsClient; Dns::Client::QueryConfig queryConfig; Dns::ServiceDiscovery::Server *dnsServer; Dns::ServiceDiscovery::Server::Counters oldServerCounters; Dns::ServiceDiscovery::Server::Counters newServerCounters; uint16_t heapAllocations; Log("--------------------------------------------------------------------------------------------"); Log("TestDnsClient"); InitTest(); for (const char *addrString : kAddresses) { otNetifAddress netifAddr; memset(&netifAddr, 0, sizeof(netifAddr)); SuccessOrQuit(AsCoreType(&netifAddr.mAddress).FromString(addrString)); netifAddr.mPrefixLength = 64; netifAddr.mAddressOrigin = OT_ADDRESS_ORIGIN_MANUAL; netifAddr.mPreferred = true; netifAddr.mValid = true; SuccessOrQuit(otIp6AddUnicastAddress(sInstance, &netifAddr)); SuccessOrQuit(addresses.PushBack(AsCoreType(&netifAddr.mAddress))); } srpServer = &sInstance->Get(); srpClient = &sInstance->Get(); dnsClient = &sInstance->Get(); dnsServer = &sInstance->Get(); heapAllocations = sHeapAllocatedPtrs.GetLength(); PrepareService1(service1); PrepareService2(service2); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Start SRP server. SuccessOrQuit(srpServer->SetAddressMode(Srp::Server::kAddressModeUnicast)); VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateDisabled); srpServer->SetEnabled(true); VerifyOrQuit(srpServer->GetState() != Srp::Server::kStateDisabled); AdvanceTime(10000); VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateRunning); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Start SRP client. srpClient->EnableAutoStartMode(nullptr, nullptr); VerifyOrQuit(srpClient->IsAutoStartModeEnabled()); AdvanceTime(2000); VerifyOrQuit(srpClient->IsRunning()); SuccessOrQuit(srpClient->SetHostName(kHostName)); SuccessOrQuit(srpClient->EnableAutoHostAddress()); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Register two services on SRP. SuccessOrQuit(srpClient->AddService(service1)); SuccessOrQuit(srpClient->AddService(service2)); AdvanceTime(2 * 1000); VerifyOrQuit(service1.GetState() == Srp::Client::kRegistered); VerifyOrQuit(service2.GetState() == Srp::Client::kRegistered); ValidateHost(*srpServer, kHostName); Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Check DNS Client's default config VerifyOrQuit(dnsClient->GetDefaultConfig().GetServiceMode() == Dns::Client::QueryConfig::kServiceModeSrvTxtOptimize); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Validate DNS Client `Browse()` sBrowseInfo.Reset(); Log("Browse(%s)", kService1FullName); SuccessOrQuit(dnsClient->Browse(kService1FullName, BrowseCallback, sInstance)); AdvanceTime(100); VerifyOrQuit(sBrowseInfo.mCallbackCount == 1); SuccessOrQuit(sBrowseInfo.mError); VerifyOrQuit(sBrowseInfo.mNumInstances == 1); sBrowseInfo.Reset(); Log("Browse(%s)", kService2FullName); SuccessOrQuit(dnsClient->Browse(kService2FullName, BrowseCallback, sInstance)); AdvanceTime(100); VerifyOrQuit(sBrowseInfo.mCallbackCount == 1); SuccessOrQuit(sBrowseInfo.mError); VerifyOrQuit(sBrowseInfo.mNumInstances == 1); sBrowseInfo.Reset(); Log("Browse(%s)", kService2SubTypeFullName); SuccessOrQuit(dnsClient->Browse(kService2SubTypeFullName, BrowseCallback, sInstance)); AdvanceTime(100); VerifyOrQuit(sBrowseInfo.mCallbackCount == 1); SuccessOrQuit(sBrowseInfo.mError); VerifyOrQuit(sBrowseInfo.mNumInstances == 1); sBrowseInfo.Reset(); Log("Browse() for unknown service"); SuccessOrQuit(dnsClient->Browse("_unknown._udp.default.service.arpa.", BrowseCallback, sInstance)); AdvanceTime(100); VerifyOrQuit(sBrowseInfo.mCallbackCount == 1); VerifyOrQuit(sBrowseInfo.mError == kErrorNotFound); Log("Issue four parallel `Browse()` at the same time"); sBrowseInfo.Reset(); SuccessOrQuit(dnsClient->Browse(kService1FullName, BrowseCallback, sInstance)); SuccessOrQuit(dnsClient->Browse(kService2FullName, BrowseCallback, sInstance)); SuccessOrQuit(dnsClient->Browse("_unknown._udp.default.service.arpa.", BrowseCallback, sInstance)); SuccessOrQuit(dnsClient->Browse("_unknown2._udp.default.service.arpa.", BrowseCallback, sInstance)); AdvanceTime(100); VerifyOrQuit(sBrowseInfo.mCallbackCount == 4); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Validate DNS Client `ResolveService()` using all service modes for (Dns::Client::QueryConfig::ServiceMode mode : kServiceModes) { Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); Log("ResolveService(%s,%s) with ServiceMode: %s", kInstance1Label, kService1FullName, ServiceModeToString(mode)); queryConfig.Clear(); queryConfig.mServiceMode = static_cast(mode); sResolveServiceInfo.Reset(); SuccessOrQuit( dnsClient->ResolveService(kInstance1Label, kService1FullName, ServiceCallback, sInstance, &queryConfig)); AdvanceTime(100); VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 1); SuccessOrQuit(sResolveServiceInfo.mError); if (mode != Dns::Client::QueryConfig::kServiceModeTxt) { VerifyOrQuit(sResolveServiceInfo.mInfo.mTtl != 0); VerifyOrQuit(sResolveServiceInfo.mInfo.mPort == service1.mPort); VerifyOrQuit(sResolveServiceInfo.mInfo.mWeight == service1.mWeight); VerifyOrQuit(strcmp(sResolveServiceInfo.mInfo.mHostNameBuffer, kHostFullName) == 0); VerifyOrQuit(sResolveServiceInfo.mNumHostAddresses == kNumAddresses); VerifyOrQuit(AsCoreType(&sResolveServiceInfo.mInfo.mHostAddress) == sResolveServiceInfo.mHostAddresses[0]); for (uint8_t index = 0; index < kNumAddresses; index++) { VerifyOrQuit(addresses.Contains(sResolveServiceInfo.mHostAddresses[index])); } } if (mode != Dns::Client::QueryConfig::kServiceModeSrv) { VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataTtl != 0); VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataSize != 0); } } Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); Log("Set TestMode on server to reject multi-question queries and send error"); dnsServer->SetTestMode(Dns::ServiceDiscovery::Server::kTestModeRejectMultiQuestionQuery); Log("ResolveService(%s,%s) with ServiceMode %s", kInstance1Label, kService1FullName, ServiceModeToString(Dns::Client::QueryConfig::kServiceModeSrvTxtOptimize)); queryConfig.Clear(); queryConfig.mServiceMode = static_cast(Dns::Client::QueryConfig::kServiceModeSrvTxtOptimize); sResolveServiceInfo.Reset(); SuccessOrQuit( dnsClient->ResolveService(kInstance1Label, kService1FullName, ServiceCallback, sInstance, &queryConfig)); AdvanceTime(200); VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 1); SuccessOrQuit(sResolveServiceInfo.mError); // Use `kServiceModeSrvTxt` and check that server does reject two questions. Log("ResolveService(%s,%s) with ServiceMode %s", kInstance1Label, kService1FullName, ServiceModeToString(Dns::Client::QueryConfig::kServiceModeSrvTxt)); queryConfig.Clear(); queryConfig.mServiceMode = static_cast(Dns::Client::QueryConfig::kServiceModeSrvTxt); sResolveServiceInfo.Reset(); SuccessOrQuit( dnsClient->ResolveService(kInstance1Label, kService1FullName, ServiceCallback, sInstance, &queryConfig)); AdvanceTime(200); VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 1); VerifyOrQuit(sResolveServiceInfo.mError != kErrorNone); dnsServer->SetTestMode(Dns::ServiceDiscovery::Server::kTestModeDisabled); Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); Log("Set TestMode on server to ignore multi-question queries (send no response)"); dnsServer->SetTestMode(Dns::ServiceDiscovery::Server::kTestModeIgnoreMultiQuestionQuery); Log("ResolveService(%s,%s) with ServiceMode %s", kInstance1Label, kService1FullName, ServiceModeToString(Dns::Client::QueryConfig::kServiceModeSrvTxtOptimize)); queryConfig.Clear(); queryConfig.mServiceMode = static_cast(Dns::Client::QueryConfig::kServiceModeSrvTxtOptimize); sResolveServiceInfo.Reset(); SuccessOrQuit( dnsClient->ResolveService(kInstance1Label, kService1FullName, ServiceCallback, sInstance, &queryConfig)); AdvanceTime(10 * 1000); // Wait longer than client response timeout. VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 1); SuccessOrQuit(sResolveServiceInfo.mError); // Use `kServiceModeSrvTxt` and check that server does ignore two questions. Log("ResolveService(%s,%s) with ServiceMode %s", kInstance1Label, kService1FullName, ServiceModeToString(Dns::Client::QueryConfig::kServiceModeSrvTxt)); queryConfig.Clear(); queryConfig.mServiceMode = static_cast(Dns::Client::QueryConfig::kServiceModeSrvTxt); sResolveServiceInfo.Reset(); SuccessOrQuit( dnsClient->ResolveService(kInstance1Label, kService1FullName, ServiceCallback, sInstance, &queryConfig)); // Wait for the client to time out after exhausting all retry attempts, and // ensure that a `kErrorResponseTimeout` error is reported. AdvanceTime(45 * 1000); VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 1); VerifyOrQuit(sResolveServiceInfo.mError == kErrorResponseTimeout); dnsServer->SetTestMode(Dns::ServiceDiscovery::Server::kTestModeDisabled); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Validate DNS Client `ResolveService()` using all service modes // when sever does not provide any RR in the addition data section. for (Dns::Client::QueryConfig::ServiceMode mode : kServiceModes) { Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); Log("Set TestMode on server to not include any RR in additional section"); dnsServer->SetTestMode(Dns::ServiceDiscovery::Server::kTestModeEmptyAdditionalSection); Log("ResolveService(%s,%s) with ServiceMode: %s", kInstance1Label, kService1FullName, ServiceModeToString(mode)); queryConfig.Clear(); queryConfig.mServiceMode = static_cast(mode); sResolveServiceInfo.Reset(); SuccessOrQuit( dnsClient->ResolveService(kInstance1Label, kService1FullName, ServiceCallback, sInstance, &queryConfig)); AdvanceTime(100); VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 1); SuccessOrQuit(sResolveServiceInfo.mError); if (mode != Dns::Client::QueryConfig::kServiceModeTxt) { VerifyOrQuit(sResolveServiceInfo.mInfo.mTtl != 0); VerifyOrQuit(sResolveServiceInfo.mInfo.mPort == service1.mPort); VerifyOrQuit(sResolveServiceInfo.mInfo.mWeight == service1.mWeight); VerifyOrQuit(strcmp(sResolveServiceInfo.mInfo.mHostNameBuffer, kHostFullName) == 0); } if (mode != Dns::Client::QueryConfig::kServiceModeSrv) { VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataTtl != 0); VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataSize != 0); } // Since server is using `kTestModeEmptyAdditionalSection`, there // should be no AAAA records for host address. VerifyOrQuit(AsCoreType(&sResolveServiceInfo.mInfo.mHostAddress).IsUnspecified()); VerifyOrQuit(sResolveServiceInfo.mNumHostAddresses == 0); } dnsServer->SetTestMode(Dns::ServiceDiscovery::Server::kTestModeDisabled); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Validate DNS Client `ResolveServiceAndHostAddress()` using all service modes // with different TestMode configs on server: // - Normal behavior when server provides AAAA records for host in // additional section. // - Server provides no records in additional section. We validate that // client will send separate query to resolve host address. for (Dns::Client::QueryConfig::ServiceMode mode : kServiceModes) { for (uint8_t testIter = 0; testIter <= 1; testIter++) { Error error; Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); if (testIter == 1) { Log("Set TestMode on server to not include any RR in additional section"); dnsServer->SetTestMode(Dns::ServiceDiscovery::Server::kTestModeEmptyAdditionalSection); } else { dnsServer->SetTestMode(Dns::ServiceDiscovery::Server::kTestModeDisabled); } Log("ResolveServiceAndHostAddress(%s,%s) with ServiceMode: %s", kInstance1Label, kService1FullName, ServiceModeToString(mode)); queryConfig.Clear(); queryConfig.mServiceMode = static_cast(mode); sResolveServiceInfo.Reset(); error = dnsClient->ResolveServiceAndHostAddress(kInstance1Label, kService1FullName, ServiceCallback, sInstance, &queryConfig); if (mode == Dns::Client::QueryConfig::kServiceModeTxt) { Log("ResolveServiceAndHostAddress() with ServiceMode: %s failed correctly", ServiceModeToString(mode)); VerifyOrQuit(error == kErrorInvalidArgs); continue; } SuccessOrQuit(error); AdvanceTime(100); VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 1); SuccessOrQuit(sResolveServiceInfo.mError); if (mode != Dns::Client::QueryConfig::kServiceModeTxt) { VerifyOrQuit(sResolveServiceInfo.mInfo.mTtl != 0); VerifyOrQuit(sResolveServiceInfo.mInfo.mPort == service1.mPort); VerifyOrQuit(sResolveServiceInfo.mInfo.mWeight == service1.mWeight); VerifyOrQuit(strcmp(sResolveServiceInfo.mInfo.mHostNameBuffer, kHostFullName) == 0); VerifyOrQuit(sResolveServiceInfo.mNumHostAddresses == kNumAddresses); VerifyOrQuit(AsCoreType(&sResolveServiceInfo.mInfo.mHostAddress) == sResolveServiceInfo.mHostAddresses[0]); for (uint8_t index = 0; index < kNumAddresses; index++) { VerifyOrQuit(addresses.Contains(sResolveServiceInfo.mHostAddresses[index])); } } if (mode != Dns::Client::QueryConfig::kServiceModeSrv) { VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataTtl != 0); VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataSize != 0); } } } dnsServer->SetTestMode(Dns::ServiceDiscovery::Server::kTestModeDisabled); Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); Log("Set TestMode on server to not include any RR in additional section AND to only accept single question"); dnsServer->SetTestMode(Dns::ServiceDiscovery::Server::kTestModeEmptyAdditionalSection + Dns::ServiceDiscovery::Server::kTestModeRejectMultiQuestionQuery); Log("ResolveServiceAndHostAddress(%s,%s) with ServiceMode: %s", kInstance1Label, kService1FullName, ServiceModeToString(Dns::Client::QueryConfig::kServiceModeSrvTxtOptimize)); queryConfig.Clear(); queryConfig.mServiceMode = static_cast(Dns::Client::QueryConfig::kServiceModeSrvTxtOptimize); oldServerCounters = dnsServer->GetCounters(); sResolveServiceInfo.Reset(); SuccessOrQuit(dnsClient->ResolveServiceAndHostAddress(kInstance1Label, kService1FullName, ServiceCallback, sInstance, &queryConfig)); AdvanceTime(100); VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 1); SuccessOrQuit(sResolveServiceInfo.mError); VerifyOrQuit(sResolveServiceInfo.mInfo.mTtl != 0); VerifyOrQuit(sResolveServiceInfo.mInfo.mPort == service1.mPort); VerifyOrQuit(sResolveServiceInfo.mInfo.mWeight == service1.mWeight); VerifyOrQuit(strcmp(sResolveServiceInfo.mInfo.mHostNameBuffer, kHostFullName) == 0); VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataTtl != 0); VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataSize != 0); VerifyOrQuit(sResolveServiceInfo.mNumHostAddresses == kNumAddresses); VerifyOrQuit(AsCoreType(&sResolveServiceInfo.mInfo.mHostAddress) == sResolveServiceInfo.mHostAddresses[0]); for (uint8_t index = 0; index < kNumAddresses; index++) { VerifyOrQuit(addresses.Contains(sResolveServiceInfo.mHostAddresses[index])); } newServerCounters = dnsServer->GetCounters(); Log("Validate (using server counter) that client first tried to query SRV/TXT together and failed"); Log("and then send separate queries (for SRV, TXT and AAAA)"); Log(" Total : %2u -> %2u", oldServerCounters.GetTotalQueries(), newServerCounters.GetTotalQueries()); Log(" Failed: %2u -> %2u", oldServerCounters.GetTotalFailedQueries(), newServerCounters.GetTotalFailedQueries()); VerifyOrQuit(newServerCounters.GetTotalFailedQueries() == 1 + oldServerCounters.GetTotalFailedQueries()); VerifyOrQuit(newServerCounters.GetTotalQueries() == 4 + oldServerCounters.GetTotalQueries()); Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); Log("Resolve service again now using `kServiceModeSrvTxtOptimize` as default config"); Log("Client should already know that server is not capable of handling multi-question query"); queryConfig.Clear(); queryConfig.mServiceMode = static_cast(Dns::Client::QueryConfig::kServiceModeSrvTxtOptimize); dnsClient->SetDefaultConfig(queryConfig); Log("ResolveService(%s,%s)", kInstance1Label, kService1FullName); oldServerCounters = dnsServer->GetCounters(); sResolveServiceInfo.Reset(); SuccessOrQuit(dnsClient->ResolveService(kInstance1Label, kService1FullName, ServiceCallback, sInstance, nullptr)); AdvanceTime(100); VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 1); SuccessOrQuit(sResolveServiceInfo.mError); VerifyOrQuit(sResolveServiceInfo.mInfo.mTtl != 0); VerifyOrQuit(sResolveServiceInfo.mInfo.mPort == service1.mPort); VerifyOrQuit(sResolveServiceInfo.mInfo.mWeight == service1.mWeight); VerifyOrQuit(strcmp(sResolveServiceInfo.mInfo.mHostNameBuffer, kHostFullName) == 0); VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataTtl != 0); VerifyOrQuit(sResolveServiceInfo.mInfo.mTxtDataSize != 0); newServerCounters = dnsServer->GetCounters(); Log("Client should already know that server is not capable of handling multi-question query"); Log("Check server counters to validate that client did send separate queries for TXT and SRV"); Log(" Total : %2u -> %2u", oldServerCounters.GetTotalQueries(), newServerCounters.GetTotalQueries()); Log(" Failed: %2u -> %2u", oldServerCounters.GetTotalFailedQueries(), newServerCounters.GetTotalFailedQueries()); VerifyOrQuit(newServerCounters.GetTotalFailedQueries() == oldServerCounters.GetTotalFailedQueries()); VerifyOrQuit(newServerCounters.GetTotalQueries() == 2 + oldServerCounters.GetTotalQueries()); dnsServer->SetTestMode(Dns::ServiceDiscovery::Server::kTestModeDisabled); Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); Log("Stop DNS-SD server"); dnsServer->Stop(); Log("ResolveService(%s,%s) with ServiceMode %s", kInstance1Label, kService1FullName, ServiceModeToString(Dns::Client::QueryConfig::kServiceModeSrvTxtSeparate)); queryConfig.Clear(); queryConfig.mServiceMode = static_cast(Dns::Client::QueryConfig::kServiceModeSrvTxtSeparate); sResolveServiceInfo.Reset(); SuccessOrQuit( dnsClient->ResolveService(kInstance1Label, kService1FullName, ServiceCallback, sInstance, &queryConfig)); AdvanceTime(25 * 1000); VerifyOrQuit(sResolveServiceInfo.mCallbackCount == 1); VerifyOrQuit(sResolveServiceInfo.mError == kErrorResponseTimeout); Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Disable SRP server, verify that all heap allocations by SRP server // and/or by DNS Client are freed. Log("Disabling SRP server"); srpServer->SetEnabled(false); AdvanceTime(100); VerifyOrQuit(heapAllocations == sHeapAllocatedPtrs.GetLength()); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Finalize OT instance and validate all heap allocations are freed. Log("Finalizing OT instance"); FinalizeTest(); VerifyOrQuit(sHeapAllocatedPtrs.IsEmpty()); Log("End of TestDnsClient"); } //---------------------------------------------------------------------------------------------------------------------- Dns::Name::Buffer sLastSubscribeName; Dns::Name::Buffer sLastUnsubscribeName; void QuerySubscribe(void *aContext, const char *aFullName) { uint16_t length = StringLength(aFullName, Dns::Name::kMaxNameSize); Log("QuerySubscribe(%s)", aFullName); VerifyOrQuit(aContext == sInstance); VerifyOrQuit(length < Dns::Name::kMaxNameSize); strcpy(sLastSubscribeName, aFullName); } void QueryUnsubscribe(void *aContext, const char *aFullName) { uint16_t length = StringLength(aFullName, Dns::Name::kMaxNameSize); Log("QueryUnsubscribe(%s)", aFullName); VerifyOrQuit(aContext == sInstance); VerifyOrQuit(length < Dns::Name::kMaxNameSize); strcpy(sLastUnsubscribeName, aFullName); } void TestDnssdServerProxyCallback(void) { Srp::Server *srpServer; Srp::Client *srpClient; Dns::Client *dnsClient; Dns::ServiceDiscovery::Server *dnsServer; otDnssdServiceInstanceInfo instanceInfo; Log("--------------------------------------------------------------------------------------------"); Log("TestDnssdServerProxyCallback"); InitTest(); srpServer = &sInstance->Get(); srpClient = &sInstance->Get(); dnsClient = &sInstance->Get(); dnsServer = &sInstance->Get(); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Start SRP server. SuccessOrQuit(srpServer->SetAddressMode(Srp::Server::kAddressModeUnicast)); VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateDisabled); srpServer->SetEnabled(true); VerifyOrQuit(srpServer->GetState() != Srp::Server::kStateDisabled); AdvanceTime(10000); VerifyOrQuit(srpServer->GetState() == Srp::Server::kStateRunning); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Start SRP client. srpClient->EnableAutoStartMode(nullptr, nullptr); VerifyOrQuit(srpClient->IsAutoStartModeEnabled()); AdvanceTime(2000); VerifyOrQuit(srpClient->IsRunning()); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Set the query subscribe/unsubscribe callbacks on server dnsServer->SetQueryCallbacks(QuerySubscribe, QueryUnsubscribe, sInstance); Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); sLastSubscribeName[0] = '\0'; sLastUnsubscribeName[0] = '\0'; sBrowseInfo.Reset(); Log("Browse(%s)", kService1FullName); SuccessOrQuit(dnsClient->Browse(kService1FullName, BrowseCallback, sInstance)); AdvanceTime(10); VerifyOrQuit(strcmp(sLastSubscribeName, kService1FullName) == 0); VerifyOrQuit(strcmp(sLastUnsubscribeName, "") == 0); VerifyOrQuit(sBrowseInfo.mCallbackCount == 0); Log("Invoke subscribe callback"); memset(&instanceInfo, 0, sizeof(instanceInfo)); instanceInfo.mFullName = kInstance1FullName; instanceInfo.mHostName = kHostFullName; instanceInfo.mPort = 200; dnsServer->HandleDiscoveredServiceInstance(kService1FullName, instanceInfo); AdvanceTime(10); VerifyOrQuit(sBrowseInfo.mCallbackCount == 1); SuccessOrQuit(sBrowseInfo.mError); VerifyOrQuit(sBrowseInfo.mNumInstances == 1); VerifyOrQuit(strcmp(sLastUnsubscribeName, kService1FullName) == 0); Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); sLastSubscribeName[0] = '\0'; sLastUnsubscribeName[0] = '\0'; sBrowseInfo.Reset(); Log("Browse(%s)", kService2FullName); SuccessOrQuit(dnsClient->Browse(kService2FullName, BrowseCallback, sInstance)); AdvanceTime(10); VerifyOrQuit(strcmp(sLastSubscribeName, kService2FullName) == 0); VerifyOrQuit(strcmp(sLastUnsubscribeName, "") == 0); Log("Invoke subscribe callback for wrong name"); memset(&instanceInfo, 0, sizeof(instanceInfo)); instanceInfo.mFullName = kInstance1FullName; instanceInfo.mHostName = kHostFullName; instanceInfo.mPort = 200; dnsServer->HandleDiscoveredServiceInstance(kService1FullName, instanceInfo); AdvanceTime(10); VerifyOrQuit(sBrowseInfo.mCallbackCount == 0); Log("Invoke subscribe callback for correct name"); memset(&instanceInfo, 0, sizeof(instanceInfo)); instanceInfo.mFullName = kInstance2FullName; instanceInfo.mHostName = kHostFullName; instanceInfo.mPort = 200; dnsServer->HandleDiscoveredServiceInstance(kService2FullName, instanceInfo); AdvanceTime(10); VerifyOrQuit(sBrowseInfo.mCallbackCount == 1); SuccessOrQuit(sBrowseInfo.mError); VerifyOrQuit(sBrowseInfo.mNumInstances == 1); VerifyOrQuit(strcmp(sLastUnsubscribeName, kService2FullName) == 0); Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); sLastSubscribeName[0] = '\0'; sLastUnsubscribeName[0] = '\0'; sBrowseInfo.Reset(); Log("Browse(%s)", kService2FullName); SuccessOrQuit(dnsClient->Browse(kService2FullName, BrowseCallback, sInstance)); AdvanceTime(10); VerifyOrQuit(strcmp(sLastSubscribeName, kService2FullName) == 0); VerifyOrQuit(strcmp(sLastUnsubscribeName, "") == 0); Log("Do not invoke subscribe callback and let query to timeout"); // Query timeout is set to 6 seconds AdvanceTime(5000); VerifyOrQuit(sBrowseInfo.mCallbackCount == 0); AdvanceTime(2000); VerifyOrQuit(sBrowseInfo.mCallbackCount == 1); SuccessOrQuit(sBrowseInfo.mError); VerifyOrQuit(sBrowseInfo.mNumInstances == 0); VerifyOrQuit(strcmp(sLastUnsubscribeName, kService2FullName) == 0); Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); sLastSubscribeName[0] = '\0'; sLastUnsubscribeName[0] = '\0'; sBrowseInfo.Reset(); Log("Browse(%s)", kService2FullName); SuccessOrQuit(dnsClient->Browse(kService2FullName, BrowseCallback, sInstance)); AdvanceTime(10); VerifyOrQuit(strcmp(sLastSubscribeName, kService2FullName) == 0); VerifyOrQuit(strcmp(sLastUnsubscribeName, "") == 0); VerifyOrQuit(sBrowseInfo.mCallbackCount == 0); Log("Do not invoke subscribe callback and stop server"); dnsServer->Stop(); AdvanceTime(10); VerifyOrQuit(sBrowseInfo.mCallbackCount == 1); VerifyOrQuit(sBrowseInfo.mError != kErrorNone); VerifyOrQuit(strcmp(sLastUnsubscribeName, kService2FullName) == 0); Log("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Finalize OT instance and validate all heap allocations are freed. Log("Finalizing OT instance"); FinalizeTest(); Log("End of TestDnssdServerProxyCallback"); } #endif // ENABLE_DNS_TEST int main(void) { #if ENABLE_DNS_TEST TestDnsClient(); TestDnssdServerProxyCallback(); printf("All tests passed\n"); #else printf("DNS_CLIENT or DSNSSD_SERVER feature is not enabled\n"); #endif return 0; }