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 : 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 : // Ensure that this generic NumericAttributeTraits implementation is being used for some type for which it
64 : // actually works.
65 : static_assert(std::is_floating_point_v<T> || std::is_integral_v<T> || std::is_enum_v<T>,
66 : "NumericAttributeTraits specialization needed for this type");
67 :
68 : // We need to make sure we never look like we are assigning NaN to an
69 : // integer, even in a not-reached branch. Without "if constexpr", the best
70 : // we can do is these functions using enable_if.
71 : template <typename U = T, typename std::enable_if_t<std::is_floating_point_v<U>, int> = 0>
72 : static constexpr StorageType GetNullValue()
73 : {
74 : return std::numeric_limits<T>::quiet_NaN();
75 : }
76 :
77 : template <typename U = T, typename std::enable_if_t<std::is_integral_v<U>, int> = 0>
78 : static constexpr StorageType GetNullValue()
79 : {
80 : return std::is_signed<T>::value ? std::numeric_limits<T>::min() : std::numeric_limits<T>::max();
81 : }
82 :
83 : template <typename U = T, typename std::enable_if_t<std::is_enum_v<U>, int> = 0>
84 : static constexpr StorageType GetNullValue()
85 : {
86 : static_assert(!std::is_signed<std::underlying_type_t<T>>::value, "Enums must be unsigned");
87 : return static_cast<StorageType>(std::numeric_limits<std::underlying_type_t<T>>::max());
88 : }
89 :
90 : public:
91 : // The value reserved in the value space of StorageType to represent null,
92 : // for cases when we have a nullable value. This value must match the value
93 : // excluded from the valid value range in the spec, so that we don't confuse
94 : // valid values with null.
95 : static constexpr StorageType kNullValue = NumericAttributeTraits::GetNullValue();
96 :
97 : template <typename U = T, typename std::enable_if_t<!std::is_floating_point<U>::value, int> = 0>
98 0 : static constexpr bool IsNullValue(StorageType value)
99 : {
100 0 : return value == kNullValue;
101 : }
102 :
103 : template <typename U = T, typename std::enable_if_t<std::is_floating_point<U>::value, int> = 0>
104 9 : static constexpr bool IsNullValue(StorageType value)
105 : {
106 : // Trying to include math.h (to use isnan()) fails on EFR32, both when
107 : // included as "cmath" and when included as "math.h". For lack of
108 : // isnan(), just fall back on the NaN != NaN thing.
109 9 : return value != value;
110 : }
111 :
112 8 : static constexpr void SetNull(StorageType & value) { value = kNullValue; }
113 :
114 : // Test whether a value can be represented in a "not null" value of the
115 : // given type, which may be a nullable value or not. This needs to be
116 : // implemented for both T and StorageType if the two are distinct.
117 : static constexpr bool CanRepresentValue(bool isNullable, T value)
118 : {
119 : // For now, allow the null-marker value for non-nullable types. It's
120 : // not what the spec says to do at the moment, but that might well
121 : // change, and we have quite a number of tests relying on this behavior
122 : // for now that we should only change once the spec really decides what
123 : // it's doing.
124 : return !isNullable || !IsNullValue(value);
125 : }
126 :
127 : static CHIP_ERROR Encode(TLV::TLVWriter & writer, TLV::Tag tag, StorageType value)
128 : {
129 : return writer.Put(tag, static_cast<T>(value));
130 : }
131 :
132 : // Utility that lets consumers treat a StorageType instance as a uint8_t*
133 : // for writing to the attribute store.
134 : static uint8_t * ToAttributeStoreRepresentation(StorageType & value) { return reinterpret_cast<uint8_t *>(&value); }
135 :
136 : // Min and max values for the type.
137 : static WorkingType MinValue(bool isNullable)
138 : {
139 : if constexpr (!std::is_signed_v<WorkingType>)
140 : {
141 : return 0;
142 : }
143 :
144 : if (isNullable)
145 : {
146 : // Smallest negative value is excluded for nullable signed types.
147 : return static_cast<WorkingType>(std::numeric_limits<WorkingType>::min() + 1);
148 : }
149 :
150 : return std::numeric_limits<WorkingType>::min();
151 : }
152 :
153 : static WorkingType MaxValue(bool isNullable)
154 : {
155 : if constexpr (std::is_signed_v<WorkingType>)
156 : {
157 : return std::numeric_limits<WorkingType>::max();
158 : }
159 :
160 : if (isNullable)
161 : {
162 : // Largest value is excluded for nullable unsigned types.
163 : return static_cast<WorkingType>(std::numeric_limits<WorkingType>::max() - 1);
164 : }
165 :
166 : return std::numeric_limits<WorkingType>::max();
167 : }
168 : };
169 :
170 : template <typename T>
171 : struct NumericAttributeTraits<BitFlags<T>>
172 : {
173 : using StorageType = T;
174 : using WorkingType = BitFlags<T>;
175 :
176 : static constexpr void WorkingToStorage(WorkingType workingValue, StorageType & storageValue)
177 : {
178 : storageValue = static_cast<StorageType>(workingValue.Raw());
179 : }
180 :
181 : static constexpr WorkingType StorageToWorking(StorageType storageValue) { return WorkingType(storageValue); }
182 :
183 : static constexpr void SetNull(StorageType & value)
184 : {
185 : //
186 : // When setting to null, store a value that has all bits set. This aliases to the same behavior
187 : // we do for other integral types, ensuring consistency across all underlying integral types in the data store as well as
188 : // re-using logic for serialization/de-serialization of that data in the IM.
189 : //
190 : value = static_cast<StorageType>(std::numeric_limits<std::underlying_type_t<T>>::max());
191 : }
192 :
193 : static constexpr bool IsNullValue(StorageType value)
194 : {
195 : //
196 : // While we store a nullable bitmap value by setting all its bits, we can be a bit more conservative when actually
197 : // testing for null since the spec only mandates that the MSB be reserved for nullable bitmaps.
198 : //
199 : constexpr auto msbSetValue = std::underlying_type_t<T>(1) << (std::numeric_limits<std::underlying_type_t<T>>::digits - 1);
200 : return !!(std::underlying_type_t<T>(value) & msbSetValue);
201 : }
202 :
203 : static constexpr bool CanRepresentValue(bool isNullable, StorageType value)
204 : {
205 : //
206 : // We permit the full-range of the underlying StorageType if !isNullable,
207 : // and the restricted range otherwise.
208 : //
209 : return !isNullable || !IsNullValue(value);
210 : }
211 :
212 : static uint8_t * ToAttributeStoreRepresentation(StorageType & value) { return reinterpret_cast<uint8_t *>(&value); }
213 : };
214 :
215 : template <typename T>
216 : struct NumericAttributeTraits<BitMask<T>> : public NumericAttributeTraits<BitFlags<T>>
217 : {
218 : using StorageType = T;
219 : using WorkingType = BitMask<T>;
220 :
221 : static constexpr WorkingType StorageToWorking(StorageType storageValue) { return WorkingType(storageValue); }
222 : };
223 :
224 : template <>
225 : struct NumericAttributeTraits<bool>
226 : {
227 : using StorageType = uint8_t;
228 : using WorkingType = bool;
229 :
230 : static constexpr void WorkingToStorage(WorkingType workingValue, StorageType & storageValue) { storageValue = workingValue; }
231 :
232 : static constexpr WorkingType StorageToWorking(StorageType storageValue) { return storageValue; }
233 :
234 : static constexpr bool IsNullValue(StorageType value) { return value == kNullValue; }
235 : static constexpr void SetNull(StorageType & value) { value = kNullValue; }
236 : static constexpr bool CanRepresentValue(bool isNullable, StorageType value)
237 : {
238 : // This treats all nonzero values (except the null value) as true.
239 : return !IsNullValue(value);
240 : }
241 : static constexpr bool CanRepresentValue(bool isNullable, bool value) { return true; }
242 :
243 : static CHIP_ERROR Encode(TLV::TLVWriter & writer, TLV::Tag tag, StorageType value)
244 : {
245 : return writer.Put(tag, static_cast<bool>(value));
246 : }
247 :
248 : static uint8_t * ToAttributeStoreRepresentation(StorageType & value) { return reinterpret_cast<uint8_t *>(&value); }
249 :
250 : static uint8_t MinValue(bool isNullable) { return 0; }
251 :
252 : static uint8_t MaxValue(bool isNullable) { return 1; }
253 :
254 : static constexpr StorageType kNullValue = 0xFF;
255 : };
256 :
257 : } // namespace app
258 : } // namespace chip
|