@@ -38,12 +38,10 @@ CHIP_ERROR CommandResponseSender::OnMessageReceived(Messaging::ExchangeContext *
38
38
err = statusError;
39
39
VerifyOrExit (err == CHIP_NO_ERROR, failureStatusToSend.SetValue (Status::InvalidAction));
40
40
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.
46
41
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.
47
45
VerifyOrExit (err == CHIP_NO_ERROR, failureStatusToSend.SetValue (Status::Failure));
48
46
49
47
bool moreToSend = !mChunks .IsNull ();
@@ -81,13 +79,28 @@ void CommandResponseSender::OnResponseTimeout(Messaging::ExchangeContext * apExc
81
79
Close ();
82
80
}
83
81
84
- CHIP_ERROR CommandResponseSender::StartSendingCommandResponses ()
82
+ void CommandResponseSender::StartSendingCommandResponses ()
85
83
{
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
+ }
91
104
92
105
if (HasMoreToSend ())
93
106
{
@@ -98,7 +111,31 @@ CHIP_ERROR CommandResponseSender::StartSendingCommandResponses()
98
111
{
99
112
Close ();
100
113
}
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);
102
139
}
103
140
104
141
CHIP_ERROR CommandResponseSender::SendCommandResponse ()
@@ -139,6 +176,9 @@ const char * CommandResponseSender::GetStateStr() const
139
176
140
177
case State::AllInvokeResponsesSent:
141
178
return " AllInvokeResponsesSent" ;
179
+
180
+ case State::ErrorSentDelayCloseUntilOnDone:
181
+ return " ErrorSentDelayCloseUntilOnDone" ;
142
182
}
143
183
#endif // CHIP_DETAIL_LOGGING
144
184
return " N/A" ;
@@ -157,12 +197,52 @@ void CommandResponseSender::MoveToState(const State aTargetState)
157
197
void CommandResponseSender::Close ()
158
198
{
159
199
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)
162
219
{
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);
164
226
}
165
227
}
166
228
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
+
167
247
} // namespace app
168
248
} // namespace chip
0 commit comments