Line data Source code
1 : /* 2 : * 3 : * Copyright (c) 2021 Project CHIP Authors 4 : * 5 : * Licensed under the Apache License, Version 2.0 (the "License"); 6 : * you may not use this file except in compliance with the License. 7 : * You may obtain a copy of the License at 8 : * 9 : * http://www.apache.org/licenses/LICENSE-2.0 10 : * 11 : * Unless required by applicable law or agreed to in writing, software 12 : * distributed under the License is distributed on an "AS IS" BASIS, 13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 : * See the License for the specific language governing permissions and 15 : * limitations under the License. 16 : */ 17 : 18 : #pragma once 19 : 20 : #include <lib/core/CHIPConfig.h> 21 : #include <lib/core/TLV.h> 22 : #include <lib/support/BitFlags.h> 23 : #include <lib/support/BitMask.h> 24 : #include <lib/support/TypeTraits.h> 25 : 26 : #include <limits> 27 : #include <type_traits> 28 : 29 : namespace chip { 30 : namespace app { 31 : 32 : template <typename T, 33 : bool IsBigEndian = 34 : // CHIP_CONFIG_BIG_ENDIAN_TARGET to match how the attribute store works, because that's 35 : // what where our data buffer is eventually ending up or coming from. 36 : #if CHIP_CONFIG_BIG_ENDIAN_TARGET 37 : true 38 : #else // CHIP_CONFIG_BIG_ENDIAN_TARGET 39 : false 40 : #endif // CHIP_CONFIG_BIG_ENDIAN_TARGET 41 : > 42 : struct NumericAttributeTraits 43 : { 44 : // StorageType is the type used to represent this C++ type in the attribute 45 : // store. 46 : using StorageType = T; 47 : 48 : // WorkingType is the type used to represent this C++ type when we are 49 : // actually working with it as a value. 50 : using WorkingType = T; 51 : 52 : // Convert a working value to a storage value. This uses an outparam 53 : // instead of a return value because some specializations have complicated 54 : // StorageTypes that can't be returned by value. This function can assume 55 : // that WorkingType passed a CanRepresentValue check. 56 0 : static constexpr void WorkingToStorage(WorkingType workingValue, StorageType & storageValue) { storageValue = workingValue; } 57 : 58 : // Convert a storage value to a working value. Some specializations do more 59 : // interesting things here. 60 : static constexpr WorkingType StorageToWorking(StorageType storageValue) { return storageValue; } 61 : 62 : private: 63 : // We need to make sure we never look like we are assigning NaN to an 64 : // integer, even in a not-reached branch. Without "if constexpr", the best 65 : // we can do is these functions using enable_if. 66 : template <typename U = T, typename std::enable_if_t<std::is_floating_point<U>::value, int> = 0> 67 : static constexpr StorageType GetNullValue() 68 : { 69 : return std::numeric_limits<T>::quiet_NaN(); 70 : } 71 : 72 : template <typename U = T, typename std::enable_if_t<std::is_integral<U>::value, int> = 0> 73 : static constexpr StorageType GetNullValue() 74 : { 75 : return std::is_signed<T>::value ? std::numeric_limits<T>::min() : std::numeric_limits<T>::max(); 76 : } 77 : 78 : template <typename U = T, typename std::enable_if_t<std::is_enum<U>::value, int> = 0> 79 : static constexpr StorageType GetNullValue() 80 : { 81 : static_assert(!std::is_signed<std::underlying_type_t<T>>::value, "Enums must be unsigned"); 82 : return static_cast<StorageType>(std::numeric_limits<std::underlying_type_t<T>>::max()); 83 : } 84 : 85 : public: 86 : // The value reserved in the value space of StorageType to represent null, 87 : // for cases when we have a nullable value. This value must match the value 88 : // excluded from the valid value range in the spec, so that we don't confuse 89 : // valid values with null. 90 : static constexpr StorageType kNullValue = NumericAttributeTraits::GetNullValue(); 91 : 92 : template <typename U = T, typename std::enable_if_t<!std::is_floating_point<U>::value, int> = 0> 93 0 : static constexpr bool IsNullValue(StorageType value) 94 : { 95 0 : return value == kNullValue; 96 : } 97 : 98 : template <typename U = T, typename std::enable_if_t<std::is_floating_point<U>::value, int> = 0> 99 0 : static constexpr bool IsNullValue(StorageType value) 100 : { 101 : // Trying to include math.h (to use isnan()) fails on EFR32, both when 102 : // included as "cmath" and when included as "math.h". For lack of 103 : // isnan(), just fall back on the NaN != NaN thing. 104 0 : return value != value; 105 : } 106 : 107 0 : static constexpr void SetNull(StorageType & value) { value = kNullValue; } 108 : 109 : // Test whether a value can be represented in a "not null" value of the 110 : // given type, which may be a nullable value or not. This needs to be 111 : // implemented for both T and StorageType if the two are distinct. 112 0 : static constexpr bool CanRepresentValue(bool isNullable, T value) 113 : { 114 : // For now, allow the null-marker value for non-nullable types. It's 115 : // not what the spec says to do at the moment, but that might well 116 : // change, and we have quite a number of tests relying on this behavior 117 : // for now that we should only change once the spec really decides what 118 : // it's doing. 119 0 : return !isNullable || !IsNullValue(value); 120 : } 121 : 122 0 : static CHIP_ERROR Encode(TLV::TLVWriter & writer, TLV::Tag tag, StorageType value) 123 : { 124 0 : return writer.Put(tag, static_cast<T>(value)); 125 : } 126 : 127 : // Utility that lets consumers treat a StorageType instance as a uint8_t* 128 : // for writing to the attribute store. 129 : static uint8_t * ToAttributeStoreRepresentation(StorageType & value) { return reinterpret_cast<uint8_t *>(&value); } 130 : }; 131 : 132 : template <typename T> 133 : struct NumericAttributeTraits<BitFlags<T>> 134 : { 135 : using StorageType = T; 136 : using WorkingType = BitFlags<T>; 137 : 138 : static constexpr void WorkingToStorage(WorkingType workingValue, StorageType & storageValue) 139 : { 140 : storageValue = static_cast<StorageType>(workingValue.Raw()); 141 : } 142 : 143 : static constexpr WorkingType StorageToWorking(StorageType storageValue) { return WorkingType(storageValue); } 144 : 145 : static constexpr void SetNull(StorageType & value) 146 : { 147 : // 148 : // When setting to null, store a value that has all bits set. This aliases to the same behavior 149 : // we do for other integral types, ensuring consistency across all underlying integral types in the data store as well as 150 : // re-using logic for serialization/de-serialization of that data in the IM. 151 : // 152 : value = static_cast<StorageType>(std::numeric_limits<std::underlying_type_t<T>>::max()); 153 : } 154 : 155 : static constexpr bool IsNullValue(StorageType value) 156 : { 157 : // 158 : // While we store a nullable bitmap value by setting all its bits, we can be a bit more conservative when actually 159 : // testing for null since the spec only mandates that the MSB be reserved for nullable bitmaps. 160 : // 161 : constexpr auto msbSetValue = std::underlying_type_t<T>(1) << (std::numeric_limits<std::underlying_type_t<T>>::digits - 1); 162 : return !!(std::underlying_type_t<T>(value) & msbSetValue); 163 : } 164 : 165 : static constexpr bool CanRepresentValue(bool isNullable, StorageType value) 166 : { 167 : // 168 : // We permit the full-range of the underlying StorageType if !isNullable, 169 : // and the restricted range otherwise. 170 : // 171 : return !isNullable || !IsNullValue(value); 172 : } 173 : 174 : static uint8_t * ToAttributeStoreRepresentation(StorageType & value) { return reinterpret_cast<uint8_t *>(&value); } 175 : }; 176 : 177 : template <typename T> 178 : struct NumericAttributeTraits<BitMask<T>> : public NumericAttributeTraits<BitFlags<T>> 179 : { 180 : using StorageType = T; 181 : using WorkingType = BitMask<T>; 182 : 183 : static constexpr WorkingType StorageToWorking(StorageType storageValue) { return WorkingType(storageValue); } 184 : }; 185 : 186 : template <> 187 : struct NumericAttributeTraits<bool> 188 : { 189 : using StorageType = uint8_t; 190 : using WorkingType = bool; 191 : 192 0 : static constexpr void WorkingToStorage(WorkingType workingValue, StorageType & storageValue) { storageValue = workingValue; } 193 : 194 : static constexpr WorkingType StorageToWorking(StorageType storageValue) { return storageValue; } 195 : 196 0 : static constexpr bool IsNullValue(StorageType value) { return value == kNullValue; } 197 0 : static constexpr void SetNull(StorageType & value) { value = kNullValue; } 198 0 : static constexpr bool CanRepresentValue(bool isNullable, StorageType value) 199 : { 200 : // This treats all nonzero values (except the null value) as true. 201 0 : return !IsNullValue(value); 202 : } 203 0 : static constexpr bool CanRepresentValue(bool isNullable, bool value) { return true; } 204 : 205 0 : static CHIP_ERROR Encode(TLV::TLVWriter & writer, TLV::Tag tag, StorageType value) 206 : { 207 0 : return writer.Put(tag, static_cast<bool>(value)); 208 : } 209 : 210 : static uint8_t * ToAttributeStoreRepresentation(StorageType & value) { return reinterpret_cast<uint8_t *>(&value); } 211 : 212 : private: 213 : static constexpr StorageType kNullValue = 0xFF; 214 : }; 215 : 216 : } // namespace app 217 : } // namespace chip