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 : /**
20 : * @file
21 : * A handler for incoming Invoke interactions.
22 : *
23 : * Allows adding responses to be sent in an InvokeResponse: see the various
24 : * "Add*" methods.
25 : *
26 : * Allows adding the responses asynchronously. See the documentation
27 : * for the CommandHandler::Handle class below.
28 : *
29 : */
30 :
31 : #pragma once
32 :
33 : #include "CommandPathRegistry.h"
34 : #include "CommandResponseSender.h"
35 :
36 : #include <app/ConcreteCommandPath.h>
37 : #include <app/data-model/Encode.h>
38 : #include <lib/core/CHIPCore.h>
39 : #include <lib/core/TLV.h>
40 : #include <lib/core/TLVDebug.h>
41 : #include <lib/support/BitFlags.h>
42 : #include <lib/support/CodeUtils.h>
43 : #include <lib/support/DLLUtil.h>
44 : #include <lib/support/Scoped.h>
45 : #include <lib/support/logging/CHIPLogging.h>
46 : #include <messaging/ExchangeHolder.h>
47 : #include <messaging/Flags.h>
48 : #include <protocols/Protocols.h>
49 : #include <protocols/interaction_model/Constants.h>
50 : #include <system/SystemPacketBuffer.h>
51 : #include <system/TLVPacketBufferBackingStore.h>
52 :
53 : #include <app/MessageDef/InvokeRequestMessage.h>
54 : #include <app/MessageDef/InvokeResponseMessage.h>
55 :
56 : namespace chip {
57 : namespace app {
58 :
59 : class CommandHandler
60 : {
61 : public:
62 : class Callback
63 : {
64 : public:
65 40 : virtual ~Callback() = default;
66 :
67 : /*
68 : * Method that signals to a registered callback that this object
69 : * has completed doing useful work and is now safe for release/destruction.
70 : */
71 : virtual void OnDone(CommandHandler & apCommandObj) = 0;
72 :
73 : /*
74 : * Upon processing of a CommandDataIB, this method is invoked to dispatch the command
75 : * to the right server-side handler provided by the application.
76 : */
77 : virtual void DispatchCommand(CommandHandler & apCommandObj, const ConcreteCommandPath & aCommandPath,
78 : TLV::TLVReader & apPayload) = 0;
79 :
80 : /*
81 : * Check to see if a command implementation exists for a specific
82 : * concrete command path. If it does, Success will be returned. If
83 : * not, one of UnsupportedEndpoint, UnsupportedCluster, or
84 : * UnsupportedCommand will be returned, depending on how the command
85 : * fails to exist.
86 : */
87 : virtual Protocols::InteractionModel::Status CommandExists(const ConcreteCommandPath & aCommandPath) = 0;
88 : };
89 :
90 : /**
91 : * Class that allows asynchronous command processing before sending a
92 : * response. When such processing is desired:
93 : *
94 : * 1) Create a Handle initialized with the CommandHandler that delivered the
95 : * incoming command.
96 : * 2) Ensure the Handle, or some Handle it's moved into via the move
97 : * constructor or move assignment operator, remains alive during the
98 : * course of the asynchronous processing.
99 : * 3) Ensure that the ConcreteCommandPath involved will be known when
100 : * sending the response.
101 : * 4) When ready to send the response:
102 : * * Ensure that no other Matter tasks are running in parallel (e.g. by
103 : * running on the Matter event loop or holding the Matter stack lock).
104 : * * Call Get() to get the CommandHandler.
105 : * * Check that Get() did not return null.
106 : * * Add the response to the CommandHandler via one of the Add* methods.
107 : * * Let the Handle get destroyed, or manually call Handle::Release() if
108 : * destruction of the Handle is not desirable for some reason.
109 : *
110 : * The Invoke Response will not be sent until all outstanding Handles have
111 : * been destroyed or have had Release called.
112 : */
113 : class Handle
114 : {
115 : public:
116 : Handle() {}
117 : Handle(const Handle & handle) = delete;
118 : Handle(Handle && handle)
119 : {
120 : mpHandler = handle.mpHandler;
121 : mMagic = handle.mMagic;
122 : handle.mpHandler = nullptr;
123 : handle.mMagic = 0;
124 : }
125 : Handle(decltype(nullptr)) {}
126 : Handle(CommandHandler * handle);
127 7 : ~Handle() { Release(); }
128 :
129 : Handle & operator=(Handle && handle)
130 : {
131 : Release();
132 : mpHandler = handle.mpHandler;
133 : mMagic = handle.mMagic;
134 : handle.mpHandler = nullptr;
135 : handle.mMagic = 0;
136 : return *this;
137 : }
138 :
139 : Handle & operator=(decltype(nullptr))
140 : {
141 : Release();
142 : return *this;
143 : }
144 :
145 : /**
146 : * Get the CommandHandler object it holds. Get() may return a nullptr if the CommandHandler object is holds is no longer
147 : * valid.
148 : */
149 : CommandHandler * Get();
150 :
151 : void Release();
152 :
153 : private:
154 : CommandHandler * mpHandler = nullptr;
155 : uint32_t mMagic = 0;
156 : };
157 :
158 : // Previously we kept adding arguments with default values individually as parameters. This is because there
159 : // is legacy code outside of the SDK that would call PrepareCommand. With the new PrepareInvokeResponseCommand
160 : // replacing PrepareCommand, we took this opportunity to create a new parameter structure to make it easier to
161 : // add new parameters without there needing to be an ever increasing parameter list with defaults.
162 : struct InvokeResponseParameters
163 : {
164 : InvokeResponseParameters(ConcreteCommandPath aRequestCommandPath) : mRequestCommandPath(aRequestCommandPath) {}
165 :
166 : InvokeResponseParameters & SetStartOrEndDataStruct(bool aStartOrEndDataStruct)
167 : {
168 : mStartOrEndDataStruct = aStartOrEndDataStruct;
169 : return *this;
170 : }
171 :
172 : ConcreteCommandPath mRequestCommandPath;
173 : /**
174 : * Whether the method this is being provided to should start/end the TLV container for the CommandFields element
175 : * within CommandDataIB.
176 : */
177 : bool mStartOrEndDataStruct = true;
178 : };
179 :
180 : class TestOnlyMarker
181 : {
182 : };
183 :
184 : /*
185 : * Constructor.
186 : *
187 : * The callback passed in has to outlive this CommandHandler object.
188 : */
189 : CommandHandler(Callback * apCallback);
190 :
191 : /*
192 : * Constructor to override number of supported paths per invoke.
193 : *
194 : * The callback and command path registry passed in has to outlive this CommandHandler object.
195 : * For testing purposes.
196 : */
197 : CommandHandler(TestOnlyMarker aTestMarker, Callback * apCallback, CommandPathRegistry * apCommandPathRegistry);
198 :
199 : /*
200 : * Main entrypoint for this class to handle an invoke request.
201 : *
202 : * This function will always call the OnDone function above on the registered callback
203 : * before returning.
204 : *
205 : * isTimedInvoke is true if and only if this is part of a Timed Invoke
206 : * transaction (i.e. was preceded by a Timed Request). If we reach here,
207 : * the timer verification has already been done.
208 : */
209 : void OnInvokeCommandRequest(Messaging::ExchangeContext * ec, const PayloadHeader & payloadHeader,
210 : System::PacketBufferHandle && payload, bool isTimedInvoke);
211 :
212 : /**
213 : * Checks that all CommandDataIB within InvokeRequests satisfy the spec's general
214 : * constraints for CommandDataIB. Additionally checks that InvokeRequestMessage is
215 : * properly formatted.
216 : *
217 : * This also builds a registry that to ensure that all commands can be responded
218 : * to with the data required as per spec.
219 : */
220 : CHIP_ERROR ValidateInvokeRequestMessageAndBuildRegistry(InvokeRequestMessage::Parser & invokeRequestMessage);
221 :
222 : /**
223 : * Adds the given command status and returns any failures in adding statuses (e.g. out
224 : * of buffer space) to the caller
225 : */
226 : CHIP_ERROR FallibleAddStatus(const ConcreteCommandPath & aCommandPath, const Protocols::InteractionModel::Status aStatus,
227 : const char * context = nullptr);
228 :
229 : /**
230 : * Adds a status when the caller is unable to handle any failures. Logging is performed
231 : * and failure to register the status is checked with VerifyOrDie.
232 : */
233 : void AddStatus(const ConcreteCommandPath & aCommandPath, const Protocols::InteractionModel::Status aStatus,
234 : const char * context = nullptr);
235 :
236 : CHIP_ERROR AddClusterSpecificSuccess(const ConcreteCommandPath & aCommandPath, ClusterStatus aClusterStatus);
237 :
238 : CHIP_ERROR AddClusterSpecificFailure(const ConcreteCommandPath & aCommandPath, ClusterStatus aClusterStatus);
239 :
240 : Protocols::InteractionModel::Status ProcessInvokeRequest(System::PacketBufferHandle && payload, bool isTimedInvoke);
241 :
242 : /**
243 : * This adds a new CommandDataIB element into InvokeResponses for the associated
244 : * aRequestCommandPath. This adds up until the `CommandFields` element within
245 : * `CommandDataIB`.
246 : *
247 : * This call will fail if CommandHandler is already in the middle of building a
248 : * CommandStatusIB or CommandDataIB (i.e. something has called Prepare*, without
249 : * calling Finish*), or is already sending InvokeResponseMessage.
250 : *
251 : * Upon success, the caller is expected to call `FinishCommand` once they have added
252 : * all the fields into the CommandFields element of CommandDataIB.
253 : *
254 : * @param [in] aResponseCommandPath the concrete response path that we are sending to Requester.
255 : * @param [in] aPrepareParameters struct containing paramters needs for preparing a command. Data
256 : * such as request path, and whether this method should start the CommandFields element within
257 : * CommandDataIB.
258 : */
259 : CHIP_ERROR PrepareInvokeResponseCommand(const ConcreteCommandPath & aResponseCommandPath,
260 : const InvokeResponseParameters & aPrepareParameters);
261 :
262 : [[deprecated("PrepareCommand now needs the requested command path. Please use PrepareInvokeResponseCommand")]] CHIP_ERROR
263 : PrepareCommand(const ConcreteCommandPath & aCommandPath, bool aStartDataStruct = true);
264 :
265 : /**
266 : * Finishes the CommandDataIB element within the InvokeResponses.
267 : *
268 : * Caller must have first successfully called `PrepareInvokeResponseCommand`.
269 : *
270 : * @param [in] aEndDataStruct end the TLV container for the CommandFields element within
271 : * CommandDataIB. This should match the boolean passed into Prepare*.
272 : *
273 : * @return CHIP_ERROR_INCORRECT_STATE
274 : * If device has not previously successfully called
275 : * `PrepareInvokeResponseCommand`.
276 : * @return CHIP_ERROR_BUFFER_TOO_SMALL
277 : * If writing the values needed to finish the InvokeReponseIB
278 : * with the current contents of the InvokeResponseMessage
279 : * would exceed the limit. When this error occurs, it is possible
280 : * we have already closed some of the IB Builders that were
281 : * previously started in `PrepareInvokeResponseCommand`.
282 : * @return CHIP_ERROR_NO_MEMORY
283 : * If TLVWriter attempted to allocate an output buffer failed due to
284 : * lack of memory.
285 : * @return other Other TLVWriter related errors. Typically occurs if
286 : * `GetCommandDataIBTLVWriter()` was called and used incorrectly.
287 : */
288 : // TODO(#30453): We should be able to eliminate the chances of OOM issues with reserve.
289 : // This will be completed in a follow up PR.
290 : CHIP_ERROR FinishCommand(bool aEndDataStruct = true);
291 :
292 : TLV::TLVWriter * GetCommandDataIBTLVWriter();
293 :
294 : /**
295 : * GetAccessingFabricIndex() may only be called during synchronous command
296 : * processing. Anything that runs async (while holding a
297 : * CommandHandler::Handle or equivalent) must not call this method, because
298 : * it will not work right if the session we're using was evicted.
299 : */
300 : FabricIndex GetAccessingFabricIndex() const;
301 :
302 : /**
303 : * @brief Best effort to add InvokeResponse to InvokeResponseMessage.
304 : *
305 : * Tries to add response using lambda. Upon failure to add response, attempts
306 : * to rollback the InvokeResponseMessage to a known good state. If failure is due
307 : * to insufficient space in the current InvokeResponseMessage:
308 : * - Finalizes the current InvokeResponseMessage.
309 : * - Allocates a new InvokeResponseMessage.
310 : * - Reattempts to add the InvokeResponse to the new InvokeResponseMessage.
311 : *
312 : * @param [in] addResponseFunction A lambda function responsible for adding the
313 : * response to the current InvokeResponseMessage.
314 : */
315 : template <typename Function>
316 37 : CHIP_ERROR TryAddingResponse(Function && addResponseFunction)
317 : {
318 : // Invalidate any existing rollback backups. The addResponseFunction is
319 : // expected to create a new backup during either PrepareInvokeResponseCommand
320 : // or PrepareStatus execution. Direct invocation of
321 : // CreateBackupForResponseRollback is avoided since the buffer used by
322 : // InvokeResponseMessage might not be allocated until a Prepare* function
323 : // is called.
324 37 : mRollbackBackupValid = false;
325 37 : CHIP_ERROR err = addResponseFunction();
326 37 : if (err == CHIP_NO_ERROR)
327 : {
328 36 : return CHIP_NO_ERROR;
329 : }
330 1 : ReturnErrorOnFailure(RollbackResponse());
331 : // If we failed to add a command due to lack of space in the
332 : // packet, we will make another attempt to add the response using
333 : // an additional InvokeResponseMessage.
334 1 : if (mState != State::AddedCommand || err != CHIP_ERROR_NO_MEMORY)
335 : {
336 0 : return err;
337 : }
338 1 : ReturnErrorOnFailure(FinalizeInvokeResponseMessageAndPrepareNext());
339 1 : err = addResponseFunction();
340 1 : if (err != CHIP_NO_ERROR)
341 : {
342 : // The return value of RollbackResponse is ignored, as we prioritize
343 : // conveying the error generated by addResponseFunction to the
344 : // caller.
345 0 : RollbackResponse();
346 : }
347 1 : return err;
348 : }
349 :
350 : /**
351 : * API for adding a data response. The template parameter T is generally
352 : * expected to be a ClusterName::Commands::CommandName::Type struct, but any
353 : * object that can be encoded using the DataModel::Encode machinery and
354 : * exposes the right command id will work.
355 : *
356 : * @param [in] aRequestCommandPath the concrete path of the command we are
357 : * responding to.
358 : * @param [in] aData the data for the response.
359 : */
360 : template <typename CommandData>
361 : CHIP_ERROR AddResponseData(const ConcreteCommandPath & aRequestCommandPath, const CommandData & aData)
362 : {
363 : return TryAddingResponse([&]() -> CHIP_ERROR { return TryAddResponseData(aRequestCommandPath, aData); });
364 : }
365 :
366 : /**
367 : * API for adding a response. This will try to encode a data response (response command), and if that fails will encode a a
368 : * Protocols::InteractionModel::Status::Failure status response instead.
369 : *
370 : * The template parameter T is generally expected to be a ClusterName::Commands::CommandName::Type struct, but any object that
371 : * can be encoded using the DataModel::Encode machinery and exposes the right command id will work.
372 : *
373 : * Since the function will call AddStatus when it fails to encode the data, it cannot send any response when it fails to encode
374 : * a status code since another AddStatus call will also fail. The error from AddStatus will just be logged.
375 : *
376 : * @param [in] aRequestCommandPath the concrete path of the command we are
377 : * responding to.
378 : * @param [in] aData the data for the response.
379 : */
380 : template <typename CommandData>
381 : void AddResponse(const ConcreteCommandPath & aRequestCommandPath, const CommandData & aData)
382 : {
383 : if (AddResponseData(aRequestCommandPath, aData) != CHIP_NO_ERROR)
384 : {
385 : AddStatus(aRequestCommandPath, Protocols::InteractionModel::Status::Failure);
386 : }
387 : }
388 :
389 : /**
390 : * Check whether the InvokeRequest we are handling is a timed invoke.
391 : */
392 0 : bool IsTimedInvoke() const { return mTimedRequest; }
393 :
394 : /**
395 : * Gets the inner exchange context object, without ownership.
396 : *
397 : * WARNING: This is dangerous, since it is directly interacting with the
398 : * exchange being managed automatically by mResponseSender and
399 : * if not done carefully, may end up with use-after-free errors.
400 : *
401 : * @return The inner exchange context, might be nullptr if no
402 : * exchange context has been assigned or the context
403 : * has been released.
404 : */
405 : Messaging::ExchangeContext * GetExchangeContext() const { return mResponseSender.GetExchangeContext(); }
406 :
407 : /**
408 : * @brief Flush acks right away for a slow command
409 : *
410 : * Some commands that do heavy lifting of storage/crypto should
411 : * ack right away to improve reliability and reduce needless retries. This
412 : * method can be manually called in commands that are especially slow to
413 : * immediately schedule an acknowledgement (if needed) since the delayed
414 : * stand-alone ack timer may actually not hit soon enough due to blocking command
415 : * execution.
416 : *
417 : */
418 : void FlushAcksRightAwayOnSlowCommand() { mResponseSender.FlushAcksRightNow(); }
419 :
420 : /**
421 : * GetSubjectDescriptor() may only be called during synchronous command
422 : * processing. Anything that runs async (while holding a
423 : * CommandHandler::Handle or equivalent) must not call this method, because
424 : * it might not work right if the session we're using was evicted.
425 : */
426 81 : Access::SubjectDescriptor GetSubjectDescriptor() const
427 : {
428 81 : VerifyOrDie(!mGoneAsync);
429 81 : return mResponseSender.GetSubjectDescriptor();
430 : }
431 :
432 : private:
433 : friend class TestCommandInteraction;
434 : friend class CommandHandler::Handle;
435 :
436 : enum class State : uint8_t
437 : {
438 : Idle, ///< Default state that the object starts out in, where no work has commenced
439 : NewResponseMessage, ///< mInvokeResponseBuilder is ready, with no responses added.
440 : Preparing, ///< We are prepaing the command or status header.
441 : AddingCommand, ///< In the process of adding a command.
442 : AddedCommand, ///< A command has been completely encoded and is awaiting transmission.
443 : DispatchResponses, ///< The command response(s) are being dispatched.
444 : AwaitingDestruction, ///< The object has completed its work and is awaiting destruction by the application.
445 : };
446 :
447 : void MoveToState(const State aTargetState);
448 : const char * GetStateStr() const;
449 :
450 : /**
451 : * Create a backup to enable rolling back to the state prior to ResponseData encoding in the event of failure.
452 : */
453 : void CreateBackupForResponseRollback();
454 :
455 : /**
456 : * Rollback the state to before encoding the current ResponseData (before calling PrepareInvokeResponseCommand / PrepareStatus)
457 : *
458 : * Requires CreateBackupForResponseRollback to be called at the start of PrepareInvokeResponseCommand / PrepareStatus
459 : */
460 : CHIP_ERROR RollbackResponse();
461 :
462 : /*
463 : * This forcibly closes the exchange context if a valid one is pointed to. Such a situation does
464 : * not arise during normal message processing flows that all normally call Close() above. This can only
465 : * arise due to application-initiated destruction of the object when this object is handling receiving/sending
466 : * message payloads.
467 : */
468 : void Abort();
469 :
470 : /**
471 : * IncrementHoldOff will increase the inner refcount of the CommandHandler.
472 : *
473 : * Users should use CommandHandler::Handle for management the lifespan of the CommandHandler.
474 : * DefRef should be released in reasonable time, and Close() should only be called when the refcount reached 0.
475 : */
476 : void IncrementHoldOff();
477 :
478 : /**
479 : * DecrementHoldOff is used by CommandHandler::Handle for decreasing the refcount of the CommandHandler.
480 : * When refcount reached 0, CommandHandler will send the response to the peer and shutdown.
481 : */
482 : void DecrementHoldOff();
483 :
484 : /*
485 : * Allocates a packet buffer used for encoding an invoke response payload.
486 : *
487 : * This can be called multiple times safely, as it will only allocate the buffer once for the lifetime
488 : * of this object.
489 : */
490 : CHIP_ERROR AllocateBuffer();
491 :
492 : /**
493 : * This will add a new CommandStatusIB element into InvokeResponses. It will put the
494 : * aCommandPath into the CommandPath element within CommandStatusIB.
495 : *
496 : * This call will fail if CommandHandler is already in the middle of building a
497 : * CommandStatusIB or CommandDataIB (i.e. something has called Prepare*, without
498 : * calling Finish*), or is already sending InvokeResponseMessage.
499 : *
500 : * Upon success, the caller is expected to call `FinishStatus` once they have encoded
501 : * StatusIB.
502 : *
503 : * @param [in] aCommandPath the concrete path of the command we are responding to.
504 : */
505 : CHIP_ERROR PrepareStatus(const ConcreteCommandPath & aCommandPath);
506 :
507 : /**
508 : * Finishes the CommandStatusIB element within the InvokeResponses.
509 : *
510 : * Caller must have first successfully called `PrepareStatus`.
511 : */
512 : CHIP_ERROR FinishStatus();
513 :
514 : CHIP_ERROR PrepareInvokeResponseCommand(const CommandPathRegistryEntry & apCommandPathRegistryEntry,
515 : const ConcreteCommandPath & aCommandPath, bool aStartDataStruct);
516 :
517 15 : CHIP_ERROR FinalizeLastInvokeResponseMessage() { return FinalizeInvokeResponseMessage(/* aHasMoreChunks = */ false); }
518 :
519 : CHIP_ERROR FinalizeInvokeResponseMessageAndPrepareNext();
520 :
521 : CHIP_ERROR FinalizeInvokeResponseMessage(bool aHasMoreChunks);
522 :
523 : /**
524 : * Called internally to signal the completion of all work on this object, gracefully close the
525 : * exchange (by calling into the base class) and finally, signal to a registerd callback that it's
526 : * safe to release this object.
527 : */
528 : void Close();
529 :
530 : /**
531 : * @brief Callback method invoked when CommandResponseSender has finished sending all messages.
532 : */
533 : static void HandleOnResponseSenderDone(void * context);
534 :
535 : /**
536 : * ProcessCommandDataIB is only called when a unicast invoke command request is received
537 : * It requires the endpointId in its command path to be able to dispatch the command
538 : */
539 : Protocols::InteractionModel::Status ProcessCommandDataIB(CommandDataIB::Parser & aCommandElement);
540 :
541 : /**
542 : * ProcessGroupCommandDataIB is only called when a group invoke command request is received
543 : * It doesn't need the endpointId in it's command path since it uses the GroupId in message metadata to find it
544 : */
545 : Protocols::InteractionModel::Status ProcessGroupCommandDataIB(CommandDataIB::Parser & aCommandElement);
546 : CHIP_ERROR StartSendingCommandResponses();
547 :
548 : CHIP_ERROR TryAddStatusInternal(const ConcreteCommandPath & aCommandPath, const StatusIB & aStatus);
549 :
550 : CHIP_ERROR AddStatusInternal(const ConcreteCommandPath & aCommandPath, const StatusIB & aStatus);
551 :
552 : /**
553 : * Non-templated function called before DataModel::Encode when attempting to add a response,
554 : * which does all the work needed before encoding the actual type-dependent data into the buffer.
555 : *
556 : * **Important:** If this function fails, the TLV buffer may be left in an inconsistent state.
557 : * Callers should create snapshots as necessary before invoking this function and implement
558 : * rollback mechanisms if needed.
559 : *
560 : * **Usage:** This function is intended to be called exclusively by TryAddResponseData. It was
561 : * factored out to optimize code size.
562 : *
563 : * @param aRequestCommandPath The concrete path of the command being responded to.
564 : * @param aResponseCommandPath The concrete path of the command response.
565 : */
566 : CHIP_ERROR TryAddResponseDataPreEncode(const ConcreteCommandPath & aRequestCommandPath,
567 : const ConcreteCommandPath & aResponseCommandPath)
568 : {
569 : // Return early in case of requests targeted to a group, since they should not add a response.
570 : VerifyOrReturnValue(!IsGroupRequest(), CHIP_NO_ERROR);
571 :
572 : InvokeResponseParameters prepareParams(aRequestCommandPath);
573 : prepareParams.SetStartOrEndDataStruct(false);
574 :
575 : ScopedChange<bool> internalCallToAddResponse(mInternalCallToAddResponseData, true);
576 : return PrepareInvokeResponseCommand(aResponseCommandPath, prepareParams);
577 : }
578 :
579 : // TODO(#31627): It would be awesome if we could remove this template all together.
580 : /**
581 : * If this function fails, it may leave our TLV buffer in an inconsistent state.
582 : * Callers should snapshot as needed before calling this function, and roll back
583 : * as needed afterward.
584 : *
585 : * @param [in] aRequestCommandPath the concrete path of the command we are
586 : * responding to.
587 : * @param [in] aData the data for the response.
588 : */
589 : template <typename CommandData>
590 : CHIP_ERROR TryAddResponseData(const ConcreteCommandPath & aRequestCommandPath, const CommandData & aData)
591 : {
592 : // This method, templated with CommandData, captures all the components needs
593 : // from CommandData with as little code as possible.
594 : //
595 : // Previously, non-essential code was unnecessarily templated, leading to
596 : // compilation and duplication N times. By isolating only the code segments
597 : // that genuinely require templating, minimizes duplicate compiled code.
598 : ConcreteCommandPath responseCommandPath = { aRequestCommandPath.mEndpointId, aRequestCommandPath.mClusterId,
599 : CommandData::GetCommandId() };
600 : ReturnErrorOnFailure(TryAddResponseDataPreEncode(aRequestCommandPath, responseCommandPath));
601 : TLV::TLVWriter * writer = GetCommandDataIBTLVWriter();
602 : VerifyOrReturnError(writer != nullptr, CHIP_ERROR_INCORRECT_STATE);
603 : ReturnErrorOnFailure(DataModel::Encode(*writer, TLV::ContextTag(CommandDataIB::Tag::kFields), aData));
604 :
605 : // FinishCommand technically should be refactored out as it is not a command that needs templating.
606 : // But, because there is only a single function call, keeping it here takes less code. If there is
607 : // ever more code between DataModel::Encode and the end of this function, it should be broken out into
608 : // TryAddResponseDataPostEncode.
609 : return FinishCommand(/* aEndDataStruct = */ false);
610 : }
611 :
612 : /**
613 : * Check whether the InvokeRequest we are handling is targeted to a group.
614 : */
615 0 : bool IsGroupRequest() { return mGroupRequest; }
616 :
617 : /**
618 : * Sets the state flag to keep the information that request we are handling is targeted to a group.
619 : */
620 0 : void SetGroupRequest(bool isGroupRequest) { mGroupRequest = isGroupRequest; }
621 :
622 37 : CommandPathRegistry & GetCommandPathRegistry() const { return *mCommandPathRegistry; }
623 :
624 36 : size_t MaxPathsPerInvoke() const { return mMaxPathsPerInvoke; }
625 :
626 : Callback * mpCallback = nullptr;
627 : InvokeResponseMessage::Builder mInvokeResponseBuilder;
628 : TLV::TLVType mDataElementContainerType = TLV::kTLVType_NotSpecified;
629 : size_t mPendingWork = 0;
630 :
631 : chip::System::PacketBufferTLVWriter mCommandMessageWriter;
632 : TLV::TLVWriter mBackupWriter;
633 : size_t mMaxPathsPerInvoke = CHIP_CONFIG_MAX_PATHS_PER_INVOKE;
634 : // TODO(#30453): See if we can reduce this size for the default cases
635 : // TODO Allow flexibility in registration.
636 : BasicCommandPathRegistry<CHIP_CONFIG_MAX_PATHS_PER_INVOKE> mBasicCommandPathRegistry;
637 : CommandPathRegistry * mCommandPathRegistry = &mBasicCommandPathRegistry;
638 : Optional<uint16_t> mRefForResponse;
639 :
640 : chip::Callback::Callback<OnResponseSenderDone> mResponseSenderDone;
641 : CommandResponseSender mResponseSender;
642 :
643 : State mState = State::Idle;
644 : State mBackupState;
645 : ScopedChangeOnly<bool> mInternalCallToAddResponseData{ false };
646 : bool mSuppressResponse = false;
647 : bool mTimedRequest = false;
648 : bool mSentStatusResponse = false;
649 : bool mGroupRequest = false;
650 : bool mBufferAllocated = false;
651 : bool mReserveSpaceForMoreChunkMessages = false;
652 : // TODO(#30453): We should introduce breaking change where calls to add CommandData
653 : // need to use AddResponse, and not CommandHandler primitives directly using
654 : // GetCommandDataIBTLVWriter.
655 : bool mRollbackBackupValid = false;
656 : // If mGoneAsync is true, we have finished out initial processing of the
657 : // incoming invoke. After this point, our session could go away at any
658 : // time.
659 : bool mGoneAsync = false;
660 : };
661 :
662 : } // namespace app
663 : } // namespace chip
|