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
|