Skip to content

Commit

Permalink
Multi Controller Fabrics (#21420) (#21513)
Browse files Browse the repository at this point in the history
This adds support for creating multiple DeviceController instances on
the same fabric to make it MUCH easier to create minima tests in Python.

Co-authored-by: yunhanw-google <[email protected]>

Co-authored-by: Jerry Johns <[email protected]>
Co-authored-by: yunhanw-google <[email protected]>
  • Loading branch information
3 people authored Aug 2, 2022
1 parent fc45bec commit 3a3fa93
Show file tree
Hide file tree
Showing 17 changed files with 663 additions and 116 deletions.
42 changes: 39 additions & 3 deletions src/controller/CHIPDeviceController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ CHIP_ERROR DeviceController::InitControllerNOCChain(const ControllerInitParams &
chip::Platform::ScopedMemoryBuffer<uint8_t> nocBuf;
Credentials::P256PublicKeySpan rootPublicKeySpan;
FabricId fabricId;
NodeId nodeId;
bool hasExternallyOwnedKeypair = false;
Crypto::P256Keypair * externalOperationalKeypair = nullptr;
VendorId newFabricVendorId = params.controllerVendorId;
Expand Down Expand Up @@ -195,16 +196,51 @@ CHIP_ERROR DeviceController::InitControllerNOCChain(const ControllerInitParams &
MutableByteSpan nocSpan = MutableByteSpan(nocBuf.Get(), chipCertAllocatedLen);

ReturnErrorOnFailure(ConvertX509CertToChipCert(params.controllerNOC, nocSpan));
ReturnErrorOnFailure(ExtractFabricIdFromCert(nocSpan, &fabricId));
ReturnErrorOnFailure(ExtractNodeIdFabricIdFromOpCert(nocSpan, &nodeId, &fabricId));

auto * fabricTable = params.systemState->Fabrics();
const FabricInfo * fabricInfo = nullptr;

//
// When multiple controllers are permitted on the same fabric, we need to find fabrics with
// nodeId as an extra discriminant since we can have multiple FabricInfo objects that all
// collide on the same fabric. Not doing so may result in a match with an existing FabricInfo
// instance that matches the fabric in the provided NOC but is associated with a different NodeId
// that is already in use by another active controller instance. That will effectively cause it
// to change its identity inadvertently, which is not acceptable.
//
// TODO: Figure out how to clean up unreclaimed FabricInfos restored from persistent
// storage that are not in use by active DeviceController instances. Also, figure out
// how to reclaim FabricInfo slots when a DeviceController instance is deleted.
//
if (params.permitMultiControllerFabrics)
{
fabricInfo = fabricTable->FindIdentity(rootPublicKey, fabricId, nodeId);
}
else
{
fabricInfo = fabricTable->FindFabric(rootPublicKey, fabricId);
}

auto * fabricTable = params.systemState->Fabrics();
auto * fabricInfo = fabricTable->FindFabric(rootPublicKey, fabricId);
bool fabricFoundInTable = (fabricInfo != nullptr);

FabricIndex fabricIndex = fabricFoundInTable ? fabricInfo->GetFabricIndex() : kUndefinedFabricIndex;

CHIP_ERROR err = CHIP_NO_ERROR;

//
// We permit colliding fabrics when multiple controllers are present on the same logical fabric
// since each controller is associated with a unique FabricInfo 'identity' object and consequently,
// a unique FabricIndex.
//
// This sets a flag that will be cleared automatically when the fabric is committed/reverted later
// in this function.
//
if (params.permitMultiControllerFabrics)
{
fabricTable->PermitCollidingFabrics();
}

// We have 4 cases to handle legacy usage of direct operational key injection
if (externalOperationalKeypair)
{
Expand Down
12 changes: 12 additions & 0 deletions src/controller/CHIPDeviceController.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,18 @@ struct ControllerInitParams
ByteSpan controllerICAC;
ByteSpan controllerRCAC;

/**
* Controls whether we permit multiple DeviceController instances to exist
* on the same logical fabric (identified by the tuple of the fabric's
* root public key + fabric id).
*
* Each controller instance will be associated with its own FabricIndex.
* This pivots the FabricTable to tracking identities instead of fabrics,
* represented by FabricInfo instances that can have colliding logical fabrics.
*
*/
bool permitMultiControllerFabrics = false;

//
// Controls enabling server cluster interactions on a controller. This in turn
// causes the following to get enabled:
Expand Down
1 change: 1 addition & 0 deletions src/controller/CHIPDeviceControllerFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ void DeviceControllerFactory::PopulateInitParams(ControllerInitParams & controll
controllerParams.controllerNOC = params.controllerNOC;
controllerParams.controllerICAC = params.controllerICAC;
controllerParams.controllerRCAC = params.controllerRCAC;
controllerParams.permitMultiControllerFabrics = params.permitMultiControllerFabrics;

controllerParams.systemState = mSystemState;
controllerParams.controllerVendorId = params.controllerVendorId;
Expand Down
12 changes: 12 additions & 0 deletions src/controller/CHIPDeviceControllerFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@ struct SetupParams
// The Device Pairing Delegated used to initialize a Commissioner
DevicePairingDelegate * pairingDelegate = nullptr;

/**
* Controls whether we permit multiple DeviceController instances to exist
* on the same logical fabric (identified by the tuple of the fabric's
* root public key + fabric id).
*
* Each controller instance will be associated with its own FabricIndex.
* This pivots the FabricTable to tracking identities instead of fabrics,
* represented by FabricInfo instances that can have colliding logical fabrics.
*
*/
bool permitMultiControllerFabrics = false;

//
// Controls enabling server cluster interactions on a controller. This in turn
// causes the following to get enabled:
Expand Down
28 changes: 27 additions & 1 deletion src/controller/python/ChipDeviceController-ScriptBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,28 @@ ChipError::StorageType pychip_DeviceController_SetWiFiCredentials(const char * s

ChipError::StorageType pychip_DeviceController_CloseSession(chip::Controller::DeviceCommissioner * devCtrl, chip::NodeId nodeid)
{
#if 0
//
// Since we permit multiple controllers per fabric and each is associated with a unique fabric index, closing a session
// requires us to do so across all controllers on the same logical fabric.
//
// TODO: Enable this and remove the call below to DisconnectDevice once #19259 is completed. This is because
// OperationalDeviceProxy instances that are currently active will remain un-affected by this call and still
// provide a valid SessionHandle in the OnDeviceConnected call later when we call DeviceController::GetConnectedDevice.
// However, it provides a SessionHandle that is incapable of actually vending exchanges since it is in a defunct state.
//
// For now, calling DisconnectDevice will at least just correctly de-activate a currently active OperationalDeviceProxy
// instance and ensure that subsequent attempts to acquire one will correctly re-establish CASE on the fabric associated
// with the provided devCtrl.
//
auto err = devCtrl->SessionMgr()->ForEachCollidingSession(ScopedNodeId(nodeid, devCtrl->GetFabricIndex()), [](auto *session) {
session->MarkAsDefunct();
});

ReturnErrorOnFailure(err.AsInteger());
#else
return devCtrl->DisconnectDevice(nodeid).AsInteger();
#endif
}

ChipError::StorageType pychip_DeviceController_EstablishPASESessionIP(chip::Controller::DeviceCommissioner * devCtrl,
Expand Down Expand Up @@ -650,7 +671,12 @@ ChipError::StorageType pychip_ExpireSessions(chip::Controller::DeviceCommissione
{
VerifyOrReturnError((devCtrl != nullptr) && (devCtrl->SessionMgr() != nullptr), CHIP_ERROR_INVALID_ARGUMENT.AsInteger());
(void) devCtrl->ReleaseOperationalDevice(nodeId);
devCtrl->SessionMgr()->ExpireAllSessions(ScopedNodeId(nodeId, devCtrl->GetFabricIndex()));

//
// Since we permit multiple controllers on the same fabric each associated with a different fabric index, expiring a session
// needs to correctly expire sessions on other controllers on matching fabrics as well.
//
devCtrl->SessionMgr()->ExpireAllSessionsOnLogicalFabric(ScopedNodeId(nodeId, devCtrl->GetFabricIndex()));
return CHIP_NO_ERROR.AsInteger();
}

Expand Down
5 changes: 3 additions & 2 deletions src/controller/python/OpCredsBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -324,8 +324,8 @@ void pychip_OnCommissioningStatusUpdate(chip::PeerId peerId, chip::Controller::C
}

ChipError::StorageType pychip_OpCreds_AllocateController(OpCredsContext * context,
chip::Controller::DeviceCommissioner ** outDevCtrl, uint8_t fabricIndex,
FabricId fabricId, chip::NodeId nodeId, chip::VendorId adminVendorId,
chip::Controller::DeviceCommissioner ** outDevCtrl, FabricId fabricId,
chip::NodeId nodeId, chip::VendorId adminVendorId,
const char * paaTrustStorePath, bool useTestCommissioner)
{
ChipLogDetail(Controller, "Creating New Device Controller");
Expand Down Expand Up @@ -374,6 +374,7 @@ ChipError::StorageType pychip_OpCreds_AllocateController(OpCredsContext * contex
initParams.controllerNOC = nocSpan;
initParams.enableServerInteractions = true;
initParams.controllerVendorId = adminVendorId;
initParams.permitMultiControllerFabrics = true;

if (useTestCommissioner)
{
Expand Down
9 changes: 7 additions & 2 deletions src/controller/python/chip/ChipDeviceCtrl.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class DCState(enum.IntEnum):
class ChipDeviceController():
activeList = set()

def __init__(self, opCredsContext: ctypes.c_void_p, fabricId: int, fabricIndex: int, nodeId: int, adminVendorId: int, paaTrustStorePath: str = "", useTestCommissioner: bool = False):
def __init__(self, opCredsContext: ctypes.c_void_p, fabricId: int, nodeId: int, adminVendorId: int, paaTrustStorePath: str = "", useTestCommissioner: bool = False):
self.state = DCState.NOT_INITIALIZED
self.devCtrl = None
self._ChipStack = builtins.chipStack
Expand All @@ -134,9 +134,11 @@ def __init__(self, opCredsContext: ctypes.c_void_p, fabricId: int, fabricIndex:

res = self._ChipStack.Call(
lambda: self._dmLib.pychip_OpCreds_AllocateController(ctypes.c_void_p(
opCredsContext), pointer(devCtrl), fabricIndex, fabricId, nodeId, adminVendorId, ctypes.c_char_p(None if len(paaTrustStorePath) == 0 else str.encode(paaTrustStorePath)), useTestCommissioner)
opCredsContext), pointer(devCtrl), fabricId, nodeId, adminVendorId, ctypes.c_char_p(None if len(paaTrustStorePath) == 0 else str.encode(paaTrustStorePath)), useTestCommissioner)
)

self.nodeId = nodeId

if res != 0:
raise self._ChipStack.ErrorToException(res)

Expand All @@ -145,6 +147,9 @@ def __init__(self, opCredsContext: ctypes.c_void_p, fabricId: int, fabricIndex:
self._Cluster = ChipClusters(builtins.chipStack)
self._Cluster.InitLib(self._dmLib)

def GetNodeId(self):
return self.nodeId

def HandleKeyExchangeComplete(err):
if err != 0:
print("Failed to establish secure session to device: {}".format(err))
Expand Down
4 changes: 2 additions & 2 deletions src/controller/python/chip/ChipReplStartup.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ def LoadFabricAdmins():

for k in adminList:
console.print(
f"[purple]Restoring FabricAdmin from storage to manage FabricId {adminList[k]['fabricId']}, FabricIndex {k}...")
f"[purple]Restoring FabricAdmin from storage to manage FabricId {adminList[k]['fabricId']}, AdminIndex {k}...")
_fabricAdmins.append(chip.FabricAdmin.FabricAdmin(vendorId=int(adminList[k]['vendorId']),
fabricId=adminList[k]['fabricId'], fabricIndex=int(k)))
fabricId=adminList[k]['fabricId'], adminIndex=int(k)))

console.print(
'\n[blue]Fabric Admins have been loaded and are available at [red]fabricAdmins')
Expand Down
Loading

0 comments on commit 3a3fa93

Please sign in to comment.