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