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

Generated by: LCOV version 2.0-1