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