| /* |
| * |
| * 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 <pw_unit_test/framework.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/core/StringBuilderAdapters.h> |
| #include <lib/support/TimeUtils.h> |
| #include <lib/support/UnitTestUtils.h> |
| #include <lib/support/logging/CHIPLogging.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]; |
| |
| // |
| // 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 Test::AppContext |
| { |
| protected: |
| // Performs setup for each test in the suite. Run once for each test function. |
| 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 }, |
| }; |
| |
| AppContext::SetUp(); // Call parent. |
| VerifyOrReturn(!HasFailure()); // Stop if parent had a failure. |
| |
| ASSERT_EQ(mEventCounter.Init(0), CHIP_NO_ERROR); |
| chip::app::EventManagement::CreateEventManagement(&GetExchangeManager(), ArraySize(logStorageResources), |
| gCircularEventBuffer, logStorageResources, &mEventCounter); |
| } |
| |
| // Performs teardown for each test in the suite. Run once for each test function. |
| void TearDown() |
| { |
| chip::app::EventManagement::DestroyEventManagement(); |
| AppContext::TearDown(); // Call parent. |
| } |
| |
| private: |
| MonotonicallyIncreasingCounter<EventNumber> mEventCounter; |
| }; |
| |
| //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 = 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, &GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), |
| app::ReadClient::InteractionType::Read); |
| |
| EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); |
| |
| 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, &GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), |
| app::ReadClient::InteractionType::Read); |
| |
| EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); |
| |
| 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, &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); |
| |
| 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, &GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), |
| app::ReadClient::InteractionType::Read); |
| readCallback.mClusterCacheAdapter.ClearEventCache(true); |
| EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); |
| |
| 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(GetExchangeManager().GetNumActiveExchanges(), 0u); |
| |
| emberAfClearDynamicEndpoint(0); |
| } |
| |
| } // namespace |