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 28 : void SetNext(CommandHandlerInterface * aNext) { mNext = aNext; }
158 60 : 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 75 : bool Matches(EndpointId aEndpointId, ClusterId aClusterId) const
167 : {
168 75 : 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 10 : 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 25 : bool Matches(const CommandHandlerInterface & aOther) const
183 : {
184 35 : return mClusterId == aOther.mClusterId &&
185 35 : (!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 : Optional<EndpointId> GetEndpointId() { return mEndpointId; }
228 :
229 : private:
230 : Optional<EndpointId> mEndpointId;
231 : ClusterId mClusterId;
232 : CommandHandlerInterface * mNext = nullptr;
233 : };
234 :
235 : } // namespace app
236 : } // namespace chip
|