| /* |
| * 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 <pw_unit_test/framework.h> |
| |
| #include <data-model-providers/codegen/tests/EmberInvokeOverride.h> |
| #include <data-model-providers/codegen/tests/EmberReadWriteOverride.h> |
| |
| #include <access/AccessControl.h> |
| #include <access/Privilege.h> |
| #include <access/SubjectDescriptor.h> |
| #include <app-common/zap-generated/attribute-type.h> |
| #include <app-common/zap-generated/cluster-objects.h> |
| #include <app/AttributeAccessInterface.h> |
| #include <app/AttributeAccessInterfaceRegistry.h> |
| #include <app/AttributeEncodeState.h> |
| #include <app/AttributeValueDecoder.h> |
| #include <app/CommandHandlerInterface.h> |
| #include <app/CommandHandlerInterfaceRegistry.h> |
| #include <app/CommandHandlerInterfaceShim.h> |
| #include <app/ConcreteAttributePath.h> |
| #include <app/ConcreteClusterPath.h> |
| #include <app/ConcreteCommandPath.h> |
| #include <app/GlobalAttributes.h> |
| #include <app/MessageDef/ReportDataMessage.h> |
| #include <app/data-model-provider/MetadataLookup.h> |
| #include <app/data-model-provider/MetadataTypes.h> |
| #include <app/data-model-provider/OperationTypes.h> |
| #include <app/data-model-provider/StringBuilderAdapters.h> |
| #include <app/data-model-provider/tests/ReadTesting.h> |
| #include <app/data-model-provider/tests/TestConstants.h> |
| #include <app/data-model-provider/tests/WriteTesting.h> |
| #include <app/data-model/Decode.h> |
| #include <app/data-model/Encode.h> |
| #include <app/data-model/List.h> |
| #include <app/data-model/Nullable.h> |
| #include <app/server-cluster/DefaultServerCluster.h> |
| #include <app/server-cluster/testing/TestEventGenerator.h> |
| #include <app/server-cluster/testing/TestServerClusterContext.h> |
| #include <app/util/attribute-metadata.h> |
| #include <app/util/ember-io-storage.h> |
| #include <app/util/mock/Constants.h> |
| #include <app/util/mock/Functions.h> |
| #include <app/util/mock/MockNodeConfig.h> |
| #include <data-model-providers/codegen/CodegenDataModelProvider.h> |
| #include <lib/core/CHIPError.h> |
| #include <lib/core/DataModelTypes.h> |
| #include <lib/core/Optional.h> |
| #include <lib/core/StringBuilderAdapters.h> |
| #include <lib/core/TLVDebug.h> |
| #include <lib/core/TLVReader.h> |
| #include <lib/core/TLVTags.h> |
| #include <lib/core/TLVTypes.h> |
| #include <lib/core/TLVWriter.h> |
| #include <lib/support/ReadOnlyBuffer.h> |
| #include <lib/support/Span.h> |
| #include <lib/support/TestPersistentStorageDelegate.h> |
| #include <lib/support/odd-sized-integers.h> |
| #include <lib/support/tests/ExtraPwTestMacros.h> |
| #include <protocols/interaction_model/StatusCode.h> |
| |
| #include <optional> |
| #include <vector> |
| |
| using namespace chip; |
| using namespace chip::Testing; |
| using namespace chip::app; |
| using namespace chip::app::DataModel; |
| using namespace chip::app::Clusters::Globals::Attributes; |
| |
| using chip::Protocols::InteractionModel::Status; |
| |
| // Mock function for linking |
| void InitDataModelHandler() {} |
| |
| namespace { |
| |
| constexpr EventId kTestEventId = 0x321; |
| |
| constexpr AttributeId kAttributeIdReadOnly = 0x3001; |
| constexpr AttributeId kAttributeIdTimedWrite = 0x3002; |
| constexpr AttributeId kAttributeIdFakeAllowsWrite = 0x3003; |
| |
| constexpr CommandId kMockCommandId1 = 0x1234; |
| constexpr CommandId kMockCommandId2 = 0x1122; |
| |
| constexpr EndpointId kEndpointIdThatIsMissing = kMockEndpointMin - 1; |
| |
| constexpr AttributeId kReadOnlyAttributeId = 0x5001; |
| |
| constexpr DeviceTypeId kDeviceTypeId1 = 123; |
| constexpr uint8_t kDeviceTypeId1Version = 10; |
| |
| constexpr DeviceTypeId kDeviceTypeId2 = 1122; |
| constexpr uint8_t kDeviceTypeId2Version = 11; |
| |
| constexpr DeviceTypeId kDeviceTypeId3 = 3; |
| constexpr uint8_t kDeviceTypeId3Version = 33; |
| |
| constexpr DeviceTypeId kDeviceTypeId4 = 1123; |
| constexpr uint8_t kDeviceTypeId4Version = 33; |
| |
| constexpr uint8_t kNamespaceID1 = 123; |
| constexpr uint8_t kTag1 = 10; |
| constexpr char kLabel1[] = "Label1"; |
| |
| constexpr uint8_t kNamespaceID2 = 254; |
| constexpr uint8_t kTag2 = 22; |
| constexpr char kLabel2[] = "Label2"; |
| |
| constexpr uint8_t kNamespaceID3 = 3; |
| constexpr uint8_t kTag3 = 32; |
| |
| static_assert(kEndpointIdThatIsMissing != kInvalidEndpointId); |
| static_assert(kEndpointIdThatIsMissing != kMockEndpoint1); |
| static_assert(kEndpointIdThatIsMissing != kMockEndpoint2); |
| static_assert(kEndpointIdThatIsMissing != kMockEndpoint3); |
| static_assert(kEndpointIdThatIsMissing != kMockEndpoint4); |
| |
| 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; |
| } |
| |
| struct TestCodegenModelViaMocks : public ::testing::Test |
| { |
| static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); } |
| static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); } |
| }; |
| |
| class TestProviderChangeListener : public ProviderChangeListener |
| { |
| public: |
| void MarkDirty(const AttributePathParams & path) override { mDirtyList.push_back(path); } |
| |
| std::vector<AttributePathParams> & DirtyList() { return mDirtyList; } |
| const std::vector<AttributePathParams> & DirtyList() const { return mDirtyList; } |
| |
| private: |
| std::vector<AttributePathParams> mDirtyList; |
| }; |
| |
| class TestActionContext : public ActionContext |
| { |
| public: |
| Messaging::ExchangeContext * CurrentExchange() override { return nullptr; } |
| }; |
| |
| class CodegenDataModelProviderWithContext : public CodegenDataModelProvider |
| { |
| public: |
| CodegenDataModelProviderWithContext() |
| { |
| SetPersistentStorageDelegate(&mStorageDelegate); |
| EXPECT_SUCCESS(Startup({ |
| .eventsGenerator = mEventGenerator, |
| .dataModelChangeListener = mChangeListener, |
| .actionContext = mActionContext, |
| })); |
| } |
| ~CodegenDataModelProviderWithContext() override { EXPECT_SUCCESS(Shutdown()); } |
| |
| TestProviderChangeListener & ChangeListener() { return mChangeListener; } |
| const TestProviderChangeListener & ChangeListener() const { return mChangeListener; } |
| |
| private: |
| // Test cases that explicitly manage the provider should not use this sub-class |
| using CodegenDataModelProvider::SetPersistentStorageDelegate; |
| using CodegenDataModelProvider::Startup; |
| |
| LogOnlyEvents mEventGenerator; |
| TestProviderChangeListener mChangeListener; |
| TestActionContext mActionContext; |
| TestPersistentStorageDelegate mStorageDelegate; |
| }; |
| |
| 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 MockCommandHandler : public CommandHandler |
| { |
| public: |
| CHIP_ERROR FallibleAddStatus(const ConcreteCommandPath & aRequestCommandPath, |
| const Protocols::InteractionModel::ClusterStatusCode & aStatus, |
| const char * context = nullptr) override |
| { |
| // MOCK: do not do anything here |
| return CHIP_NO_ERROR; |
| } |
| |
| void AddStatus(const ConcreteCommandPath & aRequestCommandPath, const Protocols::InteractionModel::ClusterStatusCode & aStatus, |
| const char * context = nullptr) override |
| { |
| // MOCK: do not do anything here |
| } |
| |
| FabricIndex GetAccessingFabricIndex() const override { return 1; } |
| |
| CHIP_ERROR AddResponseData(const ConcreteCommandPath & aRequestCommandPath, CommandId aResponseCommandId, |
| const DataModel::EncodableToTLV & aEncodable) override |
| { |
| return CHIP_NO_ERROR; |
| } |
| |
| void AddResponse(const ConcreteCommandPath & aRequestCommandPath, CommandId aResponseCommandId, |
| const DataModel::EncodableToTLV & aEncodable) override |
| {} |
| |
| bool IsTimedInvoke() const override { return false; } |
| |
| void FlushAcksRightAwayOnSlowCommand() override {} |
| |
| Access::SubjectDescriptor GetSubjectDescriptor() const override { return kAdminSubjectDescriptor; } |
| |
| Messaging::ExchangeContext * GetExchangeContext() const override { return nullptr; } |
| }; |
| |
| /// Overrides Enumerate*Commands in the CommandHandlerInterface to allow |
| /// testing of behaviors when command enumeration is done in the interace. |
| class CustomListCommandHandler : public CommandHandlerInterface |
| { |
| public: |
| CustomListCommandHandler(Optional<EndpointId> endpointId, ClusterId clusterId) : CommandHandlerInterface(endpointId, clusterId) |
| { |
| EXPECT_SUCCESS(CommandHandlerInterfaceRegistry::Instance().RegisterCommandHandler(this)); |
| } |
| ~CustomListCommandHandler() { EXPECT_SUCCESS(CommandHandlerInterfaceRegistry::Instance().UnregisterCommandHandler(this)); } |
| |
| void InvokeCommand(HandlerContext & handlerContext) override |
| { |
| if (mHandleCommand) |
| { |
| handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Protocols::InteractionModel::Status::Success); |
| handlerContext.SetCommandHandled(); |
| } |
| else |
| { |
| handlerContext.SetCommandNotHandled(); |
| } |
| } |
| |
| void SetHandleCommands(bool handle) { mHandleCommand = handle; } |
| |
| CHIP_ERROR RetrieveAcceptedCommands(const ConcreteClusterPath & cluster, |
| ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder) override |
| { |
| VerifyOrReturnError(mOverrideAccepted, CHIP_ERROR_NOT_IMPLEMENTED); |
| return builder.AppendElements(Span<const AcceptedCommandEntry>(mAccepted.data(), mAccepted.size())); |
| } |
| |
| CHIP_ERROR RetrieveGeneratedCommands(const ConcreteClusterPath & cluster, ReadOnlyBufferBuilder<CommandId> & builder) override |
| { |
| VerifyOrReturnError(mOverrideGenerated, CHIP_ERROR_NOT_IMPLEMENTED); |
| return builder.AppendElements(Span<const CommandId>(mGenerated.data(), mGenerated.size())); |
| } |
| |
| void SetOverrideAccepted(bool overrideAccepted) { mOverrideAccepted = overrideAccepted; } |
| void SetOverrideGenerated(bool overrideGenerated) { mOverrideGenerated = overrideGenerated; } |
| |
| std::vector<DataModel::AcceptedCommandEntry> & AcceptedVec() { return mAccepted; } |
| std::vector<CommandId> & GeneratedVec() { return mGenerated; } |
| |
| private: |
| bool mOverrideAccepted = false; |
| bool mOverrideGenerated = false; |
| bool mHandleCommand = false; |
| |
| std::vector<DataModel::AcceptedCommandEntry> mAccepted; |
| std::vector<CommandId> mGenerated; |
| }; |
| |
| /// Overrides Enumerate*Commands in the CommandHandlerInterface to allow |
| /// testing of behaviors when command enumeration is done in the interace. |
| class ShimCommandHandler : public CommandHandlerInterfaceShim<Clusters::UnitTesting::Id> |
| { |
| public: |
| ShimCommandHandler(Optional<EndpointId> endpointId, ClusterId clusterId) : CommandHandlerInterfaceShim(endpointId, clusterId) |
| { |
| EXPECT_SUCCESS(CommandHandlerInterfaceRegistry::Instance().RegisterCommandHandler(this)); |
| } |
| ~ShimCommandHandler() { EXPECT_SUCCESS(CommandHandlerInterfaceRegistry::Instance().UnregisterCommandHandler(this)); } |
| |
| void InvokeCommand(HandlerContext & handlerContext) override |
| { |
| if (mHandleCommand) |
| { |
| handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Protocols::InteractionModel::Status::Success); |
| handlerContext.SetCommandHandled(); |
| } |
| else |
| { |
| handlerContext.SetCommandNotHandled(); |
| } |
| } |
| |
| // |
| void SetHandleCommands(bool handle) { mHandleCommand = handle; } |
| |
| // |
| CHIP_ERROR EnumerateAcceptedCommands(const ConcreteClusterPath & cluster, CommandIdCallback callback, void * context) override |
| { |
| VerifyOrReturnError(mOverrideAccepted, CHIP_ERROR_NOT_IMPLEMENTED); |
| |
| for (auto id : mAccepted) |
| { |
| if (callback(id, context) != Loop::Continue) |
| { |
| break; |
| } |
| } |
| return CHIP_NO_ERROR; |
| } |
| |
| // |
| CHIP_ERROR EnumerateGeneratedCommands(const ConcreteClusterPath & cluster, CommandIdCallback callback, void * context) override |
| { |
| VerifyOrReturnError(mOverrideGenerated, CHIP_ERROR_NOT_IMPLEMENTED); |
| |
| for (auto id : mGenerated) |
| { |
| if (callback(id, context) != Loop::Continue) |
| { |
| break; |
| } |
| } |
| return CHIP_NO_ERROR; |
| } |
| |
| void SetOverrideAccepted(bool overrideAccepted) { mOverrideAccepted = overrideAccepted; } |
| void SetOverrideGenerated(bool overrideGenerated) { mOverrideGenerated = overrideGenerated; } |
| |
| std::vector<CommandId> & AcceptedVec() { return mAccepted; } |
| std::vector<CommandId> & GeneratedVec() { return mGenerated; } |
| |
| private: |
| bool mOverrideAccepted = false; |
| bool mOverrideGenerated = false; |
| bool mHandleCommand = false; |
| |
| std::vector<CommandId> mAccepted; |
| std::vector<CommandId> mGenerated; |
| }; |
| |
| class ScopedMockAccessControl |
| { |
| public: |
| ScopedMockAccessControl() |
| { // We are sometimes initializing twice, resulting in a failure |
| TEMPORARY_RETURN_IGNORED 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, \ |
| MATTER_ATTRIBUTE_FLAG_WRITABLE | MATTER_ATTRIBUTE_FLAG_READABLE | MATTER_ATTRIBUTE_FLAG_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, \ |
| MATTER_ATTRIBUTE_FLAG_WRITABLE | MATTER_ATTRIBUTE_FLAG_READABLE) |
| |
| // 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) |
| }), |
| MockClusterConfig(MockClusterId(3), {}, {}, {}, {}, BitMask<MockClusterSide>().Set(MockClusterSide::kClient)), |
| MockClusterConfig(MockClusterId(4), {}, {}, {}, {}, BitMask<MockClusterSide>().Set(MockClusterSide::kClient)), |
| }, { |
| { kDeviceTypeId1, kDeviceTypeId1Version}, |
| { kDeviceTypeId2, kDeviceTypeId2Version}, |
| { kDeviceTypeId3, kDeviceTypeId3Version}, |
| },{ |
| { MakeNullable(VendorId::TestVendor1), kNamespaceID1, kTag1, MakeOptional(MakeNullable(CharSpan::fromCharString(kLabel1)))}, |
| { Nullable<VendorId>(), kNamespaceID2, kTag2, MakeOptional(MakeNullable(CharSpan::fromCharString(kLabel2)))}, |
| { MakeNullable(VendorId::TestVendor3), kNamespaceID3, kTag3, NullOptional}, |
| }), |
| 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), |
| }, /* 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 */ |
| BitMask<MockClusterSide>().Set(MockClusterSide::kClient).Set(MockClusterSide::kServer) |
| ), |
| MockClusterConfig(MockClusterId(4), {}, {}, {}, {}, MockClusterSide::kClient), |
| }, { |
| { kDeviceTypeId2, kDeviceTypeId2Version}, |
| }, {}, |
| EndpointComposition::kTree), |
| 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, |
| MockAttributeConfig( |
| kReadOnlyAttributeId, |
| ZCL_INT32U_ATTRIBUTE_TYPE, |
| MATTER_ATTRIBUTE_FLAG_READABLE | MATTER_ATTRIBUTE_FLAG_NULLABLE // NOTE: explicitly NOT MATTER_ATTRIBUTE_FLAG_WRITABLE |
| ) |
| }), |
| 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_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_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), |
| |
| // Special case handling |
| MockAttributeConfig(kAttributeIdReadOnly, ZCL_INT32S_ATTRIBUTE_TYPE, 0), |
| MockAttributeConfig(kAttributeIdTimedWrite, ZCL_INT32S_ATTRIBUTE_TYPE, MATTER_ATTRIBUTE_FLAG_WRITABLE | MATTER_ATTRIBUTE_FLAG_READABLE | MATTER_ATTRIBUTE_FLAG_MUST_USE_TIMED_WRITE ), |
| MockAttributeConfig(kAttributeIdFakeAllowsWrite, ZCL_INT32U_ATTRIBUTE_TYPE, 0), |
| }), |
| }), |
| MockEndpointConfig(kMockEndpoint4, { |
| MockClusterConfig(MockClusterId(4), { |
| ClusterRevision::Id, FeatureMap::Id, MockAttributeId(4), |
| MockAttributeConfig(Clusters::Descriptor::Attributes::EndpointUniqueID::Id, ZCL_CHAR_STRING_ATTRIBUTE_TYPE), |
| }), |
| }, { |
| { kDeviceTypeId4, kDeviceTypeId4Version }, |
| }, |
| {}, // Empty semantic tags |
| EndpointComposition::kTree, |
| chip::CharSpan("AABBCCDDEEFFGGHHIIJJKKLLMMNNOO01", strlen("AABBCCDDEEFFGGHHIIJJKKLLMMNNOO01")) // Add endpointUniqueID |
| ), |
| }); |
| // clang-format on |
| |
| struct UseMockNodeConfig |
| { |
| UseMockNodeConfig(const MockNodeConfig & config) { SetMockNodeConfig(config); } |
| ~UseMockNodeConfig() { ResetMockNodeConfig(); } |
| }; |
| |
| template <typename T> |
| CHIP_ERROR DecodeList(TLV::TLVReader & reader, std::vector<T> & 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(chip::app::DataModel::Decode(reader, value)); |
| out.emplace_back(std::move(value)); |
| } |
| } |
| |
| 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 &) override |
| { |
| if (static_cast<const ConcreteAttributePath &>(path) != mPath) |
| { |
| // returning without trying to handle means "I do not handle this" |
| return CHIP_NO_ERROR; |
| } |
| |
| return CHIP_IM_GLOBAL_STATUS(UnsupportedRead); |
| } |
| |
| CHIP_ERROR Write(const ConcreteDataAttributePath & path, AttributeValueDecoder &) override |
| { |
| if (static_cast<const ConcreteAttributePath &>(path) != mPath) |
| { |
| // returning without trying to handle means "I do not handle this" |
| return CHIP_NO_ERROR; |
| } |
| |
| return CHIP_IM_GLOBAL_STATUS(UnsupportedWrite); |
| } |
| |
| private: |
| ConcreteAttributePath mPath; |
| }; |
| |
| 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<const ConcreteAttributePath &>(path) != mPath) |
| { |
| // returning without trying to handle means "I do not handle this" |
| return CHIP_NO_ERROR; |
| } |
| |
| return encoder.Encode(mData); |
| } |
| |
| CHIP_ERROR Write(const ConcreteDataAttributePath & path, AttributeValueDecoder & decoder) override |
| { |
| if (static_cast<const ConcreteAttributePath &>(path) != mPath) |
| { |
| // returning without trying to handle means "I do not handle this" |
| return CHIP_NO_ERROR; |
| } |
| |
| return decoder.Decode(mData); |
| } |
| |
| void SetReturnedData(const Clusters::UnitTesting::Structs::SimpleStruct::Type & data) { mData = data; } |
| const Clusters::UnitTesting::Structs::SimpleStruct::Type & GetData() const { return mData; } |
| |
| private: |
| ConcreteAttributePath mPath; |
| Clusters::UnitTesting::Structs::SimpleStruct::Type mData; |
| }; |
| |
| class ErrorAccessInterface : public AttributeAccessInterface |
| { |
| public: |
| ErrorAccessInterface(ConcreteAttributePath path, CHIP_ERROR err) : |
| AttributeAccessInterface(MakeOptional(path.mEndpointId), path.mClusterId), mPath(path), mError(err) |
| {} |
| ~ErrorAccessInterface() = default; |
| |
| CHIP_ERROR Read(const ConcreteReadAttributePath & path, AttributeValueEncoder & encoder) override |
| { |
| if (static_cast<const ConcreteAttributePath &>(path) != mPath) |
| { |
| // returning without trying to handle means "I do not handle this" |
| return CHIP_NO_ERROR; |
| } |
| return mError; |
| } |
| |
| CHIP_ERROR Write(const ConcreteDataAttributePath & path, AttributeValueDecoder & decoder) override |
| { |
| if (static_cast<const ConcreteAttributePath &>(path) != mPath) |
| { |
| // returning without trying to handle means "I do not handle this" |
| return CHIP_NO_ERROR; |
| } |
| return mError; |
| } |
| |
| private: |
| ConcreteAttributePath mPath; |
| CHIP_ERROR mError; |
| }; |
| |
| 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<const ConcreteAttributePath &>(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<uint8_t>(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; } |
| |
| private: |
| ConcreteAttributePath mPath; |
| Clusters::UnitTesting::Structs::SimpleStruct::Type mData; |
| unsigned mCount = 0; |
| }; |
| |
| /// RAII registration of an attribute access interface |
| template <typename T> |
| class RegisteredAttributeAccessInterface |
| { |
| public: |
| template <typename... Args> |
| RegisteredAttributeAccessInterface(Args &&... args) : mData(std::forward<Args>(args)...) |
| { |
| VerifyOrDie(AttributeAccessInterfaceRegistry::Instance().Register(&mData)); |
| } |
| ~RegisteredAttributeAccessInterface() { AttributeAccessInterfaceRegistry::Instance().Unregister(&mData); } |
| |
| T * operator->() { return &mData; } |
| T & operator*() { return mData; } |
| |
| private: |
| T mData; |
| }; |
| |
| class FakeDefaultServerCluster : public DefaultServerCluster |
| { |
| public: |
| static constexpr uint32_t kFakeFeatureMap = 0x35; |
| static constexpr uint32_t kFakeClusterRevision = 1234; |
| |
| static constexpr CommandId kGeneratedCommands[] = { 1, 2, 3, 100, 200 }; |
| static constexpr AcceptedCommandEntry kAcceptedCommands[] = { |
| { 101 }, |
| { 102 }, |
| }; |
| |
| static constexpr AttributeEntry kExtraAttributes[] = { |
| AttributeEntry{ kAttributeIdReadOnly, BitFlags<AttributeQualityFlags>{}, Access::Privilege::kView, std::nullopt }, |
| AttributeEntry{ kAttributeIdTimedWrite, BitFlags<AttributeQualityFlags>{}, Access::Privilege::kView, |
| Access::Privilege::kOperate }, |
| AttributeEntry{ kAttributeIdFakeAllowsWrite, BitFlags<AttributeQualityFlags>{}, Access::Privilege::kView, |
| Access::Privilege::kOperate }, |
| }; |
| |
| constexpr FakeDefaultServerCluster(ConcreteClusterPath path) : DefaultServerCluster(std::move(path)) {} |
| |
| [[nodiscard]] BitFlags<DataModel::ClusterQualityFlags> GetClusterFlags(const ConcreteClusterPath &) const override |
| { |
| return DataModel::ClusterQualityFlags::kDiagnosticsData; |
| } |
| |
| CHIP_ERROR AcceptedCommands(const ConcreteClusterPath & path, |
| ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder) override |
| { |
| return builder.ReferenceExisting(Span<const AcceptedCommandEntry>(kAcceptedCommands)); |
| } |
| |
| CHIP_ERROR GeneratedCommands(const ConcreteClusterPath & path, ReadOnlyBufferBuilder<CommandId> & builder) override |
| { |
| return builder.ReferenceExisting(Span<const CommandId>(kGeneratedCommands)); |
| } |
| |
| std::optional<DataModel::ActionReturnStatus> InvokeCommand(const DataModel::InvokeRequest & request, |
| chip::TLV::TLVReader & input_arguments, |
| CommandHandler * handler) override |
| { |
| return CHIP_ERROR_INCORRECT_STATE; |
| } |
| |
| DataModel::ActionReturnStatus WriteAttribute(const DataModel::WriteAttributeRequest & request, |
| AttributeValueDecoder & decoder) override |
| { |
| if (request.path.mAttributeId == kAttributeIdFakeAllowsWrite) |
| { |
| uint32_t value; |
| return decoder.Decode(value); |
| } |
| return CHIP_ERROR_INCORRECT_STATE; |
| } |
| |
| CHIP_ERROR Attributes(const ConcreteClusterPath & path, ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder) override |
| { |
| ReturnErrorOnFailure(builder.ReferenceExisting(kExtraAttributes)); |
| return DefaultServerCluster::Attributes(path, builder); |
| } |
| |
| DataModel::ActionReturnStatus ReadAttribute(const DataModel::ReadAttributeRequest & request, |
| AttributeValueEncoder & encoder) override |
| { |
| using namespace chip::app::Clusters; |
| |
| switch (request.path.mAttributeId) |
| { |
| case Globals::Attributes::FeatureMap::Id: { |
| uint32_t value = kFakeFeatureMap; |
| return encoder.Encode<uint32_t>(std::move(value)); |
| } |
| case Globals::Attributes::ClusterRevision::Id: { |
| uint32_t value = kFakeClusterRevision; |
| return encoder.Encode<uint32_t>(std::move(value)); |
| } |
| case kAttributeIdFakeAllowsWrite: { |
| uint32_t value = 0; |
| return encoder.Encode<uint32_t>(std::move(value)); |
| } |
| } |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| |
| void TestIncreaseDataVersion() { IncreaseDataVersion(); } |
| void TestNotifyAttributeChanged(AttributeId attributeId) { NotifyAttributeChanged(attributeId); } |
| |
| CHIP_ERROR EventInfo(const ConcreteEventPath & path, DataModel::EventEntry & eventInfo) override |
| { |
| eventInfo.readPrivilege = mEventInfoFakePrivilege; |
| return CHIP_NO_ERROR; |
| } |
| |
| Access::Privilege mEventInfoFakePrivilege = Access::Privilege::kView; |
| }; |
| |
| template <typename T, EmberAfAttributeType ZclType> |
| void TestEmberScalarTypeRead(typename NumericAttributeTraits<T>::WorkingType value) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| ReadOperation testRequest(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZclType)); |
| testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| // Ember encoding for integers is IDENTICAL to the in-memory representation for them |
| typename NumericAttributeTraits<T>::StorageType storage; |
| NumericAttributeTraits<T>::WorkingToStorage(value, storage); |
| chip::Testing::SetEmberReadOutput(ByteSpan(reinterpret_cast<const uint8_t *>(&storage), sizeof(storage))); |
| |
| // Data read via the encoder |
| std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(); |
| ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR); |
| ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); |
| |
| // Validate after read |
| std::vector<DecodedAttributeData> attribute_data; |
| ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR); |
| ASSERT_EQ(attribute_data.size(), 1u); |
| |
| DecodedAttributeData & encodedData = attribute_data[0]; |
| ASSERT_EQ(encodedData.attributePath, testRequest.GetRequest().path); |
| |
| typename NumericAttributeTraits<T>::WorkingType actual; |
| ASSERT_EQ(chip::app::DataModel::Decode<typename NumericAttributeTraits<T>::WorkingType>(encodedData.dataReader, actual), |
| CHIP_NO_ERROR); |
| ASSERT_EQ(actual, value); |
| } |
| |
| template <typename T, EmberAfAttributeType ZclType> |
| void TestEmberScalarNullRead() |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| ReadOperation testRequest(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZclType)); |
| testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| // Ember encoding for integers is IDENTICAL to the in-memory representation for them |
| typename NumericAttributeTraits<T>::StorageType nullValue; |
| NumericAttributeTraits<T>::SetNull(nullValue); |
| chip::Testing::SetEmberReadOutput(ByteSpan(reinterpret_cast<const uint8_t *>(&nullValue), sizeof(nullValue))); |
| |
| // Data read via the encoder |
| std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(); |
| ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR); |
| ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); |
| |
| // Validate after read |
| std::vector<DecodedAttributeData> attribute_data; |
| ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR); |
| ASSERT_EQ(attribute_data.size(), 1u); |
| |
| DecodedAttributeData & encodedData = attribute_data[0]; |
| ASSERT_EQ(encodedData.attributePath, testRequest.GetRequest().path); |
| chip::app::DataModel::Nullable<typename NumericAttributeTraits<T>::WorkingType> actual; |
| ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR); |
| ASSERT_TRUE(actual.IsNull()); |
| } |
| |
| template <typename T, EmberAfAttributeType ZclType> |
| void TestEmberScalarTypeWrite(const typename NumericAttributeTraits<T>::WorkingType value) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| // non-nullable test |
| { |
| WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZclType)); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| AttributeValueDecoder decoder = test.DecoderFor(value); |
| |
| // write should succeed |
| ASSERT_TRUE(model.WriteAttribute(test.GetRequest(), decoder).IsSuccess()); |
| |
| // Validate data after write |
| chip::ByteSpan writtenData = chip::Testing::GetEmberBuffer(); |
| |
| typename NumericAttributeTraits<T>::StorageType storage; |
| ASSERT_GE(writtenData.size(), sizeof(storage)); |
| memcpy(&storage, writtenData.data(), sizeof(storage)); |
| typename NumericAttributeTraits<T>::WorkingType actual = NumericAttributeTraits<T>::StorageToWorking(storage); |
| |
| EXPECT_EQ(actual, value); |
| ASSERT_EQ(model.ChangeListener().DirtyList().size(), 1u); |
| EXPECT_EQ(model.ChangeListener().DirtyList()[0], |
| AttributePathParams(test.GetRequest().path.mEndpointId, test.GetRequest().path.mClusterId, |
| test.GetRequest().path.mAttributeId)); |
| |
| // reset for the next test |
| model.ChangeListener().DirtyList().clear(); |
| } |
| |
| // nullable test: write null to make sure content of buffer changed (otherwise it will be a noop for dirty checking) |
| { |
| WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZclType)); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| using NumericType = NumericAttributeTraits<T>; |
| using NullableType = chip::app::DataModel::Nullable<typename NumericType::WorkingType>; |
| AttributeValueDecoder decoder = test.DecoderFor<NullableType>(NullableType()); |
| |
| // write should succeed |
| ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR); |
| |
| // dirty: we changed the value to null |
| ASSERT_EQ(model.ChangeListener().DirtyList().size(), 1u); |
| EXPECT_EQ(model.ChangeListener().DirtyList()[0], |
| AttributePathParams(test.GetRequest().path.mEndpointId, test.GetRequest().path.mClusterId, |
| test.GetRequest().path.mAttributeId)); |
| } |
| |
| // nullable test |
| { |
| WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZclType)); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| AttributeValueDecoder decoder = test.DecoderFor(value); |
| |
| // write should succeed |
| ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR); |
| |
| // Validate data after write |
| chip::ByteSpan writtenData = chip::Testing::GetEmberBuffer(); |
| |
| typename NumericAttributeTraits<T>::StorageType storage; |
| ASSERT_GE(writtenData.size(), sizeof(storage)); |
| memcpy(&storage, writtenData.data(), sizeof(storage)); |
| typename NumericAttributeTraits<T>::WorkingType actual = NumericAttributeTraits<T>::StorageToWorking(storage); |
| |
| ASSERT_EQ(actual, value); |
| // dirty a 2nd time when we moved from null to a real value |
| ASSERT_EQ(model.ChangeListener().DirtyList().size(), 2u); |
| EXPECT_EQ(model.ChangeListener().DirtyList()[1], |
| AttributePathParams(test.GetRequest().path.mEndpointId, test.GetRequest().path.mClusterId, |
| test.GetRequest().path.mAttributeId)); |
| } |
| } |
| |
| template <typename T, EmberAfAttributeType ZclType> |
| void TestEmberScalarNullWrite() |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZclType)); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| using NumericType = NumericAttributeTraits<T>; |
| using NullableType = chip::app::DataModel::Nullable<typename NumericType::WorkingType>; |
| AttributeValueDecoder decoder = test.DecoderFor<NullableType>(NullableType()); |
| |
| // write should succeed |
| ASSERT_TRUE(model.WriteAttribute(test.GetRequest(), decoder).IsSuccess()); |
| |
| // Validate data after write |
| chip::ByteSpan writtenData = chip::Testing::GetEmberBuffer(); |
| |
| using Traits = NumericAttributeTraits<T>; |
| |
| typename Traits::StorageType storage; |
| ASSERT_GE(writtenData.size(), sizeof(storage)); |
| memcpy(&storage, writtenData.data(), sizeof(storage)); |
| ASSERT_TRUE(Traits::IsNullValue(storage)); |
| } |
| |
| template <typename T, EmberAfAttributeType ZclType> |
| void TestEmberScalarTypeWriteNullValueToNullable() |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZclType)); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| using NumericType = NumericAttributeTraits<T>; |
| using NullableType = chip::app::DataModel::Nullable<typename NumericType::WorkingType>; |
| AttributeValueDecoder decoder = test.DecoderFor<NullableType>(NullableType()); |
| |
| // write should fail: we are trying to write null |
| ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_ERROR_WRONG_TLV_TYPE); |
| } |
| |
| } // namespace |
| |
| uint16_t ReadLe16(const void * buffer) |
| { |
| const uint8_t * p = reinterpret_cast<const uint8_t *>(buffer); |
| return chip::Encoding::LittleEndian::Read16(p); |
| } |
| |
| void WriteLe16(void * buffer, uint16_t value) |
| { |
| uint8_t * p = reinterpret_cast<uint8_t *>(buffer); |
| chip::Encoding::LittleEndian::Write16(p, value); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, IterateOverEndpoints) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| |
| // This iteration relies on the hard-coding that occurs when mock_ember is used |
| ReadOnlyBufferBuilder<DataModel::EndpointEntry> endpointsBuilder; |
| |
| ASSERT_EQ(model.Endpoints(endpointsBuilder), CHIP_NO_ERROR); |
| |
| auto endpoints = endpointsBuilder.TakeBuffer(); |
| |
| ASSERT_EQ(endpoints.size(), 4u); |
| |
| EXPECT_EQ(endpoints[0].id, kMockEndpoint1); |
| EXPECT_EQ(endpoints[0].parentId, kInvalidEndpointId); |
| EXPECT_EQ(endpoints[0].compositionPattern, EndpointCompositionPattern::kFullFamily); |
| |
| EXPECT_EQ(endpoints[1].id, kMockEndpoint2); |
| EXPECT_EQ(endpoints[1].parentId, kInvalidEndpointId); |
| EXPECT_EQ(endpoints[1].compositionPattern, EndpointCompositionPattern::kTree); |
| |
| EXPECT_EQ(endpoints[2].id, kMockEndpoint3); |
| EXPECT_EQ(endpoints[2].parentId, kInvalidEndpointId); |
| EXPECT_EQ(endpoints[2].compositionPattern, EndpointCompositionPattern::kFullFamily); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, IterateOverServerClusters) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| |
| chip::Testing::ResetVersion(); |
| |
| ReadOnlyBufferBuilder<DataModel::ServerClusterEntry> builder; |
| |
| EXPECT_NE(model.ServerClusters(kEndpointIdThatIsMissing, builder), CHIP_NO_ERROR); |
| EXPECT_TRUE(builder.IsEmpty()); |
| EXPECT_NE(model.ServerClusters(kInvalidEndpointId, builder), CHIP_NO_ERROR); |
| EXPECT_TRUE(builder.IsEmpty()); |
| |
| // mock endpoint 1 has 2 mock clusters: 1 and 2 |
| EXPECT_EQ(model.ServerClusters(kMockEndpoint1, builder), CHIP_NO_ERROR); |
| auto serverClusters = builder.TakeBuffer(); |
| ASSERT_EQ(serverClusters.size(), 2u); |
| |
| EXPECT_EQ(serverClusters[0].clusterId, MockClusterId(1)); |
| EXPECT_EQ(serverClusters[0].dataVersion, 0u); |
| EXPECT_EQ(serverClusters[0].flags.Raw(), 0u); |
| |
| EXPECT_EQ(serverClusters[1].clusterId, MockClusterId(2)); |
| EXPECT_EQ(serverClusters[1].dataVersion, 0u); |
| EXPECT_EQ(serverClusters[1].flags.Raw(), 0u); |
| |
| chip::Testing::BumpVersion(); |
| |
| EXPECT_EQ(model.ServerClusters(kMockEndpoint1, builder), CHIP_NO_ERROR); |
| serverClusters = builder.TakeBuffer(); |
| ASSERT_EQ(serverClusters.size(), 2u); |
| EXPECT_EQ(serverClusters[0].dataVersion, 1u); |
| EXPECT_EQ(serverClusters[1].dataVersion, 1u); |
| |
| // mock endpoint 3 has 4 mock clusters: 1 through 4 |
| EXPECT_EQ(model.ServerClusters(kMockEndpoint3, builder), CHIP_NO_ERROR); |
| serverClusters = builder.TakeBuffer(); |
| ASSERT_EQ(serverClusters.size(), 4u); |
| EXPECT_EQ(serverClusters[0].clusterId, MockClusterId(1)); |
| EXPECT_EQ(serverClusters[1].clusterId, MockClusterId(2)); |
| EXPECT_EQ(serverClusters[2].clusterId, MockClusterId(3)); |
| EXPECT_EQ(serverClusters[3].clusterId, MockClusterId(4)); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, IterateOverClientClusters) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| |
| ReadOnlyBufferBuilder<ClusterId> builder; |
| |
| EXPECT_EQ(model.ClientClusters(kEndpointIdThatIsMissing, builder), CHIP_ERROR_NOT_FOUND); |
| EXPECT_TRUE(builder.IsEmpty()); |
| EXPECT_EQ(model.ClientClusters(kInvalidEndpointId, builder), CHIP_ERROR_NOT_FOUND); |
| EXPECT_TRUE(builder.IsEmpty()); |
| |
| // mock endpoint 1 has 2 mock client clusters: 3 and 4 |
| EXPECT_EQ(model.ClientClusters(kMockEndpoint1, builder), CHIP_NO_ERROR); |
| auto clientClusters = builder.TakeBuffer(); |
| |
| const ClusterId kExpectedClusters1[] = { MockClusterId(3), MockClusterId(4) }; |
| ASSERT_TRUE(clientClusters.data_equal(Span<const ClusterId>(kExpectedClusters1))); |
| |
| // mock endpoint 2 has 1 mock client clusters: 3(has server side at the same time) and 4 |
| EXPECT_EQ(model.ClientClusters(kMockEndpoint2, builder), CHIP_NO_ERROR); |
| clientClusters = builder.TakeBuffer(); |
| |
| const ClusterId kExpectedClusters2[] = { MockClusterId(3), MockClusterId(4) }; |
| ASSERT_TRUE(clientClusters.data_equal(Span<const ClusterId>(kExpectedClusters2))); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, IterateOverAttributes) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| |
| // invalid paths should return in "no more data" |
| ASSERT_TRUE(model.AttributesIgnoreError(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).empty()); |
| ASSERT_TRUE(model.AttributesIgnoreError(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).empty()); |
| ASSERT_TRUE(model.AttributesIgnoreError(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).empty()); |
| ASSERT_TRUE(model.AttributesIgnoreError(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).empty()); |
| |
| // should be able to iterate over valid paths |
| ReadOnlyBufferBuilder<DataModel::AttributeEntry> builder; |
| |
| // invalid paths return errors |
| ASSERT_EQ(model.Attributes(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1)), builder), CHIP_ERROR_NOT_FOUND); |
| ASSERT_EQ(model.Attributes(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1)), builder), CHIP_ERROR_NOT_FOUND); |
| ASSERT_EQ(model.Attributes(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10)), builder), CHIP_ERROR_NOT_FOUND); |
| ASSERT_EQ(model.Attributes(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId), builder), CHIP_ERROR_NOT_FOUND); |
| |
| EXPECT_EQ(model.Attributes(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2)), builder), CHIP_NO_ERROR); |
| auto attributes = builder.TakeBuffer(); |
| ASSERT_EQ(attributes.size(), 7u); |
| |
| ASSERT_EQ(attributes[0].attributeId, ClusterRevision::Id); |
| ASSERT_FALSE(attributes[0].HasFlags(AttributeQualityFlags::kListAttribute)); |
| |
| ASSERT_EQ(attributes[1].attributeId, FeatureMap::Id); |
| ASSERT_FALSE(attributes[1].HasFlags(AttributeQualityFlags::kListAttribute)); |
| |
| ASSERT_EQ(attributes[2].attributeId, MockAttributeId(1)); |
| ASSERT_FALSE(attributes[2].HasFlags(AttributeQualityFlags::kListAttribute)); |
| |
| ASSERT_EQ(attributes[3].attributeId, MockAttributeId(2)); |
| ASSERT_TRUE(attributes[3].HasFlags(AttributeQualityFlags::kListAttribute)); |
| |
| // Ends with global list attributes |
| ASSERT_EQ(attributes[4].attributeId, GeneratedCommandList::Id); |
| ASSERT_TRUE(attributes[4].HasFlags(AttributeQualityFlags::kListAttribute)); |
| |
| ASSERT_EQ(attributes[5].attributeId, AcceptedCommandList::Id); |
| ASSERT_TRUE(attributes[5].HasFlags(AttributeQualityFlags::kListAttribute)); |
| |
| ASSERT_EQ(attributes[6].attributeId, AttributeList::Id); |
| ASSERT_TRUE(attributes[6].HasFlags(AttributeQualityFlags::kListAttribute)); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, FindAttribute) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| |
| AttributeFinder finder(&model); |
| |
| // various non-existent or invalid paths should return no info data |
| ASSERT_FALSE(finder.Find(ConcreteAttributePath(kInvalidEndpointId, kInvalidClusterId, kInvalidAttributeId)).has_value()); |
| ASSERT_FALSE(finder.Find(ConcreteAttributePath(kInvalidEndpointId, kInvalidClusterId, FeatureMap::Id)).has_value()); |
| ASSERT_FALSE(finder.Find(ConcreteAttributePath(kInvalidEndpointId, MockClusterId(1), FeatureMap::Id)).has_value()); |
| ASSERT_FALSE(finder.Find(ConcreteAttributePath(kMockEndpoint1, kInvalidClusterId, FeatureMap::Id)).has_value()); |
| ASSERT_FALSE(finder.Find(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), FeatureMap::Id)).has_value()); |
| ASSERT_FALSE(finder.Find(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), kInvalidAttributeId)).has_value()); |
| ASSERT_FALSE(finder.Find(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))).has_value()); |
| |
| // valid info |
| std::optional<AttributeEntry> info1 = finder.Find(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), FeatureMap::Id)); |
| ASSERT_TRUE(info1.has_value()); |
| EXPECT_FALSE(info1->HasFlags(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) |
| |
| // Mocks always set everything as R/W with administrative privileges |
| EXPECT_EQ(info1->GetReadPrivilege(), chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access) |
| EXPECT_EQ(info1->GetWritePrivilege(), chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access) |
| |
| std::optional<AttributeEntry> info2 = finder.Find(ConcreteAttributePath(kMockEndpoint2, MockClusterId(2), MockAttributeId(2))); |
| ASSERT_TRUE(info2.has_value()); |
| EXPECT_TRUE(info2->HasFlags(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) |
| EXPECT_EQ(info2->GetReadPrivilege(), chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access) |
| EXPECT_EQ(info2->GetWritePrivilege(), chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access) |
| |
| // test a read-only attribute, which will not have a write privilege |
| std::optional<AttributeEntry> info3 = |
| finder.Find(ConcreteAttributePath(kMockEndpoint3, MockClusterId(3), kReadOnlyAttributeId)); |
| ASSERT_TRUE(info3.has_value()); |
| EXPECT_FALSE(info3->HasFlags(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) |
| EXPECT_EQ(info3->GetReadPrivilege(), chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access) |
| EXPECT_FALSE(info3->GetWritePrivilege().has_value()); // NOLINT(bugprone-unchecked-optional-access) |
| } |
| |
| // global attributes are EXPLICITLY supported |
| TEST_F(TestCodegenModelViaMocks, GlobalAttributeInfo) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| |
| AttributeFinder finder(&model); |
| |
| std::optional<AttributeEntry> info1 = finder.Find( |
| ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::GeneratedCommandList::Id)); |
| |
| ASSERT_TRUE(info1.has_value()); |
| |
| std::optional<AttributeEntry> info2 = |
| finder.Find(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::AttributeList::Id)); |
| ASSERT_TRUE(info2.has_value()); |
| |
| std::optional<AttributeEntry> info3 = finder.Find( |
| ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::AcceptedCommandList::Id)); |
| ASSERT_TRUE(info3.has_value()); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, IterateOverAcceptedCommands) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| |
| ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> builder; |
| |
| // invalid paths should return in "no more data" |
| ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1)), builder), |
| CHIP_ERROR_NOT_FOUND); |
| ASSERT_TRUE(builder.IsEmpty()); |
| ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1)), builder), CHIP_ERROR_NOT_FOUND); |
| ASSERT_TRUE(builder.IsEmpty()); |
| ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10)), builder), CHIP_ERROR_NOT_FOUND); |
| ASSERT_TRUE(builder.IsEmpty()); |
| ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId), builder), CHIP_ERROR_NOT_FOUND); |
| ASSERT_TRUE(builder.IsEmpty()); |
| |
| ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2)), builder), CHIP_NO_ERROR); |
| ASSERT_EQ(builder.Size(), 3u); |
| |
| auto cmds = builder.TakeBuffer(); |
| |
| // took ownership |
| ASSERT_EQ(builder.Size(), 0u); |
| ASSERT_TRUE(builder.IsEmpty()); |
| |
| ASSERT_EQ(cmds.size(), 3u); |
| ASSERT_EQ(cmds[0].commandId, 1u); |
| ASSERT_EQ(cmds[1].commandId, 2u); |
| ASSERT_EQ(cmds[2].commandId, 23u); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, IterateOverGeneratedCommands) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| |
| ReadOnlyBufferBuilder<CommandId> builder; |
| |
| // invalid paths should return in "no more data" |
| ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1)), builder), |
| CHIP_ERROR_NOT_FOUND); |
| ASSERT_TRUE(builder.IsEmpty()); |
| ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1)), builder), CHIP_ERROR_NOT_FOUND); |
| ASSERT_TRUE(builder.IsEmpty()); |
| ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10)), builder), CHIP_ERROR_NOT_FOUND); |
| ASSERT_TRUE(builder.IsEmpty()); |
| ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId), builder), CHIP_ERROR_NOT_FOUND); |
| ASSERT_TRUE(builder.IsEmpty()); |
| |
| // should be able to iterate over valid paths |
| ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2)), builder), CHIP_NO_ERROR); |
| auto cmds = builder.TakeBuffer(); |
| |
| const CommandId expectedCommands2[] = { 2, 10 }; |
| ASSERT_TRUE(cmds.data_equal(Span<const CommandId>(expectedCommands2))); |
| |
| ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint2, MockClusterId(3)), builder), CHIP_NO_ERROR); |
| cmds = builder.TakeBuffer(); |
| const CommandId expectedCommands3[] = { 4, 6 }; |
| ASSERT_TRUE(cmds.data_equal(Span<const CommandId>(expectedCommands3))); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, AcceptedGeneratedCommandsOnInvalidEndpoints) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| |
| // register a CHI on ALL endpoints |
| CustomListCommandHandler handler(chip::NullOptional, MockClusterId(1)); |
| handler.SetHandleCommands(true); |
| |
| ReadOnlyBufferBuilder<CommandId> generatedBuilder; |
| ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> acceptedBuilder; |
| |
| // valid endpoint will result in valid data (even though list is empty) |
| ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), generatedBuilder), CHIP_NO_ERROR); |
| ASSERT_TRUE(generatedBuilder.IsEmpty()); |
| ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), acceptedBuilder), CHIP_NO_ERROR); |
| ASSERT_TRUE(acceptedBuilder.IsEmpty()); |
| |
| // Invalid endpoint fails - we will get no commands there (even though CHI is registered) |
| ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1)), generatedBuilder), |
| CHIP_ERROR_NOT_FOUND); |
| ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1)), acceptedBuilder), |
| CHIP_ERROR_NOT_FOUND); |
| |
| // same for invalid cluster ID |
| ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(0x1123)), generatedBuilder), |
| CHIP_ERROR_NOT_FOUND); |
| ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(0x1123)), acceptedBuilder), |
| CHIP_ERROR_NOT_FOUND); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, AcceptedGeneratedCommandsOnInvalidEndpointsUsingShim) |
| { |
| |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| |
| // register a CHI on ALL endpoints |
| ShimCommandHandler handler(chip::NullOptional, MockClusterId(1)); |
| handler.SetHandleCommands(true); |
| |
| ReadOnlyBufferBuilder<CommandId> generatedBuilder; |
| ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> acceptedBuilder; |
| |
| // valid endpoint will result in valid data (even though list is empty) |
| ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), generatedBuilder), CHIP_NO_ERROR); |
| ASSERT_TRUE(generatedBuilder.IsEmpty()); |
| ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), acceptedBuilder), CHIP_NO_ERROR); |
| ASSERT_TRUE(acceptedBuilder.IsEmpty()); |
| |
| // Invalid endpoint fails - we will get no commands there (even though CHI is registered) |
| ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1)), generatedBuilder), |
| CHIP_ERROR_NOT_FOUND); |
| ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1)), acceptedBuilder), |
| CHIP_ERROR_NOT_FOUND); |
| |
| // same for invalid cluster ID |
| ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(0x1123)), generatedBuilder), |
| CHIP_ERROR_NOT_FOUND); |
| ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(0x1123)), acceptedBuilder), |
| CHIP_ERROR_NOT_FOUND); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, CommandHandlerInterfaceCommandHandling) |
| { |
| |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| |
| // Command handler interface is capable to override accepted and generated commands. |
| // Validate that these work |
| CustomListCommandHandler handler(MakeOptional(kMockEndpoint1), MockClusterId(1)); |
| |
| ReadOnlyBufferBuilder<CommandId> generatedBuilder; |
| ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> acceptedBuilder; |
| |
| // At this point, without overrides, there should be no accepted/generated commands |
| ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), generatedBuilder), CHIP_NO_ERROR); |
| ASSERT_TRUE(generatedBuilder.IsEmpty()); |
| ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), acceptedBuilder), CHIP_NO_ERROR); |
| ASSERT_TRUE(acceptedBuilder.IsEmpty()); |
| |
| handler.SetOverrideAccepted(true); |
| handler.SetOverrideGenerated(true); |
| |
| // with overrides, the list is still empty ... |
| ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), generatedBuilder), CHIP_NO_ERROR); |
| ASSERT_TRUE(generatedBuilder.IsEmpty()); |
| ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), acceptedBuilder), CHIP_NO_ERROR); |
| ASSERT_TRUE(acceptedBuilder.IsEmpty()); |
| |
| // set some overrides |
| handler.AcceptedVec().push_back({ 1234 }); |
| handler.AcceptedVec().push_back({ 999 }); |
| |
| handler.GeneratedVec().push_back(33); |
| |
| ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), acceptedBuilder), CHIP_NO_ERROR); |
| auto acceptedCommands = acceptedBuilder.TakeBuffer(); |
| |
| ASSERT_EQ(acceptedCommands.size(), 2u); |
| ASSERT_EQ(acceptedCommands[0].commandId, 1234u); |
| ASSERT_EQ(acceptedCommands[1].commandId, 999u); |
| |
| ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), generatedBuilder), CHIP_NO_ERROR); |
| auto generatedCommands = generatedBuilder.TakeBuffer(); |
| ASSERT_EQ(generatedCommands.size(), std::size_t{ 1 }); |
| const CommandId expectedGeneratedCommands[] = { 33 }; |
| ASSERT_TRUE(generatedCommands.data_equal(Span<const CommandId>(expectedGeneratedCommands))); |
| } |
| |
| ////// |
| TEST_F(TestCodegenModelViaMocks, ShimCommandHandlerInterfaceCommandHandling) |
| { |
| // This node configuration needs to partially match a cluster tree, using Clusters::Unittesting as a base |
| // clang-format off |
| using namespace Clusters::UnitTesting; |
| static const MockNodeConfig kNodeConfig({ |
| MockEndpointConfig(kMockEndpoint1, { |
| MockClusterConfig(Clusters::UnitTesting::Id, { |
| ClusterRevision::Id, FeatureMap::Id, |
| }), |
| MockClusterConfig(MockClusterId(2), { |
| ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), |
| }), |
| }) |
| }); |
| // clang-format on |
| |
| UseMockNodeConfig config(kNodeConfig); |
| |
| CodegenDataModelProviderWithContext model; |
| |
| // Command handler interface is capable to override accepted and generated commands. |
| // Validate that these work |
| ShimCommandHandler handler(MakeOptional(kMockEndpoint1), Clusters::UnitTesting::Id); |
| |
| ReadOnlyBufferBuilder<CommandId> generatedBuilder; |
| ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> acceptedBuilder; |
| |
| // At this point, without overrides, there should be no accepted/generated commands |
| ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, Clusters::UnitTesting::Id), generatedBuilder), |
| CHIP_NO_ERROR); |
| ASSERT_TRUE(generatedBuilder.IsEmpty()); |
| ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, Clusters::UnitTesting::Id), acceptedBuilder), |
| CHIP_NO_ERROR); |
| ASSERT_TRUE(acceptedBuilder.IsEmpty()); |
| |
| handler.SetOverrideAccepted(true); |
| handler.SetOverrideGenerated(true); |
| |
| // with overrides, the list is still empty ... |
| ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, Clusters::UnitTesting::Id), generatedBuilder), |
| CHIP_NO_ERROR); |
| ASSERT_TRUE(generatedBuilder.IsEmpty()); |
| ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, Clusters::UnitTesting::Id), acceptedBuilder), |
| CHIP_NO_ERROR); |
| ASSERT_TRUE(acceptedBuilder.IsEmpty()); |
| |
| // set some overrides |
| handler.AcceptedVec().push_back(Commands::Test::Id); |
| handler.AcceptedVec().push_back(Commands::TestNotHandled::Id); |
| |
| handler.GeneratedVec().push_back(Commands::TestSpecific::Id); |
| |
| ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, Clusters::UnitTesting::Id), acceptedBuilder), |
| CHIP_NO_ERROR); |
| auto acceptedCommands = acceptedBuilder.TakeBuffer(); |
| |
| ASSERT_EQ(acceptedCommands.size(), 2u); |
| ASSERT_EQ(acceptedCommands[0].commandId, Commands::Test::Id); |
| ASSERT_EQ(acceptedCommands[1].commandId, Commands::TestNotHandled::Id); |
| |
| ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, Clusters::UnitTesting::Id), generatedBuilder), |
| CHIP_NO_ERROR); |
| auto generatedCommands = generatedBuilder.TakeBuffer(); |
| ASSERT_EQ(generatedCommands.size(), 1u); |
| |
| const CommandId expectedGeneratedCommands[] = { Commands::TestSpecific::Id }; |
| ASSERT_TRUE(generatedCommands.data_equal(Span<const CommandId>(expectedGeneratedCommands))); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, AccessInterfaceUnsupportedRead) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| const ConcreteAttributePath kTestPath(kMockEndpoint3, MockClusterId(4), |
| MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE)); |
| |
| ReadOperation testRequest(kTestPath); |
| testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| testRequest.SetPathExpanded(true); |
| |
| RegisteredAttributeAccessInterface<UnsupportedReadAccessInterface> aai(kTestPath); |
| |
| // 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<AttributeValueEncoder> encoder = testRequest.StartEncoding(); |
| ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR); |
| ASSERT_FALSE(encoder->TriedEncode()); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt32S) |
| { |
| TestEmberScalarTypeRead<int32_t, ZCL_INT32S_ATTRIBUTE_TYPE>(-1234); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeReadEnum16) |
| { |
| TestEmberScalarTypeRead<uint16_t, ZCL_ENUM16_ATTRIBUTE_TYPE>(0x1234); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeReadFloat) |
| { |
| TestEmberScalarTypeRead<float, ZCL_SINGLE_ATTRIBUTE_TYPE>(0.625); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeReadDouble) |
| { |
| TestEmberScalarTypeRead<double, ZCL_DOUBLE_ATTRIBUTE_TYPE>(0.625); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt24U) |
| { |
| TestEmberScalarTypeRead<OddSizedInteger<3, false>, ZCL_INT24U_ATTRIBUTE_TYPE>(0x1234AB); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt32U) |
| { |
| TestEmberScalarTypeRead<uint32_t, ZCL_INT32U_ATTRIBUTE_TYPE>(0x1234ABCD); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt40U) |
| { |
| TestEmberScalarTypeRead<OddSizedInteger<5, false>, ZCL_INT40U_ATTRIBUTE_TYPE>(0x1122334455); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt48U) |
| { |
| TestEmberScalarTypeRead<OddSizedInteger<6, false>, ZCL_INT48U_ATTRIBUTE_TYPE>(0xAABB11223344); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt56U) |
| { |
| TestEmberScalarTypeRead<OddSizedInteger<7, false>, ZCL_INT56U_ATTRIBUTE_TYPE>(0xAABB11223344); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeReadBool) |
| { |
| TestEmberScalarTypeRead<bool, ZCL_BOOLEAN_ATTRIBUTE_TYPE>(true); |
| TestEmberScalarTypeRead<bool, ZCL_BOOLEAN_ATTRIBUTE_TYPE>(false); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt8U) |
| { |
| TestEmberScalarTypeRead<uint8_t, ZCL_INT8U_ATTRIBUTE_TYPE>(0x12); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeReadNulls) |
| { |
| TestEmberScalarNullRead<uint8_t, ZCL_INT8U_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullRead<uint16_t, ZCL_INT16U_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullRead<OddSizedInteger<3, false>, ZCL_INT24U_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullRead<uint32_t, ZCL_INT32U_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullRead<OddSizedInteger<5, false>, ZCL_INT40U_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullRead<OddSizedInteger<6, false>, ZCL_INT48U_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullRead<OddSizedInteger<7, false>, ZCL_INT56U_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullRead<uint64_t, ZCL_INT64U_ATTRIBUTE_TYPE>(); |
| |
| TestEmberScalarNullRead<int8_t, ZCL_INT8S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullRead<int16_t, ZCL_INT16S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullRead<OddSizedInteger<3, true>, ZCL_INT24S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullRead<int32_t, ZCL_INT32S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullRead<OddSizedInteger<5, true>, ZCL_INT40S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullRead<OddSizedInteger<6, true>, ZCL_INT48S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullRead<OddSizedInteger<7, true>, ZCL_INT56S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullRead<int64_t, ZCL_INT64S_ATTRIBUTE_TYPE>(); |
| |
| TestEmberScalarNullRead<bool, ZCL_BOOLEAN_ATTRIBUTE_TYPE>(); |
| |
| TestEmberScalarNullRead<float, ZCL_SINGLE_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullRead<double, ZCL_DOUBLE_ATTRIBUTE_TYPE>(); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeReadErrorReading) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| { |
| ReadOperation testRequest(kMockEndpoint3, MockClusterId(4), |
| MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE)); |
| testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| chip::Testing::SetEmberReadOutput(Protocols::InteractionModel::Status::Failure); |
| |
| // Actual read via an encoder |
| std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(); |
| ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), Status::Failure); |
| } |
| |
| { |
| ReadOperation testRequest(kMockEndpoint3, MockClusterId(4), |
| MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE)); |
| testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| chip::Testing::SetEmberReadOutput(Protocols::InteractionModel::Status::Busy); |
| |
| // Actual read via an encoder |
| std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(); |
| ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), Status::Busy); |
| } |
| |
| // reset things to success to not affect other tests |
| chip::Testing::SetEmberReadOutput(ByteSpan()); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeReadNullOctetString) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| ReadOperation testRequest(kMockEndpoint3, MockClusterId(4), |
| MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE)); |
| testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| // 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::Testing::SetEmberReadOutput(ByteSpan(reinterpret_cast<const uint8_t *>(data), sizeof(data))); |
| |
| // Actual read via an encoder |
| std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(); |
| ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR); |
| ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); |
| |
| // Validate after read |
| std::vector<DecodedAttributeData> attribute_data; |
| ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR); |
| ASSERT_EQ(attribute_data.size(), 1u); |
| |
| DecodedAttributeData & encodedData = attribute_data[0]; |
| ASSERT_EQ(encodedData.attributePath, testRequest.GetRequest().path); |
| |
| // data element should be null for the given 0xFFFF length |
| ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Null); |
| |
| chip::app::DataModel::Nullable<ByteSpan> actual; |
| ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR); |
| ASSERT_TRUE(actual.IsNull()); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeReadOctetString) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| ReadOperation testRequest(kMockEndpoint3, MockClusterId(4), |
| MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE)); |
| testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| // 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"; |
| WriteLe16(data, 4); |
| chip::Testing::SetEmberReadOutput(ByteSpan(reinterpret_cast<const uint8_t *>(data), sizeof(data))); |
| |
| // Actual read via an encoder |
| std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(); |
| ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR); |
| ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); |
| |
| // Validate after read |
| std::vector<DecodedAttributeData> attribute_data; |
| ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR); |
| ASSERT_EQ(attribute_data.size(), 1u); |
| |
| const DecodedAttributeData & encodedData = attribute_data[0]; |
| ASSERT_EQ(encodedData.attributePath, testRequest.GetRequest().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<const uint8_t *>(data + 2), 4); |
| ASSERT_TRUE(actual.data_equal(expected)); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeReadLongOctetString) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| ReadOperation testRequest(kMockEndpoint3, MockClusterId(4), |
| MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE)); |
| testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| // 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::Testing::SetEmberReadOutput(ByteSpan(reinterpret_cast<const uint8_t *>(data), sizeof(data))); |
| |
| // Actual read via an encoder |
| std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(); |
| ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR); |
| ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); |
| |
| // Validate after read |
| std::vector<DecodedAttributeData> attribute_data; |
| ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR); |
| ASSERT_EQ(attribute_data.size(), 1u); |
| |
| const DecodedAttributeData & encodedData = attribute_data[0]; |
| ASSERT_EQ(encodedData.attributePath, testRequest.GetRequest().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<const uint8_t *>(data + 1), 4); |
| ASSERT_TRUE(actual.data_equal(expected)); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeReadShortString) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| ReadOperation testRequest(kMockEndpoint3, MockClusterId(4), |
| MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_CHAR_STRING_ATTRIBUTE_TYPE)); |
| testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| // 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"; |
| *data = 5; |
| chip::Testing::SetEmberReadOutput(ByteSpan(reinterpret_cast<const uint8_t *>(data), sizeof(data))); |
| |
| // Actual read via an encoder |
| std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(); |
| ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR); |
| ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); |
| |
| // Validate after reading |
| std::vector<DecodedAttributeData> attribute_data; |
| ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR); |
| ASSERT_EQ(attribute_data.size(), 1u); |
| |
| const DecodedAttributeData & encodedData = attribute_data[0]; |
| ASSERT_EQ(encodedData.attributePath, testRequest.GetRequest().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_F(TestCodegenModelViaMocks, EmberAttributeReadLongString) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| ReadOperation testRequest(kMockEndpoint3, MockClusterId(4), |
| MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE)); |
| testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| // 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"; |
| WriteLe16(data, 5); |
| chip::Testing::SetEmberReadOutput(ByteSpan(reinterpret_cast<const uint8_t *>(data), sizeof(data))); |
| |
| // Actual read via an encoder |
| std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(); |
| ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR); |
| ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); |
| |
| // Validate after reading |
| std::vector<DecodedAttributeData> attribute_data; |
| ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR); |
| ASSERT_EQ(attribute_data.size(), 1u); |
| |
| const DecodedAttributeData & encodedData = attribute_data[0]; |
| ASSERT_EQ(encodedData.attributePath, testRequest.GetRequest().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_F(TestCodegenModelViaMocks, AttributeAccessInterfaceStructRead) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4), |
| MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE)); |
| |
| ReadOperation testRequest(kStructPath); |
| testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| RegisteredAttributeAccessInterface<StructAttributeAccessInterface> 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<AttributeValueEncoder> encoder = testRequest.StartEncoding(); |
| ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR); |
| ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); |
| |
| // Validate after read |
| std::vector<DecodedAttributeData> attribute_data; |
| ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR); |
| ASSERT_EQ(attribute_data.size(), 1u); |
| |
| DecodedAttributeData & encodedData = attribute_data[0]; |
| ASSERT_EQ(encodedData.attributePath, testRequest.GetRequest().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_F(TestCodegenModelViaMocks, AttributeAccessInterfaceReadError) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4), |
| MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE)); |
| |
| ReadOperation testRequest(kStructPath); |
| testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| RegisteredAttributeAccessInterface<ErrorAccessInterface> aai(kStructPath, CHIP_ERROR_KEY_NOT_FOUND); |
| std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(); |
| ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_ERROR_KEY_NOT_FOUND); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, AttributeAccessInterfaceListRead) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4), |
| MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_ARRAY_ATTRIBUTE_TYPE)); |
| |
| ReadOperation testRequest(kStructPath); |
| testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| RegisteredAttributeAccessInterface<ListAttributeAcessInterface> 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<AttributeValueEncoder> encoder = testRequest.StartEncoding(); |
| ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR); |
| ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); |
| |
| // Validate after read |
| std::vector<DecodedAttributeData> attribute_data; |
| ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR); |
| ASSERT_EQ(attribute_data.size(), 1u); |
| |
| DecodedAttributeData & encodedData = attribute_data[0]; |
| ASSERT_EQ(encodedData.attributePath, testRequest.GetRequest().path); |
| |
| ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Array); |
| |
| std::vector<Clusters::UnitTesting::Structs::SimpleStruct::DecodableType> 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<uint8_t>(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_F(TestCodegenModelViaMocks, AttributeAccessInterfaceListOverflowRead) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4), |
| MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_ARRAY_ATTRIBUTE_TYPE)); |
| |
| ReadOperation testRequest(kStructPath); |
| testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| RegisteredAttributeAccessInterface<ListAttributeAcessInterface> 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<AttributeValueEncoder> encoder = testRequest.StartEncoding(); |
| // 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.GetRequest(), *encoder), CHIP_ERROR_BUFFER_TOO_SMALL); |
| ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); |
| |
| // Validate after read |
| std::vector<DecodedAttributeData> attribute_data; |
| ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR); |
| ASSERT_EQ(attribute_data.size(), 1u); |
| |
| DecodedAttributeData & encodedData = attribute_data[0]; |
| ASSERT_EQ(encodedData.attributePath, testRequest.GetRequest().path); |
| |
| ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Array); |
| |
| std::vector<Clusters::UnitTesting::Structs::SimpleStruct::DecodableType> 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<uint8_t>(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_F(TestCodegenModelViaMocks, AttributeAccessInterfaceListIncrementalRead) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4), |
| MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_ARRAY_ATTRIBUTE_TYPE)); |
| |
| ReadOperation testRequest(kStructPath); |
| testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| RegisteredAttributeAccessInterface<ListAttributeAcessInterface> 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<AttributeValueEncoder> encoder = |
| testRequest.StartEncoding(ReadOperation::EncodingParams().SetEncodingState(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.GetRequest(), *encoder), CHIP_ERROR_BUFFER_TOO_SMALL); |
| ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); |
| |
| // Validate after read |
| std::vector<DecodedAttributeData> attribute_data; |
| ASSERT_EQ(testRequest.GetEncodedIBs().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.GetRequest().path.mEndpointId); |
| ASSERT_EQ(encodedData.attributePath.mClusterId, testRequest.GetRequest().path.mClusterId); |
| ASSERT_EQ(encodedData.attributePath.mAttributeId, testRequest.GetRequest().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<uint8_t>((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)); |
| } |
| } |
| |
| // reading attributes is a LOT of boilerplate. This just makes tests more readable. |
| static CHIP_ERROR ReadU32Attribute(DataModel::Provider & provider, const ConcreteAttributePath & path, uint32_t & value) |
| { |
| |
| ReadOperation testRequest(path); |
| testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(); |
| ReturnErrorOnFailure(provider.ReadAttribute(testRequest.GetRequest(), *encoder).GetUnderlyingError()); |
| ReturnErrorOnFailure(testRequest.FinishEncoding()); |
| |
| std::vector<DecodedAttributeData> attribute_data; |
| |
| ReturnErrorOnFailure(testRequest.GetEncodedIBs().Decode(attribute_data)); |
| VerifyOrReturnError(attribute_data.size() == 1u, CHIP_ERROR_INCORRECT_STATE); |
| |
| DecodedAttributeData & encodedData = attribute_data[0]; |
| |
| VerifyOrReturnError(encodedData.attributePath == testRequest.GetRequest().path, CHIP_ERROR_INCORRECT_STATE); |
| |
| return chip::app::DataModel::Decode<uint32_t>(encodedData.dataReader, value); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, AttributeAccessInterfaceTakesPrecedenceOverServerClusterInterface) |
| { |
| // For backwards compatibility, we want AAI requests to be sent first and override SCI |
| // The test verifies this by adding a "error-out" AAI that overrides success SCI |
| TestServerClusterContext testContext; |
| |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProvider model; |
| |
| model.SetPersistentStorageDelegate(&testContext.StorageDelegate()); |
| ASSERT_EQ(model.Startup(testContext.ImContext()), CHIP_NO_ERROR); |
| |
| // It is important to have kTestClusterPath be valid ember paths (so we have metadata for them) |
| const ConcreteClusterPath kTestClusterPath(kMockEndpoint3, MockClusterId(4)); |
| const ConcreteAttributePath kTestAttributePath(kTestClusterPath.mEndpointId, kTestClusterPath.mClusterId, |
| kAttributeIdFakeAllowsWrite); |
| FakeDefaultServerCluster fakeClusterServer(kTestClusterPath); |
| ServerClusterRegistration registration(fakeClusterServer); |
| |
| // SCI registered for kTestClusterPath, and this will work by a R/W for kTestAttributePath |
| ASSERT_EQ(model.Registry().Register(registration), CHIP_NO_ERROR); |
| |
| { |
| // AAI registered that fails R/W |
| RegisteredAttributeAccessInterface<UnsupportedReadAccessInterface> aai(kTestAttributePath); |
| |
| // Reads fail because AAI |
| uint32_t value = 0; |
| ASSERT_EQ(ReadU32Attribute(model, kTestAttributePath, value), CHIP_IM_GLOBAL_STATUS(UnsupportedRead)); |
| |
| // Writes fail because AAI. |
| WriteOperation test(kTestAttributePath); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| AttributeValueDecoder decoder = test.DecoderFor(value); |
| |
| ASSERT_FALSE(model.WriteAttribute(test.GetRequest(), decoder).IsSuccess()); |
| } |
| |
| { |
| // now that AAI is out of the picture, SCI will read/write things ok |
| uint32_t value = 0; |
| ASSERT_EQ(ReadU32Attribute(model, kTestAttributePath, value), CHIP_NO_ERROR); |
| |
| WriteOperation test(kTestAttributePath); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| AttributeValueDecoder decoder = test.DecoderFor(value); |
| |
| // write should succeed |
| ASSERT_TRUE(model.WriteAttribute(test.GetRequest(), decoder).IsSuccess()); |
| } |
| |
| EXPECT_SUCCESS(model.Registry().Unregister(&fakeClusterServer)); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, AAISkippedIfNoEmberMetadata) |
| { |
| // For backwards compatibility, we want AAI requests to be sent first and override SCI |
| // The test verifies this by adding a "error-out" AAI that overrides success SCI |
| TestServerClusterContext testContext; |
| |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProvider model; |
| |
| model.SetPersistentStorageDelegate(&testContext.StorageDelegate()); |
| ASSERT_EQ(model.Startup(testContext.ImContext()), CHIP_NO_ERROR); |
| |
| // These paths are NOT valid for AAI, so AAI is skipped |
| const ConcreteClusterPath kTestClusterPath(kMockEndpoint1, MockClusterId(1)); |
| const ConcreteAttributePath kTestAttributePath(kTestClusterPath.mEndpointId, kTestClusterPath.mClusterId, |
| kAttributeIdFakeAllowsWrite); |
| FakeDefaultServerCluster fakeClusterServer(kTestClusterPath); |
| ServerClusterRegistration registration(fakeClusterServer); |
| |
| // SCI registered for kTestClusterPath, and this will work by a R/W for kTestAttributePath |
| ASSERT_EQ(model.Registry().Register(registration), CHIP_NO_ERROR); |
| |
| { |
| // AAI registered that fails R/W, however because ember metadata path is not valid |
| // this AAI does NOT take effect |
| RegisteredAttributeAccessInterface<UnsupportedReadAccessInterface> aai(kTestAttributePath); |
| |
| // now that AAI is out of the picture, SCI will read/write things ok |
| uint32_t value = 0; |
| ASSERT_EQ(ReadU32Attribute(model, kTestAttributePath, value), CHIP_NO_ERROR); |
| |
| WriteOperation test(kTestAttributePath); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| AttributeValueDecoder decoder = test.DecoderFor(value); |
| |
| // write should succeed |
| ASSERT_TRUE(model.WriteAttribute(test.GetRequest(), decoder).IsSuccess()); |
| } |
| |
| EXPECT_SUCCESS(model.Registry().Unregister(&fakeClusterServer)); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteBasicTypes) |
| { |
| TestEmberScalarTypeWrite<uint8_t, ZCL_INT8U_ATTRIBUTE_TYPE>(0x12); |
| TestEmberScalarTypeWrite<uint16_t, ZCL_ENUM16_ATTRIBUTE_TYPE>(0x1234); |
| TestEmberScalarTypeWrite<OddSizedInteger<3, false>, ZCL_INT24U_ATTRIBUTE_TYPE>(0x112233); |
| TestEmberScalarTypeWrite<uint32_t, ZCL_INT32U_ATTRIBUTE_TYPE>(0x11223344); |
| TestEmberScalarTypeWrite<OddSizedInteger<5, false>, ZCL_INT40U_ATTRIBUTE_TYPE>(0x1122334455ULL); |
| TestEmberScalarTypeWrite<OddSizedInteger<6, false>, ZCL_INT48U_ATTRIBUTE_TYPE>(0x112233445566ULL); |
| TestEmberScalarTypeWrite<OddSizedInteger<7, false>, ZCL_INT56U_ATTRIBUTE_TYPE>(0x11223344556677ULL); |
| TestEmberScalarTypeWrite<uint64_t, ZCL_INT64U_ATTRIBUTE_TYPE>(0x1122334455667788ULL); |
| |
| TestEmberScalarTypeWrite<int8_t, ZCL_INT8S_ATTRIBUTE_TYPE>(-10); |
| TestEmberScalarTypeWrite<int16_t, ZCL_INT16S_ATTRIBUTE_TYPE>(-123); |
| TestEmberScalarTypeWrite<OddSizedInteger<3, true>, ZCL_INT24S_ATTRIBUTE_TYPE>(-1234); |
| TestEmberScalarTypeWrite<int32_t, ZCL_INT32S_ATTRIBUTE_TYPE>(-12345); |
| TestEmberScalarTypeWrite<OddSizedInteger<5, true>, ZCL_INT40S_ATTRIBUTE_TYPE>(-123456); |
| TestEmberScalarTypeWrite<OddSizedInteger<6, true>, ZCL_INT48S_ATTRIBUTE_TYPE>(-1234567); |
| TestEmberScalarTypeWrite<OddSizedInteger<7, true>, ZCL_INT56S_ATTRIBUTE_TYPE>(-12345678); |
| TestEmberScalarTypeWrite<int64_t, ZCL_INT64S_ATTRIBUTE_TYPE>(-123456789); |
| |
| TestEmberScalarTypeWrite<bool, ZCL_BOOLEAN_ATTRIBUTE_TYPE>(true); |
| TestEmberScalarTypeWrite<bool, ZCL_BOOLEAN_ATTRIBUTE_TYPE>(false); |
| TestEmberScalarTypeWrite<float, ZCL_SINGLE_ATTRIBUTE_TYPE>(0.625); |
| TestEmberScalarTypeWrite<double, ZCL_DOUBLE_ATTRIBUTE_TYPE>(0.625); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteInvalidValueToNullable) |
| { |
| TestEmberScalarTypeWriteNullValueToNullable<uint8_t, ZCL_INT8U_ATTRIBUTE_TYPE>(); |
| TestEmberScalarTypeWriteNullValueToNullable<uint16_t, ZCL_ENUM16_ATTRIBUTE_TYPE>(); |
| TestEmberScalarTypeWriteNullValueToNullable<OddSizedInteger<3, false>, ZCL_INT24U_ATTRIBUTE_TYPE>(); |
| TestEmberScalarTypeWriteNullValueToNullable<uint32_t, ZCL_INT32U_ATTRIBUTE_TYPE>(); |
| TestEmberScalarTypeWriteNullValueToNullable<OddSizedInteger<5, false>, ZCL_INT40U_ATTRIBUTE_TYPE>(); |
| TestEmberScalarTypeWriteNullValueToNullable<OddSizedInteger<6, false>, ZCL_INT48U_ATTRIBUTE_TYPE>(); |
| TestEmberScalarTypeWriteNullValueToNullable<OddSizedInteger<7, false>, ZCL_INT56U_ATTRIBUTE_TYPE>(); |
| TestEmberScalarTypeWriteNullValueToNullable<uint64_t, ZCL_INT64U_ATTRIBUTE_TYPE>(); |
| |
| TestEmberScalarTypeWriteNullValueToNullable<int8_t, ZCL_INT8S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarTypeWriteNullValueToNullable<int16_t, ZCL_INT16S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarTypeWriteNullValueToNullable<OddSizedInteger<3, true>, ZCL_INT24S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarTypeWriteNullValueToNullable<int32_t, ZCL_INT32S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarTypeWriteNullValueToNullable<OddSizedInteger<5, true>, ZCL_INT40S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarTypeWriteNullValueToNullable<OddSizedInteger<6, true>, ZCL_INT48S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarTypeWriteNullValueToNullable<OddSizedInteger<7, true>, ZCL_INT56S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarTypeWriteNullValueToNullable<int64_t, ZCL_INT64S_ATTRIBUTE_TYPE>(); |
| |
| TestEmberScalarTypeWriteNullValueToNullable<bool, ZCL_BOOLEAN_ATTRIBUTE_TYPE>(); |
| TestEmberScalarTypeWriteNullValueToNullable<bool, ZCL_BOOLEAN_ATTRIBUTE_TYPE>(); |
| TestEmberScalarTypeWriteNullValueToNullable<float, ZCL_SINGLE_ATTRIBUTE_TYPE>(); |
| TestEmberScalarTypeWriteNullValueToNullable<double, ZCL_DOUBLE_ATTRIBUTE_TYPE>(); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberTestWriteReservedNullPlaceholderToNullable) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_INT32U_ATTRIBUTE_TYPE)); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| using NumericType = NumericAttributeTraits<uint32_t>; |
| using NullableType = chip::app::DataModel::Nullable<typename NumericType::WorkingType>; |
| AttributeValueDecoder decoder = test.DecoderFor<NullableType>(0xFFFFFFFF); |
| |
| // write should fail: we are trying to write null which is out of range |
| ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::ConstraintError); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberTestWriteOutOfRepresentableRangeOddIntegerNonNullable) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_INT24U_ATTRIBUTE_TYPE)); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| using NumericType = NumericAttributeTraits<uint32_t>; |
| using NullableType = chip::app::DataModel::Nullable<typename NumericType::WorkingType>; |
| AttributeValueDecoder decoder = test.DecoderFor<NullableType>(0x1223344); |
| |
| ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_IM_GLOBAL_STATUS(ConstraintError)); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberTestWriteOutOfRepresentableRangeOddIntegerNullable) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_INT24U_ATTRIBUTE_TYPE)); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| using NumericType = NumericAttributeTraits<uint32_t>; |
| using NullableType = chip::app::DataModel::Nullable<typename NumericType::WorkingType>; |
| AttributeValueDecoder decoder = test.DecoderFor<NullableType>(0x1223344); |
| |
| ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_IM_GLOBAL_STATUS(ConstraintError)); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteBasicTypesLowestValue) |
| { |
| TestEmberScalarTypeWrite<int8_t, ZCL_INT8S_ATTRIBUTE_TYPE>(-127); |
| TestEmberScalarTypeWrite<int16_t, ZCL_INT16S_ATTRIBUTE_TYPE>(-32767); |
| TestEmberScalarTypeWrite<OddSizedInteger<3, true>, ZCL_INT24S_ATTRIBUTE_TYPE>(-8388607); |
| TestEmberScalarTypeWrite<int32_t, ZCL_INT32S_ATTRIBUTE_TYPE>(-2147483647); |
| TestEmberScalarTypeWrite<OddSizedInteger<5, true>, ZCL_INT40S_ATTRIBUTE_TYPE>(-549755813887); |
| TestEmberScalarTypeWrite<OddSizedInteger<6, true>, ZCL_INT48S_ATTRIBUTE_TYPE>(-140737488355327); |
| TestEmberScalarTypeWrite<OddSizedInteger<7, true>, ZCL_INT56S_ATTRIBUTE_TYPE>(-36028797018963967); |
| TestEmberScalarTypeWrite<int64_t, ZCL_INT64S_ATTRIBUTE_TYPE>(-9223372036854775807); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteNulls) |
| { |
| TestEmberScalarNullWrite<uint8_t, ZCL_INT8U_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullWrite<uint16_t, ZCL_ENUM16_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullWrite<OddSizedInteger<3, false>, ZCL_INT24U_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullWrite<uint32_t, ZCL_INT32U_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullWrite<OddSizedInteger<5, false>, ZCL_INT40U_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullWrite<OddSizedInteger<6, false>, ZCL_INT48U_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullWrite<OddSizedInteger<7, false>, ZCL_INT56U_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullWrite<uint64_t, ZCL_INT64U_ATTRIBUTE_TYPE>(); |
| |
| TestEmberScalarNullWrite<int8_t, ZCL_INT8S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullWrite<int16_t, ZCL_INT16S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullWrite<OddSizedInteger<3, true>, ZCL_INT24S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullWrite<int32_t, ZCL_INT32S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullWrite<OddSizedInteger<5, true>, ZCL_INT40S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullWrite<OddSizedInteger<6, true>, ZCL_INT48S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullWrite<OddSizedInteger<7, true>, ZCL_INT56S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullWrite<int64_t, ZCL_INT64S_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullWrite<bool, ZCL_BOOLEAN_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullWrite<float, ZCL_SINGLE_ATTRIBUTE_TYPE>(); |
| TestEmberScalarNullWrite<double, ZCL_DOUBLE_ATTRIBUTE_TYPE>(); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteShortString) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_CHAR_STRING_ATTRIBUTE_TYPE)); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| AttributeValueDecoder decoder = test.DecoderFor<CharSpan>("hello world"_span); |
| |
| ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR); |
| chip::ByteSpan writtenData = GetEmberBuffer(); |
| chip::CharSpan asCharSpan(reinterpret_cast<const char *>(writtenData.data()), writtenData[0] + 1); |
| ASSERT_TRUE(asCharSpan.data_equal("\x0Bhello world"_span)); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteLongStringOutOfBounds) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| WriteOperation test(kMockEndpoint3, MockClusterId(4), |
| MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE)); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| // Mocks allow for 16 bytes only by default for string attributes |
| AttributeValueDecoder decoder = test.DecoderFor<CharSpan>( |
| "this is a very long string that will be longer than the default attribute size for our mocks"_span); |
| |
| ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::InvalidValue); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteLongString) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| WriteOperation test(kMockEndpoint3, MockClusterId(4), |
| MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE)); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| AttributeValueDecoder decoder = test.DecoderFor<CharSpan>("text"_span); |
| |
| ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR); |
| chip::ByteSpan writtenData = GetEmberBuffer(); |
| |
| uint16_t len = ReadLe16(writtenData.data()); |
| EXPECT_EQ(len, 4); |
| chip::CharSpan asCharSpan(reinterpret_cast<const char *>(writtenData.data() + 2), 4); |
| |
| ASSERT_TRUE(asCharSpan.data_equal("text"_span)); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteNullableLongStringValue) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE)); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| AttributeValueDecoder decoder = |
| test.DecoderFor<chip::app::DataModel::Nullable<CharSpan>>(chip::app::DataModel::MakeNullable("text"_span)); |
| |
| ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR); |
| chip::ByteSpan writtenData = GetEmberBuffer(); |
| |
| uint16_t len = ReadLe16(writtenData.data()); |
| EXPECT_EQ(len, 4); |
| chip::CharSpan asCharSpan(reinterpret_cast<const char *>(writtenData.data() + 2), 4); |
| |
| ASSERT_TRUE(asCharSpan.data_equal("text"_span)); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteLongNullableStringNull) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE)); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| AttributeValueDecoder decoder = |
| test.DecoderFor<chip::app::DataModel::Nullable<CharSpan>>(chip::app::DataModel::Nullable<CharSpan>()); |
| |
| ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR); |
| chip::ByteSpan writtenData = GetEmberBuffer(); |
| ASSERT_EQ(writtenData[0], 0xFF); |
| ASSERT_EQ(writtenData[1], 0xFF); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteShortBytes) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE)); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| uint8_t buffer[] = { 11, 12, 13 }; |
| |
| AttributeValueDecoder decoder = test.DecoderFor<ByteSpan>(ByteSpan(buffer)); |
| |
| ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR); |
| chip::ByteSpan writtenData = GetEmberBuffer(); |
| |
| EXPECT_EQ(writtenData[0], 3u); |
| EXPECT_EQ(writtenData[1], 11u); |
| EXPECT_EQ(writtenData[2], 12u); |
| EXPECT_EQ(writtenData[3], 13u); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteLongBytes) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| WriteOperation test(kMockEndpoint3, MockClusterId(4), |
| MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE)); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| uint8_t buffer[] = { 11, 12, 13 }; |
| |
| AttributeValueDecoder decoder = test.DecoderFor<ByteSpan>(ByteSpan(buffer)); |
| |
| ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR); |
| chip::ByteSpan writtenData = GetEmberBuffer(); |
| |
| uint16_t len = ReadLe16(writtenData.data()); |
| EXPECT_EQ(len, 3); |
| |
| EXPECT_EQ(writtenData[2], 11u); |
| EXPECT_EQ(writtenData[3], 12u); |
| EXPECT_EQ(writtenData[4], 13u); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberWriteFailure) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_INT32S_ATTRIBUTE_TYPE)); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| { |
| AttributeValueDecoder decoder = test.DecoderFor<int32_t>(1234); |
| chip::Testing::SetEmberReadOutput(Protocols::InteractionModel::Status::Failure); |
| ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::Failure); |
| } |
| { |
| AttributeValueDecoder decoder = test.DecoderFor<int32_t>(1234); |
| chip::Testing::SetEmberReadOutput(Protocols::InteractionModel::Status::Busy); |
| ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::Busy); |
| } |
| // reset things to success to not affect other tests |
| chip::Testing::SetEmberReadOutput(ByteSpan()); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberWriteAttributeAccessInterfaceTest) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4), |
| MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE)); |
| RegisteredAttributeAccessInterface<StructAttributeAccessInterface> aai(kStructPath); |
| |
| WriteOperation test(kStructPath); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| Clusters::UnitTesting::Structs::SimpleStruct::Type testValue{ |
| .a = 112, |
| .b = true, |
| .e = "aai_write_test"_span, |
| .g = 0.5, |
| .h = 0.125, |
| }; |
| |
| AttributeValueDecoder decoder = test.DecoderFor(testValue); |
| ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(aai->GetData().a, 112); |
| EXPECT_TRUE(aai->GetData().e.data_equal("aai_write_test"_span)); |
| |
| // AAI marks dirty paths |
| ASSERT_EQ(model.ChangeListener().DirtyList().size(), 1u); |
| EXPECT_EQ(model.ChangeListener().DirtyList()[0], |
| AttributePathParams(kStructPath.mEndpointId, kStructPath.mClusterId, kStructPath.mAttributeId)); |
| |
| // AAI does not prevent read/write of regular attributes |
| // validate that once AAI is added, we still can go through writing regular bits (i.e. |
| // AAI returning "unknown" has fallback to ember) |
| TestEmberScalarTypeWrite<uint32_t, ZCL_INT32U_ATTRIBUTE_TYPE>(4321); |
| TestEmberScalarNullWrite<int64_t, ZCL_INT64S_ATTRIBUTE_TYPE>(); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberInvokeTest) |
| { |
| // Ember invoke is fully code-generated - there is a single function for Dispatch |
| // that will do a `switch` on the path elements and invoke a corresponding `emberAf*` |
| // callback. |
| // |
| // The only thing that can be validated is that this `DispatchSingleClusterCommand` |
| // is actually invoked. |
| |
| UseMockNodeConfig config(gTestNodeConfig); |
| chip::app::CodegenDataModelProvider model; |
| |
| { |
| const ConcreteCommandPath kCommandPath(kMockEndpoint1, MockClusterId(1), kMockCommandId1); |
| const InvokeRequest kInvokeRequest{ .path = kCommandPath }; |
| chip::TLV::TLVReader tlvReader; |
| |
| const uint32_t kDispatchCountPre = chip::Testing::DispatchCount(); |
| |
| // Using a handler set to nullptr as it is not used by the impl |
| ASSERT_EQ(model.InvokeCommand(kInvokeRequest, tlvReader, /* handler = */ nullptr), std::nullopt); |
| |
| EXPECT_EQ(chip::Testing::DispatchCount(), kDispatchCountPre + 1); // single dispatch |
| EXPECT_EQ(chip::Testing::GetLastDispatchPath(), kCommandPath); // for the right path |
| } |
| |
| { |
| const ConcreteCommandPath kCommandPath(kMockEndpoint1, MockClusterId(1), kMockCommandId2); |
| const InvokeRequest kInvokeRequest{ .path = kCommandPath }; |
| chip::TLV::TLVReader tlvReader; |
| |
| const uint32_t kDispatchCountPre = chip::Testing::DispatchCount(); |
| |
| // Using a handler set to nullpotr as it is not used by the impl |
| ASSERT_EQ(model.InvokeCommand(kInvokeRequest, tlvReader, /* handler = */ nullptr), std::nullopt); |
| |
| EXPECT_EQ(chip::Testing::DispatchCount(), kDispatchCountPre + 1); // single dispatch |
| EXPECT_EQ(chip::Testing::GetLastDispatchPath(), kCommandPath); // for the right path |
| } |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberWriteAttributeAccessInterfaceReturningError) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4), |
| MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE)); |
| RegisteredAttributeAccessInterface<ErrorAccessInterface> aai(kStructPath, CHIP_ERROR_KEY_NOT_FOUND); |
| |
| WriteOperation test(kStructPath); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| Clusters::UnitTesting::Structs::SimpleStruct::Type testValue{ |
| .a = 112, |
| .b = true, |
| .e = "aai_write_test"_span, |
| .g = 0.5, |
| .h = 0.125, |
| }; |
| |
| AttributeValueDecoder decoder = test.DecoderFor(testValue); |
| ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_ERROR_KEY_NOT_FOUND); |
| ASSERT_TRUE(model.ChangeListener().DirtyList().empty()); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EmberWriteInvalidDataType) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| ScopedMockAccessControl accessControl; |
| |
| const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4), |
| MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE)); |
| |
| WriteOperation test(kStructPath); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| Clusters::UnitTesting::Structs::SimpleStruct::Type testValue{ |
| .a = 112, |
| .b = true, |
| .e = "aai_write_test"_span, |
| .g = 0.5, |
| .h = 0.125, |
| }; |
| |
| AttributeValueDecoder decoder = test.DecoderFor(testValue); |
| |
| // Embed specifically DOES NOT support structures. |
| // Without AAI, we expect a data type error (translated to failure) |
| ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::Failure); |
| ASSERT_TRUE(model.ChangeListener().DirtyList().empty()); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, DeviceTypeIteration) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| |
| // Mock endpoint 1 has 3 device types |
| ReadOnlyBufferBuilder<DataModel::DeviceTypeEntry> builder; |
| ASSERT_EQ(model.DeviceTypes(kMockEndpoint1, builder), CHIP_NO_ERROR); |
| auto deviceTypes = builder.TakeBuffer(); |
| ASSERT_EQ(deviceTypes.size(), 3u); |
| |
| const DeviceTypeEntry expected1[] = { |
| { .deviceTypeId = kDeviceTypeId1, .deviceTypeRevision = kDeviceTypeId1Version }, |
| { .deviceTypeId = kDeviceTypeId2, .deviceTypeRevision = kDeviceTypeId2Version }, |
| { .deviceTypeId = kDeviceTypeId3, .deviceTypeRevision = kDeviceTypeId3Version }, |
| }; |
| for (unsigned i = 0; i < 3; i++) |
| { |
| ASSERT_EQ(deviceTypes[i], expected1[i]); |
| } |
| |
| // Mock endpoint 2 has 1 device types |
| ASSERT_TRUE(builder.IsEmpty()); // ownership taken above, we start fresh |
| ASSERT_EQ(model.DeviceTypes(kMockEndpoint2, builder), CHIP_NO_ERROR); |
| deviceTypes = builder.TakeBuffer(); |
| ASSERT_EQ(deviceTypes.size(), 1u); |
| const DeviceTypeEntry expected2 = { .deviceTypeId = kDeviceTypeId2, .deviceTypeRevision = kDeviceTypeId2Version }; |
| ASSERT_EQ(deviceTypes[0], expected2); |
| |
| // empty endpoint works |
| ASSERT_TRUE(builder.IsEmpty()); // ownership taken above, we start fresh |
| ASSERT_EQ(model.DeviceTypes(kMockEndpoint3, builder), CHIP_NO_ERROR); |
| ASSERT_TRUE(builder.IsEmpty()); |
| ASSERT_TRUE(builder.TakeBuffer().empty()); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, ServerClusterInterfacesWrite) |
| { |
| TestServerClusterContext testContext; |
| |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProvider model; |
| |
| model.SetPersistentStorageDelegate(&testContext.StorageDelegate()); |
| ASSERT_EQ(model.Startup(testContext.ImContext()), CHIP_NO_ERROR); |
| |
| const ConcreteClusterPath kTestClusterPath(kMockEndpoint1, MockClusterId(2)); |
| FakeDefaultServerCluster fakeClusterServer(kTestClusterPath); |
| ServerClusterRegistration registration(fakeClusterServer); |
| |
| ASSERT_EQ(model.Registry().Register(registration), CHIP_NO_ERROR); |
| |
| // Write just works |
| { |
| WriteOperation test(kTestClusterPath.mEndpointId, kTestClusterPath.mClusterId, kAttributeIdFakeAllowsWrite); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| AttributeValueDecoder decoder = test.DecoderFor<uint32_t>(1234); |
| |
| std::optional<ActionReturnStatus> result = model.WriteAttribute(test.GetRequest(), decoder); |
| ASSERT_TRUE(result.has_value() && result->IsSuccess()); |
| } |
| |
| // Write with a data version works |
| { |
| WriteOperation test(kTestClusterPath.mEndpointId, kTestClusterPath.mClusterId, kAttributeIdFakeAllowsWrite); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| test.SetDataVersion(MakeOptional(fakeClusterServer.GetDataVersion(kTestClusterPath))); |
| |
| AttributeValueDecoder decoder = test.DecoderFor<uint32_t>(1234); |
| |
| std::optional<ActionReturnStatus> result = model.WriteAttribute(test.GetRequest(), decoder); |
| ASSERT_TRUE(result.has_value() && result->IsSuccess()); |
| } |
| |
| EXPECT_SUCCESS(model.Registry().Unregister(&fakeClusterServer)); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, ServerClusterInterfacesRead) |
| { |
| TestServerClusterContext testContext; |
| |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProvider model; |
| |
| model.SetPersistentStorageDelegate(&testContext.StorageDelegate()); |
| ASSERT_EQ(model.Startup(testContext.ImContext()), CHIP_NO_ERROR); |
| |
| const ConcreteClusterPath kTestClusterPath(kMockEndpoint1, MockClusterId(2)); |
| FakeDefaultServerCluster fakeClusterServer(kTestClusterPath); |
| ServerClusterRegistration registration(fakeClusterServer); |
| |
| ASSERT_EQ(model.Registry().Register(registration), CHIP_NO_ERROR); |
| |
| // Read just works |
| { |
| |
| uint32_t value; |
| ASSERT_EQ(ReadU32Attribute( |
| model, { kTestClusterPath.mEndpointId, kTestClusterPath.mClusterId, kAttributeIdFakeAllowsWrite }, value), |
| CHIP_NO_ERROR); |
| } |
| |
| EXPECT_SUCCESS(model.Registry().Unregister(&fakeClusterServer)); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, ServerClusterInterfacesRegistration) |
| { |
| TestServerClusterContext testContext; |
| |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProvider model; |
| |
| model.SetPersistentStorageDelegate(&testContext.StorageDelegate()); |
| ASSERT_EQ(model.Startup(testContext.ImContext()), CHIP_NO_ERROR); |
| |
| const ConcreteClusterPath kTestClusterPath(kMockEndpoint1, MockClusterId(2)); |
| |
| FakeDefaultServerCluster fakeClusterServer(kTestClusterPath); |
| ServerClusterRegistration registration(fakeClusterServer); |
| |
| uint32_t revisionEmber; |
| ASSERT_EQ(ReadU32Attribute( |
| model, |
| { kTestClusterPath.mEndpointId, kTestClusterPath.mClusterId, Clusters::Globals::Attributes::ClusterRevision::Id }, |
| revisionEmber), |
| CHIP_NO_ERROR); |
| EXPECT_EQ(revisionEmber, 0u); |
| static_assert(FakeDefaultServerCluster::kFakeClusterRevision != 0); |
| |
| ASSERT_EQ(model.Registry().Register(registration), CHIP_NO_ERROR); |
| |
| uint32_t revision; |
| ASSERT_EQ(ReadU32Attribute( |
| model, |
| { kTestClusterPath.mEndpointId, kTestClusterPath.mClusterId, Clusters::Globals::Attributes::ClusterRevision::Id }, |
| revision), |
| CHIP_NO_ERROR); |
| EXPECT_EQ(revision, FakeDefaultServerCluster::kFakeClusterRevision); |
| |
| // now that registration looks ok and DIFFERENT from ember, invoke various methods on the registered cluster |
| // to ensure behavior is redirected correctly |
| { |
| ReadOnlyBufferBuilder<AttributeEntry> builder; |
| ASSERT_EQ(model.Attributes(kTestClusterPath, builder), CHIP_NO_ERROR); |
| |
| ReadOnlyBufferBuilder<AttributeEntry> expectedEntriesBuilder; |
| ASSERT_EQ(expectedEntriesBuilder.AppendElements(FakeDefaultServerCluster::kExtraAttributes), CHIP_NO_ERROR); |
| ASSERT_EQ(expectedEntriesBuilder.AppendElements(DefaultServerCluster::GlobalAttributes()), CHIP_NO_ERROR); |
| |
| // Attributes will be just global attributes |
| ASSERT_TRUE(expectedEntriesBuilder.TakeBuffer().data_equal(builder.TakeBuffer())); |
| } |
| |
| { |
| ReadOnlyBufferBuilder<AcceptedCommandEntry> builder; |
| ASSERT_EQ(model.AcceptedCommands(kTestClusterPath, builder), CHIP_NO_ERROR); |
| ASSERT_TRUE(Span<const AcceptedCommandEntry>(FakeDefaultServerCluster::kAcceptedCommands).data_equal(builder.TakeBuffer())); |
| } |
| |
| { |
| ReadOnlyBufferBuilder<CommandId> builder; |
| ASSERT_EQ(model.GeneratedCommands(kTestClusterPath, builder), CHIP_NO_ERROR); |
| ASSERT_TRUE(Span<const CommandId>(FakeDefaultServerCluster::kGeneratedCommands).data_equal(builder.TakeBuffer())); |
| } |
| |
| // Invoke specifically on the fake server returns a unique (and non-spec really) error |
| // so we can see the right method is called. |
| { |
| const ConcreteCommandPath kCommandPath(kTestClusterPath.mEndpointId, kTestClusterPath.mClusterId, kMockCommandId1); |
| const InvokeRequest kInvokeRequest{ .path = kCommandPath }; |
| chip::TLV::TLVReader tlvReader; |
| |
| // Using a handler set to nullptr as it is not used by the impl |
| std::optional<ActionReturnStatus> result = model.InvokeCommand(kInvokeRequest, tlvReader, /* handler = */ nullptr); |
| ASSERT_TRUE(result.has_value() && result->GetUnderlyingError() == CHIP_ERROR_INCORRECT_STATE); |
| } |
| |
| // Write attribute also has a specific error to know the right code is called |
| { |
| WriteOperation test(kTestClusterPath.mEndpointId, kTestClusterPath.mClusterId, kAttributeIdTimedWrite); |
| test.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| AttributeValueDecoder decoder = test.DecoderFor<uint32_t>(1234); |
| |
| std::optional<ActionReturnStatus> result = model.WriteAttribute(test.GetRequest(), decoder); |
| ASSERT_TRUE(result.has_value() && result->GetUnderlyingError() == CHIP_ERROR_INCORRECT_STATE); |
| } |
| |
| EXPECT_SUCCESS(model.Registry().Unregister(&fakeClusterServer)); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, EventInfo) |
| { |
| // Test that we format the event info correctly |
| TestServerClusterContext testContext; |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| |
| // Mock models always set event privilege to admin. |
| EventEntry entry; |
| ASSERT_EQ(model.EventInfo({ kMockEndpoint1, MockClusterId(1), kTestEventId }, entry), CHIP_NO_ERROR); |
| ASSERT_EQ(entry.readPrivilege, Access::Privilege::kAdminister); |
| ASSERT_EQ(model.EventInfo({ kMockEndpoint1, MockClusterId(2), kTestEventId }, entry), CHIP_NO_ERROR); |
| ASSERT_EQ(entry.readPrivilege, Access::Privilege::kAdminister); |
| |
| const ConcreteClusterPath kTestClusterPath(kMockEndpoint1, MockClusterId(2)); |
| FakeDefaultServerCluster fakeClusterServer(kTestClusterPath); |
| ServerClusterRegistration registration(fakeClusterServer); |
| ASSERT_EQ(model.Registry().Register(registration), CHIP_NO_ERROR); |
| |
| fakeClusterServer.mEventInfoFakePrivilege = Access::Privilege::kOperate; |
| ASSERT_EQ(model.EventInfo({ kMockEndpoint1, MockClusterId(2), kTestEventId }, entry), CHIP_NO_ERROR); |
| ASSERT_EQ(entry.readPrivilege, Access::Privilege::kOperate); |
| |
| fakeClusterServer.mEventInfoFakePrivilege = Access::Privilege::kView; |
| ASSERT_EQ(model.EventInfo({ kMockEndpoint1, MockClusterId(2), kTestEventId }, entry), CHIP_NO_ERROR); |
| ASSERT_EQ(entry.readPrivilege, Access::Privilege::kView); |
| |
| // the other cluster is unchanged |
| ASSERT_EQ(model.EventInfo({ kMockEndpoint1, MockClusterId(1), kTestEventId }, entry), CHIP_NO_ERROR); |
| ASSERT_EQ(entry.readPrivilege, Access::Privilege::kAdminister); |
| |
| EXPECT_SUCCESS(model.Registry().Unregister(&fakeClusterServer)); |
| |
| // once unregistered, go back to the default |
| ASSERT_EQ(model.EventInfo({ kMockEndpoint1, MockClusterId(2), kTestEventId }, entry), CHIP_NO_ERROR); |
| ASSERT_EQ(entry.readPrivilege, Access::Privilege::kAdminister); |
| } |
| |
| TEST_F(TestCodegenModelViaMocks, ServerClusterInterfacesListClusters) |
| { |
| TestServerClusterContext testContext; |
| |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProvider model; |
| |
| model.SetPersistentStorageDelegate(&testContext.StorageDelegate()); |
| ASSERT_EQ(model.Startup(testContext.ImContext()), CHIP_NO_ERROR); |
| |
| // will register a fake cluster server which overrides the cluster data version |
| // once registered |
| const ConcreteClusterPath kTestClusterPath(kMockEndpoint1, MockClusterId(2)); |
| FakeDefaultServerCluster fakeClusterServer(kTestClusterPath); |
| ServerClusterRegistration registration(fakeClusterServer); |
| |
| // ensure ember and fake server do not ramdomly point to the same data |
| // version as we use this data version to differentiate between the two |
| DataVersion * versionPtr = emberAfDataVersionStorage(kTestClusterPath); |
| ASSERT_NE(versionPtr, nullptr); |
| if (*versionPtr == fakeClusterServer.GetDataVersion(kTestClusterPath)) |
| { |
| fakeClusterServer.TestIncreaseDataVersion(); |
| } |
| |
| ReadOnlyBufferBuilder<DataModel::ServerClusterEntry> builder; |
| |
| ASSERT_EQ(model.ServerClusters(kTestClusterPath.mEndpointId, builder), CHIP_NO_ERROR); |
| std::vector<ServerClusterEntry> originalClusters; |
| for (auto entry : builder.TakeBuffer()) |
| { |
| originalClusters.push_back(entry); |
| } |
| |
| ASSERT_EQ(model.Registry().Register(registration), CHIP_NO_ERROR); |
| |
| ASSERT_EQ(model.ServerClusters(kTestClusterPath.mEndpointId, builder), CHIP_NO_ERROR); |
| std::vector<ServerClusterEntry> afterRegistrationClusters; |
| for (auto entry : builder.TakeBuffer()) |
| { |
| afterRegistrationClusters.push_back(entry); |
| } |
| |
| // lists MUST be identical EXCEPT data version will be different |
| // EMBER has one data version, clusters have a random (different) one |
| EXPECT_EQ(originalClusters.size(), afterRegistrationClusters.size()); |
| std::sort(originalClusters.begin(), originalClusters.end(), |
| [](const ServerClusterEntry & a, const ServerClusterEntry & b) { return a.clusterId < b.clusterId; }); |
| std::sort(afterRegistrationClusters.begin(), afterRegistrationClusters.end(), |
| [](const ServerClusterEntry & a, const ServerClusterEntry & b) { return a.clusterId < b.clusterId; }); |
| |
| bool updatedClusterFound = false; |
| |
| for (size_t i = 0; i < originalClusters.size(); i++) |
| { |
| const ServerClusterEntry & original = originalClusters[i]; |
| const ServerClusterEntry & registered = afterRegistrationClusters[i]; |
| |
| if (original.clusterId == kTestClusterPath.mClusterId) |
| { |
| updatedClusterFound = true; |
| EXPECT_EQ(registered.clusterId, original.clusterId); |
| EXPECT_EQ(registered.dataVersion, fakeClusterServer.GetDataVersion(kTestClusterPath)); |
| EXPECT_EQ(registered.flags, fakeClusterServer.GetClusterFlags(kTestClusterPath)); |
| |
| // version MUST be different for ember for the test to make sense |
| EXPECT_NE(original.dataVersion, registered.dataVersion); |
| } |
| else |
| { |
| EXPECT_EQ(original.clusterId, registered.clusterId); |
| EXPECT_EQ(original.dataVersion, registered.dataVersion); |
| EXPECT_EQ(original.flags, registered.flags); |
| } |
| } |
| EXPECT_TRUE(updatedClusterFound); |
| |
| EXPECT_SUCCESS(model.Registry().Unregister(&fakeClusterServer)); |
| EXPECT_SUCCESS(model.Shutdown()); |
| } |
| #if CHIP_CONFIG_USE_ENDPOINT_UNIQUE_ID |
| TEST_F(TestCodegenModelViaMocks, EndpointUniqueID) |
| { |
| UseMockNodeConfig config(gTestNodeConfig); |
| CodegenDataModelProviderWithContext model; |
| |
| // Mock endpoint 1 has a unique ID |
| char buffer[chip::app::Clusters::Descriptor::Attributes::EndpointUniqueID::TypeInfo::MaxLength()] = { 0 }; |
| MutableCharSpan span(buffer); |
| // Mock endpoint 4 has a unique ID |
| // ASSERT_TRUE(builder.IsEmpty()); // ownership taken above, we start fresh |
| ASSERT_EQ(model.EndpointUniqueID(kMockEndpoint4, span), CHIP_NO_ERROR); |
| EXPECT_TRUE(span.data_equal("AABBCCDDEEFFGGHHIIJJKKLLMMNNOO01"_span)); |
| } |
| #endif |