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