From 2ecbcc2e77b6176e32e597893fc5b871313b8fdf Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Mon, 20 Jan 2025 13:35:31 -0500 Subject: [PATCH] Switch DataModel::Provider to a span-based list iterator - flash and ram savings, simpler interface (#37033) * Copied over the new AttributePathExpandIterator and will incrementally use it (so I can validate tests) * Rename AttributePathExpandIterator to legacy * Prepare for using new style iterators ... checking NOT YET enabled though * Enabled checks ... and unit tests fail, but this now can be debugged * Fix some of the underlying bugs: read handling logic assumes we are ok to undo * Unit tests pass now * Restyle * Use new iterator in IME * Update logic to use the new iterator on testRead * more updates * Restyle * Remove the legacy attribute path expand iterator * Update naming * Restyle * Remove extra argument for ReadHandler constructor * Restyle * Slight flash improvement * Fix up includes * Removed empty line * added comment on why state is a friend class * Comment updates * Restyle, add some comments and add extra checks on validity check only for expansion. This saves a tiny amount of flash (32 bytes) * Remove an include * Comment updates, renamed mLastOutputPath to mOutputPath * Fix one typo * Re-arrange members of ReadHandler to optimize for memory layout. This saves 8 bytes for struct. We still have a 20-byte padding which I am unsure how to get rid of * Restyle * Rename State to Position * One more rename * Remove redundant assigment ...we are at a net 0 txt increase now on qpg * Add more unit tests for non-obvious requirement that wildcard expansion checks path validity, however non-wildcard does not check it * Update src/app/AttributePathExpandIterator.cpp Co-authored-by: Tennessee Carmel-Veilleux * Update src/app/AttributePathExpandIterator.h Co-authored-by: Tennessee Carmel-Veilleux * Update src/app/AttributePathExpandIterator.h Co-authored-by: Tennessee Carmel-Veilleux * Update src/app/AttributePathExpandIterator.h Co-authored-by: Tennessee Carmel-Veilleux * Update src/app/ReadHandler.h Co-authored-by: Tennessee Carmel-Veilleux * Update src/app/ReadHandler.cpp Co-authored-by: Tennessee Carmel-Veilleux * Update src/app/AttributePathExpandIterator.h Co-authored-by: Tennessee Carmel-Veilleux * Use different values for the cluster ids for testing * One more state to position change * mExpanded is now set during output path returning. Removed 2 more sets to save another tinier amount of .text * Import metadatalist class and test * Remove some tests that seem redundant, keep only one * Start with generated commands, see if we can replace its usage... * Unit tests for GeneratedCommands pass * Start with an implementation of accepted commands (no testing yet) * More tests pass * Updated AcceptedCommands as well .. unit tests pass * Restyle * Fix namespaces * Slight refactor. Code is still very much ugly * A bit of refactor, code looks better and tests pass now * Code compile for semantic tag ... made std::optional support non-trivial destructors * Update test * Make chip::Optional be trivially destructible if the underlying type is. Previous implementation always had a destructor, so it was never trivially destructible. * Update src/app/AttributePathExpandIterator.cpp Co-authored-by: Boris Zbarsky * Update src/app/AttributePathExpandIterator.cpp Co-authored-by: Boris Zbarsky * Update src/app/AttributePathExpandIterator.cpp Co-authored-by: Boris Zbarsky * Update src/app/AttributePathExpandIterator.cpp Co-authored-by: Boris Zbarsky * Update src/app/InteractionModelEngine.cpp Co-authored-by: Boris Zbarsky * Update src/app/ReadHandler.h Co-authored-by: Boris Zbarsky * Update src/app/AttributePathExpandIterator.h Co-authored-by: Boris Zbarsky * Update src/app/ReadHandler.h Co-authored-by: Boris Zbarsky * Use mCompletePosition * Another rename * Undo submodule update * Restyle * Remove extra char * Remove unused variable * Update comment text to not sound like graph parsing * Rename method to be more descriptive * Remove one more unused variable * Update peek attribute iterator to rollback and update code logic a bit. Hoping for cleaner code * Semantic tags conversion is done * Restyle * Migrate device types to the new format * update comment a bit * Add unused marker for chip errors used for logging only * Fix descriptor cluster * Fix microwave oven * Restyle * Fix intentional bugprone-use-after-move * Fix intentional bugprone-use-after-move * Restyle * Fix based on clang feedback * Move endpoints to the new style of iteration * Fix includes * Fix includes * Fix includes * make it standard that test Providers are for now CodegenDataModelProvider. Saves me some typing as I move things around * Restyle * Minor update in logic: do the endpoint selection when next is called * Allow startup to try to mark attributes dirty even if no provider exists yet * Fix typo * start implementing client clusters * Update ClientCluster logic * Restyle * Start defining the server cluster query * Actually use the new server cluster functionality * Fix an include * More include fixes * implement the get attributes and adapt unit tests * Restyle * more updates to cleanup code. I am a bit concerned about O(n^2) attribute access... * A rename and moved finder methods out of inline. Saves 650 bytes of flash * Update logic to centralize metadata list code with less templating * move the generic metadata list to detail, add an assert on trivial destruction * Fix namespace prefix * Save 70 bytes by using references in condensed for loops * Replace count-if because it seems to result in smaller code (46 bytes) * Another find_if replacement * Replaced some find_if...we are down to 92 bytes on qpg * Replaced one more find-if, saving another 88 bytes of flash * Removed algorithm includes: these are slow and would like compile to fail if used * Save more flash ... we should be at a net negative now * This seems to save even more * More savings by more encode overrides ... this is silly... * Fix typos * More explicit casting, removed 64-bit overrides * Added one more check for freeing memory .Still need to track a leak that darwin finds * Fix memory leak in assignment * Self-review: fix includes * Self-review: fix includes * Self-review: fix includes * Self-review: fix includes * Update src/data-model-providers/codegen/CodegenDataModelProvider.cpp Co-authored-by: Boris Zbarsky * Update src/data-model-providers/codegen/CodegenDataModelProvider.cpp Co-authored-by: Boris Zbarsky * Update src/app/AttributePathExpandIterator.h Co-authored-by: Boris Zbarsky * Update src/app/AttributePathExpandIterator.h Co-authored-by: Boris Zbarsky * Update src/app/clusters/descriptor/descriptor.cpp Co-authored-by: Boris Zbarsky * Update src/app/clusters/descriptor/descriptor.cpp Co-authored-by: Boris Zbarsky * Update src/app/data-model-provider/MetadataList.cpp Co-authored-by: Boris Zbarsky * Fix spelling for acquire * Update src/app/data-model-provider/MetadataList.h Co-authored-by: Boris Zbarsky * Update src/app/data-model-provider/MetadataList.h Co-authored-by: Boris Zbarsky * Update src/app/data-model-provider/MetadataList.h Co-authored-by: Boris Zbarsky * Fix is_trivially_destructible * Update src/app/data-model-provider/MetadataSearch.h Co-authored-by: Boris Zbarsky * More fixes * Fix includes * Fix invalid check typo * Fix comment * Correct comment * Updated comment * Make logic between clusters/attributes/endpoints the same regarding nullopt and wildcards * Restyle * Update logic for IsDescentantof * Help compiler generate efficient code as we keep reusing the same pointer * clearer logic that we handle all cases * Fixes * Use calloc * Update comment * Fix include * Fix casting * Make cluster count functions from ember public API since they seem reusable * Also fix dynamic dispatch * update enumeration entry * another comment update * Undo submodule update * Rename metadata search to metadta lookup * Update metadata list methods to be all uppercase * Fix more renames * Fix invalid cast * Update src/data-model-providers/codegen/CodegenDataModelProvider.cpp Co-authored-by: Boris Zbarsky * Update src/data-model-providers/codegen/CodegenDataModelProvider.cpp Co-authored-by: Boris Zbarsky * Update src/data-model-providers/codegen/CodegenDataModelProvider.cpp Co-authored-by: Boris Zbarsky * Update src/app/data-model-provider/MetadataList.h Co-authored-by: Boris Zbarsky * Address comments * fix bug * Update src/app/AttributePathExpandIterator.cpp Co-authored-by: Mathieu Kardous <84793247+mkardous-silabs@users.noreply.github.com> * Update src/app/WriteHandler.cpp Co-authored-by: Mathieu Kardous <84793247+mkardous-silabs@users.noreply.github.com> * Update src/app/AttributePathExpandIterator.cpp Co-authored-by: Mathieu Kardous <84793247+mkardous-silabs@users.noreply.github.com> * Restyle and include update --------- Co-authored-by: Andrei Litvin Co-authored-by: Tennessee Carmel-Veilleux Co-authored-by: Boris Zbarsky Co-authored-by: Mathieu Kardous <84793247+mkardous-silabs@users.noreply.github.com> --- .../common/pigweed/rpc_services/Attributes.h | 5 +- src/access/ProviderDeviceTypeResolver.h | 6 +- src/app/AttributePathExpandIterator.cpp | 173 ++- src/app/AttributePathExpandIterator.h | 22 +- src/app/AttributeValueEncoder.h | 28 + src/app/InteractionModelEngine.cpp | 70 +- src/app/InteractionModelEngine.h | 10 +- src/app/WriteHandler.cpp | 5 +- src/app/clusters/descriptor/descriptor.cpp | 156 +-- src/app/clusters/descriptor/descriptor.h | 2 + .../microwave-oven-control-server.cpp | 14 +- src/app/data-model-provider/BUILD.gn | 5 +- src/app/data-model-provider/MetadataList.cpp | 133 +++ src/app/data-model-provider/MetadataList.h | 155 +++ .../data-model-provider/MetadataLookup.cpp | 83 ++ src/app/data-model-provider/MetadataLookup.h | 91 ++ src/app/data-model-provider/MetadataTypes.cpp | 50 - src/app/data-model-provider/MetadataTypes.h | 113 +- src/app/data-model-provider/Provider.h | 5 +- src/app/data-model-provider/tests/BUILD.gn | 1 + .../tests/TestMetadataList.cpp | 396 +++++++ src/app/dynamic_server/DynamicDispatcher.cpp | 8 + src/app/reporting/Engine.cpp | 28 +- src/app/reporting/reporting.cpp | 19 +- .../tests/TestAttributePathExpandIterator.cpp | 29 +- src/app/tests/test-interaction-model-api.cpp | 102 -- src/app/tests/test-interaction-model-api.h | 26 +- src/app/util/attribute-storage.cpp | 4 - src/app/util/attribute-storage.h | 4 + src/app/util/mock/attribute-storage.cpp | 8 + src/controller/tests/TestEventCaching.cpp | 12 +- .../tests/TestEventNumberCaching.cpp | 8 +- .../tests/data_model/DataModelFixtures.cpp | 102 -- .../tests/data_model/DataModelFixtures.h | 26 +- .../codegen/CodegenDataModelProvider.cpp | 989 +++++++----------- .../codegen/CodegenDataModelProvider.h | 149 +-- .../CodegenDataModelProvider_Write.cpp | 7 +- .../tests/TestCodegenModelViaMocks.cpp | 801 ++++---------- 38 files changed, 1920 insertions(+), 1925 deletions(-) create mode 100644 src/app/data-model-provider/MetadataList.cpp create mode 100644 src/app/data-model-provider/MetadataList.h create mode 100644 src/app/data-model-provider/MetadataLookup.cpp create mode 100644 src/app/data-model-provider/MetadataLookup.h delete mode 100644 src/app/data-model-provider/MetadataTypes.cpp create mode 100644 src/app/data-model-provider/tests/TestMetadataList.cpp diff --git a/examples/common/pigweed/rpc_services/Attributes.h b/examples/common/pigweed/rpc_services/Attributes.h index 261831ba9b6223..c7f102a0e6ba24 100644 --- a/examples/common/pigweed/rpc_services/Attributes.h +++ b/examples/common/pigweed/rpc_services/Attributes.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -221,7 +222,9 @@ class Attributes : public pw_rpc::nanopb::Attributes::Service request.operationFlags.Set(app::DataModel::OperationFlags::kInternal); request.subjectDescriptor = &subjectDescriptor; - std::optional info = provider->GetServerClusterInfo(path); + app::DataModel::ServerClusterFinder serverClusterFinder(provider); + auto info = serverClusterFinder.Find(path); + if (!info.has_value()) { return ::pw::Status::NotFound(); diff --git a/src/access/ProviderDeviceTypeResolver.h b/src/access/ProviderDeviceTypeResolver.h index 0a215d4a899930..404c0cfa56c1af 100644 --- a/src/access/ProviderDeviceTypeResolver.h +++ b/src/access/ProviderDeviceTypeResolver.h @@ -31,10 +31,10 @@ class DynamicProviderDeviceTypeResolver : public chip::Access::AccessControl::De bool IsDeviceTypeOnEndpoint(chip::DeviceTypeId deviceType, chip::EndpointId endpoint) override { - app::DataModel::Provider * model = mModelGetter(); - for (auto type = model->FirstDeviceType(endpoint); type.has_value(); type = model->NextDeviceType(endpoint, *type)) + auto deviceTypes = mModelGetter()->DeviceTypes(endpoint); + for (auto & type : deviceTypes.GetSpanValidForLifetime()) { - if (type->deviceTypeId == deviceType) + if (type.deviceTypeId == deviceType) { return true; } diff --git a/src/app/AttributePathExpandIterator.cpp b/src/app/AttributePathExpandIterator.cpp index 23273c7c58fcf3..bd8e3e018b0155 100644 --- a/src/app/AttributePathExpandIterator.cpp +++ b/src/app/AttributePathExpandIterator.cpp @@ -17,6 +17,9 @@ #include #include +#include +#include +#include #include #include @@ -26,6 +29,10 @@ using namespace chip::app::DataModel; namespace chip { namespace app { +AttributePathExpandIterator::AttributePathExpandIterator(DataModel::Provider * dataModel, Position & position) : + mDataModelProvider(dataModel), mPosition(position) +{} + bool AttributePathExpandIterator::AdvanceOutputPath() { /// Output path invariants @@ -113,34 +120,61 @@ bool AttributePathExpandIterator::IsValidAttributeId(AttributeId attributeId) break; } + DataModel::AttributeFinder finder(mDataModelProvider); + const ConcreteAttributePath attributePath(mPosition.mOutputPath.mEndpointId, mPosition.mOutputPath.mClusterId, attributeId); - return mDataModelProvider->GetAttributeInfo(attributePath).has_value(); + return finder.Find(attributePath).has_value(); } std::optional AttributePathExpandIterator::NextAttributeId() { if (mPosition.mOutputPath.mAttributeId == kInvalidAttributeId) { - if (mPosition.mAttributePath->mValue.HasWildcardAttributeId()) + // Attribute ID is tied to attribute index. If no attribute id is available yet + // this means the index is invalid. Processing logic in output advance only resets + // attribute ID to invalid when resetting iteration. + mAttributeIndex = kInvalidIndex; + } + + if (mAttributeIndex == kInvalidIndex) + { + // start a new iteration of attributes on the current cluster path. + mAttributes = mDataModelProvider->Attributes(mPosition.mOutputPath); + + if (mPosition.mOutputPath.mAttributeId != kInvalidAttributeId) { - AttributeEntry entry = mDataModelProvider->FirstAttribute(mPosition.mOutputPath); - return entry.IsValid() // - ? entry.path.mAttributeId // - : Clusters::Globals::Attributes::GeneratedCommandList::Id; // + // Position on the correct attribute if we have a start point + mAttributeIndex = 0; + while ((mAttributeIndex < mAttributes.Size()) && + (mAttributes[mAttributeIndex].attributeId != mPosition.mOutputPath.mAttributeId)) + { + mAttributeIndex++; + } } + } - // At this point, the attributeID is NOT a wildcard (i.e. it is fixed). - // - // For wildcard expansion, we validate that this is a valid attribute for the given - // cluster on the given endpoint. If not a wildcard expansion, return it as-is. - if (mPosition.mAttributePath->mValue.IsWildcardPath()) + if (mPosition.mOutputPath.mAttributeId == kInvalidAttributeId) + { + if (!mPosition.mAttributePath->mValue.HasWildcardAttributeId()) { - if (!IsValidAttributeId(mPosition.mAttributePath->mValue.mAttributeId)) + // The attributeID is NOT a wildcard (i.e. it is fixed). + // + // For wildcard expansion, we validate that this is a valid attribute for the given + // cluster on the given endpoint. If not a wildcard expansion, return it as-is. + if (mPosition.mAttributePath->mValue.IsWildcardPath()) { - return std::nullopt; + if (!IsValidAttributeId(mPosition.mAttributePath->mValue.mAttributeId)) + { + return std::nullopt; + } } + return mPosition.mAttributePath->mValue.mAttributeId; } - return mPosition.mAttributePath->mValue.mAttributeId; + mAttributeIndex = 0; + } + else + { + mAttributeIndex++; } // Advance the existing attribute id if it can be advanced. @@ -165,10 +199,9 @@ std::optional AttributePathExpandIterator::NextAttributeId() return std::nullopt; } - AttributeEntry entry = mDataModelProvider->NextAttribute(mPosition.mOutputPath); - if (entry.IsValid()) + if (mAttributeIndex < mAttributes.Size()) { - return entry.path.mAttributeId; + return mAttributes[mAttributeIndex].attributeId; } // Finished the data model, start with global attributes @@ -178,55 +211,113 @@ std::optional AttributePathExpandIterator::NextAttributeId() std::optional AttributePathExpandIterator::NextClusterId() { - if (mPosition.mOutputPath.mClusterId == kInvalidClusterId) { - if (mPosition.mAttributePath->mValue.HasWildcardClusterId()) + // Cluster ID is tied to cluster index. If no cluster id available yet + // this means index is invalid. Processing logic in output advance only resets + // cluster ID to invalid when resetting iteration. + mClusterIndex = kInvalidIndex; + } + + if (mClusterIndex == kInvalidIndex) + { + // start a new iteration on the current endpoint + mClusters = mDataModelProvider->ServerClusters(mPosition.mOutputPath.mEndpointId); + + if (mPosition.mOutputPath.mClusterId != kInvalidClusterId) { - ClusterEntry entry = mDataModelProvider->FirstServerCluster(mPosition.mOutputPath.mEndpointId); - return entry.IsValid() ? std::make_optional(entry.path.mClusterId) : std::nullopt; + // Position on the correct cluster if we have a start point + mClusterIndex = 0; + while ((mClusterIndex < mClusters.Size()) && (mClusters[mClusterIndex].clusterId != mPosition.mOutputPath.mClusterId)) + { + mClusterIndex++; + } } + } - // At this point, the clusterID is NOT a wildcard (i.e. is fixed). - // - // For wildcard expansion, we validate that this is a valid cluster for the endpoint. - // If non-wildcard expansion, we return as-is. - if (mPosition.mAttributePath->mValue.IsWildcardPath()) + if (mPosition.mOutputPath.mClusterId == kInvalidClusterId) + { + + if (!mPosition.mAttributePath->mValue.HasWildcardClusterId()) { - const ConcreteClusterPath clusterPath(mPosition.mOutputPath.mEndpointId, mPosition.mAttributePath->mValue.mClusterId); - if (!mDataModelProvider->GetServerClusterInfo(clusterPath).has_value()) + // The clusterID is NOT a wildcard (i.e. is fixed). + // + // For wildcard expansion, we validate that this is a valid cluster for the endpoint. + // If non-wildcard expansion, we return as-is. + if (mPosition.mAttributePath->mValue.IsWildcardPath()) { - return std::nullopt; + const ClusterId clusterId = mPosition.mAttributePath->mValue.mClusterId; + + auto span = mClusters.GetSpanValidForLifetime(); + + bool found = false; + for (auto & entry : span) + { + if (entry.clusterId == clusterId) + { + found = true; + break; + } + } + + if (!found) + { + return std::nullopt; + } } - } - return mPosition.mAttributePath->mValue.mClusterId; + return mPosition.mAttributePath->mValue.mClusterId; + } + mClusterIndex = 0; + } + else + { + mClusterIndex++; } VerifyOrReturnValue(mPosition.mAttributePath->mValue.HasWildcardClusterId(), std::nullopt); + VerifyOrReturnValue(mClusterIndex < mClusters.Size(), std::nullopt); - ClusterEntry entry = mDataModelProvider->NextServerCluster(mPosition.mOutputPath); - return entry.IsValid() ? std::make_optional(entry.path.mClusterId) : std::nullopt; + return mClusters[mClusterIndex].clusterId; } -std::optional AttributePathExpandIterator::NextEndpointId() +std::optional AttributePathExpandIterator::NextEndpointId() { + if (mEndpointIndex == kInvalidIndex) + { + // index is missing, have to start a new iteration + mEndpoints = mDataModelProvider->Endpoints(); + + if (mPosition.mOutputPath.mEndpointId != kInvalidEndpointId) + { + // Position on the correct endpoint if we have a start point + mEndpointIndex = 0; + while ((mEndpointIndex < mEndpoints.Size()) && (mEndpoints[mEndpointIndex].id != mPosition.mOutputPath.mEndpointId)) + { + mEndpointIndex++; + } + } + } + if (mPosition.mOutputPath.mEndpointId == kInvalidEndpointId) { - if (mPosition.mAttributePath->mValue.HasWildcardEndpointId()) + if (!mPosition.mAttributePath->mValue.HasWildcardEndpointId()) { - EndpointEntry ep = mDataModelProvider->FirstEndpoint(); - return (ep.id != kInvalidEndpointId) ? std::make_optional(ep.id) : std::nullopt; + return mPosition.mAttributePath->mValue.mEndpointId; } - return mPosition.mAttributePath->mValue.mEndpointId; + // start from the beginning + mEndpointIndex = 0; + } + else + { + mEndpointIndex++; } - // Expand endpoints only if it is a wildcard on the endpoint specifically. VerifyOrReturnValue(mPosition.mAttributePath->mValue.HasWildcardEndpointId(), std::nullopt); + VerifyOrReturnValue(mEndpointIndex < mEndpoints.Size(), std::nullopt); - EndpointEntry ep = mDataModelProvider->NextEndpoint(mPosition.mOutputPath.mEndpointId); - return (ep.id != kInvalidEndpointId) ? std::make_optional(ep.id) : std::nullopt; + return mEndpoints[mEndpointIndex].id; } } // namespace app diff --git a/src/app/AttributePathExpandIterator.h b/src/app/AttributePathExpandIterator.h index 2916a2e3bb7b18..75993d80ae5297 100644 --- a/src/app/AttributePathExpandIterator.h +++ b/src/app/AttributePathExpandIterator.h @@ -19,9 +19,14 @@ #include #include +#include +#include #include #include #include +#include + +#include namespace chip { namespace app { @@ -96,9 +101,7 @@ class AttributePathExpandIterator ConcreteAttributePath mOutputPath; }; - AttributePathExpandIterator(DataModel::Provider * dataModel, Position & position) : - mDataModelProvider(dataModel), mPosition(position) - {} + AttributePathExpandIterator(DataModel::Provider * dataModel, Position & position); // This class may not be copied. A new one should be created when needed and they // should not overlap. @@ -113,9 +116,20 @@ class AttributePathExpandIterator bool Next(ConcreteAttributePath & path); private: + static constexpr size_t kInvalidIndex = std::numeric_limits::max(); + DataModel::Provider * mDataModelProvider; Position & mPosition; + DataModel::MetadataList mEndpoints; // all endpoints + size_t mEndpointIndex = kInvalidIndex; + + DataModel::MetadataList mClusters; // all clusters ON THE CURRENT endpoint + size_t mClusterIndex = kInvalidIndex; + + DataModel::MetadataList mAttributes; // all attributes ON THE CURRENT cluster + size_t mAttributeIndex = kInvalidIndex; + /// Move to the next endpoint/cluster/attribute triplet that is valid given /// the current mOutputPath and mpAttributePath. /// @@ -140,7 +154,7 @@ class AttributePathExpandIterator /// Will start from the beginning if current mOutputPath.mEndpointId is kInvalidEndpointId /// /// Respects path expansion/values in mpAttributePath - std::optional NextEndpointId(); + std::optional NextEndpointId(); /// Checks if the given attributeId is valid for the current mOutputPath(endpoint/cluster) /// diff --git a/src/app/AttributeValueEncoder.h b/src/app/AttributeValueEncoder.h index 39bbe9cb03545b..a515164cda5d27 100644 --- a/src/app/AttributeValueEncoder.h +++ b/src/app/AttributeValueEncoder.h @@ -64,6 +64,34 @@ class AttributeValueEncoder return mAttributeValueEncoder.EncodeListItem(mCheckpoint, aArg); } + // overrides that save flash: no need to care about the extra const + // Without this, we have a usage of: + // chip::ChipError chip::app::AttributeValueEncoder::EncodeListItem + // Overall we tend to have very similar code expand (from nm): + // chip::ChipError chip::app::AttributeValueEncoder::Encode(unsigned char&&) + // chip::ChipError chip::app::AttributeValueEncoder::Encode(unsigned char&) + // chip::ChipError chip::app::AttributeValueEncoder::Encode(unsigned char const&) + // chip::ChipError chip::app::AttributeValueEncoder::Encode(unsigned short const&) + // chip::ChipError chip::app::AttributeValueEncoder::Encode(unsigned long&) + // chip::ChipError chip::app::AttributeValueEncoder::Encode(unsigned short&) + // chip::ChipError chip::app::AttributeValueEncoder::Encode(unsigned long long&) + // chip::ChipError chip::app::AttributeValueEncoder::Encode(unsigned short&&) + // chip::ChipError chip::app::AttributeValueEncoder::Encode(unsigned long long&&) + // that we try to reduce + // + // TODO: + // - we should figure where the extra const override is used + // - we should try to avoid having such footguns. This list template-explosion seems + // dangerous for flash. + // + // This relies on TLV numbers always being encoded as 64-bit value + inline CHIP_ERROR Encode(uint32_t const & aArg) const { return Encode(aArg); } + inline CHIP_ERROR Encode(uint32_t & aArg) const { return Encode(aArg); } + inline CHIP_ERROR Encode(uint16_t const & aArg) const { return Encode(aArg); } + inline CHIP_ERROR Encode(uint16_t & aArg) const { return Encode(aArg); } + inline CHIP_ERROR Encode(uint8_t const & aArg) const { return Encode(aArg); } + inline CHIP_ERROR Encode(uint8_t & aArg) const { return Encode(aArg); } + private: AttributeValueEncoder & mAttributeValueEncoder; // Avoid calling the TLVWriter constructor for every instantiation of diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index d4afe3347fe271..325c0dd416ec80 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -33,9 +33,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -89,14 +91,14 @@ bool MayHaveAccessibleEventPathForEndpoint(DataModel::Provider * aProvider, Endp aSubjectDescriptor); } - DataModel::ClusterEntry clusterEntry = aProvider->FirstServerCluster(aEventPath.mEndpointId); - while (clusterEntry.IsValid()) + auto serverClusters = aProvider->ServerClusters(aEventPath.mEndpointId); + for (auto & cluster : serverClusters.GetSpanValidForLifetime()) { - if (MayHaveAccessibleEventPathForEndpointAndCluster(clusterEntry.path, aEventPath, aSubjectDescriptor)) + if (MayHaveAccessibleEventPathForEndpointAndCluster(ConcreteClusterPath(aEventPath.mEndpointId, cluster.clusterId), + aEventPath, aSubjectDescriptor)) { return true; } - clusterEntry = aProvider->NextServerCluster(clusterEntry.path); } return false; @@ -112,7 +114,8 @@ bool MayHaveAccessibleEventPath(DataModel::Provider * aProvider, const EventPath return MayHaveAccessibleEventPathForEndpoint(aProvider, aEventPath.mEndpointId, aEventPath, subjectDescriptor); } - for (DataModel::EndpointEntry ep = aProvider->FirstEndpoint(); ep.IsValid(); ep = aProvider->NextEndpoint(ep.id)) + auto endpoints = aProvider->Endpoints(); + for (const DataModel::EndpointEntry & ep : endpoints.GetSpanValidForLifetime()) { if (MayHaveAccessibleEventPathForEndpoint(aProvider, ep.id, aEventPath, subjectDescriptor)) { @@ -1563,7 +1566,8 @@ CHIP_ERROR InteractionModelEngine::PushFrontAttributePathList(SingleLinkedListNo bool InteractionModelEngine::IsExistentAttributePath(const ConcreteAttributePath & path) { - return GetDataModelProvider()->GetAttributeInfo(path).has_value(); + DataModel::AttributeFinder finder(mDataModelProvider); + return finder.Find(path).has_value(); } void InteractionModelEngine::RemoveDuplicateConcreteAttributePath(SingleLinkedListNode *& aAttributePaths) @@ -1711,7 +1715,9 @@ void InteractionModelEngine::DispatchCommand(CommandHandlerImpl & apCommandObj, Protocols::InteractionModel::Status InteractionModelEngine::ValidateCommandCanBeDispatched(const DataModel::InvokeRequest & request) { - Status status = CheckCommandExistence(request.path); + DataModel::AcceptedCommandEntry acceptedCommandEntry; + + Status status = CheckCommandExistence(request.path, acceptedCommandEntry); if (status != Status::Success) { @@ -1720,13 +1726,14 @@ Protocols::InteractionModel::Status InteractionModelEngine::ValidateCommandCanBe return status; } - status = CheckCommandAccess(request); + status = CheckCommandAccess(request, acceptedCommandEntry); VerifyOrReturnValue(status == Status::Success, status); - return CheckCommandFlags(request); + return CheckCommandFlags(request, acceptedCommandEntry); } -Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandAccess(const DataModel::InvokeRequest & aRequest) +Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandAccess(const DataModel::InvokeRequest & aRequest, + const DataModel::AcceptedCommandEntry & entry) { if (aRequest.subjectDescriptor == nullptr) { @@ -1737,11 +1744,8 @@ Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandAccess(c .endpoint = aRequest.path.mEndpointId, .requestType = Access::RequestType::kCommandInvokeRequest, .entityId = aRequest.path.mCommandId }; - std::optional commandInfo = mDataModelProvider->GetAcceptedCommandInfo(aRequest.path); - Access::Privilege minimumRequiredPrivilege = - commandInfo.has_value() ? commandInfo->invokePrivilege : Access::Privilege::kOperate; - CHIP_ERROR err = Access::GetAccessControl().Check(*aRequest.subjectDescriptor, requestPath, minimumRequiredPrivilege); + CHIP_ERROR err = Access::GetAccessControl().Check(*aRequest.subjectDescriptor, requestPath, entry.invokePrivilege); if (err != CHIP_NO_ERROR) { if ((err != CHIP_ERROR_ACCESS_DENIED) && (err != CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL)) @@ -1754,14 +1758,11 @@ Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandAccess(c return Status::Success; } -Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandFlags(const DataModel::InvokeRequest & aRequest) +Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandFlags(const DataModel::InvokeRequest & aRequest, + const DataModel::AcceptedCommandEntry & entry) { - std::optional commandInfo = mDataModelProvider->GetAcceptedCommandInfo(aRequest.path); - // This is checked by previous validations, so it should not happen - VerifyOrDie(commandInfo.has_value()); - - const bool commandNeedsTimedInvoke = commandInfo->flags.Has(DataModel::CommandQualityFlags::kTimed); - const bool commandIsFabricScoped = commandInfo->flags.Has(DataModel::CommandQualityFlags::kFabricScoped); + const bool commandNeedsTimedInvoke = entry.flags.Has(DataModel::CommandQualityFlags::kTimed); + const bool commandIsFabricScoped = entry.flags.Has(DataModel::CommandQualityFlags::kFabricScoped); if (commandNeedsTimedInvoke && !aRequest.invokeFlags.Has(DataModel::InvokeFlags::kTimed)) { @@ -1784,26 +1785,35 @@ Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandFlags(co return Status::Success; } -Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandExistence(const ConcreteCommandPath & aCommandPath) +Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandExistence(const ConcreteCommandPath & aCommandPath, + DataModel::AcceptedCommandEntry & entry) { auto provider = GetDataModelProvider(); - if (provider->GetAcceptedCommandInfo(aCommandPath).has_value()) + + DataModel::MetadataList acceptedCommands = provider->AcceptedCommands(aCommandPath); + for (auto & existing : acceptedCommands.GetSpanValidForLifetime()) { - return Protocols::InteractionModel::Status::Success; + if (existing.commandId == aCommandPath.mCommandId) + { + entry = existing; + return Protocols::InteractionModel::Status::Success; + } } - // We failed, figure out why ... - // - if (provider->GetServerClusterInfo(aCommandPath).has_value()) { - return Protocols::InteractionModel::Status::UnsupportedCommand; // cluster exists, so command is invalid + DataModel::ServerClusterFinder finder(provider); + if (finder.Find(aCommandPath).has_value()) + { + // cluster exists, so command is invalid + return Protocols::InteractionModel::Status::UnsupportedCommand; + } } // At this point either cluster or endpoint does not exist. If we find the endpoint, then the cluster // is invalid - for (DataModel::EndpointEntry ep = provider->FirstEndpoint(); ep.IsValid(); ep = provider->NextEndpoint(ep.id)) { - if (ep.id == aCommandPath.mEndpointId) + DataModel::EndpointFinder finder(provider); + if (finder.Find(aCommandPath.mEndpointId)) { // endpoint exists, so cluster is invalid return Protocols::InteractionModel::Status::UnsupportedCluster; diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h index c5a4a0811d274b..362cdecab9910a 100644 --- a/src/app/InteractionModelEngine.h +++ b/src/app/InteractionModelEngine.h @@ -49,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -614,9 +615,12 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, void ShutdownMatchingSubscriptions(const Optional & aFabricIndex = NullOptional, const Optional & aPeerNodeId = NullOptional); - Status CheckCommandExistence(const ConcreteCommandPath & aCommandPath); - Status CheckCommandAccess(const DataModel::InvokeRequest & aRequest); - Status CheckCommandFlags(const DataModel::InvokeRequest & aRequest); + /** + * Validates that the command exists and on success returns the data for the command in `entry`. + */ + Status CheckCommandExistence(const ConcreteCommandPath & aCommandPath, DataModel::AcceptedCommandEntry & entry); + Status CheckCommandAccess(const DataModel::InvokeRequest & aRequest, const DataModel::AcceptedCommandEntry & entry); + Status CheckCommandFlags(const DataModel::InvokeRequest & aRequest, const DataModel::AcceptedCommandEntry & entry); /** * Check if the given attribute path is a valid path in the data model provider. diff --git a/src/app/WriteHandler.cpp b/src/app/WriteHandler.cpp index 1e129ad0af941c..9847641b61d7f3 100644 --- a/src/app/WriteHandler.cpp +++ b/src/app/WriteHandler.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -110,7 +111,9 @@ std::optional WriteHandler::IsListAttributePath(const ConcreteAttributePat return std::nullopt; } - auto info = mDataModelProvider->GetAttributeInfo(path); + DataModel::AttributeFinder finder(mDataModelProvider); + std::optional info = finder.Find(path); + if (!info.has_value()) { return std::nullopt; diff --git a/src/app/clusters/descriptor/descriptor.cpp b/src/app/clusters/descriptor/descriptor.cpp index 7d8854bca1fb5f..ecc93d4a77267a 100644 --- a/src/app/clusters/descriptor/descriptor.cpp +++ b/src/app/clusters/descriptor/descriptor.cpp @@ -15,10 +15,7 @@ * limitations under the License. */ -/**************************************************************************** - * @file - * @brief Implementation for the Descriptor Server Cluster - ***************************************************************************/ +#include "descriptor.h" #include #include @@ -29,11 +26,11 @@ #include #include #include +#include +#include #include #include -#include "descriptor.h" - using namespace chip; using namespace chip::app; using namespace chip::app::Clusters; @@ -42,6 +39,38 @@ using namespace chip::app::Clusters::Descriptor::Attributes; namespace { +/// Figures out if `childId` is a descendant of `parentId` given some specific endpoint entries +bool IsDescendantOf(const DataModel::EndpointEntry * __restrict__ childEndpoint, const EndpointId parentId, + Span allEndpoints) +{ + // NOTE: this is not very efficient as we loop through all endpoints for each parent search + // however endpoint depth should not be as large. + while (true) + { + + VerifyOrReturnValue(childEndpoint != nullptr, false); + VerifyOrReturnValue(childEndpoint->parentId != parentId, true); + + // Parent endpoint id 0 is never here: EndpointEntry::parentId uses + // kInvalidEndpointId to reference no explicit endpoint. See `EndpointEntry` + // comments. + VerifyOrReturnValue(childEndpoint->parentId != kInvalidEndpointId, false); + + const auto lookupId = childEndpoint->parentId; + childEndpoint = nullptr; // we will look it up again + + // find the requested value in the array to get its parent + for (auto & ep : allEndpoints) + { + if (ep.id == lookupId) + { + childEndpoint = &ep; + break; + } + } + } +} + class DescriptorAttrAccess : public AttributeAccessInterface { public: @@ -75,11 +104,10 @@ CHIP_ERROR DescriptorAttrAccess::ReadFeatureMap(EndpointId endpoint, AttributeVa CHIP_ERROR DescriptorAttrAccess::ReadTagListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) { return aEncoder.EncodeList([&endpoint](const auto & encoder) -> CHIP_ERROR { - auto tag = InteractionModelEngine::GetInstance()->GetDataModelProvider()->GetFirstSemanticTag(endpoint); - while (tag.has_value()) + auto tags = InteractionModelEngine::GetInstance()->GetDataModelProvider()->SemanticTags(endpoint); + for (auto & tag : tags.GetSpanValidForLifetime()) { - ReturnErrorOnFailure(encoder.Encode(tag.value())); - tag = InteractionModelEngine::GetInstance()->GetDataModelProvider()->GetNextSemanticTag(endpoint, tag.value()); + ReturnErrorOnFailure(encoder.Encode(tag)); } return CHIP_NO_ERROR; }); @@ -87,69 +115,71 @@ CHIP_ERROR DescriptorAttrAccess::ReadTagListAttribute(EndpointId endpoint, Attri CHIP_ERROR DescriptorAttrAccess::ReadPartsAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) { - CHIP_ERROR err = CHIP_NO_ERROR; - - auto endpointInfo = InteractionModelEngine::GetInstance()->GetDataModelProvider()->GetEndpointInfo(endpoint); + auto endpoints = InteractionModelEngine::GetInstance()->GetDataModelProvider()->Endpoints(); if (endpoint == 0x00) { - err = aEncoder.EncodeList([](const auto & encoder) -> CHIP_ERROR { - auto endpointEntry = InteractionModelEngine::GetInstance()->GetDataModelProvider()->FirstEndpoint(); - while (endpointEntry.IsValid()) + return aEncoder.EncodeList([&endpoints](const auto & encoder) -> CHIP_ERROR { + for (auto & ep : endpoints.GetSpanValidForLifetime()) { - if (endpointEntry.id != 0) + if (ep.id == 0) { - ReturnErrorOnFailure(encoder.Encode(endpointEntry.id)); + continue; } - endpointEntry = InteractionModelEngine::GetInstance()->GetDataModelProvider()->NextEndpoint(endpointEntry.id); + ReturnErrorOnFailure(encoder.Encode(ep.id)); } return CHIP_NO_ERROR; }); } - else if (endpointInfo.has_value() && endpointInfo->compositionPattern == DataModel::EndpointCompositionPattern::kFullFamily) + + // find the given endpoint + unsigned idx = 0; + while (idx < endpoints.Size()) { - err = aEncoder.EncodeList([endpoint](const auto & encoder) -> CHIP_ERROR { - auto endpointEntry = InteractionModelEngine::GetInstance()->GetDataModelProvider()->FirstEndpoint(); - while (endpointEntry.IsValid()) + if (endpoints[idx].id == endpoint) + { + break; + } + idx++; + } + if (idx >= endpoints.Size()) + { + // not found + return CHIP_ERROR_NOT_FOUND; + } + + auto & endpointInfo = endpoints[idx]; + + switch (endpointInfo.compositionPattern) + { + case DataModel::EndpointCompositionPattern::kFullFamily: + // encodes ALL endpoints that have the specified endpoint as a descendant + return aEncoder.EncodeList([&endpoints, endpoint](const auto & encoder) -> CHIP_ERROR { + for (auto & ep : endpoints.GetSpanValidForLifetime()) { - EndpointId parentEndpointId = endpointEntry.info.parentId; - while (parentEndpointId != chip::kInvalidEndpointId) + if (IsDescendantOf(&ep, endpoint, endpoints.GetSpanValidForLifetime())) { - if (parentEndpointId == endpoint) - { - ReturnErrorOnFailure(encoder.Encode(endpointEntry.id)); - break; - } - auto parentEndpointInfo = - InteractionModelEngine::GetInstance()->GetDataModelProvider()->GetEndpointInfo(parentEndpointId); - if (!parentEndpointInfo.has_value()) - { - break; - } - parentEndpointId = parentEndpointInfo->parentId; + ReturnErrorOnFailure(encoder.Encode(ep.id)); } - endpointEntry = InteractionModelEngine::GetInstance()->GetDataModelProvider()->NextEndpoint(endpointEntry.id); } - return CHIP_NO_ERROR; }); - } - else if (endpointInfo.has_value() && endpointInfo->compositionPattern == DataModel::EndpointCompositionPattern::kTree) - { - err = aEncoder.EncodeList([endpoint](const auto & encoder) -> CHIP_ERROR { - auto endpointEntry = InteractionModelEngine::GetInstance()->GetDataModelProvider()->FirstEndpoint(); - while (endpointEntry.IsValid()) + + case DataModel::EndpointCompositionPattern::kTree: + return aEncoder.EncodeList([&endpoints, endpoint](const auto & encoder) -> CHIP_ERROR { + for (auto & ep : endpoints.GetSpanValidForLifetime()) { - if (endpointEntry.info.parentId == endpoint) + if (ep.parentId != endpoint) { - ReturnErrorOnFailure(encoder.Encode(endpointEntry.id)); + continue; } - endpointEntry = InteractionModelEngine::GetInstance()->GetDataModelProvider()->NextEndpoint(endpointEntry.id); + ReturnErrorOnFailure(encoder.Encode(ep.id)); } return CHIP_NO_ERROR; }); } - - return err; + // not actually reachable and compiler will validate we + // handle all switch cases above + return CHIP_NO_ERROR; } CHIP_ERROR DescriptorAttrAccess::ReadDeviceAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) @@ -157,14 +187,12 @@ CHIP_ERROR DescriptorAttrAccess::ReadDeviceAttribute(EndpointId endpoint, Attrib CHIP_ERROR err = aEncoder.EncodeList([&endpoint](const auto & encoder) -> CHIP_ERROR { Descriptor::Structs::DeviceTypeStruct::Type deviceStruct; - auto deviceType = InteractionModelEngine::GetInstance()->GetDataModelProvider()->FirstDeviceType(endpoint); - - while (deviceType.has_value()) + auto deviceTypes = InteractionModelEngine::GetInstance()->GetDataModelProvider()->DeviceTypes(endpoint); + for (auto & type : deviceTypes.GetSpanValidForLifetime()) { - deviceStruct.deviceType = deviceType->deviceTypeId; - deviceStruct.revision = deviceType->deviceTypeRevision; + deviceStruct.deviceType = type.deviceTypeId; + deviceStruct.revision = type.deviceTypeRevision; ReturnErrorOnFailure(encoder.Encode(deviceStruct)); - deviceType = InteractionModelEngine::GetInstance()->GetDataModelProvider()->NextDeviceType(endpoint, *deviceType); } return CHIP_NO_ERROR; @@ -178,21 +206,18 @@ CHIP_ERROR DescriptorAttrAccess::ReadClientServerAttribute(EndpointId endpoint, CHIP_ERROR err = aEncoder.EncodeList([&endpoint, server](const auto & encoder) -> CHIP_ERROR { if (server) { - auto clusterEntry = InteractionModelEngine::GetInstance()->GetDataModelProvider()->FirstServerCluster(endpoint); - while (clusterEntry.IsValid()) + auto clusters = InteractionModelEngine::GetInstance()->GetDataModelProvider()->ServerClusters(endpoint); + for (auto & cluster : clusters.GetSpanValidForLifetime()) { - ReturnErrorOnFailure(encoder.Encode(clusterEntry.path.mClusterId)); - clusterEntry = InteractionModelEngine::GetInstance()->GetDataModelProvider()->NextServerCluster(clusterEntry.path); + ReturnErrorOnFailure(encoder.Encode(cluster.clusterId)); } } else { - ConcreteClusterPath clusterPath = - InteractionModelEngine::GetInstance()->GetDataModelProvider()->FirstClientCluster(endpoint); - while (clusterPath.HasValidIds()) + auto clusters = InteractionModelEngine::GetInstance()->GetDataModelProvider()->ClientClusters(endpoint); + for (auto & id : clusters.GetSpanValidForLifetime()) { - ReturnErrorOnFailure(encoder.Encode(clusterPath.mClusterId)); - clusterPath = InteractionModelEngine::GetInstance()->GetDataModelProvider()->NextClientCluster(clusterPath); + ReturnErrorOnFailure(encoder.Encode(id)); } } @@ -242,6 +267,7 @@ CHIP_ERROR DescriptorAttrAccess::Read(const ConcreteReadAttributePath & aPath, A } return CHIP_NO_ERROR; } + } // anonymous namespace void MatterDescriptorPluginServerInitCallback() diff --git a/src/app/clusters/descriptor/descriptor.h b/src/app/clusters/descriptor/descriptor.h index 8dd4ea745fcf66..c116a40604068e 100644 --- a/src/app/clusters/descriptor/descriptor.h +++ b/src/app/clusters/descriptor/descriptor.h @@ -14,6 +14,8 @@ * limitations under the License. */ +#include + namespace chip { namespace app { namespace Clusters { diff --git a/src/app/clusters/microwave-oven-control-server/microwave-oven-control-server.cpp b/src/app/clusters/microwave-oven-control-server/microwave-oven-control-server.cpp index 75b7e7df182c21..bdb614789f4b9b 100644 --- a/src/app/clusters/microwave-oven-control-server/microwave-oven-control-server.cpp +++ b/src/app/clusters/microwave-oven-control-server/microwave-oven-control-server.cpp @@ -19,9 +19,11 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -247,10 +249,16 @@ void Instance::HandleSetCookingParameters(HandlerContext & ctx, const Commands:: if (startAfterSetting.HasValue()) { - ConcreteCommandPath commandPath(mEndpointId, OperationalState::Id, OperationalState::Commands::Start::Id); + DataModel::MetadataList acceptedCommands = + InteractionModelEngine::GetInstance()->GetDataModelProvider()->AcceptedCommands( + ConcreteClusterPath(mEndpointId, OperationalState::Id)); + Span acceptedCommandsSpan = acceptedCommands.GetSpanValidForLifetime(); + + bool commandExists = std::find_if(acceptedCommandsSpan.begin(), acceptedCommandsSpan.end(), + [](const DataModel::AcceptedCommandEntry & entry) { + return entry.commandId == OperationalState::Commands::Start::Id; + }) != acceptedCommandsSpan.end(); - bool commandExists = - InteractionModelEngine::GetInstance()->GetDataModelProvider()->GetAcceptedCommandInfo(commandPath).has_value(); VerifyOrExit( commandExists, status = Status::InvalidCommand; ChipLogError( Zcl, diff --git a/src/app/data-model-provider/BUILD.gn b/src/app/data-model-provider/BUILD.gn index 40f34db127d501..9e86e8f1084a1a 100644 --- a/src/app/data-model-provider/BUILD.gn +++ b/src/app/data-model-provider/BUILD.gn @@ -21,7 +21,10 @@ source_set("data-model-provider") { "ActionReturnStatus.h", "Context.h", "EventsGenerator.h", - "MetadataTypes.cpp", + "MetadataList.cpp", + "MetadataList.h", + "MetadataLookup.cpp", + "MetadataLookup.h", "MetadataTypes.h", "OperationTypes.h", "Provider.h", diff --git a/src/app/data-model-provider/MetadataList.cpp b/src/app/data-model-provider/MetadataList.cpp new file mode 100644 index 00000000000000..c1d5479f57a5a7 --- /dev/null +++ b/src/app/data-model-provider/MetadataList.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * + * 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. + */ +#include + +#include +#include +#include +#include + +namespace chip { +namespace app { +namespace DataModel { +namespace detail { + +GenericMetadataList::~GenericMetadataList() +{ + Invalidate(); +} + +GenericMetadataList & GenericMetadataList::operator=(GenericMetadataList && other) +{ + if (this != &other) + { + // Generic metadata lists should not be used directly except for same-sized data + VerifyOrDie(this->mElementSize == other.mElementSize); + + if (mAllocated && (mBuffer != nullptr)) + { + chip::Platform::MemoryFree(mBuffer); + } + + this->mAllocated = other.mAllocated; + this->mBuffer = other.mBuffer; + this->mElementCount = other.mElementCount; + this->mCapacity = other.mCapacity; + this->mIsImmutable = other.mIsImmutable; + + other.mAllocated = false; + other.mBuffer = nullptr; + other.mElementCount = 0; + other.mCapacity = 0; + other.mIsImmutable = true; + } + return *this; +} + +const void * GenericMetadataList::operator[](size_t index) const +{ + VerifyOrDie(index < mElementCount); + return mBuffer + index * mElementSize; +} + +CHIP_ERROR GenericMetadataList::Reserve(size_t numElements) +{ + VerifyOrReturnError(!mIsImmutable, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mElementCount == 0, CHIP_ERROR_INCORRECT_STATE); + + if ((mBuffer != nullptr) && mAllocated) + { + chip::Platform::MemoryFree(mBuffer); + } + + mBuffer = static_cast(chip::Platform::MemoryCalloc(numElements, mElementSize)); + + VerifyOrReturnError(mBuffer != nullptr, CHIP_ERROR_NO_MEMORY); + + mAllocated = true; + mCapacity = numElements; + return CHIP_NO_ERROR; +} + +CHIP_ERROR GenericMetadataList::AppendRaw(const void * buffer) +{ + VerifyOrReturnError(!mIsImmutable, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mElementCount < mCapacity, CHIP_ERROR_NO_MEMORY); + memcpy(mBuffer + mElementCount * mElementSize, buffer, mElementSize); + mElementCount++; + return CHIP_NO_ERROR; +} + +void GenericMetadataList::Invalidate() +{ + if ((mBuffer != nullptr) && mAllocated) + { + chip::Platform::MemoryFree(mBuffer); + mBuffer = nullptr; + mAllocated = false; + } + mCapacity = 0; + mElementCount = 0; + mCapacity = 0; + mIsImmutable = true; +} + +CHIP_ERROR GenericMetadataList::CopyExistingBuffer(const void * buffer, size_t elements) +{ + ReturnErrorOnFailure(Reserve(elements)); + memcpy(mBuffer, buffer, mElementSize * elements); + mIsImmutable = true; + mElementCount = elements; + mCapacity = elements; + return CHIP_NO_ERROR; +} + +void GenericMetadataList::AcquireExistingBuffer(const void * buffer, size_t elements) +{ + Invalidate(); + mAllocated = false; + mElementCount = elements; + mCapacity = elements; + mIsImmutable = true; + // NOTE: const cast, however we are marked as immutable and not allocated, + // so will never perform any writes on mBuffer's contents or try to deallocate it. + mBuffer = static_cast(const_cast(buffer)); +} + +} // namespace detail +} // namespace DataModel +} // namespace app +} // namespace chip diff --git a/src/app/data-model-provider/MetadataList.h b/src/app/data-model-provider/MetadataList.h new file mode 100644 index 00000000000000..37432363712939 --- /dev/null +++ b/src/app/data-model-provider/MetadataList.h @@ -0,0 +1,155 @@ +/* + * + * Copyright (c) 2024 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 +#include +#include +#include + +#include + +namespace chip { +namespace app { +namespace DataModel { + +namespace detail { + +// essentially a `void *` untyped metadata list base, +// so that actual functionality does not template-explode +class GenericMetadataList +{ +public: + GenericMetadataList(size_t elementSize) : mElementSize(elementSize) {} + ~GenericMetadataList(); + + GenericMetadataList(const GenericMetadataList &) = delete; + GenericMetadataList & operator=(const GenericMetadataList & other) = delete; + + GenericMetadataList(GenericMetadataList && other) { *this = std::move(other); } + + GenericMetadataList & operator=(GenericMetadataList && other); + const void * operator[](size_t index) const; + + CHIP_ERROR Reserve(size_t numElements); + void Invalidate(); + + size_t ElementCount() const { return mElementCount; } + size_t Capacity() const { return mCapacity; } + bool Empty() const { return mElementCount == 0; } + +protected: + /// Copy over the data from the given buffer + CHIP_ERROR CopyExistingBuffer(const void * buffer, size_t elements); + + /// use existing buffer AS IS, without taking ownership. + void AcquireExistingBuffer(const void * buffer, size_t elements); + + CHIP_ERROR AppendRaw(const void * buffer); + const void * RawBuffer() const { return mBuffer; } + + /// Marks a list as immutable and immutability is acquired + /// during const access (so this is const) + void SetImmutable() const { mIsImmutable = true; } + +private: + bool mAllocated = false; + + // buffer may point to either allocated or re-used (e.g. from const arrays) buffers. + // buffer is assumed allocated if mAllocated is true. + uint8_t * mBuffer = nullptr; + size_t mElementSize; + size_t mElementCount = 0; + size_t mCapacity = 0; + + // Set to true as soon as a span is obtained, since more writes may invalidate the span. + mutable bool mIsImmutable = false; +}; + +} // namespace detail + +template +class MetadataList : public detail::GenericMetadataList +{ +public: + using SpanType = Span; + + // we do not call destructors, just malloc things. + // Note that classes should also be trivially assignable (we do NOT call the assignment operator) + // This makes this class somewhat dangerous... + // + // Note: ideally we would want `is_trivially_copyable_v` as well however our chip::Optional + // implementation does not actually report that. + static_assert(std::is_trivially_destructible_v); + + MetadataList() : GenericMetadataList(sizeof(T)) {} + MetadataList(const MetadataList &) = delete; + MetadataList & operator=(const MetadataList & other) = delete; + + MetadataList(MetadataList && other) : GenericMetadataList(sizeof(T)) { *this = std::move(other); } + + MetadataList & operator=(MetadataList && other) + { + *static_cast(this) = std::move(other); + return *this; + } + + const T & operator[](size_t index) { return *static_cast(GenericMetadataList::operator[](index)); } + + template + static MetadataList FromArray(const std::array & arr) + { + MetadataList list; + + if (list.CopyExistingBuffer(arr.data(), arr.size()) != CHIP_NO_ERROR) + { + list.Invalidate(); + } + + return list; + } + + template + static MetadataList FromConstArray(const std::array & arr) + { + MetadataList list; + list.AcquireExistingBuffer(arr.data(), arr.size()); + return list; + } + + static MetadataList FromConstSpan(const Span & span) + { + MetadataList list; + list.AcquireExistingBuffer(span.data(), span.size()); + return list; + } + + size_t Size() const { return ElementCount(); } + + Span GetSpanValidForLifetime() const + { + SetImmutable(); + return Span(static_cast(RawBuffer()), ElementCount()); + } + + CHIP_ERROR Append(const T & value) { return AppendRaw(&value); } +}; + +} // namespace DataModel +} // namespace app +} // namespace chip diff --git a/src/app/data-model-provider/MetadataLookup.cpp b/src/app/data-model-provider/MetadataLookup.cpp new file mode 100644 index 00000000000000..be94807b6c287f --- /dev/null +++ b/src/app/data-model-provider/MetadataLookup.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * + * 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. + */ +#include + +namespace chip { +namespace app { +namespace DataModel { + +std::optional ServerClusterFinder::Find(const ConcreteClusterPath & path) +{ + VerifyOrReturnValue(mProvider != nullptr, std::nullopt); + + if (mEndpointId != path.mEndpointId) + { + mEndpointId = path.mEndpointId; + mClusterEntries = mProvider->ServerClusters(path.mEndpointId); + } + + auto serverClustersSpan = mClusterEntries.GetSpanValidForLifetime(); + + for (auto & clusterEntry : serverClustersSpan) + { + if (clusterEntry.clusterId == path.mClusterId) + { + return clusterEntry; + } + } + + return std::nullopt; +} + +std::optional AttributeFinder::Find(const ConcreteAttributePath & path) +{ + VerifyOrReturnValue(mProvider != nullptr, std::nullopt); + + if (mClusterPath != path) + { + mClusterPath = path; + mAttributes = mProvider->Attributes(path); + } + + auto attributesSpan = mAttributes.GetSpanValidForLifetime(); + for (auto & attributeEntry : attributesSpan) + { + if (attributeEntry.attributeId == path.mAttributeId) + { + return attributeEntry; + } + } + + return std::nullopt; +} + +std::optional EndpointFinder::Find(EndpointId endpointId) +{ + auto endpointsSpan = mEndpoints.GetSpanValidForLifetime(); + for (auto & endpointEntry : endpointsSpan) + { + if (endpointEntry.id == endpointId) + { + return endpointEntry; + } + } + + return std::nullopt; +} + +} // namespace DataModel +} // namespace app +} // namespace chip diff --git a/src/app/data-model-provider/MetadataLookup.h b/src/app/data-model-provider/MetadataLookup.h new file mode 100644 index 00000000000000..573b46d30c2ddc --- /dev/null +++ b/src/app/data-model-provider/MetadataLookup.h @@ -0,0 +1,91 @@ +/* + * + * Copyright (c) 2024 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 +#include +#include +#include +#include +#include + +#include + +namespace chip { +namespace app { +namespace DataModel { + +/// Helps search for a specific server cluster in the given +/// metadata provider. +/// +/// Facilitates the very common operation of "find a cluster on a given cluster path". +class ServerClusterFinder +{ +public: + ServerClusterFinder(ProviderMetadataTree * provider) : mProvider(provider) {} + + std::optional Find(const ConcreteClusterPath & path); + +private: + ProviderMetadataTree * mProvider; + EndpointId mEndpointId = kInvalidEndpointId; + MetadataList mClusterEntries; +}; + +/// Helps search for a specific server attribute in the given +/// metadata provider. +/// +/// Facilitates the very common operation of "find an attribute on a given attribute path". +class AttributeFinder +{ +public: + AttributeFinder(ProviderMetadataTree * provider) : mProvider(provider), mClusterPath(kInvalidEndpointId, kInvalidClusterId) {} + + std::optional Find(const ConcreteAttributePath & path); + +private: + ProviderMetadataTree * mProvider; + ConcreteClusterPath mClusterPath; + MetadataList mAttributes; +}; + +/// Helps search for a specific server endpoint in the given +/// metadata provider. +/// +/// Facilitates the operation of "find an endpoint with a given ID". +class EndpointFinder +{ +public: + EndpointFinder(ProviderMetadataTree * provider) : mProvider(provider) + { + if (mProvider != nullptr) + { + mEndpoints = mProvider->Endpoints(); + } + } + + std::optional Find(EndpointId endpointId); + +private: + ProviderMetadataTree * mProvider; + MetadataList mEndpoints; +}; + +} // namespace DataModel +} // namespace app +} // namespace chip diff --git a/src/app/data-model-provider/MetadataTypes.cpp b/src/app/data-model-provider/MetadataTypes.cpp deleted file mode 100644 index 2729698c49b1ed..00000000000000 --- a/src/app/data-model-provider/MetadataTypes.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2024 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. - */ -#include - -namespace chip { -namespace app { -namespace DataModel { - -const AttributeEntry AttributeEntry::kInvalid{ .path = ConcreteAttributePath(kInvalidEndpointId, kInvalidClusterId, - kInvalidAttributeId) }; - -const CommandEntry CommandEntry::kInvalid{ .path = ConcreteCommandPath(kInvalidEndpointId, kInvalidClusterId, kInvalidCommandId) }; - -const ClusterEntry ClusterEntry::kInvalid{ - .path = ConcreteClusterPath(kInvalidEndpointId, kInvalidClusterId), - .info = ClusterInfo(0 /* version */), // version of invalid cluster entry does not matter -}; - -const EndpointEntry EndpointEntry::kInvalid{ .id = kInvalidEndpointId, .info = EndpointInfo(kInvalidEndpointId) }; - -// A default implementation if just first/next exist -bool ProviderMetadataTree::EndpointExists(EndpointId endpoint) -{ - for (EndpointEntry ep = FirstEndpoint(); ep.IsValid(); ep = NextEndpoint(ep.id)) - { - if (ep.id == endpoint) - { - return true; - } - } - return false; -} - -} // namespace DataModel -} // namespace app -} // namespace chip diff --git a/src/app/data-model-provider/MetadataTypes.h b/src/app/data-model-provider/MetadataTypes.h index 9eeaa7d05049f1..8a8332c40f2d78 100644 --- a/src/app/data-model-provider/MetadataTypes.h +++ b/src/app/data-model-provider/MetadataTypes.h @@ -25,10 +25,12 @@ #include #include #include +#include #include #include #include #include +#include namespace chip { namespace app { @@ -50,29 +52,26 @@ enum class EndpointCompositionPattern : uint8_t kFullFamily = 0x2, }; -struct EndpointInfo +struct EndpointEntry { + EndpointId id; + // kInvalidEndpointId if there is no explicit parent endpoint (which means the parent is endpoint 0, // for endpoints other than endpoint 0). EndpointId parentId; EndpointCompositionPattern compositionPattern; - - explicit EndpointInfo(EndpointId parent) : parentId(parent), compositionPattern(EndpointCompositionPattern::kFullFamily) {} - EndpointInfo(EndpointId parent, EndpointCompositionPattern pattern) : parentId(parent), compositionPattern(pattern) {} }; -struct EndpointEntry +enum class ClusterQualityFlags : uint32_t { - EndpointId id; - EndpointInfo info; - - bool IsValid() const { return id != kInvalidEndpointId; } - static const EndpointEntry kInvalid; + kDiagnosticsData = 0x0001, // `K` quality, may be filtered out in subscriptions }; -enum class ClusterQualityFlags : uint32_t +struct ServerClusterEntry { - kDiagnosticsData = 0x0001, // `K` quality, may be filtered out in subscriptions + ClusterId clusterId; + DataVersion dataVersion; // current cluster data version, + BitFlags flags; }; struct ClusterInfo @@ -85,16 +84,6 @@ struct ClusterInfo ClusterInfo(DataVersion version) : dataVersion(version) {} }; -struct ClusterEntry -{ - ConcreteClusterPath path; - ClusterInfo info; - - bool IsValid() const { return path.HasValidIds(); } - - static const ClusterEntry kInvalid; -}; - enum class AttributeQualityFlags : uint32_t { kListAttribute = 0x0004, // This attribute is a list attribute @@ -104,25 +93,18 @@ enum class AttributeQualityFlags : uint32_t kTimed = 0x0040, // `T` quality on attributes (writes require timed interactions) }; -struct AttributeInfo +struct AttributeEntry { + AttributeId attributeId; BitFlags flags; // read/write access will be missing if read/write is NOT allowed + // + // NOTE: this should be compacted for size std::optional readPrivilege; // generally defaults to View if readable std::optional writePrivilege; // generally defaults to Operate if writable }; -struct AttributeEntry -{ - ConcreteAttributePath path; - AttributeInfo info; - - bool IsValid() const { return path.HasValidIds(); } - - static const AttributeEntry kInvalid; -}; - // Bitmask values for different Command qualities. enum class CommandQualityFlags : uint32_t { @@ -131,22 +113,16 @@ enum class CommandQualityFlags : uint32_t kLargeMessage = 0x0004, // `L` quality on commands }; -struct CommandInfo +struct AcceptedCommandEntry { + CommandId commandId; + + // TODO: this can be more compact (use some flags for privilege) + // to make this compact, add a compact enum and make flags/invokePrivilege getters (to still be type safe) BitFlags flags; Access::Privilege invokePrivilege = Access::Privilege::kOperate; }; -struct CommandEntry -{ - ConcreteCommandPath path; - CommandInfo info; - - bool IsValid() const { return path.HasValidIds(); } - - static const CommandEntry kInvalid; -}; - /// Represents a device type that resides on an endpoint struct DeviceTypeEntry { @@ -180,47 +156,18 @@ class ProviderMetadataTree public: virtual ~ProviderMetadataTree() = default; - // This iteration will list all the endpoints in the data model - virtual EndpointEntry FirstEndpoint() = 0; - virtual EndpointEntry NextEndpoint(EndpointId before) = 0; - virtual std::optional GetEndpointInfo(EndpointId id) = 0; - virtual bool EndpointExists(EndpointId id); + using SemanticTag = Clusters::Descriptor::Structs::SemanticTagStruct::Type; + + virtual MetadataList Endpoints() = 0; - // This iteration describes device types registered on an endpoint - virtual std::optional FirstDeviceType(EndpointId endpoint) = 0; - virtual std::optional NextDeviceType(EndpointId endpoint, const DeviceTypeEntry & previous) = 0; + virtual MetadataList SemanticTags(EndpointId endpointId) = 0; + virtual MetadataList DeviceTypes(EndpointId endpointId) = 0; + virtual MetadataList ClientClusters(EndpointId endpointId) = 0; + virtual MetadataList ServerClusters(EndpointId endpointId) = 0; - // This iteration describes semantic tags registered on an endpoint - using SemanticTag = Clusters::Descriptor::Structs::SemanticTagStruct::Type; - virtual std::optional GetFirstSemanticTag(EndpointId endpoint) = 0; - virtual std::optional GetNextSemanticTag(EndpointId endpoint, const SemanticTag & previous) = 0; - - // This iteration will list all server clusters on a given endpoint - virtual ClusterEntry FirstServerCluster(EndpointId endpoint) = 0; - virtual ClusterEntry NextServerCluster(const ConcreteClusterPath & before) = 0; - virtual std::optional GetServerClusterInfo(const ConcreteClusterPath & path) = 0; - - // This iteration will list all client clusters on a given endpoint - // As the client cluster is only a client without any attributes/commands, - // these functions only return the cluster path. - virtual ConcreteClusterPath FirstClientCluster(EndpointId endpoint) = 0; - virtual ConcreteClusterPath NextClientCluster(const ConcreteClusterPath & before) = 0; - - // Attribute iteration and accessors provide cluster-level access over - // attributes - virtual AttributeEntry FirstAttribute(const ConcreteClusterPath & cluster) = 0; - virtual AttributeEntry NextAttribute(const ConcreteAttributePath & before) = 0; - virtual std::optional GetAttributeInfo(const ConcreteAttributePath & path) = 0; - - // Command iteration and accessors provide cluster-level access over commands - virtual CommandEntry FirstAcceptedCommand(const ConcreteClusterPath & cluster) = 0; - virtual CommandEntry NextAcceptedCommand(const ConcreteCommandPath & before) = 0; - virtual std::optional GetAcceptedCommandInfo(const ConcreteCommandPath & path) = 0; - - // "generated" commands are purely for reporting what types of command ids can be - // returned as responses. - virtual ConcreteCommandPath FirstGeneratedCommand(const ConcreteClusterPath & cluster) = 0; - virtual ConcreteCommandPath NextGeneratedCommand(const ConcreteCommandPath & before) = 0; + virtual MetadataList Attributes(const ConcreteClusterPath & path) = 0; + virtual MetadataList GeneratedCommands(const ConcreteClusterPath & path) = 0; + virtual MetadataList AcceptedCommands(const ConcreteClusterPath & path) = 0; /// Workaround function to report attribute change. /// diff --git a/src/app/data-model-provider/Provider.h b/src/app/data-model-provider/Provider.h index c547b75b81984f..1d3fff75324351 100644 --- a/src/app/data-model-provider/Provider.h +++ b/src/app/data-model-provider/Provider.h @@ -16,15 +16,14 @@ */ #pragma once -#include "access/SubjectDescriptor.h" -#include "app/EventPathParams.h" -#include "lib/core/CHIPError.h" +#include #include #include #include #include #include +#include #include #include diff --git a/src/app/data-model-provider/tests/BUILD.gn b/src/app/data-model-provider/tests/BUILD.gn index 006976d0dce81a..193a622f88968c 100644 --- a/src/app/data-model-provider/tests/BUILD.gn +++ b/src/app/data-model-provider/tests/BUILD.gn @@ -20,6 +20,7 @@ chip_test_suite("tests") { test_sources = [ "TestActionReturnStatus.cpp", "TestEventEmitting.cpp", + "TestMetadataList.cpp", ] cflags = [ "-Wconversion" ] diff --git a/src/app/data-model-provider/tests/TestMetadataList.cpp b/src/app/data-model-provider/tests/TestMetadataList.cpp new file mode 100644 index 00000000000000..663dfae906303d --- /dev/null +++ b/src/app/data-model-provider/tests/TestMetadataList.cpp @@ -0,0 +1,396 @@ +/* + * + * Copyright (c) 2024 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. + */ + +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include + +using namespace chip; +using namespace chip::app::DataModel; + +namespace { + +class TestMetadataList : public ::testing::Test +{ +public: + static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); } + static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); } +}; + +template +struct IdAndValue +{ + uint32_t id; + T value; + + bool operator==(const IdAndValue & other) const + { + return (this == &other) || ((this->id == other.id) && (this->value == other.value)); + } +}; + +TEST_F(TestMetadataList, MetadataListWorks) +{ + MetadataList> list1; + EXPECT_EQ(list1.Size(), 0u); + EXPECT_TRUE(list1.Empty()); + + // Reservation should always work when empty + ASSERT_EQ(list1.Reserve(5), CHIP_NO_ERROR); + EXPECT_EQ(list1.Capacity(), 5u); + EXPECT_EQ(list1.Size(), 0u); + EXPECT_TRUE(list1.Empty()); + + // You can re-reserve differently if still empty. + ASSERT_EQ(list1.Reserve(2), CHIP_NO_ERROR); + EXPECT_EQ(list1.Capacity(), 2u); + + // Values can be appended until the capacity. + EXPECT_EQ(list1.Append({ 0xA1, 111 }), CHIP_NO_ERROR); + EXPECT_EQ(list1.Size(), 1u); + + EXPECT_EQ(list1.Append({ 0xA2, 222 }), CHIP_NO_ERROR); + EXPECT_EQ(list1.Size(), 2u); + + EXPECT_EQ(list1.Append({ 0xA3, 333 }), CHIP_ERROR_NO_MEMORY); + EXPECT_EQ(list1.Size(), 2u); + + MetadataList> list2 = std::move(list1); + + // Moved-from list is "empty", un-Metadata and span is empty. + EXPECT_EQ(list1.Size(), 0u); // NOLINT(bugprone-use-after-move) + EXPECT_EQ(list1.Capacity(), 0u); // NOLINT(bugprone-use-after-move) + EXPECT_TRUE(list1.GetSpanValidForLifetime().empty()); // NOLINT(bugprone-use-after-move) + + // Moved-to list has storage. + EXPECT_EQ(list2.Size(), 2u); + + // A span can be obtained over the list. + decltype(list2)::SpanType contents = list2.GetSpanValidForLifetime(); + EXPECT_EQ(contents.size(), 2u); + + size_t idx = 0; + for (const auto & element : contents) + { + size_t oneBasedIndex = idx + 1; + EXPECT_EQ(element.id, 0xA0u + oneBasedIndex); + EXPECT_EQ(element.value, 111 * static_cast(oneBasedIndex)); + ++idx; + } + EXPECT_EQ(idx, 2u); + + // After getting a span, list becomes immutable and it is no longer possible to append to the list. + EXPECT_EQ(list2.Append({ 0xA3, 333 }), CHIP_ERROR_INCORRECT_STATE); + EXPECT_EQ(list2.Size(), 2u); + EXPECT_EQ(list2.Capacity(), 2u); + + // Cannot re-reserve once the list has become immutable due to span-taking. + EXPECT_EQ(list2.Reserve(6), CHIP_ERROR_INCORRECT_STATE); +} + +static constexpr std::array kConstantArray{ 1, 2, 3 }; + +TEST_F(TestMetadataList, MetadataListConvertersWork) +{ + { + MetadataList list{ MetadataList::FromArray(std::array{ 1, 2, 3 }) }; + EXPECT_FALSE(list.Empty()); + EXPECT_EQ(list.Size(), 3u); + EXPECT_EQ(list[0], 1); + EXPECT_EQ(list[1], 2); + EXPECT_EQ(list[2], 3); + + auto list2 = std::move(list); + EXPECT_EQ(list.Size(), 0u); // NOLINT(bugprone-use-after-move) + + auto list2Span = list2.GetSpanValidForLifetime(); + EXPECT_EQ(list2Span.size(), 3u); + EXPECT_EQ(list2Span[0], 1); + EXPECT_EQ(list2Span[1], 2); + EXPECT_EQ(list2Span[2], 3); + + EXPECT_EQ(list2.Reserve(10), CHIP_ERROR_INCORRECT_STATE); + EXPECT_EQ(list2.Append(4), CHIP_ERROR_INCORRECT_STATE); + } + + { + MetadataList list1{ MetadataList::FromConstArray(kConstantArray) }; + EXPECT_EQ(list1.Size(), 3u); + EXPECT_EQ(list1[0], 1); + EXPECT_EQ(list1[1], 2); + EXPECT_EQ(list1[2], 3); + + EXPECT_EQ(list1.Reserve(10), CHIP_ERROR_INCORRECT_STATE); + EXPECT_EQ(list1.Append(4), CHIP_ERROR_INCORRECT_STATE); + + MetadataList list2{ MetadataList::FromConstArray(kConstantArray) }; + EXPECT_EQ(list2.Size(), 3u); + EXPECT_EQ(list2[0], 1); + EXPECT_EQ(list2[1], 2); + EXPECT_EQ(list2[2], 3); + + auto list1Span = list1.GetSpanValidForLifetime(); + auto list2Span = list2.GetSpanValidForLifetime(); + + EXPECT_EQ(list1Span.data(), list2Span.data()); + EXPECT_EQ(list1Span.size(), list2Span.size()); + EXPECT_EQ(list1Span.data(), kConstantArray.data()); + } + + { + MetadataList list1{ MetadataList::FromConstSpan(Span{ kConstantArray }) }; + EXPECT_EQ(list1.Size(), 3u); + EXPECT_EQ(list1[0], 1); + EXPECT_EQ(list1[1], 2); + EXPECT_EQ(list1[2], 3); + + EXPECT_EQ(list1.Reserve(10), CHIP_ERROR_INCORRECT_STATE); + EXPECT_EQ(list1.Append(4), CHIP_ERROR_INCORRECT_STATE); + + MetadataList list2{ MetadataList::FromConstSpan(Span{ kConstantArray }) }; + EXPECT_EQ(list2.Size(), 3u); + EXPECT_EQ(list2[0], 1); + EXPECT_EQ(list2[1], 2); + EXPECT_EQ(list2[2], 3); + + auto list1Span = list1.GetSpanValidForLifetime(); + auto list2Span = list2.GetSpanValidForLifetime(); + + EXPECT_EQ(list1Span.data(), list2Span.data()); + EXPECT_EQ(list1Span.size(), list2Span.size()); + EXPECT_EQ(list1Span.data(), kConstantArray.data()); + } +} + +enum MinCommandPrivilege : uint8_t +{ + kOperate = 0u, + kManage = 1u, + kAdmin = 2u, + + kMax = kAdmin +}; + +static constexpr uint16_t kPrivilegeFieldWidth = 0x3; +static constexpr uint16_t kPrivilegeFieldMask = static_cast((1u << kPrivilegeFieldWidth) - 1); +static_assert(MinCommandPrivilege::kMax <= kPrivilegeFieldMask, "Privilege mask is not wide enough"); + +// Bitmask values for different Command qualities. +enum class CommandMetadataFlags : uint16_t +{ + kFabricScoped = 0x1 << 0, + kTimed = 0x1 << 1, // `T` quality on commands + kLargeMessage = 0x1 << 2, // `L` quality on commands + kIsResponse = 0x1 << 3, // Command is server => client response + + kMinPrivilegeValueMask = kPrivilegeFieldMask << 4 + +}; + +typedef BitMask CommandMetadata; + +// MinCommandPrivilege GetMinCommandPrivilege(CommandMetadata metadata) const { +// return static_cast(metadata.GetField(CommandMetadataFlags::kMinPrivilegeValueMask)); +// } + +struct CommandEntry +{ + CommandId mei; + CommandMetadata metadata; +}; + +enum class NetworkCommissioningFeatureBits : uint8_t +{ + // TODO: NOT REAL VALUEs + kWifi = 1 << 0, + kThread = 1 << 1, + kEthernet = 1 << 2, +}; + +enum NetworkCommissioningCommands : CommandId +{ + kScanNetworks = 0x00u, // | client => server | ScanNetworksResponse | A | WI \| TH + kScanNetworksResponse = 0x01u, // | client <= server | N | | WI \| TH + kAddOrUpdateWiFiNetwork = 0x02u, // | client => server | NetworkConfigResponse | A | WI + kAddOrUpdateThreadNetwork = 0x03u, // | client => server | NetworkConfigResponse | A | TH + kRemoveNetwork = 0x04u, // | client => server | NetworkConfigResponse | A | WI \| TH + kNetworkConfigResponse = 0x05u, // | client <= server | N | | WI \| TH + kConnectNetwork = 0x06u, // | client => server | ConnectNetworkResponse | A | WI \| TH + kConnectNetworkResponse = 0x07u, // | client <= server | N | | WI \| TH + kReorderNetwork = 0x08u, // | client => server | NetworkConfigResponse | A | WI \| TH + +}; + +static const std::array kAllCommands{ + CommandEntry{ kScanNetworks, + CommandMetadata{}.SetField(CommandMetadataFlags::kMinPrivilegeValueMask, MinCommandPrivilege::kAdmin) }, + CommandEntry{ kScanNetworksResponse, CommandMetadata{}.Set(CommandMetadataFlags::kIsResponse) }, + CommandEntry{ kAddOrUpdateWiFiNetwork, + CommandMetadata{}.SetField(CommandMetadataFlags::kMinPrivilegeValueMask, MinCommandPrivilege::kAdmin) }, + CommandEntry{ kAddOrUpdateThreadNetwork, + CommandMetadata{}.SetField(CommandMetadataFlags::kMinPrivilegeValueMask, MinCommandPrivilege::kAdmin) }, + CommandEntry{ kRemoveNetwork, + CommandMetadata{}.SetField(CommandMetadataFlags::kMinPrivilegeValueMask, MinCommandPrivilege::kAdmin) }, + CommandEntry{ kNetworkConfigResponse, CommandMetadata{}.Set(CommandMetadataFlags::kIsResponse) }, + CommandEntry{ kConnectNetwork, + CommandMetadata{}.SetField(CommandMetadataFlags::kMinPrivilegeValueMask, MinCommandPrivilege::kAdmin) }, + CommandEntry{ kConnectNetworkResponse, CommandMetadata{}.Set(CommandMetadataFlags::kIsResponse) }, + CommandEntry{ kReorderNetwork, + CommandMetadata{}.SetField(CommandMetadataFlags::kMinPrivilegeValueMask, MinCommandPrivilege::kAdmin) }, +}; + +using FilterPredicate = bool (*)(void * context, void * object); +template +MetadataList FilterElements(Span elementTable, std::function supportedPredicate) +{ + MetadataList result; + + if (result.Reserve(elementTable.size()) != CHIP_NO_ERROR) + { + result.Invalidate(); + return result; + } + + for (const auto & element : elementTable) + { + if (!supportedPredicate(element)) + { + continue; + } + + // Append as much as we can + (void) result.Append(element); + } + + return result; +} + +class MiniCluster +{ +public: + MiniCluster() {} + virtual ~MiniCluster() {} + + virtual MetadataList GetSupportedCommands() const + { + return FilterElements(Span{ kAllCommands.data(), kAllCommands.size() }, + [this](const CommandEntry & entry) { return this->IsCommandSupported(entry.mei); }); + } + +#if 0 + virtual bool IsCommandSupported(CommandId commandId) const + { + using Commands = NetworkCommissioningCommands; + + if (commandId > Commands::kReorderNetwork) + { + return false; + } + + if (mFeatures.HasAny(NetworkCommissioningFeatureBits::kWifi, NetworkCommissioningFeatureBits::kThread)) + { + if ((commandId >= Commands::kScanNetworks) && (commandId <= Commands::kScanNetworksResponse)) + { + return true; + } + + if ((commandId >= Commands::kRemoveNetwork) && (commandId <= Commands::kReorderNetwork)) + { + return true; + } + } + + if (mFeatures.Has(NetworkCommissioningFeatureBits::kWifi)) + { + if (commandId == Commands::kAddOrUpdateWiFiNetwork) + { + return true; + } + } + + if (mFeatures.Has(NetworkCommissioningFeatureBits::kThread)) + { + if (commandId == Commands::kAddOrUpdateThreadNetwork) + { + return true; + } + } + + return false; + } +#endif // 0 + + virtual bool IsCommandSupported(CommandId commandId) const + { + using Commands = NetworkCommissioningCommands; + + switch (commandId) + { + case Commands::kScanNetworks: + case Commands::kScanNetworksResponse: + case Commands::kRemoveNetwork: + case Commands::kNetworkConfigResponse: + case Commands::kConnectNetwork: + case Commands::kConnectNetworkResponse: + case Commands::kReorderNetwork: + return mFeatures.HasAny(NetworkCommissioningFeatureBits::kWifi, NetworkCommissioningFeatureBits::kThread); + case Commands::kAddOrUpdateWiFiNetwork: + return mFeatures.Has(NetworkCommissioningFeatureBits::kWifi); + case Commands::kAddOrUpdateThreadNetwork: + return mFeatures.Has(NetworkCommissioningFeatureBits::kThread); + default: + return false; + } + } + + void SetFeatures(BitFlags features) { mFeatures = features; } + +private: + BitFlags mFeatures{}; +}; + +TEST_F(TestMetadataList, CommandsForFeaturesAreAsExpected) +{ + MiniCluster cluster; + + { + cluster.SetFeatures(BitFlags{}.Set(NetworkCommissioningFeatureBits::kWifi)); + + auto supportedCommands = cluster.GetSupportedCommands(); + EXPECT_EQ(supportedCommands.Size(), 8u); + } + + { + cluster.SetFeatures(BitFlags{}.Set(NetworkCommissioningFeatureBits::kEthernet)); + + auto supportedCommands = cluster.GetSupportedCommands(); + EXPECT_EQ(supportedCommands.Size(), 0u); + } +} + +} // namespace diff --git a/src/app/dynamic_server/DynamicDispatcher.cpp b/src/app/dynamic_server/DynamicDispatcher.cpp index 498492085dd829..3376128730c5bd 100644 --- a/src/app/dynamic_server/DynamicDispatcher.cpp +++ b/src/app/dynamic_server/DynamicDispatcher.cpp @@ -195,6 +195,14 @@ uint8_t emberAfClusterCount(EndpointId endpoint, bool server) return 0; } +uint8_t emberAfClusterCountForEndpointType(const EmberAfEndpointType * type, bool server) +{ + const EmberAfClusterMask cluster_mask = server ? CLUSTER_MASK_SERVER : CLUSTER_MASK_CLIENT; + + return static_cast(std::count_if(type->cluster, type->cluster + type->clusterCount, + [=](const EmberAfCluster & cluster) { return (cluster.mask & cluster_mask) != 0; })); +} + Optional emberAfGetServerAttributeIdByIndex(EndpointId endpoint, ClusterId cluster, uint16_t attributeIndex) { return NullOptional; diff --git a/src/app/reporting/Engine.cpp b/src/app/reporting/Engine.cpp index ed23d44b055434..c9d6392286affc 100644 --- a/src/app/reporting/Engine.cpp +++ b/src/app/reporting/Engine.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -52,12 +53,16 @@ using Protocols::InteractionModel::Status; Status EventPathValid(DataModel::Provider * model, const ConcreteEventPath & eventPath) { - if (!model->GetServerClusterInfo(eventPath).has_value()) { - return model->EndpointExists(eventPath.mEndpointId) ? Status::UnsupportedCluster : Status::UnsupportedEndpoint; + DataModel::ServerClusterFinder serverClusterFinder(model); + if (serverClusterFinder.Find(eventPath).has_value()) + { + return Status::Success; + } } - return Status::Success; + DataModel::EndpointFinder endpointFinder(model); + return endpointFinder.Find(eventPath.mEndpointId).has_value() ? Status::UnsupportedCluster : Status::UnsupportedEndpoint; } /// Returns the status of ACL validation. @@ -77,7 +82,9 @@ std::optional ValidateReadAttributeACL(DataModel::Provider * dataMod .requestType = RequestType::kAttributeReadRequest, .entityId = path.mAttributeId }; - std::optional info = dataModel->GetAttributeInfo(path); + DataModel::AttributeFinder finder(dataModel); + + std::optional info = finder.Find(path); // If the attribute exists, we know whether it is readable (readPrivilege has value) // and what the required access privilege is. However for attributes missing from the metatada @@ -148,8 +155,10 @@ DataModel::ActionReturnStatus RetrieveClusterData(DataModel::Provider * dataMode readRequest.subjectDescriptor = &subjectDescriptor; readRequest.path = path; + DataModel::ServerClusterFinder serverClusterFinder(dataModel); + DataVersion version = 0; - if (std::optional clusterInfo = dataModel->GetServerClusterInfo(path); clusterInfo.has_value()) + if (auto clusterInfo = serverClusterFinder.Find(path); clusterInfo.has_value()) { version = clusterInfo->dataVersion; } @@ -209,13 +218,10 @@ DataModel::ActionReturnStatus RetrieveClusterData(DataModel::Provider * dataMode bool IsClusterDataVersionEqualTo(DataModel::Provider * dataModel, const ConcreteClusterPath & path, DataVersion dataVersion) { - std::optional info = dataModel->GetServerClusterInfo(path); - if (!info.has_value()) - { - return false; - } + DataModel::ServerClusterFinder serverClusterFinder(dataModel); + auto info = serverClusterFinder.Find(path); - return (info->dataVersion == dataVersion); + return info.has_value() && (info->dataVersion == dataVersion); } } // namespace diff --git a/src/app/reporting/reporting.cpp b/src/app/reporting/reporting.cpp index d0e07544149d6b..8ae2a6e813fa68 100644 --- a/src/app/reporting/reporting.cpp +++ b/src/app/reporting/reporting.cpp @@ -18,7 +18,9 @@ #include #include +#include #include +#include #include using namespace chip; @@ -30,8 +32,10 @@ void MatterReportingAttributeChangeCallback(EndpointId endpoint, ClusterId clust // applications notifying about changes from their end. assertChipStackLockedByCurrentThread(); - InteractionModelEngine::GetInstance()->GetDataModelProvider()->Temporary_ReportAttributeChanged( - AttributePathParams(endpoint, clusterId, attributeId)); + DataModel::Provider * provider = InteractionModelEngine::GetInstance()->GetDataModelProvider(); + VerifyOrReturn(provider != nullptr); + + provider->Temporary_ReportAttributeChanged(AttributePathParams(endpoint, clusterId, attributeId)); } void MatterReportingAttributeChangeCallback(const ConcreteAttributePath & aPath) @@ -40,8 +44,10 @@ void MatterReportingAttributeChangeCallback(const ConcreteAttributePath & aPath) // applications notifying about changes from their end. assertChipStackLockedByCurrentThread(); - InteractionModelEngine::GetInstance()->GetDataModelProvider()->Temporary_ReportAttributeChanged( - AttributePathParams(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId)); + DataModel::Provider * provider = InteractionModelEngine::GetInstance()->GetDataModelProvider(); + VerifyOrReturn(provider != nullptr); + + provider->Temporary_ReportAttributeChanged(AttributePathParams(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId)); } void MatterReportingAttributeChangeCallback(EndpointId endpoint) @@ -50,5 +56,8 @@ void MatterReportingAttributeChangeCallback(EndpointId endpoint) // applications notifying about changes from their end. assertChipStackLockedByCurrentThread(); - InteractionModelEngine::GetInstance()->GetDataModelProvider()->Temporary_ReportAttributeChanged(AttributePathParams(endpoint)); + DataModel::Provider * provider = InteractionModelEngine::GetInstance()->GetDataModelProvider(); + VerifyOrReturn(provider != nullptr); + + provider->Temporary_ReportAttributeChanged(AttributePathParams(endpoint)); } diff --git a/src/app/tests/TestAttributePathExpandIterator.cpp b/src/app/tests/TestAttributePathExpandIterator.cpp index 4a6088a8196161..2b8de0623f775a 100644 --- a/src/app/tests/TestAttributePathExpandIterator.cpp +++ b/src/app/tests/TestAttributePathExpandIterator.cpp @@ -16,7 +16,8 @@ * limitations under the License. */ -#include "pw_unit_test/framework.h" +#include + #include #include #include @@ -24,15 +25,13 @@ #include #include #include +#include #include #include #include #include #include -#include -#include - using namespace chip; using namespace chip::Test; using namespace chip::app; @@ -41,7 +40,13 @@ namespace { using P = app::ConcreteAttributePath; -TEST(TestAttributePathExpandIterator, TestAllWildcard) +struct TestAttributePathExpandIterator : public ::testing::Test +{ + static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); } + static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); } +}; + +TEST_F(TestAttributePathExpandIterator, TestAllWildcard) { SingleLinkedListNode clusInfo; @@ -128,7 +133,7 @@ TEST(TestAttributePathExpandIterator, TestAllWildcard) EXPECT_EQ(index, ArraySize(paths)); } -TEST(TestAttributePathExpandIterator, TestWildcardEndpoint) +TEST_F(TestAttributePathExpandIterator, TestWildcardEndpoint) { SingleLinkedListNode clusInfo; clusInfo.mValue.mClusterId = chip::Test::MockClusterId(3); @@ -160,7 +165,7 @@ TEST(TestAttributePathExpandIterator, TestWildcardEndpoint) EXPECT_EQ(index, ArraySize(paths)); } -TEST(TestAttributePathExpandIterator, TestWildcardCluster) +TEST_F(TestAttributePathExpandIterator, TestWildcardCluster) { SingleLinkedListNode clusInfo; clusInfo.mValue.mEndpointId = chip::Test::kMockEndpoint3; @@ -195,7 +200,7 @@ TEST(TestAttributePathExpandIterator, TestWildcardCluster) EXPECT_EQ(index, ArraySize(paths)); } -TEST(TestAttributePathExpandIterator, TestWildcardClusterGlobalAttributeNotInMetadata) +TEST_F(TestAttributePathExpandIterator, TestWildcardClusterGlobalAttributeNotInMetadata) { SingleLinkedListNode clusInfo; clusInfo.mValue.mEndpointId = chip::Test::kMockEndpoint3; @@ -231,7 +236,7 @@ TEST(TestAttributePathExpandIterator, TestWildcardClusterGlobalAttributeNotInMet EXPECT_EQ(index, ArraySize(paths)); } -TEST(TestAttributePathExpandIterator, TestWildcardAttribute) +TEST_F(TestAttributePathExpandIterator, TestWildcardAttribute) { SingleLinkedListNode clusInfo; clusInfo.mValue.mEndpointId = chip::Test::kMockEndpoint2; @@ -271,7 +276,7 @@ TEST(TestAttributePathExpandIterator, TestWildcardAttribute) EXPECT_EQ(index, ArraySize(paths)); } -TEST(TestAttributePathExpandIterator, TestNoWildcard) +TEST_F(TestAttributePathExpandIterator, TestNoWildcard) { SingleLinkedListNode clusInfo; clusInfo.mValue.mEndpointId = chip::Test::kMockEndpoint2; @@ -304,7 +309,7 @@ TEST(TestAttributePathExpandIterator, TestNoWildcard) EXPECT_EQ(index, ArraySize(paths)); } -TEST(TestAttributePathExpandIterator, TestFixedPathExpansion) +TEST_F(TestAttributePathExpandIterator, TestFixedPathExpansion) { // expansion logic requires that: // - paths for wildcard expansion ARE VALIDATED @@ -367,7 +372,7 @@ TEST(TestAttributePathExpandIterator, TestFixedPathExpansion) } } -TEST(TestAttributePathExpandIterator, TestMultipleClusInfo) +TEST_F(TestAttributePathExpandIterator, TestMultipleClusInfo) { SingleLinkedListNode clusInfo1; diff --git a/src/app/tests/test-interaction-model-api.cpp b/src/app/tests/test-interaction-model-api.cpp index 1de18681b5a223..d5659420dd6a8a 100644 --- a/src/app/tests/test-interaction-model-api.cpp +++ b/src/app/tests/test-interaction-model-api.cpp @@ -149,107 +149,5 @@ std::optional TestImCustomDataModel::Invoke(const InvokeRequ return std::make_optional(CHIP_ERROR_NOT_IMPLEMENTED); } -DataModel::EndpointEntry TestImCustomDataModel::FirstEndpoint() -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->FirstEndpoint(); -} - -DataModel::EndpointEntry TestImCustomDataModel::NextEndpoint(EndpointId before) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->NextEndpoint(before); -} - -std::optional TestImCustomDataModel::GetEndpointInfo(EndpointId endpoint) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->GetEndpointInfo(endpoint); -} - -std::optional TestImCustomDataModel::FirstDeviceType(EndpointId endpoint) -{ - return std::nullopt; -} - -std::optional TestImCustomDataModel::NextDeviceType(EndpointId endpoint, - const DataModel::DeviceTypeEntry & previous) -{ - return std::nullopt; -} - -std::optional TestImCustomDataModel::GetFirstSemanticTag(EndpointId endpoint) -{ - return std::nullopt; -} - -std::optional TestImCustomDataModel::GetNextSemanticTag(EndpointId endpoint, - const SemanticTag & previous) -{ - return std::nullopt; -} - -ClusterEntry TestImCustomDataModel::FirstServerCluster(EndpointId endpoint) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->FirstServerCluster(endpoint); -} - -ClusterEntry TestImCustomDataModel::NextServerCluster(const ConcreteClusterPath & before) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->NextServerCluster(before); -} - -std::optional TestImCustomDataModel::GetServerClusterInfo(const ConcreteClusterPath & path) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->GetServerClusterInfo(path); -} - -ConcreteClusterPath TestImCustomDataModel::FirstClientCluster(EndpointId endpoint) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->FirstClientCluster(endpoint); -} - -ConcreteClusterPath TestImCustomDataModel::NextClientCluster(const ConcreteClusterPath & before) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->NextClientCluster(before); -} - -AttributeEntry TestImCustomDataModel::FirstAttribute(const ConcreteClusterPath & cluster) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->FirstAttribute(cluster); -} - -AttributeEntry TestImCustomDataModel::NextAttribute(const ConcreteAttributePath & before) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->NextAttribute(before); -} - -std::optional TestImCustomDataModel::GetAttributeInfo(const ConcreteAttributePath & path) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->GetAttributeInfo(path); -} - -CommandEntry TestImCustomDataModel::FirstAcceptedCommand(const ConcreteClusterPath & cluster) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->FirstAcceptedCommand(cluster); -} - -CommandEntry TestImCustomDataModel::NextAcceptedCommand(const ConcreteCommandPath & before) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->NextAcceptedCommand(before); -} - -std::optional TestImCustomDataModel::GetAcceptedCommandInfo(const ConcreteCommandPath & path) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->GetAcceptedCommandInfo(path); -} - -ConcreteCommandPath TestImCustomDataModel::FirstGeneratedCommand(const ConcreteClusterPath & cluster) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->FirstGeneratedCommand(cluster); -} - -ConcreteCommandPath TestImCustomDataModel::NextGeneratedCommand(const ConcreteCommandPath & before) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->NextGeneratedCommand(before); -} - } // namespace app } // namespace chip diff --git a/src/app/tests/test-interaction-model-api.h b/src/app/tests/test-interaction-model-api.h index 2120ec4304eb8e..54efc316da95a1 100644 --- a/src/app/tests/test-interaction-model-api.h +++ b/src/app/tests/test-interaction-model-api.h @@ -26,6 +26,7 @@ #include #include #include +#include /** * Helper macro we can use to pretend we got a reply from the server in cases @@ -96,7 +97,7 @@ void DispatchSingleClusterCommand(const ConcreteCommandPath & aRequestCommandPat /// TODO items for above: /// - once IM only supports DataModel /// - break ember-overrides in this h/cpp file -class TestImCustomDataModel : public DataModel::Provider +class TestImCustomDataModel : public CodegenDataModelProvider { public: static TestImCustomDataModel & Instance(); @@ -109,29 +110,6 @@ class TestImCustomDataModel : public DataModel::Provider AttributeValueDecoder & decoder) override; std::optional Invoke(const DataModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, CommandHandler * handler) override; - - DataModel::EndpointEntry FirstEndpoint() override; - DataModel::EndpointEntry NextEndpoint(EndpointId before) override; - std::optional GetEndpointInfo(EndpointId endpoint) override; - std::optional FirstDeviceType(EndpointId endpoint) override; - std::optional NextDeviceType(EndpointId endpoint, - const DataModel::DeviceTypeEntry & previous) override; - std::optional GetFirstSemanticTag(EndpointId endpoint) override; - std::optional GetNextSemanticTag(EndpointId endpoint, const SemanticTag & previous) override; - DataModel::ClusterEntry FirstServerCluster(EndpointId endpoint) override; - DataModel::ClusterEntry NextServerCluster(const ConcreteClusterPath & before) override; - std::optional GetServerClusterInfo(const ConcreteClusterPath & path) override; - ConcreteClusterPath FirstClientCluster(EndpointId endpoint) override; - ConcreteClusterPath NextClientCluster(const ConcreteClusterPath & before) override; - DataModel::AttributeEntry FirstAttribute(const ConcreteClusterPath & cluster) override; - DataModel::AttributeEntry NextAttribute(const ConcreteAttributePath & before) override; - std::optional GetAttributeInfo(const ConcreteAttributePath & path) override; - DataModel::CommandEntry FirstAcceptedCommand(const ConcreteClusterPath & cluster) override; - DataModel::CommandEntry NextAcceptedCommand(const ConcreteCommandPath & before) override; - std::optional GetAcceptedCommandInfo(const ConcreteCommandPath & path) override; - ConcreteCommandPath FirstGeneratedCommand(const ConcreteClusterPath & cluster) override; - ConcreteCommandPath NextGeneratedCommand(const ConcreteCommandPath & before) override; - void Temporary_ReportAttributeChanged(const AttributePathParams & path) override {} }; } // namespace app diff --git a/src/app/util/attribute-storage.cpp b/src/app/util/attribute-storage.cpp index 3df2ba7c6676aa..58fe5e20f7c5de 100644 --- a/src/app/util/attribute-storage.cpp +++ b/src/app/util/attribute-storage.cpp @@ -70,10 +70,6 @@ static bool emAfMatchCluster(const EmberAfCluster * cluster, const EmberAfAttrib static bool emAfMatchAttribute(const EmberAfCluster * cluster, const EmberAfAttributeMetadata * am, const EmberAfAttributeSearchRecord * attRecord); -// If server == true, returns the number of server clusters, -// otherwise number of client clusters on the endpoint at the given index. -static uint8_t emberAfClusterCountForEndpointType(const EmberAfEndpointType * endpointType, bool server); - // If server == true, returns the number of server clusters, // otherwise number of client clusters on the endpoint at the given index. static uint8_t emberAfClusterCountByIndex(uint16_t endpointIndex, bool server); diff --git a/src/app/util/attribute-storage.h b/src/app/util/attribute-storage.h index 6930db7a965ea2..c833bf648724bb 100644 --- a/src/app/util/attribute-storage.h +++ b/src/app/util/attribute-storage.h @@ -273,6 +273,10 @@ void emberAfInitializeAttributes(chip::EndpointId endpoint); // otherwise number of client clusters on this endpoint uint8_t emberAfClusterCount(chip::EndpointId endpoint, bool server); +// If server == true, returns the number of server clusters, +// otherwise number of client clusters on the endpoint at the given index. +uint8_t emberAfClusterCountForEndpointType(const EmberAfEndpointType * endpointType, bool server); + // Returns the cluster of Nth server or client cluster, // depending on server toggle. const EmberAfCluster * emberAfGetNthCluster(chip::EndpointId endpoint, uint8_t n, bool server); diff --git a/src/app/util/mock/attribute-storage.cpp b/src/app/util/mock/attribute-storage.cpp index 3d9e910f4570da..c1647b41e6c46e 100644 --- a/src/app/util/mock/attribute-storage.cpp +++ b/src/app/util/mock/attribute-storage.cpp @@ -207,6 +207,14 @@ uint8_t emberAfClusterCount(chip::EndpointId endpoint, bool server) return (server) ? emberAfGetClusterCountForEndpoint(endpoint) : 0; } +uint8_t emberAfClusterCountForEndpointType(const EmberAfEndpointType * type, bool server) +{ + const EmberAfClusterMask cluster_mask = server ? CLUSTER_MASK_SERVER : CLUSTER_MASK_CLIENT; + + return static_cast(std::count_if(type->cluster, type->cluster + type->clusterCount, + [=](const EmberAfCluster & cluster) { return (cluster.mask & cluster_mask) != 0; })); +} + uint16_t emberAfGetServerAttributeCount(chip::EndpointId endpointId, chip::ClusterId clusterId) { auto cluster = GetMockNodeConfig().clusterByIds(endpointId, clusterId); diff --git a/src/controller/tests/TestEventCaching.cpp b/src/controller/tests/TestEventCaching.cpp index cbff4b743ea0dd..5821b84b3fab75 100644 --- a/src/controller/tests/TestEventCaching.cpp +++ b/src/controller/tests/TestEventCaching.cpp @@ -18,14 +18,13 @@ #include -#include "app-common/zap-generated/ids/Attributes.h" -#include "app-common/zap-generated/ids/Clusters.h" -#include "app/ClusterStateCache.h" -#include "app/ConcreteAttributePath.h" -#include "protocols/interaction_model/Constants.h" #include +#include +#include #include +#include #include +#include #include #include #include @@ -33,11 +32,13 @@ #include #include #include +#include #include #include #include #include #include +#include using namespace chip; using namespace chip::app; @@ -153,6 +154,7 @@ TEST_F(TestEventCaching, TestBasicCaching) app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance(); // Initialize the ember side server logic + engine->SetDataModelProvider(CodegenDataModelProviderInstance(nullptr /* delegate */)); InitDataModelHandler(); // Register our fake dynamic endpoint. diff --git a/src/controller/tests/TestEventNumberCaching.cpp b/src/controller/tests/TestEventNumberCaching.cpp index 9b580f759e06c1..666e6033f79334 100644 --- a/src/controller/tests/TestEventNumberCaching.cpp +++ b/src/controller/tests/TestEventNumberCaching.cpp @@ -18,10 +18,10 @@ #include -#include "app-common/zap-generated/ids/Clusters.h" -#include "app/ClusterStateCache.h" #include +#include #include +#include #include #include #include @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -59,6 +60,7 @@ class TestEventNumberCaching : public chip::Test::AppContext // Performs setup for each test in the suite void SetUp() { + ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); const chip::app::LogStorageResources logStorageResources[] = { { &gDebugEventBuffer[0], sizeof(gDebugEventBuffer), chip::app::PriorityLevel::Debug }, { &gInfoEventBuffer[0], sizeof(gInfoEventBuffer), chip::app::PriorityLevel::Info }, @@ -80,6 +82,7 @@ class TestEventNumberCaching : public chip::Test::AppContext { chip::app::EventManagement::DestroyEventManagement(); AppContext::TearDown(); + chip::Platform::MemoryShutdown(); } private: @@ -144,6 +147,7 @@ TEST_F(TestEventNumberCaching, TestEventNumberCaching) app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance(); // Initialize the ember side server logic + engine->SetDataModelProvider(CodegenDataModelProviderInstance(nullptr /* delegate */)); InitDataModelHandler(); // Register our fake dynamic endpoint. diff --git a/src/controller/tests/data_model/DataModelFixtures.cpp b/src/controller/tests/data_model/DataModelFixtures.cpp index d5c02b5f0d16d3..930c74690392e2 100644 --- a/src/controller/tests/data_model/DataModelFixtures.cpp +++ b/src/controller/tests/data_model/DataModelFixtures.cpp @@ -477,107 +477,5 @@ std::optional CustomDataModel::Invoke(const InvokeRequest & return std::nullopt; // handler status is set by the dispatch } -DataModel::EndpointEntry CustomDataModel::FirstEndpoint() -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->FirstEndpoint(); -} - -DataModel::EndpointEntry CustomDataModel::NextEndpoint(EndpointId before) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->NextEndpoint(before); -} - -std::optional CustomDataModel::GetEndpointInfo(EndpointId endpoint) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->GetEndpointInfo(endpoint); -} - -std::optional CustomDataModel::FirstDeviceType(EndpointId endpoint) -{ - return std::nullopt; -} - -std::optional CustomDataModel::NextDeviceType(EndpointId endpoint, - const DataModel::DeviceTypeEntry & previous) -{ - return std::nullopt; -} - -std::optional CustomDataModel::GetFirstSemanticTag(EndpointId endpoint) -{ - return std::nullopt; -} - -std::optional CustomDataModel::GetNextSemanticTag(EndpointId endpoint, - const SemanticTag & previous) -{ - return std::nullopt; -} - -ClusterEntry CustomDataModel::FirstServerCluster(EndpointId endpoint) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->FirstServerCluster(endpoint); -} - -ClusterEntry CustomDataModel::NextServerCluster(const ConcreteClusterPath & before) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->NextServerCluster(before); -} - -std::optional CustomDataModel::GetServerClusterInfo(const ConcreteClusterPath & path) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->GetServerClusterInfo(path); -} - -ConcreteClusterPath CustomDataModel::FirstClientCluster(EndpointId endpoint) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->FirstClientCluster(endpoint); -} - -ConcreteClusterPath CustomDataModel::NextClientCluster(const ConcreteClusterPath & before) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->NextClientCluster(before); -} - -AttributeEntry CustomDataModel::FirstAttribute(const ConcreteClusterPath & cluster) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->FirstAttribute(cluster); -} - -AttributeEntry CustomDataModel::NextAttribute(const ConcreteAttributePath & before) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->NextAttribute(before); -} - -std::optional CustomDataModel::GetAttributeInfo(const ConcreteAttributePath & path) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->GetAttributeInfo(path); -} - -CommandEntry CustomDataModel::FirstAcceptedCommand(const ConcreteClusterPath & cluster) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->FirstAcceptedCommand(cluster); -} - -CommandEntry CustomDataModel::NextAcceptedCommand(const ConcreteCommandPath & before) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->NextAcceptedCommand(before); -} - -std::optional CustomDataModel::GetAcceptedCommandInfo(const ConcreteCommandPath & path) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->GetAcceptedCommandInfo(path); -} - -ConcreteCommandPath CustomDataModel::FirstGeneratedCommand(const ConcreteClusterPath & cluster) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->FirstGeneratedCommand(cluster); -} - -ConcreteCommandPath CustomDataModel::NextGeneratedCommand(const ConcreteCommandPath & before) -{ - return CodegenDataModelProviderInstance(nullptr /* delegate */)->NextGeneratedCommand(before); -} - } // namespace app } // namespace chip diff --git a/src/controller/tests/data_model/DataModelFixtures.h b/src/controller/tests/data_model/DataModelFixtures.h index 181bdf8fa3bb5c..d847e4b57439cf 100644 --- a/src/controller/tests/data_model/DataModelFixtures.h +++ b/src/controller/tests/data_model/DataModelFixtures.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -108,7 +109,7 @@ extern CommandHandler::Handle gAsyncCommandHandle; /// TODO items for above: /// - once IM only supports DataModel /// - break ember-overrides in this h/cpp file -class CustomDataModel : public DataModel::Provider +class CustomDataModel : public CodegenDataModelProvider { public: static CustomDataModel & Instance(); @@ -121,29 +122,6 @@ class CustomDataModel : public DataModel::Provider AttributeValueDecoder & decoder) override; std::optional Invoke(const DataModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, CommandHandler * handler) override; - - DataModel::EndpointEntry FirstEndpoint() override; - DataModel::EndpointEntry NextEndpoint(EndpointId before) override; - std::optional GetEndpointInfo(EndpointId endpoint) override; - std::optional FirstDeviceType(EndpointId endpoint) override; - std::optional NextDeviceType(EndpointId endpoint, - const DataModel::DeviceTypeEntry & previous) override; - std::optional GetFirstSemanticTag(EndpointId endpoint) override; - std::optional GetNextSemanticTag(EndpointId endpoint, const SemanticTag & previous) override; - DataModel::ClusterEntry FirstServerCluster(EndpointId endpoint) override; - DataModel::ClusterEntry NextServerCluster(const ConcreteClusterPath & before) override; - std::optional GetServerClusterInfo(const ConcreteClusterPath & path) override; - ConcreteClusterPath FirstClientCluster(EndpointId endpoint) override; - ConcreteClusterPath NextClientCluster(const ConcreteClusterPath & before) override; - DataModel::AttributeEntry FirstAttribute(const ConcreteClusterPath & cluster) override; - DataModel::AttributeEntry NextAttribute(const ConcreteAttributePath & before) override; - std::optional GetAttributeInfo(const ConcreteAttributePath & path) override; - DataModel::CommandEntry FirstAcceptedCommand(const ConcreteClusterPath & cluster) override; - DataModel::CommandEntry NextAcceptedCommand(const ConcreteCommandPath & before) override; - std::optional GetAcceptedCommandInfo(const ConcreteCommandPath & path) override; - ConcreteCommandPath FirstGeneratedCommand(const ConcreteClusterPath & cluster) override; - ConcreteCommandPath NextGeneratedCommand(const ConcreteCommandPath & before) override; - void Temporary_ReportAttributeChanged(const AttributePathParams & path) override {} }; } // namespace DataModelTests diff --git a/src/data-model-providers/codegen/CodegenDataModelProvider.cpp b/src/data-model-providers/codegen/CodegenDataModelProvider.cpp index 1cf34fcb2c8fbb..d91b1be0a749ab 100644 --- a/src/data-model-providers/codegen/CodegenDataModelProvider.cpp +++ b/src/data-model-providers/codegen/CodegenDataModelProvider.cpp @@ -20,15 +20,18 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -38,208 +41,228 @@ #include #include +#include #include -#include namespace chip { namespace app { -namespace detail { +namespace { -Loop EnumeratorCommandFinder::HandlerCallback(CommandId id) +DataModel::AcceptedCommandEntry AcceptedCommandEntryFor(const ConcreteCommandPath & path) { - switch (mOperation) - { - case Operation::kFindFirst: - mFound = id; - return Loop::Break; - case Operation::kFindExact: - if (mTarget == id) - { - mFound = id; // found it - return Loop::Break; - } - break; - case Operation::kFindNext: - if (mTarget == id) - { - // Once we found the ID, get the first - mOperation = Operation::kFindFirst; - } - break; - } - return Loop::Continue; // keep searching + const CommandId commandId = path.mCommandId; + + DataModel::AcceptedCommandEntry entry; + + entry.commandId = path.mCommandId; + entry.invokePrivilege = RequiredPrivilege::ForInvokeCommand(path); + entry.flags.Set(DataModel::CommandQualityFlags::kTimed, CommandNeedsTimedInvoke(path.mClusterId, commandId)); + entry.flags.Set(DataModel::CommandQualityFlags::kFabricScoped, CommandIsFabricScoped(path.mClusterId, commandId)); + entry.flags.Set(DataModel::CommandQualityFlags::kLargeMessage, CommandHasLargePayload(path.mClusterId, commandId)); + + return entry; } -std::optional EnumeratorCommandFinder::FindCommandId(Operation operation, const ConcreteCommandPath & path) +/// Fills `result` with accepted command data. In case of failures, +/// returns the first failure (and stops filling the result, which may +/// be partial) +CHIP_ERROR FetchAcceptedCommands(const ConcreteClusterPath & path, const EmberAfCluster * serverCluster, + DataModel::MetadataList & result) { - mOperation = operation; - mTarget = path.mCommandId; CommandHandlerInterface * interface = CommandHandlerInterfaceRegistry::Instance().GetCommandHandler(path.mEndpointId, path.mClusterId); - - if (interface == nullptr) + if (interface != nullptr) { - return std::nullopt; // no data: no interface + size_t commandCount = 0; + + CHIP_ERROR err = interface->EnumerateAcceptedCommands( + path, + [](CommandId id, void * context) -> Loop { + *reinterpret_cast(context) += 1; + return Loop::Continue; + }, + reinterpret_cast(&commandCount)); + + if (err == CHIP_NO_ERROR) + { + using EnumerationData = struct + { + ConcreteCommandPath commandPath; + DataModel::MetadataList acceptedCommandList; + CHIP_ERROR processingError; + }; + + EnumerationData enumerationData; + enumerationData.commandPath = ConcreteCommandPath(path.mEndpointId, path.mClusterId, kInvalidCommandId); + enumerationData.processingError = CHIP_NO_ERROR; + + ReturnErrorOnFailure(enumerationData.acceptedCommandList.Reserve(commandCount)); + + ReturnErrorOnFailure(interface->EnumerateAcceptedCommands( + path, + [](CommandId commandId, void * context) -> Loop { + auto input = reinterpret_cast(context); + input->commandPath.mCommandId = commandId; + CHIP_ERROR appendError = input->acceptedCommandList.Append(AcceptedCommandEntryFor(input->commandPath)); + if (appendError != CHIP_NO_ERROR) + { + input->processingError = appendError; + return Loop::Break; + } + return Loop::Continue; + }, + reinterpret_cast(&enumerationData))); + ReturnErrorOnFailure(enumerationData.processingError); + + // the two invocations MUST return the same sizes. + VerifyOrReturnError(enumerationData.acceptedCommandList.Size() == commandCount, CHIP_ERROR_INTERNAL); + + result = std::move(enumerationData.acceptedCommandList); + return CHIP_NO_ERROR; + } + VerifyOrReturnError(err == CHIP_ERROR_NOT_IMPLEMENTED, err); } - CHIP_ERROR err = (interface->*mCallback)(path, HandlerCallbackFn, this); - if (err == CHIP_ERROR_NOT_IMPLEMENTED) + if ((serverCluster == nullptr) || (serverCluster->acceptedCommandList == nullptr)) { - return std::nullopt; // no data provided by the interface + // No data if cluster does not exist or cluster has no accepted commands + return CHIP_NO_ERROR; } - - if (err != CHIP_NO_ERROR) + const chip::CommandId * endOfList = serverCluster->acceptedCommandList; + while (*endOfList != kInvalidCommandId) { -#if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING - // Report the error here since we lose actual error. This generally should NOT be possible as CommandHandlerInterface - // usually returns unimplemented or should just work for our use case (our callback never fails) - ChipLogError(DataManagement, "Enumerate error: %" CHIP_ERROR_FORMAT, err.Format()); -#endif - return kInvalidCommandId; + endOfList++; } + const size_t commandCount = static_cast(endOfList - serverCluster->acceptedCommandList); - return mFound.value_or(kInvalidCommandId); -} - -} // namespace detail - -using detail::EnumeratorCommandFinder; + ReturnErrorOnFailure(result.Reserve(commandCount)); -namespace { - -/// Search by device type within a span of EmberAfDeviceType (finds the device type that matches the given -/// DataModel::DeviceTypeEntry) -struct ByDeviceType -{ - using Key = DataModel::DeviceTypeEntry; - using Type = const EmberAfDeviceType; - static Span GetSpan(Span & data) { return data; } - static bool HasKey(const Key & id, const Type & instance) + ConcreteCommandPath commandPath = ConcreteCommandPath(path.mEndpointId, path.mClusterId, kInvalidCommandId); + for (const chip::CommandId * p = serverCluster->acceptedCommandList; p != endOfList; p++) { - return (instance.deviceId == id.deviceTypeId) && (instance.deviceVersion == id.deviceTypeRevision); + commandPath.mCommandId = *p; + ReturnErrorOnFailure(result.Append(AcceptedCommandEntryFor(commandPath))); } -}; -const CommandId * AcceptedCommands(const EmberAfCluster & cluster) -{ - return cluster.acceptedCommandList; -} - -const CommandId * GeneratedCommands(const EmberAfCluster & cluster) -{ - return cluster.generatedCommandList; + return CHIP_NO_ERROR; } -/// Load the cluster information into the specified destination -std::variant LoadClusterInfo(const ConcreteClusterPath & path, const EmberAfCluster & cluster) +/// Fills `result` with generated command data. In case of failures, +/// returns the first failure (and stops filling the result, which may +/// be partial) +CHIP_ERROR FetchGeneratedCommands(const ConcreteClusterPath & path, const EmberAfCluster * serverCluster, + DataModel::MetadataList & result) { - DataVersion * versionPtr = emberAfDataVersionStorage(path); - if (versionPtr == nullptr) + CommandHandlerInterface * interface = + CommandHandlerInterfaceRegistry::Instance().GetCommandHandler(path.mEndpointId, path.mClusterId); + if (interface != nullptr) { -#if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING - ChipLogError(AppServer, "Failed to get data version for %d/" ChipLogFormatMEI, static_cast(path.mEndpointId), - ChipLogValueMEI(cluster.clusterId)); -#endif - return CHIP_ERROR_NOT_FOUND; - } + size_t commandCount = 0; - DataModel::ClusterInfo info(*versionPtr); - // TODO: set entry flags: - // info->flags.Set(ClusterQualityFlags::kDiagnosticsData) - return info; -} + CHIP_ERROR err = interface->EnumerateGeneratedCommands( + path, + [](CommandId id, void * context) -> Loop { + *reinterpret_cast(context) += 1; + return Loop::Continue; + }, + reinterpret_cast(&commandCount)); -/// Converts a EmberAfCluster into a ClusterEntry -std::variant ClusterEntryFrom(EndpointId endpointId, const EmberAfCluster & cluster) -{ - ConcreteClusterPath clusterPath(endpointId, cluster.clusterId); - auto info = LoadClusterInfo(clusterPath, cluster); + if (err == CHIP_NO_ERROR) + { - if (CHIP_ERROR * err = std::get_if(&info)) - { - return *err; + using EnumerationData = struct + { + DataModel::MetadataList generatedCommandList; + CHIP_ERROR processingError; + }; + EnumerationData enumerationData; + enumerationData.processingError = CHIP_NO_ERROR; + + ReturnErrorOnFailure(enumerationData.generatedCommandList.Reserve(commandCount)); + + ReturnErrorOnFailure(interface->EnumerateGeneratedCommands( + path, + [](CommandId id, void * context) -> Loop { + auto input = reinterpret_cast(context); + + CHIP_ERROR appendError = input->generatedCommandList.Append(id); + if (appendError != CHIP_NO_ERROR) + { + input->processingError = appendError; + return Loop::Break; + } + return Loop::Continue; + }, + reinterpret_cast(&enumerationData))); + ReturnErrorOnFailure(enumerationData.processingError); + + // the two invocations MUST return the same sizes. + VerifyOrReturnError(enumerationData.generatedCommandList.Size() == commandCount, CHIP_ERROR_INTERNAL); + + result = std::move(enumerationData.generatedCommandList); + return CHIP_NO_ERROR; + } + VerifyOrReturnError(err == CHIP_ERROR_NOT_IMPLEMENTED, err); } - if (DataModel::ClusterInfo * infoValue = std::get_if(&info)) + if ((serverCluster == nullptr) || (serverCluster->generatedCommandList == nullptr)) + { + // No data if cluster does not exist or cluster has no generated commands + return CHIP_NO_ERROR; + } + const chip::CommandId * endOfList = serverCluster->generatedCommandList; + while (*endOfList != kInvalidCommandId) { - return DataModel::ClusterEntry{ - .path = clusterPath, - .info = *infoValue, - }; + endOfList++; } - return CHIP_ERROR_INCORRECT_STATE; + const size_t commandCount = static_cast(endOfList - serverCluster->generatedCommandList); + result = DataModel::MetadataList::FromConstSpan({ serverCluster->generatedCommandList, commandCount }); + + return CHIP_NO_ERROR; } -/// Finds the first server cluster entry for the given endpoint data starting at [start_index] -/// -/// Returns an invalid entry if no more server clusters are found -DataModel::ClusterEntry FirstServerClusterEntry(EndpointId endpointId, const EmberAfEndpointType * endpoint, unsigned start_index, - unsigned & found_index) +DataModel::ServerClusterEntry ServerClusterEntryFrom(EndpointId endpointId, const EmberAfCluster & cluster) { - for (unsigned cluster_idx = start_index; cluster_idx < endpoint->clusterCount; cluster_idx++) - { - const EmberAfCluster & cluster = endpoint->cluster[cluster_idx]; - if (!cluster.IsServer()) - { - continue; - } + DataModel::ServerClusterEntry entry; - found_index = cluster_idx; - auto entry = ClusterEntryFrom(endpointId, cluster); + entry.clusterId = cluster.clusterId; - if (DataModel::ClusterEntry * entryValue = std::get_if(&entry)) - { - return *entryValue; - } - -#if CHIP_ERROR_LOGGING && CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING - if (CHIP_ERROR * errValue = std::get_if(&entry)) - { - ChipLogError(AppServer, "Failed to load cluster entry: %" CHIP_ERROR_FORMAT, errValue->Format()); - } - else - { - // Should NOT be possible: entryFrom has only 2 variants - ChipLogError(AppServer, "Failed to load cluster entry, UNKNOWN entry return type"); - } + DataVersion * versionPtr = emberAfDataVersionStorage(ConcreteClusterPath(endpointId, cluster.clusterId)); + if (versionPtr == nullptr) + { +#if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING + ChipLogError(AppServer, "Failed to get data version for %d/" ChipLogFormatMEI, endpointId, + ChipLogValueMEI(cluster.clusterId)); #endif + entry.dataVersion = 0; } - - return DataModel::ClusterEntry::kInvalid; -} - -ClusterId FirstClientClusterId(const EmberAfEndpointType * endpoint, unsigned start_index, unsigned & found_index) -{ - for (unsigned cluster_idx = start_index; cluster_idx < endpoint->clusterCount; cluster_idx++) + else { - const EmberAfCluster & cluster = endpoint->cluster[cluster_idx]; - if (!cluster.IsClient()) - { - continue; - } - - found_index = cluster_idx; - return cluster.clusterId; + entry.dataVersion = *versionPtr; } - return kInvalidClusterId; + // TODO: set entry flags: + // entry.flags.Set(ClusterQualityFlags::kDiagnosticsData) + + return entry; } -/// Load the attribute information into the specified destination -/// -/// `info` is assumed to be default-constructed/clear (i.e. this sets flags, but does not reset them). -void LoadAttributeInfo(const ConcreteAttributePath & path, const EmberAfAttributeMetadata & attribute, - DataModel::AttributeInfo * info) +DataModel::AttributeEntry AttributeEntryFrom(const ConcreteClusterPath & clusterPath, const EmberAfAttributeMetadata & attribute) { - info->readPrivilege = RequiredPrivilege::ForReadAttribute(path); + DataModel::AttributeEntry entry; + + const ConcreteAttributePath attributePath(clusterPath.mEndpointId, clusterPath.mClusterId, attribute.attributeId); + + entry.attributeId = attribute.attributeId; + entry.readPrivilege = RequiredPrivilege::ForReadAttribute(attributePath); if (!attribute.IsReadOnly()) { - info->writePrivilege = RequiredPrivilege::ForWriteAttribute(path); + entry.writePrivilege = RequiredPrivilege::ForWriteAttribute(attributePath); } - info->flags.Set(DataModel::AttributeQualityFlags::kListAttribute, (attribute.attributeType == ZCL_ARRAY_ATTRIBUTE_TYPE)); - info->flags.Set(DataModel::AttributeQualityFlags::kTimed, attribute.MustUseTimedWrite()); + entry.flags.Set(DataModel::AttributeQualityFlags::kListAttribute, (attribute.attributeType == ZCL_ARRAY_ATTRIBUTE_TYPE)); + entry.flags.Set(DataModel::AttributeQualityFlags::kTimed, attribute.MustUseTimedWrite()); // NOTE: we do NOT provide additional info for: // - IsExternal/IsSingleton/IsAutomaticallyPersisted is not used by IM handling @@ -248,34 +271,9 @@ void LoadAttributeInfo(const ConcreteAttributePath & path, const EmberAfAttribut // fixed, source attribution) // TODO: Set additional flags: - // info->flags.Set(DataModel::AttributeQualityFlags::kFabricScoped) - // info->flags.Set(DataModel::AttributeQualityFlags::kFabricSensitive) - // info->flags.Set(DataModel::AttributeQualityFlags::kChangesOmitted) -} - -DataModel::AttributeEntry AttributeEntryFrom(const ConcreteClusterPath & clusterPath, const EmberAfAttributeMetadata & attribute) -{ - DataModel::AttributeEntry entry; - - entry.path = ConcreteAttributePath(clusterPath.mEndpointId, clusterPath.mClusterId, attribute.attributeId); - LoadAttributeInfo(entry.path, attribute, &entry.info); - - return entry; -} - -DataModel::CommandEntry CommandEntryFrom(const ConcreteClusterPath & clusterPath, CommandId clusterCommandId) -{ - DataModel::CommandEntry entry; - entry.path = ConcreteCommandPath(clusterPath.mEndpointId, clusterPath.mClusterId, clusterCommandId); - entry.info.invokePrivilege = RequiredPrivilege::ForInvokeCommand(entry.path); - - entry.info.flags.Set(DataModel::CommandQualityFlags::kTimed, CommandNeedsTimedInvoke(clusterPath.mClusterId, clusterCommandId)); - - entry.info.flags.Set(DataModel::CommandQualityFlags::kFabricScoped, - CommandIsFabricScoped(clusterPath.mClusterId, clusterCommandId)); - - entry.info.flags.Set(DataModel::CommandQualityFlags::kLargeMessage, - CommandHasLargePayload(clusterPath.mClusterId, clusterCommandId)); + // entry.flags.Set(DataModel::AttributeQualityFlags::kFabricScoped) + // entry.flags.Set(DataModel::AttributeQualityFlags::kFabricSensitive) + // entry.flags.Set(DataModel::AttributeQualityFlags::kChangesOmitted) return entry; } @@ -283,111 +281,16 @@ DataModel::CommandEntry CommandEntryFrom(const ConcreteClusterPath & clusterPath // to a common type is probably better. Need to figure out dependencies since // this would make ember return datamodel-provider types. // See: /~https://github.com/project-chip/connectedhomeip/issues/35889 -std::optional DeviceTypeEntryFromEmber(const EmberAfDeviceType * other) +DataModel::DeviceTypeEntry DeviceTypeEntryFromEmber(const EmberAfDeviceType & other) { - if (other == nullptr) - { - return std::nullopt; - } - return DataModel::DeviceTypeEntry{ - .deviceTypeId = other->deviceId, - .deviceTypeRevision = other->deviceVersion, + .deviceTypeId = other.deviceId, + .deviceTypeRevision = other.deviceVersion, }; } const ConcreteCommandPath kInvalidCommandPath(kInvalidEndpointId, kInvalidClusterId, kInvalidCommandId); -std::optional GetEndpointInfoAtIndex(uint16_t endpointIndex) -{ - VerifyOrReturnValue(emberAfEndpointIndexIsEnabled(endpointIndex), std::nullopt); - EndpointId parent = emberAfParentEndpointFromIndex(endpointIndex); - if (GetCompositionForEndpointIndex(endpointIndex) == EndpointComposition::kFullFamily) - { - return DataModel::EndpointInfo(parent, DataModel::EndpointCompositionPattern::kFullFamily); - } - if (GetCompositionForEndpointIndex(endpointIndex) == EndpointComposition::kTree) - { - return DataModel::EndpointInfo(parent, DataModel::EndpointCompositionPattern::kTree); - } - return std::nullopt; -} - -DataModel::EndpointEntry FirstEndpointEntry(unsigned start_index, uint16_t & found_index) -{ - // find the first enabled index after the start index - const uint16_t lastEndpointIndex = emberAfEndpointCount(); - for (uint16_t endpoint_idx = static_cast(start_index); endpoint_idx < lastEndpointIndex; endpoint_idx++) - { - if (emberAfEndpointIndexIsEnabled(endpoint_idx)) - { - found_index = endpoint_idx; - DataModel::EndpointEntry endpointEntry = DataModel::EndpointEntry::kInvalid; - endpointEntry.id = emberAfEndpointFromIndex(endpoint_idx); - auto endpointInfo = GetEndpointInfoAtIndex(endpoint_idx); - // The endpoint info should have value as this endpoint should be valid at this time - VerifyOrDie(endpointInfo.has_value()); - endpointEntry.info = endpointInfo.value(); - return endpointEntry; - } - } - - // No enabled endpoint found. Give up - return DataModel::EndpointEntry::kInvalid; -} - -bool operator==(const DataModel::Provider::SemanticTag & tagA, const DataModel::Provider::SemanticTag & tagB) -{ - // Label is an optional and nullable value of CharSpan. Optional and Nullable have overload for ==, - // But `==` is deleted for CharSpan. Here we only check whether the string is the same. - if (tagA.label.HasValue() != tagB.label.HasValue()) - { - return false; - } - if (tagA.label.HasValue()) - { - if (tagA.label.Value().IsNull() != tagB.label.Value().IsNull()) - { - return false; - } - if (!tagA.label.Value().IsNull()) - { - if (!tagA.label.Value().Value().data_equal(tagB.label.Value().Value())) - { - return false; - } - } - } - return (tagA.tag == tagB.tag) && (tagA.mfgCode == tagB.mfgCode) && (tagA.namespaceID == tagB.namespaceID); -} - -std::optional FindNextSemanticTagIndex(EndpointId endpoint, const DataModel::Provider::SemanticTag & previous, - unsigned hintWherePreviousMayBe) -{ - DataModel::Provider::SemanticTag hintTag; - // Check whether the hint is the previous tag - if (GetSemanticTagForEndpointAtIndex(endpoint, hintWherePreviousMayBe, hintTag) == CHIP_NO_ERROR) - { - if (previous == hintTag) - { - return std::make_optional(hintWherePreviousMayBe + 1); - } - } - // If the hint is not the previous tag, iterate over all the tags to find the index for the previous tag - unsigned index = 0; - // Ensure that the next index is in the range - while (GetSemanticTagForEndpointAtIndex(endpoint, index + 1, hintTag) == CHIP_NO_ERROR && - GetSemanticTagForEndpointAtIndex(endpoint, index, hintTag) == CHIP_NO_ERROR) - { - if (previous == hintTag) - { - return std::make_optional(index + 1); - } - index++; - } - return std::nullopt; -} - DefaultAttributePersistenceProvider gDefaultAttributePersistence; } // namespace @@ -423,72 +326,6 @@ CHIP_ERROR CodegenDataModelProvider::Startup(DataModel::InteractionModelContext return CHIP_NO_ERROR; } -std::optional CodegenDataModelProvider::EmberCommandListIterator::First(const CommandId * list) -{ - VerifyOrReturnValue(list != nullptr, std::nullopt); - mCurrentList = mCurrentHint = list; - - VerifyOrReturnValue(*mCurrentList != kInvalidCommandId, std::nullopt); - return *mCurrentList; -} - -std::optional CodegenDataModelProvider::EmberCommandListIterator::Next(const CommandId * list, CommandId previousId) -{ - VerifyOrReturnValue(list != nullptr, std::nullopt); - VerifyOrReturnValue(previousId != kInvalidCommandId, std::nullopt); - - if (mCurrentList != list) - { - // invalidate the hint if switching lists... - mCurrentHint = nullptr; - mCurrentList = list; - } - - if ((mCurrentHint == nullptr) || (*mCurrentHint != previousId)) - { - // we did not find a usable hint. Search from the to set the hint - mCurrentHint = mCurrentList; - while ((*mCurrentHint != kInvalidCommandId) && (*mCurrentHint != previousId)) - { - mCurrentHint++; - } - } - - VerifyOrReturnValue(*mCurrentHint == previousId, std::nullopt); - - // hint is valid and can be used immediately - mCurrentHint++; // this is the next value - return (*mCurrentHint == kInvalidCommandId) ? std::nullopt : std::make_optional(*mCurrentHint); -} - -bool CodegenDataModelProvider::EmberCommandListIterator::Exists(const CommandId * list, CommandId toCheck) -{ - VerifyOrReturnValue(list != nullptr, false); - VerifyOrReturnValue(toCheck != kInvalidCommandId, false); - - if (mCurrentList != list) - { - // invalidate the hint if switching lists... - mCurrentHint = nullptr; - mCurrentList = list; - } - - // maybe already positioned correctly - if ((mCurrentHint != nullptr) && (*mCurrentHint == toCheck)) - { - return true; - } - - // move and try to find it - mCurrentHint = mCurrentList; - while ((*mCurrentHint != kInvalidCommandId) && (*mCurrentHint != toCheck)) - { - mCurrentHint++; - } - - return (*mCurrentHint == toCheck); -} - std::optional CodegenDataModelProvider::Invoke(const DataModel::InvokeRequest & request, TLV::TLVReader & input_arguments, CommandHandler * handler) @@ -513,24 +350,55 @@ std::optional CodegenDataModelProvider::Invoke(co return std::nullopt; } -bool CodegenDataModelProvider::EndpointExists(EndpointId endpoint) +DataModel::MetadataList CodegenDataModelProvider::Endpoints() { - return (emberAfIndexFromEndpoint(endpoint) != kEmberInvalidEndpointIndex); -} + DataModel::MetadataList result; -std::optional CodegenDataModelProvider::GetEndpointInfo(EndpointId endpoint) -{ - std::optional endpoint_idx = TryFindEndpointIndex(endpoint); - if (endpoint_idx.has_value()) + const uint16_t endpointCount = emberAfEndpointCount(); + + // allocate the max as some endpoints may be disabled + CHIP_ERROR err = result.Reserve(endpointCount); + if (err != CHIP_NO_ERROR) { - return GetEndpointInfoAtIndex(static_cast(*endpoint_idx)); +#if CHIP_ERROR_LOGGING && CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING + ChipLogError(AppServer, "Failed to allocate space for endpoints: %" CHIP_ERROR_FORMAT, err.Format()); +#endif + return {}; } - return std::nullopt; -} -DataModel::EndpointEntry CodegenDataModelProvider::FirstEndpoint() -{ - return FirstEndpointEntry(0, mEndpointIterationHint); + for (uint16_t endpointIndex = 0; endpointIndex < endpointCount; endpointIndex++) + { + if (!emberAfEndpointIndexIsEnabled(endpointIndex)) + { + continue; + } + + DataModel::EndpointEntry entry; + entry.id = emberAfEndpointFromIndex(endpointIndex); + entry.parentId = emberAfParentEndpointFromIndex(endpointIndex); + + switch (GetCompositionForEndpointIndex(endpointIndex)) + { + case EndpointComposition::kFullFamily: + entry.compositionPattern = DataModel::EndpointCompositionPattern::kFullFamily; + break; + case EndpointComposition::kTree: + case EndpointComposition::kInvalid: // should NOT happen, but force compiler to check we validate all versions + entry.compositionPattern = DataModel::EndpointCompositionPattern::kTree; + break; + } + + err = result.Append(entry); + if (err != CHIP_NO_ERROR) + { +#if CHIP_ERROR_LOGGING && CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING + ChipLogError(AppServer, "Failed to append endpoint data: %" CHIP_ERROR_FORMAT, err.Format()); +#endif + break; + } + } + + return result; } std::optional CodegenDataModelProvider::TryFindEndpointIndex(EndpointId id) const @@ -553,165 +421,122 @@ std::optional CodegenDataModelProvider::TryFindEndpointIndex(EndpointI return std::make_optional(idx); } -DataModel::EndpointEntry CodegenDataModelProvider::NextEndpoint(EndpointId before) -{ - std::optional before_idx = TryFindEndpointIndex(before); - if (!before_idx.has_value()) - { - return DataModel::EndpointEntry::kInvalid; - } - return FirstEndpointEntry(*before_idx + 1, mEndpointIterationHint); -} - -DataModel::ClusterEntry CodegenDataModelProvider::FirstServerCluster(EndpointId endpointId) +DataModel::MetadataList CodegenDataModelProvider::ServerClusters(EndpointId endpointId) { const EmberAfEndpointType * endpoint = emberAfFindEndpointType(endpointId); - VerifyOrReturnValue(endpoint != nullptr, DataModel::ClusterEntry::kInvalid); - VerifyOrReturnValue(endpoint->clusterCount > 0, DataModel::ClusterEntry::kInvalid); - VerifyOrReturnValue(endpoint->cluster != nullptr, DataModel::ClusterEntry::kInvalid); - return FirstServerClusterEntry(endpointId, endpoint, 0, mServerClusterIterationHint); -} + DataModel::MetadataList result; -std::optional CodegenDataModelProvider::TryFindClusterIndex(const EmberAfEndpointType * endpoint, ClusterId id, - ClusterSide side) const -{ - const unsigned clusterCount = endpoint->clusterCount; - unsigned hint = side == ClusterSide::kServer ? mServerClusterIterationHint : mClientClusterIterationHint; + VerifyOrReturnValue(endpoint != nullptr, result); + VerifyOrReturnValue(endpoint->clusterCount > 0, result); + VerifyOrReturnValue(endpoint->cluster != nullptr, result); - if (hint < clusterCount) + CHIP_ERROR err = result.Reserve(emberAfClusterCountForEndpointType(endpoint, /* server = */ true)); + if (err != CHIP_NO_ERROR) { - const EmberAfCluster & cluster = endpoint->cluster[hint]; - if (((side == ClusterSide::kServer) && cluster.IsServer()) || ((side == ClusterSide::kClient) && cluster.IsClient())) - { - if (cluster.clusterId == id) - { - return std::make_optional(hint); - } - } +#if CHIP_ERROR_LOGGING && CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING + ChipLogError(AppServer, "Failed to reserve space for client clusters: %" CHIP_ERROR_FORMAT, err.Format()); +#endif + return {}; } - // linear search, this may be slow - // does NOT use emberAfClusterIndex to not iterate over endpoints as we have - // already found the correct endpoint - for (unsigned cluster_idx = 0; cluster_idx < clusterCount; cluster_idx++) + const EmberAfCluster * begin = endpoint->cluster; + const EmberAfCluster * end = endpoint->cluster + endpoint->clusterCount; + for (const EmberAfCluster * cluster = begin; cluster != end; cluster++) { - const EmberAfCluster & cluster = endpoint->cluster[cluster_idx]; - if (((side == ClusterSide::kServer) && !cluster.IsServer()) || ((side == ClusterSide::kClient) && !cluster.IsClient())) + if (!cluster->IsServer()) { continue; } - if (cluster.clusterId == id) + + err = result.Append(ServerClusterEntryFrom(endpointId, *cluster)); + if (err != CHIP_NO_ERROR) { - return std::make_optional(cluster_idx); +#if CHIP_ERROR_LOGGING && CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING + ChipLogError(AppServer, "Failed to append server cluster entry: %" CHIP_ERROR_FORMAT, err.Format()); +#endif + break; } } - return std::nullopt; -} - -DataModel::ClusterEntry CodegenDataModelProvider::NextServerCluster(const ConcreteClusterPath & before) -{ - // TODO: This search still seems slow (ember will loop). Should use index hints as long - // as ember API supports it - const EmberAfEndpointType * endpoint = emberAfFindEndpointType(before.mEndpointId); - - VerifyOrReturnValue(endpoint != nullptr, DataModel::ClusterEntry::kInvalid); - VerifyOrReturnValue(endpoint->clusterCount > 0, DataModel::ClusterEntry::kInvalid); - VerifyOrReturnValue(endpoint->cluster != nullptr, DataModel::ClusterEntry::kInvalid); - - std::optional cluster_idx = TryFindClusterIndex(endpoint, before.mClusterId, ClusterSide::kServer); - if (!cluster_idx.has_value()) - { - return DataModel::ClusterEntry::kInvalid; - } - - return FirstServerClusterEntry(before.mEndpointId, endpoint, *cluster_idx + 1, mServerClusterIterationHint); + return result; } -std::optional CodegenDataModelProvider::GetServerClusterInfo(const ConcreteClusterPath & path) +DataModel::MetadataList CodegenDataModelProvider::Attributes(const ConcreteClusterPath & path) { const EmberAfCluster * cluster = FindServerCluster(path); - VerifyOrReturnValue(cluster != nullptr, std::nullopt); + DataModel::MetadataList result; - auto info = LoadClusterInfo(path, *cluster); + VerifyOrReturnValue(cluster != nullptr, result); + VerifyOrReturnValue(cluster->attributeCount > 0, result); + VerifyOrReturnValue(cluster->attributes != nullptr, result); - if (CHIP_ERROR * err = std::get_if(&info)) + CHIP_ERROR err = result.Reserve(cluster->attributeCount); + if (err != CHIP_NO_ERROR) { #if CHIP_ERROR_LOGGING && CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING - ChipLogError(AppServer, "Failed to load cluster info: %" CHIP_ERROR_FORMAT, err->Format()); -#else - (void) err->Format(); + ChipLogError(AppServer, "Failed to reserve space for attributes: %" CHIP_ERROR_FORMAT, err.Format()); #endif - return std::nullopt; + return {}; } - return std::make_optional(std::get(info)); -} - -ConcreteClusterPath CodegenDataModelProvider::FirstClientCluster(EndpointId endpointId) -{ - const EmberAfEndpointType * endpoint = emberAfFindEndpointType(endpointId); - VerifyOrReturnValue(endpoint != nullptr, ConcreteClusterPath(endpointId, kInvalidClusterId)); - VerifyOrReturnValue(endpoint->clusterCount > 0, ConcreteClusterPath(endpointId, kInvalidClusterId)); - VerifyOrReturnValue(endpoint->cluster != nullptr, ConcreteClusterPath(endpointId, kInvalidClusterId)); - - return ConcreteClusterPath(endpointId, FirstClientClusterId(endpoint, 0, mClientClusterIterationHint)); -} - -ConcreteClusterPath CodegenDataModelProvider::NextClientCluster(const ConcreteClusterPath & before) -{ - // TODO: This search still seems slow (ember will loop). Should use index hints as long - // as ember API supports it - const EmberAfEndpointType * endpoint = emberAfFindEndpointType(before.mEndpointId); + Span attributeSpan(cluster->attributes, cluster->attributeCount); - VerifyOrReturnValue(endpoint != nullptr, ConcreteClusterPath(before.mEndpointId, kInvalidClusterId)); - VerifyOrReturnValue(endpoint->clusterCount > 0, ConcreteClusterPath(before.mEndpointId, kInvalidClusterId)); - VerifyOrReturnValue(endpoint->cluster != nullptr, ConcreteClusterPath(before.mEndpointId, kInvalidClusterId)); - - std::optional cluster_idx = TryFindClusterIndex(endpoint, before.mClusterId, ClusterSide::kClient); - if (!cluster_idx.has_value()) + for (auto & attribute : attributeSpan) { - return ConcreteClusterPath(before.mEndpointId, kInvalidClusterId); + err = result.Append(AttributeEntryFrom(path, attribute)); + if (err != CHIP_NO_ERROR) + { +#if CHIP_ERROR_LOGGING && CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING + ChipLogError(AppServer, "Failed to append attribute entry: %" CHIP_ERROR_FORMAT, err.Format()); +#endif + break; + } } - return ConcreteClusterPath(before.mEndpointId, FirstClientClusterId(endpoint, *cluster_idx + 1, mClientClusterIterationHint)); + return result; } -DataModel::AttributeEntry CodegenDataModelProvider::FirstAttribute(const ConcreteClusterPath & path) +DataModel::MetadataList CodegenDataModelProvider::ClientClusters(EndpointId endpointId) { - const EmberAfCluster * cluster = FindServerCluster(path); + const EmberAfEndpointType * endpoint = emberAfFindEndpointType(endpointId); - VerifyOrReturnValue(cluster != nullptr, DataModel::AttributeEntry::kInvalid); - VerifyOrReturnValue(cluster->attributeCount > 0, DataModel::AttributeEntry::kInvalid); - VerifyOrReturnValue(cluster->attributes != nullptr, DataModel::AttributeEntry::kInvalid); + DataModel::MetadataList result; - mAttributeIterationHint = 0; - return AttributeEntryFrom(path, cluster->attributes[0]); -} - -std::optional CodegenDataModelProvider::TryFindAttributeIndex(const EmberAfCluster * cluster, AttributeId id) const -{ - const unsigned attributeCount = cluster->attributeCount; + VerifyOrReturnValue(endpoint != nullptr, result); + VerifyOrReturnValue(endpoint->clusterCount > 0, result); + VerifyOrReturnValue(endpoint->cluster != nullptr, result); - // attempt to find this based on the embedded hint - if ((mAttributeIterationHint < attributeCount) && (cluster->attributes[mAttributeIterationHint].attributeId == id)) + CHIP_ERROR err = result.Reserve(emberAfClusterCountForEndpointType(endpoint, /* server = */ false)); + if (err != CHIP_NO_ERROR) { - return std::make_optional(mAttributeIterationHint); +#if CHIP_ERROR_LOGGING && CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING + ChipLogError(AppServer, "Failed to reserve space for client clusters: %" CHIP_ERROR_FORMAT, err.Format()); +#endif + return {}; } - // linear search is required. This may be slow - for (unsigned attribute_idx = 0; attribute_idx < attributeCount; attribute_idx++) + const EmberAfCluster * begin = endpoint->cluster; + const EmberAfCluster * end = endpoint->cluster + endpoint->clusterCount; + for (const EmberAfCluster * cluster = begin; cluster != end; cluster++) { + if (!cluster->IsClient()) + { + continue; + } - if (cluster->attributes[attribute_idx].attributeId == id) + err = result.Append(cluster->clusterId); + if (err != CHIP_NO_ERROR) { - return std::make_optional(attribute_idx); +#if CHIP_ERROR_LOGGING && CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING + ChipLogError(AppServer, "Failed to append client cluster id: %" CHIP_ERROR_FORMAT, err.Format()); +#endif + break; } } - return std::nullopt; + return result; } const EmberAfCluster * CodegenDataModelProvider::FindServerCluster(const ConcreteClusterPath & path) @@ -732,134 +557,37 @@ const EmberAfCluster * CodegenDataModelProvider::FindServerCluster(const Concret return cluster; } -CommandId CodegenDataModelProvider::FindCommand(const ConcreteCommandPath & path, detail::EnumeratorCommandFinder & handlerFinder, - detail::EnumeratorCommandFinder::Operation operation, - CodegenDataModelProvider::EmberCommandListIterator & emberIterator, - CommandListGetter commandListGetter) -{ - - std::optional handlerCommandId = handlerFinder.FindCommandId(operation, path); - if (handlerCommandId.has_value()) - { - return *handlerCommandId; - } - - const EmberAfCluster * cluster = FindServerCluster(path); - VerifyOrReturnValue(cluster != nullptr, kInvalidCommandId); - - const CommandId * commandList = commandListGetter(*cluster); - - switch (operation) - { - case EnumeratorCommandFinder::Operation::kFindFirst: - return emberIterator.First(commandList).value_or(kInvalidCommandId); - case EnumeratorCommandFinder::Operation::kFindNext: - return emberIterator.Next(commandList, path.mCommandId).value_or(kInvalidCommandId); - case EnumeratorCommandFinder::Operation::kFindExact: - default: - return emberIterator.Exists(commandList, path.mCommandId) ? path.mCommandId : kInvalidCommandId; - } -} - -DataModel::AttributeEntry CodegenDataModelProvider::NextAttribute(const ConcreteAttributePath & before) +DataModel::MetadataList +CodegenDataModelProvider::AcceptedCommands(const ConcreteClusterPath & path) { - const EmberAfCluster * cluster = FindServerCluster(before); - VerifyOrReturnValue(cluster != nullptr, DataModel::AttributeEntry::kInvalid); - VerifyOrReturnValue(cluster->attributeCount > 0, DataModel::AttributeEntry::kInvalid); - VerifyOrReturnValue(cluster->attributes != nullptr, DataModel::AttributeEntry::kInvalid); + DataModel::MetadataList result; - // find the given attribute in the list and then return the next one - std::optional attribute_idx = TryFindAttributeIndex(cluster, before.mAttributeId); - if (!attribute_idx.has_value()) - { - return DataModel::AttributeEntry::kInvalid; - } + [[maybe_unused]] CHIP_ERROR err = FetchAcceptedCommands(path, FindServerCluster(path), result); - unsigned next_idx = *attribute_idx + 1; - if (next_idx < cluster->attributeCount) +#if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING + if (err != CHIP_NO_ERROR) { - mAttributeIterationHint = next_idx; - return AttributeEntryFrom(before, cluster->attributes[next_idx]); + ChipLogError(DataManagement, "Failed to fetch accepted commands: %" CHIP_ERROR_FORMAT, err.Format()); } +#endif - // iteration complete - return DataModel::AttributeEntry::kInvalid; + return result; } -std::optional CodegenDataModelProvider::GetAttributeInfo(const ConcreteAttributePath & path) +DataModel::MetadataList CodegenDataModelProvider::GeneratedCommands(const ConcreteClusterPath & path) { - const EmberAfCluster * cluster = FindServerCluster(path); - - VerifyOrReturnValue(cluster != nullptr, std::nullopt); - VerifyOrReturnValue(cluster->attributeCount > 0, std::nullopt); - VerifyOrReturnValue(cluster->attributes != nullptr, std::nullopt); + DataModel::MetadataList result; - std::optional attribute_idx = TryFindAttributeIndex(cluster, path.mAttributeId); + [[maybe_unused]] CHIP_ERROR err = FetchGeneratedCommands(path, FindServerCluster(path), result); - if (!attribute_idx.has_value()) +#if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING + if (err != CHIP_NO_ERROR) { - return std::nullopt; + ChipLogError(DataManagement, "Failed to fetch generated commands: %" CHIP_ERROR_FORMAT, err.Format()); } +#endif - DataModel::AttributeInfo info; - LoadAttributeInfo(path, cluster->attributes[*attribute_idx], &info); - return std::make_optional(info); -} - -DataModel::CommandEntry CodegenDataModelProvider::FirstAcceptedCommand(const ConcreteClusterPath & path) -{ - EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateAcceptedCommands); - - CommandId commandId = - FindCommand(ConcreteCommandPath(path.mEndpointId, path.mClusterId, kInvalidCommandId), handlerFinder, - detail::EnumeratorCommandFinder::Operation::kFindFirst, mAcceptedCommandsIterator, AcceptedCommands); - - VerifyOrReturnValue(commandId != kInvalidCommandId, DataModel::CommandEntry::kInvalid); - return CommandEntryFrom(path, commandId); -} - -DataModel::CommandEntry CodegenDataModelProvider::NextAcceptedCommand(const ConcreteCommandPath & before) -{ - - EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateAcceptedCommands); - CommandId commandId = FindCommand(before, handlerFinder, detail::EnumeratorCommandFinder::Operation::kFindNext, - mAcceptedCommandsIterator, AcceptedCommands); - - VerifyOrReturnValue(commandId != kInvalidCommandId, DataModel::CommandEntry::kInvalid); - return CommandEntryFrom(before, commandId); -} - -std::optional CodegenDataModelProvider::GetAcceptedCommandInfo(const ConcreteCommandPath & path) -{ - - EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateAcceptedCommands); - CommandId commandId = FindCommand(path, handlerFinder, detail::EnumeratorCommandFinder::Operation::kFindExact, - mAcceptedCommandsIterator, AcceptedCommands); - - VerifyOrReturnValue(commandId != kInvalidCommandId, std::nullopt); - return CommandEntryFrom(path, commandId).info; -} - -ConcreteCommandPath CodegenDataModelProvider::FirstGeneratedCommand(const ConcreteClusterPath & path) -{ - EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateGeneratedCommands); - CommandId commandId = - FindCommand(ConcreteCommandPath(path.mEndpointId, path.mClusterId, kInvalidCommandId), handlerFinder, - detail::EnumeratorCommandFinder::Operation::kFindFirst, mGeneratedCommandsIterator, GeneratedCommands); - - VerifyOrReturnValue(commandId != kInvalidCommandId, kInvalidCommandPath); - return ConcreteCommandPath(path.mEndpointId, path.mClusterId, commandId); -} - -ConcreteCommandPath CodegenDataModelProvider::NextGeneratedCommand(const ConcreteCommandPath & before) -{ - EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateGeneratedCommands); - - CommandId commandId = FindCommand(before, handlerFinder, detail::EnumeratorCommandFinder::Operation::kFindNext, - mGeneratedCommandsIterator, GeneratedCommands); - - VerifyOrReturnValue(commandId != kInvalidCommandId, kInvalidCommandPath); - return ConcreteCommandPath(before.mEndpointId, before.mClusterId, commandId); + return result; } void CodegenDataModelProvider::InitDataModelForTesting() @@ -868,67 +596,82 @@ void CodegenDataModelProvider::InitDataModelForTesting() InitDataModelHandler(); } -std::optional CodegenDataModelProvider::FirstDeviceType(EndpointId endpoint) +DataModel::MetadataList CodegenDataModelProvider::DeviceTypes(EndpointId endpointId) { - // Use the `Index` version even though `emberAfDeviceTypeListFromEndpoint` would work because - // index finding is cached in TryFindEndpointIndex and this avoids an extra `emberAfIndexFromEndpoint` - // during `Next` loops. This avoids O(n^2) on number of indexes when iterating over all device types. - // - // Not actually needed for `First`, however this makes First and Next consistent. - std::optional endpoint_index = TryFindEndpointIndex(endpoint); + std::optional endpoint_index = TryFindEndpointIndex(endpointId); if (!endpoint_index.has_value()) { - return std::nullopt; + return {}; } CHIP_ERROR err = CHIP_NO_ERROR; Span deviceTypes = emberAfDeviceTypeListFromEndpointIndex(*endpoint_index, err); - SpanSearchValue> searchable(&deviceTypes); - return DeviceTypeEntryFromEmber(searchable.First(mDeviceTypeIterationHint).Value()); -} - -std::optional CodegenDataModelProvider::NextDeviceType(EndpointId endpoint, - const DataModel::DeviceTypeEntry & previous) -{ - // Use the `Index` version even though `emberAfDeviceTypeListFromEndpoint` would work because - // index finding is cached in TryFindEndpointIndex and this avoids an extra `emberAfIndexFromEndpoint` - // during `Next` loops. This avoids O(n^2) on number of indexes when iterating over all device types. - std::optional endpoint_index = TryFindEndpointIndex(endpoint); - if (!endpoint_index.has_value()) + DataModel::MetadataList result; + err = result.Reserve(deviceTypes.size()); + if (err != CHIP_NO_ERROR) { - return std::nullopt; +#if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING + ChipLogError(AppServer, "Failed to reserve device type buffer space: %" CHIP_ERROR_FORMAT, err.Format()); +#endif + return {}; + } + for (auto & entry : deviceTypes) + { + err = result.Append(DeviceTypeEntryFromEmber(entry)); + if (err != CHIP_NO_ERROR) + { +#if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING + ChipLogError(AppServer, "Failed to append device type entry: %" CHIP_ERROR_FORMAT, err.Format()); +#endif + break; + } } - CHIP_ERROR err = CHIP_NO_ERROR; - chip::Span deviceTypes = emberAfDeviceTypeListFromEndpointIndex(*endpoint_index, err); - SpanSearchValue> searchable(&deviceTypes); - - return DeviceTypeEntryFromEmber(searchable.Next(previous, mDeviceTypeIterationHint).Value()); + return result; } -std::optional CodegenDataModelProvider::GetFirstSemanticTag(EndpointId endpoint) +DataModel::MetadataList CodegenDataModelProvider::SemanticTags(EndpointId endpointId) { - Clusters::Descriptor::Structs::SemanticTagStruct::Type tag; - // we start at the beginning - mSemanticTagIterationHint = 0; - if (GetSemanticTagForEndpointAtIndex(endpoint, 0, tag) == CHIP_NO_ERROR) + DataModel::Provider::SemanticTag semanticTag; + size_t count = 0; + + while (GetSemanticTagForEndpointAtIndex(endpointId, count, semanticTag) == CHIP_NO_ERROR) { - return std::make_optional(tag); + count++; } - return std::nullopt; -} + DataModel::MetadataList result; -std::optional CodegenDataModelProvider::GetNextSemanticTag(EndpointId endpoint, - const SemanticTag & previous) -{ - Clusters::Descriptor::Structs::SemanticTagStruct::Type tag; - std::optional idx = FindNextSemanticTagIndex(endpoint, previous, mSemanticTagIterationHint); - if (idx.has_value() && GetSemanticTagForEndpointAtIndex(endpoint, *idx, tag) == CHIP_NO_ERROR) + CHIP_ERROR err = result.Reserve(count); + if (err != CHIP_NO_ERROR) { - return std::make_optional(tag); +#if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING + ChipLogError(AppServer, "Failed to reserve semantic tag buffer space: %" CHIP_ERROR_FORMAT, err.Format()); +#endif + return {}; } - return std::nullopt; + + for (size_t idx = 0; idx < count; idx++) + { + err = GetSemanticTagForEndpointAtIndex(endpointId, idx, semanticTag); + if (err != CHIP_NO_ERROR) + { +#if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING + ChipLogError(AppServer, "Failed to get semantic tag data: %" CHIP_ERROR_FORMAT, err.Format()); +#endif + break; + } + err = result.Append(semanticTag); + if (err != CHIP_NO_ERROR) + { +#if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING + ChipLogError(AppServer, "Failed to append semantic tag: %" CHIP_ERROR_FORMAT, err.Format()); +#endif + break; + } + } + + return result; } } // namespace app diff --git a/src/data-model-providers/codegen/CodegenDataModelProvider.h b/src/data-model-providers/codegen/CodegenDataModelProvider.h index e3455d5ce03a1b..d0bb369e3dd23f 100644 --- a/src/data-model-providers/codegen/CodegenDataModelProvider.h +++ b/src/data-model-providers/codegen/CodegenDataModelProvider.h @@ -27,61 +27,6 @@ namespace chip { namespace app { -namespace detail { - -/// Handles going through callback-based enumeration of generated/accepted commands -/// for CommandHandlerInterface based items. -/// -/// Offers the ability to focus on some operation for finding a given -/// command id: -/// - FindFirst will return the first found element -/// - FindExact finds the element with the given id -/// - FindNext finds the element following the given id -class EnumeratorCommandFinder -{ -public: - using HandlerCallbackFunction = CHIP_ERROR (CommandHandlerInterface::*)(const ConcreteClusterPath &, - CommandHandlerInterface::CommandIdCallback, void *); - - enum class Operation - { - kFindFirst, // Find the first value in the list - kFindExact, // Find the given value - kFindNext // Find the value AFTER this value - }; - - EnumeratorCommandFinder(HandlerCallbackFunction callback) : - mCallback(callback), mOperation(Operation::kFindFirst), mTarget(kInvalidCommandId) - {} - - /// Find the given command ID that matches the given operation/path. - /// - /// If operation is kFindFirst, then path commandID is ignored. Otherwise it is used as a key to - /// kFindExact or kFindNext. - /// - /// Returns: - /// - std::nullopt if no command found using the command handler interface - /// - kInvalidCommandId if the find failed (but command handler interface does provide a list) - /// - valid id if command handler interface usage succeeds - std::optional FindCommandId(Operation operation, const ConcreteCommandPath & path); - -private: - HandlerCallbackFunction mCallback; - Operation mOperation; - CommandId mTarget; - std::optional mFound = std::nullopt; - - Loop HandlerCallback(CommandId id); - - static Loop HandlerCallbackFn(CommandId id, void * context) - { - auto self = static_cast(context); - return self->HandlerCallback(id); - } -}; - -} // namespace detail - /// An implementation of `InteractionModel::Model` that relies on code-generation /// via zap/ember. /// @@ -96,44 +41,10 @@ class EnumeratorCommandFinder /// however they would share the exact same underlying data and storage). class CodegenDataModelProvider : public DataModel::Provider { -private: - /// Ember commands are stored as a `CommandId *` pointer that is either null (i.e. no commands) - /// or is terminated with 0xFFFF_FFFF aka kInvalidCommandId - /// - /// Since iterator implementations in the data model use Next(before_path) calls, iterating - /// such lists from the beginning would be very inefficient as O(n^2). - /// - /// This class maintains a cached position inside such iteration, such that `Next` calls - /// can be faster. - class EmberCommandListIterator - { - private: - const CommandId * mCurrentList = nullptr; - const CommandId * mCurrentHint = nullptr; // Invariant: mCurrentHint is INSIDE mCurrentList - public: - EmberCommandListIterator() = default; - - /// Returns the first command in the given list (or nullopt if list is null or starts with 0xFFFFFFF) - std::optional First(const CommandId * list); - - /// Returns the command after `previousId` in the given list - std::optional Next(const CommandId * list, CommandId previousId); - - /// Checks if the given command id exists in the given list - bool Exists(const CommandId * list, CommandId toCheck); - - void Reset() { mCurrentList = mCurrentHint = nullptr; } - }; - public: /// clears out internal caching. Especially useful in unit tests, /// where path caching does not really apply (the same path may result in different outcomes) - void Reset() - { - mAcceptedCommandsIterator.Reset(); - mGeneratedCommandsIterator.Reset(); - mPreviouslyFoundCluster = std::nullopt; - } + void Reset() { mPreviouslyFoundCluster = std::nullopt; } void SetPersistentStorageDelegate(PersistentStorageDelegate * delegate) { mPersistentStorageDelegate = delegate; } PersistentStorageDelegate * GetPersistentStorageDelegate() { return mPersistentStorageDelegate; } @@ -155,35 +66,14 @@ class CodegenDataModelProvider : public DataModel::Provider CommandHandler * handler) override; /// attribute tree iteration - DataModel::EndpointEntry FirstEndpoint() override; - DataModel::EndpointEntry NextEndpoint(EndpointId before) override; - std::optional GetEndpointInfo(EndpointId endpoint) override; - bool EndpointExists(EndpointId endpoint) override; - - std::optional FirstDeviceType(EndpointId endpoint) override; - std::optional NextDeviceType(EndpointId endpoint, - const DataModel::DeviceTypeEntry & previous) override; - - std::optional GetFirstSemanticTag(EndpointId endpoint) override; - std::optional GetNextSemanticTag(EndpointId endpoint, const SemanticTag & previous) override; - - DataModel::ClusterEntry FirstServerCluster(EndpointId endpoint) override; - DataModel::ClusterEntry NextServerCluster(const ConcreteClusterPath & before) override; - std::optional GetServerClusterInfo(const ConcreteClusterPath & path) override; - - ConcreteClusterPath FirstClientCluster(EndpointId endpoint) override; - ConcreteClusterPath NextClientCluster(const ConcreteClusterPath & before) override; - - DataModel::AttributeEntry FirstAttribute(const ConcreteClusterPath & cluster) override; - DataModel::AttributeEntry NextAttribute(const ConcreteAttributePath & before) override; - std::optional GetAttributeInfo(const ConcreteAttributePath & path) override; - - DataModel::CommandEntry FirstAcceptedCommand(const ConcreteClusterPath & cluster) override; - DataModel::CommandEntry NextAcceptedCommand(const ConcreteCommandPath & before) override; - std::optional GetAcceptedCommandInfo(const ConcreteCommandPath & path) override; - - ConcreteCommandPath FirstGeneratedCommand(const ConcreteClusterPath & cluster) override; - ConcreteCommandPath NextGeneratedCommand(const ConcreteCommandPath & before) override; + DataModel::MetadataList GeneratedCommands(const ConcreteClusterPath & path) override; + DataModel::MetadataList AcceptedCommands(const ConcreteClusterPath & path) override; + DataModel::MetadataList SemanticTags(EndpointId endpointId) override; + DataModel::MetadataList DeviceTypes(EndpointId endpointId) override; + DataModel::MetadataList Endpoints() override; + DataModel::MetadataList ClientClusters(EndpointId endpointId) override; + DataModel::MetadataList ServerClusters(EndpointId endpointId) override; + DataModel::MetadataList Attributes(const ConcreteClusterPath & path) override; void Temporary_ReportAttributeChanged(const AttributePathParams & path) override; @@ -196,14 +86,7 @@ class CodegenDataModelProvider : public DataModel::Provider private: // Iteration is often done in a tight loop going through all values. // To avoid N^2 iterations, cache a hint of where something is positioned - uint16_t mEndpointIterationHint = 0; - unsigned mServerClusterIterationHint = 0; - unsigned mClientClusterIterationHint = 0; - unsigned mAttributeIterationHint = 0; - unsigned mDeviceTypeIterationHint = 0; - unsigned mSemanticTagIterationHint = 0; - EmberCommandListIterator mAcceptedCommandsIterator; - EmberCommandListIterator mGeneratedCommandsIterator; + uint16_t mEndpointIterationHint = 0; // represents a remembered cluster reference that has been found as // looking for clusters is very common (for every attribute iteration) @@ -232,20 +115,8 @@ class CodegenDataModelProvider : public DataModel::Provider /// Effectively the same as `emberAfFindServerCluster` except with some caching capabilities const EmberAfCluster * FindServerCluster(const ConcreteClusterPath & path); - /// Find the index of the given attribute id - std::optional TryFindAttributeIndex(const EmberAfCluster * cluster, AttributeId id) const; - - /// Find the index of the given cluster id - std::optional TryFindClusterIndex(const EmberAfEndpointType * endpoint, ClusterId id, ClusterSide clusterSide) const; - /// Find the index of the given endpoint id std::optional TryFindEndpointIndex(EndpointId id) const; - - using CommandListGetter = const CommandId *(const EmberAfCluster &); - - CommandId FindCommand(const ConcreteCommandPath & path, detail::EnumeratorCommandFinder & handlerFinder, - detail::EnumeratorCommandFinder::Operation operation, - CodegenDataModelProvider::EmberCommandListIterator & emberIterator, CommandListGetter commandListGetter); }; } // namespace app diff --git a/src/data-model-providers/codegen/CodegenDataModelProvider_Write.cpp b/src/data-model-providers/codegen/CodegenDataModelProvider_Write.cpp index 1eb25f122d3b36..5cd6602fe08d10 100644 --- a/src/data-model-providers/codegen/CodegenDataModelProvider_Write.cpp +++ b/src/data-model-providers/codegen/CodegenDataModelProvider_Write.cpp @@ -190,15 +190,16 @@ DataModel::ActionReturnStatus CodegenDataModelProvider::WriteAttribute(const Dat if (request.path.mDataVersion.HasValue()) { - std::optional clusterInfo = GetServerClusterInfo(request.path); - if (!clusterInfo.has_value()) + DataVersion * versionPtr = emberAfDataVersionStorage(request.path); + + if (versionPtr == nullptr) { ChipLogError(DataManagement, "Unable to get cluster info for Endpoint 0x%x, Cluster " ChipLogFormatMEI, request.path.mEndpointId, ChipLogValueMEI(request.path.mClusterId)); return Status::DataVersionMismatch; } - if (request.path.mDataVersion.Value() != clusterInfo->dataVersion) + if (request.path.mDataVersion.Value() != *versionPtr) { ChipLogError(DataManagement, "Write Version mismatch for Endpoint 0x%x, Cluster " ChipLogFormatMEI, request.path.mEndpointId, ChipLogValueMEI(request.path.mClusterId)); diff --git a/src/data-model-providers/codegen/tests/TestCodegenModelViaMocks.cpp b/src/data-model-providers/codegen/tests/TestCodegenModelViaMocks.cpp index fd2165983891b5..67aed7f1d16ab6 100644 --- a/src/data-model-providers/codegen/tests/TestCodegenModelViaMocks.cpp +++ b/src/data-model-providers/codegen/tests/TestCodegenModelViaMocks.cpp @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - #include #include @@ -34,6 +33,8 @@ #include #include #include +#include +#include #include #include #include @@ -139,6 +140,12 @@ bool operator==(const Access::SubjectDescriptor & a, const Access::SubjectDescri return true; } +struct TestCodegenModelViaMocks : public ::testing::Test +{ + static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); } + static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); } +}; + class TestProviderChangeListener : public ProviderChangeListener { public: @@ -892,331 +899,134 @@ void WriteLe16(void * buffer, uint16_t value) } // namespace -TEST(TestCodegenModelViaMocks, IterateOverEndpoints) +TEST_F(TestCodegenModelViaMocks, IterateOverEndpoints) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; // This iteration relies on the hard-coding that occurs when mock_ember is used - EndpointEntry ep = model.FirstEndpoint(); - EXPECT_EQ(ep.id, kMockEndpoint1); - EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); - EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kFullFamily); - ep = model.NextEndpoint(kMockEndpoint1); - EXPECT_EQ(ep.id, kMockEndpoint2); - EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); - EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kTree); - ep = model.NextEndpoint(kMockEndpoint2); - EXPECT_EQ(ep.id, kMockEndpoint3); - EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); - EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kFullFamily); - ep = model.NextEndpoint(kMockEndpoint3); - EXPECT_EQ(ep.id, kInvalidEndpointId); - - /// Some out of order requests should work as well - ep = model.NextEndpoint(kMockEndpoint2); - EXPECT_EQ(ep.id, kMockEndpoint3); - EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); - EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kFullFamily); - ep = model.NextEndpoint(kMockEndpoint2); - EXPECT_EQ(ep.id, kMockEndpoint3); - EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); - EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kFullFamily); - ep = model.NextEndpoint(kMockEndpoint1); - EXPECT_EQ(ep.id, kMockEndpoint2); - EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); - EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kTree); - ep = model.NextEndpoint(kMockEndpoint1); - EXPECT_EQ(ep.id, kMockEndpoint2); - EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); - EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kTree); - ep = model.NextEndpoint(kMockEndpoint2); - EXPECT_EQ(ep.id, kMockEndpoint3); - EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); - EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kFullFamily); - ep = model.NextEndpoint(kMockEndpoint1); - EXPECT_EQ(ep.id, kMockEndpoint2); - EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); - EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kTree); - ep = model.NextEndpoint(kMockEndpoint3); - EXPECT_EQ(ep.id, kInvalidEndpointId); - ep = model.NextEndpoint(kMockEndpoint3); - EXPECT_EQ(ep.id, kInvalidEndpointId); - ep = model.FirstEndpoint(); - EXPECT_EQ(ep.id, kMockEndpoint1); - EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); - EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kFullFamily); - ep = model.FirstEndpoint(); - EXPECT_EQ(ep.id, kMockEndpoint1); - EXPECT_EQ(ep.info.parentId, kInvalidEndpointId); - EXPECT_EQ(ep.info.compositionPattern, EndpointCompositionPattern::kFullFamily); - - // invalid endpoiunts - ep = model.NextEndpoint(kInvalidEndpointId); - EXPECT_EQ(ep.id, kInvalidEndpointId); - ep = model.NextEndpoint(987u); - EXPECT_EQ(ep.id, kInvalidEndpointId); -} - -TEST(TestCodegenModelViaMocks, GetEndpointInfo) -{ - UseMockNodeConfig config(gTestNodeConfig); - CodegenDataModelProviderWithContext model; + DataModel::MetadataList endpoints = model.Endpoints(); - std::optional info = model.GetEndpointInfo(kMockEndpoint1); - ASSERT_TRUE(info.has_value()); - EXPECT_EQ(info->parentId, kInvalidEndpointId); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_EQ(info->compositionPattern, // NOLINT(bugprone-unchecked-optional-access) - EndpointCompositionPattern::kFullFamily); - info = model.GetEndpointInfo(kMockEndpoint2); - ASSERT_TRUE(info.has_value()); - EXPECT_EQ(info->parentId, kInvalidEndpointId); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_EQ(info->compositionPattern, // NOLINT(bugprone-unchecked-optional-access) - EndpointCompositionPattern::kTree); - info = model.GetEndpointInfo(kMockEndpoint3); - ASSERT_TRUE(info.has_value()); - EXPECT_EQ(info->parentId, kInvalidEndpointId); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_EQ(info->compositionPattern, // NOLINT(bugprone-unchecked-optional-access) - EndpointCompositionPattern::kFullFamily); + ASSERT_EQ(endpoints.Size(), 3u); - // invalid endpoiunts - info = model.GetEndpointInfo(kInvalidEndpointId); - EXPECT_FALSE(info.has_value()); - info = model.GetEndpointInfo(987u); - EXPECT_FALSE(info.has_value()); + EXPECT_EQ(endpoints[0].id, kMockEndpoint1); + EXPECT_EQ(endpoints[0].parentId, kInvalidEndpointId); + EXPECT_EQ(endpoints[0].compositionPattern, EndpointCompositionPattern::kFullFamily); + + EXPECT_EQ(endpoints[1].id, kMockEndpoint2); + EXPECT_EQ(endpoints[1].parentId, kInvalidEndpointId); + EXPECT_EQ(endpoints[1].compositionPattern, EndpointCompositionPattern::kTree); + + EXPECT_EQ(endpoints[2].id, kMockEndpoint3); + EXPECT_EQ(endpoints[2].parentId, kInvalidEndpointId); + EXPECT_EQ(endpoints[2].compositionPattern, EndpointCompositionPattern::kFullFamily); } -TEST(TestCodegenModelViaMocks, IterateOverServerClusters) +TEST_F(TestCodegenModelViaMocks, IterateOverServerClusters) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; chip::Test::ResetVersion(); - EXPECT_FALSE(model.FirstServerCluster(kEndpointIdThatIsMissing).path.HasValidIds()); - EXPECT_FALSE(model.FirstServerCluster(kInvalidEndpointId).path.HasValidIds()); - EXPECT_FALSE(model.NextServerCluster(ConcreteClusterPath(kInvalidEndpointId, 123)).path.HasValidIds()); - EXPECT_FALSE(model.NextServerCluster(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds()); - EXPECT_FALSE(model.NextServerCluster(ConcreteClusterPath(kMockEndpoint1, 981u)).path.HasValidIds()); + EXPECT_TRUE(model.ServerClusters(kEndpointIdThatIsMissing).Empty()); + EXPECT_TRUE(model.ServerClusters(kInvalidEndpointId).Empty()); // mock endpoint 1 has 2 mock clusters: 1 and 2 - ClusterEntry entry = model.FirstServerCluster(kMockEndpoint1); - ASSERT_TRUE(entry.path.HasValidIds()); - EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint1); - EXPECT_EQ(entry.path.mClusterId, MockClusterId(1)); - EXPECT_EQ(entry.info.dataVersion, 0u); - EXPECT_EQ(entry.info.flags.Raw(), 0u); - - chip::Test::BumpVersion(); - - entry = model.NextServerCluster(entry.path); - ASSERT_TRUE(entry.path.HasValidIds()); - EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint1); - EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); - EXPECT_EQ(entry.info.dataVersion, 1u); - EXPECT_EQ(entry.info.flags.Raw(), 0u); - - entry = model.NextServerCluster(entry.path); - EXPECT_FALSE(entry.path.HasValidIds()); - - // mock endpoint 3 has 4 mock clusters: 1 through 4 - entry = model.FirstServerCluster(kMockEndpoint3); - for (uint16_t clusterId = 1; clusterId <= 4; clusterId++) - { - ASSERT_TRUE(entry.path.HasValidIds()); - EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint3); - EXPECT_EQ(entry.path.mClusterId, MockClusterId(clusterId)); - entry = model.NextServerCluster(entry.path); - } - EXPECT_FALSE(entry.path.HasValidIds()); - - // repeat calls should work - for (int i = 0; i < 10; i++) - { - entry = model.FirstServerCluster(kMockEndpoint1); - ASSERT_TRUE(entry.path.HasValidIds()); - EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint1); - EXPECT_EQ(entry.path.mClusterId, MockClusterId(1)); - } - - for (int i = 0; i < 10; i++) - { - ClusterEntry nextEntry = model.NextServerCluster(entry.path); - ASSERT_TRUE(nextEntry.path.HasValidIds()); - EXPECT_EQ(nextEntry.path.mEndpointId, kMockEndpoint1); - EXPECT_EQ(nextEntry.path.mClusterId, MockClusterId(2)); - } -} + auto serverClusters = model.ServerClusters(kMockEndpoint1); + ASSERT_EQ(serverClusters.Size(), 2u); -TEST(TestCodegenModelViaMocks, GetServerClusterInfo) -{ + EXPECT_EQ(serverClusters[0].clusterId, MockClusterId(1)); + EXPECT_EQ(serverClusters[0].dataVersion, 0u); + EXPECT_EQ(serverClusters[0].flags.Raw(), 0u); - UseMockNodeConfig config(gTestNodeConfig); - CodegenDataModelProviderWithContext model; - - chip::Test::ResetVersion(); + EXPECT_EQ(serverClusters[1].clusterId, MockClusterId(2)); + EXPECT_EQ(serverClusters[1].dataVersion, 0u); + EXPECT_EQ(serverClusters[1].flags.Raw(), 0u); - ASSERT_FALSE(model.GetServerClusterInfo(ConcreteClusterPath(kInvalidEndpointId, kInvalidClusterId)).has_value()); - ASSERT_FALSE(model.GetServerClusterInfo(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).has_value()); - ASSERT_FALSE(model.GetServerClusterInfo(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).has_value()); - ASSERT_FALSE(model.GetServerClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).has_value()); + chip::Test::BumpVersion(); - // now get the value - std::optional info = model.GetServerClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); - ASSERT_TRUE(info.has_value()); - EXPECT_EQ(info->dataVersion, 0u); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_EQ(info->flags.Raw(), 0u); // NOLINT(bugprone-unchecked-optional-access) + serverClusters = model.ServerClusters(kMockEndpoint1); + ASSERT_EQ(serverClusters.Size(), 2u); + EXPECT_EQ(serverClusters[0].dataVersion, 1u); + EXPECT_EQ(serverClusters[1].dataVersion, 1u); - chip::Test::BumpVersion(); - info = model.GetServerClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); - ASSERT_TRUE(info.has_value()); - EXPECT_EQ(info->dataVersion, 1u); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_EQ(info->flags.Raw(), 0u); // NOLINT(bugprone-unchecked-optional-access) + // mock endpoint 3 has 4 mock clusters: 1 through 4 + serverClusters = model.ServerClusters(kMockEndpoint3); + ASSERT_EQ(serverClusters.Size(), 4u); + EXPECT_EQ(serverClusters[0].clusterId, MockClusterId(1)); + EXPECT_EQ(serverClusters[1].clusterId, MockClusterId(2)); + EXPECT_EQ(serverClusters[2].clusterId, MockClusterId(3)); + EXPECT_EQ(serverClusters[3].clusterId, MockClusterId(4)); } -TEST(TestCodegenModelViaMocks, IterateOverClientClusters) +TEST_F(TestCodegenModelViaMocks, IterateOverClientClusters) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; - EXPECT_FALSE(model.FirstClientCluster(kEndpointIdThatIsMissing).HasValidIds()); - EXPECT_FALSE(model.FirstClientCluster(kInvalidEndpointId).HasValidIds()); - EXPECT_FALSE(model.NextClientCluster(ConcreteClusterPath(kInvalidEndpointId, 123)).HasValidIds()); - EXPECT_FALSE(model.NextClientCluster(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).HasValidIds()); - EXPECT_FALSE(model.NextClientCluster(ConcreteClusterPath(kMockEndpoint1, 981u)).HasValidIds()); + EXPECT_TRUE(model.ClientClusters(kEndpointIdThatIsMissing).Empty()); + EXPECT_TRUE(model.ClientClusters(kInvalidEndpointId).Empty()); // mock endpoint 1 has 2 mock client clusters: 3 and 4 - ConcreteClusterPath path = model.FirstClientCluster(kMockEndpoint1); - ASSERT_TRUE(path.HasValidIds()); - EXPECT_EQ(path.mEndpointId, kMockEndpoint1); - EXPECT_EQ(path.mClusterId, MockClusterId(3)); - - path = model.NextClientCluster(path); - ASSERT_TRUE(path.HasValidIds()); - EXPECT_EQ(path.mEndpointId, kMockEndpoint1); - EXPECT_EQ(path.mClusterId, MockClusterId(4)); + auto clientClusters = model.ClientClusters(kMockEndpoint1); - path = model.NextClientCluster(path); - EXPECT_FALSE(path.HasValidIds()); + const ClusterId kExpectedClusters1[] = { MockClusterId(3), MockClusterId(4) }; + ASSERT_TRUE(clientClusters.GetSpanValidForLifetime().data_equal(Span(kExpectedClusters1))); // mock endpoint 2 has 1 mock client clusters: 3(has server side at the same time) and 4 - path = model.FirstClientCluster(kMockEndpoint2); - for (uint16_t clusterId = 3; clusterId <= 4; clusterId++) - { - ASSERT_TRUE(path.HasValidIds()); - EXPECT_EQ(path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(path.mClusterId, MockClusterId(clusterId)); - path = model.NextClientCluster(path); - } - EXPECT_FALSE(path.HasValidIds()); - - // repeat calls should work - for (int i = 0; i < 10; i++) - { - path = model.FirstClientCluster(kMockEndpoint1); - ASSERT_TRUE(path.HasValidIds()); - EXPECT_EQ(path.mEndpointId, kMockEndpoint1); - EXPECT_EQ(path.mClusterId, MockClusterId(3)); - } + clientClusters = model.ClientClusters(kMockEndpoint2); - for (int i = 0; i < 10; i++) - { - ConcreteClusterPath nextPath = model.NextClientCluster(path); - ASSERT_TRUE(nextPath.HasValidIds()); - EXPECT_EQ(nextPath.mEndpointId, kMockEndpoint1); - EXPECT_EQ(nextPath.mClusterId, MockClusterId(4)); - } + const ClusterId kExpectedClusters2[] = { MockClusterId(3), MockClusterId(4) }; + ASSERT_TRUE(clientClusters.GetSpanValidForLifetime().data_equal(Span(kExpectedClusters2))); } -TEST(TestCodegenModelViaMocks, IterateOverAttributes) +TEST_F(TestCodegenModelViaMocks, IterateOverAttributes) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; // invalid paths should return in "no more data" - ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).path.HasValidIds()); - ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).path.HasValidIds()); - ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).path.HasValidIds()); - ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds()); - - ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kEndpointIdThatIsMissing, MockClusterId(1), 1u)).path.HasValidIds()); - ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kInvalidEndpointId, MockClusterId(1), 1u)).path.HasValidIds()); - ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), 1u)).path.HasValidIds()); - ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kMockEndpoint1, kInvalidClusterId, 1u)).path.HasValidIds()); - ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), 987u)).path.HasValidIds()); + ASSERT_TRUE(model.Attributes(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).Empty()); + ASSERT_TRUE(model.Attributes(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).Empty()); + ASSERT_TRUE(model.Attributes(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).Empty()); + ASSERT_TRUE(model.Attributes(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).Empty()); // should be able to iterate over valid paths - AttributeEntry entry = model.FirstAttribute(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); - ASSERT_TRUE(entry.path.HasValidIds()); - ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); - ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); - ASSERT_EQ(entry.path.mAttributeId, ClusterRevision::Id); - ASSERT_FALSE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); - - entry = model.NextAttribute(entry.path); - ASSERT_TRUE(entry.path.HasValidIds()); - ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); - ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); - ASSERT_EQ(entry.path.mAttributeId, FeatureMap::Id); - ASSERT_FALSE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); - - entry = model.NextAttribute(entry.path); - ASSERT_TRUE(entry.path.HasValidIds()); - ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); - ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); - ASSERT_EQ(entry.path.mAttributeId, MockAttributeId(1)); - ASSERT_FALSE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); - - entry = model.NextAttribute(entry.path); - ASSERT_TRUE(entry.path.HasValidIds()); - ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); - ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); - ASSERT_EQ(entry.path.mAttributeId, MockAttributeId(2)); - ASSERT_TRUE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); - - entry = model.NextAttribute(entry.path); - ASSERT_FALSE(entry.path.HasValidIds()); - - // repeated calls should work - for (int i = 0; i < 10; i++) - { - entry = model.FirstAttribute(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); - ASSERT_TRUE(entry.path.HasValidIds()); - ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); - ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); - ASSERT_EQ(entry.path.mAttributeId, ClusterRevision::Id); - ASSERT_FALSE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); - } + auto attributes = model.Attributes(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); + ASSERT_EQ(attributes.Size(), 4u); - for (int i = 0; i < 10; i++) - { - entry = model.NextAttribute(ConcreteAttributePath(kMockEndpoint2, MockClusterId(2), MockAttributeId(1))); - ASSERT_TRUE(entry.path.HasValidIds()); - ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); - ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); - ASSERT_EQ(entry.path.mAttributeId, MockAttributeId(2)); - ASSERT_TRUE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); - } + ASSERT_EQ(attributes[0].attributeId, ClusterRevision::Id); + ASSERT_FALSE(attributes[0].flags.Has(AttributeQualityFlags::kListAttribute)); + + ASSERT_EQ(attributes[1].attributeId, FeatureMap::Id); + ASSERT_FALSE(attributes[1].flags.Has(AttributeQualityFlags::kListAttribute)); + + ASSERT_EQ(attributes[2].attributeId, MockAttributeId(1)); + ASSERT_FALSE(attributes[2].flags.Has(AttributeQualityFlags::kListAttribute)); + + ASSERT_EQ(attributes[3].attributeId, MockAttributeId(2)); + ASSERT_TRUE(attributes[3].flags.Has(AttributeQualityFlags::kListAttribute)); } -TEST(TestCodegenModelViaMocks, GetAttributeInfo) +TEST_F(TestCodegenModelViaMocks, FindAttribute) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; + AttributeFinder finder(&model); + // various non-existent or invalid paths should return no info data - ASSERT_FALSE( - model.GetAttributeInfo(ConcreteAttributePath(kInvalidEndpointId, kInvalidClusterId, kInvalidAttributeId)).has_value()); - ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kInvalidEndpointId, kInvalidClusterId, FeatureMap::Id)).has_value()); - ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kInvalidEndpointId, MockClusterId(1), FeatureMap::Id)).has_value()); - ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, kInvalidClusterId, FeatureMap::Id)).has_value()); - ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), FeatureMap::Id)).has_value()); - ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), kInvalidAttributeId)).has_value()); - ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))).has_value()); + ASSERT_FALSE(finder.Find(ConcreteAttributePath(kInvalidEndpointId, kInvalidClusterId, kInvalidAttributeId)).has_value()); + ASSERT_FALSE(finder.Find(ConcreteAttributePath(kInvalidEndpointId, kInvalidClusterId, FeatureMap::Id)).has_value()); + ASSERT_FALSE(finder.Find(ConcreteAttributePath(kInvalidEndpointId, MockClusterId(1), FeatureMap::Id)).has_value()); + ASSERT_FALSE(finder.Find(ConcreteAttributePath(kMockEndpoint1, kInvalidClusterId, FeatureMap::Id)).has_value()); + ASSERT_FALSE(finder.Find(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), FeatureMap::Id)).has_value()); + ASSERT_FALSE(finder.Find(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), kInvalidAttributeId)).has_value()); + ASSERT_FALSE(finder.Find(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))).has_value()); // valid info - std::optional info = - model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), FeatureMap::Id)); + std::optional info = finder.Find(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), FeatureMap::Id)); ASSERT_TRUE(info.has_value()); EXPECT_FALSE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) @@ -1224,14 +1034,14 @@ TEST(TestCodegenModelViaMocks, GetAttributeInfo) EXPECT_EQ(info->readPrivilege, chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access) EXPECT_EQ(info->writePrivilege, chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access) - info = model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint2, MockClusterId(2), MockAttributeId(2))); + info = finder.Find(ConcreteAttributePath(kMockEndpoint2, MockClusterId(2), MockAttributeId(2))); ASSERT_TRUE(info.has_value()); EXPECT_TRUE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) EXPECT_EQ(info->readPrivilege, chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access) EXPECT_EQ(info->writePrivilege, chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access) // test a read-only attribute, which will not have a write privilege - info = model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint3, MockClusterId(3), kReadOnlyAttributeId)); + info = finder.Find(ConcreteAttributePath(kMockEndpoint3, MockClusterId(3), kReadOnlyAttributeId)); ASSERT_TRUE(info.has_value()); EXPECT_FALSE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) EXPECT_EQ(info->readPrivilege, chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access) @@ -1239,178 +1049,63 @@ TEST(TestCodegenModelViaMocks, GetAttributeInfo) } // global attributes are EXPLICITLY not supported -TEST(TestCodegenModelViaMocks, GlobalAttributeInfo) +TEST_F(TestCodegenModelViaMocks, GlobalAttributeInfo) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; - std::optional info = model.GetAttributeInfo( + AttributeFinder finder(&model); + + std::optional info = finder.Find( ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::GeneratedCommandList::Id)); ASSERT_FALSE(info.has_value()); - info = model.GetAttributeInfo( - ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::AttributeList::Id)); + info = finder.Find(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::AttributeList::Id)); ASSERT_FALSE(info.has_value()); } -TEST(TestCodegenModelViaMocks, IterateOverAcceptedCommands) +TEST_F(TestCodegenModelViaMocks, IterateOverAcceptedCommands) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; // invalid paths should return in "no more data" - ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).path.HasValidIds()); - ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).path.HasValidIds()); - ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).path.HasValidIds()); - ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds()); - - // should be able to iterate over valid paths - CommandEntry entry = model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); - ASSERT_TRUE(entry.path.HasValidIds()); - EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); - EXPECT_EQ(entry.path.mCommandId, 1u); - - entry = model.NextAcceptedCommand(entry.path); - ASSERT_TRUE(entry.path.HasValidIds()); - EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); - EXPECT_EQ(entry.path.mCommandId, 2u); - - entry = model.NextAcceptedCommand(entry.path); - ASSERT_TRUE(entry.path.HasValidIds()); - EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); - EXPECT_EQ(entry.path.mCommandId, 23u); - - entry = model.NextAcceptedCommand(entry.path); - ASSERT_FALSE(entry.path.HasValidIds()); - - // attempt some out-of-order requests as well - entry = model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(3))); - ASSERT_TRUE(entry.path.HasValidIds()); - EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(entry.path.mClusterId, MockClusterId(3)); - EXPECT_EQ(entry.path.mCommandId, 11u); - - for (int i = 0; i < 10; i++) - { - entry = model.NextAcceptedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 2)); - ASSERT_TRUE(entry.path.HasValidIds()); - EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); - EXPECT_EQ(entry.path.mCommandId, 23u); - } - - for (int i = 0; i < 10; i++) - { - entry = model.NextAcceptedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1)); - ASSERT_TRUE(entry.path.HasValidIds()); - EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); - EXPECT_EQ(entry.path.mCommandId, 2u); - } - - for (int i = 0; i < 10; i++) - { - entry = model.NextAcceptedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(3), 10)); - EXPECT_FALSE(entry.path.HasValidIds()); - } -} - -TEST(TestCodegenModelViaMocks, AcceptedCommandInfo) -{ - UseMockNodeConfig config(gTestNodeConfig); - CodegenDataModelProviderWithContext model; - - // invalid paths should return in "no more data" - ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kEndpointIdThatIsMissing, MockClusterId(1), 1)).has_value()); - ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kInvalidEndpointId, MockClusterId(1), 1)).has_value()); - ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(10), 1)).has_value()); - ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, kInvalidClusterId, 1)).has_value()); - ASSERT_FALSE( - model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), kInvalidCommandId)).has_value()); - - std::optional info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1u)); - ASSERT_TRUE(info.has_value()); - - info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 2u)); - ASSERT_TRUE(info.has_value()); + ASSERT_TRUE(model.AcceptedCommands(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).Empty()); + ASSERT_TRUE(model.AcceptedCommands(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).Empty()); + ASSERT_TRUE(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).Empty()); + ASSERT_TRUE(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).Empty()); - info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1u)); - ASSERT_TRUE(info.has_value()); - - info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1u)); - ASSERT_TRUE(info.has_value()); - - info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 23u)); - ASSERT_TRUE(info.has_value()); - - info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1234u)); - ASSERT_FALSE(info.has_value()); + MetadataList cmds = + model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); + ASSERT_EQ(cmds.Size(), 3u); + ASSERT_EQ(cmds[0].commandId, 1u); + ASSERT_EQ(cmds[1].commandId, 2u); + ASSERT_EQ(cmds[2].commandId, 23u); } -TEST(TestCodegenModelViaMocks, IterateOverGeneratedCommands) +TEST_F(TestCodegenModelViaMocks, IterateOverGeneratedCommands) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; // invalid paths should return in "no more data" - ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).HasValidIds()); - ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).HasValidIds()); - ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).HasValidIds()); - ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).HasValidIds()); + ASSERT_TRUE(model.GeneratedCommands(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).Empty()); + ASSERT_TRUE(model.GeneratedCommands(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).Empty()); + ASSERT_TRUE(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).Empty()); + ASSERT_TRUE(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).Empty()); // should be able to iterate over valid paths - ConcreteCommandPath path = model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); - ASSERT_TRUE(path.HasValidIds()); - EXPECT_EQ(path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(path.mClusterId, MockClusterId(2)); - EXPECT_EQ(path.mCommandId, 2u); - - path = model.NextGeneratedCommand(path); - ASSERT_TRUE(path.HasValidIds()); - EXPECT_EQ(path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(path.mClusterId, MockClusterId(2)); - EXPECT_EQ(path.mCommandId, 10u); - - path = model.NextGeneratedCommand(path); - ASSERT_FALSE(path.HasValidIds()); - - // attempt some out-of-order requests as well - path = model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(3))); - ASSERT_TRUE(path.HasValidIds()); - EXPECT_EQ(path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(path.mClusterId, MockClusterId(3)); - EXPECT_EQ(path.mCommandId, 4u); - - for (int i = 0; i < 10; i++) - { - path = model.NextGeneratedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 2)); - ASSERT_TRUE(path.HasValidIds()); - EXPECT_EQ(path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(path.mClusterId, MockClusterId(2)); - EXPECT_EQ(path.mCommandId, 10u); - } - - for (int i = 0; i < 10; i++) - { - path = model.NextGeneratedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(3), 4)); - ASSERT_TRUE(path.HasValidIds()); - EXPECT_EQ(path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(path.mClusterId, MockClusterId(3)); - EXPECT_EQ(path.mCommandId, 6u); - } + MetadataList cmds = model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); + const CommandId expectedCommands2[] = { 2, 10 }; + ASSERT_TRUE(cmds.GetSpanValidForLifetime().data_equal(Span(expectedCommands2))); - for (int i = 0; i < 10; i++) - { - path = model.NextGeneratedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(3), 6)); - EXPECT_FALSE(path.HasValidIds()); - } + cmds = model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint2, MockClusterId(3))); + const CommandId expectedCommands3[] = { 4, 6 }; + ASSERT_TRUE(cmds.GetSpanValidForLifetime().data_equal(Span(expectedCommands3))); } -TEST(TestCodegenModelViaMocks, CommandHandlerInterfaceAcceptedCommands) +TEST_F(TestCodegenModelViaMocks, CommandHandlerInterfaceCommandHandling) { UseMockNodeConfig config(gTestNodeConfig); @@ -1421,17 +1116,15 @@ TEST(TestCodegenModelViaMocks, CommandHandlerInterfaceAcceptedCommands) CustomListCommandHandler handler(MakeOptional(kMockEndpoint1), MockClusterId(1)); // At this point, without overrides, there should be no accepted/generated commands - EXPECT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))).IsValid()); - EXPECT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))).HasValidIds()); - EXPECT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 1234)).has_value()); + ASSERT_TRUE(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))).Empty()); + ASSERT_TRUE(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))).Empty()); handler.SetOverrideAccepted(true); handler.SetOverrideGenerated(true); // with overrides, the list is still empty ... - EXPECT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))).IsValid()); - EXPECT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))).HasValidIds()); - EXPECT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 1234)).has_value()); + ASSERT_TRUE(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))).Empty()); + ASSERT_TRUE(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))).Empty()); // set some overrides handler.AcceptedVec().push_back(1234); @@ -1439,32 +1132,19 @@ TEST(TestCodegenModelViaMocks, CommandHandlerInterfaceAcceptedCommands) handler.GeneratedVec().push_back(33); - DataModel::CommandEntry entry; - - entry = model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); - EXPECT_TRUE(entry.IsValid()); - EXPECT_EQ(entry.path, ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 1234)); - - entry = model.NextAcceptedCommand(entry.path); - EXPECT_TRUE(entry.IsValid()); - EXPECT_EQ(entry.path, ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 999)); + MetadataList acceptedCommands = + model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); - entry = model.NextAcceptedCommand(entry.path); - EXPECT_FALSE(entry.IsValid()); + ASSERT_EQ(acceptedCommands.Size(), 2u); + ASSERT_EQ(acceptedCommands[0].commandId, 1234u); + ASSERT_EQ(acceptedCommands[1].commandId, 999u); - ConcreteCommandPath path = model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); - EXPECT_TRUE(path.HasValidIds()); - EXPECT_EQ(path, ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 33)); - path = model.NextGeneratedCommand(path); - EXPECT_FALSE(path.HasValidIds()); - - // Command finding should work - EXPECT_TRUE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 1234)).has_value()); - EXPECT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 88)).has_value()); - EXPECT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 33)).has_value()); + MetadataList generatedCommands = model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); + const CommandId expectedGeneratedCommands[] = { 33 }; + ASSERT_TRUE(generatedCommands.GetSpanValidForLifetime().data_equal(Span(expectedGeneratedCommands))); } -TEST(TestCodegenModelViaMocks, ReadForInvalidGlobalAttributePath) +TEST_F(TestCodegenModelViaMocks, ReadForInvalidGlobalAttributePath) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -1487,7 +1167,7 @@ TEST(TestCodegenModelViaMocks, ReadForInvalidGlobalAttributePath) } } -TEST(TestCodegenModelViaMocks, EmberAttributeInvalidRead) +TEST_F(TestCodegenModelViaMocks, EmberAttributeInvalidRead) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -1521,7 +1201,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeInvalidRead) } } -TEST(TestCodegenModelViaMocks, AccessInterfaceUnsupportedRead) +TEST_F(TestCodegenModelViaMocks, AccessInterfaceUnsupportedRead) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -1544,63 +1224,63 @@ TEST(TestCodegenModelViaMocks, AccessInterfaceUnsupportedRead) ASSERT_FALSE(encoder->TriedEncode()); } -TEST(TestCodegenModelViaMocks, EmberAttributeReadInt32S) +TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt32S) { TestEmberScalarTypeRead(-1234); } -TEST(TestCodegenModelViaMocks, EmberAttributeReadEnum16) +TEST_F(TestCodegenModelViaMocks, EmberAttributeReadEnum16) { TestEmberScalarTypeRead(0x1234); } -TEST(TestCodegenModelViaMocks, EmberAttributeReadFloat) +TEST_F(TestCodegenModelViaMocks, EmberAttributeReadFloat) { TestEmberScalarTypeRead(0.625); } -TEST(TestCodegenModelViaMocks, EmberAttributeReadDouble) +TEST_F(TestCodegenModelViaMocks, EmberAttributeReadDouble) { TestEmberScalarTypeRead(0.625); } -TEST(TestCodegenModelViaMocks, EmberAttributeReadInt24U) +TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt24U) { TestEmberScalarTypeRead, ZCL_INT24U_ATTRIBUTE_TYPE>(0x1234AB); } -TEST(TestCodegenModelViaMocks, EmberAttributeReadInt32U) +TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt32U) { TestEmberScalarTypeRead(0x1234ABCD); } -TEST(TestCodegenModelViaMocks, EmberAttributeReadInt40U) +TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt40U) { TestEmberScalarTypeRead, ZCL_INT40U_ATTRIBUTE_TYPE>(0x1122334455); } -TEST(TestCodegenModelViaMocks, EmberAttributeReadInt48U) +TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt48U) { TestEmberScalarTypeRead, ZCL_INT48U_ATTRIBUTE_TYPE>(0xAABB11223344); } -TEST(TestCodegenModelViaMocks, EmberAttributeReadInt56U) +TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt56U) { TestEmberScalarTypeRead, ZCL_INT56U_ATTRIBUTE_TYPE>(0xAABB11223344); } -TEST(TestCodegenModelViaMocks, EmberAttributeReadBool) +TEST_F(TestCodegenModelViaMocks, EmberAttributeReadBool) { TestEmberScalarTypeRead(true); TestEmberScalarTypeRead(false); } -TEST(TestCodegenModelViaMocks, EmberAttributeReadInt8U) +TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt8U) { TestEmberScalarTypeRead(0x12); } -TEST(TestCodegenModelViaMocks, EmberAttributeReadNulls) +TEST_F(TestCodegenModelViaMocks, EmberAttributeReadNulls) { TestEmberScalarNullRead(); TestEmberScalarNullRead(); @@ -1626,7 +1306,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadNulls) TestEmberScalarNullRead(); } -TEST(TestCodegenModelViaMocks, EmberAttributeReadErrorReading) +TEST_F(TestCodegenModelViaMocks, EmberAttributeReadErrorReading) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -1660,7 +1340,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadErrorReading) chip::Test::SetEmberReadOutput(ByteSpan()); } -TEST(TestCodegenModelViaMocks, EmberAttributeReadNullOctetString) +TEST_F(TestCodegenModelViaMocks, EmberAttributeReadNullOctetString) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -1695,7 +1375,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadNullOctetString) ASSERT_TRUE(actual.IsNull()); } -TEST(TestCodegenModelViaMocks, EmberAttributeReadOctetString) +TEST_F(TestCodegenModelViaMocks, EmberAttributeReadOctetString) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -1733,7 +1413,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadOctetString) ASSERT_TRUE(actual.data_equal(expected)); } -TEST(TestCodegenModelViaMocks, EmberAttributeReadLongOctetString) +TEST_F(TestCodegenModelViaMocks, EmberAttributeReadLongOctetString) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -1770,7 +1450,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadLongOctetString) ASSERT_TRUE(actual.data_equal(expected)); } -TEST(TestCodegenModelViaMocks, EmberAttributeReadShortString) +TEST_F(TestCodegenModelViaMocks, EmberAttributeReadShortString) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -1806,7 +1486,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadShortString) ASSERT_TRUE(actual.data_equal("abcde"_span)); } -TEST(TestCodegenModelViaMocks, EmberAttributeReadLongString) +TEST_F(TestCodegenModelViaMocks, EmberAttributeReadLongString) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -1842,7 +1522,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadLongString) ASSERT_TRUE(actual.data_equal("abcde"_span)); } -TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceStructRead) +TEST_F(TestCodegenModelViaMocks, AttributeAccessInterfaceStructRead) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -1887,7 +1567,7 @@ TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceStructRead) ASSERT_TRUE(actual.e.data_equal("foo"_span)); } -TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceReadError) +TEST_F(TestCodegenModelViaMocks, AttributeAccessInterfaceReadError) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -1904,7 +1584,7 @@ TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceReadError) ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_ERROR_KEY_NOT_FOUND); } -TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceListRead) +TEST_F(TestCodegenModelViaMocks, AttributeAccessInterfaceListRead) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -1958,7 +1638,7 @@ TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceListRead) } } -TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceListOverflowRead) +TEST_F(TestCodegenModelViaMocks, AttributeAccessInterfaceListOverflowRead) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2018,7 +1698,7 @@ TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceListOverflowRead) } } -TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceListIncrementalRead) +TEST_F(TestCodegenModelViaMocks, AttributeAccessInterfaceListIncrementalRead) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2083,7 +1763,7 @@ TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceListIncrementalRead) } } -TEST(TestCodegenModelViaMocks, ReadGlobalAttributeAttributeList) +TEST_F(TestCodegenModelViaMocks, ReadGlobalAttributeAttributeList) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2139,7 +1819,7 @@ TEST(TestCodegenModelViaMocks, ReadGlobalAttributeAttributeList) } } -TEST(TestCodegenModelViaMocks, EmberAttributeWriteAclDeny) +TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteAclDeny) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2162,7 +1842,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeWriteAclDeny) ASSERT_TRUE(model.ChangeListener().DirtyList().empty()); } -TEST(TestCodegenModelViaMocks, EmberAttributeWriteBasicTypes) +TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteBasicTypes) { TestEmberScalarTypeWrite(0x12); TestEmberScalarTypeWrite(0x1234); @@ -2188,7 +1868,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeWriteBasicTypes) TestEmberScalarTypeWrite(0.625); } -TEST(TestCodegenModelViaMocks, EmberAttributeWriteInvalidValueToNullable) +TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteInvalidValueToNullable) { TestEmberScalarTypeWriteNullValueToNullable(); TestEmberScalarTypeWriteNullValueToNullable(); @@ -2214,7 +1894,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeWriteInvalidValueToNullable) TestEmberScalarTypeWriteNullValueToNullable(); } -TEST(TestCodegenModelViaMocks, EmberTestWriteReservedNullPlaceholderToNullable) +TEST_F(TestCodegenModelViaMocks, EmberTestWriteReservedNullPlaceholderToNullable) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2231,7 +1911,7 @@ TEST(TestCodegenModelViaMocks, EmberTestWriteReservedNullPlaceholderToNullable) ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::ConstraintError); } -TEST(TestCodegenModelViaMocks, EmberTestWriteOutOfRepresentableRangeOddIntegerNonNullable) +TEST_F(TestCodegenModelViaMocks, EmberTestWriteOutOfRepresentableRangeOddIntegerNonNullable) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2247,7 +1927,7 @@ TEST(TestCodegenModelViaMocks, EmberTestWriteOutOfRepresentableRangeOddIntegerNo ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_IM_GLOBAL_STATUS(ConstraintError)); } -TEST(TestCodegenModelViaMocks, EmberTestWriteOutOfRepresentableRangeOddIntegerNullable) +TEST_F(TestCodegenModelViaMocks, EmberTestWriteOutOfRepresentableRangeOddIntegerNullable) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2263,7 +1943,7 @@ TEST(TestCodegenModelViaMocks, EmberTestWriteOutOfRepresentableRangeOddIntegerNu ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_IM_GLOBAL_STATUS(ConstraintError)); } -TEST(TestCodegenModelViaMocksNullValueToNullables, EmberAttributeWriteBasicTypesLowestValue) +TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteBasicTypesLowestValue) { TestEmberScalarTypeWrite(-127); TestEmberScalarTypeWrite(-32767); @@ -2275,7 +1955,7 @@ TEST(TestCodegenModelViaMocksNullValueToNullables, EmberAttributeWriteBasicTypes TestEmberScalarTypeWrite(-9223372036854775807); } -TEST(TestCodegenModelViaMocks, EmberAttributeWriteNulls) +TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteNulls) { TestEmberScalarNullWrite(); TestEmberScalarNullWrite(); @@ -2299,7 +1979,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeWriteNulls) TestEmberScalarNullWrite(); } -TEST(TestCodegenModelViaMocks, EmberAttributeWriteShortString) +TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteShortString) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2316,7 +1996,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeWriteShortString) ASSERT_TRUE(asCharSpan.data_equal("\x0Bhello world"_span)); } -TEST(TestCodegenModelViaMocks, EmberAttributeWriteLongStringOutOfBounds) +TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteLongStringOutOfBounds) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2333,7 +2013,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeWriteLongStringOutOfBounds) ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::InvalidValue); } -TEST(TestCodegenModelViaMocks, EmberAttributeWriteLongString) +TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteLongString) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2355,7 +2035,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeWriteLongString) ASSERT_TRUE(asCharSpan.data_equal("text"_span)); } -TEST(TestCodegenModelViaMocks, EmberAttributeWriteNullableLongStringValue) +TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteNullableLongStringValue) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2377,7 +2057,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeWriteNullableLongStringValue) ASSERT_TRUE(asCharSpan.data_equal("text"_span)); } -TEST(TestCodegenModelViaMocks, EmberAttributeWriteLongNullableStringNull) +TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteLongNullableStringNull) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2395,7 +2075,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeWriteLongNullableStringNull) ASSERT_EQ(writtenData[1], 0xFF); } -TEST(TestCodegenModelViaMocks, EmberAttributeWriteShortBytes) +TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteShortBytes) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2417,7 +2097,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeWriteShortBytes) EXPECT_EQ(writtenData[3], 13u); } -TEST(TestCodegenModelViaMocks, EmberAttributeWriteLongBytes) +TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteLongBytes) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2442,7 +2122,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeWriteLongBytes) EXPECT_EQ(writtenData[4], 13u); } -TEST(TestCodegenModelViaMocks, EmberAttributeWriteTimedWrite) +TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteTimedWrite) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2460,7 +2140,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeWriteTimedWrite) ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR); } -TEST(TestCodegenModelViaMocks, EmberAttributeWriteReadOnlyAttribute) +TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteReadOnlyAttribute) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2478,7 +2158,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeWriteReadOnlyAttribute) ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR); } -TEST(TestCodegenModelViaMocks, EmberAttributeWriteDataVersion) +TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteDataVersion) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2504,7 +2184,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeWriteDataVersion) ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR); } -TEST(TestCodegenModelViaMocks, WriteToInvalidPath) +TEST_F(TestCodegenModelViaMocks, WriteToInvalidPath) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2534,7 +2214,7 @@ TEST(TestCodegenModelViaMocks, WriteToInvalidPath) } } -TEST(TestCodegenModelViaMocks, WriteToGlobalAttribute) +TEST_F(TestCodegenModelViaMocks, WriteToGlobalAttribute) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2547,7 +2227,7 @@ TEST(TestCodegenModelViaMocks, WriteToGlobalAttribute) ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::UnsupportedWrite); } -TEST(TestCodegenModelViaMocks, EmberWriteFailure) +TEST_F(TestCodegenModelViaMocks, EmberWriteFailure) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2570,7 +2250,7 @@ TEST(TestCodegenModelViaMocks, EmberWriteFailure) chip::Test::SetEmberReadOutput(ByteSpan()); } -TEST(TestCodegenModelViaMocks, EmberWriteAttributeAccessInterfaceTest) +TEST_F(TestCodegenModelViaMocks, EmberWriteAttributeAccessInterfaceTest) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2609,7 +2289,7 @@ TEST(TestCodegenModelViaMocks, EmberWriteAttributeAccessInterfaceTest) TestEmberScalarNullWrite(); } -TEST(TestCodegenModelViaMocks, EmberInvokeTest) +TEST_F(TestCodegenModelViaMocks, EmberInvokeTest) { // Ember invoke is fully code-generated - there is a single function for Dispatch // that will do a `switch` on the path elements and invoke a corresponding `emberAf*` @@ -2650,7 +2330,7 @@ TEST(TestCodegenModelViaMocks, EmberInvokeTest) } } -TEST(TestCodegenModelViaMocks, EmberWriteAttributeAccessInterfaceReturningError) +TEST_F(TestCodegenModelViaMocks, EmberWriteAttributeAccessInterfaceReturningError) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2676,7 +2356,7 @@ TEST(TestCodegenModelViaMocks, EmberWriteAttributeAccessInterfaceReturningError) ASSERT_TRUE(model.ChangeListener().DirtyList().empty()); } -TEST(TestCodegenModelViaMocks, EmberWriteInvalidDataType) +TEST_F(TestCodegenModelViaMocks, EmberWriteInvalidDataType) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; @@ -2704,104 +2384,63 @@ TEST(TestCodegenModelViaMocks, EmberWriteInvalidDataType) ASSERT_TRUE(model.ChangeListener().DirtyList().empty()); } -TEST(TestCodegenModelViaMocks, DeviceTypeIteration) +TEST_F(TestCodegenModelViaMocks, DeviceTypeIteration) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; // Mock endpoint 1 has 3 device types - std::optional entry = model.FirstDeviceType(kMockEndpoint1); - ASSERT_EQ(entry, - std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId1, .deviceTypeRevision = kDeviceTypeId1Version })); - // NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none - entry = model.NextDeviceType(kMockEndpoint1, *entry); - ASSERT_EQ(entry, - std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeRevision = kDeviceTypeId2Version })); - // NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none - entry = model.NextDeviceType(kMockEndpoint1, *entry); - ASSERT_EQ(entry, - std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId3, .deviceTypeRevision = kDeviceTypeId3Version })); - // NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none - entry = model.NextDeviceType(kMockEndpoint1, *entry); - ASSERT_FALSE(entry.has_value()); + auto deviceTypes = model.DeviceTypes(kMockEndpoint1); + ASSERT_EQ(deviceTypes.Size(), 3u); + + const DeviceTypeEntry expected1[] = { + { .deviceTypeId = kDeviceTypeId1, .deviceTypeRevision = kDeviceTypeId1Version }, + { .deviceTypeId = kDeviceTypeId2, .deviceTypeRevision = kDeviceTypeId2Version }, + { .deviceTypeId = kDeviceTypeId3, .deviceTypeRevision = kDeviceTypeId3Version }, + }; + for (unsigned i = 0; i < 3; i++) + { + ASSERT_EQ(deviceTypes[i], expected1[i]); + } // Mock endpoint 2 has 1 device types - entry = model.FirstDeviceType(kMockEndpoint2); - ASSERT_EQ(entry, - std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeRevision = kDeviceTypeId2Version })); - // NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none - entry = model.NextDeviceType(kMockEndpoint2, *entry); - ASSERT_FALSE(entry.has_value()); - - // out of order query works - entry = std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeRevision = kDeviceTypeId2Version }); - entry = model.NextDeviceType(kMockEndpoint1, *entry); - ASSERT_EQ(entry, - std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId3, .deviceTypeRevision = kDeviceTypeId3Version })); - - // invalid query fails - entry = std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId1, .deviceTypeRevision = kDeviceTypeId1Version }); - entry = model.NextDeviceType(kMockEndpoint2, *entry); - ASSERT_FALSE(entry.has_value()); + deviceTypes = model.DeviceTypes(kMockEndpoint2); + ASSERT_EQ(deviceTypes.Size(), 1u); + const DeviceTypeEntry expected2 = { .deviceTypeId = kDeviceTypeId2, .deviceTypeRevision = kDeviceTypeId2Version }; + ASSERT_EQ(deviceTypes[0], expected2); // empty endpoint works - entry = model.FirstDeviceType(kMockEndpoint3); - ASSERT_FALSE(entry.has_value()); + ASSERT_TRUE(model.DeviceTypes(kMockEndpoint3).Empty()); } -TEST(TestCodegenModelViaMocks, SemanticTagIteration) +TEST_F(TestCodegenModelViaMocks, SemanticTagIteration) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; - // Mock endpoint 1 has 3 semantic tags - std::optional tag = model.GetFirstSemanticTag(kMockEndpoint1); - ASSERT_TRUE(tag.has_value()); - EXPECT_EQ(tag->mfgCode, MakeNullable(VendorId::TestVendor1)); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_EQ(tag->namespaceID, kNamespaceID1); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_EQ(tag->tag, kTag1); // NOLINT(bugprone-unchecked-optional-access) - ASSERT_TRUE(tag->label.HasValue() && (!tag->label.Value().IsNull())); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_TRUE( - tag->label.Value().Value().data_equal(CharSpan::fromCharString(kLabel1))); // NOLINT(bugprone-unchecked-optional-access) - tag = model.GetNextSemanticTag(kMockEndpoint1, *tag); // NOLINT(bugprone-unchecked-optional-access) - ASSERT_TRUE(tag.has_value()); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_TRUE(tag->mfgCode.IsNull()); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_EQ(tag->namespaceID, kNamespaceID2); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_EQ(tag->tag, kTag2); // NOLINT(bugprone-unchecked-optional-access) - ASSERT_TRUE(tag->label.HasValue() && (!tag->label.Value().IsNull())); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_TRUE( - tag->label.Value().Value().data_equal(CharSpan::fromCharString(kLabel2))); // NOLINT(bugprone-unchecked-optional-access) - tag = model.GetNextSemanticTag(kMockEndpoint1, *tag); // NOLINT(bugprone-unchecked-optional-access) - ASSERT_TRUE(tag.has_value()); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_EQ(tag->mfgCode, MakeNullable(VendorId::TestVendor3)); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_EQ(tag->namespaceID, kNamespaceID3); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_EQ(tag->tag, kTag3); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_FALSE(tag->label.HasValue()); // NOLINT(bugprone-unchecked-optional-access) - tag = model.GetNextSemanticTag(kMockEndpoint1, *tag); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_FALSE(tag.has_value()); - - // out of order query works - DataModel::Provider::SemanticTag existTag = { - .mfgCode = MakeNullable(VendorId::TestVendor1), - .namespaceID = kNamespaceID1, - .tag = kTag1, - .label = MakeOptional(MakeNullable(CharSpan::fromCharString(kLabel1))), - }; - tag = model.GetNextSemanticTag(kMockEndpoint1, existTag); - ASSERT_TRUE(tag.has_value()); - EXPECT_TRUE(tag->mfgCode.IsNull()); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_EQ(tag->namespaceID, kNamespaceID2); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_EQ(tag->tag, kTag2); // NOLINT(bugprone-unchecked-optional-access) - ASSERT_TRUE(tag->label.HasValue() && (!tag->label.Value().IsNull())); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_TRUE( - tag->label.Value().Value().data_equal(CharSpan::fromCharString(kLabel2))); // NOLINT(bugprone-unchecked-optional-access) - - // invalid query fails - existTag.tag = kTag2; - tag = model.GetNextSemanticTag(kMockEndpoint1, existTag); - ASSERT_FALSE(tag.has_value()); + ASSERT_TRUE(model.SemanticTags(kMockEndpoint2).Empty()); - // empty endpoint works - tag = model.GetFirstSemanticTag(kMockEndpoint2); - ASSERT_FALSE(tag.has_value()); + // Mock endpoint 1 has 3 semantic tags + MetadataList tags = model.SemanticTags(kMockEndpoint1); + ASSERT_EQ(tags.Size(), 3u); + + auto tag = tags[0]; + EXPECT_EQ(tag.mfgCode, MakeNullable(VendorId::TestVendor1)); + EXPECT_EQ(tag.namespaceID, kNamespaceID1); + EXPECT_EQ(tag.tag, kTag1); + ASSERT_TRUE(tag.label.HasValue() && (!tag.label.Value().IsNull())); + EXPECT_TRUE(tag.label.Value().Value().data_equal(CharSpan::fromCharString(kLabel1))); + + tag = tags[1]; + EXPECT_TRUE(tag.mfgCode.IsNull()); + EXPECT_EQ(tag.namespaceID, kNamespaceID2); + EXPECT_EQ(tag.tag, kTag2); + ASSERT_TRUE(tag.label.HasValue() && (!tag.label.Value().IsNull())); + EXPECT_TRUE(tag.label.Value().Value().data_equal(CharSpan::fromCharString(kLabel2))); + + tag = tags[2]; + EXPECT_EQ(tag.mfgCode, MakeNullable(VendorId::TestVendor3)); + EXPECT_EQ(tag.namespaceID, kNamespaceID3); + EXPECT_EQ(tag.tag, kTag3); + EXPECT_FALSE(tag.label.HasValue()); }