Line data Source code
1 : /*
2 : * Copyright (c) 2024 Project CHIP Authors
3 : * All rights reserved.
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 : #pragma once
18 :
19 : #include <app/data-model-provider/Provider.h>
20 :
21 : #include <app/CommandHandlerInterface.h>
22 : #include <app/ConcreteCommandPath.h>
23 : #include <app/data-model-provider/ActionReturnStatus.h>
24 : #include <app/util/af-types.h>
25 : #include <lib/core/CHIPPersistentStorageDelegate.h>
26 :
27 : namespace chip {
28 : namespace app {
29 :
30 : namespace detail {
31 :
32 : /// Handles going through callback-based enumeration of generated/accepted commands
33 : /// for CommandHandlerInterface based items.
34 : ///
35 : /// Offers the ability to focus on some operation for finding a given
36 : /// command id:
37 : /// - FindFirst will return the first found element
38 : /// - FindExact finds the element with the given id
39 : /// - FindNext finds the element following the given id
40 : class EnumeratorCommandFinder
41 : {
42 : public:
43 : using HandlerCallbackFunction = CHIP_ERROR (CommandHandlerInterface::*)(const ConcreteClusterPath &,
44 : CommandHandlerInterface::CommandIdCallback, void *);
45 :
46 : enum class Operation
47 : {
48 : kFindFirst, // Find the first value in the list
49 : kFindExact, // Find the given value
50 : kFindNext // Find the value AFTER this value
51 : };
52 :
53 178 : EnumeratorCommandFinder(HandlerCallbackFunction callback) :
54 178 : mCallback(callback), mOperation(Operation::kFindFirst), mTarget(kInvalidCommandId)
55 178 : {}
56 :
57 : /// Find the given command ID that matches the given operation/path.
58 : ///
59 : /// If operation is kFindFirst, then path commandID is ignored. Otherwise it is used as a key to
60 : /// kFindExact or kFindNext.
61 : ///
62 : /// Returns:
63 : /// - std::nullopt if no command found using the command handler interface
64 : /// - kInvalidCommandId if the find failed (but command handler interface does provide a list)
65 : /// - valid id if command handler interface usage succeeds
66 : std::optional<CommandId> FindCommandId(Operation operation, const ConcreteCommandPath & path);
67 :
68 : private:
69 : HandlerCallbackFunction mCallback;
70 : Operation mOperation;
71 : CommandId mTarget;
72 : std::optional<CommandId> mFound = std::nullopt;
73 :
74 : Loop HandlerCallback(CommandId id);
75 :
76 18 : static Loop HandlerCallbackFn(CommandId id, void * context)
77 : {
78 18 : auto self = static_cast<EnumeratorCommandFinder *>(context);
79 18 : return self->HandlerCallback(id);
80 : }
81 : };
82 :
83 : } // namespace detail
84 :
85 : /// An implementation of `InteractionModel::Model` that relies on code-generation
86 : /// via zap/ember.
87 : ///
88 : /// The Ember framework uses generated files (like endpoint-config.h and various
89 : /// other generated metadata) to provide a cluster model.
90 : ///
91 : /// This class will use global functions generally residing in `app/util`
92 : /// as well as application-specific overrides to provide data model functionality.
93 : ///
94 : /// Given that this relies on global data at link time, there generally can be
95 : /// only one CodegenDataModelProvider per application (you can create more instances,
96 : /// however they would share the exact same underlying data and storage).
97 : class CodegenDataModelProvider : public DataModel::Provider
98 : {
99 : private:
100 : /// Ember commands are stored as a `CommandId *` pointer that is either null (i.e. no commands)
101 : /// or is terminated with 0xFFFF_FFFF aka kInvalidCommandId
102 : ///
103 : /// Since iterator implementations in the data model use Next(before_path) calls, iterating
104 : /// such lists from the beginning would be very inefficient as O(n^2).
105 : ///
106 : /// This class maintains a cached position inside such iteration, such that `Next` calls
107 : /// can be faster.
108 : class EmberCommandListIterator
109 : {
110 : private:
111 : const CommandId * mCurrentList = nullptr;
112 : const CommandId * mCurrentHint = nullptr; // Invariant: mCurrentHint is INSIDE mCurrentList
113 : public:
114 : EmberCommandListIterator() = default;
115 :
116 : /// Returns the first command in the given list (or nullopt if list is null or starts with 0xFFFFFFF)
117 : std::optional<CommandId> First(const CommandId * list);
118 :
119 : /// Returns the command after `previousId` in the given list
120 : std::optional<CommandId> Next(const CommandId * list, CommandId previousId);
121 :
122 : /// Checks if the given command id exists in the given list
123 : bool Exists(const CommandId * list, CommandId toCheck);
124 :
125 318 : void Reset() { mCurrentList = mCurrentHint = nullptr; }
126 : };
127 :
128 : public:
129 : /// clears out internal caching. Especially useful in unit tests,
130 : /// where path caching does not really apply (the same path may result in different outcomes)
131 159 : void Reset()
132 : {
133 159 : mAcceptedCommandsIterator.Reset();
134 159 : mGeneratedCommandsIterator.Reset();
135 159 : mPreviouslyFoundCluster = std::nullopt;
136 159 : }
137 :
138 1 : void SetPersistentStorageDelegate(PersistentStorageDelegate * delegate) { mPersistentStorageDelegate = delegate; }
139 : PersistentStorageDelegate * GetPersistentStorageDelegate() { return mPersistentStorageDelegate; }
140 :
141 : /// Generic model implementations
142 159 : CHIP_ERROR Shutdown() override
143 : {
144 159 : Reset();
145 159 : return CHIP_NO_ERROR;
146 : }
147 :
148 : CHIP_ERROR Startup(DataModel::InteractionModelContext context) override;
149 :
150 : DataModel::ActionReturnStatus ReadAttribute(const DataModel::ReadAttributeRequest & request,
151 : AttributeValueEncoder & encoder) override;
152 : DataModel::ActionReturnStatus WriteAttribute(const DataModel::WriteAttributeRequest & request,
153 : AttributeValueDecoder & decoder) override;
154 : std::optional<DataModel::ActionReturnStatus> Invoke(const DataModel::InvokeRequest & request, TLV::TLVReader & input_arguments,
155 : CommandHandler * handler) override;
156 :
157 : /// attribute tree iteration
158 : DataModel::EndpointEntry FirstEndpoint() override;
159 : DataModel::EndpointEntry NextEndpoint(EndpointId before) override;
160 : std::optional<DataModel::EndpointInfo> GetEndpointInfo(EndpointId endpoint) override;
161 : bool EndpointExists(EndpointId endpoint) override;
162 :
163 : std::optional<DataModel::DeviceTypeEntry> FirstDeviceType(EndpointId endpoint) override;
164 : std::optional<DataModel::DeviceTypeEntry> NextDeviceType(EndpointId endpoint,
165 : const DataModel::DeviceTypeEntry & previous) override;
166 :
167 : std::optional<SemanticTag> GetFirstSemanticTag(EndpointId endpoint) override;
168 : std::optional<SemanticTag> GetNextSemanticTag(EndpointId endpoint, const SemanticTag & previous) override;
169 :
170 : DataModel::ClusterEntry FirstServerCluster(EndpointId endpoint) override;
171 : DataModel::ClusterEntry NextServerCluster(const ConcreteClusterPath & before) override;
172 : std::optional<DataModel::ClusterInfo> GetServerClusterInfo(const ConcreteClusterPath & path) override;
173 :
174 : ConcreteClusterPath FirstClientCluster(EndpointId endpoint) override;
175 : ConcreteClusterPath NextClientCluster(const ConcreteClusterPath & before) override;
176 :
177 : DataModel::AttributeEntry FirstAttribute(const ConcreteClusterPath & cluster) override;
178 : DataModel::AttributeEntry NextAttribute(const ConcreteAttributePath & before) override;
179 : std::optional<DataModel::AttributeInfo> GetAttributeInfo(const ConcreteAttributePath & path) override;
180 :
181 : DataModel::CommandEntry FirstAcceptedCommand(const ConcreteClusterPath & cluster) override;
182 : DataModel::CommandEntry NextAcceptedCommand(const ConcreteCommandPath & before) override;
183 : std::optional<DataModel::CommandInfo> GetAcceptedCommandInfo(const ConcreteCommandPath & path) override;
184 :
185 : ConcreteCommandPath FirstGeneratedCommand(const ConcreteClusterPath & cluster) override;
186 : ConcreteCommandPath NextGeneratedCommand(const ConcreteCommandPath & before) override;
187 :
188 : void Temporary_ReportAttributeChanged(const AttributePathParams & path) override;
189 :
190 : private:
191 : // Iteration is often done in a tight loop going through all values.
192 : // To avoid N^2 iterations, cache a hint of where something is positioned
193 : uint16_t mEndpointIterationHint = 0;
194 : unsigned mServerClusterIterationHint = 0;
195 : unsigned mClientClusterIterationHint = 0;
196 : unsigned mAttributeIterationHint = 0;
197 : unsigned mDeviceTypeIterationHint = 0;
198 : unsigned mSemanticTagIterationHint = 0;
199 : EmberCommandListIterator mAcceptedCommandsIterator;
200 : EmberCommandListIterator mGeneratedCommandsIterator;
201 :
202 : // represents a remembered cluster reference that has been found as
203 : // looking for clusters is very common (for every attribute iteration)
204 : struct ClusterReference
205 : {
206 : ConcreteClusterPath path;
207 : const EmberAfCluster * cluster;
208 :
209 416 : ClusterReference(const ConcreteClusterPath p, const EmberAfCluster * c) : path(p), cluster(c) {}
210 : };
211 :
212 : enum class ClusterSide : uint8_t
213 : {
214 : kServer,
215 : kClient,
216 : };
217 :
218 : std::optional<ClusterReference> mPreviouslyFoundCluster;
219 : unsigned mEmberMetadataStructureGeneration = 0;
220 :
221 : // Ember requires a persistence provider, so we make sure we can always have something
222 : PersistentStorageDelegate * mPersistentStorageDelegate = nullptr;
223 :
224 : /// Finds the specified ember cluster
225 : ///
226 : /// Effectively the same as `emberAfFindServerCluster` except with some caching capabilities
227 : const EmberAfCluster * FindServerCluster(const ConcreteClusterPath & path);
228 :
229 : /// Find the index of the given attribute id
230 : std::optional<unsigned> TryFindAttributeIndex(const EmberAfCluster * cluster, AttributeId id) const;
231 :
232 : /// Find the index of the given cluster id
233 : std::optional<unsigned> TryFindClusterIndex(const EmberAfEndpointType * endpoint, ClusterId id, ClusterSide clusterSide) const;
234 :
235 : /// Find the index of the given endpoint id
236 : std::optional<unsigned> TryFindEndpointIndex(EndpointId id) const;
237 :
238 : using CommandListGetter = const CommandId *(const EmberAfCluster &);
239 :
240 : CommandId FindCommand(const ConcreteCommandPath & path, detail::EnumeratorCommandFinder & handlerFinder,
241 : detail::EnumeratorCommandFinder::Operation operation,
242 : CodegenDataModelProvider::EmberCommandListIterator & emberIterator, CommandListGetter commandListGetter);
243 : };
244 :
245 : } // namespace app
246 : } // namespace chip
|