From 2145cda437f8afa90a89af87111287154720e26a Mon Sep 17 00:00:00 2001 From: Jonathan Hui Date: Mon, 20 Apr 2026 00:09:01 -0700 Subject: [PATCH] [nexus] migrate REED address solicit rejected test to nexus (#12934) This commit migrates the 'test_reed_address_solicit_rejected.py' test from the thread-cert suite to the Nexus test framework. The new Nexus test 'test_reed_address_solicit_rejected.cpp' covers: - Verification that a REED node can successfully register a service and receive the corresponding Service ALOC (0xfc10). - Verification that when a REED node's attempt to upgrade to a router is rejected by the Leader, it correctly remains a child while maintaining its Service ALOC. The original Python script is removed as its functionality is now fully covered by the Nexus implementation. --- tests/nexus/CMakeLists.txt | 1 + .../test_reed_address_solicit_rejected.cpp | 131 ++++++++++++++++++ .../test_reed_address_solicit_rejected.py | 108 --------------- 3 files changed, 132 insertions(+), 108 deletions(-) create mode 100644 tests/nexus/test_reed_address_solicit_rejected.cpp delete mode 100755 tests/scripts/thread-cert/test_reed_address_solicit_rejected.py diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 81836c6cd..d0ced46f8 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -396,6 +396,7 @@ ot_nexus_test(ipv6_source_selection "core;nexus") ot_nexus_test(log_override "core;nexus") ot_nexus_test(mle_blocking_downgrade "core;nexus") ot_nexus_test(nat64_translator "core;nexus") +ot_nexus_test(reed_address_solicit_rejected "core;nexus") ot_nexus_test(router_downgrade_on_sec_policy_change "core;nexus") ot_nexus_test(srp_lease "core;nexus") ot_nexus_test(zero_len_external_route "core;nexus") diff --git a/tests/nexus/test_reed_address_solicit_rejected.cpp b/tests/nexus/test_reed_address_solicit_rejected.cpp new file mode 100644 index 000000000..1911785c1 --- /dev/null +++ b/tests/nexus/test_reed_address_solicit_rejected.cpp @@ -0,0 +1,131 @@ +/* + * 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 + +#include "platform/nexus_core.hpp" +#include "platform/nexus_node.hpp" + +namespace ot { +namespace Nexus { + +void TestReedAddressSolicitRejected(void) +{ + Core nexus; + + Node &leader = nexus.CreateNode(); + Node &reed = nexus.CreateNode(); + + leader.SetName("Leader"); + reed.SetName("REED"); + + nexus.AdvanceTime(0); + + // Leader sets RouterUpgradeThreshold to 1 to reject further routers + leader.Get().SetRouterUpgradeThreshold(1); + + Log("Step 1: Form network and join REED"); + AllowLinkBetween(leader, reed); + leader.Form(); + nexus.AdvanceTime(13 * 1000); + VerifyOrQuit(leader.Get().IsLeader()); + + reed.Join(leader); + nexus.AdvanceTime(10 * 1000); + VerifyOrQuit(reed.Get().IsChild()); + + Log("Step 2: REED adds a service and registers it"); + { + uint32_t enterpriseNumber = 123; + NetworkData::ServiceData serviceData; + NetworkData::ServerData serverData; + uint8_t serviceDataBytes[] = {0x12, 0x34}; + uint8_t serverDataBytes[] = {0xab, 0xcd}; + + serviceData.Init(serviceDataBytes, sizeof(serviceDataBytes)); + serverData.Init(serverDataBytes, sizeof(serverDataBytes)); + + SuccessOrQuit(reed.Get().AddService(enterpriseNumber, serviceData, true, serverData)); + reed.Get().HandleServerDataUpdated(); + } + + nexus.AdvanceTime(5 * 1000); + + Log("Step 3: Verify REED has the Service ALOC"); + { + bool hasAloc = false; + for (const Ip6::Netif::UnicastAddress &addr : reed.Get().GetUnicastAddresses()) + { + if (addr.GetAddress().GetIid().IsAnycastServiceLocator() && + (addr.GetAddress().GetIid().GetLocator() == 0xfc10)) + { + hasAloc = true; + break; + } + } + VerifyOrQuit(hasAloc, "REED should have the Service ALOC"); + } + + Log("Step 4: Force REED to try to become a router"); + reed.Get().SetRouterUpgradeThreshold(16); + reed.Get().SetRouterSelectionJitter(120); + + // We advance time enough to cover jitter and router upgrade attempt. + nexus.AdvanceTime(130 * 1000); + + Log("Step 5: Verify REED remains a child and still has the Service ALOC"); + VerifyOrQuit(reed.Get().IsChild(), "REED should remain a child"); + + { + bool hasAloc = false; + for (const Ip6::Netif::UnicastAddress &addr : reed.Get().GetUnicastAddresses()) + { + if (addr.GetAddress().GetIid().IsAnycastServiceLocator() && + (addr.GetAddress().GetIid().GetLocator() == 0xfc10)) + { + hasAloc = true; + break; + } + } + VerifyOrQuit(hasAloc, "REED should still have the Service ALOC"); + } + + nexus.SaveTestInfo("test_reed_address_solicit_rejected.json"); +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::TestReedAddressSolicitRejected(); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/scripts/thread-cert/test_reed_address_solicit_rejected.py b/tests/scripts/thread-cert/test_reed_address_solicit_rejected.py deleted file mode 100755 index 508d0c941..000000000 --- a/tests/scripts/thread-cert/test_reed_address_solicit_rejected.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (c) 2020, 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 config -import re -import unittest - -import thread_cert - -LEADER = 1 -REED = 2 - -SRV_0_ID = 0 -SRV_0_ENT_NUMBER = '123' -SRV_0_SERVICE_DATA = '123' -SRV_0_SERVER_DATA = 'abc' - -# Topology: -# LEADER -- REED -# - - -class TestREEDAddressSolicitRejected(thread_cert.TestCase): - SUPPORT_NCP = False - - TOPOLOGY = { - LEADER: { - 'mode': 'rdn', - }, - REED: { - 'mode': 'rdn', - }, - } - - def testAddressSolicitRejectedBeforeSvrData(self): - self.nodes[LEADER].start() - self.nodes[LEADER].set_router_upgrade_threshold(1) - self.simulator.go(config.LEADER_STARTUP_DELAY) - self.assertEqual(self.nodes[LEADER].get_state(), 'leader') - - self.nodes[REED].start() - self.simulator.go(5) - self.assertEqual(self.nodes[REED].get_state(), 'child') - - self.nodes[REED].add_service(SRV_0_ENT_NUMBER, SRV_0_SERVICE_DATA, SRV_0_SERVER_DATA) - self.nodes[REED].register_netdata() - self.simulator.go(2) - - self.assertEqual(self.hasAloc(REED, SRV_0_ID), True) - - def testAddressSolicitRejectedAfterSvrData(self): - self.nodes[LEADER].start() - self.nodes[LEADER].set_router_upgrade_threshold(1) - self.simulator.go(config.LEADER_STARTUP_DELAY) - self.assertEqual(self.nodes[LEADER].get_state(), 'leader') - - self.nodes[REED].set_router_selection_jitter(120) - self.nodes[REED].start() - # set routerupgradethreshold=1 on REED so that it won't send ADDR_SOL.req - self.nodes[REED].set_router_upgrade_threshold(1) - self.simulator.go(5) - self.assertEqual(self.nodes[REED].get_state(), 'child') - - # restore routerupgradethreshold to 16 and add service - self.nodes[REED].set_router_upgrade_threshold(16) - self.nodes[REED].add_service(SRV_0_ENT_NUMBER, SRV_0_SERVICE_DATA, SRV_0_SERVER_DATA) - self.nodes[REED].register_netdata() - self.simulator.go(130) - self.assertEqual(self.hasAloc(REED, SRV_0_ID), True) - - def hasAloc(self, node_id, service_id): - for addr in self.nodes[node_id].get_ip6_address(config.ADDRESS_TYPE.ALOC): - m = re.match('.*:fc(..)$', addr, re.I) - if m is not None: - if m.group(1) == str(service_id + 10): # for service_id=3 look for '...:fc13' - return True - - return False - - -if __name__ == '__main__': - unittest.main()