mirror of
https://github.com/espressif/openthread.git
synced 2026-08-27 20:39:54 +00:00
[nexus] add test 9.2.13 Energy Scan Requests (#12551)
This commit adds Nexus test case 9.2.13 which verifies that the Thread device properly accepts and processes Energy Scan Requests and sends the corresponding Energy Scan Reports. Implementation details: - tests/nexus/test_9_2_13.cpp: C++ test execution logic. Implements the 9.2.13 spec using direct core calls. Manages two separate networks (Network 1 on channel 11 and Network 2 on channel 12). Configures a topology with Leader, Commissioner, Router, and FED nodes in Network 1, and Leader and SED nodes in Network 2. - tests/nexus/verify_9_2_13.py: PCAP verification script. Verifies unicast and multicast MGMT_ED_SCAN.qry messages and the resulting MGMT_ED_REPORT.ans responses. Uses strict CoAP URI path and ICMPv6 type filtering. - tests/nexus/platform/nexus_core.hpp/cpp: Added an optional leader node parameter to SaveTestInfo() to allow specifying which network partition information (like Network Key and Mesh-Local Prefix) should be exported to the JSON file for verification. This ensures correct packet dissection in multi-partition test scenarios. - tests/nexus/run_nexus_tests.sh: Added 9_2_13 to default test list. - tests/nexus/CMakeLists.txt: Added nexus_9_2_13 target.
This commit is contained in:
@@ -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")
|
||||
|
||||
|
||||
@@ -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<OT_NETWORK_KEY_SIZE * 2 + 1> keyString;
|
||||
|
||||
for (Node &node : mNodes)
|
||||
{
|
||||
if (node.Get<Mle::Mle>().IsLeader())
|
||||
@@ -122,6 +118,17 @@ void Core::SaveTestInfo(const char *aFilename)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (leaderNode == nullptr)
|
||||
{
|
||||
leaderNode = mNodes.GetTail();
|
||||
}
|
||||
|
||||
if (leaderNode != nullptr)
|
||||
{
|
||||
NetworkKey networkKey;
|
||||
String<OT_NETWORK_KEY_SIZE * 2 + 1> keyString;
|
||||
|
||||
leaderNode->Get<KeyManager>().GetNetworkKey(networkKey);
|
||||
keyString.AppendHexBytes(networkKey.m8, OT_NETWORK_KEY_SIZE);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -141,6 +141,7 @@ DEFAULT_TESTS=(
|
||||
"9_2_8"
|
||||
"9_2_10"
|
||||
"9_2_11"
|
||||
"9_2_13"
|
||||
"9_2_14"
|
||||
"9_2_15"
|
||||
)
|
||||
|
||||
@@ -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 <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#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<Tmf::Agent>();
|
||||
Coap::Message *message = agent.NewPriorityConfirmablePostMessage(kUriEnergyScan);
|
||||
|
||||
VerifyOrQuit(message != nullptr);
|
||||
|
||||
SuccessOrQuit(Tlv::Append<MeshCoP::CommissionerSessionIdTlv>(*message, aSessionId));
|
||||
SuccessOrQuit(MeshCoP::ChannelMaskTlv::AppendTo(*message, (1 << kPrimaryChannel) | (1 << kSecondaryChannel)));
|
||||
SuccessOrQuit(Tlv::Append<MeshCoP::CountTlv>(*message, kScanCount));
|
||||
SuccessOrQuit(Tlv::Append<MeshCoP::PeriodTlv>(*message, kScanPeriod));
|
||||
SuccessOrQuit(Tlv::Append<MeshCoP::ScanDurationTlv>(*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<MeshCoP::Dataset::kChannel>(kPrimaryChannel);
|
||||
datasetInfo.Set<MeshCoP::Dataset::kMeshLocalPrefix>(networkPrefix);
|
||||
leader1.Get<MeshCoP::ActiveDatasetManager>().SaveLocal(datasetInfo);
|
||||
leader1.Get<ThreadNetif>().Up();
|
||||
SuccessOrQuit(leader1.Get<Mle::Mle>().Start());
|
||||
}
|
||||
|
||||
nexus.AdvanceTime(kFormNetworkTime);
|
||||
VerifyOrQuit(leader1.Get<Mle::Mle>().IsLeader());
|
||||
|
||||
commissioner.Join(leader1);
|
||||
router1.Join(leader1);
|
||||
nexus.AdvanceTime(kRouterUpgradeTime);
|
||||
|
||||
VerifyOrQuit(commissioner.Get<Mle::Mle>().IsAttached());
|
||||
VerifyOrQuit(router1.Get<Mle::Mle>().IsRouter());
|
||||
|
||||
fed1.Join(router1, Node::kAsFed);
|
||||
nexus.AdvanceTime(kJoinTime);
|
||||
VerifyOrQuit(fed1.Get<Mle::Mle>().IsAttached());
|
||||
|
||||
/** Network 2 on Secondary Channel. */
|
||||
{
|
||||
MeshCoP::Dataset::Info datasetInfo;
|
||||
SuccessOrQuit(datasetInfo.GenerateRandom(leader2.GetInstance()));
|
||||
datasetInfo.Set<MeshCoP::Dataset::kChannel>(kSecondaryChannel);
|
||||
datasetInfo.Set<MeshCoP::Dataset::kMeshLocalPrefix>(networkPrefix);
|
||||
leader2.Get<MeshCoP::ActiveDatasetManager>().SaveLocal(datasetInfo);
|
||||
leader2.Get<ThreadNetif>().Up();
|
||||
SuccessOrQuit(leader2.Get<Mle::Mle>().Start());
|
||||
}
|
||||
|
||||
nexus.AdvanceTime(kFormNetworkTime);
|
||||
VerifyOrQuit(leader2.Get<Mle::Mle>().IsLeader());
|
||||
|
||||
sed2.Join(leader2, Node::kAsSed);
|
||||
SuccessOrQuit(sed2.Get<DataPollSender>().SetExternalPollPeriod(kSedPollRate));
|
||||
nexus.AdvanceTime(kJoinTime);
|
||||
VerifyOrQuit(sed2.Get<Mle::Mle>().IsAttached());
|
||||
|
||||
/** Start ICMPv6 Echo Request from Leader_2 to SED_2. */
|
||||
leader2.SendEchoRequest(sed2.Get<Mle::Mle>().GetMeshLocalEid(), 0);
|
||||
|
||||
/** Start Commissioner. */
|
||||
SuccessOrQuit(commissioner.Get<MeshCoP::Commissioner>().Start(nullptr, nullptr, nullptr));
|
||||
nexus.AdvanceTime(kPetitionTime);
|
||||
VerifyOrQuit(commissioner.Get<MeshCoP::Commissioner>().IsActive());
|
||||
|
||||
uint16_t sessionId = commissioner.Get<MeshCoP::Commissioner>().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<Mle::Mle>().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:<mesh local prefix>::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<Mle::Mle>().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<Mle::Mle>().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;
|
||||
}
|
||||
@@ -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:<MeshLocalPrefix>::1) with the following format:
|
||||
# - CoAP Request URI: coap://[FF32:40:<MeshLocalPrefix>::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)
|
||||
+28
-10
@@ -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:
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user