From b3d169dc5476f9d1b5c57dfef8665c8c2eb25f4f Mon Sep 17 00:00:00 2001 From: Jonathan Hui Date: Mon, 23 Feb 2026 14:30:05 -0600 Subject: [PATCH] [nexus] add test 9.2.5 Updating Active Operational Dataset (#12526) This commit adds a new Nexus test case 9.2.5 which verifies the DUT's behavior when receiving MGMT_ACTIVE_SET.req from an active Thread node. The test ensures that the Leader (DUT) correctly processes updates to Active Operational Dataset parameters and disseminates them via MLE. The test implementation includes: - tests/nexus/test_9_2_5.cpp: - C++ execution logic using direct core method calls. - tests/nexus/verify_9_2_5.py: - Python script to verify pcap output against the test specification. - Implements strict validation of CoAP payload TLVs and response states. - Infrastructure improvements: - tests/nexus/verify_utils.py: Added parsing for MeshCoP TLVs in CoAP. - tests/scripts/thread-cert/pktverify/layer_fields.py: Fixed ISO timestamp parsing for compatibility with newer tshark versions. --- tests/nexus/CMakeLists.txt | 1 + tests/nexus/run_nexus_tests.sh | 1 + tests/nexus/test_9_2_5.cpp | 460 ++++++++++++++++++ tests/nexus/verify_9_2_5.py | 388 +++++++++++++++ tests/nexus/verify_utils.py | 24 + .../thread-cert/pktverify/layer_fields.py | 9 + 6 files changed, 883 insertions(+) create mode 100644 tests/nexus/test_9_2_5.cpp create mode 100644 tests/nexus/verify_9_2_5.py diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index ad2c56995..4a1fe0f4d 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -198,6 +198,7 @@ ot_nexus_test(9_2_1 "cert;nexus") ot_nexus_test(9_2_2 "cert;nexus") ot_nexus_test(9_2_3 "cert;nexus") ot_nexus_test(9_2_4 "cert;nexus") +ot_nexus_test(9_2_5 "cert;nexus") # Misc tests ot_nexus_test(border_admitter "core;nexus") diff --git a/tests/nexus/run_nexus_tests.sh b/tests/nexus/run_nexus_tests.sh index 7bd366455..6b8043a84 100755 --- a/tests/nexus/run_nexus_tests.sh +++ b/tests/nexus/run_nexus_tests.sh @@ -134,6 +134,7 @@ DEFAULT_TESTS=( "9_2_2" "9_2_3" "9_2_4" + "9_2_5" ) # Use provided arguments or the default test list diff --git a/tests/nexus/test_9_2_5.cpp b/tests/nexus/test_9_2_5.cpp new file mode 100644 index 000000000..07c2bd3bb --- /dev/null +++ b/tests/nexus/test_9_2_5.cpp @@ -0,0 +1,460 @@ +/* + * 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 "meshcop/dataset_manager.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 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 a response, in milliseconds. + */ +static constexpr uint32_t kResponseTime = 2000; + +/** + * Time to wait for ICMPv6 Echo response, in milliseconds. + */ +static constexpr uint32_t kEchoTimeout = 5000; + +static constexpr uint64_t kActiveTimestampStep2 = 20; +static constexpr uint32_t kChannelMaskStep2 = 0x07fff800; +static constexpr uint8_t kExtendedPanIdStep2[] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; +static constexpr char kNetworkNameStep2[] = "nexus-test"; +static constexpr uint8_t kPskcStep2[] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; +static constexpr uint16_t kRotationTimeStep2 = 3600; +static constexpr uint8_t kSecurityFlagsStep2[] = {0xfb}; + +static constexpr uint64_t kActiveTimestampStep7 = 10; + +static constexpr uint64_t kActiveTimestampStep9 = 30; +static constexpr uint32_t kChannelMaskStep9 = 0x001fffe0; +static constexpr uint8_t kExtendedPanIdStep9[] = {0x01, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; +static constexpr char kNetworkNameStep9[] = "nexus-925"; +static constexpr uint8_t kPskcStep9[] = {0x11, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; +static constexpr uint16_t kRotationTimeStep9 = 7200; +static constexpr uint8_t kSecurityFlagsStep9[] = {0x7b}; +static constexpr uint8_t kFutureTlv[] = {130, 2, 0xaa, 0x55}; + +static constexpr uint64_t kActiveTimestampStep14 = 40; +static constexpr uint16_t kUnsupportedChannel = 63; + +void Test9_2_5(void) +{ + /** + * 9.2.5 Updating the Active Operational Dataset via Thread Node + * + * 9.2.5.1 Topology + * - DUT as Leader, Router_1 + * + * 9.2.5.2 Purpose & Description + * The purpose of this test case is to verify the DUT’s behavior when receiving MGMT_ACTIVE_SET.req from an active + * Thread node. + * + * Spec Reference | V1.1 Section | V1.3.0 Section + * ----------------------------------------|--------------|--------------- + * Updating the Active Operational Dataset | 8.7.4 | 8.7.4 + */ + + Core nexus; + + Node &leader = nexus.CreateNode(); + Node &router1 = nexus.CreateNode(); + + leader.SetName("LEADER"); + router1.SetName("ROUTER_1"); + + 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(router1); + router1.AllowList(leader); + + leader.Form(); + nexus.AdvanceTime(kFormNetworkTime); + VerifyOrQuit(leader.Get().IsLeader()); + + router1.Join(leader); + nexus.AdvanceTime(kAttachToRouterTime); + VerifyOrQuit(router1.Get().IsRouter()); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 2: Router_1"); + + /** + * Step 2: Router_1 + * - Description: Harness instructs Router_1 to send a MGMT_ACTIVE_SET.req to the Leader (DUT)’s Routing or Anycast + * Locator: + * - new, valid Timestamp TLV + * - all valid Active Operational Dataset parameters, with new values in the TLVs that don’t affect connectivity + * - Pass Criteria: + * - CoAP Request URI: coap://[]:MM/c/as + * - CoAP Payload: + * - Active Timestamp TLV (new valid value) + * - Channel Mask TLV (new value) + * - Extended PAN ID TLV (new value) + * - Mesh-Local Prefix (old value) + * - Network Name TLV (new value) + * - PSKc TLV (new value) + * - Security Policy TLV (new value) + * - Network Master Key (old value) + * - PAN ID (old value) + * - Channel (old value) + * - The DUT’s Anycast Locator uses the Mesh local prefix with an IID of 0000:00FF:FE00:FC00. + */ + + MeshCoP::Dataset::Info datasetInfo; + MeshCoP::Timestamp timestamp; + + SuccessOrQuit(router1.Get().Read(datasetInfo)); + + timestamp.SetSeconds(kActiveTimestampStep2); + timestamp.SetTicks(0); + datasetInfo.Set(timestamp); + datasetInfo.Set(kChannelMaskStep2); + datasetInfo.Set( + AsCoreType(reinterpret_cast(kExtendedPanIdStep2))); + SuccessOrQuit(datasetInfo.Update().Set(kNetworkNameStep2)); + datasetInfo.Set(AsCoreType(reinterpret_cast(kPskcStep2))); + datasetInfo.Update().mRotationTime = kRotationTimeStep2; + datasetInfo.Update().SetFlags(kSecurityFlagsStep2, sizeof(kSecurityFlagsStep2)); + + SuccessOrQuit( + router1.Get().SendSetRequest(datasetInfo, nullptr, 0, nullptr, nullptr)); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 3: Leader (DUT)"); + + /** + * Step 3: Leader (DUT) + * - Description: Automatically sends MGMT_ACTIVE_SET.rsp to Router_1. + * - Pass Criteria: The DUT MUST send MGMT_ACTIVE_SET.rsp to Router_1 with the following format: + * - CoAP Response Code: 2.04 Changed + * - CoAP Payload: State TLV (value = Accept (01)) + */ + + nexus.AdvanceTime(kResponseTime); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 4: Leader (DUT)"); + + /** + * Step 4: Leader (DUT) + * - Description: Automatically sends a Multicast MLE Data Response. + * - Pass Criteria: The DUT MUST send a multicast MLE Data Response, including the following TLVs: + * - Source Address TLV + * - Leader Data TLV + * - Data version field [incremented] + * - Stable Version field [incremented] + * - Network Data TLV + * - Active Timestamp TLV [new value set in Step 2] + */ + + Log("---------------------------------------------------------------------------------------"); + Log("Step 5: Router_1"); + + /** + * Step 5: Router_1 + * - Description: Automatically sends a unicast MLE Data Request to Leader, including the following TLVs: + * - TLV Request TLV: + * - Network Data TLV + * - Active Timestamp TLV + * - Pass Criteria: N/A. + */ + + Log("---------------------------------------------------------------------------------------"); + Log("Step 6: Leader (DUT)"); + + /** + * Step 6: Leader (DUT) + * - Description: Automatically sends a unicast MLE Data Response to Router_1. + * - Pass Criteria: The DUT MUST send a unicast MLE Data Response to Router_1, including the following TLVs: + * - Source Address TLV + * - Leader Data TLV + * - Network Data TLV + * - Active Operational Dataset TLV + * - Channel TLV + * - Channel Mask TLV [new value set in Step 2] + * - Extended PAN ID TLV [new value set in Step 2] + * - Network Mesh-Local Prefix TLV + * - Network Master Key TLV + * - Network Name TLV [new value set in Step 2] + * - PAN ID TLV + * - PSKc TLV [new value set in Step 2] + * - Security Policy TLV [new value set in Step 2] + * - Active Timestamp TLV [new value set in Step 2] + */ + + nexus.AdvanceTime(kResponseTime); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 7: Router_1"); + + /** + * Step 7: Router_1 + * - Description: Harness instructs Router_1 to send a MGMT_ACTIVE_SET.req to the Leader (DUT)’s Routing or Anycast + * Locator: + * - old, invalid Active Timestamp TLV + * - all valid Active Operational Dataset parameters, with new values in the TLVs that don’t affect connectivity + * - Pass Criteria: + * - CoAP Request URI: coap://[]:MM/c/as + * - CoAP Payload: + * - Active Timestamp TLV (old, invalid value) + * - Channel Mask TLV (new value) + * - Extended PAN ID TLV (new value) + * - Mesh-Local Prefix (old value) + * - Network Name TLV (new value) + * - PSKc TLV (new value) + * - Security Policy TLV (new value) + * - Network Master Key (old value) + * - PAN ID (old value) + * - Channel (old value) + * - The DUT’s Anycast Locator uses the Mesh local prefix with an IID of 0000:00FF:FE00:FC00. + */ + + timestamp.SetSeconds(kActiveTimestampStep7); + datasetInfo.Set(timestamp); + + SuccessOrQuit( + router1.Get().SendSetRequest(datasetInfo, nullptr, 0, nullptr, nullptr)); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 8: Leader (DUT)"); + + /** + * Step 8: Leader (DUT) + * - Description: Automatically sends a MGMT_ACTIVE_SET.rsp to Router_1. + * - Pass Criteria: The DUT MUST send MGMT_ACTIVE_SET.rsp to Router_1, with the following format: + * - CoAP Response Code: 2.04 Changed + * - CoAP Payload: State TLV (value = Reject (ff)) + */ + + nexus.AdvanceTime(kResponseTime); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 9: Router_1"); + + /** + * Step 9: Router_1 + * - Description: Harness instructs Router_1 to send a MGMT_ACTIVE_SET.req to the Leader (DUT)’s Routing or Anycast + * Locator: + * - new, valid Active Timestamp TLV + * - all of valid Commissioner Dataset parameters plus one bogus TLV, and new values in the TLVs that don’t affect + * connectivity + * - Pass Criteria: + * - CoAP Request URI: coap://[]:MM/c/as + * - CoAP Payload: + * - Active Timestamp TLV (new, valid value) + * - Channel Mask TLV (new value, different from Step 2) + * - Extended PAN ID TLV (new value, different from Step 2) + * - Mesh-Local Prefix (old value) + * - Network Name TLV (new value, different from Step 2) + * - PSKc TLV (new value, different from Step 2) + * - Security Policy TLV (new value, different from Step 2) + * - Network Master Key (old value) + * - PAN ID (old value) + * - Channel (old value) + * - Future TLV: + * - Type 130 + * - Length 2 + * - Value (aa 55) + * - The DUT’s Anycast Locator uses the Mesh local prefix with an IID of 0000:00FF:FE00:FC00. + */ + + timestamp.SetSeconds(kActiveTimestampStep9); + datasetInfo.Set(timestamp); + datasetInfo.Set(kChannelMaskStep9); + datasetInfo.Set( + AsCoreType(reinterpret_cast(kExtendedPanIdStep9))); + SuccessOrQuit(datasetInfo.Update().Set(kNetworkNameStep9)); + datasetInfo.Set(AsCoreType(reinterpret_cast(kPskcStep9))); + datasetInfo.Update().mRotationTime = kRotationTimeStep9; + datasetInfo.Update().SetFlags(kSecurityFlagsStep9, sizeof(kSecurityFlagsStep9)); + + SuccessOrQuit(router1.Get().SendSetRequest(datasetInfo, kFutureTlv, + sizeof(kFutureTlv), nullptr, nullptr)); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 10: Leader (DUT)"); + + /** + * Step 10: Leader (DUT) + * - Description: Automatically sends a MGMT_ACTIVE_SET.rsp to Router_1. + * - Pass Criteria: The DUT MUST send MGMT_ACTIVE_SET.rsp to Router_1 with the following format: + * - CoAP Response Code: 2.04 Changed + * - CoAP Payload: State TLV (value = Accept (01)) + */ + + nexus.AdvanceTime(kResponseTime); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 11: Leader (DUT)"); + + /** + * Step 11: Leader (DUT) + * - Description: Automatically sends a multicast MLE Data Response. + * - Pass Criteria: The DUT MUST send a multicast MLE Data Response, including the following TLVs: + * - Source Address TLV + * - Leader Data TLV + * - Data version field [incremented] + * - Stable Version field [incremented] + * - Network Data TLV + * - Active Timestamp TLV [new value set in Step 9] + */ + + Log("---------------------------------------------------------------------------------------"); + Log("Step 12: Router_1"); + + /** + * Step 12: Router_1 + * - Description: Automatically sends a unicast MLE Data Request to the Leader (DUT), including the following TLVs: + * - TLV Request TLV: + * - Network Data TLV + * - Active Timestamp TLV + * - Pass Criteria: N/A. + */ + + Log("---------------------------------------------------------------------------------------"); + Log("Step 13: Leader (DUT)"); + + /** + * Step 13: Leader (DUT) + * - Description: Automatically sends a unicast MLE Data Response to Router_1. + * - Pass Criteria: The following TLVs MUST be included in the Unicast MLE Data Response: + * - Source Address TLV + * - Leader Data TLV + * - Network Data TLV + * - Stable flag set to 0 + * - Active Operational Dataset TLV + * - Channel TLV + * - Channel Mask TLV [new value set in Step 9] + * - Extended PAN ID TLV [new value set in Step 9] + * - Network Mesh-Local Prefix TLV + * - Network Master Key TLV + * - Network Name TLV [new value set in Step 9] + * - PAN ID TLV + * - PSKc TLV [new value set in Step 9] + * - Security Policy TLV [new value set in Step 9] + * - Active Timestamp TLV [new value set in Step 9] + */ + + nexus.AdvanceTime(kResponseTime); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 14: Router_1"); + + /** + * Step 14: Router_1 + * - Description: Harness instructs Router_1 to send a MGMT_ACTIVE_SET.req to the Leader (DUT)’s Routing or Anycast + * Locator: + * - new, valid Active Timestamp TLV + * - attempt to set Channel TLV to an unsupported channel + all of other TLVs + * - Pass Criteria: + * - CoAP Request URI: coap://[]:MM/c/as + * - CoAP Payload: + * - Active Timestamp TLV (new, valid value) + * - Channel TLV (unsupported value = 63) + * - Channel Mask TLV (old value set in Step 9) + * - Extended PAN ID TLV (old value set in Step 9) + * - Mesh-Local Prefix (old value) + * - Network Name TLV (old value set in Step 9) + * - PSKc TLV (old value set in Step 9) + * - Security Policy TLV (old value set in Step 9) + * - Network Master Key (old value) + * - PAN ID (old value) + * - The DUT Anycast Locator uses the Mesh local prefix with an IID of 0000:00FF:FE00:FC00. + */ + + timestamp.SetSeconds(kActiveTimestampStep14); + datasetInfo.Set(timestamp); + datasetInfo.Set(kUnsupportedChannel); + + SuccessOrQuit( + router1.Get().SendSetRequest(datasetInfo, nullptr, 0, nullptr, nullptr)); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 15: Leader (DUT)"); + + /** + * Step 15: Leader (DUT) + * - Description: Automatically sends MGMT_ACTIVE_SET.rsp to Router_1. + * - Pass Criteria: The DUT MUST send MGMT_ACTIVE_SET.rsp to Router_1 with the following format: + * - CoAP Response Code: 2.04 Changed + * - CoAP Payload: State TLV (value = Reject (ff)) + */ + + nexus.AdvanceTime(kResponseTime); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 16: All"); + + /** + * Step 16: All + * - Description: Verify connectivity by sending an ICMPv6 Echo Request to the DUT mesh local address. + * - Pass Criteria: The DUT must respond with an ICMPv6 Echo Reply. + */ + + nexus.SendAndVerifyEchoRequest(router1, leader.Get().GetMeshLocalEid(), 0, 64, kEchoTimeout); + + nexus.SaveTestInfo("test_9_2_5.json"); +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::Test9_2_5(); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/nexus/verify_9_2_5.py b/tests/nexus/verify_9_2_5.py new file mode 100644 index 000000000..c41d65dd5 --- /dev/null +++ b/tests/nexus/verify_9_2_5.py @@ -0,0 +1,388 @@ +#!/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): + # 9.2.5 Updating the Active Operational Dataset via Thread node + # + # 9.2.5.1 Topology + # DUT as Leader, Router_1 + # + # 9.2.5.2 Purpose & Description + # The purpose of this test case is to verify the DUT’s behavior when receiving MGMT_ACTIVE_SET.req from an active + # Thread node. + # + # Spec Reference | V1.1 Section | V1.3.0 Section + # ----------------------------------------|--------------|--------------- + # Updating the Active Operational Dataset | 8.7.4 | 8.7.4 + + pkts = pv.pkts + pv.summary.show() + + LEADER = pv.vars['LEADER'] + ROUTER_1 = pv.vars['ROUTER_1'] + + # Step 1: All + # - Description: Ensure topology is formed correctly. + # - Pass Criteria: N/A. + print("Step 1: Ensure topology is formed correctly.") + + # Step 2: Router_1 + # - Description: Harness instructs Router_1 to send a MGMT_ACTIVE_SET.req to the Leader (DUT)’s Routing or Anycast + # Locator: + # - new, valid Timestamp TLV + # - all valid Active Operational Dataset parameters, with new values in the TLVs that don’t affect connectivity + # - Pass Criteria: + # - CoAP Request URI: coap://[]:MM/c/as + # - CoAP Payload: + # - Active Timestamp TLV (new valid value) + # - Channel Mask TLV (new value) + # - Extended PAN ID TLV (new value) + # - Mesh-Local Prefix (old value) + # - Network Name TLV (new value) + # - PSKc TLV (new value) + # - Security Policy TLV (new value) + # - Network Master Key (old value) + # - PAN ID (old value) + # - Channel (old value) + # - The DUT’s Anycast Locator uses the Mesh local prefix with an IID of 0000:00FF:FE00:FC00. + print("Step 2: Router_1 sends MGMT_ACTIVE_SET.req with new valid Active Timestamp.") + pkts.filter_wpan_src64(ROUTER_1).\ + filter_coap_request(consts.MGMT_ACTIVE_SET_URI).\ + filter(lambda p: { + consts.NM_ACTIVE_TIMESTAMP_TLV, + consts.NM_CHANNEL_MASK_TLV, + consts.NM_EXTENDED_PAN_ID_TLV, + consts.NM_NETWORK_MESH_LOCAL_PREFIX_TLV, + consts.NM_NETWORK_NAME_TLV, + consts.NM_PSKC_TLV, + consts.NM_SECURITY_POLICY_TLV, + consts.NM_NETWORK_KEY_TLV, + consts.NM_PAN_ID_TLV, + consts.NM_CHANNEL_TLV + } <= set(p.coap.tlv.type)).\ + filter(lambda p: p.coap.tlv.active_timestamp == 20 and\ + p.coap.tlv.network_name == 'nexus-test').\ + filter(lambda p: p.ipv6.dst.endswith(bytes.fromhex("000000fffe00fc00"))).\ + must_next() + + # Step 3: Leader (DUT) + # - Description: Automatically sends MGMT_ACTIVE_SET.rsp to Router_1. + # - Pass Criteria: The DUT MUST send MGMT_ACTIVE_SET.rsp to Router_1 with the following format: + # - CoAP Response Code: 2.04 Changed + # - CoAP Payload: State TLV (value = Accept (01)) + print("Step 3: Leader sends MGMT_ACTIVE_SET.rsp with State TLV = Accept.") + pkts.filter_wpan_src64(LEADER).\ + filter_coap_ack(consts.MGMT_ACTIVE_SET_URI).\ + filter(lambda p: p.coap.tlv.state == 1).\ + must_next() + + # Step 4: Leader (DUT) + # - Description: Automatically sends a Multicast MLE Data Response. + # - Pass Criteria: The DUT MUST send a multicast MLE Data Response, including the following TLVs: + # - Source Address TLV + # - Leader Data TLV + # - Data version field [incremented] + # - Stable Version field [incremented] + # - Network Data TLV + # - Active Timestamp TLV [new value set in Step 2] + print("Step 4: Leader sends a Multicast MLE Data Response.") + pkts.filter_wpan_src64(LEADER).\ + filter_LLANMA().\ + filter_mle_cmd(consts.MLE_DATA_RESPONSE).\ + filter(lambda p: { + consts.SOURCE_ADDRESS_TLV, + consts.LEADER_DATA_TLV, + consts.NETWORK_DATA_TLV, + consts.ACTIVE_TIMESTAMP_TLV + } <= set(p.mle.tlv.type)).\ + filter(lambda p: p.mle.tlv.active_tstamp == 20).\ + must_next() + + # Step 5: Router_1 + # - Description: Automatically sends a unicast MLE Data Request to Leader, including the following TLVs: + # - TLV Request TLV: + # - Network Data TLV + # - Active Timestamp TLV + # - Pass Criteria: N/A. + print("Step 5: Router_1 sends a unicast MLE Data Request to Leader.") + pkts.filter_wpan_src64(ROUTER_1).\ + filter_wpan_dst64(LEADER).\ + filter_mle_cmd(consts.MLE_DATA_REQUEST).\ + filter(lambda p: { + consts.TLV_REQUEST_TLV, + consts.ACTIVE_TIMESTAMP_TLV + } <= set(p.mle.tlv.type)).\ + must_next() + + # Step 6: Leader (DUT) + # - Description: Automatically sends a unicast MLE Data Response to Router_1. + # - Pass Criteria: The DUT MUST send a unicast MLE Data Response to Router_1, including the following TLVs: + # - Source Address TLV + # - Leader Data TLV + # - Network Data TLV + # - Active Operational Dataset TLV + # - Channel TLV + # - Channel Mask TLV [new value set in Step 2] + # - Extended PAN ID TLV [new value set in Step 2] + # - Network Mesh-Local Prefix TLV + # - Network Master Key TLV + # - Network Name TLV [new value set in Step 2] + # - PAN ID TLV + # - PSKc TLV [new value set in Step 2] + # - Security Policy TLV [new value set in Step 2] + # - Active Timestamp TLV [new value set in Step 2] + print("Step 6: Leader sends a unicast MLE Data Response to Router_1.") + pkts.filter_wpan_src64(LEADER).\ + filter_wpan_dst64(ROUTER_1).\ + filter_mle_cmd(consts.MLE_DATA_RESPONSE).\ + filter(lambda p: { + consts.SOURCE_ADDRESS_TLV, + consts.LEADER_DATA_TLV, + consts.NETWORK_DATA_TLV, + consts.ACTIVE_OPERATION_DATASET_TLV, + consts.ACTIVE_TIMESTAMP_TLV + } <= set(p.mle.tlv.type)).\ + filter(lambda p: p.mle.tlv.active_tstamp == 20).\ + must_next() + + # Step 7: Router_1 + # - Description: Harness instructs Router_1 to send a MGMT_ACTIVE_SET.req to the Leader (DUT)’s Routing or Anycast + # Locator: + # - old, invalid Active Timestamp TLV + # - all valid Active Operational Dataset parameters, with new values in the TLVs that don’t affect connectivity + # - Pass Criteria: + # - CoAP Request URI: coap://[]:MM/c/as + # - CoAP Payload: + # - Active Timestamp TLV (old, invalid value) + # - Channel Mask TLV (new value) + # - Extended PAN ID TLV (new value) + # - Mesh-Local Prefix (old value) + # - Network Name TLV (new value) + # - PSKc TLV (new value) + # - Security Policy TLV (new value) + # - Network Master Key (old value) + # - PAN ID (old value) + # - Channel (old value) + # - The DUT’s Anycast Locator uses the Mesh local prefix with an IID of 0000:00FF:FE00:FC00. + print("Step 7: Router_1 sends MGMT_ACTIVE_SET.req with old invalid Active Timestamp.") + pkts.filter_wpan_src64(ROUTER_1).\ + filter_coap_request(consts.MGMT_ACTIVE_SET_URI).\ + filter(lambda p: p.coap.tlv.active_timestamp == 10).\ + filter(lambda p: p.ipv6.dst.endswith(bytes.fromhex("000000fffe00fc00"))).\ + must_next() + + # Step 8: Leader (DUT) + # - Description: Automatically sends a MGMT_ACTIVE_SET.rsp to Router_1. + # - Pass Criteria: The DUT MUST send MGMT_ACTIVE_SET.rsp to Router_1, with the following format: + # - CoAP Response Code: 2.04 Changed + # - CoAP Payload: State TLV (value = Reject (ff)) + print("Step 8: Leader sends MGMT_ACTIVE_SET.rsp with State TLV = Reject.") + pkts.filter_wpan_src64(LEADER).\ + filter_coap_ack(consts.MGMT_ACTIVE_SET_URI).\ + filter(lambda p: p.coap.tlv.state == 255).\ + must_next() + + # Step 9: Router_1 + # - Description: Harness instructs Router_1 to send a MGMT_ACTIVE_SET.req to the Leader (DUT)’s Routing or Anycast + # Locator: + # - new, valid Active Timestamp TLV + # - all of valid Commissioner Dataset parameters plus one bogus TLV, and new values in the TLVs that don’t affect + # connectivity + # - Pass Criteria: + # - CoAP Request URI: coap://[]:MM/c/as + # - CoAP Payload: + # - Active Timestamp TLV (new, valid value) + # - Channel Mask TLV (new value, different from Step 2) + # - Extended PAN ID TLV (new value, different from Step 2) + # - Mesh-Local Prefix (old value) + # - Network Name TLV (new value, different from Step 2) + # - PSKc TLV (new value, different from Step 2) + # - Security Policy TLV (new value, different from Step 2) + # - Network Master Key (old value) + # - PAN ID (old value) + # - Channel (old value) + # - Future TLV: + # - Type 130 + # - Length 2 + # - Value (aa 55) + # - The DUT’s Anycast Locator uses the Mesh local prefix with an IID of 0000:00FF:FE00:FC00. + print("Step 9: Router_1 sends MGMT_ACTIVE_SET.req with new valid Active Timestamp and Future TLV.") + pkts.filter_wpan_src64(ROUTER_1).\ + filter_coap_request(consts.MGMT_ACTIVE_SET_URI).\ + filter(lambda p: { + consts.NM_ACTIVE_TIMESTAMP_TLV, + consts.NM_CHANNEL_MASK_TLV, + consts.NM_EXTENDED_PAN_ID_TLV, + consts.NM_NETWORK_NAME_TLV, + consts.NM_PSKC_TLV, + consts.NM_SECURITY_POLICY_TLV + } <= set(p.coap.tlv.type)).\ + filter(lambda p: p.coap.tlv.active_timestamp == 30 and\ + p.coap.tlv.network_name == 'nexus-925').\ + filter(lambda p: 130 in p.coap.tlv.type).\ + filter(lambda p: p.ipv6.dst.endswith(bytes.fromhex("000000fffe00fc00"))).\ + must_next() + + # Step 10: Leader (DUT) + # - Description: Automatically sends a MGMT_ACTIVE_SET.rsp to Router_1. + # - Pass Criteria: The DUT MUST send MGMT_ACTIVE_SET.rsp to Router_1 with the following format: + # - CoAP Response Code: 2.04 Changed + # - CoAP Payload: State TLV (value = Accept (01)) + print("Step 10: Leader sends MGMT_ACTIVE_SET.rsp with State TLV = Accept.") + pkts.filter_wpan_src64(LEADER).\ + filter_coap_ack(consts.MGMT_ACTIVE_SET_URI).\ + filter(lambda p: p.coap.tlv.state == 1).\ + must_next() + + # Step 11: Leader (DUT) + # - Description: Automatically sends a multicast MLE Data Response. + # - Pass Criteria: The DUT MUST send a multicast MLE Data Response, including the following TLVs: + # - Source Address TLV + # - Leader Data TLV + # - Data version field [incremented] + # - Stable Version field [incremented] + # - Network Data TLV + # - Active Timestamp TLV [new value set in Step 9] + print("Step 11: Leader sends a Multicast MLE Data Response.") + pkts.filter_wpan_src64(LEADER).\ + filter_LLANMA().\ + filter_mle_cmd(consts.MLE_DATA_RESPONSE).\ + filter(lambda p: p.mle.tlv.active_tstamp == 30).\ + must_next() + + # Step 12: Router_1 + # - Description: Automatically sends a unicast MLE Data Request to the Leader (DUT), including the following TLVs: + # - TLV Request TLV: + # - Network Data TLV + # - Active Timestamp TLV + # - Pass Criteria: N/A. + print("Step 12: Router_1 sends a unicast MLE Data Request to Leader.") + pkts.filter_wpan_src64(ROUTER_1).\ + filter_wpan_dst64(LEADER).\ + filter_mle_cmd(consts.MLE_DATA_REQUEST).\ + filter(lambda p: { + consts.TLV_REQUEST_TLV, + consts.ACTIVE_TIMESTAMP_TLV + } <= set(p.mle.tlv.type)).\ + must_next() + + # Step 13: Leader (DUT) + # - Description: Automatically sends a unicast MLE Data Response to Router_1. + # - Pass Criteria: The following TLVs MUST be included in the Unicast MLE Data Response: + # - Source Address TLV + # - Leader Data TLV + # - Network Data TLV + # - Stable flag set to 0 + # - Active Operational Dataset TLV + # - Channel TLV + # - Channel Mask TLV [new value set in Step 9] + # - Extended PAN ID TLV [new value set in Step 9] + # - Network Mesh-Local Prefix TLV + # - Network Master Key TLV + # - Network Name TLV [new value set in Step 9] + # - PAN ID TLV + # - PSKc TLV [new value set in Step 9] + # - Security Policy TLV [new value set in Step 9] + # - Active Timestamp TLV [new value set in Step 9] + print("Step 13: Leader sends a unicast MLE Data Response to Router_1.") + pkts.filter_wpan_src64(LEADER).\ + filter_wpan_dst64(ROUTER_1).\ + filter_mle_cmd(consts.MLE_DATA_RESPONSE).\ + filter(lambda p: { + consts.SOURCE_ADDRESS_TLV, + consts.LEADER_DATA_TLV, + consts.NETWORK_DATA_TLV, + consts.ACTIVE_OPERATION_DATASET_TLV, + consts.ACTIVE_TIMESTAMP_TLV + } <= set(p.mle.tlv.type)).\ + filter(lambda p: p.mle.tlv.active_tstamp == 30).\ + must_next() + + # Step 14: Router_1 + # - Description: Harness instructs Router_1 to send a MGMT_ACTIVE_SET.req to the Leader (DUT)’s Routing or Anycast + # Locator: + # - new, valid Active Timestamp TLV + # - attempt to set Channel TLV to an unsupported channel + all of other TLVs + # - Pass Criteria: + # - CoAP Request URI: coap://[]:MM/c/as + # - CoAP Payload: + # - Active Timestamp TLV (new, valid value) + # - Channel TLV (unsupported value = 63) + # - Channel Mask TLV (old value set in Step 9) + # - Extended PAN ID TLV (old value set in Step 9) + # - Mesh-Local Prefix (old value) + # - Network Name TLV (old value set in Step 9) + # - PSKc TLV (old value set in Step 9) + # - Security Policy TLV (old value set in Step 9) + # - Network Master Key (old value) + # - PAN ID (old value) + # - The DUT Anycast Locator uses the Mesh local prefix with an IID of 0000:00FF:FE00:FC00. + print("Step 14: Router_1 sends MGMT_ACTIVE_SET.req with unsupported Channel.") + pkts.filter_wpan_src64(ROUTER_1).\ + filter_coap_request(consts.MGMT_ACTIVE_SET_URI).\ + filter(lambda p: p.coap.tlv.active_timestamp == 40 and\ + p.coap.tlv.channel == 63).\ + filter(lambda p: p.ipv6.dst.endswith(bytes.fromhex("000000fffe00fc00"))).\ + must_next() + + # Step 15: Leader (DUT) + # - Description: Automatically sends MGMT_ACTIVE_SET.rsp to Router_1. + # - Pass Criteria: The DUT MUST send MGMT_ACTIVE_SET.rsp to Router_1 with the following format: + # - CoAP Response Code: 2.04 Changed + # - CoAP Payload: State TLV (value = Reject (ff)) + print("Step 15: Leader sends MGMT_ACTIVE_SET.rsp with State TLV = Reject.") + pkts.filter_wpan_src64(LEADER).\ + filter_coap_ack(consts.MGMT_ACTIVE_SET_URI).\ + filter(lambda p: p.coap.tlv.state == 255).\ + must_next() + + # Step 16: All + # - Description: Verify connectivity by sending an ICMPv6 Echo Request to the DUT mesh local address. + # - Pass Criteria: The DUT must respond with an ICMPv6 Echo Reply. + print("Step 16: Verify connectivity by sending an ICMPv6 Echo Request.") + # Match any ping request and reply. + _pkt = pkts.filter_ping_request().\ + must_next() + pkts.filter_ping_reply(identifier=_pkt.icmpv6.echo.identifier).\ + must_next() + + +if __name__ == '__main__': + verify_utils.run_main(verify) diff --git a/tests/nexus/verify_utils.py b/tests/nexus/verify_utils.py index 8c99a6d62..d1a05a9d3 100644 --- a/tests/nexus/verify_utils.py +++ b/tests/nexus/verify_utils.py @@ -71,6 +71,22 @@ def thread_coap_tlv_parse(t, v, layer=None): kvs.append(('border_agent_rloc16', hex(struct.unpack('>H', v)[0]))) elif t == consts.NM_CHANNEL_TLV and len(v) == 3 and not is_diag: # DG_MAC_EXTENDED_ADDRESS_TLV is 8 kvs.append(('channel', str(struct.unpack('>H', v[1:3])[0]))) + elif t == consts.NM_ACTIVE_TIMESTAMP_TLV and len(v) == 8 and not is_diag: + kvs.append(('active_timestamp', str(struct.unpack('>Q', v)[0] >> 16))) + elif t == consts.NM_CHANNEL_MASK_TLV and not is_diag: + kvs.append(('channel_mask', v.hex())) + elif t == consts.NM_EXTENDED_PAN_ID_TLV and len(v) == 8 and not is_diag: + kvs.append(('ext_pan_id', v.hex())) + elif t == consts.NM_NETWORK_NAME_TLV and not is_diag: + kvs.append(('network_name', v.decode('utf-8', errors='replace'))) + elif t == consts.NM_PSKC_TLV and len(v) == 16 and not is_diag: + kvs.append(('pskc', v.hex())) + elif t == consts.NM_SECURITY_POLICY_TLV and not is_diag: + kvs.append(('security_policy', v.hex())) + elif t == consts.NM_NETWORK_KEY_TLV and len(v) == 16 and not is_diag: + kvs.append(('network_key', v.hex())) + elif t == consts.NM_PAN_ID_TLV and len(v) == 2 and not is_diag: + kvs.append(('pan_id', hex(struct.unpack('>H', v)[0]))) # Other Thread TLVs elif t == consts.NL_TARGET_EID_TLV and len(v) == 16: @@ -141,6 +157,14 @@ def apply_patches(): layer_fields._LAYER_FIELDS['coap.tlv.state'] = layer_fields._auto layer_fields._LAYER_FIELDS['coap.tlv.border_agent_rloc16'] = layer_fields._auto layer_fields._LAYER_FIELDS['coap.tlv.channel'] = layer_fields._auto + layer_fields._LAYER_FIELDS['coap.tlv.active_timestamp'] = layer_fields._auto + layer_fields._LAYER_FIELDS['coap.tlv.channel_mask'] = layer_fields._bytes + layer_fields._LAYER_FIELDS['coap.tlv.ext_pan_id'] = layer_fields._bytes + layer_fields._LAYER_FIELDS['coap.tlv.network_name'] = layer_fields._str + layer_fields._LAYER_FIELDS['coap.tlv.pskc'] = layer_fields._bytes + layer_fields._LAYER_FIELDS['coap.tlv.security_policy'] = layer_fields._bytes + layer_fields._LAYER_FIELDS['coap.tlv.network_key'] = layer_fields._bytes + layer_fields._LAYER_FIELDS['coap.tlv.pan_id'] = layer_fields._auto def which_tshark_patch(): default_path = '/tmp/thread-wireshark/tshark' diff --git a/tests/scripts/thread-cert/pktverify/layer_fields.py b/tests/scripts/thread-cert/pktverify/layer_fields.py index 05d247847..6a2ae46c9 100644 --- a/tests/scripts/thread-cert/pktverify/layer_fields.py +++ b/tests/scripts/thread-cert/pktverify/layer_fields.py @@ -76,6 +76,15 @@ def _auto(v: Union[LayerFieldsContainer, LayerField]): except (ValueError, TypeError): pass + try: + # ISO format: '1970-01-01T00:00:20.000000000+0000' + # we only care about the seconds part + time_str = dv.split('.')[0] + dt = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S") + return int(dt.replace(tzinfo=datetime.timezone.utc).timestamp()) + except (ValueError, TypeError, IndexError): + pass + try: int(rv, 16) return int(dv)