[nexus] add test 5.3.5 Routing - Link Quality (#12415)

This commit adds a new Nexus test case for 'Routing - Link Quality'
(5.3.5) as specified in the Thread Test Specification.

The test validates that the DUT properly routes traffic when link
qualities between nodes are adjusted. It ensures that the shortest
path is chosen based on link costs and that direct neighbors are
prioritized for equal-cost paths.

Summary of changes:
- Implemented Nexus test 5.3.5:
    - Added test_5_3_5.cpp: Sets up a multi-hop topology using
      AllowList (Leader, DUT, Router 2, Router 3). Adjusts RSSI
      to simulate Link Quality levels 3, 2, 1, and 0 between the
      Leader and DUT. Initiates pings from Router 3 to Leader.
    - Added verify_5_3_5.py: PCAP verification script that validates
      multi-hop paths and ensures the 'hopsLft' field in the 6LoWPAN
      Mesh Header is correctly maintained.
- Updated build and execution scripts:
    - Modified CMakeLists.txt to build the new 5.3.5 test executable.
    - Updated run_nexus_tests.sh to include 5.3.5 in the default list.
This commit is contained in:
Jonathan Hui
2026-02-12 08:25:59 -06:00
committed by GitHub
parent cb50246d47
commit dcc6b2158d
4 changed files with 443 additions and 0 deletions
+1
View File
@@ -141,6 +141,7 @@ ot_nexus_test(5_3_1 "cert;nexus")
ot_nexus_test(5_3_2 "cert;nexus")
ot_nexus_test(5_3_3 "cert;nexus")
ot_nexus_test(5_3_4 "cert;nexus")
ot_nexus_test(5_3_5 "cert;nexus")
# Misc tests
ot_nexus_test(border_admitter "core;nexus")
+1
View File
@@ -71,6 +71,7 @@ DEFAULT_TESTS=(
"5_3_2"
"5_3_3"
"5_3_4"
"5_3_5"
)
# Use provided arguments or the default test list
+276
View File
@@ -0,0 +1,276 @@
/*
* 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.
*/
/**
* @file
* This file implements the Nexus test 5.3.5 Routing - Link Quality.
*/
#include <stdio.h>
#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 a node to join as a child and upgrade to a router, in milliseconds.
*/
static constexpr uint32_t kAttachToRouterTime = 200 * 1000;
/**
* Time to advance for the network to stabilize after routers have attached.
*/
static constexpr uint32_t kStabilizationTime = 20 * 1000;
/**
* Time to advance for the routing cost to be updated, in milliseconds.
*/
static constexpr uint32_t kRouteUpdateWaitTime = 60 * 1000;
/**
* ICMPv6 Echo Request identifier.
*/
static constexpr uint16_t kEchoIdentifier = 0x1234;
/**
* RSSI values for different link qualities (assuming -100 dBm noise floor).
*/
static constexpr int8_t kRssiLinkQuality3 = -70; /**< Margin 30 */
static constexpr int8_t kRssiLinkQuality2 = -85; /**< Margin 15 */
static constexpr int8_t kRssiLinkQuality1 = -95; /**< Margin 5 */
static constexpr int8_t kRssiLinkQuality0 = -110; /**< Margin < 0 (unusable) */
void Test5_3_5(void)
{
/**
* 5.3.5 Routing - Link Quality
*
* 5.3.5.1 Topology
* - Leader
* - Router_1 (DUT)
* - Router_2
* - Router_3
*
* 5.3.5.2 Purpose & Description
* The purpose of this test case is to ensure that the DUT routes traffic properly when link qualities between the
* nodes are adjusted.
*
* Spec Reference | V1.1 Section | V1.3.0 Section
* -------------------------------------------------|----------------|---------------
* Routing Protocol / Full Thread Device Forwarding | 5.9 / 5.10.1.1 | 5.9 / 5.10.1.1
*/
Core nexus;
Node &leader = nexus.CreateNode();
Node &dut = nexus.CreateNode();
Node &router2 = nexus.CreateNode();
Node &router3 = nexus.CreateNode();
leader.SetName("LEADER");
dut.SetName("DUT");
router2.SetName("ROUTER_2");
router3.SetName("ROUTER_3");
nexus.AdvanceTime(0);
Instance::SetLogLevel(kLogLevelNote);
Log("---------------------------------------------------------------------------------------");
Log("Step 1: All");
/**
* Step 1: All
* - Description: Ensure topology is formed correctly.
* - Pass Criteria: N/A
*/
leader.AllowList(dut);
leader.AllowList(router2);
dut.AllowList(leader);
dut.AllowList(router2);
dut.AllowList(router3);
router2.AllowList(leader);
router2.AllowList(dut);
router3.AllowList(dut);
/** Leader and Router 2 Link Quality 3 */
SuccessOrQuit(leader.Get<Mac::Filter>().AddRssIn(router2.Get<Mac::Mac>().GetExtAddress(), kRssiLinkQuality3));
SuccessOrQuit(router2.Get<Mac::Filter>().AddRssIn(leader.Get<Mac::Mac>().GetExtAddress(), kRssiLinkQuality3));
/** Router 1 (DUT) and Router 2 Link Quality 3 */
SuccessOrQuit(dut.Get<Mac::Filter>().AddRssIn(router2.Get<Mac::Mac>().GetExtAddress(), kRssiLinkQuality3));
SuccessOrQuit(router2.Get<Mac::Filter>().AddRssIn(dut.Get<Mac::Mac>().GetExtAddress(), kRssiLinkQuality3));
/** Router 1 (DUT) and Router 3 Link Quality 3 */
SuccessOrQuit(dut.Get<Mac::Filter>().AddRssIn(router3.Get<Mac::Mac>().GetExtAddress(), kRssiLinkQuality3));
SuccessOrQuit(router3.Get<Mac::Filter>().AddRssIn(dut.Get<Mac::Mac>().GetExtAddress(), kRssiLinkQuality3));
leader.Form();
nexus.AdvanceTime(kFormNetworkTime);
dut.Join(leader);
router2.Join(leader);
router3.Join(leader);
nexus.AdvanceTime(kAttachToRouterTime);
VerifyOrQuit(leader.Get<Mle::Mle>().IsLeader());
VerifyOrQuit(dut.Get<Mle::Mle>().IsRouter());
VerifyOrQuit(router2.Get<Mle::Mle>().IsRouter());
VerifyOrQuit(router3.Get<Mle::Mle>().IsRouter());
nexus.AdvanceTime(kStabilizationTime);
Log("---------------------------------------------------------------------------------------");
Log("Step 2: Harness");
/**
* Step 2: Harness
* - Description: Modifies the link quality between the DUT and the Leader to be 3.
* - Pass Criteria: N/A
*/
SuccessOrQuit(leader.Get<Mac::Filter>().AddRssIn(dut.Get<Mac::Mac>().GetExtAddress(), kRssiLinkQuality3));
SuccessOrQuit(dut.Get<Mac::Filter>().AddRssIn(leader.Get<Mac::Mac>().GetExtAddress(), kRssiLinkQuality3));
nexus.AdvanceTime(kRouteUpdateWaitTime);
Log("---------------------------------------------------------------------------------------");
Log("Step 3: Router_3");
/**
* Step 3: Router_3
* - Description: Harness instructs the device to send an ICMPv6 Echo Request to the Leader.
* - Pass Criteria:
* - The ICMPv6 Echo Request MUST take the shortest path: Router_3 -> DUT -> Leader.
* - The hopsLft field of the 6LoWPAN Mesh Header MUST be greater than the route cost to the destination.
*/
router3.SendEchoRequest(leader.Get<Mle::Mle>().GetMeshLocalEid(), kEchoIdentifier);
nexus.AdvanceTime(kStabilizationTime);
Log("---------------------------------------------------------------------------------------");
Log("Step 4: Harness");
/**
* Step 4: Harness
* - Description: Sets the link quality between the Leader and the DUT to 1.
* - Pass Criteria: N/A
*/
SuccessOrQuit(leader.Get<Mac::Filter>().AddRssIn(dut.Get<Mac::Mac>().GetExtAddress(), kRssiLinkQuality1));
SuccessOrQuit(dut.Get<Mac::Filter>().AddRssIn(leader.Get<Mac::Mac>().GetExtAddress(), kRssiLinkQuality1));
nexus.AdvanceTime(kRouteUpdateWaitTime);
Log("---------------------------------------------------------------------------------------");
Log("Step 5: Router_3");
/**
* Step 5: Router_3
* - Description: Harness instructs the device to send an ICMPv6 Echo Request to the Leader.
* - Pass Criteria:
* - The ICMPv6 Echo Request MUST take the longer path: Router_3 -> DUT -> Router_2 -> Leader.
* - The hopsLft field of the 6LoWPAN Mesh Header MUST be greater than the route cost to the destination.
*/
router3.SendEchoRequest(leader.Get<Mle::Mle>().GetMeshLocalEid(), kEchoIdentifier);
nexus.AdvanceTime(kStabilizationTime);
Log("---------------------------------------------------------------------------------------");
Log("Step 6: Harness");
/**
* Step 6: Harness
* - Description: Sets the link quality between the Leader and the DUT to 2.
* - Pass Criteria: N/A
*/
SuccessOrQuit(leader.Get<Mac::Filter>().AddRssIn(dut.Get<Mac::Mac>().GetExtAddress(), kRssiLinkQuality2));
SuccessOrQuit(dut.Get<Mac::Filter>().AddRssIn(leader.Get<Mac::Mac>().GetExtAddress(), kRssiLinkQuality2));
nexus.AdvanceTime(kRouteUpdateWaitTime);
Log("---------------------------------------------------------------------------------------");
Log("Step 7: Router_3");
/**
* Step 7: Router_3
* - Description: Harness instructs device to send an ICMPv6 Echo Request to the Leader.
* - Pass Criteria:
* - The DUT MUST have two paths with the same cost, and MUST prioritize sending to a direct neighbor:
* Router_3 -> DUT -> Leader.
* - The hopsLft field of the 6LoWPAN Mesh Header MUST be greater than the route cost to the destination.
*/
router3.SendEchoRequest(leader.Get<Mle::Mle>().GetMeshLocalEid(), kEchoIdentifier);
nexus.AdvanceTime(kStabilizationTime);
Log("---------------------------------------------------------------------------------------");
Log("Step 8: Harness");
/**
* Step 8: Harness
* - Description: Sets the link quality between the Leader and the DUT to 0 (infinite).
* - Pass Criteria: N/A
*/
SuccessOrQuit(leader.Get<Mac::Filter>().AddRssIn(dut.Get<Mac::Mac>().GetExtAddress(), kRssiLinkQuality0));
SuccessOrQuit(dut.Get<Mac::Filter>().AddRssIn(leader.Get<Mac::Mac>().GetExtAddress(), kRssiLinkQuality0));
nexus.AdvanceTime(kRouteUpdateWaitTime);
Log("---------------------------------------------------------------------------------------");
Log("Step 9: Router_3");
/**
* Step 9: Router_3
* - Description: Harness instructs device to send an ICMPv6 Echo Request from to the Leader.
* - Pass Criteria:
* - The ICMPv6 Echo Request MUST follow the longer path: Router_3 -> DUT -> Router_2 -> Leader.
* - The hopsLft field of the 6LoWPAN Mesh Header MUST be greater than the route cost to the destination.
*/
router3.SendEchoRequest(leader.Get<Mle::Mle>().GetMeshLocalEid(), kEchoIdentifier);
nexus.AdvanceTime(kStabilizationTime);
nexus.SaveTestInfo("test_5_3_5.json");
}
} // namespace Nexus
} // namespace ot
int main(void)
{
ot::Nexus::Test5_3_5();
printf("All tests passed\n");
return 0;
}
+165
View File
@@ -0,0 +1,165 @@
#!/usr/bin/env python3
#
# 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.
#
import sys
import os
# Add the current directory to sys.path to find verify_utils
CUR_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(CUR_DIR)
import verify_utils
from pktverify import consts
# ICMPv6 Echo Request identifier used in the C++ test.
ECHO_IDENTIFIER = 0x1234
def get_hops(p):
return p.lowpan.mesh.hops8 if hasattr(p.lowpan.mesh, 'hops8') else p.lowpan.mesh.hops
def verify(pv):
# 5.3.5 Routing - Link Quality
#
# 5.3.5.1 Topology
# - Leader
# - Router_1 (DUT)
# - Router_2
# - Router_3
#
# 5.3.5.2 Purpose & Description
# The purpose of this test case is to ensure that the DUT routes traffic properly when link qualities between the
# nodes are adjusted.
#
# Spec Reference | V1.1 Section | V1.3.0 Section
# -------------------------------------------------|----------------|---------------
# Routing Protocol / Full Thread Device Forwarding | 5.9 / 5.10.1.1 | 5.9 / 5.10.1.1
pkts = pv.pkts
pv.summary.show()
LEADER_RLOC16 = pv.vars['LEADER_RLOC16']
DUT_RLOC16 = pv.vars['DUT_RLOC16']
ROUTER_2_RLOC16 = pv.vars['ROUTER_2_RLOC16']
ROUTER_3_RLOC16 = pv.vars['ROUTER_3_RLOC16']
# Step 1: All
# - Description: Ensure topology is formed correctly.
# - Pass Criteria: N/A
print("Step 1: All")
# Step 2: Harness
# - Description: Modifies the link quality between the DUT and the Leader to be 3.
# - Pass Criteria: N/A
print("Step 2: Harness")
# Step 3: Router_3
# - Description: Harness instructs the device to send an ICMPv6 Echo Request to the Leader.
# - Pass Criteria:
# - The ICMPv6 Echo Request MUST take the shortest path: Router_3 -> DUT -> Leader.
# - The hopsLft field of the 6LoWPAN Mesh Header MUST be greater than the route cost to the destination.
print("Step 3: Router_3")
# Router_3 -> DUT
p1 = pkts.filter_wpan_src16(ROUTER_3_RLOC16).filter_wpan_dst16(DUT_RLOC16).filter_ping_request(
identifier=ECHO_IDENTIFIER).must_next()
# DUT -> Leader
p2 = pkts.filter_wpan_src16(DUT_RLOC16).filter_wpan_dst16(LEADER_RLOC16).filter_ping_request(
identifier=ECHO_IDENTIFIER).filter(lambda p: p.lowpan.mesh.hops > 1).must_next()
assert get_hops(p2) == get_hops(p1) - 1, f'Hops left not decremented correctly: {get_hops(p1)} -> {get_hops(p2)}'
# Step 4: Harness
# - Description: Sets the link quality between the Leader and the DUT to 1.
# - Pass Criteria: N/A
print("Step 4: Harness")
# Step 5: Router_3
# - Description: Harness instructs the device to send an ICMPv6 Echo Request to the Leader.
# - Pass Criteria:
# - The ICMPv6 Echo Request MUST take the longer path: Router_3 -> DUT -> Router_2 -> Leader.
# - The hopsLft field of the 6LoWPAN Mesh Header MUST be greater than the route cost to the destination.
print("Step 5: Router_3")
# Router_3 -> DUT
p1 = pkts.filter_wpan_src16(ROUTER_3_RLOC16).filter_wpan_dst16(DUT_RLOC16).filter_ping_request(
identifier=ECHO_IDENTIFIER).must_next()
# DUT -> Router_2
p2 = pkts.filter_wpan_src16(DUT_RLOC16).filter_wpan_dst16(ROUTER_2_RLOC16).filter_ping_request(
identifier=ECHO_IDENTIFIER).filter(lambda p: p.lowpan.mesh.hops > 2).must_next()
assert get_hops(p2) == get_hops(p1) - 1, f'Hops left not decremented correctly: {get_hops(p1)} -> {get_hops(p2)}'
# Router_2 -> Leader
p3 = pkts.filter_wpan_src16(ROUTER_2_RLOC16).filter_wpan_dst16(LEADER_RLOC16).filter_ping_request(
identifier=ECHO_IDENTIFIER).filter(lambda p: p.lowpan.mesh.hops > 1).must_next()
assert get_hops(p3) == get_hops(p2) - 1, f'Hops left not decremented correctly: {get_hops(p2)} -> {get_hops(p3)}'
# Step 6: Harness
# - Description: Sets the link quality between the Leader and the DUT to 2.
# - Pass Criteria: N/A
print("Step 6: Harness")
# Step 7: Router_3
# - Description: Harness instructs device to send an ICMPv6 Echo Request to the Leader.
# - Pass Criteria:
# - The DUT MUST have two paths with the same cost, and MUST prioritize sending to a direct neighbor:
# Router_3 -> DUT -> Leader.
# - The hopsLft field of the 6LoWPAN Mesh Header MUST be greater than the route cost to the destination.
print("Step 7: Router_3")
# Router_3 -> DUT
p1 = pkts.filter_wpan_src16(ROUTER_3_RLOC16).filter_wpan_dst16(DUT_RLOC16).filter_ping_request(
identifier=ECHO_IDENTIFIER).must_next()
# DUT -> Leader
p2 = pkts.filter_wpan_src16(DUT_RLOC16).filter_wpan_dst16(LEADER_RLOC16).filter_ping_request(
identifier=ECHO_IDENTIFIER).filter(lambda p: p.lowpan.mesh.hops > 2).must_next()
assert get_hops(p2) == get_hops(p1) - 1, f'Hops left not decremented correctly: {get_hops(p1)} -> {get_hops(p2)}'
# Step 8: Harness
# - Description: Sets the link quality between the Leader and the DUT to 0 (infinite).
# - Pass Criteria: N/A
print("Step 8: Harness")
# Step 9: Router_3
# - Description: Harness instructs device to send an ICMPv6 Echo Request from to the Leader.
# - Pass Criteria:
# - The ICMPv6 Echo Request MUST follow the longer path: Router_3 -> DUT -> Router_2 -> Leader.
# - The hopsLft field of the 6LoWPAN Mesh Header MUST be greater than the route cost to the destination.
print("Step 9: Router_3")
# Router_3 -> DUT
p1 = pkts.filter_wpan_src16(ROUTER_3_RLOC16).filter_wpan_dst16(DUT_RLOC16).filter_ping_request(
identifier=ECHO_IDENTIFIER).must_next()
# DUT -> Router_2
p2 = pkts.filter_wpan_src16(DUT_RLOC16).filter_wpan_dst16(ROUTER_2_RLOC16).filter_ping_request(
identifier=ECHO_IDENTIFIER).filter(lambda p: p.lowpan.mesh.hops > 2).must_next()
assert get_hops(p2) == get_hops(p1) - 1, f'Hops left not decremented correctly: {get_hops(p1)} -> {get_hops(p2)}'
# Router_2 -> Leader
p3 = pkts.filter_wpan_src16(ROUTER_2_RLOC16).filter_wpan_dst16(LEADER_RLOC16).filter_ping_request(
identifier=ECHO_IDENTIFIER).filter(lambda p: p.lowpan.mesh.hops > 1).must_next()
assert get_hops(p3) == get_hops(p2) - 1, f'Hops left not decremented correctly: {get_hops(p2)} -> {get_hops(p3)}'
if __name__ == '__main__':
verify_utils.run_main(verify)