From 8fbe09e2b5a616f43ed8a221a1bce034e8b7fdb4 Mon Sep 17 00:00:00 2001 From: Jonathan Hui Date: Wed, 22 Apr 2026 02:18:34 -0700 Subject: [PATCH] [nexus] migrate test_srp_ttl.py to nexus (#12960) This commit migrates the SRP TTL test from the thread-cert Python framework to the Nexus C++ framework. The new Nexus test `test_srp_ttl.cpp` covers all four TTL clamping cases originally implemented in `test_srp_ttl.py`: 1. CLIENT_TTL < TTL_MIN < LEASE_MAX => Clamped to TTL_MIN. 2. TTL_MIN < CLIENT_TTL < TTL_MAX < LEASE_MAX => Used CLIENT_TTL. 3. TTL_MAX < LEASE_MAX < CLIENT_TTL => Clamped to TTL_MAX. 4. LEASE_MAX < TTL_MAX < CLIENT_TTL => Clamped to LEASE_MAX. Nexus tests provide faster and more scalable network simulations within a single process, improving CI efficiency. The original Python script `tests/scripts/thread-cert/test_srp_ttl.py` is removed as its functionality is now fully covered by Nexus. --- tests/nexus/CMakeLists.txt | 1 + tests/nexus/test_srp_ttl.cpp | 186 ++++++++++++++++++++++ tests/scripts/thread-cert/test_srp_ttl.py | 153 ------------------ 3 files changed, 187 insertions(+), 153 deletions(-) create mode 100644 tests/nexus/test_srp_ttl.cpp delete mode 100755 tests/scripts/thread-cert/test_srp_ttl.py diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 638314227..14c4e03d4 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -414,6 +414,7 @@ ot_nexus_test(srp_many_services_mtu_check "core;nexus") ot_nexus_test(srp_register_services_diff_lease "core;nexus") ot_nexus_test(srp_scale "core;nexus") ot_nexus_test(srp_server_reboot_port "core;nexus") +ot_nexus_test(srp_ttl "core;nexus") ot_nexus_test(zero_len_external_route "core;nexus") # Trel diff --git a/tests/nexus/test_srp_ttl.cpp b/tests/nexus/test_srp_ttl.cpp new file mode 100644 index 000000000..a1866c872 --- /dev/null +++ b/tests/nexus/test_srp_ttl.cpp @@ -0,0 +1,186 @@ +/* + * 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 + +#include "platform/nexus_core.hpp" +#include "platform/nexus_node.hpp" + +namespace ot { +namespace Nexus { + +static void CheckTtl(Core &aNexus, Node &aServer, uint32_t aExpectedTtl) +{ + const Srp::Server::Host *host = nullptr; + + // We advance time enough to trigger a refresh from the client. + // The lease interval is at most 240 seconds in our test cases. + aNexus.AdvanceTime(300 * Time::kOneSecondInMsec); + + while ((host = aServer.Get().GetNextHost(host)) != nullptr) + { + if (StringMatch(host->GetFullName(), "my-host.default.service.arpa.", kStringCaseInsensitiveMatch)) + { + break; + } + } + + VerifyOrQuit(host != nullptr); + + const Srp::Server::Service *serverService = host->GetNextService(nullptr); + + VerifyOrQuit(serverService != nullptr); + Log("Expected TTL: %u, Actual TTL: %u", aExpectedTtl, serverService->GetTtl()); + VerifyOrQuit(serverService->GetTtl() == aExpectedTtl); +} + +void TestSrpTtl(void) +{ + Core nexus; + Node &server = nexus.CreateNode(); + Node &client = nexus.CreateNode(); + + Log("----------------------------------------------------------------------------------------------------------"); + Log("Testing SRP TTL"); + + server.Form(); + nexus.AdvanceTime(10 * Time::kOneSecondInMsec); + VerifyOrQuit(server.Get().IsLeader()); + + server.Get().SetEnabled(true); + nexus.AdvanceTime(1 * Time::kOneSecondInMsec); + + client.Join(server, Node::kAsFed); + nexus.AdvanceTime(10 * Time::kOneSecondInMsec); + VerifyOrQuit(client.Get().IsChild()); + + client.Get().EnableAutoStartMode(nullptr, nullptr); + nexus.AdvanceTime(1 * Time::kOneSecondInMsec); + + SuccessOrQuit(client.Get().SetHostName("my-host")); + + Ip6::Address address; + SuccessOrQuit(address.FromString("2001::1")); + SuccessOrQuit(client.Get().SetHostAddresses(&address, 1)); + + Srp::Client::Service service; + ClearAllBytes(service); + service.mName = "_ipps._tcp"; + service.mInstanceName = "my-service"; + service.mPort = 12345; + SuccessOrQuit(client.Get().AddService(service)); + + // Case 1: CLIENT_TTL < TTL_MIN < LEASE_MAX ==> TTL_MIN + Log("Case 1: CLIENT_TTL < TTL_MIN < LEASE_MAX ==> TTL_MIN"); + { + Srp::Server::TtlConfig ttlConfig; + ttlConfig.mMinTtl = 120; + ttlConfig.mMaxTtl = 240; + SuccessOrQuit(server.Get().SetTtlConfig(ttlConfig)); + + Srp::Server::LeaseConfig leaseConfig; + leaseConfig.mMinLease = 120; + leaseConfig.mMaxLease = 240; + leaseConfig.mMinKeyLease = 240; + leaseConfig.mMaxKeyLease = 240; + SuccessOrQuit(server.Get().SetLeaseConfig(leaseConfig)); + + client.Get().SetTtl(100); + CheckTtl(nexus, server, 120); + } + + // Case 2: TTL_MIN < CLIENT_TTL < TTL_MAX < LEASE_MAX ==> CLIENT_TTL + Log("Case 2: TTL_MIN < CLIENT_TTL < TTL_MAX < LEASE_MAX ==> CLIENT_TTL"); + { + Srp::Server::TtlConfig ttlConfig; + ttlConfig.mMinTtl = 60; + ttlConfig.mMaxTtl = 120; + SuccessOrQuit(server.Get().SetTtlConfig(ttlConfig)); + + Srp::Server::LeaseConfig leaseConfig; + leaseConfig.mMinLease = 120; + leaseConfig.mMaxLease = 240; + leaseConfig.mMinKeyLease = 240; + leaseConfig.mMaxKeyLease = 240; + SuccessOrQuit(server.Get().SetLeaseConfig(leaseConfig)); + + client.Get().SetTtl(100); + CheckTtl(nexus, server, 100); + } + + // Case 3: TTL_MAX < LEASE_MAX < CLIENT_TTL ==> TTL_MAX + Log("Case 3: TTL_MAX < LEASE_MAX < CLIENT_TTL ==> TTL_MAX"); + { + Srp::Server::TtlConfig ttlConfig; + ttlConfig.mMinTtl = 60; + ttlConfig.mMaxTtl = 120; + SuccessOrQuit(server.Get().SetTtlConfig(ttlConfig)); + + Srp::Server::LeaseConfig leaseConfig; + leaseConfig.mMinLease = 120; + leaseConfig.mMaxLease = 240; + leaseConfig.mMinKeyLease = 240; + leaseConfig.mMaxKeyLease = 240; + SuccessOrQuit(server.Get().SetLeaseConfig(leaseConfig)); + + client.Get().SetTtl(240); + CheckTtl(nexus, server, 120); + } + + // Case 4: LEASE_MAX < TTL_MAX < CLIENT_TTL ==> LEASE_MAX + Log("Case 4: LEASE_MAX < TTL_MAX < CLIENT_TTL ==> LEASE_MAX"); + { + Srp::Server::TtlConfig ttlConfig; + ttlConfig.mMinTtl = 60; + ttlConfig.mMaxTtl = 120; + SuccessOrQuit(server.Get().SetTtlConfig(ttlConfig)); + + Srp::Server::LeaseConfig leaseConfig; + leaseConfig.mMinLease = 30; + leaseConfig.mMaxLease = 60; + leaseConfig.mMinKeyLease = 240; + leaseConfig.mMaxKeyLease = 240; + SuccessOrQuit(server.Get().SetLeaseConfig(leaseConfig)); + + client.Get().SetTtl(240); + CheckTtl(nexus, server, 60); + } + + Log("Test passed successfully"); +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::TestSrpTtl(); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/scripts/thread-cert/test_srp_ttl.py b/tests/scripts/thread-cert/test_srp_ttl.py deleted file mode 100755 index 70a60721e..000000000 --- a/tests/scripts/thread-cert/test_srp_ttl.py +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (c) 2022, 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 the SRP server and client properly handle SRP host -# and service instance TTLs. -# -# Topology: -# LEADER (SRP server) -# | -# | -# ROUTER (SRP client) -# - -SERVER = 1 -CLIENT = 2 -KEY_LEASE = 240 # Seconds - - -class SrpTtl(thread_cert.TestCase): - USE_MESSAGE_FACTORY = False - SUPPORT_NCP = False - - TOPOLOGY = { - SERVER: { - 'name': 'SRP_SERVER', - 'mode': 'rdn', - }, - CLIENT: { - 'name': 'SRP_CLIENT', - 'mode': 'rdn', - }, - } - - def test(self): - server = self.nodes[SERVER] - client = self.nodes[CLIENT] - - # - # Start the server and client devices. - # - - server.srp_server_set_enabled(True) - server.srp_server_set_lease_range(120, 240, KEY_LEASE, KEY_LEASE) - server.start() - self.simulator.go(config.LEADER_STARTUP_DELAY) - self.assertEqual(server.get_state(), 'leader') - self.simulator.go(5) - - client.srp_server_set_enabled(False) - client.start() - self.simulator.go(config.ROUTER_STARTUP_DELAY) - self.assertEqual(client.get_state(), 'router') - - self.simulator.go(15) - self.assertEqual(client.srp_client_get_auto_start_mode(), 'Enabled') - - client.srp_client_set_host_name('my-host') - client.srp_client_set_host_address('2001::1') - client.srp_client_add_service('my-service', '_ipps._tcp', 12345) - self.simulator.go(2) - - # - # CLIENT_TTL < TTL_MIN < LEASE_MAX ==> TTL_MIN - # - - client.srp_client_set_ttl(100) - server.srp_server_set_ttl_range(120, 240) - server.srp_server_set_lease_range(120, 240, KEY_LEASE, KEY_LEASE) - self.simulator.go(KEY_LEASE) - self.check_ttl(120) - - # - # TTL_MIN < CLIENT_TTL < TTL_MAX < LEASE_MAX ==> CLIENT_TTL - # - - client.srp_client_set_ttl(100) - server.srp_server_set_ttl_range(60, 120) - server.srp_server_set_lease_range(120, 240, KEY_LEASE, KEY_LEASE) - self.simulator.go(KEY_LEASE) - self.check_ttl(100) - - # - # TTL_MAX < LEASE_MAX < CLIENT_TTL ==> TTL_MAX - # - - client.srp_client_set_ttl(240) - server.srp_server_set_ttl_range(60, 120) - server.srp_server_set_lease_range(120, 240, KEY_LEASE, KEY_LEASE) - self.simulator.go(KEY_LEASE) - self.check_ttl(120) - - # - # LEASE_MAX < TTL_MAX < CLIENT_TTL ==> LEASE_MAX - # - - client.srp_client_set_ttl(240) - server.srp_server_set_ttl_range(60, 120) - server.srp_server_set_lease_range(30, 60, KEY_LEASE, KEY_LEASE) - self.simulator.go(KEY_LEASE) - self.check_ttl(60) - - def check_ttl(self, ttl): - """Check that we have properly registered host and service instance. - """ - - server = self.nodes[SERVER] - - server_services = server.srp_server_get_services() - print(server_services) - self.assertEqual(len(server_services), 1) - server_service = server_services[0] - - # Verify that the server accepted the SRP registration and stored - # the same service resources. - self.assertEqual(int(server_service['ttl']), ttl) - - -if __name__ == '__main__': - unittest.main()