diff --git a/src/controller/python/ChipDeviceController-Discovery.cpp b/src/controller/python/ChipDeviceController-Discovery.cpp index 7c669603f4ff1d..74c3a097be39c7 100644 --- a/src/controller/python/ChipDeviceController-Discovery.cpp +++ b/src/controller/python/ChipDeviceController-Discovery.cpp @@ -23,12 +23,16 @@ * */ +#include + #include #include #include #include #include #include +#include +#include using namespace chip; @@ -186,4 +190,21 @@ bool pychip_DeviceController_GetIPForDiscoveredDevice(Controller::DeviceCommissi } return false; } + +PyChipError pychip_CreateManualCode(uint16_t longDiscriminator, uint32_t passcode, char* manualCodeBuffer, size_t bufsize){ + SetupPayload payload; + SetupDiscriminator discriminator; + discriminator.SetLongValue(longDiscriminator); + payload.discriminator = discriminator; + payload.setUpPINCode = passcode; + std::string setupManualCode; + + CHIP_ERROR err = ManualSetupPayloadGenerator(payload).payloadDecimalStringRepresentation(setupManualCode); + if (err == CHIP_NO_ERROR) { + MutableCharSpan span(manualCodeBuffer, bufsize); + CopyCharSpanToMutableCharSpan(CharSpan(setupManualCode.c_str(), setupManualCode.length()), span); + } + + return ToPyChipError(err); } +} \ No newline at end of file diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py index 24fe59486f78d5..938290d99e13dd 100644 --- a/src/controller/python/chip/ChipDeviceCtrl.py +++ b/src/controller/python/chip/ChipDeviceCtrl.py @@ -1711,6 +1711,16 @@ def InitGroupTestingData(self): self.devCtrl) ).raise_on_error() + def CreateManualCode(self, discriminator: int, passcode: int) -> str: + """ Creates a standard flow manual code from the given discriminator and passcode.""" + # 64 bytes is WAY more than required, but let's be safe + size = 64 + buf = create_string_buffer(size) + self._ChipStack.Call( + lambda: self._dmLib.pychip_CreateManualCode(discriminator, passcode, buf, size) + ).raise_on_error() + return buf.value.decode() + # ----- Private Members ----- def _InitLib(self): if self._dmLib is None: @@ -1938,6 +1948,9 @@ def _InitLib(self): self._dmLib.pychip_DeviceProxy_GetRemoteSessionParameters.restype = PyChipError self._dmLib.pychip_DeviceProxy_GetRemoteSessionParameters.argtypes = [c_void_p, c_char_p] + self._dmLib.pychip_CreateManualCode.restype = PyChipError + self._dmLib.pychip_CreateManualCode.argtypes = [c_uint16, c_uint32, c_char_p, c_size_t] + class ChipDeviceController(ChipDeviceControllerBase): ''' The ChipDeviceCommissioner binding, named as ChipDeviceController diff --git a/src/python_testing/TC_DeviceBasicComposition.py b/src/python_testing/TC_DeviceBasicComposition.py index 7af7b865542e42..e3120038cf4b5e 100644 --- a/src/python_testing/TC_DeviceBasicComposition.py +++ b/src/python_testing/TC_DeviceBasicComposition.py @@ -19,14 +19,58 @@ # for details about the block below. # # === BEGIN CI TEST ARGUMENTS === -# test-runner-runs: run1 +# test-runner-runs: run1 run2 run3 run4 # test-runner-run/run1/app: ${ALL_CLUSTERS_APP} # test-runner-run/run1/factoryreset: True # test-runner-run/run1/quiet: True # test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json # test-runner-run/run1/script-args: --storage-path admin_storage.json --manual-code 10054912339 --PICS src/app/tests/suites/certification/ci-pics-values --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# +# test-runner-run/run2/app: ${CHIP_LOCK_APP} +# test-runner-run/run2/factoryreset: True +# test-runner-run/run2/quiet: True +# test-runner-run/run2/app-args: --discriminator 1234 --KVS kvs1 +# test-runner-run/run2/script-args: --storage-path admin_storage.json --manual-code 10054912339 +# +# test-runner-run/run3/app: ${CHIP_LOCK_APP} +# test-runner-run/run3/factoryreset: True +# test-runner-run/run3/quiet: True +# test-runner-run/run3/app-args: --discriminator 1234 --KVS kvs1 +# test-runner-run/run3/script-args: --storage-path admin_storage.json --qr-code MT:-24J0Q1212-10648G00 +# +# test-runner-run/run4/app: ${CHIP_LOCK_APP} +# test-runner-run/run4/factoryreset: True +# test-runner-run/run4/quiet: True +# test-runner-run/run4/app-args: --discriminator 1234 --KVS kvs1 +# test-runner-run/run4/script-args: --storage-path admin_storage.json --discriminator 1234 --passcode 20202021 +# +# test-runner-run/run5/app: ${CHIP_LOCK_APP} +# test-runner-run/run5/factoryreset: True +# test-runner-run/run5/quiet: True +# test-runner-run/run5/app-args: --discriminator 1234 --KVS kvs1 +# test-runner-run/run5/script-args: --storage-path admin_storage.json --manual-code 10054912339 --commissioning-method on-network +# +# test-runner-run/run6/app: ${CHIP_LOCK_APP} +# test-runner-run/run6/factoryreset: True +# test-runner-run/run6/quiet: True +# test-runner-run/run6/app-args: --discriminator 1234 --KVS kvs1 +# test-runner-run/run6/script-args: --storage-path admin_storage.json --qr-code MT:-24J0Q1212-10648G00 --commissioning-method on-network +# +# test-runner-run/run7/app: ${CHIP_LOCK_APP} +# test-runner-run/run7/factoryreset: True +# test-runner-run/run7/quiet: True +# test-runner-run/run7/app-args: --discriminator 1234 --KVS kvs1 +# test-runner-run/run7/script-args: --storage-path admin_storage.json --discriminator 1234 --passcode 20202021 --commissioning-method on-network # === END CI TEST ARGUMENTS === +# Run 1: runs through all tests +# Run 2: tests PASE connection using manual code (12.1 only) +# Run 3: tests PASE connection using QR code (12.1 only) +# Run 4: tests PASE connection using discriminator and passcode (12.1 only) +# Run 5: Tests CASE connection using manual code (12.1 only) +# Run 6: Tests CASE connection using QR code (12.1 only) +# Run 7: Tests CASE connection using manual discriminator and passcode (12.1 only) + import logging from dataclasses import dataclass from typing import Any, Callable diff --git a/src/python_testing/basic_composition_support.py b/src/python_testing/basic_composition_support.py index 678c249d0abf5d..ee1213237d5053 100644 --- a/src/python_testing/basic_composition_support.py +++ b/src/python_testing/basic_composition_support.py @@ -15,7 +15,7 @@ # limitations under the License. # - +import asyncio import base64 import copy import json @@ -98,12 +98,15 @@ def ConvertValue(value) -> Any: class BasicCompositionTests: - async def connect_over_pase(self, dev_ctrl): - asserts.assert_true(self.matter_test_config.qr_code_content == [] or self.matter_test_config.manual_code == [], - "Cannot have both QR and manual code specified") - setupCode = self.matter_test_config.qr_code_content + self.matter_test_config.manual_code - asserts.assert_equal(len(setupCode), 1, "Require one of either --qr-code or --manual-code.") - await dev_ctrl.FindOrEstablishPASESession(setupCode[0], self.dut_node_id) + def get_code(self, dev_ctrl): + created_codes = [] + for idx, d in enumerate(self.matter_test_config.discriminators): + created_codes.append(dev_ctrl.CreateManualCode(d, self.matter_test_config.setup_passcodes[idx])) + + setup_codes = self.matter_test_config.qr_code_content + self.matter_test_config.manual_code + created_codes + asserts.assert_equal(len(setup_codes), 1, + "Require exactly one of either --qr-code, --manual-code or (--discriminator and --passcode).") + return setup_codes[0] def dump_wildcard(self, dump_device_composition_path: typing.Optional[str]): node_dump_dict = {endpoint_id: MatterTlvToJson(self.endpoints_tlv[endpoint_id]) for endpoint_id in self.endpoints_tlv} @@ -115,19 +118,34 @@ def dump_wildcard(self, dump_device_composition_path: typing.Optional[str]): with open(pathlib.Path(dump_device_composition_path).with_suffix(".txt"), "wt+") as outfile: pprint(self.endpoints, outfile, indent=1, width=200, compact=True) - async def setup_class_helper(self, default_to_pase: bool = True): + async def setup_class_helper(self, allow_pase: bool = True): dev_ctrl = self.default_controller self.problems = [] - do_test_over_pase = self.user_params.get("use_pase_only", default_to_pase) dump_device_composition_path: Optional[str] = self.user_params.get("dump_device_composition_path", None) - if do_test_over_pase: - await self.connect_over_pase(dev_ctrl) - node_id = self.dut_node_id - else: - # Using the already commissioned node - node_id = self.dut_node_id + node_id = self.dut_node_id + + task_list = [] + if allow_pase: + setup_code = self.get_code(dev_ctrl) + pase_future = dev_ctrl.EstablishPASESession(setup_code, self.dut_node_id) + task_list.append(asyncio.create_task(pase_future)) + + case_future = dev_ctrl.GetConnectedDevice(nodeid=node_id, allowPASE=False) + task_list.append(asyncio.create_task(case_future)) + + for task in task_list: + asyncio.ensure_future(task) + + done, pending = await asyncio.wait(task_list, return_when=asyncio.FIRST_COMPLETED) + + for task in pending: + try: + task.cancel() + await task + except asyncio.CancelledError: + pass wildcard_read = (await dev_ctrl.Read(node_id, [()])) diff --git a/src/python_testing/matter_testing_support.py b/src/python_testing/matter_testing_support.py index eb8a6ba20d63b9..c81d4d2e64b4de 100644 --- a/src/python_testing/matter_testing_support.py +++ b/src/python_testing/matter_testing_support.py @@ -1546,9 +1546,6 @@ def populate_commissioning_args(args: argparse.Namespace, config: MatterTestConf config.qr_code_content.extend(args.qr_code) config.manual_code.extend(args.manual_code) - if args.commissioning_method is None: - return True - if args.discriminators == [] and (args.qr_code == [] and args.manual_code == []): print("error: Missing --discriminator when no --qr-code/--manual-code present!") return False @@ -1588,6 +1585,9 @@ def populate_commissioning_args(args: argparse.Namespace, config: MatterTestConf print("error: Duplicate value in discriminator list") return False + if args.commissioning_method is None: + return True + if config.commissioning_method == "ble-wifi": if args.wifi_ssid is None: print("error: missing --wifi-ssid for --commissioning-method ble-wifi!")