blob: b4c262725f74d304103ca0f7ef680bbf6bee3c25 [file] [log] [blame]
/*
*
* Copyright (c) 2022 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 <gtest/gtest.h>
#include "app-common/zap-generated/ids/Attributes.h"
#include "app-common/zap-generated/ids/Clusters.h"
#include "app/ClusterStateCache.h"
#include "app/ConcreteAttributePath.h"
#include "protocols/interaction_model/Constants.h"
#include <app-common/zap-generated/cluster-objects.h>
#include <app/BufferedReadCallback.h>
#include <app/CommandHandlerInterface.h>
#include <app/EventLogging.h>
#include <app/InteractionModelEngine.h>
#include <app/data-model/Decode.h>
#include <app/tests/AppTestContext.h>
#include <app/util/DataModelHandler.h>
#include <app/util/attribute-storage.h>
#include <controller/InvokeInteraction.h>
#include <lib/core/ErrorStr.h>
#include <lib/support/TimeUtils.h>
#include <lib/support/UnitTestUtils.h>
#include <lib/support/logging/CHIPLogging.h>
#include <messaging/tests/MessagingContext.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
namespace {
static uint8_t gDebugEventBuffer[4096];
static uint8_t gInfoEventBuffer[4096];
static uint8_t gCritEventBuffer[4096];
static chip::app::CircularEventBuffer gCircularEventBuffer[3];
using TestContext = chip::Test::AppContext;
//
// The generated endpoint_config for the controller app has Endpoint 1
// already used in the fixed endpoint set of size 1. Consequently, let's use the next
// number higher than that for our dynamic test endpoint.
//
constexpr EndpointId kTestEndpointId = 2;
class TestEventCaching : public ::testing::Test
{
public:
// Performs shared setup for all tests in the test suite
static void SetUpTestSuite()
{
if (mpContext == nullptr)
{
mpContext = new TestContext();
ASSERT_NE(mpContext, nullptr);
}
mpContext->SetUpTestSuite();
}
// Performs shared teardown for all tests in the test suite
static void TearDownTestSuite()
{
mpContext->TearDownTestSuite();
if (mpContext != nullptr)
{
delete mpContext;
mpContext = nullptr;
}
}
protected:
// Performs setup for each test in the suite
void SetUp()
{
const chip::app::LogStorageResources logStorageResources[] = {
{ &gDebugEventBuffer[0], sizeof(gDebugEventBuffer), chip::app::PriorityLevel::Debug },
{ &gInfoEventBuffer[0], sizeof(gInfoEventBuffer), chip::app::PriorityLevel::Info },
{ &gCritEventBuffer[0], sizeof(gCritEventBuffer), chip::app::PriorityLevel::Critical },
};
mpContext->SetUp();
CHIP_ERROR err = CHIP_NO_ERROR;
// TODO: use ASSERT_EQ, once transition to pw_unit_test is complete
VerifyOrDieWithMsg((err = mEventCounter.Init(0)) == CHIP_NO_ERROR, AppServer,
"Init EventCounter failed: %" CHIP_ERROR_FORMAT, err.Format());
chip::app::EventManagement::CreateEventManagement(&mpContext->GetExchangeManager(), ArraySize(logStorageResources),
gCircularEventBuffer, logStorageResources, &mEventCounter);
}
// Performs teardown for each test in the suite
void TearDown()
{
chip::app::EventManagement::DestroyEventManagement();
mpContext->TearDown();
}
static TestContext * mpContext;
private:
MonotonicallyIncreasingCounter<EventNumber> mEventCounter;
};
TestContext * TestEventCaching::mpContext = nullptr;
//clang-format off
DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(testClusterAttrs)
DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(testEndpointClusters)
DECLARE_DYNAMIC_CLUSTER(Clusters::UnitTesting::Id, testClusterAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr),
DECLARE_DYNAMIC_CLUSTER_LIST_END;
DECLARE_DYNAMIC_ENDPOINT(testEndpoint, testEndpointClusters);
//clang-format on
class TestReadCallback : public app::ClusterStateCache::Callback
{
public:
TestReadCallback() : mClusterCacheAdapter(*this) {}
void OnDone(app::ReadClient *) override {}
void OnEventData(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus) override
{
++mEventsSeen;
}
app::ClusterStateCache mClusterCacheAdapter;
size_t mEventsSeen = 0;
};
namespace {
void GenerateEvents(chip::EventNumber & firstEventNumber, chip::EventNumber & lastEventNumber)
{
CHIP_ERROR err = CHIP_NO_ERROR;
static uint8_t generationCount = 0;
Clusters::UnitTesting::Events::TestEvent::Type content;
for (int i = 0; i < 5; i++)
{
content.arg1 = static_cast<uint8_t>(generationCount++);
EXPECT_EQ((err = app::LogEvent(content, kTestEndpointId, lastEventNumber)), CHIP_NO_ERROR);
if (i == 0)
{
firstEventNumber = lastEventNumber;
}
}
}
} // namespace
/*
* This validates event caching by forcing a bunch of events to get generated, then reading them back
* and upon completion of that operation, iterating over any events that have accumulated in the cache
* and validating their contents.
*
* It then proceeds to do another round of generation and re-do the read, validating that the old and new
* events are present in the cache.
*
*/
TEST_F(TestEventCaching, TestBasicCaching)
{
auto sessionHandle = mpContext->GetSessionBobToAlice();
app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance();
// Initialize the ember side server logic
InitDataModelHandler();
// Register our fake dynamic endpoint.
DataVersion dataVersionStorage[ArraySize(testEndpointClusters)];
emberAfSetDynamicEndpoint(0, kTestEndpointId, &testEndpoint, Span<DataVersion>(dataVersionStorage));
chip::EventNumber firstEventNumber;
chip::EventNumber lastEventNumber;
GenerateEvents(firstEventNumber, lastEventNumber);
EXPECT_GT(lastEventNumber, firstEventNumber);
app::EventPathParams eventPath;
eventPath.mEndpointId = kTestEndpointId;
eventPath.mClusterId = app::Clusters::UnitTesting::Id;
app::ReadPrepareParams readParams(sessionHandle);
readParams.mpEventPathParamsList = &eventPath;
readParams.mEventPathParamsListSize = 1;
readParams.mEventNumber.SetValue(firstEventNumber);
TestReadCallback readCallback;
{
app::ReadClient readClient(engine, &mpContext->GetExchangeManager(),
readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read);
EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR);
mpContext->DrainAndServiceIO();
uint8_t generationCount = 0;
readCallback.mClusterCacheAdapter.ForEachEventData(
[&readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) {
EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id);
EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id);
EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId);
EXPECT_GE(header.mEventNumber, firstEventNumber);
EXPECT_LE(header.mEventNumber, lastEventNumber);
Clusters::UnitTesting::Events::TestEvent::DecodableType eventData;
EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR);
EXPECT_EQ(eventData.arg1, generationCount);
generationCount++;
return CHIP_NO_ERROR;
});
EXPECT_EQ(generationCount, lastEventNumber - firstEventNumber + 1);
Optional<EventNumber> highestEventNumber;
readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber);
EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == lastEventNumber);
//
// Re-run the iterator but pass in a path filter: EP*/TestCluster/EID*
//
generationCount = 0;
readCallback.mClusterCacheAdapter.ForEachEventData(
[&readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) {
EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id);
EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id);
EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId);
EXPECT_GE(header.mEventNumber, firstEventNumber);
EXPECT_LE(header.mEventNumber, lastEventNumber);
Clusters::UnitTesting::Events::TestEvent::DecodableType eventData;
EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR);
EXPECT_EQ(eventData.arg1, generationCount);
generationCount++;
return CHIP_NO_ERROR;
},
app::EventPathParams(kInvalidEndpointId, Clusters::UnitTesting::Id, kInvalidEventId));
EXPECT_EQ(generationCount, lastEventNumber - firstEventNumber + 1);
//
// Re-run the iterator but pass in a path filter: EP*/TestCluster/TestEvent
//
generationCount = 0;
readCallback.mClusterCacheAdapter.ForEachEventData(
[&readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) {
EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id);
EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id);
EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId);
EXPECT_GE(header.mEventNumber, firstEventNumber);
EXPECT_LE(header.mEventNumber, lastEventNumber);
Clusters::UnitTesting::Events::TestEvent::DecodableType eventData;
EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR);
EXPECT_EQ(eventData.arg1, generationCount);
generationCount++;
return CHIP_NO_ERROR;
},
app::EventPathParams(kInvalidEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Events::TestEvent::Id));
EXPECT_EQ(generationCount, lastEventNumber - firstEventNumber + 1);
//
// Re-run the iterator but pass in a min event number filter
// (EventNumber = firstEventNumber + 1). We should only receive 4 events.
//
generationCount = 1;
readCallback.mClusterCacheAdapter.ForEachEventData(
[&readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) {
EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id);
EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id);
EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId);
EXPECT_GE(header.mEventNumber, firstEventNumber + 1);
EXPECT_LE(header.mEventNumber, lastEventNumber);
Clusters::UnitTesting::Events::TestEvent::DecodableType eventData;
EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR);
EXPECT_EQ(eventData.arg1, generationCount);
generationCount++;
return CHIP_NO_ERROR;
},
app::EventPathParams(), firstEventNumber + 1);
EXPECT_EQ(generationCount, lastEventNumber - firstEventNumber + 1);
//
// Re-run the iterator but pass in a min event number filter
// (EventNumber = firstEventNumber + 1) AND a path filter. We should only receive 4
// events.
//
generationCount = 1;
readCallback.mClusterCacheAdapter.ForEachEventData(
[&readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) {
EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id);
EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id);
EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId);
EXPECT_GE(header.mEventNumber, firstEventNumber + 1);
EXPECT_LE(header.mEventNumber, lastEventNumber);
Clusters::UnitTesting::Events::TestEvent::DecodableType eventData;
EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR);
EXPECT_EQ(eventData.arg1, generationCount);
generationCount++;
return CHIP_NO_ERROR;
},
app::EventPathParams(kInvalidEndpointId, Clusters::UnitTesting::Id, kInvalidEventId), firstEventNumber + 1);
EXPECT_EQ(generationCount, lastEventNumber - firstEventNumber + 1);
}
//
// Generate more events.
//
const EventNumber oldFirstEventNumber = firstEventNumber;
GenerateEvents(firstEventNumber, lastEventNumber);
{
app::ReadClient readClient(engine, &mpContext->GetExchangeManager(),
readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read);
EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR);
mpContext->DrainAndServiceIO();
//
// Validate that we still have all 5 of the old events we received, as well as the new ones that just got generated.
// This also ensures that we don't receive duplicate events in the `ForEachEventData` call below.
//
uint8_t generationCount = 0;
readCallback.mClusterCacheAdapter.ForEachEventData(
[&readCallback, &generationCount, oldFirstEventNumber, lastEventNumber](const app::EventHeader & header) {
EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id);
EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id);
EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId);
EXPECT_GE(header.mEventNumber, oldFirstEventNumber);
EXPECT_LE(header.mEventNumber, lastEventNumber);
Clusters::UnitTesting::Events::TestEvent::DecodableType eventData;
EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR);
EXPECT_EQ(eventData.arg1, generationCount);
generationCount++;
return CHIP_NO_ERROR;
});
EXPECT_EQ(generationCount, lastEventNumber - oldFirstEventNumber + 1);
Optional<EventNumber> highestEventNumber;
readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber);
EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == 9);
readCallback.mClusterCacheAdapter.ClearEventCache();
generationCount = 0;
readCallback.mClusterCacheAdapter.ForEachEventData([&generationCount](const app::EventHeader & header) {
generationCount++;
return CHIP_NO_ERROR;
});
EXPECT_EQ(generationCount, 0u);
readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber);
EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == 9);
}
//
// Clear out the event cache and set its highest received event number to a non zero value. Validate that
// we don't receive events lower than that value.
//
{
app::ReadClient readClient(engine, &mpContext->GetExchangeManager(),
readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read);
readCallback.mClusterCacheAdapter.ClearEventCache();
constexpr EventNumber kLastSeenEventNumber = 3;
EXPECT_LT(kLastSeenEventNumber, lastEventNumber);
readCallback.mClusterCacheAdapter.SetHighestReceivedEventNumber(kLastSeenEventNumber);
readParams.mEventNumber.ClearValue();
readCallback.mEventsSeen = 0;
EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR);
mpContext->DrainAndServiceIO();
// We should only get events with event numbers larger than kHighestEventNumberSeen.
EXPECT_EQ(readCallback.mEventsSeen, lastEventNumber - kLastSeenEventNumber);
uint8_t generationCount = kLastSeenEventNumber + 1;
readCallback.mClusterCacheAdapter.ForEachEventData(
[&readCallback, &generationCount, lastEventNumber, kLastSeenEventNumber](const app::EventHeader & header) {
EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id);
EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id);
EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId);
EXPECT_GT(header.mEventNumber, kLastSeenEventNumber);
EXPECT_LE(header.mEventNumber, lastEventNumber);
Clusters::UnitTesting::Events::TestEvent::DecodableType eventData;
EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR);
EXPECT_EQ(eventData.arg1, generationCount);
generationCount++;
return CHIP_NO_ERROR;
});
EXPECT_EQ(generationCount, lastEventNumber - oldFirstEventNumber + 1);
Optional<EventNumber> highestEventNumber;
readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber);
EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == lastEventNumber);
}
//
// Set user-provided event number, then read client would use user-provided event number and not use the cached one in read
// client
//
{
readParams.mEventNumber.SetValue(5);
app::ReadClient readClient(engine, &mpContext->GetExchangeManager(),
readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read);
readCallback.mClusterCacheAdapter.ClearEventCache(true);
EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR);
mpContext->DrainAndServiceIO();
//
// Validate that we would receive 5 events
//
uint8_t generationCount = 5;
readCallback.mClusterCacheAdapter.ForEachEventData([&readCallback, &generationCount](const app::EventHeader & header) {
EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id);
EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id);
EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId);
Clusters::UnitTesting::Events::TestEvent::DecodableType eventData;
EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR);
EXPECT_EQ(eventData.arg1, generationCount);
generationCount++;
return CHIP_NO_ERROR;
});
EXPECT_EQ(generationCount, 10u);
Optional<EventNumber> highestEventNumber;
readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber);
EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == 9);
}
EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
emberAfClearDynamicEndpoint(0);
}
} // namespace