/* * * 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 defines Reporting Engine for a CHIP Interaction Model * */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include namespace chip { namespace app { class InteractionModelEngine; class TestReadInteraction; namespace reporting { /* * @class Engine * * @brief The reporting engine is responsible for generating reports to reader. It is able to find the intersection * between the path interest set of each reader with what has changed in the publisher data store and generate tailored * reports for each reader. * * At its core, it tries to gather and pack as much relevant attributes changes and/or events as possible into a report * message before sending that to the reader. It continues to do so until it has no more work to do. */ class Engine : public DataModel::ProviderChangeListener { public: /** * Constructor Engine with a valid InteractionModelEngine pointer. */ Engine(InteractionModelEngine * apImEngine); /** * Initializes the reporting engine. Should only be called once. * * @retval #CHIP_NO_ERROR On success. * @retval other Was unable to retrieve data and write it into the writer. */ CHIP_ERROR Init(); void Shutdown(); #if CONFIG_BUILD_FOR_HOST_UNIT_TEST void SetWriterReserved(uint32_t aReservedSize) { mReservedSize = aReservedSize; } void SetMaxAttributesPerChunk(uint32_t aMaxAttributesPerChunk) { mMaxAttributesPerChunk = aMaxAttributesPerChunk; } #endif /** * Should be invoked when the device receives a Status report, or when the Report data request times out. * This allows the engine to do some clean-up. * */ void OnReportConfirm(); /** * Main work-horse function that executes the run-loop asynchronously on the CHIP thread */ CHIP_ERROR ScheduleRun(); /** * Application marks mutated change path and would be sent out in later report. */ CHIP_ERROR SetDirty(const AttributePathParams & aAttributePathParams); /** * @brief * Schedule the event delivery * */ CHIP_ERROR ScheduleEventDelivery(ConcreteEventPath & aPath, uint32_t aBytesWritten); /* * Resets the tracker that tracks the currently serviced read handler. * apReadHandler can be non-null to indicate that the reset is due to a * specific ReadHandler being deallocated. */ void ResetReadHandlerTracker(ReadHandler * apReadHandlerBeingDeleted) { if (apReadHandlerBeingDeleted == mRunningReadHandler) { // Just decrement, so our increment after we finish running it will // do the right thing. --mCurReadHandlerIdx; } else { // No idea what to do here to make the indexing sane. Just start at // the beginning. We need to do better here; see // https://github.com/project-chip/connectedhomeip/issues/13809 mCurReadHandlerIdx = 0; } } uint32_t GetNumReportsInFlight() const { return mNumReportsInFlight; } uint64_t GetDirtySetGeneration() const { return mDirtyGeneration; } /** * Schedule event delivery to happen immediately and run reporting to get * those reports into messages and on the wire. This can be done either for * a specific fabric, identified by the provided FabricIndex, or across all * fabrics if no FabricIndex is provided. */ void ScheduleUrgentEventDeliverySync(Optional fabricIndex = NullOptional); #if CONFIG_BUILD_FOR_HOST_UNIT_TEST size_t GetGlobalDirtySetSize() { return mGlobalDirtySet.Allocated(); } #endif /* ProviderChangeListener implementation */ void MarkDirty(const AttributePathParams & path) override; private: /** * Main work-horse function that executes the run-loop. */ void Run(); friend class TestReportingEngine; friend class ::chip::app::TestReadInteraction; bool IsRunScheduled() const { return mRunScheduled; } struct AttributePathParamsWithGeneration : public AttributePathParams { AttributePathParamsWithGeneration() {} AttributePathParamsWithGeneration(const AttributePathParams aPath) : AttributePathParams(aPath) {} uint64_t mGeneration = 0; }; /** * Build Single Report Data including attribute changes and event data stream, and send out * */ CHIP_ERROR BuildAndSendSingleReportData(ReadHandler * apReadHandler); CHIP_ERROR BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Builder & reportDataBuilder, ReadHandler * apReadHandler, bool * apHasMoreChunks, bool * apHasEncodedData); CHIP_ERROR BuildSingleReportDataEventReports(ReportDataMessage::Builder & reportDataBuilder, ReadHandler * apReadHandler, bool aBufferIsUsed, bool * apHasMoreChunks, bool * apHasEncodedData); CHIP_ERROR CheckAccessDeniedEventPaths(TLV::TLVWriter & aWriter, bool & aHasEncodedData, ReadHandler * apReadHandler); // If version match, it means don't send, if version mismatch, it means send. // If client sends the same path with multiple data versions, client will get the data back per the spec, because at least one // of those will fail to match. This function should return false if either nothing in the list matches the given // endpoint+cluster in the path or there is an entry in the list that matches the endpoint+cluster in the path but does not // match the current data version of that cluster. bool IsClusterDataVersionMatch(const SingleLinkedListNode * aDataVersionFilterList, const ConcreteReadAttributePath & aPath); /** * Send Report via ReadHandler * */ CHIP_ERROR SendReport(ReadHandler * apReadHandler, System::PacketBufferHandle && aPayload, bool aHasMoreChunks); /** * Generate and send the report data request when there exists subscription or read request * */ static void Run(System::Layer * aSystemLayer, void * apAppState); CHIP_ERROR ScheduleBufferPressureEventDelivery(uint32_t aBytesWritten); void GetMinEventLogPosition(uint32_t & aMinLogPosition); /** * If the provided path is a superset of our of our existing paths, update that existing path to match the * provided path. * * Return whether one of our paths is now a superset of the provided path. */ bool MergeOverlappedAttributePath(const AttributePathParams & aAttributePath); /** * If we are running out of ObjectPool for the global dirty set, we will try to merge the existing items by clusters. * * Returns whether we have released any paths. */ bool MergeDirtyPathsUnderSameCluster(); /** * If we are running out of ObjectPool for the global dirty set and we cannot find a slot after merging the existing items by * clusters, we will try to merge the existing items by endpoints. * * Returns whether we have released any paths. */ bool MergeDirtyPathsUnderSameEndpoint(); /** * During the iterating of the paths, releasing the object in the inner loop will cause undefined behavior of the ObjectPool, so * we replace the items to be cleared by a tomb first, then clear all the tombs after the iteration. * * Returns whether we have released any paths. */ bool ClearTombPaths(); CHIP_ERROR InsertPathIntoDirtySet(const AttributePathParams & aAttributePath); inline void BumpDirtySetGeneration() { mDirtyGeneration++; } /** * Boolean to indicate if ScheduleRun is pending. This flag is used to prevent calling ScheduleRun multiple times * within the same execution context to avoid applying too much pressure on platforms that use small, fixed size event queues. * */ bool mRunScheduled = false; /** * The number of report date request in flight * */ uint32_t mNumReportsInFlight = 0; /** * Current read handler index * */ uint32_t mCurReadHandlerIdx = 0; /** * The read handler we're calling BuildAndSendSingleReportData on right now. */ ReadHandler * mRunningReadHandler = nullptr; /** * mGlobalDirtySet is used to track the set of attribute/event paths marked dirty for reporting purposes. * */ #if CONFIG_BUILD_FOR_HOST_UNIT_TEST // For unit tests, always use inline allocation for code coverage. ObjectPool mGlobalDirtySet; #else ObjectPool mGlobalDirtySet; #endif /** * A generation counter for the dirty attrbute set. * ReadHandlers can save the generation value when generating reports. * * Then we can tell whether they might have missed reporting an attribute by * comparing its generation counter to the saved one. * * mDirtySetGeneration will increase by one when SetDirty is called. * * Count it from 1, so 0 can be used in ReadHandler to indicate "the read handler has never * completed a report". */ uint64_t mDirtyGeneration = 1; #if CONFIG_BUILD_FOR_HOST_UNIT_TEST uint32_t mReservedSize = 0; uint32_t mMaxAttributesPerChunk = UINT32_MAX; #endif InteractionModelEngine * mpImEngine = nullptr; }; }; // namespace reporting }; // namespace app }; // namespace chip