LCOV - code coverage report
Current view: top level - app - ReadHandler.cpp (source / functions) Hit Total Coverage
Test: lcov_final.info Lines: 347 411 84.4 %
Date: 2024-02-15 08:20:41 Functions: 28 31 90.3 %

          Line data    Source code
       1             : /*
       2             :  *
       3             :  *    Copyright (c) 2020 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 defines read handler for a CHIP Interaction Data model
      22             :  *
      23             :  */
      24             : 
      25             : #include <app/AppConfig.h>
      26             : #include <app/InteractionModelEngine.h>
      27             : #include <app/MessageDef/EventPathIB.h>
      28             : #include <app/MessageDef/StatusResponseMessage.h>
      29             : #include <app/MessageDef/SubscribeRequestMessage.h>
      30             : #include <app/MessageDef/SubscribeResponseMessage.h>
      31             : #include <app/icd/server/ICDServerConfig.h>
      32             : #include <lib/core/TLVUtilities.h>
      33             : #include <messaging/ExchangeContext.h>
      34             : 
      35             : #include <app/ReadHandler.h>
      36             : #include <app/reporting/Engine.h>
      37             : 
      38             : #if CHIP_CONFIG_ENABLE_ICD_SERVER
      39             : #include <app/icd/server/ICDConfigurationData.h> //nogncheck
      40             : #endif
      41             : 
      42             : namespace chip {
      43             : namespace app {
      44             : using Status = Protocols::InteractionModel::Status;
      45             : 
      46          17 : uint16_t ReadHandler::GetPublisherSelectedIntervalLimit()
      47             : {
      48             : #if CHIP_CONFIG_ENABLE_ICD_SERVER
      49             :     return std::chrono::duration_cast<System::Clock::Seconds16>(ICDConfigurationData::GetInstance().GetIdleModeDuration()).count();
      50             : #else
      51          17 :     return kSubscriptionMaxIntervalPublisherLimit;
      52             : #endif
      53             : }
      54             : 
      55         975 : ReadHandler::ReadHandler(ManagementCallback & apCallback, Messaging::ExchangeContext * apExchangeContext,
      56         975 :                          InteractionType aInteractionType, Observer * observer) :
      57         975 :     mExchangeCtx(*this),
      58        1950 :     mManagementCallback(apCallback)
      59             : {
      60         975 :     VerifyOrDie(apExchangeContext != nullptr);
      61             : 
      62         975 :     mExchangeCtx.Grab(apExchangeContext);
      63             : #if CHIP_CONFIG_UNSAFE_SUBSCRIPTION_EXCHANGE_MANAGER_USE
      64             :     // TODO: this should be replaced by a pointer to the InteractionModelEngine that created the ReadHandler
      65             :     // once InteractionModelEngine is no longer a singleton (see issue 23625)
      66             :     mExchangeMgr = apExchangeContext->GetExchangeMgr();
      67             : #endif // CHIP_CONFIG_UNSAFE_SUBSCRIPTION_EXCHANGE_MANAGER_USE
      68             : 
      69         975 :     mInteractionType            = aInteractionType;
      70         975 :     mLastWrittenEventsBytes     = 0;
      71         975 :     mTransactionStartGeneration = mManagementCallback.GetInteractionModelEngine()->GetReportingEngine().GetDirtySetGeneration();
      72         975 :     mFlags.ClearAll();
      73         975 :     SetStateFlag(ReadHandlerFlags::PrimingReports);
      74             : 
      75         975 :     mSessionHandle.Grab(mExchangeCtx->GetSessionHandle());
      76             : 
      77         975 :     VerifyOrDie(observer != nullptr);
      78         975 :     mObserver = observer;
      79         975 : }
      80             : 
      81             : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
      82           0 : ReadHandler::ReadHandler(ManagementCallback & apCallback, Observer * observer) :
      83           0 :     mExchangeCtx(*this), mManagementCallback(apCallback)
      84             : {
      85           0 :     mInteractionType = InteractionType::Subscribe;
      86           0 :     mFlags.ClearAll();
      87             : 
      88           0 :     VerifyOrDie(observer != nullptr);
      89           0 :     mObserver = observer;
      90           0 : }
      91             : 
      92           0 : void ReadHandler::OnSubscriptionResumed(const SessionHandle & sessionHandle,
      93             :                                         SubscriptionResumptionSessionEstablisher & resumptionSessionEstablisher)
      94             : {
      95           0 :     mSubscriptionId          = resumptionSessionEstablisher.mSubscriptionInfo.mSubscriptionId;
      96           0 :     mMinIntervalFloorSeconds = resumptionSessionEstablisher.mSubscriptionInfo.mMinInterval;
      97           0 :     mMaxInterval             = resumptionSessionEstablisher.mSubscriptionInfo.mMaxInterval;
      98           0 :     SetStateFlag(ReadHandlerFlags::FabricFiltered, resumptionSessionEstablisher.mSubscriptionInfo.mFabricFiltered);
      99             : 
     100             :     // Move dynamically allocated attributes and events from the SubscriptionInfo struct into
     101             :     // the object pool managed by the IM engine
     102           0 :     for (size_t i = 0; i < resumptionSessionEstablisher.mSubscriptionInfo.mAttributePaths.AllocatedSize(); i++)
     103             :     {
     104           0 :         AttributePathParams params = resumptionSessionEstablisher.mSubscriptionInfo.mAttributePaths[i].GetParams();
     105           0 :         CHIP_ERROR err = mManagementCallback.GetInteractionModelEngine()->PushFrontAttributePathList(mpAttributePathList, params);
     106           0 :         if (err != CHIP_NO_ERROR)
     107             :         {
     108           0 :             Close();
     109           0 :             return;
     110             :         }
     111             :     }
     112           0 :     for (size_t i = 0; i < resumptionSessionEstablisher.mSubscriptionInfo.mEventPaths.AllocatedSize(); i++)
     113             :     {
     114           0 :         EventPathParams params = resumptionSessionEstablisher.mSubscriptionInfo.mEventPaths[i].GetParams();
     115           0 :         CHIP_ERROR err = mManagementCallback.GetInteractionModelEngine()->PushFrontEventPathParamsList(mpEventPathList, params);
     116           0 :         if (err != CHIP_NO_ERROR)
     117             :         {
     118           0 :             Close();
     119           0 :             return;
     120             :         }
     121             :     }
     122             : 
     123           0 :     mSessionHandle.Grab(sessionHandle);
     124             : 
     125           0 :     SetStateFlag(ReadHandlerFlags::ActiveSubscription);
     126             : 
     127           0 :     auto * appCallback = mManagementCallback.GetAppCallback();
     128           0 :     if (appCallback)
     129             :     {
     130           0 :         appCallback->OnSubscriptionEstablished(*this);
     131             :     }
     132             :     // Notify the observer that a subscription has been resumed
     133           0 :     mObserver->OnSubscriptionEstablished(this);
     134             : 
     135           0 :     MoveToState(HandlerState::CanStartReporting);
     136             : 
     137           0 :     ObjectList<AttributePathParams> * attributePath = mpAttributePathList;
     138           0 :     while (attributePath)
     139             :     {
     140           0 :         mManagementCallback.GetInteractionModelEngine()->GetReportingEngine().SetDirty(attributePath->mValue);
     141           0 :         attributePath = attributePath->mpNext;
     142             :     }
     143             : }
     144             : 
     145             : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
     146             : 
     147         975 : ReadHandler::~ReadHandler()
     148             : {
     149         975 :     mObserver->OnReadHandlerDestroyed(this);
     150             : 
     151         975 :     auto * appCallback = mManagementCallback.GetAppCallback();
     152         975 :     if (mFlags.Has(ReadHandlerFlags::ActiveSubscription) && appCallback)
     153             :     {
     154          99 :         appCallback->OnSubscriptionTerminated(*this);
     155             :     }
     156             : 
     157         975 :     if (IsAwaitingReportResponse())
     158             :     {
     159           3 :         mManagementCallback.GetInteractionModelEngine()->GetReportingEngine().OnReportConfirm();
     160             :     }
     161         975 :     mManagementCallback.GetInteractionModelEngine()->ReleaseAttributePathList(mpAttributePathList);
     162         975 :     mManagementCallback.GetInteractionModelEngine()->ReleaseEventPathList(mpEventPathList);
     163         975 :     mManagementCallback.GetInteractionModelEngine()->ReleaseDataVersionFilterList(mpDataVersionFilterList);
     164         975 : }
     165             : 
     166         728 : void ReadHandler::Close(CloseOptions options)
     167             : {
     168             : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
     169         728 :     if (IsType(InteractionType::Subscribe) && options == CloseOptions::kDropPersistedSubscription)
     170             :     {
     171          33 :         auto * subscriptionResumptionStorage = mManagementCallback.GetInteractionModelEngine()->GetSubscriptionResumptionStorage();
     172          33 :         if (subscriptionResumptionStorage)
     173             :         {
     174           0 :             subscriptionResumptionStorage->Delete(GetInitiatorNodeId(), GetAccessingFabricIndex(), mSubscriptionId);
     175             :         }
     176             :     }
     177             : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
     178         728 :     MoveToState(HandlerState::AwaitingDestruction);
     179         728 :     mManagementCallback.OnDone(*this);
     180         728 : }
     181             : 
     182         945 : void ReadHandler::OnInitialRequest(System::PacketBufferHandle && aPayload)
     183             : {
     184         945 :     CHIP_ERROR err = CHIP_NO_ERROR;
     185         945 :     System::PacketBufferHandle response;
     186             : 
     187         945 :     if (IsType(InteractionType::Subscribe))
     188             :     {
     189         229 :         err = ProcessSubscribeRequest(std::move(aPayload));
     190             :     }
     191             :     else
     192             :     {
     193         716 :         err = ProcessReadRequest(std::move(aPayload));
     194             :     }
     195             : 
     196         945 :     if (err != CHIP_NO_ERROR)
     197             :     {
     198           8 :         Status status = Status::InvalidAction;
     199           8 :         if (err.IsIMStatus())
     200             :         {
     201           0 :             status = StatusIB(err).mStatus;
     202             :         }
     203           8 :         StatusResponse::Send(status, mExchangeCtx.Get(), /* aExpectResponse = */ false);
     204             :         // At this point we can't have a persisted subscription, since that
     205             :         // happens only when ProcessSubscribeRequest returns success. And our
     206             :         // subscription id is almost certainly not actually useful at this
     207             :         // point, either.  So don't try to mess with persisted subscriptions in
     208             :         // Close().
     209           8 :         Close(CloseOptions::kKeepPersistedSubscription);
     210             :     }
     211             :     else
     212             :     {
     213             :         // Force us to be in a dirty state so we get processed by the reporting
     214         937 :         SetStateFlag(ReadHandlerFlags::ForceDirty);
     215             :     }
     216         945 : }
     217             : 
     218        1153 : CHIP_ERROR ReadHandler::OnStatusResponse(Messaging::ExchangeContext * apExchangeContext, System::PacketBufferHandle && aPayload,
     219             :                                          bool & aSendStatusResponse)
     220             : {
     221        1153 :     CHIP_ERROR err         = CHIP_NO_ERROR;
     222        1153 :     aSendStatusResponse    = true;
     223        1153 :     CHIP_ERROR statusError = CHIP_NO_ERROR;
     224        1153 :     SuccessOrExit(err = StatusResponse::ProcessStatusResponse(std::move(aPayload), statusError));
     225             :     // Since this is a valid Status Response message, we don't have to send a Status Response in reply to it.
     226        1151 :     aSendStatusResponse = false;
     227        1151 :     SuccessOrExit(err = statusError);
     228        1139 :     switch (mState)
     229             :     {
     230        1139 :     case HandlerState::AwaitingReportResponse:
     231        1139 :         if (IsChunkedReport())
     232             :         {
     233         851 :             mExchangeCtx->WillSendMessage();
     234             :         }
     235         288 :         else if (IsType(InteractionType::Subscribe))
     236             :         {
     237         288 :             if (IsPriming())
     238             :             {
     239         211 :                 err = SendSubscribeResponse();
     240             : 
     241         211 :                 SetStateFlag(ReadHandlerFlags::ActiveSubscription);
     242             : 
     243         211 :                 auto * appCallback = mManagementCallback.GetAppCallback();
     244         211 :                 if (appCallback)
     245             :                 {
     246          99 :                     appCallback->OnSubscriptionEstablished(*this);
     247             :                 }
     248         211 :                 mObserver->OnSubscriptionEstablished(this);
     249             :             }
     250             :         }
     251             :         else
     252             :         {
     253             :             //
     254             :             // We're done processing a read, so let's close out and return.
     255             :             //
     256           0 :             Close();
     257           0 :             return CHIP_NO_ERROR;
     258             :         }
     259             : 
     260        1139 :         MoveToState(HandlerState::CanStartReporting);
     261        1139 :         break;
     262             : 
     263           0 :     case HandlerState::CanStartReporting:
     264             :     case HandlerState::Idle:
     265             :     default:
     266           0 :         err = CHIP_ERROR_INCORRECT_STATE;
     267           0 :         break;
     268             :     }
     269             : 
     270        1153 : exit:
     271        1153 :     return err;
     272             : }
     273             : 
     274          31 : CHIP_ERROR ReadHandler::SendStatusReport(Protocols::InteractionModel::Status aStatus)
     275             : {
     276          31 :     VerifyOrReturnLogError(mState == HandlerState::CanStartReporting, CHIP_ERROR_INCORRECT_STATE);
     277          31 :     if (IsPriming() || IsChunkedReport())
     278             :     {
     279          31 :         mSessionHandle.Grab(mExchangeCtx->GetSessionHandle());
     280             :     }
     281             :     else
     282             :     {
     283           0 :         VerifyOrReturnLogError(!mExchangeCtx, CHIP_ERROR_INCORRECT_STATE);
     284           0 :         VerifyOrReturnLogError(mSessionHandle, CHIP_ERROR_INCORRECT_STATE);
     285             : #if CHIP_CONFIG_UNSAFE_SUBSCRIPTION_EXCHANGE_MANAGER_USE
     286             :         auto exchange = mExchangeMgr->NewContext(mSessionHandle.Get().Value(), this);
     287             : #else  // CHIP_CONFIG_UNSAFE_SUBSCRIPTION_EXCHANGE_MANAGER_USE
     288             :         auto exchange =
     289           0 :             mManagementCallback.GetInteractionModelEngine()->GetExchangeManager()->NewContext(mSessionHandle.Get().Value(), this);
     290             : #endif // CHIP_CONFIG_UNSAFE_SUBSCRIPTION_EXCHANGE_MANAGER_USE
     291           0 :         VerifyOrReturnLogError(exchange != nullptr, CHIP_ERROR_INCORRECT_STATE);
     292           0 :         mExchangeCtx.Grab(exchange);
     293             :     }
     294             : 
     295          31 :     VerifyOrReturnLogError(mExchangeCtx, CHIP_ERROR_INCORRECT_STATE);
     296          31 :     return StatusResponse::Send(aStatus, mExchangeCtx.Get(), /* aExpectResponse = */ false);
     297             : }
     298             : 
     299        1808 : CHIP_ERROR ReadHandler::SendReportData(System::PacketBufferHandle && aPayload, bool aMoreChunks)
     300             : {
     301        1808 :     VerifyOrReturnLogError(mState == HandlerState::CanStartReporting, CHIP_ERROR_INCORRECT_STATE);
     302        1804 :     VerifyOrDie(!IsAwaitingReportResponse()); // Should not be reportable!
     303        1804 :     if (IsPriming() || IsChunkedReport())
     304             :     {
     305        1713 :         mSessionHandle.Grab(mExchangeCtx->GetSessionHandle());
     306             :     }
     307             :     else
     308             :     {
     309          91 :         VerifyOrReturnLogError(!mExchangeCtx, CHIP_ERROR_INCORRECT_STATE);
     310          91 :         VerifyOrReturnLogError(mSessionHandle, CHIP_ERROR_INCORRECT_STATE);
     311             : #if CHIP_CONFIG_UNSAFE_SUBSCRIPTION_EXCHANGE_MANAGER_USE
     312             :         auto exchange = mExchangeMgr->NewContext(mSessionHandle.Get().Value(), this);
     313             : #else  // CHIP_CONFIG_UNSAFE_SUBSCRIPTION_EXCHANGE_MANAGER_USE
     314             :         auto exchange =
     315          91 :             mManagementCallback.GetInteractionModelEngine()->GetExchangeManager()->NewContext(mSessionHandle.Get().Value(), this);
     316             : #endif // CHIP_CONFIG_UNSAFE_SUBSCRIPTION_EXCHANGE_MANAGER_USE
     317          91 :         VerifyOrReturnLogError(exchange != nullptr, CHIP_ERROR_INCORRECT_STATE);
     318          89 :         mExchangeCtx.Grab(exchange);
     319             :     }
     320             : 
     321        1802 :     VerifyOrReturnLogError(mExchangeCtx, CHIP_ERROR_INCORRECT_STATE);
     322             : 
     323        1802 :     if (!IsReporting())
     324             :     {
     325         995 :         mCurrentReportsBeginGeneration =
     326         995 :             mManagementCallback.GetInteractionModelEngine()->GetReportingEngine().GetDirtySetGeneration();
     327             :     }
     328        1802 :     SetStateFlag(ReadHandlerFlags::ChunkedReport, aMoreChunks);
     329        1802 :     bool responseExpected = IsType(InteractionType::Subscribe) || aMoreChunks;
     330             : 
     331        1802 :     mExchangeCtx->UseSuggestedResponseTimeout(app::kExpectedIMProcessingTime);
     332        1802 :     CHIP_ERROR err = mExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::ReportData, std::move(aPayload),
     333             :                                                responseExpected ? Messaging::SendMessageFlags::kExpectResponse
     334             :                                                                 : Messaging::SendMessageFlags::kNone);
     335        1802 :     if (err == CHIP_NO_ERROR)
     336             :     {
     337        1800 :         if (responseExpected)
     338             :         {
     339        1167 :             MoveToState(HandlerState::AwaitingReportResponse);
     340             :         }
     341             :         else
     342             :         {
     343             :             // Make sure we're not treated as an in-flight report waiting for a
     344             :             // response by the reporting engine.
     345         633 :             mManagementCallback.GetInteractionModelEngine()->GetReportingEngine().OnReportConfirm();
     346             :         }
     347             : 
     348             :         // If we just finished a non-priming subscription report, notify our observers.
     349             :         // Priming reports are handled when we send a SubscribeResponse.
     350        1800 :         if (IsType(InteractionType::Subscribe) && !IsPriming() && !IsChunkedReport())
     351             :         {
     352          87 :             mObserver->OnSubscriptionReportSent(this);
     353             :         }
     354             :     }
     355        1802 :     if (!aMoreChunks)
     356             :     {
     357         941 :         mPreviousReportsBeginGeneration = mCurrentReportsBeginGeneration;
     358         941 :         ClearForceDirtyFlag();
     359         941 :         mManagementCallback.GetInteractionModelEngine()->ReleaseDataVersionFilterList(mpDataVersionFilterList);
     360             :     }
     361             : 
     362        1802 :     return err;
     363             : }
     364             : 
     365        1155 : CHIP_ERROR ReadHandler::OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
     366             :                                           System::PacketBufferHandle && aPayload)
     367             : {
     368        1155 :     CHIP_ERROR err          = CHIP_NO_ERROR;
     369        1155 :     bool sendStatusResponse = true;
     370        1155 :     if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::StatusResponse))
     371             :     {
     372        1153 :         err = OnStatusResponse(apExchangeContext, std::move(aPayload), sendStatusResponse);
     373             :     }
     374             :     else
     375             :     {
     376           2 :         ChipLogDetail(DataManagement, "ReadHandler:: Msg type %d not supported", aPayloadHeader.GetMessageType());
     377           2 :         err = CHIP_ERROR_INVALID_MESSAGE_TYPE;
     378             :     }
     379             : 
     380        1155 :     if (sendStatusResponse)
     381             :     {
     382           4 :         StatusResponse::Send(Status::InvalidAction, apExchangeContext, false /*aExpectResponse*/);
     383             :     }
     384             : 
     385        1155 :     if (err != CHIP_NO_ERROR)
     386             :     {
     387          16 :         Close();
     388             :     }
     389        1155 :     return err;
     390             : }
     391             : 
     392           7 : bool ReadHandler::IsFromSubscriber(Messaging::ExchangeContext & apExchangeContext) const
     393             : {
     394           7 :     return (IsType(InteractionType::Subscribe) &&
     395          21 :             GetInitiatorNodeId() == apExchangeContext.GetSessionHandle()->AsSecureSession()->GetPeerNodeId() &&
     396          14 :             GetAccessingFabricIndex() == apExchangeContext.GetSessionHandle()->GetFabricIndex());
     397             : }
     398             : 
     399           8 : void ReadHandler::OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext)
     400             : {
     401           8 :     ChipLogError(DataManagement, "Time out! failed to receive status response from Exchange: " ChipLogFormatExchange,
     402             :                  ChipLogValueExchange(apExchangeContext));
     403             : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
     404           8 :     Close(CloseOptions::kKeepPersistedSubscription);
     405             : #else
     406             :     Close();
     407             : #endif
     408           8 : }
     409             : 
     410         720 : CHIP_ERROR ReadHandler::ProcessReadRequest(System::PacketBufferHandle && aPayload)
     411             : {
     412         720 :     CHIP_ERROR err = CHIP_NO_ERROR;
     413         720 :     System::PacketBufferTLVReader reader;
     414             : 
     415         720 :     ReadRequestMessage::Parser readRequestParser;
     416         720 :     EventPathIBs::Parser eventPathListParser;
     417         720 :     EventFilterIBs::Parser eventFilterIBsParser;
     418         720 :     AttributePathIBs::Parser attributePathListParser;
     419             : 
     420         720 :     reader.Init(std::move(aPayload));
     421             : 
     422         720 :     ReturnErrorOnFailure(readRequestParser.Init(reader));
     423             : 
     424             :     // No need to pretty-print here.  We pretty-print read requests in the read
     425             :     // case of InteractionModelEngine::OnReadInitialRequest, so we do it even if
     426             :     // we reject a read request.
     427             : 
     428         720 :     err = readRequestParser.GetAttributeRequests(&attributePathListParser);
     429         720 :     if (err == CHIP_END_OF_TLV)
     430             :     {
     431         121 :         err = CHIP_NO_ERROR;
     432             :     }
     433         599 :     else if (err == CHIP_NO_ERROR)
     434             :     {
     435         599 :         ReturnErrorOnFailure(ProcessAttributePaths(attributePathListParser));
     436         599 :         DataVersionFilterIBs::Parser dataVersionFilterListParser;
     437         599 :         err = readRequestParser.GetDataVersionFilters(&dataVersionFilterListParser);
     438         599 :         if (err == CHIP_END_OF_TLV)
     439             :         {
     440         588 :             err = CHIP_NO_ERROR;
     441             :         }
     442          11 :         else if (err == CHIP_NO_ERROR)
     443             :         {
     444          11 :             ReturnErrorOnFailure(ProcessDataVersionFilterList(dataVersionFilterListParser));
     445             :         }
     446             :     }
     447         720 :     ReturnErrorOnFailure(err);
     448         720 :     err = readRequestParser.GetEventRequests(&eventPathListParser);
     449         720 :     if (err == CHIP_END_OF_TLV)
     450             :     {
     451         403 :         err = CHIP_NO_ERROR;
     452             :     }
     453         317 :     else if (err == CHIP_NO_ERROR)
     454             :     {
     455         317 :         ReturnErrorOnFailure(err);
     456         317 :         ReturnErrorOnFailure(ProcessEventPaths(eventPathListParser));
     457         317 :         err = readRequestParser.GetEventFilters(&eventFilterIBsParser);
     458         317 :         if (err == CHIP_END_OF_TLV)
     459             :         {
     460           3 :             err = CHIP_NO_ERROR;
     461             :         }
     462         314 :         else if (err == CHIP_NO_ERROR)
     463             :         {
     464         314 :             ReturnErrorOnFailure(ProcessEventFilters(eventFilterIBsParser));
     465             :         }
     466             :     }
     467         720 :     ReturnErrorOnFailure(err);
     468             : 
     469             :     bool isFabricFiltered;
     470         720 :     ReturnErrorOnFailure(readRequestParser.GetIsFabricFiltered(&isFabricFiltered));
     471         716 :     SetStateFlag(ReadHandlerFlags::FabricFiltered, isFabricFiltered);
     472         716 :     ReturnErrorOnFailure(readRequestParser.ExitContainer());
     473         716 :     MoveToState(HandlerState::CanStartReporting);
     474             : 
     475         716 :     mExchangeCtx->WillSendMessage();
     476             : 
     477             :     // There must be no code after the WillSendMessage() call that can cause
     478             :     // this method to return a failure.
     479             : 
     480         716 :     return CHIP_NO_ERROR;
     481         720 : }
     482             : 
     483         824 : CHIP_ERROR ReadHandler::ProcessAttributePaths(AttributePathIBs::Parser & aAttributePathListParser)
     484             : {
     485         824 :     CHIP_ERROR err = CHIP_NO_ERROR;
     486             :     TLV::TLVReader reader;
     487         824 :     aAttributePathListParser.GetReader(&reader);
     488        2219 :     while (CHIP_NO_ERROR == (err = reader.Next()))
     489             :     {
     490        1395 :         VerifyOrReturnError(TLV::AnonymousTag() == reader.GetTag(), CHIP_ERROR_INVALID_TLV_TAG);
     491        1395 :         AttributePathParams attribute;
     492        1395 :         AttributePathIB::Parser path;
     493        1395 :         ReturnErrorOnFailure(path.Init(reader));
     494        1395 :         ReturnErrorOnFailure(path.ParsePath(attribute));
     495        1395 :         ReturnErrorOnFailure(
     496             :             mManagementCallback.GetInteractionModelEngine()->PushFrontAttributePathList(mpAttributePathList, attribute));
     497             :     }
     498             :     // if we have exhausted this container
     499         824 :     if (CHIP_END_OF_TLV == err)
     500             :     {
     501         824 :         mManagementCallback.GetInteractionModelEngine()->RemoveDuplicateConcreteAttributePath(mpAttributePathList);
     502         824 :         mAttributePathExpandIterator = AttributePathExpandIterator(mpAttributePathList);
     503         824 :         err                          = CHIP_NO_ERROR;
     504             :     }
     505         824 :     return err;
     506             : }
     507             : 
     508          76 : CHIP_ERROR ReadHandler::ProcessDataVersionFilterList(DataVersionFilterIBs::Parser & aDataVersionFilterListParser)
     509             : {
     510          76 :     CHIP_ERROR err = CHIP_NO_ERROR;
     511             :     TLV::TLVReader reader;
     512             : 
     513          76 :     aDataVersionFilterListParser.GetReader(&reader);
     514         154 :     while (CHIP_NO_ERROR == (err = reader.Next()))
     515             :     {
     516          78 :         VerifyOrReturnError(TLV::AnonymousTag() == reader.GetTag(), CHIP_ERROR_INVALID_TLV_TAG);
     517          78 :         DataVersionFilter versionFilter;
     518          78 :         ClusterPathIB::Parser path;
     519          78 :         DataVersionFilterIB::Parser filter;
     520          78 :         ReturnErrorOnFailure(filter.Init(reader));
     521          78 :         DataVersion version = 0;
     522          78 :         ReturnErrorOnFailure(filter.GetDataVersion(&version));
     523          78 :         versionFilter.mDataVersion.SetValue(version);
     524          78 :         ReturnErrorOnFailure(filter.GetPath(&path));
     525          78 :         ReturnErrorOnFailure(path.GetEndpoint(&(versionFilter.mEndpointId)));
     526          78 :         ReturnErrorOnFailure(path.GetCluster(&(versionFilter.mClusterId)));
     527          78 :         VerifyOrReturnError(versionFilter.IsValidDataVersionFilter(), CHIP_ERROR_IM_MALFORMED_DATA_VERSION_FILTER_IB);
     528          78 :         ReturnErrorOnFailure(mManagementCallback.GetInteractionModelEngine()->PushFrontDataVersionFilterList(
     529             :             mpDataVersionFilterList, versionFilter));
     530          78 :     }
     531             : 
     532          76 :     if (CHIP_END_OF_TLV == err)
     533             :     {
     534          76 :         err = CHIP_NO_ERROR;
     535             :     }
     536          76 :     return err;
     537             : }
     538             : 
     539         335 : CHIP_ERROR ReadHandler::ProcessEventPaths(EventPathIBs::Parser & aEventPathsParser)
     540             : {
     541         335 :     CHIP_ERROR err = CHIP_NO_ERROR;
     542             :     TLV::TLVReader reader;
     543         335 :     aEventPathsParser.GetReader(&reader);
     544         832 :     while (CHIP_NO_ERROR == (err = reader.Next()))
     545             :     {
     546         497 :         VerifyOrReturnError(TLV::AnonymousTag() == reader.GetTag(), CHIP_ERROR_INVALID_TLV_TAG);
     547         497 :         EventPathParams event;
     548         497 :         EventPathIB::Parser path;
     549         497 :         ReturnErrorOnFailure(path.Init(reader));
     550         497 :         ReturnErrorOnFailure(path.ParsePath(event));
     551         497 :         ReturnErrorOnFailure(mManagementCallback.GetInteractionModelEngine()->PushFrontEventPathParamsList(mpEventPathList, event));
     552             :     }
     553             : 
     554             :     // if we have exhausted this container
     555         335 :     if (CHIP_END_OF_TLV == err)
     556             :     {
     557         335 :         err = CHIP_NO_ERROR;
     558             :     }
     559         335 :     return err;
     560             : }
     561             : 
     562         314 : CHIP_ERROR ReadHandler::ProcessEventFilters(EventFilterIBs::Parser & aEventFiltersParser)
     563             : {
     564         314 :     CHIP_ERROR err = CHIP_NO_ERROR;
     565             :     TLV::TLVReader reader;
     566         314 :     aEventFiltersParser.GetReader(&reader);
     567             : 
     568         628 :     while (CHIP_NO_ERROR == (err = reader.Next()))
     569             :     {
     570         314 :         VerifyOrReturnError(TLV::AnonymousTag() == reader.GetTag(), CHIP_ERROR_INVALID_TLV_TAG);
     571         314 :         EventFilterIB::Parser filter;
     572         314 :         ReturnErrorOnFailure(filter.Init(reader));
     573             :         // this is for current node, and would have only one event filter.
     574         314 :         ReturnErrorOnFailure(filter.GetEventMin(&(mEventMin)));
     575             :     }
     576         314 :     if (CHIP_END_OF_TLV == err)
     577             :     {
     578         314 :         err = CHIP_NO_ERROR;
     579             :     }
     580         314 :     return err;
     581             : }
     582             : 
     583        3987 : const char * ReadHandler::GetStateStr() const
     584             : {
     585             : #if CHIP_DETAIL_LOGGING
     586        3987 :     switch (mState)
     587             :     {
     588           2 :     case HandlerState::Idle:
     589           2 :         return "Idle";
     590         728 :     case HandlerState::AwaitingDestruction:
     591         728 :         return "AwaitingDestruction";
     592        2090 :     case HandlerState::CanStartReporting:
     593        2090 :         return "CanStartReporting";
     594             : 
     595        1167 :     case HandlerState::AwaitingReportResponse:
     596        1167 :         return "AwaitingReportResponse";
     597             :     }
     598             : #endif // CHIP_DETAIL_LOGGING
     599           0 :     return "N/A";
     600             : }
     601             : 
     602        3987 : void ReadHandler::MoveToState(const HandlerState aTargetState)
     603             : {
     604        3987 :     if (aTargetState == mState)
     605             :     {
     606           0 :         return;
     607             :     }
     608             : 
     609        3987 :     if (IsAwaitingReportResponse() && aTargetState != HandlerState::AwaitingReportResponse)
     610             :     {
     611        1164 :         mManagementCallback.GetInteractionModelEngine()->GetReportingEngine().OnReportConfirm();
     612             :     }
     613             : 
     614        3987 :     mState = aTargetState;
     615        3987 :     ChipLogDetail(DataManagement, "IM RH moving to [%s]", GetStateStr());
     616             : 
     617             :     //
     618             :     // If we just unblocked sending reports, let's go ahead and schedule the reporting
     619             :     // engine to run to kick that off.
     620             :     //
     621        3987 :     if (aTargetState == HandlerState::CanStartReporting)
     622             :     {
     623        2090 :         if (ShouldReportUnscheduled())
     624             :         {
     625        1769 :             mManagementCallback.GetInteractionModelEngine()->GetReportingEngine().ScheduleRun();
     626             :         }
     627             :         else
     628             :         {
     629             :             // If we became reportable, the scheduler will schedule a run as soon as allowed
     630         321 :             mObserver->OnBecameReportable(this);
     631             :         }
     632             :     }
     633             : }
     634             : 
     635         887 : bool ReadHandler::CheckEventClean(EventManagement & aEventManager)
     636             : {
     637         887 :     if (mFlags.Has(ReadHandlerFlags::ChunkedReport))
     638             :     {
     639         525 :         if ((mLastScheduledEventNumber != 0) && (mEventMin <= mLastScheduledEventNumber))
     640             :         {
     641         519 :             return false;
     642             :         }
     643             :     }
     644             :     else
     645             :     {
     646         362 :         EventNumber lastEventNumber = aEventManager.GetLastEventNumber();
     647         362 :         if ((lastEventNumber != 0) && (mEventMin <= lastEventNumber))
     648             :         {
     649             :             // We have more events. snapshot last event number
     650         344 :             aEventManager.SetScheduledEventInfo(mLastScheduledEventNumber, mLastWrittenEventsBytes);
     651         344 :             return false;
     652             :         }
     653             :     }
     654          24 :     return true;
     655             : }
     656             : 
     657         211 : CHIP_ERROR ReadHandler::SendSubscribeResponse()
     658             : {
     659         211 :     System::PacketBufferHandle packet = System::PacketBufferHandle::New(chip::app::kMaxSecureSduLengthBytes);
     660         211 :     VerifyOrReturnLogError(!packet.IsNull(), CHIP_ERROR_NO_MEMORY);
     661             : 
     662         211 :     System::PacketBufferTLVWriter writer;
     663         211 :     writer.Init(std::move(packet));
     664             : 
     665         211 :     SubscribeResponseMessage::Builder response;
     666         211 :     ReturnErrorOnFailure(response.Init(&writer));
     667         211 :     ReturnErrorOnFailure(response.SubscriptionId(mSubscriptionId).MaxInterval(mMaxInterval).EndOfSubscribeResponseMessage());
     668             : 
     669         211 :     ReturnErrorOnFailure(writer.Finalize(&packet));
     670         211 :     VerifyOrReturnLogError(mExchangeCtx, CHIP_ERROR_INCORRECT_STATE);
     671             : 
     672         211 :     ClearStateFlag(ReadHandlerFlags::PrimingReports);
     673         211 :     return mExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::SubscribeResponse, std::move(packet));
     674         211 : }
     675             : 
     676         231 : CHIP_ERROR ReadHandler::ProcessSubscribeRequest(System::PacketBufferHandle && aPayload)
     677             : {
     678         231 :     System::PacketBufferTLVReader reader;
     679         231 :     reader.Init(std::move(aPayload));
     680             : 
     681         231 :     SubscribeRequestMessage::Parser subscribeRequestParser;
     682         231 :     ReturnErrorOnFailure(subscribeRequestParser.Init(reader));
     683             : 
     684             :     // No need to pretty-print here.  We pretty-print subscribe requests in the
     685             :     // subscribe case of InteractionModelEngine::OnReadInitialRequest, so we do
     686             :     // it even if we reject a subscribe request.
     687             : 
     688         231 :     AttributePathIBs::Parser attributePathListParser;
     689         231 :     CHIP_ERROR err = subscribeRequestParser.GetAttributeRequests(&attributePathListParser);
     690         231 :     if (err == CHIP_END_OF_TLV)
     691             :     {
     692           6 :         err = CHIP_NO_ERROR;
     693             :     }
     694         225 :     else if (err == CHIP_NO_ERROR)
     695             :     {
     696         225 :         ReturnErrorOnFailure(ProcessAttributePaths(attributePathListParser));
     697         225 :         DataVersionFilterIBs::Parser dataVersionFilterListParser;
     698         225 :         err = subscribeRequestParser.GetDataVersionFilters(&dataVersionFilterListParser);
     699         225 :         if (err == CHIP_END_OF_TLV)
     700             :         {
     701         160 :             err = CHIP_NO_ERROR;
     702             :         }
     703          65 :         else if (err == CHIP_NO_ERROR)
     704             :         {
     705          65 :             ReturnErrorOnFailure(ProcessDataVersionFilterList(dataVersionFilterListParser));
     706             :         }
     707             :     }
     708         231 :     ReturnErrorOnFailure(err);
     709             : 
     710         231 :     EventPathIBs::Parser eventPathListParser;
     711         231 :     err = subscribeRequestParser.GetEventRequests(&eventPathListParser);
     712         231 :     if (err == CHIP_END_OF_TLV)
     713             :     {
     714         213 :         err = CHIP_NO_ERROR;
     715             :     }
     716          18 :     else if (err == CHIP_NO_ERROR)
     717             :     {
     718          18 :         ReturnErrorOnFailure(ProcessEventPaths(eventPathListParser));
     719          18 :         EventFilterIBs::Parser eventFilterIBsParser;
     720          18 :         err = subscribeRequestParser.GetEventFilters(&eventFilterIBsParser);
     721          18 :         if (err == CHIP_END_OF_TLV)
     722             :         {
     723          18 :             err = CHIP_NO_ERROR;
     724             :         }
     725           0 :         else if (err == CHIP_NO_ERROR)
     726             :         {
     727           0 :             ReturnErrorOnFailure(ProcessEventFilters(eventFilterIBsParser));
     728             :         }
     729             :     }
     730         231 :     ReturnErrorOnFailure(err);
     731             : 
     732         231 :     ReturnErrorOnFailure(subscribeRequestParser.GetMinIntervalFloorSeconds(&mMinIntervalFloorSeconds));
     733         231 :     ReturnErrorOnFailure(subscribeRequestParser.GetMaxIntervalCeilingSeconds(&mMaxInterval));
     734         231 :     VerifyOrReturnError(mMinIntervalFloorSeconds <= mMaxInterval, CHIP_ERROR_INVALID_ARGUMENT);
     735             : 
     736             : #if CHIP_CONFIG_ENABLE_ICD_SERVER
     737             : 
     738             :     // Default behavior for ICDs where the wanted MaxInterval for a subscription is the IdleModeDuration
     739             :     // defined in the ICD Management Cluster.
     740             :     // Behavior can be changed with the OnSubscriptionRequested function defined in the application callbacks
     741             : 
     742             :     // Default Behavior Steps :
     743             :     // If MinInterval > IdleModeDuration, try to set the MaxInterval to the first interval of IdleModeDurations above the
     744             :     // MinInterval.
     745             :     // If the next interval is greater than the MaxIntervalCeiling, use the MaxIntervalCeiling.
     746             :     // Otherwise, use IdleModeDuration as MaxInterval
     747             : 
     748             :     // GetPublisherSelectedIntervalLimit() returns the IdleModeDuration if the device is an ICD
     749             :     uint32_t decidedMaxInterval = GetPublisherSelectedIntervalLimit();
     750             : 
     751             :     // Check if the PublisherSelectedIntervalLimit is 0. If so, set decidedMaxInterval to MaxIntervalCeiling
     752             :     if (decidedMaxInterval == 0)
     753             :     {
     754             :         decidedMaxInterval = mMaxInterval;
     755             :     }
     756             : 
     757             :     // If requestedMinInterval is greater than the IdleTimeInterval, select next active up time as max interval
     758             :     if (mMinIntervalFloorSeconds > decidedMaxInterval)
     759             :     {
     760             :         uint16_t ratio = mMinIntervalFloorSeconds / static_cast<uint16_t>(decidedMaxInterval);
     761             :         if (mMinIntervalFloorSeconds % decidedMaxInterval)
     762             :         {
     763             :             ratio++;
     764             :         }
     765             : 
     766             :         decidedMaxInterval *= ratio;
     767             :     }
     768             : 
     769             :     // Verify that decidedMaxInterval is an acceptable value (overflow)
     770             :     if (decidedMaxInterval > System::Clock::Seconds16::max().count())
     771             :     {
     772             :         decidedMaxInterval = System::Clock::Seconds16::max().count();
     773             :     }
     774             : 
     775             :     // Verify that the decidedMaxInterval respects MAX(GetPublisherSelectedIntervalLimit(), MaxIntervalCeiling)
     776             :     uint16_t maximumMaxInterval = std::max(GetPublisherSelectedIntervalLimit(), mMaxInterval);
     777             :     if (decidedMaxInterval > maximumMaxInterval)
     778             :     {
     779             :         decidedMaxInterval = maximumMaxInterval;
     780             :     }
     781             : 
     782             :     // Set max interval of the subscription
     783             :     mMaxInterval = static_cast<uint16_t>(decidedMaxInterval);
     784             : 
     785             : #endif // CHIP_CONFIG_ENABLE_ICD_SERVER
     786             : 
     787             :     //
     788             :     // Notify the application (if requested) of the impending subscription and check whether we should still proceed to set it up.
     789             :     // This also provides the application an opportunity to modify the negotiated min/max intervals set above.
     790             :     //
     791         231 :     auto * appCallback = mManagementCallback.GetAppCallback();
     792         231 :     if (appCallback)
     793             :     {
     794         105 :         if (appCallback->OnSubscriptionRequested(*this, *mExchangeCtx->GetSessionHandle()->AsSecureSession()) != CHIP_NO_ERROR)
     795             :         {
     796           6 :             return CHIP_ERROR_TRANSACTION_CANCELED;
     797             :         }
     798             :     }
     799             : 
     800         225 :     ChipLogProgress(DataManagement, "Final negotiated min/max parameters: Min = %ds, Max = %ds", mMinIntervalFloorSeconds,
     801             :                     mMaxInterval);
     802             : 
     803             :     bool isFabricFiltered;
     804         225 :     ReturnErrorOnFailure(subscribeRequestParser.GetIsFabricFiltered(&isFabricFiltered));
     805         225 :     SetStateFlag(ReadHandlerFlags::FabricFiltered, isFabricFiltered);
     806         225 :     ReturnErrorOnFailure(Crypto::DRBG_get_bytes(reinterpret_cast<uint8_t *>(&mSubscriptionId), sizeof(mSubscriptionId)));
     807         225 :     ReturnErrorOnFailure(subscribeRequestParser.ExitContainer());
     808         225 :     MoveToState(HandlerState::CanStartReporting);
     809             : 
     810         225 :     mExchangeCtx->WillSendMessage();
     811             : 
     812             : #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
     813         225 :     PersistSubscription();
     814             : #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
     815             : 
     816         225 :     return CHIP_NO_ERROR;
     817         231 : }
     818             : 
     819         225 : void ReadHandler::PersistSubscription()
     820             : {
     821         225 :     auto * subscriptionResumptionStorage = mManagementCallback.GetInteractionModelEngine()->GetSubscriptionResumptionStorage();
     822         225 :     VerifyOrReturn(subscriptionResumptionStorage != nullptr);
     823             : 
     824           0 :     SubscriptionResumptionStorage::SubscriptionInfo subscriptionInfo = { .mNodeId         = GetInitiatorNodeId(),
     825           0 :                                                                          .mFabricIndex    = GetAccessingFabricIndex(),
     826           0 :                                                                          .mSubscriptionId = mSubscriptionId,
     827           0 :                                                                          .mMinInterval    = mMinIntervalFloorSeconds,
     828           0 :                                                                          .mMaxInterval    = mMaxInterval,
     829           0 :                                                                          .mFabricFiltered = IsFabricFiltered() };
     830           0 :     VerifyOrReturn(subscriptionInfo.SetAttributePaths(mpAttributePathList) == CHIP_NO_ERROR);
     831           0 :     VerifyOrReturn(subscriptionInfo.SetEventPaths(mpEventPathList) == CHIP_NO_ERROR);
     832             : 
     833           0 :     CHIP_ERROR err = subscriptionResumptionStorage->Save(subscriptionInfo);
     834           0 :     if (err != CHIP_NO_ERROR)
     835             :     {
     836           0 :         ChipLogError(DataManagement, "Failed to save subscription info error: '%" CHIP_ERROR_FORMAT, err.Format());
     837             :     }
     838           0 : }
     839             : 
     840        1028 : void ReadHandler::ResetPathIterator()
     841             : {
     842        1028 :     mAttributePathExpandIterator = AttributePathExpandIterator(mpAttributePathList);
     843        1028 :     mAttributeEncoderState       = AttributeValueEncoder::AttributeEncodeState();
     844        1028 : }
     845             : 
     846         345 : void ReadHandler::AttributePathIsDirty(const AttributePathParams & aAttributeChanged)
     847             : {
     848         345 :     ConcreteAttributePath path;
     849             : 
     850         345 :     mDirtyGeneration = mManagementCallback.GetInteractionModelEngine()->GetReportingEngine().GetDirtySetGeneration();
     851             : 
     852             :     // We won't reset the path iterator for every AttributePathIsDirty call to reduce the number of full data reports.
     853             :     // The iterator will be reset after finishing each report session.
     854             :     //
     855             :     // Here we just reset the iterator to the beginning of the current cluster, if the dirty path affects it.
     856             :     // This will ensure the reports are consistent within a single cluster generated from a single path in the request.
     857             : 
     858             :     // TODO (#16699): Currently we can only guarantee the reports generated from a single path in the request are consistent. The
     859             :     // data might be inconsistent if the user send a request with two paths from the same cluster. We need to clearify the behavior
     860             :     // or make it consistent.
     861         609 :     if (mAttributePathExpandIterator.Get(path) &&
     862        1135 :         (aAttributeChanged.HasWildcardEndpointId() || aAttributeChanged.mEndpointId == path.mEndpointId) &&
     863         526 :         (aAttributeChanged.HasWildcardClusterId() || aAttributeChanged.mClusterId == path.mClusterId))
     864             :     {
     865         263 :         ChipLogDetail(DataManagement,
     866             :                       "The dirty path intersects the cluster we are currently reporting; reset the iterator to the beginning of "
     867             :                       "that cluster");
     868             :         // If we're currently in the middle of generating reports for a given cluster and that in turn is marked dirty, let's reset
     869             :         // our iterator to point back to the beginning of that cluster. This ensures that the receiver will get a coherent view of
     870             :         // the state of the cluster as present on the server
     871         263 :         mAttributePathExpandIterator.ResetCurrentCluster();
     872         263 :         mAttributeEncoderState = AttributeValueEncoder::AttributeEncodeState();
     873             :     }
     874             : 
     875             :     // ReportScheduler will take care of verifying the reportability of the handler and schedule the run
     876         345 :     mObserver->OnBecameReportable(this);
     877         345 : }
     878             : 
     879        6704 : Transport::SecureSession * ReadHandler::GetSession() const
     880             : {
     881        6704 :     if (!mSessionHandle)
     882             :     {
     883           0 :         return nullptr;
     884             :     }
     885        6704 :     return mSessionHandle->AsSecureSession();
     886             : }
     887             : 
     888          19 : void ReadHandler::ForceDirtyState()
     889             : {
     890          19 :     SetStateFlag(ReadHandlerFlags::ForceDirty);
     891          19 : }
     892             : 
     893        6064 : void ReadHandler::SetStateFlag(ReadHandlerFlags aFlag, bool aValue)
     894             : {
     895        6064 :     bool oldReportable = ShouldStartReporting();
     896        6064 :     mFlags.Set(aFlag, aValue);
     897             : 
     898             :     // If we became reportable, schedule a reporting run.
     899        6064 :     if (!oldReportable && ShouldStartReporting())
     900             :     {
     901             :         // If we became reportable, the scheduler will schedule a run as soon as allowed
     902          13 :         mObserver->OnBecameReportable(this);
     903             :     }
     904        6064 : }
     905             : 
     906        1169 : void ReadHandler::ClearStateFlag(ReadHandlerFlags aFlag)
     907             : {
     908        1169 :     SetStateFlag(aFlag, false);
     909        1169 : }
     910             : 
     911             : } // namespace app
     912             : } // namespace chip

Generated by: LCOV version 1.14