/* * * 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. */ #pragma once #include "lib/core/CHIPError.h" #include "system/SystemPacketBuffer.h" #include "system/TLVPacketBufferBackingStore.h" #include #include #include #include #include #include #include #include #include #include #include #include #if CHIP_CONFIG_ENABLE_READ_CLIENT namespace chip { namespace app { /* * 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. * * The cache can be used with either read/subscribe, with the consumer connecting it up appropriately * to the right ReadClient instance. * * The cache provides an up-to-date and consistent view of the state of a target node, with the scope of the * 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 (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. * * The cache serves as a callback adapter as well in that it 'forwards' the ReadClient::Callback calls transparently * through to a registered callback. In addition, it provides its own enhancements to the base ReadClient::Callback * to make it easier to know what has changed in the cache. * * **NOTE** * 1. This already includes the BufferedReadCallback, so there is no need to add that to the ReadClient callback chain. * 2. The same cache cannot be used by multiple subscribe/read interactions at the same time. * */ template class ClusterStateCacheT : protected ReadClient::Callback { public: class Callback : public ReadClient::Callback { public: Callback() = default; // Callbacks are not expected to be copyable or movable. Callback(const Callback &) = delete; Callback(Callback &&) = delete; Callback & operator=(const Callback &) = delete; Callback & operator=(Callback &&) = delete; /* * Called anytime an attribute value has changed in the cache */ virtual void OnAttributeChanged(ClusterStateCacheT * cache, const ConcreteAttributePath & path){}; /* * Called anytime any attribute in a cluster has changed in the cache */ virtual void OnClusterChanged(ClusterStateCacheT * cache, EndpointId endpointId, ClusterId clusterId){}; /* * Called anytime an endpoint was added to the cache */ virtual void OnEndpointAdded(ClusterStateCacheT * cache, EndpointId endpointId){}; }; /** * * @param [in] callback the derived callback which inherit from ReadClient::Callback * @param [in] highestReceivedEventNumber optional highest received event number, if cache receive the events with the number * less than or equal to this value, skip those events */ ClusterStateCacheT(Callback & callback, Optional highestReceivedEventNumber = Optional::Missing()) : mCallback(callback), mBufferedReader(*this) { mHighestReceivedEventNumber = highestReceivedEventNumber; } template = true> ClusterStateCacheT(Callback & callback, Optional highestReceivedEventNumber = Optional::Missing(), bool cacheData = true) : mCallback(callback), mBufferedReader(*this), mCacheData(cacheData) { mHighestReceivedEventNumber = highestReceivedEventNumber; } ClusterStateCacheT(const ClusterStateCacheT &) = delete; ClusterStateCacheT(ClusterStateCacheT &&) = delete; ClusterStateCacheT & operator=(const ClusterStateCacheT &) = delete; ClusterStateCacheT & operator=(ClusterStateCacheT &&) = delete; void SetHighestReceivedEventNumber(EventNumber highestReceivedEventNumber) { mHighestReceivedEventNumber.SetValue(highestReceivedEventNumber); } /* * 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 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 * valid until the cached value for that path is updated, so it must not be held * across any async call boundaries. * * The template parameter AttributeObjectTypeT is generally expected to be a * ClusterName::Attributes::AttributeName::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 Attribute IDs don't match that of the provided path, * a CHIP_ERROR_SCHEMA_MISMATCH shall be returned. * * - If neither data or status for the specified path don't exist in the cache, CHIP_ERROR_KEY_NOT_FOUND * shall be returned. * * - If a StatusIB is present in the cache instead of data, a CHIP_ERROR_IM_STATUS_CODE_RECEIVED error * shall be returned from this call instead. The actual StatusIB can be retrieved using the GetStatus() API below. * */ template CHIP_ERROR Get(const ConcreteAttributePath & path, typename AttributeObjectTypeT::DecodableType & value) const { TLV::TLVReader reader; if (path.mClusterId != AttributeObjectTypeT::GetClusterId() || path.mAttributeId != AttributeObjectTypeT::GetAttributeId()) { return CHIP_ERROR_SCHEMA_MISMATCH; } ReturnErrorOnFailure(Get(path, reader)); return DataModel::Decode(reader, value); } /** * Get the value of a particular attribute for the given endpoint. See the * documentation for Get() with a ConcreteAttributePath above. */ template CHIP_ERROR Get(EndpointId endpoint, typename AttributeObjectTypeT::DecodableType & value) const { ConcreteAttributePath path(endpoint, AttributeObjectTypeT::GetClusterId(), AttributeObjectTypeT::GetAttributeId()); return Get(path, value); } /* * Retrieve the StatusIB for a given attribute if one exists currently in the cache. * * Notable return values: * - If neither data or status for the specified path don't exist in the cache, CHIP_ERROR_KEY_NOT_FOUND * shall be returned. * * - If data exists in the cache instead of status, CHIP_ERROR_INVALID_ARGUMENT shall be returned. * */ CHIP_ERROR GetStatus(const ConcreteAttributePath & path, StatusIB & status) const; /* * Encapsulates a StatusIB and a ConcreteAttributePath pair. */ struct AttributeStatus { AttributeStatus(const ConcreteAttributePath & path, StatusIB & status) : mPath(path), mStatus(status) {} ConcreteAttributePath mPath; StatusIB mStatus; }; /* * Retrieve the value of an entire cluster instance from the cache (if present) given a path * and decode it using DataModel::Decode into the in-out argument 'value'. If any StatusIBs * are present in the cache instead of data, they will be provided in the statusList argument. * * 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 * valid until the cached value for that path is updated, so it must not be held * across any async call boundaries. * * The template parameter ClusterObjectT is generally expected to be a * ClusterName::Attributes::DecodableType, but any * object that can be decoded using the DataModel::Decode machinery will work. * * Notable return values: * - If neither data or status for the specified path exist in the cache, CHIP_ERROR_KEY_NOT_FOUND * shall be returned. * */ template CHIP_ERROR Get(EndpointId endpointId, ClusterId clusterId, ClusterObjectTypeT & value, std::list & statusList) const { statusList.clear(); return ForEachAttribute(endpointId, clusterId, [&value, this, &statusList](const ConcreteAttributePath & path) { TLV::TLVReader reader; CHIP_ERROR err; err = Get(path, reader); if (err == CHIP_ERROR_IM_STATUS_CODE_RECEIVED) { StatusIB status; ReturnErrorOnFailure(GetStatus(path, status)); statusList.push_back(AttributeStatus(path, status)); err = CHIP_NO_ERROR; } else if (err == CHIP_NO_ERROR) { ReturnErrorOnFailure(DataModel::Decode(reader, path, value)); } else { return err; } return CHIP_NO_ERROR; }); } /* * Retrieve the value of an attribute by updating a in-out TLVReader to be positioned * right at the attribute value. * * The underlying TLV buffer only remains valid until the cached value for that path is updated, so it must * not be held across any async call boundaries. * * Notable return values: * - If neither data nor status for the specified path exist in the cache, CHIP_ERROR_KEY_NOT_FOUND * shall be returned. * * - If a StatusIB is present in the cache instead of data, a CHIP_ERROR_IM_STATUS_CODE_RECEIVED error * shall be returned from this call instead. The actual StatusIB can be retrieved using the GetStatus() API above. * */ CHIP_ERROR Get(const ConcreteAttributePath & path, TLV::TLVReader & reader) const; /* * Retrieve the data version for the given cluster. If there is no data for the specified path in the cache, * CHIP_ERROR_KEY_NOT_FOUND shall be returned. Otherwise aVersion will be set to the * current data version for the cluster (which may have no value if we don't have a known data version * for it, for example because none of our paths were wildcards that covered the whole cluster). */ CHIP_ERROR GetVersion(const ConcreteClusterPath & path, Optional & aVersion) const; /* * Get highest received event number. */ virtual CHIP_ERROR GetHighestReceivedEventNumber(Optional & aEventNumber) final { aEventNumber = mHighestReceivedEventNumber; return CHIP_NO_ERROR; } /* * 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 event 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 CHIP_ERROR Get(EventNumber eventNumber, EventObjectTypeT & value) const { 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) const; /* * 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) const; /* * 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. * * The iterator is expected to have this signature: * CHIP_ERROR IteratorFunc(const ConcreteAttributePath &path); * * Notable return values: * - If a cluster instance corresponding to endpointId and clusterId doesn't exist in the cache, * CHIP_ERROR_KEY_NOT_FOUND shall be returned. * * - If func returns an error, that will result in termination of any further iteration over attributes * and that error shall be returned back up to the original call to this function. * */ template CHIP_ERROR ForEachAttribute(EndpointId endpointId, ClusterId clusterId, IteratorFunc func) const { CHIP_ERROR err; auto clusterState = GetClusterState(endpointId, clusterId, err); ReturnErrorOnFailure(err); for (auto & attributeIter : clusterState->mAttributes) { const ConcreteAttributePath path(endpointId, clusterId, attributeIter.first); ReturnErrorOnFailure(func(path)); } return CHIP_NO_ERROR; } /* * Execute an iterator function that is called for every attribute * for a given cluster across all endpoints in the cache. The function is passed a * concrete attribute path to every attribute that matches in the cache. * * The iterator is expected to have this signature: * CHIP_ERROR IteratorFunc(const ConcreteAttributePath &path); * * Notable return values: * - If func returns an error, that will result in termination of any further iteration over attributes * and that error shall be returned back up to the original call to this function. * */ template CHIP_ERROR ForEachAttribute(ClusterId clusterId, IteratorFunc func) const { for (auto & endpointIter : mCache) { for (auto & clusterIter : endpointIter.second) { if (clusterIter.first == clusterId) { for (auto & attributeIter : clusterIter.second.mAttributes) { const ConcreteAttributePath path(endpointIter.first, clusterId, attributeIter.first); ReturnErrorOnFailure(func(path)); } } } } return CHIP_NO_ERROR; } /* * Execute an iterator function that is called for every cluster * in a given endpoint and passed a ClusterId for every cluster that * matches. * * The iterator is expected to have this signature: * CHIP_ERROR IteratorFunc(ClusterId clusterId); * * Notable return values: * - If func returns an error, that will result in termination of any further iteration over attributes * and that error shall be returned back up to the original call to this function. * */ template CHIP_ERROR ForEachCluster(EndpointId endpointId, IteratorFunc func) const { auto endpointIter = mCache.find(endpointId); if (endpointIter->first == endpointId) { for (auto & clusterIter : endpointIter->second) { ReturnErrorOnFailure(func(clusterIter.first)); } } 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 CHIP_ERROR ForEachEventData(IteratorFunc func, EventPathParams pathFilter = EventPathParams(), EventNumber minEventNumberFilter = 0) const { 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 CHIP_ERROR ForEachEventStatus(IteratorFunc func) const { for (const auto & item : mEventStatusCache) { ReturnErrorOnFailure(func(item.first, item.second)); } } /* * Clear out all the attribute data and DataVersions stored for a given endpoint. */ void ClearAttributes(EndpointId endpoint); /* * Clear out all the attribute data and the DataVersion stored for a given cluster. */ void ClearAttributes(const ConcreteClusterPath & cluster); /* * Clear out the data (or size, if not storing data) stored for an * attribute. */ void ClearAttribute(const ConcreteAttributePath & attribute); /* * 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(); } /* * Get the last concrete report data path, if path is not concrete cluster path, return CHIP_ERROR_NOT_FOUND * */ CHIP_ERROR GetLastReportDataPath(ConcreteClusterPath & aPath); private: // An attribute state can be one of three things: // * If we got a path-specific error for the attribute, the corresponding // status. // * If we got data for the attribute and we are storing data ourselves, the // data. // * If we got data for the attribute and we are not storing data // oureselves, the size of the data, so we can still prioritize sending // DataVersions correctly. // // The data for a single attribute is not going to be gigabytes in size, so // using uint32_t for the size is fine; on 64-bit systems this can save // quite a bit of space. using AttributeData = Platform::ScopedMemoryBufferWithSize; using AttributeState = std::conditional_t, uint32_t>; // mPendingDataVersion represents a tentative data version for a cluster that we have gotten some reports for. // // mCurrentDataVersion represents a known data version for a cluster. In order for this to have a // value the cluster must be included in a path in mRequestPathSet that has a wildcard attribute // and we must not be in the middle of receiving reports for that cluster. struct ClusterState { std::map mAttributes; Optional mPendingDataVersion; Optional mCommittedDataVersion; }; using EndpointState = std::map; using NodeState = std::map; struct Comparator { bool operator()(const AttributePathParams & x, const AttributePathParams & y) const { return x.mEndpointId < y.mEndpointId || x.mClusterId < y.mClusterId; } }; using EventData = std::pair; // // This is a custom comparator for use with the std::set 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. * * In all variants, the respective slice is returned if a valid path is provided. 'err' is updated to reflect * the status of the operation. * * Notable status values: * - If a cluster instance corresponding to endpointId and clusterId doesn't exist in the cache, * CHIP_ERROR_KEY_NOT_FOUND shall be returned. * */ const EndpointState * GetEndpointState(EndpointId endpointId, CHIP_ERROR & err) const; const ClusterState * GetClusterState(EndpointId endpointId, ClusterId clusterId, CHIP_ERROR & err) const; const AttributeState * GetAttributeState(EndpointId endpointId, ClusterId clusterId, AttributeId attributeId, CHIP_ERROR & err) const; const EventData * GetEventData(EventNumber number, CHIP_ERROR & err) const; /* * Updates the state of an attribute in the cache given a reader. If the reader is null, the state is updated * with the provided status. */ 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 { return mCallback.OnError(aError); } void OnEventData(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus) override; void OnDone(ReadClient * apReadClient) override { mRequestPathSet.clear(); return mCallback.OnDone(apReadClient); } void OnSubscriptionEstablished(SubscriptionId aSubscriptionId) override { mCallback.OnSubscriptionEstablished(aSubscriptionId); } CHIP_ERROR OnResubscriptionNeeded(ReadClient * apReadClient, CHIP_ERROR aTerminationCause) override { return mCallback.OnResubscriptionNeeded(apReadClient, aTerminationCause); } void OnDeallocatePaths(chip::app::ReadPrepareParams && aReadPrepareParams) override { mCallback.OnDeallocatePaths(std::move(aReadPrepareParams)); } virtual CHIP_ERROR OnUpdateDataVersionFilterList(DataVersionFilterIBs::Builder & aDataVersionFilterIBsBuilder, const Span & aAttributePaths, bool & aEncodedDataVersionList) override; void OnUnsolicitedMessageFromPublisher(ReadClient * apReadClient) override { return mCallback.OnUnsolicitedMessageFromPublisher(apReadClient); } void OnCASESessionEstablished(const SessionHandle & aSession, ReadPrepareParams & aSubscriptionParams) override { return mCallback.OnCASESessionEstablished(aSession, aSubscriptionParams); } // Commit the pending cluster data version, if there is one. void CommitPendingDataVersion(); // Get our list of data version filters, sorted from larges to smallest by the total size of the TLV // payload for the filter's cluster. Applying filters in this order should maximize space savings // on the wire if not all filters can be applied. void GetSortedFilters(std::vector> & aVector) const; CHIP_ERROR GetElementTLVSize(TLV::TLVReader * apData, uint32_t & aSize); Callback & mCallback; NodeState mCache; std::set mChangedAttributeSet; std::set mRequestPathSet; // wildcard attribute request path only std::vector mAddedEndpoints; std::set mEventDataCache; Optional mHighestReceivedEventNumber; std::map mEventStatusCache; BufferedReadCallback mBufferedReader; ConcreteClusterPath mLastReportDataPath = ConcreteClusterPath(kInvalidEndpointId, kInvalidClusterId); const bool mCacheData = CanEnableDataCaching; }; using ClusterStateCache = ClusterStateCacheT; using ClusterStateCacheNoData = ClusterStateCacheT; }; // namespace app }; // namespace chip #endif // CHIP_CONFIG_ENABLE_READ_CLIENT