From 30812a9914670b39efa3a5db537fdb5116df55d7 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 7 May 2024 15:17:48 -0400 Subject: [PATCH 001/123] Initial copy with a clean history --- src/BUILD.gn | 1 + src/app/ConcreteClusterPath.h | 2 +- src/app/codegen-interaction-model/BUILD.gn | 22 ++ src/app/codegen-interaction-model/Model.cpp | 267 ++++++++++++++++ src/app/codegen-interaction-model/Model.h | 52 ++++ src/app/codegen-interaction-model/model.gni | 35 +++ .../codegen-interaction-model/tests/BUILD.gn | 35 +++ .../tests/TestCodegenModelViaMocks.cpp | 290 ++++++++++++++++++ src/app/interaction-model/IterationTypes.h | 16 +- src/app/interaction-model/tests/BUILD.gn | 2 + src/app/util/mock/attribute-storage.cpp | 1 + 11 files changed, 721 insertions(+), 2 deletions(-) create mode 100644 src/app/codegen-interaction-model/BUILD.gn create mode 100644 src/app/codegen-interaction-model/Model.cpp create mode 100644 src/app/codegen-interaction-model/Model.h create mode 100644 src/app/codegen-interaction-model/model.gni create mode 100644 src/app/codegen-interaction-model/tests/BUILD.gn create mode 100644 src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp diff --git a/src/BUILD.gn b/src/BUILD.gn index d1dfcc7856135e..8ff1e2cf1f463b 100644 --- a/src/BUILD.gn +++ b/src/BUILD.gn @@ -55,6 +55,7 @@ if (chip_build_tests) { chip_test_group("tests") { deps = [] tests = [ + "${chip_root}/src/app/codegen-interaction-model/tests", "${chip_root}/src/app/interaction-model/tests", "${chip_root}/src/access/tests", "${chip_root}/src/crypto/tests", diff --git a/src/app/ConcreteClusterPath.h b/src/app/ConcreteClusterPath.h index 58b2f5b477f139..47feae1fd67269 100644 --- a/src/app/ConcreteClusterPath.h +++ b/src/app/ConcreteClusterPath.h @@ -52,7 +52,7 @@ struct ConcreteClusterPath // to alignment requirements it's "free" in the sense of not needing more // memory to put it here. But we don't initialize it, because that // increases codesize for the non-consumers. - bool mExpanded; // NOTE: in between larger members + bool mExpanded = false; // NOTE: in between larger members ClusterId mClusterId = 0; }; diff --git a/src/app/codegen-interaction-model/BUILD.gn b/src/app/codegen-interaction-model/BUILD.gn new file mode 100644 index 00000000000000..bbb4a30e96d621 --- /dev/null +++ b/src/app/codegen-interaction-model/BUILD.gn @@ -0,0 +1,22 @@ +# Copyright (c) 2024 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import("//build_overrides/chip.gni") +# This source set is TIGHLY coupled with code-generated data models +# as generally implemented by `src/app/util` +# +# Corresponding functions defined in attribute-storace.cpp/attribute-table.cpp must +# be available at link time for this model to use +# Use `model.gni` to get access to: +# model.cpp +# model.h diff --git a/src/app/codegen-interaction-model/Model.cpp b/src/app/codegen-interaction-model/Model.cpp new file mode 100644 index 00000000000000..dc3fbd9cbca7d0 --- /dev/null +++ b/src/app/codegen-interaction-model/Model.cpp @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +#include +#include +#include + +namespace chip { +namespace app { +namespace CodegenDataModel { + +namespace { + +/// Checks if the specified ember cluster mask corresponds to a valid +/// server cluster. +bool IsServerMask(EmberAfClusterMask mask) +{ + return (mask == 0) || ((mask & CLUSTER_MASK_SERVER) != 0); +} + +/// Load the cluster information into the specified destination +void LoadClusterInfo(const ConcreteClusterPath & path, const EmberAfCluster & cluster, InteractionModel::ClusterInfo * info) +{ + chip::DataVersion * versionPtr = emberAfDataVersionStorage(path); + if (versionPtr != nullptr) + { + info->dataVersion = *versionPtr; + } + else + { + ChipLogError(AppServer, "Failed to get data version for %d/" ChipLogFormatMEI, static_cast(path.mEndpointId), + ChipLogValueMEI(cluster.clusterId)); + info->dataVersion = 0; + } + + // TODO: set entry flags: + // info->flags.Set(ClusterQualityFlags::kDiagnosticsData) +} + +/// Converts a EmberAfCluster into a ClusterEntry +InteractionModel::ClusterEntry ClusterEntryFrom(EndpointId endpointId, const EmberAfCluster & cluster) +{ + InteractionModel::ClusterEntry entry; + + entry.path = ConcreteClusterPath(endpointId, cluster.clusterId); + LoadClusterInfo(entry.path, cluster, &entry.info); + + return entry; +} + +/// Finds the first server cluster entry for the given endpoint data starting at [start_index] +/// +/// Returns an invalid entry if no more server clusters are found +InteractionModel::ClusterEntry FirstServerClusterEntry(EndpointId endpointId, const EmberAfEndpointType * endpoint, + uint16_t start_index) +{ + for (unsigned i = start_index; i < endpoint->clusterCount; i++) + { + const EmberAfCluster & cluster = endpoint->cluster[i]; + if (!IsServerMask(cluster.mask & CLUSTER_MASK_SERVER)) + { + continue; + } + + return ClusterEntryFrom(endpointId, cluster); + } + + return InteractionModel::ClusterEntry::Invalid(); +} + +/// Load the cluster information into the specified destination +void LoadAttributeInfo(const ConcreteAttributePath & path, const EmberAfAttributeMetadata & attribute, + InteractionModel::AttributeInfo * info) +{ + if (attribute.attributeType == ZCL_ARRAY_ATTRIBUTE_TYPE) + { + info->flags.Set(InteractionModel::AttributeQualityFlags::kListAttribute); + } + + // TODO: Set additional flags: + // info->flags.Set(InteractionModel::AttributeQualityFlags::kChangesOmitted) +} + +InteractionModel::AttributeEntry AttributeEntryFrom(const ConcreteClusterPath & clusterPath, + const EmberAfAttributeMetadata & attribute) +{ + InteractionModel::AttributeEntry entry; + + entry.path = ConcreteAttributePath(clusterPath.mEndpointId, clusterPath.mClusterId, attribute.attributeId); + LoadAttributeInfo(entry.path, attribute, &entry.info); + + return entry; +} + +} // namespace + +CHIP_ERROR Model::ReadAttribute(const InteractionModel::ReadAttributeRequest & request, InteractionModel::ReadState & state, + AttributeValueEncoder & encoder) +{ + // TODO: this needs an implementation + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR Model::WriteAttribute(const InteractionModel::WriteAttributeRequest & request, AttributeValueDecoder & decoder) +{ + // TODO: this needs an implementation + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR Model::Invoke(const InteractionModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, + InteractionModel::InvokeReply & reply) +{ + // TODO: this needs an implementation + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +EndpointId Model::FirstEndpoint() +{ + // find the first enabled index + const uint16_t lastEndpointIndex = emberAfEndpointCount(); + for (uint16_t endpointIndex = 0; endpointIndex < lastEndpointIndex; endpointIndex++) + { + if (emberAfEndpointIndexIsEnabled(endpointIndex)) + { + return emberAfEndpointFromIndex(endpointIndex); + } + } + + // No enabled endpoint found. Give up + return kInvalidEndpointId; +} + +EndpointId Model::NextEndpoint(EndpointId before) +{ + // find the first enabled index + bool beforeFound = false; + + const uint16_t lastEndpointIndex = emberAfEndpointCount(); + for (uint16_t endpointIndex = 0; endpointIndex < lastEndpointIndex; endpointIndex++) + { + if (!beforeFound) + { + beforeFound = (before == emberAfEndpointFromIndex(endpointIndex)); + continue; + } + + if (emberAfEndpointIndexIsEnabled(endpointIndex)) + { + return emberAfEndpointFromIndex(endpointIndex); + } + } + + // No enabled enpoint after "before" was found, give up + return kInvalidEndpointId; +} + +InteractionModel::ClusterEntry Model::FirstCluster(EndpointId endpointId) +{ + const EmberAfEndpointType * endpoint = emberAfFindEndpointType(endpointId); + VerifyOrReturnValue(endpoint != nullptr, InteractionModel::ClusterEntry::Invalid()); + VerifyOrReturnValue(endpoint->clusterCount > 0, InteractionModel::ClusterEntry::Invalid()); + VerifyOrReturnValue(endpoint->cluster != nullptr, InteractionModel::ClusterEntry::Invalid()); + + return FirstServerClusterEntry(endpointId, endpoint, 0); +} + +InteractionModel::ClusterEntry Model::NextCluster(const ConcreteClusterPath & before) +{ + const EmberAfEndpointType * endpoint = emberAfFindEndpointType(before.mEndpointId); + VerifyOrReturnValue(endpoint != nullptr, InteractionModel::ClusterEntry::Invalid()); + VerifyOrReturnValue(endpoint->clusterCount > 0, InteractionModel::ClusterEntry::Invalid()); + VerifyOrReturnValue(endpoint->cluster != nullptr, InteractionModel::ClusterEntry::Invalid()); + + for (uint16_t i = 0; i < endpoint->clusterCount; i++) + { + const EmberAfCluster & cluster = endpoint->cluster[i]; + if (IsServerMask(cluster.mask) && (cluster.clusterId == before.mClusterId)) + { + return FirstServerClusterEntry(before.mEndpointId, endpoint, i + 1); + } + } + + return InteractionModel::ClusterEntry::Invalid(); +} + +std::optional Model::GetClusterInfo(const ConcreteClusterPath & path) +{ + const EmberAfCluster * cluster = emberAfFindServerCluster(path.mEndpointId, path.mClusterId); + VerifyOrReturnValue(cluster != nullptr, std::nullopt); + + InteractionModel::ClusterInfo info; + LoadClusterInfo(path, *cluster, &info); + + return std::make_optional(info); +} + +InteractionModel::AttributeEntry Model::FirstAttribute(const ConcreteClusterPath & path) +{ + const EmberAfCluster * cluster = emberAfFindServerCluster(path.mEndpointId, path.mClusterId); + VerifyOrReturnValue(cluster != nullptr, InteractionModel::AttributeEntry::Invalid()); + VerifyOrReturnValue(cluster->attributeCount > 0, InteractionModel::AttributeEntry::Invalid()); + VerifyOrReturnValue(cluster->attributes != nullptr, InteractionModel::AttributeEntry::Invalid()); + + return AttributeEntryFrom(path, cluster->attributes[0]); +} + +InteractionModel::AttributeEntry Model::NextAttribute(const ConcreteAttributePath & before) +{ + const EmberAfCluster * cluster = emberAfFindServerCluster(before.mEndpointId, before.mClusterId); + VerifyOrReturnValue(cluster != nullptr, InteractionModel::AttributeEntry::Invalid()); + VerifyOrReturnValue(cluster->attributeCount > 0, InteractionModel::AttributeEntry::Invalid()); + VerifyOrReturnValue(cluster->attributes != nullptr, InteractionModel::AttributeEntry::Invalid()); + + // find the given attribute in the list and then return the next one + bool foundPosition = false; + const unsigned attributeCount = cluster->attributeCount; + for (unsigned i = 0; i < attributeCount; i++) + { + if (foundPosition) + { + return AttributeEntryFrom(before, cluster->attributes[i]); + } + + foundPosition = (cluster->attributes[i].attributeId == before.mAttributeId); + } + + return InteractionModel::AttributeEntry::Invalid(); +} + +std::optional Model::GetAttributeInfo(const ConcreteAttributePath & path) +{ + const EmberAfCluster * cluster = emberAfFindServerCluster(path.mEndpointId, path.mClusterId); + VerifyOrReturnValue(cluster != nullptr, std::nullopt); + VerifyOrReturnValue(cluster->attributeCount > 0, std::nullopt); + VerifyOrReturnValue(cluster->attributes != nullptr, std::nullopt); + const unsigned attributeCount = cluster->attributeCount; + for (unsigned i = 0; i < attributeCount; i++) + { + if (cluster->attributes[i].attributeId == path.mAttributeId) + { + InteractionModel::AttributeInfo info; + LoadAttributeInfo(path, cluster->attributes[i], &info); + return std::make_optional(info); + } + } + + return std::nullopt; +} + +} // namespace CodegenDataModel +} // namespace app +} // namespace chip diff --git a/src/app/codegen-interaction-model/Model.h b/src/app/codegen-interaction-model/Model.h new file mode 100644 index 00000000000000..3f48970852a7bc --- /dev/null +++ b/src/app/codegen-interaction-model/Model.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +namespace chip { +namespace app { +namespace CodegenDataModel { + +class Model : public chip::app::InteractionModel::Model +{ +public: + /// Generic model implementations + CHIP_ERROR Shutdown() override { return CHIP_NO_ERROR; } + + CHIP_ERROR ReadAttribute(const InteractionModel::ReadAttributeRequest & request, InteractionModel::ReadState & state, + AttributeValueEncoder & encoder) override; + CHIP_ERROR WriteAttribute(const InteractionModel::WriteAttributeRequest & request, AttributeValueDecoder & decoder) override; + CHIP_ERROR Invoke(const InteractionModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, + InteractionModel::InvokeReply & reply) override; + + /// attribute tree iteration + EndpointId FirstEndpoint() override; + EndpointId NextEndpoint(EndpointId before) override; + + InteractionModel::ClusterEntry FirstCluster(EndpointId endpoint) override; + InteractionModel::ClusterEntry NextCluster(const ConcreteClusterPath & before) override; + std::optional GetClusterInfo(const ConcreteClusterPath & path) override; + + InteractionModel::AttributeEntry FirstAttribute(const ConcreteClusterPath & cluster) override; + InteractionModel::AttributeEntry NextAttribute(const ConcreteAttributePath & before) override; + std::optional GetAttributeInfo(const ConcreteAttributePath & path) override; +}; + +} // namespace CodegenDataModel +} // namespace app +} // namespace chip diff --git a/src/app/codegen-interaction-model/model.gni b/src/app/codegen-interaction-model/model.gni new file mode 100644 index 00000000000000..ddf7b1b172cc5a --- /dev/null +++ b/src/app/codegen-interaction-model/model.gni @@ -0,0 +1,35 @@ +# Copyright (c) 2024 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import("//build_overrides/chip.gni") + +# The sources in this directory are TIGHLY coupled with code-generated data models +# as generally implemented by `src/app/util` +# +# Corresponding functions defined in attribute-storace.cpp/attribute-table.cpp must +# be available at link time for this model to use and constants heavily depend +# on `zap-generated/endpoint_config.h` (generally compile-time constants that +# are code generated) +# +# As a result, the files here are NOT a source_set or similar because they cannot +# be cleanly built as a stand-alone and instead have to be imported as part of +# a different data model or compilation unit. +codegen_interaction_model_SOURCES = [ + "${chip_root}/src/app/codegen-interaction-model/Model.h", + "${chip_root}/src/app/codegen-interaction-model/Model.cpp", +] + +codegen_interaction_model_PUBLIC_DEPS = [ + "${chip_root}/src/app/common:attribute-type", + "${chip_root}/src/app/interaction-model", +] diff --git a/src/app/codegen-interaction-model/tests/BUILD.gn b/src/app/codegen-interaction-model/tests/BUILD.gn new file mode 100644 index 00000000000000..f543bc8d0cc24b --- /dev/null +++ b/src/app/codegen-interaction-model/tests/BUILD.gn @@ -0,0 +1,35 @@ +# Copyright (c) 2024 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import("//build_overrides/chip.gni") +import("${chip_root}/build/chip/chip_test_suite.gni") +import("${chip_root}/src/app/codegen-interaction-model/model.gni") + +source_set("mock_model") { + sources = codegen_interaction_model_SOURCES + + public_deps = codegen_interaction_model_PUBLIC_DEPS + + # this ties in the codegen model to an actual ember implementation + public_deps += [ "${chip_root}/src/app/util/mock:mock_ember" ] +} + +chip_test_suite("tests") { + output_name = "libCodegenInteractionModelTests" + + test_sources = [ "TestCodegenModelViaMocks.cpp" ] + + cflags = [ "-Wconversion" ] + + public_deps = [ ":mock_model" ] +} diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp new file mode 100644 index 00000000000000..fc34b566963df2 --- /dev/null +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -0,0 +1,290 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +#include +#include +#include + +#include + +using namespace chip; +using namespace chip::Test; +using namespace chip::app; +using namespace chip::app::InteractionModel; +using namespace chip::app::Clusters::Globals::Attributes; + +namespace { + +constexpr EndpointId kEndpointIdThatIsMissing = kMockEndpointMin - 1; + +static_assert(kEndpointIdThatIsMissing != kInvalidEndpointId); +static_assert(kEndpointIdThatIsMissing != kMockEndpoint1); +static_assert(kEndpointIdThatIsMissing != kMockEndpoint2); +static_assert(kEndpointIdThatIsMissing != kMockEndpoint3); + +// clang-format off +const MockNodeConfig gTestNodeConfig({ + MockEndpointConfig(kMockEndpoint1, { + MockClusterConfig(MockClusterId(1), { + ClusterRevision::Id, FeatureMap::Id, + }, { + MockEventId(1), MockEventId(2), + }), + MockClusterConfig(MockClusterId(2), { + ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), + }), + }), + MockEndpointConfig(kMockEndpoint2, { + MockClusterConfig(MockClusterId(1), { + ClusterRevision::Id, FeatureMap::Id, + }), + MockClusterConfig(MockClusterId(2), { + ClusterRevision::Id, + FeatureMap::Id, + MockAttributeId(1), + MockAttributeConfig(MockAttributeId(2), ZCL_ARRAY_ATTRIBUTE_TYPE), + }), + MockClusterConfig(MockClusterId(3), { + ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), MockAttributeId(2), MockAttributeId(3), + }), + }), + MockEndpointConfig(kMockEndpoint3, { + MockClusterConfig(MockClusterId(1), { + ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), + }), + MockClusterConfig(MockClusterId(2), { + ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), MockAttributeId(2), MockAttributeId(3), MockAttributeId(4), + }), + MockClusterConfig(MockClusterId(3), { + ClusterRevision::Id, FeatureMap::Id, + }), + MockClusterConfig(MockClusterId(4), { + ClusterRevision::Id, FeatureMap::Id, + }), + }), +}); +// clang-format on + +struct UseMockNodeConfig +{ + UseMockNodeConfig(const MockNodeConfig & config) { SetMockNodeConfig(config); } + ~UseMockNodeConfig() { ResetMockNodeConfig(); } +}; + +} // namespace + +TEST(TestCodegenModelViaMocks, IterateOverEndpoints) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel::Model model; + + // This iteration relies on the hard-coding that occurs when mock_ember is used + ASSERT_EQ(model.FirstEndpoint(), kMockEndpoint1); + ASSERT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); + ASSERT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); + ASSERT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); + + /// Some out of order requests should work as well + ASSERT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); + ASSERT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); + ASSERT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); + ASSERT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); + ASSERT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); + ASSERT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); + ASSERT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); + ASSERT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); + ASSERT_EQ(model.FirstEndpoint(), kMockEndpoint1); + ASSERT_EQ(model.FirstEndpoint(), kMockEndpoint1); +} + +TEST(TestCodegenModelViaMocks, IterateOverClusters) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel::Model model; + + chip::Test::ResetVersion(); + + ASSERT_FALSE(model.FirstCluster(kEndpointIdThatIsMissing).path.HasValidIds()); + ASSERT_FALSE(model.FirstCluster(kInvalidEndpointId).path.HasValidIds()); + + // mock endpoint 1 has 2 mock clusters: 1 and 2 + ClusterEntry entry = model.FirstCluster(kMockEndpoint1); + ASSERT_TRUE(entry.path.HasValidIds()); + ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint1); + ASSERT_EQ(entry.path.mClusterId, MockClusterId(1)); + ASSERT_EQ(entry.info.dataVersion, 0u); + ASSERT_EQ(entry.info.flags.Raw(), 0u); + + chip::Test::BumpVersion(); + + entry = model.NextCluster(entry.path); + ASSERT_TRUE(entry.path.HasValidIds()); + ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint1); + ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); + ASSERT_EQ(entry.info.dataVersion, 1u); + ASSERT_EQ(entry.info.flags.Raw(), 0u); + + entry = model.NextCluster(entry.path); + ASSERT_FALSE(entry.path.HasValidIds()); + + // mock endpoint 3 has 4 mock clusters: 1 through 4 + entry = model.FirstCluster(kMockEndpoint3); + for (uint16_t clusterId = 1; clusterId <= 4; clusterId++) + { + ASSERT_TRUE(entry.path.HasValidIds()); + ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint3); + ASSERT_EQ(entry.path.mClusterId, MockClusterId(clusterId)); + entry = model.NextCluster(entry.path); + } + ASSERT_FALSE(entry.path.HasValidIds()); + + // repeat calls should work + for (int i = 0; i < 10; i++) + { + entry = model.FirstCluster(kMockEndpoint1); + ASSERT_TRUE(entry.path.HasValidIds()); + ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint1); + ASSERT_EQ(entry.path.mClusterId, MockClusterId(1)); + } + + for (int i = 0; i < 10; i++) + { + ClusterEntry nextEntry = model.NextCluster(entry.path); + ASSERT_TRUE(nextEntry.path.HasValidIds()); + ASSERT_EQ(nextEntry.path.mEndpointId, kMockEndpoint1); + ASSERT_EQ(nextEntry.path.mClusterId, MockClusterId(2)); + } +} + +TEST(TestCodegenModelViaMocks, GetCluterInfo) +{ + + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel::Model model; + + chip::Test::ResetVersion(); + + ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kInvalidEndpointId, kInvalidClusterId)).has_value()); + ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).has_value()); + ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).has_value()); + ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).has_value()); + + // now get the value + std::optional info = model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); + ASSERT_TRUE(info.has_value()); + EXPECT_EQ(info->dataVersion, 0u); + EXPECT_EQ(info->flags.Raw(), 0u); + + chip::Test::BumpVersion(); + info = model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); + ASSERT_TRUE(info.has_value()); + EXPECT_EQ(info->dataVersion, 1u); + EXPECT_EQ(info->flags.Raw(), 0u); +} + +TEST(TestCodegenModelViaMocks, IterateOverAttributes) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel::Model model; + + // invalid paths should return in "no more data" + ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).path.HasValidIds()); + ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).path.HasValidIds()); + ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).path.HasValidIds()); + ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds()); + + // should be able to iterate over valid paths + AttributeEntry entry = model.FirstAttribute(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); + ASSERT_TRUE(entry.path.HasValidIds()); + ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); + ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); + ASSERT_EQ(entry.path.mAttributeId, ClusterRevision::Id); + ASSERT_FALSE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); + + entry = model.NextAttribute(entry.path); + ASSERT_TRUE(entry.path.HasValidIds()); + ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); + ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); + ASSERT_EQ(entry.path.mAttributeId, FeatureMap::Id); + ASSERT_FALSE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); + + entry = model.NextAttribute(entry.path); + ASSERT_TRUE(entry.path.HasValidIds()); + ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); + ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); + ASSERT_EQ(entry.path.mAttributeId, MockAttributeId(1)); + ASSERT_FALSE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); + + entry = model.NextAttribute(entry.path); + ASSERT_TRUE(entry.path.HasValidIds()); + ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); + ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); + ASSERT_EQ(entry.path.mAttributeId, MockAttributeId(2)); + ASSERT_TRUE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); + + entry = model.NextAttribute(entry.path); + ASSERT_FALSE(entry.path.HasValidIds()); + + // repeated calls should work + for (int i = 0; i < 10; i++) + { + entry = model.FirstAttribute(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); + ASSERT_TRUE(entry.path.HasValidIds()); + ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); + ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); + ASSERT_EQ(entry.path.mAttributeId, ClusterRevision::Id); + ASSERT_FALSE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); + } + + for (int i = 0; i < 10; i++) + { + entry = model.NextAttribute(ConcreteAttributePath(kMockEndpoint2, MockClusterId(2), MockAttributeId(1))); + ASSERT_TRUE(entry.path.HasValidIds()); + ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); + ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); + ASSERT_EQ(entry.path.mAttributeId, MockAttributeId(2)); + ASSERT_TRUE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); + } +} + +TEST(TestCodegenModelViaMocks, GetAttributeInfo) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel::Model model; + + // various non-existent or invalid paths should return no info data + ASSERT_FALSE( + model.GetAttributeInfo(ConcreteAttributePath(kInvalidEndpointId, kInvalidClusterId, kInvalidAttributeId)).has_value()); + ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kInvalidEndpointId, kInvalidClusterId, FeatureMap::Id)).has_value()); + ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kInvalidEndpointId, MockClusterId(1), FeatureMap::Id)).has_value()); + ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, kInvalidClusterId, FeatureMap::Id)).has_value()); + ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), FeatureMap::Id)).has_value()); + ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), kInvalidAttributeId)).has_value()); + ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))).has_value()); + + // valid info + std::optional info = + model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), FeatureMap::Id)); + ASSERT_TRUE(info.has_value()); + ASSERT_FALSE(info->flags.Has(AttributeQualityFlags::kListAttribute)); + + info = model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint2, MockClusterId(2), MockAttributeId(2))); + ASSERT_TRUE(info.has_value()); + ASSERT_TRUE(info->flags.Has(AttributeQualityFlags::kListAttribute)); +} diff --git a/src/app/interaction-model/IterationTypes.h b/src/app/interaction-model/IterationTypes.h index 32ad8bc42b73be..c948d4bcb428f8 100644 --- a/src/app/interaction-model/IterationTypes.h +++ b/src/app/interaction-model/IterationTypes.h @@ -35,7 +35,7 @@ enum class ClusterQualityFlags : uint32_t struct ClusterInfo { - DataVersion dataVersion; // current version of this cluster + DataVersion dataVersion = 0; // current version of this cluster BitFlags flags; }; @@ -43,6 +43,13 @@ struct ClusterEntry { ConcreteClusterPath path; ClusterInfo info; + + static ClusterEntry Invalid() + { + ClusterEntry result; + result.path = ConcreteClusterPath(kInvalidEndpointId, kInvalidClusterId); + return result; + } }; enum class AttributeQualityFlags : uint32_t @@ -60,6 +67,13 @@ struct AttributeEntry { ConcreteAttributePath path; AttributeInfo info; + + static AttributeEntry Invalid() + { + AttributeEntry result; + result.path = ConcreteAttributePath(kInvalidEndpointId, kInvalidClusterId, kInvalidAttributeId); + return result; + } }; /// Provides metadata information for a data model diff --git a/src/app/interaction-model/tests/BUILD.gn b/src/app/interaction-model/tests/BUILD.gn index c7d36b4f1dcdca..4767d61dd12ed1 100644 --- a/src/app/interaction-model/tests/BUILD.gn +++ b/src/app/interaction-model/tests/BUILD.gn @@ -15,6 +15,8 @@ import("//build_overrides/chip.gni") import("${chip_root}/build/chip/chip_test_suite.gni") chip_test_suite("tests") { + output_name = "libIMInterfaceTests" + test_sources = [ "TestEventEmitting.cpp" ] cflags = [ "-Wconversion" ] diff --git a/src/app/util/mock/attribute-storage.cpp b/src/app/util/mock/attribute-storage.cpp index 2293a48a8403e4..131385be8373bb 100644 --- a/src/app/util/mock/attribute-storage.cpp +++ b/src/app/util/mock/attribute-storage.cpp @@ -414,6 +414,7 @@ void SetMockNodeConfig(const MockNodeConfig & config) mockConfig = &config; } +/// Resets the mock attribute storage to the default configuration. void ResetMockNodeConfig() { mockConfig = nullptr; From 53891f3f7bd03abd10dea2b2ac57520aa8f86458 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 7 May 2024 15:28:05 -0400 Subject: [PATCH 002/123] make linter happy --- src/app/codegen-interaction-model/BUILD.gn | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/app/codegen-interaction-model/BUILD.gn b/src/app/codegen-interaction-model/BUILD.gn index bbb4a30e96d621..7bacec118c55ad 100644 --- a/src/app/codegen-interaction-model/BUILD.gn +++ b/src/app/codegen-interaction-model/BUILD.gn @@ -12,11 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. import("//build_overrides/chip.gni") + # This source set is TIGHLY coupled with code-generated data models # as generally implemented by `src/app/util` # # Corresponding functions defined in attribute-storace.cpp/attribute-table.cpp must # be available at link time for this model to use +# # Use `model.gni` to get access to: -# model.cpp -# model.h +# Model.cpp +# Model.h +# +# The abolve list of files exists to satisfy the "dependency linter" +# since those files should technically be "visible to gn" even though we +# are supposed to go through model.gni constants From 0947dd68091194f62afb2eaba0735714aa02a1db Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 7 May 2024 15:29:39 -0400 Subject: [PATCH 003/123] Restyle --- src/app/codegen-interaction-model/BUILD.gn | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/codegen-interaction-model/BUILD.gn b/src/app/codegen-interaction-model/BUILD.gn index 7bacec118c55ad..8b859afc6b2e48 100644 --- a/src/app/codegen-interaction-model/BUILD.gn +++ b/src/app/codegen-interaction-model/BUILD.gn @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. import("//build_overrides/chip.gni") - # This source set is TIGHLY coupled with code-generated data models # as generally implemented by `src/app/util` # From 5a2a10809c959778b9163297059631f8d15cb0dd Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 7 May 2024 15:54:47 -0400 Subject: [PATCH 004/123] Fix typo --- .../tests/TestCodegenModelViaMocks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index fc34b566963df2..9baf73dd5a6b11 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -172,7 +172,7 @@ TEST(TestCodegenModelViaMocks, IterateOverClusters) } } -TEST(TestCodegenModelViaMocks, GetCluterInfo) +TEST(TestCodegenModelViaMocks, GetClusterInfo) { UseMockNodeConfig config(gTestNodeConfig); From 26bb33b36e7cfd47fc1403256fd91e8e311756b3 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 8 May 2024 08:54:47 -0400 Subject: [PATCH 005/123] Add nolint: assert will return before we use the underlying value --- .../tests/TestCodegenModelViaMocks.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 9baf73dd5a6b11..6c1982678082f3 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -188,14 +188,14 @@ TEST(TestCodegenModelViaMocks, GetClusterInfo) // now get the value std::optional info = model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); ASSERT_TRUE(info.has_value()); - EXPECT_EQ(info->dataVersion, 0u); - EXPECT_EQ(info->flags.Raw(), 0u); + EXPECT_EQ(info->dataVersion, 0u); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_EQ(info->flags.Raw(), 0u); // NOLINT(bugprone-unchecked-optional-access) chip::Test::BumpVersion(); info = model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); ASSERT_TRUE(info.has_value()); - EXPECT_EQ(info->dataVersion, 1u); - EXPECT_EQ(info->flags.Raw(), 0u); + EXPECT_EQ(info->dataVersion, 1u); // NOLINT(bugprone-unchecked-optional-access) + EXPECT_EQ(info->flags.Raw(), 0u); // NOLINT(bugprone-unchecked-optional-access) } TEST(TestCodegenModelViaMocks, IterateOverAttributes) From 4b55a2082d3249f12eee46eb716244bb2210e18b Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 8 May 2024 08:55:43 -0400 Subject: [PATCH 006/123] 2 more fixes regarding unchecked access --- .../tests/TestCodegenModelViaMocks.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 6c1982678082f3..1e4f734b823256 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -282,9 +282,9 @@ TEST(TestCodegenModelViaMocks, GetAttributeInfo) std::optional info = model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), FeatureMap::Id)); ASSERT_TRUE(info.has_value()); - ASSERT_FALSE(info->flags.Has(AttributeQualityFlags::kListAttribute)); + EXPECT_FALSE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) info = model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint2, MockClusterId(2), MockAttributeId(2))); ASSERT_TRUE(info.has_value()); - ASSERT_TRUE(info->flags.Has(AttributeQualityFlags::kListAttribute)); + EXPECT_TRUE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) } From d8b8433462a7e3f69d65b2f91e486692ea7768f6 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 8 May 2024 08:57:52 -0400 Subject: [PATCH 007/123] Switch some asserts to expects, for better test logic --- .../tests/TestCodegenModelViaMocks.cpp | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 1e4f734b823256..a8b69e797a0d6f 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -95,8 +95,8 @@ TEST(TestCodegenModelViaMocks, IterateOverEndpoints) chip::app::CodegenDataModel::Model model; // This iteration relies on the hard-coding that occurs when mock_ember is used - ASSERT_EQ(model.FirstEndpoint(), kMockEndpoint1); - ASSERT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); + EXPECT_EQ(model.FirstEndpoint(), kMockEndpoint1); + EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); ASSERT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); ASSERT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); @@ -120,55 +120,55 @@ TEST(TestCodegenModelViaMocks, IterateOverClusters) chip::Test::ResetVersion(); - ASSERT_FALSE(model.FirstCluster(kEndpointIdThatIsMissing).path.HasValidIds()); - ASSERT_FALSE(model.FirstCluster(kInvalidEndpointId).path.HasValidIds()); + EXPECT_FALSE(model.FirstCluster(kEndpointIdThatIsMissing).path.HasValidIds()); + EXPECT_FALSE(model.FirstCluster(kInvalidEndpointId).path.HasValidIds()); // mock endpoint 1 has 2 mock clusters: 1 and 2 ClusterEntry entry = model.FirstCluster(kMockEndpoint1); ASSERT_TRUE(entry.path.HasValidIds()); - ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint1); - ASSERT_EQ(entry.path.mClusterId, MockClusterId(1)); - ASSERT_EQ(entry.info.dataVersion, 0u); - ASSERT_EQ(entry.info.flags.Raw(), 0u); + EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint1); + EXPECT_EQ(entry.path.mClusterId, MockClusterId(1)); + EXPECT_EQ(entry.info.dataVersion, 0u); + EXPECT_EQ(entry.info.flags.Raw(), 0u); chip::Test::BumpVersion(); entry = model.NextCluster(entry.path); ASSERT_TRUE(entry.path.HasValidIds()); - ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint1); - ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); - ASSERT_EQ(entry.info.dataVersion, 1u); - ASSERT_EQ(entry.info.flags.Raw(), 0u); + EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint1); + EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); + EXPECT_EQ(entry.info.dataVersion, 1u); + EXPECT_EQ(entry.info.flags.Raw(), 0u); entry = model.NextCluster(entry.path); - ASSERT_FALSE(entry.path.HasValidIds()); + EXPECT_FALSE(entry.path.HasValidIds()); // mock endpoint 3 has 4 mock clusters: 1 through 4 entry = model.FirstCluster(kMockEndpoint3); for (uint16_t clusterId = 1; clusterId <= 4; clusterId++) { ASSERT_TRUE(entry.path.HasValidIds()); - ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint3); - ASSERT_EQ(entry.path.mClusterId, MockClusterId(clusterId)); + EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint3); + EXPECT_EQ(entry.path.mClusterId, MockClusterId(clusterId)); entry = model.NextCluster(entry.path); } - ASSERT_FALSE(entry.path.HasValidIds()); + EXPECT_FALSE(entry.path.HasValidIds()); // repeat calls should work for (int i = 0; i < 10; i++) { entry = model.FirstCluster(kMockEndpoint1); ASSERT_TRUE(entry.path.HasValidIds()); - ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint1); - ASSERT_EQ(entry.path.mClusterId, MockClusterId(1)); + EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint1); + EXPECT_EQ(entry.path.mClusterId, MockClusterId(1)); } for (int i = 0; i < 10; i++) { ClusterEntry nextEntry = model.NextCluster(entry.path); ASSERT_TRUE(nextEntry.path.HasValidIds()); - ASSERT_EQ(nextEntry.path.mEndpointId, kMockEndpoint1); - ASSERT_EQ(nextEntry.path.mClusterId, MockClusterId(2)); + EXPECT_EQ(nextEntry.path.mEndpointId, kMockEndpoint1); + EXPECT_EQ(nextEntry.path.mClusterId, MockClusterId(2)); } } From 21376d6efb40d41ffe69f8bb41142967f5380252 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 8 May 2024 10:33:36 -0400 Subject: [PATCH 008/123] Start implementing a read ... wip while I switch to a few other tests --- src/app/codegen-interaction-model/BUILD.gn | 1 + src/app/codegen-interaction-model/Model.cpp | 7 - .../codegen-interaction-model/Model_Read.cpp | 382 ++++++++++++++++++ src/app/codegen-interaction-model/model.gni | 1 + 4 files changed, 384 insertions(+), 7 deletions(-) create mode 100644 src/app/codegen-interaction-model/Model_Read.cpp diff --git a/src/app/codegen-interaction-model/BUILD.gn b/src/app/codegen-interaction-model/BUILD.gn index 8b859afc6b2e48..03a24557711cfa 100644 --- a/src/app/codegen-interaction-model/BUILD.gn +++ b/src/app/codegen-interaction-model/BUILD.gn @@ -20,6 +20,7 @@ import("//build_overrides/chip.gni") # # Use `model.gni` to get access to: # Model.cpp +# Model_Read.cpp # Model.h # # The abolve list of files exists to satisfy the "dependency linter" diff --git a/src/app/codegen-interaction-model/Model.cpp b/src/app/codegen-interaction-model/Model.cpp index dc3fbd9cbca7d0..fd4f204c519dfe 100644 --- a/src/app/codegen-interaction-model/Model.cpp +++ b/src/app/codegen-interaction-model/Model.cpp @@ -109,13 +109,6 @@ InteractionModel::AttributeEntry AttributeEntryFrom(const ConcreteClusterPath & } // namespace -CHIP_ERROR Model::ReadAttribute(const InteractionModel::ReadAttributeRequest & request, InteractionModel::ReadState & state, - AttributeValueEncoder & encoder) -{ - // TODO: this needs an implementation - return CHIP_ERROR_NOT_IMPLEMENTED; -} - CHIP_ERROR Model::WriteAttribute(const InteractionModel::WriteAttributeRequest & request, AttributeValueDecoder & decoder) { // TODO: this needs an implementation diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp new file mode 100644 index 00000000000000..b238c9e303d28e --- /dev/null +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace app { +namespace CodegenDataModel { +namespace { + +// Will set at most one of the out-params (aAttributeCluster or +// aAttributeMetadata) to non-null. Both null means attribute not supported, +// aAttributeCluster non-null means this is a supported global attribute that +// does not have metadata. +CHIP_ERROR FindAttributeMetadata(const ConcreteAttributePath & aPath, const EmberAfCluster ** aAttributeCluster, + const EmberAfAttributeMetadata ** aAttributeMetadata) +{ + *aAttributeCluster = nullptr; + *aAttributeMetadata = nullptr; + + for (auto & attr : GlobalAttributesNotInMetadata) + { + if (attr == aPath.mAttributeId) + { + *aAttributeCluster = emberAfFindServerCluster(aPath.mEndpointId, aPath.mClusterId); + return CHIP_NO_ERROR; + } + } + + *aAttributeMetadata = emberAfLocateAttributeMetadata(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId); + + if (*aAttributeMetadata == nullptr) + { + const EmberAfEndpointType * type = emberAfFindEndpointType(aPath.mEndpointId); + if (type == nullptr) + { + return CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint); + } + + const EmberAfCluster * cluster = emberAfFindClusterInType(type, aPath.mClusterId, CLUSTER_MASK_SERVER); + if (cluster == nullptr) + { + return CHIP_IM_GLOBAL_STATUS(UnsupportedCluster); + } + + // Since we know the attribute is unsupported and the endpoint/cluster are + // OK, this is the only option left. + return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR CheckAccessPrivilege(const ConcreteAttributePath & path, const chip::Access::SubjectDescriptor & descriptor) +{ + Access::RequestPath requestPath{ .cluster = path.mClusterId, .endpoint = path.mEndpointId }; + Access::Privilege requestPrivilege = RequiredPrivilege::ForReadAttribute(path); + CHIP_ERROR err = Access::GetAccessControl().Check(descriptor, requestPath, requestPrivilege); + if (err != CHIP_NO_ERROR) + { + ReturnErrorCodeIf(err != CHIP_ERROR_ACCESS_DENIED, err); + if (path.mExpanded) + { + return CHIP_NO_ERROR; + } + + return SendFailureStatus(path, aAttributeReports, Protocols::InteractionModel::Status::UnsupportedAccess, nullptr); + } + + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +} // namespace + +/// separated-out ReadAttribute implementation (given existing complexity) +CHIP_ERROR Model::ReadAttribute(const InteractionModel::ReadAttributeRequest & request, InteractionModel::ReadState & state, + AttributeValueEncoder & encoder) +{ + ChipLogDetail(DataManagement, + "Reading attribute: Cluster=" ChipLogFormatMEI " Endpoint=%x AttributeId=" ChipLogFormatMEI " (expanded=%d)", + ChipLogValueMEI(request.path.mClusterId), request.path.mEndpointId, ChipLogValueMEI(request.path.mAttributeId), + request.path.mExpanded); + + const EmberAfCluster * attributeCluster = nullptr; + const EmberAfAttributeMetadata * attributeMetadata = nullptr; + + ReturnErrorOnFailure(FindAttributeMetadata(request.path, &attributeCluster, &attributeMetadata)); + VerifyOrDie(attributeMetadata != nullptr); // this is the contract of FindAttributeMetadata + + CHIP_ERROR err = CheckAccessPrivilege(request.path, request.subjectDescriptor); + if (err == CHIP_ERROR_ACCESS_DENIED && request.path.mExpanded) + { + // return success, however without filling in the attribute value at all as there is nothing to be done here... + return CHIP_NO_ERROR; + } + +#if 0 + + + { + // Special handling for mandatory global attributes: these are always for attribute list, using a special + // reader (which can be lightweight constructed even from nullptr). + GlobalAttributeReader reader(attributeCluster); + AttributeAccessInterface * attributeOverride = + (attributeCluster != nullptr) ? &reader : GetAttributeAccessOverride(aPath.mEndpointId, aPath.mClusterId); + if (attributeOverride) + { + bool triedEncode = false; + ReturnErrorOnFailure(ReadViaAccessInterface(aSubjectDescriptor, aIsFabricFiltered, aPath, aAttributeReports, + apEncoderState, attributeOverride, &triedEncode)); + ReturnErrorCodeIf(triedEncode, CHIP_NO_ERROR); + } + } + + // Read attribute using Ember, if it doesn't have an override. + + TLV::TLVWriter backup; + aAttributeReports.Checkpoint(backup); + + AttributeReportIB::Builder & attributeReport = aAttributeReports.CreateAttributeReport(); + ReturnErrorOnFailure(aAttributeReports.GetError()); + + AttributeDataIB::Builder & attributeDataIBBuilder = attributeReport.CreateAttributeData(); + ReturnErrorOnFailure(attributeDataIBBuilder.GetError()); + + DataVersion version = 0; + ReturnErrorOnFailure(ReadClusterDataVersion(aPath, version)); + attributeDataIBBuilder.DataVersion(version); + ReturnErrorOnFailure(attributeDataIBBuilder.GetError()); + + AttributePathIB::Builder & attributePathIBBuilder = attributeDataIBBuilder.CreatePath(); + ReturnErrorOnFailure(attributeDataIBBuilder.GetError()); + + CHIP_ERROR err = attributePathIBBuilder.Endpoint(aPath.mEndpointId) + .Cluster(aPath.mClusterId) + .Attribute(aPath.mAttributeId) + .EndOfAttributePathIB(); + ReturnErrorOnFailure(err); + + EmberAfAttributeSearchRecord record; + record.endpoint = aPath.mEndpointId; + record.clusterId = aPath.mClusterId; + record.attributeId = aPath.mAttributeId; + Status status = emAfReadOrWriteAttribute(&record, &attributeMetadata, attributeData, sizeof(attributeData), + /* write = */ false); + + if (status == Status::Success) + { + EmberAfAttributeType attributeType = attributeMetadata->attributeType; + bool isNullable = attributeMetadata->IsNullable(); + TLV::TLVWriter * writer = attributeDataIBBuilder.GetWriter(); + VerifyOrReturnError(writer != nullptr, CHIP_NO_ERROR); + TLV::Tag tag = TLV::ContextTag(AttributeDataIB::Tag::kData); + switch (BaseType(attributeType)) + { + case ZCL_NO_DATA_ATTRIBUTE_TYPE: // No data + ReturnErrorOnFailure(writer->PutNull(tag)); + break; + case ZCL_BOOLEAN_ATTRIBUTE_TYPE: // Boolean + ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); + break; + case ZCL_INT8U_ATTRIBUTE_TYPE: // Unsigned 8-bit integer + ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); + break; + case ZCL_INT16U_ATTRIBUTE_TYPE: // Unsigned 16-bit integer + { + ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); + break; + } + case ZCL_INT24U_ATTRIBUTE_TYPE: // Unsigned 24-bit integer + { + using IntType = OddSizedInteger<3, false>; + ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); + break; + } + case ZCL_INT32U_ATTRIBUTE_TYPE: // Unsigned 32-bit integer + { + ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); + break; + } + case ZCL_INT40U_ATTRIBUTE_TYPE: // Unsigned 40-bit integer + { + using IntType = OddSizedInteger<5, false>; + ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); + break; + } + case ZCL_INT48U_ATTRIBUTE_TYPE: // Unsigned 48-bit integer + { + using IntType = OddSizedInteger<6, false>; + ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); + break; + } + case ZCL_INT56U_ATTRIBUTE_TYPE: // Unsigned 56-bit integer + { + using IntType = OddSizedInteger<7, false>; + ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); + break; + } + case ZCL_INT64U_ATTRIBUTE_TYPE: // Unsigned 64-bit integer + { + ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); + break; + } + case ZCL_INT8S_ATTRIBUTE_TYPE: // Signed 8-bit integer + { + ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); + break; + } + case ZCL_INT16S_ATTRIBUTE_TYPE: // Signed 16-bit integer + { + ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); + break; + } + case ZCL_INT24S_ATTRIBUTE_TYPE: // Signed 24-bit integer + { + using IntType = OddSizedInteger<3, true>; + ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); + break; + } + case ZCL_INT32S_ATTRIBUTE_TYPE: // Signed 32-bit integer + { + ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); + break; + } + case ZCL_INT40S_ATTRIBUTE_TYPE: // Signed 40-bit integer + { + using IntType = OddSizedInteger<5, true>; + ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); + break; + } + case ZCL_INT48S_ATTRIBUTE_TYPE: // Signed 48-bit integer + { + using IntType = OddSizedInteger<6, true>; + ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); + break; + } + case ZCL_INT56S_ATTRIBUTE_TYPE: // Signed 56-bit integer + { + using IntType = OddSizedInteger<7, true>; + ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); + break; + } + case ZCL_INT64S_ATTRIBUTE_TYPE: // Signed 64-bit integer + { + ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); + break; + } + case ZCL_SINGLE_ATTRIBUTE_TYPE: // 32-bit float + { + ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); + break; + } + case ZCL_DOUBLE_ATTRIBUTE_TYPE: // 64-bit float + { + ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); + break; + } + case ZCL_CHAR_STRING_ATTRIBUTE_TYPE: // Char string + { + char * actualData = reinterpret_cast(attributeData + 1); + uint8_t dataLength = attributeData[0]; + if (dataLength == 0xFF) + { + if (isNullable) + { + ReturnErrorOnFailure(writer->PutNull(tag)); + } + else + { + return CHIP_ERROR_INCORRECT_STATE; + } + } + else + { + ReturnErrorOnFailure(writer->PutString(tag, actualData, dataLength)); + } + break; + } + case ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE: { + char * actualData = reinterpret_cast(attributeData + 2); // The pascal string contains 2 bytes length + uint16_t dataLength; + memcpy(&dataLength, attributeData, sizeof(dataLength)); + if (dataLength == 0xFFFF) + { + if (isNullable) + { + ReturnErrorOnFailure(writer->PutNull(tag)); + } + else + { + return CHIP_ERROR_INCORRECT_STATE; + } + } + else + { + ReturnErrorOnFailure(writer->PutString(tag, actualData, dataLength)); + } + break; + } + case ZCL_OCTET_STRING_ATTRIBUTE_TYPE: // Octet string + { + uint8_t * actualData = attributeData + 1; + uint8_t dataLength = attributeData[0]; + if (dataLength == 0xFF) + { + if (isNullable) + { + ReturnErrorOnFailure(writer->PutNull(tag)); + } + else + { + return CHIP_ERROR_INCORRECT_STATE; + } + } + else + { + ReturnErrorOnFailure(writer->Put(tag, chip::ByteSpan(actualData, dataLength))); + } + break; + } + case ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE: { + uint8_t * actualData = attributeData + 2; // The pascal string contains 2 bytes length + uint16_t dataLength; + memcpy(&dataLength, attributeData, sizeof(dataLength)); + if (dataLength == 0xFFFF) + { + if (isNullable) + { + ReturnErrorOnFailure(writer->PutNull(tag)); + } + else + { + return CHIP_ERROR_INCORRECT_STATE; + } + } + else + { + ReturnErrorOnFailure(writer->Put(tag, chip::ByteSpan(actualData, dataLength))); + } + break; + } + default: + ChipLogError(DataManagement, "Attribute type 0x%x not handled", static_cast(attributeType)); + status = Status::UnsupportedRead; + } + } + + if (status == Protocols::InteractionModel::Status::Success) + { + return SendSuccessStatus(attributeReport, attributeDataIBBuilder); + } + + return SendFailureStatus(aPath, aAttributeReports, status, &backup); +#endif + + // TODO: this needs an implementation + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +} // namespace CodegenDataModel +} // namespace app +} // namespace chip diff --git a/src/app/codegen-interaction-model/model.gni b/src/app/codegen-interaction-model/model.gni index ddf7b1b172cc5a..3395e15bdbae50 100644 --- a/src/app/codegen-interaction-model/model.gni +++ b/src/app/codegen-interaction-model/model.gni @@ -27,6 +27,7 @@ import("//build_overrides/chip.gni") codegen_interaction_model_SOURCES = [ "${chip_root}/src/app/codegen-interaction-model/Model.h", "${chip_root}/src/app/codegen-interaction-model/Model.cpp", + "${chip_root}/src/app/codegen-interaction-model/Model_Read.cpp", ] codegen_interaction_model_PUBLIC_DEPS = [ From 9f5eff9a18d4c3ee215d1cdb7ecd6d16ba187b39 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 8 May 2024 11:28:49 -0400 Subject: [PATCH 009/123] More implementation and we seem to need privilege check as well --- .../codegen-interaction-model/Model_Read.cpp | 26 ++++++------- src/app/interaction-model/OperationTypes.h | 2 +- src/app/util/mock/BUILD.gn | 1 + src/app/util/mock/privilege-storage.cpp | 37 +++++++++++++++++++ 4 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 src/app/util/mock/privilege-storage.cpp diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp index b238c9e303d28e..c0f840438a3011 100644 --- a/src/app/codegen-interaction-model/Model_Read.cpp +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -16,10 +16,12 @@ */ #include +#include #include #include #include #include +#include #include #include @@ -76,18 +78,14 @@ CHIP_ERROR CheckAccessPrivilege(const ConcreteAttributePath & path, const chip:: Access::RequestPath requestPath{ .cluster = path.mClusterId, .endpoint = path.mEndpointId }; Access::Privilege requestPrivilege = RequiredPrivilege::ForReadAttribute(path); CHIP_ERROR err = Access::GetAccessControl().Check(descriptor, requestPath, requestPrivilege); - if (err != CHIP_NO_ERROR) - { - ReturnErrorCodeIf(err != CHIP_ERROR_ACCESS_DENIED, err); - if (path.mExpanded) - { - return CHIP_NO_ERROR; - } - return SendFailureStatus(path, aAttributeReports, Protocols::InteractionModel::Status::UnsupportedAccess, nullptr); - } + // access denied sent as-is + ReturnErrorCodeIf(err == CHIP_ERROR_ACCESS_DENIED, CHIP_ERROR_ACCESS_DENIED); - return CHIP_ERROR_NOT_IMPLEMENTED; + // anything else is an opaque failure + ReturnErrorCodeIf(err != CHIP_NO_ERROR, CHIP_IM_GLOBAL_STATUS(UnsupportedAccess)); + + return CHIP_NO_ERROR; } } // namespace @@ -107,11 +105,11 @@ CHIP_ERROR Model::ReadAttribute(const InteractionModel::ReadAttributeRequest & r ReturnErrorOnFailure(FindAttributeMetadata(request.path, &attributeCluster, &attributeMetadata)); VerifyOrDie(attributeMetadata != nullptr); // this is the contract of FindAttributeMetadata - CHIP_ERROR err = CheckAccessPrivilege(request.path, request.subjectDescriptor); - if (err == CHIP_ERROR_ACCESS_DENIED && request.path.mExpanded) + // ACL check for non-internal requests + if (!request.operationFlags.Has(InteractionModel::OperationFlags::kInternal)) { - // return success, however without filling in the attribute value at all as there is nothing to be done here... - return CHIP_NO_ERROR; + ReturnErrorCodeIf(!request.subjectDescriptor.has_value(), CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorOnFailure(CheckAccessPrivilege(request.path, *request.subjectDescriptor)); } #if 0 diff --git a/src/app/interaction-model/OperationTypes.h b/src/app/interaction-model/OperationTypes.h index 115dc11a1ff3bc..4ef3eb51847345 100644 --- a/src/app/interaction-model/OperationTypes.h +++ b/src/app/interaction-model/OperationTypes.h @@ -37,7 +37,7 @@ enum class OperationFlags : uint32_t /// This information is available for ALL interactions: read/write/invoke struct OperationRequest { - OperationFlags operationFlags; + BitFlags operationFlags; /// Current authentication data EXCEPT for internal requests. /// - Non-internal requests MUST have this set. diff --git a/src/app/util/mock/BUILD.gn b/src/app/util/mock/BUILD.gn index da44fb84e5a809..b63c49e68f2494 100644 --- a/src/app/util/mock/BUILD.gn +++ b/src/app/util/mock/BUILD.gn @@ -25,6 +25,7 @@ source_set("mock_ember") { "MockNodeConfig.cpp", "MockNodeConfig.h", "attribute-storage.cpp", + "privilege-storage.cpp", ] public_deps = [ diff --git a/src/app/util/mock/privilege-storage.cpp b/src/app/util/mock/privilege-storage.cpp new file mode 100644 index 00000000000000..ef84a64f31dd89 --- /dev/null +++ b/src/app/util/mock/privilege-storage.cpp @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2024 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include + +chip::Access::Privilege MatterGetAccessPrivilegeForReadAttribute(chip::ClusterId cluster, chip::AttributeId attribute) +{ + return chip::Access::Privilege::kView; +} + +chip::Access::Privilege MatterGetAccessPrivilegeForWriteAttribute(chip::ClusterId cluster, chip::AttributeId attribute) +{ + return chip::Access::Privilege::kOperate; +} + +chip::Access::Privilege MatterGetAccessPrivilegeForInvokeCommand(chip::ClusterId cluster, chip::CommandId command) +{ + return chip::Access::Privilege::kOperate; +} + +chip::Access::Privilege MatterGetAccessPrivilegeForReadEvent(chip::ClusterId cluster, chip::EventId event) +{ + return chip::Access::Privilege::kView; +} From 7b6f2eb7d818bbe36c8dc7c6f9e7c82305291c8e Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 8 May 2024 11:40:09 -0400 Subject: [PATCH 010/123] More mock ember functions implemented, to make it at least link for now --- src/app/util/mock/attribute-storage.cpp | 44 +++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/app/util/mock/attribute-storage.cpp b/src/app/util/mock/attribute-storage.cpp index 131385be8373bb..e4c965e98905f4 100644 --- a/src/app/util/mock/attribute-storage.cpp +++ b/src/app/util/mock/attribute-storage.cpp @@ -152,6 +152,50 @@ uint8_t emberAfGetClusterCountForEndpoint(EndpointId endpointId) return static_cast(endpoint->clusters.size()); } +const EmberAfAttributeMetadata * emberAfLocateAttributeMetadata(EndpointId endpointId, ClusterId clusterId, AttributeId attributeId) +{ + auto ep = GetMockNodeConfig().endpointById(endpointId); + VerifyOrReturnValue(ep != nullptr, nullptr); + + auto cluster = ep->clusterById(clusterId); + VerifyOrReturnValue(cluster != nullptr, nullptr); + + auto attr = cluster->attributeById(attributeId); + VerifyOrReturnValue(attr != nullptr, nullptr); + + return &attr->attributeMetaData; +} + +const EmberAfCluster * emberAfFindClusterInType(const EmberAfEndpointType * endpointType, ClusterId clusterId, + EmberAfClusterMask mask, uint8_t * index) +{ + // This is a copy & paste implementation from ember attribute storage + // TODO: this hard-codes ember logic and is duplicated code. + uint8_t scopedIndex = 0; + + for (uint8_t i = 0; i < endpointType->clusterCount; i++) + { + const EmberAfCluster * cluster = &(endpointType->cluster[i]); + + if (mask == 0 || ((cluster->mask & mask) != 0)) + { + if (cluster->clusterId == clusterId) + { + if (index) + { + *index = scopedIndex; + } + + return cluster; + } + + scopedIndex++; + } + } + + return nullptr; +} + uint8_t emberAfClusterCount(chip::EndpointId endpoint, bool server) { return (server) ? emberAfGetClusterCountForEndpoint(endpoint) : 0; From 06e5881fbe6b84578ae1e037c6ec303ce0bbaa28 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 8 May 2024 14:41:16 -0400 Subject: [PATCH 011/123] A bit of work on AAI support. Still need to split out global attribute readers --- .../codegen-interaction-model/Model_Read.cpp | 79 ++++++++++++++++--- 1 file changed, 66 insertions(+), 13 deletions(-) diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp index c0f840438a3011..07c1ccdded48cd 100644 --- a/src/app/codegen-interaction-model/Model_Read.cpp +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -20,10 +20,13 @@ #include #include #include +#include +#include #include #include #include #include +#include namespace chip { namespace app { @@ -88,6 +91,44 @@ CHIP_ERROR CheckAccessPrivilege(const ConcreteAttributePath & path, const chip:: return CHIP_NO_ERROR; } +/// Attempts to read via an attribute access interface (AAI) +/// +/// Expected returns: +/// - CHIP_IM_GLOBAL_STATUS(UnsupportedRead) IF AND ONLY IF processing denied by the AAI (considered final) +/// - +/// +/// If it returns a VALUE, then this is a FINAL result (i.e. either failure or success). +/// If it returns std::nullopt, then there is no AAI to handle the given path +/// and processing should figure out the value otherwise (generally from other ember data) +std::optional TryReadViaAccessInterface(const ConcreteAttributePath & path, AttributeAccessInterface * aai, + AttributeValueEncoder & encoder) +{ + // Processing can happen only if an attribute access interface actually exists.. + if (aai == nullptr) + { + return std::nullopt; + } + + // TODO: AAI seems to NEVER be able to return anything else EXCEPT + // UnsupportedRead to be handled upward + + CHIP_ERROR err = aai->Read(path, encoder); + + // explict translate UnsupportedAccess to Access denied. This is to allow callers to determine a + // translation for this: usually wildcard subscriptions MAY just ignore these where as direct reads + // MUST translate them to UnsupportedAccess + ReturnErrorCodeIf(err == CHIP_IM_GLOBAL_STATUS(UnsupportedAccess), CHIP_ERROR_ACCESS_DENIED); + + if (err != CHIP_NO_ERROR) + { + return std::make_optional(err); + } + + // if no attempt was made to encode anything, assume the AAI did not even + // try to handle it, so handling has to be deferred + return encoder.TriedEncode() ? std::make_optional(CHIP_NO_ERROR) : std::nullopt; +} + } // namespace /// separated-out ReadAttribute implementation (given existing complexity) @@ -112,23 +153,35 @@ CHIP_ERROR Model::ReadAttribute(const InteractionModel::ReadAttributeRequest & r ReturnErrorOnFailure(CheckAccessPrivilege(request.path, *request.subjectDescriptor)); } -#if 0 + std::optional aai_result; + if (attributeCluster != nullptr) + { + aai_result = TryReadViaAccessInterface( + request.path, GetAttributeAccessOverride(request.path.mEndpointId, request.path.mClusterId), encoder); + } + else + { + GlobalAttributeReader aai(attributeCluster); + aai_result = TryReadViaAccessInterface(request.path, &aai, request.path.mClusterId), encoder); + } + if (aai_result.has_value()) { - // Special handling for mandatory global attributes: these are always for attribute list, using a special - // reader (which can be lightweight constructed even from nullptr). - GlobalAttributeReader reader(attributeCluster); - AttributeAccessInterface * attributeOverride = - (attributeCluster != nullptr) ? &reader : GetAttributeAccessOverride(aPath.mEndpointId, aPath.mClusterId); - if (attributeOverride) - { - bool triedEncode = false; - ReturnErrorOnFailure(ReadViaAccessInterface(aSubjectDescriptor, aIsFabricFiltered, aPath, aAttributeReports, - apEncoderState, attributeOverride, &triedEncode)); - ReturnErrorCodeIf(triedEncode, CHIP_NO_ERROR); - } + // save the reading state for later use (if lists are being decoded) + state.listEncodeStart = encoder.GetState().CurrentEncodingListIndex(); + return *aai_result; } + state.listEncodeStart = kInvalidListIndex; + + // TODO: AAI reading + // - Global attribute access override get + // - procss via access override, including using and saving back the read state + // TODO: ember reading + +#if 0 + + // Read attribute using Ember, if it doesn't have an override. From 2bc8b270824922782ffcc8635b11deffa88b5406 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 8 May 2024 14:50:50 -0400 Subject: [PATCH 012/123] Split out global AAI...for now --- .../codegen-interaction-model/Model_Read.cpp | 5 +- .../util/ember-compatibility-functions.cpp | 138 +----------------- ...mber-global-attribute-access-interface.cpp | 136 +++++++++++++++++ .../ember-global-attribute-access-interface.h | 56 +++++++ 4 files changed, 196 insertions(+), 139 deletions(-) create mode 100644 src/app/util/ember-global-attribute-access-interface.cpp create mode 100644 src/app/util/ember-global-attribute-access-interface.h diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp index 07c1ccdded48cd..ba719871b27eec 100644 --- a/src/app/codegen-interaction-model/Model_Read.cpp +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -162,8 +163,8 @@ CHIP_ERROR Model::ReadAttribute(const InteractionModel::ReadAttributeRequest & r } else { - GlobalAttributeReader aai(attributeCluster); - aai_result = TryReadViaAccessInterface(request.path, &aai, request.path.mClusterId), encoder); + Compatibility::GlobalAttributeReader aai(attributeCluster); + aai_result = TryReadViaAccessInterface(request.path, &aai, encoder); } if (aai_result.has_value()) diff --git a/src/app/util/ember-compatibility-functions.cpp b/src/app/util/ember-compatibility-functions.cpp index e87e0f564a5ff7..1e796b4d1fe486 100644 --- a/src/app/util/ember-compatibility-functions.cpp +++ b/src/app/util/ember-compatibility-functions.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -285,143 +286,6 @@ CHIP_ERROR SendFailureStatus(const ConcreteAttributePath & aPath, AttributeRepor return aAttributeReports.EncodeAttributeStatus(aPath, StatusIB(aStatus)); } -// This reader should never actually be registered; we do manual dispatch to it -// for the one attribute it handles. -class MandatoryGlobalAttributeReader : public AttributeAccessInterface -{ -public: - MandatoryGlobalAttributeReader(const EmberAfCluster * aCluster) : - AttributeAccessInterface(MakeOptional(kInvalidEndpointId), kInvalidClusterId), mCluster(aCluster) - {} - -protected: - const EmberAfCluster * mCluster; -}; - -class GlobalAttributeReader : public MandatoryGlobalAttributeReader -{ -public: - GlobalAttributeReader(const EmberAfCluster * aCluster) : MandatoryGlobalAttributeReader(aCluster) {} - - CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; - -private: - typedef CHIP_ERROR (CommandHandlerInterface::*CommandListEnumerator)(const ConcreteClusterPath & cluster, - CommandHandlerInterface::CommandIdCallback callback, - void * context); - static CHIP_ERROR EncodeCommandList(const ConcreteClusterPath & aClusterPath, AttributeValueEncoder & aEncoder, - CommandListEnumerator aEnumerator, const CommandId * aClusterCommandList); -}; - -CHIP_ERROR GlobalAttributeReader::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) -{ - using namespace Clusters::Globals::Attributes; - switch (aPath.mAttributeId) - { - case AttributeList::Id: - return aEncoder.EncodeList([this](const auto & encoder) { - const size_t count = mCluster->attributeCount; - bool addedExtraGlobals = false; - for (size_t i = 0; i < count; ++i) - { - AttributeId id = mCluster->attributes[i].attributeId; - constexpr auto lastGlobalId = GlobalAttributesNotInMetadata[ArraySize(GlobalAttributesNotInMetadata) - 1]; -#if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE - // The GlobalAttributesNotInMetadata shouldn't have any gaps in their ids here. - static_assert(lastGlobalId - GlobalAttributesNotInMetadata[0] == ArraySize(GlobalAttributesNotInMetadata) - 1, - "Ids in GlobalAttributesNotInMetadata not consecutive"); -#else - // If EventList is not supported. The GlobalAttributesNotInMetadata is missing one id here. - static_assert(lastGlobalId - GlobalAttributesNotInMetadata[0] == ArraySize(GlobalAttributesNotInMetadata), - "Ids in GlobalAttributesNotInMetadata not consecutive (except EventList)"); -#endif // CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE - if (!addedExtraGlobals && id > lastGlobalId) - { - for (const auto & globalId : GlobalAttributesNotInMetadata) - { - ReturnErrorOnFailure(encoder.Encode(globalId)); - } - addedExtraGlobals = true; - } - ReturnErrorOnFailure(encoder.Encode(id)); - } - if (!addedExtraGlobals) - { - for (const auto & globalId : GlobalAttributesNotInMetadata) - { - ReturnErrorOnFailure(encoder.Encode(globalId)); - } - } - return CHIP_NO_ERROR; - }); -#if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE - case EventList::Id: - return aEncoder.EncodeList([this](const auto & encoder) { - for (size_t i = 0; i < mCluster->eventCount; ++i) - { - ReturnErrorOnFailure(encoder.Encode(mCluster->eventList[i])); - } - return CHIP_NO_ERROR; - }); -#endif // CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE - case AcceptedCommandList::Id: - return EncodeCommandList(aPath, aEncoder, &CommandHandlerInterface::EnumerateAcceptedCommands, - mCluster->acceptedCommandList); - case GeneratedCommandList::Id: - return EncodeCommandList(aPath, aEncoder, &CommandHandlerInterface::EnumerateGeneratedCommands, - mCluster->generatedCommandList); - default: - // This function is only called if attributeCluster is non-null in - // ReadSingleClusterData, which only happens for attributes listed in - // GlobalAttributesNotInMetadata. If we reach this code, someone added - // a global attribute to that list but not the above switch. - VerifyOrDieWithMsg(false, DataManagement, "Unexpected global attribute: " ChipLogFormatMEI, - ChipLogValueMEI(aPath.mAttributeId)); - return CHIP_NO_ERROR; - } -} - -CHIP_ERROR GlobalAttributeReader::EncodeCommandList(const ConcreteClusterPath & aClusterPath, AttributeValueEncoder & aEncoder, - GlobalAttributeReader::CommandListEnumerator aEnumerator, - const CommandId * aClusterCommandList) -{ - return aEncoder.EncodeList([&](const auto & encoder) { - auto * commandHandler = - InteractionModelEngine::GetInstance()->FindCommandHandler(aClusterPath.mEndpointId, aClusterPath.mClusterId); - if (commandHandler) - { - struct Context - { - decltype(encoder) & commandIdEncoder; - CHIP_ERROR err; - } context{ encoder, CHIP_NO_ERROR }; - CHIP_ERROR err = (commandHandler->*aEnumerator)( - aClusterPath, - [](CommandId command, void * closure) -> Loop { - auto * ctx = static_cast(closure); - ctx->err = ctx->commandIdEncoder.Encode(command); - if (ctx->err != CHIP_NO_ERROR) - { - return Loop::Break; - } - return Loop::Continue; - }, - &context); - if (err != CHIP_ERROR_NOT_IMPLEMENTED) - { - return context.err; - } - // Else fall through to the list in aClusterCommandList. - } - - for (const CommandId * cmd = aClusterCommandList; cmd != nullptr && *cmd != kInvalidCommandId; cmd++) - { - ReturnErrorOnFailure(encoder.Encode(*cmd)); - } - return CHIP_NO_ERROR; - }); -} - // Helper function for trying to read an attribute value via an // AttributeAccessInterface. On failure, the read has failed. On success, the // aTriedEncode outparam is set to whether the AttributeAccessInterface tried to encode a value. diff --git a/src/app/util/ember-global-attribute-access-interface.cpp b/src/app/util/ember-global-attribute-access-interface.cpp new file mode 100644 index 00000000000000..327ab09c479512 --- /dev/null +++ b/src/app/util/ember-global-attribute-access-interface.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2021-2024 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +#include +#include + +namespace chip { +namespace app { +namespace Compatibility { + +CHIP_ERROR GlobalAttributeReader::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) +{ + using namespace Clusters::Globals::Attributes; + switch (aPath.mAttributeId) + { + case AttributeList::Id: + return aEncoder.EncodeList([this](const auto & encoder) { + const size_t count = mCluster->attributeCount; + bool addedExtraGlobals = false; + for (size_t i = 0; i < count; ++i) + { + AttributeId id = mCluster->attributes[i].attributeId; + constexpr auto lastGlobalId = GlobalAttributesNotInMetadata[ArraySize(GlobalAttributesNotInMetadata) - 1]; +#if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE + // The GlobalAttributesNotInMetadata shouldn't have any gaps in their ids here. + static_assert(lastGlobalId - GlobalAttributesNotInMetadata[0] == ArraySize(GlobalAttributesNotInMetadata) - 1, + "Ids in GlobalAttributesNotInMetadata not consecutive"); +#else + // If EventList is not supported. The GlobalAttributesNotInMetadata is missing one id here. + static_assert(lastGlobalId - GlobalAttributesNotInMetadata[0] == ArraySize(GlobalAttributesNotInMetadata), + "Ids in GlobalAttributesNotInMetadata not consecutive (except EventList)"); +#endif // CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE + if (!addedExtraGlobals && id > lastGlobalId) + { + for (const auto & globalId : GlobalAttributesNotInMetadata) + { + ReturnErrorOnFailure(encoder.Encode(globalId)); + } + addedExtraGlobals = true; + } + ReturnErrorOnFailure(encoder.Encode(id)); + } + if (!addedExtraGlobals) + { + for (const auto & globalId : GlobalAttributesNotInMetadata) + { + ReturnErrorOnFailure(encoder.Encode(globalId)); + } + } + return CHIP_NO_ERROR; + }); +#if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE + case EventList::Id: + return aEncoder.EncodeList([this](const auto & encoder) { + for (size_t i = 0; i < mCluster->eventCount; ++i) + { + ReturnErrorOnFailure(encoder.Encode(mCluster->eventList[i])); + } + return CHIP_NO_ERROR; + }); +#endif // CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE + case AcceptedCommandList::Id: + return EncodeCommandList(aPath, aEncoder, &CommandHandlerInterface::EnumerateAcceptedCommands, + mCluster->acceptedCommandList); + case GeneratedCommandList::Id: + return EncodeCommandList(aPath, aEncoder, &CommandHandlerInterface::EnumerateGeneratedCommands, + mCluster->generatedCommandList); + default: + // This function is only called if attributeCluster is non-null in + // ReadSingleClusterData, which only happens for attributes listed in + // GlobalAttributesNotInMetadata. If we reach this code, someone added + // a global attribute to that list but not the above switch. + VerifyOrDieWithMsg(false, DataManagement, "Unexpected global attribute: " ChipLogFormatMEI, + ChipLogValueMEI(aPath.mAttributeId)); + return CHIP_NO_ERROR; + } +} + +CHIP_ERROR GlobalAttributeReader::EncodeCommandList(const ConcreteClusterPath & aClusterPath, AttributeValueEncoder & aEncoder, + GlobalAttributeReader::CommandListEnumerator aEnumerator, + const CommandId * aClusterCommandList) +{ + return aEncoder.EncodeList([&](const auto & encoder) { + auto * commandHandler = + InteractionModelEngine::GetInstance()->FindCommandHandler(aClusterPath.mEndpointId, aClusterPath.mClusterId); + if (commandHandler) + { + struct Context + { + decltype(encoder) & commandIdEncoder; + CHIP_ERROR err; + } context{ encoder, CHIP_NO_ERROR }; + CHIP_ERROR err = (commandHandler->*aEnumerator)( + aClusterPath, + [](CommandId command, void * closure) -> Loop { + auto * ctx = static_cast(closure); + ctx->err = ctx->commandIdEncoder.Encode(command); + if (ctx->err != CHIP_NO_ERROR) + { + return Loop::Break; + } + return Loop::Continue; + }, + &context); + if (err != CHIP_ERROR_NOT_IMPLEMENTED) + { + return context.err; + } + // Else fall through to the list in aClusterCommandList. + } + + for (const CommandId * cmd = aClusterCommandList; cmd != nullptr && *cmd != kInvalidCommandId; cmd++) + { + ReturnErrorOnFailure(encoder.Encode(*cmd)); + } + return CHIP_NO_ERROR; + }); +} + +} // namespace Compatibility +} // namespace app +} // namespace chip diff --git a/src/app/util/ember-global-attribute-access-interface.h b/src/app/util/ember-global-attribute-access-interface.h new file mode 100644 index 00000000000000..d17e1b286dbd81 --- /dev/null +++ b/src/app/util/ember-global-attribute-access-interface.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021-2024 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include + +namespace chip { +namespace app { +namespace Compatibility { + +// This reader should never actually be registered; we do manual dispatch to it +// for the one attribute it handles. +class MandatoryGlobalAttributeReader : public AttributeAccessInterface +{ +public: + MandatoryGlobalAttributeReader(const EmberAfCluster * aCluster) : + AttributeAccessInterface(MakeOptional(kInvalidEndpointId), kInvalidClusterId), mCluster(aCluster) + {} + +protected: + const EmberAfCluster * mCluster; +}; + +class GlobalAttributeReader : public MandatoryGlobalAttributeReader +{ +public: + GlobalAttributeReader(const EmberAfCluster * aCluster) : MandatoryGlobalAttributeReader(aCluster) {} + + CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; + +private: + typedef CHIP_ERROR (CommandHandlerInterface::*CommandListEnumerator)(const ConcreteClusterPath & cluster, + CommandHandlerInterface::CommandIdCallback callback, + void * context); + static CHIP_ERROR EncodeCommandList(const ConcreteClusterPath & aClusterPath, AttributeValueEncoder & aEncoder, + CommandListEnumerator aEnumerator, const CommandId * aClusterCommandList); +}; + +} // namespace Compatibility +} // namespace app +} // namespace chip From 8acf68c83f1c7a64b7deb877874be50e75e86b56 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 8 May 2024 14:52:47 -0400 Subject: [PATCH 013/123] Add global attribute access interface as part of ember data model definitions ... they seem needed --- src/app/chip_data_model.cmake | 1 + src/app/chip_data_model.gni | 1 + 2 files changed, 2 insertions(+) diff --git a/src/app/chip_data_model.cmake b/src/app/chip_data_model.cmake index 5573eeb275acd7..5c4357c5de6c9d 100644 --- a/src/app/chip_data_model.cmake +++ b/src/app/chip_data_model.cmake @@ -144,6 +144,7 @@ function(chip_configure_data_model APP_TARGET) ${CHIP_APP_BASE_DIR}/icd/server/ICDConfigurationData.cpp ${CHIP_APP_BASE_DIR}/util/DataModelHandler.cpp ${CHIP_APP_BASE_DIR}/util/ember-compatibility-functions.cpp + ${CHIP_APP_BASE_DIR}/util/ember-global-attribute-access-interface.cpp ${CHIP_APP_BASE_DIR}/util/generic-callback-stubs.cpp ${CHIP_APP_BASE_DIR}/util/privilege-storage.cpp ${CHIP_APP_BASE_DIR}/util/util.cpp diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni index c8a30b1b5bd36a..a59405e9989c9f 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -209,6 +209,7 @@ template("chip_data_model") { "${_app_root}/util/attribute-storage.cpp", "${_app_root}/util/attribute-table.cpp", "${_app_root}/util/ember-compatibility-functions.cpp", + "${_app_root}/util/ember-global-attribute-access-interface.cpp", "${_app_root}/util/util.cpp", ] } From 2408818fc38b4c0faca961623be758fbb96c3412 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 8 May 2024 15:12:44 -0400 Subject: [PATCH 014/123] Drop RequiredPrivilegeStubs ... those are odd and if you use mock ember you may as well mock these out as well --- .../codegen-interaction-model/tests/BUILD.gn | 15 +++++++- src/app/tests/BUILD.gn | 1 - src/app/tests/integration/BUILD.gn | 2 - .../integration/RequiredPrivilegeStubs.cpp | 38 ------------------- src/app/util/mock/privilege-storage.cpp | 12 ++++-- 5 files changed, 22 insertions(+), 46 deletions(-) delete mode 100644 src/app/tests/integration/RequiredPrivilegeStubs.cpp diff --git a/src/app/codegen-interaction-model/tests/BUILD.gn b/src/app/codegen-interaction-model/tests/BUILD.gn index f543bc8d0cc24b..34538262ee63c2 100644 --- a/src/app/codegen-interaction-model/tests/BUILD.gn +++ b/src/app/codegen-interaction-model/tests/BUILD.gn @@ -15,13 +15,26 @@ import("//build_overrides/chip.gni") import("${chip_root}/build/chip/chip_test_suite.gni") import("${chip_root}/src/app/codegen-interaction-model/model.gni") +source_set("ember_extra_files") { + sources = [ + # This IS TERRIBLE, however we want to pretent AAI exists for global + # items + "${chip_root}/src/app/util/ember-global-attribute-access-interface.cpp", + ] + + public_deps = [ "${chip_root}/src/app/util/mock:mock_ember" ] +} + source_set("mock_model") { sources = codegen_interaction_model_SOURCES public_deps = codegen_interaction_model_PUBLIC_DEPS # this ties in the codegen model to an actual ember implementation - public_deps += [ "${chip_root}/src/app/util/mock:mock_ember" ] + public_deps += [ + ":ember_extra_files", + "${chip_root}/src/app/util/mock:mock_ember", + ] } chip_test_suite("tests") { diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index 49196f2fdaaf8f..df6737a713e6e4 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -29,7 +29,6 @@ static_library("helpers") { "${chip_root}/src/app/reporting/tests/MockReportScheduler.cpp", "AppTestContext.cpp", "AppTestContext.h", - "integration/RequiredPrivilegeStubs.cpp", ] cflags = [ "-Wconversion" ] diff --git a/src/app/tests/integration/BUILD.gn b/src/app/tests/integration/BUILD.gn index da0db88682f180..49aea9034e9766 100644 --- a/src/app/tests/integration/BUILD.gn +++ b/src/app/tests/integration/BUILD.gn @@ -38,7 +38,6 @@ source_set("common") { executable("chip-im-initiator") { sources = [ "${chip_root}/src/app/reporting/tests/MockReportScheduler.cpp", - "RequiredPrivilegeStubs.cpp", "chip_im_initiator.cpp", ] @@ -61,7 +60,6 @@ executable("chip-im-responder") { "${chip_root}/src/app/reporting/tests/MockReportScheduler.cpp", "MockEvents.cpp", "MockEvents.h", - "RequiredPrivilegeStubs.cpp", "chip_im_responder.cpp", ] diff --git a/src/app/tests/integration/RequiredPrivilegeStubs.cpp b/src/app/tests/integration/RequiredPrivilegeStubs.cpp deleted file mode 100644 index 2cc23056e5d34a..00000000000000 --- a/src/app/tests/integration/RequiredPrivilegeStubs.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/* - * - * Copyright (c) 2022 Project CHIP Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -chip::Access::Privilege MatterGetAccessPrivilegeForReadAttribute(chip::ClusterId cluster, chip::AttributeId attribute) -{ - return chip::Access::Privilege::kAdminister; -} - -chip::Access::Privilege MatterGetAccessPrivilegeForWriteAttribute(chip::ClusterId cluster, chip::AttributeId attribute) -{ - return chip::Access::Privilege::kAdminister; -} - -chip::Access::Privilege MatterGetAccessPrivilegeForInvokeCommand(chip::ClusterId cluster, chip::CommandId command) -{ - return chip::Access::Privilege::kAdminister; -} - -chip::Access::Privilege MatterGetAccessPrivilegeForReadEvent(chip::ClusterId cluster, chip::EventId event) -{ - return chip::Access::Privilege::kAdminister; -} diff --git a/src/app/util/mock/privilege-storage.cpp b/src/app/util/mock/privilege-storage.cpp index ef84a64f31dd89..26ff8967944e12 100644 --- a/src/app/util/mock/privilege-storage.cpp +++ b/src/app/util/mock/privilege-storage.cpp @@ -16,22 +16,26 @@ #include #include +// Privilege mocks here are MUCH more strict so that +// testing code can generally validatate access without something +// being permissive like kView. + chip::Access::Privilege MatterGetAccessPrivilegeForReadAttribute(chip::ClusterId cluster, chip::AttributeId attribute) { - return chip::Access::Privilege::kView; + return chip::Access::Privilege::kAdminister; } chip::Access::Privilege MatterGetAccessPrivilegeForWriteAttribute(chip::ClusterId cluster, chip::AttributeId attribute) { - return chip::Access::Privilege::kOperate; + return chip::Access::Privilege::kAdminister; } chip::Access::Privilege MatterGetAccessPrivilegeForInvokeCommand(chip::ClusterId cluster, chip::CommandId command) { - return chip::Access::Privilege::kOperate; + return chip::Access::Privilege::kAdminister; } chip::Access::Privilege MatterGetAccessPrivilegeForReadEvent(chip::ClusterId cluster, chip::EventId event) { - return chip::Access::Privilege::kView; + return chip::Access::Privilege::kAdminister; } From c43ae2ad7b2e4588dfa7211e3522c23dbc88e595 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 8 May 2024 15:12:58 -0400 Subject: [PATCH 015/123] Restyle --- src/app/util/ember-compatibility-functions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/util/ember-compatibility-functions.cpp b/src/app/util/ember-compatibility-functions.cpp index 1e796b4d1fe486..8ebe51723e2f07 100644 --- a/src/app/util/ember-compatibility-functions.cpp +++ b/src/app/util/ember-compatibility-functions.cpp @@ -32,8 +32,8 @@ #include #include #include -#include #include +#include #include #include #include From ce42236ebd121035c9e9dfe643e388ac5dcb20f6 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 8 May 2024 15:45:13 -0400 Subject: [PATCH 016/123] Added sufficient overrides for things to compile ... this is a MESS --- .../codegen-interaction-model/tests/BUILD.gn | 6 +- .../InteractionModelTemporaryOverrides.cpp | 81 +++++++++++++++++++ .../tests/TestCodegenModelViaMocks.cpp | 1 - 3 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp diff --git a/src/app/codegen-interaction-model/tests/BUILD.gn b/src/app/codegen-interaction-model/tests/BUILD.gn index 34538262ee63c2..057bc3f5691f53 100644 --- a/src/app/codegen-interaction-model/tests/BUILD.gn +++ b/src/app/codegen-interaction-model/tests/BUILD.gn @@ -20,9 +20,13 @@ source_set("ember_extra_files") { # This IS TERRIBLE, however we want to pretent AAI exists for global # items "${chip_root}/src/app/util/ember-global-attribute-access-interface.cpp", + "InteractionModelTemporaryOverrides.cpp", ] - public_deps = [ "${chip_root}/src/app/util/mock:mock_ember" ] + public_deps = [ + "${chip_root}/src/app/util/mock:mock_ember", + "${chip_root}/src/protocols", + ] } source_set("mock_model") { diff --git a/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp b/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp new file mode 100644 index 00000000000000..1b10a03fa4d612 --- /dev/null +++ b/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include +#include +#include + +// TODO: most of the functions here are part of EmberCompatibilityFunctions and is NOT decoupled +// from IM current, but it SHOULD be +namespace chip { +namespace app { + +bool ConcreteAttributePathExists(const ConcreteAttributePath & aPath) +{ + // TODO: this is just a noop which may be potentially invalid + return true; +} + +bool IsClusterDataVersionEqual(const ConcreteClusterPath & aConcreteClusterPath, DataVersion aRequiredVersion) +{ + // TODO: this is just a noop which may be potentially invalid + return true; +} + +const EmberAfAttributeMetadata * GetAttributeMetadata(const ConcreteAttributePath & aPath) +{ + return emberAfLocateAttributeMetadata(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId); +} + +Protocols::InteractionModel::Status ServerClusterCommandExists(const ConcreteCommandPath & aCommandPath) +{ + // TODO: this is just a noop which may be potentially invalid + return Protocols::InteractionModel::Status::Success; +} + +Protocols::InteractionModel::Status CheckEventSupportStatus(const ConcreteEventPath & aPath) +{ + // TODO: this is just a noop which may be potentially invalid + return Protocols::InteractionModel::Status::Success; +} + +CHIP_ERROR WriteSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, const ConcreteDataAttributePath & aPath, + TLV::TLVReader & aReader, WriteHandler * apWriteHandler) +{ + // this is just to get things to compile. eventually this method should NOT be used + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered, + const ConcreteReadAttributePath & aPath, AttributeReportIBs::Builder & aAttributeReports, + AttributeEncodeState * apEncoderState) +{ + // this is just to get things to compile. eventually this method should NOT be used + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +void DispatchSingleClusterCommand(const ConcreteCommandPath & aRequestCommandPath, chip::TLV::TLVReader & aReader, + CommandHandler * apCommandObj) +{ + // TODO: total hardcoded noop +} + +} // namespace app +} // namespace chip diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index a8b69e797a0d6f..f3438e1aeb2166 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -1,5 +1,4 @@ /* - * * Copyright (c) 2024 Project CHIP Authors * All rights reserved. * From 53ad55685915b2f2428b8184ba7f1285e35f0574 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 8 May 2024 17:19:22 -0400 Subject: [PATCH 017/123] Some more changes to compile --- .../codegen-interaction-model/Model_Read.cpp | 128 +++++++++++++++++- .../include/zap-generated/endpoint_config.h | 2 + 2 files changed, 125 insertions(+), 5 deletions(-) diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp index ba719871b27eec..898ccf1335a98e 100644 --- a/src/app/codegen-interaction-model/Model_Read.cpp +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -24,15 +24,110 @@ #include #include #include +#include #include #include #include #include +#include namespace chip { namespace app { namespace CodegenDataModel { namespace { +// On some apps, ATTRIBUTE_LARGEST can as small as 3, making compiler unhappy since data[kAttributeReadBufferSize] cannot hold +// uint64_t. Make kAttributeReadBufferSize at least 8 so it can fit all basic types. +constexpr size_t kAttributeReadBufferSize = (ATTRIBUTE_LARGEST >= 8 ? ATTRIBUTE_LARGEST : 8); +uint8_t attributeReadBufferSpace[kAttributeReadBufferSize]; + +// BasicType maps the type to basic int(8|16|32|64)(s|u) types. +// TODO: this code should be SHARED! +EmberAfAttributeType BaseType(EmberAfAttributeType type) +{ + switch (type) + { + case ZCL_ACTION_ID_ATTRIBUTE_TYPE: // Action Id + case ZCL_FABRIC_IDX_ATTRIBUTE_TYPE: // Fabric Index + case ZCL_BITMAP8_ATTRIBUTE_TYPE: // 8-bit bitmap + case ZCL_ENUM8_ATTRIBUTE_TYPE: // 8-bit enumeration + case ZCL_STATUS_ATTRIBUTE_TYPE: // Status Code + case ZCL_PERCENT_ATTRIBUTE_TYPE: // Percentage + static_assert(std::is_same::value, + "chip::Percent is expected to be uint8_t, change this when necessary"); + return ZCL_INT8U_ATTRIBUTE_TYPE; + + case ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE: // Endpoint Number + case ZCL_GROUP_ID_ATTRIBUTE_TYPE: // Group Id + case ZCL_VENDOR_ID_ATTRIBUTE_TYPE: // Vendor Id + case ZCL_ENUM16_ATTRIBUTE_TYPE: // 16-bit enumeration + case ZCL_BITMAP16_ATTRIBUTE_TYPE: // 16-bit bitmap + case ZCL_PERCENT100THS_ATTRIBUTE_TYPE: // 100ths of a percent + static_assert(std::is_same::value, + "chip::EndpointId is expected to be uint16_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::GroupId is expected to be uint16_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::Percent100ths is expected to be uint16_t, change this when necessary"); + return ZCL_INT16U_ATTRIBUTE_TYPE; + + case ZCL_CLUSTER_ID_ATTRIBUTE_TYPE: // Cluster Id + case ZCL_ATTRIB_ID_ATTRIBUTE_TYPE: // Attribute Id + case ZCL_FIELD_ID_ATTRIBUTE_TYPE: // Field Id + case ZCL_EVENT_ID_ATTRIBUTE_TYPE: // Event Id + case ZCL_COMMAND_ID_ATTRIBUTE_TYPE: // Command Id + case ZCL_TRANS_ID_ATTRIBUTE_TYPE: // Transaction Id + case ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE: // Device Type Id + case ZCL_DATA_VER_ATTRIBUTE_TYPE: // Data Version + case ZCL_BITMAP32_ATTRIBUTE_TYPE: // 32-bit bitmap + case ZCL_EPOCH_S_ATTRIBUTE_TYPE: // Epoch Seconds + case ZCL_ELAPSED_S_ATTRIBUTE_TYPE: // Elapsed Seconds + static_assert(std::is_same::value, + "chip::Cluster is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::AttributeId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::AttributeId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::EventId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::CommandId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::TransactionId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::DeviceTypeId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::DataVersion is expected to be uint32_t, change this when necessary"); + return ZCL_INT32U_ATTRIBUTE_TYPE; + + case ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE: // Amperage milliamps + case ZCL_ENERGY_MWH_ATTRIBUTE_TYPE: // Energy milliwatt-hours + case ZCL_POWER_MW_ATTRIBUTE_TYPE: // Power milliwatts + case ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE: // Voltage millivolts + return ZCL_INT64S_ATTRIBUTE_TYPE; + + case ZCL_EVENT_NO_ATTRIBUTE_TYPE: // Event Number + case ZCL_FABRIC_ID_ATTRIBUTE_TYPE: // Fabric Id + case ZCL_NODE_ID_ATTRIBUTE_TYPE: // Node Id + case ZCL_BITMAP64_ATTRIBUTE_TYPE: // 64-bit bitmap + case ZCL_EPOCH_US_ATTRIBUTE_TYPE: // Epoch Microseconds + case ZCL_POSIX_MS_ATTRIBUTE_TYPE: // POSIX Milliseconds + case ZCL_SYSTIME_MS_ATTRIBUTE_TYPE: // System time Milliseconds + case ZCL_SYSTIME_US_ATTRIBUTE_TYPE: // System time Microseconds + static_assert(std::is_same::value, + "chip::EventNumber is expected to be uint64_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::FabricId is expected to be uint64_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::NodeId is expected to be uint64_t, change this when necessary"); + return ZCL_INT64U_ATTRIBUTE_TYPE; + + case ZCL_TEMPERATURE_ATTRIBUTE_TYPE: // Temperature + return ZCL_INT16S_ATTRIBUTE_TYPE; + + default: + return type; + } +} // Will set at most one of the out-params (aAttributeCluster or // aAttributeMetadata) to non-null. Both null means attribute not supported, @@ -175,15 +270,38 @@ CHIP_ERROR Model::ReadAttribute(const InteractionModel::ReadAttributeRequest & r } state.listEncodeStart = kInvalidListIndex; - // TODO: AAI reading - // - Global attribute access override get - // - procss via access override, including using and saving back the read state - // TODO: ember reading + // At this point, we have to use ember directly to read the data. + // Available methods: + // - emberAfReadOrWriteAttribute should do the processing of read vs write + // - we write to a fixed buffer, so the actual type has to be determined + // by the attributeType (base type) -#if 0 + // TODO: ember reading + // + EmberAfAttributeSearchRecord record; + record.endpoint = request.path.mEndpointId; + record.clusterId = request.path.mClusterId; + record.attributeId = request.path.mAttributeId; + Protocols::InteractionModel::Status status = + emAfReadOrWriteAttribute(&record, &attributeMetadata, attributeReadBufferSpace, sizeof(attributeReadBufferSpace), + /* write = */ false); + + if (status != Protocols::InteractionModel::Status::Success) + { + return ChipError(ChipError::SdkPart::kIMGlobalStatus, to_underlying(status), __FILE__, __LINE__); + } + switch (BaseType(attributeMetadata->attributeType)) + { + // FIXME: implement + default: + return CHIP_IM_GLOBAL_STATUS(UnsupportedRead); + } + // TODO: return no error once we have a propper impl + return CHIP_ERROR_NOT_IMPLEMENTED; +#if 0 // Read attribute using Ember, if it doesn't have an override. TLV::TLVWriter backup; diff --git a/src/app/util/mock/include/zap-generated/endpoint_config.h b/src/app/util/mock/include/zap-generated/endpoint_config.h index 33d1e87e320896..620e6e29880f36 100644 --- a/src/app/util/mock/include/zap-generated/endpoint_config.h +++ b/src/app/util/mock/include/zap-generated/endpoint_config.h @@ -1,2 +1,4 @@ // Number of fixed endpoints #define FIXED_ENDPOINT_COUNT (3) + +#define ATTRIBUTE_LARGEST (1003) From 09f1e9ebb017cb9ee419fe7c25273fc5aa8a870d Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 8 May 2024 17:24:01 -0400 Subject: [PATCH 018/123] Things link now ... but boy do we have many workarounds... --- .../InteractionModelTemporaryOverrides.cpp | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp b/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp index 1b10a03fa4d612..863890eb36474f 100644 --- a/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp +++ b/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp @@ -22,11 +22,14 @@ #include #include +using chip::Protocols::InteractionModel::Status; + // TODO: most of the functions here are part of EmberCompatibilityFunctions and is NOT decoupled // from IM current, but it SHOULD be namespace chip { namespace app { + bool ConcreteAttributePathExists(const ConcreteAttributePath & aPath) { // TODO: this is just a noop which may be potentially invalid @@ -44,16 +47,16 @@ const EmberAfAttributeMetadata * GetAttributeMetadata(const ConcreteAttributePat return emberAfLocateAttributeMetadata(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId); } -Protocols::InteractionModel::Status ServerClusterCommandExists(const ConcreteCommandPath & aCommandPath) +Status ServerClusterCommandExists(const ConcreteCommandPath & aCommandPath) { // TODO: this is just a noop which may be potentially invalid - return Protocols::InteractionModel::Status::Success; + return Status::Success; } -Protocols::InteractionModel::Status CheckEventSupportStatus(const ConcreteEventPath & aPath) +Status CheckEventSupportStatus(const ConcreteEventPath & aPath) { // TODO: this is just a noop which may be potentially invalid - return Protocols::InteractionModel::Status::Success; + return Status::Success; } CHIP_ERROR WriteSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, const ConcreteDataAttributePath & aPath, @@ -79,3 +82,14 @@ void DispatchSingleClusterCommand(const ConcreteCommandPath & aRequestCommandPat } // namespace app } // namespace chip + +Status emAfReadOrWriteAttribute(const EmberAfAttributeSearchRecord * attRecord, const EmberAfAttributeMetadata ** metadata, + uint8_t * buffer, uint16_t readLength, bool write) +{ + + // FIXME: this is supposed to be an ember implementation + // however mock library SHOULD be able to implement it ... it is unclear + // to me why we do not have this... + return Status::NotFound; +} + From 22417f3772250d734053b60ee972b67783960064 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 8 May 2024 17:27:12 -0400 Subject: [PATCH 019/123] Remove double return --- src/app/codegen-interaction-model/Model_Read.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp index 898ccf1335a98e..3bf3ae7b7fbd8b 100644 --- a/src/app/codegen-interaction-model/Model_Read.cpp +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -542,9 +542,6 @@ CHIP_ERROR Model::ReadAttribute(const InteractionModel::ReadAttributeRequest & r return SendFailureStatus(aPath, aAttributeReports, status, &backup); #endif - - // TODO: this needs an implementation - return CHIP_ERROR_NOT_IMPLEMENTED; } } // namespace CodegenDataModel From 1a97cd4b61f4eb215c40c2ce3bc1317bf9af67b3 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 8 May 2024 17:27:25 -0400 Subject: [PATCH 020/123] Restyle --- .../tests/InteractionModelTemporaryOverrides.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp b/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp index 863890eb36474f..4c6656d75fcbdc 100644 --- a/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp +++ b/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp @@ -29,7 +29,6 @@ using chip::Protocols::InteractionModel::Status; namespace chip { namespace app { - bool ConcreteAttributePathExists(const ConcreteAttributePath & aPath) { // TODO: this is just a noop which may be potentially invalid @@ -92,4 +91,3 @@ Status emAfReadOrWriteAttribute(const EmberAfAttributeSearchRecord * attRecord, // to me why we do not have this... return Status::NotFound; } - From 0eea694402a5b64649c87b75bfbb1b125c952a40 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 9 May 2024 11:24:07 -0400 Subject: [PATCH 021/123] Full ember handling --- .../codegen-interaction-model/Model_Read.cpp | 419 +++++++----------- .../InteractionModelTemporaryOverrides.cpp | 1 + 2 files changed, 167 insertions(+), 253 deletions(-) diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp index 3bf3ae7b7fbd8b..483bda8538ae8e 100644 --- a/src/app/codegen-interaction-model/Model_Read.cpp +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include +#include #include #include @@ -22,17 +22,39 @@ #include #include #include +#include #include #include +#include +#include +#include #include +#include #include #include #include -#include +#include +#include +#include + #include namespace chip { namespace app { +#if 0 +namespace DataModel { + +// Ensure odd sized integers are not considered fabric scoped for the purpose of +// encoding via AttributeValueEncoder (they are just integers) +template +class IsFabricScoped> +{ +public: + static constexpr bool value = false; +}; +} // namespace DataModel +#endif + namespace CodegenDataModel { namespace { // On some apps, ATTRIBUTE_LARGEST can as small as 3, making compiler unhappy since data[kAttributeReadBufferSize] cannot hold @@ -225,6 +247,147 @@ std::optional TryReadViaAccessInterface(const ConcreteAttributePath return encoder.TriedEncode() ? std::make_optional(CHIP_NO_ERROR) : std::nullopt; } +static constexpr uint8_t kEmberStringNullLength = 0xFF; +static constexpr uint16_t kEmberLongStringNullLength = 0xFFFF; + +template +std::optional ExtractEmberShortString(ByteSpan data) +{ + uint8_t len = data[0]; + + if (len == kEmberStringNullLength) + { + return std::nullopt; + } + VerifyOrDie(static_cast(len + 1) <= data.size()); + + return std::make_optional(reinterpret_cast(data.data() + 1), len); +} + +template +std::optional ExtractEmberLongString(ByteSpan data) +{ + uint16_t len; + + VerifyOrDie(sizeof(len) <= data.size()); + memcpy(&len, data.data(), sizeof(len)); + + if (len == kEmberLongStringNullLength) + { + return std::nullopt; + } + VerifyOrDie(static_cast(len + 1) <= data.size()); + + return std::make_optional(reinterpret_cast(data.data() + 1), len); +} + +// TODO: string handling: +// - length (1 or 2 bytes, null is 0xFF or 0xFFFF) +// - either Put(ByteSpan) or PutString() -> this seems to need a Encoder equivalent? +// - Span calls EncodeString +// - ByteSpan calls Encode +// + +template +CHIP_ERROR EncodeStringLike(std::optional data, bool isNullable, AttributeValueEncoder & encoder) +{ + if (!data.has_value()) + { + if (isNullable) + { + return encoder.EncodeNull(); + } + return CHIP_ERROR_INCORRECT_STATE; + } + + // encode value as-is + return encoder.Encode(*data); +} + +template +CHIP_ERROR EncodeFromSpan(ByteSpan data, bool isNullable, AttributeValueEncoder & encoder) +{ + typename NumericAttributeTraits::StorageType value; + + VerifyOrReturnError(data.size() >= sizeof(value), CHIP_ERROR_INVALID_ARGUMENT); + memcpy(&value, data.data(), sizeof(value)); + + if (isNullable && NumericAttributeTraits::IsNullValue(value)) + { + return encoder.EncodeNull(); + } + + if (!NumericAttributeTraits::CanRepresentValue(isNullable, value)) + { + return CHIP_ERROR_INCORRECT_STATE; + } + + return encoder.Encode(NumericAttributeTraits::StorageToWorking(value)); +} + +/// Converts raw ember data from `data` into the encoder +CHIP_ERROR EncodeEmberValue(ByteSpan data, const EmberAfAttributeMetadata * metadata, AttributeValueEncoder & encoder) +{ + VerifyOrReturnError(metadata != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + const bool isNullable = metadata->IsNullable(); + + switch (BaseType(metadata->attributeType)) + { + case ZCL_NO_DATA_ATTRIBUTE_TYPE: // No data + return encoder.EncodeNull(); + case ZCL_BOOLEAN_ATTRIBUTE_TYPE: // Boolean + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_INT8U_ATTRIBUTE_TYPE: // Unsigned 8-bit integer + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_INT16U_ATTRIBUTE_TYPE: // Unsigned 16-bit integer + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_INT24U_ATTRIBUTE_TYPE: // Unsigned 24-bit integer + return EncodeFromSpan>(data, isNullable, encoder); + case ZCL_INT32U_ATTRIBUTE_TYPE: // Unsigned 32-bit integer + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_INT40U_ATTRIBUTE_TYPE: // Unsigned 40-bit integer + return EncodeFromSpan>(data, isNullable, encoder); + case ZCL_INT48U_ATTRIBUTE_TYPE: // Unsigned 48-bit integer + return EncodeFromSpan>(data, isNullable, encoder); + case ZCL_INT56U_ATTRIBUTE_TYPE: // Unsigned 56-bit integer + return EncodeFromSpan>(data, isNullable, encoder); + case ZCL_INT64U_ATTRIBUTE_TYPE: // Unsigned 64-bit integer + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_INT8S_ATTRIBUTE_TYPE: // Signed 8-bit integer + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_INT16S_ATTRIBUTE_TYPE: // Signed 16-bit integer + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_INT24S_ATTRIBUTE_TYPE: // Signed 24-bit integer + return EncodeFromSpan>(data, isNullable, encoder); + case ZCL_INT32S_ATTRIBUTE_TYPE: // Signed 32-bit integer + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_INT40S_ATTRIBUTE_TYPE: // Signed 40-bit integer + return EncodeFromSpan>(data, isNullable, encoder); + case ZCL_INT48S_ATTRIBUTE_TYPE: // Signed 48-bit integer + return EncodeFromSpan>(data, isNullable, encoder); + case ZCL_INT56S_ATTRIBUTE_TYPE: // Signed 56-bit integer + return EncodeFromSpan>(data, isNullable, encoder); + case ZCL_INT64S_ATTRIBUTE_TYPE: // Signed 64-bit integer + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_SINGLE_ATTRIBUTE_TYPE: // 32-bit float + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_DOUBLE_ATTRIBUTE_TYPE: // 64-bit float + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_CHAR_STRING_ATTRIBUTE_TYPE: // Char string + return EncodeStringLike(ExtractEmberShortString(data), isNullable, encoder); + case ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE: + return EncodeStringLike(ExtractEmberLongString(data), isNullable, encoder); + case ZCL_OCTET_STRING_ATTRIBUTE_TYPE: // Octet string + return EncodeStringLike(ExtractEmberShortString(data), isNullable, encoder); + case ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE: + return EncodeStringLike(ExtractEmberLongString(data), isNullable, encoder); + default: + ChipLogError(DataManagement, "Attribute type 0x%x not handled", static_cast(metadata->attributeType)); + return CHIP_IM_GLOBAL_STATUS(UnsupportedRead); + } +} + } // namespace /// separated-out ReadAttribute implementation (given existing complexity) @@ -291,257 +454,7 @@ CHIP_ERROR Model::ReadAttribute(const InteractionModel::ReadAttributeRequest & r return ChipError(ChipError::SdkPart::kIMGlobalStatus, to_underlying(status), __FILE__, __LINE__); } - switch (BaseType(attributeMetadata->attributeType)) - { - // FIXME: implement - default: - return CHIP_IM_GLOBAL_STATUS(UnsupportedRead); - } - - // TODO: return no error once we have a propper impl - return CHIP_ERROR_NOT_IMPLEMENTED; - -#if 0 - // Read attribute using Ember, if it doesn't have an override. - - TLV::TLVWriter backup; - aAttributeReports.Checkpoint(backup); - - AttributeReportIB::Builder & attributeReport = aAttributeReports.CreateAttributeReport(); - ReturnErrorOnFailure(aAttributeReports.GetError()); - - AttributeDataIB::Builder & attributeDataIBBuilder = attributeReport.CreateAttributeData(); - ReturnErrorOnFailure(attributeDataIBBuilder.GetError()); - - DataVersion version = 0; - ReturnErrorOnFailure(ReadClusterDataVersion(aPath, version)); - attributeDataIBBuilder.DataVersion(version); - ReturnErrorOnFailure(attributeDataIBBuilder.GetError()); - - AttributePathIB::Builder & attributePathIBBuilder = attributeDataIBBuilder.CreatePath(); - ReturnErrorOnFailure(attributeDataIBBuilder.GetError()); - - CHIP_ERROR err = attributePathIBBuilder.Endpoint(aPath.mEndpointId) - .Cluster(aPath.mClusterId) - .Attribute(aPath.mAttributeId) - .EndOfAttributePathIB(); - ReturnErrorOnFailure(err); - - EmberAfAttributeSearchRecord record; - record.endpoint = aPath.mEndpointId; - record.clusterId = aPath.mClusterId; - record.attributeId = aPath.mAttributeId; - Status status = emAfReadOrWriteAttribute(&record, &attributeMetadata, attributeData, sizeof(attributeData), - /* write = */ false); - - if (status == Status::Success) - { - EmberAfAttributeType attributeType = attributeMetadata->attributeType; - bool isNullable = attributeMetadata->IsNullable(); - TLV::TLVWriter * writer = attributeDataIBBuilder.GetWriter(); - VerifyOrReturnError(writer != nullptr, CHIP_NO_ERROR); - TLV::Tag tag = TLV::ContextTag(AttributeDataIB::Tag::kData); - switch (BaseType(attributeType)) - { - case ZCL_NO_DATA_ATTRIBUTE_TYPE: // No data - ReturnErrorOnFailure(writer->PutNull(tag)); - break; - case ZCL_BOOLEAN_ATTRIBUTE_TYPE: // Boolean - ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); - break; - case ZCL_INT8U_ATTRIBUTE_TYPE: // Unsigned 8-bit integer - ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); - break; - case ZCL_INT16U_ATTRIBUTE_TYPE: // Unsigned 16-bit integer - { - ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); - break; - } - case ZCL_INT24U_ATTRIBUTE_TYPE: // Unsigned 24-bit integer - { - using IntType = OddSizedInteger<3, false>; - ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); - break; - } - case ZCL_INT32U_ATTRIBUTE_TYPE: // Unsigned 32-bit integer - { - ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); - break; - } - case ZCL_INT40U_ATTRIBUTE_TYPE: // Unsigned 40-bit integer - { - using IntType = OddSizedInteger<5, false>; - ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); - break; - } - case ZCL_INT48U_ATTRIBUTE_TYPE: // Unsigned 48-bit integer - { - using IntType = OddSizedInteger<6, false>; - ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); - break; - } - case ZCL_INT56U_ATTRIBUTE_TYPE: // Unsigned 56-bit integer - { - using IntType = OddSizedInteger<7, false>; - ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); - break; - } - case ZCL_INT64U_ATTRIBUTE_TYPE: // Unsigned 64-bit integer - { - ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); - break; - } - case ZCL_INT8S_ATTRIBUTE_TYPE: // Signed 8-bit integer - { - ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); - break; - } - case ZCL_INT16S_ATTRIBUTE_TYPE: // Signed 16-bit integer - { - ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); - break; - } - case ZCL_INT24S_ATTRIBUTE_TYPE: // Signed 24-bit integer - { - using IntType = OddSizedInteger<3, true>; - ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); - break; - } - case ZCL_INT32S_ATTRIBUTE_TYPE: // Signed 32-bit integer - { - ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); - break; - } - case ZCL_INT40S_ATTRIBUTE_TYPE: // Signed 40-bit integer - { - using IntType = OddSizedInteger<5, true>; - ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); - break; - } - case ZCL_INT48S_ATTRIBUTE_TYPE: // Signed 48-bit integer - { - using IntType = OddSizedInteger<6, true>; - ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); - break; - } - case ZCL_INT56S_ATTRIBUTE_TYPE: // Signed 56-bit integer - { - using IntType = OddSizedInteger<7, true>; - ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); - break; - } - case ZCL_INT64S_ATTRIBUTE_TYPE: // Signed 64-bit integer - { - ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); - break; - } - case ZCL_SINGLE_ATTRIBUTE_TYPE: // 32-bit float - { - ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); - break; - } - case ZCL_DOUBLE_ATTRIBUTE_TYPE: // 64-bit float - { - ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); - break; - } - case ZCL_CHAR_STRING_ATTRIBUTE_TYPE: // Char string - { - char * actualData = reinterpret_cast(attributeData + 1); - uint8_t dataLength = attributeData[0]; - if (dataLength == 0xFF) - { - if (isNullable) - { - ReturnErrorOnFailure(writer->PutNull(tag)); - } - else - { - return CHIP_ERROR_INCORRECT_STATE; - } - } - else - { - ReturnErrorOnFailure(writer->PutString(tag, actualData, dataLength)); - } - break; - } - case ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE: { - char * actualData = reinterpret_cast(attributeData + 2); // The pascal string contains 2 bytes length - uint16_t dataLength; - memcpy(&dataLength, attributeData, sizeof(dataLength)); - if (dataLength == 0xFFFF) - { - if (isNullable) - { - ReturnErrorOnFailure(writer->PutNull(tag)); - } - else - { - return CHIP_ERROR_INCORRECT_STATE; - } - } - else - { - ReturnErrorOnFailure(writer->PutString(tag, actualData, dataLength)); - } - break; - } - case ZCL_OCTET_STRING_ATTRIBUTE_TYPE: // Octet string - { - uint8_t * actualData = attributeData + 1; - uint8_t dataLength = attributeData[0]; - if (dataLength == 0xFF) - { - if (isNullable) - { - ReturnErrorOnFailure(writer->PutNull(tag)); - } - else - { - return CHIP_ERROR_INCORRECT_STATE; - } - } - else - { - ReturnErrorOnFailure(writer->Put(tag, chip::ByteSpan(actualData, dataLength))); - } - break; - } - case ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE: { - uint8_t * actualData = attributeData + 2; // The pascal string contains 2 bytes length - uint16_t dataLength; - memcpy(&dataLength, attributeData, sizeof(dataLength)); - if (dataLength == 0xFFFF) - { - if (isNullable) - { - ReturnErrorOnFailure(writer->PutNull(tag)); - } - else - { - return CHIP_ERROR_INCORRECT_STATE; - } - } - else - { - ReturnErrorOnFailure(writer->Put(tag, chip::ByteSpan(actualData, dataLength))); - } - break; - } - default: - ChipLogError(DataManagement, "Attribute type 0x%x not handled", static_cast(attributeType)); - status = Status::UnsupportedRead; - } - } - - if (status == Protocols::InteractionModel::Status::Success) - { - return SendSuccessStatus(attributeReport, attributeDataIBBuilder); - } - - return SendFailureStatus(aPath, aAttributeReports, status, &backup); -#endif + return EncodeEmberValue(ByteSpan(attributeReadBufferSpace), attributeMetadata, encoder); } } // namespace CodegenDataModel diff --git a/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp b/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp index 4c6656d75fcbdc..a7d398f96f3f29 100644 --- a/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp +++ b/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp @@ -82,6 +82,7 @@ void DispatchSingleClusterCommand(const ConcreteCommandPath & aRequestCommandPat } // namespace app } // namespace chip +/// TODO: this SHOULD be part of attribute-storage mocks. Status emAfReadOrWriteAttribute(const EmberAfAttributeSearchRecord * attRecord, const EmberAfAttributeMetadata ** metadata, uint8_t * buffer, uint16_t readLength, bool write) { From fec8237e4dd2dbac137792487fdcc225013e135c Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 9 May 2024 11:31:30 -0400 Subject: [PATCH 022/123] Comment updates and slight restyle --- .../codegen-interaction-model/Model_Read.cpp | 35 ++++++------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp index 483bda8538ae8e..8474aa61ac1ea8 100644 --- a/src/app/codegen-interaction-model/Model_Read.cpp +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -211,11 +211,9 @@ CHIP_ERROR CheckAccessPrivilege(const ConcreteAttributePath & path, const chip:: /// Attempts to read via an attribute access interface (AAI) /// -/// Expected returns: -/// - CHIP_IM_GLOBAL_STATUS(UnsupportedRead) IF AND ONLY IF processing denied by the AAI (considered final) -/// - +/// If it returns a CHIP_ERROR, then this is a FINAL result (i.e. either failure or success): +/// - in particular, CHIP_ERROR_ACCESS_DENIED will be used for UnsupportedRead AII returns /// -/// If it returns a VALUE, then this is a FINAL result (i.e. either failure or success). /// If it returns std::nullopt, then there is no AAI to handle the given path /// and processing should figure out the value otherwise (generally from other ember data) std::optional TryReadViaAccessInterface(const ConcreteAttributePath & path, AttributeAccessInterface * aai, @@ -227,23 +225,24 @@ std::optional TryReadViaAccessInterface(const ConcreteAttributePath return std::nullopt; } - // TODO: AAI seems to NEVER be able to return anything else EXCEPT - // UnsupportedRead to be handled upward - + // TODO: AAI returns: + // - UnsupportedRead is FINAL + // (this seems to be) CHIP_ERROR err = aai->Read(path, encoder); - // explict translate UnsupportedAccess to Access denied. This is to allow callers to determine a + // explict translate UnsupportedRead to Access denied. This is to allow callers to determine a // translation for this: usually wildcard subscriptions MAY just ignore these where as direct reads // MUST translate them to UnsupportedAccess - ReturnErrorCodeIf(err == CHIP_IM_GLOBAL_STATUS(UnsupportedAccess), CHIP_ERROR_ACCESS_DENIED); + ReturnErrorCodeIf(err == CHIP_IM_GLOBAL_STATUS(UnsupportedRead), CHIP_ERROR_ACCESS_DENIED); if (err != CHIP_NO_ERROR) { return std::make_optional(err); } - // if no attempt was made to encode anything, assume the AAI did not even - // try to handle it, so handling has to be deferred + // If the encoder tried to encode, then a value should have been written. + // - if encode, assueme DONE (i.e. FINAL CHIP_NO_ERROR) + // - if no encode, say that processing must continue return encoder.TriedEncode() ? std::make_optional(CHIP_NO_ERROR) : std::nullopt; } @@ -281,13 +280,6 @@ std::optional ExtractEmberLongString(ByteSpan data) return std::make_optional(reinterpret_cast(data.data() + 1), len); } -// TODO: string handling: -// - length (1 or 2 bytes, null is 0xFF or 0xFFFF) -// - either Put(ByteSpan) or PutString() -> this seems to need a Encoder equivalent? -// - Span calls EncodeString -// - ByteSpan calls Encode -// - template CHIP_ERROR EncodeStringLike(std::optional data, bool isNullable, AttributeValueEncoder & encoder) { @@ -434,13 +426,6 @@ CHIP_ERROR Model::ReadAttribute(const InteractionModel::ReadAttributeRequest & r state.listEncodeStart = kInvalidListIndex; // At this point, we have to use ember directly to read the data. - // Available methods: - // - emberAfReadOrWriteAttribute should do the processing of read vs write - // - we write to a fixed buffer, so the actual type has to be determined - // by the attributeType (base type) - - // TODO: ember reading - // EmberAfAttributeSearchRecord record; record.endpoint = request.path.mEndpointId; record.clusterId = request.path.mClusterId; From 17637c7a99a2ba8f2fbdd7b60ffbcc67cc2bce68 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 9 May 2024 12:30:00 -0400 Subject: [PATCH 023/123] remove commented out code --- src/app/codegen-interaction-model/Model_Read.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp index 8474aa61ac1ea8..1a2452983684f9 100644 --- a/src/app/codegen-interaction-model/Model_Read.cpp +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -41,20 +41,6 @@ namespace chip { namespace app { -#if 0 -namespace DataModel { - -// Ensure odd sized integers are not considered fabric scoped for the purpose of -// encoding via AttributeValueEncoder (they are just integers) -template -class IsFabricScoped> -{ -public: - static constexpr bool value = false; -}; -} // namespace DataModel -#endif - namespace CodegenDataModel { namespace { // On some apps, ATTRIBUTE_LARGEST can as small as 3, making compiler unhappy since data[kAttributeReadBufferSize] cannot hold From 6db60e832b7056641a5b173f575fbc47c7c897cd Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 9 May 2024 12:39:15 -0400 Subject: [PATCH 024/123] Remove read-state from arguments - attributevalueencoder maintains its own state, so we should not duplicate things at this time --- src/app/codegen-interaction-model/Model.h | 3 +-- src/app/codegen-interaction-model/Model_Read.cpp | 12 ++---------- src/app/interaction-model/Model.h | 15 +++++---------- src/app/interaction-model/OperationTypes.h | 7 ------- 4 files changed, 8 insertions(+), 29 deletions(-) diff --git a/src/app/codegen-interaction-model/Model.h b/src/app/codegen-interaction-model/Model.h index 3f48970852a7bc..9f1cca0a3fee76 100644 --- a/src/app/codegen-interaction-model/Model.h +++ b/src/app/codegen-interaction-model/Model.h @@ -28,8 +28,7 @@ class Model : public chip::app::InteractionModel::Model /// Generic model implementations CHIP_ERROR Shutdown() override { return CHIP_NO_ERROR; } - CHIP_ERROR ReadAttribute(const InteractionModel::ReadAttributeRequest & request, InteractionModel::ReadState & state, - AttributeValueEncoder & encoder) override; + CHIP_ERROR ReadAttribute(const InteractionModel::ReadAttributeRequest & request, AttributeValueEncoder & encoder) override; CHIP_ERROR WriteAttribute(const InteractionModel::WriteAttributeRequest & request, AttributeValueDecoder & decoder) override; CHIP_ERROR Invoke(const InteractionModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, InteractionModel::InvokeReply & reply) override; diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp index 1a2452983684f9..9aeff37285d2ca 100644 --- a/src/app/codegen-interaction-model/Model_Read.cpp +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -369,8 +369,7 @@ CHIP_ERROR EncodeEmberValue(ByteSpan data, const EmberAfAttributeMetadata * meta } // namespace /// separated-out ReadAttribute implementation (given existing complexity) -CHIP_ERROR Model::ReadAttribute(const InteractionModel::ReadAttributeRequest & request, InteractionModel::ReadState & state, - AttributeValueEncoder & encoder) +CHIP_ERROR Model::ReadAttribute(const InteractionModel::ReadAttributeRequest & request, AttributeValueEncoder & encoder) { ChipLogDetail(DataManagement, "Reading attribute: Cluster=" ChipLogFormatMEI " Endpoint=%x AttributeId=" ChipLogFormatMEI " (expanded=%d)", @@ -402,14 +401,7 @@ CHIP_ERROR Model::ReadAttribute(const InteractionModel::ReadAttributeRequest & r Compatibility::GlobalAttributeReader aai(attributeCluster); aai_result = TryReadViaAccessInterface(request.path, &aai, encoder); } - - if (aai_result.has_value()) - { - // save the reading state for later use (if lists are being decoded) - state.listEncodeStart = encoder.GetState().CurrentEncodingListIndex(); - return *aai_result; - } - state.listEncodeStart = kInvalidListIndex; + ReturnErrorCodeIf(aai_result.has_value(), *aai_result); // At this point, we have to use ember directly to read the data. EmberAfAttributeSearchRecord record; diff --git a/src/app/interaction-model/Model.h b/src/app/interaction-model/Model.h index b3a127c074aaa1..0ef187aaf406c8 100644 --- a/src/app/interaction-model/Model.h +++ b/src/app/interaction-model/Model.h @@ -55,22 +55,17 @@ class Model : public AttributeTreeIterator // event emitting, path marking and other operations virtual InteractionModelActions CurrentActions() { return mActions; } - /// List reading has specific handling logic: - /// `state` contains in/out data about the current list reading. MUST start with kInvalidListIndex on first call - /// /// Return codes: - /// CHIP_ERROR_MORE_LIST_DATA_AVAILABLE (NOTE: new error defined for this purpose) - /// - partial data written to the destination - /// - destination will contain AT LEAST one valid list entry fully serialized - /// - destination will be fully valid (it will be rolled back on partial list writes) + /// CHIP_ERROR_ACCESS_DENIED: + /// - May be ignored if reads use path expansion (e.g. to skip inaccessible attributes + /// during subscription requests). This code MUST be used for ACL failures and only for ACL failures. /// CHIP_IM_GLOBAL_STATUS(code): /// - error codes that are translatable in IM status codes (otherwise we expect Failure to be reported) - /// - In particular, some handlers rely on special handling for: - /// - `UnsupportedAccess` - for ACL checks (e.g. wildcard expansion may choose to skip these) /// - to check for this, CHIP_ERROR provides: /// - ::IsPart(ChipError::SdkPart::kIMGlobalStatus) -> bool /// - ::GetSdkCode() -> uint8_t to translate to the actual code - virtual CHIP_ERROR ReadAttribute(const ReadAttributeRequest & request, ReadState & state, AttributeValueEncoder & encoder) = 0; + /// other internal falures + virtual CHIP_ERROR ReadAttribute(const ReadAttributeRequest & request, AttributeValueEncoder & encoder) = 0; /// Requests a write of an attribute. /// diff --git a/src/app/interaction-model/OperationTypes.h b/src/app/interaction-model/OperationTypes.h index 4ef3eb51847345..cb3ca615fa93b8 100644 --- a/src/app/interaction-model/OperationTypes.h +++ b/src/app/interaction-model/OperationTypes.h @@ -57,13 +57,6 @@ struct ReadAttributeRequest : OperationRequest BitFlags readFlags; }; -struct ReadState -{ - // When reading lists, reading will start at this index. - // As list data is read, this index is incremented - ListIndex listEncodeStart = kInvalidListIndex; -}; - enum class WriteFlags : uint32_t { kTimed = 0x0001, // Received as a 2nd command after a timed invoke From 55c0c2a27989d15bb47a6861f95a37a48d2e4d35 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 9 May 2024 14:31:20 -0400 Subject: [PATCH 025/123] more comments and some logic cleanup on alternatives ... code should be less broken now --- .../codegen-interaction-model/Model_Read.cpp | 65 ++++++++++++------- src/app/interaction-model/Model.h | 4 ++ 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp index 9aeff37285d2ca..1ea7cff68fc46d 100644 --- a/src/app/codegen-interaction-model/Model_Read.cpp +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -14,7 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "app/util/af-types.h" #include +#include #include #include @@ -137,28 +139,30 @@ EmberAfAttributeType BaseType(EmberAfAttributeType type) } } -// Will set at most one of the out-params (aAttributeCluster or -// aAttributeMetadata) to non-null. Both null means attribute not supported, -// aAttributeCluster non-null means this is a supported global attribute that -// does not have metadata. -CHIP_ERROR FindAttributeMetadata(const ConcreteAttributePath & aPath, const EmberAfCluster ** aAttributeCluster, - const EmberAfAttributeMetadata ** aAttributeMetadata) +// Fetch the source for the given attribute path: either a cluster (for global ones) or attribute +// path. +// +// if returning a CHIP_ERROR, it will NEVER be CHIP_NO_ERROR. +std::variant +FindAttributeMetadata(const ConcreteAttributePath & aPath) { - *aAttributeCluster = nullptr; - *aAttributeMetadata = nullptr; - for (auto & attr : GlobalAttributesNotInMetadata) { if (attr == aPath.mAttributeId) { - *aAttributeCluster = emberAfFindServerCluster(aPath.mEndpointId, aPath.mClusterId); - return CHIP_NO_ERROR; + const EmberAfCluster * cluster = emberAfFindServerCluster(aPath.mEndpointId, aPath.mClusterId); + ReturnErrorCodeIf(cluster == nullptr, CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); + return cluster; } } - *aAttributeMetadata = emberAfLocateAttributeMetadata(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId); + const EmberAfAttributeMetadata * metadata = + emberAfLocateAttributeMetadata(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId); - if (*aAttributeMetadata == nullptr) + if (metadata == nullptr) { const EmberAfEndpointType * type = emberAfFindEndpointType(aPath.mEndpointId); if (type == nullptr) @@ -177,7 +181,7 @@ CHIP_ERROR FindAttributeMetadata(const ConcreteAttributePath & aPath, const Embe return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); } - return CHIP_NO_ERROR; + return metadata; } CHIP_ERROR CheckAccessPrivilege(const ConcreteAttributePath & path, const chip::Access::SubjectDescriptor & descriptor) @@ -376,12 +380,6 @@ CHIP_ERROR Model::ReadAttribute(const InteractionModel::ReadAttributeRequest & r ChipLogValueMEI(request.path.mClusterId), request.path.mEndpointId, ChipLogValueMEI(request.path.mAttributeId), request.path.mExpanded); - const EmberAfCluster * attributeCluster = nullptr; - const EmberAfAttributeMetadata * attributeMetadata = nullptr; - - ReturnErrorOnFailure(FindAttributeMetadata(request.path, &attributeCluster, &attributeMetadata)); - VerifyOrDie(attributeMetadata != nullptr); // this is the contract of FindAttributeMetadata - // ACL check for non-internal requests if (!request.operationFlags.Has(InteractionModel::OperationFlags::kInternal)) { @@ -391,18 +389,35 @@ CHIP_ERROR Model::ReadAttribute(const InteractionModel::ReadAttributeRequest & r std::optional aai_result; - if (attributeCluster != nullptr) + auto metadata = FindAttributeMetadata(request.path); + + if (const CHIP_ERROR * err = std::get_if(&metadata)) { - aai_result = TryReadViaAccessInterface( - request.path, GetAttributeAccessOverride(request.path.mEndpointId, request.path.mClusterId), encoder); + VerifyOrDie(*err != CHIP_NO_ERROR); + return *err; } - else + + if (const EmberAfCluster ** cluster = std::get_if(&metadata)) { - Compatibility::GlobalAttributeReader aai(attributeCluster); + Compatibility::GlobalAttributeReader aai(*cluster); aai_result = TryReadViaAccessInterface(request.path, &aai, encoder); } + else + { + aai_result = TryReadViaAccessInterface( + request.path, GetAttributeAccessOverride(request.path.mEndpointId, request.path.mClusterId), encoder); + } ReturnErrorCodeIf(aai_result.has_value(), *aai_result); + if (!std::holds_alternative(metadata)) + { + // if we only got a cluster, this was for a global attribute. We cannot read ember attributes + // at this point, so give up (although GlobalAttributeReader should have returned something here). + // Return a permanent failure... + return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); + } + const EmberAfAttributeMetadata * attributeMetadata = std::get(metadata); + // At this point, we have to use ember directly to read the data. EmberAfAttributeSearchRecord record; record.endpoint = request.path.mEndpointId; diff --git a/src/app/interaction-model/Model.h b/src/app/interaction-model/Model.h index 0ef187aaf406c8..3cb4c1d0981ea6 100644 --- a/src/app/interaction-model/Model.h +++ b/src/app/interaction-model/Model.h @@ -59,6 +59,10 @@ class Model : public AttributeTreeIterator /// CHIP_ERROR_ACCESS_DENIED: /// - May be ignored if reads use path expansion (e.g. to skip inaccessible attributes /// during subscription requests). This code MUST be used for ACL failures and only for ACL failures. + /// CHIP_ERROR_NO_MEMORY or CHIP_ERROR_BUFFER_TOO_SMALL: + /// - Indicates that list encoding had insufficient buffer space to encode elements. + /// - encoder::GetState().AllowPartialData() determines if these errors are permanent (no partial + /// data allowed) or further encoding can be retried (AllowPartialData true for list encoding) /// CHIP_IM_GLOBAL_STATUS(code): /// - error codes that are translatable in IM status codes (otherwise we expect Failure to be reported) /// - to check for this, CHIP_ERROR provides: From a536b84b960fe69c2e27b4e9beeb66accc901aee Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 9 May 2024 14:53:11 -0400 Subject: [PATCH 026/123] clean one more comment that seemed odd --- src/app/codegen-interaction-model/Model_Read.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp index 1ea7cff68fc46d..3b306cdccccd61 100644 --- a/src/app/codegen-interaction-model/Model_Read.cpp +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -215,9 +215,6 @@ std::optional TryReadViaAccessInterface(const ConcreteAttributePath return std::nullopt; } - // TODO: AAI returns: - // - UnsupportedRead is FINAL - // (this seems to be) CHIP_ERROR err = aai->Read(path, encoder); // explict translate UnsupportedRead to Access denied. This is to allow callers to determine a From 4006b1adb5c3ffab9f4a2ba52dabe400c82abb27 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 9 May 2024 14:56:47 -0400 Subject: [PATCH 027/123] Fix off by one bug for long strin processing --- src/app/codegen-interaction-model/Model_Read.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp index 3b306cdccccd61..6c705c9acdc7b9 100644 --- a/src/app/codegen-interaction-model/Model_Read.cpp +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -245,26 +245,29 @@ std::optional ExtractEmberShortString(ByteSpan data) { return std::nullopt; } - VerifyOrDie(static_cast(len + 1) <= data.size()); + VerifyOrDie(static_cast(len + 1) <= data.size()); return std::make_optional(reinterpret_cast(data.data() + 1), len); } +static constexpr size_t kLongStringLengthBytes = 2; + template std::optional ExtractEmberLongString(ByteSpan data) { uint16_t len; + static_assert(sizeof(len) == kLongStringLengthBytes); - VerifyOrDie(sizeof(len) <= data.size()); - memcpy(&len, data.data(), sizeof(len)); + VerifyOrDie(kLongStringLengthBytes <= data.size()); + memcpy(&len, data.data(), kLongStringLengthBytes); if (len == kEmberLongStringNullLength) { return std::nullopt; } - VerifyOrDie(static_cast(len + 1) <= data.size()); - return std::make_optional(reinterpret_cast(data.data() + 1), len); + VerifyOrDie(static_cast(len + kLongStringLengthBytes) <= data.size()); + return std::make_optional(reinterpret_cast(data.data() + kLongStringLengthBytes), len); } template From c9663d62c09c677521f324a8df3f43dc8a377d3b Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 9 May 2024 15:07:51 -0400 Subject: [PATCH 028/123] More generics --- .../codegen-interaction-model/Model_Read.cpp | 61 +++++++++---------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp index 6c705c9acdc7b9..6c34968646c220 100644 --- a/src/app/codegen-interaction-model/Model_Read.cpp +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -233,47 +233,44 @@ std::optional TryReadViaAccessInterface(const ConcreteAttributePath return encoder.TriedEncode() ? std::make_optional(CHIP_NO_ERROR) : std::nullopt; } -static constexpr uint8_t kEmberStringNullLength = 0xFF; -static constexpr uint16_t kEmberLongStringNullLength = 0xFFFF; - -template -std::optional ExtractEmberShortString(ByteSpan data) +struct ShortPascalString { - uint8_t len = data[0]; - - if (len == kEmberStringNullLength) - { - return std::nullopt; - } + using LengthType = uint8_t; + static constexpr LengthType kNullLength = 0xFF; +}; - VerifyOrDie(static_cast(len + 1) <= data.size()); - return std::make_optional(reinterpret_cast(data.data() + 1), len); -} +struct LongPascalString +{ + using LengthType = uint16_t; + static constexpr LengthType kNullLength = 0xFFFF; +}; -static constexpr size_t kLongStringLengthBytes = 2; +// ember assumptions ... should just work +static_assert(sizeof(ShortPascalString::LengthType) == 1); +static_assert(sizeof(LongPascalString::LengthType) == 2); -template -std::optional ExtractEmberLongString(ByteSpan data) +template +std::optional ExtractEmberString(ByteSpan data) { - uint16_t len; - static_assert(sizeof(len) == kLongStringLengthBytes); + typename ENCODING::LengthType len; - VerifyOrDie(kLongStringLengthBytes <= data.size()); - memcpy(&len, data.data(), kLongStringLengthBytes); + VerifyOrDie(sizeof(len) <= data.size()); + memcpy(&len, data.data(), sizeof(len)); - if (len == kEmberLongStringNullLength) + if (len == ENCODING::kNullLength) { return std::nullopt; } - VerifyOrDie(static_cast(len + kLongStringLengthBytes) <= data.size()); - return std::make_optional(reinterpret_cast(data.data() + kLongStringLengthBytes), len); + VerifyOrDie(static_cast(len + sizeof(len)) <= data.size()); + return std::make_optional(reinterpret_cast(data.data() + sizeof(len)), len); } -template -CHIP_ERROR EncodeStringLike(std::optional data, bool isNullable, AttributeValueEncoder & encoder) +template +CHIP_ERROR EncodeStringLike(ByteSpan data, bool isNullable, AttributeValueEncoder & encoder) { - if (!data.has_value()) + std::optional value = ExtractEmberString(data); + if (!value.has_value()) { if (isNullable) { @@ -283,7 +280,7 @@ CHIP_ERROR EncodeStringLike(std::optional data, bool isNullable, AttributeVal } // encode value as-is - return encoder.Encode(*data); + return encoder.Encode(*value); } template @@ -357,13 +354,13 @@ CHIP_ERROR EncodeEmberValue(ByteSpan data, const EmberAfAttributeMetadata * meta case ZCL_DOUBLE_ATTRIBUTE_TYPE: // 64-bit float return EncodeFromSpan(data, isNullable, encoder); case ZCL_CHAR_STRING_ATTRIBUTE_TYPE: // Char string - return EncodeStringLike(ExtractEmberShortString(data), isNullable, encoder); + return EncodeStringLike(data, isNullable, encoder); case ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE: - return EncodeStringLike(ExtractEmberLongString(data), isNullable, encoder); + return EncodeStringLike(data, isNullable, encoder); case ZCL_OCTET_STRING_ATTRIBUTE_TYPE: // Octet string - return EncodeStringLike(ExtractEmberShortString(data), isNullable, encoder); + return EncodeStringLike(data, isNullable, encoder); case ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE: - return EncodeStringLike(ExtractEmberLongString(data), isNullable, encoder); + return EncodeStringLike(data, isNullable, encoder); default: ChipLogError(DataManagement, "Attribute type 0x%x not handled", static_cast(metadata->attributeType)); return CHIP_IM_GLOBAL_STATUS(UnsupportedRead); From 688cda370f7b8635e78ac305a499056d6529e0b6 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 9 May 2024 15:39:43 -0400 Subject: [PATCH 029/123] Split out io storage and type conversion as shared functions for ember-compatibility --- src/app/chip_data_model.cmake | 1 + src/app/chip_data_model.gni | 1 + .../codegen-interaction-model/Model_Read.cpp | 111 ++----------- .../codegen-interaction-model/tests/BUILD.gn | 4 +- .../util/ember-compatibility-functions.cpp | 149 ++++-------------- src/app/util/ember-io-storage.cpp | 127 +++++++++++++++ src/app/util/ember-io-storage.h | 46 ++++++ 7 files changed, 215 insertions(+), 224 deletions(-) create mode 100644 src/app/util/ember-io-storage.cpp create mode 100644 src/app/util/ember-io-storage.h diff --git a/src/app/chip_data_model.cmake b/src/app/chip_data_model.cmake index 5c4357c5de6c9d..2d4149b66f6a8a 100644 --- a/src/app/chip_data_model.cmake +++ b/src/app/chip_data_model.cmake @@ -145,6 +145,7 @@ function(chip_configure_data_model APP_TARGET) ${CHIP_APP_BASE_DIR}/util/DataModelHandler.cpp ${CHIP_APP_BASE_DIR}/util/ember-compatibility-functions.cpp ${CHIP_APP_BASE_DIR}/util/ember-global-attribute-access-interface.cpp + ${CHIP_APP_BASE_DIR}/util/ember-io-storage.cpp ${CHIP_APP_BASE_DIR}/util/generic-callback-stubs.cpp ${CHIP_APP_BASE_DIR}/util/privilege-storage.cpp ${CHIP_APP_BASE_DIR}/util/util.cpp diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni index a59405e9989c9f..a68d193d241541 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -210,6 +210,7 @@ template("chip_data_model") { "${_app_root}/util/attribute-table.cpp", "${_app_root}/util/ember-compatibility-functions.cpp", "${_app_root}/util/ember-global-attribute-access-interface.cpp", + "${_app_root}/util/ember-io-storage.cpp", "${_app_root}/util/util.cpp", ] } diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp index 6c34968646c220..067dcbda469a22 100644 --- a/src/app/codegen-interaction-model/Model_Read.cpp +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -45,99 +46,7 @@ namespace chip { namespace app { namespace CodegenDataModel { namespace { -// On some apps, ATTRIBUTE_LARGEST can as small as 3, making compiler unhappy since data[kAttributeReadBufferSize] cannot hold -// uint64_t. Make kAttributeReadBufferSize at least 8 so it can fit all basic types. -constexpr size_t kAttributeReadBufferSize = (ATTRIBUTE_LARGEST >= 8 ? ATTRIBUTE_LARGEST : 8); -uint8_t attributeReadBufferSpace[kAttributeReadBufferSize]; - -// BasicType maps the type to basic int(8|16|32|64)(s|u) types. -// TODO: this code should be SHARED! -EmberAfAttributeType BaseType(EmberAfAttributeType type) -{ - switch (type) - { - case ZCL_ACTION_ID_ATTRIBUTE_TYPE: // Action Id - case ZCL_FABRIC_IDX_ATTRIBUTE_TYPE: // Fabric Index - case ZCL_BITMAP8_ATTRIBUTE_TYPE: // 8-bit bitmap - case ZCL_ENUM8_ATTRIBUTE_TYPE: // 8-bit enumeration - case ZCL_STATUS_ATTRIBUTE_TYPE: // Status Code - case ZCL_PERCENT_ATTRIBUTE_TYPE: // Percentage - static_assert(std::is_same::value, - "chip::Percent is expected to be uint8_t, change this when necessary"); - return ZCL_INT8U_ATTRIBUTE_TYPE; - - case ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE: // Endpoint Number - case ZCL_GROUP_ID_ATTRIBUTE_TYPE: // Group Id - case ZCL_VENDOR_ID_ATTRIBUTE_TYPE: // Vendor Id - case ZCL_ENUM16_ATTRIBUTE_TYPE: // 16-bit enumeration - case ZCL_BITMAP16_ATTRIBUTE_TYPE: // 16-bit bitmap - case ZCL_PERCENT100THS_ATTRIBUTE_TYPE: // 100ths of a percent - static_assert(std::is_same::value, - "chip::EndpointId is expected to be uint16_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::GroupId is expected to be uint16_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::Percent100ths is expected to be uint16_t, change this when necessary"); - return ZCL_INT16U_ATTRIBUTE_TYPE; - - case ZCL_CLUSTER_ID_ATTRIBUTE_TYPE: // Cluster Id - case ZCL_ATTRIB_ID_ATTRIBUTE_TYPE: // Attribute Id - case ZCL_FIELD_ID_ATTRIBUTE_TYPE: // Field Id - case ZCL_EVENT_ID_ATTRIBUTE_TYPE: // Event Id - case ZCL_COMMAND_ID_ATTRIBUTE_TYPE: // Command Id - case ZCL_TRANS_ID_ATTRIBUTE_TYPE: // Transaction Id - case ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE: // Device Type Id - case ZCL_DATA_VER_ATTRIBUTE_TYPE: // Data Version - case ZCL_BITMAP32_ATTRIBUTE_TYPE: // 32-bit bitmap - case ZCL_EPOCH_S_ATTRIBUTE_TYPE: // Epoch Seconds - case ZCL_ELAPSED_S_ATTRIBUTE_TYPE: // Elapsed Seconds - static_assert(std::is_same::value, - "chip::Cluster is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::AttributeId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::AttributeId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::EventId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::CommandId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::TransactionId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::DeviceTypeId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::DataVersion is expected to be uint32_t, change this when necessary"); - return ZCL_INT32U_ATTRIBUTE_TYPE; - - case ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE: // Amperage milliamps - case ZCL_ENERGY_MWH_ATTRIBUTE_TYPE: // Energy milliwatt-hours - case ZCL_POWER_MW_ATTRIBUTE_TYPE: // Power milliwatts - case ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE: // Voltage millivolts - return ZCL_INT64S_ATTRIBUTE_TYPE; - - case ZCL_EVENT_NO_ATTRIBUTE_TYPE: // Event Number - case ZCL_FABRIC_ID_ATTRIBUTE_TYPE: // Fabric Id - case ZCL_NODE_ID_ATTRIBUTE_TYPE: // Node Id - case ZCL_BITMAP64_ATTRIBUTE_TYPE: // 64-bit bitmap - case ZCL_EPOCH_US_ATTRIBUTE_TYPE: // Epoch Microseconds - case ZCL_POSIX_MS_ATTRIBUTE_TYPE: // POSIX Milliseconds - case ZCL_SYSTIME_MS_ATTRIBUTE_TYPE: // System time Milliseconds - case ZCL_SYSTIME_US_ATTRIBUTE_TYPE: // System time Microseconds - static_assert(std::is_same::value, - "chip::EventNumber is expected to be uint64_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::FabricId is expected to be uint64_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::NodeId is expected to be uint64_t, change this when necessary"); - return ZCL_INT64U_ATTRIBUTE_TYPE; - - case ZCL_TEMPERATURE_ATTRIBUTE_TYPE: // Temperature - return ZCL_INT16S_ATTRIBUTE_TYPE; - - default: - return type; - } -} +using namespace chip::app::Compatibility::Internal; // Fetch the source for the given attribute path: either a cluster (for global ones) or attribute // path. @@ -311,7 +220,7 @@ CHIP_ERROR EncodeEmberValue(ByteSpan data, const EmberAfAttributeMetadata * meta const bool isNullable = metadata->IsNullable(); - switch (BaseType(metadata->attributeType)) + switch (AttributeBaseType(metadata->attributeType)) { case ZCL_NO_DATA_ATTRIBUTE_TYPE: // No data return encoder.EncodeNull(); @@ -417,19 +326,19 @@ CHIP_ERROR Model::ReadAttribute(const InteractionModel::ReadAttributeRequest & r // At this point, we have to use ember directly to read the data. EmberAfAttributeSearchRecord record; - record.endpoint = request.path.mEndpointId; - record.clusterId = request.path.mClusterId; - record.attributeId = request.path.mAttributeId; - Protocols::InteractionModel::Status status = - emAfReadOrWriteAttribute(&record, &attributeMetadata, attributeReadBufferSpace, sizeof(attributeReadBufferSpace), - /* write = */ false); + record.endpoint = request.path.mEndpointId; + record.clusterId = request.path.mClusterId; + record.attributeId = request.path.mAttributeId; + Protocols::InteractionModel::Status status = emAfReadOrWriteAttribute( + &record, &attributeMetadata, gEmberAttributeIOBufferSpan.data(), gEmberAttributeIOBufferSpan.size(), + /* write = */ false); if (status != Protocols::InteractionModel::Status::Success) { return ChipError(ChipError::SdkPart::kIMGlobalStatus, to_underlying(status), __FILE__, __LINE__); } - return EncodeEmberValue(ByteSpan(attributeReadBufferSpace), attributeMetadata, encoder); + return EncodeEmberValue(gEmberAttributeIOBufferSpan, attributeMetadata, encoder); } } // namespace CodegenDataModel diff --git a/src/app/codegen-interaction-model/tests/BUILD.gn b/src/app/codegen-interaction-model/tests/BUILD.gn index 057bc3f5691f53..35e712486b161e 100644 --- a/src/app/codegen-interaction-model/tests/BUILD.gn +++ b/src/app/codegen-interaction-model/tests/BUILD.gn @@ -18,8 +18,10 @@ import("${chip_root}/src/app/codegen-interaction-model/model.gni") source_set("ember_extra_files") { sources = [ # This IS TERRIBLE, however we want to pretent AAI exists for global - # items + # items and we need a shared IO storage to reduce overhead between + # data-model access and ember-compatibility (we share the same buffer) "${chip_root}/src/app/util/ember-global-attribute-access-interface.cpp", + "${chip_root}/src/app/util/ember-io-storage.cpp", "InteractionModelTemporaryOverrides.cpp", ] diff --git a/src/app/util/ember-compatibility-functions.cpp b/src/app/util/ember-compatibility-functions.cpp index 8ebe51723e2f07..df49d37ead4436 100644 --- a/src/app/util/ember-compatibility-functions.cpp +++ b/src/app/util/ember-compatibility-functions.cpp @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "app/util/ember-io-storage.h" #include #include @@ -34,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -55,118 +57,18 @@ using chip::Protocols::InteractionModel::Status; using namespace chip; using namespace chip::app; using namespace chip::Access; +using namespace chip::app::Compatibility; +using namespace chip::app::Compatibility::Internal; namespace chip { namespace app { -namespace Compatibility { namespace { -// On some apps, ATTRIBUTE_LARGEST can as small as 3, making compiler unhappy since data[kAttributeReadBufferSize] cannot hold -// uint64_t. Make kAttributeReadBufferSize at least 8 so it can fit all basic types. -constexpr size_t kAttributeReadBufferSize = (ATTRIBUTE_LARGEST >= 8 ? ATTRIBUTE_LARGEST : 8); - -// BasicType maps the type to basic int(8|16|32|64)(s|u) types. -EmberAfAttributeType BaseType(EmberAfAttributeType type) -{ - switch (type) - { - case ZCL_ACTION_ID_ATTRIBUTE_TYPE: // Action Id - case ZCL_FABRIC_IDX_ATTRIBUTE_TYPE: // Fabric Index - case ZCL_BITMAP8_ATTRIBUTE_TYPE: // 8-bit bitmap - case ZCL_ENUM8_ATTRIBUTE_TYPE: // 8-bit enumeration - case ZCL_STATUS_ATTRIBUTE_TYPE: // Status Code - case ZCL_PERCENT_ATTRIBUTE_TYPE: // Percentage - static_assert(std::is_same::value, - "chip::Percent is expected to be uint8_t, change this when necessary"); - return ZCL_INT8U_ATTRIBUTE_TYPE; - - case ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE: // Endpoint Number - case ZCL_GROUP_ID_ATTRIBUTE_TYPE: // Group Id - case ZCL_VENDOR_ID_ATTRIBUTE_TYPE: // Vendor Id - case ZCL_ENUM16_ATTRIBUTE_TYPE: // 16-bit enumeration - case ZCL_BITMAP16_ATTRIBUTE_TYPE: // 16-bit bitmap - case ZCL_PERCENT100THS_ATTRIBUTE_TYPE: // 100ths of a percent - static_assert(std::is_same::value, - "chip::EndpointId is expected to be uint16_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::GroupId is expected to be uint16_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::Percent100ths is expected to be uint16_t, change this when necessary"); - return ZCL_INT16U_ATTRIBUTE_TYPE; - - case ZCL_CLUSTER_ID_ATTRIBUTE_TYPE: // Cluster Id - case ZCL_ATTRIB_ID_ATTRIBUTE_TYPE: // Attribute Id - case ZCL_FIELD_ID_ATTRIBUTE_TYPE: // Field Id - case ZCL_EVENT_ID_ATTRIBUTE_TYPE: // Event Id - case ZCL_COMMAND_ID_ATTRIBUTE_TYPE: // Command Id - case ZCL_TRANS_ID_ATTRIBUTE_TYPE: // Transaction Id - case ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE: // Device Type Id - case ZCL_DATA_VER_ATTRIBUTE_TYPE: // Data Version - case ZCL_BITMAP32_ATTRIBUTE_TYPE: // 32-bit bitmap - case ZCL_EPOCH_S_ATTRIBUTE_TYPE: // Epoch Seconds - case ZCL_ELAPSED_S_ATTRIBUTE_TYPE: // Elapsed Seconds - static_assert(std::is_same::value, - "chip::Cluster is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::AttributeId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::AttributeId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::EventId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::CommandId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::TransactionId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::DeviceTypeId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::DataVersion is expected to be uint32_t, change this when necessary"); - return ZCL_INT32U_ATTRIBUTE_TYPE; - - case ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE: // Amperage milliamps - case ZCL_ENERGY_MWH_ATTRIBUTE_TYPE: // Energy milliwatt-hours - case ZCL_POWER_MW_ATTRIBUTE_TYPE: // Power milliwatts - case ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE: // Voltage millivolts - return ZCL_INT64S_ATTRIBUTE_TYPE; - - case ZCL_EVENT_NO_ATTRIBUTE_TYPE: // Event Number - case ZCL_FABRIC_ID_ATTRIBUTE_TYPE: // Fabric Id - case ZCL_NODE_ID_ATTRIBUTE_TYPE: // Node Id - case ZCL_BITMAP64_ATTRIBUTE_TYPE: // 64-bit bitmap - case ZCL_EPOCH_US_ATTRIBUTE_TYPE: // Epoch Microseconds - case ZCL_POSIX_MS_ATTRIBUTE_TYPE: // POSIX Milliseconds - case ZCL_SYSTIME_MS_ATTRIBUTE_TYPE: // System time Milliseconds - case ZCL_SYSTIME_US_ATTRIBUTE_TYPE: // System time Microseconds - static_assert(std::is_same::value, - "chip::EventNumber is expected to be uint64_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::FabricId is expected to be uint64_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::NodeId is expected to be uint64_t, change this when necessary"); - return ZCL_INT64U_ATTRIBUTE_TYPE; - - case ZCL_TEMPERATURE_ATTRIBUTE_TYPE: // Temperature - return ZCL_INT16S_ATTRIBUTE_TYPE; - - default: - return type; - } -} - -} // namespace - -} // namespace Compatibility - -using namespace chip::app::Compatibility; - -namespace { -// Common buffer for ReadSingleClusterData & WriteSingleClusterData -uint8_t attributeData[kAttributeReadBufferSize]; template CHIP_ERROR attributeBufferToNumericTlvData(TLV::TLVWriter & writer, bool isNullable) { typename NumericAttributeTraits::StorageType value; - memcpy(&value, attributeData, sizeof(value)); + memcpy(&value, gEmberAttributeIOBufferSpan.data(), sizeof(value)); TLV::Tag tag = TLV::ContextTag(AttributeDataIB::Tag::kData); if (isNullable && NumericAttributeTraits::IsNullValue(value)) { @@ -467,7 +369,8 @@ CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, b record.endpoint = aPath.mEndpointId; record.clusterId = aPath.mClusterId; record.attributeId = aPath.mAttributeId; - Status status = emAfReadOrWriteAttribute(&record, &attributeMetadata, attributeData, sizeof(attributeData), + Status status = emAfReadOrWriteAttribute(&record, &attributeMetadata, gEmberAttributeIOBufferSpan.data(), + static_cast(gEmberAttributeIOBufferSpan.size()), /* write = */ false); if (status == Status::Success) @@ -477,7 +380,7 @@ CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, b TLV::TLVWriter * writer = attributeDataIBBuilder.GetWriter(); VerifyOrReturnError(writer != nullptr, CHIP_NO_ERROR); TLV::Tag tag = TLV::ContextTag(AttributeDataIB::Tag::kData); - switch (BaseType(attributeType)) + switch (AttributeBaseType(attributeType)) { case ZCL_NO_DATA_ATTRIBUTE_TYPE: // No data ReturnErrorOnFailure(writer->PutNull(tag)); @@ -583,8 +486,8 @@ CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, b } case ZCL_CHAR_STRING_ATTRIBUTE_TYPE: // Char string { - char * actualData = reinterpret_cast(attributeData + 1); - uint8_t dataLength = attributeData[0]; + char * actualData = reinterpret_cast(gEmberAttributeIOBufferSpan.data() + 1); + uint8_t dataLength = gEmberAttributeIOBufferSpan[0]; if (dataLength == 0xFF) { if (isNullable) @@ -603,9 +506,10 @@ CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, b break; } case ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE: { - char * actualData = reinterpret_cast(attributeData + 2); // The pascal string contains 2 bytes length + char * actualData = + reinterpret_cast(gEmberAttributeIOBufferSpan.data() + 2); // The pascal string contains 2 bytes length uint16_t dataLength; - memcpy(&dataLength, attributeData, sizeof(dataLength)); + memcpy(&dataLength, gEmberAttributeIOBufferSpan.data(), sizeof(dataLength)); if (dataLength == 0xFFFF) { if (isNullable) @@ -625,8 +529,8 @@ CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, b } case ZCL_OCTET_STRING_ATTRIBUTE_TYPE: // Octet string { - uint8_t * actualData = attributeData + 1; - uint8_t dataLength = attributeData[0]; + uint8_t * actualData = gEmberAttributeIOBufferSpan.data() + 1; + uint8_t dataLength = gEmberAttributeIOBufferSpan[0]; if (dataLength == 0xFF) { if (isNullable) @@ -645,9 +549,9 @@ CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, b break; } case ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE: { - uint8_t * actualData = attributeData + 2; // The pascal string contains 2 bytes length + uint8_t * actualData = gEmberAttributeIOBufferSpan.data() + 2; // The pascal string contains 2 bytes length uint16_t dataLength; - memcpy(&dataLength, attributeData, sizeof(dataLength)); + memcpy(&dataLength, gEmberAttributeIOBufferSpan.data(), sizeof(dataLength)); if (dataLength == 0xFFFF) { if (isNullable) @@ -685,7 +589,8 @@ template CHIP_ERROR numericTlvDataToAttributeBuffer(TLV::TLVReader & aReader, bool isNullable, uint16_t & dataLen) { typename NumericAttributeTraits::StorageType value; - static_assert(sizeof(value) <= sizeof(attributeData), "Value cannot fit into attribute data"); + VerifyOrDie(sizeof(value) <= gEmberAttributeIOBufferSpan.size()); + if (isNullable && aReader.GetType() == TLV::kTLVType_Null) { NumericAttributeTraits::SetNull(value); @@ -698,7 +603,7 @@ CHIP_ERROR numericTlvDataToAttributeBuffer(TLV::TLVReader & aReader, bool isNull NumericAttributeTraits::WorkingToStorage(val, value); } dataLen = sizeof(value); - memcpy(attributeData, &value, sizeof(value)); + memcpy(gEmberAttributeIOBufferSpan.data(), &value, sizeof(value)); return CHIP_NO_ERROR; } @@ -711,7 +616,7 @@ CHIP_ERROR stringTlvDataToAttributeBuffer(TLV::TLVReader & aReader, bool isOctet { // Null is represented by an 0xFF or 0xFFFF length, respectively. len = std::numeric_limits::max(); - memcpy(&attributeData[0], &len, sizeof(len)); + memcpy(gEmberAttributeIOBufferSpan.data(), &len, sizeof(len)); dataLen = sizeof(len); } else @@ -723,10 +628,10 @@ CHIP_ERROR stringTlvDataToAttributeBuffer(TLV::TLVReader & aReader, bool isOctet ReturnErrorOnFailure(aReader.GetDataPtr(data)); len = static_cast(aReader.GetLength()); VerifyOrReturnError(len != std::numeric_limits::max(), CHIP_ERROR_MESSAGE_TOO_LONG); - VerifyOrReturnError(len + sizeof(len) /* length at the beginning of data */ <= sizeof(attributeData), + VerifyOrReturnError(len + sizeof(len) /* length at the beginning of data */ <= gEmberAttributeIOBufferSpan.size(), CHIP_ERROR_MESSAGE_TOO_LONG); - memcpy(&attributeData[0], &len, sizeof(len)); - memcpy(&attributeData[sizeof(len)], data, len); + memcpy(gEmberAttributeIOBufferSpan.data(), &len, sizeof(len)); + memcpy(gEmberAttributeIOBufferSpan.data() + sizeof(len), data, len); dataLen = static_cast(len + sizeof(len)); } return CHIP_NO_ERROR; @@ -734,7 +639,7 @@ CHIP_ERROR stringTlvDataToAttributeBuffer(TLV::TLVReader & aReader, bool isOctet CHIP_ERROR prepareWriteData(const EmberAfAttributeMetadata * attributeMetadata, TLV::TLVReader & aReader, uint16_t & dataLen) { - EmberAfAttributeType expectedType = BaseType(attributeMetadata->attributeType); + EmberAfAttributeType expectedType = AttributeBaseType(attributeMetadata->attributeType); bool isNullable = attributeMetadata->IsNullable(); switch (expectedType) { @@ -895,8 +800,8 @@ CHIP_ERROR WriteSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, return apWriteHandler->AddStatus(aPath, Protocols::InteractionModel::Status::InvalidValue); } - auto status = emAfWriteAttributeExternal(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId, attributeData, - attributeMetadata->attributeType); + auto status = emAfWriteAttributeExternal(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId, + gEmberAttributeIOBufferSpan.data(), attributeMetadata->attributeType); return apWriteHandler->AddStatus(aPath, status); } diff --git a/src/app/util/ember-io-storage.cpp b/src/app/util/ember-io-storage.cpp new file mode 100644 index 00000000000000..568a0854e2cc66 --- /dev/null +++ b/src/app/util/ember-io-storage.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +#include +#include + +#include + +namespace chip +{ +namespace app { +namespace Compatibility { +namespace Internal { + +// On some apps, ATTRIBUTE_LARGEST can as small as 3, making compiler unhappy since data[kAttributeReadBufferSize] cannot hold +// uint64_t. Make kAttributeReadBufferSize at least 8 so it can fit all basic types. +constexpr size_t kAttributeReadBufferSize = (ATTRIBUTE_LARGEST >= 8 ? ATTRIBUTE_LARGEST : 8); +uint8_t attributeIOBuffer[kAttributeReadBufferSize]; + +MutableByteSpan gEmberAttributeIOBufferSpan(attributeIOBuffer); + +EmberAfAttributeType AttributeBaseType(EmberAfAttributeType type) +{ + switch (type) + { + case ZCL_ACTION_ID_ATTRIBUTE_TYPE: // Action Id + case ZCL_FABRIC_IDX_ATTRIBUTE_TYPE: // Fabric Index + case ZCL_BITMAP8_ATTRIBUTE_TYPE: // 8-bit bitmap + case ZCL_ENUM8_ATTRIBUTE_TYPE: // 8-bit enumeration + case ZCL_STATUS_ATTRIBUTE_TYPE: // Status Code + case ZCL_PERCENT_ATTRIBUTE_TYPE: // Percentage + static_assert(std::is_same::value, + "chip::Percent is expected to be uint8_t, change this when necessary"); + return ZCL_INT8U_ATTRIBUTE_TYPE; + + case ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE: // Endpoint Number + case ZCL_GROUP_ID_ATTRIBUTE_TYPE: // Group Id + case ZCL_VENDOR_ID_ATTRIBUTE_TYPE: // Vendor Id + case ZCL_ENUM16_ATTRIBUTE_TYPE: // 16-bit enumeration + case ZCL_BITMAP16_ATTRIBUTE_TYPE: // 16-bit bitmap + case ZCL_PERCENT100THS_ATTRIBUTE_TYPE: // 100ths of a percent + static_assert(std::is_same::value, + "chip::EndpointId is expected to be uint16_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::GroupId is expected to be uint16_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::Percent100ths is expected to be uint16_t, change this when necessary"); + return ZCL_INT16U_ATTRIBUTE_TYPE; + + case ZCL_CLUSTER_ID_ATTRIBUTE_TYPE: // Cluster Id + case ZCL_ATTRIB_ID_ATTRIBUTE_TYPE: // Attribute Id + case ZCL_FIELD_ID_ATTRIBUTE_TYPE: // Field Id + case ZCL_EVENT_ID_ATTRIBUTE_TYPE: // Event Id + case ZCL_COMMAND_ID_ATTRIBUTE_TYPE: // Command Id + case ZCL_TRANS_ID_ATTRIBUTE_TYPE: // Transaction Id + case ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE: // Device Type Id + case ZCL_DATA_VER_ATTRIBUTE_TYPE: // Data Version + case ZCL_BITMAP32_ATTRIBUTE_TYPE: // 32-bit bitmap + case ZCL_EPOCH_S_ATTRIBUTE_TYPE: // Epoch Seconds + case ZCL_ELAPSED_S_ATTRIBUTE_TYPE: // Elapsed Seconds + static_assert(std::is_same::value, + "chip::Cluster is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::AttributeId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::AttributeId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::EventId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::CommandId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::TransactionId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::DeviceTypeId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::DataVersion is expected to be uint32_t, change this when necessary"); + return ZCL_INT32U_ATTRIBUTE_TYPE; + + case ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE: // Amperage milliamps + case ZCL_ENERGY_MWH_ATTRIBUTE_TYPE: // Energy milliwatt-hours + case ZCL_POWER_MW_ATTRIBUTE_TYPE: // Power milliwatts + case ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE: // Voltage millivolts + return ZCL_INT64S_ATTRIBUTE_TYPE; + + case ZCL_EVENT_NO_ATTRIBUTE_TYPE: // Event Number + case ZCL_FABRIC_ID_ATTRIBUTE_TYPE: // Fabric Id + case ZCL_NODE_ID_ATTRIBUTE_TYPE: // Node Id + case ZCL_BITMAP64_ATTRIBUTE_TYPE: // 64-bit bitmap + case ZCL_EPOCH_US_ATTRIBUTE_TYPE: // Epoch Microseconds + case ZCL_POSIX_MS_ATTRIBUTE_TYPE: // POSIX Milliseconds + case ZCL_SYSTIME_MS_ATTRIBUTE_TYPE: // System time Milliseconds + case ZCL_SYSTIME_US_ATTRIBUTE_TYPE: // System time Microseconds + static_assert(std::is_same::value, + "chip::EventNumber is expected to be uint64_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::FabricId is expected to be uint64_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::NodeId is expected to be uint64_t, change this when necessary"); + return ZCL_INT64U_ATTRIBUTE_TYPE; + + case ZCL_TEMPERATURE_ATTRIBUTE_TYPE: // Temperature + return ZCL_INT16S_ATTRIBUTE_TYPE; + + default: + return type; + } +} + +} // namespace Internal +} // namespace Compatibility +} // namespace app +} // namespace chip diff --git a/src/app/util/ember-io-storage.h b/src/app/util/ember-io-storage.h new file mode 100644 index 00000000000000..02eccccce8f7cf --- /dev/null +++ b/src/app/util/ember-io-storage.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +#include +#include + +namespace chip { +namespace app { +namespace Compatibility { +namespace Internal { + +/// A buffer guaranteed to be sized to contain any individual value from +/// ember (i.e. a buffer that can be used to read ember data into). +extern MutableByteSpan gEmberAttributeIOBufferSpan; + +/// Maps a generic attribute to a basic int(8|16|32|64)(s|u) type +/// +/// For example: +/// ZCL_ENUM8_ATTRIBUTE_TYPE maps to ZCL_INT8U_ATTRIBUTE_TYPE +/// ZCL_VENDOR_ID_ATTRIBUTE_TYPE maps to ZCL_INT16U_ATTRIBUTE_TYPE +/// ZCL_BITMAP32_ATTRIBUTE_TYPE maps to ZCL_INT32U_ATTRIBUTE_TYPE +/// ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE maps to ZCL_INT64S_ATTRIBUTE_TYPE +/// ... +EmberAfAttributeType AttributeBaseType(EmberAfAttributeType type); + +} // namespace Internal +} // namespace Compatibility +} // namespace app +} // namespace chip From 3d4ccf11ed764ead5a25b2d104faee2a6cda7731 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 9 May 2024 15:40:09 -0400 Subject: [PATCH 030/123] Restyle --- src/app/util/ember-io-storage.cpp | 5 ++--- src/app/util/ember-io-storage.h | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/app/util/ember-io-storage.cpp b/src/app/util/ember-io-storage.cpp index 568a0854e2cc66..cc5eacf733480c 100644 --- a/src/app/util/ember-io-storage.cpp +++ b/src/app/util/ember-io-storage.cpp @@ -16,13 +16,12 @@ */ #include -#include #include +#include #include -namespace chip -{ +namespace chip { namespace app { namespace Compatibility { namespace Internal { diff --git a/src/app/util/ember-io-storage.h b/src/app/util/ember-io-storage.h index 02eccccce8f7cf..b08a4b7e639d15 100644 --- a/src/app/util/ember-io-storage.h +++ b/src/app/util/ember-io-storage.h @@ -26,7 +26,7 @@ namespace app { namespace Compatibility { namespace Internal { -/// A buffer guaranteed to be sized to contain any individual value from +/// A buffer guaranteed to be sized to contain any individual value from /// ember (i.e. a buffer that can be used to read ember data into). extern MutableByteSpan gEmberAttributeIOBufferSpan; From 3ff8865ddae2521d451d941da18d1355379822be Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 9 May 2024 15:47:02 -0400 Subject: [PATCH 031/123] Added more comments --- .../codegen-interaction-model/Model_Read.cpp | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp index 067dcbda469a22..8c7baac625cc2d 100644 --- a/src/app/codegen-interaction-model/Model_Read.cpp +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -142,12 +142,14 @@ std::optional TryReadViaAccessInterface(const ConcreteAttributePath return encoder.TriedEncode() ? std::make_optional(CHIP_NO_ERROR) : std::nullopt; } +/// Metadata of what a ember/pascal short string means (prepended by a u8 length) struct ShortPascalString { using LengthType = uint8_t; static constexpr LengthType kNullLength = 0xFF; }; +/// Metadata of what a ember/pascal LONG string means (prepended by a u16 length) struct LongPascalString { using LengthType = uint16_t; @@ -158,6 +160,9 @@ struct LongPascalString static_assert(sizeof(ShortPascalString::LengthType) == 1); static_assert(sizeof(LongPascalString::LengthType) == 2); +/// Given a ByteSpan containing data from ember, interpret it +/// as a span of type OUT (i.e. ByteSpan or CharSpan) given a ENCODING +/// where ENCODING is Short or Long pascal strings. template std::optional ExtractEmberString(ByteSpan data) { @@ -175,6 +180,12 @@ std::optional ExtractEmberString(ByteSpan data) return std::make_optional(reinterpret_cast(data.data() + sizeof(len)), len); } +/// Encode a value inside `encoder` +/// +/// The value encoded will be of type T (e.g. CharSpan or ByteSpan) and it will be decoded +/// via the given ENCODING (i.e. ShortPascalString or LongPascalString) +/// +/// isNullable defines if the value of NULL is allowed to be encoded. template CHIP_ERROR EncodeStringLike(ByteSpan data, bool isNullable, AttributeValueEncoder & encoder) { @@ -192,6 +203,9 @@ CHIP_ERROR EncodeStringLike(ByteSpan data, bool isNullable, AttributeValueEncode return encoder.Encode(*value); } +/// Encodes a numeric data value of type T from the given ember-encoded buffer `data`. +/// +/// isNullable defines if the value of NULL is allowed to be encoded. template CHIP_ERROR EncodeFromSpan(ByteSpan data, bool isNullable, AttributeValueEncoder & encoder) { @@ -214,6 +228,9 @@ CHIP_ERROR EncodeFromSpan(ByteSpan data, bool isNullable, AttributeValueEncoder } /// Converts raw ember data from `data` into the encoder +/// +/// Uses the attribute `metadata` to determine how the data is encoded into `data` and +/// write a suitable value into `encoder`. CHIP_ERROR EncodeEmberValue(ByteSpan data, const EmberAfAttributeMetadata * metadata, AttributeValueEncoder & encoder) { VerifyOrReturnError(metadata != nullptr, CHIP_ERROR_INVALID_ARGUMENT); @@ -279,6 +296,11 @@ CHIP_ERROR EncodeEmberValue(ByteSpan data, const EmberAfAttributeMetadata * meta } // namespace /// separated-out ReadAttribute implementation (given existing complexity) +/// +/// Generally will: +/// - validate ACL (only for non-internal requests) +/// - Try to read attribute via the AttributeAccessInterface +/// - Try to read the value from ember RAM storage CHIP_ERROR Model::ReadAttribute(const InteractionModel::ReadAttributeRequest & request, AttributeValueEncoder & encoder) { ChipLogDetail(DataManagement, From 23f8d48ea262292e81f84a65c4a60402a26830e8 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 9 May 2024 15:50:12 -0400 Subject: [PATCH 032/123] Fix lint errors ... these files are not in libraries since they depend on metadata --- .github/workflows/lint.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5f7a956c1ec25e..663d3e9ae0998b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -118,6 +118,10 @@ jobs: --known-failure app/util/DataModelHandler.h \ --known-failure app/util/ember-compatibility-functions.cpp \ --known-failure app/util/ember-compatibility-functions.h \ + --known-failure app/util/ember-global-attribute-access-interface.cpp \ + --known-failure app/util/ember-global-attribute-access-interface.h \ + --known-failure app/util/ember-io-storage.cpp \ + --known-failure app/util/ember-io-storage.h \ --known-failure app/util/endpoint-config-api.h \ --known-failure app/util/generic-callbacks.h \ --known-failure app/util/generic-callback-stubs.cpp \ From 682df96f40c529034839a630bc357f141d3e6126 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 9 May 2024 15:50:40 -0400 Subject: [PATCH 033/123] Restyle --- src/app/codegen-interaction-model/Model_Read.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp index 8c7baac625cc2d..db7a8baaadad85 100644 --- a/src/app/codegen-interaction-model/Model_Read.cpp +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -160,7 +160,7 @@ struct LongPascalString static_assert(sizeof(ShortPascalString::LengthType) == 1); static_assert(sizeof(LongPascalString::LengthType) == 2); -/// Given a ByteSpan containing data from ember, interpret it +/// Given a ByteSpan containing data from ember, interpret it /// as a span of type OUT (i.e. ByteSpan or CharSpan) given a ENCODING /// where ENCODING is Short or Long pascal strings. template From d905219e40339305541056a645cc703b23218838 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 9 May 2024 15:54:44 -0400 Subject: [PATCH 034/123] Fix the access denied error logic ... the translation to UnsupportedAccess is to be done by the caller at a later time, since caller may skip --- src/app/codegen-interaction-model/Model_Read.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp index db7a8baaadad85..537586880f1332 100644 --- a/src/app/codegen-interaction-model/Model_Read.cpp +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -93,19 +93,17 @@ FindAttributeMetadata(const ConcreteAttributePath & aPath) return metadata; } +/// Validates access to the given path +/// +/// Notable errors: +/// CHIP_ERROR_ACCESS_DENIED will be returned on permission failures. Such +/// an error may trigger a "skip" of attributes on wildcard reads/subscriptions CHIP_ERROR CheckAccessPrivilege(const ConcreteAttributePath & path, const chip::Access::SubjectDescriptor & descriptor) { Access::RequestPath requestPath{ .cluster = path.mClusterId, .endpoint = path.mEndpointId }; Access::Privilege requestPrivilege = RequiredPrivilege::ForReadAttribute(path); - CHIP_ERROR err = Access::GetAccessControl().Check(descriptor, requestPath, requestPrivilege); - - // access denied sent as-is - ReturnErrorCodeIf(err == CHIP_ERROR_ACCESS_DENIED, CHIP_ERROR_ACCESS_DENIED); - - // anything else is an opaque failure - ReturnErrorCodeIf(err != CHIP_NO_ERROR, CHIP_IM_GLOBAL_STATUS(UnsupportedAccess)); - return CHIP_NO_ERROR; + return Access::GetAccessControl().Check(descriptor, requestPath, requestPrivilege); } /// Attempts to read via an attribute access interface (AAI) From 55f042c705b211d276fa3a060cf76e9c53d978f0 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 9 May 2024 15:59:43 -0400 Subject: [PATCH 035/123] Shorter code is better --- .../codegen-interaction-model/Model_Read.cpp | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp index 537586880f1332..0f030acc6ef6dc 100644 --- a/src/app/codegen-interaction-model/Model_Read.cpp +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -93,19 +93,6 @@ FindAttributeMetadata(const ConcreteAttributePath & aPath) return metadata; } -/// Validates access to the given path -/// -/// Notable errors: -/// CHIP_ERROR_ACCESS_DENIED will be returned on permission failures. Such -/// an error may trigger a "skip" of attributes on wildcard reads/subscriptions -CHIP_ERROR CheckAccessPrivilege(const ConcreteAttributePath & path, const chip::Access::SubjectDescriptor & descriptor) -{ - Access::RequestPath requestPath{ .cluster = path.mClusterId, .endpoint = path.mEndpointId }; - Access::Privilege requestPrivilege = RequiredPrivilege::ForReadAttribute(path); - - return Access::GetAccessControl().Check(descriptor, requestPath, requestPrivilege); -} - /// Attempts to read via an attribute access interface (AAI) /// /// If it returns a CHIP_ERROR, then this is a FINAL result (i.e. either failure or success): @@ -310,7 +297,10 @@ CHIP_ERROR Model::ReadAttribute(const InteractionModel::ReadAttributeRequest & r if (!request.operationFlags.Has(InteractionModel::OperationFlags::kInternal)) { ReturnErrorCodeIf(!request.subjectDescriptor.has_value(), CHIP_ERROR_INVALID_ARGUMENT); - ReturnErrorOnFailure(CheckAccessPrivilege(request.path, *request.subjectDescriptor)); + + Access::RequestPath requestPath{ .cluster = request.path.mClusterId, .endpoint = request.path.mEndpointId }; + ReturnErrorOnFailure(Access::GetAccessControl().Check(*request.subjectDescriptor, requestPath, + RequiredPrivilege::ForReadAttribute(request.path))); } std::optional aai_result; From d609545ea866ec8f823bd95e53a6ffcf78c65258 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 9 May 2024 16:05:58 -0400 Subject: [PATCH 036/123] Some comments, cleaner code --- src/app/codegen-interaction-model/Model_Read.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp index 0f030acc6ef6dc..394dc7f8fab1a7 100644 --- a/src/app/codegen-interaction-model/Model_Read.cpp +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -303,16 +303,17 @@ CHIP_ERROR Model::ReadAttribute(const InteractionModel::ReadAttributeRequest & r RequiredPrivilege::ForReadAttribute(request.path))); } - std::optional aai_result; - auto metadata = FindAttributeMetadata(request.path); + // Explicit failure in finding a suitable metadata if (const CHIP_ERROR * err = std::get_if(&metadata)) { VerifyOrDie(*err != CHIP_NO_ERROR); return *err; } + // Read via AAI + std::optional aai_result; if (const EmberAfCluster ** cluster = std::get_if(&metadata)) { Compatibility::GlobalAttributeReader aai(*cluster); From 6d40c813bab82dd14d70d93d23ff12972528f432 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Mon, 13 May 2024 11:43:05 -0400 Subject: [PATCH 037/123] Fix auto-include --- src/app/codegen-interaction-model/Model_Read.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/codegen-interaction-model/Model_Read.cpp b/src/app/codegen-interaction-model/Model_Read.cpp index 394dc7f8fab1a7..4d1314fe2f2a6b 100644 --- a/src/app/codegen-interaction-model/Model_Read.cpp +++ b/src/app/codegen-interaction-model/Model_Read.cpp @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "app/util/af-types.h" #include #include @@ -29,6 +28,7 @@ #include #include #include +#include #include #include #include From ed2894d7cd8c46cd9fa87ea6af4704876f73bb86 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Mon, 13 May 2024 17:05:53 -0400 Subject: [PATCH 038/123] add some TODO because access control is needed --- .../tests/TestCodegenModelViaMocks.cpp | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index f3438e1aeb2166..29b4e478d72f32 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -16,20 +16,41 @@ */ #include +#include + +#include #include #include #include #include +#include + using namespace chip; using namespace chip::Test; using namespace chip::app; using namespace chip::app::InteractionModel; using namespace chip::app::Clusters::Globals::Attributes; +namespace pw { +template <> +StatusWithSize ToString(const CHIP_ERROR & err, pw::span buffer) +{ + if (CHIP_ERROR::IsSuccess(err)) + { + // source location probably does not matter + return pw::string::Format(buffer, "CHIP_NO_ERROR"); + } + return pw::string::Format(buffer, "CHIP_ERROR:<%" CHIP_ERROR_FORMAT ">", err.Format()); +} +} // namespace pw + namespace { +constexpr FabricIndex kTestFabrixIndex = kMinValidFabricIndex; +constexpr NodeId kTestNodeId = 0xFFFF'1234'ABCD'4321; + constexpr EndpointId kEndpointIdThatIsMissing = kMockEndpointMin - 1; static_assert(kEndpointIdThatIsMissing != kInvalidEndpointId); @@ -37,6 +58,12 @@ static_assert(kEndpointIdThatIsMissing != kMockEndpoint1); static_assert(kEndpointIdThatIsMissing != kMockEndpoint2); static_assert(kEndpointIdThatIsMissing != kMockEndpoint3); +constexpr Access::SubjectDescriptor kCaseSubjectDescriptor{ + .fabricIndex = kTestFabrixIndex, + .authMode = Access::AuthMode::kCase, + .subject = kTestNodeId, +}; + // clang-format off const MockNodeConfig gTestNodeConfig({ MockEndpointConfig(kMockEndpoint1, { @@ -287,3 +314,39 @@ TEST(TestCodegenModelViaMocks, GetAttributeInfo) ASSERT_TRUE(info.has_value()); EXPECT_TRUE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) } + +TEST(TestCodegenModelViaMocks, EmberAttributeRead) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel::Model model; + + + // TODO: AccessControl init how? + // Access::GetAccessControl().Init(delegate, resolver); + + ReadAttributeRequest readRequest; + + // operationFlags is 0 i.e. not internal + // readFlags is 0 i.e. not fabric filtered + // dataVersion is missing (no data version filtering) + readRequest.subjectDescriptor = kCaseSubjectDescriptor; + readRequest.path = ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10)); + + std::optional info = model.GetClusterInfo(readRequest.path); + ASSERT_TRUE(info.has_value()); + + DataVersion dataVersion = info->dataVersion; // NOLINT(bugprone-unchecked-optional-access) + + uint8_t tlvBuffer[1024]; + + TLV::TLVWriter tlvWriter; + tlvWriter.Init(tlvBuffer); + + AttributeReportIBs::Builder builder; + CHIP_ERROR err = builder.Init(&tlvWriter); + ASSERT_EQ(err, CHIP_NO_ERROR); + AttributeValueEncoder encoder(builder, kCaseSubjectDescriptor, readRequest.path, dataVersion); + + err = model.ReadAttribute(readRequest, encoder); + ASSERT_EQ(err, CHIP_NO_ERROR); +} From 27e106a520eb7e73aa6590e2a258dbda61245c79 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 14 May 2024 09:42:49 -0400 Subject: [PATCH 039/123] Model renames --- src/app/codegen-interaction-model/BUILD.gn | 4 +- src/app/codegen-interaction-model/Model.cpp | 267 ------------------ src/app/codegen-interaction-model/Model.h | 52 ---- src/app/codegen-interaction-model/model.gni | 4 +- .../tests/TestCodegenModelViaMocks.cpp | 12 +- src/app/interaction-model/IterationTypes.h | 2 +- 6 files changed, 11 insertions(+), 330 deletions(-) delete mode 100644 src/app/codegen-interaction-model/Model.cpp delete mode 100644 src/app/codegen-interaction-model/Model.h diff --git a/src/app/codegen-interaction-model/BUILD.gn b/src/app/codegen-interaction-model/BUILD.gn index 8b859afc6b2e48..65a672076e4a8a 100644 --- a/src/app/codegen-interaction-model/BUILD.gn +++ b/src/app/codegen-interaction-model/BUILD.gn @@ -19,8 +19,8 @@ import("//build_overrides/chip.gni") # be available at link time for this model to use # # Use `model.gni` to get access to: -# Model.cpp -# Model.h +# CodegenDataModel.cpp +# CodegenDataModel.h # # The abolve list of files exists to satisfy the "dependency linter" # since those files should technically be "visible to gn" even though we diff --git a/src/app/codegen-interaction-model/Model.cpp b/src/app/codegen-interaction-model/Model.cpp deleted file mode 100644 index dc3fbd9cbca7d0..00000000000000 --- a/src/app/codegen-interaction-model/Model.cpp +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (c) 2024 Project CHIP Authors - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include - -#include -#include -#include - -namespace chip { -namespace app { -namespace CodegenDataModel { - -namespace { - -/// Checks if the specified ember cluster mask corresponds to a valid -/// server cluster. -bool IsServerMask(EmberAfClusterMask mask) -{ - return (mask == 0) || ((mask & CLUSTER_MASK_SERVER) != 0); -} - -/// Load the cluster information into the specified destination -void LoadClusterInfo(const ConcreteClusterPath & path, const EmberAfCluster & cluster, InteractionModel::ClusterInfo * info) -{ - chip::DataVersion * versionPtr = emberAfDataVersionStorage(path); - if (versionPtr != nullptr) - { - info->dataVersion = *versionPtr; - } - else - { - ChipLogError(AppServer, "Failed to get data version for %d/" ChipLogFormatMEI, static_cast(path.mEndpointId), - ChipLogValueMEI(cluster.clusterId)); - info->dataVersion = 0; - } - - // TODO: set entry flags: - // info->flags.Set(ClusterQualityFlags::kDiagnosticsData) -} - -/// Converts a EmberAfCluster into a ClusterEntry -InteractionModel::ClusterEntry ClusterEntryFrom(EndpointId endpointId, const EmberAfCluster & cluster) -{ - InteractionModel::ClusterEntry entry; - - entry.path = ConcreteClusterPath(endpointId, cluster.clusterId); - LoadClusterInfo(entry.path, cluster, &entry.info); - - return entry; -} - -/// Finds the first server cluster entry for the given endpoint data starting at [start_index] -/// -/// Returns an invalid entry if no more server clusters are found -InteractionModel::ClusterEntry FirstServerClusterEntry(EndpointId endpointId, const EmberAfEndpointType * endpoint, - uint16_t start_index) -{ - for (unsigned i = start_index; i < endpoint->clusterCount; i++) - { - const EmberAfCluster & cluster = endpoint->cluster[i]; - if (!IsServerMask(cluster.mask & CLUSTER_MASK_SERVER)) - { - continue; - } - - return ClusterEntryFrom(endpointId, cluster); - } - - return InteractionModel::ClusterEntry::Invalid(); -} - -/// Load the cluster information into the specified destination -void LoadAttributeInfo(const ConcreteAttributePath & path, const EmberAfAttributeMetadata & attribute, - InteractionModel::AttributeInfo * info) -{ - if (attribute.attributeType == ZCL_ARRAY_ATTRIBUTE_TYPE) - { - info->flags.Set(InteractionModel::AttributeQualityFlags::kListAttribute); - } - - // TODO: Set additional flags: - // info->flags.Set(InteractionModel::AttributeQualityFlags::kChangesOmitted) -} - -InteractionModel::AttributeEntry AttributeEntryFrom(const ConcreteClusterPath & clusterPath, - const EmberAfAttributeMetadata & attribute) -{ - InteractionModel::AttributeEntry entry; - - entry.path = ConcreteAttributePath(clusterPath.mEndpointId, clusterPath.mClusterId, attribute.attributeId); - LoadAttributeInfo(entry.path, attribute, &entry.info); - - return entry; -} - -} // namespace - -CHIP_ERROR Model::ReadAttribute(const InteractionModel::ReadAttributeRequest & request, InteractionModel::ReadState & state, - AttributeValueEncoder & encoder) -{ - // TODO: this needs an implementation - return CHIP_ERROR_NOT_IMPLEMENTED; -} - -CHIP_ERROR Model::WriteAttribute(const InteractionModel::WriteAttributeRequest & request, AttributeValueDecoder & decoder) -{ - // TODO: this needs an implementation - return CHIP_ERROR_NOT_IMPLEMENTED; -} - -CHIP_ERROR Model::Invoke(const InteractionModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, - InteractionModel::InvokeReply & reply) -{ - // TODO: this needs an implementation - return CHIP_ERROR_NOT_IMPLEMENTED; -} - -EndpointId Model::FirstEndpoint() -{ - // find the first enabled index - const uint16_t lastEndpointIndex = emberAfEndpointCount(); - for (uint16_t endpointIndex = 0; endpointIndex < lastEndpointIndex; endpointIndex++) - { - if (emberAfEndpointIndexIsEnabled(endpointIndex)) - { - return emberAfEndpointFromIndex(endpointIndex); - } - } - - // No enabled endpoint found. Give up - return kInvalidEndpointId; -} - -EndpointId Model::NextEndpoint(EndpointId before) -{ - // find the first enabled index - bool beforeFound = false; - - const uint16_t lastEndpointIndex = emberAfEndpointCount(); - for (uint16_t endpointIndex = 0; endpointIndex < lastEndpointIndex; endpointIndex++) - { - if (!beforeFound) - { - beforeFound = (before == emberAfEndpointFromIndex(endpointIndex)); - continue; - } - - if (emberAfEndpointIndexIsEnabled(endpointIndex)) - { - return emberAfEndpointFromIndex(endpointIndex); - } - } - - // No enabled enpoint after "before" was found, give up - return kInvalidEndpointId; -} - -InteractionModel::ClusterEntry Model::FirstCluster(EndpointId endpointId) -{ - const EmberAfEndpointType * endpoint = emberAfFindEndpointType(endpointId); - VerifyOrReturnValue(endpoint != nullptr, InteractionModel::ClusterEntry::Invalid()); - VerifyOrReturnValue(endpoint->clusterCount > 0, InteractionModel::ClusterEntry::Invalid()); - VerifyOrReturnValue(endpoint->cluster != nullptr, InteractionModel::ClusterEntry::Invalid()); - - return FirstServerClusterEntry(endpointId, endpoint, 0); -} - -InteractionModel::ClusterEntry Model::NextCluster(const ConcreteClusterPath & before) -{ - const EmberAfEndpointType * endpoint = emberAfFindEndpointType(before.mEndpointId); - VerifyOrReturnValue(endpoint != nullptr, InteractionModel::ClusterEntry::Invalid()); - VerifyOrReturnValue(endpoint->clusterCount > 0, InteractionModel::ClusterEntry::Invalid()); - VerifyOrReturnValue(endpoint->cluster != nullptr, InteractionModel::ClusterEntry::Invalid()); - - for (uint16_t i = 0; i < endpoint->clusterCount; i++) - { - const EmberAfCluster & cluster = endpoint->cluster[i]; - if (IsServerMask(cluster.mask) && (cluster.clusterId == before.mClusterId)) - { - return FirstServerClusterEntry(before.mEndpointId, endpoint, i + 1); - } - } - - return InteractionModel::ClusterEntry::Invalid(); -} - -std::optional Model::GetClusterInfo(const ConcreteClusterPath & path) -{ - const EmberAfCluster * cluster = emberAfFindServerCluster(path.mEndpointId, path.mClusterId); - VerifyOrReturnValue(cluster != nullptr, std::nullopt); - - InteractionModel::ClusterInfo info; - LoadClusterInfo(path, *cluster, &info); - - return std::make_optional(info); -} - -InteractionModel::AttributeEntry Model::FirstAttribute(const ConcreteClusterPath & path) -{ - const EmberAfCluster * cluster = emberAfFindServerCluster(path.mEndpointId, path.mClusterId); - VerifyOrReturnValue(cluster != nullptr, InteractionModel::AttributeEntry::Invalid()); - VerifyOrReturnValue(cluster->attributeCount > 0, InteractionModel::AttributeEntry::Invalid()); - VerifyOrReturnValue(cluster->attributes != nullptr, InteractionModel::AttributeEntry::Invalid()); - - return AttributeEntryFrom(path, cluster->attributes[0]); -} - -InteractionModel::AttributeEntry Model::NextAttribute(const ConcreteAttributePath & before) -{ - const EmberAfCluster * cluster = emberAfFindServerCluster(before.mEndpointId, before.mClusterId); - VerifyOrReturnValue(cluster != nullptr, InteractionModel::AttributeEntry::Invalid()); - VerifyOrReturnValue(cluster->attributeCount > 0, InteractionModel::AttributeEntry::Invalid()); - VerifyOrReturnValue(cluster->attributes != nullptr, InteractionModel::AttributeEntry::Invalid()); - - // find the given attribute in the list and then return the next one - bool foundPosition = false; - const unsigned attributeCount = cluster->attributeCount; - for (unsigned i = 0; i < attributeCount; i++) - { - if (foundPosition) - { - return AttributeEntryFrom(before, cluster->attributes[i]); - } - - foundPosition = (cluster->attributes[i].attributeId == before.mAttributeId); - } - - return InteractionModel::AttributeEntry::Invalid(); -} - -std::optional Model::GetAttributeInfo(const ConcreteAttributePath & path) -{ - const EmberAfCluster * cluster = emberAfFindServerCluster(path.mEndpointId, path.mClusterId); - VerifyOrReturnValue(cluster != nullptr, std::nullopt); - VerifyOrReturnValue(cluster->attributeCount > 0, std::nullopt); - VerifyOrReturnValue(cluster->attributes != nullptr, std::nullopt); - const unsigned attributeCount = cluster->attributeCount; - for (unsigned i = 0; i < attributeCount; i++) - { - if (cluster->attributes[i].attributeId == path.mAttributeId) - { - InteractionModel::AttributeInfo info; - LoadAttributeInfo(path, cluster->attributes[i], &info); - return std::make_optional(info); - } - } - - return std::nullopt; -} - -} // namespace CodegenDataModel -} // namespace app -} // namespace chip diff --git a/src/app/codegen-interaction-model/Model.h b/src/app/codegen-interaction-model/Model.h deleted file mode 100644 index 3f48970852a7bc..00000000000000 --- a/src/app/codegen-interaction-model/Model.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2024 Project CHIP Authors - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#pragma once - -#include - -namespace chip { -namespace app { -namespace CodegenDataModel { - -class Model : public chip::app::InteractionModel::Model -{ -public: - /// Generic model implementations - CHIP_ERROR Shutdown() override { return CHIP_NO_ERROR; } - - CHIP_ERROR ReadAttribute(const InteractionModel::ReadAttributeRequest & request, InteractionModel::ReadState & state, - AttributeValueEncoder & encoder) override; - CHIP_ERROR WriteAttribute(const InteractionModel::WriteAttributeRequest & request, AttributeValueDecoder & decoder) override; - CHIP_ERROR Invoke(const InteractionModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, - InteractionModel::InvokeReply & reply) override; - - /// attribute tree iteration - EndpointId FirstEndpoint() override; - EndpointId NextEndpoint(EndpointId before) override; - - InteractionModel::ClusterEntry FirstCluster(EndpointId endpoint) override; - InteractionModel::ClusterEntry NextCluster(const ConcreteClusterPath & before) override; - std::optional GetClusterInfo(const ConcreteClusterPath & path) override; - - InteractionModel::AttributeEntry FirstAttribute(const ConcreteClusterPath & cluster) override; - InteractionModel::AttributeEntry NextAttribute(const ConcreteAttributePath & before) override; - std::optional GetAttributeInfo(const ConcreteAttributePath & path) override; -}; - -} // namespace CodegenDataModel -} // namespace app -} // namespace chip diff --git a/src/app/codegen-interaction-model/model.gni b/src/app/codegen-interaction-model/model.gni index ddf7b1b172cc5a..f44beefd94079b 100644 --- a/src/app/codegen-interaction-model/model.gni +++ b/src/app/codegen-interaction-model/model.gni @@ -25,8 +25,8 @@ import("//build_overrides/chip.gni") # be cleanly built as a stand-alone and instead have to be imported as part of # a different data model or compilation unit. codegen_interaction_model_SOURCES = [ - "${chip_root}/src/app/codegen-interaction-model/Model.h", - "${chip_root}/src/app/codegen-interaction-model/Model.cpp", + "${chip_root}/src/app/codegen-interaction-model/CodegenDataModel.h", + "${chip_root}/src/app/codegen-interaction-model/CodegenDataModel.cpp", ] codegen_interaction_model_PUBLIC_DEPS = [ diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index a8b69e797a0d6f..9904939b5b0525 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -15,7 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include +#include #include #include @@ -92,7 +92,7 @@ struct UseMockNodeConfig TEST(TestCodegenModelViaMocks, IterateOverEndpoints) { UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel::Model model; + chip::app::CodegenDataModel model; // This iteration relies on the hard-coding that occurs when mock_ember is used EXPECT_EQ(model.FirstEndpoint(), kMockEndpoint1); @@ -116,7 +116,7 @@ TEST(TestCodegenModelViaMocks, IterateOverEndpoints) TEST(TestCodegenModelViaMocks, IterateOverClusters) { UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel::Model model; + chip::app::CodegenDataModel model; chip::Test::ResetVersion(); @@ -176,7 +176,7 @@ TEST(TestCodegenModelViaMocks, GetClusterInfo) { UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel::Model model; + chip::app::CodegenDataModel model; chip::Test::ResetVersion(); @@ -201,7 +201,7 @@ TEST(TestCodegenModelViaMocks, GetClusterInfo) TEST(TestCodegenModelViaMocks, IterateOverAttributes) { UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel::Model model; + chip::app::CodegenDataModel model; // invalid paths should return in "no more data" ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).path.HasValidIds()); @@ -266,7 +266,7 @@ TEST(TestCodegenModelViaMocks, IterateOverAttributes) TEST(TestCodegenModelViaMocks, GetAttributeInfo) { UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel::Model model; + chip::app::CodegenDataModel model; // various non-existent or invalid paths should return no info data ASSERT_FALSE( diff --git a/src/app/interaction-model/IterationTypes.h b/src/app/interaction-model/IterationTypes.h index c948d4bcb428f8..68862ed40f5bc6 100644 --- a/src/app/interaction-model/IterationTypes.h +++ b/src/app/interaction-model/IterationTypes.h @@ -35,7 +35,7 @@ enum class ClusterQualityFlags : uint32_t struct ClusterInfo { - DataVersion dataVersion = 0; // current version of this cluster + DataVersion dataVersion = 0; // current cluster data version BitFlags flags; }; From 5b73a1de1c16dd10e448c99e150e8631370bf1ad Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 14 May 2024 09:46:13 -0400 Subject: [PATCH 040/123] Add renamed files --- .../CodegenDataModel.cpp | 264 ++++++++++++++++++ .../CodegenDataModel.h | 50 ++++ 2 files changed, 314 insertions(+) create mode 100644 src/app/codegen-interaction-model/CodegenDataModel.cpp create mode 100644 src/app/codegen-interaction-model/CodegenDataModel.h diff --git a/src/app/codegen-interaction-model/CodegenDataModel.cpp b/src/app/codegen-interaction-model/CodegenDataModel.cpp new file mode 100644 index 00000000000000..ab24dd265ac682 --- /dev/null +++ b/src/app/codegen-interaction-model/CodegenDataModel.cpp @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +#include +#include +#include + +namespace chip { +namespace app { +namespace { + +/// Checks if the specified ember cluster mask corresponds to a valid +/// server cluster. +bool IsServerMask(EmberAfClusterMask mask) +{ + return (mask == 0) || ((mask & CLUSTER_MASK_SERVER) != 0); +} + +/// Load the cluster information into the specified destination +void LoadClusterInfo(const ConcreteClusterPath & path, const EmberAfCluster & cluster, InteractionModel::ClusterInfo * info) +{ + chip::DataVersion * versionPtr = emberAfDataVersionStorage(path); + if (versionPtr != nullptr) + { + info->dataVersion = *versionPtr; + } + else + { + ChipLogError(AppServer, "Failed to get data version for %d/" ChipLogFormatMEI, static_cast(path.mEndpointId), + ChipLogValueMEI(cluster.clusterId)); + info->dataVersion = 0; + } + + // TODO: set entry flags: + // info->flags.Set(ClusterQualityFlags::kDiagnosticsData) +} + +/// Converts a EmberAfCluster into a ClusterEntry +InteractionModel::ClusterEntry ClusterEntryFrom(EndpointId endpointId, const EmberAfCluster & cluster) +{ + InteractionModel::ClusterEntry entry; + + entry.path = ConcreteClusterPath(endpointId, cluster.clusterId); + LoadClusterInfo(entry.path, cluster, &entry.info); + + return entry; +} + +/// Finds the first server cluster entry for the given endpoint data starting at [start_index] +/// +/// Returns an invalid entry if no more server clusters are found +InteractionModel::ClusterEntry FirstServerClusterEntry(EndpointId endpointId, const EmberAfEndpointType * endpoint, + uint16_t start_index) +{ + for (unsigned cluster_idx = start_index; cluster_idx < endpoint->clusterCount; cluster_idx++) + { + const EmberAfCluster & cluster = endpoint->cluster[cluster_idx]; + if (!IsServerMask(cluster.mask & CLUSTER_MASK_SERVER)) + { + continue; + } + + return ClusterEntryFrom(endpointId, cluster); + } + + return InteractionModel::ClusterEntry::Invalid(); +} + +/// Load the cluster information into the specified destination +void LoadAttributeInfo(const ConcreteAttributePath & path, const EmberAfAttributeMetadata & attribute, + InteractionModel::AttributeInfo * info) +{ + if (attribute.attributeType == ZCL_ARRAY_ATTRIBUTE_TYPE) + { + info->flags.Set(InteractionModel::AttributeQualityFlags::kListAttribute); + } + + // TODO: Set additional flags: + // info->flags.Set(InteractionModel::AttributeQualityFlags::kChangesOmitted) +} + +InteractionModel::AttributeEntry AttributeEntryFrom(const ConcreteClusterPath & clusterPath, + const EmberAfAttributeMetadata & attribute) +{ + InteractionModel::AttributeEntry entry; + + entry.path = ConcreteAttributePath(clusterPath.mEndpointId, clusterPath.mClusterId, attribute.attributeId); + LoadAttributeInfo(entry.path, attribute, &entry.info); + + return entry; +} + +} // namespace + +CHIP_ERROR CodegenDataModel::ReadAttribute(const InteractionModel::ReadAttributeRequest & request, InteractionModel::ReadState & state, + AttributeValueEncoder & encoder) +{ + // TODO: this needs an implementation + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR CodegenDataModel::WriteAttribute(const InteractionModel::WriteAttributeRequest & request, AttributeValueDecoder & decoder) +{ + // TODO: this needs an implementation + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR CodegenDataModel::Invoke(const InteractionModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, + InteractionModel::InvokeReply & reply) +{ + // TODO: this needs an implementation + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +EndpointId CodegenDataModel::FirstEndpoint() +{ + // find the first enabled index + const uint16_t lastEndpointIndex = emberAfEndpointCount(); + for (uint16_t endpoint_idx = 0; endpoint_idx < lastEndpointIndex; endpoint_idx++) + { + if (emberAfEndpointIndexIsEnabled(endpoint_idx)) + { + return emberAfEndpointFromIndex(endpoint_idx); + } + } + + // No enabled endpoint found. Give up + return kInvalidEndpointId; +} + +EndpointId CodegenDataModel::NextEndpoint(EndpointId before) +{ + // find the first enabled index + bool beforeFound = false; + + const uint16_t lastEndpointIndex = emberAfEndpointCount(); + for (uint16_t endpoint_idx = 0; endpoint_idx < lastEndpointIndex; endpoint_idx++) + { + if (!beforeFound) + { + beforeFound = (before == emberAfEndpointFromIndex(endpoint_idx)); + continue; + } + + if (emberAfEndpointIndexIsEnabled(endpoint_idx)) + { + return emberAfEndpointFromIndex(endpoint_idx); + } + } + + // No enabled enpoint after "before" was found, give up + return kInvalidEndpointId; +} + +InteractionModel::ClusterEntry CodegenDataModel::FirstCluster(EndpointId endpointId) +{ + const EmberAfEndpointType * endpoint = emberAfFindEndpointType(endpointId); + VerifyOrReturnValue(endpoint != nullptr, InteractionModel::ClusterEntry::Invalid()); + VerifyOrReturnValue(endpoint->clusterCount > 0, InteractionModel::ClusterEntry::Invalid()); + VerifyOrReturnValue(endpoint->cluster != nullptr, InteractionModel::ClusterEntry::Invalid()); + + return FirstServerClusterEntry(endpointId, endpoint, 0); +} + +InteractionModel::ClusterEntry CodegenDataModel::NextCluster(const ConcreteClusterPath & before) +{ + const EmberAfEndpointType * endpoint = emberAfFindEndpointType(before.mEndpointId); + VerifyOrReturnValue(endpoint != nullptr, InteractionModel::ClusterEntry::Invalid()); + VerifyOrReturnValue(endpoint->clusterCount > 0, InteractionModel::ClusterEntry::Invalid()); + VerifyOrReturnValue(endpoint->cluster != nullptr, InteractionModel::ClusterEntry::Invalid()); + + for (uint16_t cluster_idx = 0; cluster_idx < endpoint->clusterCount; cluster_idx++) + { + const EmberAfCluster & cluster = endpoint->cluster[cluster_idx]; + if (IsServerMask(cluster.mask) && (cluster.clusterId == before.mClusterId)) + { + return FirstServerClusterEntry(before.mEndpointId, endpoint, cluster_idx + 1); + } + } + + return InteractionModel::ClusterEntry::Invalid(); +} + +std::optional CodegenDataModel::GetClusterInfo(const ConcreteClusterPath & path) +{ + const EmberAfCluster * cluster = emberAfFindServerCluster(path.mEndpointId, path.mClusterId); + VerifyOrReturnValue(cluster != nullptr, std::nullopt); + + InteractionModel::ClusterInfo info; + LoadClusterInfo(path, *cluster, &info); + + return std::make_optional(info); +} + +InteractionModel::AttributeEntry CodegenDataModel::FirstAttribute(const ConcreteClusterPath & path) +{ + const EmberAfCluster * cluster = emberAfFindServerCluster(path.mEndpointId, path.mClusterId); + VerifyOrReturnValue(cluster != nullptr, InteractionModel::AttributeEntry::Invalid()); + VerifyOrReturnValue(cluster->attributeCount > 0, InteractionModel::AttributeEntry::Invalid()); + VerifyOrReturnValue(cluster->attributes != nullptr, InteractionModel::AttributeEntry::Invalid()); + + return AttributeEntryFrom(path, cluster->attributes[0]); +} + +InteractionModel::AttributeEntry CodegenDataModel::NextAttribute(const ConcreteAttributePath & before) +{ + const EmberAfCluster * cluster = emberAfFindServerCluster(before.mEndpointId, before.mClusterId); + VerifyOrReturnValue(cluster != nullptr, InteractionModel::AttributeEntry::Invalid()); + VerifyOrReturnValue(cluster->attributeCount > 0, InteractionModel::AttributeEntry::Invalid()); + VerifyOrReturnValue(cluster->attributes != nullptr, InteractionModel::AttributeEntry::Invalid()); + + // find the given attribute in the list and then return the next one + bool foundPosition = false; + const unsigned attributeCount = cluster->attributeCount; + for (unsigned attribute_idx = 0; attribute_idx < attributeCount; attribute_idx++) + { + if (foundPosition) + { + return AttributeEntryFrom(before, cluster->attributes[attribute_idx]); + } + + foundPosition = (cluster->attributes[i].attributeId == before.mAttributeId); + } + + return InteractionModel::AttributeEntry::Invalid(); +} + +std::optional CodegenDataModel::GetAttributeInfo(const ConcreteAttributePath & path) +{ + const EmberAfCluster * cluster = emberAfFindServerCluster(path.mEndpointId, path.mClusterId); + VerifyOrReturnValue(cluster != nullptr, std::nullopt); + VerifyOrReturnValue(cluster->attributeCount > 0, std::nullopt); + VerifyOrReturnValue(cluster->attributes != nullptr, std::nullopt); + const unsigned attributeCount = cluster->attributeCount; + for (unsigned attribute_idx = 0; attribute_idx < attributeCount; attribute_idx++) + { + if (cluster->attributes[attribute_idx].attributeId == path.mAttributeId) + { + InteractionModel::AttributeInfo info; + LoadAttributeInfo(path, cluster->attributes[attribute_idx], &info); + return std::make_optional(info); + } + } + + return std::nullopt; +} + +} // namespace app +} // namespace chip diff --git a/src/app/codegen-interaction-model/CodegenDataModel.h b/src/app/codegen-interaction-model/CodegenDataModel.h new file mode 100644 index 00000000000000..490404f2e68a4a --- /dev/null +++ b/src/app/codegen-interaction-model/CodegenDataModel.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +namespace chip { +namespace app { + +class CodegenDataModel : public chip::app::InteractionModel::Model +{ +public: + /// Generic model implementations + CHIP_ERROR Shutdown() override { return CHIP_NO_ERROR; } + + CHIP_ERROR ReadAttribute(const InteractionModel::ReadAttributeRequest & request, InteractionModel::ReadState & state, + AttributeValueEncoder & encoder) override; + CHIP_ERROR WriteAttribute(const InteractionModel::WriteAttributeRequest & request, AttributeValueDecoder & decoder) override; + CHIP_ERROR Invoke(const InteractionModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, + InteractionModel::InvokeReply & reply) override; + + /// attribute tree iteration + EndpointId FirstEndpoint() override; + EndpointId NextEndpoint(EndpointId before) override; + + InteractionModel::ClusterEntry FirstCluster(EndpointId endpoint) override; + InteractionModel::ClusterEntry NextCluster(const ConcreteClusterPath & before) override; + std::optional GetClusterInfo(const ConcreteClusterPath & path) override; + + InteractionModel::AttributeEntry FirstAttribute(const ConcreteClusterPath & cluster) override; + InteractionModel::AttributeEntry NextAttribute(const ConcreteAttributePath & before) override; + std::optional GetAttributeInfo(const ConcreteAttributePath & path) override; +}; + +} // namespace app +} // namespace chip From 9bf0f85f4d9285e047c30e0b45c757bae3b9c38c Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 14 May 2024 09:51:21 -0400 Subject: [PATCH 041/123] Add some attribute iteration hint --- .../CodegenDataModel.cpp | 23 ++++++++++++++----- .../CodegenDataModel.h | 8 +++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/app/codegen-interaction-model/CodegenDataModel.cpp b/src/app/codegen-interaction-model/CodegenDataModel.cpp index ab24dd265ac682..a322aa8c489477 100644 --- a/src/app/codegen-interaction-model/CodegenDataModel.cpp +++ b/src/app/codegen-interaction-model/CodegenDataModel.cpp @@ -107,21 +107,22 @@ InteractionModel::AttributeEntry AttributeEntryFrom(const ConcreteClusterPath & } // namespace -CHIP_ERROR CodegenDataModel::ReadAttribute(const InteractionModel::ReadAttributeRequest & request, InteractionModel::ReadState & state, - AttributeValueEncoder & encoder) +CHIP_ERROR CodegenDataModel::ReadAttribute(const InteractionModel::ReadAttributeRequest & request, + InteractionModel::ReadState & state, AttributeValueEncoder & encoder) { // TODO: this needs an implementation return CHIP_ERROR_NOT_IMPLEMENTED; } -CHIP_ERROR CodegenDataModel::WriteAttribute(const InteractionModel::WriteAttributeRequest & request, AttributeValueDecoder & decoder) +CHIP_ERROR CodegenDataModel::WriteAttribute(const InteractionModel::WriteAttributeRequest & request, + AttributeValueDecoder & decoder) { // TODO: this needs an implementation return CHIP_ERROR_NOT_IMPLEMENTED; } CHIP_ERROR CodegenDataModel::Invoke(const InteractionModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, - InteractionModel::InvokeReply & reply) + InteractionModel::InvokeReply & reply) { // TODO: this needs an implementation return CHIP_ERROR_NOT_IMPLEMENTED; @@ -227,14 +228,24 @@ InteractionModel::AttributeEntry CodegenDataModel::NextAttribute(const ConcreteA // find the given attribute in the list and then return the next one bool foundPosition = false; const unsigned attributeCount = cluster->attributeCount; - for (unsigned attribute_idx = 0; attribute_idx < attributeCount; attribute_idx++) + unsigned startIdx = 0; + + // Attempt to use the hint + if ((mAttributeIterationHint < attributeCount) && + (cluster->attributes[mAttributeIterationHint].attributeId == before.mAttributeId)) + { + startIdx = mAttributeIterationHint; + } + + for (unsigned attribute_idx = startIdx; attribute_idx < attributeCount; attribute_idx++) { if (foundPosition) { + mAttributeIterationHint = attribute_idx; return AttributeEntryFrom(before, cluster->attributes[attribute_idx]); } - foundPosition = (cluster->attributes[i].attributeId == before.mAttributeId); + foundPosition = (cluster->attributes[attribute_idx].attributeId == before.mAttributeId); } return InteractionModel::AttributeEntry::Invalid(); diff --git a/src/app/codegen-interaction-model/CodegenDataModel.h b/src/app/codegen-interaction-model/CodegenDataModel.h index 490404f2e68a4a..ae70ce6b997d92 100644 --- a/src/app/codegen-interaction-model/CodegenDataModel.h +++ b/src/app/codegen-interaction-model/CodegenDataModel.h @@ -44,6 +44,14 @@ class CodegenDataModel : public chip::app::InteractionModel::Model InteractionModel::AttributeEntry FirstAttribute(const ConcreteClusterPath & cluster) override; InteractionModel::AttributeEntry NextAttribute(const ConcreteAttributePath & before) override; std::optional GetAttributeInfo(const ConcreteAttributePath & path) override; + +private: + // Iteration is often done in a tight loop going through all values. + // To avoid N^2 iterations, cache a hint of where something is positioned + // uint16_t mEndpointIterationHint = 0; + // uint16_t mClusterIterationHint = 0; + unsigned mAttributeIterationHint = 0; + }; } // namespace app From 64f0db7afbc3440df5242c4d6c3c4b808413fd5e Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 14 May 2024 10:39:13 -0400 Subject: [PATCH 042/123] Make use of the attribute cache --- .../CodegenDataModel.cpp | 65 +++++++++++-------- .../CodegenDataModel.h | 4 ++ 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/src/app/codegen-interaction-model/CodegenDataModel.cpp b/src/app/codegen-interaction-model/CodegenDataModel.cpp index a322aa8c489477..371d9d4b14c406 100644 --- a/src/app/codegen-interaction-model/CodegenDataModel.cpp +++ b/src/app/codegen-interaction-model/CodegenDataModel.cpp @@ -218,6 +218,29 @@ InteractionModel::AttributeEntry CodegenDataModel::FirstAttribute(const Concrete return AttributeEntryFrom(path, cluster->attributes[0]); } +std::optional CodegenDataModel::TryFindAttributeIndex(const EmberAfCluster * cluster, chip::AttributeId id) const +{ + const unsigned attributeCount = cluster->attributeCount; + + // attempt to find this based on the embedded hint + if ((mAttributeIterationHint < attributeCount) && (cluster->attributes[mAttributeIterationHint].attributeId == id)) + { + return std::make_optional(mAttributeIterationHint); + } + + // linear search is required. This may be slow + for (unsigned attribute_idx = 0; attribute_idx < attributeCount; attribute_idx++) + { + + if (cluster->attributes[attribute_idx].attributeId == id) + { + return std::make_optional(attribute_idx); + } + } + + return std::nullopt; +} + InteractionModel::AttributeEntry CodegenDataModel::NextAttribute(const ConcreteAttributePath & before) { const EmberAfCluster * cluster = emberAfFindServerCluster(before.mEndpointId, before.mClusterId); @@ -226,29 +249,20 @@ InteractionModel::AttributeEntry CodegenDataModel::NextAttribute(const ConcreteA VerifyOrReturnValue(cluster->attributes != nullptr, InteractionModel::AttributeEntry::Invalid()); // find the given attribute in the list and then return the next one - bool foundPosition = false; - const unsigned attributeCount = cluster->attributeCount; - unsigned startIdx = 0; - - // Attempt to use the hint - if ((mAttributeIterationHint < attributeCount) && - (cluster->attributes[mAttributeIterationHint].attributeId == before.mAttributeId)) + std::optional attribute_idx = TryFindAttributeIndex(cluster, before.mAttributeId); + if (!attribute_idx.has_value()) { - startIdx = mAttributeIterationHint; + return InteractionModel::AttributeEntry::Invalid(); } - for (unsigned attribute_idx = startIdx; attribute_idx < attributeCount; attribute_idx++) + unsigned next_idx = *attribute_idx + 1; + if (next_idx >= cluster->attributeCount) { - if (foundPosition) - { - mAttributeIterationHint = attribute_idx; - return AttributeEntryFrom(before, cluster->attributes[attribute_idx]); - } - - foundPosition = (cluster->attributes[attribute_idx].attributeId == before.mAttributeId); + return InteractionModel::AttributeEntry::Invalid(); } - return InteractionModel::AttributeEntry::Invalid(); + mAttributeIterationHint = next_idx; + return AttributeEntryFrom(before, cluster->attributes[next_idx]); } std::optional CodegenDataModel::GetAttributeInfo(const ConcreteAttributePath & path) @@ -257,18 +271,17 @@ std::optional CodegenDataModel::GetAttributeInf VerifyOrReturnValue(cluster != nullptr, std::nullopt); VerifyOrReturnValue(cluster->attributeCount > 0, std::nullopt); VerifyOrReturnValue(cluster->attributes != nullptr, std::nullopt); - const unsigned attributeCount = cluster->attributeCount; - for (unsigned attribute_idx = 0; attribute_idx < attributeCount; attribute_idx++) + + std::optional attribute_idx = TryFindAttributeIndex(cluster, path.mAttributeId); + + if (!attribute_idx.has_value()) { - if (cluster->attributes[attribute_idx].attributeId == path.mAttributeId) - { - InteractionModel::AttributeInfo info; - LoadAttributeInfo(path, cluster->attributes[attribute_idx], &info); - return std::make_optional(info); - } + return std::nullopt; } - return std::nullopt; + InteractionModel::AttributeInfo info; + LoadAttributeInfo(path, cluster->attributes[*attribute_idx], &info); + return std::make_optional(info); } } // namespace app diff --git a/src/app/codegen-interaction-model/CodegenDataModel.h b/src/app/codegen-interaction-model/CodegenDataModel.h index ae70ce6b997d92..88375ebc99356e 100644 --- a/src/app/codegen-interaction-model/CodegenDataModel.h +++ b/src/app/codegen-interaction-model/CodegenDataModel.h @@ -18,6 +18,8 @@ #include +#include + namespace chip { namespace app { @@ -52,6 +54,8 @@ class CodegenDataModel : public chip::app::InteractionModel::Model // uint16_t mClusterIterationHint = 0; unsigned mAttributeIterationHint = 0; + /// Find the index of the given attribute id + std::optional TryFindAttributeIndex(const EmberAfCluster *cluster, chip::AttributeId search_for_id) const; }; } // namespace app From 777ee68114f5da384192a631146f25e445326dfe Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 14 May 2024 10:39:48 -0400 Subject: [PATCH 043/123] Restyle --- src/app/codegen-interaction-model/CodegenDataModel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/codegen-interaction-model/CodegenDataModel.h b/src/app/codegen-interaction-model/CodegenDataModel.h index 88375ebc99356e..8462e48ac99613 100644 --- a/src/app/codegen-interaction-model/CodegenDataModel.h +++ b/src/app/codegen-interaction-model/CodegenDataModel.h @@ -55,7 +55,7 @@ class CodegenDataModel : public chip::app::InteractionModel::Model unsigned mAttributeIterationHint = 0; /// Find the index of the given attribute id - std::optional TryFindAttributeIndex(const EmberAfCluster *cluster, chip::AttributeId search_for_id) const; + std::optional TryFindAttributeIndex(const EmberAfCluster * cluster, chip::AttributeId search_for_id) const; }; } // namespace app From 851e0a4bd81568f6c959969db32ad943ae70544e Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 14 May 2024 10:50:56 -0400 Subject: [PATCH 044/123] Add a cluster iteration hint --- .../CodegenDataModel.cpp | 42 +++++++++++++++---- .../CodegenDataModel.h | 7 +++- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/app/codegen-interaction-model/CodegenDataModel.cpp b/src/app/codegen-interaction-model/CodegenDataModel.cpp index 371d9d4b14c406..5c3b5936e3303e 100644 --- a/src/app/codegen-interaction-model/CodegenDataModel.cpp +++ b/src/app/codegen-interaction-model/CodegenDataModel.cpp @@ -65,7 +65,7 @@ InteractionModel::ClusterEntry ClusterEntryFrom(EndpointId endpointId, const Emb /// /// Returns an invalid entry if no more server clusters are found InteractionModel::ClusterEntry FirstServerClusterEntry(EndpointId endpointId, const EmberAfEndpointType * endpoint, - uint16_t start_index) + uint16_t start_index, unsigned & found_index) { for (unsigned cluster_idx = start_index; cluster_idx < endpoint->clusterCount; cluster_idx++) { @@ -75,6 +75,7 @@ InteractionModel::ClusterEntry FirstServerClusterEntry(EndpointId endpointId, co continue; } + found_index = cluster_idx; return ClusterEntryFrom(endpointId, cluster); } @@ -175,7 +176,33 @@ InteractionModel::ClusterEntry CodegenDataModel::FirstCluster(EndpointId endpoin VerifyOrReturnValue(endpoint->clusterCount > 0, InteractionModel::ClusterEntry::Invalid()); VerifyOrReturnValue(endpoint->cluster != nullptr, InteractionModel::ClusterEntry::Invalid()); - return FirstServerClusterEntry(endpointId, endpoint, 0); + return FirstServerClusterEntry(endpointId, endpoint, 0, mClusterIterationHint); +} + +std::optional CodegenDataModel::TryFindServerClusterIndex(const EmberAfEndpointType * endpoint, chip::ClusterId id) const +{ + const unsigned clusterCount = endpoint->clusterCount; + + if (mClusterIterationHint < clusterCount) + { + const EmberAfCluster & cluster = endpoint->cluster[mClusterIterationHint]; + if (IsServerMask(cluster.mask) && (cluster.clusterId == id)) + { + return std::make_optional(mClusterIterationHint); + } + } + + // linear search, this may be slow + for (unsigned cluster_idx = 0; cluster_idx < clusterCount; cluster_idx++) + { + const EmberAfCluster & cluster = endpoint->cluster[cluster_idx]; + if (IsServerMask(cluster.mask) && (cluster.clusterId == id)) + { + return std::make_optional(cluster_idx); + } + } + + return std::nullopt; } InteractionModel::ClusterEntry CodegenDataModel::NextCluster(const ConcreteClusterPath & before) @@ -185,16 +212,13 @@ InteractionModel::ClusterEntry CodegenDataModel::NextCluster(const ConcreteClust VerifyOrReturnValue(endpoint->clusterCount > 0, InteractionModel::ClusterEntry::Invalid()); VerifyOrReturnValue(endpoint->cluster != nullptr, InteractionModel::ClusterEntry::Invalid()); - for (uint16_t cluster_idx = 0; cluster_idx < endpoint->clusterCount; cluster_idx++) + std::optional cluster_idx = TryFindServerClusterIndex(endpoint, before.mClusterId); + if (!cluster_idx.has_value()) { - const EmberAfCluster & cluster = endpoint->cluster[cluster_idx]; - if (IsServerMask(cluster.mask) && (cluster.clusterId == before.mClusterId)) - { - return FirstServerClusterEntry(before.mEndpointId, endpoint, cluster_idx + 1); - } + return InteractionModel::ClusterEntry::Invalid(); } - return InteractionModel::ClusterEntry::Invalid(); + return FirstServerClusterEntry(before.mEndpointId, endpoint, *cluster_idx + 1, mClusterIterationHint); } std::optional CodegenDataModel::GetClusterInfo(const ConcreteClusterPath & path) diff --git a/src/app/codegen-interaction-model/CodegenDataModel.h b/src/app/codegen-interaction-model/CodegenDataModel.h index 8462e48ac99613..2a5d2b9d7d9a14 100644 --- a/src/app/codegen-interaction-model/CodegenDataModel.h +++ b/src/app/codegen-interaction-model/CodegenDataModel.h @@ -51,11 +51,14 @@ class CodegenDataModel : public chip::app::InteractionModel::Model // Iteration is often done in a tight loop going through all values. // To avoid N^2 iterations, cache a hint of where something is positioned // uint16_t mEndpointIterationHint = 0; - // uint16_t mClusterIterationHint = 0; + unsigned mClusterIterationHint = 0; unsigned mAttributeIterationHint = 0; /// Find the index of the given attribute id - std::optional TryFindAttributeIndex(const EmberAfCluster * cluster, chip::AttributeId search_for_id) const; + std::optional TryFindAttributeIndex(const EmberAfCluster * cluster, chip::AttributeId id) const; + + /// Find the index of the given cluster id + std::optional TryFindServerClusterIndex(const EmberAfEndpointType * endpoint, chip::ClusterId id) const; }; } // namespace app From 5009d8ee4f3232aa99b10506363b48d9f667f358 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 14 May 2024 11:00:27 -0400 Subject: [PATCH 045/123] Add a few more hints. Ember code still contains loops though, so this may not be ideal still --- .../CodegenDataModel.cpp | 42 +++++++++++++++---- .../CodegenDataModel.h | 7 +++- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/app/codegen-interaction-model/CodegenDataModel.cpp b/src/app/codegen-interaction-model/CodegenDataModel.cpp index 5c3b5936e3303e..232355719b04e4 100644 --- a/src/app/codegen-interaction-model/CodegenDataModel.cpp +++ b/src/app/codegen-interaction-model/CodegenDataModel.cpp @@ -19,6 +19,7 @@ #include #include #include +#include namespace chip { namespace app { @@ -145,22 +146,49 @@ EndpointId CodegenDataModel::FirstEndpoint() return kInvalidEndpointId; } -EndpointId CodegenDataModel::NextEndpoint(EndpointId before) +std::optional CodegenDataModel::TryFindEndpointIndex(chip::EndpointId id) const { - // find the first enabled index - bool beforeFound = false; + const unsigned lastEndpointIndex = emberAfEndpointCount(); - const uint16_t lastEndpointIndex = emberAfEndpointCount(); - for (uint16_t endpoint_idx = 0; endpoint_idx < lastEndpointIndex; endpoint_idx++) + if ((mEndpointIterationHint < lastEndpointIndex) && emberAfEndpointIndexIsEnabled(mEndpointIterationHint) && + (id == emberAfEndpointFromIndex(mEndpointIterationHint))) { - if (!beforeFound) + return std::make_optional(mEndpointIterationHint); + } + + // Linear search, this may be slow + for (unsigned endpoint_idx = 0; endpoint_idx < lastEndpointIndex; endpoint_idx++) + { + if (!emberAfEndpointIndexIsEnabled(endpoint_idx)) { - beforeFound = (before == emberAfEndpointFromIndex(endpoint_idx)); continue; } + if (id == emberAfEndpointFromIndex(endpoint_idx)) + { + return std::make_optional(endpoint_idx); + } + } + + return std::nullopt; +} + +EndpointId CodegenDataModel::NextEndpoint(EndpointId before) +{ + const unsigned lastEndpointIndex = emberAfEndpointCount(); + + std::optional before_idx = TryFindEndpointIndex(before); + if (!before_idx.has_value()) + { + return kInvalidEndpointId; + } + + // find the first enabled index + for (unsigned endpoint_idx = *before_idx + 1; endpoint_idx < lastEndpointIndex; endpoint_idx++) + { if (emberAfEndpointIndexIsEnabled(endpoint_idx)) { + mEndpointIterationHint = endpoint_idx; return emberAfEndpointFromIndex(endpoint_idx); } } diff --git a/src/app/codegen-interaction-model/CodegenDataModel.h b/src/app/codegen-interaction-model/CodegenDataModel.h index 2a5d2b9d7d9a14..22a2d0369ae4ca 100644 --- a/src/app/codegen-interaction-model/CodegenDataModel.h +++ b/src/app/codegen-interaction-model/CodegenDataModel.h @@ -50,8 +50,8 @@ class CodegenDataModel : public chip::app::InteractionModel::Model private: // Iteration is often done in a tight loop going through all values. // To avoid N^2 iterations, cache a hint of where something is positioned - // uint16_t mEndpointIterationHint = 0; - unsigned mClusterIterationHint = 0; + uint16_t mEndpointIterationHint = 0; + unsigned mClusterIterationHint = 0; unsigned mAttributeIterationHint = 0; /// Find the index of the given attribute id @@ -59,6 +59,9 @@ class CodegenDataModel : public chip::app::InteractionModel::Model /// Find the index of the given cluster id std::optional TryFindServerClusterIndex(const EmberAfEndpointType * endpoint, chip::ClusterId id) const; + + /// Find the index of the given endpoint id + std::optional TryFindEndpointIndex(chip::EndpointId id) const; }; } // namespace app From 88e5bbcc3de8587952231433b3d8aea442ffbd10 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 14 May 2024 11:06:06 -0400 Subject: [PATCH 046/123] Add some TODO items for using faster iterations for data. Ember index vs value duality still needs some work --- .../CodegenDataModel.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/app/codegen-interaction-model/CodegenDataModel.cpp b/src/app/codegen-interaction-model/CodegenDataModel.cpp index 232355719b04e4..9c435dce9b5b6b 100644 --- a/src/app/codegen-interaction-model/CodegenDataModel.cpp +++ b/src/app/codegen-interaction-model/CodegenDataModel.cpp @@ -235,7 +235,10 @@ std::optional CodegenDataModel::TryFindServerClusterIndex(const EmberA InteractionModel::ClusterEntry CodegenDataModel::NextCluster(const ConcreteClusterPath & before) { + // TODO: This search still seems slow (ember will loop). Should use index hints as long + // as ember API supports it const EmberAfEndpointType * endpoint = emberAfFindEndpointType(before.mEndpointId); + VerifyOrReturnValue(endpoint != nullptr, InteractionModel::ClusterEntry::Invalid()); VerifyOrReturnValue(endpoint->clusterCount > 0, InteractionModel::ClusterEntry::Invalid()); VerifyOrReturnValue(endpoint->cluster != nullptr, InteractionModel::ClusterEntry::Invalid()); @@ -251,7 +254,10 @@ InteractionModel::ClusterEntry CodegenDataModel::NextCluster(const ConcreteClust std::optional CodegenDataModel::GetClusterInfo(const ConcreteClusterPath & path) { + // TODO: This search still seems slow (ember will loop). Should use index hints as long + // as ember API supports it const EmberAfCluster * cluster = emberAfFindServerCluster(path.mEndpointId, path.mClusterId); + VerifyOrReturnValue(cluster != nullptr, std::nullopt); InteractionModel::ClusterInfo info; @@ -262,7 +268,10 @@ std::optional CodegenDataModel::GetClusterInfo(co InteractionModel::AttributeEntry CodegenDataModel::FirstAttribute(const ConcreteClusterPath & path) { + // TODO: This search still seems slow (ember will loop). Should use index hints as long + // as ember API supports it const EmberAfCluster * cluster = emberAfFindServerCluster(path.mEndpointId, path.mClusterId); + VerifyOrReturnValue(cluster != nullptr, InteractionModel::AttributeEntry::Invalid()); VerifyOrReturnValue(cluster->attributeCount > 0, InteractionModel::AttributeEntry::Invalid()); VerifyOrReturnValue(cluster->attributes != nullptr, InteractionModel::AttributeEntry::Invalid()); @@ -295,7 +304,10 @@ std::optional CodegenDataModel::TryFindAttributeIndex(const EmberAfClu InteractionModel::AttributeEntry CodegenDataModel::NextAttribute(const ConcreteAttributePath & before) { + // TODO: This search still seems slow (ember will loop). Should use index hints as long + // as ember API supports it const EmberAfCluster * cluster = emberAfFindServerCluster(before.mEndpointId, before.mClusterId); + VerifyOrReturnValue(cluster != nullptr, InteractionModel::AttributeEntry::Invalid()); VerifyOrReturnValue(cluster->attributeCount > 0, InteractionModel::AttributeEntry::Invalid()); VerifyOrReturnValue(cluster->attributes != nullptr, InteractionModel::AttributeEntry::Invalid()); @@ -319,7 +331,10 @@ InteractionModel::AttributeEntry CodegenDataModel::NextAttribute(const ConcreteA std::optional CodegenDataModel::GetAttributeInfo(const ConcreteAttributePath & path) { + // TODO: This search still seems slow (ember will loop). Should use index hints as long + // as ember API supports it const EmberAfCluster * cluster = emberAfFindServerCluster(path.mEndpointId, path.mClusterId); + VerifyOrReturnValue(cluster != nullptr, std::nullopt); VerifyOrReturnValue(cluster->attributeCount > 0, std::nullopt); VerifyOrReturnValue(cluster->attributes != nullptr, std::nullopt); From 7672d22e178c1dabde670e892811127d9b53b0de Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 14 May 2024 11:17:33 -0400 Subject: [PATCH 047/123] Add a cluster type cache as well. This relies on ember being reasonably static --- .../CodegenDataModel.cpp | 33 +++++++++++-------- .../CodegenDataModel.h | 17 ++++++++++ 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/app/codegen-interaction-model/CodegenDataModel.cpp b/src/app/codegen-interaction-model/CodegenDataModel.cpp index 9c435dce9b5b6b..7c139763d4306a 100644 --- a/src/app/codegen-interaction-model/CodegenDataModel.cpp +++ b/src/app/codegen-interaction-model/CodegenDataModel.cpp @@ -254,9 +254,7 @@ InteractionModel::ClusterEntry CodegenDataModel::NextCluster(const ConcreteClust std::optional CodegenDataModel::GetClusterInfo(const ConcreteClusterPath & path) { - // TODO: This search still seems slow (ember will loop). Should use index hints as long - // as ember API supports it - const EmberAfCluster * cluster = emberAfFindServerCluster(path.mEndpointId, path.mClusterId); + const EmberAfCluster * cluster = FindServerCluster(path); VerifyOrReturnValue(cluster != nullptr, std::nullopt); @@ -268,9 +266,7 @@ std::optional CodegenDataModel::GetClusterInfo(co InteractionModel::AttributeEntry CodegenDataModel::FirstAttribute(const ConcreteClusterPath & path) { - // TODO: This search still seems slow (ember will loop). Should use index hints as long - // as ember API supports it - const EmberAfCluster * cluster = emberAfFindServerCluster(path.mEndpointId, path.mClusterId); + const EmberAfCluster * cluster = FindServerCluster(path); VerifyOrReturnValue(cluster != nullptr, InteractionModel::AttributeEntry::Invalid()); VerifyOrReturnValue(cluster->attributeCount > 0, InteractionModel::AttributeEntry::Invalid()); @@ -302,12 +298,25 @@ std::optional CodegenDataModel::TryFindAttributeIndex(const EmberAfClu return std::nullopt; } -InteractionModel::AttributeEntry CodegenDataModel::NextAttribute(const ConcreteAttributePath & before) +const EmberAfCluster * CodegenDataModel::FindServerCluster(const ConcreteClusterPath & path) { - // TODO: This search still seems slow (ember will loop). Should use index hints as long - // as ember API supports it - const EmberAfCluster * cluster = emberAfFindServerCluster(before.mEndpointId, before.mClusterId); + // cache things + if (mPreviouslyFoundCluster.has_value() && (mPreviouslyFoundCluster->path == path)) + { + return mPreviouslyFoundCluster->cluster; + } + const EmberAfCluster * cluster = emberAfFindServerCluster(path.mEndpointId, path.mClusterId); + if (cluster != nullptr) + { + mPreviouslyFoundCluster = std::make_optional(path, cluster); + } + return cluster; +} + +InteractionModel::AttributeEntry CodegenDataModel::NextAttribute(const ConcreteAttributePath & before) +{ + const EmberAfCluster * cluster = FindServerCluster(before); VerifyOrReturnValue(cluster != nullptr, InteractionModel::AttributeEntry::Invalid()); VerifyOrReturnValue(cluster->attributeCount > 0, InteractionModel::AttributeEntry::Invalid()); VerifyOrReturnValue(cluster->attributes != nullptr, InteractionModel::AttributeEntry::Invalid()); @@ -331,9 +340,7 @@ InteractionModel::AttributeEntry CodegenDataModel::NextAttribute(const ConcreteA std::optional CodegenDataModel::GetAttributeInfo(const ConcreteAttributePath & path) { - // TODO: This search still seems slow (ember will loop). Should use index hints as long - // as ember API supports it - const EmberAfCluster * cluster = emberAfFindServerCluster(path.mEndpointId, path.mClusterId); + const EmberAfCluster * cluster = FindServerCluster(path); VerifyOrReturnValue(cluster != nullptr, std::nullopt); VerifyOrReturnValue(cluster->attributeCount > 0, std::nullopt); diff --git a/src/app/codegen-interaction-model/CodegenDataModel.h b/src/app/codegen-interaction-model/CodegenDataModel.h index 22a2d0369ae4ca..723831b4368dcb 100644 --- a/src/app/codegen-interaction-model/CodegenDataModel.h +++ b/src/app/codegen-interaction-model/CodegenDataModel.h @@ -16,6 +16,7 @@ */ #pragma once +#include "app/ConcreteClusterPath.h" #include #include @@ -54,6 +55,22 @@ class CodegenDataModel : public chip::app::InteractionModel::Model unsigned mClusterIterationHint = 0; unsigned mAttributeIterationHint = 0; + // represents a remembered cluster reference that has been found as + // looking for clusters is very common (for every attribute iteration) + struct ClusterReference + { + ConcreteClusterPath path; + const EmberAfCluster * cluster; + + ClusterReference(const ConcreteClusterPath p, const EmberAfCluster * c) : path(p), cluster(c) {} + }; + std::optional mPreviouslyFoundCluster; + + /// Finds the specified ember cluster + /// + /// Effectively the same as `emberAfFindServerCluster` except with some caching capabilities + const EmberAfCluster * FindServerCluster(const ConcreteClusterPath & path); + /// Find the index of the given attribute id std::optional TryFindAttributeIndex(const EmberAfCluster * cluster, chip::AttributeId id) const; From 23a988d185186f51897124da5b7d58297ff424ed Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 14 May 2024 12:56:04 -0400 Subject: [PATCH 048/123] Add global attribute handling --- .../CodegenDataModel.cpp | 57 +++++++++++++++++-- .../tests/TestCodegenModelViaMocks.cpp | 34 +++++++++++ 2 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/app/codegen-interaction-model/CodegenDataModel.cpp b/src/app/codegen-interaction-model/CodegenDataModel.cpp index 7c139763d4306a..1bb1c1c0085028 100644 --- a/src/app/codegen-interaction-model/CodegenDataModel.cpp +++ b/src/app/codegen-interaction-model/CodegenDataModel.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include namespace chip { @@ -107,6 +108,17 @@ InteractionModel::AttributeEntry AttributeEntryFrom(const ConcreteClusterPath & return entry; } +InteractionModel::AttributeEntry AttributeEntryForGlobalListAttribute(const ConcreteClusterPath & clusterPath, + chip::AttributeId globalAttributeId) +{ + InteractionModel::AttributeEntry entry; + + entry.path = ConcreteAttributePath(clusterPath.mEndpointId, clusterPath.mClusterId, globalAttributeId); + entry.info.flags.Set(InteractionModel::AttributeQualityFlags::kListAttribute); + + return entry; +} + } // namespace CHIP_ERROR CodegenDataModel::ReadAttribute(const InteractionModel::ReadAttributeRequest & request, @@ -321,6 +333,24 @@ InteractionModel::AttributeEntry CodegenDataModel::NextAttribute(const ConcreteA VerifyOrReturnValue(cluster->attributeCount > 0, InteractionModel::AttributeEntry::Invalid()); VerifyOrReturnValue(cluster->attributes != nullptr, InteractionModel::AttributeEntry::Invalid()); + // Handles global attribute iteration: if we got a global attribute, move to the next + switch (before.mAttributeId) + { + case Clusters::Globals::Attributes::GeneratedCommandList::Id: + return AttributeEntryForGlobalListAttribute(before, Clusters::Globals::Attributes::AcceptedCommandList::Id); + case Clusters::Globals::Attributes::AcceptedCommandList::Id: +#if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE + return AttributeEntryForGlobalListAttribute(before, Clusters::Globals::Attributes::EventList::Id); + case Clusters::Globals::Attributes::EventList::Id: +#endif // CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE + return AttributeEntryForGlobalListAttribute(before, Clusters::Globals::Attributes::AttributeList::Id); + case Clusters::Globals::Attributes::AttributeList::Id: + return InteractionModel::AttributeEntry::Invalid(); + default: + // pass-through: not a global attribute, try to find a "regular" attribute + break; + } + // find the given attribute in the list and then return the next one std::optional attribute_idx = TryFindAttributeIndex(cluster, before.mAttributeId); if (!attribute_idx.has_value()) @@ -329,13 +359,15 @@ InteractionModel::AttributeEntry CodegenDataModel::NextAttribute(const ConcreteA } unsigned next_idx = *attribute_idx + 1; - if (next_idx >= cluster->attributeCount) + if (next_idx < cluster->attributeCount) { - return InteractionModel::AttributeEntry::Invalid(); + mAttributeIterationHint = next_idx; + return AttributeEntryFrom(before, cluster->attributes[next_idx]); } - mAttributeIterationHint = next_idx; - return AttributeEntryFrom(before, cluster->attributes[next_idx]); + // We reach here if next_idx is just past the last attribute metadata. Return the first global + // attribute + return AttributeEntryForGlobalListAttribute(before, Clusters::Globals::Attributes::GeneratedCommandList::Id); } std::optional CodegenDataModel::GetAttributeInfo(const ConcreteAttributePath & path) @@ -346,6 +378,23 @@ std::optional CodegenDataModel::GetAttributeInf VerifyOrReturnValue(cluster->attributeCount > 0, std::nullopt); VerifyOrReturnValue(cluster->attributes != nullptr, std::nullopt); + switch (path.mAttributeId) + { + case Clusters::Globals::Attributes::GeneratedCommandList::Id: + case Clusters::Globals::Attributes::AcceptedCommandList::Id: +#if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE + case Clusters::Globals::Attributes::EventList::Id: +#endif // CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE + case Clusters::Globals::Attributes::AttributeList::Id: { + InteractionModel::AttributeInfo info; + info.flags.Set(InteractionModel::AttributeQualityFlags::kListAttribute); + return info; + } + default: + // pass-through: not a global attribute, try to find a "regular" attribute + break; + } + std::optional attribute_idx = TryFindAttributeIndex(cluster, path.mAttributeId); if (!attribute_idx.has_value()) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 9904939b5b0525..c4cd5fa2977e79 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -15,6 +15,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "app/GlobalAttributes.h" +#include "lib/core/DataModelTypes.h" #include #include @@ -238,6 +240,21 @@ TEST(TestCodegenModelViaMocks, IterateOverAttributes) ASSERT_EQ(entry.path.mAttributeId, MockAttributeId(2)); ASSERT_TRUE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); + // Iteration MUST include global attributes. Ember does not provide those, so we + // assert here that we present them in order + for (auto globalAttributeId : GlobalAttributesNotInMetadata) + { + + entry = model.NextAttribute(entry.path); + ASSERT_TRUE(entry.path.HasValidIds()); + ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); + ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); + ASSERT_EQ(entry.path.mAttributeId, globalAttributeId); + + // all global attributes not in ember metadata are LIST typed + ASSERT_TRUE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); + } + entry = model.NextAttribute(entry.path); ASSERT_FALSE(entry.path.HasValidIds()); @@ -288,3 +305,20 @@ TEST(TestCodegenModelViaMocks, GetAttributeInfo) ASSERT_TRUE(info.has_value()); EXPECT_TRUE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) } + +TEST(TestCodegenModelViaMocks, GlobalAttributeInfo) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + + std::optional info = model.GetAttributeInfo( + ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::GeneratedCommandList::Id)); + + ASSERT_TRUE(info.has_value()); + EXPECT_TRUE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) + + info = model.GetAttributeInfo( + ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::AttributeList::Id)); + ASSERT_TRUE(info.has_value()); + EXPECT_TRUE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) +} From f4651fc612bc74be04c3e9911f173d7c8117687b Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 14 May 2024 13:14:08 -0400 Subject: [PATCH 049/123] Fix typing u16 vs unsigned --- src/app/codegen-interaction-model/CodegenDataModel.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/codegen-interaction-model/CodegenDataModel.cpp b/src/app/codegen-interaction-model/CodegenDataModel.cpp index 1bb1c1c0085028..c5bedcb75d68f5 100644 --- a/src/app/codegen-interaction-model/CodegenDataModel.cpp +++ b/src/app/codegen-interaction-model/CodegenDataModel.cpp @@ -67,7 +67,7 @@ InteractionModel::ClusterEntry ClusterEntryFrom(EndpointId endpointId, const Emb /// /// Returns an invalid entry if no more server clusters are found InteractionModel::ClusterEntry FirstServerClusterEntry(EndpointId endpointId, const EmberAfEndpointType * endpoint, - uint16_t start_index, unsigned & found_index) + unsigned start_index, unsigned & found_index) { for (unsigned cluster_idx = start_index; cluster_idx < endpoint->clusterCount; cluster_idx++) { @@ -160,7 +160,7 @@ EndpointId CodegenDataModel::FirstEndpoint() std::optional CodegenDataModel::TryFindEndpointIndex(chip::EndpointId id) const { - const unsigned lastEndpointIndex = emberAfEndpointCount(); + const uint16_t lastEndpointIndex = emberAfEndpointCount(); if ((mEndpointIterationHint < lastEndpointIndex) && emberAfEndpointIndexIsEnabled(mEndpointIterationHint) && (id == emberAfEndpointFromIndex(mEndpointIterationHint))) @@ -169,7 +169,7 @@ std::optional CodegenDataModel::TryFindEndpointIndex(chip::EndpointId } // Linear search, this may be slow - for (unsigned endpoint_idx = 0; endpoint_idx < lastEndpointIndex; endpoint_idx++) + for (uint16_t endpoint_idx = 0; endpoint_idx < lastEndpointIndex; endpoint_idx++) { if (!emberAfEndpointIndexIsEnabled(endpoint_idx)) { @@ -196,7 +196,7 @@ EndpointId CodegenDataModel::NextEndpoint(EndpointId before) } // find the first enabled index - for (unsigned endpoint_idx = *before_idx + 1; endpoint_idx < lastEndpointIndex; endpoint_idx++) + for (uint16_t endpoint_idx = static_cast(*before_idx + 1); endpoint_idx < lastEndpointIndex; endpoint_idx++) { if (emberAfEndpointIndexIsEnabled(endpoint_idx)) { From e1df31b06d85f0cef5d09d0ecf861e0e48d46996 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 14 May 2024 15:59:49 -0400 Subject: [PATCH 050/123] Unit test preparation: make ACL pass and have an ACL test as well --- .../tests/TestCodegenModelViaMocks.cpp | 128 +++++++++++++++++- 1 file changed, 122 insertions(+), 6 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 29b4e478d72f32..e918c4c086e156 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -14,6 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "access/SubjectDescriptor.h" +#include "lib/core/CHIPError.h" #include #include @@ -34,6 +36,7 @@ using namespace chip::app::InteractionModel; using namespace chip::app::Clusters::Globals::Attributes; namespace pw { + template <> StatusWithSize ToString(const CHIP_ERROR & err, pw::span buffer) { @@ -44,6 +47,7 @@ StatusWithSize ToString(const CHIP_ERROR & err, pw::span buffe } return pw::string::Format(buffer, "CHIP_ERROR:<%" CHIP_ERROR_FORMAT ">", err.Format()); } + } // namespace pw namespace { @@ -58,11 +62,76 @@ static_assert(kEndpointIdThatIsMissing != kMockEndpoint1); static_assert(kEndpointIdThatIsMissing != kMockEndpoint2); static_assert(kEndpointIdThatIsMissing != kMockEndpoint3); -constexpr Access::SubjectDescriptor kCaseSubjectDescriptor{ +constexpr Access::SubjectDescriptor kAdminSubjectDescriptor{ .fabricIndex = kTestFabrixIndex, .authMode = Access::AuthMode::kCase, .subject = kTestNodeId, }; +constexpr Access::SubjectDescriptor kViewSubjectDescriptor{ + .fabricIndex = kTestFabrixIndex + 1, + .authMode = Access::AuthMode::kCase, + .subject = kTestNodeId, +}; + +constexpr Access::SubjectDescriptor kDenySubjectDescriptor{ + .fabricIndex = kTestFabrixIndex + 2, + .authMode = Access::AuthMode::kCase, + .subject = kTestNodeId, +}; + +bool operator==(const Access::SubjectDescriptor & a, const Access::SubjectDescriptor & b) +{ + if (a.fabricIndex != b.fabricIndex) + { + return false; + } + if (a.authMode != b.authMode) + { + return false; + } + if (a.subject != b.subject) + { + return false; + } + for (unsigned i = 0; i < a.cats.values.size(); i++) + { + if (a.cats.values[i] != b.cats.values[i]) + { + return false; + } + } + return true; +} + +class MockAccessControl : public Access::AccessControl::Delegate, public Access::AccessControl::DeviceTypeResolver +{ +public: + CHIP_ERROR Check(const Access::SubjectDescriptor & subjectDescriptor, const Access::RequestPath & requestPath, + Access::Privilege requestPrivilege) override + { + if (subjectDescriptor == kAdminSubjectDescriptor) + { + return CHIP_NO_ERROR; + } + if ((subjectDescriptor == kViewSubjectDescriptor) && (requestPrivilege == Access::Privilege::kView)) + { + return CHIP_NO_ERROR; + } + return CHIP_ERROR_ACCESS_DENIED; + } + + bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint) override { return true; } +}; + +class ScopedMockAccessControl +{ +public: + ScopedMockAccessControl() { Access::GetAccessControl().Init(&mMock, mMock); } + ~ScopedMockAccessControl() { Access::GetAccessControl().Finish(); } + +private: + MockAccessControl mMock; +}; // clang-format off const MockNodeConfig gTestNodeConfig({ @@ -315,21 +384,68 @@ TEST(TestCodegenModelViaMocks, GetAttributeInfo) EXPECT_TRUE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) } -TEST(TestCodegenModelViaMocks, EmberAttributeRead) +class ADelegate : public Access::AccessControl::Delegate +{ +public: + virtual CHIP_ERROR Check(const Access::SubjectDescriptor & subjectDescriptor, const Access::RequestPath & requestPath, + Access::Privilege requestPrivilege) + { + // return CHIP_ERROR_ACCESS_DENIED; + return CHIP_NO_ERROR; + } +}; + +class AResolver : public Access::AccessControl::DeviceTypeResolver +{ +public: + bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint) override { return true; } +}; + +TEST(TestCodegenModelViaMocks, EmberAttributeReadAclDeny) { UseMockNodeConfig config(gTestNodeConfig); chip::app::CodegenDataModel::Model model; + ScopedMockAccessControl accessControl; + + ReadAttributeRequest readRequest; + // operationFlags is 0 i.e. not internal + // readFlags is 0 i.e. not fabric filtered + // dataVersion is missing (no data version filtering) + readRequest.subjectDescriptor = kDenySubjectDescriptor; + readRequest.path = ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10)); + + std::optional info = model.GetClusterInfo(readRequest.path); + ASSERT_TRUE(info.has_value()); + + DataVersion dataVersion = info->dataVersion; // NOLINT(bugprone-unchecked-optional-access) - // TODO: AccessControl init how? - // Access::GetAccessControl().Init(delegate, resolver); + uint8_t tlvBuffer[1024]; + + TLV::TLVWriter tlvWriter; + tlvWriter.Init(tlvBuffer); + + AttributeReportIBs::Builder builder; + CHIP_ERROR err = builder.Init(&tlvWriter); + ASSERT_EQ(err, CHIP_NO_ERROR); + AttributeValueEncoder encoder(builder, kAdminSubjectDescriptor, readRequest.path, dataVersion); + + err = model.ReadAttribute(readRequest, encoder); + ASSERT_EQ(err, CHIP_ERROR_ACCESS_DENIED); +} + +TEST(TestCodegenModelViaMocks, EmberAttributeRead) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel::Model model; + ScopedMockAccessControl accessControl; ReadAttributeRequest readRequest; // operationFlags is 0 i.e. not internal // readFlags is 0 i.e. not fabric filtered // dataVersion is missing (no data version filtering) - readRequest.subjectDescriptor = kCaseSubjectDescriptor; + readRequest.subjectDescriptor = kAdminSubjectDescriptor; readRequest.path = ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10)); std::optional info = model.GetClusterInfo(readRequest.path); @@ -345,7 +461,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeRead) AttributeReportIBs::Builder builder; CHIP_ERROR err = builder.Init(&tlvWriter); ASSERT_EQ(err, CHIP_NO_ERROR); - AttributeValueEncoder encoder(builder, kCaseSubjectDescriptor, readRequest.path, dataVersion); + AttributeValueEncoder encoder(builder, kAdminSubjectDescriptor, readRequest.path, dataVersion); err = model.ReadAttribute(readRequest, encoder); ASSERT_EQ(err, CHIP_NO_ERROR); From b923fabb6d5fad6486e65883f8304c14509e3330 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 14 May 2024 16:17:10 -0400 Subject: [PATCH 051/123] Some progress in testability. No mock reads, however at least some progress --- .../InteractionModelTemporaryOverrides.cpp | 10 +++-- .../tests/TestCodegenModelViaMocks.cpp | 39 ++++++++++++++++++- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp b/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp index a7d398f96f3f29..1a9688fd1b2fa3 100644 --- a/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp +++ b/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "lib/support/logging/TextOnlyLogging.h" #include #include #include @@ -86,9 +87,10 @@ void DispatchSingleClusterCommand(const ConcreteCommandPath & aRequestCommandPat Status emAfReadOrWriteAttribute(const EmberAfAttributeSearchRecord * attRecord, const EmberAfAttributeMetadata ** metadata, uint8_t * buffer, uint16_t readLength, bool write) { + ChipLogError(Test, "Read/Write attribute is NOT implemented!!!") - // FIXME: this is supposed to be an ember implementation - // however mock library SHOULD be able to implement it ... it is unclear - // to me why we do not have this... - return Status::NotFound; + // FIXME: this is supposed to be an ember implementation + // however mock library SHOULD be able to implement it ... it is unclear + // to me why we do not have this... + return Status::NotFound; } diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index e918c4c086e156..adbfca3501b3b6 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -434,7 +434,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadAclDeny) ASSERT_EQ(err, CHIP_ERROR_ACCESS_DENIED); } -TEST(TestCodegenModelViaMocks, EmberAttributeRead) +TEST(TestCodegenModelViaMocks, EmberAttributeInvalidRead) { UseMockNodeConfig config(gTestNodeConfig); chip::app::CodegenDataModel::Model model; @@ -463,6 +463,43 @@ TEST(TestCodegenModelViaMocks, EmberAttributeRead) ASSERT_EQ(err, CHIP_NO_ERROR); AttributeValueEncoder encoder(builder, kAdminSubjectDescriptor, readRequest.path, dataVersion); + err = model.ReadAttribute(readRequest, encoder); + ASSERT_EQ(err, CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); + + // TODO: value validation here? +} + +TEST(TestCodegenModelViaMocks, EmberAttributeRead) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel::Model model; + ScopedMockAccessControl accessControl; + + ReadAttributeRequest readRequest; + + // operationFlags is 0 i.e. not internal + // readFlags is 0 i.e. not fabric filtered + // dataVersion is missing (no data version filtering) + readRequest.subjectDescriptor = kAdminSubjectDescriptor; + readRequest.path = ConcreteAttributePath(kMockEndpoint3, MockClusterId(2), MockAttributeId(3)); + + std::optional info = model.GetClusterInfo(readRequest.path); + ASSERT_TRUE(info.has_value()); + + DataVersion dataVersion = info->dataVersion; // NOLINT(bugprone-unchecked-optional-access) + + uint8_t tlvBuffer[1024]; + + TLV::TLVWriter tlvWriter; + tlvWriter.Init(tlvBuffer); + + AttributeReportIBs::Builder builder; + CHIP_ERROR err = builder.Init(&tlvWriter); + ASSERT_EQ(err, CHIP_NO_ERROR); + AttributeValueEncoder encoder(builder, kAdminSubjectDescriptor, readRequest.path, dataVersion); + err = model.ReadAttribute(readRequest, encoder); ASSERT_EQ(err, CHIP_NO_ERROR); + + // TODO: value validation here? } From 126300a9454089d29b302decf77b354f7faa35ec Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 07:57:10 -0400 Subject: [PATCH 052/123] Make some unit tests pass with ember overrides --- .../codegen-interaction-model/tests/BUILD.gn | 2 + .../tests/EmberReadWriteOverride.cpp | 85 +++++++++++++++++++ .../tests/EmberReadWriteOverride.h | 31 +++++++ .../InteractionModelTemporaryOverrides.cpp | 13 --- .../tests/TestCodegenModelViaMocks.cpp | 10 ++- 5 files changed, 125 insertions(+), 16 deletions(-) create mode 100644 src/app/codegen-interaction-model/tests/EmberReadWriteOverride.cpp create mode 100644 src/app/codegen-interaction-model/tests/EmberReadWriteOverride.h diff --git a/src/app/codegen-interaction-model/tests/BUILD.gn b/src/app/codegen-interaction-model/tests/BUILD.gn index 35e712486b161e..172252ed3a6219 100644 --- a/src/app/codegen-interaction-model/tests/BUILD.gn +++ b/src/app/codegen-interaction-model/tests/BUILD.gn @@ -22,6 +22,8 @@ source_set("ember_extra_files") { # data-model access and ember-compatibility (we share the same buffer) "${chip_root}/src/app/util/ember-global-attribute-access-interface.cpp", "${chip_root}/src/app/util/ember-io-storage.cpp", + "EmberReadWriteOverride.cpp", + "EmberReadWriteOverride.h", "InteractionModelTemporaryOverrides.cpp", ] diff --git a/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.cpp b/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.cpp new file mode 100644 index 00000000000000..1b375e4385121b --- /dev/null +++ b/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "EmberReadWriteOverride.h" + +#include + +using chip::Protocols::InteractionModel::Status; + +namespace { + +constexpr size_t kMaxTesetIoSize = 128; + +uint8_t gEmberIoBuffer[kMaxTesetIoSize]; +size_t gEmberIoBufferFill; +Status gEmberStatusCode = Status::InvalidAction; + +} // namespace + +namespace Testing { + +void SetEmberReadOutput(std::variant what) +{ + if (const chip::ByteSpan * span = std::get_if(&what)) + { + gEmberStatusCode = Status::Success; + + if (span->size() > sizeof(gEmberIoBufferFill)) + { + ChipLogError(Test, "UNEXPECTED STATE: Too much data set for ember read output"); + gEmberStatusCode = Status::ResourceExhausted; + + return; + } + + memcpy(gEmberIoBuffer, span->data(), span->size()); + gEmberIoBufferFill = span->size(); + return; + } + + if (const Status * status = std::get_if(&what)) + { + gEmberIoBufferFill = 0; + gEmberStatusCode = *status; + return; + } + + ChipLogError(Test, "UNEXPECTED STATE: invalid ember read output setting"); + gEmberStatusCode = Status::InvalidAction; +} + +} // namespace Testing + +/// TODO: this SHOULD be part of attribute-storage mocks and allow proper I/O control +/// with helpers for "ember encoding" +Status emAfReadOrWriteAttribute(const EmberAfAttributeSearchRecord * attRecord, const EmberAfAttributeMetadata ** metadata, + uint8_t * buffer, uint16_t readLength, bool write) +{ + if (gEmberStatusCode != Status::Success) + { + return gEmberStatusCode; + } + + if (gEmberIoBufferFill > readLength) + { + ChipLogError(Test, "Internal TEST error: insufficient output buffer space."); + return Status::ResourceExhausted; + } + + memcpy(buffer, gEmberIoBuffer, gEmberIoBufferFill); + return Status::Success; +} diff --git a/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.h b/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.h new file mode 100644 index 00000000000000..b025aec0e5b48b --- /dev/null +++ b/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +#include + +namespace Testing { + +/// specify what the next `emAfReadOrWriteAttribute` will contain +/// +/// It may return a value with success or some error. The byte span WILL BE COPIED. +void SetEmberReadOutput(std::variant what); + +} diff --git a/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp b/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp index 1a9688fd1b2fa3..85baca85781088 100644 --- a/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp +++ b/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "lib/support/logging/TextOnlyLogging.h" #include #include #include @@ -82,15 +81,3 @@ void DispatchSingleClusterCommand(const ConcreteCommandPath & aRequestCommandPat } // namespace app } // namespace chip - -/// TODO: this SHOULD be part of attribute-storage mocks. -Status emAfReadOrWriteAttribute(const EmberAfAttributeSearchRecord * attRecord, const EmberAfAttributeMetadata ** metadata, - uint8_t * buffer, uint16_t readLength, bool write) -{ - ChipLogError(Test, "Read/Write attribute is NOT implemented!!!") - - // FIXME: this is supposed to be an ember implementation - // however mock library SHOULD be able to implement it ... it is unclear - // to me why we do not have this... - return Status::NotFound; -} diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index adbfca3501b3b6..1e628763d5b43a 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -14,16 +14,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "access/SubjectDescriptor.h" -#include "lib/core/CHIPError.h" #include -#include +#include "EmberReadWriteOverride.h" +#include +#include #include #include #include #include +#include #include @@ -498,6 +499,9 @@ TEST(TestCodegenModelViaMocks, EmberAttributeRead) ASSERT_EQ(err, CHIP_NO_ERROR); AttributeValueEncoder encoder(builder, kAdminSubjectDescriptor, readRequest.path, dataVersion); + uint8_t data[] = {0x01, 0x02, 0x03, 0x04}; + Testing::SetEmberReadOutput(ByteSpan(data)); + err = model.ReadAttribute(readRequest, encoder); ASSERT_EQ(err, CHIP_NO_ERROR); From bb98ce8f0fecb90bea8619c40814ee90dd99d7ed Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 07:58:08 -0400 Subject: [PATCH 053/123] Restyle --- .../codegen-interaction-model/tests/EmberReadWriteOverride.h | 2 +- .../tests/TestCodegenModelViaMocks.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.h b/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.h index b025aec0e5b48b..aecb5eaf1a69e8 100644 --- a/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.h +++ b/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.h @@ -28,4 +28,4 @@ namespace Testing { /// It may return a value with success or some error. The byte span WILL BE COPIED. void SetEmberReadOutput(std::variant what); -} +} // namespace Testing diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 1e628763d5b43a..2487dbbc0d894f 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -499,7 +499,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeRead) ASSERT_EQ(err, CHIP_NO_ERROR); AttributeValueEncoder encoder(builder, kAdminSubjectDescriptor, readRequest.path, dataVersion); - uint8_t data[] = {0x01, 0x02, 0x03, 0x04}; + uint8_t data[] = { 0x01, 0x02, 0x03, 0x04 }; Testing::SetEmberReadOutput(ByteSpan(data)); err = model.ReadAttribute(readRequest, encoder); From c2ee606afbffc63d718bc90af44cdc837eca1d0e Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 14:57:11 -0400 Subject: [PATCH 054/123] Attempt to decode data. It does NOT work yet --- .../tests/TestCodegenModelViaMocks.cpp | 81 ++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 2487dbbc0d894f..5aaef0f2242746 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -25,9 +25,13 @@ #include #include #include +#include +#include #include +#include +// TODO: CHIP_ERROR tostring should be separated out #include using namespace chip; @@ -183,6 +187,59 @@ struct UseMockNodeConfig ~UseMockNodeConfig() { ResetMockNodeConfig(); } }; +struct DecodedAttributeData +{ + chip::DataVersion dataVersion; + ConcreteDataAttributePath attributePath; + ByteSpan dataSpan; + + CHIP_ERROR DecodeFrom(const AttributeDataIB::Parser & parser) + { + ReturnErrorOnFailure(parser.GetDataVersion(&dataVersion)); + + AttributePathIB::Parser pathParser; + ReturnErrorOnFailure(parser.GetPath(&pathParser)); + ReturnErrorOnFailure(pathParser.GetConcreteAttributePath(attributePath, AttributePathIB::ValidateIdRanges::kNo)); + + TLV::TLVReader dataReader; + ReturnErrorOnFailure(parser.GetData(&dataReader)); + + dataSpan = ByteSpan(dataReader.GetReadPoint(), dataReader.GetLength()); + + return CHIP_NO_ERROR; + } +}; + +CHIP_ERROR DecodeAttributeReportIBs(ByteSpan data, std::vector & decoded_items) +{ + TLV::TLVReader reportIBsReader; + reportIBsReader.Init(data); + + CHIP_ERROR err = CHIP_NO_ERROR; + + while (CHIP_NO_ERROR == (err = reportIBsReader.Next())) + { + TLV::TLVReader attributeReportReader = reportIBsReader; + AttributeReportIB::Parser attributeReportParser; + ReturnErrorOnFailure(attributeReportParser.Init(attributeReportReader)); + + AttributeDataIB::Parser dataParser; + // NOTE: to also grab statuses, use GetAttributeStatus and check for CHIP_END_OF_TLV + ReturnErrorOnFailure(attributeReportParser.GetAttributeData(&dataParser)); + + DecodedAttributeData decoded; + ReturnErrorOnFailure(decoded.DecodeFrom(dataParser)); + decoded_items.push_back(decoded); + } + + if (CHIP_END_OF_TLV == err) + { + return CHIP_NO_ERROR; + } + + return err; +} + } // namespace TEST(TestCodegenModelViaMocks, IterateOverEndpoints) @@ -493,9 +550,14 @@ TEST(TestCodegenModelViaMocks, EmberAttributeRead) TLV::TLVWriter tlvWriter; tlvWriter.Init(tlvBuffer); + TLV::TLVType outer; + + CHIP_ERROR err = tlvWriter.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outer); + ASSERT_EQ(err, CHIP_NO_ERROR); AttributeReportIBs::Builder builder; - CHIP_ERROR err = builder.Init(&tlvWriter); + + err = builder.Init(&tlvWriter, 0x01 /* report context tag */); ASSERT_EQ(err, CHIP_NO_ERROR); AttributeValueEncoder encoder(builder, kAdminSubjectDescriptor, readRequest.path, dataVersion); @@ -505,5 +567,20 @@ TEST(TestCodegenModelViaMocks, EmberAttributeRead) err = model.ReadAttribute(readRequest, encoder); ASSERT_EQ(err, CHIP_NO_ERROR); - // TODO: value validation here? + builder.EndOfContainer(); + + err = tlvWriter.EndContainer(outer); + ASSERT_EQ(err, CHIP_NO_ERROR); + + err = tlvWriter.Finalize(); + ASSERT_EQ(err, CHIP_NO_ERROR); + + //// VALIDATE + std::vector attribute_data; + err = DecodeAttributeReportIBs(ByteSpan(tlvBuffer, tlvWriter.GetLengthWritten()), attribute_data); + ASSERT_EQ(err, CHIP_NO_ERROR); + + EXPECT_EQ(attribute_data.size(), 1u); + + // FIXME: validate data } From 4e86e6abe02a30bc9f91fcbacb65b30a3b227544 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 15:20:54 -0400 Subject: [PATCH 055/123] Validation actually passes --- .../tests/TestCodegenModelViaMocks.cpp | 74 +++++++++++++++---- 1 file changed, 60 insertions(+), 14 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 5aaef0f2242746..2b48c8cfcd688f 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -191,7 +192,7 @@ struct DecodedAttributeData { chip::DataVersion dataVersion; ConcreteDataAttributePath attributePath; - ByteSpan dataSpan; + TLV::TLVReader dataReader; CHIP_ERROR DecodeFrom(const AttributeDataIB::Parser & parser) { @@ -200,23 +201,42 @@ struct DecodedAttributeData AttributePathIB::Parser pathParser; ReturnErrorOnFailure(parser.GetPath(&pathParser)); ReturnErrorOnFailure(pathParser.GetConcreteAttributePath(attributePath, AttributePathIB::ValidateIdRanges::kNo)); - - TLV::TLVReader dataReader; ReturnErrorOnFailure(parser.GetData(&dataReader)); - dataSpan = ByteSpan(dataReader.GetReadPoint(), dataReader.GetLength()); - return CHIP_NO_ERROR; } }; CHIP_ERROR DecodeAttributeReportIBs(ByteSpan data, std::vector & decoded_items) { + // Espected data format: + // CONTAINER (anonymous) + // 0x01 => Array (i.e. report data ib) + // ReportIB* + // + // Overally this is VERY hard to process ... + // TLV::TLVReader reportIBsReader; reportIBsReader.Init(data); - CHIP_ERROR err = CHIP_NO_ERROR; + ReturnErrorOnFailure(reportIBsReader.Next()); + if (reportIBsReader.GetType() != TLV::TLVType::kTLVType_Structure) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + TLV::TLVType outer1; + reportIBsReader.EnterContainer(outer1); + + ReturnErrorOnFailure(reportIBsReader.Next()); + if (reportIBsReader.GetType() != TLV::TLVType::kTLVType_Array) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + TLV::TLVType outer2; + reportIBsReader.EnterContainer(outer2); + CHIP_ERROR err = CHIP_NO_ERROR; while (CHIP_NO_ERROR == (err = reportIBsReader.Next())) { TLV::TLVReader attributeReportReader = reportIBsReader; @@ -232,11 +252,26 @@ CHIP_ERROR DecodeAttributeReportIBs(ByteSpan data, std::vector attribute_data; err = DecodeAttributeReportIBs(ByteSpan(tlvBuffer, tlvWriter.GetLengthWritten()), attribute_data); ASSERT_EQ(err, CHIP_NO_ERROR); - - EXPECT_EQ(attribute_data.size(), 1u); - - // FIXME: validate data + ASSERT_EQ(attribute_data.size(), 1u); + + const DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, readRequest.path); + + // data element should be a uint32 encoded as TLV + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_UnsignedInteger); + uint32_t expected; + static_assert(sizeof(expected) == sizeof(data)); + memcpy(&expected, data, sizeof(expected)); + uint32_t actual; + err = encodedData.dataReader.Get(actual); + ASSERT_EQ(CHIP_NO_ERROR, err); + ASSERT_EQ(actual, expected); } From e09fbe649a0993f4d7c81f327b55f382e603395c Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 15:38:19 -0400 Subject: [PATCH 056/123] Start splitting into unrelated files ...this is painful --- .../codegen-interaction-model/tests/BUILD.gn | 2 + .../tests/TestAttributeReportIBsEncoding.cpp | 109 ++++++++++++++++++ .../tests/TestAttributeReportIBsEncoding.h | 42 +++++++ .../tests/TestCodegenModelViaMocks.cpp | 88 +------------- 4 files changed, 154 insertions(+), 87 deletions(-) create mode 100644 src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.cpp create mode 100644 src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.h diff --git a/src/app/codegen-interaction-model/tests/BUILD.gn b/src/app/codegen-interaction-model/tests/BUILD.gn index 172252ed3a6219..9762e74c62a3b8 100644 --- a/src/app/codegen-interaction-model/tests/BUILD.gn +++ b/src/app/codegen-interaction-model/tests/BUILD.gn @@ -25,6 +25,8 @@ source_set("ember_extra_files") { "EmberReadWriteOverride.cpp", "EmberReadWriteOverride.h", "InteractionModelTemporaryOverrides.cpp", + "TestAttributeReportIBsEncoding.cpp", + "TestAttributeReportIBsEncoding.h", ] public_deps = [ diff --git a/src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.cpp b/src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.cpp new file mode 100644 index 00000000000000..0ee8c0a4a6c702 --- /dev/null +++ b/src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "TestAttributeReportIBsEncoding.h" + +#include +#include + +using namespace chip::app; + +namespace chip { +namespace Test { + +CHIP_ERROR DecodedAttributeData::DecodeFrom(const chip::app::AttributeDataIB::Parser & parser) +{ + ReturnErrorOnFailure(parser.GetDataVersion(&dataVersion)); + + AttributePathIB::Parser pathParser; + ReturnErrorOnFailure(parser.GetPath(&pathParser)); + ReturnErrorOnFailure(pathParser.GetConcreteAttributePath(attributePath, AttributePathIB::ValidateIdRanges::kNo)); + ReturnErrorOnFailure(parser.GetData(&dataReader)); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DecodeAttributeReportIBs(ByteSpan data, std::vector & decoded_items) +{ + // Espected data format: + // CONTAINER (anonymous) + // 0x01 => Array (i.e. report data ib) + // ReportIB* + // + // Overally this is VERY hard to process ... + // + TLV::TLVReader reportIBsReader; + reportIBsReader.Init(data); + + ReturnErrorOnFailure(reportIBsReader.Next()); + if (reportIBsReader.GetType() != TLV::TLVType::kTLVType_Structure) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + TLV::TLVType outer1; + reportIBsReader.EnterContainer(outer1); + + ReturnErrorOnFailure(reportIBsReader.Next()); + if (reportIBsReader.GetType() != TLV::TLVType::kTLVType_Array) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + TLV::TLVType outer2; + reportIBsReader.EnterContainer(outer2); + + CHIP_ERROR err = CHIP_NO_ERROR; + while (CHIP_NO_ERROR == (err = reportIBsReader.Next())) + { + TLV::TLVReader attributeReportReader = reportIBsReader; + AttributeReportIB::Parser attributeReportParser; + ReturnErrorOnFailure(attributeReportParser.Init(attributeReportReader)); + + AttributeDataIB::Parser dataParser; + // NOTE: to also grab statuses, use GetAttributeStatus and check for CHIP_END_OF_TLV + ReturnErrorOnFailure(attributeReportParser.GetAttributeData(&dataParser)); + + DecodedAttributeData decoded; + ReturnErrorOnFailure(decoded.DecodeFrom(dataParser)); + decoded_items.push_back(decoded); + } + + if ((CHIP_END_OF_TLV != err) && (err != CHIP_NO_ERROR)) + { + return CHIP_NO_ERROR; + } + + ReturnErrorOnFailure(reportIBsReader.ExitContainer(outer2)); + ReturnErrorOnFailure(reportIBsReader.ExitContainer(outer1)); + + err = reportIBsReader.Next(); + + if (CHIP_ERROR_END_OF_TLV == err) + { + return CHIP_NO_ERROR; + } + if (CHIP_NO_ERROR == err) + { + // This is NOT ok ... we have multiple things in our buffer? + return CHIP_ERROR_INVALID_ARGUMENT; + } + + return err; +} + + +} // namespace Test +} // namespace chip diff --git a/src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.h b/src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.h new file mode 100644 index 00000000000000..b62d2e18d89f84 --- /dev/null +++ b/src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include +#include + +#include + +namespace chip { +namespace Test { + +struct DecodedAttributeData +{ + chip::DataVersion dataVersion; + chip::app::ConcreteDataAttributePath attributePath; + chip::TLV::TLVReader dataReader; + + CHIP_ERROR DecodeFrom(const chip::app::AttributeDataIB::Parser & parser); + +}; + +CHIP_ERROR DecodeAttributeReportIBs(ByteSpan data, std::vector & decoded_items); + +} // namespace Test +} // namespace chip diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 2b48c8cfcd688f..ddd9dfb87b29b3 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -17,6 +17,7 @@ #include #include "EmberReadWriteOverride.h" +#include "TestAttributeReportIBsEncoding.h" #include #include @@ -188,93 +189,6 @@ struct UseMockNodeConfig ~UseMockNodeConfig() { ResetMockNodeConfig(); } }; -struct DecodedAttributeData -{ - chip::DataVersion dataVersion; - ConcreteDataAttributePath attributePath; - TLV::TLVReader dataReader; - - CHIP_ERROR DecodeFrom(const AttributeDataIB::Parser & parser) - { - ReturnErrorOnFailure(parser.GetDataVersion(&dataVersion)); - - AttributePathIB::Parser pathParser; - ReturnErrorOnFailure(parser.GetPath(&pathParser)); - ReturnErrorOnFailure(pathParser.GetConcreteAttributePath(attributePath, AttributePathIB::ValidateIdRanges::kNo)); - ReturnErrorOnFailure(parser.GetData(&dataReader)); - - return CHIP_NO_ERROR; - } -}; - -CHIP_ERROR DecodeAttributeReportIBs(ByteSpan data, std::vector & decoded_items) -{ - // Espected data format: - // CONTAINER (anonymous) - // 0x01 => Array (i.e. report data ib) - // ReportIB* - // - // Overally this is VERY hard to process ... - // - TLV::TLVReader reportIBsReader; - reportIBsReader.Init(data); - - ReturnErrorOnFailure(reportIBsReader.Next()); - if (reportIBsReader.GetType() != TLV::TLVType::kTLVType_Structure) - { - return CHIP_ERROR_INVALID_ARGUMENT; - } - TLV::TLVType outer1; - reportIBsReader.EnterContainer(outer1); - - ReturnErrorOnFailure(reportIBsReader.Next()); - if (reportIBsReader.GetType() != TLV::TLVType::kTLVType_Array) - { - return CHIP_ERROR_INVALID_ARGUMENT; - } - - TLV::TLVType outer2; - reportIBsReader.EnterContainer(outer2); - - CHIP_ERROR err = CHIP_NO_ERROR; - while (CHIP_NO_ERROR == (err = reportIBsReader.Next())) - { - TLV::TLVReader attributeReportReader = reportIBsReader; - AttributeReportIB::Parser attributeReportParser; - ReturnErrorOnFailure(attributeReportParser.Init(attributeReportReader)); - - AttributeDataIB::Parser dataParser; - // NOTE: to also grab statuses, use GetAttributeStatus and check for CHIP_END_OF_TLV - ReturnErrorOnFailure(attributeReportParser.GetAttributeData(&dataParser)); - - DecodedAttributeData decoded; - ReturnErrorOnFailure(decoded.DecodeFrom(dataParser)); - decoded_items.push_back(decoded); - } - - if ((CHIP_END_OF_TLV != err) && (err != CHIP_NO_ERROR)) - { - return CHIP_NO_ERROR; - } - - ReturnErrorOnFailure(reportIBsReader.ExitContainer(outer2)); - ReturnErrorOnFailure(reportIBsReader.ExitContainer(outer1)); - - err = reportIBsReader.Next(); - - if (CHIP_ERROR_END_OF_TLV == err) - { - return CHIP_NO_ERROR; - } - if (CHIP_NO_ERROR == err) - { - // This is NOT ok ... we have multiple things in our buffer? - return CHIP_ERROR_INVALID_ARGUMENT; - } - - return err; -} - } // namespace TEST(TestCodegenModelViaMocks, IterateOverEndpoints) From a325e5e72aa31ab23f67b783aad4c309f36485d2 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 15:38:38 -0400 Subject: [PATCH 057/123] Restyle --- .../tests/TestAttributeReportIBsEncoding.cpp | 1 - .../tests/TestAttributeReportIBsEncoding.h | 1 - 2 files changed, 2 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.cpp b/src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.cpp index 0ee8c0a4a6c702..35f6fd93d7e8e2 100644 --- a/src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.cpp +++ b/src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.cpp @@ -104,6 +104,5 @@ CHIP_ERROR DecodeAttributeReportIBs(ByteSpan data, std::vector & decoded_items); From 3d5009336ae26a8ba9e0139e451bbe945288dd09 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 15:42:01 -0400 Subject: [PATCH 058/123] Better test namespacing - be consistent --- .../tests/EmberReadWriteOverride.cpp | 6 ++++-- .../tests/EmberReadWriteOverride.h | 6 ++++-- .../tests/TestCodegenModelViaMocks.cpp | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.cpp b/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.cpp index 1b375e4385121b..f8d61ffef31979 100644 --- a/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.cpp +++ b/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.cpp @@ -30,7 +30,8 @@ Status gEmberStatusCode = Status::InvalidAction; } // namespace -namespace Testing { +namespace chip { +namespace Test { void SetEmberReadOutput(std::variant what) { @@ -62,7 +63,8 @@ void SetEmberReadOutput(std::variant what) gEmberStatusCode = Status::InvalidAction; } -} // namespace Testing +} // namespace Test +} // namespace chip /// TODO: this SHOULD be part of attribute-storage mocks and allow proper I/O control /// with helpers for "ember encoding" diff --git a/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.h b/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.h index aecb5eaf1a69e8..527a6cfd0d18c7 100644 --- a/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.h +++ b/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.h @@ -21,11 +21,13 @@ #include -namespace Testing { +namespace chip { +namespace Test { /// specify what the next `emAfReadOrWriteAttribute` will contain /// /// It may return a value with success or some error. The byte span WILL BE COPIED. void SetEmberReadOutput(std::variant what); -} // namespace Testing +} // namespace Test +} // namespace chip diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index ddd9dfb87b29b3..9e4bc5ea5cb535 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -512,7 +512,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeRead) AttributeValueEncoder encoder(builder, kAdminSubjectDescriptor, readRequest.path, dataVersion); uint8_t data[] = { 0x01, 0x02, 0x03, 0x04 }; - Testing::SetEmberReadOutput(ByteSpan(data)); + chip::Test::SetEmberReadOutput(ByteSpan(data)); err = model.ReadAttribute(readRequest, encoder); ASSERT_EQ(err, CHIP_NO_ERROR); From 6f1c553027cb0c1d83645cddbd3cc72d8dc29f61 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 16:15:50 -0400 Subject: [PATCH 059/123] Re-organize a bit ... boilerplate still seems A LOT --- .../tests/TestAttributeReportIBsEncoding.cpp | 22 ++++++++ .../tests/TestAttributeReportIBsEncoding.h | 27 +++++++++ .../tests/TestCodegenModelViaMocks.cpp | 55 ++++--------------- 3 files changed, 61 insertions(+), 43 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.cpp b/src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.cpp index 35f6fd93d7e8e2..5154acc5596291 100644 --- a/src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.cpp +++ b/src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.cpp @@ -104,5 +104,27 @@ CHIP_ERROR DecodeAttributeReportIBs(ByteSpan data, std::vector & decoded_items) +{ + return DecodeAttributeReportIBs(mDecodeSpan, decoded_items); +} + } // namespace Test } // namespace chip diff --git a/src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.h b/src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.h index e6dd2654827301..83c079f810ccb8 100644 --- a/src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.h +++ b/src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.h @@ -18,8 +18,11 @@ #include #include +#include +#include #include #include +#include #include @@ -37,5 +40,29 @@ struct DecodedAttributeData CHIP_ERROR DecodeAttributeReportIBs(ByteSpan data, std::vector & decoded_items); +/// Maintains an internal TLV buffer for data encoding and +/// decoding for ReportIBs. +/// +/// Main use case is that explicit TLV layouts (structure and container starting) need to be +/// prepared to have a proper AttributeReportIBs::Builder/parser to exist. +class EncodedReportIBs +{ +public: + /// Initialize the report structures required to encode a + CHIP_ERROR StartEncoding(app::AttributeReportIBs::Builder & builder); + CHIP_ERROR FinishEncoding(app::AttributeReportIBs::Builder & builder); + + /// Decode the embedded attribute report IBs. + /// The TLVReaders inside data have a lifetime tied to the current object (its readers point + /// inside the current object) + CHIP_ERROR Decode(std::vector & decoded_items); + +private: + uint8_t mTlvDataBuffer[1024]; + TLV::TLVType mOuterStructureType; + TLV::TLVWriter mEncodeWriter; + ByteSpan mDecodeSpan; +}; + } // namespace Test } // namespace chip diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 9e4bc5ea5cb535..0c92b7277790d0 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -426,18 +426,12 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadAclDeny) ASSERT_TRUE(info.has_value()); DataVersion dataVersion = info->dataVersion; // NOLINT(bugprone-unchecked-optional-access) - - uint8_t tlvBuffer[1024]; - - TLV::TLVWriter tlvWriter; - tlvWriter.Init(tlvBuffer); - + EncodedReportIBs reportIBs; AttributeReportIBs::Builder builder; - CHIP_ERROR err = builder.Init(&tlvWriter); - ASSERT_EQ(err, CHIP_NO_ERROR); - AttributeValueEncoder encoder(builder, kAdminSubjectDescriptor, readRequest.path, dataVersion); + ASSERT_EQ(reportIBs.StartEncoding(builder), CHIP_NO_ERROR); + AttributeValueEncoder encoder(builder, kDenySubjectDescriptor, readRequest.path, dataVersion); - err = model.ReadAttribute(readRequest, encoder); + CHIP_ERROR err = model.ReadAttribute(readRequest, encoder); ASSERT_EQ(err, CHIP_ERROR_ACCESS_DENIED); } @@ -459,21 +453,13 @@ TEST(TestCodegenModelViaMocks, EmberAttributeInvalidRead) ASSERT_TRUE(info.has_value()); DataVersion dataVersion = info->dataVersion; // NOLINT(bugprone-unchecked-optional-access) - - uint8_t tlvBuffer[1024]; - - TLV::TLVWriter tlvWriter; - tlvWriter.Init(tlvBuffer); - + EncodedReportIBs reportIBs; AttributeReportIBs::Builder builder; - CHIP_ERROR err = builder.Init(&tlvWriter); - ASSERT_EQ(err, CHIP_NO_ERROR); + ASSERT_EQ(reportIBs.StartEncoding(builder), CHIP_NO_ERROR); AttributeValueEncoder encoder(builder, kAdminSubjectDescriptor, readRequest.path, dataVersion); - err = model.ReadAttribute(readRequest, encoder); + CHIP_ERROR err = model.ReadAttribute(readRequest, encoder); ASSERT_EQ(err, CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); - - // TODO: value validation here? } TEST(TestCodegenModelViaMocks, EmberAttributeRead) @@ -495,39 +481,22 @@ TEST(TestCodegenModelViaMocks, EmberAttributeRead) DataVersion dataVersion = info->dataVersion; // NOLINT(bugprone-unchecked-optional-access) - uint8_t tlvBuffer[1024]; - - TLV::TLVWriter tlvWriter; - tlvWriter.Init(tlvBuffer); - CHIP_ERROR err = CHIP_NO_ERROR; - - TLV::TLVType outer; - err = tlvWriter.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outer); - ASSERT_EQ(err, CHIP_NO_ERROR); - + EncodedReportIBs reportIBs; AttributeReportIBs::Builder builder; - - err = builder.Init(&tlvWriter, to_underlying(ReportDataMessage::Tag::kAttributeReportIBs)); - ASSERT_EQ(err, CHIP_NO_ERROR); + ASSERT_EQ(reportIBs.StartEncoding(builder), CHIP_NO_ERROR); AttributeValueEncoder encoder(builder, kAdminSubjectDescriptor, readRequest.path, dataVersion); uint8_t data[] = { 0x01, 0x02, 0x03, 0x04 }; chip::Test::SetEmberReadOutput(ByteSpan(data)); - err = model.ReadAttribute(readRequest, encoder); + CHIP_ERROR err = model.ReadAttribute(readRequest, encoder); ASSERT_EQ(err, CHIP_NO_ERROR); - builder.EndOfContainer(); - - err = tlvWriter.EndContainer(outer); - ASSERT_EQ(err, CHIP_NO_ERROR); - - err = tlvWriter.Finalize(); - ASSERT_EQ(err, CHIP_NO_ERROR); + ASSERT_EQ(reportIBs.FinishEncoding(builder), CHIP_NO_ERROR); //// VALIDATE std::vector attribute_data; - err = DecodeAttributeReportIBs(ByteSpan(tlvBuffer, tlvWriter.GetLengthWritten()), attribute_data); + err = reportIBs.Decode(attribute_data); ASSERT_EQ(err, CHIP_NO_ERROR); ASSERT_EQ(attribute_data.size(), 1u); From 7533d78f36da45ac48cf906d127cf9642314b32d Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 16:17:34 -0400 Subject: [PATCH 060/123] Minor comments to start organizing the code better - I NEED more readable things --- .../tests/TestCodegenModelViaMocks.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 0c92b7277790d0..47ba9ec0172289 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -468,6 +468,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeRead) chip::app::CodegenDataModel::Model model; ScopedMockAccessControl accessControl; + ///////// INPUT ARGUMENTS SETUP ReadAttributeRequest readRequest; // operationFlags is 0 i.e. not internal @@ -486,15 +487,17 @@ TEST(TestCodegenModelViaMocks, EmberAttributeRead) ASSERT_EQ(reportIBs.StartEncoding(builder), CHIP_NO_ERROR); AttributeValueEncoder encoder(builder, kAdminSubjectDescriptor, readRequest.path, dataVersion); + ///////// FAKE DATA SETUP uint8_t data[] = { 0x01, 0x02, 0x03, 0x04 }; chip::Test::SetEmberReadOutput(ByteSpan(data)); CHIP_ERROR err = model.ReadAttribute(readRequest, encoder); ASSERT_EQ(err, CHIP_NO_ERROR); + ///////// OVERHEAD: finish! ASSERT_EQ(reportIBs.FinishEncoding(builder), CHIP_NO_ERROR); - //// VALIDATE + /////// VALIDATE std::vector attribute_data; err = reportIBs.Decode(attribute_data); ASSERT_EQ(err, CHIP_NO_ERROR); From a797b5e113baf06631af1762c56c656490adf13c Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 17:04:07 -0400 Subject: [PATCH 061/123] Restyle and re-organize for readability --- .../tests/TestCodegenModelViaMocks.cpp | 130 +++++++++--------- 1 file changed, 62 insertions(+), 68 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 47ba9ec0172289..aa327f99e1ec3f 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -189,6 +189,49 @@ struct UseMockNodeConfig ~UseMockNodeConfig() { ResetMockNodeConfig(); } }; +struct TestReadRequest +{ + ReadAttributeRequest request; + + // encoded-used classes + EncodedReportIBs encodedIBs; + AttributeReportIBs::Builder reportBuilder; + std::unique_ptr encoder; + + TestReadRequest(const Access::SubjectDescriptor & subject, const ConcreteAttributePath & path) + { + // operationFlags is 0 i.e. not internal + // readFlags is 0 i.e. not fabric filtered + // dataVersion is missing (no data version filtering) + request.subjectDescriptor = subject; + request.path = path; + } + + std::unique_ptr StartEncoding(chip::app::InteractionModel::Model * model) + { + std::optional info = model->GetClusterInfo(request.path); + if (!info.has_value()) + { + ChipLogError(Test, "Missing cluster information - no data version"); + return nullptr; + } + + DataVersion dataVersion = info->dataVersion; // NOLINT(bugprone-unchecked-optional-access) + + CHIP_ERROR err = encodedIBs.StartEncoding(reportBuilder); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Test, "FAILURE starting encoding %" CHIP_ERROR_FORMAT, err.Format()); + return nullptr; + } + + // TODO: isFabricFiltered? EncodeState? + return std::make_unique(reportBuilder, request.subjectDescriptor.value(), request.path, dataVersion); + } + + CHIP_ERROR FinishEncoding() { return encodedIBs.FinishEncoding(reportBuilder); } +}; + } // namespace TEST(TestCodegenModelViaMocks, IterateOverEndpoints) @@ -414,25 +457,11 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadAclDeny) chip::app::CodegenDataModel::Model model; ScopedMockAccessControl accessControl; - ReadAttributeRequest readRequest; - - // operationFlags is 0 i.e. not internal - // readFlags is 0 i.e. not fabric filtered - // dataVersion is missing (no data version filtering) - readRequest.subjectDescriptor = kDenySubjectDescriptor; - readRequest.path = ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10)); + TestReadRequest testRequest(kDenySubjectDescriptor, + ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))); + std::unique_ptr encoder = testRequest.StartEncoding(&model); - std::optional info = model.GetClusterInfo(readRequest.path); - ASSERT_TRUE(info.has_value()); - - DataVersion dataVersion = info->dataVersion; // NOLINT(bugprone-unchecked-optional-access) - EncodedReportIBs reportIBs; - AttributeReportIBs::Builder builder; - ASSERT_EQ(reportIBs.StartEncoding(builder), CHIP_NO_ERROR); - AttributeValueEncoder encoder(builder, kDenySubjectDescriptor, readRequest.path, dataVersion); - - CHIP_ERROR err = model.ReadAttribute(readRequest, encoder); - ASSERT_EQ(err, CHIP_ERROR_ACCESS_DENIED); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_ERROR_ACCESS_DENIED); } TEST(TestCodegenModelViaMocks, EmberAttributeInvalidRead) @@ -441,25 +470,11 @@ TEST(TestCodegenModelViaMocks, EmberAttributeInvalidRead) chip::app::CodegenDataModel::Model model; ScopedMockAccessControl accessControl; - ReadAttributeRequest readRequest; - - // operationFlags is 0 i.e. not internal - // readFlags is 0 i.e. not fabric filtered - // dataVersion is missing (no data version filtering) - readRequest.subjectDescriptor = kAdminSubjectDescriptor; - readRequest.path = ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10)); + TestReadRequest testRequest(kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))); + std::unique_ptr encoder = testRequest.StartEncoding(&model); - std::optional info = model.GetClusterInfo(readRequest.path); - ASSERT_TRUE(info.has_value()); - - DataVersion dataVersion = info->dataVersion; // NOLINT(bugprone-unchecked-optional-access) - EncodedReportIBs reportIBs; - AttributeReportIBs::Builder builder; - ASSERT_EQ(reportIBs.StartEncoding(builder), CHIP_NO_ERROR); - AttributeValueEncoder encoder(builder, kAdminSubjectDescriptor, readRequest.path, dataVersion); - - CHIP_ERROR err = model.ReadAttribute(readRequest, encoder); - ASSERT_EQ(err, CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); } TEST(TestCodegenModelViaMocks, EmberAttributeRead) @@ -468,51 +483,30 @@ TEST(TestCodegenModelViaMocks, EmberAttributeRead) chip::app::CodegenDataModel::Model model; ScopedMockAccessControl accessControl; - ///////// INPUT ARGUMENTS SETUP - ReadAttributeRequest readRequest; - - // operationFlags is 0 i.e. not internal - // readFlags is 0 i.e. not fabric filtered - // dataVersion is missing (no data version filtering) - readRequest.subjectDescriptor = kAdminSubjectDescriptor; - readRequest.path = ConcreteAttributePath(kMockEndpoint3, MockClusterId(2), MockAttributeId(3)); - - std::optional info = model.GetClusterInfo(readRequest.path); - ASSERT_TRUE(info.has_value()); - - DataVersion dataVersion = info->dataVersion; // NOLINT(bugprone-unchecked-optional-access) + TestReadRequest testRequest(kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(2), MockAttributeId(3))); - EncodedReportIBs reportIBs; - AttributeReportIBs::Builder builder; - ASSERT_EQ(reportIBs.StartEncoding(builder), CHIP_NO_ERROR); - AttributeValueEncoder encoder(builder, kAdminSubjectDescriptor, readRequest.path, dataVersion); + std::unique_ptr encoder = testRequest.StartEncoding(&model); - ///////// FAKE DATA SETUP - uint8_t data[] = { 0x01, 0x02, 0x03, 0x04 }; - chip::Test::SetEmberReadOutput(ByteSpan(data)); + // Ember encoding for integers is IDENTICAL to the in-memory representation for them + const uint32_t expected = 0x01020304; + chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(&expected), sizeof(expected))); - CHIP_ERROR err = model.ReadAttribute(readRequest, encoder); - ASSERT_EQ(err, CHIP_NO_ERROR); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); - ///////// OVERHEAD: finish! - ASSERT_EQ(reportIBs.FinishEncoding(builder), CHIP_NO_ERROR); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); /////// VALIDATE std::vector attribute_data; - err = reportIBs.Decode(attribute_data); - ASSERT_EQ(err, CHIP_NO_ERROR); + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); ASSERT_EQ(attribute_data.size(), 1u); const DecodedAttributeData & encodedData = attribute_data[0]; - ASSERT_EQ(encodedData.attributePath, readRequest.path); + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); // data element should be a uint32 encoded as TLV ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_UnsignedInteger); - uint32_t expected; - static_assert(sizeof(expected) == sizeof(data)); - memcpy(&expected, data, sizeof(expected)); uint32_t actual; - err = encodedData.dataReader.Get(actual); - ASSERT_EQ(CHIP_NO_ERROR, err); + ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR); ASSERT_EQ(actual, expected); } From 7aa3209434b1c1f6c12e29bfeacccebdf19cb9fe Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 17:17:47 -0400 Subject: [PATCH 062/123] More code changes to support testing strings ... however this FAILS right now --- .../tests/TestCodegenModelViaMocks.cpp | 125 +++++++++++++++++- 1 file changed, 121 insertions(+), 4 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index aa327f99e1ec3f..f6a19144710d30 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -140,6 +140,9 @@ class ScopedMockAccessControl MockAccessControl mMock; }; +#define MOCK_ATTRIBUTE_ID_FOR_TYPE(zcl_type) MockAttributeId(zcl_type + 0x1000) +#define MOCK_ATTRIBUTE_CONFIG(zcl_type) MockAttributeConfig(MOCK_ATTRIBUTE_ID_FOR_TYPE(zcl_type), zcl_type) + // clang-format off const MockNodeConfig gTestNodeConfig({ MockEndpointConfig(kMockEndpoint1, { @@ -177,7 +180,82 @@ const MockNodeConfig gTestNodeConfig({ ClusterRevision::Id, FeatureMap::Id, }), MockClusterConfig(MockClusterId(4), { - ClusterRevision::Id, FeatureMap::Id, + ClusterRevision::Id, + FeatureMap::Id, + // several attributes of varying data types for testing. + MOCK_ATTRIBUTE_CONFIG(ZCL_BOOLEAN_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_BITMAP8_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_BITMAP16_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_BITMAP32_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_BITMAP64_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_INT8U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_INT16U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_INT24U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_INT32U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_INT40U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_INT48U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_INT56U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_INT64U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_INT8S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_INT16S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_INT24S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_INT32S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_INT40S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_INT48S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_INT56S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_INT64S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_ENUM8_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_ENUM16_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_PRIORITY_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_STATUS_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_SINGLE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_DOUBLE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_OCTET_STRING_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_CHAR_STRING_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_ARRAY_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_STRUCT_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_GROUP_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_VENDOR_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_FABRIC_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_FABRIC_IDX_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_ENTRY_IDX_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_DATA_VER_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_EVENT_NO_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_SEMTAG_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_NAMESPACE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_TAG_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_SYSTIME_US_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_SYSTIME_MS_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_ELAPSED_S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_TEMPERATURE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_POWER_MW_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_ENERGY_MWH_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_TOD_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_DATE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_EPOCH_US_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_EPOCH_S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_POSIX_MS_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_PERCENT_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_PERCENT100THS_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_CLUSTER_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_ATTRIB_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_FIELD_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_EVENT_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_COMMAND_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_ACTION_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_TRANS_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_NODE_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_IPADR_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_IPV4ADR_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_IPV6ADR_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_IPV6PRE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG(ZCL_HWADR_ATTRIBUTE_TYPE), }), }), }); @@ -477,14 +555,15 @@ TEST(TestCodegenModelViaMocks, EmberAttributeInvalidRead) ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); } -TEST(TestCodegenModelViaMocks, EmberAttributeRead) +TEST(TestCodegenModelViaMocks, EmberAttributeReadInt32U) { UseMockNodeConfig config(gTestNodeConfig); chip::app::CodegenDataModel::Model model; ScopedMockAccessControl accessControl; - TestReadRequest testRequest(kAdminSubjectDescriptor, - ConcreteAttributePath(kMockEndpoint3, MockClusterId(2), MockAttributeId(3))); + TestReadRequest testRequest( + kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_TYPE(ZCL_INT32U_ATTRIBUTE_TYPE))); std::unique_ptr encoder = testRequest.StartEncoding(&model); @@ -510,3 +589,41 @@ TEST(TestCodegenModelViaMocks, EmberAttributeRead) ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR); ASSERT_EQ(actual, expected); } + +TEST(TestCodegenModelViaMocks, EmberAttributeReadOctetString) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel::Model model; + ScopedMockAccessControl accessControl; + + TestReadRequest testRequest( + kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_TYPE(ZCL_INT32U_ATTRIBUTE_TYPE))); + + std::unique_ptr encoder = testRequest.StartEncoding(&model); + + // NOTE: This is a pascal string, so actual data is "test" + // the longer encoding is to make it clear we do not encode the overflow + const char data[] = "\x04testing here with overflow"; + chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); + + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + /////// VALIDATE + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + const DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + + // data element should be a uint32 encoded as TLV + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_ByteString); + ByteSpan actual; + ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR); + + ByteSpan expected(reinterpret_cast(data + 1), 4); + ASSERT_TRUE(actual.data_equal(expected)); +} From 87d86d7e16eb8570ea02ac3c0064d0194f811895 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 17:18:01 -0400 Subject: [PATCH 063/123] Restyle --- .../tests/TestCodegenModelViaMocks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index f6a19144710d30..f47e9b1f66eb21 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -180,7 +180,7 @@ const MockNodeConfig gTestNodeConfig({ ClusterRevision::Id, FeatureMap::Id, }), MockClusterConfig(MockClusterId(4), { - ClusterRevision::Id, + ClusterRevision::Id, FeatureMap::Id, // several attributes of varying data types for testing. MOCK_ATTRIBUTE_CONFIG(ZCL_BOOLEAN_ATTRIBUTE_TYPE), From 4cd9fa80bb707e294fb83311c09fcb839a47df8a Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 17:23:34 -0400 Subject: [PATCH 064/123] Fix a typo ... still broken though --- .../codegen-interaction-model/tests/EmberReadWriteOverride.cpp | 2 +- .../tests/TestCodegenModelViaMocks.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.cpp b/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.cpp index f8d61ffef31979..619f6ee555f568 100644 --- a/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.cpp +++ b/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.cpp @@ -39,7 +39,7 @@ void SetEmberReadOutput(std::variant what) { gEmberStatusCode = Status::Success; - if (span->size() > sizeof(gEmberIoBufferFill)) + if (span->size() > sizeof(gEmberIoBuffer)) { ChipLogError(Test, "UNEXPECTED STATE: Too much data set for ember read output"); gEmberStatusCode = Status::ResourceExhausted; diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index f47e9b1f66eb21..599d450dfa3b8a 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -619,7 +619,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadOctetString) const DecodedAttributeData & encodedData = attribute_data[0]; ASSERT_EQ(encodedData.attributePath, testRequest.request.path); - // data element should be a uint32 encoded as TLV + // data element should be a encoded byte string as this is what the attribute type is ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_ByteString); ByteSpan actual; ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR); From 582b93986dcae2e32e0db795b17f8368a00225d3 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 17:26:06 -0400 Subject: [PATCH 065/123] one more typo fix ... test passes --- .../tests/TestCodegenModelViaMocks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 599d450dfa3b8a..46bf65c0b3a81b 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -598,7 +598,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadOctetString) TestReadRequest testRequest( kAdminSubjectDescriptor, - ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_TYPE(ZCL_INT32U_ATTRIBUTE_TYPE))); + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_TYPE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE))); std::unique_ptr encoder = testRequest.StartEncoding(&model); From b05881e1b7b19f2535b58473ddd1af9c62e45b42 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 17:29:22 -0400 Subject: [PATCH 066/123] Long octet string test as well --- .../tests/TestCodegenModelViaMocks.cpp | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 46bf65c0b3a81b..6288c404d7ae63 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -627,3 +627,41 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadOctetString) ByteSpan expected(reinterpret_cast(data + 1), 4); ASSERT_TRUE(actual.data_equal(expected)); } + +TEST(TestCodegenModelViaMocks, EmberAttributeReadLongString) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel::Model model; + ScopedMockAccessControl accessControl; + + TestReadRequest testRequest( + kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_TYPE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE))); + + std::unique_ptr encoder = testRequest.StartEncoding(&model); + + // NOTE: This is a pascal string, so actual data is "test" + // the longer encoding is to make it clear we do not encode the overflow + char data[] = "\0\0abcdef...this is the alphabet"; + uint16_t len = 4; + memcpy(data, &len, sizeof(uint16_t)); + chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); + + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + /////// VALIDATE + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + const DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + + // data element should be a encoded byte string as this is what the attribute type is + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_UTF8String); + CharSpan actual; + ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR); + ASSERT_TRUE(actual.data_equal("abcd"_span)); +} From f16d17f61cf9ed1ceb399ab6e95a1143b607e998 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 17:30:02 -0400 Subject: [PATCH 067/123] Restyle --- .../tests/TestCodegenModelViaMocks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 6288c404d7ae63..bffe4fd7a58e86 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -642,7 +642,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadLongString) // NOTE: This is a pascal string, so actual data is "test" // the longer encoding is to make it clear we do not encode the overflow - char data[] = "\0\0abcdef...this is the alphabet"; + char data[] = "\0\0abcdef...this is the alphabet"; uint16_t len = 4; memcpy(data, &len, sizeof(uint16_t)); chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); From c76d197f93308806e75a0d33aca47650be851114 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 17:34:59 -0400 Subject: [PATCH 068/123] Fix comment and change the size of the string --- .../tests/TestCodegenModelViaMocks.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index bffe4fd7a58e86..31c1ecf2745ef8 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -640,10 +640,10 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadLongString) std::unique_ptr encoder = testRequest.StartEncoding(&model); - // NOTE: This is a pascal string, so actual data is "test" + // NOTE: This is a pascal string, so actual data is "abcde" // the longer encoding is to make it clear we do not encode the overflow char data[] = "\0\0abcdef...this is the alphabet"; - uint16_t len = 4; + uint16_t len = 5; memcpy(data, &len, sizeof(uint16_t)); chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); @@ -663,5 +663,5 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadLongString) ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_UTF8String); CharSpan actual; ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR); - ASSERT_TRUE(actual.data_equal("abcd"_span)); + ASSERT_TRUE(actual.data_equal("abcde"_span)); } From 4a78d2657763c7eb87e31a49572242aec75e0a9c Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 20:11:54 -0400 Subject: [PATCH 069/123] Add several ember-specific tests --- .../tests/TestCodegenModelViaMocks.cpp | 89 ++++++++++++++----- 1 file changed, 65 insertions(+), 24 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 31c1ecf2745ef8..e9f11d48fd297f 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -310,6 +310,40 @@ struct TestReadRequest CHIP_ERROR FinishEncoding() { return encodedIBs.FinishEncoding(reportBuilder); } }; +template +void TestEmberScalarTypeRead(T value) { + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel::Model model; + ScopedMockAccessControl accessControl; + + TestReadRequest testRequest( + kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_TYPE(ZclType))); + + std::unique_ptr encoder = testRequest.StartEncoding(&model); + + // Ember encoding for integers is IDENTICAL to the in-memory representation for them + chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(&value), sizeof(value))); + + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + /////// VALIDATE + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + const DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + + // data element should be a uint32 encoded as TLV + ASSERT_EQ(encodedData.dataReader.GetType(), TlvType); + T actual; + ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR); + ASSERT_EQ(actual, value); +} + } // namespace TEST(TestCodegenModelViaMocks, IterateOverEndpoints) @@ -555,39 +589,46 @@ TEST(TestCodegenModelViaMocks, EmberAttributeInvalidRead) ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); } -TEST(TestCodegenModelViaMocks, EmberAttributeReadInt32U) +TEST(TestCodegenModelViaMocks, EmberAttributeReadInt32S) { - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel::Model model; - ScopedMockAccessControl accessControl; + TestEmberScalarTypeRead(-1234); +} - TestReadRequest testRequest( - kAdminSubjectDescriptor, - ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_TYPE(ZCL_INT32U_ATTRIBUTE_TYPE))); +TEST(TestCodegenModelViaMocks, EmberAttributeReadEnum16) +{ + TestEmberScalarTypeRead(0x1234); +} - std::unique_ptr encoder = testRequest.StartEncoding(&model); +TEST(TestCodegenModelViaMocks, EmberAttributeReadFloat) +{ + TestEmberScalarTypeRead(0.625); +} - // Ember encoding for integers is IDENTICAL to the in-memory representation for them - const uint32_t expected = 0x01020304; - chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(&expected), sizeof(expected))); +TEST(TestCodegenModelViaMocks, EmberAttributeReadDouble) +{ + TestEmberScalarTypeRead(0.625); +} - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); - ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); +TEST(TestCodegenModelViaMocks, EmberAttributeReadInt32U) +{ + TestEmberScalarTypeRead(0x1234ABCD); +} - /////// VALIDATE - std::vector attribute_data; - ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); - ASSERT_EQ(attribute_data.size(), 1u); +TEST(TestCodegenModelViaMocks, EmberAttributeReadInt48U) +{ + TestEmberScalarTypeRead(0xAABB11223344); +} - const DecodedAttributeData & encodedData = attribute_data[0]; - ASSERT_EQ(encodedData.attributePath, testRequest.request.path); +TEST(TestCodegenModelViaMocks, EmberAttributeReadBool) +{ + TestEmberScalarTypeRead(true); + TestEmberScalarTypeRead(false); +} - // data element should be a uint32 encoded as TLV - ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_UnsignedInteger); - uint32_t actual; - ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR); - ASSERT_EQ(actual, expected); +TEST(TestCodegenModelViaMocks, EmberAttributeReadInt8U) +{ + TestEmberScalarTypeRead(0x12); } TEST(TestCodegenModelViaMocks, EmberAttributeReadOctetString) From 320cecbf1304baadde18c967236d8dfb04ee0a8b Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 20:52:21 -0400 Subject: [PATCH 070/123] unit tests and working with nullable values --- .../tests/TestCodegenModelViaMocks.cpp | 324 ++++++++++++------ 1 file changed, 227 insertions(+), 97 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index e9f11d48fd297f..4e48f12e68cb8c 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -22,6 +22,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -140,8 +143,13 @@ class ScopedMockAccessControl MockAccessControl mMock; }; -#define MOCK_ATTRIBUTE_ID_FOR_TYPE(zcl_type) MockAttributeId(zcl_type + 0x1000) -#define MOCK_ATTRIBUTE_CONFIG(zcl_type) MockAttributeConfig(MOCK_ATTRIBUTE_ID_FOR_TYPE(zcl_type), zcl_type) +#define MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(zcl_type) MockAttributeId(zcl_type + 0x1000) +#define MOCK_ATTRIBUTE_CONFIG_NULLABLE(zcl_type) \ + MockAttributeConfig(MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(zcl_type), zcl_type, ATTRIBUTE_MASK_WRITABLE | ATTRIBUTE_MASK_NULLABLE) + +#define MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(zcl_type) MockAttributeId(zcl_type + 0x2000) +#define MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(zcl_type) \ + MockAttributeConfig(MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(zcl_type), zcl_type, ATTRIBUTE_MASK_WRITABLE) // clang-format off const MockNodeConfig gTestNodeConfig({ @@ -183,79 +191,152 @@ const MockNodeConfig gTestNodeConfig({ ClusterRevision::Id, FeatureMap::Id, // several attributes of varying data types for testing. - MOCK_ATTRIBUTE_CONFIG(ZCL_BOOLEAN_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_BITMAP8_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_BITMAP16_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_BITMAP32_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_BITMAP64_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_INT8U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_INT16U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_INT24U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_INT32U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_INT40U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_INT48U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_INT56U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_INT64U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_INT8S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_INT16S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_INT24S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_INT32S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_INT40S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_INT48S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_INT56S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_INT64S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_ENUM8_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_ENUM16_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_PRIORITY_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_STATUS_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_SINGLE_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_DOUBLE_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_OCTET_STRING_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_CHAR_STRING_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_ARRAY_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_STRUCT_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_GROUP_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_VENDOR_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_FABRIC_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_FABRIC_IDX_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_ENTRY_IDX_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_DATA_VER_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_EVENT_NO_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_SEMTAG_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_NAMESPACE_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_TAG_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_SYSTIME_US_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_SYSTIME_MS_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_ELAPSED_S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_TEMPERATURE_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_POWER_MW_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_ENERGY_MWH_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_TOD_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_DATE_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_EPOCH_US_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_EPOCH_S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_POSIX_MS_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_PERCENT_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_PERCENT100THS_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_CLUSTER_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_ATTRIB_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_FIELD_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_EVENT_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_COMMAND_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_ACTION_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_TRANS_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_NODE_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_IPADR_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_IPV4ADR_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_IPV6ADR_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_IPV6PRE_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG(ZCL_HWADR_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BOOLEAN_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BITMAP8_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BITMAP16_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BITMAP32_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BITMAP64_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT8U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT16U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT24U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT32U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT40U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT48U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT56U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT64U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT8S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT16S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT24S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT32S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT40S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT48S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT56S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT64S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENUM8_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENUM16_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_PRIORITY_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_STATUS_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_SINGLE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DOUBLE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_CHAR_STRING_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ARRAY_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_STRUCT_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_GROUP_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_VENDOR_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_FABRIC_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_FABRIC_IDX_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENTRY_IDX_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DATA_VER_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EVENT_NO_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_SEMTAG_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_NAMESPACE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TAG_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_SYSTIME_US_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_SYSTIME_MS_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ELAPSED_S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TEMPERATURE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_POWER_MW_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENERGY_MWH_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TOD_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DATE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EPOCH_US_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EPOCH_S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_POSIX_MS_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_PERCENT_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_PERCENT100THS_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_CLUSTER_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ATTRIB_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_FIELD_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EVENT_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_COMMAND_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ACTION_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TRANS_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_NODE_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPADR_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPV4ADR_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPV6ADR_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPV6PRE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_HWADR_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BOOLEAN_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP8_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP16_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP32_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP64_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT8U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT16U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT24U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT32U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT40U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT48U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT56U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT64U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT8S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT16S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT24S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT32S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT40S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT48S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT56S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT64S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENUM8_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENUM16_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_PRIORITY_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_STATUS_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SINGLE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DOUBLE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_CHAR_STRING_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ARRAY_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_STRUCT_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_GROUP_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_VENDOR_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_FABRIC_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_FABRIC_IDX_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENTRY_IDX_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DATA_VER_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EVENT_NO_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SEMTAG_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_NAMESPACE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TAG_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SYSTIME_US_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SYSTIME_MS_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ELAPSED_S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TEMPERATURE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_POWER_MW_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENERGY_MWH_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TOD_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DATE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EPOCH_US_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EPOCH_S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_POSIX_MS_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_PERCENT_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_PERCENT100THS_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_CLUSTER_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ATTRIB_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_FIELD_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EVENT_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_COMMAND_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ACTION_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TRANS_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_NODE_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPADR_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPV4ADR_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPV6ADR_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPV6PRE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_HWADR_ATTRIBUTE_TYPE), }), }), }); @@ -310,15 +391,16 @@ struct TestReadRequest CHIP_ERROR FinishEncoding() { return encodedIBs.FinishEncoding(reportBuilder); } }; -template -void TestEmberScalarTypeRead(T value) { +template +void TestEmberScalarTypeRead(typename NumericAttributeTraits::WorkingType value) +{ UseMockNodeConfig config(gTestNodeConfig); chip::app::CodegenDataModel::Model model; ScopedMockAccessControl accessControl; TestReadRequest testRequest( kAdminSubjectDescriptor, - ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_TYPE(ZclType))); + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZclType))); std::unique_ptr encoder = testRequest.StartEncoding(&model); @@ -334,16 +416,49 @@ void TestEmberScalarTypeRead(T value) { ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); ASSERT_EQ(attribute_data.size(), 1u); - const DecodedAttributeData & encodedData = attribute_data[0]; + DecodedAttributeData & encodedData = attribute_data[0]; ASSERT_EQ(encodedData.attributePath, testRequest.request.path); - // data element should be a uint32 encoded as TLV - ASSERT_EQ(encodedData.dataReader.GetType(), TlvType); - T actual; - ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR); + typename NumericAttributeTraits::WorkingType actual; + ASSERT_EQ(chip::app::DataModel::Decode::WorkingType>(encodedData.dataReader, actual), + CHIP_NO_ERROR); ASSERT_EQ(actual, value); } +template +void TestEmberScalarNullRead() +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel::Model model; + ScopedMockAccessControl accessControl; + + TestReadRequest testRequest( + kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZclType))); + + std::unique_ptr encoder = testRequest.StartEncoding(&model); + + // Ember encoding for integers is IDENTICAL to the in-memory representation for them + typename NumericAttributeTraits::StorageType nullValue; + NumericAttributeTraits::SetNull(nullValue); + chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(&nullValue), sizeof(nullValue))); + + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + /////// VALIDATE + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + chip::app::DataModel::Nullable::StorageType> actual; + ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR); + ASSERT_TRUE(actual.IsNull()); +} + } // namespace TEST(TestCodegenModelViaMocks, IterateOverEndpoints) @@ -591,44 +706,58 @@ TEST(TestCodegenModelViaMocks, EmberAttributeInvalidRead) TEST(TestCodegenModelViaMocks, EmberAttributeReadInt32S) { - TestEmberScalarTypeRead(-1234); + TestEmberScalarTypeRead(-1234); } TEST(TestCodegenModelViaMocks, EmberAttributeReadEnum16) { - TestEmberScalarTypeRead(0x1234); + TestEmberScalarTypeRead(0x1234); } TEST(TestCodegenModelViaMocks, EmberAttributeReadFloat) { - TestEmberScalarTypeRead(0.625); + TestEmberScalarTypeRead(0.625); } TEST(TestCodegenModelViaMocks, EmberAttributeReadDouble) { - TestEmberScalarTypeRead(0.625); + TestEmberScalarTypeRead(0.625); } - TEST(TestCodegenModelViaMocks, EmberAttributeReadInt32U) { - TestEmberScalarTypeRead(0x1234ABCD); + TestEmberScalarTypeRead(0x1234ABCD); } TEST(TestCodegenModelViaMocks, EmberAttributeReadInt48U) { - TestEmberScalarTypeRead(0xAABB11223344); + TestEmberScalarTypeRead(0xAABB11223344); } TEST(TestCodegenModelViaMocks, EmberAttributeReadBool) { - TestEmberScalarTypeRead(true); - TestEmberScalarTypeRead(false); + TestEmberScalarTypeRead(true); + TestEmberScalarTypeRead(false); } TEST(TestCodegenModelViaMocks, EmberAttributeReadInt8U) { - TestEmberScalarTypeRead(0x12); + TestEmberScalarTypeRead(0x12); +} + +TEST(TestCodegenModelViaMocks, EmberAttributeReadNulls) +{ + TestEmberScalarNullRead(); + TestEmberScalarNullRead(); + TestEmberScalarNullRead(); + TestEmberScalarNullRead(); + + TestEmberScalarNullRead(); + TestEmberScalarNullRead(); + TestEmberScalarNullRead(); + TestEmberScalarNullRead(); + + TestEmberScalarNullRead(); } TEST(TestCodegenModelViaMocks, EmberAttributeReadOctetString) @@ -637,9 +766,9 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadOctetString) chip::app::CodegenDataModel::Model model; ScopedMockAccessControl accessControl; - TestReadRequest testRequest( - kAdminSubjectDescriptor, - ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_TYPE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE))); + TestReadRequest testRequest(kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE))); std::unique_ptr encoder = testRequest.StartEncoding(&model); @@ -677,7 +806,8 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadLongString) TestReadRequest testRequest( kAdminSubjectDescriptor, - ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_TYPE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE))); + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE))); std::unique_ptr encoder = testRequest.StartEncoding(&model); From 2a17d026e0d3831fea88cd21fe5c82577ca98e4c Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 20:57:29 -0400 Subject: [PATCH 071/123] Fix up the tests --- .../tests/TestCodegenModelViaMocks.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 4e48f12e68cb8c..03ea04867003c8 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -405,7 +406,9 @@ void TestEmberScalarTypeRead(typename NumericAttributeTraits::WorkingType val std::unique_ptr encoder = testRequest.StartEncoding(&model); // Ember encoding for integers is IDENTICAL to the in-memory representation for them - chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(&value), sizeof(value))); + typename NumericAttributeTraits::StorageType storage; + NumericAttributeTraits::WorkingToStorage(value, storage); + chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(&storage), sizeof(storage))); ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); @@ -454,7 +457,7 @@ void TestEmberScalarNullRead() DecodedAttributeData & encodedData = attribute_data[0]; ASSERT_EQ(encodedData.attributePath, testRequest.request.path); - chip::app::DataModel::Nullable::StorageType> actual; + chip::app::DataModel::Nullable::WorkingType> actual; ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR); ASSERT_TRUE(actual.IsNull()); } @@ -731,7 +734,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadInt32U) TEST(TestCodegenModelViaMocks, EmberAttributeReadInt48U) { - TestEmberScalarTypeRead(0xAABB11223344); + TestEmberScalarTypeRead, ZCL_INT48U_ATTRIBUTE_TYPE>(0xAABB11223344); } TEST(TestCodegenModelViaMocks, EmberAttributeReadBool) @@ -749,6 +752,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadNulls) { TestEmberScalarNullRead(); TestEmberScalarNullRead(); + TestEmberScalarNullRead, ZCL_INT24U_ATTRIBUTE_TYPE>(); TestEmberScalarNullRead(); TestEmberScalarNullRead(); @@ -756,8 +760,12 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadNulls) TestEmberScalarNullRead(); TestEmberScalarNullRead(); TestEmberScalarNullRead(); + TestEmberScalarNullRead, ZCL_INT40S_ATTRIBUTE_TYPE>(); TestEmberScalarNullRead(); + + TestEmberScalarNullRead(); + TestEmberScalarNullRead(); } TEST(TestCodegenModelViaMocks, EmberAttributeReadOctetString) From f55cdeade8a9a53cd1f17bbb86a4d404ecfdcba0 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 15 May 2024 20:57:40 -0400 Subject: [PATCH 072/123] Restyle --- .../tests/TestCodegenModelViaMocks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 03ea04867003c8..6bcd2a0bf16d83 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -25,10 +25,10 @@ #include #include #include -#include #include #include #include +#include #include #include #include From 9185cc3c2d1e1506031595009518b3052685b9a5 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 16 May 2024 14:59:38 -0400 Subject: [PATCH 073/123] AAI unit test for read --- .../tests/TestCodegenModelViaMocks.cpp | 105 +++++++++++++++++- 1 file changed, 103 insertions(+), 2 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 6bcd2a0bf16d83..32892a5b7b027c 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -16,11 +16,14 @@ */ #include -#include "EmberReadWriteOverride.h" -#include "TestAttributeReportIBsEncoding.h" +#include +#include #include #include +#include +#include +#include #include #include #include @@ -349,6 +352,57 @@ struct UseMockNodeConfig ~UseMockNodeConfig() { ResetMockNodeConfig(); } }; +class StructAttributeAccessInterface : public AttributeAccessInterface +{ +public: + StructAttributeAccessInterface(ConcreteAttributePath path) : + AttributeAccessInterface(MakeOptional(path.mEndpointId), path.mClusterId), mPath(path) + {} + ~StructAttributeAccessInterface() = default; + + CHIP_ERROR Read(const ConcreteReadAttributePath & path, AttributeValueEncoder & encoder) override + { + if (static_cast(path) != mPath) + { + // returning without trying to handle means "I do not handle this" + return CHIP_NO_ERROR; + } + + return encoder.Encode(mData); + } + + void SetReturnedData(const Clusters::UnitTesting::Structs::SimpleStruct::Type & data) { mData = data; } + Clusters::UnitTesting::Structs::SimpleStruct::Type simpleStruct; + +private: + ConcreteAttributePath mPath; + Clusters::UnitTesting::Structs::SimpleStruct::Type mData; +}; + +/// RAII registration of an attribute access interface +template +class RegisteredAttributeAccessInterface +{ +public: + template + RegisteredAttributeAccessInterface(Args &&... args) : mData(std::forward(args)...) + { + VerifyOrDie(registerAttributeAccessOverride(&mData)); + } + ~RegisteredAttributeAccessInterface() { unregisterAttributeAccessOverride(&mData); } + + T * operator->() { return &mData; } + T & operator*() { return mData; } + +private: + T mData; +}; + +/// Contains a `ReadAttributeRequest` as well as classes to convert this into a AttributeReportIBs +/// and later decode it +/// +/// It wraps boilerplate code to obtain a `AttributeValueEncoder` as well as later decoding +/// the underlying encoded data for verification. struct TestReadRequest { ReadAttributeRequest request; @@ -844,3 +898,50 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadLongString) ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR); ASSERT_TRUE(actual.data_equal("abcde"_span)); } + +TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceStructRead) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel::Model model; + ScopedMockAccessControl accessControl; + + const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE)); + + TestReadRequest testRequest(kAdminSubjectDescriptor, kStructPath); + RegisteredAttributeAccessInterface aai(kStructPath); + + aai->SetReturnedData(Clusters::UnitTesting::Structs::SimpleStruct::Type{ + .a = 123, + .b = true, + .e = "foo"_span, + .g = 0.5, + .h = 0.125, + }); + + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + /////// VALIDATE + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + + // data element should be a encoded byte string as this is what the attribute type is + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Structure); + + + Clusters::UnitTesting::Structs::SimpleStruct::DecodableType actual; + ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR); + + ASSERT_EQ(actual.a, 123); + ASSERT_EQ(actual.b, true); + ASSERT_EQ(actual.g, 0.5); + ASSERT_EQ(actual.h, 0.125); + ASSERT_TRUE(actual.e.data_equal("foo"_span)); +} + From 414d61edd5ddb35f9442ec82676d59bd009229e0 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 16 May 2024 15:02:02 -0400 Subject: [PATCH 074/123] Slight comment updates --- .../tests/TestCodegenModelViaMocks.cpp | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 32892a5b7b027c..075391f2435c61 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -457,18 +457,17 @@ void TestEmberScalarTypeRead(typename NumericAttributeTraits::WorkingType val kAdminSubjectDescriptor, ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZclType))); - std::unique_ptr encoder = testRequest.StartEncoding(&model); - // Ember encoding for integers is IDENTICAL to the in-memory representation for them typename NumericAttributeTraits::StorageType storage; NumericAttributeTraits::WorkingToStorage(value, storage); chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(&storage), sizeof(storage))); + // Data read via the encoder + std::unique_ptr encoder = testRequest.StartEncoding(&model); ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); - ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); - /////// VALIDATE + // Validate after read std::vector attribute_data; ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); ASSERT_EQ(attribute_data.size(), 1u); @@ -493,18 +492,17 @@ void TestEmberScalarNullRead() kAdminSubjectDescriptor, ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZclType))); - std::unique_ptr encoder = testRequest.StartEncoding(&model); - // Ember encoding for integers is IDENTICAL to the in-memory representation for them typename NumericAttributeTraits::StorageType nullValue; NumericAttributeTraits::SetNull(nullValue); chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(&nullValue), sizeof(nullValue))); + // Data read via the encoder + std::unique_ptr encoder = testRequest.StartEncoding(&model); ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); - ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); - /////// VALIDATE + // Validate after read std::vector attribute_data; ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); ASSERT_EQ(attribute_data.size(), 1u); @@ -832,18 +830,17 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadOctetString) ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE))); - std::unique_ptr encoder = testRequest.StartEncoding(&model); - // NOTE: This is a pascal string, so actual data is "test" // the longer encoding is to make it clear we do not encode the overflow const char data[] = "\x04testing here with overflow"; chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); + // Actual read via an encoder + std::unique_ptr encoder = testRequest.StartEncoding(&model); ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); - ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); - /////// VALIDATE + // Validate after read std::vector attribute_data; ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); ASSERT_EQ(attribute_data.size(), 1u); @@ -871,8 +868,6 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadLongString) ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE))); - std::unique_ptr encoder = testRequest.StartEncoding(&model); - // NOTE: This is a pascal string, so actual data is "abcde" // the longer encoding is to make it clear we do not encode the overflow char data[] = "\0\0abcdef...this is the alphabet"; @@ -880,11 +875,12 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadLongString) memcpy(data, &len, sizeof(uint16_t)); chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); + // Actual read via an encoder + std::unique_ptr encoder = testRequest.StartEncoding(&model); ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); - ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); - /////// VALIDATE + // Validate after reading std::vector attribute_data; ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); ASSERT_EQ(attribute_data.size(), 1u); @@ -923,7 +919,7 @@ TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceStructRead) ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); - /////// VALIDATE + // Validate after read std::vector attribute_data; ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); ASSERT_EQ(attribute_data.size(), 1u); @@ -931,10 +927,7 @@ TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceStructRead) DecodedAttributeData & encodedData = attribute_data[0]; ASSERT_EQ(encodedData.attributePath, testRequest.request.path); - // data element should be a encoded byte string as this is what the attribute type is ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Structure); - - Clusters::UnitTesting::Structs::SimpleStruct::DecodableType actual; ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR); From 9c61ebb818e805de26ae7b3caff29fe57d77fbe5 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 16 May 2024 15:02:21 -0400 Subject: [PATCH 075/123] Restyle --- .../codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 075391f2435c61..cd816f74192509 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -937,4 +937,3 @@ TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceStructRead) ASSERT_EQ(actual.h, 0.125); ASSERT_TRUE(actual.e.data_equal("foo"_span)); } - From 5bb67726dadc8298ec3a0a4bd225cc931d13e85c Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 17 May 2024 10:30:04 -0400 Subject: [PATCH 076/123] Use StringBuilderAdapters to format chip_error nicely --- .../codegen-interaction-model/tests/BUILD.gn | 1 + .../tests/TestCodegenModelViaMocks.cpp | 18 +----- src/lib/core/BUILD.gn | 12 ++++ src/lib/core/StringBuilderAdapters.h | 55 +++++++++++++++++++ 4 files changed, 69 insertions(+), 17 deletions(-) create mode 100644 src/lib/core/StringBuilderAdapters.h diff --git a/src/app/codegen-interaction-model/tests/BUILD.gn b/src/app/codegen-interaction-model/tests/BUILD.gn index 9762e74c62a3b8..946df1849e9bc8 100644 --- a/src/app/codegen-interaction-model/tests/BUILD.gn +++ b/src/app/codegen-interaction-model/tests/BUILD.gn @@ -44,6 +44,7 @@ source_set("mock_model") { public_deps += [ ":ember_extra_files", "${chip_root}/src/app/util/mock:mock_ember", + "${chip_root}/src/lib/core:string-builder-adapters", ] } diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index cd816f74192509..2079427e896326 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -36,33 +36,17 @@ #include #include #include +#include #include #include -// TODO: CHIP_ERROR tostring should be separated out -#include - using namespace chip; using namespace chip::Test; using namespace chip::app; using namespace chip::app::InteractionModel; using namespace chip::app::Clusters::Globals::Attributes; -namespace pw { - -template <> -StatusWithSize ToString(const CHIP_ERROR & err, pw::span buffer) -{ - if (CHIP_ERROR::IsSuccess(err)) - { - // source location probably does not matter - return pw::string::Format(buffer, "CHIP_NO_ERROR"); - } - return pw::string::Format(buffer, "CHIP_ERROR:<%" CHIP_ERROR_FORMAT ">", err.Format()); -} - -} // namespace pw namespace { diff --git a/src/lib/core/BUILD.gn b/src/lib/core/BUILD.gn index 43a6763a709697..1f0d110d34520d 100644 --- a/src/lib/core/BUILD.gn +++ b/src/lib/core/BUILD.gn @@ -15,6 +15,7 @@ import("//build_overrides/build.gni") import("//build_overrides/chip.gni") import("//build_overrides/nlio.gni") +import("//build_overrides/pigweed.gni") import("${chip_root}/build/chip/buildconfig_header.gni") import("${chip_root}/build/chip/tests.gni") @@ -106,6 +107,17 @@ source_set("error") { ] } +source_set("string-builder-adapters") { + sources = [ + "StringBuilderAdapters.h", + ] + + public_deps = [ + ":error", + "$dir_pw_string", + ] +} + source_set("encoding") { sources = [ "CHIPEncoding.h" ] public_deps = [ "${nlio_root}:nlio" ] diff --git a/src/lib/core/StringBuilderAdapters.h b/src/lib/core/StringBuilderAdapters.h new file mode 100644 index 00000000000000..0c069b794f44bd --- /dev/null +++ b/src/lib/core/StringBuilderAdapters.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +/// This header includes pigweed stringbuilder adaptations for various chip types. +/// You can see https://pigweed.dev/pw_string/guide.html as a reference. +/// +/// In particular, pigweed code generally looks like: +/// +/// pw::StringBuffer<42> sb; +/// sb << "Here is a value: "; +/// sb << value; +/// +/// Where specific formatters exist for "value". In particular these are used when +/// reporting unit test assertions such as ASSERT_EQ/EXPECT_EQ so if you write code +/// like: +/// +/// ASSERT_EQ(SomeCall(), CHIP_NO_ERROR); +/// +/// On failure without adapters, the objects are reported as "24-byte object at 0x....." +/// which is not as helpful as a full CHIP_ERROR formatted output. + +#include + +#include + + +namespace pw { + +template <> + +StatusWithSize ToString(const CHIP_ERROR & err, pw::span buffer) +{ + if (CHIP_ERROR::IsSuccess(err)) + { + // source location probably does not matter + return pw::string::Format(buffer, "CHIP_NO_ERROR"); + } + return pw::string::Format(buffer, "CHIP_ERROR:<%" CHIP_ERROR_FORMAT ">", err.Format()); +} + +} From 684a431b166f00e6d9dab36c253755c96d23f897 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 17 May 2024 10:37:47 -0400 Subject: [PATCH 077/123] Format --- .../tests/TestCodegenModelViaMocks.cpp | 3 +-- src/lib/core/BUILD.gn | 4 +--- src/lib/core/StringBuilderAdapters.h | 5 ++--- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 2079427e896326..b0f3c98dd1fa3a 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -33,10 +33,10 @@ #include #include #include +#include #include #include #include -#include #include #include @@ -47,7 +47,6 @@ using namespace chip::app; using namespace chip::app::InteractionModel; using namespace chip::app::Clusters::Globals::Attributes; - namespace { constexpr FabricIndex kTestFabrixIndex = kMinValidFabricIndex; diff --git a/src/lib/core/BUILD.gn b/src/lib/core/BUILD.gn index 1f0d110d34520d..8173b51a09a75a 100644 --- a/src/lib/core/BUILD.gn +++ b/src/lib/core/BUILD.gn @@ -108,9 +108,7 @@ source_set("error") { } source_set("string-builder-adapters") { - sources = [ - "StringBuilderAdapters.h", - ] + sources = [ "StringBuilderAdapters.h" ] public_deps = [ ":error", diff --git a/src/lib/core/StringBuilderAdapters.h b/src/lib/core/StringBuilderAdapters.h index 0c069b794f44bd..dd84881d020408 100644 --- a/src/lib/core/StringBuilderAdapters.h +++ b/src/lib/core/StringBuilderAdapters.h @@ -19,7 +19,7 @@ /// You can see https://pigweed.dev/pw_string/guide.html as a reference. /// /// In particular, pigweed code generally looks like: -/// +/// /// pw::StringBuffer<42> sb; /// sb << "Here is a value: "; /// sb << value; @@ -37,7 +37,6 @@ #include - namespace pw { template <> @@ -52,4 +51,4 @@ StatusWithSize ToString(const CHIP_ERROR & err, pw::span buffe return pw::string::Format(buffer, "CHIP_ERROR:<%" CHIP_ERROR_FORMAT ">", err.Format()); } -} +} // namespace pw From 8324eb9575288ec823ea0ad2883d10af8ac6a2d8 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 17 May 2024 11:26:34 -0400 Subject: [PATCH 078/123] More unit tests for lists ... test overflow as well --- .../tests/TestCodegenModelViaMocks.cpp | 171 ++++++++++++++++++ third_party/silabs/matter_support | 2 +- 2 files changed, 172 insertions(+), 1 deletion(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index b0f3c98dd1fa3a..5cd68a3779a57d 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -14,6 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "app/AttributeValueDecoder.h" +#include "lib/core/TLVTypes.h" +#include "lib/support/CodeUtils.h" #include #include @@ -335,6 +338,27 @@ struct UseMockNodeConfig ~UseMockNodeConfig() { ResetMockNodeConfig(); } }; +template +CHIP_ERROR DecodeList(TLV::TLVReader & reader, std::vector & out) +{ + TLV::TLVType outer; + ReturnErrorOnFailure(reader.EnterContainer(outer)); + while (true) + { + CHIP_ERROR err = reader.Next(); + + if (err == CHIP_END_OF_TLV) + { + return CHIP_NO_ERROR; + } + ReturnErrorOnFailure(err); + + T value; + ReturnErrorOnFailure(DataModel::Decode(reader, value)); + out.emplace_back(std::move(value)); + } +} + class StructAttributeAccessInterface : public AttributeAccessInterface { public: @@ -362,6 +386,42 @@ class StructAttributeAccessInterface : public AttributeAccessInterface Clusters::UnitTesting::Structs::SimpleStruct::Type mData; }; +class ListAttributeAcessInterface : public AttributeAccessInterface +{ +public: + ListAttributeAcessInterface(ConcreteAttributePath path) : + AttributeAccessInterface(MakeOptional(path.mEndpointId), path.mClusterId), mPath(path) + {} + ~ListAttributeAcessInterface() = default; + + CHIP_ERROR Read(const ConcreteReadAttributePath & path, AttributeValueEncoder & encoder) override + { + if (static_cast(path) != mPath) + { + // returning without trying to handle means "I do not handle this" + return CHIP_NO_ERROR; + } + + return encoder.EncodeList([this](const auto & encoder) { + for (unsigned i = 0; i < mCount; i++) + { + mData.a = static_cast(i % 0xFF); + ReturnErrorOnFailure(encoder.Encode(mData)); + } + return CHIP_NO_ERROR; + }); + } + + void SetReturnedData(const Clusters::UnitTesting::Structs::SimpleStruct::Type & data) { mData = data; } + void SetReturnedDataCount(unsigned count) { mCount = count; } + Clusters::UnitTesting::Structs::SimpleStruct::Type simpleStruct; + +private: + ConcreteAttributePath mPath; + Clusters::UnitTesting::Structs::SimpleStruct::Type mData; + unsigned mCount = 0; +}; + /// RAII registration of an attribute access interface template class RegisteredAttributeAccessInterface @@ -920,3 +980,114 @@ TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceStructRead) ASSERT_EQ(actual.h, 0.125); ASSERT_TRUE(actual.e.data_equal("foo"_span)); } + +TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceListRead) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel::Model model; + ScopedMockAccessControl accessControl; + + const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_ARRAY_ATTRIBUTE_TYPE)); + + TestReadRequest testRequest(kAdminSubjectDescriptor, kStructPath); + RegisteredAttributeAccessInterface aai(kStructPath); + + constexpr unsigned kDataCount = 5; + aai->SetReturnedData(Clusters::UnitTesting::Structs::SimpleStruct::Type{ + .b = true, + .e = "xyz"_span, + .g = 0.25, + .h = 0.5, + }); + aai->SetReturnedDataCount(kDataCount); + + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + // Validate after read + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Array); + + std::vector items; + ASSERT_EQ(DecodeList(encodedData.dataReader, items), CHIP_NO_ERROR); + + ASSERT_EQ(items.size(), kDataCount); + + for (unsigned i = 0; i < kDataCount; i++) + { + Clusters::UnitTesting::Structs::SimpleStruct::DecodableType & actual = items[i]; + + ASSERT_EQ(actual.a, static_cast(i & 0xFF)); + ASSERT_EQ(actual.b, true); + ASSERT_EQ(actual.g, 0.25); + ASSERT_EQ(actual.h, 0.5); + ASSERT_TRUE(actual.e.data_equal("xyz"_span)); + } +} + +TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceListOverflowRead) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel::Model model; + ScopedMockAccessControl accessControl; + + const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_ARRAY_ATTRIBUTE_TYPE)); + + TestReadRequest testRequest(kAdminSubjectDescriptor, kStructPath); + RegisteredAttributeAccessInterface aai(kStructPath); + + constexpr unsigned kDataCount = 1024; + aai->SetReturnedData(Clusters::UnitTesting::Structs::SimpleStruct::Type{ + .b = true, + .e = "thisislongertofillupfaster"_span, + .g = 0.25, + .h = 0.5, + }); + aai->SetReturnedDataCount(kDataCount); + + std::unique_ptr encoder = testRequest.StartEncoding(&model); + // NOTE: overflow, however data should be valid. Technically both NO_MEMORY and BUFFER_TOO_SMALL + // should be ok here, however we know buffer-too-small is the error in this case hence + // the compare (easier to write the test and read the output) + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_ERROR_BUFFER_TOO_SMALL); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + // Validate after read + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Array); + + std::vector items; + ASSERT_EQ(DecodeList(encodedData.dataReader, items), CHIP_NO_ERROR); + + // On last check, 16 items can be encoded. Set some non-zero range to be enforced here that + // SOME list items are actually encoded. Actual lower bound here IS ARBITRARY and was picked + // to just ensure non-zero item count for checks. + ASSERT_GT(items.size(), 5u); + ASSERT_LT(items.size(), kDataCount); + + for (unsigned i = 0; i < items.size(); i++) + { + Clusters::UnitTesting::Structs::SimpleStruct::DecodableType & actual = items[i]; + + ASSERT_EQ(actual.a, static_cast(i & 0xFF)); + ASSERT_EQ(actual.b, true); + ASSERT_EQ(actual.g, 0.25); + ASSERT_EQ(actual.h, 0.5); + ASSERT_TRUE(actual.e.data_equal("thisislongertofillupfaster"_span)); + } +} diff --git a/third_party/silabs/matter_support b/third_party/silabs/matter_support index 56d4d4ec0dea03..edbfeba723c9ff 160000 --- a/third_party/silabs/matter_support +++ b/third_party/silabs/matter_support @@ -1 +1 @@ -Subproject commit 56d4d4ec0dea032302f52632c15d4d7813f8e9f5 +Subproject commit edbfeba723c9ffdc93f57ad1eee85186b1643f25 From 58f86b787ca096b882df45ed4af92eb808f2f56e Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 17 May 2024 11:41:24 -0400 Subject: [PATCH 079/123] Add test support for partially encoded lists --- .../tests/TestCodegenModelViaMocks.cpp | 70 ++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 5cd68a3779a57d..ef8ae296ba1e72 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -14,7 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "app/AttributeEncodeState.h" #include "app/AttributeValueDecoder.h" +#include "app/ConcreteAttributePath.h" #include "lib/core/TLVTypes.h" #include "lib/support/CodeUtils.h" #include @@ -464,7 +466,8 @@ struct TestReadRequest request.path = path; } - std::unique_ptr StartEncoding(chip::app::InteractionModel::Model * model) + std::unique_ptr StartEncoding(chip::app::InteractionModel::Model * model, + AttributeEncodeState state = AttributeEncodeState()) { std::optional info = model->GetClusterInfo(request.path); if (!info.has_value()) @@ -483,7 +486,8 @@ struct TestReadRequest } // TODO: isFabricFiltered? EncodeState? - return std::make_unique(reportBuilder, request.subjectDescriptor.value(), request.path, dataVersion); + return std::make_unique(reportBuilder, request.subjectDescriptor.value(), request.path, dataVersion, + false /* aIsFabricFiltered */, state); } CHIP_ERROR FinishEncoding() { return encodedIBs.FinishEncoding(reportBuilder); } @@ -1091,3 +1095,65 @@ TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceListOverflowRead) ASSERT_TRUE(actual.e.data_equal("thisislongertofillupfaster"_span)); } } + +TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceListIncrementalRead) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel::Model model; + ScopedMockAccessControl accessControl; + + const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_ARRAY_ATTRIBUTE_TYPE)); + + TestReadRequest testRequest(kAdminSubjectDescriptor, kStructPath); + RegisteredAttributeAccessInterface aai(kStructPath); + + constexpr unsigned kDataCount = 1024; + constexpr unsigned kEncodeIndexStart = 101; + aai->SetReturnedData(Clusters::UnitTesting::Structs::SimpleStruct::Type{ + .b = true, + .e = "thisislongertofillupfaster"_span, + .g = 0.25, + .h = 0.5, + }); + aai->SetReturnedDataCount(kDataCount); + + AttributeEncodeState encodeState; + encodeState.SetCurrentEncodingListIndex(kEncodeIndexStart); + + std::unique_ptr encoder = testRequest.StartEncoding(&model, encodeState); + // NOTE: overflow, however data should be valid. Technically both NO_MEMORY and BUFFER_TOO_SMALL + // should be ok here, however we know buffer-too-small is the error in this case hence + // the compare (easier to write the test and read the output) + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_ERROR_BUFFER_TOO_SMALL); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + // Validate after read + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + + // Incremental encodes are separate list items, repeated + // actual size IS ARBITRARY (current test sets it at 11) + ASSERT_GT(attribute_data.size(), 3u); + + for (unsigned i = 0; i < attribute_data.size(); i++) + { + DecodedAttributeData & encodedData = attribute_data[i]; + ASSERT_EQ(encodedData.attributePath.mEndpointId, testRequest.request.path.mEndpointId); + ASSERT_EQ(encodedData.attributePath.mClusterId, testRequest.request.path.mClusterId); + ASSERT_EQ(encodedData.attributePath.mAttributeId, testRequest.request.path.mAttributeId); + ASSERT_EQ(encodedData.attributePath.mListOp, ConcreteDataAttributePath::ListOperation::AppendItem); + + // individual structures encoded in each item + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Structure); + + Clusters::UnitTesting::Structs::SimpleStruct::DecodableType actual; + ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR); + + ASSERT_EQ(actual.a, static_cast((i + kEncodeIndexStart) & 0xFF)); + ASSERT_EQ(actual.b, true); + ASSERT_EQ(actual.g, 0.25); + ASSERT_EQ(actual.h, 0.5); + ASSERT_TRUE(actual.e.data_equal("thisislongertofillupfaster"_span)); + } +} From a373c389f74716e90161d4157b274bebcff9acee Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 17 May 2024 12:43:12 -0400 Subject: [PATCH 080/123] add missing file --- .../CodegenDataModel_Read.cpp | 356 ++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 src/app/codegen-interaction-model/CodegenDataModel_Read.cpp diff --git a/src/app/codegen-interaction-model/CodegenDataModel_Read.cpp b/src/app/codegen-interaction-model/CodegenDataModel_Read.cpp new file mode 100644 index 00000000000000..6dff019139ab38 --- /dev/null +++ b/src/app/codegen-interaction-model/CodegenDataModel_Read.cpp @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace chip { +namespace app { +namespace { +using namespace chip::app::Compatibility::Internal; + +// Fetch the source for the given attribute path: either a cluster (for global ones) or attribute +// path. +// +// if returning a CHIP_ERROR, it will NEVER be CHIP_NO_ERROR. +std::variant +FindAttributeMetadata(const ConcreteAttributePath & aPath) +{ + for (auto & attr : GlobalAttributesNotInMetadata) + { + if (attr == aPath.mAttributeId) + { + const EmberAfCluster * cluster = emberAfFindServerCluster(aPath.mEndpointId, aPath.mClusterId); + ReturnErrorCodeIf(cluster == nullptr, CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); + return cluster; + } + } + + const EmberAfAttributeMetadata * metadata = + emberAfLocateAttributeMetadata(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId); + + if (metadata == nullptr) + { + const EmberAfEndpointType * type = emberAfFindEndpointType(aPath.mEndpointId); + if (type == nullptr) + { + return CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint); + } + + const EmberAfCluster * cluster = emberAfFindClusterInType(type, aPath.mClusterId, CLUSTER_MASK_SERVER); + if (cluster == nullptr) + { + return CHIP_IM_GLOBAL_STATUS(UnsupportedCluster); + } + + // Since we know the attribute is unsupported and the endpoint/cluster are + // OK, this is the only option left. + return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); + } + + return metadata; +} + +/// Attempts to read via an attribute access interface (AAI) +/// +/// If it returns a CHIP_ERROR, then this is a FINAL result (i.e. either failure or success): +/// - in particular, CHIP_ERROR_ACCESS_DENIED will be used for UnsupportedRead AII returns +/// +/// If it returns std::nullopt, then there is no AAI to handle the given path +/// and processing should figure out the value otherwise (generally from other ember data) +std::optional TryReadViaAccessInterface(const ConcreteAttributePath & path, AttributeAccessInterface * aai, + AttributeValueEncoder & encoder) +{ + // Processing can happen only if an attribute access interface actually exists.. + if (aai == nullptr) + { + return std::nullopt; + } + + CHIP_ERROR err = aai->Read(path, encoder); + + // explict translate UnsupportedRead to Access denied. This is to allow callers to determine a + // translation for this: usually wildcard subscriptions MAY just ignore these where as direct reads + // MUST translate them to UnsupportedAccess + ReturnErrorCodeIf(err == CHIP_IM_GLOBAL_STATUS(UnsupportedRead), CHIP_ERROR_ACCESS_DENIED); + + if (err != CHIP_NO_ERROR) + { + return std::make_optional(err); + } + + // If the encoder tried to encode, then a value should have been written. + // - if encode, assueme DONE (i.e. FINAL CHIP_NO_ERROR) + // - if no encode, say that processing must continue + return encoder.TriedEncode() ? std::make_optional(CHIP_NO_ERROR) : std::nullopt; +} + +/// Metadata of what a ember/pascal short string means (prepended by a u8 length) +struct ShortPascalString +{ + using LengthType = uint8_t; + static constexpr LengthType kNullLength = 0xFF; +}; + +/// Metadata of what a ember/pascal LONG string means (prepended by a u16 length) +struct LongPascalString +{ + using LengthType = uint16_t; + static constexpr LengthType kNullLength = 0xFFFF; +}; + +// ember assumptions ... should just work +static_assert(sizeof(ShortPascalString::LengthType) == 1); +static_assert(sizeof(LongPascalString::LengthType) == 2); + +/// Given a ByteSpan containing data from ember, interpret it +/// as a span of type OUT (i.e. ByteSpan or CharSpan) given a ENCODING +/// where ENCODING is Short or Long pascal strings. +template +std::optional ExtractEmberString(ByteSpan data) +{ + typename ENCODING::LengthType len; + + VerifyOrDie(sizeof(len) <= data.size()); + memcpy(&len, data.data(), sizeof(len)); + + if (len == ENCODING::kNullLength) + { + return std::nullopt; + } + + VerifyOrDie(static_cast(len + sizeof(len)) <= data.size()); + return std::make_optional(reinterpret_cast(data.data() + sizeof(len)), len); +} + +/// Encode a value inside `encoder` +/// +/// The value encoded will be of type T (e.g. CharSpan or ByteSpan) and it will be decoded +/// via the given ENCODING (i.e. ShortPascalString or LongPascalString) +/// +/// isNullable defines if the value of NULL is allowed to be encoded. +template +CHIP_ERROR EncodeStringLike(ByteSpan data, bool isNullable, AttributeValueEncoder & encoder) +{ + std::optional value = ExtractEmberString(data); + if (!value.has_value()) + { + if (isNullable) + { + return encoder.EncodeNull(); + } + return CHIP_ERROR_INCORRECT_STATE; + } + + // encode value as-is + return encoder.Encode(*value); +} + +/// Encodes a numeric data value of type T from the given ember-encoded buffer `data`. +/// +/// isNullable defines if the value of NULL is allowed to be encoded. +template +CHIP_ERROR EncodeFromSpan(ByteSpan data, bool isNullable, AttributeValueEncoder & encoder) +{ + typename NumericAttributeTraits::StorageType value; + + VerifyOrReturnError(data.size() >= sizeof(value), CHIP_ERROR_INVALID_ARGUMENT); + memcpy(&value, data.data(), sizeof(value)); + + if (isNullable && NumericAttributeTraits::IsNullValue(value)) + { + return encoder.EncodeNull(); + } + + if (!NumericAttributeTraits::CanRepresentValue(isNullable, value)) + { + return CHIP_ERROR_INCORRECT_STATE; + } + + return encoder.Encode(NumericAttributeTraits::StorageToWorking(value)); +} + +/// Converts raw ember data from `data` into the encoder +/// +/// Uses the attribute `metadata` to determine how the data is encoded into `data` and +/// write a suitable value into `encoder`. +CHIP_ERROR EncodeEmberValue(ByteSpan data, const EmberAfAttributeMetadata * metadata, AttributeValueEncoder & encoder) +{ + VerifyOrReturnError(metadata != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + const bool isNullable = metadata->IsNullable(); + + switch (AttributeBaseType(metadata->attributeType)) + { + case ZCL_NO_DATA_ATTRIBUTE_TYPE: // No data + return encoder.EncodeNull(); + case ZCL_BOOLEAN_ATTRIBUTE_TYPE: // Boolean + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_INT8U_ATTRIBUTE_TYPE: // Unsigned 8-bit integer + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_INT16U_ATTRIBUTE_TYPE: // Unsigned 16-bit integer + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_INT24U_ATTRIBUTE_TYPE: // Unsigned 24-bit integer + return EncodeFromSpan>(data, isNullable, encoder); + case ZCL_INT32U_ATTRIBUTE_TYPE: // Unsigned 32-bit integer + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_INT40U_ATTRIBUTE_TYPE: // Unsigned 40-bit integer + return EncodeFromSpan>(data, isNullable, encoder); + case ZCL_INT48U_ATTRIBUTE_TYPE: // Unsigned 48-bit integer + return EncodeFromSpan>(data, isNullable, encoder); + case ZCL_INT56U_ATTRIBUTE_TYPE: // Unsigned 56-bit integer + return EncodeFromSpan>(data, isNullable, encoder); + case ZCL_INT64U_ATTRIBUTE_TYPE: // Unsigned 64-bit integer + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_INT8S_ATTRIBUTE_TYPE: // Signed 8-bit integer + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_INT16S_ATTRIBUTE_TYPE: // Signed 16-bit integer + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_INT24S_ATTRIBUTE_TYPE: // Signed 24-bit integer + return EncodeFromSpan>(data, isNullable, encoder); + case ZCL_INT32S_ATTRIBUTE_TYPE: // Signed 32-bit integer + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_INT40S_ATTRIBUTE_TYPE: // Signed 40-bit integer + return EncodeFromSpan>(data, isNullable, encoder); + case ZCL_INT48S_ATTRIBUTE_TYPE: // Signed 48-bit integer + return EncodeFromSpan>(data, isNullable, encoder); + case ZCL_INT56S_ATTRIBUTE_TYPE: // Signed 56-bit integer + return EncodeFromSpan>(data, isNullable, encoder); + case ZCL_INT64S_ATTRIBUTE_TYPE: // Signed 64-bit integer + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_SINGLE_ATTRIBUTE_TYPE: // 32-bit float + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_DOUBLE_ATTRIBUTE_TYPE: // 64-bit float + return EncodeFromSpan(data, isNullable, encoder); + case ZCL_CHAR_STRING_ATTRIBUTE_TYPE: // Char string + return EncodeStringLike(data, isNullable, encoder); + case ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE: + return EncodeStringLike(data, isNullable, encoder); + case ZCL_OCTET_STRING_ATTRIBUTE_TYPE: // Octet string + return EncodeStringLike(data, isNullable, encoder); + case ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE: + return EncodeStringLike(data, isNullable, encoder); + default: + ChipLogError(DataManagement, "Attribute type 0x%x not handled", static_cast(metadata->attributeType)); + return CHIP_IM_GLOBAL_STATUS(UnsupportedRead); + } +} + +} // namespace + +/// separated-out ReadAttribute implementation (given existing complexity) +/// +/// Generally will: +/// - validate ACL (only for non-internal requests) +/// - Try to read attribute via the AttributeAccessInterface +/// - Try to read the value from ember RAM storage +CHIP_ERROR CodegenDataModel::ReadAttribute(const InteractionModel::ReadAttributeRequest & request, AttributeValueEncoder & encoder) +{ + ChipLogDetail(DataManagement, + "Reading attribute: Cluster=" ChipLogFormatMEI " Endpoint=%x AttributeId=" ChipLogFormatMEI " (expanded=%d)", + ChipLogValueMEI(request.path.mClusterId), request.path.mEndpointId, ChipLogValueMEI(request.path.mAttributeId), + request.path.mExpanded); + + // ACL check for non-internal requests + if (!request.operationFlags.Has(InteractionModel::OperationFlags::kInternal)) + { + ReturnErrorCodeIf(!request.subjectDescriptor.has_value(), CHIP_ERROR_INVALID_ARGUMENT); + + Access::RequestPath requestPath{ .cluster = request.path.mClusterId, .endpoint = request.path.mEndpointId }; + ReturnErrorOnFailure(Access::GetAccessControl().Check(*request.subjectDescriptor, requestPath, + RequiredPrivilege::ForReadAttribute(request.path))); + } + + auto metadata = FindAttributeMetadata(request.path); + + // Explicit failure in finding a suitable metadata + if (const CHIP_ERROR * err = std::get_if(&metadata)) + { + VerifyOrDie(*err != CHIP_NO_ERROR); + return *err; + } + + // Read via AAI + std::optional aai_result; + if (const EmberAfCluster ** cluster = std::get_if(&metadata)) + { + Compatibility::GlobalAttributeReader aai(*cluster); + aai_result = TryReadViaAccessInterface(request.path, &aai, encoder); + } + else + { + aai_result = TryReadViaAccessInterface( + request.path, GetAttributeAccessOverride(request.path.mEndpointId, request.path.mClusterId), encoder); + } + ReturnErrorCodeIf(aai_result.has_value(), *aai_result); + + if (!std::holds_alternative(metadata)) + { + // if we only got a cluster, this was for a global attribute. We cannot read ember attributes + // at this point, so give up (although GlobalAttributeReader should have returned something here). + // Return a permanent failure... + return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); + } + const EmberAfAttributeMetadata * attributeMetadata = std::get(metadata); + + // At this point, we have to use ember directly to read the data. + EmberAfAttributeSearchRecord record; + record.endpoint = request.path.mEndpointId; + record.clusterId = request.path.mClusterId; + record.attributeId = request.path.mAttributeId; + Protocols::InteractionModel::Status status = emAfReadOrWriteAttribute( + &record, &attributeMetadata, gEmberAttributeIOBufferSpan.data(), gEmberAttributeIOBufferSpan.size(), + /* write = */ false); + + if (status != Protocols::InteractionModel::Status::Success) + { + return ChipError(ChipError::SdkPart::kIMGlobalStatus, to_underlying(status), __FILE__, __LINE__); + } + + return EncodeEmberValue(gEmberAttributeIOBufferSpan, attributeMetadata, encoder); +} + +} // namespace app +} // namespace chip From c2b4edc3f3a0b43c79572f8504777d2d696ee678 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 17 May 2024 13:43:06 -0400 Subject: [PATCH 081/123] Fix name shadowing --- examples/chef/chef.py | 2 +- .../tests/TestCodegenModelViaMocks.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/chef/chef.py b/examples/chef/chef.py index 816840f8584a45..3a6c276e55402b 100755 --- a/examples/chef/chef.py +++ b/examples/chef/chef.py @@ -868,7 +868,7 @@ def main() -> int: """)) if options.do_clean: shell.run_cmd("rm -rf out") - shell.run_cmd("gn gen out") + shell.run_cmd("gn gen --export-compile-commands out") shell.run_cmd("ninja -C out") # diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 48003d6820f6e5..bca7def21f037c 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -403,11 +403,11 @@ class ListAttributeAcessInterface : public AttributeAccessInterface return CHIP_NO_ERROR; } - return encoder.EncodeList([this](const auto & encoder) { + return encoder.EncodeList([this](const auto & listEncoder) { for (unsigned i = 0; i < mCount; i++) { mData.a = static_cast(i % 0xFF); - ReturnErrorOnFailure(encoder.Encode(mData)); + ReturnErrorOnFailure(listEncoder.Encode(mData)); } return CHIP_NO_ERROR; }); From eba45aefd3a961e99d9ad66601e708f18b5d3187 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 17 May 2024 14:32:30 -0400 Subject: [PATCH 082/123] Fix auto-added include names --- src/app/codegen-interaction-model/CodegenDataModel.h | 1 - .../tests/TestCodegenModelViaMocks.cpp | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/app/codegen-interaction-model/CodegenDataModel.h b/src/app/codegen-interaction-model/CodegenDataModel.h index 723831b4368dcb..f3e9ca1c763426 100644 --- a/src/app/codegen-interaction-model/CodegenDataModel.h +++ b/src/app/codegen-interaction-model/CodegenDataModel.h @@ -16,7 +16,6 @@ */ #pragma once -#include "app/ConcreteClusterPath.h" #include #include diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index c4cd5fa2977e79..ace5236ebfd2a1 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -15,13 +15,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "app/GlobalAttributes.h" -#include "lib/core/DataModelTypes.h" #include +#include #include #include #include +#include #include From 98458eb35b31fce44c96342a18355388b073bbf4 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 17 May 2024 15:47:58 -0400 Subject: [PATCH 083/123] Test global attribute read via AAI --- .../tests/TestCodegenModelViaMocks.cpp | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 51f02eb648b363..ae423b395a5686 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -1172,3 +1172,59 @@ TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceListIncrementalRead) ASSERT_TRUE(actual.e.data_equal("thisislongertofillupfaster"_span)); } } + +TEST(TestCodegenModelViaMocks, ReadGlobalAttributeAttributeList) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; + + TestReadRequest testRequest(kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint2, MockClusterId(3), AttributeList::Id)); + + // Data read via the encoder + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + // Validate after read + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Array); + + std::vector items; + ASSERT_EQ(DecodeList(encodedData.dataReader, items), CHIP_NO_ERROR); + + // Mock data contains ClusterRevision and FeatureMap. + // After this, Global attributes are auto-added + std::vector expected; + + // Encoding in global-attribute-access-interface has a logic of: + // - Append global attributes in front of the first specified + // large number global attribute. + // Since ClusterRevision and FeatureMap are + // global attributes, the order here is reversed for them + for (AttributeId id : GlobalAttributesNotInMetadata) + { + expected.push_back(id); + } + expected.push_back(ClusterRevision::Id); + expected.push_back(FeatureMap::Id); + expected.push_back(MockAttributeId(1)); + expected.push_back(MockAttributeId(2)); + expected.push_back(MockAttributeId(3)); + + ASSERT_EQ(items.size(), expected.size()); + + // Since we have no std::vector formatter, comparing element by element is somewhat + // more readable in case of failure. + for (unsigned i = 0; i < items.size(); i++) + { + EXPECT_EQ(items[i], expected[i]); + } +} From 2afa9fad11596ec504bc6eaf464b05b8c67d25ea Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 17 May 2024 15:55:38 -0400 Subject: [PATCH 084/123] More unit test coverage --- .../tests/TestCodegenModelViaMocks.cpp | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index ae423b395a5686..850999f8fe0507 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -841,16 +841,31 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadDouble) TestEmberScalarTypeRead(0.625); } +TEST(TestCodegenModelViaMocks, EmberAttributeReadInt24U) +{ + TestEmberScalarTypeRead, ZCL_INT24U_ATTRIBUTE_TYPE>(0x1234AB); +} + TEST(TestCodegenModelViaMocks, EmberAttributeReadInt32U) { TestEmberScalarTypeRead(0x1234ABCD); } +TEST(TestCodegenModelViaMocks, EmberAttributeReadInt40U) +{ + TestEmberScalarTypeRead, ZCL_INT40U_ATTRIBUTE_TYPE>(0x1122334455); +} + TEST(TestCodegenModelViaMocks, EmberAttributeReadInt48U) { TestEmberScalarTypeRead, ZCL_INT48U_ATTRIBUTE_TYPE>(0xAABB11223344); } +TEST(TestCodegenModelViaMocks, EmberAttributeReadInt56U) +{ + TestEmberScalarTypeRead, ZCL_INT56U_ATTRIBUTE_TYPE>(0xAABB11223344); +} + TEST(TestCodegenModelViaMocks, EmberAttributeReadBool) { TestEmberScalarTypeRead(true); @@ -868,13 +883,19 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadNulls) TestEmberScalarNullRead(); TestEmberScalarNullRead, ZCL_INT24U_ATTRIBUTE_TYPE>(); TestEmberScalarNullRead(); + TestEmberScalarNullRead, ZCL_INT40U_ATTRIBUTE_TYPE>(); + TestEmberScalarNullRead, ZCL_INT48U_ATTRIBUTE_TYPE>(); + TestEmberScalarNullRead, ZCL_INT56U_ATTRIBUTE_TYPE>(); TestEmberScalarNullRead(); TestEmberScalarNullRead(); TestEmberScalarNullRead(); + TestEmberScalarNullRead, ZCL_INT24S_ATTRIBUTE_TYPE>(); TestEmberScalarNullRead(); - TestEmberScalarNullRead(); TestEmberScalarNullRead, ZCL_INT40S_ATTRIBUTE_TYPE>(); + TestEmberScalarNullRead, ZCL_INT48S_ATTRIBUTE_TYPE>(); + TestEmberScalarNullRead, ZCL_INT56S_ATTRIBUTE_TYPE>(); + TestEmberScalarNullRead(); TestEmberScalarNullRead(); From 285c4b010bf2e5af613b4e630e49d75c74bd99ea Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 17 May 2024 16:05:58 -0400 Subject: [PATCH 085/123] Test nullable string reads --- .../tests/TestCodegenModelViaMocks.cpp | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 850999f8fe0507..ef1e1e1f11d8f0 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -903,12 +903,87 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadNulls) TestEmberScalarNullRead(); } +TEST(TestCodegenModelViaMocks, EmberAttributeReadNullOctetString) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; + + TestReadRequest testRequest(kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE))); + + // NOTE: This is a pascal string of size 0xFFFF which for null strings is a null marker + char data[] = "\xFF\xFFInvalid length string is null"; + chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); + + // Actual read via an encoder + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + // Validate after read + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + + // data element should be null for the given 0xFFFF length + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Null); + + chip::app::DataModel::Nullable actual; + ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR); + ASSERT_TRUE(actual.IsNull()); +} + TEST(TestCodegenModelViaMocks, EmberAttributeReadOctetString) { UseMockNodeConfig config(gTestNodeConfig); chip::app::CodegenDataModel model; ScopedMockAccessControl accessControl; + TestReadRequest testRequest( + kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE))); + + // NOTE: This is a pascal string, so actual data is "test" + // the longer encoding is to make it clear we do not encode the overflow + char data[] = "\0\0testing here with overflow"; + uint16_t len = 4; + memcpy(data, &len, sizeof(uint16_t)); + chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); + + // Actual read via an encoder + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + // Validate after read + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + const DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + + // data element should be a encoded byte string as this is what the attribute type is + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_ByteString); + ByteSpan actual; + ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR); + + ByteSpan expected(reinterpret_cast(data + 2), 4); + ASSERT_TRUE(actual.data_equal(expected)); +} + +TEST(TestCodegenModelViaMocks, EmberAttributeReadLongOctetString) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; + TestReadRequest testRequest(kAdminSubjectDescriptor, ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE))); @@ -940,6 +1015,43 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadOctetString) ASSERT_TRUE(actual.data_equal(expected)); } +TEST(TestCodegenModelViaMocks, EmberAttributeReadShortString) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; + + TestReadRequest testRequest(kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_CHAR_STRING_ATTRIBUTE_TYPE))); + + // NOTE: This is a pascal string, so actual data is "abcde" + // the longer encoding is to make it clear we do not encode the overflow + char data[] = "\0abcdef...this is the alphabet"; + uint16_t len = 5; + memcpy(data, &len, sizeof(uint8_t)); + chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); + + // Actual read via an encoder + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + // Validate after reading + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + const DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + + // data element should be a encoded byte string as this is what the attribute type is + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_UTF8String); + CharSpan actual; + ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR); + ASSERT_TRUE(actual.data_equal("abcde"_span)); +} + TEST(TestCodegenModelViaMocks, EmberAttributeReadLongString) { UseMockNodeConfig config(gTestNodeConfig); From 8a3319394f5929bce1aa50d8b9b2ff450dfd20e7 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 17 May 2024 16:11:45 -0400 Subject: [PATCH 086/123] One more test for error pahs --- .../tests/TestCodegenModelViaMocks.cpp | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index ef1e1e1f11d8f0..f4bc5edd0c9c97 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -903,6 +903,39 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadNulls) TestEmberScalarNullRead(); } +TEST(TestCodegenModelViaMocks, EmberAttributeReadErrorReading) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; + + { + TestReadRequest testRequest( + kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE))); + + chip::Test::SetEmberReadOutput(Protocols::InteractionModel::Status::Failure); + + // Actual read via an encoder + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(Failure)); + } + + { + TestReadRequest testRequest( + kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE))); + + chip::Test::SetEmberReadOutput(Protocols::InteractionModel::Status::Busy); + + // Actual read via an encoder + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(Busy)); + } +} + TEST(TestCodegenModelViaMocks, EmberAttributeReadNullOctetString) { UseMockNodeConfig config(gTestNodeConfig); From 38c5759a19af2ab19a2b4828b8292ccefd50cb04 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 17 May 2024 16:15:42 -0400 Subject: [PATCH 087/123] More failure cases on failure path --- .../tests/TestCodegenModelViaMocks.cpp | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index f4bc5edd0c9c97..3b8d0b431da6e3 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -814,11 +814,32 @@ TEST(TestCodegenModelViaMocks, EmberAttributeInvalidRead) chip::app::CodegenDataModel model; ScopedMockAccessControl accessControl; - TestReadRequest testRequest(kAdminSubjectDescriptor, - ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))); - std::unique_ptr encoder = testRequest.StartEncoding(&model); + // Invalid attribute + { + TestReadRequest testRequest(kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))); + std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); + } + + // Invalid cluster + { + TestReadRequest testRequest(kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint1, MockClusterId(100), MockAttributeId(1))); + std::unique_ptr encoder = testRequest.StartEncoding(&model); + + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedCluster)); + } + + // Invalid endpoint + { + TestReadRequest testRequest(kAdminSubjectDescriptor, + ConcreteAttributePath(kEndpointIdThatIsMissing, MockClusterId(1), MockAttributeId(1))); + std::unique_ptr encoder = testRequest.StartEncoding(&model); + + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint)); + } } TEST(TestCodegenModelViaMocks, EmberAttributeReadInt32S) From 7befbcda43ae56e67d174ec5fd15cbfb3a33580e Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 17 May 2024 16:20:29 -0400 Subject: [PATCH 088/123] Restyle --- .../tests/TestCodegenModelViaMocks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp index 3b8d0b431da6e3..7188fd61f6e948 100644 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp @@ -822,7 +822,7 @@ TEST(TestCodegenModelViaMocks, EmberAttributeInvalidRead) ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); } - + // Invalid cluster { TestReadRequest testRequest(kAdminSubjectDescriptor, From 22e9df0e330b251a45a489cffc6992aa3b5e7763 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Tue, 21 May 2024 11:52:28 -0400 Subject: [PATCH 089/123] Remove back the initialization and make the comment more obvious --- src/app/ConcreteClusterPath.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/ConcreteClusterPath.h b/src/app/ConcreteClusterPath.h index 47feae1fd67269..8b701efa83967b 100644 --- a/src/app/ConcreteClusterPath.h +++ b/src/app/ConcreteClusterPath.h @@ -52,7 +52,7 @@ struct ConcreteClusterPath // to alignment requirements it's "free" in the sense of not needing more // memory to put it here. But we don't initialize it, because that // increases codesize for the non-consumers. - bool mExpanded = false; // NOTE: in between larger members + bool mExpanded; // NOTE: in between larger members, NOT initialized (see above) ClusterId mClusterId = 0; }; From 48f2fe8bd3f10ba52040eebcc1ae5b20f54218e8 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 22 May 2024 10:56:28 -0400 Subject: [PATCH 090/123] Undo odd include that got auto-added --- src/app/util/ember-compatibility-functions.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/util/ember-compatibility-functions.cpp b/src/app/util/ember-compatibility-functions.cpp index df49d37ead4436..cc3185f78a5df6 100644 --- a/src/app/util/ember-compatibility-functions.cpp +++ b/src/app/util/ember-compatibility-functions.cpp @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "app/util/ember-io-storage.h" #include #include From 60ed0bed45595c3f3c07c04c59f01d7b6c1424a9 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 12 Jun 2024 10:38:26 -0400 Subject: [PATCH 091/123] Place files back past renames --- src/app/codegen-data-model/BUILD.gn | 1 + src/app/codegen-data-model/CodegenDataModel.h | 2 +- .../CodegenDataModel_Read.cpp | 0 src/app/codegen-data-model/model.gni | 1 + src/app/codegen-data-model/tests/BUILD.gn | 28 +- .../tests/EmberReadWriteOverride.cpp | 0 .../tests/EmberReadWriteOverride.h | 0 .../InteractionModelTemporaryOverrides.cpp | 0 .../tests/TestAttributeReportIBsEncoding.cpp | 0 .../tests/TestAttributeReportIBsEncoding.h | 0 .../tests/TestCodegenModelViaMocks.cpp | 1251 +++++++++++++-- src/app/codegen-interaction-model/BUILD.gn | 28 - .../CodegenDataModel.cpp | 404 ----- .../CodegenDataModel.h | 83 - src/app/codegen-interaction-model/model.gni | 36 - .../codegen-interaction-model/tests/BUILD.gn | 59 - .../tests/TestCodegenModelViaMocks.cpp | 1417 ----------------- 17 files changed, 1119 insertions(+), 2191 deletions(-) rename src/app/{codegen-interaction-model => codegen-data-model}/CodegenDataModel_Read.cpp (100%) rename src/app/{codegen-interaction-model => codegen-data-model}/tests/EmberReadWriteOverride.cpp (100%) rename src/app/{codegen-interaction-model => codegen-data-model}/tests/EmberReadWriteOverride.h (100%) rename src/app/{codegen-interaction-model => codegen-data-model}/tests/InteractionModelTemporaryOverrides.cpp (100%) rename src/app/{codegen-interaction-model => codegen-data-model}/tests/TestAttributeReportIBsEncoding.cpp (100%) rename src/app/{codegen-interaction-model => codegen-data-model}/tests/TestAttributeReportIBsEncoding.h (100%) delete mode 100644 src/app/codegen-interaction-model/BUILD.gn delete mode 100644 src/app/codegen-interaction-model/CodegenDataModel.cpp delete mode 100644 src/app/codegen-interaction-model/CodegenDataModel.h delete mode 100644 src/app/codegen-interaction-model/model.gni delete mode 100644 src/app/codegen-interaction-model/tests/BUILD.gn delete mode 100644 src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp diff --git a/src/app/codegen-data-model/BUILD.gn b/src/app/codegen-data-model/BUILD.gn index 418983a2fc8b8f..5803f01a37778e 100644 --- a/src/app/codegen-data-model/BUILD.gn +++ b/src/app/codegen-data-model/BUILD.gn @@ -20,6 +20,7 @@ import("//build_overrides/chip.gni") # # Use `model.gni` to get access to: # CodegenDataModel.cpp +# CodegenDataModel_Read.cpp # CodegenDataModel.h # # The above list of files exists to satisfy the "dependency linter" diff --git a/src/app/codegen-data-model/CodegenDataModel.h b/src/app/codegen-data-model/CodegenDataModel.h index f8d997451bdace..3fc69cc8709df5 100644 --- a/src/app/codegen-data-model/CodegenDataModel.h +++ b/src/app/codegen-data-model/CodegenDataModel.h @@ -68,7 +68,7 @@ class CodegenDataModel : public chip::app::InteractionModel::DataModel /// Generic model implementations CHIP_ERROR Shutdown() override { return CHIP_NO_ERROR; } - CHIP_ERROR ReadAttribute(const InteractionModel::ReadAttributeRequest & request, InteractionModel::ReadState & state, + CHIP_ERROR ReadAttribute(const InteractionModel::ReadAttributeRequest & request, AttributeValueEncoder & encoder) override; CHIP_ERROR WriteAttribute(const InteractionModel::WriteAttributeRequest & request, AttributeValueDecoder & decoder) override; CHIP_ERROR Invoke(const InteractionModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, diff --git a/src/app/codegen-interaction-model/CodegenDataModel_Read.cpp b/src/app/codegen-data-model/CodegenDataModel_Read.cpp similarity index 100% rename from src/app/codegen-interaction-model/CodegenDataModel_Read.cpp rename to src/app/codegen-data-model/CodegenDataModel_Read.cpp diff --git a/src/app/codegen-data-model/model.gni b/src/app/codegen-data-model/model.gni index c8dce5617b9a7b..753c392eeda852 100644 --- a/src/app/codegen-data-model/model.gni +++ b/src/app/codegen-data-model/model.gni @@ -27,6 +27,7 @@ import("//build_overrides/chip.gni") codegen_interaction_model_SOURCES = [ "${chip_root}/src/app/codegen-data-model/CodegenDataModel.h", "${chip_root}/src/app/codegen-data-model/CodegenDataModel.cpp", + "${chip_root}/src/app/codegen-data-model/CodegenDataModel_Read.cpp", ] codegen_interaction_model_PUBLIC_DEPS = [ diff --git a/src/app/codegen-data-model/tests/BUILD.gn b/src/app/codegen-data-model/tests/BUILD.gn index a3857184c25b74..946df1849e9bc8 100644 --- a/src/app/codegen-data-model/tests/BUILD.gn +++ b/src/app/codegen-data-model/tests/BUILD.gn @@ -13,7 +13,27 @@ # limitations under the License. import("//build_overrides/chip.gni") import("${chip_root}/build/chip/chip_test_suite.gni") -import("${chip_root}/src/app/codegen-data-model/model.gni") +import("${chip_root}/src/app/codegen-interaction-model/model.gni") + +source_set("ember_extra_files") { + sources = [ + # This IS TERRIBLE, however we want to pretent AAI exists for global + # items and we need a shared IO storage to reduce overhead between + # data-model access and ember-compatibility (we share the same buffer) + "${chip_root}/src/app/util/ember-global-attribute-access-interface.cpp", + "${chip_root}/src/app/util/ember-io-storage.cpp", + "EmberReadWriteOverride.cpp", + "EmberReadWriteOverride.h", + "InteractionModelTemporaryOverrides.cpp", + "TestAttributeReportIBsEncoding.cpp", + "TestAttributeReportIBsEncoding.h", + ] + + public_deps = [ + "${chip_root}/src/app/util/mock:mock_ember", + "${chip_root}/src/protocols", + ] +} source_set("mock_model") { sources = codegen_interaction_model_SOURCES @@ -21,7 +41,11 @@ source_set("mock_model") { public_deps = codegen_interaction_model_PUBLIC_DEPS # this ties in the codegen model to an actual ember implementation - public_deps += [ "${chip_root}/src/app/util/mock:mock_ember" ] + public_deps += [ + ":ember_extra_files", + "${chip_root}/src/app/util/mock:mock_ember", + "${chip_root}/src/lib/core:string-builder-adapters", + ] } chip_test_suite("tests") { diff --git a/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.cpp b/src/app/codegen-data-model/tests/EmberReadWriteOverride.cpp similarity index 100% rename from src/app/codegen-interaction-model/tests/EmberReadWriteOverride.cpp rename to src/app/codegen-data-model/tests/EmberReadWriteOverride.cpp diff --git a/src/app/codegen-interaction-model/tests/EmberReadWriteOverride.h b/src/app/codegen-data-model/tests/EmberReadWriteOverride.h similarity index 100% rename from src/app/codegen-interaction-model/tests/EmberReadWriteOverride.h rename to src/app/codegen-data-model/tests/EmberReadWriteOverride.h diff --git a/src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp b/src/app/codegen-data-model/tests/InteractionModelTemporaryOverrides.cpp similarity index 100% rename from src/app/codegen-interaction-model/tests/InteractionModelTemporaryOverrides.cpp rename to src/app/codegen-data-model/tests/InteractionModelTemporaryOverrides.cpp diff --git a/src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.cpp b/src/app/codegen-data-model/tests/TestAttributeReportIBsEncoding.cpp similarity index 100% rename from src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.cpp rename to src/app/codegen-data-model/tests/TestAttributeReportIBsEncoding.cpp diff --git a/src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.h b/src/app/codegen-data-model/tests/TestAttributeReportIBsEncoding.h similarity index 100% rename from src/app/codegen-interaction-model/tests/TestAttributeReportIBsEncoding.h rename to src/app/codegen-data-model/tests/TestAttributeReportIBsEncoding.h diff --git a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp index 4403303c70cbb3..7188fd61f6e948 100644 --- a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp @@ -1,5 +1,4 @@ /* - * * Copyright (c) 2024 Project CHIP Authors * All rights reserved. * @@ -15,14 +14,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include - +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include +#include +#include #include +#include +#include +#include +#include #include +#include using namespace chip; using namespace chip::Test; @@ -32,6 +54,9 @@ using namespace chip::app::Clusters::Globals::Attributes; namespace { +constexpr FabricIndex kTestFabrixIndex = kMinValidFabricIndex; +constexpr NodeId kTestNodeId = 0xFFFF'1234'ABCD'4321; + constexpr EndpointId kEndpointIdThatIsMissing = kMockEndpointMin - 1; static_assert(kEndpointIdThatIsMissing != kInvalidEndpointId); @@ -39,6 +64,85 @@ static_assert(kEndpointIdThatIsMissing != kMockEndpoint1); static_assert(kEndpointIdThatIsMissing != kMockEndpoint2); static_assert(kEndpointIdThatIsMissing != kMockEndpoint3); +constexpr Access::SubjectDescriptor kAdminSubjectDescriptor{ + .fabricIndex = kTestFabrixIndex, + .authMode = Access::AuthMode::kCase, + .subject = kTestNodeId, +}; +constexpr Access::SubjectDescriptor kViewSubjectDescriptor{ + .fabricIndex = kTestFabrixIndex + 1, + .authMode = Access::AuthMode::kCase, + .subject = kTestNodeId, +}; + +constexpr Access::SubjectDescriptor kDenySubjectDescriptor{ + .fabricIndex = kTestFabrixIndex + 2, + .authMode = Access::AuthMode::kCase, + .subject = kTestNodeId, +}; + +bool operator==(const Access::SubjectDescriptor & a, const Access::SubjectDescriptor & b) +{ + if (a.fabricIndex != b.fabricIndex) + { + return false; + } + if (a.authMode != b.authMode) + { + return false; + } + if (a.subject != b.subject) + { + return false; + } + for (unsigned i = 0; i < a.cats.values.size(); i++) + { + if (a.cats.values[i] != b.cats.values[i]) + { + return false; + } + } + return true; +} + +class MockAccessControl : public Access::AccessControl::Delegate, public Access::AccessControl::DeviceTypeResolver +{ +public: + CHIP_ERROR Check(const Access::SubjectDescriptor & subjectDescriptor, const Access::RequestPath & requestPath, + Access::Privilege requestPrivilege) override + { + if (subjectDescriptor == kAdminSubjectDescriptor) + { + return CHIP_NO_ERROR; + } + if ((subjectDescriptor == kViewSubjectDescriptor) && (requestPrivilege == Access::Privilege::kView)) + { + return CHIP_NO_ERROR; + } + return CHIP_ERROR_ACCESS_DENIED; + } + + bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint) override { return true; } +}; + +class ScopedMockAccessControl +{ +public: + ScopedMockAccessControl() { Access::GetAccessControl().Init(&mMock, mMock); } + ~ScopedMockAccessControl() { Access::GetAccessControl().Finish(); } + +private: + MockAccessControl mMock; +}; + +#define MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(zcl_type) MockAttributeId(zcl_type + 0x1000) +#define MOCK_ATTRIBUTE_CONFIG_NULLABLE(zcl_type) \ + MockAttributeConfig(MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(zcl_type), zcl_type, ATTRIBUTE_MASK_WRITABLE | ATTRIBUTE_MASK_NULLABLE) + +#define MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(zcl_type) MockAttributeId(zcl_type + 0x2000) +#define MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(zcl_type) \ + MockAttributeConfig(MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(zcl_type), zcl_type, ATTRIBUTE_MASK_WRITABLE) + // clang-format off const MockNodeConfig gTestNodeConfig({ MockEndpointConfig(kMockEndpoint1, { @@ -55,27 +159,15 @@ const MockNodeConfig gTestNodeConfig({ MockClusterConfig(MockClusterId(1), { ClusterRevision::Id, FeatureMap::Id, }), - MockClusterConfig( - MockClusterId(2), - { - ClusterRevision::Id, - FeatureMap::Id, - MockAttributeId(1), - MockAttributeConfig(MockAttributeId(2), ZCL_ARRAY_ATTRIBUTE_TYPE), - }, /* attributes */ - {}, /* events */ - {1, 2, 23}, /* acceptedCommands */ - {2, 10} /* generatedCommands */ - ), - MockClusterConfig( - MockClusterId(3), - { - ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), MockAttributeId(2), MockAttributeId(3), - }, /* attributes */ - {}, /* events */ - {11}, /* acceptedCommands */ - {4, 6} /* generatedCommands */ - ), + MockClusterConfig(MockClusterId(2), { + ClusterRevision::Id, + FeatureMap::Id, + MockAttributeId(1), + MockAttributeConfig(MockAttributeId(2), ZCL_ARRAY_ATTRIBUTE_TYPE), + }), + MockClusterConfig(MockClusterId(3), { + ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), MockAttributeId(2), MockAttributeId(3), + }), }), MockEndpointConfig(kMockEndpoint3, { MockClusterConfig(MockClusterId(1), { @@ -88,7 +180,155 @@ const MockNodeConfig gTestNodeConfig({ ClusterRevision::Id, FeatureMap::Id, }), MockClusterConfig(MockClusterId(4), { - ClusterRevision::Id, FeatureMap::Id, + ClusterRevision::Id, + FeatureMap::Id, + // several attributes of varying data types for testing. + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BOOLEAN_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BITMAP8_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BITMAP16_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BITMAP32_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BITMAP64_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT8U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT16U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT24U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT32U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT40U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT48U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT56U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT64U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT8S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT16S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT24S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT32S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT40S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT48S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT56S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT64S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENUM8_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENUM16_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_PRIORITY_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_STATUS_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_SINGLE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DOUBLE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_CHAR_STRING_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ARRAY_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_STRUCT_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_GROUP_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_VENDOR_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_FABRIC_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_FABRIC_IDX_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENTRY_IDX_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DATA_VER_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EVENT_NO_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_SEMTAG_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_NAMESPACE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TAG_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_SYSTIME_US_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_SYSTIME_MS_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ELAPSED_S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TEMPERATURE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_POWER_MW_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENERGY_MWH_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TOD_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DATE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EPOCH_US_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EPOCH_S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_POSIX_MS_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_PERCENT_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_PERCENT100THS_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_CLUSTER_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ATTRIB_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_FIELD_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EVENT_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_COMMAND_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ACTION_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TRANS_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_NODE_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPADR_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPV4ADR_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPV6ADR_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPV6PRE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_HWADR_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BOOLEAN_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP8_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP16_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP32_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP64_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT8U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT16U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT24U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT32U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT40U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT48U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT56U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT64U_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT8S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT16S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT24S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT32S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT40S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT48S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT56S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT64S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENUM8_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENUM16_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_PRIORITY_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_STATUS_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SINGLE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DOUBLE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_CHAR_STRING_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ARRAY_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_STRUCT_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_GROUP_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_VENDOR_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_FABRIC_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_FABRIC_IDX_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENTRY_IDX_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DATA_VER_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EVENT_NO_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SEMTAG_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_NAMESPACE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TAG_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SYSTIME_US_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SYSTIME_MS_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ELAPSED_S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TEMPERATURE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_POWER_MW_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENERGY_MWH_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TOD_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DATE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EPOCH_US_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EPOCH_S_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_POSIX_MS_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_PERCENT_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_PERCENT100THS_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_CLUSTER_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ATTRIB_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_FIELD_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EVENT_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_COMMAND_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ACTION_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TRANS_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_NODE_ID_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPADR_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPV4ADR_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPV6ADR_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPV6PRE_ATTRIBUTE_TYPE), + MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_HWADR_ATTRIBUTE_TYPE), }), }), }); @@ -100,6 +340,227 @@ struct UseMockNodeConfig ~UseMockNodeConfig() { ResetMockNodeConfig(); } }; +template +CHIP_ERROR DecodeList(TLV::TLVReader & reader, std::vector & out) +{ + TLV::TLVType outer; + ReturnErrorOnFailure(reader.EnterContainer(outer)); + while (true) + { + CHIP_ERROR err = reader.Next(); + + if (err == CHIP_END_OF_TLV) + { + return CHIP_NO_ERROR; + } + ReturnErrorOnFailure(err); + + T value; + ReturnErrorOnFailure(DataModel::Decode(reader, value)); + out.emplace_back(std::move(value)); + } +} + +class StructAttributeAccessInterface : public AttributeAccessInterface +{ +public: + StructAttributeAccessInterface(ConcreteAttributePath path) : + AttributeAccessInterface(MakeOptional(path.mEndpointId), path.mClusterId), mPath(path) + {} + ~StructAttributeAccessInterface() = default; + + CHIP_ERROR Read(const ConcreteReadAttributePath & path, AttributeValueEncoder & encoder) override + { + if (static_cast(path) != mPath) + { + // returning without trying to handle means "I do not handle this" + return CHIP_NO_ERROR; + } + + return encoder.Encode(mData); + } + + void SetReturnedData(const Clusters::UnitTesting::Structs::SimpleStruct::Type & data) { mData = data; } + Clusters::UnitTesting::Structs::SimpleStruct::Type simpleStruct; + +private: + ConcreteAttributePath mPath; + Clusters::UnitTesting::Structs::SimpleStruct::Type mData; +}; + +class ListAttributeAcessInterface : public AttributeAccessInterface +{ +public: + ListAttributeAcessInterface(ConcreteAttributePath path) : + AttributeAccessInterface(MakeOptional(path.mEndpointId), path.mClusterId), mPath(path) + {} + ~ListAttributeAcessInterface() = default; + + CHIP_ERROR Read(const ConcreteReadAttributePath & path, AttributeValueEncoder & encoder) override + { + if (static_cast(path) != mPath) + { + // returning without trying to handle means "I do not handle this" + return CHIP_NO_ERROR; + } + + return encoder.EncodeList([this](const auto & listEncoder) { + for (unsigned i = 0; i < mCount; i++) + { + mData.a = static_cast(i % 0xFF); + ReturnErrorOnFailure(listEncoder.Encode(mData)); + } + return CHIP_NO_ERROR; + }); + } + + void SetReturnedData(const Clusters::UnitTesting::Structs::SimpleStruct::Type & data) { mData = data; } + void SetReturnedDataCount(unsigned count) { mCount = count; } + Clusters::UnitTesting::Structs::SimpleStruct::Type simpleStruct; + +private: + ConcreteAttributePath mPath; + Clusters::UnitTesting::Structs::SimpleStruct::Type mData; + unsigned mCount = 0; +}; + +/// RAII registration of an attribute access interface +template +class RegisteredAttributeAccessInterface +{ +public: + template + RegisteredAttributeAccessInterface(Args &&... args) : mData(std::forward(args)...) + { + VerifyOrDie(registerAttributeAccessOverride(&mData)); + } + ~RegisteredAttributeAccessInterface() { unregisterAttributeAccessOverride(&mData); } + + T * operator->() { return &mData; } + T & operator*() { return mData; } + +private: + T mData; +}; + +/// Contains a `ReadAttributeRequest` as well as classes to convert this into a AttributeReportIBs +/// and later decode it +/// +/// It wraps boilerplate code to obtain a `AttributeValueEncoder` as well as later decoding +/// the underlying encoded data for verification. +struct TestReadRequest +{ + ReadAttributeRequest request; + + // encoded-used classes + EncodedReportIBs encodedIBs; + AttributeReportIBs::Builder reportBuilder; + std::unique_ptr encoder; + + TestReadRequest(const Access::SubjectDescriptor & subject, const ConcreteAttributePath & path) + { + // operationFlags is 0 i.e. not internal + // readFlags is 0 i.e. not fabric filtered + // dataVersion is missing (no data version filtering) + request.subjectDescriptor = subject; + request.path = path; + } + + std::unique_ptr StartEncoding(chip::app::InteractionModel::Model * model, + AttributeEncodeState state = AttributeEncodeState()) + { + std::optional info = model->GetClusterInfo(request.path); + if (!info.has_value()) + { + ChipLogError(Test, "Missing cluster information - no data version"); + return nullptr; + } + + DataVersion dataVersion = info->dataVersion; // NOLINT(bugprone-unchecked-optional-access) + + CHIP_ERROR err = encodedIBs.StartEncoding(reportBuilder); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Test, "FAILURE starting encoding %" CHIP_ERROR_FORMAT, err.Format()); + return nullptr; + } + + // TODO: isFabricFiltered? EncodeState? + return std::make_unique(reportBuilder, request.subjectDescriptor.value(), request.path, dataVersion, + false /* aIsFabricFiltered */, state); + } + + CHIP_ERROR FinishEncoding() { return encodedIBs.FinishEncoding(reportBuilder); } +}; + +template +void TestEmberScalarTypeRead(typename NumericAttributeTraits::WorkingType value) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; + + TestReadRequest testRequest( + kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZclType))); + + // Ember encoding for integers is IDENTICAL to the in-memory representation for them + typename NumericAttributeTraits::StorageType storage; + NumericAttributeTraits::WorkingToStorage(value, storage); + chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(&storage), sizeof(storage))); + + // Data read via the encoder + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + // Validate after read + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + + typename NumericAttributeTraits::WorkingType actual; + ASSERT_EQ(chip::app::DataModel::Decode::WorkingType>(encodedData.dataReader, actual), + CHIP_NO_ERROR); + ASSERT_EQ(actual, value); +} + +template +void TestEmberScalarNullRead() +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; + + TestReadRequest testRequest( + kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZclType))); + + // Ember encoding for integers is IDENTICAL to the in-memory representation for them + typename NumericAttributeTraits::StorageType nullValue; + NumericAttributeTraits::SetNull(nullValue); + chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(&nullValue), sizeof(nullValue))); + + // Data read via the encoder + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + // Validate after read + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + chip::app::DataModel::Nullable::WorkingType> actual; + ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR); + ASSERT_TRUE(actual.IsNull()); +} + } // namespace TEST(TestCodegenModelViaMocks, IterateOverEndpoints) @@ -110,24 +571,20 @@ TEST(TestCodegenModelViaMocks, IterateOverEndpoints) // This iteration relies on the hard-coding that occurs when mock_ember is used EXPECT_EQ(model.FirstEndpoint(), kMockEndpoint1); EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); - EXPECT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); - EXPECT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); + ASSERT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); + ASSERT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); /// Some out of order requests should work as well - EXPECT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); - EXPECT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); - EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); - EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); - EXPECT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); - EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); - EXPECT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); - EXPECT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); - EXPECT_EQ(model.FirstEndpoint(), kMockEndpoint1); - EXPECT_EQ(model.FirstEndpoint(), kMockEndpoint1); - - // invalid endpoiunts - EXPECT_EQ(model.NextEndpoint(kInvalidEndpointId), kInvalidEndpointId); - EXPECT_EQ(model.NextEndpoint(987u), kInvalidEndpointId); + ASSERT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); + ASSERT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); + ASSERT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); + ASSERT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); + ASSERT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); + ASSERT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); + ASSERT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); + ASSERT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); + ASSERT_EQ(model.FirstEndpoint(), kMockEndpoint1); + ASSERT_EQ(model.FirstEndpoint(), kMockEndpoint1); } TEST(TestCodegenModelViaMocks, IterateOverClusters) @@ -139,9 +596,6 @@ TEST(TestCodegenModelViaMocks, IterateOverClusters) EXPECT_FALSE(model.FirstCluster(kEndpointIdThatIsMissing).path.HasValidIds()); EXPECT_FALSE(model.FirstCluster(kInvalidEndpointId).path.HasValidIds()); - EXPECT_FALSE(model.NextCluster(ConcreteClusterPath(kInvalidEndpointId, 123)).path.HasValidIds()); - EXPECT_FALSE(model.NextCluster(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds()); - EXPECT_FALSE(model.NextCluster(ConcreteClusterPath(kMockEndpoint1, 981u)).path.HasValidIds()); // mock endpoint 1 has 2 mock clusters: 1 and 2 ClusterEntry entry = model.FirstCluster(kMockEndpoint1); @@ -229,12 +683,6 @@ TEST(TestCodegenModelViaMocks, IterateOverAttributes) ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).path.HasValidIds()); ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds()); - ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kEndpointIdThatIsMissing, MockClusterId(1), 1u)).path.HasValidIds()); - ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kInvalidEndpointId, MockClusterId(1), 1u)).path.HasValidIds()); - ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), 1u)).path.HasValidIds()); - ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kMockEndpoint1, kInvalidClusterId, 1u)).path.HasValidIds()); - ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), 987u)).path.HasValidIds()); - // should be able to iterate over valid paths AttributeEntry entry = model.FirstAttribute(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); ASSERT_TRUE(entry.path.HasValidIds()); @@ -264,6 +712,21 @@ TEST(TestCodegenModelViaMocks, IterateOverAttributes) ASSERT_EQ(entry.path.mAttributeId, MockAttributeId(2)); ASSERT_TRUE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); + // Iteration MUST include global attributes. Ember does not provide those, so we + // assert here that we present them in order + for (auto globalAttributeId : GlobalAttributesNotInMetadata) + { + + entry = model.NextAttribute(entry.path); + ASSERT_TRUE(entry.path.HasValidIds()); + ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); + ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); + ASSERT_EQ(entry.path.mAttributeId, globalAttributeId); + + // all global attributes not in ember metadata are LIST typed + ASSERT_TRUE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); + } + entry = model.NextAttribute(entry.path); ASSERT_FALSE(entry.path.HasValidIds()); @@ -315,7 +778,6 @@ TEST(TestCodegenModelViaMocks, GetAttributeInfo) EXPECT_TRUE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) } -// global attributes are EXPLICITLY not supported TEST(TestCodegenModelViaMocks, GlobalAttributeInfo) { UseMockNodeConfig config(gTestNodeConfig); @@ -324,165 +786,632 @@ TEST(TestCodegenModelViaMocks, GlobalAttributeInfo) std::optional info = model.GetAttributeInfo( ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::GeneratedCommandList::Id)); - ASSERT_FALSE(info.has_value()); + ASSERT_TRUE(info.has_value()); + EXPECT_TRUE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) info = model.GetAttributeInfo( ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::AttributeList::Id)); - ASSERT_FALSE(info.has_value()); + ASSERT_TRUE(info.has_value()); + EXPECT_TRUE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) } -TEST(TestCodegenModelViaMocks, IterateOverAcceptedCommands) +TEST(TestCodegenModelViaMocks, EmberAttributeReadAclDeny) { UseMockNodeConfig config(gTestNodeConfig); chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; - // invalid paths should return in "no more data" - ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).path.HasValidIds()); - ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).path.HasValidIds()); - ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).path.HasValidIds()); - ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds()); + TestReadRequest testRequest(kDenySubjectDescriptor, + ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))); + std::unique_ptr encoder = testRequest.StartEncoding(&model); - // should be able to iterate over valid paths - CommandEntry entry = model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); - ASSERT_TRUE(entry.path.HasValidIds()); - EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); - EXPECT_EQ(entry.path.mCommandId, 1u); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_ERROR_ACCESS_DENIED); +} - entry = model.NextAcceptedCommand(entry.path); - ASSERT_TRUE(entry.path.HasValidIds()); - EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); - EXPECT_EQ(entry.path.mCommandId, 2u); +TEST(TestCodegenModelViaMocks, EmberAttributeInvalidRead) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; - entry = model.NextAcceptedCommand(entry.path); - ASSERT_TRUE(entry.path.HasValidIds()); - EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); - EXPECT_EQ(entry.path.mCommandId, 23u); + // Invalid attribute + { + TestReadRequest testRequest(kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))); + std::unique_ptr encoder = testRequest.StartEncoding(&model); - entry = model.NextAcceptedCommand(entry.path); - ASSERT_FALSE(entry.path.HasValidIds()); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); + } - // attempt some out-of-order requests as well - entry = model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(3))); - ASSERT_TRUE(entry.path.HasValidIds()); - EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(entry.path.mClusterId, MockClusterId(3)); - EXPECT_EQ(entry.path.mCommandId, 11u); + // Invalid cluster + { + TestReadRequest testRequest(kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint1, MockClusterId(100), MockAttributeId(1))); + std::unique_ptr encoder = testRequest.StartEncoding(&model); - for (int i = 0; i < 10; i++) + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedCluster)); + } + + // Invalid endpoint { - entry = model.NextAcceptedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 2)); - ASSERT_TRUE(entry.path.HasValidIds()); - EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); - EXPECT_EQ(entry.path.mCommandId, 23u); + TestReadRequest testRequest(kAdminSubjectDescriptor, + ConcreteAttributePath(kEndpointIdThatIsMissing, MockClusterId(1), MockAttributeId(1))); + std::unique_ptr encoder = testRequest.StartEncoding(&model); + + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint)); } +} + +TEST(TestCodegenModelViaMocks, EmberAttributeReadInt32S) +{ + TestEmberScalarTypeRead(-1234); +} + +TEST(TestCodegenModelViaMocks, EmberAttributeReadEnum16) +{ + TestEmberScalarTypeRead(0x1234); +} + +TEST(TestCodegenModelViaMocks, EmberAttributeReadFloat) +{ + TestEmberScalarTypeRead(0.625); +} + +TEST(TestCodegenModelViaMocks, EmberAttributeReadDouble) +{ + TestEmberScalarTypeRead(0.625); +} + +TEST(TestCodegenModelViaMocks, EmberAttributeReadInt24U) +{ + TestEmberScalarTypeRead, ZCL_INT24U_ATTRIBUTE_TYPE>(0x1234AB); +} + +TEST(TestCodegenModelViaMocks, EmberAttributeReadInt32U) +{ + TestEmberScalarTypeRead(0x1234ABCD); +} + +TEST(TestCodegenModelViaMocks, EmberAttributeReadInt40U) +{ + TestEmberScalarTypeRead, ZCL_INT40U_ATTRIBUTE_TYPE>(0x1122334455); +} + +TEST(TestCodegenModelViaMocks, EmberAttributeReadInt48U) +{ + TestEmberScalarTypeRead, ZCL_INT48U_ATTRIBUTE_TYPE>(0xAABB11223344); +} + +TEST(TestCodegenModelViaMocks, EmberAttributeReadInt56U) +{ + TestEmberScalarTypeRead, ZCL_INT56U_ATTRIBUTE_TYPE>(0xAABB11223344); +} + +TEST(TestCodegenModelViaMocks, EmberAttributeReadBool) +{ + TestEmberScalarTypeRead(true); + TestEmberScalarTypeRead(false); +} + +TEST(TestCodegenModelViaMocks, EmberAttributeReadInt8U) +{ + TestEmberScalarTypeRead(0x12); +} + +TEST(TestCodegenModelViaMocks, EmberAttributeReadNulls) +{ + TestEmberScalarNullRead(); + TestEmberScalarNullRead(); + TestEmberScalarNullRead, ZCL_INT24U_ATTRIBUTE_TYPE>(); + TestEmberScalarNullRead(); + TestEmberScalarNullRead, ZCL_INT40U_ATTRIBUTE_TYPE>(); + TestEmberScalarNullRead, ZCL_INT48U_ATTRIBUTE_TYPE>(); + TestEmberScalarNullRead, ZCL_INT56U_ATTRIBUTE_TYPE>(); + TestEmberScalarNullRead(); + + TestEmberScalarNullRead(); + TestEmberScalarNullRead(); + TestEmberScalarNullRead, ZCL_INT24S_ATTRIBUTE_TYPE>(); + TestEmberScalarNullRead(); + TestEmberScalarNullRead, ZCL_INT40S_ATTRIBUTE_TYPE>(); + TestEmberScalarNullRead, ZCL_INT48S_ATTRIBUTE_TYPE>(); + TestEmberScalarNullRead, ZCL_INT56S_ATTRIBUTE_TYPE>(); + TestEmberScalarNullRead(); + + TestEmberScalarNullRead(); + + TestEmberScalarNullRead(); + TestEmberScalarNullRead(); +} + +TEST(TestCodegenModelViaMocks, EmberAttributeReadErrorReading) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; - for (int i = 0; i < 10; i++) { - entry = model.NextAcceptedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1)); - ASSERT_TRUE(entry.path.HasValidIds()); - EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); - EXPECT_EQ(entry.path.mCommandId, 2u); + TestReadRequest testRequest( + kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE))); + + chip::Test::SetEmberReadOutput(Protocols::InteractionModel::Status::Failure); + + // Actual read via an encoder + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(Failure)); } - for (int i = 0; i < 10; i++) { - entry = model.NextAcceptedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(3), 10)); - EXPECT_FALSE(entry.path.HasValidIds()); + TestReadRequest testRequest( + kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE))); + + chip::Test::SetEmberReadOutput(Protocols::InteractionModel::Status::Busy); + + // Actual read via an encoder + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(Busy)); } } -TEST(TestCodegenModelViaMocks, AcceptedCommandInfo) +TEST(TestCodegenModelViaMocks, EmberAttributeReadNullOctetString) { UseMockNodeConfig config(gTestNodeConfig); chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; - // invalid paths should return in "no more data" - ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kEndpointIdThatIsMissing, MockClusterId(1), 1)).has_value()); - ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kInvalidEndpointId, MockClusterId(1), 1)).has_value()); - ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(10), 1)).has_value()); - ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, kInvalidClusterId, 1)).has_value()); - ASSERT_FALSE( - model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), kInvalidCommandId)).has_value()); + TestReadRequest testRequest(kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE))); - std::optional info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1u)); - ASSERT_TRUE(info.has_value()); + // NOTE: This is a pascal string of size 0xFFFF which for null strings is a null marker + char data[] = "\xFF\xFFInvalid length string is null"; + chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); - info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 2u)); - ASSERT_TRUE(info.has_value()); + // Actual read via an encoder + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); - info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1u)); - ASSERT_TRUE(info.has_value()); + // Validate after read + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); - info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1u)); - ASSERT_TRUE(info.has_value()); + DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); - info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 23u)); - ASSERT_TRUE(info.has_value()); + // data element should be null for the given 0xFFFF length + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Null); - info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1234u)); - ASSERT_FALSE(info.has_value()); + chip::app::DataModel::Nullable actual; + ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR); + ASSERT_TRUE(actual.IsNull()); } -TEST(TestCodegenModelViaMocks, IterateOverGeneratedCommands) +TEST(TestCodegenModelViaMocks, EmberAttributeReadOctetString) { UseMockNodeConfig config(gTestNodeConfig); chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; + + TestReadRequest testRequest( + kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE))); + + // NOTE: This is a pascal string, so actual data is "test" + // the longer encoding is to make it clear we do not encode the overflow + char data[] = "\0\0testing here with overflow"; + uint16_t len = 4; + memcpy(data, &len, sizeof(uint16_t)); + chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); + + // Actual read via an encoder + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + // Validate after read + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + const DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + + // data element should be a encoded byte string as this is what the attribute type is + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_ByteString); + ByteSpan actual; + ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR); + + ByteSpan expected(reinterpret_cast(data + 2), 4); + ASSERT_TRUE(actual.data_equal(expected)); +} - // invalid paths should return in "no more data" - ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).HasValidIds()); - ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).HasValidIds()); - ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).HasValidIds()); - ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).HasValidIds()); +TEST(TestCodegenModelViaMocks, EmberAttributeReadLongOctetString) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; - // should be able to iterate over valid paths - ConcreteCommandPath path = model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); - ASSERT_TRUE(path.HasValidIds()); - EXPECT_EQ(path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(path.mClusterId, MockClusterId(2)); - EXPECT_EQ(path.mCommandId, 2u); - - path = model.NextGeneratedCommand(path); - ASSERT_TRUE(path.HasValidIds()); - EXPECT_EQ(path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(path.mClusterId, MockClusterId(2)); - EXPECT_EQ(path.mCommandId, 10u); - - path = model.NextGeneratedCommand(path); - ASSERT_FALSE(path.HasValidIds()); - - // attempt some out-of-order requests as well - path = model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(3))); - ASSERT_TRUE(path.HasValidIds()); - EXPECT_EQ(path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(path.mClusterId, MockClusterId(3)); - EXPECT_EQ(path.mCommandId, 4u); + TestReadRequest testRequest(kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE))); - for (int i = 0; i < 10; i++) + // NOTE: This is a pascal string, so actual data is "test" + // the longer encoding is to make it clear we do not encode the overflow + const char data[] = "\x04testing here with overflow"; + chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); + + // Actual read via an encoder + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + // Validate after read + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + const DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + + // data element should be a encoded byte string as this is what the attribute type is + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_ByteString); + ByteSpan actual; + ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR); + + ByteSpan expected(reinterpret_cast(data + 1), 4); + ASSERT_TRUE(actual.data_equal(expected)); +} + +TEST(TestCodegenModelViaMocks, EmberAttributeReadShortString) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; + + TestReadRequest testRequest(kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_CHAR_STRING_ATTRIBUTE_TYPE))); + + // NOTE: This is a pascal string, so actual data is "abcde" + // the longer encoding is to make it clear we do not encode the overflow + char data[] = "\0abcdef...this is the alphabet"; + uint16_t len = 5; + memcpy(data, &len, sizeof(uint8_t)); + chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); + + // Actual read via an encoder + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + // Validate after reading + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + const DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + + // data element should be a encoded byte string as this is what the attribute type is + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_UTF8String); + CharSpan actual; + ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR); + ASSERT_TRUE(actual.data_equal("abcde"_span)); +} + +TEST(TestCodegenModelViaMocks, EmberAttributeReadLongString) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; + + TestReadRequest testRequest( + kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE))); + + // NOTE: This is a pascal string, so actual data is "abcde" + // the longer encoding is to make it clear we do not encode the overflow + char data[] = "\0\0abcdef...this is the alphabet"; + uint16_t len = 5; + memcpy(data, &len, sizeof(uint16_t)); + chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); + + // Actual read via an encoder + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + // Validate after reading + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + const DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + + // data element should be a encoded byte string as this is what the attribute type is + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_UTF8String); + CharSpan actual; + ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR); + ASSERT_TRUE(actual.data_equal("abcde"_span)); +} + +TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceStructRead) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; + + const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE)); + + TestReadRequest testRequest(kAdminSubjectDescriptor, kStructPath); + RegisteredAttributeAccessInterface aai(kStructPath); + + aai->SetReturnedData(Clusters::UnitTesting::Structs::SimpleStruct::Type{ + .a = 123, + .b = true, + .e = "foo"_span, + .g = 0.5, + .h = 0.125, + }); + + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + // Validate after read + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Structure); + Clusters::UnitTesting::Structs::SimpleStruct::DecodableType actual; + ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR); + + ASSERT_EQ(actual.a, 123); + ASSERT_EQ(actual.b, true); + ASSERT_EQ(actual.g, 0.5); + ASSERT_EQ(actual.h, 0.125); + ASSERT_TRUE(actual.e.data_equal("foo"_span)); +} + +TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceListRead) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; + + const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_ARRAY_ATTRIBUTE_TYPE)); + + TestReadRequest testRequest(kAdminSubjectDescriptor, kStructPath); + RegisteredAttributeAccessInterface aai(kStructPath); + + constexpr unsigned kDataCount = 5; + aai->SetReturnedData(Clusters::UnitTesting::Structs::SimpleStruct::Type{ + .b = true, + .e = "xyz"_span, + .g = 0.25, + .h = 0.5, + }); + aai->SetReturnedDataCount(kDataCount); + + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + // Validate after read + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Array); + + std::vector items; + ASSERT_EQ(DecodeList(encodedData.dataReader, items), CHIP_NO_ERROR); + + ASSERT_EQ(items.size(), kDataCount); + + for (unsigned i = 0; i < kDataCount; i++) { - path = model.NextGeneratedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 2)); - ASSERT_TRUE(path.HasValidIds()); - EXPECT_EQ(path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(path.mClusterId, MockClusterId(2)); - EXPECT_EQ(path.mCommandId, 10u); + Clusters::UnitTesting::Structs::SimpleStruct::DecodableType & actual = items[i]; + + ASSERT_EQ(actual.a, static_cast(i & 0xFF)); + ASSERT_EQ(actual.b, true); + ASSERT_EQ(actual.g, 0.25); + ASSERT_EQ(actual.h, 0.5); + ASSERT_TRUE(actual.e.data_equal("xyz"_span)); } +} - for (int i = 0; i < 10; i++) +TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceListOverflowRead) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; + + const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_ARRAY_ATTRIBUTE_TYPE)); + + TestReadRequest testRequest(kAdminSubjectDescriptor, kStructPath); + RegisteredAttributeAccessInterface aai(kStructPath); + + constexpr unsigned kDataCount = 1024; + aai->SetReturnedData(Clusters::UnitTesting::Structs::SimpleStruct::Type{ + .b = true, + .e = "thisislongertofillupfaster"_span, + .g = 0.25, + .h = 0.5, + }); + aai->SetReturnedDataCount(kDataCount); + + std::unique_ptr encoder = testRequest.StartEncoding(&model); + // NOTE: overflow, however data should be valid. Technically both NO_MEMORY and BUFFER_TOO_SMALL + // should be ok here, however we know buffer-too-small is the error in this case hence + // the compare (easier to write the test and read the output) + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_ERROR_BUFFER_TOO_SMALL); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + // Validate after read + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Array); + + std::vector items; + ASSERT_EQ(DecodeList(encodedData.dataReader, items), CHIP_NO_ERROR); + + // On last check, 16 items can be encoded. Set some non-zero range to be enforced here that + // SOME list items are actually encoded. Actual lower bound here IS ARBITRARY and was picked + // to just ensure non-zero item count for checks. + ASSERT_GT(items.size(), 5u); + ASSERT_LT(items.size(), kDataCount); + + for (unsigned i = 0; i < items.size(); i++) { - path = model.NextGeneratedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(3), 4)); - ASSERT_TRUE(path.HasValidIds()); - EXPECT_EQ(path.mEndpointId, kMockEndpoint2); - EXPECT_EQ(path.mClusterId, MockClusterId(3)); - EXPECT_EQ(path.mCommandId, 6u); + Clusters::UnitTesting::Structs::SimpleStruct::DecodableType & actual = items[i]; + + ASSERT_EQ(actual.a, static_cast(i & 0xFF)); + ASSERT_EQ(actual.b, true); + ASSERT_EQ(actual.g, 0.25); + ASSERT_EQ(actual.h, 0.5); + ASSERT_TRUE(actual.e.data_equal("thisislongertofillupfaster"_span)); } +} - for (int i = 0; i < 10; i++) +TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceListIncrementalRead) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; + + const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_ARRAY_ATTRIBUTE_TYPE)); + + TestReadRequest testRequest(kAdminSubjectDescriptor, kStructPath); + RegisteredAttributeAccessInterface aai(kStructPath); + + constexpr unsigned kDataCount = 1024; + constexpr unsigned kEncodeIndexStart = 101; + aai->SetReturnedData(Clusters::UnitTesting::Structs::SimpleStruct::Type{ + .b = true, + .e = "thisislongertofillupfaster"_span, + .g = 0.25, + .h = 0.5, + }); + aai->SetReturnedDataCount(kDataCount); + + AttributeEncodeState encodeState; + encodeState.SetCurrentEncodingListIndex(kEncodeIndexStart); + + std::unique_ptr encoder = testRequest.StartEncoding(&model, encodeState); + // NOTE: overflow, however data should be valid. Technically both NO_MEMORY and BUFFER_TOO_SMALL + // should be ok here, however we know buffer-too-small is the error in this case hence + // the compare (easier to write the test and read the output) + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_ERROR_BUFFER_TOO_SMALL); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + // Validate after read + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + + // Incremental encodes are separate list items, repeated + // actual size IS ARBITRARY (current test sets it at 11) + ASSERT_GT(attribute_data.size(), 3u); + + for (unsigned i = 0; i < attribute_data.size(); i++) + { + DecodedAttributeData & encodedData = attribute_data[i]; + ASSERT_EQ(encodedData.attributePath.mEndpointId, testRequest.request.path.mEndpointId); + ASSERT_EQ(encodedData.attributePath.mClusterId, testRequest.request.path.mClusterId); + ASSERT_EQ(encodedData.attributePath.mAttributeId, testRequest.request.path.mAttributeId); + ASSERT_EQ(encodedData.attributePath.mListOp, ConcreteDataAttributePath::ListOperation::AppendItem); + + // individual structures encoded in each item + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Structure); + + Clusters::UnitTesting::Structs::SimpleStruct::DecodableType actual; + ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR); + + ASSERT_EQ(actual.a, static_cast((i + kEncodeIndexStart) & 0xFF)); + ASSERT_EQ(actual.b, true); + ASSERT_EQ(actual.g, 0.25); + ASSERT_EQ(actual.h, 0.5); + ASSERT_TRUE(actual.e.data_equal("thisislongertofillupfaster"_span)); + } +} + +TEST(TestCodegenModelViaMocks, ReadGlobalAttributeAttributeList) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; + + TestReadRequest testRequest(kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint2, MockClusterId(3), AttributeList::Id)); + + // Data read via the encoder + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); + + // Validate after read + std::vector attribute_data; + ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); + ASSERT_EQ(attribute_data.size(), 1u); + + DecodedAttributeData & encodedData = attribute_data[0]; + ASSERT_EQ(encodedData.attributePath, testRequest.request.path); + + ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Array); + + std::vector items; + ASSERT_EQ(DecodeList(encodedData.dataReader, items), CHIP_NO_ERROR); + + // Mock data contains ClusterRevision and FeatureMap. + // After this, Global attributes are auto-added + std::vector expected; + + // Encoding in global-attribute-access-interface has a logic of: + // - Append global attributes in front of the first specified + // large number global attribute. + // Since ClusterRevision and FeatureMap are + // global attributes, the order here is reversed for them + for (AttributeId id : GlobalAttributesNotInMetadata) + { + expected.push_back(id); + } + expected.push_back(ClusterRevision::Id); + expected.push_back(FeatureMap::Id); + expected.push_back(MockAttributeId(1)); + expected.push_back(MockAttributeId(2)); + expected.push_back(MockAttributeId(3)); + + ASSERT_EQ(items.size(), expected.size()); + + // Since we have no std::vector formatter, comparing element by element is somewhat + // more readable in case of failure. + for (unsigned i = 0; i < items.size(); i++) { - path = model.NextGeneratedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(3), 6)); - EXPECT_FALSE(path.HasValidIds()); + EXPECT_EQ(items[i], expected[i]); } } diff --git a/src/app/codegen-interaction-model/BUILD.gn b/src/app/codegen-interaction-model/BUILD.gn deleted file mode 100644 index 1b3061d4f15721..00000000000000 --- a/src/app/codegen-interaction-model/BUILD.gn +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (c) 2024 Project CHIP Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import("//build_overrides/chip.gni") -# This source set is TIGHLY coupled with code-generated data models -# as generally implemented by `src/app/util` -# -# Corresponding functions defined in attribute-storace.cpp/attribute-table.cpp must -# be available at link time for this model to use -# -# Use `model.gni` to get access to: -# CodegenDataModel.cpp -# CodegenDataModel_Read.cpp -# CodegenDataModel.h -# -# The abolve list of files exists to satisfy the "dependency linter" -# since those files should technically be "visible to gn" even though we -# are supposed to go through model.gni constants diff --git a/src/app/codegen-interaction-model/CodegenDataModel.cpp b/src/app/codegen-interaction-model/CodegenDataModel.cpp deleted file mode 100644 index 860521c860c2ef..00000000000000 --- a/src/app/codegen-interaction-model/CodegenDataModel.cpp +++ /dev/null @@ -1,404 +0,0 @@ -/* - * Copyright (c) 2024 Project CHIP Authors - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include - -#include -#include -#include -#include -#include - -namespace chip { -namespace app { -namespace { - -/// Checks if the specified ember cluster mask corresponds to a valid -/// server cluster. -bool IsServerMask(EmberAfClusterMask mask) -{ - return (mask == 0) || ((mask & CLUSTER_MASK_SERVER) != 0); -} - -/// Load the cluster information into the specified destination -void LoadClusterInfo(const ConcreteClusterPath & path, const EmberAfCluster & cluster, InteractionModel::ClusterInfo * info) -{ - chip::DataVersion * versionPtr = emberAfDataVersionStorage(path); - if (versionPtr != nullptr) - { - info->dataVersion = *versionPtr; - } - else - { - ChipLogError(AppServer, "Failed to get data version for %d/" ChipLogFormatMEI, static_cast(path.mEndpointId), - ChipLogValueMEI(cluster.clusterId)); - info->dataVersion = 0; - } - - // TODO: set entry flags: - // info->flags.Set(ClusterQualityFlags::kDiagnosticsData) -} - -/// Converts a EmberAfCluster into a ClusterEntry -InteractionModel::ClusterEntry ClusterEntryFrom(EndpointId endpointId, const EmberAfCluster & cluster) -{ - InteractionModel::ClusterEntry entry; - - entry.path = ConcreteClusterPath(endpointId, cluster.clusterId); - LoadClusterInfo(entry.path, cluster, &entry.info); - - return entry; -} - -/// Finds the first server cluster entry for the given endpoint data starting at [start_index] -/// -/// Returns an invalid entry if no more server clusters are found -InteractionModel::ClusterEntry FirstServerClusterEntry(EndpointId endpointId, const EmberAfEndpointType * endpoint, - unsigned start_index, unsigned & found_index) -{ - for (unsigned cluster_idx = start_index; cluster_idx < endpoint->clusterCount; cluster_idx++) - { - const EmberAfCluster & cluster = endpoint->cluster[cluster_idx]; - if (!IsServerMask(cluster.mask & CLUSTER_MASK_SERVER)) - { - continue; - } - - found_index = cluster_idx; - return ClusterEntryFrom(endpointId, cluster); - } - - return InteractionModel::ClusterEntry::Invalid(); -} - -/// Load the cluster information into the specified destination -void LoadAttributeInfo(const ConcreteAttributePath & path, const EmberAfAttributeMetadata & attribute, - InteractionModel::AttributeInfo * info) -{ - if (attribute.attributeType == ZCL_ARRAY_ATTRIBUTE_TYPE) - { - info->flags.Set(InteractionModel::AttributeQualityFlags::kListAttribute); - } - - // TODO: Set additional flags: - // info->flags.Set(InteractionModel::AttributeQualityFlags::kChangesOmitted) -} - -InteractionModel::AttributeEntry AttributeEntryFrom(const ConcreteClusterPath & clusterPath, - const EmberAfAttributeMetadata & attribute) -{ - InteractionModel::AttributeEntry entry; - - entry.path = ConcreteAttributePath(clusterPath.mEndpointId, clusterPath.mClusterId, attribute.attributeId); - LoadAttributeInfo(entry.path, attribute, &entry.info); - - return entry; -} - -InteractionModel::AttributeEntry AttributeEntryForGlobalListAttribute(const ConcreteClusterPath & clusterPath, - chip::AttributeId globalAttributeId) -{ - InteractionModel::AttributeEntry entry; - - entry.path = ConcreteAttributePath(clusterPath.mEndpointId, clusterPath.mClusterId, globalAttributeId); - entry.info.flags.Set(InteractionModel::AttributeQualityFlags::kListAttribute); - - return entry; -} - -} // namespace - -CHIP_ERROR CodegenDataModel::WriteAttribute(const InteractionModel::WriteAttributeRequest & request, - AttributeValueDecoder & decoder) -{ - // TODO: this needs an implementation - return CHIP_ERROR_NOT_IMPLEMENTED; -} - -CHIP_ERROR CodegenDataModel::Invoke(const InteractionModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, - InteractionModel::InvokeReply & reply) -{ - // TODO: this needs an implementation - return CHIP_ERROR_NOT_IMPLEMENTED; -} - -EndpointId CodegenDataModel::FirstEndpoint() -{ - // find the first enabled index - const uint16_t lastEndpointIndex = emberAfEndpointCount(); - for (uint16_t endpoint_idx = 0; endpoint_idx < lastEndpointIndex; endpoint_idx++) - { - if (emberAfEndpointIndexIsEnabled(endpoint_idx)) - { - return emberAfEndpointFromIndex(endpoint_idx); - } - } - - // No enabled endpoint found. Give up - return kInvalidEndpointId; -} - -std::optional CodegenDataModel::TryFindEndpointIndex(chip::EndpointId id) const -{ - const uint16_t lastEndpointIndex = emberAfEndpointCount(); - - if ((mEndpointIterationHint < lastEndpointIndex) && emberAfEndpointIndexIsEnabled(mEndpointIterationHint) && - (id == emberAfEndpointFromIndex(mEndpointIterationHint))) - { - return std::make_optional(mEndpointIterationHint); - } - - // Linear search, this may be slow - for (uint16_t endpoint_idx = 0; endpoint_idx < lastEndpointIndex; endpoint_idx++) - { - if (!emberAfEndpointIndexIsEnabled(endpoint_idx)) - { - continue; - } - - if (id == emberAfEndpointFromIndex(endpoint_idx)) - { - return std::make_optional(endpoint_idx); - } - } - - return std::nullopt; -} - -EndpointId CodegenDataModel::NextEndpoint(EndpointId before) -{ - const unsigned lastEndpointIndex = emberAfEndpointCount(); - - std::optional before_idx = TryFindEndpointIndex(before); - if (!before_idx.has_value()) - { - return kInvalidEndpointId; - } - - // find the first enabled index - for (uint16_t endpoint_idx = static_cast(*before_idx + 1); endpoint_idx < lastEndpointIndex; endpoint_idx++) - { - if (emberAfEndpointIndexIsEnabled(endpoint_idx)) - { - mEndpointIterationHint = endpoint_idx; - return emberAfEndpointFromIndex(endpoint_idx); - } - } - - // No enabled enpoint after "before" was found, give up - return kInvalidEndpointId; -} - -InteractionModel::ClusterEntry CodegenDataModel::FirstCluster(EndpointId endpointId) -{ - const EmberAfEndpointType * endpoint = emberAfFindEndpointType(endpointId); - VerifyOrReturnValue(endpoint != nullptr, InteractionModel::ClusterEntry::Invalid()); - VerifyOrReturnValue(endpoint->clusterCount > 0, InteractionModel::ClusterEntry::Invalid()); - VerifyOrReturnValue(endpoint->cluster != nullptr, InteractionModel::ClusterEntry::Invalid()); - - return FirstServerClusterEntry(endpointId, endpoint, 0, mClusterIterationHint); -} - -std::optional CodegenDataModel::TryFindServerClusterIndex(const EmberAfEndpointType * endpoint, chip::ClusterId id) const -{ - const unsigned clusterCount = endpoint->clusterCount; - - if (mClusterIterationHint < clusterCount) - { - const EmberAfCluster & cluster = endpoint->cluster[mClusterIterationHint]; - if (IsServerMask(cluster.mask) && (cluster.clusterId == id)) - { - return std::make_optional(mClusterIterationHint); - } - } - - // linear search, this may be slow - for (unsigned cluster_idx = 0; cluster_idx < clusterCount; cluster_idx++) - { - const EmberAfCluster & cluster = endpoint->cluster[cluster_idx]; - if (IsServerMask(cluster.mask) && (cluster.clusterId == id)) - { - return std::make_optional(cluster_idx); - } - } - - return std::nullopt; -} - -InteractionModel::ClusterEntry CodegenDataModel::NextCluster(const ConcreteClusterPath & before) -{ - // TODO: This search still seems slow (ember will loop). Should use index hints as long - // as ember API supports it - const EmberAfEndpointType * endpoint = emberAfFindEndpointType(before.mEndpointId); - - VerifyOrReturnValue(endpoint != nullptr, InteractionModel::ClusterEntry::Invalid()); - VerifyOrReturnValue(endpoint->clusterCount > 0, InteractionModel::ClusterEntry::Invalid()); - VerifyOrReturnValue(endpoint->cluster != nullptr, InteractionModel::ClusterEntry::Invalid()); - - std::optional cluster_idx = TryFindServerClusterIndex(endpoint, before.mClusterId); - if (!cluster_idx.has_value()) - { - return InteractionModel::ClusterEntry::Invalid(); - } - - return FirstServerClusterEntry(before.mEndpointId, endpoint, *cluster_idx + 1, mClusterIterationHint); -} - -std::optional CodegenDataModel::GetClusterInfo(const ConcreteClusterPath & path) -{ - const EmberAfCluster * cluster = FindServerCluster(path); - - VerifyOrReturnValue(cluster != nullptr, std::nullopt); - - InteractionModel::ClusterInfo info; - LoadClusterInfo(path, *cluster, &info); - - return std::make_optional(info); -} - -InteractionModel::AttributeEntry CodegenDataModel::FirstAttribute(const ConcreteClusterPath & path) -{ - const EmberAfCluster * cluster = FindServerCluster(path); - - VerifyOrReturnValue(cluster != nullptr, InteractionModel::AttributeEntry::Invalid()); - VerifyOrReturnValue(cluster->attributeCount > 0, InteractionModel::AttributeEntry::Invalid()); - VerifyOrReturnValue(cluster->attributes != nullptr, InteractionModel::AttributeEntry::Invalid()); - - return AttributeEntryFrom(path, cluster->attributes[0]); -} - -std::optional CodegenDataModel::TryFindAttributeIndex(const EmberAfCluster * cluster, chip::AttributeId id) const -{ - const unsigned attributeCount = cluster->attributeCount; - - // attempt to find this based on the embedded hint - if ((mAttributeIterationHint < attributeCount) && (cluster->attributes[mAttributeIterationHint].attributeId == id)) - { - return std::make_optional(mAttributeIterationHint); - } - - // linear search is required. This may be slow - for (unsigned attribute_idx = 0; attribute_idx < attributeCount; attribute_idx++) - { - - if (cluster->attributes[attribute_idx].attributeId == id) - { - return std::make_optional(attribute_idx); - } - } - - return std::nullopt; -} - -const EmberAfCluster * CodegenDataModel::FindServerCluster(const ConcreteClusterPath & path) -{ - // cache things - if (mPreviouslyFoundCluster.has_value() && (mPreviouslyFoundCluster->path == path)) - { - return mPreviouslyFoundCluster->cluster; - } - - const EmberAfCluster * cluster = emberAfFindServerCluster(path.mEndpointId, path.mClusterId); - if (cluster != nullptr) - { - mPreviouslyFoundCluster = std::make_optional(path, cluster); - } - return cluster; -} - -InteractionModel::AttributeEntry CodegenDataModel::NextAttribute(const ConcreteAttributePath & before) -{ - const EmberAfCluster * cluster = FindServerCluster(before); - VerifyOrReturnValue(cluster != nullptr, InteractionModel::AttributeEntry::Invalid()); - VerifyOrReturnValue(cluster->attributeCount > 0, InteractionModel::AttributeEntry::Invalid()); - VerifyOrReturnValue(cluster->attributes != nullptr, InteractionModel::AttributeEntry::Invalid()); - - // Handles global attribute iteration: if we got a global attribute, move to the next - switch (before.mAttributeId) - { - case Clusters::Globals::Attributes::GeneratedCommandList::Id: - return AttributeEntryForGlobalListAttribute(before, Clusters::Globals::Attributes::AcceptedCommandList::Id); - case Clusters::Globals::Attributes::AcceptedCommandList::Id: -#if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE - return AttributeEntryForGlobalListAttribute(before, Clusters::Globals::Attributes::EventList::Id); - case Clusters::Globals::Attributes::EventList::Id: -#endif // CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE - return AttributeEntryForGlobalListAttribute(before, Clusters::Globals::Attributes::AttributeList::Id); - case Clusters::Globals::Attributes::AttributeList::Id: - return InteractionModel::AttributeEntry::Invalid(); - default: - // pass-through: not a global attribute, try to find a "regular" attribute - break; - } - - // find the given attribute in the list and then return the next one - std::optional attribute_idx = TryFindAttributeIndex(cluster, before.mAttributeId); - if (!attribute_idx.has_value()) - { - return InteractionModel::AttributeEntry::Invalid(); - } - - unsigned next_idx = *attribute_idx + 1; - if (next_idx < cluster->attributeCount) - { - mAttributeIterationHint = next_idx; - return AttributeEntryFrom(before, cluster->attributes[next_idx]); - } - - // We reach here if next_idx is just past the last attribute metadata. Return the first global - // attribute - return AttributeEntryForGlobalListAttribute(before, Clusters::Globals::Attributes::GeneratedCommandList::Id); -} - -std::optional CodegenDataModel::GetAttributeInfo(const ConcreteAttributePath & path) -{ - const EmberAfCluster * cluster = FindServerCluster(path); - - VerifyOrReturnValue(cluster != nullptr, std::nullopt); - VerifyOrReturnValue(cluster->attributeCount > 0, std::nullopt); - VerifyOrReturnValue(cluster->attributes != nullptr, std::nullopt); - - switch (path.mAttributeId) - { - case Clusters::Globals::Attributes::GeneratedCommandList::Id: - case Clusters::Globals::Attributes::AcceptedCommandList::Id: -#if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE - case Clusters::Globals::Attributes::EventList::Id: -#endif // CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE - case Clusters::Globals::Attributes::AttributeList::Id: { - InteractionModel::AttributeInfo info; - info.flags.Set(InteractionModel::AttributeQualityFlags::kListAttribute); - return info; - } - default: - // pass-through: not a global attribute, try to find a "regular" attribute - break; - } - - std::optional attribute_idx = TryFindAttributeIndex(cluster, path.mAttributeId); - - if (!attribute_idx.has_value()) - { - return std::nullopt; - } - - InteractionModel::AttributeInfo info; - LoadAttributeInfo(path, cluster->attributes[*attribute_idx], &info); - return std::make_optional(info); -} - -} // namespace app -} // namespace chip diff --git a/src/app/codegen-interaction-model/CodegenDataModel.h b/src/app/codegen-interaction-model/CodegenDataModel.h deleted file mode 100644 index beff5d1971dea9..00000000000000 --- a/src/app/codegen-interaction-model/CodegenDataModel.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2024 Project CHIP Authors - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#pragma once - -#include - -#include - -namespace chip { -namespace app { - -class CodegenDataModel : public chip::app::InteractionModel::Model -{ -public: - /// Generic model implementations - CHIP_ERROR Shutdown() override { return CHIP_NO_ERROR; } - - CHIP_ERROR ReadAttribute(const InteractionModel::ReadAttributeRequest & request, AttributeValueEncoder & encoder) override; - CHIP_ERROR WriteAttribute(const InteractionModel::WriteAttributeRequest & request, AttributeValueDecoder & decoder) override; - CHIP_ERROR Invoke(const InteractionModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, - InteractionModel::InvokeReply & reply) override; - - /// attribute tree iteration - EndpointId FirstEndpoint() override; - EndpointId NextEndpoint(EndpointId before) override; - - InteractionModel::ClusterEntry FirstCluster(EndpointId endpoint) override; - InteractionModel::ClusterEntry NextCluster(const ConcreteClusterPath & before) override; - std::optional GetClusterInfo(const ConcreteClusterPath & path) override; - - InteractionModel::AttributeEntry FirstAttribute(const ConcreteClusterPath & cluster) override; - InteractionModel::AttributeEntry NextAttribute(const ConcreteAttributePath & before) override; - std::optional GetAttributeInfo(const ConcreteAttributePath & path) override; - -private: - // Iteration is often done in a tight loop going through all values. - // To avoid N^2 iterations, cache a hint of where something is positioned - uint16_t mEndpointIterationHint = 0; - unsigned mClusterIterationHint = 0; - unsigned mAttributeIterationHint = 0; - - // represents a remembered cluster reference that has been found as - // looking for clusters is very common (for every attribute iteration) - struct ClusterReference - { - ConcreteClusterPath path; - const EmberAfCluster * cluster; - - ClusterReference(const ConcreteClusterPath p, const EmberAfCluster * c) : path(p), cluster(c) {} - }; - std::optional mPreviouslyFoundCluster; - - /// Finds the specified ember cluster - /// - /// Effectively the same as `emberAfFindServerCluster` except with some caching capabilities - const EmberAfCluster * FindServerCluster(const ConcreteClusterPath & path); - - /// Find the index of the given attribute id - std::optional TryFindAttributeIndex(const EmberAfCluster * cluster, chip::AttributeId id) const; - - /// Find the index of the given cluster id - std::optional TryFindServerClusterIndex(const EmberAfEndpointType * endpoint, chip::ClusterId id) const; - - /// Find the index of the given endpoint id - std::optional TryFindEndpointIndex(chip::EndpointId id) const; -}; - -} // namespace app -} // namespace chip diff --git a/src/app/codegen-interaction-model/model.gni b/src/app/codegen-interaction-model/model.gni deleted file mode 100644 index 175a96167e486e..00000000000000 --- a/src/app/codegen-interaction-model/model.gni +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) 2024 Project CHIP Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import("//build_overrides/chip.gni") - -# The sources in this directory are TIGHLY coupled with code-generated data models -# as generally implemented by `src/app/util` -# -# Corresponding functions defined in attribute-storace.cpp/attribute-table.cpp must -# be available at link time for this model to use and constants heavily depend -# on `zap-generated/endpoint_config.h` (generally compile-time constants that -# are code generated) -# -# As a result, the files here are NOT a source_set or similar because they cannot -# be cleanly built as a stand-alone and instead have to be imported as part of -# a different data model or compilation unit. -codegen_interaction_model_SOURCES = [ - "${chip_root}/src/app/codegen-interaction-model/CodegenDataModel.h", - "${chip_root}/src/app/codegen-interaction-model/CodegenDataModel.cpp", - "${chip_root}/src/app/codegen-interaction-model/CodegenDataModel_Read.cpp", -] - -codegen_interaction_model_PUBLIC_DEPS = [ - "${chip_root}/src/app/common:attribute-type", - "${chip_root}/src/app/interaction-model", -] diff --git a/src/app/codegen-interaction-model/tests/BUILD.gn b/src/app/codegen-interaction-model/tests/BUILD.gn deleted file mode 100644 index 946df1849e9bc8..00000000000000 --- a/src/app/codegen-interaction-model/tests/BUILD.gn +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright (c) 2024 Project CHIP Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import("//build_overrides/chip.gni") -import("${chip_root}/build/chip/chip_test_suite.gni") -import("${chip_root}/src/app/codegen-interaction-model/model.gni") - -source_set("ember_extra_files") { - sources = [ - # This IS TERRIBLE, however we want to pretent AAI exists for global - # items and we need a shared IO storage to reduce overhead between - # data-model access and ember-compatibility (we share the same buffer) - "${chip_root}/src/app/util/ember-global-attribute-access-interface.cpp", - "${chip_root}/src/app/util/ember-io-storage.cpp", - "EmberReadWriteOverride.cpp", - "EmberReadWriteOverride.h", - "InteractionModelTemporaryOverrides.cpp", - "TestAttributeReportIBsEncoding.cpp", - "TestAttributeReportIBsEncoding.h", - ] - - public_deps = [ - "${chip_root}/src/app/util/mock:mock_ember", - "${chip_root}/src/protocols", - ] -} - -source_set("mock_model") { - sources = codegen_interaction_model_SOURCES - - public_deps = codegen_interaction_model_PUBLIC_DEPS - - # this ties in the codegen model to an actual ember implementation - public_deps += [ - ":ember_extra_files", - "${chip_root}/src/app/util/mock:mock_ember", - "${chip_root}/src/lib/core:string-builder-adapters", - ] -} - -chip_test_suite("tests") { - output_name = "libCodegenInteractionModelTests" - - test_sources = [ "TestCodegenModelViaMocks.cpp" ] - - cflags = [ "-Wconversion" ] - - public_deps = [ ":mock_model" ] -} diff --git a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp deleted file mode 100644 index 7188fd61f6e948..00000000000000 --- a/src/app/codegen-interaction-model/tests/TestCodegenModelViaMocks.cpp +++ /dev/null @@ -1,1417 +0,0 @@ -/* - * Copyright (c) 2024 Project CHIP Authors - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace chip; -using namespace chip::Test; -using namespace chip::app; -using namespace chip::app::InteractionModel; -using namespace chip::app::Clusters::Globals::Attributes; - -namespace { - -constexpr FabricIndex kTestFabrixIndex = kMinValidFabricIndex; -constexpr NodeId kTestNodeId = 0xFFFF'1234'ABCD'4321; - -constexpr EndpointId kEndpointIdThatIsMissing = kMockEndpointMin - 1; - -static_assert(kEndpointIdThatIsMissing != kInvalidEndpointId); -static_assert(kEndpointIdThatIsMissing != kMockEndpoint1); -static_assert(kEndpointIdThatIsMissing != kMockEndpoint2); -static_assert(kEndpointIdThatIsMissing != kMockEndpoint3); - -constexpr Access::SubjectDescriptor kAdminSubjectDescriptor{ - .fabricIndex = kTestFabrixIndex, - .authMode = Access::AuthMode::kCase, - .subject = kTestNodeId, -}; -constexpr Access::SubjectDescriptor kViewSubjectDescriptor{ - .fabricIndex = kTestFabrixIndex + 1, - .authMode = Access::AuthMode::kCase, - .subject = kTestNodeId, -}; - -constexpr Access::SubjectDescriptor kDenySubjectDescriptor{ - .fabricIndex = kTestFabrixIndex + 2, - .authMode = Access::AuthMode::kCase, - .subject = kTestNodeId, -}; - -bool operator==(const Access::SubjectDescriptor & a, const Access::SubjectDescriptor & b) -{ - if (a.fabricIndex != b.fabricIndex) - { - return false; - } - if (a.authMode != b.authMode) - { - return false; - } - if (a.subject != b.subject) - { - return false; - } - for (unsigned i = 0; i < a.cats.values.size(); i++) - { - if (a.cats.values[i] != b.cats.values[i]) - { - return false; - } - } - return true; -} - -class MockAccessControl : public Access::AccessControl::Delegate, public Access::AccessControl::DeviceTypeResolver -{ -public: - CHIP_ERROR Check(const Access::SubjectDescriptor & subjectDescriptor, const Access::RequestPath & requestPath, - Access::Privilege requestPrivilege) override - { - if (subjectDescriptor == kAdminSubjectDescriptor) - { - return CHIP_NO_ERROR; - } - if ((subjectDescriptor == kViewSubjectDescriptor) && (requestPrivilege == Access::Privilege::kView)) - { - return CHIP_NO_ERROR; - } - return CHIP_ERROR_ACCESS_DENIED; - } - - bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint) override { return true; } -}; - -class ScopedMockAccessControl -{ -public: - ScopedMockAccessControl() { Access::GetAccessControl().Init(&mMock, mMock); } - ~ScopedMockAccessControl() { Access::GetAccessControl().Finish(); } - -private: - MockAccessControl mMock; -}; - -#define MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(zcl_type) MockAttributeId(zcl_type + 0x1000) -#define MOCK_ATTRIBUTE_CONFIG_NULLABLE(zcl_type) \ - MockAttributeConfig(MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(zcl_type), zcl_type, ATTRIBUTE_MASK_WRITABLE | ATTRIBUTE_MASK_NULLABLE) - -#define MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(zcl_type) MockAttributeId(zcl_type + 0x2000) -#define MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(zcl_type) \ - MockAttributeConfig(MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(zcl_type), zcl_type, ATTRIBUTE_MASK_WRITABLE) - -// clang-format off -const MockNodeConfig gTestNodeConfig({ - MockEndpointConfig(kMockEndpoint1, { - MockClusterConfig(MockClusterId(1), { - ClusterRevision::Id, FeatureMap::Id, - }, { - MockEventId(1), MockEventId(2), - }), - MockClusterConfig(MockClusterId(2), { - ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), - }), - }), - MockEndpointConfig(kMockEndpoint2, { - MockClusterConfig(MockClusterId(1), { - ClusterRevision::Id, FeatureMap::Id, - }), - MockClusterConfig(MockClusterId(2), { - ClusterRevision::Id, - FeatureMap::Id, - MockAttributeId(1), - MockAttributeConfig(MockAttributeId(2), ZCL_ARRAY_ATTRIBUTE_TYPE), - }), - MockClusterConfig(MockClusterId(3), { - ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), MockAttributeId(2), MockAttributeId(3), - }), - }), - MockEndpointConfig(kMockEndpoint3, { - MockClusterConfig(MockClusterId(1), { - ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), - }), - MockClusterConfig(MockClusterId(2), { - ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), MockAttributeId(2), MockAttributeId(3), MockAttributeId(4), - }), - MockClusterConfig(MockClusterId(3), { - ClusterRevision::Id, FeatureMap::Id, - }), - MockClusterConfig(MockClusterId(4), { - ClusterRevision::Id, - FeatureMap::Id, - // several attributes of varying data types for testing. - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BOOLEAN_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BITMAP8_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BITMAP16_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BITMAP32_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BITMAP64_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT8U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT16U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT24U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT32U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT40U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT48U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT56U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT64U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT8S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT16S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT24S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT32S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT40S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT48S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT56S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT64S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENUM8_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENUM16_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_PRIORITY_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_STATUS_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_SINGLE_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DOUBLE_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_CHAR_STRING_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ARRAY_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_STRUCT_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_GROUP_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_VENDOR_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_FABRIC_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_FABRIC_IDX_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENTRY_IDX_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DATA_VER_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EVENT_NO_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_SEMTAG_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_NAMESPACE_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TAG_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_SYSTIME_US_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_SYSTIME_MS_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ELAPSED_S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TEMPERATURE_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_POWER_MW_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENERGY_MWH_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TOD_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DATE_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EPOCH_US_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EPOCH_S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_POSIX_MS_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_PERCENT_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_PERCENT100THS_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_CLUSTER_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ATTRIB_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_FIELD_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EVENT_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_COMMAND_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ACTION_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TRANS_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_NODE_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPADR_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPV4ADR_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPV6ADR_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPV6PRE_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_HWADR_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BOOLEAN_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP8_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP16_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP32_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP64_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT8U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT16U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT24U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT32U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT40U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT48U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT56U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT64U_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT8S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT16S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT24S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT32S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT40S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT48S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT56S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT64S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENUM8_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENUM16_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_PRIORITY_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_STATUS_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SINGLE_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DOUBLE_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_CHAR_STRING_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ARRAY_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_STRUCT_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_GROUP_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_VENDOR_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_FABRIC_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_FABRIC_IDX_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENTRY_IDX_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DATA_VER_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EVENT_NO_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SEMTAG_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_NAMESPACE_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TAG_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SYSTIME_US_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SYSTIME_MS_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ELAPSED_S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TEMPERATURE_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_POWER_MW_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENERGY_MWH_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TOD_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DATE_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EPOCH_US_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EPOCH_S_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_POSIX_MS_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_PERCENT_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_PERCENT100THS_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_CLUSTER_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ATTRIB_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_FIELD_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EVENT_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_COMMAND_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ACTION_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TRANS_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_NODE_ID_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPADR_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPV4ADR_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPV6ADR_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPV6PRE_ATTRIBUTE_TYPE), - MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_HWADR_ATTRIBUTE_TYPE), - }), - }), -}); -// clang-format on - -struct UseMockNodeConfig -{ - UseMockNodeConfig(const MockNodeConfig & config) { SetMockNodeConfig(config); } - ~UseMockNodeConfig() { ResetMockNodeConfig(); } -}; - -template -CHIP_ERROR DecodeList(TLV::TLVReader & reader, std::vector & out) -{ - TLV::TLVType outer; - ReturnErrorOnFailure(reader.EnterContainer(outer)); - while (true) - { - CHIP_ERROR err = reader.Next(); - - if (err == CHIP_END_OF_TLV) - { - return CHIP_NO_ERROR; - } - ReturnErrorOnFailure(err); - - T value; - ReturnErrorOnFailure(DataModel::Decode(reader, value)); - out.emplace_back(std::move(value)); - } -} - -class StructAttributeAccessInterface : public AttributeAccessInterface -{ -public: - StructAttributeAccessInterface(ConcreteAttributePath path) : - AttributeAccessInterface(MakeOptional(path.mEndpointId), path.mClusterId), mPath(path) - {} - ~StructAttributeAccessInterface() = default; - - CHIP_ERROR Read(const ConcreteReadAttributePath & path, AttributeValueEncoder & encoder) override - { - if (static_cast(path) != mPath) - { - // returning without trying to handle means "I do not handle this" - return CHIP_NO_ERROR; - } - - return encoder.Encode(mData); - } - - void SetReturnedData(const Clusters::UnitTesting::Structs::SimpleStruct::Type & data) { mData = data; } - Clusters::UnitTesting::Structs::SimpleStruct::Type simpleStruct; - -private: - ConcreteAttributePath mPath; - Clusters::UnitTesting::Structs::SimpleStruct::Type mData; -}; - -class ListAttributeAcessInterface : public AttributeAccessInterface -{ -public: - ListAttributeAcessInterface(ConcreteAttributePath path) : - AttributeAccessInterface(MakeOptional(path.mEndpointId), path.mClusterId), mPath(path) - {} - ~ListAttributeAcessInterface() = default; - - CHIP_ERROR Read(const ConcreteReadAttributePath & path, AttributeValueEncoder & encoder) override - { - if (static_cast(path) != mPath) - { - // returning without trying to handle means "I do not handle this" - return CHIP_NO_ERROR; - } - - return encoder.EncodeList([this](const auto & listEncoder) { - for (unsigned i = 0; i < mCount; i++) - { - mData.a = static_cast(i % 0xFF); - ReturnErrorOnFailure(listEncoder.Encode(mData)); - } - return CHIP_NO_ERROR; - }); - } - - void SetReturnedData(const Clusters::UnitTesting::Structs::SimpleStruct::Type & data) { mData = data; } - void SetReturnedDataCount(unsigned count) { mCount = count; } - Clusters::UnitTesting::Structs::SimpleStruct::Type simpleStruct; - -private: - ConcreteAttributePath mPath; - Clusters::UnitTesting::Structs::SimpleStruct::Type mData; - unsigned mCount = 0; -}; - -/// RAII registration of an attribute access interface -template -class RegisteredAttributeAccessInterface -{ -public: - template - RegisteredAttributeAccessInterface(Args &&... args) : mData(std::forward(args)...) - { - VerifyOrDie(registerAttributeAccessOverride(&mData)); - } - ~RegisteredAttributeAccessInterface() { unregisterAttributeAccessOverride(&mData); } - - T * operator->() { return &mData; } - T & operator*() { return mData; } - -private: - T mData; -}; - -/// Contains a `ReadAttributeRequest` as well as classes to convert this into a AttributeReportIBs -/// and later decode it -/// -/// It wraps boilerplate code to obtain a `AttributeValueEncoder` as well as later decoding -/// the underlying encoded data for verification. -struct TestReadRequest -{ - ReadAttributeRequest request; - - // encoded-used classes - EncodedReportIBs encodedIBs; - AttributeReportIBs::Builder reportBuilder; - std::unique_ptr encoder; - - TestReadRequest(const Access::SubjectDescriptor & subject, const ConcreteAttributePath & path) - { - // operationFlags is 0 i.e. not internal - // readFlags is 0 i.e. not fabric filtered - // dataVersion is missing (no data version filtering) - request.subjectDescriptor = subject; - request.path = path; - } - - std::unique_ptr StartEncoding(chip::app::InteractionModel::Model * model, - AttributeEncodeState state = AttributeEncodeState()) - { - std::optional info = model->GetClusterInfo(request.path); - if (!info.has_value()) - { - ChipLogError(Test, "Missing cluster information - no data version"); - return nullptr; - } - - DataVersion dataVersion = info->dataVersion; // NOLINT(bugprone-unchecked-optional-access) - - CHIP_ERROR err = encodedIBs.StartEncoding(reportBuilder); - if (err != CHIP_NO_ERROR) - { - ChipLogError(Test, "FAILURE starting encoding %" CHIP_ERROR_FORMAT, err.Format()); - return nullptr; - } - - // TODO: isFabricFiltered? EncodeState? - return std::make_unique(reportBuilder, request.subjectDescriptor.value(), request.path, dataVersion, - false /* aIsFabricFiltered */, state); - } - - CHIP_ERROR FinishEncoding() { return encodedIBs.FinishEncoding(reportBuilder); } -}; - -template -void TestEmberScalarTypeRead(typename NumericAttributeTraits::WorkingType value) -{ - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - ScopedMockAccessControl accessControl; - - TestReadRequest testRequest( - kAdminSubjectDescriptor, - ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZclType))); - - // Ember encoding for integers is IDENTICAL to the in-memory representation for them - typename NumericAttributeTraits::StorageType storage; - NumericAttributeTraits::WorkingToStorage(value, storage); - chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(&storage), sizeof(storage))); - - // Data read via the encoder - std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); - ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); - - // Validate after read - std::vector attribute_data; - ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); - ASSERT_EQ(attribute_data.size(), 1u); - - DecodedAttributeData & encodedData = attribute_data[0]; - ASSERT_EQ(encodedData.attributePath, testRequest.request.path); - - typename NumericAttributeTraits::WorkingType actual; - ASSERT_EQ(chip::app::DataModel::Decode::WorkingType>(encodedData.dataReader, actual), - CHIP_NO_ERROR); - ASSERT_EQ(actual, value); -} - -template -void TestEmberScalarNullRead() -{ - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - ScopedMockAccessControl accessControl; - - TestReadRequest testRequest( - kAdminSubjectDescriptor, - ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZclType))); - - // Ember encoding for integers is IDENTICAL to the in-memory representation for them - typename NumericAttributeTraits::StorageType nullValue; - NumericAttributeTraits::SetNull(nullValue); - chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(&nullValue), sizeof(nullValue))); - - // Data read via the encoder - std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); - ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); - - // Validate after read - std::vector attribute_data; - ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); - ASSERT_EQ(attribute_data.size(), 1u); - - DecodedAttributeData & encodedData = attribute_data[0]; - ASSERT_EQ(encodedData.attributePath, testRequest.request.path); - chip::app::DataModel::Nullable::WorkingType> actual; - ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR); - ASSERT_TRUE(actual.IsNull()); -} - -} // namespace - -TEST(TestCodegenModelViaMocks, IterateOverEndpoints) -{ - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - - // This iteration relies on the hard-coding that occurs when mock_ember is used - EXPECT_EQ(model.FirstEndpoint(), kMockEndpoint1); - EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); - ASSERT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); - ASSERT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); - - /// Some out of order requests should work as well - ASSERT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); - ASSERT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); - ASSERT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); - ASSERT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); - ASSERT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); - ASSERT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); - ASSERT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); - ASSERT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); - ASSERT_EQ(model.FirstEndpoint(), kMockEndpoint1); - ASSERT_EQ(model.FirstEndpoint(), kMockEndpoint1); -} - -TEST(TestCodegenModelViaMocks, IterateOverClusters) -{ - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - - chip::Test::ResetVersion(); - - EXPECT_FALSE(model.FirstCluster(kEndpointIdThatIsMissing).path.HasValidIds()); - EXPECT_FALSE(model.FirstCluster(kInvalidEndpointId).path.HasValidIds()); - - // mock endpoint 1 has 2 mock clusters: 1 and 2 - ClusterEntry entry = model.FirstCluster(kMockEndpoint1); - ASSERT_TRUE(entry.path.HasValidIds()); - EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint1); - EXPECT_EQ(entry.path.mClusterId, MockClusterId(1)); - EXPECT_EQ(entry.info.dataVersion, 0u); - EXPECT_EQ(entry.info.flags.Raw(), 0u); - - chip::Test::BumpVersion(); - - entry = model.NextCluster(entry.path); - ASSERT_TRUE(entry.path.HasValidIds()); - EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint1); - EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); - EXPECT_EQ(entry.info.dataVersion, 1u); - EXPECT_EQ(entry.info.flags.Raw(), 0u); - - entry = model.NextCluster(entry.path); - EXPECT_FALSE(entry.path.HasValidIds()); - - // mock endpoint 3 has 4 mock clusters: 1 through 4 - entry = model.FirstCluster(kMockEndpoint3); - for (uint16_t clusterId = 1; clusterId <= 4; clusterId++) - { - ASSERT_TRUE(entry.path.HasValidIds()); - EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint3); - EXPECT_EQ(entry.path.mClusterId, MockClusterId(clusterId)); - entry = model.NextCluster(entry.path); - } - EXPECT_FALSE(entry.path.HasValidIds()); - - // repeat calls should work - for (int i = 0; i < 10; i++) - { - entry = model.FirstCluster(kMockEndpoint1); - ASSERT_TRUE(entry.path.HasValidIds()); - EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint1); - EXPECT_EQ(entry.path.mClusterId, MockClusterId(1)); - } - - for (int i = 0; i < 10; i++) - { - ClusterEntry nextEntry = model.NextCluster(entry.path); - ASSERT_TRUE(nextEntry.path.HasValidIds()); - EXPECT_EQ(nextEntry.path.mEndpointId, kMockEndpoint1); - EXPECT_EQ(nextEntry.path.mClusterId, MockClusterId(2)); - } -} - -TEST(TestCodegenModelViaMocks, GetClusterInfo) -{ - - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - - chip::Test::ResetVersion(); - - ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kInvalidEndpointId, kInvalidClusterId)).has_value()); - ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).has_value()); - ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).has_value()); - ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).has_value()); - - // now get the value - std::optional info = model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); - ASSERT_TRUE(info.has_value()); - EXPECT_EQ(info->dataVersion, 0u); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_EQ(info->flags.Raw(), 0u); // NOLINT(bugprone-unchecked-optional-access) - - chip::Test::BumpVersion(); - info = model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); - ASSERT_TRUE(info.has_value()); - EXPECT_EQ(info->dataVersion, 1u); // NOLINT(bugprone-unchecked-optional-access) - EXPECT_EQ(info->flags.Raw(), 0u); // NOLINT(bugprone-unchecked-optional-access) -} - -TEST(TestCodegenModelViaMocks, IterateOverAttributes) -{ - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - - // invalid paths should return in "no more data" - ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).path.HasValidIds()); - ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).path.HasValidIds()); - ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).path.HasValidIds()); - ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds()); - - // should be able to iterate over valid paths - AttributeEntry entry = model.FirstAttribute(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); - ASSERT_TRUE(entry.path.HasValidIds()); - ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); - ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); - ASSERT_EQ(entry.path.mAttributeId, ClusterRevision::Id); - ASSERT_FALSE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); - - entry = model.NextAttribute(entry.path); - ASSERT_TRUE(entry.path.HasValidIds()); - ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); - ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); - ASSERT_EQ(entry.path.mAttributeId, FeatureMap::Id); - ASSERT_FALSE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); - - entry = model.NextAttribute(entry.path); - ASSERT_TRUE(entry.path.HasValidIds()); - ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); - ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); - ASSERT_EQ(entry.path.mAttributeId, MockAttributeId(1)); - ASSERT_FALSE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); - - entry = model.NextAttribute(entry.path); - ASSERT_TRUE(entry.path.HasValidIds()); - ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); - ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); - ASSERT_EQ(entry.path.mAttributeId, MockAttributeId(2)); - ASSERT_TRUE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); - - // Iteration MUST include global attributes. Ember does not provide those, so we - // assert here that we present them in order - for (auto globalAttributeId : GlobalAttributesNotInMetadata) - { - - entry = model.NextAttribute(entry.path); - ASSERT_TRUE(entry.path.HasValidIds()); - ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); - ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); - ASSERT_EQ(entry.path.mAttributeId, globalAttributeId); - - // all global attributes not in ember metadata are LIST typed - ASSERT_TRUE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); - } - - entry = model.NextAttribute(entry.path); - ASSERT_FALSE(entry.path.HasValidIds()); - - // repeated calls should work - for (int i = 0; i < 10; i++) - { - entry = model.FirstAttribute(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); - ASSERT_TRUE(entry.path.HasValidIds()); - ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); - ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); - ASSERT_EQ(entry.path.mAttributeId, ClusterRevision::Id); - ASSERT_FALSE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); - } - - for (int i = 0; i < 10; i++) - { - entry = model.NextAttribute(ConcreteAttributePath(kMockEndpoint2, MockClusterId(2), MockAttributeId(1))); - ASSERT_TRUE(entry.path.HasValidIds()); - ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); - ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); - ASSERT_EQ(entry.path.mAttributeId, MockAttributeId(2)); - ASSERT_TRUE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); - } -} - -TEST(TestCodegenModelViaMocks, GetAttributeInfo) -{ - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - - // various non-existent or invalid paths should return no info data - ASSERT_FALSE( - model.GetAttributeInfo(ConcreteAttributePath(kInvalidEndpointId, kInvalidClusterId, kInvalidAttributeId)).has_value()); - ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kInvalidEndpointId, kInvalidClusterId, FeatureMap::Id)).has_value()); - ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kInvalidEndpointId, MockClusterId(1), FeatureMap::Id)).has_value()); - ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, kInvalidClusterId, FeatureMap::Id)).has_value()); - ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), FeatureMap::Id)).has_value()); - ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), kInvalidAttributeId)).has_value()); - ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))).has_value()); - - // valid info - std::optional info = - model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), FeatureMap::Id)); - ASSERT_TRUE(info.has_value()); - EXPECT_FALSE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) - - info = model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint2, MockClusterId(2), MockAttributeId(2))); - ASSERT_TRUE(info.has_value()); - EXPECT_TRUE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) -} - -TEST(TestCodegenModelViaMocks, GlobalAttributeInfo) -{ - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - - std::optional info = model.GetAttributeInfo( - ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::GeneratedCommandList::Id)); - - ASSERT_TRUE(info.has_value()); - EXPECT_TRUE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) - - info = model.GetAttributeInfo( - ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::AttributeList::Id)); - ASSERT_TRUE(info.has_value()); - EXPECT_TRUE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) -} - -TEST(TestCodegenModelViaMocks, EmberAttributeReadAclDeny) -{ - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - ScopedMockAccessControl accessControl; - - TestReadRequest testRequest(kDenySubjectDescriptor, - ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))); - std::unique_ptr encoder = testRequest.StartEncoding(&model); - - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_ERROR_ACCESS_DENIED); -} - -TEST(TestCodegenModelViaMocks, EmberAttributeInvalidRead) -{ - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - ScopedMockAccessControl accessControl; - - // Invalid attribute - { - TestReadRequest testRequest(kAdminSubjectDescriptor, - ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))); - std::unique_ptr encoder = testRequest.StartEncoding(&model); - - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); - } - - // Invalid cluster - { - TestReadRequest testRequest(kAdminSubjectDescriptor, - ConcreteAttributePath(kMockEndpoint1, MockClusterId(100), MockAttributeId(1))); - std::unique_ptr encoder = testRequest.StartEncoding(&model); - - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedCluster)); - } - - // Invalid endpoint - { - TestReadRequest testRequest(kAdminSubjectDescriptor, - ConcreteAttributePath(kEndpointIdThatIsMissing, MockClusterId(1), MockAttributeId(1))); - std::unique_ptr encoder = testRequest.StartEncoding(&model); - - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint)); - } -} - -TEST(TestCodegenModelViaMocks, EmberAttributeReadInt32S) -{ - TestEmberScalarTypeRead(-1234); -} - -TEST(TestCodegenModelViaMocks, EmberAttributeReadEnum16) -{ - TestEmberScalarTypeRead(0x1234); -} - -TEST(TestCodegenModelViaMocks, EmberAttributeReadFloat) -{ - TestEmberScalarTypeRead(0.625); -} - -TEST(TestCodegenModelViaMocks, EmberAttributeReadDouble) -{ - TestEmberScalarTypeRead(0.625); -} - -TEST(TestCodegenModelViaMocks, EmberAttributeReadInt24U) -{ - TestEmberScalarTypeRead, ZCL_INT24U_ATTRIBUTE_TYPE>(0x1234AB); -} - -TEST(TestCodegenModelViaMocks, EmberAttributeReadInt32U) -{ - TestEmberScalarTypeRead(0x1234ABCD); -} - -TEST(TestCodegenModelViaMocks, EmberAttributeReadInt40U) -{ - TestEmberScalarTypeRead, ZCL_INT40U_ATTRIBUTE_TYPE>(0x1122334455); -} - -TEST(TestCodegenModelViaMocks, EmberAttributeReadInt48U) -{ - TestEmberScalarTypeRead, ZCL_INT48U_ATTRIBUTE_TYPE>(0xAABB11223344); -} - -TEST(TestCodegenModelViaMocks, EmberAttributeReadInt56U) -{ - TestEmberScalarTypeRead, ZCL_INT56U_ATTRIBUTE_TYPE>(0xAABB11223344); -} - -TEST(TestCodegenModelViaMocks, EmberAttributeReadBool) -{ - TestEmberScalarTypeRead(true); - TestEmberScalarTypeRead(false); -} - -TEST(TestCodegenModelViaMocks, EmberAttributeReadInt8U) -{ - TestEmberScalarTypeRead(0x12); -} - -TEST(TestCodegenModelViaMocks, EmberAttributeReadNulls) -{ - TestEmberScalarNullRead(); - TestEmberScalarNullRead(); - TestEmberScalarNullRead, ZCL_INT24U_ATTRIBUTE_TYPE>(); - TestEmberScalarNullRead(); - TestEmberScalarNullRead, ZCL_INT40U_ATTRIBUTE_TYPE>(); - TestEmberScalarNullRead, ZCL_INT48U_ATTRIBUTE_TYPE>(); - TestEmberScalarNullRead, ZCL_INT56U_ATTRIBUTE_TYPE>(); - TestEmberScalarNullRead(); - - TestEmberScalarNullRead(); - TestEmberScalarNullRead(); - TestEmberScalarNullRead, ZCL_INT24S_ATTRIBUTE_TYPE>(); - TestEmberScalarNullRead(); - TestEmberScalarNullRead, ZCL_INT40S_ATTRIBUTE_TYPE>(); - TestEmberScalarNullRead, ZCL_INT48S_ATTRIBUTE_TYPE>(); - TestEmberScalarNullRead, ZCL_INT56S_ATTRIBUTE_TYPE>(); - TestEmberScalarNullRead(); - - TestEmberScalarNullRead(); - - TestEmberScalarNullRead(); - TestEmberScalarNullRead(); -} - -TEST(TestCodegenModelViaMocks, EmberAttributeReadErrorReading) -{ - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - ScopedMockAccessControl accessControl; - - { - TestReadRequest testRequest( - kAdminSubjectDescriptor, - ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), - MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE))); - - chip::Test::SetEmberReadOutput(Protocols::InteractionModel::Status::Failure); - - // Actual read via an encoder - std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(Failure)); - } - - { - TestReadRequest testRequest( - kAdminSubjectDescriptor, - ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), - MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE))); - - chip::Test::SetEmberReadOutput(Protocols::InteractionModel::Status::Busy); - - // Actual read via an encoder - std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(Busy)); - } -} - -TEST(TestCodegenModelViaMocks, EmberAttributeReadNullOctetString) -{ - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - ScopedMockAccessControl accessControl; - - TestReadRequest testRequest(kAdminSubjectDescriptor, - ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), - MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE))); - - // NOTE: This is a pascal string of size 0xFFFF which for null strings is a null marker - char data[] = "\xFF\xFFInvalid length string is null"; - chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); - - // Actual read via an encoder - std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); - ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); - - // Validate after read - std::vector attribute_data; - ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); - ASSERT_EQ(attribute_data.size(), 1u); - - DecodedAttributeData & encodedData = attribute_data[0]; - ASSERT_EQ(encodedData.attributePath, testRequest.request.path); - - // data element should be null for the given 0xFFFF length - ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Null); - - chip::app::DataModel::Nullable actual; - ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR); - ASSERT_TRUE(actual.IsNull()); -} - -TEST(TestCodegenModelViaMocks, EmberAttributeReadOctetString) -{ - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - ScopedMockAccessControl accessControl; - - TestReadRequest testRequest( - kAdminSubjectDescriptor, - ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), - MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE))); - - // NOTE: This is a pascal string, so actual data is "test" - // the longer encoding is to make it clear we do not encode the overflow - char data[] = "\0\0testing here with overflow"; - uint16_t len = 4; - memcpy(data, &len, sizeof(uint16_t)); - chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); - - // Actual read via an encoder - std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); - ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); - - // Validate after read - std::vector attribute_data; - ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); - ASSERT_EQ(attribute_data.size(), 1u); - - const DecodedAttributeData & encodedData = attribute_data[0]; - ASSERT_EQ(encodedData.attributePath, testRequest.request.path); - - // data element should be a encoded byte string as this is what the attribute type is - ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_ByteString); - ByteSpan actual; - ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR); - - ByteSpan expected(reinterpret_cast(data + 2), 4); - ASSERT_TRUE(actual.data_equal(expected)); -} - -TEST(TestCodegenModelViaMocks, EmberAttributeReadLongOctetString) -{ - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - ScopedMockAccessControl accessControl; - - TestReadRequest testRequest(kAdminSubjectDescriptor, - ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), - MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE))); - - // NOTE: This is a pascal string, so actual data is "test" - // the longer encoding is to make it clear we do not encode the overflow - const char data[] = "\x04testing here with overflow"; - chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); - - // Actual read via an encoder - std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); - ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); - - // Validate after read - std::vector attribute_data; - ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); - ASSERT_EQ(attribute_data.size(), 1u); - - const DecodedAttributeData & encodedData = attribute_data[0]; - ASSERT_EQ(encodedData.attributePath, testRequest.request.path); - - // data element should be a encoded byte string as this is what the attribute type is - ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_ByteString); - ByteSpan actual; - ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR); - - ByteSpan expected(reinterpret_cast(data + 1), 4); - ASSERT_TRUE(actual.data_equal(expected)); -} - -TEST(TestCodegenModelViaMocks, EmberAttributeReadShortString) -{ - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - ScopedMockAccessControl accessControl; - - TestReadRequest testRequest(kAdminSubjectDescriptor, - ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), - MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_CHAR_STRING_ATTRIBUTE_TYPE))); - - // NOTE: This is a pascal string, so actual data is "abcde" - // the longer encoding is to make it clear we do not encode the overflow - char data[] = "\0abcdef...this is the alphabet"; - uint16_t len = 5; - memcpy(data, &len, sizeof(uint8_t)); - chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); - - // Actual read via an encoder - std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); - ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); - - // Validate after reading - std::vector attribute_data; - ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); - ASSERT_EQ(attribute_data.size(), 1u); - - const DecodedAttributeData & encodedData = attribute_data[0]; - ASSERT_EQ(encodedData.attributePath, testRequest.request.path); - - // data element should be a encoded byte string as this is what the attribute type is - ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_UTF8String); - CharSpan actual; - ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR); - ASSERT_TRUE(actual.data_equal("abcde"_span)); -} - -TEST(TestCodegenModelViaMocks, EmberAttributeReadLongString) -{ - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - ScopedMockAccessControl accessControl; - - TestReadRequest testRequest( - kAdminSubjectDescriptor, - ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), - MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE))); - - // NOTE: This is a pascal string, so actual data is "abcde" - // the longer encoding is to make it clear we do not encode the overflow - char data[] = "\0\0abcdef...this is the alphabet"; - uint16_t len = 5; - memcpy(data, &len, sizeof(uint16_t)); - chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); - - // Actual read via an encoder - std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); - ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); - - // Validate after reading - std::vector attribute_data; - ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); - ASSERT_EQ(attribute_data.size(), 1u); - - const DecodedAttributeData & encodedData = attribute_data[0]; - ASSERT_EQ(encodedData.attributePath, testRequest.request.path); - - // data element should be a encoded byte string as this is what the attribute type is - ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_UTF8String); - CharSpan actual; - ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR); - ASSERT_TRUE(actual.data_equal("abcde"_span)); -} - -TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceStructRead) -{ - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - ScopedMockAccessControl accessControl; - - const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4), - MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE)); - - TestReadRequest testRequest(kAdminSubjectDescriptor, kStructPath); - RegisteredAttributeAccessInterface aai(kStructPath); - - aai->SetReturnedData(Clusters::UnitTesting::Structs::SimpleStruct::Type{ - .a = 123, - .b = true, - .e = "foo"_span, - .g = 0.5, - .h = 0.125, - }); - - std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); - ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); - - // Validate after read - std::vector attribute_data; - ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); - ASSERT_EQ(attribute_data.size(), 1u); - - DecodedAttributeData & encodedData = attribute_data[0]; - ASSERT_EQ(encodedData.attributePath, testRequest.request.path); - - ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Structure); - Clusters::UnitTesting::Structs::SimpleStruct::DecodableType actual; - ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR); - - ASSERT_EQ(actual.a, 123); - ASSERT_EQ(actual.b, true); - ASSERT_EQ(actual.g, 0.5); - ASSERT_EQ(actual.h, 0.125); - ASSERT_TRUE(actual.e.data_equal("foo"_span)); -} - -TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceListRead) -{ - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - ScopedMockAccessControl accessControl; - - const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4), - MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_ARRAY_ATTRIBUTE_TYPE)); - - TestReadRequest testRequest(kAdminSubjectDescriptor, kStructPath); - RegisteredAttributeAccessInterface aai(kStructPath); - - constexpr unsigned kDataCount = 5; - aai->SetReturnedData(Clusters::UnitTesting::Structs::SimpleStruct::Type{ - .b = true, - .e = "xyz"_span, - .g = 0.25, - .h = 0.5, - }); - aai->SetReturnedDataCount(kDataCount); - - std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); - ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); - - // Validate after read - std::vector attribute_data; - ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); - ASSERT_EQ(attribute_data.size(), 1u); - - DecodedAttributeData & encodedData = attribute_data[0]; - ASSERT_EQ(encodedData.attributePath, testRequest.request.path); - - ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Array); - - std::vector items; - ASSERT_EQ(DecodeList(encodedData.dataReader, items), CHIP_NO_ERROR); - - ASSERT_EQ(items.size(), kDataCount); - - for (unsigned i = 0; i < kDataCount; i++) - { - Clusters::UnitTesting::Structs::SimpleStruct::DecodableType & actual = items[i]; - - ASSERT_EQ(actual.a, static_cast(i & 0xFF)); - ASSERT_EQ(actual.b, true); - ASSERT_EQ(actual.g, 0.25); - ASSERT_EQ(actual.h, 0.5); - ASSERT_TRUE(actual.e.data_equal("xyz"_span)); - } -} - -TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceListOverflowRead) -{ - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - ScopedMockAccessControl accessControl; - - const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4), - MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_ARRAY_ATTRIBUTE_TYPE)); - - TestReadRequest testRequest(kAdminSubjectDescriptor, kStructPath); - RegisteredAttributeAccessInterface aai(kStructPath); - - constexpr unsigned kDataCount = 1024; - aai->SetReturnedData(Clusters::UnitTesting::Structs::SimpleStruct::Type{ - .b = true, - .e = "thisislongertofillupfaster"_span, - .g = 0.25, - .h = 0.5, - }); - aai->SetReturnedDataCount(kDataCount); - - std::unique_ptr encoder = testRequest.StartEncoding(&model); - // NOTE: overflow, however data should be valid. Technically both NO_MEMORY and BUFFER_TOO_SMALL - // should be ok here, however we know buffer-too-small is the error in this case hence - // the compare (easier to write the test and read the output) - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_ERROR_BUFFER_TOO_SMALL); - ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); - - // Validate after read - std::vector attribute_data; - ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); - ASSERT_EQ(attribute_data.size(), 1u); - - DecodedAttributeData & encodedData = attribute_data[0]; - ASSERT_EQ(encodedData.attributePath, testRequest.request.path); - - ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Array); - - std::vector items; - ASSERT_EQ(DecodeList(encodedData.dataReader, items), CHIP_NO_ERROR); - - // On last check, 16 items can be encoded. Set some non-zero range to be enforced here that - // SOME list items are actually encoded. Actual lower bound here IS ARBITRARY and was picked - // to just ensure non-zero item count for checks. - ASSERT_GT(items.size(), 5u); - ASSERT_LT(items.size(), kDataCount); - - for (unsigned i = 0; i < items.size(); i++) - { - Clusters::UnitTesting::Structs::SimpleStruct::DecodableType & actual = items[i]; - - ASSERT_EQ(actual.a, static_cast(i & 0xFF)); - ASSERT_EQ(actual.b, true); - ASSERT_EQ(actual.g, 0.25); - ASSERT_EQ(actual.h, 0.5); - ASSERT_TRUE(actual.e.data_equal("thisislongertofillupfaster"_span)); - } -} - -TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceListIncrementalRead) -{ - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - ScopedMockAccessControl accessControl; - - const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4), - MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_ARRAY_ATTRIBUTE_TYPE)); - - TestReadRequest testRequest(kAdminSubjectDescriptor, kStructPath); - RegisteredAttributeAccessInterface aai(kStructPath); - - constexpr unsigned kDataCount = 1024; - constexpr unsigned kEncodeIndexStart = 101; - aai->SetReturnedData(Clusters::UnitTesting::Structs::SimpleStruct::Type{ - .b = true, - .e = "thisislongertofillupfaster"_span, - .g = 0.25, - .h = 0.5, - }); - aai->SetReturnedDataCount(kDataCount); - - AttributeEncodeState encodeState; - encodeState.SetCurrentEncodingListIndex(kEncodeIndexStart); - - std::unique_ptr encoder = testRequest.StartEncoding(&model, encodeState); - // NOTE: overflow, however data should be valid. Technically both NO_MEMORY and BUFFER_TOO_SMALL - // should be ok here, however we know buffer-too-small is the error in this case hence - // the compare (easier to write the test and read the output) - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_ERROR_BUFFER_TOO_SMALL); - ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); - - // Validate after read - std::vector attribute_data; - ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); - - // Incremental encodes are separate list items, repeated - // actual size IS ARBITRARY (current test sets it at 11) - ASSERT_GT(attribute_data.size(), 3u); - - for (unsigned i = 0; i < attribute_data.size(); i++) - { - DecodedAttributeData & encodedData = attribute_data[i]; - ASSERT_EQ(encodedData.attributePath.mEndpointId, testRequest.request.path.mEndpointId); - ASSERT_EQ(encodedData.attributePath.mClusterId, testRequest.request.path.mClusterId); - ASSERT_EQ(encodedData.attributePath.mAttributeId, testRequest.request.path.mAttributeId); - ASSERT_EQ(encodedData.attributePath.mListOp, ConcreteDataAttributePath::ListOperation::AppendItem); - - // individual structures encoded in each item - ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Structure); - - Clusters::UnitTesting::Structs::SimpleStruct::DecodableType actual; - ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR); - - ASSERT_EQ(actual.a, static_cast((i + kEncodeIndexStart) & 0xFF)); - ASSERT_EQ(actual.b, true); - ASSERT_EQ(actual.g, 0.25); - ASSERT_EQ(actual.h, 0.5); - ASSERT_TRUE(actual.e.data_equal("thisislongertofillupfaster"_span)); - } -} - -TEST(TestCodegenModelViaMocks, ReadGlobalAttributeAttributeList) -{ - UseMockNodeConfig config(gTestNodeConfig); - chip::app::CodegenDataModel model; - ScopedMockAccessControl accessControl; - - TestReadRequest testRequest(kAdminSubjectDescriptor, - ConcreteAttributePath(kMockEndpoint2, MockClusterId(3), AttributeList::Id)); - - // Data read via the encoder - std::unique_ptr encoder = testRequest.StartEncoding(&model); - ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); - ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); - - // Validate after read - std::vector attribute_data; - ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR); - ASSERT_EQ(attribute_data.size(), 1u); - - DecodedAttributeData & encodedData = attribute_data[0]; - ASSERT_EQ(encodedData.attributePath, testRequest.request.path); - - ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Array); - - std::vector items; - ASSERT_EQ(DecodeList(encodedData.dataReader, items), CHIP_NO_ERROR); - - // Mock data contains ClusterRevision and FeatureMap. - // After this, Global attributes are auto-added - std::vector expected; - - // Encoding in global-attribute-access-interface has a logic of: - // - Append global attributes in front of the first specified - // large number global attribute. - // Since ClusterRevision and FeatureMap are - // global attributes, the order here is reversed for them - for (AttributeId id : GlobalAttributesNotInMetadata) - { - expected.push_back(id); - } - expected.push_back(ClusterRevision::Id); - expected.push_back(FeatureMap::Id); - expected.push_back(MockAttributeId(1)); - expected.push_back(MockAttributeId(2)); - expected.push_back(MockAttributeId(3)); - - ASSERT_EQ(items.size(), expected.size()); - - // Since we have no std::vector formatter, comparing element by element is somewhat - // more readable in case of failure. - for (unsigned i = 0; i < items.size(); i++) - { - EXPECT_EQ(items[i], expected[i]); - } -} From e9347f9295bf9d07b3c78e989d33f1ac1a7e9cba Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 12 Jun 2024 10:39:52 -0400 Subject: [PATCH 092/123] A bit more renaming --- src/app/codegen-data-model/model.gni | 4 ++-- src/app/codegen-data-model/tests/BUILD.gn | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/codegen-data-model/model.gni b/src/app/codegen-data-model/model.gni index 753c392eeda852..3be7b2d2610513 100644 --- a/src/app/codegen-data-model/model.gni +++ b/src/app/codegen-data-model/model.gni @@ -24,13 +24,13 @@ import("//build_overrides/chip.gni") # As a result, the files here are NOT a source_set or similar because they cannot # be cleanly built as a stand-alone and instead have to be imported as part of # a different data model or compilation unit. -codegen_interaction_model_SOURCES = [ +codegen_data_model_SOURCES = [ "${chip_root}/src/app/codegen-data-model/CodegenDataModel.h", "${chip_root}/src/app/codegen-data-model/CodegenDataModel.cpp", "${chip_root}/src/app/codegen-data-model/CodegenDataModel_Read.cpp", ] -codegen_interaction_model_PUBLIC_DEPS = [ +codegen_data_model_PUBLIC_DEPS = [ "${chip_root}/src/app/common:attribute-type", "${chip_root}/src/app/data-model-interface", ] diff --git a/src/app/codegen-data-model/tests/BUILD.gn b/src/app/codegen-data-model/tests/BUILD.gn index 946df1849e9bc8..3f3503b1d8ece5 100644 --- a/src/app/codegen-data-model/tests/BUILD.gn +++ b/src/app/codegen-data-model/tests/BUILD.gn @@ -13,7 +13,7 @@ # limitations under the License. import("//build_overrides/chip.gni") import("${chip_root}/build/chip/chip_test_suite.gni") -import("${chip_root}/src/app/codegen-interaction-model/model.gni") +import("${chip_root}/src/app/codegen-data-model/model.gni") source_set("ember_extra_files") { sources = [ @@ -36,9 +36,9 @@ source_set("ember_extra_files") { } source_set("mock_model") { - sources = codegen_interaction_model_SOURCES + sources = codegen_data_model_SOURCES - public_deps = codegen_interaction_model_PUBLIC_DEPS + public_deps = codegen_data_model_PUBLIC_DEPS # this ties in the codegen model to an actual ember implementation public_deps += [ @@ -49,7 +49,7 @@ source_set("mock_model") { } chip_test_suite("tests") { - output_name = "libCodegenInteractionModelTests" + output_name = "libCodegenDataModelTests" test_sources = [ "TestCodegenModelViaMocks.cpp" ] From a2b7ae5d85ecf3625e6bfeeda064afa25beba54c Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 12 Jun 2024 10:40:31 -0400 Subject: [PATCH 093/123] Fix includes --- src/app/codegen-data-model/CodegenDataModel_Read.cpp | 2 +- .../codegen-data-model/tests/TestCodegenModelViaMocks.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/codegen-data-model/CodegenDataModel_Read.cpp b/src/app/codegen-data-model/CodegenDataModel_Read.cpp index 6dff019139ab38..99374c26d0335c 100644 --- a/src/app/codegen-data-model/CodegenDataModel_Read.cpp +++ b/src/app/codegen-data-model/CodegenDataModel_Read.cpp @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include +#include #include #include diff --git a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp index 7188fd61f6e948..be24a3f2ffbd0b 100644 --- a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp @@ -14,10 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include +#include -#include -#include +#include +#include #include #include From 6dab6ba5bbf286f00fc30a846f5d3e5508652da4 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 12 Jun 2024 10:41:56 -0400 Subject: [PATCH 094/123] More updates to the latest code --- src/app/codegen-data-model/CodegenDataModel.cpp | 7 ------- .../codegen-data-model/tests/TestCodegenModelViaMocks.cpp | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/app/codegen-data-model/CodegenDataModel.cpp b/src/app/codegen-data-model/CodegenDataModel.cpp index e1597dbdc4f8b4..d46deeeddaa1e0 100644 --- a/src/app/codegen-data-model/CodegenDataModel.cpp +++ b/src/app/codegen-data-model/CodegenDataModel.cpp @@ -231,13 +231,6 @@ bool CodegenDataModel::EmberCommandListIterator::Exists(const CommandId * list, return (*mCurrentHint == toCheck); } -CHIP_ERROR CodegenDataModel::ReadAttribute(const InteractionModel::ReadAttributeRequest & request, - InteractionModel::ReadState & state, AttributeValueEncoder & encoder) -{ - // TODO: this needs an implementation - return CHIP_ERROR_NOT_IMPLEMENTED; -} - CHIP_ERROR CodegenDataModel::WriteAttribute(const InteractionModel::WriteAttributeRequest & request, AttributeValueDecoder & decoder) { diff --git a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp index be24a3f2ffbd0b..512b60bb6ff20a 100644 --- a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp @@ -466,7 +466,7 @@ struct TestReadRequest request.path = path; } - std::unique_ptr StartEncoding(chip::app::InteractionModel::Model * model, + std::unique_ptr StartEncoding(chip::app::InteractionModel::DataModel * model, AttributeEncodeState state = AttributeEncodeState()) { std::optional info = model->GetClusterInfo(request.path); From 97a54170bc52719f372e70054490f66f8e7a57e7 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 12 Jun 2024 10:47:45 -0400 Subject: [PATCH 095/123] Fix unit test merging --- .../tests/TestCodegenModelViaMocks.cpp | 243 +++++++++++++++--- 1 file changed, 204 insertions(+), 39 deletions(-) diff --git a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp index 512b60bb6ff20a..fd8d3f6e77ed50 100644 --- a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp @@ -159,15 +159,27 @@ const MockNodeConfig gTestNodeConfig({ MockClusterConfig(MockClusterId(1), { ClusterRevision::Id, FeatureMap::Id, }), - MockClusterConfig(MockClusterId(2), { - ClusterRevision::Id, - FeatureMap::Id, - MockAttributeId(1), - MockAttributeConfig(MockAttributeId(2), ZCL_ARRAY_ATTRIBUTE_TYPE), - }), - MockClusterConfig(MockClusterId(3), { - ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), MockAttributeId(2), MockAttributeId(3), - }), + MockClusterConfig( + MockClusterId(2), + { + ClusterRevision::Id, + FeatureMap::Id, + MockAttributeId(1), + MockAttributeConfig(MockAttributeId(2), ZCL_ARRAY_ATTRIBUTE_TYPE), + }, /* attributes */ + {}, /* events */ + {1, 2, 23}, /* acceptedCommands */ + {2, 10} /* generatedCommands */ + ), + MockClusterConfig( + MockClusterId(3), + { + ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), MockAttributeId(2), MockAttributeId(3), + }, /* attributes */ + {}, /* events */ + {11}, /* acceptedCommands */ + {4, 6} /* generatedCommands */ + ), }), MockEndpointConfig(kMockEndpoint3, { MockClusterConfig(MockClusterId(1), { @@ -571,20 +583,24 @@ TEST(TestCodegenModelViaMocks, IterateOverEndpoints) // This iteration relies on the hard-coding that occurs when mock_ember is used EXPECT_EQ(model.FirstEndpoint(), kMockEndpoint1); EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); - ASSERT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); - ASSERT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); + EXPECT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); + EXPECT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); /// Some out of order requests should work as well - ASSERT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); - ASSERT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); - ASSERT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); - ASSERT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); - ASSERT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); - ASSERT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); - ASSERT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); - ASSERT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); - ASSERT_EQ(model.FirstEndpoint(), kMockEndpoint1); - ASSERT_EQ(model.FirstEndpoint(), kMockEndpoint1); + EXPECT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); + EXPECT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); + EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); + EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); + EXPECT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); + EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); + EXPECT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); + EXPECT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); + EXPECT_EQ(model.FirstEndpoint(), kMockEndpoint1); + EXPECT_EQ(model.FirstEndpoint(), kMockEndpoint1); + + // invalid endpoiunts + EXPECT_EQ(model.NextEndpoint(kInvalidEndpointId), kInvalidEndpointId); + EXPECT_EQ(model.NextEndpoint(987u), kInvalidEndpointId); } TEST(TestCodegenModelViaMocks, IterateOverClusters) @@ -596,6 +612,9 @@ TEST(TestCodegenModelViaMocks, IterateOverClusters) EXPECT_FALSE(model.FirstCluster(kEndpointIdThatIsMissing).path.HasValidIds()); EXPECT_FALSE(model.FirstCluster(kInvalidEndpointId).path.HasValidIds()); + EXPECT_FALSE(model.NextCluster(ConcreteClusterPath(kInvalidEndpointId, 123)).path.HasValidIds()); + EXPECT_FALSE(model.NextCluster(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds()); + EXPECT_FALSE(model.NextCluster(ConcreteClusterPath(kMockEndpoint1, 981u)).path.HasValidIds()); // mock endpoint 1 has 2 mock clusters: 1 and 2 ClusterEntry entry = model.FirstCluster(kMockEndpoint1); @@ -683,6 +702,12 @@ TEST(TestCodegenModelViaMocks, IterateOverAttributes) ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).path.HasValidIds()); ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds()); + ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kEndpointIdThatIsMissing, MockClusterId(1), 1u)).path.HasValidIds()); + ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kInvalidEndpointId, MockClusterId(1), 1u)).path.HasValidIds()); + ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), 1u)).path.HasValidIds()); + ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kMockEndpoint1, kInvalidClusterId, 1u)).path.HasValidIds()); + ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), 987u)).path.HasValidIds()); + // should be able to iterate over valid paths AttributeEntry entry = model.FirstAttribute(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); ASSERT_TRUE(entry.path.HasValidIds()); @@ -712,21 +737,6 @@ TEST(TestCodegenModelViaMocks, IterateOverAttributes) ASSERT_EQ(entry.path.mAttributeId, MockAttributeId(2)); ASSERT_TRUE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); - // Iteration MUST include global attributes. Ember does not provide those, so we - // assert here that we present them in order - for (auto globalAttributeId : GlobalAttributesNotInMetadata) - { - - entry = model.NextAttribute(entry.path); - ASSERT_TRUE(entry.path.HasValidIds()); - ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); - ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); - ASSERT_EQ(entry.path.mAttributeId, globalAttributeId); - - // all global attributes not in ember metadata are LIST typed - ASSERT_TRUE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); - } - entry = model.NextAttribute(entry.path); ASSERT_FALSE(entry.path.HasValidIds()); @@ -778,6 +788,7 @@ TEST(TestCodegenModelViaMocks, GetAttributeInfo) EXPECT_TRUE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) } +// global attributes are EXPLICITLY not supported TEST(TestCodegenModelViaMocks, GlobalAttributeInfo) { UseMockNodeConfig config(gTestNodeConfig); @@ -786,13 +797,167 @@ TEST(TestCodegenModelViaMocks, GlobalAttributeInfo) std::optional info = model.GetAttributeInfo( ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::GeneratedCommandList::Id)); - ASSERT_TRUE(info.has_value()); - EXPECT_TRUE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) + ASSERT_FALSE(info.has_value()); info = model.GetAttributeInfo( ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::AttributeList::Id)); + ASSERT_FALSE(info.has_value()); +} + +TEST(TestCodegenModelViaMocks, IterateOverAcceptedCommands) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + + // invalid paths should return in "no more data" + ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).path.HasValidIds()); + ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).path.HasValidIds()); + ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).path.HasValidIds()); + ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds()); + + // should be able to iterate over valid paths + CommandEntry entry = model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); + ASSERT_TRUE(entry.path.HasValidIds()); + EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); + EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); + EXPECT_EQ(entry.path.mCommandId, 1u); + + entry = model.NextAcceptedCommand(entry.path); + ASSERT_TRUE(entry.path.HasValidIds()); + EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); + EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); + EXPECT_EQ(entry.path.mCommandId, 2u); + + entry = model.NextAcceptedCommand(entry.path); + ASSERT_TRUE(entry.path.HasValidIds()); + EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); + EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); + EXPECT_EQ(entry.path.mCommandId, 23u); + + entry = model.NextAcceptedCommand(entry.path); + ASSERT_FALSE(entry.path.HasValidIds()); + + // attempt some out-of-order requests as well + entry = model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(3))); + ASSERT_TRUE(entry.path.HasValidIds()); + EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); + EXPECT_EQ(entry.path.mClusterId, MockClusterId(3)); + EXPECT_EQ(entry.path.mCommandId, 11u); + + for (int i = 0; i < 10; i++) + { + entry = model.NextAcceptedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 2)); + ASSERT_TRUE(entry.path.HasValidIds()); + EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); + EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); + EXPECT_EQ(entry.path.mCommandId, 23u); + } + + for (int i = 0; i < 10; i++) + { + entry = model.NextAcceptedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1)); + ASSERT_TRUE(entry.path.HasValidIds()); + EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); + EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); + EXPECT_EQ(entry.path.mCommandId, 2u); + } + + for (int i = 0; i < 10; i++) + { + entry = model.NextAcceptedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(3), 10)); + EXPECT_FALSE(entry.path.HasValidIds()); + } +} + +TEST(TestCodegenModelViaMocks, AcceptedCommandInfo) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + + // invalid paths should return in "no more data" + ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kEndpointIdThatIsMissing, MockClusterId(1), 1)).has_value()); + ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kInvalidEndpointId, MockClusterId(1), 1)).has_value()); + ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(10), 1)).has_value()); + ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, kInvalidClusterId, 1)).has_value()); + ASSERT_FALSE( + model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), kInvalidCommandId)).has_value()); + + std::optional info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1u)); ASSERT_TRUE(info.has_value()); - EXPECT_TRUE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) + + info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 2u)); + ASSERT_TRUE(info.has_value()); + + info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1u)); + ASSERT_TRUE(info.has_value()); + + info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1u)); + ASSERT_TRUE(info.has_value()); + + info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 23u)); + ASSERT_TRUE(info.has_value()); + + info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1234u)); + ASSERT_FALSE(info.has_value()); +} + +TEST(TestCodegenModelViaMocks, IterateOverGeneratedCommands) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + + // invalid paths should return in "no more data" + ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).HasValidIds()); + ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).HasValidIds()); + ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).HasValidIds()); + ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).HasValidIds()); + + // should be able to iterate over valid paths + ConcreteCommandPath path = model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); + ASSERT_TRUE(path.HasValidIds()); + EXPECT_EQ(path.mEndpointId, kMockEndpoint2); + EXPECT_EQ(path.mClusterId, MockClusterId(2)); + EXPECT_EQ(path.mCommandId, 2u); + + path = model.NextGeneratedCommand(path); + ASSERT_TRUE(path.HasValidIds()); + EXPECT_EQ(path.mEndpointId, kMockEndpoint2); + EXPECT_EQ(path.mClusterId, MockClusterId(2)); + EXPECT_EQ(path.mCommandId, 10u); + + path = model.NextGeneratedCommand(path); + ASSERT_FALSE(path.HasValidIds()); + + // attempt some out-of-order requests as well + path = model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(3))); + ASSERT_TRUE(path.HasValidIds()); + EXPECT_EQ(path.mEndpointId, kMockEndpoint2); + EXPECT_EQ(path.mClusterId, MockClusterId(3)); + EXPECT_EQ(path.mCommandId, 4u); + + for (int i = 0; i < 10; i++) + { + path = model.NextGeneratedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 2)); + ASSERT_TRUE(path.HasValidIds()); + EXPECT_EQ(path.mEndpointId, kMockEndpoint2); + EXPECT_EQ(path.mClusterId, MockClusterId(2)); + EXPECT_EQ(path.mCommandId, 10u); + } + + for (int i = 0; i < 10; i++) + { + path = model.NextGeneratedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(3), 4)); + ASSERT_TRUE(path.HasValidIds()); + EXPECT_EQ(path.mEndpointId, kMockEndpoint2); + EXPECT_EQ(path.mClusterId, MockClusterId(3)); + EXPECT_EQ(path.mCommandId, 6u); + } + + for (int i = 0; i < 10; i++) + { + path = model.NextGeneratedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(3), 6)); + EXPECT_FALSE(path.HasValidIds()); + } } TEST(TestCodegenModelViaMocks, EmberAttributeReadAclDeny) From e2ec97dea17df4a64a504f1257ef6ccc1af4d0b3 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 12 Jun 2024 11:11:22 -0400 Subject: [PATCH 096/123] Resolve data model ambiguity --- src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp index fd8d3f6e77ed50..addc339225696c 100644 --- a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp @@ -368,7 +368,7 @@ CHIP_ERROR DecodeList(TLV::TLVReader & reader, std::vector & out) ReturnErrorOnFailure(err); T value; - ReturnErrorOnFailure(DataModel::Decode(reader, value)); + ReturnErrorOnFailure(chip::app::DataModel::Decode(reader, value)); out.emplace_back(std::move(value)); } } From ecf3fc0487a38a15c1dee34a9146d65c33acfb1f Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 12 Jun 2024 11:13:00 -0400 Subject: [PATCH 097/123] Restyle --- src/app/codegen-data-model/CodegenDataModel.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/codegen-data-model/CodegenDataModel.h b/src/app/codegen-data-model/CodegenDataModel.h index 3fc69cc8709df5..b65f38b9155e73 100644 --- a/src/app/codegen-data-model/CodegenDataModel.h +++ b/src/app/codegen-data-model/CodegenDataModel.h @@ -68,8 +68,7 @@ class CodegenDataModel : public chip::app::InteractionModel::DataModel /// Generic model implementations CHIP_ERROR Shutdown() override { return CHIP_NO_ERROR; } - CHIP_ERROR ReadAttribute(const InteractionModel::ReadAttributeRequest & request, - AttributeValueEncoder & encoder) override; + CHIP_ERROR ReadAttribute(const InteractionModel::ReadAttributeRequest & request, AttributeValueEncoder & encoder) override; CHIP_ERROR WriteAttribute(const InteractionModel::WriteAttributeRequest & request, AttributeValueDecoder & decoder) override; CHIP_ERROR Invoke(const InteractionModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, InteractionModel::InvokeReply & reply) override; From cbbdba5a64f52a869c0f370c45737cbaa2741b24 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 12 Jun 2024 11:49:23 -0400 Subject: [PATCH 098/123] Do not try to compile ember tests on zephyr as it is not compatible with single large executable --- src/BUILD.gn | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/BUILD.gn b/src/BUILD.gn index c6c8fa9b5e2d81..9e1acc3d414109 100644 --- a/src/BUILD.gn +++ b/src/BUILD.gn @@ -55,7 +55,6 @@ if (chip_build_tests) { chip_test_group("tests") { deps = [] tests = [ - "${chip_root}/src/app/codegen-data-model/tests", "${chip_root}/src/app/data-model-interface/tests", "${chip_root}/src/access/tests", "${chip_root}/src/crypto/tests", @@ -82,7 +81,10 @@ if (chip_build_tests) { } if (current_os != "zephyr" && current_os != "mbed") { - tests += [ "${chip_root}/src/lib/dnssd/minimal_mdns/records/tests" ] + tests += [ + "${chip_root}/src/app/codegen-data-model/tests", # contains mock-ember functions + "${chip_root}/src/lib/dnssd/minimal_mdns/records/tests", + ] } if (current_os != "zephyr" && current_os != "mbed" && From a55ccfc6f303302c403e2cf8593c542bee83ae71 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 12 Jun 2024 11:52:44 -0400 Subject: [PATCH 099/123] Update placement and comment for including codegen test into one large binary --- src/BUILD.gn | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/BUILD.gn b/src/BUILD.gn index 9e1acc3d414109..81be0f166ee6c2 100644 --- a/src/BUILD.gn +++ b/src/BUILD.gn @@ -81,15 +81,21 @@ if (chip_build_tests) { } if (current_os != "zephyr" && current_os != "mbed") { - tests += [ - "${chip_root}/src/app/codegen-data-model/tests", # contains mock-ember functions - "${chip_root}/src/lib/dnssd/minimal_mdns/records/tests", - ] + tests += [ "${chip_root}/src/lib/dnssd/minimal_mdns/records/tests" ] } if (current_os != "zephyr" && current_os != "mbed" && chip_device_platform != "efr32") { + + # Avoid these items from "one single binary" test executions. Once tests + # are split, we can re-visit this (and likely many others) + # + # In particular: + # "app/codegen-data-model/tests" contains symbols for ember mocks which + # are used by other tests + tests += [ + "${chip_root}/src/app/codegen-data-model/tests", "${chip_root}/src/setup_payload/tests", "${chip_root}/src/transport/raw/tests", ] From 2db453ee28728889de76c050bf1c6f058aa1f690 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 12 Jun 2024 11:53:02 -0400 Subject: [PATCH 100/123] Restyle --- src/BUILD.gn | 1 - 1 file changed, 1 deletion(-) diff --git a/src/BUILD.gn b/src/BUILD.gn index 81be0f166ee6c2..eb334ad110029a 100644 --- a/src/BUILD.gn +++ b/src/BUILD.gn @@ -86,7 +86,6 @@ if (chip_build_tests) { if (current_os != "zephyr" && current_os != "mbed" && chip_device_platform != "efr32") { - # Avoid these items from "one single binary" test executions. Once tests # are split, we can re-visit this (and likely many others) # From 53ca74b0401e24c05ab93a835bdad74497bcf1ba Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 12 Jun 2024 12:50:58 -0400 Subject: [PATCH 101/123] Add static cast for ember calls for span sizes ... we know we are less than 65K --- src/app/codegen-data-model/CodegenDataModel_Read.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/codegen-data-model/CodegenDataModel_Read.cpp b/src/app/codegen-data-model/CodegenDataModel_Read.cpp index 99374c26d0335c..866cf37a4a7986 100644 --- a/src/app/codegen-data-model/CodegenDataModel_Read.cpp +++ b/src/app/codegen-data-model/CodegenDataModel_Read.cpp @@ -341,7 +341,7 @@ CHIP_ERROR CodegenDataModel::ReadAttribute(const InteractionModel::ReadAttribute record.clusterId = request.path.mClusterId; record.attributeId = request.path.mAttributeId; Protocols::InteractionModel::Status status = emAfReadOrWriteAttribute( - &record, &attributeMetadata, gEmberAttributeIOBufferSpan.data(), gEmberAttributeIOBufferSpan.size(), + &record, &attributeMetadata, gEmberAttributeIOBufferSpan.data(), static_cast(gEmberAttributeIOBufferSpan.size()), /* write = */ false); if (status != Protocols::InteractionModel::Status::Success) From 660789f4bf413ec7e63128f512ef4d823663a7a2 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 13 Jun 2024 09:26:41 -0400 Subject: [PATCH 102/123] Fix clang-tidy error report --- .../codegen-data-model/tests/TestCodegenModelViaMocks.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp index addc339225696c..14fd43e12bbcdd 100644 --- a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp @@ -497,8 +497,11 @@ struct TestReadRequest return nullptr; } - // TODO: isFabricFiltered? EncodeState? - return std::make_unique(reportBuilder, request.subjectDescriptor.value(), request.path, dataVersion, + // TODO: could we test isFabricFiltered and EncodeState? + + // request.subjectDescriptor is known non-null because it is set in the constructor + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) + return std::make_unique(reportBuilder, *request.subjectDescriptor, request.path, dataVersion, false /* aIsFabricFiltered */, state); } From 4eede5ac4e37f7cfeaec974cf752170e62509057 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 13 Jun 2024 09:30:04 -0400 Subject: [PATCH 103/123] Restyle --- src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp index 14fd43e12bbcdd..bb19e8c24808c9 100644 --- a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp @@ -498,7 +498,7 @@ struct TestReadRequest } // TODO: could we test isFabricFiltered and EncodeState? - + // request.subjectDescriptor is known non-null because it is set in the constructor // NOLINTNEXTLINE(bugprone-unchecked-optional-access) return std::make_unique(reportBuilder, *request.subjectDescriptor, request.path, dataVersion, From 39cc446392044ab7bb7e7924bae0191a4bc77a2c Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 13 Jun 2024 09:43:43 -0400 Subject: [PATCH 104/123] Do not try to translate UnsupportedRead --- src/app/codegen-data-model/CodegenDataModel_Read.cpp | 8 +------- src/app/data-model-interface/DataModel.h | 3 +++ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/app/codegen-data-model/CodegenDataModel_Read.cpp b/src/app/codegen-data-model/CodegenDataModel_Read.cpp index 866cf37a4a7986..31770c7b426787 100644 --- a/src/app/codegen-data-model/CodegenDataModel_Read.cpp +++ b/src/app/codegen-data-model/CodegenDataModel_Read.cpp @@ -95,8 +95,7 @@ FindAttributeMetadata(const ConcreteAttributePath & aPath) /// Attempts to read via an attribute access interface (AAI) /// -/// If it returns a CHIP_ERROR, then this is a FINAL result (i.e. either failure or success): -/// - in particular, CHIP_ERROR_ACCESS_DENIED will be used for UnsupportedRead AII returns +/// If it returns a CHIP_ERROR, then this is a FINAL result (i.e. either failure or success). /// /// If it returns std::nullopt, then there is no AAI to handle the given path /// and processing should figure out the value otherwise (generally from other ember data) @@ -111,11 +110,6 @@ std::optional TryReadViaAccessInterface(const ConcreteAttributePath CHIP_ERROR err = aai->Read(path, encoder); - // explict translate UnsupportedRead to Access denied. This is to allow callers to determine a - // translation for this: usually wildcard subscriptions MAY just ignore these where as direct reads - // MUST translate them to UnsupportedAccess - ReturnErrorCodeIf(err == CHIP_IM_GLOBAL_STATUS(UnsupportedRead), CHIP_ERROR_ACCESS_DENIED); - if (err != CHIP_NO_ERROR) { return std::make_optional(err); diff --git a/src/app/data-model-interface/DataModel.h b/src/app/data-model-interface/DataModel.h index 13d336b176f4b1..04516e80f3c89d 100644 --- a/src/app/data-model-interface/DataModel.h +++ b/src/app/data-model-interface/DataModel.h @@ -59,6 +59,9 @@ class DataModel : public DataModelMetadataTree /// CHIP_ERROR_ACCESS_DENIED: /// - May be ignored if reads use path expansion (e.g. to skip inaccessible attributes /// during subscription requests). This code MUST be used for ACL failures and only for ACL failures. + /// CHIP_IM_GLOBAL_STATUS(UnsupportedRead): + /// - May be ignored if reads use path expansion (e.g. to skip reading write-only attributes + /// during subscription requests). /// CHIP_ERROR_NO_MEMORY or CHIP_ERROR_BUFFER_TOO_SMALL: /// - Indicates that list encoding had insufficient buffer space to encode elements. /// - encoder::GetState().AllowPartialData() determines if these errors are permanent (no partial From a2249a6d67b5bd2315f10a55f25e3877a9e7663a Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Thu, 13 Jun 2024 10:04:36 -0400 Subject: [PATCH 105/123] Typo fixes --- src/app/codegen-data-model/CodegenDataModel_Read.cpp | 2 +- src/app/codegen-data-model/tests/BUILD.gn | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/codegen-data-model/CodegenDataModel_Read.cpp b/src/app/codegen-data-model/CodegenDataModel_Read.cpp index 31770c7b426787..5fa12967cc43b6 100644 --- a/src/app/codegen-data-model/CodegenDataModel_Read.cpp +++ b/src/app/codegen-data-model/CodegenDataModel_Read.cpp @@ -117,7 +117,7 @@ std::optional TryReadViaAccessInterface(const ConcreteAttributePath // If the encoder tried to encode, then a value should have been written. // - if encode, assueme DONE (i.e. FINAL CHIP_NO_ERROR) - // - if no encode, say that processing must continue + // - if no encode, say that processing must continue return encoder.TriedEncode() ? std::make_optional(CHIP_NO_ERROR) : std::nullopt; } diff --git a/src/app/codegen-data-model/tests/BUILD.gn b/src/app/codegen-data-model/tests/BUILD.gn index 3f3503b1d8ece5..6a7a266bfce8f6 100644 --- a/src/app/codegen-data-model/tests/BUILD.gn +++ b/src/app/codegen-data-model/tests/BUILD.gn @@ -17,7 +17,7 @@ import("${chip_root}/src/app/codegen-data-model/model.gni") source_set("ember_extra_files") { sources = [ - # This IS TERRIBLE, however we want to pretent AAI exists for global + # This IS TERRIBLE, however we want to pretend AAI exists for global # items and we need a shared IO storage to reduce overhead between # data-model access and ember-compatibility (we share the same buffer) "${chip_root}/src/app/util/ember-global-attribute-access-interface.cpp", From 819df5db1c7a4b61c22ad3fa152b96575d9f0bc5 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 14 Jun 2024 16:53:13 -0400 Subject: [PATCH 106/123] Code review feedback: correct the return code for global attribute missing the cluster --- src/app/codegen-data-model/CodegenDataModel_Read.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/app/codegen-data-model/CodegenDataModel_Read.cpp b/src/app/codegen-data-model/CodegenDataModel_Read.cpp index 5fa12967cc43b6..1e47982d2b3980 100644 --- a/src/app/codegen-data-model/CodegenDataModel_Read.cpp +++ b/src/app/codegen-data-model/CodegenDataModel_Read.cpp @@ -60,14 +60,19 @@ FindAttributeMetadata(const ConcreteAttributePath & aPath) { for (auto & attr : GlobalAttributesNotInMetadata) { + if (attr == aPath.mAttributeId) { const EmberAfCluster * cluster = emberAfFindServerCluster(aPath.mEndpointId, aPath.mClusterId); - ReturnErrorCodeIf(cluster == nullptr, CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); + if (cluster == nullptr) + { + return (emberAfFindEndpointType(aPath.mEndpointId) == nullptr) ? CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint) + : CHIP_IM_GLOBAL_STATUS(UnsupportedCluster); + } + return cluster; } } - const EmberAfAttributeMetadata * metadata = emberAfLocateAttributeMetadata(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId); From ff01c99199819ec9c0d0fc79e26643e92f2716c8 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 14 Jun 2024 16:56:13 -0400 Subject: [PATCH 107/123] Some spelling updates and format --- src/app/codegen-data-model/CodegenDataModel_Read.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/codegen-data-model/CodegenDataModel_Read.cpp b/src/app/codegen-data-model/CodegenDataModel_Read.cpp index 1e47982d2b3980..876b6d32168334 100644 --- a/src/app/codegen-data-model/CodegenDataModel_Read.cpp +++ b/src/app/codegen-data-model/CodegenDataModel_Read.cpp @@ -121,7 +121,7 @@ std::optional TryReadViaAccessInterface(const ConcreteAttributePath } // If the encoder tried to encode, then a value should have been written. - // - if encode, assueme DONE (i.e. FINAL CHIP_NO_ERROR) + // - if encode, assume DONE (i.e. FINAL CHIP_NO_ERROR) // - if no encode, say that processing must continue return encoder.TriedEncode() ? std::make_optional(CHIP_NO_ERROR) : std::nullopt; } @@ -288,7 +288,7 @@ CHIP_ERROR EncodeEmberValue(ByteSpan data, const EmberAfAttributeMetadata * meta CHIP_ERROR CodegenDataModel::ReadAttribute(const InteractionModel::ReadAttributeRequest & request, AttributeValueEncoder & encoder) { ChipLogDetail(DataManagement, - "Reading attribute: Cluster=" ChipLogFormatMEI " Endpoint=%x AttributeId=" ChipLogFormatMEI " (expanded=%d)", + "Reading attribute: Cluster=" ChipLogFormatMEI " Endpoint=0x%x AttributeId=" ChipLogFormatMEI " (expanded=%d)", ChipLogValueMEI(request.path.mClusterId), request.path.mEndpointId, ChipLogValueMEI(request.path.mAttributeId), request.path.mExpanded); From 7eb0588c3f4e4a53127b6126efc05485b6f39f10 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 14 Jun 2024 17:05:12 -0400 Subject: [PATCH 108/123] ChipDie if internal logic for read fails --- src/app/codegen-data-model/CodegenDataModel_Read.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/codegen-data-model/CodegenDataModel_Read.cpp b/src/app/codegen-data-model/CodegenDataModel_Read.cpp index 876b6d32168334..1fdc56265f8804 100644 --- a/src/app/codegen-data-model/CodegenDataModel_Read.cpp +++ b/src/app/codegen-data-model/CodegenDataModel_Read.cpp @@ -329,8 +329,7 @@ CHIP_ERROR CodegenDataModel::ReadAttribute(const InteractionModel::ReadAttribute { // if we only got a cluster, this was for a global attribute. We cannot read ember attributes // at this point, so give up (although GlobalAttributeReader should have returned something here). - // Return a permanent failure... - return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); + chipDie(); } const EmberAfAttributeMetadata * attributeMetadata = std::get(metadata); From 51a20bb3ff929324fc97514be219351bf9facc79 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 14 Jun 2024 17:05:47 -0400 Subject: [PATCH 109/123] Fix typo --- .../codegen-data-model/tests/TestAttributeReportIBsEncoding.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/codegen-data-model/tests/TestAttributeReportIBsEncoding.cpp b/src/app/codegen-data-model/tests/TestAttributeReportIBsEncoding.cpp index 5154acc5596291..2fd46e0ac7d75c 100644 --- a/src/app/codegen-data-model/tests/TestAttributeReportIBsEncoding.cpp +++ b/src/app/codegen-data-model/tests/TestAttributeReportIBsEncoding.cpp @@ -43,7 +43,7 @@ CHIP_ERROR DecodeAttributeReportIBs(ByteSpan data, std::vector Array (i.e. report data ib) // ReportIB* // - // Overally this is VERY hard to process ... + // Generally this is VERY hard to process ... // TLV::TLVReader reportIBsReader; reportIBsReader.Init(data); From adbb76b3f61ffe63d2df05fe3af9699dfccc72db Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 14 Jun 2024 17:30:22 -0400 Subject: [PATCH 110/123] Add unit test for invalid global attribute read --- .../tests/TestCodegenModelViaMocks.cpp | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp index bb19e8c24808c9..d56496fb810ffb 100644 --- a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp @@ -976,6 +976,27 @@ TEST(TestCodegenModelViaMocks, EmberAttributeReadAclDeny) ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_ERROR_ACCESS_DENIED); } +TEST(TestCodegenModelViaMocks, ReadForInvalidGlobalAttributePath) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; + + { + TestReadRequest testRequest(kAdminSubjectDescriptor, + ConcreteAttributePath(kEndpointIdThatIsMissing, MockClusterId(1), AttributeList::Id)); + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint)); + } + + { + TestReadRequest testRequest(kAdminSubjectDescriptor, + ConcreteAttributePath(kMockEndpoint1, kInvalidClusterId, AttributeList::Id)); + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedCluster)); + } +} + TEST(TestCodegenModelViaMocks, EmberAttributeInvalidRead) { UseMockNodeConfig config(gTestNodeConfig); From 9394cd63dcdc190460d1ae9ac37188550d064124 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Mon, 17 Jun 2024 10:24:35 -0400 Subject: [PATCH 111/123] Code review feedback: comment about internal flags and implement path expansion logic for skipping data encoding --- .../CodegenDataModel_Read.cpp | 22 ++++++++++++++++--- src/app/data-model-interface/DataModel.h | 17 +++++++++----- src/app/data-model-interface/OperationTypes.h | 14 +++++++++++- 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/app/codegen-data-model/CodegenDataModel_Read.cpp b/src/app/codegen-data-model/CodegenDataModel_Read.cpp index 5fa12967cc43b6..7d9081ff77ce5a 100644 --- a/src/app/codegen-data-model/CodegenDataModel_Read.cpp +++ b/src/app/codegen-data-model/CodegenDataModel_Read.cpp @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "lib/core/CHIPError.h" #include #include @@ -112,7 +113,13 @@ std::optional TryReadViaAccessInterface(const ConcreteAttributePath if (err != CHIP_NO_ERROR) { - return std::make_optional(err); + // Implementation of 8.4.3.2 of the spec for path expansion + if (path.mExpanded && (err == CHIP_IM_GLOBAL_STATUS(UnsupportedRead))) + { + return CHIP_NO_ERROR; + } + + return err; } // If the encoder tried to encode, then a value should have been written. @@ -293,8 +300,17 @@ CHIP_ERROR CodegenDataModel::ReadAttribute(const InteractionModel::ReadAttribute ReturnErrorCodeIf(!request.subjectDescriptor.has_value(), CHIP_ERROR_INVALID_ARGUMENT); Access::RequestPath requestPath{ .cluster = request.path.mClusterId, .endpoint = request.path.mEndpointId }; - ReturnErrorOnFailure(Access::GetAccessControl().Check(*request.subjectDescriptor, requestPath, - RequiredPrivilege::ForReadAttribute(request.path))); + CHIP_ERROR err = Access::GetAccessControl().Check(*request.subjectDescriptor, requestPath, + RequiredPrivilege::ForReadAttribute(request.path)); + if (err != CHIP_NO_ERROR) + { + // Implementation of 8.4.3.2 of the spec for path expansion + if (request.path.mExpanded && (err == CHIP_ERROR_ACCESS_DENIED)) + { + return CHIP_NO_ERROR; + } + return err; + } } auto metadata = FindAttributeMetadata(request.path); diff --git a/src/app/data-model-interface/DataModel.h b/src/app/data-model-interface/DataModel.h index 04516e80f3c89d..6f1d79f5bbe34a 100644 --- a/src/app/data-model-interface/DataModel.h +++ b/src/app/data-model-interface/DataModel.h @@ -55,13 +55,18 @@ class DataModel : public DataModelMetadataTree // event emitting, path marking and other operations virtual InteractionModelContext CurrentContext() const { return mContext; } + /// TEMPORARY/TRANSITIONAL requirement for transitioning from ember-specific code + /// ReadAttribute is REQUIRED to perform: + /// - ACL validation (see notes on OperationFlags::kInternal) + /// - Validation of readability/writability + /// - use request.path.mExpanded to skip encoding replies for data according + /// to 8.4.3.2 of the spec: + /// > If the path indicates attribute data that is not readable, then the path SHALL + /// be discarded. + /// > Else if reading from the attribute in the path requires a privilege that is not + /// granted to access the cluster in the path, then the path SHALL be discarded. + /// /// Return codes: - /// CHIP_ERROR_ACCESS_DENIED: - /// - May be ignored if reads use path expansion (e.g. to skip inaccessible attributes - /// during subscription requests). This code MUST be used for ACL failures and only for ACL failures. - /// CHIP_IM_GLOBAL_STATUS(UnsupportedRead): - /// - May be ignored if reads use path expansion (e.g. to skip reading write-only attributes - /// during subscription requests). /// CHIP_ERROR_NO_MEMORY or CHIP_ERROR_BUFFER_TOO_SMALL: /// - Indicates that list encoding had insufficient buffer space to encode elements. /// - encoder::GetState().AllowPartialData() determines if these errors are permanent (no partial diff --git a/src/app/data-model-interface/OperationTypes.h b/src/app/data-model-interface/OperationTypes.h index aafa73c05bdfd8..d19621db71d26a 100644 --- a/src/app/data-model-interface/OperationTypes.h +++ b/src/app/data-model-interface/OperationTypes.h @@ -31,7 +31,17 @@ namespace InteractionModel { /// Contains common flags among all interaction model operations: read/write/invoke enum class OperationFlags : uint32_t { - kInternal = 0x0001, // Internal request for data changes (can bypass checks/ACL etc.) + // NOTE: temporary flag. This flag exists to faciliate transition from ember-compatibilty-functions + // implementation to DataModel Interface functionality. Specifically currently the + // model is expected to perform ACL and readability/writability checks. + // + // In the future, this flag will be removed and InteractionModelEngine/ReportingEngine + // will perform the required validation. + // + // Currently the flag FORCES a bypass of: + // - ACL validation (will allow any read/write) + // - Access validation (will allow reading write-only data for example) + kInternal = 0x0001, }; /// This information is available for ALL interactions: read/write/invoke @@ -42,6 +52,8 @@ struct OperationRequest /// Current authentication data EXCEPT for internal requests. /// - Non-internal requests MUST have this set. /// - operationFlags.Has(OperationFlags::kInternal) MUST NOT have this set + /// + /// NOTE: once kInternal flag is removed, this will become non-optional std::optional subjectDescriptor; }; From 39b4b11e18c829f0e40fa6b4a0476366ff737efe Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Mon, 17 Jun 2024 10:50:34 -0400 Subject: [PATCH 112/123] Restyle and unit test for expansion --- .../tests/TestCodegenModelViaMocks.cpp | 18 ++++++++++++++++++ src/app/data-model-interface/DataModel.h | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp index d56496fb810ffb..93248674dcbbb5 100644 --- a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp @@ -1031,6 +1031,24 @@ TEST(TestCodegenModelViaMocks, EmberAttributeInvalidRead) } } +TEST(TestCodegenModelViaMocks, EmberAttributePathExpansionAccessDeniedRead) +{ + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; + + TestReadRequest testRequest(kDenySubjectDescriptor, + ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))); + std::unique_ptr encoder = testRequest.StartEncoding(&model); + + testRequest.request.path.mExpanded = true; + + // For expanded paths, access control failures succeed without encoding anything + // This is temporary until ACL checks are moved inside the IM/ReportEngine + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + ASSERT_FALSE(encoder->TriedEncode()); +} + TEST(TestCodegenModelViaMocks, EmberAttributeReadInt32S) { TestEmberScalarTypeRead(-1234); diff --git a/src/app/data-model-interface/DataModel.h b/src/app/data-model-interface/DataModel.h index 6f1d79f5bbe34a..04911fd75cccc3 100644 --- a/src/app/data-model-interface/DataModel.h +++ b/src/app/data-model-interface/DataModel.h @@ -61,7 +61,7 @@ class DataModel : public DataModelMetadataTree /// - Validation of readability/writability /// - use request.path.mExpanded to skip encoding replies for data according /// to 8.4.3.2 of the spec: - /// > If the path indicates attribute data that is not readable, then the path SHALL + /// > If the path indicates attribute data that is not readable, then the path SHALL /// be discarded. /// > Else if reading from the attribute in the path requires a privilege that is not /// granted to access the cluster in the path, then the path SHALL be discarded. From ea8356f865d8524b5a772f3fabf983230fcc2dd1 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Mon, 17 Jun 2024 10:55:44 -0400 Subject: [PATCH 113/123] Another unit test for AAI this time for unsupported read --- .../tests/TestCodegenModelViaMocks.cpp | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp index 93248674dcbbb5..4489e0c16214c5 100644 --- a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp @@ -373,6 +373,28 @@ CHIP_ERROR DecodeList(TLV::TLVReader & reader, std::vector & out) } } +class UnsupportedReadAccessInterface : public AttributeAccessInterface +{ +public: + UnsupportedReadAccessInterface(ConcreteAttributePath path) : + AttributeAccessInterface(MakeOptional(path.mEndpointId), path.mClusterId), mPath(path) + {} + ~UnsupportedReadAccessInterface() = default; + + CHIP_ERROR Read(const ConcreteReadAttributePath & path, AttributeValueEncoder & encoder) override + { + if (static_cast(path) != mPath) + { + // returning without trying to handle means "I do not handle this" + return CHIP_NO_ERROR; + } + + return CHIP_IM_GLOBAL_STATUS(UnsupportedRead); + } +private: + ConcreteAttributePath mPath; +}; + class StructAttributeAccessInterface : public AttributeAccessInterface { public: @@ -1049,6 +1071,27 @@ TEST(TestCodegenModelViaMocks, EmberAttributePathExpansionAccessDeniedRead) ASSERT_FALSE(encoder->TriedEncode()); } +TEST(TestCodegenModelViaMocks, AccessInterfaceUnsupportedRead) { + UseMockNodeConfig config(gTestNodeConfig); + chip::app::CodegenDataModel model; + ScopedMockAccessControl accessControl; + + const ConcreteAttributePath kTestPath(kMockEndpoint3, MockClusterId(4), + MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE)); + + TestReadRequest testRequest(kAdminSubjectDescriptor, kTestPath); + RegisteredAttributeAccessInterface aai(kTestPath); + + testRequest.request.path.mExpanded = true; + + // For expanded paths, unsupported read from AAI (i.e. reading write-only data) + // succeed without attempting to encode. + // This is temporary until ACL checks are moved inside the IM/ReportEngine + std::unique_ptr encoder = testRequest.StartEncoding(&model); + ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR); + ASSERT_FALSE(encoder->TriedEncode()); +} + TEST(TestCodegenModelViaMocks, EmberAttributeReadInt32S) { TestEmberScalarTypeRead(-1234); From be0270fc57b86f49be362f186a62c3e6274c1cac Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Mon, 17 Jun 2024 11:23:59 -0400 Subject: [PATCH 114/123] Restyle --- .../codegen-data-model/tests/TestCodegenModelViaMocks.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp index 4489e0c16214c5..625fcbd768c7fc 100644 --- a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp @@ -391,6 +391,7 @@ class UnsupportedReadAccessInterface : public AttributeAccessInterface return CHIP_IM_GLOBAL_STATUS(UnsupportedRead); } + private: ConcreteAttributePath mPath; }; @@ -1071,13 +1072,14 @@ TEST(TestCodegenModelViaMocks, EmberAttributePathExpansionAccessDeniedRead) ASSERT_FALSE(encoder->TriedEncode()); } -TEST(TestCodegenModelViaMocks, AccessInterfaceUnsupportedRead) { +TEST(TestCodegenModelViaMocks, AccessInterfaceUnsupportedRead) +{ UseMockNodeConfig config(gTestNodeConfig); chip::app::CodegenDataModel model; ScopedMockAccessControl accessControl; const ConcreteAttributePath kTestPath(kMockEndpoint3, MockClusterId(4), - MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE)); + MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE)); TestReadRequest testRequest(kAdminSubjectDescriptor, kTestPath); RegisteredAttributeAccessInterface aai(kTestPath); From 36f866b45681d87f33d404a8da72fab67b065fc2 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 21 Jun 2024 16:19:08 -0400 Subject: [PATCH 115/123] Update src/app/codegen-data-model/tests/TestAttributeReportIBsEncoding.cpp Co-authored-by: Boris Zbarsky --- .../codegen-data-model/tests/TestAttributeReportIBsEncoding.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/codegen-data-model/tests/TestAttributeReportIBsEncoding.cpp b/src/app/codegen-data-model/tests/TestAttributeReportIBsEncoding.cpp index 2fd46e0ac7d75c..c57a5c7bb74d55 100644 --- a/src/app/codegen-data-model/tests/TestAttributeReportIBsEncoding.cpp +++ b/src/app/codegen-data-model/tests/TestAttributeReportIBsEncoding.cpp @@ -24,7 +24,7 @@ using namespace chip::app; namespace chip { namespace Test { -CHIP_ERROR DecodedAttributeData::DecodeFrom(const chip::app::AttributeDataIB::Parser & parser) +CHIP_ERROR DecodedAttributeData::DecodeFrom(const AttributeDataIB::Parser & parser) { ReturnErrorOnFailure(parser.GetDataVersion(&dataVersion)); From ddc94609d39b1977c1a66aefa797ea716add6fbc Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 21 Jun 2024 16:51:42 -0400 Subject: [PATCH 116/123] Rename files to not have a Test prefix when not containing tests --- ...eportIBsEncoding.cpp => AttributeReportIBEncodeDecode.cpp} | 2 +- ...uteReportIBsEncoding.h => AttributeReportIBEncodeDecode.h} | 0 src/app/codegen-data-model/tests/BUILD.gn | 4 ++-- src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename src/app/codegen-data-model/tests/{TestAttributeReportIBsEncoding.cpp => AttributeReportIBEncodeDecode.cpp} (98%) rename src/app/codegen-data-model/tests/{TestAttributeReportIBsEncoding.h => AttributeReportIBEncodeDecode.h} (100%) diff --git a/src/app/codegen-data-model/tests/TestAttributeReportIBsEncoding.cpp b/src/app/codegen-data-model/tests/AttributeReportIBEncodeDecode.cpp similarity index 98% rename from src/app/codegen-data-model/tests/TestAttributeReportIBsEncoding.cpp rename to src/app/codegen-data-model/tests/AttributeReportIBEncodeDecode.cpp index c57a5c7bb74d55..29088d0cdc55e1 100644 --- a/src/app/codegen-data-model/tests/TestAttributeReportIBsEncoding.cpp +++ b/src/app/codegen-data-model/tests/AttributeReportIBEncodeDecode.cpp @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "TestAttributeReportIBsEncoding.h" +#include "AttributeReportIBEncodeDecode.h" #include #include diff --git a/src/app/codegen-data-model/tests/TestAttributeReportIBsEncoding.h b/src/app/codegen-data-model/tests/AttributeReportIBEncodeDecode.h similarity index 100% rename from src/app/codegen-data-model/tests/TestAttributeReportIBsEncoding.h rename to src/app/codegen-data-model/tests/AttributeReportIBEncodeDecode.h diff --git a/src/app/codegen-data-model/tests/BUILD.gn b/src/app/codegen-data-model/tests/BUILD.gn index 6a7a266bfce8f6..3d265a96a66b29 100644 --- a/src/app/codegen-data-model/tests/BUILD.gn +++ b/src/app/codegen-data-model/tests/BUILD.gn @@ -22,11 +22,11 @@ source_set("ember_extra_files") { # data-model access and ember-compatibility (we share the same buffer) "${chip_root}/src/app/util/ember-global-attribute-access-interface.cpp", "${chip_root}/src/app/util/ember-io-storage.cpp", + "AttributeReportIBEncodeDecode.cpp", + "AttributeReportIBEncodeDecode.h", "EmberReadWriteOverride.cpp", "EmberReadWriteOverride.h", "InteractionModelTemporaryOverrides.cpp", - "TestAttributeReportIBsEncoding.cpp", - "TestAttributeReportIBsEncoding.h", ] public_deps = [ diff --git a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp index 625fcbd768c7fc..11264bbd33a36e 100644 --- a/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-data-model/tests/TestCodegenModelViaMocks.cpp @@ -16,8 +16,8 @@ */ #include +#include #include -#include #include #include From 6ed59f1b6628a10f7d0185d10d59099016b76f1f Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 28 Jun 2024 10:24:44 -0400 Subject: [PATCH 117/123] Add comment as per review comment --- src/app/codegen-data-model/CodegenDataModel_Read.cpp | 2 ++ third_party/openthread/ot-nxp | 2 +- third_party/silabs/matter_support | 2 +- third_party/silabs/wifi_sdk | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/codegen-data-model/CodegenDataModel_Read.cpp b/src/app/codegen-data-model/CodegenDataModel_Read.cpp index fa528c4443a62c..be550116b15302 100644 --- a/src/app/codegen-data-model/CodegenDataModel_Read.cpp +++ b/src/app/codegen-data-model/CodegenDataModel_Read.cpp @@ -159,6 +159,8 @@ std::optional ExtractEmberString(ByteSpan data) { typename ENCODING::LengthType len; + // Ember storage format for pascal-prefix data is specifically "native byte order", + // hence the use of memcpy. VerifyOrDie(sizeof(len) <= data.size()); memcpy(&len, data.data(), sizeof(len)); diff --git a/third_party/openthread/ot-nxp b/third_party/openthread/ot-nxp index b8471857ab7b2c..2128e8dbe8af40 160000 --- a/third_party/openthread/ot-nxp +++ b/third_party/openthread/ot-nxp @@ -1 +1 @@ -Subproject commit b8471857ab7b2c843c9ec5bc5d7e7711b2cd30a4 +Subproject commit 2128e8dbe8af40d0cce55fc514a39a0b1376a95b diff --git a/third_party/silabs/matter_support b/third_party/silabs/matter_support index 96fc0a37ecb2e6..edbfeba723c9ff 160000 --- a/third_party/silabs/matter_support +++ b/third_party/silabs/matter_support @@ -1 +1 @@ -Subproject commit 96fc0a37ecb2e6791f57d62d5619db65d870ae4a +Subproject commit edbfeba723c9ffdc93f57ad1eee85186b1643f25 diff --git a/third_party/silabs/wifi_sdk b/third_party/silabs/wifi_sdk index e97a0ed00ddda3..aa514d4fac4b56 160000 --- a/third_party/silabs/wifi_sdk +++ b/third_party/silabs/wifi_sdk @@ -1 +1 @@ -Subproject commit e97a0ed00ddda347a8a39e8276f470e1c5fea469 +Subproject commit aa514d4fac4b568d03e1f6d3d19c7811034d5077 From cdac2ac55dff91338de38a6b4c8b744d9c0dcfe1 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 28 Jun 2024 10:28:09 -0400 Subject: [PATCH 118/123] Use macros for error returns --- src/app/codegen-data-model/CodegenDataModel_Read.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/codegen-data-model/CodegenDataModel_Read.cpp b/src/app/codegen-data-model/CodegenDataModel_Read.cpp index be550116b15302..f877067a8e1b64 100644 --- a/src/app/codegen-data-model/CodegenDataModel_Read.cpp +++ b/src/app/codegen-data-model/CodegenDataModel_Read.cpp @@ -362,7 +362,7 @@ CHIP_ERROR CodegenDataModel::ReadAttribute(const InteractionModel::ReadAttribute if (status != Protocols::InteractionModel::Status::Success) { - return ChipError(ChipError::SdkPart::kIMGlobalStatus, to_underlying(status), __FILE__, __LINE__); + return CHIP_SDK_ERROR(ChipError::SdkPart::kIMGlobalStatus, to_underlying(status)); } return EncodeEmberValue(gEmberAttributeIOBufferSpan, attributeMetadata, encoder); From da6b870d7d793c8d155a99fd090ac70c4d6ec312 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 28 Jun 2024 10:30:00 -0400 Subject: [PATCH 119/123] Fix typo --- src/app/codegen-data-model/tests/EmberReadWriteOverride.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/codegen-data-model/tests/EmberReadWriteOverride.cpp b/src/app/codegen-data-model/tests/EmberReadWriteOverride.cpp index 619f6ee555f568..8c65ee29b05556 100644 --- a/src/app/codegen-data-model/tests/EmberReadWriteOverride.cpp +++ b/src/app/codegen-data-model/tests/EmberReadWriteOverride.cpp @@ -22,9 +22,9 @@ using chip::Protocols::InteractionModel::Status; namespace { -constexpr size_t kMaxTesetIoSize = 128; +constexpr size_t kMaxTestIoSize = 128; -uint8_t gEmberIoBuffer[kMaxTesetIoSize]; +uint8_t gEmberIoBuffer[kMaxTestIoSize]; size_t gEmberIoBufferFill; Status gEmberStatusCode = Status::InvalidAction; From 4ef4438155e462d9c9a804af11074802f2516d5a Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 28 Jun 2024 10:36:33 -0400 Subject: [PATCH 120/123] Add a macro for dynamic global IM status codes in chip_error --- src/app/codegen-data-model/CodegenDataModel_Read.cpp | 2 +- src/lib/core/CHIPError.h | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/app/codegen-data-model/CodegenDataModel_Read.cpp b/src/app/codegen-data-model/CodegenDataModel_Read.cpp index f877067a8e1b64..04265d37f8623f 100644 --- a/src/app/codegen-data-model/CodegenDataModel_Read.cpp +++ b/src/app/codegen-data-model/CodegenDataModel_Read.cpp @@ -362,7 +362,7 @@ CHIP_ERROR CodegenDataModel::ReadAttribute(const InteractionModel::ReadAttribute if (status != Protocols::InteractionModel::Status::Success) { - return CHIP_SDK_ERROR(ChipError::SdkPart::kIMGlobalStatus, to_underlying(status)); + return CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(status); } return EncodeEmberValue(gEmberAttributeIOBufferSpan, attributeMetadata, encoder); diff --git a/src/lib/core/CHIPError.h b/src/lib/core/CHIPError.h index aebf7804aefb10..1142d385900681 100644 --- a/src/lib/core/CHIPError.h +++ b/src/lib/core/CHIPError.h @@ -439,6 +439,15 @@ using CHIP_ERROR = ::chip::ChipError; CHIP_SDK_ERROR(::chip::ChipError::SdkPart::kIMGlobalStatus, \ ::chip::to_underlying(::chip::Protocols::InteractionModel::Status::type)) +// Defines a runtime-value for a chip-error. +#if CHIP_CONFIG_ERROR_SOURCE +#define CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(status_value) \ + ::chip::ChipError(::chip::ChipError::SdkPart::kIMGlobalStatus, ::chip::to_underlying(status_value), __FILE__, __LINE__) +#else +#define CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(status_value) \ + ::chip::ChipError(::chip::ChipError::SdkPart::kIMGlobalStatus, ::chip::to_underlying(status_value)) +#endif // CHIP_CONFIG_ERROR_SOURCE + // // type must be a compile-time constant as mandated by CHIP_SDK_ERROR. // From 6308bdb81d0f8e360e5b1cccc1f0c5751e27f60a Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 28 Jun 2024 10:39:56 -0400 Subject: [PATCH 121/123] Reference issue in comment --- .../tests/InteractionModelTemporaryOverrides.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/codegen-data-model/tests/InteractionModelTemporaryOverrides.cpp b/src/app/codegen-data-model/tests/InteractionModelTemporaryOverrides.cpp index 85baca85781088..868a26880d3ce7 100644 --- a/src/app/codegen-data-model/tests/InteractionModelTemporaryOverrides.cpp +++ b/src/app/codegen-data-model/tests/InteractionModelTemporaryOverrides.cpp @@ -26,6 +26,7 @@ using chip::Protocols::InteractionModel::Status; // TODO: most of the functions here are part of EmberCompatibilityFunctions and is NOT decoupled // from IM current, but it SHOULD be +// Open issue https://github.com/project-chip/connectedhomeip/issues/34137 for this work. namespace chip { namespace app { From 810cc06c357139dc81419ee0c1e4b90bd5a275a8 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 28 Jun 2024 10:41:49 -0400 Subject: [PATCH 122/123] Fix comment a bit --- src/lib/core/CHIPError.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/core/CHIPError.h b/src/lib/core/CHIPError.h index 1142d385900681..b49088247e126c 100644 --- a/src/lib/core/CHIPError.h +++ b/src/lib/core/CHIPError.h @@ -439,7 +439,7 @@ using CHIP_ERROR = ::chip::ChipError; CHIP_SDK_ERROR(::chip::ChipError::SdkPart::kIMGlobalStatus, \ ::chip::to_underlying(::chip::Protocols::InteractionModel::Status::type)) -// Defines a runtime-value for a chip-error. +// Defines a runtime-value for a chip-error that contains a global IM Status. #if CHIP_CONFIG_ERROR_SOURCE #define CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(status_value) \ ::chip::ChipError(::chip::ChipError::SdkPart::kIMGlobalStatus, ::chip::to_underlying(status_value), __FILE__, __LINE__) From c4e4aa709ce7dcf35e3caf827592d33c9011bcbd Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 28 Jun 2024 10:43:37 -0400 Subject: [PATCH 123/123] Undo module updates ... this time for real --- third_party/openthread/ot-nxp | 2 +- third_party/silabs/matter_support | 2 +- third_party/silabs/wifi_sdk | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/third_party/openthread/ot-nxp b/third_party/openthread/ot-nxp index 2128e8dbe8af40..b8471857ab7b2c 160000 --- a/third_party/openthread/ot-nxp +++ b/third_party/openthread/ot-nxp @@ -1 +1 @@ -Subproject commit 2128e8dbe8af40d0cce55fc514a39a0b1376a95b +Subproject commit b8471857ab7b2c843c9ec5bc5d7e7711b2cd30a4 diff --git a/third_party/silabs/matter_support b/third_party/silabs/matter_support index edbfeba723c9ff..96fc0a37ecb2e6 160000 --- a/third_party/silabs/matter_support +++ b/third_party/silabs/matter_support @@ -1 +1 @@ -Subproject commit edbfeba723c9ffdc93f57ad1eee85186b1643f25 +Subproject commit 96fc0a37ecb2e6791f57d62d5619db65d870ae4a diff --git a/third_party/silabs/wifi_sdk b/third_party/silabs/wifi_sdk index aa514d4fac4b56..e97a0ed00ddda3 160000 --- a/third_party/silabs/wifi_sdk +++ b/third_party/silabs/wifi_sdk @@ -1 +1 @@ -Subproject commit aa514d4fac4b568d03e1f6d3d19c7811034d5077 +Subproject commit e97a0ed00ddda347a8a39e8276f470e1c5fea469