Skip to content

Commit

Permalink
Python CAT Value support for Controllers (project-chip#22019)
Browse files Browse the repository at this point in the history
* Python CAT Value support for Controllers

* Review feedback
  • Loading branch information
mrjerryjohns authored and tcarmelveilleux committed Aug 22, 2022
1 parent 49eeb9b commit 756cea0
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 16 deletions.
16 changes: 13 additions & 3 deletions src/controller/python/OpCredsBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -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;
Expand Down
29 changes: 20 additions & 9 deletions src/controller/python/chip/ChipDeviceCtrl.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand All @@ -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(
Expand All @@ -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.
Expand All @@ -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!")

Expand Down
9 changes: 5 additions & 4 deletions src/controller/python/chip/FabricAdmin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
31 changes: 31 additions & 0 deletions src/controller/python/test/test_scripts/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
'''
Expand Down
3 changes: 3 additions & 0 deletions src/controller/python/test/test_scripts/mobile-device-test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down

0 comments on commit 756cea0

Please sign in to comment.