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