Coverage Report

Created: 2026-05-16 06:48

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