LCOV - code coverage report
Current view: top level - app/reporting - Engine.cpp (source / functions) Hit Total Coverage
Test: lcov_final.info Lines: 398 433 91.9 %
Date: 2024-02-15 08:20:41 Functions: 38 40 95.0 %

          Line data    Source code
       1             : /*
       2             :  *
       3             :  *    Copyright (c) 2021 Project CHIP Authors
       4             :  *    All rights reserved.
       5             :  *
       6             :  *    Licensed under the Apache License, Version 2.0 (the "License");
       7             :  *    you may not use this file except in compliance with the License.
       8             :  *    You may obtain a copy of the License at
       9             :  *
      10             :  *        http://www.apache.org/licenses/LICENSE-2.0
      11             :  *
      12             :  *    Unless required by applicable law or agreed to in writing, software
      13             :  *    distributed under the License is distributed on an "AS IS" BASIS,
      14             :  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      15             :  *    See the License for the specific language governing permissions and
      16             :  *    limitations under the License.
      17             :  */
      18             : 
      19             : /**
      20             :  *    @file
      21             :  *      This file implements reporting engine for CHIP
      22             :  *      Data Model profile.
      23             :  *
      24             :  */
      25             : 
      26             : #include <app/icd/server/ICDServerConfig.h>
      27             : #if CHIP_CONFIG_ENABLE_ICD_SERVER
      28             : #include <app/icd/server/ICDNotifier.h> // nogncheck
      29             : #endif
      30             : #include <app/AppConfig.h>
      31             : #include <app/InteractionModelEngine.h>
      32             : #include <app/RequiredPrivilege.h>
      33             : #include <app/reporting/Engine.h>
      34             : #include <app/util/MatterCallbacks.h>
      35             : 
      36             : using namespace chip::Access;
      37             : 
      38             : namespace chip {
      39             : namespace app {
      40             : namespace reporting {
      41             : 
      42          26 : Engine::Engine(InteractionModelEngine * apImEngine) : mpImEngine(apImEngine) {}
      43             : 
      44         367 : CHIP_ERROR Engine::Init()
      45             : {
      46         367 :     mNumReportsInFlight = 0;
      47         367 :     mCurReadHandlerIdx  = 0;
      48         367 :     return CHIP_NO_ERROR;
      49             : }
      50             : 
      51         365 : void Engine::Shutdown()
      52             : {
      53             :     // Flush out the event buffer synchronously
      54         365 :     ScheduleUrgentEventDeliverySync();
      55             : 
      56         365 :     mNumReportsInFlight = 0;
      57         365 :     mCurReadHandlerIdx  = 0;
      58         365 :     mGlobalDirtySet.ReleaseAll();
      59         365 : }
      60             : 
      61        3494 : bool Engine::IsClusterDataVersionMatch(const ObjectList<DataVersionFilter> * aDataVersionFilterList,
      62             :                                        const ConcreteReadAttributePath & aPath)
      63             : {
      64        3494 :     bool existPathMatch       = false;
      65        3494 :     bool existVersionMismatch = false;
      66        3632 :     for (auto filter = aDataVersionFilterList; filter != nullptr; filter = filter->mpNext)
      67             :     {
      68         138 :         if (aPath.mEndpointId == filter->mValue.mEndpointId && aPath.mClusterId == filter->mValue.mClusterId)
      69             :         {
      70         109 :             existPathMatch = true;
      71         109 :             if (!IsClusterDataVersionEqual(ConcreteClusterPath(filter->mValue.mEndpointId, filter->mValue.mClusterId),
      72         109 :                                            filter->mValue.mDataVersion.Value()))
      73             :             {
      74          79 :                 existVersionMismatch = true;
      75             :             }
      76             :         }
      77             :     }
      78        3494 :     return existPathMatch && !existVersionMismatch;
      79             : }
      80             : 
      81             : CHIP_ERROR
      82        3697 : Engine::RetrieveClusterData(const SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered,
      83             :                             AttributeReportIBs::Builder & aAttributeReportIBs, const ConcreteReadAttributePath & aPath,
      84             :                             AttributeValueEncoder::AttributeEncodeState * aEncoderState)
      85             : {
      86        3697 :     ChipLogDetail(DataManagement, "<RE:Run> Cluster %" PRIx32 ", Attribute %" PRIx32 " is dirty", aPath.mClusterId,
      87             :                   aPath.mAttributeId);
      88        3697 :     MatterPreAttributeReadCallback(aPath);
      89        3697 :     ReturnErrorOnFailure(ReadSingleClusterData(aSubjectDescriptor, aIsFabricFiltered, aPath, aAttributeReportIBs, aEncoderState));
      90        3306 :     MatterPostAttributeReadCallback(aPath);
      91        3306 :     return CHIP_NO_ERROR;
      92             : }
      93             : 
      94        2741 : static bool IsOutOfWriterSpaceError(CHIP_ERROR err)
      95             : {
      96        2741 :     return err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL;
      97             : }
      98             : 
      99        1835 : CHIP_ERROR Engine::BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Builder & aReportDataBuilder,
     100             :                                                            ReadHandler * apReadHandler, bool * apHasMoreChunks,
     101             :                                                            bool * apHasEncodedData)
     102             : {
     103        1835 :     CHIP_ERROR err            = CHIP_NO_ERROR;
     104        1835 :     bool attributeDataWritten = false;
     105        1835 :     bool hasMoreChunks        = true;
     106        1835 :     TLV::TLVWriter backup;
     107        1835 :     const uint32_t kReservedSizeEndOfReportIBs = 1;
     108        1835 :     bool reservedEndOfReportIBs                = false;
     109             : 
     110        1835 :     aReportDataBuilder.Checkpoint(backup);
     111             : 
     112        1835 :     AttributeReportIBs::Builder & attributeReportIBs = aReportDataBuilder.CreateAttributeReportIBs();
     113        1835 :     size_t emptyReportDataLength                     = 0;
     114             : 
     115        1835 :     SuccessOrExit(err = aReportDataBuilder.GetError());
     116             : 
     117        1835 :     emptyReportDataLength = attributeReportIBs.GetWriter()->GetLengthWritten();
     118             :     //
     119             :     // Reserve enough space for closing out the Report IB list
     120             :     //
     121        1835 :     SuccessOrExit(err = attributeReportIBs.GetWriter()->ReserveBuffer(kReservedSizeEndOfReportIBs));
     122        1835 :     reservedEndOfReportIBs = true;
     123             : 
     124             :     {
     125             :         // TODO: Figure out how AttributePathExpandIterator should handle read
     126             :         // vs write paths.
     127        1835 :         ConcreteAttributePath readPath;
     128             : 
     129        1835 :         ChipLogDetail(DataManagement,
     130             :                       "Building Reports for ReadHandler with LastReportGeneration = 0x" ChipLogFormatX64
     131             :                       " DirtyGeneration = 0x" ChipLogFormatX64,
     132             :                       ChipLogValueX64(apReadHandler->mPreviousReportsBeginGeneration),
     133             :                       ChipLogValueX64(apReadHandler->mDirtyGeneration));
     134             : 
     135             :         // This ReadHandler is not generating reports, so we reset the iterator for a clean start.
     136        1835 :         if (!apReadHandler->IsReporting())
     137             :         {
     138        1028 :             apReadHandler->ResetPathIterator();
     139             :         }
     140             : 
     141             : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
     142        1835 :         uint32_t attributesRead = 0;
     143             : #endif
     144             : 
     145             :         // For each path included in the interested path of the read handler...
     146        5595 :         for (; apReadHandler->GetAttributePathExpandIterator()->Get(readPath);
     147        3760 :              apReadHandler->GetAttributePathExpandIterator()->Next())
     148             :         {
     149        4171 :             if (!apReadHandler->IsPriming())
     150             :             {
     151         677 :                 bool concretePathDirty = false;
     152             :                 // TODO: Optimize this implementation by making the iterator only emit intersected paths.
     153         677 :                 mGlobalDirtySet.ForEachActiveObject([&](auto * dirtyPath) {
     154         815 :                     if (dirtyPath->IsAttributePathSupersetOf(readPath))
     155             :                     {
     156             :                         // We don't need to worry about paths that were already marked dirty before the last time this read handler
     157             :                         // started a report that it completed: those paths already got reported.
     158         252 :                         if (dirtyPath->mGeneration > apReadHandler->mPreviousReportsBeginGeneration)
     159             :                         {
     160         249 :                             concretePathDirty = true;
     161         249 :                             return Loop::Break;
     162             :                         }
     163             :                     }
     164         566 :                     return Loop::Continue;
     165             :                 });
     166             : 
     167         677 :                 if (!concretePathDirty)
     168             :                 {
     169             :                     // This attribute is not dirty, we just skip this one.
     170         428 :                     continue;
     171             :                 }
     172             :             }
     173             :             else
     174             :             {
     175        3494 :                 if (IsClusterDataVersionMatch(apReadHandler->GetDataVersionFilterList(), readPath))
     176             :                 {
     177          26 :                     continue;
     178             :                 }
     179             :             }
     180             : 
     181             : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
     182        3717 :             attributesRead++;
     183        3717 :             if (attributesRead > mMaxAttributesPerChunk)
     184             :             {
     185         411 :                 ExitNow(err = CHIP_ERROR_BUFFER_TOO_SMALL);
     186             :             }
     187             : #endif
     188             : 
     189             :             // If we are processing a read request, or the initial report of a subscription, just regard all paths as dirty
     190             :             // paths.
     191        3697 :             TLV::TLVWriter attributeBackup;
     192        3697 :             attributeReportIBs.Checkpoint(attributeBackup);
     193        3697 :             ConcreteReadAttributePath pathForRetrieval(readPath);
     194             :             // Load the saved state from previous encoding session for chunking of one single attribute (list chunking).
     195        3697 :             AttributeValueEncoder::AttributeEncodeState encodeState = apReadHandler->GetAttributeEncodeState();
     196        3697 :             err = RetrieveClusterData(apReadHandler->GetSubjectDescriptor(), apReadHandler->IsFabricFiltered(), attributeReportIBs,
     197             :                                       pathForRetrieval, &encodeState);
     198        3697 :             if (err != CHIP_NO_ERROR)
     199             :             {
     200         391 :                 ChipLogError(DataManagement,
     201             :                              "Error retrieving data from clusterId: " ChipLogFormatMEI ", err = %" CHIP_ERROR_FORMAT,
     202             :                              ChipLogValueMEI(pathForRetrieval.mClusterId), err.Format());
     203             : 
     204             :                 // If error is not an "out of writer space" error, rollback and encode status.
     205             :                 // Otherwise, if partial data allowed, save the encode state.
     206             :                 // Otherwise roll back. If we have already encoded some chunks, we are done; otherwise encode status.
     207             : 
     208         391 :                 if (encodeState.AllowPartialData() && IsOutOfWriterSpaceError(err))
     209             :                 {
     210             :                     // Encoding is aborted but partial data is allowed, then we don't rollback and save the state for next chunk.
     211             :                     // The expectation is that RetrieveClusterData has already reset attributeReportIBs to a good state (rolled
     212             :                     // back any partially-written AttributeReportIB instances, reset its error status).  Since AllowPartialData()
     213             :                     // is true, we may not have encoded a complete attribute value, but we did, if we encoded anything, encode a
     214             :                     // set of complete AttributeReportIB instances that represent part of the attribute value.
     215         254 :                     apReadHandler->SetAttributeEncodeState(encodeState);
     216             :                 }
     217             :                 else
     218             :                 {
     219             :                     // We met a error during writing reports, one common case is we are running out of buffer, rollback the
     220             :                     // attributeReportIB to avoid any partial data.
     221         137 :                     attributeReportIBs.Rollback(attributeBackup);
     222         137 :                     apReadHandler->SetAttributeEncodeState(AttributeValueEncoder::AttributeEncodeState());
     223             : 
     224         137 :                     if (!IsOutOfWriterSpaceError(err))
     225             :                     {
     226             :                         // Try to encode our error as a status response.
     227           0 :                         err = attributeReportIBs.EncodeAttributeStatus(pathForRetrieval, StatusIB(err));
     228           0 :                         if (err != CHIP_NO_ERROR)
     229             :                         {
     230             :                             // OK, just roll back again and give up; if we still ran out of space we
     231             :                             // will send this status response in the next chunk.
     232           0 :                             attributeReportIBs.Rollback(attributeBackup);
     233             :                         }
     234             :                     }
     235             :                 }
     236             :             }
     237        3697 :             SuccessOrExit(err);
     238             :             // Successfully encoded the attribute, clear the internal state.
     239        3306 :             apReadHandler->SetAttributeEncodeState(AttributeValueEncoder::AttributeEncodeState());
     240        3697 :         }
     241             :         // We just visited all paths interested by this read handler and did not abort in the middle of iteration, there are no more
     242             :         // chunks for this report.
     243        1424 :         hasMoreChunks = false;
     244             :     }
     245        1835 : exit:
     246        1835 :     if (attributeReportIBs.GetWriter()->GetLengthWritten() != emptyReportDataLength)
     247             :     {
     248             :         // We may encounter BUFFER_TOO_SMALL with nothing actually written for the case of list chunking, so we check if we have
     249             :         // actually
     250        1160 :         attributeDataWritten = true;
     251             :     }
     252             : 
     253        1835 :     if (apHasEncodedData != nullptr)
     254             :     {
     255        1835 :         *apHasEncodedData = attributeDataWritten;
     256             :     }
     257             :     //
     258             :     // Running out of space is an error that we're expected to handle - the incompletely written DataIB has already been rolled back
     259             :     // earlier to ensure only whole and complete DataIBs are present in the stream.
     260             :     //
     261             :     // We can safely clear out the error so that the rest of the machinery to close out the reports, etc. will function correctly.
     262             :     // These are are guaranteed to not fail since we've already reserved memory for the remaining 'close out' TLV operations in this
     263             :     // function and its callers.
     264             :     //
     265        1835 :     if (IsOutOfWriterSpaceError(err) && reservedEndOfReportIBs)
     266             :     {
     267         411 :         ChipLogDetail(DataManagement, "<RE:Run> We cannot put more chunks into this report. Enable chunking.");
     268         411 :         err = CHIP_NO_ERROR;
     269             :     }
     270             : 
     271             :     //
     272             :     // Only close out the report if we haven't hit an error yet so far.
     273             :     //
     274        1835 :     if (err == CHIP_NO_ERROR)
     275             :     {
     276        1835 :         attributeReportIBs.GetWriter()->UnreserveBuffer(kReservedSizeEndOfReportIBs);
     277             : 
     278        1835 :         err = attributeReportIBs.EndOfAttributeReportIBs();
     279             : 
     280             :         //
     281             :         // We reserved space for this earlier - consequently, the call to end the ReportIBs should
     282             :         // never fail, so assert if we do since that's a logic bug.
     283             :         //
     284        1835 :         VerifyOrDie(err == CHIP_NO_ERROR);
     285             :     }
     286             : 
     287             :     //
     288             :     // Rollback the the entire ReportIB array if we never wrote any attributes
     289             :     // AND never hit an error.
     290             :     //
     291        1835 :     if (!attributeDataWritten && err == CHIP_NO_ERROR)
     292             :     {
     293         675 :         aReportDataBuilder.Rollback(backup);
     294             :     }
     295             : 
     296             :     // hasMoreChunks + no data encoded is a flag that we have encountered some trouble when processing the attribute.
     297             :     // BuildAndSendSingleReportData will abort the read transaction if we encoded no attribute and no events but hasMoreChunks is
     298             :     // set.
     299        1835 :     if (apHasMoreChunks != nullptr)
     300             :     {
     301        1835 :         *apHasMoreChunks = hasMoreChunks;
     302             :     }
     303             : 
     304        1835 :     return err;
     305             : }
     306             : 
     307         863 : CHIP_ERROR Engine::CheckAccessDeniedEventPaths(TLV::TLVWriter & aWriter, bool & aHasEncodedData, ReadHandler * apReadHandler)
     308             : {
     309             :     using Protocols::InteractionModel::Status;
     310             : 
     311         863 :     CHIP_ERROR err = CHIP_NO_ERROR;
     312        1756 :     for (auto current = apReadHandler->mpEventPathList; current != nullptr;)
     313             :     {
     314         893 :         if (current->mValue.IsWildcardPath())
     315             :         {
     316         837 :             current = current->mpNext;
     317         837 :             continue;
     318             :         }
     319             : 
     320          56 :         ConcreteEventPath path(current->mValue.mEndpointId, current->mValue.mClusterId, current->mValue.mEventId);
     321          56 :         Status status = CheckEventSupportStatus(path);
     322          56 :         if (status != Status::Success)
     323             :         {
     324           0 :             TLV::TLVWriter checkpoint = aWriter;
     325           0 :             err                       = EventReportIB::ConstructEventStatusIB(aWriter, path, StatusIB(status));
     326           0 :             if (err != CHIP_NO_ERROR)
     327             :             {
     328           0 :                 aWriter = checkpoint;
     329           0 :                 break;
     330             :             }
     331           0 :             aHasEncodedData = true;
     332             :         }
     333             : 
     334          56 :         Access::RequestPath requestPath{ .cluster = current->mValue.mClusterId, .endpoint = current->mValue.mEndpointId };
     335          56 :         Access::Privilege requestPrivilege = RequiredPrivilege::ForReadEvent(path);
     336             : 
     337          56 :         err = Access::GetAccessControl().Check(apReadHandler->GetSubjectDescriptor(), requestPath, requestPrivilege);
     338          56 :         if (err != CHIP_ERROR_ACCESS_DENIED)
     339             :         {
     340          54 :             ReturnErrorOnFailure(err);
     341             :         }
     342             :         else
     343             :         {
     344           2 :             TLV::TLVWriter checkpoint = aWriter;
     345           2 :             err                       = EventReportIB::ConstructEventStatusIB(aWriter, path, StatusIB(Status::UnsupportedAccess));
     346           2 :             if (err != CHIP_NO_ERROR)
     347             :             {
     348           0 :                 aWriter = checkpoint;
     349           0 :                 break;
     350             :             }
     351           2 :             aHasEncodedData = true;
     352           2 :             ChipLogDetail(InteractionModel, "Access to event (%u, " ChipLogFormatMEI ", " ChipLogFormatMEI ") denied by ACL",
     353             :                           current->mValue.mEndpointId, ChipLogValueMEI(current->mValue.mClusterId),
     354             :                           ChipLogValueMEI(current->mValue.mEventId));
     355             :         }
     356          56 :         current = current->mpNext;
     357             :     }
     358             : 
     359         863 :     return err;
     360             : }
     361             : 
     362        1835 : CHIP_ERROR Engine::BuildSingleReportDataEventReports(ReportDataMessage::Builder & aReportDataBuilder, ReadHandler * apReadHandler,
     363             :                                                      bool aBufferIsUsed, bool * apHasMoreChunks, bool * apHasEncodedData)
     364             : {
     365        1835 :     CHIP_ERROR err        = CHIP_NO_ERROR;
     366        1835 :     size_t eventCount     = 0;
     367        1835 :     bool hasEncodedStatus = false;
     368        1835 :     TLV::TLVWriter backup;
     369        1835 :     bool eventClean                = true;
     370        1835 :     auto & eventMin                = apReadHandler->GetEventMin();
     371        1835 :     EventManagement & eventManager = EventManagement::GetInstance();
     372        1835 :     bool hasMoreChunks             = false;
     373             : 
     374        1835 :     aReportDataBuilder.Checkpoint(backup);
     375             : 
     376        1835 :     VerifyOrExit(apReadHandler->GetEventPathList() != nullptr, );
     377             : 
     378             :     // If the eventManager is not valid or has not been initialized,
     379             :     // skip the rest of processing
     380         890 :     VerifyOrExit(eventManager.IsValid(), ChipLogError(DataManagement, "EventManagement has not yet initialized"));
     381             : 
     382         887 :     eventClean = apReadHandler->CheckEventClean(eventManager);
     383             : 
     384             :     // proceed only if there are new events.
     385         887 :     if (eventClean)
     386             :     {
     387          24 :         ExitNow(); // Read clean, move along
     388             :     }
     389             : 
     390             :     {
     391             :         // Just like what we do in BuildSingleReportDataAttributeReportIBs(), we need to reserve one byte for end of container tag
     392             :         // when encoding events to ensure we can close the container successfully.
     393         863 :         const uint32_t kReservedSizeEndOfReportIBs = 1;
     394         863 :         EventReportIBs::Builder & eventReportIBs   = aReportDataBuilder.CreateEventReports();
     395         863 :         SuccessOrExit(err = aReportDataBuilder.GetError());
     396         863 :         VerifyOrExit(eventReportIBs.GetWriter() != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
     397         863 :         SuccessOrExit(err = eventReportIBs.GetWriter()->ReserveBuffer(kReservedSizeEndOfReportIBs));
     398             : 
     399         863 :         err = CheckAccessDeniedEventPaths(*(eventReportIBs.GetWriter()), hasEncodedStatus, apReadHandler);
     400         863 :         SuccessOrExit(err);
     401             : 
     402         863 :         err = eventManager.FetchEventsSince(*(eventReportIBs.GetWriter()), apReadHandler->GetEventPathList(), eventMin, eventCount,
     403         863 :                                             apReadHandler->GetSubjectDescriptor());
     404             : 
     405         863 :         if ((err == CHIP_END_OF_TLV) || (err == CHIP_ERROR_TLV_UNDERRUN) || (err == CHIP_NO_ERROR))
     406             :         {
     407         348 :             err           = CHIP_NO_ERROR;
     408         348 :             hasMoreChunks = false;
     409             :         }
     410         515 :         else if (IsOutOfWriterSpaceError(err))
     411             :         {
     412             :             // when first cluster event is too big to fit in the packet, ignore that cluster event.
     413             :             // However, we may have encoded some attributes before, we don't skip it in that case.
     414         515 :             if (eventCount == 0)
     415             :             {
     416         206 :                 if (!aBufferIsUsed)
     417             :                 {
     418           0 :                     eventMin++;
     419             :                 }
     420         206 :                 ChipLogDetail(DataManagement, "<RE:Run> first cluster event is too big so that it fails to fit in the packet!");
     421         206 :                 err = CHIP_NO_ERROR;
     422             :             }
     423             :             else
     424             :             {
     425             :                 // `FetchEventsSince` has filled the available space
     426             :                 // within the allowed buffer before it fit all the
     427             :                 // available events.  This is an expected condition,
     428             :                 // so we do not propagate the error to higher levels;
     429             :                 // instead, we terminate the event processing for now
     430         309 :                 err = CHIP_NO_ERROR;
     431             :             }
     432         515 :             hasMoreChunks = true;
     433             :         }
     434             :         else
     435             :         {
     436             :             // All other errors are propagated to higher level.
     437             :             // Exiting here and returning an error will lead to
     438             :             // abandoning subscription.
     439           0 :             ExitNow();
     440             :         }
     441             : 
     442         863 :         SuccessOrExit(err = eventReportIBs.GetWriter()->UnreserveBuffer(kReservedSizeEndOfReportIBs));
     443         863 :         SuccessOrExit(err = eventReportIBs.EndOfEventReports());
     444             :     }
     445         863 :     ChipLogDetail(DataManagement, "Fetched %u events", static_cast<unsigned int>(eventCount));
     446             : 
     447           0 : exit:
     448        1835 :     if (apHasEncodedData != nullptr)
     449             :     {
     450        1835 :         *apHasEncodedData = hasEncodedStatus || (eventCount != 0);
     451             :     }
     452             : 
     453             :     // Maybe encoding the attributes has already used up all space.
     454        1835 :     if ((err == CHIP_NO_ERROR || IsOutOfWriterSpaceError(err)) && !(hasEncodedStatus || (eventCount != 0)))
     455             :     {
     456        1195 :         aReportDataBuilder.Rollback(backup);
     457        1195 :         err = CHIP_NO_ERROR;
     458             :     }
     459             : 
     460             :     // hasMoreChunks + no data encoded is a flag that we have encountered some trouble when processing the attribute.
     461             :     // BuildAndSendSingleReportData will abort the read transaction if we encoded no attribute and no events but hasMoreChunks is
     462             :     // set.
     463        1835 :     if (apHasMoreChunks != nullptr)
     464             :     {
     465        1835 :         *apHasMoreChunks = hasMoreChunks;
     466             :     }
     467        1835 :     return err;
     468             : }
     469             : 
     470        1835 : CHIP_ERROR Engine::BuildAndSendSingleReportData(ReadHandler * apReadHandler)
     471             : {
     472        1835 :     CHIP_ERROR err = CHIP_NO_ERROR;
     473        1835 :     chip::System::PacketBufferTLVWriter reportDataWriter;
     474        1835 :     ReportDataMessage::Builder reportDataBuilder;
     475        1835 :     chip::System::PacketBufferHandle bufHandle = System::PacketBufferHandle::New(chip::app::kMaxSecureSduLengthBytes);
     476        1835 :     uint16_t reservedSize                      = 0;
     477        1835 :     bool hasMoreChunks                         = false;
     478        1835 :     bool needCloseReadHandler                  = false;
     479             : 
     480             :     // Reserved size for the MoreChunks boolean flag, which takes up 1 byte for the control tag and 1 byte for the context tag.
     481        1835 :     const uint32_t kReservedSizeForMoreChunksFlag = 1 + 1;
     482             : 
     483             :     // Reserved size for the uint8_t InteractionModelRevision flag, which takes up 1 byte for the control tag and 1 byte for the
     484             :     // context tag, 1 byte for value
     485        1835 :     const uint32_t kReservedSizeForIMRevision = 1 + 1 + 1;
     486             : 
     487             :     // Reserved size for the end of report message, which is an end-of-container (i.e 1 byte for the control tag).
     488        1835 :     const uint32_t kReservedSizeForEndOfReportMessage = 1;
     489             : 
     490             :     // Reserved size for an empty EventReportIBs, so we can at least check if there are any events need to be reported.
     491        1835 :     const uint32_t kReservedSizeForEventReportIBs = 3; // type, tag, end of container
     492             : 
     493        1835 :     VerifyOrExit(apReadHandler != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
     494        1835 :     VerifyOrExit(apReadHandler->GetSession() != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
     495        1835 :     VerifyOrExit(!bufHandle.IsNull(), err = CHIP_ERROR_NO_MEMORY);
     496             : 
     497        1835 :     if (bufHandle->AvailableDataLength() > kMaxSecureSduLengthBytes)
     498             :     {
     499           0 :         reservedSize = static_cast<uint16_t>(bufHandle->AvailableDataLength() - kMaxSecureSduLengthBytes);
     500             :     }
     501             : 
     502        1835 :     reportDataWriter.Init(std::move(bufHandle));
     503             : 
     504             : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
     505        1835 :     reportDataWriter.ReserveBuffer(mReservedSize);
     506             : #endif
     507             : 
     508             :     // Always limit the size of the generated packet to fit within kMaxSecureSduLengthBytes regardless of the available buffer
     509             :     // capacity.
     510             :     // Also, we need to reserve some extra space for the MIC field.
     511        1835 :     reportDataWriter.ReserveBuffer(static_cast<uint32_t>(reservedSize + chip::Crypto::CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES));
     512             : 
     513             :     // Create a report data.
     514        1835 :     err = reportDataBuilder.Init(&reportDataWriter);
     515        1835 :     SuccessOrExit(err);
     516             : 
     517        1835 :     if (apReadHandler->IsType(ReadHandler::InteractionType::Subscribe))
     518             :     {
     519             : #if CHIP_CONFIG_ENABLE_ICD_SERVER
     520             :         // Notify the ICDManager that we are about to send a subscription report before we prepare the Report payload.
     521             :         // This allows the ICDManager to trigger any necessary updates and have the information in the report about to be sent.
     522             :         app::ICDNotifier::GetInstance().NotifySubscriptionReport();
     523             : #endif // CHIP_CONFIG_ENABLE_ICD_SERVER
     524             : 
     525         364 :         SubscriptionId subscriptionId = 0;
     526         364 :         apReadHandler->GetSubscriptionId(subscriptionId);
     527         364 :         reportDataBuilder.SubscriptionId(subscriptionId);
     528             :     }
     529             : 
     530        1835 :     SuccessOrExit(err = reportDataWriter.ReserveBuffer(kReservedSizeForMoreChunksFlag + kReservedSizeForIMRevision +
     531             :                                                        kReservedSizeForEndOfReportMessage + kReservedSizeForEventReportIBs));
     532             : 
     533             :     {
     534        1835 :         bool hasMoreChunksForAttributes = false;
     535        1835 :         bool hasMoreChunksForEvents     = false;
     536        1835 :         bool hasEncodedAttributes       = false;
     537        1835 :         bool hasEncodedEvents           = false;
     538             : 
     539        1835 :         err = BuildSingleReportDataAttributeReportIBs(reportDataBuilder, apReadHandler, &hasMoreChunksForAttributes,
     540             :                                                       &hasEncodedAttributes);
     541        1866 :         SuccessOrExit(err);
     542        1835 :         SuccessOrExit(err = reportDataWriter.UnreserveBuffer(kReservedSizeForEventReportIBs));
     543        1835 :         err = BuildSingleReportDataEventReports(reportDataBuilder, apReadHandler, hasEncodedAttributes, &hasMoreChunksForEvents,
     544             :                                                 &hasEncodedEvents);
     545        1835 :         SuccessOrExit(err);
     546             : 
     547        1835 :         hasMoreChunks = hasMoreChunksForAttributes || hasMoreChunksForEvents;
     548             : 
     549        1835 :         if (!hasEncodedAttributes && !hasEncodedEvents && hasMoreChunks)
     550             :         {
     551          31 :             ChipLogError(DataManagement,
     552             :                          "No data actually encoded but hasMoreChunks flag is set, close read handler! (attribute too big?)");
     553          31 :             err = apReadHandler->SendStatusReport(Protocols::InteractionModel::Status::ResourceExhausted);
     554          31 :             if (err == CHIP_NO_ERROR)
     555             :             {
     556          31 :                 needCloseReadHandler = true;
     557             :             }
     558          31 :             ExitNow();
     559             :         }
     560             :     }
     561             : 
     562        1804 :     SuccessOrExit(err = reportDataBuilder.GetError());
     563        1804 :     SuccessOrExit(err = reportDataWriter.UnreserveBuffer(kReservedSizeForMoreChunksFlag + kReservedSizeForIMRevision +
     564             :                                                          kReservedSizeForEndOfReportMessage));
     565        1804 :     if (hasMoreChunks)
     566             :     {
     567         861 :         reportDataBuilder.MoreChunkedMessages(true);
     568             :     }
     569         943 :     else if (apReadHandler->IsType(ReadHandler::InteractionType::Read))
     570             :     {
     571         635 :         reportDataBuilder.SuppressResponse(true);
     572             :     }
     573             : 
     574        1804 :     reportDataBuilder.EndOfReportDataMessage();
     575             : 
     576             :     //
     577             :     // Since we've already reserved space for both the MoreChunked/SuppressResponse flags, as well as
     578             :     // the end-of-container flag for the end of the report, we should never hit an error closing out the message.
     579             :     //
     580        1804 :     VerifyOrDie(reportDataBuilder.GetError() == CHIP_NO_ERROR);
     581             : 
     582        1804 :     err = reportDataWriter.Finalize(&bufHandle);
     583        1804 :     SuccessOrExit(err);
     584             : 
     585        1804 :     ChipLogDetail(DataManagement, "<RE> Sending report (payload has %" PRIu32 " bytes)...", reportDataWriter.GetLengthWritten());
     586        1804 :     err = SendReport(apReadHandler, std::move(bufHandle), hasMoreChunks);
     587        1804 :     VerifyOrExit(err == CHIP_NO_ERROR,
     588             :                  ChipLogError(DataManagement, "<RE> Error sending out report data with %" CHIP_ERROR_FORMAT "!", err.Format()));
     589             : 
     590        1800 :     ChipLogDetail(DataManagement, "<RE> ReportsInFlight = %" PRIu32 " with readHandler %" PRIu32 ", RE has %s", mNumReportsInFlight,
     591             :                   mCurReadHandlerIdx, hasMoreChunks ? "more messages" : "no more messages");
     592             : 
     593           0 : exit:
     594        1835 :     if (err != CHIP_NO_ERROR || (apReadHandler->IsType(ReadHandler::InteractionType::Read) && !hasMoreChunks) ||
     595             :         needCloseReadHandler)
     596             :     {
     597             :         //
     598             :         // In the case of successful report generation and we're on the last chunk of a read, we don't expect
     599             :         // any further activity on this exchange. The EC layer will automatically close our EC, so shutdown the ReadHandler
     600             :         // gracefully.
     601             :         //
     602         668 :         apReadHandler->Close();
     603             :     }
     604             : 
     605        1835 :     return err;
     606        1835 : }
     607             : 
     608        1654 : void Engine::Run(System::Layer * aSystemLayer, void * apAppState)
     609             : {
     610        1654 :     Engine * const pEngine = reinterpret_cast<Engine *>(apAppState);
     611        1654 :     pEngine->mRunScheduled = false;
     612        1654 :     pEngine->Run();
     613        1654 : }
     614             : 
     615        1987 : CHIP_ERROR Engine::ScheduleRun()
     616             : {
     617        1987 :     if (IsRunScheduled())
     618             :     {
     619         330 :         return CHIP_NO_ERROR;
     620             :     }
     621             : 
     622        1657 :     Messaging::ExchangeManager * exchangeManager = mpImEngine->GetExchangeManager();
     623        1657 :     if (exchangeManager == nullptr)
     624             :     {
     625           0 :         return CHIP_ERROR_INCORRECT_STATE;
     626             :     }
     627        1657 :     SessionManager * sessionManager = exchangeManager->GetSessionManager();
     628        1657 :     if (sessionManager == nullptr)
     629             :     {
     630           0 :         return CHIP_ERROR_INCORRECT_STATE;
     631             :     }
     632        1657 :     System::Layer * systemLayer = sessionManager->SystemLayer();
     633        1657 :     if (systemLayer == nullptr)
     634             :     {
     635           0 :         return CHIP_ERROR_INCORRECT_STATE;
     636             :     }
     637        1657 :     ReturnErrorOnFailure(systemLayer->ScheduleWork(Run, this));
     638        1657 :     mRunScheduled = true;
     639        1657 :     return CHIP_NO_ERROR;
     640             : }
     641             : 
     642        2019 : void Engine::Run()
     643             : {
     644        2019 :     uint32_t numReadHandled = 0;
     645             : 
     646             :     // We may be deallocating read handlers as we go.  Track how many we had
     647             :     // initially, so we make sure to go through all of them.
     648        2019 :     size_t initialAllocated = mpImEngine->mReadHandlers.Allocated();
     649        4023 :     while ((mNumReportsInFlight < CHIP_IM_MAX_REPORTS_IN_FLIGHT) && (numReadHandled < initialAllocated))
     650             :     {
     651             :         ReadHandler * readHandler =
     652        2008 :             mpImEngine->ActiveHandlerAt(mCurReadHandlerIdx % (uint32_t) mpImEngine->mReadHandlers.Allocated());
     653        2008 :         VerifyOrDie(readHandler != nullptr);
     654             : 
     655        2008 :         if (readHandler->ShouldReportUnscheduled() || mpImEngine->GetReportScheduler()->IsReportableNow(readHandler))
     656             :         {
     657             : 
     658        1834 :             mRunningReadHandler = readHandler;
     659        1834 :             CHIP_ERROR err      = BuildAndSendSingleReportData(readHandler);
     660        1834 :             mRunningReadHandler = nullptr;
     661        1834 :             if (err != CHIP_NO_ERROR)
     662             :             {
     663           4 :                 return;
     664             :             }
     665             :         }
     666             : 
     667        2004 :         numReadHandled++;
     668             :         // If readHandler removed itself from our list, we also decremented
     669             :         // mCurReadHandlerIdx to account for that removal, so it's safe to
     670             :         // increment here.
     671        2004 :         mCurReadHandlerIdx++;
     672             :     }
     673             : 
     674             :     //
     675             :     // If our tracker has exceeded the bounds of the handler list, reset it back to 0.
     676             :     // This isn't strictly necessary, but does make it easier to debug issues in this code if they
     677             :     // do arise.
     678             :     //
     679        2015 :     if (mCurReadHandlerIdx >= mpImEngine->mReadHandlers.Allocated())
     680             :     {
     681        1974 :         mCurReadHandlerIdx = 0;
     682             :     }
     683             : 
     684        2015 :     bool allReadClean = true;
     685             : 
     686        2015 :     mpImEngine->mReadHandlers.ForEachActiveObject([&allReadClean](ReadHandler * handler) {
     687        2233 :         if (handler->IsDirty())
     688             :         {
     689         847 :             allReadClean = false;
     690         847 :             return Loop::Break;
     691             :         }
     692             : 
     693        1386 :         return Loop::Continue;
     694             :     });
     695             : 
     696        2015 :     if (allReadClean)
     697             :     {
     698        1168 :         ChipLogDetail(DataManagement, "All ReadHandler-s are clean, clear GlobalDirtySet");
     699             : 
     700        1168 :         mGlobalDirtySet.ReleaseAll();
     701             :     }
     702             : }
     703             : 
     704         276 : bool Engine::MergeOverlappedAttributePath(const AttributePathParams & aAttributePath)
     705             : {
     706         276 :     return Loop::Break == mGlobalDirtySet.ForEachActiveObject([&](auto * path) {
     707         214 :         if (path->IsAttributePathSupersetOf(aAttributePath))
     708             :         {
     709         112 :             path->mGeneration = GetDirtySetGeneration();
     710         112 :             return Loop::Break;
     711             :         }
     712         102 :         if (aAttributePath.IsAttributePathSupersetOf(*path))
     713             :         {
     714             :             // TODO: the wildcard input path may be superset of next paths in globalDirtySet, it is fine at this moment, since
     715             :             // when building report, it would use the first path of globalDirtySet to compare against interested paths read clients
     716             :             // want.
     717             :             // It is better to eliminate the duplicate wildcard paths in follow-up
     718           2 :             path->mGeneration  = GetDirtySetGeneration();
     719           2 :             path->mEndpointId  = aAttributePath.mEndpointId;
     720           2 :             path->mClusterId   = aAttributePath.mClusterId;
     721           2 :             path->mListIndex   = aAttributePath.mListIndex;
     722           2 :             path->mAttributeId = aAttributePath.mAttributeId;
     723           2 :             return Loop::Break;
     724             :         }
     725         100 :         return Loop::Continue;
     726         276 :     });
     727             : }
     728             : 
     729           8 : bool Engine::ClearTombPaths()
     730             : {
     731           8 :     bool pathReleased = false;
     732           8 :     mGlobalDirtySet.ForEachActiveObject([&](auto * path) {
     733          64 :         if (path->mGeneration == 0)
     734             :         {
     735          28 :             mGlobalDirtySet.ReleaseObject(path);
     736          28 :             pathReleased = true;
     737             :         }
     738          64 :         return Loop::Continue;
     739             :     });
     740           8 :     return pathReleased;
     741             : }
     742             : 
     743           5 : bool Engine::MergeDirtyPathsUnderSameCluster()
     744             : {
     745           5 :     mGlobalDirtySet.ForEachActiveObject([&](auto * outerPath) {
     746          40 :         if (outerPath->HasWildcardClusterId() || outerPath->mGeneration == 0)
     747             :         {
     748          14 :             return Loop::Continue;
     749             :         }
     750          26 :         mGlobalDirtySet.ForEachActiveObject([&](auto * innerPath) {
     751         208 :             if (innerPath == outerPath)
     752             :             {
     753          26 :                 return Loop::Continue;
     754             :             }
     755             :             // We don't support paths with a wildcard endpoint + a concrete cluster in global dirty set, so we do a simple == check
     756             :             // here.
     757         182 :             if (innerPath->mEndpointId != outerPath->mEndpointId || innerPath->mClusterId != outerPath->mClusterId)
     758             :             {
     759         168 :                 return Loop::Continue;
     760             :             }
     761          14 :             if (innerPath->mGeneration > outerPath->mGeneration)
     762             :             {
     763           0 :                 outerPath->mGeneration = innerPath->mGeneration;
     764             :             }
     765          14 :             outerPath->SetWildcardAttributeId();
     766             : 
     767             :             // The object pool does not allow us to release objects in a nested iteration, mark the path as a tomb by setting its
     768             :             // generation to 0 and then clear it later.
     769          14 :             innerPath->mGeneration = 0;
     770          14 :             return Loop::Continue;
     771             :         });
     772          26 :         return Loop::Continue;
     773             :     });
     774             : 
     775           5 :     return ClearTombPaths();
     776             : }
     777             : 
     778           3 : bool Engine::MergeDirtyPathsUnderSameEndpoint()
     779             : {
     780           3 :     mGlobalDirtySet.ForEachActiveObject([&](auto * outerPath) {
     781          24 :         if (outerPath->HasWildcardEndpointId() || outerPath->mGeneration == 0)
     782             :         {
     783          14 :             return Loop::Continue;
     784             :         }
     785          10 :         mGlobalDirtySet.ForEachActiveObject([&](auto * innerPath) {
     786          80 :             if (innerPath == outerPath)
     787             :             {
     788          10 :                 return Loop::Continue;
     789             :             }
     790          70 :             if (innerPath->mEndpointId != outerPath->mEndpointId)
     791             :             {
     792          56 :                 return Loop::Continue;
     793             :             }
     794          14 :             if (innerPath->mGeneration > outerPath->mGeneration)
     795             :             {
     796           0 :                 outerPath->mGeneration = innerPath->mGeneration;
     797             :             }
     798          14 :             outerPath->SetWildcardClusterId();
     799          14 :             outerPath->SetWildcardAttributeId();
     800             : 
     801             :             // The object pool does not allow us to release objects in a nested iteration, mark the path as a tomb by setting its
     802             :             // generation to 0 and then clear it later.
     803          14 :             innerPath->mGeneration = 0;
     804          14 :             return Loop::Continue;
     805             :         });
     806          10 :         return Loop::Continue;
     807             :     });
     808           3 :     return ClearTombPaths();
     809             : }
     810             : 
     811         189 : CHIP_ERROR Engine::InsertPathIntoDirtySet(const AttributePathParams & aAttributePath)
     812             : {
     813         189 :     ReturnErrorCodeIf(MergeOverlappedAttributePath(aAttributePath), CHIP_NO_ERROR);
     814             : 
     815          82 :     if (mGlobalDirtySet.Exhausted() && !MergeDirtyPathsUnderSameCluster() && !MergeDirtyPathsUnderSameEndpoint())
     816             :     {
     817           1 :         ChipLogDetail(DataManagement, "Global dirty set pool exhausted, merge all paths.");
     818           1 :         mGlobalDirtySet.ReleaseAll();
     819           1 :         auto object         = mGlobalDirtySet.CreateObject();
     820           1 :         object->mGeneration = GetDirtySetGeneration();
     821             :     }
     822             : 
     823          82 :     ReturnErrorCodeIf(MergeOverlappedAttributePath(aAttributePath), CHIP_NO_ERROR);
     824          79 :     ChipLogDetail(DataManagement, "Cannot merge the new path into any existing path, create one.");
     825             : 
     826          79 :     auto object = mGlobalDirtySet.CreateObject();
     827          79 :     if (object == nullptr)
     828             :     {
     829             :         // This should not happen, this path should be merged into the wildcard endpoint at least.
     830           0 :         ChipLogError(DataManagement, "mGlobalDirtySet pool full, cannot handle more entries!");
     831           0 :         return CHIP_ERROR_NO_MEMORY;
     832             :     }
     833          79 :     *object             = aAttributePath;
     834          79 :     object->mGeneration = GetDirtySetGeneration();
     835             : 
     836          79 :     return CHIP_NO_ERROR;
     837             : }
     838             : 
     839        2680 : CHIP_ERROR Engine::SetDirty(AttributePathParams & aAttributePath)
     840             : {
     841        2680 :     BumpDirtySetGeneration();
     842             : 
     843        2680 :     bool intersectsInterestPath = false;
     844        2680 :     mpImEngine->mReadHandlers.ForEachActiveObject([&aAttributePath, &intersectsInterestPath](ReadHandler * handler) {
     845             :         // We call AttributePathIsDirty for both read interactions and subscribe interactions, since we may send inconsistent
     846             :         // attribute data between two chunks. AttributePathIsDirty will not schedule a new run for read handlers which are
     847             :         // waiting for a response to the last message chunk for read interactions.
     848         477 :         if (handler->CanStartReporting() || handler->IsAwaitingReportResponse())
     849             :         {
     850         934 :             for (auto object = handler->GetAttributePathList(); object != nullptr; object = object->mpNext)
     851             :             {
     852         802 :                 if (object->mValue.Intersects(aAttributePath))
     853             :                 {
     854         345 :                     handler->AttributePathIsDirty(aAttributePath);
     855         345 :                     intersectsInterestPath = true;
     856         345 :                     break;
     857             :                 }
     858             :             }
     859             :         }
     860             : 
     861         477 :         return Loop::Continue;
     862             :     });
     863             : 
     864        2680 :     if (!intersectsInterestPath)
     865             :     {
     866        2496 :         return CHIP_NO_ERROR;
     867             :     }
     868         184 :     ReturnErrorOnFailure(InsertPathIntoDirtySet(aAttributePath));
     869             : 
     870         184 :     return CHIP_NO_ERROR;
     871             : }
     872             : 
     873        1804 : CHIP_ERROR Engine::SendReport(ReadHandler * apReadHandler, System::PacketBufferHandle && aPayload, bool aHasMoreChunks)
     874             : {
     875        1804 :     CHIP_ERROR err = CHIP_NO_ERROR;
     876             : 
     877             :     // We can only have 1 report in flight for any given read - increment and break out.
     878        1804 :     mNumReportsInFlight++;
     879        1804 :     err = apReadHandler->SendReportData(std::move(aPayload), aHasMoreChunks);
     880        1804 :     if (err != CHIP_NO_ERROR)
     881             :     {
     882           4 :         --mNumReportsInFlight;
     883             :     }
     884        1804 :     return err;
     885             : }
     886             : 
     887        1800 : void Engine::OnReportConfirm()
     888             : {
     889        1800 :     VerifyOrDie(mNumReportsInFlight > 0);
     890             : 
     891        1800 :     if (mNumReportsInFlight == CHIP_IM_MAX_REPORTS_IN_FLIGHT)
     892             :     {
     893             :         // We could have other things waiting to go now that this report is no
     894             :         // longer in flight.
     895          45 :         ScheduleRun();
     896             :     }
     897        1800 :     mNumReportsInFlight--;
     898        1800 :     ChipLogDetail(DataManagement, "<RE> OnReportConfirm: NumReports = %" PRIu32, mNumReportsInFlight);
     899        1800 : }
     900             : 
     901          20 : void Engine::GetMinEventLogPosition(uint32_t & aMinLogPosition)
     902             : {
     903          20 :     mpImEngine->mReadHandlers.ForEachActiveObject([&aMinLogPosition](ReadHandler * handler) {
     904          20 :         if (handler->IsType(ReadHandler::InteractionType::Read))
     905             :         {
     906           0 :             return Loop::Continue;
     907             :         }
     908             : 
     909          20 :         uint32_t initialWrittenEventsBytes = handler->GetLastWrittenEventsBytes();
     910          20 :         if (initialWrittenEventsBytes < aMinLogPosition)
     911             :         {
     912          20 :             aMinLogPosition = initialWrittenEventsBytes;
     913             :         }
     914             : 
     915          20 :         return Loop::Continue;
     916             :     });
     917          20 : }
     918             : 
     919          20 : CHIP_ERROR Engine::ScheduleBufferPressureEventDelivery(uint32_t aBytesWritten)
     920             : {
     921          20 :     uint32_t minEventLogPosition = aBytesWritten;
     922          20 :     GetMinEventLogPosition(minEventLogPosition);
     923          20 :     if (aBytesWritten - minEventLogPosition > CHIP_CONFIG_EVENT_LOGGING_BYTE_THRESHOLD)
     924             :     {
     925           0 :         ChipLogDetail(DataManagement, "<RE> Buffer overfilled CHIP_CONFIG_EVENT_LOGGING_BYTE_THRESHOLD %d, schedule engine run",
     926             :                       CHIP_CONFIG_EVENT_LOGGING_BYTE_THRESHOLD);
     927           0 :         return ScheduleRun();
     928             :     }
     929          20 :     return CHIP_NO_ERROR;
     930             : }
     931             : 
     932         662 : CHIP_ERROR Engine::ScheduleEventDelivery(ConcreteEventPath & aPath, uint32_t aBytesWritten)
     933             : {
     934             :     // If we literally have no read handlers right now that care about any events,
     935             :     // we don't need to call schedule run for event.
     936             :     // If schedule run is called, actually we would not delivery events as well.
     937             :     // Just wanna save one schedule run here
     938         662 :     if (mpImEngine->mEventPathPool.Allocated() == 0)
     939             :     {
     940         630 :         return CHIP_NO_ERROR;
     941             :     }
     942             : 
     943          32 :     bool isUrgentEvent = false;
     944          32 :     mpImEngine->mReadHandlers.ForEachActiveObject([&aPath, &isUrgentEvent](ReadHandler * handler) {
     945          40 :         if (handler->IsType(ReadHandler::InteractionType::Read))
     946             :         {
     947           0 :             return Loop::Continue;
     948             :         }
     949             : 
     950         104 :         for (auto * interestedPath = handler->GetEventPathList(); interestedPath != nullptr;
     951          64 :              interestedPath        = interestedPath->mpNext)
     952             :         {
     953          76 :             if (interestedPath->mValue.IsEventPathSupersetOf(aPath) && interestedPath->mValue.mIsUrgentEvent)
     954             :             {
     955          12 :                 isUrgentEvent = true;
     956          12 :                 handler->ForceDirtyState();
     957          12 :                 break;
     958             :             }
     959             :         }
     960             : 
     961          40 :         return Loop::Continue;
     962             :     });
     963             : 
     964          32 :     if (isUrgentEvent)
     965             :     {
     966          12 :         ChipLogDetail(DataManagement, "Urgent event will be sent once reporting is not blocked by the min interval");
     967          12 :         return CHIP_NO_ERROR;
     968             :     }
     969             : 
     970          20 :     return ScheduleBufferPressureEventDelivery(aBytesWritten);
     971             : }
     972             : 
     973         365 : void Engine::ScheduleUrgentEventDeliverySync(Optional<FabricIndex> fabricIndex)
     974             : {
     975         365 :     mpImEngine->mReadHandlers.ForEachActiveObject([fabricIndex](ReadHandler * handler) {
     976           0 :         if (handler->IsType(ReadHandler::InteractionType::Read))
     977             :         {
     978           0 :             return Loop::Continue;
     979             :         }
     980             : 
     981           0 :         if (fabricIndex.HasValue() && fabricIndex.Value() != handler->GetAccessingFabricIndex())
     982             :         {
     983           0 :             return Loop::Continue;
     984             :         }
     985             : 
     986           0 :         handler->ForceDirtyState();
     987             : 
     988           0 :         return Loop::Continue;
     989             :     });
     990             : 
     991         365 :     Run();
     992         365 : }
     993             : 
     994             : }; // namespace reporting
     995             : } // namespace app
     996             : } // namespace chip
     997             : 
     998        3697 : void __attribute__((weak)) MatterPreAttributeReadCallback(const chip::app::ConcreteAttributePath & attributePath) {}
     999        3306 : void __attribute__((weak)) MatterPostAttributeReadCallback(const chip::app::ConcreteAttributePath & attributePath) {}
    1000             : 
    1001             : // TODO: MatterReportingAttributeChangeCallback should just live in libCHIP,
    1002             : // instead of being in ember-compatibility-functions.  It does not depend on any
    1003             : // app-specific generated bits.
    1004             : void __attribute__((weak))
    1005           0 : MatterReportingAttributeChangeCallback(chip::EndpointId endpoint, chip::ClusterId clusterId, chip::AttributeId attributeId)
    1006           0 : {}

Generated by: LCOV version 1.14