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

Generated by: LCOV version 2.0-1