diff --git a/src/app/EventManagement.cpp b/src/app/EventManagement.cpp index 3fc21e01d03192..e0d4cd877cb1ec 100644 --- a/src/app/EventManagement.cpp +++ b/src/app/EventManagement.cpp @@ -319,6 +319,7 @@ CHIP_ERROR EventManagement::ConstructEvent(EventLoadOutContext * apContext, Even // The fabricIndex profile tag is internal use only for fabric filtering when retrieving event from circular event buffer, // and would not go on the wire. + // Revisit FabricRemovedCB function should the encoding of fabricIndex change in the future. if (apOptions->mFabricIndex != kUndefinedFabricIndex) { apContext->mWriter.Put(TLV::ProfileTag(kEventManagementProfile, kFabricIndexTag), apOptions->mFabricIndex); @@ -570,7 +571,9 @@ CHIP_ERROR EventManagement::CheckEventContext(EventLoadOutContext * eventLoadOut return CHIP_ERROR_UNEXPECTED_EVENT; } - if (event.mFabricIndex != kUndefinedFabricIndex && eventLoadOutContext->mSubjectDescriptor.fabricIndex != event.mFabricIndex) + if (event.mFabricIndex.HasValue() && + (event.mFabricIndex.Value() == kUndefinedFabricIndex || + eventLoadOutContext->mSubjectDescriptor.fabricIndex != event.mFabricIndex.Value())) { return CHIP_ERROR_UNEXPECTED_EVENT; } @@ -748,6 +751,69 @@ CHIP_ERROR EventManagement::FetchEventsSince(TLVWriter & aWriter, const ObjectLi return err; } +CHIP_ERROR EventManagement::FabricRemovedCB(const TLV::TLVReader & aReader, size_t aDepth, void * apContext) +{ + // the function does not actually remove the event, instead, it sets the fabric index to an invalid value. + FabricIndex * invalidFabricIndex = static_cast(apContext); + + TLVReader event; + TLVType tlvType; + TLVType tlvType1; + event.Init(aReader); + VerifyOrReturnError(event.EnterContainer(tlvType) == CHIP_NO_ERROR, CHIP_NO_ERROR); + VerifyOrReturnError(event.Next(TLV::ContextTag(to_underlying(EventReportIB::Tag::kEventData))) == CHIP_NO_ERROR, CHIP_NO_ERROR); + VerifyOrReturnError(event.EnterContainer(tlvType1) == CHIP_NO_ERROR, CHIP_NO_ERROR); + + while (CHIP_NO_ERROR == event.Next()) + { + if (event.GetTag() == TLV::ProfileTag(kEventManagementProfile, kFabricIndexTag)) + { + uint8_t fabricIndex = 0; + VerifyOrReturnError(event.Get(fabricIndex) == CHIP_NO_ERROR, CHIP_NO_ERROR); + if (fabricIndex == *invalidFabricIndex) + { + CHIPCircularTLVBuffer * readBuffer = static_cast(event.GetBackingStore()); + // fabricIndex is encoded as an integer; the dataPtr will point to a location immediately after its encoding + // shift the dataPtr to point to the encoding of the fabric index, accounting for wraparound in backing storage + // we cannot get the actual encoding size from current container beginning to the fabric index because of several + // optional parameters, so we are assuming minimal encoding is used and the fabric index is 1 byte. + uint8_t * dataPtr; + if (event.GetReadPoint() != readBuffer->GetQueue()) + { + dataPtr = readBuffer->GetQueue() + (event.GetReadPoint() - readBuffer->GetQueue() - 1); + } + else + { + dataPtr = readBuffer->GetQueue() + readBuffer->GetTotalDataLength() - 1; + } + + *dataPtr = kUndefinedFabricIndex; + } + return CHIP_NO_ERROR; + } + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR EventManagement::FabricRemoved(FabricIndex aFabricIndex) +{ + const bool recurse = false; + TLVReader reader; + CircularEventBufferWrapper bufWrapper; + +#if !CHIP_SYSTEM_CONFIG_NO_LOCKING + ScopedLock lock(sInstance); +#endif // !CHIP_SYSTEM_CONFIG_NO_LOCKING + + ReturnErrorOnFailure(GetEventReader(reader, PriorityLevel::Critical, &bufWrapper)); + CHIP_ERROR err = TLV::Utilities::Iterate(reader, FabricRemovedCB, &aFabricIndex, recurse); + if (err == CHIP_END_OF_TLV) + { + err = CHIP_NO_ERROR; + } + return err; +} + CHIP_ERROR EventManagement::GetEventReader(TLVReader & aReader, PriorityLevel aPriority, CircularEventBufferWrapper * apBufWrapper) { CircularEventBuffer * buffer = GetPriorityBuffer(aPriority); @@ -761,7 +827,7 @@ CHIP_ERROR EventManagement::GetEventReader(TLVReader & aReader, PriorityLevel aP return CHIP_NO_ERROR; } -CHIP_ERROR EventManagement::FetchEventParameters(const TLVReader & aReader, size_t aDepth, void * apContext) +CHIP_ERROR EventManagement::FetchEventParameters(const TLVReader & aReader, size_t, void * apContext) { EventEnvelopeContext * const envelope = static_cast(apContext); TLVReader reader; @@ -808,7 +874,9 @@ CHIP_ERROR EventManagement::FetchEventParameters(const TLVReader & aReader, size if (reader.GetTag() == TLV::ProfileTag(kEventManagementProfile, kFabricIndexTag)) { - ReturnErrorOnFailure(reader.Get(envelope->mFabricIndex)); + uint8_t fabricIndex = kUndefinedFabricIndex; + ReturnErrorOnFailure(reader.Get(fabricIndex)); + envelope->mFabricIndex.SetValue(fabricIndex); } return CHIP_NO_ERROR; } diff --git a/src/app/EventManagement.h b/src/app/EventManagement.h index 8afd26c903aaeb..235fdfe48d402c 100644 --- a/src/app/EventManagement.h +++ b/src/app/EventManagement.h @@ -337,6 +337,11 @@ class EventManagement CHIP_ERROR FetchEventsSince(chip::TLV::TLVWriter & aWriter, const ObjectList * apEventPathList, EventNumber & aEventMin, size_t & aEventCount, const Access::SubjectDescriptor & aSubjectDescriptor); + /** + * @brief brief Iterate all events and invalidate the fabric-sensitive events whose associated fabric has the given fabric + * index. + */ + CHIP_ERROR FabricRemoved(FabricIndex aFabricIndex); /** * @brief @@ -378,7 +383,7 @@ class EventManagement EndpointId mEndpointId = 0; EventId mEventId = 0; EventNumber mEventNumber = 0; - FabricIndex mFabricIndex = kUndefinedFabricIndex; + Optional mFabricIndex; }; void VendEventNumber(); @@ -419,6 +424,16 @@ class EventManagement */ CHIP_ERROR EnsureSpaceInCircularBuffer(size_t aRequiredSpace); + /** + * @brief Iterate the event elements inside event tlv and mark the fabric index as kUndefinedFabricIndex if + * it matches the FabricIndex apFabricIndex points to. + * + * @param[in] aReader event tlv reader + * @param[in] apFabricIndex A FabricIndex* pointing to the fabric index for which we want to effectively evict events. + * + */ + static CHIP_ERROR FabricRemovedCB(const TLV::TLVReader & aReader, size_t, void * apFabricIndex); + /** * @brief * Internal API used to implement #FetchEventsSince diff --git a/src/app/server/Server.h b/src/app/server/Server.h index 6a2192414dc770..86d5114e1815e3 100644 --- a/src/app/server/Server.h +++ b/src/app/server/Server.h @@ -328,6 +328,7 @@ class Server } } } + app::EventManagement::GetInstance().FabricRemoved(fabricIndex); }; void OnFabricRetrievedFromStorage(FabricInfo * fabricInfo) override { (void) fabricInfo; } diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index bb9f276adf921d..f2bb46253637ed 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -82,6 +82,7 @@ chip_test_suite("tests") { "TestEventLogging.cpp", "TestEventOverflow.cpp", "TestEventPathParams.cpp", + "TestFabricScopedEventLogging.cpp", "TestInteractionModelEngine.cpp", "TestMessageDef.cpp", "TestNumericAttributeTraits.cpp", diff --git a/src/app/tests/TestFabricScopedEventLogging.cpp b/src/app/tests/TestFabricScopedEventLogging.cpp new file mode 100644 index 00000000000000..e75a98e03312d9 --- /dev/null +++ b/src/app/tests/TestFabricScopedEventLogging.cpp @@ -0,0 +1,302 @@ +/* + * + * 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. + */ + +/** + * @file + * This file implements a test for CHIP Interaction Model Fabric-scoped Event logging + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +static const chip::ClusterId kLivenessClusterId = 0x00000022; +static const uint32_t kLivenessChangeEvent = 1; +static const chip::EndpointId kTestEndpointId1 = 2; +static const chip::EndpointId kTestEndpointId2 = 3; +static const chip::TLV::Tag kLivenessDeviceStatus = chip::TLV::ContextTag(1); + +static uint8_t gDebugEventBuffer[128]; +static uint8_t gInfoEventBuffer[128]; +static uint8_t gCritEventBuffer[128]; +static chip::app::CircularEventBuffer gCircularEventBuffer[3]; + +class TestContext : public chip::Test::AppContext +{ +public: + static int Initialize(void * context) + { + if (AppContext::Initialize(context) != SUCCESS) + return FAILURE; + + auto * ctx = static_cast(context); + + if (ctx->mEventCounter.Init(0) != CHIP_NO_ERROR) + { + return FAILURE; + } + + 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 }, + }; + + chip::app::EventManagement::CreateEventManagement(&ctx->GetExchangeManager(), + sizeof(logStorageResources) / sizeof(logStorageResources[0]), + gCircularEventBuffer, logStorageResources, &ctx->mEventCounter); + + return SUCCESS; + } + + static int Finalize(void * context) + { + chip::app::EventManagement::DestroyEventManagement(); + + if (AppContext::Finalize(context) != SUCCESS) + return FAILURE; + + return SUCCESS; + } + +private: + chip::MonotonicallyIncreasingCounter mEventCounter; +}; + +void ENFORCE_FORMAT(1, 2) SimpleDumpWriter(const char * aFormat, ...) +{ + va_list args; + + va_start(args, aFormat); + + vprintf(aFormat, args); + + va_end(args); +} + +void PrintEventLog(chip::app::PriorityLevel aPriorityLevel) +{ + chip::TLV::TLVReader reader; + size_t elementCount; + chip::app::CircularEventBufferWrapper bufWrapper; + chip::app::EventManagement::GetInstance().GetEventReader(reader, aPriorityLevel, &bufWrapper); + + chip::TLV::Utilities::Count(reader, elementCount, false); + printf("Found %u elements \n", static_cast(elementCount)); + chip::TLV::Debug::Dump(reader, SimpleDumpWriter); +} + +static void CheckLogState(nlTestSuite * apSuite, chip::app::EventManagement & aLogMgmt, size_t expectedNumEvents, + chip::app::PriorityLevel aPriority) +{ + CHIP_ERROR err; + chip::TLV::TLVReader reader; + size_t elementCount; + chip::app::CircularEventBufferWrapper bufWrapper; + err = aLogMgmt.GetEventReader(reader, aPriority, &bufWrapper); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + err = chip::TLV::Utilities::Count(reader, elementCount, false); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + NL_TEST_ASSERT(apSuite, elementCount == expectedNumEvents); + printf("elementCount vs expectedNumEvents : %u vs %u \n", static_cast(elementCount), + static_cast(expectedNumEvents)); +} + +static void CheckLogReadOut(nlTestSuite * apSuite, chip::app::EventManagement & alogMgmt, chip::EventNumber startingEventNumber, + size_t expectedNumEvents, chip::app::ObjectList * clusterInfo, + const chip::Access::SubjectDescriptor & aSubjectDescriptor) +{ + CHIP_ERROR err; + chip::TLV::TLVReader reader; + chip::TLV::TLVWriter writer; + size_t eventCount = 0; + uint8_t backingStore[1024]; + size_t totalNumElements; + writer.Init(backingStore, 1024); + err = alogMgmt.FetchEventsSince(writer, clusterInfo, startingEventNumber, eventCount, aSubjectDescriptor); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR || err == CHIP_END_OF_TLV); + + reader.Init(backingStore, writer.GetLengthWritten()); + + err = chip::TLV::Utilities::Count(reader, totalNumElements, false); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + printf("totalNumElements vs expectedNumEvents vs eventCount : %u vs %u vs %u \n", static_cast(totalNumElements), + static_cast(expectedNumEvents), static_cast(eventCount)); + NL_TEST_ASSERT(apSuite, totalNumElements == expectedNumEvents && totalNumElements == eventCount); + reader.Init(backingStore, writer.GetLengthWritten()); + chip::TLV::Debug::Dump(reader, SimpleDumpWriter); +} + +class TestEventGenerator : public chip::app::EventLoggingDelegate +{ +public: + CHIP_ERROR WriteEvent(chip::TLV::TLVWriter & aWriter) + { + chip::TLV::TLVType dataContainerType; + ReturnErrorOnFailure(aWriter.StartContainer(chip::TLV::ContextTag(chip::to_underlying(chip::app::EventDataIB::Tag::kData)), + chip::TLV::kTLVType_Structure, dataContainerType)); + ReturnErrorOnFailure(aWriter.Put(kLivenessDeviceStatus, mStatus)); + return aWriter.EndContainer(dataContainerType); + } + + void SetStatus(int32_t aStatus) { mStatus = aStatus; } + +private: + int32_t mStatus; +}; + +static void CheckLogEventWithEvictToNextBuffer(nlTestSuite * apSuite, void * apContext) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + chip::EventNumber eid1, eid2, eid3, eid4; + chip::app::EventOptions options1; + chip::app::EventOptions options2; + TestEventGenerator testEventGenerator; + + options1.mPath = { kTestEndpointId1, kLivenessClusterId, kLivenessChangeEvent }; + options1.mPriority = chip::app::PriorityLevel::Info; + options1.mFabricIndex = 1; + options2.mPath = { kTestEndpointId2, kLivenessClusterId, kLivenessChangeEvent }; + options2.mPriority = chip::app::PriorityLevel::Info; + options2.mFabricIndex = 2; + chip::app::EventManagement & logMgmt = chip::app::EventManagement::GetInstance(); + testEventGenerator.SetStatus(0); + err = logMgmt.LogEvent(&testEventGenerator, options1, eid1); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + CheckLogState(apSuite, logMgmt, 1, chip::app::PriorityLevel::Debug); + testEventGenerator.SetStatus(1); + err = logMgmt.LogEvent(&testEventGenerator, options1, eid2); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + CheckLogState(apSuite, logMgmt, 2, chip::app::PriorityLevel::Debug); + testEventGenerator.SetStatus(0); + err = logMgmt.LogEvent(&testEventGenerator, options1, eid3); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + CheckLogState(apSuite, logMgmt, 3, chip::app::PriorityLevel::Info); + // Start to copy info event to next buffer since current debug buffer is full and info event is higher priority + testEventGenerator.SetStatus(1); + err = logMgmt.LogEvent(&testEventGenerator, options2, eid4); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + CheckLogState(apSuite, logMgmt, 4, chip::app::PriorityLevel::Info); + + PrintEventLog(chip::app::PriorityLevel::Debug); + PrintEventLog(chip::app::PriorityLevel::Info); + + NL_TEST_ASSERT(apSuite, (eid1 + 1) == eid2); + NL_TEST_ASSERT(apSuite, (eid2 + 1) == eid3); + NL_TEST_ASSERT(apSuite, (eid3 + 1) == eid4); + + chip::app::ObjectList paths[2]; + + paths[0].mValue.mEndpointId = kTestEndpointId1; + paths[0].mValue.mClusterId = kLivenessClusterId; + + paths[1].mValue.mEndpointId = kTestEndpointId2; + paths[1].mValue.mClusterId = kLivenessClusterId; + paths[1].mValue.mEventId = kLivenessChangeEvent; + + chip::Access::SubjectDescriptor descriptor; + descriptor.fabricIndex = 1; + + CheckLogReadOut(apSuite, logMgmt, 0, 3, &paths[0], descriptor); + CheckLogReadOut(apSuite, logMgmt, 1, 2, &paths[0], descriptor); + CheckLogReadOut(apSuite, logMgmt, 2, 1, &paths[0], descriptor); + CheckLogReadOut(apSuite, logMgmt, 3, 0, &paths[1], descriptor); + descriptor.fabricIndex = 2; + CheckLogReadOut(apSuite, logMgmt, 3, 1, &paths[1], descriptor); + + paths[0].mpNext = &paths[1]; + descriptor.fabricIndex = 1; + + CheckLogReadOut(apSuite, logMgmt, 0, 3, paths, descriptor); + descriptor.fabricIndex = 2; + CheckLogReadOut(apSuite, logMgmt, 0, 1, paths, descriptor); + + // Fabric event + wildcard test, only have one fabric-scoped event with fabric 2 + chip::app::ObjectList pathsWithWildcard[2]; + paths[0].mValue.mEndpointId = kTestEndpointId1; + paths[0].mValue.mClusterId = kLivenessClusterId; + + CheckLogReadOut(apSuite, logMgmt, 0, 1, &pathsWithWildcard[1], descriptor); + + paths[0].mpNext = &paths[1]; + CheckLogReadOut(apSuite, logMgmt, 0, 1, pathsWithWildcard, descriptor); + + // Invalidate obsolete fabric-scope event + + // Invalidate 3 event with fabric 1 + descriptor.fabricIndex = 1; + logMgmt.FabricRemoved(descriptor.fabricIndex); + CheckLogReadOut(apSuite, logMgmt, 0, 0, &pathsWithWildcard[1], descriptor); + + // Invalidate 1 event with fabric 2 + descriptor.fabricIndex = 2; + logMgmt.FabricRemoved(descriptor.fabricIndex); + CheckLogReadOut(apSuite, logMgmt, 0, 0, &pathsWithWildcard[1], descriptor); +} +/** + * Test Suite. It lists all the test functions. + */ + +const nlTest sTests[] = { NL_TEST_DEF("CheckLogEventWithEvictToNextBuffer", CheckLogEventWithEvictToNextBuffer), + NL_TEST_SENTINEL() }; + +// clang-format off +nlTestSuite sSuite = +{ + "TestFabricScopedEventLogging", + &sTests[0], + TestContext::Initialize, + TestContext::Finalize +}; +// clang-format on + +} // namespace + +int TestFabricScopedEventLogging() +{ + TestContext gContext; + nlTestRunner(&sSuite, &gContext); + return (nlTestRunnerStats(&sSuite)); +} + +CHIP_REGISTER_TEST_SUITE(TestFabricScopedEventLogging) diff --git a/src/lib/core/CHIPTLV.h b/src/lib/core/CHIPTLV.h index f43b0a288a5797..eaff1c29b9fee7 100644 --- a/src/lib/core/CHIPTLV.h +++ b/src/lib/core/CHIPTLV.h @@ -839,6 +839,12 @@ class DLL_EXPORT TLVReader */ uint32_t GetRemainingLength() const { return mMaxLen - mLenRead; } + /** + * Returns the stored backing store. + * + * @return the stored backing store. + */ + TLVBackingStore * GetBackingStore() { return mBackingStore; } /** * Gets the point in the underlying input buffer that corresponds to the reader's current position. *