Matter SDK Coverage Report
Current view: top level - app/reporting - Engine.cpp (source / functions) Coverage Total Hit
Test: SHA:b879ecb8e99e175eea0a293a888bda853da2b19c Lines: 91.5 % 497 455
Test Date: 2025-01-17 19:00:11 Functions: 95.2 % 42 40

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

Generated by: LCOV version 2.0-1