diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index a3e9ffd9d..20681463d 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -409,6 +409,7 @@ ot_nexus_test(netdata_publisher "core;nexus") ot_nexus_test(reed_address_solicit_rejected "core;nexus") ot_nexus_test(reset "core;nexus") ot_nexus_test(router_downgrade_on_sec_policy_change "core;nexus") +ot_nexus_test(router_multicast_link_request "core;nexus") ot_nexus_test(router_reattach "core;nexus") ot_nexus_test(router_reboot_multiple_link_request "core;nexus") ot_nexus_test(srp_auto_start "core;nexus") diff --git a/tests/nexus/test_router_multicast_link_request.cpp b/tests/nexus/test_router_multicast_link_request.cpp new file mode 100644 index 000000000..0cc22410b --- /dev/null +++ b/tests/nexus/test_router_multicast_link_request.cpp @@ -0,0 +1,154 @@ +/* + * 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 "thread/mle_types.hpp" +#include "thread/router_table.hpp" + +namespace ot { +namespace Nexus { + +/** + * Time to advance for a node to form a network and become leader. + */ +static constexpr uint32_t kFormNetworkTime = 13 * 1000; + +/** + * Time to advance for a node to join as a child and upgrade to a router. + */ +static constexpr uint32_t kAttachToRouterTime = 200 * 1000; + +/** + * Link establishment delay threshold (in milliseconds). + * test_router_multicast_link_request.py uses 3 seconds + 3 seconds buffer. + */ +static constexpr uint32_t kLinkEstablishDelay = 6 * 1000; + +static void CheckLink(Node &aNodeA, Node &aNodeB) +{ + Router::Info info; + uint8_t bRouterId = Mle::RouterIdFromRloc16(aNodeB.Get().GetRloc16()); + uint8_t aRouterId = Mle::RouterIdFromRloc16(aNodeA.Get().GetRloc16()); + + Log("Checking link between %s (ID %d) and %s (ID %d)", aNodeA.GetName(), aRouterId, aNodeB.GetName(), bRouterId); + + SuccessOrQuit(aNodeA.Get().GetRouterInfo(bRouterId, info)); + VerifyOrQuit(info.mLinkEstablished); + + SuccessOrQuit(aNodeB.Get().GetRouterInfo(aRouterId, info)); + VerifyOrQuit(info.mLinkEstablished); +} + +void TestRouterMulticastLinkRequest(const char *aJsonFileName) +{ + /** + * This test verifies if a device can quickly and efficiently establish links with + * neighboring routers by sending a multicast Link Request message after becoming + * a router. + */ + + Core nexus; + + Node &leader = nexus.CreateNode(); + Node &router1 = nexus.CreateNode(); + Node &router2 = nexus.CreateNode(); + Node &router3 = nexus.CreateNode(); + Node &reed = nexus.CreateNode(); + + leader.SetName("LEADER"); + router1.SetName("ROUTER_1"); + router2.SetName("ROUTER_2"); + router3.SetName("ROUTER_3"); + reed.SetName("REED"); + + // Topology: + // Leader is connected to Router1, Router2, Router3. + // REED is connected to Router1, Router2, Router3. + AllowLinkBetween(leader, router1); + AllowLinkBetween(leader, router2); + AllowLinkBetween(leader, router3); + AllowLinkBetween(router1, reed); + AllowLinkBetween(router2, reed); + AllowLinkBetween(router3, reed); + + nexus.AdvanceTime(0); + + SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelInfo)); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 1: Leader forms network"); + leader.Form(); + nexus.AdvanceTime(kFormNetworkTime); + VerifyOrQuit(leader.Get().IsLeader()); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 2: Routers join the leader"); + router1.Join(leader); + router2.Join(leader); + router3.Join(leader); + nexus.AdvanceTime(kAttachToRouterTime); + VerifyOrQuit(router1.Get().IsRouter()); + VerifyOrQuit(router2.Get().IsRouter()); + VerifyOrQuit(router3.Get().IsRouter()); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 3: Wait for network to stabilize"); + nexus.AdvanceTime(60 * 1000); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 4: REED joins and upgrades to router"); + // REED joins through router1 + reed.Join(router1); + nexus.AdvanceTime(kAttachToRouterTime); + VerifyOrQuit(reed.Get().IsRouter()); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 5: Verify REED establishes links with all neighboring routers"); + nexus.AdvanceTime(kLinkEstablishDelay); + + CheckLink(reed, router1); + CheckLink(reed, router2); + CheckLink(reed, router3); + + nexus.SaveTestInfo(aJsonFileName); +} + +} // namespace Nexus +} // namespace ot + +int main(int argc, char *argv[]) +{ + const char *jsonFileName = (argc > 2) ? argv[2] : "test_router_multicast_link_request.json"; + + ot::Nexus::TestRouterMulticastLinkRequest(jsonFileName); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/scripts/thread-cert/test_router_multicast_link_request.py b/tests/scripts/thread-cert/test_router_multicast_link_request.py deleted file mode 100755 index fb366053e..000000000 --- a/tests/scripts/thread-cert/test_router_multicast_link_request.py +++ /dev/null @@ -1,122 +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 logging -import unittest - -import config -import thread_cert -from pktverify.consts import MLE_LINK_REQUEST -from pktverify.packet_verifier import PacketVerifier - -LEADER = 1 -REED = 2 -ROUTER1, ROUTER2, ROUTER3 = 3, 4, 5 - -# Test Purpose and Description: -# ----------------------------- -# This test verifies if a device can quickly and efficiently establish links with -# neighboring routers by sending a multicast Link Request message after becoming -# a router -# -# Test Topology: -# ------------- -# Leader------+ -# | \ \ -# Router1 Router2 Router3 -# \ | | -# +---REED-----+ -# - -# REED should establish links to the three Routers within the delay threshold. -# The max delay consists: -# Leader may delay 1 second for MLE Advertisement -# Router may delay 1 second for MLE Advertisement -# Router may delay 1 second for MLE Link Accept after receiving MLE Link Request (multicast) -LINK_ESTABLISH_DELAY_THRESHOLD = 3 - - -class TestRouterMulticastLinkRequest(thread_cert.TestCase): - USE_MESSAGE_FACTORY = False - - TOPOLOGY = { - LEADER: { - 'name': 'LEADER', - 'mode': 'rdn', - 'allowlist': [ROUTER1, ROUTER2, ROUTER3] - }, - ROUTER1: { - 'name': 'ROUTER1', - 'mode': 'rdn', - 'allowlist': [LEADER, REED] - }, - ROUTER2: { - 'name': 'ROUTER2', - 'mode': 'rdn', - 'allowlist': [LEADER, REED] - }, - ROUTER3: { - 'name': 'ROUTER3', - 'mode': 'rdn', - 'allowlist': [LEADER, REED] - }, - REED: { - 'name': 'REED', - 'mode': 'rdn', - 'allowlist': [ROUTER1, ROUTER2, ROUTER3] - }, - } - - def test(self): - self.nodes[LEADER].start() - self.simulator.go(config.LEADER_STARTUP_DELAY) - self.assertEqual(self.nodes[LEADER].get_state(), 'leader') - - for routerid in (ROUTER1, ROUTER2, ROUTER3): - self.nodes[routerid].start() - self.simulator.go(config.ROUTER_STARTUP_DELAY) - self.assertEqual(self.nodes[routerid].get_state(), 'router') - - # Wait for the network to stabilize - self.simulator.go(60) - - self.nodes[REED].start() - self.simulator.go(config.ROUTER_STARTUP_DELAY) - self.assertEqual(self.nodes[REED].get_state(), 'router') - self.simulator.go(LINK_ESTABLISH_DELAY_THRESHOLD + 3) - - # Verify that REED has established link with all routers - reed_table = self.nodes[REED].router_table() - reed_id = self.nodes[REED].get_router_id() - for router in [self.nodes[ROUTER1], self.nodes[ROUTER2], self.nodes[ROUTER3]]: - self.assertEqual(reed_table[router.get_router_id()]['link'], 1) - self.assertEqual(router.router_table()[reed_id]['link'], 1) - - -if __name__ == '__main__': - unittest.main()