blob: 8ce369b7bd822083d186a7f15171c9078ac2031e [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 <app/codegen-data-model-provider/tests/EmberInvokeOverride.h>
#include <app/codegen-data-model-provider/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/ConcreteCommandPath.h>
#include <app/GlobalAttributes.h>
#include <app/MessageDef/ReportDataMessage.h>
#include <app/codegen-data-model-provider/CodegenDataModelProvider.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/Nullable.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 <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/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;
namespace {
constexpr AttributeId kAttributeIdReadOnly = 0x3001;
constexpr AttributeId kAttributeIdTimedWrite = 0x3002;
constexpr CommandId kMockCommandId1 = 0x1234;
constexpr CommandId kMockCommandId2 = 0x1122;
constexpr EndpointId kEndpointIdThatIsMissing = kMockEndpointMin - 1;
constexpr AttributeId kReadOnlyAttributeId = 0x5001;
constexpr DeviceTypeId kDeviceTypeId1 = 123;
constexpr uint8_t kDeviceTypeId1Version = 10;
constexpr DeviceTypeId kDeviceTypeId2 = 1122;
constexpr uint8_t kDeviceTypeId2Version = 11;
constexpr DeviceTypeId kDeviceTypeId3 = 3;
constexpr uint8_t kDeviceTypeId3Version = 33;
static_assert(kEndpointIdThatIsMissing != kInvalidEndpointId);
static_assert(kEndpointIdThatIsMissing != kMockEndpoint1);
static_assert(kEndpointIdThatIsMissing != kMockEndpoint2);
static_assert(kEndpointIdThatIsMissing != kMockEndpoint3);
bool operator==(const Access::SubjectDescriptor & a, const Access::SubjectDescriptor & b)
{
if (a.fabricIndex != b.fabricIndex)
{
return false;
}
if (a.authMode != b.authMode)
{
return false;
}
if (a.subject != b.subject)
{
return false;
}
for (unsigned i = 0; i < a.cats.values.size(); i++)
{
if (a.cats.values[i] != b.cats.values[i])
{
return false;
}
}
return true;
}
class TestProviderChangeListener : public ProviderChangeListener
{
public:
void MarkDirty(const AttributePathParams & path) override { mDirtyList.push_back(path); }
std::vector<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; }
};
/// 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 { handlerContext.SetCommandNotHandled(); }
CHIP_ERROR EnumerateAcceptedCommands(const ConcreteClusterPath & cluster, CommandIdCallback callback, void * context) override
{
VerifyOrReturnError(mOverrideAccepted, CHIP_ERROR_NOT_IMPLEMENTED);
for (auto id : mAccepted)
{
if (callback(id, context) != Loop::Continue)
{
break;
}
}
return CHIP_NO_ERROR;
}
CHIP_ERROR EnumerateGeneratedCommands(const ConcreteClusterPath & cluster, CommandIdCallback callback, void * context) override
{
VerifyOrReturnError(mOverrideGenerated, CHIP_ERROR_NOT_IMPLEMENTED);
for (auto id : mGenerated)
{
if (callback(id, context) != Loop::Continue)
{
break;
}
}
return CHIP_NO_ERROR;
}
void SetOverrideAccepted(bool o) { mOverrideAccepted = o; }
void SetOverrideGenerated(bool o) { mOverrideGenerated = o; }
std::vector<CommandId> & AcceptedVec() { return mAccepted; }
std::vector<CommandId> & GeneratedVec() { return mGenerated; }
private:
bool mOverrideAccepted = false;
bool mOverrideGenerated = 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, ATTRIBUTE_MASK_WRITABLE | ATTRIBUTE_MASK_NULLABLE)
#define MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(zcl_type) MockAttributeId(zcl_type + 0x2000)
#define MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(zcl_type) \
MockAttributeConfig(MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(zcl_type), zcl_type, ATTRIBUTE_MASK_WRITABLE)
// clang-format off
const MockNodeConfig gTestNodeConfig({
MockEndpointConfig(kMockEndpoint1, {
MockClusterConfig(MockClusterId(1), {
ClusterRevision::Id, FeatureMap::Id,
}, {
MockEventId(1), MockEventId(2),
}),
MockClusterConfig(MockClusterId(2), {
ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1),
}),
}, {
{ kDeviceTypeId1, kDeviceTypeId1Version},
{ kDeviceTypeId2, kDeviceTypeId2Version},
{ kDeviceTypeId3, kDeviceTypeId3Version},
}),
MockEndpointConfig(kMockEndpoint2, {
MockClusterConfig(MockClusterId(1), {
ClusterRevision::Id, FeatureMap::Id,
}),
MockClusterConfig(
MockClusterId(2),
{
ClusterRevision::Id,
FeatureMap::Id,
MockAttributeId(1),
MockAttributeConfig(MockAttributeId(2), ZCL_ARRAY_ATTRIBUTE_TYPE),
}, /* attributes */
{}, /* events */
{1, 2, 23}, /* acceptedCommands */
{2, 10} /* generatedCommands */
),
MockClusterConfig(
MockClusterId(3),
{
ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), MockAttributeId(2), MockAttributeId(3),
}, /* attributes */
{}, /* events */
{11}, /* acceptedCommands */
{4, 6} /* generatedCommands */
),
}, {
{ kDeviceTypeId2, kDeviceTypeId2Version},
}),
MockEndpointConfig(kMockEndpoint3, {
MockClusterConfig(MockClusterId(1), {
ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1),
}),
MockClusterConfig(MockClusterId(2), {
ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), MockAttributeId(2), MockAttributeId(3), MockAttributeId(4),
}),
MockClusterConfig(MockClusterId(3), {
ClusterRevision::Id, FeatureMap::Id,
MockAttributeConfig(
kReadOnlyAttributeId,
ZCL_INT32U_ATTRIBUTE_TYPE,
ATTRIBUTE_MASK_NULLABLE // NOTE: explicltly NOT ATTRIBUTE_MASK_WRITABLE
)
}),
MockClusterConfig(MockClusterId(4), {
ClusterRevision::Id,
FeatureMap::Id,
// several attributes of varying data types for testing.
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BOOLEAN_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BITMAP8_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BITMAP16_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BITMAP32_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_BITMAP64_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT8U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT16U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT24U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT32U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT40U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT48U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT56U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT64U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT8S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT16S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT24S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT32S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT40S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT48S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT56S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_INT64S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENUM8_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENUM16_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_PRIORITY_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_STATUS_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_SINGLE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DOUBLE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_CHAR_STRING_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ARRAY_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_STRUCT_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_GROUP_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_VENDOR_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_FABRIC_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_FABRIC_IDX_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENTRY_IDX_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DATA_VER_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EVENT_NO_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_SEMTAG_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_NAMESPACE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TAG_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_SYSTIME_US_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_SYSTIME_MS_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ELAPSED_S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TEMPERATURE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_POWER_MW_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ENERGY_MWH_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TOD_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_DATE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EPOCH_US_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EPOCH_S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_POSIX_MS_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_PERCENT_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_PERCENT100THS_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_CLUSTER_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ATTRIB_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_FIELD_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_EVENT_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_COMMAND_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_ACTION_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_TRANS_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_NODE_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPADR_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPV4ADR_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPV6ADR_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_IPV6PRE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(ZCL_HWADR_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BOOLEAN_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP8_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP16_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP32_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_BITMAP64_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT8U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT16U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT24U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT32U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT40U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT48U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT56U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT64U_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT8S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT16S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT24S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT32S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT40S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT48S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT56S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_INT64S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENUM8_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENUM16_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_PRIORITY_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_STATUS_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SINGLE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DOUBLE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_CHAR_STRING_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ARRAY_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_STRUCT_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_GROUP_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_VENDOR_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_FABRIC_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_FABRIC_IDX_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENTRY_IDX_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DATA_VER_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EVENT_NO_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SEMTAG_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_NAMESPACE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TAG_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SYSTIME_US_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_SYSTIME_MS_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ELAPSED_S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TEMPERATURE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_POWER_MW_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ENERGY_MWH_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TOD_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_DATE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EPOCH_US_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EPOCH_S_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_POSIX_MS_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_PERCENT_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_PERCENT100THS_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_CLUSTER_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ATTRIB_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_FIELD_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_EVENT_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_COMMAND_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_ACTION_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_TRANS_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_NODE_ID_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPADR_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPV4ADR_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPV6ADR_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_IPV6PRE_ATTRIBUTE_TYPE),
MOCK_ATTRIBUTE_CONFIG_NULLABLE(ZCL_HWADR_ATTRIBUTE_TYPE),
// Special case handling
MockAttributeConfig(kAttributeIdReadOnly, ZCL_INT32S_ATTRIBUTE_TYPE, 0),
MockAttributeConfig(kAttributeIdTimedWrite, ZCL_INT32S_ATTRIBUTE_TYPE, ATTRIBUTE_MASK_WRITABLE | ATTRIBUTE_MASK_MUST_USE_TIMED_WRITE),
}),
}),
});
// clang-format on
struct UseMockNodeConfig
{
UseMockNodeConfig(const MockNodeConfig & config) { SetMockNodeConfig(config); }
~UseMockNodeConfig() { ResetMockNodeConfig(); }
};
template <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;
};
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);
}
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);
}
} // namespace
TEST(TestCodegenModelViaMocks, IterateOverEndpoints)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
// This iteration relies on the hard-coding that occurs when mock_ember is used
EXPECT_EQ(model.FirstEndpoint(), kMockEndpoint1);
EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2);
EXPECT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3);
EXPECT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId);
/// Some out of order requests should work as well
EXPECT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3);
EXPECT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3);
EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2);
EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2);
EXPECT_EQ(model.NextEndpoint(kMockEndpoint2), kMockEndpoint3);
EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2);
EXPECT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId);
EXPECT_EQ(model.NextEndpoint(kMockEndpoint3), kInvalidEndpointId);
EXPECT_EQ(model.FirstEndpoint(), kMockEndpoint1);
EXPECT_EQ(model.FirstEndpoint(), kMockEndpoint1);
// invalid endpoiunts
EXPECT_EQ(model.NextEndpoint(kInvalidEndpointId), kInvalidEndpointId);
EXPECT_EQ(model.NextEndpoint(987u), kInvalidEndpointId);
}
TEST(TestCodegenModelViaMocks, IterateOverClusters)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
chip::Test::ResetVersion();
EXPECT_FALSE(model.FirstCluster(kEndpointIdThatIsMissing).path.HasValidIds());
EXPECT_FALSE(model.FirstCluster(kInvalidEndpointId).path.HasValidIds());
EXPECT_FALSE(model.NextCluster(ConcreteClusterPath(kInvalidEndpointId, 123)).path.HasValidIds());
EXPECT_FALSE(model.NextCluster(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds());
EXPECT_FALSE(model.NextCluster(ConcreteClusterPath(kMockEndpoint1, 981u)).path.HasValidIds());
// mock endpoint 1 has 2 mock clusters: 1 and 2
ClusterEntry entry = model.FirstCluster(kMockEndpoint1);
ASSERT_TRUE(entry.path.HasValidIds());
EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint1);
EXPECT_EQ(entry.path.mClusterId, MockClusterId(1));
EXPECT_EQ(entry.info.dataVersion, 0u);
EXPECT_EQ(entry.info.flags.Raw(), 0u);
chip::Test::BumpVersion();
entry = model.NextCluster(entry.path);
ASSERT_TRUE(entry.path.HasValidIds());
EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint1);
EXPECT_EQ(entry.path.mClusterId, MockClusterId(2));
EXPECT_EQ(entry.info.dataVersion, 1u);
EXPECT_EQ(entry.info.flags.Raw(), 0u);
entry = model.NextCluster(entry.path);
EXPECT_FALSE(entry.path.HasValidIds());
// mock endpoint 3 has 4 mock clusters: 1 through 4
entry = model.FirstCluster(kMockEndpoint3);
for (uint16_t clusterId = 1; clusterId <= 4; clusterId++)
{
ASSERT_TRUE(entry.path.HasValidIds());
EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint3);
EXPECT_EQ(entry.path.mClusterId, MockClusterId(clusterId));
entry = model.NextCluster(entry.path);
}
EXPECT_FALSE(entry.path.HasValidIds());
// repeat calls should work
for (int i = 0; i < 10; i++)
{
entry = model.FirstCluster(kMockEndpoint1);
ASSERT_TRUE(entry.path.HasValidIds());
EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint1);
EXPECT_EQ(entry.path.mClusterId, MockClusterId(1));
}
for (int i = 0; i < 10; i++)
{
ClusterEntry nextEntry = model.NextCluster(entry.path);
ASSERT_TRUE(nextEntry.path.HasValidIds());
EXPECT_EQ(nextEntry.path.mEndpointId, kMockEndpoint1);
EXPECT_EQ(nextEntry.path.mClusterId, MockClusterId(2));
}
}
TEST(TestCodegenModelViaMocks, GetClusterInfo)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
chip::Test::ResetVersion();
ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kInvalidEndpointId, kInvalidClusterId)).has_value());
ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).has_value());
ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).has_value());
ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).has_value());
// now get the value
std::optional<ClusterInfo> info = model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)));
ASSERT_TRUE(info.has_value());
EXPECT_EQ(info->dataVersion, 0u); // NOLINT(bugprone-unchecked-optional-access)
EXPECT_EQ(info->flags.Raw(), 0u); // NOLINT(bugprone-unchecked-optional-access)
chip::Test::BumpVersion();
info = model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)));
ASSERT_TRUE(info.has_value());
EXPECT_EQ(info->dataVersion, 1u); // NOLINT(bugprone-unchecked-optional-access)
EXPECT_EQ(info->flags.Raw(), 0u); // NOLINT(bugprone-unchecked-optional-access)
}
TEST(TestCodegenModelViaMocks, IterateOverAttributes)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
// invalid paths should return in "no more data"
ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).path.HasValidIds());
ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).path.HasValidIds());
ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).path.HasValidIds());
ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds());
ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kEndpointIdThatIsMissing, MockClusterId(1), 1u)).path.HasValidIds());
ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kInvalidEndpointId, MockClusterId(1), 1u)).path.HasValidIds());
ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), 1u)).path.HasValidIds());
ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kMockEndpoint1, kInvalidClusterId, 1u)).path.HasValidIds());
ASSERT_FALSE(model.NextAttribute(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), 987u)).path.HasValidIds());
// should be able to iterate over valid paths
AttributeEntry entry = model.FirstAttribute(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2)));
ASSERT_TRUE(entry.path.HasValidIds());
ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2);
ASSERT_EQ(entry.path.mClusterId, MockClusterId(2));
ASSERT_EQ(entry.path.mAttributeId, ClusterRevision::Id);
ASSERT_FALSE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute));
entry = model.NextAttribute(entry.path);
ASSERT_TRUE(entry.path.HasValidIds());
ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2);
ASSERT_EQ(entry.path.mClusterId, MockClusterId(2));
ASSERT_EQ(entry.path.mAttributeId, FeatureMap::Id);
ASSERT_FALSE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute));
entry = model.NextAttribute(entry.path);
ASSERT_TRUE(entry.path.HasValidIds());
ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2);
ASSERT_EQ(entry.path.mClusterId, MockClusterId(2));
ASSERT_EQ(entry.path.mAttributeId, MockAttributeId(1));
ASSERT_FALSE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute));
entry = model.NextAttribute(entry.path);
ASSERT_TRUE(entry.path.HasValidIds());
ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2);
ASSERT_EQ(entry.path.mClusterId, MockClusterId(2));
ASSERT_EQ(entry.path.mAttributeId, MockAttributeId(2));
ASSERT_TRUE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute));
entry = model.NextAttribute(entry.path);
ASSERT_FALSE(entry.path.HasValidIds());
// repeated calls should work
for (int i = 0; i < 10; i++)
{
entry = model.FirstAttribute(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2)));
ASSERT_TRUE(entry.path.HasValidIds());
ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2);
ASSERT_EQ(entry.path.mClusterId, MockClusterId(2));
ASSERT_EQ(entry.path.mAttributeId, ClusterRevision::Id);
ASSERT_FALSE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute));
}
for (int i = 0; i < 10; i++)
{
entry = model.NextAttribute(ConcreteAttributePath(kMockEndpoint2, MockClusterId(2), MockAttributeId(1)));
ASSERT_TRUE(entry.path.HasValidIds());
ASSERT_EQ(entry.path.mEndpointId, kMockEndpoint2);
ASSERT_EQ(entry.path.mClusterId, MockClusterId(2));
ASSERT_EQ(entry.path.mAttributeId, MockAttributeId(2));
ASSERT_TRUE(entry.info.flags.Has(AttributeQualityFlags::kListAttribute));
}
}
TEST(TestCodegenModelViaMocks, GetAttributeInfo)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
// various non-existent or invalid paths should return no info data
ASSERT_FALSE(
model.GetAttributeInfo(ConcreteAttributePath(kInvalidEndpointId, kInvalidClusterId, kInvalidAttributeId)).has_value());
ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kInvalidEndpointId, kInvalidClusterId, FeatureMap::Id)).has_value());
ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kInvalidEndpointId, MockClusterId(1), FeatureMap::Id)).has_value());
ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, kInvalidClusterId, FeatureMap::Id)).has_value());
ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), FeatureMap::Id)).has_value());
ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), kInvalidAttributeId)).has_value());
ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))).has_value());
// valid info
std::optional<AttributeInfo> info =
model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), FeatureMap::Id));
ASSERT_TRUE(info.has_value());
EXPECT_FALSE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access)
// Mocks always set everything as R/W with administrative privileges
EXPECT_EQ(info->readPrivilege, chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access)
EXPECT_EQ(info->writePrivilege, chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access)
info = model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint2, MockClusterId(2), MockAttributeId(2)));
ASSERT_TRUE(info.has_value());
EXPECT_TRUE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access)
EXPECT_EQ(info->readPrivilege, chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access)
EXPECT_EQ(info->writePrivilege, chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access)
// test a read-only attribute, which will not have a write privilege
info = model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint3, MockClusterId(3), kReadOnlyAttributeId));
ASSERT_TRUE(info.has_value());
EXPECT_FALSE(info->flags.Has(AttributeQualityFlags::kListAttribute)); // NOLINT(bugprone-unchecked-optional-access)
EXPECT_EQ(info->readPrivilege, chip::Access::Privilege::kAdminister); // NOLINT(bugprone-unchecked-optional-access)
EXPECT_FALSE(info->writePrivilege.has_value()); // NOLINT(bugprone-unchecked-optional-access)
}
// global attributes are EXPLICITLY not supported
TEST(TestCodegenModelViaMocks, GlobalAttributeInfo)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
std::optional<AttributeInfo> info = model.GetAttributeInfo(
ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::GeneratedCommandList::Id));
ASSERT_FALSE(info.has_value());
info = model.GetAttributeInfo(
ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), Clusters::Globals::Attributes::AttributeList::Id));
ASSERT_FALSE(info.has_value());
}
TEST(TestCodegenModelViaMocks, IterateOverAcceptedCommands)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
// invalid paths should return in "no more data"
ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).path.HasValidIds());
ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).path.HasValidIds());
ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).path.HasValidIds());
ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds());
// should be able to iterate over valid paths
CommandEntry entry = model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2)));
ASSERT_TRUE(entry.path.HasValidIds());
EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(entry.path.mClusterId, MockClusterId(2));
EXPECT_EQ(entry.path.mCommandId, 1u);
entry = model.NextAcceptedCommand(entry.path);
ASSERT_TRUE(entry.path.HasValidIds());
EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(entry.path.mClusterId, MockClusterId(2));
EXPECT_EQ(entry.path.mCommandId, 2u);
entry = model.NextAcceptedCommand(entry.path);
ASSERT_TRUE(entry.path.HasValidIds());
EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(entry.path.mClusterId, MockClusterId(2));
EXPECT_EQ(entry.path.mCommandId, 23u);
entry = model.NextAcceptedCommand(entry.path);
ASSERT_FALSE(entry.path.HasValidIds());
// attempt some out-of-order requests as well
entry = model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(3)));
ASSERT_TRUE(entry.path.HasValidIds());
EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(entry.path.mClusterId, MockClusterId(3));
EXPECT_EQ(entry.path.mCommandId, 11u);
for (int i = 0; i < 10; i++)
{
entry = model.NextAcceptedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 2));
ASSERT_TRUE(entry.path.HasValidIds());
EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(entry.path.mClusterId, MockClusterId(2));
EXPECT_EQ(entry.path.mCommandId, 23u);
}
for (int i = 0; i < 10; i++)
{
entry = model.NextAcceptedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1));
ASSERT_TRUE(entry.path.HasValidIds());
EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(entry.path.mClusterId, MockClusterId(2));
EXPECT_EQ(entry.path.mCommandId, 2u);
}
for (int i = 0; i < 10; i++)
{
entry = model.NextAcceptedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(3), 10));
EXPECT_FALSE(entry.path.HasValidIds());
}
}
TEST(TestCodegenModelViaMocks, AcceptedCommandInfo)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
// invalid paths should return in "no more data"
ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kEndpointIdThatIsMissing, MockClusterId(1), 1)).has_value());
ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kInvalidEndpointId, MockClusterId(1), 1)).has_value());
ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(10), 1)).has_value());
ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, kInvalidClusterId, 1)).has_value());
ASSERT_FALSE(
model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), kInvalidCommandId)).has_value());
std::optional<CommandInfo> info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1u));
ASSERT_TRUE(info.has_value());
info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 2u));
ASSERT_TRUE(info.has_value());
info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1u));
ASSERT_TRUE(info.has_value());
info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1u));
ASSERT_TRUE(info.has_value());
info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 23u));
ASSERT_TRUE(info.has_value());
info = model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1234u));
ASSERT_FALSE(info.has_value());
}
TEST(TestCodegenModelViaMocks, IterateOverGeneratedCommands)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
// invalid paths should return in "no more data"
ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).HasValidIds());
ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).HasValidIds());
ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).HasValidIds());
ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).HasValidIds());
// should be able to iterate over valid paths
ConcreteCommandPath path = model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2)));
ASSERT_TRUE(path.HasValidIds());
EXPECT_EQ(path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(path.mClusterId, MockClusterId(2));
EXPECT_EQ(path.mCommandId, 2u);
path = model.NextGeneratedCommand(path);
ASSERT_TRUE(path.HasValidIds());
EXPECT_EQ(path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(path.mClusterId, MockClusterId(2));
EXPECT_EQ(path.mCommandId, 10u);
path = model.NextGeneratedCommand(path);
ASSERT_FALSE(path.HasValidIds());
// attempt some out-of-order requests as well
path = model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(3)));
ASSERT_TRUE(path.HasValidIds());
EXPECT_EQ(path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(path.mClusterId, MockClusterId(3));
EXPECT_EQ(path.mCommandId, 4u);
for (int i = 0; i < 10; i++)
{
path = model.NextGeneratedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 2));
ASSERT_TRUE(path.HasValidIds());
EXPECT_EQ(path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(path.mClusterId, MockClusterId(2));
EXPECT_EQ(path.mCommandId, 10u);
}
for (int i = 0; i < 10; i++)
{
path = model.NextGeneratedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(3), 4));
ASSERT_TRUE(path.HasValidIds());
EXPECT_EQ(path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(path.mClusterId, MockClusterId(3));
EXPECT_EQ(path.mCommandId, 6u);
}
for (int i = 0; i < 10; i++)
{
path = model.NextGeneratedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(3), 6));
EXPECT_FALSE(path.HasValidIds());
}
}
TEST(TestCodegenModelViaMocks, CommandHandlerInterfaceAcceptedCommands)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
// Command handler interface is capable to override accepted and generated commands.
// Validate that these work
CustomListCommandHandler handler(MakeOptional(kMockEndpoint1), MockClusterId(1));
// At this point, without overrides, there should be no accepted/generated commands
EXPECT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))).IsValid());
EXPECT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))).HasValidIds());
EXPECT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 1234)).has_value());
handler.SetOverrideAccepted(true);
handler.SetOverrideGenerated(true);
// with overrides, the list is still empty ...
EXPECT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))).IsValid());
EXPECT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1))).HasValidIds());
EXPECT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 1234)).has_value());
// set some overrides
handler.AcceptedVec().push_back(1234);
handler.AcceptedVec().push_back(999);
handler.GeneratedVec().push_back(33);
DataModel::CommandEntry entry;
entry = model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)));
EXPECT_TRUE(entry.IsValid());
EXPECT_EQ(entry.path, ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 1234));
entry = model.NextAcceptedCommand(entry.path);
EXPECT_TRUE(entry.IsValid());
EXPECT_EQ(entry.path, ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 999));
entry = model.NextAcceptedCommand(entry.path);
EXPECT_FALSE(entry.IsValid());
ConcreteCommandPath path = model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(1)));
EXPECT_TRUE(path.HasValidIds());
EXPECT_EQ(path, ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 33));
path = model.NextGeneratedCommand(path);
EXPECT_FALSE(path.HasValidIds());
// Command finding should work
EXPECT_TRUE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 1234)).has_value());
EXPECT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 88)).has_value());
EXPECT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), 33)).has_value());
}
TEST(TestCodegenModelViaMocks, EmberAttributeReadAclDeny)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
ReadOperation testRequest(kMockEndpoint1, MockClusterId(1), MockAttributeId(10));
testRequest.SetSubjectDescriptor(kDenySubjectDescriptor);
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding();
ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), Status::UnsupportedAccess);
}
TEST(TestCodegenModelViaMocks, ReadForInvalidGlobalAttributePath)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
{
ReadOperation testRequest(kEndpointIdThatIsMissing, MockClusterId(1), AttributeList::Id);
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
std::unique_ptr<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(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(TestCodegenModelViaMocks, EmberAttributePathExpansionAccessDeniedRead)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
ReadOperation testRequest(kMockEndpoint1, MockClusterId(1), MockAttributeId(10));
testRequest.SetSubjectDescriptor(kDenySubjectDescriptor);
testRequest.SetPathExpanded(true);
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding();
// For expanded paths, access control failures succeed without encoding anything
// This is temporary until ACL checks are moved inside the IM/ReportEngine
ASSERT_EQ(model.ReadAttribute(testRequest.GetRequest(), *encoder), CHIP_NO_ERROR);
ASSERT_FALSE(encoder->TriedEncode());
}
TEST(TestCodegenModelViaMocks, AccessInterfaceUnsupportedRead)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
const ConcreteAttributePath kTestPath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE));
ReadOperation testRequest(kTestPath);
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
testRequest.SetPathExpanded(true);
RegisteredAttributeAccessInterface<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(TestCodegenModelViaMocks, EmberAttributeReadInt32S)
{
TestEmberScalarTypeRead<int32_t, ZCL_INT32S_ATTRIBUTE_TYPE>(-1234);
}
TEST(TestCodegenModelViaMocks, EmberAttributeReadEnum16)
{
TestEmberScalarTypeRead<uint16_t, ZCL_ENUM16_ATTRIBUTE_TYPE>(0x1234);
}
TEST(TestCodegenModelViaMocks, EmberAttributeReadFloat)
{
TestEmberScalarTypeRead<float, ZCL_SINGLE_ATTRIBUTE_TYPE>(0.625);
}
TEST(TestCodegenModelViaMocks, EmberAttributeReadDouble)
{
TestEmberScalarTypeRead<double, ZCL_DOUBLE_ATTRIBUTE_TYPE>(0.625);
}
TEST(TestCodegenModelViaMocks, EmberAttributeReadInt24U)
{
TestEmberScalarTypeRead<OddSizedInteger<3, false>, ZCL_INT24U_ATTRIBUTE_TYPE>(0x1234AB);
}
TEST(TestCodegenModelViaMocks, EmberAttributeReadInt32U)
{
TestEmberScalarTypeRead<uint32_t, ZCL_INT32U_ATTRIBUTE_TYPE>(0x1234ABCD);
}
TEST(TestCodegenModelViaMocks, EmberAttributeReadInt40U)
{
TestEmberScalarTypeRead<OddSizedInteger<5, false>, ZCL_INT40U_ATTRIBUTE_TYPE>(0x1122334455);
}
TEST(TestCodegenModelViaMocks, EmberAttributeReadInt48U)
{
TestEmberScalarTypeRead<OddSizedInteger<6, false>, ZCL_INT48U_ATTRIBUTE_TYPE>(0xAABB11223344);
}
TEST(TestCodegenModelViaMocks, EmberAttributeReadInt56U)
{
TestEmberScalarTypeRead<OddSizedInteger<7, false>, ZCL_INT56U_ATTRIBUTE_TYPE>(0xAABB11223344);
}
TEST(TestCodegenModelViaMocks, EmberAttributeReadBool)
{
TestEmberScalarTypeRead<bool, ZCL_BOOLEAN_ATTRIBUTE_TYPE>(true);
TestEmberScalarTypeRead<bool, ZCL_BOOLEAN_ATTRIBUTE_TYPE>(false);
}
TEST(TestCodegenModelViaMocks, EmberAttributeReadInt8U)
{
TestEmberScalarTypeRead<uint8_t, ZCL_INT8U_ATTRIBUTE_TYPE>(0x12);
}
TEST(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(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(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(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(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(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(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(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(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(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(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(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(TestCodegenModelViaMocks, ReadGlobalAttributeAttributeList)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
ReadOperation testRequest(kMockEndpoint2, MockClusterId(3), AttributeList::Id);
testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor);
// Data read via the encoder
std::unique_ptr<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<AttributeId> items;
ASSERT_EQ(DecodeList(encodedData.dataReader, items), CHIP_NO_ERROR);
// Mock data contains ClusterRevision and FeatureMap.
// After this, Global attributes are auto-added
std::vector<AttributeId> expected;
// Encoding in global-attribute-access-interface has a logic of:
// - Append global attributes in front of the first specified
// large number global attribute.
// Since ClusterRevision and FeatureMap are
// global attributes, the order here is reversed for them
for (AttributeId id : GlobalAttributesNotInMetadata)
{
expected.push_back(id);
}
expected.push_back(ClusterRevision::Id);
expected.push_back(FeatureMap::Id);
expected.push_back(MockAttributeId(1));
expected.push_back(MockAttributeId(2));
expected.push_back(MockAttributeId(3));
ASSERT_EQ(items.size(), expected.size());
// Since we have no std::vector formatter, comparing element by element is somewhat
// more readable in case of failure.
for (unsigned i = 0; i < items.size(); i++)
{
EXPECT_EQ(items[i], expected[i]);
}
}
TEST(TestCodegenModelViaMocks, EmberAttributeWriteAclDeny)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
/* Using this path is also failing existence checks, so this cannot be enabled
* until we fix ordering of ACL to be done before existence checks
WriteOperation test(kMockEndpoint1, MockClusterId(1), MockAttributeId(10));
AttributeValueDecoder decoder = test.DecoderFor<uint32_t>(1234);
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::UnsupportedAccess);
ASSERT_TRUE(model.ChangeListener().DirtyList().empty());
*/
WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_INT32U_ATTRIBUTE_TYPE));
AttributeValueDecoder decoder = test.DecoderFor<uint32_t>(1234);
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::UnsupportedAccess);
ASSERT_TRUE(model.ChangeListener().DirtyList().empty());
}
TEST(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(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(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(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(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(TestCodegenModelViaMocksNullValueToNullables, 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(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(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(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(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(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(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(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(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(TestCodegenModelViaMocks, EmberAttributeWriteTimedWrite)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
WriteOperation test(kMockEndpoint3, MockClusterId(4), kAttributeIdTimedWrite);
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
AttributeValueDecoder decoder = test.DecoderFor<int32_t>(1234);
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::NeedsTimedInteraction);
// writing as timed should be fine
test.SetWriteFlags(WriteFlags::kTimed);
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR);
}
TEST(TestCodegenModelViaMocks, EmberAttributeWriteReadOnlyAttribute)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
WriteOperation test(kMockEndpoint3, MockClusterId(4), kAttributeIdReadOnly);
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
AttributeValueDecoder decoder = test.DecoderFor<int32_t>(1234);
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::UnsupportedWrite);
// Internal writes bypass the read only requirement
test.SetOperationFlags(OperationFlags::kInternal);
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), CHIP_NO_ERROR);
}
TEST(TestCodegenModelViaMocks, EmberAttributeWriteDataVersion)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
WriteOperation test(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_INT32S_ATTRIBUTE_TYPE));
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
// Initialize to some version
ResetVersion();
BumpVersion();
test.SetDataVersion(MakeOptional(GetVersion()));
// Make version invalid
BumpVersion();
AttributeValueDecoder decoder = test.DecoderFor<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(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(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(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(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(TestCodegenModelViaMocks, EmberInvokeTest)
{
// Ember invoke is fully code-generated - there is a single function for Dispatch
// that will do a `switch` on the path elements and invoke a corresponding `emberAf*`
// callback.
//
// The only thing that can be validated is that this `DispatchSingleClusterCommand`
// is actually invoked.
UseMockNodeConfig config(gTestNodeConfig);
chip::app::CodegenDataModelProvider model;
{
const ConcreteCommandPath kCommandPath(kMockEndpoint1, MockClusterId(1), kMockCommandId1);
const InvokeRequest kInvokeRequest{ .path = kCommandPath };
chip::TLV::TLVReader tlvReader;
const uint32_t kDispatchCountPre = chip::Test::DispatchCount();
// Using a handler set to nullptr as it is not used by the impl
ASSERT_EQ(model.Invoke(kInvokeRequest, tlvReader, /* handler = */ nullptr), std::nullopt);
EXPECT_EQ(chip::Test::DispatchCount(), kDispatchCountPre + 1); // single dispatch
EXPECT_EQ(chip::Test::GetLastDispatchPath(), kCommandPath); // for the right path
}
{
const ConcreteCommandPath kCommandPath(kMockEndpoint1, MockClusterId(1), kMockCommandId2);
const InvokeRequest kInvokeRequest{ .path = kCommandPath };
chip::TLV::TLVReader tlvReader;
const uint32_t kDispatchCountPre = chip::Test::DispatchCount();
// Using a handler set to nullpotr as it is not used by the impl
ASSERT_EQ(model.Invoke(kInvokeRequest, tlvReader, /* handler = */ nullptr), std::nullopt);
EXPECT_EQ(chip::Test::DispatchCount(), kDispatchCountPre + 1); // single dispatch
EXPECT_EQ(chip::Test::GetLastDispatchPath(), kCommandPath); // for the right path
}
}
TEST(TestCodegenModelViaMocks, EmberWriteAttributeAccessInterfaceReturningError)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE));
RegisteredAttributeAccessInterface<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(TestCodegenModelViaMocks, EmberWriteInvalidDataType)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
ScopedMockAccessControl accessControl;
const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE));
WriteOperation test(kStructPath);
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
Clusters::UnitTesting::Structs::SimpleStruct::Type testValue{
.a = 112,
.b = true,
.e = "aai_write_test"_span,
.g = 0.5,
.h = 0.125,
};
AttributeValueDecoder decoder = test.DecoderFor(testValue);
// Embed specifically DOES NOT support structures.
// Without AAI, we expect a data type error (translated to failure)
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::Failure);
ASSERT_TRUE(model.ChangeListener().DirtyList().empty());
}
TEST(TestCodegenModelViaMocks, DeviceTypeIteration)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;
// Mock endpoint 1 has 3 device types
std::optional<DeviceTypeEntry> entry = model.FirstDeviceType(kMockEndpoint1);
ASSERT_EQ(entry,
std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId1, .deviceTypeVersion = kDeviceTypeId1Version }));
// NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none
entry = model.NextDeviceType(kMockEndpoint1, *entry);
ASSERT_EQ(entry,
std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeVersion = kDeviceTypeId2Version }));
// NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none
entry = model.NextDeviceType(kMockEndpoint1, *entry);
ASSERT_EQ(entry,
std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId3, .deviceTypeVersion = kDeviceTypeId3Version }));
// NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none
entry = model.NextDeviceType(kMockEndpoint1, *entry);
ASSERT_FALSE(entry.has_value());
// Mock endpoint 2 has 1 device types
entry = model.FirstDeviceType(kMockEndpoint2);
ASSERT_EQ(entry,
std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeVersion = kDeviceTypeId2Version }));
// NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none
entry = model.NextDeviceType(kMockEndpoint2, *entry);
ASSERT_FALSE(entry.has_value());
// out of order query works
entry = std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeVersion = kDeviceTypeId2Version });
entry = model.NextDeviceType(kMockEndpoint1, *entry);
ASSERT_EQ(entry,
std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId3, .deviceTypeVersion = kDeviceTypeId3Version }));
// invalid query fails
entry = std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId1, .deviceTypeVersion = kDeviceTypeId1Version });
entry = model.NextDeviceType(kMockEndpoint2, *entry);
ASSERT_FALSE(entry.has_value());
// empty endpoint works
entry = model.FirstDeviceType(kMockEndpoint3);
ASSERT_FALSE(entry.has_value());
}