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 : {}
|