diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index d2c97e188192ed..443919026df953 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -114,6 +114,12 @@ source_set("pre-encoded-value") { ] } +source_set("subscription-manager") { + sources = [ "SubscriptionManager.h" ] + + public_deps = [ "${chip_root}/src/lib/core" ] +} + source_set("message-def") { sources = [ "MessageDef/ArrayBuilder.cpp", @@ -247,6 +253,7 @@ static_library("interaction-model") { ":app_config", ":message-def", ":paths", + ":subscription-manager", "${chip_root}/src/app/icd:icd_config", "${chip_root}/src/app/icd:observer", "${chip_root}/src/lib/address_resolve", diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index 34e2c2055e36f2..2b73251fff3b09 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -330,7 +330,7 @@ void InteractionModelEngine::ShutdownMatchingSubscriptions(const Optional #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include #include #include @@ -51,10 +36,14 @@ #include #include #include +#include +#include #include #include #include #include +#include +#include #include #include #include @@ -62,6 +51,17 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -79,7 +79,8 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, public Messaging::ExchangeDelegate, public CommandHandler::Callback, public ReadHandler::ManagementCallback, - public FabricTable::Delegate + public FabricTable::Delegate, + public SubscriptionManager { public: /** @@ -323,8 +324,9 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, CHIP_ERROR ResumeSubscriptions(); - // Check if a given subject (CAT or NodeId) has at least 1 active subscription - bool SubjectHasActiveSubscription(const FabricIndex aFabricIndex, const NodeId & subject); + bool SubjectHasActiveSubscription(const FabricIndex & aFabricIndex, const NodeId & subject) override; + + bool SubjectHasPersistedSubscription(const FabricIndex & aFabricIndex, const NodeId & subject) override; #if CONFIG_BUILD_FOR_HOST_UNIT_TEST // diff --git a/src/app/SubscriptionManager.h b/src/app/SubscriptionManager.h new file mode 100644 index 00000000000000..6ca47c19de8a8b --- /dev/null +++ b/src/app/SubscriptionManager.h @@ -0,0 +1,67 @@ +/* + * + * 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. + */ + +/** + * @file + * This file defines an interface that exposes all the public subscription management APIs. + * The interface is implemented by the InteractionModelEngine to avoid creating unnecessary dependencies + * Since the IMEgine has more dependency than its consummers need. + * By leveraging the SubscriptionManager APIs, a consummer avoids depending on the global data model functions. + */ + +#pragma once + +#include +#include + +namespace chip { +namespace app { + +class SubscriptionManager +{ +public: + virtual ~SubscriptionManager(){}; + + /** + * @brief Check if a given subject (CAT or operational NodeId) has at least 1 active subscription. + * + * @param[in] aFabricIndex fabric index of the subject + * @param[in] subject NodeId of the subect + * + * @return true subject has at least one active subscription with the device + * false subject doesn't have any acitve subscription with the device + */ + virtual bool SubjectHasActiveSubscription(const FabricIndex & aFabricIndex, const NodeId & subject) = 0; + + /** + * @brief Check if a given subject (CAT or operational NodeId) has at least 1 persisted subscription. + * If CHIP_CONFIG_PERSIST_SUBSCRIPTIONS is not enable, function alweays returns false. + * See the CHIP_CONFIG_PERSIST_SUBSCRIPTIONS for more information on persisted subscriptions. + * + * @param[in] aFabricIndex fabric index of the subject + * @param[in] subject NodeId of the subect + * + * @return true subject has at least one persisted subscription with the device + * false subject doesn't have any acitve subscription with the device + * false If CHIP_CONFIG_PERSIST_SUBSCRIPTIONS is not enabled + */ + virtual bool SubjectHasPersistedSubscription(const FabricIndex & aFabricIndex, const NodeId & subject) = 0; +}; + +} // namespace app +} // namespace chip diff --git a/src/app/icd/BUILD.gn b/src/app/icd/BUILD.gn index ba72052fffbffa..f1d3a8a382c9d0 100644 --- a/src/app/icd/BUILD.gn +++ b/src/app/icd/BUILD.gn @@ -70,7 +70,7 @@ source_set("manager") { ":notifier", ":observer", ":sender", - "${chip_root}/src/app:interaction-model", + "${chip_root}/src/app:subscription-manager", "${chip_root}/src/credentials:credentials", ] } @@ -114,5 +114,6 @@ source_set("configuration-data") { deps = [ ":icd_config", "${chip_root}/src/lib/core", + "${chip_root}/src/lib/support", ] } diff --git a/src/app/icd/ICDConfigurationData.cpp b/src/app/icd/ICDConfigurationData.cpp index 4df2ca75f217b9..9c2079a4386742 100644 --- a/src/app/icd/ICDConfigurationData.cpp +++ b/src/app/icd/ICDConfigurationData.cpp @@ -17,6 +17,7 @@ #include "ICDConfigurationData.h" #include +#include namespace chip { @@ -36,4 +37,16 @@ System::Clock::Milliseconds32 ICDConfigurationData::GetSlowPollingInterval() return mSlowPollingInterval; } +CHIP_ERROR ICDConfigurationData::SetModeDurations(uint32_t activeModeDuration_ms, uint32_t idleModeInterval_s) +{ + VerifyOrReturnError(activeModeDuration_ms <= (idleModeInterval_s * 1000), CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(idleModeInterval_s <= kMaxIdleModeDuration_s, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(idleModeInterval_s >= kMinIdleModeDuration_s, CHIP_ERROR_INVALID_ARGUMENT); + + mIdleModeDuration_s = idleModeInterval_s; + mActiveModeDuration_ms = activeModeDuration_ms; + + return CHIP_NO_ERROR; +} + } // namespace chip diff --git a/src/app/icd/ICDConfigurationData.h b/src/app/icd/ICDConfigurationData.h index 48f3966a5e5948..2762077429b006 100644 --- a/src/app/icd/ICDConfigurationData.h +++ b/src/app/icd/ICDConfigurationData.h @@ -25,6 +25,11 @@ namespace chip { namespace app { // Forward declaration of ICDManager to allow it to be friend with ICDConfigurationData class ICDManager; + +// Forward declaration of TestICDManager to allow it to be friend with the ICDConfigurationData +// Used in unit tests +class TestICDManager; + } // namespace app /** @@ -47,9 +52,9 @@ class ICDConfigurationData static ICDConfigurationData & GetInstance() { return instance; }; - uint32_t GetIdleModeDurationSec() { return mIdleInterval_s; } + uint32_t GetIdleModeDurationSec() { return mIdleModeDuration_s; } - uint32_t GetActiveModeDurationMs() { return mActiveInterval_ms; } + uint32_t GetActiveModeDurationMs() { return mActiveModeDuration_ms; } uint16_t GetActiveModeThresholdMs() { return mActiveThreshold_ms; } @@ -89,6 +94,7 @@ class ICDConfigurationData // the ICDManager, the ICDManager is a friend that can access the private setters. If a consummer needs to be notified when a // value is changed, they can leverage the Observer events the ICDManager generates. See src/app/icd/ICDStateObserver.h friend class chip::app::ICDManager; + friend class chip::app::TestICDManager; void SetICDMode(ICDMode mode) { mICDMode = mode; }; void SetICDCounter(uint32_t count) { mICDCounter = count; } @@ -97,15 +103,31 @@ class ICDConfigurationData static constexpr uint32_t kMinLitActiveModeThreshold_ms = 5000; - static_assert((CHIP_CONFIG_ICD_IDLE_MODE_DURATION_SEC) <= 64800, + /** + * @brief Change the ActiveModeDuration and IdleModeDuration value + * To only change one value, pass the old value for the other one + * + * @param[in] activeModeDuration_ms new ActiveModeDuration value + * @param[in] idleModeDuration_s new IdleModeDuration value + * @return CHIP_ERROR CHIP_ERROR_INVALID_ARGUMENT is returned if idleModeDuration_s is smaller than activeModeDuration_ms + * is returned if idleModeDuration_s is greater than 64800 seconds + * is returned if idleModeDuration_s is smaller than 1 seconds + * CHIP_NO_ERROR is returned if the new intervals were set + */ + CHIP_ERROR SetModeDurations(uint32_t activeModeDuration_ms, uint32_t idleModeDuration_s); + + static constexpr uint32_t kMaxIdleModeDuration_s = 64800; + static constexpr uint32_t kMinIdleModeDuration_s = 1; + + static_assert((CHIP_CONFIG_ICD_IDLE_MODE_DURATION_SEC) <= kMaxIdleModeDuration_s, "Spec requires the IdleModeDuration to be equal or inferior to 64800s."); - static_assert((CHIP_CONFIG_ICD_IDLE_MODE_DURATION_SEC) >= 1, + static_assert((CHIP_CONFIG_ICD_IDLE_MODE_DURATION_SEC) >= kMinIdleModeDuration_s, "Spec requires the IdleModeDuration to be equal or greater to 1s."); - uint32_t mIdleInterval_s = CHIP_CONFIG_ICD_IDLE_MODE_DURATION_SEC; + uint32_t mIdleModeDuration_s = CHIP_CONFIG_ICD_IDLE_MODE_DURATION_SEC; static_assert((CHIP_CONFIG_ICD_ACTIVE_MODE_DURATION_MS) <= (CHIP_CONFIG_ICD_IDLE_MODE_DURATION_SEC * kMillisecondsPerSecond), "Spec requires the IdleModeDuration be equal or greater to the ActiveModeDuration."); - uint32_t mActiveInterval_ms = CHIP_CONFIG_ICD_ACTIVE_MODE_DURATION_MS; + uint32_t mActiveModeDuration_ms = CHIP_CONFIG_ICD_ACTIVE_MODE_DURATION_MS; uint16_t mActiveThreshold_ms = CHIP_CONFIG_ICD_ACTIVE_MODE_THRESHOLD_MS; diff --git a/src/app/icd/ICDManager.cpp b/src/app/icd/ICDManager.cpp index 99e8e950dd853f..f6c1fe5cca32b8 100644 --- a/src/app/icd/ICDManager.cpp +++ b/src/app/icd/ICDManager.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -41,12 +40,13 @@ static_assert(UINT8_MAX >= CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS, "ICDManager::mOpenExchangeContextCount cannot hold count for the max exchange count"); void ICDManager::Init(PersistentStorageDelegate * storage, FabricTable * fabricTable, Crypto::SymmetricKeystore * symmetricKeystore, - Messaging::ExchangeManager * exchangeManager) + Messaging::ExchangeManager * exchangeManager, SubscriptionManager * manager) { VerifyOrDie(storage != nullptr); VerifyOrDie(fabricTable != nullptr); VerifyOrDie(symmetricKeystore != nullptr); VerifyOrDie(exchangeManager != nullptr); + VerifyOrDie(manager != nullptr); // LIT ICD Verification Checks if (SupportsFeature(Feature::kLongIdleTimeSupport)) @@ -63,11 +63,13 @@ void ICDManager::Init(PersistentStorageDelegate * storage, FabricTable * fabricT // "LIT support is required for slow polling intervals superior to 15 seconds"); } - mStorage = storage; - mFabricTable = fabricTable; VerifyOrDie(ICDNotifier::GetInstance().Subscribe(this) == CHIP_NO_ERROR); + + mStorage = storage; + mFabricTable = fabricTable; mSymmetricKeystore = symmetricKeystore; mExchangeManager = exchangeManager; + mSubManager = manager; VerifyOrDie(InitCounter() == CHIP_NO_ERROR); @@ -78,14 +80,19 @@ void ICDManager::Init(PersistentStorageDelegate * storage, FabricTable * fabricT void ICDManager::Shutdown() { ICDNotifier::GetInstance().Unsubscribe(this); + // cancel any running timer of the icd DeviceLayer::SystemLayer().CancelTimer(OnIdleModeDone, this); DeviceLayer::SystemLayer().CancelTimer(OnActiveModeDone, this); DeviceLayer::SystemLayer().CancelTimer(OnTransitionToIdle, this); + ICDConfigurationData::GetInstance().SetICDMode(ICDConfigurationData::ICDMode::SIT); mOperationalState = OperationalState::ActiveMode; - mStorage = nullptr; - mFabricTable = nullptr; + + mStorage = nullptr; + mFabricTable = nullptr; + mSubManager = nullptr; + mStateObserverPool.ReleaseAll(); mICDSenderPool.ReleaseAll(); } @@ -137,8 +144,7 @@ void ICDManager::SendCheckInMsgs() continue; } - bool active = - InteractionModelEngine::GetInstance()->SubjectHasActiveSubscription(entry.fabricIndex, entry.monitoredSubject); + bool active = mSubManager->SubjectHasActiveSubscription(entry.fabricIndex, entry.monitoredSubject); if (active) { continue; @@ -272,7 +278,9 @@ void ICDManager::UpdateOperationState(OperationalState state) mOperationalState = OperationalState::IdleMode; // When the active mode interval is 0, we stay in idleMode until a notification brings the icd into active mode - if (ICDConfigurationData::GetInstance().GetActiveModeDurationMs() > 0) + // unless the device would need to send Check-In messages + // TODO(#30281) : Verify how persistent subscriptions affects this at ICDManager::Init + if (ICDConfigurationData::GetInstance().GetActiveModeDurationMs() > 0 || CheckInMessagesWouldBeSent()) { uint32_t idleModeDuration = ICDConfigurationData::GetInstance().GetIdleModeDurationSec(); DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(idleModeDuration), OnIdleModeDone, this); @@ -538,5 +546,44 @@ void ICDManager::postObserverEvent(ObserverEventType event) } }); } + +bool ICDManager::CheckInMessagesWouldBeSent() +{ + for (const auto & fabricInfo : *mFabricTable) + { + uint16_t supported_clients = ICDConfigurationData::GetInstance().GetClientsSupportedPerFabric(); + + ICDMonitoringTable table(*mStorage, fabricInfo.GetFabricIndex(), supported_clients /*Table entry limit*/, + mSymmetricKeystore); + if (table.IsEmpty()) + { + continue; + } + + for (uint16_t i = 0; i < table.Limit(); i++) + { + ICDMonitoringEntry entry(mSymmetricKeystore); + CHIP_ERROR err = table.Get(i, entry); + if (err == CHIP_ERROR_NOT_FOUND) + { + break; + } + + if (err != CHIP_NO_ERROR) + { + // Try to fetch the next entry upon failure (should not happen). + ChipLogError(AppServer, "Failed to retrieved ICDMonitoring entry, will try next entry."); + continue; + } + + // At least one registration would require a Check-In message + VerifyOrReturnValue(mSubManager->SubjectHasActiveSubscription(entry.fabricIndex, entry.monitoredSubject), true); + } + } + + // None of the registrations would require a Check-In message + return false; +} + } // namespace app } // namespace chip diff --git a/src/app/icd/ICDManager.h b/src/app/icd/ICDManager.h index 81011c688858c3..429942302d2227 100644 --- a/src/app/icd/ICDManager.h +++ b/src/app/icd/ICDManager.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include #include #include @@ -66,7 +67,7 @@ class ICDManager : public ICDListener ICDManager() {} void Init(PersistentStorageDelegate * storage, FabricTable * fabricTable, Crypto::SymmetricKeystore * symmetricKeyStore, - Messaging::ExchangeManager * exchangeManager); + Messaging::ExchangeManager * exchangeManager, SubscriptionManager * manager); void Shutdown(); void UpdateICDMode(); void UpdateOperationState(OperationalState state); @@ -100,7 +101,7 @@ class ICDManager : public ICDListener #endif // Implementation of ICDListener functions. - // Callers must origin from the chip task context or be holding the ChipStack lock. + // Callers must origin from the chip task context or hold the ChipStack lock. void OnNetworkActivity() override; void OnKeepActiveRequest(KeepActiveFlags request) override; void OnActiveRequestWithdrawal(KeepActiveFlags request) override; @@ -129,15 +130,28 @@ class ICDManager : public ICDListener uint8_t mCheckInRequestCount = 0; private: + /** + * @brief Function checks if at least one client registration would require a Check-In message + * + * @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. + */ + bool CheckInMessagesWouldBeSent(); + KeepActiveFlags mKeepActiveFlags{ 0 }; // Initialize mOperationalState to ActiveMode so the init sequence at bootup triggers the IdleMode behaviour first. - OperationalState mOperationalState = OperationalState::ActiveMode; + OperationalState mOperationalState = OperationalState::ActiveMode; + PersistentStorageDelegate * mStorage = nullptr; FabricTable * mFabricTable = nullptr; Messaging::ExchangeManager * mExchangeManager = nullptr; - bool mTransitionToIdleCalled = false; Crypto::SymmetricKeystore * mSymmetricKeystore = nullptr; + SubscriptionManager * mSubManager = nullptr; + + bool mTransitionToIdleCalled = false; + ObjectPool mStateObserverPool; ObjectPool mICDSenderPool; diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp index 4a0fa91f56d520..3c33c45537d5ef 100644 --- a/src/app/server/Server.cpp +++ b/src/app/server/Server.cpp @@ -339,7 +339,8 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams) mICDManager.RegisterObserver(mReportScheduler); mICDManager.RegisterObserver(&app::DnssdServer::Instance()); - mICDManager.Init(mDeviceStorage, &GetFabricTable(), mSessionKeystore, &mExchangeMgr); + mICDManager.Init(mDeviceStorage, &GetFabricTable(), mSessionKeystore, &mExchangeMgr, + chip::app::InteractionModelEngine::GetInstance()); #endif // CHIP_CONFIG_ENABLE_ICD_SERVER // This code is necessary to restart listening to existing groups after a reboot diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index 9a21f1f4589d81..520fb3adf85675 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -231,7 +231,7 @@ chip_test_suite_using_nltest("tests") { # Do not run TestCommissionManager when running ICD specific unit tests. # ICDManager has a dependency on the Accessors.h file which causes a link error # when building the TestCommissionManager - if (!chip_enable_icd_server && chip_config_network_layer_ble && + if (chip_config_network_layer_ble && (chip_device_platform == "linux" || chip_device_platform == "darwin")) { test_sources += [ "TestCommissionManager.cpp" ] public_deps += [ "${chip_root}/src/app/server" ] diff --git a/src/app/tests/TestICDManager.cpp b/src/app/tests/TestICDManager.cpp index f4ff46fd713556..06669b45d1cd51 100644 --- a/src/app/tests/TestICDManager.cpp +++ b/src/app/tests/TestICDManager.cpp @@ -16,11 +16,14 @@ * limitations under the License. */ #include +#include #include #include #include #include #include +#include +#include #include #include #include @@ -68,6 +71,21 @@ class TestICDStateObserver : public app::ICDStateObserver void OnICDModeChange() {} }; +class TestSubscriptionManager : public SubscriptionManager +{ +public: + TestSubscriptionManager() = default; + ~TestSubscriptionManager(){}; + + void SetReturnValue(bool value) { mReturnValue = value; }; + + bool SubjectHasActiveSubscription(const FabricIndex & aFabricIndex, const NodeId & subject) { return mReturnValue; }; + bool SubjectHasPersistedSubscription(const FabricIndex & aFabricIndex, const NodeId & subject) { return mReturnValue; }; + +private: + bool mReturnValue = false; +}; + class TestContext : public chip::Test::AppContext { public: @@ -93,7 +111,7 @@ class TestContext : public chip::Test::AppContext CHIP_ERROR SetUp() override { ReturnErrorOnFailure(chip::Test::AppContext::SetUp()); - mICDManager.Init(&testStorage, &GetFabricTable(), &mKeystore, &GetExchangeManager()); + mICDManager.Init(&testStorage, &GetFabricTable(), &mKeystore, &GetExchangeManager(), &mSubManager); mICDManager.RegisterObserver(&mICDStateObserver); return CHIP_NO_ERROR; } @@ -108,6 +126,7 @@ class TestContext : public chip::Test::AppContext System::Clock::Internal::MockClock mMockClock; TestSessionKeystoreImpl mKeystore; app::ICDManager mICDManager; + TestSubscriptionManager mSubManager; TestPersistentStorageDelegate testStorage; private: @@ -142,17 +161,17 @@ class TestICDManager // After the init we should be in Idle mode NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode); AdvanceClockAndRunEventLoop(ctx, SecondsToMilliseconds(ICDConfigurationData::GetInstance().GetIdleModeDurationSec()) + 1); - // Idle mode interval expired, ICDManager transitioned to the ActiveMode. + // Idle mode Duration expired, ICDManager transitioned to the ActiveMode. NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode); AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeDurationMs() + 1); - // Active mode interval expired, ICDManager transitioned to the IdleMode. + // Active mode Duration expired, ICDManager transitioned to the IdleMode. NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode); AdvanceClockAndRunEventLoop(ctx, SecondsToMilliseconds(ICDConfigurationData::GetInstance().GetIdleModeDurationSec()) + 1); - // Idle mode interval expired, ICDManager transitioned to the ActiveMode. + // Idle mode Duration expired, ICDManager transitioned to the ActiveMode. NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode); // Events updating the Operation to Active mode can extend the current active mode time by 1 Active mode threshold. - // Kick an active Threshold just before the end of the Active interval and validate that the active mode is extended. + // Kick an active Threshold just before the end of the ActiveMode duration and validate that the active mode is extended. AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeDurationMs() - 1); ICDNotifier::GetInstance().NotifyNetworkActivityNotification(); AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeThresholdMs() / 2); @@ -161,6 +180,175 @@ class TestICDManager NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode); } + /** + * @brief Test verifies that the ICDManager starts its timers correctly based on if it will have any messages to send + * when the IdleModeDuration expires + */ + static void TestICDModeDurationsWith0ActiveModeDurationWithoutActiveSub(nlTestSuite * aSuite, void * aContext) + { + TestContext * ctx = static_cast(aContext); + typedef ICDListener::ICDManagementEvents ICDMEvent; + ICDConfigurationData & icdConfigData = ICDConfigurationData::GetInstance(); + + // Set FeatureMap - Configures CIP, UAT and LITS to 1 + ctx->mICDManager.SetTestFeatureMapValue(0x07); + + // Set that there are no matching subscriptions + ctx->mSubManager.SetReturnValue(false); + + // Set New durations for test case + uint32_t oldActiveModeDuration_ms = icdConfigData.GetActiveModeDurationMs(); + icdConfigData.SetModeDurations(0, icdConfigData.GetIdleModeDurationSec()); + + // Verify That ICDManager starts in Idle + NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode); + + // Reset IdleModeInterval since it was started before the ActiveModeDuration change + AdvanceClockAndRunEventLoop(ctx, SecondsToMilliseconds(icdConfigData.GetIdleModeDurationSec()) + 1); + NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode); + + // Force the device to return to IdleMode - Increase time by ActiveModeThreshold since ActiveModeDuration is now 0 + AdvanceClockAndRunEventLoop(ctx, icdConfigData.GetActiveModeThresholdMs() + 1); + NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode); + + // Expire Idle mode duration; ICDManager should remain in IdleMode since it has no message to send + AdvanceClockAndRunEventLoop(ctx, SecondsToMilliseconds(icdConfigData.GetIdleModeDurationSec()) + 1); + NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode); + + // Add an entry to the ICDMonitoringTable + ICDMonitoringTable table(ctx->testStorage, kTestFabricIndex1, kMaxTestClients, &(ctx->mKeystore)); + + ICDMonitoringEntry entry(&(ctx->mKeystore)); + entry.checkInNodeID = kClientNodeId11; + entry.monitoredSubject = kClientNodeId11; + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == entry.SetKey(ByteSpan(kKeyBuffer1a))); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == table.Set(0, entry)); + + // Trigger register event after first entry was added + ICDNotifier::GetInstance().NotifyICDManagementEvent(ICDMEvent::kTableUpdated); + + // Check ICDManager is now in the LIT operating mode + NL_TEST_ASSERT(aSuite, icdConfigData.GetICDMode() == ICDConfigurationData::ICDMode::LIT); + + // Kick an ActiveModeThreshold since a Registration can only happen from an incoming message that would transition the ICD + // to ActiveMode + ICDNotifier::GetInstance().NotifyNetworkActivityNotification(); + NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode); + + // Return the device to return to IdleMode - Increase time by ActiveModeThreshold since ActiveModeDuration is 0 + AdvanceClockAndRunEventLoop(ctx, icdConfigData.GetActiveModeThresholdMs() + 1); + NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode); + + // Expire IdleModeDuration - Device should be in ActiveMode since it has an ICDM registration + AdvanceClockAndRunEventLoop(ctx, SecondsToMilliseconds(icdConfigData.GetIdleModeDurationSec()) + 1); + NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode); + + // Remove entry from the fabric - ICDManager won't have any messages to send + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == table.Remove(0)); + NL_TEST_ASSERT(aSuite, table.IsEmpty()); + + // Return the device to return to IdleMode - Increase time by ActiveModeThreshold since ActiveModeDuration is 0 + AdvanceClockAndRunEventLoop(ctx, icdConfigData.GetActiveModeThresholdMs() + 1); + NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode); + + // Expire Idle mode duration; ICDManager should remain in IdleMode since it has no message to send + AdvanceClockAndRunEventLoop(ctx, SecondsToMilliseconds(icdConfigData.GetIdleModeDurationSec()) + 1); + NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode); + + // Reset Old durations + icdConfigData.SetModeDurations(oldActiveModeDuration_ms, icdConfigData.GetIdleModeDurationSec()); + } + + /** + * @brief Test verifies that the ICDManager remains in IdleMode since it will not have any messages to send + * when the IdleModeDuration expires + */ + static void TestICDModeDurationsWith0ActiveModeDurationWithActiveSub(nlTestSuite * aSuite, void * aContext) + { + TestContext * ctx = static_cast(aContext); + typedef ICDListener::ICDManagementEvents ICDMEvent; + ICDConfigurationData & icdConfigData = ICDConfigurationData::GetInstance(); + + // Set FeatureMap - Configures CIP, UAT and LITS to 1 + ctx->mICDManager.SetTestFeatureMapValue(0x07); + + // Set that there are not matching subscriptions + ctx->mSubManager.SetReturnValue(true); + + // Set New durations for test case + uint32_t oldActiveModeDuration_ms = icdConfigData.GetActiveModeDurationMs(); + icdConfigData.SetModeDurations(0, icdConfigData.GetIdleModeDurationSec()); + + // Verify That ICDManager starts in Idle + NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode); + + // Reset IdleModeInterval since it was started before the ActiveModeDuration change + AdvanceClockAndRunEventLoop(ctx, SecondsToMilliseconds(icdConfigData.GetIdleModeDurationSec()) + 1); + NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode); + + // Force the device to return to IdleMode - Increase time by ActiveModeThreshold since ActiveModeDuration is now 0 + AdvanceClockAndRunEventLoop(ctx, icdConfigData.GetActiveModeThresholdMs() + 1); + NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode); + + // Expire Idle mode duration; ICDManager should remain in IdleMode since it has no message to send + AdvanceClockAndRunEventLoop(ctx, SecondsToMilliseconds(icdConfigData.GetIdleModeDurationSec()) + 1); + NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode); + + // Add an entry to the ICDMonitoringTable + ICDMonitoringTable table(ctx->testStorage, kTestFabricIndex1, kMaxTestClients, &(ctx->mKeystore)); + + ICDMonitoringEntry entry(&(ctx->mKeystore)); + entry.checkInNodeID = kClientNodeId11; + entry.monitoredSubject = kClientNodeId11; + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == entry.SetKey(ByteSpan(kKeyBuffer1a))); + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == table.Set(0, entry)); + + // Trigger register event after first entry was added + ICDNotifier::GetInstance().NotifyICDManagementEvent(ICDMEvent::kTableUpdated); + + // Check ICDManager is now in the LIT operating mode + NL_TEST_ASSERT(aSuite, icdConfigData.GetICDMode() == ICDConfigurationData::ICDMode::LIT); + + // Kick an ActiveModeThreshold since a Registration can only happen from an incoming message that would transition the ICD + // to ActiveMode + ICDNotifier::GetInstance().NotifyNetworkActivityNotification(); + NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode); + + // Return the device to return to IdleMode - Increase time by ActiveModeThreshold since ActiveModeDuration is 0 + AdvanceClockAndRunEventLoop(ctx, icdConfigData.GetActiveModeThresholdMs() + 1); + NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode); + + // Expire IdleModeDuration - Device stay in IdleMode since it has an active subscription for the ICDM entry + AdvanceClockAndRunEventLoop(ctx, SecondsToMilliseconds(icdConfigData.GetIdleModeDurationSec()) + 1); + NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode); + + // Remove entry from the fabric + NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == table.Remove(0)); + NL_TEST_ASSERT(aSuite, table.IsEmpty()); + + // Trigger unregister event after last entry was removed + ICDNotifier::GetInstance().NotifyICDManagementEvent(ICDMEvent::kTableUpdated); + + // Check ICDManager is now in the LIT operating mode + NL_TEST_ASSERT(aSuite, icdConfigData.GetICDMode() == ICDConfigurationData::ICDMode::SIT); + + // Kick an ActiveModeThreshold since a Unregistration can only happen from an incoming message that would transition the ICD + // to ActiveMode + ICDNotifier::GetInstance().NotifyNetworkActivityNotification(); + NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode); + + // Return the device to return to IdleMode - Increase time by ActiveModeThreshold since ActiveModeDuration is 0 + AdvanceClockAndRunEventLoop(ctx, icdConfigData.GetActiveModeThresholdMs() + 1); + NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode); + + // Expire Idle mode duration; ICDManager should remain in IdleMode since it has no message to send + AdvanceClockAndRunEventLoop(ctx, SecondsToMilliseconds(icdConfigData.GetIdleModeDurationSec()) + 1); + NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode); + + // Reset Old durations + icdConfigData.SetModeDurations(oldActiveModeDuration_ms, icdConfigData.GetIdleModeDurationSec()); + } + static void TestKeepActivemodeRequests(nlTestSuite * aSuite, void * aContext) { TestContext * ctx = static_cast(aContext); @@ -170,7 +358,7 @@ class TestICDManager // Setting a requirement will transition the ICD to active mode. notifier.NotifyActiveRequestNotification(ActiveFlag::kCommissioningWindowOpen); NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode); - // Advance time so active mode interval expires. + // Advance time so active mode duration expires. AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeDurationMs() + 1); // Requirement flag still set. We stay in active mode NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode); @@ -183,13 +371,13 @@ class TestICDManager // Requirement will transition us to active mode. NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode); - // Advance time, but by less than the active mode interval and remove the requirement. + // Advance time, but by less than the active mode duration and remove the requirement. // We should stay in active mode. AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeDurationMs() / 2); notifier.NotifyActiveRequestWithdrawal(ActiveFlag::kFailSafeArmed); NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode); - // Advance time again, The activemode interval is completed. + // Advance time again, The activemode duration is completed. AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeDurationMs() + 1); NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode); @@ -197,7 +385,7 @@ class TestICDManager notifier.NotifyActiveRequestNotification(ActiveFlag::kFailSafeArmed); notifier.NotifyActiveRequestNotification(ActiveFlag::kExchangeContextOpen); NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode); - // advance time so the active mode interval expires. + // advance time so the active mode duration expires. AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeDurationMs() + 1); // A requirement flag is still set. We stay in active mode. NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode); @@ -352,6 +540,10 @@ namespace { static const nlTest sTests[] = { NL_TEST_DEF("TestICDModeDurations", TestICDManager::TestICDModeDurations), NL_TEST_DEF("TestOnSubscriptionReport", TestICDManager::TestOnSubscriptionReport), + NL_TEST_DEF("TestICDModeDurationsWith0ActiveModeDurationWithoutActiveSub", + TestICDManager::TestICDModeDurationsWith0ActiveModeDurationWithoutActiveSub), + NL_TEST_DEF("TestICDModeDurationsWith0ActiveModeDurationWithActiveSub", + TestICDManager::TestICDModeDurationsWith0ActiveModeDurationWithActiveSub), NL_TEST_DEF("TestKeepActivemodeRequests", TestICDManager::TestKeepActivemodeRequests), NL_TEST_DEF("TestICDMRegisterUnregisterEvents", TestICDManager::TestICDMRegisterUnregisterEvents), NL_TEST_DEF("TestICDCounter", TestICDManager::TestICDCounter),