[ICD] StayActiveRequest (#32247)

* Implementation of StayActive request with test

* Regenerated zap files

* Added StayActiveRequest scenarios to the  yaml test

* Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
Applied comments about #if and clarifying maximum guaranteed stay active

* Added the #if back for platforms that build the icd-management-server cluster without being ICD devices

* Applied suggestions
diff --git a/examples/contact-sensor-app/nxp/zap-lit/contact-sensor-app.matter b/examples/contact-sensor-app/nxp/zap-lit/contact-sensor-app.matter
index 9a6eeb7..fa6101b 100644
--- a/examples/contact-sensor-app/nxp/zap-lit/contact-sensor-app.matter
+++ b/examples/contact-sensor-app/nxp/zap-lit/contact-sensor-app.matter
@@ -1337,6 +1337,10 @@
     optional octet_string<16> verificationKey = 1;
   }
 
+  request struct StayActiveRequestRequest {
+    int32u stayActiveDuration = 0;
+  }
+
   response struct StayActiveResponse = 4 {
     int32u promisedActiveDuration = 0;
   }
@@ -1346,7 +1350,7 @@
   /** Unregister a client from an end device */
   fabric command access(invoke: manage) UnregisterClient(UnregisterClientRequest): DefaultSuccess = 2;
   /** Request the end device to stay in Active Mode for an additional ActiveModeThreshold */
-  command access(invoke: manage) StayActiveRequest(): StayActiveResponse = 3;
+  command access(invoke: manage) StayActiveRequest(StayActiveRequestRequest): StayActiveResponse = 3;
 }
 
 endpoint 0 {
diff --git a/examples/contact-sensor-app/nxp/zap-sit/contact-sensor-app.matter b/examples/contact-sensor-app/nxp/zap-sit/contact-sensor-app.matter
index 479f8ba..f82f6a9 100644
--- a/examples/contact-sensor-app/nxp/zap-sit/contact-sensor-app.matter
+++ b/examples/contact-sensor-app/nxp/zap-sit/contact-sensor-app.matter
@@ -1337,6 +1337,10 @@
     optional octet_string<16> verificationKey = 1;
   }
 
+  request struct StayActiveRequestRequest {
+    int32u stayActiveDuration = 0;
+  }
+
   response struct StayActiveResponse = 4 {
     int32u promisedActiveDuration = 0;
   }
@@ -1346,7 +1350,7 @@
   /** Unregister a client from an end device */
   fabric command access(invoke: manage) UnregisterClient(UnregisterClientRequest): DefaultSuccess = 2;
   /** Request the end device to stay in Active Mode for an additional ActiveModeThreshold */
-  command access(invoke: manage) StayActiveRequest(): StayActiveResponse = 3;
+  command access(invoke: manage) StayActiveRequest(StayActiveRequestRequest): StayActiveResponse = 3;
 }
 
 endpoint 0 {
diff --git a/examples/light-switch-app/light-switch-common/light-switch-app.matter b/examples/light-switch-app/light-switch-common/light-switch-app.matter
index 90e6058..b96bf90 100644
--- a/examples/light-switch-app/light-switch-common/light-switch-app.matter
+++ b/examples/light-switch-app/light-switch-common/light-switch-app.matter
@@ -2007,6 +2007,10 @@
     optional octet_string<16> verificationKey = 1;
   }
 
+  request struct StayActiveRequestRequest {
+    int32u stayActiveDuration = 0;
+  }
+
   response struct StayActiveResponse = 4 {
     int32u promisedActiveDuration = 0;
   }
@@ -2016,7 +2020,7 @@
   /** Unregister a client from an end device */
   fabric command access(invoke: manage) UnregisterClient(UnregisterClientRequest): DefaultSuccess = 2;
   /** Request the end device to stay in Active Mode for an additional ActiveModeThreshold */
-  command access(invoke: manage) StayActiveRequest(): StayActiveResponse = 3;
+  command access(invoke: manage) StayActiveRequest(StayActiveRequestRequest): StayActiveResponse = 3;
 }
 
 /** Attributes and commands for scene configuration and manipulation. */
diff --git a/examples/light-switch-app/qpg/zap/switch.matter b/examples/light-switch-app/qpg/zap/switch.matter
index b7f217d..0475721 100644
--- a/examples/light-switch-app/qpg/zap/switch.matter
+++ b/examples/light-switch-app/qpg/zap/switch.matter
@@ -1804,6 +1804,10 @@
     optional octet_string<16> verificationKey = 1;
   }
 
+  request struct StayActiveRequestRequest {
+    int32u stayActiveDuration = 0;
+  }
+
   response struct StayActiveResponse = 4 {
     int32u promisedActiveDuration = 0;
   }
@@ -1813,7 +1817,7 @@
   /** Unregister a client from an end device */
   fabric command access(invoke: manage) UnregisterClient(UnregisterClientRequest): DefaultSuccess = 2;
   /** Request the end device to stay in Active Mode for an additional ActiveModeThreshold */
-  command access(invoke: manage) StayActiveRequest(): StayActiveResponse = 3;
+  command access(invoke: manage) StayActiveRequest(StayActiveRequestRequest): StayActiveResponse = 3;
 }
 
 /** Attributes and commands for scene configuration and manipulation. */
diff --git a/examples/lit-icd-app/lit-icd-common/lit-icd-server-app.matter b/examples/lit-icd-app/lit-icd-common/lit-icd-server-app.matter
index db80008..d4b7f3f 100644
--- a/examples/lit-icd-app/lit-icd-common/lit-icd-server-app.matter
+++ b/examples/lit-icd-app/lit-icd-common/lit-icd-server-app.matter
@@ -1465,6 +1465,10 @@
     optional octet_string<16> verificationKey = 1;
   }
 
+  request struct StayActiveRequestRequest {
+    int32u stayActiveDuration = 0;
+  }
+
   response struct StayActiveResponse = 4 {
     int32u promisedActiveDuration = 0;
   }
@@ -1474,7 +1478,7 @@
   /** Unregister a client from an end device */
   fabric command access(invoke: manage) UnregisterClient(UnregisterClientRequest): DefaultSuccess = 2;
   /** Request the end device to stay in Active Mode for an additional ActiveModeThreshold */
-  command access(invoke: manage) StayActiveRequest(): StayActiveResponse = 3;
+  command access(invoke: manage) StayActiveRequest(StayActiveRequestRequest): StayActiveResponse = 3;
 }
 
 endpoint 0 {
diff --git a/examples/lock-app/lock-common/lock-app.matter b/examples/lock-app/lock-common/lock-app.matter
index f07b85c..1b34782 100644
--- a/examples/lock-app/lock-common/lock-app.matter
+++ b/examples/lock-app/lock-common/lock-app.matter
@@ -1825,6 +1825,10 @@
     optional octet_string<16> verificationKey = 1;
   }
 
+  request struct StayActiveRequestRequest {
+    int32u stayActiveDuration = 0;
+  }
+
   response struct StayActiveResponse = 4 {
     int32u promisedActiveDuration = 0;
   }
@@ -1834,7 +1838,7 @@
   /** Unregister a client from an end device */
   fabric command access(invoke: manage) UnregisterClient(UnregisterClientRequest): DefaultSuccess = 2;
   /** Request the end device to stay in Active Mode for an additional ActiveModeThreshold */
-  command access(invoke: manage) StayActiveRequest(): StayActiveResponse = 3;
+  command access(invoke: manage) StayActiveRequest(StayActiveRequestRequest): StayActiveResponse = 3;
 }
 
 /** An interface to a generic way to secure a door */
diff --git a/examples/lock-app/lock-common/lock-app.zap b/examples/lock-app/lock-common/lock-app.zap
index c5201e7..8ec233c 100644
--- a/examples/lock-app/lock-common/lock-app.zap
+++ b/examples/lock-app/lock-common/lock-app.zap
@@ -2412,10 +2412,10 @@
               "side": "server",
               "type": "bitmap32",
               "included": 1,
-              "storageOption": "RAM",
+              "storageOption": "External",
               "singleton": 0,
               "bounded": 0,
-              "defaultValue": "0",
+              "defaultValue": null,
               "reportable": 1,
               "minInterval": 1,
               "maxInterval": 65534,
@@ -2428,10 +2428,10 @@
               "side": "server",
               "type": "int16u",
               "included": 1,
-              "storageOption": "RAM",
+              "storageOption": "External",
               "singleton": 0,
               "bounded": 0,
-              "defaultValue": "0x0002",
+              "defaultValue": null,
               "reportable": 1,
               "minInterval": 0,
               "maxInterval": 65344,
@@ -4939,6 +4939,7 @@
           "define": "ICD_MANAGEMENT_CLUSTER",
           "side": "server",
           "enabled": 1,
+          "commands": [],
           "attributes": [
             {
               "name": "IdleModeDuration",
diff --git a/examples/lock-app/qpg/zap/lock.matter b/examples/lock-app/qpg/zap/lock.matter
index 91ff769..9815def 100644
--- a/examples/lock-app/qpg/zap/lock.matter
+++ b/examples/lock-app/qpg/zap/lock.matter
@@ -1481,6 +1481,10 @@
     optional octet_string<16> verificationKey = 1;
   }
 
+  request struct StayActiveRequestRequest {
+    int32u stayActiveDuration = 0;
+  }
+
   response struct StayActiveResponse = 4 {
     int32u promisedActiveDuration = 0;
   }
@@ -1490,7 +1494,7 @@
   /** Unregister a client from an end device */
   fabric command access(invoke: manage) UnregisterClient(UnregisterClientRequest): DefaultSuccess = 2;
   /** Request the end device to stay in Active Mode for an additional ActiveModeThreshold */
-  command access(invoke: manage) StayActiveRequest(): StayActiveResponse = 3;
+  command access(invoke: manage) StayActiveRequest(StayActiveRequestRequest): StayActiveResponse = 3;
 }
 
 /** An interface to a generic way to secure a door */
diff --git a/examples/smoke-co-alarm-app/smoke-co-alarm-common/smoke-co-alarm-app.matter b/examples/smoke-co-alarm-app/smoke-co-alarm-common/smoke-co-alarm-app.matter
index 6c1938c..f318835 100644
--- a/examples/smoke-co-alarm-app/smoke-co-alarm-common/smoke-co-alarm-app.matter
+++ b/examples/smoke-co-alarm-app/smoke-co-alarm-common/smoke-co-alarm-app.matter
@@ -1801,6 +1801,10 @@
     optional octet_string<16> verificationKey = 1;
   }
 
+  request struct StayActiveRequestRequest {
+    int32u stayActiveDuration = 0;
+  }
+
   response struct StayActiveResponse = 4 {
     int32u promisedActiveDuration = 0;
   }
@@ -1810,7 +1814,7 @@
   /** Unregister a client from an end device */
   fabric command access(invoke: manage) UnregisterClient(UnregisterClientRequest): DefaultSuccess = 2;
   /** Request the end device to stay in Active Mode for an additional ActiveModeThreshold */
-  command access(invoke: manage) StayActiveRequest(): StayActiveResponse = 3;
+  command access(invoke: manage) StayActiveRequest(StayActiveRequestRequest): StayActiveResponse = 3;
 }
 
 /** This cluster provides an interface for observing and managing the state of smoke and CO alarms. */
diff --git a/examples/window-app/common/window-app.matter b/examples/window-app/common/window-app.matter
index 7e0053f..7f1b3d3 100644
--- a/examples/window-app/common/window-app.matter
+++ b/examples/window-app/common/window-app.matter
@@ -1899,6 +1899,10 @@
     optional octet_string<16> verificationKey = 1;
   }
 
+  request struct StayActiveRequestRequest {
+    int32u stayActiveDuration = 0;
+  }
+
   response struct StayActiveResponse = 4 {
     int32u promisedActiveDuration = 0;
   }
@@ -1908,7 +1912,7 @@
   /** Unregister a client from an end device */
   fabric command access(invoke: manage) UnregisterClient(UnregisterClientRequest): DefaultSuccess = 2;
   /** Request the end device to stay in Active Mode for an additional ActiveModeThreshold */
-  command access(invoke: manage) StayActiveRequest(): StayActiveResponse = 3;
+  command access(invoke: manage) StayActiveRequest(StayActiveRequestRequest): StayActiveResponse = 3;
 }
 
 /** Provides an interface for controlling and adjusting automatic window coverings. */
diff --git a/src/app/clusters/icd-management-server/icd-management-server.cpp b/src/app/clusters/icd-management-server/icd-management-server.cpp
index 27ecfe7..0f71345 100644
--- a/src/app/clusters/icd-management-server/icd-management-server.cpp
+++ b/src/app/clusters/icd-management-server/icd-management-server.cpp
@@ -364,14 +364,6 @@
 
 #endif // CHIP_CONFIG_ENABLE_ICD_CIP
 
-Status ICDManagementServer::StayActiveRequest(FabricIndex fabricIndex)
-{
-    // TODO: Implementent stay awake logic for end device
-    // https://github.com/project-chip/connectedhomeip/issues/24259
-    ICDNotifier::GetInstance().NotifyICDManagementEvent(ICDListener::ICDManagementEvents::kStayActiveRequestReceived);
-    return InteractionModel::Status::UnsupportedCommand;
-}
-
 void ICDManagementServer::Init(PersistentStorageDelegate & storage, Crypto::SymmetricKeystore * symmetricKeystore,
                                ICDConfigurationData & icdConfigurationData)
 {
@@ -433,10 +425,14 @@
 bool emberAfIcdManagementClusterStayActiveRequestCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
                                                           const Commands::StayActiveRequest::DecodableType & commandData)
 {
-    ICDManagementServer server;
-    InteractionModel::Status status = server.StayActiveRequest(commandObj->GetAccessingFabricIndex());
-
-    commandObj->AddStatus(commandPath, status);
+// Note: We only need this #if statement for platform examples that enable the ICD management server without building the sample
+// as an ICD. Since this is not spec compliant, we should remove this #if statement once we stop compiling the ICD management
+// server in those examples.
+#if CHIP_CONFIG_ENABLE_ICD_SERVER
+    IcdManagement::Commands::StayActiveResponse::Type response;
+    response.promisedActiveDuration = Server::GetInstance().GetICDManager().StayActiveRequest(commandData.stayActiveDuration);
+    commandObj->AddResponse(commandPath, response);
+#endif // CHIP_CONFIG_ENABLE_ICD_SERVER
     return true;
 }
 
diff --git a/src/app/clusters/icd-management-server/icd-management-server.h b/src/app/clusters/icd-management-server/icd-management-server.h
index 4462cb9..5c6b838 100644
--- a/src/app/clusters/icd-management-server/icd-management-server.h
+++ b/src/app/clusters/icd-management-server/icd-management-server.h
@@ -68,8 +68,6 @@
                      const chip::app::Clusters::IcdManagement::Commands::UnregisterClient::DecodableType & commandData);
 #endif // CHIP_CONFIG_ENABLE_ICD_CIP
 
-    chip::Protocols::InteractionModel::Status StayActiveRequest(chip::FabricIndex fabricIndex);
-
 private:
 #if CHIP_CONFIG_ENABLE_ICD_CIP
     /**
diff --git a/src/app/icd/server/ICDConfigurationData.h b/src/app/icd/server/ICDConfigurationData.h
index 3fb3e9a..8b87d09 100644
--- a/src/app/icd/server/ICDConfigurationData.h
+++ b/src/app/icd/server/ICDConfigurationData.h
@@ -61,6 +61,8 @@
 
     System::Clock::Milliseconds16 GetActiveModeThreshold() { return mActiveThreshold; }
 
+    System::Clock::Milliseconds32 GetGuaranteedStayActiveDuration() { return kGuaranteedStayActiveDuration; }
+
     Protocols::SecureChannel::CheckInCounter & GetICDCounter() { return mICDCounter; }
 
     uint16_t GetClientsSupportedPerFabric() { return mFabricClientsSupported; }
@@ -123,6 +125,9 @@
 
     static constexpr System::Clock::Seconds32 kMaxIdleModeDuration = System::Clock::Seconds32(18 * kSecondsPerHour);
     static constexpr System::Clock::Seconds32 kMinIdleModeDuration = System::Clock::Seconds32(1);
+    // As defined in the spec, the maximum guaranteed duration for the StayActiveDuration is 30s  "Matter Application
+    // Clusters: 9.17.7.5.1. PromisedActiveDuration Field"
+    static constexpr System::Clock::Milliseconds32 kGuaranteedStayActiveDuration = System::Clock::Milliseconds32(30000);
 
     static_assert((CHIP_CONFIG_ICD_IDLE_MODE_DURATION_SEC) <= kMaxIdleModeDuration.count(),
                   "Spec requires the IdleModeDuration to be equal or inferior to 64800s.");
diff --git a/src/app/icd/server/ICDManager.cpp b/src/app/icd/server/ICDManager.cpp
index 057cc08..9317377 100644
--- a/src/app/icd/server/ICDManager.cpp
+++ b/src/app/icd/server/ICDManager.cpp
@@ -120,6 +120,22 @@
 #endif // !CONFIG_BUILD_FOR_HOST_UNIT_TEST
 }
 
+uint32_t ICDManager::StayActiveRequest(uint32_t stayActiveDuration)
+{
+    // This should only be called when the device is in ActiveMode
+    VerifyOrReturnValue(mOperationalState == OperationalState::ActiveMode, 0);
+
+    uint32_t promisedActiveDuration =
+        std::min(ICDConfigurationData::GetInstance().GetGuaranteedStayActiveDuration().count(), stayActiveDuration);
+
+    // If the device is already in ActiveMode, we need to extend the active mode duration
+    // for whichever is smallest between 30000 milliseconds and stayActiveDuration, taking in account the remaining active time.
+    ExtendActiveMode(System::Clock::Milliseconds16(promisedActiveDuration));
+    promisedActiveDuration = DeviceLayer::SystemLayer().GetRemainingTime(OnActiveModeDone, this).count();
+
+    return promisedActiveDuration;
+}
+
 #if CHIP_CONFIG_ENABLE_ICD_CIP
 void ICDManager::SendCheckInMsgs()
 {
@@ -366,17 +382,7 @@
         }
         else
         {
-            Milliseconds16 activeModeThreshold = ICDConfigurationData::GetInstance().GetActiveModeThreshold();
-            DeviceLayer::SystemLayer().ExtendTimerTo(activeModeThreshold, OnActiveModeDone, this);
-
-            Milliseconds32 activeModeJitterThreshold = Milliseconds32(ICD_ACTIVE_TIME_JITTER_MS);
-            activeModeJitterThreshold =
-                (activeModeThreshold >= activeModeJitterThreshold) ? activeModeThreshold - activeModeJitterThreshold : kZero;
-
-            if (!mTransitionToIdleCalled)
-            {
-                DeviceLayer::SystemLayer().ExtendTimerTo(activeModeJitterThreshold, OnTransitionToIdle, this);
-            }
+            ExtendActiveMode(ICDConfigurationData::GetInstance().GetActiveModeThreshold());
         }
     }
 }
@@ -521,11 +527,6 @@
     case ICDManagementEvents::kTableUpdated:
         this->UpdateICDMode();
         break;
-
-    case ICDManagementEvents::kStayActiveRequestReceived:
-        // TODO : Implement the StayActiveRequest
-        // https://github.com/project-chip/connectedhomeip/issues/24259
-        break;
     default:
         break;
     }
@@ -540,6 +541,19 @@
     this->UpdateOperationState(OperationalState::ActiveMode);
 }
 
+void ICDManager::ExtendActiveMode(Milliseconds16 extendDuration)
+{
+    DeviceLayer::SystemLayer().ExtendTimerTo(extendDuration, OnActiveModeDone, this);
+
+    Milliseconds32 activeModeJitterThreshold = Milliseconds32(ICD_ACTIVE_TIME_JITTER_MS);
+    activeModeJitterThreshold = (extendDuration >= activeModeJitterThreshold) ? extendDuration - activeModeJitterThreshold : kZero;
+
+    if (!mTransitionToIdleCalled)
+    {
+        DeviceLayer::SystemLayer().ExtendTimerTo(activeModeJitterThreshold, OnTransitionToIdle, this);
+    }
+}
+
 ICDManager::ObserverPointer * ICDManager::RegisterObserver(ICDStateObserver * observer)
 {
     return mStateObserverPool.CreateObject(observer);
diff --git a/src/app/icd/server/ICDManager.h b/src/app/icd/server/ICDManager.h
index 396e6be..5e391c1 100644
--- a/src/app/icd/server/ICDManager.h
+++ b/src/app/icd/server/ICDManager.h
@@ -109,6 +109,14 @@
     void postObserverEvent(ObserverEventType event);
     OperationalState GetOperationalState() { return mOperationalState; }
 
+    /**
+     * @brief Ensures that the remaining Active Mode duration is at least the smaller of 30000 milliseconds and stayActiveDuration.
+     *
+     * @param stayActiveDuration The duration (in milliseconds) requested by the client to stay in Active Mode
+     * @return The duration (in milliseconds) the device will stay in Active Mode
+     */
+    uint32_t StayActiveRequest(uint32_t stayActiveDuration);
+
 #if CHIP_CONFIG_ENABLE_ICD_CIP
     void SendCheckInMsgs();
 
@@ -131,6 +139,12 @@
     void OnSubscriptionReport() override;
 
 protected:
+    /**
+     * @brief Hepler function that extends the Active Mode duration as well as the Active Mode Jitter timer for the transition to
+     * iddle mode.
+     */
+    void ExtendActiveMode(System::Clock::Milliseconds16 extendDuration);
+
     friend class TestICDManager;
 
     static void OnIdleModeDone(System::Layer * aLayer, void * appState);
diff --git a/src/app/icd/server/ICDNotifier.h b/src/app/icd/server/ICDNotifier.h
index 1223732..5db1450 100644
--- a/src/app/icd/server/ICDNotifier.h
+++ b/src/app/icd/server/ICDNotifier.h
@@ -48,8 +48,7 @@
 
     enum class ICDManagementEvents : uint8_t
     {
-        kTableUpdated              = 0x01,
-        kStayActiveRequestReceived = 0x02,
+        kTableUpdated = 0x01,
     };
 
     using KeepActiveFlags = BitFlags<KeepActiveFlagsValues>;
diff --git a/src/app/tests/TestICDManager.cpp b/src/app/tests/TestICDManager.cpp
index da549ff..ba311f8 100644
--- a/src/app/tests/TestICDManager.cpp
+++ b/src/app/tests/TestICDManager.cpp
@@ -540,6 +540,83 @@
         // After the init we should be in Idle mode
         NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
     }
+
+    /* Test that verifies the logic of the ICDManager when it receives a StayActiveRequest*/
+    static void TestICDMStayActive(nlTestSuite * aSuite, void * aContext)
+    {
+        TestContext * ctx                    = static_cast<TestContext *>(aContext);
+        ICDNotifier notifier                 = ICDNotifier::GetInstance();
+        ICDConfigurationData & icdConfigData = ICDConfigurationData::GetInstance();
+
+        // Verify That ICDManager starts in Idle
+        NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
+
+        // Trigger a subscription report. Put the ICD manager into active mode.
+        notifier.NotifySubscriptionReport();
+        NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
+
+        // Advance time by the ActiveModeDuration - 1
+        AdvanceClockAndRunEventLoop(ctx, icdConfigData.GetActiveModeDuration() - 1_ms32);
+        // Confirm ICD manager is in active mode
+        NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
+
+        uint32_t stayActiveRequestedMs = 20000;
+        // Send a stay active request for 20 seconds
+        uint32_t stayActivePromisedMs = ctx->mICDManager.StayActiveRequest(stayActiveRequestedMs);
+        // confirm the promised time is the same as the requested time
+        NL_TEST_ASSERT(aSuite, stayActivePromisedMs == stayActiveRequestedMs);
+
+        // Advance time by the duration of the stay stayActiveRequestedMs - 1 ms
+        AdvanceClockAndRunEventLoop(ctx, System::Clock::Milliseconds32(stayActiveRequestedMs) - 1_ms32);
+        // Confirm ICD manager is in active mode
+        NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
+
+        // Advance time by 1ms and Confirm ICD manager is in idle mode
+        AdvanceClockAndRunEventLoop(ctx, 1_ms32);
+        NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
+
+        // Trigger a subscription report Put the ICD manager into active mode
+        notifier.NotifySubscriptionReport();
+        NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
+
+        // Advance time by the duration of the stay active request - 1 ms
+        AdvanceClockAndRunEventLoop(ctx, icdConfigData.GetActiveModeDuration() - 1_ms32);
+        stayActiveRequestedMs = 35000;
+        // Send a stay active request for 35 seconds, which is higher than the maximum stay active duration (30 seconds)
+        stayActivePromisedMs = ctx->mICDManager.StayActiveRequest(stayActiveRequestedMs);
+        // confirm the promised time is the maximum stay active duration (30 seconds)
+        NL_TEST_ASSERT(aSuite, stayActivePromisedMs == 30000);
+
+        // Advance time by the duration of the max stay active duration - 1 ms
+        AdvanceClockAndRunEventLoop(ctx, System::Clock::Milliseconds32(30000) - 1_ms32);
+        NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
+
+        // Advance time by 1ms and Confirm ICD manager is in idle mode
+        AdvanceClockAndRunEventLoop(ctx, 1_ms32);
+        NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
+
+        // Trigger a subscription report Put the ICD manager into active mode
+        notifier.NotifySubscriptionReport();
+        NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
+
+        // Advance time by the duration of the stay active request - 1 ms
+        AdvanceClockAndRunEventLoop(ctx, icdConfigData.GetActiveModeDuration() - 1_ms32);
+        stayActiveRequestedMs = 30000;
+        // Send a stay active request for 30 seconds
+        stayActivePromisedMs = ctx->mICDManager.StayActiveRequest(stayActiveRequestedMs);
+        // confirm the promised time is the same as the requested time
+        NL_TEST_ASSERT(aSuite, stayActivePromisedMs == 30000);
+
+        // Advance time by the duration of the stay active request - 20000 ms
+        AdvanceClockAndRunEventLoop(ctx, System::Clock::Milliseconds32(stayActiveRequestedMs) - 20000_ms32);
+        // Confirm ICD manager is in active mode, we should have 20000 seconds left at that point
+        NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
+
+        stayActiveRequestedMs = 10000;
+        stayActivePromisedMs  = ctx->mICDManager.StayActiveRequest(stayActiveRequestedMs);
+        // confirm the promised time is 20000 since the device is already planing to stay active longer than the requested time
+        NL_TEST_ASSERT(aSuite, stayActivePromisedMs == 20000);
+    }
 };
 
 } // namespace app
@@ -556,6 +633,7 @@
     NL_TEST_DEF("TestKeepActivemodeRequests", TestICDManager::TestKeepActivemodeRequests),
     NL_TEST_DEF("TestICDMRegisterUnregisterEvents", TestICDManager::TestICDMRegisterUnregisterEvents),
     NL_TEST_DEF("TestICDCounter", TestICDManager::TestICDCounter),
+    NL_TEST_DEF("TestICDStayActive", TestICDManager::TestICDMStayActive),
     NL_TEST_SENTINEL(),
 };
 
diff --git a/src/app/tests/suites/TestIcdManagementCluster.yaml b/src/app/tests/suites/TestIcdManagementCluster.yaml
index d9d275b..3d00815 100644
--- a/src/app/tests/suites/TestIcdManagementCluster.yaml
+++ b/src/app/tests/suites/TestIcdManagementCluster.yaml
@@ -372,3 +372,82 @@
                 value: 102
       response:
           error: NOT_FOUND
+
+    - label:
+          "Wait for a little bit less than the active mode duration (10000ms)"
+      cluster: "DelayCommands"
+      command: "WaitForMs"
+      arguments:
+          values:
+              - name: "ms"
+                value: 9000
+
+    - label:
+          "StayActive Scenario 1: Confirm the promised active duration is
+          increased to a specific if a value less than 30000ms is requested and
+          the device does not intend to stay active longer"
+      command: "StayActiveRequest"
+      arguments:
+          values:
+              - name: "StayActiveDuration"
+                value: 20000
+      response:
+          values:
+              - name: "PromisedActiveDuration"
+                constraints:
+                    type: int32u
+                    minValue: 19500
+                    maxValue: 20500
+
+    - label:
+          "Wait for a little bit less than the new promied active mode duration
+          (20000ms)"
+      cluster: "DelayCommands"
+      command: "WaitForMs"
+      arguments:
+          values:
+              - name: "ms"
+                value: 19000
+
+    - label:
+          "StayActive Scenario 2: Confirm the promised active duration is
+          reduced to 30000ms if a value greater than 30000ms is requested"
+      command: "StayActiveRequest"
+      arguments:
+          values:
+              - name: "StayActiveDuration"
+                value: 35000
+      response:
+          values:
+              - name: "PromisedActiveDuration"
+                constraints:
+                    type: int32u
+                    minValue: 29500
+                    maxValue: 30500
+
+    - label:
+          "Wait for a 20000 less than the new promied active mode duration
+          (30000ms)"
+      cluster: "DelayCommands"
+      command: "WaitForMs"
+      arguments:
+          values:
+              - name: "ms"
+                value: 10000
+
+    - label:
+          "StayActive Scenario 3: confirm that the device ignores the request if
+          the device intends to stay active longer than the requested duration
+          we should have about 20000ms left here"
+      command: "StayActiveRequest"
+      arguments:
+          values:
+              - name: "StayActiveDuration"
+                value: 10000
+      response:
+          values:
+              - name: "PromisedActiveDuration"
+                constraints:
+                    type: int32u
+                    minValue: 19500
+                    maxValue: 20500
diff --git a/src/app/zap-templates/zcl/data-model/chip/icd-management-cluster.xml b/src/app/zap-templates/zcl/data-model/chip/icd-management-cluster.xml
index a266591..d68cc30 100644
--- a/src/app/zap-templates/zcl/data-model/chip/icd-management-cluster.xml
+++ b/src/app/zap-templates/zcl/data-model/chip/icd-management-cluster.xml
@@ -111,6 +111,7 @@
         <command source="client" code="0x03" name="StayActiveRequest" response="StayActiveResponse" optional="true">
             <description>Request the end device to stay in Active Mode for an additional ActiveModeThreshold</description>
             <access op="invoke" privilege="manage"/>
+            <arg name="StayActiveDuration" type="int32u" isNullable="false"/>
         </command>
 
         <command source="server" code="0x04" name="StayActiveResponse" optional="true" disableDefaultResponse="true">
diff --git a/src/controller/data_model/controller-clusters.matter b/src/controller/data_model/controller-clusters.matter
index 8611f4b..5dece39 100644
--- a/src/controller/data_model/controller-clusters.matter
+++ b/src/controller/data_model/controller-clusters.matter
@@ -2740,6 +2740,10 @@
     optional octet_string<16> verificationKey = 1;
   }
 
+  request struct StayActiveRequestRequest {
+    int32u stayActiveDuration = 0;
+  }
+
   response struct StayActiveResponse = 4 {
     int32u promisedActiveDuration = 0;
   }
@@ -2749,7 +2753,7 @@
   /** Unregister a client from an end device */
   fabric command access(invoke: manage) UnregisterClient(UnregisterClientRequest): DefaultSuccess = 2;
   /** Request the end device to stay in Active Mode for an additional ActiveModeThreshold */
-  command access(invoke: manage) StayActiveRequest(): StayActiveResponse = 3;
+  command access(invoke: manage) StayActiveRequest(StayActiveRequestRequest): StayActiveResponse = 3;
 }
 
 /** This cluster supports creating a simple timer functionality. */
diff --git a/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java b/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java
index 7025f3c..c55a45b 100644
--- a/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java
+++ b/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java
@@ -18170,14 +18170,18 @@
         }}, commandId, commandArgs, timedInvokeTimeoutMs);
     }
 
-    public void stayActiveRequest(StayActiveResponseCallback callback) {
-      stayActiveRequest(callback, 0);
+    public void stayActiveRequest(StayActiveResponseCallback callback, Long stayActiveDuration) {
+      stayActiveRequest(callback, stayActiveDuration, 0);
     }
 
-    public void stayActiveRequest(StayActiveResponseCallback callback, int timedInvokeTimeoutMs) {
+    public void stayActiveRequest(StayActiveResponseCallback callback, Long stayActiveDuration, int timedInvokeTimeoutMs) {
       final long commandId = 3L;
 
       ArrayList<StructElement> elements = new ArrayList<>();
+      final long stayActiveDurationFieldID = 0L;
+      BaseTLVType stayActiveDurationtlvValue = new UIntType(stayActiveDuration);
+      elements.add(new StructElement(stayActiveDurationFieldID, stayActiveDurationtlvValue));
+
       StructType commandArgs = new StructType(elements);
       invoke(new InvokeCallbackImpl(callback) {
           @Override
diff --git a/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java b/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java
index a1d43e3..3437b8a 100644
--- a/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java
+++ b/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java
@@ -5881,6 +5881,23 @@
                         }
                         throw new NoSuchFieldError();
                     }
+                }public enum StayActiveRequestCommandField {StayActiveDuration(0),;
+                    private final int id;
+                    StayActiveRequestCommandField(int id) {
+                        this.id = id;
+                    }
+
+                    public int getID() {
+                        return id;
+                    }
+                    public static StayActiveRequestCommandField value(int id) throws NoSuchFieldError {
+                        for (StayActiveRequestCommandField field : StayActiveRequestCommandField.values()) {
+                        if (field.getID() == id) {
+                            return field;
+                        }
+                        }
+                        throw new NoSuchFieldError();
+                    }
                 }@Override
         public String getAttributeName(long id) throws NoSuchFieldError {
             return Attribute.value(id).toString();
diff --git a/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java b/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java
index 5162f97..4ded328 100644
--- a/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java
+++ b/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java
@@ -23352,10 +23352,16 @@
     icdManagementClusterInteractionInfoMap.put("unregisterClient", icdManagementunregisterClientInteractionInfo);
 
     Map<String, CommandParameterInfo> icdManagementstayActiveRequestCommandParams = new LinkedHashMap<String, CommandParameterInfo>();
+
+    CommandParameterInfo icdManagementstayActiveRequeststayActiveDurationCommandParameterInfo = new CommandParameterInfo("stayActiveDuration", Long.class, Long.class);
+    icdManagementstayActiveRequestCommandParams.put("stayActiveDuration",icdManagementstayActiveRequeststayActiveDurationCommandParameterInfo);
     InteractionInfo icdManagementstayActiveRequestInteractionInfo = new InteractionInfo(
       (cluster, callback, commandArguments) -> {
         ((ChipClusters.IcdManagementCluster) cluster)
           .stayActiveRequest((ChipClusters.IcdManagementCluster.StayActiveResponseCallback) callback
+           , (Long)
+             commandArguments.get("stayActiveDuration")
+
             );
         },
         () -> new DelegatedIcdManagementClusterStayActiveResponseCallback(),
diff --git a/src/controller/java/generated/java/matter/controller/cluster/clusters/IcdManagementCluster.kt b/src/controller/java/generated/java/matter/controller/cluster/clusters/IcdManagementCluster.kt
index 9421d92..008aa63 100644
--- a/src/controller/java/generated/java/matter/controller/cluster/clusters/IcdManagementCluster.kt
+++ b/src/controller/java/generated/java/matter/controller/cluster/clusters/IcdManagementCluster.kt
@@ -193,11 +193,17 @@
     logger.log(Level.FINE, "Invoke command succeeded: ${response}")
   }
 
-  suspend fun stayActiveRequest(timedInvokeTimeout: Duration? = null): StayActiveResponse {
+  suspend fun stayActiveRequest(
+    stayActiveDuration: UInt,
+    timedInvokeTimeout: Duration? = null
+  ): StayActiveResponse {
     val commandId: UInt = 3u
 
     val tlvWriter = TlvWriter()
     tlvWriter.startStructure(AnonymousTag)
+
+    val TAG_STAY_ACTIVE_DURATION_REQ: Int = 0
+    tlvWriter.put(ContextSpecificTag(TAG_STAY_ACTIVE_DURATION_REQ), stayActiveDuration)
     tlvWriter.endStructure()
 
     val request: InvokeRequest =
diff --git a/src/controller/python/chip/clusters/CHIPClusters.py b/src/controller/python/chip/clusters/CHIPClusters.py
index 853fd1f..3bc7158 100644
--- a/src/controller/python/chip/clusters/CHIPClusters.py
+++ b/src/controller/python/chip/clusters/CHIPClusters.py
@@ -4078,6 +4078,7 @@
                 "commandId": 0x00000003,
                 "commandName": "StayActiveRequest",
                 "args": {
+                    "stayActiveDuration": "int",
                 },
             },
         },
diff --git a/src/controller/python/chip/clusters/Objects.py b/src/controller/python/chip/clusters/Objects.py
index 3f5c116..cd411b9 100644
--- a/src/controller/python/chip/clusters/Objects.py
+++ b/src/controller/python/chip/clusters/Objects.py
@@ -14304,8 +14304,11 @@
             def descriptor(cls) -> ClusterObjectDescriptor:
                 return ClusterObjectDescriptor(
                     Fields=[
+                        ClusterObjectFieldDescriptor(Label="stayActiveDuration", Tag=0, Type=uint),
                     ])
 
+            stayActiveDuration: 'uint' = 0
+
         @dataclass
         class StayActiveResponse(ClusterCommand):
             cluster_id: typing.ClassVar[int] = 0x00000046
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h
index 9f7d6f8..6232b64 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h
@@ -4491,9 +4491,7 @@
  *
  * Request the end device to stay in Active Mode for an additional ActiveModeThreshold
  */
-- (void)stayActiveRequestWithParams:(MTRICDManagementClusterStayActiveRequestParams * _Nullable)params completion:(void (^)(MTRICDManagementClusterStayActiveResponseParams * _Nullable data, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE;
-- (void)stayActiveRequestWithCompletion:(void (^)(MTRICDManagementClusterStayActiveResponseParams * _Nullable data, NSError * _Nullable error))completion
-    MTR_PROVISIONALLY_AVAILABLE;
+- (void)stayActiveRequestWithParams:(MTRICDManagementClusterStayActiveRequestParams *)params completion:(void (^)(MTRICDManagementClusterStayActiveResponseParams * _Nullable data, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE;
 
 - (void)readAttributeIdleModeDurationWithCompletion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE;
 - (void)subscribeAttributeIdleModeDurationWithParams:(MTRSubscribeParams *)params
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm
index 8cca4c8..b375488 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm
@@ -37049,11 +37049,7 @@
                                              queue:self.callbackQueue
                                         completion:responseHandler];
 }
-- (void)stayActiveRequestWithCompletion:(void (^)(MTRICDManagementClusterStayActiveResponseParams * _Nullable data, NSError * _Nullable error))completion
-{
-    [self stayActiveRequestWithParams:nil completion:completion];
-}
-- (void)stayActiveRequestWithParams:(MTRICDManagementClusterStayActiveRequestParams * _Nullable)params completion:(void (^)(MTRICDManagementClusterStayActiveResponseParams * _Nullable data, NSError * _Nullable error))completion
+- (void)stayActiveRequestWithParams:(MTRICDManagementClusterStayActiveRequestParams *)params completion:(void (^)(MTRICDManagementClusterStayActiveResponseParams * _Nullable data, NSError * _Nullable error))completion
 {
     if (params == nil) {
         params = [[MTRICDManagementClusterStayActiveRequestParams
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRClusters.h b/src/darwin/Framework/CHIP/zap-generated/MTRClusters.h
index d8bc645..7ce97cf 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRClusters.h
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRClusters.h
@@ -2090,9 +2090,7 @@
 
 - (void)registerClientWithParams:(MTRICDManagementClusterRegisterClientParams *)params expectedValues:(NSArray<NSDictionary<NSString *, id> *> * _Nullable)expectedDataValueDictionaries expectedValueInterval:(NSNumber * _Nullable)expectedValueIntervalMs completion:(void (^)(MTRICDManagementClusterRegisterClientResponseParams * _Nullable data, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE;
 - (void)unregisterClientWithParams:(MTRICDManagementClusterUnregisterClientParams *)params expectedValues:(NSArray<NSDictionary<NSString *, id> *> * _Nullable)expectedDataValueDictionaries expectedValueInterval:(NSNumber * _Nullable)expectedValueIntervalMs completion:(MTRStatusCompletion)completion MTR_PROVISIONALLY_AVAILABLE;
-- (void)stayActiveRequestWithParams:(MTRICDManagementClusterStayActiveRequestParams * _Nullable)params expectedValues:(NSArray<NSDictionary<NSString *, id> *> * _Nullable)expectedDataValueDictionaries expectedValueInterval:(NSNumber * _Nullable)expectedValueIntervalMs completion:(void (^)(MTRICDManagementClusterStayActiveResponseParams * _Nullable data, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE;
-- (void)stayActiveRequestWithExpectedValues:(NSArray<NSDictionary<NSString *, id> *> * _Nullable)expectedValues expectedValueInterval:(NSNumber * _Nullable)expectedValueIntervalMs completion:(void (^)(MTRICDManagementClusterStayActiveResponseParams * _Nullable data, NSError * _Nullable error))completion
-    MTR_PROVISIONALLY_AVAILABLE;
+- (void)stayActiveRequestWithParams:(MTRICDManagementClusterStayActiveRequestParams *)params expectedValues:(NSArray<NSDictionary<NSString *, id> *> * _Nullable)expectedDataValueDictionaries expectedValueInterval:(NSNumber * _Nullable)expectedValueIntervalMs completion:(void (^)(MTRICDManagementClusterStayActiveResponseParams * _Nullable data, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE;
 
 - (NSDictionary<NSString *, id> * _Nullable)readAttributeIdleModeDurationWithParams:(MTRReadParams * _Nullable)params MTR_PROVISIONALLY_AVAILABLE;
 
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm b/src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm
index 2374d7f..db27925 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm
@@ -6080,11 +6080,7 @@
                                         completion:responseHandler];
 }
 
-- (void)stayActiveRequestWithExpectedValues:(NSArray<NSDictionary<NSString *, id> *> *)expectedValues expectedValueInterval:(NSNumber *)expectedValueIntervalMs completion:(void (^)(MTRICDManagementClusterStayActiveResponseParams * _Nullable data, NSError * _Nullable error))completion
-{
-    [self stayActiveRequestWithParams:nil expectedValues:expectedValues expectedValueInterval:expectedValueIntervalMs completion:completion];
-}
-- (void)stayActiveRequestWithParams:(MTRICDManagementClusterStayActiveRequestParams * _Nullable)params expectedValues:(NSArray<NSDictionary<NSString *, id> *> * _Nullable)expectedValues expectedValueInterval:(NSNumber * _Nullable)expectedValueIntervalMs completion:(void (^)(MTRICDManagementClusterStayActiveResponseParams * _Nullable data, NSError * _Nullable error))completion
+- (void)stayActiveRequestWithParams:(MTRICDManagementClusterStayActiveRequestParams *)params expectedValues:(NSArray<NSDictionary<NSString *, id> *> * _Nullable)expectedValues expectedValueInterval:(NSNumber * _Nullable)expectedValueIntervalMs completion:(void (^)(MTRICDManagementClusterStayActiveResponseParams * _Nullable data, NSError * _Nullable error))completion
 {
     if (params == nil) {
         params = [[MTRICDManagementClusterStayActiveRequestParams
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h
index 8f2b070..ccd23b3 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h
@@ -3697,6 +3697,8 @@
 
 MTR_PROVISIONALLY_AVAILABLE
 @interface MTRICDManagementClusterStayActiveRequestParams : NSObject <NSCopying>
+
+@property (nonatomic, copy) NSNumber * _Nonnull stayActiveDuration MTR_PROVISIONALLY_AVAILABLE;
 /**
  * Controls whether the command is a timed command (using Timed Invoke).
  *
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm
index 60757c9..3105c9a 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm
@@ -9783,6 +9783,8 @@
 - (instancetype)init
 {
     if (self = [super init]) {
+
+        _stayActiveDuration = @(0);
         _timedInvokeTimeoutMs = nil;
         _serverSideProcessingTimeout = nil;
     }
@@ -9793,6 +9795,7 @@
 {
     auto other = [[MTRICDManagementClusterStayActiveRequestParams alloc] init];
 
+    other.stayActiveDuration = self.stayActiveDuration;
     other.timedInvokeTimeoutMs = self.timedInvokeTimeoutMs;
     other.serverSideProcessingTimeout = self.serverSideProcessingTimeout;
 
@@ -9801,7 +9804,7 @@
 
 - (NSString *)description
 {
-    NSString * descriptionString = [NSString stringWithFormat:@"<%@: >", NSStringFromClass([self class])];
+    NSString * descriptionString = [NSString stringWithFormat:@"<%@: stayActiveDuration:%@; >", NSStringFromClass([self class]), _stayActiveDuration];
     return descriptionString;
 }
 
@@ -9813,6 +9816,9 @@
 {
     chip::app::Clusters::IcdManagement::Commands::StayActiveRequest::Type encodableStruct;
     ListFreer listFreer;
+    {
+        encodableStruct.stayActiveDuration = self.stayActiveDuration.unsignedIntValue;
+    }
 
     auto buffer = chip::System::PacketBufferHandle::New(chip::System::PacketBuffer::kMaxSizeWithoutReserve, 0);
     if (buffer.IsNull()) {
diff --git a/src/system/SystemLayer.h b/src/system/SystemLayer.h
index 427bf00..d91a80d 100644
--- a/src/system/SystemLayer.h
+++ b/src/system/SystemLayer.h
@@ -142,6 +142,9 @@
      *   This method searches for the timer matching the provided parameters.
      *   and returns whether it is still "running" and waiting to trigger or not.
      *
+     *   @note This is used to verify by how long the ExtendTimer method extends the timer, as it may ignore an extension request
+     *        if it is shorter than the current timer's remaining time.
+     *
      *   @param[in]  onComplete         A pointer to the function called when timer expires.
      *   @param[in]  appState           A pointer to the application state object used when timer expires.
      *
@@ -151,6 +154,17 @@
     virtual bool IsTimerActive(TimerCompleteCallback onComplete, void * appState) = 0;
 
     /**
+     * @brief
+     *   This method searches for the timer matching the provided parameters
+     *   and returns the remaining time left before it expires.
+     *   @param[in]  onComplete         A pointer to the function called when timer expires.
+     *   @param[in]  appState           A pointer to the application state object used when timer expires.
+     *
+     *  @return The remaining time left before the timer expires.
+     */
+    virtual Clock::Timeout GetRemainingTime(TimerCompleteCallback onComplete, void * appState) = 0;
+
+    /**
      * @brief This method cancels a one-shot timer, started earlier through @p StartTimer().  This method must
      *        be called while in the Matter context (from the Matter event loop, or while holding the Matter
      *        stack lock).
diff --git a/src/system/SystemLayerImplFreeRTOS.cpp b/src/system/SystemLayerImplFreeRTOS.cpp
index cfc8e09..59265f6 100644
--- a/src/system/SystemLayerImplFreeRTOS.cpp
+++ b/src/system/SystemLayerImplFreeRTOS.cpp
@@ -96,6 +96,11 @@
     return (mTimerList.GetRemainingTime(onComplete, appState) > Clock::kZero);
 }
 
+Clock::Timeout LayerImplFreeRTOS::GetRemainingTime(TimerCompleteCallback onComplete, void * appState)
+{
+    return mTimerList.GetRemainingTime(onComplete, appState);
+}
+
 void LayerImplFreeRTOS::CancelTimer(TimerCompleteCallback onComplete, void * appState)
 {
     assertChipStackLockedByCurrentThread();
diff --git a/src/system/SystemLayerImplFreeRTOS.h b/src/system/SystemLayerImplFreeRTOS.h
index 2e7401d..fe0e9be 100644
--- a/src/system/SystemLayerImplFreeRTOS.h
+++ b/src/system/SystemLayerImplFreeRTOS.h
@@ -42,6 +42,7 @@
     CHIP_ERROR StartTimer(Clock::Timeout delay, TimerCompleteCallback onComplete, void * appState) override;
     CHIP_ERROR ExtendTimerTo(Clock::Timeout delay, TimerCompleteCallback onComplete, void * appState) override;
     bool IsTimerActive(TimerCompleteCallback onComplete, void * appState) override;
+    Clock::Timeout GetRemainingTime(TimerCompleteCallback onComplete, void * appState) override;
     void CancelTimer(TimerCompleteCallback onComplete, void * appState) override;
     CHIP_ERROR ScheduleWork(TimerCompleteCallback onComplete, void * appState) override;
 
diff --git a/src/system/SystemLayerImplSelect.cpp b/src/system/SystemLayerImplSelect.cpp
index 7ae25a0..86ff1cb 100644
--- a/src/system/SystemLayerImplSelect.cpp
+++ b/src/system/SystemLayerImplSelect.cpp
@@ -255,6 +255,11 @@
     return timerIsActive;
 }
 
+Clock::Timeout LayerImplSelect::GetRemainingTime(TimerCompleteCallback onComplete, void * appState)
+{
+    return mTimerList.GetRemainingTime(onComplete, appState);
+}
+
 void LayerImplSelect::CancelTimer(TimerCompleteCallback onComplete, void * appState)
 {
     assertChipStackLockedByCurrentThread();
diff --git a/src/system/SystemLayerImplSelect.h b/src/system/SystemLayerImplSelect.h
index 060657e..1bab3db 100644
--- a/src/system/SystemLayerImplSelect.h
+++ b/src/system/SystemLayerImplSelect.h
@@ -65,6 +65,7 @@
     CHIP_ERROR StartTimer(Clock::Timeout delay, TimerCompleteCallback onComplete, void * appState) override;
     CHIP_ERROR ExtendTimerTo(Clock::Timeout delay, TimerCompleteCallback onComplete, void * appState) override;
     bool IsTimerActive(TimerCompleteCallback onComplete, void * appState) override;
+    Clock::Timeout GetRemainingTime(TimerCompleteCallback onComplete, void * appState) override;
     void CancelTimer(TimerCompleteCallback onComplete, void * appState) override;
     CHIP_ERROR ScheduleWork(TimerCompleteCallback onComplete, void * appState) override;
 
diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp
index fc5a2bd..32b6263 100644
--- a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp
+++ b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp
@@ -9794,6 +9794,7 @@
 CHIP_ERROR Type::Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const
 {
     DataModel::WrappedStructEncoder encoder{ aWriter, aTag };
+    encoder.Encode(to_underlying(Fields::kStayActiveDuration), stayActiveDuration);
     return encoder.Finalize();
 }
 
@@ -9807,6 +9808,19 @@
         {
             return std::get<CHIP_ERROR>(__element);
         }
+
+        CHIP_ERROR err              = CHIP_NO_ERROR;
+        const uint8_t __context_tag = std::get<uint8_t>(__element);
+
+        if (__context_tag == to_underlying(Fields::kStayActiveDuration))
+        {
+            err = DataModel::Decode(reader, stayActiveDuration);
+        }
+        else
+        {
+        }
+
+        ReturnErrorOnFailure(err);
     }
 }
 } // namespace StayActiveRequest.
diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h
index dfdf2e6..89c36f3 100644
--- a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h
+++ b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h
@@ -13302,6 +13302,7 @@
 namespace StayActiveRequest {
 enum class Fields : uint8_t
 {
+    kStayActiveDuration = 0,
 };
 
 struct Type
@@ -13311,6 +13312,8 @@
     static constexpr CommandId GetCommandId() { return Commands::StayActiveRequest::Id; }
     static constexpr ClusterId GetClusterId() { return Clusters::IcdManagement::Id; }
 
+    uint32_t stayActiveDuration = static_cast<uint32_t>(0);
+
     CHIP_ERROR Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const;
 
     using ResponseType = Clusters::IcdManagement::Commands::StayActiveResponse::DecodableType;
@@ -13324,6 +13327,7 @@
     static constexpr CommandId GetCommandId() { return Commands::StayActiveRequest::Id; }
     static constexpr ClusterId GetClusterId() { return Clusters::IcdManagement::Id; }
 
+    uint32_t stayActiveDuration = static_cast<uint32_t>(0);
     CHIP_ERROR Decode(TLV::TLVReader & reader);
 };
 }; // namespace StayActiveRequest
diff --git a/zzz_generated/chip-tool/zap-generated/cluster/Commands.h b/zzz_generated/chip-tool/zap-generated/cluster/Commands.h
index 6191eb2..8a3081e 100644
--- a/zzz_generated/chip-tool/zap-generated/cluster/Commands.h
+++ b/zzz_generated/chip-tool/zap-generated/cluster/Commands.h
@@ -4314,6 +4314,7 @@
     IcdManagementStayActiveRequest(CredentialIssuerCommands * credsIssuerConfig) :
         ClusterCommand("stay-active-request", credsIssuerConfig)
     {
+        AddArgument("StayActiveDuration", 0, UINT32_MAX, &mRequest.stayActiveDuration);
         ClusterCommand::AddArguments();
     }
 
diff --git a/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h b/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h
index 60843e7..be046cb 100644
--- a/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h
+++ b/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h
@@ -47653,6 +47653,9 @@
     IcdManagementStayActiveRequest()
         : ClusterCommand("stay-active-request")
     {
+#if MTR_ENABLE_PROVISIONAL
+        AddArgument("StayActiveDuration", 0, UINT32_MAX, &mRequest.stayActiveDuration);
+#endif // MTR_ENABLE_PROVISIONAL
         ClusterCommand::AddArguments();
     }
 
@@ -47667,6 +47670,9 @@
         __auto_type * cluster = [[MTRBaseClusterICDManagement alloc] initWithDevice:device endpointID:@(endpointId) queue:callbackQueue];
         __auto_type * params = [[MTRICDManagementClusterStayActiveRequestParams alloc] init];
         params.timedInvokeTimeoutMs = mTimedInteractionTimeoutMs.HasValue() ? [NSNumber numberWithUnsignedShort:mTimedInteractionTimeoutMs.Value()] : nil;
+#if MTR_ENABLE_PROVISIONAL
+        params.stayActiveDuration = [NSNumber numberWithUnsignedInt:mRequest.stayActiveDuration];
+#endif // MTR_ENABLE_PROVISIONAL
         uint16_t repeatCount = mRepeatCount.ValueOr(1);
         uint16_t __block responsesNeeded = repeatCount;
         while (repeatCount--) {
@@ -47693,6 +47699,7 @@
     }
 
 private:
+    chip::app::Clusters::IcdManagement::Commands::StayActiveRequest::Type mRequest;
 };
 
 #endif // MTR_ENABLE_PROVISIONAL