blob: f5f1bb50c6b4bfcf83a4fcdc4df34af0997140b8 [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.
*/
#pragma once
#include <app/util/attribute-storage-null-handling.h>
#include <lib/support/TypeTraits.h>
#include <limits>
namespace chip {
namespace app {
namespace detail {
template <int ByteSize, bool IsSigned>
struct WorkingTypeMapper
{
};
template <int ByteSize>
struct WorkingTypeMapper<ByteSize, true>
{
using WorkingType = int64_t;
};
template <>
struct WorkingTypeMapper<3, true>
{
using WorkingType = int32_t;
};
template <int ByteSize>
struct WorkingTypeMapper<ByteSize, false>
{
using WorkingType = uint64_t;
};
template <>
struct WorkingTypeMapper<3, false>
{
using WorkingType = uint32_t;
};
} // namespace detail
template <int ByteSize, bool IsSigned>
struct OddSizedInteger
{
// WorkingType is the type we use to represent the value as an actual
// integer that we can do arithmetic, greater/less-than compares, etc on.
using WorkingType = typename detail::WorkingTypeMapper<ByteSize, IsSigned>::WorkingType;
// StorageType is the type "at rest" in the attribute store. It's a
// native-endian byte buffer.
using StorageType = uint8_t[ByteSize];
};
namespace detail {
template <int ByteSize, bool IsBigEndian>
struct IntegerByteIndexing;
template <int ByteSize>
struct IntegerByteIndexing<ByteSize, true>
{
static constexpr int highIndex = 0;
static constexpr int lowIndex = ByteSize - 1;
static constexpr int lowerIndex = 1;
static constexpr int raiseIndex = -1;
static constexpr int pastLowIndex = ByteSize;
static constexpr int pastHighIndex = -1;
};
template <int ByteSize>
struct IntegerByteIndexing<ByteSize, false>
{
static constexpr int highIndex = ByteSize - 1;
static constexpr int lowIndex = 0;
static constexpr int lowerIndex = -1;
static constexpr int raiseIndex = 1;
static constexpr int pastLowIndex = -1;
static constexpr int pastHighIndex = ByteSize;
};
} // namespace detail
template <int ByteSize, bool IsSigned, bool IsBigEndian>
struct NumericAttributeTraits<OddSizedInteger<ByteSize, IsSigned>, IsBigEndian> : detail::IntegerByteIndexing<ByteSize, IsBigEndian>
{
using IntType = OddSizedInteger<ByteSize, IsSigned>;
// StorageType is the type "at rest" in the attribute store. It's a
// native-endian byte buffer.
using StorageType = typename IntType::StorageType;
// WorkingType is the type we use to represent the value as an actual
// integer that we can do arithmetic, greater/less-than compares, etc on.
using WorkingType = typename IntType::WorkingType;
using Indexing = detail::IntegerByteIndexing<ByteSize, IsBigEndian>;
using Indexing::highIndex;
using Indexing::lowerIndex;
using Indexing::lowIndex;
using Indexing::pastHighIndex;
using Indexing::pastLowIndex;
using Indexing::raiseIndex;
static constexpr WorkingType StorageToWorking(StorageType storageValue)
{
// WorkingType can always fit all of our bit-shifting, because it has at
// least one extra byte.
WorkingType value = 0;
for (int i = highIndex; i != pastLowIndex; i += lowerIndex)
{
value = (value << 8) | storageValue[i];
}
// If unsigned, we are done. If signed, we need to make sure our high
// bit gets treated as a sign bit, not a value bit, with our bits in 2s
// complement.
if (IsSigned)
{
constexpr WorkingType MaxPositive = (static_cast<WorkingType>(1) << (8 * ByteSize - 1)) - 1;
if (value > MaxPositive)
{
value = value - (static_cast<WorkingType>(1) << (8 * ByteSize));
}
}
return value;
}
static constexpr void WorkingToStorage(WorkingType workingValue, StorageType & storageValue)
{
// We can just grab the low ByteSize bytes of workingValue.
for (int i = lowIndex; i != pastHighIndex; i += raiseIndex)
{
// Casting to uint8_t exactly grabs the lowest byte.
storageValue[i] = static_cast<uint8_t>(workingValue);
workingValue = workingValue >> 8;
}
}
static constexpr bool IsNullValue(StorageType value)
{
if (IsSigned)
{
// Check for the equivalent of the most negative integer, in 2s
// complement notation.
if (value[highIndex] != 0x80)
{
return false;
}
for (int i = highIndex + lowerIndex; i != pastLowIndex; i += lowerIndex)
{
if (value[i] != 0x00)
{
return false;
}
}
return true;
}
// Check for the equivalent of the largest unsigned integer.
for (int i = highIndex; i != pastLowIndex; i += lowerIndex)
{
if (value[i] != 0xFF)
{
return false;
}
}
return true;
}
static constexpr void SetNull(StorageType & value)
{
if (IsSigned)
{
value[highIndex] = 0x80;
for (int i = highIndex + lowerIndex; i != pastLowIndex; i += lowerIndex)
{
value[i] = 0x00;
}
}
else
{
for (int i = highIndex; i != pastLowIndex; i += lowerIndex)
{
value[i] = 0xFF;
}
}
}
static constexpr bool CanRepresentValue(bool isNullable, StorageType value) { return !isNullable || !IsNullValue(value); }
static constexpr bool CanRepresentValue(bool isNullable, WorkingType value)
{
// Since WorkingType has at least one extra byte, none of our bitshifts
// overflow.
if (IsSigned)
{
WorkingType max = (static_cast<WorkingType>(1) << (8 * ByteSize - 1)) - 1;
WorkingType min = -max;
if (!isNullable)
{
// We have one more value.
min -= 1;
}
return value >= min && value <= max;
}
WorkingType max = (static_cast<WorkingType>(1) << (8 * ByteSize)) - 1;
if (isNullable)
{
// we have one less value
max -= 1;
}
return value <= max;
}
static CHIP_ERROR Encode(TLV::TLVWriter & writer, TLV::Tag tag, StorageType value)
{
return writer.Put(tag, StorageToWorking(value));
}
static uint8_t * ToAttributeStoreRepresentation(StorageType & value) { return value; }
};
} // namespace app
} // namespace chip