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