From 6ef6ad13f998d2fd7b4ef83af51e0e1c352f8cea Mon Sep 17 00:00:00 2001 From: Terence Hampson Date: Wed, 28 Aug 2024 14:55:26 +0000 Subject: [PATCH 1/6] Add src/python_testing/TC_MCORE_FS_1_5.py --- src/python_testing/TC_MCORE_FS_1_5.py | 281 ++++++++++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100755 src/python_testing/TC_MCORE_FS_1_5.py diff --git a/src/python_testing/TC_MCORE_FS_1_5.py b/src/python_testing/TC_MCORE_FS_1_5.py new file mode 100755 index 00000000000000..cb438f4e791f1d --- /dev/null +++ b/src/python_testing/TC_MCORE_FS_1_5.py @@ -0,0 +1,281 @@ +# +# Copyright (c) 2024 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. +# + +# TODO: add to CI. See https://github.com/project-chip/connectedhomeip/issues/34676 +# for details about the block below. +# + +import hashlib +import logging +import os +import queue +import secrets +import signal +import struct +import subprocess +import time +import uuid +from dataclasses import dataclass + +import chip.clusters as Clusters +from chip import ChipDeviceCtrl +from ecdsa.curves import NIST256p +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main, type_matches +from mobly import asserts +from TC_SC_3_6 import AttributeChangeAccumulator + +# Length of `w0s` and `w1s` elements +WS_LENGTH = NIST256p.baselen + 8 + + +def _generate_verifier(passcode: int, salt: bytes, iterations: int) -> bytes: + ws = hashlib.pbkdf2_hmac('sha256', struct.pack('>> pairing onnetwork 111 {setup_params.passcode}") + + def steps_TC_MCORE_FS_1_4(self) -> list[TestStep]: + steps = [TestStep(1, "TH subscribes to PartsList attribute of the Descriptor cluster of DUT_FSA endpoint 0."), + TestStep(2, "Follow manufacturer provided instructions to have DUT_FSA commission TH_SERVER"), + TestStep(3, "TH waits up to 30 seconds for subscription report from the PartsList attribute of the Descriptor to contain new endpoint"), + TestStep(4, "TH uses DUT to open commissioning window to TH_SERVER"), + TestStep(5, "TH commissions TH_SERVER"), + TestStep(6, "TH subscribes to AdministratorCommissioning attrbutes on DUT_FSA for the newly added endpoint identified in step 3"), + TestStep(7, "TH opens commissioning window to TH_SERVER directly (not using DUT)"), + TestStep(8, "TH reads AdministratorCommissioning from TH_SERVER directly (not using DUT)"), + TestStep(9, "TH waits up to 10 seconds for subscription report from the AdministratorCommissioning attribute (from step 6) to reflect values from previous step")] + return steps + + @property + def default_timeout(self) -> int: + return self.user_params.get("report_waiting_timeout_delay_sec", 10)*2 + 60 + + @async_test_body + async def test_TC_MCORE_FS_1_4(self): + self.is_ci = self.check_pics('PICS_SDK_CI_ONLY') + + min_report_interval_sec = 0 + max_report_interval_sec = 30 + th_server_port = self.user_params.get("th_server_port", 5543) + self._th_server_app_path = self.user_params.get("th_server_app_path", None) + if not self._th_server_app_path: + asserts.fail('This test requires a TH_SERVER app. Specify app path with --string-arg th_server_app_path:') + if not os.path.exists(self._th_server_app_path): + asserts.fail(f'The path {self._th_server_app_path} does not exist') + + self.step(1) + # Subscribe to the PartsList + root_endpoint = 0 + parts_list_subscription_contents = [ + (root_endpoint, Clusters.Descriptor.Attributes.PartsList) + ] + parts_list_sub = await self.default_controller.ReadAttribute( + nodeid=self.dut_node_id, + attributes=parts_list_subscription_contents, + reportInterval=(min_report_interval_sec, max_report_interval_sec), + keepSubscriptions=False + ) + + parts_list_queue = queue.Queue() + parts_list_attribute_handler = AttributeChangeAccumulator( + name=self.default_controller.name, expected_attribute=Clusters.Descriptor.Attributes.PartsList, output=parts_list_queue) + parts_list_sub.SetAttributeUpdateCallback(parts_list_attribute_handler) + parts_list_cached_attributes = parts_list_sub.GetAttributes() + step_1_dut_parts_list = parts_list_cached_attributes[root_endpoint][Clusters.Descriptor][Clusters.Descriptor.Attributes.PartsList] + + asserts.assert_true(type_matches(step_1_dut_parts_list, list), "PartsList is expected to be a list") + + self.step(2) + setup_params = await self._create_th_server(th_server_port) + self._ask_for_vendor_commissioning_ux_operation(setup_params) + + self.step(3) + report_waiting_timeout_delay_sec = 30 + logging.info("Waiting for update to PartsList.") + start_time = time.time() + elapsed = 0 + time_remaining = report_waiting_timeout_delay_sec + + parts_list_endpoint_count_from_step_1 = len(step_1_dut_parts_list) + step_3_dut_parts_list = None + while time_remaining > 0: + try: + item = parts_list_queue.get(block=True, timeout=time_remaining) + endpoint, attribute, value = item['endpoint'], item['attribute'], item['value'] + + # Record arrival of an expected subscription change when seen + if endpoint == root_endpoint and attribute == Clusters.Descriptor.Attributes.PartsList and len(value) > parts_list_endpoint_count_from_step_1: + step_3_dut_parts_list = value + break + + except queue.Empty: + # No error, we update timeouts and keep going + pass + + elapsed = time.time() - start_time + time_remaining = report_waiting_timeout_delay_sec - elapsed + + asserts.assert_not_equal(step_3_dut_parts_list, None, "Timed out getting updated PartsList with new endpoint") + set_of_step_1_parts_list_endpoint = set(step_1_dut_parts_list) + set_of_step_3_parts_list_endpoint = set(step_3_dut_parts_list) + unique_endpoints_set = set_of_step_3_parts_list_endpoint - set_of_step_1_parts_list_endpoint + asserts.assert_equal(len(unique_endpoints_set), 1, "Expected only one new endpoint") + newly_added_endpoint = list(unique_endpoints_set)[0] + + self.step(4) + + discriminator = 3840 + passcode = 20202021 + salt = secrets.token_bytes(16) + iterations = 2000 + verifier = _generate_verifier(passcode, salt, iterations) + + # min commissioning timeout is 3*60 seconds + cmd = Clusters.AdministratorCommissioning.Commands.OpenCommissioningWindow(commissioningTimeout=3*60, + PAKEPasscodeVerifier=verifier, + discriminator=discriminator, + iterations=iterations, + salt=salt) + await self.send_single_cmd(cmd, dev_ctrl=self.default_controller, node_id=self.dut_node_id, endpoint=newly_added_endpoint, timedRequestTimeoutMs=5000) + + self.step(5) + self.th_server_local_nodeid = 1111 + await self.default_controller.CommissionOnNetwork(nodeId=self.th_server_local_nodeid, setupPinCode=passcode, filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR, filter=discriminator) + + self.step(6) + cadmin_subscription_contents = [ + (newly_added_endpoint, Clusters.AdministratorCommissioning) + ] + cadmin_sub = await self.default_controller.ReadAttribute( + nodeid=self.dut_node_id, + attributes=cadmin_subscription_contents, + reportInterval=(min_report_interval_sec, max_report_interval_sec), + keepSubscriptions=False + ) + + cadmin_queue = queue.Queue() + # This AttributeChangeAccumulator is really just to let us know when new subscription came in + cadmin_attribute_handler = AttributeChangeAccumulator( + name=self.default_controller.name, expected_attribute=Clusters.AdministratorCommissioning.Attributes.WindowStatus, output=cadmin_queue) + cadmin_sub.SetAttributeUpdateCallback(cadmin_attribute_handler) + time.sleep(1) + + self.step(7) + await self.default_controller.OpenCommissioningWindow(nodeid=self.th_server_local_nodeid, timeout=180, iteration=1000, discriminator=3840, option=1) + + self.step(8) + th_server_directly_read_result = await self.default_controller.ReadAttribute(self.th_server_local_nodeid, [(root_endpoint, Clusters.AdministratorCommissioning)]) + th_server_direct_cadmin = th_server_directly_read_result[root_endpoint][Clusters.AdministratorCommissioning] + + self.step(9) + report_waiting_timeout_delay_sec = 10 + logging.info("Waiting for update to AdministratorCommissioning attributes.") + start_time = time.time() + elapsed = 0 + time_remaining = report_waiting_timeout_delay_sec + + cadmin_sub_new_data = False + while time_remaining > 0: + try: + item = cadmin_queue.get(block=True, timeout=time_remaining) + endpoint, attribute, value = item['endpoint'], item['attribute'], item['value'] + + # Record arrival of an expected subscription change when seen + if endpoint == newly_added_endpoint and attribute == Clusters.AdministratorCommissioning.Attributes.WindowStatus: + cadmin_sub_new_data = True + break + + except queue.Empty: + # No error, we update timeouts and keep going + pass + + elapsed = time.time() - start_time + time_remaining = report_waiting_timeout_delay_sec - elapsed + + asserts.assert_true(cadmin_sub_new_data, "Timed out waiting for DUT to reflect AdministratorCommissioning attributes for bridged device") + + dut_read = await self.default_controller.ReadAttribute(self.dut_node_id, [(newly_added_endpoint, Clusters.AdministratorCommissioning)]) + dut_cadmin_for_th_server = dut_read[newly_added_endpoint][Clusters.AdministratorCommissioning] + cadmin_attr = Clusters.AdministratorCommissioning.Attributes + asserts.assert_equal(th_server_direct_cadmin[cadmin_attr.WindowStatus], + dut_cadmin_for_th_server[cadmin_attr.WindowStatus], "WindowStatus incorrectly reported by DUT") + asserts.assert_equal(th_server_direct_cadmin[cadmin_attr.AdminFabricIndex], + dut_cadmin_for_th_server[cadmin_attr.AdminFabricIndex], "AdminFabricIndex incorrectly reported by DUT") + asserts.assert_equal(th_server_direct_cadmin[cadmin_attr.AdminVendorId], + dut_cadmin_for_th_server[cadmin_attr.AdminVendorId], "AdminVendorId incorrectly reported by DUT") + + +if __name__ == "__main__": + default_matter_test_main() From c0dc0ac602bcad2efcbb70c3f29101298e0fc028 Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Wed, 28 Aug 2024 14:55:59 +0000 Subject: [PATCH 2/6] Restyled by autopep8 --- src/python_testing/TC_MCORE_FS_1_5.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/python_testing/TC_MCORE_FS_1_5.py b/src/python_testing/TC_MCORE_FS_1_5.py index cb438f4e791f1d..bccc443e8b6841 100755 --- a/src/python_testing/TC_MCORE_FS_1_5.py +++ b/src/python_testing/TC_MCORE_FS_1_5.py @@ -264,7 +264,8 @@ async def test_TC_MCORE_FS_1_4(self): elapsed = time.time() - start_time time_remaining = report_waiting_timeout_delay_sec - elapsed - asserts.assert_true(cadmin_sub_new_data, "Timed out waiting for DUT to reflect AdministratorCommissioning attributes for bridged device") + asserts.assert_true(cadmin_sub_new_data, + "Timed out waiting for DUT to reflect AdministratorCommissioning attributes for bridged device") dut_read = await self.default_controller.ReadAttribute(self.dut_node_id, [(newly_added_endpoint, Clusters.AdministratorCommissioning)]) dut_cadmin_for_th_server = dut_read[newly_added_endpoint][Clusters.AdministratorCommissioning] From f4c502a8333ca0303e8b49f5eac2744eb7580f5a Mon Sep 17 00:00:00 2001 From: Terence Hampson Date: Wed, 28 Aug 2024 16:20:37 +0000 Subject: [PATCH 3/6] Fix spelling issues --- src/python_testing/TC_MCORE_FS_1_5.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python_testing/TC_MCORE_FS_1_5.py b/src/python_testing/TC_MCORE_FS_1_5.py index bccc443e8b6841..a5f8870b91ae2f 100755 --- a/src/python_testing/TC_MCORE_FS_1_5.py +++ b/src/python_testing/TC_MCORE_FS_1_5.py @@ -112,7 +112,7 @@ def steps_TC_MCORE_FS_1_4(self) -> list[TestStep]: TestStep(3, "TH waits up to 30 seconds for subscription report from the PartsList attribute of the Descriptor to contain new endpoint"), TestStep(4, "TH uses DUT to open commissioning window to TH_SERVER"), TestStep(5, "TH commissions TH_SERVER"), - TestStep(6, "TH subscribes to AdministratorCommissioning attrbutes on DUT_FSA for the newly added endpoint identified in step 3"), + TestStep(6, "TH subscribes to AdministratorCommissioning attributes on DUT_FSA for the newly added endpoint identified in step 3"), TestStep(7, "TH opens commissioning window to TH_SERVER directly (not using DUT)"), TestStep(8, "TH reads AdministratorCommissioning from TH_SERVER directly (not using DUT)"), TestStep(9, "TH waits up to 10 seconds for subscription report from the AdministratorCommissioning attribute (from step 6) to reflect values from previous step")] From eb0a054b78a4994dc5a641fc7c6860d1da729587 Mon Sep 17 00:00:00 2001 From: Terence Hampson Date: Wed, 28 Aug 2024 17:04:00 +0000 Subject: [PATCH 4/6] Add test to execute_python_tests.py --- src/python_testing/execute_python_tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/python_testing/execute_python_tests.py b/src/python_testing/execute_python_tests.py index 91db2b2614e56f..7e7d7d501e81b2 100644 --- a/src/python_testing/execute_python_tests.py +++ b/src/python_testing/execute_python_tests.py @@ -75,6 +75,7 @@ def main(search_directory, env_file): "TC_MCORE_FS_1_1.py", "TC_MCORE_FS_1_2.py", "TC_MCORE_FS_1_3.py", + "TC_MCORE_FS_1_5.py", "TC_OCC_3_1.py", "TC_OCC_3_2.py", "TC_BRBINFO_4_1.py", From 9c7563c5913f5af9d5f94c8e4e8f56f7b6f48b30 Mon Sep 17 00:00:00 2001 From: Terence Hampson Date: Thu, 29 Aug 2024 17:46:22 +0000 Subject: [PATCH 5/6] Updates after changes to testplan PR --- src/python_testing/TC_MCORE_FS_1_5.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/python_testing/TC_MCORE_FS_1_5.py b/src/python_testing/TC_MCORE_FS_1_5.py index a5f8870b91ae2f..dd47222e88f5af 100755 --- a/src/python_testing/TC_MCORE_FS_1_5.py +++ b/src/python_testing/TC_MCORE_FS_1_5.py @@ -59,7 +59,7 @@ class _SetupParamters: passcode: int -class TC_MCORE_FS_1_4(MatterBaseTest): +class TC_MCORE_FS_1_5(MatterBaseTest): @async_test_body async def setup_class(self): super().setup_class() @@ -106,7 +106,7 @@ def _ask_for_vendor_commissioning_ux_operation(self, setup_params: _SetupParamte f"If using FabricSync Admin test app, you may type:\n" f">>> pairing onnetwork 111 {setup_params.passcode}") - def steps_TC_MCORE_FS_1_4(self) -> list[TestStep]: + def steps_TC_MCORE_FS_1_5(self) -> list[TestStep]: steps = [TestStep(1, "TH subscribes to PartsList attribute of the Descriptor cluster of DUT_FSA endpoint 0."), TestStep(2, "Follow manufacturer provided instructions to have DUT_FSA commission TH_SERVER"), TestStep(3, "TH waits up to 30 seconds for subscription report from the PartsList attribute of the Descriptor to contain new endpoint"), @@ -114,8 +114,9 @@ def steps_TC_MCORE_FS_1_4(self) -> list[TestStep]: TestStep(5, "TH commissions TH_SERVER"), TestStep(6, "TH subscribes to AdministratorCommissioning attributes on DUT_FSA for the newly added endpoint identified in step 3"), TestStep(7, "TH opens commissioning window to TH_SERVER directly (not using DUT)"), - TestStep(8, "TH reads AdministratorCommissioning from TH_SERVER directly (not using DUT)"), - TestStep(9, "TH waits up to 10 seconds for subscription report from the AdministratorCommissioning attribute (from step 6) to reflect values from previous step")] + TestStep(8, "TH reads CurrentFabricIndex attributes on OperationalCredentials cluster from TH_SERVER directly (not using DUT_FSA)"), + TestStep(9, "TH reads AdministratorCommissioning from TH_SERVER directly (not using DUT)"), + TestStep(10, "TH waits up to 10 seconds for subscription report from the AdministratorCommissioning attribute (from step 6) to reflect values from previous step")] return steps @property @@ -123,7 +124,7 @@ def default_timeout(self) -> int: return self.user_params.get("report_waiting_timeout_delay_sec", 10)*2 + 60 @async_test_body - async def test_TC_MCORE_FS_1_4(self): + async def test_TC_MCORE_FS_1_5(self): self.is_ci = self.check_pics('PICS_SDK_CI_ONLY') min_report_interval_sec = 0 @@ -236,10 +237,18 @@ async def test_TC_MCORE_FS_1_4(self): await self.default_controller.OpenCommissioningWindow(nodeid=self.th_server_local_nodeid, timeout=180, iteration=1000, discriminator=3840, option=1) self.step(8) + current_fabric_index = await self.read_single_attribute_check_success(node_id=self.th_server_local_nodeid, cluster=Clusters.OperationalCredentials, attribute=Clusters.OperationalCredentials.Attributes.CurrentFabricIndex) + + self.step(9) th_server_directly_read_result = await self.default_controller.ReadAttribute(self.th_server_local_nodeid, [(root_endpoint, Clusters.AdministratorCommissioning)]) th_server_direct_cadmin = th_server_directly_read_result[root_endpoint][Clusters.AdministratorCommissioning] + cadmin_attr = Clusters.AdministratorCommissioning.Attributes + asserts.assert_equal(th_server_direct_cadmin[cadmin_attr.WindowStatus], + Clusters.AdministratorCommissioning.Enums.CommissioningWindowStatusEnum.kEnhancedWindowOpen, "WindowStatus is expected to be EnhancedWindowOpen") + asserts.assert_equal(th_server_direct_cadmin[cadmin_attr.AdminFabricIndex], + current_fabric_index, "AdminFabricIndex is unexpected") - self.step(9) + self.step(10) report_waiting_timeout_delay_sec = 10 logging.info("Waiting for update to AdministratorCommissioning attributes.") start_time = time.time() @@ -269,7 +278,6 @@ async def test_TC_MCORE_FS_1_4(self): dut_read = await self.default_controller.ReadAttribute(self.dut_node_id, [(newly_added_endpoint, Clusters.AdministratorCommissioning)]) dut_cadmin_for_th_server = dut_read[newly_added_endpoint][Clusters.AdministratorCommissioning] - cadmin_attr = Clusters.AdministratorCommissioning.Attributes asserts.assert_equal(th_server_direct_cadmin[cadmin_attr.WindowStatus], dut_cadmin_for_th_server[cadmin_attr.WindowStatus], "WindowStatus incorrectly reported by DUT") asserts.assert_equal(th_server_direct_cadmin[cadmin_attr.AdminFabricIndex], From 7c85d2206567134a8a94394e08996254afc397ba Mon Sep 17 00:00:00 2001 From: Terence Hampson Date: Fri, 30 Aug 2024 13:41:04 +0000 Subject: [PATCH 6/6] Address PR comment --- src/python_testing/TC_MCORE_FS_1_5.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/python_testing/TC_MCORE_FS_1_5.py b/src/python_testing/TC_MCORE_FS_1_5.py index dd47222e88f5af..dbf20660e7560d 100755 --- a/src/python_testing/TC_MCORE_FS_1_5.py +++ b/src/python_testing/TC_MCORE_FS_1_5.py @@ -16,8 +16,6 @@ # # TODO: add to CI. See https://github.com/project-chip/connectedhomeip/issues/34676 -# for details about the block below. -# import hashlib import logging