diff --git a/src/controller/python/test/test_scripts/base.py b/src/controller/python/test/test_scripts/base.py index 19e3261822e3ee..020d7744d44724 100644 --- a/src/controller/python/test/test_scripts/base.py +++ b/src/controller/python/test/test_scripts/base.py @@ -63,6 +63,8 @@ def FailIfNot(cond, message): _enabled_tests = [] _disabled_tests = [] +_UINT16_MAX = 65535 + def SetTestSet(enabled_tests, disabled_tests): global _enabled_tests, _disabled_tests @@ -179,6 +181,10 @@ def __init__(self, nodeid: int, paaTrustStorePath: str, testCommissioner: bool = self.paaTrustStorePath = paaTrustStorePath logging.getLogger().setLevel(logging.DEBUG) + async def _GetCommissonedFabricCount(self, nodeid: int): + data = await self.devCtrl.ReadAttribute(nodeid, [(Clusters.OperationalCredentials.Attributes.CommissionedFabrics)]) + return data[0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.CommissionedFabrics] + def _WaitForOneDiscoveredDevice(self, timeoutSeconds: int = 2): print("Waiting for device responses...") strlen = 100 @@ -357,6 +363,114 @@ def TestFailsafe(self, nodeid: int): return True return False + async def TestAddUpdateRemoveFabric(self, nodeid: int): + logger.info("Testing AddNOC, UpdateNOC and RemoveFabric") + + self.logger.info("Waiting for attribute read for CommissionedFabrics") + startOfTestFabricCount = await self._GetCommissonedFabricCount(nodeid) + + tempFabric = chip.FabricAdmin.FabricAdmin(fabricId=3, fabricIndex=3) + tempDevCtrl = tempFabric.NewController(self.controllerNodeId, self.paaTrustStorePath) + + self.logger.info("Setting failsafe on CASE connection") + resp = await self.devCtrl.SendCommand(nodeid, 0, Clusters.GeneralCommissioning.Commands.ArmFailSafe(60), timedRequestTimeoutMs=1000) + if resp.errorCode is not Clusters.GeneralCommissioning.Enums.CommissioningError.kOk: + self.logger.error( + "Incorrect response received from arm failsafe - wanted OK, received {}".format(resp)) + return False + + csrForAddNOC = await self.devCtrl.SendCommand(nodeid, 0, Clusters.OperationalCredentials.Commands.CSRRequest(CSRNonce=b'1' * 32)) + + chainForAddNOC = tempDevCtrl.IssueNOCChain(csrForAddNOC, nodeid) + if chainForAddNOC.rcacBytes is None or chainForAddNOC.icacBytes is None or chainForAddNOC.nocBytes is None or chainForAddNOC.ipkBytes is None: + self.logger.error("IssueNOCChain failed to generate valid cert chain for AddNOC") + return False + + self.logger.info("Starting AddNOC portion of test") + # TODO error check on the commands below + await self.devCtrl.SendCommand(nodeid, 0, Clusters.OperationalCredentials.Commands.AddTrustedRootCertificate(chainForAddNOC.rcacBytes)) + await self.devCtrl.SendCommand(nodeid, 0, Clusters.OperationalCredentials.Commands.AddNOC(chainForAddNOC.nocBytes, chainForAddNOC.icacBytes, chainForAddNOC.ipkBytes, tempDevCtrl.GetNodeId(), 0xFFF1)) + await tempDevCtrl.SendCommand(nodeid, 0, Clusters.GeneralCommissioning.Commands.CommissioningComplete()) + + afterAddNocFabricCount = await self._GetCommissonedFabricCount(nodeid) + if startOfTestFabricCount == afterAddNocFabricCount: + self.logger.error("Expected commissioned fabric count to change after AddNOC") + return False + + resp = await tempDevCtrl.ReadAttribute(nodeid, [(Clusters.Basic.Attributes.ClusterRevision)]) + clusterRevision = resp[0][Clusters.Basic][Clusters.Basic.Attributes.ClusterRevision] + if not isinstance(clusterRevision, chip.tlv.uint) or clusterRevision < 1 or clusterRevision > _UINT16_MAX: + self.logger.error( + "Failed to read valid cluster revision on newly added fabric {}".format(resp)) + return False + + self.logger.info("Starting UpdateNOC using same node ID") + resp = await tempDevCtrl.SendCommand(nodeid, 0, Clusters.GeneralCommissioning.Commands.ArmFailSafe(600), timedRequestTimeoutMs=1000) + if resp.errorCode is not Clusters.GeneralCommissioning.Enums.CommissioningError.kOk: + self.logger.error( + "Incorrect response received from arm failsafe - wanted OK, received {}".format(resp)) + return False + csrForUpdateNOC = await tempDevCtrl.SendCommand( + nodeid, 0, Clusters.OperationalCredentials.Commands.CSRRequest(CSRNonce=b'1' * 32, isForUpdateNOC=True)) + chainForUpdateNOC = tempDevCtrl.IssueNOCChain(csrForUpdateNOC, nodeid) + if chainForUpdateNOC.rcacBytes is None or chainForUpdateNOC.icacBytes is None or chainForUpdateNOC.nocBytes is None or chainForUpdateNOC.ipkBytes is None: + self.logger.error("IssueNOCChain failed to generate valid cert chain for UpdateNOC") + return False + + await tempDevCtrl.SendCommand(nodeid, 0, Clusters.OperationalCredentials.Commands.UpdateNOC(chainForUpdateNOC.nocBytes, chainForUpdateNOC.icacBytes)) + await tempDevCtrl.SendCommand(nodeid, 0, Clusters.GeneralCommissioning.Commands.CommissioningComplete()) + afterUpdateNocFabricCount = await self._GetCommissonedFabricCount(nodeid) + if afterUpdateNocFabricCount != afterAddNocFabricCount: + self.logger.error("Expected commissioned fabric count to remain unchanged after UpdateNOC") + return False + + resp = await tempDevCtrl.ReadAttribute(nodeid, [(Clusters.Basic.Attributes.ClusterRevision)]) + clusterRevision = resp[0][Clusters.Basic][Clusters.Basic.Attributes.ClusterRevision] + if not isinstance(clusterRevision, chip.tlv.uint) or clusterRevision < 1 or clusterRevision > _UINT16_MAX: + self.logger.error( + "Failed to read valid cluster revision on after UpdateNOC {}".format(resp)) + return False + + self.logger.info("Starting UpdateNOC using different node ID") + resp = await tempDevCtrl.SendCommand(nodeid, 0, Clusters.GeneralCommissioning.Commands.ArmFailSafe(600), timedRequestTimeoutMs=1000) + if resp.errorCode is not Clusters.GeneralCommissioning.Enums.CommissioningError.kOk: + self.logger.error( + "Incorrect response received from arm failsafe - wanted OK, received {}".format(resp)) + return False + csrForUpdateNOC = await tempDevCtrl.SendCommand( + nodeid, 0, Clusters.OperationalCredentials.Commands.CSRRequest(CSRNonce=b'1' * 32, isForUpdateNOC=True)) + + newNodeIdForUpdateNoc = nodeid + 1 + chainForSecondUpdateNOC = tempDevCtrl.IssueNOCChain(csrForUpdateNOC, newNodeIdForUpdateNoc) + if chainForSecondUpdateNOC.rcacBytes is None or chainForSecondUpdateNOC.icacBytes is None or chainForSecondUpdateNOC.nocBytes is None or chainForSecondUpdateNOC.ipkBytes is None: + self.logger.error("IssueNOCChain failed to generate valid cert chain for UpdateNOC with new node ID") + return False + updateNocResponse = await tempDevCtrl.SendCommand(nodeid, 0, Clusters.OperationalCredentials.Commands.UpdateNOC(chainForSecondUpdateNOC.nocBytes, chainForSecondUpdateNOC.icacBytes)) + await tempDevCtrl.SendCommand(newNodeIdForUpdateNoc, 0, Clusters.GeneralCommissioning.Commands.CommissioningComplete()) + afterUpdateNocWithNewNodeIdFabricCount = await self._GetCommissonedFabricCount(nodeid) + if afterUpdateNocFabricCount != afterUpdateNocWithNewNodeIdFabricCount: + self.logger.error("Expected commissioned fabric count to remain unchanged after UpdateNOC with new node ID") + return False + + # TODO Read using old node ID and expect that it fails. + resp = await tempDevCtrl.ReadAttribute(newNodeIdForUpdateNoc, [(Clusters.Basic.Attributes.ClusterRevision)]) + clusterRevision = resp[0][Clusters.Basic][Clusters.Basic.Attributes.ClusterRevision] + if not isinstance(clusterRevision, chip.tlv.uint) or clusterRevision < 1 or clusterRevision > _UINT16_MAX: + self.logger.error( + "Failed to read valid cluster revision on after UpdateNOC with new node ID {}".format(resp)) + return False + + removeFabricResponse = await tempDevCtrl.SendCommand(newNodeIdForUpdateNoc, 0, Clusters.OperationalCredentials.Commands.RemoveFabric(updateNocResponse.fabricIndex)) + + endOfTestFabricCount = await self._GetCommissonedFabricCount(nodeid) + if endOfTestFabricCount != startOfTestFabricCount: + self.logger.error("Expected fabric count to be the same at the end of test as when it started") + return False + + tempDevCtrl.Shutdown() + tempFabric.Shutdown() + return True + async def TestCaseEviction(self, nodeid: int): self.logger.info("Testing CASE eviction") diff --git a/src/controller/python/test/test_scripts/mobile-device-test.py b/src/controller/python/test/test_scripts/mobile-device-test.py index 7ea8173d15d031..a2f470763738e5 100755 --- a/src/controller/python/test/test_scripts/mobile-device-test.py +++ b/src/controller/python/test/test_scripts/mobile-device-test.py @@ -81,6 +81,9 @@ def ethernet_commissioning(test: BaseTestHelper, discriminator: int, setup_pin: nodeid=1)) FailIfNot(ok, "Failed to commission multi-fabric") + FailIfNot(asyncio.run(test.TestAddUpdateRemoveFabric(nodeid=device_nodeid)), + "Failed AddUpdateRemoveFabric test") + logger.info("Testing CASE Eviction") FailIfNot(asyncio.run(test.TestCaseEviction(device_nodeid)), "Failed TestCaseEviction")