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 : #include <data-model-providers/codegen/CodegenDataModelProvider.h>
18 :
19 : #include <access/AccessControl.h>
20 : #include <app-common/zap-generated/attribute-type.h>
21 : #include <app/CommandHandlerInterface.h>
22 : #include <app/CommandHandlerInterfaceRegistry.h>
23 : #include <app/ConcreteClusterPath.h>
24 : #include <app/ConcreteCommandPath.h>
25 : #include <app/EventPathParams.h>
26 : #include <app/RequiredPrivilege.h>
27 : #include <app/data-model-provider/MetadataTypes.h>
28 : #include <app/data-model-provider/Provider.h>
29 : #include <app/util/IMClusterCommandHandler.h>
30 : #include <app/util/af-types.h>
31 : #include <app/util/attribute-storage.h>
32 : #include <app/util/endpoint-config-api.h>
33 : #include <app/util/persistence/AttributePersistenceProvider.h>
34 : #include <app/util/persistence/DefaultAttributePersistenceProvider.h>
35 : #include <lib/core/CHIPError.h>
36 : #include <lib/core/DataModelTypes.h>
37 : #include <lib/support/CodeUtils.h>
38 : #include <lib/support/SpanSearchValue.h>
39 :
40 : #include <optional>
41 : #include <variant>
42 :
43 : namespace chip {
44 : namespace app {
45 : namespace detail {
46 :
47 18 : Loop EnumeratorCommandFinder::HandlerCallback(CommandId id)
48 : {
49 18 : switch (mOperation)
50 : {
51 3 : case Operation::kFindFirst:
52 3 : mFound = id;
53 3 : return Loop::Break;
54 11 : case Operation::kFindExact:
55 11 : if (mTarget == id)
56 : {
57 7 : mFound = id; // found it
58 7 : return Loop::Break;
59 : }
60 4 : break;
61 4 : case Operation::kFindNext:
62 4 : if (mTarget == id)
63 : {
64 : // Once we found the ID, get the first
65 3 : mOperation = Operation::kFindFirst;
66 : }
67 4 : break;
68 : }
69 8 : return Loop::Continue; // keep searching
70 : }
71 :
72 178 : std::optional<CommandId> EnumeratorCommandFinder::FindCommandId(Operation operation, const ConcreteCommandPath & path)
73 : {
74 178 : mOperation = operation;
75 178 : mTarget = path.mCommandId;
76 :
77 : CommandHandlerInterface * interface =
78 178 : CommandHandlerInterfaceRegistry::Instance().GetCommandHandler(path.mEndpointId, path.mClusterId);
79 :
80 178 : if (interface == nullptr)
81 : {
82 152 : return std::nullopt; // no data: no interface
83 : }
84 :
85 26 : CHIP_ERROR err = (interface->*mCallback)(path, HandlerCallbackFn, this);
86 26 : if (err == CHIP_ERROR_NOT_IMPLEMENTED)
87 : {
88 8 : return std::nullopt; // no data provided by the interface
89 : }
90 :
91 18 : if (err != CHIP_NO_ERROR)
92 : {
93 : #if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING
94 : // Report the error here since we lose actual error. This generally should NOT be possible as CommandHandlerInterface
95 : // usually returns unimplemented or should just work for our use case (our callback never fails)
96 0 : ChipLogError(DataManagement, "Enumerate error: %" CHIP_ERROR_FORMAT, err.Format());
97 : #endif
98 0 : return kInvalidCommandId;
99 : }
100 :
101 18 : return mFound.value_or(kInvalidCommandId);
102 : }
103 :
104 : } // namespace detail
105 :
106 : using detail::EnumeratorCommandFinder;
107 :
108 : namespace {
109 :
110 : /// Search by device type within a span of EmberAfDeviceType (finds the device type that matches the given
111 : /// DataModel::DeviceTypeEntry)
112 : struct ByDeviceType
113 : {
114 : using Key = DataModel::DeviceTypeEntry;
115 : using Type = const EmberAfDeviceType;
116 9 : static Span<Type> GetSpan(Span<const EmberAfDeviceType> & data) { return data; }
117 7 : static bool HasKey(const Key & id, const Type & instance)
118 : {
119 7 : return (instance.deviceId == id.deviceTypeId) && (instance.deviceVersion == id.deviceTypeRevision);
120 : }
121 : };
122 :
123 112 : const CommandId * AcceptedCommands(const EmberAfCluster & cluster)
124 : {
125 112 : return cluster.acceptedCommandList;
126 : }
127 :
128 35 : const CommandId * GeneratedCommands(const EmberAfCluster & cluster)
129 : {
130 35 : return cluster.generatedCommandList;
131 : }
132 :
133 : /// Load the cluster information into the specified destination
134 5221 : std::variant<CHIP_ERROR, DataModel::ClusterInfo> LoadClusterInfo(const ConcreteClusterPath & path, const EmberAfCluster & cluster)
135 : {
136 5221 : DataVersion * versionPtr = emberAfDataVersionStorage(path);
137 5221 : if (versionPtr == nullptr)
138 : {
139 : #if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING
140 0 : ChipLogError(AppServer, "Failed to get data version for %d/" ChipLogFormatMEI, static_cast<int>(path.mEndpointId),
141 : ChipLogValueMEI(cluster.clusterId));
142 : #endif
143 0 : return CHIP_ERROR_NOT_FOUND;
144 : }
145 :
146 5221 : DataModel::ClusterInfo info(*versionPtr);
147 : // TODO: set entry flags:
148 : // info->flags.Set(ClusterQualityFlags::kDiagnosticsData)
149 5221 : return info;
150 : }
151 :
152 : /// Converts a EmberAfCluster into a ClusterEntry
153 348 : std::variant<CHIP_ERROR, DataModel::ClusterEntry> ClusterEntryFrom(EndpointId endpointId, const EmberAfCluster & cluster)
154 : {
155 348 : ConcreteClusterPath clusterPath(endpointId, cluster.clusterId);
156 348 : auto info = LoadClusterInfo(clusterPath, cluster);
157 :
158 348 : if (CHIP_ERROR * err = std::get_if<CHIP_ERROR>(&info))
159 : {
160 0 : return *err;
161 : }
162 :
163 348 : if (DataModel::ClusterInfo * infoValue = std::get_if<DataModel::ClusterInfo>(&info))
164 : {
165 348 : return DataModel::ClusterEntry{
166 : .path = clusterPath,
167 : .info = *infoValue,
168 348 : };
169 : }
170 0 : return CHIP_ERROR_INCORRECT_STATE;
171 : }
172 :
173 : /// Finds the first server cluster entry for the given endpoint data starting at [start_index]
174 : ///
175 : /// Returns an invalid entry if no more server clusters are found
176 500 : DataModel::ClusterEntry FirstServerClusterEntry(EndpointId endpointId, const EmberAfEndpointType * endpoint, unsigned start_index,
177 : unsigned & found_index)
178 : {
179 1893 : for (unsigned cluster_idx = start_index; cluster_idx < endpoint->clusterCount; cluster_idx++)
180 : {
181 1741 : const EmberAfCluster & cluster = endpoint->cluster[cluster_idx];
182 1741 : if (!cluster.IsServer())
183 : {
184 1393 : continue;
185 : }
186 :
187 348 : found_index = cluster_idx;
188 348 : auto entry = ClusterEntryFrom(endpointId, cluster);
189 :
190 348 : if (DataModel::ClusterEntry * entryValue = std::get_if<DataModel::ClusterEntry>(&entry))
191 : {
192 348 : return *entryValue;
193 : }
194 :
195 : #if CHIP_ERROR_LOGGING && CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING
196 0 : if (CHIP_ERROR * errValue = std::get_if<CHIP_ERROR>(&entry))
197 : {
198 0 : ChipLogError(AppServer, "Failed to load cluster entry: %" CHIP_ERROR_FORMAT, errValue->Format());
199 : }
200 : else
201 : {
202 : // Should NOT be possible: entryFrom has only 2 variants
203 0 : ChipLogError(AppServer, "Failed to load cluster entry, UNKNOWN entry return type");
204 : }
205 : #endif
206 : }
207 :
208 152 : return DataModel::ClusterEntry::kInvalid;
209 : }
210 :
211 26 : ClusterId FirstClientClusterId(const EmberAfEndpointType * endpoint, unsigned start_index, unsigned & found_index)
212 : {
213 50 : for (unsigned cluster_idx = start_index; cluster_idx < endpoint->clusterCount; cluster_idx++)
214 : {
215 48 : const EmberAfCluster & cluster = endpoint->cluster[cluster_idx];
216 48 : if (!cluster.IsClient())
217 : {
218 24 : continue;
219 : }
220 :
221 24 : found_index = cluster_idx;
222 24 : return cluster.clusterId;
223 : }
224 :
225 2 : return kInvalidClusterId;
226 : }
227 :
228 : /// Load the attribute information into the specified destination
229 : ///
230 : /// `info` is assumed to be default-constructed/clear (i.e. this sets flags, but does not reset them).
231 7224 : void LoadAttributeInfo(const ConcreteAttributePath & path, const EmberAfAttributeMetadata & attribute,
232 : DataModel::AttributeInfo * info)
233 : {
234 7224 : info->readPrivilege = RequiredPrivilege::ForReadAttribute(path);
235 7224 : if (!attribute.IsReadOnly())
236 : {
237 3547 : info->writePrivilege = RequiredPrivilege::ForWriteAttribute(path);
238 : }
239 :
240 7224 : info->flags.Set(DataModel::AttributeQualityFlags::kListAttribute, (attribute.attributeType == ZCL_ARRAY_ATTRIBUTE_TYPE));
241 7224 : info->flags.Set(DataModel::AttributeQualityFlags::kTimed, attribute.MustUseTimedWrite());
242 :
243 : // NOTE: we do NOT provide additional info for:
244 : // - IsExternal/IsSingleton/IsAutomaticallyPersisted is not used by IM handling
245 : // - IsSingleton spec defines it for CLUSTERS where as we have it for ATTRIBUTES
246 : // - Several specification flags are not available (reportable, quieter reporting,
247 : // fixed, source attribution)
248 :
249 : // TODO: Set additional flags:
250 : // info->flags.Set(DataModel::AttributeQualityFlags::kFabricScoped)
251 : // info->flags.Set(DataModel::AttributeQualityFlags::kFabricSensitive)
252 : // info->flags.Set(DataModel::AttributeQualityFlags::kChangesOmitted)
253 7224 : }
254 :
255 2555 : DataModel::AttributeEntry AttributeEntryFrom(const ConcreteClusterPath & clusterPath, const EmberAfAttributeMetadata & attribute)
256 : {
257 2555 : DataModel::AttributeEntry entry;
258 :
259 2555 : entry.path = ConcreteAttributePath(clusterPath.mEndpointId, clusterPath.mClusterId, attribute.attributeId);
260 2555 : LoadAttributeInfo(entry.path, attribute, &entry.info);
261 :
262 2555 : return entry;
263 : }
264 :
265 101 : DataModel::CommandEntry CommandEntryFrom(const ConcreteClusterPath & clusterPath, CommandId clusterCommandId)
266 : {
267 101 : DataModel::CommandEntry entry;
268 101 : entry.path = ConcreteCommandPath(clusterPath.mEndpointId, clusterPath.mClusterId, clusterCommandId);
269 101 : entry.info.invokePrivilege = RequiredPrivilege::ForInvokeCommand(entry.path);
270 :
271 101 : entry.info.flags.Set(DataModel::CommandQualityFlags::kTimed, CommandNeedsTimedInvoke(clusterPath.mClusterId, clusterCommandId));
272 :
273 101 : entry.info.flags.Set(DataModel::CommandQualityFlags::kFabricScoped,
274 101 : CommandIsFabricScoped(clusterPath.mClusterId, clusterCommandId));
275 :
276 101 : entry.info.flags.Set(DataModel::CommandQualityFlags::kLargeMessage,
277 101 : CommandHasLargePayload(clusterPath.mClusterId, clusterCommandId));
278 101 : return entry;
279 : }
280 :
281 : // TODO: DeviceTypeEntry content is IDENTICAL to EmberAfDeviceType, so centralizing
282 : // to a common type is probably better. Need to figure out dependencies since
283 : // this would make ember return datamodel-provider types.
284 : // See: https://github.com/project-chip/connectedhomeip/issues/35889
285 9 : std::optional<DataModel::DeviceTypeEntry> DeviceTypeEntryFromEmber(const EmberAfDeviceType * other)
286 : {
287 9 : if (other == nullptr)
288 : {
289 4 : return std::nullopt;
290 : }
291 :
292 10 : return DataModel::DeviceTypeEntry{
293 5 : .deviceTypeId = other->deviceId,
294 5 : .deviceTypeRevision = other->deviceVersion,
295 5 : };
296 : }
297 :
298 : const ConcreteCommandPath kInvalidCommandPath(kInvalidEndpointId, kInvalidClusterId, kInvalidCommandId);
299 :
300 153 : std::optional<DataModel::EndpointInfo> GetEndpointInfoAtIndex(uint16_t endpointIndex)
301 : {
302 153 : VerifyOrReturnValue(emberAfEndpointIndexIsEnabled(endpointIndex), std::nullopt);
303 153 : EndpointId parent = emberAfParentEndpointFromIndex(endpointIndex);
304 153 : if (GetCompositionForEndpointIndex(endpointIndex) == EndpointComposition::kFullFamily)
305 : {
306 131 : return DataModel::EndpointInfo(parent, DataModel::EndpointCompositionPattern::kFullFamily);
307 : }
308 22 : if (GetCompositionForEndpointIndex(endpointIndex) == EndpointComposition::kTree)
309 : {
310 22 : return DataModel::EndpointInfo(parent, DataModel::EndpointCompositionPattern::kTree);
311 : }
312 0 : return std::nullopt;
313 : }
314 :
315 189 : DataModel::EndpointEntry FirstEndpointEntry(unsigned start_index, uint16_t & found_index)
316 : {
317 : // find the first enabled index after the start index
318 189 : const uint16_t lastEndpointIndex = emberAfEndpointCount();
319 212 : for (uint16_t endpoint_idx = static_cast<uint16_t>(start_index); endpoint_idx < lastEndpointIndex; endpoint_idx++)
320 : {
321 173 : if (emberAfEndpointIndexIsEnabled(endpoint_idx))
322 : {
323 150 : found_index = endpoint_idx;
324 150 : DataModel::EndpointEntry endpointEntry = DataModel::EndpointEntry::kInvalid;
325 150 : endpointEntry.id = emberAfEndpointFromIndex(endpoint_idx);
326 150 : auto endpointInfo = GetEndpointInfoAtIndex(endpoint_idx);
327 : // The endpoint info should have value as this endpoint should be valid at this time
328 150 : VerifyOrDie(endpointInfo.has_value());
329 150 : endpointEntry.info = endpointInfo.value();
330 150 : return endpointEntry;
331 : }
332 : }
333 :
334 : // No enabled endpoint found. Give up
335 39 : return DataModel::EndpointEntry::kInvalid;
336 : }
337 :
338 11 : bool operator==(const DataModel::Provider::SemanticTag & tagA, const DataModel::Provider::SemanticTag & tagB)
339 : {
340 : // Label is an optional and nullable value of CharSpan. Optional and Nullable have overload for ==,
341 : // But `==` is deleted for CharSpan. Here we only check whether the string is the same.
342 11 : if (tagA.label.HasValue() != tagB.label.HasValue())
343 : {
344 3 : return false;
345 : }
346 8 : if (tagA.label.HasValue())
347 : {
348 8 : if (tagA.label.Value().IsNull() != tagB.label.Value().IsNull())
349 : {
350 0 : return false;
351 : }
352 8 : if (!tagA.label.Value().IsNull())
353 : {
354 8 : if (!tagA.label.Value().Value().data_equal(tagB.label.Value().Value()))
355 : {
356 3 : return false;
357 : }
358 : }
359 : }
360 5 : return (tagA.tag == tagB.tag) && (tagA.mfgCode == tagB.mfgCode) && (tagA.namespaceID == tagB.namespaceID);
361 : }
362 :
363 5 : std::optional<unsigned> FindNextSemanticTagIndex(EndpointId endpoint, const DataModel::Provider::SemanticTag & previous,
364 : unsigned hintWherePreviousMayBe)
365 : {
366 5 : DataModel::Provider::SemanticTag hintTag;
367 : // Check whether the hint is the previous tag
368 5 : if (GetSemanticTagForEndpointAtIndex(endpoint, hintWherePreviousMayBe, hintTag) == CHIP_NO_ERROR)
369 : {
370 5 : if (previous == hintTag)
371 : {
372 2 : return std::make_optional(hintWherePreviousMayBe + 1);
373 : }
374 : }
375 : // If the hint is not the previous tag, iterate over all the tags to find the index for the previous tag
376 3 : unsigned index = 0;
377 : // Ensure that the next index is in the range
378 14 : while (GetSemanticTagForEndpointAtIndex(endpoint, index + 1, hintTag) == CHIP_NO_ERROR &&
379 14 : GetSemanticTagForEndpointAtIndex(endpoint, index, hintTag) == CHIP_NO_ERROR)
380 : {
381 6 : if (previous == hintTag)
382 : {
383 1 : return std::make_optional(index + 1);
384 : }
385 5 : index++;
386 : }
387 2 : return std::nullopt;
388 5 : }
389 :
390 : DefaultAttributePersistenceProvider gDefaultAttributePersistence;
391 :
392 : } // namespace
393 :
394 162 : CHIP_ERROR CodegenDataModelProvider::Startup(DataModel::InteractionModelContext context)
395 : {
396 162 : ReturnErrorOnFailure(DataModel::Provider::Startup(context));
397 :
398 : // Ember NVM requires have a data model provider. attempt to create one if one is not available
399 : //
400 : // It is not a critical failure to not have one, however if one is not set up, ember NVM operations
401 : // will error out with a `persistence not available`.
402 162 : if (GetAttributePersistenceProvider() == nullptr)
403 : {
404 : #if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING
405 162 : ChipLogProgress(DataManagement, "Ember attribute persistence requires setting up");
406 : #endif
407 162 : if (mPersistentStorageDelegate != nullptr)
408 : {
409 1 : ReturnErrorOnFailure(gDefaultAttributePersistence.Init(mPersistentStorageDelegate));
410 1 : SetAttributePersistenceProvider(&gDefaultAttributePersistence);
411 : #if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING
412 : }
413 : else
414 : {
415 161 : ChipLogError(DataManagement, "No storage delegate available, will not set up attribute persistence.");
416 : #endif
417 : }
418 : }
419 :
420 162 : return CHIP_NO_ERROR;
421 : }
422 :
423 6 : std::optional<CommandId> CodegenDataModelProvider::EmberCommandListIterator::First(const CommandId * list)
424 : {
425 6 : VerifyOrReturnValue(list != nullptr, std::nullopt);
426 4 : mCurrentList = mCurrentHint = list;
427 :
428 4 : VerifyOrReturnValue(*mCurrentList != kInvalidCommandId, std::nullopt);
429 4 : return *mCurrentList;
430 : }
431 :
432 65 : std::optional<CommandId> CodegenDataModelProvider::EmberCommandListIterator::Next(const CommandId * list, CommandId previousId)
433 : {
434 65 : VerifyOrReturnValue(list != nullptr, std::nullopt);
435 65 : VerifyOrReturnValue(previousId != kInvalidCommandId, std::nullopt);
436 :
437 65 : if (mCurrentList != list)
438 : {
439 : // invalidate the hint if switching lists...
440 4 : mCurrentHint = nullptr;
441 4 : mCurrentList = list;
442 : }
443 :
444 65 : if ((mCurrentHint == nullptr) || (*mCurrentHint != previousId))
445 : {
446 : // we did not find a usable hint. Search from the to set the hint
447 59 : mCurrentHint = mCurrentList;
448 88 : while ((*mCurrentHint != kInvalidCommandId) && (*mCurrentHint != previousId))
449 : {
450 29 : mCurrentHint++;
451 : }
452 : }
453 :
454 65 : VerifyOrReturnValue(*mCurrentHint == previousId, std::nullopt);
455 :
456 : // hint is valid and can be used immediately
457 55 : mCurrentHint++; // this is the next value
458 55 : return (*mCurrentHint == kInvalidCommandId) ? std::nullopt : std::make_optional(*mCurrentHint);
459 : }
460 :
461 76 : bool CodegenDataModelProvider::EmberCommandListIterator::Exists(const CommandId * list, CommandId toCheck)
462 : {
463 76 : VerifyOrReturnValue(list != nullptr, false);
464 73 : VerifyOrReturnValue(toCheck != kInvalidCommandId, false);
465 :
466 73 : if (mCurrentList != list)
467 : {
468 : // invalidate the hint if switching lists...
469 5 : mCurrentHint = nullptr;
470 5 : mCurrentList = list;
471 : }
472 :
473 : // maybe already positioned correctly
474 73 : if ((mCurrentHint != nullptr) && (*mCurrentHint == toCheck))
475 : {
476 57 : return true;
477 : }
478 :
479 : // move and try to find it
480 16 : mCurrentHint = mCurrentList;
481 38 : while ((*mCurrentHint != kInvalidCommandId) && (*mCurrentHint != toCheck))
482 : {
483 22 : mCurrentHint++;
484 : }
485 :
486 16 : return (*mCurrentHint == toCheck);
487 : }
488 :
489 5 : std::optional<DataModel::ActionReturnStatus> CodegenDataModelProvider::Invoke(const DataModel::InvokeRequest & request,
490 : TLV::TLVReader & input_arguments,
491 : CommandHandler * handler)
492 : {
493 : CommandHandlerInterface * handler_interface =
494 5 : CommandHandlerInterfaceRegistry::Instance().GetCommandHandler(request.path.mEndpointId, request.path.mClusterId);
495 :
496 5 : if (handler_interface)
497 : {
498 3 : CommandHandlerInterface::HandlerContext context(*handler, request.path, input_arguments);
499 3 : handler_interface->InvokeCommand(context);
500 :
501 : // If the command was handled, don't proceed any further and return successfully.
502 3 : if (context.mCommandHandled)
503 : {
504 3 : return std::nullopt;
505 : }
506 : }
507 :
508 : // Ember always sets the return in the handler
509 2 : DispatchSingleClusterCommand(request.path, input_arguments, handler);
510 2 : return std::nullopt;
511 : }
512 :
513 2 : bool CodegenDataModelProvider::EndpointExists(EndpointId endpoint)
514 : {
515 2 : return (emberAfIndexFromEndpoint(endpoint) != kEmberInvalidEndpointIndex);
516 : }
517 :
518 5 : std::optional<DataModel::EndpointInfo> CodegenDataModelProvider::GetEndpointInfo(EndpointId endpoint)
519 : {
520 5 : std::optional<unsigned> endpoint_idx = TryFindEndpointIndex(endpoint);
521 5 : if (endpoint_idx.has_value())
522 : {
523 3 : return GetEndpointInfoAtIndex(static_cast<uint16_t>(*endpoint_idx));
524 : }
525 2 : return std::nullopt;
526 : }
527 :
528 55 : DataModel::EndpointEntry CodegenDataModelProvider::FirstEndpoint()
529 : {
530 55 : return FirstEndpointEntry(0, mEndpointIterationHint);
531 : }
532 :
533 150 : std::optional<unsigned> CodegenDataModelProvider::TryFindEndpointIndex(EndpointId id) const
534 : {
535 150 : const uint16_t lastEndpointIndex = emberAfEndpointCount();
536 :
537 300 : if ((mEndpointIterationHint < lastEndpointIndex) && emberAfEndpointIndexIsEnabled(mEndpointIterationHint) &&
538 150 : (id == emberAfEndpointFromIndex(mEndpointIterationHint)))
539 : {
540 133 : return std::make_optional(mEndpointIterationHint);
541 : }
542 :
543 : // Linear search, this may be slow
544 17 : uint16_t idx = emberAfIndexFromEndpoint(id);
545 17 : if (idx == kEmberInvalidEndpointIndex)
546 : {
547 4 : return std::nullopt;
548 : }
549 :
550 13 : return std::make_optional<unsigned>(idx);
551 : }
552 :
553 136 : DataModel::EndpointEntry CodegenDataModelProvider::NextEndpoint(EndpointId before)
554 : {
555 136 : std::optional<unsigned> before_idx = TryFindEndpointIndex(before);
556 136 : if (!before_idx.has_value())
557 : {
558 2 : return DataModel::EndpointEntry::kInvalid;
559 : }
560 134 : return FirstEndpointEntry(*before_idx + 1, mEndpointIterationHint);
561 : }
562 :
563 244 : DataModel::ClusterEntry CodegenDataModelProvider::FirstServerCluster(EndpointId endpointId)
564 : {
565 244 : const EmberAfEndpointType * endpoint = emberAfFindEndpointType(endpointId);
566 244 : VerifyOrReturnValue(endpoint != nullptr, DataModel::ClusterEntry::kInvalid);
567 242 : VerifyOrReturnValue(endpoint->clusterCount > 0, DataModel::ClusterEntry::kInvalid);
568 242 : VerifyOrReturnValue(endpoint->cluster != nullptr, DataModel::ClusterEntry::kInvalid);
569 :
570 242 : return FirstServerClusterEntry(endpointId, endpoint, 0, mServerClusterIterationHint);
571 : }
572 :
573 276 : std::optional<unsigned> CodegenDataModelProvider::TryFindClusterIndex(const EmberAfEndpointType * endpoint, ClusterId id,
574 : ClusterSide side) const
575 : {
576 276 : const unsigned clusterCount = endpoint->clusterCount;
577 276 : unsigned hint = side == ClusterSide::kServer ? mServerClusterIterationHint : mClientClusterIterationHint;
578 :
579 276 : if (hint < clusterCount)
580 : {
581 276 : const EmberAfCluster & cluster = endpoint->cluster[hint];
582 276 : if (((side == ClusterSide::kServer) && cluster.IsServer()) || ((side == ClusterSide::kClient) && cluster.IsClient()))
583 : {
584 274 : if (cluster.clusterId == id)
585 : {
586 254 : return std::make_optional(hint);
587 : }
588 : }
589 : }
590 :
591 : // linear search, this may be slow
592 : // does NOT use emberAfClusterIndex to not iterate over endpoints as we have
593 : // already found the correct endpoint
594 56 : for (unsigned cluster_idx = 0; cluster_idx < clusterCount; cluster_idx++)
595 : {
596 52 : const EmberAfCluster & cluster = endpoint->cluster[cluster_idx];
597 52 : if (((side == ClusterSide::kServer) && !cluster.IsServer()) || ((side == ClusterSide::kClient) && !cluster.IsClient()))
598 : {
599 26 : continue;
600 : }
601 26 : if (cluster.clusterId == id)
602 : {
603 18 : return std::make_optional(cluster_idx);
604 : }
605 : }
606 :
607 4 : return std::nullopt;
608 : }
609 :
610 261 : DataModel::ClusterEntry CodegenDataModelProvider::NextServerCluster(const ConcreteClusterPath & before)
611 : {
612 : // TODO: This search still seems slow (ember will loop). Should use index hints as long
613 : // as ember API supports it
614 261 : const EmberAfEndpointType * endpoint = emberAfFindEndpointType(before.mEndpointId);
615 :
616 261 : VerifyOrReturnValue(endpoint != nullptr, DataModel::ClusterEntry::kInvalid);
617 260 : VerifyOrReturnValue(endpoint->clusterCount > 0, DataModel::ClusterEntry::kInvalid);
618 260 : VerifyOrReturnValue(endpoint->cluster != nullptr, DataModel::ClusterEntry::kInvalid);
619 :
620 260 : std::optional<unsigned> cluster_idx = TryFindClusterIndex(endpoint, before.mClusterId, ClusterSide::kServer);
621 260 : if (!cluster_idx.has_value())
622 : {
623 2 : return DataModel::ClusterEntry::kInvalid;
624 : }
625 :
626 258 : return FirstServerClusterEntry(before.mEndpointId, endpoint, *cluster_idx + 1, mServerClusterIterationHint);
627 : }
628 :
629 5005 : std::optional<DataModel::ClusterInfo> CodegenDataModelProvider::GetServerClusterInfo(const ConcreteClusterPath & path)
630 : {
631 5005 : const EmberAfCluster * cluster = FindServerCluster(path);
632 :
633 5005 : VerifyOrReturnValue(cluster != nullptr, std::nullopt);
634 :
635 4873 : auto info = LoadClusterInfo(path, *cluster);
636 :
637 4873 : if (CHIP_ERROR * err = std::get_if<CHIP_ERROR>(&info))
638 : {
639 : #if CHIP_ERROR_LOGGING && CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING
640 0 : ChipLogError(AppServer, "Failed to load cluster info: %" CHIP_ERROR_FORMAT, err->Format());
641 : #else
642 : (void) err->Format();
643 : #endif
644 0 : return std::nullopt;
645 : }
646 :
647 4873 : return std::make_optional(std::get<DataModel::ClusterInfo>(info));
648 : }
649 :
650 14 : ConcreteClusterPath CodegenDataModelProvider::FirstClientCluster(EndpointId endpointId)
651 : {
652 14 : const EmberAfEndpointType * endpoint = emberAfFindEndpointType(endpointId);
653 14 : VerifyOrReturnValue(endpoint != nullptr, ConcreteClusterPath(endpointId, kInvalidClusterId));
654 12 : VerifyOrReturnValue(endpoint->clusterCount > 0, ConcreteClusterPath(endpointId, kInvalidClusterId));
655 12 : VerifyOrReturnValue(endpoint->cluster != nullptr, ConcreteClusterPath(endpointId, kInvalidClusterId));
656 :
657 12 : return ConcreteClusterPath(endpointId, FirstClientClusterId(endpoint, 0, mClientClusterIterationHint));
658 : }
659 :
660 17 : ConcreteClusterPath CodegenDataModelProvider::NextClientCluster(const ConcreteClusterPath & before)
661 : {
662 : // TODO: This search still seems slow (ember will loop). Should use index hints as long
663 : // as ember API supports it
664 17 : const EmberAfEndpointType * endpoint = emberAfFindEndpointType(before.mEndpointId);
665 :
666 17 : VerifyOrReturnValue(endpoint != nullptr, ConcreteClusterPath(before.mEndpointId, kInvalidClusterId));
667 16 : VerifyOrReturnValue(endpoint->clusterCount > 0, ConcreteClusterPath(before.mEndpointId, kInvalidClusterId));
668 16 : VerifyOrReturnValue(endpoint->cluster != nullptr, ConcreteClusterPath(before.mEndpointId, kInvalidClusterId));
669 :
670 16 : std::optional<unsigned> cluster_idx = TryFindClusterIndex(endpoint, before.mClusterId, ClusterSide::kClient);
671 16 : if (!cluster_idx.has_value())
672 : {
673 2 : return ConcreteClusterPath(before.mEndpointId, kInvalidClusterId);
674 : }
675 :
676 14 : return ConcreteClusterPath(before.mEndpointId, FirstClientClusterId(endpoint, *cluster_idx + 1, mClientClusterIterationHint));
677 : }
678 :
679 751 : DataModel::AttributeEntry CodegenDataModelProvider::FirstAttribute(const ConcreteClusterPath & path)
680 : {
681 751 : const EmberAfCluster * cluster = FindServerCluster(path);
682 :
683 751 : VerifyOrReturnValue(cluster != nullptr, DataModel::AttributeEntry::kInvalid);
684 747 : VerifyOrReturnValue(cluster->attributeCount > 0, DataModel::AttributeEntry::kInvalid);
685 747 : VerifyOrReturnValue(cluster->attributes != nullptr, DataModel::AttributeEntry::kInvalid);
686 :
687 747 : mAttributeIterationHint = 0;
688 747 : return AttributeEntryFrom(path, cluster->attributes[0]);
689 : }
690 :
691 8155 : std::optional<unsigned> CodegenDataModelProvider::TryFindAttributeIndex(const EmberAfCluster * cluster, AttributeId id) const
692 : {
693 8155 : const unsigned attributeCount = cluster->attributeCount;
694 :
695 : // attempt to find this based on the embedded hint
696 8155 : if ((mAttributeIterationHint < attributeCount) && (cluster->attributes[mAttributeIterationHint].attributeId == id))
697 : {
698 5062 : return std::make_optional(mAttributeIterationHint);
699 : }
700 :
701 : // linear search is required. This may be slow
702 12289 : for (unsigned attribute_idx = 0; attribute_idx < attributeCount; attribute_idx++)
703 : {
704 :
705 11059 : if (cluster->attributes[attribute_idx].attributeId == id)
706 : {
707 1863 : return std::make_optional(attribute_idx);
708 : }
709 : }
710 :
711 1230 : return std::nullopt;
712 : }
713 :
714 14467 : const EmberAfCluster * CodegenDataModelProvider::FindServerCluster(const ConcreteClusterPath & path)
715 : {
716 28063 : if (mPreviouslyFoundCluster.has_value() && (mPreviouslyFoundCluster->path == path) &&
717 13596 : (mEmberMetadataStructureGeneration == emberAfMetadataStructureGeneration()))
718 :
719 : {
720 13506 : return mPreviouslyFoundCluster->cluster;
721 : }
722 :
723 961 : const EmberAfCluster * cluster = emberAfFindServerCluster(path.mEndpointId, path.mClusterId);
724 961 : if (cluster != nullptr)
725 : {
726 416 : mPreviouslyFoundCluster = std::make_optional<ClusterReference>(path, cluster);
727 416 : mEmberMetadataStructureGeneration = emberAfMetadataStructureGeneration();
728 : }
729 961 : return cluster;
730 : }
731 :
732 178 : CommandId CodegenDataModelProvider::FindCommand(const ConcreteCommandPath & path, detail::EnumeratorCommandFinder & handlerFinder,
733 : detail::EnumeratorCommandFinder::Operation operation,
734 : CodegenDataModelProvider::EmberCommandListIterator & emberIterator,
735 : CommandListGetter commandListGetter)
736 : {
737 :
738 178 : std::optional<CommandId> handlerCommandId = handlerFinder.FindCommandId(operation, path);
739 178 : if (handlerCommandId.has_value())
740 : {
741 18 : return *handlerCommandId;
742 : }
743 :
744 160 : const EmberAfCluster * cluster = FindServerCluster(path);
745 160 : VerifyOrReturnValue(cluster != nullptr, kInvalidCommandId);
746 :
747 147 : const CommandId * commandList = commandListGetter(*cluster);
748 :
749 147 : switch (operation)
750 : {
751 6 : case EnumeratorCommandFinder::Operation::kFindFirst:
752 6 : return emberIterator.First(commandList).value_or(kInvalidCommandId);
753 65 : case EnumeratorCommandFinder::Operation::kFindNext:
754 65 : return emberIterator.Next(commandList, path.mCommandId).value_or(kInvalidCommandId);
755 76 : case EnumeratorCommandFinder::Operation::kFindExact:
756 : default:
757 76 : return emberIterator.Exists(commandList, path.mCommandId) ? path.mCommandId : kInvalidCommandId;
758 : }
759 : }
760 :
761 2261 : DataModel::AttributeEntry CodegenDataModelProvider::NextAttribute(const ConcreteAttributePath & before)
762 : {
763 2261 : const EmberAfCluster * cluster = FindServerCluster(before);
764 2261 : VerifyOrReturnValue(cluster != nullptr, DataModel::AttributeEntry::kInvalid);
765 2257 : VerifyOrReturnValue(cluster->attributeCount > 0, DataModel::AttributeEntry::kInvalid);
766 2257 : VerifyOrReturnValue(cluster->attributes != nullptr, DataModel::AttributeEntry::kInvalid);
767 :
768 : // find the given attribute in the list and then return the next one
769 2257 : std::optional<unsigned> attribute_idx = TryFindAttributeIndex(cluster, before.mAttributeId);
770 2257 : if (!attribute_idx.has_value())
771 : {
772 1 : return DataModel::AttributeEntry::kInvalid;
773 : }
774 :
775 2256 : unsigned next_idx = *attribute_idx + 1;
776 2256 : if (next_idx < cluster->attributeCount)
777 : {
778 1808 : mAttributeIterationHint = next_idx;
779 1808 : return AttributeEntryFrom(before, cluster->attributes[next_idx]);
780 : }
781 :
782 : // iteration complete
783 448 : return DataModel::AttributeEntry::kInvalid;
784 : }
785 :
786 6290 : std::optional<DataModel::AttributeInfo> CodegenDataModelProvider::GetAttributeInfo(const ConcreteAttributePath & path)
787 : {
788 6290 : const EmberAfCluster * cluster = FindServerCluster(path);
789 :
790 6290 : VerifyOrReturnValue(cluster != nullptr, std::nullopt);
791 5898 : VerifyOrReturnValue(cluster->attributeCount > 0, std::nullopt);
792 5898 : VerifyOrReturnValue(cluster->attributes != nullptr, std::nullopt);
793 :
794 5898 : std::optional<unsigned> attribute_idx = TryFindAttributeIndex(cluster, path.mAttributeId);
795 :
796 5898 : if (!attribute_idx.has_value())
797 : {
798 1229 : return std::nullopt;
799 : }
800 :
801 4669 : DataModel::AttributeInfo info;
802 4669 : LoadAttributeInfo(path, cluster->attributes[*attribute_idx], &info);
803 4669 : return std::make_optional(info);
804 : }
805 :
806 9 : DataModel::CommandEntry CodegenDataModelProvider::FirstAcceptedCommand(const ConcreteClusterPath & path)
807 : {
808 9 : EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateAcceptedCommands);
809 :
810 : CommandId commandId =
811 9 : FindCommand(ConcreteCommandPath(path.mEndpointId, path.mClusterId, kInvalidCommandId), handlerFinder,
812 9 : detail::EnumeratorCommandFinder::Operation::kFindFirst, mAcceptedCommandsIterator, AcceptedCommands);
813 :
814 9 : VerifyOrReturnValue(commandId != kInvalidCommandId, DataModel::CommandEntry::kInvalid);
815 3 : return CommandEntryFrom(path, commandId);
816 : }
817 :
818 35 : DataModel::CommandEntry CodegenDataModelProvider::NextAcceptedCommand(const ConcreteCommandPath & before)
819 : {
820 :
821 35 : EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateAcceptedCommands);
822 35 : CommandId commandId = FindCommand(before, handlerFinder, detail::EnumeratorCommandFinder::Operation::kFindNext,
823 35 : mAcceptedCommandsIterator, AcceptedCommands);
824 :
825 35 : VerifyOrReturnValue(commandId != kInvalidCommandId, DataModel::CommandEntry::kInvalid);
826 23 : return CommandEntryFrom(before, commandId);
827 : }
828 :
829 92 : std::optional<DataModel::CommandInfo> CodegenDataModelProvider::GetAcceptedCommandInfo(const ConcreteCommandPath & path)
830 : {
831 :
832 92 : EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateAcceptedCommands);
833 92 : CommandId commandId = FindCommand(path, handlerFinder, detail::EnumeratorCommandFinder::Operation::kFindExact,
834 92 : mAcceptedCommandsIterator, AcceptedCommands);
835 :
836 92 : VerifyOrReturnValue(commandId != kInvalidCommandId, std::nullopt);
837 75 : return CommandEntryFrom(path, commandId).info;
838 : }
839 :
840 9 : ConcreteCommandPath CodegenDataModelProvider::FirstGeneratedCommand(const ConcreteClusterPath & path)
841 : {
842 9 : EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateGeneratedCommands);
843 : CommandId commandId =
844 9 : FindCommand(ConcreteCommandPath(path.mEndpointId, path.mClusterId, kInvalidCommandId), handlerFinder,
845 9 : detail::EnumeratorCommandFinder::Operation::kFindFirst, mGeneratedCommandsIterator, GeneratedCommands);
846 :
847 9 : VerifyOrReturnValue(commandId != kInvalidCommandId, kInvalidCommandPath);
848 3 : return ConcreteCommandPath(path.mEndpointId, path.mClusterId, commandId);
849 : }
850 :
851 33 : ConcreteCommandPath CodegenDataModelProvider::NextGeneratedCommand(const ConcreteCommandPath & before)
852 : {
853 33 : EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateGeneratedCommands);
854 :
855 33 : CommandId commandId = FindCommand(before, handlerFinder, detail::EnumeratorCommandFinder::Operation::kFindNext,
856 33 : mGeneratedCommandsIterator, GeneratedCommands);
857 :
858 33 : VerifyOrReturnValue(commandId != kInvalidCommandId, kInvalidCommandPath);
859 21 : return ConcreteCommandPath(before.mEndpointId, before.mClusterId, commandId);
860 : }
861 :
862 3 : std::optional<DataModel::DeviceTypeEntry> CodegenDataModelProvider::FirstDeviceType(EndpointId endpoint)
863 : {
864 : // Use the `Index` version even though `emberAfDeviceTypeListFromEndpoint` would work because
865 : // index finding is cached in TryFindEndpointIndex and this avoids an extra `emberAfIndexFromEndpoint`
866 : // during `Next` loops. This avoids O(n^2) on number of indexes when iterating over all device types.
867 : //
868 : // Not actually needed for `First`, however this makes First and Next consistent.
869 3 : std::optional<unsigned> endpoint_index = TryFindEndpointIndex(endpoint);
870 3 : if (!endpoint_index.has_value())
871 : {
872 0 : return std::nullopt;
873 : }
874 :
875 3 : CHIP_ERROR err = CHIP_NO_ERROR;
876 3 : Span<const EmberAfDeviceType> deviceTypes = emberAfDeviceTypeListFromEndpointIndex(*endpoint_index, err);
877 3 : SpanSearchValue<chip::Span<const EmberAfDeviceType>> searchable(&deviceTypes);
878 :
879 3 : return DeviceTypeEntryFromEmber(searchable.First<ByDeviceType>(mDeviceTypeIterationHint).Value());
880 : }
881 :
882 6 : std::optional<DataModel::DeviceTypeEntry> CodegenDataModelProvider::NextDeviceType(EndpointId endpoint,
883 : const DataModel::DeviceTypeEntry & previous)
884 : {
885 : // Use the `Index` version even though `emberAfDeviceTypeListFromEndpoint` would work because
886 : // index finding is cached in TryFindEndpointIndex and this avoids an extra `emberAfIndexFromEndpoint`
887 : // during `Next` loops. This avoids O(n^2) on number of indexes when iterating over all device types.
888 6 : std::optional<unsigned> endpoint_index = TryFindEndpointIndex(endpoint);
889 6 : if (!endpoint_index.has_value())
890 : {
891 0 : return std::nullopt;
892 : }
893 :
894 6 : CHIP_ERROR err = CHIP_NO_ERROR;
895 6 : chip::Span<const EmberAfDeviceType> deviceTypes = emberAfDeviceTypeListFromEndpointIndex(*endpoint_index, err);
896 6 : SpanSearchValue<chip::Span<const EmberAfDeviceType>> searchable(&deviceTypes);
897 :
898 6 : return DeviceTypeEntryFromEmber(searchable.Next<ByDeviceType>(previous, mDeviceTypeIterationHint).Value());
899 : }
900 :
901 2 : std::optional<DataModel::Provider::SemanticTag> CodegenDataModelProvider::GetFirstSemanticTag(EndpointId endpoint)
902 : {
903 2 : Clusters::Descriptor::Structs::SemanticTagStruct::Type tag;
904 : // we start at the beginning
905 2 : mSemanticTagIterationHint = 0;
906 2 : if (GetSemanticTagForEndpointAtIndex(endpoint, 0, tag) == CHIP_NO_ERROR)
907 : {
908 1 : return std::make_optional(tag);
909 : }
910 1 : return std::nullopt;
911 2 : }
912 :
913 5 : std::optional<DataModel::Provider::SemanticTag> CodegenDataModelProvider::GetNextSemanticTag(EndpointId endpoint,
914 : const SemanticTag & previous)
915 : {
916 5 : Clusters::Descriptor::Structs::SemanticTagStruct::Type tag;
917 5 : std::optional<unsigned> idx = FindNextSemanticTagIndex(endpoint, previous, mSemanticTagIterationHint);
918 5 : if (idx.has_value() && GetSemanticTagForEndpointAtIndex(endpoint, *idx, tag) == CHIP_NO_ERROR)
919 : {
920 3 : return std::make_optional(tag);
921 : }
922 2 : return std::nullopt;
923 5 : }
924 :
925 : } // namespace app
926 : } // namespace chip
|