/* * Copyright (c) 2026, 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 "platform/nexus_core.hpp" #include "platform/nexus_node.hpp" #include "net/mdns.hpp" namespace ot { namespace Nexus { /** * Time to advance for a node to form a network and become leader, in milliseconds. */ static constexpr uint32_t kFormNetworkTime = 13 * 1000; /** * Time to advance for a node to join as a child and upgrade to a router, in milliseconds. */ static constexpr uint32_t kJoinNetworkTime = 200 * 1000; /** * Time to advance for the BR to perform automatic actions (RA, Network Data), in milliseconds. */ static constexpr uint32_t kBrActionTime = 20 * 1000; /** * Time to wait for SRP registration to complete. */ static constexpr uint32_t kSrpRegistrationTime = 5 * 1000; /** * Time to wait for DNS response. */ static constexpr uint32_t kDnsResponseTime = 20 * 1000; /** * Infrastructure interface index. */ static constexpr uint32_t kInfraIfIndex = 1; /** * Service and Host names. */ static const char kInfraServiceType[] = "_infra-test._udp"; static const char kInfraHostName[] = "host-test-eth"; static const char kDiscoveryProxyType[] = "_infra-test._udp.default.service.arpa."; struct ServiceInfo { const char *mInstanceName; uint16_t mPort; const char *mTxtData; }; static const ServiceInfo kInfraServices[] = { {"service-test-1", 55551, "\x0Adummy=abcd\x0Fthread-test=a49"}, {"service-test-2", 55552, "\x04a=2\x12thread-test=b50_test"}, {"service-test-3", 55553, "\x0Ba_dummy=42\x0Cthread-test="}, {"service-test-4", 55554, "\x0Dthread-test=1\x15ignorethis=thread-test="}, {"service-test-5", 55555, "\x0B" "a88=thread-\x10THREAD-TEST=C51\x14ignorethis=thread-test"}, }; static void RegisterMdnsServices(Node &aEth1) { Dns::Multicast::Core &mdns = aEth1.Get(); SuccessOrQuit(mdns.SetEnabled(true, kInfraIfIndex)); { Dns::Multicast::Core::Host host; ClearAllBytes(host); host.mHostName = kInfraHostName; host.mAddresses = &aEth1.mInfraIf.GetAddresses()[1]; // ULA address host.mAddressesLength = 1; SuccessOrQuit(mdns.RegisterHost(host, 0, nullptr)); } for (const ServiceInfo &info : kInfraServices) { Dns::Multicast::Core::Service service; ClearAllBytes(service); service.mServiceInstance = info.mInstanceName; service.mServiceType = kInfraServiceType; service.mHostName = kInfraHostName; service.mPort = info.mPort; service.mTxtData = reinterpret_cast(info.mTxtData); service.mTxtDataLength = static_cast(strlen(info.mTxtData)); SuccessOrQuit(mdns.RegisterService(service, 0, nullptr)); } } void Test_1_3_DPR_TC_1(const char *aJsonFileName) { /** * 5.1. [1.3] [CERT] Service discovery of services on Thread and Infrastructure - Single BR * * 5.1.1 Purpose * To test the following: * 1. Discovery proxy on the BR to perform mDNS query on the infrastructure link in the case where it doesn’t have * any answer. * 2. Respond to unicast DNS queries made by a Thread Device on the Thread interface. * 3. Thread Device can discover services advertised by infrastructure device (mDNS) via BR DUT. * 4. Single BR case * * 5.1.2 Topology * - BR_1 (DUT) - Thread Border Router, and the Leader * - ED_1 - Test Bed device operating as a Thread End Device, attached to BR_1 * - Eth_1 - Test Bed border router device on an Adjacent Infrastructure Link * * Spec Reference | V1.1 Section | V1.3.0 Section * --------------------|--------------|--------------- * Discovery Proxy | N/A | 5.1 */ Core nexus; Node &br1 = nexus.CreateNode(); Node &ed1 = nexus.CreateNode(); Node ð1 = nexus.CreateNode(); Srp::Client::Service service; Dns::TxtEntry txtEntry; br1.SetName("BR_1"); ed1.SetName("ED_1"); eth1.SetName("Eth_1"); nexus.AdvanceTime(0); SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelNote)); Log("---------------------------------------------------------------------------------------"); Log("Step 1: Device: BR_1 (DUT) Description (DPR-5.1): Enable"); /** * Step 1 * - Device: BR_1 (DUT) * - Description (DPR-5.1): Enable * - Pass Criteria: N/A */ br1.Form(); nexus.AdvanceTime(kFormNetworkTime); VerifyOrQuit(br1.Get().IsLeader()); Log("---------------------------------------------------------------------------------------"); Log("Step 2: Device: Eth_1, ED_1 Description (DPR-5.1): Enable"); /** * Step 2 * - Device: Eth_1, ED_1 * - Description (DPR-5.1): Enable * - Pass Criteria: N/A */ ed1.Join(br1, Node::kAsFed); nexus.AdvanceTime(kJoinNetworkTime); VerifyOrQuit(ed1.Get().IsAttached()); Log("---------------------------------------------------------------------------------------"); Log("Step 3: Device: BR_1 (DUT) Description (DPR-5.1): Automatically adds its SRP Server information..."); /** * Step 3 * - Device: BR_1 (DUT) * - Description (DPR-5.1): Automatically adds its SRP Server information in the Thread Network Data. * Automatically configures an OMR prefix on the Thread Network. Automatically configures a ULA prefix on the AIL. * - Pass Criteria: * - The DUT’s SRP Server information MUST appear in the Thread Network Data. * - It MUST be a DNS/SRP Unicast Dataset, or DNS/SRP Anycast Dataset, or both. */ br1.Get().Init(kInfraIfIndex, true); br1.Get().Init(); SuccessOrQuit(br1.Get().SetEnabled(true)); SuccessOrQuit(br1.Get().SetAddressMode(Srp::Server::kAddressModeUnicast)); br1.Get().SetEnabled(true); nexus.AdvanceTime(kBrActionTime); Ip6::Prefix omrPrefix; SuccessOrQuit(br1.Get().GetOmrPrefix(omrPrefix)); nexus.AddTestVar("OMR_PREFIX", omrPrefix.ToString().AsCString()); Ip6::Prefix pre1Prefix; SuccessOrQuit(br1.Get().GetOnLinkPrefix(pre1Prefix)); nexus.AddTestVar("PRE_1_PREFIX", pre1Prefix.ToString().AsCString()); Log("---------------------------------------------------------------------------------------"); Log("Step 4: Device: Eth_1 Description (DPR-5.1): Harness instructs device to advertise N=5 services..."); /** * Step 4 * - Device: Eth_1 * - Description (DPR-5.1): Harness instructs device to advertise N=5 services using mDNS. * - Pass Criteria: N/A */ RegisterMdnsServices(eth1); nexus.AdvanceTime(kDnsResponseTime); Log("---------------------------------------------------------------------------------------"); Log("Step 5: Device: ED_1 Description (DPR-5.1): Harness instructs the device to send an SRP Update..."); /** * Step 5 * - Device: ED_1 * - Description (DPR-5.1): Harness instructs the device to send an SRP Update to DUT as follows: * $ORIGIN default.service.arpa. service-test-6._thread-test._udp ( SRV 0 0 55551 host-test-1 TXT * thread-te=trick=value ) host-test-1 AAAA * - Pass Criteria: N/A */ ed1.Get().EnableAutoStartMode(nullptr, nullptr); SuccessOrQuit(ed1.Get().SetHostName("host-test-1")); SuccessOrQuit(ed1.Get().EnableAutoHostAddress()); ClearAllBytes(service); service.mName = "_thread-test._udp"; service.mInstanceName = "service-test-6"; service.mPort = 55551; txtEntry.mKey = "thread-te"; txtEntry.mValue = reinterpret_cast("trick=value"); txtEntry.mValueLength = 11; service.mTxtEntries = &txtEntry; service.mNumTxtEntries = 1; SuccessOrQuit(ed1.Get().AddService(service)); nexus.AdvanceTime(kSrpRegistrationTime); Log("---------------------------------------------------------------------------------------"); Log("Step 6: Device: BR_1 (DUT) Description (DPR-5.1): Automatically sends SRP Update Response with SUCCESS"); /** * Step 6 * - Device: BR_1 (DUT) * - Description (DPR-5.1): Automatically sends SRP Update Response with “SUCCESS”. * - Pass Criteria: * - The DUT MUST send SRP Update Response with status 0 ( “NoError” ). */ VerifyOrQuit(ed1.Get().GetHostInfo().GetState() == Srp::Client::kRegistered); Log("---------------------------------------------------------------------------------------"); Log("Step 7: Device: ED_1 Description (DPR-5.1): Harness instructs the device to unicast DNS query QType=PTR..."); /** * Step 7 * - Device: ED_1 * - Description (DPR-5.1): Harness instructs the device to unicast DNS query QType=PTR to the DUT for services * of type “_infra-test._udp.default.service.arpa.” * - Pass Criteria: N/A */ const Ip6::Address &br1Mleid = br1.Get().GetMeshLocalEid(); Dns::Client::QueryConfig queryConfig; queryConfig.Clear(); AsCoreType(&queryConfig.mServerSockAddr.mAddress) = br1Mleid; queryConfig.mServerSockAddr.mPort = Dns::ServiceDiscovery::Server::kPort; SuccessOrQuit(ed1.Get().Browse(kDiscoveryProxyType, nullptr, nullptr, &queryConfig)); Log("---------------------------------------------------------------------------------------"); Log("Step 8: Device: BR_1 (DUT) Description (DPR-5.1): Automatically MAY send mDNS query..."); /** * Step 8 * - Device: BR_1 (DUT) * - Description (DPR-5.1): Automatically MAY send mDNS query on the infrastructure link. * - Pass Criteria: N/A */ nexus.AdvanceTime(1000); Log("---------------------------------------------------------------------------------------"); Log("Step 9: Device: Eth_1 Description (DPR-5.1): In case DUT queried in step 8, it automatically sends mDNS..."); /** * Step 9 * - Device: Eth_1 * - Description (DPR-5.1): In case DUT queried in step 8, it automatically sends mDNS response with its active * services. * - Pass Criteria: N/A */ nexus.AdvanceTime(kDnsResponseTime); Log("---------------------------------------------------------------------------------------"); Log("Step 10: Device: BR_1 (DUT) Description (DPR-5.1): Automatically sends a DNS response to ED_1"); /** * Step 10 * - Device: BR_1 (DUT) * - Description (DPR-5.1): Automatically sends a DNS response to ED_1. * - Pass Criteria: N/A (validation of response in the next step) */ Log("---------------------------------------------------------------------------------------"); Log("Step 11: Device: ED_1 Description (DPR-5.1): Receives the DNS response with at least one of the N services"); /** * Step 11 * - Device: ED_1 * - Description (DPR-5.1): Receives the DNS response with at least one of the N services. * - Pass Criteria: * - The DUT must send a valid DNS Response, containing in the Answer records section at least one of the services * advertised by Eth_1, specifically the below where N is a number out of 1, 2, 3, 4, 5: * $ORIGIN default.service.arpa. _infra-test._udp ( PTR service-test-N._infra-test._udp ) * - It MAY contain in the Additional records section one or more of the below services identified by number N * plus the host (AAAA) record: * $ORIGIN default.service.arpa. service-test-1._infra-test._udp ( SRV 0 0 55551 host-test-eth TXT * dummy=abcd;thread-test=a49 ) ... host-test-eth AAAA * - Response MUST NOT contain any AAAA record with a link-local address. * - Response MUST NOT contain “service-test-6” records. * - Response MUST NOT contain any “host-test-1” records. */ nexus.SaveTestInfo(aJsonFileName); } } // namespace Nexus } // namespace ot int main(int argc, char *argv[]) { const char *jsonFile = (argc > 1) ? argv[argc - 1] : "test_1_3_DPR_TC_1.json"; ot::Nexus::Test_1_3_DPR_TC_1(jsonFile); printf("All tests passed\n"); return 0; }