LCOV - code coverage report
Current view: top level - app - CommandHandler.cpp (source / functions) Hit Total Coverage
Test: lcov_final.info Lines: 329 413 79.7 %
Date: 2024-02-15 08:20:41 Functions: 35 39 89.7 %

          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 object for a CHIP IM Invoke Command Handler
      22             :  *
      23             :  */
      24             : 
      25             : #include "CommandHandler.h"
      26             : #include "InteractionModelEngine.h"
      27             : #include "RequiredPrivilege.h"
      28             : #include "messaging/ExchangeContext.h"
      29             : 
      30             : #include <access/AccessControl.h>
      31             : #include <app-common/zap-generated/cluster-objects.h>
      32             : #include <app/RequiredPrivilege.h>
      33             : #include <app/util/MatterCallbacks.h>
      34             : #include <credentials/GroupDataProvider.h>
      35             : #include <lib/core/CHIPConfig.h>
      36             : #include <lib/core/TLVData.h>
      37             : #include <lib/core/TLVUtilities.h>
      38             : #include <lib/support/TypeTraits.h>
      39             : #include <platform/LockTracker.h>
      40             : #include <protocols/secure_channel/Constants.h>
      41             : 
      42             : namespace chip {
      43             : namespace app {
      44             : using Status = Protocols::InteractionModel::Status;
      45             : 
      46          49 : CommandHandler::CommandHandler(Callback * apCallback) :
      47          49 :     mpCallback(apCallback), mResponseSenderDone(HandleOnResponseSenderDone, this), mSuppressResponse(false)
      48          49 : {}
      49             : 
      50           5 : CommandHandler::CommandHandler(TestOnlyMarker aTestMarker, Callback * apCallback, CommandPathRegistry * apCommandPathRegistry) :
      51           5 :     CommandHandler(apCallback)
      52             : {
      53           5 :     mMaxPathsPerInvoke   = apCommandPathRegistry->MaxSize();
      54           5 :     mCommandPathRegistry = apCommandPathRegistry;
      55           5 : }
      56             : 
      57          65 : CHIP_ERROR CommandHandler::AllocateBuffer()
      58             : {
      59          65 :     if (!mBufferAllocated)
      60             :     {
      61          46 :         mCommandMessageWriter.Reset();
      62             : 
      63          46 :         System::PacketBufferHandle commandPacket = System::PacketBufferHandle::New(chip::app::kMaxSecureSduLengthBytes);
      64          46 :         VerifyOrReturnError(!commandPacket.IsNull(), CHIP_ERROR_NO_MEMORY);
      65             : 
      66          46 :         mCommandMessageWriter.Init(std::move(commandPacket));
      67          46 :         ReturnErrorOnFailure(mInvokeResponseBuilder.InitWithEndBufferReserved(&mCommandMessageWriter));
      68             : 
      69          46 :         if (mReserveSpaceForMoreChunkMessages)
      70             :         {
      71          10 :             ReturnErrorOnFailure(mInvokeResponseBuilder.ReserveSpaceForMoreChunkedMessages());
      72             :         }
      73             : 
      74             :         // Sending an InvokeResponse to an InvokeResponse is going to be removed from the spec soon.
      75             :         // It was never implemented in the SDK, and there are no command responses that expect a
      76             :         // command response. This means we will never receive an InvokeResponse Message in response
      77             :         // to an InvokeResponse Message that we are sending. This means that the only response
      78             :         // we are expecting to receive in response to an InvokeResponse Message that we are
      79             :         // sending-out is a status when we are chunking multiple responses. As a result, to satisfy the
      80             :         // condition that we don't set SuppressResponse to true while also setting
      81             :         // MoreChunkedMessages to true, we are hardcoding the value to false here.
      82          46 :         mInvokeResponseBuilder.SuppressResponse(/* aSuppressResponse = */ false);
      83          46 :         ReturnErrorOnFailure(mInvokeResponseBuilder.GetError());
      84             : 
      85          46 :         mInvokeResponseBuilder.CreateInvokeResponses(/* aReserveEndBuffer = */ true);
      86          46 :         ReturnErrorOnFailure(mInvokeResponseBuilder.GetError());
      87             : 
      88          46 :         mBufferAllocated = true;
      89          46 :         MoveToState(State::NewResponseMessage);
      90          46 :     }
      91             : 
      92          65 :     return CHIP_NO_ERROR;
      93             : }
      94             : 
      95          31 : void CommandHandler::OnInvokeCommandRequest(Messaging::ExchangeContext * ec, const PayloadHeader & payloadHeader,
      96             :                                             System::PacketBufferHandle && payload, bool isTimedInvoke)
      97             : {
      98          31 :     System::PacketBufferHandle response;
      99          31 :     Status status = Status::Failure;
     100          31 :     VerifyOrDieWithMsg(ec != nullptr, DataManagement, "Incoming exchange context should not be null");
     101          31 :     VerifyOrDieWithMsg(mState == State::Idle, DataManagement, "state should be Idle");
     102             : 
     103             :     // NOTE: we already know this is an InvokeCommand Request message because we explicitly registered with the
     104             :     // Exchange Manager for unsolicited InvokeCommand Requests.
     105          31 :     mResponseSender.SetExchangeContext(ec);
     106             : 
     107             :     // Use the RAII feature, if this is the only Handle when this function returns, DecrementHoldOff will trigger sending response.
     108             :     // TODO: This is broken!  If something under here returns error, we will try
     109             :     // to StartSendingCommandResponses(), and then our caller will try to send a status
     110             :     // response too.  Figure out at what point it's our responsibility to
     111             :     // handler errors vs our caller's.
     112          31 :     Handle workHandle(this);
     113             : 
     114             :     // TODO(#30453): It should be possible for SetExchangeContext to internally call WillSendMessage.
     115             :     // Unfortunately, doing so would require us to either:
     116             :     // * Make TestCommandInteraction a friend of CommandResponseSender to allow it to set the exchange
     117             :     //   context without calling WillSendMessage, or
     118             :     // * Understand why unit tests fail when WillSendMessage is called during the execution of
     119             :     //   SetExchangeContext.
     120          31 :     mResponseSender.WillSendMessage();
     121          31 :     status = ProcessInvokeRequest(std::move(payload), isTimedInvoke);
     122          31 :     if (status != Status::Success)
     123             :     {
     124           3 :         mResponseSender.SendStatusResponse(status);
     125           3 :         mSentStatusResponse = true;
     126             :     }
     127             : 
     128          31 :     mGoneAsync = true;
     129          31 : }
     130             : 
     131          36 : CHIP_ERROR CommandHandler::ValidateInvokeRequestMessageAndBuildRegistry(InvokeRequestMessage::Parser & invokeRequestMessage)
     132             : {
     133          36 :     CHIP_ERROR err          = CHIP_NO_ERROR;
     134          36 :     size_t commandCount     = 0;
     135          36 :     bool commandRefExpected = false;
     136          36 :     InvokeRequests::Parser invokeRequests;
     137             : 
     138          36 :     ReturnErrorOnFailure(invokeRequestMessage.GetInvokeRequests(&invokeRequests));
     139             :     TLV::TLVReader invokeRequestsReader;
     140          36 :     invokeRequests.GetReader(&invokeRequestsReader);
     141             : 
     142          36 :     ReturnErrorOnFailure(TLV::Utilities::Count(invokeRequestsReader, commandCount, false /* recurse */));
     143             : 
     144             :     // If this is a GroupRequest the only thing to check is that there is only one
     145             :     // CommandDataIB.
     146          36 :     if (IsGroupRequest())
     147             :     {
     148           0 :         VerifyOrReturnError(commandCount == 1, CHIP_ERROR_INVALID_ARGUMENT);
     149           0 :         return CHIP_NO_ERROR;
     150             :     }
     151             :     // While technically any commandCount == 1 should already be unique and does not need
     152             :     // any further validation, we do need to read and populate the registry to help
     153             :     // in building the InvokeResponse.
     154             : 
     155          36 :     VerifyOrReturnError(commandCount <= MaxPathsPerInvoke(), CHIP_ERROR_INVALID_ARGUMENT);
     156             : 
     157             :     // If there is more than one CommandDataIB, spec states that CommandRef must be provided.
     158          34 :     commandRefExpected = commandCount > 1;
     159             : 
     160          69 :     while (CHIP_NO_ERROR == (err = invokeRequestsReader.Next()))
     161             :     {
     162          37 :         VerifyOrReturnError(TLV::AnonymousTag() == invokeRequestsReader.GetTag(), CHIP_ERROR_INVALID_ARGUMENT);
     163          36 :         CommandDataIB::Parser commandData;
     164          36 :         ReturnErrorOnFailure(commandData.Init(invokeRequestsReader));
     165             : 
     166             :         // First validate that we can get a ConcreteCommandPath.
     167          36 :         CommandPathIB::Parser commandPath;
     168          36 :         ConcreteCommandPath concretePath(0, 0, 0);
     169          36 :         ReturnErrorOnFailure(commandData.GetPath(&commandPath));
     170          36 :         ReturnErrorOnFailure(commandPath.GetConcreteCommandPath(concretePath));
     171             : 
     172             :         // Grab the CommandRef if there is one, and validate that it's there when it
     173             :         // has to be.
     174          35 :         Optional<uint16_t> commandRef;
     175             :         uint16_t ref;
     176          35 :         err = commandData.GetRef(&ref);
     177          35 :         VerifyOrReturnError(err == CHIP_NO_ERROR || err == CHIP_END_OF_TLV, err);
     178          35 :         if (err == CHIP_END_OF_TLV && commandRefExpected)
     179             :         {
     180           0 :             return CHIP_ERROR_INVALID_ARGUMENT;
     181             :         }
     182          35 :         if (err == CHIP_NO_ERROR)
     183             :         {
     184           4 :             commandRef.SetValue(ref);
     185             :         }
     186             : 
     187             :         // Adding can fail if concretePath is not unique, or if commandRef is a value
     188             :         // and is not unique, or if we have already added more paths than we support.
     189          35 :         ReturnErrorOnFailure(GetCommandPathRegistry().Add(concretePath, commandRef));
     190          35 :     }
     191             : 
     192             :     // It's OK/expected to have reached the end of the container without failure.
     193          33 :     if (CHIP_END_OF_TLV == err)
     194             :     {
     195          33 :         err = CHIP_NO_ERROR;
     196             :     }
     197          33 :     ReturnErrorOnFailure(err);
     198          33 :     return invokeRequestMessage.ExitContainer();
     199             : }
     200             : 
     201          39 : Status CommandHandler::ProcessInvokeRequest(System::PacketBufferHandle && payload, bool isTimedInvoke)
     202             : {
     203          39 :     CHIP_ERROR err = CHIP_NO_ERROR;
     204          39 :     System::PacketBufferTLVReader reader;
     205          39 :     InvokeRequestMessage::Parser invokeRequestMessage;
     206          39 :     InvokeRequests::Parser invokeRequests;
     207          39 :     reader.Init(std::move(payload));
     208          39 :     VerifyOrReturnError(invokeRequestMessage.Init(reader) == CHIP_NO_ERROR, Status::InvalidAction);
     209             : #if CHIP_CONFIG_IM_PRETTY_PRINT
     210          38 :     invokeRequestMessage.PrettyPrint();
     211             : #endif
     212          38 :     if (mResponseSender.IsForGroup())
     213             :     {
     214           0 :         SetGroupRequest(true);
     215             :     }
     216             : 
     217          38 :     VerifyOrReturnError(invokeRequestMessage.GetSuppressResponse(&mSuppressResponse) == CHIP_NO_ERROR, Status::InvalidAction);
     218          38 :     VerifyOrReturnError(invokeRequestMessage.GetTimedRequest(&mTimedRequest) == CHIP_NO_ERROR, Status::InvalidAction);
     219          38 :     VerifyOrReturnError(invokeRequestMessage.GetInvokeRequests(&invokeRequests) == CHIP_NO_ERROR, Status::InvalidAction);
     220          38 :     VerifyOrReturnError(mTimedRequest == isTimedInvoke, Status::TimedRequestMismatch);
     221             : 
     222             :     {
     223          36 :         InvokeRequestMessage::Parser validationInvokeRequestMessage = invokeRequestMessage;
     224          36 :         VerifyOrReturnError(ValidateInvokeRequestMessageAndBuildRegistry(validationInvokeRequestMessage) == CHIP_NO_ERROR,
     225             :                             Status::InvalidAction);
     226             :     }
     227             : 
     228             :     TLV::TLVReader invokeRequestsReader;
     229          32 :     invokeRequests.GetReader(&invokeRequestsReader);
     230             : 
     231          32 :     size_t commandCount = 0;
     232          32 :     VerifyOrReturnError(TLV::Utilities::Count(invokeRequestsReader, commandCount, false /* recurse */) == CHIP_NO_ERROR,
     233             :                         Status::InvalidAction);
     234          32 :     if (commandCount > 1)
     235             :     {
     236           2 :         mReserveSpaceForMoreChunkMessages = true;
     237             :     }
     238             : 
     239          66 :     while (CHIP_NO_ERROR == (err = invokeRequestsReader.Next()))
     240             :     {
     241          34 :         VerifyOrReturnError(TLV::AnonymousTag() == invokeRequestsReader.GetTag(), Status::InvalidAction);
     242          34 :         CommandDataIB::Parser commandData;
     243          34 :         VerifyOrReturnError(commandData.Init(invokeRequestsReader) == CHIP_NO_ERROR, Status::InvalidAction);
     244          34 :         Status status = Status::Success;
     245          34 :         if (IsGroupRequest())
     246             :         {
     247           0 :             status = ProcessGroupCommandDataIB(commandData);
     248             :         }
     249             :         else
     250             :         {
     251          34 :             status = ProcessCommandDataIB(commandData);
     252             :         }
     253          34 :         if (status != Status::Success)
     254             :         {
     255           0 :             return status;
     256             :         }
     257             :     }
     258             : 
     259             :     // if we have exhausted this container
     260          32 :     if (CHIP_END_OF_TLV == err)
     261             :     {
     262          32 :         err = CHIP_NO_ERROR;
     263             :     }
     264          32 :     VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
     265          32 :     VerifyOrReturnError(invokeRequestMessage.ExitContainer() == CHIP_NO_ERROR, Status::InvalidAction);
     266          32 :     return Status::Success;
     267          39 : }
     268             : 
     269          32 : void CommandHandler::Close()
     270             : {
     271          32 :     mSuppressResponse = false;
     272          32 :     MoveToState(State::AwaitingDestruction);
     273             : 
     274             :     // We must finish all async work before we can shut down a CommandHandler. The actual CommandHandler MUST finish their work
     275             :     // in reasonable time or there is a bug. The only case for releasing CommandHandler without CommandHandler::Handle releasing its
     276             :     // reference is the stack shutting down, in which case Close() is not called. So the below check should always pass.
     277          32 :     VerifyOrDieWithMsg(mPendingWork == 0, DataManagement, "CommandHandler::Close() called with %u unfinished async work items",
     278             :                        static_cast<unsigned int>(mPendingWork));
     279             : 
     280          32 :     if (mpCallback)
     281             :     {
     282          32 :         mpCallback->OnDone(*this);
     283             :     }
     284          32 : }
     285             : 
     286           0 : void CommandHandler::HandleOnResponseSenderDone(void * context)
     287             : {
     288           0 :     CommandHandler * const _this = static_cast<CommandHandler *>(context);
     289           0 :     VerifyOrDie(_this != nullptr);
     290             : 
     291           0 :     _this->Close();
     292           0 : }
     293             : 
     294          34 : void CommandHandler::IncrementHoldOff()
     295             : {
     296          34 :     mPendingWork++;
     297          34 : }
     298             : 
     299          34 : void CommandHandler::DecrementHoldOff()
     300             : {
     301          34 :     mPendingWork--;
     302          34 :     ChipLogDetail(DataManagement, "Decreasing reference count for CommandHandler, remaining %u",
     303             :                   static_cast<unsigned int>(mPendingWork));
     304          34 :     if (mPendingWork != 0)
     305             :     {
     306           3 :         return;
     307             :     }
     308             : 
     309          31 :     if (!mSentStatusResponse)
     310             :     {
     311          28 :         if (!mResponseSender.HasExchangeContext())
     312             :         {
     313           0 :             ChipLogProgress(DataManagement, "Skipping command response: exchange context is null");
     314             :         }
     315          28 :         else if (!IsGroupRequest())
     316             :         {
     317          28 :             CHIP_ERROR err = StartSendingCommandResponses();
     318          28 :             if (err != CHIP_NO_ERROR)
     319             :             {
     320           2 :                 ChipLogError(DataManagement, "Failed to send command response: %" CHIP_ERROR_FORMAT, err.Format());
     321             :                 // TODO(#30453): It should be our responsibility to send a Failure StatusResponse to the requestor
     322             :                 // if there is a SessionHandle, but legacy unit tests explicitly check the behavior where
     323             :                 // CommandHandler does not send any message. Changing this behavior should be done in a standalone
     324             :                 // PR where only that specific change is made. Here is a possible solution that should
     325             :                 // be done that fulfills our responsibility to send a Failure StatusResponse, but this causes unit
     326             :                 // tests to start failing.
     327             :                 //   ```
     328             :                 //   if (mResponseSender.HasSessionHandle())
     329             :                 //   {
     330             :                 //       mResponseSender.SendStatusResponse(Status::Failure);
     331             :                 //   }
     332             :                 //   Close();
     333             :                 //   return;
     334             :                 //   ```
     335             :             }
     336             :         }
     337             :     }
     338             : 
     339          31 :     if (mResponseSender.AwaitingStatusResponse())
     340             :     {
     341             :         // If we are awaiting a status response, we want to call Close() only once the response sender is done.
     342             :         // Therefore, register to be notified when CommandResponseSender is done.
     343           0 :         mResponseSender.RegisterOnResponseSenderDoneCallback(&mResponseSenderDone);
     344           0 :         return;
     345             :     }
     346          31 :     Close();
     347             : }
     348             : 
     349          30 : CHIP_ERROR CommandHandler::StartSendingCommandResponses()
     350             : {
     351          30 :     VerifyOrReturnError(mPendingWork == 0, CHIP_ERROR_INCORRECT_STATE);
     352          30 :     VerifyOrReturnError(mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
     353          28 :     VerifyOrReturnError(mResponseSender.HasExchangeContext(), CHIP_ERROR_INCORRECT_STATE);
     354             : 
     355          28 :     ReturnErrorOnFailure(FinalizeLastInvokeResponseMessage());
     356          28 :     ReturnErrorOnFailure(mResponseSender.StartSendingCommandResponses());
     357          27 :     return CHIP_NO_ERROR;
     358             : }
     359             : 
     360             : namespace {
     361             : // We use this when the sender did not actually provide a CommandFields struct,
     362             : // to avoid downstream consumers having to worry about cases when there is or is
     363             : // not a struct available.  We use an empty struct with anonymous tag, since we
     364             : // can't use a context tag at top level, and consumers should not care about the
     365             : // tag here).
     366             : constexpr uint8_t sNoFields[] = {
     367             :     CHIP_TLV_STRUCTURE(CHIP_TLV_TAG_ANONYMOUS),
     368             :     CHIP_TLV_END_OF_CONTAINER,
     369             : };
     370             : } // anonymous namespace
     371             : 
     372          34 : Status CommandHandler::ProcessCommandDataIB(CommandDataIB::Parser & aCommandElement)
     373             : {
     374          34 :     CHIP_ERROR err = CHIP_NO_ERROR;
     375          34 :     CommandPathIB::Parser commandPath;
     376          34 :     ConcreteCommandPath concretePath(0, 0, 0);
     377             :     TLV::TLVReader commandDataReader;
     378             : 
     379             :     // NOTE: errors may occur before the concrete command path is even fully decoded.
     380             : 
     381          34 :     err = aCommandElement.GetPath(&commandPath);
     382          34 :     VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
     383             : 
     384          34 :     err = commandPath.GetConcreteCommandPath(concretePath);
     385          34 :     VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
     386             : 
     387             :     {
     388          34 :         Status commandExists = mpCallback->CommandExists(concretePath);
     389          34 :         if (commandExists != Status::Success)
     390             :         {
     391           7 :             ChipLogDetail(DataManagement, "No command " ChipLogFormatMEI " in Cluster " ChipLogFormatMEI " on Endpoint 0x%x",
     392             :                           ChipLogValueMEI(concretePath.mCommandId), ChipLogValueMEI(concretePath.mClusterId),
     393             :                           concretePath.mEndpointId);
     394           7 :             return FallibleAddStatus(concretePath, commandExists) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
     395             :         }
     396             :     }
     397             : 
     398          27 :     VerifyOrExit(mResponseSender.HasSessionHandle(), err = CHIP_ERROR_INCORRECT_STATE);
     399             : 
     400             :     {
     401          27 :         Access::SubjectDescriptor subjectDescriptor = GetSubjectDescriptor();
     402          27 :         Access::RequestPath requestPath{ .cluster = concretePath.mClusterId, .endpoint = concretePath.mEndpointId };
     403          27 :         Access::Privilege requestPrivilege = RequiredPrivilege::ForInvokeCommand(concretePath);
     404          27 :         err                                = Access::GetAccessControl().Check(subjectDescriptor, requestPath, requestPrivilege);
     405          27 :         if (err != CHIP_NO_ERROR)
     406             :         {
     407           0 :             if (err != CHIP_ERROR_ACCESS_DENIED)
     408             :             {
     409           0 :                 return FallibleAddStatus(concretePath, Status::Failure) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
     410             :             }
     411             :             // TODO: when wildcard invokes are supported, handle them to discard rather than fail with status
     412           0 :             return FallibleAddStatus(concretePath, Status::UnsupportedAccess) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
     413             :         }
     414             :     }
     415             : 
     416          27 :     if (CommandNeedsTimedInvoke(concretePath.mClusterId, concretePath.mCommandId) && !IsTimedInvoke())
     417             :     {
     418             :         // TODO: when wildcard invokes are supported, discard a
     419             :         // wildcard-expanded path instead of returning a status.
     420           0 :         return FallibleAddStatus(concretePath, Status::NeedsTimedInteraction) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
     421             :     }
     422             : 
     423          27 :     if (CommandIsFabricScoped(concretePath.mClusterId, concretePath.mCommandId))
     424             :     {
     425             :         // SPEC: Else if the command in the path is fabric-scoped and there is no accessing fabric,
     426             :         // a CommandStatusIB SHALL be generated with the UNSUPPORTED_ACCESS Status Code.
     427             : 
     428             :         // Fabric-scoped commands are not allowed before a specific accessing fabric is available.
     429             :         // This is mostly just during a PASE session before AddNOC.
     430           0 :         if (GetAccessingFabricIndex() == kUndefinedFabricIndex)
     431             :         {
     432             :             // TODO: when wildcard invokes are supported, discard a
     433             :             // wildcard-expanded path instead of returning a status.
     434           0 :             return FallibleAddStatus(concretePath, Status::UnsupportedAccess) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
     435             :         }
     436             :     }
     437             : 
     438          27 :     err = aCommandElement.GetFields(&commandDataReader);
     439          27 :     if (CHIP_END_OF_TLV == err)
     440             :     {
     441           2 :         ChipLogDetail(DataManagement,
     442             :                       "Received command without data for Endpoint=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI,
     443             :                       concretePath.mEndpointId, ChipLogValueMEI(concretePath.mClusterId), ChipLogValueMEI(concretePath.mCommandId));
     444           2 :         commandDataReader.Init(sNoFields);
     445           2 :         err = commandDataReader.Next();
     446             :     }
     447          27 :     if (CHIP_NO_ERROR == err)
     448             :     {
     449          27 :         ChipLogDetail(DataManagement, "Received command for Endpoint=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI,
     450             :                       concretePath.mEndpointId, ChipLogValueMEI(concretePath.mClusterId), ChipLogValueMEI(concretePath.mCommandId));
     451          27 :         SuccessOrExit(err = MatterPreCommandReceivedCallback(concretePath, GetSubjectDescriptor()));
     452          27 :         mpCallback->DispatchCommand(*this, concretePath, commandDataReader);
     453          27 :         MatterPostCommandReceivedCallback(concretePath, GetSubjectDescriptor());
     454             :     }
     455             : 
     456           0 : exit:
     457          27 :     if (err != CHIP_NO_ERROR)
     458             :     {
     459           0 :         return FallibleAddStatus(concretePath, Status::InvalidCommand) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
     460             :     }
     461             : 
     462             :     // We have handled the error status above and put the error status in response, now return success status so we can process
     463             :     // other commands in the invoke request.
     464          27 :     return Status::Success;
     465             : }
     466             : 
     467           0 : Status CommandHandler::ProcessGroupCommandDataIB(CommandDataIB::Parser & aCommandElement)
     468             : {
     469           0 :     CHIP_ERROR err = CHIP_NO_ERROR;
     470           0 :     CommandPathIB::Parser commandPath;
     471             :     TLV::TLVReader commandDataReader;
     472             :     ClusterId clusterId;
     473             :     CommandId commandId;
     474             :     GroupId groupId;
     475             :     FabricIndex fabric;
     476             : 
     477           0 :     Credentials::GroupDataProvider::GroupEndpoint mapping;
     478           0 :     Credentials::GroupDataProvider * groupDataProvider = Credentials::GetGroupDataProvider();
     479             :     Credentials::GroupDataProvider::EndpointIterator * iterator;
     480             : 
     481           0 :     err = aCommandElement.GetPath(&commandPath);
     482           0 :     VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
     483             : 
     484           0 :     err = commandPath.GetGroupCommandPath(&clusterId, &commandId);
     485           0 :     VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
     486             : 
     487           0 :     groupId = mResponseSender.GetGroupId();
     488           0 :     fabric  = GetAccessingFabricIndex();
     489             : 
     490           0 :     ChipLogDetail(DataManagement, "Received group command for Group=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI,
     491             :                   groupId, ChipLogValueMEI(clusterId), ChipLogValueMEI(commandId));
     492             : 
     493           0 :     err = aCommandElement.GetFields(&commandDataReader);
     494           0 :     if (CHIP_END_OF_TLV == err)
     495             :     {
     496           0 :         ChipLogDetail(DataManagement,
     497             :                       "Received command without data for Group=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI, groupId,
     498             :                       ChipLogValueMEI(clusterId), ChipLogValueMEI(commandId));
     499           0 :         commandDataReader.Init(sNoFields);
     500           0 :         err = commandDataReader.Next();
     501           0 :         VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
     502             :     }
     503           0 :     VerifyOrReturnError(err == CHIP_NO_ERROR, Status::Failure);
     504             : 
     505             :     // Per spec, we do the "is this a timed command?" check for every path, but
     506             :     // since all paths that fail it just get silently discarded we can do it
     507             :     // once up front and discard all the paths at once.  Ordering with respect
     508             :     // to ACL and command presence checks does not matter, because the behavior
     509             :     // is the same for all of them: ignore the path.
     510           0 :     if (CommandNeedsTimedInvoke(clusterId, commandId))
     511             :     {
     512             :         // Group commands are never timed.
     513           0 :         return Status::Success;
     514             :     }
     515             : 
     516             :     // No check for `CommandIsFabricScoped` unlike in `ProcessCommandDataIB()` since group commands
     517             :     // always have an accessing fabric, by definition.
     518             : 
     519             :     // Find which endpoints can process the command, and dispatch to them.
     520           0 :     iterator = groupDataProvider->IterateEndpoints(fabric);
     521           0 :     VerifyOrReturnError(iterator != nullptr, Status::Failure);
     522             : 
     523           0 :     while (iterator->Next(mapping))
     524             :     {
     525           0 :         if (groupId != mapping.group_id)
     526             :         {
     527           0 :             continue;
     528             :         }
     529             : 
     530           0 :         ChipLogDetail(DataManagement,
     531             :                       "Processing group command for Endpoint=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI,
     532             :                       mapping.endpoint_id, ChipLogValueMEI(clusterId), ChipLogValueMEI(commandId));
     533             : 
     534           0 :         const ConcreteCommandPath concretePath(mapping.endpoint_id, clusterId, commandId);
     535             : 
     536           0 :         if (mpCallback->CommandExists(concretePath) != Status::Success)
     537             :         {
     538           0 :             ChipLogDetail(DataManagement, "No command " ChipLogFormatMEI " in Cluster " ChipLogFormatMEI " on Endpoint 0x%x",
     539             :                           ChipLogValueMEI(commandId), ChipLogValueMEI(clusterId), mapping.endpoint_id);
     540             : 
     541           0 :             continue;
     542             :         }
     543             : 
     544             :         {
     545           0 :             Access::SubjectDescriptor subjectDescriptor = GetSubjectDescriptor();
     546           0 :             Access::RequestPath requestPath{ .cluster = concretePath.mClusterId, .endpoint = concretePath.mEndpointId };
     547           0 :             Access::Privilege requestPrivilege = RequiredPrivilege::ForInvokeCommand(concretePath);
     548           0 :             err                                = Access::GetAccessControl().Check(subjectDescriptor, requestPath, requestPrivilege);
     549           0 :             if (err != CHIP_NO_ERROR)
     550             :             {
     551             :                 // NOTE: an expected error is CHIP_ERROR_ACCESS_DENIED, but there could be other unexpected errors;
     552             :                 // therefore, keep processing subsequent commands, and if any errors continue, those subsequent
     553             :                 // commands will likewise fail.
     554           0 :                 continue;
     555             :             }
     556             :         }
     557           0 :         if ((err = MatterPreCommandReceivedCallback(concretePath, GetSubjectDescriptor())) == CHIP_NO_ERROR)
     558             :         {
     559           0 :             TLV::TLVReader dataReader(commandDataReader);
     560           0 :             mpCallback->DispatchCommand(*this, concretePath, dataReader);
     561           0 :             MatterPostCommandReceivedCallback(concretePath, GetSubjectDescriptor());
     562             :         }
     563             :         else
     564             :         {
     565           0 :             ChipLogError(DataManagement,
     566             :                          "Error when calling MatterPreCommandReceivedCallback for Endpoint=%u Cluster=" ChipLogFormatMEI
     567             :                          " Command=" ChipLogFormatMEI " : %" CHIP_ERROR_FORMAT,
     568             :                          mapping.endpoint_id, ChipLogValueMEI(clusterId), ChipLogValueMEI(commandId), err.Format());
     569           0 :             continue;
     570             :         }
     571             :     }
     572           0 :     iterator->Release();
     573           0 :     return Status::Success;
     574             : }
     575             : 
     576          38 : CHIP_ERROR CommandHandler::TryAddStatusInternal(const ConcreteCommandPath & aCommandPath, const StatusIB & aStatus)
     577             : {
     578          38 :     ReturnErrorOnFailure(PrepareStatus(aCommandPath));
     579          37 :     CommandStatusIB::Builder & commandStatus = mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().GetStatus();
     580          37 :     StatusIB::Builder & statusIBBuilder      = commandStatus.CreateErrorStatus();
     581          37 :     ReturnErrorOnFailure(commandStatus.GetError());
     582          37 :     statusIBBuilder.EncodeStatusIB(aStatus);
     583          37 :     ReturnErrorOnFailure(statusIBBuilder.GetError());
     584          37 :     return FinishStatus();
     585             : }
     586             : 
     587          37 : CHIP_ERROR CommandHandler::AddStatusInternal(const ConcreteCommandPath & aCommandPath, const StatusIB & aStatus)
     588             : {
     589          75 :     return TryAddingResponse([&]() -> CHIP_ERROR { return TryAddStatusInternal(aCommandPath, aStatus); });
     590             : }
     591             : 
     592          21 : void CommandHandler::AddStatus(const ConcreteCommandPath & aCommandPath, const Protocols::InteractionModel::Status aStatus,
     593             :                                const char * context)
     594             : {
     595             :     // Return early in case of requests targeted to a group, since they should not add a response.
     596          21 :     VerifyOrReturn(!IsGroupRequest());
     597          21 :     VerifyOrDie(FallibleAddStatus(aCommandPath, aStatus, context) == CHIP_NO_ERROR);
     598             : }
     599             : 
     600          35 : CHIP_ERROR CommandHandler::FallibleAddStatus(const ConcreteCommandPath & path, const Protocols::InteractionModel::Status status,
     601             :                                              const char * context)
     602             : {
     603             : 
     604          35 :     if (status != Status::Success)
     605             :     {
     606          15 :         if (context == nullptr)
     607             :         {
     608          15 :             context = "no additional context";
     609             :         }
     610             : 
     611          15 :         ChipLogError(DataManagement,
     612             :                      "Endpoint=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI " status " ChipLogFormatIMStatus " (%s)",
     613             :                      path.mEndpointId, ChipLogValueMEI(path.mClusterId), ChipLogValueMEI(path.mCommandId),
     614             :                      ChipLogValueIMStatus(status), context);
     615             :     }
     616             : 
     617          35 :     return AddStatusInternal(path, StatusIB(status));
     618             : }
     619             : 
     620           1 : CHIP_ERROR CommandHandler::AddClusterSpecificSuccess(const ConcreteCommandPath & aCommandPath, ClusterStatus aClusterStatus)
     621             : {
     622           1 :     return AddStatusInternal(aCommandPath, StatusIB(Status::Success, aClusterStatus));
     623             : }
     624             : 
     625           1 : CHIP_ERROR CommandHandler::AddClusterSpecificFailure(const ConcreteCommandPath & aCommandPath, ClusterStatus aClusterStatus)
     626             : {
     627           1 :     return AddStatusInternal(aCommandPath, StatusIB(Status::Failure, aClusterStatus));
     628             : }
     629             : 
     630          19 : CHIP_ERROR CommandHandler::PrepareInvokeResponseCommand(const ConcreteCommandPath & aResponseCommandPath,
     631             :                                                         const CommandHandler::InvokeResponseParameters & aPrepareParameters)
     632             : {
     633          19 :     auto commandPathRegistryEntry = GetCommandPathRegistry().Find(aPrepareParameters.mRequestCommandPath);
     634          19 :     VerifyOrReturnValue(commandPathRegistryEntry.HasValue(), CHIP_ERROR_INCORRECT_STATE);
     635             : 
     636          19 :     return PrepareInvokeResponseCommand(commandPathRegistryEntry.Value(), aResponseCommandPath,
     637          38 :                                         aPrepareParameters.mStartOrEndDataStruct);
     638          19 : }
     639             : 
     640           0 : CHIP_ERROR CommandHandler::PrepareCommand(const ConcreteCommandPath & aResponseCommandPath, bool aStartDataStruct)
     641             : {
     642             :     // Legacy code is calling the deprecated version of PrepareCommand. If we are in a case where
     643             :     // there was a single command in the request, we can just assume this response is triggered by
     644             :     // the single command.
     645           0 :     size_t countOfPathRegistryEntries = GetCommandPathRegistry().Count();
     646             : 
     647             :     // At this point application supports Batch Invoke Commands since CommandPathRegistry has more than 1 entry,
     648             :     // but application is calling the deprecated PrepareCommand. We have no way to determine the associated CommandRef
     649             :     // to put into the InvokeResponse.
     650           0 :     VerifyOrDieWithMsg(countOfPathRegistryEntries == 1, DataManagement,
     651             :                        "Seemingly device supports batch commands, but is calling the deprecated PrepareCommand API");
     652             : 
     653           0 :     auto commandPathRegistryEntry = GetCommandPathRegistry().GetFirstEntry();
     654           0 :     VerifyOrReturnValue(commandPathRegistryEntry.HasValue(), CHIP_ERROR_INCORRECT_STATE);
     655             : 
     656           0 :     return PrepareInvokeResponseCommand(commandPathRegistryEntry.Value(), aResponseCommandPath, aStartDataStruct);
     657           0 : }
     658             : 
     659          19 : CHIP_ERROR CommandHandler::PrepareInvokeResponseCommand(const CommandPathRegistryEntry & apCommandPathRegistryEntry,
     660             :                                                         const ConcreteCommandPath & aCommandPath, bool aStartDataStruct)
     661             : {
     662          19 :     ReturnErrorOnFailure(AllocateBuffer());
     663             : 
     664          19 :     if (!mInternalCallToAddResponseData && mState == State::AddedCommand)
     665             :     {
     666             :         // An attempt is being made to add CommandData InvokeResponse using primitive
     667             :         // CommandHandler APIs. While not recommended, as this potentially leaves the
     668             :         // CommandHandler in an incorrect state upon failure, this approach is permitted
     669             :         // for legacy reasons. To maximize the likelihood of success, particularly when
     670             :         // handling large amounts of data, we try to obtain a new, completely empty
     671             :         // InvokeResponseMessage, as the existing one already has space occupied.
     672           3 :         ReturnErrorOnFailure(FinalizeInvokeResponseMessageAndPrepareNext());
     673             :     }
     674             : 
     675          19 :     CreateBackupForResponseRollback();
     676             :     //
     677             :     // We must not be in the middle of preparing a command, or having prepared or sent one.
     678             :     //
     679          19 :     VerifyOrReturnError(mState == State::NewResponseMessage || mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
     680             : 
     681             :     // TODO(#30453): See if we can pass this back up the stack so caller can provide this instead of taking up
     682             :     // space in CommandHanlder.
     683          19 :     mRefForResponse = apCommandPathRegistryEntry.ref;
     684             : 
     685          19 :     MoveToState(State::Preparing);
     686          19 :     InvokeResponseIBs::Builder & invokeResponses = mInvokeResponseBuilder.GetInvokeResponses();
     687          19 :     InvokeResponseIB::Builder & invokeResponse   = invokeResponses.CreateInvokeResponse();
     688          19 :     ReturnErrorOnFailure(invokeResponses.GetError());
     689             : 
     690          18 :     CommandDataIB::Builder & commandData = invokeResponse.CreateCommand();
     691          18 :     ReturnErrorOnFailure(commandData.GetError());
     692          18 :     CommandPathIB::Builder & path = commandData.CreatePath();
     693          18 :     ReturnErrorOnFailure(commandData.GetError());
     694          18 :     ReturnErrorOnFailure(path.Encode(aCommandPath));
     695          18 :     if (aStartDataStruct)
     696             :     {
     697           7 :         ReturnErrorOnFailure(commandData.GetWriter()->StartContainer(TLV::ContextTag(CommandDataIB::Tag::kFields),
     698             :                                                                      TLV::kTLVType_Structure, mDataElementContainerType));
     699             :     }
     700          18 :     MoveToState(State::AddingCommand);
     701          18 :     return CHIP_NO_ERROR;
     702             : }
     703             : 
     704          15 : CHIP_ERROR CommandHandler::FinishCommand(bool aStartDataStruct)
     705             : {
     706          15 :     VerifyOrReturnError(mState == State::AddingCommand, CHIP_ERROR_INCORRECT_STATE);
     707          15 :     CommandDataIB::Builder & commandData = mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().GetCommand();
     708          15 :     if (aStartDataStruct)
     709             :     {
     710           6 :         ReturnErrorOnFailure(commandData.GetWriter()->EndContainer(mDataElementContainerType));
     711             :     }
     712             : 
     713          15 :     if (mRefForResponse.HasValue())
     714             :     {
     715           7 :         ReturnErrorOnFailure(commandData.Ref(mRefForResponse.Value()));
     716             :     }
     717             : 
     718          15 :     ReturnErrorOnFailure(commandData.EndOfCommandDataIB());
     719          15 :     ReturnErrorOnFailure(mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().EndOfInvokeResponseIB());
     720          15 :     MoveToState(State::AddedCommand);
     721          15 :     return CHIP_NO_ERROR;
     722             : }
     723             : 
     724          38 : CHIP_ERROR CommandHandler::PrepareStatus(const ConcreteCommandPath & aCommandPath)
     725             : {
     726          38 :     ReturnErrorOnFailure(AllocateBuffer());
     727             :     //
     728             :     // We must not be in the middle of preparing a command, or having prepared or sent one.
     729             :     //
     730          38 :     VerifyOrReturnError(mState == State::NewResponseMessage || mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
     731          38 :     if (mState == State::AddedCommand)
     732             :     {
     733           8 :         CreateBackupForResponseRollback();
     734             :     }
     735             : 
     736          38 :     auto commandPathRegistryEntry = GetCommandPathRegistry().Find(aCommandPath);
     737          38 :     VerifyOrReturnError(commandPathRegistryEntry.HasValue(), CHIP_ERROR_INCORRECT_STATE);
     738          38 :     mRefForResponse = commandPathRegistryEntry.Value().ref;
     739             : 
     740          38 :     MoveToState(State::Preparing);
     741          38 :     InvokeResponseIBs::Builder & invokeResponses = mInvokeResponseBuilder.GetInvokeResponses();
     742          38 :     InvokeResponseIB::Builder & invokeResponse   = invokeResponses.CreateInvokeResponse();
     743          38 :     ReturnErrorOnFailure(invokeResponses.GetError());
     744          37 :     CommandStatusIB::Builder & commandStatus = invokeResponse.CreateStatus();
     745          37 :     ReturnErrorOnFailure(commandStatus.GetError());
     746          37 :     CommandPathIB::Builder & path = commandStatus.CreatePath();
     747          37 :     ReturnErrorOnFailure(commandStatus.GetError());
     748          37 :     ReturnErrorOnFailure(path.Encode(aCommandPath));
     749          37 :     MoveToState(State::AddingCommand);
     750          37 :     return CHIP_NO_ERROR;
     751          38 : }
     752             : 
     753          37 : CHIP_ERROR CommandHandler::FinishStatus()
     754             : {
     755          37 :     VerifyOrReturnError(mState == State::AddingCommand, CHIP_ERROR_INCORRECT_STATE);
     756             : 
     757          37 :     CommandStatusIB::Builder & commandStatus = mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().GetStatus();
     758          37 :     if (mRefForResponse.HasValue())
     759             :     {
     760           3 :         ReturnErrorOnFailure(commandStatus.Ref(mRefForResponse.Value()));
     761             :     }
     762             : 
     763          37 :     ReturnErrorOnFailure(mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().GetStatus().EndOfCommandStatusIB());
     764          37 :     ReturnErrorOnFailure(mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().EndOfInvokeResponseIB());
     765          37 :     MoveToState(State::AddedCommand);
     766          37 :     return CHIP_NO_ERROR;
     767             : }
     768             : 
     769          27 : void CommandHandler::CreateBackupForResponseRollback()
     770             : {
     771          27 :     VerifyOrReturn(mState == State::NewResponseMessage || mState == State::AddedCommand);
     772          27 :     VerifyOrReturn(mInvokeResponseBuilder.GetInvokeResponses().GetError() == CHIP_NO_ERROR);
     773          27 :     VerifyOrReturn(mInvokeResponseBuilder.GetError() == CHIP_NO_ERROR);
     774          27 :     mInvokeResponseBuilder.Checkpoint(mBackupWriter);
     775          27 :     mBackupState         = mState;
     776          27 :     mRollbackBackupValid = true;
     777             : }
     778             : 
     779           4 : CHIP_ERROR CommandHandler::RollbackResponse()
     780             : {
     781           4 :     VerifyOrReturnError(mRollbackBackupValid, CHIP_ERROR_INCORRECT_STATE);
     782           4 :     VerifyOrReturnError(mState == State::Preparing || mState == State::AddingCommand, CHIP_ERROR_INCORRECT_STATE);
     783             :     // TODO(#30453): Rollback of mInvokeResponseBuilder should handle resetting
     784             :     // InvokeResponses.
     785           4 :     mInvokeResponseBuilder.GetInvokeResponses().ResetError();
     786           4 :     mInvokeResponseBuilder.Rollback(mBackupWriter);
     787           4 :     MoveToState(mBackupState);
     788           4 :     mRollbackBackupValid = false;
     789           4 :     return CHIP_NO_ERROR;
     790             : }
     791             : 
     792          16 : TLV::TLVWriter * CommandHandler::GetCommandDataIBTLVWriter()
     793             : {
     794          16 :     if (mState != State::AddingCommand)
     795             :     {
     796           0 :         return nullptr;
     797             :     }
     798             : 
     799          16 :     return mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().GetCommand().GetWriter();
     800             : }
     801             : 
     802           0 : FabricIndex CommandHandler::GetAccessingFabricIndex() const
     803             : {
     804           0 :     VerifyOrDie(!mGoneAsync);
     805           0 :     return mResponseSender.GetAccessingFabricIndex();
     806             : }
     807             : 
     808           4 : CommandHandler * CommandHandler::Handle::Get()
     809             : {
     810             :     // Not safe to work with CommandHandler in parallel with other Matter work.
     811           4 :     assertChipStackLockedByCurrentThread();
     812             : 
     813           4 :     return (mMagic == InteractionModelEngine::GetInstance()->GetMagicNumber()) ? mpHandler : nullptr;
     814             : }
     815             : 
     816          60 : void CommandHandler::Handle::Release()
     817             : {
     818          60 :     if (mpHandler != nullptr)
     819             :     {
     820          34 :         if (mMagic == InteractionModelEngine::GetInstance()->GetMagicNumber())
     821             :         {
     822          34 :             mpHandler->DecrementHoldOff();
     823             :         }
     824          34 :         mpHandler = nullptr;
     825          34 :         mMagic    = 0;
     826             :     }
     827          60 : }
     828             : 
     829          34 : CommandHandler::Handle::Handle(CommandHandler * handle)
     830             : {
     831          34 :     if (handle != nullptr)
     832             :     {
     833          34 :         handle->IncrementHoldOff();
     834          34 :         mpHandler = handle;
     835          34 :         mMagic    = InteractionModelEngine::GetInstance()->GetMagicNumber();
     836             :     }
     837          34 : }
     838             : 
     839           5 : CHIP_ERROR CommandHandler::FinalizeInvokeResponseMessageAndPrepareNext()
     840             : {
     841           5 :     ReturnErrorOnFailure(FinalizeInvokeResponseMessage(/* aHasMoreChunks = */ true));
     842             :     // After successfully finalizing InvokeResponseMessage, no buffer should remain
     843             :     // allocated.
     844           5 :     VerifyOrDie(!mBufferAllocated);
     845           5 :     CHIP_ERROR err = AllocateBuffer();
     846           5 :     if (err != CHIP_NO_ERROR)
     847             :     {
     848             :         // TODO(#30453): Improve ResponseDropped calls to occur only when dropping is
     849             :         // definitively guaranteed.
     850             :         // Response dropping is not yet definitive as a subsequent call
     851             :         // to AllocateBuffer might succeed.
     852           0 :         mResponseSender.ResponseDropped();
     853             :     }
     854           5 :     return err;
     855             : }
     856             : 
     857          38 : CHIP_ERROR CommandHandler::FinalizeInvokeResponseMessage(bool aHasMoreChunks)
     858             : {
     859          38 :     System::PacketBufferHandle packet;
     860             : 
     861          38 :     VerifyOrReturnError(mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
     862          38 :     ReturnErrorOnFailure(mInvokeResponseBuilder.GetInvokeResponses().EndOfInvokeResponses());
     863          38 :     if (aHasMoreChunks)
     864             :     {
     865             :         // Unreserving space previously reserved for MoreChunkedMessages is done
     866             :         // in the call to mInvokeResponseBuilder.MoreChunkedMessages.
     867           5 :         mInvokeResponseBuilder.MoreChunkedMessages(aHasMoreChunks);
     868           5 :         ReturnErrorOnFailure(mInvokeResponseBuilder.GetError());
     869             :     }
     870          38 :     ReturnErrorOnFailure(mInvokeResponseBuilder.EndOfInvokeResponseMessage());
     871          38 :     ReturnErrorOnFailure(mCommandMessageWriter.Finalize(&packet));
     872          38 :     mResponseSender.AddInvokeResponseToSend(std::move(packet));
     873          38 :     mBufferAllocated     = false;
     874          38 :     mRollbackBackupValid = false;
     875          38 :     return CHIP_NO_ERROR;
     876          38 : }
     877             : 
     878         246 : const char * CommandHandler::GetStateStr() const
     879             : {
     880             : #if CHIP_DETAIL_LOGGING
     881         246 :     switch (mState)
     882             :     {
     883           0 :     case State::Idle:
     884           0 :         return "Idle";
     885             : 
     886          48 :     case State::NewResponseMessage:
     887          48 :         return "NewResponseMessage";
     888             : 
     889          57 :     case State::Preparing:
     890          57 :         return "Preparing";
     891             : 
     892          55 :     case State::AddingCommand:
     893          55 :         return "AddingCommand";
     894             : 
     895          54 :     case State::AddedCommand:
     896          54 :         return "AddedCommand";
     897             : 
     898           0 :     case State::DispatchResponses:
     899           0 :         return "DispatchResponses";
     900             : 
     901          32 :     case State::AwaitingDestruction:
     902          32 :         return "AwaitingDestruction";
     903             :     }
     904             : #endif // CHIP_DETAIL_LOGGING
     905           0 :     return "N/A";
     906             : }
     907             : 
     908         246 : void CommandHandler::MoveToState(const State aTargetState)
     909             : {
     910         246 :     mState = aTargetState;
     911         246 :     ChipLogDetail(DataManagement, "Command handler moving to [%10.10s]", GetStateStr());
     912         246 : }
     913             : 
     914             : } // namespace app
     915             : } // namespace chip
     916             : 
     917          27 : CHIP_ERROR __attribute__((weak)) MatterPreCommandReceivedCallback(const chip::app::ConcreteCommandPath & commandPath,
     918             :                                                                   const chip::Access::SubjectDescriptor & subjectDescriptor)
     919             : {
     920          27 :     return CHIP_NO_ERROR;
     921             : }
     922          27 : void __attribute__((weak)) MatterPostCommandReceivedCallback(const chip::app::ConcreteCommandPath & commandPath,
     923             :                                                              const chip::Access::SubjectDescriptor & subjectDescriptor)
     924          27 : {}

Generated by: LCOV version 1.14