blob: 1296e7190833fd4ffcb8fd14e8589c8960738ef5 [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-provider/EmberAttributeDataBuffer.h>
#include <app-common/zap-generated/attribute-type.h>
#include <app/AttributeValueEncoder.h>
#include <app/util/attribute-metadata.h>
#include <app/util/attribute-storage-null-handling.h>
#include <app/util/odd-sized-integers.h>
#include <lib/core/CHIPError.h>
#include <lib/core/TLVTags.h>
#include <lib/core/TLVTypes.h>
#include <lib/core/TLVWriter.h>
#include <lib/support/CodeUtils.h>
#include <protocols/interaction_model/Constants.h>
#include <protocols/interaction_model/StatusCode.h>
#include <cstdint>
#include <limits>
namespace chip {
namespace app {
namespace Ember {
namespace {
/// Maximum length of a string, inclusive
///
/// the max size value (0xFF and 0xFFFF) is reserved for NULL representation so
/// it is not available
constexpr uint32_t MaxLength(EmberAttributeDataBuffer::PascalStringType s)
{
if (s == EmberAttributeDataBuffer::PascalStringType::kShort)
{
return std::numeric_limits<uint8_t>::max() - 1;
}
// EmberAttributeDataBuffer::PascalStringType::kLong:
return std::numeric_limits<uint16_t>::max() - 1;
}
constexpr unsigned GetByteCountOfIntegerType(EmberAfAttributeType type)
{
// This TERRIBLE bit fiddling, however it is small in flash
// and we assert statically the actual values we care about
// ZCL_INT8U_ATTRIBUTE_TYPE = 0x20, // Unsigned 8-bit integer
// ZCL_INT16U_ATTRIBUTE_TYPE = 0x21, // Unsigned 16-bit integer
// ZCL_INT24U_ATTRIBUTE_TYPE = 0x22, // Unsigned 24-bit integer
// ZCL_INT32U_ATTRIBUTE_TYPE = 0x23, // Unsigned 32-bit integer
// ZCL_INT40U_ATTRIBUTE_TYPE = 0x24, // Unsigned 40-bit integer
// ZCL_INT48U_ATTRIBUTE_TYPE = 0x25, // Unsigned 48-bit integer
// ZCL_INT56U_ATTRIBUTE_TYPE = 0x26, // Unsigned 56-bit integer
// ZCL_INT64U_ATTRIBUTE_TYPE = 0x27, // Unsigned 64-bit integer
//
// ZCL_INT8S_ATTRIBUTE_TYPE = 0x28, // Signed 8-bit integer
// ZCL_INT16S_ATTRIBUTE_TYPE = 0x29, // Signed 16-bit integer
// ZCL_INT24S_ATTRIBUTE_TYPE = 0x2A, // Signed 24-bit integer
// ZCL_INT32S_ATTRIBUTE_TYPE = 0x2B, // Signed 32-bit integer
// ZCL_INT40S_ATTRIBUTE_TYPE = 0x2C, // Signed 40-bit integer
// ZCL_INT48S_ATTRIBUTE_TYPE = 0x2D, // Signed 48-bit integer
// ZCL_INT56S_ATTRIBUTE_TYPE = 0x2E, // Signed 56-bit integer
// ZCL_INT64S_ATTRIBUTE_TYPE = 0x2F, // Signed 64-bit integer
return (static_cast<unsigned>(type) % 8) + 1;
}
static_assert(GetByteCountOfIntegerType(ZCL_INT8U_ATTRIBUTE_TYPE) == 1);
static_assert(GetByteCountOfIntegerType(ZCL_INT8S_ATTRIBUTE_TYPE) == 1);
static_assert(GetByteCountOfIntegerType(ZCL_INT16U_ATTRIBUTE_TYPE) == 2);
static_assert(GetByteCountOfIntegerType(ZCL_INT16S_ATTRIBUTE_TYPE) == 2);
static_assert(GetByteCountOfIntegerType(ZCL_INT24U_ATTRIBUTE_TYPE) == 3);
static_assert(GetByteCountOfIntegerType(ZCL_INT24S_ATTRIBUTE_TYPE) == 3);
static_assert(GetByteCountOfIntegerType(ZCL_INT32U_ATTRIBUTE_TYPE) == 4);
static_assert(GetByteCountOfIntegerType(ZCL_INT32S_ATTRIBUTE_TYPE) == 4);
static_assert(GetByteCountOfIntegerType(ZCL_INT40U_ATTRIBUTE_TYPE) == 5);
static_assert(GetByteCountOfIntegerType(ZCL_INT40S_ATTRIBUTE_TYPE) == 5);
static_assert(GetByteCountOfIntegerType(ZCL_INT48U_ATTRIBUTE_TYPE) == 6);
static_assert(GetByteCountOfIntegerType(ZCL_INT48S_ATTRIBUTE_TYPE) == 6);
static_assert(GetByteCountOfIntegerType(ZCL_INT56U_ATTRIBUTE_TYPE) == 7);
static_assert(GetByteCountOfIntegerType(ZCL_INT56S_ATTRIBUTE_TYPE) == 7);
static_assert(GetByteCountOfIntegerType(ZCL_INT64U_ATTRIBUTE_TYPE) == 8);
static_assert(GetByteCountOfIntegerType(ZCL_INT64S_ATTRIBUTE_TYPE) == 8);
/// Encodes the string of type stringType pointed to by `reader` into the TLV `writer`.
/// Then encoded string will be at tag `tag` and of type `tlvType`
CHIP_ERROR EncodeString(EmberAttributeDataBuffer::PascalStringType stringType, TLV::TLVType tlvType, TLV::TLVWriter & writer,
TLV::Tag tag, EmberAttributeDataBuffer::EndianReader & reader, bool nullable)
{
unsigned stringLen;
if (stringType == EmberAttributeDataBuffer::PascalStringType::kShort)
{
uint8_t len;
if (!reader.Read8(&len).IsSuccess())
{
return reader.StatusCode();
}
if (len == NumericAttributeTraits<uint8_t>::kNullValue)
{
VerifyOrReturnError(nullable, CHIP_ERROR_INVALID_ARGUMENT);
return writer.PutNull(tag);
}
stringLen = len;
}
else
{
uint16_t len;
if (!reader.Read16(&len).IsSuccess())
{
return reader.StatusCode();
}
if (len == NumericAttributeTraits<uint16_t>::kNullValue)
{
VerifyOrReturnError(nullable, CHIP_ERROR_INVALID_ARGUMENT);
return writer.PutNull(tag);
}
stringLen = len;
}
const uint8_t * data;
if (!reader.ZeroCopyProcessBytes(stringLen, &data).IsSuccess())
{
return reader.StatusCode();
}
if (tlvType == TLV::kTLVType_UTF8String)
{
return writer.PutString(tag, reinterpret_cast<const char *>(data), stringLen);
}
return writer.PutBytes(tag, data, stringLen);
}
} // namespace
CHIP_ERROR EmberAttributeDataBuffer::DecodeUnsignedInteger(chip::TLV::TLVReader & reader, EndianWriter & writer)
{
const unsigned byteCount = GetByteCountOfIntegerType(mAttributeType);
const uint64_t maxValue = NumericLimits::MaxUnsignedValue(byteCount);
// Any size of integer can be read by TLV getting 64-bit integers
uint64_t value;
if (reader.GetType() == TLV::kTLVType_Null)
{
// we know mIsNullable due to the check at the top of ::Decode
value = NumericLimits::UnsignedMaxValueToNullValue(maxValue);
}
else
{
ReturnErrorOnFailure(reader.Get(value));
bool valid =
// Value is in [0, max] RANGE
(value <= maxValue)
// Nullable values reserve a specific value to mean NULL
&& !(mIsNullable && (value == NumericLimits::UnsignedMaxValueToNullValue(maxValue)));
VerifyOrReturnError(valid, CHIP_IM_GLOBAL_STATUS(ConstraintError));
}
writer.EndianPut(value, byteCount);
return CHIP_NO_ERROR;
}
CHIP_ERROR EmberAttributeDataBuffer::DecodeSignedInteger(chip::TLV::TLVReader & reader, EndianWriter & writer)
{
const unsigned byteCount = GetByteCountOfIntegerType(mAttributeType);
const int64_t minValue = NumericLimits::MinSignedValue(byteCount);
const int64_t maxValue = NumericLimits::MaxSignedValue(byteCount);
// Any size of integer can be read by TLV getting 64-bit integers
int64_t value;
if (reader.GetType() == TLV::kTLVType_Null)
{
// we know mIsNullable due to the check at the top of ::Decode
value = NumericLimits::SignedMinValueToNullValue(minValue);
}
else
{
ReturnErrorOnFailure(reader.Get(value));
bool valid =
// Value is in [min, max] RANGE
((value >= minValue) && (value <= maxValue))
// Nullable values reserve a specific value to mean NULL
&& !(mIsNullable && (value == NumericLimits::SignedMinValueToNullValue(minValue)));
VerifyOrReturnError(valid, CHIP_IM_GLOBAL_STATUS(ConstraintError));
}
writer.EndianPutSigned(value, byteCount);
return CHIP_NO_ERROR;
}
CHIP_ERROR EmberAttributeDataBuffer::DecodeAsString(chip::TLV::TLVReader & reader, PascalStringType stringType,
TLV::TLVType tlvType, EndianWriter & writer)
{
// Handle null first, then the actual data
if (reader.GetType() == TLV::kTLVType_Null)
{
// we know mIsNullable due to the check at the top of ::Decode
switch (stringType)
{
case PascalStringType::kShort:
writer.Put8(NumericAttributeTraits<uint8_t>::kNullValue);
break;
case PascalStringType::kLong:
writer.Put16(NumericAttributeTraits<uint16_t>::kNullValue);
break;
}
return CHIP_NO_ERROR;
}
const uint32_t stringLength = reader.GetLength();
VerifyOrReturnError(reader.GetType() == tlvType, CHIP_ERROR_WRONG_TLV_TYPE);
VerifyOrReturnError(stringLength <= MaxLength(stringType), CHIP_ERROR_INVALID_ARGUMENT);
// Size is a prefix, where 0xFF/0xFFFF is the null marker (if applicable)
switch (stringType)
{
case PascalStringType::kShort:
writer.Put8(static_cast<uint8_t>(stringLength));
break;
case PascalStringType::kLong:
writer.Put16(static_cast<uint16_t>(stringLength));
break;
}
// data copy
const uint8_t * tlvData;
ReturnErrorOnFailure(reader.GetDataPtr(tlvData));
writer.Put(tlvData, stringLength);
return CHIP_NO_ERROR;
}
CHIP_ERROR EmberAttributeDataBuffer::Decode(chip::TLV::TLVReader & reader)
{
// all methods below assume that nullable setting matches (this is to reduce code size
// even though clarity suffers)
VerifyOrReturnError(mIsNullable || reader.GetType() != TLV::kTLVType_Null, CHIP_ERROR_WRONG_TLV_TYPE);
EndianWriter endianWriter(mDataBuffer.data(), mDataBuffer.size());
switch (mAttributeType)
{
case ZCL_BOOLEAN_ATTRIBUTE_TYPE: // Boolean
// Boolean values:
// 0x00 is FALSE
// 0x01 is TRUE
// 0xFF is NULL
if (reader.GetType() == TLV::kTLVType_Null)
{
// we know mIsNullable due to the check at the top of ::Decode
endianWriter.Put8(NumericAttributeTraits<bool>::kNullValue);
}
else
{
bool value;
ReturnErrorOnFailure(reader.Get(value));
endianWriter.Put8(value ? 1 : 0);
}
break;
case ZCL_INT8U_ATTRIBUTE_TYPE: // Unsigned 8-bit integer
case ZCL_INT16U_ATTRIBUTE_TYPE: // Unsigned 16-bit integer
case ZCL_INT24U_ATTRIBUTE_TYPE: // Unsigned 24-bit integer
case ZCL_INT32U_ATTRIBUTE_TYPE: // Unsigned 32-bit integer
case ZCL_INT40U_ATTRIBUTE_TYPE: // Unsigned 40-bit integer
case ZCL_INT48U_ATTRIBUTE_TYPE: // Unsigned 48-bit integer
case ZCL_INT56U_ATTRIBUTE_TYPE: // Unsigned 56-bit integer
case ZCL_INT64U_ATTRIBUTE_TYPE: // Unsigned 64-bit integer
ReturnErrorOnFailure(DecodeUnsignedInteger(reader, endianWriter));
break;
case ZCL_INT8S_ATTRIBUTE_TYPE: // Signed 8-bit integer
case ZCL_INT16S_ATTRIBUTE_TYPE: // Signed 16-bit integer
case ZCL_INT24S_ATTRIBUTE_TYPE: // Signed 24-bit integer
case ZCL_INT32S_ATTRIBUTE_TYPE: // Signed 32-bit integer
case ZCL_INT40S_ATTRIBUTE_TYPE: // Signed 40-bit integer
case ZCL_INT48S_ATTRIBUTE_TYPE: // Signed 48-bit integer
case ZCL_INT56S_ATTRIBUTE_TYPE: // Signed 56-bit integer
case ZCL_INT64S_ATTRIBUTE_TYPE: // Signed 64-bit integer
ReturnErrorOnFailure(DecodeSignedInteger(reader, endianWriter));
break;
case ZCL_SINGLE_ATTRIBUTE_TYPE: { // 32-bit float
float value;
if (reader.GetType() == TLV::kTLVType_Null)
{
// we know mIsNullable due to the check at the top of ::Decode
NumericAttributeTraits<float>::SetNull(value);
}
else
{
ReturnErrorOnFailure(reader.Get(value));
}
endianWriter.Put(&value, sizeof(value));
break;
}
case ZCL_DOUBLE_ATTRIBUTE_TYPE: { // 64-bit float
double value;
if (reader.GetType() == TLV::kTLVType_Null)
{
// we know mIsNullable due to the check at the top of ::Decode
NumericAttributeTraits<double>::SetNull(value);
}
else
{
ReturnErrorOnFailure(reader.Get(value));
}
endianWriter.Put(&value, sizeof(value));
break;
}
case ZCL_CHAR_STRING_ATTRIBUTE_TYPE: // Char string
ReturnErrorOnFailure(DecodeAsString(reader, PascalStringType::kShort, TLV::kTLVType_UTF8String, endianWriter));
break;
case ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE:
ReturnErrorOnFailure(DecodeAsString(reader, PascalStringType::kLong, TLV::kTLVType_UTF8String, endianWriter));
break;
case ZCL_OCTET_STRING_ATTRIBUTE_TYPE: // Octet string
ReturnErrorOnFailure(DecodeAsString(reader, PascalStringType::kShort, TLV::kTLVType_ByteString, endianWriter));
break;
case ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE:
ReturnErrorOnFailure(DecodeAsString(reader, PascalStringType::kLong, TLV::kTLVType_ByteString, endianWriter));
break;
default:
ChipLogError(DataManagement, "Attribute type 0x%x not handled", mAttributeType);
return CHIP_IM_GLOBAL_STATUS(Failure);
}
size_t written;
if (!endianWriter.Fit(written))
{
return CHIP_ERROR_NO_MEMORY;
}
mDataBuffer.reduce_size(written);
return CHIP_NO_ERROR;
}
CHIP_ERROR EmberAttributeDataBuffer::EncodeInteger(chip::TLV::TLVWriter & writer, TLV::Tag tag, EndianReader & reader) const
{
// Encodes an integer by first reading as raw bytes and then
// bitshift-convert
//
// This optimizes code size rather than readability at this point.
uint8_t raw_bytes[8];
const bool isSigned = IsSignedAttributeType(mAttributeType);
const unsigned byteCount = GetByteCountOfIntegerType(mAttributeType);
const uint64_t nullValueAsU64 = isSigned
? static_cast<uint64_t>(NumericLimits::SignedMinValueToNullValue(NumericLimits::MinSignedValue(byteCount)))
: NumericLimits::UnsignedMaxValueToNullValue(NumericLimits::MaxUnsignedValue(byteCount));
VerifyOrDie(sizeof(raw_bytes) >= byteCount);
if (!reader.ReadBytes(raw_bytes, byteCount).IsSuccess())
{
return reader.StatusCode();
}
// At this point, RAW_VALUE contains the actual value, need to make it "real"
union
{
int64_t int_value;
uint64_t uint_value;
} value;
value.uint_value = 0;
#if CHIP_CONFIG_BIG_ENDIAN_TARGET
bool isNegative = isSigned && (raw_bytes[0] >= 0x80);
if (isNegative)
{
value.int_value = -1;
}
for (int i = 0; i < static_cast<int>(byteCount); i++)
{
#else
bool isNegative = isSigned && (raw_bytes[byteCount - 1] >= 0x80);
if (isNegative)
{
value.int_value = -1;
}
for (int i = static_cast<int>(byteCount) - 1; i >= 0; i--)
{
#endif
value.uint_value <<= 8;
value.uint_value = (value.uint_value & ~0xFFULL) | raw_bytes[i];
}
// We place the null value as either int_value or uint_value into a union that is
// bit-formatted as both int64 and uint64. When we define the nullValue,
// it is bitcast into u64 hence this comparison. This is ugly, however this
// code prioritizes code size over readability here.
if (mIsNullable && (value.uint_value == nullValueAsU64))
{
return writer.PutNull(tag);
}
if (isSigned)
{
return writer.Put(tag, value.int_value);
}
return writer.Put(tag, value.uint_value);
}
CHIP_ERROR EmberAttributeDataBuffer::Encode(chip::TLV::TLVWriter & writer, TLV::Tag tag) const
{
EndianReader endianReader(mDataBuffer.data(), mDataBuffer.size());
switch (mAttributeType)
{
case ZCL_NO_DATA_ATTRIBUTE_TYPE: // No data
return writer.PutNull(tag);
case ZCL_BOOLEAN_ATTRIBUTE_TYPE: { // Boolean
uint8_t value;
if (!endianReader.Read8(&value).IsSuccess())
{
return endianReader.StatusCode();
}
switch (value)
{
case 0:
case 1:
return writer.PutBoolean(tag, value != 0);
case 0xFF:
VerifyOrReturnError(mIsNullable, CHIP_ERROR_INVALID_ARGUMENT);
return writer.PutNull(tag);
default:
// Unknown types
return CHIP_ERROR_INVALID_ARGUMENT;
}
}
case ZCL_INT8U_ATTRIBUTE_TYPE: // Unsigned 8-bit integer
case ZCL_INT16U_ATTRIBUTE_TYPE: // Unsigned 16-bit integer
case ZCL_INT24U_ATTRIBUTE_TYPE: // Unsigned 24-bit integer
case ZCL_INT32U_ATTRIBUTE_TYPE: // Unsigned 32-bit integer
case ZCL_INT40U_ATTRIBUTE_TYPE: // Unsigned 40-bit integer
case ZCL_INT48U_ATTRIBUTE_TYPE: // Unsigned 48-bit integer
case ZCL_INT56U_ATTRIBUTE_TYPE: // Unsigned 56-bit integer
case ZCL_INT64U_ATTRIBUTE_TYPE: // Unsigned 64-bit integer
case ZCL_INT8S_ATTRIBUTE_TYPE: // Signed 8-bit integer
case ZCL_INT16S_ATTRIBUTE_TYPE: // Signed 16-bit integer
case ZCL_INT24S_ATTRIBUTE_TYPE: // Signed 24-bit integer
case ZCL_INT32S_ATTRIBUTE_TYPE: // Signed 32-bit integer
case ZCL_INT40S_ATTRIBUTE_TYPE: // Signed 40-bit integer
case ZCL_INT48S_ATTRIBUTE_TYPE: // Signed 48-bit integer
case ZCL_INT56S_ATTRIBUTE_TYPE: // Signed 56-bit integer
case ZCL_INT64S_ATTRIBUTE_TYPE: // Signed 64-bit integer
return EncodeInteger(writer, tag, endianReader);
case ZCL_SINGLE_ATTRIBUTE_TYPE: { // 32-bit float
union
{
uint8_t raw[sizeof(float)];
float value;
} value;
if (!endianReader.ReadBytes(value.raw, sizeof(value)).IsSuccess())
{
return endianReader.StatusCode();
}
if (mIsNullable && NumericAttributeTraits<float>::IsNullValue(value.value))
{
return writer.PutNull(tag);
}
return writer.Put(tag, value.value);
}
case ZCL_DOUBLE_ATTRIBUTE_TYPE: { // 64-bit float
union
{
uint8_t raw[sizeof(double)];
double value;
} value;
if (!endianReader.ReadBytes(value.raw, sizeof(value)).IsSuccess())
{
return endianReader.StatusCode();
}
if (mIsNullable && NumericAttributeTraits<double>::IsNullValue(value.value))
{
return writer.PutNull(tag);
}
return writer.Put(tag, value.value);
}
case ZCL_CHAR_STRING_ATTRIBUTE_TYPE: // Char string
return EncodeString(PascalStringType::kShort, TLV::kTLVType_UTF8String, writer, tag, endianReader, mIsNullable);
case ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE:
return EncodeString(PascalStringType::kLong, TLV::kTLVType_UTF8String, writer, tag, endianReader, mIsNullable);
case ZCL_OCTET_STRING_ATTRIBUTE_TYPE: // Octet string
return EncodeString(PascalStringType::kShort, TLV::kTLVType_ByteString, writer, tag, endianReader, mIsNullable);
case ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE:
return EncodeString(PascalStringType::kLong, TLV::kTLVType_ByteString, writer, tag, endianReader, mIsNullable);
default:
ChipLogError(DataManagement, "Attribute type 0x%x not handled", static_cast<int>(mAttributeType));
return CHIP_IM_GLOBAL_STATUS(Failure);
}
}
} // namespace Ember
} // namespace app
} // namespace chip