diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 2d6b1f6e1..173d2c281 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -252,6 +252,7 @@ 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") +ot_nexus_test(1_2_BBR_TC_2 "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 f5f73c6b8..bfbf7e3ff 100755 --- a/tests/nexus/run_nexus_tests.sh +++ b/tests/nexus/run_nexus_tests.sh @@ -187,6 +187,7 @@ DEFAULT_TESTS=( "1_2_MATN_TC_23" "1_2_MATN_TC_26" "1_2_BBR_TC_1" + "1_2_BBR_TC_2" ) # Use provided arguments or the default test list diff --git a/tests/nexus/test_1_2_BBR_TC_2.cpp b/tests/nexus/test_1_2_BBR_TC_2.cpp new file mode 100644 index 000000000..2a94ca527 --- /dev/null +++ b/tests/nexus/test_1_2_BBR_TC_2.cpp @@ -0,0 +1,376 @@ +/* + * 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 kAttachToRouterTime = 200 * 1000; + +/** + * Time to advance for the network to stabilize after routers have attached. + */ +static constexpr uint32_t kStabilizationTime = 10 * 1000; + +/** + * Partition weight for Router_1 to ensure it becomes leader. + */ +static constexpr uint8_t kRouter1PartitionWeight = 74; + +/** + * BBR Sequence Number used by BR_2 to trigger role switch in DUT. + */ +static constexpr uint8_t kHighBbrSequenceNumber = 255; + +/** + * Multicast address MA1 for MLR.req test. + */ +static constexpr char kMa1Address[] = "ff04::1234:777a:1"; + +void Test_1_2_BBR_TC_2(void) +{ + /** + * 5.11.2 BBR-TC-02: BBR MUST remove its BBR Dataset from Network Data + * + * 5.11.2.1 Topology + * - BR_1 (DUT) + * - BR_2 + * - Router_1 + * + * 5.11.2.2 Purpose & Description + * The purpose of this test case is to verify that if two BBR Datasets are present in a network, the DUT BR not + * being elected as Primary will delete its own BBR Dataset from the Network Data according to the rules of + * [ThreadSpec] Section 5.10.3.6, which are referred from Section 5.21.4.3. Note: in the present test procedure, + * only a subset of Section 5.10.3.6/5.21.4.3 rules is checked; other checks should be added in the future. + * + * Spec Reference | V1.2 Section | V1.3.0 Section + * -----------------|--------------|--------------- + * BBR Role Switch | 5.10.3.6 | 5.21.4.3 + */ + + Core nexus; + + Node &br1 = nexus.CreateNode(); + Node &br2 = nexus.CreateNode(); + Node &router1 = nexus.CreateNode(); + + br1.SetName("BR_1"); + br2.SetName("BR_2"); + router1.SetName("Router_1"); + + nexus.AdvanceTime(0); + + Instance::SetLogLevel(kLogLevelNote); + + /** + * Step 0 + * - Device: N/A + * - Description: Topology formation - Router_1 (Leader). Topology addition - BR_1 (DUT). The DUT must be booted + * and joined to the test network + * - Pass Criteria: + * - N/A + */ + Log("Step 0: Topology formation"); + + /** P1: Router_1 is configured with a Partition Weight 74 to force it to become Leader and stay Leader. */ + router1.Get().SetLeaderWeight(kRouter1PartitionWeight); + + router1.Form(); + nexus.AdvanceTime(kFormNetworkTime); + + br1.Join(router1); + nexus.AdvanceTime(kAttachToRouterTime); + + /** + * Step 1 + * - Device: BR_1 (DUT) + * - Description: Automatically receives Network Data and determines to send its BBR Dataset to the Leader. + * - Pass Criteria: + * - The DUT MUST unicast SVR_DATA.ntf to the Leader as follows: coap://[]:MM/a/sd + * - Where the payload contains inside the Thread Network Data TLV: Service TLV: BBR Dataset with + * - T=1 + * - S_service_data Length = 1 + * - S_service_data = 0x01 (THREAD_SERVICE_DATA_BBR) + * - One Server TLV as sub-TLV formatted per 5.21.3.2 [ThreadSpec]. + */ + Log("Step 1: BR_1 (DUT) sends its BBR Dataset to the Leader"); + + br1.Get().SetEnabled(true); + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 2 + * - Device: Router_1 + * - Description: Automatically responds to the Server Data notification as follows: 2.04 changed. Also it MUST be + * verified here (or alternatively at the end of the test) that the received BBR Dataset contains: BBR Sequence + * Number < 127 + * - Pass Criteria: + * - The Received BBR Dataset MUST contain: BBR Sequence Number < 127 + */ + Log("Step 2: Router_1 responds to the Server Data notification"); + + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 3 + * - Device: BR_2 + * - Description: Topology addition - BR_2. Harness instructs the device to attach to the network. It automatically + * receives the Network Data, detects that there is already a BBR Dataset, and does not send its own BBR Dataset + * to Leader. + * - Pass Criteria: + * - N/A + */ + Log("Step 3: Topology addition - BR_2"); + + br2.Join(router1); + nexus.AdvanceTime(kAttachToRouterTime); + br2.Get().SetEnabled(true); + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 4 + * - Device: BR_2 + * - Description: Harness instructs the device to set the BBR Sequence Number to 255. [Harness instructs the device + * to send a Coap message formatted like SVR_DATA.ntf to Leader with BBR Dataset with highest possible BBR + * Sequence Number (255). SVR_DATA.ntf contains a Thread Network Data TLV, with inside: Service TLV: BBR Dataset + * with + * - T=1 + * - S_service_data Length = 1 + * - S_service_data = 0x01 (THREAD_SERVICE_DATA_BBR) + * - One Server TLV as sub-TLV formatted per 5.21.3.2 [ThreadSpec] and values as specified below. In above Server + * TLV, + * - BBR Sequence Number is 255 + * - Reregistration Delay is 5 + * - MLR Timeout is 3600] + * - Pass Criteria: + * - N/A + */ + Log("Step 4: BR_2 sets the BBR Sequence Number to 255"); + + { + BackboneRouter::Config config; + br2.Get().GetConfig(config); + config.mSequenceNumber = kHighBbrSequenceNumber; + SuccessOrQuit(br2.Get().SetConfig(config)); + SuccessOrQuit(br2.Get().AddService(BackboneRouter::Local::kForceRegistration)); + } + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 5 + * - Device: Router_1 + * - Description: Automatically distributes new Network Data including the "fake" BBR Dataset of BR_2 that was + * submitted in the previous step. This means the Network Data now contains 2 BBR Datasets. + * - Pass Criteria: + * - N/A + */ + Log("Step 5: Router_1 distributes new Network Data"); + + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 6 + * - Device: BR_1 (DUT) + * - Description: Receives new Network Data, automatically detects that there are two BBR Datasets; and that its + * own BBR Sequence Number is lower than the other's i.e. < 255. Automatically switches from Primary to Secondary + * BBR role and sends SVR_DATA.ntf to the Leader, this time excluding its BBR Dataset, in order to delete its BBR + * Dataset at the Leader. + * - Pass Criteria: + * - The DUT MUST unicast SVR_DATA.ntf to Leader as follows: coap://[]:MM/a/sd + * - The Thread Network Data TLV MUST NOT contain the following: Service TLV: BBR Dataset + * - T=1 + * - S_service_data Length = 1 + * - S_service_data = 0x01 (THREAD_SERVICE_DATA_BBR) + */ + Log("Step 6: BR_1 (DUT) switches to Secondary BBR role and deletes its BBR Dataset"); + + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 6b + * - Device: BR_2 + * - Description: Receives new Network Data, detects there there are two BBR Datasets; and that its own BBR Dataset + * is included with BBR Sequence Number higher than the other's (i.e. it is 255). It then automatically switches + * from Secondary to Primary BBR role. Informational: the BBR Sequence Number of BR_2 will remain 255 after the + * role switch. + * - Pass Criteria: + * - N/A + */ + Log("Step 6b: BR_2 switches to Primary BBR role"); + + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 7 + * - Device: Router_1 + * - Description: Receives the SVR_DATA.ntf and automatically responds 2.04 Changed. Automatically distributes the + * new Network Data with only one BBR Dataset (the one of BR_2). + * - Pass Criteria: + * - N/A + */ + Log("Step 7: Router_1 distributes new Network Data with only BR_2's BBR Dataset"); + + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 8 + * - Device: BR_1 (DUT) + * - Description: Receives the new Network Data and automatically determines not to reregister any new BBR Dataset. + * It remains in Secondary BBR role. + * - Pass Criteria: + * - The DUT MAY send SVR_DATA.ntf + * - In SVR_DATA.ntf, the Thread Network Data TLV MUST NOT contain the following (as in step 6): Service TLV: BBR + * Dataset + * - T=1 + * - S_service_data Length = 1 + * - S_service_data = 0x01 (THREAD_SERVICE_DATA_BBR) + */ + Log("Step 8: BR_1 (DUT) remains in Secondary BBR role"); + + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 9 + * - Device: BR_2 + * - Description: Receives the new Network Data and remains in Primary BBR role. + * - Pass Criteria: + * - N/A + */ + Log("Step 9: BR_2 remains in Primary BBR role"); + + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 10 + * - Device: Router_1 + * - Description: Harness instructs the device to send a MLR.req message to register for multicast group MA1 to the + * Secondary BBR, BR_1 (the DUT). + * - Pass Criteria: + * - N/A + */ + Log("Step 10: Router_1 sends MLR.req to BR_1 (DUT)"); + + { + Ip6::Address ma1; + Coap::Message *message; + Ip6AddressesTlv addressesTlv; + Ip6::Address destAddr; + + SuccessOrQuit(ma1.FromString(kMa1Address)); + + message = router1.Get().AllocateAndInitConfirmablePostMessage(kUriMlr); + VerifyOrQuit(message != nullptr); + + addressesTlv.Init(); + addressesTlv.SetLength(sizeof(Ip6::Address)); + SuccessOrQuit(message->Append(addressesTlv)); + SuccessOrQuit(message->Append(ma1)); + + destAddr = br1.Get().GetMeshLocalEid(); + SuccessOrQuit(router1.Get().SendMessageTo(*message, destAddr, nullptr, nullptr)); + } + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 11 + * - Device: BR_1 (DUT) + * - Description: Automatically responds with an error to the MLR.req, since it is not Primary BBR anymore. + * - Pass Criteria: + * - unicast an MLR.rsp CoAP response to Router_1 as follows: 2.04 changed + * - Where the payload contains: Status TLV: 5 [ST_MLR_BBR_NOT_PRIMARY] + */ + Log("Step 11: BR_1 (DUT) responds with an error to the MLR.req"); + + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 12 + * - Device: BR_2 + * - Description: Harness instructs the device to stop being a Router, so that in the coming steps it cannot + * assume the Leader role. Note: in OT CLI this can be done by: routereligible disable + * - Pass Criteria: + * - N/A + */ + Log("Step 12: BR_2 stops being a Router"); + + SuccessOrQuit(br2.Get().SetRouterEligible(false)); + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 13 + * - Device: Router_1 + * - Description: Harness instructs the device to stop being a Leader. Note: in OT CLI this can be done by: + * routereligible disable + * - Pass Criteria: + * - N/A + */ + Log("Step 13: Router_1 stops being a Leader"); + + SuccessOrQuit(router1.Get().SetRouterEligible(false)); + nexus.AdvanceTime(kStabilizationTime); + + /** + * Step 14 + * - Device: BR_1 (DUT) + * - Description: After some time, automatically becomes the Leader and Primary BBR. + * - Pass Criteria: + * - MUST become the Leader of the network. + * - MUST be Primary BBR. This can be verified by checking that the network data in Router_1 contains the BBR + * Dataset of DUT. The OT CLI command "bbr" on Router_1 can also be used for verification. + */ + Log("Step 14: BR_1 (DUT) becomes Leader and Primary BBR"); + + nexus.AdvanceTime(kAttachToRouterTime); + + VerifyOrQuit(br1.Get().IsLeader()); + VerifyOrQuit(br1.Get().IsPrimary()); + + nexus.SaveTestInfo("test_1_2_BBR_TC_2.json"); +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::Test_1_2_BBR_TC_2(); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/nexus/verify_1_2_BBR_TC_2.py b/tests/nexus/verify_1_2_BBR_TC_2.py new file mode 100644 index 000000000..0239b613c --- /dev/null +++ b/tests/nexus/verify_1_2_BBR_TC_2.py @@ -0,0 +1,273 @@ +#!/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, errors + +MLR_URI = '/n/mr' +ST_MLR_BBR_NOT_PRIMARY = 5 + + +def verify(pv): + # 5.11.2 BBR-TC-02: BBR MUST remove its BBR Dataset from Network Data + # + # 5.11.2.1 Topology + # - BR_1 (DUT) + # - BR_2 + # - Router_1 + # + # 5.11.2.2 Purpose & Description + # The purpose of this test case is to verify that if two BBR Datasets are present in a network, the DUT BR not + # being elected as Primary will delete its own BBR Dataset from the Network Data according to the rules of + # [ThreadSpec] Section 5.10.3.6, which are referred from Section 5.21.4.3. Note: in the present test procedure, + # only a subset of Section 5.10.3.6/5.21.4.3 rules is checked; other checks should be added in the future. + # + # Spec Reference | V1.2 Section | V1.3.0 Section + # -----------------|--------------|--------------- + # BBR Role Switch | 5.10.3.6 | 5.21.4.3 + + pkts = pv.pkts + pv.summary.show() + + BR_1 = pv.vars['BR_1'] + BR_2 = pv.vars['BR_2'] + ROUTER_1 = pv.vars['Router_1'] + + # Step 0 + # - Device: N/A + # - Description: Topology formation - Router_1 (Leader). Topology addition - BR_1 (DUT). The DUT must be booted + # and joined to the test network + # - Pass Criteria: + # - N/A + print("Step 0: Topology formation") + + # Step 1 + # - Device: BR_1 (DUT) + # - Description: Automatically receives Network Data and determines to send its BBR Dataset to the Leader. + # - Pass Criteria: + # - The DUT MUST unicast SVR_DATA.ntf to the Leader as follows: coap://[]:MM/a/sd + # - Where the payload contains inside the Thread Network Data TLV: Service TLV: BBR Dataset with + # - T=1 + # - S_service_data Length = 1 + # - S_service_data = 0x01 (THREAD_SERVICE_DATA_BBR) + # - One Server TLV as sub-TLV formatted per 5.21.3.2 [ThreadSpec]. + print("Step 1: BR_1 (DUT) sends its BBR Dataset to the Leader") + pkts.filter_wpan_src64(BR_1).\ + filter_coap_request(consts.SVR_DATA_URI).\ + filter(lambda p: consts.NL_THREAD_NETWORK_DATA_TLV in p.coap.tlv.type).\ + must_next().\ + must_verify("coap.tlv.bbr_seqno < 127") + + # Step 2 + # - Device: Router_1 + # - Description: Automatically responds to the Server Data notification as follows: 2.04 changed. Also it MUST be + # verified here (or alternatively at the end of the test) that the received BBR Dataset contains: BBR Sequence + # Number < 127 + # - Pass Criteria: + # - The Received BBR Dataset MUST contain: BBR Sequence Number < 127 + print("Step 2: Router_1 responds to the Server Data notification") + pkts.filter_wpan_src64(ROUTER_1).\ + filter_coap_ack(consts.SVR_DATA_URI).\ + must_next() + + # Step 3 + # - Device: BR_2 + # - Description: Topology addition - BR_2. Harness instructs the device to attach to the network. It automatically + # receives the Network Data, detects that there is already a BBR Dataset, and does not send its own BBR Dataset + # to Leader. + # - Pass Criteria: + # - N/A + print("Step 3: Topology addition - BR_2") + + # Step 4 + # - Device: BR_2 + # - Description: Harness instructs the device to set the BBR Sequence Number to 255. [Harness instructs the device + # to send a Coap message formatted like SVR_DATA.ntf to Leader with BBR Dataset with highest possible BBR + # Sequence Number (255). SVR_DATA.ntf contains a Thread Network Data TLV, with inside: Service TLV: BBR Dataset + # with + # - T=1 + # - S_service_data Length = 1 + # - S_service_data = 0x01 (THREAD_SERVICE_DATA_BBR) + # - One Server TLV as sub-TLV formatted per 5.21.3.2 [ThreadSpec] and values as specified below. In above Server + # TLV, + # - BBR Sequence Number is 255 + # - Reregistration Delay is 5 + # - MLR Timeout is 3600] + # - Pass Criteria: + # - N/A + print("Step 4: BR_2 sets the BBR Sequence Number to 255") + pkts.filter_wpan_src64(BR_2).\ + filter_coap_request(consts.SVR_DATA_URI).\ + filter(lambda p: consts.NL_THREAD_NETWORK_DATA_TLV in p.coap.tlv.type).\ + must_next().\ + must_verify("coap.tlv.bbr_seqno == 255") + + # Step 5 + # - Device: Router_1 + # - Description: Automatically distributes new Network Data including the "fake" BBR Dataset of BR_2 that was + # submitted in the previous step. This means the Network Data now contains 2 BBR Datasets. + # - Pass Criteria: + # - N/A + print("Step 5: Router_1 distributes new Network Data") + + # Step 6 + # - Device: BR_1 (DUT) + # - Description: Receives new Network Data, automatically detects that there are two BBR Datasets; and that its own + # BBR Sequence Number is lower than the other's i.e. < 255. Automatically switches from Primary to Secondary BBR + # role and sends SVR_DATA.ntf to the Leader, this time excluding its BBR Dataset, in order to delete its BBR + # Dataset at the Leader. + # - Pass Criteria: + # - The DUT MUST unicast SVR_DATA.ntf to Leader as follows: coap://[]:MM/a/sd + # - The Thread Network Data TLV MUST NOT contain the following: Service TLV: BBR Dataset + # - T=1 + # - S_service_data Length = 1 + # - S_service_data = 0x01 (THREAD_SERVICE_DATA_BBR) + print("Step 6: BR_1 (DUT) switches to Secondary BBR role and deletes its BBR Dataset") + pkts.filter_wpan_src64(BR_1).\ + filter_coap_request(consts.SVR_DATA_URI).\ + filter(lambda p: consts.NL_THREAD_NETWORK_DATA_TLV in p.coap.tlv.type).\ + must_next().\ + must_not_verify("coap.tlv.bbr_seqno is not null") + + # Step 6b + # - Device: BR_2 + # - Description: Receives new Network Data, detects there there are two BBR Datasets; and that its own BBR Dataset + # is included with BBR Sequence Number higher than the other's (i.e. it is 255). It then automatically switches + # from Secondary to Primary BBR role. Informational: the BBR Sequence Number of BR_2 will remain 255 after the + # role switch. + # - Pass Criteria: + # - N/A + print("Step 6b: BR_2 switches to Primary BBR role") + + # Step 7 + # - Device: Router_1 + # - Description: Receives the SVR_DATA.ntf and automatically responds 2.04 Changed. Automatically distributes the + # new Network Data with only one BBR Dataset (the one of BR_2). + # - Pass Criteria: + # - N/A + print("Step 7: Router_1 distributes new Network Data with only BR_2's BBR Dataset") + + # Step 8 + # - Device: BR_1 (DUT) + # - Description: Receives the new Network Data and automatically determines not to reregister any new BBR Dataset. + # It remains in Secondary BBR role. + # - Pass Criteria: + # - The DUT MAY send SVR_DATA.ntf + # - In SVR_DATA.ntf, the Thread Network Data TLV MUST NOT contain the following (as in step 6): Service TLV: BBR + # Dataset + # - T=1 + # - S_service_data Length = 1 + # - S_service_data = 0x01 (THREAD_SERVICE_DATA_BBR) + print("Step 8: BR_1 (DUT) remains in Secondary BBR role") + # If BR_1 sends SVR_DATA.ntf, it must not contain its BBR dataset. + with pkts.save_index(): + p8 = pkts.filter_wpan_src64(BR_1).\ + filter_coap_request(consts.SVR_DATA_URI).\ + filter(lambda p: consts.NL_THREAD_NETWORK_DATA_TLV in p.coap.tlv.type).\ + next() + if p8: + p8.must_not_verify("coap.tlv.bbr_seqno is not null") + + # Step 9 + # - Device: BR_2 + # - Description: Receives the new Network Data and remains in Primary BBR role. + # - Pass Criteria: + # - N/A + print("Step 9: BR_2 remains in Primary BBR role") + + # Step 10 + # - Device: Router_1 + # - Description: Harness instructs the device to send a MLR.req message to register for multicast group MA1 to the + # Secondary BBR, BR_1 (the DUT). + # - Pass Criteria: + # - N/A + print("Step 10: Router_1 sends MLR.req to BR_1 (DUT)") + pkts.filter_wpan_src64(ROUTER_1).\ + filter_coap_request(MLR_URI).\ + must_next() + + # Step 11 + # - Device: BR_1 (DUT) + # - Description: Automatically responds with an error to the MLR.req, since it is not Primary BBR anymore. + # - Pass Criteria: + # - unicast an MLR.rsp CoAP response to Router_1 as follows: 2.04 changed + # - Where the payload contains: Status TLV: 5 [ST_MLR_BBR_NOT_PRIMARY] + print("Step 11: BR_1 (DUT) responds with an error to the MLR.req") + # Skip checks for MLDv2 and BLMR.ntf as requested. + pkts.filter_wpan_src64(BR_1).\ + filter_coap_ack(MLR_URI).\ + filter(lambda p: p.coap.tlv.status == ST_MLR_BBR_NOT_PRIMARY).\ + must_next() + + # Step 12 + # - Device: BR_2 + # - Description: Harness instructs the device to stop being a Router, so that in the coming steps it cannot + # assume the Leader role. Note: in OT CLI this can be done by: routereligible disable + # - Pass Criteria: + # - N/A + print("Step 12: BR_2 stops being a Router") + + # Step 13 + # - Device: Router_1 + # - Description: Harness instructs the device to stop being a Leader. Note: in OT CLI this can be done by: + # routereligible disable + # - Pass Criteria: + # - N/A + print("Step 13: Router_1 stops being a Leader") + + # Step 14 + # - Device: BR_1 (DUT) + # - Description: After some time, automatically becomes the Leader and Primary BBR. + # - Pass Criteria: + # - MUST become the Leader of the network. + # - MUST be Primary BBR. This can be verified by checking that the network data in Router_1 contains the BBR + # Dataset of DUT. The OT CLI command "bbr" on Router_1 can also be used for verification. + print("Step 14: BR_1 (DUT) becomes Leader and Primary BBR") + # Verify BR_1 becomes Leader + pkts.filter_wpan_src64(BR_1).\ + filter_mle_advertisement('Leader').\ + must_next() + + # Verify BR_1 distributes its BBR Dataset in Network Data + pkts.filter_wpan_src64(BR_1).\ + filter_mle_cmd(consts.MLE_DATA_RESPONSE).\ + filter(lambda p: consts.NETWORK_DATA_TLV in p.mle.tlv.type).\ + filter_has_bbr_dataset().\ + 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 29ead99ed..469243617 100644 --- a/tests/nexus/verify_utils.py +++ b/tests/nexus/verify_utils.py @@ -156,6 +156,40 @@ def thread_coap_tlv_parse(t, v, layer=None): elif t == consts.NM_FUTURE_TLV: kvs.append(('future_tlv', v)) + # Network Data TLV (often in SVR_DATA.ntf) + elif t == consts.NL_THREAD_NETWORK_DATA_TLV: + # Value is Network Data + # We only care about Service TLV (Type 5) -> Server TLV (Type 6) + # In Network Data, TLV type is 5 bits + 1 bit Stable flag. + pos = 0 + while pos + 2 <= len(v): + nwd_t = v[pos] + nwd_l = v[pos + 1] + nwd_v = v[pos + 2:pos + 2 + nwd_l] + if pos + 2 + nwd_l > len(v): + break + if (nwd_t >> 1) == consts.NWD_SERVICE_TLV: # Service TLV + if len(nwd_v) >= 2: + # nwd_v[0] is T bit (1) and Service ID (7 bits) + s_data_len = nwd_v[1] + if len(nwd_v) >= 2 + s_data_len: + s_data = nwd_v[2:2 + s_data_len] + if s_data_len >= 1 and s_data[0] == consts.THREAD_SERVICE_DATA_BBR: # THREAD_SERVICE_DATA_BBR + # Sub-TLVs start after S_data + sub_pos = 2 + s_data_len + while sub_pos + 2 <= len(nwd_v): + sub_t = nwd_v[sub_pos] + sub_l = nwd_v[sub_pos + 1] + sub_v = nwd_v[sub_pos + 2:sub_pos + 2 + sub_l] + if sub_pos + 2 + sub_l > len(nwd_v): + break + if (sub_t >> 1) == consts.NWD_SERVER_TLV: # Server TLV + # sub_v is [RLOC16(2), SeqNo(1), ReregDelay(2), MLRTimeout(4)] + if len(sub_v) >= 3: + kvs.append(('bbr_seqno', sub_v[2])) + sub_pos += 2 + sub_l + pos += 2 + nwd_l + # Other Thread TLVs elif t == consts.NL_TARGET_EID_TLV and len(v) == 16: kvs.append(('target_eid', str(Ipv6Addr(v)))) @@ -366,6 +400,7 @@ def apply_patches(): return t, kvs, val_pos + len_ coap.CoapLayer._parse_next_tlv = staticmethod(_parse_next_tlv_patched) + layer_fields._LAYER_FIELDS['coap.tlv.bbr_seqno'] = layer_fields._auto layer_fields._LAYER_FIELDS['thread_meshcop.tlv.delay_timer'] = layer_fields._auto layer_fields._LAYER_FIELDS['mle.tlv.link_query_options'] = layer_fields._bytes diff --git a/tests/scripts/thread-cert/pktverify/consts.py b/tests/scripts/thread-cert/pktverify/consts.py index 71cdbe3ef..06510f5b0 100644 --- a/tests/scripts/thread-cert/pktverify/consts.py +++ b/tests/scripts/thread-cert/pktverify/consts.py @@ -65,6 +65,8 @@ PBBR_ALOC = Ipv6Addr(DEFAULT_MESH_LOCAL_PREFIX + PBBR_ALOC_IID) # Minimum value of the MLR Timeout parameter in the BBR Dataset MLR_TIMEOUT_MIN = 300 +THREAD_SERVICE_DATA_BBR = 0x01 + # Max response delay MLE_MAX_RESPONSE_DELAY = 1