Matter SDK Coverage Report
Current view: top level - app - CommandHandlerImpl.cpp (source / functions) Coverage Total Hit
Test: SHA:3f9cd168e84cd831b7699126f5296f5c5498690f Lines: 69.9 % 491 343
Test Date: 2026-04-27 19:52:19 Functions: 83.0 % 47 39

            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              : #include <app/CommandHandlerImpl.h>
      19              : 
      20              : #include <access/AccessControl.h>
      21              : #include <access/SubjectDescriptor.h>
      22              : #include <app-common/zap-generated/cluster-objects.h>
      23              : #include <app/MessageDef/StatusIB.h>
      24              : #include <app/StatusResponse.h>
      25              : #include <app/data-model-provider/OperationTypes.h>
      26              : #include <app/util/MatterCallbacks.h>
      27              : #include <credentials/GroupDataProvider.h>
      28              : #include <lib/core/CHIPConfig.h>
      29              : #include <lib/core/TLVData.h>
      30              : #include <lib/core/TLVUtilities.h>
      31              : #include <lib/support/IntrusiveList.h>
      32              : #include <lib/support/TypeTraits.h>
      33              : #include <messaging/ExchangeContext.h>
      34              : #include <platform/LockTracker.h>
      35              : #include <protocols/interaction_model/StatusCode.h>
      36              : #include <protocols/secure_channel/Constants.h>
      37              : #include <transport/raw/GroupcastTesting.h>
      38              : 
      39              : namespace chip {
      40              : namespace app {
      41              : using Status = Protocols::InteractionModel::Status;
      42              : 
      43           77 : CommandHandlerImpl::CommandHandlerImpl(Callback * apCallback) : mpCallback(apCallback), mSuppressResponse(false) {}
      44              : 
      45            8 : CommandHandlerImpl::CommandHandlerImpl(TestOnlyOverrides & aTestOverride, Callback * apCallback) : CommandHandlerImpl(apCallback)
      46              : {
      47            8 :     if (aTestOverride.commandPathRegistry)
      48              :     {
      49            8 :         mMaxPathsPerInvoke   = aTestOverride.commandPathRegistry->MaxSize();
      50            8 :         mCommandPathRegistry = aTestOverride.commandPathRegistry;
      51              :     }
      52            8 :     if (aTestOverride.commandResponder)
      53              :     {
      54            8 :         SetExchangeInterface(aTestOverride.commandResponder);
      55              :     }
      56            8 : }
      57              : 
      58           77 : CommandHandlerImpl::~CommandHandlerImpl()
      59              : {
      60           77 :     InvalidateHandles();
      61           77 : }
      62              : 
      63           99 : CHIP_ERROR CommandHandlerImpl::AllocateBuffer()
      64              : {
      65              :     // We should only allocate a buffer if we will be sending out a response.
      66           99 :     VerifyOrReturnError(ResponsesAccepted(), CHIP_ERROR_INCORRECT_STATE);
      67              : 
      68           99 :     if (!mBufferAllocated)
      69              :     {
      70           68 :         mCommandMessageWriter.Reset();
      71              : 
      72           68 :         const size_t commandBufferMaxSize = mpResponder->GetCommandResponseMaxBufferSize();
      73           68 :         auto commandPacket                = System::PacketBufferHandle::New(commandBufferMaxSize);
      74           68 :         VerifyOrReturnError(!commandPacket.IsNull(), CHIP_ERROR_NO_MEMORY);
      75              :         // On some platforms we can get more available length in the packet than what we requested.
      76              :         // It is vital that we only use up to commandBufferMaxSize for the entire packet and
      77              :         // nothing more.
      78           68 :         uint32_t reservedSize = 0;
      79           68 :         if (commandPacket->AvailableDataLength() > commandBufferMaxSize)
      80              :         {
      81            0 :             reservedSize = static_cast<uint32_t>(commandPacket->AvailableDataLength() - commandBufferMaxSize);
      82              :         }
      83              : 
      84           68 :         mCommandMessageWriter.Init(std::move(commandPacket));
      85           68 :         ReturnErrorOnFailure(mInvokeResponseBuilder.InitWithEndBufferReserved(&mCommandMessageWriter));
      86              : 
      87           68 :         if (mReserveSpaceForMoreChunkMessages)
      88              :         {
      89           10 :             ReturnErrorOnFailure(mInvokeResponseBuilder.ReserveSpaceForMoreChunkedMessages());
      90              :         }
      91              : 
      92              :         // Reserving space for MIC at the end.
      93           68 :         ReturnErrorOnFailure(
      94              :             mInvokeResponseBuilder.GetWriter()->ReserveBuffer(reservedSize + Crypto::CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES));
      95              : 
      96              :         // Sending an InvokeResponse to an InvokeResponse is going to be removed from the spec soon.
      97              :         // It was never implemented in the SDK, and there are no command responses that expect a
      98              :         // command response. This means we will never receive an InvokeResponse Message in response
      99              :         // to an InvokeResponse Message that we are sending. This means that the only response
     100              :         // we are expecting to receive in response to an InvokeResponse Message that we are
     101              :         // sending-out is a status when we are chunking multiple responses. As a result, to satisfy the
     102              :         // condition that we don't set SuppressResponse to true while also setting
     103              :         // MoreChunkedMessages to true, we are hardcoding the value to false here.
     104           68 :         mInvokeResponseBuilder.SuppressResponse(/* aSuppressResponse = */ false);
     105           68 :         ReturnErrorOnFailure(mInvokeResponseBuilder.GetError());
     106              : 
     107           68 :         mInvokeResponseBuilder.CreateInvokeResponses(/* aReserveEndBuffer = */ true);
     108           68 :         ReturnErrorOnFailure(mInvokeResponseBuilder.GetError());
     109              : 
     110           68 :         mBufferAllocated = true;
     111           68 :         MoveToState(State::NewResponseMessage);
     112           68 :     }
     113              : 
     114           99 :     return CHIP_NO_ERROR;
     115              : }
     116              : 
     117           59 : Status CommandHandlerImpl::OnInvokeCommandRequest(CommandHandlerExchangeInterface & commandResponder,
     118              :                                                   System::PacketBufferHandle && payload, bool isTimedInvoke)
     119              : {
     120           59 :     VerifyOrDieWithMsg(mState == State::Idle, DataManagement, "state should be Idle");
     121              : 
     122           59 :     SetExchangeInterface(&commandResponder);
     123              : 
     124              :     // Using RAII here: if this is the only handle remaining, DecrementHoldOff will
     125              :     // call the CommandHandlerImpl::OnDone callback when this function returns.
     126           59 :     Handle workHandle(this);
     127              : 
     128           59 :     Status status = ProcessInvokeRequest(std::move(payload), isTimedInvoke);
     129           59 :     mGoneAsync    = true;
     130          118 :     return status;
     131           59 : }
     132              : 
     133           31 : CHIP_ERROR CommandHandlerImpl::TryAddResponseData(const ConcreteCommandPath & aRequestCommandPath, CommandId aResponseCommandId,
     134              :                                                   const DataModel::EncodableToTLV & aEncodable)
     135              : {
     136           31 :     ConcreteCommandPath responseCommandPath = { aRequestCommandPath.mEndpointId, aRequestCommandPath.mClusterId,
     137           31 :                                                 aResponseCommandId };
     138              : 
     139           31 :     InvokeResponseParameters prepareParams(aRequestCommandPath);
     140           31 :     prepareParams.SetStartOrEndDataStruct(false);
     141              : 
     142              :     {
     143           31 :         ScopedChange<bool> internalCallToAddResponse(mInternalCallToAddResponseData, true);
     144           31 :         ReturnErrorOnFailure(PrepareInvokeResponseCommand(responseCommandPath, prepareParams));
     145           31 :     }
     146              : 
     147           29 :     TLV::TLVWriter * writer = GetCommandDataIBTLVWriter();
     148           29 :     VerifyOrReturnError(writer != nullptr, CHIP_ERROR_INCORRECT_STATE);
     149              : 
     150           29 :     auto context = TryGetExchangeContextWhenAsync();
     151              :     // If we have no exchange or it has no session, we won't be able to send a
     152              :     // response anyway, so it doesn't matter how we encode it, but we have unit
     153              :     // tests that have a kinda-broken CommandHandler with no session... just use
     154              :     // kUndefinedFabricIndex in those cases.
     155              :     //
     156              :     // Note that just calling GetAccessingFabricIndex() here is not OK, because
     157              :     // we may have gone async already and our exchange/session may be gone, so
     158              :     // that would crash.  Which is one of the reasons GetAccessingFabricIndex()
     159              :     // is not allowed to be called once we have gone async.
     160              :     FabricIndex accessingFabricIndex;
     161           29 :     if (context && context->HasSessionHandle())
     162              :     {
     163           16 :         accessingFabricIndex = context->GetSessionHandle()->GetFabricIndex();
     164              :     }
     165              :     else
     166              :     {
     167           13 :         accessingFabricIndex = kUndefinedFabricIndex;
     168              :     }
     169              : 
     170           29 :     DataModel::FabricAwareTLVWriter responseWriter(*writer, accessingFabricIndex);
     171              : 
     172           29 :     ReturnErrorOnFailure(aEncodable.EncodeTo(responseWriter, TLV::ContextTag(CommandDataIB::Tag::kFields)));
     173           27 :     return FinishCommand(/* aEndDataStruct = */ false);
     174              : }
     175              : 
     176           30 : CHIP_ERROR CommandHandlerImpl::AddResponseData(const ConcreteCommandPath & aRequestCommandPath, CommandId aResponseCommandId,
     177              :                                                const DataModel::EncodableToTLV & aEncodable)
     178              : {
     179              :     // Return early when response should not be sent out.
     180           30 :     VerifyOrReturnValue(ResponsesAccepted(), CHIP_NO_ERROR);
     181           58 :     return TryAddingResponse(
     182           60 :         [&]() -> CHIP_ERROR { return TryAddResponseData(aRequestCommandPath, aResponseCommandId, aEncodable); });
     183              : }
     184              : 
     185           58 : CHIP_ERROR CommandHandlerImpl::ValidateInvokeRequestMessageAndBuildRegistry(InvokeRequestMessage::Parser & invokeRequestMessage)
     186              : {
     187           58 :     CHIP_ERROR err          = CHIP_NO_ERROR;
     188           58 :     size_t commandCount     = 0;
     189           58 :     bool commandRefExpected = false;
     190           58 :     InvokeRequests::Parser invokeRequests;
     191              : 
     192           58 :     ReturnErrorOnFailure(invokeRequestMessage.GetInvokeRequests(&invokeRequests));
     193           58 :     TLV::TLVReader invokeRequestsReader;
     194           58 :     invokeRequests.GetReader(&invokeRequestsReader);
     195              : 
     196           58 :     ReturnErrorOnFailure(TLV::Utilities::Count(invokeRequestsReader, commandCount, false /* recurse */));
     197              : 
     198              :     // If this is a GroupRequest the only thing to check is that there is only one
     199              :     // CommandDataIB.
     200           58 :     if (IsGroupRequest())
     201              :     {
     202            0 :         VerifyOrReturnError(commandCount == 1, CHIP_ERROR_INVALID_ARGUMENT);
     203            0 :         return CHIP_NO_ERROR;
     204              :     }
     205              :     // While technically any commandCount == 1 should already be unique and does not need
     206              :     // any further validation, we do need to read and populate the registry to help
     207              :     // in building the InvokeResponse.
     208              : 
     209           58 :     VerifyOrReturnError(commandCount <= MaxPathsPerInvoke(), CHIP_ERROR_INVALID_ARGUMENT);
     210              : 
     211              :     // If there is more than one CommandDataIB, spec states that CommandRef must be provided.
     212           56 :     commandRefExpected = commandCount > 1;
     213              : 
     214          224 :     while (CHIP_NO_ERROR == (err = invokeRequestsReader.Next()))
     215              :     {
     216           58 :         VerifyOrReturnError(TLV::AnonymousTag() == invokeRequestsReader.GetTag(), CHIP_ERROR_INVALID_ARGUMENT);
     217           58 :         CommandDataIB::Parser commandData;
     218           58 :         ReturnErrorOnFailure(commandData.Init(invokeRequestsReader));
     219              : 
     220              :         // First validate that we can get a ConcreteCommandPath.
     221           58 :         CommandPathIB::Parser commandPath;
     222           58 :         ConcreteCommandPath concretePath(0, 0, 0);
     223           58 :         ReturnErrorOnFailure(commandData.GetPath(&commandPath));
     224           58 :         ReturnErrorOnFailure(commandPath.GetConcreteCommandPath(concretePath));
     225              : 
     226              :         // Grab the CommandRef if there is one, and validate that it's there when it
     227              :         // has to be.
     228           57 :         std::optional<uint16_t> commandRef;
     229              :         uint16_t ref;
     230           57 :         err = commandData.GetRef(&ref);
     231          166 :         VerifyOrReturnError(err == CHIP_NO_ERROR || err == CHIP_END_OF_TLV, err);
     232          114 :         if (err == CHIP_END_OF_TLV && commandRefExpected)
     233              :         {
     234            0 :             return CHIP_ERROR_INVALID_ARGUMENT;
     235              :         }
     236          114 :         if (err == CHIP_NO_ERROR)
     237              :         {
     238            5 :             commandRef.emplace(ref);
     239              :         }
     240              : 
     241              :         // Adding can fail if concretePath is not unique, or if commandRef is a value
     242              :         // and is not unique, or if we have already added more paths than we support.
     243           57 :         ReturnErrorOnFailure(GetCommandPathRegistry().Add(concretePath, commandRef));
     244              :     }
     245              : 
     246              :     // It's OK/expected to have reached the end of the container without failure.
     247          108 :     if (CHIP_END_OF_TLV == err)
     248              :     {
     249           54 :         err = CHIP_NO_ERROR;
     250              :     }
     251           54 :     ReturnErrorOnFailure(err);
     252           54 :     return invokeRequestMessage.ExitContainer();
     253              : }
     254              : 
     255           61 : Status CommandHandlerImpl::ProcessInvokeRequest(System::PacketBufferHandle && payload, bool isTimedInvoke)
     256              : {
     257           61 :     CHIP_ERROR err = CHIP_NO_ERROR;
     258           61 :     System::PacketBufferTLVReader reader;
     259           61 :     InvokeRequestMessage::Parser invokeRequestMessage;
     260           61 :     InvokeRequests::Parser invokeRequests;
     261           61 :     reader.Init(std::move(payload));
     262          122 :     VerifyOrReturnError(invokeRequestMessage.Init(reader) == CHIP_NO_ERROR, Status::InvalidAction);
     263              : #if CHIP_CONFIG_IM_PRETTY_PRINT
     264           60 :     TEMPORARY_RETURN_IGNORED invokeRequestMessage.PrettyPrint();
     265              : #endif
     266           60 :     VerifyOrDie(mpResponder);
     267           60 :     if (mpResponder->GetGroupId().HasValue())
     268              :     {
     269            0 :         SetGroupRequest(true);
     270              :     }
     271              : 
     272              :     // When updating this code, please remember to make corresponding changes to TestOnlyInvokeCommandRequestWithFaultsInjected.
     273          120 :     VerifyOrReturnError(invokeRequestMessage.GetSuppressResponse(&mSuppressResponse) == CHIP_NO_ERROR, Status::InvalidAction);
     274          120 :     VerifyOrReturnError(invokeRequestMessage.GetTimedRequest(&mTimedRequest) == CHIP_NO_ERROR, Status::InvalidAction);
     275          120 :     VerifyOrReturnError(invokeRequestMessage.GetInvokeRequests(&invokeRequests) == CHIP_NO_ERROR, Status::InvalidAction);
     276           60 :     VerifyOrReturnError(mTimedRequest == isTimedInvoke, Status::TimedRequestMismatch);
     277              : 
     278              :     {
     279           58 :         InvokeRequestMessage::Parser validationInvokeRequestMessage = invokeRequestMessage;
     280          116 :         VerifyOrReturnError(ValidateInvokeRequestMessageAndBuildRegistry(validationInvokeRequestMessage) == CHIP_NO_ERROR,
     281              :                             Status::InvalidAction);
     282              :     }
     283              : 
     284           53 :     TLV::TLVReader invokeRequestsReader;
     285           53 :     invokeRequests.GetReader(&invokeRequestsReader);
     286              : 
     287           53 :     size_t commandCount = 0;
     288          106 :     VerifyOrReturnError(TLV::Utilities::Count(invokeRequestsReader, commandCount, false /* recurse */) == CHIP_NO_ERROR,
     289              :                         Status::InvalidAction);
     290           53 :     if (commandCount > 1)
     291              :     {
     292            1 :         mReserveSpaceForMoreChunkMessages = true;
     293              :     }
     294              : 
     295          214 :     while (CHIP_NO_ERROR == (err = invokeRequestsReader.Next()))
     296              :     {
     297           54 :         VerifyOrReturnError(TLV::AnonymousTag() == invokeRequestsReader.GetTag(), Status::InvalidAction);
     298           54 :         CommandDataIB::Parser commandData;
     299          108 :         VerifyOrReturnError(commandData.Init(invokeRequestsReader) == CHIP_NO_ERROR, Status::InvalidAction);
     300           54 :         Status status = Status::Success;
     301           54 :         if (IsGroupRequest())
     302              :         {
     303            0 :             status = ProcessGroupCommandDataIB(commandData);
     304              :         }
     305              :         else
     306              :         {
     307           54 :             status = ProcessCommandDataIB(commandData);
     308              :         }
     309           54 :         if (status != Status::Success)
     310              :         {
     311            0 :             return status;
     312              :         }
     313              :     }
     314              : 
     315              :     // if we have exhausted this container
     316          106 :     if (CHIP_END_OF_TLV == err)
     317              :     {
     318           53 :         err = CHIP_NO_ERROR;
     319              :     }
     320          106 :     VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
     321          106 :     VerifyOrReturnError(invokeRequestMessage.ExitContainer() == CHIP_NO_ERROR, Status::InvalidAction);
     322           53 :     return Status::Success;
     323           61 : }
     324              : 
     325           66 : void CommandHandlerImpl::Close()
     326              : {
     327           66 :     mSuppressResponse = false;
     328           66 :     mpResponder       = nullptr;
     329           66 :     MoveToState(State::AwaitingDestruction);
     330              : 
     331              :     // We must finish all async work before we can shut down a CommandHandlerImpl. The actual CommandHandlerImpl MUST finish their
     332              :     // work in reasonable time or there is a bug. The only case for releasing CommandHandlerImpl without CommandHandler::Handle
     333              :     // releasing its reference is the stack shutting down, in which case Close() is not called. So the below check should always
     334              :     // pass.
     335           66 :     VerifyOrDieWithMsg(mPendingWork == 0, DataManagement, "CommandHandlerImpl::Close() called with %u unfinished async work items",
     336              :                        static_cast<unsigned int>(mPendingWork));
     337           66 :     InvalidateHandles();
     338              : 
     339           66 :     if (mpCallback)
     340              :     {
     341           63 :         mpCallback->OnDone(*this);
     342              :     }
     343           66 : }
     344              : 
     345          129 : void CommandHandlerImpl::AddToHandleList(Handle * apHandle)
     346              : {
     347          129 :     mpHandleList.PushBack(apHandle);
     348          129 : }
     349              : 
     350          129 : void CommandHandlerImpl::RemoveFromHandleList(Handle * apHandle)
     351              : {
     352          129 :     VerifyOrDie(mpHandleList.Contains(apHandle));
     353          129 :     mpHandleList.Remove(apHandle);
     354          129 : }
     355              : 
     356          143 : void CommandHandlerImpl::InvalidateHandles()
     357              : {
     358          143 :     for (auto handle = mpHandleList.begin(); handle != mpHandleList.end(); ++handle)
     359              :     {
     360            0 :         handle->Invalidate();
     361              :     }
     362          143 :     mpHandleList.Clear();
     363          143 : }
     364              : 
     365          129 : void CommandHandlerImpl::IncrementHoldOff(Handle * apHandle)
     366              : {
     367          129 :     mPendingWork++;
     368          129 :     AddToHandleList(apHandle);
     369          129 : }
     370              : 
     371          129 : void CommandHandlerImpl::DecrementHoldOff(Handle * apHandle)
     372              : {
     373              : 
     374          129 :     mPendingWork--;
     375          129 :     ChipLogDetail(DataManagement, "Decreasing reference count for CommandHandlerImpl, remaining %u",
     376              :                   static_cast<unsigned int>(mPendingWork));
     377              : 
     378          129 :     RemoveFromHandleList(apHandle);
     379              : 
     380          129 :     if (mPendingWork != 0)
     381              :     {
     382           63 :         return;
     383              :     }
     384              : 
     385           66 :     if (mpResponder == nullptr)
     386              :     {
     387            0 :         ChipLogProgress(DataManagement, "Skipping command response: response sender is null");
     388              :     }
     389           66 :     else if (!IsGroupRequest())
     390              :     {
     391           66 :         CHIP_ERROR err = FinalizeLastInvokeResponseMessage();
     392          132 :         if (err != CHIP_NO_ERROR)
     393              :         {
     394            9 :             ChipLogError(DataManagement, "Failed to finalize command response: %" CHIP_ERROR_FORMAT, err.Format());
     395              :         }
     396              :     }
     397              : 
     398           66 :     Close();
     399              : }
     400              : 
     401              : namespace {
     402              : // We use this when the sender did not actually provide a CommandFields struct,
     403              : // to avoid downstream consumers having to worry about cases when there is or is
     404              : // not a struct available.  We use an empty struct with anonymous tag, since we
     405              : // can't use a context tag at top level, and consumers should not care about the
     406              : // tag here).
     407              : constexpr uint8_t sNoFields[] = {
     408              :     CHIP_TLV_STRUCTURE(CHIP_TLV_TAG_ANONYMOUS),
     409              :     CHIP_TLV_END_OF_CONTAINER,
     410              : };
     411              : } // anonymous namespace
     412              : 
     413           54 : Status CommandHandlerImpl::ProcessCommandDataIB(CommandDataIB::Parser & aCommandElement)
     414              : {
     415           54 :     CHIP_ERROR err = CHIP_NO_ERROR;
     416           54 :     CommandPathIB::Parser commandPath;
     417           54 :     ConcreteCommandPath concretePath(0, 0, 0);
     418           54 :     TLV::TLVReader commandDataReader;
     419              : 
     420              :     // NOTE: errors may occur before the concrete command path is even fully decoded.
     421              : 
     422           54 :     err = aCommandElement.GetPath(&commandPath);
     423          108 :     VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
     424              : 
     425           54 :     err = commandPath.GetConcreteCommandPath(concretePath);
     426          108 :     VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
     427              : 
     428              :     {
     429           54 :         Access::SubjectDescriptor subjectDescriptor = GetSubjectDescriptor();
     430           54 :         DataModel::InvokeRequest request(concretePath, subjectDescriptor);
     431              : 
     432           54 :         request.invokeFlags.Set(DataModel::InvokeFlags::kTimed, IsTimedInvoke());
     433              : 
     434           54 :         Status preCheckStatus = mpCallback->ValidateCommandCanBeDispatched(request);
     435           54 :         if (preCheckStatus != Status::Success)
     436              :         {
     437           26 :             return FallibleAddStatus(concretePath, preCheckStatus) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
     438              :         }
     439              :     }
     440              : 
     441           41 :     err = aCommandElement.GetFields(&commandDataReader);
     442           82 :     if (CHIP_END_OF_TLV == err)
     443              :     {
     444            2 :         ChipLogDetail(DataManagement,
     445              :                       "Received command without data for Endpoint=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI,
     446              :                       concretePath.mEndpointId, ChipLogValueMEI(concretePath.mClusterId), ChipLogValueMEI(concretePath.mCommandId));
     447            2 :         commandDataReader.Init(sNoFields);
     448            2 :         err = commandDataReader.Next();
     449              :     }
     450           82 :     if (CHIP_NO_ERROR == err)
     451              :     {
     452           41 :         ChipLogDetail(DataManagement, "Received command for Endpoint=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI,
     453              :                       concretePath.mEndpointId, ChipLogValueMEI(concretePath.mClusterId), ChipLogValueMEI(concretePath.mCommandId));
     454           41 :         SuccessOrExit(err = DataModelCallbacks::GetInstance()->PreCommandReceived(concretePath, GetSubjectDescriptor()));
     455           41 :         mpCallback->DispatchCommand(*this, concretePath, commandDataReader);
     456           41 :         DataModelCallbacks::GetInstance()->PostCommandReceived(concretePath, GetSubjectDescriptor());
     457              :     }
     458              : 
     459            0 : exit:
     460           82 :     if (err != CHIP_NO_ERROR)
     461              :     {
     462            0 :         return FallibleAddStatus(concretePath, Status::InvalidCommand) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
     463              :     }
     464              : 
     465              :     // We have handled the error status above and put the error status in response, now return success status so we can process
     466              :     // other commands in the invoke request.
     467           41 :     return Status::Success;
     468              : }
     469              : 
     470            0 : Status CommandHandlerImpl::ProcessGroupCommandDataIB(CommandDataIB::Parser & aCommandElement)
     471              : {
     472            0 :     CHIP_ERROR err = CHIP_NO_ERROR;
     473            0 :     CommandPathIB::Parser commandPath;
     474            0 :     TLV::TLVReader commandDataReader;
     475              :     ClusterId clusterId;
     476              :     CommandId commandId;
     477              :     GroupId groupId;
     478              :     FabricIndex fabric;
     479              : 
     480            0 :     Credentials::GroupDataProvider::GroupEndpoint mapping;
     481            0 :     Credentials::GroupDataProvider * groupDataProvider = Credentials::GetGroupDataProvider();
     482              :     Credentials::GroupDataProvider::EndpointIterator * iterator;
     483              : 
     484            0 :     err = aCommandElement.GetPath(&commandPath);
     485            0 :     VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
     486              : 
     487            0 :     err = commandPath.GetGroupCommandPath(&clusterId, &commandId);
     488            0 :     VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
     489              : 
     490            0 :     VerifyOrDie(mpResponder);
     491              :     // The optionalGroupId must have a value, otherwise we wouldn't have reached this code path.
     492            0 :     groupId = mpResponder->GetGroupId().Value();
     493            0 :     fabric  = GetAccessingFabricIndex();
     494              : 
     495            0 :     ChipLogDetail(DataManagement, "Received group command for Group=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI,
     496              :                   groupId, ChipLogValueMEI(clusterId), ChipLogValueMEI(commandId));
     497              : 
     498            0 :     err = aCommandElement.GetFields(&commandDataReader);
     499            0 :     if (CHIP_END_OF_TLV == err)
     500              :     {
     501            0 :         ChipLogDetail(DataManagement,
     502              :                       "Received command without data for Group=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI, groupId,
     503              :                       ChipLogValueMEI(clusterId), ChipLogValueMEI(commandId));
     504            0 :         commandDataReader.Init(sNoFields);
     505            0 :         err = commandDataReader.Next();
     506            0 :         VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
     507              :     }
     508            0 :     VerifyOrReturnError(err == CHIP_NO_ERROR, Status::Failure);
     509              : 
     510              :     // No check for `CommandIsFabricScoped` unlike in `ProcessCommandDataIB()` since group commands
     511              :     // always have an accessing fabric, by definition.
     512              : 
     513              :     // Find which endpoints can process the command, and dispatch to them.
     514            0 :     iterator = groupDataProvider->IterateEndpoints(fabric);
     515            0 :     VerifyOrReturnError(iterator != nullptr, Status::Failure);
     516              : 
     517            0 :     while (iterator->Next(mapping))
     518              :     {
     519            0 :         if (groupId != mapping.group_id)
     520              :         {
     521            0 :             continue;
     522              :         }
     523              : 
     524            0 :         ChipLogDetail(DataManagement,
     525              :                       "Processing group command for Endpoint=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI,
     526              :                       mapping.endpoint_id, ChipLogValueMEI(clusterId), ChipLogValueMEI(commandId));
     527              : 
     528            0 :         const ConcreteCommandPath concretePath(mapping.endpoint_id, clusterId, commandId);
     529              :         // Groupcast Testing
     530            0 :         auto & testing = Groupcast::GetTesting();
     531            0 :         if (testing.IsEnabled() && testing.IsFabricUnderTest(fabric))
     532              :         {
     533            0 :             testing.SetGroupID(groupId);
     534            0 :             testing.SetEndpointID(mapping.endpoint_id);
     535            0 :             testing.SetClusterID(clusterId);
     536            0 :             testing.SetElementID(static_cast<uint32_t>(commandId));
     537              :         }
     538              : 
     539              :         {
     540            0 :             Access::SubjectDescriptor subjectDescriptor = GetSubjectDescriptor();
     541            0 :             DataModel::InvokeRequest request(concretePath, subjectDescriptor);
     542              : 
     543            0 :             request.invokeFlags.Set(DataModel::InvokeFlags::kTimed, IsTimedInvoke());
     544              : 
     545            0 :             Status preCheckStatus = mpCallback->ValidateCommandCanBeDispatched(request);
     546            0 :             if (preCheckStatus != Status::Success)
     547              :             {
     548              :                 // Command failed for a specific path, but keep trying the rest of the paths.
     549            0 :                 continue;
     550              :             }
     551              :         }
     552              : 
     553            0 :         if ((err = DataModelCallbacks::GetInstance()->PreCommandReceived(concretePath, GetSubjectDescriptor())) == CHIP_NO_ERROR)
     554              :         {
     555            0 :             TLV::TLVReader dataReader(commandDataReader);
     556            0 :             mpCallback->DispatchCommand(*this, concretePath, dataReader);
     557            0 :             DataModelCallbacks::GetInstance()->PostCommandReceived(concretePath, GetSubjectDescriptor());
     558              :         }
     559              :         else
     560              :         {
     561            0 :             ChipLogError(DataManagement,
     562              :                          "Error when calling PreCommandReceived for Endpoint=%u Cluster=" ChipLogFormatMEI
     563              :                          " Command=" ChipLogFormatMEI " : %" CHIP_ERROR_FORMAT,
     564              :                          mapping.endpoint_id, ChipLogValueMEI(clusterId), ChipLogValueMEI(commandId), err.Format());
     565            0 :             continue;
     566              :         }
     567              :     }
     568            0 :     iterator->Release();
     569            0 :     return Status::Success;
     570              : }
     571              : 
     572           58 : CHIP_ERROR CommandHandlerImpl::TryAddStatusInternal(const ConcreteCommandPath & aCommandPath, const StatusIB & aStatus)
     573              : {
     574              :     // Return early when response should not be sent out.
     575           58 :     VerifyOrReturnValue(ResponsesAccepted(), CHIP_NO_ERROR);
     576              : 
     577           57 :     ReturnErrorOnFailure(PrepareStatus(aCommandPath));
     578           56 :     CommandStatusIB::Builder & commandStatus = mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().GetStatus();
     579           56 :     StatusIB::Builder & statusIBBuilder      = commandStatus.CreateErrorStatus();
     580           56 :     ReturnErrorOnFailure(commandStatus.GetError());
     581           56 :     statusIBBuilder.EncodeStatusIB(aStatus);
     582           56 :     ReturnErrorOnFailure(statusIBBuilder.GetError());
     583           56 :     return FinishStatus();
     584              : }
     585              : 
     586           57 : CHIP_ERROR CommandHandlerImpl::AddStatusInternal(const ConcreteCommandPath & aCommandPath, const StatusIB & aStatus)
     587              : {
     588          115 :     return TryAddingResponse([&]() -> CHIP_ERROR { return TryAddStatusInternal(aCommandPath, aStatus); });
     589              : }
     590              : 
     591           37 : void CommandHandlerImpl::AddStatus(const ConcreteCommandPath & aCommandPath,
     592              :                                    const Protocols::InteractionModel::ClusterStatusCode & status, const char * context)
     593              : {
     594              : 
     595           37 :     CHIP_ERROR error = FallibleAddStatus(aCommandPath, status, context);
     596              : 
     597           74 :     if (error != CHIP_NO_ERROR)
     598              :     {
     599            0 :         ChipLogError(DataManagement, "Failed to add command status: %" CHIP_ERROR_FORMAT, error.Format());
     600              :         // TODO(#30453) we could call mpResponder->ResponseDropped() if err == CHIP_ERROR_NO_MEMORY. This should
     601              :         // be done as a follow up so that change can be evaluated as a standalone PR.
     602              : 
     603              :         // Do not crash if the status has not been added due to running out of packet buffers or other resources.
     604              :         // It is better to drop a single response than to go offline and lose all sessions and subscriptions.
     605            0 :         VerifyOrDie(error == CHIP_ERROR_NO_MEMORY);
     606              :     }
     607           37 : }
     608              : 
     609           57 : CHIP_ERROR CommandHandlerImpl::FallibleAddStatus(const ConcreteCommandPath & path,
     610              :                                                  const Protocols::InteractionModel::ClusterStatusCode & status,
     611              :                                                  const char * context)
     612              : {
     613           57 :     if (!status.IsSuccess())
     614              :     {
     615           25 :         if (context == nullptr)
     616              :         {
     617           25 :             context = "no additional context";
     618              :         }
     619              : 
     620           25 :         if (const auto clusterStatus = status.GetClusterSpecificCode(); clusterStatus.has_value())
     621              :         {
     622            1 :             ChipLogError(DataManagement,
     623              :                          "Endpoint=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI " status " ChipLogFormatIMStatus
     624              :                          " ClusterSpecificCode=%u (%s)",
     625              :                          path.mEndpointId, ChipLogValueMEI(path.mClusterId), ChipLogValueMEI(path.mCommandId),
     626              :                          ChipLogValueIMStatus(status.GetStatus()), static_cast<unsigned>(*clusterStatus), context);
     627              :         }
     628              :         else
     629              :         {
     630           24 :             ChipLogError(DataManagement,
     631              :                          "Endpoint=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI " status " ChipLogFormatIMStatus
     632              :                          " (%s)",
     633              :                          path.mEndpointId, ChipLogValueMEI(path.mClusterId), ChipLogValueMEI(path.mCommandId),
     634              :                          ChipLogValueIMStatus(status.GetStatus()), context);
     635              :         }
     636              :     }
     637              : 
     638           57 :     return AddStatusInternal(path, StatusIB{ status });
     639              : }
     640              : 
     641           34 : CHIP_ERROR CommandHandlerImpl::PrepareInvokeResponseCommand(const ConcreteCommandPath & aResponseCommandPath,
     642              :                                                             const CommandHandlerImpl::InvokeResponseParameters & aPrepareParameters)
     643              : {
     644           34 :     auto commandPathRegistryEntry = GetCommandPathRegistry().Find(aPrepareParameters.mRequestCommandPath);
     645           34 :     VerifyOrReturnValue(commandPathRegistryEntry.has_value(), CHIP_ERROR_INCORRECT_STATE);
     646              : 
     647           33 :     return PrepareInvokeResponseCommand(*commandPathRegistryEntry, aResponseCommandPath, aPrepareParameters.mStartOrEndDataStruct);
     648              : }
     649              : 
     650           33 : CHIP_ERROR CommandHandlerImpl::PrepareInvokeResponseCommand(const CommandPathRegistryEntry & apCommandPathRegistryEntry,
     651              :                                                             const ConcreteCommandPath & aCommandPath, bool aStartDataStruct)
     652              : {
     653              :     // Intentionally omitting the ResponsesAccepted early exit. Direct use of PrepareInvokeResponseCommand
     654              :     // is discouraged, as it often indicates incorrect usage patterns (see GitHub issue #32486).
     655              :     // If you're encountering CHIP_ERROR_INCORRECT_STATE, refactoring to use AddResponse is recommended.
     656           33 :     ReturnErrorOnFailure(AllocateBuffer());
     657              : 
     658           33 :     if (!mInternalCallToAddResponseData && mState == State::AddedCommand)
     659              :     {
     660              :         // An attempt is being made to add CommandData InvokeResponse using primitive
     661              :         // CommandHandlerImpl APIs. While not recommended, as this potentially leaves the
     662              :         // CommandHandlerImpl in an incorrect state upon failure, this approach is permitted
     663              :         // for legacy reasons. To maximize the likelihood of success, particularly when
     664              :         // handling large amounts of data, we try to obtain a new, completely empty
     665              :         // InvokeResponseMessage, as the existing one already has space occupied.
     666            0 :         ReturnErrorOnFailure(FinalizeInvokeResponseMessageAndPrepareNext());
     667              :     }
     668              : 
     669           33 :     CreateBackupForResponseRollback();
     670              :     //
     671              :     // We must not be in the middle of preparing a command, or having prepared or sent one.
     672              :     //
     673           33 :     VerifyOrReturnError(mState == State::NewResponseMessage || mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
     674              : 
     675              :     // TODO(#30453): See if we can pass this back up the stack so caller can provide this instead of taking up
     676              :     // space in CommandHanlder.
     677           33 :     mRefForResponse = apCommandPathRegistryEntry.ref;
     678              : 
     679           33 :     MoveToState(State::Preparing);
     680           33 :     InvokeResponseIBs::Builder & invokeResponses = mInvokeResponseBuilder.GetInvokeResponses();
     681           33 :     InvokeResponseIB::Builder & invokeResponse   = invokeResponses.CreateInvokeResponse();
     682           33 :     ReturnErrorOnFailure(invokeResponses.GetError());
     683              : 
     684           31 :     CommandDataIB::Builder & commandData = invokeResponse.CreateCommand();
     685           31 :     ReturnErrorOnFailure(commandData.GetError());
     686           31 :     CommandPathIB::Builder & path = commandData.CreatePath();
     687           31 :     ReturnErrorOnFailure(commandData.GetError());
     688           31 :     ReturnErrorOnFailure(path.Encode(aCommandPath));
     689           31 :     if (aStartDataStruct)
     690              :     {
     691            2 :         ReturnErrorOnFailure(commandData.GetWriter()->StartContainer(TLV::ContextTag(CommandDataIB::Tag::kFields),
     692              :                                                                      TLV::kTLVType_Structure, mDataElementContainerType));
     693              :     }
     694           31 :     MoveToState(State::AddingCommand);
     695           31 :     return CHIP_NO_ERROR;
     696              : }
     697              : 
     698           29 : CHIP_ERROR CommandHandlerImpl::FinishCommand(bool aStartDataStruct)
     699              : {
     700              :     // Intentionally omitting the ResponsesAccepted early exit. Direct use of FinishCommand
     701              :     // is discouraged, as it often indicates incorrect usage patterns (see GitHub issue #32486).
     702              :     // If you're encountering CHIP_ERROR_INCORRECT_STATE, refactoring to use AddResponse is recommended.
     703           29 :     VerifyOrReturnError(mState == State::AddingCommand, CHIP_ERROR_INCORRECT_STATE);
     704           28 :     CommandDataIB::Builder & commandData = mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().GetCommand();
     705           28 :     if (aStartDataStruct)
     706              :     {
     707            1 :         ReturnErrorOnFailure(commandData.GetWriter()->EndContainer(mDataElementContainerType));
     708              :     }
     709              : 
     710           28 :     if (mRefForResponse.has_value())
     711              :     {
     712            9 :         ReturnErrorOnFailure(commandData.Ref(*mRefForResponse));
     713              :     }
     714              : 
     715           28 :     ReturnErrorOnFailure(commandData.EndOfCommandDataIB());
     716           28 :     ReturnErrorOnFailure(mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().EndOfInvokeResponseIB());
     717           28 :     MoveToState(State::AddedCommand);
     718           28 :     return CHIP_NO_ERROR;
     719              : }
     720              : 
     721           57 : CHIP_ERROR CommandHandlerImpl::PrepareStatus(const ConcreteCommandPath & aCommandPath)
     722              : {
     723           57 :     ReturnErrorOnFailure(AllocateBuffer());
     724              :     //
     725              :     // We must not be in the middle of preparing a command, or having prepared or sent one.
     726              :     //
     727           57 :     VerifyOrReturnError(mState == State::NewResponseMessage || mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
     728           57 :     if (mState == State::AddedCommand)
     729              :     {
     730           17 :         CreateBackupForResponseRollback();
     731              :     }
     732              : 
     733           57 :     auto commandPathRegistryEntry = GetCommandPathRegistry().Find(aCommandPath);
     734           57 :     VerifyOrReturnError(commandPathRegistryEntry.has_value(), CHIP_ERROR_INCORRECT_STATE);
     735           57 :     mRefForResponse = commandPathRegistryEntry->ref;
     736              : 
     737           57 :     MoveToState(State::Preparing);
     738           57 :     InvokeResponseIBs::Builder & invokeResponses = mInvokeResponseBuilder.GetInvokeResponses();
     739           57 :     InvokeResponseIB::Builder & invokeResponse   = invokeResponses.CreateInvokeResponse();
     740           57 :     ReturnErrorOnFailure(invokeResponses.GetError());
     741           56 :     CommandStatusIB::Builder & commandStatus = invokeResponse.CreateStatus();
     742           56 :     ReturnErrorOnFailure(commandStatus.GetError());
     743           56 :     CommandPathIB::Builder & path = commandStatus.CreatePath();
     744           56 :     ReturnErrorOnFailure(commandStatus.GetError());
     745           56 :     ReturnErrorOnFailure(path.Encode(aCommandPath));
     746           56 :     MoveToState(State::AddingCommand);
     747           56 :     return CHIP_NO_ERROR;
     748              : }
     749              : 
     750           56 : CHIP_ERROR CommandHandlerImpl::FinishStatus()
     751              : {
     752           56 :     VerifyOrReturnError(mState == State::AddingCommand, CHIP_ERROR_INCORRECT_STATE);
     753              : 
     754           56 :     CommandStatusIB::Builder & commandStatus = mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().GetStatus();
     755           56 :     if (mRefForResponse.has_value())
     756              :     {
     757            3 :         ReturnErrorOnFailure(commandStatus.Ref(*mRefForResponse));
     758              :     }
     759              : 
     760           56 :     ReturnErrorOnFailure(mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().GetStatus().EndOfCommandStatusIB());
     761           56 :     ReturnErrorOnFailure(mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().EndOfInvokeResponseIB());
     762           56 :     MoveToState(State::AddedCommand);
     763           56 :     return CHIP_NO_ERROR;
     764              : }
     765              : 
     766           50 : void CommandHandlerImpl::CreateBackupForResponseRollback()
     767              : {
     768           50 :     VerifyOrReturn(mState == State::NewResponseMessage || mState == State::AddedCommand);
     769          100 :     VerifyOrReturn(mInvokeResponseBuilder.GetInvokeResponses().GetError() == CHIP_NO_ERROR);
     770          100 :     VerifyOrReturn(mInvokeResponseBuilder.GetError() == CHIP_NO_ERROR);
     771           50 :     mInvokeResponseBuilder.Checkpoint(mBackupWriter);
     772           50 :     mBackupState         = mState;
     773           50 :     mRollbackBackupValid = true;
     774              : }
     775              : 
     776            5 : CHIP_ERROR CommandHandlerImpl::RollbackResponse()
     777              : {
     778            5 :     VerifyOrReturnError(mRollbackBackupValid, CHIP_ERROR_INCORRECT_STATE);
     779            5 :     VerifyOrReturnError(mState == State::Preparing || mState == State::AddingCommand, CHIP_ERROR_INCORRECT_STATE);
     780            5 :     ChipLogDetail(DataManagement, "Rolling back response");
     781              :     // TODO(#30453): Rollback of mInvokeResponseBuilder should handle resetting
     782              :     // InvokeResponses.
     783            5 :     mInvokeResponseBuilder.GetInvokeResponses().ResetError();
     784            5 :     mInvokeResponseBuilder.Rollback(mBackupWriter);
     785            5 :     MoveToState(mBackupState);
     786            5 :     mRollbackBackupValid = false;
     787            5 :     return CHIP_NO_ERROR;
     788              : }
     789              : 
     790           30 : TLV::TLVWriter * CommandHandlerImpl::GetCommandDataIBTLVWriter()
     791              : {
     792           30 :     if (mState != State::AddingCommand)
     793              :     {
     794            1 :         return nullptr;
     795              :     }
     796              : 
     797           29 :     return mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().GetCommand().GetWriter();
     798              : }
     799              : 
     800            0 : FabricIndex CommandHandlerImpl::GetAccessingFabricIndex() const
     801              : {
     802            0 :     VerifyOrDie(!mGoneAsync);
     803            0 :     VerifyOrDie(mpResponder);
     804            0 :     return mpResponder->GetAccessingFabricIndex();
     805              : }
     806              : 
     807            3 : CHIP_ERROR CommandHandlerImpl::FinalizeInvokeResponseMessageAndPrepareNext()
     808              : {
     809            3 :     ReturnErrorOnFailure(FinalizeInvokeResponseMessage(/* aHasMoreChunks = */ true));
     810              :     // After successfully finalizing InvokeResponseMessage, no buffer should remain
     811              :     // allocated.
     812            3 :     VerifyOrDie(!mBufferAllocated);
     813            3 :     CHIP_ERROR err = AllocateBuffer();
     814            6 :     if (err != CHIP_NO_ERROR)
     815              :     {
     816              :         // TODO(#30453): Improve ResponseDropped calls to occur only when dropping is
     817              :         // definitively guaranteed.
     818              :         // Response dropping is not yet definitive as a subsequent call
     819              :         // to AllocateBuffer might succeed.
     820            0 :         VerifyOrDie(mpResponder);
     821            0 :         mpResponder->ResponseDropped();
     822              :     }
     823            3 :     return err;
     824              : }
     825              : 
     826           69 : CHIP_ERROR CommandHandlerImpl::FinalizeInvokeResponseMessage(bool aHasMoreChunks)
     827              : {
     828           69 :     System::PacketBufferHandle packet;
     829              : 
     830           69 :     VerifyOrReturnError(mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
     831           60 :     ReturnErrorOnFailure(mInvokeResponseBuilder.GetInvokeResponses().EndOfInvokeResponses());
     832           60 :     if (aHasMoreChunks)
     833              :     {
     834              :         // Unreserving space previously reserved for MoreChunkedMessages is done
     835              :         // in the call to mInvokeResponseBuilder.MoreChunkedMessages.
     836            3 :         mInvokeResponseBuilder.MoreChunkedMessages(aHasMoreChunks);
     837            3 :         ReturnErrorOnFailure(mInvokeResponseBuilder.GetError());
     838              :     }
     839           60 :     ReturnErrorOnFailure(mInvokeResponseBuilder.EndOfInvokeResponseMessage());
     840           60 :     ReturnErrorOnFailure(mCommandMessageWriter.Finalize(&packet));
     841           60 :     VerifyOrDie(mpResponder);
     842           60 :     mpResponder->AddInvokeResponseToSend(std::move(packet));
     843           60 :     mBufferAllocated     = false;
     844           60 :     mRollbackBackupValid = false;
     845           60 :     return CHIP_NO_ERROR;
     846           69 : }
     847              : 
     848           74 : void CommandHandlerImpl::SetExchangeInterface(CommandHandlerExchangeInterface * commandResponder)
     849              : {
     850           74 :     VerifyOrDieWithMsg(mState == State::Idle, DataManagement, "CommandResponseSender can only be set in idle state");
     851           74 :     mpResponder = commandResponder;
     852           74 : }
     853              : 
     854            0 : const char * CommandHandlerImpl::GetStateStr() const
     855              : {
     856              : #if CHIP_DETAIL_LOGGING
     857            0 :     switch (mState)
     858              :     {
     859            0 :     case State::Idle:
     860            0 :         return "Idle";
     861              : 
     862            0 :     case State::NewResponseMessage:
     863            0 :         return "NewResponseMessage";
     864              : 
     865            0 :     case State::Preparing:
     866            0 :         return "Preparing";
     867              : 
     868            0 :     case State::AddingCommand:
     869            0 :         return "AddingCommand";
     870              : 
     871            0 :     case State::AddedCommand:
     872            0 :         return "AddedCommand";
     873              : 
     874            0 :     case State::DispatchResponses:
     875            0 :         return "DispatchResponses";
     876              : 
     877            0 :     case State::AwaitingDestruction:
     878            0 :         return "AwaitingDestruction";
     879              :     }
     880              : #endif // CHIP_DETAIL_LOGGING
     881            0 :     return "N/A";
     882              : }
     883              : 
     884          400 : void CommandHandlerImpl::MoveToState(const State aTargetState)
     885              : {
     886          400 :     mState = aTargetState;
     887          400 :     ChipLogDetail(DataManagement, "Command handler moving to [%10.10s]", GetStateStr());
     888          400 : }
     889              : 
     890            0 : void CommandHandlerImpl::FlushAcksRightAwayOnSlowCommand()
     891              : {
     892            0 :     if (mpResponder)
     893              :     {
     894            0 :         mpResponder->HandlingSlowCommand();
     895              :     }
     896            0 : }
     897              : 
     898          173 : Access::SubjectDescriptor CommandHandlerImpl::GetSubjectDescriptor() const
     899              : {
     900          173 :     VerifyOrDie(!mGoneAsync);
     901          173 :     VerifyOrDie(mpResponder);
     902          173 :     return mpResponder->GetSubjectDescriptor();
     903              : }
     904              : 
     905           91 : bool CommandHandlerImpl::IsTimedInvoke() const
     906              : {
     907           91 :     return mTimedRequest;
     908              : }
     909              : 
     910           18 : void CommandHandlerImpl::AddResponse(const ConcreteCommandPath & aRequestCommandPath, CommandId aResponseCommandId,
     911              :                                      const DataModel::EncodableToTLV & aEncodable)
     912              : {
     913           18 :     CHIP_ERROR err = AddResponseData(aRequestCommandPath, aResponseCommandId, aEncodable);
     914           36 :     if (err != CHIP_NO_ERROR)
     915              :     {
     916            1 :         ChipLogError(DataManagement, "Adding response failed: %" CHIP_ERROR_FORMAT ". Returning failure instead.", err.Format());
     917            1 :         AddStatus(aRequestCommandPath, Protocols::InteractionModel::Status::Failure);
     918              :     }
     919           18 : }
     920              : 
     921            1 : Messaging::ExchangeContext * CommandHandlerImpl::GetExchangeContext() const
     922              : {
     923            1 :     VerifyOrReturnValue((mpResponder != nullptr) && !mGoneAsync, nullptr);
     924            0 :     return mpResponder->GetExchangeContext();
     925              : }
     926              : 
     927           31 : Messaging::ExchangeContext * CommandHandlerImpl::TryGetExchangeContextWhenAsync() const
     928              : {
     929           31 :     VerifyOrReturnValue(mpResponder, nullptr);
     930           31 :     return mpResponder->GetExchangeContext();
     931              : }
     932              : 
     933              : #if CHIP_WITH_NLFAULTINJECTION
     934              : 
     935              : namespace {
     936              : 
     937            0 : CHIP_ERROR TestOnlyExtractCommandPathFromNextInvokeRequest(TLV::TLVReader & invokeRequestsReader,
     938              :                                                            ConcreteCommandPath & concretePath)
     939              : {
     940            0 :     ReturnErrorOnFailure(invokeRequestsReader.Next(TLV::AnonymousTag()));
     941            0 :     CommandDataIB::Parser commandData;
     942            0 :     ReturnErrorOnFailure(commandData.Init(invokeRequestsReader));
     943            0 :     CommandPathIB::Parser commandPath;
     944            0 :     ReturnErrorOnFailure(commandData.GetPath(&commandPath));
     945            0 :     return commandPath.GetConcreteCommandPath(concretePath);
     946              : }
     947              : 
     948            0 : [[maybe_unused]] const char * GetFaultInjectionTypeStr(CommandHandlerImpl::NlFaultInjectionType faultType)
     949              : {
     950            0 :     switch (faultType)
     951              :     {
     952            0 :     case CommandHandlerImpl::NlFaultInjectionType::SeparateResponseMessages:
     953              :         return "Each response will be sent in a separate InvokeResponseMessage. The order of responses will be the same as the "
     954            0 :                "original request.";
     955            0 :     case CommandHandlerImpl::NlFaultInjectionType::SeparateResponseMessagesAndInvertedResponseOrder:
     956              :         return "Each response will be sent in a separate InvokeResponseMessage. The order of responses will be reversed from the "
     957            0 :                "original request.";
     958            0 :     case CommandHandlerImpl::NlFaultInjectionType::SkipSecondResponse:
     959            0 :         return "Single InvokeResponseMessages. Dropping response to second request";
     960              :     }
     961            0 :     ChipLogError(DataManagement, "TH Failure: Unexpected fault type");
     962            0 :     chipAbort();
     963              : }
     964              : 
     965              : } // anonymous namespace
     966              : 
     967              : // This method intentionally duplicates code from other sections. While code consolidation
     968              : // is generally preferred, here we prioritize generating a clear crash message to aid in
     969              : // troubleshooting test failures.
     970            0 : void CommandHandlerImpl::TestOnlyInvokeCommandRequestWithFaultsInjected(CommandHandlerExchangeInterface & commandResponder,
     971              :                                                                         System::PacketBufferHandle && payload, bool isTimedInvoke,
     972              :                                                                         NlFaultInjectionType faultType)
     973              : {
     974            0 :     VerifyOrDieWithMsg(mState == State::Idle, DataManagement, "TH Failure: state should be Idle, issue with TH");
     975            0 :     SetExchangeInterface(&commandResponder);
     976              : 
     977            0 :     ChipLogProgress(DataManagement, "Response to InvokeRequestMessage overridden by fault injection");
     978            0 :     ChipLogProgress(DataManagement, "   Injecting the following response:%s", GetFaultInjectionTypeStr(faultType));
     979              : 
     980            0 :     Handle workHandle(this);
     981            0 :     VerifyOrDieWithMsg(!commandResponder.GetGroupId().HasValue(), DataManagement, "DUT Failure: Unexpected Group Command");
     982              : 
     983            0 :     System::PacketBufferTLVReader reader;
     984            0 :     InvokeRequestMessage::Parser invokeRequestMessage;
     985            0 :     InvokeRequests::Parser invokeRequests;
     986            0 :     reader.Init(std::move(payload));
     987            0 :     VerifyOrDieWithMsg(invokeRequestMessage.Init(reader) == CHIP_NO_ERROR, DataManagement,
     988              :                        "TH Failure: Failed 'invokeRequestMessage.Init(reader)'");
     989              : #if CHIP_CONFIG_IM_PRETTY_PRINT
     990            0 :     TEMPORARY_RETURN_IGNORED invokeRequestMessage.PrettyPrint();
     991              : #endif
     992              : 
     993            0 :     VerifyOrDieWithMsg(invokeRequestMessage.GetSuppressResponse(&mSuppressResponse) == CHIP_NO_ERROR, DataManagement,
     994              :                        "DUT Failure: Mandatory SuppressResponse field missing");
     995            0 :     VerifyOrDieWithMsg(invokeRequestMessage.GetTimedRequest(&mTimedRequest) == CHIP_NO_ERROR, DataManagement,
     996              :                        "DUT Failure: Mandatory TimedRequest field missing");
     997            0 :     VerifyOrDieWithMsg(invokeRequestMessage.GetInvokeRequests(&invokeRequests) == CHIP_NO_ERROR, DataManagement,
     998              :                        "DUT Failure: Mandatory InvokeRequests field missing");
     999            0 :     VerifyOrDieWithMsg(mTimedRequest == isTimedInvoke, DataManagement,
    1000              :                        "DUT Failure: TimedRequest value in message mismatches action");
    1001              : 
    1002              :     {
    1003            0 :         InvokeRequestMessage::Parser validationInvokeRequestMessage = invokeRequestMessage;
    1004            0 :         VerifyOrDieWithMsg(ValidateInvokeRequestMessageAndBuildRegistry(validationInvokeRequestMessage) == CHIP_NO_ERROR,
    1005              :                            DataManagement, "DUT Failure: InvokeRequestMessage contents were invalid");
    1006              :     }
    1007              : 
    1008            0 :     TLV::TLVReader invokeRequestsReader;
    1009            0 :     invokeRequests.GetReader(&invokeRequestsReader);
    1010              : 
    1011            0 :     size_t commandCount = 0;
    1012            0 :     VerifyOrDieWithMsg(TLV::Utilities::Count(invokeRequestsReader, commandCount, false /* recurse */) == CHIP_NO_ERROR,
    1013              :                        DataManagement,
    1014              :                        "TH Failure: Failed to get the length of InvokeRequests after InvokeRequestMessage validation");
    1015              : 
    1016              :     // The command count check (specifically for a count of 2) is tied to IDM_1_3. This may need adjustment for
    1017              :     // compatibility with future test plans.
    1018            0 :     VerifyOrDieWithMsg(commandCount == 2, DataManagement, "DUT failure: We were strictly expecting exactly 2 InvokeRequests");
    1019            0 :     mReserveSpaceForMoreChunkMessages = true;
    1020              : 
    1021              :     {
    1022              :         // Response path is the same as request path since we are replying with a failure message.
    1023            0 :         ConcreteCommandPath concreteResponsePath1;
    1024            0 :         ConcreteCommandPath concreteResponsePath2;
    1025            0 :         VerifyOrDieWithMsg(
    1026              :             TestOnlyExtractCommandPathFromNextInvokeRequest(invokeRequestsReader, concreteResponsePath1) == CHIP_NO_ERROR,
    1027              :             DataManagement, "DUT Failure: Issues encountered while extracting the ConcreteCommandPath from the first request");
    1028            0 :         VerifyOrDieWithMsg(
    1029              :             TestOnlyExtractCommandPathFromNextInvokeRequest(invokeRequestsReader, concreteResponsePath2) == CHIP_NO_ERROR,
    1030              :             DataManagement, "DUT Failure: Issues encountered while extracting the ConcreteCommandPath from the second request");
    1031              : 
    1032            0 :         if (faultType == NlFaultInjectionType::SeparateResponseMessagesAndInvertedResponseOrder)
    1033              :         {
    1034            0 :             ConcreteCommandPath temp(concreteResponsePath1);
    1035            0 :             concreteResponsePath1 = concreteResponsePath2;
    1036            0 :             concreteResponsePath2 = temp;
    1037              :         }
    1038              : 
    1039            0 :         VerifyOrDieWithMsg(FallibleAddStatus(concreteResponsePath1, Status::Failure) == CHIP_NO_ERROR, DataManagement,
    1040              :                            "TH Failure: Error adding the first InvokeResponse");
    1041            0 :         if (faultType == NlFaultInjectionType::SeparateResponseMessages ||
    1042              :             faultType == NlFaultInjectionType::SeparateResponseMessagesAndInvertedResponseOrder)
    1043              :         {
    1044            0 :             VerifyOrDieWithMsg(FinalizeInvokeResponseMessageAndPrepareNext() == CHIP_NO_ERROR, DataManagement,
    1045              :                                "TH Failure: Failed to create second InvokeResponseMessage");
    1046              :         }
    1047            0 :         if (faultType != NlFaultInjectionType::SkipSecondResponse)
    1048              :         {
    1049            0 :             VerifyOrDieWithMsg(FallibleAddStatus(concreteResponsePath2, Status::Failure) == CHIP_NO_ERROR, DataManagement,
    1050              :                                "TH Failure: Error adding the second InvokeResponse");
    1051              :         }
    1052              :     }
    1053              : 
    1054            0 :     VerifyOrDieWithMsg(invokeRequestsReader.Next() == CHIP_END_OF_TLV, DataManagement,
    1055              :                        "DUT Failure: Unexpected TLV ending of InvokeRequests");
    1056            0 :     VerifyOrDieWithMsg(invokeRequestMessage.ExitContainer() == CHIP_NO_ERROR, DataManagement,
    1057              :                        "DUT Failure: InvokeRequestMessage TLV is not properly terminated");
    1058            0 : }
    1059              : #endif // CHIP_WITH_NLFAULTINJECTION
    1060              : 
    1061              : } // namespace app
    1062              : } // namespace chip
        

Generated by: LCOV version 2.0-1