diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 4e9f86b16..2d6b1f6e1 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -251,6 +251,7 @@ ot_nexus_test(1_2_MATN_TC_21 "cert;nexus") ot_nexus_test(1_2_MATN_TC_22 "cert;nexus") ot_nexus_test(1_2_MATN_TC_23 "cert;nexus") ot_nexus_test(1_2_MATN_TC_26 "cert;nexus") +ot_nexus_test(1_2_BBR_TC_1 "cert;nexus") # Misc tests ot_nexus_test(border_admitter "core;nexus") diff --git a/tests/nexus/platform/nexus_infra_if.cpp b/tests/nexus/platform/nexus_infra_if.cpp index 88b75ce6a..d2b66472d 100644 --- a/tests/nexus/platform/nexus_infra_if.cpp +++ b/tests/nexus/platform/nexus_infra_if.cpp @@ -324,6 +324,7 @@ void InfraIf::Receive(Node &aSrcNode, const Ip6::Header &aHeader, Message &aMess Node &node = GetNode(); bool isIcmp6Nd = false; bool isEchoRequest = false; + bool isEchoReply = false; VerifyOrExit(!node.mInfraIf.HasAddress(aHeader.GetSource())); VerifyOrExit(!node.Get().IsOnMesh(aHeader.GetSource())); @@ -359,6 +360,9 @@ void InfraIf::Receive(Node &aSrcNode, const Ip6::Header &aHeader, Message &aMess case Ip6::Icmp::Header::kTypeEchoRequest: isEchoRequest = true; break; + case Ip6::Icmp::Header::kTypeEchoReply: + isEchoReply = true; + break; default: break; } @@ -380,6 +384,10 @@ void InfraIf::Receive(Node &aSrcNode, const Ip6::Header &aHeader, Message &aMess { HandleEchoRequest(aHeader, aMessage); } + else if (isEchoReply) + { + HandleEchoReply(aHeader, aMessage); + } else { // We also deliver generic IPv6 packets to the stack if they are NOT ICMPv6 ND packets. @@ -442,6 +450,14 @@ void InfraIf::HandleEchoRequest(const Ip6::Header &aHeader, Message &aMessage) mPendingTxQueue.Enqueue(*replyMessage); } +void InfraIf::HandleEchoReply(const Ip6::Header &aHeader, Message &aMessage) +{ + Ip6::Icmp::Header icmpHeader; + + SuccessOrQuit(aMessage.Read(sizeof(Ip6::Header), icmpHeader)); + mEchoReplyCallback.InvokeIfSet(aHeader.GetSource(), icmpHeader.GetId(), icmpHeader.GetSequence()); +} + void InfraIf::GetLinkLayerAddress(LinkLayerAddress &aLinkLayerAddress) const { // Use a unique MAC address based on Node ID diff --git a/tests/nexus/platform/nexus_infra_if.hpp b/tests/nexus/platform/nexus_infra_if.hpp index 02ac21400..9518467ea 100644 --- a/tests/nexus/platform/nexus_infra_if.hpp +++ b/tests/nexus/platform/nexus_infra_if.hpp @@ -75,6 +75,10 @@ public: void Receive(Node &aSrcNode, const Ip6::Header &aHeader, Message &aMessage); void GetLinkLayerAddress(LinkLayerAddress &aLinkLayerAddress) const; + typedef void (*EchoReplyHandler)(void *aContext, const Ip6::Address &aSource, uint16_t aId, uint16_t aSequence); + + void SetEchoReplyHandler(EchoReplyHandler aHandler, void *aContext) { mEchoReplyCallback.Set(aHandler, aContext); } + Node &GetNode(void); const Node &GetNode(void) const; @@ -84,11 +88,13 @@ private: void ProcessIcmp6Nd(const Ip6::Address &aSrcAddress, const uint8_t *aBuffer, uint16_t aBufferLength); void HandlePrefixInfoOption(const Ip6::Nd::PrefixInfoOption &aPio); void HandleEchoRequest(const Ip6::Header &aHeader, Message &aMessage); + void HandleEchoReply(const Ip6::Header &aHeader, Message &aMessage); - Node *mNode; - uint32_t mNodeId; - uint32_t mIfIndex; - Heap::Array mAddresses; + Node *mNode; + uint32_t mNodeId; + uint32_t mIfIndex; + Heap::Array mAddresses; + Callback mEchoReplyCallback; }; } // namespace Nexus diff --git a/tests/nexus/run_nexus_tests.sh b/tests/nexus/run_nexus_tests.sh index 8a8997df9..f5f73c6b8 100755 --- a/tests/nexus/run_nexus_tests.sh +++ b/tests/nexus/run_nexus_tests.sh @@ -186,6 +186,7 @@ DEFAULT_TESTS=( "1_2_MATN_TC_22" "1_2_MATN_TC_23" "1_2_MATN_TC_26" + "1_2_BBR_TC_1" ) # Use provided arguments or the default test list diff --git a/tests/nexus/test_1_2_BBR_TC_1.cpp b/tests/nexus/test_1_2_BBR_TC_1.cpp new file mode 100644 index 000000000..c9e6070ee --- /dev/null +++ b/tests/nexus/test_1_2_BBR_TC_1.cpp @@ -0,0 +1,365 @@ +/* + * 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 "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 kJoinNetworkTime = 200 * 1000; + +/** + * Time to advance for the BBR registration process, in milliseconds. + */ +static constexpr uint32_t kBbrRegistrationTime = 10 * 1000; + +/** + * Time to advance for the MLR registration process, in milliseconds. + */ +static constexpr uint32_t kMlrRegistrationTime = 5 * 1000; + +/** + * Time to advance for the ping response, in milliseconds. + */ +static constexpr uint32_t kPingResponseTime = 5 * 1000; + +/** + * Leader weight to prevent the DUT from creating its own partition. + */ +static constexpr uint8_t kLeaderWeight = 72; + +/** + * Multicast address MA1 used in the test. + */ +static const char kMa1Address[] = "ff04::1234"; + +/** + * Context for ICMP Echo Response. + */ +struct EchoContext +{ + uint16_t mIdentifier; + bool mReceived; +}; + +/** + * Handler for ICMP Echo Response. + */ +static void HandleEchoResponse(void *aContext, const Ip6::Address &aSource, uint16_t aId, uint16_t aSequence) +{ + OT_UNUSED_VARIABLE(aSource); + OT_UNUSED_VARIABLE(aSequence); + + EchoContext *context = static_cast(aContext); + + if (aId == context->mIdentifier) + { + context->mReceived = true; + } +} + +/** + * ICMP Echo Request identifier. + */ +static constexpr uint16_t kEchoIdentifier = 0x1234; + +/** + * ICMP Echo Request payload size. + */ +static constexpr uint16_t kEchoPayloadSize = 10; + +/** + * ICMP Echo Request hop limit. + */ +static constexpr uint8_t kEchoHopLimit = 64; + +/** + * Backbone interface index. + */ +static constexpr uint32_t kBackboneIfIndex = 1; + +void Test_1_2_BBR_TC_1(void) +{ + /** + * 5.11.1 BBR-TC-01: Device MUST send its BBR dataset to Leader if none exists + * + * 5.11.1.1 Topology + * - BR_1 (DUT): BR device operating as a BBR. + * - Leader: Test bed bed device operating as a Thread Leader. + * - Router: Test bed device operating as a Thread Router. + * - Host: Test bed BR device operating as a non-Thread device. + * + * 5.11.1.2 Purpose & Description + * The purpose of this test case is to verify that if no BBR dataset is present in a network (i.e. not sent around + * by Leader), then a BBR function MUST send its BBR dataset to the Leader. + * + * Spec Reference | V1.2 Section + * ----------------|------------- + * BBR Dataset | 5.21.3 + */ + + Core nexus; + + Node &leader = nexus.CreateNode(); + Node &router = nexus.CreateNode(); + Node &dut = nexus.CreateNode(); + Node &host = nexus.CreateNode(); + + leader.SetName("LEADER"); + router.SetName("ROUTER"); + dut.SetName("BR_1"); + host.SetName("HOST"); + + nexus.AdvanceTime(0); + + Instance::SetLogLevel(kLogLevelNote); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 0: Topology formation - Leader, Router"); + + /** + * Step 0 + * - Device: N/A + * - Description: Topology formation - Leader, Router + * - Pass Criteria: N/A + */ + + leader.Get().SetLeaderWeight(kLeaderWeight); + leader.Form(); + nexus.AdvanceTime(kFormNetworkTime); + VerifyOrQuit(leader.Get().IsLeader()); + + router.Join(leader); + nexus.AdvanceTime(kJoinNetworkTime); + VerifyOrQuit(router.Get().IsRouter()); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 1: Topology addition - BR_1 (DUT)"); + + /** + * Step 1 + * - Device: BR_1 (DUT) + * - Description: Topology addition - BR_1 (DUT). The DUT must be booted and configured join the Thread Network. + * - Pass Criteria: + * - The DUT MUST attach to the Thread Network. + */ + + dut.Get().Init(kBackboneIfIndex, true); + dut.Get().Init(); + SuccessOrQuit(dut.Get().SetEnabled(true)); + dut.Get().SetEnabled(true); + + dut.Join(leader); + nexus.AdvanceTime(kJoinNetworkTime); + VerifyOrQuit(dut.Get().IsAttached()); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 2: Leader Automatically sends Network Data with no BBR Dataset."); + + /** + * Step 2 + * - Device: Leader + * - Description: Automatically sends Network Data with no BBR Dataset. + * - Pass Criteria: N/A + */ + + Log("---------------------------------------------------------------------------------------"); + Log("Step 3: BR_1 (DUT) sends its BBR Dataset to the Leader."); + + /** + * Step 3 + * - Device: BR_1 (DUT) + * - Description: Checks received Network Data and automatically determines that it needs to send its BBR Dataset + * to the Leader to become primary BBR. + * - Pass Criteria: + * - The DUT MUST unicast a SVR_DATA.ntf CoAP notification to Leader. + */ + + Log("---------------------------------------------------------------------------------------"); + Log("Step 4: Leader responds to the Server Data notification."); + + /** + * Step 4 + * - Device: Leader + * - Description: Automatically responds to the Server Data notification. + * - Pass Criteria: N/A + */ + + Log("---------------------------------------------------------------------------------------"); + Log("Step 5: Leader multicasts MLE Data Response message."); + + /** + * Step 5 + * - Device: Leader + * - Description: Automatically multicasts MLE Data Response message with BR_1's short address in the BBR Dataset, + * electing BR_1 as Primary. + * - Pass Criteria: N/A + */ + + nexus.AdvanceTime(kBbrRegistrationTime); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 6: BR_1 (DUT) becomes the Primary BBR."); + + /** + * Step 6 + * - Device: BR_1 (DUT) + * - Description: Automatically acknowledges the presence of its short address in the BBR Dataset and becomes the + * Primary BBR. + * - Pass Criteria: None + */ + + VerifyOrQuit(dut.Get().GetState() == BackboneRouter::Local::kStatePrimary); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 7: Router registers the multicast address, MA1, at BR_1 (DUT)."); + + /** + * Step 7 + * - Device: Router + * - Description: Harness instructs the device to register the multicast address, MA1, at BR_1 (DUT). Unicasts an + * MLR.req CoAP request to BR_1 (DUT). + * - Pass Criteria: N/A + */ + + Ip6::Address ma1; + SuccessOrQuit(ma1.FromString(kMa1Address)); + SuccessOrQuit(router.Get().SubscribeExternalMulticast(ma1)); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 8: BR_1 (DUT) internally registers the multicast address MA1."); + + /** + * Step 8 + * - Device: BR_1 (DUT) + * - Description: Automatically internally registers the multicast address MA1 in its Multicast Listeners Table. + * - Pass Criteria: None. + */ + + Log("---------------------------------------------------------------------------------------"); + Log("Step 9: BR_1 (DUT) responds to the multicast listener registration."); + + /** + * Step 9 + * - Device: BR_1 (DUT) + * - Description: Automatically responds to the multicast listener registration. + * - Pass Criteria: + * - The DUT MUST unicast a MLR.rsp CoAP response to Router. + */ + + nexus.AdvanceTime(kMlrRegistrationTime); + VerifyOrQuit(dut.Get().Has(ma1)); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 10: BR_1 (DUT) multicasts an MLDv2 message."); + + /** + * Step 10 + * - Device: BR_1 (DUT) + * - Description: Automatically multicasts an MLDv2 message to start listening to the group MA1. + * - Pass Criteria: + * - The DUT MUST multicast an MLDv2 message of type "Version 2 Multicast Listener Report". + */ + + Log("---------------------------------------------------------------------------------------"); + Log("Step 11: Host sends ICMPv6 Echo Request packet to destination MA1."); + + /** + * Step 11 + * - Device: Host + * - Description: Harness instructs the device to sends ICMPv6 Echo (ping) Request packet to destination MA1. + * - Pass Criteria: N/A + */ + + const Ip6::Address &hostAddr = host.mInfraIf.FindMatchingAddress("fd00::/8"); + + nexus.AddTestVar("HOST_BACKBONE_ULA", hostAddr.ToString().AsCString()); + + EchoContext echoContext; + echoContext.mIdentifier = kEchoIdentifier; + echoContext.mReceived = false; + host.mInfraIf.SetEchoReplyHandler(HandleEchoResponse, &echoContext); + + host.mInfraIf.SendEchoRequest(hostAddr, ma1, kEchoIdentifier, kEchoPayloadSize, kEchoHopLimit); + + Log("---------------------------------------------------------------------------------------"); + Log("Step 12: BR_1 (DUT) determines that the multicast packet needs to be forwarded."); + + /** + * Step 12 + * - Device: BR_1 (DUT) + * - Description: Internally determines that the multicast packet to MA1 needs to be forwarded. + * - Pass Criteria: None. + */ + + Log("---------------------------------------------------------------------------------------"); + Log("Step 13: BR_1 (DUT) forwards the multicast ping request packet."); + + /** + * Step 13 + * - Device: BR_1 (DUT) + * - Description: Automatically forwards the multicast ping request packet to Router. + * - Pass Criteria: Not explicitly validated in this step. + */ + + Log("---------------------------------------------------------------------------------------"); + Log("Step 14: Router receives ping request packet and responds back to Host."); + + /** + * Step 14 + * - Device: Router + * - Description: Receives ping request packet and automatically responds back to Host via BR_1. + * - Pass Criteria: Receives the ping packet sent by Host. + */ + + nexus.AdvanceTime(kPingResponseTime); + VerifyOrQuit(echoContext.mReceived); + + nexus.SaveTestInfo("test_1_2_BBR_TC_1.json"); +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::Test_1_2_BBR_TC_1(); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/nexus/verify_1_2_BBR_TC_1.py b/tests/nexus/verify_1_2_BBR_TC_1.py new file mode 100644 index 000000000..711b41888 --- /dev/null +++ b/tests/nexus/verify_1_2_BBR_TC_1.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2026, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +import sys +import os + +# Add the current directory to sys.path to find verify_utils +CUR_DIR = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(CUR_DIR) + +import verify_utils +from pktverify import consts +from pktverify.addrs import Ipv6Addr + + +def verify(pv): + # 5.11.1 BBR-TC-01: Device MUST send its BBR dataset to Leader if none exists + # + # 5.11.1.1 Topology + # - BR_1 (DUT): BR device operating as a BBR. + # - Leader: Test bed bed device operating as a Thread Leader. + # - Router: Test bed device operating as a Thread Router. + # - Host: Test bed BR device operating as a non-Thread device. + # + # 5.11.1.2 Purpose and Description + # The purpose of this test case is to verify that if no BBR dataset is present in a network (i.e. not sent around + # by Leader), then a BBR function MUST send its BBR dataset to the Leader. + # + # Spec Reference | V1.2 Section + # ----------------|------------- + # BBR Dataset | 5.21.3 + + pkts = pv.pkts + pv.summary.show() + + LEADER = pv.vars['LEADER'] + BR_1 = pv.vars['BR_1'] + ROUTER = pv.vars['ROUTER'] + BR_1_RLOC16 = pv.vars['BR_1_RLOC16'] + + MA1 = Ipv6Addr("ff04::1234") + + # Step 1: Topology addition - BR_1 (DUT) + # - Description: Topology addition - BR_1 (DUT). The DUT must be booted and configured join the Thread Network. + # - Pass Criteria: + # - The DUT MUST attach to the Thread Network. + print("Step 1: BR_1 (DUT) attaches to the Thread Network.") + pkts.filter_wpan_src64(BR_1).\ + filter_mle_cmd(consts.MLE_CHILD_ID_REQUEST).\ + must_next() + + # Step 2: Leader + # - Description: Automatically sends Network Data with no BBR Dataset. + # - Pass Criteria: N/A + print("Step 2: Leader sends Network Data with no BBR Dataset.") + + # Step 3: BR_1 (DUT) + # - Description: Checks received Network Data and automatically determines that it needs to send its BBR Dataset + # to the Leader to become primary BBR. + # - Pass Criteria: + # - The DUT MUST unicast a SVR_DATA.ntf CoAP notification to Leader. + # - Where the payload contains (amongst other fields): Service TLV: BBR Dataset with T=1, S_service_data = 0x01 + # - Server TLV: BBR Dataset containing BR_1 RLOC + print("Step 3: BR_1 (DUT) sends SVR_DATA.ntf CoAP notification to Leader.") + # We just verify that BR_1 sends a Server Data notification to the Leader. + # The content is verified in Step 5 when the Leader multicasts the new Network Data. + pkts.filter_wpan_src64(BR_1).\ + filter_coap_request(consts.SVR_DATA_URI).\ + must_next() + + # Step 4: Leader + # - Description: Automatically responds to the Server Data notification. + # - Pass Criteria: N/A + print("Step 4: Leader responds to SVR_DATA.ntf.") + pkts.filter_wpan_src64(LEADER).\ + filter_coap_ack(consts.SVR_DATA_URI).\ + must_next() + + # Step 5: Leader + # - Description: Automatically multicasts MLE Data Response message with BR_1's short address in the BBR Dataset, + # electing BR_1 as Primary. + # - Pass Criteria: N/A + print("Step 5: Leader multicasts MLE Data Response electing BR_1 as Primary.") + pkts.filter_wpan_src64(LEADER).\ + filter_LLANMA().\ + filter_mle_cmd(consts.MLE_DATA_RESPONSE).\ + filter(lambda p: consts.NWD_SERVICE_TLV in p.thread_nwd.tlv.type).\ + filter(lambda p: BR_1_RLOC16 in p.thread_nwd.tlv.server_16).\ + must_next() + + # Step 6: BR_1 (DUT) + # - Description: Automatically acknowledges the presence of its short address in the BBR Dataset and becomes the + # Primary BBR. + # - Pass Criteria: None + print("Step 6: BR_1 (DUT) becomes Primary BBR.") + + # Step 7: Router + # - Description: Harness instructs the device to register the multicast address, MA1, at BR_1 (DUT). Unicasts an + # MLR.req CoAP request to BR_1 (DUT). + # - Pass Criteria: N/A + print("Step 7: Router registers MA1 at BR_1 (DUT).") + pkts.filter_wpan_src64(ROUTER).\ + filter_coap_request('/n/mr').\ + filter(lambda p: MA1 in p.coap.tlv.ipv6_address).\ + must_next() + + # Step 8: BR_1 (DUT) + # - Description: Automatically internally registers the multicast address MA1 in its Multicast Listeners Table. + # - Pass Criteria: None. + print("Step 8: BR_1 (DUT) registers MA1 internally.") + + # Step 9: BR_1 (DUT) + # - Description: Automatically responds to the multicast listener registration. + # - Pass Criteria: + # - The DUT MUST unicast a MLR.rsp CoAP response to Router. + print("Step 9: BR_1 (DUT) responds with MLR.rsp.") + pkts.filter_wpan_src64(BR_1).\ + filter_coap_ack('/n/mr').\ + filter(lambda p: p.coap.tlv.status == 0).\ + must_next() + + # Step 10: BR_1 (DUT) + # - Description: Automatically multicasts an MLDv2 message to start listening to the group MA1. + # - Pass Criteria: + # - The DUT MUST multicast an MLDv2 message of type "Version 2 Multicast Listener Report". + # + # Note: OpenThread does not currently implement sending MLDv2 messages in this simulation environment. + print("Step 10: BR_1 (DUT) multicasts MLDv2 message (SKIPPED).") + + # Step 11: Host + # - Description: Harness instructs the device to sends ICMPv6 Echo (ping) Request packet to destination MA1. + # - Pass Criteria: N/A + print("Step 11: Host sends ICMPv6 Echo Request to MA1.") + pkts.filter_eth_src(pv.vars['HOST_ETH']).\ + filter_ipv6_dst(MA1).\ + filter_ping_request().\ + must_next() + + # Step 12: BR_1 (DUT) + # - Description: Internally determines that the multicast packet to MA1 needs to be forwarded. + # - Pass Criteria: None. + print("Step 12: BR_1 (DUT) determines forwarding needed.") + + # Step 13: BR_1 (DUT) + # - Description: Automatically forwards the multicast ping request packet to Router. + # - Pass Criteria: Not explicitly validated in this step. + print("Step 13: BR_1 (DUT) forwards the multicast ping.") + pkts.filter_wpan_src64(BR_1).\ + filter_ping_request().\ + filter(lambda p: p.ipv6.dst == 'ff03::fc' or p.ipv6.dst == MA1).\ + must_next() + + # Step 14: Router receives ping request packet and automatically responds back to Host via BR_1. + # - Pass Criteria: Receives the ping packet sent by Host. + print("Step 14: Router receives ping and responds.") + pkts.filter_eth_src(pv.vars['BR_1_ETH']).\ + filter(lambda p: p.ipv6.dst == Ipv6Addr(pv.vars['HOST_BACKBONE_ULA'])).\ + filter_ping_reply().\ + must_next() + + +if __name__ == '__main__': + verify_utils.run_main(verify)