blob: 2a4328b853283f4d892b13dae16fddc9fc60d86e [file] [log] [blame]
/*
*
* Copyright (c) 2021 Project CHIP Authors
*
* 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.
*/
/****************************************************************************
* @file
* @brief Implementation for the Test Server Cluster
***************************************************************************/
#include <app-common/zap-generated/cluster-objects.h>
#include <app-common/zap-generated/ids/Attributes.h>
#include <app-common/zap-generated/ids/Commands.h>
#include <app/AttributeAccessInterface.h>
#include <app/CommandHandler.h>
#include <app/ConcreteCommandPath.h>
#include <app/util/attribute-storage.h>
#include <lib/core/CHIPSafeCasts.h>
#include <lib/core/CHIPTLV.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/ScopedBuffer.h>
#include <lib/support/logging/CHIPLogging.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::TestCluster;
using namespace chip::app::Clusters::TestCluster::Commands;
using namespace chip::app::Clusters::TestCluster::Attributes;
// The number of elements in the test attribute list
constexpr uint8_t kAttributeListLength = 4;
// The maximum length of the test attribute list element in bytes
constexpr uint8_t kAttributeEntryLength = 6;
namespace {
class OctetStringData
{
public:
uint8_t * Data() { return mDataBuf; }
size_t Length() const { return mDataLen; }
void SetLength(size_t size) { mDataLen = size; }
private:
uint8_t mDataBuf[kAttributeEntryLength];
size_t mDataLen = 0;
};
class TestAttrAccess : public AttributeAccessInterface
{
public:
// Register for the Test Cluster cluster on all endpoints.
TestAttrAccess() : AttributeAccessInterface(Optional<EndpointId>::Missing(), TestCluster::Id) {}
CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override;
CHIP_ERROR Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder) override;
private:
CHIP_ERROR ReadListInt8uAttribute(AttributeValueEncoder & aEncoder);
CHIP_ERROR WriteListInt8uAttribute(AttributeValueDecoder & aDecoder);
CHIP_ERROR ReadListOctetStringAttribute(AttributeValueEncoder & aEncoder);
CHIP_ERROR WriteListOctetStringAttribute(AttributeValueDecoder & aDecoder);
CHIP_ERROR ReadListStructOctetStringAttribute(AttributeValueEncoder & aEncoder);
CHIP_ERROR WriteListStructOctetStringAttribute(AttributeValueDecoder & aDecoder);
CHIP_ERROR ReadListNullablesAndOptionalsStructAttribute(AttributeValueEncoder & aEncoder);
CHIP_ERROR WriteListNullablesAndOptionalsStructAttribute(AttributeValueDecoder & aDecoder);
CHIP_ERROR ReadStructAttribute(AttributeValueEncoder & aEncoder);
CHIP_ERROR WriteStructAttribute(AttributeValueDecoder & aDecoder);
CHIP_ERROR ReadNullableStruct(AttributeValueEncoder & aEncoder);
CHIP_ERROR WriteNullableStruct(AttributeValueDecoder & aDecoder);
};
TestAttrAccess gAttrAccess;
uint8_t gListUint8Data[kAttributeListLength];
OctetStringData gListOctetStringData[kAttributeListLength];
OctetStringData gListOperationalCert[kAttributeListLength];
Structs::TestListStructOctet::Type listStructOctetStringData[kAttributeListLength];
Structs::SimpleStruct::Type gStructAttributeValue = { 0, false, SimpleEnum::kValueA,
ByteSpan(), CharSpan(), BitFlags<SimpleBitmap>(),
0, 0 };
NullableStruct::TypeInfo::Type gNullableStructAttributeValue;
CHIP_ERROR TestAttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
{
switch (aPath.mAttributeId)
{
case ListInt8u::Id: {
return ReadListInt8uAttribute(aEncoder);
}
case ListOctetString::Id: {
return ReadListOctetStringAttribute(aEncoder);
}
case ListStructOctetString::Id: {
return ReadListStructOctetStringAttribute(aEncoder);
}
case ListNullablesAndOptionalsStruct::Id: {
return ReadListNullablesAndOptionalsStructAttribute(aEncoder);
}
case Struct::Id: {
return ReadStructAttribute(aEncoder);
}
case NullableStruct::Id: {
return ReadNullableStruct(aEncoder);
}
default: {
break;
}
}
return CHIP_NO_ERROR;
}
CHIP_ERROR TestAttrAccess::Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder)
{
switch (aPath.mAttributeId)
{
case ListInt8u::Id: {
return WriteListInt8uAttribute(aDecoder);
}
case ListOctetString::Id: {
return WriteListOctetStringAttribute(aDecoder);
}
case ListStructOctetString::Id: {
return WriteListStructOctetStringAttribute(aDecoder);
}
case ListNullablesAndOptionalsStruct::Id: {
return WriteListNullablesAndOptionalsStructAttribute(aDecoder);
}
case Struct::Id: {
return WriteStructAttribute(aDecoder);
}
case NullableStruct::Id: {
return WriteNullableStruct(aDecoder);
}
default: {
break;
}
}
return CHIP_NO_ERROR;
}
CHIP_ERROR TestAttrAccess::ReadNullableStruct(AttributeValueEncoder & aEncoder)
{
return aEncoder.Encode(gNullableStructAttributeValue);
}
CHIP_ERROR TestAttrAccess::WriteNullableStruct(AttributeValueDecoder & aDecoder)
{
return aDecoder.Decode(gNullableStructAttributeValue);
}
CHIP_ERROR TestAttrAccess::ReadListInt8uAttribute(AttributeValueEncoder & aEncoder)
{
return aEncoder.EncodeList([](const TagBoundEncoder & encoder) -> CHIP_ERROR {
for (uint8_t index = 0; index < kAttributeListLength; index++)
{
ReturnErrorOnFailure(encoder.Encode(gListUint8Data[index]));
}
return CHIP_NO_ERROR;
});
}
CHIP_ERROR TestAttrAccess::WriteListInt8uAttribute(AttributeValueDecoder & aDecoder)
{
ListInt8u::TypeInfo::DecodableType list;
ReturnErrorOnFailure(aDecoder.Decode(list));
uint8_t index = 0;
auto iter = list.begin();
while (iter.Next())
{
auto & entry = iter.GetValue();
VerifyOrReturnError(index < kAttributeListLength, CHIP_ERROR_BUFFER_TOO_SMALL);
gListUint8Data[index++] = entry;
}
return iter.GetStatus();
}
CHIP_ERROR TestAttrAccess::ReadListOctetStringAttribute(AttributeValueEncoder & aEncoder)
{
return aEncoder.EncodeList([](const TagBoundEncoder & encoder) -> CHIP_ERROR {
for (uint8_t index = 0; index < kAttributeListLength; index++)
{
ByteSpan span(gListOctetStringData[index].Data(), gListOctetStringData[index].Length());
ReturnErrorOnFailure(encoder.Encode(span));
}
return CHIP_NO_ERROR;
});
}
CHIP_ERROR TestAttrAccess::WriteListOctetStringAttribute(AttributeValueDecoder & aDecoder)
{
ListOctetString::TypeInfo::DecodableType list;
ReturnErrorOnFailure(aDecoder.Decode(list));
uint8_t index = 0;
auto iter = list.begin();
while (iter.Next())
{
const auto & entry = iter.GetValue();
VerifyOrReturnError(index < kAttributeListLength, CHIP_ERROR_BUFFER_TOO_SMALL);
VerifyOrReturnError(entry.size() <= kAttributeEntryLength, CHIP_ERROR_BUFFER_TOO_SMALL);
memcpy(gListOctetStringData[index].Data(), entry.data(), entry.size());
gListOctetStringData[index].SetLength(entry.size());
index++;
}
return iter.GetStatus();
}
CHIP_ERROR TestAttrAccess::ReadListStructOctetStringAttribute(AttributeValueEncoder & aEncoder)
{
return aEncoder.EncodeList([](const TagBoundEncoder & encoder) -> CHIP_ERROR {
for (uint8_t index = 0; index < kAttributeListLength; index++)
{
Structs::TestListStructOctet::Type structOctet;
structOctet.fabricIndex = listStructOctetStringData[index].fabricIndex;
structOctet.operationalCert = listStructOctetStringData[index].operationalCert;
ReturnErrorOnFailure(encoder.Encode(structOctet));
}
return CHIP_NO_ERROR;
});
}
CHIP_ERROR TestAttrAccess::WriteListStructOctetStringAttribute(AttributeValueDecoder & aDecoder)
{
ListStructOctetString::TypeInfo::DecodableType list;
ReturnErrorOnFailure(aDecoder.Decode(list));
uint8_t index = 0;
auto iter = list.begin();
while (iter.Next())
{
const auto & entry = iter.GetValue();
VerifyOrReturnError(index < kAttributeListLength, CHIP_ERROR_BUFFER_TOO_SMALL);
VerifyOrReturnError(entry.operationalCert.size() <= kAttributeEntryLength, CHIP_ERROR_BUFFER_TOO_SMALL);
memcpy(gListOperationalCert[index].Data(), entry.operationalCert.data(), entry.operationalCert.size());
gListOperationalCert[index].SetLength(entry.operationalCert.size());
listStructOctetStringData[index].fabricIndex = entry.fabricIndex;
listStructOctetStringData[index].operationalCert =
ByteSpan(gListOperationalCert[index].Data(), gListOperationalCert[index].Length());
index++;
}
if (iter.GetStatus() != CHIP_NO_ERROR)
{
return CHIP_ERROR_INVALID_DATA_LIST;
}
return CHIP_NO_ERROR;
}
CHIP_ERROR TestAttrAccess::ReadListNullablesAndOptionalsStructAttribute(AttributeValueEncoder & aEncoder)
{
return aEncoder.EncodeList([](const TagBoundEncoder & encoder) -> CHIP_ERROR {
// Just encode a single default-initialized
// entry for now.
Structs::NullablesAndOptionalsStruct::Type entry;
ReturnErrorOnFailure(encoder.Encode(entry));
return CHIP_NO_ERROR;
});
}
CHIP_ERROR TestAttrAccess::WriteListNullablesAndOptionalsStructAttribute(AttributeValueDecoder & aDecoder)
{
// TODO Add yaml test case for NullablesAndOptionalsStruct list
return CHIP_NO_ERROR;
}
CHIP_ERROR TestAttrAccess::ReadStructAttribute(AttributeValueEncoder & aEncoder)
{
return aEncoder.Encode(gStructAttributeValue);
}
CHIP_ERROR TestAttrAccess::WriteStructAttribute(AttributeValueDecoder & aDecoder)
{
return aDecoder.Decode(gStructAttributeValue);
}
} // namespace
bool emberAfTestClusterClusterTestCallback(app::CommandHandler *, const app::ConcreteCommandPath & commandPath,
const Test::DecodableType & commandData)
{
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
return true;
}
bool emberAfTestClusterClusterTestSpecificCallback(CommandHandler * apCommandObj, const ConcreteCommandPath & commandPath,
const TestSpecific::DecodableType & commandData)
{
TestSpecificResponse::Type responseData;
responseData.returnValue = 7;
CHIP_ERROR err = apCommandObj->AddResponseData(commandPath, responseData);
if (CHIP_NO_ERROR != err)
{
ChipLogError(Zcl, "Test Cluster: failed to send TestSpecific response: %" CHIP_ERROR_FORMAT, err.Format());
}
return true;
}
bool emberAfTestClusterClusterTestNotHandledCallback(CommandHandler *, const ConcreteCommandPath & commandPath,
const TestNotHandled::DecodableType & commandData)
{
return false;
}
bool emberAfTestClusterClusterTestAddArgumentsCallback(CommandHandler * apCommandObj, const ConcreteCommandPath & commandPath,
const TestAddArguments::DecodableType & commandData)
{
if (commandData.arg1 > UINT8_MAX - commandData.arg2)
{
return emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
}
TestAddArgumentsResponse::Type responseData;
responseData.returnValue = static_cast<uint8_t>(commandData.arg1 + commandData.arg2);
CHIP_ERROR err = apCommandObj->AddResponseData(commandPath, responseData);
if (CHIP_NO_ERROR != err)
{
ChipLogError(Zcl, "Test Cluster: failed to send TestAddArguments response: %" CHIP_ERROR_FORMAT, err.Format());
}
return true;
}
static bool SendBooleanResponse(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, bool value)
{
Commands::BooleanResponse::Type response;
response.value = value;
CHIP_ERROR err = commandObj->AddResponseData(commandPath, response);
if (err != CHIP_NO_ERROR)
{
commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::Failure);
}
return true;
}
bool emberAfTestClusterClusterTestStructArgumentRequestCallback(
app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::TestStructArgumentRequest::DecodableType & commandData)
{
return SendBooleanResponse(commandObj, commandPath, commandData.arg1.b);
}
bool emberAfTestClusterClusterTestNestedStructArgumentRequestCallback(
app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::TestNestedStructArgumentRequest::DecodableType & commandData)
{
return SendBooleanResponse(commandObj, commandPath, commandData.arg1.c.b);
}
bool emberAfTestClusterClusterTestListStructArgumentRequestCallback(
app::CommandHandler * commandObj, app::ConcreteCommandPath const & commandPath,
Commands::TestListStructArgumentRequest::DecodableType const & commandData)
{
bool shouldReturnTrue = true;
auto structIterator = commandData.arg1.begin();
while (structIterator.Next())
{
auto & structValue = structIterator.GetValue();
shouldReturnTrue = shouldReturnTrue && structValue.b;
}
if (CHIP_NO_ERROR != structIterator.GetStatus())
{
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
return true;
}
return SendBooleanResponse(commandObj, commandPath, shouldReturnTrue);
}
bool emberAfTestClusterClusterTestListInt8UArgumentRequestCallback(
app::CommandHandler * commandObj, app::ConcreteCommandPath const & commandPath,
Commands::TestListInt8UArgumentRequest::DecodableType const & commandData)
{
bool shouldReturnTrue = true;
auto uint8Iterator = commandData.arg1.begin();
while (uint8Iterator.Next())
{
auto & value = uint8Iterator.GetValue();
shouldReturnTrue = shouldReturnTrue && (value != 0);
}
if (CHIP_NO_ERROR != uint8Iterator.GetStatus())
{
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
return true;
}
return SendBooleanResponse(commandObj, commandPath, shouldReturnTrue);
}
bool emberAfTestClusterClusterTestNestedStructListArgumentRequestCallback(
app::CommandHandler * commandObj, app::ConcreteCommandPath const & commandPath,
Commands::TestNestedStructListArgumentRequest::DecodableType const & commandData)
{
bool shouldReturnTrue = commandData.arg1.c.b;
auto structIterator = commandData.arg1.d.begin();
while (structIterator.Next())
{
auto & structValue = structIterator.GetValue();
shouldReturnTrue = shouldReturnTrue && structValue.b;
}
if (CHIP_NO_ERROR != structIterator.GetStatus())
{
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
return true;
}
return SendBooleanResponse(commandObj, commandPath, shouldReturnTrue);
}
bool emberAfTestClusterClusterTestListNestedStructListArgumentRequestCallback(
app::CommandHandler * commandObj, app::ConcreteCommandPath const & commandPath,
Commands::TestListNestedStructListArgumentRequest::DecodableType const & commandData)
{
bool shouldReturnTrue = true;
auto structIterator = commandData.arg1.begin();
while (structIterator.Next())
{
auto & structValue = structIterator.GetValue();
shouldReturnTrue = shouldReturnTrue && structValue.c.b;
auto subStructIterator = structValue.d.begin();
while (subStructIterator.Next())
{
auto & subStructValue = subStructIterator.GetValue();
shouldReturnTrue = shouldReturnTrue && subStructValue.b;
}
if (CHIP_NO_ERROR != subStructIterator.GetStatus())
{
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
return true;
}
}
if (CHIP_NO_ERROR != structIterator.GetStatus())
{
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
return true;
}
return SendBooleanResponse(commandObj, commandPath, shouldReturnTrue);
}
bool emberAfTestClusterClusterTestListInt8UReverseRequestCallback(
CommandHandler * commandObj, ConcreteCommandPath const & commandPath,
Commands::TestListInt8UReverseRequest::DecodableType const & commandData)
{
size_t count = 0;
CHIP_ERROR err = commandData.arg1.ComputeSize(&count);
VerifyOrExit(err == CHIP_NO_ERROR, );
{
auto iter = commandData.arg1.begin();
Commands::TestListInt8UReverseResponse::Type responseData;
size_t cur = count;
Platform::ScopedMemoryBuffer<uint8_t> responseBuf;
VerifyOrExit(responseBuf.Calloc(count), );
while (iter.Next() && cur > 0)
{
responseBuf[cur - 1] = iter.GetValue();
--cur;
}
VerifyOrExit(cur == 0, );
VerifyOrExit(iter.GetStatus() == CHIP_NO_ERROR, );
responseData.arg1 = DataModel::List<uint8_t>(responseBuf.Get(), count);
SuccessOrExit(commandObj->AddResponseData(commandPath, responseData));
return true;
}
exit:
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
return true;
}
bool emberAfTestClusterClusterTestEnumsRequestCallback(CommandHandler * commandObj, ConcreteCommandPath const & commandPath,
TestEnumsRequest::DecodableType const & commandData)
{
TestEnumsResponse::Type response;
response.arg1 = commandData.arg1;
response.arg2 = commandData.arg2;
CHIP_ERROR err = commandObj->AddResponseData(commandPath, response);
if (err != CHIP_NO_ERROR)
{
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
}
return true;
}
bool emberAfTestClusterClusterTestNullableOptionalRequestCallback(
CommandHandler * commandObj, ConcreteCommandPath const & commandPath,
Commands::TestNullableOptionalRequest::DecodableType const & commandData)
{
Commands::TestNullableOptionalResponse::Type response;
response.wasPresent = commandData.arg1.HasValue();
if (response.wasPresent)
{
bool wasNull = commandData.arg1.Value().IsNull();
response.wasNull.SetValue(wasNull);
if (!wasNull)
{
response.value.SetValue(commandData.arg1.Value().Value());
}
response.originalValue.Emplace(commandData.arg1.Value());
}
CHIP_ERROR err = commandObj->AddResponseData(commandPath, response);
if (err != CHIP_NO_ERROR)
{
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
}
return true;
}
bool emberAfTestClusterClusterSimpleStructEchoRequestCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
const Commands::SimpleStructEchoRequest::DecodableType & commandData)
{
Commands::SimpleStructResponse::Type response;
response.arg1.a = commandData.arg1.a;
response.arg1.b = commandData.arg1.b;
response.arg1.c = commandData.arg1.c;
response.arg1.d = commandData.arg1.d;
response.arg1.e = commandData.arg1.e;
response.arg1.f = commandData.arg1.f;
response.arg1.g = commandData.arg1.g;
response.arg1.h = commandData.arg1.h;
CHIP_ERROR err = commandObj->AddResponseData(commandPath, response);
if (err != CHIP_NO_ERROR)
{
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
}
return true;
}
bool emberAfTestClusterClusterTimedInvokeRequestCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
const Commands::TimedInvokeRequest::DecodableType & commandData)
{
commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::Success);
return true;
}
bool emberAfTestClusterClusterTestSimpleOptionalArgumentRequestCallback(
CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
const Commands::TestSimpleOptionalArgumentRequest::DecodableType & commandData)
{
Protocols::InteractionModel::Status status = commandData.arg1.HasValue() ? Protocols::InteractionModel::Status::Success
: Protocols::InteractionModel::Status::InvalidValue;
commandObj->AddStatus(commandPath, status);
return true;
}
// -----------------------------------------------------------------------------
// Plugin initialization
void MatterTestClusterPluginServerInitCallback(void)
{
registerAttributeAccessOverride(&gAttrAccess);
}