blob: d4bf81190281ba21564fb1275d1e755006b4c30f [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;
#if !CHIP_CLUSTER_CONFIG_ENABLE_COMPLEX_ATTRIBUTE_READ
constexpr const char * kErrorStr = "Test Cluster: List Octet cluster (0x%02x) Error setting '%s' attribute: 0x%02x";
#endif // CHIP_CLUSTER_CONFIG_ENABLE_COMPLEX_ATTRIBUTE_READ
namespace {
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 ConcreteAttributePath & aPath, AttributeValueEncoder & aEncoder) override;
private:
CHIP_ERROR ReadListInt8uAttribute(AttributeValueEncoder & aEncoder);
CHIP_ERROR ReadListOctetStringAttribute(AttributeValueEncoder & aEncoder);
CHIP_ERROR ReadListStructOctetStringAttribute(AttributeValueEncoder & aEncoder);
CHIP_ERROR ReadListNullablesAndOptionalsStructAttribute(AttributeValueEncoder & aEncoder);
};
TestAttrAccess gAttrAccess;
CHIP_ERROR TestAttrAccess::Read(const ConcreteAttributePath & 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);
}
default: {
break;
}
}
return CHIP_NO_ERROR;
}
#if !CHIP_CLUSTER_CONFIG_ENABLE_COMPLEX_ATTRIBUTE_READ
EmberAfStatus writeAttribute(EndpointId endpoint, AttributeId attributeId, uint8_t * buffer, int32_t index = -1)
{
EmberAfAttributeSearchRecord record;
record.endpoint = endpoint;
record.clusterId = TestCluster::Id;
record.clusterMask = CLUSTER_MASK_SERVER;
record.manufacturerCode = EMBER_AF_NULL_MANUFACTURER_CODE;
record.attributeId = attributeId;
// When reading or writing a List attribute the 'index' value could have 3 types of values:
// -1: Read/Write the whole list content, including the number of elements in the list
// 0: Read/Write the number of elements in the list, represented as a uint16_t
// n: Read/Write the nth element of the list
//
// Since the first 2 bytes of the attribute are used to store the number of elements, elements indexing starts
// at 1. In order to hide this to the rest of the code of this file, the element index is incremented by 1 here.
// This also allows calling writeAttribute() with no index arg to mean "write the length".
return emAfReadOrWriteAttribute(&record, NULL, buffer, 0, true, index + 1);
}
EmberAfStatus writeTestListInt8uAttribute(EndpointId endpoint)
{
EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS;
AttributeId attributeId = ZCL_LIST_ATTRIBUTE_ID;
uint16_t attributeCount = 4;
for (uint8_t index = 0; index < attributeCount; index++)
{
status = writeAttribute(endpoint, attributeId, (uint8_t *) &index, index);
VerifyOrReturnError(status == EMBER_ZCL_STATUS_SUCCESS, status);
}
status = writeAttribute(endpoint, attributeId, (uint8_t *) &attributeCount);
VerifyOrReturnError(status == EMBER_ZCL_STATUS_SUCCESS, status);
return status;
}
#endif // !CHIP_CLUSTER_CONFIG_ENABLE_COMPLEX_ATTRIBUTE_READ
CHIP_ERROR TestAttrAccess::ReadListInt8uAttribute(AttributeValueEncoder & aEncoder)
{
return aEncoder.EncodeList([](const TagBoundEncoder & encoder) -> CHIP_ERROR {
constexpr uint16_t attributeCount = 4;
for (uint8_t index = 0; index < attributeCount; index++)
{
ReturnErrorOnFailure(encoder.Encode(index));
}
return CHIP_NO_ERROR;
});
}
#if !CHIP_CLUSTER_CONFIG_ENABLE_COMPLEX_ATTRIBUTE_READ
EmberAfStatus writeTestListOctetAttribute(EndpointId endpoint)
{
EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS;
AttributeId attributeId = ZCL_LIST_OCTET_STRING_ATTRIBUTE_ID;
uint16_t attributeCount = 4;
char data[6] = { 'T', 'e', 's', 't', 'N', '\0' };
ByteSpan span = ByteSpan(Uint8::from_char(data), strlen(data));
for (uint8_t index = 0; index < attributeCount; index++)
{
sprintf(data + strlen(data) - 1, "%d", index);
status = writeAttribute(endpoint, attributeId, (uint8_t *) &span, index);
VerifyOrReturnError(status == EMBER_ZCL_STATUS_SUCCESS, status);
}
status = writeAttribute(endpoint, attributeId, (uint8_t *) &attributeCount);
VerifyOrReturnError(status == EMBER_ZCL_STATUS_SUCCESS, status);
return status;
}
#endif // !CHIP_CLUSTER_CONFIG_ENABLE_COMPLEX_ATTRIBUTE_READ
CHIP_ERROR TestAttrAccess::ReadListOctetStringAttribute(AttributeValueEncoder & aEncoder)
{
return aEncoder.EncodeList([](const TagBoundEncoder & encoder) -> CHIP_ERROR {
constexpr uint16_t attributeCount = 4;
char data[6] = { 'T', 'e', 's', 't', 'N', '\0' };
for (uint8_t index = 0; index < attributeCount; index++)
{
snprintf(data + strlen(data) - 1, 2, "%d", index);
ByteSpan span(Uint8::from_char(data), strlen(data));
ReturnErrorOnFailure(encoder.Encode(span));
}
return CHIP_NO_ERROR;
});
}
#if !CHIP_CLUSTER_CONFIG_ENABLE_COMPLEX_ATTRIBUTE_READ
EmberAfStatus writeTestListStructOctetAttribute(EndpointId endpoint)
{
EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS;
AttributeId attributeId = ZCL_LIST_STRUCT_OCTET_STRING_ATTRIBUTE_ID;
uint16_t attributeCount = 4;
char data[6] = { 'T', 'e', 's', 't', 'N', '\0' };
ByteSpan span = ByteSpan(Uint8::from_char(data), strlen(data));
for (uint8_t index = 0; index < attributeCount; index++)
{
sprintf(data + strlen(data) - 1, "%d", index);
_TestListStructOctet structOctet;
structOctet.fabricIndex = index;
structOctet.operationalCert = span;
status = writeAttribute(endpoint, attributeId, (uint8_t *) &structOctet, index);
VerifyOrReturnError(status == EMBER_ZCL_STATUS_SUCCESS, status);
}
status = writeAttribute(endpoint, attributeId, (uint8_t *) &attributeCount);
VerifyOrReturnError(status == EMBER_ZCL_STATUS_SUCCESS, status);
return status;
}
#endif // !CHIP_CLUSTER_CONFIG_ENABLE_COMPLEX_ATTRIBUTE_READ
CHIP_ERROR TestAttrAccess::ReadListStructOctetStringAttribute(AttributeValueEncoder & aEncoder)
{
return aEncoder.EncodeList([](const TagBoundEncoder & encoder) -> CHIP_ERROR {
constexpr uint16_t attributeCount = 4;
char data[6] = { 'T', 'e', 's', 't', 'N', '\0' };
for (uint8_t index = 0; index < attributeCount; index++)
{
snprintf(data + strlen(data) - 1, 2, "%d", index);
ByteSpan span(Uint8::from_char(data), strlen(data));
Structs::TestListStructOctet::Type structOctet;
structOctet.fabricIndex = index;
structOctet.operationalCert = span;
ReturnErrorOnFailure(encoder.Encode(structOctet));
}
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;
});
}
} // 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_ARGUMENT);
}
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;
}
bool emberAfTestClusterClusterTestStructArgumentRequestCallback(
app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::TestStructArgumentRequest::DecodableType & commandData)
{
emberAfSendImmediateDefaultResponse(commandData.arg1.b ? EMBER_ZCL_STATUS_SUCCESS : EMBER_ZCL_STATUS_FAILURE);
return true;
}
bool emberAfTestClusterClusterTestNestedStructArgumentRequestCallback(
app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::TestNestedStructArgumentRequest::DecodableType & commandData)
{
emberAfSendImmediateDefaultResponse(commandData.arg1.c.b ? EMBER_ZCL_STATUS_SUCCESS : EMBER_ZCL_STATUS_FAILURE);
return true;
}
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())
{
shouldReturnTrue = false;
}
emberAfSendImmediateDefaultResponse(shouldReturnTrue ? EMBER_ZCL_STATUS_SUCCESS : EMBER_ZCL_STATUS_FAILURE);
return true;
}
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())
{
shouldReturnTrue = false;
}
emberAfSendImmediateDefaultResponse(shouldReturnTrue ? EMBER_ZCL_STATUS_SUCCESS : EMBER_ZCL_STATUS_FAILURE);
return true;
}
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())
{
shouldReturnTrue = false;
}
emberAfSendImmediateDefaultResponse(shouldReturnTrue ? EMBER_ZCL_STATUS_SUCCESS : EMBER_ZCL_STATUS_FAILURE);
return true;
}
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())
{
shouldReturnTrue = false;
break;
}
}
if (CHIP_NO_ERROR != structIterator.GetStatus())
{
shouldReturnTrue = false;
}
emberAfSendImmediateDefaultResponse(shouldReturnTrue ? EMBER_ZCL_STATUS_SUCCESS : EMBER_ZCL_STATUS_FAILURE);
return true;
}
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;
}
// -----------------------------------------------------------------------------
// Plugin initialization
void MatterTestClusterPluginServerInitCallback(void)
{
#if CHIP_CLUSTER_CONFIG_ENABLE_COMPLEX_ATTRIBUTE_READ
registerAttributeAccessOverride(&gAttrAccess);
#else // CHIP_CLUSTER_CONFIG_ENABLE_COMPLEX_ATTRIBUTE_READ
EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS;
for (uint8_t index = 0; index < emberAfEndpointCount(); index++)
{
EndpointId endpoint = emberAfEndpointFromIndex(index);
if (!emberAfContainsCluster(endpoint, TestCluster::Id))
{
continue;
}
status = writeTestListInt8uAttribute(endpoint);
VerifyOrReturn(status == EMBER_ZCL_STATUS_SUCCESS, ChipLogError(Zcl, kErrorStr, endpoint, "test list int8u", status));
status = writeTestListOctetAttribute(endpoint);
VerifyOrReturn(status == EMBER_ZCL_STATUS_SUCCESS, ChipLogError(Zcl, kErrorStr, endpoint, "test list octet", status));
status = writeTestListStructOctetAttribute(endpoint);
VerifyOrReturn(status == EMBER_ZCL_STATUS_SUCCESS,
ChipLogError(Zcl, kErrorStr, endpoint, "test list struct octet", status));
}
#endif // CHIP_CLUSTER_CONFIG_ENABLE_COMPLEX_ATTRIBUTE_READ
}