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