blob: df759e78666659067fe01c03db2114cf6329718a [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 <app/codegen-data-model/CodegenDataModel.h>
#include <app/codegen-data-model/tests/AttributeReportIBEncodeDecode.h>
#include <app/codegen-data-model/tests/EmberReadWriteOverride.h>
#include <access/AccessControl.h>
#include <access/SubjectDescriptor.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/ConcreteAttributePath.h>
#include <app/GlobalAttributes.h>
#include <app/MessageDef/ReportDataMessage.h>
#include <app/data-model/Decode.h>
#include <app/data-model/Encode.h>
#include <app/util/attribute-storage-null-handling.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/StringBuilderAdapters.h>
#include <lib/core/TLVDebug.h>
#include <lib/core/TLVReader.h>
#include <lib/core/TLVWriter.h>
#include <gtest/gtest.h>
#include <vector>
using namespace chip;
using namespace chip::Test;
using namespace chip::app;
using namespace chip::app::InteractionModel;
using namespace chip::app::Clusters::Globals::Attributes;
namespace {
constexpr FabricIndex kTestFabrixIndex = kMinValidFabricIndex;
constexpr NodeId kTestNodeId = 0xFFFF'1234'ABCD'4321;
constexpr EndpointId kEndpointIdThatIsMissing = kMockEndpointMin - 1;
constexpr AttributeId kReadOnlyAttributeId = 0x5001;
static_assert(kEndpointIdThatIsMissing != kInvalidEndpointId);
static_assert(kEndpointIdThatIsMissing != kMockEndpoint1);
static_assert(kEndpointIdThatIsMissing != kMockEndpoint2);
static_assert(kEndpointIdThatIsMissing != kMockEndpoint3);
constexpr Access::SubjectDescriptor kAdminSubjectDescriptor{
.fabricIndex = kTestFabrixIndex,
.authMode = Access::AuthMode::kCase,
.subject = kTestNodeId,
};
constexpr Access::SubjectDescriptor kViewSubjectDescriptor{
.fabricIndex = kTestFabrixIndex + 1,
.authMode = Access::AuthMode::kCase,
.subject = kTestNodeId,
};
constexpr Access::SubjectDescriptor kDenySubjectDescriptor{
.fabricIndex = kTestFabrixIndex + 2,
.authMode = Access::AuthMode::kCase,
.subject = kTestNodeId,
};
bool operator==(const Access::SubjectDescriptor & a, const Access::SubjectDescriptor & b)
{
if (a.fabricIndex != b.fabricIndex)
{
return false;
}
if (a.authMode != b.authMode)
{
return false;
}
if (a.subject != b.subject)
{
return false;
}
for (unsigned i = 0; i < a.cats.values.size(); i++)
{
if (a.cats.values[i] != b.cats.values[i])
{
return false;
}
}
return true;
}
class MockAccessControl : public Access::AccessControl::Delegate, public Access::AccessControl::DeviceTypeResolver
{
public:
CHIP_ERROR Check(const Access::SubjectDescriptor & subjectDescriptor, const Access::RequestPath & requestPath,
Access::Privilege requestPrivilege) override
{
if (subjectDescriptor == kAdminSubjectDescriptor)
{
return CHIP_NO_ERROR;
}
if ((subjectDescriptor == kViewSubjectDescriptor) && (requestPrivilege == Access::Privilege::kView))
{
return CHIP_NO_ERROR;
}
return CHIP_ERROR_ACCESS_DENIED;
}
bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint) override { return true; }
};
class ScopedMockAccessControl
{
public:
ScopedMockAccessControl() { Access::GetAccessControl().Init(&mMock, mMock); }
~ScopedMockAccessControl() { Access::GetAccessControl().Finish(); }
private:
MockAccessControl mMock;
};
#define MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(zcl_type) MockAttributeId(zcl_type + 0x1000)
#define MOCK_ATTRIBUTE_CONFIG_NULLABLE(zcl_type) \
MockAttributeConfig(MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(zcl_type), zcl_type, ATTRIBUTE_MASK_WRITABLE | ATTRIBUTE_MASK_NULLABLE)
#define MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(zcl_type) MockAttributeId(zcl_type + 0x2000)
#define MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(zcl_type) \
MockAttributeConfig(MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(zcl_type), zcl_type, ATTRIBUTE_MASK_WRITABLE)
// clang-format off
const MockNodeConfig gTestNodeConfig({
MockEndpointConfig(kMockEndpoint1, {
MockClusterConfig(MockClusterId(1), {
ClusterRevision::Id, FeatureMap::Id,
}, {
MockEventId(1), MockEventId(2),
}),
MockClusterConfig(MockClusterId(2), {
ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1),
}),
}),
MockEndpointConfig(kMockEndpoint2, {
MockClusterConfig(MockClusterId(1), {
ClusterRevision::Id, FeatureMap::Id,
}),
MockClusterConfig(
MockClusterId(2),
{
ClusterRevision::Id,
FeatureMap::Id,
MockAttributeId(1),
MockAttributeConfig(MockAttributeId(2), ZCL_ARRAY_ATTRIBUTE_TYPE),
}, /* attributes */
{}, /* events */
{1, 2, 23}, /* acceptedCommands */
{2, 10} /* generatedCommands */
),
MockClusterConfig(
MockClusterId(3),
{
ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1), MockAttributeId(2), MockAttributeId(3),
}, /* attributes */
{}, /* events */
{11}, /* acceptedCommands */
{4, 6} /* generatedCommands */
),
}),
MockEndpointConfig(kMockEndpoint3, {
MockClusterConfig(MockClusterId(1), {
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),
}),
}),
});
// 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);
}
void SetReturnedData(const Clusters::UnitTesting::Structs::SimpleStruct::Type & data) { mData = data; }
Clusters::UnitTesting::Structs::SimpleStruct::Type simpleStruct;
private:
ConcreteAttributePath mPath;
Clusters::UnitTesting::Structs::SimpleStruct::Type mData;
};
class ListAttributeAcessInterface : public AttributeAccessInterface
{
public:
ListAttributeAcessInterface(ConcreteAttributePath path) :
AttributeAccessInterface(MakeOptional(path.mEndpointId), path.mClusterId), mPath(path)
{}
~ListAttributeAcessInterface() = default;
CHIP_ERROR Read(const ConcreteReadAttributePath & path, AttributeValueEncoder & encoder) override
{
if (static_cast<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; }
Clusters::UnitTesting::Structs::SimpleStruct::Type simpleStruct;
private:
ConcreteAttributePath mPath;
Clusters::UnitTesting::Structs::SimpleStruct::Type mData;
unsigned mCount = 0;
};
/// RAII registration of an attribute access interface
template <typename T>
class RegisteredAttributeAccessInterface
{
public:
template <typename... Args>
RegisteredAttributeAccessInterface(Args &&... args) : mData(std::forward<Args>(args)...)
{
VerifyOrDie(registerAttributeAccessOverride(&mData));
}
~RegisteredAttributeAccessInterface() { unregisterAttributeAccessOverride(&mData); }
T * operator->() { return &mData; }
T & operator*() { return mData; }
private:
T mData;
};
/// Contains a `ReadAttributeRequest` as well as classes to convert this into a AttributeReportIBs
/// and later decode it
///
/// It wraps boilerplate code to obtain a `AttributeValueEncoder` as well as later decoding
/// the underlying encoded data for verification.
struct TestReadRequest
{
ReadAttributeRequest request;
// encoded-used classes
EncodedReportIBs encodedIBs;
AttributeReportIBs::Builder reportBuilder;
std::unique_ptr<AttributeValueEncoder> encoder;
TestReadRequest(const Access::SubjectDescriptor & subject, const ConcreteAttributePath & path)
{
// operationFlags is 0 i.e. not internal
// readFlags is 0 i.e. not fabric filtered
// dataVersion is missing (no data version filtering)
request.subjectDescriptor = subject;
request.path = path;
}
std::unique_ptr<AttributeValueEncoder> StartEncoding(chip::app::InteractionModel::DataModel * model,
AttributeEncodeState state = AttributeEncodeState())
{
std::optional<ClusterInfo> info = model->GetClusterInfo(request.path);
if (!info.has_value())
{
ChipLogError(Test, "Missing cluster information - no data version");
return nullptr;
}
DataVersion dataVersion = info->dataVersion; // NOLINT(bugprone-unchecked-optional-access)
CHIP_ERROR err = encodedIBs.StartEncoding(reportBuilder);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Test, "FAILURE starting encoding %" CHIP_ERROR_FORMAT, err.Format());
return nullptr;
}
// TODO: could we test isFabricFiltered and EncodeState?
// request.subjectDescriptor is known non-null because it is set in the constructor
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
return std::make_unique<AttributeValueEncoder>(reportBuilder, *request.subjectDescriptor, request.path, dataVersion,
false /* aIsFabricFiltered */, state);
}
CHIP_ERROR FinishEncoding() { return encodedIBs.FinishEncoding(reportBuilder); }
};
template <typename T, EmberAfAttributeType ZclType>
void TestEmberScalarTypeRead(typename NumericAttributeTraits<T>::WorkingType value)
{
UseMockNodeConfig config(gTestNodeConfig);
chip::app::CodegenDataModel model;
ScopedMockAccessControl accessControl;
TestReadRequest testRequest(
kAdminSubjectDescriptor,
ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZclType)));
// Ember encoding for integers is IDENTICAL to the in-memory representation for them
typename NumericAttributeTraits<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(&model);
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after read
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.request.path);
typename NumericAttributeTraits<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);
chip::app::CodegenDataModel model;
ScopedMockAccessControl accessControl;
TestReadRequest testRequest(
kAdminSubjectDescriptor,
ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZclType)));
// Ember encoding for integers is IDENTICAL to the in-memory representation for them
typename NumericAttributeTraits<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(&model);
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after read
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.request.path);
chip::app::DataModel::Nullable<typename NumericAttributeTraits<T>::WorkingType> actual;
ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR);
ASSERT_TRUE(actual.IsNull());
}
} // namespace
TEST(TestCodegenModelViaMocks, IterateOverEndpoints)
{
UseMockNodeConfig config(gTestNodeConfig);
chip::app::CodegenDataModel model;
// This iteration relies on the hard-coding that occurs when mock_ember is used
EXPECT_EQ(model.FirstEndpoint(), kMockEndpoint1);
EXPECT_EQ(model.NextEndpoint(kMockEndpoint1), kMockEndpoint2);
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);
chip::app::CodegenDataModel 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);
chip::app::CodegenDataModel model;
chip::Test::ResetVersion();
ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kInvalidEndpointId, kInvalidClusterId)).has_value());
ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).has_value());
ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).has_value());
ASSERT_FALSE(model.GetClusterInfo(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).has_value());
// now get the value
std::optional<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);
chip::app::CodegenDataModel model;
// invalid paths should return in "no more data"
ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).path.HasValidIds());
ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).path.HasValidIds());
ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).path.HasValidIds());
ASSERT_FALSE(model.FirstAttribute(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds());
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);
chip::app::CodegenDataModel model;
// various non-existent or invalid paths should return no info data
ASSERT_FALSE(
model.GetAttributeInfo(ConcreteAttributePath(kInvalidEndpointId, kInvalidClusterId, kInvalidAttributeId)).has_value());
ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kInvalidEndpointId, kInvalidClusterId, FeatureMap::Id)).has_value());
ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kInvalidEndpointId, MockClusterId(1), FeatureMap::Id)).has_value());
ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, kInvalidClusterId, FeatureMap::Id)).has_value());
ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), FeatureMap::Id)).has_value());
ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(10), kInvalidAttributeId)).has_value());
ASSERT_FALSE(model.GetAttributeInfo(ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10))).has_value());
// valid info
std::optional<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);
chip::app::CodegenDataModel 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);
chip::app::CodegenDataModel model;
// invalid paths should return in "no more data"
ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).path.HasValidIds());
ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).path.HasValidIds());
ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).path.HasValidIds());
ASSERT_FALSE(model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).path.HasValidIds());
// should be able to iterate over valid paths
CommandEntry entry = model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2)));
ASSERT_TRUE(entry.path.HasValidIds());
EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(entry.path.mClusterId, MockClusterId(2));
EXPECT_EQ(entry.path.mCommandId, 1u);
entry = model.NextAcceptedCommand(entry.path);
ASSERT_TRUE(entry.path.HasValidIds());
EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(entry.path.mClusterId, MockClusterId(2));
EXPECT_EQ(entry.path.mCommandId, 2u);
entry = model.NextAcceptedCommand(entry.path);
ASSERT_TRUE(entry.path.HasValidIds());
EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(entry.path.mClusterId, MockClusterId(2));
EXPECT_EQ(entry.path.mCommandId, 23u);
entry = model.NextAcceptedCommand(entry.path);
ASSERT_FALSE(entry.path.HasValidIds());
// attempt some out-of-order requests as well
entry = model.FirstAcceptedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(3)));
ASSERT_TRUE(entry.path.HasValidIds());
EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(entry.path.mClusterId, MockClusterId(3));
EXPECT_EQ(entry.path.mCommandId, 11u);
for (int i = 0; i < 10; i++)
{
entry = model.NextAcceptedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 2));
ASSERT_TRUE(entry.path.HasValidIds());
EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(entry.path.mClusterId, MockClusterId(2));
EXPECT_EQ(entry.path.mCommandId, 23u);
}
for (int i = 0; i < 10; i++)
{
entry = model.NextAcceptedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 1));
ASSERT_TRUE(entry.path.HasValidIds());
EXPECT_EQ(entry.path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(entry.path.mClusterId, MockClusterId(2));
EXPECT_EQ(entry.path.mCommandId, 2u);
}
for (int i = 0; i < 10; i++)
{
entry = model.NextAcceptedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(3), 10));
EXPECT_FALSE(entry.path.HasValidIds());
}
}
TEST(TestCodegenModelViaMocks, AcceptedCommandInfo)
{
UseMockNodeConfig config(gTestNodeConfig);
chip::app::CodegenDataModel model;
// invalid paths should return in "no more data"
ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kEndpointIdThatIsMissing, MockClusterId(1), 1)).has_value());
ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kInvalidEndpointId, MockClusterId(1), 1)).has_value());
ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(10), 1)).has_value());
ASSERT_FALSE(model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, kInvalidClusterId, 1)).has_value());
ASSERT_FALSE(
model.GetAcceptedCommandInfo(ConcreteCommandPath(kMockEndpoint1, MockClusterId(1), kInvalidCommandId)).has_value());
std::optional<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);
chip::app::CodegenDataModel model;
// invalid paths should return in "no more data"
ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kEndpointIdThatIsMissing, MockClusterId(1))).HasValidIds());
ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kInvalidEndpointId, MockClusterId(1))).HasValidIds());
ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, MockClusterId(10))).HasValidIds());
ASSERT_FALSE(model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint1, kInvalidClusterId)).HasValidIds());
// should be able to iterate over valid paths
ConcreteCommandPath path = model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(2)));
ASSERT_TRUE(path.HasValidIds());
EXPECT_EQ(path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(path.mClusterId, MockClusterId(2));
EXPECT_EQ(path.mCommandId, 2u);
path = model.NextGeneratedCommand(path);
ASSERT_TRUE(path.HasValidIds());
EXPECT_EQ(path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(path.mClusterId, MockClusterId(2));
EXPECT_EQ(path.mCommandId, 10u);
path = model.NextGeneratedCommand(path);
ASSERT_FALSE(path.HasValidIds());
// attempt some out-of-order requests as well
path = model.FirstGeneratedCommand(ConcreteClusterPath(kMockEndpoint2, MockClusterId(3)));
ASSERT_TRUE(path.HasValidIds());
EXPECT_EQ(path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(path.mClusterId, MockClusterId(3));
EXPECT_EQ(path.mCommandId, 4u);
for (int i = 0; i < 10; i++)
{
path = model.NextGeneratedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(2), 2));
ASSERT_TRUE(path.HasValidIds());
EXPECT_EQ(path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(path.mClusterId, MockClusterId(2));
EXPECT_EQ(path.mCommandId, 10u);
}
for (int i = 0; i < 10; i++)
{
path = model.NextGeneratedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(3), 4));
ASSERT_TRUE(path.HasValidIds());
EXPECT_EQ(path.mEndpointId, kMockEndpoint2);
EXPECT_EQ(path.mClusterId, MockClusterId(3));
EXPECT_EQ(path.mCommandId, 6u);
}
for (int i = 0; i < 10; i++)
{
path = model.NextGeneratedCommand(ConcreteCommandPath(kMockEndpoint2, MockClusterId(3), 6));
EXPECT_FALSE(path.HasValidIds());
}
}
TEST(TestCodegenModelViaMocks, EmberAttributeReadAclDeny)
{
UseMockNodeConfig config(gTestNodeConfig);
chip::app::CodegenDataModel model;
ScopedMockAccessControl accessControl;
TestReadRequest testRequest(kDenySubjectDescriptor,
ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10)));
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(&model);
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_ERROR_ACCESS_DENIED);
}
TEST(TestCodegenModelViaMocks, ReadForInvalidGlobalAttributePath)
{
UseMockNodeConfig config(gTestNodeConfig);
chip::app::CodegenDataModel model;
ScopedMockAccessControl accessControl;
{
TestReadRequest testRequest(kAdminSubjectDescriptor,
ConcreteAttributePath(kEndpointIdThatIsMissing, MockClusterId(1), AttributeList::Id));
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(&model);
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint));
}
{
TestReadRequest testRequest(kAdminSubjectDescriptor,
ConcreteAttributePath(kMockEndpoint1, kInvalidClusterId, AttributeList::Id));
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(&model);
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedCluster));
}
}
TEST(TestCodegenModelViaMocks, EmberAttributeInvalidRead)
{
UseMockNodeConfig config(gTestNodeConfig);
chip::app::CodegenDataModel model;
ScopedMockAccessControl accessControl;
// Invalid attribute
{
TestReadRequest testRequest(kAdminSubjectDescriptor,
ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10)));
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(&model);
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute));
}
// Invalid cluster
{
TestReadRequest testRequest(kAdminSubjectDescriptor,
ConcreteAttributePath(kMockEndpoint1, MockClusterId(100), MockAttributeId(1)));
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(&model);
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedCluster));
}
// Invalid endpoint
{
TestReadRequest testRequest(kAdminSubjectDescriptor,
ConcreteAttributePath(kEndpointIdThatIsMissing, MockClusterId(1), MockAttributeId(1)));
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(&model);
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint));
}
}
TEST(TestCodegenModelViaMocks, EmberAttributePathExpansionAccessDeniedRead)
{
UseMockNodeConfig config(gTestNodeConfig);
chip::app::CodegenDataModel model;
ScopedMockAccessControl accessControl;
TestReadRequest testRequest(kDenySubjectDescriptor,
ConcreteAttributePath(kMockEndpoint1, MockClusterId(1), MockAttributeId(10)));
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(&model);
testRequest.request.path.mExpanded = true;
// For expanded paths, access control failures succeed without encoding anything
// This is temporary until ACL checks are moved inside the IM/ReportEngine
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR);
ASSERT_FALSE(encoder->TriedEncode());
}
TEST(TestCodegenModelViaMocks, AccessInterfaceUnsupportedRead)
{
UseMockNodeConfig config(gTestNodeConfig);
chip::app::CodegenDataModel model;
ScopedMockAccessControl accessControl;
const ConcreteAttributePath kTestPath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE));
TestReadRequest testRequest(kAdminSubjectDescriptor, kTestPath);
RegisteredAttributeAccessInterface<UnsupportedReadAccessInterface> aai(kTestPath);
testRequest.request.path.mExpanded = true;
// For expanded paths, unsupported read from AAI (i.e. reading write-only data)
// succeed without attempting to encode.
// This is temporary until ACL checks are moved inside the IM/ReportEngine
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(&model);
ASSERT_EQ(model.ReadAttribute(testRequest.request, *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);
chip::app::CodegenDataModel model;
ScopedMockAccessControl accessControl;
{
TestReadRequest testRequest(
kAdminSubjectDescriptor,
ConcreteAttributePath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE)));
chip::Test::SetEmberReadOutput(Protocols::InteractionModel::Status::Failure);
// Actual read via an encoder
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(&model);
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(Failure));
}
{
TestReadRequest testRequest(
kAdminSubjectDescriptor,
ConcreteAttributePath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE)));
chip::Test::SetEmberReadOutput(Protocols::InteractionModel::Status::Busy);
// Actual read via an encoder
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(&model);
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_IM_GLOBAL_STATUS(Busy));
}
}
TEST(TestCodegenModelViaMocks, EmberAttributeReadNullOctetString)
{
UseMockNodeConfig config(gTestNodeConfig);
chip::app::CodegenDataModel model;
ScopedMockAccessControl accessControl;
TestReadRequest testRequest(kAdminSubjectDescriptor,
ConcreteAttributePath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE)));
// NOTE: This is a pascal string of size 0xFFFF which for null strings is a null marker
char data[] = "\xFF\xFFInvalid length string is null";
chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast<const uint8_t *>(data), sizeof(data)));
// Actual read via an encoder
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(&model);
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after read
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.request.path);
// data element should be null for the given 0xFFFF length
ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Null);
chip::app::DataModel::Nullable<ByteSpan> actual;
ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR);
ASSERT_TRUE(actual.IsNull());
}
TEST(TestCodegenModelViaMocks, EmberAttributeReadOctetString)
{
UseMockNodeConfig config(gTestNodeConfig);
chip::app::CodegenDataModel model;
ScopedMockAccessControl accessControl;
TestReadRequest testRequest(
kAdminSubjectDescriptor,
ConcreteAttributePath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE)));
// NOTE: This is a pascal string, so actual data is "test"
// the longer encoding is to make it clear we do not encode the overflow
char data[] = "\0\0testing here with overflow";
uint16_t len = 4;
memcpy(data, &len, sizeof(uint16_t));
chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast<const uint8_t *>(data), sizeof(data)));
// Actual read via an encoder
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(&model);
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after read
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
const DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.request.path);
// data element should be a encoded byte string as this is what the attribute type is
ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_ByteString);
ByteSpan actual;
ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR);
ByteSpan expected(reinterpret_cast<const uint8_t *>(data + 2), 4);
ASSERT_TRUE(actual.data_equal(expected));
}
TEST(TestCodegenModelViaMocks, EmberAttributeReadLongOctetString)
{
UseMockNodeConfig config(gTestNodeConfig);
chip::app::CodegenDataModel model;
ScopedMockAccessControl accessControl;
TestReadRequest testRequest(kAdminSubjectDescriptor,
ConcreteAttributePath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_OCTET_STRING_ATTRIBUTE_TYPE)));
// NOTE: This is a pascal string, so actual data is "test"
// the longer encoding is to make it clear we do not encode the overflow
const char data[] = "\x04testing here with overflow";
chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast<const uint8_t *>(data), sizeof(data)));
// Actual read via an encoder
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(&model);
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after read
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
const DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.request.path);
// data element should be a encoded byte string as this is what the attribute type is
ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_ByteString);
ByteSpan actual;
ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR);
ByteSpan expected(reinterpret_cast<const uint8_t *>(data + 1), 4);
ASSERT_TRUE(actual.data_equal(expected));
}
TEST(TestCodegenModelViaMocks, EmberAttributeReadShortString)
{
UseMockNodeConfig config(gTestNodeConfig);
chip::app::CodegenDataModel model;
ScopedMockAccessControl accessControl;
TestReadRequest testRequest(kAdminSubjectDescriptor,
ConcreteAttributePath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_CHAR_STRING_ATTRIBUTE_TYPE)));
// NOTE: This is a pascal string, so actual data is "abcde"
// the longer encoding is to make it clear we do not encode the overflow
char data[] = "\0abcdef...this is the alphabet";
uint16_t len = 5;
memcpy(data, &len, sizeof(uint8_t));
chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast<const uint8_t *>(data), sizeof(data)));
// Actual read via an encoder
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(&model);
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after reading
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
const DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.request.path);
// data element should be a encoded byte string as this is what the attribute type is
ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_UTF8String);
CharSpan actual;
ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR);
ASSERT_TRUE(actual.data_equal("abcde"_span));
}
TEST(TestCodegenModelViaMocks, EmberAttributeReadLongString)
{
UseMockNodeConfig config(gTestNodeConfig);
chip::app::CodegenDataModel model;
ScopedMockAccessControl accessControl;
TestReadRequest testRequest(
kAdminSubjectDescriptor,
ConcreteAttributePath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE)));
// NOTE: This is a pascal string, so actual data is "abcde"
// the longer encoding is to make it clear we do not encode the overflow
char data[] = "\0\0abcdef...this is the alphabet";
uint16_t len = 5;
memcpy(data, &len, sizeof(uint16_t));
chip::Test::SetEmberReadOutput(ByteSpan(reinterpret_cast<const uint8_t *>(data), sizeof(data)));
// Actual read via an encoder
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(&model);
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after reading
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
const DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.request.path);
// data element should be a encoded byte string as this is what the attribute type is
ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_UTF8String);
CharSpan actual;
ASSERT_EQ(encodedData.dataReader.Get(actual), CHIP_NO_ERROR);
ASSERT_TRUE(actual.data_equal("abcde"_span));
}
TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceStructRead)
{
UseMockNodeConfig config(gTestNodeConfig);
chip::app::CodegenDataModel model;
ScopedMockAccessControl accessControl;
const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_STRUCT_ATTRIBUTE_TYPE));
TestReadRequest testRequest(kAdminSubjectDescriptor, kStructPath);
RegisteredAttributeAccessInterface<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(&model);
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after read
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.request.path);
ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Structure);
Clusters::UnitTesting::Structs::SimpleStruct::DecodableType actual;
ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR);
ASSERT_EQ(actual.a, 123);
ASSERT_EQ(actual.b, true);
ASSERT_EQ(actual.g, 0.5);
ASSERT_EQ(actual.h, 0.125);
ASSERT_TRUE(actual.e.data_equal("foo"_span));
}
TEST(TestCodegenModelViaMocks, AttributeAccessInterfaceListRead)
{
UseMockNodeConfig config(gTestNodeConfig);
chip::app::CodegenDataModel model;
ScopedMockAccessControl accessControl;
const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_ARRAY_ATTRIBUTE_TYPE));
TestReadRequest testRequest(kAdminSubjectDescriptor, kStructPath);
RegisteredAttributeAccessInterface<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(&model);
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after read
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.request.path);
ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Array);
std::vector<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);
chip::app::CodegenDataModel model;
ScopedMockAccessControl accessControl;
const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_ARRAY_ATTRIBUTE_TYPE));
TestReadRequest testRequest(kAdminSubjectDescriptor, kStructPath);
RegisteredAttributeAccessInterface<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(&model);
// NOTE: overflow, however data should be valid. Technically both NO_MEMORY and BUFFER_TOO_SMALL
// should be ok here, however we know buffer-too-small is the error in this case hence
// the compare (easier to write the test and read the output)
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_ERROR_BUFFER_TOO_SMALL);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after read
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.request.path);
ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Array);
std::vector<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);
chip::app::CodegenDataModel model;
ScopedMockAccessControl accessControl;
const ConcreteAttributePath kStructPath(kMockEndpoint3, MockClusterId(4),
MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(ZCL_ARRAY_ATTRIBUTE_TYPE));
TestReadRequest testRequest(kAdminSubjectDescriptor, kStructPath);
RegisteredAttributeAccessInterface<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(&model, encodeState);
// NOTE: overflow, however data should be valid. Technically both NO_MEMORY and BUFFER_TOO_SMALL
// should be ok here, however we know buffer-too-small is the error in this case hence
// the compare (easier to write the test and read the output)
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_ERROR_BUFFER_TOO_SMALL);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after read
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR);
// Incremental encodes are separate list items, repeated
// actual size IS ARBITRARY (current test sets it at 11)
ASSERT_GT(attribute_data.size(), 3u);
for (unsigned i = 0; i < attribute_data.size(); i++)
{
DecodedAttributeData & encodedData = attribute_data[i];
ASSERT_EQ(encodedData.attributePath.mEndpointId, testRequest.request.path.mEndpointId);
ASSERT_EQ(encodedData.attributePath.mClusterId, testRequest.request.path.mClusterId);
ASSERT_EQ(encodedData.attributePath.mAttributeId, testRequest.request.path.mAttributeId);
ASSERT_EQ(encodedData.attributePath.mListOp, ConcreteDataAttributePath::ListOperation::AppendItem);
// individual structures encoded in each item
ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Structure);
Clusters::UnitTesting::Structs::SimpleStruct::DecodableType actual;
ASSERT_EQ(chip::app::DataModel::Decode(encodedData.dataReader, actual), CHIP_NO_ERROR);
ASSERT_EQ(actual.a, static_cast<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);
chip::app::CodegenDataModel model;
ScopedMockAccessControl accessControl;
TestReadRequest testRequest(kAdminSubjectDescriptor,
ConcreteAttributePath(kMockEndpoint2, MockClusterId(3), AttributeList::Id));
// Data read via the encoder
std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(&model);
ASSERT_EQ(model.ReadAttribute(testRequest.request, *encoder), CHIP_NO_ERROR);
ASSERT_EQ(testRequest.FinishEncoding(), CHIP_NO_ERROR);
// Validate after read
std::vector<DecodedAttributeData> attribute_data;
ASSERT_EQ(testRequest.encodedIBs.Decode(attribute_data), CHIP_NO_ERROR);
ASSERT_EQ(attribute_data.size(), 1u);
DecodedAttributeData & encodedData = attribute_data[0];
ASSERT_EQ(encodedData.attributePath, testRequest.request.path);
ASSERT_EQ(encodedData.dataReader.GetType(), TLV::kTLVType_Array);
std::vector<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]);
}
}