mirror of
https://github.com/espressif/openthread.git
synced 2026-07-27 06:17:47 +00:00
[nexus] add test 6.5.3 and fix SED polling verification (#12496)
This commit adds a new Nexus test case for 'Child Synchronization
after Reset - MLE Child Update Request' (6.5.3) as specified in the
Thread test specification.
It also corrects and improves the verification of Sleepy End Device
(SED) polling behavior across Nexus tests.
Summary of changes:
- Implemented Nexus test 6.5.3:
- Added tests/nexus/test_6_5_3.cpp: Implements the test
execution for Topology B (SED_1). The test simulates a DUT
reset for a time shorter than its Child Timeout and verifies
correct synchronization with its parent.
- Added tests/nexus/verify_6_5_3.py: PCAP verification script
validating the MLE Child Update Request and SED polling.
- Fixed SED polling verification:
- Updated verify_6_5_3.py and verify_6_5_1.py to correctly
check for MAC Data Request frames (WPAN_DATA_REQUEST) instead
of MLE Data Requests.
- Switched to RLOC16-based filtering for MAC Data Requests to
reliably match packets even when 64-bit addresses are not
available due to encryption or short addressing.
- Updated build and execution scripts:
- Modified tests/nexus/CMakeLists.txt to build the new test.
- Updated tests/nexus/run_nexus_tests.sh to include 6_5_3.
This commit is contained in:
@@ -177,6 +177,7 @@ ot_nexus_test(6_4_1 "cert;nexus")
|
||||
ot_nexus_test(6_4_2 "cert;nexus")
|
||||
ot_nexus_test(6_5_1 "cert;nexus")
|
||||
ot_nexus_test(6_5_2 "cert;nexus")
|
||||
ot_nexus_test(6_5_3 "cert;nexus")
|
||||
ot_nexus_test(6_6_1 "cert;nexus")
|
||||
ot_nexus_test(6_6_2 "cert;nexus")
|
||||
ot_nexus_test(7_1_1 "cert;nexus")
|
||||
|
||||
@@ -113,6 +113,7 @@ DEFAULT_TESTS=(
|
||||
"6_4_2"
|
||||
"6_5_1"
|
||||
"6_5_2"
|
||||
"6_5_3"
|
||||
"6_6_1"
|
||||
"6_6_2"
|
||||
"7_1_1"
|
||||
@@ -206,7 +207,7 @@ run_test()
|
||||
expanded_tests=()
|
||||
for t in "${TESTS_TO_RUN[@]}"; do
|
||||
case "$t" in
|
||||
6_1_1 | 6_1_2 | 6_1_3 | 6_1_6 | 6_2_1 | 6_2_2 | 6_3_1 | 6_3_2 | 6_4_1 | 6_4_2 | 6_5_1 | 6_5_2 | 6_6_1 | 6_6_2)
|
||||
6_1_1 | 6_1_2 | 6_1_3 | 6_1_6 | 6_2_1 | 6_2_2 | 6_3_1 | 6_3_2 | 6_4_1 | 6_4_2 | 6_5_1 | 6_5_2 | 6_5_3 | 6_6_1 | 6_6_2)
|
||||
expanded_tests+=("${t}_A" "${t}_B")
|
||||
;;
|
||||
*)
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mac/data_poll_sender.hpp"
|
||||
#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 the DUT to attach to the router, in milliseconds.
|
||||
*/
|
||||
static constexpr uint32_t kAttachTime = 10 * 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 wait for ICMPv6 Echo response, in milliseconds.
|
||||
*/
|
||||
static constexpr uint32_t kEchoTimeout = 5000;
|
||||
|
||||
/**
|
||||
* Data poll period for SED, in milliseconds.
|
||||
*/
|
||||
static constexpr uint32_t kPollPeriod = 500;
|
||||
|
||||
/**
|
||||
* Child timeout duration for SED in seconds.
|
||||
*/
|
||||
static constexpr uint32_t kChildTimeoutSeconds = 10;
|
||||
|
||||
/**
|
||||
* Time to reset the DUT, in milliseconds. Must be shorter than Child Timeout.
|
||||
*/
|
||||
static constexpr uint32_t kResetTimeMs = (kChildTimeoutSeconds / 2) * 1000;
|
||||
|
||||
/**
|
||||
* Time to advance for synchronization, in milliseconds.
|
||||
*/
|
||||
static constexpr uint32_t kSyncTime = 10 * 1000;
|
||||
|
||||
/**
|
||||
* Payload size for ICMPv6 Echo Request.
|
||||
*/
|
||||
static constexpr uint16_t kEchoPayloadSize = 0;
|
||||
|
||||
/**
|
||||
* Hop limit for ICMPv6 Echo Request.
|
||||
*/
|
||||
static constexpr uint8_t kEchoHopLimit = 64;
|
||||
|
||||
enum Topology
|
||||
{
|
||||
kTopologyA,
|
||||
kTopologyB,
|
||||
};
|
||||
|
||||
void RunTest6_5_3(Topology aTopology, const char *aJsonFile)
|
||||
{
|
||||
/**
|
||||
* 6.5.3 Child Synchronization after Reset - MLE Child Update Request
|
||||
*
|
||||
* 6.5.3.1 Topology
|
||||
* - Topology A: DUT as End Device (ED_1) attached to Leader
|
||||
* - Topology B: DUT as Sleepy End Device (SED_1) attached to Leader
|
||||
*
|
||||
* 6.5.3.2 Purpose & Description
|
||||
* The purpose of this test case is to validate that after the DUT resets for a time period shorter than the Child
|
||||
* Timeout value, it sends an MLE Child Update Request to its parent and remains connected to its parent.
|
||||
*
|
||||
* Spec Reference | V1.1 Section | V1.3.0 Section
|
||||
* ----------------------------------|--------------|---------------
|
||||
* Child Synchronization after Reset | 4.7.6 | 4.6.4
|
||||
*/
|
||||
|
||||
Core nexus;
|
||||
|
||||
Node &leader = nexus.CreateNode();
|
||||
Node &router1 = nexus.CreateNode();
|
||||
Node &dut = nexus.CreateNode();
|
||||
|
||||
leader.SetName("LEADER");
|
||||
router1.SetName("ROUTER_1");
|
||||
|
||||
if (aTopology == kTopologyA)
|
||||
{
|
||||
dut.SetName("ED_1");
|
||||
}
|
||||
else
|
||||
{
|
||||
dut.SetName("SED_1");
|
||||
}
|
||||
|
||||
nexus.AdvanceTime(0);
|
||||
|
||||
Instance::SetLogLevel(kLogLevelNote);
|
||||
|
||||
/**
|
||||
* Step 1: All
|
||||
* - Description: Ensure topology is formed correctly.
|
||||
* - Pass Criteria: N/A.
|
||||
*/
|
||||
Log("Step 1: All");
|
||||
|
||||
leader.AllowList(router1);
|
||||
router1.AllowList(leader);
|
||||
router1.AllowList(dut);
|
||||
dut.AllowList(router1);
|
||||
|
||||
leader.Form();
|
||||
nexus.AdvanceTime(kFormNetworkTime);
|
||||
VerifyOrQuit(leader.Get<Mle::Mle>().IsLeader());
|
||||
|
||||
router1.Join(leader);
|
||||
nexus.AdvanceTime(kAttachToRouterTime);
|
||||
VerifyOrQuit(router1.Get<Mle::Mle>().IsRouter());
|
||||
|
||||
if (aTopology == kTopologyA)
|
||||
{
|
||||
dut.Join(router1, Node::kAsMed);
|
||||
}
|
||||
else
|
||||
{
|
||||
dut.Join(router1, Node::kAsSed);
|
||||
SuccessOrQuit(dut.Get<DataPollSender>().SetExternalPollPeriod(kPollPeriod));
|
||||
}
|
||||
dut.Get<Mle::Mle>().SetTimeout(kChildTimeoutSeconds);
|
||||
|
||||
nexus.AdvanceTime(kAttachTime);
|
||||
VerifyOrQuit(dut.Get<Mle::Mle>().IsChild());
|
||||
VerifyOrQuit(dut.Get<Mle::Mle>().GetParent().GetExtAddress() == router1.Get<Mac::Mac>().GetExtAddress());
|
||||
|
||||
/**
|
||||
* Step 2: ED_1 / SED_1 (DUT)
|
||||
* - Description: Test Harness Prompt: Reset End Device for a time shorter than the Child Timeout Duration.
|
||||
* - Pass Criteria: N/A.
|
||||
*/
|
||||
Log("Step 2: ED_1 / SED_1 (DUT)");
|
||||
|
||||
dut.Reset();
|
||||
nexus.AdvanceTime(kResetTimeMs);
|
||||
|
||||
/**
|
||||
* Step 3: ED_1 / SED_1 (DUT)
|
||||
* - Description: Automatically sends MLE Child Update Request to the Leader.
|
||||
* - Pass Criteria:
|
||||
* - The following TLVs MUST be included in the Child Update Request:
|
||||
* - Mode TLV
|
||||
* - Challenge TLV (required for Thread version >= 4)
|
||||
* - Address Registration TLV (optional)
|
||||
* - If the DUT is a SED, it MUST resume polling after sending MLE Child Update Request.
|
||||
*/
|
||||
Log("Step 3: ED_1 / SED_1 (DUT)");
|
||||
|
||||
dut.Get<ThreadNetif>().Up();
|
||||
if (aTopology == kTopologyB)
|
||||
{
|
||||
SuccessOrQuit(dut.Get<DataPollSender>().SetExternalPollPeriod(kPollPeriod));
|
||||
}
|
||||
SuccessOrQuit(dut.Get<Mle::Mle>().Start());
|
||||
|
||||
nexus.AdvanceTime(kSyncTime);
|
||||
|
||||
/**
|
||||
* Step 4: Leader
|
||||
* - Description: Automatically sends an MLE Child Update Response.
|
||||
* - Pass Criteria: N/A.
|
||||
*/
|
||||
Log("Step 4: Leader");
|
||||
|
||||
nexus.AdvanceTime(kSyncTime);
|
||||
|
||||
/**
|
||||
* Step 5: Leader
|
||||
* - Description: Harness verifies connectivity by instructing the device to send an ICMPv6 Echo Request to the
|
||||
* DUT link local address.
|
||||
* - Pass Criteria:
|
||||
* - The DUT MUST respond with ICMPv6 Echo Reply.
|
||||
*/
|
||||
Log("Step 5: Leader");
|
||||
|
||||
nexus.SendAndVerifyEchoRequest(router1, dut.Get<Mle::Mle>().GetLinkLocalAddress(), kEchoPayloadSize, kEchoHopLimit,
|
||||
kEchoTimeout);
|
||||
|
||||
nexus.SaveTestInfo(aJsonFile);
|
||||
}
|
||||
|
||||
} // namespace Nexus
|
||||
} // namespace ot
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc < 2)
|
||||
{
|
||||
ot::Nexus::RunTest6_5_3(ot::Nexus::kTopologyA, "test_6_5_3_A.json");
|
||||
ot::Nexus::RunTest6_5_3(ot::Nexus::kTopologyB, "test_6_5_3_B.json");
|
||||
}
|
||||
else if (strcmp(argv[1], "A") == 0)
|
||||
{
|
||||
ot::Nexus::RunTest6_5_3(ot::Nexus::kTopologyA, (argc > 2) ? argv[2] : "test_6_5_3_A.json");
|
||||
}
|
||||
else if (strcmp(argv[1], "B") == 0)
|
||||
{
|
||||
ot::Nexus::RunTest6_5_3(ot::Nexus::kTopologyB, (argc > 2) ? argv[2] : "test_6_5_3_B.json");
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Error: Invalid topology '%s'. Must be 'A' or 'B'.\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("All tests passed\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -89,6 +89,13 @@ def verify(pv):
|
||||
} <= set(p.mle.tlv.type)).\
|
||||
must_next()
|
||||
|
||||
if 'SED_1' in pv.vars:
|
||||
with pkts.save_index():
|
||||
pkts.filter(lambda p: p.wpan.src16 == pv.vars['SED_1_RLOC16'] and
|
||||
p.wpan.dst16 == pv.vars['LEADER_RLOC16']).\
|
||||
filter_wpan_cmd(consts.WPAN_DATA_REQUEST).\
|
||||
must_next()
|
||||
|
||||
# Step 4: Leader
|
||||
# - Description: Automatically sends an MLE Child Update Response with a status of “Error”.
|
||||
# - Pass Criteria: N/A
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
#!/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
|
||||
|
||||
|
||||
def verify(pv):
|
||||
# 6.5.3 Child Synchronization after Reset - MLE Child Update Request
|
||||
#
|
||||
# 6.5.3.1 Topology
|
||||
# - Topology A: DUT as End Device (ED_1) attached to Leader
|
||||
# - Topology B: DUT as Sleepy End Device (SED_1) attached to Leader
|
||||
#
|
||||
# 6.5.3.2 Purpose & Description
|
||||
# The purpose of this test case is to validate that after the DUT resets for a time period shorter than the Child
|
||||
# Timeout value, it sends an MLE Child Update Request to its parent and remains connected to its parent.
|
||||
#
|
||||
# Spec Reference | V1.1 Section | V1.3.0 Section
|
||||
# ----------------------------------|--------------|---------------
|
||||
# Child Synchronization after Reset | 4.7.6 | 4.6.4
|
||||
|
||||
pkts = pv.pkts
|
||||
pv.summary.show()
|
||||
|
||||
ROUTER_1 = pv.vars['ROUTER_1']
|
||||
|
||||
if 'ED_1' in pv.vars:
|
||||
dut = pv.vars['ED_1']
|
||||
name = 'ED_1'
|
||||
topology = 'A'
|
||||
elif 'SED_1' in pv.vars:
|
||||
dut = pv.vars['SED_1']
|
||||
name = 'SED_1'
|
||||
topology = 'B'
|
||||
else:
|
||||
raise ValueError("Neither ED_1 nor SED_1 found in topology vars")
|
||||
|
||||
# Step 1: All
|
||||
# - Description: Ensure topology is formed correctly.
|
||||
# - Pass Criteria: N/A.
|
||||
print("Step 1: All")
|
||||
|
||||
# Step 2: ED_1 / SED_1 (DUT)
|
||||
# - Description: Test Harness Prompt: Reset End Device for a time shorter than the Child Timeout Duration.
|
||||
# - Pass Criteria: N/A.
|
||||
print(f"Step 2: {name} (DUT)")
|
||||
|
||||
# Step 3: ED_1 / SED_1 (DUT)
|
||||
# - Description: Automatically sends MLE Child Update Request to the Leader.
|
||||
# - Pass Criteria:
|
||||
# - The following TLVs MUST be included in the Child Update Request:
|
||||
# - Mode TLV
|
||||
# - Challenge TLV (required for Thread version >= 4)
|
||||
# - Address Registration TLV (optional)
|
||||
# - If the DUT is a SED, it MUST resume polling after sending MLE Child Update Request.
|
||||
print(f"Step 3: {name} (DUT)")
|
||||
|
||||
pkts.filter_wpan_src64(dut).\
|
||||
filter_wpan_dst64(ROUTER_1).\
|
||||
filter_mle_cmd(consts.MLE_CHILD_UPDATE_REQUEST).\
|
||||
filter(lambda p: {
|
||||
consts.MODE_TLV,
|
||||
consts.CHALLENGE_TLV
|
||||
} <= set(p.mle.tlv.type)).\
|
||||
must_next()
|
||||
|
||||
if topology == 'B':
|
||||
with pkts.save_index():
|
||||
pkts.filter(lambda p: p.wpan.src16 == pv.vars[name + '_RLOC16'] and
|
||||
p.wpan.dst16 == pv.vars['ROUTER_1_RLOC16']).\
|
||||
filter_wpan_cmd(consts.WPAN_DATA_REQUEST).\
|
||||
must_next()
|
||||
|
||||
# Step 4: Leader
|
||||
# - Description: Automatically sends an MLE Child Update Response.
|
||||
# - Pass Criteria: N/A.
|
||||
print("Step 4: Leader")
|
||||
|
||||
pkts.filter_wpan_src64(ROUTER_1).\
|
||||
filter_wpan_dst64(dut).\
|
||||
filter_mle_cmd(consts.MLE_CHILD_UPDATE_RESPONSE).\
|
||||
must_next()
|
||||
|
||||
# Step 5: Leader
|
||||
# - Description: Harness verifies connectivity by instructing the device to send an ICMPv6 Echo Request to the
|
||||
# DUT link local address.
|
||||
# - Pass Criteria:
|
||||
# - The DUT MUST respond with ICMPv6 Echo Reply.
|
||||
print("Step 5: Leader")
|
||||
|
||||
_pkt = pkts.filter_ping_request().\
|
||||
filter_wpan_src64(ROUTER_1).\
|
||||
filter_wpan_dst64(dut).\
|
||||
must_next()
|
||||
|
||||
pkts.filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier).\
|
||||
filter_wpan_src64(dut).\
|
||||
filter_wpan_dst64(ROUTER_1).\
|
||||
must_next()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
verify_utils.run_main(verify)
|
||||
Reference in New Issue
Block a user