-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
CommandHandler.h
342 lines (300 loc) · 13.4 KB
/
CommandHandler.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
/*
*
* Copyright (c) 2020 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http:https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* This file defines object for a CHIP IM Invoke Command Handler
*
*/
#pragma once
#include <app/ConcreteCommandPath.h>
#include <app/data-model/Encode.h>
#include <lib/core/CHIPCore.h>
#include <lib/core/CHIPTLV.h>
#include <lib/core/CHIPTLVDebug.hpp>
#include <lib/support/BitFlags.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/DLLUtil.h>
#include <lib/support/logging/CHIPLogging.h>
#include <messaging/ExchangeContext.h>
#include <messaging/Flags.h>
#include <protocols/Protocols.h>
#include <protocols/interaction_model/Constants.h>
#include <system/SystemPacketBuffer.h>
#include <system/TLVPacketBufferBackingStore.h>
#include <app/MessageDef/InvokeRequestMessage.h>
#include <app/MessageDef/InvokeResponseMessage.h>
namespace chip {
namespace app {
class CommandHandler
{
public:
/*
* Destructor - as part of destruction, it will abort the exchange context
* if a valid one still exists.
*
* See Abort() for details on when that might occur.
*/
virtual ~CommandHandler() { Abort(); }
class Callback
{
public:
virtual ~Callback() = default;
/*
* Method that signals to a registered callback that this object
* has completed doing useful work and is now safe for release/destruction.
*/
virtual void OnDone(CommandHandler & apCommandObj) = 0;
/*
* Upon processing of a CommandDataIB, this method is invoked to dispatch the command
* to the right server-side handler provided by the application.
*/
virtual void DispatchCommand(CommandHandler & apCommandObj, const ConcreteCommandPath & aCommandPath,
TLV::TLVReader & apPayload) = 0;
/*
* Check to see if a command implementation exists for a specific
* concrete command path. If it does, Success will be returned. If
* not, one of UnsupportedEndpoint, UnsupportedCluster, or
* UnsupportedCommand will be returned, depending on how the command
* fails to exist.
*/
virtual Protocols::InteractionModel::Status CommandExists(const ConcreteCommandPath & aCommandPath) = 0;
};
class Handle
{
public:
Handle() {}
Handle(const Handle & handle) = delete;
Handle(Handle && handle)
{
mpHandler = handle.mpHandler;
mMagic = handle.mMagic;
handle.mpHandler = nullptr;
handle.mMagic = 0;
}
Handle(decltype(nullptr)) {}
Handle(CommandHandler * handle);
~Handle() { Release(); }
Handle & operator=(Handle && handle)
{
Release();
mpHandler = handle.mpHandler;
mMagic = handle.mMagic;
handle.mpHandler = nullptr;
handle.mMagic = 0;
return *this;
}
Handle & operator=(decltype(nullptr))
{
Release();
return *this;
}
/**
* Get the CommandHandler object it holds. Get() may return a nullptr if the CommandHandler object is holds is no longer
* valid.
*/
CommandHandler * Get();
void Release();
private:
CommandHandler * mpHandler = nullptr;
uint32_t mMagic = 0;
};
/*
* Constructor.
*
* The callback passed in has to outlive this CommandHandler object.
*/
CommandHandler(Callback * apCallback);
/*
* Main entrypoint for this class to handle an invoke request.
*
* This function will always call the OnDone function above on the registered callback
* before returning.
*
* isTimedInvoke is true if and only if this is part of a Timed Invoke
* transaction (i.e. was preceded by a Timed Request). If we reach here,
* the timer verification has already been done.
*/
CHIP_ERROR OnInvokeCommandRequest(Messaging::ExchangeContext * ec, const PayloadHeader & payloadHeader,
System::PacketBufferHandle && payload, bool isTimedInvoke);
CHIP_ERROR AddStatus(const ConcreteCommandPath & aCommandPath, const Protocols::InteractionModel::Status aStatus);
CHIP_ERROR AddClusterSpecificSuccess(const ConcreteCommandPath & aCommandPath, ClusterStatus aClusterStatus);
CHIP_ERROR AddClusterSpecificFailure(const ConcreteCommandPath & aCommandPath, ClusterStatus aClusterStatus);
CHIP_ERROR ProcessInvokeRequest(System::PacketBufferHandle && payload, bool isTimedInvoke);
CHIP_ERROR PrepareCommand(const ConcreteCommandPath & aCommandPath, bool aStartDataStruct = true);
CHIP_ERROR FinishCommand(bool aEndDataStruct = true);
CHIP_ERROR PrepareStatus(const ConcreteCommandPath & aCommandPath);
CHIP_ERROR FinishStatus();
TLV::TLVWriter * GetCommandDataIBTLVWriter();
FabricIndex GetAccessingFabricIndex() const;
/**
* API for adding a data response. The template parameter T is generally
* expected to be a ClusterName::Commands::CommandName::Type struct, but any
* object that can be encoded using the DataModel::Encode machinery and
* exposes the right command id will work.
*
* @param [in] aRequestCommandPath the concrete path of the command we are
* responding to.
* @param [in] aData the data for the response.
*/
template <typename CommandData>
CHIP_ERROR AddResponseData(const ConcreteCommandPath & aRequestCommandPath, const CommandData & aData)
{
// TryAddResponseData will ensure we are in the correct state when calling AddResponseData.
CHIP_ERROR err = TryAddResponseData(aRequestCommandPath, aData);
if (err != CHIP_NO_ERROR)
{
// The state guarantees that either we can rollback or we don't have to rollback the buffer, so we don't care about the
// return value of RollbackResponse.
RollbackResponse();
}
return err;
}
/**
* API for adding a response. This will try to encode a data response (response command), and if that fails will encode a a
* Protocols::InteractionModel::Status::Failure status response instead.
*
* The template parameter T is generally expected to be a ClusterName::Commands::CommandName::Type struct, but any object that
* can be encoded using the DataModel::Encode machinery and exposes the right command id will work.
*
* Since the function will call AddStatus when it fails to encode the data, it cannot send any response when it fails to encode
* a status code since another AddStatus call will also fail. The error from AddStatus will just be logged.
*
* @param [in] aRequestCommandPath the concrete path of the command we are
* responding to.
* @param [in] aData the data for the response.
*/
template <typename CommandData>
void AddResponse(const ConcreteCommandPath & aRequestCommandPath, const CommandData & aData)
{
if (CHIP_NO_ERROR != AddResponseData(aRequestCommandPath, aData))
{
CHIP_ERROR err = AddStatus(aRequestCommandPath, Protocols::InteractionModel::Status::Failure);
if (err != CHIP_NO_ERROR)
{
ChipLogError(DataManagement, "Failed to encode status: %" CHIP_ERROR_FORMAT, err.Format());
}
}
}
/**
* Check whether the InvokeRequest we are handling is a timed invoke.
*/
bool IsTimedInvoke() const { return mTimedRequest; }
/**
* Gets the inner exchange context object, without ownership.
*
* @return The inner exchange context, might be nullptr if no
* exchange context has been assigned or the context
* has been released.
*/
Messaging::ExchangeContext * GetExchangeContext() const { return mpExchangeCtx; }
Access::SubjectDescriptor GetSubjectDescriptor() const { return mpExchangeCtx->GetSessionHandle()->GetSubjectDescriptor(); }
private:
friend class TestCommandInteraction;
friend class CommandHandler::Handle;
enum class State
{
Idle, ///< Default state that the object starts out in, where no work has commenced
Preparing, ///< We are prepaing the command or status header.
AddingCommand, ///< In the process of adding a command.
AddedCommand, ///< A command has been completely encoded and is awaiting transmission.
CommandSent, ///< The command has been sent successfully.
AwaitingDestruction, ///< The object has completed its work and is awaiting destruction by the application.
};
void MoveToState(const State aTargetState);
const char * GetStateStr() const;
/**
* Rollback the state to before encoding the current ResponseData (before calling PrepareCommand / PrepareStatus)
*/
CHIP_ERROR RollbackResponse();
/*
* This forcibly closes the exchange context if a valid one is pointed to. Such a situation does
* not arise during normal message processing flows that all normally call Close() above. This can only
* arise due to application-initiated destruction of the object when this object is handling receiving/sending
* message payloads.
*/
void Abort();
/**
* IncrementHoldOff will increase the inner refcount of the CommandHandler.
*
* Users should use CommandHandler::Handle for management the lifespan of the CommandHandler.
* DefRef should be released in reasonable time, and Close() should only be called when the refcount reached 0.
*/
void IncrementHoldOff();
/**
* DecrementHoldOff is used by CommandHandler::Handle for decreasing the refcount of the CommandHandler.
* When refcount reached 0, CommandHandler will send the response to the peer and shutdown.
*/
void DecrementHoldOff();
/*
* Allocates a packet buffer used for encoding an invoke response payload.
*
* This can be called multiple times safely, as it will only allocate the buffer once for the lifetime
* of this object.
*/
CHIP_ERROR AllocateBuffer();
CHIP_ERROR Finalize(System::PacketBufferHandle & commandPacket);
/**
* Called internally to signal the completion of all work on this object, gracefully close the
* exchange (by calling into the base class) and finally, signal to a registerd callback that it's
* safe to release this object.
*/
void Close();
/**
* ProcessCommandDataIB is only called when a unicast invoke command request is received
* It requires the endpointId in its command path to be able to dispatch the command
*/
CHIP_ERROR ProcessCommandDataIB(CommandDataIB::Parser & aCommandElement);
/**
* ProcessGroupCommandDataIB is only called when a group invoke command request is received
* It doesn't need the endpointId in it's command path since it uses the GroupId in message metadata to find it
*/
CHIP_ERROR ProcessGroupCommandDataIB(CommandDataIB::Parser & aCommandElement);
CHIP_ERROR SendCommandResponse();
CHIP_ERROR AddStatusInternal(const ConcreteCommandPath & aCommandPath, const StatusIB & aStatus);
/**
* If this function fails, it may leave our TLV buffer in an inconsistent state. Callers should snapshot as needed before
* calling this function, and roll back as needed afterward.
*
* @param [in] aRequestCommandPath the concrete path of the command we are
* responding to.
* @param [in] aData the data for the response.
*/
template <typename CommandData>
CHIP_ERROR TryAddResponseData(const ConcreteCommandPath & aRequestCommandPath, const CommandData & aData)
{
ConcreteCommandPath path = { aRequestCommandPath.mEndpointId, aRequestCommandPath.mClusterId, CommandData::GetCommandId() };
ReturnErrorOnFailure(PrepareCommand(path, false));
TLV::TLVWriter * writer = GetCommandDataIBTLVWriter();
VerifyOrReturnError(writer != nullptr, CHIP_ERROR_INCORRECT_STATE);
ReturnErrorOnFailure(DataModel::Encode(*writer, TLV::ContextTag(to_underlying(CommandDataIB::Tag::kFields)), aData));
return FinishCommand(/* aEndDataStruct = */ false);
}
Messaging::ExchangeContext * mpExchangeCtx = nullptr;
Callback * mpCallback = nullptr;
InvokeResponseMessage::Builder mInvokeResponseBuilder;
TLV::TLVType mDataElementContainerType = TLV::kTLVType_NotSpecified;
size_t mPendingWork = 0;
bool mSuppressResponse = false;
bool mTimedRequest = false;
State mState = State::Idle;
chip::System::PacketBufferTLVWriter mCommandMessageWriter;
TLV::TLVWriter mBackupWriter;
bool mBufferAllocated = false;
};
} // namespace app
} // namespace chip