blob: acc19c12f44da01fcb40859ff360df683195a66b [file] [log] [blame]
/*
*
* Copyright (c) 2021 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/AppConfig.h>
#include <app/InteractionModelEngine.h>
#include <app/codegen-data-model-provider/Instance.h>
#include <app/icd/server/ICDServerConfig.h>
#include <app/reporting/tests/MockReportScheduler.h>
#include <app/tests/AppTestContext.h>
#include <app/util/mock/Constants.h>
#include <app/util/mock/Functions.h>
#include <lib/core/CASEAuthTag.h>
#include <lib/core/ErrorStr.h>
#include <lib/core/StringBuilderAdapters.h>
#include <lib/core/TLV.h>
#include <lib/core/TLVDebug.h>
#include <lib/core/TLVUtilities.h>
#include <lib/support/tests/ExtraPwTestMacros.h>
#include <messaging/ExchangeContext.h>
#include <messaging/Flags.h>
#include <platform/CHIPDeviceLayer.h>
#include <pw_unit_test/framework.h>
#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
#include <app/SimpleSubscriptionResumptionStorage.h>
#include <lib/support/TestPersistentStorageDelegate.h>
#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
namespace {
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 : public chip::Test::AppContext
{
public:
void TestSubjectHasActiveSubscriptionSingleSubOneEntry();
void TestSubjectHasActiveSubscriptionSingleSubMultipleEntries();
void TestSubjectHasActiveSubscriptionMultipleSubsSingleEntry();
void TestSubjectHasActiveSubscriptionMultipleSubsMultipleEntries();
void TestSubjectHasActiveSubscriptionSubWithCAT();
void TestSubscriptionResumptionTimer();
void TestDecrementNumSubscriptionsToResume();
static int GetAttributePathListLength(SingleLinkedListNode<AttributePathParams> * apattributePathParamsList);
};
int TestInteractionModelEngine::GetAttributePathListLength(SingleLinkedListNode<AttributePathParams> * apAttributePathParamsList)
{
int length = 0;
SingleLinkedListNode<AttributePathParams> * runner = apAttributePathParamsList;
while (runner != nullptr)
{
runner = runner->mpNext;
length++;
}
return length;
}
TEST_F(TestInteractionModelEngine, TestAttributePathParamsPushRelease)
{
InteractionModelEngine * engine = InteractionModelEngine::GetInstance();
engine->SetDataModelProvider(CodegenDataModelProviderInstance());
EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), app::reporting::GetDefaultReportScheduler()), CHIP_NO_ERROR);
SingleLinkedListNode<AttributePathParams> * attributePathParamsList = nullptr;
AttributePathParams attributePathParams1;
AttributePathParams attributePathParams2;
AttributePathParams attributePathParams3;
attributePathParams1.mEndpointId = 1;
attributePathParams2.mEndpointId = 2;
attributePathParams3.mEndpointId = 3;
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams1);
ASSERT_NE(attributePathParamsList, nullptr);
EXPECT_EQ(attributePathParams1.mEndpointId, attributePathParamsList->mValue.mEndpointId);
EXPECT_EQ(GetAttributePathListLength(attributePathParamsList), 1);
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams2);
ASSERT_NE(attributePathParamsList, nullptr);
EXPECT_EQ(attributePathParams2.mEndpointId, attributePathParamsList->mValue.mEndpointId);
EXPECT_EQ(GetAttributePathListLength(attributePathParamsList), 2);
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams3);
ASSERT_NE(attributePathParamsList, nullptr);
EXPECT_EQ(attributePathParams3.mEndpointId, attributePathParamsList->mValue.mEndpointId);
EXPECT_EQ(GetAttributePathListLength(attributePathParamsList), 3);
engine->ReleaseAttributePathList(attributePathParamsList);
EXPECT_EQ(GetAttributePathListLength(attributePathParamsList), 0);
}
TEST_F(TestInteractionModelEngine, TestRemoveDuplicateConcreteAttribute)
{
InteractionModelEngine * engine = InteractionModelEngine::GetInstance();
engine->SetDataModelProvider(CodegenDataModelProviderInstance());
EXPECT_EQ(CHIP_NO_ERROR, engine->Init(&GetExchangeManager(), &GetFabricTable(), app::reporting::GetDefaultReportScheduler()));
SingleLinkedListNode<AttributePathParams> * attributePathParamsList = nullptr;
AttributePathParams attributePathParams1;
AttributePathParams attributePathParams2;
AttributePathParams attributePathParams3;
// Three concrete paths, no duplicates
attributePathParams1.mEndpointId = chip::Test::kMockEndpoint3;
attributePathParams1.mClusterId = chip::Test::MockClusterId(2);
attributePathParams1.mAttributeId = chip::Test::MockAttributeId(1);
attributePathParams2.mEndpointId = chip::Test::kMockEndpoint3;
attributePathParams2.mClusterId = chip::Test::MockClusterId(2);
attributePathParams2.mAttributeId = chip::Test::MockAttributeId(2);
attributePathParams3.mEndpointId = chip::Test::kMockEndpoint3;
attributePathParams3.mClusterId = chip::Test::MockClusterId(2);
attributePathParams3.mAttributeId = chip::Test::MockAttributeId(3);
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams1);
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams2);
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams3);
engine->RemoveDuplicateConcreteAttributePath(attributePathParamsList);
EXPECT_EQ(GetAttributePathListLength(attributePathParamsList), 3);
engine->ReleaseAttributePathList(attributePathParamsList);
attributePathParams1.mEndpointId = kInvalidEndpointId;
attributePathParams1.mClusterId = kInvalidClusterId;
attributePathParams1.mAttributeId = kInvalidAttributeId;
attributePathParams2.mEndpointId = chip::Test::kMockEndpoint3;
attributePathParams2.mClusterId = chip::Test::MockClusterId(2);
attributePathParams2.mAttributeId = chip::Test::MockAttributeId(2);
attributePathParams3.mEndpointId = chip::Test::kMockEndpoint3;
attributePathParams3.mClusterId = chip::Test::MockClusterId(2);
attributePathParams3.mAttributeId = chip::Test::MockAttributeId(3);
// 1st path is wildcard endpoint, 2nd, 3rd paths are concrete paths, the concrete ones would be removed.
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams1);
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams2);
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams3);
engine->RemoveDuplicateConcreteAttributePath(attributePathParamsList);
EXPECT_EQ(GetAttributePathListLength(attributePathParamsList), 1);
engine->ReleaseAttributePathList(attributePathParamsList);
// 2nd path is wildcard endpoint, 1st, 3rd paths are concrete paths, the latter two would be removed.
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams2);
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams1);
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams3);
engine->RemoveDuplicateConcreteAttributePath(attributePathParamsList);
EXPECT_EQ(GetAttributePathListLength(attributePathParamsList), 1);
engine->ReleaseAttributePathList(attributePathParamsList);
// 3nd path is wildcard endpoint, 1st, 2nd paths are concrete paths, the latter two would be removed.
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams2);
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams3);
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams1);
engine->RemoveDuplicateConcreteAttributePath(attributePathParamsList);
EXPECT_EQ(GetAttributePathListLength(attributePathParamsList), 1);
engine->ReleaseAttributePathList(attributePathParamsList);
attributePathParams1.mEndpointId = chip::Test::kMockEndpoint3;
attributePathParams1.mClusterId = chip::Test::MockClusterId(2);
attributePathParams1.mAttributeId = kInvalidAttributeId;
attributePathParams2.mEndpointId = chip::Test::kMockEndpoint2;
attributePathParams2.mClusterId = chip::Test::MockClusterId(2);
attributePathParams2.mAttributeId = chip::Test::MockAttributeId(2);
attributePathParams3.mEndpointId = chip::Test::kMockEndpoint2;
attributePathParams3.mClusterId = chip::Test::MockClusterId(2);
attributePathParams3.mAttributeId = chip::Test::MockAttributeId(3);
// 1st is wildcard one, but not intersect with the latter two concrete paths, so the paths in total are 3 finally
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams1);
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams2);
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams3);
engine->RemoveDuplicateConcreteAttributePath(attributePathParamsList);
EXPECT_EQ(GetAttributePathListLength(attributePathParamsList), 3);
engine->ReleaseAttributePathList(attributePathParamsList);
attributePathParams1.mEndpointId = kInvalidEndpointId;
attributePathParams1.mClusterId = kInvalidClusterId;
attributePathParams1.mAttributeId = kInvalidAttributeId;
attributePathParams2.mEndpointId = chip::Test::kMockEndpoint3;
attributePathParams2.mClusterId = kInvalidClusterId;
attributePathParams2.mAttributeId = kInvalidAttributeId;
attributePathParams3.mEndpointId = kInvalidEndpointId;
attributePathParams3.mClusterId = kInvalidClusterId;
attributePathParams3.mAttributeId = chip::Test::MockAttributeId(3);
// Wildcards cannot be deduplicated.
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams1);
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams2);
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams3);
engine->RemoveDuplicateConcreteAttributePath(attributePathParamsList);
EXPECT_EQ(GetAttributePathListLength(attributePathParamsList), 3);
engine->ReleaseAttributePathList(attributePathParamsList);
attributePathParams1.mEndpointId = kInvalidEndpointId;
attributePathParams1.mClusterId = chip::Test::MockClusterId(2);
attributePathParams1.mAttributeId = chip::Test::MockAttributeId(10);
attributePathParams2.mEndpointId = chip::Test::kMockEndpoint3;
attributePathParams2.mClusterId = chip::Test::MockClusterId(2);
attributePathParams2.mAttributeId = chip::Test::MockAttributeId(10);
// 1st path is wildcard endpoint, 2nd path is invalid attribute
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams1);
engine->PushFrontAttributePathList(attributePathParamsList, attributePathParams2);
engine->RemoveDuplicateConcreteAttributePath(attributePathParamsList);
EXPECT_EQ(GetAttributePathListLength(attributePathParamsList), 2);
engine->ReleaseAttributePathList(attributePathParamsList);
}
/**
* @brief Test verifies the SubjectHasActiveSubscription with a single subscription with a single entry
*/
TEST_F_FROM_FIXTURE(TestInteractionModelEngine, TestSubjectHasActiveSubscriptionSingleSubOneEntry)
{
NullReadHandlerCallback nullCallback;
InteractionModelEngine * engine = InteractionModelEngine::GetInstance();
NodeId bobNodeId = 0x12344321ull;
FabricIndex bobFabricIndex = 1;
// Create ExchangeContext
Messaging::ExchangeContext * exchangeCtx1 = NewExchangeToBob(nullptr, false);
ASSERT_TRUE(exchangeCtx1);
// InteractionModelEngine init
engine->SetDataModelProvider(CodegenDataModelProviderInstance());
EXPECT_EQ(CHIP_NO_ERROR, engine->Init(&GetExchangeManager(), &GetFabricTable(), reporting::GetDefaultReportScheduler()));
// Verify that there are no active subscriptions
EXPECT_FALSE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
// Create and setup readHandler 1
ReadHandler * readHandler1 =
engine->GetReadHandlerPool().CreateObject(nullCallback, exchangeCtx1, ReadHandler::InteractionType::Subscribe,
reporting::GetDefaultReportScheduler(), CodegenDataModelProviderInstance());
// Verify that Bob still doesn't have an active subscription
EXPECT_FALSE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
// Set readHandler1 to active
readHandler1->SetStateFlag(ReadHandler::ReadHandlerFlags::ActiveSubscription, true);
// Verify that Bob still has an active subscription
EXPECT_TRUE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
// Clean up read handlers
engine->GetReadHandlerPool().ReleaseAll();
// Verify that there are no active subscriptions
EXPECT_FALSE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
}
/**
* @brief Test verifies that the SubjectHasActiveSubscription will continue iterating till it fines at least one valid active
* subscription
*/
TEST_F_FROM_FIXTURE(TestInteractionModelEngine, TestSubjectHasActiveSubscriptionSingleSubMultipleEntries)
{
NullReadHandlerCallback nullCallback;
InteractionModelEngine * engine = InteractionModelEngine::GetInstance();
NodeId bobNodeId = 0x12344321ull;
FabricIndex bobFabricIndex = 1;
// Create ExchangeContexts
Messaging::ExchangeContext * exchangeCtx1 = NewExchangeToBob(nullptr, false);
ASSERT_TRUE(exchangeCtx1);
Messaging::ExchangeContext * exchangeCtx2 = NewExchangeToBob(nullptr, false);
ASSERT_TRUE(exchangeCtx1);
// InteractionModelEngine init
engine->SetDataModelProvider(CodegenDataModelProviderInstance());
EXPECT_EQ(CHIP_NO_ERROR, engine->Init(&GetExchangeManager(), &GetFabricTable(), reporting::GetDefaultReportScheduler()));
// Verify that both Alice and Bob have no active subscriptions
EXPECT_FALSE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
// Create readHandler 1
engine->GetReadHandlerPool().CreateObject(nullCallback, exchangeCtx1, ReadHandler::InteractionType::Subscribe,
reporting::GetDefaultReportScheduler(), CodegenDataModelProviderInstance());
// Verify that Bob still doesn't have an active subscription
EXPECT_FALSE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
// Create and setup readHandler 2
ReadHandler * readHandler2 =
engine->GetReadHandlerPool().CreateObject(nullCallback, exchangeCtx2, ReadHandler::InteractionType::Subscribe,
reporting::GetDefaultReportScheduler(), CodegenDataModelProviderInstance());
// Verify that Bob still doesn't have an active subscription
EXPECT_FALSE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
// Set readHandler2 to active
readHandler2->SetStateFlag(ReadHandler::ReadHandlerFlags::ActiveSubscription, true);
// Verify that Bob has an active subscription
EXPECT_TRUE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
// Release active ReadHandler
engine->GetReadHandlerPool().ReleaseObject(readHandler2);
// Verify that there are no active subscriptions
EXPECT_FALSE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
// Clean up read handlers
engine->GetReadHandlerPool().ReleaseAll();
}
/**
* @brief Test validates that the SubjectHasActiveSubscription can support multiple subscriptions from different clients
*/
TEST_F_FROM_FIXTURE(TestInteractionModelEngine, TestSubjectHasActiveSubscriptionMultipleSubsSingleEntry)
{
NullReadHandlerCallback nullCallback;
InteractionModelEngine * engine = InteractionModelEngine::GetInstance();
NodeId bobNodeId = 0x12344321ull;
FabricIndex bobFabricIndex = 1;
NodeId aliceNodeId = 0x11223344ull;
FabricIndex aliceFabricIndex = 2;
// Create ExchangeContexts
Messaging::ExchangeContext * exchangeCtx1 = NewExchangeToBob(nullptr, false);
ASSERT_TRUE(exchangeCtx1);
Messaging::ExchangeContext * exchangeCtx2 = NewExchangeToAlice(nullptr, false);
ASSERT_TRUE(exchangeCtx2);
// InteractionModelEngine init
engine->SetDataModelProvider(CodegenDataModelProviderInstance());
EXPECT_EQ(CHIP_NO_ERROR, engine->Init(&GetExchangeManager(), &GetFabricTable(), reporting::GetDefaultReportScheduler()));
// Verify that both Alice and Bob have no active subscriptions
EXPECT_FALSE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
EXPECT_FALSE(engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId));
// Create and setup readHandler 1
ReadHandler * readHandler1 =
engine->GetReadHandlerPool().CreateObject(nullCallback, exchangeCtx1, ReadHandler::InteractionType::Subscribe,
reporting::GetDefaultReportScheduler(), CodegenDataModelProviderInstance());
// Create and setup readHandler 2
ReadHandler * readHandler2 =
engine->GetReadHandlerPool().CreateObject(nullCallback, exchangeCtx2, ReadHandler::InteractionType::Subscribe,
reporting::GetDefaultReportScheduler(), CodegenDataModelProviderInstance());
// Verify that Bob still doesn't have an active subscription
EXPECT_FALSE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
// Set readHandler1 to active
readHandler1->SetStateFlag(ReadHandler::ReadHandlerFlags::ActiveSubscription, true);
// Verify that Bob has an active subscription
EXPECT_TRUE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
// Verify that Alice still doesn't have an active subscription
EXPECT_FALSE(engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId));
// Set readHandler2 to active
readHandler2->SetStateFlag(ReadHandler::ReadHandlerFlags::ActiveSubscription, true);
// Verify that Bob has an active subscription
EXPECT_TRUE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
// Verify that Alice has an active subscription
EXPECT_TRUE(engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId));
// Set readHandler1 to inactive
readHandler1->SetStateFlag(ReadHandler::ReadHandlerFlags::ActiveSubscription, false);
// Verify that Bob doesn't have an active subscription
EXPECT_FALSE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
// Verify that Alice still has an active subscription
EXPECT_TRUE(engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId));
// Clean up read handlers
engine->GetReadHandlerPool().ReleaseAll();
// Verify that both Alice and Bob have no active subscriptions
EXPECT_FALSE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
EXPECT_FALSE(engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId));
}
/**
* @brief Test validates that the SubjectHasActiveSubscription can find the active subscription even if there are multiple
* subscriptions for each client
*/
TEST_F_FROM_FIXTURE(TestInteractionModelEngine, TestSubjectHasActiveSubscriptionMultipleSubsMultipleEntries)
{
NullReadHandlerCallback nullCallback;
InteractionModelEngine * engine = InteractionModelEngine::GetInstance();
NodeId bobNodeId = 0x12344321ull;
FabricIndex bobFabricIndex = 1;
NodeId aliceNodeId = 0x11223344ull;
FabricIndex aliceFabricIndex = 2;
// Create ExchangeContexts
Messaging::ExchangeContext * exchangeCtx11 = NewExchangeToBob(nullptr, false);
ASSERT_TRUE(exchangeCtx11);
Messaging::ExchangeContext * exchangeCtx12 = NewExchangeToBob(nullptr, false);
ASSERT_TRUE(exchangeCtx12);
Messaging::ExchangeContext * exchangeCtx21 = NewExchangeToAlice(nullptr, false);
ASSERT_TRUE(exchangeCtx21);
Messaging::ExchangeContext * exchangeCtx22 = NewExchangeToAlice(nullptr, false);
ASSERT_TRUE(exchangeCtx22);
// InteractionModelEngine init
engine->SetDataModelProvider(CodegenDataModelProviderInstance());
EXPECT_EQ(CHIP_NO_ERROR, engine->Init(&GetExchangeManager(), &GetFabricTable(), reporting::GetDefaultReportScheduler()));
// Verify that both Alice and Bob have no active subscriptions
EXPECT_FALSE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
EXPECT_FALSE(engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId));
// Create and setup readHandler 1-1
engine->GetReadHandlerPool().CreateObject(nullCallback, exchangeCtx11, ReadHandler::InteractionType::Subscribe,
reporting::GetDefaultReportScheduler(), CodegenDataModelProviderInstance());
// Create and setup readHandler 1-2
ReadHandler * readHandler12 =
engine->GetReadHandlerPool().CreateObject(nullCallback, exchangeCtx12, ReadHandler::InteractionType::Subscribe,
reporting::GetDefaultReportScheduler(), CodegenDataModelProviderInstance());
// Create and setup readHandler 2-1
engine->GetReadHandlerPool().CreateObject(nullCallback, exchangeCtx21, ReadHandler::InteractionType::Subscribe,
reporting::GetDefaultReportScheduler(), CodegenDataModelProviderInstance());
// Create and setup readHandler 2-2
ReadHandler * readHandler22 =
engine->GetReadHandlerPool().CreateObject(nullCallback, exchangeCtx22, ReadHandler::InteractionType::Subscribe,
reporting::GetDefaultReportScheduler(), CodegenDataModelProviderInstance());
// Verify that both Alice and Bob have no active subscriptions
EXPECT_FALSE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
EXPECT_FALSE(engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId));
// Set readHandler 1-2 to active
readHandler12->SetStateFlag(ReadHandler::ReadHandlerFlags::ActiveSubscription, true);
// Verify that Bob has an active subscription
EXPECT_TRUE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
// Verify that Alice still doesn't have an active subscription
EXPECT_FALSE(engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId));
// Set readHandler 2-2 to active
readHandler22->SetStateFlag(ReadHandler::ReadHandlerFlags::ActiveSubscription, true);
// Verify that Bob has an active subscription
EXPECT_TRUE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
// Verify that Alice has an active subscription
EXPECT_TRUE(engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId));
// Set readHandler1 to inactive
readHandler12->SetStateFlag(ReadHandler::ReadHandlerFlags::ActiveSubscription, false);
// Verify that Bob doesn't have an active subscription
EXPECT_FALSE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
// Verify that Alice still has an active subscription
EXPECT_TRUE(engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId));
// Clean up read handlers
engine->GetReadHandlerPool().ReleaseAll();
// Verify that both Alice and Bob have no active subscriptions
EXPECT_FALSE(engine->SubjectHasActiveSubscription(bobFabricIndex, bobNodeId));
EXPECT_FALSE(engine->SubjectHasActiveSubscription(aliceFabricIndex, aliceNodeId));
}
/**
* @brief Verifies that SubjectHasActiveSubscription support CATs as a subject-id
*/
TEST_F_FROM_FIXTURE(TestInteractionModelEngine, TestSubjectHasActiveSubscriptionSubWithCAT)
{
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
engine->SetDataModelProvider(CodegenDataModelProviderInstance());
EXPECT_EQ(CHIP_NO_ERROR, engine->Init(&GetExchangeManager(), &GetFabricTable(), reporting::GetDefaultReportScheduler()));
// Make sure we are using CASE sessions, because there is no defunct-marking for PASE.
ExpireSessionBobToAlice();
ExpireSessionAliceToBob();
EXPECT_EQ(CHIP_NO_ERROR, CreateCASESessionBobToAlice(cats));
EXPECT_EQ(CHIP_NO_ERROR, CreateCASESessionAliceToBob(cats));
// Create ExchangeContexts
Messaging::ExchangeContext * exchangeCtx = NewExchangeToBob(nullptr, false);
ASSERT_TRUE(exchangeCtx);
// Create readHandler
ReadHandler * readHandler =
engine->GetReadHandlerPool().CreateObject(nullCallback, exchangeCtx, ReadHandler::InteractionType::Subscribe,
reporting::GetDefaultReportScheduler(), CodegenDataModelProviderInstance());
// Verify there are not active subscriptions
EXPECT_FALSE(engine->SubjectHasActiveSubscription(bobFabricIndex, valideSubjectId));
EXPECT_FALSE(engine->SubjectHasActiveSubscription(bobFabricIndex, invalideSubjectId));
// Set readHandler to active
readHandler->SetStateFlag(ReadHandler::ReadHandlerFlags::ActiveSubscription, true);
// Verify tthat valid subjectID has an active subscription
EXPECT_TRUE(engine->SubjectHasActiveSubscription(bobFabricIndex, valideSubjectId));
EXPECT_FALSE(engine->SubjectHasActiveSubscription(bobFabricIndex, invalideSubjectId));
// Clean up read handlers
engine->GetReadHandlerPool().ReleaseAll();
// Verify there are not active subscriptions
EXPECT_FALSE(engine->SubjectHasActiveSubscription(bobFabricIndex, valideSubjectId));
EXPECT_FALSE(engine->SubjectHasActiveSubscription(bobFabricIndex, invalideSubjectId));
}
#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
/**
* @brief Test verifies the SubjectHasPersistedSubscription with single and multiple persisted subscriptions.
*/
TEST_F(TestInteractionModelEngine, TestSubjectHasPersistedSubscription)
{
InteractionModelEngine * engine = InteractionModelEngine::GetInstance();
chip::TestPersistentStorageDelegate storage;
chip::app::SimpleSubscriptionResumptionStorage subscriptionStorage;
EXPECT_EQ(subscriptionStorage.Init(&storage), CHIP_NO_ERROR);
engine->SetDataModelProvider(CodegenDataModelProviderInstance());
EXPECT_EQ(CHIP_NO_ERROR,
engine->Init(&GetExchangeManager(), &GetFabricTable(), app::reporting::GetDefaultReportScheduler(), nullptr,
&subscriptionStorage));
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
EXPECT_FALSE(engine->SubjectHasPersistedSubscription(fabric1, nodeId1));
// Add one entry
EXPECT_EQ(CHIP_NO_ERROR, subscriptionStorage.Save(info1));
// Verify that entry matches - Should return true
EXPECT_TRUE(engine->SubjectHasPersistedSubscription(fabric1, nodeId1));
// Test with absent subscription - Should return false
EXPECT_FALSE(engine->SubjectHasPersistedSubscription(fabric2, nodeId2));
// Add second entry
EXPECT_EQ(CHIP_NO_ERROR, subscriptionStorage.Save(info2));
// Verify that entry matches - Should return true
EXPECT_TRUE(engine->SubjectHasPersistedSubscription(fabric2, nodeId2));
EXPECT_TRUE(engine->SubjectHasPersistedSubscription(fabric1, nodeId1));
// Remove an entry
EXPECT_EQ(CHIP_NO_ERROR, subscriptionStorage.Delete(nodeId1, fabric1, sub1));
// Test with absent subscription - Should return false
EXPECT_FALSE(engine->SubjectHasPersistedSubscription(fabric1, nodeId1));
// Clean Up entries
subscriptionStorage.DeleteAll(fabric1);
subscriptionStorage.DeleteAll(fabric2);
}
#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
TEST_F_FROM_FIXTURE(TestInteractionModelEngine, TestSubscriptionResumptionTimer)
{
InteractionModelEngine * engine = InteractionModelEngine::GetInstance();
engine->SetDataModelProvider(CodegenDataModelProviderInstance());
EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), app::reporting::GetDefaultReportScheduler()), CHIP_NO_ERROR);
uint32_t timeTillNextResubscriptionMs;
engine->mNumSubscriptionResumptionRetries = 0;
timeTillNextResubscriptionMs = engine->ComputeTimeSecondsTillNextSubscriptionResumption();
EXPECT_EQ(timeTillNextResubscriptionMs, (unsigned) CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MIN_RETRY_INTERVAL_SECS);
uint32_t lastTimeTillNextResubscriptionMs = timeTillNextResubscriptionMs;
for (engine->mNumSubscriptionResumptionRetries = 1;
engine->mNumSubscriptionResumptionRetries <= CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX;
engine->mNumSubscriptionResumptionRetries++)
{
timeTillNextResubscriptionMs = engine->ComputeTimeSecondsTillNextSubscriptionResumption();
EXPECT_GE(timeTillNextResubscriptionMs, lastTimeTillNextResubscriptionMs);
EXPECT_LT(timeTillNextResubscriptionMs, (unsigned) CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS);
lastTimeTillNextResubscriptionMs = timeTillNextResubscriptionMs;
}
engine->mNumSubscriptionResumptionRetries = 2000;
timeTillNextResubscriptionMs = engine->ComputeTimeSecondsTillNextSubscriptionResumption();
EXPECT_EQ(timeTillNextResubscriptionMs, (unsigned) CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS);
}
#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
TEST_F_FROM_FIXTURE(TestInteractionModelEngine, TestDecrementNumSubscriptionsToResume)
{
InteractionModelEngine * engine = InteractionModelEngine::GetInstance();
constexpr uint8_t kNumberOfSubsToResume = 5;
uint8_t numberOfSubsRemaining = kNumberOfSubsToResume;
engine->SetDataModelProvider(CodegenDataModelProviderInstance());
EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), app::reporting::GetDefaultReportScheduler()), 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
EXPECT_FALSE(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();
EXPECT_EQ(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
EXPECT_FALSE(manager.GetIsBootUpResumeSubscriptionExecuted());
#endif // CHIP_CONFIG_ENABLE_ICD_CIP && CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
numberOfSubsRemaining--;
engine->DecrementNumSubscriptionsToResume();
EXPECT_EQ(numberOfSubsRemaining, engine->mNumOfSubscriptionsToResume);
}
#if CHIP_CONFIG_ENABLE_ICD_CIP && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
// Verify mIsBootUpResumeSubscriptionExecuted has been set
EXPECT_TRUE(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();
EXPECT_EQ(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