diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 2344191f3..0ff28fc0c 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -205,6 +205,7 @@ ot_nexus_test(9_2_7 "cert;nexus") ot_nexus_test(9_2_8 "cert;nexus") ot_nexus_test(9_2_10 "cert;nexus") ot_nexus_test(9_2_11 "cert;nexus") +ot_nexus_test(9_2_13 "cert;nexus") ot_nexus_test(9_2_14 "cert;nexus") ot_nexus_test(9_2_15 "cert;nexus") diff --git a/tests/nexus/platform/nexus_core.cpp b/tests/nexus/platform/nexus_core.cpp index 189a29d23..5d8288e27 100644 --- a/tests/nexus/platform/nexus_core.cpp +++ b/tests/nexus/platform/nexus_core.cpp @@ -61,7 +61,7 @@ Core::Core(void) } } -void Core::SaveTestInfo(const char *aFilename) +void Core::SaveTestInfo(const char *aFilename, Node *aLeaderNode) { FILE *file = fopen(aFilename, "w"); Node *tail = mNodes.GetTail(); @@ -70,7 +70,7 @@ void Core::SaveTestInfo(const char *aFilename) const char *dot; const char *version; int testcaseLen; - Node *leaderNode = nullptr; + Node *leaderNode = aLeaderNode; VerifyOrExit(file != nullptr); @@ -108,12 +108,8 @@ void Core::SaveTestInfo(const char *aFilename) fprintf(file, " \"testcase\": \"%.*s\",\n", testcaseLen, testcase); fprintf(file, " \"pcap\": \"%s\",\n", getenv("OT_NEXUS_PCAP_FILE") ? getenv("OT_NEXUS_PCAP_FILE") : ""); - if (!mNodes.IsEmpty()) + if (leaderNode == nullptr) { - leaderNode = mNodes.GetTail(); - NetworkKey networkKey; - String keyString; - for (Node &node : mNodes) { if (node.Get().IsLeader()) @@ -122,6 +118,17 @@ void Core::SaveTestInfo(const char *aFilename) break; } } + } + + if (leaderNode == nullptr) + { + leaderNode = mNodes.GetTail(); + } + + if (leaderNode != nullptr) + { + NetworkKey networkKey; + String keyString; leaderNode->Get().GetNetworkKey(networkKey); keyString.AppendHexBytes(networkKey.m8, OT_NETWORK_KEY_SIZE); diff --git a/tests/nexus/platform/nexus_core.hpp b/tests/nexus/platform/nexus_core.hpp index b5b9c2b02..df4e0733f 100644 --- a/tests/nexus/platform/nexus_core.hpp +++ b/tests/nexus/platform/nexus_core.hpp @@ -59,7 +59,7 @@ public: //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Test specific helper methods - void SaveTestInfo(const char *aFilename); + void SaveTestInfo(const char *aFilename, Node *aLeaderNode = nullptr); void SendAndVerifyEchoRequest(Node &aSender, const Ip6::Address &aDestination, uint16_t aPayloadSize = 0, diff --git a/tests/nexus/run_nexus_tests.sh b/tests/nexus/run_nexus_tests.sh index c816266c3..2f3f2c743 100755 --- a/tests/nexus/run_nexus_tests.sh +++ b/tests/nexus/run_nexus_tests.sh @@ -141,6 +141,7 @@ DEFAULT_TESTS=( "9_2_8" "9_2_10" "9_2_11" + "9_2_13" "9_2_14" "9_2_15" ) diff --git a/tests/nexus/test_9_2_13.cpp b/tests/nexus/test_9_2_13.cpp new file mode 100644 index 000000000..5f906a534 --- /dev/null +++ b/tests/nexus/test_9_2_13.cpp @@ -0,0 +1,328 @@ +/* + * 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 + +#include "meshcop/commissioner.hpp" +#include "meshcop/meshcop_tlvs.hpp" +#include "platform/nexus_core.hpp" +#include "platform/nexus_node.hpp" +#include "thread/mle.hpp" +#include "thread/thread_netif.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 a network, in milliseconds. + */ +static constexpr uint32_t kJoinTime = 10 * 1000; + +/** + * Time to advance for a node to upgrade to a router, in milliseconds. + */ +static constexpr uint32_t kRouterUpgradeTime = 200 * 1000; + +/** + * Time to advance for a commissioner to become active, in milliseconds. + */ +static constexpr uint32_t kPetitionTime = 5 * 1000; + +/** + * Time to wait for a response, in milliseconds. + */ +static constexpr uint32_t kResponseTime = 5000; + +/** + * Time to wait for ICMPv6 Echo response, in milliseconds. + */ +static constexpr uint32_t kEchoTimeout = 5000; + +/** + * Primary Channel. + */ +static constexpr uint8_t kPrimaryChannel = 11; + +/** + * Secondary Channel. + */ +static constexpr uint8_t kSecondaryChannel = 12; + +/** + * Scan Count. + */ +static constexpr uint8_t kScanCount = 2; + +/** + * Scan Period. + */ +static constexpr uint16_t kScanPeriod = 200; + +/** + * Scan Duration. + */ +static constexpr uint16_t kScanDuration = 32; + +/** + * SED Data Poll Rate. + */ +static constexpr uint32_t kSedPollRate = 500; + +static void SendMgmtEnergyScanQuery(Node &aCommissioner, const Ip6::Address &aDestAddr, uint16_t aSessionId) +{ + Tmf::Agent &agent = aCommissioner.Get(); + Coap::Message *message = agent.NewPriorityConfirmablePostMessage(kUriEnergyScan); + + VerifyOrQuit(message != nullptr); + + SuccessOrQuit(Tlv::Append(*message, aSessionId)); + SuccessOrQuit(MeshCoP::ChannelMaskTlv::AppendTo(*message, (1 << kPrimaryChannel) | (1 << kSecondaryChannel))); + SuccessOrQuit(Tlv::Append(*message, kScanCount)); + SuccessOrQuit(Tlv::Append(*message, kScanPeriod)); + SuccessOrQuit(Tlv::Append(*message, kScanDuration)); + + Tmf::MessageInfo messageInfo(aCommissioner.GetInstance()); + messageInfo.SetPeerAddr(aDestAddr); + SuccessOrQuit(agent.SendMessage(*message, messageInfo)); +} + +void Test9_2_13(void) +{ + /** + * 9.2.13 Energy Scan Requests + * + * 9.2.13.1 Topology + * - NOTE: Two sniffers are required to run this test case! + * - Leader_2 & SED_2 formed a separate network on another channel (Secondary channel). + * - SED_2 is configured with a data poll rate set to 500ms. + * + * 9.2.13.2 Purpose & Description + * The purpose of this test case is to ensure that the DUT is able to properly accept and process Energy Scan + * Requests with a MGMT_ED_REPORT.ans. + * + * Spec Reference | V1.1 Section | V1.3.0 Section + * -----------------------------------|--------------|--------------- + * Collecting Energy Scan Information | 8.7.10 | 8.7.10 + */ + + Core nexus; + + Node &leader1 = nexus.CreateNode(); + Node &commissioner = nexus.CreateNode(); + Node &router1 = nexus.CreateNode(); + Node &fed1 = nexus.CreateNode(); + Node &leader2 = nexus.CreateNode(); + Node &sed2 = nexus.CreateNode(); + + leader1.SetName("Leader_1"); + commissioner.SetName("Commissioner"); + router1.SetName("Router_1"); + fed1.SetName("FED_1"); + leader2.SetName("Leader_2"); + sed2.SetName("SED_2"); + + nexus.AdvanceTime(0); + + Instance::SetLogLevel(kLogLevelNote); + + Log("Step 1: Form topology and start a constant ICMPv6 Echo Request from Leader_2 to SED_2."); + + /** + * Step 1: All + * - Description: Form topology and start a constant ICMPv6 Echo Request from Leader_2 to SED_2. + * - Pass Criteria: N/A. + */ + + /** Set up AllowList for links. */ + leader1.AllowList(commissioner); + commissioner.AllowList(leader1); + + leader1.AllowList(router1); + router1.AllowList(leader1); + + router1.AllowList(fed1); + fed1.AllowList(router1); + + leader2.AllowList(sed2); + sed2.AllowList(leader2); + + Ip6::Prefix prefix; + Ip6::NetworkPrefix networkPrefix; + + SuccessOrQuit(prefix.FromString("fd00:7d1:a11:1::/64")); + SuccessOrQuit(networkPrefix.SetFrom(prefix)); + + /** Network 1 on Primary Channel. */ + { + MeshCoP::Dataset::Info datasetInfo; + SuccessOrQuit(datasetInfo.GenerateRandom(leader1.GetInstance())); + datasetInfo.Set(kPrimaryChannel); + datasetInfo.Set(networkPrefix); + leader1.Get().SaveLocal(datasetInfo); + leader1.Get().Up(); + SuccessOrQuit(leader1.Get().Start()); + } + + nexus.AdvanceTime(kFormNetworkTime); + VerifyOrQuit(leader1.Get().IsLeader()); + + commissioner.Join(leader1); + router1.Join(leader1); + nexus.AdvanceTime(kRouterUpgradeTime); + + VerifyOrQuit(commissioner.Get().IsAttached()); + VerifyOrQuit(router1.Get().IsRouter()); + + fed1.Join(router1, Node::kAsFed); + nexus.AdvanceTime(kJoinTime); + VerifyOrQuit(fed1.Get().IsAttached()); + + /** Network 2 on Secondary Channel. */ + { + MeshCoP::Dataset::Info datasetInfo; + SuccessOrQuit(datasetInfo.GenerateRandom(leader2.GetInstance())); + datasetInfo.Set(kSecondaryChannel); + datasetInfo.Set(networkPrefix); + leader2.Get().SaveLocal(datasetInfo); + leader2.Get().Up(); + SuccessOrQuit(leader2.Get().Start()); + } + + nexus.AdvanceTime(kFormNetworkTime); + VerifyOrQuit(leader2.Get().IsLeader()); + + sed2.Join(leader2, Node::kAsSed); + SuccessOrQuit(sed2.Get().SetExternalPollPeriod(kSedPollRate)); + nexus.AdvanceTime(kJoinTime); + VerifyOrQuit(sed2.Get().IsAttached()); + + /** Start ICMPv6 Echo Request from Leader_2 to SED_2. */ + leader2.SendEchoRequest(sed2.Get().GetMeshLocalEid(), 0); + + /** Start Commissioner. */ + SuccessOrQuit(commissioner.Get().Start(nullptr, nullptr, nullptr)); + nexus.AdvanceTime(kPetitionTime); + VerifyOrQuit(commissioner.Get().IsActive()); + + uint16_t sessionId = commissioner.Get().GetSessionId(); + + Log("Step 2: Commissioner sends a unicast MGMT_ED_SCAN.qry to the DUT."); + + /** + * Step 2: Commissioner + * - Description: Harness instructs the Commissioner to send a unicast MGMT_ED_SCAN.qry to the DUT for the Primary + * and Secondary channels: + * - CoAP Request URI: coap://[DUT]:MM/c/es + * - CoAP Payload: + * - Commissioner Session ID TLV + * - Channel Mask TLV (Primary and Secondary) + * - Count TLV <0x02> + * - Period TLV <0x00C8> + * - Scan Duration TLV <0x20> + * - Pass Criteria: N/A. + */ + + SendMgmtEnergyScanQuery(commissioner, router1.Get().GetMeshLocalEid(), sessionId); + + Log("Step 3: DUT sends a MGMT_ED_REPORT.ans response to the Commissioner."); + + /** + * Step 3: DUT + * - Description: Automatically sends a MGMT_ED_REPORT.ans response to the Commissioner. + * - Pass Criteria: The DUT MUST send MGMT_ED_REPORT.ans to the Commissioner and report energy measurements for the + * Primary and Secondary channels: + * - CoAP Request URI: coap://[Commissioner]:MM/c/er + * - CoAP Payload: + * - Channel Mask TLV (Primary and Secondary) + * - Energy List TLV (4 bytes) + */ + + nexus.AdvanceTime(kResponseTime); + + Log("Step 4: Commissioner sends multicast MGMT_ED_SCAN.qry."); + + /** + * Step 4: Commissioner + * - Description: Harness instructs the Commissioner to send MGMT_ED_SCAN.qry to the Realm Local All Thread Nodes + * multicast address: FF33:0040:::1 for the Primary and Secondary channels: + * - CoAP Request URI: coap://[Destination]:MM/c/es + * - CoAP Payload: + * - Commissioner Session ID TLV + * - Channel Mask TLV (Primary and Secondary) + * - Count TLV <0x02> + * - Period TLV <0x00C8> + * - Scan Duration TLV <0x20> + * - Pass Criteria: N/A. + */ + + SendMgmtEnergyScanQuery(commissioner, commissioner.Get().GetRealmLocalAllThreadNodesAddress(), sessionId); + + Log("Step 5: DUT sends a MGMT_ED_REPORT.ans response to the Commissioner."); + + /** + * Step 5: DUT + * - Description: Automatically sends a MGMT_ED_REPORT.ans response to the Commissioner. + * - Pass Criteria: The DUT MUST send MGMT_ED_REPORT.ans to the Commissioner and report energy measurements for the + * Primary and Secondary channels: + * - CoAP Request URI: coap://[Commissioner]:MM/c/er + * - CoAP Payload: + * - Channel Mask TLV (Primary and Secondary) + * - Energy List TLV (length of 4 bytes) + */ + + nexus.AdvanceTime(kResponseTime); + + Log("Step 6: Commissioner sends an ICMPv6 Echo Request to the DUT."); + + /** + * Step 6: Commissioner + * - Description: Harness verifies connectivity by instructing the Commissioner to send an ICMPv6 Echo Request to + * the DUT mesh local address. + * - Pass Criteria: The DUT MUST respond with an ICMPv6 Echo Reply. + */ + + nexus.SendAndVerifyEchoRequest(commissioner, router1.Get().GetMeshLocalEid(), 0, 64, kEchoTimeout); + + nexus.SaveTestInfo("test_9_2_13.json", &leader1); +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::Test9_2_13(); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/nexus/verify_9_2_13.py b/tests/nexus/verify_9_2_13.py new file mode 100644 index 000000000..627a8144e --- /dev/null +++ b/tests/nexus/verify_9_2_13.py @@ -0,0 +1,164 @@ +#!/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 + +PRIMARY_CHANNEL = 11 +SECONDARY_CHANNEL = 12 + + +def verify(pv): + # 9.2.13 Energy Scan Requests + # + # 9.2.13.1 Topology + # - Two sniffers are required to run this test case! + # - Leader_2 & SED_2 formed a separate network on another channel (Secondary channel). + # - SED_2 is configured to use a short Poll Period (e.g. 500ms). + # - Router_1 & FED_1 & Commissioner joined the network on the Primary channel. + # - Commissioner & Leader_1 & Router_1 are on the same Primary channel. + # + # 9.2.13.2 Purpose & Description + # - The purpose of this test case is to verify the behavior of the DUT when receiving MGMT_ED_SCAN.qry. + # + # Spec Reference | V1.1 Section | V1.3.0 Section + # ----------------------------------------|--------------|--------------- + # Collecting Energy Scan Information | 8.7.10 | 8.7.10 + + pkts = pv.pkts + pv.summary.show() + + # Step 1: DUT + # - Description: Ensure topology is formed correctly. + # - Pass Criteria: N/A. + print("Step 1: DUT") + + # Step 2: Commissioner + # - Description: Commissioner sends unicast MGMT_ED_SCAN.qry to the DUT. + # - Pass Criteria: Commissioner MUST send MGMT_ED_SCAN.qry to the DUT with the following format: + # - CoAP Request URI: coap://[DUT]:MM/c/es + # - CoAP Payload: + # - Commissioner Session ID TLV (valid) + # - Channel Mask TLV (Primary and Secondary) + # - Count TLV <0x02> + # - Period TLV <0x00c8> (200 ms) + # - Scan Duration TLV <0x20> (32 ms) + print("Step 2: Commissioner sends unicast MGMT_ED_SCAN.qry to the DUT.") + mask = (1 << (31 - PRIMARY_CHANNEL)) | (1 << (31 - SECONDARY_CHANNEL)) + expected_channel_mask = '0004' + f'{mask:08x}' + pkts.filter(lambda p: p.wpan.frame_type == consts.WPAN_DATA and\ + p.wpan.src16 == pv.vars['Commissioner_RLOC16'] and\ + p.wpan.dst16 != 0xffff and\ + p.coap.uri_path == consts.MGMT_ED_SCAN).\ + must_next().\ + must_verify(lambda p: p.coap.tlv.channel_mask == expected_channel_mask and\ + p.coap.tlv.count == 2 and\ + p.coap.tlv.period == 200 and\ + p.coap.tlv.scan_duration == 32) + + # Step 3: DUT + # - Description: Automatically sends a MGMT_ED_REPORT.ans response to the Commissioner. + # - Pass Criteria: The DUT MUST send MGMT_ED_REPORT.ans to the Commissioner and report energy measurements for the + # Primary and Secondary channels: + # - CoAP Request URI: coap://[Commissioner]:MM/c/er + # - CoAP Payload: + # - Channel Mask TLV (Primary and Secondary) + # - Energy List TLV (4 bytes) + print("Step 3: DUT sends a MGMT_ED_REPORT.ans response to the Commissioner.") + pkts.filter(lambda p: p.wpan.frame_type == consts.WPAN_DATA and\ + p.wpan.src16 == pv.vars['Router_1_RLOC16'] and\ + p.wpan.dst16 != 0xffff and\ + p.coap.uri_path == consts.MGMT_ED_REPORT).\ + must_next().\ + must_verify(lambda p: p.coap.tlv.channel_mask == expected_channel_mask and\ + len(p.coap.tlv.energy_list) == 4) + + # Step 4: Commissioner + # - Description: Commissioner sends multicast MGMT_ED_SCAN.qry. + # - Pass Criteria: Commissioner MUST send MGMT_ED_SCAN.qry to the Link-Local All Thread Nodes multicast address + # (FF32:40:::1) with the following format: + # - CoAP Request URI: coap://[FF32:40:::1]:MM/c/es + # - CoAP Payload: + # - Commissioner Session ID TLV (valid) + # - Channel Mask TLV (Primary and Secondary) + # - Count TLV <0x02> + # - Period TLV <0x00c8> (200 ms) + # - Scan Duration TLV <0x20> (32 ms) + print("Step 4: Commissioner sends multicast MGMT_ED_SCAN.qry.") + pkts.filter(lambda p: p.wpan.frame_type == consts.WPAN_DATA and\ + p.wpan.src16 == pv.vars['Commissioner_RLOC16'] and\ + p.wpan.dst16 == 0xffff and\ + p.coap.uri_path == consts.MGMT_ED_SCAN).\ + must_next().\ + must_verify(lambda p: p.coap.tlv.channel_mask == expected_channel_mask and\ + p.coap.tlv.count == 2 and\ + p.coap.tlv.period == 200 and\ + p.coap.tlv.scan_duration == 32) + + # Step 5: DUT + # - Description: Automatically sends a MGMT_ED_REPORT.ans response to the Commissioner. + # - Pass Criteria: The DUT MUST send MGMT_ED_REPORT.ans to the Commissioner and report energy measurements for the + # Primary and Secondary channels: + # - CoAP Request URI: coap://[Commissioner]:MM/c/er + # - CoAP Payload: + # - Channel Mask TLV (Primary and Secondary) + # - Energy List TLV (length of 4 bytes) + print("Step 5: DUT sends a MGMT_ED_REPORT.ans response to the Commissioner.") + pkts.filter(lambda p: p.wpan.frame_type == consts.WPAN_DATA and\ + p.wpan.src16 == pv.vars['Router_1_RLOC16'] and\ + p.wpan.dst16 != 0xffff and\ + p.coap.uri_path == consts.MGMT_ED_REPORT).\ + must_next().\ + must_verify(lambda p: p.coap.tlv.channel_mask == expected_channel_mask and\ + len(p.coap.tlv.energy_list) == 4) + + # Step 6: Commissioner + # - Description: Harness verifies connectivity by instructing the Commissioner to send an ICMPv6 Echo Request to + # the DUT mesh local address. + # - Pass Criteria: The DUT MUST respond with an ICMPv6 Echo Reply. + print("Step 6: Commissioner sends an ICMPv6 Echo Request to the DUT.") + pkts.filter(lambda p: p.wpan.frame_type == consts.WPAN_DATA and\ + p.wpan.src16 == pv.vars['Commissioner_RLOC16'] and\ + p.icmpv6.type == consts.ICMPV6_TYPE_ECHO_REQUEST).\ + must_next() + pkts.filter(lambda p: p.wpan.frame_type == consts.WPAN_DATA and\ + p.wpan.src16 == pv.vars['Router_1_RLOC16'] and\ + p.icmpv6.type == consts.ICMPV6_TYPE_ECHO_REPLY).\ + 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 4ccd8bfe3..c3f7c0a6e 100644 --- a/tests/nexus/verify_utils.py +++ b/tests/nexus/verify_utils.py @@ -66,11 +66,11 @@ def thread_coap_tlv_parse(t, v, layer=None): elif t == consts.NM_STATE_TLV and len(v) == 1 and not is_diag: kvs.append(('state', v[0])) elif t == consts.NM_STEERING_DATA_TLV and not is_diag: # DG_IPV6_ADDRESS_LIST_TLV is 16*n - kvs.append(('steering_data', v.hex())) + kvs.append(('steering_data', v)) elif t == consts.NM_BORDER_AGENT_LOCATOR_TLV and len(v) == 2 and not is_diag: # DG_MAC_COUNTERS_TLV is 4*n kvs.append(('border_agent_rloc16', struct.unpack('>H', v)[0])) elif t == consts.TLV_REQUEST_TLV: - kvs.append(('tlv_request', v.hex())) + kvs.append(('tlv_request', v)) elif t == consts.NM_CHANNEL_TLV and len(v) == 3 and not is_diag: # DG_MAC_EXTENDED_ADDRESS_TLV is 8 kvs.append(('channel', struct.unpack('>H', v[1:3])[0])) elif t == consts.NM_ACTIVE_TIMESTAMP_TLV and len(v) == 8 and not is_diag: @@ -80,15 +80,23 @@ def thread_coap_tlv_parse(t, v, layer=None): elif t == consts.NM_DELAY_TIMER_TLV and len(v) == 4 and not is_diag: kvs.append(('delay_timer', struct.unpack('>I', v)[0])) elif t == consts.NM_CHANNEL_MASK_TLV and not is_diag: - kvs.append(('channel_mask', v.hex())) + kvs.append(('channel_mask', v)) + elif t == consts.NM_COUNT_TLV and len(v) == 1 and not is_diag: + kvs.append(('count', v[0])) + elif t == consts.NM_PERIOD_TLV and len(v) == 2 and not is_diag: + kvs.append(('period', struct.unpack('>H', v)[0])) + elif t == consts.NM_SCAN_DURATION and len(v) == 2 and not is_diag: + kvs.append(('scan_duration', struct.unpack('>H', v)[0])) + elif t == consts.NM_ENERGY_LIST_TLV and not is_diag: + kvs.append(('energy_list', v)) elif t == consts.NM_EXTENDED_PAN_ID_TLV and len(v) == 8 and not is_diag: - kvs.append(('ext_pan_id', v.hex())) + kvs.append(('ext_pan_id', v)) 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())) + kvs.append(('pskc', v)) elif t == consts.NM_SECURITY_POLICY_TLV and not is_diag: - kvs.append(('security_policy', v.hex())) + kvs.append(('security_policy', v)) if len(v) >= 3: # v[0:2] is rotation time # v[2] is the first byte of flags @@ -113,13 +121,13 @@ def thread_coap_tlv_parse(t, v, layer=None): kvs.append(('sec_policy_p', flags & 1)) elif t == consts.NM_NETWORK_KEY_TLV and len(v) == 16 and not is_diag: - kvs.append(('network_key', v.hex())) + kvs.append(('network_key', v)) elif t == consts.NM_PAN_ID_TLV and len(v) == 2 and not is_diag: kvs.append(('pan_id', struct.unpack('>H', v)[0])) elif t == consts.NM_NETWORK_MESH_LOCAL_PREFIX_TLV and len(v) == 8 and not is_diag: - kvs.append(('mesh_local_prefix', v.hex())) + kvs.append(('mesh_local_prefix', v)) elif t == consts.NM_FUTURE_TLV: - kvs.append(('future_tlv', v.hex())) + kvs.append(('future_tlv', v)) # Other Thread TLVs elif t == consts.NL_TARGET_EID_TLV and len(v) == 16: @@ -170,7 +178,7 @@ def thread_coap_tlv_parse(t, v, layer=None): kvs.append(('child_id', hex(child_id))) kvs.append(('child_mode', hex(mode))) elif t == consts.DG_CHANNEL_PAGES_TLV: - kvs.append(('channel_pages', v.hex())) + kvs.append(('channel_pages', v)) elif t == consts.NM_DISCOVERY_RESPONSE_TLV: # Discovery Response TLV # Bits 7-4: Version, Bit 3: Native Commissioner if len(v) >= 1: @@ -228,6 +236,10 @@ def apply_patches(): layer_fields._LAYER_FIELDS['coap.tlv.pending_timestamp'] = layer_fields._auto layer_fields._LAYER_FIELDS['coap.tlv.delay_timer'] = layer_fields._auto layer_fields._LAYER_FIELDS['coap.tlv.channel_mask'] = layer_fields._bytes + layer_fields._LAYER_FIELDS['coap.tlv.count'] = layer_fields._auto + layer_fields._LAYER_FIELDS['coap.tlv.period'] = layer_fields._auto + layer_fields._LAYER_FIELDS['coap.tlv.scan_duration'] = layer_fields._auto + layer_fields._LAYER_FIELDS['coap.tlv.energy_list'] = layer_fields._bytes layer_fields._LAYER_FIELDS['wpan_tap.ch_num'] = layer_fields._auto layer_fields._LAYER_FIELDS['wpan-tap.ch_num'] = layer_fields._auto layer_fields._LAYER_FIELDS['coap.tlv.ext_pan_id'] = layer_fields._bytes @@ -300,6 +312,12 @@ def run_main(verify_func): pv = PacketVerifier(json_file, wireshark_prefs=wireshark_prefs) pv.add_common_vars() + + # Add RLOC16 variables for convenience + for node_id, rloc16 in data.get('rloc16s', {}).items(): + name = pv.test_info.get_node_name(int(node_id)) + pv.add_vars(**{f'{name}_RLOC16': int(rloc16, 16)}) + verify_func(pv) print("Verification PASSED") except Exception as e: diff --git a/tests/scripts/thread-cert/pktverify/coap.py b/tests/scripts/thread-cert/pktverify/coap.py index 69a25992a..78b37b677 100644 --- a/tests/scripts/thread-cert/pktverify/coap.py +++ b/tests/scripts/thread-cert/pktverify/coap.py @@ -91,6 +91,8 @@ class CoapLayer(Layer): self._add_field('coap.tlv.type', hex(t)) for k, v in tvs: assert isinstance(k, str), (t, k, v) + if isinstance(v, (bytes, bytearray)): + v = v.hex() assert isinstance(v, (str, int)), (t, k, v) self._add_field('coap.tlv.' + k, str(v)) diff --git a/tests/scripts/thread-cert/pktverify/consts.py b/tests/scripts/thread-cert/pktverify/consts.py index e81de672f..025fc5044 100644 --- a/tests/scripts/thread-cert/pktverify/consts.py +++ b/tests/scripts/thread-cert/pktverify/consts.py @@ -213,6 +213,8 @@ NM_JOINER_ROUTER_KEK_TLV = 21 NM_PENDING_TIMESTAMP_TLV = 51 NM_DELAY_TIMER_TLV = 52 NM_CHANNEL_MASK_TLV = 53 +NM_COUNT_TLV = 54 +NM_PERIOD_TLV = 55 NM_SCAN_DURATION = 56 NM_ENERGY_LIST_TLV = 57 NM_DISCOVERY_REQUEST_TLV = 128