From 96329aeaea27d844067039e513eacd001011a149 Mon Sep 17 00:00:00 2001 From: mkardous-silabs <84793247+mkardous-silabs@users.noreply.github.com> Date: Wed, 3 Apr 2024 22:17:11 -0400 Subject: [PATCH] [ICD] Add Check-In message at boot logic and persistent subscription checks (#32613) * wip * Add unit tests to validate NodeID subscriptions * Add CATS unit test * Clean up conditionnal checks * finish PR clean up * Fix conditionnals * update comment * fix typo * Fix build issue rename gn source-set to match header * fix unit test #ifdef to #if * Add project config to ESP all-clusters-app to enable the unit test defines * force define to 1 * add CHIPCore.h include * add include after ChipCore * restyle * fix include ordering * restyle * move include * add unit test define to qemu build * fix esp all-clusters-minimal build proble * Apply suggestions from code review Co-authored-by: Boris Zbarsky * Fix comment * Finish refactor of the Sub logic when interacting with the ICDManager * Rename verifier function * Clean up comments & code * Apply suggestions from code review Co-authored-by: Boris Zbarsky * Fix comment application * Add / update comments * update comment with more details --------- Co-authored-by: Boris Zbarsky --- .../esp32/main/Kconfig.projbuild | 4 + .../esp32/main/include/CHIPProjectConfig.h | 38 ++ .../esp32/main/Kconfig.projbuild | 4 + .../esp32/main/include/CHIPProjectConfig.h | 38 ++ src/app/BUILD.gn | 5 +- src/app/InteractionModelEngine.cpp | 77 ++- src/app/InteractionModelEngine.h | 38 +- src/app/ReadHandler.cpp | 1 + src/app/ReadHandler.h | 2 + ...bscriptionResumptionSessionEstablisher.cpp | 17 +- src/app/icd/server/BUILD.gn | 8 +- src/app/icd/server/ICDManager.cpp | 107 +++- src/app/icd/server/ICDManager.h | 43 +- src/app/reporting/ReportScheduler.h | 5 +- src/app/server/Server.cpp | 26 +- src/app/server/Server.h | 12 + src/app/tests/TestICDManager.cpp | 96 +++- src/app/tests/TestInteractionModelEngine.cpp | 504 +++++++++++++++++- src/messaging/tests/MessagingContext.cpp | 14 + src/messaging/tests/MessagingContext.h | 3 + .../esp32/main/include/CHIPProjectConfig.h | 2 + 21 files changed, 981 insertions(+), 63 deletions(-) create mode 100644 examples/all-clusters-app/esp32/main/include/CHIPProjectConfig.h create mode 100644 examples/all-clusters-minimal-app/esp32/main/include/CHIPProjectConfig.h diff --git a/examples/all-clusters-app/esp32/main/Kconfig.projbuild b/examples/all-clusters-app/esp32/main/Kconfig.projbuild index 2cec0c32093734..e1d8805e689326 100644 --- a/examples/all-clusters-app/esp32/main/Kconfig.projbuild +++ b/examples/all-clusters-app/esp32/main/Kconfig.projbuild @@ -59,6 +59,10 @@ menu "Demo" depends on IDF_TARGET_ESP32H2 endchoice + config CHIP_PROJECT_CONFIG + string "CHIP Project Configuration file" + default "main/include/CHIPProjectConfig.h" + choice prompt "Rendezvous Mode" default RENDEZVOUS_MODE_BLE if BT_ENABLED diff --git a/examples/all-clusters-app/esp32/main/include/CHIPProjectConfig.h b/examples/all-clusters-app/esp32/main/include/CHIPProjectConfig.h new file mode 100644 index 00000000000000..9fff1bd10e67b4 --- /dev/null +++ b/examples/all-clusters-app/esp32/main/include/CHIPProjectConfig.h @@ -0,0 +1,38 @@ +/* + * + * Copyright (c) 2023 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. + */ + +/** + * @file + * Example project configuration file for CHIP. + * + * This is a place to put application or project-specific overrides + * to the default configuration values for general CHIP features. + * + */ + +#pragma once + +/** + * @def CONFIG_BUILD_FOR_HOST_UNIT_TEST + * + * @brief Defines whether we're currently building for unit testing, which enables a set of features + * that are only utilized in those tests. This flag should not be enabled on devices. If you have a test + * that uses this flag, either appropriately conditionalize the entire test on this flag, or to exclude + * the compliation of that test source file entirely. + */ +#define CONFIG_BUILD_FOR_HOST_UNIT_TEST 1 diff --git a/examples/all-clusters-minimal-app/esp32/main/Kconfig.projbuild b/examples/all-clusters-minimal-app/esp32/main/Kconfig.projbuild index 171af4f0ba2c24..9fe8f460e6b0a4 100644 --- a/examples/all-clusters-minimal-app/esp32/main/Kconfig.projbuild +++ b/examples/all-clusters-minimal-app/esp32/main/Kconfig.projbuild @@ -48,6 +48,10 @@ menu "Demo" depends on IDF_TARGET_ESP32C2 endchoice + config CHIP_PROJECT_CONFIG + string "CHIP Project Configuration file" + default "main/include/CHIPProjectConfig.h" + choice prompt "Rendezvous Mode" default RENDEZVOUS_MODE_BLE if BT_ENABLED diff --git a/examples/all-clusters-minimal-app/esp32/main/include/CHIPProjectConfig.h b/examples/all-clusters-minimal-app/esp32/main/include/CHIPProjectConfig.h new file mode 100644 index 00000000000000..9fff1bd10e67b4 --- /dev/null +++ b/examples/all-clusters-minimal-app/esp32/main/include/CHIPProjectConfig.h @@ -0,0 +1,38 @@ +/* + * + * Copyright (c) 2023 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. + */ + +/** + * @file + * Example project configuration file for CHIP. + * + * This is a place to put application or project-specific overrides + * to the default configuration values for general CHIP features. + * + */ + +#pragma once + +/** + * @def CONFIG_BUILD_FOR_HOST_UNIT_TEST + * + * @brief Defines whether we're currently building for unit testing, which enables a set of features + * that are only utilized in those tests. This flag should not be enabled on devices. If you have a test + * that uses this flag, either appropriately conditionalize the entire test on this flag, or to exclude + * the compliation of that test source file entirely. + */ +#define CONFIG_BUILD_FOR_HOST_UNIT_TEST 1 diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index e2b96e44868c0d..87508380a08083 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -122,7 +122,7 @@ source_set("global-attributes") { ] } -source_set("subscription-manager") { +source_set("subscription-info-provider") { sources = [ "SubscriptionsInfoProvider.h" ] public_deps = [ "${chip_root}/src/lib/core" ] @@ -207,9 +207,10 @@ static_library("interaction-model") { ":app_config", ":constants", ":paths", - ":subscription-manager", + ":subscription-info-provider", "${chip_root}/src/app/MessageDef", "${chip_root}/src/app/icd/server:icd-server-config", + "${chip_root}/src/app/icd/server:manager", "${chip_root}/src/app/icd/server:observer", "${chip_root}/src/app/util:af-types", "${chip_root}/src/app/util:callbacks", diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index 81bea8af9b4626..d0b020caf96315 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -336,16 +336,10 @@ bool InteractionModelEngine::SubjectHasActiveSubscription(FabricIndex aFabricInd { bool isActive = false; mReadHandlers.ForEachActiveObject([aFabricIndex, subjectID, &isActive](ReadHandler * handler) { - if (!handler->IsType(ReadHandler::InteractionType::Subscribe)) - { - return Loop::Continue; - } + VerifyOrReturnValue(handler->IsType(ReadHandler::InteractionType::Subscribe), Loop::Continue); Access::SubjectDescriptor subject = handler->GetSubjectDescriptor(); - if (subject.fabricIndex != aFabricIndex) - { - return Loop::Continue; - } + VerifyOrReturnValue(subject.fabricIndex == aFabricIndex, Loop::Continue); if (subject.authMode == Access::AuthMode::kCase) { @@ -353,13 +347,9 @@ bool InteractionModelEngine::SubjectHasActiveSubscription(FabricIndex aFabricInd { isActive = handler->IsActiveSubscription(); - // Exit loop only if isActive is set to true - // Otherwise keep looking for another subscription that could - // match the subject - if (isActive) - { - return Loop::Break; - } + // Exit loop only if isActive is set to true. + // Otherwise keep looking for another subscription that could match the subject. + VerifyOrReturnValue(!isActive, Loop::Break); } } @@ -371,8 +361,30 @@ bool InteractionModelEngine::SubjectHasActiveSubscription(FabricIndex aFabricInd bool InteractionModelEngine::SubjectHasPersistedSubscription(FabricIndex aFabricIndex, NodeId subjectID) { - // TODO(#30281) : Implement persisted sub check and verify how persistent subscriptions affects this at ICDManager::Init - return false; + bool persistedSubMatches = false; + +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS + auto * iterator = mpSubscriptionResumptionStorage->IterateSubscriptions(); + // Verify that we were able to allocate an iterator. If not, we are probably currently trying to resubscribe to our persisted + // subscriptions. As such, we assume we have a persisted subscription and return true. + // If we don't have a persisted subscription for the given fabric index and subjectID, we will send a Check-In message next time + // we transition to ActiveMode. + VerifyOrReturnValue(iterator, true); + + SubscriptionResumptionStorage::SubscriptionInfo subscriptionInfo; + while (iterator->Next(subscriptionInfo)) + { + // TODO(#31873): Persistent subscription only stores the NodeID for now. We cannot check if the CAT matches + if (subscriptionInfo.mFabricIndex == aFabricIndex && subscriptionInfo.mNodeId == subjectID) + { + persistedSubMatches = true; + break; + } + } + iterator->Release(); +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS + + return persistedSubMatches; } void InteractionModelEngine::OnDone(CommandResponseSender & apResponderObj) @@ -1917,22 +1929,22 @@ CHIP_ERROR InteractionModelEngine::ResumeSubscriptions() // future improvements: https://github.com/project-chip/connectedhomeip/issues/25439 SubscriptionResumptionStorage::SubscriptionInfo subscriptionInfo; - auto * iterator = mpSubscriptionResumptionStorage->IterateSubscriptions(); - int subscriptionsToResume = 0; - uint16_t minInterval = 0; + auto * iterator = mpSubscriptionResumptionStorage->IterateSubscriptions(); + mNumOfSubscriptionsToResume = 0; + uint16_t minInterval = 0; while (iterator->Next(subscriptionInfo)) { - subscriptionsToResume++; + mNumOfSubscriptionsToResume++; minInterval = std::max(minInterval, subscriptionInfo.mMinInterval); } iterator->Release(); - if (subscriptionsToResume) + if (mNumOfSubscriptionsToResume) { #if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION mSubscriptionResumptionScheduled = true; #endif - ChipLogProgress(InteractionModel, "Resuming %d subscriptions in %u seconds", subscriptionsToResume, minInterval); + ChipLogProgress(InteractionModel, "Resuming %d subscriptions in %u seconds", mNumOfSubscriptionsToResume, minInterval); ReturnErrorOnFailure(mpExchangeMgr->GetSessionManager()->SystemLayer()->StartTimer(System::Clock::Seconds16(minInterval), ResumeSubscriptionsTimerCallback, this)); } @@ -2054,5 +2066,24 @@ bool InteractionModelEngine::HasSubscriptionsToResume() } #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS +void InteractionModelEngine::DecrementNumSubscriptionsToResume() +{ + VerifyOrReturn(mNumOfSubscriptionsToResume > 0); +#if CHIP_CONFIG_ENABLE_ICD_CIP && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + VerifyOrDie(mICDManager); +#endif // CHIP_CONFIG_ENABLE_ICD_CIP && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + + mNumOfSubscriptionsToResume--; + +#if CHIP_CONFIG_ENABLE_ICD_CIP && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + if (!mNumOfSubscriptionsToResume) + { + mICDManager->SetBootUpResumeSubscriptionExecuted(); + } +#endif // CHIP_CONFIG_ENABLE_ICD_CIP && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION +} +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS + } // namespace app } // namespace chip diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h index 57d97f7517b085..935bca002b0dd1 100644 --- a/src/app/InteractionModelEngine.h +++ b/src/app/InteractionModelEngine.h @@ -25,6 +25,9 @@ #pragma once +// TODO(#32628): Remove the CHIPCore.h header when the esp32 build is correctly fixed +#include + #include #include #include @@ -47,6 +50,7 @@ #include #include #include +#include #include #include #include @@ -66,6 +70,10 @@ #include +#if CHIP_CONFIG_ENABLE_ICD_SERVER +#include // nogncheck +#endif // CHIP_CONFIG_ENABLE_ICD_SERVER + namespace chip { namespace app { @@ -127,6 +135,10 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, void Shutdown(); +#if CHIP_CONFIG_ENABLE_ICD_SERVER + void SetICDManager(ICDManager * manager) { mICDManager = manager; }; +#endif // CHIP_CONFIG_ENABLE_ICD_SERVER + Messaging::ExchangeManager * GetExchangeManager(void) const { return mpExchangeMgr; } /** @@ -317,6 +329,15 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, bool SubjectHasPersistedSubscription(FabricIndex aFabricIndex, NodeId subjectID) override; +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS + /** + * @brief Function decrements the number of subscriptions to resume counter - mNumOfSubscriptionsToResume. + * This should be called after we have completed a re-subscribe attempt on a persisted subscription wether the attempt + * was succesful or not. + */ + void DecrementNumSubscriptionsToResume(); +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS + #if CONFIG_BUILD_FOR_HOST_UNIT_TEST // // Get direct access to the underlying read handler pool @@ -602,6 +623,10 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, CommandHandlerInterface * mCommandHandlerList = nullptr; +#if CHIP_CONFIG_ENABLE_ICD_SERVER + ICDManager * mICDManager = nullptr; +#endif // CHIP_CONFIG_ENABLE_ICD_SERVER + ObjectPool mCommandResponderObjs; ObjectPool mTimedHandlers; WriteHandler mWriteHandlers[CHIP_IM_MAX_NUM_WRITE_HANDLER]; @@ -660,12 +685,21 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, #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 +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS + /** + * mNumOfSubscriptionsToResume tracks the number of subscriptions that the device will try to resume at its next resumption + * attempt. At boot up, the attempt will be at the highest min interval of all the subscriptions to resume. + * When the subscription timeout resumption feature is present, after the boot up attempt, the next attempt will be determined + * by ComputeTimeSecondsTillNextSubscriptionResumption. + */ + int8_t mNumOfSubscriptionsToResume = 0; +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION bool HasSubscriptionsToResume(); uint32_t ComputeTimeSecondsTillNextSubscriptionResumption(); uint32_t mNumSubscriptionResumptionRetries = 0; bool mSubscriptionResumptionScheduled = false; -#endif +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS FabricTable * mpFabricTable; diff --git a/src/app/ReadHandler.cpp b/src/app/ReadHandler.cpp index 63da2a53aa7470..142df8015601ca 100644 --- a/src/app/ReadHandler.cpp +++ b/src/app/ReadHandler.cpp @@ -821,6 +821,7 @@ void ReadHandler::PersistSubscription() auto * subscriptionResumptionStorage = mManagementCallback.GetInteractionModelEngine()->GetSubscriptionResumptionStorage(); VerifyOrReturn(subscriptionResumptionStorage != nullptr); + // TODO(#31873): We need to store the CAT information to enable better interactions with ICDs SubscriptionResumptionStorage::SubscriptionInfo subscriptionInfo = { .mNodeId = GetInitiatorNodeId(), .mFabricIndex = GetAccessingFabricIndex(), .mSubscriptionId = mSubscriptionId, diff --git a/src/app/ReadHandler.h b/src/app/ReadHandler.h index c597bac8a58d70..bf7d229e51f346 100644 --- a/src/app/ReadHandler.h +++ b/src/app/ReadHandler.h @@ -70,6 +70,7 @@ class TestReportScheduler; } // namespace reporting class InteractionModelEngine; +class TestInteractionModelEngine; /** * @class ReadHandler @@ -433,6 +434,7 @@ class ReadHandler : public Messaging::ExchangeDelegate // friend class chip::app::reporting::Engine; friend class chip::app::InteractionModelEngine; + friend class TestInteractionModelEngine; // The report scheduler needs to be able to access StateFlag private functions ShouldStartReporting(), CanStartReporting(), // ForceDirtyState() and IsDirty() to know when to schedule a run so it is declared as a friend class. diff --git a/src/app/SubscriptionResumptionSessionEstablisher.cpp b/src/app/SubscriptionResumptionSessionEstablisher.cpp index 3c6b3969512c89..6e016684410fc8 100644 --- a/src/app/SubscriptionResumptionSessionEstablisher.cpp +++ b/src/app/SubscriptionResumptionSessionEstablisher.cpp @@ -90,15 +90,23 @@ void SubscriptionResumptionSessionEstablisher::HandleDeviceConnected(void * cont AutoDeleteEstablisher establisher(static_cast(context)); SubscriptionResumptionStorage::SubscriptionInfo & subscriptionInfo = establisher->mSubscriptionInfo; InteractionModelEngine * imEngine = InteractionModelEngine::GetInstance(); + + // Decrement the number of subscriptions to resume since we have completed our retry attempt for a given subscription. + // We do this before the readHandler creation since we do not care if the subscription has successfully been resumed or + // not. Counter only tracks the number of individual subscriptions we will try to resume. + imEngine->DecrementNumSubscriptionsToResume(); + if (!imEngine->EnsureResourceForSubscription(subscriptionInfo.mFabricIndex, subscriptionInfo.mAttributePaths.AllocatedSize(), subscriptionInfo.mEventPaths.AllocatedSize())) { + // TODO - Should we keep the subscription here? ChipLogProgress(InteractionModel, "no resource for subscription resumption"); return; } ReadHandler * readHandler = imEngine->mReadHandlers.CreateObject(*imEngine, imEngine->GetReportScheduler()); if (readHandler == nullptr) { + // TODO - Should we keep the subscription here? ChipLogProgress(InteractionModel, "no resource for ReadHandler creation"); return; } @@ -118,10 +126,17 @@ void SubscriptionResumptionSessionEstablisher::HandleDeviceConnectionFailure(voi CHIP_ERROR error) { AutoDeleteEstablisher establisher(static_cast(context)); + InteractionModelEngine * imEngine = InteractionModelEngine::GetInstance(); SubscriptionResumptionStorage::SubscriptionInfo & subscriptionInfo = establisher->mSubscriptionInfo; ChipLogError(DataManagement, "Failed to establish CASE for subscription-resumption with error '%" CHIP_ERROR_FORMAT "'", error.Format()); - auto * subscriptionResumptionStorage = InteractionModelEngine::GetInstance()->GetSubscriptionResumptionStorage(); + + // Decrement the number of subscriptions to resume since we have completed our retry attempt for a given subscription. + // We do this here since we were not able to connect to the subscriber thus we have completed our resumption attempt. + // Counter only tracks the number of individual subscriptions we will try to resume. + imEngine->DecrementNumSubscriptionsToResume(); + + auto * subscriptionResumptionStorage = imEngine->GetSubscriptionResumptionStorage(); if (!subscriptionResumptionStorage) { ChipLogError(DataManagement, "Failed to get subscription resumption storage"); diff --git a/src/app/icd/server/BUILD.gn b/src/app/icd/server/BUILD.gn index 9a10430a3303ff..a6c5096dde7d0c 100644 --- a/src/app/icd/server/BUILD.gn +++ b/src/app/icd/server/BUILD.gn @@ -22,6 +22,11 @@ buildconfig_header("icd-server-buildconfig") { header = "ICDServerBuildConfig.h" header_dir = "app/icd/server" + if (chip_enable_icd_lit || chip_enable_icd_checkin || + chip_enable_icd_user_active_mode_trigger) { + assert(chip_enable_icd_server) + } + if (chip_enable_icd_lit) { assert(chip_enable_icd_checkin && chip_enable_icd_user_active_mode_trigger) } @@ -75,7 +80,7 @@ source_set("manager") { ":configuration-data", ":notifier", ":observer", - "${chip_root}/src/app:subscription-manager", + "${chip_root}/src/app:subscription-info-provider", "${chip_root}/src/credentials:credentials", "${chip_root}/src/messaging", ] @@ -84,6 +89,7 @@ source_set("manager") { public_deps += [ ":monitoring-table", ":sender", + "${chip_root}/src/app:app_config", ] } } diff --git a/src/app/icd/server/ICDManager.cpp b/src/app/icd/server/ICDManager.cpp index 931737783dde9f..f4442e85972ee2 100644 --- a/src/app/icd/server/ICDManager.cpp +++ b/src/app/icd/server/ICDManager.cpp @@ -105,6 +105,10 @@ void ICDManager::Shutdown() mFabricTable = nullptr; mSubInfoProvider = nullptr; mICDSenderPool.ReleaseAll(); + +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + mIsBootUpResumeSubscriptionExecuted = false; +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION #endif // CHIP_CONFIG_ENABLE_ICD_CIP } @@ -152,6 +156,7 @@ void ICDManager::SendCheckInMsgs() ICDMonitoringTable table(*mStorage, fabricInfo.GetFabricIndex(), supported_clients /*Table entry limit*/, mSymmetricKeystore); + if (table.IsEmpty()) { continue; @@ -173,8 +178,7 @@ void ICDManager::SendCheckInMsgs() continue; } - bool active = mSubInfoProvider->SubjectHasActiveSubscription(entry.fabricIndex, entry.monitoredSubject); - if (active) + if (!ShouldCheckInMsgsBeSentAtActiveModeFunction(entry.fabricIndex, entry.monitoredSubject)) { continue; } @@ -204,8 +208,10 @@ void ICDManager::SendCheckInMsgs() #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST } -bool ICDManager::CheckInMessagesWouldBeSent() +bool ICDManager::CheckInMessagesWouldBeSent(const std::function & shouldCheckInMsgsBeSentFunction) { + VerifyOrReturnValue(shouldCheckInMsgsBeSentFunction, false); + for (const auto & fabricInfo : *mFabricTable) { uint16_t supported_clients = ICDConfigurationData::GetInstance().GetClientsSupportedPerFabric(); @@ -234,7 +240,7 @@ bool ICDManager::CheckInMessagesWouldBeSent() } // At least one registration would require a Check-In message - VerifyOrReturnValue(mSubInfoProvider->SubjectHasActiveSubscription(entry.fabricIndex, entry.monitoredSubject), true); + VerifyOrReturnValue(!shouldCheckInMsgsBeSentFunction(entry.fabricIndex, entry.monitoredSubject), true); } } @@ -242,7 +248,87 @@ bool ICDManager::CheckInMessagesWouldBeSent() return false; } -void ICDManager::TriggerCheckInMessages() +/** + * ShouldCheckInMsgsBeSentAtActiveModeFunction is used to determine if a Check-In message is required for a given registration. + * Due to how the ICD Check-In use-case interacts with the persistent subscription and subscription timeout resumption features, + * having a single implementation of the function renders the implementation very difficult to understand and maintain. + * Because of this, each valid feature combination has its own implementation of the function. + */ +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION +/** + * @brief Implementation for when the persistent subscription and subscription timeout resumption feature are present. + * Function checks that there are no active or persisted subscriptions for a given fabricIndex or subjectID. + * + * @note When the persistent subscription and subscription timeout resumption feature are present, we need to check for + * persisted subscription at each transition to ActiveMode since there will be persisted subscriptions during normal + * operation for the subscription timeout resumption feature. Once we have finished all our subscription resumption attempts + * for a given subscription, the entry is deleted from persisted storage which will enable us to send Check-In messages for + * the client registration. This logic avoids the device sending a Check-In message while trying to resume subscriptions. + * + * @param aFabricIndex + * @param subjectID subjectID to check. Can be an operational node id or a CAT + * + * @return true Returns true if the fabricIndex and subjectId combination does not have an active or a persisted subscription. + * @return false Returns false if the fabricIndex and subjectId combination has an active or persisted subscription. + */ +bool ICDManager::ShouldCheckInMsgsBeSentAtActiveModeFunction(FabricIndex aFabricIndex, NodeId subjectID) +{ + return !(mSubInfoProvider->SubjectHasActiveSubscription(aFabricIndex, subjectID) || + mSubInfoProvider->SubjectHasPersistedSubscription(aFabricIndex, subjectID)); +} +#else +/** + * @brief Implementation for when the persistent subscription feature is present without the subscription timeout resumption + * feature. Function checks that there are no active subscriptions. If the boot up subscription resumption has not been completed, + * function also checks if there are persisted subscriptions. + * + * @note The persistent subscriptions feature tries to resume subscriptions at the highest min interval + * of all the persisted subscriptions. As such, it is possible for the ICD to return to Idle Mode + * until the timer elaspses. We do not want to send Check-In messages to clients with persisted subscriptions + * until we have tried to resubscribe. + * + * @param aFabricIndex + * @param subjectID subjectID to check. Can be an opperationnal node id or a CAT + * + * @return true Returns true if the fabricIndex and subjectId combination does not have an active subscription. + * If the boot up subscription resumption has not been completed, there must not be a persisted subscription either. + * @return false Returns false if the fabricIndex and subjectId combination has an active subscription. + * If the boot up subscription resumption has not been completed, + * returns false if the fabricIndex and subjectId combination has a persisted subscription. + */ +bool ICDManager::ShouldCheckInMsgsBeSentAtActiveModeFunction(FabricIndex aFabricIndex, NodeId subjectID) +{ + bool mightHaveSubscription = mSubInfoProvider->SubjectHasActiveSubscription(aFabricIndex, subjectID); + if (!mightHaveSubscription && !mIsBootUpResumeSubscriptionExecuted) + { + mightHaveSubscription = mSubInfoProvider->SubjectHasPersistedSubscription(aFabricIndex, subjectID); + } + + return !mightHaveSubscription; +} +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION +#else +/** + * @brief Implementation for when neither the persistent subscription nor the subscription timeout resumption features are present. + * Function checks that there no active sbuscriptions for a given fabricIndex and subjectId combination. + * + * @note When neither the persistent subscription nor the subscription timeout resumption features are present, we only need to + * check for active subscription since we will never have any persisted subscription. + * + * @param aFabricIndex + * @param subjectID subjectID to check. Can be an opperationnal node id or a CAT + * + * @return true Returns true if the fabricIndex and subjectId combination does not have an active subscription. + * @return false Returns false if the fabricIndex and subjectId combination has an active subscription. + */ +bool ICDManager::ShouldCheckInMsgsBeSentAtActiveModeFunction(FabricIndex aFabricIndex, NodeId subjectID) +{ + return !(mSubInfoProvider->SubjectHasActiveSubscription(aFabricIndex, subjectID)); +} +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS + +void ICDManager::TriggerCheckInMessages(const std::function & verifier) { VerifyOrReturn(SupportsFeature(Feature::kCheckInProtocolSupport)); @@ -251,8 +337,7 @@ void ICDManager::TriggerCheckInMessages() VerifyOrReturn(mOperationalState == OperationalState::IdleMode); // If we don't have any Check-In messages to send, do nothing - VerifyOrReturn(CheckInMessagesWouldBeSent()); - + VerifyOrReturn(CheckInMessagesWouldBeSent(verifier)); UpdateOperationState(OperationalState::ActiveMode); } #endif // CHIP_CONFIG_ENABLE_ICD_CIP @@ -313,12 +398,16 @@ void ICDManager::UpdateOperationState(OperationalState state) { mOperationalState = OperationalState::IdleMode; +#if CHIP_CONFIG_ENABLE_ICD_CIP + std::function sendCheckInMessagesOnActiveMode = + std::bind(&ICDManager::ShouldCheckInMsgsBeSentAtActiveModeFunction, this, std::placeholders::_1, std::placeholders::_2); +#endif // CHIP_CONFIG_ENABLE_ICD_CIP + // When the active mode interval is 0, we stay in idleMode until a notification brings the icd into active mode // unless the device would need to send Check-In messages - // TODO(#30281) : Verify how persistent subscriptions affects this at ICDManager::Init if (ICDConfigurationData::GetInstance().GetActiveModeDuration() > kZero #if CHIP_CONFIG_ENABLE_ICD_CIP - || CheckInMessagesWouldBeSent() + || CheckInMessagesWouldBeSent(sendCheckInMessagesOnActiveMode) #endif // CHIP_CONFIG_ENABLE_ICD_CIP ) { diff --git a/src/app/icd/server/ICDManager.h b/src/app/icd/server/ICDManager.h index 5e391c1469143c..058285802eb309 100644 --- a/src/app/icd/server/ICDManager.h +++ b/src/app/icd/server/ICDManager.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -79,6 +80,17 @@ class ICDManager : public ICDListener ICDModeChange, }; + /** + * @brief Verifier template function + * This type can be used to implement specific verifiers that can be used in the CheckInMessagesWouldBeSent function. + * The goal is to avoid having multiple functions that implement the iterator loop with only the check changing. + * + * @return true if at least one Check-In message would be sent + * false No Check-In messages would be sent + */ + + using ShouldCheckInMsgsBeSentFunction = bool(FabricIndex aFabricIndex, NodeId subjectID); + ICDManager() {} void Init(PersistentStorageDelegate * storage, FabricTable * fabricTable, Crypto::SymmetricKeystore * symmetricKeyStore, Messaging::ExchangeManager * exchangeManager, SubscriptionsInfoProvider * manager); @@ -122,12 +134,26 @@ class ICDManager : public ICDListener /** * @brief Trigger the ICDManager to send Check-In message if necessary + * + * @param[in] function to use to determine if we need to send check-in messages */ - void TriggerCheckInMessages(); + void TriggerCheckInMessages(const std::function & function); + +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + /** + * @brief Set mSubCheckInBootCheckExecuted to true + * Function allows the InteractionModelEngine to notify the ICDManager that the boot up subscription resumption has been + * completed. + */ + void SetBootUpResumeSubscriptionExecuted() { mIsBootUpResumeSubscriptionExecuted = true; }; +#endif // !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION && CHIP_CONFIG_PERSIST_SUBSCRIPTIONS #endif // CHIP_CONFIG_ENABLE_ICD_CIP -#ifdef CONFIG_BUILD_FOR_HOST_UNIT_TEST +#if CONFIG_BUILD_FOR_HOST_UNIT_TEST void SetTestFeatureMapValue(uint32_t featureMap) { mFeatureMap = featureMap; }; +#if !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION && CHIP_CONFIG_PERSIST_SUBSCRIPTIONS + bool GetIsBootUpResumeSubscriptionExecuted() { return mIsBootUpResumeSubscriptionExecuted; }; +#endif // !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION && CHIP_CONFIG_PERSIST_SUBSCRIPTIONS #endif // Implementation of ICDListener functions. @@ -166,14 +192,18 @@ class ICDManager : public ICDListener private: #if CHIP_CONFIG_ENABLE_ICD_CIP + bool ShouldCheckInMsgsBeSentAtActiveModeFunction(FabricIndex aFabricIndex, NodeId subjectID); + /** * @brief Function checks if at least one client registration would require a Check-In message * + * @param[in] function function to use to determine if a Check-In message would be sent for a given registration + * * @return true At least one registration would require an Check-In message if we were entering ActiveMode. - * @return false None of the registration would require a Check-In message either because there are no registration or because - * they all have associated subscriptions. + * @return false None of the registration would require a Check-In message either because there are no registration or + * because they all have associated subscriptions. */ - bool CheckInMessagesWouldBeSent(); + bool CheckInMessagesWouldBeSent(const std::function & function); #endif // CHIP_CONFIG_ENABLE_ICD_CIP KeepActiveFlags mKeepActiveFlags{ 0 }; @@ -184,6 +214,9 @@ class ICDManager : public ICDListener ObjectPool mStateObserverPool; #if CHIP_CONFIG_ENABLE_ICD_CIP +#if !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION && CHIP_CONFIG_PERSIST_SUBSCRIPTIONS + bool mIsBootUpResumeSubscriptionExecuted = false; +#endif // !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION && CHIP_CONFIG_PERSIST_SUBSCRIPTIONS PersistentStorageDelegate * mStorage = nullptr; FabricTable * mFabricTable = nullptr; Messaging::ExchangeManager * mExchangeManager = nullptr; diff --git a/src/app/reporting/ReportScheduler.h b/src/app/reporting/ReportScheduler.h index 5700eed6dfa025..f0e6ce3ff82f27 100644 --- a/src/app/reporting/ReportScheduler.h +++ b/src/app/reporting/ReportScheduler.h @@ -18,6 +18,9 @@ #pragma once +// TODO(#32628): Remove the CHIPCore.h header when the esp32 build is correctly fixed +#include + #include #include #include @@ -213,7 +216,7 @@ class ReportScheduler : public ReadHandler::Observer, public ICDStateObserver /// @brief Get the number of ReadHandlers registered in the scheduler's node pool size_t GetNumReadHandlers() const { return mNodesPool.Allocated(); } -#ifdef CONFIG_BUILD_FOR_HOST_UNIT_TEST +#if CONFIG_BUILD_FOR_HOST_UNIT_TEST Timestamp GetMinTimestampForHandler(const ReadHandler * aReadHandler) { ReadHandlerNode * node = FindReadHandlerNode(aReadHandler); diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp index d2983636450691..eff11222bfae7b 100644 --- a/src/app/server/Server.cpp +++ b/src/app/server/Server.cpp @@ -333,6 +333,10 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams) &mCASESessionManager, mSubscriptionResumptionStorage); SuccessOrExit(err); +#if CHIP_CONFIG_ENABLE_ICD_SERVER + app::InteractionModelEngine::GetInstance()->SetICDManager(&mICDManager); +#endif // CHIP_CONFIG_ENABLE_ICD_SERVER + // ICD Init needs to be after data model init and InteractionModel Init #if CHIP_CONFIG_ENABLE_ICD_SERVER @@ -446,12 +450,14 @@ void Server::OnPlatformEvent(const DeviceLayer::ChipDeviceEvent & event) // We trigger Check-In messages before resuming subscriptions to avoid doing both. if (!mFailSafeContext.IsFailSafeArmed()) { - mICDManager.TriggerCheckInMessages(); + std::function sendCheckInMessagesOnBootUp = + std::bind(&Server::ShouldCheckInMsgsBeSentAtBootFunction, this, std::placeholders::_1, std::placeholders::_2); + mICDManager.TriggerCheckInMessages(sendCheckInMessagesOnBootUp); } #endif // CHIP_CONFIG_ENABLE_ICD_SERVER && CHIP_CONFIG_ENABLE_ICD_CIP #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS ResumeSubscriptions(); -#endif +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS break; #if CHIP_SYSTEM_CONFIG_USE_OPEN_THREAD_ENDPOINT case DeviceEventType::kThreadConnectivityChange: @@ -519,6 +525,19 @@ void Server::RejoinExistingMulticastGroups() } } +#if CHIP_CONFIG_ENABLE_ICD_CIP +bool Server::ShouldCheckInMsgsBeSentAtBootFunction(FabricIndex aFabricIndex, NodeId subjectID) +{ +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS + // If at least one registration has a persisted entry, do not send Check-In message. + // The resumption of the persisted subscription will serve the same function a check-in would have served. + return !app::InteractionModelEngine::GetInstance()->SubjectHasPersistedSubscription(aFabricIndex, subjectID); +#else + return true; +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS +} +#endif // CHIP_CONFIG_ENABLE_ICD_CIP + void Server::GenerateShutDownEvent() { PlatformMgr().ScheduleWork([](intptr_t) { PlatformMgr().HandleServerShuttingDown(); }); @@ -561,6 +580,9 @@ void Server::Shutdown() chip::Dnssd::Resolver::Instance().Shutdown(); chip::app::InteractionModelEngine::GetInstance()->Shutdown(); +#if CHIP_CONFIG_ENABLE_ICD_SERVER + app::InteractionModelEngine::GetInstance()->SetICDManager(nullptr); +#endif // CHIP_CONFIG_ENABLE_ICD_SERVER mCommissioningWindowManager.Shutdown(); mMessageCounterManager.Shutdown(); mExchangeMgr.Shutdown(); diff --git a/src/app/server/Server.h b/src/app/server/Server.h index 302471b8bebb89..8f6fcd5abecc66 100644 --- a/src/app/server/Server.h +++ b/src/app/server/Server.h @@ -369,6 +369,18 @@ class Server #if CHIP_CONFIG_ENABLE_ICD_SERVER app::ICDManager & GetICDManager() { return mICDManager; } + +#if CHIP_CONFIG_ENABLE_ICD_CIP + /** + * @brief Function to determine if a Check-In message would be sent at Boot up + * + * @param aFabricIndex client fabric index + * @param subjectID client subject ID + * @return true Check-In message would be sent on boot up. + * @return false Device has a persisted subscription with the client. See CHIP_CONFIG_PERSIST_SUBSCRIPTIONS. + */ + bool ShouldCheckInMsgsBeSentAtBootFunction(FabricIndex aFabricIndex, NodeId subjectID); +#endif // CHIP_CONFIG_ENABLE_ICD_CIP #endif // CHIP_CONFIG_ENABLE_ICD_SERVER /** diff --git a/src/app/tests/TestICDManager.cpp b/src/app/tests/TestICDManager.cpp index ba311f889989f8..ac8c220351fd86 100644 --- a/src/app/tests/TestICDManager.cpp +++ b/src/app/tests/TestICDManager.cpp @@ -48,10 +48,10 @@ namespace { constexpr uint16_t kMaxTestClients = 2; constexpr FabricIndex kTestFabricIndex1 = 1; constexpr FabricIndex kTestFabricIndex2 = kMaxValidFabricIndex; -constexpr uint64_t kClientNodeId11 = 0x100001; -constexpr uint64_t kClientNodeId12 = 0x100002; -constexpr uint64_t kClientNodeId21 = 0x200001; -constexpr uint64_t kClientNodeId22 = 0x200002; +constexpr NodeId kClientNodeId11 = 0x100001; +constexpr NodeId kClientNodeId12 = 0x100002; +constexpr NodeId kClientNodeId21 = 0x200001; +constexpr NodeId kClientNodeId22 = 0x200002; constexpr uint8_t kKeyBuffer1a[] = { 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f @@ -80,13 +80,15 @@ class TestSubscriptionsInfoProvider : public SubscriptionsInfoProvider TestSubscriptionsInfoProvider() = default; ~TestSubscriptionsInfoProvider(){}; - void SetReturnValue(bool value) { mReturnValue = value; }; + void SetHasActiveSubscription(bool value) { mHasActiveSubscription = value; }; + void SetHasPersistedSubscription(bool value) { mHasPersistedSubscription = value; }; - bool SubjectHasActiveSubscription(FabricIndex aFabricIndex, NodeId subject) { return mReturnValue; }; - bool SubjectHasPersistedSubscription(FabricIndex aFabricIndex, NodeId subject) { return mReturnValue; }; + bool SubjectHasActiveSubscription(FabricIndex aFabricIndex, NodeId subject) { return mHasActiveSubscription; }; + bool SubjectHasPersistedSubscription(FabricIndex aFabricIndex, NodeId subject) { return mHasPersistedSubscription; }; private: - bool mReturnValue = false; + bool mHasActiveSubscription = false; + bool mHasPersistedSubscription = false; }; class TestContext : public chip::Test::AppContext @@ -197,7 +199,8 @@ class TestICDManager ctx->mICDManager.SetTestFeatureMapValue(0x07); // Set that there are no matching subscriptions - ctx->mSubInfoProvider.SetReturnValue(false); + ctx->mSubInfoProvider.SetHasActiveSubscription(false); + ctx->mSubInfoProvider.SetHasPersistedSubscription(false); // Set New durations for test case Milliseconds32 oldActiveModeDuration = icdConfigData.GetActiveModeDuration(); @@ -276,7 +279,8 @@ class TestICDManager ctx->mICDManager.SetTestFeatureMapValue(0x07); // Set that there are not matching subscriptions - ctx->mSubInfoProvider.SetReturnValue(true); + ctx->mSubInfoProvider.SetHasActiveSubscription(true); + ctx->mSubInfoProvider.SetHasPersistedSubscription(true); // Set New durations for test case Milliseconds32 oldActiveModeDuration = icdConfigData.GetActiveModeDuration(); @@ -617,6 +621,77 @@ class TestICDManager // 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); } + +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + static void TestShouldCheckInMsgsBeSentAtActiveModeFunction(nlTestSuite * aSuite, void * aContext) + { + TestContext * ctx = static_cast(aContext); + + // Test 1 - Has no ActiveSubscription & no persisted subscription + ctx->mSubInfoProvider.SetHasActiveSubscription(false); + ctx->mSubInfoProvider.SetHasPersistedSubscription(false); + NL_TEST_ASSERT(aSuite, ctx->mICDManager.ShouldCheckInMsgsBeSentAtActiveModeFunction(kTestFabricIndex1, kClientNodeId11)); + + // Test 2 - Has no active subscription & a persisted subscription + ctx->mSubInfoProvider.SetHasActiveSubscription(false); + ctx->mSubInfoProvider.SetHasPersistedSubscription(true); + NL_TEST_ASSERT(aSuite, !(ctx->mICDManager.ShouldCheckInMsgsBeSentAtActiveModeFunction(kTestFabricIndex1, kClientNodeId11))); + + // Test 3 - Has an active subscription & a persisted subscription + ctx->mSubInfoProvider.SetHasActiveSubscription(true); + ctx->mSubInfoProvider.SetHasPersistedSubscription(true); + NL_TEST_ASSERT(aSuite, !(ctx->mICDManager.ShouldCheckInMsgsBeSentAtActiveModeFunction(kTestFabricIndex1, kClientNodeId11))); + } +#else + static void TestShouldCheckInMsgsBeSentAtActiveModeFunction(nlTestSuite * aSuite, void * aContext) + { + TestContext * ctx = static_cast(aContext); + + // Test 1 - Has no active subscription and no persisted subscription at boot up + ctx->mSubInfoProvider.SetHasActiveSubscription(false); + ctx->mSubInfoProvider.SetHasPersistedSubscription(false); + ctx->mICDManager.mIsBootUpResumeSubscriptionExecuted = false; + NL_TEST_ASSERT(aSuite, ctx->mICDManager.ShouldCheckInMsgsBeSentAtActiveModeFunction(kTestFabricIndex1, kClientNodeId11)); + + // Test 2 - Has no active subscription and a persisted subscription at boot up + ctx->mSubInfoProvider.SetHasActiveSubscription(false); + ctx->mSubInfoProvider.SetHasPersistedSubscription(true); + ctx->mICDManager.mIsBootUpResumeSubscriptionExecuted = false; + NL_TEST_ASSERT(aSuite, !(ctx->mICDManager.ShouldCheckInMsgsBeSentAtActiveModeFunction(kTestFabricIndex1, kClientNodeId11))); + + // Test 3 - Has an active subscription and a persisted subscription during normal operations + ctx->mSubInfoProvider.SetHasActiveSubscription(true); + ctx->mSubInfoProvider.SetHasPersistedSubscription(true); + ctx->mICDManager.mIsBootUpResumeSubscriptionExecuted = true; + NL_TEST_ASSERT(aSuite, !(ctx->mICDManager.ShouldCheckInMsgsBeSentAtActiveModeFunction(kTestFabricIndex1, kClientNodeId11))); + + // Test 4 - Has no active subscription and a persisted subscription during normal operations + ctx->mSubInfoProvider.SetHasActiveSubscription(false); + ctx->mSubInfoProvider.SetHasPersistedSubscription(true); + ctx->mICDManager.mIsBootUpResumeSubscriptionExecuted = true; + NL_TEST_ASSERT(aSuite, ctx->mICDManager.ShouldCheckInMsgsBeSentAtActiveModeFunction(kTestFabricIndex1, kClientNodeId11)); + } +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION +#else + static void TestShouldCheckInMsgsBeSentAtActiveModeFunction(nlTestSuite * aSuite, void * aContext) + { + TestContext * ctx = static_cast(aContext); + + // Test 1 - Has an active subscription + ctx->mSubInfoProvider.SetHasActiveSubscription(true); + NL_TEST_ASSERT(aSuite, + ctx->mICDManager.ShouldCheckInMsgsBeSentAtActiveModeFunction(kTestFabricIndex1, kClientNodeId11) == false); + + // Test 2 - Has no active subscription + ctx->mSubInfoProvider.SetHasActiveSubscription(false); + NL_TEST_ASSERT(aSuite, ctx->mICDManager.ShouldCheckInMsgsBeSentAtActiveModeFunction(kTestFabricIndex1, kClientNodeId11)); + + // Test 3 - Make sure that the persisted subscription has no impact + ctx->mSubInfoProvider.SetHasPersistedSubscription(true); + NL_TEST_ASSERT(aSuite, ctx->mICDManager.ShouldCheckInMsgsBeSentAtActiveModeFunction(kTestFabricIndex1, kClientNodeId11)); + } +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS }; } // namespace app @@ -634,6 +709,7 @@ static const nlTest sTests[] = { NL_TEST_DEF("TestICDMRegisterUnregisterEvents", TestICDManager::TestICDMRegisterUnregisterEvents), NL_TEST_DEF("TestICDCounter", TestICDManager::TestICDCounter), NL_TEST_DEF("TestICDStayActive", TestICDManager::TestICDMStayActive), + NL_TEST_DEF("TestShouldCheckInMsgsBeSentAtActiveModeFunction", TestICDManager::TestShouldCheckInMsgsBeSentAtActiveModeFunction), NL_TEST_SENTINEL(), }; diff --git a/src/app/tests/TestInteractionModelEngine.cpp b/src/app/tests/TestInteractionModelEngine.cpp index fcdc7d53e1594f..4f0b75315355ad 100644 --- a/src/app/tests/TestInteractionModelEngine.cpp +++ b/src/app/tests/TestInteractionModelEngine.cpp @@ -22,26 +22,49 @@ * */ +#include #include +#include #include #include #include #include -#include +#include #include #include #include #include #include +#include #include #include #include #include +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS +#include +#include +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS + #include +namespace { + using TestContext = chip::Test::AppContext; +class NullReadHandlerCallback : public chip::app::ReadHandler::ManagementCallback +{ +public: + void OnDone(chip::app::ReadHandler & apReadHandlerObj) override {} + chip::app::ReadHandler::ApplicationCallback * GetAppCallback() override { return nullptr; } + chip::app::InteractionModelEngine * GetInteractionModelEngine() override + { + return chip::app::InteractionModelEngine::GetInstance(); + } +}; + +} // namespace + namespace chip { namespace app { class TestInteractionModelEngine @@ -49,10 +72,19 @@ 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 +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS + static void TestSubjectHasPersistedSubscription(nlTestSuite * apSuite, void * apContext); +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION static void TestSubscriptionResumptionTimer(nlTestSuite * apSuite, void * apContext); -#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS static int GetAttributePathListLength(SingleLinkedListNode * apattributePathParamsList); + static void TestSubjectHasActiveSubscriptionSingleSubOneEntry(nlTestSuite * apSuite, void * apContext); + static void TestSubjectHasActiveSubscriptionSingleSubMultipleEntries(nlTestSuite * apSuite, void * apContext); + static void TestSubjectHasActiveSubscriptionMultipleSubsSingleEntry(nlTestSuite * apSuite, void * apContext); + static void TestSubjectHasActiveSubscriptionMultipleSubsMultipleEntries(nlTestSuite * apSuite, void * apContext); + static void TestSubjectHasActiveSubscriptionSubWithCAT(nlTestSuite * apSuite, void * apContext); + static void TestDecrementNumSubscriptionsToResume(nlTestSuite * apSuite, void * apContext); }; int TestInteractionModelEngine::GetAttributePathListLength(SingleLinkedListNode * apAttributePathParamsList) @@ -229,7 +261,397 @@ void TestInteractionModelEngine::TestRemoveDuplicateConcreteAttribute(nlTestSuit InteractionModelEngine::GetInstance()->ReleaseAttributePathList(attributePathParamsList); } -#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION +/** + * @brief Test verifies the SubjectHasActiveSubscription with a single subscription with a single entry + */ +void TestInteractionModelEngine::TestSubjectHasActiveSubscriptionSingleSubOneEntry(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + NullReadHandlerCallback nullCallback; + InteractionModelEngine * engine = InteractionModelEngine::GetInstance(); + + NodeId bobNodeId = 0x12344321ull; + FabricIndex bobFabricIndex = 1; + + // Create ExchangeContext + Messaging::ExchangeContext * exchangeCtx1 = ctx.NewExchangeToBob(nullptr, false); + NL_TEST_ASSERT(apSuite, exchangeCtx1); + + // InteractionModelEngine init + NL_TEST_ASSERT(apSuite, + CHIP_NO_ERROR == + engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reporting::GetDefaultReportScheduler())); + + // Verify that there are no active subscriptions + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId) == false); + + // Create and setup readHandler 1 + ReadHandler * readHandler1 = engine->GetReadHandlerPool().CreateObject( + nullCallback, exchangeCtx1, ReadHandler::InteractionType::Subscribe, reporting::GetDefaultReportScheduler()); + + // Verify that Bob still doesn't have an active subscription + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId) == false); + + // Set readHandler1 to active + readHandler1->SetStateFlag(ReadHandler::ReadHandlerFlags::ActiveSubscription, true); + + // Verify that Bob still has an active subscription + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId)); + + // Clean up read handlers + engine->GetReadHandlerPool().ReleaseAll(); + + // Verify that there are no active subscriptions + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId) == false); +} + +/** + * @brief Test verifies that the SubjectHasActiveSubscription will continue iterating till it fines at least one valid active + * subscription + */ +void TestInteractionModelEngine::TestSubjectHasActiveSubscriptionSingleSubMultipleEntries(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + NullReadHandlerCallback nullCallback; + InteractionModelEngine * engine = InteractionModelEngine::GetInstance(); + + NodeId bobNodeId = 0x12344321ull; + FabricIndex bobFabricIndex = 1; + + // Create ExchangeContexts + Messaging::ExchangeContext * exchangeCtx1 = ctx.NewExchangeToBob(nullptr, false); + NL_TEST_ASSERT(apSuite, exchangeCtx1); + + Messaging::ExchangeContext * exchangeCtx2 = ctx.NewExchangeToBob(nullptr, false); + NL_TEST_ASSERT(apSuite, exchangeCtx1); + + // InteractionModelEngine init + NL_TEST_ASSERT(apSuite, + CHIP_NO_ERROR == + engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reporting::GetDefaultReportScheduler())); + + // Verify that both Alice and Bob have no active subscriptions + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId) == false); + + // Create readHandler 1 + engine->GetReadHandlerPool().CreateObject(nullCallback, exchangeCtx1, ReadHandler::InteractionType::Subscribe, + reporting::GetDefaultReportScheduler()); + + // Verify that Bob still doesn't have an active subscription + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId) == false); + + // Create and setup readHandler 2 + ReadHandler * readHandler2 = engine->GetReadHandlerPool().CreateObject( + nullCallback, exchangeCtx2, ReadHandler::InteractionType::Subscribe, reporting::GetDefaultReportScheduler()); + + // Verify that Bob still doesn't have an active subscription + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId) == false); + + // Set readHandler2 to active + readHandler2->SetStateFlag(ReadHandler::ReadHandlerFlags::ActiveSubscription, true); + + // Verify that Bob still doesn't have an active subscription + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId)); + + // Release active ReadHandler + engine->GetReadHandlerPool().ReleaseObject(readHandler2); + + // Verify that there are no active subscriptions + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId) == false); + + // Clean up read handlers + engine->GetReadHandlerPool().ReleaseAll(); +} + +/** + * @brief Test validates that the SubjectHasActiveSubscription can support multiple subscriptions from different clients + */ +void TestInteractionModelEngine::TestSubjectHasActiveSubscriptionMultipleSubsSingleEntry(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + NullReadHandlerCallback nullCallback; + InteractionModelEngine * engine = InteractionModelEngine::GetInstance(); + + NodeId bobNodeId = 0x12344321ull; + FabricIndex bobFabricIndex = 1; + NodeId aliceNodeId = 0x11223344ull; + FabricIndex aliceFabricIndex = 2; + + // Create ExchangeContexts + Messaging::ExchangeContext * exchangeCtx1 = ctx.NewExchangeToBob(nullptr, false); + NL_TEST_ASSERT(apSuite, exchangeCtx1); + + Messaging::ExchangeContext * exchangeCtx2 = ctx.NewExchangeToAlice(nullptr, false); + NL_TEST_ASSERT(apSuite, exchangeCtx2); + + // InteractionModelEngine init + NL_TEST_ASSERT(apSuite, + CHIP_NO_ERROR == + engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reporting::GetDefaultReportScheduler())); + + // Verify that both Alice and Bob have no active subscriptions + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId) == false); + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId) == false); + + // Create and setup readHandler 1 + ReadHandler * readHandler1 = engine->GetReadHandlerPool().CreateObject( + nullCallback, exchangeCtx1, ReadHandler::InteractionType::Subscribe, reporting::GetDefaultReportScheduler()); + + // Create and setup readHandler 2 + ReadHandler * readHandler2 = engine->GetReadHandlerPool().CreateObject( + nullCallback, exchangeCtx2, ReadHandler::InteractionType::Subscribe, reporting::GetDefaultReportScheduler()); + + // Verify that Bob still doesn't have an active subscription + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId) == false); + + // Set readHandler1 to active + readHandler1->SetStateFlag(ReadHandler::ReadHandlerFlags::ActiveSubscription, true); + + // Verify that Bob has an active subscription + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId)); + + // Verify that Alice still doesn't have an active subscription + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId) == false); + + // Set readHandler2 to active + readHandler2->SetStateFlag(ReadHandler::ReadHandlerFlags::ActiveSubscription, true); + + // Verify that Bob has an active subscription + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId)); + + // Verify that Alice still doesn't have an active subscription + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId)); + + // Set readHandler1 to inactive + readHandler1->SetStateFlag(ReadHandler::ReadHandlerFlags::ActiveSubscription, false); + + // Verify that Bob doesn't have an active subscription + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId) == false); + + // Verify that Alice still has an active subscription + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId)); + + // Clean up read handlers + engine->GetReadHandlerPool().ReleaseAll(); + + // Verify that both Alice and Bob have no active subscriptions + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId) == false); + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId) == false); +} + +/** + * @brief Test validates that the SubjectHasActiveSubscription can find the active subscription even if there are multiple + * subscriptions for each client + */ +void TestInteractionModelEngine::TestSubjectHasActiveSubscriptionMultipleSubsMultipleEntries(nlTestSuite * apSuite, + void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + NullReadHandlerCallback nullCallback; + InteractionModelEngine * engine = InteractionModelEngine::GetInstance(); + + NodeId bobNodeId = 0x12344321ull; + FabricIndex bobFabricIndex = 1; + NodeId aliceNodeId = 0x11223344ull; + FabricIndex aliceFabricIndex = 2; + + // Create ExchangeContexts + Messaging::ExchangeContext * exchangeCtx11 = ctx.NewExchangeToBob(nullptr, false); + NL_TEST_ASSERT(apSuite, exchangeCtx11); + + Messaging::ExchangeContext * exchangeCtx12 = ctx.NewExchangeToBob(nullptr, false); + NL_TEST_ASSERT(apSuite, exchangeCtx12); + + Messaging::ExchangeContext * exchangeCtx21 = ctx.NewExchangeToAlice(nullptr, false); + NL_TEST_ASSERT(apSuite, exchangeCtx21); + + Messaging::ExchangeContext * exchangeCtx22 = ctx.NewExchangeToAlice(nullptr, false); + NL_TEST_ASSERT(apSuite, exchangeCtx22); + + // InteractionModelEngine init + NL_TEST_ASSERT(apSuite, + CHIP_NO_ERROR == + engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reporting::GetDefaultReportScheduler())); + + // Verify that both Alice and Bob have no active subscriptions + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId) == false); + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId) == false); + + // Create and setup readHandler 1-1 + engine->GetReadHandlerPool().CreateObject(nullCallback, exchangeCtx11, ReadHandler::InteractionType::Subscribe, + reporting::GetDefaultReportScheduler()); + + // Create and setup readHandler 1-2 + ReadHandler * readHandler12 = engine->GetReadHandlerPool().CreateObject( + nullCallback, exchangeCtx12, ReadHandler::InteractionType::Subscribe, reporting::GetDefaultReportScheduler()); + + // Create and setup readHandler 2-1 + engine->GetReadHandlerPool().CreateObject(nullCallback, exchangeCtx21, ReadHandler::InteractionType::Subscribe, + reporting::GetDefaultReportScheduler()); + + // Create and setup readHandler 2-2 + ReadHandler * readHandler22 = engine->GetReadHandlerPool().CreateObject( + nullCallback, exchangeCtx22, ReadHandler::InteractionType::Subscribe, reporting::GetDefaultReportScheduler()); + + // Verify that both Alice and Bob have no active subscriptions + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId) == false); + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId) == false); + + // Set readHandler 1-2 to active + readHandler12->SetStateFlag(ReadHandler::ReadHandlerFlags::ActiveSubscription, true); + + // Verify that Bob has an active subscription + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId)); + + // Verify that Alice still doesn't have an active subscription + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId) == false); + + // Set readHandler 2-2 to active + readHandler22->SetStateFlag(ReadHandler::ReadHandlerFlags::ActiveSubscription, true); + + // Verify that Bob has an active subscription + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId)); + + // Verify that Alice still doesn't have an active subscription + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId)); + + // Set readHandler1 to inactive + readHandler12->SetStateFlag(ReadHandler::ReadHandlerFlags::ActiveSubscription, false); + + // Verify that Bob doesn't have an active subscription + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId) == false); + + // Verify that Alice still has an active subscription + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId)); + + // Clean up read handlers + engine->GetReadHandlerPool().ReleaseAll(); + + // Verify that both Alice and Bob have no active subscriptions + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId) == false); + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId) == false); +} + +/** + * @brief Verifies that SubjectHasActiveSubscription support CATs as a subject-id + */ +void TestInteractionModelEngine::TestSubjectHasActiveSubscriptionSubWithCAT(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *reinterpret_cast(apContext); + InteractionModelEngine * engine = InteractionModelEngine::GetInstance(); + NullReadHandlerCallback nullCallback; + + CASEAuthTag cat = 0x1111'0001; + CASEAuthTag invalidCAT = 0x1112'0001; + CATValues cats = CATValues{ { cat } }; + NodeId valideSubjectId = NodeIdFromCASEAuthTag(cat); + NodeId invalideSubjectId = NodeIdFromCASEAuthTag(invalidCAT); + FabricIndex bobFabricIndex = 1; + + // InteractionModelEngine init + NL_TEST_ASSERT(apSuite, + CHIP_NO_ERROR == + engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), reporting::GetDefaultReportScheduler())); + + // Make sure we are using CASE sessions, because there is no defunct-marking for PASE. + ctx.ExpireSessionBobToAlice(); + ctx.ExpireSessionAliceToBob(); + NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == ctx.CreateCASESessionBobToAlice(cats)); + NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == ctx.CreateCASESessionAliceToBob(cats)); + + // Create ExchangeContexts + Messaging::ExchangeContext * exchangeCtx = ctx.NewExchangeToBob(nullptr, false); + NL_TEST_ASSERT(apSuite, exchangeCtx); + + // Create readHandler + ReadHandler * readHandler = engine->GetReadHandlerPool().CreateObject( + nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe, reporting::GetDefaultReportScheduler()); + + // Verify there are not active subscriptions + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, valideSubjectId) == false); + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, invalideSubjectId) == false); + + // Set readHandler to active + readHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::ActiveSubscription, true); + + // Verify tthat valid subjectID has an active subscription + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, valideSubjectId)); + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, invalideSubjectId) == false); + + // Clean up read handlers + engine->GetReadHandlerPool().ReleaseAll(); + + // Verify there are not active subscriptions + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, valideSubjectId) == false); + NL_TEST_ASSERT(apSuite, engine->SubjectHasActiveSubscription(bobFabricIndex, invalideSubjectId) == false); +} + +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS + +/** + * @brief Test verifies the SubjectHasPersistedSubscription with single and multiple persisted subscriptions. + */ +void TestInteractionModelEngine::TestSubjectHasPersistedSubscription(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + CHIP_ERROR err = CHIP_NO_ERROR; + + chip::TestPersistentStorageDelegate storage; + chip::app::SimpleSubscriptionResumptionStorage subscriptionStorage; + + err = subscriptionStorage.Init(&storage); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), + app::reporting::GetDefaultReportScheduler(), nullptr, &subscriptionStorage); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + NodeId nodeId1 = 1; + FabricIndex fabric1 = 1; + SubscriptionId sub1 = 1; + NodeId nodeId2 = 2; + FabricIndex fabric2 = 2; + SubscriptionId sub2 = 2; + + SubscriptionResumptionStorage::SubscriptionInfo info1 = { .mNodeId = nodeId1, + .mFabricIndex = fabric1, + .mSubscriptionId = sub1 }; + SubscriptionResumptionStorage::SubscriptionInfo info2 = { .mNodeId = nodeId2, + .mFabricIndex = fabric2, + .mSubscriptionId = sub2 }; + + // Test with no persisted subscriptions - Should return false + NL_TEST_ASSERT(apSuite, InteractionModelEngine::GetInstance()->SubjectHasPersistedSubscription(fabric1, nodeId1) == false); + + // Add one entry + NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == subscriptionStorage.Save(info1)); + + // Verify that entry matches - Should return true + NL_TEST_ASSERT(apSuite, InteractionModelEngine::GetInstance()->SubjectHasPersistedSubscription(fabric1, nodeId1)); + + // Test with absent subscription - Should return false + NL_TEST_ASSERT(apSuite, InteractionModelEngine::GetInstance()->SubjectHasPersistedSubscription(fabric2, nodeId2) == false); + + // Add second entry + NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == subscriptionStorage.Save(info2)); + + // Verify that entry matches - Should return true + NL_TEST_ASSERT(apSuite, InteractionModelEngine::GetInstance()->SubjectHasPersistedSubscription(fabric2, nodeId2)); + NL_TEST_ASSERT(apSuite, InteractionModelEngine::GetInstance()->SubjectHasPersistedSubscription(fabric1, nodeId1)); + + // Remove an entry + NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == subscriptionStorage.Delete(nodeId1, fabric1, sub1)); + + // Test with absent subscription - Should return false + NL_TEST_ASSERT(apSuite, InteractionModelEngine::GetInstance()->SubjectHasPersistedSubscription(fabric1, nodeId1) == false); + + // Clean Up entries + subscriptionStorage.DeleteAll(fabric1); + subscriptionStorage.DeleteAll(fabric2); +} + +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + void TestInteractionModelEngine::TestSubscriptionResumptionTimer(nlTestSuite * apSuite, void * apContext) { TestContext & ctx = *static_cast(apContext); @@ -259,7 +681,66 @@ void TestInteractionModelEngine::TestSubscriptionResumptionTimer(nlTestSuite * a 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 + +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + +void TestInteractionModelEngine::TestDecrementNumSubscriptionsToResume(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + CHIP_ERROR err = CHIP_NO_ERROR; + InteractionModelEngine * engine = InteractionModelEngine::GetInstance(); + constexpr uint8_t kNumberOfSubsToResume = 5; + uint8_t numberOfSubsRemaining = kNumberOfSubsToResume; + + err = engine->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable(), app::reporting::GetDefaultReportScheduler()); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + +#if CHIP_CONFIG_ENABLE_ICD_CIP && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + ICDManager manager; + engine->SetICDManager(&manager); +#endif // CHIP_CONFIG_ENABLE_ICD_CIP && CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + + // Set number of subs + engine->mNumOfSubscriptionsToResume = kNumberOfSubsToResume; + +#if CHIP_CONFIG_ENABLE_ICD_CIP && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + // Verify mIsBootUpResumeSubscriptionExecuted has not been set + NL_TEST_ASSERT(apSuite, !manager.GetIsBootUpResumeSubscriptionExecuted()); +#endif // CHIP_CONFIG_ENABLE_ICD_CIP && CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + + // Decrease number of subs by 1 + numberOfSubsRemaining--; + engine->DecrementNumSubscriptionsToResume(); + NL_TEST_ASSERT_EQUALS(apSuite, numberOfSubsRemaining, engine->mNumOfSubscriptionsToResume); + + // Decrease to 0 subs remaining + while (numberOfSubsRemaining > 0) + { +#if CHIP_CONFIG_ENABLE_ICD_CIP && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + // Verify mIsBootUpResumeSubscriptionExecuted has not been set + NL_TEST_ASSERT(apSuite, !manager.GetIsBootUpResumeSubscriptionExecuted()); +#endif // CHIP_CONFIG_ENABLE_ICD_CIP && CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + + numberOfSubsRemaining--; + engine->DecrementNumSubscriptionsToResume(); + NL_TEST_ASSERT_EQUALS(apSuite, numberOfSubsRemaining, engine->mNumOfSubscriptionsToResume); + } + +#if CHIP_CONFIG_ENABLE_ICD_CIP && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + // Verify mIsBootUpResumeSubscriptionExecuted has been set + NL_TEST_ASSERT(apSuite, manager.GetIsBootUpResumeSubscriptionExecuted()); +#endif // CHIP_CONFIG_ENABLE_ICD_CIP && CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + + // Make sure we don't rollover / go negative + engine->DecrementNumSubscriptionsToResume(); + NL_TEST_ASSERT_EQUALS(apSuite, numberOfSubsRemaining, engine->mNumOfSubscriptionsToResume); + + // Clean up +#if CHIP_CONFIG_ENABLE_ICD_CIP && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + engine->SetICDManager(nullptr); +#endif // CHIP_CONFIG_ENABLE_ICD_CIP && CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION +} +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS } // namespace app } // namespace chip @@ -271,9 +752,18 @@ 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 +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS + NL_TEST_DEF("TestSubjectHasPersistedSubscription", chip::app::TestInteractionModelEngine::TestSubjectHasPersistedSubscription), + NL_TEST_DEF("TestDecrementNumSubscriptionsToResume", chip::app::TestInteractionModelEngine::TestDecrementNumSubscriptionsToResume), +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION NL_TEST_DEF("TestSubscriptionResumptionTimer", chip::app::TestInteractionModelEngine::TestSubscriptionResumptionTimer), -#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS + NL_TEST_DEF("TestSubjectHasActiveSubscriptionSingleSubOneEntry", chip::app::TestInteractionModelEngine::TestSubjectHasActiveSubscriptionSingleSubOneEntry), + NL_TEST_DEF("TestSubjectHasActiveSubscriptionSingleSubMultipleEntries", chip::app::TestInteractionModelEngine::TestSubjectHasActiveSubscriptionSingleSubMultipleEntries), + NL_TEST_DEF("TestSubjectHasActiveSubscriptionMultipleSubsSingleEntry", chip::app::TestInteractionModelEngine::TestSubjectHasActiveSubscriptionMultipleSubsSingleEntry), + NL_TEST_DEF("TestSubjectHasActiveSubscriptionMultipleSubsMultipleEntries", chip::app::TestInteractionModelEngine::TestSubjectHasActiveSubscriptionMultipleSubsMultipleEntries), + NL_TEST_DEF("TestSubjectHasActiveSubscriptionSubWithCAT", chip::app::TestInteractionModelEngine::TestSubjectHasActiveSubscriptionSubWithCAT), NL_TEST_SENTINEL() }; // clang-format on diff --git a/src/messaging/tests/MessagingContext.cpp b/src/messaging/tests/MessagingContext.cpp index 441492e80d5694..dc41292dc0f313 100644 --- a/src/messaging/tests/MessagingContext.cpp +++ b/src/messaging/tests/MessagingContext.cpp @@ -174,6 +174,13 @@ CHIP_ERROR MessagingContext::CreateCASESessionBobToAlice() CryptoContext::SessionRole::kInitiator); } +CHIP_ERROR MessagingContext::CreateCASESessionBobToAlice(const CATValues & cats) +{ + return mSessionManager.InjectCaseSessionWithTestKey(mSessionBobToAlice, kBobKeyId, kAliceKeyId, GetBobFabric()->GetNodeId(), + GetAliceFabric()->GetNodeId(), mBobFabricIndex, mAliceAddress, + CryptoContext::SessionRole::kInitiator, cats); +} + CHIP_ERROR MessagingContext::CreateSessionAliceToBob() { return mSessionManager.InjectPaseSessionWithTestKey(mSessionAliceToBob, kAliceKeyId, GetBobFabric()->GetNodeId(), kBobKeyId, @@ -187,6 +194,13 @@ CHIP_ERROR MessagingContext::CreateCASESessionAliceToBob() CryptoContext::SessionRole::kResponder); } +CHIP_ERROR MessagingContext::CreateCASESessionAliceToBob(const CATValues & cats) +{ + return mSessionManager.InjectCaseSessionWithTestKey(mSessionAliceToBob, kAliceKeyId, kBobKeyId, GetAliceFabric()->GetNodeId(), + GetBobFabric()->GetNodeId(), mAliceFabricIndex, mBobAddress, + CryptoContext::SessionRole::kResponder, cats); +} + CHIP_ERROR MessagingContext::CreatePASESessionCharlieToDavid() { return mSessionManager.InjectPaseSessionWithTestKey(mSessionCharlieToDavid, kCharlieKeyId, 0xdeadbeef, kDavidKeyId, diff --git a/src/messaging/tests/MessagingContext.h b/src/messaging/tests/MessagingContext.h index 31143a727326be..7ae452b9678a90 100644 --- a/src/messaging/tests/MessagingContext.h +++ b/src/messaging/tests/MessagingContext.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -141,8 +142,10 @@ class MessagingContext : public PlatformMemoryUser CHIP_ERROR CreateSessionBobToAlice(); // Creates PASE session CHIP_ERROR CreateCASESessionBobToAlice(); + CHIP_ERROR CreateCASESessionBobToAlice(const CATValues & cats); CHIP_ERROR CreateSessionAliceToBob(); // Creates PASE session CHIP_ERROR CreateCASESessionAliceToBob(); + CHIP_ERROR CreateCASESessionAliceToBob(const CATValues & cats); CHIP_ERROR CreateSessionBobToFriends(); // Creates PASE session CHIP_ERROR CreatePASESessionCharlieToDavid(); CHIP_ERROR CreatePASESessionDavidToCharlie(); diff --git a/src/test_driver/esp32/main/include/CHIPProjectConfig.h b/src/test_driver/esp32/main/include/CHIPProjectConfig.h index 3a25601cfd884e..1e4dc9626b5033 100644 --- a/src/test_driver/esp32/main/include/CHIPProjectConfig.h +++ b/src/test_driver/esp32/main/include/CHIPProjectConfig.h @@ -30,4 +30,6 @@ // Enable support functions for parsing command-line arguments #define CHIP_CONFIG_ENABLE_ARG_PARSER 1 +#define CONFIG_BUILD_FOR_HOST_UNIT_TEST 1 + #endif // CHIP_PROJECT_CONFIG_H