Files
openthread/tests/nexus/test_1_3_SRPC_TC_5.cpp
Abtin Keshavarzian 2a120a9ddf [nexus] remove redundant mInfraIf initializations (#12851)
This commit removes redundant calls to `mInfraIf.Init()` and
`mInfraIf.AddAddress()` from various Nexus test cases.

The infrastructure interface (`mInfraIf`) is automatically initialized
and assigned a link-local address by the core framework when a new
`Node` is added. The `InfraIf::Init()` method derives the link-local
address from the MAC address and adds it to the interface. Therefore,
these explicit manual calls in individual test scripts are unnecessary
and can be removed to simplify the test setup.
2026-04-08 16:09:17 -05:00

572 lines
21 KiB
C++

/*
* 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 <stdio.h>
#include <string.h>
#include "platform/nexus_core.hpp"
#include "platform/nexus_node.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 a network, in milliseconds.
*/
static constexpr uint32_t kJoinNetworkTime = 10 * 1000;
/**
* Time to advance for the network to stabilize, in milliseconds.
*/
static constexpr uint32_t kStabilizationTime = 15 * 1000;
/**
* Time to wait for SRP registration to complete.
*/
static constexpr uint32_t kSrpRegistrationTime = 10 * 1000;
/**
* Time to advance for DNS query processing.
*/
static constexpr uint32_t kDnsQueryTime = 5 * 1000;
/**
* Infrastructure interface index.
*/
static constexpr uint32_t kInfraIfIndex = 1;
/**
* SRP Lease time in seconds.
*/
static constexpr uint32_t kSrpLease5m = 300;
/**
* SRP Key Lease time in seconds.
*/
static constexpr uint32_t kSrpKeyLease10m = 600;
/**
* SRP service ports.
*/
static constexpr uint16_t kSrpServicePort1 = 55551;
static constexpr uint16_t kSrpServicePort2 = 55552;
static constexpr uint16_t kSrpServicePort3 = 55553;
static constexpr uint16_t kSrpServicePort4 = 55554;
static constexpr uint16_t kSrpServicePort5 = 55555;
/**
* SRP service and host names.
*/
static const char kSrpServiceType[] = "_thread-test._udp";
static const char kSrpFullServiceType[] = "_thread-test._udp.default.service.arpa.";
static const char kSrpHostName[] = "host-test-2";
/**
* Expected values for "thread-test" key in TXT records.
*/
static const char *const kExpectedTxtValues[] = {
"a49", "b50_test", "", "1", "C51",
};
/**
* Test context to track progress.
*/
struct TestContext
{
Node *mNode;
uint8_t mBrowseCount;
uint8_t mResolveCount;
uint8_t mUdpCount;
};
void HandleResolveResponse(otError aError, const otDnsServiceResponse *aResponse, void *aContext)
{
TestContext *context = static_cast<TestContext *>(aContext);
const Dns::Client::ServiceResponse &response = *static_cast<const Dns::Client::ServiceResponse *>(aResponse);
Dns::Client::ServiceInfo serviceInfo;
Ip6::Address address;
uint32_t ttl;
Dns::Name::Buffer hostName;
static constexpr uint16_t kMaxTxtDataSize = 254;
uint8_t txtData[kMaxTxtDataSize];
Ip6::MessageInfo messageInfo;
Message *message;
if (aError != OT_ERROR_NONE)
{
Log("HandleResolveResponse error: %s", ErrorToString(aError));
SuccessOrQuit(aError);
}
serviceInfo.mHostNameBuffer = hostName;
serviceInfo.mHostNameBufferSize = sizeof(hostName);
serviceInfo.mTxtData = txtData;
serviceInfo.mTxtDataSize = sizeof(txtData);
SuccessOrQuit(response.GetServiceInfo(serviceInfo));
context->mResolveCount++;
/**
* Step 7
* - Device: TD_1 (DUT)
* - Description (SRPC-3.5): Harness receives each of the N service responses via API from the DUT Component.
* For each service, it uses the API to extract the TXT record key "thread-test" and compares against the
* expected value.
* - Pass Criteria:
* - The DUT MUST return the correct values for the "thread-test" key as defined below for each of the
* respective N services:
* - 1. a49
* - 2. b50 test
* - 3. (empty string of 0 characters)
* - 4. 1
* - 5. C51
*/
Log("Step 7: Verify TXT record key 'thread-test' for resolved service on port %u", serviceInfo.mPort);
Dns::TxtEntry::Iterator iterator;
Dns::TxtEntry entry;
bool found = false;
iterator.Init(serviceInfo.mTxtData, serviceInfo.mTxtDataSize);
while (iterator.GetNextEntry(entry) == kErrorNone)
{
if (entry.MatchesKey("thread-test"))
{
uint8_t index = 0;
switch (serviceInfo.mPort)
{
case kSrpServicePort1:
index = 0;
break;
case kSrpServicePort2:
index = 1;
break;
case kSrpServicePort3:
index = 2;
break;
case kSrpServicePort4:
index = 3;
break;
case kSrpServicePort5:
index = 4;
break;
default:
VerifyOrQuit(false, "Unknown port");
break;
}
if (entry.mValueLength == strlen(kExpectedTxtValues[index]))
{
if (entry.mValueLength == 0 || memcmp(entry.mValue, kExpectedTxtValues[index], entry.mValueLength) == 0)
{
found = true;
break;
}
}
}
}
VerifyOrQuit(found, "thread-test key not found or value mismatch");
/**
* Step 8
* - Device: TD 1 (DUT)
* - Description (SRPC-3.5): Harness uses API to resolve for each of the N services the IPv6 address and port of
* the service. For each returned/resolved service, Harness uses the API to send a UDP packet to the service's
* IPv6 address at the respective returned port number.
* - Pass Criteria:
* - The DUT MUST transmit 5 UDP packets to ED_2, to the following port numbers, which may be in any order:
* - 1. 55551
* - 2. 55552
* - 3. 55553
* - 4. 55554
* - 5. 55555
* - The DUT MAY send to either the ML-EID or OMR address of ED 2.
*/
Log("Step 8: Resolve service address and send UDP packet to port %u", serviceInfo.mPort);
{
bool hasMeshLocal = false;
for (uint16_t i = 0; response.GetHostAddress(hostName, i, address, ttl) == kErrorNone; i++)
{
if (context->mNode->Get<Mle::Mle>().IsMeshLocalAddress(address))
{
hasMeshLocal = true;
break;
}
}
if (!hasMeshLocal)
{
// If no mesh-local address was found, get the first address.
// This will fail and quit if there are no addresses at all.
SuccessOrQuit(response.GetHostAddress(hostName, 0, address, ttl));
}
}
messageInfo.SetPeerAddr(address);
messageInfo.SetPeerPort(serviceInfo.mPort);
message = context->mNode->Get<Ip6::Udp>().NewMessage();
VerifyOrQuit(message != nullptr);
SuccessOrQuit(message->AppendBytes("hello", 5));
SuccessOrQuit(context->mNode->Get<Ip6::Udp>().SendDatagram(*message, messageInfo));
context->mUdpCount++;
}
void HandleBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse, void *aContext)
{
TestContext *context = static_cast<TestContext *>(aContext);
const Dns::Client::BrowseResponse &response = *static_cast<const Dns::Client::BrowseResponse *>(aResponse);
Dns::Name::LabelBuffer label;
if (aError != OT_ERROR_NONE)
{
Log("HandleBrowseResponse error: %s", ErrorToString(aError));
SuccessOrQuit(aError);
}
for (uint16_t index = 0; response.GetServiceInstance(index, label, sizeof(label)) == kErrorNone; index++)
{
context->mBrowseCount++;
Log("Step 5/6: Found service instance %s, starting resolve", label);
SuccessOrQuit(context->mNode->Get<Dns::Client>().ResolveService(label, kSrpFullServiceType,
HandleResolveResponse, context));
}
}
void Test_1_3_SRPC_TC_5(const char *aJsonFileName)
{
/**
* 3.5. [1.3] [CERT] [COMPONENT] DNS-SD client discovers services
*
* 3.5.1. Purpose
* To test the following:
* - DNS-SD client discovers services and sends out correctly formatted DNS query
* - Client handling multiple service responses
* - Client handling of both mesh-local discovered services and off-mesh services
* - Note: Thread Product DUTs must skip this test.
*
* 3.5.2. Topology
* - BR 1-Border Router Reference Device and Leader
* - TD_1 (DUT)-Thread End Device or Thread Router DUT (Component DUT)
* - ED 2-Thread Reference End Device
* - Eth 1-Host Reference Device on AIL
*
* Spec Reference | V1.1 Section | V1.3.0 Section
* -----------------|--------------|---------------
* SRP Client | N/A | 2.3.2
*/
Core nexus;
Ip6::Address addresses[3];
Node &br1 = nexus.CreateNode();
Node &td1 = nexus.CreateNode();
Node &ed2 = nexus.CreateNode();
Node &eth1 = nexus.CreateNode();
br1.SetName("BR_1");
td1.SetName("TD_1");
ed2.SetName("ED_2");
eth1.SetName("Eth_1");
SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelNote));
Log("---------------------------------------------------------------------------------------");
Log("Step 1: Device: BR 1. ED 2, Eth 1 Description (SRPC-3.5): Enable");
/**
* Step 1
* - Device: BR 1. ED 2, Eth 1
* - Description (SRPC-3.5): Enable
* - Pass Criteria:
* - N/A
*/
br1.Form();
nexus.AdvanceTime(kFormNetworkTime);
ed2.Join(br1, Node::kAsFed);
nexus.AdvanceTime(kJoinNetworkTime);
Log("---------------------------------------------------------------------------------------");
Log("Step 2: Device: BR 1 Description (SRPC-3.5): Automatically adds its SRP Server information");
/**
* Step 2
* - Device: BR 1
* - Description (SRPC-3.5): Automatically adds its SRP Server information in the DNS/SRP Unicast Dataset in the
* Thread Network Data. Automatically configures an OMR prefix in Thread Network Data.
* - Pass Criteria:
* - N/A
*/
br1.Get<BorderRouter::InfraIf>().Init(kInfraIfIndex, true);
br1.Get<BorderRouter::RoutingManager>().Init();
SuccessOrQuit(br1.Get<BorderRouter::RoutingManager>().SetEnabled(true));
SuccessOrQuit(br1.Get<Srp::Server>().SetAddressMode(Srp::Server::kAddressModeUnicast));
br1.Get<Srp::Server>().SetEnabled(true);
nexus.AdvanceTime(kStabilizationTime);
Log("---------------------------------------------------------------------------------------");
Log("Step 3: Device: ED 2 Description (SRPC-3.5): Harness instructs device to register multiple N=5 services");
/**
* Step 3
* - Device: ED 2
* - Description (SRPC-3.5): Harness instructs device to register multiple N=5 services, each with TXT record per
* DNS-SD key/value formatting:
* - service-test-1. thread-test._udp ( SRV 8 8 55551 host-test-2 TXT dummy=abcd; thread-test=a49 )
* - service-test-2._thread-test._udp ( SRV 8 1 55552 host-test-2 TXT a=2; thread-test=b50_test )
* - service-test-3. thread-test._udp ( SRV 1 55553 host-test-2 TXT a_dummy=42; thread-test= )
* - service-test-4. thread-test._udp ( SRV 1 1 55554 host-test-2 TXT thread-test=1;ignorethis; thread-test= )
* - service-test-5. thread-test.udp ( SRV 2 2 55555 host-test-2 TXT a=88;thread- THREAD-TEST=C51; ignorethis;
* thread-test )
* - host-test-2 AAAA <non-routable address 2001::1234>
* - host-test-2 AAAA <OMR address of ED 2>
* - host-test-2 AAAA <ML-EID address of ED_2>
* - with the following options: Update Lease Option Lease: 5 minutes Key Lease: 10 minutes.
* - Pass Criteria:
* - N/A
*/
ed2.Get<Srp::Client>().EnableAutoStartMode(nullptr, nullptr);
addresses[0] = ed2.FindGlobalAddress();
addresses[1] = ed2.Get<Mle::Mle>().GetMeshLocalEid();
SuccessOrQuit(addresses[2].FromString("2001::1234"));
SuccessOrQuit(ed2.Get<Srp::Client>().SetHostAddresses(addresses, 3));
SuccessOrQuit(ed2.Get<Srp::Client>().SetHostName(kSrpHostName));
ed2.Get<Srp::Client>().SetLeaseInterval(kSrpLease5m);
ed2.Get<Srp::Client>().SetKeyLeaseInterval(kSrpKeyLease10m);
Srp::Client::Service services[5];
Dns::TxtEntry txtEntries[5][5];
ClearAllBytes(services);
ClearAllBytes(txtEntries);
// Service 1
services[0].mName = kSrpServiceType;
services[0].mInstanceName = "service-test-1";
services[0].mPort = kSrpServicePort1;
services[0].mPriority = 8;
services[0].mWeight = 8;
txtEntries[0][0].mKey = "dummy";
txtEntries[0][0].mValue = reinterpret_cast<const uint8_t *>("abcd");
txtEntries[0][0].mValueLength = 4;
txtEntries[0][1].mKey = "thread-test";
txtEntries[0][1].mValue = reinterpret_cast<const uint8_t *>("a49");
txtEntries[0][1].mValueLength = 3;
services[0].mTxtEntries = txtEntries[0];
services[0].mNumTxtEntries = 2;
SuccessOrQuit(services[0].Init());
SuccessOrQuit(ed2.Get<Srp::Client>().AddService(services[0]));
// Service 2
services[1].mName = kSrpServiceType;
services[1].mInstanceName = "service-test-2";
services[1].mPort = kSrpServicePort2;
services[1].mPriority = 8;
services[1].mWeight = 1;
txtEntries[1][0].mKey = "a";
txtEntries[1][0].mValue = reinterpret_cast<const uint8_t *>("2");
txtEntries[1][0].mValueLength = 1;
txtEntries[1][1].mKey = "thread-test";
txtEntries[1][1].mValue = reinterpret_cast<const uint8_t *>("b50_test");
txtEntries[1][1].mValueLength = 8;
services[1].mTxtEntries = txtEntries[1];
services[1].mNumTxtEntries = 2;
SuccessOrQuit(services[1].Init());
SuccessOrQuit(ed2.Get<Srp::Client>().AddService(services[1]));
// Service 3
services[2].mName = kSrpServiceType;
services[2].mInstanceName = "service-test-3";
services[2].mPort = kSrpServicePort3;
services[2].mPriority = 1;
services[2].mWeight = 0;
txtEntries[2][0].mKey = "a_dummy";
txtEntries[2][0].mValue = reinterpret_cast<const uint8_t *>("42");
txtEntries[2][0].mValueLength = 2;
txtEntries[2][1].mKey = "thread-test";
txtEntries[2][1].mValue = reinterpret_cast<const uint8_t *>("");
txtEntries[2][1].mValueLength = 0;
services[2].mTxtEntries = txtEntries[2];
services[2].mNumTxtEntries = 2;
SuccessOrQuit(services[2].Init());
SuccessOrQuit(ed2.Get<Srp::Client>().AddService(services[2]));
// Service 4
services[3].mName = kSrpServiceType;
services[3].mInstanceName = "service-test-4";
services[3].mPort = kSrpServicePort4;
services[3].mPriority = 1;
services[3].mWeight = 1;
txtEntries[3][0].mKey = "thread-test";
txtEntries[3][0].mValue = reinterpret_cast<const uint8_t *>("1");
txtEntries[3][0].mValueLength = 1;
txtEntries[3][1].mKey = "ignorethis";
txtEntries[3][1].mValue = nullptr;
txtEntries[3][1].mValueLength = 0;
txtEntries[3][2].mKey = "thread-test";
txtEntries[3][2].mValue = reinterpret_cast<const uint8_t *>("");
txtEntries[3][2].mValueLength = 0;
services[3].mTxtEntries = txtEntries[3];
services[3].mNumTxtEntries = 3;
SuccessOrQuit(services[3].Init());
SuccessOrQuit(ed2.Get<Srp::Client>().AddService(services[3]));
// Service 5
services[4].mName = kSrpServiceType;
services[4].mInstanceName = "service-test-5";
services[4].mPort = kSrpServicePort5;
services[4].mPriority = 2;
services[4].mWeight = 2;
txtEntries[4][0].mKey = "a";
txtEntries[4][0].mValue = reinterpret_cast<const uint8_t *>("88");
txtEntries[4][0].mValueLength = 2;
txtEntries[4][1].mKey = "thread-";
txtEntries[4][1].mValue = nullptr;
txtEntries[4][1].mValueLength = 0;
txtEntries[4][2].mKey = "THREAD-TEST";
txtEntries[4][2].mValue = reinterpret_cast<const uint8_t *>("C51");
txtEntries[4][2].mValueLength = 3;
txtEntries[4][3].mKey = "ignorethis";
txtEntries[4][3].mValue = nullptr;
txtEntries[4][3].mValueLength = 0;
txtEntries[4][4].mKey = "thread-test";
txtEntries[4][4].mValue = nullptr;
txtEntries[4][4].mValueLength = 0;
services[4].mTxtEntries = txtEntries[4];
services[4].mNumTxtEntries = 5;
SuccessOrQuit(services[4].Init());
SuccessOrQuit(ed2.Get<Srp::Client>().AddService(services[4]));
nexus.AdvanceTime(kSrpRegistrationTime);
{
Srp::Client::ItemState state = ed2.Get<Srp::Client>().GetHostInfo().GetState();
VerifyOrQuit(state == Srp::Client::kRegistered || state == Srp::Client::kToRefresh ||
state == Srp::Client::kRefreshing);
}
Log("---------------------------------------------------------------------------------------");
Log("Step 4: Device: TD 1 (DUT) Description (SRPC-3.5): Enable");
/**
* Step 4
* - Device: TD 1 (DUT)
* - Description (SRPC-3.5): Enable
* - Pass Criteria:
* - N/A
*/
td1.Join(br1, Node::kAsFed);
nexus.AdvanceTime(kJoinNetworkTime);
Log("---------------------------------------------------------------------------------------");
Log("Step 5: Device: TD_1 (DUT) Description (SRPC-3.5): Harness instructs device to query for any services");
/**
* Step 5
* - Device: TD_1 (DUT)
* - Description (SRPC-3.5): Harness instructs device to query for any services of type
* "_thread-test._udp.default.service.arpa." using unicast DNS query QType=PTR
* - Pass Criteria:
* - The DUT MUST send a correctly formatted DNS query.
*/
TestContext context;
context.mNode = &td1;
context.mBrowseCount = 0;
context.mResolveCount = 0;
context.mUdpCount = 0;
Dns::Client::QueryConfig queryConfig = td1.Get<Dns::Client>().GetDefaultConfig();
AsCoreType(&queryConfig.mServerSockAddr).SetAddress(br1.Get<Mle::Mle>().GetMeshLocalEid());
queryConfig.mServerSockAddr.mPort = 53;
td1.Get<Dns::Client>().SetDefaultConfig(queryConfig);
SuccessOrQuit(td1.Get<Dns::Client>().Browse(kSrpFullServiceType, HandleBrowseResponse, &context));
nexus.AdvanceTime(kDnsQueryTime);
Log("---------------------------------------------------------------------------------------");
Log("Step 6: Device: BR 1 Description (SRPC-3.5): Automatically responds with all N services");
/**
* Step 6
* - Device: BR 1
* - Description (SRPC-3.5): Automatically responds with all N services.
* - Pass Criteria:
* - Responds with DNS answer with all N services as indicated in step 3 in the Answer records section.
* - DNS response Rcode MUST be 'NoError' (0).
* - DNS response MUST contain N=5 answer records.
*/
nexus.AdvanceTime(kDnsQueryTime * 2);
// Verify all steps completed
VerifyOrQuit(context.mBrowseCount == 5);
VerifyOrQuit(context.mResolveCount == 5);
VerifyOrQuit(context.mUdpCount == 5);
nexus.AddTestVar("BR_1_MLEID_ADDR", br1.Get<Mle::Mle>().GetMeshLocalEid().ToString().AsCString());
nexus.AddTestVar("TD_1_MLEID_ADDR", td1.Get<Mle::Mle>().GetMeshLocalEid().ToString().AsCString());
nexus.AddTestVar("ED_2_MLEID_ADDR", ed2.Get<Mle::Mle>().GetMeshLocalEid().ToString().AsCString());
nexus.AddTestVar("ED_2_OMR_ADDR", ed2.FindGlobalAddress().ToString().AsCString());
nexus.SaveTestInfo(aJsonFileName);
}
} // namespace Nexus
} // namespace ot
int main(int argc, char *argv[])
{
ot::Nexus::Test_1_3_SRPC_TC_5((argc > 2) ? argv[2] : "test_1_3_SRPC_TC_5.json");
printf("All tests passed\n");
return 0;
}