-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
Copy pathCommandHandlerInterface.h
238 lines (215 loc) · 9.62 KB
/
CommandHandlerInterface.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
/*
*
* Copyright (c) 2021 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://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.
*/
#pragma once
#include <app/CommandHandler.h>
#include <app/ConcreteClusterPath.h>
#include <app/ConcreteCommandPath.h>
#include <app/data-model-provider/MetadataList.h> // So we can get filled buffers
#include <app/data-model-provider/MetadataTypes.h>
#include <app/data-model/Decode.h>
#include <app/data-model/List.h> // So we can encode lists
#include <lib/core/DataModelTypes.h>
#include <lib/support/Iterators.h>
namespace chip {
namespace app {
/*
* This interface permits applications to register a server-side command handler
* at run-time for a given cluster. The handler can either be configured to handle all endpoints
* for the given cluster or only handle a specific endpoint.
*
* If a command is not handled through this interface, it will default to invoking the generated DispatchSingleClusterCommand
* instead.
*
*/
class CommandHandlerInterface
{
public:
struct HandlerContext
{
public:
HandlerContext(CommandHandler & commandHandler, const ConcreteCommandPath & requestPath, TLV::TLVReader & aReader) :
mCommandHandler(commandHandler), mRequestPath(requestPath), mPayload(aReader)
{}
void SetCommandHandled() { mCommandHandled = true; }
void SetCommandNotHandled() { mCommandHandled = false; }
/*
* Returns a TLVReader positioned at the TLV struct that contains the payload of the command.
*
* If the reader is requested from the context, then we can assume there is an intention
* to access the payload of this command and consequently, to handle this command.
*
* If this is not true, the application should call SetCommandNotHandled().
*
*/
TLV::TLVReader & GetReader()
{
SetCommandHandled();
return mPayload;
}
CommandHandler & mCommandHandler;
const ConcreteCommandPath & mRequestPath;
TLV::TLVReader & mPayload;
bool mCommandHandled = false;
};
/**
* aEndpointId can be Missing to indicate that this object is meant to be
* used with all endpoints.
*/
CommandHandlerInterface(Optional<EndpointId> aEndpointId, ClusterId aClusterId) :
mEndpointId(aEndpointId), mClusterId(aClusterId)
{}
virtual ~CommandHandlerInterface() {}
/**
* Callback that must be implemented to handle an invoke request.
*
* The callee is required to handle *all* errors that may occur during the handling of this command,
* including errors like those encountered during decode and encode of the payloads as
* well as application-specific errors. As part of handling the error, the callee is required
* to handle generation of an appropriate status response.
*
* The only exception to this rule is if the HandleCommand helper method is used below - it will
* help handle some of these cases (see below).
*
* @param [in] handlerContext Context that encapsulates the current invoke request.
* Handlers are responsible for correctly calling SetCommandHandled()
* on the context if they did handle the command.
*
* This is not necessary if the HandleCommand() method below is invoked.
*/
virtual void InvokeCommand(HandlerContext & handlerContext) = 0;
typedef Loop (*CommandIdCallback)(CommandId id, void * context);
/**
* Function that may be implemented to enumerate accepted (client-to-server)
* commands for the given cluster.
*
* If this function returns CHIP_ERROR_NOT_IMPLEMENTED, the list of accepted
* commands will come from the endpoint metadata for the cluster.
*
* If this function returns any other error, that will be treated as an
* error condition by the caller, and handling will depend on the caller.
*
* Otherwise the list of accepted commands will be the list of values passed
* to the provided callback.
*
* The implementation _must_ pass the provided context to the callback.
*
* If the callback returns Loop::Break, there must be no more calls to it.
* This is used by callbacks that just look for a particular value in the
* list.
*/
virtual CHIP_ERROR EnumerateAcceptedCommands(const ConcreteClusterPath & cluster,
DataModel::ListBuilder<DataModel::AcceptedCommandEntry> & builder)
{
return CHIP_ERROR_NOT_IMPLEMENTED;
}
/**
* Function that may be implemented to enumerate generated (response)
* commands for the given cluster.
*
* If this function returns CHIP_ERROR_NOT_IMPLEMENTED, the list of
* generated commands will come from the endpoint metadata for the cluster.
*
* If this function returns any other error, that will be treated as an
* error condition by the caller, and handling will depend on the caller.
*
* Otherwise the list of generated commands will be the list of values
* passed to the provided callback.
*
* The implementation _must_ pass the provided context to the callback.
*
* If the callback returns Loop::Break, there must be no more calls to it.
* This is used by callbacks that just look for a particular value in the
* list.
*/
virtual CHIP_ERROR EnumerateGeneratedCommands(const ConcreteClusterPath & cluster, DataModel::ListBuilder<CommandId> & builder)
{
return CHIP_ERROR_NOT_IMPLEMENTED;
}
/**
* Mechanism for keeping track of a chain of CommandHandlerInterface.
*/
void SetNext(CommandHandlerInterface * aNext) { mNext = aNext; }
CommandHandlerInterface * GetNext() const { return mNext; }
/**
* Check whether a this CommandHandlerInterface is relevant for a
* particular endpoint+cluster. An CommandHandlerInterface will be used
* for an invoke from a particular cluster only when this function returns
* true.
*/
bool Matches(EndpointId aEndpointId, ClusterId aClusterId) const
{
return (!mEndpointId.HasValue() || mEndpointId.Value() == aEndpointId) && mClusterId == aClusterId;
}
/**
* Check whether an CommandHandlerInterface is relevant for a particular
* specific endpoint. This is used to clean up overrides registered for an
* endpoint that becomes disabled.
*/
bool MatchesEndpoint(EndpointId aEndpointId) const { return mEndpointId.HasValue() && mEndpointId.Value() == aEndpointId; }
/**
* Check whether another CommandHandlerInterface wants to handle the same set of
* commands as we do.
*/
bool Matches(const CommandHandlerInterface & aOther) const
{
return mClusterId == aOther.mClusterId &&
(!mEndpointId.HasValue() || !aOther.mEndpointId.HasValue() || mEndpointId.Value() == aOther.mEndpointId.Value());
}
protected:
/*
* Helper function to automatically de-serialize the data payload into a cluster object
* of type RequestT if the Cluster ID and Command ID in the context match. Upon successful
* de-serialization, the provided function is invoked and passed in a reference to the cluster object.
*
* Any errors encountered in this function prior to calling func result in the automatic generation of a status response.
* If `func` is called, the responsibility for doing so shifts to the callee to handle any further errors that are encountered.
*
* The provided function is expected to have the following signature:
* void Func(HandlerContext &handlerContext, const RequestT &requestPayload);
*/
template <typename RequestT, typename FuncT>
void HandleCommand(HandlerContext & handlerContext, FuncT func)
{
if (!handlerContext.mCommandHandled && (handlerContext.mRequestPath.mClusterId == RequestT::GetClusterId()) &&
(handlerContext.mRequestPath.mCommandId == RequestT::GetCommandId()))
{
RequestT requestPayload;
//
// If the command matches what the caller is looking for, let's mark this as being handled
// even if errors happen after this. This ensures that we don't execute any fall-back strategies
// to handle this command since at this point, the caller is taking responsibility for handling
// the command in its entirety, warts and all.
//
handlerContext.SetCommandHandled();
if (DataModel::Decode(handlerContext.mPayload, requestPayload) != CHIP_NO_ERROR)
{
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath,
Protocols::InteractionModel::Status::InvalidCommand);
return;
}
func(handlerContext, requestPayload);
}
}
Optional<EndpointId> GetEndpointId() { return mEndpointId; }
private:
Optional<EndpointId> mEndpointId;
ClusterId mClusterId;
CommandHandlerInterface * mNext = nullptr;
};
} // namespace app
} // namespace chip