blob: a1be9a180be237f5b41c541435389ab6618cbc4e [file] [log] [blame]
#
# Copyright (c) 2021 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.
#
# Needed to use types in type hints before they are fully defined.
from __future__ import annotations
import ctypes
from dataclasses import dataclass, field
from typing import *
from ctypes import *
from rich.pretty import pprint
import ipdb
import json
import logging
import builtins
import base64
import chip.exceptions
from chip import ChipDeviceCtrl
import copy
class FabricAdmin:
''' Administers a specific fabric as identified by the tuple of RCAC subject public key and Fabric ID.
The Fabric ID can be passed into the constructor while the RCAC and ICAC are generated.
The Fabric ID *does not* have to be unique across multiple FabricAdmin instances as
it is scoped to the key pair used by the root CA and whose public key is in the RCAC.
Each admin is identified by an 'admin index' that is unique to the running
process. This is used to store credential information to disk so that
it can be easily loaded later if neccessary (see 'Persistence' below for more details)
When vending ChipDeviceController instances on a given fabric, each controller instance
is associated with a unique fabric index. In the underlying FabricTable, each FabricInfo
instance can be treated as unique identities that can collide on the same logical fabric.
>> C++ Binding Details
Each instance of the fabric admin is associated with a single instance
of the OperationalCredentialsAdapter. This adapter instance implements
the OperationalCredentialsDelegate and is meant to provide a Python
adapter to the functions in that delegate so that the fabric admin
can in turn, provide users the ability to generate their own NOCs for devices
on the network (not implemented yet). For now, it relies on the in-built
ExampleOperationalCredentialsIssuer to do that.
TODO: Add support for FabricAdmin to permit callers to hook up their own GenerateNOC
logic.
>> Persistence
Specifically, each instance persists its fabric ID and admin
index to storage. This is in addition to the persistence built into the ExampleOperationalCredentialsIssuer that persists details
about the RCAC/ICAC and associated keys as well. This facilitates re-construction of a fabric admin on subsequent
boot for a given fabric and ensuring it automatically picks up the right ICAC/RCAC details as well.
'''
activeAdminIndexList = set()
activeAdmins = set()
@classmethod
def _Handle(cls):
return chip.native.GetLibraryHandle()
def AllocateNextAdminIndex(self):
''' Allocate the next un-used admin index.
'''
nextAdminIndex = 1
while nextAdminIndex in FabricAdmin.activeAdminIndexList:
nextAdminIndex = nextAdminIndex + 1
return nextAdminIndex
def __init__(self, vendorId: int, adminIndex: int = None, fabricId: int = 1):
''' Creates a valid FabricAdmin object with valid RCAC/ICAC, and registers itself as an OperationalCredentialsDelegate
for other parts of the system (notably, DeviceController) to vend NOCs.
vendorId: Valid operational Vendor ID associated with this fabric.
adminIndex: Local index to be associated with this fabric. This is NOT the fabric index. Each controller on the fabric
is assigned a unique fabric index.
If omitted, one will be automatically assigned.
fabricId: Fabric ID to be associated with this fabric. This is scoped to the public key of the resultant
root generated by the underlying ExampleOperationalCredentialsIssuer.
'''
if (vendorId is None or vendorId == 0):
raise ValueError(
f"Invalid VendorID ({vendorId}) provided!")
self.vendorId = vendorId
self._fabricId = fabricId
if (adminIndex is None):
self._adminIndex = self.AllocateNextAdminIndex()
else:
if (adminIndex in FabricAdmin.activeAdminIndexList):
raise ValueError(
f"AdminIndex {adminIndex} is already being managed by an existing FabricAdmin object!")
self._adminIndex = adminIndex
FabricAdmin.activeAdminIndexList.add(self._adminIndex)
print(
f"New FabricAdmin: FabricId: 0x{self._fabricId:016X}, AdminIndex: {self._adminIndex}, VendorId = 0x{self.vendorId:04X}")
self._Handle().pychip_OpCreds_InitializeDelegate.restype = c_void_p
self.closure = builtins.chipStack.Call(
lambda: self._Handle().pychip_OpCreds_InitializeDelegate(
ctypes.py_object(self), ctypes.c_uint32(self._adminIndex))
)
if (self.closure is None):
raise ValueError("Encountered error initializing OpCreds adapter")
#
# Persist details to storage (read modify write).
#
try:
adminList = builtins.chipStack.GetStorageManager().GetReplKey('fabricAdmins')
except KeyError:
adminList = {str(self._adminIndex): {'fabricId': self._fabricId}}
builtins.chipStack.GetStorageManager().SetReplKey('fabricAdmins', adminList)
adminList[str(self._adminIndex)] = {'fabricId': self._fabricId, 'vendorId': self.vendorId}
builtins.chipStack.GetStorageManager().SetReplKey('fabricAdmins', adminList)
self._isActive = True
self.nextControllerId = 112233
FabricAdmin.activeAdmins.add(self)
def NewController(self, nodeId: int = None, paaTrustStorePath: str = "", useTestCommissioner: bool = False):
''' Vend a new controller on this fabric seeded with the right fabric details.
'''
if (not(self._isActive)):
raise RuntimeError(
f"FabricAdmin object was previously shutdown and is no longer valid!")
if (nodeId is None):
nodeId = self.nextControllerId
self.nextControllerId = self.nextControllerId + 1
print(
f"Allocating new controller with FabricId: 0x{self._fabricId:016X}, NodeId: 0x{nodeId:016X}")
controller = ChipDeviceCtrl.ChipDeviceController(
self.closure, self._fabricId, nodeId, self.vendorId, paaTrustStorePath, useTestCommissioner)
return controller
def ShutdownAll():
''' Shuts down all active fabrics, but without deleting them from storage.
'''
activeAdmins = copy.copy(FabricAdmin.activeAdmins)
for admin in activeAdmins:
admin.Shutdown(False)
FabricAdmin.activeAdmins.clear()
def Shutdown(self, deleteFromStorage: bool = True):
''' Shutdown this fabric and free up its resources. This is important since relying
solely on the destructor will not guarantee relishining of C++-side resources.
deleteFromStorage: Whether to delete this fabric's details from persistent storage.
'''
if (self._isActive):
builtins.chipStack.Call(
lambda: self._Handle().pychip_OpCreds_FreeDelegate(
ctypes.c_void_p(self.closure))
)
FabricAdmin.activeAdminIndexList.remove(self._adminIndex)
if (deleteFromStorage):
adminList = builtins.chipStack.GetStorageManager().GetReplKey('fabricAdmins')
del(adminList[str(self._adminIndex)])
if (len(adminList) == 0):
adminList = None
builtins.chipStack.GetStorageManager().SetReplKey('fabricAdmins', adminList)
FabricAdmin.activeAdmins.remove(self)
self._isActive = False
def __del__(self):
self.Shutdown(False)