Skip to content

Commit

Permalink
Move AddNOC and UpdateNOC to util functions for chip-repl (#20430)
Browse files Browse the repository at this point in the history
* Move AddNOC and UpdateNOC to util functions that can be called using chip-repl

* Restyle

* Address PR comments

* Address PR comments
  • Loading branch information
tehampson authored Jul 7, 2022
1 parent f1f4d29 commit 996f5f3
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 75 deletions.
2 changes: 2 additions & 0 deletions src/controller/python/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions src/controller/python/build-chip-wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ def finalize_options(self):
'chip.ble.commissioning',
'chip.configuration',
'chip.clusters',
'chip.utils',
'chip.discovery',
'chip.exceptions',
'chip.internal',
Expand Down
1 change: 1 addition & 0 deletions src/controller/python/chip/ChipReplStartup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import argparse
import builtins
import chip.FabricAdmin
from chip.utils import CommissioningBuildingBlocks
import atexit

_fabricAdmins = None
Expand Down
131 changes: 131 additions & 0 deletions src/controller/python/chip/utils/CommissioningBuildingBlocks.py
Original file line number Diff line number Diff line change
@@ -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
24 changes: 24 additions & 0 deletions src/controller/python/chip/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -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
93 changes: 18 additions & 75 deletions src/controller/python/test/test_scripts/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down

0 comments on commit 996f5f3

Please sign in to comment.