/* * * 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 #include #include namespace chip { namespace app { namespace detail { template struct WorkingTypeMapper { }; template struct WorkingTypeMapper { using WorkingType = int64_t; }; template <> struct WorkingTypeMapper<3, true> { using WorkingType = int32_t; }; template struct WorkingTypeMapper { using WorkingType = uint64_t; }; template <> struct WorkingTypeMapper<3, false> { using WorkingType = uint32_t; }; } // namespace detail template 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::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 struct IntegerByteIndexing; template struct IntegerByteIndexing { 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 struct IntegerByteIndexing { 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 struct NumericAttributeTraits, IsBigEndian> : detail::IntegerByteIndexing { using IntType = OddSizedInteger; // 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; 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(1) << (8 * ByteSize - 1)) - 1; if (value > MaxPositive) { value = value - (static_cast(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(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) { return MinValue(isNullable) <= value && value <= MaxValue(isNullable); } 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; } static WorkingType MinValue(bool isNullable) { if constexpr (!IsSigned) { return 0; } // Since WorkingType has at least one extra byte, the bitshift cannot overflow. constexpr WorkingType signedMin = -(static_cast(1) << (8 * ByteSize - 1)); if (isNullable) { // Smallest negative value is excluded for nullable signed types. return signedMin + 1; } return signedMin; } static WorkingType MaxValue(bool isNullable) { // Since WorkingType has at least one extra byte, none of our bitshifts // overflow. if constexpr (IsSigned) { return (static_cast(1) << (8 * ByteSize - 1)) - 1; } constexpr WorkingType unsignedMax = (static_cast(1) << (8 * ByteSize)) - 1; if (isNullable) { // Largest value is excluded for nullable unsigned types. return unsignedMax - 1; } return unsignedMax; } }; } // namespace app } // namespace chip