Skip to content

Commit 5fbe16d

Browse files
tehampsonrestyled-commits
andauthoredApr 3, 2024
Refactor CommandHandler to be more unittestable (project-chip#32467)
* Refactor CommandHandler * Restyle and quick fix * Self review * Add tests * Restyled by whitespace * Restyled by clang-format * Resolve conflict * Restyled by whitespace * Fix CI * Rename CommandResponder to CommandResponseSender for easier reviewing * Restyled by clang-format * Address PR comments * Restyled by whitespace * Restyled by clang-format * Address PR comment * Restyled by clang-format * Address PR comments * Restyled by clang-format * nit fix * Address PR comments --------- Co-authored-by: Restyled.io <commits@restyled.io>
1 parent c172e30 commit 5fbe16d

10 files changed

+707
-463
lines changed
 

‎.github/workflows/lint.yml

+1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ jobs:
9494
--known-failure app/AttributeAccessToken.h \
9595
--known-failure app/CommandHandler.h \
9696
--known-failure app/CommandHandlerInterface.h \
97+
--known-failure app/CommandResponseSender.h \
9798
--known-failure app/CommandSenderLegacyCallback.h \
9899
--known-failure app/ReadHandler.h \
99100
--known-failure app/reporting/reporting.cpp \

‎src/app/BUILD.gn

+2-1
Original file line numberDiff line numberDiff line change
@@ -281,9 +281,9 @@ static_library("app") {
281281
"ChunkedWriteCallback.cpp",
282282
"ChunkedWriteCallback.h",
283283
"CommandHandler.cpp",
284+
"CommandHandlerExchangeInterface.h",
284285
"CommandResponseHelper.h",
285286
"CommandResponseSender.cpp",
286-
"CommandResponseSender.h",
287287
"DefaultAttributePersistenceProvider.cpp",
288288
"DefaultAttributePersistenceProvider.h",
289289
"DeferredAttributePersistenceProvider.cpp",
@@ -304,6 +304,7 @@ static_library("app") {
304304
# TODO: the following items cannot be included due to interaction-model circularity
305305
# (app depending on im and im including these headers):
306306
# Name with _ so that linter does not recognize it
307+
# "CommandResponseSender._h"
307308
# "CommandHandler._h"
308309
# "ReadHandler._h",
309310
# "WriteHandler._h"

‎src/app/CommandHandler.cpp

+63-101
Large diffs are not rendered by default.

‎src/app/CommandHandler.h

+102-82
Large diffs are not rendered by default.
+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright (c) 2024 Project CHIP Authors
3+
* All rights reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
#pragma once
19+
20+
#include <access/SubjectDescriptor.h>
21+
#include <lib/core/DataModelTypes.h>
22+
#include <lib/core/GroupId.h>
23+
#include <lib/core/Optional.h>
24+
#include <messaging/ExchangeContext.h>
25+
#include <protocols/interaction_model/StatusCode.h>
26+
#include <system/SystemPacketBuffer.h>
27+
28+
namespace chip {
29+
namespace app {
30+
31+
/**
32+
* Interface for sending InvokeResponseMessage(s).
33+
*
34+
* Provides information about the associated exchange context.
35+
*
36+
* Design Rationale: This interface enhances unit testability and allows applications to
37+
* customize InvokeResponse behavior. For example, a bridge application might locally execute
38+
* a command using cluster APIs without intending to sending a response on an exchange.
39+
* These cluster APIs require providing an instance of CommandHandler where a status response
40+
* is added (see https://github.com/project-chip/connectedhomeip/issues/32030).
41+
*/
42+
class CommandHandlerExchangeInterface
43+
{
44+
public:
45+
virtual ~CommandHandlerExchangeInterface() = default;
46+
47+
/**
48+
* Get a non-owning pointer to the exchange context the InvokeRequestMessage was
49+
* delivered on.
50+
*
51+
* @return The exchange context. Might be nullptr if no exchange context has been
52+
* assigned or the context has been released. For example, the exchange
53+
* context might not be assigned in unit tests, or if an application wishes
54+
* to locally execute cluster APIs and still receive response data without
55+
* sending it on an exchange.
56+
*/
57+
virtual Messaging::ExchangeContext * GetExchangeContext() const = 0;
58+
59+
// TODO(#30453): Follow up refactor. It should be possible to remove
60+
// GetSubjectDescriptor and GetAccessingFabricIndex, as CommandHandler can get these
61+
// values from ExchangeContext.
62+
63+
/**
64+
* Gets subject descriptor of the exchange.
65+
*
66+
* WARNING: This method should only be called when the caller is certain the
67+
* session has not been evicted.
68+
*/
69+
virtual Access::SubjectDescriptor GetSubjectDescriptor() const = 0;
70+
71+
/**
72+
* Gets accessing fabic index of the exchange.
73+
*
74+
* WARNING: This method should only be called when the caller is certain the
75+
* session has not been evicted.
76+
*/
77+
virtual FabricIndex GetAccessingFabricIndex() const = 0;
78+
/**
79+
* If session for the exchange is a group session, returns its group ID. Otherwise,
80+
* returns a null optional.
81+
*/
82+
virtual Optional<GroupId> GetGroupId() const = 0;
83+
84+
/**
85+
* @brief Called to indicate a slow command is being processed.
86+
*
87+
* Enables the exchange to send whatever transport-level acks might be needed without waiting
88+
* for command processing to complete.
89+
*/
90+
virtual void HandlingSlowCommand() = 0;
91+
92+
/**
93+
* @brief Adds a completed InvokeResponseMessage to the queue for sending to requester.
94+
*
95+
* Called by CommandHandler.
96+
*/
97+
virtual void AddInvokeResponseToSend(System::PacketBufferHandle && aPacket) = 0;
98+
99+
/**
100+
* @brief Called to indicate that an InvokeResponse was dropped.
101+
*
102+
* Called by CommandHandler to relay this information to the requester.
103+
*/
104+
virtual void ResponseDropped() = 0;
105+
};
106+
107+
} // namespace app
108+
} // namespace chip

‎src/app/CommandResponseSender.cpp

+95-15
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,10 @@ CHIP_ERROR CommandResponseSender::OnMessageReceived(Messaging::ExchangeContext *
3838
err = statusError;
3939
VerifyOrExit(err == CHIP_NO_ERROR, failureStatusToSend.SetValue(Status::InvalidAction));
4040

41-
// If SendCommandResponse() fails, we are responsible for closing the exchange,
42-
// as stipulated by the API contract. We fulfill this obligation by not sending
43-
// a message expecting a response on the exchange. Since we are in the middle
44-
// of processing an incoming message, the exchange will close itself once we are
45-
// done processing it, if there is no response to wait for at that point.
4641
err = SendCommandResponse();
42+
// If SendCommandResponse() fails, we must close the exchange. We signal the failure to the
43+
// requester with a StatusResponse ('Failure'). Since we're in the middle of processing an
44+
// incoming message, we close the exchange by indicating that we don't expect a further response.
4745
VerifyOrExit(err == CHIP_NO_ERROR, failureStatusToSend.SetValue(Status::Failure));
4846

4947
bool moreToSend = !mChunks.IsNull();
@@ -81,13 +79,28 @@ void CommandResponseSender::OnResponseTimeout(Messaging::ExchangeContext * apExc
8179
Close();
8280
}
8381

84-
CHIP_ERROR CommandResponseSender::StartSendingCommandResponses()
82+
void CommandResponseSender::StartSendingCommandResponses()
8583
{
86-
VerifyOrReturnError(mState == State::ReadyForInvokeResponses, CHIP_ERROR_INCORRECT_STATE);
87-
// If SendCommandResponse() fails, we are obligated to close the exchange as per the API
88-
// contract. However, this method's contract also stipulates that in the event of our
89-
// failure, the caller bears the responsibility of closing the exchange.
90-
ReturnErrorOnFailure(SendCommandResponse());
84+
VerifyOrDie(mState == State::ReadyForInvokeResponses);
85+
CHIP_ERROR err = SendCommandResponse();
86+
if (err != CHIP_NO_ERROR)
87+
{
88+
ChipLogError(DataManagement, "Failed to send InvokeResponseMessage");
89+
// TODO(#30453): It should be our responsibility to send a Failure StatusResponse to the requestor
90+
// if there is a SessionHandle, but legacy unit tests explicitly check the behavior where
91+
// we do not send any message. Changing this behavior should be done in a standalone
92+
// PR where only that specific change is made. Here is a possible solution that could
93+
// be used that fulfills our responsibility to send a Failure StatusResponse. This causes unit
94+
// tests to start failing.
95+
// ```
96+
// if (mExchangeCtx && mExchangeCtx->HasSessionHandle())
97+
// {
98+
// SendStatusResponse(Status::Failure);
99+
// }
100+
// ```
101+
Close();
102+
return;
103+
}
91104

92105
if (HasMoreToSend())
93106
{
@@ -98,7 +111,31 @@ CHIP_ERROR CommandResponseSender::StartSendingCommandResponses()
98111
{
99112
Close();
100113
}
101-
return CHIP_NO_ERROR;
114+
}
115+
116+
void CommandResponseSender::OnDone(CommandHandler & apCommandObj)
117+
{
118+
if (mState == State::ErrorSentDelayCloseUntilOnDone)
119+
{
120+
// We have already sent a message to the client indicating that we are not expecting
121+
// a response.
122+
Close();
123+
return;
124+
}
125+
StartSendingCommandResponses();
126+
}
127+
128+
void CommandResponseSender::DispatchCommand(CommandHandler & apCommandObj, const ConcreteCommandPath & aCommandPath,
129+
TLV::TLVReader & apPayload)
130+
{
131+
VerifyOrReturn(mpCommandHandlerCallback);
132+
mpCommandHandlerCallback->DispatchCommand(apCommandObj, aCommandPath, apPayload);
133+
}
134+
135+
Status CommandResponseSender::CommandExists(const ConcreteCommandPath & aCommandPath)
136+
{
137+
VerifyOrReturnValue(mpCommandHandlerCallback, Protocols::InteractionModel::Status::UnsupportedCommand);
138+
return mpCommandHandlerCallback->CommandExists(aCommandPath);
102139
}
103140

104141
CHIP_ERROR CommandResponseSender::SendCommandResponse()
@@ -139,6 +176,9 @@ const char * CommandResponseSender::GetStateStr() const
139176

140177
case State::AllInvokeResponsesSent:
141178
return "AllInvokeResponsesSent";
179+
180+
case State::ErrorSentDelayCloseUntilOnDone:
181+
return "ErrorSentDelayCloseUntilOnDone";
142182
}
143183
#endif // CHIP_DETAIL_LOGGING
144184
return "N/A";
@@ -157,12 +197,52 @@ void CommandResponseSender::MoveToState(const State aTargetState)
157197
void CommandResponseSender::Close()
158198
{
159199
MoveToState(State::AllInvokeResponsesSent);
160-
mCloseCalled = true;
161-
if (mResponseSenderDoneCallback)
200+
mpCallback->OnDone(*this);
201+
}
202+
203+
void CommandResponseSender::OnInvokeCommandRequest(Messaging::ExchangeContext * ec, System::PacketBufferHandle && payload,
204+
bool isTimedInvoke)
205+
{
206+
VerifyOrDieWithMsg(ec != nullptr, DataManagement, "Incoming exchange context should not be null");
207+
VerifyOrDieWithMsg(mState == State::ReadyForInvokeResponses, DataManagement, "state should be ReadyForInvokeResponses");
208+
209+
// NOTE: we already know this is an InvokeRequestMessage because we explicitly registered with the
210+
// Exchange Manager for unsolicited InvokeRequestMessages.
211+
mExchangeCtx.Grab(ec);
212+
mExchangeCtx->WillSendMessage();
213+
214+
// Grabbing Handle to prevent mCommandHandler from calling OnDone before OnInvokeCommandRequest returns.
215+
// This allows us to send a StatusResponse error instead of any potentially queued up InvokeResponseMessages.
216+
CommandHandler::Handle workHandle(&mCommandHandler);
217+
Status status = mCommandHandler.OnInvokeCommandRequest(*this, std::move(payload), isTimedInvoke);
218+
if (status != Status::Success)
162219
{
163-
mResponseSenderDoneCallback->mCall(mResponseSenderDoneCallback->mContext);
220+
VerifyOrDie(mState == State::ReadyForInvokeResponses);
221+
SendStatusResponse(status);
222+
// The API contract of OnInvokeCommandRequest requires the CommandResponder instance to outlive
223+
// the CommandHandler. Therefore, we cannot safely call Close() here, even though we have
224+
// finished sending data. Closing must be deferred until the CommandHandler::OnDone callback.
225+
MoveToState(State::ErrorSentDelayCloseUntilOnDone);
164226
}
165227
}
166228

229+
#if CHIP_WITH_NLFAULTINJECTION
230+
231+
void CommandResponseSender::TestOnlyInvokeCommandRequestWithFaultsInjected(Messaging::ExchangeContext * ec,
232+
System::PacketBufferHandle && payload,
233+
bool isTimedInvoke,
234+
CommandHandler::NlFaultInjectionType faultType)
235+
{
236+
VerifyOrDieWithMsg(ec != nullptr, DataManagement, "TH Failure: Incoming exchange context should not be null");
237+
VerifyOrDieWithMsg(mState == State::ReadyForInvokeResponses, DataManagement,
238+
"TH Failure: state should be ReadyForInvokeResponses, issue with TH");
239+
240+
mExchangeCtx.Grab(ec);
241+
mExchangeCtx->WillSendMessage();
242+
243+
mCommandHandler.TestOnlyInvokeCommandRequestWithFaultsInjected(*this, std::move(payload), isTimedInvoke, faultType);
244+
}
245+
#endif // CHIP_WITH_NLFAULTINJECTION
246+
167247
} // namespace app
168248
} // namespace chip

‎src/app/CommandResponseSender.h

+90-63
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,55 @@
1717

1818
#pragma once
1919

20+
#include <app/CommandHandler.h>
21+
#include <app/CommandHandlerExchangeInterface.h>
2022
#include <app/StatusResponse.h>
2123
#include <messaging/ExchangeHolder.h>
2224
#include <system/SystemPacketBuffer.h>
2325

2426
namespace chip {
2527
namespace app {
2628

27-
typedef void (*OnResponseSenderDone)(void * context);
28-
class CommandHandler;
29-
29+
// TODO(#30453): Rename CommandResponseSender to CommandResponder in follow up PR
3030
/**
31-
* Class manages the process of sending `InvokeResponseMessage`(s) back to the initial requester.
31+
* Manages the process of sending InvokeResponseMessage(s) to the requester.
32+
*
33+
* Implements the CommandHandlerExchangeInterface. Uses a CommandHandler member to process
34+
* InvokeCommandRequest. The CommandHandler is provided a reference to this
35+
* CommandHandlerExchangeInterface implementation to enable sending InvokeResponseMessage(s).
3236
*/
33-
class CommandResponseSender : public Messaging::ExchangeDelegate
37+
class CommandResponseSender : public Messaging::ExchangeDelegate,
38+
public CommandHandler::Callback,
39+
public CommandHandlerExchangeInterface
3440
{
3541
public:
36-
CommandResponseSender() : mExchangeCtx(*this) {}
42+
class Callback
43+
{
44+
public:
45+
virtual ~Callback() = default;
46+
/*
47+
* Signals registered callback that this object has finished its work and can now be
48+
* safely destroyed/released.
49+
*/
50+
virtual void OnDone(CommandResponseSender & apResponderObj) = 0;
51+
};
52+
53+
CommandResponseSender(Callback * apCallback, CommandHandler::Callback * apDispatchCallback) :
54+
mpCallback(apCallback), mpCommandHandlerCallback(apDispatchCallback), mCommandHandler(this), mExchangeCtx(*this)
55+
{}
3756

3857
CHIP_ERROR OnMessageReceived(Messaging::ExchangeContext * ec, const PayloadHeader & payloadHeader,
3958
System::PacketBufferHandle && payload) override;
4059

4160
void OnResponseTimeout(Messaging::ExchangeContext * ec) override;
4261

62+
void OnDone(CommandHandler & apCommandObj) override;
63+
64+
void DispatchCommand(CommandHandler & apCommandObj, const ConcreteCommandPath & aCommandPath,
65+
TLV::TLVReader & apPayload) override;
66+
67+
Protocols::InteractionModel::Status CommandExists(const ConcreteCommandPath & aCommandPath) override;
68+
4369
/**
4470
* Gets the inner exchange context object, without ownership.
4571
*
@@ -51,115 +77,116 @@ class CommandResponseSender : public Messaging::ExchangeDelegate
5177
* exchange context has been assigned or the context
5278
* has been released.
5379
*/
54-
Messaging::ExchangeContext * GetExchangeContext() const { return mExchangeCtx.Get(); }
55-
56-
/**
57-
* @brief Flush acks right now, typically done when processing a slow command
58-
*/
59-
void FlushAcksRightNow()
60-
{
61-
VerifyOrReturn(mExchangeCtx);
62-
auto * msgContext = mExchangeCtx->GetReliableMessageContext();
63-
VerifyOrReturn(msgContext != nullptr);
64-
msgContext->FlushAcks();
65-
}
80+
Messaging::ExchangeContext * GetExchangeContext() const override { return mExchangeCtx.Get(); }
6681

6782
/**
6883
* Gets subject descriptor of the exchange.
6984
*
7085
* WARNING: This method should only be called when the caller is certain the
7186
* session has not been evicted.
7287
*/
73-
Access::SubjectDescriptor GetSubjectDescriptor() const
88+
Access::SubjectDescriptor GetSubjectDescriptor() const override
7489
{
7590
VerifyOrDie(mExchangeCtx);
7691
return mExchangeCtx->GetSessionHandle()->GetSubjectDescriptor();
7792
}
7893

79-
bool HasSessionHandle() { return mExchangeCtx && mExchangeCtx->HasSessionHandle(); }
80-
81-
FabricIndex GetAccessingFabricIndex() const
94+
FabricIndex GetAccessingFabricIndex() const override
8295
{
8396
VerifyOrDie(mExchangeCtx);
8497
return mExchangeCtx->GetSessionHandle()->GetFabricIndex();
8598
}
8699

87-
void SetExchangeContext(Messaging::ExchangeContext * ec) { mExchangeCtx.Grab(ec); }
88-
89-
void WillSendMessage() { mExchangeCtx->WillSendMessage(); }
90-
91-
bool IsForGroup()
100+
Optional<GroupId> GetGroupId() const override
92101
{
93102
VerifyOrDie(mExchangeCtx);
94-
return mExchangeCtx->IsGroupExchangeContext();
103+
auto sessionHandle = mExchangeCtx->GetSessionHandle();
104+
if (sessionHandle->GetSessionType() != Transport::Session::SessionType::kGroupIncoming)
105+
{
106+
return NullOptional;
107+
}
108+
return MakeOptional(sessionHandle->AsIncomingGroupSession()->GetGroupId());
95109
}
96110

97-
bool HasExchangeContext() { return mExchangeCtx.Get() != nullptr; }
98-
99-
GroupId GetGroupId()
100-
{
101-
VerifyOrDie(mExchangeCtx);
102-
return mExchangeCtx->GetSessionHandle()->AsIncomingGroupSession()->GetGroupId();
103-
}
104-
105-
/**
106-
* @brief Initiates the sending of InvokeResponses previously queued using AddInvokeResponseToSend.
107-
*
108-
* Upon failure, the caller is responsible for closing the exchange appropriately, potentially
109-
* by calling `SendStatusResponse`.
110-
*/
111-
CHIP_ERROR StartSendingCommandResponses();
112-
113-
void SendStatusResponse(Protocols::InteractionModel::Status aStatus)
111+
void HandlingSlowCommand() override
114112
{
115-
StatusResponse::Send(aStatus, mExchangeCtx.Get(), /*aExpectResponse = */ false);
113+
VerifyOrReturn(mExchangeCtx);
114+
auto * msgContext = mExchangeCtx->GetReliableMessageContext();
115+
VerifyOrReturn(msgContext != nullptr);
116+
msgContext->FlushAcks();
116117
}
117118

118-
bool AwaitingStatusResponse() { return mState == State::AwaitingStatusResponse; }
119-
120-
void AddInvokeResponseToSend(System::PacketBufferHandle && aPacket)
119+
void AddInvokeResponseToSend(System::PacketBufferHandle && aPacket) override
121120
{
122121
VerifyOrDie(mState == State::ReadyForInvokeResponses);
123122
mChunks.AddToEnd(std::move(aPacket));
124123
}
125124

126-
/**
127-
* @brief Called to indicate that response was dropped
125+
void ResponseDropped() override { mReportResponseDropped = true; }
126+
127+
/*
128+
* Main entrypoint for this class to handle an invoke request.
129+
*
130+
* isTimedInvoke is true if and only if this is part of a Timed Invoke
131+
* transaction (i.e. was preceded by a Timed Request). If we reach here,
132+
* the timer verification has already been done.
128133
*/
129-
void ResponseDropped() { mReportResponseDropped = true; }
134+
void OnInvokeCommandRequest(Messaging::ExchangeContext * ec, System::PacketBufferHandle && payload, bool isTimedInvoke);
130135

136+
#if CHIP_WITH_NLFAULTINJECTION
131137
/**
132-
* @brief Registers a callback to be invoked when CommandResponseSender has finished sending responses.
138+
* @brief Sends InvokeResponseMessages with injected faults for certification testing.
139+
*
140+
* The Test Harness (TH) uses this to simulate various server response behaviors,
141+
* ensuring the Device Under Test (DUT) handles responses per specification.
142+
*
143+
* This function strictly validates the DUT's InvokeRequestMessage against the test plan.
144+
* If deviations occur, the TH terminates with a detailed error message.
145+
*
146+
* @param ec Exchange context for sending InvokeResponseMessages to the client.
147+
* @param payload Payload of the incoming InvokeRequestMessage from the client.
148+
* @param isTimedInvoke Indicates whether the interaction is timed.
149+
* @param faultType The specific type of fault to inject into the response.
133150
*/
134-
void RegisterOnResponseSenderDoneCallback(Callback::Callback<OnResponseSenderDone> * aResponseSenderDoneCallback)
135-
{
136-
VerifyOrDie(!mCloseCalled);
137-
mResponseSenderDoneCallback = aResponseSenderDoneCallback;
138-
}
151+
void TestOnlyInvokeCommandRequestWithFaultsInjected(Messaging::ExchangeContext * ec, System::PacketBufferHandle && payload,
152+
bool isTimedInvoke, CommandHandler::NlFaultInjectionType faultType);
153+
#endif // CHIP_WITH_NLFAULTINJECTION
139154

140155
private:
141156
enum class State : uint8_t
142157
{
143-
ReadyForInvokeResponses, ///< Accepting InvokeResponses to send back to requester.
144-
AwaitingStatusResponse, ///< Awaiting status response from requester, after sending InvokeResponse.
145-
AllInvokeResponsesSent, ///< All InvokeResponses have been sent out.
158+
ReadyForInvokeResponses, ///< Accepting InvokeResponses to send back to requester.
159+
AwaitingStatusResponse, ///< Awaiting status response from requester, after sending InvokeResponse.
160+
AllInvokeResponsesSent, ///< All InvokeResponses have been sent out.
161+
ErrorSentDelayCloseUntilOnDone ///< We have sent an early error response, but still need to clean up.
146162
};
147163

148164
void MoveToState(const State aTargetState);
149165
const char * GetStateStr() const;
150166

167+
/**
168+
* @brief Initiates the sending of InvokeResponses previously queued using AddInvokeResponseToSend.
169+
*/
170+
void StartSendingCommandResponses();
171+
172+
void SendStatusResponse(Protocols::InteractionModel::Status aStatus)
173+
{
174+
StatusResponse::Send(aStatus, mExchangeCtx.Get(), /*aExpectResponse = */ false);
175+
}
176+
151177
CHIP_ERROR SendCommandResponse();
152178
bool HasMoreToSend() { return !mChunks.IsNull() || mReportResponseDropped; }
153179
void Close();
154180

155181
// A list of InvokeResponseMessages to be sent out by CommandResponseSender.
156182
System::PacketBufferHandle mChunks;
157183

158-
chip::Callback::Callback<OnResponseSenderDone> * mResponseSenderDoneCallback = nullptr;
184+
Callback * mpCallback;
185+
CommandHandler::Callback * mpCommandHandlerCallback;
186+
CommandHandler mCommandHandler;
159187
Messaging::ExchangeHolder mExchangeCtx;
160188
State mState = State::ReadyForInvokeResponses;
161189

162-
bool mCloseCalled = false;
163190
bool mReportResponseDropped = false;
164191
};
165192

‎src/app/InteractionModelEngine.cpp

+18-9
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ void InteractionModelEngine::Shutdown()
113113
// Increase magic number to invalidate all Handle-s.
114114
mMagic++;
115115

116-
mCommandHandlerObjs.ReleaseAll();
116+
mCommandResponderObjs.ReleaseAll();
117117

118118
mTimedHandlers.ForEachActiveObject([this](TimedHandler * obj) -> Loop {
119119
mpExchangeMgr->CloseAllContextsForDelegate(obj);
@@ -375,9 +375,17 @@ bool InteractionModelEngine::SubjectHasPersistedSubscription(FabricIndex aFabric
375375
return false;
376376
}
377377

378+
void InteractionModelEngine::OnDone(CommandResponseSender & apResponderObj)
379+
{
380+
mCommandResponderObjs.ReleaseObject(&apResponderObj);
381+
}
382+
383+
// TODO(#30453): Follow up refactor. Remove need for InteractionModelEngine::OnDone(CommandHandler).
378384
void InteractionModelEngine::OnDone(CommandHandler & apCommandObj)
379385
{
380-
mCommandHandlerObjs.ReleaseObject(&apCommandObj);
386+
// We are no longer expecting to receive this callback. With the introduction of CommandResponseSender, it is now
387+
// responsible for receiving this callback.
388+
VerifyOrDie(false);
381389
}
382390

383391
void InteractionModelEngine::OnDone(ReadHandler & apReadObj)
@@ -413,28 +421,29 @@ Status InteractionModelEngine::OnInvokeCommandRequest(Messaging::ExchangeContext
413421
const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload,
414422
bool aIsTimedInvoke)
415423
{
416-
CommandHandler * commandHandler = mCommandHandlerObjs.CreateObject(this);
417-
if (commandHandler == nullptr)
424+
// TODO(#30453): Refactor CommandResponseSender's constructor to accept an exchange context parameter.
425+
CommandResponseSender * commandResponder = mCommandResponderObjs.CreateObject(this, this);
426+
if (commandResponder == nullptr)
418427
{
419428
ChipLogProgress(InteractionModel, "no resource for Invoke interaction");
420429
return Status::Busy;
421430
}
422431
CHIP_FAULT_INJECT(
423432
FaultInjection::kFault_IMInvoke_SeparateResponses,
424-
commandHandler->TestOnlyInvokeCommandRequestWithFaultsInjected(
433+
commandResponder->TestOnlyInvokeCommandRequestWithFaultsInjected(
425434
apExchangeContext, std::move(aPayload), aIsTimedInvoke, CommandHandler::NlFaultInjectionType::SeparateResponseMessages);
426435
return Status::Success;);
427436
CHIP_FAULT_INJECT(FaultInjection::kFault_IMInvoke_SeparateResponsesInvertResponseOrder,
428-
commandHandler->TestOnlyInvokeCommandRequestWithFaultsInjected(
437+
commandResponder->TestOnlyInvokeCommandRequestWithFaultsInjected(
429438
apExchangeContext, std::move(aPayload), aIsTimedInvoke,
430439
CommandHandler::NlFaultInjectionType::SeparateResponseMessagesAndInvertedResponseOrder);
431440
return Status::Success;);
432441
CHIP_FAULT_INJECT(
433442
FaultInjection::kFault_IMInvoke_SkipSecondResponse,
434-
commandHandler->TestOnlyInvokeCommandRequestWithFaultsInjected(apExchangeContext, std::move(aPayload), aIsTimedInvoke,
435-
CommandHandler::NlFaultInjectionType::SkipSecondResponse);
443+
commandResponder->TestOnlyInvokeCommandRequestWithFaultsInjected(apExchangeContext, std::move(aPayload), aIsTimedInvoke,
444+
CommandHandler::NlFaultInjectionType::SkipSecondResponse);
436445
return Status::Success;);
437-
commandHandler->OnInvokeCommandRequest(apExchangeContext, aPayloadHeader, std::move(aPayload), aIsTimedInvoke);
446+
commandResponder->OnInvokeCommandRequest(apExchangeContext, std::move(aPayload), aIsTimedInvoke);
438447
return Status::Success;
439448
}
440449

‎src/app/InteractionModelEngine.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <app/AttributePathParams.h>
3131
#include <app/CommandHandler.h>
3232
#include <app/CommandHandlerInterface.h>
33+
#include <app/CommandResponseSender.h>
3334
#include <app/CommandSender.h>
3435
#include <app/ConcreteAttributePath.h>
3536
#include <app/ConcreteCommandPath.h>
@@ -77,6 +78,7 @@ namespace app {
7778
*/
7879
class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler,
7980
public Messaging::ExchangeDelegate,
81+
public CommandResponseSender::Callback,
8082
public CommandHandler::Callback,
8183
public ReadHandler::ManagementCallback,
8284
public FabricTable::Delegate,
@@ -402,6 +404,7 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler,
402404
friend class SubscriptionResumptionSessionEstablisher;
403405
using Status = Protocols::InteractionModel::Status;
404406

407+
void OnDone(CommandResponseSender & apResponderObj) override;
405408
void OnDone(CommandHandler & apCommandObj) override;
406409
void OnDone(ReadHandler & apReadObj) override;
407410

@@ -599,7 +602,7 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler,
599602

600603
CommandHandlerInterface * mCommandHandlerList = nullptr;
601604

602-
ObjectPool<CommandHandler, CHIP_IM_MAX_NUM_COMMAND_HANDLER> mCommandHandlerObjs;
605+
ObjectPool<CommandResponseSender, CHIP_IM_MAX_NUM_COMMAND_HANDLER> mCommandResponderObjs;
603606
ObjectPool<TimedHandler, CHIP_IM_MAX_NUM_TIMED_HANDLER> mTimedHandlers;
604607
WriteHandler mWriteHandlers[CHIP_IM_MAX_NUM_WRITE_HANDLER];
605608
reporting::Engine mReportingEngine;

‎src/app/tests/TestCommandInteraction.cpp

+224-191
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.