blob: efac2116479243e43dcb4c6bbdd3031b8898bdfb [file] [log] [blame]
/*
* Copyright (c) 2024 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pw_unit_test/framework.h>
#include <data-model-providers/codegen/tests/EmberInvokeOverride.h>
#include <data-model-providers/codegen/tests/EmberReadWriteOverride.h>
#include <access/AccessControl.h>
#include <access/SubjectDescriptor.h>
#include <app-common/zap-generated/attribute-type.h>
#include <app-common/zap-generated/cluster-objects.h>
#include <app/AttributeAccessInterface.h>
#include <app/AttributeAccessInterfaceRegistry.h>
#include <app/AttributeEncodeState.h>
#include <app/AttributeValueDecoder.h>
#include <app/CommandHandlerInterface.h>
#include <app/CommandHandlerInterfaceRegistry.h>
#include <app/ConcreteAttributePath.h>
#include <app/ConcreteClusterPath.h>
#include <app/ConcreteCommandPath.h>
#include <app/GlobalAttributes.h>
#include <app/MessageDef/ReportDataMessage.h>
#include <app/data-model-provider/MetadataLookup.h>
#include <app/data-model-provider/MetadataTypes.h>
#include <app/data-model-provider/OperationTypes.h>
#include <app/data-model-provider/StringBuilderAdapters.h>
#include <app/data-model-provider/tests/ReadTesting.h>
#include <app/data-model-provider/tests/TestConstants.h>
#include <app/data-model-provider/tests/WriteTesting.h>
#include <app/data-model/Decode.h>
#include <app/data-model/Encode.h>
#include <app/data-model/List.h>
#include <app/data-model/Nullable.h>
#include <app/server-cluster/DefaultServerCluster.h>
#include <app/server-cluster/testing/TestServerClusterContext.h>
#include <app/util/attribute-metadata.h>
#include <app/util/attribute-storage-null-handling.h>
#include <app/util/ember-io-storage.h>
#include <app/util/mock/Constants.h>
#include <app/util/mock/Functions.h>
#include <app/util/mock/MockNodeConfig.h>
#include <app/util/odd-sized-integers.h>
#include <data-model-providers/codegen/CodegenDataModelProvider.h>
#include <lib/core/CHIPError.h>
#include <lib/core/DataModelTypes.h>
#include <lib/core/Optional.h>
#include <lib/core/StringBuilderAdapters.h>
#include <lib/core/TLVDebug.h>
#include <lib/core/TLVReader.h>
#include <lib/core/TLVTags.h>
#include <lib/core/TLVTypes.h>
#include <lib/core/TLVWriter.h>
#include <lib/support/ReadOnlyBuffer.h>
#include <lib/support/Span.h>
#include <protocols/interaction_model/StatusCode.h>
#include <optional>
#include <vector>
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;
// Mock function for linking
void InitDataModelHandler() {}
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;
constexpr DeviceTypeId kDeviceTypeId4 = 1123;
constexpr uint8_t kDeviceTypeId4Version = 33;
constexpr uint8_t kNamespaceID1 = 123;
constexpr uint8_t kTag1 = 10;
constexpr char kLabel1[] = "Label1";
constexpr uint8_t kNamespaceID2 = 254;
constexpr uint8_t kTag2 = 22;
constexpr char kLabel2[] = "Label2";
constexpr uint8_t kNamespaceID3 = 3;
constexpr uint8_t kTag3 = 32;
static_assert(kEndpointIdThatIsMissing != kInvalidEndpointId);
static_assert(kEndpointIdThatIsMissing != kMockEndpoint1);
static_assert(kEndpointIdThatIsMissing != kMockEndpoint2);
static_assert(kEndpointIdThatIsMissing != kMockEndpoint3);
static_assert(kEndpointIdThatIsMissing != kMockEndpoint4);
bool operator==(const Access::SubjectDescriptor & a, const Access::SubjectDescriptor & b)
{
if (a.fabricIndex != b.fabricIndex)
{
return false;
}
if (a.authMode != b.authMode)
{
return false;
}
if (a.subject != b.subject)
{
return false;
}
for (unsigned i = 0; i < a.cats.values.size(); i++)
{
if (a.cats.values[i] != b.cats.values[i])
{
return false;
}
}
return true;
}
struct TestCodegenModelViaMocks : public ::testing::Test
{
static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); }
static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); }
};
class TestProviderChangeListener : public ProviderChangeListener
{
public:
void MarkDirty(const AttributePathParams & path) override { mDirtyList.push_back(path); }
std::vector<AttributePathParams> & DirtyList() { return mDirtyList; }
const std::vector<AttributePathParams> & DirtyList() const { return mDirtyList; }
private:
std::vector<AttributePathParams> mDirtyList;
};
class 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; }
};
class MockCommandHandler : public CommandHandler
{
public:
CHIP_ERROR FallibleAddStatus(const ConcreteCommandPath & aRequestCommandPath,
const Protocols::InteractionModel::ClusterStatusCode & aStatus,
const char * context = nullptr) override
{
// MOCK: do not do anything here
return CHIP_NO_ERROR;
}
void AddStatus(const ConcreteCommandPath & aRequestCommandPath, const Protocols::InteractionModel::ClusterStatusCode & aStatus,
const char * context = nullptr) override
{
// MOCK: do not do anything here
}
FabricIndex GetAccessingFabricIndex() const override { return 1; }
CHIP_ERROR AddResponseData(const ConcreteCommandPath & aRequestCommandPath, CommandId aResponseCommandId,
const DataModel::EncodableToTLV & aEncodable) override
{
return CHIP_NO_ERROR;
}
void AddResponse(const ConcreteCommandPath & aRequestCommandPath, CommandId aResponseCommandId,
const DataModel::EncodableToTLV & aEncodable) override
{}
bool IsTimedInvoke() const override { return false; }
void FlushAcksRightAwayOnSlowCommand() override {}
Access::SubjectDescriptor GetSubjectDescriptor() const override { return kAdminSubjectDescriptor; }
Messaging::ExchangeContext * GetExchangeContext() const override { return nullptr; }
};
/// Overrides Enumerate*Commands in the CommandHandlerInterface to allow
/// testing of behaviors when command enumeration is done in the interace.
class CustomListCommandHandler : public CommandHandlerInterface
{
public:
CustomListCommandHandler(Optional<EndpointId> endpointId, ClusterId clusterId) : CommandHandlerInterface(endpointId, clusterId)
{
CommandHandlerInterfaceRegistry::Instance().RegisterCommandHandler(this);
}
~CustomListCommandHandler() { CommandHandlerInterfaceRegistry::Instance().UnregisterCommandHandler(this); }
void InvokeCommand(HandlerContext & handlerContext) override
{
if (mHandleCommand)
{
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Protocols::InteractionModel::Status::Success);
handlerContext.SetCommandHandled();
}
else
{
handlerContext.SetCommandNotHandled();
}
}
void SetHandleCommands(bool handle) { mHandleCommand = handle; }
CHIP_ERROR EnumerateAcceptedCommands(const ConcreteClusterPath & cluster, CommandIdCallback callback, void * context) override
{
VerifyOrReturnError(mOverrideAccepted, CHIP_ERROR_NOT_IMPLEMENTED);
for (auto id : mAccepted)
{
if (callback(id, context) != Loop::Continue)
{
break;
}
}
return CHIP_NO_ERROR;
}
CHIP_ERROR EnumerateGeneratedCommands(const ConcreteClusterPath & cluster, CommandIdCallback callback, void * context) override
{
VerifyOrReturnError(mOverrideGenerated, CHIP_ERROR_NOT_IMPLEMENTED);
for (auto id : mGenerated)
{
if (callback(id, context) != Loop::Continue)
{
break;
}
}
return CHIP_NO_ERROR;
}
void SetOverrideAccepted(bool o) { mOverrideAccepted = o; }
void SetOverrideGenerated(bool o) { mOverrideGenerated = o; }
std::vector<CommandId> & AcceptedVec() { return mAccepted; }
std::vector<CommandId> & GeneratedVec() { return mGenerated; }
private:
bool mOverrideAccepted = false;
bool mOverrideGenerated = false;
bool mHandleCommand = false;
std::vector<CommandId> mAccepted;
std::vector<CommandId> mGenerated;
};
class ScopedMockAccessControl
{
public:
ScopedMockAccessControl() { Access::GetAccessControl().Init(&mMock, mMock); }
~ScopedMockAccessControl() { Access::GetAccessControl().Finish(); }
private:
MockAccessControl mMock;
};
#define MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(zcl_type) MockAttributeId(zcl_type + 0x1000)
#define MOCK_ATTRIBUTE_CONFIG_NULLABLE(zcl_type) \
MockAttributeConfig(MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(zcl_type), zcl_type, \
MATTER_ATTRIBUTE_FLAG_WRITABLE | MATTER_ATTRIBUTE_FLAG_NULLABLE)
#define MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(zcl_type) MockAttributeId(zcl_type + 0x2000)
#define MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(zcl_type) \
MockAttributeConfig(MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(zcl_type), zcl_type, MATTER_ATTRIBUTE_FLAG_WRITABLE)
// clang-format off
const MockNodeConfig gTestNodeConfig({
MockEndpointConfig(kMockEndpoint1, {
MockClusterConfig(MockClusterId(1), {
ClusterRevision::Id, FeatureMap::Id,
}, {
MockEventId(1), MockEventId(2),
}),
MockClusterConfig(MockClusterId(2), {
ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1),
}),
MockClusterConfig(MockClusterId(3), {}, {}, {}, {}, BitMask<MockClusterSide>().Set(MockClusterSide::kClient)),
MockClusterConfig(MockClusterId(4), {}, {}, {}, {}, BitMask<MockClusterSide>().Set(MockClusterSide::kClient)),
}, {
{ kDeviceTypeId1, kDeviceTypeId1Version},
{ kDeviceTypeId2, kDeviceTypeId2Version},
{ kDeviceTypeId3, kDeviceTypeId3Version},
},{
{ MakeNullable(VendorId::TestVendor1), kNamespaceID1, kTag1, MakeOptional(MakeNullable(CharSpan::fromCharString(kLabel1)))},
{ Nullable<VendorId>(), kNamespaceID2, kTag2, MakeOptional(MakeNullable(CharSpan::fromCharString(kLabel2)))},
{ MakeNullable(VendorId::TestVendor3), kNamespaceID3, kTag3, NullOptional},
}),
MockEndpointConfig(kMockEndpoint2, {
MockClusterConfig(MockClusterId(1), {
ClusterRevision::Id, FeatureMap::Id,
}),
MockClusterConfig(
MockClusterId(2),
{
ClusterRevision::Id,
FeatureMap::Id,
MockAttributeId(1),
MockAttributeConfig(MockAttributeId(2), ZCL_ARRAY_ATTRIBUTE_TYPE),
}, /* attributes */
{}, /* events */
{1, 2, 23}, /* acceptedCommands */
{2, 10} /* generatedCommands */
),
MockClusterConfig(
MockClusterId(3),
{
ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), MockAttributeId(2), MockAttributeId(3),
}, /* attributes */
{}, /* events */
{11}, /* acceptedCommands */
{4, 6}, /* generatedCommands */
BitMask<MockClusterSide>().Set(MockClusterSide::kClient).Set(MockClusterSide::kServer)
),
MockClusterConfig(MockClusterId(4), {}, {}, {}, {}, MockClusterSide::kClient),
}, {
{ kDeviceTypeId2, kDeviceTypeId2Version},
}, {},
EndpointComposition::kTree),
MockEndpointConfig(kMockEndpoint3, {
MockClusterConfig(MockClusterId(1), {
ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1),
}),
MockClusterConfig(MockClusterId(2), {
ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), MockAttributeId(2), MockAttributeId(3), MockAttributeId(4),
}),
MockClusterConfig(MockClusterId(3), {
ClusterRevision::Id, FeatureMap::Id,
MockAttributeConfig(
kReadOnlyAttributeId,
ZCL_INT32U_ATTRIBUTE_TYPE,
MATTER_ATTRIBUTE_FLAG_NULLABLE // NOTE: explicltly NOT MATTER_ATTRIBUTE_FLAG_WRITABLE
)
}),
MockClusterConfig(MockClusterId(4), {
ClusterRevision::Id,
FeatureMap::Id,
// several attributes of varying data types for testing.
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BOOLEAN_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BITMAP8_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BITMAP16_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BITMAP32_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BITMAP64_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT8U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT16U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT24U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT32U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT40U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT48U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT56U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT64U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT8S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT16S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT24S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT32S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT40S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT48S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT56S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT64S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENUM8_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENUM16_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_PRIORITY_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_STATUS_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_SINGLE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DOUBLE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_CHAR_STRING_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ARRAY_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_STRUCT_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_GROUP_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_VENDOR_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_FABRIC_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_FABRIC_IDX_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENTRY_IDX_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DATA_VER_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EVENT_NO_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_SEMTAG_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_NAMESPACE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TAG_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_SYSTIME_US_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_SYSTIME_MS_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ELAPSED_S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TEMPERATURE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TOD_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DATE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EPOCH_US_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EPOCH_S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_POSIX_MS_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_PERCENT_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_PERCENT100THS_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_CLUSTER_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ATTRIB_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_FIELD_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EVENT_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_COMMAND_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ACTION_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TRANS_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_NODE_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPADR_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPV4ADR_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPV6ADR_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPV6PRE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_HWADR_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BOOLEAN_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP8_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP16_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP32_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP64_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT8U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT16U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT24U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT32U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT40U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT48U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT56U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT64U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT8S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT16S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT24S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT32S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT40S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT48S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT56S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT64S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENUM8_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENUM16_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_PRIORITY_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_STATUS_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SINGLE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DOUBLE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_CHAR_STRING_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ARRAY_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_STRUCT_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_GROUP_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_VENDOR_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_FABRIC_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_FABRIC_IDX_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENTRY_IDX_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DATA_VER_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EVENT_NO_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SEMTAG_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_NAMESPACE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TAG_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SYSTIME_US_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SYSTIME_MS_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ELAPSED_S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TEMPERATURE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TOD_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DATE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EPOCH_US_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EPOCH_S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_POSIX_MS_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_PERCENT_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_PERCENT100THS_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_CLUSTER_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ATTRIB_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_FIELD_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EVENT_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_COMMAND_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ACTION_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TRANS_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_NODE_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPADR_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPV4ADR_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPV6ADR_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPV6PRE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_HWADR_ATTRIBUTE_TYPE),
// Special case handling
MockAttributeConfig(kAttributeIdReadOnly, ZCL_INT32S_ATTRIBUTE_TYPE, 0),
MockAttributeConfig(kAttributeIdTimedWrite, ZCL_INT32S_ATTRIBUTE_TYPE, MATTER_ATTRIBUTE_FLAG_WRITABLE | MATTER_ATTRIBUTE_FLAG_MUST_USE_TIMED_WRITE),
}),
}),
MockEndpointConfig(kMockEndpoint4, {
MockClusterConfig(MockClusterId(4), {
ClusterRevision::Id, FeatureMap::Id, MockAttributeId(4),
MockAttributeConfig(Clusters::Descriptor::Attributes::EndpointUniqueID::Id, ZCL_CHAR_STRING_ATTRIBUTE_TYPE),
}),
}, {
{ kDeviceTypeId4, kDeviceTypeId4Version },
},
{}, // Empty semantic tags
EndpointComposition::kTree,
chip::CharSpan("AABBCCDDEEFFGGHHIIJJKKLLMMNNOO01", strlen("AABBCCDDEEFFGGHHIIJJKKLLMMNNOO01")) // Add endpointUniqueID
),
});
// clang-format on
struct UseMockNodeConfig
{
UseMockNodeConfig(const MockNodeConfig & config) { SetMockNodeConfig(config); }
~UseMockNodeConfig() { ResetMockNodeConfig(); }
};
template <typename T>
CHIP_ERROR DecodeList(TLV::TLVReader & reader, std::vector<T> & out)
{
TLV::TLVType outer;
ReturnErrorOnFailure(reader.EnterContainer(outer));
while (true)
{
CHIP_ERROR err = reader.Next();
if (err == CHIP_END_OF_TLV)
{
return CHIP_NO_ERROR;
}
ReturnErrorOnFailure(err);
T value;
ReturnErrorOnFailure(chip::app::DataModel::Decode(reader, value));
out.emplace_back(std::move(value));
}
}
class UnsupportedReadAccessInterface : public AttributeAccessInterface
{
public:
UnsupportedReadAccessInterface(ConcreteAttributePath path) :
AttributeAccessInterface(MakeOptional(path.mEndpointId), path.mClusterId), mPath(path)
{}
~UnsupportedReadAccessInterface() = default;
CHIP_ERROR Read(const ConcreteReadAttributePath & path, AttributeValueEncoder & encoder) override
{
if (static_cast<const ConcreteAttributePath &>(path) != mPath)
{
// returning without trying to handle means "I do not handle this"
return CHIP_NO_ERROR;
}
return 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<const ConcreteAttributePath &>(path) != mPath)
{
// returning without trying to handle means "I do not handle this"
return CHIP_NO_ERROR;
}
return encoder.Encode(mData);
}
CHIP_ERROR Write(const ConcreteDataAttributePath & path, AttributeValueDecoder & decoder) override
{
if (static_cast<const ConcreteAttributePath &>(path) != mPath)
{
// returning without trying to handle means "I do not handle this"
return CHIP_NO_ERROR;
}
return decoder.Decode(mData);
}
void SetReturnedData(const Clusters::UnitTesting::Structs::SimpleStruct::Type & data) { mData = data; }
const Clusters::UnitTesting::Structs::SimpleStruct::Type & GetData() const { return mData; }
private:
ConcreteAttributePath mPath;
Clusters::UnitTesting::Structs::SimpleStruct::Type mData;
};
class ErrorAccessInterface : public AttributeAccessInterface
{
public:
ErrorAccessInterface(ConcreteAttributePath path, CHIP_ERROR err) :
AttributeAccessInterface(MakeOptional(path.mEndpointId), path.mClusterId), mPath(path), mError(err)
{}
~ErrorAccessInterface() = default;
CHIP_ERROR Read(const ConcreteReadAttributePath & path, AttributeValueEncoder & encoder) override
{
if (static_cast<const ConcreteAttributePath &>(path) != mPath)
{
// returning without trying to handle means "I do not handle this"
return CHIP_NO_ERROR;
}
return mError;
}
CHIP_ERROR Write(const ConcreteDataAttributePath & path, AttributeValueDecoder & decoder) override
{
if (static_cast<const ConcreteAttributePath &>(path) != mPath)
{
// returning without trying to handle means "I do not handle this"
return CHIP_NO_ERROR;
}
return mError;
}
private:
ConcreteAttributePath mPath;
CHIP_ERROR mError;
};
class ListAttributeAcessInterface : public AttributeAccessInterface
{
public:
ListAttributeAcessInterface(ConcreteAttributePath path) :
AttributeAccessInterface(MakeOptional(path.mEndpointId), path.mClusterId), mPath(path)
{}
~ListAttributeAcessInterface() = default;
CHIP_ERROR Read(const ConcreteReadAttributePath & path, AttributeValueEncoder & encoder) override
{
if (static_cast<const ConcreteAttributePath &>(path) != mPath)
{
// returning without trying to handle means "I do not handle this"
return CHIP_NO_ERROR;
}
return encoder.EncodeList([this](const auto & listEncoder) {
for (unsigned i = 0; i < mCount; i++)
{
mData.a = static_cast<uint8_t>(i % 0xFF);
ReturnErrorOnFailure(listEncoder.Encode(mData));
}
return CHIP_NO_ERROR;
});
}
void SetReturnedData(const Clusters::UnitTesting::Structs::SimpleStruct::Type & data) { mData = data; }
void SetReturnedDataCount(unsigned count) { mCount = count; }
private:
ConcreteAttributePath mPath;
Clusters::UnitTesting::Structs::SimpleStruct::Type mData;
unsigned mCount = 0;
};
/// RAII registration of an attribute access interface
template <typename T>
class RegisteredAttributeAccessInterface
{
public:
template <typename... Args>
RegisteredAttributeAccessInterface(Args &&... args) : mData(std::forward<Args>(args)...)
{
VerifyOrDie(AttributeAccessInterfaceRegistry::Instance().Register(&mData));
}
~RegisteredAttributeAccessInterface() { AttributeAccessInterfaceRegistry::Instance().Unregister(&mData); }
T * operator->() { return &mData; }
T & operator*() { return mData; }
private:
T mData;
};
class FakeDefaultServerCluster : public DefaultServerCluster
{
public:
static constexpr uint32_t kFakeFeatureMap = 0x35;
static constexpr uint32_t kFakeClusterRevision = 1234;
static constexpr CommandId kGeneratedCommands[] = { 1, 2, 3, 100, 200 };
static constexpr AcceptedCommandEntry kAcceptedCommands[] = {
{ 101 },
{ 102 },
};
FakeDefaultServerCluster(const ConcreteClusterPath & path) : DefaultServerCluster(path) {}
[[nodiscard]] BitFlags<DataModel::ClusterQualityFlags> GetClusterFlags(const ConcreteClusterPath &) const override
{
return DataModel::ClusterQualityFlags::kDiagnosticsData;
}
CHIP_ERROR AcceptedCommands(const ConcreteClusterPath & path,
ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder) override
{
return builder.ReferenceExisting(Span<const AcceptedCommandEntry>(kAcceptedCommands));
}
CHIP_ERROR GeneratedCommands(const ConcreteClusterPath & path, ReadOnlyBufferBuilder<CommandId> & builder) override
{
return builder.ReferenceExisting(Span<const CommandId>(kGeneratedCommands));
}
std::optional<DataModel::ActionReturnStatus> InvokeCommand(const DataModel::InvokeRequest & request,
chip::TLV::TLVReader & input_arguments,
CommandHandler * handler) override
{
return CHIP_ERROR_INCORRECT_STATE;
}
DataModel::ActionReturnStatus WriteAttribute(const DataModel::WriteAttributeRequest & request,
AttributeValueDecoder & decoder) override
{
return CHIP_ERROR_INCORRECT_STATE;
}
DataModel::ActionReturnStatus ReadAttribute(const DataModel::ReadAttributeRequest & request,
AttributeValueEncoder & encoder) override
{
using namespace chip::app::Clusters;
switch (request.path.mAttributeId)
{
case Globals::Attributes::FeatureMap::Id: {
uint32_t value = kFakeFeatureMap;
return encoder.Encode<uint32_t>(std::move(value));
}
case Globals::Attributes::ClusterRevision::Id: {
uint32_t value = kFakeClusterRevision;
return encoder.Encode<uint32_t>(std::move(value));
}
}
return CHIP_ERROR_INVALID_ARGUMENT;
}
void TestIncreaseDataVersion() { IncreaseDataVersion(); }
void TestNotifyAttributeChanged(AttributeId attributeId) { NotifyAttributeChanged(attributeId); }
};
template <typename T, EmberAfAttributeType ZclType>
void TestEmberScalarTypeRead(typename NumericAttributeTraits<T>::WorkingType value)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
ReadOperation testRequest(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZclType));
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
// Ember encoding for integers is IDENTICAL to the in-memory representation for them
typename NumericAttributeTraits<T>::StorageType storage;
NumericAttributeTraits<T>::WorkingToStorage(value, storage);
chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast<const uint8_t *>(&storage), sizeof(storage)));
// Data read via the encoder
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding();
ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after read
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.GetRequest().path);
typename NumericAttributeTraits<T>::WorkingType actual;
ASSERT_EQ(chip::app::DataModel::Decode<typename NumericAttributeTraits<T>::WorkingType>(encodedData.dataReader, actual),
CHIP_NO_ERROR);
ASSERT_EQ(actual, value);
}
template <typename T, EmberAfAttributeType ZclType>
void TestEmberScalarNullRead()
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
ReadOperation testRequest(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZclType));
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
// Ember encoding for integers is IDENTICAL to the in-memory representation for them
typename NumericAttributeTraits<T>::StorageType nullValue;
NumericAttributeTraits<T>::SetNull(nullValue);
chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast<const uint8_t *>(&nullValue), sizeof(nullValue)));
// Data read via the encoder
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding();
ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after read
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.GetRequest().path);
chip::app::DataModel::Nullable<typename NumericAttributeTraits<T>::WorkingType> actual;
ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR);
ASSERT_TRUE(actual.IsNull());
}
template <typename T, EmberAfAttributeType ZclType>
void TestEmberScalarTypeWrite(const typename NumericAttributeTraits<T>::WorkingType value)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
// non-nullable test
{
WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZclType));
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
AttributeValueDecoder decoder = test.DecoderFor(value);
// write should succeed
ASSERT_TRUE(model.WriteAttribute(test.GetRequest(), decoder).IsSuccess());
// Validate data after write
chip::ByteSpan writtenData = Test::GetEmberBuffer();
typename NumericAttributeTraits<T>::StorageType storage;
ASSERT_GE(writtenData.size(), sizeof(storage));
memcpy(&storage, writtenData.data(), sizeof(storage));
typename NumericAttributeTraits<T>::WorkingType actual = NumericAttributeTraits<T>::StorageToWorking(storage);
EXPECT_EQ(actual, value);
ASSERT_EQ(model.ChangeListener().DirtyList().size(), 1u);
EXPECT_EQ(model.ChangeListener().DirtyList()[0],
AttributePathParams(test.GetRequest().path.mEndpointId, test.GetRequest().path.mClusterId,
test.GetRequest().path.mAttributeId));
// reset for the next test
model.ChangeListener().DirtyList().clear();
}
// nullable test: write null to make sure content of buffer changed (otherwise it will be a noop for dirty checking)
{
WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZclType));
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
using NumericType = NumericAttributeTraits<T>;
using NullableType = chip::app::DataModel::Nullable<typename NumericType::WorkingType>;
AttributeValueDecoder decoder = test.DecoderFor<NullableType>(NullableType());
// write should succeed
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR);
// dirty: we changed the value to null
ASSERT_EQ(model.ChangeListener().DirtyList().size(), 1u);
EXPECT_EQ(model.ChangeListener().DirtyList()[0],
AttributePathParams(test.GetRequest().path.mEndpointId, test.GetRequest().path.mClusterId,
test.GetRequest().path.mAttributeId));
}
// nullable test
{
WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZclType));
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
AttributeValueDecoder decoder = test.DecoderFor(value);
// write should succeed
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR);
// Validate data after write
chip::ByteSpan writtenData = Test::GetEmberBuffer();
typename NumericAttributeTraits<T>::StorageType storage;
ASSERT_GE(writtenData.size(), sizeof(storage));
memcpy(&storage, writtenData.data(), sizeof(storage));
typename NumericAttributeTraits<T>::WorkingType actual = NumericAttributeTraits<T>::StorageToWorking(storage);
ASSERT_EQ(actual, value);
// dirty a 2nd time when we moved from null to a real value
ASSERT_EQ(model.ChangeListener().DirtyList().size(), 2u);
EXPECT_EQ(model.ChangeListener().DirtyList()[1],
AttributePathParams(test.GetRequest().path.mEndpointId, test.GetRequest().path.mClusterId,
test.GetRequest().path.mAttributeId));
}
}
template <typename T, EmberAfAttributeType ZclType>
void TestEmberScalarNullWrite()
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZclType));
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
using NumericType = NumericAttributeTraits<T>;
using NullableType = chip::app::DataModel::Nullable<typename NumericType::WorkingType>;
AttributeValueDecoder decoder = test.DecoderFor<NullableType>(NullableType());
// write should succeed
ASSERT_TRUE(model.WriteAttribute(test.GetRequest(), decoder).IsSuccess());
// Validate data after write
chip::ByteSpan writtenData = Test::GetEmberBuffer();
using Traits = NumericAttributeTraits<T>;
typename Traits::StorageType storage;
ASSERT_GE(writtenData.size(), sizeof(storage));
memcpy(&storage, writtenData.data(), sizeof(storage));
ASSERT_TRUE(Traits::IsNullValue(storage));
}
template <typename T, EmberAfAttributeType ZclType>
void TestEmberScalarTypeWriteNullValueToNullable()
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZclType));
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
using NumericType = NumericAttributeTraits<T>;
using NullableType = chip::app::DataModel::Nullable<typename NumericType::WorkingType>;
AttributeValueDecoder decoder = test.DecoderFor<NullableType>(NullableType());
// write should fail: we are trying to write null
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_ERROR_WRONG_TLV_TYPE);
}
} // namespace
uint16_t ReadLe16(const void * buffer)
{
const uint8_t * p = reinterpret_cast<const uint8_t *>(buffer);
return chip::Encoding::LittleEndian::Read16(p);
}
void WriteLe16(void * buffer, uint16_t value)
{
uint8_t * p = reinterpret_cast<uint8_t *>(buffer);
chip::Encoding::LittleEndian::Write16(p, value);
}
TEST_F(TestCodegenModelViaMocks, IterateOverEndpoints)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
// This iteration relies on the hard-coding that occurs when mock_ember is used
ReadOnlyBufferBuilder<DataModel::EndpointEntry> endpointsBuilder;
ASSERT_EQ(model.Endpoints(endpointsBuilder), CHIP_NO_ERROR);
auto endpoints = endpointsBuilder.TakeBuffer();
ASSERT_EQ(endpoints.size(), 4u);
EXPECT_EQ(endpoints[0].id, kMockEndpoint1);
EXPECT_EQ(endpoints[0].parentId, kInvalidEndpointId);
EXPECT_EQ(endpoints[0].compositionPattern, EndpointCompositionPattern::kFullFamily);
EXPECT_EQ(endpoints[1].id, kMockEndpoint2);
EXPECT_EQ(endpoints[1].parentId, kInvalidEndpointId);
EXPECT_EQ(endpoints[1].compositionPattern, EndpointCompositionPattern::kTree);
EXPECT_EQ(endpoints[2].id, kMockEndpoint3);
EXPECT_EQ(endpoints[2].parentId, kInvalidEndpointId);
EXPECT_EQ(endpoints[2].compositionPattern, EndpointCompositionPattern::kFullFamily);
}
TEST_F(TestCodegenModelViaMocks, IterateOverServerClusters)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
chip::Test::ResetVersion();
ReadOnlyBufferBuilder<DataModel::ServerClusterEntry> builder;
EXPECT_NE(model.ServerClusters(kEndpointIdThatIsMissing, builder), CHIP_NO_ERROR);
EXPECT_TRUE(builder.IsEmpty());
EXPECT_NE(model.ServerClusters(kInvalidEndpointId, builder), CHIP_NO_ERROR);
EXPECT_TRUE(builder.IsEmpty());
// mock endpoint 1 has 2 mock clusters: 1 and 2
EXPECT_EQ(model.ServerClusters(kMockEndpoint1, builder), CHIP_NO_ERROR);
auto serverClusters = builder.TakeBuffer();
ASSERT_EQ(serverClusters.size(), 2u);
EXPECT_EQ(serverClusters[0].clusterId, MockClusterId(1));
EXPECT_EQ(serverClusters[0].dataVersion, 0u);
EXPECT_EQ(serverClusters[0].flags.Raw(), 0u);
EXPECT_EQ(serverClusters[1].clusterId, MockClusterId(2));
EXPECT_EQ(serverClusters[1].dataVersion, 0u);
EXPECT_EQ(serverClusters[1].flags.Raw(), 0u);
chip::Test::BumpVersion();
EXPECT_EQ(model.ServerClusters(kMockEndpoint1, builder), CHIP_NO_ERROR);
serverClusters = builder.TakeBuffer();
ASSERT_EQ(serverClusters.size(), 2u);
EXPECT_EQ(serverClusters[0].dataVersion, 1u);
EXPECT_EQ(serverClusters[1].dataVersion, 1u);
// mock endpoint 3 has 4 mock clusters: 1 through 4
EXPECT_EQ(model.ServerClusters(kMockEndpoint3, builder), CHIP_NO_ERROR);
serverClusters = builder.TakeBuffer();
ASSERT_EQ(serverClusters.size(), 4u);
EXPECT_EQ(serverClusters[0].clusterId, MockClusterId(1));
EXPECT_EQ(serverClusters[1].clusterId, MockClusterId(2));
EXPECT_EQ(serverClusters[2].clusterId, MockClusterId(3));
EXPECT_EQ(serverClusters[3].clusterId, MockClusterId(4));
}
TEST_F(TestCodegenModelViaMocks, IterateOverClientClusters)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ReadOnlyBufferBuilder<ClusterId> builder;
EXPECT_EQ(model.ClientClusters(kEndpointIdThatIsMissing, builder), CHIP_ERROR_NOT_FOUND);
EXPECT_TRUE(builder.IsEmpty());
EXPECT_EQ(model.ClientClusters(kInvalidEndpointId, builder), CHIP_ERROR_NOT_FOUND);
EXPECT_TRUE(builder.IsEmpty());
// mock endpoint 1 has 2 mock client clusters: 3 and 4
EXPECT_EQ(model.ClientClusters(kMockEndpoint1, builder), CHIP_NO_ERROR);
auto clientClusters = builder.TakeBuffer();
const ClusterId kExpectedClusters1[] = { MockClusterId(3), MockClusterId(4) };
ASSERT_TRUE(clientClusters.data_equal(Span<const ClusterId>(kExpectedClusters1)));
// mock endpoint 2 has 1 mock client clusters: 3(has server side at the same time) and 4
EXPECT_EQ(model.ClientClusters(kMockEndpoint2, builder), CHIP_NO_ERROR);
clientClusters = builder.TakeBuffer();
const ClusterId kExpectedClusters2[] = { MockClusterId(3), MockClusterId(4) };
ASSERT_TRUE(clientClusters.data_equal(Span<const ClusterId>(kExpectedClusters2)));
}
TEST_F(TestCodegenModelViaMocks, IterateOverAttributes)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
// invalid paths should return in "no more data"
ASSERT_TRUE(model.AttributesIgnoreError(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).empty());
ASSERT_TRUE(model.AttributesIgnoreError(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).empty());
ASSERT_TRUE(model.AttributesIgnoreError(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).empty());
ASSERT_TRUE(model.AttributesIgnoreError(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).empty());
// should be able to iterate over valid paths
ReadOnlyBufferBuilder<DataModel::AttributeEntry> builder;
// invalid paths return errors
ASSERT_EQ(model.Attributes(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1)), builder), CHIP_ERROR_NOT_FOUND);
ASSERT_EQ(model.Attributes(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1)), builder), CHIP_ERROR_NOT_FOUND);
ASSERT_EQ(model.Attributes(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10)), builder), CHIP_ERROR_NOT_FOUND);
ASSERT_EQ(model.Attributes(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId), builder), CHIP_ERROR_NOT_FOUND);
EXPECT_EQ(model.Attributes(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2)), builder), CHIP_NO_ERROR);
auto attributes = builder.TakeBuffer();
ASSERT_EQ(attributes.size(), 7u);
ASSERT_EQ(attributes[0].attributeId, ClusterRevision::Id);
ASSERT_FALSE(attributes[0].HasFlags(AttributeQualityFlags::kListAttribute));
ASSERT_EQ(attributes[1].attributeId, FeatureMap::Id);
ASSERT_FALSE(attributes[1].HasFlags(AttributeQualityFlags::kListAttribute));
ASSERT_EQ(attributes[2].attributeId, MockAttributeId(1));
ASSERT_FALSE(attributes[2].HasFlags(AttributeQualityFlags::kListAttribute));
ASSERT_EQ(attributes[3].attributeId, MockAttributeId(2));
ASSERT_TRUE(attributes[3].HasFlags(AttributeQualityFlags::kListAttribute));
// Ends with global list attributes
ASSERT_EQ(attributes[4].attributeId, GeneratedCommandList::Id);
ASSERT_TRUE(attributes[4].HasFlags(AttributeQualityFlags::kListAttribute));
ASSERT_EQ(attributes[5].attributeId, AcceptedCommandList::Id);
ASSERT_TRUE(attributes[5].HasFlags(AttributeQualityFlags::kListAttribute));
ASSERT_EQ(attributes[6].attributeId, AttributeList::Id);
ASSERT_TRUE(attributes[6].HasFlags(AttributeQualityFlags::kListAttribute));
}
TEST_F(TestCodegenModelViaMocks, FindAttribute)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
AttributeFinder finder(&model);
// various non-existent or invalid paths should return no info data
ASSERT_FALSE(finder.Find(ConcreteAttributePath(kInvalidEndpointId, kInvalidClusterId, kInvalidAttributeId)).has_value());
ASSERT_FALSE(finder.Find(ConcreteAttributePath(kInvalidEndpointId, kInvalidClusterId, FeatureMap::Id)).has_value());
ASSERT_FALSE(finder.Find(ConcreteAttributePath(kInvalidEndpointId, MockClusterId(1), FeatureMap::Id)).has_value());
ASSERT_FALSE(finder.Find(ConcreteAttributePath(kMockEndpoint1, kInvalidClusterId, FeatureMap::Id)).has_value());
ASSERT_FALSE(finder.Find(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), FeatureMap::Id)).has_value());
ASSERT_FALSE(finder.Find(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), kInvalidAttributeId)).has_value());
ASSERT_FALSE(finder.Find(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))).has_value());
// valid info
std::optional<AttributeEntry> info = finder.Find(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), FeatureMap::Id));
ASSERT_TRUE(info.has_value());
EXPECT_FALSE(info->HasFlags(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access)
// Mocks always set everything as R/W with administrative privileges
EXPECT_EQ(info->GetReadPrivilege(), chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access)
EXPECT_EQ(info->GetWritePrivilege(), chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access)
info = finder.Find(ConcreteAttributePath(kMockEndpoint2, MockClusterId(2), MockAttributeId(2)));
ASSERT_TRUE(info.has_value());
EXPECT_TRUE(info->HasFlags(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access)
EXPECT_EQ(info->GetReadPrivilege(), chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access)
EXPECT_EQ(info->GetWritePrivilege(), chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access)
// test a read-only attribute, which will not have a write privilege
info = finder.Find(ConcreteAttributePath(kMockEndpoint3, MockClusterId(3), kReadOnlyAttributeId));
ASSERT_TRUE(info.has_value());
EXPECT_FALSE(info->HasFlags(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access)
EXPECT_EQ(info->GetReadPrivilege(), chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access)
EXPECT_FALSE(info->GetWritePrivilege().has_value()); // NOLINT(bugprone-unchecked-optional-access)
}
// global attributes are EXPLICITLY supported
TEST_F(TestCodegenModelViaMocks, GlobalAttributeInfo)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
AttributeFinder finder(&model);
std::optional<AttributeEntry> info = finder.Find(
ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::GeneratedCommandList::Id));
ASSERT_TRUE(info.has_value());
info = finder.Find(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::AttributeList::Id));
ASSERT_TRUE(info.has_value());
info = finder.Find(
ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::AcceptedCommandList::Id));
ASSERT_TRUE(info.has_value());
}
TEST_F(TestCodegenModelViaMocks, IterateOverAcceptedCommands)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> builder;
// invalid paths should return in "no more data"
ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1)), builder),
CHIP_ERROR_NOT_FOUND);
ASSERT_TRUE(builder.IsEmpty());
ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1)), builder), CHIP_ERROR_NOT_FOUND);
ASSERT_TRUE(builder.IsEmpty());
ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10)), builder), CHIP_ERROR_NOT_FOUND);
ASSERT_TRUE(builder.IsEmpty());
ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId), builder), CHIP_ERROR_NOT_FOUND);
ASSERT_TRUE(builder.IsEmpty());
ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2)), builder), CHIP_NO_ERROR);
ASSERT_EQ(builder.Size(), 3u);
auto cmds = builder.TakeBuffer();
// took ownership
ASSERT_EQ(builder.Size(), 0u);
ASSERT_TRUE(builder.IsEmpty());
ASSERT_EQ(cmds.size(), 3u);
ASSERT_EQ(cmds[0].commandId, 1u);
ASSERT_EQ(cmds[1].commandId, 2u);
ASSERT_EQ(cmds[2].commandId, 23u);
}
TEST_F(TestCodegenModelViaMocks, IterateOverGeneratedCommands)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ReadOnlyBufferBuilder<CommandId> builder;
// invalid paths should return in "no more data"
ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1)), builder),
CHIP_ERROR_NOT_FOUND);
ASSERT_TRUE(builder.IsEmpty());
ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1)), builder), CHIP_ERROR_NOT_FOUND);
ASSERT_TRUE(builder.IsEmpty());
ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10)), builder), CHIP_ERROR_NOT_FOUND);
ASSERT_TRUE(builder.IsEmpty());
ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId), builder), CHIP_ERROR_NOT_FOUND);
ASSERT_TRUE(builder.IsEmpty());
// should be able to iterate over valid paths
ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2)), builder), CHIP_NO_ERROR);
auto cmds = builder.TakeBuffer();
const CommandId expectedCommands2[] = { 2, 10 };
ASSERT_TRUE(cmds.data_equal(Span<const CommandId>(expectedCommands2)));
ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint2, MockClusterId(3)), builder), CHIP_NO_ERROR);
cmds = builder.TakeBuffer();
const CommandId expectedCommands3[] = { 4, 6 };
ASSERT_TRUE(cmds.data_equal(Span<const CommandId>(expectedCommands3)));
}
TEST_F(TestCodegenModelViaMocks, AcceptedGeneratedCommandsOnInvalidEndpoints)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
// register a CHI on ALL endpoints
CustomListCommandHandler handler(chip::NullOptional, MockClusterId(1));
handler.SetHandleCommands(true);
ReadOnlyBufferBuilder<CommandId> generatedBuilder;
ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> acceptedBuilder;
// valid endpoint will result in valid data (even though list is empty)
ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), generatedBuilder), CHIP_NO_ERROR);
ASSERT_TRUE(generatedBuilder.IsEmpty());
ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), acceptedBuilder), CHIP_NO_ERROR);
ASSERT_TRUE(acceptedBuilder.IsEmpty());
// Invalid endpoint fails - we will get no commands there (even though CHI is registered)
ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1)), generatedBuilder),
CHIP_ERROR_NOT_FOUND);
ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1)), acceptedBuilder),
CHIP_ERROR_NOT_FOUND);
// same for invalid cluster ID
ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(0x1123)), generatedBuilder),
CHIP_ERROR_NOT_FOUND);
ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(0x1123)), acceptedBuilder),
CHIP_ERROR_NOT_FOUND);
}
TEST_F(TestCodegenModelViaMocks, CommandHandlerInterfaceCommandHandling)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
// Command handler interface is capable to override accepted and generated commands.
// Validate that these work
CustomListCommandHandler handler(MakeOptional(kMockEndpoint1), MockClusterId(1));
ReadOnlyBufferBuilder<CommandId> generatedBuilder;
ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> acceptedBuilder;
// At this point, without overrides, there should be no accepted/generated commands
ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), generatedBuilder), CHIP_NO_ERROR);
ASSERT_TRUE(generatedBuilder.IsEmpty());
ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), acceptedBuilder), CHIP_NO_ERROR);
ASSERT_TRUE(acceptedBuilder.IsEmpty());
handler.SetOverrideAccepted(true);
handler.SetOverrideGenerated(true);
// with overrides, the list is still empty ...
ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), generatedBuilder), CHIP_NO_ERROR);
ASSERT_TRUE(generatedBuilder.IsEmpty());
ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), acceptedBuilder), CHIP_NO_ERROR);
ASSERT_TRUE(acceptedBuilder.IsEmpty());
// set some overrides
handler.AcceptedVec().push_back(1234);
handler.AcceptedVec().push_back(999);
handler.GeneratedVec().push_back(33);
ASSERT_EQ(model.AcceptedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), acceptedBuilder), CHIP_NO_ERROR);
auto acceptedCommands = acceptedBuilder.TakeBuffer();
ASSERT_EQ(acceptedCommands.size(), 2u);
ASSERT_EQ(acceptedCommands[0].commandId, 1234u);
ASSERT_EQ(acceptedCommands[1].commandId, 999u);
ASSERT_EQ(model.GeneratedCommands(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)), generatedBuilder), CHIP_NO_ERROR);
auto generatedCommands = generatedBuilder.TakeBuffer();
const CommandId expectedGeneratedCommands[] = { 33 };
ASSERT_TRUE(generatedCommands.data_equal(Span<const CommandId>(expectedGeneratedCommands)));
}
TEST_F(TestCodegenModelViaMocks, ReadForInvalidGlobalAttributePath)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
{
ReadOperation testRequest(kEndpointIdThatIsMissing, MockClusterId(1), AttributeList::Id);
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding();
ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), Status::UnsupportedEndpoint);
}
{
ReadOperation testRequest(kMockEndpoint1, kInvalidClusterId, AttributeList::Id);
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding();
ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), Status::UnsupportedCluster);
}
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeInvalidRead)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
// Invalid attribute
{
ReadOperation testRequest(kMockEndpoint1, MockClusterId(1), MockAttributeId(10));
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
std::unique_ptr<AttributeValueEncoder> 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<AttributeValueEncoder> 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<AttributeValueEncoder> encoder = testRequest.StartEncoding();
ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), Status::UnsupportedEndpoint);
}
}
TEST_F(TestCodegenModelViaMocks, AccessInterfaceUnsupportedRead)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
const ConcreteAttributePath kTestPath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE));
ReadOperation testRequest(kTestPath);
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
testRequest.SetPathExpanded(true);
RegisteredAttributeAccessInterface<UnsupportedReadAccessInterface> aai(kTestPath);
// For expanded paths, unsupported read from AAI (i.e. reading write-only data)
// succeed without attempting to encode.
// This is temporary until ACL checks are moved inside the IM/ReportEngine
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding();
ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR);
ASSERT_FALSE(encoder->TriedEncode());
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt32S)
{
TestEmberScalarTypeRead<int32_t, ZCL_INT32S_ATTRIBUTE_TYPE>(-1234);
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeReadEnum16)
{
TestEmberScalarTypeRead<uint16_t, ZCL_ENUM16_ATTRIBUTE_TYPE>(0x1234);
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeReadFloat)
{
TestEmberScalarTypeRead<float, ZCL_SINGLE_ATTRIBUTE_TYPE>(0.625);
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeReadDouble)
{
TestEmberScalarTypeRead<double, ZCL_DOUBLE_ATTRIBUTE_TYPE>(0.625);
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt24U)
{
TestEmberScalarTypeRead<OddSizedInteger<3, false>, ZCL_INT24U_ATTRIBUTE_TYPE>(0x1234AB);
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt32U)
{
TestEmberScalarTypeRead<uint32_t, ZCL_INT32U_ATTRIBUTE_TYPE>(0x1234ABCD);
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt40U)
{
TestEmberScalarTypeRead<OddSizedInteger<5, false>, ZCL_INT40U_ATTRIBUTE_TYPE>(0x1122334455);
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt48U)
{
TestEmberScalarTypeRead<OddSizedInteger<6, false>, ZCL_INT48U_ATTRIBUTE_TYPE>(0xAABB11223344);
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt56U)
{
TestEmberScalarTypeRead<OddSizedInteger<7, false>, ZCL_INT56U_ATTRIBUTE_TYPE>(0xAABB11223344);
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeReadBool)
{
TestEmberScalarTypeRead<bool, ZCL_BOOLEAN_ATTRIBUTE_TYPE>(true);
TestEmberScalarTypeRead<bool, ZCL_BOOLEAN_ATTRIBUTE_TYPE>(false);
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeReadInt8U)
{
TestEmberScalarTypeRead<uint8_t, ZCL_INT8U_ATTRIBUTE_TYPE>(0x12);
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeReadNulls)
{
TestEmberScalarNullRead<uint8_t, ZCL_INT8U_ATTRIBUTE_TYPE>();
TestEmberScalarNullRead<uint16_t, ZCL_INT16U_ATTRIBUTE_TYPE>();
TestEmberScalarNullRead<OddSizedInteger<3, false>, ZCL_INT24U_ATTRIBUTE_TYPE>();
TestEmberScalarNullRead<uint32_t, ZCL_INT32U_ATTRIBUTE_TYPE>();
TestEmberScalarNullRead<OddSizedInteger<5, false>, ZCL_INT40U_ATTRIBUTE_TYPE>();
TestEmberScalarNullRead<OddSizedInteger<6, false>, ZCL_INT48U_ATTRIBUTE_TYPE>();
TestEmberScalarNullRead<OddSizedInteger<7, false>, ZCL_INT56U_ATTRIBUTE_TYPE>();
TestEmberScalarNullRead<uint64_t, ZCL_INT64U_ATTRIBUTE_TYPE>();
TestEmberScalarNullRead<int8_t, ZCL_INT8S_ATTRIBUTE_TYPE>();
TestEmberScalarNullRead<int16_t, ZCL_INT16S_ATTRIBUTE_TYPE>();
TestEmberScalarNullRead<OddSizedInteger<3, true>, ZCL_INT24S_ATTRIBUTE_TYPE>();
TestEmberScalarNullRead<int32_t, ZCL_INT32S_ATTRIBUTE_TYPE>();
TestEmberScalarNullRead<OddSizedInteger<5, true>, ZCL_INT40S_ATTRIBUTE_TYPE>();
TestEmberScalarNullRead<OddSizedInteger<6, true>, ZCL_INT48S_ATTRIBUTE_TYPE>();
TestEmberScalarNullRead<OddSizedInteger<7, true>, ZCL_INT56S_ATTRIBUTE_TYPE>();
TestEmberScalarNullRead<int64_t, ZCL_INT64S_ATTRIBUTE_TYPE>();
TestEmberScalarNullRead<bool, ZCL_BOOLEAN_ATTRIBUTE_TYPE>();
TestEmberScalarNullRead<float, ZCL_SINGLE_ATTRIBUTE_TYPE>();
TestEmberScalarNullRead<double, ZCL_DOUBLE_ATTRIBUTE_TYPE>();
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeReadErrorReading)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
{
ReadOperation testRequest(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE));
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
chip::Test::SetEmberReadOutput(Protocols::InteractionModel::Status::Failure);
// Actual read via an encoder
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding();
ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), Status::Failure);
}
{
ReadOperation testRequest(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE));
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
chip::Test::SetEmberReadOutput(Protocols::InteractionModel::Status::Busy);
// Actual read via an encoder
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding();
ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), Status::Busy);
}
// reset things to success to not affect other tests
chip::Test::SetEmberReadOutput(ByteSpan());
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeReadNullOctetString)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
ReadOperation testRequest(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE));
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
// NOTE: This is a pascal string of size 0xFFFF which for null strings is a null marker
char data[] = "\xFF\xFFInvalid length string is null";
chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast<const uint8_t *>(data), sizeof(data)));
// Actual read via an encoder
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding();
ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after read
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.GetRequest().path);
// data element should be null for the given 0xFFFF length
ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Null);
chip::app::DataModel::Nullable<ByteSpan> actual;
ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR);
ASSERT_TRUE(actual.IsNull());
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeReadOctetString)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
ReadOperation testRequest(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE));
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
// NOTE: This is a pascal string, so actual data is "test"
// the longer encoding is to make it clear we do not encode the overflow
char data[] = "\0\0testing here with overflow";
WriteLe16(data, 4);
chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast<const uint8_t *>(data), sizeof(data)));
// Actual read via an encoder
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding();
ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after read
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
const DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.GetRequest().path);
// data element should be a encoded byte string as this is what the attribute type is
ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_ByteString);
ByteSpan actual;
ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR);
ByteSpan expected(reinterpret_cast<const uint8_t *>(data + 2), 4);
ASSERT_TRUE(actual.data_equal(expected));
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeReadLongOctetString)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
ReadOperation testRequest(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE));
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
// NOTE: This is a pascal string, so actual data is "test"
// the longer encoding is to make it clear we do not encode the overflow
const char data[] = "\x04testing here with overflow";
chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast<const uint8_t *>(data), sizeof(data)));
// Actual read via an encoder
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding();
ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after read
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
const DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.GetRequest().path);
// data element should be a encoded byte string as this is what the attribute type is
ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_ByteString);
ByteSpan actual;
ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR);
ByteSpan expected(reinterpret_cast<const uint8_t *>(data + 1), 4);
ASSERT_TRUE(actual.data_equal(expected));
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeReadShortString)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
ReadOperation testRequest(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_CHAR_STRING_ATTRIBUTE_TYPE));
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
// NOTE: This is a pascal string, so actual data is "abcde"
// the longer encoding is to make it clear we do not encode the overflow
char data[] = "\0abcdef...this is the alphabet";
*data = 5;
chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast<const uint8_t *>(data), sizeof(data)));
// Actual read via an encoder
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding();
ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after reading
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
const DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.GetRequest().path);
// data element should be a encoded byte string as this is what the attribute type is
ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_UTF8String);
CharSpan actual;
ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR);
ASSERT_TRUE(actual.data_equal("abcde"_span));
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeReadLongString)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
ReadOperation testRequest(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE));
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
// NOTE: This is a pascal string, so actual data is "abcde"
// the longer encoding is to make it clear we do not encode the overflow
char data[] = "\0\0abcdef...this is the alphabet";
WriteLe16(data, 5);
chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast<const uint8_t *>(data), sizeof(data)));
// Actual read via an encoder
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding();
ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after reading
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
const DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.GetRequest().path);
// data element should be a encoded byte string as this is what the attribute type is
ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_UTF8String);
CharSpan actual;
ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR);
ASSERT_TRUE(actual.data_equal("abcde"_span));
}
TEST_F(TestCodegenModelViaMocks, AttributeAccessInterfaceStructRead)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE));
ReadOperation testRequest(kStructPath);
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
RegisteredAttributeAccessInterface<StructAttributeAccessInterface> aai(kStructPath);
aai->SetReturnedData(Clusters::UnitTesting::Structs::SimpleStruct::Type{
.a = 123,
.b = true,
.e = "foo"_span,
.g = 0.5,
.h = 0.125,
});
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding();
ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after read
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.GetRequest().path);
ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Structure);
Clusters::UnitTesting::Structs::SimpleStruct::DecodableType actual;
ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR);
ASSERT_EQ(actual.a, 123);
ASSERT_EQ(actual.b, true);
ASSERT_EQ(actual.g, 0.5);
ASSERT_EQ(actual.h, 0.125);
ASSERT_TRUE(actual.e.data_equal("foo"_span));
}
TEST_F(TestCodegenModelViaMocks, AttributeAccessInterfaceReadError)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE));
ReadOperation testRequest(kStructPath);
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
RegisteredAttributeAccessInterface<ErrorAccessInterface> aai(kStructPath, CHIP_ERROR_KEY_NOT_FOUND);
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding();
ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_ERROR_KEY_NOT_FOUND);
}
TEST_F(TestCodegenModelViaMocks, AttributeAccessInterfaceListRead)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_ARRAY_ATTRIBUTE_TYPE));
ReadOperation testRequest(kStructPath);
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
RegisteredAttributeAccessInterface<ListAttributeAcessInterface> aai(kStructPath);
constexpr unsigned kDataCount = 5;
aai->SetReturnedData(Clusters::UnitTesting::Structs::SimpleStruct::Type{
.b = true,
.e = "xyz"_span,
.g = 0.25,
.h = 0.5,
});
aai->SetReturnedDataCount(kDataCount);
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding();
ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after read
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.GetRequest().path);
ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Array);
std::vector<Clusters::UnitTesting::Structs::SimpleStruct::DecodableType> items;
ASSERT_EQ(DecodeList(encodedData.dataReader, items), CHIP_NO_ERROR);
ASSERT_EQ(items.size(), kDataCount);
for (unsigned i = 0; i < kDataCount; i++)
{
Clusters::UnitTesting::Structs::SimpleStruct::DecodableType & actual = items[i];
ASSERT_EQ(actual.a, static_cast<uint8_t>(i & 0xFF));
ASSERT_EQ(actual.b, true);
ASSERT_EQ(actual.g, 0.25);
ASSERT_EQ(actual.h, 0.5);
ASSERT_TRUE(actual.e.data_equal("xyz"_span));
}
}
TEST_F(TestCodegenModelViaMocks, AttributeAccessInterfaceListOverflowRead)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_ARRAY_ATTRIBUTE_TYPE));
ReadOperation testRequest(kStructPath);
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
RegisteredAttributeAccessInterface<ListAttributeAcessInterface> aai(kStructPath);
constexpr unsigned kDataCount = 1024;
aai->SetReturnedData(Clusters::UnitTesting::Structs::SimpleStruct::Type{
.b = true,
.e = "thisislongertofillupfaster"_span,
.g = 0.25,
.h = 0.5,
});
aai->SetReturnedDataCount(kDataCount);
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding();
// NOTE: overflow, however data should be valid. Technically both NO_MEMORY and BUFFER_TOO_SMALL
// should be ok here, however we know buffer-too-small is the error in this case hence
// the compare (easier to write the test and read the output)
ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_ERROR_BUFFER_TOO_SMALL);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after read
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.GetRequest().path);
ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Array);
std::vector<Clusters::UnitTesting::Structs::SimpleStruct::DecodableType> items;
ASSERT_EQ(DecodeList(encodedData.dataReader, items), CHIP_NO_ERROR);
// On last check, 16 items can be encoded. Set some non-zero range to be enforced here that
// SOME list items are actually encoded. Actual lower bound here IS ARBITRARY and was picked
// to just ensure non-zero item count for checks.
ASSERT_GT(items.size(), 5u);
ASSERT_LT(items.size(), kDataCount);
for (unsigned i = 0; i < items.size(); i++)
{
Clusters::UnitTesting::Structs::SimpleStruct::DecodableType & actual = items[i];
ASSERT_EQ(actual.a, static_cast<uint8_t>(i & 0xFF));
ASSERT_EQ(actual.b, true);
ASSERT_EQ(actual.g, 0.25);
ASSERT_EQ(actual.h, 0.5);
ASSERT_TRUE(actual.e.data_equal("thisislongertofillupfaster"_span));
}
}
TEST_F(TestCodegenModelViaMocks, AttributeAccessInterfaceListIncrementalRead)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_ARRAY_ATTRIBUTE_TYPE));
ReadOperation testRequest(kStructPath);
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
RegisteredAttributeAccessInterface<ListAttributeAcessInterface> aai(kStructPath);
constexpr unsigned kDataCount = 1024;
constexpr unsigned kEncodeIndexStart = 101;
aai->SetReturnedData(Clusters::UnitTesting::Structs::SimpleStruct::Type{
.b = true,
.e = "thisislongertofillupfaster"_span,
.g = 0.25,
.h = 0.5,
});
aai->SetReturnedDataCount(kDataCount);
AttributeEncodeState encodeState;
encodeState.SetCurrentEncodingListIndex(kEncodeIndexStart);
std::unique_ptr<AttributeValueEncoder> encoder =
testRequest.StartEncoding(ReadOperation::EncodingParams().SetEncodingState(encodeState));
// NOTE: overflow, however data should be valid. Technically both NO_MEMORY and BUFFER_TOO_SMALL
// should be ok here, however we know buffer-too-small is the error in this case hence
// the compare (easier to write the test and read the output)
ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_ERROR_BUFFER_TOO_SMALL);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after read
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.GetEncodedIBs().Decode(attribute_data), CHIP_NO_ERROR);
// Incremental encodes are separate list items, repeated
// actual size IS ARBITRARY (current test sets it at 11)
ASSERT_GT(attribute_data.size(), 3u);
for (unsigned i = 0; i < attribute_data.size(); i++)
{
DecodedAttributeData & encodedData = attribute_data[i];
ASSERT_EQ(encodedData.attributePath.mEndpointId, testRequest.GetRequest().path.mEndpointId);
ASSERT_EQ(encodedData.attributePath.mClusterId, testRequest.GetRequest().path.mClusterId);
ASSERT_EQ(encodedData.attributePath.mAttributeId, testRequest.GetRequest().path.mAttributeId);
ASSERT_EQ(encodedData.attributePath.mListOp, ConcreteDataAttributePath::ListOperation::AppendItem);
// individual structures encoded in each item
ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Structure);
Clusters::UnitTesting::Structs::SimpleStruct::DecodableType actual;
ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR);
ASSERT_EQ(actual.a, static_cast<uint8_t>((i + kEncodeIndexStart) & 0xFF));
ASSERT_EQ(actual.b, true);
ASSERT_EQ(actual.g, 0.25);
ASSERT_EQ(actual.h, 0.5);
ASSERT_TRUE(actual.e.data_equal("thisislongertofillupfaster"_span));
}
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteBasicTypes)
{
TestEmberScalarTypeWrite<uint8_t, ZCL_INT8U_ATTRIBUTE_TYPE>(0x12);
TestEmberScalarTypeWrite<uint16_t, ZCL_ENUM16_ATTRIBUTE_TYPE>(0x1234);
TestEmberScalarTypeWrite<OddSizedInteger<3, false>, ZCL_INT24U_ATTRIBUTE_TYPE>(0x112233);
TestEmberScalarTypeWrite<uint32_t, ZCL_INT32U_ATTRIBUTE_TYPE>(0x11223344);
TestEmberScalarTypeWrite<OddSizedInteger<5, false>, ZCL_INT40U_ATTRIBUTE_TYPE>(0x1122334455ULL);
TestEmberScalarTypeWrite<OddSizedInteger<6, false>, ZCL_INT48U_ATTRIBUTE_TYPE>(0x112233445566ULL);
TestEmberScalarTypeWrite<OddSizedInteger<7, false>, ZCL_INT56U_ATTRIBUTE_TYPE>(0x11223344556677ULL);
TestEmberScalarTypeWrite<uint64_t, ZCL_INT64U_ATTRIBUTE_TYPE>(0x1122334455667788ULL);
TestEmberScalarTypeWrite<int8_t, ZCL_INT8S_ATTRIBUTE_TYPE>(-10);
TestEmberScalarTypeWrite<int16_t, ZCL_INT16S_ATTRIBUTE_TYPE>(-123);
TestEmberScalarTypeWrite<OddSizedInteger<3, true>, ZCL_INT24S_ATTRIBUTE_TYPE>(-1234);
TestEmberScalarTypeWrite<int32_t, ZCL_INT32S_ATTRIBUTE_TYPE>(-12345);
TestEmberScalarTypeWrite<OddSizedInteger<5, true>, ZCL_INT40S_ATTRIBUTE_TYPE>(-123456);
TestEmberScalarTypeWrite<OddSizedInteger<6, true>, ZCL_INT48S_ATTRIBUTE_TYPE>(-1234567);
TestEmberScalarTypeWrite<OddSizedInteger<7, true>, ZCL_INT56S_ATTRIBUTE_TYPE>(-12345678);
TestEmberScalarTypeWrite<int64_t, ZCL_INT64S_ATTRIBUTE_TYPE>(-123456789);
TestEmberScalarTypeWrite<bool, ZCL_BOOLEAN_ATTRIBUTE_TYPE>(true);
TestEmberScalarTypeWrite<bool, ZCL_BOOLEAN_ATTRIBUTE_TYPE>(false);
TestEmberScalarTypeWrite<float, ZCL_SINGLE_ATTRIBUTE_TYPE>(0.625);
TestEmberScalarTypeWrite<double, ZCL_DOUBLE_ATTRIBUTE_TYPE>(0.625);
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteInvalidValueToNullable)
{
TestEmberScalarTypeWriteNullValueToNullable<uint8_t, ZCL_INT8U_ATTRIBUTE_TYPE>();
TestEmberScalarTypeWriteNullValueToNullable<uint16_t, ZCL_ENUM16_ATTRIBUTE_TYPE>();
TestEmberScalarTypeWriteNullValueToNullable<OddSizedInteger<3, false>, ZCL_INT24U_ATTRIBUTE_TYPE>();
TestEmberScalarTypeWriteNullValueToNullable<uint32_t, ZCL_INT32U_ATTRIBUTE_TYPE>();
TestEmberScalarTypeWriteNullValueToNullable<OddSizedInteger<5, false>, ZCL_INT40U_ATTRIBUTE_TYPE>();
TestEmberScalarTypeWriteNullValueToNullable<OddSizedInteger<6, false>, ZCL_INT48U_ATTRIBUTE_TYPE>();
TestEmberScalarTypeWriteNullValueToNullable<OddSizedInteger<7, false>, ZCL_INT56U_ATTRIBUTE_TYPE>();
TestEmberScalarTypeWriteNullValueToNullable<uint64_t, ZCL_INT64U_ATTRIBUTE_TYPE>();
TestEmberScalarTypeWriteNullValueToNullable<int8_t, ZCL_INT8S_ATTRIBUTE_TYPE>();
TestEmberScalarTypeWriteNullValueToNullable<int16_t, ZCL_INT16S_ATTRIBUTE_TYPE>();
TestEmberScalarTypeWriteNullValueToNullable<OddSizedInteger<3, true>, ZCL_INT24S_ATTRIBUTE_TYPE>();
TestEmberScalarTypeWriteNullValueToNullable<int32_t, ZCL_INT32S_ATTRIBUTE_TYPE>();
TestEmberScalarTypeWriteNullValueToNullable<OddSizedInteger<5, true>, ZCL_INT40S_ATTRIBUTE_TYPE>();
TestEmberScalarTypeWriteNullValueToNullable<OddSizedInteger<6, true>, ZCL_INT48S_ATTRIBUTE_TYPE>();
TestEmberScalarTypeWriteNullValueToNullable<OddSizedInteger<7, true>, ZCL_INT56S_ATTRIBUTE_TYPE>();
TestEmberScalarTypeWriteNullValueToNullable<int64_t, ZCL_INT64S_ATTRIBUTE_TYPE>();
TestEmberScalarTypeWriteNullValueToNullable<bool, ZCL_BOOLEAN_ATTRIBUTE_TYPE>();
TestEmberScalarTypeWriteNullValueToNullable<bool, ZCL_BOOLEAN_ATTRIBUTE_TYPE>();
TestEmberScalarTypeWriteNullValueToNullable<float, ZCL_SINGLE_ATTRIBUTE_TYPE>();
TestEmberScalarTypeWriteNullValueToNullable<double, ZCL_DOUBLE_ATTRIBUTE_TYPE>();
}
TEST_F(TestCodegenModelViaMocks, EmberTestWriteReservedNullPlaceholderToNullable)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_INT32U_ATTRIBUTE_TYPE));
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
using NumericType = NumericAttributeTraits<uint32_t>;
using NullableType = chip::app::DataModel::Nullable<typename NumericType::WorkingType>;
AttributeValueDecoder decoder = test.DecoderFor<NullableType>(0xFFFFFFFF);
// write should fail: we are trying to write null which is out of range
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::ConstraintError);
}
TEST_F(TestCodegenModelViaMocks, EmberTestWriteOutOfRepresentableRangeOddIntegerNonNullable)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_INT24U_ATTRIBUTE_TYPE));
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
using NumericType = NumericAttributeTraits<uint32_t>;
using NullableType = chip::app::DataModel::Nullable<typename NumericType::WorkingType>;
AttributeValueDecoder decoder = test.DecoderFor<NullableType>(0x1223344);
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_IM_GLOBAL_STATUS(ConstraintError));
}
TEST_F(TestCodegenModelViaMocks, EmberTestWriteOutOfRepresentableRangeOddIntegerNullable)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_INT24U_ATTRIBUTE_TYPE));
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
using NumericType = NumericAttributeTraits<uint32_t>;
using NullableType = chip::app::DataModel::Nullable<typename NumericType::WorkingType>;
AttributeValueDecoder decoder = test.DecoderFor<NullableType>(0x1223344);
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_IM_GLOBAL_STATUS(ConstraintError));
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteBasicTypesLowestValue)
{
TestEmberScalarTypeWrite<int8_t, ZCL_INT8S_ATTRIBUTE_TYPE>(-127);
TestEmberScalarTypeWrite<int16_t, ZCL_INT16S_ATTRIBUTE_TYPE>(-32767);
TestEmberScalarTypeWrite<OddSizedInteger<3, true>, ZCL_INT24S_ATTRIBUTE_TYPE>(-8388607);
TestEmberScalarTypeWrite<int32_t, ZCL_INT32S_ATTRIBUTE_TYPE>(-2147483647);
TestEmberScalarTypeWrite<OddSizedInteger<5, true>, ZCL_INT40S_ATTRIBUTE_TYPE>(-549755813887);
TestEmberScalarTypeWrite<OddSizedInteger<6, true>, ZCL_INT48S_ATTRIBUTE_TYPE>(-140737488355327);
TestEmberScalarTypeWrite<OddSizedInteger<7, true>, ZCL_INT56S_ATTRIBUTE_TYPE>(-36028797018963967);
TestEmberScalarTypeWrite<int64_t, ZCL_INT64S_ATTRIBUTE_TYPE>(-9223372036854775807);
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteNulls)
{
TestEmberScalarNullWrite<uint8_t, ZCL_INT8U_ATTRIBUTE_TYPE>();
TestEmberScalarNullWrite<uint16_t, ZCL_ENUM16_ATTRIBUTE_TYPE>();
TestEmberScalarNullWrite<OddSizedInteger<3, false>, ZCL_INT24U_ATTRIBUTE_TYPE>();
TestEmberScalarNullWrite<uint32_t, ZCL_INT32U_ATTRIBUTE_TYPE>();
TestEmberScalarNullWrite<OddSizedInteger<5, false>, ZCL_INT40U_ATTRIBUTE_TYPE>();
TestEmberScalarNullWrite<OddSizedInteger<6, false>, ZCL_INT48U_ATTRIBUTE_TYPE>();
TestEmberScalarNullWrite<OddSizedInteger<7, false>, ZCL_INT56U_ATTRIBUTE_TYPE>();
TestEmberScalarNullWrite<uint64_t, ZCL_INT64U_ATTRIBUTE_TYPE>();
TestEmberScalarNullWrite<int8_t, ZCL_INT8S_ATTRIBUTE_TYPE>();
TestEmberScalarNullWrite<int16_t, ZCL_INT16S_ATTRIBUTE_TYPE>();
TestEmberScalarNullWrite<OddSizedInteger<3, true>, ZCL_INT24S_ATTRIBUTE_TYPE>();
TestEmberScalarNullWrite<int32_t, ZCL_INT32S_ATTRIBUTE_TYPE>();
TestEmberScalarNullWrite<OddSizedInteger<5, true>, ZCL_INT40S_ATTRIBUTE_TYPE>();
TestEmberScalarNullWrite<OddSizedInteger<6, true>, ZCL_INT48S_ATTRIBUTE_TYPE>();
TestEmberScalarNullWrite<OddSizedInteger<7, true>, ZCL_INT56S_ATTRIBUTE_TYPE>();
TestEmberScalarNullWrite<int64_t, ZCL_INT64S_ATTRIBUTE_TYPE>();
TestEmberScalarNullWrite<bool, ZCL_BOOLEAN_ATTRIBUTE_TYPE>();
TestEmberScalarNullWrite<float, ZCL_SINGLE_ATTRIBUTE_TYPE>();
TestEmberScalarNullWrite<double, ZCL_DOUBLE_ATTRIBUTE_TYPE>();
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteShortString)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_CHAR_STRING_ATTRIBUTE_TYPE));
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
AttributeValueDecoder decoder = test.DecoderFor<CharSpan>("hello world"_span);
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR);
chip::ByteSpan writtenData = GetEmberBuffer();
chip::CharSpan asCharSpan(reinterpret_cast<const char *>(writtenData.data()), writtenData[0] + 1);
ASSERT_TRUE(asCharSpan.data_equal("\x0Bhello world"_span));
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteLongStringOutOfBounds)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
WriteOperation test(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE));
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
// Mocks allow for 16 bytes only by default for string attributes
AttributeValueDecoder decoder = test.DecoderFor<CharSpan>(
"this is a very long string that will be longer than the default attribute size for our mocks"_span);
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::InvalidValue);
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteLongString)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
WriteOperation test(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE));
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
AttributeValueDecoder decoder = test.DecoderFor<CharSpan>("text"_span);
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR);
chip::ByteSpan writtenData = GetEmberBuffer();
uint16_t len = ReadLe16(writtenData.data());
EXPECT_EQ(len, 4);
chip::CharSpan asCharSpan(reinterpret_cast<const char *>(writtenData.data() + 2), 4);
ASSERT_TRUE(asCharSpan.data_equal("text"_span));
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteNullableLongStringValue)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE));
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
AttributeValueDecoder decoder =
test.DecoderFor<chip::app::DataModel::Nullable<CharSpan>>(chip::app::DataModel::MakeNullable("text"_span));
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR);
chip::ByteSpan writtenData = GetEmberBuffer();
uint16_t len = ReadLe16(writtenData.data());
EXPECT_EQ(len, 4);
chip::CharSpan asCharSpan(reinterpret_cast<const char *>(writtenData.data() + 2), 4);
ASSERT_TRUE(asCharSpan.data_equal("text"_span));
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteLongNullableStringNull)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE));
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
AttributeValueDecoder decoder =
test.DecoderFor<chip::app::DataModel::Nullable<CharSpan>>(chip::app::DataModel::Nullable<CharSpan>());
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR);
chip::ByteSpan writtenData = GetEmberBuffer();
ASSERT_EQ(writtenData[0], 0xFF);
ASSERT_EQ(writtenData[1], 0xFF);
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteShortBytes)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE));
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
uint8_t buffer[] = { 11, 12, 13 };
AttributeValueDecoder decoder = test.DecoderFor<ByteSpan>(ByteSpan(buffer));
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR);
chip::ByteSpan writtenData = GetEmberBuffer();
EXPECT_EQ(writtenData[0], 3u);
EXPECT_EQ(writtenData[1], 11u);
EXPECT_EQ(writtenData[2], 12u);
EXPECT_EQ(writtenData[3], 13u);
}
TEST_F(TestCodegenModelViaMocks, EmberAttributeWriteLongBytes)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
WriteOperation test(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE));
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
uint8_t buffer[] = { 11, 12, 13 };
AttributeValueDecoder decoder = test.DecoderFor<ByteSpan>(ByteSpan(buffer));
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR);
chip::ByteSpan writtenData = GetEmberBuffer();
uint16_t len = ReadLe16(writtenData.data());
EXPECT_EQ(len, 3);
EXPECT_EQ(writtenData[2], 11u);
EXPECT_EQ(writtenData[3], 12u);
EXPECT_EQ(writtenData[4], 13u);
}
TEST_F(TestCodegenModelViaMocks, 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<int32_t>(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_F(TestCodegenModelViaMocks, WriteToInvalidPath)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
{
WriteOperation test(kInvalidEndpointId, MockClusterId(1234), 1234);
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
AttributeValueDecoder decoder = test.DecoderFor<int32_t>(1234);
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::UnsupportedEndpoint);
}
{
WriteOperation test(kMockEndpoint1, MockClusterId(1234), 1234);
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
AttributeValueDecoder decoder = test.DecoderFor<int32_t>(1234);
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::UnsupportedCluster);
}
{
WriteOperation test(kMockEndpoint1, MockClusterId(1), 1234);
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
AttributeValueDecoder decoder = test.DecoderFor<int32_t>(1234);
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::UnsupportedAttribute);
}
}
TEST_F(TestCodegenModelViaMocks, WriteToGlobalAttribute)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
WriteOperation test(kMockEndpoint1, MockClusterId(1), AttributeList::Id);
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
AttributeValueDecoder decoder = test.DecoderFor<int32_t>(1234);
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::UnsupportedWrite);
}
TEST_F(TestCodegenModelViaMocks, EmberWriteFailure)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_INT32S_ATTRIBUTE_TYPE));
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
{
AttributeValueDecoder decoder = test.DecoderFor<int32_t>(1234);
chip::Test::SetEmberReadOutput(Protocols::InteractionModel::Status::Failure);
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::Failure);
}
{
AttributeValueDecoder decoder = test.DecoderFor<int32_t>(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_F(TestCodegenModelViaMocks, EmberWriteAttributeAccessInterfaceTest)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE));
RegisteredAttributeAccessInterface<StructAttributeAccessInterface> aai(kStructPath);
WriteOperation test(kStructPath);
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
Clusters::UnitTesting::Structs::SimpleStruct::Type testValue{
.a = 112,
.b = true,
.e = "aai_write_test"_span,
.g = 0.5,
.h = 0.125,
};
AttributeValueDecoder decoder = test.DecoderFor(testValue);
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR);
EXPECT_EQ(aai->GetData().a, 112);
EXPECT_TRUE(aai->GetData().e.data_equal("aai_write_test"_span));
// AAI marks dirty paths
ASSERT_EQ(model.ChangeListener().DirtyList().size(), 1u);
EXPECT_EQ(model.ChangeListener().DirtyList()[0],
AttributePathParams(kStructPath.mEndpointId, kStructPath.mClusterId, kStructPath.mAttributeId));
// AAI does not prevent read/write of regular attributes
// validate that once AAI is added, we still can go through writing regular bits (i.e.
// AAI returning "unknown" has fallback to ember)
TestEmberScalarTypeWrite<uint32_t, ZCL_INT32U_ATTRIBUTE_TYPE>(4321);
TestEmberScalarNullWrite<int64_t, ZCL_INT64S_ATTRIBUTE_TYPE>();
}
TEST_F(TestCodegenModelViaMocks, EmberInvokeTest)
{
// Ember invoke is fully code-generated - there is a single function for Dispatch
// that will do a `switch` on the path elements and invoke a corresponding `emberAf*`
// callback.
//
// The only thing that can be validated is that this `DispatchSingleClusterCommand`
// is actually invoked.
UseMockNodeConfig config(gTestNodeConfig);
chip::app::CodegenDataModelProvider model;
{
const ConcreteCommandPath kCommandPath(kMockEndpoint1, MockClusterId(1), kMockCommandId1);
const InvokeRequest kInvokeRequest{ .path = kCommandPath };
chip::TLV::TLVReader tlvReader;
const uint32_t kDispatchCountPre = chip::Test::DispatchCount();
// Using a handler set to nullptr as it is not used by the impl
ASSERT_EQ(model.InvokeCommand(kInvokeRequest, tlvReader, /* handler = */ nullptr), std::nullopt);
EXPECT_EQ(chip::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.InvokeCommand(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_F(TestCodegenModelViaMocks, EmberWriteAttributeAccessInterfaceReturningError)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE));
RegisteredAttributeAccessInterface<ErrorAccessInterface> aai(kStructPath, CHIP_ERROR_KEY_NOT_FOUND);
WriteOperation test(kStructPath);
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
Clusters::UnitTesting::Structs::SimpleStruct::Type testValue{
.a = 112,
.b = true,
.e = "aai_write_test"_span,
.g = 0.5,
.h = 0.125,
};
AttributeValueDecoder decoder = test.DecoderFor(testValue);
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_ERROR_KEY_NOT_FOUND);
ASSERT_TRUE(model.ChangeListener().DirtyList().empty());
}
TEST_F(TestCodegenModelViaMocks, EmberWriteInvalidDataType)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE));
WriteOperation test(kStructPath);
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
Clusters::UnitTesting::Structs::SimpleStruct::Type testValue{
.a = 112,
.b = true,
.e = "aai_write_test"_span,
.g = 0.5,
.h = 0.125,
};
AttributeValueDecoder decoder = test.DecoderFor(testValue);
// Embed specifically DOES NOT support structures.
// Without AAI, we expect a data type error (translated to failure)
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::Failure);
ASSERT_TRUE(model.ChangeListener().DirtyList().empty());
}
TEST_F(TestCodegenModelViaMocks, DeviceTypeIteration)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
// Mock endpoint 1 has 3 device types
ReadOnlyBufferBuilder<DataModel::DeviceTypeEntry> builder;
ASSERT_EQ(model.DeviceTypes(kMockEndpoint1, builder), CHIP_NO_ERROR);
auto deviceTypes = builder.TakeBuffer();
ASSERT_EQ(deviceTypes.size(), 3u);
const DeviceTypeEntry expected1[] = {
{ .deviceTypeId = kDeviceTypeId1, .deviceTypeRevision = kDeviceTypeId1Version },
{ .deviceTypeId = kDeviceTypeId2, .deviceTypeRevision = kDeviceTypeId2Version },
{ .deviceTypeId = kDeviceTypeId3, .deviceTypeRevision = kDeviceTypeId3Version },
};
for (unsigned i = 0; i < 3; i++)
{
ASSERT_EQ(deviceTypes[i], expected1[i]);
}
// Mock endpoint 2 has 1 device types
ASSERT_TRUE(builder.IsEmpty()); // ownership taken above, we start fresh
ASSERT_EQ(model.DeviceTypes(kMockEndpoint2, builder), CHIP_NO_ERROR);
deviceTypes = builder.TakeBuffer();
ASSERT_EQ(deviceTypes.size(), 1u);
const DeviceTypeEntry expected2 = { .deviceTypeId = kDeviceTypeId2, .deviceTypeRevision = kDeviceTypeId2Version };
ASSERT_EQ(deviceTypes[0], expected2);
// empty endpoint works
ASSERT_TRUE(builder.IsEmpty()); // ownership taken above, we start fresh
ASSERT_EQ(model.DeviceTypes(kMockEndpoint3, builder), CHIP_NO_ERROR);
ASSERT_TRUE(builder.IsEmpty());
ASSERT_TRUE(builder.TakeBuffer().empty());
}
TEST_F(TestCodegenModelViaMocks, SemanticTagIteration)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ReadOnlyBufferBuilder<Provider::SemanticTag> builder;
ASSERT_EQ(model.SemanticTags(kMockEndpoint2, builder), CHIP_NO_ERROR);
ASSERT_TRUE(builder.IsEmpty());
auto tags = builder.TakeBuffer();
ASSERT_TRUE(tags.empty());
// Mock endpoint 1 has 3 semantic tags
ASSERT_EQ(model.SemanticTags(kMockEndpoint1, builder), CHIP_NO_ERROR);
ASSERT_EQ(builder.Size(), 3u);
tags = builder.TakeBuffer();
ASSERT_EQ(tags.size(), 3u);
ASSERT_TRUE(builder.IsEmpty()); // ownership taken
auto tag = tags[0];
EXPECT_EQ(tag.mfgCode, MakeNullable(VendorId::TestVendor1));
EXPECT_EQ(tag.namespaceID, kNamespaceID1);
EXPECT_EQ(tag.tag, kTag1);
ASSERT_TRUE(tag.label.HasValue() && (!tag.label.Value().IsNull()));
EXPECT_TRUE(tag.label.Value().Value().data_equal(CharSpan::fromCharString(kLabel1)));
tag = tags[1];
EXPECT_TRUE(tag.mfgCode.IsNull());
EXPECT_EQ(tag.namespaceID, kNamespaceID2);
EXPECT_EQ(tag.tag, kTag2);
ASSERT_TRUE(tag.label.HasValue() && (!tag.label.Value().IsNull()));
EXPECT_TRUE(tag.label.Value().Value().data_equal(CharSpan::fromCharString(kLabel2)));
tag = tags[2];
EXPECT_EQ(tag.mfgCode, MakeNullable(VendorId::TestVendor3));
EXPECT_EQ(tag.namespaceID, kNamespaceID3);
EXPECT_EQ(tag.tag, kTag3);
EXPECT_FALSE(tag.label.HasValue());
}
// reading attributes is a LOT of boilerplate. This just makes tests more readable.
static CHIP_ERROR ReadU32Attribute(DataModel::Provider & provider, const ConcreteAttributePath & path, uint32_t & value)
{
ReadOperation testRequest(path);
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding();
ReturnErrorOnFailure(provider.ReadAttribute(testRequest.GetRequest(), *encoder).GetUnderlyingError());
ReturnErrorOnFailure(testRequest.FinishEncoding());
std::vector<DecodedAttributeData> attribute_data;
ReturnErrorOnFailure(testRequest.GetEncodedIBs().Decode(attribute_data));
VerifyOrReturnError(attribute_data.size() == 1u, CHIP_ERROR_INCORRECT_STATE);
DecodedAttributeData & encodedData = attribute_data[0];
VerifyOrReturnError(encodedData.attributePath == testRequest.GetRequest().path, CHIP_ERROR_INCORRECT_STATE);
return chip::app::DataModel::Decode<uint32_t>(encodedData.dataReader, value);
}
TEST_F(TestCodegenModelViaMocks, ServerClusterInterfacesRegistration)
{
TestServerClusterContext testContext;
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
model.SetPersistentStorageDelegate(&testContext.StorageDelegate());
ASSERT_EQ(model.Startup(testContext.ImContext()), CHIP_NO_ERROR);
const ConcreteClusterPath kTestClusterPath(kMockEndpoint1, MockClusterId(2));
FakeDefaultServerCluster fakeClusterServer(kTestClusterPath);
ServerClusterRegistration registration(fakeClusterServer);
uint32_t revisionEmber;
ASSERT_EQ(ReadU32Attribute(
model,
{ kTestClusterPath.mEndpointId, kTestClusterPath.mClusterId, Clusters::Globals::Attributes::ClusterRevision::Id },
revisionEmber),
CHIP_NO_ERROR);
EXPECT_EQ(revisionEmber, 0u);
static_assert(FakeDefaultServerCluster::kFakeClusterRevision != 0);
ASSERT_EQ(model.Registry().Register(registration), CHIP_NO_ERROR);
uint32_t revision;
ASSERT_EQ(ReadU32Attribute(
model,
{ kTestClusterPath.mEndpointId, kTestClusterPath.mClusterId, Clusters::Globals::Attributes::ClusterRevision::Id },
revision),
CHIP_NO_ERROR);
EXPECT_EQ(revision, FakeDefaultServerCluster::kFakeClusterRevision);
// now that registration looks ok and DIFFERENT from ember, invoke various methods on the registered cluster
// to ensure behavior is redirected correctly
{
ReadOnlyBufferBuilder<AttributeEntry> builder;
ASSERT_EQ(model.Attributes(kTestClusterPath, builder), CHIP_NO_ERROR);
// Attributes will be just global attributes
ASSERT_TRUE(DefaultServerCluster::GlobalAttributes().data_equal(builder.TakeBuffer()));
}
{
ReadOnlyBufferBuilder<AcceptedCommandEntry> builder;
ASSERT_EQ(model.AcceptedCommands(kTestClusterPath, builder), CHIP_NO_ERROR);
ASSERT_TRUE(Span<const AcceptedCommandEntry>(FakeDefaultServerCluster::kAcceptedCommands).data_equal(builder.TakeBuffer()));
}
{
ReadOnlyBufferBuilder<CommandId> builder;
ASSERT_EQ(model.GeneratedCommands(kTestClusterPath, builder), CHIP_NO_ERROR);
ASSERT_TRUE(Span<const CommandId>(FakeDefaultServerCluster::kGeneratedCommands).data_equal(builder.TakeBuffer()));
}
// Invoke specifically on the fake server returns a unique (and non-spec really) error
// so we can see the right method is called.
{
const ConcreteCommandPath kCommandPath(kTestClusterPath.mEndpointId, kTestClusterPath.mClusterId, kMockCommandId1);
const InvokeRequest kInvokeRequest{ .path = kCommandPath };
chip::TLV::TLVReader tlvReader;
// Using a handler set to nullptr as it is not used by the impl
std::optional<ActionReturnStatus> result = model.InvokeCommand(kInvokeRequest, tlvReader, /* handler = */ nullptr);
ASSERT_TRUE(result.has_value() && result->GetUnderlyingError() == CHIP_ERROR_INCORRECT_STATE);
}
// Write attribute also has a specific error to know the right code is called
{
WriteOperation test(kTestClusterPath.mEndpointId, kTestClusterPath.mClusterId, kAttributeIdReadOnly);
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
AttributeValueDecoder decoder = test.DecoderFor<uint32_t>(1234);
std::optional<ActionReturnStatus> result = model.WriteAttribute(test.GetRequest(), decoder);
ASSERT_TRUE(result.has_value() && result->GetUnderlyingError() == CHIP_ERROR_INCORRECT_STATE);
}
model.Registry().Unregister(&fakeClusterServer);
model.Shutdown();
}
TEST_F(TestCodegenModelViaMocks, ServerClusterInterfacesListClusters)
{
TestServerClusterContext testContext;
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
model.SetPersistentStorageDelegate(&testContext.StorageDelegate());
ASSERT_EQ(model.Startup(testContext.ImContext()), CHIP_NO_ERROR);
// will register a fake cluster server which overrides the cluster data version
// once registered
const ConcreteClusterPath kTestClusterPath(kMockEndpoint1, MockClusterId(2));
FakeDefaultServerCluster fakeClusterServer(kTestClusterPath);
ServerClusterRegistration registration(fakeClusterServer);
// ensure ember and fake server do not ramdomly point to the same data
// version as we use this data version to differentiate between the two
DataVersion * versionPtr = emberAfDataVersionStorage(kTestClusterPath);
ASSERT_NE(versionPtr, nullptr);
if (*versionPtr == fakeClusterServer.GetDataVersion(kTestClusterPath))
{
fakeClusterServer.TestIncreaseDataVersion();
}
ReadOnlyBufferBuilder<DataModel::ServerClusterEntry> builder;
ASSERT_EQ(model.ServerClusters(kTestClusterPath.mEndpointId, builder), CHIP_NO_ERROR);
std::vector<ServerClusterEntry> originalClusters;
for (auto entry : builder.TakeBuffer())
{
originalClusters.push_back(entry);
}
ASSERT_EQ(model.Registry().Register(registration), CHIP_NO_ERROR);
ASSERT_EQ(model.ServerClusters(kTestClusterPath.mEndpointId, builder), CHIP_NO_ERROR);
std::vector<ServerClusterEntry> afterRegistrationClusters;
for (auto entry : builder.TakeBuffer())
{
afterRegistrationClusters.push_back(entry);
}
// lists MUST be identical EXCEPT data version will be different
// EMBER has one data version, clusters have a random (different) one
EXPECT_EQ(originalClusters.size(), afterRegistrationClusters.size());
std::sort(originalClusters.begin(), originalClusters.end(),
[](const ServerClusterEntry & a, const ServerClusterEntry & b) { return a.clusterId < b.clusterId; });
std::sort(afterRegistrationClusters.begin(), afterRegistrationClusters.end(),
[](const ServerClusterEntry & a, const ServerClusterEntry & b) { return a.clusterId < b.clusterId; });
bool updatedClusterFound = false;
for (size_t i = 0; i < originalClusters.size(); i++)
{
const ServerClusterEntry & original = originalClusters[i];
const ServerClusterEntry & registered = afterRegistrationClusters[i];
if (original.clusterId == kTestClusterPath.mClusterId)
{
updatedClusterFound = true;
EXPECT_EQ(registered.clusterId, original.clusterId);
EXPECT_EQ(registered.dataVersion, fakeClusterServer.GetDataVersion(kTestClusterPath));
EXPECT_EQ(registered.flags, fakeClusterServer.GetClusterFlags(kTestClusterPath));
// version MUST be different for ember for the test to make sense
EXPECT_NE(original.dataVersion, registered.dataVersion);
}
else
{
EXPECT_EQ(original.clusterId, registered.clusterId);
EXPECT_EQ(original.dataVersion, registered.dataVersion);
EXPECT_EQ(original.flags, registered.flags);
}
}
EXPECT_TRUE(updatedClusterFound);
model.Registry().Unregister(&fakeClusterServer);
model.Shutdown();
}
#if CHIP_CONFIG_USE_ENDPOINT_UNIQUE_ID
TEST_F(TestCodegenModelViaMocks, EndpointUniqueID)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
// Mock endpoint 1 has a unique ID
char buffer[chip::app::Clusters::Descriptor::Attributes::EndpointUniqueID::TypeInfo::MaxLength()] = { 0 };
MutableCharSpan span(buffer);
// Mock endpoint 4 has a unique ID
// ASSERT_TRUE(builder.IsEmpty()); // ownership taken above, we start fresh
ASSERT_EQ(model.EndpointUniqueID(kMockEndpoint4, span), CHIP_NO_ERROR);
EXPECT_TRUE(span.data_equal("AABBCCDDEEFFGGHHIIJJKKLLMMNNOO01"_span));
}
#endif