Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2020 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 : #pragma once
19 :
20 : #include <access/SubjectDescriptor.h>
21 : #include <app/ConcreteCommandPath.h>
22 : #include <app/data-model/EncodableToTLV.h>
23 : #include <app/data-model/Encode.h>
24 : #include <lib/core/CHIPCore.h>
25 : #include <lib/support/CodeUtils.h>
26 : #include <lib/support/IntrusiveList.h>
27 : #include <lib/support/logging/CHIPLogging.h>
28 : #include <messaging/ExchangeContext.h>
29 : #include <protocols/interaction_model/StatusCode.h>
30 :
31 : namespace chip {
32 : namespace app {
33 :
34 : /**
35 : * A handler for incoming Invoke interactions.
36 : *
37 : * Allows adding responses to be sent in an InvokeResponse: see the various
38 : * "Add*" methods.
39 : *
40 : * Allows adding the responses asynchronously when using `CommandHandler::Handle`
41 : * (see documentation for `CommandHandler::Handle` for details)
42 : *
43 : * Upgrading notes: this class has moved to an interface from a previous more complex
44 : * implementation. If upgrading code between versions, please see docs/upgrading.md
45 : */
46 : class CommandHandler
47 : {
48 : public:
49 57 : virtual ~CommandHandler() = default;
50 :
51 : /**
52 : * Class that allows asynchronous command processing before sending a
53 : * response. When such processing is desired:
54 : *
55 : * 1) Create a Handle initialized with the CommandHandler that delivered the
56 : * incoming command.
57 : * 2) Ensure the Handle, or some Handle it's moved into via the move
58 : * constructor or move assignment operator, remains alive during the
59 : * course of the asynchronous processing.
60 : * 3) Ensure that the ConcreteCommandPath involved will be known when
61 : * sending the response.
62 : * 4) When ready to send the response:
63 : * * Ensure that no other Matter tasks are running in parallel (e.g. by
64 : * running on the Matter event loop or holding the Matter stack lock).
65 : * * Call Get() to get the CommandHandler.
66 : * * Check that Get() did not return null.
67 : * * Add the response to the CommandHandler via one of the Add* methods.
68 : * * Let the Handle get destroyed, or manually call Handle::Release() if
69 : * destruction of the Handle is not desirable for some reason.
70 : *
71 : * The Invoke Response will not be sent until all outstanding Handles have
72 : * been destroyed or have had Release called.
73 : */
74 : class Handle : public IntrusiveListNodeBase<>
75 : {
76 : public:
77 : Handle() {}
78 : Handle(const Handle & handle) = delete;
79 : Handle(Handle && handle)
80 : {
81 : Init(handle.mpHandler);
82 : handle.Release();
83 : }
84 : Handle(decltype(nullptr)) {}
85 : Handle(CommandHandler * handler);
86 36 : ~Handle() { Release(); }
87 :
88 : Handle & operator=(Handle && handle)
89 : {
90 : Release();
91 : Init(handle.mpHandler);
92 :
93 : handle.Release();
94 : return *this;
95 : }
96 :
97 : Handle & operator=(decltype(nullptr))
98 : {
99 : Release();
100 : return *this;
101 : }
102 :
103 : /**
104 : * Get the CommandHandler object it holds. Get() may return a nullptr if the CommandHandler object it holds is no longer
105 : * valid.
106 : */
107 : CommandHandler * Get();
108 :
109 : void Release();
110 :
111 87 : void Invalidate() { mpHandler = nullptr; }
112 :
113 : private:
114 : void Init(CommandHandler * handler);
115 :
116 : CommandHandler * mpHandler = nullptr;
117 : };
118 :
119 : /**
120 : * Adds the given command status and returns any failures in adding statuses (e.g. out
121 : * of buffer space) to the caller. `context` is an optional (if not nullptr)
122 : * debug string to include in logging.
123 : */
124 : virtual CHIP_ERROR FallibleAddStatus(const ConcreteCommandPath & aRequestCommandPath,
125 : const Protocols::InteractionModel::ClusterStatusCode & aStatus,
126 : const char * context = nullptr) = 0;
127 14 : CHIP_ERROR FallibleAddStatus(const ConcreteCommandPath & aRequestCommandPath, const Protocols::InteractionModel::Status aStatus,
128 : const char * context = nullptr)
129 : {
130 14 : return FallibleAddStatus(aRequestCommandPath, Protocols::InteractionModel::ClusterStatusCode{ aStatus }, context);
131 : }
132 :
133 : /**
134 : * Adds an IM global or Cluster status when the caller is unable to handle any failures. Logging is performed
135 : * and failure to register the status is checked with VerifyOrDie. `context` is an optional (if not nullptr)
136 : * debug string to include in logging.
137 : */
138 : virtual void AddStatus(const ConcreteCommandPath & aRequestCommandPath,
139 : const Protocols::InteractionModel::ClusterStatusCode & aStatus, const char * context = nullptr) = 0;
140 0 : void AddStatus(const ConcreteCommandPath & aRequestCommandPath, const Protocols::InteractionModel::Status aStatus,
141 : const char * context = nullptr)
142 : {
143 0 : AddStatus(aRequestCommandPath, Protocols::InteractionModel::ClusterStatusCode{ aStatus }, context);
144 0 : }
145 :
146 : /**
147 : * Sets the response to indicate Success with a cluster-specific status code `aClusterStatus` included.
148 : *
149 : * NOTE: For regular success, what you want is AddStatus/FailibleAddStatus(aRequestCommandPath,
150 : * InteractionModel::Status::Success).
151 : */
152 0 : virtual CHIP_ERROR AddClusterSpecificSuccess(const ConcreteCommandPath & aRequestCommandPath, ClusterStatus aClusterStatus)
153 : {
154 0 : return FallibleAddStatus(aRequestCommandPath,
155 0 : Protocols::InteractionModel::ClusterStatusCode::ClusterSpecificSuccess(aClusterStatus));
156 : }
157 :
158 : /**
159 : * Sets the response to indicate Failure with a cluster-specific status code `aClusterStatus` included.
160 : */
161 0 : virtual CHIP_ERROR AddClusterSpecificFailure(const ConcreteCommandPath & aRequestCommandPath, ClusterStatus aClusterStatus)
162 : {
163 0 : return FallibleAddStatus(aRequestCommandPath,
164 0 : Protocols::InteractionModel::ClusterStatusCode::ClusterSpecificFailure(aClusterStatus));
165 : }
166 :
167 : /**
168 : * GetAccessingFabricIndex() may only be called during synchronous command
169 : * processing. Anything that runs async (while holding a
170 : * CommandHandler::Handle or equivalent) must not call this method, because
171 : * it will not work right if the session we're using was evicted.
172 : */
173 : virtual FabricIndex GetAccessingFabricIndex() const = 0;
174 :
175 : /**
176 : * API for adding a data response. The `aEncodable` is generally expected to encode
177 : * a ClusterName::Commands::CommandName::Type struct, however any object should work.
178 : *
179 : * @param [in] aRequestCommandPath the concrete path of the command we are
180 : * responding to.
181 : * @param [in] aResponseCommandId the command whose content is being encoded.
182 : * @param [in] aEncodable - an encodable that places the command data structure
183 : * for `aResponseCommandId` into a TLV Writer.
184 : *
185 : * If you have no great way of handling the returned CHIP_ERROR, consider
186 : * using `AddResponse` which will automatically reply with `Failure` in
187 : * case AddResponseData fails.
188 : */
189 : virtual CHIP_ERROR AddResponseData(const ConcreteCommandPath & aRequestCommandPath, CommandId aResponseCommandId,
190 : const DataModel::EncodableToTLV & aEncodable) = 0;
191 :
192 : /**
193 : * Attempts to encode a response to a command.
194 : *
195 : * `aRequestCommandPath` represents the request path (endpoint/cluster/commandid) and the reply
196 : * will preserve the same path and switch the command id to aResponseCommandId.
197 : *
198 : * As this command does not return any error codes, it must try its best to encode the reply
199 : * and if it fails, it MUST encode a `Protocols::InteractionModel::Status::Failure` as a
200 : * reply (i.e. a reply is guaranteed to be sent).
201 : *
202 : * Above is the main difference from AddResponseData: AddResponse will auto-reply with failure while
203 : * AddResponseData allows the caller to try to deal with any CHIP_ERRORs.
204 : */
205 : virtual void AddResponse(const ConcreteCommandPath & aRequestCommandPath, CommandId aResponseCommandId,
206 : const DataModel::EncodableToTLV & aEncodable) = 0;
207 :
208 : /**
209 : * Check whether the InvokeRequest we are handling is a timed invoke.
210 : */
211 : virtual bool IsTimedInvoke() const = 0;
212 :
213 : /**
214 : * @brief Flush acks right away for a slow command
215 : *
216 : * Some commands that do heavy lifting of storage/crypto should
217 : * ack right away to improve reliability and reduce needless retries. This
218 : * method can be manually called in commands that are especially slow to
219 : * immediately schedule an acknowledgement (if needed) since the delayed
220 : * stand-alone ack timer may actually not hit soon enough due to blocking command
221 : * execution.
222 : *
223 : */
224 : virtual void FlushAcksRightAwayOnSlowCommand() = 0;
225 :
226 : virtual Access::SubjectDescriptor GetSubjectDescriptor() const = 0;
227 :
228 : /**
229 : * Gets the inner exchange context object, without ownership.
230 : *
231 : * WARNING: This is dangerous, since it is directly interacting with the
232 : * exchange being managed automatically by mpResponder and
233 : * if not done carefully, may end up with use-after-free errors.
234 : *
235 : * @return The inner exchange context, might be nullptr if no
236 : * exchange context has been assigned or the context
237 : * has been released.
238 : */
239 : virtual Messaging::ExchangeContext * GetExchangeContext() const = 0;
240 :
241 : /**
242 : * API for adding a data response. The template parameter T is generally
243 : * expected to be a ClusterName::Commands::CommandName::Type struct, but any
244 : * object that can be encoded using the DataModel::Encode machinery and
245 : * exposes the right command id will work.
246 : *
247 : * If you have no great way of handling the returned CHIP_ERROR, consider
248 : * using `AddResponse` which will automatically reply with `Failure` in
249 : * case AddResponseData fails.
250 : *
251 : * @param [in] aRequestCommandPath the concrete path of the command we are
252 : * responding to.
253 : *
254 : * The response path will be the same as the request, except the
255 : * reply command ID used will be `CommandData::GetCommandId()` assumed
256 : * to be a member of the templated type
257 : *
258 : * @param [in] aData the data for the response. It is expected to provide
259 : * `GetCommandData` as a STATIC on its type as well as encode the
260 : * correct data structure for building a reply.
261 : */
262 : template <typename CommandData>
263 : CHIP_ERROR AddResponseData(const ConcreteCommandPath & aRequestCommandPath, const CommandData & aData)
264 : {
265 : DataModel::EncodableType<CommandData> encoder(aData);
266 : return AddResponseData(aRequestCommandPath, CommandData::GetCommandId(), encoder);
267 : }
268 :
269 : /**
270 : * API for adding a response. This will try to encode a data response (response command), and if that fails
271 : * it will encode a Protocols::InteractionModel::Status::Failure status response instead.
272 : *
273 : * Above is the main difference from AddResponseData: AddResponse will auto-reply with failure while
274 : * AddResponseData allows the caller to try to deal with any CHIP_ERRORs.
275 : *
276 : * The template parameter T is generally expected to be a ClusterName::Commands::CommandName::Type struct, but any object that
277 : * can be encoded using the DataModel::Encode machinery and exposes the right command id will work.
278 : *
279 : * Since the function will call AddStatus when it fails to encode the data, it cannot send any response when it fails to encode
280 : * a status code since another AddStatus call will also fail. The error from AddStatus will just be logged.
281 : *
282 : * @param [in] aRequestCommandPath the concrete path of the command we are
283 : * responding to.
284 : * @param [in] aData the data for the response.
285 : */
286 : template <typename CommandData>
287 : void AddResponse(const ConcreteCommandPath & aRequestCommandPath, const CommandData & aData)
288 : {
289 : DataModel::EncodableType<CommandData> encodable(aData);
290 : AddResponse(aRequestCommandPath, CommandData::GetCommandId(), encodable);
291 : }
292 :
293 : protected:
294 : /**
295 : * IncrementHoldOff will increase the inner refcount of the CommandHandler.
296 : *
297 : * Users should use CommandHandler::Handle for management the lifespan of the CommandHandler.
298 : * DefRef should be released in reasonable time, and Close() should only be called when the refcount reached 0.
299 : */
300 0 : virtual void IncrementHoldOff(Handle * apHandle) {}
301 :
302 : /**
303 : * DecrementHoldOff is used by CommandHandler::Handle for decreasing the refcount of the CommandHandler.
304 : * When refcount reached 0, CommandHandler will send the response to the peer and shutdown.
305 : */
306 0 : virtual void DecrementHoldOff(Handle * apHandle) {}
307 : };
308 :
309 : } // namespace app
310 : } // namespace chip
|