Skip to content

Commit 8753139

Browse files
tehampsonrestyled-commitsbzbarsky-apple
authored
CommandSender: For batch commands, track requests are responded to (#32105)
* CommandSender: For batch commands, track requests are responded to * Rename * Restyled by whitespace * Restyled by clang-format * Restyled by gn * Self review updates * Quick fix * Quick fix * Restyled by whitespace * More fixes * Fix CI * Restyled by whitespace * Restyled by clang-format * Restyled by gn * Apply suggestions from code review Co-authored-by: Boris Zbarsky <bzbarsky@apple.com> * Address PR comments * Fix CI and address some PR comments * Restyled by clang-format * Update src/app/CommandSender.cpp Co-authored-by: Boris Zbarsky <bzbarsky@apple.com> * Update src/app/CommandSender.cpp Co-authored-by: Boris Zbarsky <bzbarsky@apple.com> * Address PR comments * Address comment --------- Co-authored-by: Restyled.io <commits@restyled.io> Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
1 parent a04f478 commit 8753139

12 files changed

+517
-49
lines changed

scripts/tools/check_includes_config.py

+3
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@
160160
'src/tracing/json/json_tracing.cpp': {'string', 'sstream'},
161161
'src/tracing/json/json_tracing.h': {'fstream', 'unordered_map'},
162162

163+
# Not intended for embedded clients
164+
'src/app/PendingResponseTrackerImpl.h': {'unordered_set'},
165+
163166
# Not intended for embedded clients
164167
'src/lib/support/jsontlv/JsonToTlv.cpp': {'sstream'},
165168
'src/lib/support/jsontlv/JsonToTlv.h': {'string'},

src/app/BUILD.gn

+3
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,9 @@ static_library("app") {
231231
"FailSafeContext.cpp",
232232
"FailSafeContext.h",
233233
"OTAUserConsentCommon.h",
234+
"PendingResponseTracker.h",
235+
"PendingResponseTrackerImpl.cpp",
236+
"PendingResponseTrackerImpl.h",
234237
"ReadHandler.cpp",
235238
"SafeAttributePersistenceProvider.h",
236239
"TimedRequest.cpp",

src/app/CommandSender.cpp

+70-15
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ CommandSender::CommandSender(ExtendableCallback * apExtendableCallback, Messagin
7575
mTimedRequest(aIsTimedRequest), mUseExtendableCallback(true)
7676
{
7777
assertChipStackLockedByCurrentThread();
78+
#if CHIP_CONFIG_COMMAND_SENDER_BUILTIN_SUPPORT_FOR_BATCHED_COMMANDS
79+
mpPendingResponseTracker = &mNonTestPendingResponseTracker;
80+
#endif // CHIP_CONFIG_COMMAND_SENDER_BUILTIN_SUPPORT_FOR_BATCHED_COMMANDS
7881
}
7982

8083
CommandSender::~CommandSender()
@@ -222,9 +225,9 @@ CHIP_ERROR CommandSender::OnMessageReceived(Messaging::ExchangeContext * apExcha
222225

223226
if (aPayloadHeader.HasMessageType(MsgType::InvokeCommandResponse))
224227
{
228+
mInvokeResponseMessageCount++;
225229
err = ProcessInvokeResponse(std::move(aPayload), moreChunkedMessages);
226230
SuccessOrExit(err);
227-
mInvokeResponseMessageCount++;
228231
if (moreChunkedMessages)
229232
{
230233
StatusResponse::Send(Status::Success, apExchangeContext, /*aExpectResponse = */ true);
@@ -258,6 +261,10 @@ CHIP_ERROR CommandSender::OnMessageReceived(Messaging::ExchangeContext * apExcha
258261

259262
if (mState != State::AwaitingResponse)
260263
{
264+
if (err == CHIP_NO_ERROR)
265+
{
266+
FlushNoCommandResponse();
267+
}
261268
Close();
262269
}
263270
// Else we got a response to a Timed Request and just sent the invoke.
@@ -331,12 +338,25 @@ void CommandSender::OnResponseTimeout(Messaging::ExchangeContext * apExchangeCon
331338
Close();
332339
}
333340

341+
void CommandSender::FlushNoCommandResponse()
342+
{
343+
if (mpPendingResponseTracker && mUseExtendableCallback && mCallbackHandle.extendableCallback)
344+
{
345+
Optional<uint16_t> commandRef = mpPendingResponseTracker->PopPendingResponse();
346+
while (commandRef.HasValue())
347+
{
348+
NoResponseData noResponseData = { commandRef.Value() };
349+
mCallbackHandle.extendableCallback->OnNoResponse(this, noResponseData);
350+
commandRef = mpPendingResponseTracker->PopPendingResponse();
351+
}
352+
}
353+
}
354+
334355
void CommandSender::Close()
335356
{
336357
mSuppressResponse = false;
337358
mTimedRequest = false;
338359
MoveToState(State::AwaitingDestruction);
339-
340360
OnDoneCallback();
341361
}
342362

@@ -350,10 +370,10 @@ CHIP_ERROR CommandSender::ProcessInvokeResponseIB(InvokeResponseIB::Parser & aIn
350370
StatusIB statusIB;
351371

352372
{
353-
bool commandRefExpected = (mFinishedCommandCount > 1);
354-
bool hasDataResponse = false;
373+
bool hasDataResponse = false;
355374
TLV::TLVReader commandDataReader;
356375
Optional<uint16_t> commandRef;
376+
bool commandRefExpected = mpPendingResponseTracker && (mpPendingResponseTracker->Count() > 1);
357377

358378
CommandStatusIB::Parser commandStatus;
359379
err = aInvokeResponse.GetStatus(&commandStatus);
@@ -409,6 +429,27 @@ CHIP_ERROR CommandSender::ProcessInvokeResponseIB(InvokeResponseIB::Parser & aIn
409429
}
410430
ReturnErrorOnFailure(err);
411431

432+
if (commandRef.HasValue() && mpPendingResponseTracker != nullptr)
433+
{
434+
err = mpPendingResponseTracker->Remove(commandRef.Value());
435+
if (err != CHIP_NO_ERROR)
436+
{
437+
// This can happen for two reasons:
438+
// 1. The current InvokeResponse is a duplicate (based on its commandRef).
439+
// 2. The current InvokeResponse is for a request we never sent (based on its commandRef).
440+
// Used when logging errors related to server violating spec.
441+
[[maybe_unused]] ScopedNodeId remoteScopedNode;
442+
if (mExchangeCtx.Get()->HasSessionHandle())
443+
{
444+
remoteScopedNode = mExchangeCtx.Get()->GetSessionHandle()->GetPeer();
445+
}
446+
ChipLogError(DataManagement,
447+
"Received Unexpected Response from remote node " ChipLogFormatScopedNodeId ", commandRef=%u",
448+
ChipLogValueScopedNodeId(remoteScopedNode), commandRef.Value());
449+
return err;
450+
}
451+
}
452+
412453
// When using ExtendableCallbacks, we are adhering to a different API contract where path
413454
// specific errors are sent to the OnResponse callback. For more information on the history
414455
// of this issue please see https://github.com/project-chip/connectedhomeip/issues/30991
@@ -430,17 +471,19 @@ CHIP_ERROR CommandSender::ProcessInvokeResponseIB(InvokeResponseIB::Parser & aIn
430471

431472
CHIP_ERROR CommandSender::SetCommandSenderConfig(CommandSender::ConfigParameters & aConfigParams)
432473
{
433-
#if CHIP_CONFIG_SENDING_BATCH_COMMANDS_ENABLED
434474
VerifyOrReturnError(mState == State::Idle, CHIP_ERROR_INCORRECT_STATE);
435475
VerifyOrReturnError(aConfigParams.remoteMaxPathsPerInvoke > 0, CHIP_ERROR_INVALID_ARGUMENT);
476+
if (mpPendingResponseTracker != nullptr)
477+
{
436478

437-
mRemoteMaxPathsPerInvoke = aConfigParams.remoteMaxPathsPerInvoke;
438-
mBatchCommandsEnabled = (aConfigParams.remoteMaxPathsPerInvoke > 1);
439-
return CHIP_NO_ERROR;
440-
#else
441-
VerifyOrReturnError(aConfigParams.remoteMaxPathsPerInvoke == 1, CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
479+
mRemoteMaxPathsPerInvoke = aConfigParams.remoteMaxPathsPerInvoke;
480+
mBatchCommandsEnabled = (aConfigParams.remoteMaxPathsPerInvoke > 1);
481+
}
482+
else
483+
{
484+
VerifyOrReturnError(aConfigParams.remoteMaxPathsPerInvoke == 1, CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
485+
}
442486
return CHIP_NO_ERROR;
443-
#endif
444487
}
445488

446489
CHIP_ERROR CommandSender::PrepareCommand(const CommandPathParams & aCommandPathParams,
@@ -453,12 +496,19 @@ CHIP_ERROR CommandSender::PrepareCommand(const CommandPathParams & aCommandPathP
453496
//
454497
bool canAddAnotherCommand = (mState == State::AddedCommand && mBatchCommandsEnabled && mUseExtendableCallback);
455498
VerifyOrReturnError(mState == State::Idle || canAddAnotherCommand, CHIP_ERROR_INCORRECT_STATE);
456-
VerifyOrReturnError(mFinishedCommandCount < mRemoteMaxPathsPerInvoke, CHIP_ERROR_MAXIMUM_PATHS_PER_INVOKE_EXCEEDED);
499+
500+
if (mpPendingResponseTracker != nullptr)
501+
{
502+
size_t pendingCount = mpPendingResponseTracker->Count();
503+
VerifyOrReturnError(pendingCount < mRemoteMaxPathsPerInvoke, CHIP_ERROR_MAXIMUM_PATHS_PER_INVOKE_EXCEEDED);
504+
}
457505

458506
if (mBatchCommandsEnabled)
459507
{
508+
VerifyOrReturnError(mpPendingResponseTracker != nullptr, CHIP_ERROR_INCORRECT_STATE);
460509
VerifyOrReturnError(aPrepareCommandParams.commandRef.HasValue(), CHIP_ERROR_INVALID_ARGUMENT);
461-
VerifyOrReturnError(aPrepareCommandParams.commandRef.Value() == mFinishedCommandCount, CHIP_ERROR_INVALID_ARGUMENT);
510+
uint16_t commandRef = aPrepareCommandParams.commandRef.Value();
511+
VerifyOrReturnError(!mpPendingResponseTracker->IsTracked(commandRef), CHIP_ERROR_INVALID_ARGUMENT);
462512
}
463513

464514
InvokeRequests::Builder & invokeRequests = mInvokeRequestBuilder.GetInvokeRequests();
@@ -482,8 +532,10 @@ CHIP_ERROR CommandSender::FinishCommand(FinishCommandParameters & aFinishCommand
482532
{
483533
if (mBatchCommandsEnabled)
484534
{
535+
VerifyOrReturnError(mpPendingResponseTracker != nullptr, CHIP_ERROR_INCORRECT_STATE);
485536
VerifyOrReturnError(aFinishCommandParams.commandRef.HasValue(), CHIP_ERROR_INVALID_ARGUMENT);
486-
VerifyOrReturnError(aFinishCommandParams.commandRef.Value() == mFinishedCommandCount, CHIP_ERROR_INVALID_ARGUMENT);
537+
uint16_t commandRef = aFinishCommandParams.commandRef.Value();
538+
VerifyOrReturnError(!mpPendingResponseTracker->IsTracked(commandRef), CHIP_ERROR_INVALID_ARGUMENT);
487539
}
488540

489541
return FinishCommandInternal(aFinishCommandParams);
@@ -511,7 +563,10 @@ CHIP_ERROR CommandSender::FinishCommandInternal(FinishCommandParameters & aFinis
511563

512564
MoveToState(State::AddedCommand);
513565

514-
mFinishedCommandCount++;
566+
if (mpPendingResponseTracker && aFinishCommandParams.commandRef.HasValue())
567+
{
568+
mpPendingResponseTracker->Add(aFinishCommandParams.commandRef.Value());
569+
}
515570

516571
if (aFinishCommandParams.timedInvokeTimeoutMs.HasValue())
517572
{

src/app/CommandSender.h

+50-13
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include <app/MessageDef/InvokeRequestMessage.h>
3333
#include <app/MessageDef/InvokeResponseMessage.h>
3434
#include <app/MessageDef/StatusIB.h>
35+
#include <app/PendingResponseTrackerImpl.h>
3536
#include <app/data-model/Encode.h>
3637
#include <lib/core/CHIPCore.h>
3738
#include <lib/core/Optional.h>
@@ -75,6 +76,16 @@ class CommandSender final : public Messaging::ExchangeDelegate
7576
Optional<uint16_t> commandRef;
7677
};
7778

79+
// CommandSender::ExtendableCallback::OnNoResponse is public SDK API, so we cannot break
80+
// source compatibility for it. To allow for additional values to be added at a future
81+
// time without constantly changing the function's declaration parameter list, we are
82+
// defining the struct NoResponseData and adding that to the parameter list to allow for
83+
// future extendability.
84+
struct NoResponseData
85+
{
86+
uint16_t commandRef;
87+
};
88+
7889
// CommandSender::ExtendableCallback::OnError is public SDK API, so we cannot break source
7990
// compatibility for it. To allow for additional values to be added at a future time
8091
// without constantly changing the function's declaration parameter list, we are
@@ -127,9 +138,21 @@ class CommandSender final : public Messaging::ExchangeDelegate
127138
* @param[in] apCommandSender The command sender object that initiated the command transaction.
128139
* @param[in] aResponseData Information pertaining to the response.
129140
*/
130-
;
131141
virtual void OnResponse(CommandSender * commandSender, const ResponseData & aResponseData) {}
132142

143+
/**
144+
* Called for each request that failed to receive a response after the server indicates completion of all requests.
145+
*
146+
* This callback may be omitted if clients have alternative ways to track non-responses.
147+
*
148+
* The CommandSender object MUST continue to exist after this call is completed. The application shall wait until it
149+
* receives an OnDone call to destroy the object.
150+
*
151+
* @param apCommandSender The CommandSender object that initiated the transaction.
152+
* @param aNoResponseData Details about the request without a response.
153+
*/
154+
virtual void OnNoResponse(CommandSender * commandSender, const NoResponseData & aNoResponseData) {}
155+
133156
/**
134157
* OnError will be called when a non-path-specific error occurs *after* a successful call to SendCommandRequest().
135158
*
@@ -229,12 +252,6 @@ class CommandSender final : public Messaging::ExchangeDelegate
229252
// early in PrepareCommand, even though it's not used until FinishCommand. This proactive
230253
// validation helps prevent unnecessary writing an InvokeRequest into the packet that later
231254
// needs to be undone.
232-
//
233-
// Currently, provided commandRefs for the first request must start at 0 and increment by one
234-
// for each subsequent request. This requirement can be relaxed in the future if a compelling
235-
// need arises.
236-
// TODO(#30453): After introducing Request/Response tracking, remove statement above about
237-
// this currently enforced requirement on commandRefs.
238255
Optional<uint16_t> commandRef;
239256
// If the InvokeRequest needs to be in a state with a started data TLV struct container
240257
bool startDataStruct = false;
@@ -278,6 +295,10 @@ class CommandSender final : public Messaging::ExchangeDelegate
278295
bool endDataStruct = false;
279296
};
280297

298+
class TestOnlyMarker
299+
{
300+
};
301+
281302
/*
282303
* Constructor.
283304
*
@@ -287,12 +308,20 @@ class CommandSender final : public Messaging::ExchangeDelegate
287308
*/
288309
CommandSender(Callback * apCallback, Messaging::ExchangeManager * apExchangeMgr, bool aIsTimedRequest = false,
289310
bool aSuppressResponse = false);
290-
CommandSender(ExtendableCallback * apCallback, Messaging::ExchangeManager * apExchangeMgr, bool aIsTimedRequest = false,
291-
bool aSuppressResponse = false);
292311
CommandSender(std::nullptr_t, Messaging::ExchangeManager * apExchangeMgr, bool aIsTimedRequest = false,
293312
bool aSuppressResponse = false) :
294313
CommandSender(static_cast<Callback *>(nullptr), apExchangeMgr, aIsTimedRequest, aSuppressResponse)
295314
{}
315+
CommandSender(ExtendableCallback * apCallback, Messaging::ExchangeManager * apExchangeMgr, bool aIsTimedRequest = false,
316+
bool aSuppressResponse = false);
317+
// TODO(#32138): After there is a macro that is always defined for all unit tests, the constructor with
318+
// TestOnlyMarker should only be compiled if that macro is defined.
319+
CommandSender(TestOnlyMarker aTestMarker, ExtendableCallback * apCallback, Messaging::ExchangeManager * apExchangeMgr,
320+
PendingResponseTracker * apPendingResponseTracker, bool aIsTimedRequest = false, bool aSuppressResponse = false) :
321+
CommandSender(apCallback, apExchangeMgr, aIsTimedRequest, aSuppressResponse)
322+
{
323+
mpPendingResponseTracker = apPendingResponseTracker;
324+
}
296325
~CommandSender();
297326

298327
/**
@@ -307,11 +336,14 @@ class CommandSender final : public Messaging::ExchangeDelegate
307336
* based on how many paths the remote peer claims to support.
308337
*
309338
* @return CHIP_ERROR_INCORRECT_STATE
310-
* If device has previously called `PrepareCommand`.
339+
* If device has previously called `PrepareCommand`.
311340
* @return CHIP_ERROR_INVALID_ARGUMENT
312-
* Invalid argument value.
341+
* Invalid argument value.
313342
* @return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE
314-
* Device has not enabled CHIP_CONFIG_SENDING_BATCH_COMMANDS_ENABLED.
343+
* Device has not enabled batch command support. To enable:
344+
* 1. Enable the CHIP_CONFIG_COMMAND_SENDER_BUILTIN_SUPPORT_FOR_BATCHED_COMMANDS
345+
* configuration option.
346+
* 2. Ensure you provide ExtendableCallback.
315347
*/
316348
CHIP_ERROR SetCommandSenderConfig(ConfigParameters & aConfigParams);
317349

@@ -497,6 +529,7 @@ class CommandSender final : public Messaging::ExchangeDelegate
497529
System::PacketBufferHandle && aPayload) override;
498530
void OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext) override;
499531

532+
void FlushNoCommandResponse();
500533
//
501534
// Called internally to signal the completion of all work on this object, gracefully close the
502535
// exchange (by calling into the base class) and finally, signal to the application that it's
@@ -580,8 +613,12 @@ class CommandSender final : public Messaging::ExchangeDelegate
580613

581614
chip::System::PacketBufferTLVWriter mCommandMessageWriter;
582615

616+
#if CHIP_CONFIG_COMMAND_SENDER_BUILTIN_SUPPORT_FOR_BATCHED_COMMANDS
617+
PendingResponseTrackerImpl mNonTestPendingResponseTracker;
618+
#endif // CHIP_CONFIG_COMMAND_SENDER_BUILTIN_SUPPORT_FOR_BATCHED_COMMANDS
619+
PendingResponseTracker * mpPendingResponseTracker = nullptr;
620+
583621
uint16_t mInvokeResponseMessageCount = 0;
584-
uint16_t mFinishedCommandCount = 0;
585622
uint16_t mRemoteMaxPathsPerInvoke = 1;
586623

587624
State mState = State::Idle;

0 commit comments

Comments
 (0)