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