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 78 : 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 4 : InvokeResponseParameters(const ConcreteCommandPath & aRequestCommandPath) : mRequestCommandPath(aRequestCommandPath) {}
84 :
85 20 : InvokeResponseParameters & SetStartOrEndDataStruct(bool aStartOrEndDataStruct)
86 : {
87 20 : mStartOrEndDataStruct = aStartOrEndDataStruct;
88 20 : 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 : protected:
260 : // Lifetime management for CommandHandler::Handle
261 :
262 : void IncrementHoldOff(Handle * apHandle) override;
263 : void DecrementHoldOff(Handle * apHandle) override;
264 :
265 : private:
266 : friend class TestCommandInteraction;
267 : friend class CommandHandler::Handle;
268 :
269 : enum class State : uint8_t
270 : {
271 : Idle, ///< Default state that the object starts out in, where no work has commenced
272 : NewResponseMessage, ///< mInvokeResponseBuilder is ready, with no responses added.
273 : Preparing, ///< We are prepaing the command or status header.
274 : AddingCommand, ///< In the process of adding a command.
275 : AddedCommand, ///< A command has been completely encoded and is awaiting transmission.
276 : DispatchResponses, ///< The command response(s) are being dispatched.
277 : AwaitingDestruction, ///< The object has completed its work and is awaiting destruction by the application.
278 : };
279 :
280 : /**
281 : * @brief Best effort to add InvokeResponse to InvokeResponseMessage.
282 : *
283 : * Tries to add response using lambda. Upon failure to add response, attempts
284 : * to rollback the InvokeResponseMessage to a known good state. If failure is due
285 : * to insufficient space in the current InvokeResponseMessage:
286 : * - Finalizes the current InvokeResponseMessage.
287 : * - Allocates a new InvokeResponseMessage.
288 : * - Reattempts to add the InvokeResponse to the new InvokeResponseMessage.
289 : *
290 : * @param [in] addResponseFunction A lambda function responsible for adding the
291 : * response to the current InvokeResponseMessage.
292 : */
293 : template <typename Function>
294 57 : CHIP_ERROR TryAddingResponse(Function && addResponseFunction)
295 : {
296 : // Invalidate any existing rollback backups. The addResponseFunction is
297 : // expected to create a new backup during either PrepareInvokeResponseCommand
298 : // or PrepareStatus execution. Direct invocation of
299 : // CreateBackupForResponseRollback is avoided since the buffer used by
300 : // InvokeResponseMessage might not be allocated until a Prepare* function
301 : // is called.
302 57 : mRollbackBackupValid = false;
303 57 : CHIP_ERROR err = addResponseFunction();
304 57 : if (err == CHIP_NO_ERROR)
305 : {
306 52 : return CHIP_NO_ERROR;
307 : }
308 : // The error value of RollbackResponse is not important if it fails, we prioritize
309 : // conveying the error generated by addResponseFunction to the caller.
310 5 : if (RollbackResponse() != CHIP_NO_ERROR)
311 : {
312 0 : return err;
313 : }
314 : // If we failed to add a command due to lack of space in the
315 : // packet, we will make another attempt to add the response using
316 : // an additional InvokeResponseMessage.
317 5 : if (mState != State::AddedCommand || err != CHIP_ERROR_NO_MEMORY)
318 : {
319 2 : return err;
320 : }
321 3 : ReturnErrorOnFailure(FinalizeInvokeResponseMessageAndPrepareNext());
322 3 : err = addResponseFunction();
323 3 : if (err != CHIP_NO_ERROR)
324 : {
325 : // The return value of RollbackResponse is ignored, as we prioritize
326 : // conveying the error generated by addResponseFunction to the
327 : // caller.
328 0 : RollbackResponse();
329 : }
330 3 : return err;
331 : }
332 :
333 : void MoveToState(const State aTargetState);
334 : const char * GetStateStr() const;
335 :
336 : /**
337 : * Create a backup to enable rolling back to the state prior to ResponseData encoding in the event of failure.
338 : */
339 : void CreateBackupForResponseRollback();
340 :
341 : /**
342 : * Rollback the state to before encoding the current ResponseData (before calling PrepareInvokeResponseCommand / PrepareStatus)
343 : *
344 : * Requires CreateBackupForResponseRollback to be called at the start of PrepareInvokeResponseCommand / PrepareStatus
345 : */
346 : CHIP_ERROR RollbackResponse();
347 :
348 : /*
349 : * This forcibly closes the exchange context if a valid one is pointed to. Such a situation does
350 : * not arise during normal message processing flows that all normally call Close() above. This can only
351 : * arise due to application-initiated destruction of the object when this object is handling receiving/sending
352 : * message payloads.
353 : */
354 : void Abort();
355 :
356 : /*
357 : * Allocates a packet buffer used for encoding an invoke response payload.
358 : *
359 : * This can be called multiple times safely, as it will only allocate the buffer once for the lifetime
360 : * of this object.
361 : */
362 : CHIP_ERROR AllocateBuffer();
363 :
364 : /**
365 : * This will add a new CommandStatusIB element into InvokeResponses. It will put the
366 : * aCommandPath into the CommandPath element within CommandStatusIB.
367 : *
368 : * This call will fail if CommandHandler is already in the middle of building a
369 : * CommandStatusIB or CommandDataIB (i.e. something has called Prepare*, without
370 : * calling Finish*), or is already sending InvokeResponseMessage.
371 : *
372 : * Upon success, the caller is expected to call `FinishStatus` once they have encoded
373 : * StatusIB.
374 : *
375 : * @param [in] aCommandPath the concrete path of the command we are responding to.
376 : */
377 : CHIP_ERROR PrepareStatus(const ConcreteCommandPath & aCommandPath);
378 :
379 : /**
380 : * Finishes the CommandStatusIB element within the InvokeResponses.
381 : *
382 : * Caller must have first successfully called `PrepareStatus`.
383 : */
384 : CHIP_ERROR FinishStatus();
385 :
386 : CHIP_ERROR PrepareInvokeResponseCommand(const CommandPathRegistryEntry & apCommandPathRegistryEntry,
387 : const ConcreteCommandPath & aCommandPath, bool aStartDataStruct);
388 :
389 46 : CHIP_ERROR FinalizeLastInvokeResponseMessage() { return FinalizeInvokeResponseMessage(/* aHasMoreChunks = */ false); }
390 :
391 : CHIP_ERROR FinalizeInvokeResponseMessageAndPrepareNext();
392 :
393 : CHIP_ERROR FinalizeInvokeResponseMessage(bool aHasMoreChunks);
394 :
395 : Protocols::InteractionModel::Status ProcessInvokeRequest(System::PacketBufferHandle && payload, bool isTimedInvoke);
396 :
397 : /**
398 : * Called internally to signal the completion of all work on this object, gracefully close the
399 : * exchange (by calling into the base class) and finally, signal to a registerd callback that it's
400 : * safe to release this object.
401 : */
402 : void Close();
403 :
404 : /**
405 : * ProcessCommandDataIB is only called when a unicast invoke command request is received
406 : * It requires the endpointId in its command path to be able to dispatch the command
407 : */
408 : Protocols::InteractionModel::Status ProcessCommandDataIB(CommandDataIB::Parser & aCommandElement);
409 :
410 : /**
411 : * ProcessGroupCommandDataIB is only called when a group invoke command request is received
412 : * It doesn't need the endpointId in it's command path since it uses the GroupId in message metadata to find it
413 : */
414 : Protocols::InteractionModel::Status ProcessGroupCommandDataIB(CommandDataIB::Parser & aCommandElement);
415 :
416 : CHIP_ERROR TryAddStatusInternal(const ConcreteCommandPath & aCommandPath, const StatusIB & aStatus);
417 :
418 : CHIP_ERROR AddStatusInternal(const ConcreteCommandPath & aCommandPath, const StatusIB & aStatus);
419 :
420 : /**
421 : * If this function fails, it may leave our TLV buffer in an inconsistent state.
422 : * Callers should snapshot as needed before calling this function, and roll back
423 : * as needed afterward.
424 : *
425 : * @param [in] aRequestCommandPath the concrete path of the command we are responding to
426 : * @param [in] aResponseCommandId the id of the command to encode
427 : * @param [in] aEncodable the data to encode for the given aResponseCommandId
428 : */
429 : CHIP_ERROR TryAddResponseData(const ConcreteCommandPath & aRequestCommandPath, CommandId aResponseCommandId,
430 : const DataModel::EncodableToTLV & aEncodable);
431 :
432 : void SetExchangeInterface(CommandHandlerExchangeInterface * commandResponder);
433 :
434 : /**
435 : * Check whether the InvokeRequest we are handling is targeted to a group.
436 : */
437 118 : bool IsGroupRequest() { return mGroupRequest; }
438 :
439 129 : bool ResponsesAccepted() { return !(mGroupRequest || mpResponder == nullptr); }
440 :
441 : /**
442 : * Sets the state flag to keep the information that request we are handling is targeted to a group.
443 : */
444 0 : void SetGroupRequest(bool isGroupRequest) { mGroupRequest = isGroupRequest; }
445 :
446 37 : CommandPathRegistry & GetCommandPathRegistry() const { return *mCommandPathRegistry; }
447 :
448 38 : size_t MaxPathsPerInvoke() const { return mMaxPathsPerInvoke; }
449 :
450 : void AddToHandleList(Handle * handle);
451 :
452 : void RemoveFromHandleList(Handle * handle);
453 :
454 : void InvalidateHandles();
455 :
456 : bool TestOnlyIsInIdleState() const { return mState == State::Idle; }
457 :
458 : Callback * mpCallback = nullptr;
459 : InvokeResponseMessage::Builder mInvokeResponseBuilder;
460 : TLV::TLVType mDataElementContainerType = TLV::kTLVType_NotSpecified;
461 : size_t mPendingWork = 0;
462 : /* List to store all currently-outstanding Handles for this Command Handler.*/
463 : IntrusiveList<Handle> mpHandleList;
464 :
465 : chip::System::PacketBufferTLVWriter mCommandMessageWriter;
466 : TLV::TLVWriter mBackupWriter;
467 : size_t mMaxPathsPerInvoke = CHIP_CONFIG_MAX_PATHS_PER_INVOKE;
468 : // TODO(#30453): See if we can reduce this size for the default cases
469 : // TODO Allow flexibility in registration.
470 : BasicCommandPathRegistry<CHIP_CONFIG_MAX_PATHS_PER_INVOKE> mBasicCommandPathRegistry;
471 : CommandPathRegistry * mCommandPathRegistry = &mBasicCommandPathRegistry;
472 : std::optional<uint16_t> mRefForResponse;
473 :
474 : CommandHandlerExchangeInterface * mpResponder = nullptr;
475 :
476 : State mState = State::Idle;
477 : State mBackupState;
478 : ScopedChangeOnly<bool> mInternalCallToAddResponseData{ false };
479 : bool mSuppressResponse = false;
480 : bool mTimedRequest = false;
481 : bool mGroupRequest = false;
482 : bool mBufferAllocated = false;
483 : bool mReserveSpaceForMoreChunkMessages = false;
484 : // TODO(#32486): We should introduce breaking change where calls to add CommandData
485 : // need to use AddResponse, and not CommandHandler primitives directly using
486 : // GetCommandDataIBTLVWriter.
487 : bool mRollbackBackupValid = false;
488 : // If mGoneAsync is true, we have finished out initial processing of the
489 : // incoming invoke. After this point, our session could go away at any
490 : // time.
491 : bool mGoneAsync = false;
492 : };
493 : } // namespace app
494 : } // namespace chip
|