diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index beadf319b..a5ccd17b2 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -414,6 +414,7 @@ ot_nexus_test(netdata_publisher "core;nexus") ot_nexus_test(on_mesh_prefix "core;nexus") ot_nexus_test(pbbr_aloc "core;nexus") ot_nexus_test(ping_lla_src "core;nexus") +ot_nexus_test(radio_filter "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") diff --git a/tests/nexus/test_radio_filter.cpp b/tests/nexus/test_radio_filter.cpp new file mode 100644 index 000000000..d4c81ba36 --- /dev/null +++ b/tests/nexus/test_radio_filter.cpp @@ -0,0 +1,148 @@ +/* + * 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 { + +void TestRadioFilter(void) +{ + Core nexus; + + Node &leader = nexus.CreateNode(); + Node &router = nexus.CreateNode(); + Node &sed = nexus.CreateNode(); + + leader.SetName("Leader"); + router.SetName("Router"); + sed.SetName("SED"); + + AllowLinkBetween(leader, router); + AllowLinkBetween(leader, sed); + + nexus.AdvanceTime(0); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 1: Form network"); + + leader.Form(); + nexus.AdvanceTime(13 * 1000); + VerifyOrQuit(leader.Get().IsLeader()); + + router.Join(leader); + nexus.AdvanceTime(200 * 1000); + VerifyOrQuit(router.Get().IsRouter()); + + sed.Join(leader, Node::kAsSed); + nexus.AdvanceTime(200 * 1000); + VerifyOrQuit(sed.Get().IsChild()); + + // Set poll period for SED to receive replies faster + SuccessOrQuit(sed.Get().SetExternalPollPeriod(40)); + + Ip6::Address leaderMleid = leader.Get().GetMeshLocalEid(); + Ip6::Address routerMleid = router.Get().GetMeshLocalEid(); + Ip6::Address sedMleid = sed.Get().GetMeshLocalEid(); + + // Validate initial state of `radiofilter` upon start + VerifyOrQuit(!leader.Get().IsRadioFilterEnabled()); + VerifyOrQuit(!router.Get().IsRadioFilterEnabled()); + VerifyOrQuit(!sed.Get().IsRadioFilterEnabled()); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 2: Validate connections by pinging"); + nexus.SendAndVerifyEchoRequest(router, leaderMleid); + nexus.SendAndVerifyEchoRequest(sed, leaderMleid); + nexus.SendAndVerifyEchoRequest(leader, routerMleid); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 3: Validate behavior when `radiofilter` is enabled on router"); + router.Get().SetRadioFilterEnabled(true); + VerifyOrQuit(router.Get().IsRadioFilterEnabled()); + + nexus.SendAndVerifyNoEchoResponse(leader, routerMleid); + nexus.SendAndVerifyNoEchoResponse(router, leaderMleid); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 4: Validate behavior when `radiofilter` is disabled on router"); + router.Get().SetRadioFilterEnabled(false); + VerifyOrQuit(!router.Get().IsRadioFilterEnabled()); + + nexus.SendAndVerifyEchoRequest(leader, routerMleid); + nexus.SendAndVerifyEchoRequest(router, leaderMleid); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 5: Validate `radiofilter` behavior on sed"); + nexus.SendAndVerifyEchoRequest(sed, leaderMleid); + + sed.Get().SetRadioFilterEnabled(true); + VerifyOrQuit(sed.Get().IsRadioFilterEnabled()); + + nexus.SendAndVerifyNoEchoResponse(sed, leaderMleid); + + // Advance time to trigger detach + nexus.AdvanceTime(300 * 1000); + VerifyOrQuit(sed.Get().IsDetached()); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 6: Validate behavior when `radiofilter` is disabled on sed"); + sed.Get().SetRadioFilterEnabled(false); + VerifyOrQuit(!sed.Get().IsRadioFilterEnabled()); + + sed.Join(leader, Node::kAsSed); + + // Advance time to allow reattach + for (int i = 0; i < 200; i++) + { + nexus.AdvanceTime(1000); + if (sed.Get().IsChild()) + { + break; + } + } + VerifyOrQuit(sed.Get().IsChild()); + + nexus.SendAndVerifyEchoRequest(leader, sedMleid); + nexus.SendAndVerifyEchoRequest(sed, leaderMleid); + + nexus.SaveTestInfo("test_radio_filter.json"); +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::TestRadioFilter(); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/scripts/thread-cert/test_radio_filter.py b/tests/scripts/thread-cert/test_radio_filter.py deleted file mode 100755 index 7ce082bf2..000000000 --- a/tests/scripts/thread-cert/test_radio_filter.py +++ /dev/null @@ -1,151 +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 config -import command -import thread_cert - -# Test description: -# -# This test verifies the radio filter mechanism. -# -# Topology: -# -# Leader -- Router -# | -# | -# Child -# -# - -LEADER = 1 -ROUTER = 2 -SED = 3 - -WAIT_TIME = 5 - - -class RadioFilter(thread_cert.TestCase): - USE_MESSAGE_FACTORY = False - SUPPORT_NCP = False - - TOPOLOGY = { - LEADER: { - 'mode': 'rdn', - 'allowlist': [ROUTER, SED] - }, - ROUTER: { - 'mode': 'rdn', - 'allowlist': [LEADER] - }, - SED: { - 'is_mtd': True, - 'mode': '-', - 'timeout': config.DEFAULT_CHILD_TIMEOUT, - 'allowlist': [LEADER] - }, - } - - def test(self): - leader = self.nodes[LEADER] - router = self.nodes[ROUTER] - sed = self.nodes[SED] - - nodes = [leader, router, sed] - - leader.start() - self.simulator.go(config.LEADER_STARTUP_DELAY) - self.assertEqual(leader.get_state(), 'leader') - - router.start() - self.simulator.go(WAIT_TIME) - self.assertEqual(router.get_state(), 'router') - - sed.start() - sed.set_pollperiod(40) - self.simulator.go(WAIT_TIME) - self.assertEqual(sed.get_state(), 'child') - - # Validate initial state of `radiofilter` upon start - for node in nodes: - self.assertFalse(node.radiofilter_is_enabled()) - - leader_mleid = leader.get_mleid() - router_mleid = router.get_mleid() - sed_mleid = sed.get_mleid() - - # Validate connections by pinging from leader, SED, and router - self.assertTrue(leader.ping(router_mleid)) - self.assertTrue(sed.ping(leader_mleid)) - self.assertTrue(router.ping(leader_mleid)) - - # Validate behavior when `radiofilter` is enabled on router - router.radiofilter_enable() - self.assertTrue(router.radiofilter_is_enabled()) - self.assertFalse(leader.ping(router_mleid)) - self.assertFalse(router.ping(leader_mleid)) - - # Validate behavior when `radiofilter` is disabled on router - router.radiofilter_disable() - self.assertFalse(router.radiofilter_is_enabled()) - self.assertTrue(leader.ping(router_mleid)) - self.assertTrue(router.ping(leader_mleid)) - - # Validate `radiofilter` behavior on sed. - self.assertTrue(sed.ping(leader_mleid)) - - sed.radiofilter_enable() - self.assertTrue(sed.radiofilter_is_enabled()) - self.assertFalse(sed.ping(leader_mleid)) - - self.assertTrue(sed.radiofilter_is_enabled()) - self.assertEqual(sed.get_state(), 'detached') - - sed.radiofilter_disable() - self.assertFalse(sed.radiofilter_is_enabled()) - - self.simulator.go(WAIT_TIME) - self.assertEqual(sed.get_state(), 'child') - self.assertTrue(leader.ping(sed_mleid)) - self.assertTrue(sed.ping(leader_mleid)) - - # Validate energy scan when `radiofilter` is enabled - scan_result = leader.scan_energy() - self.assertTrue(len(scan_result) > 0) - leader.radiofilter_enable() - self.assertTrue(leader.radiofilter_is_enabled()) - scan_result = leader.scan_energy() - self.assertTrue(len(scan_result) == 0) - - -if __name__ == '__main__': - unittest.main()