[ICD] Invoke stayActive in the end of commissioning flow (#32496)

* Invoke stayActive in the end of commissioning flow

* address comment

* Restyled by clang-format

* address comments

* Restyled by whitespace

* Restyled by clang-format

* address comments

* Restyled by clang-format

* Send ICDStayActive over case

* Add another 3 state machine to handle

-- To release all previous stale case sssion
-- To handle case session for ICD stay active
-- To handle case session for commision complete

* address comment

---------

Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/examples/chip-tool/commands/pairing/PairingCommand.cpp b/examples/chip-tool/commands/pairing/PairingCommand.cpp
index 27edae0..ed80bc0 100644
--- a/examples/chip-tool/commands/pairing/PairingCommand.cpp
+++ b/examples/chip-tool/commands/pairing/PairingCommand.cpp
@@ -154,6 +154,10 @@
         // 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());
     }
@@ -465,6 +469,12 @@
                     ChipLogValueX64(mICDMonitoredSubject.Value()), icdSymmetricKeyHex);
 }
 
+void PairingCommand::OnICDStayActiveComplete(NodeId deviceId, uint32_t promisedActiveDuration)
+{
+    ChipLogProgress(chipTool, "ICD Stay Active Complete for device " ChipLogFormatX64 " / promisedActiveDuration: %u",
+                    ChipLogValueX64(deviceId), promisedActiveDuration);
+}
+
 void PairingCommand::OnDiscoveredDevice(const chip::Dnssd::DiscoveredNodeData & nodeData)
 {
     // Ignore nodes with closed commissioning window
diff --git a/examples/chip-tool/commands/pairing/PairingCommand.h b/examples/chip-tool/commands/pairing/PairingCommand.h
index c1df172..0baf701 100644
--- a/examples/chip-tool/commands/pairing/PairingCommand.h
+++ b/examples/chip-tool/commands/pairing/PairingCommand.h
@@ -72,7 +72,8 @@
         AddArgument("icd-monitored-subject", 0, UINT64_MAX, &mICDMonitoredSubject,
                     "The monitored subject of the ICD, default: The node id used for icd-check-in-nodeid.");
         AddArgument("icd-symmetric-key", &mICDSymmetricKey, "The 16 bytes ICD symmetric key, default: randomly generated.");
-
+        AddArgument("icd-stay-active-duration", 0, UINT32_MAX, &mICDStayActiveDurationMsec,
+                    "If set, a LIT ICD that is commissioned will be requested to stay active for this many milliseconds");
         switch (networkType)
         {
         case PairingNetworkType::None:
@@ -197,6 +198,7 @@
     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;
 
     /////////// DeviceDiscoveryDelegate Interface /////////
     void OnDiscoveredDevice(const chip::Dnssd::DiscoveredNodeData & nodeData) override;
@@ -235,6 +237,7 @@
     chip::Optional<NodeId> mICDCheckInNodeId;
     chip::Optional<chip::ByteSpan> mICDSymmetricKey;
     chip::Optional<uint64_t> mICDMonitoredSubject;
+    chip::Optional<uint32_t> mICDStayActiveDurationMsec;
     chip::app::DataModel::List<chip::app::Clusters::TimeSynchronization::Structs::TimeZoneStruct::Type> mTimeZoneList;
     TypedComplexArgument<chip::app::DataModel::List<chip::app::Clusters::TimeSynchronization::Structs::TimeZoneStruct::Type>>
         mComplex_TimeZones;
diff --git a/src/controller/AutoCommissioner.cpp b/src/controller/AutoCommissioner.cpp
index 557cdf4..42b39a1 100644
--- a/src/controller/AutoCommissioner.cpp
+++ b/src/controller/AutoCommissioner.cpp
@@ -416,13 +416,10 @@
             }
             return CommissioningStage::kICDGetRegistrationInfo;
         }
-        return GetNextCommissioningStageInternal(CommissioningStage::kICDSendStayActive, lastErr);
+        return GetNextCommissioningStageInternal(CommissioningStage::kICDRegistration, lastErr);
     case CommissioningStage::kICDGetRegistrationInfo:
         return CommissioningStage::kICDRegistration;
     case CommissioningStage::kICDRegistration:
-        // TODO(#24259): StayActiveRequest is not supported by server. We may want to SendStayActive after OpDiscovery.
-        return CommissioningStage::kICDSendStayActive;
-    case CommissioningStage::kICDSendStayActive:
         // TODO(cecille): device attestation casues operational cert provisioning to happen, This should be a separate stage.
         // For thread and wifi, this should go to network setup then enable. For on-network we can skip right to finding the
         // operational network because the provisioning of certificates will trigger the device to start operational advertising.
@@ -446,7 +443,7 @@
             {
                 return CommissioningStage::kCleanup;
             }
-            return CommissioningStage::kFindOperational;
+            return CommissioningStage::kEvictPreviousCaseSessions;
         }
     case CommissioningStage::kScanNetworks:
         return CommissioningStage::kNeedsNetworkCreds;
@@ -489,7 +486,7 @@
         else
         {
             SetCASEFailsafeTimerIfNeeded();
-            return CommissioningStage::kFindOperational;
+            return CommissioningStage::kEvictPreviousCaseSessions;
         }
     case CommissioningStage::kThreadNetworkEnable:
         SetCASEFailsafeTimerIfNeeded();
@@ -497,8 +494,14 @@
         {
             return CommissioningStage::kCleanup;
         }
-        return CommissioningStage::kFindOperational;
-    case CommissioningStage::kFindOperational:
+        return CommissioningStage::kEvictPreviousCaseSessions;
+    case CommissioningStage::kEvictPreviousCaseSessions:
+        return CommissioningStage::kFindOperationalForStayActive;
+    case CommissioningStage::kFindOperationalForStayActive:
+        return CommissioningStage::kICDSendStayActive;
+    case CommissioningStage::kICDSendStayActive:
+        return CommissioningStage::kFindOperationalForCommissioningComplete;
+    case CommissioningStage::kFindOperationalForCommissioningComplete:
         return CommissioningStage::kSendComplete;
     case CommissioningStage::kSendComplete:
         return CommissioningStage::kCleanup;
@@ -539,7 +542,7 @@
         //
         // A false return from ExtendArmFailSafe is fine; we don't want to make
         // the fail-safe shorter here.
-        mCommissioner->ExtendArmFailSafe(mCommissioneeDeviceProxy, CommissioningStage::kFindOperational,
+        mCommissioner->ExtendArmFailSafe(mCommissioneeDeviceProxy, mCommissioner->GetCommissioningStage(),
                                          mParams.GetCASEFailsafeTimerSeconds().Value(),
                                          GetCommandTimeout(mCommissioneeDeviceProxy, CommissioningStage::kArmFailsafe),
                                          OnExtendFailsafeSuccessForCASE, OnFailsafeFailureForCASE);
@@ -754,6 +757,11 @@
                     mNeedIcdRegistration = true;
                     ChipLogDetail(Controller, "AutoCommissioner: ICD supports the check-in protocol.");
                 }
+                else if (mParams.GetICDStayActiveDurationMsec().HasValue())
+                {
+                    ChipLogDetail(Controller, "AutoCommissioner: Clear ICD StayActiveDurationMsec");
+                    mParams.ClearICDStayActiveDurationMsec();
+                }
             }
             break;
         }
@@ -822,10 +830,8 @@
         case CommissioningStage::kICDRegistration:
             // Noting to do. DevicePairingDelegate will handle this.
             break;
-        case CommissioningStage::kICDSendStayActive:
-            // Nothing to do.
-            break;
-        case CommissioningStage::kFindOperational:
+        case CommissioningStage::kFindOperationalForStayActive:
+        case CommissioningStage::kFindOperationalForCommissioningComplete:
             mOperationalDeviceProxy = report.Get<OperationalNodeFoundData>().operationalProxy;
             break;
         case CommissioningStage::kCleanup:
@@ -861,7 +867,7 @@
 
 DeviceProxy * AutoCommissioner::GetDeviceProxyForStep(CommissioningStage nextStage)
 {
-    if (nextStage == CommissioningStage::kSendComplete ||
+    if (nextStage == CommissioningStage::kSendComplete || nextStage == CommissioningStage::kICDSendStayActive ||
         (nextStage == CommissioningStage::kCleanup && mOperationalDeviceProxy.GetDeviceId() != kUndefinedNodeId))
     {
         return &mOperationalDeviceProxy;
diff --git a/src/controller/AutoCommissioner.h b/src/controller/AutoCommissioner.h
index e311b72..389beee 100644
--- a/src/controller/AutoCommissioner.h
+++ b/src/controller/AutoCommissioner.h
@@ -123,7 +123,6 @@
     bool mNeedsDST = false;
 
     bool mNeedIcdRegistration = false;
-
     // TODO: Why were the nonces statically allocated, but the certs dynamically allocated?
     uint8_t * mDAC   = nullptr;
     uint16_t mDACLen = 0;
diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp
index 2c67693..af0a66e 100644
--- a/src/controller/CHIPDeviceController.cpp
+++ b/src/controller/CHIPDeviceController.cpp
@@ -1201,24 +1201,39 @@
 void DeviceCommissioner::OnICDManagementRegisterClientResponse(
     void * context, const app::Clusters::IcdManagement::Commands::RegisterClientResponse::DecodableType & data)
 {
+    CHIP_ERROR err                    = CHIP_NO_ERROR;
     DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
-    VerifyOrReturn(commissioner != nullptr, ChipLogProgress(Controller, "Command response callback with null context. Ignoring"));
-
-    if (commissioner->mCommissioningStage != CommissioningStage::kICDRegistration)
-    {
-        return;
-    }
-
-    if (commissioner->mDeviceBeingCommissioned == nullptr)
-    {
-        return;
-    }
+    VerifyOrExit(commissioner != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
+    VerifyOrExit(commissioner->mCommissioningStage == CommissioningStage::kICDRegistration, err = CHIP_ERROR_INCORRECT_STATE);
+    VerifyOrExit(commissioner->mDeviceBeingCommissioned != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
 
     if (commissioner->mPairingDelegate != nullptr)
     {
         commissioner->mPairingDelegate->OnICDRegistrationComplete(commissioner->mDeviceBeingCommissioned->GetDeviceId(),
                                                                   data.ICDCounter);
     }
+
+exit:
+    CommissioningDelegate::CommissioningReport report;
+    commissioner->CommissioningStageComplete(err, report);
+}
+
+void DeviceCommissioner::OnICDManagementStayActiveResponse(
+    void * context, const app::Clusters::IcdManagement::Commands::StayActiveResponse::DecodableType & data)
+{
+    CHIP_ERROR err                    = CHIP_NO_ERROR;
+    DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
+    VerifyOrExit(commissioner != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
+    VerifyOrExit(commissioner->mCommissioningStage == CommissioningStage::kICDSendStayActive, err = CHIP_ERROR_INCORRECT_STATE);
+    VerifyOrExit(commissioner->mDeviceBeingCommissioned != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+
+    if (commissioner->mPairingDelegate != nullptr)
+    {
+        commissioner->mPairingDelegate->OnICDStayActiveComplete(commissioner->mDeviceBeingCommissioned->GetDeviceId(),
+                                                                data.promisedActiveDuration);
+    }
+
+exit:
     CommissioningDelegate::CommissioningReport report;
     commissioner->CommissioningStageComplete(CHIP_NO_ERROR, report);
 }
@@ -1854,7 +1869,8 @@
 {
     // CASE session established.
     DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
-    VerifyOrDie(commissioner->mCommissioningStage == CommissioningStage::kFindOperational);
+    VerifyOrDie(commissioner->mCommissioningStage == CommissioningStage::kFindOperationalForStayActive ||
+                commissioner->mCommissioningStage == CommissioningStage::kFindOperationalForCommissioningComplete);
     VerifyOrDie(commissioner->mDeviceBeingCommissioned->GetDeviceId() == sessionHandle->GetPeer().GetNodeId());
     commissioner->CancelCASECallbacks(); // ensure all CASE callbacks are unregistered
 
@@ -1867,7 +1883,8 @@
 {
     // CASE session establishment failed.
     DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
-    VerifyOrDie(commissioner->mCommissioningStage == CommissioningStage::kFindOperational);
+    VerifyOrDie(commissioner->mCommissioningStage == CommissioningStage::kFindOperationalForStayActive ||
+                commissioner->mCommissioningStage == CommissioningStage::kFindOperationalForCommissioningComplete);
     VerifyOrDie(commissioner->mDeviceBeingCommissioned->GetDeviceId() == peerId.GetNodeId());
     commissioner->CancelCASECallbacks(); // ensure all CASE callbacks are unregistered
 
@@ -1908,7 +1925,8 @@
                  ChipLogValueScopedNodeId(peerId), error.Format(), retryTimeout.count());
 
     auto self = static_cast<DeviceCommissioner *>(context);
-    VerifyOrDie(self->mCommissioningStage == CommissioningStage::kFindOperational);
+    VerifyOrDie(self->GetCommissioningStage() == CommissioningStage::kFindOperationalForStayActive ||
+                self->GetCommissioningStage() == CommissioningStage::kFindOperationalForCommissioningComplete);
     VerifyOrDie(self->mDeviceBeingCommissioned->GetDeviceId() == peerId.GetNodeId());
 
     // We need to do the fail-safe arming over the PASE session.
@@ -1936,7 +1954,7 @@
     }
 
     // A false return is fine; we don't want to make the fail-safe shorter here.
-    self->ExtendArmFailSafeInternal(commissioneeDevice, CommissioningStage::kFindOperational, failsafeTimeout,
+    self->ExtendArmFailSafeInternal(commissioneeDevice, self->GetCommissioningStage(), failsafeTimeout,
                                     MakeOptional(kMinimumCommissioningStepTimeout), OnExtendFailsafeForCASERetrySuccess,
                                     OnExtendFailsafeForCASERetryFailure, /* fireAndForget = */ true);
 }
@@ -3180,13 +3198,7 @@
         }
     }
     break;
-    case CommissioningStage::kICDSendStayActive: {
-        // TODO(#24259): Send StayActiveRequest once server supports this.
-        CommissioningStageComplete(CHIP_NO_ERROR);
-    }
-    break;
-    case CommissioningStage::kFindOperational: {
-        // If there is an error, CommissioningStageComplete will be called from OnDeviceConnectionFailureFn.
+    case CommissioningStage::kEvictPreviousCaseSessions: {
         auto scopedPeerId = GetPeerScopedId(proxy->GetDeviceId());
 
         // If we ever had a commissioned device with this node ID before, we may
@@ -3196,7 +3208,13 @@
         // clearing the ones associated with our fabric index is good enough and
         // we don't need to worry about ExpireAllSessionsOnLogicalFabric.
         mSystemState->SessionMgr()->ExpireAllSessions(scopedPeerId);
-
+        CommissioningStageComplete(CHIP_NO_ERROR);
+        return;
+    }
+    case CommissioningStage::kFindOperationalForStayActive:
+    case CommissioningStage::kFindOperationalForCommissioningComplete: {
+        // If there is an error, CommissioningStageComplete will be called from OnDeviceConnectionFailureFn.
+        auto scopedPeerId = GetPeerScopedId(proxy->GetDeviceId());
         mSystemState->CASESessionMgr()->FindOrEstablishSession(scopedPeerId, &mOnDeviceConnectedCallback,
                                                                &mOnDeviceConnectionFailureCallback
 #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
@@ -3206,7 +3224,31 @@
         );
     }
     break;
+    case CommissioningStage::kICDSendStayActive: {
+        if (!(params.GetICDStayActiveDurationMsec().HasValue()))
+        {
+            ChipLogProgress(Controller, "Skipping kICDSendStayActive");
+            CommissioningStageComplete(CHIP_NO_ERROR);
+            return;
+        }
+
+        // StayActive Command happens over CASE Connection
+        IcdManagement::Commands::StayActiveRequest::Type request;
+        request.stayActiveDuration = params.GetICDStayActiveDurationMsec().Value();
+        ChipLogError(Controller, "Send ICD StayActive with Duration %u", request.stayActiveDuration);
+        CHIP_ERROR err =
+            SendCommissioningCommand(proxy, request, OnICDManagementStayActiveResponse, OnBasicFailure, endpoint, timeout);
+        if (err != CHIP_NO_ERROR)
+        {
+            // We won't get any async callbacks here, so just complete our stage.
+            ChipLogError(Controller, "Failed to send IcdManagement.StayActive command: %" CHIP_ERROR_FORMAT, err.Format());
+            CommissioningStageComplete(err);
+            return;
+        }
+    }
+    break;
     case CommissioningStage::kSendComplete: {
+        // CommissioningComplete command happens over the CASE connection.
         GeneralCommissioning::Commands::CommissioningComplete::Type request;
         CHIP_ERROR err =
             SendCommissioningCommand(proxy, request, OnCommissioningCompleteResponse, OnBasicFailure, endpoint, timeout);
diff --git a/src/controller/CHIPDeviceController.h b/src/controller/CHIPDeviceController.h
index a635edb..dd7b5bc 100644
--- a/src/controller/CHIPDeviceController.h
+++ b/src/controller/CHIPDeviceController.h
@@ -921,6 +921,10 @@
     static void OnICDManagementRegisterClientResponse(
         void * context, const app::Clusters::IcdManagement::Commands::RegisterClientResponse::DecodableType & data);
 
+    static void
+    OnICDManagementStayActiveResponse(void * context,
+                                      const app::Clusters::IcdManagement::Commands::StayActiveResponse::DecodableType & data);
+
     /**
      * @brief
      *   This function processes the CSR sent by the device.
diff --git a/src/controller/CommissioningDelegate.cpp b/src/controller/CommissioningDelegate.cpp
index 74f6d3c..bbe82de 100644
--- a/src/controller/CommissioningDelegate.cpp
+++ b/src/controller/CommissioningDelegate.cpp
@@ -27,143 +27,114 @@
     {
     case kError:
         return "Error";
-        break;
 
     case kSecurePairing:
         return "SecurePairing";
-        break;
 
     case kReadCommissioningInfo:
         return "ReadCommissioningInfo";
-        break;
 
     case kReadCommissioningInfo2:
         return "ReadCommissioningInfo2";
-        break;
 
     case kArmFailsafe:
         return "ArmFailSafe";
-        break;
 
     case kScanNetworks:
         return "ScanNetworks";
-        break;
 
     case kConfigRegulatory:
         return "ConfigRegulatory";
-        break;
 
     case kConfigureUTCTime:
         return "ConfigureUTCTime";
-        break;
 
     case kConfigureTimeZone:
         return "ConfigureTimeZone";
-        break;
 
     case kConfigureDSTOffset:
         return "ConfigureDSTOffset";
-        break;
 
     case kConfigureDefaultNTP:
         return "ConfigureDefaultNTP";
-        break;
 
     case kSendPAICertificateRequest:
         return "SendPAICertificateRequest";
-        break;
 
     case kSendDACCertificateRequest:
         return "SendDACCertificateRequest";
-        break;
 
     case kSendAttestationRequest:
         return "SendAttestationRequest";
-        break;
 
     case kAttestationVerification:
         return "AttestationVerification";
-        break;
 
     case kSendOpCertSigningRequest:
         return "SendOpCertSigningRequest";
-        break;
 
     case kValidateCSR:
         return "ValidateCSR";
-        break;
 
     case kGenerateNOCChain:
         return "GenerateNOCChain";
-        break;
 
     case kSendTrustedRootCert:
         return "SendTrustedRootCert";
-        break;
 
     case kSendNOC:
         return "SendNOC";
-        break;
 
     case kConfigureTrustedTimeSource:
         return "ConfigureTrustedTimeSource";
-        break;
 
     case kICDGetRegistrationInfo:
         return "ICDGetRegistrationInfo";
-        break;
 
     case kICDRegistration:
         return "ICDRegistration";
-        break;
-
-    case kICDSendStayActive:
-        return "ICDSendStayActive";
-        break;
 
     case kWiFiNetworkSetup:
         return "WiFiNetworkSetup";
-        break;
 
     case kThreadNetworkSetup:
         return "ThreadNetworkSetup";
-        break;
 
     case kFailsafeBeforeWiFiEnable:
         return "FailsafeBeforeWiFiEnable";
-        break;
 
     case kFailsafeBeforeThreadEnable:
         return "FailsafeBeforeThreadEnable";
-        break;
 
     case kWiFiNetworkEnable:
         return "WiFiNetworkEnable";
-        break;
 
     case kThreadNetworkEnable:
         return "ThreadNetworkEnable";
-        break;
 
-    case kFindOperational:
-        return "FindOperational";
-        break;
+    case kEvictPreviousCaseSessions:
+        return "kEvictPreviousCaseSessions";
+
+    case kFindOperationalForStayActive:
+        return "kFindOperationalForStayActive";
+
+    case kFindOperationalForCommissioningComplete:
+        return "kFindOperationalForCommissioningComplete";
+
+    case kICDSendStayActive:
+        return "ICDSendStayActive";
 
     case kSendComplete:
         return "SendComplete";
-        break;
 
     case kCleanup:
         return "Cleanup";
-        break;
 
     case kNeedsNetworkCreds:
         return "NeedsNetworkCreds";
-        break;
 
     default:
         return "???";
-        break;
     }
 }
 
diff --git a/src/controller/CommissioningDelegate.h b/src/controller/CommissioningDelegate.h
index 2a78663..4b1040f 100644
--- a/src/controller/CommissioningDelegate.h
+++ b/src/controller/CommissioningDelegate.h
@@ -54,16 +54,20 @@
     kConfigureTrustedTimeSource, ///< Configure a trusted time source if one is required and available (must be done after SendNOC)
     kICDGetRegistrationInfo,     ///< Waiting for the higher layer to provide ICD registraion informations.
     kICDRegistration,            ///< Register for ICD management
-    kICDSendStayActive,          ///< Send Keep Alive to ICD
     kWiFiNetworkSetup,           ///< Send AddOrUpdateWiFiNetwork (0x31:2) command to the device
     kThreadNetworkSetup,         ///< Send AddOrUpdateThreadNetwork (0x31:3) command to the device
     kFailsafeBeforeWiFiEnable,   ///< Extend the fail-safe before doing kWiFiNetworkEnable
     kFailsafeBeforeThreadEnable, ///< Extend the fail-safe before doing kThreadNetworkEnable
     kWiFiNetworkEnable,          ///< Send ConnectNetwork (0x31:6) command to the device for the WiFi network
     kThreadNetworkEnable,        ///< Send ConnectNetwork (0x31:6) command to the device for the Thread network
-    kFindOperational,            ///< Perform operational discovery and establish a CASE session with the device
-    kSendComplete,               ///< Send CommissioningComplete (0x30:4) command to the device
-    kCleanup,                    ///< Call delegates with status, free memory, clear timers and state
+    kEvictPreviousCaseSessions,  ///< Evict previous stale case sessions from a commissioned device with this node ID before
+    kFindOperationalForStayActive, ///< Perform operational discovery and establish a CASE session with the device for ICD
+                                   ///< StayActive command
+    kFindOperationalForCommissioningComplete, ///< Perform operational discovery and establish a CASE session with the device for
+                                              ///< Commissioning Complete command
+    kSendComplete,                            ///< Send CommissioningComplete (0x30:4) command to the device
+    kICDSendStayActive,                       ///< Send Keep Alive to ICD
+    kCleanup,                                 ///< Call delegates with status, free memory, clear timers and state
     /// Send ScanNetworks (0x31:0) command to the device.
     /// ScanNetworks can happen anytime after kArmFailsafe.
     /// However, the cirque tests fail if it is earlier in the list
@@ -544,6 +548,14 @@
         return *this;
     }
 
+    Optional<uint32_t> GetICDStayActiveDurationMsec() const { return mICDStayActiveDurationMsec; }
+    CommissioningParameters & SetICDStayActiveDurationMsec(uint32_t stayActiveDurationMsec)
+    {
+        mICDStayActiveDurationMsec = MakeOptional(stayActiveDurationMsec);
+        return *this;
+    }
+    void ClearICDStayActiveDurationMsec() { mICDStayActiveDurationMsec.ClearValue(); }
+
     // Clear all members that depend on some sort of external buffer.  Can be
     // used to make sure that we are not holding any dangling pointers.
     void ClearExternalBufferDependentValues()
@@ -610,7 +622,7 @@
     Optional<NodeId> mICDCheckInNodeId;
     Optional<uint64_t> mICDMonitoredSubject;
     Optional<ByteSpan> mICDSymmetricKey;
-
+    Optional<uint32_t> mICDStayActiveDurationMsec;
     ICDRegistrationStrategy mICDRegistrationStrategy = ICDRegistrationStrategy::kIgnore;
     bool mCheckForMatchingFabric                     = false;
 };
@@ -763,15 +775,18 @@
      * kSendOpCertSigningRequest: CSRResponse
      * kGenerateNOCChain: NocChain
      * kSendTrustedRootCert: None
-     * kSendNOC: none
+     * kSendNOC: None
      * kConfigureTrustedTimeSource: None
      * kWiFiNetworkSetup: NetworkCommissioningStatusInfo if there is an error
      * kThreadNetworkSetup: NetworkCommissioningStatusInfo if there is an error
      * kWiFiNetworkEnable: NetworkCommissioningStatusInfo if there is an error
      * kThreadNetworkEnable: NetworkCommissioningStatusInfo if there is an error
-     * kFindOperational: OperationalNodeFoundData
+     * kEvictPreviousCaseSessions: None
+     * kFindOperationalForStayActive OperationalNodeFoundData
+     * kFindOperationalForCommissioningComplete: OperationalNodeFoundData
+     * kICDSendStayActive: CommissioningErrorInfo if there is an error
      * kSendComplete: CommissioningErrorInfo if there is an error
-     * kCleanup: none
+     * kCleanup: None
      */
     struct CommissioningReport
         : Variant<RequestedCertificate, AttestationResponse, CSRResponse, NocChain, OperationalNodeFoundData, ReadCommissioningInfo,
diff --git a/src/controller/DevicePairingDelegate.h b/src/controller/DevicePairingDelegate.h
index 6cf28d9..558a6c1 100644
--- a/src/controller/DevicePairingDelegate.h
+++ b/src/controller/DevicePairingDelegate.h
@@ -130,13 +130,24 @@
     virtual void OnICDRegistrationInfoRequired() {}
 
     /**
-     * @bried
+     * @brief
      *   Called when the registration flow for the ICD completes.
      *
      * @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) {}
+
+    /**
+     * @brief
+     *   Called upon completion of the LIT ICD commissioning flow, when ICDStayActiveDuration is set
+     *   and the corresponding stayActive command response is received
+     *
+     * @param[in] icdNodeId    The node id of the ICD.
+     * @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) {}
 };
 
 } // namespace Controller