blob: 2e5d7e65f7ee4275b231234562ffe6661da2d581 [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 "DataModelFixtures.h"
#include <app-common/zap-generated/cluster-objects.h>
#include <app-common/zap-generated/ids/Clusters.h>
#include <app/AttributeValueDecoder.h>
#include <app/InteractionModelEngine.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::DataModelTests;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::UnitTesting;
using namespace chip::Protocols;
namespace chip {
namespace app {
namespace DataModelTests {
ScopedChangeOnly<ReadResponseDirective> gReadResponseDirective(ReadResponseDirective::kSendDataResponse);
ScopedChangeOnly<WriteResponseDirective> gWriteResponseDirective(WriteResponseDirective::kSendAttributeSuccess);
ScopedChangeOnly<CommandResponseDirective> gCommandResponseDirective(CommandResponseDirective::kSendSuccessStatusCode);
ScopedChangeOnly<bool> gIsLitIcd(false);
uint16_t gInt16uTotalReadCount = 0;
CommandHandler::Handle gAsyncCommandHandle;
} // namespace DataModelTests
CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered,
const ConcreteReadAttributePath & aPath, AttributeReportIBs::Builder & aAttributeReports,
AttributeEncodeState * apEncoderState)
{
if (aPath.mEndpointId >= chip::Test::kMockEndpointMin)
{
return chip::Test::ReadSingleMockClusterData(aSubjectDescriptor.fabricIndex, aPath, aAttributeReports, apEncoderState);
}
if (gReadResponseDirective == ReadResponseDirective::kSendManyDataResponses ||
gReadResponseDirective == ReadResponseDirective::kSendManyDataResponsesWrongPath)
{
if (aPath.mClusterId != Clusters::UnitTesting::Id || aPath.mAttributeId != Clusters::UnitTesting::Attributes::Boolean::Id)
{
return CHIP_ERROR_INCORRECT_STATE;
}
for (size_t i = 0; i < 4; ++i)
{
ConcreteAttributePath path(aPath);
// Use an incorrect attribute id for some of the responses.
path.mAttributeId = static_cast<AttributeId>(
path.mAttributeId + (i / 2) + (gReadResponseDirective == ReadResponseDirective::kSendManyDataResponsesWrongPath));
AttributeEncodeState state(apEncoderState);
AttributeValueEncoder valueEncoder(aAttributeReports, aSubjectDescriptor, path, kDataVersion, aIsFabricFiltered, state);
ReturnErrorOnFailure(valueEncoder.Encode(true));
}
return CHIP_NO_ERROR;
}
if (gReadResponseDirective == ReadResponseDirective::kSendDataResponse)
{
if (aPath.mClusterId == app::Clusters::UnitTesting::Id &&
aPath.mAttributeId == app::Clusters::UnitTesting::Attributes::ListFabricScoped::Id)
{
AttributeEncodeState state(apEncoderState);
AttributeValueEncoder valueEncoder(aAttributeReports, aSubjectDescriptor, aPath, kDataVersion, aIsFabricFiltered,
state);
return valueEncoder.EncodeList([aSubjectDescriptor](const auto & encoder) -> CHIP_ERROR {
app::Clusters::UnitTesting::Structs::TestFabricScoped::Type val;
val.fabricIndex = aSubjectDescriptor.fabricIndex;
ReturnErrorOnFailure(encoder.Encode(val));
val.fabricIndex = (val.fabricIndex == 1) ? 2 : 1;
ReturnErrorOnFailure(encoder.Encode(val));
return CHIP_NO_ERROR;
});
}
if (aPath.mClusterId == app::Clusters::UnitTesting::Id &&
aPath.mAttributeId == app::Clusters::UnitTesting::Attributes::Int16u::Id)
{
AttributeEncodeState state(apEncoderState);
AttributeValueEncoder valueEncoder(aAttributeReports, aSubjectDescriptor, aPath, kDataVersion, aIsFabricFiltered,
state);
return valueEncoder.Encode(++gInt16uTotalReadCount);
}
if (aPath.mClusterId == kPerpetualClusterId ||
(aPath.mClusterId == app::Clusters::UnitTesting::Id && aPath.mAttributeId == kPerpetualAttributeid))
{
AttributeEncodeState state;
AttributeValueEncoder valueEncoder(aAttributeReports, aSubjectDescriptor, aPath, kDataVersion, aIsFabricFiltered,
state);
CHIP_ERROR err = valueEncoder.EncodeList([](const auto & encoder) -> CHIP_ERROR {
encoder.Encode(static_cast<uint8_t>(1));
return CHIP_ERROR_NO_MEMORY;
});
if (err != CHIP_NO_ERROR)
{
// If the err is not CHIP_NO_ERROR, means the encoding was aborted, then the valueEncoder may save its state.
// The state is used by list chunking feature for now.
if (apEncoderState != nullptr)
{
*apEncoderState = valueEncoder.GetState();
}
return err;
}
}
if (aPath.mClusterId == app::Clusters::IcdManagement::Id &&
aPath.mAttributeId == app::Clusters::IcdManagement::Attributes::OperatingMode::Id)
{
AttributeEncodeState state(apEncoderState);
AttributeValueEncoder valueEncoder(aAttributeReports, aSubjectDescriptor, aPath, kDataVersion, aIsFabricFiltered,
state);
return valueEncoder.Encode(gIsLitIcd ? Clusters::IcdManagement::OperatingModeEnum::kLit
: Clusters::IcdManagement::OperatingModeEnum::kSit);
}
AttributeReportIB::Builder & attributeReport = aAttributeReports.CreateAttributeReport();
ReturnErrorOnFailure(aAttributeReports.GetError());
AttributeDataIB::Builder & attributeData = attributeReport.CreateAttributeData();
ReturnErrorOnFailure(attributeReport.GetError());
Clusters::UnitTesting::Attributes::ListStructOctetString::TypeInfo::Type value;
Clusters::UnitTesting::Structs::TestListStructOctet::Type valueBuf[4];
value = valueBuf;
uint8_t i = 0;
for (auto & item : valueBuf)
{
item.member1 = i;
i++;
}
attributeData.DataVersion(kDataVersion);
ReturnErrorOnFailure(attributeData.GetError());
AttributePathIB::Builder & attributePath = attributeData.CreatePath();
attributePath.Endpoint(aPath.mEndpointId).Cluster(aPath.mClusterId).Attribute(aPath.mAttributeId).EndOfAttributePathIB();
ReturnErrorOnFailure(attributePath.GetError());
ReturnErrorOnFailure(DataModel::Encode(*(attributeData.GetWriter()), TLV::ContextTag(AttributeDataIB::Tag::kData), value));
ReturnErrorOnFailure(attributeData.EndOfAttributeDataIB());
return attributeReport.EndOfAttributeReportIB();
}
for (size_t i = 0; i < (gReadResponseDirective == ReadResponseDirective::kSendTwoDataErrors ? 2 : 1); ++i)
{
AttributeReportIB::Builder & attributeReport = aAttributeReports.CreateAttributeReport();
ReturnErrorOnFailure(aAttributeReports.GetError());
AttributeStatusIB::Builder & attributeStatus = attributeReport.CreateAttributeStatus();
AttributePathIB::Builder & attributePath = attributeStatus.CreatePath();
attributePath.Endpoint(aPath.mEndpointId).Cluster(aPath.mClusterId).Attribute(aPath.mAttributeId).EndOfAttributePathIB();
ReturnErrorOnFailure(attributePath.GetError());
StatusIB::Builder & errorStatus = attributeStatus.CreateErrorStatus();
ReturnErrorOnFailure(attributeStatus.GetError());
errorStatus.EncodeStatusIB(StatusIB(Protocols::InteractionModel::Status::Busy));
attributeStatus.EndOfAttributeStatusIB();
ReturnErrorOnFailure(attributeStatus.GetError());
ReturnErrorOnFailure(attributeReport.EndOfAttributeReportIB());
}
return CHIP_NO_ERROR;
}
bool IsClusterDataVersionEqual(const app::ConcreteClusterPath & aConcreteClusterPath, DataVersion aRequiredVersion)
{
if (aRequiredVersion == kDataVersion)
{
return true;
}
if (Test::GetVersion() == aRequiredVersion)
{
return true;
}
return false;
}
bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint)
{
return false;
}
bool ConcreteAttributePathExists(const ConcreteAttributePath & aPath)
{
return true;
}
Protocols::InteractionModel::Status CheckEventSupportStatus(const ConcreteEventPath & aPath)
{
return Protocols::InteractionModel::Status::Success;
}
const EmberAfAttributeMetadata * GetAttributeMetadata(const ConcreteAttributePath & aConcreteClusterPath)
{
// Note: This test does not make use of the real attribute metadata.
static EmberAfAttributeMetadata stub = { .defaultValue = EmberAfDefaultOrMinMaxAttributeValue(uint32_t(0)) };
return &stub;
}
CHIP_ERROR WriteSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, const ConcreteDataAttributePath & aPath,
TLV::TLVReader & aReader, WriteHandler * aWriteHandler)
{
static ListIndex listStructOctetStringElementCount = 0;
if (aPath.mDataVersion.HasValue() && aPath.mDataVersion.Value() == kRejectedDataVersion)
{
return aWriteHandler->AddStatus(aPath, Protocols::InteractionModel::Status::DataVersionMismatch);
}
if (aPath.mClusterId == Clusters::UnitTesting::Id &&
aPath.mAttributeId == Attributes::ListStructOctetString::TypeInfo::GetAttributeId())
{
if (gWriteResponseDirective == WriteResponseDirective::kSendAttributeSuccess)
{
if (!aPath.IsListOperation() || aPath.mListOp == ConcreteDataAttributePath::ListOperation::ReplaceAll)
{
Attributes::ListStructOctetString::TypeInfo::DecodableType value;
ReturnErrorOnFailure(DataModel::Decode(aReader, value));
auto iter = value.begin();
listStructOctetStringElementCount = 0;
while (iter.Next())
{
auto & item = iter.GetValue();
VerifyOrReturnError(item.member1 == listStructOctetStringElementCount, CHIP_ERROR_INVALID_ARGUMENT);
listStructOctetStringElementCount++;
}
aWriteHandler->AddStatus(aPath, Protocols::InteractionModel::Status::Success);
}
else if (aPath.mListOp == ConcreteDataAttributePath::ListOperation::AppendItem)
{
Structs::TestListStructOctet::DecodableType item;
ReturnErrorOnFailure(DataModel::Decode(aReader, item));
VerifyOrReturnError(item.member1 == listStructOctetStringElementCount, CHIP_ERROR_INVALID_ARGUMENT);
listStructOctetStringElementCount++;
aWriteHandler->AddStatus(aPath, Protocols::InteractionModel::Status::Success);
}
else
{
return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
}
}
else
{
aWriteHandler->AddStatus(aPath, Protocols::InteractionModel::Status::Failure);
}
return CHIP_NO_ERROR;
}
if (aPath.mClusterId == Clusters::UnitTesting::Id && aPath.mAttributeId == Attributes::ListFabricScoped::Id)
{
// Mock a invalid SubjectDescriptor
AttributeValueDecoder decoder(aReader, Access::SubjectDescriptor());
if (!aPath.IsListOperation() || aPath.mListOp == ConcreteDataAttributePath::ListOperation::ReplaceAll)
{
Attributes::ListFabricScoped::TypeInfo::DecodableType value;
ReturnErrorOnFailure(decoder.Decode(value));
auto iter = value.begin();
while (iter.Next())
{
auto & item = iter.GetValue();
(void) item;
}
aWriteHandler->AddStatus(aPath, Protocols::InteractionModel::Status::Success);
}
else if (aPath.mListOp == ConcreteDataAttributePath::ListOperation::AppendItem)
{
Structs::TestFabricScoped::DecodableType item;
ReturnErrorOnFailure(decoder.Decode(item));
aWriteHandler->AddStatus(aPath, Protocols::InteractionModel::Status::Success);
}
else
{
return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
}
return CHIP_NO_ERROR;
}
// Boolean attribute of unit testing cluster triggers "multiple errors" case.
if (aPath.mClusterId == Clusters::UnitTesting::Id && aPath.mAttributeId == Attributes::Boolean::TypeInfo::GetAttributeId())
{
Protocols::InteractionModel::ClusterStatusCode status{ Protocols::InteractionModel::Status::InvalidValue };
if (gWriteResponseDirective == WriteResponseDirective::kSendMultipleSuccess)
{
status = Protocols::InteractionModel::Status::Success;
}
else if (gWriteResponseDirective == WriteResponseDirective::kSendMultipleErrors)
{
status = Protocols::InteractionModel::Status::Failure;
}
else
{
VerifyOrDie(false);
}
for (size_t i = 0; i < 4; ++i)
{
aWriteHandler->AddStatus(aPath, status);
}
return CHIP_NO_ERROR;
}
if (aPath.mClusterId == Clusters::UnitTesting::Id && aPath.mAttributeId == Attributes::Int8u::TypeInfo::GetAttributeId())
{
Protocols::InteractionModel::ClusterStatusCode status{ Protocols::InteractionModel::Status::InvalidValue };
if (gWriteResponseDirective == WriteResponseDirective::kSendClusterSpecificSuccess)
{
status = Protocols::InteractionModel::ClusterStatusCode::ClusterSpecificSuccess(kExampleClusterSpecificSuccess);
}
else if (gWriteResponseDirective == WriteResponseDirective::kSendClusterSpecificFailure)
{
status = Protocols::InteractionModel::ClusterStatusCode::ClusterSpecificFailure(kExampleClusterSpecificFailure);
}
else
{
VerifyOrDie(false);
}
aWriteHandler->AddStatus(aPath, status);
return CHIP_NO_ERROR;
}
return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
}
void DispatchSingleClusterCommand(const ConcreteCommandPath & aCommandPath, chip::TLV::TLVReader & aReader,
CommandHandler * apCommandObj)
{
ChipLogDetail(Controller, "Received Cluster Command: Endpoint=%x Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI,
aCommandPath.mEndpointId, ChipLogValueMEI(aCommandPath.mClusterId), ChipLogValueMEI(aCommandPath.mCommandId));
if (aCommandPath.mClusterId == Clusters::UnitTesting::Id &&
aCommandPath.mCommandId == Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Type::GetCommandId())
{
Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::DecodableType dataRequest;
if (DataModel::Decode(aReader, dataRequest) != CHIP_NO_ERROR)
{
apCommandObj->AddStatus(aCommandPath, Protocols::InteractionModel::Status::Failure, "Unable to decode the request");
return;
}
if (gCommandResponseDirective == CommandResponseDirective::kSendDataResponse)
{
Clusters::UnitTesting::Commands::TestStructArrayArgumentResponse::Type dataResponse;
Clusters::UnitTesting::Structs::NestedStructList::Type nestedStructList[4];
uint8_t i = 0;
for (auto & item : nestedStructList)
{
item.a = i;
item.b = false;
item.c.a = i;
item.c.b = true;
i++;
}
dataResponse.arg1 = nestedStructList;
dataResponse.arg6 = true;
apCommandObj->AddResponse(aCommandPath, dataResponse);
}
else if (gCommandResponseDirective == CommandResponseDirective::kSendSuccessStatusCode)
{
apCommandObj->AddStatus(aCommandPath, Protocols::InteractionModel::Status::Success);
}
else if (gCommandResponseDirective == CommandResponseDirective::kSendMultipleSuccessStatusCodes)
{
apCommandObj->AddStatus(aCommandPath, Protocols::InteractionModel::Status::Success,
"No error but testing status success case");
// TODO: Right now all but the first AddStatus call fail, so this
// test is not really testing what it should.
for (size_t i = 0; i < 3; ++i)
{
(void) apCommandObj->FallibleAddStatus(aCommandPath, Protocols::InteractionModel::Status::Success,
"No error but testing status success case");
}
// And one failure on the end.
(void) apCommandObj->FallibleAddStatus(aCommandPath, Protocols::InteractionModel::Status::Failure);
}
else if (gCommandResponseDirective == CommandResponseDirective::kSendError)
{
apCommandObj->AddStatus(aCommandPath, Protocols::InteractionModel::Status::Failure);
}
else if (gCommandResponseDirective == CommandResponseDirective::kSendMultipleErrors)
{
apCommandObj->AddStatus(aCommandPath, Protocols::InteractionModel::Status::Failure);
// TODO: Right now all but the first AddStatus call fail, so this
// test is not really testing what it should.
for (size_t i = 0; i < 3; ++i)
{
(void) apCommandObj->FallibleAddStatus(aCommandPath, Protocols::InteractionModel::Status::Failure);
}
}
else if (gCommandResponseDirective == CommandResponseDirective::kSendSuccessStatusCodeWithClusterStatus)
{
apCommandObj->AddStatus(
aCommandPath, Protocols::InteractionModel::ClusterStatusCode::ClusterSpecificSuccess(kTestSuccessClusterStatus));
}
else if (gCommandResponseDirective == CommandResponseDirective::kSendErrorWithClusterStatus)
{
apCommandObj->AddStatus(
aCommandPath, Protocols::InteractionModel::ClusterStatusCode::ClusterSpecificFailure(kTestFailureClusterStatus));
}
else if (gCommandResponseDirective == CommandResponseDirective::kAsync)
{
gAsyncCommandHandle = apCommandObj;
}
}
}
Protocols::InteractionModel::Status ServerClusterCommandExists(const ConcreteCommandPath & aCommandPath)
{
// Mock cluster catalog, only support commands on one cluster on one endpoint.
using Protocols::InteractionModel::Status;
if (aCommandPath.mEndpointId != kTestEndpointId)
{
return Status::UnsupportedEndpoint;
}
if (aCommandPath.mClusterId != Clusters::UnitTesting::Id)
{
return Status::UnsupportedCluster;
}
return Status::Success;
}
} // namespace app
} // namespace chip