[nexus] add MATN-TC-22 test case for low MLR timeout (#12710)

This commit adds a new Nexus test case MATN-TC-22 to verify that a
Primary Backbone Border Router (BBR) that is configured with a low
value of Multicast Listener Registration (MLR) timeout
(< MLR_TIMEOUT_MIN) is interpreted as using an MLR timeout of
MLR_TIMEOUT_MIN by Thread Devices (DUT).

The test performs the following steps:
- Configures the Primary BBR (BR_1) with an MLR timeout of
  MLR_TIMEOUT_MIN / 4.
- Verifies that the DUT registers a multicast address (MA1) at BR_1.
- Confirms that the DUT automatically re-registers for MA1 within
  MLR_TIMEOUT_MIN seconds of the initial registration.
- Ensures that no more than 2 re-registrations occur within this time
  period.

Included changes:
- New test implementation: test_1_2_MATN_TC_22.cpp.
- New verification script: verify_1_2_MATN_TC_22.py.
- Registration of the test in CMakeLists.txt and run_nexus_tests.sh.

The test implementation uses direct method calls in C++ and provides
step-by-step logging in both C++ and Python to match the test
specification.
This commit is contained in:
Jonathan Hui
2026-03-18 03:09:51 -05:00
committed by GitHub
parent 640fbc895a
commit 3f908aa5c3
4 changed files with 342 additions and 0 deletions
+1
View File
@@ -248,6 +248,7 @@ ot_nexus_test(1_2_MATN_TC_16 "cert;nexus")
ot_nexus_test(1_2_MATN_TC_19 "cert;nexus")
ot_nexus_test(1_2_MATN_TC_20 "cert;nexus")
ot_nexus_test(1_2_MATN_TC_21 "cert;nexus")
ot_nexus_test(1_2_MATN_TC_22 "cert;nexus")
# Misc tests
ot_nexus_test(border_admitter "core;nexus")
+1
View File
@@ -183,6 +183,7 @@ DEFAULT_TESTS=(
"1_2_MATN_TC_19"
"1_2_MATN_TC_20"
"1_2_MATN_TC_21"
"1_2_MATN_TC_22"
)
# Use provided arguments or the default test list
+194
View File
@@ -0,0 +1,194 @@
/*
* 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 "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 = 10 * 1000;
/**
* Time to advance for a node to join as a router, in milliseconds.
*/
static constexpr uint32_t kAttachToRouterTime = 200 * 1000;
/**
* Time to advance for the network to stabilize, in milliseconds.
*/
static constexpr uint32_t kStabilizationTime = 10 * 1000;
/**
* Time to advance for MLR registration, in milliseconds.
*/
static constexpr uint32_t kMlrRegistrationTime = 5 * 1000;
/**
* MLR Timeout Minimum value in seconds.
*/
static constexpr uint32_t kMlrTimeoutMin = 300;
/**
* Low MLR Timeout value in seconds.
*/
static constexpr uint32_t kMlrTimeoutLow = kMlrTimeoutMin / 4;
/**
* Multicast address MA1.
*/
static const char kMA1[] = "ff04::1234:777a:1";
void TestMatnTc22(void)
{
/**
* 5.10.18 MATN-TC-22: Use of low MLR timeout defaults to MLR_TIMEOUT_MIN
*
* 5.10.18.1 Topology
* - BR_1
* - TD (DUT)
*
* 5.10.18.2 Purpose & Description
* The purpose of this test case is to verify that a Primary BBR that is configured with a low value of MLR timeout
* (< MLR_TIMEOUT_MIN) is interpreted as using an MLR timeout of MLR_TIMEOUT_MIN by all the Thread Devices (DUT).
*
* Valid DUT Roles:
* - DUT as TD (FED, REED or Router)
*
* Required Devices and Topology:
* - BR_1: Test bed BR device operating as the Primary BBR.
* - TD (DUT): Device operating as a Thread FTD (FED, REED or Router).
*
* Initial Conditions:
* - P1: BR_1 MLR timeout value to configured to of BR_1 to be (MLR_TIMEOUT_MIN / 4) seconds.
*
* Spec Reference | V1.2 Section | V1.3.0 Section
* -----------------|--------------|---------------
* Multicast | 5.10.18 | 5.10.18
*/
Core nexus;
Node &br1 = nexus.CreateNode();
Node &dut1 = nexus.CreateNode(); // Router
Node &dut2 = nexus.CreateNode(); // FED
Ip6::Address ma1;
br1.SetName("BR_1");
dut1.SetName("DUT_Router");
dut2.SetName("DUT_FED");
SuccessOrQuit(ma1.FromString(kMA1));
nexus.AdvanceTime(0);
Instance::SetLogLevel(kLogLevelNote);
/**
* Step 0
* - Device: N/A
* - Description: Topology formation BR_1. Topology addition TD (DUT). Boot the DUT. Configure the DUT to
* register multicast address, MA1, at its parent.
* - Pass Criteria:
* - N/A
*/
Log("Step 0: Topology formation BR_1. Topology addition TD (DUT_Router and DUT_FED). DUTs register MA1.");
br1.AllowList(dut1);
dut1.AllowList(br1);
br1.AllowList(dut2);
dut2.AllowList(br1);
br1.Form();
nexus.AdvanceTime(kFormNetworkTime);
VerifyOrQuit(br1.Get<Mle::Mle>().IsLeader());
br1.Get<BorderRouter::InfraIf>().Init(1, true);
br1.Get<BorderRouter::RoutingManager>().Init();
SuccessOrQuit(br1.Get<BorderRouter::RoutingManager>().SetEnabled(true));
br1.Get<BackboneRouter::Local>().SetEnabled(true);
/** Configure BR_1 with low MLR timeout. */
{
BackboneRouter::Config config;
br1.Get<BackboneRouter::Local>().GetConfig(config);
config.mMlrTimeout = kMlrTimeoutLow;
SuccessOrQuit(br1.Get<BackboneRouter::Local>().SetConfig(config));
}
dut1.Join(br1, Node::kAsFtd);
dut2.Join(br1, Node::kAsFed);
nexus.AdvanceTime(kAttachToRouterTime);
VerifyOrQuit(dut1.Get<Mle::Mle>().IsRouter());
VerifyOrQuit(dut2.Get<Mle::Mle>().IsChild());
nexus.AdvanceTime(kStabilizationTime);
VerifyOrQuit(br1.Get<BackboneRouter::Local>().IsPrimary());
nexus.AddTestVar("MA1", kMA1);
{
String<16> timeoutString;
timeoutString.Append("%lu", ToUlong(kMlrTimeoutMin));
nexus.AddTestVar("MLR_TIMEOUT_MIN", timeoutString.AsCString());
}
SuccessOrQuit(dut1.Get<Ip6::Netif>().SubscribeExternalMulticast(ma1));
SuccessOrQuit(dut2.Get<Ip6::Netif>().SubscribeExternalMulticast(ma1));
nexus.AdvanceTime(kMlrRegistrationTime);
/**
* Step 1
* - Device: TD (DUT)
* - Description: Within MLR_TIMEOUT_MIN seconds of initial registration, the device automatically re-registers for
* multicast address, MA1, at BR_1.
* - Pass Criteria:
* - The DUT MUST re-register for multicast address, MA1, at BR_1.
* - The re-registration MUST occur within the time MLR_TIMEOUT_MIN seconds since initial registration.
* - No more than 2 re-registrations MUST occur within this time.
*/
Log("Step 1: Within MLR_TIMEOUT_MIN seconds, DUT re-registers for MA1.");
nexus.AdvanceTime(kMlrTimeoutMin * 1000);
nexus.SaveTestInfo("test_1_2_MATN_TC_22.json");
}
} // namespace Nexus
} // namespace ot
int main(void)
{
ot::Nexus::TestMatnTc22();
printf("All tests passed\n");
return 0;
}
+146
View File
@@ -0,0 +1,146 @@
#!/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
# Status SUCCESS
ST_MLR_SUCCESS = 0
def verify(pv):
# 5.10.18 MATN-TC-22: Use of low MLR timeout defaults to MLR_TIMEOUT_MIN
#
# 5.10.18.1 Topology
# - BR_1
# - TD (DUT)
#
# 5.10.18.2 Purpose & Description
# The purpose of this test case is to verify that a Primary BBR that is configured with a low value of MLR timeout
# (< MLR_TIMEOUT_MIN) is interpreted as using an MLR timeout of MLR_TIMEOUT_MIN by all the Thread Devices (DUT).
#
# Valid DUT Roles:
# - DUT as TD (FED, REED or Router)
#
# Required Devices and Topology:
# - BR_1: Test bed BR device operating as the Primary BBR.
# - TD (DUT): Device operating as a Thread FTD (FED, REED or Router).
#
# Initial Conditions:
# - P1: BR_1 MLR timeout value to configured to of BR_1 to be (MLR_TIMEOUT_MIN / 4) seconds.
#
# Spec Reference | V1.2 Section | V1.3.0 Section
# -----------------|--------------|---------------
# Multicast | 5.10.18 | 5.10.18
pv.summary.show()
MA1 = pv.vars['MA1']
MLR_TIMEOUT_MIN = int(pv.vars['MLR_TIMEOUT_MIN'])
BR_RLOC = pv.vars['BR_1_RLOC']
duts = {
'Router': pv.vars['DUT_Router_RLOC'],
'FED': pv.vars['DUT_FED_RLOC'],
}
# Step 0
# - Device: N/A
# - Description: Topology formation BR_1. Topology addition TD (DUT). Boot the DUT. Configure the DUT to
# register multicast address, MA1, at its parent.
# - Pass Criteria:
# - N/A
print("Step 0: Topology formation BR_1. Topology addition TD (DUT). DUT registers MA1.")
initial_regs = {}
for role, rloc in duts.items():
print(f"Checking initial registration for DUT as {role} ({rloc})")
# Use range((0,0), cascade=False) to start search from the beginning for each DUT without affecting pv.pkts
pkts = pv.pkts.range((0, 0), cascade=False)
initial_reg = pkts.filter_coap_request('/n/mr').\
filter(lambda p: p.ipv6.src == rloc).\
filter(lambda p: p.ipv6.dst == BR_RLOC).\
filter(lambda p: MA1 in p.coap.tlv.ipv6_address).\
must_next()
initial_regs[role] = initial_reg
# Search for the ACK after the request and match the CoAP message ID.
pv.pkts.range((initial_reg.number, 0), cascade=False).filter_coap_ack('/n/mr').\
filter(lambda p: p.ipv6.src == BR_RLOC).\
filter(lambda p: p.ipv6.dst == rloc).\
filter(lambda p: p.coap.mid == initial_reg.coap.mid).\
filter(lambda p: p.coap.tlv.status == ST_MLR_SUCCESS).\
must_next()
# Step 1
# - Device: TD (DUT)
# - Description: Within MLR_TIMEOUT_MIN seconds of initial registration, the device automatically re-registers for
# multicast address, MA1, at BR_1.
# - Pass Criteria:
# - The DUT MUST re-register for multicast address, MA1, at BR_1.
# - The re-registration MUST occur within the time MLR_TIMEOUT_MIN seconds since initial registration.
# - No more than 2 re-registrations MUST occur within this time.
print("Step 1: Within MLR_TIMEOUT_MIN seconds, the device automatically re-registers for MA1 at BR_1.")
for role, rloc in duts.items():
print(f"Checking re-registration for DUT as {role} ({rloc})")
initial_reg = initial_regs[role]
# Use range from initial_reg's position
pkts = pv.pkts.range((initial_reg.number, 0), cascade=False)
# Helper to filter for re-registration packets
def find_re_reg_pkts(start_time):
return pkts.filter_coap_request('/n/mr').\
filter(lambda p: p.ipv6.src == rloc).\
filter(lambda p: p.ipv6.dst == BR_RLOC).\
filter(lambda p: MA1 in p.coap.tlv.ipv6_address).\
filter(lambda p: p.sniff_timestamp > start_time).\
filter(lambda p: p.sniff_timestamp <= initial_reg.sniff_timestamp + MLR_TIMEOUT_MIN)
# The DUT MUST re-register.
first_re_reg = find_re_reg_pkts(initial_reg.sniff_timestamp).must_next()
# A second re-registration is allowed.
second_re_reg = find_re_reg_pkts(first_re_reg.sniff_timestamp).next()
# But no more than 2 re-registrations MUST occur.
if second_re_reg:
find_re_reg_pkts(second_re_reg.sniff_timestamp).must_not_next()
if __name__ == '__main__':
verify_utils.run_main(verify)