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