Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move AddNOC and UpdateNOC to util functions for chip-repl #20430

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/controller/python/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,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(fabricId=3, fabricIndex=3)
tempFabric = chip.FabricAdmin.FabricAdmin()
tehampson marked this conversation as resolved.
Show resolved Hide resolved
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