Matter SDK Coverage Report
Current view: top level - app - CommandHandlerImpl.cpp (source / functions) Coverage Total Hit
Test: SHA:6c8f029dd2432dc900f1c9245c324e69bd79e40a Lines: 79.0 % 490 387
Test Date: 2026-08-08 07:40:09 Functions: 87.2 % 47 41

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

Generated by: LCOV version 2.0-1