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