[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