[nexus] add test 6.5.1 Child Synchronization after Reset - Reattach (#12488)

This commit adds a new Nexus test case for 'Child Synchronization after
Reset - Reattach' (6.5.1) as specified in the test specification.

Summary of changes:
- Implemented Nexus test 6.5.1:
    - Added tests/nexus/test_6_5_1.cpp: Implements the test execution
      for both Topology A (Minimal End Device 'MED_1') and Topology B
      (Sleepy End Device 'SED_1'). The test simulates a DUT reset for
      a time longer than its Child Timeout and verifies that it
      correctly reattaches to its parent (Leader). The test uses direct
      method calls, sets log level to note, and uses AllowList for
      connectivity.
    - Added tests/nexus/verify_6_5_1.py: PCAP verification script.
      Validates the MLE Child Update Request TLVs, handles the 'Error'
      status response from the Leader, and confirms the subsequent
      reattachment handshake and ICMPv6 connectivity. Patches
      pktverify to support the mle.tlv.status field.
- 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_1 in the
      default test list and added expansion logic for A/B topologies.
This commit is contained in:
Jonathan Hui
2026-02-19 11:49:14 -06:00
committed by GitHub
parent 04f719e7f7
commit c60b4fc01c
4 changed files with 408 additions and 1 deletions
+1
View File
@@ -171,6 +171,7 @@ ot_nexus_test(6_2_2 "cert;nexus")
ot_nexus_test(6_3_1 "cert;nexus")
ot_nexus_test(6_3_2 "cert;nexus")
ot_nexus_test(6_4_1 "cert;nexus")
ot_nexus_test(6_5_1 "cert;nexus")
ot_nexus_test(7_1_1 "cert;nexus")
# Misc tests
+2 -1
View File
@@ -107,6 +107,7 @@ DEFAULT_TESTS=(
"6_3_2"
"6_4_1_A"
"6_4_1_B"
"6_5_1"
"7_1_1"
)
@@ -199,7 +200,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_2_1 | 6_2_2 | 6_3_1 | 6_3_2 | 6_4_1)
6_1_1 | 6_1_2 | 6_1_3 | 6_2_1 | 6_2_2 | 6_3_1 | 6_3_2 | 6_4_1 | 6_5_1)
expanded_tests+=("${t}_A" "${t}_B")
;;
*)
+240
View File
@@ -0,0 +1,240 @@
/*
* 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 leader, in milliseconds.
*/
static constexpr uint32_t kAttachTime = 10 * 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 kChildTimeout = 4;
/**
* Time to reset the DUT, in milliseconds. Must be greater than Child Timeout.
*/
static constexpr uint32_t kResetTime = (kChildTimeout + 5) * 1000;
/**
* Time to advance for re-attachment and synchronization, in milliseconds.
*/
static constexpr uint32_t kReattachTime = 40 * 1000;
enum Topology
{
kTopologyA,
kTopologyB,
};
void RunTest6_5_1(Topology aTopology, const char *aJsonFile)
{
/**
* 6.5.1 Child Synchronization after Reset - Reattach
*
* 6.5.1.1 Topology
* - Topology A: DUT as End Device (ED_1)
* - Topology B: DUT as Sleepy End Device (SED_1)
* - Leader
*
* 6.5.1.2 Purpose & Description
* The purpose of this test case is to validate that after the DUT resets for a time period longer than the Child
* Timeout value, it sends an MLE Child Update Request and reattaches 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 &dut = nexus.CreateNode();
leader.SetName("LEADER");
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(dut);
dut.AllowList(leader);
leader.Form();
nexus.AdvanceTime(kFormNetworkTime);
VerifyOrQuit(leader.Get<Mle::Mle>().IsLeader());
if (aTopology == kTopologyA)
{
dut.Join(leader, Node::kAsMed);
}
else
{
dut.Join(leader, Node::kAsSed);
SuccessOrQuit(dut.Get<DataPollSender>().SetExternalPollPeriod(kPollPeriod));
}
dut.Get<Mle::Mle>().SetTimeout(kChildTimeout);
nexus.AdvanceTime(kAttachTime);
VerifyOrQuit(dut.Get<Mle::Mle>().IsChild());
/**
* Step 2: ED_1 / SED_1 (DUT)
* - Description: Test Harness Prompt: Reset the DUT for a time greater than the Child Timeout Duration.
* - Pass Criteria: N/A
*/
Log("Step 2: ED_1 / SED_1 (DUT)");
dut.Reset();
nexus.AdvanceTime(kResetTime);
/**
* 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.
*/
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(5000);
/**
* Step 4: Leader
* - Description: Automatically sends an MLE Child Update Response with a status of “Error”.
* - Pass Criteria: N/A
*/
Log("Step 4: Leader");
nexus.AdvanceTime(5000);
/**
* Step 5: ED_1 / SED_1 (DUT)
* - Description: Automatically reattaches to the Leader.
* - Pass Criteria:
* - The DUT MUST reattach to the Leader following the procedure in 6.1.1 Attaching to a Router.
*/
Log("Step 5: ED_1 / SED_1 (DUT)");
nexus.AdvanceTime(kReattachTime);
VerifyOrQuit(dut.Get<Mle::Mle>().IsAttached());
VerifyOrQuit(dut.Get<Mle::Mle>().IsChild());
/**
* Step 6: Leader
* - Description: Harness verifies connectivity by instructing 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 6: Leader");
nexus.SendAndVerifyEchoRequest(leader, dut.Get<Mle::Mle>().GetLinkLocalAddress(), 0, 64, kEchoTimeout);
nexus.SaveTestInfo(aJsonFile);
}
} // namespace Nexus
} // namespace ot
int main(int argc, char *argv[])
{
if (argc < 2)
{
ot::Nexus::RunTest6_5_1(ot::Nexus::kTopologyA, "test_6_5_1_A.json");
ot::Nexus::RunTest6_5_1(ot::Nexus::kTopologyB, "test_6_5_1_B.json");
}
else if (strcmp(argv[1], "A") == 0)
{
ot::Nexus::RunTest6_5_1(ot::Nexus::kTopologyA, (argc > 2) ? argv[2] : "test_6_5_1_A.json");
}
else if (strcmp(argv[1], "B") == 0)
{
ot::Nexus::RunTest6_5_1(ot::Nexus::kTopologyB, (argc > 2) ? argv[2] : "test_6_5_1_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;
}
+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
from pktverify.null_field import nullField
def verify(pv):
# 6.5.1 Child Synchronization after Reset - Reattach
#
# 6.5.1.1 Topology
# - Topology A: DUT as End Device (ED_1)
# - Topology B: DUT as Sleepy End Device (SED_1)
# - Leader
#
# 6.5.1.2 Purpose & Description
# The purpose of this test case is to validate that after the DUT resets for a time period longer than the Child
# Timeout value, it sends an MLE Child Update Request and reattaches 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()
LEADER = pv.vars['LEADER']
DUT = pv.vars['ED_1'] if 'ED_1' in pv.vars else pv.vars['SED_1']
# 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 the DUT for a time greater than the Child Timeout Duration.
# - Pass Criteria: N/A
print("Step 2: ED_1 / SED_1 (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.
print("Step 3: ED_1 / SED_1 (DUT)")
pkts.filter_wpan_src64(DUT).\
filter_wpan_dst64(LEADER).\
filter_mle_cmd(consts.MLE_CHILD_UPDATE_REQUEST).\
filter(lambda p: {
consts.MODE_TLV,
consts.CHALLENGE_TLV
} <= set(p.mle.tlv.type)).\
must_next()
# Step 4: Leader
# - Description: Automatically sends an MLE Child Update Response with a status of “Error”.
# - Pass Criteria: N/A
print("Step 4: Leader")
if 'ED_1' in pv.vars:
pkts.filter_wpan_src64(LEADER).\
filter_wpan_dst64(DUT).\
filter_mle_cmd(consts.MLE_CHILD_UPDATE_RESPONSE).\
filter(lambda p: p.mle.tlv.status == 1).\
must_next()
# Step 5: ED_1 / SED_1 (DUT)
# - Description: Automatically reattaches to the Leader.
# - Pass Criteria:
# - The DUT MUST reattach to the Leader following the procedure in 6.1.1 Attaching to a Router.
print("Step 5: ED_1 / SED_1 (DUT)")
# 5.1 MLE Parent Request
pkts.filter_wpan_src64(DUT).\
filter_LLARMA().\
filter_mle_cmd(consts.MLE_PARENT_REQUEST).\
filter(lambda p: {
consts.CHALLENGE_TLV,
consts.MODE_TLV,
consts.SCAN_MASK_TLV,
consts.VERSION_TLV
} <= set(p.mle.tlv.type)).\
must_next()
# 5.2 MLE Parent Response
pkts.filter_wpan_src64(LEADER).\
filter_wpan_dst64(DUT).\
filter_mle_cmd(consts.MLE_PARENT_RESPONSE).\
must_next()
# 5.3 MLE Child ID Request
pkts.filter_wpan_src64(DUT).\
filter_wpan_dst64(LEADER).\
filter_mle_cmd(consts.MLE_CHILD_ID_REQUEST).\
filter(lambda p: {
consts.LINK_LAYER_FRAME_COUNTER_TLV,
consts.MODE_TLV,
consts.RESPONSE_TLV,
consts.TIMEOUT_TLV,
consts.TLV_REQUEST_TLV,
consts.VERSION_TLV
} <= set(p.mle.tlv.type)).\
must_next()
# 5.4 MLE Child ID Response
pkts.filter_wpan_src64(LEADER).\
filter_wpan_dst64(DUT).\
filter_mle_cmd(consts.MLE_CHILD_ID_RESPONSE).\
must_next()
# Step 6: Leader
# - Description: Harness verifies connectivity by instructing 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 6: Leader")
_pkt = pkts.filter_ping_request().\
filter_wpan_src64(LEADER).\
filter_wpan_dst64(DUT).\
must_next()
pkts.filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier).\
filter_wpan_src64(DUT).\
filter_wpan_dst64(LEADER).\
must_next()
if __name__ == '__main__':
from pktverify import layer_fields
layer_fields._LAYER_FIELDS['mle.tlv.status'] = layer_fields._auto
verify_utils.run_main(verify)