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 42 : CHIP_ERROR GetRef(ParserT aParser, Optional<uint16_t> & aRef, bool commandRefRequired)
35 : {
36 42 : CHIP_ERROR err = CHIP_NO_ERROR;
37 : uint16_t ref;
38 42 : err = aParser.GetRef(&ref);
39 :
40 42 : VerifyOrReturnError(err == CHIP_NO_ERROR || err == CHIP_END_OF_TLV, err);
41 42 : if (err == CHIP_END_OF_TLV)
42 : {
43 40 : if (commandRefRequired)
44 : {
45 0 : return CHIP_ERROR_INVALID_ARGUMENT;
46 : }
47 40 : aRef = NullOptional;
48 40 : return CHIP_NO_ERROR;
49 : }
50 :
51 2 : aRef = MakeOptional(ref);
52 2 : return CHIP_NO_ERROR;
53 : }
54 :
55 : } // namespace
56 :
57 38 : CommandSender::CommandSender(Callback * apCallback, Messaging::ExchangeManager * apExchangeMgr, bool aIsTimedRequest,
58 38 : bool aSuppressResponse, bool aAllowLargePayload) :
59 38 : mExchangeCtx(*this),
60 38 : mCallbackHandle(apCallback), mpExchangeMgr(apExchangeMgr), mSuppressResponse(aSuppressResponse), mTimedRequest(aIsTimedRequest),
61 76 : mAllowLargePayload(aAllowLargePayload)
62 : {
63 38 : assertChipStackLockedByCurrentThread();
64 38 : }
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 50 : CommandSender::~CommandSender()
79 : {
80 50 : assertChipStackLockedByCurrentThread();
81 50 : }
82 :
83 73 : CHIP_ERROR CommandSender::AllocateBuffer()
84 : {
85 73 : if (!mBufferAllocated)
86 : {
87 46 : mCommandMessageWriter.Reset();
88 :
89 46 : System::PacketBufferHandle commandPacket;
90 46 : size_t bufferSizeToAllocate = kMaxSecureSduLengthBytes;
91 46 : if (mAllowLargePayload)
92 : {
93 0 : bufferSizeToAllocate = kMaxLargeSecureSduLengthBytes;
94 : }
95 46 : commandPacket = System::PacketBufferHandle::New(bufferSizeToAllocate);
96 :
97 46 : 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 46 : uint32_t reservedSize = 0;
102 46 : if (commandPacket->AvailableDataLength() > bufferSizeToAllocate)
103 : {
104 0 : reservedSize = static_cast<uint32_t>(commandPacket->AvailableDataLength() - bufferSizeToAllocate);
105 : }
106 :
107 46 : mCommandMessageWriter.Init(std::move(commandPacket));
108 46 : ReturnErrorOnFailure(mInvokeRequestBuilder.InitWithEndBufferReserved(&mCommandMessageWriter));
109 : // Reserving space for MIC at the end.
110 46 : ReturnErrorOnFailure(
111 : mInvokeRequestBuilder.GetWriter()->ReserveBuffer(reservedSize + Crypto::CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES));
112 :
113 46 : mInvokeRequestBuilder.SuppressResponse(mSuppressResponse).TimedRequest(mTimedRequest);
114 46 : ReturnErrorOnFailure(mInvokeRequestBuilder.GetError());
115 :
116 46 : mInvokeRequestBuilder.CreateInvokeRequests(/* aReserveEndBuffer = */ true);
117 46 : ReturnErrorOnFailure(mInvokeRequestBuilder.GetError());
118 :
119 46 : mBufferAllocated = true;
120 46 : }
121 :
122 73 : return CHIP_NO_ERROR;
123 : }
124 :
125 41 : CHIP_ERROR CommandSender::SendCommandRequestInternal(const SessionHandle & session, Optional<System::Clock::Timeout> timeout)
126 : {
127 41 : VerifyOrReturnError(mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
128 :
129 40 : ReturnErrorOnFailure(Finalize(mPendingInvokeData));
130 :
131 : // Create a new exchange context.
132 40 : auto exchange = mpExchangeMgr->NewContext(session, this);
133 40 : VerifyOrReturnError(exchange != nullptr, CHIP_ERROR_NO_MEMORY);
134 :
135 40 : mExchangeCtx.Grab(exchange);
136 40 : VerifyOrReturnError(!mExchangeCtx->IsGroupExchangeContext(), CHIP_ERROR_INVALID_MESSAGE_TYPE);
137 :
138 80 : mExchangeCtx->SetResponseTimeout(
139 40 : timeout.ValueOr(session->ComputeRoundTripTimeout(app::kExpectedIMProcessingTime, true /*isFirstMessageOnExchange*/)));
140 :
141 40 : 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 40 : 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 41 : 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 41 : if (mAllowLargePayload)
165 : {
166 0 : VerifyOrReturnError(session->AllowsLargePayload(), CHIP_ERROR_INCORRECT_STATE);
167 : }
168 :
169 41 : 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 41 : 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 40 : CHIP_ERROR CommandSender::SendInvokeRequest()
200 : {
201 : using namespace Protocols::InteractionModel;
202 : using namespace Messaging;
203 :
204 40 : ReturnErrorOnFailure(
205 : mExchangeCtx->SendMessage(MsgType::InvokeCommandRequest, std::move(mPendingInvokeData), SendMessageFlags::kExpectResponse));
206 40 : MoveToState(State::AwaitingResponse);
207 :
208 40 : return CHIP_NO_ERROR;
209 : }
210 :
211 37 : CHIP_ERROR CommandSender::OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
212 : System::PacketBufferHandle && aPayload)
213 : {
214 : using namespace Protocols::InteractionModel;
215 :
216 37 : if (mState == State::AwaitingResponse)
217 : {
218 37 : MoveToState(State::ResponseReceived);
219 : }
220 :
221 37 : CHIP_ERROR err = CHIP_NO_ERROR;
222 37 : bool sendStatusResponse = false;
223 37 : bool moreChunkedMessages = false;
224 37 : VerifyOrExit(apExchangeContext == mExchangeCtx.Get(), err = CHIP_ERROR_INCORRECT_STATE);
225 37 : sendStatusResponse = true;
226 :
227 37 : 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 37 : if (aPayloadHeader.HasMessageType(MsgType::InvokeCommandResponse))
247 : {
248 32 : mInvokeResponseMessageCount++;
249 32 : err = ProcessInvokeResponse(std::move(aPayload), moreChunkedMessages);
250 32 : SuccessOrExit(err);
251 31 : 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 31 : 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 37 : exit:
272 37 : if (err != CHIP_NO_ERROR)
273 : {
274 6 : OnErrorCallback(err);
275 : }
276 :
277 37 : if (sendStatusResponse)
278 : {
279 6 : StatusResponse::Send(Status::InvalidAction, apExchangeContext, /*aExpectResponse = */ false);
280 : }
281 :
282 37 : if (mState != State::AwaitingResponse)
283 : {
284 37 : if (err == CHIP_NO_ERROR)
285 : {
286 31 : FlushNoCommandResponse();
287 : }
288 37 : Close();
289 : }
290 : // Else we got a response to a Timed Request and just sent the invoke.
291 :
292 37 : return err;
293 : }
294 :
295 36 : CHIP_ERROR CommandSender::ProcessInvokeResponse(System::PacketBufferHandle && payload, bool & moreChunkedMessages)
296 : {
297 36 : CHIP_ERROR err = CHIP_NO_ERROR;
298 36 : System::PacketBufferTLVReader reader;
299 36 : TLV::TLVReader invokeResponsesReader;
300 36 : InvokeResponseMessage::Parser invokeResponseMessage;
301 36 : InvokeResponseIBs::Parser invokeResponses;
302 36 : bool suppressResponse = false;
303 :
304 36 : reader.Init(std::move(payload));
305 36 : ReturnErrorOnFailure(invokeResponseMessage.Init(reader));
306 :
307 : #if CHIP_CONFIG_IM_PRETTY_PRINT
308 35 : invokeResponseMessage.PrettyPrint();
309 : #endif
310 :
311 35 : ReturnErrorOnFailure(invokeResponseMessage.GetSuppressResponse(&suppressResponse));
312 35 : ReturnErrorOnFailure(invokeResponseMessage.GetInvokeResponses(&invokeResponses));
313 35 : invokeResponses.GetReader(&invokeResponsesReader);
314 :
315 76 : while (CHIP_NO_ERROR == (err = invokeResponsesReader.Next()))
316 : {
317 42 : VerifyOrReturnError(TLV::AnonymousTag() == invokeResponsesReader.GetTag(), CHIP_ERROR_INVALID_TLV_TAG);
318 42 : InvokeResponseIB::Parser invokeResponse;
319 42 : ReturnErrorOnFailure(invokeResponse.Init(invokeResponsesReader));
320 42 : ReturnErrorOnFailure(ProcessInvokeResponseIB(invokeResponse));
321 : }
322 :
323 34 : 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 34 : if (CHIP_END_OF_TLV == err)
327 : {
328 34 : moreChunkedMessages = false;
329 34 : err = CHIP_NO_ERROR;
330 : }
331 34 : ReturnErrorOnFailure(err);
332 :
333 34 : 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 34 : if (CHIP_END_OF_TLV == err)
342 : {
343 0 : err = CHIP_NO_ERROR;
344 : }
345 34 : ReturnErrorOnFailure(err);
346 34 : return invokeResponseMessage.ExitContainer();
347 36 : }
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 33 : void CommandSender::FlushNoCommandResponse()
359 : {
360 33 : 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 33 : }
371 :
372 41 : void CommandSender::Close()
373 : {
374 41 : mSuppressResponse = false;
375 41 : mTimedRequest = false;
376 41 : MoveToState(State::AwaitingDestruction);
377 41 : OnDoneCallback();
378 41 : }
379 :
380 42 : CHIP_ERROR CommandSender::ProcessInvokeResponseIB(InvokeResponseIB::Parser & aInvokeResponse)
381 : {
382 42 : 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 42 : StatusIB statusIB;
388 :
389 : {
390 42 : bool hasDataResponse = false;
391 42 : TLV::TLVReader commandDataReader;
392 42 : Optional<uint16_t> commandRef;
393 42 : bool commandRefRequired = (mFinishedCommandCount > 1);
394 :
395 42 : CommandStatusIB::Parser commandStatus;
396 42 : err = aInvokeResponse.GetStatus(&commandStatus);
397 42 : if (CHIP_NO_ERROR == err)
398 : {
399 31 : CommandPathIB::Parser commandPath;
400 31 : ReturnErrorOnFailure(commandStatus.GetPath(&commandPath));
401 31 : ReturnErrorOnFailure(commandPath.GetClusterId(&clusterId));
402 31 : ReturnErrorOnFailure(commandPath.GetCommandId(&commandId));
403 31 : ReturnErrorOnFailure(commandPath.GetEndpointId(&endpointId));
404 :
405 31 : StatusIB::Parser status;
406 31 : commandStatus.GetErrorStatus(&status);
407 31 : ReturnErrorOnFailure(status.DecodeStatusIB(statusIB));
408 31 : 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 42 : 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 42 : 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 31 : 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 42 : ReturnErrorOnFailure(err);
448 :
449 42 : 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 45 : 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 41 : 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 19 : OnErrorCallback(statusIB.ToChipError());
493 : }
494 : }
495 41 : 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 54 : CHIP_ERROR CommandSender::PrepareCommand(const CommandPathParams & aCommandPathParams,
516 : PrepareCommandParameters & aPrepareCommandParams)
517 : {
518 54 : 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 54 : bool canAddAnotherCommand = (mState == State::AddedCommand && mBatchCommandsEnabled && mUseExtendableCallback);
524 54 : VerifyOrReturnError(mState == State::Idle || canAddAnotherCommand, CHIP_ERROR_INCORRECT_STATE);
525 :
526 53 : VerifyOrReturnError(mFinishedCommandCount < mRemoteMaxPathsPerInvoke, CHIP_ERROR_MAXIMUM_PATHS_PER_INVOKE_EXCEEDED);
527 :
528 53 : 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 52 : InvokeRequests::Builder & invokeRequests = mInvokeRequestBuilder.GetInvokeRequests();
537 52 : CommandDataIB::Builder & invokeRequest = invokeRequests.CreateCommandData();
538 52 : ReturnErrorOnFailure(invokeRequests.GetError());
539 52 : CommandPathIB::Builder & path = invokeRequest.CreatePath();
540 52 : ReturnErrorOnFailure(invokeRequest.GetError());
541 52 : ReturnErrorOnFailure(path.Encode(aCommandPathParams));
542 :
543 52 : if (aPrepareCommandParams.startDataStruct)
544 : {
545 33 : ReturnErrorOnFailure(invokeRequest.GetWriter()->StartContainer(TLV::ContextTag(CommandDataIB::Tag::kFields),
546 : TLV::kTLVType_Structure, mDataElementContainerType));
547 : }
548 :
549 52 : MoveToState(State::AddingCommand);
550 52 : return CHIP_NO_ERROR;
551 : }
552 :
553 48 : CHIP_ERROR CommandSender::FinishCommand(FinishCommandParameters & aFinishCommandParams)
554 : {
555 48 : 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 48 : 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 50 : CHIP_ERROR CommandSender::FinishCommandInternal(FinishCommandParameters & aFinishCommandParams)
584 : {
585 50 : CHIP_ERROR err = CHIP_NO_ERROR;
586 :
587 50 : VerifyOrReturnError(mState == State::AddingCommand, err = CHIP_ERROR_INCORRECT_STATE);
588 :
589 50 : CommandDataIB::Builder & commandData = mInvokeRequestBuilder.GetInvokeRequests().GetCommandData();
590 :
591 50 : if (aFinishCommandParams.endDataStruct)
592 : {
593 32 : ReturnErrorOnFailure(commandData.GetWriter()->EndContainer(mDataElementContainerType));
594 : }
595 :
596 50 : if (aFinishCommandParams.commandRef.HasValue())
597 : {
598 13 : ReturnErrorOnFailure(commandData.Ref(aFinishCommandParams.commandRef.Value()));
599 : }
600 :
601 50 : ReturnErrorOnFailure(commandData.EndOfCommandDataIB());
602 :
603 50 : MoveToState(State::AddedCommand);
604 50 : mFinishedCommandCount++;
605 :
606 50 : if (mpPendingResponseTracker && aFinishCommandParams.commandRef.HasValue())
607 : {
608 12 : mpPendingResponseTracker->Add(aFinishCommandParams.commandRef.Value());
609 : }
610 :
611 50 : if (aFinishCommandParams.timedInvokeTimeoutMs.HasValue())
612 : {
613 0 : SetTimedInvokeTimeoutMs(aFinishCommandParams.timedInvokeTimeoutMs);
614 : }
615 :
616 50 : return CHIP_NO_ERROR;
617 : }
618 :
619 52 : TLV::TLVWriter * CommandSender::GetCommandDataIBTLVWriter()
620 : {
621 52 : if (mState != State::AddingCommand)
622 : {
623 0 : return nullptr;
624 : }
625 :
626 52 : 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 18 : size_t CommandSender::GetInvokeResponseMessageCount()
643 : {
644 18 : return static_cast<size_t>(mInvokeResponseMessageCount);
645 : }
646 :
647 43 : CHIP_ERROR CommandSender::Finalize(System::PacketBufferHandle & commandPacket)
648 : {
649 43 : VerifyOrReturnError(mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
650 43 : ReturnErrorOnFailure(mInvokeRequestBuilder.GetInvokeRequests().EndOfInvokeRequests());
651 43 : ReturnErrorOnFailure(mInvokeRequestBuilder.EndOfInvokeRequestMessage());
652 43 : return mCommandMessageWriter.Finalize(&commandPacket);
653 : }
654 :
655 223 : const char * CommandSender::GetStateStr() const
656 : {
657 : #if CHIP_DETAIL_LOGGING
658 223 : switch (mState)
659 : {
660 0 : case State::Idle:
661 0 : return "Idle";
662 :
663 52 : case State::AddingCommand:
664 52 : return "AddingCommand";
665 :
666 53 : case State::AddedCommand:
667 53 : return "AddedCommand";
668 :
669 0 : case State::AwaitingTimedStatus:
670 0 : return "AwaitingTimedStatus";
671 :
672 40 : case State::AwaitingResponse:
673 40 : return "AwaitingResponse";
674 :
675 37 : case State::ResponseReceived:
676 37 : return "ResponseReceived";
677 :
678 41 : case State::AwaitingDestruction:
679 41 : return "AwaitingDestruction";
680 : }
681 : #endif // CHIP_DETAIL_LOGGING
682 0 : return "N/A";
683 : }
684 :
685 223 : void CommandSender::MoveToState(const State aTargetState)
686 : {
687 223 : mState = aTargetState;
688 223 : ChipLogDetail(DataManagement, "ICR moving to [%10.10s]", GetStateStr());
689 223 : }
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
|