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