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

Generated by: LCOV version 2.0-1