From 996f5f3138f05cd97b9be70d713a195d3be12959 Mon Sep 17 00:00:00 2001 From: Terence Hampson Date: Thu, 7 Jul 2022 19:35:34 -0400 Subject: [PATCH] Move AddNOC and UpdateNOC to util functions for chip-repl (#20430) * Move AddNOC and UpdateNOC to util functions that can be called using chip-repl * Restyle * Address PR comments * Address PR comments --- src/controller/python/BUILD.gn | 2 + src/controller/python/build-chip-wheel.py | 1 + src/controller/python/chip/ChipReplStartup.py | 1 + .../chip/utils/CommissioningBuildingBlocks.py | 131 ++++++++++++++++++ src/controller/python/chip/utils/__init__.py | 24 ++++ .../python/test/test_scripts/base.py | 93 +++---------- 6 files changed, 177 insertions(+), 75 deletions(-) create mode 100644 src/controller/python/chip/utils/CommissioningBuildingBlocks.py create mode 100644 src/controller/python/chip/utils/__init__.py diff --git a/src/controller/python/BUILD.gn b/src/controller/python/BUILD.gn index 63332c5fe59071..b6322f59236ab7 100644 --- a/src/controller/python/BUILD.gn +++ b/src/controller/python/BUILD.gn @@ -164,6 +164,8 @@ pw_python_action("python") { "chip/setup_payload/setup_payload.py", "chip/storage/__init__.py", "chip/tlv/__init__.py", + "chip/utils/CommissioningBuildingBlocks.py", + "chip/utils/__init__.py", ] if (chip_controller) { diff --git a/src/controller/python/build-chip-wheel.py b/src/controller/python/build-chip-wheel.py index 05b64ec28ffb9f..169cf655374208 100644 --- a/src/controller/python/build-chip-wheel.py +++ b/src/controller/python/build-chip-wheel.py @@ -158,6 +158,7 @@ def finalize_options(self): 'chip.ble.commissioning', 'chip.configuration', 'chip.clusters', + 'chip.utils', 'chip.discovery', 'chip.exceptions', 'chip.internal', diff --git a/src/controller/python/chip/ChipReplStartup.py b/src/controller/python/chip/ChipReplStartup.py index 2e0a09a26ca542..3fe949c174c30a 100644 --- a/src/controller/python/chip/ChipReplStartup.py +++ b/src/controller/python/chip/ChipReplStartup.py @@ -12,6 +12,7 @@ import argparse import builtins import chip.FabricAdmin +from chip.utils import CommissioningBuildingBlocks import atexit _fabricAdmins = None diff --git a/src/controller/python/chip/utils/CommissioningBuildingBlocks.py b/src/controller/python/chip/utils/CommissioningBuildingBlocks.py new file mode 100644 index 00000000000000..a85c50a62b9bd7 --- /dev/null +++ b/src/controller/python/chip/utils/CommissioningBuildingBlocks.py @@ -0,0 +1,131 @@ +# +# 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. +# + +import asyncio +import logging +import os + +import chip.clusters as Clusters +import chip.tlv +from chip.clusters import OperationalCredentials as opCreds +from chip.clusters import GeneralCommissioning as generalCommissioning + +_UINT16_MAX = 65535 + +logger = logging.getLogger() + + +async def _IsNodeInFabricList(devCtrl, nodeId): + resp = await devCtrl.ReadAttribute(nodeId, [(opCreds.Attributes.Fabrics)]) + listOfFabricsDescriptor = resp[0][opCreds][Clusters.OperationalCredentials.Attributes.Fabrics] + for fabricDescriptor in listOfFabricsDescriptor: + if fabricDescriptor.nodeId == nodeId: + return True + + return False + + +async def AddNOCForNewFabricFromExisting(commissionerDevCtrl, newFabricDevCtrl, existingNodeId, newNodeId): + """ Perform sequence to commission new frabric using existing commissioned fabric. + + Args: + commissionerDevCtrl (ChipDeviceController): Already commissioned device controller used + to commission a new fabric on `newFabricDevCtrl`. + newFabricDevCtrl (ChipDeviceController): New device controller which is used for the new + fabric we are establishing. + existingNodeId (int): Node ID of the server we are establishing a CASE session on the + existing fabric that we will used to perform AddNOC. + newNodeId (int): Node ID that we would like to server to used on the new fabric being + added. + + Return: + bool: True if successful, False otherwise. + + """ + resp = await commissionerDevCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(60), timedRequestTimeoutMs=1000) + if resp.errorCode is not generalCommissioning.Enums.CommissioningError.kOk: + return False + + csrForAddNOC = await commissionerDevCtrl.SendCommand(existingNodeId, 0, opCreds.Commands.CSRRequest(CSRNonce=os.urandom(32))) + + chainForAddNOC = newFabricDevCtrl.IssueNOCChain(csrForAddNOC, newNodeId) + if chainForAddNOC.rcacBytes is None or chainForAddNOC.icacBytes is None or chainForAddNOC.nocBytes is None or chainForAddNOC.ipkBytes is None: + # Expiring the failsafe timer in an attempt to clean up. + await commissionerDevCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0), timedRequestTimeoutMs=1000) + return False + + await commissionerDevCtrl.SendCommand(existingNodeId, 0, opCreds.Commands.AddTrustedRootCertificate(chainForAddNOC.rcacBytes)) + resp = await commissionerDevCtrl.SendCommand(existingNodeId, 0, opCreds.Commands.AddNOC(chainForAddNOC.nocBytes, chainForAddNOC.icacBytes, chainForAddNOC.ipkBytes, newFabricDevCtrl.GetNodeId(), 0xFFF1)) + if resp.statusCode is not opCreds.Enums.OperationalCertStatus.kSuccess: + # Expiring the failsafe timer in an attempt to clean up. + await commissionerDevCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0), timedRequestTimeoutMs=1000) + return False + + resp = await newFabricDevCtrl.SendCommand(newNodeId, 0, generalCommissioning.Commands.CommissioningComplete()) + if resp.errorCode is not generalCommissioning.Enums.CommissioningError.kOk: + # Expiring the failsafe timer in an attempt to clean up. + await commissionerDevCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0), timedRequestTimeoutMs=1000) + return False + + if not await _IsNodeInFabricList(newFabricDevCtrl, newNodeId): + return False + + return True + + +async def UpdateNOC(devCtrl, existingNodeId, newNodeId): + """ Perform sequence to generate a new NOC cert and issue updated NOC to server. + + Args: + commissionerDevCtrl (ChipDeviceController): Already commissioned device controller used + which we wish to update the NOC certificate for. + existingNodeId (int): Node ID of the server we are establishing a CASE session to + perform UpdateNOC. + newNodeId (int): Node ID that we would like to update the server to use. This can be + the same as `existingNodeId` if you wish to keep the node ID unchanged, but only + update the NOC certificate. + + Return: + bool: True if successful, False otherwise. + + """ + resp = await devCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(600), timedRequestTimeoutMs=1000) + if resp.errorCode is not generalCommissioning.Enums.CommissioningError.kOk: + return False + csrForUpdateNOC = await devCtrl.SendCommand( + existingNodeId, 0, opCreds.Commands.CSRRequest(CSRNonce=os.urandom(32), isForUpdateNOC=True)) + chainForUpdateNOC = devCtrl.IssueNOCChain(csrForUpdateNOC, newNodeId) + if chainForUpdateNOC.rcacBytes is None or chainForUpdateNOC.icacBytes is None or chainForUpdateNOC.nocBytes is None or chainForUpdateNOC.ipkBytes is None: + await devCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0), timedRequestTimeoutMs=1000) + return False + + resp = await devCtrl.SendCommand(existingNodeId, 0, opCreds.Commands.UpdateNOC(chainForUpdateNOC.nocBytes, chainForUpdateNOC.icacBytes)) + if resp.statusCode is not opCreds.Enums.OperationalCertStatus.kSuccess: + # Expiring the failsafe timer in an attempt to clean up. + await devCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0), timedRequestTimeoutMs=1000) + return False + + resp = await devCtrl.SendCommand(newNodeId, 0, generalCommissioning.Commands.CommissioningComplete()) + if resp.errorCode is not generalCommissioning.Enums.CommissioningError.kOk: + # Expiring the failsafe timer in an attempt to clean up. + await devCtrl.SendCommand(existingNodeId, 0, generalCommissioning.Commands.ArmFailSafe(0), timedRequestTimeoutMs=1000) + return False + + if not await _IsNodeInFabricList(devCtrl, newNodeId): + return False + + return True diff --git a/src/controller/python/chip/utils/__init__.py b/src/controller/python/chip/utils/__init__.py new file mode 100644 index 00000000000000..198b3b6d575898 --- /dev/null +++ b/src/controller/python/chip/utils/__init__.py @@ -0,0 +1,24 @@ +# +# 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. +# + +# +# @file +# Provides Python APIs for CHIP. +# + +"""Provides commissioning building blocks Python APIs for CHIP.""" +from . import CommissioningBuildingBlocks diff --git a/src/controller/python/test/test_scripts/base.py b/src/controller/python/test/test_scripts/base.py index 1bceb3d7e748e8..afb775541f530a 100644 --- a/src/controller/python/test/test_scripts/base.py +++ b/src/controller/python/test/test_scripts/base.py @@ -32,6 +32,7 @@ import ctypes import chip.clusters as Clusters import chip.clusters.Attribute as Attribute +from chip.utils import CommissioningBuildingBlocks from chip.ChipStack import * import chip.FabricAdmin import copy @@ -63,8 +64,6 @@ def FailIfNot(cond, message): _enabled_tests = [] _disabled_tests = [] -_UINT16_MAX = 65535 - def SetTestSet(enabled_tests, disabled_tests): global _enabled_tests, _disabled_tests @@ -369,101 +368,45 @@ async def TestAddUpdateRemoveFabric(self, nodeid: int): self.logger.info("Waiting for attribute read for CommissionedFabrics") startOfTestFabricCount = await self._GetCommissonedFabricCount(nodeid) - tempFabric = chip.FabricAdmin.FabricAdmin(vendorId=0xFFF1, fabricId=3, fabricIndex=3) + tempFabric = chip.FabricAdmin.FabricAdmin(vendorId=0xFFF1) 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") + self.logger.info("Starting AddNOC using same node ID") + if not await CommissioningBuildingBlocks.AddNOCForNewFabricFromExisting(self.devCtrl, tempDevCtrl, nodeid, nodeid): + self.logger.error("AddNOC failed") 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: + expectedFabricCountUntilRemoveFabric = startOfTestFabricCount + 1 + if expectedFabricCountUntilRemoveFabric != await self._GetCommissonedFabricCount(nodeid): 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") + if not await CommissioningBuildingBlocks.UpdateNOC(tempDevCtrl, nodeid, nodeid): + self.logger.error("UpdateNOC using same node ID failed") 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: + if expectedFabricCountUntilRemoveFabric != await self._GetCommissonedFabricCount(nodeid): 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") + if not await CommissioningBuildingBlocks.UpdateNOC(tempDevCtrl, nodeid, newNodeIdForUpdateNoc): + self.logger.error("UpdateNOC using different node ID failed") 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: + + if expectedFabricCountUntilRemoveFabric != await self._GetCommissonedFabricCount(nodeid): 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)) + currentFabricIndexResponse = await tempDevCtrl.ReadAttribute(newNodeIdForUpdateNoc, [(Clusters.OperationalCredentials.Attributes.CurrentFabricIndex)]) + updatedNOCFabricIndex = currentFabricIndexResponse[0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.CurrentFabricIndex] + removeFabricResponse = await tempDevCtrl.SendCommand(newNodeIdForUpdateNoc, 0, Clusters.OperationalCredentials.Commands.RemoveFabric(updatedNOCFabricIndex)) - endOfTestFabricCount = await self._GetCommissonedFabricCount(nodeid) - if endOfTestFabricCount != startOfTestFabricCount: + if startOfTestFabricCount != await self._GetCommissonedFabricCount(nodeid): self.logger.error("Expected fabric count to be the same at the end of test as when it started") return False