Add support for caching events to the `AttributeCache` (#17030)
* s/AttributeCache/ClusterStateCache/g in preparation of the event caching getting subsumed
* Add EventCaching support to the ClusterStateCache.
* Review feedback
* Reverted the rename of the Darwin files for now since it was starting to get a bit out of hand
* Apply suggestions from code review
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
* Review feedback
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
diff --git a/scripts/tools/check_includes_config.py b/scripts/tools/check_includes_config.py
index 36491a3..ccc3de5 100644
--- a/scripts/tools/check_includes_config.py
+++ b/scripts/tools/check_includes_config.py
@@ -103,7 +103,7 @@
ALLOW: Dict[str, Set[str]] = {
# Not intended for embedded clients (#11705).
- 'src/app/AttributeCache.h': {'list', 'map', 'set', 'vector'},
+ 'src/app/ClusterStateCache.h': {'list', 'map', 'set', 'vector', 'queue'},
'src/app/BufferedReadCallback.h': {'vector'},
# Itself in DENY.
diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn
index d5ee809..51080fa 100644
--- a/src/app/BUILD.gn
+++ b/src/app/BUILD.gn
@@ -53,8 +53,6 @@
sources = [
"AttributeAccessInterface.cpp",
- "AttributeCache.cpp",
- "AttributeCache.h",
"AttributePathExpandIterator.cpp",
"AttributePathExpandIterator.h",
"AttributePathParams.h",
@@ -67,6 +65,8 @@
"CASESessionManager.h",
"ChunkedWriteCallback.cpp",
"ChunkedWriteCallback.h",
+ "ClusterStateCache.cpp",
+ "ClusterStateCache.h",
"CommandHandler.cpp",
"CommandResponseHelper.h",
"CommandSender.cpp",
diff --git a/src/app/AttributeCache.cpp b/src/app/ClusterStateCache.cpp
similarity index 73%
rename from src/app/AttributeCache.cpp
rename to src/app/ClusterStateCache.cpp
index 0970b78..8d8c72a 100644
--- a/src/app/AttributeCache.cpp
+++ b/src/app/ClusterStateCache.cpp
@@ -17,14 +17,15 @@
*/
#include "system/SystemPacketBuffer.h"
-#include <app/AttributeCache.h>
+#include <app/ClusterStateCache.h>
#include <app/InteractionModelEngine.h>
#include <tuple>
namespace chip {
namespace app {
-CHIP_ERROR AttributeCache::UpdateCache(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus)
+CHIP_ERROR ClusterStateCache::UpdateCache(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData,
+ const StatusIB & aStatus)
{
AttributeState state;
System::PacketBufferHandle handle;
@@ -85,6 +86,7 @@
{
mCache[aPath.mEndpointId][aPath.mClusterId].mPendingDataVersion = aPath.mDataVersion;
}
+
mLastReportDataPath = aPath;
}
else
@@ -106,7 +108,49 @@
return CHIP_NO_ERROR;
}
-void AttributeCache::OnReportBegin()
+CHIP_ERROR ClusterStateCache::UpdateEventCache(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus)
+{
+ if (apData)
+ {
+ //
+ // If we've already seen this event before, there's no more work to be done.
+ //
+ if (mHighestReceivedEventNumber.HasValue() && aEventHeader.mEventNumber <= mHighestReceivedEventNumber.Value())
+ {
+ return CHIP_NO_ERROR;
+ }
+
+ System::PacketBufferHandle handle = System::PacketBufferHandle::New(chip::app::kMaxSecureSduLengthBytes);
+
+ System::PacketBufferTLVWriter writer;
+ writer.Init(std::move(handle), false);
+
+ ReturnErrorOnFailure(writer.CopyElement(TLV::AnonymousTag(), *apData));
+ ReturnErrorOnFailure(writer.Finalize(&handle));
+
+ //
+ // Compact the buffer down to a more reasonably sized packet buffer
+ // if we can.
+ //
+ handle.RightSize();
+
+ EventData eventData;
+ eventData.first = aEventHeader;
+ eventData.second = std::move(handle);
+
+ mEventDataCache.insert(std::move(eventData));
+
+ mHighestReceivedEventNumber.SetValue(aEventHeader.mEventNumber);
+ }
+ else if (apStatus)
+ {
+ mEventStatusCache[aEventHeader.mPath] = *apStatus;
+ }
+
+ return CHIP_NO_ERROR;
+}
+
+void ClusterStateCache::OnReportBegin()
{
mLastReportDataPath = ConcreteClusterPath(kInvalidEndpointId, kInvalidClusterId);
mChangedAttributeSet.clear();
@@ -114,7 +158,7 @@
mCallback.OnReportBegin();
}
-void AttributeCache::CommitPendingDataVersion()
+void ClusterStateCache::CommitPendingDataVersion()
{
if (!mLastReportDataPath.IsValidConcreteClusterPath())
{
@@ -129,7 +173,7 @@
}
}
-void AttributeCache::OnReportEnd()
+void ClusterStateCache::OnReportEnd()
{
CommitPendingDataVersion();
mLastReportDataPath = ConcreteClusterPath(kInvalidEndpointId, kInvalidClusterId);
@@ -158,7 +202,112 @@
mCallback.OnReportEnd();
}
-void AttributeCache::OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus)
+CHIP_ERROR ClusterStateCache::Get(const ConcreteAttributePath & path, TLV::TLVReader & reader)
+{
+ CHIP_ERROR err;
+
+ auto attributeState = GetAttributeState(path.mEndpointId, path.mClusterId, path.mAttributeId, err);
+ ReturnErrorOnFailure(err);
+
+ if (attributeState->Is<StatusIB>())
+ {
+ return CHIP_ERROR_IM_STATUS_CODE_RECEIVED;
+ }
+
+ System::PacketBufferTLVReader bufReader;
+
+ bufReader.Init(attributeState->Get<System::PacketBufferHandle>().Retain());
+ ReturnErrorOnFailure(bufReader.Next());
+
+ reader.Init(bufReader);
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR ClusterStateCache::Get(EventNumber eventNumber, TLV::TLVReader & reader)
+{
+ CHIP_ERROR err;
+
+ auto eventData = GetEventData(eventNumber, err);
+ ReturnErrorOnFailure(err);
+
+ System::PacketBufferTLVReader bufReader;
+
+ bufReader.Init(eventData->second.Retain());
+ ReturnErrorOnFailure(bufReader.Next());
+
+ reader.Init(bufReader);
+ return CHIP_NO_ERROR;
+}
+
+ClusterStateCache::EndpointState * ClusterStateCache::GetEndpointState(EndpointId endpointId, CHIP_ERROR & err)
+{
+ auto endpointIter = mCache.find(endpointId);
+ if (endpointIter == mCache.end())
+ {
+ err = CHIP_ERROR_KEY_NOT_FOUND;
+ return nullptr;
+ }
+
+ err = CHIP_NO_ERROR;
+ return &endpointIter->second;
+}
+
+ClusterStateCache::ClusterState * ClusterStateCache::GetClusterState(EndpointId endpointId, ClusterId clusterId, CHIP_ERROR & err)
+{
+ auto endpointState = GetEndpointState(endpointId, err);
+ if (err != CHIP_NO_ERROR)
+ {
+ return nullptr;
+ }
+
+ auto clusterState = endpointState->find(clusterId);
+ if (clusterState == endpointState->end())
+ {
+ err = CHIP_ERROR_KEY_NOT_FOUND;
+ return nullptr;
+ }
+
+ err = CHIP_NO_ERROR;
+ return &clusterState->second;
+}
+
+const ClusterStateCache::AttributeState * ClusterStateCache::GetAttributeState(EndpointId endpointId, ClusterId clusterId,
+ AttributeId attributeId, CHIP_ERROR & err)
+{
+ auto clusterState = GetClusterState(endpointId, clusterId, err);
+ if (err != CHIP_NO_ERROR)
+ {
+ return nullptr;
+ }
+
+ auto attributeState = clusterState->mAttributes.find(attributeId);
+ if (attributeState == clusterState->mAttributes.end())
+ {
+ err = CHIP_ERROR_KEY_NOT_FOUND;
+ return nullptr;
+ }
+
+ err = CHIP_NO_ERROR;
+ return &attributeState->second;
+}
+
+const ClusterStateCache::EventData * ClusterStateCache::GetEventData(EventNumber eventNumber, CHIP_ERROR & err)
+{
+ EventData compareKey;
+
+ compareKey.first.mEventNumber = eventNumber;
+ auto eventData = mEventDataCache.find(std::move(compareKey));
+ if (eventData == mEventDataCache.end())
+ {
+ err = CHIP_ERROR_KEY_NOT_FOUND;
+ return nullptr;
+ }
+
+ err = CHIP_NO_ERROR;
+ return &(*eventData);
+}
+
+void ClusterStateCache::OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus)
{
//
// Since the cache itself is a ReadClient::Callback, it may be incorrectly passed in directly when registering with the
@@ -188,28 +337,7 @@
mCallback.OnAttributeData(aPath, apData ? &dataSnapshot : nullptr, aStatus);
}
-CHIP_ERROR AttributeCache::Get(const ConcreteAttributePath & path, TLV::TLVReader & reader)
-{
- CHIP_ERROR err;
-
- auto attributeState = GetAttributeState(path.mEndpointId, path.mClusterId, path.mAttributeId, err);
- ReturnErrorOnFailure(err);
-
- if (attributeState->Is<StatusIB>())
- {
- return CHIP_ERROR_IM_STATUS_CODE_RECEIVED;
- }
-
- System::PacketBufferTLVReader bufReader;
-
- bufReader.Init(attributeState->Get<System::PacketBufferHandle>().Retain());
- ReturnErrorOnFailure(bufReader.Next());
-
- reader.Init(bufReader);
- return CHIP_NO_ERROR;
-}
-
-CHIP_ERROR AttributeCache::GetVersion(EndpointId mEndpointId, ClusterId mClusterId, Optional<DataVersion> & aVersion)
+CHIP_ERROR ClusterStateCache::GetVersion(EndpointId mEndpointId, ClusterId mClusterId, Optional<DataVersion> & aVersion)
{
CHIP_ERROR err;
auto clusterState = GetClusterState(mEndpointId, mClusterId, err);
@@ -218,59 +346,21 @@
return CHIP_NO_ERROR;
}
-AttributeCache::EndpointState * AttributeCache::GetEndpointState(EndpointId endpointId, CHIP_ERROR & err)
+void ClusterStateCache::OnEventData(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus)
{
- auto endpointIter = mCache.find(endpointId);
- if (endpointIter == mCache.end())
+ VerifyOrDie(apData != nullptr || apStatus != nullptr);
+
+ TLV::TLVReader dataSnapshot;
+ if (apData)
{
- err = CHIP_ERROR_KEY_NOT_FOUND;
- return nullptr;
+ dataSnapshot.Init(*apData);
}
- err = CHIP_NO_ERROR;
- return &endpointIter->second;
+ UpdateEventCache(aEventHeader, apData, apStatus);
+ mCallback.OnEventData(aEventHeader, apData ? &dataSnapshot : nullptr, apStatus);
}
-AttributeCache::ClusterState * AttributeCache::GetClusterState(EndpointId endpointId, ClusterId clusterId, CHIP_ERROR & err)
-{
- auto endpointState = GetEndpointState(endpointId, err);
- if (err != CHIP_NO_ERROR)
- {
- return nullptr;
- }
-
- auto clusterState = endpointState->find(clusterId);
- if (clusterState == endpointState->end())
- {
- err = CHIP_ERROR_KEY_NOT_FOUND;
- return nullptr;
- }
-
- err = CHIP_NO_ERROR;
- return &clusterState->second;
-}
-
-AttributeCache::AttributeState * AttributeCache::GetAttributeState(EndpointId endpointId, ClusterId clusterId,
- AttributeId attributeId, CHIP_ERROR & err)
-{
- auto clusterState = GetClusterState(endpointId, clusterId, err);
- if (err != CHIP_NO_ERROR)
- {
- return nullptr;
- }
-
- auto attributeState = clusterState->mAttributes.find(attributeId);
- if (attributeState == clusterState->mAttributes.end())
- {
- err = CHIP_ERROR_KEY_NOT_FOUND;
- return nullptr;
- }
-
- err = CHIP_NO_ERROR;
- return &attributeState->second;
-}
-
-CHIP_ERROR AttributeCache::GetStatus(const ConcreteAttributePath & path, StatusIB & status)
+CHIP_ERROR ClusterStateCache::GetStatus(const ConcreteAttributePath & path, StatusIB & status)
{
CHIP_ERROR err;
@@ -286,7 +376,19 @@
return CHIP_NO_ERROR;
}
-void AttributeCache::GetSortedFilters(std::vector<std::pair<DataVersionFilter, size_t>> & aVector)
+CHIP_ERROR ClusterStateCache::GetStatus(const ConcreteEventPath & path, StatusIB & status)
+{
+ auto statusIter = mEventStatusCache.find(path);
+ if (statusIter == mEventStatusCache.end())
+ {
+ return CHIP_ERROR_KEY_NOT_FOUND;
+ }
+
+ status = statusIter->second;
+ return CHIP_NO_ERROR;
+}
+
+void ClusterStateCache::GetSortedFilters(std::vector<std::pair<DataVersionFilter, size_t>> & aVector)
{
for (auto const & endpointIter : mCache)
{
@@ -342,9 +444,9 @@
});
}
-CHIP_ERROR AttributeCache::OnUpdateDataVersionFilterList(DataVersionFilterIBs::Builder & aDataVersionFilterIBsBuilder,
- const Span<AttributePathParams> & aAttributePaths,
- bool & aEncodedDataVersionList)
+CHIP_ERROR ClusterStateCache::OnUpdateDataVersionFilterList(DataVersionFilterIBs::Builder & aDataVersionFilterIBsBuilder,
+ const Span<AttributePathParams> & aAttributePaths,
+ bool & aEncodedDataVersionList)
{
CHIP_ERROR err = CHIP_NO_ERROR;
TLV::TLVWriter backup;
diff --git a/src/app/AttributeCache.h b/src/app/ClusterStateCache.h
similarity index 65%
rename from src/app/AttributeCache.h
rename to src/app/ClusterStateCache.h
index 3a602b1..720766e 100644
--- a/src/app/AttributeCache.h
+++ b/src/app/ClusterStateCache.h
@@ -29,13 +29,14 @@
#include <lib/support/Variant.h>
#include <list>
#include <map>
+#include <queue>
#include <set>
#include <vector>
namespace chip {
namespace app {
/*
- * This implements an attribute cache designed to aggregate attribute data received by a client
+ * This implements a cluster state cache designed to aggregate both attribute and event data received by a client
* from either read or subscribe interactions and keep it resident and available for clients to
* query at any time while the cache is active.
*
@@ -46,9 +47,11 @@
* state being determined by the associated ReadClient's path set.
*
* The cache provides a number of getters and helper functions to iterate over the topology
- * of the received data which is organized by endpoint, cluster and attribute ID. These permit greater
+ * of the received data which is organized by endpoint, cluster and attribute ID (for attributes). These permit greater
* flexibility when dealing with interactions that use wildcards heavily.
*
+ * For events, functions that permit iteration over the cached events sorted by event number are provided.
+ *
* The data is stored internally in the cache as TLV. This permits re-use of the existing cluster objects
* to de-serialize the state on-demand.
*
@@ -61,7 +64,7 @@
* 2. The same cache cannot be used by multiple subscribe/read interactions at the same time.
*
*/
-class AttributeCache : protected ReadClient::Callback
+class ClusterStateCache : protected ReadClient::Callback
{
public:
class Callback : public ReadClient::Callback
@@ -70,31 +73,40 @@
/*
* Called anytime an attribute value has changed in the cache
*/
- virtual void OnAttributeChanged(AttributeCache * cache, const ConcreteAttributePath & path){};
+ virtual void OnAttributeChanged(ClusterStateCache * cache, const ConcreteAttributePath & path){};
/*
* Called anytime any attribute in a cluster has changed in the cache
*/
- virtual void OnClusterChanged(AttributeCache * cache, EndpointId endpointId, ClusterId clusterId){};
+ virtual void OnClusterChanged(ClusterStateCache * cache, EndpointId endpointId, ClusterId clusterId){};
/*
* Called anytime an endpoint was added to the cache
*/
- virtual void OnEndpointAdded(AttributeCache * cache, EndpointId endpointId){};
+ virtual void OnEndpointAdded(ClusterStateCache * cache, EndpointId endpointId){};
};
- AttributeCache(Callback & callback) : mCallback(callback), mBufferedReader(*this) {}
+ ClusterStateCache(Callback & callback, Optional<EventNumber> highestReceivedEventNumber = Optional<EventNumber>::Missing()) :
+ mCallback(callback), mBufferedReader(*this)
+ {
+ mHighestReceivedEventNumber = highestReceivedEventNumber;
+ }
+
+ void SetHighestReceivedEventNumber(EventNumber highestReceivedEventNumber)
+ {
+ mHighestReceivedEventNumber.SetValue(highestReceivedEventNumber);
+ }
/*
- * When registering as a callback to the ReadClient, the AttributeCache cannot not be passed as a callback
+ * When registering as a callback to the ReadClient, the ClusterStateCache cannot not be passed as a callback
* directly. Instead, utilize this method below to correctly set up the callback chain such that
* the buffered reader is the first callback in the chain before calling into cache subsequently.
*/
ReadClient::Callback & GetBufferedCallback() { return mBufferedReader; }
/*
- * Retrieve the value of an attribute from the cache (if present) given a concrete path and decode
- * is using DataModel::Decode into the in-out argument 'value'.
+ * Retrieve the value of an attribute from the cache (if present) given a concrete path by decoding
+ * it using DataModel::Decode into the in-out argument 'value'.
*
* For some types of attributes, the value for the attribute is directly backed by the underlying TLV buffer
* and has pointers into that buffer. (e.g octet strings, char strings and lists). This buffer only remains
@@ -227,6 +239,69 @@
CHIP_ERROR GetVersion(EndpointId mEndpointId, ClusterId mClusterId, Optional<DataVersion> & aVersion);
/*
+ * Retrieve the value of an event from the cache given an EventNumber by decoding
+ * it using DataModel::Decode into the in-out argument 'value'.
+ *
+ * This should be used in conjunction with the ForEachEvent() iterator function to
+ * retrieve the EventHeader (and corresponding metadata information for the event) along with its EventNumber.
+ *
+ * For some types of events, the values for the fields in the event are directly backed by the underlying TLV buffer
+ * and have pointers into that buffer. (e.g octet strings, char strings and lists). Unlike its attribute counterpart,
+ * these pointers are stable and will not change until a call to `ClearEventCache` happens.
+ *
+ * The template parameter EventObjectTypeT is generally expected to be a
+ * ClusterName::Events::EventName::DecodableType, but any
+ * object that can be decoded using the DataModel::Decode machinery will work.
+ *
+ * Notable return values:
+ * - If the provided attribute object's Cluster and Event IDs don't match those of the event in the cache,
+ * a CHIP_ERROR_SCHEMA_MISMATCH shall be returned.
+ *
+ * - If event doesn't exist in the cache, CHIP_ERROR_KEY_NOT_FOUND
+ * shall be returned.
+ */
+
+ template <typename EventObjectTypeT>
+ CHIP_ERROR Get(EventNumber eventNumber, EventObjectTypeT & value)
+ {
+ TLV::TLVReader reader;
+ CHIP_ERROR err;
+
+ auto * eventData = GetEventData(eventNumber, err);
+ ReturnErrorOnFailure(err);
+
+ if (eventData->first.mPath.mClusterId != value.GetClusterId() || eventData->first.mPath.mEventId != value.GetEventId())
+ {
+ return CHIP_ERROR_SCHEMA_MISMATCH;
+ }
+
+ ReturnErrorOnFailure(Get(eventNumber, reader));
+ return DataModel::Decode(reader, value);
+ }
+
+ /*
+ * Retrieve the data of an event by updating a in-out TLVReader to be positioned
+ * right at the structure that encapsulates the event payload.
+ *
+ * Notable return values:
+ * - If no event with a matching eventNumber exists in the cache, CHIP_ERROR_KEY_NOT_FOUND
+ * shall be returned.
+ *
+ */
+ CHIP_ERROR Get(EventNumber eventNumber, TLV::TLVReader & reader);
+
+ /*
+ * Retrieve the StatusIB for a specific event from the event status cache (if one exists).
+ * Otherwise, a CHIP_ERROR_KEY_NOT_FOUND error will be returned.
+ *
+ * This is to be used with the `ForEachEventStatus` iterator function.
+ *
+ * NOTE: Receipt of a StatusIB does not affect any pre-existing or future event data entries in the cache (and vice-versa).
+ *
+ */
+ CHIP_ERROR GetStatus(const ConcreteEventPath & path, StatusIB & status);
+
+ /*
* Execute an iterator function that is called for every attribute
* in a given endpoint and cluster. The function when invoked is provided a concrete attribute path
* to every attribute that matches in the cache.
@@ -319,6 +394,85 @@
return CHIP_NO_ERROR;
}
+ /*
+ * Execute an iterator function that is called for every event in the event data cache that satisfies the following
+ * conditions:
+ * - It matches the provided path filter
+ * - Its event number is greater than or equal to the provided minimum event number filter.
+ *
+ * Each filter argument can be omitted from the match criteria above by passing in an empty EventPathParams() and/or
+ * a minimum event filter of 0.
+ *
+ * This iterator is called in increasing order from the event with the lowest event number to the highest.
+ *
+ * The function is passed a const reference to the EventHeader associated with that event.
+ *
+ * The iterator is expected to have this signature:
+ * CHIP_ERROR IteratorFunc(const EventHeader & eventHeader);
+ *
+ * Notable return values:
+ * - If func returns an error, that will result in termination of any further iteration over events
+ * and that error shall be returned back up to the original call to this function.
+ *
+ */
+ template <typename IteratorFunc>
+ CHIP_ERROR ForEachEventData(IteratorFunc func, EventPathParams pathFilter = EventPathParams(),
+ EventNumber minEventNumberFilter = 0)
+ {
+ for (const auto & item : mEventDataCache)
+ {
+ if (pathFilter.IsEventPathSupersetOf(item.first.mPath) && item.first.mEventNumber >= minEventNumberFilter)
+ {
+ ReturnErrorOnFailure(func(item.first));
+ }
+ }
+
+ return CHIP_NO_ERROR;
+ }
+
+ /*
+ * Execute an iterator function that is called for every StatusIB in the event status cache.
+ *
+ * The iterator is expected to have this signature:
+ * CHIP_ERROR IteratorFunc(const ConcreteEventPath & eventPath, const StatusIB & statusIB);
+ *
+ * Notable return values:
+ * - If func returns an error, that will result in termination of any further iteration over events
+ * and that error shall be returned back up to the original call to this function.
+ *
+ * NOTE: Receipt of a StatusIB does not affect any pre-existing event data entries in the cache (and vice-versa).
+ *
+ */
+ template <typename IteratorFunc>
+ CHIP_ERROR ForEachEventStatus(IteratorFunc func)
+ {
+ for (const auto & item : mEventStatusCache)
+ {
+ ReturnErrorOnFailure(func(item.first, item.second));
+ }
+ }
+
+ /*
+ * Clear out the event data and status caches.
+ *
+ * By default, this will not clear out any internally tracked event counters, specifically:
+ * - the highest event number seen so far. This is used in reads/subscribe requests to express to the receiving
+ * server to not send events that the client has already seen so far.
+ *
+ * That can be over-ridden by passing in 'true' to `resetTrackedEventCounters`.
+ *
+ */
+ void ClearEventCache(bool resetTrackedEventCounters = false)
+ {
+ mEventDataCache.clear();
+ if (resetTrackedEventCounters)
+ {
+ mHighestReceivedEventNumber.ClearValue();
+ }
+
+ mEventStatusCache.clear();
+ }
+
private:
using AttributeState = Variant<System::PacketBufferHandle, StatusIB>;
// mPendingDataVersion represents a tentative data version for a cluster that we have gotten some reports for.
@@ -342,6 +496,21 @@
return x.mEndpointId < y.mEndpointId || x.mClusterId < y.mClusterId;
}
};
+
+ using EventData = std::pair<EventHeader, System::PacketBufferHandle>;
+
+ //
+ // This is a custom comparator for use with the std::set<EventData> below. Uniqueness
+ // is determined solely by the event number associated with each event.
+ //
+ struct EventDataCompare
+ {
+ bool operator()(const EventData & lhs, const EventData & rhs) const
+ {
+ return (lhs.first.mEventNumber < rhs.first.mEventNumber);
+ }
+ };
+
/*
* These functions provide a way to index into the cached state with different sub-sets of a path, returning
* appropriate slices of the data as requested.
@@ -356,7 +525,9 @@
*/
EndpointState * GetEndpointState(EndpointId endpointId, CHIP_ERROR & err);
ClusterState * GetClusterState(EndpointId endpointId, ClusterId clusterId, CHIP_ERROR & err);
- AttributeState * GetAttributeState(EndpointId endpointId, ClusterId clusterId, AttributeId attributeId, CHIP_ERROR & err);
+ const AttributeState * GetAttributeState(EndpointId endpointId, ClusterId clusterId, AttributeId attributeId, CHIP_ERROR & err);
+
+ const EventData * GetEventData(EventNumber number, CHIP_ERROR & err);
/*
* Updates the state of an attribute in the cache given a reader. If the reader is null, the state is updated
@@ -364,18 +535,24 @@
*/
CHIP_ERROR UpdateCache(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus);
+ /*
+ * If apData is not null, updates the cached event set with the specified event header + payload.
+ * If apData is null and apStatus is not null, the StatusIB is stored in the event status cache.
+ *
+ * Storage of either of these do not affect pre-existing data for the other events in the cache.
+ *
+ */
+ CHIP_ERROR UpdateEventCache(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus);
+
//
// ReadClient::Callback
//
void OnReportBegin() override;
void OnReportEnd() override;
void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) override;
- void OnError(CHIP_ERROR aError) override { mCallback.OnError(aError); }
+ void OnError(CHIP_ERROR aError) override { return mCallback.OnError(aError); }
- void OnEventData(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus) override
- {
- mCallback.OnEventData(aEventHeader, apData, apStatus);
- }
+ void OnEventData(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus) override;
void OnDone() override
{
@@ -407,6 +584,10 @@
std::set<ConcreteAttributePath> mChangedAttributeSet;
std::set<AttributePathParams, Comparator> mRequestPathSet; // wildcard attribute request path only
std::vector<EndpointId> mAddedEndpoints;
+
+ std::set<EventData, EventDataCompare> mEventDataCache;
+ Optional<EventNumber> mHighestReceivedEventNumber;
+ std::map<ConcreteEventPath, StatusIB> mEventStatusCache;
BufferedReadCallback mBufferedReader;
ConcreteClusterPath mLastReportDataPath = ConcreteClusterPath(kInvalidEndpointId, kInvalidClusterId);
};
diff --git a/src/app/ConcreteEventPath.h b/src/app/ConcreteEventPath.h
index 92dc185..3909b84 100644
--- a/src/app/ConcreteEventPath.h
+++ b/src/app/ConcreteEventPath.h
@@ -45,6 +45,12 @@
bool operator!=(const ConcreteEventPath & aOther) const { return !(*this == aOther); }
+ bool operator<(const ConcreteEventPath & path) const
+ {
+ return (mEndpointId < path.mEndpointId) || ((mEndpointId == path.mEndpointId) && (mClusterId < path.mClusterId)) ||
+ ((mEndpointId == path.mEndpointId) && (mClusterId == path.mClusterId) && (mEventId < path.mEventId));
+ }
+
EventId mEventId = 0;
};
} // namespace app
diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn
index 907bb22..bb9f276 100644
--- a/src/app/tests/BUILD.gn
+++ b/src/app/tests/BUILD.gn
@@ -100,7 +100,7 @@
#
if (chip_device_platform != "nrfconnect") {
test_sources += [ "TestBufferedReadCallback.cpp" ]
- test_sources += [ "TestAttributeCache.cpp" ]
+ test_sources += [ "TestClusterStateCache.cpp" ]
}
cflags = [ "-Wconversion" ]
diff --git a/src/app/tests/TestAttributeCache.cpp b/src/app/tests/TestClusterStateCache.cpp
similarity index 96%
rename from src/app/tests/TestAttributeCache.cpp
rename to src/app/tests/TestClusterStateCache.cpp
index 6c10e12..0bca990 100644
--- a/src/app/tests/TestAttributeCache.cpp
+++ b/src/app/tests/TestClusterStateCache.cpp
@@ -23,7 +23,7 @@
#include "system/SystemPacketBuffer.h"
#include "system/TLVPacketBufferBackingStore.h"
#include <app-common/zap-generated/cluster-objects.h>
-#include <app/AttributeCache.h>
+#include <app/ClusterStateCache.h>
#include <app/data-model/DecodableList.h>
#include <app/data-model/Decode.h>
#include <app/tests/AppTestContext.h>
@@ -270,7 +270,7 @@
callback->OnReportEnd();
}
-class CacheValidator : public AttributeCache::Callback
+class CacheValidator : public ClusterStateCache::Callback
{
public:
CacheValidator(AttributeInstructionListType & instructionList, ForwardedDataCallbackValidator & dataCallbackValidator);
@@ -301,7 +301,7 @@
}
}
- void DecodeAttribute(const AttributeInstruction & instruction, const ConcreteAttributePath & path, AttributeCache * cache)
+ void DecodeAttribute(const AttributeInstruction & instruction, const ConcreteAttributePath & path, ClusterStateCache * cache)
{
CHIP_ERROR err;
bool gotStatus = false;
@@ -401,9 +401,10 @@
}
}
- void DecodeClusterObject(const AttributeInstruction & instruction, const ConcreteAttributePath & path, AttributeCache * cache)
+ void DecodeClusterObject(const AttributeInstruction & instruction, const ConcreteAttributePath & path,
+ ClusterStateCache * cache)
{
- std::list<AttributeCache::AttributeStatus> statusList;
+ std::list<ClusterStateCache::AttributeStatus> statusList;
NL_TEST_ASSERT(gSuite, cache->Get(path.mEndpointId, path.mClusterId, clusterValue, statusList) == CHIP_NO_ERROR);
if (instruction.mValueType == AttributeInstruction::kData)
@@ -454,7 +455,7 @@
}
}
- void OnAttributeChanged(AttributeCache * cache, const ConcreteAttributePath & path) override
+ void OnAttributeChanged(ClusterStateCache * cache, const ConcreteAttributePath & path) override
{
StatusIB status;
@@ -482,14 +483,14 @@
}
}
- void OnClusterChanged(AttributeCache * cache, EndpointId endpointId, ClusterId clusterId) override
+ void OnClusterChanged(ClusterStateCache * cache, EndpointId endpointId, ClusterId clusterId) override
{
auto iter = mExpectedClusters.find(std::make_tuple(endpointId, clusterId));
NL_TEST_ASSERT(gSuite, iter != mExpectedClusters.end());
mExpectedClusters.erase(iter);
}
- void OnEndpointAdded(AttributeCache * cache, EndpointId endpointId) override
+ void OnEndpointAdded(ClusterStateCache * cache, EndpointId endpointId) override
{
auto iter = mExpectedEndpoints.find(endpointId);
NL_TEST_ASSERT(gSuite, iter != mExpectedEndpoints.end());
@@ -538,7 +539,7 @@
{
ForwardedDataCallbackValidator dataCallbackValidator;
CacheValidator client(list, dataCallbackValidator);
- AttributeCache cache(client);
+ ClusterStateCache cache(client);
DataSeriesGenerator generator(&cache.GetBufferedCallback(), list);
generator.Generate(dataCallbackValidator);
}
@@ -622,7 +623,7 @@
nlTestSuite theSuite =
{
- "TestAttributeCache",
+ "TestClusterStateCache",
&sTests[0],
TestContext::InitializeAsync,
TestContext::Finalize
@@ -631,7 +632,7 @@
}
// clang-format on
-int TestAttributeCache()
+int TestClusterStateCache()
{
TestContext gContext;
gSuite = &theSuite;
@@ -639,4 +640,4 @@
return (nlTestRunnerStats(&theSuite));
}
-CHIP_REGISTER_TEST_SUITE(TestAttributeCache)
+CHIP_REGISTER_TEST_SUITE(TestClusterStateCache)
diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp
index 09a712c..b1ce32d 100644
--- a/src/controller/CHIPDeviceController.cpp
+++ b/src/controller/CHIPDeviceController.cpp
@@ -1572,7 +1572,7 @@
}
}
-// AttributeCache::Callback impl
+// ClusterStateCache::Callback impl
void DeviceCommissioner::OnDone()
{
CHIP_ERROR err;
@@ -1835,7 +1835,7 @@
{
readParams.mTimeout = timeout.Value();
}
- auto attributeCache = Platform::MakeUnique<app::AttributeCache>(*this);
+ auto attributeCache = Platform::MakeUnique<app::ClusterStateCache>(*this);
auto readClient = chip::Platform::MakeUnique<app::ReadClient>(
engine, proxy->GetExchangeManager(), attributeCache->GetBufferedCallback(), app::ReadClient::InteractionType::Read);
CHIP_ERROR err = readClient->SendRequest(readParams);
diff --git a/src/controller/CHIPDeviceController.h b/src/controller/CHIPDeviceController.h
index 5b5108e..e09490f 100644
--- a/src/controller/CHIPDeviceController.h
+++ b/src/controller/CHIPDeviceController.h
@@ -28,9 +28,9 @@
#pragma once
-#include <app/AttributeCache.h>
#include <app/CASEClientPool.h>
#include <app/CASESessionManager.h>
+#include <app/ClusterStateCache.h>
#include <app/OperationalDeviceProxy.h>
#include <app/OperationalDeviceProxyPool.h>
#include <controller/AbstractDnssdDiscoveryController.h>
@@ -280,7 +280,7 @@
public Protocols::UserDirectedCommissioning::InstanceNameResolver,
#endif
public SessionEstablishmentDelegate,
- public app::AttributeCache::Callback
+ public app::ClusterStateCache::Callback
{
public:
DeviceCommissioner();
@@ -549,7 +549,7 @@
void RegisterPairingDelegate(DevicePairingDelegate * pairingDelegate) { mPairingDelegate = pairingDelegate; }
DevicePairingDelegate * GetPairingDelegate() const { return mPairingDelegate; }
- // AttributeCache::Callback impl
+ // ClusterStateCache::Callback impl
void OnDone() override;
// Commissioner will establish new device connections after PASE.
@@ -764,7 +764,7 @@
nullptr; // Commissioning delegate that issued the PerformCommissioningStep command
CompletionStatus commissioningCompletionStatus;
- Platform::UniquePtr<app::AttributeCache> mAttributeCache;
+ Platform::UniquePtr<app::ClusterStateCache> mAttributeCache;
Platform::UniquePtr<app::ReadClient> mReadClient;
Credentials::AttestationVerificationResult mAttestationResult;
};
diff --git a/src/controller/tests/BUILD.gn b/src/controller/tests/BUILD.gn
index 2d83326..2c5373e 100644
--- a/src/controller/tests/BUILD.gn
+++ b/src/controller/tests/BUILD.gn
@@ -28,6 +28,7 @@
test_sources += [ "TestServerCommandDispatch.cpp" ]
test_sources += [ "TestReadChunking.cpp" ]
test_sources += [ "TestEventChunking.cpp" ]
+ test_sources += [ "TestEventCaching.cpp" ]
test_sources += [ "TestWriteChunking.cpp" ]
}
diff --git a/src/controller/tests/TestEventCaching.cpp b/src/controller/tests/TestEventCaching.cpp
new file mode 100644
index 0000000..ee6dc6c
--- /dev/null
+++ b/src/controller/tests/TestEventCaching.cpp
@@ -0,0 +1,410 @@
+/*
+ *
+ * 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 "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/AppBuildConfig.h>
+#include <app/AttributeAccessInterface.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/support/ErrorStr.h>
+#include <lib/support/TimeUtils.h>
+#include <lib/support/UnitTestRegistration.h>
+#include <lib/support/UnitTestUtils.h>
+#include <lib/support/logging/CHIPLogging.h>
+#include <messaging/tests/MessagingContext.h>
+#include <nlunit-test.h>
+
+using namespace chip;
+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];
+
+class TestContext : public chip::Test::AppContext
+{
+public:
+ static int InitializeAsync(void * context)
+ {
+ if (AppContext::InitializeAsync(context) != SUCCESS)
+ return FAILURE;
+
+ auto * ctx = static_cast<TestContext *>(context);
+
+ 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, nullptr, 0, nullptr);
+
+ return SUCCESS;
+ }
+
+ static int Finalize(void * context)
+ {
+ chip::app::EventManagement::DestroyEventManagement();
+
+ if (AppContext::Finalize(context) != SUCCESS)
+ return FAILURE;
+
+ return SUCCESS;
+ }
+};
+
+nlTestSuite * gSuite = nullptr;
+
+//
+// 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 TestReadEvents
+{
+public:
+ TestReadEvents() {}
+ static void TestBasicCaching(nlTestSuite * apSuite, void * apContext);
+
+private:
+};
+
+//clang-format off
+DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(testClusterAttrs)
+DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
+
+DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(testEndpointClusters)
+DECLARE_DYNAMIC_CLUSTER(TestCluster::Id, testClusterAttrs, 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::ClusterStateCache mClusterCacheAdapter;
+};
+
+namespace {
+
+void GenerateEvents(nlTestSuite * apSuite, chip::EventNumber & firstEventNumber, chip::EventNumber & lastEventNumber)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ static uint8_t generationCount = 0;
+
+ TestCluster::Events::TestEvent::Type content;
+
+ for (int i = 0; i < 5; i++)
+ {
+ content.arg1 = static_cast<uint8_t>(generationCount++);
+ NL_TEST_ASSERT(apSuite, (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.
+ *
+ */
+void TestReadEvents::TestBasicCaching(nlTestSuite * apSuite, void * apContext)
+{
+ TestContext & ctx = *static_cast<TestContext *>(apContext);
+ auto sessionHandle = ctx.GetSessionBobToAlice();
+ app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance();
+
+ // Initialize the ember side server logic
+ InitDataModelHandler(&ctx.GetExchangeManager());
+
+ // Register our fake dynamic endpoint.
+ DataVersion dataVersionStorage[ArraySize(testEndpointClusters)];
+ emberAfSetDynamicEndpoint(0, kTestEndpointId, &testEndpoint, Span<DataVersion>(dataVersionStorage));
+
+ chip::EventNumber firstEventNumber;
+ chip::EventNumber lastEventNumber;
+
+ GenerateEvents(apSuite, firstEventNumber, lastEventNumber);
+
+ app::EventPathParams eventPath;
+ eventPath.mEndpointId = kTestEndpointId;
+ eventPath.mClusterId = app::Clusters::TestCluster::Id;
+ app::ReadPrepareParams readParams(sessionHandle);
+
+ readParams.mpEventPathParamsList = &eventPath;
+ readParams.mEventPathParamsListSize = 1;
+ readParams.mEventNumber = firstEventNumber;
+
+ TestReadCallback readCallback;
+
+ {
+ app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(),
+ app::ReadClient::InteractionType::Read);
+
+ NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR);
+
+ ctx.DrainAndServiceIO();
+
+ uint8_t generationCount = 0;
+ readCallback.mClusterCacheAdapter.ForEachEventData(
+ [&apSuite, &readCallback, &generationCount](const app::EventHeader & header) {
+ NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == TestCluster::Id);
+ NL_TEST_ASSERT(apSuite, header.mPath.mEventId == TestCluster::Events::TestEvent::Id);
+ NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId);
+
+ TestCluster::Events::TestEvent::DecodableType eventData;
+ NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR);
+
+ NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount);
+ generationCount++;
+ return CHIP_NO_ERROR;
+ });
+
+ NL_TEST_ASSERT(apSuite, generationCount == 5);
+
+ //
+ // Re-run the iterator but pass in a path filter: EP*/TestCluster/EID*
+ //
+ generationCount = 0;
+ readCallback.mClusterCacheAdapter.ForEachEventData(
+ [&apSuite, &readCallback, &generationCount](const app::EventHeader & header) {
+ NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == TestCluster::Id);
+ NL_TEST_ASSERT(apSuite, header.mPath.mEventId == TestCluster::Events::TestEvent::Id);
+ NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId);
+
+ TestCluster::Events::TestEvent::DecodableType eventData;
+ NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR);
+
+ NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount);
+ generationCount++;
+ return CHIP_NO_ERROR;
+ },
+ app::EventPathParams(kInvalidEndpointId, TestCluster::Id, kInvalidEventId));
+
+ NL_TEST_ASSERT(apSuite, generationCount == 5);
+
+ //
+ // Re-run the iterator but pass in a path filter: EP*/TestCluster/TestEvent
+ //
+ generationCount = 0;
+ readCallback.mClusterCacheAdapter.ForEachEventData(
+ [&apSuite, &readCallback, &generationCount](const app::EventHeader & header) {
+ NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == TestCluster::Id);
+ NL_TEST_ASSERT(apSuite, header.mPath.mEventId == TestCluster::Events::TestEvent::Id);
+ NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId);
+
+ TestCluster::Events::TestEvent::DecodableType eventData;
+ NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR);
+
+ NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount);
+ generationCount++;
+ return CHIP_NO_ERROR;
+ },
+ app::EventPathParams(kInvalidEndpointId, TestCluster::Id, TestCluster::Events::TestEvent::Id));
+
+ NL_TEST_ASSERT(apSuite, generationCount == 5);
+
+ //
+ // Re-run the iterator but pass in a min event number filter (EventNumber = 1). We should only receive 4 events.
+ //
+ generationCount = 1;
+ readCallback.mClusterCacheAdapter.ForEachEventData(
+ [&apSuite, &readCallback, &generationCount](const app::EventHeader & header) {
+ NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == TestCluster::Id);
+ NL_TEST_ASSERT(apSuite, header.mPath.mEventId == TestCluster::Events::TestEvent::Id);
+ NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId);
+
+ TestCluster::Events::TestEvent::DecodableType eventData;
+ NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR);
+
+ NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount);
+ generationCount++;
+ return CHIP_NO_ERROR;
+ },
+ app::EventPathParams(), 1);
+
+ NL_TEST_ASSERT(apSuite, generationCount == 5);
+
+ //
+ // Re-run the iterator but pass in a min event number filter (EventNumber = 1) AND a path filter. We should only receive 4
+ // events.
+ //
+ generationCount = 1;
+ readCallback.mClusterCacheAdapter.ForEachEventData(
+ [&apSuite, &readCallback, &generationCount](const app::EventHeader & header) {
+ NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == TestCluster::Id);
+ NL_TEST_ASSERT(apSuite, header.mPath.mEventId == TestCluster::Events::TestEvent::Id);
+ NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId);
+
+ TestCluster::Events::TestEvent::DecodableType eventData;
+ NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR);
+
+ NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount);
+ generationCount++;
+ return CHIP_NO_ERROR;
+ },
+ app::EventPathParams(kInvalidEndpointId, TestCluster::Id, kInvalidEventId), 1);
+
+ NL_TEST_ASSERT(apSuite, generationCount == 5);
+ }
+
+ //
+ // Generate more events.
+ //
+ GenerateEvents(apSuite, firstEventNumber, lastEventNumber);
+
+ {
+ app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(),
+ app::ReadClient::InteractionType::Read);
+
+ NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR);
+
+ ctx.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(
+ [&apSuite, &readCallback, &generationCount](const app::EventHeader & header) {
+ NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == TestCluster::Id);
+ NL_TEST_ASSERT(apSuite, header.mPath.mEventId == TestCluster::Events::TestEvent::Id);
+ NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId);
+
+ TestCluster::Events::TestEvent::DecodableType eventData;
+ NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR);
+
+ NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount);
+ generationCount++;
+
+ return CHIP_NO_ERROR;
+ });
+
+ NL_TEST_ASSERT(apSuite, generationCount == 10);
+
+ readCallback.mClusterCacheAdapter.ClearEventCache();
+ generationCount = 0;
+ readCallback.mClusterCacheAdapter.ForEachEventData([&generationCount](const app::EventHeader & header) {
+ generationCount++;
+ return CHIP_NO_ERROR;
+ });
+
+ NL_TEST_ASSERT(apSuite, generationCount == 0);
+ }
+
+ //
+ // 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, &ctx.GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(),
+ app::ReadClient::InteractionType::Read);
+
+ readCallback.mClusterCacheAdapter.ClearEventCache();
+ readCallback.mClusterCacheAdapter.SetHighestReceivedEventNumber(3);
+
+ NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR);
+
+ ctx.DrainAndServiceIO();
+
+ uint8_t generationCount = 4;
+ readCallback.mClusterCacheAdapter.ForEachEventData(
+ [&apSuite, &readCallback, &generationCount](const app::EventHeader & header) {
+ NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == TestCluster::Id);
+ NL_TEST_ASSERT(apSuite, header.mPath.mEventId == TestCluster::Events::TestEvent::Id);
+ NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId);
+
+ TestCluster::Events::TestEvent::DecodableType eventData;
+ NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR);
+
+ NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount);
+ generationCount++;
+
+ return CHIP_NO_ERROR;
+ });
+
+ NL_TEST_ASSERT(apSuite, generationCount == 10);
+ }
+
+ NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+
+ emberAfClearDynamicEndpoint(0);
+}
+
+// clang-format off
+const nlTest sTests[] =
+{
+ NL_TEST_DEF("TestBasicCaching", TestReadEvents::TestBasicCaching),
+ NL_TEST_SENTINEL()
+};
+
+// clang-format on
+
+// clang-format off
+nlTestSuite sSuite =
+{
+ "TestEventCaching",
+ &sTests[0],
+ TestContext::InitializeAsync,
+ TestContext::Finalize
+};
+// clang-format on
+
+} // namespace
+
+int TestEventCaching()
+{
+ TestContext gContext;
+ gSuite = &sSuite;
+ nlTestRunner(&sSuite, &gContext);
+ return (nlTestRunnerStats(&sSuite));
+}
+
+CHIP_REGISTER_TEST_SUITE(TestEventCaching)
diff --git a/src/controller/tests/data_model/TestRead.cpp b/src/controller/tests/data_model/TestRead.cpp
index 3bb43c7..295ad93 100644
--- a/src/controller/tests/data_model/TestRead.cpp
+++ b/src/controller/tests/data_model/TestRead.cpp
@@ -18,7 +18,7 @@
#include "transport/SecureSession.h"
#include <app-common/zap-generated/cluster-objects.h>
-#include <app/AttributeCache.h>
+#include <app/ClusterStateCache.h>
#include <app/InteractionModelEngine.h>
#include <app/tests/AppTestContext.h>
#include <app/util/mock/Constants.h>
@@ -221,7 +221,7 @@
TestReadInteraction gTestReadInteraction;
-class MockInteractionModelApp : public chip::app::AttributeCache::Callback
+class MockInteractionModelApp : public chip::app::ClusterStateCache::Callback
{
public:
void OnEventData(const chip::app::EventHeader & aEventHeader, chip::TLV::TLVReader * apData,
@@ -361,7 +361,7 @@
responseDirective = kSendDataResponse;
MockInteractionModelApp delegate;
- chip::app::AttributeCache cache(delegate);
+ chip::app::ClusterStateCache cache(delegate);
auto * engine = chip::app::InteractionModelEngine::GetInstance();
err = engine->Init(&ctx.GetExchangeManager());
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
@@ -388,7 +388,7 @@
// Expect no versions would be cached.
{
testId++;
- ChipLogProgress(DataManagement, "\t -- Running Read with AttributeCache Test ID %d", testId);
+ ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId);
app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(),
cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
chip::app::AttributePathParams attributePathParams1[3];
@@ -424,7 +424,7 @@
// previous test. Expect cache E2C2 version
{
testId++;
- ChipLogProgress(DataManagement, "\t -- Running Read with AttributeCache Test ID %d", testId);
+ ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId);
app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(),
cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
chip::app::AttributePathParams attributePathParams2[2];
@@ -457,7 +457,7 @@
// path intersects with previous cached data version Expect no E2C3 attributes in report, only E3C2A1 attribute in report
{
testId++;
- ChipLogProgress(DataManagement, "\t -- Running Read with AttributeCache Test ID %d", testId);
+ ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId);
app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(),
cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
chip::app::AttributePathParams attributePathParams1[3];
@@ -494,7 +494,7 @@
// intersects with previous cached data version Expect no C1 attributes in report, only E3C2A2 attribute in report
{
testId++;
- ChipLogProgress(DataManagement, "\t -- Running Read with AttributeCache Test ID %d", testId);
+ ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId);
app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(),
cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
chip::app::AttributePathParams attributePathParams2[2];
@@ -529,7 +529,7 @@
// report, and invalidate the cached pending and committed data version since no wildcard attributes exists in mRequestPathSet.
{
testId++;
- ChipLogProgress(DataManagement, "\t -- Running Read with AttributeCache Test ID %d", testId);
+ ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId);
app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(),
cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
chip::app::AttributePathParams attributePathParams1[3];
@@ -566,7 +566,7 @@
// cache any committed data version. Expect E2C3A1, E2C3A2 and E3C2A2 attribute in report
{
testId++;
- ChipLogProgress(DataManagement, "\t -- Running Read with AttributeCache Test ID %d", testId);
+ ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId);
app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(),
cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
chip::app::AttributePathParams attributePathParams1[3];
@@ -603,7 +603,7 @@
// Expect E2C3A* attributes in report, and E3C2A2 attribute in report and cache latest data version
{
testId++;
- ChipLogProgress(DataManagement, "\t -- Running Read with AttributeCache Test ID %d", testId);
+ ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId);
app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(),
cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
chip::app::AttributePathParams attributePathParams2[2];
@@ -636,7 +636,7 @@
// E2C3A* attributes in report, and E3C2A2 attribute in report
{
testId++;
- ChipLogProgress(DataManagement, "\t -- Running Read with AttributeCache Test ID %d", testId);
+ ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId);
app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(),
cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
chip::app::AttributePathParams attributePathParams2[2];
@@ -676,7 +676,7 @@
// changed in server. Expect E1C2A* and C2C3A* and E2C2A* attributes in report, and cache their versions
{
testId++;
- ChipLogProgress(DataManagement, "\t -- Running Read with AttributeCache Test ID %d", testId);
+ ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId);
app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(),
cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
@@ -720,7 +720,7 @@
// filter with only C2. Expect E1C2A*, E2C2A* attributes(7 attributes) in report,
{
testId++;
- ChipLogProgress(DataManagement, "\t -- Running Read with AttributeCache Test ID %d", testId);
+ ChipLogProgress(DataManagement, "\t -- Running Read with ClusterStateCache Test ID %d", testId);
app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(),
cache.GetBufferedCallback(), chip::app::ReadClient::InteractionType::Read);
diff --git a/src/darwin/Framework/CHIP/CHIPAttributeCacheContainer.mm b/src/darwin/Framework/CHIP/CHIPAttributeCacheContainer.mm
index 6b06037..9a7df6d 100644
--- a/src/darwin/Framework/CHIP/CHIPAttributeCacheContainer.mm
+++ b/src/darwin/Framework/CHIP/CHIPAttributeCacheContainer.mm
@@ -53,7 +53,7 @@
}
static CHIP_ERROR AppendAttibuteValueToArray(
- const chip::app::ConcreteAttributePath & path, chip::app::AttributeCache * cache, NSMutableArray * array)
+ const chip::app::ConcreteAttributePath & path, chip::app::ClusterStateCache * cache, NSMutableArray * array)
{
chip::TLV::TLVReader reader;
CHIP_ERROR err = cache->Get(path, reader);
diff --git a/src/darwin/Framework/CHIP/CHIPAttributeCacheContainer_Internal.h b/src/darwin/Framework/CHIP/CHIPAttributeCacheContainer_Internal.h
index 9e58fc1..cb7bf68 100644
--- a/src/darwin/Framework/CHIP/CHIPAttributeCacheContainer_Internal.h
+++ b/src/darwin/Framework/CHIP/CHIPAttributeCacheContainer_Internal.h
@@ -20,13 +20,13 @@
#import "CHIPAttributeCacheContainer.h"
#import "CHIPDeviceControllerOverXPC.h"
-#include <app/AttributeCache.h>
+#include <app/ClusterStateCache.h>
NS_ASSUME_NONNULL_BEGIN
@interface CHIPAttributeCacheContainer ()
-@property (atomic, readwrite, nullable) chip::app::AttributeCache * cppAttributeCache;
+@property (atomic, readwrite, nullable) chip::app::ClusterStateCache * cppAttributeCache;
@property (nonatomic, readwrite) uint64_t deviceId;
@property (nonatomic, readwrite, weak, nullable) CHIPDeviceControllerXPCConnection * xpcConnection;
@property (nonatomic, readwrite, strong, nullable) id<NSCopying> xpcControllerId;
diff --git a/src/darwin/Framework/CHIP/CHIPDevice.mm b/src/darwin/Framework/CHIP/CHIPDevice.mm
index a46ec0d..b8800ac 100644
--- a/src/darwin/Framework/CHIP/CHIPDevice.mm
+++ b/src/darwin/Framework/CHIP/CHIPDevice.mm
@@ -27,9 +27,9 @@
#include "lib/core/CHIPError.h"
#include "lib/core/DataModelTypes.h"
-#include <app/AttributeCache.h>
#include <app/AttributePathParams.h>
#include <app/BufferedReadCallback.h>
+#include <app/ClusterStateCache.h>
#include <app/InteractionModelEngine.h>
#include <app/ReadClient.h>
#include <app/util/error-mapping.h>
@@ -238,7 +238,7 @@
namespace {
-class SubscriptionCallback final : public AttributeCache::Callback {
+class SubscriptionCallback final : public ClusterStateCache::Callback {
public:
SubscriptionCallback(dispatch_queue_t queue, ReportCallback reportCallback,
SubscriptionEstablishedHandler _Nullable subscriptionEstablishedHandler)
@@ -263,7 +263,7 @@
// We need to exist to get a ReadClient, so can't take this as a constructor argument.
void AdoptReadClient(std::unique_ptr<ReadClient> aReadClient) { mReadClient = std::move(aReadClient); }
- void AdoptAttributeCache(std::unique_ptr<AttributeCache> aAttributeCache) { mAttributeCache = std::move(aAttributeCache); }
+ void AdoptAttributeCache(std::unique_ptr<ClusterStateCache> aAttributeCache) { mAttributeCache = std::move(aAttributeCache); }
private:
void OnReportBegin() override;
@@ -306,7 +306,7 @@
// error callback, but not both, by tracking whether we have a queued-up
// deletion.
std::unique_ptr<ReadClient> mReadClient;
- std::unique_ptr<AttributeCache> mAttributeCache;
+ std::unique_ptr<ClusterStateCache> mAttributeCache;
bool mHaveQueuedDeletion = false;
void (^mOnDoneHandler)(void) = nil;
};
@@ -341,7 +341,7 @@
std::unique_ptr<SubscriptionCallback> callback;
std::unique_ptr<ReadClient> readClient;
- std::unique_ptr<AttributeCache> attributeCache;
+ std::unique_ptr<ClusterStateCache> attributeCache;
if (attributeCacheContainer) {
__weak CHIPAttributeCacheContainer * weakPtr = attributeCacheContainer;
callback = std::make_unique<SubscriptionCallback>(queue, reportHandler, subscriptionEstablishedHandler, ^{
@@ -350,7 +350,7 @@
container.cppAttributeCache = nullptr;
}
});
- attributeCache = std::make_unique<AttributeCache>(*callback.get());
+ attributeCache = std::make_unique<ClusterStateCache>(*callback.get());
readClient = std::make_unique<ReadClient>(InteractionModelEngine::GetInstance(), device->GetExchangeManager(),
attributeCache->GetBufferedCallback(), ReadClient::InteractionType::Subscribe);
} else {
@@ -378,7 +378,7 @@
if (attributeCacheContainer) {
attributeCacheContainer.cppAttributeCache = attributeCache.get();
- // AttributeCache will be deleted when OnDone is called or an error is encountered as well.
+ // ClusterStateCache will be deleted when OnDone is called or an error is encountered as well.
callback->AdoptAttributeCache(std::move(attributeCache));
}
// Callback and ReadClient will be deleted when OnDone is called or an error is