Matter SDK Coverage Report
Current view: top level - app - CommandHandlerInterface.h (source / functions) Coverage Total Hit
Test: SHA:eef3dff3495fc72e1a63ed53e147d1a295978be2 Lines: 76.7 % 30 23
Test Date: 2025-08-14 07:13:02 Functions: 45.5 % 22 10

            Line data    Source code
       1              : /*
       2              :  *
       3              :  *    Copyright (c) 2021 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              : 
      19              : #pragma once
      20              : 
      21              : #include <app/CommandHandler.h>
      22              : #include <app/ConcreteClusterPath.h>
      23              : #include <app/ConcreteCommandPath.h>
      24              : #include <app/data-model-provider/MetadataTypes.h>
      25              : #include <app/data-model/Decode.h>
      26              : #include <app/data-model/FabricScoped.h>
      27              : #include <app/data-model/List.h> // So we can encode lists
      28              : #include <lib/core/DataModelTypes.h>
      29              : #include <lib/support/Iterators.h>
      30              : #include <lib/support/ReadOnlyBuffer.h>
      31              : 
      32              : #include <type_traits>
      33              : namespace chip {
      34              : namespace app {
      35              : 
      36              : /*
      37              :  * This interface permits applications to register a server-side command handler
      38              :  * at run-time for a given cluster. The handler can either be configured to handle all endpoints
      39              :  * for the given cluster or only handle a specific endpoint.
      40              :  *
      41              :  * If a command is not handled through this interface, it will default to invoking the generated DispatchSingleClusterCommand
      42              :  * instead.
      43              :  *
      44              :  */
      45              : class CommandHandlerInterface
      46              : {
      47              : public:
      48              :     struct HandlerContext
      49              :     {
      50              :     public:
      51            3 :         HandlerContext(CommandHandler & commandHandler, const ConcreteCommandPath & requestPath, TLV::TLVReader & aReader) :
      52            3 :             mCommandHandler(commandHandler), mRequestPath(requestPath), mPayload(aReader)
      53            3 :         {}
      54              : 
      55            3 :         void SetCommandHandled() { mCommandHandled = true; }
      56            0 :         void SetCommandNotHandled() { mCommandHandled = false; }
      57              : 
      58              :         /*
      59              :          * Returns a TLVReader positioned at the TLV struct that contains the payload of the command.
      60              :          *
      61              :          * If the reader is requested from the context, then we can assume there is an intention
      62              :          * to access the payload of this command and consequently, to handle this command.
      63              :          *
      64              :          * If this is not true, the application should call SetCommandNotHandled().
      65              :          *
      66              :          */
      67              :         TLV::TLVReader & GetReader()
      68              :         {
      69              :             SetCommandHandled();
      70              :             return mPayload;
      71              :         }
      72              : 
      73              :         CommandHandler & mCommandHandler;
      74              :         const ConcreteCommandPath & mRequestPath;
      75              :         TLV::TLVReader & mPayload;
      76              :         bool mCommandHandled = false;
      77              :     };
      78              : 
      79              :     /**
      80              :      * aEndpointId can be Missing to indicate that this object is meant to be
      81              :      * used with all endpoints.
      82              :      */
      83           29 :     CommandHandlerInterface(Optional<EndpointId> aEndpointId, ClusterId aClusterId) :
      84           29 :         mEndpointId(aEndpointId), mClusterId(aClusterId)
      85           29 :     {}
      86              : 
      87           29 :     virtual ~CommandHandlerInterface() {}
      88              : 
      89              :     /**
      90              :      * Callback that must be implemented to handle an invoke request.
      91              :      *
      92              :      * The callee is required to handle *all* errors that may occur during the handling of this command,
      93              :      * including errors like those encountered during decode and encode of the payloads as
      94              :      * well as application-specific errors. As part of handling the error, the callee is required
      95              :      * to handle generation of an appropriate status response.
      96              :      *
      97              :      * The only exception to this rule is if the HandleCommand helper method is used below - it will
      98              :      * help handle some of these cases (see below).
      99              :      *
     100              :      * @param [in] handlerContext Context that encapsulates the current invoke request.
     101              :      *                            Handlers are responsible for correctly calling SetCommandHandled()
     102              :      *                            on the context if they did handle the command.
     103              :      *
     104              :      *                            This is not necessary if the HandleCommand() method below is invoked.
     105              :      */
     106              :     virtual void InvokeCommand(HandlerContext & handlerContext) = 0;
     107              : 
     108              :     typedef Loop (*CommandIdCallback)(CommandId id, void * context);
     109              : 
     110              :     /**
     111              :      * Function that may be implemented to retrieve accepted (client-to-server)
     112              :      * commands for the given cluster.
     113              :      *
     114              :      * If this function returns CHIP_ERROR_NOT_IMPLEMENTED, the list of accepted
     115              :      * commands will come from the endpoint metadata for the cluster.
     116              :      *
     117              :      * Otherwise the list of accepted commands will be added to the builder.
     118              :      */
     119            0 :     virtual CHIP_ERROR RetrieveAcceptedCommands(const ConcreteClusterPath & cluster,
     120              :                                                 ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder)
     121              :     {
     122            0 :         return CHIP_ERROR_NOT_IMPLEMENTED;
     123              :     }
     124              : 
     125              :     /**
     126              :      * Function that may be implemented to enumerate generated (response)
     127              :      * commands for the given cluster.
     128              :      *
     129              :      * If this function returns CHIP_ERROR_NOT_IMPLEMENTED, the list of generated
     130              :      * commands will come from the endpoint metadata for the cluster.
     131              :      *
     132              :      * Otherwise the list of generated commands will be added to the builder.
     133              :      */
     134            0 :     virtual CHIP_ERROR RetrieveGeneratedCommands(const ConcreteClusterPath & cluster, ReadOnlyBufferBuilder<CommandId> & builder)
     135              :     {
     136            0 :         return CHIP_ERROR_NOT_IMPLEMENTED;
     137              :     }
     138              : 
     139              :     /**
     140              :      * Mechanism for keeping track of a chain of CommandHandlerInterface.
     141              :      */
     142           36 :     void SetNext(CommandHandlerInterface * aNext) { mNext = aNext; }
     143           64 :     CommandHandlerInterface * GetNext() const { return mNext; }
     144              : 
     145              :     /**
     146              :      * Check whether a this CommandHandlerInterface is relevant for a
     147              :      * particular endpoint+cluster.  An CommandHandlerInterface will be used
     148              :      * for an invoke from a particular cluster only when this function returns
     149              :      * true.
     150              :      */
     151           71 :     bool Matches(EndpointId aEndpointId, ClusterId aClusterId) const
     152              :     {
     153           71 :         return (!mEndpointId.HasValue() || mEndpointId.Value() == aEndpointId) && mClusterId == aClusterId;
     154              :     }
     155              : 
     156              :     /**
     157              :      * Check whether an CommandHandlerInterface is relevant for a particular
     158              :      * specific endpoint.  This is used to clean up overrides registered for an
     159              :      * endpoint that becomes disabled.
     160              :      */
     161           10 :     bool MatchesEndpoint(EndpointId aEndpointId) const { return mEndpointId.HasValue() && mEndpointId.Value() == aEndpointId; }
     162              : 
     163              :     /**
     164              :      * Check whether another CommandHandlerInterface wants to handle the same set of
     165              :      * commands as we do.
     166              :      */
     167           29 :     bool Matches(const CommandHandlerInterface & aOther) const
     168              :     {
     169           43 :         return mClusterId == aOther.mClusterId &&
     170           43 :             (!mEndpointId.HasValue() || !aOther.mEndpointId.HasValue() || mEndpointId.Value() == aOther.mEndpointId.Value());
     171              :     }
     172              : 
     173              : protected:
     174              :     /*
     175              :      * Helper function to automatically de-serialize the data payload into a cluster object
     176              :      * of type RequestT if the Cluster ID and Command ID in the context match. Upon successful
     177              :      * de-serialization, the provided function is invoked and passed in a reference to the cluster object.
     178              :      *
     179              :      * Any errors encountered in this function prior to calling func result in the automatic generation of a status response.
     180              :      * If `func` is called, the responsibility for doing so shifts to the callee to handle any further errors that are encountered.
     181              :      *
     182              :      * The provided function is expected to have the following signature:
     183              :      *  void Func(HandlerContext &handlerContext, const RequestT &requestPayload);
     184              :      */
     185              :     template <typename RequestT, typename FuncT,
     186              :               typename std::enable_if_t<!DataModel::IsFabricScoped<RequestT>::value, bool> = true>
     187            3 :     void HandleCommand(HandlerContext & handlerContext, FuncT func)
     188              :     {
     189            6 :         if (!handlerContext.mCommandHandled && (handlerContext.mRequestPath.mClusterId == RequestT::GetClusterId()) &&
     190            3 :             (handlerContext.mRequestPath.mCommandId == RequestT::GetCommandId()))
     191              :         {
     192            3 :             RequestT requestPayload;
     193              : 
     194              :             //
     195              :             // If the command matches what the caller is looking for, let's mark this as being handled
     196              :             // even if errors happen after this. This ensures that we don't execute any fall-back strategies
     197              :             // to handle this command since at this point, the caller is taking responsibility for handling
     198              :             // the command in its entirety, warts and all.
     199              :             //
     200            3 :             handlerContext.SetCommandHandled();
     201              : 
     202            3 :             if (DataModel::Decode(handlerContext.mPayload, requestPayload) != CHIP_NO_ERROR)
     203              :             {
     204            0 :                 handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath,
     205              :                                                          Protocols::InteractionModel::Status::InvalidCommand);
     206            0 :                 return;
     207              :             }
     208              : 
     209            3 :             func(handlerContext, requestPayload);
     210              :         }
     211              :     }
     212              : 
     213              :     /*
     214              :      * Helper function to automatically de-serialize the data payload into a cluster object
     215              :      * of type RequestT if the Cluster ID and Command ID in the context match. Upon successful
     216              :      * de-serialization, the provided function is invoked and passed in a reference to the cluster object.
     217              :      *
     218              :      * Any errors encountered in this function prior to calling func result in the automatic generation of a status response.
     219              :      * If `func` is called, the responsibility for doing so shifts to the callee to handle any further errors that are encountered.
     220              :      *
     221              :      * The provided function is expected to have the following signature:
     222              :      *  void Func(HandlerContext &handlerContext, const RequestT &requestPayload);
     223              :      */
     224              :     template <typename RequestT, typename FuncT, typename std::enable_if_t<DataModel::IsFabricScoped<RequestT>::value, bool> = true>
     225              :     void HandleCommand(HandlerContext & handlerContext, FuncT func)
     226              :     {
     227              :         if (!handlerContext.mCommandHandled && (handlerContext.mRequestPath.mClusterId == RequestT::GetClusterId()) &&
     228              :             (handlerContext.mRequestPath.mCommandId == RequestT::GetCommandId()))
     229              :         {
     230              :             RequestT requestPayload;
     231              : 
     232              :             //
     233              :             // If the command matches what the caller is looking for, let's mark this as being handled
     234              :             // even if errors happen after this. This ensures that we don't execute any fall-back strategies
     235              :             // to handle this command since at this point, the caller is taking responsibility for handling
     236              :             // the command in its entirety, warts and all.
     237              :             //
     238              :             handlerContext.SetCommandHandled();
     239              : 
     240              :             if (requestPayload.Decode(handlerContext.mPayload, handlerContext.mCommandHandler.GetAccessingFabricIndex()) !=
     241              :                 CHIP_NO_ERROR)
     242              :             {
     243              :                 handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath,
     244              :                                                          Protocols::InteractionModel::Status::InvalidCommand);
     245              :                 return;
     246              :             }
     247              : 
     248              :             func(handlerContext, requestPayload);
     249              :         }
     250              :     }
     251              : 
     252              :     Optional<EndpointId> GetEndpointId() { return mEndpointId; }
     253              : 
     254              : private:
     255              :     Optional<EndpointId> mEndpointId;
     256              :     ClusterId mClusterId;
     257              :     CommandHandlerInterface * mNext = nullptr;
     258              : };
     259              : 
     260              : } // namespace app
     261              : } // namespace chip
        

Generated by: LCOV version 2.0-1