diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 3e4b614b5..6a710dddd 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -408,6 +408,7 @@ ot_nexus_test(router_downgrade_on_sec_policy_change "core;nexus") ot_nexus_test(srp_auto_start "core;nexus") ot_nexus_test(srp_client_change_lease "core;nexus") ot_nexus_test(srp_client_remove_host "core;nexus") +ot_nexus_test(srp_client_save_server_info "core;nexus") ot_nexus_test(srp_lease "core;nexus") ot_nexus_test(srp_many_services_mtu_check "core;nexus") ot_nexus_test(srp_scale "core;nexus") diff --git a/tests/nexus/test_srp_client_save_server_info.cpp b/tests/nexus/test_srp_client_save_server_info.cpp new file mode 100644 index 000000000..ff8e404f0 --- /dev/null +++ b/tests/nexus/test_srp_client_save_server_info.cpp @@ -0,0 +1,187 @@ +/* + * 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" + +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 the network to stabilize. + */ +static constexpr uint32_t kWaitTime = 16 * 1000; + +/** + * SRP service port. + */ +static constexpr uint16_t kSrpServicePort = 12345; + +void TestSrpClientSaveServerInfo(const char *aJsonFileName) +{ + Core nexus; + + Node &client = nexus.CreateNode(); + Node &server1 = nexus.CreateNode(); + Node &server2 = nexus.CreateNode(); + Node &server3 = nexus.CreateNode(); + + client.SetName("CLIENT"); + server1.SetName("SERVER1"); + server2.SetName("SERVER2"); + server3.SetName("SERVER3"); + + SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelNote)); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 1: Start the server & client devices."); + + client.Form(); + nexus.AdvanceTime(kFormNetworkTime); + VerifyOrQuit(client.Get().IsLeader()); + + AllowLinkBetween(client, server1); + AllowLinkBetween(client, server2); + AllowLinkBetween(client, server3); + + server1.Join(client); + server2.Join(client); + server3.Join(client); + nexus.AdvanceTime(Time::SecToMsec(200)); + + VerifyOrQuit(server1.Get().IsRouterOrLeader()); + VerifyOrQuit(server2.Get().IsRouterOrLeader()); + VerifyOrQuit(server3.Get().IsRouterOrLeader()); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 2: Enable auto start mode on client and check that server1 is used."); + + SuccessOrQuit(server1.Get().SetAddressMode(Srp::Server::kAddressModeUnicast)); + server1.Get().SetEnabled(true); + nexus.AdvanceTime(kWaitTime); + + SuccessOrQuit(client.Get().SetHostName("host")); + SuccessOrQuit(client.Get().EnableAutoHostAddress()); + { + Srp::Client::Service service; + ClearAllBytes(service); + service.mName = "_ipps._tcp"; + service.mInstanceName = "my-service"; + service.mPort = kSrpServicePort; + SuccessOrQuit(client.Get().AddService(service)); + } + + client.Get().EnableAutoStartMode(nullptr, nullptr); + VerifyOrQuit(client.Get().IsAutoStartModeEnabled()); + + nexus.AdvanceTime(kWaitTime); + + VerifyOrQuit(client.Get().IsRunning()); + VerifyOrQuit( + server1.Get().HasUnicastAddress(client.Get().GetServerAddress().GetAddress())); + VerifyOrQuit(client.Get().GetHostInfo().GetState() == Srp::Client::kRegistered); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 3: Enable server2 and server3 and check that server1 is still used."); + + SuccessOrQuit(server2.Get().SetAddressMode(Srp::Server::kAddressModeUnicast)); + server2.Get().SetEnabled(true); + SuccessOrQuit(server3.Get().SetAddressMode(Srp::Server::kAddressModeUnicast)); + server3.Get().SetEnabled(true); + + nexus.AdvanceTime(kWaitTime); + VerifyOrQuit( + server1.Get().HasUnicastAddress(client.Get().GetServerAddress().GetAddress())); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 4: Stop and restart the client (multiple times) and verify that server1 is always picked."); + + for (int iter = 0; iter < 5; iter++) + { + client.Get().Stop(); + client.Get().EnableAutoStartMode(nullptr, nullptr); + nexus.AdvanceTime(kWaitTime); + VerifyOrQuit( + server1.Get().HasUnicastAddress(client.Get().GetServerAddress().GetAddress())); + VerifyOrQuit(client.Get().GetHostInfo().GetState() == Srp::Client::kRegistered); + } + + Log("---------------------------------------------------------------------------------------"); + Log("Step 5: Stop the client, then stop server1 and restart client and verify that server1 is no longer used."); + + client.Get().Stop(); + server1.Get().SetEnabled(false); + nexus.AdvanceTime(kWaitTime); + + client.Get().EnableAutoStartMode(nullptr, nullptr); + nexus.AdvanceTime(kWaitTime); + + Ip6::Address serverAddress = client.Get().GetServerAddress().GetAddress(); + VerifyOrQuit(!server1.Get().HasUnicastAddress(serverAddress)); + VerifyOrQuit(server2.Get().HasUnicastAddress(serverAddress) || + server3.Get().HasUnicastAddress(serverAddress)); + VerifyOrQuit(client.Get().GetHostInfo().GetState() == Srp::Client::kRegistered); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 6: Start back server1, then restart client and verify that now we remain with the new saved server " + "info."); + + server1.Get().SetEnabled(true); + nexus.AdvanceTime(kWaitTime); + + for (int iter = 0; iter < 5; iter++) + { + client.Get().Stop(); + nexus.AdvanceTime(kWaitTime); + client.Get().EnableAutoStartMode(nullptr, nullptr); + nexus.AdvanceTime(kWaitTime); + VerifyOrQuit(client.Get().GetServerAddress().GetAddress() == serverAddress); + VerifyOrQuit(client.Get().GetHostInfo().GetState() == Srp::Client::kRegistered); + } + + Log("---------------------------------------------------------------------------------------"); + + nexus.SaveTestInfo(aJsonFileName); +} + +} // namespace Nexus +} // namespace ot + +int main(int argc, char *argv[]) +{ + ot::Nexus::TestSrpClientSaveServerInfo((argc > 2) ? argv[2] : "test_srp_client_save_server_info.json"); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/scripts/thread-cert/test_srp_client_save_server_info.py b/tests/scripts/thread-cert/test_srp_client_save_server_info.py deleted file mode 100755 index 094205a35..000000000 --- a/tests/scripts/thread-cert/test_srp_client_save_server_info.py +++ /dev/null @@ -1,162 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (c) 2021, 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. -# - -import ipaddress -import unittest - -import command -import config -import thread_cert - -# Test description: -# This test verifies SRP client behavior to save the selected server address info (unicast) by -# auto-start feature. -# -# Topology: -# -# 4 node (SRP client as leader, 3 SRP servers). -# - -CLIENT = 1 -SERVER1 = 2 -SERVER2 = 3 -SERVER3 = 4 - -WAIT_TIME = 16 -MAX_ITER = 5 - - -class SrpAutoStartMode(thread_cert.TestCase): - USE_MESSAGE_FACTORY = False - SUPPORT_NCP = False - - TOPOLOGY = { - CLIENT: { - 'name': 'SRP_CLIENT', - 'network_key': '00112233445566778899aabbccddeeff', - 'mode': 'rdn', - }, - SERVER1: { - 'name': 'SRP_SERVER1', - 'network_key': '00112233445566778899aabbccddeeff', - 'mode': 'rn', - }, - SERVER2: { - 'name': 'SRP_SERVER2', - 'network_key': '00112233445566778899aabbccddeeff', - 'mode': 'rn', - }, - SERVER3: { - 'name': 'SRP_SERVER3', - 'network_key': '00112233445566778899aabbccddeeff', - 'mode': 'rn', - }, - } - - def test(self): - client = self.nodes[CLIENT] - server1 = self.nodes[SERVER1] - server2 = self.nodes[SERVER2] - server3 = self.nodes[SERVER3] - - # Start the server & client devices. - - client.start() - self.simulator.go(config.LEADER_STARTUP_DELAY) - self.assertEqual(client.get_state(), 'leader') - - for node in [server1, server2, server3]: - node.start() - self.simulator.go(WAIT_TIME) - self.assertEqual(node.get_state(), 'child') - - # Enable auto start mode on client and check that server1 is used. - - server1.srp_server_set_enabled(True) - self.simulator.go(WAIT_TIME) - - client.srp_client_set_host_name('host') - client.srp_client_set_host_address('2001::1') - client.srp_client_add_service('my-service', '_ipps._tcp', 12345) - - self.assertEqual(client.srp_client_get_auto_start_mode(), 'Enabled') - self.simulator.go(WAIT_TIME) - - self.assertEqual(client.srp_client_get_state(), 'Enabled') - self.assertTrue(server1.has_ipaddr(client.srp_client_get_server_address())) - self.assertEqual(client.srp_client_get_host_state(), 'Registered') - - # Enable server2 and server3 and check that server1 is still used. - - server2.srp_server_set_enabled(True) - server3.srp_server_set_enabled(True) - self.simulator.go(WAIT_TIME) - self.assertTrue(server1.has_ipaddr(client.srp_client_get_server_address())) - - # Stop and restart the client (multiple times) and verify that - # server1 is always picked. - - for iter in range(0, MAX_ITER): - client.srp_client_stop() - client.srp_client_enable_auto_start_mode() - self.simulator.go(WAIT_TIME) - self.assertTrue(server1.has_ipaddr(client.srp_client_get_server_address())) - self.assertEqual(client.srp_client_get_host_state(), 'Registered') - - # Stop the client, then stop server1 and restart client and - # verify that server1 is no longer used. - - client.srp_client_stop() - server1.srp_server_set_enabled(False) - self.simulator.go(WAIT_TIME) - - client.srp_client_enable_auto_start_mode() - self.simulator.go(WAIT_TIME) - server_address = client.srp_client_get_server_address() - self.assertFalse(server1.has_ipaddr(server_address)) - self.assertTrue(server2.has_ipaddr(server_address) or server3.has_ipaddr(server_address)) - self.assertEqual(client.srp_client_get_host_state(), 'Registered') - - # Start back server1, then restart client and verify that now we remain - # with the new saved server info. - - server1.srp_server_set_enabled(True) - self.simulator.go(WAIT_TIME) - - for iter in range(0, MAX_ITER): - client.srp_client_stop() - self.simulator.go(WAIT_TIME) - client.srp_client_enable_auto_start_mode() - self.simulator.go(WAIT_TIME) - self.assertEqual(client.srp_client_get_server_address(), server_address) - self.assertEqual(client.srp_client_get_host_state(), 'Registered') - - -if __name__ == '__main__': - unittest.main()