Matter SDK Coverage Report
Current view: top level - app - CommandSender.cpp (source / functions) Coverage Total Hit
Test: SHA:6c8f029dd2432dc900f1c9245c324e69bd79e40a Lines: 84.6 % 370 313
Test Date: 2026-08-08 07:40:09 Functions: 87.5 % 32 28

            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              : 
      19              : #include "CommandSender.h"
      20              : #include "StatusResponse.h"
      21              : #include <app/InteractionModelTimeout.h>
      22              : #include <app/TimedRequest.h>
      23              : #include <platform/LockTracker.h>
      24              : #include <protocols/Protocols.h>
      25              : #include <protocols/interaction_model/Constants.h>
      26              : 
      27              : namespace chip {
      28              : namespace app {
      29              : namespace {
      30              : 
      31              : // Gets the CommandRef if available. Error returned if we expected CommandRef and it wasn't
      32              : // provided in the response.
      33              : template <typename ParserT>
      34           63 : CHIP_ERROR GetRef(ParserT aParser, Optional<uint16_t> & aRef, bool commandRefRequired)
      35              : {
      36           63 :     CHIP_ERROR err = CHIP_NO_ERROR;
      37              :     uint16_t ref;
      38           63 :     err = aParser.GetRef(&ref);
      39              : 
      40          187 :     VerifyOrReturnError(err == CHIP_NO_ERROR || err == CHIP_END_OF_TLV, err);
      41          126 :     if (err == CHIP_END_OF_TLV)
      42              :     {
      43           61 :         if (commandRefRequired)
      44              :         {
      45            0 :             return CHIP_ERROR_INVALID_ARGUMENT;
      46              :         }
      47           61 :         aRef = NullOptional;
      48           61 :         return CHIP_NO_ERROR;
      49              :     }
      50              : 
      51            2 :     aRef = MakeOptional(ref);
      52            2 :     return CHIP_NO_ERROR;
      53              : }
      54              : 
      55              : } // namespace
      56              : 
      57           53 : CommandSender::CommandSender(Callback * apCallback, Messaging::ExchangeManager * apExchangeMgr, bool aIsTimedRequest,
      58           53 :                              bool aSuppressResponse, bool aAllowLargePayload) :
      59           53 :     mExchangeCtx(*this),
      60           53 :     mCallbackHandle(apCallback), mpExchangeMgr(apExchangeMgr), mSuppressResponse(aSuppressResponse), mTimedRequest(aIsTimedRequest),
      61          106 :     mAllowLargePayload(aAllowLargePayload)
      62              : {
      63           53 :     assertChipStackLockedByCurrentThread();
      64           53 : }
      65              : 
      66           12 : CommandSender::CommandSender(ExtendableCallback * apExtendableCallback, Messaging::ExchangeManager * apExchangeMgr,
      67           12 :                              bool aIsTimedRequest, bool aSuppressResponse, bool aAllowLargePayload) :
      68           12 :     mExchangeCtx(*this),
      69           12 :     mCallbackHandle(apExtendableCallback), mpExchangeMgr(apExchangeMgr), mSuppressResponse(aSuppressResponse),
      70           24 :     mTimedRequest(aIsTimedRequest), mUseExtendableCallback(true), mAllowLargePayload(aAllowLargePayload)
      71              : {
      72           12 :     assertChipStackLockedByCurrentThread();
      73              : #if CHIP_CONFIG_COMMAND_SENDER_BUILTIN_SUPPORT_FOR_BATCHED_COMMANDS
      74           12 :     mpPendingResponseTracker = &mNonTestPendingResponseTracker;
      75              : #endif // CHIP_CONFIG_COMMAND_SENDER_BUILTIN_SUPPORT_FOR_BATCHED_COMMANDS
      76           12 : }
      77              : 
      78           65 : CommandSender::~CommandSender()
      79              : {
      80           65 :     assertChipStackLockedByCurrentThread();
      81           65 : }
      82              : 
      83           99 : CHIP_ERROR CommandSender::AllocateBuffer()
      84              : {
      85           99 :     if (!mBufferAllocated)
      86              :     {
      87           61 :         mCommandMessageWriter.Reset();
      88              : 
      89           61 :         System::PacketBufferHandle commandPacket;
      90           61 :         size_t bufferSizeToAllocate = kMaxSecureSduLengthBytes;
      91           61 :         if (mAllowLargePayload)
      92              :         {
      93            0 :             bufferSizeToAllocate = kMaxLargeSecureSduLengthBytes;
      94              :         }
      95           61 :         commandPacket = System::PacketBufferHandle::New(bufferSizeToAllocate);
      96              : 
      97           61 :         VerifyOrReturnError(!commandPacket.IsNull(), CHIP_ERROR_NO_MEMORY);
      98              :         // On some platforms we can get more available length in the packet than what we requested.
      99              :         // It is vital that we only use up to bufferSizeToAllocate for the entire packet and
     100              :         // nothing more.
     101           61 :         uint32_t reservedSize = 0;
     102           61 :         if (commandPacket->AvailableDataLength() > bufferSizeToAllocate)
     103              :         {
     104            0 :             reservedSize = static_cast<uint32_t>(commandPacket->AvailableDataLength() - bufferSizeToAllocate);
     105              :         }
     106              : 
     107           61 :         mCommandMessageWriter.Init(std::move(commandPacket));
     108           61 :         ReturnErrorOnFailure(mInvokeRequestBuilder.InitWithEndBufferReserved(&mCommandMessageWriter));
     109              :         // Reserving space for MIC at the end.
     110           61 :         ReturnErrorOnFailure(
     111              :             mInvokeRequestBuilder.GetWriter()->ReserveBuffer(reservedSize + Crypto::CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES));
     112              : 
     113           61 :         mInvokeRequestBuilder.SuppressResponse(mSuppressResponse).TimedRequest(mTimedRequest);
     114           61 :         ReturnErrorOnFailure(mInvokeRequestBuilder.GetError());
     115              : 
     116           61 :         mInvokeRequestBuilder.CreateInvokeRequests(/* aReserveEndBuffer = */ true);
     117           61 :         ReturnErrorOnFailure(mInvokeRequestBuilder.GetError());
     118              : 
     119           61 :         mBufferAllocated = true;
     120           61 :     }
     121              : 
     122           99 :     return CHIP_NO_ERROR;
     123              : }
     124              : 
     125           55 : CHIP_ERROR CommandSender::SendCommandRequestInternal(const SessionHandle & session, Optional<System::Clock::Timeout> timeout)
     126              : {
     127           55 :     VerifyOrReturnError(mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
     128              : 
     129           54 :     ReturnErrorOnFailure(Finalize(mPendingInvokeData));
     130              : 
     131              :     // Create a new exchange context.
     132           54 :     auto exchange = mpExchangeMgr->NewContext(session, this);
     133           54 :     VerifyOrReturnError(exchange != nullptr, CHIP_ERROR_NO_MEMORY);
     134              : 
     135           54 :     mExchangeCtx.Grab(exchange);
     136           54 :     VerifyOrReturnError(!mExchangeCtx->IsGroupExchangeContext(), CHIP_ERROR_INVALID_MESSAGE_TYPE);
     137              : 
     138          108 :     mExchangeCtx->SetResponseTimeout(
     139           54 :         timeout.ValueOr(session->ComputeRoundTripTimeout(app::kExpectedIMProcessingTime, true /*isFirstMessageOnExchange*/)));
     140              : 
     141           54 :     if (mTimedInvokeTimeoutMs.HasValue())
     142              :     {
     143            0 :         ReturnErrorOnFailure(TimedRequest::Send(mExchangeCtx.Get(), mTimedInvokeTimeoutMs.Value()));
     144            0 :         MoveToState(State::AwaitingTimedStatus);
     145            0 :         return CHIP_NO_ERROR;
     146              :     }
     147              : 
     148           54 :     CHIP_ERROR err = SendInvokeRequest();
     149          108 :     if (err == CHIP_NO_ERROR && mSuppressResponse)
     150              :     {
     151            2 :         Close();
     152              :     }
     153           54 :     return err;
     154              : }
     155              : 
     156              : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
     157            0 : CHIP_ERROR CommandSender::TestOnlyCommandSenderTimedRequestFlagWithNoTimedInvoke(const SessionHandle & session,
     158              :                                                                                  Optional<System::Clock::Timeout> timeout)
     159              : {
     160            0 :     VerifyOrReturnError(mTimedRequest, CHIP_ERROR_INCORRECT_STATE);
     161            0 :     return SendCommandRequestInternal(session, timeout);
     162              : }
     163              : #endif
     164              : 
     165           55 : CHIP_ERROR CommandSender::SendCommandRequest(const SessionHandle & session, Optional<System::Clock::Timeout> timeout)
     166              : {
     167              :     // If the command is expected to be large, ensure that the underlying
     168              :     // session supports it.
     169           55 :     if (mAllowLargePayload)
     170              :     {
     171            0 :         VerifyOrReturnError(session->AllowsLargePayload(), CHIP_ERROR_INCORRECT_STATE);
     172              :     }
     173              : 
     174           55 :     if (mTimedRequest != mTimedInvokeTimeoutMs.HasValue())
     175              :     {
     176            0 :         ChipLogError(
     177              :             DataManagement,
     178              :             "Inconsistent timed request state in CommandSender: mTimedRequest (%d) != mTimedInvokeTimeoutMs.HasValue() (%d)",
     179              :             mTimedRequest, mTimedInvokeTimeoutMs.HasValue());
     180            0 :         return CHIP_ERROR_INCORRECT_STATE;
     181              :     }
     182              : 
     183           55 :     if (mTimedRequest && mSuppressResponse)
     184              :     {
     185            0 :         ChipLogError(DataManagement, "TimedRequest cannot be sent with SuppressResponse");
     186            0 :         return CHIP_ERROR_INVALID_ARGUMENT;
     187              :     }
     188              : 
     189           55 :     return SendCommandRequestInternal(session, timeout);
     190              : }
     191              : 
     192            1 : CHIP_ERROR CommandSender::SendGroupCommandRequest(const SessionHandle & session)
     193              : {
     194            1 :     VerifyOrReturnError(mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
     195              : 
     196            1 :     ReturnErrorOnFailure(Finalize(mPendingInvokeData));
     197              : 
     198              :     // Create a new exchange context.
     199            1 :     auto exchange = mpExchangeMgr->NewContext(session, this);
     200            1 :     VerifyOrReturnError(exchange != nullptr, CHIP_ERROR_NO_MEMORY);
     201              : 
     202            1 :     mExchangeCtx.Grab(exchange);
     203            1 :     VerifyOrReturnError(mExchangeCtx->IsGroupExchangeContext(), CHIP_ERROR_INVALID_MESSAGE_TYPE);
     204              : 
     205            1 :     ReturnErrorOnFailure(SendInvokeRequest());
     206              : 
     207            1 :     Close();
     208            1 :     return CHIP_NO_ERROR;
     209              : }
     210              : 
     211           55 : CHIP_ERROR CommandSender::SendInvokeRequest()
     212              : {
     213              :     using namespace Protocols::InteractionModel;
     214              :     using namespace Messaging;
     215              : 
     216           55 :     SendFlags sendFlags;
     217           55 :     sendFlags.Set(SendMessageFlags::kExpectResponse, !mSuppressResponse);
     218              : 
     219           55 :     ReturnErrorOnFailure(mExchangeCtx->SendMessage(MsgType::InvokeCommandRequest, std::move(mPendingInvokeData), sendFlags));
     220              : 
     221           55 :     if (mSuppressResponse)
     222              :     {
     223              :         // No response, so we can immediately terminate
     224            2 :         return CHIP_NO_ERROR;
     225              :     }
     226              : 
     227           53 :     MoveToState(State::AwaitingResponse);
     228              : 
     229           53 :     return CHIP_NO_ERROR;
     230              : }
     231              : 
     232           49 : CHIP_ERROR CommandSender::OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
     233              :                                             System::PacketBufferHandle && aPayload)
     234              : {
     235              :     using namespace Protocols::InteractionModel;
     236              : 
     237           49 :     if (mState == State::AwaitingResponse)
     238              :     {
     239           49 :         MoveToState(State::ResponseReceived);
     240              :     }
     241              : 
     242           49 :     CHIP_ERROR err           = CHIP_NO_ERROR;
     243           49 :     bool sendStatusResponse  = false;
     244           49 :     bool moreChunkedMessages = false;
     245           49 :     VerifyOrExit(apExchangeContext == mExchangeCtx.Get(), err = CHIP_ERROR_INCORRECT_STATE);
     246           49 :     sendStatusResponse = true;
     247              : 
     248           49 :     if (mState == State::AwaitingTimedStatus)
     249              :     {
     250            0 :         if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::StatusResponse))
     251              :         {
     252            0 :             CHIP_ERROR statusError = CHIP_NO_ERROR;
     253            0 :             SuccessOrExit(err = StatusResponse::ProcessStatusResponse(std::move(aPayload), statusError));
     254            0 :             sendStatusResponse = false;
     255            0 :             SuccessOrExit(err = statusError);
     256            0 :             err = SendInvokeRequest();
     257              :         }
     258              :         else
     259              :         {
     260            0 :             err = CHIP_ERROR_INVALID_MESSAGE_TYPE;
     261              :         }
     262              :         // Skip all other processing here (which is for the response to the
     263              :         // invoke request), no matter whether err is success or not.
     264            0 :         goto exit;
     265              :     }
     266              : 
     267           49 :     if (aPayloadHeader.HasMessageType(MsgType::InvokeCommandResponse))
     268              :     {
     269           44 :         mInvokeResponseMessageCount++;
     270           44 :         err = ProcessInvokeResponse(std::move(aPayload), moreChunkedMessages);
     271           44 :         SuccessOrExit(err);
     272           43 :         if (moreChunkedMessages)
     273              :         {
     274            0 :             TEMPORARY_RETURN_IGNORED StatusResponse::Send(Status::Success, apExchangeContext, /*aExpectResponse = */ true);
     275            0 :             MoveToState(State::AwaitingResponse);
     276            0 :             return CHIP_NO_ERROR;
     277              :         }
     278           43 :         sendStatusResponse = false;
     279              :     }
     280            5 :     else if (aPayloadHeader.HasMessageType(MsgType::StatusResponse))
     281              :     {
     282            4 :         CHIP_ERROR statusError = CHIP_NO_ERROR;
     283            7 :         SuccessOrExit(err = StatusResponse::ProcessStatusResponse(std::move(aPayload), statusError));
     284            3 :         SuccessOrExit(err = statusError);
     285            0 :         err = CHIP_ERROR_INVALID_MESSAGE_TYPE;
     286              :     }
     287              :     else
     288              :     {
     289            1 :         err = CHIP_ERROR_INVALID_MESSAGE_TYPE;
     290              :     }
     291              : 
     292           49 : exit:
     293           98 :     if (err != CHIP_NO_ERROR)
     294              :     {
     295            6 :         OnErrorCallback(err);
     296              :     }
     297              : 
     298           49 :     if (sendStatusResponse)
     299              :     {
     300            6 :         TEMPORARY_RETURN_IGNORED StatusResponse::Send(Status::InvalidAction, apExchangeContext, /*aExpectResponse = */ false);
     301              :     }
     302              : 
     303           49 :     if (mState != State::AwaitingResponse)
     304              :     {
     305           98 :         if (err == CHIP_NO_ERROR)
     306              :         {
     307           43 :             FlushNoCommandResponse();
     308              :         }
     309           49 :         Close();
     310              :     }
     311              :     // Else we got a response to a Timed Request and just sent the invoke.
     312              : 
     313           49 :     return err;
     314              : }
     315              : 
     316           48 : CHIP_ERROR CommandSender::ProcessInvokeResponse(System::PacketBufferHandle && payload, bool & moreChunkedMessages)
     317              : {
     318           48 :     CHIP_ERROR err = CHIP_NO_ERROR;
     319           48 :     System::PacketBufferTLVReader reader;
     320           48 :     TLV::TLVReader invokeResponsesReader;
     321           48 :     InvokeResponseMessage::Parser invokeResponseMessage;
     322           48 :     InvokeResponseIBs::Parser invokeResponses;
     323           48 :     bool suppressResponse = false;
     324              : 
     325           48 :     reader.Init(std::move(payload));
     326           48 :     ReturnErrorOnFailure(invokeResponseMessage.Init(reader));
     327              : 
     328              : #if CHIP_CONFIG_IM_PRETTY_PRINT
     329           47 :     TEMPORARY_RETURN_IGNORED invokeResponseMessage.PrettyPrint();
     330              : #endif
     331              : 
     332           47 :     ReturnErrorOnFailure(invokeResponseMessage.GetSuppressResponse(&suppressResponse));
     333           47 :     ReturnErrorOnFailure(invokeResponseMessage.GetInvokeResponses(&invokeResponses));
     334           47 :     invokeResponses.GetReader(&invokeResponsesReader);
     335              : 
     336          218 :     while (CHIP_NO_ERROR == (err = invokeResponsesReader.Next()))
     337              :     {
     338           63 :         VerifyOrReturnError(TLV::AnonymousTag() == invokeResponsesReader.GetTag(), CHIP_ERROR_INVALID_TLV_TAG);
     339           63 :         InvokeResponseIB::Parser invokeResponse;
     340           63 :         ReturnErrorOnFailure(invokeResponse.Init(invokeResponsesReader));
     341           63 :         ReturnErrorOnFailure(ProcessInvokeResponseIB(invokeResponse));
     342              :     }
     343              : 
     344           46 :     err = invokeResponseMessage.GetMoreChunkedMessages(&moreChunkedMessages);
     345              :     // If the MoreChunkedMessages element is absent, we receive CHIP_END_OF_TLV. In this
     346              :     // case, per the specification, a default value of false is used.
     347           92 :     if (CHIP_END_OF_TLV == err)
     348              :     {
     349           46 :         moreChunkedMessages = false;
     350           46 :         err                 = CHIP_NO_ERROR;
     351              :     }
     352           46 :     ReturnErrorOnFailure(err);
     353              : 
     354           46 :     if (suppressResponse && moreChunkedMessages)
     355              :     {
     356            0 :         ChipLogError(DataManagement, "Spec violation! InvokeResponse has suppressResponse=true, and moreChunkedMessages=true");
     357              :         // TODO Is there a better error to return here?
     358            0 :         return CHIP_ERROR_INVALID_TLV_ELEMENT;
     359              :     }
     360              : 
     361              :     // if we have exhausted this container
     362           92 :     if (CHIP_END_OF_TLV == err)
     363              :     {
     364            0 :         err = CHIP_NO_ERROR;
     365              :     }
     366           46 :     ReturnErrorOnFailure(err);
     367           46 :     return invokeResponseMessage.ExitContainer();
     368           48 : }
     369              : 
     370            4 : void CommandSender::OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext)
     371              : {
     372              :     // TimedInvoke requires a StatusResponse. So it is wrong to send a TimedInvoke with SuppressResponse.
     373              :     // If the invoke is not TimedRequest and users set SuppressResponse, a timer would NOT be triggered and
     374              :     // OnResponseTimeout would never be called. So we can safely report error without checking mSuppressResponse here.
     375            4 :     ChipLogProgress(DataManagement, "Time out! failed to receive invoke command response from Exchange: " ChipLogFormatExchange,
     376              :                     ChipLogValueExchange(apExchangeContext));
     377              : 
     378            4 :     OnErrorCallback(CHIP_ERROR_TIMEOUT);
     379            4 :     Close();
     380            4 : }
     381              : 
     382           45 : void CommandSender::FlushNoCommandResponse()
     383              : {
     384           45 :     if (mpPendingResponseTracker && mUseExtendableCallback && mCallbackHandle.extendableCallback)
     385              :     {
     386            6 :         Optional<uint16_t> commandRef = mpPendingResponseTracker->PopPendingResponse();
     387            7 :         while (commandRef.HasValue())
     388              :         {
     389            1 :             NoResponseData noResponseData = { commandRef.Value() };
     390            1 :             mCallbackHandle.extendableCallback->OnNoResponse(this, noResponseData);
     391            1 :             commandRef = mpPendingResponseTracker->PopPendingResponse();
     392              :         }
     393              :     }
     394           45 : }
     395              : 
     396           56 : void CommandSender::Close()
     397              : {
     398           56 :     mSuppressResponse = false;
     399           56 :     mTimedRequest     = false;
     400           56 :     MoveToState(State::AwaitingDestruction);
     401           56 :     OnDoneCallback();
     402           56 : }
     403              : 
     404           63 : CHIP_ERROR CommandSender::ProcessInvokeResponseIB(InvokeResponseIB::Parser & aInvokeResponse)
     405              : {
     406           63 :     CHIP_ERROR err = CHIP_NO_ERROR;
     407              :     ClusterId clusterId;
     408              :     CommandId commandId;
     409              :     EndpointId endpointId;
     410              :     // Default to success when an invoke response is received.
     411           63 :     StatusIB statusIB;
     412              : 
     413              :     {
     414           63 :         bool hasDataResponse = false;
     415           63 :         TLV::TLVReader commandDataReader;
     416           63 :         Optional<uint16_t> commandRef;
     417           63 :         bool commandRefRequired = (mFinishedCommandCount > 1);
     418              : 
     419           63 :         CommandStatusIB::Parser commandStatus;
     420           63 :         err = aInvokeResponse.GetStatus(&commandStatus);
     421          126 :         if (CHIP_NO_ERROR == err)
     422              :         {
     423           43 :             CommandPathIB::Parser commandPath;
     424           43 :             ReturnErrorOnFailure(commandStatus.GetPath(&commandPath));
     425           43 :             ReturnErrorOnFailure(commandPath.GetClusterId(&clusterId));
     426           43 :             ReturnErrorOnFailure(commandPath.GetCommandId(&commandId));
     427           43 :             ReturnErrorOnFailure(commandPath.GetEndpointId(&endpointId));
     428              : 
     429           43 :             StatusIB::Parser status;
     430           43 :             TEMPORARY_RETURN_IGNORED commandStatus.GetErrorStatus(&status);
     431           43 :             ReturnErrorOnFailure(status.DecodeStatusIB(statusIB));
     432           43 :             ReturnErrorOnFailure(GetRef(commandStatus, commandRef, commandRefRequired));
     433              :         }
     434           40 :         else if (CHIP_END_OF_TLV == err)
     435              :         {
     436           20 :             CommandDataIB::Parser commandData;
     437           20 :             CommandPathIB::Parser commandPath;
     438           20 :             ReturnErrorOnFailure(aInvokeResponse.GetCommand(&commandData));
     439           20 :             ReturnErrorOnFailure(commandData.GetPath(&commandPath));
     440           20 :             ReturnErrorOnFailure(commandPath.GetEndpointId(&endpointId));
     441           20 :             ReturnErrorOnFailure(commandPath.GetClusterId(&clusterId));
     442           20 :             ReturnErrorOnFailure(commandPath.GetCommandId(&commandId));
     443           20 :             TEMPORARY_RETURN_IGNORED commandData.GetFields(&commandDataReader);
     444           20 :             ReturnErrorOnFailure(GetRef(commandData, commandRef, commandRefRequired));
     445           20 :             err             = CHIP_NO_ERROR;
     446           20 :             hasDataResponse = true;
     447              :         }
     448              : 
     449          126 :         if (err != CHIP_NO_ERROR)
     450              :         {
     451            0 :             ChipLogError(DataManagement, "Received malformed Command Response, err=%" CHIP_ERROR_FORMAT, err.Format());
     452              :         }
     453              :         else
     454              :         {
     455           63 :             if (hasDataResponse)
     456              :             {
     457           20 :                 ChipLogProgress(DataManagement,
     458              :                                 "Received Command Response Data, Endpoint=%u Cluster=" ChipLogFormatMEI
     459              :                                 " Command=" ChipLogFormatMEI,
     460              :                                 endpointId, ChipLogValueMEI(clusterId), ChipLogValueMEI(commandId));
     461              :             }
     462              :             else
     463              :             {
     464           43 :                 ChipLogProgress(DataManagement,
     465              :                                 "Received Command Response Status for Endpoint=%u Cluster=" ChipLogFormatMEI
     466              :                                 " Command=" ChipLogFormatMEI " Status=0x%x",
     467              :                                 endpointId, ChipLogValueMEI(clusterId), ChipLogValueMEI(commandId),
     468              :                                 to_underlying(statusIB.mStatus));
     469              :             }
     470              :         }
     471           63 :         ReturnErrorOnFailure(err);
     472              : 
     473           63 :         if (commandRef.HasValue() && mpPendingResponseTracker != nullptr)
     474              :         {
     475            2 :             err = mpPendingResponseTracker->Remove(commandRef.Value());
     476            4 :             if (err != CHIP_NO_ERROR)
     477              :             {
     478              :                 // This can happen for two reasons:
     479              :                 // 1. The current InvokeResponse is a duplicate (based on its commandRef).
     480              :                 // 2. The current InvokeResponse is for a request we never sent (based on its commandRef).
     481              :                 // Used when logging errors related to server violating spec.
     482            1 :                 [[maybe_unused]] ScopedNodeId remoteScopedNode;
     483            1 :                 if (mExchangeCtx.Get() && mExchangeCtx.Get()->HasSessionHandle())
     484              :                 {
     485            0 :                     remoteScopedNode = mExchangeCtx.Get()->GetSessionHandle()->GetPeer();
     486              :                 }
     487            1 :                 ChipLogError(DataManagement,
     488              :                              "Received Unexpected Response from remote node " ChipLogFormatScopedNodeId ", commandRef=%u",
     489              :                              ChipLogValueScopedNodeId(remoteScopedNode), commandRef.Value());
     490            1 :                 return err;
     491              :             }
     492              :         }
     493              : 
     494           66 :         if (!commandRef.HasValue() && !commandRefRequired && mpPendingResponseTracker != nullptr &&
     495            4 :             mpPendingResponseTracker->Count() == 1)
     496              :         {
     497              :             // We have sent out a single invoke request. As per spec, server in this case doesn't need to provide the CommandRef
     498              :             // in the response. This is allowed to support communicating with a legacy server. In this case we assume the response
     499              :             // is associated with the only command we sent out.
     500            1 :             commandRef = mpPendingResponseTracker->PopPendingResponse();
     501              :         }
     502              : 
     503              :         // When using ExtendableCallbacks, we are adhering to a different API contract where path
     504              :         // specific errors are sent to the OnResponse callback. For more information on the history
     505              :         // of this issue please see https://github.com/project-chip/connectedhomeip/issues/30991
     506           62 :         if (statusIB.IsSuccess() || mUseExtendableCallback)
     507              :         {
     508           41 :             const ConcreteCommandPath concretePath = ConcreteCommandPath(endpointId, clusterId, commandId);
     509           41 :             ResponseData responseData              = { concretePath, statusIB };
     510           41 :             responseData.data                      = hasDataResponse ? &commandDataReader : nullptr;
     511           41 :             responseData.commandRef                = commandRef;
     512           41 :             OnResponseCallback(responseData);
     513              :         }
     514              :         else
     515              :         {
     516           21 :             OnErrorCallback(statusIB.ToChipError());
     517              :         }
     518              :     }
     519           62 :     return CHIP_NO_ERROR;
     520              : }
     521              : 
     522           10 : CHIP_ERROR CommandSender::SetCommandSenderConfig(CommandSender::ConfigParameters & aConfigParams)
     523              : {
     524           10 :     VerifyOrReturnError(mState == State::Idle, CHIP_ERROR_INCORRECT_STATE);
     525           10 :     VerifyOrReturnError(aConfigParams.remoteMaxPathsPerInvoke > 0, CHIP_ERROR_INVALID_ARGUMENT);
     526           10 :     if (mpPendingResponseTracker != nullptr)
     527              :     {
     528              : 
     529            9 :         mRemoteMaxPathsPerInvoke = aConfigParams.remoteMaxPathsPerInvoke;
     530            9 :         mBatchCommandsEnabled    = (aConfigParams.remoteMaxPathsPerInvoke > 1);
     531              :     }
     532              :     else
     533              :     {
     534            1 :         VerifyOrReturnError(aConfigParams.remoteMaxPathsPerInvoke == 1, CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
     535              :     }
     536            9 :     return CHIP_NO_ERROR;
     537              : }
     538              : 
     539           69 : CHIP_ERROR CommandSender::PrepareCommand(const CommandPathParams & aCommandPathParams,
     540              :                                          PrepareCommandParameters & aPrepareCommandParams)
     541              : {
     542           69 :     ReturnErrorOnFailure(AllocateBuffer());
     543              : 
     544              :     //
     545              :     // We must not be in the middle of preparing a command, and must not have already sent InvokeRequestMessage.
     546              :     //
     547           69 :     bool canAddAnotherCommand = (mState == State::AddedCommand && mBatchCommandsEnabled && mUseExtendableCallback);
     548           69 :     VerifyOrReturnError(mState == State::Idle || canAddAnotherCommand, CHIP_ERROR_INCORRECT_STATE);
     549              : 
     550           68 :     VerifyOrReturnError(mFinishedCommandCount < mRemoteMaxPathsPerInvoke, CHIP_ERROR_MAXIMUM_PATHS_PER_INVOKE_EXCEEDED);
     551              : 
     552           68 :     if (mBatchCommandsEnabled)
     553              :     {
     554           14 :         VerifyOrReturnError(mpPendingResponseTracker != nullptr, CHIP_ERROR_INCORRECT_STATE);
     555           14 :         VerifyOrReturnError(aPrepareCommandParams.commandRef.HasValue(), CHIP_ERROR_INVALID_ARGUMENT);
     556           14 :         uint16_t commandRef = aPrepareCommandParams.commandRef.Value();
     557           14 :         VerifyOrReturnError(!mpPendingResponseTracker->IsTracked(commandRef), CHIP_ERROR_INVALID_ARGUMENT);
     558              :     }
     559              : 
     560           67 :     InvokeRequests::Builder & invokeRequests = mInvokeRequestBuilder.GetInvokeRequests();
     561           67 :     CommandDataIB::Builder & invokeRequest   = invokeRequests.CreateCommandData();
     562           67 :     ReturnErrorOnFailure(invokeRequests.GetError());
     563           67 :     CommandPathIB::Builder & path = invokeRequest.CreatePath();
     564           67 :     ReturnErrorOnFailure(invokeRequest.GetError());
     565           67 :     ReturnErrorOnFailure(path.Encode(aCommandPathParams));
     566              : 
     567           67 :     if (aPrepareCommandParams.startDataStruct)
     568              :     {
     569           37 :         ReturnErrorOnFailure(invokeRequest.GetWriter()->StartContainer(TLV::ContextTag(CommandDataIB::Tag::kFields),
     570              :                                                                        TLV::kTLVType_Structure, mDataElementContainerType));
     571              :     }
     572              : 
     573           67 :     MoveToState(State::AddingCommand);
     574           67 :     return CHIP_NO_ERROR;
     575              : }
     576              : 
     577           62 : CHIP_ERROR CommandSender::FinishCommand(FinishCommandParameters & aFinishCommandParams)
     578              : {
     579           62 :     if (mBatchCommandsEnabled)
     580              :     {
     581           10 :         VerifyOrReturnError(mpPendingResponseTracker != nullptr, CHIP_ERROR_INCORRECT_STATE);
     582           10 :         VerifyOrReturnError(aFinishCommandParams.commandRef.HasValue(), CHIP_ERROR_INVALID_ARGUMENT);
     583           10 :         uint16_t commandRef = aFinishCommandParams.commandRef.Value();
     584           10 :         VerifyOrReturnError(!mpPendingResponseTracker->IsTracked(commandRef), CHIP_ERROR_INVALID_ARGUMENT);
     585              :     }
     586              : 
     587           62 :     return FinishCommandInternal(aFinishCommandParams);
     588              : }
     589              : 
     590           30 : CHIP_ERROR CommandSender::AddRequestData(const CommandPathParams & aCommandPath, const DataModel::EncodableToTLV & aEncodable,
     591              :                                          AddRequestDataParameters & aAddRequestDataParams)
     592              : {
     593           30 :     ReturnErrorOnFailure(AllocateBuffer());
     594              : 
     595           30 :     RollbackInvokeRequest rollback(*this);
     596           30 :     PrepareCommandParameters prepareCommandParams(aAddRequestDataParams);
     597           30 :     ReturnErrorOnFailure(PrepareCommand(aCommandPath, prepareCommandParams));
     598           30 :     TLV::TLVWriter * writer = GetCommandDataIBTLVWriter();
     599           30 :     VerifyOrReturnError(writer != nullptr, CHIP_ERROR_INCORRECT_STATE);
     600           30 :     ReturnErrorOnFailure(aEncodable.EncodeTo(*writer, TLV::ContextTag(CommandDataIB::Tag::kFields)));
     601           29 :     FinishCommandParameters finishCommandParams(aAddRequestDataParams);
     602           29 :     ReturnErrorOnFailure(FinishCommand(finishCommandParams));
     603           29 :     rollback.DisableAutomaticRollback();
     604           29 :     return CHIP_NO_ERROR;
     605           30 : }
     606              : 
     607           64 : CHIP_ERROR CommandSender::FinishCommandInternal(FinishCommandParameters & aFinishCommandParams)
     608              : {
     609           64 :     CHIP_ERROR err = CHIP_NO_ERROR;
     610              : 
     611           64 :     VerifyOrReturnError(mState == State::AddingCommand, err = CHIP_ERROR_INCORRECT_STATE);
     612              : 
     613           64 :     CommandDataIB::Builder & commandData = mInvokeRequestBuilder.GetInvokeRequests().GetCommandData();
     614              : 
     615           64 :     if (aFinishCommandParams.endDataStruct)
     616              :     {
     617           35 :         ReturnErrorOnFailure(commandData.GetWriter()->EndContainer(mDataElementContainerType));
     618              :     }
     619              : 
     620           64 :     if (aFinishCommandParams.commandRef.HasValue())
     621              :     {
     622           13 :         ReturnErrorOnFailure(commandData.Ref(aFinishCommandParams.commandRef.Value()));
     623              :     }
     624              : 
     625           64 :     ReturnErrorOnFailure(commandData.EndOfCommandDataIB());
     626              : 
     627           64 :     MoveToState(State::AddedCommand);
     628           64 :     mFinishedCommandCount++;
     629              : 
     630           64 :     if (mpPendingResponseTracker && aFinishCommandParams.commandRef.HasValue())
     631              :     {
     632           12 :         TEMPORARY_RETURN_IGNORED mpPendingResponseTracker->Add(aFinishCommandParams.commandRef.Value());
     633              :     }
     634              : 
     635           64 :     if (aFinishCommandParams.timedInvokeTimeoutMs.HasValue())
     636              :     {
     637            0 :         SetTimedInvokeTimeoutMs(aFinishCommandParams.timedInvokeTimeoutMs);
     638              :     }
     639              : 
     640           64 :     return CHIP_NO_ERROR;
     641              : }
     642              : 
     643           67 : TLV::TLVWriter * CommandSender::GetCommandDataIBTLVWriter()
     644              : {
     645           67 :     if (mState != State::AddingCommand)
     646              :     {
     647            0 :         return nullptr;
     648              :     }
     649              : 
     650           67 :     return mInvokeRequestBuilder.GetInvokeRequests().GetCommandData().GetWriter();
     651              : }
     652              : 
     653            0 : void CommandSender::SetTimedInvokeTimeoutMs(const Optional<uint16_t> & aTimedInvokeTimeoutMs)
     654              : {
     655            0 :     if (!mTimedInvokeTimeoutMs.HasValue())
     656              :     {
     657            0 :         mTimedInvokeTimeoutMs = aTimedInvokeTimeoutMs;
     658              :     }
     659            0 :     else if (aTimedInvokeTimeoutMs.HasValue())
     660              :     {
     661            0 :         uint16_t newValue = std::min(mTimedInvokeTimeoutMs.Value(), aTimedInvokeTimeoutMs.Value());
     662            0 :         mTimedInvokeTimeoutMs.SetValue(newValue);
     663              :     }
     664            0 : }
     665              : 
     666           21 : size_t CommandSender::GetInvokeResponseMessageCount()
     667              : {
     668           21 :     return static_cast<size_t>(mInvokeResponseMessageCount);
     669              : }
     670              : 
     671           58 : CHIP_ERROR CommandSender::Finalize(System::PacketBufferHandle & commandPacket)
     672              : {
     673           58 :     VerifyOrReturnError(mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
     674           58 :     ReturnErrorOnFailure(mInvokeRequestBuilder.GetInvokeRequests().EndOfInvokeRequests());
     675           58 :     ReturnErrorOnFailure(mInvokeRequestBuilder.EndOfInvokeRequestMessage());
     676           58 :     return mCommandMessageWriter.Finalize(&commandPacket);
     677              : }
     678              : 
     679            0 : const char * CommandSender::GetStateStr() const
     680              : {
     681              : #if CHIP_DETAIL_LOGGING
     682            0 :     switch (mState)
     683              :     {
     684            0 :     case State::Idle:
     685            0 :         return "Idle";
     686              : 
     687            0 :     case State::AddingCommand:
     688            0 :         return "AddingCommand";
     689              : 
     690            0 :     case State::AddedCommand:
     691            0 :         return "AddedCommand";
     692              : 
     693            0 :     case State::AwaitingTimedStatus:
     694            0 :         return "AwaitingTimedStatus";
     695              : 
     696            0 :     case State::AwaitingResponse:
     697            0 :         return "AwaitingResponse";
     698              : 
     699            0 :     case State::ResponseReceived:
     700            0 :         return "ResponseReceived";
     701              : 
     702            0 :     case State::AwaitingDestruction:
     703            0 :         return "AwaitingDestruction";
     704              :     }
     705              : #endif // CHIP_DETAIL_LOGGING
     706            0 :     return "N/A";
     707              : }
     708              : 
     709          293 : void CommandSender::MoveToState(const State aTargetState)
     710              : {
     711          293 :     mState = aTargetState;
     712          293 :     ChipLogDetail(DataManagement, "ICR moving to [%10.10s]", GetStateStr());
     713          293 : }
     714              : 
     715           30 : CommandSender::RollbackInvokeRequest::RollbackInvokeRequest(CommandSender & aCommandSender) : mCommandSender(aCommandSender)
     716              : {
     717           30 :     VerifyOrReturn(mCommandSender.mBufferAllocated);
     718           30 :     VerifyOrReturn(mCommandSender.mState == State::Idle || mCommandSender.mState == State::AddedCommand);
     719           60 :     VerifyOrReturn(mCommandSender.mInvokeRequestBuilder.GetInvokeRequests().GetError() == CHIP_NO_ERROR);
     720           60 :     VerifyOrReturn(mCommandSender.mInvokeRequestBuilder.GetError() == CHIP_NO_ERROR);
     721           30 :     mCommandSender.mInvokeRequestBuilder.Checkpoint(mBackupWriter);
     722           30 :     mBackupState          = mCommandSender.mState;
     723           30 :     mRollbackInDestructor = true;
     724              : }
     725              : 
     726           31 : CommandSender::RollbackInvokeRequest::~RollbackInvokeRequest()
     727              : {
     728           30 :     VerifyOrReturn(mRollbackInDestructor);
     729            1 :     VerifyOrReturn(mCommandSender.mState == State::AddingCommand);
     730            1 :     ChipLogDetail(DataManagement, "Rolling back response");
     731              :     // TODO(#30453): Rollback of mInvokeRequestBuilder should handle resetting
     732              :     // InvokeRequests.
     733            1 :     mCommandSender.mInvokeRequestBuilder.GetInvokeRequests().ResetError();
     734            1 :     mCommandSender.mInvokeRequestBuilder.Rollback(mBackupWriter);
     735            1 :     mCommandSender.MoveToState(mBackupState);
     736            1 :     mRollbackInDestructor = false;
     737           30 : }
     738              : 
     739           29 : void CommandSender::RollbackInvokeRequest::DisableAutomaticRollback()
     740              : {
     741           29 :     mRollbackInDestructor = false;
     742           29 : }
     743              : 
     744              : } // namespace app
     745              : } // namespace chip
        

Generated by: LCOV version 2.0-1