Matter SDK Coverage Report
Current view: top level - app - CommandHandler.h (source / functions) Coverage Total Hit
Test: SHA:5853f10e345717417494f970a7d13b422d94af51 Lines: 31.2 % 16 5
Test Date: 2025-06-30 07:09:23 Functions: 40.0 % 10 4

            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              : #pragma once
      19              : 
      20              : #include <access/SubjectDescriptor.h>
      21              : #include <app/ConcreteCommandPath.h>
      22              : #include <app/data-model/EncodableToTLV.h>
      23              : #include <app/data-model/Encode.h>
      24              : #include <app/data-model/FabricScoped.h>
      25              : #include <lib/core/CHIPCore.h>
      26              : #include <lib/support/CodeUtils.h>
      27              : #include <lib/support/IntrusiveList.h>
      28              : #include <lib/support/logging/CHIPLogging.h>
      29              : #include <messaging/ExchangeContext.h>
      30              : #include <protocols/interaction_model/StatusCode.h>
      31              : 
      32              : namespace chip {
      33              : namespace app {
      34              : 
      35              : /**
      36              :  *  A handler for incoming Invoke interactions.  This handles incoming Invoke
      37              :  *  Request messages and generates Invoke Response messages.
      38              :  *
      39              :  *  Allows adding responses (status, or server to client command) to be sent in
      40              :  *  the Invoke Response message: see the various "Add*" methods.
      41              :  *
      42              :  *  Allows adding the responses asynchronously when using `CommandHandler::Handle`
      43              :  *  (see documentation for `CommandHandler::Handle` for details)
      44              :  *
      45              :  *  Upgrading notes: this class has moved to an interface from a previous more complex
      46              :  *  implementation. If upgrading code between versions, please see docs/upgrading.md
      47              :  */
      48              : class CommandHandler
      49              : {
      50              : public:
      51           59 :     virtual ~CommandHandler() = default;
      52              : 
      53              :     /**
      54              :      * Class that allows asynchronous command processing before sending a
      55              :      * response.  When such processing is desired:
      56              :      *
      57              :      * 1) Create a Handle initialized with the CommandHandler that delivered the
      58              :      *    incoming command.
      59              :      * 2) Ensure the Handle, or some Handle it's moved into via the move
      60              :      *    constructor or move assignment operator, remains alive during the
      61              :      *    course of the asynchronous processing.
      62              :      * 3) Ensure that the ConcreteCommandPath involved will be known when
      63              :      *    sending the response.
      64              :      * 4) When ready to send the response:
      65              :      *    * Ensure that no other Matter tasks are running in parallel (e.g. by
      66              :      *      running on the Matter event loop or holding the Matter stack lock).
      67              :      *    * Call Get() to get the CommandHandler.
      68              :      *    * Check that Get() did not return null.
      69              :      *    * Add the response to the CommandHandler via one of the Add* methods.
      70              :      *    * Let the Handle get destroyed, or manually call Handle::Release() if
      71              :      *      destruction of the Handle is not desirable for some reason.
      72              :      *
      73              :      * The Invoke Response will not be sent until all outstanding Handles have
      74              :      * been destroyed or have had Release called.
      75              :      */
      76              :     class Handle : public IntrusiveListNodeBase<>
      77              :     {
      78              :     public:
      79              :         Handle() {}
      80              :         Handle(const Handle & handle) = delete;
      81              :         Handle(Handle && handle)
      82              :         {
      83              :             Init(handle.mpHandler);
      84              :             handle.Release();
      85              :         }
      86              :         Handle(decltype(nullptr)) {}
      87              :         Handle(CommandHandler * handler);
      88           36 :         ~Handle() { Release(); }
      89              : 
      90              :         Handle & operator=(Handle && handle)
      91              :         {
      92              :             Release();
      93              :             Init(handle.mpHandler);
      94              : 
      95              :             handle.Release();
      96              :             return *this;
      97              :         }
      98              : 
      99              :         Handle & operator=(decltype(nullptr))
     100              :         {
     101              :             Release();
     102              :             return *this;
     103              :         }
     104              : 
     105              :         /**
     106              :          * Get the CommandHandler object it holds. Get() may return a nullptr if the CommandHandler object it holds is no longer
     107              :          * valid.
     108              :          */
     109              :         CommandHandler * Get();
     110              : 
     111              :         bool IsValid() const { return mpHandler != nullptr; }
     112              : 
     113              :         void Release();
     114              : 
     115           91 :         void Invalidate() { mpHandler = nullptr; }
     116              : 
     117              :     private:
     118              :         void Init(CommandHandler * handler);
     119              : 
     120              :         CommandHandler * mpHandler = nullptr;
     121              :     };
     122              : 
     123              :     /**
     124              :      * Adds the given command status and returns any failures in adding statuses (e.g. out
     125              :      * of buffer space) to the caller. `context` is an optional (if not nullptr)
     126              :      * debug string to include in logging.
     127              :      */
     128              :     virtual CHIP_ERROR FallibleAddStatus(const ConcreteCommandPath & aRequestCommandPath,
     129              :                                          const Protocols::InteractionModel::ClusterStatusCode & aStatus,
     130              :                                          const char * context = nullptr) = 0;
     131           14 :     CHIP_ERROR FallibleAddStatus(const ConcreteCommandPath & aRequestCommandPath, const Protocols::InteractionModel::Status aStatus,
     132              :                                  const char * context = nullptr)
     133              :     {
     134           14 :         return FallibleAddStatus(aRequestCommandPath, Protocols::InteractionModel::ClusterStatusCode{ aStatus }, context);
     135              :     }
     136              : 
     137              :     /**
     138              :      * Adds an IM global or Cluster status when the caller is unable to handle any failures. Logging is performed
     139              :      * and failure to register the status is checked with VerifyOrDie. `context` is an optional (if not nullptr)
     140              :      * debug string to include in logging.
     141              :      */
     142              :     virtual void AddStatus(const ConcreteCommandPath & aRequestCommandPath,
     143              :                            const Protocols::InteractionModel::ClusterStatusCode & aStatus, const char * context = nullptr) = 0;
     144            0 :     void AddStatus(const ConcreteCommandPath & aRequestCommandPath, const Protocols::InteractionModel::Status aStatus,
     145              :                    const char * context = nullptr)
     146              :     {
     147            0 :         AddStatus(aRequestCommandPath, Protocols::InteractionModel::ClusterStatusCode{ aStatus }, context);
     148            0 :     }
     149              : 
     150              :     /**
     151              :      * Sets the response to indicate Success with a cluster-specific status code `aClusterStatus` included.
     152              :      *
     153              :      * NOTE: For regular success, what you want is AddStatus/FailibleAddStatus(aRequestCommandPath,
     154              :      * InteractionModel::Status::Success).
     155              :      */
     156            0 :     virtual CHIP_ERROR AddClusterSpecificSuccess(const ConcreteCommandPath & aRequestCommandPath, ClusterStatus aClusterStatus)
     157              :     {
     158            0 :         return FallibleAddStatus(aRequestCommandPath,
     159            0 :                                  Protocols::InteractionModel::ClusterStatusCode::ClusterSpecificSuccess(aClusterStatus));
     160              :     }
     161              : 
     162              :     /**
     163              :      * Sets the response to indicate Failure with a cluster-specific status code `aClusterStatus` included.
     164              :      */
     165            0 :     virtual CHIP_ERROR AddClusterSpecificFailure(const ConcreteCommandPath & aRequestCommandPath, ClusterStatus aClusterStatus)
     166              :     {
     167            0 :         return FallibleAddStatus(aRequestCommandPath,
     168            0 :                                  Protocols::InteractionModel::ClusterStatusCode::ClusterSpecificFailure(aClusterStatus));
     169              :     }
     170              : 
     171              :     /**
     172              :      * GetAccessingFabricIndex() may only be called during synchronous command
     173              :      * processing.  Anything that runs async (while holding a
     174              :      * CommandHandler::Handle or equivalent) must not call this method, because
     175              :      * it will not work right if the session we're using was evicted.
     176              :      */
     177              :     virtual FabricIndex GetAccessingFabricIndex() const = 0;
     178              : 
     179              :     /**
     180              :      * API for adding a data response.  The `aEncodable` is generally expected to encode
     181              :      * a ClusterName::Commands::CommandName::Type struct, however any object should work.
     182              :      *
     183              :      * @param [in] aRequestCommandPath the concrete path of the command we are
     184              :      *             responding to.
     185              :      * @param [in] aResponseCommandId the command whose content is being encoded.
     186              :      * @param [in] aEncodable - an encodable that places the command data structure
     187              :      *             for `aResponseCommandId` into a TLV Writer.
     188              :      *
     189              :      * If you have no great way of handling the returned CHIP_ERROR, consider
     190              :      * using `AddResponse` which will automatically reply with `Failure` in
     191              :      * case AddResponseData fails.
     192              :      */
     193              :     virtual CHIP_ERROR AddResponseData(const ConcreteCommandPath & aRequestCommandPath, CommandId aResponseCommandId,
     194              :                                        const DataModel::EncodableToTLV & aEncodable) = 0;
     195              : 
     196              :     /**
     197              :      * Attempts to encode a response to a command.
     198              :      *
     199              :      * `aRequestCommandPath` represents the request path (endpoint/cluster/commandid) and the reply
     200              :      * will preserve the same path and switch the command id to aResponseCommandId.
     201              :      *
     202              :      * As this command does not return any error codes, it must try its best to encode the reply
     203              :      * and if it fails, it MUST encode a `Protocols::InteractionModel::Status::Failure` as a
     204              :      * reply (i.e. a reply is guaranteed to be sent).
     205              :      *
     206              :      * Above is the main difference from AddResponseData: AddResponse will auto-reply with failure while
     207              :      * AddResponseData allows the caller to try to deal with any CHIP_ERRORs.
     208              :      */
     209              :     virtual void AddResponse(const ConcreteCommandPath & aRequestCommandPath, CommandId aResponseCommandId,
     210              :                              const DataModel::EncodableToTLV & aEncodable) = 0;
     211              : 
     212              :     /**
     213              :      * Check whether the InvokeRequest we are handling is a timed invoke.
     214              :      */
     215              :     virtual bool IsTimedInvoke() const = 0;
     216              : 
     217              :     /**
     218              :      * @brief Flush acks right away for a slow command
     219              :      *
     220              :      * Some commands that do heavy lifting of storage/crypto should
     221              :      * ack right away to improve reliability and reduce needless retries. This
     222              :      * method can be manually called in commands that are especially slow to
     223              :      * immediately schedule an acknowledgement (if needed) since the delayed
     224              :      * stand-alone ack timer may actually not hit soon enough due to blocking command
     225              :      * execution.
     226              :      *
     227              :      */
     228              :     virtual void FlushAcksRightAwayOnSlowCommand() = 0;
     229              : 
     230              :     virtual Access::SubjectDescriptor GetSubjectDescriptor() const = 0;
     231              : 
     232              :     /**
     233              :      * Gets the inner exchange context object, without ownership.
     234              :      *
     235              :      * WARNING: This is dangerous, since it is directly interacting with the
     236              :      *          exchange being managed automatically by mpResponder and
     237              :      *          if not done carefully, may end up with use-after-free errors.
     238              :      *
     239              :      * @return The inner exchange context, might be nullptr if no
     240              :      *         exchange context has been assigned or the context
     241              :      *         has been released.
     242              :      */
     243              :     virtual Messaging::ExchangeContext * GetExchangeContext() const = 0;
     244              : 
     245              :     /**
     246              :      * API for adding a data response.  The template parameter T is generally
     247              :      * expected to be a ClusterName::Commands::CommandName::Type struct, but any
     248              :      * object that can be encoded using the DataModel::Encode machinery and
     249              :      * exposes the right command id will work.
     250              :      *
     251              :      * If you have no great way of handling the returned CHIP_ERROR, consider
     252              :      * using `AddResponse` which will automatically reply with `Failure` in
     253              :      * case AddResponseData fails.
     254              :      *
     255              :      * @param [in] aRequestCommandPath the concrete path of the command we are
     256              :      *             responding to.
     257              :      *
     258              :      *             The response path will be the same as the request, except the
     259              :      *             reply command ID used will be `CommandData::GetCommandId()` assumed
     260              :      *             to be a member of the templated type
     261              :      *
     262              :      * @param [in] aData the data for the response. It is expected to provide
     263              :      *             `GetCommandData` as a STATIC on its type as well as encode the
     264              :      *             correct data structure for building a reply.
     265              :      */
     266              :     template <typename CommandData>
     267              :     CHIP_ERROR AddResponseData(const ConcreteCommandPath & aRequestCommandPath, const CommandData & aData)
     268              :     {
     269              :         EncodableResponseCommandPayload<CommandData> encoder(aData);
     270              :         return AddResponseData(aRequestCommandPath, CommandData::GetCommandId(), encoder);
     271              :     }
     272              : 
     273              :     /**
     274              :      * API for adding a response.  This will try to encode a data response (response command), and if that fails
     275              :      * it will encode a Protocols::InteractionModel::Status::Failure status response instead.
     276              :      *
     277              :      * Above is the main difference from AddResponseData: AddResponse will auto-reply with failure while
     278              :      * AddResponseData allows the caller to try to deal with any CHIP_ERRORs.
     279              :      *
     280              :      * The template parameter T is generally expected to be a ClusterName::Commands::CommandName::Type struct, but any object that
     281              :      * can be encoded using the DataModel::Encode machinery and exposes the right command id will work.
     282              :      *
     283              :      * Since the function will call AddStatus when it fails to encode the data, it cannot send any response when it fails to encode
     284              :      * a status code since another AddStatus call will also fail. The error from AddStatus will just be logged.
     285              :      *
     286              :      * @param [in] aRequestCommandPath the concrete path of the command we are
     287              :      *             responding to.
     288              :      * @param [in] aData the data for the response.
     289              :      */
     290              :     template <typename CommandData>
     291              :     void AddResponse(const ConcreteCommandPath & aRequestCommandPath, const CommandData & aData)
     292              :     {
     293              :         EncodableResponseCommandPayload<CommandData> encodable(aData);
     294              :         AddResponse(aRequestCommandPath, CommandData::GetCommandId(), encodable);
     295              :     }
     296              : 
     297              : protected:
     298              :     // Encoding a response command payload requires a fabric index, in general,
     299              :     // because any fabric-scoped fields in the payload need it to deal with
     300              :     // their fabric-sensitive fields.
     301              :     template <typename CommandData>
     302              :     class EncodableResponseCommandPayload : public DataModel::EncodableToTLV
     303              :     {
     304              :     public:
     305              :         EncodableResponseCommandPayload(const CommandData & value) : mValue(value) {}
     306              : 
     307              :         CHIP_ERROR EncodeTo(DataModel::FabricAwareTLVWriter & writer, TLV::Tag tag) const final
     308              :         {
     309              :             return DataModel::EncodeResponseCommandPayload(writer, tag, mValue);
     310              :         }
     311              : 
     312              :         CHIP_ERROR EncodeTo(TLV::TLVWriter & writer, TLV::Tag tag) const final
     313              :         {
     314              :             // Not used, keep it as small as we can.
     315              :             return CHIP_ERROR_INCORRECT_STATE;
     316              :         }
     317              : 
     318              :     private:
     319              :         const CommandData & mValue;
     320              :     };
     321              : 
     322              :     /**
     323              :      * IncrementHoldOff will increase the inner refcount of the CommandHandler.
     324              :      *
     325              :      * Users should use CommandHandler::Handle for management the lifespan of the CommandHandler.
     326              :      * DefRef should be released in reasonable time, and Close() should only be called when the refcount reached 0.
     327              :      */
     328            0 :     virtual void IncrementHoldOff(Handle * apHandle) {}
     329              : 
     330              :     /**
     331              :      * DecrementHoldOff is used by CommandHandler::Handle for decreasing the refcount of the CommandHandler.
     332              :      * When refcount reached 0, CommandHandler will send the response to the peer and shutdown.
     333              :      */
     334            0 :     virtual void DecrementHoldOff(Handle * apHandle) {}
     335              : };
     336              : 
     337              : } // namespace app
     338              : } // namespace chip
        

Generated by: LCOV version 2.0-1