From cea27e16cd46aa559800f8114df452cb1122df30 Mon Sep 17 00:00:00 2001 From: Jerry Johns Date: Thu, 18 Aug 2022 20:25:15 -0700 Subject: [PATCH] Python CAT Value support for Controllers (#22019) * Python CAT Value support for Controllers * Review feedback --- src/controller/python/OpCredsBinding.cpp | 16 ++++++++-- src/controller/python/chip/ChipDeviceCtrl.py | 29 +++++++++++------ src/controller/python/chip/FabricAdmin.py | 9 +++--- .../python/test/test_scripts/base.py | 31 +++++++++++++++++++ .../test/test_scripts/mobile-device-test.py | 3 ++ 5 files changed, 72 insertions(+), 16 deletions(-) diff --git a/src/controller/python/OpCredsBinding.cpp b/src/controller/python/OpCredsBinding.cpp index fa779e3d383fc0..e6ed27a6da1db3 100644 --- a/src/controller/python/OpCredsBinding.cpp +++ b/src/controller/python/OpCredsBinding.cpp @@ -321,7 +321,8 @@ void pychip_OnCommissioningStatusUpdate(chip::PeerId peerId, chip::Controller::C ChipError::StorageType pychip_OpCreds_AllocateController(OpCredsContext * context, chip::Controller::DeviceCommissioner ** outDevCtrl, FabricId fabricId, chip::NodeId nodeId, chip::VendorId adminVendorId, - const char * paaTrustStorePath, bool useTestCommissioner) + const char * paaTrustStorePath, bool useTestCommissioner, + CASEAuthTag * caseAuthTags, uint32_t caseAuthTagLen) { ChipLogDetail(Controller, "Creating New Device Controller"); @@ -357,8 +358,17 @@ ChipError::StorageType pychip_OpCreds_AllocateController(OpCredsContext * contex ReturnErrorCodeIf(!rcac.Alloc(Controller::kMaxCHIPDERCertLength), CHIP_ERROR_NO_MEMORY.AsInteger()); MutableByteSpan rcacSpan(rcac.Get(), Controller::kMaxCHIPDERCertLength); - err = context->mAdapter->GenerateNOCChain(nodeId, fabricId, chip::kUndefinedCATs, ephemeralKey.Pubkey(), rcacSpan, icacSpan, - nocSpan); + CATValues catValues; + + if ((caseAuthTagLen + 1) > kMaxSubjectCATAttributeCount) + { + ChipLogError(Controller, "# of CASE Tags exceeds kMaxSubjectCATAttributeCount"); + return CHIP_ERROR_INVALID_ARGUMENT.AsInteger(); + } + + memcpy(catValues.values.data(), caseAuthTags, caseAuthTagLen * sizeof(CASEAuthTag)); + + err = context->mAdapter->GenerateNOCChain(nodeId, fabricId, catValues, ephemeralKey.Pubkey(), rcacSpan, icacSpan, nocSpan); VerifyOrReturnError(err == CHIP_NO_ERROR, err.AsInteger()); Controller::SetupParams initParams; diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py index edac6bc8f516dc..8e11f27a953b16 100644 --- a/src/controller/python/chip/ChipDeviceCtrl.py +++ b/src/controller/python/chip/ChipDeviceCtrl.py @@ -157,7 +157,7 @@ class DiscoveryFilterType(enum.IntEnum): class ChipDeviceController(): activeList = set() - def __init__(self, opCredsContext: ctypes.c_void_p, fabricId: int, nodeId: int, adminVendorId: int, paaTrustStorePath: str = "", useTestCommissioner: bool = False, fabricAdmin: FabricAdmin = None, name: str = None): + def __init__(self, opCredsContext: ctypes.c_void_p, fabricId: int, nodeId: int, adminVendorId: int, catTags: typing.List[int] = [], paaTrustStorePath: str = "", useTestCommissioner: bool = False, fabricAdmin: FabricAdmin = None, name: str = None): self.state = DCState.NOT_INITIALIZED self.devCtrl = None self._ChipStack = builtins.chipStack @@ -169,9 +169,18 @@ def __init__(self, opCredsContext: ctypes.c_void_p, fabricId: int, nodeId: int, devCtrl = c_void_p(None) + c_catTags = (c_uint32 * len(catTags))() + + for i, item in enumerate(catTags): + c_catTags[i] = item + + self._dmLib.pychip_OpCreds_AllocateController.argtypes = [c_void_p, POINTER( + c_void_p), c_uint64, c_uint64, c_uint16, c_char_p, c_bool, POINTER(c_uint32), c_uint32] + self._dmLib.pychip_OpCreds_AllocateController.restype = c_uint32 + res = self._ChipStack.Call( - lambda: self._dmLib.pychip_OpCreds_AllocateController(ctypes.c_void_p( - opCredsContext), pointer(devCtrl), fabricId, nodeId, adminVendorId, ctypes.c_char_p(None if len(paaTrustStorePath) == 0 else str.encode(paaTrustStorePath)), useTestCommissioner) + lambda: self._dmLib.pychip_OpCreds_AllocateController(c_void_p( + opCredsContext), pointer(devCtrl), fabricId, nodeId, adminVendorId, c_char_p(None if len(paaTrustStorePath) == 0 else str.encode(paaTrustStorePath)), useTestCommissioner, c_catTags, len(catTags)) ) if res != 0: @@ -233,7 +242,7 @@ def HandlePASEEstablishmentComplete(err): self.devCtrl, self.cbHandleCommissioningCompleteFunct) self.state = DCState.IDLE - self.isActive = True + self._isActive = True # Validate FabricID/NodeID followed from NOC Chain self._fabricId = self.GetFabricIdInternal() @@ -249,12 +258,10 @@ def fabricAdmin(self) -> FabricAdmin: @property def nodeId(self) -> int: - self.CheckIsActive() return self._nodeId @property def fabricId(self) -> int: - self.CheckIsActive() return self._fabricId @property @@ -269,11 +276,15 @@ def name(self) -> str: def name(self, new_name: str): self._name = new_name + @property + def isActive(self) -> bool: + return self._isActive + def Shutdown(self): ''' Shuts down this controller and reclaims any used resources, including the bound C++ constructor instance in the SDK. ''' - if (self.isActive): + if (self._isActive): if self.devCtrl != None: self._ChipStack.Call( lambda: self._dmLib.pychip_DeviceController_DeleteDeviceController( @@ -282,7 +293,7 @@ def Shutdown(self): self.devCtrl = None ChipDeviceController.activeList.remove(self) - self.isActive = False + self._isActive = False def ShutdownAll(): ''' Shut down all active controllers and reclaim any used resources. @@ -304,7 +315,7 @@ def ShutdownAll(): ChipDeviceController.activeList.clear() def CheckIsActive(self): - if (not self.isActive): + if (not self._isActive): raise RuntimeError( "DeviceCtrl instance was already shutdown previously!") diff --git a/src/controller/python/chip/FabricAdmin.py b/src/controller/python/chip/FabricAdmin.py index 216da4ebad7646..3cfe03ffdb7e95 100644 --- a/src/controller/python/chip/FabricAdmin.py +++ b/src/controller/python/chip/FabricAdmin.py @@ -73,7 +73,7 @@ def __init__(self, certificateAuthority: CertificateAuthority, vendorId: int, fa self._isActive = True self._activeControllers = [] - def NewController(self, nodeId: int = None, paaTrustStorePath: str = "", useTestCommissioner: bool = False): + def NewController(self, nodeId: int = None, paaTrustStorePath: str = "", useTestCommissioner: bool = False, catTags: List[int] = []): ''' Create a new chip.ChipDeviceCtrl.ChipDeviceController instance on this fabric. When vending ChipDeviceController instances on a given fabric, each controller instance @@ -85,12 +85,13 @@ def NewController(self, nodeId: int = None, paaTrustStorePath: str = "", useTest paaTrustStorePath: Path to the PAA trust store. If one isn't provided, a suitable default is selected. useTestCommissioner: If a test commmisioner is to be created. + catTags: A list of 32-bit CAT tags that will added to the NOC generated for this controller. ''' if (not(self._isActive)): raise RuntimeError( f"FabricAdmin object was previously shutdown and is no longer valid!") - nodeIdList = [controller.nodeId for controller in self._activeControllers] + nodeIdList = [controller.nodeId for controller in self._activeControllers if controller.isActive] if (nodeId is None): if (len(nodeIdList) != 0): nodeId = max(nodeIdList) + 1 @@ -103,8 +104,8 @@ def NewController(self, nodeId: int = None, paaTrustStorePath: str = "", useTest self.logger().warning( f"Allocating new controller with CaIndex: {self._certificateAuthority.caIndex}, FabricId: 0x{self._fabricId:016X}, NodeId: 0x{nodeId:016X}") - controller = ChipDeviceCtrl.ChipDeviceController( - self._certificateAuthority.GetOpCredsContext(), self._fabricId, nodeId, self._vendorId, paaTrustStorePath, useTestCommissioner, fabricAdmin=self) + controller = ChipDeviceCtrl.ChipDeviceController(opCredsContext=self._certificateAuthority.GetOpCredsContext(), fabricId=self._fabricId, nodeId=nodeId, + adminVendorId=self._vendorId, paaTrustStorePath=paaTrustStorePath, useTestCommissioner=useTestCommissioner, fabricAdmin=self, catTags=catTags) self._activeControllers.append(controller) return controller diff --git a/src/controller/python/test/test_scripts/base.py b/src/controller/python/test/test_scripts/base.py index 3b9f3d9288a7ba..8488b2e80b6ce6 100644 --- a/src/controller/python/test/test_scripts/base.py +++ b/src/controller/python/test/test_scripts/base.py @@ -386,6 +386,37 @@ def TestFailsafe(self, nodeid: int): return True return False + async def TestControllerCATValues(self, nodeid: int): + ''' This tests controllers using CAT Values + ''' + + # Allocate a new controller instance with a CAT tag. + newController = self.fabricAdmin.NewController(nodeId=300, catTags=[0x00010001]) + + # Read out an attribute using the new controller. It has no privileges, so this should fail with an UnsupportedAccess error. + res = await newController.ReadAttribute(nodeid=nodeid, attributes=[(0, Clusters.AccessControl.Attributes.Acl)]) + if(res[0][Clusters.AccessControl][Clusters.AccessControl.Attributes.Acl].Reason.status != IM.Status.UnsupportedAccess): + self.logger.error(f"1: Received data instead of an error:{res}") + return False + + # Do a read-modify-write operation on the ACL list to add the CAT tag to the ACL list. + aclList = (await self.devCtrl.ReadAttribute(nodeid, [(0, Clusters.AccessControl.Attributes.Acl)]))[0][Clusters.AccessControl][Clusters.AccessControl.Attributes.Acl] + origAclList = copy.deepcopy(aclList) + aclList[0].subjects.append(0xFFFFFFFD00010001) + await self.devCtrl.WriteAttribute(nodeid, [(0, Clusters.AccessControl.Attributes.Acl(aclList))]) + + # Read out the attribute again - this time, it should succeed. + res = await newController.ReadAttribute(nodeid=nodeid, attributes=[(0, Clusters.AccessControl.Attributes.Acl)]) + if (type(res[0][Clusters.AccessControl][Clusters.AccessControl.Attributes.Acl][0]) != Clusters.AccessControl.Structs.AccessControlEntry): + self.logger.error(f"2: Received something other than data:{res}") + return False + + # Write back the old entry to reset ACL list back. + await self.devCtrl.WriteAttribute(nodeid, [(0, Clusters.AccessControl.Attributes.Acl(origAclList))]) + newController.Shutdown() + + return True + async def TestMultiControllerFabric(self, nodeid: int): ''' This tests having multiple controller instances on the same fabric. ''' 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 ffea217fa526c9..99f17aabe27363 100755 --- a/src/controller/python/test/test_scripts/mobile-device-test.py +++ b/src/controller/python/test/test_scripts/mobile-device-test.py @@ -77,6 +77,9 @@ def ethernet_commissioning(test: BaseTestHelper, discriminator: int, setup_pin: logger.info("Testing multi-controller setup on the same fabric") FailIfNot(asyncio.run(test.TestMultiControllerFabric(nodeid=device_nodeid)), "Failed the multi-controller test") + logger.info("Testing CATs used on controllers") + FailIfNot(asyncio.run(test.TestControllerCATValues(nodeid=device_nodeid)), "Failed the controller CAT test") + ok = asyncio.run(test.TestMultiFabric(ip=address, setuppin=20202021, nodeid=1))