diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index 487c43e3182a5b..c9ff534f949dfe 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -55,6 +55,7 @@ buildconfig_header("app_buildconfig") { "CHIP_CONFIG_ENABLE_SESSION_RESUMPTION=${chip_enable_session_resumption}", "CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY=${chip_access_control_policy_logging_verbosity}", "CHIP_CONFIG_PERSIST_SUBSCRIPTIONS=${chip_persist_subscriptions}", + "CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION=${chip_subscription_timeout_resumption}", "CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE=${enable_eventlist_attribute}", "CHIP_CONFIG_ENABLE_ICD_SERVER=${chip_enable_icd_server}", ] diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index 6c32952135c902..569d0b0fdf4a4e 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -34,6 +34,7 @@ #include #include #include +#include namespace chip { namespace app { @@ -324,6 +325,17 @@ void InteractionModelEngine::OnDone(ReadHandler & apReadObj) mReportingEngine.ResetReadHandlerTracker(&apReadObj); mReadHandlers.ReleaseObject(&apReadObj); + +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + if (!mSubscriptionResumptionScheduled && HasSubscriptionsToResume()) + { + mSubscriptionResumptionScheduled = true; + auto timeTillNextResubscriptionSecs = ComputeTimeSecondsTillNextSubscriptionResumption(); + mpExchangeMgr->GetSessionManager()->SystemLayer()->StartTimer(System::Clock::Seconds32(timeTillNextResubscriptionSecs), + ResumeSubscriptionsTimerCallback, this); + mNumSubscriptionResumptionRetries++; + } +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS } Status InteractionModelEngine::OnInvokeCommandRequest(Messaging::ExchangeContext * apExchangeContext, @@ -1752,6 +1764,9 @@ CHIP_ERROR InteractionModelEngine::ResumeSubscriptions() { #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS ReturnErrorCodeIf(!mpSubscriptionResumptionStorage, CHIP_NO_ERROR); +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + ReturnErrorCodeIf(mSubscriptionResumptionScheduled, CHIP_NO_ERROR); +#endif // To avoid the case of a reboot loop causing rapid traffic generation / power consumption, subscription resumption should make // use of the persisted min-interval values, and wait before resumption. Ideally, each persisted subscription should wait their @@ -1776,6 +1791,9 @@ CHIP_ERROR InteractionModelEngine::ResumeSubscriptions() if (subscriptionsToResume) { +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + mSubscriptionResumptionScheduled = true; +#endif ChipLogProgress(InteractionModel, "Resuming %d subscriptions in %u seconds", subscriptionsToResume, minInterval); ReturnErrorOnFailure(mpExchangeMgr->GetSessionManager()->SystemLayer()->StartTimer(System::Clock::Seconds16(minInterval), ResumeSubscriptionsTimerCallback, this)); @@ -1794,6 +1812,10 @@ void InteractionModelEngine::ResumeSubscriptionsTimerCallback(System::Layer * ap #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS VerifyOrReturn(apAppState != nullptr); InteractionModelEngine * imEngine = static_cast(apAppState); +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + imEngine->mSubscriptionResumptionScheduled = false; + bool resumedSubscriptions = false; +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION SubscriptionResumptionStorage::SubscriptionInfo subscriptionInfo; auto * iterator = imEngine->mpSubscriptionResumptionStorage->IterateSubscriptions(); while (iterator->Next(subscriptionInfo)) @@ -1833,10 +1855,67 @@ void InteractionModelEngine::ResumeSubscriptionsTimerCallback(System::Layer * ap ChipLogProgress(InteractionModel, "Resuming subscriptionId %" PRIu32, subscriptionInfo.mSubscriptionId); handler->ResumeSubscription(*imEngine->mpCASESessionMgr, subscriptionInfo); +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + resumedSubscriptions = true; +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION } iterator->Release(); + +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + // If no persisted subscriptions needed resumption then all resumption retries are done + if (!resumedSubscriptions) + { + imEngine->mNumSubscriptionResumptionRetries = 0; + } +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS } +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION +uint32_t InteractionModelEngine::ComputeTimeSecondsTillNextSubscriptionResumption() +{ + if (mNumSubscriptionResumptionRetries > CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX) + { + return CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS; + } + + return CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MIN_RETRY_INTERVAL_SECS + + GetFibonacciForIndex(mNumSubscriptionResumptionRetries) * + CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_WAIT_TIME_MULTIPLIER_SECS; +} + +bool InteractionModelEngine::HasSubscriptionsToResume() +{ + VerifyOrReturnValue(mpSubscriptionResumptionStorage != nullptr, false); + + // Look through persisted subscriptions and see if any aren't already in mReadHandlers pool + SubscriptionResumptionStorage::SubscriptionInfo subscriptionInfo; + auto * iterator = mpSubscriptionResumptionStorage->IterateSubscriptions(); + bool foundSubscriptionToResume = false; + while (iterator->Next(subscriptionInfo)) + { + if (Loop::Break == mReadHandlers.ForEachActiveObject([&](ReadHandler * handler) { + SubscriptionId subscriptionId; + handler->GetSubscriptionId(subscriptionId); + if (subscriptionId == subscriptionInfo.mSubscriptionId) + { + return Loop::Break; + } + return Loop::Continue; + })) + { + continue; + } + + foundSubscriptionToResume = true; + break; + } + iterator->Release(); + + return foundSubscriptionToResume; +} +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + } // namespace app } // namespace chip diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h index b7de62e16ff0be..9e6ed36cce2396 100644 --- a/src/app/InteractionModelEngine.h +++ b/src/app/InteractionModelEngine.h @@ -366,6 +366,7 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, private: friend class reporting::Engine; friend class TestCommandInteraction; + friend class TestInteractionModelEngine; using Status = Protocols::InteractionModel::Status; void OnDone(CommandHandler & apCommandObj) override; @@ -613,6 +614,13 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, bool mForceHandlerQuota = false; #endif +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + bool HasSubscriptionsToResume(); + uint32_t ComputeTimeSecondsTillNextSubscriptionResumption(); + uint32_t mNumSubscriptionResumptionRetries = 0; + bool mSubscriptionResumptionScheduled = false; +#endif + FabricTable * mpFabricTable; CASESessionManager * mpCASESessionMgr = nullptr; diff --git a/src/app/ReadHandler.cpp b/src/app/ReadHandler.cpp index 1328559a85ac64..bbaf9f7992eb30 100644 --- a/src/app/ReadHandler.cpp +++ b/src/app/ReadHandler.cpp @@ -425,7 +425,6 @@ void ReadHandler::OnResponseTimeout(Messaging::ExchangeContext * apExchangeConte ChipLogError(DataManagement, "Time out! failed to receive status response from Exchange: " ChipLogFormatExchange, ChipLogValueExchange(apExchangeContext)); #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS - // TODO: Have a retry mechanism tied to wake interval for IC devices Close(CloseOptions::kKeepPersistedSubscription); #else Close(); diff --git a/src/app/tests/TestInteractionModelEngine.cpp b/src/app/tests/TestInteractionModelEngine.cpp index a7ebc2b49320fd..6fc5b1da27dcbe 100644 --- a/src/app/tests/TestInteractionModelEngine.cpp +++ b/src/app/tests/TestInteractionModelEngine.cpp @@ -48,6 +48,9 @@ class TestInteractionModelEngine public: static void TestAttributePathParamsPushRelease(nlTestSuite * apSuite, void * apContext); static void TestRemoveDuplicateConcreteAttribute(nlTestSuite * apSuite, void * apContext); +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + static void TestSubscriptionResumptionTimer(nlTestSuite * apSuite, void * apContext); +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION static int GetAttributePathListLength(ObjectList * apattributePathParamsList); }; @@ -223,6 +226,37 @@ void TestInteractionModelEngine::TestRemoveDuplicateConcreteAttribute(nlTestSuit InteractionModelEngine::GetInstance()->ReleaseAttributePathList(attributePathParamsList); } +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION +void TestInteractionModelEngine::TestSubscriptionResumptionTimer(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + CHIP_ERROR err = CHIP_NO_ERROR; + err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + uint32_t timeTillNextResubscriptionMs; + InteractionModelEngine::GetInstance()->mNumSubscriptionResumptionRetries = 0; + timeTillNextResubscriptionMs = InteractionModelEngine::GetInstance()->ComputeTimeSecondsTillNextSubscriptionResumption(); + NL_TEST_ASSERT(apSuite, timeTillNextResubscriptionMs == CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MIN_RETRY_INTERVAL_SECS); + + uint32_t lastTimeTillNextResubscriptionMs = timeTillNextResubscriptionMs; + for (InteractionModelEngine::GetInstance()->mNumSubscriptionResumptionRetries = 1; + InteractionModelEngine::GetInstance()->mNumSubscriptionResumptionRetries <= + CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX; + InteractionModelEngine::GetInstance()->mNumSubscriptionResumptionRetries++) + { + timeTillNextResubscriptionMs = InteractionModelEngine::GetInstance()->ComputeTimeSecondsTillNextSubscriptionResumption(); + NL_TEST_ASSERT(apSuite, timeTillNextResubscriptionMs >= lastTimeTillNextResubscriptionMs); + NL_TEST_ASSERT(apSuite, timeTillNextResubscriptionMs < CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS); + lastTimeTillNextResubscriptionMs = timeTillNextResubscriptionMs; + } + + InteractionModelEngine::GetInstance()->mNumSubscriptionResumptionRetries = 2000; + timeTillNextResubscriptionMs = InteractionModelEngine::GetInstance()->ComputeTimeSecondsTillNextSubscriptionResumption(); + NL_TEST_ASSERT(apSuite, timeTillNextResubscriptionMs == CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS); +} +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + } // namespace app } // namespace chip @@ -233,6 +267,9 @@ const nlTest sTests[] = { NL_TEST_DEF("TestAttributePathParamsPushRelease", chip::app::TestInteractionModelEngine::TestAttributePathParamsPushRelease), NL_TEST_DEF("TestRemoveDuplicateConcreteAttribute", chip::app::TestInteractionModelEngine::TestRemoveDuplicateConcreteAttribute), +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + NL_TEST_DEF("TestSubscriptionResumptionTimer", chip::app::TestInteractionModelEngine::TestSubscriptionResumptionTimer), +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION NL_TEST_SENTINEL() }; // clang-format on diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index 9ec11778f615c5..f83951ba3775c7 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -902,6 +902,10 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #define CHIP_IM_MAX_NUM_TIMED_HANDLER 8 #endif +/** + * @} + */ + /** * @def CONFIG_BUILD_FOR_HOST_UNIT_TEST * @@ -1497,5 +1501,60 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #endif /** - * @} + * @name Configuation for resuming subscriptions that timed out + * + * @brief + * The following definitions sets the parameters for subscription resumption in the case of subscription timeout. + * * #CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX + * * #CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MIN_RETRY_INTERVAL_SECS + * * #CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_WAIT_TIME_MULTIPLIER_SECS + * * #CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS + * + * @{ + */ + +/** + * @def CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX + * + * @brief + * If subscription timeout resumption is enabled, specify the max fibonacci step index. + * + * This index must satisfy below conditions (for readability "CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_" prefix is omitted): + * * MIN_RETRY_INTERVAL_SECS + Fibonacci(MAX_FIBONACCI_STEP_INDEX + 1) * WAIT_TIME_MULTIPLIER_SECS > MAX_RETRY_INTERVAL_SECS + * * MIN_RETRY_INTERVAL_SECS + Fibonacci(MAX_FIBONACCI_STEP_INDEX) * WAIT_TIME_MULTIPLIER_SECS < MAX_RETRY_INTERVAL_SECS + * + */ +#ifndef CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX +#define CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX 10 +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX + +/** + * @def CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MIN_RETRY_INTERVAL_SECS + * + * @brief The minimum interval before resuming a subsciption that timed out. + */ +#ifndef CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MIN_RETRY_INTERVAL_SECS +#define CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MIN_RETRY_INTERVAL_SECS 300 +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MIN_RETRY_INTERVAL_SECS + +/** + * @def CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_WAIT_TIME_MULTIPLIER_SECS + * + * @brief The multiplier per step in the calculation of retry interval. + */ +#ifndef CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_WAIT_TIME_MULTIPLIER_SECS +#define CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_WAIT_TIME_MULTIPLIER_SECS 300 +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_WAIT_TIME_MULTIPLIER_SECS + +/** + * @def CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS + * + * @brief The maximum interval before resuming a subsciption that timed out. + */ +#ifndef CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS +#define CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS (3600 * 6) +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS + +/** + * @} */ diff --git a/src/platform/device.gni b/src/platform/device.gni index ecf01173dbeb05..b70b576644318e 100644 --- a/src/platform/device.gni +++ b/src/platform/device.gni @@ -111,6 +111,11 @@ declare_args() { } } +declare_args() { + # Enable subscription resumption after timeout - separate configuration for power use measurement + chip_subscription_timeout_resumption = chip_persist_subscriptions +} + if (chip_device_platform == "bl702" || chip_device_platform == "bl702l") { if (chip_enable_openthread) { chip_mdns = "platform"