Line data Source code
1 : /*
2 : * Copyright (c) 2020-2024 Project CHIP Authors
3 : * All rights reserved.
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 : #pragma once
18 :
19 : #include <app/CommandHandler.h>
20 :
21 : #include <app/CommandHandlerExchangeInterface.h>
22 : #include <app/CommandHandlerInterface.h>
23 : #include <app/CommandPathRegistry.h>
24 : #include <app/MessageDef/InvokeRequestMessage.h>
25 : #include <app/MessageDef/InvokeResponseMessage.h>
26 : #include <app/data-model-provider/OperationTypes.h>
27 : #include <lib/core/TLV.h>
28 : #include <lib/core/TLVDebug.h>
29 : #include <lib/support/BitFlags.h>
30 : #include <lib/support/Scoped.h>
31 : #include <messaging/ExchangeHolder.h>
32 : #include <messaging/Flags.h>
33 : #include <protocols/Protocols.h>
34 : #include <protocols/interaction_model/Constants.h>
35 : #include <protocols/interaction_model/StatusCode.h>
36 : #include <system/SystemPacketBuffer.h>
37 : #include <system/TLVPacketBufferBackingStore.h>
38 :
39 : namespace chip {
40 : namespace app {
41 :
42 : class CommandHandlerImpl : public CommandHandler
43 : {
44 : public:
45 : class Callback
46 : {
47 : public:
48 246 : virtual ~Callback() = default;
49 :
50 : /*
51 : * Method that signals to a registered callback that this object
52 : * has completed doing useful work and is now safe for release/destruction.
53 : */
54 : virtual void OnDone(CommandHandlerImpl & apCommandObj) = 0;
55 :
56 : /**
57 : * Perform pre-validation that the command dispatch can be performed. In particular:
58 : * - check command existence/validity
59 : * - validate ACL
60 : * - validate timed-invoke and fabric-scoped requirements
61 : *
62 : * Returns Status::Success if the command can be dispatched, otherwise it will
63 : * return the status to be forwarded to the client on failure.
64 : *
65 : * Possible error return codes:
66 : * - UnsupportedEndpoint/UnsupportedCluster/UnsupportedCommand if the command path is invalid
67 : * - NeedsTimedInteraction
68 : * - UnsupportedAccess (ACL failure or fabric scoped without a valid fabric index)
69 : * - AccessRestricted
70 : */
71 : virtual Protocols::InteractionModel::Status ValidateCommandCanBeDispatched(const DataModel::InvokeRequest & request) = 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(CommandHandlerImpl & apCommandObj, const ConcreteCommandPath & aCommandPath,
78 : TLV::TLVReader & apPayload) = 0;
79 : };
80 :
81 : struct InvokeResponseParameters
82 : {
83 34 : InvokeResponseParameters(const ConcreteCommandPath & aRequestCommandPath) : mRequestCommandPath(aRequestCommandPath) {}
84 :
85 31 : InvokeResponseParameters & SetStartOrEndDataStruct(bool aStartOrEndDataStruct)
86 : {
87 31 : mStartOrEndDataStruct = aStartOrEndDataStruct;
88 31 : return *this;
89 : }
90 :
91 : ConcreteCommandPath mRequestCommandPath;
92 : /**
93 : * Whether the method this is being provided to should start/end the TLV container for the CommandFields element
94 : * within CommandDataIB.
95 : */
96 : bool mStartOrEndDataStruct = true;
97 : };
98 :
99 : struct TestOnlyOverrides
100 : {
101 : public:
102 : CommandPathRegistry * commandPathRegistry = nullptr;
103 : CommandHandlerExchangeInterface * commandResponder = nullptr;
104 : };
105 :
106 : /*
107 : * The callback passed in has to outlive this CommandHandler object.
108 : */
109 : CommandHandlerImpl(Callback * apCallback);
110 :
111 : /*
112 : * The destructor will also invalidate all Handles created for this CommandHandlerImpl.
113 : */
114 : virtual ~CommandHandlerImpl();
115 :
116 : /*
117 : * Constructor to override the number of supported paths per invoke and command responder.
118 : *
119 : * The callback and any pointers passed via TestOnlyOverrides must outlive this
120 : * CommandHandlerImpl object.
121 : *
122 : * For testing purposes.
123 : */
124 : CommandHandlerImpl(TestOnlyOverrides & aTestOverride, Callback * apCallback);
125 :
126 : /**************** CommandHandler interface implementation ***********************/
127 :
128 : using CommandHandler::AddResponseData;
129 : using CommandHandler::AddStatus;
130 : using CommandHandler::FallibleAddStatus;
131 :
132 : void FlushAcksRightAwayOnSlowCommand() override;
133 :
134 : CHIP_ERROR FallibleAddStatus(const ConcreteCommandPath & aRequestCommandPath,
135 : const Protocols::InteractionModel::ClusterStatusCode & aStatus,
136 : const char * context = nullptr) override;
137 : void AddStatus(const ConcreteCommandPath & aCommandPath, const Protocols::InteractionModel::ClusterStatusCode & aStatus,
138 : const char * context = nullptr) override;
139 :
140 : CHIP_ERROR AddResponseData(const ConcreteCommandPath & aRequestCommandPath, CommandId aResponseCommandId,
141 : const DataModel::EncodableToTLV & aEncodable) override;
142 : void AddResponse(const ConcreteCommandPath & aRequestCommandPath, CommandId aResponseCommandId,
143 : const DataModel::EncodableToTLV & aEncodable) override;
144 :
145 : Access::SubjectDescriptor GetSubjectDescriptor() const override;
146 : FabricIndex GetAccessingFabricIndex() const override;
147 : bool IsTimedInvoke() const override;
148 : Messaging::ExchangeContext * GetExchangeContext() const override;
149 :
150 : /**************** Implementation-specific logic ***********************/
151 :
152 : /*
153 : * Main entrypoint for this class to handle an InvokeRequestMessage.
154 : *
155 : * This function MAY call the registered OnDone callback before returning.
156 : * To prevent immediate OnDone invocation, callers can wrap their CommandHandlerImpl instance
157 : * within a CommandHandler::Handle.
158 : *
159 : * isTimedInvoke is true if and only if this is part of a Timed Invoke
160 : * transaction (i.e. was preceded by a Timed Request). If we reach here,
161 : * the timer verification has already been done.
162 : *
163 : * commandResponder handles sending InvokeResponses, added by clusters, to the client. The
164 : * command responder object must outlive this CommandHandler object. It is only safe to
165 : * release after the caller of OnInvokeCommandRequest receives the OnDone callback.
166 : */
167 : Protocols::InteractionModel::Status OnInvokeCommandRequest(CommandHandlerExchangeInterface & commandResponder,
168 : System::PacketBufferHandle && payload, bool isTimedInvoke);
169 :
170 : /**
171 : * Checks that all CommandDataIB within InvokeRequests satisfy the spec's general
172 : * constraints for CommandDataIB. Additionally checks that InvokeRequestMessage is
173 : * properly formatted.
174 : *
175 : * This also builds a registry to ensure that all commands can be responded
176 : * to with the data required as per spec.
177 : */
178 : CHIP_ERROR ValidateInvokeRequestMessageAndBuildRegistry(InvokeRequestMessage::Parser & invokeRequestMessage);
179 :
180 : /**
181 : * This adds a new CommandDataIB element into InvokeResponses for the associated
182 : * aRequestCommandPath. This adds up until the `CommandFields` element within
183 : * `CommandDataIB`.
184 : *
185 : * This call will fail if CommandHandler is already in the middle of building a
186 : * CommandStatusIB or CommandDataIB (i.e. something has called Prepare*, without
187 : * calling Finish*), or is already sending InvokeResponseMessage.
188 : *
189 : * Upon success, the caller is expected to call `FinishCommand` once they have added
190 : * all the fields into the CommandFields element of CommandDataIB.
191 : *
192 : * @param [in] aResponseCommandPath the concrete response path that we are sending to Requester.
193 : * @param [in] aPrepareParameters struct containing paramters needs for preparing a command. Data
194 : * such as request path, and whether this method should start the CommandFields element within
195 : * CommandDataIB.
196 : */
197 : CHIP_ERROR PrepareInvokeResponseCommand(const ConcreteCommandPath & aResponseCommandPath,
198 : const InvokeResponseParameters & aPrepareParameters);
199 :
200 : /**
201 : * Finishes the CommandDataIB element within the InvokeResponses.
202 : *
203 : * Caller must have first successfully called `PrepareInvokeResponseCommand`.
204 : *
205 : * @param [in] aEndDataStruct end the TLV container for the CommandFields element within
206 : * CommandDataIB. This should match the boolean passed into Prepare*.
207 : *
208 : * @return CHIP_ERROR_INCORRECT_STATE
209 : * If device has not previously successfully called
210 : * `PrepareInvokeResponseCommand`.
211 : * @return CHIP_ERROR_BUFFER_TOO_SMALL
212 : * If writing the values needed to finish the InvokeReponseIB
213 : * with the current contents of the InvokeResponseMessage
214 : * would exceed the limit. When this error occurs, it is possible
215 : * we have already closed some of the IB Builders that were
216 : * previously started in `PrepareInvokeResponseCommand`.
217 : * @return CHIP_ERROR_NO_MEMORY
218 : * If TLVWriter attempted to allocate an output buffer failed due to
219 : * lack of memory.
220 : * @return other Other TLVWriter related errors. Typically occurs if
221 : * `GetCommandDataIBTLVWriter()` was called and used incorrectly.
222 : */
223 : // TODO(#30453): We should be able to eliminate the chances of OOM issues with reserve.
224 : // This will be completed in a follow up PR.
225 : CHIP_ERROR FinishCommand(bool aEndDataStruct = true);
226 :
227 : TLV::TLVWriter * GetCommandDataIBTLVWriter();
228 :
229 : #if CHIP_WITH_NLFAULTINJECTION
230 :
231 : enum class NlFaultInjectionType : uint8_t
232 : {
233 : SeparateResponseMessages,
234 : SeparateResponseMessagesAndInvertedResponseOrder,
235 : SkipSecondResponse
236 : };
237 :
238 : /**
239 : * @brief Sends InvokeResponseMessages with injected faults for certification testing.
240 : *
241 : * The Test Harness (TH) uses this to simulate various server response behaviors,
242 : * ensuring the Device Under Test (DUT) handles responses per specification.
243 : *
244 : * This function strictly validates the DUT's InvokeRequestMessage against the test plan.
245 : * If deviations occur, the TH terminates with a detailed error message.
246 : *
247 : * @param commandResponder commandResponder that will send the InvokeResponseMessages to the client.
248 : * @param payload Payload of the incoming InvokeRequestMessage from the client.
249 : * @param isTimedInvoke Indicates whether the interaction is timed.
250 : * @param faultType The specific type of fault to inject into the response.
251 : */
252 : // TODO(#30453): After refactoring CommandHandler for better unit testability, create a
253 : // unit test specifically for the fault injection behavior.
254 : void TestOnlyInvokeCommandRequestWithFaultsInjected(CommandHandlerExchangeInterface & commandResponder,
255 : System::PacketBufferHandle && payload, bool isTimedInvoke,
256 : NlFaultInjectionType faultType);
257 : #endif // CHIP_WITH_NLFAULTINJECTION
258 :
259 : /**
260 : * Check whether the InvokeRequest we are handling is targeted to a group.
261 : */
262 238 : bool IsGroupRequest() const { return mGroupRequest; }
263 :
264 : /**
265 : * Check whether the SuppressResponse flag is set.
266 : */
267 55 : bool IsResponseSuppressed() const { return mSuppressResponse; }
268 :
269 : protected:
270 : // Lifetime management for CommandHandler::Handle
271 :
272 : void IncrementHoldOff(Handle * apHandle) override;
273 : void DecrementHoldOff(Handle * apHandle) override;
274 :
275 : private:
276 : friend class TestCommandInteraction;
277 : friend class CommandHandler::Handle;
278 :
279 : enum class State : uint8_t
280 : {
281 : Idle, ///< Default state that the object starts out in, where no work has commenced
282 : NewResponseMessage, ///< mInvokeResponseBuilder is ready, with no responses added.
283 : Preparing, ///< We are prepaing the command or status header.
284 : AddingCommand, ///< In the process of adding a command.
285 : AddedCommand, ///< A command has been completely encoded and is awaiting transmission.
286 : DispatchResponses, ///< The command response(s) are being dispatched.
287 : AwaitingDestruction, ///< The object has completed its work and is awaiting destruction by the application.
288 : };
289 :
290 : /**
291 : * @brief Best effort to add InvokeResponse to InvokeResponseMessage.
292 : *
293 : * Tries to add response using lambda. Upon failure to add response, attempts
294 : * to rollback the InvokeResponseMessage to a known good state. If failure is due
295 : * to insufficient space in the current InvokeResponseMessage:
296 : * - Finalizes the current InvokeResponseMessage.
297 : * - Allocates a new InvokeResponseMessage.
298 : * - Reattempts to add the InvokeResponse to the new InvokeResponseMessage.
299 : *
300 : * @param [in] addResponseFunction A lambda function responsible for adding the
301 : * response to the current InvokeResponseMessage.
302 : */
303 : template <typename Function>
304 88 : CHIP_ERROR TryAddingResponse(Function && addResponseFunction)
305 : {
306 : // Invalidate any existing rollback backups. The addResponseFunction is
307 : // expected to create a new backup during either PrepareInvokeResponseCommand
308 : // or PrepareStatus execution. Direct invocation of
309 : // CreateBackupForResponseRollback is avoided since the buffer used by
310 : // InvokeResponseMessage might not be allocated until a Prepare* function
311 : // is called.
312 88 : mRollbackBackupValid = false;
313 88 : CHIP_ERROR err = addResponseFunction();
314 176 : if (err == CHIP_NO_ERROR)
315 : {
316 83 : return CHIP_NO_ERROR;
317 : }
318 : // The error value of RollbackResponse is not important if it fails, we prioritize
319 : // conveying the error generated by addResponseFunction to the caller.
320 10 : if (RollbackResponse() != CHIP_NO_ERROR)
321 : {
322 0 : return err;
323 : }
324 : // If we failed to add a command due to lack of space in the
325 : // packet, we will make another attempt to add the response using
326 : // an additional InvokeResponseMessage.
327 8 : if (mState != State::AddedCommand || err != CHIP_ERROR_NO_MEMORY)
328 : {
329 2 : return err;
330 : }
331 3 : ReturnErrorOnFailure(FinalizeInvokeResponseMessageAndPrepareNext());
332 3 : err = addResponseFunction();
333 6 : if (err != CHIP_NO_ERROR)
334 : {
335 : // The return value of RollbackResponse is ignored, as we prioritize
336 : // conveying the error generated by addResponseFunction to the
337 : // caller.
338 0 : TEMPORARY_RETURN_IGNORED RollbackResponse();
339 : }
340 3 : return err;
341 : }
342 :
343 : void MoveToState(const State aTargetState);
344 : const char * GetStateStr() const;
345 :
346 : /**
347 : * Create a backup to enable rolling back to the state prior to ResponseData encoding in the event of failure.
348 : */
349 : void CreateBackupForResponseRollback();
350 :
351 : /**
352 : * Rollback the state to before encoding the current ResponseData (before calling PrepareInvokeResponseCommand / PrepareStatus)
353 : *
354 : * Requires CreateBackupForResponseRollback to be called at the start of PrepareInvokeResponseCommand / PrepareStatus
355 : */
356 : CHIP_ERROR RollbackResponse();
357 :
358 : /*
359 : * This forcibly closes the exchange context if a valid one is pointed to. Such a situation does
360 : * not arise during normal message processing flows that all normally call Close() above. This can only
361 : * arise due to application-initiated destruction of the object when this object is handling receiving/sending
362 : * message payloads.
363 : */
364 : void Abort();
365 :
366 : /*
367 : * Allocates a packet buffer used for encoding an invoke response payload.
368 : *
369 : * This can be called multiple times safely, as it will only allocate the buffer once for the lifetime
370 : * of this object.
371 : */
372 : CHIP_ERROR AllocateBuffer();
373 :
374 : /**
375 : * This will add a new CommandStatusIB element into InvokeResponses. It will put the
376 : * aCommandPath into the CommandPath element within CommandStatusIB.
377 : *
378 : * This call will fail if CommandHandler is already in the middle of building a
379 : * CommandStatusIB or CommandDataIB (i.e. something has called Prepare*, without
380 : * calling Finish*), or is already sending InvokeResponseMessage.
381 : *
382 : * Upon success, the caller is expected to call `FinishStatus` once they have encoded
383 : * StatusIB.
384 : *
385 : * @param [in] aCommandPath the concrete path of the command we are responding to.
386 : */
387 : CHIP_ERROR PrepareStatus(const ConcreteCommandPath & aCommandPath);
388 :
389 : /**
390 : * Finishes the CommandStatusIB element within the InvokeResponses.
391 : *
392 : * Caller must have first successfully called `PrepareStatus`.
393 : */
394 : CHIP_ERROR FinishStatus();
395 :
396 : CHIP_ERROR PrepareInvokeResponseCommand(const CommandPathRegistryEntry & apCommandPathRegistryEntry,
397 : const ConcreteCommandPath & aCommandPath, bool aStartDataStruct);
398 :
399 68 : CHIP_ERROR FinalizeLastInvokeResponseMessage() { return FinalizeInvokeResponseMessage(/* aHasMoreChunks = */ false); }
400 :
401 : CHIP_ERROR FinalizeInvokeResponseMessageAndPrepareNext();
402 :
403 : CHIP_ERROR FinalizeInvokeResponseMessage(bool aHasMoreChunks);
404 :
405 : Protocols::InteractionModel::Status ProcessInvokeRequest(System::PacketBufferHandle && payload, bool isTimedInvoke);
406 :
407 : /**
408 : * Called internally to signal the completion of all work on this object, gracefully close the
409 : * exchange (by calling into the base class) and finally, signal to a registerd callback that it's
410 : * safe to release this object.
411 : */
412 : void Close();
413 :
414 : /**
415 : * ProcessCommandDataIB is only called when a unicast invoke command request is received
416 : * It requires the endpointId in its command path to be able to dispatch the command
417 : */
418 : Protocols::InteractionModel::Status ProcessCommandDataIB(CommandDataIB::Parser & aCommandElement);
419 :
420 : /**
421 : * ProcessGroupCommandDataIB is only called when a group invoke command request is received
422 : * It doesn't need the endpointId in it's command path since it uses the GroupId in message metadata to find it
423 : */
424 : Protocols::InteractionModel::Status ProcessGroupCommandDataIB(CommandDataIB::Parser & aCommandElement);
425 :
426 : CHIP_ERROR TryAddStatusInternal(const ConcreteCommandPath & aCommandPath, const StatusIB & aStatus);
427 :
428 : CHIP_ERROR AddStatusInternal(const ConcreteCommandPath & aCommandPath, const StatusIB & aStatus);
429 :
430 : /**
431 : * If this function fails, it may leave our TLV buffer in an inconsistent state.
432 : * Callers should snapshot as needed before calling this function, and roll back
433 : * as needed afterward.
434 : *
435 : * @param [in] aRequestCommandPath the concrete path of the command we are responding to
436 : * @param [in] aResponseCommandId the id of the command to encode
437 : * @param [in] aEncodable the data to encode for the given aResponseCommandId
438 : */
439 : CHIP_ERROR TryAddResponseData(const ConcreteCommandPath & aRequestCommandPath, CommandId aResponseCommandId,
440 : const DataModel::EncodableToTLV & aEncodable);
441 :
442 : void SetExchangeInterface(CommandHandlerExchangeInterface * commandResponder);
443 :
444 189 : bool ResponsesAccepted() { return mpResponder != nullptr && !mGroupRequest && !mSuppressResponse; }
445 :
446 : /**
447 : * Sets the state flag to keep the information that request we are handling is targeted to a group.
448 : */
449 1 : void SetGroupRequest(bool isGroupRequest) { mGroupRequest = isGroupRequest; }
450 :
451 157 : CommandPathRegistry & GetCommandPathRegistry() const { return *mCommandPathRegistry; }
452 :
453 60 : size_t MaxPathsPerInvoke() const { return mMaxPathsPerInvoke; }
454 :
455 : void AddToHandleList(Handle * handle);
456 :
457 : void RemoveFromHandleList(Handle * handle);
458 :
459 : void InvalidateHandles();
460 :
461 3 : bool TestOnlyIsInIdleState() const { return mState == State::Idle; }
462 :
463 : /**
464 : * Returns the ExchangeContext, if one is still available, for use during asynchronous
465 : * command processing. This is a best-effort accessor with no guarantees that
466 : * an ExchangeContext is present once a command has gone async.
467 : *
468 : * This method exists to prevent use of GetExchangeContext() in async code paths and
469 : * must NOT be used by cluster implementations.
470 : */
471 : Messaging::ExchangeContext * TryGetExchangeContextWhenAsync() const override;
472 :
473 : Callback * mpCallback = nullptr;
474 : InvokeResponseMessage::Builder mInvokeResponseBuilder;
475 : TLV::TLVType mDataElementContainerType = TLV::kTLVType_NotSpecified;
476 : size_t mPendingWork = 0;
477 : /* List to store all currently-outstanding Handles for this Command Handler.*/
478 : IntrusiveList<Handle> mpHandleList;
479 :
480 : chip::System::PacketBufferTLVWriter mCommandMessageWriter;
481 : TLV::TLVWriter mBackupWriter;
482 : size_t mMaxPathsPerInvoke = CHIP_CONFIG_MAX_PATHS_PER_INVOKE;
483 : // TODO(#30453): See if we can reduce this size for the default cases
484 : // TODO Allow flexibility in registration.
485 : BasicCommandPathRegistry<CHIP_CONFIG_MAX_PATHS_PER_INVOKE> mBasicCommandPathRegistry;
486 : CommandPathRegistry * mCommandPathRegistry = &mBasicCommandPathRegistry;
487 : std::optional<uint16_t> mRefForResponse;
488 :
489 : CommandHandlerExchangeInterface * mpResponder = nullptr;
490 :
491 : State mState = State::Idle;
492 : State mBackupState;
493 : ScopedChangeOnly<bool> mInternalCallToAddResponseData{ false };
494 : bool mSuppressResponse = false;
495 : bool mTimedRequest = false;
496 : bool mGroupRequest = false;
497 : bool mBufferAllocated = false;
498 : bool mReserveSpaceForMoreChunkMessages = false;
499 : // TODO(#32486): We should introduce breaking change where calls to add CommandData
500 : // need to use AddResponse, and not CommandHandler primitives directly using
501 : // GetCommandDataIBTLVWriter.
502 : bool mRollbackBackupValid = false;
503 : // If mGoneAsync is true, we have finished out initial processing of the
504 : // incoming invoke. After this point, our session could go away at any
505 : // time.
506 : bool mGoneAsync = false;
507 : };
508 : } // namespace app
509 : } // namespace chip
|