From 43f37d1f30851b3bea220e5d83f9b7d3a1f9f3d2 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 23 Jan 2025 15:40:42 -0500 Subject: [PATCH] Update metadata-tree from `MetadaList Foo(path)` to `CHIP_ERROR Foo(path, ListBuilder &)` (#37127) * Append-only API update: use CHIP_ERROR and builders for MetadataTree * Fix includes * Remove odd comment * ScopedSpan == ReadOnlyBuffer and Build == TakeBuffer * Update src/app/InteractionModelEngine.cpp Co-authored-by: Tennessee Carmel-Veilleux * Update src/app/clusters/descriptor/descriptor.cpp Co-authored-by: Tennessee Carmel-Veilleux * Update src/app/clusters/descriptor/descriptor.cpp Co-authored-by: Tennessee Carmel-Veilleux * Update src/app/clusters/descriptor/descriptor.cpp Co-authored-by: Tennessee Carmel-Veilleux * Update src/app/clusters/descriptor/descriptor.cpp Co-authored-by: Tennessee Carmel-Veilleux * Update src/app/clusters/descriptor/descriptor.cpp Co-authored-by: Tennessee Carmel-Veilleux * Update src/app/data-model-provider/MetadataList.cpp Co-authored-by: Tennessee Carmel-Veilleux * Replaced a lot of auto with const auto for readability * Update src/app/clusters/microwave-oven-control-server/microwave-oven-control-server.cpp Co-authored-by: Tennessee Carmel-Veilleux * Remove old comment * Fix some typos * Fix typo * Update src/app/data-model-provider/MetadataTypes.h Co-authored-by: Junior Martinez <67972863+jmartinez-silabs@users.noreply.github.com> --------- Co-authored-by: Tennessee Carmel-Veilleux Co-authored-by: Junior Martinez <67972863+jmartinez-silabs@users.noreply.github.com> Co-authored-by: Andrei Litvin --- src/access/ProviderDeviceTypeResolver.h | 7 +- src/app/AttributePathExpandIterator.cpp | 23 +- src/app/AttributePathExpandIterator.h | 6 +- src/app/InteractionModelEngine.cpp | 13 +- src/app/clusters/descriptor/descriptor.cpp | 80 +-- .../microwave-oven-control-server.cpp | 21 +- src/app/data-model-provider/BUILD.gn | 1 + src/app/data-model-provider/MetadataList.cpp | 185 ++++--- src/app/data-model-provider/MetadataList.h | 184 ++++--- .../data-model-provider/MetadataLookup.cpp | 22 +- src/app/data-model-provider/MetadataLookup.h | 15 +- src/app/data-model-provider/MetadataTypes.cpp | 51 ++ src/app/data-model-provider/MetadataTypes.h | 22 +- .../tests/TestMetadataList.cpp | 361 ++++--------- .../codegen/CodegenDataModelProvider.cpp | 486 ++++++------------ .../codegen/CodegenDataModelProvider.h | 18 +- .../tests/TestCodegenModelViaMocks.cpp | 183 +++++-- 17 files changed, 787 insertions(+), 891 deletions(-) create mode 100644 src/app/data-model-provider/MetadataTypes.cpp diff --git a/src/access/ProviderDeviceTypeResolver.h b/src/access/ProviderDeviceTypeResolver.h index 404c0cfa56c1af..aff5b424a693f1 100644 --- a/src/access/ProviderDeviceTypeResolver.h +++ b/src/access/ProviderDeviceTypeResolver.h @@ -16,6 +16,8 @@ #pragma once #include +#include +#include #include namespace chip { @@ -31,8 +33,9 @@ class DynamicProviderDeviceTypeResolver : public chip::Access::AccessControl::De bool IsDeviceTypeOnEndpoint(chip::DeviceTypeId deviceType, chip::EndpointId endpoint) override { - auto deviceTypes = mModelGetter()->DeviceTypes(endpoint); - for (auto & type : deviceTypes.GetSpanValidForLifetime()) + app::DataModel::ListBuilder builder; + (void) mModelGetter()->DeviceTypes(endpoint, builder); + for (auto & type : builder.TakeBuffer()) { if (type.deviceTypeId == deviceType) { diff --git a/src/app/AttributePathExpandIterator.cpp b/src/app/AttributePathExpandIterator.cpp index bd8e3e018b0155..7f357164427b44 100644 --- a/src/app/AttributePathExpandIterator.cpp +++ b/src/app/AttributePathExpandIterator.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -139,13 +140,13 @@ std::optional AttributePathExpandIterator::NextAttributeId() if (mAttributeIndex == kInvalidIndex) { // start a new iteration of attributes on the current cluster path. - mAttributes = mDataModelProvider->Attributes(mPosition.mOutputPath); + mAttributes = mDataModelProvider->AttributesIgnoreError(mPosition.mOutputPath); if (mPosition.mOutputPath.mAttributeId != kInvalidAttributeId) { // Position on the correct attribute if we have a start point mAttributeIndex = 0; - while ((mAttributeIndex < mAttributes.Size()) && + while ((mAttributeIndex < mAttributes.size()) && (mAttributes[mAttributeIndex].attributeId != mPosition.mOutputPath.mAttributeId)) { mAttributeIndex++; @@ -199,7 +200,7 @@ std::optional AttributePathExpandIterator::NextAttributeId() return std::nullopt; } - if (mAttributeIndex < mAttributes.Size()) + if (mAttributeIndex < mAttributes.size()) { return mAttributes[mAttributeIndex].attributeId; } @@ -222,13 +223,13 @@ std::optional AttributePathExpandIterator::NextClusterId() if (mClusterIndex == kInvalidIndex) { // start a new iteration on the current endpoint - mClusters = mDataModelProvider->ServerClusters(mPosition.mOutputPath.mEndpointId); + mClusters = mDataModelProvider->ServerClustersIgnoreError(mPosition.mOutputPath.mEndpointId); if (mPosition.mOutputPath.mClusterId != kInvalidClusterId) { // Position on the correct cluster if we have a start point mClusterIndex = 0; - while ((mClusterIndex < mClusters.Size()) && (mClusters[mClusterIndex].clusterId != mPosition.mOutputPath.mClusterId)) + while ((mClusterIndex < mClusters.size()) && (mClusters[mClusterIndex].clusterId != mPosition.mOutputPath.mClusterId)) { mClusterIndex++; } @@ -248,10 +249,8 @@ std::optional AttributePathExpandIterator::NextClusterId() { const ClusterId clusterId = mPosition.mAttributePath->mValue.mClusterId; - auto span = mClusters.GetSpanValidForLifetime(); - bool found = false; - for (auto & entry : span) + for (auto & entry : mClusters) { if (entry.clusterId == clusterId) { @@ -276,7 +275,7 @@ std::optional AttributePathExpandIterator::NextClusterId() } VerifyOrReturnValue(mPosition.mAttributePath->mValue.HasWildcardClusterId(), std::nullopt); - VerifyOrReturnValue(mClusterIndex < mClusters.Size(), std::nullopt); + VerifyOrReturnValue(mClusterIndex < mClusters.size(), std::nullopt); return mClusters[mClusterIndex].clusterId; } @@ -286,13 +285,13 @@ std::optional AttributePathExpandIterator::NextEndpointId() if (mEndpointIndex == kInvalidIndex) { // index is missing, have to start a new iteration - mEndpoints = mDataModelProvider->Endpoints(); + mEndpoints = mDataModelProvider->EndpointsIgnoreError(); 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)) + while ((mEndpointIndex < mEndpoints.size()) && (mEndpoints[mEndpointIndex].id != mPosition.mOutputPath.mEndpointId)) { mEndpointIndex++; } @@ -315,7 +314,7 @@ std::optional AttributePathExpandIterator::NextEndpointId() } VerifyOrReturnValue(mPosition.mAttributePath->mValue.HasWildcardEndpointId(), std::nullopt); - VerifyOrReturnValue(mEndpointIndex < mEndpoints.Size(), std::nullopt); + VerifyOrReturnValue(mEndpointIndex < mEndpoints.size(), std::nullopt); return mEndpoints[mEndpointIndex].id; } diff --git a/src/app/AttributePathExpandIterator.h b/src/app/AttributePathExpandIterator.h index 75993d80ae5297..e5d1f961434eb2 100644 --- a/src/app/AttributePathExpandIterator.h +++ b/src/app/AttributePathExpandIterator.h @@ -121,13 +121,13 @@ class AttributePathExpandIterator DataModel::Provider * mDataModelProvider; Position & mPosition; - DataModel::MetadataList mEndpoints; // all endpoints + DataModel::ReadOnlyBuffer mEndpoints; // all endpoints size_t mEndpointIndex = kInvalidIndex; - DataModel::MetadataList mClusters; // all clusters ON THE CURRENT endpoint + DataModel::ReadOnlyBuffer mClusters; // all clusters ON THE CURRENT endpoint size_t mClusterIndex = kInvalidIndex; - DataModel::MetadataList mAttributes; // all attributes ON THE CURRENT cluster + DataModel::ReadOnlyBuffer mAttributes; // all attributes ON THE CURRENT cluster size_t mAttributeIndex = kInvalidIndex; /// Move to the next endpoint/cluster/attribute triplet that is valid given diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index 325c0dd416ec80..ec999d3cc53a31 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -37,9 +37,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -91,8 +93,7 @@ bool MayHaveAccessibleEventPathForEndpoint(DataModel::Provider * aProvider, Endp aSubjectDescriptor); } - auto serverClusters = aProvider->ServerClusters(aEventPath.mEndpointId); - for (auto & cluster : serverClusters.GetSpanValidForLifetime()) + for (auto & cluster : aProvider->ServerClustersIgnoreError(aEventPath.mEndpointId)) { if (MayHaveAccessibleEventPathForEndpointAndCluster(ConcreteClusterPath(aEventPath.mEndpointId, cluster.clusterId), aEventPath, aSubjectDescriptor)) @@ -114,8 +115,7 @@ bool MayHaveAccessibleEventPath(DataModel::Provider * aProvider, const EventPath return MayHaveAccessibleEventPathForEndpoint(aProvider, aEventPath.mEndpointId, aEventPath, subjectDescriptor); } - auto endpoints = aProvider->Endpoints(); - for (const DataModel::EndpointEntry & ep : endpoints.GetSpanValidForLifetime()) + for (const DataModel::EndpointEntry & ep : aProvider->EndpointsIgnoreError()) { if (MayHaveAccessibleEventPathForEndpoint(aProvider, ep.id, aEventPath, subjectDescriptor)) { @@ -1790,8 +1790,9 @@ Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandExistenc { auto provider = GetDataModelProvider(); - DataModel::MetadataList acceptedCommands = provider->AcceptedCommands(aCommandPath); - for (auto & existing : acceptedCommands.GetSpanValidForLifetime()) + DataModel::ListBuilder acceptedCommands; + (void) provider->AcceptedCommands(aCommandPath, acceptedCommands); + for (auto & existing : acceptedCommands.TakeBuffer()) { if (existing.commandId == aCommandPath.mCommandId) { diff --git a/src/app/clusters/descriptor/descriptor.cpp b/src/app/clusters/descriptor/descriptor.cpp index ecc93d4a77267a..35ddcabfd428d2 100644 --- a/src/app/clusters/descriptor/descriptor.cpp +++ b/src/app/clusters/descriptor/descriptor.cpp @@ -23,7 +23,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -60,7 +62,7 @@ bool IsDescendantOf(const DataModel::EndpointEntry * __restrict__ childEndpoint, childEndpoint = nullptr; // we will look it up again // find the requested value in the array to get its parent - for (auto & ep : allEndpoints) + for (const auto & ep : allEndpoints) { if (ep.id == lookupId) { @@ -83,7 +85,8 @@ class DescriptorAttrAccess : public AttributeAccessInterface CHIP_ERROR ReadTagListAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder); CHIP_ERROR ReadPartsAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder); CHIP_ERROR ReadDeviceAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder); - CHIP_ERROR ReadClientServerAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder, bool server); + CHIP_ERROR ReadClientClusters(EndpointId endpoint, AttributeValueEncoder & aEncoder); + CHIP_ERROR ReadServerClusters(EndpointId endpoint, AttributeValueEncoder & aEncoder); CHIP_ERROR ReadClusterRevision(EndpointId endpoint, AttributeValueEncoder & aEncoder); CHIP_ERROR ReadFeatureMap(EndpointId endpoint, AttributeValueEncoder & aEncoder); }; @@ -103,9 +106,11 @@ 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 tags = InteractionModelEngine::GetInstance()->GetDataModelProvider()->SemanticTags(endpoint); - for (auto & tag : tags.GetSpanValidForLifetime()) + DataModel::ListBuilder semanticTagsList; + ReturnErrorOnFailure(InteractionModelEngine::GetInstance()->GetDataModelProvider()->SemanticTags(endpoint, semanticTagsList)); + + return aEncoder.EncodeList([&semanticTagsList](const auto & encoder) -> CHIP_ERROR { + for (const auto & tag : semanticTagsList.TakeBuffer()) { ReturnErrorOnFailure(encoder.Encode(tag)); } @@ -115,11 +120,13 @@ CHIP_ERROR DescriptorAttrAccess::ReadTagListAttribute(EndpointId endpoint, Attri CHIP_ERROR DescriptorAttrAccess::ReadPartsAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) { - auto endpoints = InteractionModelEngine::GetInstance()->GetDataModelProvider()->Endpoints(); + DataModel::ListBuilder endpointsList; + ReturnErrorOnFailure(InteractionModelEngine::GetInstance()->GetDataModelProvider()->Endpoints(endpointsList)); + auto endpoints = endpointsList.TakeBuffer(); if (endpoint == 0x00) { return aEncoder.EncodeList([&endpoints](const auto & encoder) -> CHIP_ERROR { - for (auto & ep : endpoints.GetSpanValidForLifetime()) + for (const auto & ep : endpoints) { if (ep.id == 0) { @@ -133,7 +140,7 @@ CHIP_ERROR DescriptorAttrAccess::ReadPartsAttribute(EndpointId endpoint, Attribu // find the given endpoint unsigned idx = 0; - while (idx < endpoints.Size()) + while (idx < endpoints.size()) { if (endpoints[idx].id == endpoint) { @@ -141,7 +148,7 @@ CHIP_ERROR DescriptorAttrAccess::ReadPartsAttribute(EndpointId endpoint, Attribu } idx++; } - if (idx >= endpoints.Size()) + if (idx >= endpoints.size()) { // not found return CHIP_ERROR_NOT_FOUND; @@ -154,9 +161,9 @@ CHIP_ERROR DescriptorAttrAccess::ReadPartsAttribute(EndpointId endpoint, Attribu 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()) + for (const auto & ep : endpoints) { - if (IsDescendantOf(&ep, endpoint, endpoints.GetSpanValidForLifetime())) + if (IsDescendantOf(&ep, endpoint, endpoints)) { ReturnErrorOnFailure(encoder.Encode(ep.id)); } @@ -166,7 +173,7 @@ CHIP_ERROR DescriptorAttrAccess::ReadPartsAttribute(EndpointId endpoint, Attribu case DataModel::EndpointCompositionPattern::kTree: return aEncoder.EncodeList([&endpoints, endpoint](const auto & encoder) -> CHIP_ERROR { - for (auto & ep : endpoints.GetSpanValidForLifetime()) + for (const auto & ep : endpoints) { if (ep.parentId != endpoint) { @@ -184,11 +191,14 @@ CHIP_ERROR DescriptorAttrAccess::ReadPartsAttribute(EndpointId endpoint, Attribu CHIP_ERROR DescriptorAttrAccess::ReadDeviceAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder) { - CHIP_ERROR err = aEncoder.EncodeList([&endpoint](const auto & encoder) -> CHIP_ERROR { - Descriptor::Structs::DeviceTypeStruct::Type deviceStruct; + DataModel::ListBuilder deviceTypesList; + ReturnErrorOnFailure(InteractionModelEngine::GetInstance()->GetDataModelProvider()->DeviceTypes(endpoint, deviceTypesList)); - auto deviceTypes = InteractionModelEngine::GetInstance()->GetDataModelProvider()->DeviceTypes(endpoint); - for (auto & type : deviceTypes.GetSpanValidForLifetime()) + auto deviceTypes = deviceTypesList.TakeBuffer(); + + CHIP_ERROR err = aEncoder.EncodeList([&deviceTypes](const auto & encoder) -> CHIP_ERROR { + Descriptor::Structs::DeviceTypeStruct::Type deviceStruct; + for (const auto & type : deviceTypes) { deviceStruct.deviceType = type.deviceTypeId; deviceStruct.revision = type.deviceTypeRevision; @@ -201,30 +211,30 @@ CHIP_ERROR DescriptorAttrAccess::ReadDeviceAttribute(EndpointId endpoint, Attrib return err; } -CHIP_ERROR DescriptorAttrAccess::ReadClientServerAttribute(EndpointId endpoint, AttributeValueEncoder & aEncoder, bool server) +CHIP_ERROR DescriptorAttrAccess::ReadServerClusters(EndpointId endpoint, AttributeValueEncoder & aEncoder) { - CHIP_ERROR err = aEncoder.EncodeList([&endpoint, server](const auto & encoder) -> CHIP_ERROR { - if (server) + DataModel::ListBuilder builder; + ReturnErrorOnFailure(InteractionModelEngine::GetInstance()->GetDataModelProvider()->ServerClusters(endpoint, builder)); + return aEncoder.EncodeList([&builder](const auto & encoder) -> CHIP_ERROR { + for (const auto & cluster : builder.TakeBuffer()) { - auto clusters = InteractionModelEngine::GetInstance()->GetDataModelProvider()->ServerClusters(endpoint); - for (auto & cluster : clusters.GetSpanValidForLifetime()) - { - ReturnErrorOnFailure(encoder.Encode(cluster.clusterId)); - } + ReturnErrorOnFailure(encoder.Encode(cluster.clusterId)); } - else + return CHIP_NO_ERROR; + }); +} + +CHIP_ERROR DescriptorAttrAccess::ReadClientClusters(EndpointId endpoint, AttributeValueEncoder & aEncoder) +{ + DataModel::ListBuilder clusterIdList; + ReturnErrorOnFailure(InteractionModelEngine::GetInstance()->GetDataModelProvider()->ClientClusters(endpoint, clusterIdList)); + return aEncoder.EncodeList([&clusterIdList](const auto & encoder) -> CHIP_ERROR { + for (const auto & id : clusterIdList.TakeBuffer()) { - auto clusters = InteractionModelEngine::GetInstance()->GetDataModelProvider()->ClientClusters(endpoint); - for (auto & id : clusters.GetSpanValidForLifetime()) - { - ReturnErrorOnFailure(encoder.Encode(id)); - } + ReturnErrorOnFailure(encoder.Encode(id)); } - return CHIP_NO_ERROR; }); - - return err; } CHIP_ERROR DescriptorAttrAccess::ReadClusterRevision(EndpointId endpoint, AttributeValueEncoder & aEncoder) @@ -244,10 +254,10 @@ CHIP_ERROR DescriptorAttrAccess::Read(const ConcreteReadAttributePath & aPath, A return ReadDeviceAttribute(aPath.mEndpointId, aEncoder); } case ServerList::Id: { - return ReadClientServerAttribute(aPath.mEndpointId, aEncoder, true); + return ReadServerClusters(aPath.mEndpointId, aEncoder); } case ClientList::Id: { - return ReadClientServerAttribute(aPath.mEndpointId, aEncoder, false); + return ReadClientClusters(aPath.mEndpointId, aEncoder); } case PartsList::Id: { return ReadPartsAttribute(aPath.mEndpointId, aEncoder); 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 bdb614789f4b9b..5d823d0cafe3cb 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 @@ -16,6 +16,7 @@ * */ +#include "app/data-model-provider/MetadataList.h" #include #include #include @@ -249,15 +250,17 @@ void Instance::HandleSetCookingParameters(HandlerContext & ctx, const Commands:: if (startAfterSetting.HasValue()) { - 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(); + + DataModel::ListBuilder acceptedCommandsList; + + InteractionModelEngine::GetInstance()->GetDataModelProvider()->AcceptedCommands( + ConcreteClusterPath(mEndpointId, OperationalState::Id), acceptedCommandsList); + auto acceptedCommands = acceptedCommandsList.TakeBuffer(); + + bool commandExists = + std::find_if(acceptedCommands.begin(), acceptedCommands.end(), [](const DataModel::AcceptedCommandEntry & entry) { + return entry.commandId == OperationalState::Commands::Start::Id; + }) != acceptedCommands.end(); VerifyOrExit( commandExists, status = Status::InvalidCommand; ChipLogError( diff --git a/src/app/data-model-provider/BUILD.gn b/src/app/data-model-provider/BUILD.gn index a80315a1f89c85..5804b0acf59cf5 100644 --- a/src/app/data-model-provider/BUILD.gn +++ b/src/app/data-model-provider/BUILD.gn @@ -25,6 +25,7 @@ source_set("data-model-provider") { "MetadataList.h", "MetadataLookup.cpp", "MetadataLookup.h", + "MetadataTypes.cpp", "MetadataTypes.h", "OperationTypes.h", "Provider.h", diff --git a/src/app/data-model-provider/MetadataList.cpp b/src/app/data-model-provider/MetadataList.cpp index c1d5479f57a5a7..564ccdf53f91cd 100644 --- a/src/app/data-model-provider/MetadataList.cpp +++ b/src/app/data-model-provider/MetadataList.cpp @@ -25,106 +25,157 @@ namespace app { namespace DataModel { namespace detail { -GenericMetadataList::~GenericMetadataList() +GenericAppendOnlyBuffer::~GenericAppendOnlyBuffer() { - Invalidate(); -} - -GenericMetadataList & GenericMetadataList::operator=(GenericMetadataList && other) -{ - if (this != &other) + if (mBufferIsAllocated && (mBuffer != nullptr)) { - // 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; + Platform::MemoryFree(mBuffer); } - return *this; } -const void * GenericMetadataList::operator[](size_t index) const +GenericAppendOnlyBuffer::GenericAppendOnlyBuffer(GenericAppendOnlyBuffer && other) : mElementSize(other.mElementSize) { - VerifyOrDie(index < mElementCount); - return mBuffer + index * mElementSize; + // take over the data + mBuffer = other.mBuffer; + mElementCount = other.mElementCount; + mCapacity = other.mCapacity; + mBufferIsAllocated = other.mBufferIsAllocated; + + // clear other + other.mBuffer = nullptr; + other.mElementCount = 0; + other.mCapacity = 0; + other.mBufferIsAllocated = false; } -CHIP_ERROR GenericMetadataList::Reserve(size_t numElements) +GenericAppendOnlyBuffer & GenericAppendOnlyBuffer::operator=(GenericAppendOnlyBuffer && other) { - VerifyOrReturnError(!mIsImmutable, CHIP_ERROR_INCORRECT_STATE); - VerifyOrReturnError(mElementCount == 0, CHIP_ERROR_INCORRECT_STATE); + VerifyOrDie(mElementSize == other.mElementSize); + + if (mBufferIsAllocated && (mBuffer != nullptr)) + { + Platform::Impl::PlatformMemoryManagement::MemoryFree(mBuffer); + } + + // take over the data + mBuffer = other.mBuffer; + mElementCount = other.mElementCount; + mCapacity = other.mCapacity; + mBufferIsAllocated = other.mBufferIsAllocated; + + // clear other + other.mBuffer = nullptr; + other.mElementCount = 0; + other.mCapacity = 0; + other.mBufferIsAllocated = false; - if ((mBuffer != nullptr) && mAllocated) + return *this; +} + +CHIP_ERROR GenericAppendOnlyBuffer::EnsureAppendCapacity(size_t numElements) +{ + if (mCapacity >= mElementCount + numElements) { - chip::Platform::MemoryFree(mBuffer); + // Sufficient capacity already exists + return CHIP_NO_ERROR; } - mBuffer = static_cast(chip::Platform::MemoryCalloc(numElements, mElementSize)); + if (mBuffer == nullptr) + { + mBuffer = static_cast(Platform::MemoryCalloc(numElements, mElementSize)); + mCapacity = numElements; + mBufferIsAllocated = true; + return mBuffer != nullptr ? CHIP_NO_ERROR : CHIP_ERROR_NO_MEMORY; + } - VerifyOrReturnError(mBuffer != nullptr, CHIP_ERROR_NO_MEMORY); + // we already have the data in buffer. we have two choices: + // - allocated buffer needs to be extended + // - re-used const buffer needs to be copied over + if (mBufferIsAllocated) + { + auto new_buffer = static_cast(Platform::MemoryRealloc(mBuffer, (mElementCount + numElements) * mElementSize)); + VerifyOrReturnError(new_buffer != nullptr, CHIP_ERROR_NO_MEMORY); + mBuffer = new_buffer; + } + else + { + // this is NOT an allocated buffer, but it should become one + auto new_buffer = static_cast(Platform::MemoryCalloc(mElementCount + numElements, mElementSize)); + mBufferIsAllocated = true; + VerifyOrReturnError(new_buffer != nullptr, CHIP_ERROR_NO_MEMORY); + memcpy(new_buffer, mBuffer, mElementCount * mElementSize); + mBuffer = new_buffer; + } + mCapacity = mElementCount + numElements; - mAllocated = true; - mCapacity = numElements; return CHIP_NO_ERROR; } -CHIP_ERROR GenericMetadataList::AppendRaw(const void * buffer) +CHIP_ERROR GenericAppendOnlyBuffer::AppendSingleElementRaw(const void * buffer) { - VerifyOrReturnError(!mIsImmutable, CHIP_ERROR_INCORRECT_STATE); - VerifyOrReturnError(mElementCount < mCapacity, CHIP_ERROR_NO_MEMORY); + VerifyOrReturnError(mElementCount < mCapacity, CHIP_ERROR_BUFFER_TOO_SMALL); memcpy(mBuffer + mElementCount * mElementSize, buffer, mElementSize); mElementCount++; return CHIP_NO_ERROR; } -void GenericMetadataList::Invalidate() +CHIP_ERROR GenericAppendOnlyBuffer::AppendElementArrayRaw(const void * buffer, size_t numElements) { - if ((mBuffer != nullptr) && mAllocated) + ReturnErrorOnFailure(EnsureAppendCapacity(numElements)); + + memcpy(mBuffer + mElementCount * mElementSize, buffer, numElements * mElementSize); + mElementCount += numElements; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR GenericAppendOnlyBuffer::ReferenceExistingElementArrayRaw(const void * buffer, size_t numElements) +{ + if (mBuffer == nullptr) { - chip::Platform::MemoryFree(mBuffer); - mBuffer = nullptr; - mAllocated = false; + // we can NEVER append with 0 capacity, so const cast is safe + mBuffer = const_cast(static_cast(buffer)); + mElementCount = numElements; + // The assertions below are because we know the buffer is null/not allocated yet + VerifyOrDie(mCapacity == 0); + VerifyOrDie(!mBufferIsAllocated); + return CHIP_NO_ERROR; } - mCapacity = 0; - mElementCount = 0; - mCapacity = 0; - mIsImmutable = true; + + return AppendElementArrayRaw(buffer, numElements); } -CHIP_ERROR GenericMetadataList::CopyExistingBuffer(const void * buffer, size_t elements) +void GenericAppendOnlyBuffer::ReleaseBuffer(void *& buffer, size_t & size, bool & allocated) { - ReturnErrorOnFailure(Reserve(elements)); - memcpy(mBuffer, buffer, mElementSize * elements); - mIsImmutable = true; - mElementCount = elements; - mCapacity = elements; - return CHIP_NO_ERROR; + buffer = mBuffer; + size = mElementCount; + allocated = mBufferIsAllocated; + + // we release the ownership + mBuffer = nullptr; + mCapacity = 0; + mElementCount = 0; + mBufferIsAllocated = false; } -void GenericMetadataList::AcquireExistingBuffer(const void * buffer, size_t elements) +ScopedBuffer::~ScopedBuffer() { - 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)); + if (mBuffer != nullptr) + { + Platform::MemoryFree(mBuffer); + } +} + +ScopedBuffer & ScopedBuffer::operator=(ScopedBuffer && other) +{ + if (mBuffer != nullptr) + { + Platform::MemoryFree(mBuffer); + } + + mBuffer = other.mBuffer; + other.mBuffer = nullptr; + return *this; } } // namespace detail diff --git a/src/app/data-model-provider/MetadataList.h b/src/app/data-model-provider/MetadataList.h index 37432363712939..5086a430749b93 100644 --- a/src/app/data-model-provider/MetadataList.h +++ b/src/app/data-model-provider/MetadataList.h @@ -18,10 +18,12 @@ #pragma once #include +#include #include #include #include +#include #include namespace chip { @@ -30,61 +32,106 @@ namespace DataModel { namespace detail { -// essentially a `void *` untyped metadata list base, -// so that actual functionality does not template-explode -class GenericMetadataList +class GenericAppendOnlyBuffer { public: - GenericMetadataList(size_t elementSize) : mElementSize(elementSize) {} - ~GenericMetadataList(); + GenericAppendOnlyBuffer(size_t elementSize) : mElementSize(elementSize) {} + ~GenericAppendOnlyBuffer(); - GenericMetadataList(const GenericMetadataList &) = delete; - GenericMetadataList & operator=(const GenericMetadataList & other) = delete; + GenericAppendOnlyBuffer(GenericAppendOnlyBuffer && other); + GenericAppendOnlyBuffer & operator=(GenericAppendOnlyBuffer &&); - GenericMetadataList(GenericMetadataList && other) { *this = std::move(other); } + GenericAppendOnlyBuffer() = delete; + GenericAppendOnlyBuffer & operator=(const GenericAppendOnlyBuffer &) = delete; - GenericMetadataList & operator=(GenericMetadataList && other); - const void * operator[](size_t index) const; + /// Ensure that at least the specified number of elements + /// can be appended to the internal buffer; + /// + /// This will cause the internal buffer to become and allocated buffer + CHIP_ERROR EnsureAppendCapacity(size_t numElements); - CHIP_ERROR Reserve(size_t numElements); - void Invalidate(); + bool IsEmpty() const { return mElementCount == 0; } - size_t ElementCount() const { return mElementCount; } - size_t Capacity() const { return mCapacity; } - bool Empty() const { return mElementCount == 0; } + /// Number of elements stored in the object. + size_t Size() const { return mElementCount; } protected: - /// Copy over the data from the given buffer - CHIP_ERROR CopyExistingBuffer(const void * buffer, size_t elements); + /// Appends a single element of mElementSize size. + /// + /// ALWAYS COPIES the given element internally. + /// Sufficient capacity MUST exist to append. + CHIP_ERROR AppendSingleElementRaw(const void * buffer); + + /// Appends a list of elements from a raw array. + /// + /// This ALWAYS COPIES the elements internally. + /// Additional capacity is AUTOMATICALLY ADDED. + CHIP_ERROR AppendElementArrayRaw(const void * buffer, size_t numElements); + + /// Appends a list of elements from a raw array. + /// + /// If the buffer contains no elements, this will just REFERENCE the given + /// buffer, so its lifetime MUST be longer than the lifetime of this buffer and + /// its usage. + /// + /// If the buffer already contains some elements, this will AUTOMATICALLY + /// add additional capacity and COPY the elements at the end of the internal array. + CHIP_ERROR ReferenceExistingElementArrayRaw(const void * buffer, size_t numElements); + + /// release ownership of any used buffer. + /// + /// Returns the current buffer details and releases ownership of it (clears internal state) + void ReleaseBuffer(void *& buffer, size_t & size, bool & allocated); - /// 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; } +private: + const size_t mElementSize; // size of one element in the buffer + uint8_t * mBuffer = nullptr; + size_t mElementCount = 0; // how many elements are stored in the class + size_t mCapacity = 0; // how many elements can be stored in total in mBuffer + bool mBufferIsAllocated = false; // if mBuffer is an allocated buffer +}; - /// Marks a list as immutable and immutability is acquired - /// during const access (so this is const) - void SetImmutable() const { mIsImmutable = true; } +/// Represents a RAII instance owning a buffer. +/// +/// It auto-frees the owned buffer on destruction +class ScopedBuffer +{ +public: + ScopedBuffer(void * buffer) : mBuffer(buffer) {} + ~ScopedBuffer(); -private: - bool mAllocated = false; + ScopedBuffer(const ScopedBuffer &) = delete; + ScopedBuffer & operator=(const ScopedBuffer &) = delete; - // 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; + ScopedBuffer(ScopedBuffer && other) : mBuffer(other.mBuffer) { other.mBuffer = nullptr; } + ScopedBuffer & operator=(ScopedBuffer && other); - // Set to true as soon as a span is obtained, since more writes may invalidate the span. - mutable bool mIsImmutable = false; +private: + void * mBuffer; }; } // namespace detail template -class MetadataList : public detail::GenericMetadataList +class ReadOnlyBuffer : public Span, detail::ScopedBuffer +{ +public: + ReadOnlyBuffer() : ScopedBuffer(nullptr) {} + ReadOnlyBuffer(const T * buffer, size_t size, bool allocated) : + Span(buffer, size), ScopedBuffer(allocated ? const_cast(static_cast(buffer)) : nullptr) + {} + ~ReadOnlyBuffer() = default; + + ReadOnlyBuffer & operator=(ReadOnlyBuffer && other) + { + *static_cast *>(this) = other; + *static_cast(this) = std::move(other); + return *this; + } +}; + +template +class ListBuilder : public detail::GenericAppendOnlyBuffer { public: using SpanType = Span; @@ -97,57 +144,44 @@ class MetadataList : public detail::GenericMetadataList // 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; + ListBuilder() : GenericAppendOnlyBuffer(sizeof(T)) {} - MetadataList(MetadataList && other) : GenericMetadataList(sizeof(T)) { *this = std::move(other); } + ListBuilder(const ListBuilder &) = delete; + ListBuilder & operator=(const ListBuilder & other) = delete; - MetadataList & operator=(MetadataList && other) + ListBuilder(ListBuilder && other) : GenericAppendOnlyBuffer(sizeof(T)) { *this = std::move(other); } + + ListBuilder & operator=(ListBuilder && other) { - *static_cast(this) = std::move(other); + *static_cast(this) = std::move(other); return *this; } - const T & operator[](size_t index) { return *static_cast(GenericMetadataList::operator[](index)); } + /// Reference methods attempt to reference the existing array IN PLACE + /// so its lifetime is assumed to be longer than the usage of this list. + CHIP_ERROR ReferenceExisting(SpanType span) { return ReferenceExistingElementArrayRaw(span.data(), span.size()); } - template - static MetadataList FromArray(const std::array & arr) - { - MetadataList list; + /// Append always attempts to append/extend existing memory. + /// + /// Automatically attempts to allocate sufficient space to fulfill the element + /// requirements. + CHIP_ERROR AppendElements(SpanType span) { return AppendElementArrayRaw(span.data(), span.size()); } - if (list.CopyExistingBuffer(arr.data(), arr.size()) != CHIP_NO_ERROR) - { - list.Invalidate(); - } + /// Append a single element. + /// Sufficent append capacity MUST exist. + CHIP_ERROR Append(const T & value) { return AppendSingleElementRaw(&value); } - return list; - } - - template - static MetadataList FromConstArray(const std::array & arr) + /// Once a list is built, the data is taken as a scoped SPAN that owns its data + /// and the original list is cleared + ReadOnlyBuffer TakeBuffer() { - MetadataList list; - list.AcquireExistingBuffer(arr.data(), arr.size()); - return list; - } + void * buffer; + size_t size; + bool allocated; + ReleaseBuffer(buffer, size, allocated); - 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()); + return ReadOnlyBuffer(static_cast(buffer), size, allocated); } - - CHIP_ERROR Append(const T & value) { return AppendRaw(&value); } }; } // namespace DataModel diff --git a/src/app/data-model-provider/MetadataLookup.cpp b/src/app/data-model-provider/MetadataLookup.cpp index be94807b6c287f..5ec6b451ed264d 100644 --- a/src/app/data-model-provider/MetadataLookup.cpp +++ b/src/app/data-model-provider/MetadataLookup.cpp @@ -15,6 +15,8 @@ */ #include +#include + namespace chip { namespace app { namespace DataModel { @@ -26,12 +28,10 @@ std::optional ServerClusterFinder::Find(const ConcreteCluste if (mEndpointId != path.mEndpointId) { mEndpointId = path.mEndpointId; - mClusterEntries = mProvider->ServerClusters(path.mEndpointId); + mClusterEntries = mProvider->ServerClustersIgnoreError(path.mEndpointId); } - auto serverClustersSpan = mClusterEntries.GetSpanValidForLifetime(); - - for (auto & clusterEntry : serverClustersSpan) + for (auto & clusterEntry : mClusterEntries) { if (clusterEntry.clusterId == path.mClusterId) { @@ -49,11 +49,10 @@ std::optional AttributeFinder::Find(const ConcreteAttributePath if (mClusterPath != path) { mClusterPath = path; - mAttributes = mProvider->Attributes(path); + mAttributes = mProvider->AttributesIgnoreError(path); } - auto attributesSpan = mAttributes.GetSpanValidForLifetime(); - for (auto & attributeEntry : attributesSpan) + for (auto & attributeEntry : mAttributes) { if (attributeEntry.attributeId == path.mAttributeId) { @@ -64,10 +63,15 @@ std::optional AttributeFinder::Find(const ConcreteAttributePath return std::nullopt; } +EndpointFinder::EndpointFinder(ProviderMetadataTree * provider) : mProvider(provider) +{ + VerifyOrReturn(mProvider != nullptr); + mEndpoints = mProvider->EndpointsIgnoreError(); +} + std::optional EndpointFinder::Find(EndpointId endpointId) { - auto endpointsSpan = mEndpoints.GetSpanValidForLifetime(); - for (auto & endpointEntry : endpointsSpan) + for (auto & endpointEntry : mEndpoints) { if (endpointEntry.id == endpointId) { diff --git a/src/app/data-model-provider/MetadataLookup.h b/src/app/data-model-provider/MetadataLookup.h index 573b46d30c2ddc..9c2978d71a5ae6 100644 --- a/src/app/data-model-provider/MetadataLookup.h +++ b/src/app/data-model-provider/MetadataLookup.h @@ -44,7 +44,7 @@ class ServerClusterFinder private: ProviderMetadataTree * mProvider; EndpointId mEndpointId = kInvalidEndpointId; - MetadataList mClusterEntries; + ReadOnlyBuffer mClusterEntries; }; /// Helps search for a specific server attribute in the given @@ -61,7 +61,7 @@ class AttributeFinder private: ProviderMetadataTree * mProvider; ConcreteClusterPath mClusterPath; - MetadataList mAttributes; + ReadOnlyBuffer mAttributes; }; /// Helps search for a specific server endpoint in the given @@ -71,19 +71,12 @@ class AttributeFinder class EndpointFinder { public: - EndpointFinder(ProviderMetadataTree * provider) : mProvider(provider) - { - if (mProvider != nullptr) - { - mEndpoints = mProvider->Endpoints(); - } - } - + EndpointFinder(ProviderMetadataTree * provider); std::optional Find(EndpointId endpointId); private: ProviderMetadataTree * mProvider; - MetadataList mEndpoints; + ReadOnlyBuffer mEndpoints; }; } // namespace DataModel diff --git a/src/app/data-model-provider/MetadataTypes.cpp b/src/app/data-model-provider/MetadataTypes.cpp new file mode 100644 index 00000000000000..6d87d9b5ea54ac --- /dev/null +++ b/src/app/data-model-provider/MetadataTypes.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025 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 + +namespace chip { +namespace app { +namespace DataModel { + +ReadOnlyBuffer ProviderMetadataTree::EndpointsIgnoreError() +{ + + ListBuilder builder; + (void) Endpoints(builder); + return builder.TakeBuffer(); +} + +ReadOnlyBuffer ProviderMetadataTree::ServerClustersIgnoreError(EndpointId endpointId) +{ + + ListBuilder builder; + (void) ServerClusters(endpointId, builder); + return builder.TakeBuffer(); +} + +ReadOnlyBuffer ProviderMetadataTree::AttributesIgnoreError(const ConcreteClusterPath & path) +{ + ListBuilder builder; + (void) Attributes(path, builder); + return builder.TakeBuffer(); +} + +} // 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 8a8332c40f2d78..5b6bccb863e660 100644 --- a/src/app/data-model-provider/MetadataTypes.h +++ b/src/app/data-model-provider/MetadataTypes.h @@ -158,16 +158,16 @@ class ProviderMetadataTree using SemanticTag = Clusters::Descriptor::Structs::SemanticTagStruct::Type; - virtual MetadataList Endpoints() = 0; + virtual CHIP_ERROR Endpoints(ListBuilder & builder) = 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; + virtual CHIP_ERROR SemanticTags(EndpointId endpointId, ListBuilder & builder) = 0; + virtual CHIP_ERROR DeviceTypes(EndpointId endpointId, ListBuilder & builder) = 0; + virtual CHIP_ERROR ClientClusters(EndpointId endpointId, ListBuilder & builder) = 0; + virtual CHIP_ERROR ServerClusters(EndpointId endpointId, ListBuilder & builder) = 0; - virtual MetadataList Attributes(const ConcreteClusterPath & path) = 0; - virtual MetadataList GeneratedCommands(const ConcreteClusterPath & path) = 0; - virtual MetadataList AcceptedCommands(const ConcreteClusterPath & path) = 0; + virtual CHIP_ERROR Attributes(const ConcreteClusterPath & path, ListBuilder & builder) = 0; + virtual CHIP_ERROR GeneratedCommands(const ConcreteClusterPath & path, ListBuilder & builder) = 0; + virtual CHIP_ERROR AcceptedCommands(const ConcreteClusterPath & path, ListBuilder & builder) = 0; /// Workaround function to report attribute change. /// @@ -186,6 +186,12 @@ class ProviderMetadataTree /// TODO: We should remove this function when the AttributeAccessInterface/CommandHandlerInterface is able to report /// the attribute changes. virtual void Temporary_ReportAttributeChanged(const AttributePathParams & path) = 0; + + // "convenience" functions that just return the data and ignore the error + // This returns the builder as-is even after the error (e.g. not found would return empty data) + ReadOnlyBuffer EndpointsIgnoreError(); + ReadOnlyBuffer ServerClustersIgnoreError(EndpointId endpointId); + ReadOnlyBuffer AttributesIgnoreError(const ConcreteClusterPath & path); }; } // namespace DataModel diff --git a/src/app/data-model-provider/tests/TestMetadataList.cpp b/src/app/data-model-provider/tests/TestMetadataList.cpp index 663dfae906303d..f9fd7ab62be208 100644 --- a/src/app/data-model-provider/tests/TestMetadataList.cpp +++ b/src/app/data-model-provider/tests/TestMetadataList.cpp @@ -16,8 +16,8 @@ * limitations under the License. */ +#include #include -#include #include #include @@ -54,21 +54,17 @@ struct IdAndValue } }; -TEST_F(TestMetadataList, MetadataListWorks) +TEST_F(TestMetadataList, ListBuilderWorks) { - MetadataList> list1; + ListBuilder> list1; EXPECT_EQ(list1.Size(), 0u); - EXPECT_TRUE(list1.Empty()); + EXPECT_TRUE(list1.IsEmpty()); - // Reservation should always work when empty - ASSERT_EQ(list1.Reserve(5), CHIP_NO_ERROR); - EXPECT_EQ(list1.Capacity(), 5u); + ASSERT_EQ(list1.EnsureAppendCapacity(3), CHIP_NO_ERROR); EXPECT_EQ(list1.Size(), 0u); - EXPECT_TRUE(list1.Empty()); + EXPECT_TRUE(list1.IsEmpty()); - // You can re-reserve differently if still empty. - ASSERT_EQ(list1.Reserve(2), CHIP_NO_ERROR); - EXPECT_EQ(list1.Capacity(), 2u); + ASSERT_EQ(list1.EnsureAppendCapacity(2), CHIP_NO_ERROR); // Values can be appended until the capacity. EXPECT_EQ(list1.Append({ 0xA1, 111 }), CHIP_NO_ERROR); @@ -77,22 +73,31 @@ TEST_F(TestMetadataList, MetadataListWorks) 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); + // capacity is 3 because of the largest ensure + EXPECT_EQ(list1.Append({ 0xA3, 333 }), CHIP_NO_ERROR); + EXPECT_EQ(list1.Size(), 3u); + + EXPECT_EQ(list1.Append({ 0xA4, 444 }), CHIP_ERROR_BUFFER_TOO_SMALL); + EXPECT_EQ(list1.Size(), 3u); - MetadataList> list2 = std::move(list1); + ListBuilder> 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) + EXPECT_EQ(list1.Size(), 0u); // NOLINT(bugprone-use-after-move) + EXPECT_TRUE(list1.IsEmpty()); // NOLINT(bugprone-use-after-move) + EXPECT_TRUE(list1.TakeBuffer().empty()); // NOLINT(bugprone-use-after-move) // Moved-to list has storage. - EXPECT_EQ(list2.Size(), 2u); + EXPECT_EQ(list2.Size(), 3u); + EXPECT_FALSE(list2.IsEmpty()); // A span can be obtained over the list. - decltype(list2)::SpanType contents = list2.GetSpanValidForLifetime(); - EXPECT_EQ(contents.size(), 2u); + auto contents = list2.TakeBuffer(); + EXPECT_EQ(contents.size(), 3u); + + // contents takes ownersip of the list and clears it (and has no capacity) + EXPECT_TRUE(list2.IsEmpty()); + EXPECT_EQ(list2.Append({ 1, 2 }), CHIP_ERROR_BUFFER_TOO_SMALL); size_t idx = 0; for (const auto & element : contents) @@ -102,294 +107,100 @@ TEST_F(TestMetadataList, MetadataListWorks) 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); + EXPECT_EQ(idx, 3u); } -static constexpr std::array kConstantArray{ 1, 2, 3 }; - -TEST_F(TestMetadataList, MetadataListConvertersWork) +TEST_F(TestMetadataList, ListBuilderConvertersWorks) { { - 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); + ListBuilder list; + std::array kArray{ 1, 2, 3 }; + EXPECT_EQ(list.ReferenceExisting(Span(kArray)), CHIP_NO_ERROR); auto list2 = std::move(list); EXPECT_EQ(list.Size(), 0u); // NOLINT(bugprone-use-after-move) - auto list2Span = list2.GetSpanValidForLifetime(); + auto list2Span = list2.TakeBuffer(); + EXPECT_TRUE(list2.IsEmpty()); // took over 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 + ListBuilder list; + std::array kArray{ 1, 2, 3 }; + std::array kArray2{ 4, 5, 6 }; + EXPECT_EQ(list.ReferenceExisting(Span(kArray)), CHIP_NO_ERROR); + EXPECT_EQ(list.ReferenceExisting(Span(kArray2)), CHIP_NO_ERROR); -}; - -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; + auto list2 = std::move(list); + EXPECT_EQ(list.Size(), 0u); // NOLINT(bugprone-use-after-move) - if (result.Reserve(elementTable.size()) != CHIP_NO_ERROR) - { - result.Invalidate(); - return result; + auto list2Span = list2.TakeBuffer(); + EXPECT_TRUE(list2.IsEmpty()); // took over + EXPECT_EQ(list2Span.size(), 6u); + EXPECT_EQ(list2Span[0], 1); + EXPECT_EQ(list2Span[1], 2); + EXPECT_EQ(list2Span[2], 3); + EXPECT_EQ(list2Span[3], 4); + EXPECT_EQ(list2Span[4], 5); + EXPECT_EQ(list2Span[5], 6); } - for (const auto & element : elementTable) { - if (!supportedPredicate(element)) - { - continue; - } + ListBuilder list; - // Append as much as we can - (void) result.Append(element); - } + EXPECT_EQ(list.Append(10), CHIP_ERROR_BUFFER_TOO_SMALL); + EXPECT_EQ(list.EnsureAppendCapacity(5), CHIP_NO_ERROR); - return result; -} + EXPECT_EQ(list.Append(10), CHIP_NO_ERROR); + EXPECT_EQ(list.Append(11), CHIP_NO_ERROR); -class MiniCluster -{ -public: - MiniCluster() {} - virtual ~MiniCluster() {} + std::array kArray{ 1, 2, 3 }; - virtual MetadataList GetSupportedCommands() const - { - return FilterElements(Span{ kAllCommands.data(), kAllCommands.size() }, - [this](const CommandEntry & entry) { return this->IsCommandSupported(entry.mei); }); - } + EXPECT_EQ(list.ReferenceExisting(Span(kArray)), CHIP_NO_ERROR); -#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 + auto list2 = std::move(list); + EXPECT_EQ(list.Size(), 0u); // NOLINT(bugprone-use-after-move) - 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; - } + auto list2Span = list2.TakeBuffer(); + EXPECT_TRUE(list2.IsEmpty()); // took over + EXPECT_EQ(list2Span.size(), 5u); + EXPECT_EQ(list2Span[0], 10); + EXPECT_EQ(list2Span[1], 11); + EXPECT_EQ(list2Span[2], 1); + EXPECT_EQ(list2Span[3], 2); + EXPECT_EQ(list2Span[4], 3); } + { + ListBuilder list; - void SetFeatures(BitFlags features) { mFeatures = features; } - -private: - BitFlags mFeatures{}; -}; + EXPECT_EQ(list.Append(10), CHIP_ERROR_BUFFER_TOO_SMALL); + EXPECT_EQ(list.EnsureAppendCapacity(1), CHIP_NO_ERROR); -TEST_F(TestMetadataList, CommandsForFeaturesAreAsExpected) -{ - MiniCluster cluster; + EXPECT_EQ(list.Append(10), CHIP_NO_ERROR); + EXPECT_EQ(list.Append(11), CHIP_ERROR_BUFFER_TOO_SMALL); - { - cluster.SetFeatures(BitFlags{}.Set(NetworkCommissioningFeatureBits::kWifi)); + std::array kArray{ 1, 2, 3 }; - auto supportedCommands = cluster.GetSupportedCommands(); - EXPECT_EQ(supportedCommands.Size(), 8u); - } + EXPECT_EQ(list.AppendElements(Span(kArray)), CHIP_NO_ERROR); + EXPECT_EQ(list.ReferenceExisting(Span(kArray)), CHIP_NO_ERROR); - { - cluster.SetFeatures(BitFlags{}.Set(NetworkCommissioningFeatureBits::kEthernet)); + auto list2 = std::move(list); + EXPECT_EQ(list.Size(), 0u); // NOLINT(bugprone-use-after-move) - auto supportedCommands = cluster.GetSupportedCommands(); - EXPECT_EQ(supportedCommands.Size(), 0u); + auto list2Span = list2.TakeBuffer(); + EXPECT_TRUE(list2.IsEmpty()); // took over + EXPECT_EQ(list2Span.size(), 7u); + EXPECT_EQ(list2Span[0], 10); + EXPECT_EQ(list2Span[1], 1); + EXPECT_EQ(list2Span[2], 2); + EXPECT_EQ(list2Span[3], 3); + EXPECT_EQ(list2Span[4], 1); + EXPECT_EQ(list2Span[5], 2); + EXPECT_EQ(list2Span[6], 3); } } diff --git a/src/data-model-providers/codegen/CodegenDataModelProvider.cpp b/src/data-model-providers/codegen/CodegenDataModelProvider.cpp index d91b1be0a749ab..a7572fbb0322f7 100644 --- a/src/data-model-providers/codegen/CodegenDataModelProvider.cpp +++ b/src/data-model-providers/codegen/CodegenDataModelProvider.cpp @@ -63,165 +63,6 @@ DataModel::AcceptedCommandEntry AcceptedCommandEntryFor(const ConcreteCommandPat return entry; } -/// 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) -{ - - CommandHandlerInterface * interface = - CommandHandlerInterfaceRegistry::Instance().GetCommandHandler(path.mEndpointId, path.mClusterId); - if (interface != nullptr) - { - 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); - } - - if ((serverCluster == nullptr) || (serverCluster->acceptedCommandList == nullptr)) - { - // No data if cluster does not exist or cluster has no accepted commands - return CHIP_NO_ERROR; - } - const chip::CommandId * endOfList = serverCluster->acceptedCommandList; - while (*endOfList != kInvalidCommandId) - { - endOfList++; - } - const size_t commandCount = static_cast(endOfList - serverCluster->acceptedCommandList); - - ReturnErrorOnFailure(result.Reserve(commandCount)); - - ConcreteCommandPath commandPath = ConcreteCommandPath(path.mEndpointId, path.mClusterId, kInvalidCommandId); - for (const chip::CommandId * p = serverCluster->acceptedCommandList; p != endOfList; p++) - { - commandPath.mCommandId = *p; - ReturnErrorOnFailure(result.Append(AcceptedCommandEntryFor(commandPath))); - } - - return CHIP_NO_ERROR; -} - -/// 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) -{ - CommandHandlerInterface * interface = - CommandHandlerInterfaceRegistry::Instance().GetCommandHandler(path.mEndpointId, path.mClusterId); - if (interface != nullptr) - { - size_t commandCount = 0; - - CHIP_ERROR err = interface->EnumerateGeneratedCommands( - path, - [](CommandId id, void * context) -> Loop { - *reinterpret_cast(context) += 1; - return Loop::Continue; - }, - reinterpret_cast(&commandCount)); - - if (err == CHIP_NO_ERROR) - { - - 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 ((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) - { - endOfList++; - } - const size_t commandCount = static_cast(endOfList - serverCluster->generatedCommandList); - result = DataModel::MetadataList::FromConstSpan({ serverCluster->generatedCommandList, commandCount }); - - return CHIP_NO_ERROR; -} - DataModel::ServerClusterEntry ServerClusterEntryFrom(EndpointId endpointId, const EmberAfCluster & cluster) { DataModel::ServerClusterEntry entry; @@ -350,21 +191,11 @@ std::optional CodegenDataModelProvider::Invoke(co return std::nullopt; } -DataModel::MetadataList CodegenDataModelProvider::Endpoints() +CHIP_ERROR CodegenDataModelProvider::Endpoints(DataModel::ListBuilder & builder) { - DataModel::MetadataList result; - 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) - { -#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 {}; - } + ReturnErrorOnFailure(builder.EnsureAppendCapacity(endpointCount)); for (uint16_t endpointIndex = 0; endpointIndex < endpointCount; endpointIndex++) { @@ -387,18 +218,10 @@ DataModel::MetadataList CodegenDataModelProvider::Endp 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; - } + ReturnErrorOnFailure(builder.Append(entry)); } - return result; + return CHIP_NO_ERROR; } std::optional CodegenDataModelProvider::TryFindEndpointIndex(EndpointId id) const @@ -421,24 +244,16 @@ std::optional CodegenDataModelProvider::TryFindEndpointIndex(EndpointI return std::make_optional(idx); } -DataModel::MetadataList CodegenDataModelProvider::ServerClusters(EndpointId endpointId) +CHIP_ERROR CodegenDataModelProvider::ServerClusters(EndpointId endpointId, + DataModel::ListBuilder & builder) { const EmberAfEndpointType * endpoint = emberAfFindEndpointType(endpointId); - DataModel::MetadataList result; + VerifyOrReturnValue(endpoint != nullptr, CHIP_ERROR_NOT_FOUND); + VerifyOrReturnValue(endpoint->clusterCount > 0, CHIP_NO_ERROR); + VerifyOrReturnValue(endpoint->cluster != nullptr, CHIP_NO_ERROR); - VerifyOrReturnValue(endpoint != nullptr, result); - VerifyOrReturnValue(endpoint->clusterCount > 0, result); - VerifyOrReturnValue(endpoint->cluster != nullptr, result); - - CHIP_ERROR err = result.Reserve(emberAfClusterCountForEndpointType(endpoint, /* server = */ true)); - if (err != CHIP_NO_ERROR) - { -#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 {}; - } + ReturnErrorOnFailure(builder.EnsureAppendCapacity(emberAfClusterCountForEndpointType(endpoint, /* server = */ true))); const EmberAfCluster * begin = endpoint->cluster; const EmberAfCluster * end = endpoint->cluster + endpoint->clusterCount; @@ -448,74 +263,43 @@ DataModel::MetadataList CodegenDataModelProvider: { continue; } - - err = result.Append(ServerClusterEntryFrom(endpointId, *cluster)); - if (err != CHIP_NO_ERROR) - { -#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; - } + ReturnErrorOnFailure(builder.Append(ServerClusterEntryFrom(endpointId, *cluster))); } - return result; + return CHIP_NO_ERROR; } -DataModel::MetadataList CodegenDataModelProvider::Attributes(const ConcreteClusterPath & path) +CHIP_ERROR CodegenDataModelProvider::Attributes(const ConcreteClusterPath & path, + DataModel::ListBuilder & builder) { const EmberAfCluster * cluster = FindServerCluster(path); - DataModel::MetadataList result; - - VerifyOrReturnValue(cluster != nullptr, result); - VerifyOrReturnValue(cluster->attributeCount > 0, result); - VerifyOrReturnValue(cluster->attributes != nullptr, result); + VerifyOrReturnValue(cluster != nullptr, CHIP_ERROR_NOT_FOUND); + VerifyOrReturnValue(cluster->attributeCount > 0, CHIP_NO_ERROR); + VerifyOrReturnValue(cluster->attributes != nullptr, CHIP_NO_ERROR); - 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 reserve space for attributes: %" CHIP_ERROR_FORMAT, err.Format()); -#endif - return {}; - } + // TODO: if ember would encode data in AttributeEntry form, we could reference things directly + ReturnErrorOnFailure(builder.EnsureAppendCapacity(cluster->attributeCount)); Span attributeSpan(cluster->attributes, cluster->attributeCount); for (auto & attribute : attributeSpan) { - 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; - } + ReturnErrorOnFailure(builder.Append(AttributeEntryFrom(path, attribute))); } - return result; + return CHIP_NO_ERROR; } -DataModel::MetadataList CodegenDataModelProvider::ClientClusters(EndpointId endpointId) +CHIP_ERROR CodegenDataModelProvider::ClientClusters(EndpointId endpointId, DataModel::ListBuilder & builder) { const EmberAfEndpointType * endpoint = emberAfFindEndpointType(endpointId); - DataModel::MetadataList result; + VerifyOrReturnValue(endpoint != nullptr, CHIP_ERROR_NOT_FOUND); + VerifyOrReturnValue(endpoint->clusterCount > 0, CHIP_NO_ERROR); + VerifyOrReturnValue(endpoint->cluster != nullptr, CHIP_NO_ERROR); - VerifyOrReturnValue(endpoint != nullptr, result); - VerifyOrReturnValue(endpoint->clusterCount > 0, result); - VerifyOrReturnValue(endpoint->cluster != nullptr, result); - - CHIP_ERROR err = result.Reserve(emberAfClusterCountForEndpointType(endpoint, /* server = */ false)); - if (err != CHIP_NO_ERROR) - { -#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 {}; - } + ReturnErrorOnFailure(builder.EnsureAppendCapacity(emberAfClusterCountForEndpointType(endpoint, /* server = */ false))); const EmberAfCluster * begin = endpoint->cluster; const EmberAfCluster * end = endpoint->cluster + endpoint->clusterCount; @@ -525,18 +309,10 @@ DataModel::MetadataList CodegenDataModelProvider::ClientClusters(Endp { continue; } - - err = result.Append(cluster->clusterId); - if (err != CHIP_NO_ERROR) - { -#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; - } + ReturnErrorOnFailure(builder.Append(cluster->clusterId)); } - return result; + return CHIP_NO_ERROR; } const EmberAfCluster * CodegenDataModelProvider::FindServerCluster(const ConcreteClusterPath & path) @@ -557,37 +333,150 @@ const EmberAfCluster * CodegenDataModelProvider::FindServerCluster(const Concret return cluster; } -DataModel::MetadataList -CodegenDataModelProvider::AcceptedCommands(const ConcreteClusterPath & path) +CHIP_ERROR CodegenDataModelProvider::AcceptedCommands(const ConcreteClusterPath & path, + DataModel::ListBuilder & builder) { - DataModel::MetadataList result; + CommandHandlerInterface * interface = + CommandHandlerInterfaceRegistry::Instance().GetCommandHandler(path.mEndpointId, path.mClusterId); + if (interface != nullptr) + { + size_t commandCount = 0; - [[maybe_unused]] CHIP_ERROR err = FetchAcceptedCommands(path, FindServerCluster(path), result); + CHIP_ERROR err = interface->EnumerateAcceptedCommands( + path, + [](CommandId id, void * context) -> Loop { + *reinterpret_cast(context) += 1; + return Loop::Continue; + }, + reinterpret_cast(&commandCount)); -#if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING - if (err != CHIP_NO_ERROR) + if (err == CHIP_NO_ERROR) + { + using EnumerationData = struct + { + ConcreteCommandPath commandPath; + DataModel::ListBuilder * acceptedCommandList; + CHIP_ERROR processingError; + }; + + EnumerationData enumerationData; + enumerationData.commandPath = ConcreteCommandPath(path.mEndpointId, path.mClusterId, kInvalidCommandId); + enumerationData.processingError = CHIP_NO_ERROR; + enumerationData.acceptedCommandList = &builder; + + ReturnErrorOnFailure(builder.EnsureAppendCapacity(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(builder.Size() == commandCount, CHIP_ERROR_INTERNAL); + return CHIP_NO_ERROR; + } + VerifyOrReturnError(err == CHIP_ERROR_NOT_IMPLEMENTED, err); + } + + const EmberAfCluster * serverCluster = FindServerCluster(path); + VerifyOrReturnError(serverCluster != nullptr, CHIP_ERROR_NOT_FOUND); + VerifyOrReturnError(serverCluster->acceptedCommandList != nullptr, CHIP_NO_ERROR); + + const chip::CommandId * endOfList = serverCluster->acceptedCommandList; + while (*endOfList != kInvalidCommandId) { - ChipLogError(DataManagement, "Failed to fetch accepted commands: %" CHIP_ERROR_FORMAT, err.Format()); + endOfList++; } -#endif + const auto commandCount = static_cast(endOfList - serverCluster->acceptedCommandList); + + // TODO: if ember would store command entries, we could simplify this code to use static data + ReturnErrorOnFailure(builder.EnsureAppendCapacity(commandCount)); - return result; + ConcreteCommandPath commandPath = ConcreteCommandPath(path.mEndpointId, path.mClusterId, kInvalidCommandId); + for (const chip::CommandId * p = serverCluster->acceptedCommandList; p != endOfList; p++) + { + commandPath.mCommandId = *p; + ReturnErrorOnFailure(builder.Append(AcceptedCommandEntryFor(commandPath))); + } + + return CHIP_NO_ERROR; } -DataModel::MetadataList CodegenDataModelProvider::GeneratedCommands(const ConcreteClusterPath & path) +CHIP_ERROR CodegenDataModelProvider::GeneratedCommands(const ConcreteClusterPath & path, + DataModel::ListBuilder & builder) { - DataModel::MetadataList result; + CommandHandlerInterface * interface = + CommandHandlerInterfaceRegistry::Instance().GetCommandHandler(path.mEndpointId, path.mClusterId); + if (interface != nullptr) + { + size_t commandCount = 0; - [[maybe_unused]] CHIP_ERROR err = FetchGeneratedCommands(path, FindServerCluster(path), result); + CHIP_ERROR err = interface->EnumerateGeneratedCommands( + path, + [](CommandId id, void * context) -> Loop { + *reinterpret_cast(context) += 1; + return Loop::Continue; + }, + reinterpret_cast(&commandCount)); -#if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING - if (err != CHIP_NO_ERROR) - { - ChipLogError(DataManagement, "Failed to fetch generated commands: %" CHIP_ERROR_FORMAT, err.Format()); + if (err == CHIP_NO_ERROR) + { + ReturnErrorOnFailure(builder.EnsureAppendCapacity(commandCount)); + + using EnumerationData = struct + { + DataModel::ListBuilder * generatedCommandList; + CHIP_ERROR processingError; + }; + EnumerationData enumerationData; + enumerationData.processingError = CHIP_NO_ERROR; + enumerationData.generatedCommandList = &builder; + + 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(builder.Size() == commandCount, CHIP_ERROR_INTERNAL); + return CHIP_NO_ERROR; + } + VerifyOrReturnError(err == CHIP_ERROR_NOT_IMPLEMENTED, err); } -#endif - return result; + const EmberAfCluster * serverCluster = FindServerCluster(path); + VerifyOrReturnError(serverCluster != nullptr, CHIP_ERROR_NOT_FOUND); + VerifyOrReturnError(serverCluster->generatedCommandList != nullptr, CHIP_NO_ERROR); + + const chip::CommandId * endOfList = serverCluster->generatedCommandList; + while (*endOfList != kInvalidCommandId) + { + endOfList++; + } + const auto commandCount = static_cast(endOfList - serverCluster->generatedCommandList); + return builder.ReferenceExisting({ serverCluster->generatedCommandList, commandCount }); } void CodegenDataModelProvider::InitDataModelForTesting() @@ -596,7 +485,8 @@ void CodegenDataModelProvider::InitDataModelForTesting() InitDataModelHandler(); } -DataModel::MetadataList CodegenDataModelProvider::DeviceTypes(EndpointId endpointId) +CHIP_ERROR CodegenDataModelProvider::DeviceTypes(EndpointId endpointId, + DataModel::ListBuilder & builder) { std::optional endpoint_index = TryFindEndpointIndex(endpointId); if (!endpoint_index.has_value()) @@ -607,31 +497,17 @@ DataModel::MetadataList CodegenDataModelProvider::De CHIP_ERROR err = CHIP_NO_ERROR; Span deviceTypes = emberAfDeviceTypeListFromEndpointIndex(*endpoint_index, err); - DataModel::MetadataList result; - err = result.Reserve(deviceTypes.size()); - if (err != CHIP_NO_ERROR) - { -#if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING - ChipLogError(AppServer, "Failed to reserve device type buffer space: %" CHIP_ERROR_FORMAT, err.Format()); -#endif - return {}; - } + ReturnErrorOnFailure(builder.EnsureAppendCapacity(deviceTypes.size())); + 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; - } + ReturnErrorOnFailure(builder.Append(DeviceTypeEntryFromEmber(entry))); } - return result; + return CHIP_NO_ERROR; } -DataModel::MetadataList CodegenDataModelProvider::SemanticTags(EndpointId endpointId) +CHIP_ERROR CodegenDataModelProvider::SemanticTags(EndpointId endpointId, DataModel::ListBuilder & builder) { DataModel::Provider::SemanticTag semanticTag; size_t count = 0; @@ -640,38 +516,16 @@ DataModel::MetadataList CodegenDataModelProvid { count++; } - DataModel::MetadataList result; - CHIP_ERROR err = result.Reserve(count); - if (err != CHIP_NO_ERROR) - { -#if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING - ChipLogError(AppServer, "Failed to reserve semantic tag buffer space: %" CHIP_ERROR_FORMAT, err.Format()); -#endif - return {}; - } + ReturnErrorOnFailure(builder.EnsureAppendCapacity(count)); 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; - } + ReturnErrorOnFailure(GetSemanticTagForEndpointAtIndex(endpointId, idx, semanticTag)); + ReturnErrorOnFailure(builder.Append(semanticTag)); } - return result; + return CHIP_NO_ERROR; } } // namespace app diff --git a/src/data-model-providers/codegen/CodegenDataModelProvider.h b/src/data-model-providers/codegen/CodegenDataModelProvider.h index d0bb369e3dd23f..764f5f74874673 100644 --- a/src/data-model-providers/codegen/CodegenDataModelProvider.h +++ b/src/data-model-providers/codegen/CodegenDataModelProvider.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -66,14 +67,15 @@ class CodegenDataModelProvider : public DataModel::Provider CommandHandler * handler) override; /// attribute tree iteration - 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; + CHIP_ERROR Endpoints(DataModel::ListBuilder & out) override; + CHIP_ERROR SemanticTags(EndpointId endpointId, DataModel::ListBuilder & builder) override; + CHIP_ERROR DeviceTypes(EndpointId endpointId, DataModel::ListBuilder & builder) override; + CHIP_ERROR ClientClusters(EndpointId endpointId, DataModel::ListBuilder & builder) override; + CHIP_ERROR ServerClusters(EndpointId endpointId, DataModel::ListBuilder & builder) override; + CHIP_ERROR GeneratedCommands(const ConcreteClusterPath & path, DataModel::ListBuilder & builder) override; + CHIP_ERROR AcceptedCommands(const ConcreteClusterPath & path, + DataModel::ListBuilder & builder) override; + CHIP_ERROR Attributes(const ConcreteClusterPath & path, DataModel::ListBuilder & builder) override; void Temporary_ReportAttributeChanged(const AttributePathParams & path) override; diff --git a/src/data-model-providers/codegen/tests/TestCodegenModelViaMocks.cpp b/src/data-model-providers/codegen/tests/TestCodegenModelViaMocks.cpp index 67aed7f1d16ab6..1045114b7f9b4c 100644 --- a/src/data-model-providers/codegen/tests/TestCodegenModelViaMocks.cpp +++ b/src/data-model-providers/codegen/tests/TestCodegenModelViaMocks.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -905,9 +906,13 @@ TEST_F(TestCodegenModelViaMocks, IterateOverEndpoints) CodegenDataModelProviderWithContext model; // This iteration relies on the hard-coding that occurs when mock_ember is used - DataModel::MetadataList endpoints = model.Endpoints(); + DataModel::ListBuilder endpointsBuilder; - ASSERT_EQ(endpoints.Size(), 3u); + ASSERT_EQ(model.Endpoints(endpointsBuilder), CHIP_NO_ERROR); + + auto endpoints = endpointsBuilder.TakeBuffer(); + + ASSERT_EQ(endpoints.size(), 3u); EXPECT_EQ(endpoints[0].id, kMockEndpoint1); EXPECT_EQ(endpoints[0].parentId, kInvalidEndpointId); @@ -929,12 +934,17 @@ TEST_F(TestCodegenModelViaMocks, IterateOverServerClusters) chip::Test::ResetVersion(); - EXPECT_TRUE(model.ServerClusters(kEndpointIdThatIsMissing).Empty()); - EXPECT_TRUE(model.ServerClusters(kInvalidEndpointId).Empty()); + DataModel::ListBuilder builder; + + EXPECT_NE(model.ServerClusters(kEndpointIdThatIsMissing, builder), CHIP_NO_ERROR); + EXPECT_TRUE(builder.IsEmpty()); + EXPECT_NE(model.ServerClusters(kInvalidEndpointId, builder), CHIP_NO_ERROR); + EXPECT_TRUE(builder.IsEmpty()); // mock endpoint 1 has 2 mock clusters: 1 and 2 - auto serverClusters = model.ServerClusters(kMockEndpoint1); - ASSERT_EQ(serverClusters.Size(), 2u); + EXPECT_EQ(model.ServerClusters(kMockEndpoint1, builder), CHIP_NO_ERROR); + auto serverClusters = builder.TakeBuffer(); + ASSERT_EQ(serverClusters.size(), 2u); EXPECT_EQ(serverClusters[0].clusterId, MockClusterId(1)); EXPECT_EQ(serverClusters[0].dataVersion, 0u); @@ -946,14 +956,16 @@ TEST_F(TestCodegenModelViaMocks, IterateOverServerClusters) chip::Test::BumpVersion(); - serverClusters = model.ServerClusters(kMockEndpoint1); - ASSERT_EQ(serverClusters.Size(), 2u); + EXPECT_EQ(model.ServerClusters(kMockEndpoint1, builder), CHIP_NO_ERROR); + serverClusters = builder.TakeBuffer(); + ASSERT_EQ(serverClusters.size(), 2u); EXPECT_EQ(serverClusters[0].dataVersion, 1u); EXPECT_EQ(serverClusters[1].dataVersion, 1u); // mock endpoint 3 has 4 mock clusters: 1 through 4 - serverClusters = model.ServerClusters(kMockEndpoint3); - ASSERT_EQ(serverClusters.Size(), 4u); + EXPECT_EQ(model.ServerClusters(kMockEndpoint3, builder), CHIP_NO_ERROR); + serverClusters = builder.TakeBuffer(); + 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)); @@ -965,20 +977,26 @@ TEST_F(TestCodegenModelViaMocks, IterateOverClientClusters) UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; - EXPECT_TRUE(model.ClientClusters(kEndpointIdThatIsMissing).Empty()); - EXPECT_TRUE(model.ClientClusters(kInvalidEndpointId).Empty()); + DataModel::ListBuilder builder; + + EXPECT_EQ(model.ClientClusters(kEndpointIdThatIsMissing, builder), CHIP_ERROR_NOT_FOUND); + EXPECT_TRUE(builder.IsEmpty()); + EXPECT_EQ(model.ClientClusters(kInvalidEndpointId, builder), CHIP_ERROR_NOT_FOUND); + EXPECT_TRUE(builder.IsEmpty()); // mock endpoint 1 has 2 mock client clusters: 3 and 4 - auto clientClusters = model.ClientClusters(kMockEndpoint1); + EXPECT_EQ(model.ClientClusters(kMockEndpoint1, builder), CHIP_NO_ERROR); + auto clientClusters = builder.TakeBuffer(); const ClusterId kExpectedClusters1[] = { MockClusterId(3), MockClusterId(4) }; - ASSERT_TRUE(clientClusters.GetSpanValidForLifetime().data_equal(Span(kExpectedClusters1))); + ASSERT_TRUE(clientClusters.data_equal(Span(kExpectedClusters1))); // mock endpoint 2 has 1 mock client clusters: 3(has server side at the same time) and 4 - clientClusters = model.ClientClusters(kMockEndpoint2); + EXPECT_EQ(model.ClientClusters(kMockEndpoint2, builder), CHIP_NO_ERROR); + clientClusters = builder.TakeBuffer(); const ClusterId kExpectedClusters2[] = { MockClusterId(3), MockClusterId(4) }; - ASSERT_TRUE(clientClusters.GetSpanValidForLifetime().data_equal(Span(kExpectedClusters2))); + ASSERT_TRUE(clientClusters.data_equal(Span(kExpectedClusters2))); } TEST_F(TestCodegenModelViaMocks, IterateOverAttributes) @@ -987,14 +1005,23 @@ TEST_F(TestCodegenModelViaMocks, IterateOverAttributes) CodegenDataModelProviderWithContext model; // invalid paths should return in "no more data" - 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()); + ASSERT_TRUE(model.AttributesIgnoreError(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).empty()); + ASSERT_TRUE(model.AttributesIgnoreError(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).empty()); + ASSERT_TRUE(model.AttributesIgnoreError(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).empty()); + ASSERT_TRUE(model.AttributesIgnoreError(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).empty()); // should be able to iterate over valid paths - auto attributes = model.Attributes(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); - ASSERT_EQ(attributes.Size(), 4u); + DataModel::ListBuilder builder; + + // invalid paths return errors + ASSERT_EQ(model.Attributes(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1)), builder), CHIP_ERROR_NOT_FOUND); + ASSERT_EQ(model.Attributes(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1)), builder), CHIP_ERROR_NOT_FOUND); + ASSERT_EQ(model.Attributes(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10)), builder), CHIP_ERROR_NOT_FOUND); + ASSERT_EQ(model.Attributes(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId), builder), CHIP_ERROR_NOT_FOUND); + + EXPECT_EQ(model.Attributes(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2)), builder), CHIP_NO_ERROR); + auto attributes = builder.TakeBuffer(); + ASSERT_EQ(attributes.size(), 4u); ASSERT_EQ(attributes[0].attributeId, ClusterRevision::Id); ASSERT_FALSE(attributes[0].flags.Has(AttributeQualityFlags::kListAttribute)); @@ -1070,15 +1097,29 @@ TEST_F(TestCodegenModelViaMocks, IterateOverAcceptedCommands) UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; + DataModel::ListBuilder builder; + // invalid paths should return in "no more data" - 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()); - - MetadataList cmds = - model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); - ASSERT_EQ(cmds.Size(), 3u); + ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1)), builder), + CHIP_ERROR_NOT_FOUND); + ASSERT_TRUE(builder.IsEmpty()); + ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1)), builder), CHIP_ERROR_NOT_FOUND); + ASSERT_TRUE(builder.IsEmpty()); + ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10)), builder), CHIP_ERROR_NOT_FOUND); + ASSERT_TRUE(builder.IsEmpty()); + ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId), builder), CHIP_ERROR_NOT_FOUND); + ASSERT_TRUE(builder.IsEmpty()); + + ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2)), builder), CHIP_NO_ERROR); + ASSERT_EQ(builder.Size(), 3u); + + auto cmds = builder.TakeBuffer(); + + // took ownership + ASSERT_EQ(builder.Size(), 0u); + ASSERT_TRUE(builder.IsEmpty()); + + ASSERT_EQ(cmds.size(), 3u); ASSERT_EQ(cmds[0].commandId, 1u); ASSERT_EQ(cmds[1].commandId, 2u); ASSERT_EQ(cmds[2].commandId, 23u); @@ -1089,20 +1130,30 @@ TEST_F(TestCodegenModelViaMocks, IterateOverGeneratedCommands) UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; + DataModel::ListBuilder builder; + // invalid paths should return in "no more data" - 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()); + ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1)), builder), + CHIP_ERROR_NOT_FOUND); + ASSERT_TRUE(builder.IsEmpty()); + ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1)), builder), CHIP_ERROR_NOT_FOUND); + ASSERT_TRUE(builder.IsEmpty()); + ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10)), builder), CHIP_ERROR_NOT_FOUND); + ASSERT_TRUE(builder.IsEmpty()); + ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId), builder), CHIP_ERROR_NOT_FOUND); + ASSERT_TRUE(builder.IsEmpty()); // should be able to iterate over valid paths - MetadataList cmds = model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); + ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2)), builder), CHIP_NO_ERROR); + auto cmds = builder.TakeBuffer(); + const CommandId expectedCommands2[] = { 2, 10 }; - ASSERT_TRUE(cmds.GetSpanValidForLifetime().data_equal(Span(expectedCommands2))); + ASSERT_TRUE(cmds.data_equal(Span(expectedCommands2))); - cmds = model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint2, MockClusterId(3))); + ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint2, MockClusterId(3)), builder), CHIP_NO_ERROR); + cmds = builder.TakeBuffer(); const CommandId expectedCommands3[] = { 4, 6 }; - ASSERT_TRUE(cmds.GetSpanValidForLifetime().data_equal(Span(expectedCommands3))); + ASSERT_TRUE(cmds.data_equal(Span(expectedCommands3))); } TEST_F(TestCodegenModelViaMocks, CommandHandlerInterfaceCommandHandling) @@ -1115,16 +1166,23 @@ TEST_F(TestCodegenModelViaMocks, CommandHandlerInterfaceCommandHandling) // Validate that these work CustomListCommandHandler handler(MakeOptional(kMockEndpoint1), MockClusterId(1)); + DataModel::ListBuilder generatedBuilder; + DataModel::ListBuilder acceptedBuilder; + // At this point, without overrides, there should be no accepted/generated commands - ASSERT_TRUE(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))).Empty()); - ASSERT_TRUE(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))).Empty()); + ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), generatedBuilder), CHIP_NO_ERROR); + ASSERT_TRUE(generatedBuilder.IsEmpty()); + ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), acceptedBuilder), CHIP_NO_ERROR); + ASSERT_TRUE(acceptedBuilder.IsEmpty()); handler.SetOverrideAccepted(true); handler.SetOverrideGenerated(true); // with overrides, the list is still empty ... - ASSERT_TRUE(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))).Empty()); - ASSERT_TRUE(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))).Empty()); + ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), generatedBuilder), CHIP_NO_ERROR); + ASSERT_TRUE(generatedBuilder.IsEmpty()); + ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), acceptedBuilder), CHIP_NO_ERROR); + ASSERT_TRUE(acceptedBuilder.IsEmpty()); // set some overrides handler.AcceptedVec().push_back(1234); @@ -1132,16 +1190,17 @@ TEST_F(TestCodegenModelViaMocks, CommandHandlerInterfaceCommandHandling) handler.GeneratedVec().push_back(33); - MetadataList acceptedCommands = - model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); + ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), acceptedBuilder), CHIP_NO_ERROR); + auto acceptedCommands = acceptedBuilder.TakeBuffer(); - ASSERT_EQ(acceptedCommands.Size(), 2u); + ASSERT_EQ(acceptedCommands.size(), 2u); ASSERT_EQ(acceptedCommands[0].commandId, 1234u); ASSERT_EQ(acceptedCommands[1].commandId, 999u); - MetadataList generatedCommands = model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); + ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), generatedBuilder), CHIP_NO_ERROR); + auto generatedCommands = generatedBuilder.TakeBuffer(); const CommandId expectedGeneratedCommands[] = { 33 }; - ASSERT_TRUE(generatedCommands.GetSpanValidForLifetime().data_equal(Span(expectedGeneratedCommands))); + ASSERT_TRUE(generatedCommands.data_equal(Span(expectedGeneratedCommands))); } TEST_F(TestCodegenModelViaMocks, ReadForInvalidGlobalAttributePath) @@ -2390,8 +2449,10 @@ TEST_F(TestCodegenModelViaMocks, DeviceTypeIteration) CodegenDataModelProviderWithContext model; // Mock endpoint 1 has 3 device types - auto deviceTypes = model.DeviceTypes(kMockEndpoint1); - ASSERT_EQ(deviceTypes.Size(), 3u); + DataModel::ListBuilder builder; + ASSERT_EQ(model.DeviceTypes(kMockEndpoint1, builder), CHIP_NO_ERROR); + auto deviceTypes = builder.TakeBuffer(); + ASSERT_EQ(deviceTypes.size(), 3u); const DeviceTypeEntry expected1[] = { { .deviceTypeId = kDeviceTypeId1, .deviceTypeRevision = kDeviceTypeId1Version }, @@ -2404,13 +2465,18 @@ TEST_F(TestCodegenModelViaMocks, DeviceTypeIteration) } // Mock endpoint 2 has 1 device types - deviceTypes = model.DeviceTypes(kMockEndpoint2); - ASSERT_EQ(deviceTypes.Size(), 1u); + ASSERT_TRUE(builder.IsEmpty()); // ownership taken above, we start fresh + ASSERT_EQ(model.DeviceTypes(kMockEndpoint2, builder), CHIP_NO_ERROR); + deviceTypes = builder.TakeBuffer(); + ASSERT_EQ(deviceTypes.size(), 1u); const DeviceTypeEntry expected2 = { .deviceTypeId = kDeviceTypeId2, .deviceTypeRevision = kDeviceTypeId2Version }; ASSERT_EQ(deviceTypes[0], expected2); // empty endpoint works - ASSERT_TRUE(model.DeviceTypes(kMockEndpoint3).Empty()); + ASSERT_TRUE(builder.IsEmpty()); // ownership taken above, we start fresh + ASSERT_EQ(model.DeviceTypes(kMockEndpoint3, builder), CHIP_NO_ERROR); + ASSERT_TRUE(builder.IsEmpty()); + ASSERT_TRUE(builder.TakeBuffer().empty()); } TEST_F(TestCodegenModelViaMocks, SemanticTagIteration) @@ -2418,11 +2484,18 @@ TEST_F(TestCodegenModelViaMocks, SemanticTagIteration) UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; - ASSERT_TRUE(model.SemanticTags(kMockEndpoint2).Empty()); + DataModel::ListBuilder builder; + ASSERT_EQ(model.SemanticTags(kMockEndpoint2, builder), CHIP_NO_ERROR); + ASSERT_TRUE(builder.IsEmpty()); + auto tags = builder.TakeBuffer(); + ASSERT_TRUE(tags.empty()); // Mock endpoint 1 has 3 semantic tags - MetadataList tags = model.SemanticTags(kMockEndpoint1); - ASSERT_EQ(tags.Size(), 3u); + ASSERT_EQ(model.SemanticTags(kMockEndpoint1, builder), CHIP_NO_ERROR); + ASSERT_EQ(builder.Size(), 3u); + tags = builder.TakeBuffer(); + ASSERT_EQ(tags.size(), 3u); + ASSERT_TRUE(builder.IsEmpty()); // ownership taken auto tag = tags[0]; EXPECT_EQ(tag.mfgCode, MakeNullable(VendorId::TestVendor1));