[Fabric-Sync] Support icd-registration during device sync (#36569)
diff --git a/examples/fabric-sync/admin/BUILD.gn b/examples/fabric-sync/admin/BUILD.gn
index 2ccc30c..e535903 100644
--- a/examples/fabric-sync/admin/BUILD.gn
+++ b/examples/fabric-sync/admin/BUILD.gn
@@ -44,8 +44,12 @@
"FabricAdmin.h",
"FabricSyncGetter.cpp",
"FabricSyncGetter.h",
+ "IcdManager.cpp",
+ "IcdManager.h",
"PairingManager.cpp",
"PairingManager.h",
+ "StayActiveSender.cpp",
+ "StayActiveSender.h",
"UniqueIdGetter.cpp",
"UniqueIdGetter.h",
]
@@ -53,6 +57,8 @@
deps = [
"${chip_root}/examples/fabric-sync/bridge:fabric-bridge-lib",
"${chip_root}/examples/platform/linux:app-main",
+ "${chip_root}/src/app/icd/client:handler",
+ "${chip_root}/src/app/icd/client:manager",
"${chip_root}/src/lib",
]
}
diff --git a/examples/fabric-sync/admin/FabricAdmin.cpp b/examples/fabric-sync/admin/FabricAdmin.cpp
index ed2126d..afc9456 100644
--- a/examples/fabric-sync/admin/FabricAdmin.cpp
+++ b/examples/fabric-sync/admin/FabricAdmin.cpp
@@ -16,6 +16,9 @@
*/
#include "FabricAdmin.h"
+#include <AppMain.h>
+#include <bridge/include/FabricBridge.h>
+#include <controller/CHIPDeviceControllerFactory.h>
using namespace ::chip;
@@ -28,16 +31,35 @@
} // namespace
FabricAdmin FabricAdmin::sInstance;
+app::DefaultICDClientStorage FabricAdmin::sICDClientStorage;
+app::CheckInHandler FabricAdmin::sCheckInHandler;
FabricAdmin & FabricAdmin::Instance()
{
if (!sInstance.mInitialized)
{
- sInstance.Init();
+ VerifyOrDie(sInstance.Init() == CHIP_NO_ERROR);
}
return sInstance;
}
+CHIP_ERROR FabricAdmin::Init()
+{
+ IcdManager::Instance().SetDelegate(&sInstance);
+
+ ReturnLogErrorOnFailure(sICDClientStorage.Init(GetPersistentStorageDelegate(), GetSessionKeystore()));
+
+ auto engine = chip::app::InteractionModelEngine::GetInstance();
+ VerifyOrReturnError(engine != nullptr, CHIP_ERROR_INCORRECT_STATE);
+ ReturnLogErrorOnFailure(IcdManager::Instance().Init(&sICDClientStorage, engine));
+ ReturnLogErrorOnFailure(sCheckInHandler.Init(Controller::DeviceControllerFactory::GetInstance().GetSystemState()->ExchangeMgr(),
+ &sICDClientStorage, &IcdManager::Instance(), engine));
+
+ mInitialized = true;
+
+ return CHIP_NO_ERROR;
+}
+
CHIP_ERROR FabricAdmin::OpenCommissioningWindow(Controller::CommissioningWindowVerifierParams params, FabricIndex fabricIndex)
{
ScopedNodeId scopedNodeId(params.GetNodeId(), fabricIndex);
@@ -116,6 +138,46 @@
return CHIP_NO_ERROR;
}
+void FabricAdmin::OnCheckInCompleted(const app::ICDClientInfo & clientInfo)
+{
+ // Accessing mPendingCheckIn should only be done while holding ChipStackLock
+ assertChipStackLockedByCurrentThread();
+ ScopedNodeId scopedNodeId = clientInfo.peer_node;
+ auto it = mPendingCheckIn.find(scopedNodeId);
+ VerifyOrReturn(it != mPendingCheckIn.end());
+
+ KeepActiveDataForCheckIn checkInData = it->second;
+ // Removed from pending map as check-in from this node has occured and we will handle the pending KeepActive
+ // request.
+ mPendingCheckIn.erase(scopedNodeId);
+
+ auto timeNow = System::SystemClock().GetMonotonicTimestamp();
+ if (timeNow > checkInData.mRequestExpiryTimestamp)
+ {
+ ChipLogError(NotSpecified,
+ "ICD check-in for device we have been waiting, came after KeepActive expiry. Request dropped for ID: "
+ "[%d:0x " ChipLogFormatX64 "]",
+ scopedNodeId.GetFabricIndex(), ChipLogValueX64(scopedNodeId.GetNodeId()));
+ return;
+ }
+
+ // TODO https://github.com/CHIP-Specifications/connectedhomeip-spec/issues/10448. Spec does
+ // not define what to do if we fail to send the StayActiveRequest. We are assuming that any
+ // further attempts to send a StayActiveRequest will result in a similar failure. Because
+ // there is no mechanism for us to communicate with the client that sent out the KeepActive
+ // command that there was a failure, we simply fail silently. After spec issue is
+ // addressed, we can implement what spec defines here.
+ auto onDone = [=](uint32_t promisedActiveDuration) {
+ bridge::FabricBridge::Instance().ActiveChanged(scopedNodeId, promisedActiveDuration);
+ };
+ CHIP_ERROR err = StayActiveSender::SendStayActiveCommand(checkInData.mStayActiveDurationMs, clientInfo.peer_node,
+ app::InteractionModelEngine::GetInstance(), onDone);
+ if (err != CHIP_NO_ERROR)
+ {
+ ChipLogError(NotSpecified, "Failed to send StayActive command %s", err.AsString());
+ }
+}
+
void FabricAdmin::OnCommissioningComplete(NodeId deviceId, CHIP_ERROR err)
{
if (mNodeId != deviceId)
diff --git a/examples/fabric-sync/admin/FabricAdmin.h b/examples/fabric-sync/admin/FabricAdmin.h
index f263415..8554737 100644
--- a/examples/fabric-sync/admin/FabricAdmin.h
+++ b/examples/fabric-sync/admin/FabricAdmin.h
@@ -18,7 +18,12 @@
#pragma once
#include "DeviceManager.h"
+#include "IcdManager.h"
+#include "StayActiveSender.h"
+#include <app/icd/client/CheckInHandler.h>
+#include <app/icd/client/DefaultCheckInDelegate.h>
+#include <app/icd/client/DefaultICDClientStorage.h>
#include <bridge/include/FabricAdminDelegate.h>
#include <map>
#include <setup_payload/QRCodeSetupPayloadGenerator.h>
@@ -37,10 +42,11 @@
}
};
-class FabricAdmin final : public bridge::FabricAdminDelegate, public PairingDelegate
+class FabricAdmin final : public bridge::FabricAdminDelegate, public PairingDelegate, public IcdManager::Delegate
{
public:
static FabricAdmin & Instance();
+ static chip::app::DefaultICDClientStorage & GetDefaultICDClientStorage() { return sICDClientStorage; }
CHIP_ERROR OpenCommissioningWindow(chip::Controller::CommissioningWindowVerifierParams params,
chip::FabricIndex fabricIndex) override;
@@ -51,6 +57,8 @@
CHIP_ERROR KeepActive(chip::ScopedNodeId scopedNodeId, uint32_t stayActiveDurationMs, uint32_t timeoutMs) override;
+ void OnCheckInCompleted(const chip::app::ICDClientInfo & clientInfo) override;
+
void OnCommissioningComplete(chip::NodeId deviceId, CHIP_ERROR err) override;
void ScheduleSendingKeepActiveOnCheckIn(chip::ScopedNodeId scopedNodeId, uint32_t stayActiveDurationMs, uint32_t timeoutMs);
@@ -89,11 +97,13 @@
std::unordered_map<chip::ScopedNodeId, KeepActiveDataForCheckIn, ScopedNodeIdHasher> mPendingCheckIn;
static FabricAdmin sInstance;
+ static chip::app::DefaultICDClientStorage sICDClientStorage;
+ static chip::app::CheckInHandler sCheckInHandler;
bool mInitialized = false;
chip::NodeId mNodeId = chip::kUndefinedNodeId;
- void Init() { mInitialized = true; }
+ CHIP_ERROR Init();
};
} // namespace admin
diff --git a/examples/fabric-sync/admin/IcdManager.cpp b/examples/fabric-sync/admin/IcdManager.cpp
new file mode 100644
index 0000000..75c49a7
--- /dev/null
+++ b/examples/fabric-sync/admin/IcdManager.cpp
@@ -0,0 +1,51 @@
+/*
+ * 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 "IcdManager.h"
+
+namespace admin {
+
+IcdManager IcdManager::sInstance;
+
+IcdManager & IcdManager::Instance()
+{
+ return sInstance;
+}
+
+void IcdManager::OnCheckInComplete(const chip::app::ICDClientInfo & clientInfo)
+{
+ DefaultCheckInDelegate::OnCheckInComplete(clientInfo);
+ if (mDelegate)
+ {
+ mDelegate->OnCheckInCompleted(clientInfo);
+ }
+}
+
+void IcdManager::SetDelegate(Delegate * delegate)
+{
+ // To keep IcdManager simple, there is an assumption that there is only ever
+ // one delegate set and it's lifetime is identical to IcdManager. In the
+ // future this assumption can change should there be a need, but that will
+ // require code changes to IcdManager. For now we will crash if someone tries
+ // to call SetDelegate for a second time or if delegate is non-null.
+ VerifyOrDie(delegate);
+ VerifyOrDie(!mDelegate);
+ mDelegate = delegate;
+}
+
+} // namespace admin
diff --git a/examples/fabric-sync/admin/IcdManager.h b/examples/fabric-sync/admin/IcdManager.h
new file mode 100644
index 0000000..0700363
--- /dev/null
+++ b/examples/fabric-sync/admin/IcdManager.h
@@ -0,0 +1,55 @@
+/*
+ * 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 admin {
+
+/**
+ * @brief Manages check-ins from ICD devices.
+ *
+ * Intended to be used as a thin CheckInDelegate. This allows a delegate register
+ * themselves so they can be aware when ICD device checks-in allowing the
+ * delegate to interact with the ICD device during the short window that it is
+ * awake.
+ */
+class IcdManager : public chip::app::DefaultCheckInDelegate
+{
+public:
+ class Delegate
+ {
+ public:
+ virtual ~Delegate() = default;
+ virtual void OnCheckInCompleted(const chip::app::ICDClientInfo & clientInfo) = 0;
+ };
+
+ static IcdManager & Instance();
+ void OnCheckInComplete(const chip::app::ICDClientInfo & clientInfo) override;
+
+ // There is an assumption delegate assigned only happens once and that it lives
+ // for the entirety of the lifetime of fabric admin.
+ void SetDelegate(Delegate * delegate);
+
+private:
+ static IcdManager sInstance;
+ Delegate * mDelegate = nullptr;
+};
+
+} // namespace admin
diff --git a/examples/fabric-sync/admin/PairingManager.cpp b/examples/fabric-sync/admin/PairingManager.cpp
index eeb8ad0..63a2c67 100644
--- a/examples/fabric-sync/admin/PairingManager.cpp
+++ b/examples/fabric-sync/admin/PairingManager.cpp
@@ -18,6 +18,7 @@
#include "PairingManager.h"
#include "DeviceManager.h"
#include "DeviceSynchronization.h"
+#include "FabricAdmin.h"
#include <netdb.h>
#include <sys/socket.h>
@@ -103,7 +104,7 @@
CHIP_ERROR PairingManager::Init(Controller::DeviceCommissioner * commissioner)
{
VerifyOrReturnError(commissioner != nullptr, CHIP_ERROR_INCORRECT_STATE);
-
+ FabricAdmin::Instance().GetDefaultICDClientStorage().UpdateFabricList(commissioner->GetFabricIndex());
mCommissioner = commissioner;
return CHIP_NO_ERROR;
@@ -294,10 +295,20 @@
// mCommissioner has a lifetime that is the entire life of the application itself
// so it is safe to provide to StartDeviceSynchronization.
- DeviceSynchronizer::Instance().StartDeviceSynchronization(mCommissioner, nodeId, false);
+ DeviceSynchronizer::Instance().StartDeviceSynchronization(mCommissioner, nodeId, mDeviceIsICD);
}
else
{
+ // When ICD device commissioning fails, the ICDClientInfo stored in OnICDRegistrationComplete needs to be removed.
+ if (mDeviceIsICD)
+ {
+ CHIP_ERROR deleteEntryError = FabricAdmin::Instance().GetDefaultICDClientStorage().DeleteEntry(
+ ScopedNodeId(nodeId, mCommissioner->GetFabricIndex()));
+ if (deleteEntryError != CHIP_NO_ERROR)
+ {
+ ChipLogError(NotSpecified, "Failed to delete ICD entry: %s", ErrorStr(err));
+ }
+ }
ChipLogProgress(NotSpecified, "Device commissioning Failure: %s", ErrorStr(err));
}
}
@@ -329,6 +340,49 @@
info.icd.idleModeDuration, info.icd.activeModeDuration, info.icd.activeModeThreshold);
}
+void PairingManager::OnICDRegistrationComplete(ScopedNodeId nodeId, uint32_t icdCounter)
+{
+ char icdSymmetricKeyHex[Crypto::kAES_CCM128_Key_Length * 2 + 1];
+
+ Encoding::BytesToHex(mICDSymmetricKey.Value().data(), mICDSymmetricKey.Value().size(), icdSymmetricKeyHex,
+ sizeof(icdSymmetricKeyHex), Encoding::HexFlags::kNullTerminate);
+
+ app::ICDClientInfo clientInfo;
+ clientInfo.peer_node = nodeId;
+ clientInfo.monitored_subject = mICDMonitoredSubject.Value();
+ clientInfo.start_icd_counter = icdCounter;
+ auto & ICDClientStorage = FabricAdmin::Instance().GetDefaultICDClientStorage();
+
+ CHIP_ERROR err = ICDClientStorage.SetKey(clientInfo, mICDSymmetricKey.Value());
+ if (err == CHIP_NO_ERROR)
+ {
+ err = ICDClientStorage.StoreEntry(clientInfo);
+ }
+
+ if (err != CHIP_NO_ERROR)
+ {
+ ICDClientStorage.RemoveKey(clientInfo);
+ ChipLogError(NotSpecified, "Failed to persist symmetric key for " ChipLogFormatX64 ": %s",
+ ChipLogValueX64(nodeId.GetNodeId()), err.AsString());
+ return;
+ }
+
+ mDeviceIsICD = true;
+
+ 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.GetNodeId()), ChipLogValueX64(mICDCheckInNodeId.Value()),
+ ChipLogValueX64(mICDMonitoredSubject.Value()), icdSymmetricKeyHex, icdCounter);
+}
+
+void PairingManager::OnICDStayActiveComplete(ScopedNodeId deviceId, uint32_t promisedActiveDuration)
+{
+ ChipLogProgress(NotSpecified, "ICD Stay Active Complete for device " ChipLogFormatX64 " / promisedActiveDuration: %u",
+ ChipLogValueX64(deviceId.GetNodeId()), promisedActiveDuration);
+}
+
void PairingManager::OnDiscoveredDevice(const Dnssd::CommissionNodeData & nodeData)
{
// Ignore nodes with closed commissioning window
@@ -436,6 +490,39 @@
params.SetSkipCommissioningComplete(false);
params.SetDeviceAttestationDelegate(this);
+ if (mICDRegistration.ValueOr(false))
+ {
+ params.SetICDRegistrationStrategy(ICDRegistrationStrategy::kBeforeComplete);
+
+ if (!mICDSymmetricKey.HasValue())
+ {
+ Crypto::DRBG_get_bytes(mRandomGeneratedICDSymmetricKey, sizeof(mRandomGeneratedICDSymmetricKey));
+ mICDSymmetricKey.SetValue(ByteSpan(mRandomGeneratedICDSymmetricKey));
+ }
+ if (!mICDCheckInNodeId.HasValue())
+ {
+ mICDCheckInNodeId.SetValue(mCommissioner->GetNodeId());
+ }
+ if (!mICDMonitoredSubject.HasValue())
+ {
+ mICDMonitoredSubject.SetValue(mICDCheckInNodeId.Value());
+ }
+ if (!mICDClientType.HasValue())
+ {
+ mICDClientType.SetValue(app::Clusters::IcdManagement::ClientTypeEnum::kPermanent);
+ }
+ // These Optionals must have values now.
+ // The commissioner will verify these values.
+ params.SetICDSymmetricKey(mICDSymmetricKey.Value());
+ if (mICDStayActiveDurationMsec.HasValue())
+ {
+ params.SetICDStayActiveDurationMsec(mICDStayActiveDurationMsec.Value());
+ }
+ params.SetICDCheckInNodeId(mICDCheckInNodeId.Value());
+ params.SetICDMonitoredSubject(mICDMonitoredSubject.Value());
+ params.SetICDClientType(mICDClientType.Value());
+ }
+
return params;
}
@@ -482,6 +569,7 @@
void PairingManager::InitPairingCommand()
{
mCommissioner->RegisterPairingDelegate(this);
+ mDeviceIsICD = false;
}
CHIP_ERROR PairingManager::PairDeviceWithCode(NodeId nodeId, const char * payload)
diff --git a/examples/fabric-sync/admin/PairingManager.h b/examples/fabric-sync/admin/PairingManager.h
index 9d10991..37e9aed 100644
--- a/examples/fabric-sync/admin/PairingManager.h
+++ b/examples/fabric-sync/admin/PairingManager.h
@@ -149,6 +149,8 @@
void OnPairingDeleted(CHIP_ERROR error) override;
void OnReadCommissioningInfo(const chip::Controller::ReadCommissioningInfo & info) override;
void OnCommissioningComplete(chip::NodeId deviceId, CHIP_ERROR error) 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;
@@ -176,11 +178,20 @@
chip::ByteSpan mSalt;
uint16_t mDiscriminator = 0;
uint32_t mSetupPINCode = 0;
+ bool mDeviceIsICD = false;
+ uint8_t mRandomGeneratedICDSymmetricKey[chip::Crypto::kAES_CCM128_Key_Length];
uint8_t mVerifierBuffer[chip::Crypto::kSpake2p_VerifierSerialized_Length];
uint8_t mSaltBuffer[chip::Crypto::kSpake2p_Max_PBKDF_Salt_Length];
char mRemoteIpAddr[chip::Inet::IPAddress::kMaxStringLength];
char mOnboardingPayload[kMaxManualCodeLength + 1];
+ chip::Optional<bool> mICDRegistration;
+ chip::Optional<chip::NodeId> mICDCheckInNodeId;
+ chip::Optional<chip::app::Clusters::IcdManagement::ClientTypeEnum> mICDClientType;
+ chip::Optional<chip::ByteSpan> mICDSymmetricKey;
+ chip::Optional<uint64_t> mICDMonitoredSubject;
+ chip::Optional<uint32_t> mICDStayActiveDurationMsec;
+
/**
* Holds the unique_ptr to the current CommissioningWindowOpener.
* Only one commissioning window opener can be active at a time.
diff --git a/examples/fabric-sync/admin/StayActiveSender.cpp b/examples/fabric-sync/admin/StayActiveSender.cpp
new file mode 100644
index 0000000..51927c4
--- /dev/null
+++ b/examples/fabric-sync/admin/StayActiveSender.cpp
@@ -0,0 +1,103 @@
+/*
+ * 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 "StayActiveSender.h"
+
+#include <app-common/zap-generated/cluster-objects.h>
+#include <app/ConcreteCommandPath.h>
+#include <controller/InvokeInteraction.h>
+#include <support/CHIPMem.h>
+
+namespace admin {
+
+CHIP_ERROR StayActiveSender::SendStayActiveCommand(uint32_t stayActiveDurationMs, const chip::ScopedNodeId & peerNode,
+ chip::app::InteractionModelEngine * engine, OnDoneCallbackType onDone)
+{
+ ConstructorOnlyInternallyCallable internal;
+ auto stayActiveSender = chip::Platform::New<StayActiveSender>(internal, stayActiveDurationMs, peerNode,
+ chip::app::InteractionModelEngine::GetInstance(), onDone);
+ VerifyOrReturnError(stayActiveSender != nullptr, CHIP_ERROR_NO_MEMORY);
+ CHIP_ERROR err = stayActiveSender->EstablishSessionToPeer();
+ if (CHIP_NO_ERROR != err)
+ {
+ chip::Platform::Delete(stayActiveSender);
+ }
+ return err;
+}
+
+StayActiveSender::StayActiveSender(const ConstructorOnlyInternallyCallable & _, uint32_t stayActiveDurationMs,
+ const chip::ScopedNodeId & peerNode, chip::app::InteractionModelEngine * engine,
+ OnDoneCallbackType onDone) :
+ mStayActiveDurationMs(stayActiveDurationMs),
+ mPeerNode(peerNode), mpImEngine(engine), mOnDone(onDone), mOnConnectedCallback(HandleDeviceConnected, this),
+ mOnConnectionFailureCallback(HandleDeviceConnectionFailure, this)
+{}
+
+CHIP_ERROR StayActiveSender::SendStayActiveCommand(chip::Messaging::ExchangeManager & exchangeMgr,
+ const chip::SessionHandle & sessionHandle)
+{
+ auto onSuccess = [&](const chip::app::ConcreteCommandPath & commandPath, const chip::app::StatusIB & status,
+ const auto & dataResponse) {
+ uint32_t promisedActiveDurationMs = dataResponse.promisedActiveDuration;
+ ChipLogProgress(ICD, "StayActive command succeeded with promised duration %u", promisedActiveDurationMs);
+ mOnDone(promisedActiveDurationMs);
+ chip::Platform::Delete(this);
+ };
+
+ auto onFailure = [&](CHIP_ERROR error) {
+ ChipLogError(ICD, "StayActive command failed: %" CHIP_ERROR_FORMAT, error.Format());
+ chip::Platform::Delete(this);
+ };
+
+ chip::EndpointId endpointId = 0;
+ chip::app::Clusters::IcdManagement::Commands::StayActiveRequest::Type request;
+ request.stayActiveDuration = mStayActiveDurationMs;
+ return chip::Controller::InvokeCommandRequest(&exchangeMgr, sessionHandle, endpointId, request, onSuccess, onFailure);
+}
+
+CHIP_ERROR StayActiveSender::EstablishSessionToPeer()
+{
+ ChipLogProgress(ICD, "Trying to establish a CASE session to extend the active period for lit icd device");
+ auto * caseSessionManager = mpImEngine->GetCASESessionManager();
+ VerifyOrReturnError(caseSessionManager != nullptr, CHIP_ERROR_INVALID_CASE_PARAMETER);
+ caseSessionManager->FindOrEstablishSession(mPeerNode, &mOnConnectedCallback, &mOnConnectionFailureCallback);
+ return CHIP_NO_ERROR;
+}
+
+void StayActiveSender::HandleDeviceConnected(void * context, chip::Messaging::ExchangeManager & exchangeMgr,
+ const chip::SessionHandle & sessionHandle)
+{
+ StayActiveSender * const _this = static_cast<StayActiveSender *>(context);
+ VerifyOrDie(_this != nullptr);
+
+ CHIP_ERROR err = _this->SendStayActiveCommand(exchangeMgr, sessionHandle);
+ if (CHIP_NO_ERROR != err)
+ {
+ ChipLogError(ICD, "Failed to send stay active command");
+ chip::Platform::Delete(_this);
+ }
+}
+
+void StayActiveSender::HandleDeviceConnectionFailure(void * context, const chip::ScopedNodeId & peerId, CHIP_ERROR err)
+{
+ StayActiveSender * const _this = static_cast<StayActiveSender *>(context);
+ VerifyOrDie(_this != nullptr);
+ ChipLogError(ICD, "Failed to establish CASE for stay active command with error '%" CHIP_ERROR_FORMAT "'", err.Format());
+ chip::Platform::Delete(_this);
+}
+
+} // namespace admin
diff --git a/examples/fabric-sync/admin/StayActiveSender.h b/examples/fabric-sync/admin/StayActiveSender.h
new file mode 100644
index 0000000..dd4a66d
--- /dev/null
+++ b/examples/fabric-sync/admin/StayActiveSender.h
@@ -0,0 +1,111 @@
+/*
+ *
+ * Copyright (c) 2024 Project CHIP Authors
+ *
+ * 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 <stddef.h>
+
+#include <app/InteractionModelEngine.h>
+#include <lib/core/ScopedNodeId.h>
+#include <messaging/ExchangeMgr.h>
+
+namespace admin {
+
+/**
+ * @brief StayActiveSender contains all the data and methods needed for active period extension of an ICD client.
+ *
+ * Lifetime of instance of StayActiveSender is entirely self managed.
+ */
+class StayActiveSender
+{
+private:
+ // Ideally StayActiveSender would be a private constructor, unfortunately that is not possible as Platform::New
+ // does not have access to private constructors. As a workaround we have defined this private struct that can
+ // be forwarded by Platform::New that allows us to enforce that the only way StayActiveSender is constructed is
+ // if SendStayActiveCommand is called.
+ struct ConstructorOnlyInternallyCallable
+ {
+ };
+
+public:
+ using OnDoneCallbackType = std::function<void(uint32_t promisedActiveDurationMs)>;
+
+ /**
+ * @brief Attempts to send a StayActiveRequest command
+ *
+ * @param[in] stayActiveDurationMs StayActiveRequest command parameter.
+ * @param[in] peerNode Peer node we sending StayActiveRequest command to
+ * @param[in] engine Interaction Model Engine instance for sending command.
+ * @param[in] onDone Upon this function returning success, it is expected that onDone will be called after we
+ * have successfully recieved a response
+ *
+ * @return CHIP_ERROR CHIP_NO_ERROR on success, or corresponding error code.
+ */
+ static CHIP_ERROR SendStayActiveCommand(uint32_t stayActiveDurationMs, const chip::ScopedNodeId & peerNode,
+ chip::app::InteractionModelEngine * engine, OnDoneCallbackType onDone);
+
+ // Ideally this would be a private constructor, unfortunately that is not possible as Platform::New does not
+ // have access to private constructors. As a workaround we have defined a private struct that can be forwarded
+ // by Platform::New that allows us to enforce that the only way this is constructed is if SendStayActiveCommand
+ // is called.
+ StayActiveSender(const ConstructorOnlyInternallyCallable & _, uint32_t stayActiveDurationMs,
+ const chip::ScopedNodeId & peerNode, chip::app::InteractionModelEngine * engine, OnDoneCallbackType onDone);
+
+private:
+ /**
+ * @brief Sets up a CASE session with the peer to extend the client's active period with that peer.
+ * Returns error if we did not even manage to kick off a CASE attempt.
+ */
+ CHIP_ERROR EstablishSessionToPeer();
+
+ // CASE session callbacks
+ /**
+ *@brief Callback received on successfully establishing a CASE session in order to keep the 'lit icd device' active
+ *
+ * @param[in] context - context of the client establishing the CASE session
+ * @param[in] exchangeMgr - exchange manager to use for the re-registration
+ * @param[in] sessionHandle - session handle to use for the re-registration
+ */
+ static void HandleDeviceConnected(void * context, chip::Messaging::ExchangeManager & exchangeMgr,
+ const chip::SessionHandle & sessionHandle);
+ /**
+ * @brief Callback received on failure to establish a CASE session
+ *
+ * @param[in] context - context of the client establishing the CASE session
+ * @param[in] peerId - Scoped Node ID of the peer node
+ * @param[in] err - failure reason
+ */
+ static void HandleDeviceConnectionFailure(void * context, const chip::ScopedNodeId & peerId, CHIP_ERROR err);
+
+ /**
+ * @brief Used to send a stayActive command to the peer
+ *
+ * @param[in] exchangeMgr - exchange manager to use for the re-registration
+ * @param[in] sessionHandle - session handle to use for the re-registration
+ */
+ CHIP_ERROR SendStayActiveCommand(chip::Messaging::ExchangeManager & exchangeMgr, const chip::SessionHandle & sessionHandle);
+
+ uint32_t mStayActiveDurationMs = 0;
+ chip::ScopedNodeId mPeerNode;
+ chip::app::InteractionModelEngine * mpImEngine = nullptr;
+ OnDoneCallbackType mOnDone;
+
+ chip::Callback::Callback<chip::OnDeviceConnected> mOnConnectedCallback;
+ chip::Callback::Callback<chip::OnDeviceConnectionFailure> mOnConnectionFailureCallback;
+};
+
+} // namespace admin
diff --git a/examples/platform/linux/AppMain.h b/examples/platform/linux/AppMain.h
index 94bf4d3..21ba04d 100644
--- a/examples/platform/linux/AppMain.h
+++ b/examples/platform/linux/AppMain.h
@@ -21,6 +21,7 @@
#include <app/server/Server.h>
#include <controller/CHIPDeviceController.h>
#include <controller/CommissionerDiscoveryController.h>
+#include <crypto/RawKeySessionKeystore.h>
#include <lib/core/CHIPError.h>
#include <lib/core/DataModelTypes.h>
#include <lib/core/Optional.h>
@@ -90,7 +91,9 @@
#if CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE
+using chip::PersistentStorageDelegate;
using chip::Controller::DeviceCommissioner;
+using chip::Crypto::SessionKeystore;
using chip::Transport::PeerAddress;
CHIP_ERROR CommissionerPairOnNetwork(uint32_t pincode, uint16_t disc, PeerAddress address);
@@ -98,6 +101,8 @@
DeviceCommissioner * GetDeviceCommissioner();
CommissionerDiscoveryController * GetCommissionerDiscoveryController();
+SessionKeystore * GetSessionKeystore();
+PersistentStorageDelegate * GetPersistentStorageDelegate();
#endif // CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE