Add records of session establishment for subscription resumption (#31755)

* Add records of session establishment for subscription resumption

* Restyled by clang-format

* review changes

* Schedule subscription resumption when failing to establish the session in SubscriptionResumptionSessionEstablisher

* Add option to set subscription timeout resumption retry interval seconds for Linux app
Add cirque test for subscription resumption timeout

* Restyled by clang-format

* Restyled by autopep8

* Restyled by isort

* fix CI building

* Add test to the test list

* add subscription resumption restries number to SubscriptionInfo struct

* review changes

* make resumption retries persistent

* Restyled by clang-format

* ci build fixes

* try to fix cirque test

---------

Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp
index 71701f7..b92d76e 100644
--- a/examples/platform/linux/AppMain.cpp
+++ b/examples/platform/linux/AppMain.cpp
@@ -570,7 +570,12 @@
     chip::app::InteractionModelEngine::GetInstance()->SetHandlerCapacityForSubscriptions(
         LinuxDeviceOptions::GetInstance().subscriptionCapacity);
     chip::app::InteractionModelEngine::GetInstance()->SetForceHandlerQuota(true);
-#endif
+#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
+    // Set subscription time resumption retry interval seconds
+    chip::app::InteractionModelEngine::GetInstance()->SetSubscriptionTimeoutResumptionRetryIntervalSeconds(
+        LinuxDeviceOptions::GetInstance().subscriptionResumptionRetryIntervalSec);
+#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
+#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST
 
     // Now that the server has started and we are done with our startup logging,
     // log our discovery/onboarding information again so it's not lost in the
diff --git a/examples/platform/linux/Options.cpp b/examples/platform/linux/Options.cpp
index 8398c72..3017e77 100644
--- a/examples/platform/linux/Options.cpp
+++ b/examples/platform/linux/Options.cpp
@@ -90,7 +90,10 @@
 #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
     kDeviceOption_SubscriptionCapacity = 0x1024,
 #endif
-    kDeviceOption_WiFiSupports5g = 0x1025
+    kDeviceOption_WiFiSupports5g = 0x1025,
+#if CONFIG_BUILD_FOR_HOST_UNIT_TEST
+    kDeviceOption_SubscriptionResumptionRetryIntervalSec = 0x1026,
+#endif
 };
 
 constexpr unsigned kAppUsageLength = 64;
@@ -151,6 +154,7 @@
 #endif
 #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
     { "subscription-capacity", kArgumentRequired, kDeviceOption_SubscriptionCapacity },
+    { "subscription-resumption-retry-interval", kArgumentRequired, kDeviceOption_SubscriptionResumptionRetryIntervalSec },
 #endif
     {}
 };
@@ -280,6 +284,8 @@
 #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
     "  --subscription-capacity\n"
     "       Max number of subscriptions the device will allow\n"
+    "  --subscription-resumption-retry-interval\n"
+    "       subscription timeout resumption retry interval in seconds\n"
 #endif
     "\n";
 
@@ -547,6 +553,9 @@
     case kDeviceOption_SubscriptionCapacity:
         LinuxDeviceOptions::GetInstance().subscriptionCapacity = static_cast<int32_t>(atoi(aValue));
         break;
+    case kDeviceOption_SubscriptionResumptionRetryIntervalSec:
+        LinuxDeviceOptions::GetInstance().subscriptionResumptionRetryIntervalSec = static_cast<int32_t>(atoi(aValue));
+        break;
 #endif
     default:
         PrintArgError("%s: INTERNAL ERROR: Unhandled option: %s\n", aProgram, aName);
diff --git a/examples/platform/linux/Options.h b/examples/platform/linux/Options.h
index ca8082b..5063d03 100644
--- a/examples/platform/linux/Options.h
+++ b/examples/platform/linux/Options.h
@@ -73,7 +73,8 @@
     uint16_t rpcServerPort = 33000;
 #endif
 #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
-    int32_t subscriptionCapacity = CHIP_IM_MAX_NUM_SUBSCRIPTIONS;
+    int32_t subscriptionCapacity                   = CHIP_IM_MAX_NUM_SUBSCRIPTIONS;
+    int32_t subscriptionResumptionRetryIntervalSec = -1;
 #endif
     static LinuxDeviceOptions & GetInstance();
 };
diff --git a/scripts/tests/cirque_tests.sh b/scripts/tests/cirque_tests.sh
index 0f9fe22..67bf0f2 100755
--- a/scripts/tests/cirque_tests.sh
+++ b/scripts/tests/cirque_tests.sh
@@ -51,6 +51,7 @@
     "CommissioningWindowTest"
     "SubscriptionResumptionTest"
     "SubscriptionResumptionCapacityTest"
+    "SubscriptionResumptionTimeoutTest"
 )
 
 BOLD_GREEN_TEXT="\033[1;32m"
diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp
index 8e4b5a8..68ab452 100644
--- a/src/app/InteractionModelEngine.cpp
+++ b/src/app/InteractionModelEngine.cpp
@@ -389,7 +389,11 @@
     mReportingEngine.ResetReadHandlerTracker(&apReadObj);
 
     mReadHandlers.ReleaseObject(&apReadObj);
+    TryToResumeSubscriptions();
+}
 
+void InteractionModelEngine::TryToResumeSubscriptions()
+{
 #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
     if (!mSubscriptionResumptionScheduled && HasSubscriptionsToResume())
     {
@@ -398,8 +402,10 @@
         mpExchangeMgr->GetSessionManager()->SystemLayer()->StartTimer(
             System::Clock::Seconds32(timeTillNextSubscriptionResumptionSecs), ResumeSubscriptionsTimerCallback, this);
         mNumSubscriptionResumptionRetries++;
+        ChipLogProgress(InteractionModel, "Schedule subscription resumption when failing to establish session, Retries: %" PRIu32,
+                        mNumSubscriptionResumptionRetries);
     }
-#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
+#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
 }
 
 Status InteractionModelEngine::OnInvokeCommandRequest(Messaging::ExchangeContext * apExchangeContext,
@@ -1990,6 +1996,12 @@
 #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
 uint32_t InteractionModelEngine::ComputeTimeSecondsTillNextSubscriptionResumption()
 {
+#if CONFIG_BUILD_FOR_HOST_UNIT_TEST
+    if (mSubscriptionResumptionRetrySecondsOverride > 0)
+    {
+        return static_cast<uint32_t>(mSubscriptionResumptionRetrySecondsOverride);
+    }
+#endif
     if (mNumSubscriptionResumptionRetries > CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX)
     {
         return CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS;
diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h
index 1c05767..22ec1fb 100644
--- a/src/app/InteractionModelEngine.h
+++ b/src/app/InteractionModelEngine.h
@@ -354,6 +354,19 @@
     //
     void SetForceHandlerQuota(bool forceHandlerQuota) { mForceHandlerQuota = forceHandlerQuota; }
 
+#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
+    //
+    // Override the subscription timeout resumption retry interval seconds. The default retry interval will be
+    // 300s + GetFibonacciForIndex(retry_times) * 300s, which is too long for unit-tests.
+    //
+    // If -1 is passed in, no override is instituted and default behavior resumes.
+    //
+    void SetSubscriptionTimeoutResumptionRetryIntervalSeconds(int32_t seconds)
+    {
+        mSubscriptionResumptionRetrySecondsOverride = seconds;
+    }
+#endif
+
     //
     // When testing subscriptions using the high-level APIs in src/controller/ReadInteraction.h,
     // they don't provide for the ability to shut down those subscriptions after they've been established.
@@ -392,6 +405,8 @@
     void OnDone(CommandHandler & apCommandObj) override;
     void OnDone(ReadHandler & apReadObj) override;
 
+    void TryToResumeSubscriptions();
+
     ReadHandler::ApplicationCallback * GetAppCallback() override { return mpReadHandlerApplicationCallback; }
 
     InteractionModelEngine * GetInteractionModelEngine() override { return this; }
@@ -637,7 +652,10 @@
     // enforce such check based on the configured size. This flag is used for unit tests only, there is another compare time flag
     // CHIP_CONFIG_IM_FORCE_FABRIC_QUOTA_CHECK for stress tests.
     bool mForceHandlerQuota = false;
-#endif
+#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
+    int mSubscriptionResumptionRetrySecondsOverride = -1;
+#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
+#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST
 
 #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
     bool HasSubscriptionsToResume();
diff --git a/src/app/SimpleSubscriptionResumptionStorage.cpp b/src/app/SimpleSubscriptionResumptionStorage.cpp
index f6f9697..e233c18 100644
--- a/src/app/SimpleSubscriptionResumptionStorage.cpp
+++ b/src/app/SimpleSubscriptionResumptionStorage.cpp
@@ -46,6 +46,7 @@
 constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kAttributeIdTag;
 constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kEventIdTag;
 constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kEventPathTypeTag;
+constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kResumptionRetriesTag;
 
 SimpleSubscriptionResumptionStorage::SimpleSubscriptionInfoIterator::SimpleSubscriptionInfoIterator(
     SimpleSubscriptionResumptionStorage & storage) :
@@ -252,6 +253,18 @@
     }
     ReturnErrorOnFailure(reader.ExitContainer(eventsListType));
 
+#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
+    // If the reader cannot get resumption retries, set it to 0 for subscriptionInfo
+    if (reader.Next(kResumptionRetriesTag) == CHIP_NO_ERROR)
+    {
+        ReturnErrorOnFailure(reader.Get(subscriptionInfo.mResumptionRetries));
+    }
+    else
+    {
+        subscriptionInfo.mResumptionRetries = 0;
+    }
+#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
+
     ReturnErrorOnFailure(reader.ExitContainer(subscriptionContainerType));
 
     return CHIP_NO_ERROR;
@@ -307,6 +320,9 @@
         ReturnErrorOnFailure(writer.EndContainer(eventContainerType));
     }
     ReturnErrorOnFailure(writer.EndContainer(eventsListType));
+#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
+    ReturnErrorOnFailure(writer.Put(kResumptionRetriesTag, subscriptionInfo.mResumptionRetries));
+#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
 
     ReturnErrorOnFailure(writer.EndContainer(subscriptionContainerType));
 
diff --git a/src/app/SimpleSubscriptionResumptionStorage.h b/src/app/SimpleSubscriptionResumptionStorage.h
index 7cd6a0a..729f6f9 100644
--- a/src/app/SimpleSubscriptionResumptionStorage.h
+++ b/src/app/SimpleSubscriptionResumptionStorage.h
@@ -132,6 +132,7 @@
     static constexpr TLV::Tag kAttributeIdTag        = TLV::ContextTag(13);
     static constexpr TLV::Tag kEventIdTag            = TLV::ContextTag(14);
     static constexpr TLV::Tag kEventPathTypeTag      = TLV::ContextTag(16);
+    static constexpr TLV::Tag kResumptionRetriesTag  = TLV::ContextTag(17);
 
     PersistentStorageDelegate * mStorage;
     ObjectPool<SimpleSubscriptionInfoIterator, kIteratorsMax> mSubscriptionInfoIterators;
diff --git a/src/app/SubscriptionResumptionSessionEstablisher.cpp b/src/app/SubscriptionResumptionSessionEstablisher.cpp
index f0d5356..3c6b396 100644
--- a/src/app/SubscriptionResumptionSessionEstablisher.cpp
+++ b/src/app/SubscriptionResumptionSessionEstablisher.cpp
@@ -50,6 +50,9 @@
     mSubscriptionInfo.mMinInterval    = subscriptionInfo.mMinInterval;
     mSubscriptionInfo.mMaxInterval    = subscriptionInfo.mMaxInterval;
     mSubscriptionInfo.mFabricFiltered = subscriptionInfo.mFabricFiltered;
+#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
+    mSubscriptionInfo.mResumptionRetries = subscriptionInfo.mResumptionRetries;
+#endif
     // Copy the Attribute Paths and Event Paths
     if (subscriptionInfo.mAttributePaths.AllocatedSize() > 0)
     {
@@ -100,6 +103,15 @@
         return;
     }
     readHandler->OnSubscriptionResumed(sessionHandle, *establisher);
+#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
+    // Reset the resumption retries to 0 if subscription is resumed
+    subscriptionInfo.mResumptionRetries  = 0;
+    auto * subscriptionResumptionStorage = InteractionModelEngine::GetInstance()->GetSubscriptionResumptionStorage();
+    if (subscriptionResumptionStorage)
+    {
+        subscriptionResumptionStorage->Save(subscriptionInfo);
+    }
+#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
 }
 
 void SubscriptionResumptionSessionEstablisher::HandleDeviceConnectionFailure(void * context, const ScopedNodeId & peerId,
@@ -109,12 +121,25 @@
     SubscriptionResumptionStorage::SubscriptionInfo & subscriptionInfo = establisher->mSubscriptionInfo;
     ChipLogError(DataManagement, "Failed to establish CASE for subscription-resumption with error '%" CHIP_ERROR_FORMAT "'",
                  error.Format());
-    // If the device fails to establish the session, the subscriber might be offline and its subscription read client will
-    // be deleted when the device reconnect to the subscriber. This subscription will be never used again. So clean up
-    // the persistent subscription information storage.
     auto * subscriptionResumptionStorage = InteractionModelEngine::GetInstance()->GetSubscriptionResumptionStorage();
-    if (subscriptionResumptionStorage)
+    if (!subscriptionResumptionStorage)
     {
+        ChipLogError(DataManagement, "Failed to get subscription resumption storage");
+        return;
+    }
+#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
+    if (subscriptionInfo.mResumptionRetries <= CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX)
+    {
+        InteractionModelEngine::GetInstance()->TryToResumeSubscriptions();
+        subscriptionInfo.mResumptionRetries++;
+        subscriptionResumptionStorage->Save(subscriptionInfo);
+    }
+    else
+#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
+    {
+        // If the device fails to establish the session several times, the subscriber might be offline and its subscription
+        // read client will be deleted when the device reconnects to the subscriber. This subscription will be never used again.
+        // Clean up the persistent subscription information storage.
         subscriptionResumptionStorage->Delete(subscriptionInfo.mNodeId, subscriptionInfo.mFabricIndex,
                                               subscriptionInfo.mSubscriptionId);
     }
diff --git a/src/app/SubscriptionResumptionStorage.h b/src/app/SubscriptionResumptionStorage.h
index 1934259..74158ec 100644
--- a/src/app/SubscriptionResumptionStorage.h
+++ b/src/app/SubscriptionResumptionStorage.h
@@ -73,6 +73,9 @@
         NodeId mNodeId;
         FabricIndex mFabricIndex;
         SubscriptionId mSubscriptionId;
+#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
+        uint32_t mResumptionRetries;
+#endif
         uint16_t mMinInterval;
         uint16_t mMaxInterval;
         bool mFabricFiltered;
diff --git a/src/controller/python/test/test_scripts/subscription_resumption_timeout_test.py b/src/controller/python/test/test_scripts/subscription_resumption_timeout_test.py
new file mode 100755
index 0000000..1f6411f
--- /dev/null
+++ b/src/controller/python/test/test_scripts/subscription_resumption_timeout_test.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env python3
+
+#
+#    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.
+#
+
+# Commissioning test.
+
+import os
+import sys
+from optparse import OptionParser
+
+from base import BaseTestHelper, FailIfNot, TestFail, TestTimeout, logger
+
+TEST_DISCRIMINATOR = 3840
+TEST_SETUPPIN = 20202021
+
+TEST_ENDPOINT_ID = 0
+
+
+def main():
+    optParser = OptionParser()
+    optParser.add_option(
+        "-t",
+        "--timeout",
+        action="store",
+        dest="testTimeout",
+        default=90,
+        type='int',
+        help="The program will return with timeout after specified seconds.",
+        metavar="<timeout-second>",
+    )
+    optParser.add_option(
+        "-a",
+        "--address",
+        action="store",
+        dest="deviceAddress",
+        default='',
+        type='str',
+        help="Address of the device",
+        metavar="<device-addr>",
+    )
+    optParser.add_option(
+        "--nodeid",
+        action="store",
+        dest="nodeid",
+        default=1,
+        type=int,
+        help="The Node ID issued to the device",
+        metavar="<nodeid>"
+    )
+    optParser.add_option(
+        "--discriminator",
+        action="store",
+        dest="discriminator",
+        default=TEST_DISCRIMINATOR,
+        type=int,
+        help="Discriminator of the device",
+        metavar="<nodeid>"
+    )
+    optParser.add_option(
+        "--setuppin",
+        action="store",
+        dest="setuppin",
+        default=TEST_SETUPPIN,
+        type=int,
+        help="Setup PIN of the device",
+        metavar="<nodeid>"
+    )
+    optParser.add_option(
+        "-p",
+        "--paa-trust-store-path",
+        action="store",
+        dest="paaTrustStorePath",
+        default='',
+        type='str',
+        help="Path that contains valid and trusted PAA Root Certificates.",
+        metavar="<paa-trust-store-path>"
+    )
+
+    (options, remainingArgs) = optParser.parse_args(sys.argv[1:])
+
+    timeoutTicker = TestTimeout(options.testTimeout)
+    timeoutTicker.start()
+
+    test = BaseTestHelper(
+        nodeid=112233, paaTrustStorePath=options.paaTrustStorePath, testCommissioner=True)
+
+    FailIfNot(
+        test.TestOnNetworkCommissioning(options.discriminator, options.setuppin, options.nodeid, options.deviceAddress),
+        "Failed on on-network commissioing")
+    try:
+        test.devCtrl.ZCLSubscribeAttribute("BasicInformation", "NodeLabel", options.nodeid, TEST_ENDPOINT_ID, 1, 2,
+                                           keepSubscriptions=True, autoResubscribe=False)
+    except Exception as ex:
+        TestFail(f"Failed to subscribe attribute: {ex}")
+
+    timeoutTicker.stop()
+
+    logger.info("Test finished")
+
+    # TODO: Python device controller cannot be shutdown clean sometimes and will block on AsyncDNSResolverSockets shutdown.
+    # Call os._exit(0) to force close it.
+    os._exit(0)
+
+
+if __name__ == "__main__":
+    try:
+        main()
+    except Exception as ex:
+        logger.exception(ex)
+        TestFail("Exception occurred when running tests.")
diff --git a/src/test_driver/linux-cirque/SubscriptionResumptionTimeoutTest.py b/src/test_driver/linux-cirque/SubscriptionResumptionTimeoutTest.py
new file mode 100755
index 0000000..d916504
--- /dev/null
+++ b/src/test_driver/linux-cirque/SubscriptionResumptionTimeoutTest.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python3
+"""
+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.
+"""
+
+import logging
+import os
+import sys
+import time
+
+from helper.CHIPTestBase import CHIPVirtualHome
+
+"""
+Subscription Resumption Timeout Test to validate that the device will keep resuming subscription
+when it receives no status report from the controller.
+Steps for this test:
+    1. Subcription an attribute on the controller
+    2. Shutdown the controller
+    3. Verify that the server app with keep resuming the subscription
+"""
+
+logger = logging.getLogger('SubscriptionResumptionTest')
+logger.setLevel(logging.INFO)
+
+sh = logging.StreamHandler()
+sh.setFormatter(
+    logging.Formatter(
+        '%(asctime)s [%(name)s] %(levelname)s %(message)s'))
+logger.addHandler(sh)
+
+CHIP_PORT = 5540
+
+CIRQUE_URL = "http://localhost:5000"
+CHIP_REPO = os.path.join(os.path.abspath(
+    os.path.dirname(__file__)), "..", "..", "..")
+TEST_EXTPANID = "fedcba9876543210"
+TEST_DISCRIMINATOR = 3840
+MATTER_DEVELOPMENT_PAA_ROOT_CERTS = "credentials/development/paa-root-certs"
+TEST_END_DEVICE_APP = "chip-all-clusters-app"
+
+DEVICE_CONFIG = {
+    'device0': {
+        'type': 'MobileDevice',
+        'base_image': '@default',
+        'capability': ['TrafficControl', 'Mount'],
+        'rcp_mode': True,
+        'docker_network': 'Ipv6',
+        'traffic_control': {'latencyMs': 25},
+        "mount_pairs": [[CHIP_REPO, CHIP_REPO]],
+    },
+    'device1': {
+        'type': 'CHIPEndDevice',
+        'base_image': '@default',
+        'capability': ['Thread', 'TrafficControl', 'Mount'],
+        'rcp_mode': True,
+        'docker_network': 'Ipv6',
+        'traffic_control': {'latencyMs': 25},
+        "mount_pairs": [[CHIP_REPO, CHIP_REPO]],
+    }
+}
+
+
+class TestSubscriptionResumptionTimeout(CHIPVirtualHome):
+    def __init__(self, device_config):
+        super().__init__(CIRQUE_URL, device_config)
+        self.logger = logger
+
+    def setup(self):
+        self.initialize_home()
+
+    def test_routine(self):
+        self.run_subscription_resumption_timeout_test()
+
+    def run_subscription_resumption_timeout_test(self):
+        ethernet_ip = [device['description']['ipv6_addr'] for device in self.non_ap_devices
+                       if device['type'] == 'CHIPEndDevice'][0]
+        server_ids = [device['id'] for device in self.non_ap_devices
+                      if device['type'] == 'CHIPEndDevice']
+        req_ids = [device['id'] for device in self.non_ap_devices
+                   if device['type'] == 'MobileDevice']
+
+        server_device_id = server_ids[0]
+        self.execute_device_cmd(
+            server_device_id,
+            ("CHIPCirqueDaemon.py -- run gdb -batch -return-child-result -q -ex \"set pagination off\" "
+             "-ex run -ex \"thread apply all bt\" --args {} --thread --discriminator {} "
+             "--subscription-resumption-retry-interval 5").format(
+                os.path.join(CHIP_REPO, "out/debug/standalone", TEST_END_DEVICE_APP), TEST_DISCRIMINATOR))
+
+        self.reset_thread_devices(server_ids)
+
+        req_device_id = req_ids[0]
+
+        self.execute_device_cmd(req_device_id, "pip3 install {}".format(os.path.join(
+            CHIP_REPO, "out/debug/linux_x64_gcc/controller/python/chip_clusters-0.0-py3-none-any.whl")))
+        self.execute_device_cmd(req_device_id, "pip3 install {}".format(os.path.join(
+            CHIP_REPO, "out/debug/linux_x64_gcc/controller/python/chip_core-0.0-cp37-abi3-linux_x86_64.whl")))
+        self.execute_device_cmd(req_device_id, "pip3 install {}".format(os.path.join(
+            CHIP_REPO, "out/debug/linux_x64_gcc/controller/python/chip_repl-0.0-py3-none-any.whl")))
+
+        command = ("gdb -batch -return-child-result -q -ex run -ex \"thread apply all bt\" "
+                   "--args python3 {} -t 300 -a {} --paa-trust-store-path {}").format(
+            os.path.join(
+                CHIP_REPO, "src/controller/python/test/test_scripts/subscription_resumption_timeout_test.py"), ethernet_ip,
+            os.path.join(CHIP_REPO, MATTER_DEVELOPMENT_PAA_ROOT_CERTS))
+        ret = self.execute_device_cmd(req_device_id, command)
+
+        self.assertEqual(ret['return_code'], '0',
+                         "Test failed: non-zero return code")
+
+        # Wait for some time so that the sever will try to resume the subscription for several times
+        time.sleep(120)
+
+        # Check the device can resume subscriptions
+        self.logger.info("checking device log for {}".format(
+            self.get_device_pretty_id(server_device_id)))
+        self.assertTrue(self.sequenceMatch(self.get_device_log(server_device_id).decode('utf-8'), [
+            "Schedule subscription resumption when failing to establish session, Retries: 1",
+            "Schedule subscription resumption when failing to establish session, Retries: 2"]),
+            "SubscriptionResumption test failed: cannot find matching string from device {}".format(server_device_id))
+
+
+if __name__ == "__main__":
+    sys.exit(TestSubscriptionResumptionTimeout(DEVICE_CONFIG).run_test())