blob: fd93a146cf2755c69f01b87ad28849fc2cbab0fc [file] [log] [blame]
/*
*
* 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.
*/
#include <app/EventManagement.h>
#include <app/SubscriptionsInfoProvider.h>
#include <app/TestEventTriggerDelegate.h>
#include <app/icd/server/ICDConfigurationData.h>
#include <app/icd/server/ICDManager.h>
#include <app/icd/server/ICDNotifier.h>
#include <app/icd/server/ICDStateObserver.h>
#include <app/tests/AppTestContext.h>
#include <lib/core/DataModelTypes.h>
#include <lib/core/NodeId.h>
#include <lib/support/TestPersistentStorageDelegate.h>
#include <lib/support/TimeUtils.h>
#include <lib/support/UnitTestContext.h>
#include <lib/support/UnitTestExtendedAssertions.h>
#include <lib/support/UnitTestRegistration.h>
#include <nlunit-test.h>
#include <system/SystemLayerImpl.h>
#include <crypto/DefaultSessionKeystore.h>
using namespace chip;
using namespace chip::app;
using namespace chip::System;
using namespace chip::System::Clock;
using namespace chip::System::Clock::Literals;
using TestSessionKeystoreImpl = Crypto::DefaultSessionKeystore;
namespace {
// Test Values
constexpr uint16_t kMaxTestClients = 2;
constexpr FabricIndex kTestFabricIndex1 = 1;
constexpr FabricIndex kTestFabricIndex2 = kMaxValidFabricIndex;
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
};
constexpr uint8_t kKeyBuffer1b[] = {
0xf1, 0xe1, 0xd1, 0xc1, 0xb1, 0xa1, 0x91, 0x81, 0x71, 0x61, 0x51, 0x14, 0x31, 0x21, 0x11, 0x01
};
constexpr uint8_t kKeyBuffer2a[] = {
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f
};
constexpr uint8_t kKeyBuffer2b[] = {
0xf2, 0xe2, 0xd2, 0xc2, 0xb2, 0xa2, 0x92, 0x82, 0x72, 0x62, 0x52, 0x42, 0x32, 0x22, 0x12, 0x02
};
// Taken from the ICDManager Implementation
enum class ICDTestEventTriggerEvent : uint64_t
{
kAddActiveModeReq = 0x0046'0000'00000001,
kRemoveActiveModeReq = 0x0046'0000'00000002,
};
class TestICDStateObserver : public app::ICDStateObserver
{
public:
void OnEnterActiveMode() {}
void OnTransitionToIdle() {}
void OnICDModeChange() {}
};
class TestSubscriptionsInfoProvider : public SubscriptionsInfoProvider
{
public:
TestSubscriptionsInfoProvider() = default;
~TestSubscriptionsInfoProvider(){};
void SetHasActiveSubscription(bool value) { mHasActiveSubscription = value; };
void SetHasPersistedSubscription(bool value) { mHasPersistedSubscription = value; };
bool SubjectHasActiveSubscription(FabricIndex aFabricIndex, NodeId subject) { return mHasActiveSubscription; };
bool SubjectHasPersistedSubscription(FabricIndex aFabricIndex, NodeId subject) { return mHasPersistedSubscription; };
private:
bool mHasActiveSubscription = false;
bool mHasPersistedSubscription = false;
};
class TestContext : public chip::Test::AppContext
{
public:
// Performs shared setup for all tests in the test suite
CHIP_ERROR SetUpTestSuite() override
{
ReturnErrorOnFailure(chip::Test::AppContext::SetUpTestSuite());
DeviceLayer::SetSystemLayerForTesting(&GetSystemLayer());
mRealClock = &chip::System::SystemClock();
System::Clock::Internal::SetSystemClockForTesting(&mMockClock);
return CHIP_NO_ERROR;
}
// Performs shared teardown for all tests in the test suite
void TearDownTestSuite() override
{
System::Clock::Internal::SetSystemClockForTesting(mRealClock);
DeviceLayer::SetSystemLayerForTesting(nullptr);
chip::Test::AppContext::TearDownTestSuite();
}
// Performs setup for each individual test in the test suite
CHIP_ERROR SetUp() override
{
ReturnErrorOnFailure(chip::Test::AppContext::SetUp());
mICDManager.Init(&testStorage, &GetFabricTable(), &mKeystore, &GetExchangeManager(), &mSubInfoProvider);
mICDManager.RegisterObserver(&mICDStateObserver);
return CHIP_NO_ERROR;
}
// Performs teardown for each individual test in the test suite
void TearDown() override
{
mICDManager.Shutdown();
chip::Test::AppContext::TearDown();
}
System::Clock::Internal::MockClock mMockClock;
TestSessionKeystoreImpl mKeystore;
app::ICDManager mICDManager;
TestSubscriptionsInfoProvider mSubInfoProvider;
TestPersistentStorageDelegate testStorage;
TestICDStateObserver mICDStateObserver;
private:
System::Clock::ClockBase * mRealClock;
};
} // namespace
namespace chip {
namespace app {
class TestICDManager
{
public:
/*
* Advance the test Mock clock time by the amout passed in argument
* and then force the SystemLayer Timer event loop. It will check for any expired timer,
* and invoke their callbacks if there are any.
*
* @param time_ms: Value in milliseconds.
*/
static void AdvanceClockAndRunEventLoop(TestContext * ctx, Milliseconds64 time)
{
ctx->mMockClock.AdvanceMonotonic(time);
ctx->GetIOContext().DriveIO();
}
static void TestICDModeDurations(nlTestSuite * aSuite, void * aContext)
{
TestContext * ctx = static_cast<TestContext *>(aContext);
// After the init we should be in Idle mode
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetIdleModeDuration() + 1_s);
// Idle mode Duration expired, ICDManager transitioned to the ActiveMode.
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeDuration() + 1_ms32);
// Active mode Duration expired, ICDManager transitioned to the IdleMode.
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetIdleModeDuration() + 1_s);
// 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 ActiveMode duration and validate that the active mode is extended.
AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeDuration() - 1_ms32);
ICDNotifier::GetInstance().NotifyNetworkActivityNotification();
AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeThreshold() / 2);
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeThreshold());
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<TestContext *>(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->mSubInfoProvider.SetHasActiveSubscription(false);
ctx->mSubInfoProvider.SetHasPersistedSubscription(false);
// Set New durations for test case
Milliseconds32 oldActiveModeDuration = icdConfigData.GetActiveModeDuration();
icdConfigData.SetModeDurations(MakeOptional<Milliseconds32>(0), NullOptional);
// 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, icdConfigData.GetIdleModeDuration() + 1_s);
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.GetActiveModeThreshold() + 1_ms16);
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, icdConfigData.GetIdleModeDuration() + 1_s);
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.GetActiveModeThreshold() + 1_ms16);
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, icdConfigData.GetIdleModeDuration() + 1_s);
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.GetActiveModeThreshold() + 1_ms16);
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, icdConfigData.GetIdleModeDuration() + 1_s);
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
// Reset Old durations
icdConfigData.SetModeDurations(MakeOptional(oldActiveModeDuration), NullOptional);
}
/**
* @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<TestContext *>(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->mSubInfoProvider.SetHasActiveSubscription(true);
ctx->mSubInfoProvider.SetHasPersistedSubscription(true);
// Set New durations for test case
Milliseconds32 oldActiveModeDuration = icdConfigData.GetActiveModeDuration();
icdConfigData.SetModeDurations(MakeOptional<Milliseconds32>(0), NullOptional);
// 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, icdConfigData.GetIdleModeDuration() + 1_s);
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.GetActiveModeThreshold() + 1_ms16);
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, icdConfigData.GetIdleModeDuration() + 1_s);
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.GetActiveModeThreshold() + 1_ms16);
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, icdConfigData.GetIdleModeDuration() + 1_s);
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.GetActiveModeThreshold() + 1_ms16);
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, icdConfigData.GetIdleModeDuration() + 1_s);
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
// Reset Old durations
icdConfigData.SetModeDurations(MakeOptional<Milliseconds32>(oldActiveModeDuration), NullOptional);
}
static void TestKeepActivemodeRequests(nlTestSuite * aSuite, void * aContext)
{
TestContext * ctx = static_cast<TestContext *>(aContext);
typedef ICDListener::KeepActiveFlag ActiveFlag;
ICDNotifier notifier = ICDNotifier::GetInstance();
// 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 duration expires.
AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeDuration() + 1_ms32);
// Requirement flag still set. We stay in active mode
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
// Remove requirement. we should directly transition to idle mode.
notifier.NotifyActiveRequestWithdrawal(ActiveFlag::kCommissioningWindowOpen);
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
notifier.NotifyActiveRequestNotification(ActiveFlag::kFailSafeArmed);
// 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 duration and remove the requirement.
// We should stay in active mode.
AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeDuration() / 2);
notifier.NotifyActiveRequestWithdrawal(ActiveFlag::kFailSafeArmed);
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
// Advance time again, The activemode duration is completed.
AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeDuration() + 1_ms32);
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
// Set two requirements
notifier.NotifyActiveRequestNotification(ActiveFlag::kFailSafeArmed);
notifier.NotifyActiveRequestNotification(ActiveFlag::kExchangeContextOpen);
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
// advance time so the active mode duration expires.
AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeDuration() + 1_ms32);
// A requirement flag is still set. We stay in active mode.
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
// remove 1 requirement. Active mode is maintained
notifier.NotifyActiveRequestWithdrawal(ActiveFlag::kFailSafeArmed);
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
// remove the last requirement
notifier.NotifyActiveRequestWithdrawal(ActiveFlag::kExchangeContextOpen);
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
}
/*
* Test that verifies that the ICDManager is the correct operating mode based on entries
* in the ICDMonitoringTable
*/
static void TestICDMRegisterUnregisterEvents(nlTestSuite * aSuite, void * aContext)
{
TestContext * ctx = static_cast<TestContext *>(aContext);
typedef ICDListener::ICDManagementEvents ICDMEvent;
ICDNotifier notifier = ICDNotifier::GetInstance();
// Set FeatureMap
// Configures CIP, UAT and LITS to 1
ctx->mICDManager.SetTestFeatureMapValue(0x07);
// Check ICDManager starts in SIT mode if no entries are present
NL_TEST_ASSERT(aSuite, ICDConfigurationData::GetInstance().GetICDMode() == ICDConfigurationData::ICDMode::SIT);
// Trigger a "fake" register, ICDManager shoudl remain in SIT mode
notifier.NotifyICDManagementEvent(ICDMEvent::kTableUpdated);
// Check ICDManager stayed in SIT mode
NL_TEST_ASSERT(aSuite, ICDConfigurationData::GetInstance().GetICDMode() == ICDConfigurationData::ICDMode::SIT);
// Create tables with different fabrics
ICDMonitoringTable table1(ctx->testStorage, kTestFabricIndex1, kMaxTestClients, &(ctx->mKeystore));
ICDMonitoringTable table2(ctx->testStorage, kTestFabricIndex2, kMaxTestClients, &(ctx->mKeystore));
// Add first entry to the first fabric
ICDMonitoringEntry entry1(&(ctx->mKeystore));
entry1.checkInNodeID = kClientNodeId11;
entry1.monitoredSubject = kClientNodeId12;
NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == entry1.SetKey(ByteSpan(kKeyBuffer1a)));
NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == table1.Set(0, entry1));
// Trigger register event after first entry was added
notifier.NotifyICDManagementEvent(ICDMEvent::kTableUpdated);
// Check ICDManager is now in the LIT operating mode
NL_TEST_ASSERT(aSuite, ICDConfigurationData::GetInstance().GetICDMode() == ICDConfigurationData::ICDMode::LIT);
// Add second entry to the first fabric
ICDMonitoringEntry entry2(&(ctx->mKeystore));
entry2.checkInNodeID = kClientNodeId12;
entry2.monitoredSubject = kClientNodeId11;
NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == entry2.SetKey(ByteSpan(kKeyBuffer1b)));
NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == table1.Set(1, entry2));
// Trigger register event after first entry was added
notifier.NotifyICDManagementEvent(ICDMEvent::kTableUpdated);
// Check ICDManager is now in the LIT operating mode
NL_TEST_ASSERT(aSuite, ICDConfigurationData::GetInstance().GetICDMode() == ICDConfigurationData::ICDMode::LIT);
// Add first entry to the first fabric
ICDMonitoringEntry entry3(&(ctx->mKeystore));
entry3.checkInNodeID = kClientNodeId21;
entry3.monitoredSubject = kClientNodeId22;
NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == entry3.SetKey(ByteSpan(kKeyBuffer2a)));
NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == table2.Set(0, entry3));
// Trigger register event after first entry was added
notifier.NotifyICDManagementEvent(ICDMEvent::kTableUpdated);
// Check ICDManager is now in the LIT operating mode
NL_TEST_ASSERT(aSuite, ICDConfigurationData::GetInstance().GetICDMode() == ICDConfigurationData::ICDMode::LIT);
// Add second entry to the first fabric
ICDMonitoringEntry entry4(&(ctx->mKeystore));
entry4.checkInNodeID = kClientNodeId22;
entry4.monitoredSubject = kClientNodeId21;
NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == entry4.SetKey(ByteSpan(kKeyBuffer2b)));
NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == table2.Set(1, entry4));
// Clear a fabric
NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == table2.RemoveAll());
// Trigger register event after fabric was cleared
notifier.NotifyICDManagementEvent(ICDMEvent::kTableUpdated);
// Check ICDManager is still in the LIT operating mode
NL_TEST_ASSERT(aSuite, ICDConfigurationData::GetInstance().GetICDMode() == ICDConfigurationData::ICDMode::LIT);
// Remove single entry from remaining fabric
NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == table1.Remove(1));
// Trigger register event after fabric was cleared
notifier.NotifyICDManagementEvent(ICDMEvent::kTableUpdated);
// Check ICDManager is still in the LIT operating mode
NL_TEST_ASSERT(aSuite, ICDConfigurationData::GetInstance().GetICDMode() == ICDConfigurationData::ICDMode::LIT);
// Remove last entry from remaining fabric
NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == table1.Remove(0));
NL_TEST_ASSERT(aSuite, table1.IsEmpty());
NL_TEST_ASSERT(aSuite, table2.IsEmpty());
// Trigger register event after fabric was cleared
notifier.NotifyICDManagementEvent(ICDMEvent::kTableUpdated);
// Check ICDManager is still in the LIT operating mode
NL_TEST_ASSERT(aSuite, ICDConfigurationData::GetInstance().GetICDMode() == ICDConfigurationData::ICDMode::SIT);
}
static void TestICDCounter(nlTestSuite * aSuite, void * aContext)
{
TestContext * ctx = static_cast<TestContext *>(aContext);
uint32_t counter = ICDConfigurationData::GetInstance().GetICDCounter().GetValue();
// Shut down and reinit ICDManager to increment counter
ctx->mICDManager.Shutdown();
ctx->mICDManager.Init(&(ctx->testStorage), &(ctx->GetFabricTable()), &(ctx->mKeystore), &(ctx->GetExchangeManager()),
&(ctx->mSubInfoProvider));
ctx->mICDManager.RegisterObserver(&(ctx->mICDStateObserver));
NL_TEST_ASSERT_EQUALS(aSuite, counter + ICDConfigurationData::kICDCounterPersistenceIncrement,
ICDConfigurationData::GetInstance().GetICDCounter().GetValue());
}
static void TestOnSubscriptionReport(nlTestSuite * aSuite, void * aContext)
{
TestContext * ctx = static_cast<TestContext *>(aContext);
ICDNotifier notifier = ICDNotifier::GetInstance();
// After the init we should be in Idle mode
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
// Trigger a subscription report
notifier.NotifySubscriptionReport();
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
// Trigger another subscription report - active time should not be increased
notifier.NotifySubscriptionReport();
// Advance time so active mode interval expires.
AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeDuration() + 1_ms32);
// After the init we should be in Idle mode
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
}
/* Test that verifies the logic of the ICDManager when it receives a StayActiveRequest*/
static void TestICDMStayActive(nlTestSuite * aSuite, void * aContext)
{
TestContext * ctx = static_cast<TestContext *>(aContext);
ICDNotifier notifier = ICDNotifier::GetInstance();
ICDConfigurationData & icdConfigData = ICDConfigurationData::GetInstance();
// Verify That ICDManager starts in Idle
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
// Trigger a subscription report. Put the ICD manager into active mode.
notifier.NotifySubscriptionReport();
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
// Advance time by the ActiveModeDuration - 1
AdvanceClockAndRunEventLoop(ctx, icdConfigData.GetActiveModeDuration() - 1_ms32);
// Confirm ICD manager is in active mode
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
uint32_t stayActiveRequestedMs = 20000;
// Send a stay active request for 20 seconds
uint32_t stayActivePromisedMs = ctx->mICDManager.StayActiveRequest(stayActiveRequestedMs);
// confirm the promised time is the same as the requested time
NL_TEST_ASSERT(aSuite, stayActivePromisedMs == stayActiveRequestedMs);
// Advance time by the duration of the stay stayActiveRequestedMs - 1 ms
AdvanceClockAndRunEventLoop(ctx, System::Clock::Milliseconds32(stayActiveRequestedMs) - 1_ms32);
// Confirm ICD manager is in active mode
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
// Advance time by 1ms and Confirm ICD manager is in idle mode
AdvanceClockAndRunEventLoop(ctx, 1_ms32);
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
// Trigger a subscription report Put the ICD manager into active mode
notifier.NotifySubscriptionReport();
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
// Advance time by the duration of the stay active request - 1 ms
AdvanceClockAndRunEventLoop(ctx, icdConfigData.GetActiveModeDuration() - 1_ms32);
stayActiveRequestedMs = 35000;
// Send a stay active request for 35 seconds, which is higher than the maximum stay active duration (30 seconds)
stayActivePromisedMs = ctx->mICDManager.StayActiveRequest(stayActiveRequestedMs);
// confirm the promised time is the maximum stay active duration (30 seconds)
NL_TEST_ASSERT(aSuite, stayActivePromisedMs == 30000);
// Advance time by the duration of the max stay active duration - 1 ms
AdvanceClockAndRunEventLoop(ctx, System::Clock::Milliseconds32(30000) - 1_ms32);
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
// Advance time by 1ms and Confirm ICD manager is in idle mode
AdvanceClockAndRunEventLoop(ctx, 1_ms32);
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
// Trigger a subscription report Put the ICD manager into active mode
notifier.NotifySubscriptionReport();
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
// Advance time by the duration of the stay active request - 1 ms
AdvanceClockAndRunEventLoop(ctx, icdConfigData.GetActiveModeDuration() - 1_ms32);
stayActiveRequestedMs = 30000;
// Send a stay active request for 30 seconds
stayActivePromisedMs = ctx->mICDManager.StayActiveRequest(stayActiveRequestedMs);
// confirm the promised time is the same as the requested time
NL_TEST_ASSERT(aSuite, stayActivePromisedMs == 30000);
// Advance time by the duration of the stay active request - 20000 ms
AdvanceClockAndRunEventLoop(ctx, System::Clock::Milliseconds32(stayActiveRequestedMs) - 20000_ms32);
// Confirm ICD manager is in active mode, we should have 20000 seconds left at that point
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
stayActiveRequestedMs = 10000;
stayActivePromisedMs = ctx->mICDManager.StayActiveRequest(stayActiveRequestedMs);
// confirm the promised time is 20000 since the device is already planing to stay active longer than the requested time
NL_TEST_ASSERT(aSuite, stayActivePromisedMs == 20000);
}
#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
static void TestShouldCheckInMsgsBeSentAtActiveModeFunction(nlTestSuite * aSuite, void * aContext)
{
TestContext * ctx = static_cast<TestContext *>(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<TestContext *>(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<TestContext *>(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
static void TestHandleTestEventTriggerActiveModeReq(nlTestSuite * aSuite, void * aContext)
{
TestContext * ctx = static_cast<TestContext *>(aContext);
// Verify That ICDManager starts in Idle
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
// Add ActiveMode req for the Test event trigger event
ctx->mICDManager.HandleEventTrigger(static_cast<uint64_t>(ICDTestEventTriggerEvent::kAddActiveModeReq));
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
// Advance clock by the ActiveModeDuration and check that the device is still in ActiveMode
AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeDuration() + 1_ms32);
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
// Remove req and device should go to IdleMode
ctx->mICDManager.HandleEventTrigger(static_cast<uint64_t>(ICDTestEventTriggerEvent::kRemoveActiveModeReq));
NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
}
};
} // namespace app
} // namespace chip
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),
NL_TEST_DEF("TestICDStayActive", TestICDManager::TestICDMStayActive),
NL_TEST_DEF("TestShouldCheckInMsgsBeSentAtActiveModeFunction", TestICDManager::TestShouldCheckInMsgsBeSentAtActiveModeFunction),
NL_TEST_DEF("TestHandleTestEventTriggerActiveModeReq", TestICDManager::TestHandleTestEventTriggerActiveModeReq),
NL_TEST_SENTINEL(),
};
nlTestSuite cmSuite = {
"TestICDManager",
&sTests[0],
TestContext::nlTestSetUpTestSuite,
TestContext::nlTestTearDownTestSuite,
TestContext::nlTestSetUp,
TestContext::nlTestTearDown,
};
} // namespace
int TestSuiteICDManager()
{
return ExecuteTestsWithContext<TestContext>(&cmSuite);
}
CHIP_REGISTER_TEST_SUITE(TestSuiteICDManager)