From 04963aae2c3b3f7a1553db22d36cf1fc2279a4d3 Mon Sep 17 00:00:00 2001 From: juandediosg Date: Mon, 13 Jan 2025 10:12:13 -0600 Subject: [PATCH 01/25] Implement partial solution up to step 5 --- src/python_testing/TC_CGEN_2_2.py | 173 ++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 src/python_testing/TC_CGEN_2_2.py diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py new file mode 100644 index 00000000000000..5d4358a5837ade --- /dev/null +++ b/src/python_testing/TC_CGEN_2_2.py @@ -0,0 +1,173 @@ +# +# Copyright (c) 2022 Project CHIP Authors +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# See https://github.com/project-chip/connectedhomeip/blob/master/docs/testing/python.md#defining-the-ci-test-arguments +# for details about the block below. +# +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: +# run1: +# app: ${ALL_CLUSTERS_APP} +# app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# script-args: > +# --storage-path admin_storage.json +# --commissioning-method on-network +# --discriminator 1234 +# --passcode 20202021 +# --trace-to json:${TRACE_TEST_JSON}.json +# --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# factory-reset: true +# quiet: true +# === END CI TEST ARGUMENTS === + +import chip.clusters as Clusters +from chip.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from chip.utils import CommissioningBuildingBlocks +from mobly import asserts +import logging +import random +import binascii +import gc + +# Create a logger +logger = logging.getLogger(__name__) + + +class TC_CGEN_2_2(MatterBaseTest): + def desc_TC_CGEN_2_2(self) -> str: + return "[TC-CGEN-2.2] ArmFailSafe command verification [DUT - Server]" + + def pics_TC_CGEN_2_2(self): + """Return the PICS definitions associated with this test.""" + pics = [ + "CGEN.S", # Pics + ] + return pics + + def steps_TC_CGEN_2_2(self) -> list[TestStep]: + steps = [ + TestStep(0, "Commissioning, already done", is_commissioning=True), + TestStep(1, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster + and saves the number of list items as numTrustedRootsOriginal"""), + TestStep(2, """TH1 reads the BasicCommissioningInfo attribute + and saves the MaxCumulativeFailsafeSeconds as maxFailsafe"""), + TestStep(3, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds + and the Breadcrumb value as 1"""), + TestStep(4, "TH1 reads the Breadcrumb attributee"), + TestStep(5, """TH1 sends AddTrustedRootCertificate command to DUT with RootCACertificate set to Root_CA_Certificate_TH1 + verify SUCCESS"""), + # TestStep(6, "TH..."), + # TestStep(7, "TH..."), + # TestStep(8, "TH..."), + # TestStep(9, "TH..."), + # TestStep(10, "TH..."), + # TestStep(11, "TH..."), + ] + return steps + + @async_test_body + async def test_TC_CGEN_2_2(self): + cluster_opcreds = Clusters.OperationalCredentials + cluster_cgen = Clusters.GeneralCommissioning + + self.step(0) + + # Read the Spteps + self.step(1) + trusted_root_list_original = await self.read_single_attribute_check_success( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cluster=cluster_opcreds, + attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + trusted_root_list_original_size = len(trusted_root_list_original) + logger.info(f'The trusted_root_list is {trusted_root_list_original}') + logger.info(f'The size of the trusted_root_list is {trusted_root_list_original_size}') + # asserts.assert_equal(len(trusted_root_list_original), 1, + # "Unexpected number of entries in the TrustedRootCertificates table") + + self.step(2) + basic_commissioning_info = await self.read_single_attribute_check_success( + cluster=cluster_cgen, + attribute=cluster_cgen.Attributes.BasicCommissioningInfo) + maxFailsafe = basic_commissioning_info.maxCumulativeFailsafeSeconds + logger.info(f'The MaxCumulativeFailsafeSeconds is {maxFailsafe}') + + self.step(3) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe, breadcrumb=1) + resp = await self.send_single_cmd( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cmd=cmd) + logger.info(f'response attributesare: {vars(resp)}') + + # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "Failure status returned from arm failsafe") + + # Verify that DebugText is empty or has a maximum length of 512 characters + debug_text = resp.debugText + assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" + + self.step(4) + breadcrumb_info = await self.read_single_attribute_check_success( + cluster=cluster_cgen, + attribute=cluster_cgen.Attributes.Breadcrumb) + logger.info(f'The breadcrumb_attribute is {breadcrumb_info}') + + self.step(5) + + root_cert_hex = "1530010828c376ebc17f21512402013703271401000000cacacaca182604ef171b2726056eb5b94c3706271401000000cacacaca182407012408013009410452c19fd9d329a738fd65722a8309fa68bcaa9ffe87d8114b802c922e5066d0b2f0573b89b38bf98fc9c424ab8ffdabcb18d42e623d82a02d0ca0c062ccadb4bc370a350129011824026030041457934de5405e9a40eacb86ee647e583141ae78f430051457934de5405e9a40eacb86ee647e583141ae78f418300b40a0b0d57bddbc7bcf44480a8b7bd0231d54ccacd68d90efb67b7aa3206adbd268725092992a0388c8e934504178613c5b932d422eed7463f38fd82aaa429b574a18" + bin_root_cert = binascii.unhexlify(root_cert_hex) + + # 1 Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) + cmd = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=True) + csr_update = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) + + # 2 Create new certificate authority for TH1 + th1_ca_new = self.certificate_authority_manager.NewCertificateAuthority(maximizeCertChains=True) + th1_fabric_admin_new = th1_ca_new.NewFabricAdmin(vendorId=0xFFF2, fabricId=2) + + # 3 Create the new controller + th1_new = th1_fabric_admin_new.NewController(nodeId=self.default_controller.nodeId+1) + + # 4 Isue the certificates + th1_certs_new = await th1_new.IssueNOCChain(csr_update, self.dut_node_id+1) + new_root_cert = th1_certs_new.rcacBytes + logger.info(f"RCAC Certificate: {new_root_cert}") + noc_update_new_root = th1_certs_new.nocBytes + logger.info(f"NOC Certificate: {noc_update_new_root}") + icac_update_new_root = th1_certs_new.icacBytes + logger.info(f"ICAC Certificate: {icac_update_new_root}") + + # 5 Send command to add new trusted root certificate (in binary format) + cmd = cluster_opcreds.Commands.AddTrustedRootCertificate(new_root_cert) + resp = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) + logger.info(f'AddTrustedRootCertificate command response: {resp}') + + # Verify that the response does not have an error + asserts.assert_equal(resp.errorCode, Clusters.OperationalCredentials.Enums.OperationalCertStatusEnum.kSuccess, + "Failed to add trusted root certificate") + logger.info("Root certificate added successfull") + + + # self.step(7) + # self.step(8) + # self.step(9) + # self.step(10) + # self.step(11) +if __name__ == "__main__": + default_matter_test_main() From 5a6105fac5d7f17e7eca22f5b10b098e4ee43074 Mon Sep 17 00:00:00 2001 From: juandediosg Date: Wed, 22 Jan 2025 12:59:34 -0600 Subject: [PATCH 02/25] Draft: Work in progress until step 22 --- src/python_testing/TC_CGEN_2_2.py | 422 +++++++++++++++++++++++++++--- 1 file changed, 379 insertions(+), 43 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index 5d4358a5837ade..84cfb5f6e0c26f 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -35,19 +35,56 @@ # === END CI TEST ARGUMENTS === import chip.clusters as Clusters +from chip import ChipDeviceCtrl +from chip.exceptions import ChipStackError +import chip.discovery as Discovery from chip.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main from chip.utils import CommissioningBuildingBlocks from mobly import asserts import logging import random -import binascii -import gc +import asyncio +import time +from chip.exceptions import ChipStackError +from chip.native import PyChipError -# Create a logger +# Create logger logger = logging.getLogger(__name__) class TC_CGEN_2_2(MatterBaseTest): + async def FindAndEstablishPase(self, longDiscriminator: int, setupPinCode: int, nodeid: int, dev_ctrl: ChipDeviceCtrl = None): + if dev_ctrl is None: + dev_ctrl = self.default_controller + + devices = await dev_ctrl.DiscoverCommissionableNodes( + filterType=Discovery.FilterType.LONG_DISCRIMINATOR, filter=longDiscriminator, stopOnFirst=False) + # For some reason, the devices returned here aren't filtered, so filter ourselves + device = next(filter(lambda d: d.commissioningMode == + Discovery.FilterType.LONG_DISCRIMINATOR and d.longDiscriminator == longDiscriminator, devices)) + for a in device.addresses: + try: + await dev_ctrl.EstablishPASESessionIP(ipaddr=a, setupPinCode=setupPinCode, + nodeid=nodeid, port=device.port) + break + except ChipStackError: + continue + try: + dev_ctrl.GetConnectedDeviceSync(nodeid=nodeid, allowPASE=True, timeoutMs=1000) + except TimeoutError: + asserts.fail("Unable to establish a PASE session to the device") + + async def OpenCommissioningWindow(self, dev_ctrl: ChipDeviceCtrl, node_id: int): + # TODO: abstract this in the base layer? Do we do this a lot? + longDiscriminator = random.randint(0, 4095) + try: + params = await dev_ctrl.OpenCommissioningWindow( + nodeid=node_id, timeout=600, iteration=10000, discriminator=longDiscriminator, option=ChipDeviceCtrl.ChipDeviceControllerBase.CommissioningWindowPasscode.kTokenWithRandomPin) + except Exception as e: + logging.exception('Error running OpenCommissioningWindow %s', e) + asserts.assert_true(False, 'Failed to open commissioning window') + return (longDiscriminator, params) + def desc_TC_CGEN_2_2(self) -> str: return "[TC-CGEN-2.2] ArmFailSafe command verification [DUT - Server]" @@ -66,16 +103,38 @@ def steps_TC_CGEN_2_2(self) -> list[TestStep]: TestStep(2, """TH1 reads the BasicCommissioningInfo attribute and saves the MaxCumulativeFailsafeSeconds as maxFailsafe"""), TestStep(3, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds - and the Breadcrumb value as 1"""), - TestStep(4, "TH1 reads the Breadcrumb attributee"), - TestStep(5, """TH1 sends AddTrustedRootCertificate command to DUT with RootCACertificate set to Root_CA_Certificate_TH1 - verify SUCCESS"""), - # TestStep(6, "TH..."), - # TestStep(7, "TH..."), - # TestStep(8, "TH..."), - # TestStep(9, "TH..."), - # TestStep(10, "TH..."), - # TestStep(11, "TH..."), + and the Breadcrumb value as 1. + DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0)"""), + TestStep(4, """TH1 reads the Breadcrumb attribute. + Verify the breadcrumb attribute is 1"""), + TestStep(5, """TH1 generates a new TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. + TH1 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate. + DUT responds with SUCCESS"""), + TestStep(6, """"TH1 reads the TrustedRootCertificate attribute + and verify that the number of items in the returned list is numTrustedRootsOriginal + 1"""), + TestStep(7, "TH1 waits for PIXIT.CGEN.FailsafeExpiryLengthSeconds to ensure the failsafe timer has expired"), + TestStep(8, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster + and verify that the number of items in the returned list is numTrustedRootsOriginal"""), + TestStep(9, """"TH1 reads the Breadcrumb attribute and verify that the breadcrumb attribute is 0"""), + TestStep(10, """TH1 repeats steps #3 through #5."""), + TestStep(11, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0"""), + TestStep(12, """TH1 Repeat steps 8 through 9"""), + TestStep(13, """TH1 sends the OpenCommissioningWindow command to the Administrator Commissioning cluster"""), + TestStep(14, """TH1 TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds"""), + TestStep(15, """TH2 opens a PASE connection to the DUT"""), + TestStep(16, """TH2 obtains or generates a TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. + TH2 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate + DUT responds with SUCCESS"""), + TestStep(17, """TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0"""), + TestStep(18, """TH1 reads the NOCs attribute from the Node Operational Credentials cluster using a non-fabric-filtered read + and saves the returned list as nocs"""), + TestStep(19, """TH1 reads the Fabrics attribute from the Node Operational Credentials cluster using a non-fabric-filtered read + and saves the returned list as fabrics"""), + TestStep(20, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster + and saves the returned list as trustedroots"""), + TestStep(21, """TH2 starts commissioning the DUT. It performs all steps up to establishing a CASE connection, + but DOES NOT send the CommissioningComplete command"""), + TestStep(22, """TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0"""), ] return steps @@ -84,6 +143,8 @@ async def test_TC_CGEN_2_2(self): cluster_opcreds = Clusters.OperationalCredentials cluster_cgen = Clusters.GeneralCommissioning + maxFailsafe_tmp = 10 + self.step(0) # Read the Spteps @@ -94,10 +155,12 @@ async def test_TC_CGEN_2_2(self): cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.TrustedRootCertificates) trusted_root_list_original_size = len(trusted_root_list_original) - logger.info(f'The trusted_root_list is {trusted_root_list_original}') - logger.info(f'The size of the trusted_root_list is {trusted_root_list_original_size}') - # asserts.assert_equal(len(trusted_root_list_original), 1, - # "Unexpected number of entries in the TrustedRootCertificates table") + logger.info(f'The original trusted_root_list is {trusted_root_list_original}') + logger.info(f'The original trusted_root_list size is {trusted_root_list_original_size}') + + # Verify that original list has 1 certificate + asserts.assert_equal(len(trusted_root_list_original), 1, + "Unexpected number of entries in the TrustedRootCertificates table") self.step(2) basic_commissioning_info = await self.read_single_attribute_check_success( @@ -107,12 +170,12 @@ async def test_TC_CGEN_2_2(self): logger.info(f'The MaxCumulativeFailsafeSeconds is {maxFailsafe}') self.step(3) - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe, breadcrumb=1) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp, breadcrumb=1) resp = await self.send_single_cmd( dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - logger.info(f'response attributesare: {vars(resp)}') + logger.info(f'response attributes are: {vars(resp)}') # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, @@ -129,45 +192,318 @@ async def test_TC_CGEN_2_2(self): logger.info(f'The breadcrumb_attribute is {breadcrumb_info}') self.step(5) + logger.info("Generating a new CSR to update the root certificate...") - root_cert_hex = "1530010828c376ebc17f21512402013703271401000000cacacaca182604ef171b2726056eb5b94c3706271401000000cacacaca182407012408013009410452c19fd9d329a738fd65722a8309fa68bcaa9ffe87d8114b802c922e5066d0b2f0573b89b38bf98fc9c424ab8ffdabcb18d42e623d82a02d0ca0c062ccadb4bc370a350129011824026030041457934de5405e9a40eacb86ee647e583141ae78f430051457934de5405e9a40eacb86ee647e583141ae78f418300b40a0b0d57bddbc7bcf44480a8b7bd0231d54ccacd68d90efb67b7aa3206adbd268725092992a0388c8e934504178613c5b932d422eed7463f38fd82aaa429b574a18" - bin_root_cert = binascii.unhexlify(root_cert_hex) - - # 1 Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) - cmd = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=True) + # 5.1 Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) + cmd = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) csr_update = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - # 2 Create new certificate authority for TH1 - th1_ca_new = self.certificate_authority_manager.NewCertificateAuthority(maximizeCertChains=True) - th1_fabric_admin_new = th1_ca_new.NewFabricAdmin(vendorId=0xFFF2, fabricId=2) + # Create new certificate authority for TH1 + # th1_ca_new = self.certificate_authority_manager.NewCertificateAuthority(maximizeCertChains=True) + # th1_fabric_admin_new = th1_ca_new.NewFabricAdmin(vendorId=0xFFF2, fabricId=2) - # 3 Create the new controller - th1_new = th1_fabric_admin_new.NewController(nodeId=self.default_controller.nodeId+1) + # Create the new controller + # th1_new = th1_fabric_admin_new.NewController(nodeId=self.default_controller.nodeId + 1) - # 4 Isue the certificates - th1_certs_new = await th1_new.IssueNOCChain(csr_update, self.dut_node_id+1) + # 5.2 Isue the certificates + th1_certs_new = await self.default_controller.IssueNOCChain(csr_update, self.dut_node_id) new_root_cert = th1_certs_new.rcacBytes logger.info(f"RCAC Certificate: {new_root_cert}") - noc_update_new_root = th1_certs_new.nocBytes - logger.info(f"NOC Certificate: {noc_update_new_root}") - icac_update_new_root = th1_certs_new.icacBytes - logger.info(f"ICAC Certificate: {icac_update_new_root}") - # 5 Send command to add new trusted root certificate (in binary format) + # 5.3 Send command to add new trusted root certificate cmd = cluster_opcreds.Commands.AddTrustedRootCertificate(new_root_cert) resp = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) logger.info(f'AddTrustedRootCertificate command response: {resp}') - # Verify that the response does not have an error - asserts.assert_equal(resp.errorCode, Clusters.OperationalCredentials.Enums.OperationalCertStatusEnum.kSuccess, - "Failed to add trusted root certificate") - logger.info("Root certificate added successfull") + self.step(6) + trusted_root_list_original_updated = await self.read_single_attribute_check_success( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cluster=cluster_opcreds, + attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) + logger.info(f'The updated trusted_root_list is {trusted_root_list_original_updated}') + logger.info(f'The updated trusted_root_list size is {trusted_root_list_original_size_updated}') + + # Verify that the trusted root list size has increased by 1 + asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 1, + "Unexpected number of entries in the TrustedRootCertificates table after update") + + # Optionally, check if the new certificate is in the updated list + assert new_root_cert in trusted_root_list_original_updated, \ + "New root certificate was not added to the trusted root list." + + self.step(7) + logger.info(f"Waiting for Failsafe timer to expire for {maxFailsafe_tmp} seconds...") + start_time = time.time() + + # Wait for the maximum failsafe time + await asyncio.sleep(12) + + elapsed_time = time.time() - start_time + logger.info(f"Failsafe timer expired after {elapsed_time} seconds.") + + # Verify that the elapsed time is as expected (within a tolerance margin) + # assert abs(elapsed_time - maxFailsafe) <= 1, f"Failsafe timer did not expire correctly. Expected {maxFailsafe} seconds but got {elapsed_time} seconds." + + self.step(8) + trusted_root_list_original_after_wait = await self.read_single_attribute_check_success( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cluster=cluster_opcreds, + attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + + # Get the size of the list after waiting + trusted_root_list_original_size_after_wait = len(trusted_root_list_original_after_wait) + logger.info( + f'The trusted_root_list size after waiting for failsafe timeout is {trusted_root_list_original_size_after_wait}') + + # Verifying if the number of entries in the TrustedRootCertificates table is the same as before + # assert trusted_root_list_original_size_after_wait == trusted_root_list_original_size_updated, \ + # f"Expected {trusted_root_list_original_size_updated} TrustedRootCertificates, but found {trusted_root_list_original_size_after_wait}" + + self.step(9) + breadcrumb_info_after_wait = await self.read_single_attribute_check_success( + cluster=cluster_cgen, + attribute=cluster_cgen.Attributes.Breadcrumb) + + logger.info(f"The breadcrumb_attribute after waiting for failsafe timeout is: {breadcrumb_info_after_wait}") + # asserts.assert_equal(breadcrumb_info_after_wait, 0, "Breadcrumb value is not 0 after waiting for failsafe timer") + + self.step(10) + # self.step(3) ************************************************************************************************ + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp, breadcrumb=1) + resp = await self.send_single_cmd( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cmd=cmd) + logger.info(f'response attributes are: {vars(resp)}') + + # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "Failure status returned from arm failsafe") + + # Verify that DebugText is empty or has a maximum length of 512 characters + debug_text = resp.debugText + assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" + + # self.step(4) *************************************************************************************** + + breadcrumb_info = await self.read_single_attribute_check_success( + cluster=cluster_cgen, + attribute=cluster_cgen.Attributes.Breadcrumb) + logger.info(f'The breadcrumb_attribute (Step #10) is {breadcrumb_info}') + + # self.step(5) ********************************************************************** + logger.info("Generating a new CSR to update the root certificate...") + + # 5.1 Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) + cmd = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) + csr_update = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) + + # Create new certificate authority for TH1 + # th1_ca_new = self.certificate_authority_manager.NewCertificateAuthority(maximizeCertChains=True) + # th1_fabric_admin_new = th1_ca_new.NewFabricAdmin(vendorId=0xFFF2, fabricId=2) + + # Create the new controller + # th1_new = th1_fabric_admin_new.NewController(nodeId=self.default_controller.nodeId + 1) + + # 5.2 Isue the certificates + th1_certs_new = await self.default_controller.IssueNOCChain(csr_update, self.dut_node_id) + new_root_cert = th1_certs_new.rcacBytes + logger.info(f"RCAC Certificate: {new_root_cert}") + + # 5.3 Send command to add new trusted root certificate + cmd = cluster_opcreds.Commands.AddTrustedRootCertificate(new_root_cert) + resp = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) + logger.info(f'AddTrustedRootCertificate command response (Step #10): {resp}') + + # self.step(6) ******************************************este no va + trusted_root_list_original_updated = await self.read_single_attribute_check_success( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cluster=cluster_opcreds, + attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) + logger.info(f'The updated trusted_root_list is (Step #10) {trusted_root_list_original_updated}') + logger.info(f'The updated trusted_root_list size is (Step #10) {trusted_root_list_original_size_updated}') + + # Verify that the trusted root list size has increased by 1 + asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 1, + "Unexpected number of entries in the TrustedRootCertificates table after update") + # Optionally, check if the new certificate is in the updated list + assert new_root_cert in trusted_root_list_original_updated, \ + "New root certificate was not added to the trusted root list." - # self.step(7) + self.step(11) + # self.step(3) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=0) + resp = await self.send_single_cmd( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cmd=cmd) + logger.info(f'response attributes are: {vars(resp)}') + + # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "Failure status returned from arm failsafe") + + # Verify that DebugText is empty or has a maximum length of 512 characters + debug_text = resp.debugText + assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" + + self.step(12) # self.step(8) + trusted_root_list_original_after_wait = await self.read_single_attribute_check_success( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cluster=cluster_opcreds, + attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + + # Get the size of the list after waiting + trusted_root_list_original_size_after_wait = len(trusted_root_list_original_after_wait) + logger.info( + f'(Step #12) The trusted_root_list size after waiting for failsafe timeout is {trusted_root_list_original_size_after_wait}') + + # Verifying if the number of entries in the TrustedRootCertificates table is the same as before + # assert trusted_root_list_original_size_after_wait == trusted_root_list_original_size_updated, \ + # f"Expected {trusted_root_list_original_size_updated} TrustedRootCertificates, but found {trusted_root_list_original_size_after_wait}" + # self.step(9) - # self.step(10) - # self.step(11) + breadcrumb_info_after_wait = await self.read_single_attribute_check_success( + cluster=cluster_cgen, + attribute=cluster_cgen.Attributes.Breadcrumb) + + logger.info(f"(Step #12)The breadcrumb_attribute after waiting for failsafe timeout is: {breadcrumb_info_after_wait}") + # asserts.assert_equal(breadcrumb_info_after_wait, 0, "Breadcrumb value is not 0 after waiting for failsafe timer") + + self.step(13) + + # Create TH2 + # Maximize cert chains so we can use this below + TH2_CA_real = self.certificate_authority_manager.NewCertificateAuthority(maximizeCertChains=True) + TH2_vid = 0xFFF2 + TH2_fabric_admin_real = TH2_CA_real.NewFabricAdmin(vendorId=TH2_vid, fabricId=2) + TH2_nodeid = self.default_controller.nodeId+1 + TH2 = TH2_fabric_admin_real.NewController(nodeId=TH2_nodeid) + + longDiscriminator, params = await self.OpenCommissioningWindow(self.default_controller, self.dut_node_id) + # params = await self.OpenCommissioningWindow(self.default_controller, self.dut_node_id) + setup_pin_code = setup_pin_code = params.setupPinCode + + logger.info(f"step #13 - params with vars: {vars(params)}") + logger.info(f"step #13 - params: {params}") + logger.info(f"step #13 - setupPinCode: {setup_pin_code}") + logger.info(f"step #13 - Attributes of params: {dir(params)}") + + self.step(14) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp, breadcrumb=1) + resp = await self.send_single_cmd( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cmd=cmd) + logger.info(f'step #14 - response attributes are: {vars(resp)}') + logger.info(f'step #14 - response attributes are: {resp.errorCode}') + + self.step(15) + newNodeId = self.dut_node_id + 1 + await self.FindAndEstablishPase(dev_ctrl=TH2, longDiscriminator=longDiscriminator, + setupPinCode=setup_pin_code, nodeid=newNodeId) + + self.step(16) + logger.info("TH2 Generating a new CSR to update the root certificate...") + + # 5.1 Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) + cmd = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) + th2_csr = await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId, cmd=cmd) + + # 5.2 Isue the certificates + th2_certs_new = await TH2.IssueNOCChain(th2_csr, newNodeId) + th2_new_root_cert = th2_certs_new.rcacBytes + logger.info(f"Step #16 - TH2 RCAC Certificate: {th2_new_root_cert}") + + # 5.3 Send command to add new trusted root certificate + cmd = cluster_opcreds.Commands.AddTrustedRootCertificate(th2_new_root_cert) + resp = await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId, cmd=cmd) + logger.info(f'Step #16 - TH2 AddTrustedRootCertificate command response: {resp}') + + self.step(17) + logger.info(f'Step #17 - TH2_nodeid : {TH2_nodeid}') + logger.info(f'Step #17 - newNodeId : {newNodeId}') + + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=0) + resp = await self.send_single_cmd( + dev_ctrl=TH2, + node_id=newNodeId, + cmd=cmd) + logger.info(f'Step #17 - response attributes are: {vars(resp)}') + + # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "Failure status returned from arm failsafe") + + # Verify that DebugText is empty or has a maximum length of 512 characters + debug_text = resp.debugText + assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" + + # ***************** + self.step(18) + logger.info(f'Step #18 - TH1 self.default_controller: {vars(self.default_controller)}') + logger.info(f'Step #18 - TH2 self.default_controller: {vars(TH2)}') + + nocs = await self.read_single_attribute_check_success(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.NOCs, fabric_filtered=False) + nocs_original_size = len(nocs) + logger.info(f'Step #18 - TH1 nocs_original_size: {nocs_original_size}') + + self.step(19) + fabrics = await self.read_single_attribute_check_success(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.Fabrics, fabric_filtered=False) + fabrics_original_size = len(fabrics) + logger.info(f'Step #19 - TH1 fabrics_original_size: {fabrics_original_size}') + + self.step(20) + trustedroots_list = await self.read_single_attribute_check_success(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + trustedroots_list_size = len(trustedroots_list) + logger.info(f'Step #20 - TH1 trusted_root_list: {trustedroots_list_size}') + logger.info(f'Step #20 - from #12 - TH1 trusted_root_list: {trusted_root_list_original_size_after_wait}') + + self.step(21) + logger.info(f"Step #21 - TH2 starts commissioning, establishing a CASE connection, but DOES NOT send the CommissioningComplete") + # Commissioning stage numbers - we should find a better way to match these to the C++ code + # CommissioningDelegate.h + # TODO: https://github.com/project-chip/connectedhomeip/issues/36629 + kFindOperationalForCommissioningComplete = 30 + logger.info( + f'Step #21 - Commissioning stage SetTestCommissionerPrematureCompleteAfter enum: {kFindOperationalForCommissioningComplete}') + + # reset_com = TH2.ResetTestCommissioner() + test = TH2.SetTestCommissionerPrematureCompleteAfter(kFindOperationalForCommissioningComplete) + logger.info(f'Step #21 - test: {test}') + # Wait for clean up + await asyncio.sleep(1) + + self.step(22) + logger.info(f"Step #22 - Sending ArmFailSafe command with expiry set to 0 seconds") + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=0) + resp = await self.send_single_cmd( + dev_ctrl=TH2, + node_id=newNodeId, + cmd=cmd + ) + logger.info(f'Step #22 - response attributes are: {vars(resp)}') + + # TH2_dut_nodeid = self.dut_node_id+2 + # TH2.ExpireSessions(newNodeId) + # # longDiscriminator, params = await self.OpenCommissioningWindow(self.default_controller, self.dut_node_id) + # # TH2.ResetTestCommissioner() + + # # await TH2.CommissionOnNetwork( + # # nodeId=TH2_dut_nodeid, setupPinCode=setup_pin_code, + # # filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=longDiscriminator) + # # FUNCIONA + # await self.FindAndEstablishPase(dev_ctrl=TH2, longDiscriminator=longDiscriminator, + # setupPinCode=setup_pin_code, nodeid=newNodeId) + + if __name__ == "__main__": default_matter_test_main() From 6a2b1e9b88163eb791c41db517b3cbf58cef44ce Mon Sep 17 00:00:00 2001 From: juandediosg Date: Tue, 28 Jan 2025 10:11:43 -0600 Subject: [PATCH 03/25] Draft PR - Verify assert values and finalize test case for TC_CGEN_2_2 --- src/python_testing/TC_CGEN_2_2.py | 662 ++++++++++++++++++++++++------ 1 file changed, 527 insertions(+), 135 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index 84cfb5f6e0c26f..33ab8836ceabb7 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -34,19 +34,20 @@ # quiet: true # === END CI TEST ARGUMENTS === -import chip.clusters as Clusters -from chip import ChipDeviceCtrl -from chip.exceptions import ChipStackError -import chip.discovery as Discovery -from chip.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main -from chip.utils import CommissioningBuildingBlocks -from mobly import asserts +import asyncio import logging import random -import asyncio import time +from datetime import datetime + +from mobly import asserts + +import chip.clusters as Clusters +import chip.discovery as Discovery +from chip import ChipDeviceCtrl from chip.exceptions import ChipStackError -from chip.native import PyChipError +from chip.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main + # Create logger logger = logging.getLogger(__name__) @@ -54,14 +55,39 @@ class TC_CGEN_2_2(MatterBaseTest): async def FindAndEstablishPase(self, longDiscriminator: int, setupPinCode: int, nodeid: int, dev_ctrl: ChipDeviceCtrl = None): + """ + Establishes a PASE session to a device using longDiscriminator. + + This method will discover commissionable nodes and filter by longDiscriminator, + then attempts to establish a PASE session with setupPinCode and nodeid. + + If no device controller is provided, the default controller will be used. + + Args: + longDiscriminator (int): The long discriminator used to identify the device. + setupPinCode (int): The setup PIN code for establishing the session. + nodeid (int): The node ID for the device. + dev_ctrl (ChipDeviceCtrl, optional): The device controller. If None, + the default controller is used. + + Raises: + TimeoutError: If unable to establish the PASE session within the timeout. + ChipStackError: If an error occurs during the establishment of the PASE session. + """ if dev_ctrl is None: dev_ctrl = self.default_controller devices = await dev_ctrl.DiscoverCommissionableNodes( filterType=Discovery.FilterType.LONG_DISCRIMINATOR, filter=longDiscriminator, stopOnFirst=False) + # For some reason, the devices returned here aren't filtered, so filter ourselves - device = next(filter(lambda d: d.commissioningMode == - Discovery.FilterType.LONG_DISCRIMINATOR and d.longDiscriminator == longDiscriminator, devices)) + device = next( + filter( + lambda d: d.commissioningMode == Discovery.FilterType.LONG_DISCRIMINATOR + and d.longDiscriminator == longDiscriminator, devices + ) + ) + for a in device.addresses: try: await dev_ctrl.EstablishPASESessionIP(ipaddr=a, setupPinCode=setupPinCode, @@ -69,32 +95,15 @@ async def FindAndEstablishPase(self, longDiscriminator: int, setupPinCode: int, break except ChipStackError: continue + try: dev_ctrl.GetConnectedDeviceSync(nodeid=nodeid, allowPASE=True, timeoutMs=1000) except TimeoutError: asserts.fail("Unable to establish a PASE session to the device") - async def OpenCommissioningWindow(self, dev_ctrl: ChipDeviceCtrl, node_id: int): - # TODO: abstract this in the base layer? Do we do this a lot? - longDiscriminator = random.randint(0, 4095) - try: - params = await dev_ctrl.OpenCommissioningWindow( - nodeid=node_id, timeout=600, iteration=10000, discriminator=longDiscriminator, option=ChipDeviceCtrl.ChipDeviceControllerBase.CommissioningWindowPasscode.kTokenWithRandomPin) - except Exception as e: - logging.exception('Error running OpenCommissioningWindow %s', e) - asserts.assert_true(False, 'Failed to open commissioning window') - return (longDiscriminator, params) - def desc_TC_CGEN_2_2(self) -> str: return "[TC-CGEN-2.2] ArmFailSafe command verification [DUT - Server]" - def pics_TC_CGEN_2_2(self): - """Return the PICS definitions associated with this test.""" - pics = [ - "CGEN.S", # Pics - ] - return pics - def steps_TC_CGEN_2_2(self) -> list[TestStep]: steps = [ TestStep(0, "Commissioning, already done", is_commissioning=True), @@ -124,7 +133,7 @@ def steps_TC_CGEN_2_2(self) -> list[TestStep]: TestStep(15, """TH2 opens a PASE connection to the DUT"""), TestStep(16, """TH2 obtains or generates a TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. TH2 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate - DUT responds with SUCCESS"""), + Verifuy DUT responds with SUCCESS"""), TestStep(17, """TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0"""), TestStep(18, """TH1 reads the NOCs attribute from the Node Operational Credentials cluster using a non-fabric-filtered read and saves the returned list as nocs"""), @@ -134,7 +143,55 @@ def steps_TC_CGEN_2_2(self) -> list[TestStep]: and saves the returned list as trustedroots"""), TestStep(21, """TH2 starts commissioning the DUT. It performs all steps up to establishing a CASE connection, but DOES NOT send the CommissioningComplete command"""), - TestStep(22, """TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0"""), + TestStep(22, """TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0 + Verify DUT cannot proceed because the session has not been fully commissioned, leading to a timeout error"""), + TestStep(23, """TH1 reads the NOCs attribute from the Node Operational Credentials cluster using a non-fabric-filtered read + Verify that the returned list matches nocs"""), + TestStep(24, """TH1 reads the Fabrics attribute from the Node Operational Credentials cluster using a non-fabric-filtered read + Verify that the returned list matches fabrics"""), + TestStep(25, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster + Verify that the returned list matches trustedroots"""), + TestStep(26, """TH2 fully commissions the DUT + Verify that the commissioning completes successfully"""), + TestStep(27, """TH2 obtains or generates a TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. + TH2 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate + Verify DUT response with FAILSAFE_REQUIRED"""), + TestStep(28, """TH1 reads the Fabrics attribute from the Node Operational Credentials cluster using a non-fabric-filtered read + Verify that the returned list includes an additional entry for TH2 when compared to fabrics"""), + TestStep(29, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds + and the Breadcrumb value as 1 + Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0)"""), + TestStep(30, """TH1 obtains or generates a new TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. + TH1 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificateS + Verify that the DUT responds with SUCCESS"""), + TestStep(31, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster + Verify that the number of items in the returned list is numTrustedRootsOriginal + 1"""), + TestStep(32, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to maxFailsafe + Verify DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0)"""), + TestStep(33, """TH1 waits for PIXIT.CGEN.FailsafeExpiryLengthSeconds + Verify waits maxFailsafe"""), + TestStep(34, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster + Verify that the number of items in the returned list is still numTrustedRootsOriginal + 1"""), + TestStep(35, """TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds + and the Breadcrumb value as 1 + Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as BusyWithOtherAdmin"""), + TestStep(36, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0 + Verify that the DUT responds with ..."""), + TestStep(37, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to maxFailsafe + Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0)"""), + TestStep(38, """TH1 saves the current wall time clock in seconds as Tstart"""), + TestStep(39, """TH1 obtains or generates a new TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. + TH1 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificateS + Verify that the DUT responds with SUCCESS"""), + TestStep(40, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster + Verify that the number of items in the returned list is numTrustedRootsOriginal + 1"""), + TestStep(41, """TH1 waits until the current wall time clock is Tstart + maxFailsafe/2"""), + TestStep(42, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to maxFailsafe + Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0)"""), + TestStep(43, """TH1 waits until the current wall time clock is Tstart + maxFailsafe. maxFailsafe is the maximum amount of time a failsafe can be armed for, + so the failsafe is required to time out at this point, despite having been re-armed in step 42."""), + TestStep(44, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster + Verify that the number of items in the returned list is numTrustedRootsOriginal"""), ] return steps @@ -147,7 +204,7 @@ async def test_TC_CGEN_2_2(self): self.step(0) - # Read the Spteps + # Read the Spteps self.step(1) trusted_root_list_original = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, @@ -155,8 +212,8 @@ async def test_TC_CGEN_2_2(self): cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.TrustedRootCertificates) trusted_root_list_original_size = len(trusted_root_list_original) - logger.info(f'The original trusted_root_list is {trusted_root_list_original}') - logger.info(f'The original trusted_root_list size is {trusted_root_list_original_size}') + logger.info(f'Step #1 - The original trusted_roots_original: {trusted_root_list_original}') + logger.info(f'Step #1 - The size of the origina num_trusted_roots_original list: {trusted_root_list_original_size}') # Verify that original list has 1 certificate asserts.assert_equal(len(trusted_root_list_original), 1, @@ -167,7 +224,7 @@ async def test_TC_CGEN_2_2(self): cluster=cluster_cgen, attribute=cluster_cgen.Attributes.BasicCommissioningInfo) maxFailsafe = basic_commissioning_info.maxCumulativeFailsafeSeconds - logger.info(f'The MaxCumulativeFailsafeSeconds is {maxFailsafe}') + logger.info(f'Step #2 - The MaxCumulativeFailsafeSeconds (max_fail_safe): {maxFailsafe}') self.step(3) cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp, breadcrumb=1) @@ -175,11 +232,10 @@ async def test_TC_CGEN_2_2(self): dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - logger.info(f'response attributes are: {vars(resp)}') - + # logger.info(f'Step #3 - response attributes are: {vars(resp)}') # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, - "Failure status returned from arm failsafe") + "Step #3 - Failure status returned from arm failsafe") # Verify that DebugText is empty or has a maximum length of 512 characters debug_text = resp.debugText @@ -189,31 +245,26 @@ async def test_TC_CGEN_2_2(self): breadcrumb_info = await self.read_single_attribute_check_success( cluster=cluster_cgen, attribute=cluster_cgen.Attributes.Breadcrumb) - logger.info(f'The breadcrumb_attribute is {breadcrumb_info}') + logger.info(f'Step #4 - The Breadcrumb attribute: {breadcrumb_info}') + asserts.assert_equal(breadcrumb_info, 1, + "Step #6 - The Breadcrumb attribute is not 1") self.step(5) - logger.info("Generating a new CSR to update the root certificate...") - - # 5.1 Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) + logger.info("Step #5.1 - Generating a new CSR to update the root certificate...") + # 5.1 Request CSR (Certificate Signing Request) cmd = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) csr_update = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - - # Create new certificate authority for TH1 - # th1_ca_new = self.certificate_authority_manager.NewCertificateAuthority(maximizeCertChains=True) - # th1_fabric_admin_new = th1_ca_new.NewFabricAdmin(vendorId=0xFFF2, fabricId=2) - - # Create the new controller - # th1_new = th1_fabric_admin_new.NewController(nodeId=self.default_controller.nodeId + 1) + # logger.info(f'Step #5.1 - New CSR: {csr_update}') # 5.2 Isue the certificates th1_certs_new = await self.default_controller.IssueNOCChain(csr_update, self.dut_node_id) new_root_cert = th1_certs_new.rcacBytes - logger.info(f"RCAC Certificate: {new_root_cert}") + # logger.info(f'Step #5.2 - New Certificate: {new_root_cert}') # 5.3 Send command to add new trusted root certificate cmd = cluster_opcreds.Commands.AddTrustedRootCertificate(new_root_cert) resp = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - logger.info(f'AddTrustedRootCertificate command response: {resp}') + # logger.info(f'Step #5.3 - "AddTrustedRootCertificate" command response: {resp}') self.step(6) trusted_root_list_original_updated = await self.read_single_attribute_check_success( @@ -222,29 +273,28 @@ async def test_TC_CGEN_2_2(self): cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.TrustedRootCertificates) trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) - logger.info(f'The updated trusted_root_list is {trusted_root_list_original_updated}') - logger.info(f'The updated trusted_root_list size is {trusted_root_list_original_size_updated}') + # logger.info(f'Step #6 - The updated trusted_roots_original: {trusted_root_list_original_updated}') + logger.info(f'Step #6 - The updated size of the num_trusted_roots_original list: {trusted_root_list_original_size_updated}') # Verify that the trusted root list size has increased by 1 asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 1, - "Unexpected number of entries in the TrustedRootCertificates table after update") + "Step #6 - Unexpected number of entries in the TrustedRootCertificates table after update") # Optionally, check if the new certificate is in the updated list assert new_root_cert in trusted_root_list_original_updated, \ - "New root certificate was not added to the trusted root list." + "Step #6 - New root certificate was not added to the trusted root list." self.step(7) - logger.info(f"Waiting for Failsafe timer to expire for {maxFailsafe_tmp} seconds...") + # Using maxFailsafe_tmp=10 instead of maxFailsafe=PIXIT.CGEN.FailsafeExpiryLengthSeconds + logger.info( + f"Step #7 - Waiting for Failsafe timer to expire for PIXIT.CGEN.FailsafeExpiryLengthSeconds (max_fail_safe): {maxFailsafe_tmp} seconds...") start_time = time.time() - # Wait for the maximum failsafe time - await asyncio.sleep(12) + # Wait for the maximum failsafe time - Adding a 2 seconds buffer + await asyncio.sleep(maxFailsafe_tmp + 2) elapsed_time = time.time() - start_time - logger.info(f"Failsafe timer expired after {elapsed_time} seconds.") - - # Verify that the elapsed time is as expected (within a tolerance margin) - # assert abs(elapsed_time - maxFailsafe) <= 1, f"Failsafe timer did not expire correctly. Expected {maxFailsafe} seconds but got {elapsed_time} seconds." + logger.info(f"Step #7 - Failsafe timer (max_fail_safe) expired after {elapsed_time} seconds.") self.step(8) trusted_root_list_original_after_wait = await self.read_single_attribute_check_success( @@ -252,33 +302,27 @@ async def test_TC_CGEN_2_2(self): node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.TrustedRootCertificates) - - # Get the size of the list after waiting trusted_root_list_original_size_after_wait = len(trusted_root_list_original_after_wait) logger.info( - f'The trusted_root_list size after waiting for failsafe timeout is {trusted_root_list_original_size_after_wait}') - - # Verifying if the number of entries in the TrustedRootCertificates table is the same as before - # assert trusted_root_list_original_size_after_wait == trusted_root_list_original_size_updated, \ - # f"Expected {trusted_root_list_original_size_updated} TrustedRootCertificates, but found {trusted_root_list_original_size_after_wait}" + f'Step #8 - The size of the num_trusted_roots_original list after waiting for failsafe timeout: {trusted_root_list_original_size_after_wait}') + asserts.assert_equal(trusted_root_list_original_size_after_wait, trusted_root_list_original_size, + "Step #8 - Unexpected number of entries in the TrustedRootCertificates table after wait") self.step(9) breadcrumb_info_after_wait = await self.read_single_attribute_check_success( cluster=cluster_cgen, attribute=cluster_cgen.Attributes.Breadcrumb) - - logger.info(f"The breadcrumb_attribute after waiting for failsafe timeout is: {breadcrumb_info_after_wait}") - # asserts.assert_equal(breadcrumb_info_after_wait, 0, "Breadcrumb value is not 0 after waiting for failsafe timer") + logger.info(f'Step #9 - The Breadcrumb attribute after waiting for failsafe timeout is: {breadcrumb_info_after_wait}') + asserts.assert_equal(breadcrumb_info_after_wait, 0, "Breadcrumb value is not 0 after waiting for failsafe timer") self.step(10) - # self.step(3) ************************************************************************************************ + logger.info(f'Step #10 - TH1 repeats steps 3 through 5') + logger.info(f'Step #10 repet #3 - TH1 sends ArmFailSafe') cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp, breadcrumb=1) resp = await self.send_single_cmd( dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - logger.info(f'response attributes are: {vars(resp)}') - # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, "Failure status returned from arm failsafe") @@ -287,63 +331,51 @@ async def test_TC_CGEN_2_2(self): debug_text = resp.debugText assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" - # self.step(4) *************************************************************************************** - breadcrumb_info = await self.read_single_attribute_check_success( cluster=cluster_cgen, attribute=cluster_cgen.Attributes.Breadcrumb) - logger.info(f'The breadcrumb_attribute (Step #10) is {breadcrumb_info}') + logger.info(f'Step #10 repet #4 - The Breadcrumb attribute: {breadcrumb_info}') + asserts.assert_equal(breadcrumb_info, 1, + "Step #10 - The Breadcrumb attribute is not 1") - # self.step(5) ********************************************************************** - logger.info("Generating a new CSR to update the root certificate...") - - # 5.1 Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) + # Flow generates a new TrustedRootCertificate - Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) + logger.info("Step #10 repet #5 - Generating a new CSR to update the root certificate...") cmd = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) - csr_update = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - - # Create new certificate authority for TH1 - # th1_ca_new = self.certificate_authority_manager.NewCertificateAuthority(maximizeCertChains=True) - # th1_fabric_admin_new = th1_ca_new.NewFabricAdmin(vendorId=0xFFF2, fabricId=2) + # logger.info(f'Step #10 - New CSR: {csr_update}') - # Create the new controller - # th1_new = th1_fabric_admin_new.NewController(nodeId=self.default_controller.nodeId + 1) - - # 5.2 Isue the certificates + # Flow generates a new TrustedRootCertificate - Isue the certificates th1_certs_new = await self.default_controller.IssueNOCChain(csr_update, self.dut_node_id) new_root_cert = th1_certs_new.rcacBytes - logger.info(f"RCAC Certificate: {new_root_cert}") + # logger.info(f"Step #10 - New RCAC Certificate: {new_root_cert}") - # 5.3 Send command to add new trusted root certificate + # Flow generates a new TrustedRootCertificate - Send command to add new trusted root certificate cmd = cluster_opcreds.Commands.AddTrustedRootCertificate(new_root_cert) resp = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - logger.info(f'AddTrustedRootCertificate command response (Step #10): {resp}') + # logger.info(f'Step #10 - AddTrustedRootCertificate command response: {resp}') - # self.step(6) ******************************************este no va trusted_root_list_original_updated = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.TrustedRootCertificates) trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) - logger.info(f'The updated trusted_root_list is (Step #10) {trusted_root_list_original_updated}') - logger.info(f'The updated trusted_root_list size is (Step #10) {trusted_root_list_original_size_updated}') - - # Verify that the trusted root list size has increased by 1 + # logger.info(f'Step #10 - The updated original trusted_roots_original: {trusted_root_list_original_updated}') + logger.info( + f'Step #10 - The updated size of the num_trusted_roots_original list: {trusted_root_list_original_size_updated}') asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 1, - "Unexpected number of entries in the TrustedRootCertificates table after update") + "Step #10 - Unexpected number of entries in the TrustedRootCertificates table after update") # Optionally, check if the new certificate is in the updated list assert new_root_cert in trusted_root_list_original_updated, \ - "New root certificate was not added to the trusted root list." + "Step #10 - New root certificate was not added to the trusted root list." self.step(11) - # self.step(3) cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=0) resp = await self.send_single_cmd( dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - logger.info(f'response attributes are: {vars(resp)}') + # logger.info(f'response attributes are: {vars(resp)}') # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, @@ -354,32 +386,28 @@ async def test_TC_CGEN_2_2(self): assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" self.step(12) - # self.step(8) - trusted_root_list_original_after_wait = await self.read_single_attribute_check_success( + logger.info(f'Step #12 - TH1 repeats steps 8 through 9') + logger.info(f'Step #12 repet #8 - TH1 reads the TrustedRootCertificates') + trusted_root_list_original_updated = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.TrustedRootCertificates) - - # Get the size of the list after waiting - trusted_root_list_original_size_after_wait = len(trusted_root_list_original_after_wait) + trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) logger.info( - f'(Step #12) The trusted_root_list size after waiting for failsafe timeout is {trusted_root_list_original_size_after_wait}') - - # Verifying if the number of entries in the TrustedRootCertificates table is the same as before - # assert trusted_root_list_original_size_after_wait == trusted_root_list_original_size_updated, \ - # f"Expected {trusted_root_list_original_size_updated} TrustedRootCertificates, but found {trusted_root_list_original_size_after_wait}" + f'Step #12 - The updated size of the num_trusted_roots_original list: {trusted_root_list_original_size_updated}') + asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size, + "Step #12 - Unexpected number of entries in the TrustedRootCertificates table after update") - # self.step(9) - breadcrumb_info_after_wait = await self.read_single_attribute_check_success( + logger.info(f'Step #12 repet #9 - TH1 reads the Breadcrumb attribute') + breadcrumb_info_updated = await self.read_single_attribute_check_success( cluster=cluster_cgen, attribute=cluster_cgen.Attributes.Breadcrumb) - logger.info(f"(Step #12)The breadcrumb_attribute after waiting for failsafe timeout is: {breadcrumb_info_after_wait}") - # asserts.assert_equal(breadcrumb_info_after_wait, 0, "Breadcrumb value is not 0 after waiting for failsafe timer") + logger.info(f"Step #12 - The Breadcrumb attribute: {breadcrumb_info_updated}") + asserts.assert_equal(breadcrumb_info_updated, 0, "Breadcrumb value is not 0 after waiting for failsafe timer") self.step(13) - # Create TH2 # Maximize cert chains so we can use this below TH2_CA_real = self.certificate_authority_manager.NewCertificateAuthority(maximizeCertChains=True) @@ -388,14 +416,19 @@ async def test_TC_CGEN_2_2(self): TH2_nodeid = self.default_controller.nodeId+1 TH2 = TH2_fabric_admin_real.NewController(nodeId=TH2_nodeid) - longDiscriminator, params = await self.OpenCommissioningWindow(self.default_controller, self.dut_node_id) + # longDiscriminator, params = await self.OpenCommissioningWindow(self.default_controller, self.dut_node_id) + # ESTE FUNCION--EL DE ABAJO + params = await self.open_commissioning_window(self.default_controller, self.dut_node_id) + setup_pin_code = params.commissioningParameters.setupPinCode + longDiscriminator = params.randomDiscriminator + # params = await self.OpenCommissioningWindow(self.default_controller, self.dut_node_id) - setup_pin_code = setup_pin_code = params.setupPinCode + # setup_pin_code = setup_pin_code = params.setupPinCode logger.info(f"step #13 - params with vars: {vars(params)}") logger.info(f"step #13 - params: {params}") logger.info(f"step #13 - setupPinCode: {setup_pin_code}") - logger.info(f"step #13 - Attributes of params: {dir(params)}") + logger.info(f"step #13 - longDiscriminator: {longDiscriminator}") self.step(14) cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp, breadcrumb=1) @@ -403,8 +436,11 @@ async def test_TC_CGEN_2_2(self): dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - logger.info(f'step #14 - response attributes are: {vars(resp)}') - logger.info(f'step #14 - response attributes are: {resp.errorCode}') + logger.info(f'Step #14 - response attributes are: {vars(resp)}') + logger.info(f'Step #14 - response attributes are: {resp.errorCode}') + # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'BusyWithOtherAdmin'(4) + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kBusyWithOtherAdmin, + "Failure status returned from arm failsafe") self.step(15) newNodeId = self.dut_node_id + 1 @@ -447,7 +483,6 @@ async def test_TC_CGEN_2_2(self): debug_text = resp.debugText assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" - # ***************** self.step(18) logger.info(f'Step #18 - TH1 self.default_controller: {vars(self.default_controller)}') logger.info(f'Step #18 - TH2 self.default_controller: {vars(TH2)}') @@ -464,11 +499,10 @@ async def test_TC_CGEN_2_2(self): self.step(20) trustedroots_list = await self.read_single_attribute_check_success(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.TrustedRootCertificates) trustedroots_list_size = len(trustedroots_list) - logger.info(f'Step #20 - TH1 trusted_root_list: {trustedroots_list_size}') + logger.info(f'Step #20 - TH1 the size of the trusted_root: {trustedroots_list_size}') logger.info(f'Step #20 - from #12 - TH1 trusted_root_list: {trusted_root_list_original_size_after_wait}') self.step(21) - logger.info(f"Step #21 - TH2 starts commissioning, establishing a CASE connection, but DOES NOT send the CommissioningComplete") # Commissioning stage numbers - we should find a better way to match these to the C++ code # CommissioningDelegate.h # TODO: https://github.com/project-chip/connectedhomeip/issues/36629 @@ -479,31 +513,389 @@ async def test_TC_CGEN_2_2(self): # reset_com = TH2.ResetTestCommissioner() test = TH2.SetTestCommissionerPrematureCompleteAfter(kFindOperationalForCommissioningComplete) logger.info(f'Step #21 - test: {test}') - # Wait for clean up - await asyncio.sleep(1) self.step(22) - logger.info(f"Step #22 - Sending ArmFailSafe command with expiry set to 0 seconds") - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=0) + # resp = TH2.ResetTestCommissioner() + # logger.info(f'Step #22 - celanup: {resp}') + + # Wait for clean up + # await asyncio.sleep(10) + + # logging.info('Step #22 - Open_commissioning_window') + # params = await self.open_commissioning_window(self.default_controller, self.dut_node_id) + # logger.info(f"step #22 - params with vars: {vars(params)}") + + logger.info("Step #22 - Waiting for PASE session to stabilize...") + await self.FindAndEstablishPase(dev_ctrl=TH2, longDiscriminator=longDiscriminator, + setupPinCode=setup_pin_code, nodeid=newNodeId) + + try: + logger.info("Step #22 - ArmFailSafe") + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=0) + resp = await self.send_single_cmd( + dev_ctrl=TH2, + node_id=newNodeId, + cmd=cmd + ) + logger.info(f"Step #22 - response attributes are: {vars(resp)}") + # except chip.exceptions.ChipStackError as e: + except Exception as e: # Capturamos cualquier excepción + logger.info(f"Step #22 - Expected error occurred during ArmFailSafe command: {str(e)}. Proceeding to next step.") + + self.step(23) + nocs2 = await self.read_single_attribute_check_success(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.NOCs, fabric_filtered=False) + nocs_original_size2 = len(nocs2) + logger.info(f'Step #23 from #18 - TH1 nocs_original_size: {nocs_original_size}') + logger.info(f'Step #23 - TH1 nocs_original_size2: {nocs_original_size2}') + + self.step(24) + fabrics2 = await self.read_single_attribute_check_success(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.Fabrics, fabric_filtered=False) + fabrics_original_size2 = len(fabrics2) + logger.info(f'Step #24 from #19 - TH1 fabrics_original_size: {fabrics_original_size}') + logger.info(f'Step #24 - TH1 fabrics_original_size2: {fabrics_original_size2}') + + self.step(25) + trustedroots_list_updated = await self.read_single_attribute_check_success(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + trustedroots_list_size_updated = len(trustedroots_list_updated) + logger.info(f'Step #25 - TH1 the size of the trusted_root_list: {trustedroots_list_size_updated}') + + self.step(26) + basic_commissioning_info = await self.read_single_attribute_check_success(cluster=Clusters.GeneralCommissioning, attribute=Clusters.GeneralCommissioning.Attributes.BasicCommissioningInfo) + logger.info(f'Step #26 - basic_commissioning_info: {basic_commissioning_info}') + + logger.info(f'Step #26 - Fully commissioned started') + resp = await TH2.CommissionOnNetwork( + nodeId=newNodeId+1, setupPinCode=setup_pin_code, + filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=longDiscriminator) + logger.info(f'Step #26 - Fully commissioned done: {resp}') + + self.step(27) + logger.info(f'Step #27 - TH2 TrustedRootCertificate FAILSAFE_REQUIRED') + + try: + logger.info("Step #27 - TH2 Generating a new CSR to update the root certificate...") + # 5.1 Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) + cmd2 = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) + th2_csr2 = await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId+1, cmd=cmd2) + + # 5.2 Isue the certificates + th2_certs_new2 = await TH2.IssueNOCChain(th2_csr2, newNodeId+1) + th2_new_root_cert2 = th2_certs_new2.rcacBytes + logger.info(f"Step #27 - TH2 RCAC Certificate: {th2_new_root_cert2}") + + # 5.3 Send command to add new trusted root certificate + cmd2 = cluster_opcreds.Commands.AddTrustedRootCertificate(th2_new_root_cert2) + resp2 = await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId+1, cmd=cmd2) + logger.info(f'Step #27 - TH2 AddTrustedRootCertificate command response: {resp2}') + + except Exception as e: + logger.info( + f"Step #27 - Expected error occurred during TrustedRootCertificate command: {str(e)}. Proceeding to next step.") + + self.step(28) + fabrics3 = await self.read_single_attribute_check_success(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.Fabrics, fabric_filtered=False) + fabrics_original_size3 = len(fabrics3) + logger.info(f'Step #28 from #19 - TH1 fabrics_original_size: {fabrics_original_size}') + logger.info(f'Step #28 from #24- TH1 fabrics_original_size2: {fabrics_original_size2}') + logger.info(f'Step #28 - TH1 fabrics_original_size3: {fabrics_original_size3}') + + self.step(29) + # logger.info("Step #29 - TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds and the Breadcrumb value as 1") + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp, breadcrumb=1) + resp = await self.send_single_cmd( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cmd=cmd) + logger.info(f'Step #29 - response attributes are: {vars(resp)}') + + self.step(30) + logger.info("Step #30 - Generating a new CSR to update the root certificate...") + # 5.1 Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) + cmd3 = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) + csr_update3 = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd3) + + # 5.2 Isue the certificates + th1_certs_new3 = await self.default_controller.IssueNOCChain(csr_update3, self.dut_node_id) + new_root_cert3 = th1_certs_new3.rcacBytes + # logger.info(f"Step #30 - RCAC Certificate: {new_root_cert3}") + + # 5.3 Send command to add new trusted root certificate + cmd3 = cluster_opcreds.Commands.AddTrustedRootCertificate(new_root_cert3) + resp = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd3) + logger.info(f'Step #30 - AddTrustedRootCertificate command response: {resp}') + + self.step(31) + trusted_root_list_original_updated3 = await self.read_single_attribute_check_success( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cluster=cluster_opcreds, + attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + trusted_root_list_original_size_updated3 = len(trusted_root_list_original_updated3) + # logger.info(f'Step #31 - The updated trusted_root_list_original_updated3 is {trusted_root_list_original_updated3}') + logger.info( + f'Step #31 - The updated trusted_root_list_original_size_updated3 size is {trusted_root_list_original_size_updated3}') + logger.info( + f'Step #31 from #10 - The updated trusted_root_list_original_size_updated size is {trusted_root_list_original_size_updated}') + logger.info(f'Step #31 from #1 - The updated trusted_root_list_original_size size is {trusted_root_list_original_size}') + + # Verify that the trusted root list size has increased by 1 + # asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 1, + # "Step #31 - Unexpected number of entries in the TrustedRootCertificates table after update") + + # Optionally, check if the new certificate is in the updated list + assert new_root_cert in trusted_root_list_original_updated, \ + "Step #31 - New root certificate was not added to the trusted root list." + + self.step(32) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp) + resp = await self.send_single_cmd( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cmd=cmd) + logger.info(f'Step #32 - response attributes are: {vars(resp)}') + logger.info(f'Step #32 - response attributes are: {resp.errorCode}') + # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "Failure status returned from arm failsafe") + + self.step(33) + logger.info(f"Step #33 - Waiting for Failsafe timer to expire for maxFailsafe: {maxFailsafe_tmp} seconds...") + start_time = time.time() + + self.step(34) + trusted_root_list_original_updated4 = await self.read_single_attribute_check_success( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cluster=cluster_opcreds, + attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + trusted_root_list_original_size_updated4 = len(trusted_root_list_original_updated4) + # logger.info(f'Step #34 - The updated trusted_root_list_original_updated4 is {trusted_root_list_original_updated4}') + logger.info( + f'Step #34 - The updated trusted_root_list_original_size_updated4 size is {trusted_root_list_original_size_updated4}') + logger.info( + f'Step #34 - The updated trusted_root_list_original_size_updated3 size is {trusted_root_list_original_size_updated3}') + logger.info( + f'Step #34 from Step #31 - The updated trusted_root_list_original_size_updated size is {trusted_root_list_original_size_updated}') + logger.info( + f'Step #34 from Step #31 - The updated trusted_root_list_original_size size is {trusted_root_list_original_size}') + logger.info( + f'Step #34 from Step #31 - The updated trusted_root_list_original_size size + 1 is {trusted_root_list_original_size + 1}') + + # # Verify that the trusted root list size has increased by 1 + # asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 1, + # "Unexpected number of entries in the TrustedRootCertificates table after update") + + # # Optionally, check if the new certificate is in the updated list + # assert new_root_cert in trusted_root_list_original_updated, \ + # "New root certificate was not added to the trusted root list." + + # ***************************************************************************** + + self.step(35) + # logger.info(f'Step #35 - TH2 ArmFailSafe') + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp, breadcrumb=1) resp = await self.send_single_cmd( dev_ctrl=TH2, - node_id=newNodeId, + node_id=newNodeId+1, cmd=cmd ) - logger.info(f'Step #22 - response attributes are: {vars(resp)}') + logger.info(f'Step #35 - response attributes are: {vars(resp)}') + logger.info(f'step #35 - response attributes are: {resp.errorCode}') + # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'BusyWithOtherAdmin'(4) + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kBusyWithOtherAdmin, + "Failure status returned from arm failsafe") + + self.step(36) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=0) + resp = await self.send_single_cmd( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cmd=cmd) + logger.info(f'Step #36 - response attributes are: {vars(resp)}') + logger.info(f'Step #36 - response attributes are: {resp.errorCode}') + # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "Failure status returned from arm failsafe") + + self.step(37) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp) + resp = await self.send_single_cmd( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cmd=cmd) + logger.info(f'Step #37 - response attributes are: {vars(resp)}') + logger.info(f'Step #37 - response attributes are: {resp.errorCode}') + # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "Failure status returned from arm failsafe") + + self.step(38) + t_start = time.time() + formatted_time = datetime.fromtimestamp(t_start).strftime('%m-%d %H:%M:%S') + milliseconds = str(round((t_start % 1) * 1000)).zfill(3) + formatted_time_with_ms = f"{formatted_time}.{milliseconds}" + logger.info(f"Step #38 - TH1 saves the current wall time clock in seconds as Tstart: {formatted_time_with_ms} seconds...") + + self.step(39) + logger.info("Step #39 - Generating a new CSR to update the root certificate...") + + # 5.1 Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) + cmd4 = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) + csr_update4 = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd4) + + # Create new certificate authority for TH1 + # th1_ca_new = self.certificate_authority_manager.NewCertificateAuthority(maximizeCertChains=True) + # th1_fabric_admin_new = th1_ca_new.NewFabricAdmin(vendorId=0xFFF2, fabricId=2) + + # Create the new controller + # th1_new = th1_fabric_admin_new.NewController(nodeId=self.default_controller.nodeId + 1) + + # 5.2 Isue the certificates + th1_certs_new4 = await self.default_controller.IssueNOCChain(csr_update4, self.dut_node_id) + new_root_cert4 = th1_certs_new4.rcacBytes + logger.info(f"Step #39 - RCAC Certificate: {new_root_cert4}") + + # 5.3 Send command to add new trusted root certificate + cmd4 = cluster_opcreds.Commands.AddTrustedRootCertificate(new_root_cert4) + resp = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd4) + logger.info(f'Step #39 - AddTrustedRootCertificate command response: {resp}') + + self.step(40) + trusted_root_list_original_updated5 = await self.read_single_attribute_check_success( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cluster=cluster_opcreds, + attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + trusted_root_list_original_size_updated5 = len(trusted_root_list_original_updated5) + logger.info(f'Step #40 - The updated trusted_root_list_original_updated5 is {trusted_root_list_original_updated5}') + logger.info( + f'Step #40 - The updated trusted_root_list_original_size_updated5 size is {trusted_root_list_original_size_updated5}') + logger.info(f'Step #40 from #34 - The updated trusted_root_list_original_updated4 is {trusted_root_list_original_updated4}') + logger.info( + f'Step #40 from #34 - The updated trusted_root_list_original_size_updated4 size is {trusted_root_list_original_size_updated4}') + logger.info( + f'Step #40 from #31 - The updated trusted_root_list_original_size_updated3 size is {trusted_root_list_original_size_updated3}') + logger.info( + f'Step #40 from Step #31 - The updated trusted_root_list_original_size_updated size is {trusted_root_list_original_size_updated}') + logger.info( + f'Step #40 from Step #31 - The updated trusted_root_list_original_size size is {trusted_root_list_original_size}') + logger.info( + f'Step #40 from Step #31 - The updated trusted_root_list_original_size size + 1 is {trusted_root_list_original_size + 1}') + + self.step(41) + target_time = t_start + (maxFailsafe_tmp / 2) + formatted_target_time = datetime.fromtimestamp(target_time).strftime('%m-%d %H:%M:%S') + target_milliseconds = str(round((target_time % 1) * 1000)).zfill(3) + formatted_target_time_with_ms = f"{formatted_target_time}.{target_milliseconds}" + + logger.info(f"Step #41 - Target time (Tstart + maxFailsafe / 2): {formatted_target_time_with_ms}") + + while time.time() < target_time: + await asyncio.sleep(0.1) # Short sleep to avoid busy-waiting + + # Validation: check if the current time reached the target time + if time.time() >= target_time: + logger.info("Step #41 - Target time reached. Proceeding...") + else: + logger.error("Step #41 - Failed to reach target time.") + raise Exception("Target time not reached. Cannot proceed.") + + self.step(42) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp) + resp = await self.send_single_cmd( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cmd=cmd) + logger.info(f'Step #42 - response attributes are: {vars(resp)}') + logger.info(f'Step #42 - response attributes are: {resp.errorCode}') + # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "Failure status returned from arm failsafe") + + self.step(43) + target_time_full = t_start + maxFailsafe_tmp + formatted_target_time_full = datetime.fromtimestamp(target_time_full).strftime('%m-%d %H:%M:%S') + target_milliseconds_full = str(round((target_time_full % 1) * 1000)).zfill(3) + formatted_target_time_with_ms_full = f"{formatted_target_time_full}.{target_milliseconds_full}" + + logger.info(f"Step #43 - Target time (Tstart + maxFailsafe): {formatted_target_time_with_ms_full}") + + while time.time() < target_time_full: + await asyncio.sleep(0.1) # Short sleep to avoid busy-waiting + + # Validation: check if the current time reached the target time + if time.time() >= target_time_full: + logger.info("Step #43 - Target time reached. Proceeding...") + else: + logger.error("Step #43 - Failed to reach target time.") + raise Exception("Target time not reached. Cannot proceed.") + + self.step(44) + # logger.info(f"Step #44 - ") + trusted_root_list_original_updated6 = await self.read_single_attribute_check_success( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cluster=cluster_opcreds, + attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + trusted_root_list_original_size_updated6 = len(trusted_root_list_original_updated6) + # logger.info(f'Step #44 - The updated trusted_root_list_original_updated6 is {trusted_root_list_original_updated6}') + logger.info( + f'Step #44 - The updated trusted_root_list_original_size_updated6 size is {trusted_root_list_original_size_updated6}') + logger.info( + f'Step #44 from #40 - The updated trusted_root_list_original_size_updated5 size is {trusted_root_list_original_size_updated5}') + logger.info( + f'Step #44 from #34 - The updated trusted_root_list_original_size_updated4 size is {trusted_root_list_original_size_updated4}') + logger.info( + f'Step #44 from #31 - The updated trusted_root_list_original_size_updated3 size is {trusted_root_list_original_size_updated3}') + logger.info( + f'Step #44 from Step #31 - The updated trusted_root_list_original_size_updated size is {trusted_root_list_original_size_updated}') + logger.info( + f'Step #44 from Step #31 - The updated trusted_root_list_original_size size is {trusted_root_list_original_size}') + logger.info( + f'Step #44 from Step #31 - The updated trusted_root_list_original_size size + 1 is {trusted_root_list_original_size + 1}') + + # ************************************************************************** + + # logger.info(f"Step #22 - Sending ArmFailSafe command with expiry set to 0 seconds") + # cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=0) + + # try: + # logger.info(f"Step #22 - Entro al try") + # resp = await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId, cmd=cmd) + # logger.info(f'Step #22 - response attributes are: {vars(resp)}') + + # except Exception as e: # Capturamos cualquier excepción + # # Si hubo un error (timeout o cualquier otra excepción) + # logger.error(f"Step #22 - Error detected: {e}") + + # # Asegúrate de que el error es relacionado con el timeout o la expiración de la sesión PASE + # if 'CHIP Error 0x00000032: Timeout' in str(e): # Si el error es un Timeout + # logger.info("Step #22 - Timeout error detected, restoring PASE session...") + # # Paso 15: Restablecer la sesión PASE + # await self.FindAndEstablishPase(dev_ctrl=TH2, longDiscriminator=longDiscriminator, + # setupPinCode=setup_pin_code, nodeid=newNodeId) + + # # Espera para estabilizar la sesión PASE + # logger.info("Step #22 - Waiting for PASE session to stabilize...") + # await asyncio.sleep(2) + + # # Reintentar enviar el comando ArmFailSafe + # logger.info(f"Step #22 - Reattempting Step #22 - Sending ArmFailSafe command with expiry set to 0 seconds") + # resp = await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId, cmd=cmd) + # logger.info(f'Step #22 - response attributes after retry: {vars(resp)}') + + # ******************************************* + # TH2_dut_nodeid = self.dut_node_id+2 # TH2.ExpireSessions(newNodeId) # # longDiscriminator, params = await self.OpenCommissioningWindow(self.default_controller, self.dut_node_id) # # TH2.ResetTestCommissioner() - # # await TH2.CommissionOnNetwork( # # nodeId=TH2_dut_nodeid, setupPinCode=setup_pin_code, # # filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=longDiscriminator) # # FUNCIONA # await self.FindAndEstablishPase(dev_ctrl=TH2, longDiscriminator=longDiscriminator, # setupPinCode=setup_pin_code, nodeid=newNodeId) - - if __name__ == "__main__": default_matter_test_main() From d34bfe42fb96226669896b31ac13ed91b4154192 Mon Sep 17 00:00:00 2001 From: juandediosg Date: Wed, 29 Jan 2025 07:26:44 -0600 Subject: [PATCH 04/25] Improvements in failsafe handling, timeouts, and preparation for step 44 in TC_CGEN_2_2 --- src/python_testing/TC_CGEN_2_2.py | 587 ++++++++++++------------------ 1 file changed, 241 insertions(+), 346 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index 33ab8836ceabb7..60e3107452da96 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -108,90 +108,68 @@ def steps_TC_CGEN_2_2(self) -> list[TestStep]: steps = [ TestStep(0, "Commissioning, already done", is_commissioning=True), TestStep(1, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster - and saves the number of list items as numTrustedRootsOriginal"""), + and saves the number of list items as numTrustedRootsOriginal."""), TestStep(2, """TH1 reads the BasicCommissioningInfo attribute - and saves the MaxCumulativeFailsafeSeconds as maxFailsafe"""), - TestStep(3, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds - and the Breadcrumb value as 1. - DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0)"""), - TestStep(4, """TH1 reads the Breadcrumb attribute. - Verify the breadcrumb attribute is 1"""), - TestStep(5, """TH1 generates a new TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. - TH1 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate. - DUT responds with SUCCESS"""), - TestStep(6, """"TH1 reads the TrustedRootCertificate attribute - and verify that the number of items in the returned list is numTrustedRootsOriginal + 1"""), - TestStep(7, "TH1 waits for PIXIT.CGEN.FailsafeExpiryLengthSeconds to ensure the failsafe timer has expired"), - TestStep(8, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster - and verify that the number of items in the returned list is numTrustedRootsOriginal"""), - TestStep(9, """"TH1 reads the Breadcrumb attribute and verify that the breadcrumb attribute is 0"""), + and saves the MaxCumulativeFailsafeSeconds as maxFailsafe."""), + TestStep(3, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds + and the Breadcrumb value as 1."""), + TestStep(4, """TH1 reads the Breadcrumb attribute."""), + TestStep(5, """TH1 generates a new TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. + TH1 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate."""), + TestStep(6, """TH1 reads the TrustedRootCertificate attribute."""), + TestStep(7, """TH1 waits for PIXIT.CGEN.FailsafeExpiryLengthSeconds to ensure the failsafe timer has expired."""), + TestStep(8, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster."""), + TestStep(9, """TH1 reads the Breadcrumb attribute and verify that the breadcrumb attribute is 0."""), TestStep(10, """TH1 repeats steps #3 through #5."""), - TestStep(11, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0"""), - TestStep(12, """TH1 Repeat steps 8 through 9"""), - TestStep(13, """TH1 sends the OpenCommissioningWindow command to the Administrator Commissioning cluster"""), - TestStep(14, """TH1 TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds"""), - TestStep(15, """TH2 opens a PASE connection to the DUT"""), - TestStep(16, """TH2 obtains or generates a TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. - TH2 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate - Verifuy DUT responds with SUCCESS"""), - TestStep(17, """TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0"""), - TestStep(18, """TH1 reads the NOCs attribute from the Node Operational Credentials cluster using a non-fabric-filtered read - and saves the returned list as nocs"""), - TestStep(19, """TH1 reads the Fabrics attribute from the Node Operational Credentials cluster using a non-fabric-filtered read - and saves the returned list as fabrics"""), - TestStep(20, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster - and saves the returned list as trustedroots"""), - TestStep(21, """TH2 starts commissioning the DUT. It performs all steps up to establishing a CASE connection, - but DOES NOT send the CommissioningComplete command"""), - TestStep(22, """TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0 - Verify DUT cannot proceed because the session has not been fully commissioned, leading to a timeout error"""), - TestStep(23, """TH1 reads the NOCs attribute from the Node Operational Credentials cluster using a non-fabric-filtered read - Verify that the returned list matches nocs"""), - TestStep(24, """TH1 reads the Fabrics attribute from the Node Operational Credentials cluster using a non-fabric-filtered read - Verify that the returned list matches fabrics"""), - TestStep(25, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster - Verify that the returned list matches trustedroots"""), - TestStep(26, """TH2 fully commissions the DUT - Verify that the commissioning completes successfully"""), - TestStep(27, """TH2 obtains or generates a TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. - TH2 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate - Verify DUT response with FAILSAFE_REQUIRED"""), - TestStep(28, """TH1 reads the Fabrics attribute from the Node Operational Credentials cluster using a non-fabric-filtered read - Verify that the returned list includes an additional entry for TH2 when compared to fabrics"""), - TestStep(29, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds - and the Breadcrumb value as 1 - Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0)"""), - TestStep(30, """TH1 obtains or generates a new TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. - TH1 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificateS - Verify that the DUT responds with SUCCESS"""), - TestStep(31, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster - Verify that the number of items in the returned list is numTrustedRootsOriginal + 1"""), - TestStep(32, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to maxFailsafe - Verify DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0)"""), + TestStep(11, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0."""), + TestStep(12, """TH1 Repeat steps 8 through 9."""), + TestStep(13, """TH1 sends the OpenCommissioningWindow command to the Administrator Commissioning cluster."""), + TestStep(14, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds"""), + TestStep(15, """TH2 opens a PASE connection to the DUT."""), + TestStep(16, """TH2 obtains or generates a TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. + TH2 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate."""), + TestStep(17, """TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0."""), + TestStep(18, """TH1 reads the NOCs attribute from the Node Operational Credentials cluster using a non-fabric-filtered read + and saves the returned list as nocs."""), + TestStep(19, """TH1 reads the Fabrics attribute from the Node Operational Credentials cluster using a non-fabric-filtered read + and saves the returned list as fabrics."""), + TestStep(20, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster + and saves the returned list as trustedroots."""), + TestStep(21, """TH2 starts commissioning the DUT. It performs all steps up to establishing a CASE connection, + but DOES NOT send the CommissioningComplete command."""), + TestStep(22, """TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0."""), + TestStep(23, """TH1 reads the NOCs attribute from the Node Operational Credentials cluster using a non-fabric-filtered read."""), + TestStep(24, """TH1 reads the Fabrics attribute from the Node Operational Credentials cluster using a non-fabric-filtered read."""), + TestStep(25, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster."""), + TestStep(26, """TH2 fully commissions the DUT."""), + TestStep(27, """TH2 obtains or generates a TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. + TH2 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate."""), + TestStep(28, """TH1 reads the Fabrics attribute from the Node Operational Credentials cluster using a non-fabric-filtered read."""), + TestStep(29, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds + and the Breadcrumb value as 1."""), + TestStep(30, """TH1 obtains or generates a new TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. + TH1 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate."""), + TestStep(31, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster."""), + TestStep(32, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to maxFailsafe"""), TestStep(33, """TH1 waits for PIXIT.CGEN.FailsafeExpiryLengthSeconds Verify waits maxFailsafe"""), TestStep(34, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster Verify that the number of items in the returned list is still numTrustedRootsOriginal + 1"""), - TestStep(35, """TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds - and the Breadcrumb value as 1 - Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as BusyWithOtherAdmin"""), + TestStep(35, """TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds + and the Breadcrumb value as 1."""), TestStep(36, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0 Verify that the DUT responds with ..."""), TestStep(37, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to maxFailsafe Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0)"""), TestStep(38, """TH1 saves the current wall time clock in seconds as Tstart"""), - TestStep(39, """TH1 obtains or generates a new TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. - TH1 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificateS - Verify that the DUT responds with SUCCESS"""), - TestStep(40, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster - Verify that the number of items in the returned list is numTrustedRootsOriginal + 1"""), - TestStep(41, """TH1 waits until the current wall time clock is Tstart + maxFailsafe/2"""), - TestStep(42, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to maxFailsafe - Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0)"""), - TestStep(43, """TH1 waits until the current wall time clock is Tstart + maxFailsafe. maxFailsafe is the maximum amount of time a failsafe can be armed for, + TestStep(39, """TH1 obtains or generates a new TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. + TH1 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate."""), + TestStep(40, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster."""), + TestStep(41, """TH1 waits until the current wall time clock is Tstart + maxFailsafe/2."""), + TestStep(42, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to maxFailsafe."""), + TestStep(43, """TH1 waits until the current wall time clock is Tstart + maxFailsafe. maxFailsafe is the maximum amount of time a failsafe can be armed for, so the failsafe is required to time out at this point, despite having been re-armed in step 42."""), - TestStep(44, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster - Verify that the number of items in the returned list is numTrustedRootsOriginal"""), + TestStep(44, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster."""), ] return steps @@ -212,11 +190,10 @@ async def test_TC_CGEN_2_2(self): cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.TrustedRootCertificates) trusted_root_list_original_size = len(trusted_root_list_original) - logger.info(f'Step #1 - The original trusted_roots_original: {trusted_root_list_original}') logger.info(f'Step #1 - The size of the origina num_trusted_roots_original list: {trusted_root_list_original_size}') # Verify that original list has 1 certificate - asserts.assert_equal(len(trusted_root_list_original), 1, + asserts.assert_equal(trusted_root_list_original_size, 1, "Unexpected number of entries in the TrustedRootCertificates table") self.step(2) @@ -236,10 +213,10 @@ async def test_TC_CGEN_2_2(self): # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, "Step #3 - Failure status returned from arm failsafe") - # Verify that DebugText is empty or has a maximum length of 512 characters debug_text = resp.debugText assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" + logger.info(f'Step #3 - ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') self.step(4) breadcrumb_info = await self.read_single_attribute_check_success( @@ -254,17 +231,14 @@ async def test_TC_CGEN_2_2(self): # 5.1 Request CSR (Certificate Signing Request) cmd = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) csr_update = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - # logger.info(f'Step #5.1 - New CSR: {csr_update}') # 5.2 Isue the certificates th1_certs_new = await self.default_controller.IssueNOCChain(csr_update, self.dut_node_id) new_root_cert = th1_certs_new.rcacBytes - # logger.info(f'Step #5.2 - New Certificate: {new_root_cert}') # 5.3 Send command to add new trusted root certificate cmd = cluster_opcreds.Commands.AddTrustedRootCertificate(new_root_cert) resp = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - # logger.info(f'Step #5.3 - "AddTrustedRootCertificate" command response: {resp}') self.step(6) trusted_root_list_original_updated = await self.read_single_attribute_check_success( @@ -273,7 +247,6 @@ async def test_TC_CGEN_2_2(self): cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.TrustedRootCertificates) trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) - # logger.info(f'Step #6 - The updated trusted_roots_original: {trusted_root_list_original_updated}') logger.info(f'Step #6 - The updated size of the num_trusted_roots_original list: {trusted_root_list_original_size_updated}') # Verify that the trusted root list size has increased by 1 @@ -291,7 +264,7 @@ async def test_TC_CGEN_2_2(self): start_time = time.time() # Wait for the maximum failsafe time - Adding a 2 seconds buffer - await asyncio.sleep(maxFailsafe_tmp + 2) + await asyncio.sleep(maxFailsafe_tmp + 1) elapsed_time = time.time() - start_time logger.info(f"Step #7 - Failsafe timer (max_fail_safe) expired after {elapsed_time} seconds.") @@ -309,11 +282,11 @@ async def test_TC_CGEN_2_2(self): "Step #8 - Unexpected number of entries in the TrustedRootCertificates table after wait") self.step(9) - breadcrumb_info_after_wait = await self.read_single_attribute_check_success( + breadcrumb_info = await self.read_single_attribute_check_success( cluster=cluster_cgen, attribute=cluster_cgen.Attributes.Breadcrumb) - logger.info(f'Step #9 - The Breadcrumb attribute after waiting for failsafe timeout is: {breadcrumb_info_after_wait}') - asserts.assert_equal(breadcrumb_info_after_wait, 0, "Breadcrumb value is not 0 after waiting for failsafe timer") + logger.info(f'Step #9 - After waiting for failsafe timeout the Breadcrumb attribute: {breadcrumb_info}') + asserts.assert_equal(breadcrumb_info, 0, "Breadcrumb value is not 0 after waiting for failsafe timer") self.step(10) logger.info(f'Step #10 - TH1 repeats steps 3 through 5') @@ -326,10 +299,10 @@ async def test_TC_CGEN_2_2(self): # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, "Failure status returned from arm failsafe") - # Verify that DebugText is empty or has a maximum length of 512 characters debug_text = resp.debugText assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" + logger.info(f'Step #10 - ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') breadcrumb_info = await self.read_single_attribute_check_success( cluster=cluster_cgen, @@ -341,17 +314,14 @@ async def test_TC_CGEN_2_2(self): # Flow generates a new TrustedRootCertificate - Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) logger.info("Step #10 repet #5 - Generating a new CSR to update the root certificate...") cmd = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) - # logger.info(f'Step #10 - New CSR: {csr_update}') # Flow generates a new TrustedRootCertificate - Isue the certificates th1_certs_new = await self.default_controller.IssueNOCChain(csr_update, self.dut_node_id) new_root_cert = th1_certs_new.rcacBytes - # logger.info(f"Step #10 - New RCAC Certificate: {new_root_cert}") # Flow generates a new TrustedRootCertificate - Send command to add new trusted root certificate cmd = cluster_opcreds.Commands.AddTrustedRootCertificate(new_root_cert) resp = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - # logger.info(f'Step #10 - AddTrustedRootCertificate command response: {resp}') trusted_root_list_original_updated = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, @@ -375,15 +345,14 @@ async def test_TC_CGEN_2_2(self): dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - # logger.info(f'response attributes are: {vars(resp)}') # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, "Failure status returned from arm failsafe") - # Verify that DebugText is empty or has a maximum length of 512 characters debug_text = resp.debugText assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" + logger.info(f'Step #11 - ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') self.step(12) logger.info(f'Step #12 - TH1 repeats steps 8 through 9') @@ -400,107 +369,102 @@ async def test_TC_CGEN_2_2(self): "Step #12 - Unexpected number of entries in the TrustedRootCertificates table after update") logger.info(f'Step #12 repet #9 - TH1 reads the Breadcrumb attribute') - breadcrumb_info_updated = await self.read_single_attribute_check_success( + breadcrumb_info = await self.read_single_attribute_check_success( cluster=cluster_cgen, attribute=cluster_cgen.Attributes.Breadcrumb) - logger.info(f"Step #12 - The Breadcrumb attribute: {breadcrumb_info_updated}") - asserts.assert_equal(breadcrumb_info_updated, 0, "Breadcrumb value is not 0 after waiting for failsafe timer") + logger.info(f"Step #12 - The Breadcrumb attribute: {breadcrumb_info}") + asserts.assert_equal(breadcrumb_info, 0, "Breadcrumb value is not 0 after waiting for failsafe timer") self.step(13) - # Create TH2 - # Maximize cert chains so we can use this below - TH2_CA_real = self.certificate_authority_manager.NewCertificateAuthority(maximizeCertChains=True) - TH2_vid = 0xFFF2 - TH2_fabric_admin_real = TH2_CA_real.NewFabricAdmin(vendorId=TH2_vid, fabricId=2) - TH2_nodeid = self.default_controller.nodeId+1 - TH2 = TH2_fabric_admin_real.NewController(nodeId=TH2_nodeid) - - # longDiscriminator, params = await self.OpenCommissioningWindow(self.default_controller, self.dut_node_id) - # ESTE FUNCION--EL DE ABAJO params = await self.open_commissioning_window(self.default_controller, self.dut_node_id) setup_pin_code = params.commissioningParameters.setupPinCode longDiscriminator = params.randomDiscriminator - - # params = await self.OpenCommissioningWindow(self.default_controller, self.dut_node_id) - # setup_pin_code = setup_pin_code = params.setupPinCode - - logger.info(f"step #13 - params with vars: {vars(params)}") - logger.info(f"step #13 - params: {params}") - logger.info(f"step #13 - setupPinCode: {setup_pin_code}") - logger.info(f"step #13 - longDiscriminator: {longDiscriminator}") + logger.info(f'Step #13 - Open Commissioning Window params with vars: {vars(params)}') self.step(14) - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp, breadcrumb=1) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp) resp = await self.send_single_cmd( dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - logger.info(f'Step #14 - response attributes are: {vars(resp)}') - logger.info(f'Step #14 - response attributes are: {resp.errorCode}') # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'BusyWithOtherAdmin'(4) asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kBusyWithOtherAdmin, "Failure status returned from arm failsafe") + logger.info(f'Step #14 - ArmFailSafeResponse with ErrorCode as BusyWithOtherAdmin ({resp.errorCode})') self.step(15) + # Create TH2 + TH2_CA_real = self.certificate_authority_manager.NewCertificateAuthority(maximizeCertChains=True) + TH2_vid = 0xFFF2 + TH2_fabric_admin_real = TH2_CA_real.NewFabricAdmin(vendorId=TH2_vid, fabricId=2) + TH2_nodeid = self.default_controller.nodeId+1 + TH2 = TH2_fabric_admin_real.NewController(nodeId=TH2_nodeid) newNodeId = self.dut_node_id + 1 - await self.FindAndEstablishPase(dev_ctrl=TH2, longDiscriminator=longDiscriminator, - setupPinCode=setup_pin_code, nodeid=newNodeId) - self.step(16) - logger.info("TH2 Generating a new CSR to update the root certificate...") + resp = await self.FindAndEstablishPase( + dev_ctrl=TH2, + longDiscriminator=longDiscriminator, + setupPinCode=setup_pin_code, + nodeid=newNodeId) + logger.info(f'Step #15 - TH2 successfully establish PASE session completed') - # 5.1 Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) + self.step(16) + logger.info(f'Step #16 - TH2 Generating a new CSR to update the root certificate...') + # Flow generates a new TrustedRootCertificate - Request CSR (Certificate Signing Request) cmd = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) th2_csr = await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId, cmd=cmd) - # 5.2 Isue the certificates + # Flow generates a new TrustedRootCertificate - Isue the certificates th2_certs_new = await TH2.IssueNOCChain(th2_csr, newNodeId) th2_new_root_cert = th2_certs_new.rcacBytes - logger.info(f"Step #16 - TH2 RCAC Certificate: {th2_new_root_cert}") - # 5.3 Send command to add new trusted root certificate + # Flow generates a new TrustedRootCertificate - Send command to add new trusted root certificate cmd = cluster_opcreds.Commands.AddTrustedRootCertificate(th2_new_root_cert) resp = await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId, cmd=cmd) - logger.info(f'Step #16 - TH2 AddTrustedRootCertificate command response: {resp}') self.step(17) - logger.info(f'Step #17 - TH2_nodeid : {TH2_nodeid}') - logger.info(f'Step #17 - newNodeId : {newNodeId}') - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=0) resp = await self.send_single_cmd( dev_ctrl=TH2, node_id=newNodeId, cmd=cmd) - logger.info(f'Step #17 - response attributes are: {vars(resp)}') - # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, "Failure status returned from arm failsafe") - # Verify that DebugText is empty or has a maximum length of 512 characters debug_text = resp.debugText assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" + logger.info(f'Step #17 - ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') self.step(18) - logger.info(f'Step #18 - TH1 self.default_controller: {vars(self.default_controller)}') - logger.info(f'Step #18 - TH2 self.default_controller: {vars(TH2)}') - - nocs = await self.read_single_attribute_check_success(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.NOCs, fabric_filtered=False) + nocs = await self.read_single_attribute_check_success( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cluster=cluster_opcreds, + attribute=cluster_opcreds.Attributes.NOCs, + fabric_filtered=False) nocs_original_size = len(nocs) - logger.info(f'Step #18 - TH1 nocs_original_size: {nocs_original_size}') + logger.info(f'Step #18 - TH1 Original size of the nocs list: {nocs_original_size}') self.step(19) - fabrics = await self.read_single_attribute_check_success(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.Fabrics, fabric_filtered=False) + fabrics = await self.read_single_attribute_check_success( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cluster=cluster_opcreds, + attribute=cluster_opcreds.Attributes.Fabrics, + fabric_filtered=False) fabrics_original_size = len(fabrics) - logger.info(f'Step #19 - TH1 fabrics_original_size: {fabrics_original_size}') + logger.info(f'Step #19 - TH1 Original size of the fabrics list: {fabrics_original_size}') self.step(20) - trustedroots_list = await self.read_single_attribute_check_success(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.TrustedRootCertificates) - trustedroots_list_size = len(trustedroots_list) - logger.info(f'Step #20 - TH1 the size of the trusted_root: {trustedroots_list_size}') - logger.info(f'Step #20 - from #12 - TH1 trusted_root_list: {trusted_root_list_original_size_after_wait}') + trusted_roots_list = await self.read_single_attribute_check_success( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cluster=cluster_opcreds, + attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + trusted_roots_list_size = len(trusted_roots_list) + logger.info(f'Step #20 - TH1 Original size of the trusted_root list: {trusted_roots_list_size}') self.step(21) # Commissioning stage numbers - we should find a better way to match these to the C++ code @@ -508,56 +472,64 @@ async def test_TC_CGEN_2_2(self): # TODO: https://github.com/project-chip/connectedhomeip/issues/36629 kFindOperationalForCommissioningComplete = 30 logger.info( - f'Step #21 - Commissioning stage SetTestCommissionerPrematureCompleteAfter enum: {kFindOperationalForCommissioningComplete}') + f'Step #21 - TH2 Commissioning stage SetTestCommissionerPrematureCompleteAfter enum: {kFindOperationalForCommissioningComplete}') - # reset_com = TH2.ResetTestCommissioner() - test = TH2.SetTestCommissionerPrematureCompleteAfter(kFindOperationalForCommissioningComplete) - logger.info(f'Step #21 - test: {test}') + resp = TH2.SetTestCommissionerPrematureCompleteAfter(kFindOperationalForCommissioningComplete) + logger.info(f'Step #21 - TH2 Commissioning DOES NOT send the CommissioningComplete command') self.step(22) - # resp = TH2.ResetTestCommissioner() - # logger.info(f'Step #22 - celanup: {resp}') - - # Wait for clean up - # await asyncio.sleep(10) - - # logging.info('Step #22 - Open_commissioning_window') - # params = await self.open_commissioning_window(self.default_controller, self.dut_node_id) - # logger.info(f"step #22 - params with vars: {vars(params)}") - - logger.info("Step #22 - Waiting for PASE session to stabilize...") - await self.FindAndEstablishPase(dev_ctrl=TH2, longDiscriminator=longDiscriminator, - setupPinCode=setup_pin_code, nodeid=newNodeId) + logger.info("Step #22 - TH1 Waiting for PASE session to stabilize...") + resp = await self.FindAndEstablishPase( + dev_ctrl=TH2, + longDiscriminator=longDiscriminator, + setupPinCode=setup_pin_code, + nodeid=newNodeId) + logger.info(f'Step #22 - TH2 successfully establish PASE session completed') try: - logger.info("Step #22 - ArmFailSafe") + # Verify DUT cannot proceed because the session has not been fully commissioned, leading to a timeout error cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=0) resp = await self.send_single_cmd( dev_ctrl=TH2, node_id=newNodeId, - cmd=cmd - ) - logger.info(f"Step #22 - response attributes are: {vars(resp)}") - # except chip.exceptions.ChipStackError as e: - except Exception as e: # Capturamos cualquier excepción - logger.info(f"Step #22 - Expected error occurred during ArmFailSafe command: {str(e)}. Proceeding to next step.") + cmd=cmd) + except Exception as e: + logger.info(f"Step #22 - TH2 Expected error occurred during ArmFailSafe command: {str(e)}. Proceeding to next step.") self.step(23) - nocs2 = await self.read_single_attribute_check_success(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.NOCs, fabric_filtered=False) - nocs_original_size2 = len(nocs2) - logger.info(f'Step #23 from #18 - TH1 nocs_original_size: {nocs_original_size}') - logger.info(f'Step #23 - TH1 nocs_original_size2: {nocs_original_size2}') + nocs_updated = await self.read_single_attribute_check_success( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cluster=cluster_opcreds, + attribute=cluster_opcreds.Attributes.NOCs, + fabric_filtered=False) + nocs_updated_size = len(nocs_updated) + logger.info(f'Step #23 - TH1 nocs_updated: {nocs_updated_size}') + asserts.assert_equal(nocs_updated_size, nocs_original_size, + "Step #23 - The nocs list size should match the original nocs list size.") self.step(24) - fabrics2 = await self.read_single_attribute_check_success(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.Fabrics, fabric_filtered=False) - fabrics_original_size2 = len(fabrics2) - logger.info(f'Step #24 from #19 - TH1 fabrics_original_size: {fabrics_original_size}') - logger.info(f'Step #24 - TH1 fabrics_original_size2: {fabrics_original_size2}') + fabrics_updated = await self.read_single_attribute_check_success( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cluster=cluster_opcreds, + attribute=cluster_opcreds.Attributes.Fabrics, + fabric_filtered=False) + fabrics_updated_size = len(fabrics_updated) + logger.info(f'Step #24 - TH1 fabrics_original_size2: {fabrics_updated_size}') + asserts.assert_equal(fabrics_updated_size, fabrics_original_size, + "Step #24 - The fabrics list size should match the original fabrics list size.") self.step(25) - trustedroots_list_updated = await self.read_single_attribute_check_success(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.TrustedRootCertificates) - trustedroots_list_size_updated = len(trustedroots_list_updated) - logger.info(f'Step #25 - TH1 the size of the trusted_root_list: {trustedroots_list_size_updated}') + trusted_roots_list_updated = await self.read_single_attribute_check_success( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cluster=cluster_opcreds, + attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + trusted_roots_list_updated_size = len(trusted_roots_list_updated) + logger.info(f'Step #25 - TH1 the size of the trusted_roots_list_size_updated: {trusted_roots_list_updated_size}') + asserts.assert_equal(trusted_roots_list_updated_size, trusted_roots_list_size, + "Step #25 - The trusted_roots list size should match the original trusted_roots list size.") self.step(26) basic_commissioning_info = await self.read_single_attribute_check_success(cluster=Clusters.GeneralCommissioning, attribute=Clusters.GeneralCommissioning.Attributes.BasicCommissioningInfo) @@ -570,34 +542,36 @@ async def test_TC_CGEN_2_2(self): logger.info(f'Step #26 - Fully commissioned done: {resp}') self.step(27) - logger.info(f'Step #27 - TH2 TrustedRootCertificate FAILSAFE_REQUIRED') - + # TH2 TrustedRootCertificate response with FAILSAFE_REQUIRED try: logger.info("Step #27 - TH2 Generating a new CSR to update the root certificate...") - # 5.1 Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) - cmd2 = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) - th2_csr2 = await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId+1, cmd=cmd2) + # Flow generates a new TrustedRootCertificate - Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) + cmd = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) + th2_csr = await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId+1, cmd=cmd) - # 5.2 Isue the certificates - th2_certs_new2 = await TH2.IssueNOCChain(th2_csr2, newNodeId+1) - th2_new_root_cert2 = th2_certs_new2.rcacBytes - logger.info(f"Step #27 - TH2 RCAC Certificate: {th2_new_root_cert2}") + # Flow generates a new TrustedRootCertificate - Isue the certificates + th2_certs_new = await TH2.IssueNOCChain(th2_csr, newNodeId+1) + th2_new_root_cert = th2_certs_new.rcacBytes - # 5.3 Send command to add new trusted root certificate - cmd2 = cluster_opcreds.Commands.AddTrustedRootCertificate(th2_new_root_cert2) - resp2 = await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId+1, cmd=cmd2) - logger.info(f'Step #27 - TH2 AddTrustedRootCertificate command response: {resp2}') + # Flow generates a new TrustedRootCertificate - Send command to add new trusted root certificate + cmd = cluster_opcreds.Commands.AddTrustedRootCertificate(th2_new_root_cert) + resp = await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId+1, cmd=cmd) except Exception as e: logger.info( f"Step #27 - Expected error occurred during TrustedRootCertificate command: {str(e)}. Proceeding to next step.") self.step(28) - fabrics3 = await self.read_single_attribute_check_success(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.Fabrics, fabric_filtered=False) - fabrics_original_size3 = len(fabrics3) - logger.info(f'Step #28 from #19 - TH1 fabrics_original_size: {fabrics_original_size}') - logger.info(f'Step #28 from #24- TH1 fabrics_original_size2: {fabrics_original_size2}') - logger.info(f'Step #28 - TH1 fabrics_original_size3: {fabrics_original_size3}') + fabrics_updated = await self.read_single_attribute_check_success( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cluster=cluster_opcreds, + attribute=cluster_opcreds.Attributes.Fabrics, + fabric_filtered=False) + fabrics_updated_size = len(fabrics_updated) + logger.info(f'Step #28 - TH1 fabrics updated with additional entry for TH2: {fabrics_updated_size}') + asserts.assert_equal(fabrics_updated_size, fabrics_original_size + 1, + "Step #28 - The fabrics list size should match the original fabrics list size + 1.") self.step(29) # logger.info("Step #29 - TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds and the Breadcrumb value as 1") @@ -606,45 +580,39 @@ async def test_TC_CGEN_2_2(self): dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - logger.info(f'Step #29 - response attributes are: {vars(resp)}') + # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "Failure status returned from arm failsafe") + # Verify that DebugText is empty or has a maximum length of 512 characters + debug_text = resp.debugText + assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" + logger.info(f'Step #29 - ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') self.step(30) logger.info("Step #30 - Generating a new CSR to update the root certificate...") - # 5.1 Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) - cmd3 = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) - csr_update3 = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd3) + # Flow generates a new TrustedRootCertificate - Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) + cmd = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) + csr_update = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - # 5.2 Isue the certificates - th1_certs_new3 = await self.default_controller.IssueNOCChain(csr_update3, self.dut_node_id) - new_root_cert3 = th1_certs_new3.rcacBytes - # logger.info(f"Step #30 - RCAC Certificate: {new_root_cert3}") + # Flow generates a new TrustedRootCertificate - Isue the certificates + th1_certs_new = await self.default_controller.IssueNOCChain(csr_update, self.dut_node_id) + new_root_cert = th1_certs_new.rcacBytes - # 5.3 Send command to add new trusted root certificate - cmd3 = cluster_opcreds.Commands.AddTrustedRootCertificate(new_root_cert3) - resp = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd3) - logger.info(f'Step #30 - AddTrustedRootCertificate command response: {resp}') + # Flow generates a new TrustedRootCertificate - Send command to add new trusted root certificate + cmd = cluster_opcreds.Commands.AddTrustedRootCertificate(new_root_cert) + resp = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) self.step(31) - trusted_root_list_original_updated3 = await self.read_single_attribute_check_success( + trusted_root_list_original_updated = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.TrustedRootCertificates) - trusted_root_list_original_size_updated3 = len(trusted_root_list_original_updated3) - # logger.info(f'Step #31 - The updated trusted_root_list_original_updated3 is {trusted_root_list_original_updated3}') - logger.info( - f'Step #31 - The updated trusted_root_list_original_size_updated3 size is {trusted_root_list_original_size_updated3}') - logger.info( - f'Step #31 from #10 - The updated trusted_root_list_original_size_updated size is {trusted_root_list_original_size_updated}') - logger.info(f'Step #31 from #1 - The updated trusted_root_list_original_size size is {trusted_root_list_original_size}') - + trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) + logger.info(f'Step #31 - The updated num_trusted_roots_original: {trusted_root_list_original_size_updated}') # Verify that the trusted root list size has increased by 1 - # asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 1, - # "Step #31 - Unexpected number of entries in the TrustedRootCertificates table after update") - - # Optionally, check if the new certificate is in the updated list - assert new_root_cert in trusted_root_list_original_updated, \ - "Step #31 - New root certificate was not added to the trusted root list." + asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 2, + "Step #31 - Unexpected number of entries in the TrustedRootCertificates table after update") self.step(32) cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp) @@ -652,56 +620,49 @@ async def test_TC_CGEN_2_2(self): dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - logger.info(f'Step #32 - response attributes are: {vars(resp)}') - logger.info(f'Step #32 - response attributes are: {resp.errorCode}') # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, "Failure status returned from arm failsafe") + # Verify that DebugText is empty or has a maximum length of 512 characters + debug_text = resp.debugText + assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" + logger.info(f'Step #32 - ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') self.step(33) - logger.info(f"Step #33 - Waiting for Failsafe timer to expire for maxFailsafe: {maxFailsafe_tmp} seconds...") + # Using maxFailsafe_tmp=10 instead of maxFailsafe=PIXIT.CGEN.FailsafeExpiryLengthSeconds + logger.info( + f"Step #33 - Waiting for Failsafe timer to expire for PIXIT.CGEN.FailsafeExpiryLengthSeconds (max_fail_safe): {maxFailsafe_tmp} seconds...") start_time = time.time() + await asyncio.sleep(1) + + # # Wait for the maximum failsafe time - Adding a 2 seconds buffer + # await asyncio.sleep(maxFailsafe_tmp + 1) + + # elapsed_time = time.time() - start_time + # logger.info(f"Step #33 - Failsafe timer (max_fail_safe) expired after {elapsed_time} seconds.") self.step(34) - trusted_root_list_original_updated4 = await self.read_single_attribute_check_success( + trusted_root_list_original_updated = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.TrustedRootCertificates) - trusted_root_list_original_size_updated4 = len(trusted_root_list_original_updated4) - # logger.info(f'Step #34 - The updated trusted_root_list_original_updated4 is {trusted_root_list_original_updated4}') - logger.info( - f'Step #34 - The updated trusted_root_list_original_size_updated4 size is {trusted_root_list_original_size_updated4}') - logger.info( - f'Step #34 - The updated trusted_root_list_original_size_updated3 size is {trusted_root_list_original_size_updated3}') - logger.info( - f'Step #34 from Step #31 - The updated trusted_root_list_original_size_updated size is {trusted_root_list_original_size_updated}') - logger.info( - f'Step #34 from Step #31 - The updated trusted_root_list_original_size size is {trusted_root_list_original_size}') - logger.info( - f'Step #34 from Step #31 - The updated trusted_root_list_original_size size + 1 is {trusted_root_list_original_size + 1}') - - # # Verify that the trusted root list size has increased by 1 - # asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 1, - # "Unexpected number of entries in the TrustedRootCertificates table after update") - - # # Optionally, check if the new certificate is in the updated list - # assert new_root_cert in trusted_root_list_original_updated, \ - # "New root certificate was not added to the trusted root list." + trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) + logger.info(f'Step #34 - The updated num_trusted_roots_original: {trusted_root_list_original_size_updated}') - # ***************************************************************************** + # Verify that the trusted root list size has increased by 1 + # 1 cert was removed on step #33 whne FailSafe expired (from 3 to 2) + asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 2, + "Step #34 - Unexpected number of entries in the TrustedRootCertificates table after update") self.step(35) - # logger.info(f'Step #35 - TH2 ArmFailSafe') cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp, breadcrumb=1) resp = await self.send_single_cmd( dev_ctrl=TH2, node_id=newNodeId+1, - cmd=cmd - ) - logger.info(f'Step #35 - response attributes are: {vars(resp)}') - logger.info(f'step #35 - response attributes are: {resp.errorCode}') + cmd=cmd) # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'BusyWithOtherAdmin'(4) + logger.info(f'Step #35 - ArmFailSafeResponse with ErrorCode as BusyWithOtherAdmin ({resp.errorCode})') asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kBusyWithOtherAdmin, "Failure status returned from arm failsafe") @@ -711,7 +672,6 @@ async def test_TC_CGEN_2_2(self): dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - logger.info(f'Step #36 - response attributes are: {vars(resp)}') logger.info(f'Step #36 - response attributes are: {resp.errorCode}') # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, @@ -723,7 +683,6 @@ async def test_TC_CGEN_2_2(self): dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - logger.info(f'Step #37 - response attributes are: {vars(resp)}') logger.info(f'Step #37 - response attributes are: {resp.errorCode}') # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, @@ -737,50 +696,39 @@ async def test_TC_CGEN_2_2(self): logger.info(f"Step #38 - TH1 saves the current wall time clock in seconds as Tstart: {formatted_time_with_ms} seconds...") self.step(39) - logger.info("Step #39 - Generating a new CSR to update the root certificate...") - - # 5.1 Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) - cmd4 = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) - csr_update4 = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd4) - - # Create new certificate authority for TH1 - # th1_ca_new = self.certificate_authority_manager.NewCertificateAuthority(maximizeCertChains=True) - # th1_fabric_admin_new = th1_ca_new.NewFabricAdmin(vendorId=0xFFF2, fabricId=2) + trusted_root_list_original_updated = await self.read_single_attribute_check_success( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cluster=cluster_opcreds, + attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) + logger.info( + f'Step #39 - The updated size of the num_trusted_roots_original list: {trusted_root_list_original_size_updated}') - # Create the new controller - # th1_new = th1_fabric_admin_new.NewController(nodeId=self.default_controller.nodeId + 1) + # Flow generates a new TrustedRootCertificate - Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) + logger.info("Step #10 repet #5 - Generating a new CSR to update the root certificate...") + cmd = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) - # 5.2 Isue the certificates - th1_certs_new4 = await self.default_controller.IssueNOCChain(csr_update4, self.dut_node_id) - new_root_cert4 = th1_certs_new4.rcacBytes - logger.info(f"Step #39 - RCAC Certificate: {new_root_cert4}") + # Flow generates a new TrustedRootCertificate - Isue the certificates + th1_certs_new = await self.default_controller.IssueNOCChain(csr_update, self.dut_node_id) + new_root_cert = th1_certs_new.rcacBytes - # 5.3 Send command to add new trusted root certificate - cmd4 = cluster_opcreds.Commands.AddTrustedRootCertificate(new_root_cert4) - resp = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd4) - logger.info(f'Step #39 - AddTrustedRootCertificate command response: {resp}') + # Flow generates a new TrustedRootCertificate - Send command to add new trusted root certificate + cmd = cluster_opcreds.Commands.AddTrustedRootCertificate(new_root_cert) + resp = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) self.step(40) - trusted_root_list_original_updated5 = await self.read_single_attribute_check_success( + trusted_root_list_original_updated = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.TrustedRootCertificates) - trusted_root_list_original_size_updated5 = len(trusted_root_list_original_updated5) - logger.info(f'Step #40 - The updated trusted_root_list_original_updated5 is {trusted_root_list_original_updated5}') - logger.info( - f'Step #40 - The updated trusted_root_list_original_size_updated5 size is {trusted_root_list_original_size_updated5}') - logger.info(f'Step #40 from #34 - The updated trusted_root_list_original_updated4 is {trusted_root_list_original_updated4}') - logger.info( - f'Step #40 from #34 - The updated trusted_root_list_original_size_updated4 size is {trusted_root_list_original_size_updated4}') - logger.info( - f'Step #40 from #31 - The updated trusted_root_list_original_size_updated3 size is {trusted_root_list_original_size_updated3}') - logger.info( - f'Step #40 from Step #31 - The updated trusted_root_list_original_size_updated size is {trusted_root_list_original_size_updated}') - logger.info( - f'Step #40 from Step #31 - The updated trusted_root_list_original_size size is {trusted_root_list_original_size}') + trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) logger.info( - f'Step #40 from Step #31 - The updated trusted_root_list_original_size size + 1 is {trusted_root_list_original_size + 1}') + f'Step #40 - The updated size of the num_trusted_roots_original list: {trusted_root_list_original_size_updated}') + # Verify that the trusted root list size has increased by 2 from oririnal trusted root list, Should be 3 + asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 2, + "Step #31 - Unexpected number of entries in the TrustedRootCertificates table after update") self.step(41) target_time = t_start + (maxFailsafe_tmp / 2) @@ -831,71 +779,18 @@ async def test_TC_CGEN_2_2(self): raise Exception("Target time not reached. Cannot proceed.") self.step(44) - # logger.info(f"Step #44 - ") - trusted_root_list_original_updated6 = await self.read_single_attribute_check_success( + trusted_root_list_original_updated = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.TrustedRootCertificates) - trusted_root_list_original_size_updated6 = len(trusted_root_list_original_updated6) - # logger.info(f'Step #44 - The updated trusted_root_list_original_updated6 is {trusted_root_list_original_updated6}') - logger.info( - f'Step #44 - The updated trusted_root_list_original_size_updated6 size is {trusted_root_list_original_size_updated6}') - logger.info( - f'Step #44 from #40 - The updated trusted_root_list_original_size_updated5 size is {trusted_root_list_original_size_updated5}') - logger.info( - f'Step #44 from #34 - The updated trusted_root_list_original_size_updated4 size is {trusted_root_list_original_size_updated4}') - logger.info( - f'Step #44 from #31 - The updated trusted_root_list_original_size_updated3 size is {trusted_root_list_original_size_updated3}') - logger.info( - f'Step #44 from Step #31 - The updated trusted_root_list_original_size_updated size is {trusted_root_list_original_size_updated}') - logger.info( - f'Step #44 from Step #31 - The updated trusted_root_list_original_size size is {trusted_root_list_original_size}') + trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) logger.info( - f'Step #44 from Step #31 - The updated trusted_root_list_original_size size + 1 is {trusted_root_list_original_size + 1}') - - # ************************************************************************** - - # logger.info(f"Step #22 - Sending ArmFailSafe command with expiry set to 0 seconds") - # cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=0) - - # try: - # logger.info(f"Step #22 - Entro al try") - # resp = await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId, cmd=cmd) - # logger.info(f'Step #22 - response attributes are: {vars(resp)}') - - # except Exception as e: # Capturamos cualquier excepción - # # Si hubo un error (timeout o cualquier otra excepción) - # logger.error(f"Step #22 - Error detected: {e}") - - # # Asegúrate de que el error es relacionado con el timeout o la expiración de la sesión PASE - # if 'CHIP Error 0x00000032: Timeout' in str(e): # Si el error es un Timeout - # logger.info("Step #22 - Timeout error detected, restoring PASE session...") - # # Paso 15: Restablecer la sesión PASE - # await self.FindAndEstablishPase(dev_ctrl=TH2, longDiscriminator=longDiscriminator, - # setupPinCode=setup_pin_code, nodeid=newNodeId) - - # # Espera para estabilizar la sesión PASE - # logger.info("Step #22 - Waiting for PASE session to stabilize...") - # await asyncio.sleep(2) - - # # Reintentar enviar el comando ArmFailSafe - # logger.info(f"Step #22 - Reattempting Step #22 - Sending ArmFailSafe command with expiry set to 0 seconds") - # resp = await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId, cmd=cmd) - # logger.info(f'Step #22 - response attributes after retry: {vars(resp)}') - - # ******************************************* - - - # TH2_dut_nodeid = self.dut_node_id+2 - # TH2.ExpireSessions(newNodeId) - # # longDiscriminator, params = await self.OpenCommissioningWindow(self.default_controller, self.dut_node_id) - # # TH2.ResetTestCommissioner() - # # await TH2.CommissionOnNetwork( - # # nodeId=TH2_dut_nodeid, setupPinCode=setup_pin_code, - # # filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=longDiscriminator) - # # FUNCIONA - # await self.FindAndEstablishPase(dev_ctrl=TH2, longDiscriminator=longDiscriminator, - # setupPinCode=setup_pin_code, nodeid=newNodeId) + f'Step #44 - The updated size of the num_trusted_roots_original list: {trusted_root_list_original_size_updated}') + # Verify that the trusted root list size has increased by 2 from oririnal trusted root list, Should be 3 + # asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 2, + # "Step #31 - Unexpected number of entries in the TrustedRootCertificates table after update") + + if __name__ == "__main__": default_matter_test_main() From 36442c5c2203f283733c801c85f30a534e369aa7 Mon Sep 17 00:00:00 2001 From: juandediosg Date: Wed, 29 Jan 2025 18:43:19 -0600 Subject: [PATCH 05/25] Implemented failsafe expiry workaround, cleanup for TC_CGEN_2_2, and step 44 updated --- src/python_testing/TC_CGEN_2_2.py | 149 +++++++++++++++--------------- 1 file changed, 77 insertions(+), 72 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index 60e3107452da96..1df16f677ad5a7 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -101,6 +101,28 @@ async def FindAndEstablishPase(self, longDiscriminator: int, setupPinCode: int, except TimeoutError: asserts.fail("Unable to establish a PASE session to the device") + async def expire_failsafe_timer(self, dev_ctrl, node_id): + """ + Triggering the failsafe expiry to clean up resources like fabric tables, NOCs (Node Operational Credentials), + and trusted root certificates. This is necessary to avoid accumulation of invalid data, which could be caused + by misconfigurations (e.g., incorrect network credentials). + + In CI environments, this function helps bypass long waits by resetting the failsafe timer to 0 seconds, + allowing the test to proceed without unnecessary delays. + + Args: + dev_ctrl: The device controller to send the command. + node_id: The node identifier to which the command is sent. + + Returns: + response: The response from the command sent to the device. + """ + # Resetting the failsafe timer to 0 seconds to clean up resources and avoid waiting in CI. + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=0) + # Sending the command to the DUT (Device Under Test). + resp = await self.send_single_cmd(dev_ctrl=dev_ctrl, node_id=node_id, cmd=cmd) + return resp + def desc_TC_CGEN_2_2(self) -> str: return "[TC-CGEN-2.2] ArmFailSafe command verification [DUT - Server]" @@ -177,8 +199,7 @@ def steps_TC_CGEN_2_2(self) -> list[TestStep]: async def test_TC_CGEN_2_2(self): cluster_opcreds = Clusters.OperationalCredentials cluster_cgen = Clusters.GeneralCommissioning - - maxFailsafe_tmp = 10 + # maxFailsafe_tmp = 60 self.step(0) @@ -204,7 +225,7 @@ async def test_TC_CGEN_2_2(self): logger.info(f'Step #2 - The MaxCumulativeFailsafeSeconds (max_fail_safe): {maxFailsafe}') self.step(3) - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp, breadcrumb=1) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe, breadcrumb=1) resp = await self.send_single_cmd( dev_ctrl=self.default_controller, node_id=self.dut_node_id, @@ -258,16 +279,14 @@ async def test_TC_CGEN_2_2(self): "Step #6 - New root certificate was not added to the trusted root list." self.step(7) - # Using maxFailsafe_tmp=10 instead of maxFailsafe=PIXIT.CGEN.FailsafeExpiryLengthSeconds - logger.info( - f"Step #7 - Waiting for Failsafe timer to expire for PIXIT.CGEN.FailsafeExpiryLengthSeconds (max_fail_safe): {maxFailsafe_tmp} seconds...") - start_time = time.time() - - # Wait for the maximum failsafe time - Adding a 2 seconds buffer - await asyncio.sleep(maxFailsafe_tmp + 1) + # Step 7 - Bypassing the wait for the failsafe timer to expire (PIXIT.CGEN.FailsafeExpiryLengthSeconds) + # Originally, this step required waiting for the failsafe timer to expire after a specific duration defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds. + # However, in CI environments, this wait introduces unnecessary delays. To avoid this, we use the `expire_failsafe_timer` function, + # which immediately expires the failsafe timer (sets it to 0), bypassing the natural wait and speeding up the test execution. - elapsed_time = time.time() - start_time - logger.info(f"Step #7 - Failsafe timer (max_fail_safe) expired after {elapsed_time} seconds.") + # This function bypasses the wait for the FailsafeTimer to expire for TH1 as originally defined in the test plan. + resp = await self.expire_failsafe_timer(dev_ctrl=self.default_controller, node_id=self.dut_node_id) + logger.info(f'Step # 7 - Failsafe timer expiration bypassed for TH1 by setting expiryLengthSeconds to 0. Test continues without the original wait.') self.step(8) trusted_root_list_original_after_wait = await self.read_single_attribute_check_success( @@ -291,7 +310,7 @@ async def test_TC_CGEN_2_2(self): self.step(10) logger.info(f'Step #10 - TH1 repeats steps 3 through 5') logger.info(f'Step #10 repet #3 - TH1 sends ArmFailSafe') - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp, breadcrumb=1) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe, breadcrumb=1) resp = await self.send_single_cmd( dev_ctrl=self.default_controller, node_id=self.dut_node_id, @@ -383,7 +402,7 @@ async def test_TC_CGEN_2_2(self): logger.info(f'Step #13 - Open Commissioning Window params with vars: {vars(params)}') self.step(14) - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe) resp = await self.send_single_cmd( dev_ctrl=self.default_controller, node_id=self.dut_node_id, @@ -575,7 +594,7 @@ async def test_TC_CGEN_2_2(self): self.step(29) # logger.info("Step #29 - TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds and the Breadcrumb value as 1") - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp, breadcrumb=1) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe, breadcrumb=1) resp = await self.send_single_cmd( dev_ctrl=self.default_controller, node_id=self.dut_node_id, @@ -615,7 +634,7 @@ async def test_TC_CGEN_2_2(self): "Step #31 - Unexpected number of entries in the TrustedRootCertificates table after update") self.step(32) - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe) resp = await self.send_single_cmd( dev_ctrl=self.default_controller, node_id=self.dut_node_id, @@ -629,17 +648,14 @@ async def test_TC_CGEN_2_2(self): logger.info(f'Step #32 - ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') self.step(33) - # Using maxFailsafe_tmp=10 instead of maxFailsafe=PIXIT.CGEN.FailsafeExpiryLengthSeconds - logger.info( - f"Step #33 - Waiting for Failsafe timer to expire for PIXIT.CGEN.FailsafeExpiryLengthSeconds (max_fail_safe): {maxFailsafe_tmp} seconds...") - start_time = time.time() - await asyncio.sleep(1) + # Step 33 - Bypassing the wait for the failsafe timer to expire (PIXIT.CGEN.FailsafeExpiryLengthSeconds) + # Originally, this step required waiting for the failsafe timer to expire after a specific duration defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds. + # However, in CI environments, this wait introduces unnecessary delays. To avoid this, we use the `expire_failsafe_timer` function, + # which immediately expires the failsafe timer (sets it to 0), bypassing the natural wait and speeding up the test execution. - # # Wait for the maximum failsafe time - Adding a 2 seconds buffer - # await asyncio.sleep(maxFailsafe_tmp + 1) - - # elapsed_time = time.time() - start_time - # logger.info(f"Step #33 - Failsafe timer (max_fail_safe) expired after {elapsed_time} seconds.") + # This function bypasses the wait for the FailsafeTimer to expire for TH2 as originally defined in the test plan. + resp = await self.expire_failsafe_timer(dev_ctrl=TH2, node_id=newNodeId+1) + logger.info(f'Step # 33 - Failsafe timer expiration bypassed for TH2 by setting expiryLengthSeconds to 0. Test continues without the original wait.') self.step(34) trusted_root_list_original_updated = await self.read_single_attribute_check_success( @@ -649,20 +665,18 @@ async def test_TC_CGEN_2_2(self): attribute=cluster_opcreds.Attributes.TrustedRootCertificates) trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) logger.info(f'Step #34 - The updated num_trusted_roots_original: {trusted_root_list_original_size_updated}') - - # Verify that the trusted root list size has increased by 1 - # 1 cert was removed on step #33 whne FailSafe expired (from 3 to 2) + # Verify that the trusted root list size asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 2, "Step #34 - Unexpected number of entries in the TrustedRootCertificates table after update") self.step(35) - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp, breadcrumb=1) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe, breadcrumb=1) resp = await self.send_single_cmd( dev_ctrl=TH2, node_id=newNodeId+1, cmd=cmd) # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'BusyWithOtherAdmin'(4) - logger.info(f'Step #35 - ArmFailSafeResponse with ErrorCode as BusyWithOtherAdmin ({resp.errorCode})') + logger.info(f'Step #35 - TH2 ArmFailSafeResponse with ErrorCode as BusyWithOtherAdmin ({resp.errorCode})') asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kBusyWithOtherAdmin, "Failure status returned from arm failsafe") @@ -678,7 +692,7 @@ async def test_TC_CGEN_2_2(self): "Failure status returned from arm failsafe") self.step(37) - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe) resp = await self.send_single_cmd( dev_ctrl=self.default_controller, node_id=self.dut_node_id, @@ -689,11 +703,11 @@ async def test_TC_CGEN_2_2(self): "Failure status returned from arm failsafe") self.step(38) - t_start = time.time() - formatted_time = datetime.fromtimestamp(t_start).strftime('%m-%d %H:%M:%S') - milliseconds = str(round((t_start % 1) * 1000)).zfill(3) - formatted_time_with_ms = f"{formatted_time}.{milliseconds}" - logger.info(f"Step #38 - TH1 saves the current wall time clock in seconds as Tstart: {formatted_time_with_ms} seconds...") + # Step 38 - Skipped + # Originally, this step saved the current wall time clock in seconds as Tstart to use it for later comparisons. + # However, with the failsafe expiration workaround (expiryLengthSeconds=0), we no longer rely on this timestamp, + # as the failsafe timer is immediately expired, bypassing the need for time calculations or waits. + logger.info("Step 38 skipped: Saving Tstart (current wall time clock) is bypassed due to failsafe expiration workaround.") self.step(39) trusted_root_list_original_updated = await self.read_single_attribute_check_success( @@ -731,25 +745,15 @@ async def test_TC_CGEN_2_2(self): "Step #31 - Unexpected number of entries in the TrustedRootCertificates table after update") self.step(41) - target_time = t_start + (maxFailsafe_tmp / 2) - formatted_target_time = datetime.fromtimestamp(target_time).strftime('%m-%d %H:%M:%S') - target_milliseconds = str(round((target_time % 1) * 1000)).zfill(3) - formatted_target_time_with_ms = f"{formatted_target_time}.{target_milliseconds}" - - logger.info(f"Step #41 - Target time (Tstart + maxFailsafe / 2): {formatted_target_time_with_ms}") - - while time.time() < target_time: - await asyncio.sleep(0.1) # Short sleep to avoid busy-waiting - - # Validation: check if the current time reached the target time - if time.time() >= target_time: - logger.info("Step #41 - Target time reached. Proceeding...") - else: - logger.error("Step #41 - Failed to reach target time.") - raise Exception("Target time not reached. Cannot proceed.") + # Step 41 - Skipped + # Previously, this step waited until the current wall time clock reached Tstart + maxFailsafe/2. + # However, with the failsafe expiration workaround (expiryLengthSeconds=0), this wait is unnecessary and has been skipped, + # as the failsafe timer is immediately expired and the test proceeds without delay. + logger.info( + "Step 41 skipped: Waiting for the wall time clock to reach Tstart + maxFailsafe/2 is bypassed due to failsafe expiration workaround.") self.step(42) - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_tmp) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe) resp = await self.send_single_cmd( dev_ctrl=self.default_controller, node_id=self.dut_node_id, @@ -761,24 +765,25 @@ async def test_TC_CGEN_2_2(self): "Failure status returned from arm failsafe") self.step(43) - target_time_full = t_start + maxFailsafe_tmp - formatted_target_time_full = datetime.fromtimestamp(target_time_full).strftime('%m-%d %H:%M:%S') - target_milliseconds_full = str(round((target_time_full % 1) * 1000)).zfill(3) - formatted_target_time_with_ms_full = f"{formatted_target_time_full}.{target_milliseconds_full}" - - logger.info(f"Step #43 - Target time (Tstart + maxFailsafe): {formatted_target_time_with_ms_full}") - - while time.time() < target_time_full: - await asyncio.sleep(0.1) # Short sleep to avoid busy-waiting + # Step 43 - Bypassing the wait for the failsafe timer to expire + # Originally, this step required waiting until the current wall time clock reached Tstart + maxFailsafe. + # However, in CI environments, this wait introduces unnecessary delays. To avoid this, we use the `expire_failsafe_timer` function, + # which immediately expires the failsafe timer (sets it to 0), bypassing the natural wait and speeding up the test execution. - # Validation: check if the current time reached the target time - if time.time() >= target_time_full: - logger.info("Step #43 - Target time reached. Proceeding...") - else: - logger.error("Step #43 - Failed to reach target time.") - raise Exception("Target time not reached. Cannot proceed.") + # This function bypasses the wait for the FailsafeTimer to expire for TH1 as originally defined in the test plan. + resp = await self.expire_failsafe_timer(dev_ctrl=self.default_controller, node_id=self.dut_node_id) + logger.info(f'Step # 43 - Failsafe timer expiration bypassed for TH1 by setting expiryLengthSeconds to 0. Test continues without the original wait.') self.step(44) + # Remove Fabric from TH2 to ensure it is correctly cleaned up and that the root certificates are aligned with expectations. + # Read the CurrentFabricIndex attribute from TH2 (the second controller) + fabric_idx = await self.read_single_attribute_check_success(dev_ctrl=TH2, node_id=newNodeId+1, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.CurrentFabricIndex) + logger.info(f'Step #44 - TH2 CurrentFabricIndex attribute: {fabric_idx}') + # Remove the fabric from TH2 (second controller) + cmd = Clusters.OperationalCredentials.Commands.RemoveFabric(fabricIndex=fabric_idx) + await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId+1, cmd=cmd) + + # The expected number of root certificates should be 1 after removing the fabric trusted_root_list_original_updated = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, @@ -787,9 +792,9 @@ async def test_TC_CGEN_2_2(self): trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) logger.info( f'Step #44 - The updated size of the num_trusted_roots_original list: {trusted_root_list_original_size_updated}') - # Verify that the trusted root list size has increased by 2 from oririnal trusted root list, Should be 3 - # asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 2, - # "Step #31 - Unexpected number of entries in the TrustedRootCertificates table after update") + # Verify that the trusted root list size from oririnal trusted root list is 1 + asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size, + "Step #44 - Unexpected number of entries in the TrustedRootCertificates table after update") if __name__ == "__main__": From f657b97fc6b47020e53aa47558e88e26c24d3654 Mon Sep 17 00:00:00 2001 From: juandediosg Date: Thu, 30 Jan 2025 12:07:10 -0600 Subject: [PATCH 06/25] Updated code-lints and fixed f-string warnings --- src/python_testing/TC_CGEN_2_2.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index 1df16f677ad5a7..2378077f1b1774 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -34,11 +34,8 @@ # quiet: true # === END CI TEST ARGUMENTS === -import asyncio import logging import random -import time -from datetime import datetime from mobly import asserts @@ -286,7 +283,7 @@ async def test_TC_CGEN_2_2(self): # This function bypasses the wait for the FailsafeTimer to expire for TH1 as originally defined in the test plan. resp = await self.expire_failsafe_timer(dev_ctrl=self.default_controller, node_id=self.dut_node_id) - logger.info(f'Step # 7 - Failsafe timer expiration bypassed for TH1 by setting expiryLengthSeconds to 0. Test continues without the original wait.') + logger.info('Step # 7 - Failsafe timer expiration bypassed for TH1 by setting expiryLengthSeconds to 0. Test continues without the original wait.') self.step(8) trusted_root_list_original_after_wait = await self.read_single_attribute_check_success( @@ -308,8 +305,8 @@ async def test_TC_CGEN_2_2(self): asserts.assert_equal(breadcrumb_info, 0, "Breadcrumb value is not 0 after waiting for failsafe timer") self.step(10) - logger.info(f'Step #10 - TH1 repeats steps 3 through 5') - logger.info(f'Step #10 repet #3 - TH1 sends ArmFailSafe') + logger.info('Step #10 - TH1 repeats steps 3 through 5') + logger.info('Step #10 repet #3 - TH1 sends ArmFailSafe') cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe, breadcrumb=1) resp = await self.send_single_cmd( dev_ctrl=self.default_controller, @@ -374,8 +371,8 @@ async def test_TC_CGEN_2_2(self): logger.info(f'Step #11 - ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') self.step(12) - logger.info(f'Step #12 - TH1 repeats steps 8 through 9') - logger.info(f'Step #12 repet #8 - TH1 reads the TrustedRootCertificates') + logger.info('Step #12 - TH1 repeats steps 8 through 9') + logger.info('Step #12 repet #8 - TH1 reads the TrustedRootCertificates') trusted_root_list_original_updated = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, @@ -387,7 +384,7 @@ async def test_TC_CGEN_2_2(self): asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size, "Step #12 - Unexpected number of entries in the TrustedRootCertificates table after update") - logger.info(f'Step #12 repet #9 - TH1 reads the Breadcrumb attribute') + logger.info('Step #12 repet #9 - TH1 reads the Breadcrumb attribute') breadcrumb_info = await self.read_single_attribute_check_success( cluster=cluster_cgen, attribute=cluster_cgen.Attributes.Breadcrumb) @@ -426,10 +423,10 @@ async def test_TC_CGEN_2_2(self): longDiscriminator=longDiscriminator, setupPinCode=setup_pin_code, nodeid=newNodeId) - logger.info(f'Step #15 - TH2 successfully establish PASE session completed') + logger.info('Step #15 - TH2 successfully establish PASE session completed') self.step(16) - logger.info(f'Step #16 - TH2 Generating a new CSR to update the root certificate...') + logger.info('Step #16 - TH2 Generating a new CSR to update the root certificate...') # Flow generates a new TrustedRootCertificate - Request CSR (Certificate Signing Request) cmd = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) th2_csr = await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId, cmd=cmd) @@ -494,7 +491,7 @@ async def test_TC_CGEN_2_2(self): f'Step #21 - TH2 Commissioning stage SetTestCommissionerPrematureCompleteAfter enum: {kFindOperationalForCommissioningComplete}') resp = TH2.SetTestCommissionerPrematureCompleteAfter(kFindOperationalForCommissioningComplete) - logger.info(f'Step #21 - TH2 Commissioning DOES NOT send the CommissioningComplete command') + logger.info('Step #21 - TH2 Commissioning DOES NOT send the CommissioningComplete command') self.step(22) logger.info("Step #22 - TH1 Waiting for PASE session to stabilize...") @@ -503,7 +500,7 @@ async def test_TC_CGEN_2_2(self): longDiscriminator=longDiscriminator, setupPinCode=setup_pin_code, nodeid=newNodeId) - logger.info(f'Step #22 - TH2 successfully establish PASE session completed') + logger.info('Step #22 - TH2 successfully establish PASE session completed') try: # Verify DUT cannot proceed because the session has not been fully commissioned, leading to a timeout error @@ -554,7 +551,7 @@ async def test_TC_CGEN_2_2(self): basic_commissioning_info = await self.read_single_attribute_check_success(cluster=Clusters.GeneralCommissioning, attribute=Clusters.GeneralCommissioning.Attributes.BasicCommissioningInfo) logger.info(f'Step #26 - basic_commissioning_info: {basic_commissioning_info}') - logger.info(f'Step #26 - Fully commissioned started') + logger.info('Step #26 - Fully commissioned started') resp = await TH2.CommissionOnNetwork( nodeId=newNodeId+1, setupPinCode=setup_pin_code, filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=longDiscriminator) @@ -655,7 +652,7 @@ async def test_TC_CGEN_2_2(self): # This function bypasses the wait for the FailsafeTimer to expire for TH2 as originally defined in the test plan. resp = await self.expire_failsafe_timer(dev_ctrl=TH2, node_id=newNodeId+1) - logger.info(f'Step # 33 - Failsafe timer expiration bypassed for TH2 by setting expiryLengthSeconds to 0. Test continues without the original wait.') + logger.info('Step # 33 - Failsafe timer expiration bypassed for TH2 by setting expiryLengthSeconds to 0. Test continues without the original wait.') self.step(34) trusted_root_list_original_updated = await self.read_single_attribute_check_success( @@ -772,7 +769,7 @@ async def test_TC_CGEN_2_2(self): # This function bypasses the wait for the FailsafeTimer to expire for TH1 as originally defined in the test plan. resp = await self.expire_failsafe_timer(dev_ctrl=self.default_controller, node_id=self.dut_node_id) - logger.info(f'Step # 43 - Failsafe timer expiration bypassed for TH1 by setting expiryLengthSeconds to 0. Test continues without the original wait.') + logger.info('Step # 43 - Failsafe timer expiration bypassed for TH1 by setting expiryLengthSeconds to 0. Test continues without the original wait.') self.step(44) # Remove Fabric from TH2 to ensure it is correctly cleaned up and that the root certificates are aligned with expectations. From 437f99aed19175483ba2127544d0c1a0010f7b18 Mon Sep 17 00:00:00 2001 From: juandediosg Date: Thu, 30 Jan 2025 13:57:14 -0600 Subject: [PATCH 07/25] Fixed import order and to conflicts for Restyled --- src/python_testing/TC_CGEN_2_2.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index 2378077f1b1774..5913914a7bfb89 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -37,14 +37,12 @@ import logging import random -from mobly import asserts - import chip.clusters as Clusters import chip.discovery as Discovery from chip import ChipDeviceCtrl from chip.exceptions import ChipStackError from chip.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main - +from mobly import asserts # Create logger logger = logging.getLogger(__name__) From 045a72af7ea1f7522b60db73081f02b7e6daba05 Mon Sep 17 00:00:00 2001 From: juandediosg Date: Thu, 6 Feb 2025 18:38:11 -0600 Subject: [PATCH 08/25] Code review done, changes and refactoring implemented --- src/python_testing/TC_CGEN_2_2.py | 883 +++++++++++++++++------------- 1 file changed, 502 insertions(+), 381 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index 5913914a7bfb89..9e0a52378e206b 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2022 Project CHIP Authors +# Copyright (c) 2025 Project CHIP Authors # All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,8 +34,11 @@ # quiet: true # === END CI TEST ARGUMENTS === +import asyncio import logging import random +import time +from datetime import datetime, timezone import chip.clusters as Clusters import chip.discovery as Discovery @@ -49,60 +52,17 @@ class TC_CGEN_2_2(MatterBaseTest): - async def FindAndEstablishPase(self, longDiscriminator: int, setupPinCode: int, nodeid: int, dev_ctrl: ChipDeviceCtrl = None): - """ - Establishes a PASE session to a device using longDiscriminator. - This method will discover commissionable nodes and filter by longDiscriminator, - then attempts to establish a PASE session with setupPinCode and nodeid. + cluster_opcreds = Clusters.OperationalCredentials + cluster_cgen = Clusters.GeneralCommissioning - If no device controller is provided, the default controller will be used. - - Args: - longDiscriminator (int): The long discriminator used to identify the device. - setupPinCode (int): The setup PIN code for establishing the session. - nodeid (int): The node ID for the device. - dev_ctrl (ChipDeviceCtrl, optional): The device controller. If None, - the default controller is used. - - Raises: - TimeoutError: If unable to establish the PASE session within the timeout. - ChipStackError: If an error occurs during the establishment of the PASE session. - """ - if dev_ctrl is None: - dev_ctrl = self.default_controller - - devices = await dev_ctrl.DiscoverCommissionableNodes( - filterType=Discovery.FilterType.LONG_DISCRIMINATOR, filter=longDiscriminator, stopOnFirst=False) - - # For some reason, the devices returned here aren't filtered, so filter ourselves - device = next( - filter( - lambda d: d.commissioningMode == Discovery.FilterType.LONG_DISCRIMINATOR - and d.longDiscriminator == longDiscriminator, devices - ) - ) - - for a in device.addresses: - try: - await dev_ctrl.EstablishPASESessionIP(ipaddr=a, setupPinCode=setupPinCode, - nodeid=nodeid, port=device.port) - break - except ChipStackError: - continue - - try: - dev_ctrl.GetConnectedDeviceSync(nodeid=nodeid, allowPASE=True, timeoutMs=1000) - except TimeoutError: - asserts.fail("Unable to establish a PASE session to the device") - - async def expire_failsafe_timer(self, dev_ctrl, node_id): - """ + async def expire_failsafe_timer(self, dev_ctrl, node_id, expiration_time_seconds): + ''' Triggering the failsafe expiry to clean up resources like fabric tables, NOCs (Node Operational Credentials), and trusted root certificates. This is necessary to avoid accumulation of invalid data, which could be caused by misconfigurations (e.g., incorrect network credentials). - In CI environments, this function helps bypass long waits by resetting the failsafe timer to 0 seconds, + In CI environments, this function helps bypass long waits by resetting the failsafe timer to 1 seconds, allowing the test to proceed without unnecessary delays. Args: @@ -111,247 +71,332 @@ async def expire_failsafe_timer(self, dev_ctrl, node_id): Returns: response: The response from the command sent to the device. - """ - # Resetting the failsafe timer to 0 seconds to clean up resources and avoid waiting in CI. - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=0) + ''' + # Buffer for latency + buffer_latency = .5 + + # Resetting the failsafe timer to 1 seconds to clean up resources and avoid waiting in CI. + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=expiration_time_seconds) # Sending the command to the DUT (Device Under Test). resp = await self.send_single_cmd(dev_ctrl=dev_ctrl, node_id=node_id, cmd=cmd) - return resp - def desc_TC_CGEN_2_2(self) -> str: - return "[TC-CGEN-2.2] ArmFailSafe command verification [DUT - Server]" + start_time = time.time() + # Wait for the expiration time (fail-safe will expire after this time), plus buffer for latency + logger.info(f'Waiting for {expiration_time_seconds} seconds (with additional buffer {buffer_latency} seconds for latency).') + await asyncio.sleep(expiration_time_seconds + buffer_latency) + elapsed_time = time.time() - start_time + logger.info( + f'Failsafe timer expired after: {elapsed_time:.2f} seconds (including {buffer_latency} seconds for latency buffer).') - def steps_TC_CGEN_2_2(self) -> list[TestStep]: - steps = [ - TestStep(0, "Commissioning, already done", is_commissioning=True), - TestStep(1, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster - and saves the number of list items as numTrustedRootsOriginal."""), - TestStep(2, """TH1 reads the BasicCommissioningInfo attribute - and saves the MaxCumulativeFailsafeSeconds as maxFailsafe."""), - TestStep(3, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds - and the Breadcrumb value as 1."""), - TestStep(4, """TH1 reads the Breadcrumb attribute."""), - TestStep(5, """TH1 generates a new TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. - TH1 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate."""), - TestStep(6, """TH1 reads the TrustedRootCertificate attribute."""), - TestStep(7, """TH1 waits for PIXIT.CGEN.FailsafeExpiryLengthSeconds to ensure the failsafe timer has expired."""), - TestStep(8, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster."""), - TestStep(9, """TH1 reads the Breadcrumb attribute and verify that the breadcrumb attribute is 0."""), - TestStep(10, """TH1 repeats steps #3 through #5."""), - TestStep(11, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0."""), - TestStep(12, """TH1 Repeat steps 8 through 9."""), - TestStep(13, """TH1 sends the OpenCommissioningWindow command to the Administrator Commissioning cluster."""), - TestStep(14, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds"""), - TestStep(15, """TH2 opens a PASE connection to the DUT."""), - TestStep(16, """TH2 obtains or generates a TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. - TH2 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate."""), - TestStep(17, """TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0."""), - TestStep(18, """TH1 reads the NOCs attribute from the Node Operational Credentials cluster using a non-fabric-filtered read - and saves the returned list as nocs."""), - TestStep(19, """TH1 reads the Fabrics attribute from the Node Operational Credentials cluster using a non-fabric-filtered read - and saves the returned list as fabrics."""), - TestStep(20, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster - and saves the returned list as trustedroots."""), - TestStep(21, """TH2 starts commissioning the DUT. It performs all steps up to establishing a CASE connection, - but DOES NOT send the CommissioningComplete command."""), - TestStep(22, """TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0."""), - TestStep(23, """TH1 reads the NOCs attribute from the Node Operational Credentials cluster using a non-fabric-filtered read."""), - TestStep(24, """TH1 reads the Fabrics attribute from the Node Operational Credentials cluster using a non-fabric-filtered read."""), - TestStep(25, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster."""), - TestStep(26, """TH2 fully commissions the DUT."""), - TestStep(27, """TH2 obtains or generates a TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. - TH2 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate."""), - TestStep(28, """TH1 reads the Fabrics attribute from the Node Operational Credentials cluster using a non-fabric-filtered read."""), - TestStep(29, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds - and the Breadcrumb value as 1."""), - TestStep(30, """TH1 obtains or generates a new TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. - TH1 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate."""), - TestStep(31, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster."""), - TestStep(32, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to maxFailsafe"""), - TestStep(33, """TH1 waits for PIXIT.CGEN.FailsafeExpiryLengthSeconds - Verify waits maxFailsafe"""), - TestStep(34, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster - Verify that the number of items in the returned list is still numTrustedRootsOriginal + 1"""), - TestStep(35, """TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds - and the Breadcrumb value as 1."""), - TestStep(36, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0 - Verify that the DUT responds with ..."""), - TestStep(37, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to maxFailsafe - Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0)"""), - TestStep(38, """TH1 saves the current wall time clock in seconds as Tstart"""), - TestStep(39, """TH1 obtains or generates a new TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. - TH1 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate."""), - TestStep(40, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster."""), - TestStep(41, """TH1 waits until the current wall time clock is Tstart + maxFailsafe/2."""), - TestStep(42, """TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to maxFailsafe."""), - TestStep(43, """TH1 waits until the current wall time clock is Tstart + maxFailsafe. maxFailsafe is the maximum amount of time a failsafe can be armed for, - so the failsafe is required to time out at this point, despite having been re-armed in step 42."""), - TestStep(44, """TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster."""), - ] - return steps + return resp - @async_test_body - async def test_TC_CGEN_2_2(self): - cluster_opcreds = Clusters.OperationalCredentials - cluster_cgen = Clusters.GeneralCommissioning - # maxFailsafe_tmp = 60 + async def run_steps_3_to_5(self, maxFailsafe: int, is_first_run: bool): + ''' + Executes steps 3 through 5, with optional Test Step. - self.step(0) + Step 3: Sends an ArmFailSafe command to the DUT and verifies the response. + Step 4: Reads the Breadcrumb attribute from the DUT and verifies its value. + Step 5: Generates a new CSR (Certificate Signing Request) to update the root certificate, + issues the new certificates, and adds the new root certificate to the DUT. + Step 10: If is_first_run is False, repeats steps #3, #4 and #5 on step #10. - # Read the Spteps - self.step(1) - trusted_root_list_original = await self.read_single_attribute_check_success( - dev_ctrl=self.default_controller, - node_id=self.dut_node_id, - cluster=cluster_opcreds, - attribute=cluster_opcreds.Attributes.TrustedRootCertificates) - trusted_root_list_original_size = len(trusted_root_list_original) - logger.info(f'Step #1 - The size of the origina num_trusted_roots_original list: {trusted_root_list_original_size}') - # Verify that original list has 1 certificate - asserts.assert_equal(trusted_root_list_original_size, 1, - "Unexpected number of entries in the TrustedRootCertificates table") - - self.step(2) - basic_commissioning_info = await self.read_single_attribute_check_success( - cluster=cluster_cgen, - attribute=cluster_cgen.Attributes.BasicCommissioningInfo) - maxFailsafe = basic_commissioning_info.maxCumulativeFailsafeSeconds - logger.info(f'Step #2 - The MaxCumulativeFailsafeSeconds (max_fail_safe): {maxFailsafe}') + Args: + maxFailsafe (int): The maximum failsafe timeout value in seconds to be used in Step 3. + is_first_run (bool): A flag to control whether the step should be executed or skipped. - self.step(3) + Returns: + new_root_cert + ''' + if is_first_run: + self.step(3) cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe, breadcrumb=1) resp = await self.send_single_cmd( dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - # logger.info(f'Step #3 - response attributes are: {vars(resp)}') # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, - "Step #3 - Failure status returned from arm failsafe") + "Failure status returned from arm failsafe") # Verify that DebugText is empty or has a maximum length of 512 characters debug_text = resp.debugText - assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" - logger.info(f'Step #3 - ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') - - self.step(4) + asserts.assert_true(debug_text == '' or len(debug_text) <= 512, + "debugText must be empty or have a maximum length of 512 characters.") + if is_first_run: + logger.info(f'Step #3: ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') + else: + logger.info(f'Step #10 - Repeated Step #3: ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') + + if is_first_run: + self.step(4) breadcrumb_info = await self.read_single_attribute_check_success( - cluster=cluster_cgen, - attribute=cluster_cgen.Attributes.Breadcrumb) - logger.info(f'Step #4 - The Breadcrumb attribute: {breadcrumb_info}') + cluster=self.cluster_cgen, + attribute=self.cluster_cgen.Attributes.Breadcrumb) asserts.assert_equal(breadcrumb_info, 1, - "Step #6 - The Breadcrumb attribute is not 1") - - self.step(5) - logger.info("Step #5.1 - Generating a new CSR to update the root certificate...") + "The Breadcrumb attribute is not 1") + if is_first_run: + logger.info(f'Step #4: The Breadcrumb attribute: {breadcrumb_info}') + else: + logger.info(f'Step #10 - Repeated Step #4: The Breadcrumb attribute: {breadcrumb_info}') + + if is_first_run: + self.step(5) # 5.1 Request CSR (Certificate Signing Request) - cmd = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) + cmd = self.cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) csr_update = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) + # NOTE: This step is required because RCAC generation is not exposed separately in the API. + # The default controller forces re-generation of root certificates when new ones are issued. + # Ideally, a root certificate would be self-signed and not require a CSR, but the current APIs don’t support this. # 5.2 Isue the certificates th1_certs_new = await self.default_controller.IssueNOCChain(csr_update, self.dut_node_id) new_root_cert = th1_certs_new.rcacBytes + # NOTE: The IssueNOCChain generates a new RCAC because it is initialized to produce maximally sized certificates. # 5.3 Send command to add new trusted root certificate - cmd = cluster_opcreds.Commands.AddTrustedRootCertificate(new_root_cert) + cmd = self.cluster_opcreds.Commands.AddTrustedRootCertificate(new_root_cert) resp = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - self.step(6) - trusted_root_list_original_updated = await self.read_single_attribute_check_success( - dev_ctrl=self.default_controller, - node_id=self.dut_node_id, - cluster=cluster_opcreds, - attribute=cluster_opcreds.Attributes.TrustedRootCertificates) - trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) - logger.info(f'Step #6 - The updated size of the num_trusted_roots_original list: {trusted_root_list_original_size_updated}') + if is_first_run: + logger.info('Step #5: Completed - CSR generated, certificates issued, and new trusted root certificate added.') + else: + logger.info('Step #10 - Repeated Step #5: Completed - CSR generated, certificates issued, and new trusted root certificate added.') - # Verify that the trusted root list size has increased by 1 - asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 1, - "Step #6 - Unexpected number of entries in the TrustedRootCertificates table after update") + return new_root_cert - # Optionally, check if the new certificate is in the updated list - assert new_root_cert in trusted_root_list_original_updated, \ - "Step #6 - New root certificate was not added to the trusted root list." + async def run_steps_8_to_9(self, trusted_root_list_original_size: int, is_first_run: bool): + ''' + Executes steps 8 through 9, with optional Test Step. + + Step 8: Waits for the failsafe timeout and reads the TrustedRootCertificates attribute. + Step 9: Reads the Breadcrumb attribute from the DUT and verifies its value after the failsafe timeout. + Step 12: If is_first_run is False, repeats steps #8 and #9 on step #12. - self.step(7) - # Step 7 - Bypassing the wait for the failsafe timer to expire (PIXIT.CGEN.FailsafeExpiryLengthSeconds) - # Originally, this step required waiting for the failsafe timer to expire after a specific duration defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds. - # However, in CI environments, this wait introduces unnecessary delays. To avoid this, we use the `expire_failsafe_timer` function, - # which immediately expires the failsafe timer (sets it to 0), bypassing the natural wait and speeding up the test execution. - # This function bypasses the wait for the FailsafeTimer to expire for TH1 as originally defined in the test plan. - resp = await self.expire_failsafe_timer(dev_ctrl=self.default_controller, node_id=self.dut_node_id) - logger.info('Step # 7 - Failsafe timer expiration bypassed for TH1 by setting expiryLengthSeconds to 0. Test continues without the original wait.') + Args: + trusted_root_list_original_size (int): The original number of trusted root certificates before the failsafe timeout. + is_first_run (bool): A flag to control whether the step should be executed or skipped. - self.step(8) + Returns: + None + ''' + if is_first_run: + self.step(8) trusted_root_list_original_after_wait = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, - cluster=cluster_opcreds, - attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + cluster=self.cluster_opcreds, + attribute=self.cluster_opcreds.Attributes.TrustedRootCertificates) trusted_root_list_original_size_after_wait = len(trusted_root_list_original_after_wait) - logger.info( - f'Step #8 - The size of the num_trusted_roots_original list after waiting for failsafe timeout: {trusted_root_list_original_size_after_wait}') asserts.assert_equal(trusted_root_list_original_size_after_wait, trusted_root_list_original_size, - "Step #8 - Unexpected number of entries in the TrustedRootCertificates table after wait") + "Unexpected number of entries in the TrustedRootCertificates table after wait") + if is_first_run: + logger.info( + f'Step #8: The size of the num_trusted_roots_original list after waiting for failsafe timeout: {trusted_root_list_original_size_after_wait}') + else: + logger.info( + f'Step #12 - Repeated Step #8: The size of the num_trusted_roots_original list after waiting for failsafe timeout: {trusted_root_list_original_size_after_wait}') - self.step(9) + if is_first_run: + self.step(9) breadcrumb_info = await self.read_single_attribute_check_success( - cluster=cluster_cgen, - attribute=cluster_cgen.Attributes.Breadcrumb) - logger.info(f'Step #9 - After waiting for failsafe timeout the Breadcrumb attribute: {breadcrumb_info}') + cluster=self.cluster_cgen, + attribute=self.cluster_cgen.Attributes.Breadcrumb) asserts.assert_equal(breadcrumb_info, 0, "Breadcrumb value is not 0 after waiting for failsafe timer") + if is_first_run: + logger.info(f'Step #9: After waiting for failsafe timeout the Breadcrumb attribute: {breadcrumb_info}') + else: + logger.info( + f'Step #12 - Repeated Step #9: After waiting for failsafe timeout the Breadcrumb attribute: {breadcrumb_info}') - self.step(10) - logger.info('Step #10 - TH1 repeats steps 3 through 5') - logger.info('Step #10 repet #3 - TH1 sends ArmFailSafe') - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe, breadcrumb=1) - resp = await self.send_single_cmd( - dev_ctrl=self.default_controller, - node_id=self.dut_node_id, - cmd=cmd) - # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) - asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, - "Failure status returned from arm failsafe") - # Verify that DebugText is empty or has a maximum length of 512 characters - debug_text = resp.debugText - assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" - logger.info(f'Step #10 - ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') + def get_current_utc_time_str(self): + ''' + Get the current time in UTC and return it as a formatted string. - breadcrumb_info = await self.read_single_attribute_check_success( - cluster=cluster_cgen, - attribute=cluster_cgen.Attributes.Breadcrumb) - logger.info(f'Step #10 repet #4 - The Breadcrumb attribute: {breadcrumb_info}') - asserts.assert_equal(breadcrumb_info, 1, - "Step #10 - The Breadcrumb attribute is not 1") + The format of the returned string will be 'dd-mm hh:mm:ss.sss', + where 'dd' is the day, 'mm' is the month, 'hh' is the hour, + 'mm' is the minute, 'ss' is the second, and 'sss' are the milliseconds. - # Flow generates a new TrustedRootCertificate - Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) - logger.info("Step #10 repet #5 - Generating a new CSR to update the root certificate...") - cmd = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) + Returns: + str: The current time formatted as 'dd-mm hh:mm:ss.sss'. + ''' + current_time = time.time() # Get the current timestamp + formatted_time = ( + datetime.fromtimestamp(current_time, tz=timezone.utc) + .strftime('%d-%m %H:%M:%S.') + + str(int((current_time % 1) * 1000)).zfill(3) + ) + return formatted_time - # Flow generates a new TrustedRootCertificate - Isue the certificates - th1_certs_new = await self.default_controller.IssueNOCChain(csr_update, self.dut_node_id) - new_root_cert = th1_certs_new.rcacBytes + def desc_TC_CGEN_2_2(self) -> str: + return '[TC-CGEN-2.2] ArmFailSafe command verification [DUT - Server]' - # Flow generates a new TrustedRootCertificate - Send command to add new trusted root certificate - cmd = cluster_opcreds.Commands.AddTrustedRootCertificate(new_root_cert) - resp = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) + def pics_TC_CGEN_2_2(self): + """Return the PICS definitions associated with this test.""" + pics = [ + "CGEN.S", + "OPCREDS.S", + ] + return pics + def steps_TC_CGEN_2_2(self) -> list[TestStep]: + steps = [ + TestStep(0, 'Commissioning, already done', is_commissioning=True), + TestStep(1, '''TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster + and saves the number of list items as numTrustedRootsOriginal.'''), + TestStep(2, '''TH1 reads the BasicCommissioningInfo attribute + and saves the MaxCumulativeFailsafeSeconds as maxFailsafe.'''), + TestStep('3-5', 'TH1 execute function run_steps_3_to_5 to run steps #3 through #5.'), + TestStep(3, '''TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds + and the Breadcrumb value as 1.'''), + TestStep(4, 'TH1 reads the Breadcrumb attribute.'), + TestStep(5, '''TH1 generates a new TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. + TH1 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate.'''), + TestStep(6, 'TH1 reads the TrustedRootCertificate attribute.'), + TestStep(7, 'TH1 waits for PIXIT.CGEN.FailsafeExpiryLengthSeconds to ensure the failsafe timer has expired.'), + TestStep('8-9', 'TH1 execute function run_steps_8_to_9 to run steps #8 through #9.'), + TestStep(8, 'TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster.'), + TestStep(9, 'TH1 reads the Breadcrumb attribute and verify that the breadcrumb attribute is 0.'), + TestStep(10, 'TH1 repeats steps #3 through #5 using function run_steps_3_to_5.'), + TestStep(11, 'TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0.'), + TestStep(12, 'TH1 Repeat steps 8 through 9.'), + TestStep(13, 'TH1 sends the OpenCommissioningWindow command to the Administrator Commissioning cluster.'), + TestStep(14, 'TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds'), + TestStep(15, 'TH2 opens a PASE connection to the DUT.'), + TestStep(16, '''TH2 obtains or generates a TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. + TH2 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate.'''), + TestStep(17, 'TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0.'), + TestStep(18, '''TH1 reads the NOCs attribute from the Node Operational Credentials cluster using a non-fabric-filtered read + and saves the returned list as nocs.'''), + TestStep(19, '''TH1 reads the Fabrics attribute from the Node Operational Credentials cluster using a non-fabric-filtered read + and saves the returned list as fabrics.'''), + TestStep(20, '''TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster + and saves the returned list as trustedroots.'''), + TestStep(21, '''TH2 starts commissioning the DUT. It performs all steps up to establishing a CASE connection, + but DOES NOT send the CommissioningComplete command.'''), + TestStep(22, 'TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0.'), + TestStep(23, 'TH1 reads the NOCs attribute from the Node Operational Credentials cluster using a non-fabric-filtered read.'), + TestStep(24, 'TH1 reads the Fabrics attribute from the Node Operational Credentials cluster using a non-fabric-filtered read.'), + TestStep(25, 'TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster.'), + TestStep(26, 'TH2 fully commissions the DUT.'), + TestStep(27, '''TH2 obtains or generates a TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. + TH2 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate.'''), + TestStep(28, 'TH1 reads the Fabrics attribute from the Node Operational Credentials cluster using a non-fabric-filtered read.'), + TestStep(29, '''TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds + and the Breadcrumb value as 1.'''), + TestStep(30, '''TH1 obtains or generates a new TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. + TH1 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate.'''), + TestStep(31, 'TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster.'), + TestStep(32, 'TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to maxFailsafe.'), + TestStep(33, 'TH1 waits for PIXIT.CGEN.FailsafeExpiryLengthSeconds.'), + TestStep(34, 'TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster.'), + # TestStep(35, '''TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds + # and the Breadcrumb value as 1.'''), + TestStep(36, 'TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0.'), + TestStep(37, 'TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to maxFailsafe.'), + TestStep(38, 'TH1 saves the current wall time clock in seconds as Tstart,'), + TestStep(39, """TH1 obtains or generates a new TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. + TH1 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate."""), + TestStep(40, 'TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster.'), + TestStep(41, 'TH1 waits until the current wall time clock is Tstart + maxFailsafe/2.'), + TestStep(42, 'TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to maxFailsafe.'), + TestStep(43, '''TH1 waits until the current wall time clock is Tstart + maxFailsafe. maxFailsafe is the maximum amount of time a failsafe can be armed for, + so the failsafe is required to time out at this point, despite having been re-armed in step 42.'''), + TestStep(44, 'TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster.'), + ] + return steps + + @async_test_body + async def test_TC_CGEN_2_2(self): + + self.step(0) + + # Read the Steps + self.step(1) + trusted_root_list_original = await self.read_single_attribute_check_success( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cluster=self.cluster_opcreds, + attribute=self.cluster_opcreds.Attributes.TrustedRootCertificates) + trusted_root_list_original_size = len(trusted_root_list_original) + logger.info(f'Step #1: The size of the original num_trusted_roots_original list: {trusted_root_list_original_size}') + + self.step(2) + basic_commissioning_info = await self.read_single_attribute_check_success( + cluster=self.cluster_cgen, + attribute=self.cluster_cgen.Attributes.BasicCommissioningInfo) + maxFailsafe = basic_commissioning_info.maxCumulativeFailsafeSeconds + logger.info(f'Step #2: The MaxCumulativeFailsafeSeconds (max_fail_safe): {maxFailsafe}') + + # TH1 steps #3 through #5 using the function run_steps_3_to_5 + self.step('3-5') + new_root_cert = await self.run_steps_3_to_5(maxFailsafe, is_first_run=True) + + self.step(6) trusted_root_list_original_updated = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, - cluster=cluster_opcreds, - attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + cluster=self.cluster_opcreds, + attribute=self.cluster_opcreds.Attributes.TrustedRootCertificates) trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) - # logger.info(f'Step #10 - The updated original trusted_roots_original: {trusted_root_list_original_updated}') - logger.info( - f'Step #10 - The updated size of the num_trusted_roots_original list: {trusted_root_list_original_size_updated}') + + # Verify that the trusted root list size has increased by 1 asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 1, - "Step #10 - Unexpected number of entries in the TrustedRootCertificates table after update") + "Unexpected number of entries in the TrustedRootCertificates table after update") # Optionally, check if the new certificate is in the updated list - assert new_root_cert in trusted_root_list_original_updated, \ - "Step #10 - New root certificate was not added to the trusted root list." + asserts.assert_in(new_root_cert, trusted_root_list_original_updated, + "New root certificate was not added to the trusted root list.") + + logger.info(f'Step #6: The updated size of the num_trusted_roots_original list: {trusted_root_list_original_size_updated}') + + self.step(7) + if self.check_pics('PICS_SDK_CI_ONLY'): + # Step 7 - In CI environments, the 'expire_failsafe_timer' function is used to immediately force the failsafe timer to expire, + # avoiding the original wait time defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds + # and speeding up test execution by setting the expiration time to 1 second. + + # Running identifier + run_type = "CI Test" + logger.info( + f'Step 7: {run_type} - Bypassing failsafe expiration to avoid unnecessary delays in CI environment.') + + # Expiration time set to 1 second to immediately expire the failsafe timer + expiration_time_seconds = 1 + + # Force the failsafe timer to expire immediately for TH1, avoiding unnecessary delays in CI environments + resp = await self.expire_failsafe_timer( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + expiration_time_seconds=expiration_time_seconds) + logger.info( + f'Step #7 {run_type} - Failsafe timer expiration bypassed for TH1 by setting expiration time to {expiration_time_seconds} seconds. ' + f'Test continues without the original wait.' + ) + else: + # Running identifier + run_type = "Cert Test" + # The maxFailsafe value is taken from PIXIT.CGEN.FailsafeExpiryLengthSeconds, which is set to 900 seconds (15 minutes) + logger.info( + f'Step #7: {run_type} - Waiting for Failsafe timer to expire for PIXIT.CGEN.FailsafeExpiryLengthSeconds (max_fail_safe): {maxFailsafe} seconds...') + + # Get the start time to measure the elapsed time + start_time = time.time() + + # Wait for the full duration of the maxFailsafe time with an additional 0.5-second buffer + await asyncio.sleep(maxFailsafe + 0.5) + + # Calculate and log the elapsed time since the start of the wait + elapsed_time = time.time() - start_time + logger.info(f'Step #7: Cert Test - Failsafe timer (max_fail_safe) expired after {elapsed_time} seconds.') + + # TH1 steps #8 through #9 using the function run_steps_8_to_9 + self.step('8-9') + resp = await self.run_steps_8_to_9(trusted_root_list_original_size, is_first_run=True) + + self.step(10) + # Repeat TH1 steps #3 through #5 using the function run_steps_3_to_5 + new_root_cert = await self.run_steps_3_to_5(maxFailsafe, is_first_run=False) self.step(11) cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=0) @@ -365,36 +410,20 @@ async def test_TC_CGEN_2_2(self): "Failure status returned from arm failsafe") # Verify that DebugText is empty or has a maximum length of 512 characters debug_text = resp.debugText - assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" - logger.info(f'Step #11 - ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') + asserts.assert_true(debug_text == '' or len(debug_text) <= 512, + "debugText must be empty or have a maximum length of 512 characters.") + logger.info(f'Step #11: ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') self.step(12) - logger.info('Step #12 - TH1 repeats steps 8 through 9') - logger.info('Step #12 repet #8 - TH1 reads the TrustedRootCertificates') - trusted_root_list_original_updated = await self.read_single_attribute_check_success( - dev_ctrl=self.default_controller, - node_id=self.dut_node_id, - cluster=cluster_opcreds, - attribute=cluster_opcreds.Attributes.TrustedRootCertificates) - trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) - logger.info( - f'Step #12 - The updated size of the num_trusted_roots_original list: {trusted_root_list_original_size_updated}') - asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size, - "Step #12 - Unexpected number of entries in the TrustedRootCertificates table after update") - - logger.info('Step #12 repet #9 - TH1 reads the Breadcrumb attribute') - breadcrumb_info = await self.read_single_attribute_check_success( - cluster=cluster_cgen, - attribute=cluster_cgen.Attributes.Breadcrumb) - - logger.info(f"Step #12 - The Breadcrumb attribute: {breadcrumb_info}") - asserts.assert_equal(breadcrumb_info, 0, "Breadcrumb value is not 0 after waiting for failsafe timer") + # Repeat TH1 steps #8 through #9 using the function run_steps_8_to_9 + resp = await self.run_steps_8_to_9(trusted_root_list_original_size, is_first_run=False) self.step(13) params = await self.open_commissioning_window(self.default_controller, self.dut_node_id) setup_pin_code = params.commissioningParameters.setupPinCode longDiscriminator = params.randomDiscriminator - logger.info(f'Step #13 - Open Commissioning Window params with vars: {vars(params)}') + setup_qr_code = params.commissioningParameters.setupQRCode + logger.info(f'Step #13: Open Commissioning Window params with vars: {vars(params)}') self.step(14) cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe) @@ -405,10 +434,11 @@ async def test_TC_CGEN_2_2(self): # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'BusyWithOtherAdmin'(4) asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kBusyWithOtherAdmin, "Failure status returned from arm failsafe") - logger.info(f'Step #14 - ArmFailSafeResponse with ErrorCode as BusyWithOtherAdmin ({resp.errorCode})') + logger.info(f'Step #14: ArmFailSafeResponse with ErrorCode as BusyWithOtherAdmin ({resp.errorCode})') self.step(15) # Create TH2 + # NOTE: The 'maximizeCertChains' argument is being used to ensure new trusted roots are generated each time this process is executed. TH2_CA_real = self.certificate_authority_manager.NewCertificateAuthority(maximizeCertChains=True) TH2_vid = 0xFFF2 TH2_fabric_admin_real = TH2_CA_real.NewFabricAdmin(vendorId=TH2_vid, fabricId=2) @@ -416,17 +446,13 @@ async def test_TC_CGEN_2_2(self): TH2 = TH2_fabric_admin_real.NewController(nodeId=TH2_nodeid) newNodeId = self.dut_node_id + 1 - resp = await self.FindAndEstablishPase( - dev_ctrl=TH2, - longDiscriminator=longDiscriminator, - setupPinCode=setup_pin_code, - nodeid=newNodeId) + resp = await TH2.FindOrEstablishPASESession(setupCode=setup_qr_code, nodeid=newNodeId) logger.info('Step #15 - TH2 successfully establish PASE session completed') self.step(16) logger.info('Step #16 - TH2 Generating a new CSR to update the root certificate...') # Flow generates a new TrustedRootCertificate - Request CSR (Certificate Signing Request) - cmd = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) + cmd = self.cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) th2_csr = await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId, cmd=cmd) # Flow generates a new TrustedRootCertificate - Isue the certificates @@ -434,7 +460,7 @@ async def test_TC_CGEN_2_2(self): th2_new_root_cert = th2_certs_new.rcacBytes # Flow generates a new TrustedRootCertificate - Send command to add new trusted root certificate - cmd = cluster_opcreds.Commands.AddTrustedRootCertificate(th2_new_root_cert) + cmd = self.cluster_opcreds.Commands.AddTrustedRootCertificate(th2_new_root_cert) resp = await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId, cmd=cmd) self.step(17) @@ -448,15 +474,16 @@ async def test_TC_CGEN_2_2(self): "Failure status returned from arm failsafe") # Verify that DebugText is empty or has a maximum length of 512 characters debug_text = resp.debugText - assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" + asserts.assert_true(debug_text == '' or len(debug_text) <= 512, + "debugText must be empty or have a maximum length of 512 characters.") logger.info(f'Step #17 - ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') self.step(18) nocs = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, - cluster=cluster_opcreds, - attribute=cluster_opcreds.Attributes.NOCs, + cluster=self.cluster_opcreds, + attribute=self.cluster_opcreds.Attributes.NOCs, fabric_filtered=False) nocs_original_size = len(nocs) logger.info(f'Step #18 - TH1 Original size of the nocs list: {nocs_original_size}') @@ -465,8 +492,8 @@ async def test_TC_CGEN_2_2(self): fabrics = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, - cluster=cluster_opcreds, - attribute=cluster_opcreds.Attributes.Fabrics, + cluster=self.cluster_opcreds, + attribute=self.cluster_opcreds.Attributes.Fabrics, fabric_filtered=False) fabrics_original_size = len(fabrics) logger.info(f'Step #19 - TH1 Original size of the fabrics list: {fabrics_original_size}') @@ -475,8 +502,8 @@ async def test_TC_CGEN_2_2(self): trusted_roots_list = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, - cluster=cluster_opcreds, - attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + cluster=self.cluster_opcreds, + attribute=self.cluster_opcreds.Attributes.TrustedRootCertificates) trusted_roots_list_size = len(trusted_roots_list) logger.info(f'Step #20 - TH1 Original size of the trusted_root list: {trusted_roots_list_size}') @@ -491,13 +518,12 @@ async def test_TC_CGEN_2_2(self): resp = TH2.SetTestCommissionerPrematureCompleteAfter(kFindOperationalForCommissioningComplete) logger.info('Step #21 - TH2 Commissioning DOES NOT send the CommissioningComplete command') + # TH2.SetSkipCommissioningComplete(True) + logger.info('Step #21 - CommissioningComplete skipped to avoid premature completion.') + self.step(22) logger.info("Step #22 - TH1 Waiting for PASE session to stabilize...") - resp = await self.FindAndEstablishPase( - dev_ctrl=TH2, - longDiscriminator=longDiscriminator, - setupPinCode=setup_pin_code, - nodeid=newNodeId) + resp = await TH2.FindOrEstablishPASESession(setupCode=setup_qr_code, nodeid=newNodeId) logger.info('Step #22 - TH2 successfully establish PASE session completed') try: @@ -514,36 +540,36 @@ async def test_TC_CGEN_2_2(self): nocs_updated = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, - cluster=cluster_opcreds, - attribute=cluster_opcreds.Attributes.NOCs, + cluster=self.cluster_opcreds, + attribute=self.cluster_opcreds.Attributes.NOCs, fabric_filtered=False) nocs_updated_size = len(nocs_updated) logger.info(f'Step #23 - TH1 nocs_updated: {nocs_updated_size}') asserts.assert_equal(nocs_updated_size, nocs_original_size, - "Step #23 - The nocs list size should match the original nocs list size.") + "The nocs list size should match the original nocs list size.") self.step(24) fabrics_updated = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, - cluster=cluster_opcreds, - attribute=cluster_opcreds.Attributes.Fabrics, + cluster=self.cluster_opcreds, + attribute=self.cluster_opcreds.Attributes.Fabrics, fabric_filtered=False) fabrics_updated_size = len(fabrics_updated) logger.info(f'Step #24 - TH1 fabrics_original_size2: {fabrics_updated_size}') asserts.assert_equal(fabrics_updated_size, fabrics_original_size, - "Step #24 - The fabrics list size should match the original fabrics list size.") + "The fabrics list size should match the original fabrics list size.") self.step(25) trusted_roots_list_updated = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, - cluster=cluster_opcreds, - attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + cluster=self.cluster_opcreds, + attribute=self.cluster_opcreds.Attributes.TrustedRootCertificates) trusted_roots_list_updated_size = len(trusted_roots_list_updated) logger.info(f'Step #25 - TH1 the size of the trusted_roots_list_size_updated: {trusted_roots_list_updated_size}') asserts.assert_equal(trusted_roots_list_updated_size, trusted_roots_list_size, - "Step #25 - The trusted_roots list size should match the original trusted_roots list size.") + "The trusted_roots list size should match the original trusted_roots list size.") self.step(26) basic_commissioning_info = await self.read_single_attribute_check_success(cluster=Clusters.GeneralCommissioning, attribute=Clusters.GeneralCommissioning.Attributes.BasicCommissioningInfo) @@ -560,7 +586,7 @@ async def test_TC_CGEN_2_2(self): try: logger.info("Step #27 - TH2 Generating a new CSR to update the root certificate...") # Flow generates a new TrustedRootCertificate - Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) - cmd = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) + cmd = self.cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) th2_csr = await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId+1, cmd=cmd) # Flow generates a new TrustedRootCertificate - Isue the certificates @@ -568,7 +594,7 @@ async def test_TC_CGEN_2_2(self): th2_new_root_cert = th2_certs_new.rcacBytes # Flow generates a new TrustedRootCertificate - Send command to add new trusted root certificate - cmd = cluster_opcreds.Commands.AddTrustedRootCertificate(th2_new_root_cert) + cmd = self.cluster_opcreds.Commands.AddTrustedRootCertificate(th2_new_root_cert) resp = await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId+1, cmd=cmd) except Exception as e: @@ -579,13 +605,13 @@ async def test_TC_CGEN_2_2(self): fabrics_updated = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, - cluster=cluster_opcreds, - attribute=cluster_opcreds.Attributes.Fabrics, + cluster=self.cluster_opcreds, + attribute=self.cluster_opcreds.Attributes.Fabrics, fabric_filtered=False) fabrics_updated_size = len(fabrics_updated) logger.info(f'Step #28 - TH1 fabrics updated with additional entry for TH2: {fabrics_updated_size}') asserts.assert_equal(fabrics_updated_size, fabrics_original_size + 1, - "Step #28 - The fabrics list size should match the original fabrics list size + 1.") + "The fabrics list size should match the original fabrics list size + 1.") self.step(29) # logger.info("Step #29 - TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds and the Breadcrumb value as 1") @@ -599,36 +625,42 @@ async def test_TC_CGEN_2_2(self): "Failure status returned from arm failsafe") # Verify that DebugText is empty or has a maximum length of 512 characters debug_text = resp.debugText - assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" - logger.info(f'Step #29 - ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') + asserts.assert_true(debug_text == '' or len(debug_text) <= 512, + "debugText must be empty or have a maximum length of 512 characters.") + logger.info(f'Step #29: ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') self.step(30) - logger.info("Step #30 - Generating a new CSR to update the root certificate...") - # Flow generates a new TrustedRootCertificate - Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) - cmd = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) - csr_update = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - - # Flow generates a new TrustedRootCertificate - Isue the certificates - th1_certs_new = await self.default_controller.IssueNOCChain(csr_update, self.dut_node_id) - new_root_cert = th1_certs_new.rcacBytes - - # Flow generates a new TrustedRootCertificate - Send command to add new trusted root certificate - cmd = cluster_opcreds.Commands.AddTrustedRootCertificate(new_root_cert) + # Reused TrustedRootCertificate created in step #27 - Send command to add new trusted root certificate + cmd = self.cluster_opcreds.Commands.AddTrustedRootCertificate(th2_new_root_cert) resp = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) self.step(31) trusted_root_list_original_updated = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, - cluster=cluster_opcreds, - attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + cluster=self.cluster_opcreds, + attribute=self.cluster_opcreds.Attributes.TrustedRootCertificates) trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) logger.info(f'Step #31 - The updated num_trusted_roots_original: {trusted_root_list_original_size_updated}') # Verify that the trusted root list size has increased by 1 asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 2, - "Step #31 - Unexpected number of entries in the TrustedRootCertificates table after update") + "Unexpected number of entries in the TrustedRootCertificates table after update") self.step(32) + # Save the original value of maxFailsafe before making any changes + original_maxFailsafe = maxFailsafe + if self.check_pics('PICS_SDK_CI_ONLY'): + # In CI environments, we set maxFailsafe to 10 to avoid unnecessary delays caused by waiting for failsafe timers to expire. + # This bypasses the normal behavior defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds and speeds up the test process. + + run_type = "CI Test" + maxFailsafe = 5 + else: + maxFailsafe = maxFailsafe + # Cert Test environment: use the defined maxFailsafe value (as per PIXIT.CGEN.FailsafeExpiryLengthSeconds) + run_type = "Cert Test" + # maxFailsafe remains unchanged (it will be set earlier or passed as an argument) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe) resp = await self.send_single_cmd( dev_ctrl=self.default_controller, @@ -640,40 +672,90 @@ async def test_TC_CGEN_2_2(self): # Verify that DebugText is empty or has a maximum length of 512 characters debug_text = resp.debugText assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" - logger.info(f'Step #32 - ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') + logger.info(f'Step #32: {run_type} - ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') self.step(33) - # Step 33 - Bypassing the wait for the failsafe timer to expire (PIXIT.CGEN.FailsafeExpiryLengthSeconds) - # Originally, this step required waiting for the failsafe timer to expire after a specific duration defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds. - # However, in CI environments, this wait introduces unnecessary delays. To avoid this, we use the `expire_failsafe_timer` function, - # which immediately expires the failsafe timer (sets it to 0), bypassing the natural wait and speeding up the test execution. + # Divide the maxFailsafe time in half + maxFailsafe_half = maxFailsafe / 2 + logger.info( + f'Step #33: {run_type} - Waiting for half of the maxFailsafe time ({maxFailsafe_half} seconds) before Failsafe timer expires.') + + start_time = time.time() + target_time = start_time + maxFailsafe_half + + # Wait the half of the maximum failsafe time using a while loop + while time.time() < target_time: + try: + await asyncio.sleep(0.1) # Esperar un tiempo pequeño en cada iteración + except asyncio.CancelledError: + # Si se lanza el TimeoutError, simplemente lo ignoramos + continue # Ignoramos el error y seguimos esperando hasta que se cumpla el tiempo objetivo + + elapsed_time = time.time() - start_time + logger.info(f'Step #33: {run_type} - Elapsed time: {elapsed_time} seconds after waiting for half of the maxFailsafe.') + + # Send the second ArmFailSafe command before the failsafe time expires + # This re-arms the failsafe timer, preventing it from expiring prematurely. + logger.info(f'Step #33: {run_type} - Send the second ArmFailSafe command before the timer expires.') + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe) + resp = await self.send_single_cmd( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + cmd=cmd) + # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "Failure status returned from arm failsafe") + # Verify that DebugText is empty or has a maximum length of 512 characters + debug_text = resp.debugText + assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" + logger.info(f'Step #33: {run_type} - ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') + + # Re-set the Failsafe timer to full duration (maxFailsafe) + logger.info( + f'Step #33: {run_type} - Failsafe timer (max_fail_safe) is set to the maxFailsafe time: {maxFailsafe} seconds...') + + start_time = time.time() + + # Adding a buffer of 1 second to ensure the failsafe is cleanly disarmed before proceeding. + target_time = start_time + maxFailsafe + 1 + while time.time() < target_time: + try: + await asyncio.sleep(0.1) # Esperar un tiempo pequeño en cada iteración + except asyncio.CancelledError: + # Si se lanza el TimeoutError, simplemente lo ignoramos + continue # Ignoramos el error y seguimos esperando hasta que se cumpla el tiempo objetivo - # This function bypasses the wait for the FailsafeTimer to expire for TH2 as originally defined in the test plan. - resp = await self.expire_failsafe_timer(dev_ctrl=TH2, node_id=newNodeId+1) - logger.info('Step # 33 - Failsafe timer expiration bypassed for TH2 by setting expiryLengthSeconds to 0. Test continues without the original wait.') + # await asyncio.sleep(maxFailsafe + 1) + elapsed_time = time.time() - start_time + logger.info(f'Step #33: {run_type} - Failsafe timer (max_fail_safe) expired after {elapsed_time} seconds.') + + # After the test actions, restore the original maxFailsafe value + maxFailsafe = original_maxFailsafe self.step(34) trusted_root_list_original_updated = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, - cluster=cluster_opcreds, - attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + cluster=self.cluster_opcreds, + attribute=self.cluster_opcreds.Attributes.TrustedRootCertificates) trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) logger.info(f'Step #34 - The updated num_trusted_roots_original: {trusted_root_list_original_size_updated}') # Verify that the trusted root list size - asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 2, + asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 1, "Step #34 - Unexpected number of entries in the TrustedRootCertificates table after update") - self.step(35) - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe, breadcrumb=1) - resp = await self.send_single_cmd( - dev_ctrl=TH2, - node_id=newNodeId+1, - cmd=cmd) - # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'BusyWithOtherAdmin'(4) - logger.info(f'Step #35 - TH2 ArmFailSafeResponse with ErrorCode as BusyWithOtherAdmin ({resp.errorCode})') - asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kBusyWithOtherAdmin, - "Failure status returned from arm failsafe") + # self.step(35) + # cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe, breadcrumb=1) + # resp = await self.send_single_cmd( + # dev_ctrl=self.default_controller, + # node_id=self.dut_node_id, + # # dev_ctrl=TH2, + # # node_id=newNodeId+1, + # cmd=cmd) + # # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'BusyWithOtherAdmin'(4) + # logger.info(f'Step #35 - TH2 ArmFailSafeResponse with ErrorCode as BusyWithOtherAdmin ({resp.errorCode})') + # asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kBusyWithOtherAdmin, + # "Failure status returned from arm failsafe") self.step(36) cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=0) @@ -681,10 +763,10 @@ async def test_TC_CGEN_2_2(self): dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - logger.info(f'Step #36 - response attributes are: {resp.errorCode}') # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, "Failure status returned from arm failsafe") + logger.info(f'Step #36: ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') self.step(37) cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe) @@ -692,60 +774,68 @@ async def test_TC_CGEN_2_2(self): dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - logger.info(f'Step #37 - response attributes are: {resp.errorCode}') # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, "Failure status returned from arm failsafe") + logger.info(f'Step #37: ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') self.step(38) - # Step 38 - Skipped - # Originally, this step saved the current wall time clock in seconds as Tstart to use it for later comparisons. - # However, with the failsafe expiration workaround (expiryLengthSeconds=0), we no longer rely on this timestamp, - # as the failsafe timer is immediately expired, bypassing the need for time calculations or waits. - logger.info("Step 38 skipped: Saving Tstart (current wall time clock) is bypassed due to failsafe expiration workaround.") - - self.step(39) - trusted_root_list_original_updated = await self.read_single_attribute_check_success( - dev_ctrl=self.default_controller, - node_id=self.dut_node_id, - cluster=cluster_opcreds, - attribute=cluster_opcreds.Attributes.TrustedRootCertificates) - trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) - logger.info( - f'Step #39 - The updated size of the num_trusted_roots_original list: {trusted_root_list_original_size_updated}') + if self.check_pics('PICS_SDK_CI_ONLY'): + # In CI environment, bypass the wait for the failsafe expiration to avoid unnecessary delays. + run_type = "CI Test" + logger.info( + f'Step 38: {run_type} - Bypassing due to failsafe expiration workaround to avoid unnecessary delays in CI environment.') + else: + run_type = "Cert Test" + start_time = time.time() - # Flow generates a new TrustedRootCertificate - Request CSR (Certificate Signing Request) and update NOC (Node Operational Certificate) - logger.info("Step #10 repet #5 - Generating a new CSR to update the root certificate...") - cmd = cluster_opcreds.Commands.CSRRequest(CSRNonce=random.randbytes(32), isForUpdateNOC=False) + # Get the current time and format it for logging + start_time_formatted = self.get_current_utc_time_str() + logger.info(f'Step #38: {run_type} - TH1 saves the Current time: {start_time_formatted}') - # Flow generates a new TrustedRootCertificate - Isue the certificates - th1_certs_new = await self.default_controller.IssueNOCChain(csr_update, self.dut_node_id) - new_root_cert = th1_certs_new.rcacBytes - - # Flow generates a new TrustedRootCertificate - Send command to add new trusted root certificate - cmd = cluster_opcreds.Commands.AddTrustedRootCertificate(new_root_cert) + self.step(39) + # Reused TrustedRootCertificate created in step #5 - Send command to add new trusted root certificate + cmd = self.cluster_opcreds.Commands.AddTrustedRootCertificate(new_root_cert) resp = await self.send_single_cmd(dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) self.step(40) trusted_root_list_original_updated = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, - cluster=cluster_opcreds, - attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + cluster=self.cluster_opcreds, + attribute=self.cluster_opcreds.Attributes.TrustedRootCertificates) trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) logger.info( - f'Step #40 - The updated size of the num_trusted_roots_original list: {trusted_root_list_original_size_updated}') - # Verify that the trusted root list size has increased by 2 from oririnal trusted root list, Should be 3 + f'Step #40: The updated size of the num_trusted_roots_original list is {trusted_root_list_original_size_updated}') + # Verify that the trusted root list size has increased to numTrustedRootsOriginal + 2 + # ensuring that one element has been added, and the total size is now 3 asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 2, - "Step #31 - Unexpected number of entries in the TrustedRootCertificates table after update") + "Unexpected number of entries in the TrustedRootCertificates table after update") self.step(41) - # Step 41 - Skipped - # Previously, this step waited until the current wall time clock reached Tstart + maxFailsafe/2. - # However, with the failsafe expiration workaround (expiryLengthSeconds=0), this wait is unnecessary and has been skipped, - # as the failsafe timer is immediately expired and the test proceeds without delay. - logger.info( - "Step 41 skipped: Waiting for the wall time clock to reach Tstart + maxFailsafe/2 is bypassed due to failsafe expiration workaround.") + if self.check_pics('PICS_SDK_CI_ONLY'): + # In CI environment, bypass the wait for the failsafe expiration to avoid unnecessary delays. + run_type = "CI Test" + logger.info( + f'Step 41: {run_type} - Bypassing due to failsafe expiration workaround to avoid unnecessary delays in CI environment.') + else: + run_type = "Cert Test" + start_time = time.time() + + # Divide the maxFailsafe time to half + target_time = start_time + (maxFailsafe / 2) + # Make TH1 wait until the current time is greater than or equal to the target time + while time.time() < target_time: + time.sleep(0.1) # Wait for 100ms to avoid excessive CPU usage + + # Check if the elapsed time since start_time has reached or exceeded half of the maxFailsafe time (maxFailsafe / 2). + # If the current time surpasses the start time by at least half of the maxFailsafe time, the TH1 process is allowed to proceed. + current_time = self.get_current_utc_time_str() + logger.info( + f'Step #41: {run_type} - TH1 can proceed. ' + f'Started expiration time: {start_time_formatted}, Current time: {current_time}, ' + f'MaxFailsafe time at half ({maxFailsafe/2} seconds) has passed, confirming that ArmFailSafe has NOT expired yet.' + ) self.step(42) cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe) @@ -753,43 +843,74 @@ async def test_TC_CGEN_2_2(self): dev_ctrl=self.default_controller, node_id=self.dut_node_id, cmd=cmd) - logger.info(f'Step #42 - response attributes are: {vars(resp)}') - logger.info(f'Step #42 - response attributes are: {resp.errorCode}') # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, "Failure status returned from arm failsafe") + logger.info(f'Step #42: ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') self.step(43) - # Step 43 - Bypassing the wait for the failsafe timer to expire - # Originally, this step required waiting until the current wall time clock reached Tstart + maxFailsafe. - # However, in CI environments, this wait introduces unnecessary delays. To avoid this, we use the `expire_failsafe_timer` function, - # which immediately expires the failsafe timer (sets it to 0), bypassing the natural wait and speeding up the test execution. + if self.check_pics('PICS_SDK_CI_ONLY'): + # Step 43 - In CI environments, the 'expire_failsafe_timer' function is used to immediately force the failsafe timer to expire, + # avoiding the original wait time defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds, + # and speeding up test execution by setting the expiration time to 1 second. + + run_type = "CI Test" + logger.info( + f'Step 43: {run_type} - Bypassing due to failsafe expiration workaround to avoid unnecessary delays in CI environment.') + + # Expiration time set to 1 second to immediately expire the failsafe timer + expiration_time_seconds = 1 + + # Force the failsafe timer to expire immediately for TH1, avoiding unnecessary delays in CI environments + resp = await self.expire_failsafe_timer( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + expiration_time_seconds=expiration_time_seconds) + logger.info( + f'Step #43 {run_type} - Failsafe timer expiration bypassed for TH1 by setting expiration time to {expiration_time_seconds} seconds. ' + f'Test continues without the original wait.' + ) + else: + run_type = "Cert Test" + start_time = time.time() + + # Calculate the target time (Tstart + maxFailsafe) + target_time = start_time + maxFailsafe - # This function bypasses the wait for the FailsafeTimer to expire for TH1 as originally defined in the test plan. - resp = await self.expire_failsafe_timer(dev_ctrl=self.default_controller, node_id=self.dut_node_id) - logger.info('Step # 43 - Failsafe timer expiration bypassed for TH1 by setting expiryLengthSeconds to 0. Test continues without the original wait.') + # Make TH1 wait until the current time is greater than or equal to the target time + while time.time() < target_time: + time.sleep(0.1) # Wait for 100ms to avoid excessive CPU usage + + # Checks if the elapsed time from start_time has met or exceeded maxFailsafe + # If the current time has passed the start time by at least maxFailsafe, the TH1 process can proceed + current_time = self.get_current_utc_time_str() + logger.info( + f'Step #43: {run_type} - TH1 can proceed. ' + f'Started expiration time: {start_time_formatted}, Current time: {current_time}, ' + f'MaxFailsafe: {maxFailsafe} seconds has passed, confirming ArmFailSafe has expired.' + ) self.step(44) # Remove Fabric from TH2 to ensure it is correctly cleaned up and that the root certificates are aligned with expectations. # Read the CurrentFabricIndex attribute from TH2 (the second controller) - fabric_idx = await self.read_single_attribute_check_success(dev_ctrl=TH2, node_id=newNodeId+1, cluster=cluster_opcreds, attribute=cluster_opcreds.Attributes.CurrentFabricIndex) - logger.info(f'Step #44 - TH2 CurrentFabricIndex attribute: {fabric_idx}') + fabric_idx = await self.read_single_attribute_check_success(dev_ctrl=TH2, node_id=newNodeId+1, cluster=self.cluster_opcreds, attribute=self.cluster_opcreds.Attributes.CurrentFabricIndex) + logger.info(f'Step #44: TH2 CurrentFabricIndex attribute: {fabric_idx}') # Remove the fabric from TH2 (second controller) cmd = Clusters.OperationalCredentials.Commands.RemoveFabric(fabricIndex=fabric_idx) await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId+1, cmd=cmd) - # The expected number of root certificates should be 1 after removing the fabric + # The expected number of root certificates should be numTrustedRootsOriginal after removing the fabric trusted_root_list_original_updated = await self.read_single_attribute_check_success( dev_ctrl=self.default_controller, node_id=self.dut_node_id, - cluster=cluster_opcreds, - attribute=cluster_opcreds.Attributes.TrustedRootCertificates) + cluster=self.cluster_opcreds, + attribute=self.cluster_opcreds.Attributes.TrustedRootCertificates) trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) logger.info( - f'Step #44 - The updated size of the num_trusted_roots_original list: {trusted_root_list_original_size_updated}') + f'Step #44: he updated size of the num_trusted_roots_original list: {trusted_root_list_original_size_updated}') # Verify that the trusted root list size from oririnal trusted root list is 1 asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size, - "Step #44 - Unexpected number of entries in the TrustedRootCertificates table after update") + "Unexpected number of entries in the TrustedRootCertificates table after update") if __name__ == "__main__": From c50fefbea1694686e3be5f08db76e958ed2c6881 Mon Sep 17 00:00:00 2001 From: juandediosg Date: Fri, 7 Feb 2025 08:45:06 -0600 Subject: [PATCH 09/25] Improving long waits for cert test --- src/python_testing/TC_CGEN_2_2.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index 9e0a52378e206b..c7108bf74947cb 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -291,7 +291,7 @@ def steps_TC_CGEN_2_2(self) -> list[TestStep]: TestStep(33, 'TH1 waits for PIXIT.CGEN.FailsafeExpiryLengthSeconds.'), TestStep(34, 'TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster.'), # TestStep(35, '''TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds - # and the Breadcrumb value as 1.'''), + # and the Breadcrumb value as 1.'''), TestStep(36, 'TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0.'), TestStep(37, 'TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to maxFailsafe.'), TestStep(38, 'TH1 saves the current wall time clock in seconds as Tstart,'), @@ -382,9 +382,14 @@ async def test_TC_CGEN_2_2(self): # Get the start time to measure the elapsed time start_time = time.time() + target_time = start_time + (maxFailsafe + .5) # Wait for the full duration of the maxFailsafe time with an additional 0.5-second buffer - await asyncio.sleep(maxFailsafe + 0.5) + while time.time() < target_time: + try: + await asyncio.sleep(0.1) + except asyncio.CancelledError: + continue # Calculate and log the elapsed time since the start of the wait elapsed_time = time.time() - start_time @@ -686,10 +691,9 @@ async def test_TC_CGEN_2_2(self): # Wait the half of the maximum failsafe time using a while loop while time.time() < target_time: try: - await asyncio.sleep(0.1) # Esperar un tiempo pequeño en cada iteración + await asyncio.sleep(0.1) except asyncio.CancelledError: - # Si se lanza el TimeoutError, simplemente lo ignoramos - continue # Ignoramos el error y seguimos esperando hasta que se cumpla el tiempo objetivo + continue elapsed_time = time.time() - start_time logger.info(f'Step #33: {run_type} - Elapsed time: {elapsed_time} seconds after waiting for half of the maxFailsafe.') @@ -720,12 +724,10 @@ async def test_TC_CGEN_2_2(self): target_time = start_time + maxFailsafe + 1 while time.time() < target_time: try: - await asyncio.sleep(0.1) # Esperar un tiempo pequeño en cada iteración + await asyncio.sleep(0.1) except asyncio.CancelledError: - # Si se lanza el TimeoutError, simplemente lo ignoramos - continue # Ignoramos el error y seguimos esperando hasta que se cumpla el tiempo objetivo + continue - # await asyncio.sleep(maxFailsafe + 1) elapsed_time = time.time() - start_time logger.info(f'Step #33: {run_type} - Failsafe timer (max_fail_safe) expired after {elapsed_time} seconds.') @@ -747,10 +749,8 @@ async def test_TC_CGEN_2_2(self): # self.step(35) # cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe, breadcrumb=1) # resp = await self.send_single_cmd( - # dev_ctrl=self.default_controller, - # node_id=self.dut_node_id, - # # dev_ctrl=TH2, - # # node_id=newNodeId+1, + # dev_ctrl=TH2, + # node_id=newNodeId+1, # cmd=cmd) # # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'BusyWithOtherAdmin'(4) # logger.info(f'Step #35 - TH2 ArmFailSafeResponse with ErrorCode as BusyWithOtherAdmin ({resp.errorCode})') @@ -826,7 +826,7 @@ async def test_TC_CGEN_2_2(self): target_time = start_time + (maxFailsafe / 2) # Make TH1 wait until the current time is greater than or equal to the target time while time.time() < target_time: - time.sleep(0.1) # Wait for 100ms to avoid excessive CPU usage + await asyncio.sleep(0.1) # Check if the elapsed time since start_time has reached or exceeded half of the maxFailsafe time (maxFailsafe / 2). # If the current time surpasses the start time by at least half of the maxFailsafe time, the TH1 process is allowed to proceed. @@ -879,7 +879,7 @@ async def test_TC_CGEN_2_2(self): # Make TH1 wait until the current time is greater than or equal to the target time while time.time() < target_time: - time.sleep(0.1) # Wait for 100ms to avoid excessive CPU usage + await asyncio.sleep(0.1) # Checks if the elapsed time from start_time has met or exceeded maxFailsafe # If the current time has passed the start time by at least maxFailsafe, the TH1 process can proceed From 22189217426dba87881b1d89134044caa14a04fb Mon Sep 17 00:00:00 2001 From: juandediosg Date: Fri, 7 Feb 2025 10:28:40 -0600 Subject: [PATCH 10/25] Add PICS argument for CI --- src/python_testing/TC_CGEN_2_2.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index c7108bf74947cb..f0edd2d97b432e 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -28,6 +28,7 @@ # --commissioning-method on-network # --discriminator 1234 # --passcode 20202021 +# --PICS src/app/tests/suites/certification/ci-pics-values # --trace-to json:${TRACE_TEST_JSON}.json # --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto # factory-reset: true @@ -327,6 +328,9 @@ async def test_TC_CGEN_2_2(self): attribute=self.cluster_cgen.Attributes.BasicCommissioningInfo) maxFailsafe = basic_commissioning_info.maxCumulativeFailsafeSeconds logger.info(f'Step #2: The MaxCumulativeFailsafeSeconds (max_fail_safe): {maxFailsafe}') + maxFailsafe = 20 + logger.info( + f'Step #2: Overridden MaxCumulativeFailsafeSeconds (max_fail_safe) to {maxFailsafe} seconds for optimal execution in Cert and to avoid long waits') # TH1 steps #3 through #5 using the function run_steps_3_to_5 self.step('3-5') @@ -344,7 +348,7 @@ async def test_TC_CGEN_2_2(self): asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 1, "Unexpected number of entries in the TrustedRootCertificates table after update") - # Optionally, check if the new certificate is in the updated list + # Check if the new certificate is in the updated list asserts.assert_in(new_root_cert, trusted_root_list_original_updated, "New root certificate was not added to the trusted root list.") @@ -619,7 +623,6 @@ async def test_TC_CGEN_2_2(self): "The fabrics list size should match the original fabrics list size + 1.") self.step(29) - # logger.info("Step #29 - TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds and the Breadcrumb value as 1") cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe, breadcrumb=1) resp = await self.send_single_cmd( dev_ctrl=self.default_controller, From ddde2fc3e2fe0b9ee2d1ffc087bd2e81724d6b2f Mon Sep 17 00:00:00 2001 From: juandediosg Date: Fri, 7 Feb 2025 14:05:58 -0600 Subject: [PATCH 11/25] Refactor code: improved flow handling and optimized wait times --- src/python_testing/TC_CGEN_2_2.py | 189 ++++++++++++------------------ 1 file changed, 72 insertions(+), 117 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index f0edd2d97b432e..f692060e029139 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -91,7 +91,7 @@ async def expire_failsafe_timer(self, dev_ctrl, node_id, expiration_time_seconds return resp - async def run_steps_3_to_5(self, maxFailsafe: int, is_first_run: bool): + async def run_steps_3_to_5(self, failsafe_duration: int, is_first_run: bool): ''' Executes steps 3 through 5, with optional Test Step. @@ -103,7 +103,8 @@ async def run_steps_3_to_5(self, maxFailsafe: int, is_first_run: bool): Args: - maxFailsafe (int): The maximum failsafe timeout value in seconds to be used in Step 3. + failsafe_duration (int): The duration in seconds for which the failsafe remains active. + This should be less than the DUT MaxCumulativeFailsafeSeconds is_first_run (bool): A flag to control whether the step should be executed or skipped. Returns: @@ -111,7 +112,7 @@ async def run_steps_3_to_5(self, maxFailsafe: int, is_first_run: bool): ''' if is_first_run: self.step(3) - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe, breadcrumb=1) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=failsafe_duration, breadcrumb=1) resp = await self.send_single_cmd( dev_ctrl=self.default_controller, node_id=self.dut_node_id, @@ -291,8 +292,8 @@ def steps_TC_CGEN_2_2(self) -> list[TestStep]: TestStep(32, 'TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to maxFailsafe.'), TestStep(33, 'TH1 waits for PIXIT.CGEN.FailsafeExpiryLengthSeconds.'), TestStep(34, 'TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster.'), - # TestStep(35, '''TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds - # and the Breadcrumb value as 1.'''), + TestStep(35, '''TH2 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds + and the Breadcrumb value as 1.'''), TestStep(36, 'TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 0.'), TestStep(37, 'TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to maxFailsafe.'), TestStep(38, 'TH1 saves the current wall time clock in seconds as Tstart,'), @@ -310,6 +311,10 @@ def steps_TC_CGEN_2_2(self) -> list[TestStep]: @async_test_body async def test_TC_CGEN_2_2(self): + # PIXIT.CGEN.FailsafeExpiryLengthSeconds: + # Timeout used in test steps to verify failsafe. Must be less than DUT MaxCumulativeFailsafeSeconds + failsafe_expiration_seconds = 20 + self.step(0) # Read the Steps @@ -328,13 +333,10 @@ async def test_TC_CGEN_2_2(self): attribute=self.cluster_cgen.Attributes.BasicCommissioningInfo) maxFailsafe = basic_commissioning_info.maxCumulativeFailsafeSeconds logger.info(f'Step #2: The MaxCumulativeFailsafeSeconds (max_fail_safe): {maxFailsafe}') - maxFailsafe = 20 - logger.info( - f'Step #2: Overridden MaxCumulativeFailsafeSeconds (max_fail_safe) to {maxFailsafe} seconds for optimal execution in Cert and to avoid long waits') # TH1 steps #3 through #5 using the function run_steps_3_to_5 self.step('3-5') - new_root_cert = await self.run_steps_3_to_5(maxFailsafe, is_first_run=True) + new_root_cert = await self.run_steps_3_to_5(failsafe_expiration_seconds, is_first_run=True) self.step(6) trusted_root_list_original_updated = await self.read_single_attribute_check_success( @@ -380,24 +382,12 @@ async def test_TC_CGEN_2_2(self): else: # Running identifier run_type = "Cert Test" - # The maxFailsafe value is taken from PIXIT.CGEN.FailsafeExpiryLengthSeconds, which is set to 900 seconds (15 minutes) + # PIXIT.CGEN.FailsafeExpiryLengthSeconds, adjusted to avoid long waits logger.info( - f'Step #7: {run_type} - Waiting for Failsafe timer to expire for PIXIT.CGEN.FailsafeExpiryLengthSeconds (max_fail_safe): {maxFailsafe} seconds...') - - # Get the start time to measure the elapsed time - start_time = time.time() - target_time = start_time + (maxFailsafe + .5) - - # Wait for the full duration of the maxFailsafe time with an additional 0.5-second buffer - while time.time() < target_time: - try: - await asyncio.sleep(0.1) - except asyncio.CancelledError: - continue + f'Step #7: {run_type} - Waiting for Failsafe timer to expire for PIXIT.CGEN.FailsafeExpiryLengthSeconds: {failsafe_expiration_seconds} seconds...') - # Calculate and log the elapsed time since the start of the wait - elapsed_time = time.time() - start_time - logger.info(f'Step #7: Cert Test - Failsafe timer (max_fail_safe) expired after {elapsed_time} seconds.') + # Wait for the full duration of the PIXIT.CGEN.FailsafeExpiryLengthSeconds time with an additional 0.5-second buffer + await asyncio.sleep(failsafe_expiration_seconds + .5) # TH1 steps #8 through #9 using the function run_steps_8_to_9 self.step('8-9') @@ -405,7 +395,7 @@ async def test_TC_CGEN_2_2(self): self.step(10) # Repeat TH1 steps #3 through #5 using the function run_steps_3_to_5 - new_root_cert = await self.run_steps_3_to_5(maxFailsafe, is_first_run=False) + new_root_cert = await self.run_steps_3_to_5(failsafe_expiration_seconds, is_first_run=False) self.step(11) cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=0) @@ -435,7 +425,7 @@ async def test_TC_CGEN_2_2(self): logger.info(f'Step #13: Open Commissioning Window params with vars: {vars(params)}') self.step(14) - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=failsafe_expiration_seconds) resp = await self.send_single_cmd( dev_ctrl=self.default_controller, node_id=self.dut_node_id, @@ -623,7 +613,7 @@ async def test_TC_CGEN_2_2(self): "The fabrics list size should match the original fabrics list size + 1.") self.step(29) - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe, breadcrumb=1) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=failsafe_expiration_seconds, breadcrumb=1) resp = await self.send_single_cmd( dev_ctrl=self.default_controller, node_id=self.dut_node_id, @@ -650,24 +640,12 @@ async def test_TC_CGEN_2_2(self): attribute=self.cluster_opcreds.Attributes.TrustedRootCertificates) trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) logger.info(f'Step #31 - The updated num_trusted_roots_original: {trusted_root_list_original_size_updated}') - # Verify that the trusted root list size has increased by 1 + # Verify that the trusted root list size has increased by 1, the trusted root list size is numTrustedRootsOriginal + 2 asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 2, "Unexpected number of entries in the TrustedRootCertificates table after update") self.step(32) - # Save the original value of maxFailsafe before making any changes - original_maxFailsafe = maxFailsafe - if self.check_pics('PICS_SDK_CI_ONLY'): - # In CI environments, we set maxFailsafe to 10 to avoid unnecessary delays caused by waiting for failsafe timers to expire. - # This bypasses the normal behavior defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds and speeds up the test process. - - run_type = "CI Test" - maxFailsafe = 5 - else: - maxFailsafe = maxFailsafe - # Cert Test environment: use the defined maxFailsafe value (as per PIXIT.CGEN.FailsafeExpiryLengthSeconds) - run_type = "Cert Test" - # maxFailsafe remains unchanged (it will be set earlier or passed as an argument) + logger.info(f'Step #32: - The maxFailsafe (max_fail_safe): {maxFailsafe}') cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe) resp = await self.send_single_cmd( @@ -683,59 +661,37 @@ async def test_TC_CGEN_2_2(self): logger.info(f'Step #32: {run_type} - ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') self.step(33) - # Divide the maxFailsafe time in half - maxFailsafe_half = maxFailsafe / 2 - logger.info( - f'Step #33: {run_type} - Waiting for half of the maxFailsafe time ({maxFailsafe_half} seconds) before Failsafe timer expires.') - - start_time = time.time() - target_time = start_time + maxFailsafe_half - - # Wait the half of the maximum failsafe time using a while loop - while time.time() < target_time: - try: - await asyncio.sleep(0.1) - except asyncio.CancelledError: - continue - - elapsed_time = time.time() - start_time - logger.info(f'Step #33: {run_type} - Elapsed time: {elapsed_time} seconds after waiting for half of the maxFailsafe.') - - # Send the second ArmFailSafe command before the failsafe time expires - # This re-arms the failsafe timer, preventing it from expiring prematurely. - logger.info(f'Step #33: {run_type} - Send the second ArmFailSafe command before the timer expires.') - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe) - resp = await self.send_single_cmd( - dev_ctrl=self.default_controller, - node_id=self.dut_node_id, - cmd=cmd) - # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'OK'(0) - asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, - "Failure status returned from arm failsafe") - # Verify that DebugText is empty or has a maximum length of 512 characters - debug_text = resp.debugText - assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" - logger.info(f'Step #33: {run_type} - ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') + if self.check_pics('PICS_SDK_CI_ONLY'): + # Step 33 - In CI environments avoiding the original wait time defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds + # and speeding up test execution by setting the expiration time to 2 seconds. - # Re-set the Failsafe timer to full duration (maxFailsafe) - logger.info( - f'Step #33: {run_type} - Failsafe timer (max_fail_safe) is set to the maxFailsafe time: {maxFailsafe} seconds...') + # Running identifier + run_type = "CI Test" + logger.info( + f'Step 33: {run_type} - Bypassing failsafe expiration to avoid unnecessary delays in CI environment.') - start_time = time.time() + # Set the failsafe expiration timeout to 2 seconds, must be less than maxFailsafe (max_fail_safe). + failsafe_timeout_less_than_max = 2 + logger.info( + f'Step #33: {run_type} - Waiting for the failsafe timer ' + f'(PIXIT.CGEN.FailsafeExpiryLengthSeconds --adjusted time for CI) to approach expiration, ' + f'but not allowing it to fully expire. Waiting for: {failsafe_timeout_less_than_max} seconds.') - # Adding a buffer of 1 second to ensure the failsafe is cleanly disarmed before proceeding. - target_time = start_time + maxFailsafe + 1 - while time.time() < target_time: - try: - await asyncio.sleep(0.1) - except asyncio.CancelledError: - continue + # Wait PIXIT.CGEN.FailsafeExpiryLengthSeconds time with an additional 0.5-second buffer, not allowing the fully exire (max_fail_safe). + await asyncio.sleep(failsafe_timeout_less_than_max + .5) + else: + # Running identifier + run_type = "Cert Test" - elapsed_time = time.time() - start_time - logger.info(f'Step #33: {run_type} - Failsafe timer (max_fail_safe) expired after {elapsed_time} seconds.') + # Set the failsafe expiration timeout to PIXIT.CGEN.FailsafeExpiryLengthSecondsseconds, must be less than maxFailsafe (max_fail_safe). + failsafe_timeout_less_than_max = failsafe_expiration_seconds - # After the test actions, restore the original maxFailsafe value - maxFailsafe = original_maxFailsafe + logger.info( + f'Step #33: {run_type} - Waiting for the failsafe timer ' + f'(PIXIT.CGEN.FailsafeExpiryLengthSeconds --adjusted time for CI) to approach expiration, ' + f'but not allowing it to fully expire. Waiting for: {failsafe_timeout_less_than_max} seconds.') + # Wait PIXIT.CGEN.FailsafeExpiryLengthSeconds time with an additional 0.5-second buffer, not allowing the fully exire (max_fail_safe). + await asyncio.sleep(failsafe_timeout_less_than_max + .5) self.step(34) trusted_root_list_original_updated = await self.read_single_attribute_check_success( @@ -745,20 +701,20 @@ async def test_TC_CGEN_2_2(self): attribute=self.cluster_opcreds.Attributes.TrustedRootCertificates) trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) logger.info(f'Step #34 - The updated num_trusted_roots_original: {trusted_root_list_original_size_updated}') - # Verify that the trusted root list size - asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 1, + # Verify that the trusted root list size is numTrustedRootsOriginal + 2 + asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 2, "Step #34 - Unexpected number of entries in the TrustedRootCertificates table after update") - # self.step(35) - # cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe, breadcrumb=1) - # resp = await self.send_single_cmd( - # dev_ctrl=TH2, - # node_id=newNodeId+1, - # cmd=cmd) - # # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'BusyWithOtherAdmin'(4) - # logger.info(f'Step #35 - TH2 ArmFailSafeResponse with ErrorCode as BusyWithOtherAdmin ({resp.errorCode})') - # asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kBusyWithOtherAdmin, - # "Failure status returned from arm failsafe") + self.step(35) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=failsafe_expiration_seconds, breadcrumb=1) + resp = await self.send_single_cmd( + dev_ctrl=TH2, + node_id=newNodeId+1, + cmd=cmd) + # Verify that the DUT responds with ArmFailSafeResponse with ErrorCode as 'BusyWithOtherAdmin'(4) + logger.info(f'Step #35 - TH2 ArmFailSafeResponse with ErrorCode as BusyWithOtherAdmin ({resp.errorCode})') + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kBusyWithOtherAdmin, + "Failure status returned from arm failsafe") self.step(36) cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=0) @@ -810,12 +766,13 @@ async def test_TC_CGEN_2_2(self): trusted_root_list_original_size_updated = len(trusted_root_list_original_updated) logger.info( f'Step #40: The updated size of the num_trusted_roots_original list is {trusted_root_list_original_size_updated}') - # Verify that the trusted root list size has increased to numTrustedRootsOriginal + 2 - # ensuring that one element has been added, and the total size is now 3 + # Verify that the trusted root list size is numTrustedRootsOriginal + 2 asserts.assert_equal(trusted_root_list_original_size_updated, trusted_root_list_original_size + 2, "Unexpected number of entries in the TrustedRootCertificates table after update") self.step(41) + # Limit maxFailsafe to 20 seconds to prevent excessively long waits in tests (due maxFailsafe = 900 seconds). + maxFailsafe = failsafe_expiration_seconds if self.check_pics('PICS_SDK_CI_ONLY'): # In CI environment, bypass the wait for the failsafe expiration to avoid unnecessary delays. run_type = "CI Test" @@ -823,21 +780,18 @@ async def test_TC_CGEN_2_2(self): f'Step 41: {run_type} - Bypassing due to failsafe expiration workaround to avoid unnecessary delays in CI environment.') else: run_type = "Cert Test" - start_time = time.time() - # Divide the maxFailsafe time to half - target_time = start_time + (maxFailsafe / 2) - # Make TH1 wait until the current time is greater than or equal to the target time - while time.time() < target_time: - await asyncio.sleep(0.1) + # Make TH1 wait until the target_time is greater than or equal to half of the maxFailsafe time. + target_time = maxFailsafe/2 + await asyncio.sleep(target_time + .5) - # Check if the elapsed time since start_time has reached or exceeded half of the maxFailsafe time (maxFailsafe / 2). - # If the current time surpasses the start time by at least half of the maxFailsafe time, the TH1 process is allowed to proceed. + # Verify that at least half of the maxFailsafe time has passed, allowing TH1 to proceed. current_time = self.get_current_utc_time_str() logger.info( f'Step #41: {run_type} - TH1 can proceed. ' f'Started expiration time: {start_time_formatted}, Current time: {current_time}, ' - f'MaxFailsafe time at half ({maxFailsafe/2} seconds) has passed, confirming that ArmFailSafe has NOT expired yet.' + f'Half of the maxFailsafe duration ({target_time} seconds) has passed. ' + f'Confirmation that ArmFailSafe has not expired yet.' ) self.step(42) @@ -875,22 +829,23 @@ async def test_TC_CGEN_2_2(self): ) else: run_type = "Cert Test" - start_time = time.time() # Calculate the target time (Tstart + maxFailsafe) target_time = start_time + maxFailsafe + logger.info(f'Step #43: target_time: {target_time}') - # Make TH1 wait until the current time is greater than or equal to the target time + # Make TH1 wait until the target_time is greater than or equal to maxFailsafe time. while time.time() < target_time: await asyncio.sleep(0.1) # Checks if the elapsed time from start_time has met or exceeded maxFailsafe - # If the current time has passed the start time by at least maxFailsafe, the TH1 process can proceed + # TH1 process can proceed current_time = self.get_current_utc_time_str() logger.info( f'Step #43: {run_type} - TH1 can proceed. ' f'Started expiration time: {start_time_formatted}, Current time: {current_time}, ' - f'MaxFailsafe: {maxFailsafe} seconds has passed, confirming ArmFailSafe has expired.' + f'MaxFailsafe duration ({target_time} seconds) has passed. ' + f'Confirmation that ArmFailSafe has expired.' ) self.step(44) From 02ae474d734b50d85f1ff12c5f40e0122e6d3bfa Mon Sep 17 00:00:00 2001 From: juandediosg Date: Fri, 7 Feb 2025 17:16:10 -0600 Subject: [PATCH 12/25] Refactor function call to is_pics_sdk_ci_only and update/improve waits --- src/python_testing/TC_CGEN_2_2.py | 70 +++++++++++++++---------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index f692060e029139..aba6973d0ab466 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -42,12 +42,11 @@ from datetime import datetime, timezone import chip.clusters as Clusters -import chip.discovery as Discovery from chip import ChipDeviceCtrl -from chip.exceptions import ChipStackError from chip.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main from mobly import asserts + # Create logger logger = logging.getLogger(__name__) @@ -211,7 +210,7 @@ async def run_steps_8_to_9(self, trusted_root_list_original_size: int, is_first_ logger.info( f'Step #12 - Repeated Step #9: After waiting for failsafe timeout the Breadcrumb attribute: {breadcrumb_info}') - def get_current_utc_time_str(self): + def get_current_utc_time_str(self, c_time=None): ''' Get the current time in UTC and return it as a formatted string. @@ -222,11 +221,15 @@ def get_current_utc_time_str(self): Returns: str: The current time formatted as 'dd-mm hh:mm:ss.sss'. ''' - current_time = time.time() # Get the current timestamp + + # If c_time is not provided, use the current time + if c_time is None: + c_time = time.time() + formatted_time = ( - datetime.fromtimestamp(current_time, tz=timezone.utc) + datetime.fromtimestamp(c_time) .strftime('%d-%m %H:%M:%S.') - + str(int((current_time % 1) * 1000)).zfill(3) + + str(int((c_time % 1) * 1000)).zfill(3) ) return formatted_time @@ -236,8 +239,7 @@ def desc_TC_CGEN_2_2(self) -> str: def pics_TC_CGEN_2_2(self): """Return the PICS definitions associated with this test.""" pics = [ - "CGEN.S", - "OPCREDS.S", + "CGEN.S" ] return pics @@ -357,7 +359,7 @@ async def test_TC_CGEN_2_2(self): logger.info(f'Step #6: The updated size of the num_trusted_roots_original list: {trusted_root_list_original_size_updated}') self.step(7) - if self.check_pics('PICS_SDK_CI_ONLY'): + if self.is_pics_sdk_ci_only: # Step 7 - In CI environments, the 'expire_failsafe_timer' function is used to immediately force the failsafe timer to expire, # avoiding the original wait time defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds # and speeding up test execution by setting the expiration time to 1 second. @@ -658,14 +660,13 @@ async def test_TC_CGEN_2_2(self): # Verify that DebugText is empty or has a maximum length of 512 characters debug_text = resp.debugText assert debug_text == '' or len(debug_text) <= 512, "debugText must be empty or have a maximum length of 512 characters" - logger.info(f'Step #32: {run_type} - ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') + logger.info(f'Step #32: ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') self.step(33) - if self.check_pics('PICS_SDK_CI_ONLY'): + if self.is_pics_sdk_ci_only: # Step 33 - In CI environments avoiding the original wait time defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds # and speeding up test execution by setting the expiration time to 2 seconds. - # Running identifier run_type = "CI Test" logger.info( f'Step 33: {run_type} - Bypassing failsafe expiration to avoid unnecessary delays in CI environment.') @@ -680,7 +681,6 @@ async def test_TC_CGEN_2_2(self): # Wait PIXIT.CGEN.FailsafeExpiryLengthSeconds time with an additional 0.5-second buffer, not allowing the fully exire (max_fail_safe). await asyncio.sleep(failsafe_timeout_less_than_max + .5) else: - # Running identifier run_type = "Cert Test" # Set the failsafe expiration timeout to PIXIT.CGEN.FailsafeExpiryLengthSecondsseconds, must be less than maxFailsafe (max_fail_safe). @@ -739,18 +739,18 @@ async def test_TC_CGEN_2_2(self): logger.info(f'Step #37: ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') self.step(38) - if self.check_pics('PICS_SDK_CI_ONLY'): + if self.is_pics_sdk_ci_only: # In CI environment, bypass the wait for the failsafe expiration to avoid unnecessary delays. run_type = "CI Test" logger.info( f'Step 38: {run_type} - Bypassing due to failsafe expiration workaround to avoid unnecessary delays in CI environment.') else: run_type = "Cert Test" - start_time = time.time() + t_start = time.time() # Get the current time and format it for logging - start_time_formatted = self.get_current_utc_time_str() - logger.info(f'Step #38: {run_type} - TH1 saves the Current time: {start_time_formatted}') + start_time_formatted = self.get_current_utc_time_str(t_start) + logger.info(f'Step #38: {run_type} - TH1 saves the Current time as t_start: {start_time_formatted}') self.step(39) # Reused TrustedRootCertificate created in step #5 - Send command to add new trusted root certificate @@ -773,7 +773,7 @@ async def test_TC_CGEN_2_2(self): self.step(41) # Limit maxFailsafe to 20 seconds to prevent excessively long waits in tests (due maxFailsafe = 900 seconds). maxFailsafe = failsafe_expiration_seconds - if self.check_pics('PICS_SDK_CI_ONLY'): + if self.is_pics_sdk_ci_only: # In CI environment, bypass the wait for the failsafe expiration to avoid unnecessary delays. run_type = "CI Test" logger.info( @@ -781,16 +781,17 @@ async def test_TC_CGEN_2_2(self): else: run_type = "Cert Test" - # Make TH1 wait until the target_time is greater than or equal to half of the maxFailsafe time. + # Make TH1 wait until the target_time is greater than or equal to half of the maxFailsafe time with an additional 0.5-second buffer. target_time = maxFailsafe/2 await asyncio.sleep(target_time + .5) # Verify that at least half of the maxFailsafe time has passed, allowing TH1 to proceed. - current_time = self.get_current_utc_time_str() + current_time_formatted = self.get_current_utc_time_str() logger.info( - f'Step #41: {run_type} - TH1 can proceed. ' - f'Started expiration time: {start_time_formatted}, Current time: {current_time}, ' - f'Half of the maxFailsafe duration ({target_time} seconds) has passed. ' + f'Step #41: {run_type} - - MaxFailsafe is {maxFailsafe}. ' + f'TH1 can proceed. Started expiration time (t_start): {start_time_formatted}, ' + f'Current time: {current_time_formatted}, ' + f'The target time ({target_time} seconds) has passed. ' f'Confirmation that ArmFailSafe has not expired yet.' ) @@ -806,7 +807,7 @@ async def test_TC_CGEN_2_2(self): logger.info(f'Step #42: ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') self.step(43) - if self.check_pics('PICS_SDK_CI_ONLY'): + if self.is_pics_sdk_ci_only: # Step 43 - In CI environments, the 'expire_failsafe_timer' function is used to immediately force the failsafe timer to expire, # avoiding the original wait time defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds, # and speeding up test execution by setting the expiration time to 1 second. @@ -830,22 +831,21 @@ async def test_TC_CGEN_2_2(self): else: run_type = "Cert Test" - # Calculate the target time (Tstart + maxFailsafe) - target_time = start_time + maxFailsafe - logger.info(f'Step #43: target_time: {target_time}') + # Calculate the target time (Tstart + maxFailsafe) with an additional 0.5-second buffer + target_time = (t_start + maxFailsafe) + .5 - # Make TH1 wait until the target_time is greater than or equal to maxFailsafe time. - while time.time() < target_time: - await asyncio.sleep(0.1) + # Wait until the target_time is reached using asyncio.sleep to avoid busy-waiting + await asyncio.sleep(target_time - time.time()) # Checks if the elapsed time from start_time has met or exceeded maxFailsafe # TH1 process can proceed - current_time = self.get_current_utc_time_str() + current_time_formatted = self.get_current_utc_time_str() logger.info( - f'Step #43: {run_type} - TH1 can proceed. ' - f'Started expiration time: {start_time_formatted}, Current time: {current_time}, ' - f'MaxFailsafe duration ({target_time} seconds) has passed. ' - f'Confirmation that ArmFailSafe has expired.' + f'Step #43: {run_type} - - MaxFailsafe is {maxFailsafe}. ' + f'TH1 can proceed. , ' + f'Current time: {current_time_formatted}, ' + f'The target time ({maxFailsafe} seconds) has passed since expiration time started (t_start): {start_time_formatted}. ' + f'Confirmation that ArmFailSafe has not expired yet.' ) self.step(44) From 60129f6154ef32dc7abead608317e0a6cb556d99 Mon Sep 17 00:00:00 2001 From: juandediosg Date: Fri, 7 Feb 2025 17:32:10 -0600 Subject: [PATCH 13/25] Fixed: Lint Code Base --- src/python_testing/TC_CGEN_2_2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index aba6973d0ab466..3dd4c954cd038b 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -39,7 +39,7 @@ import logging import random import time -from datetime import datetime, timezone +from datetime import datetime import chip.clusters as Clusters from chip import ChipDeviceCtrl From 4e911a3944beb6c04db496d2bc60017c95fc18c5 Mon Sep 17 00:00:00 2001 From: juandediosg Date: Fri, 7 Feb 2025 18:11:39 -0600 Subject: [PATCH 14/25] Code cleanup and restyle --- src/python_testing/TC_CGEN_2_2.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index 3dd4c954cd038b..d60613b25b7e8c 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -46,8 +46,6 @@ from chip.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main from mobly import asserts - -# Create logger logger = logging.getLogger(__name__) From cc7284606e560fe68070390be16c5f833145f4e9 Mon Sep 17 00:00:00 2001 From: juandediosg Date: Wed, 12 Feb 2025 13:19:26 -0600 Subject: [PATCH 15/25] Fixed: Update try/except with specific exceptions and fail the test if the expected exception is not raised --- src/python_testing/TC_CGEN_2_2.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index d60613b25b7e8c..97df8d23ff5374 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -42,7 +42,9 @@ from datetime import datetime import chip.clusters as Clusters +from chip.exceptions import ChipStackError from chip import ChipDeviceCtrl +from chip.interaction_model import InteractionModelError from chip.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main from mobly import asserts @@ -532,8 +534,12 @@ async def test_TC_CGEN_2_2(self): dev_ctrl=TH2, node_id=newNodeId, cmd=cmd) - except Exception as e: + except ChipStackError as e: + asserts.assert_in('Timeout', + str(e), f'Expected Timeout error, but got {str(e)}') logger.info(f"Step #22 - TH2 Expected error occurred during ArmFailSafe command: {str(e)}. Proceeding to next step.") + else: + asserts.assert_true(False, 'Expected Timeout, but no exception occurred.') self.step(23) nocs_updated = await self.read_single_attribute_check_success( @@ -596,9 +602,12 @@ async def test_TC_CGEN_2_2(self): cmd = self.cluster_opcreds.Commands.AddTrustedRootCertificate(th2_new_root_cert) resp = await self.send_single_cmd(dev_ctrl=TH2, node_id=newNodeId+1, cmd=cmd) - except Exception as e: - logger.info( - f"Step #27 - Expected error occurred during TrustedRootCertificate command: {str(e)}. Proceeding to next step.") + except InteractionModelError as e: + asserts.assert_in('FailsafeRequired (0xca)', + str(e), f'Expected FailsafeRequired error, but got {str(e)}') + logger.info(f'Step #27 - Expected error occurred: {str(e)}. Proceeding to next step.') + else: + asserts.assert_true(False, 'Expected InteractionModelError with FailsafeRequired, but no exception occurred.') self.step(28) fabrics_updated = await self.read_single_attribute_check_success( From fb499da6ed9227045ee976af77e09f2488caf313 Mon Sep 17 00:00:00 2001 From: juandediosg Date: Wed, 12 Feb 2025 20:35:32 -0600 Subject: [PATCH 16/25] Fixed: Rename fn to for clarity based on feedback --- src/python_testing/TC_CGEN_2_2.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index 97df8d23ff5374..1f489693c23bb1 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -56,7 +56,7 @@ class TC_CGEN_2_2(MatterBaseTest): cluster_opcreds = Clusters.OperationalCredentials cluster_cgen = Clusters.GeneralCommissioning - async def expire_failsafe_timer(self, dev_ctrl, node_id, expiration_time_seconds): + async def set_failsafe_timer(self, dev_ctrl, node_id, expiration_time_seconds): ''' Triggering the failsafe expiry to clean up resources like fabric tables, NOCs (Node Operational Credentials), and trusted root certificates. This is necessary to avoid accumulation of invalid data, which could be caused @@ -360,7 +360,7 @@ async def test_TC_CGEN_2_2(self): self.step(7) if self.is_pics_sdk_ci_only: - # Step 7 - In CI environments, the 'expire_failsafe_timer' function is used to immediately force the failsafe timer to expire, + # Step 7 - In CI environments, the 'set_failsafe_timer' function is used to immediately force the failsafe timer to expire, # avoiding the original wait time defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds # and speeding up test execution by setting the expiration time to 1 second. @@ -373,7 +373,7 @@ async def test_TC_CGEN_2_2(self): expiration_time_seconds = 1 # Force the failsafe timer to expire immediately for TH1, avoiding unnecessary delays in CI environments - resp = await self.expire_failsafe_timer( + resp = await self.set_failsafe_timer( dev_ctrl=self.default_controller, node_id=self.dut_node_id, expiration_time_seconds=expiration_time_seconds) @@ -815,7 +815,7 @@ async def test_TC_CGEN_2_2(self): self.step(43) if self.is_pics_sdk_ci_only: - # Step 43 - In CI environments, the 'expire_failsafe_timer' function is used to immediately force the failsafe timer to expire, + # Step 43 - In CI environments, the 'set_failsafe_timer' function is used to immediately force the failsafe timer to expire, # avoiding the original wait time defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds, # and speeding up test execution by setting the expiration time to 1 second. @@ -827,7 +827,7 @@ async def test_TC_CGEN_2_2(self): expiration_time_seconds = 1 # Force the failsafe timer to expire immediately for TH1, avoiding unnecessary delays in CI environments - resp = await self.expire_failsafe_timer( + resp = await self.set_failsafe_timer( dev_ctrl=self.default_controller, node_id=self.dut_node_id, expiration_time_seconds=expiration_time_seconds) From de602895c163a611c37cdf7c58197519ab82389a Mon Sep 17 00:00:00 2001 From: juandediosg Date: Wed, 12 Feb 2025 21:27:04 -0600 Subject: [PATCH 17/25] Fixed: Removed UTC time function, logging elapsed time in ms in steps 41 and 43. --- src/python_testing/TC_CGEN_2_2.py | 42 ++++++++----------------------- 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index 1f489693c23bb1..c6c1cf20bfd182 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -210,29 +210,6 @@ async def run_steps_8_to_9(self, trusted_root_list_original_size: int, is_first_ logger.info( f'Step #12 - Repeated Step #9: After waiting for failsafe timeout the Breadcrumb attribute: {breadcrumb_info}') - def get_current_utc_time_str(self, c_time=None): - ''' - Get the current time in UTC and return it as a formatted string. - - The format of the returned string will be 'dd-mm hh:mm:ss.sss', - where 'dd' is the day, 'mm' is the month, 'hh' is the hour, - 'mm' is the minute, 'ss' is the second, and 'sss' are the milliseconds. - - Returns: - str: The current time formatted as 'dd-mm hh:mm:ss.sss'. - ''' - - # If c_time is not provided, use the current time - if c_time is None: - c_time = time.time() - - formatted_time = ( - datetime.fromtimestamp(c_time) - .strftime('%d-%m %H:%M:%S.') - + str(int((c_time % 1) * 1000)).zfill(3) - ) - return formatted_time - def desc_TC_CGEN_2_2(self) -> str: return '[TC-CGEN-2.2] ArmFailSafe command verification [DUT - Server]' @@ -756,8 +733,7 @@ async def test_TC_CGEN_2_2(self): t_start = time.time() # Get the current time and format it for logging - start_time_formatted = self.get_current_utc_time_str(t_start) - logger.info(f'Step #38: {run_type} - TH1 saves the Current time as t_start: {start_time_formatted}') + logger.info(f'Step #38: {run_type} - TH1 saves the Current time as t_start') self.step(39) # Reused TrustedRootCertificate created in step #5 - Send command to add new trusted root certificate @@ -792,12 +768,13 @@ async def test_TC_CGEN_2_2(self): target_time = maxFailsafe/2 await asyncio.sleep(target_time + .5) + c_time = time.time() + elapsed_time = (c_time - t_start) * 1000 + # Verify that at least half of the maxFailsafe time has passed, allowing TH1 to proceed. - current_time_formatted = self.get_current_utc_time_str() logger.info( f'Step #41: {run_type} - - MaxFailsafe is {maxFailsafe}. ' - f'TH1 can proceed. Started expiration time (t_start): {start_time_formatted}, ' - f'Current time: {current_time_formatted}, ' + f'TH1 can proceed. Elapsed time: {elapsed_time:.2f} ms.' f'The target time ({target_time} seconds) has passed. ' f'Confirmation that ArmFailSafe has not expired yet.' ) @@ -844,14 +821,15 @@ async def test_TC_CGEN_2_2(self): # Wait until the target_time is reached using asyncio.sleep to avoid busy-waiting await asyncio.sleep(target_time - time.time()) + c_time = time.time() + elapsed_time = (c_time - t_start) * 1000 + # Checks if the elapsed time from start_time has met or exceeded maxFailsafe # TH1 process can proceed - current_time_formatted = self.get_current_utc_time_str() logger.info( f'Step #43: {run_type} - - MaxFailsafe is {maxFailsafe}. ' - f'TH1 can proceed. , ' - f'Current time: {current_time_formatted}, ' - f'The target time ({maxFailsafe} seconds) has passed since expiration time started (t_start): {start_time_formatted}. ' + f'TH1 can proceed. Elapsed time: {elapsed_time:.2f} ms. ' + f'The target time ({maxFailsafe} seconds) has passed ' f'Confirmation that ArmFailSafe has not expired yet.' ) From 6255dd74ccfe3adb18f08aef8dbd0d8368d6210d Mon Sep 17 00:00:00 2001 From: juandediosg Date: Thu, 13 Feb 2025 17:42:58 -0600 Subject: [PATCH 18/25] Fixed: Hooked failsafe_expiration_seconds to user parameter PIXIT.CGEN.FailsafeExpiryLengthSeconds for CI or Cert flow flexibility. --- src/python_testing/TC_CGEN_2_2.py | 45 +++++++++++++++---------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index c6c1cf20bfd182..1a71210744b12f 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -29,6 +29,7 @@ # --discriminator 1234 # --passcode 20202021 # --PICS src/app/tests/suites/certification/ci-pics-values +# --int-arg PIXIT.CGEN.FailsafeExpiryLengthSeconds:20 # --trace-to json:${TRACE_TEST_JSON}.json # --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto # factory-reset: true @@ -292,7 +293,8 @@ async def test_TC_CGEN_2_2(self): # PIXIT.CGEN.FailsafeExpiryLengthSeconds: # Timeout used in test steps to verify failsafe. Must be less than DUT MaxCumulativeFailsafeSeconds - failsafe_expiration_seconds = 20 + failsafe_expiration_seconds = self.matter_test_config.global_test_params['PIXIT.CGEN.FailsafeExpiryLengthSeconds'] + logger.info(f'Value of PIXIT.CGEN.FailsafeExpiryLengthSeconds: {failsafe_expiration_seconds}') self.step(0) @@ -346,16 +348,13 @@ async def test_TC_CGEN_2_2(self): logger.info( f'Step 7: {run_type} - Bypassing failsafe expiration to avoid unnecessary delays in CI environment.') - # Expiration time set to 1 second to immediately expire the failsafe timer - expiration_time_seconds = 1 - # Force the failsafe timer to expire immediately for TH1, avoiding unnecessary delays in CI environments resp = await self.set_failsafe_timer( dev_ctrl=self.default_controller, node_id=self.dut_node_id, - expiration_time_seconds=expiration_time_seconds) + expiration_time_seconds=failsafe_expiration_seconds) logger.info( - f'Step #7 {run_type} - Failsafe timer expiration bypassed for TH1 by setting expiration time to {expiration_time_seconds} seconds. ' + f'Step #7 {run_type} - Failsafe timer expiration bypassed for TH1 by setting expiration time to {failsafe_expiration_seconds} seconds. ' f'Test continues without the original wait.' ) else: @@ -647,6 +646,12 @@ async def test_TC_CGEN_2_2(self): logger.info(f'Step #32: ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') self.step(33) + + # Set the failsafe expiration timeout to PIXIT.CGEN.FailsafeExpiryLengthSeconds seconds, must be less than maxFailsafe (max_fail_safe). + failsafe_timeout_less_than_max = failsafe_expiration_seconds + # Verify that failsafe_timeout_less_than_max is less than max_fail_safe + asserts.assert_less(failsafe_timeout_less_than_max, maxFailsafe) + if self.is_pics_sdk_ci_only: # Step 33 - In CI environments avoiding the original wait time defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds # and speeding up test execution by setting the expiration time to 2 seconds. @@ -655,24 +660,18 @@ async def test_TC_CGEN_2_2(self): logger.info( f'Step 33: {run_type} - Bypassing failsafe expiration to avoid unnecessary delays in CI environment.') - # Set the failsafe expiration timeout to 2 seconds, must be less than maxFailsafe (max_fail_safe). - failsafe_timeout_less_than_max = 2 logger.info( f'Step #33: {run_type} - Waiting for the failsafe timer ' f'(PIXIT.CGEN.FailsafeExpiryLengthSeconds --adjusted time for CI) to approach expiration, ' f'but not allowing it to fully expire. Waiting for: {failsafe_timeout_less_than_max} seconds.') - # Wait PIXIT.CGEN.FailsafeExpiryLengthSeconds time with an additional 0.5-second buffer, not allowing the fully exire (max_fail_safe). await asyncio.sleep(failsafe_timeout_less_than_max + .5) else: run_type = "Cert Test" - # Set the failsafe expiration timeout to PIXIT.CGEN.FailsafeExpiryLengthSecondsseconds, must be less than maxFailsafe (max_fail_safe). - failsafe_timeout_less_than_max = failsafe_expiration_seconds - logger.info( f'Step #33: {run_type} - Waiting for the failsafe timer ' - f'(PIXIT.CGEN.FailsafeExpiryLengthSeconds --adjusted time for CI) to approach expiration, ' + f'(PIXIT.CGEN.FailsafeExpiryLengthSeconds) to approach expiration, ' f'but not allowing it to fully expire. Waiting for: {failsafe_timeout_less_than_max} seconds.') # Wait PIXIT.CGEN.FailsafeExpiryLengthSeconds time with an additional 0.5-second buffer, not allowing the fully exire (max_fail_safe). await asyncio.sleep(failsafe_timeout_less_than_max + .5) @@ -727,7 +726,7 @@ async def test_TC_CGEN_2_2(self): # In CI environment, bypass the wait for the failsafe expiration to avoid unnecessary delays. run_type = "CI Test" logger.info( - f'Step 38: {run_type} - Bypassing due to failsafe expiration workaround to avoid unnecessary delays in CI environment.') + f'Step #38: {run_type} - Bypassing due to failsafe expiration workaround to avoid unnecessary delays in CI environment.') else: run_type = "Cert Test" t_start = time.time() @@ -754,13 +753,14 @@ async def test_TC_CGEN_2_2(self): "Unexpected number of entries in the TrustedRootCertificates table after update") self.step(41) - # Limit maxFailsafe to 20 seconds to prevent excessively long waits in tests (due maxFailsafe = 900 seconds). + # Limit maxFailsafe to PIXIT.CGEN.FailsafeExpiryLengthSeconds seconds to prevent excessively long waits in tests (due maxFailsafe = 900 seconds). + maxFailsafe_original = maxFailsafe maxFailsafe = failsafe_expiration_seconds if self.is_pics_sdk_ci_only: # In CI environment, bypass the wait for the failsafe expiration to avoid unnecessary delays. run_type = "CI Test" logger.info( - f'Step 41: {run_type} - Bypassing due to failsafe expiration workaround to avoid unnecessary delays in CI environment.') + f'Step #41: {run_type} - Bypassing due to failsafe expiration workaround to avoid unnecessary delays in CI environment.') else: run_type = "Cert Test" @@ -780,7 +780,7 @@ async def test_TC_CGEN_2_2(self): ) self.step(42) - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=maxFailsafe_original) resp = await self.send_single_cmd( dev_ctrl=self.default_controller, node_id=self.dut_node_id, @@ -798,18 +798,15 @@ async def test_TC_CGEN_2_2(self): run_type = "CI Test" logger.info( - f'Step 43: {run_type} - Bypassing due to failsafe expiration workaround to avoid unnecessary delays in CI environment.') - - # Expiration time set to 1 second to immediately expire the failsafe timer - expiration_time_seconds = 1 + f'Step #43: {run_type} - Bypassing due to failsafe expiration workaround to avoid unnecessary delays in CI environment.') # Force the failsafe timer to expire immediately for TH1, avoiding unnecessary delays in CI environments resp = await self.set_failsafe_timer( dev_ctrl=self.default_controller, node_id=self.dut_node_id, - expiration_time_seconds=expiration_time_seconds) + expiration_time_seconds=failsafe_expiration_seconds) logger.info( - f'Step #43 {run_type} - Failsafe timer expiration bypassed for TH1 by setting expiration time to {expiration_time_seconds} seconds. ' + f'Step #43 {run_type} - Failsafe timer expiration bypassed for TH1 by setting expiration time to {failsafe_expiration_seconds} seconds. ' f'Test continues without the original wait.' ) else: @@ -827,7 +824,7 @@ async def test_TC_CGEN_2_2(self): # Checks if the elapsed time from start_time has met or exceeded maxFailsafe # TH1 process can proceed logger.info( - f'Step #43: {run_type} - - MaxFailsafe is {maxFailsafe}. ' + f'Step #43: {run_type} - MaxFailsafe is {maxFailsafe}. ' f'TH1 can proceed. Elapsed time: {elapsed_time:.2f} ms. ' f'The target time ({maxFailsafe} seconds) has passed ' f'Confirmation that ArmFailSafe has not expired yet.' From 722f4e28dbaeaa2577d8f1b9eecfd5f196773f34 Mon Sep 17 00:00:00 2001 From: juandediosg Date: Thu, 13 Feb 2025 18:23:14 -0600 Subject: [PATCH 19/25] Fixed: Lint Code base and Restyled. --- src/python_testing/TC_CGEN_2_2.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index 1a71210744b12f..904a918398b7ee 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -40,11 +40,10 @@ import logging import random import time -from datetime import datetime import chip.clusters as Clusters -from chip.exceptions import ChipStackError from chip import ChipDeviceCtrl +from chip.exceptions import ChipStackError from chip.interaction_model import InteractionModelError from chip.testing.matter_testing import MatterBaseTest, TestStep, async_test_body, default_matter_test_main from mobly import asserts From b1bb5a4785eb608124b31a2ed34fde7fbb5ce782 Mon Sep 17 00:00:00 2001 From: juandediosg Date: Fri, 14 Feb 2025 12:08:21 -0600 Subject: [PATCH 20/25] Fixed: Argument PIXIT.CGEN.FailsafeExpiryLengthSeconds updated to 1 --- src/python_testing/TC_CGEN_2_2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index 904a918398b7ee..0312f25788d1f5 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -29,7 +29,7 @@ # --discriminator 1234 # --passcode 20202021 # --PICS src/app/tests/suites/certification/ci-pics-values -# --int-arg PIXIT.CGEN.FailsafeExpiryLengthSeconds:20 +# --int-arg PIXIT.CGEN.FailsafeExpiryLengthSeconds:1 # --trace-to json:${TRACE_TEST_JSON}.json # --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto # factory-reset: true From 0134ef560fc731d7864e8b35dfe77d42ea540691 Mon Sep 17 00:00:00 2001 From: juandediosg Date: Wed, 26 Feb 2025 11:43:55 -0600 Subject: [PATCH 21/25] Added constant variable FAILSAFE_EXPIRATION_SECONDS for cert tests to handle timeout more effectively --- src/python_testing/TC_CGEN_2_2.py | 36 +++++++++++++++++++------------ 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index 0312f25788d1f5..f77d90e457e3c8 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -56,6 +56,8 @@ class TC_CGEN_2_2(MatterBaseTest): cluster_opcreds = Clusters.OperationalCredentials cluster_cgen = Clusters.GeneralCommissioning + CERT_FAILSAFE_EXPIRATION_TIME_SECONDS = 1 + async def set_failsafe_timer(self, dev_ctrl, node_id, expiration_time_seconds): ''' Triggering the failsafe expiry to clean up resources like fabric tables, NOCs (Node Operational Credentials), @@ -290,11 +292,6 @@ def steps_TC_CGEN_2_2(self) -> list[TestStep]: @async_test_body async def test_TC_CGEN_2_2(self): - # PIXIT.CGEN.FailsafeExpiryLengthSeconds: - # Timeout used in test steps to verify failsafe. Must be less than DUT MaxCumulativeFailsafeSeconds - failsafe_expiration_seconds = self.matter_test_config.global_test_params['PIXIT.CGEN.FailsafeExpiryLengthSeconds'] - logger.info(f'Value of PIXIT.CGEN.FailsafeExpiryLengthSeconds: {failsafe_expiration_seconds}') - self.step(0) # Read the Steps @@ -314,6 +311,18 @@ async def test_TC_CGEN_2_2(self): maxFailsafe = basic_commissioning_info.maxCumulativeFailsafeSeconds logger.info(f'Step #2: The MaxCumulativeFailsafeSeconds (max_fail_safe): {maxFailsafe}') + if self.is_pics_sdk_ci_only: + run_type = "CI Test" + failsafe_expiration_seconds = self.matter_test_config.global_test_params['PIXIT.CGEN.FailsafeExpiryLengthSeconds'] + else: + run_type = "Cert Test" + failsafe_expiration_seconds = self.CERT_FAILSAFE_EXPIRATION_TIME_SECONDS + + # Timeout used in test steps to verify failsafe. Must be less than DUT MaxCumulativeFailsafeSeconds + asserts.assert_less(failsafe_expiration_seconds, maxFailsafe, + "failsafe_expiration_seconds should be less than MaxCumulativeFailsafeSeconds.") + logger.info(f'Step #2: {run_type} - Value of FailsafeExpiryLengthSeconds: {failsafe_expiration_seconds}') + # TH1 steps #3 through #5 using the function run_steps_3_to_5 self.step('3-5') new_root_cert = await self.run_steps_3_to_5(failsafe_expiration_seconds, is_first_run=True) @@ -353,17 +362,17 @@ async def test_TC_CGEN_2_2(self): node_id=self.dut_node_id, expiration_time_seconds=failsafe_expiration_seconds) logger.info( - f'Step #7 {run_type} - Failsafe timer expiration bypassed for TH1 by setting expiration time to {failsafe_expiration_seconds} seconds. ' + f'Step #7: {run_type} - Failsafe timer expiration bypassed for TH1 by setting expiration time to {failsafe_expiration_seconds} seconds. ' f'Test continues without the original wait.' ) else: # Running identifier run_type = "Cert Test" - # PIXIT.CGEN.FailsafeExpiryLengthSeconds, adjusted to avoid long waits + # # FAILSAFE_EXPIRATION_SECONDS constant, adjusted to avoid long waits logger.info( - f'Step #7: {run_type} - Waiting for Failsafe timer to expire for PIXIT.CGEN.FailsafeExpiryLengthSeconds: {failsafe_expiration_seconds} seconds...') + f'Step #7: {run_type} - Waiting for Failsafe timer to expire for FAILSAFE_EXPIRATION_SECONDS constant: {failsafe_expiration_seconds} seconds...') - # Wait for the full duration of the PIXIT.CGEN.FailsafeExpiryLengthSeconds time with an additional 0.5-second buffer + # Wait for the full duration of the FAILSAFE_EXPIRATION_SECONDS constant time with an additional 0.5-second buffer await asyncio.sleep(failsafe_expiration_seconds + .5) # TH1 steps #8 through #9 using the function run_steps_8_to_9 @@ -645,8 +654,7 @@ async def test_TC_CGEN_2_2(self): logger.info(f'Step #32: ArmFailSafeResponse with ErrorCode as OK({resp.errorCode})') self.step(33) - - # Set the failsafe expiration timeout to PIXIT.CGEN.FailsafeExpiryLengthSeconds seconds, must be less than maxFailsafe (max_fail_safe). + # Set the failsafe expiration timeout to PIXIT.CGEN.FailsafeExpiryLengthSeconds seconds fpr CI or FAILSAFE_EXPIRATION_SECONDS constant for Cert, must be less than maxFailsafe (max_fail_safe). failsafe_timeout_less_than_max = failsafe_expiration_seconds # Verify that failsafe_timeout_less_than_max is less than max_fail_safe asserts.assert_less(failsafe_timeout_less_than_max, maxFailsafe) @@ -670,9 +678,9 @@ async def test_TC_CGEN_2_2(self): logger.info( f'Step #33: {run_type} - Waiting for the failsafe timer ' - f'(PIXIT.CGEN.FailsafeExpiryLengthSeconds) to approach expiration, ' + f'(FAILSAFE_EXPIRATION_SECONDS constant) to approach expiration, ' f'but not allowing it to fully expire. Waiting for: {failsafe_timeout_less_than_max} seconds.') - # Wait PIXIT.CGEN.FailsafeExpiryLengthSeconds time with an additional 0.5-second buffer, not allowing the fully exire (max_fail_safe). + # Wait FAILSAFE_EXPIRATION_SECONDS constant time with an additional 0.5-second buffer, not allowing the fully exire (max_fail_safe). await asyncio.sleep(failsafe_timeout_less_than_max + .5) self.step(34) @@ -752,7 +760,7 @@ async def test_TC_CGEN_2_2(self): "Unexpected number of entries in the TrustedRootCertificates table after update") self.step(41) - # Limit maxFailsafe to PIXIT.CGEN.FailsafeExpiryLengthSeconds seconds to prevent excessively long waits in tests (due maxFailsafe = 900 seconds). + # Limit maxFailsafe to PIXIT.CGEN.FailsafeExpiryLengthSeconds seconds for CI or FAILSAFE_EXPIRATION_SECONDS constant for Cert to prevent excessively long waits in tests (due maxFailsafe = 900 seconds). maxFailsafe_original = maxFailsafe maxFailsafe = failsafe_expiration_seconds if self.is_pics_sdk_ci_only: From 5eeea03a4ccecc122bd95a2a618d1d3a6c3aab28 Mon Sep 17 00:00:00 2001 From: juandediosg Date: Fri, 28 Feb 2025 09:36:58 -0600 Subject: [PATCH 22/25] Add step 2a to configure failsafe timer for CI and Cert environments, and create constant RUN_TEST_ to define the run test type. --- src/python_testing/TC_CGEN_2_2.py | 35 ++++++++++++++++++------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index f77d90e457e3c8..a9b1c8dbb021bd 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -57,6 +57,8 @@ class TC_CGEN_2_2(MatterBaseTest): cluster_cgen = Clusters.GeneralCommissioning CERT_FAILSAFE_EXPIRATION_TIME_SECONDS = 1 + RUN_TEST_CI = "CI Test" + RUN_TEST_CERT = "Cert Test" async def set_failsafe_timer(self, dev_ctrl, node_id, expiration_time_seconds): ''' @@ -229,6 +231,9 @@ def steps_TC_CGEN_2_2(self) -> list[TestStep]: and saves the number of list items as numTrustedRootsOriginal.'''), TestStep(2, '''TH1 reads the BasicCommissioningInfo attribute and saves the MaxCumulativeFailsafeSeconds as maxFailsafe.'''), + TestStep('2a', '''The failsafe timer is set to 1 second to avoid delays in test execution (which can be adjusted based on the environment for flexibility). + The variable is `PIXIT.CGEN.FailsafeExpiryLengthSeconds` for CI and + `CERT_FAILSAFE_EXPIRATION_TIME_SECONDS` for Cert.'''), TestStep('3-5', 'TH1 execute function run_steps_3_to_5 to run steps #3 through #5.'), TestStep(3, '''TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds and the Breadcrumb value as 1.'''), @@ -311,17 +316,19 @@ async def test_TC_CGEN_2_2(self): maxFailsafe = basic_commissioning_info.maxCumulativeFailsafeSeconds logger.info(f'Step #2: The MaxCumulativeFailsafeSeconds (max_fail_safe): {maxFailsafe}') + self.step('2a') + # Configure the failsafe timer. For CI, use PIXIT.CGEN.FailsafeExpiryLengthSeconds; for Cert, use CERT_FAILSAFE_EXPIRATION_TIME_SECONDS. if self.is_pics_sdk_ci_only: - run_type = "CI Test" + run_type = self.RUN_TEST_CI failsafe_expiration_seconds = self.matter_test_config.global_test_params['PIXIT.CGEN.FailsafeExpiryLengthSeconds'] else: - run_type = "Cert Test" + run_type = self.RUN_TEST_CERT failsafe_expiration_seconds = self.CERT_FAILSAFE_EXPIRATION_TIME_SECONDS # Timeout used in test steps to verify failsafe. Must be less than DUT MaxCumulativeFailsafeSeconds asserts.assert_less(failsafe_expiration_seconds, maxFailsafe, "failsafe_expiration_seconds should be less than MaxCumulativeFailsafeSeconds.") - logger.info(f'Step #2: {run_type} - Value of FailsafeExpiryLengthSeconds: {failsafe_expiration_seconds}') + logger.info(f'Step #2a: {run_type} - Value of FailsafeExpiryLengthSeconds: {failsafe_expiration_seconds}') # TH1 steps #3 through #5 using the function run_steps_3_to_5 self.step('3-5') @@ -352,9 +359,9 @@ async def test_TC_CGEN_2_2(self): # and speeding up test execution by setting the expiration time to 1 second. # Running identifier - run_type = "CI Test" + run_type = self.RUN_TEST_CI logger.info( - f'Step 7: {run_type} - Bypassing failsafe expiration to avoid unnecessary delays in CI environment.') + f'Step #7: {run_type} - Bypassing failsafe expiration to avoid unnecessary delays in CI environment.') # Force the failsafe timer to expire immediately for TH1, avoiding unnecessary delays in CI environments resp = await self.set_failsafe_timer( @@ -367,7 +374,7 @@ async def test_TC_CGEN_2_2(self): ) else: # Running identifier - run_type = "Cert Test" + run_type = self.RUN_TEST_CERT # # FAILSAFE_EXPIRATION_SECONDS constant, adjusted to avoid long waits logger.info( f'Step #7: {run_type} - Waiting for Failsafe timer to expire for FAILSAFE_EXPIRATION_SECONDS constant: {failsafe_expiration_seconds} seconds...') @@ -663,7 +670,7 @@ async def test_TC_CGEN_2_2(self): # Step 33 - In CI environments avoiding the original wait time defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds # and speeding up test execution by setting the expiration time to 2 seconds. - run_type = "CI Test" + run_type = self.RUN_TEST_CI logger.info( f'Step 33: {run_type} - Bypassing failsafe expiration to avoid unnecessary delays in CI environment.') @@ -674,7 +681,7 @@ async def test_TC_CGEN_2_2(self): # Wait PIXIT.CGEN.FailsafeExpiryLengthSeconds time with an additional 0.5-second buffer, not allowing the fully exire (max_fail_safe). await asyncio.sleep(failsafe_timeout_less_than_max + .5) else: - run_type = "Cert Test" + run_type = self.RUN_TEST_CERT logger.info( f'Step #33: {run_type} - Waiting for the failsafe timer ' @@ -731,11 +738,11 @@ async def test_TC_CGEN_2_2(self): self.step(38) if self.is_pics_sdk_ci_only: # In CI environment, bypass the wait for the failsafe expiration to avoid unnecessary delays. - run_type = "CI Test" + run_type = self.RUN_TEST_CI logger.info( f'Step #38: {run_type} - Bypassing due to failsafe expiration workaround to avoid unnecessary delays in CI environment.') else: - run_type = "Cert Test" + run_type = self.RUN_TEST_CERT t_start = time.time() # Get the current time and format it for logging @@ -765,11 +772,11 @@ async def test_TC_CGEN_2_2(self): maxFailsafe = failsafe_expiration_seconds if self.is_pics_sdk_ci_only: # In CI environment, bypass the wait for the failsafe expiration to avoid unnecessary delays. - run_type = "CI Test" + run_type = self.RUN_TEST_CI logger.info( f'Step #41: {run_type} - Bypassing due to failsafe expiration workaround to avoid unnecessary delays in CI environment.') else: - run_type = "Cert Test" + run_type = self.RUN_TEST_CERT # Make TH1 wait until the target_time is greater than or equal to half of the maxFailsafe time with an additional 0.5-second buffer. target_time = maxFailsafe/2 @@ -803,7 +810,7 @@ async def test_TC_CGEN_2_2(self): # avoiding the original wait time defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds, # and speeding up test execution by setting the expiration time to 1 second. - run_type = "CI Test" + run_type = self.RUN_TEST_CI logger.info( f'Step #43: {run_type} - Bypassing due to failsafe expiration workaround to avoid unnecessary delays in CI environment.') @@ -817,7 +824,7 @@ async def test_TC_CGEN_2_2(self): f'Test continues without the original wait.' ) else: - run_type = "Cert Test" + run_type = self.RUN_TEST_CERT # Calculate the target time (Tstart + maxFailsafe) with an additional 0.5-second buffer target_time = (t_start + maxFailsafe) + .5 From c9e8cb0dcb8ef6599a84f67eccce7262081f779b Mon Sep 17 00:00:00 2001 From: juandediosg Date: Tue, 4 Mar 2025 07:06:49 -0600 Subject: [PATCH 23/25] Fixed: Adjust failsafe timer for CI and Cert, with configurable expiration and default value of 20 seconds --- src/python_testing/TC_CGEN_2_2.py | 73 +++++++++++-------------------- 1 file changed, 26 insertions(+), 47 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index a9b1c8dbb021bd..b1caeeb44e73fc 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -56,7 +56,7 @@ class TC_CGEN_2_2(MatterBaseTest): cluster_opcreds = Clusters.OperationalCredentials cluster_cgen = Clusters.GeneralCommissioning - CERT_FAILSAFE_EXPIRATION_TIME_SECONDS = 1 + default_failsafe = 20 RUN_TEST_CI = "CI Test" RUN_TEST_CERT = "Cert Test" @@ -66,12 +66,13 @@ async def set_failsafe_timer(self, dev_ctrl, node_id, expiration_time_seconds): and trusted root certificates. This is necessary to avoid accumulation of invalid data, which could be caused by misconfigurations (e.g., incorrect network credentials). - In CI environments, this function helps bypass long waits by resetting the failsafe timer to 1 seconds, + In CI/Cert environments, this function helps bypass long waits by resetting the failsafe timer to failsafe_expiration_seconds, allowing the test to proceed without unnecessary delays. Args: dev_ctrl: The device controller to send the command. node_id: The node identifier to which the command is sent. + failsafe_expiration_seconds: Configure the failsafe timer using PIXIT.CGEN.FailsafeExpiryLengthSeconds. Returns: response: The response from the command sent to the device. @@ -80,7 +81,7 @@ async def set_failsafe_timer(self, dev_ctrl, node_id, expiration_time_seconds): buffer_latency = .5 # Resetting the failsafe timer to 1 seconds to clean up resources and avoid waiting in CI. - cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=expiration_time_seconds) + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(expiryLengthSeconds=expiration_time_seconds, breadcrumb=2) # Sending the command to the DUT (Device Under Test). resp = await self.send_single_cmd(dev_ctrl=dev_ctrl, node_id=node_id, cmd=cmd) @@ -317,13 +318,15 @@ async def test_TC_CGEN_2_2(self): logger.info(f'Step #2: The MaxCumulativeFailsafeSeconds (max_fail_safe): {maxFailsafe}') self.step('2a') - # Configure the failsafe timer. For CI, use PIXIT.CGEN.FailsafeExpiryLengthSeconds; for Cert, use CERT_FAILSAFE_EXPIRATION_TIME_SECONDS. + # Configure the failsafe timer. For both CI and Cert, using PIXIT.CGEN.FailsafeExpiryLengthSeconds. + # Default value for failsafe_expiration_seconds is 20 seconds if not specified. + + failsafe_expiration_seconds = self.user_params.get("PIXIT.CGEN.FailsafeExpiryLengthSeconds", self.default_failsafe) + if self.is_pics_sdk_ci_only: run_type = self.RUN_TEST_CI - failsafe_expiration_seconds = self.matter_test_config.global_test_params['PIXIT.CGEN.FailsafeExpiryLengthSeconds'] else: run_type = self.RUN_TEST_CERT - failsafe_expiration_seconds = self.CERT_FAILSAFE_EXPIRATION_TIME_SECONDS # Timeout used in test steps to verify failsafe. Must be less than DUT MaxCumulativeFailsafeSeconds asserts.assert_less(failsafe_expiration_seconds, maxFailsafe, @@ -353,34 +356,24 @@ async def test_TC_CGEN_2_2(self): logger.info(f'Step #6: The updated size of the num_trusted_roots_original list: {trusted_root_list_original_size_updated}') self.step(7) - if self.is_pics_sdk_ci_only: - # Step 7 - In CI environments, the 'set_failsafe_timer' function is used to immediately force the failsafe timer to expire, - # avoiding the original wait time defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds - # and speeding up test execution by setting the expiration time to 1 second. + # Step 7 - 'set_failsafe_timer' function is used to force the failsafe timer to expire, + # avoiding unnecessary delays in CI/Cert environments - # Running identifier - run_type = self.RUN_TEST_CI - logger.info( - f'Step #7: {run_type} - Bypassing failsafe expiration to avoid unnecessary delays in CI environment.') + # By default, the failsafe expiration time is set to 20 seconds, but this value can be adjusted through + # the configuration parameter 'PIXIT.CGEN.FailsafeExpiryLengthSeconds'. If not specified, the default is used. - # Force the failsafe timer to expire immediately for TH1, avoiding unnecessary delays in CI environments - resp = await self.set_failsafe_timer( - dev_ctrl=self.default_controller, - node_id=self.dut_node_id, - expiration_time_seconds=failsafe_expiration_seconds) - logger.info( - f'Step #7: {run_type} - Failsafe timer expiration bypassed for TH1 by setting expiration time to {failsafe_expiration_seconds} seconds. ' - f'Test continues without the original wait.' - ) - else: - # Running identifier - run_type = self.RUN_TEST_CERT - # # FAILSAFE_EXPIRATION_SECONDS constant, adjusted to avoid long waits - logger.info( - f'Step #7: {run_type} - Waiting for Failsafe timer to expire for FAILSAFE_EXPIRATION_SECONDS constant: {failsafe_expiration_seconds} seconds...') + logger.info( + f'Step #7: {run_type} - Forcing failsafe expiration by sending an ArmFailSafe command to the DUT with ExpiryLengthSeconds set to {failsafe_expiration_seconds} seconds.' + ) - # Wait for the full duration of the FAILSAFE_EXPIRATION_SECONDS constant time with an additional 0.5-second buffer - await asyncio.sleep(failsafe_expiration_seconds + .5) + resp = await self.set_failsafe_timer( + dev_ctrl=self.default_controller, + node_id=self.dut_node_id, + expiration_time_seconds=failsafe_expiration_seconds) + + logger.info( + f'Step #7: {run_type} - Failsafe timer expired after sending ArmFailSafe command. ExpiryLengthSeconds was set to {failsafe_expiration_seconds} seconds.' + ) # TH1 steps #8 through #9 using the function run_steps_8_to_9 self.step('8-9') @@ -669,8 +662,6 @@ async def test_TC_CGEN_2_2(self): if self.is_pics_sdk_ci_only: # Step 33 - In CI environments avoiding the original wait time defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds # and speeding up test execution by setting the expiration time to 2 seconds. - - run_type = self.RUN_TEST_CI logger.info( f'Step 33: {run_type} - Bypassing failsafe expiration to avoid unnecessary delays in CI environment.') @@ -681,8 +672,6 @@ async def test_TC_CGEN_2_2(self): # Wait PIXIT.CGEN.FailsafeExpiryLengthSeconds time with an additional 0.5-second buffer, not allowing the fully exire (max_fail_safe). await asyncio.sleep(failsafe_timeout_less_than_max + .5) else: - run_type = self.RUN_TEST_CERT - logger.info( f'Step #33: {run_type} - Waiting for the failsafe timer ' f'(FAILSAFE_EXPIRATION_SECONDS constant) to approach expiration, ' @@ -738,11 +727,9 @@ async def test_TC_CGEN_2_2(self): self.step(38) if self.is_pics_sdk_ci_only: # In CI environment, bypass the wait for the failsafe expiration to avoid unnecessary delays. - run_type = self.RUN_TEST_CI logger.info( f'Step #38: {run_type} - Bypassing due to failsafe expiration workaround to avoid unnecessary delays in CI environment.') else: - run_type = self.RUN_TEST_CERT t_start = time.time() # Get the current time and format it for logging @@ -772,12 +759,9 @@ async def test_TC_CGEN_2_2(self): maxFailsafe = failsafe_expiration_seconds if self.is_pics_sdk_ci_only: # In CI environment, bypass the wait for the failsafe expiration to avoid unnecessary delays. - run_type = self.RUN_TEST_CI logger.info( f'Step #41: {run_type} - Bypassing due to failsafe expiration workaround to avoid unnecessary delays in CI environment.') else: - run_type = self.RUN_TEST_CERT - # Make TH1 wait until the target_time is greater than or equal to half of the maxFailsafe time with an additional 0.5-second buffer. target_time = maxFailsafe/2 await asyncio.sleep(target_time + .5) @@ -806,11 +790,8 @@ async def test_TC_CGEN_2_2(self): self.step(43) if self.is_pics_sdk_ci_only: - # Step 43 - In CI environments, the 'set_failsafe_timer' function is used to immediately force the failsafe timer to expire, - # avoiding the original wait time defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds, - # and speeding up test execution by setting the expiration time to 1 second. - - run_type = self.RUN_TEST_CI + # Step 43 - In CI environments, the 'set_failsafe_timer' function is used to force the failsafe timer to expire, + # avoiding the original wait time defined in PIXIT.CGEN.FailsafeExpiryLengthSeconds, and speeding up test execution logger.info( f'Step #43: {run_type} - Bypassing due to failsafe expiration workaround to avoid unnecessary delays in CI environment.') @@ -824,8 +805,6 @@ async def test_TC_CGEN_2_2(self): f'Test continues without the original wait.' ) else: - run_type = self.RUN_TEST_CERT - # Calculate the target time (Tstart + maxFailsafe) with an additional 0.5-second buffer target_time = (t_start + maxFailsafe) + .5 From 6822e6079572e6c3586892c140fef3cde22eed2a Mon Sep 17 00:00:00 2001 From: juandediosg Date: Tue, 4 Mar 2025 07:16:00 -0600 Subject: [PATCH 24/25] Fixed: Step 7 updated. --- src/python_testing/TC_CGEN_2_2.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index b1caeeb44e73fc..a5ee85103528f0 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -242,7 +242,8 @@ def steps_TC_CGEN_2_2(self) -> list[TestStep]: TestStep(5, '''TH1 generates a new TrustedRootCertificate that is different from the previously commissioned TrustedRootCertificate for TH1. TH1 sends an AddTrustedRootCertificate command to the Node Operational Credentials cluster to install this new certificate.'''), TestStep(6, 'TH1 reads the TrustedRootCertificate attribute.'), - TestStep(7, 'TH1 waits for PIXIT.CGEN.FailsafeExpiryLengthSeconds to ensure the failsafe timer has expired.'), + TestStep(7, '''TH1 sends an ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to 1 and the Breadcrumb value as 2. + TH1 then waits 1.5 seconds to ensure the failsafe timer has expired'''), TestStep('8-9', 'TH1 execute function run_steps_8_to_9 to run steps #8 through #9.'), TestStep(8, 'TH1 reads the TrustedRootCertificates attribute from the Node Operational Credentials cluster.'), TestStep(9, 'TH1 reads the Breadcrumb attribute and verify that the breadcrumb attribute is 0.'), From 19896ac99765cdff2ffcda409cc779536fbcf8b1 Mon Sep 17 00:00:00 2001 From: juandediosg Date: Tue, 4 Mar 2025 11:25:43 -0600 Subject: [PATCH 25/25] Fixed: Step 2a updated in order to match with test plan. --- src/python_testing/TC_CGEN_2_2.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_2.py b/src/python_testing/TC_CGEN_2_2.py index a5ee85103528f0..73ae61986e9bed 100644 --- a/src/python_testing/TC_CGEN_2_2.py +++ b/src/python_testing/TC_CGEN_2_2.py @@ -232,9 +232,8 @@ def steps_TC_CGEN_2_2(self) -> list[TestStep]: and saves the number of list items as numTrustedRootsOriginal.'''), TestStep(2, '''TH1 reads the BasicCommissioningInfo attribute and saves the MaxCumulativeFailsafeSeconds as maxFailsafe.'''), - TestStep('2a', '''The failsafe timer is set to 1 second to avoid delays in test execution (which can be adjusted based on the environment for flexibility). - The variable is `PIXIT.CGEN.FailsafeExpiryLengthSeconds` for CI and - `CERT_FAILSAFE_EXPIRATION_TIME_SECONDS` for Cert.'''), + TestStep('2a', '''The failsafe timer is set based on PIXIT.CGEN.FailsafeExpiryLengthSeconds if provided; + otherwise, it defaults to 20 seconds, ensuring flexibility and preventing delays in test execution.'''), TestStep('3-5', 'TH1 execute function run_steps_3_to_5 to run steps #3 through #5.'), TestStep(3, '''TH1 sends ArmFailSafe command to the DUT with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds and the Breadcrumb value as 1.'''),