Matter SDK Coverage Report
Current view: top level - app/reporting - Engine.cpp (source / functions) Coverage Total Hit
Test: SHA:f84fe08d06f240e801b5d923f8a938a9938ca110 Lines: 91.8 % 501 460
Test Date: 2025-02-22 08:08:07 Functions: 95.1 % 41 39

            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              : #include <access/AccessRestrictionProvider.h>
      20              : #include <access/Privilege.h>
      21              : #include <app/AppConfig.h>
      22              : #include <app/AttributePathExpandIterator.h>
      23              : #include <app/ConcreteEventPath.h>
      24              : #include <app/GlobalAttributes.h>
      25              : #include <app/InteractionModelEngine.h>
      26              : #include <app/RequiredPrivilege.h>
      27              : #include <app/data-model-provider/ActionReturnStatus.h>
      28              : #include <app/data-model-provider/MetadataLookup.h>
      29              : #include <app/data-model-provider/MetadataTypes.h>
      30              : #include <app/data-model-provider/Provider.h>
      31              : #include <app/icd/server/ICDServerConfig.h>
      32              : #include <app/reporting/Engine.h>
      33              : #include <app/reporting/reporting.h>
      34              : #include <app/util/MatterCallbacks.h>
      35              : #include <lib/core/CHIPError.h>
      36              : #include <lib/core/DataModelTypes.h>
      37              : #include <lib/support/CodeUtils.h>
      38              : #include <protocols/interaction_model/StatusCode.h>
      39              : 
      40              : #include <optional>
      41              : 
      42              : #if CHIP_CONFIG_ENABLE_ICD_SERVER
      43              : #include <app/icd/server/ICDNotifier.h> // nogncheck
      44              : #endif
      45              : 
      46              : using namespace chip::Access;
      47              : 
      48              : namespace chip {
      49              : namespace app {
      50              : namespace reporting {
      51              : namespace {
      52              : 
      53              : using Protocols::InteractionModel::Status;
      54              : 
      55              : /// Returns the status of ACL validation.
      56              : ///   If the return value has a status set, that means the ACL check failed,
      57              : ///   the read must not be performed, and the returned status (which may
      58              : ///   be success, when dealing with non-concrete paths) should be used
      59              : ///   as the status for the read.
      60              : ///
      61              : ///   If the returned value is std::nullopt, that means the ACL check passed and the
      62              : ///   read should proceed.
      63         4417 : std::optional<CHIP_ERROR> ValidateReadAttributeACL(DataModel::Provider * dataModel, const SubjectDescriptor & subjectDescriptor,
      64              :                                                    const ConcreteReadAttributePath & path)
      65              : {
      66              : 
      67         4417 :     RequestPath requestPath{ .cluster     = path.mClusterId,
      68         4417 :                              .endpoint    = path.mEndpointId,
      69              :                              .requestType = RequestType::kAttributeReadRequest,
      70         4417 :                              .entityId    = path.mAttributeId };
      71              : 
      72         4417 :     DataModel::AttributeFinder finder(dataModel);
      73              : 
      74         4417 :     std::optional<DataModel::AttributeEntry> info = finder.Find(path);
      75              : 
      76              :     // If the attribute exists, we know whether it is readable (readPrivilege has value)
      77              :     // and what the required access privilege is. However for attributes missing from the metatada
      78              :     // (e.g. global attributes) or completely missing attributes we do not actually know of a required
      79              :     // privilege and default to kView (this is correct for global attributes and a reasonable check
      80              :     // for others)
      81         4417 :     Privilege requiredPrivilege = Privilege::kView;
      82         4417 :     if (info.has_value() && info->readPrivilege.has_value())
      83              :     {
      84              :         // attribute exists and is readable, set the correct read privilege
      85         4303 :         requiredPrivilege = *info->readPrivilege;
      86              :     }
      87              : 
      88         4417 :     CHIP_ERROR err = GetAccessControl().Check(subjectDescriptor, requestPath, requiredPrivilege);
      89         4417 :     if (err == CHIP_NO_ERROR)
      90              :     {
      91         4417 :         if (IsSupportedGlobalAttributeNotInMetadata(path.mAttributeId))
      92              :         {
      93              :             // Global attributes passing a kView check is ok
      94         1190 :             return std::nullopt;
      95              :         }
      96              : 
      97              :         // We want to return "success" (i.e. nulopt) IF AND ONLY IF the attribute exists and is readable (has read privilege).
      98              :         // Since the Access control check above may have passed with kView, we do another check here:
      99              :         //    - Attribute exists (info has value)
     100              :         //    - Attribute is readable (readProvilege has value) and not "write only"
     101              :         // If the attribute exists and is not readable, we will return UnsupportedRead (spec 8.4.3.2: "Else if the path indicates
     102              :         // attribute data that is not readable, an AttributeStatusIB SHALL be generated with the UNSUPPORTED_READ Status Code.")
     103              :         //
     104              :         // TODO:: https://github.com/CHIP-Specifications/connectedhomeip-spec/pull/9024 requires interleaved ordering that
     105              :         //        is NOT implemented here. Spec requires:
     106              :         //           - check cluster access check (done here as kView at least)
     107              :         //           - unsupported endpoint/cluster/attribute check (NOT done here) when the attribute is missing.
     108              :         //             this SHOULD be done here when info does not have a value. This was not done as a first pass to
     109              :         //             minimize amount of delta in the initial PR.
     110              :         //           - "write-only" attributes should return UNSUPPORTED_READ (this is done here)
     111         3227 :         if (info.has_value() && !info->readPrivilege.has_value())
     112              :         {
     113            0 :             return CHIP_IM_GLOBAL_STATUS(UnsupportedRead);
     114              :         }
     115              : 
     116         3227 :         return std::nullopt;
     117              :     }
     118            0 :     VerifyOrReturnError((err == CHIP_ERROR_ACCESS_DENIED) || (err == CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL), err);
     119              : 
     120              :     // Implementation of 8.4.3.2 of the spec for path expansion
     121            0 :     if (path.mExpanded)
     122              :     {
     123            0 :         return CHIP_NO_ERROR;
     124              :     }
     125              : 
     126              :     // access denied and access restricted have specific codes for IM
     127            0 :     return err == CHIP_ERROR_ACCESS_DENIED ? CHIP_IM_GLOBAL_STATUS(UnsupportedAccess) : CHIP_IM_GLOBAL_STATUS(AccessRestricted);
     128         4417 : }
     129              : 
     130         4417 : DataModel::ActionReturnStatus RetrieveClusterData(DataModel::Provider * dataModel, const SubjectDescriptor & subjectDescriptor,
     131              :                                                   bool isFabricFiltered, AttributeReportIBs::Builder & reportBuilder,
     132              :                                                   const ConcreteReadAttributePath & path, AttributeEncodeState * encoderState)
     133              : {
     134         4417 :     ChipLogDetail(DataManagement, "<RE:Run> Cluster %" PRIx32 ", Attribute %" PRIx32 " is dirty", path.mClusterId,
     135              :                   path.mAttributeId);
     136         4417 :     DataModelCallbacks::GetInstance()->AttributeOperation(DataModelCallbacks::OperationType::Read,
     137              :                                                           DataModelCallbacks::OperationOrder::Pre, path);
     138              : 
     139         4417 :     DataModel::ReadAttributeRequest readRequest;
     140              : 
     141         4417 :     readRequest.readFlags.Set(DataModel::ReadFlags::kFabricFiltered, isFabricFiltered);
     142         4417 :     readRequest.subjectDescriptor = &subjectDescriptor;
     143         4417 :     readRequest.path              = path;
     144              : 
     145         4417 :     DataModel::ServerClusterFinder serverClusterFinder(dataModel);
     146              : 
     147         4417 :     DataVersion version = 0;
     148         4417 :     if (auto clusterInfo = serverClusterFinder.Find(path); clusterInfo.has_value())
     149              :     {
     150         4321 :         version = clusterInfo->dataVersion;
     151              :     }
     152              :     else
     153              :     {
     154           96 :         ChipLogError(DataManagement, "Read request on unknown cluster - no data version available");
     155              :     }
     156              : 
     157         4417 :     TLV::TLVWriter checkpoint;
     158         4417 :     reportBuilder.Checkpoint(checkpoint);
     159              : 
     160         4417 :     DataModel::ActionReturnStatus status(CHIP_NO_ERROR);
     161         4417 :     AttributeValueEncoder attributeValueEncoder(reportBuilder, subjectDescriptor, path, version, isFabricFiltered, encoderState);
     162              : 
     163              :     // TODO: we explicitly DO NOT validate that path is a valid cluster path (even more, above serverClusterFinder
     164              :     //       explicitly ignores that case). This means that global attribute reads as well as ReadAttribute
     165              :     //       can be passed invalid paths when an invalid Read is detected and must handle them.
     166              :     //
     167              :     //       See https://github.com/project-chip/connectedhomeip/issues/37410
     168              : 
     169         4417 :     if (auto access_status = ValidateReadAttributeACL(dataModel, subjectDescriptor, path); access_status.has_value())
     170              :     {
     171            0 :         status = *access_status;
     172              :     }
     173         4417 :     else if (IsSupportedGlobalAttributeNotInMetadata(readRequest.path.mAttributeId))
     174              :     {
     175              :         // Global attributes are NOT directly handled by data model providers, instead
     176              :         // the are routed through metadata.
     177         1190 :         status = ReadGlobalAttributeFromMetadata(dataModel, readRequest.path, attributeValueEncoder);
     178              :     }
     179              :     else
     180              :     {
     181         3227 :         status = dataModel->ReadAttribute(readRequest, attributeValueEncoder);
     182              :     }
     183              : 
     184         4417 :     if (status.IsSuccess())
     185              :     {
     186              :         // TODO: this callback being only executed on success is awkward. The Write callback is always done
     187              :         //       for both read and write.
     188              :         //
     189              :         //       For now this preserves existing/previous code logic, however we should consider to ALWAYS
     190              :         //       call this.
     191         4022 :         DataModelCallbacks::GetInstance()->AttributeOperation(DataModelCallbacks::OperationType::Read,
     192              :                                                               DataModelCallbacks::OperationOrder::Post, path);
     193         4022 :         return status;
     194              :     }
     195              : 
     196              :     // Encoder state is relevant for errors in case they are retryable.
     197              :     //
     198              :     // Generally only out of space encoding errors would be retryable, however we save the state
     199              :     // for all errors in case this is information that is useful (retry or error position).
     200          395 :     if (encoderState != nullptr)
     201              :     {
     202          395 :         *encoderState = attributeValueEncoder.GetState();
     203              :     }
     204              : 
     205              : #if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING
     206              :     // Out of space errors may be chunked data, reporting those cases would be very confusing
     207              :     // as they are not fully errors. Report only others (which presumably are not recoverable
     208              :     // and will be sent to the client as well).
     209          395 :     if (!status.IsOutOfSpaceEncodingResponse())
     210              :     {
     211            0 :         DataModel::ActionReturnStatus::StringStorage storage;
     212            0 :         ChipLogError(DataManagement, "Failed to read attribute: %s", status.c_str(storage));
     213              :     }
     214              : #endif
     215          395 :     return status;
     216         4417 : }
     217              : 
     218          109 : bool IsClusterDataVersionEqualTo(DataModel::Provider * dataModel, const ConcreteClusterPath & path, DataVersion dataVersion)
     219              : {
     220          109 :     DataModel::ServerClusterFinder serverClusterFinder(dataModel);
     221          109 :     auto info = serverClusterFinder.Find(path);
     222              : 
     223          109 :     return info.has_value() && (info->dataVersion == dataVersion);
     224          109 : }
     225              : 
     226              : } // namespace
     227              : 
     228           61 : Engine::Engine(InteractionModelEngine * apImEngine) : mpImEngine(apImEngine) {}
     229              : 
     230          406 : CHIP_ERROR Engine::Init(EventManagement * apEventManagement)
     231              : {
     232          406 :     VerifyOrReturnError(apEventManagement != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
     233          406 :     mNumReportsInFlight = 0;
     234          406 :     mCurReadHandlerIdx  = 0;
     235          406 :     mpEventManagement   = apEventManagement;
     236              : 
     237          406 :     return CHIP_NO_ERROR;
     238              : }
     239              : 
     240          285 : void Engine::Shutdown()
     241              : {
     242              :     // Flush out the event buffer synchronously
     243          285 :     ScheduleUrgentEventDeliverySync();
     244              : 
     245          285 :     mNumReportsInFlight = 0;
     246          285 :     mCurReadHandlerIdx  = 0;
     247          285 :     mGlobalDirtySet.ReleaseAll();
     248          285 : }
     249              : 
     250         4214 : bool Engine::IsClusterDataVersionMatch(const SingleLinkedListNode<DataVersionFilter> * aDataVersionFilterList,
     251              :                                        const ConcreteReadAttributePath & aPath)
     252              : {
     253         4214 :     bool existPathMatch       = false;
     254         4214 :     bool existVersionMismatch = false;
     255        26078 :     for (auto filter = aDataVersionFilterList; filter != nullptr; filter = filter->mpNext)
     256              :     {
     257        21864 :         if (aPath.mEndpointId == filter->mValue.mEndpointId && aPath.mClusterId == filter->mValue.mClusterId)
     258              :         {
     259          109 :             existPathMatch = true;
     260              : 
     261          109 :             if (!IsClusterDataVersionEqualTo(mpImEngine->GetDataModelProvider(),
     262          218 :                                              ConcreteClusterPath(filter->mValue.mEndpointId, filter->mValue.mClusterId),
     263          109 :                                              filter->mValue.mDataVersion.Value()))
     264              :             {
     265           79 :                 existVersionMismatch = true;
     266              :             }
     267              :         }
     268              :     }
     269         4214 :     return existPathMatch && !existVersionMismatch;
     270              : }
     271              : 
     272         2488 : static bool IsOutOfWriterSpaceError(CHIP_ERROR err)
     273              : {
     274         2488 :     return err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL;
     275              : }
     276              : 
     277         1973 : CHIP_ERROR Engine::BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Builder & aReportDataBuilder,
     278              :                                                            ReadHandler * apReadHandler, bool * apHasMoreChunks,
     279              :                                                            bool * apHasEncodedData)
     280              : {
     281         1973 :     CHIP_ERROR err            = CHIP_NO_ERROR;
     282         1973 :     bool attributeDataWritten = false;
     283         1973 :     bool hasMoreChunks        = true;
     284         1973 :     TLV::TLVWriter backup;
     285         1973 :     const uint32_t kReservedSizeEndOfReportIBs = 1;
     286         1973 :     bool reservedEndOfReportIBs                = false;
     287              : 
     288         1973 :     aReportDataBuilder.Checkpoint(backup);
     289              : 
     290         1973 :     AttributeReportIBs::Builder & attributeReportIBs = aReportDataBuilder.CreateAttributeReportIBs();
     291         1973 :     size_t emptyReportDataLength                     = 0;
     292              : 
     293         1973 :     SuccessOrExit(err = aReportDataBuilder.GetError());
     294              : 
     295         1973 :     emptyReportDataLength = attributeReportIBs.GetWriter()->GetLengthWritten();
     296              :     //
     297              :     // Reserve enough space for closing out the Report IB list
     298              :     //
     299         1973 :     SuccessOrExit(err = attributeReportIBs.GetWriter()->ReserveBuffer(kReservedSizeEndOfReportIBs));
     300         1973 :     reservedEndOfReportIBs = true;
     301              : 
     302              :     {
     303              :         // TODO: Figure out how AttributePathExpandIterator should handle read
     304              :         // vs write paths.
     305         1973 :         ConcreteAttributePath readPath;
     306              : 
     307         1973 :         ChipLogDetail(DataManagement,
     308              :                       "Building Reports for ReadHandler with LastReportGeneration = 0x" ChipLogFormatX64
     309              :                       " DirtyGeneration = 0x" ChipLogFormatX64,
     310              :                       ChipLogValueX64(apReadHandler->mPreviousReportsBeginGeneration),
     311              :                       ChipLogValueX64(apReadHandler->mDirtyGeneration));
     312              : 
     313              :         // This ReadHandler is not generating reports, so we reset the iterator for a clean start.
     314         1973 :         if (!apReadHandler->IsReporting())
     315              :         {
     316         1162 :             apReadHandler->ResetPathIterator();
     317              :         }
     318              : 
     319              : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
     320         1973 :         uint32_t attributesRead = 0;
     321              : #endif
     322              : 
     323              :         // For each path included in the interested path of the read handler...
     324         1973 :         for (RollbackAttributePathExpandIterator iterator(mpImEngine->GetDataModelProvider(),
     325         1973 :                                                           apReadHandler->AttributeIterationPosition());
     326         6449 :              iterator.Next(readPath); iterator.MarkCompleted())
     327              :         {
     328         4891 :             if (!apReadHandler->IsPriming())
     329              :             {
     330          677 :                 bool concretePathDirty = false;
     331              :                 // TODO: Optimize this implementation by making the iterator only emit intersected paths.
     332          677 :                 mGlobalDirtySet.ForEachActiveObject([&](auto * dirtyPath) {
     333          815 :                     if (dirtyPath->IsAttributePathSupersetOf(readPath))
     334              :                     {
     335              :                         // We don't need to worry about paths that were already marked dirty before the last time this read handler
     336              :                         // started a report that it completed: those paths already got reported.
     337          252 :                         if (dirtyPath->mGeneration > apReadHandler->mPreviousReportsBeginGeneration)
     338              :                         {
     339          249 :                             concretePathDirty = true;
     340          249 :                             return Loop::Break;
     341              :                         }
     342              :                     }
     343          566 :                     return Loop::Continue;
     344              :                 });
     345              : 
     346          677 :                 if (!concretePathDirty)
     347              :                 {
     348              :                     // This attribute is not dirty, we just skip this one.
     349          428 :                     continue;
     350              :                 }
     351              :             }
     352              :             else
     353              :             {
     354         4214 :                 if (IsClusterDataVersionMatch(apReadHandler->GetDataVersionFilterList(), readPath))
     355              :                 {
     356           26 :                     continue;
     357              :                 }
     358              :             }
     359              : 
     360              : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
     361         4437 :             attributesRead++;
     362         4437 :             if (attributesRead > mMaxAttributesPerChunk)
     363              :             {
     364          415 :                 ExitNow(err = CHIP_ERROR_BUFFER_TOO_SMALL);
     365              :             }
     366              : #endif
     367              : 
     368              :             // If we are processing a read request, or the initial report of a subscription, just regard all paths as dirty
     369              :             // paths.
     370         4417 :             TLV::TLVWriter attributeBackup;
     371         4417 :             attributeReportIBs.Checkpoint(attributeBackup);
     372         4417 :             ConcreteReadAttributePath pathForRetrieval(readPath);
     373              :             // Load the saved state from previous encoding session for chunking of one single attribute (list chunking).
     374         4417 :             AttributeEncodeState encodeState = apReadHandler->GetAttributeEncodeState();
     375              :             DataModel::ActionReturnStatus status =
     376        13251 :                 RetrieveClusterData(mpImEngine->GetDataModelProvider(), apReadHandler->GetSubjectDescriptor(),
     377         4417 :                                     apReadHandler->IsFabricFiltered(), attributeReportIBs, pathForRetrieval, &encodeState);
     378         4417 :             if (status.IsError())
     379              :             {
     380              :                 // Operation error set, since this will affect early return or override on status encoding
     381              :                 // it will also be used for error reporting below.
     382          395 :                 err = status.GetUnderlyingError();
     383              : 
     384              :                 // If error is not an "out of writer space" error, rollback and encode status.
     385              :                 // Otherwise, if partial data allowed, save the encode state.
     386              :                 // Otherwise roll back. If we have already encoded some chunks, we are done; otherwise encode status.
     387              : 
     388          395 :                 if (encodeState.AllowPartialData() && status.IsOutOfSpaceEncodingResponse())
     389              :                 {
     390          256 :                     ChipLogDetail(DataManagement,
     391              :                                   "List does not fit in packet, chunk between list items for clusterId: " ChipLogFormatMEI
     392              :                                   ", attributeId: " ChipLogFormatMEI,
     393              :                                   ChipLogValueMEI(pathForRetrieval.mClusterId), ChipLogValueMEI(pathForRetrieval.mAttributeId));
     394              :                     // Encoding is aborted but partial data is allowed, then we don't rollback and save the state for next chunk.
     395              :                     // The expectation is that RetrieveClusterData has already reset attributeReportIBs to a good state (rolled
     396              :                     // back any partially-written AttributeReportIB instances, reset its error status).  Since AllowPartialData()
     397              :                     // is true, we may not have encoded a complete attribute value, but we did, if we encoded anything, encode a
     398              :                     // set of complete AttributeReportIB instances that represent part of the attribute value.
     399          256 :                     apReadHandler->SetAttributeEncodeState(encodeState);
     400              :                 }
     401              :                 else
     402              :                 {
     403              :                     // We met a error during writing reports, one common case is we are running out of buffer, rollback the
     404              :                     // attributeReportIB to avoid any partial data.
     405          139 :                     attributeReportIBs.Rollback(attributeBackup);
     406          139 :                     apReadHandler->SetAttributeEncodeState(AttributeEncodeState());
     407              : 
     408          139 :                     if (!status.IsOutOfSpaceEncodingResponse())
     409              :                     {
     410            0 :                         ChipLogError(DataManagement,
     411              :                                      "Fail to retrieve data, roll back and encode status on clusterId: " ChipLogFormatMEI
     412              :                                      ", attributeId: " ChipLogFormatMEI "err = %" CHIP_ERROR_FORMAT,
     413              :                                      ChipLogValueMEI(pathForRetrieval.mClusterId), ChipLogValueMEI(pathForRetrieval.mAttributeId),
     414              :                                      err.Format());
     415              :                         // Try to encode our error as a status response.
     416            0 :                         err = attributeReportIBs.EncodeAttributeStatus(pathForRetrieval, StatusIB(status.GetStatusCode()));
     417            0 :                         if (err != CHIP_NO_ERROR)
     418              :                         {
     419              :                             // OK, just roll back again and give up; if we still ran out of space we
     420              :                             // will send this status response in the next chunk.
     421            0 :                             attributeReportIBs.Rollback(attributeBackup);
     422              :                         }
     423              :                     }
     424              :                     else
     425              :                     {
     426          139 :                         ChipLogDetail(DataManagement,
     427              :                                       "Next attribute value does not fit in packet, roll back on clusterId: " ChipLogFormatMEI
     428              :                                       ", attributeId: " ChipLogFormatMEI ", err = %" CHIP_ERROR_FORMAT,
     429              :                                       ChipLogValueMEI(pathForRetrieval.mClusterId), ChipLogValueMEI(pathForRetrieval.mAttributeId),
     430              :                                       err.Format());
     431              :                     }
     432              :                 }
     433              :             }
     434         4417 :             SuccessOrExit(err);
     435              :             // Successfully encoded the attribute, clear the internal state.
     436         4022 :             apReadHandler->SetAttributeEncodeState(AttributeEncodeState());
     437         1973 :         }
     438              : 
     439              :         // We just visited all paths interested by this read handler and did not abort in the middle of iteration, there are no more
     440              :         // chunks for this report.
     441         1558 :         hasMoreChunks = false;
     442              :     }
     443         1973 : exit:
     444         1973 :     if (attributeReportIBs.GetWriter()->GetLengthWritten() != emptyReportDataLength)
     445              :     {
     446              :         // We may encounter BUFFER_TOO_SMALL with nothing actually written for the case of list chunking, so we check if we have
     447              :         // actually
     448         1298 :         attributeDataWritten = true;
     449              :     }
     450              : 
     451         1973 :     if (apHasEncodedData != nullptr)
     452              :     {
     453         1973 :         *apHasEncodedData = attributeDataWritten;
     454              :     }
     455              :     //
     456              :     // Running out of space is an error that we're expected to handle - the incompletely written DataIB has already been rolled back
     457              :     // earlier to ensure only whole and complete DataIBs are present in the stream.
     458              :     //
     459              :     // We can safely clear out the error so that the rest of the machinery to close out the reports, etc. will function correctly.
     460              :     // These are are guaranteed to not fail since we've already reserved memory for the remaining 'close out' TLV operations in this
     461              :     // function and its callers.
     462              :     //
     463         1973 :     if (IsOutOfWriterSpaceError(err) && reservedEndOfReportIBs)
     464              :     {
     465          415 :         ChipLogDetail(DataManagement, "<RE:Run> We cannot put more chunks into this report. Enable chunking.");
     466          415 :         err = CHIP_NO_ERROR;
     467              :     }
     468              : 
     469              :     //
     470              :     // Only close out the report if we haven't hit an error yet so far.
     471              :     //
     472         1973 :     if (err == CHIP_NO_ERROR)
     473              :     {
     474         1973 :         attributeReportIBs.GetWriter()->UnreserveBuffer(kReservedSizeEndOfReportIBs);
     475              : 
     476         1973 :         err = attributeReportIBs.EndOfAttributeReportIBs();
     477              : 
     478              :         //
     479              :         // We reserved space for this earlier - consequently, the call to end the ReportIBs should
     480              :         // never fail, so assert if we do since that's a logic bug.
     481              :         //
     482         1973 :         VerifyOrDie(err == CHIP_NO_ERROR);
     483              :     }
     484              : 
     485              :     //
     486              :     // Rollback the the entire ReportIB array if we never wrote any attributes
     487              :     // AND never hit an error.
     488              :     //
     489         1973 :     if (!attributeDataWritten && err == CHIP_NO_ERROR)
     490              :     {
     491          675 :         aReportDataBuilder.Rollback(backup);
     492              :     }
     493              : 
     494              :     // hasMoreChunks + no data encoded is a flag that we have encountered some trouble when processing the attribute.
     495              :     // BuildAndSendSingleReportData will abort the read transaction if we encoded no attribute and no events but hasMoreChunks is
     496              :     // set.
     497         1973 :     if (apHasMoreChunks != nullptr)
     498              :     {
     499         1973 :         *apHasMoreChunks = hasMoreChunks;
     500              :     }
     501              : 
     502         1973 :     return err;
     503              : }
     504              : 
     505          863 : CHIP_ERROR Engine::CheckAccessDeniedEventPaths(TLV::TLVWriter & aWriter, bool & aHasEncodedData, ReadHandler * apReadHandler)
     506              : {
     507              :     using Protocols::InteractionModel::Status;
     508              : 
     509          863 :     CHIP_ERROR err = CHIP_NO_ERROR;
     510         1756 :     for (auto current = apReadHandler->mpEventPathList; current != nullptr;)
     511              :     {
     512          893 :         if (current->mValue.IsWildcardPath())
     513              :         {
     514          837 :             current = current->mpNext;
     515          837 :             continue;
     516              :         }
     517              : 
     518           56 :         ConcreteEventPath path(current->mValue.mEndpointId, current->mValue.mClusterId, current->mValue.mEventId);
     519              : 
     520              :         // A event path is valid only if the cluster is valid
     521           56 :         Status status = DataModel::ValidateClusterPath(mpImEngine->GetDataModelProvider(), path, Status::Success);
     522              : 
     523           56 :         if (status != Status::Success)
     524              :         {
     525            2 :             TLV::TLVWriter checkpoint = aWriter;
     526            2 :             err                       = EventReportIB::ConstructEventStatusIB(aWriter, path, StatusIB(status));
     527            2 :             if (err != CHIP_NO_ERROR)
     528              :             {
     529            0 :                 aWriter = checkpoint;
     530            0 :                 break;
     531              :             }
     532            2 :             aHasEncodedData = true;
     533              :         }
     534              : 
     535           56 :         RequestPath requestPath{ .cluster     = current->mValue.mClusterId,
     536           56 :                                  .endpoint    = current->mValue.mEndpointId,
     537              :                                  .requestType = RequestType::kEventReadRequest,
     538           56 :                                  .entityId    = current->mValue.mEventId };
     539           56 :         Privilege requestPrivilege = RequiredPrivilege::ForReadEvent(path);
     540              : 
     541           56 :         err = GetAccessControl().Check(apReadHandler->GetSubjectDescriptor(), requestPath, requestPrivilege);
     542           56 :         if ((err != CHIP_ERROR_ACCESS_DENIED) && (err != CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL))
     543              :         {
     544           54 :             ReturnErrorOnFailure(err);
     545              :         }
     546              :         else
     547              :         {
     548            2 :             TLV::TLVWriter checkpoint = aWriter;
     549            2 :             err                       = EventReportIB::ConstructEventStatusIB(aWriter, path,
     550            2 :                                                         err == CHIP_ERROR_ACCESS_DENIED ? StatusIB(Status::UnsupportedAccess)
     551              :                                                                                                               : StatusIB(Status::AccessRestricted));
     552              : 
     553            2 :             if (err != CHIP_NO_ERROR)
     554              :             {
     555            0 :                 aWriter = checkpoint;
     556            0 :                 break;
     557              :             }
     558            2 :             aHasEncodedData = true;
     559            2 :             ChipLogDetail(InteractionModel, "Access to event (%u, " ChipLogFormatMEI ", " ChipLogFormatMEI ") denied by %s",
     560              :                           current->mValue.mEndpointId, ChipLogValueMEI(current->mValue.mClusterId),
     561              :                           ChipLogValueMEI(current->mValue.mEventId), err == CHIP_ERROR_ACCESS_DENIED ? "ACL" : "ARL");
     562              :         }
     563           56 :         current = current->mpNext;
     564              :     }
     565              : 
     566          863 :     return err;
     567              : }
     568              : 
     569         1973 : CHIP_ERROR Engine::BuildSingleReportDataEventReports(ReportDataMessage::Builder & aReportDataBuilder, ReadHandler * apReadHandler,
     570              :                                                      bool aBufferIsUsed, bool * apHasMoreChunks, bool * apHasEncodedData)
     571              : {
     572         1973 :     CHIP_ERROR err        = CHIP_NO_ERROR;
     573         1973 :     size_t eventCount     = 0;
     574         1973 :     bool hasEncodedStatus = false;
     575         1973 :     TLV::TLVWriter backup;
     576         1973 :     bool eventClean    = true;
     577         1973 :     auto & eventMin    = apReadHandler->GetEventMin();
     578         1973 :     bool hasMoreChunks = false;
     579              : 
     580         1973 :     aReportDataBuilder.Checkpoint(backup);
     581              : 
     582         1973 :     VerifyOrExit(apReadHandler->GetEventPathList() != nullptr, );
     583              : 
     584              :     // If the mpEventManagement is not valid or has not been initialized,
     585              :     // skip the rest of processing
     586          890 :     VerifyOrExit(mpEventManagement != nullptr && mpEventManagement->IsValid(),
     587              :                  ChipLogError(DataManagement, "EventManagement has not yet initialized"));
     588              : 
     589          887 :     eventClean = apReadHandler->CheckEventClean(*mpEventManagement);
     590              : 
     591              :     // proceed only if there are new events.
     592          887 :     if (eventClean)
     593              :     {
     594           24 :         ExitNow(); // Read clean, move along
     595              :     }
     596              : 
     597              :     {
     598              :         // Just like what we do in BuildSingleReportDataAttributeReportIBs(), we need to reserve one byte for end of container tag
     599              :         // when encoding events to ensure we can close the container successfully.
     600          863 :         const uint32_t kReservedSizeEndOfReportIBs = 1;
     601          863 :         EventReportIBs::Builder & eventReportIBs   = aReportDataBuilder.CreateEventReports();
     602          863 :         SuccessOrExit(err = aReportDataBuilder.GetError());
     603          863 :         VerifyOrExit(eventReportIBs.GetWriter() != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
     604          863 :         SuccessOrExit(err = eventReportIBs.GetWriter()->ReserveBuffer(kReservedSizeEndOfReportIBs));
     605              : 
     606          863 :         err = CheckAccessDeniedEventPaths(*(eventReportIBs.GetWriter()), hasEncodedStatus, apReadHandler);
     607          863 :         SuccessOrExit(err);
     608              : 
     609          863 :         err = mpEventManagement->FetchEventsSince(*(eventReportIBs.GetWriter()), apReadHandler->GetEventPathList(), eventMin,
     610          863 :                                                   eventCount, apReadHandler->GetSubjectDescriptor());
     611              : 
     612          863 :         if ((err == CHIP_END_OF_TLV) || (err == CHIP_ERROR_TLV_UNDERRUN) || (err == CHIP_NO_ERROR))
     613              :         {
     614          348 :             err           = CHIP_NO_ERROR;
     615          348 :             hasMoreChunks = false;
     616              :         }
     617          515 :         else if (IsOutOfWriterSpaceError(err))
     618              :         {
     619              :             // when first cluster event is too big to fit in the packet, ignore that cluster event.
     620              :             // However, we may have encoded some attributes before, we don't skip it in that case.
     621          515 :             if (eventCount == 0)
     622              :             {
     623          206 :                 if (!aBufferIsUsed)
     624              :                 {
     625            0 :                     eventMin++;
     626              :                 }
     627          206 :                 ChipLogDetail(DataManagement, "<RE:Run> first cluster event is too big so that it fails to fit in the packet!");
     628          206 :                 err = CHIP_NO_ERROR;
     629              :             }
     630              :             else
     631              :             {
     632              :                 // `FetchEventsSince` has filled the available space
     633              :                 // within the allowed buffer before it fit all the
     634              :                 // available events.  This is an expected condition,
     635              :                 // so we do not propagate the error to higher levels;
     636              :                 // instead, we terminate the event processing for now
     637          309 :                 err = CHIP_NO_ERROR;
     638              :             }
     639          515 :             hasMoreChunks = true;
     640              :         }
     641              :         else
     642              :         {
     643              :             // All other errors are propagated to higher level.
     644              :             // Exiting here and returning an error will lead to
     645              :             // abandoning subscription.
     646            0 :             ExitNow();
     647              :         }
     648              : 
     649          863 :         SuccessOrExit(err = eventReportIBs.GetWriter()->UnreserveBuffer(kReservedSizeEndOfReportIBs));
     650          863 :         SuccessOrExit(err = eventReportIBs.EndOfEventReports());
     651              :     }
     652          863 :     ChipLogDetail(DataManagement, "Fetched %u events", static_cast<unsigned int>(eventCount));
     653              : 
     654            0 : exit:
     655         1973 :     if (apHasEncodedData != nullptr)
     656              :     {
     657         1973 :         *apHasEncodedData = hasEncodedStatus || (eventCount != 0);
     658              :     }
     659              : 
     660              :     // Maybe encoding the attributes has already used up all space.
     661         1973 :     if ((err == CHIP_NO_ERROR || IsOutOfWriterSpaceError(err)) && !(hasEncodedStatus || (eventCount != 0)))
     662              :     {
     663         1333 :         aReportDataBuilder.Rollback(backup);
     664         1333 :         err = CHIP_NO_ERROR;
     665              :     }
     666              : 
     667              :     // hasMoreChunks + no data encoded is a flag that we have encountered some trouble when processing the attribute.
     668              :     // BuildAndSendSingleReportData will abort the read transaction if we encoded no attribute and no events but hasMoreChunks is
     669              :     // set.
     670         1973 :     if (apHasMoreChunks != nullptr)
     671              :     {
     672         1973 :         *apHasMoreChunks = hasMoreChunks;
     673              :     }
     674         1973 :     return err;
     675              : }
     676              : 
     677         1973 : CHIP_ERROR Engine::BuildAndSendSingleReportData(ReadHandler * apReadHandler)
     678              : {
     679         1973 :     CHIP_ERROR err = CHIP_NO_ERROR;
     680         1973 :     System::PacketBufferTLVWriter reportDataWriter;
     681         1973 :     ReportDataMessage::Builder reportDataBuilder;
     682         1973 :     System::PacketBufferHandle bufHandle = nullptr;
     683         1973 :     uint16_t reservedSize                = 0;
     684         1973 :     bool hasMoreChunks                   = false;
     685         1973 :     bool needCloseReadHandler            = false;
     686         1973 :     size_t reportBufferMaxSize           = 0;
     687              : 
     688              :     // Reserved size for the MoreChunks boolean flag, which takes up 1 byte for the control tag and 1 byte for the context tag.
     689         1973 :     const uint32_t kReservedSizeForMoreChunksFlag = 1 + 1;
     690              : 
     691              :     // Reserved size for the uint8_t InteractionModelRevision flag, which takes up 1 byte for the control tag and 1 byte for the
     692              :     // context tag, 1 byte for value
     693         1973 :     const uint32_t kReservedSizeForIMRevision = 1 + 1 + 1;
     694              : 
     695              :     // Reserved size for the end of report message, which is an end-of-container (i.e 1 byte for the control tag).
     696         1973 :     const uint32_t kReservedSizeForEndOfReportMessage = 1;
     697              : 
     698              :     // Reserved size for an empty EventReportIBs, so we can at least check if there are any events need to be reported.
     699         1973 :     const uint32_t kReservedSizeForEventReportIBs = 3; // type, tag, end of container
     700              : 
     701         1973 :     VerifyOrExit(apReadHandler != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
     702         1973 :     VerifyOrExit(apReadHandler->GetSession() != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
     703              : 
     704         1973 :     reportBufferMaxSize = apReadHandler->GetReportBufferMaxSize();
     705              : 
     706         1973 :     bufHandle = System::PacketBufferHandle::New(reportBufferMaxSize);
     707         1973 :     VerifyOrExit(!bufHandle.IsNull(), err = CHIP_ERROR_NO_MEMORY);
     708              : 
     709         1973 :     if (bufHandle->AvailableDataLength() > reportBufferMaxSize)
     710              :     {
     711            0 :         reservedSize = static_cast<uint16_t>(bufHandle->AvailableDataLength() - reportBufferMaxSize);
     712              :     }
     713              : 
     714         1973 :     reportDataWriter.Init(std::move(bufHandle));
     715              : 
     716              : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
     717         1973 :     reportDataWriter.ReserveBuffer(mReservedSize);
     718              : #endif
     719              : 
     720              :     // Always limit the size of the generated packet to fit within the max size returned by the ReadHandler regardless
     721              :     // of the available buffer capacity.
     722              :     // Also, we need to reserve some extra space for the MIC field.
     723         1973 :     reportDataWriter.ReserveBuffer(static_cast<uint32_t>(reservedSize + Crypto::CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES));
     724              : 
     725              :     // Create a report data.
     726         1973 :     err = reportDataBuilder.Init(&reportDataWriter);
     727         1973 :     SuccessOrExit(err);
     728              : 
     729         1973 :     if (apReadHandler->IsType(ReadHandler::InteractionType::Subscribe))
     730              :     {
     731              : #if CHIP_CONFIG_ENABLE_ICD_SERVER
     732              :         // Notify the ICDManager that we are about to send a subscription report before we prepare the Report payload.
     733              :         // This allows the ICDManager to trigger any necessary updates and have the information in the report about to be sent.
     734              :         app::ICDNotifier::GetInstance().NotifySubscriptionReport();
     735              : #endif // CHIP_CONFIG_ENABLE_ICD_SERVER
     736              : 
     737          431 :         SubscriptionId subscriptionId = 0;
     738          431 :         apReadHandler->GetSubscriptionId(subscriptionId);
     739          431 :         reportDataBuilder.SubscriptionId(subscriptionId);
     740              :     }
     741              : 
     742         1973 :     SuccessOrExit(err = reportDataWriter.ReserveBuffer(kReservedSizeForMoreChunksFlag + kReservedSizeForIMRevision +
     743              :                                                        kReservedSizeForEndOfReportMessage + kReservedSizeForEventReportIBs));
     744              : 
     745              :     {
     746         1973 :         bool hasMoreChunksForAttributes = false;
     747         1973 :         bool hasMoreChunksForEvents     = false;
     748         1973 :         bool hasEncodedAttributes       = false;
     749         1973 :         bool hasEncodedEvents           = false;
     750              : 
     751         1973 :         err = BuildSingleReportDataAttributeReportIBs(reportDataBuilder, apReadHandler, &hasMoreChunksForAttributes,
     752              :                                                       &hasEncodedAttributes);
     753         2004 :         SuccessOrExit(err);
     754         1973 :         SuccessOrExit(err = reportDataWriter.UnreserveBuffer(kReservedSizeForEventReportIBs));
     755         1973 :         err = BuildSingleReportDataEventReports(reportDataBuilder, apReadHandler, hasEncodedAttributes, &hasMoreChunksForEvents,
     756              :                                                 &hasEncodedEvents);
     757         1973 :         SuccessOrExit(err);
     758              : 
     759         1973 :         hasMoreChunks = hasMoreChunksForAttributes || hasMoreChunksForEvents;
     760              : 
     761         1973 :         if (!hasEncodedAttributes && !hasEncodedEvents && hasMoreChunks)
     762              :         {
     763           31 :             ChipLogError(DataManagement,
     764              :                          "No data actually encoded but hasMoreChunks flag is set, close read handler! (attribute too big?)");
     765           31 :             err = apReadHandler->SendStatusReport(Protocols::InteractionModel::Status::ResourceExhausted);
     766           31 :             if (err == CHIP_NO_ERROR)
     767              :             {
     768           31 :                 needCloseReadHandler = true;
     769              :             }
     770           31 :             ExitNow();
     771              :         }
     772              :     }
     773              : 
     774         1942 :     SuccessOrExit(err = reportDataBuilder.GetError());
     775         1942 :     SuccessOrExit(err = reportDataWriter.UnreserveBuffer(kReservedSizeForMoreChunksFlag + kReservedSizeForIMRevision +
     776              :                                                          kReservedSizeForEndOfReportMessage));
     777         1942 :     if (hasMoreChunks)
     778              :     {
     779          865 :         reportDataBuilder.MoreChunkedMessages(true);
     780              :     }
     781         1077 :     else if (apReadHandler->IsType(ReadHandler::InteractionType::Read))
     782              :     {
     783          704 :         reportDataBuilder.SuppressResponse(true);
     784              :     }
     785              : 
     786         1942 :     reportDataBuilder.EndOfReportDataMessage();
     787              : 
     788              :     //
     789              :     // Since we've already reserved space for both the MoreChunked/SuppressResponse flags, as well as
     790              :     // the end-of-container flag for the end of the report, we should never hit an error closing out the message.
     791              :     //
     792         1942 :     VerifyOrDie(reportDataBuilder.GetError() == CHIP_NO_ERROR);
     793              : 
     794         1942 :     err = reportDataWriter.Finalize(&bufHandle);
     795         1942 :     SuccessOrExit(err);
     796              : 
     797         1942 :     ChipLogDetail(DataManagement, "<RE> Sending report (payload has %" PRIu32 " bytes)...", reportDataWriter.GetLengthWritten());
     798         1942 :     err = SendReport(apReadHandler, std::move(bufHandle), hasMoreChunks);
     799         1942 :     VerifyOrExit(err == CHIP_NO_ERROR,
     800              :                  ChipLogError(DataManagement, "<RE> Error sending out report data with %" CHIP_ERROR_FORMAT "!", err.Format()));
     801              : 
     802         1938 :     ChipLogDetail(DataManagement, "<RE> ReportsInFlight = %" PRIu32 " with readHandler %" PRIu32 ", RE has %s", mNumReportsInFlight,
     803              :                   mCurReadHandlerIdx, hasMoreChunks ? "more messages" : "no more messages");
     804              : 
     805            0 : exit:
     806         1973 :     if (err != CHIP_NO_ERROR || (apReadHandler->IsType(ReadHandler::InteractionType::Read) && !hasMoreChunks) ||
     807              :         needCloseReadHandler)
     808              :     {
     809              :         //
     810              :         // In the case of successful report generation and we're on the last chunk of a read, we don't expect
     811              :         // any further activity on this exchange. The EC layer will automatically close our EC, so shutdown the ReadHandler
     812              :         // gracefully.
     813              :         //
     814          737 :         apReadHandler->Close();
     815              :     }
     816              : 
     817         1973 :     return err;
     818         1973 : }
     819              : 
     820         1751 : void Engine::Run(System::Layer * aSystemLayer, void * apAppState)
     821              : {
     822         1751 :     Engine * const pEngine = reinterpret_cast<Engine *>(apAppState);
     823         1751 :     pEngine->mRunScheduled = false;
     824         1751 :     pEngine->Run();
     825         1751 : }
     826              : 
     827         2147 : CHIP_ERROR Engine::ScheduleRun()
     828              : {
     829         2147 :     if (IsRunScheduled())
     830              :     {
     831          396 :         return CHIP_NO_ERROR;
     832              :     }
     833              : 
     834         1751 :     Messaging::ExchangeManager * exchangeManager = mpImEngine->GetExchangeManager();
     835         1751 :     if (exchangeManager == nullptr)
     836              :     {
     837            0 :         return CHIP_ERROR_INCORRECT_STATE;
     838              :     }
     839         1751 :     SessionManager * sessionManager = exchangeManager->GetSessionManager();
     840         1751 :     if (sessionManager == nullptr)
     841              :     {
     842            0 :         return CHIP_ERROR_INCORRECT_STATE;
     843              :     }
     844         1751 :     System::Layer * systemLayer = sessionManager->SystemLayer();
     845         1751 :     if (systemLayer == nullptr)
     846              :     {
     847            0 :         return CHIP_ERROR_INCORRECT_STATE;
     848              :     }
     849         1751 :     ReturnErrorOnFailure(systemLayer->ScheduleWork(Run, this));
     850         1751 :     mRunScheduled = true;
     851         1751 :     return CHIP_NO_ERROR;
     852              : }
     853              : 
     854         2036 : void Engine::Run()
     855              : {
     856         2036 :     uint32_t numReadHandled = 0;
     857              : 
     858              :     // We may be deallocating read handlers as we go.  Track how many we had
     859              :     // initially, so we make sure to go through all of them.
     860         2036 :     size_t initialAllocated = mpImEngine->mReadHandlers.Allocated();
     861         4246 :     while ((mNumReportsInFlight < CHIP_IM_MAX_REPORTS_IN_FLIGHT) && (numReadHandled < initialAllocated))
     862              :     {
     863              :         ReadHandler * readHandler =
     864         2214 :             mpImEngine->ActiveHandlerAt(mCurReadHandlerIdx % (uint32_t) mpImEngine->mReadHandlers.Allocated());
     865         2214 :         VerifyOrDie(readHandler != nullptr);
     866              : 
     867         2214 :         if (readHandler->ShouldReportUnscheduled() || mpImEngine->GetReportScheduler()->IsReportableNow(readHandler))
     868              :         {
     869              : 
     870         1972 :             mRunningReadHandler = readHandler;
     871         1972 :             CHIP_ERROR err      = BuildAndSendSingleReportData(readHandler);
     872         1972 :             mRunningReadHandler = nullptr;
     873         1972 :             if (err != CHIP_NO_ERROR)
     874              :             {
     875            4 :                 return;
     876              :             }
     877              :         }
     878              : 
     879         2210 :         numReadHandled++;
     880              :         // If readHandler removed itself from our list, we also decremented
     881              :         // mCurReadHandlerIdx to account for that removal, so it's safe to
     882              :         // increment here.
     883         2210 :         mCurReadHandlerIdx++;
     884              :     }
     885              : 
     886              :     //
     887              :     // If our tracker has exceeded the bounds of the handler list, reset it back to 0.
     888              :     // This isn't strictly necessary, but does make it easier to debug issues in this code if they
     889              :     // do arise.
     890              :     //
     891         2032 :     if (mCurReadHandlerIdx >= mpImEngine->mReadHandlers.Allocated())
     892              :     {
     893         1975 :         mCurReadHandlerIdx = 0;
     894              :     }
     895              : 
     896         2032 :     bool allReadClean = true;
     897              : 
     898         2032 :     mpImEngine->mReadHandlers.ForEachActiveObject([&allReadClean](ReadHandler * handler) {
     899         2866 :         if (handler->IsDirty())
     900              :         {
     901          867 :             allReadClean = false;
     902          867 :             return Loop::Break;
     903              :         }
     904              : 
     905         1999 :         return Loop::Continue;
     906              :     });
     907              : 
     908         2032 :     if (allReadClean)
     909              :     {
     910         1165 :         ChipLogDetail(DataManagement, "All ReadHandler-s are clean, clear GlobalDirtySet");
     911              : 
     912         1165 :         mGlobalDirtySet.ReleaseAll();
     913              :     }
     914              : }
     915              : 
     916          276 : bool Engine::MergeOverlappedAttributePath(const AttributePathParams & aAttributePath)
     917              : {
     918          276 :     return Loop::Break == mGlobalDirtySet.ForEachActiveObject([&](auto * path) {
     919          214 :         if (path->IsAttributePathSupersetOf(aAttributePath))
     920              :         {
     921          112 :             path->mGeneration = GetDirtySetGeneration();
     922          112 :             return Loop::Break;
     923              :         }
     924          102 :         if (aAttributePath.IsAttributePathSupersetOf(*path))
     925              :         {
     926              :             // TODO: the wildcard input path may be superset of next paths in globalDirtySet, it is fine at this moment, since
     927              :             // when building report, it would use the first path of globalDirtySet to compare against interested paths read clients
     928              :             // want.
     929              :             // It is better to eliminate the duplicate wildcard paths in follow-up
     930            2 :             path->mGeneration  = GetDirtySetGeneration();
     931            2 :             path->mEndpointId  = aAttributePath.mEndpointId;
     932            2 :             path->mClusterId   = aAttributePath.mClusterId;
     933            2 :             path->mListIndex   = aAttributePath.mListIndex;
     934            2 :             path->mAttributeId = aAttributePath.mAttributeId;
     935            2 :             return Loop::Break;
     936              :         }
     937          100 :         return Loop::Continue;
     938          276 :     });
     939              : }
     940              : 
     941            8 : bool Engine::ClearTombPaths()
     942              : {
     943            8 :     bool pathReleased = false;
     944            8 :     mGlobalDirtySet.ForEachActiveObject([&](auto * path) {
     945           64 :         if (path->mGeneration == 0)
     946              :         {
     947           28 :             mGlobalDirtySet.ReleaseObject(path);
     948           28 :             pathReleased = true;
     949              :         }
     950           64 :         return Loop::Continue;
     951              :     });
     952            8 :     return pathReleased;
     953              : }
     954              : 
     955            5 : bool Engine::MergeDirtyPathsUnderSameCluster()
     956              : {
     957            5 :     mGlobalDirtySet.ForEachActiveObject([&](auto * outerPath) {
     958           40 :         if (outerPath->HasWildcardClusterId() || outerPath->mGeneration == 0)
     959              :         {
     960           14 :             return Loop::Continue;
     961              :         }
     962           26 :         mGlobalDirtySet.ForEachActiveObject([&](auto * innerPath) {
     963          208 :             if (innerPath == outerPath)
     964              :             {
     965           26 :                 return Loop::Continue;
     966              :             }
     967              :             // We don't support paths with a wildcard endpoint + a concrete cluster in global dirty set, so we do a simple == check
     968              :             // here.
     969          182 :             if (innerPath->mEndpointId != outerPath->mEndpointId || innerPath->mClusterId != outerPath->mClusterId)
     970              :             {
     971          168 :                 return Loop::Continue;
     972              :             }
     973           14 :             if (innerPath->mGeneration > outerPath->mGeneration)
     974              :             {
     975            0 :                 outerPath->mGeneration = innerPath->mGeneration;
     976              :             }
     977           14 :             outerPath->SetWildcardAttributeId();
     978              : 
     979              :             // The object pool does not allow us to release objects in a nested iteration, mark the path as a tomb by setting its
     980              :             // generation to 0 and then clear it later.
     981           14 :             innerPath->mGeneration = 0;
     982           14 :             return Loop::Continue;
     983              :         });
     984           26 :         return Loop::Continue;
     985              :     });
     986              : 
     987            5 :     return ClearTombPaths();
     988              : }
     989              : 
     990            3 : bool Engine::MergeDirtyPathsUnderSameEndpoint()
     991              : {
     992            3 :     mGlobalDirtySet.ForEachActiveObject([&](auto * outerPath) {
     993           24 :         if (outerPath->HasWildcardEndpointId() || outerPath->mGeneration == 0)
     994              :         {
     995           14 :             return Loop::Continue;
     996              :         }
     997           10 :         mGlobalDirtySet.ForEachActiveObject([&](auto * innerPath) {
     998           80 :             if (innerPath == outerPath)
     999              :             {
    1000           10 :                 return Loop::Continue;
    1001              :             }
    1002           70 :             if (innerPath->mEndpointId != outerPath->mEndpointId)
    1003              :             {
    1004           56 :                 return Loop::Continue;
    1005              :             }
    1006           14 :             if (innerPath->mGeneration > outerPath->mGeneration)
    1007              :             {
    1008            0 :                 outerPath->mGeneration = innerPath->mGeneration;
    1009              :             }
    1010           14 :             outerPath->SetWildcardClusterId();
    1011           14 :             outerPath->SetWildcardAttributeId();
    1012              : 
    1013              :             // The object pool does not allow us to release objects in a nested iteration, mark the path as a tomb by setting its
    1014              :             // generation to 0 and then clear it later.
    1015           14 :             innerPath->mGeneration = 0;
    1016           14 :             return Loop::Continue;
    1017              :         });
    1018           10 :         return Loop::Continue;
    1019              :     });
    1020            3 :     return ClearTombPaths();
    1021              : }
    1022              : 
    1023          189 : CHIP_ERROR Engine::InsertPathIntoDirtySet(const AttributePathParams & aAttributePath)
    1024              : {
    1025          189 :     VerifyOrReturnError(!MergeOverlappedAttributePath(aAttributePath), CHIP_NO_ERROR);
    1026              : 
    1027           82 :     if (mGlobalDirtySet.Exhausted() && !MergeDirtyPathsUnderSameCluster() && !MergeDirtyPathsUnderSameEndpoint())
    1028              :     {
    1029            1 :         ChipLogDetail(DataManagement, "Global dirty set pool exhausted, merge all paths.");
    1030            1 :         mGlobalDirtySet.ReleaseAll();
    1031            1 :         auto object         = mGlobalDirtySet.CreateObject();
    1032            1 :         object->mGeneration = GetDirtySetGeneration();
    1033              :     }
    1034              : 
    1035           82 :     VerifyOrReturnError(!MergeOverlappedAttributePath(aAttributePath), CHIP_NO_ERROR);
    1036           79 :     ChipLogDetail(DataManagement, "Cannot merge the new path into any existing path, create one.");
    1037              : 
    1038           79 :     auto object = mGlobalDirtySet.CreateObject();
    1039           79 :     if (object == nullptr)
    1040              :     {
    1041              :         // This should not happen, this path should be merged into the wildcard endpoint at least.
    1042            0 :         ChipLogError(DataManagement, "mGlobalDirtySet pool full, cannot handle more entries!");
    1043            0 :         return CHIP_ERROR_NO_MEMORY;
    1044              :     }
    1045           79 :     *object             = aAttributePath;
    1046           79 :     object->mGeneration = GetDirtySetGeneration();
    1047              : 
    1048           79 :     return CHIP_NO_ERROR;
    1049              : }
    1050              : 
    1051         2680 : CHIP_ERROR Engine::SetDirty(const AttributePathParams & aAttributePath)
    1052              : {
    1053         2680 :     BumpDirtySetGeneration();
    1054              : 
    1055         2680 :     bool intersectsInterestPath     = false;
    1056         2680 :     DataModel::Provider * dataModel = mpImEngine->GetDataModelProvider();
    1057         2680 :     mpImEngine->mReadHandlers.ForEachActiveObject([&dataModel, &aAttributePath, &intersectsInterestPath](ReadHandler * handler) {
    1058              :         // We call AttributePathIsDirty for both read interactions and subscribe interactions, since we may send inconsistent
    1059              :         // attribute data between two chunks. AttributePathIsDirty will not schedule a new run for read handlers which are
    1060              :         // waiting for a response to the last message chunk for read interactions.
    1061          477 :         if (handler->CanStartReporting() || handler->IsAwaitingReportResponse())
    1062              :         {
    1063          934 :             for (auto object = handler->GetAttributePathList(); object != nullptr; object = object->mpNext)
    1064              :             {
    1065          802 :                 if (object->mValue.Intersects(aAttributePath))
    1066              :                 {
    1067          345 :                     handler->AttributePathIsDirty(dataModel, aAttributePath);
    1068          345 :                     intersectsInterestPath = true;
    1069          345 :                     break;
    1070              :                 }
    1071              :             }
    1072              :         }
    1073              : 
    1074          477 :         return Loop::Continue;
    1075              :     });
    1076              : 
    1077         2680 :     if (!intersectsInterestPath)
    1078              :     {
    1079         2496 :         return CHIP_NO_ERROR;
    1080              :     }
    1081          184 :     ReturnErrorOnFailure(InsertPathIntoDirtySet(aAttributePath));
    1082              : 
    1083          184 :     return CHIP_NO_ERROR;
    1084              : }
    1085              : 
    1086         1942 : CHIP_ERROR Engine::SendReport(ReadHandler * apReadHandler, System::PacketBufferHandle && aPayload, bool aHasMoreChunks)
    1087              : {
    1088         1942 :     CHIP_ERROR err = CHIP_NO_ERROR;
    1089              : 
    1090              :     // We can only have 1 report in flight for any given read - increment and break out.
    1091         1942 :     mNumReportsInFlight++;
    1092         1942 :     err = apReadHandler->SendReportData(std::move(aPayload), aHasMoreChunks);
    1093         1942 :     if (err != CHIP_NO_ERROR)
    1094              :     {
    1095            4 :         --mNumReportsInFlight;
    1096              :     }
    1097         1942 :     return err;
    1098              : }
    1099              : 
    1100         1938 : void Engine::OnReportConfirm()
    1101              : {
    1102         1938 :     VerifyOrDie(mNumReportsInFlight > 0);
    1103              : 
    1104         1938 :     if (mNumReportsInFlight == CHIP_IM_MAX_REPORTS_IN_FLIGHT)
    1105              :     {
    1106              :         // We could have other things waiting to go now that this report is no
    1107              :         // longer in flight.
    1108           61 :         ScheduleRun();
    1109              :     }
    1110         1938 :     mNumReportsInFlight--;
    1111         1938 :     ChipLogDetail(DataManagement, "<RE> OnReportConfirm: NumReports = %" PRIu32, mNumReportsInFlight);
    1112         1938 : }
    1113              : 
    1114           20 : void Engine::GetMinEventLogPosition(uint32_t & aMinLogPosition)
    1115              : {
    1116           20 :     mpImEngine->mReadHandlers.ForEachActiveObject([&aMinLogPosition](ReadHandler * handler) {
    1117           20 :         if (handler->IsType(ReadHandler::InteractionType::Read))
    1118              :         {
    1119            0 :             return Loop::Continue;
    1120              :         }
    1121              : 
    1122           20 :         uint32_t initialWrittenEventsBytes = handler->GetLastWrittenEventsBytes();
    1123           20 :         if (initialWrittenEventsBytes < aMinLogPosition)
    1124              :         {
    1125           20 :             aMinLogPosition = initialWrittenEventsBytes;
    1126              :         }
    1127              : 
    1128           20 :         return Loop::Continue;
    1129              :     });
    1130           20 : }
    1131              : 
    1132           20 : CHIP_ERROR Engine::ScheduleBufferPressureEventDelivery(uint32_t aBytesWritten)
    1133              : {
    1134           20 :     uint32_t minEventLogPosition = aBytesWritten;
    1135           20 :     GetMinEventLogPosition(minEventLogPosition);
    1136           20 :     if (aBytesWritten - minEventLogPosition > CHIP_CONFIG_EVENT_LOGGING_BYTE_THRESHOLD)
    1137              :     {
    1138            0 :         ChipLogDetail(DataManagement, "<RE> Buffer overfilled CHIP_CONFIG_EVENT_LOGGING_BYTE_THRESHOLD %d, schedule engine run",
    1139              :                       CHIP_CONFIG_EVENT_LOGGING_BYTE_THRESHOLD);
    1140            0 :         return ScheduleRun();
    1141              :     }
    1142           20 :     return CHIP_NO_ERROR;
    1143              : }
    1144              : 
    1145          662 : CHIP_ERROR Engine::NewEventGenerated(ConcreteEventPath & aPath, uint32_t aBytesConsumed)
    1146              : {
    1147              :     // If we literally have no read handlers right now that care about any events,
    1148              :     // we don't need to call schedule run for event.
    1149              :     // If schedule run is called, actually we would not delivery events as well.
    1150              :     // Just wanna save one schedule run here
    1151          662 :     if (mpImEngine->mEventPathPool.Allocated() == 0)
    1152              :     {
    1153          630 :         return CHIP_NO_ERROR;
    1154              :     }
    1155              : 
    1156           32 :     bool isUrgentEvent = false;
    1157           32 :     mpImEngine->mReadHandlers.ForEachActiveObject([&aPath, &isUrgentEvent](ReadHandler * handler) {
    1158           40 :         if (handler->IsType(ReadHandler::InteractionType::Read))
    1159              :         {
    1160            0 :             return Loop::Continue;
    1161              :         }
    1162              : 
    1163          104 :         for (auto * interestedPath = handler->GetEventPathList(); interestedPath != nullptr;
    1164           64 :              interestedPath        = interestedPath->mpNext)
    1165              :         {
    1166           76 :             if (interestedPath->mValue.IsEventPathSupersetOf(aPath) && interestedPath->mValue.mIsUrgentEvent)
    1167              :             {
    1168           12 :                 isUrgentEvent = true;
    1169           12 :                 handler->ForceDirtyState();
    1170           12 :                 break;
    1171              :             }
    1172              :         }
    1173              : 
    1174           40 :         return Loop::Continue;
    1175              :     });
    1176              : 
    1177           32 :     if (isUrgentEvent)
    1178              :     {
    1179           12 :         ChipLogDetail(DataManagement, "Urgent event will be sent once reporting is not blocked by the min interval");
    1180           12 :         return CHIP_NO_ERROR;
    1181              :     }
    1182              : 
    1183           20 :     return ScheduleBufferPressureEventDelivery(aBytesConsumed);
    1184              : }
    1185              : 
    1186          285 : void Engine::ScheduleUrgentEventDeliverySync(Optional<FabricIndex> fabricIndex)
    1187              : {
    1188          285 :     mpImEngine->mReadHandlers.ForEachActiveObject([fabricIndex](ReadHandler * handler) {
    1189            0 :         if (handler->IsType(ReadHandler::InteractionType::Read))
    1190              :         {
    1191            0 :             return Loop::Continue;
    1192              :         }
    1193              : 
    1194            0 :         if (fabricIndex.HasValue() && fabricIndex.Value() != handler->GetAccessingFabricIndex())
    1195              :         {
    1196            0 :             return Loop::Continue;
    1197              :         }
    1198              : 
    1199            0 :         handler->ForceDirtyState();
    1200              : 
    1201            0 :         return Loop::Continue;
    1202              :     });
    1203              : 
    1204          285 :     Run();
    1205          285 : }
    1206              : 
    1207         2429 : void Engine::MarkDirty(const AttributePathParams & path)
    1208              : {
    1209         2429 :     CHIP_ERROR err = SetDirty(path);
    1210         2429 :     if (err != CHIP_NO_ERROR)
    1211              :     {
    1212            0 :         ChipLogError(DataManagement, "Failed to set path dirty: %" CHIP_ERROR_FORMAT, err.Format());
    1213              :     }
    1214         2429 : }
    1215              : 
    1216              : } // namespace reporting
    1217              : } // namespace app
    1218              : } // namespace chip
    1219              : 
    1220              : // TODO: MatterReportingAttributeChangeCallback should just live in libCHIP, It does not depend on any
    1221              : // app-specific generated bits.
    1222              : void __attribute__((weak))
    1223            0 : MatterReportingAttributeChangeCallback(chip::EndpointId endpoint, chip::ClusterId clusterId, chip::AttributeId attributeId)
    1224            0 : {}
        

Generated by: LCOV version 2.0-1