/* * Copyright (c) 2024 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace chip; using namespace chip::Test; using namespace chip::app; using namespace chip::app::Testing; using namespace chip::app::DataModel; using namespace chip::app::Clusters::Globals::Attributes; using chip::Protocols::InteractionModel::Status; namespace { constexpr AttributeId kAttributeIdReadOnly = 0x3001; constexpr AttributeId kAttributeIdTimedWrite = 0x3002; 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; static_assert(kEndpointIdThatIsMissing != kInvalidEndpointId); static_assert(kEndpointIdThatIsMissing != kMockEndpoint1); static_assert(kEndpointIdThatIsMissing != kMockEndpoint2); static_assert(kEndpointIdThatIsMissing != kMockEndpoint3); bool operator==(const Access::SubjectDescriptor & a, const Access::SubjectDescriptor & b) { if (a.fabricIndex != b.fabricIndex) { return false; } if (a.authMode != b.authMode) { return false; } if (a.subject != b.subject) { return false; } for (unsigned i = 0; i < a.cats.values.size(); i++) { if (a.cats.values[i] != b.cats.values[i]) { return false; } } return true; } class TestProviderChangeListener : public ProviderChangeListener { public: void MarkDirty(const AttributePathParams & path) override { mDirtyList.push_back(path); } std::vector & DirtyList() { return mDirtyList; } const std::vector & DirtyList() const { return mDirtyList; } private: std::vector mDirtyList; }; class TestEventGenerator : public EventsGenerator { CHIP_ERROR GenerateEvent(EventLoggingDelegate * eventPayloadWriter, const EventOptions & options, EventNumber & generatedEventNumber) override { return CHIP_ERROR_NOT_IMPLEMENTED; } }; class TestActionContext : public ActionContext { public: Messaging::ExchangeContext * CurrentExchange() override { return nullptr; } }; class CodegenDataModelProviderWithContext : public CodegenDataModelProvider { public: CodegenDataModelProviderWithContext() { InteractionModelContext context{ .eventsGenerator = &mEventGenerator, .dataModelChangeListener = &mChangeListener, .actionContext = &mActionContext, }; Startup(context); } ~CodegenDataModelProviderWithContext() { Shutdown(); } TestProviderChangeListener & ChangeListener() { return mChangeListener; } const TestProviderChangeListener & ChangeListener() const { return mChangeListener; } private: TestEventGenerator mEventGenerator; TestProviderChangeListener mChangeListener; TestActionContext mActionContext; }; 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; } }; /// 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, ClusterId clusterId) : CommandHandlerInterface(endpointId, clusterId) { CommandHandlerInterfaceRegistry::Instance().RegisterCommandHandler(this); } ~CustomListCommandHandler() { CommandHandlerInterfaceRegistry::Instance().UnregisterCommandHandler(this); } void InvokeCommand(HandlerContext & handlerContext) override { handlerContext.SetCommandNotHandled(); } 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 o) { mOverrideAccepted = o; } void SetOverrideGenerated(bool o) { mOverrideGenerated = o; } std::vector & AcceptedVec() { return mAccepted; } std::vector & GeneratedVec() { return mGenerated; } private: bool mOverrideAccepted = false; bool mOverrideGenerated = false; std::vector mAccepted; std::vector mGenerated; }; class ScopedMockAccessControl { public: ScopedMockAccessControl() { Access::GetAccessControl().Init(&mMock, mMock); } ~ScopedMockAccessControl() { Access::GetAccessControl().Finish(); } private: MockAccessControl mMock; }; #define MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(zcl_type) MockAttributeId(zcl_type + 0x1000) #define MOCK_ATTRIBUTE_CONFIG_NULLABLE(zcl_type) \ MockAttributeConfig(MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(zcl_type), zcl_type, ATTRIBUTE_MASK_WRITABLE | ATTRIBUTE_MASK_NULLABLE) #define MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(zcl_type) MockAttributeId(zcl_type + 0x2000) #define MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(zcl_type) \ MockAttributeConfig(MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(zcl_type), zcl_type, ATTRIBUTE_MASK_WRITABLE) // clang-format off const MockNodeConfig gTestNodeConfig({ MockEndpointConfig(kMockEndpoint1, { MockClusterConfig(MockClusterId(1), { ClusterRevision::Id, FeatureMap::Id, }, { MockEventId(1), MockEventId(2), }), MockClusterConfig(MockClusterId(2), { ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), }), }, { { kDeviceTypeId1, kDeviceTypeId1Version}, { kDeviceTypeId2, kDeviceTypeId2Version}, { kDeviceTypeId3, kDeviceTypeId3Version}, }), 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 */ ), }, { { kDeviceTypeId2, kDeviceTypeId2Version}, }), 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, ATTRIBUTE_MASK_NULLABLE // NOTE: explicltly NOT ATTRIBUTE_MASK_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_POWER_MW_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENERGY_MWH_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TOD_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DATE_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EPOCH_US_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EPOCH_S_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_POSIX_MS_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_PERCENT_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_PERCENT100THS_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_CLUSTER_ID_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ATTRIB_ID_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_FIELD_ID_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EVENT_ID_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_COMMAND_ID_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ACTION_ID_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TRANS_ID_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_NODE_ID_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPADR_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPV4ADR_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPV6ADR_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPV6PRE_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_HWADR_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BOOLEAN_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP8_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP16_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP32_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP64_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT8U_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT16U_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT24U_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT32U_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT40U_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT48U_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT56U_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT64U_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT8S_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT16S_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT24S_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT32S_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT40S_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT48S_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT56S_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT64S_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENUM8_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENUM16_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_PRIORITY_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_STATUS_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SINGLE_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DOUBLE_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_CHAR_STRING_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ARRAY_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_STRUCT_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_GROUP_ID_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_VENDOR_ID_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_FABRIC_ID_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_FABRIC_IDX_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENTRY_IDX_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DATA_VER_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EVENT_NO_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SEMTAG_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_NAMESPACE_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TAG_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SYSTIME_US_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SYSTIME_MS_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ELAPSED_S_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TEMPERATURE_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_POWER_MW_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENERGY_MWH_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TOD_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DATE_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EPOCH_US_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EPOCH_S_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_POSIX_MS_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_PERCENT_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_PERCENT100THS_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_CLUSTER_ID_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ATTRIB_ID_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_FIELD_ID_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EVENT_ID_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_COMMAND_ID_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ACTION_ID_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TRANS_ID_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_NODE_ID_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPADR_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPV4ADR_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPV6ADR_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPV6PRE_ATTRIBUTE_TYPE), MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_HWADR_ATTRIBUTE_TYPE), // Special case handling MockAttributeConfig(kAttributeIdReadOnly, ZCL_INT32S_ATTRIBUTE_TYPE, 0), MockAttributeConfig(kAttributeIdTimedWrite, ZCL_INT32S_ATTRIBUTE_TYPE, ATTRIBUTE_MASK_WRITABLE | ATTRIBUTE_MASK_MUST_USE_TIMED_WRITE), }), }), }); // clang-format on struct UseMockNodeConfig { UseMockNodeConfig(const MockNodeConfig & config) { SetMockNodeConfig(config); } ~UseMockNodeConfig() { ResetMockNodeConfig(); } }; template CHIP_ERROR DecodeList(TLV::TLVReader & reader, std::vector & out) { TLV::TLVType outer; ReturnErrorOnFailure(reader.EnterContainer(outer)); while (true) { CHIP_ERROR err = reader.Next(); if (err == CHIP_END_OF_TLV) { return CHIP_NO_ERROR; } ReturnErrorOnFailure(err); T value; ReturnErrorOnFailure(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 & encoder) override { if (static_cast(path) != mPath) { // returning without trying to handle means "I do not handle this" return CHIP_NO_ERROR; } return CHIP_IM_GLOBAL_STATUS(UnsupportedRead); } private: ConcreteAttributePath mPath; }; class StructAttributeAccessInterface : public AttributeAccessInterface { public: StructAttributeAccessInterface(ConcreteAttributePath path) : AttributeAccessInterface(MakeOptional(path.mEndpointId), path.mClusterId), mPath(path) {} ~StructAttributeAccessInterface() = default; CHIP_ERROR Read(const ConcreteReadAttributePath & path, AttributeValueEncoder & encoder) override { if (static_cast(path) != mPath) { // returning without trying to handle means "I do not handle this" return CHIP_NO_ERROR; } return encoder.Encode(mData); } CHIP_ERROR Write(const ConcreteDataAttributePath & path, AttributeValueDecoder & decoder) override { if (static_cast(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(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(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(path) != mPath) { // returning without trying to handle means "I do not handle this" return CHIP_NO_ERROR; } return encoder.EncodeList([this](const auto & listEncoder) { for (unsigned i = 0; i < mCount; i++) { mData.a = static_cast(i % 0xFF); ReturnErrorOnFailure(listEncoder.Encode(mData)); } return CHIP_NO_ERROR; }); } void SetReturnedData(const Clusters::UnitTesting::Structs::SimpleStruct::Type & data) { mData = data; } void SetReturnedDataCount(unsigned count) { mCount = count; } private: ConcreteAttributePath mPath; Clusters::UnitTesting::Structs::SimpleStruct::Type mData; unsigned mCount = 0; }; /// RAII registration of an attribute access interface template class RegisteredAttributeAccessInterface { public: template RegisteredAttributeAccessInterface(Args &&... args) : mData(std::forward(args)...) { VerifyOrDie(AttributeAccessInterfaceRegistry::Instance().Register(&mData)); } ~RegisteredAttributeAccessInterface() { AttributeAccessInterfaceRegistry::Instance().Unregister(&mData); } T * operator->() { return &mData; } T & operator*() { return mData; } private: T mData; }; template void TestEmberScalarTypeRead(typename NumericAttributeTraits::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::StorageType storage; NumericAttributeTraits::WorkingToStorage(value, storage); chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(&storage), sizeof(storage))); // Data read via the encoder std::unique_ptr encoder = testRequest.StartEncoding(); ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR); ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); // Validate after read std::vector 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::WorkingType actual; ASSERT_EQ(chip::app::DataModel::Decode::WorkingType>(encodedData.dataReader, actual), CHIP_NO_ERROR); ASSERT_EQ(actual, value); } template void TestEmberScalarNullRead() { UseMockNodeConfig config(gTestNodeConfig); 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::StorageType nullValue; NumericAttributeTraits::SetNull(nullValue); chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(&nullValue), sizeof(nullValue))); // Data read via the encoder std::unique_ptr encoder = testRequest.StartEncoding(); ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR); ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); // Validate after read std::vector 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::WorkingType> actual; ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR); ASSERT_TRUE(actual.IsNull()); } template void TestEmberScalarTypeWrite(const typename NumericAttributeTraits::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 = Test::GetEmberBuffer(); typename NumericAttributeTraits::StorageType storage; ASSERT_GE(writtenData.size(), sizeof(storage)); memcpy(&storage, writtenData.data(), sizeof(storage)); typename NumericAttributeTraits::WorkingType actual = NumericAttributeTraits::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; using NullableType = chip::app::DataModel::Nullable; AttributeValueDecoder decoder = test.DecoderFor(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 = Test::GetEmberBuffer(); typename NumericAttributeTraits::StorageType storage; ASSERT_GE(writtenData.size(), sizeof(storage)); memcpy(&storage, writtenData.data(), sizeof(storage)); typename NumericAttributeTraits::WorkingType actual = NumericAttributeTraits::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 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; using NullableType = chip::app::DataModel::Nullable; AttributeValueDecoder decoder = test.DecoderFor(NullableType()); // write should succeed ASSERT_TRUE(model.WriteAttribute(test.GetRequest(), decoder).IsSuccess()); // Validate data after write chip::ByteSpan writtenData = Test::GetEmberBuffer(); using Traits = NumericAttributeTraits; typename Traits::StorageType storage; ASSERT_GE(writtenData.size(), sizeof(storage)); memcpy(&storage, writtenData.data(), sizeof(storage)); ASSERT_TRUE(Traits::IsNullValue(storage)); } template 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; using NullableType = chip::app::DataModel::Nullable; AttributeValueDecoder decoder = test.DecoderFor(NullableType()); // write should fail: we are trying to write null ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_ERROR_WRONG_TLV_TYPE); } uint16_t ReadLe16(const void * buffer) { const uint8_t * p = reinterpret_cast(buffer); return chip::Encoding::LittleEndian::Read16(p); } void WriteLe16(void * buffer, uint16_t value) { uint8_t * p = reinterpret_cast(buffer); chip::Encoding::LittleEndian::Write16(p, value); } } // namespace TEST(TestCodegenModelViaMocks, IterateOverEndpoints) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; // This iteration relies on the hard-coding that occurs when mock_ember is used EXPECT_EQ(model.FirstEndpoint(), kMockEndpoint1); EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); EXPECT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); EXPECT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); /// Some out of order requests should work as well EXPECT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); EXPECT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); EXPECT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3); EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2); EXPECT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); EXPECT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId); EXPECT_EQ(model.FirstEndpoint(), kMockEndpoint1); EXPECT_EQ(model.FirstEndpoint(), kMockEndpoint1); // invalid endpoiunts EXPECT_EQ(model.NextEndpoint(kInvalidEndpointId), kInvalidEndpointId); EXPECT_EQ(model.NextEndpoint(987u), kInvalidEndpointId); } TEST(TestCodegenModelViaMocks, IterateOverClusters) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; chip::Test::ResetVersion(); EXPECT_FALSE(model.FirstCluster(kEndpointIdThatIsMissing).path.HasValidIds()); EXPECT_FALSE(model.FirstCluster(kInvalidEndpointId).path.HasValidIds()); EXPECT_FALSE(model.NextCluster(ConcreteClusterPath(kInvalidEndpointId, 123)).path.HasValidIds()); EXPECT_FALSE(model.NextCluster(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds()); EXPECT_FALSE(model.NextCluster(ConcreteClusterPath(kMockEndpoint1, 981u)).path.HasValidIds()); // mock endpoint 1 has 2 mock clusters: 1 and 2 ClusterEntry entry = model.FirstCluster(kMockEndpoint1); ASSERT_TRUE(entry.path.HasValidIds()); EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint1); EXPECT_EQ(entry.path.mClusterId, MockClusterId(1)); EXPECT_EQ(entry.info.dataVersion, 0u); EXPECT_EQ(entry.info.flags.Raw(), 0u); chip::Test::BumpVersion(); entry = model.NextCluster(entry.path); ASSERT_TRUE(entry.path.HasValidIds()); EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint1); EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); EXPECT_EQ(entry.info.dataVersion, 1u); EXPECT_EQ(entry.info.flags.Raw(), 0u); entry = model.NextCluster(entry.path); EXPECT_FALSE(entry.path.HasValidIds()); // mock endpoint 3 has 4 mock clusters: 1 through 4 entry = model.FirstCluster(kMockEndpoint3); for (uint16_t clusterId = 1; clusterId <= 4; clusterId++) { ASSERT_TRUE(entry.path.HasValidIds()); EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint3); EXPECT_EQ(entry.path.mClusterId, MockClusterId(clusterId)); entry = model.NextCluster(entry.path); } EXPECT_FALSE(entry.path.HasValidIds()); // repeat calls should work for (int i = 0; i < 10; i++) { entry = model.FirstCluster(kMockEndpoint1); ASSERT_TRUE(entry.path.HasValidIds()); EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint1); EXPECT_EQ(entry.path.mClusterId, MockClusterId(1)); } for (int i = 0; i < 10; i++) { ClusterEntry nextEntry = model.NextCluster(entry.path); ASSERT_TRUE(nextEntry.path.HasValidIds()); EXPECT_EQ(nextEntry.path.mEndpointId, kMockEndpoint1); EXPECT_EQ(nextEntry.path.mClusterId, MockClusterId(2)); } } TEST(TestCodegenModelViaMocks, GetClusterInfo) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; chip::Test::ResetVersion(); ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kInvalidEndpointId, kInvalidClusterId)).has_value()); ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).has_value()); ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).has_value()); ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).has_value()); // now get the value std::optional info = model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); ASSERT_TRUE(info.has_value()); EXPECT_EQ(info->dataVersion, 0u); // NOLINT(bugprone-unchecked-optional-access) EXPECT_EQ(info->flags.Raw(), 0u); // NOLINT(bugprone-unchecked-optional-access) chip::Test::BumpVersion(); info = model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); ASSERT_TRUE(info.has_value()); EXPECT_EQ(info->dataVersion, 1u); // NOLINT(bugprone-unchecked-optional-access) EXPECT_EQ(info->flags.Raw(), 0u); // NOLINT(bugprone-unchecked-optional-access) } TEST(TestCodegenModelViaMocks, IterateOverAttributes) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; // invalid paths should return in "no more data" ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).path.HasValidIds()); ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).path.HasValidIds()); ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).path.HasValidIds()); ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds()); ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kEndpointIdThatIsMissing, MockClusterId(1), 1u)).path.HasValidIds()); ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kInvalidEndpointId, MockClusterId(1), 1u)).path.HasValidIds()); ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), 1u)).path.HasValidIds()); ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kMockEndpoint1, kInvalidClusterId, 1u)).path.HasValidIds()); ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), 987u)).path.HasValidIds()); // should be able to iterate over valid paths AttributeEntry entry = model.FirstAttribute(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); ASSERT_TRUE(entry.path.HasValidIds()); ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); ASSERT_EQ(entry.path.mAttributeId, ClusterRevision::Id); ASSERT_FALSE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); entry = model.NextAttribute(entry.path); ASSERT_TRUE(entry.path.HasValidIds()); ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); ASSERT_EQ(entry.path.mAttributeId, FeatureMap::Id); ASSERT_FALSE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); entry = model.NextAttribute(entry.path); ASSERT_TRUE(entry.path.HasValidIds()); ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); ASSERT_EQ(entry.path.mAttributeId, MockAttributeId(1)); ASSERT_FALSE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); entry = model.NextAttribute(entry.path); ASSERT_TRUE(entry.path.HasValidIds()); ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); ASSERT_EQ(entry.path.mAttributeId, MockAttributeId(2)); ASSERT_TRUE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); entry = model.NextAttribute(entry.path); ASSERT_FALSE(entry.path.HasValidIds()); // repeated calls should work for (int i = 0; i < 10; i++) { entry = model.FirstAttribute(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); ASSERT_TRUE(entry.path.HasValidIds()); ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); ASSERT_EQ(entry.path.mAttributeId, ClusterRevision::Id); ASSERT_FALSE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); } for (int i = 0; i < 10; i++) { entry = model.NextAttribute(ConcreteAttributePath(kMockEndpoint2, MockClusterId(2), MockAttributeId(1))); ASSERT_TRUE(entry.path.HasValidIds()); ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2); ASSERT_EQ(entry.path.mClusterId, MockClusterId(2)); ASSERT_EQ(entry.path.mAttributeId, MockAttributeId(2)); ASSERT_TRUE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute)); } } TEST(TestCodegenModelViaMocks, GetAttributeInfo) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; // various non-existent or invalid paths should return no info data ASSERT_FALSE( model.GetAttributeInfo(ConcreteAttributePath(kInvalidEndpointId, kInvalidClusterId, kInvalidAttributeId)).has_value()); ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kInvalidEndpointId, kInvalidClusterId, FeatureMap::Id)).has_value()); ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kInvalidEndpointId, MockClusterId(1), FeatureMap::Id)).has_value()); ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, kInvalidClusterId, FeatureMap::Id)).has_value()); ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), FeatureMap::Id)).has_value()); ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), kInvalidAttributeId)).has_value()); ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))).has_value()); // valid info std::optional info = model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), FeatureMap::Id)); ASSERT_TRUE(info.has_value()); EXPECT_FALSE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) // Mocks always set everything as R/W with administrative privileges EXPECT_EQ(info->readPrivilege, chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access) EXPECT_EQ(info->writePrivilege, chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access) info = model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint2, MockClusterId(2), MockAttributeId(2))); ASSERT_TRUE(info.has_value()); EXPECT_TRUE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) EXPECT_EQ(info->readPrivilege, chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access) EXPECT_EQ(info->writePrivilege, chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access) // test a read-only attribute, which will not have a write privilege info = model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint3, MockClusterId(3), kReadOnlyAttributeId)); ASSERT_TRUE(info.has_value()); EXPECT_FALSE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access) EXPECT_EQ(info->readPrivilege, chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access) EXPECT_FALSE(info->writePrivilege.has_value()); // NOLINT(bugprone-unchecked-optional-access) } // global attributes are EXPLICITLY not supported TEST(TestCodegenModelViaMocks, GlobalAttributeInfo) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; std::optional info = model.GetAttributeInfo( ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::GeneratedCommandList::Id)); ASSERT_FALSE(info.has_value()); info = model.GetAttributeInfo( ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::AttributeList::Id)); ASSERT_FALSE(info.has_value()); } TEST(TestCodegenModelViaMocks, IterateOverAcceptedCommands) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; // invalid paths should return in "no more data" ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).path.HasValidIds()); ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).path.HasValidIds()); ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).path.HasValidIds()); ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds()); // should be able to iterate over valid paths CommandEntry entry = model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); ASSERT_TRUE(entry.path.HasValidIds()); EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); EXPECT_EQ(entry.path.mCommandId, 1u); entry = model.NextAcceptedCommand(entry.path); ASSERT_TRUE(entry.path.HasValidIds()); EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); EXPECT_EQ(entry.path.mCommandId, 2u); entry = model.NextAcceptedCommand(entry.path); ASSERT_TRUE(entry.path.HasValidIds()); EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); EXPECT_EQ(entry.path.mCommandId, 23u); entry = model.NextAcceptedCommand(entry.path); ASSERT_FALSE(entry.path.HasValidIds()); // attempt some out-of-order requests as well entry = model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(3))); ASSERT_TRUE(entry.path.HasValidIds()); EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); EXPECT_EQ(entry.path.mClusterId, MockClusterId(3)); EXPECT_EQ(entry.path.mCommandId, 11u); for (int i = 0; i < 10; i++) { entry = model.NextAcceptedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 2)); ASSERT_TRUE(entry.path.HasValidIds()); EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); EXPECT_EQ(entry.path.mCommandId, 23u); } for (int i = 0; i < 10; i++) { entry = model.NextAcceptedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1)); ASSERT_TRUE(entry.path.HasValidIds()); EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2); EXPECT_EQ(entry.path.mClusterId, MockClusterId(2)); EXPECT_EQ(entry.path.mCommandId, 2u); } for (int i = 0; i < 10; i++) { entry = model.NextAcceptedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(3), 10)); EXPECT_FALSE(entry.path.HasValidIds()); } } TEST(TestCodegenModelViaMocks, AcceptedCommandInfo) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; // invalid paths should return in "no more data" ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kEndpointIdThatIsMissing, MockClusterId(1), 1)).has_value()); ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kInvalidEndpointId, MockClusterId(1), 1)).has_value()); ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(10), 1)).has_value()); ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, kInvalidClusterId, 1)).has_value()); ASSERT_FALSE( model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), kInvalidCommandId)).has_value()); std::optional info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1u)); ASSERT_TRUE(info.has_value()); info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 2u)); ASSERT_TRUE(info.has_value()); info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1u)); ASSERT_TRUE(info.has_value()); info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1u)); ASSERT_TRUE(info.has_value()); info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 23u)); ASSERT_TRUE(info.has_value()); info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1234u)); ASSERT_FALSE(info.has_value()); } TEST(TestCodegenModelViaMocks, IterateOverGeneratedCommands) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; // invalid paths should return in "no more data" ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).HasValidIds()); ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).HasValidIds()); ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).HasValidIds()); ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).HasValidIds()); // should be able to iterate over valid paths ConcreteCommandPath path = model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2))); ASSERT_TRUE(path.HasValidIds()); EXPECT_EQ(path.mEndpointId, kMockEndpoint2); EXPECT_EQ(path.mClusterId, MockClusterId(2)); EXPECT_EQ(path.mCommandId, 2u); path = model.NextGeneratedCommand(path); ASSERT_TRUE(path.HasValidIds()); EXPECT_EQ(path.mEndpointId, kMockEndpoint2); EXPECT_EQ(path.mClusterId, MockClusterId(2)); EXPECT_EQ(path.mCommandId, 10u); path = model.NextGeneratedCommand(path); ASSERT_FALSE(path.HasValidIds()); // attempt some out-of-order requests as well path = model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(3))); ASSERT_TRUE(path.HasValidIds()); EXPECT_EQ(path.mEndpointId, kMockEndpoint2); EXPECT_EQ(path.mClusterId, MockClusterId(3)); EXPECT_EQ(path.mCommandId, 4u); for (int i = 0; i < 10; i++) { path = model.NextGeneratedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 2)); ASSERT_TRUE(path.HasValidIds()); EXPECT_EQ(path.mEndpointId, kMockEndpoint2); EXPECT_EQ(path.mClusterId, MockClusterId(2)); EXPECT_EQ(path.mCommandId, 10u); } for (int i = 0; i < 10; i++) { path = model.NextGeneratedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(3), 4)); ASSERT_TRUE(path.HasValidIds()); EXPECT_EQ(path.mEndpointId, kMockEndpoint2); EXPECT_EQ(path.mClusterId, MockClusterId(3)); EXPECT_EQ(path.mCommandId, 6u); } for (int i = 0; i < 10; i++) { path = model.NextGeneratedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(3), 6)); EXPECT_FALSE(path.HasValidIds()); } } TEST(TestCodegenModelViaMocks, CommandHandlerInterfaceAcceptedCommands) { 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)); // At this point, without overrides, there should be no accepted/generated commands EXPECT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))).IsValid()); EXPECT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))).HasValidIds()); EXPECT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 1234)).has_value()); handler.SetOverrideAccepted(true); handler.SetOverrideGenerated(true); // with overrides, the list is still empty ... EXPECT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))).IsValid()); EXPECT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))).HasValidIds()); EXPECT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 1234)).has_value()); // set some overrides handler.AcceptedVec().push_back(1234); handler.AcceptedVec().push_back(999); handler.GeneratedVec().push_back(33); DataModel::CommandEntry entry; entry = model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); EXPECT_TRUE(entry.IsValid()); EXPECT_EQ(entry.path, ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 1234)); entry = model.NextAcceptedCommand(entry.path); EXPECT_TRUE(entry.IsValid()); EXPECT_EQ(entry.path, ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 999)); entry = model.NextAcceptedCommand(entry.path); EXPECT_FALSE(entry.IsValid()); ConcreteCommandPath path = model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))); EXPECT_TRUE(path.HasValidIds()); EXPECT_EQ(path, ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 33)); path = model.NextGeneratedCommand(path); EXPECT_FALSE(path.HasValidIds()); // Command finding should work EXPECT_TRUE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 1234)).has_value()); EXPECT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 88)).has_value()); EXPECT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 33)).has_value()); } TEST(TestCodegenModelViaMocks, EmberAttributeReadAclDeny) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; ScopedMockAccessControl accessControl; ReadOperation testRequest(kMockEndpoint1, MockClusterId(1), MockAttributeId(10)); testRequest.SetSubjectDescriptor(kDenySubjectDescriptor); std::unique_ptr encoder = testRequest.StartEncoding(); ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), Status::UnsupportedAccess); } TEST(TestCodegenModelViaMocks, ReadForInvalidGlobalAttributePath) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; ScopedMockAccessControl accessControl; { ReadOperation testRequest(kEndpointIdThatIsMissing, MockClusterId(1), AttributeList::Id); testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); std::unique_ptr encoder = testRequest.StartEncoding(); ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), Status::UnsupportedEndpoint); } { ReadOperation testRequest(kMockEndpoint1, kInvalidClusterId, AttributeList::Id); testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); std::unique_ptr encoder = testRequest.StartEncoding(); ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), Status::UnsupportedCluster); } } TEST(TestCodegenModelViaMocks, EmberAttributeInvalidRead) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; ScopedMockAccessControl accessControl; // Invalid attribute { ReadOperation testRequest(kMockEndpoint1, MockClusterId(1), MockAttributeId(10)); testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); std::unique_ptr encoder = testRequest.StartEncoding(); ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), Status::UnsupportedAttribute); } // Invalid cluster { ReadOperation testRequest(kMockEndpoint1, MockClusterId(100), MockAttributeId(1)); testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); std::unique_ptr encoder = testRequest.StartEncoding(); ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), Status::UnsupportedCluster); } // Invalid endpoint { ReadOperation testRequest(kEndpointIdThatIsMissing, MockClusterId(1), MockAttributeId(1)); testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); std::unique_ptr encoder = testRequest.StartEncoding(); ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), Status::UnsupportedEndpoint); } } TEST(TestCodegenModelViaMocks, EmberAttributePathExpansionAccessDeniedRead) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; ScopedMockAccessControl accessControl; ReadOperation testRequest(kMockEndpoint1, MockClusterId(1), MockAttributeId(10)); testRequest.SetSubjectDescriptor(kDenySubjectDescriptor); testRequest.SetPathExpanded(true); std::unique_ptr encoder = testRequest.StartEncoding(); // For expanded paths, access control failures succeed without encoding anything // This is temporary until ACL checks are moved inside the IM/ReportEngine ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR); ASSERT_FALSE(encoder->TriedEncode()); } TEST(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 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 encoder = testRequest.StartEncoding(); ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR); ASSERT_FALSE(encoder->TriedEncode()); } TEST(TestCodegenModelViaMocks, EmberAttributeReadInt32S) { TestEmberScalarTypeRead(-1234); } TEST(TestCodegenModelViaMocks, EmberAttributeReadEnum16) { TestEmberScalarTypeRead(0x1234); } TEST(TestCodegenModelViaMocks, EmberAttributeReadFloat) { TestEmberScalarTypeRead(0.625); } TEST(TestCodegenModelViaMocks, EmberAttributeReadDouble) { TestEmberScalarTypeRead(0.625); } TEST(TestCodegenModelViaMocks, EmberAttributeReadInt24U) { TestEmberScalarTypeRead, ZCL_INT24U_ATTRIBUTE_TYPE>(0x1234AB); } TEST(TestCodegenModelViaMocks, EmberAttributeReadInt32U) { TestEmberScalarTypeRead(0x1234ABCD); } TEST(TestCodegenModelViaMocks, EmberAttributeReadInt40U) { TestEmberScalarTypeRead, ZCL_INT40U_ATTRIBUTE_TYPE>(0x1122334455); } TEST(TestCodegenModelViaMocks, EmberAttributeReadInt48U) { TestEmberScalarTypeRead, ZCL_INT48U_ATTRIBUTE_TYPE>(0xAABB11223344); } TEST(TestCodegenModelViaMocks, EmberAttributeReadInt56U) { TestEmberScalarTypeRead, ZCL_INT56U_ATTRIBUTE_TYPE>(0xAABB11223344); } TEST(TestCodegenModelViaMocks, EmberAttributeReadBool) { TestEmberScalarTypeRead(true); TestEmberScalarTypeRead(false); } TEST(TestCodegenModelViaMocks, EmberAttributeReadInt8U) { TestEmberScalarTypeRead(0x12); } TEST(TestCodegenModelViaMocks, EmberAttributeReadNulls) { TestEmberScalarNullRead(); TestEmberScalarNullRead(); TestEmberScalarNullRead, ZCL_INT24U_ATTRIBUTE_TYPE>(); TestEmberScalarNullRead(); TestEmberScalarNullRead, ZCL_INT40U_ATTRIBUTE_TYPE>(); TestEmberScalarNullRead, ZCL_INT48U_ATTRIBUTE_TYPE>(); TestEmberScalarNullRead, ZCL_INT56U_ATTRIBUTE_TYPE>(); TestEmberScalarNullRead(); TestEmberScalarNullRead(); TestEmberScalarNullRead(); TestEmberScalarNullRead, ZCL_INT24S_ATTRIBUTE_TYPE>(); TestEmberScalarNullRead(); TestEmberScalarNullRead, ZCL_INT40S_ATTRIBUTE_TYPE>(); TestEmberScalarNullRead, ZCL_INT48S_ATTRIBUTE_TYPE>(); TestEmberScalarNullRead, ZCL_INT56S_ATTRIBUTE_TYPE>(); TestEmberScalarNullRead(); TestEmberScalarNullRead(); TestEmberScalarNullRead(); TestEmberScalarNullRead(); } TEST(TestCodegenModelViaMocks, EmberAttributeReadErrorReading) { UseMockNodeConfig config(gTestNodeConfig); 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::Test::SetEmberReadOutput(Protocols::InteractionModel::Status::Failure); // Actual read via an encoder std::unique_ptr 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::Test::SetEmberReadOutput(Protocols::InteractionModel::Status::Busy); // Actual read via an encoder std::unique_ptr encoder = testRequest.StartEncoding(); ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), Status::Busy); } // reset things to success to not affect other tests chip::Test::SetEmberReadOutput(ByteSpan()); } TEST(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::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); // Actual read via an encoder std::unique_ptr 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 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 actual; ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR); ASSERT_TRUE(actual.IsNull()); } TEST(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::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); // Actual read via an encoder std::unique_ptr 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 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(data + 2), 4); ASSERT_TRUE(actual.data_equal(expected)); } TEST(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::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); // Actual read via an encoder std::unique_ptr 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 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(data + 1), 4); ASSERT_TRUE(actual.data_equal(expected)); } TEST(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::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); // Actual read via an encoder std::unique_ptr 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 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(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::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast(data), sizeof(data))); // Actual read via an encoder std::unique_ptr 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 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(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 aai(kStructPath); aai->SetReturnedData(Clusters::UnitTesting::Structs::SimpleStruct::Type{ .a = 123, .b = true, .e = "foo"_span, .g = 0.5, .h = 0.125, }); std::unique_ptr encoder = testRequest.StartEncoding(); ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR); ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); // Validate after read std::vector 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(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 aai(kStructPath, CHIP_ERROR_KEY_NOT_FOUND); std::unique_ptr encoder = testRequest.StartEncoding(); ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_ERROR_KEY_NOT_FOUND); } TEST(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 aai(kStructPath); constexpr unsigned kDataCount = 5; aai->SetReturnedData(Clusters::UnitTesting::Structs::SimpleStruct::Type{ .b = true, .e = "xyz"_span, .g = 0.25, .h = 0.5, }); aai->SetReturnedDataCount(kDataCount); std::unique_ptr encoder = testRequest.StartEncoding(); ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR); ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR); // Validate after read std::vector 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 items; ASSERT_EQ(DecodeList(encodedData.dataReader, items), CHIP_NO_ERROR); ASSERT_EQ(items.size(), kDataCount); for (unsigned i = 0; i < kDataCount; i++) { Clusters::UnitTesting::Structs::SimpleStruct::DecodableType & actual = items[i]; ASSERT_EQ(actual.a, static_cast(i & 0xFF)); ASSERT_EQ(actual.b, true); ASSERT_EQ(actual.g, 0.25); ASSERT_EQ(actual.h, 0.5); ASSERT_TRUE(actual.e.data_equal("xyz"_span)); } } TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceListOverflowRead) { UseMockNodeConfig config(gTestNodeConfig); 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 aai(kStructPath); constexpr unsigned kDataCount = 1024; aai->SetReturnedData(Clusters::UnitTesting::Structs::SimpleStruct::Type{ .b = true, .e = "thisislongertofillupfaster"_span, .g = 0.25, .h = 0.5, }); aai->SetReturnedDataCount(kDataCount); std::unique_ptr encoder = testRequest.StartEncoding(); // 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 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 items; ASSERT_EQ(DecodeList(encodedData.dataReader, items), CHIP_NO_ERROR); // On last check, 16 items can be encoded. Set some non-zero range to be enforced here that // SOME list items are actually encoded. Actual lower bound here IS ARBITRARY and was picked // to just ensure non-zero item count for checks. ASSERT_GT(items.size(), 5u); ASSERT_LT(items.size(), kDataCount); for (unsigned i = 0; i < items.size(); i++) { Clusters::UnitTesting::Structs::SimpleStruct::DecodableType & actual = items[i]; ASSERT_EQ(actual.a, static_cast(i & 0xFF)); ASSERT_EQ(actual.b, true); ASSERT_EQ(actual.g, 0.25); ASSERT_EQ(actual.h, 0.5); ASSERT_TRUE(actual.e.data_equal("thisislongertofillupfaster"_span)); } } TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceListIncrementalRead) { UseMockNodeConfig config(gTestNodeConfig); 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 aai(kStructPath); constexpr unsigned kDataCount = 1024; constexpr unsigned kEncodeIndexStart = 101; aai->SetReturnedData(Clusters::UnitTesting::Structs::SimpleStruct::Type{ .b = true, .e = "thisislongertofillupfaster"_span, .g = 0.25, .h = 0.5, }); aai->SetReturnedDataCount(kDataCount); AttributeEncodeState encodeState; encodeState.SetCurrentEncodingListIndex(kEncodeIndexStart); std::unique_ptr encoder = testRequest.StartEncoding(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 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((i + kEncodeIndexStart) & 0xFF)); ASSERT_EQ(actual.b, true); ASSERT_EQ(actual.g, 0.25); ASSERT_EQ(actual.h, 0.5); ASSERT_TRUE(actual.e.data_equal("thisislongertofillupfaster"_span)); } } TEST(TestCodegenModelViaMocks, ReadGlobalAttributeAttributeList) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; ScopedMockAccessControl accessControl; ReadOperation testRequest(kMockEndpoint2, MockClusterId(3), AttributeList::Id); testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); // Data read via the encoder std::unique_ptr 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 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 items; ASSERT_EQ(DecodeList(encodedData.dataReader, items), CHIP_NO_ERROR); // Mock data contains ClusterRevision and FeatureMap. // After this, Global attributes are auto-added std::vector expected; // Encoding in global-attribute-access-interface has a logic of: // - Append global attributes in front of the first specified // large number global attribute. // Since ClusterRevision and FeatureMap are // global attributes, the order here is reversed for them for (AttributeId id : GlobalAttributesNotInMetadata) { expected.push_back(id); } expected.push_back(ClusterRevision::Id); expected.push_back(FeatureMap::Id); expected.push_back(MockAttributeId(1)); expected.push_back(MockAttributeId(2)); expected.push_back(MockAttributeId(3)); ASSERT_EQ(items.size(), expected.size()); // Since we have no std::vector formatter, comparing element by element is somewhat // more readable in case of failure. for (unsigned i = 0; i < items.size(); i++) { EXPECT_EQ(items[i], expected[i]); } } TEST(TestCodegenModelViaMocks, EmberAttributeWriteAclDeny) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; ScopedMockAccessControl accessControl; /* Using this path is also failing existence checks, so this cannot be enabled * until we fix ordering of ACL to be done before existence checks WriteOperation test(kMockEndpoint1, MockClusterId(1), MockAttributeId(10)); AttributeValueDecoder decoder = test.DecoderFor(1234); ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::UnsupportedAccess); ASSERT_TRUE(model.ChangeListener().DirtyList().empty()); */ WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_INT32U_ATTRIBUTE_TYPE)); AttributeValueDecoder decoder = test.DecoderFor(1234); ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::UnsupportedAccess); ASSERT_TRUE(model.ChangeListener().DirtyList().empty()); } TEST(TestCodegenModelViaMocks, EmberAttributeWriteBasicTypes) { TestEmberScalarTypeWrite(0x12); TestEmberScalarTypeWrite(0x1234); TestEmberScalarTypeWrite, ZCL_INT24U_ATTRIBUTE_TYPE>(0x112233); TestEmberScalarTypeWrite(0x11223344); TestEmberScalarTypeWrite, ZCL_INT40U_ATTRIBUTE_TYPE>(0x1122334455ULL); TestEmberScalarTypeWrite, ZCL_INT48U_ATTRIBUTE_TYPE>(0x112233445566ULL); TestEmberScalarTypeWrite, ZCL_INT56U_ATTRIBUTE_TYPE>(0x11223344556677ULL); TestEmberScalarTypeWrite(0x1122334455667788ULL); TestEmberScalarTypeWrite(-10); TestEmberScalarTypeWrite(-123); TestEmberScalarTypeWrite, ZCL_INT24S_ATTRIBUTE_TYPE>(-1234); TestEmberScalarTypeWrite(-12345); TestEmberScalarTypeWrite, ZCL_INT40S_ATTRIBUTE_TYPE>(-123456); TestEmberScalarTypeWrite, ZCL_INT48S_ATTRIBUTE_TYPE>(-1234567); TestEmberScalarTypeWrite, ZCL_INT56S_ATTRIBUTE_TYPE>(-12345678); TestEmberScalarTypeWrite(-123456789); TestEmberScalarTypeWrite(true); TestEmberScalarTypeWrite(false); TestEmberScalarTypeWrite(0.625); TestEmberScalarTypeWrite(0.625); } TEST(TestCodegenModelViaMocks, EmberAttributeWriteInvalidValueToNullable) { TestEmberScalarTypeWriteNullValueToNullable(); TestEmberScalarTypeWriteNullValueToNullable(); TestEmberScalarTypeWriteNullValueToNullable, ZCL_INT24U_ATTRIBUTE_TYPE>(); TestEmberScalarTypeWriteNullValueToNullable(); TestEmberScalarTypeWriteNullValueToNullable, ZCL_INT40U_ATTRIBUTE_TYPE>(); TestEmberScalarTypeWriteNullValueToNullable, ZCL_INT48U_ATTRIBUTE_TYPE>(); TestEmberScalarTypeWriteNullValueToNullable, ZCL_INT56U_ATTRIBUTE_TYPE>(); TestEmberScalarTypeWriteNullValueToNullable(); TestEmberScalarTypeWriteNullValueToNullable(); TestEmberScalarTypeWriteNullValueToNullable(); TestEmberScalarTypeWriteNullValueToNullable, ZCL_INT24S_ATTRIBUTE_TYPE>(); TestEmberScalarTypeWriteNullValueToNullable(); TestEmberScalarTypeWriteNullValueToNullable, ZCL_INT40S_ATTRIBUTE_TYPE>(); TestEmberScalarTypeWriteNullValueToNullable, ZCL_INT48S_ATTRIBUTE_TYPE>(); TestEmberScalarTypeWriteNullValueToNullable, ZCL_INT56S_ATTRIBUTE_TYPE>(); TestEmberScalarTypeWriteNullValueToNullable(); TestEmberScalarTypeWriteNullValueToNullable(); TestEmberScalarTypeWriteNullValueToNullable(); TestEmberScalarTypeWriteNullValueToNullable(); TestEmberScalarTypeWriteNullValueToNullable(); } TEST(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; using NullableType = chip::app::DataModel::Nullable; AttributeValueDecoder decoder = test.DecoderFor(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(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; using NullableType = chip::app::DataModel::Nullable; AttributeValueDecoder decoder = test.DecoderFor(0x1223344); // write should fail: written value is not in range // NOTE: this matches legacy behaviour, however realistically maybe ConstraintError would be more correct ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_ERROR_INVALID_ARGUMENT); } TEST(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; using NullableType = chip::app::DataModel::Nullable; AttributeValueDecoder decoder = test.DecoderFor(0x1223344); // write should fail: written value is not in range // NOTE: this matches legacy behaviour, however realistically maybe ConstraintError would be more correct ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_ERROR_INVALID_ARGUMENT); } TEST(TestCodegenModelViaMoceNullValueToNullables, EmberAttributeWriteBasicTypesLowestValue) { TestEmberScalarTypeWrite(-127); TestEmberScalarTypeWrite(-32767); TestEmberScalarTypeWrite, ZCL_INT24S_ATTRIBUTE_TYPE>(-8388607); TestEmberScalarTypeWrite(-2147483647); TestEmberScalarTypeWrite, ZCL_INT40S_ATTRIBUTE_TYPE>(-549755813887); TestEmberScalarTypeWrite, ZCL_INT48S_ATTRIBUTE_TYPE>(-140737488355327); TestEmberScalarTypeWrite, ZCL_INT56S_ATTRIBUTE_TYPE>(-36028797018963967); TestEmberScalarTypeWrite(-9223372036854775807); } TEST(TestCodegenModelViaMocks, EmberAttributeWriteNulls) { TestEmberScalarNullWrite(); TestEmberScalarNullWrite(); TestEmberScalarNullWrite, ZCL_INT24U_ATTRIBUTE_TYPE>(); TestEmberScalarNullWrite(); TestEmberScalarNullWrite, ZCL_INT40U_ATTRIBUTE_TYPE>(); TestEmberScalarNullWrite, ZCL_INT48U_ATTRIBUTE_TYPE>(); TestEmberScalarNullWrite, ZCL_INT56U_ATTRIBUTE_TYPE>(); TestEmberScalarNullWrite(); TestEmberScalarNullWrite(); TestEmberScalarNullWrite(); TestEmberScalarNullWrite, ZCL_INT24S_ATTRIBUTE_TYPE>(); TestEmberScalarNullWrite(); TestEmberScalarNullWrite, ZCL_INT40S_ATTRIBUTE_TYPE>(); TestEmberScalarNullWrite, ZCL_INT48S_ATTRIBUTE_TYPE>(); TestEmberScalarNullWrite, ZCL_INT56S_ATTRIBUTE_TYPE>(); TestEmberScalarNullWrite(); TestEmberScalarNullWrite(); TestEmberScalarNullWrite(); TestEmberScalarNullWrite(); } TEST(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("hello world"_span); ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR); chip::ByteSpan writtenData = GetEmberBuffer(); chip::CharSpan asCharSpan(reinterpret_cast(writtenData.data()), writtenData[0] + 1); ASSERT_TRUE(asCharSpan.data_equal("\x0Bhello world"_span)); } TEST(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( "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(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("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(writtenData.data() + 2), 4); ASSERT_TRUE(asCharSpan.data_equal("text"_span)); } TEST(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::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(writtenData.data() + 2), 4); ASSERT_TRUE(asCharSpan.data_equal("text"_span)); } TEST(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()); 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(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(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(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(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(TestCodegenModelViaMocks, EmberAttributeWriteTimedWrite) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; ScopedMockAccessControl accessControl; WriteOperation test(kMockEndpoint3, MockClusterId(4), kAttributeIdTimedWrite); test.SetSubjectDescriptor(kAdminSubjectDescriptor); AttributeValueDecoder decoder = test.DecoderFor(1234); ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::NeedsTimedInteraction); // writing as timed should be fine test.SetWriteFlags(WriteFlags::kTimed); ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR); } TEST(TestCodegenModelViaMocks, EmberAttributeWriteReadOnlyAttribute) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; ScopedMockAccessControl accessControl; WriteOperation test(kMockEndpoint3, MockClusterId(4), kAttributeIdReadOnly); test.SetSubjectDescriptor(kAdminSubjectDescriptor); AttributeValueDecoder decoder = test.DecoderFor(1234); ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::UnsupportedWrite); // Internal writes bypass the read only requirement test.SetOperationFlags(OperationFlags::kInternal); ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR); } TEST(TestCodegenModelViaMocks, EmberAttributeWriteDataVersion) { 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); // Initialize to some version ResetVersion(); BumpVersion(); test.SetDataVersion(MakeOptional(GetVersion())); // Make version invalid BumpVersion(); AttributeValueDecoder decoder = test.DecoderFor(1234); ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::DataVersionMismatch); // Write passes if we set the right version for the data test.SetDataVersion(MakeOptional(GetVersion())); ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR); } TEST(TestCodegenModelViaMocks, WriteToInvalidPath) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; ScopedMockAccessControl accessControl; { WriteOperation test(kInvalidEndpointId, MockClusterId(1234), 1234); test.SetSubjectDescriptor(kAdminSubjectDescriptor); AttributeValueDecoder decoder = test.DecoderFor(1234); ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::UnsupportedEndpoint); } { WriteOperation test(kMockEndpoint1, MockClusterId(1234), 1234); test.SetSubjectDescriptor(kAdminSubjectDescriptor); AttributeValueDecoder decoder = test.DecoderFor(1234); ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::UnsupportedCluster); } { WriteOperation test(kMockEndpoint1, MockClusterId(1), 1234); test.SetSubjectDescriptor(kAdminSubjectDescriptor); AttributeValueDecoder decoder = test.DecoderFor(1234); ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::UnsupportedAttribute); } } TEST(TestCodegenModelViaMocks, WriteToGlobalAttribute) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; ScopedMockAccessControl accessControl; WriteOperation test(kMockEndpoint1, MockClusterId(1), AttributeList::Id); test.SetSubjectDescriptor(kAdminSubjectDescriptor); AttributeValueDecoder decoder = test.DecoderFor(1234); ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::UnsupportedWrite); } TEST(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(1234); chip::Test::SetEmberReadOutput(Protocols::InteractionModel::Status::Failure); ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::Failure); } { AttributeValueDecoder decoder = test.DecoderFor(1234); chip::Test::SetEmberReadOutput(Protocols::InteractionModel::Status::Busy); ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::Busy); } // reset things to success to not affect other tests chip::Test::SetEmberReadOutput(ByteSpan()); } TEST(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 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(4321); TestEmberScalarNullWrite(); } TEST(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::Test::DispatchCount(); // Using a handler set to nullptr as it is not used by the impl ASSERT_EQ(model.Invoke(kInvokeRequest, tlvReader, /* handler = */ nullptr), std::nullopt); EXPECT_EQ(chip::Test::DispatchCount(), kDispatchCountPre + 1); // single dispatch EXPECT_EQ(chip::Test::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::Test::DispatchCount(); // Using a handler set to nullpotr as it is not used by the impl ASSERT_EQ(model.Invoke(kInvokeRequest, tlvReader, /* handler = */ nullptr), std::nullopt); EXPECT_EQ(chip::Test::DispatchCount(), kDispatchCountPre + 1); // single dispatch EXPECT_EQ(chip::Test::GetLastDispatchPath(), kCommandPath); // for the right path } } TEST(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 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(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(TestCodegenModelViaMocks, DeviceTypeIteration) { UseMockNodeConfig config(gTestNodeConfig); CodegenDataModelProviderWithContext model; // Mock endpoint 1 has 3 device types std::optional entry = model.FirstDeviceType(kMockEndpoint1); ASSERT_EQ(entry, std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId1, .deviceTypeVersion = kDeviceTypeId1Version })); // NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none entry = model.NextDeviceType(kMockEndpoint1, *entry); ASSERT_EQ(entry, std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeVersion = kDeviceTypeId2Version })); // NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none entry = model.NextDeviceType(kMockEndpoint1, *entry); ASSERT_EQ(entry, std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId3, .deviceTypeVersion = kDeviceTypeId3Version })); // NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none entry = model.NextDeviceType(kMockEndpoint1, *entry); ASSERT_FALSE(entry.has_value()); // Mock endpoint 2 has 1 device types entry = model.FirstDeviceType(kMockEndpoint2); ASSERT_EQ(entry, std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeVersion = kDeviceTypeId2Version })); // NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none entry = model.NextDeviceType(kMockEndpoint2, *entry); ASSERT_FALSE(entry.has_value()); // out of order query works entry = std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeVersion = kDeviceTypeId2Version }); entry = model.NextDeviceType(kMockEndpoint1, *entry); ASSERT_EQ(entry, std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId3, .deviceTypeVersion = kDeviceTypeId3Version })); // invalid query fails entry = std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId1, .deviceTypeVersion = kDeviceTypeId1Version }); entry = model.NextDeviceType(kMockEndpoint2, *entry); ASSERT_FALSE(entry.has_value()); // empty endpoint works entry = model.FirstDeviceType(kMockEndpoint3); ASSERT_FALSE(entry.has_value()); }