[python] Add ICD support to Python binding (#33533)
* [icd] Add ICD support to python
* [python] Add ICD support to Python binding
* Update
* update
* update
* Update
* Use ScopedNodeId in ICD commissioning callbacks
* update
* fix
diff --git a/examples/chip-tool/commands/pairing/PairingCommand.cpp b/examples/chip-tool/commands/pairing/PairingCommand.cpp
index 5f6c179..76c8ac8 100644
--- a/examples/chip-tool/commands/pairing/PairingCommand.cpp
+++ b/examples/chip-tool/commands/pairing/PairingCommand.cpp
@@ -436,7 +436,7 @@
info.icd.idleModeDuration, info.icd.activeModeDuration, info.icd.activeModeThreshold);
}
-void PairingCommand::OnICDRegistrationComplete(NodeId nodeId, uint32_t icdCounter)
+void PairingCommand::OnICDRegistrationComplete(ScopedNodeId nodeId, uint32_t icdCounter)
{
char icdSymmetricKeyHex[chip::Crypto::kAES_CCM128_Key_Length * 2 + 1];
@@ -444,7 +444,7 @@
sizeof(icdSymmetricKeyHex), chip::Encoding::HexFlags::kNullTerminate);
app::ICDClientInfo clientInfo;
- clientInfo.peer_node = ScopedNodeId(nodeId, CurrentCommissioner().GetFabricIndex());
+ clientInfo.peer_node = nodeId;
clientInfo.monitored_subject = mICDMonitoredSubject.Value();
clientInfo.start_icd_counter = icdCounter;
@@ -457,7 +457,7 @@
if (err != CHIP_NO_ERROR)
{
CHIPCommand::sICDClientStorage.RemoveKey(clientInfo);
- ChipLogError(chipTool, "Failed to persist symmetric key for " ChipLogFormatX64 ": %s", ChipLogValueX64(nodeId),
+ ChipLogError(chipTool, "Failed to persist symmetric key for " ChipLogFormatX64 ": %s", ChipLogValueX64(nodeId.GetNodeId()),
err.AsString());
SetCommandExitStatus(err);
return;
@@ -465,18 +465,18 @@
mDeviceIsICD = true;
- ChipLogProgress(chipTool, "Saved ICD Symmetric key for " ChipLogFormatX64, ChipLogValueX64(nodeId));
+ ChipLogProgress(chipTool, "Saved ICD Symmetric key for " ChipLogFormatX64, ChipLogValueX64(nodeId.GetNodeId()));
ChipLogProgress(chipTool,
"ICD Registration Complete for device " ChipLogFormatX64 " / Check-In NodeID: " ChipLogFormatX64
" / Monitored Subject: " ChipLogFormatX64 " / Symmetric Key: %s / ICDCounter %u",
- ChipLogValueX64(nodeId), ChipLogValueX64(mICDCheckInNodeId.Value()),
+ ChipLogValueX64(nodeId.GetNodeId()), ChipLogValueX64(mICDCheckInNodeId.Value()),
ChipLogValueX64(mICDMonitoredSubject.Value()), icdSymmetricKeyHex, icdCounter);
}
-void PairingCommand::OnICDStayActiveComplete(NodeId deviceId, uint32_t promisedActiveDuration)
+void PairingCommand::OnICDStayActiveComplete(ScopedNodeId deviceId, uint32_t promisedActiveDuration)
{
ChipLogProgress(chipTool, "ICD Stay Active Complete for device " ChipLogFormatX64 " / promisedActiveDuration: %u",
- ChipLogValueX64(deviceId), promisedActiveDuration);
+ ChipLogValueX64(deviceId.GetNodeId()), promisedActiveDuration);
}
void PairingCommand::OnDiscoveredDevice(const chip::Dnssd::CommissionNodeData & nodeData)
diff --git a/examples/chip-tool/commands/pairing/PairingCommand.h b/examples/chip-tool/commands/pairing/PairingCommand.h
index 99b0fd0..aaa8dc7 100644
--- a/examples/chip-tool/commands/pairing/PairingCommand.h
+++ b/examples/chip-tool/commands/pairing/PairingCommand.h
@@ -197,8 +197,8 @@
void OnPairingDeleted(CHIP_ERROR error) override;
void OnReadCommissioningInfo(const chip::Controller::ReadCommissioningInfo & info) override;
void OnCommissioningComplete(NodeId deviceId, CHIP_ERROR error) override;
- void OnICDRegistrationComplete(NodeId deviceId, uint32_t icdCounter) override;
- void OnICDStayActiveComplete(NodeId deviceId, uint32_t promisedActiveDuration) override;
+ void OnICDRegistrationComplete(chip::ScopedNodeId deviceId, uint32_t icdCounter) override;
+ void OnICDStayActiveComplete(chip::ScopedNodeId deviceId, uint32_t promisedActiveDuration) override;
/////////// DeviceDiscoveryDelegate Interface /////////
void OnDiscoveredDevice(const chip::Dnssd::CommissionNodeData & nodeData) override;
diff --git a/examples/fabric-admin/commands/pairing/PairingCommand.cpp b/examples/fabric-admin/commands/pairing/PairingCommand.cpp
index 379b564..92754c2 100644
--- a/examples/fabric-admin/commands/pairing/PairingCommand.cpp
+++ b/examples/fabric-admin/commands/pairing/PairingCommand.cpp
@@ -436,7 +436,7 @@
info.icd.idleModeDuration, info.icd.activeModeDuration, info.icd.activeModeThreshold);
}
-void PairingCommand::OnICDRegistrationComplete(NodeId nodeId, uint32_t icdCounter)
+void PairingCommand::OnICDRegistrationComplete(ScopedNodeId nodeId, uint32_t icdCounter)
{
char icdSymmetricKeyHex[chip::Crypto::kAES_CCM128_Key_Length * 2 + 1];
@@ -444,7 +444,7 @@
sizeof(icdSymmetricKeyHex), chip::Encoding::HexFlags::kNullTerminate);
app::ICDClientInfo clientInfo;
- clientInfo.peer_node = ScopedNodeId(nodeId, CurrentCommissioner().GetFabricIndex());
+ clientInfo.peer_node = nodeId;
clientInfo.monitored_subject = mICDMonitoredSubject.Value();
clientInfo.start_icd_counter = icdCounter;
@@ -457,26 +457,26 @@
if (err != CHIP_NO_ERROR)
{
CHIPCommand::sICDClientStorage.RemoveKey(clientInfo);
- ChipLogError(NotSpecified, "Failed to persist symmetric key for " ChipLogFormatX64 ": %s", ChipLogValueX64(nodeId),
- err.AsString());
+ ChipLogError(NotSpecified, "Failed to persist symmetric key for " ChipLogFormatX64 ": %s",
+ ChipLogValueX64(nodeId.GetNodeId()), err.AsString());
SetCommandExitStatus(err);
return;
}
mDeviceIsICD = true;
- ChipLogProgress(NotSpecified, "Saved ICD Symmetric key for " ChipLogFormatX64, ChipLogValueX64(nodeId));
+ ChipLogProgress(NotSpecified, "Saved ICD Symmetric key for " ChipLogFormatX64, ChipLogValueX64(nodeId.GetNodeId()));
ChipLogProgress(NotSpecified,
"ICD Registration Complete for device " ChipLogFormatX64 " / Check-In NodeID: " ChipLogFormatX64
" / Monitored Subject: " ChipLogFormatX64 " / Symmetric Key: %s / ICDCounter %u",
- ChipLogValueX64(nodeId), ChipLogValueX64(mICDCheckInNodeId.Value()),
+ ChipLogValueX64(nodeId.GetNodeId()), ChipLogValueX64(mICDCheckInNodeId.Value()),
ChipLogValueX64(mICDMonitoredSubject.Value()), icdSymmetricKeyHex, icdCounter);
}
-void PairingCommand::OnICDStayActiveComplete(NodeId deviceId, uint32_t promisedActiveDuration)
+void PairingCommand::OnICDStayActiveComplete(ScopedNodeId deviceId, uint32_t promisedActiveDuration)
{
ChipLogProgress(NotSpecified, "ICD Stay Active Complete for device " ChipLogFormatX64 " / promisedActiveDuration: %u",
- ChipLogValueX64(deviceId), promisedActiveDuration);
+ ChipLogValueX64(deviceId.GetNodeId()), promisedActiveDuration);
}
void PairingCommand::OnDiscoveredDevice(const chip::Dnssd::CommissionNodeData & nodeData)
diff --git a/examples/fabric-admin/commands/pairing/PairingCommand.h b/examples/fabric-admin/commands/pairing/PairingCommand.h
index 8de34a0..331d177 100644
--- a/examples/fabric-admin/commands/pairing/PairingCommand.h
+++ b/examples/fabric-admin/commands/pairing/PairingCommand.h
@@ -196,8 +196,8 @@
void OnPairingDeleted(CHIP_ERROR error) override;
void OnReadCommissioningInfo(const chip::Controller::ReadCommissioningInfo & info) override;
void OnCommissioningComplete(NodeId deviceId, CHIP_ERROR error) override;
- void OnICDRegistrationComplete(NodeId deviceId, uint32_t icdCounter) override;
- void OnICDStayActiveComplete(NodeId deviceId, uint32_t promisedActiveDuration) override;
+ void OnICDRegistrationComplete(chip::ScopedNodeId deviceId, uint32_t icdCounter) override;
+ void OnICDStayActiveComplete(chip::ScopedNodeId deviceId, uint32_t promisedActiveDuration) override;
/////////// DeviceDiscoveryDelegate Interface /////////
void OnDiscoveredDevice(const chip::Dnssd::CommissionNodeData & nodeData) override;
diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp
index 1381ed3..75c95ee 100644
--- a/src/controller/CHIPDeviceController.cpp
+++ b/src/controller/CHIPDeviceController.cpp
@@ -1326,8 +1326,8 @@
if (commissioner->mPairingDelegate != nullptr)
{
- commissioner->mPairingDelegate->OnICDRegistrationComplete(commissioner->mDeviceBeingCommissioned->GetDeviceId(),
- data.ICDCounter);
+ commissioner->mPairingDelegate->OnICDRegistrationComplete(
+ ScopedNodeId(commissioner->mDeviceBeingCommissioned->GetDeviceId(), commissioner->GetFabricIndex()), data.ICDCounter);
}
exit:
@@ -1346,8 +1346,10 @@
if (commissioner->mPairingDelegate != nullptr)
{
- commissioner->mPairingDelegate->OnICDStayActiveComplete(commissioner->mDeviceBeingCommissioned->GetDeviceId(),
- data.promisedActiveDuration);
+ commissioner->mPairingDelegate->OnICDStayActiveComplete(
+
+ ScopedNodeId(commissioner->mDeviceBeingCommissioned->GetDeviceId(), commissioner->GetFabricIndex()),
+ data.promisedActiveDuration);
}
exit:
diff --git a/src/controller/DevicePairingDelegate.h b/src/controller/DevicePairingDelegate.h
index 558a6c1..849df45 100644
--- a/src/controller/DevicePairingDelegate.h
+++ b/src/controller/DevicePairingDelegate.h
@@ -136,7 +136,7 @@
* @param[in] icdNodeId The node id of the ICD.
* @param[in] icdCounter The ICD Counter received from the device.
*/
- virtual void OnICDRegistrationComplete(NodeId icdNodeId, uint32_t icdCounter) {}
+ virtual void OnICDRegistrationComplete(ScopedNodeId icdNodeId, uint32_t icdCounter) {}
/**
* @brief
@@ -147,7 +147,7 @@
* @param[in] promisedActiveDurationMsec The actual duration that the ICD server can stay active
* from the time it receives the StayActiveRequest command.
*/
- virtual void OnICDStayActiveComplete(NodeId icdNodeId, uint32_t promisedActiveDurationMsec) {}
+ virtual void OnICDStayActiveComplete(ScopedNodeId icdNodeId, uint32_t promisedActiveDurationMsec) {}
};
} // namespace Controller
diff --git a/src/controller/java/AndroidDeviceControllerWrapper.cpp b/src/controller/java/AndroidDeviceControllerWrapper.cpp
index 526f683..87f5373 100644
--- a/src/controller/java/AndroidDeviceControllerWrapper.cpp
+++ b/src/controller/java/AndroidDeviceControllerWrapper.cpp
@@ -994,13 +994,13 @@
env->CallVoidMethod(mJavaObjectRef.ObjectRef(), onICDRegistrationInfoRequiredMethod);
}
-void AndroidDeviceControllerWrapper::OnICDRegistrationComplete(chip::NodeId icdNodeId, uint32_t icdCounter)
+void AndroidDeviceControllerWrapper::OnICDRegistrationComplete(chip::ScopedNodeId icdNodeId, uint32_t icdCounter)
{
chip::DeviceLayer::StackUnlock unlock;
CHIP_ERROR err = CHIP_NO_ERROR;
chip::app::ICDClientInfo clientInfo;
- clientInfo.peer_node = ScopedNodeId(icdNodeId, Controller()->GetFabricIndex());
+ clientInfo.peer_node = icdNodeId;
clientInfo.monitored_subject = mAutoCommissioner.GetCommissioningParameters().GetICDMonitoredSubject().Value();
clientInfo.start_icd_counter = icdCounter;
@@ -1014,13 +1014,13 @@
if (err == CHIP_NO_ERROR)
{
- ChipLogProgress(Controller, "Saved ICD Symmetric key for " ChipLogFormatX64, ChipLogValueX64(icdNodeId));
+ ChipLogProgress(Controller, "Saved ICD Symmetric key for " ChipLogFormatX64, ChipLogValueX64(icdNodeId.GetNodeId()));
}
else
{
getICDClientStorage()->RemoveKey(clientInfo);
- ChipLogError(Controller, "Failed to persist symmetric key for " ChipLogFormatX64 ": %s", ChipLogValueX64(icdNodeId),
- err.AsString());
+ ChipLogError(Controller, "Failed to persist symmetric key for " ChipLogFormatX64 ": %s",
+ ChipLogValueX64(icdNodeId.GetNodeId()), err.AsString());
}
mDeviceIsICD = true;
@@ -1055,7 +1055,7 @@
icdDeviceInfoObj = env->NewObject(
icdDeviceInfoClass, icdDeviceInfoStructCtor, jSymmetricKey, static_cast<jint>(mUserActiveModeTriggerHint.Raw()),
jUserActiveModeTriggerInstruction, static_cast<jlong>(mIdleModeDuration), static_cast<jlong>(mActiveModeDuration),
- static_cast<jint>(mActiveModeThreshold), static_cast<jlong>(icdNodeId), static_cast<jlong>(icdCounter),
+ static_cast<jint>(mActiveModeThreshold), static_cast<jlong>(icdNodeId.GetNodeId()), static_cast<jlong>(icdCounter),
static_cast<jlong>(mAutoCommissioner.GetCommissioningParameters().GetICDMonitoredSubject().Value()),
static_cast<jlong>(Controller()->GetFabricId()), static_cast<jint>(Controller()->GetFabricIndex()));
diff --git a/src/controller/java/AndroidDeviceControllerWrapper.h b/src/controller/java/AndroidDeviceControllerWrapper.h
index 1d26d31..02d5049 100644
--- a/src/controller/java/AndroidDeviceControllerWrapper.h
+++ b/src/controller/java/AndroidDeviceControllerWrapper.h
@@ -118,7 +118,7 @@
const chip::app::Clusters::NetworkCommissioning::Commands::ScanNetworksResponse::DecodableType & dataResponse) override;
void OnScanNetworksFailure(CHIP_ERROR error) override;
void OnICDRegistrationInfoRequired() override;
- void OnICDRegistrationComplete(chip::NodeId icdNodeId, uint32_t icdCounter) override;
+ void OnICDRegistrationComplete(chip::ScopedNodeId icdNodeId, uint32_t icdCounter) override;
// PersistentStorageDelegate implementation
CHIP_ERROR SyncSetKeyValue(const char * key, const void * value, uint16_t size) override;
diff --git a/src/controller/python/BUILD.gn b/src/controller/python/BUILD.gn
index 4676f89..9adda77 100644
--- a/src/controller/python/BUILD.gn
+++ b/src/controller/python/BUILD.gn
@@ -76,6 +76,8 @@
"chip/crypto/p256keypair.cpp",
"chip/crypto/p256keypair.h",
"chip/discovery/NodeResolution.cpp",
+ "chip/icd/PyChipCheckInDelegate.cpp",
+ "chip/icd/PyChipCheckInDelegate.h",
"chip/interaction_model/Delegate.cpp",
"chip/interaction_model/Delegate.h",
"chip/internal/ChipThreadWork.cpp",
@@ -121,6 +123,7 @@
public_deps = [
"${chip_root}/src/app",
+ "${chip_root}/src/app/icd/client:handler",
"${chip_root}/src/app/server",
"${chip_root}/src/credentials:default_attestation_verifier",
"${chip_root}/src/lib",
diff --git a/src/controller/python/ChipDeviceController-ScriptBinding.cpp b/src/controller/python/ChipDeviceController-ScriptBinding.cpp
index 728fd58..821b2a3 100644
--- a/src/controller/python/ChipDeviceController-ScriptBinding.cpp
+++ b/src/controller/python/ChipDeviceController-ScriptBinding.cpp
@@ -42,6 +42,9 @@
#include <app/DeviceProxy.h>
#include <app/InteractionModelEngine.h>
+#include <app/icd/client/CheckInHandler.h>
+#include <app/icd/client/DefaultCheckInDelegate.h>
+#include <app/icd/client/DefaultICDClientStorage.h>
#include <app/server/Dnssd.h>
#include <controller/AutoCommissioner.h>
#include <controller/CHIPDeviceController.h>
@@ -55,7 +58,9 @@
#include <controller/python/ChipDeviceController-ScriptDevicePairingDelegate.h>
#include <controller/python/ChipDeviceController-ScriptPairingDeviceDiscoveryDelegate.h>
#include <controller/python/ChipDeviceController-StorageDelegate.h>
+#include <controller/python/chip/icd/PyChipCheckInDelegate.h>
#include <controller/python/chip/interaction_model/Delegate.h>
+#include <controller/python/chip/native/ChipMainLoopWork.h>
#include <controller/python/chip/native/PyChipError.h>
#include <credentials/GroupDataProviderImpl.h>
@@ -101,20 +106,24 @@
app::Clusters::TimeSynchronization::Structs::DSTOffsetStruct::Type sDSTBuf;
app::Clusters::TimeSynchronization::Structs::TimeZoneStruct::Type sTimeZoneBuf;
chip::Platform::ScopedMemoryBuffer<char> sTimeZoneNameBuf;
-chip::Controller::CommissioningParameters sCommissioningParameters;
-
} // namespace
+chip::Controller::CommissioningParameters sCommissioningParameters;
+chip::app::DefaultICDClientStorage sICDClientStorage;
chip::Controller::ScriptPairingDeviceDiscoveryDelegate sPairingDeviceDiscoveryDelegate;
chip::Credentials::GroupDataProviderImpl sGroupDataProvider;
chip::Credentials::PersistentStorageOpCertStore sPersistentStorageOpCertStore;
chip::Crypto::RawKeySessionKeystore sSessionKeystore;
+chip::app::CheckInHandler sCheckInHandler;
+
// NOTE: Remote device ID is in sync with the echo server device id
// At some point, we may want to add an option to connect to a device without
// knowing its id, because the ID can be learned on the first response that is received.
+chip::Controller::PyChipCheckInDelegate sCheckInDelegate;
chip::NodeId kDefaultLocalDeviceId = chip::kTestControllerNodeId;
chip::NodeId kRemoteDeviceId = chip::kTestDeviceNodeId;
+uint8_t sICDSymmetricKey[chip::Crypto::kAES_CCM128_Key_Length];
extern "C" {
PyChipError pychip_DeviceController_StackInit(Controller::Python::StorageAdapter * storageAdapter, bool enableServerInteractions);
@@ -126,6 +135,7 @@
char * outAddress, uint64_t maxAddressLen, uint16_t * outPort);
PyChipError pychip_DeviceController_GetCompressedFabricId(chip::Controller::DeviceCommissioner * devCtrl, uint64_t * outFabricId);
PyChipError pychip_DeviceController_GetFabricId(chip::Controller::DeviceCommissioner * devCtrl, uint64_t * outFabricId);
+PyChipError pychip_DeviceController_GetFabricIndex(chip::Controller::DeviceCommissioner * devCtrl, uint8_t * outFabricIndex);
PyChipError pychip_DeviceController_GetNodeId(chip::Controller::DeviceCommissioner * devCtrl, uint64_t * outNodeId);
// Rendezvous
@@ -144,6 +154,8 @@
PyChipError pychip_DeviceController_SetDefaultNtp(const char * defaultNTP);
PyChipError pychip_DeviceController_SetTrustedTimeSource(chip::NodeId nodeId, chip::EndpointId endpoint);
PyChipError pychip_DeviceController_SetCheckMatchingFabric(bool check);
+struct IcdRegistrationParameters;
+PyChipError pychip_DeviceController_SetIcdRegistrationParameters(bool enabled, const IcdRegistrationParameters * params);
PyChipError pychip_DeviceController_ResetCommissioningParameters();
PyChipError pychip_DeviceController_CloseSession(chip::Controller::DeviceCommissioner * devCtrl, chip::NodeId nodeid);
PyChipError pychip_DeviceController_EstablishPASESessionIP(chip::Controller::DeviceCommissioner * devCtrl, const char * peerAddrStr,
@@ -235,6 +247,11 @@
chip::Controller::Python::SetGetKeyValueCb getCb,
chip::Controller::Python::SyncDeleteKeyValueCb deleteCb);
void pychip_Storage_ShutdownAdapter(chip::Controller::Python::StorageAdapter * storageAdapter);
+
+//
+// ICD
+//
+void pychip_CheckInDelegate_SetOnCheckInCompleteCallback(PyChipCheckInDelegate::OnCheckInCompleteCallback * callback);
}
void * pychip_Storage_InitializeStorageAdapter(chip::Controller::Python::PyObject * context,
@@ -260,6 +277,8 @@
factoryParams.fabricIndependentStorage = storageAdapter;
factoryParams.sessionKeystore = &sSessionKeystore;
+ sICDClientStorage.Init(storageAdapter, &sSessionKeystore);
+
sGroupDataProvider.SetStorageDelegate(storageAdapter);
sGroupDataProvider.SetSessionKeystore(factoryParams.sessionKeystore);
PyReturnErrorOnFailure(ToPyChipError(sGroupDataProvider.Init()));
@@ -290,6 +309,11 @@
//
DeviceControllerFactory::GetInstance().RetainSystemState();
+ auto engine = chip::app::InteractionModelEngine::GetInstance();
+ PyReturnErrorOnFailure(ToPyChipError(sCheckInDelegate.Init(&sICDClientStorage, engine)));
+ PyReturnErrorOnFailure(ToPyChipError(sCheckInHandler.Init(
+ DeviceControllerFactory::GetInstance().GetSystemState()->ExchangeMgr(), &sICDClientStorage, &sCheckInDelegate, engine)));
+
//
// Finally, start up the main Matter thread. Any further interactions with the stack
// will now need to happen on the Matter thread, OR protected with the stack lock.
@@ -342,6 +366,12 @@
return ToPyChipError(CHIP_NO_ERROR);
}
+PyChipError pychip_DeviceController_GetFabricIndex(chip::Controller::DeviceCommissioner * devCtrl, uint8_t * outFabricIndex)
+{
+ *outFabricIndex = devCtrl->GetFabricIndex();
+ return ToPyChipError(CHIP_NO_ERROR);
+}
+
PyChipError pychip_DeviceController_GetNodeId(chip::Controller::DeviceCommissioner * devCtrl, uint64_t * outNodeId)
{
*outNodeId = devCtrl->GetNodeId();
@@ -554,6 +584,55 @@
return ToPyChipError(CHIP_NO_ERROR);
}
+struct IcdRegistrationParameters
+{
+ uint8_t * symmetricKey;
+ size_t symmetricKeyLength;
+ uint64_t checkInNodeId;
+ uint64_t monitoredSubject;
+ uint32_t stayActiveMsec;
+};
+
+PyChipError pychip_DeviceController_SetIcdRegistrationParameters(bool enabled, const IcdRegistrationParameters * params)
+{
+ if (!enabled)
+ {
+ sCommissioningParameters.SetICDRegistrationStrategy(ICDRegistrationStrategy::kIgnore);
+ return ToPyChipError(CHIP_NO_ERROR);
+ }
+
+ if (params == nullptr)
+ {
+ return ToPyChipError(CHIP_ERROR_INVALID_ARGUMENT);
+ }
+
+ if (params->symmetricKey == nullptr || params->symmetricKeyLength != sizeof(sICDSymmetricKey))
+ {
+ return ToPyChipError(CHIP_ERROR_INVALID_ARGUMENT);
+ }
+
+ if (params->checkInNodeId == 0)
+ {
+ return ToPyChipError(CHIP_ERROR_INVALID_ARGUMENT);
+ }
+ if (params->monitoredSubject == 0)
+ {
+ return ToPyChipError(CHIP_ERROR_INVALID_ARGUMENT);
+ }
+
+ memcpy(sICDSymmetricKey, params->symmetricKey, sizeof(sICDSymmetricKey));
+ sCommissioningParameters.SetICDSymmetricKey(ByteSpan(sICDSymmetricKey));
+ if (params->stayActiveMsec != 0)
+ {
+ sCommissioningParameters.SetICDStayActiveDurationMsec(params->stayActiveMsec);
+ }
+ sCommissioningParameters.SetICDCheckInNodeId(params->checkInNodeId);
+ sCommissioningParameters.SetICDMonitoredSubject(params->monitoredSubject);
+ sCommissioningParameters.SetICDRegistrationStrategy(ICDRegistrationStrategy::kBeforeComplete);
+
+ return ToPyChipError(CHIP_NO_ERROR);
+}
+
PyChipError pychip_DeviceController_ResetCommissioningParameters()
{
sCommissioningParameters = CommissioningParameters();
@@ -871,3 +950,8 @@
PlatformMgr().ScheduleWork(callback, reinterpret_cast<intptr_t>(pythonContext));
return ToPyChipError(CHIP_NO_ERROR);
}
+
+void pychip_CheckInDelegate_SetOnCheckInCompleteCallback(PyChipCheckInDelegate::OnCheckInCompleteCallback * callback)
+{
+ chip::MainLoopWork::ExecuteInMainLoop([callback]() { sCheckInDelegate.SetOnCheckInCompleteCallback(callback); });
+}
diff --git a/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.cpp b/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.cpp
index c1df812..c979e0d 100644
--- a/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.cpp
+++ b/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.cpp
@@ -19,12 +19,17 @@
#include "ChipDeviceController-ScriptDevicePairingDelegate.h"
#include "lib/support/TypeTraits.h"
+#include <app/icd/client/DefaultICDClientStorage.h>
#include <controller/python/chip/native/PyChipError.h>
#include <setup_payload/ManualSetupPayloadGenerator.h>
#include <setup_payload/QRCodeSetupPayloadGenerator.h>
#include <string>
+extern chip::app::DefaultICDClientStorage sICDClientStorage;
+extern chip::Controller::CommissioningParameters sCommissioningParameters;
+extern uint8_t sICDSymmetricKey[chip::Crypto::kAES_CCM128_Key_Length];
+
namespace chip {
namespace Controller {
@@ -180,5 +185,40 @@
return &mOpenWindowCallback;
}
+void ScriptDevicePairingDelegate::OnICDRegistrationComplete(ScopedNodeId nodeId, uint32_t icdCounter)
+{
+ app::ICDClientInfo clientInfo;
+ clientInfo.peer_node = nodeId;
+ clientInfo.monitored_subject = sCommissioningParameters.GetICDMonitoredSubject().Value();
+ clientInfo.start_icd_counter = icdCounter;
+
+ CHIP_ERROR err = sICDClientStorage.SetKey(clientInfo, ByteSpan(sICDSymmetricKey));
+ if (err == CHIP_NO_ERROR)
+ {
+ err = sICDClientStorage.StoreEntry(clientInfo);
+ }
+
+ if (err != CHIP_NO_ERROR)
+ {
+ sICDClientStorage.RemoveKey(clientInfo);
+ ChipLogError(Controller, "Failed to persist symmetric key for " ChipLogFormatX64 ": %s",
+ ChipLogValueX64(nodeId.GetNodeId()), err.AsString());
+ return;
+ }
+
+ ChipLogProgress(Controller, "Saved ICD Symmetric key for " ChipLogFormatX64, ChipLogValueX64(nodeId.GetNodeId()));
+ ChipLogProgress(Controller,
+ "ICD Registration Complete for device " ChipLogFormatX64 " / Check-In NodeID: " ChipLogFormatX64
+ " / Monitored Subject: " ChipLogFormatX64 " / ICDCounter %u",
+ ChipLogValueX64(nodeId.GetNodeId()), ChipLogValueX64(sCommissioningParameters.GetICDCheckInNodeId().Value()),
+ ChipLogValueX64(clientInfo.monitored_subject), icdCounter);
+}
+
+void ScriptDevicePairingDelegate::OnICDStayActiveComplete(ScopedNodeId deviceId, uint32_t promisedActiveDuration)
+{
+ ChipLogProgress(Controller, "ICD Stay Active Complete for device " ChipLogFormatX64 " / promisedActiveDuration: %u",
+ ChipLogValueX64(deviceId.GetNodeId()), promisedActiveDuration);
+}
+
} // namespace Controller
} // namespace chip
diff --git a/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.h b/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.h
index 2740b6e..d6665f8 100644
--- a/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.h
+++ b/src/controller/python/ChipDeviceController-ScriptDevicePairingDelegate.h
@@ -27,6 +27,7 @@
#include <controller/CHIPDeviceController.h>
#include <controller/CommissioningWindowOpener.h>
+#include <controller/python/chip/icd/PyChipCheckInDelegate.h>
#include <controller/python/chip/native/PyChipError.h>
namespace chip {
@@ -68,11 +69,14 @@
void OnCommissioningFailure(PeerId peerId, CHIP_ERROR error, CommissioningStage stageFailed,
Optional<Credentials::AttestationVerificationResult> additionalErrorInfo) override;
void OnCommissioningStatusUpdate(PeerId peerId, CommissioningStage stageCompleted, CHIP_ERROR error) override;
+ void OnICDRegistrationComplete(ScopedNodeId deviceId, uint32_t icdCounter) override;
+ void OnICDStayActiveComplete(ScopedNodeId deviceId, uint32_t promisedActiveDuration) override;
void OnFabricCheck(NodeId matchingNodeId) override;
Callback::Callback<Controller::OnOpenCommissioningWindow> *
GetOpenWindowCallback(Controller::CommissioningWindowOpener * context);
void OnOpenCommissioningWindow(NodeId deviceId, CHIP_ERROR status, SetupPayload payload);
void SetExpectingPairingComplete(bool value) { expectingPairingComplete = value; }
+ void SetFabricIndex(FabricIndex fabricIndex) { mFabricIndex = fabricIndex; }
private:
DevicePairingDelegate_OnPairingCompleteFunct mOnPairingCompleteCallback = nullptr;
@@ -84,7 +88,9 @@
DevicePairingDelegate_OnFabricCheckFunct mOnFabricCheckCallback = nullptr;
Callback::Callback<Controller::OnOpenCommissioningWindow> mOpenWindowCallback;
Controller::CommissioningWindowOpener * mWindowOpener = nullptr;
- bool expectingPairingComplete = false;
+
+ bool expectingPairingComplete = false;
+ FabricIndex mFabricIndex = 0;
};
} // namespace Controller
diff --git a/src/controller/python/OpCredsBinding.cpp b/src/controller/python/OpCredsBinding.cpp
index 5fd4205..427ee9b 100644
--- a/src/controller/python/OpCredsBinding.cpp
+++ b/src/controller/python/OpCredsBinding.cpp
@@ -26,6 +26,7 @@
#include "controller/python/chip/crypto/p256keypair.h"
#include "controller/python/chip/interaction_model/Delegate.h"
+#include <app/icd/client/DefaultICDClientStorage.h>
#include <controller/CHIPDeviceController.h>
#include <controller/CHIPDeviceControllerFactory.h>
#include <controller/ExampleOperationalCredentialsIssuer.h>
@@ -104,6 +105,7 @@
extern chip::Credentials::GroupDataProviderImpl sGroupDataProvider;
extern chip::Controller::ScriptDevicePairingDelegate sPairingDelegate;
+extern chip::app::DefaultICDClientStorage sICDClientStorage;
class TestCommissioner : public chip::Controller::AutoCommissioner
{
@@ -569,6 +571,9 @@
chip::Credentials::SetSingleIpkEpochKey(&sGroupDataProvider, devCtrl->GetFabricIndex(), defaultIpk, compressedFabricIdSpan);
VerifyOrReturnError(err == CHIP_NO_ERROR, ToPyChipError(err));
+ sICDClientStorage.UpdateFabricList(devCtrl->GetFabricIndex());
+ pairingDelegate->SetFabricIndex(devCtrl->GetFabricIndex());
+
*outDevCtrl = devCtrl.release();
*outPairingDelegate = pairingDelegate.release();
diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py
index 4fb8fb2..2ba7c58 100644
--- a/src/controller/python/chip/ChipDeviceCtrl.py
+++ b/src/controller/python/chip/ChipDeviceCtrl.py
@@ -35,11 +35,12 @@
import enum
import json
import logging
+import secrets
import threading
import time
import typing
-from ctypes import (CDLL, CFUNCTYPE, POINTER, byref, c_bool, c_char, c_char_p, c_int, c_int32, c_size_t, c_uint8, c_uint16,
- c_uint32, c_uint64, c_void_p, create_string_buffer, pointer, py_object, resize, string_at)
+from ctypes import (CDLL, CFUNCTYPE, POINTER, Structure, byref, c_bool, c_char, c_char_p, c_int, c_int32, c_size_t, c_uint8,
+ c_uint16, c_uint32, c_uint64, c_void_p, create_string_buffer, pointer, py_object, resize, string_at)
from dataclasses import dataclass
import dacite
@@ -98,6 +99,21 @@
adminSubject: int
+@dataclass
+class ICDRegistrationParameters:
+ symmetricKey: typing.Optional[bytes]
+ checkInNodeId: typing.Optional[int]
+ monitoredSubject: typing.Optional[int]
+ stayActiveMs: typing.Optional[int]
+
+ class CStruct(Structure):
+ _fields_ = [('symmetricKey', c_char_p), ('symmetricKeyLength', c_size_t), ('checkInNodeId',
+ c_uint64), ('monitoredSubject', c_uint64), ('stayActiveMsec', c_uint32)]
+
+ def to_c(self):
+ return ICDRegistrationParameters.CStruct(self.symmetricKey, len(self.symmetricKey), self.checkInNodeId, self.monitoredSubject, self.stayActiveMs)
+
+
@_DeviceAvailableCallbackFunct
def _DeviceAvailableCallback(closure, device, err):
closure.deviceAvailable(device, err)
@@ -123,6 +139,77 @@
nocChain = NOCChain(nocBytes, icacBytes, rcacBytes, ipkBytes, adminSubject)
devCtrl.NOCChainCallback(nocChain)
+
+# Methods for ICD
+class ScopedNodeId(Structure):
+ _fields_ = [("nodeId", c_uint64), ("fabricIndex", c_uint8)]
+
+ def __hash__(self):
+ return self.nodeId << 8 | self.fabricIndex
+
+ def __str__(self):
+ return f"({self.fabricIndex}:{self.nodeId:16x})"
+
+ def __eq__(self, other):
+ return self.nodeId == other.nodeId and self.fabricIndex == other.fabricIndex
+
+
+_OnCheckInCompleteFunct = CFUNCTYPE(None, ScopedNodeId)
+
+_OnCheckInCompleteWaitListLock = threading.Lock()
+_OnCheckInCompleteWaitList = dict()
+
+
+@_OnCheckInCompleteFunct
+def _OnCheckInComplete(scopedNodeId: ScopedNodeId):
+ callbacks = []
+ with _OnCheckInCompleteWaitListLock:
+ callbacks = list(_OnCheckInCompleteWaitList.get(scopedNodeId, set()))
+
+ for callback in callbacks:
+ callback(scopedNodeId)
+
+
+def RegisterOnActiveCallback(scopedNodeId: ScopedNodeId, callback: typing.Callable[None, [ScopedNodeId]]):
+ ''' Registers a callback when the device with given (fabric index, node id) becomes active.
+
+ Does nothing if the callback is already registered.
+ '''
+ with _OnCheckInCompleteWaitListLock:
+ waitList = _OnCheckInCompleteWaitList.get(scopedNodeId, set())
+ waitList.add(callback)
+ _OnCheckInCompleteWaitList[scopedNodeId] = waitList
+
+
+def UnregisterOnActiveCallback(scopedNodeId: ScopedNodeId, callback: typing.Callable[None, [ScopedNodeId]]):
+ ''' Unregisters a callback when the device with given (fabric index, node id) becomes active.
+
+ Does nothing if the callback has not been registered.
+ '''
+ with _OnCheckInCompleteWaitListLock:
+ _OnCheckInCompleteWaitList.get(scopedNodeId, set()).remove(callback)
+
+
+async def WaitForCheckIn(scopedNodeId: ScopedNodeId, timeoutSeconds: float):
+ ''' Waits for a device becomes active.
+
+ Returns:
+ - A future, completes when the device becomes active.
+ '''
+ eventLoop = asyncio.get_running_loop()
+ future = eventLoop.create_future()
+
+ def OnCheckInCallback(nodeid):
+ eventLoop.call_soon_threadsafe(lambda: future.done() or future.set_result(None))
+
+ RegisterOnActiveCallback(scopedNodeId, OnCheckInCallback)
+
+ try:
+ async with asyncio.timeout(timeoutSeconds):
+ await future
+ finally:
+ UnregisterOnActiveCallback(scopedNodeId, OnCheckInCallback)
+
# This is a fix for WEAV-429. Jay Logue recommends revisiting this at a later
# date to allow for truly multiple instances so this is temporary.
@@ -269,6 +356,7 @@
else:
logging.warning("Failed to commission: {}".format(err))
+ self._dmLib.pychip_DeviceController_SetIcdRegistrationParameters(False, None)
self.state = DCState.IDLE
self._ChipStack.callbackRes = err
self._ChipStack.commissioningEventRes = err
@@ -347,6 +435,7 @@
self._isActive = True
# Validate FabricID/NodeID followed from NOC Chain
self._fabricId = self.GetFabricIdInternal()
+ self._fabricIndex = self.GetFabricIndexInternal()
self._nodeId = self.GetNodeIdInternal()
def _finish_init(self):
@@ -766,6 +855,19 @@
return fabricid.value
+ def GetFabricIndexInternal(self):
+ """Get the fabric index from the object. Only used to validate cached value from property."""
+ self.CheckIsActive()
+
+ fabricindex = c_uint8(0)
+
+ self._ChipStack.Call(
+ lambda: self._dmLib.pychip_DeviceController_GetFabricIndex(
+ self.devCtrl, pointer(fabricindex))
+ ).raise_on_error()
+
+ return fabricindex.value
+
def GetNodeIdInternal(self) -> int:
"""Get the node ID from the object. Only used to validate cached value from property."""
self.CheckIsActive()
@@ -841,6 +943,18 @@
return DeviceProxyWrapper(returnDevice, self._dmLib)
+ async def WaitForActive(self, nodeid, *, timeoutSeconds=30.0, stayActiveDurationMs=30000):
+ ''' Waits a LIT ICD device to become active. Will send a StayActive command to the device on active to allow human operations.
+
+ nodeId: Node ID of the LID ICD
+ stayActiveDurationMs: The duration in the StayActive command, in milliseconds
+
+ Returns:
+ - StayActiveResponse on success
+ '''
+ await WaitForCheckIn(ScopedNodeId(nodeid, self._fabricIndex), timeoutSeconds=timeoutSeconds)
+ return await self.SendCommand(nodeid, 0, Clusters.IcdManagement.Commands.StayActiveRequest(stayActiveDuration=stayActiveDurationMs))
+
async def GetConnectedDevice(self, nodeid, allowPASE: bool = True, timeoutMs: int = None):
''' Gets an OperationalDeviceProxy or CommissioneeDeviceProxy for the specified Node.
@@ -1536,6 +1650,11 @@
self._dmLib.pychip_DeviceController_SetCheckMatchingFabric.restype = PyChipError
self._dmLib.pychip_DeviceController_SetCheckMatchingFabric.argtypes = [c_bool]
+ self._dmLib.pychip_DeviceController_SetIcdRegistrationParameters.restype = PyChipError
+ self._dmLib.pychip_DeviceController_SetIcdRegistrationParameters.argtypes = [
+ c_bool, c_void_p
+ ]
+
self._dmLib.pychip_DeviceController_ResetCommissioningParameters.restype = PyChipError
self._dmLib.pychip_DeviceController_ResetCommissioningParameters.argtypes = []
@@ -1722,6 +1841,9 @@
self._dmLib.pychip_DeviceController_GetFabricId.argtypes = [c_void_p, POINTER(c_uint64)]
self._dmLib.pychip_DeviceController_GetFabricId.restype = PyChipError
+ self._dmLib.pychip_DeviceController_GetFabricIndex.argtypes = [c_void_p, POINTER(c_uint8)]
+ self._dmLib.pychip_DeviceController_GetFabricIndex.restype = PyChipError
+
self._dmLib.pychip_DeviceController_GetLogFilter = [None]
self._dmLib.pychip_DeviceController_GetLogFilter = c_uint8
@@ -1736,6 +1858,11 @@
self._dmLib.pychip_DeviceController_SetIpk.argtypes = [c_void_p, POINTER(c_char), c_size_t]
self._dmLib.pychip_DeviceController_SetIpk.restype = PyChipError
+ self._dmLib.pychip_CheckInDelegate_SetOnCheckInCompleteCallback.restype = None
+ self._dmLib.pychip_CheckInDelegate_SetOnCheckInCompleteCallback.argtypes = [_OnCheckInCompleteFunct]
+
+ self._dmLib.pychip_CheckInDelegate_SetOnCheckInCompleteCallback(_OnCheckInComplete)
+
class ChipDeviceController(ChipDeviceControllerBase):
''' The ChipDeviceCommissioner binding, named as ChipDeviceController
@@ -1884,6 +2011,38 @@
lambda: self._dmLib.pychip_DeviceController_SetCheckMatchingFabric(check)
).raise_on_error()
+ def GenerateICDRegistrationParameters(self):
+ ''' Generates ICD registration parameters for this controller. '''
+ return ICDRegistrationParameters(
+ secrets.token_bytes(16),
+ self._nodeId,
+ self._nodeId,
+ 30)
+
+ def EnableICDRegistration(self, parameters: ICDRegistrationParameters):
+ ''' Enables ICD registration for the following commissioning session.
+
+ Args:
+ parameters: A ICDRegistrationParameters for the parameters used for ICD registration, or None for default arguments.
+ '''
+ if parameters is None:
+ raise ValueError("ICD registration parameter required.")
+ if len(parameters.symmetricKey) != 16:
+ raise ValueError("symmetricKey should be 16 bytes")
+
+ self.CheckIsActive()
+ self._ChipStack.Call(
+ lambda: self._dmLib.pychip_DeviceController_SetIcdRegistrationParameters(
+ True, pointer(parameters.to_c()))
+ ).raise_on_error()
+
+ def DisableICDRegistration(self):
+ ''' Disables ICD registration. '''
+ self.CheckIsActive()
+ self._ChipStack.Call(
+ lambda: self._dmLib.pychip_DeviceController_SetIcdRegistrationParameters(False, None)
+ ).raise_on_error()
+
def GetFabricCheckResult(self) -> int:
''' Returns the fabric check result if SetCheckMatchingFabric was used.'''
return self.fabricCheckNodeId
diff --git a/src/controller/python/chip/ChipReplStartup.py b/src/controller/python/chip/ChipReplStartup.py
index b75c77e..13a93a1 100644
--- a/src/controller/python/chip/ChipReplStartup.py
+++ b/src/controller/python/chip/ChipReplStartup.py
@@ -96,6 +96,8 @@
"-t", "--trust-store", help="Path to the PAA trust store.", action="store", default="./credentials/development/paa-root-certs")
parser.add_argument(
"-b", "--ble-adapter", help="Set the Bluetooth adapter index.", type=int, default=None)
+ parser.add_argument(
+ "-s", "--server-interactions", help="Enable server interactions.", action="store_true")
args = parser.parse_args()
if not os.path.exists(args.trust_store):
@@ -139,7 +141,7 @@
ReplInit(args.debug)
- chipStack = ChipStack(persistentStoragePath=args.storagepath, enableServerInteractions=False)
+ chipStack = ChipStack(persistentStoragePath=args.storagepath, enableServerInteractions=args.server_interactions)
certificateAuthorityManager = chip.CertificateAuthority.CertificateAuthorityManager(chipStack, chipStack.GetStorageManager())
certificateAuthorityManager.LoadAuthoritiesFromStorage()
diff --git a/src/controller/python/chip/icd/PyChipCheckInDelegate.cpp b/src/controller/python/chip/icd/PyChipCheckInDelegate.cpp
new file mode 100644
index 0000000..03f6b7d
--- /dev/null
+++ b/src/controller/python/chip/icd/PyChipCheckInDelegate.cpp
@@ -0,0 +1,33 @@
+/*
+ *
+ * Copyright (c) 2024 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.
+ */
+
+#include "PyChipCheckInDelegate.h"
+
+using namespace ::chip;
+using namespace ::chip::app;
+using namespace ::chip::Controller;
+
+void PyChipCheckInDelegate::OnCheckInComplete(const ICDClientInfo & clientInfo)
+{
+ DefaultCheckInDelegate::OnCheckInComplete(clientInfo);
+
+ if (mCallback != nullptr)
+ {
+ mCallback(clientInfo.peer_node);
+ }
+}
diff --git a/src/controller/python/chip/icd/PyChipCheckInDelegate.h b/src/controller/python/chip/icd/PyChipCheckInDelegate.h
new file mode 100644
index 0000000..3e3a3d9
--- /dev/null
+++ b/src/controller/python/chip/icd/PyChipCheckInDelegate.h
@@ -0,0 +1,42 @@
+/*
+ *
+ * Copyright (c) 2024 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.
+ */
+
+#pragma once
+
+#include <app/icd/client/DefaultCheckInDelegate.h>
+
+namespace chip {
+namespace Controller {
+
+class PyChipCheckInDelegate : public chip::app::DefaultCheckInDelegate
+{
+public:
+ using OnCheckInCompleteCallback = void(chip::ScopedNodeId);
+
+ virtual ~PyChipCheckInDelegate() = default;
+
+ void OnCheckInComplete(const chip::app::ICDClientInfo & clientInfo) override;
+
+ void SetOnCheckInCompleteCallback(OnCheckInCompleteCallback * callback) { mCallback = callback; }
+
+private:
+ OnCheckInCompleteCallback * mCallback;
+};
+
+} // namespace Controller
+} // namespace chip
diff --git a/src/controller/python/chip/internal/types.py b/src/controller/python/chip/internal/types.py
index 1904569..34c1473 100644
--- a/src/controller/python/chip/internal/types.py
+++ b/src/controller/python/chip/internal/types.py
@@ -14,7 +14,7 @@
# limitations under the License.
#
-from ctypes import CFUNCTYPE, c_size_t, c_uint32, c_void_p
+from ctypes import CFUNCTYPE, Structure, c_size_t, c_uint32, c_uint64, c_void_p
# General callback of 'network credentials requested. No python-data
# is available as the underlying callback is used internally
@@ -26,3 +26,7 @@
# Notification that pairing has been coompleted
PairingComplete = CFUNCTYPE(None, c_uint32)
+
+
+class ScopedNodeId(Structure):
+ _fields_ = [("node_id", c_uint64), ("fabric_index", "c_uint8")]