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