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

Generated by: LCOV version 2.0-1