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