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 <app/util/attribute-storage-null-handling.h>
21 : #include <lib/support/TypeTraits.h>
22 :
23 : #include <cstdint>
24 : #include <limits>
25 :
26 : namespace chip {
27 : namespace app {
28 :
29 : namespace detail {
30 : template <int ByteSize, bool IsSigned>
31 : struct WorkingTypeMapper
32 : {
33 : };
34 : template <int ByteSize>
35 : struct WorkingTypeMapper<ByteSize, true>
36 : {
37 : using WorkingType = int64_t;
38 : };
39 : template <>
40 : struct WorkingTypeMapper<3, true>
41 : {
42 : using WorkingType = int32_t;
43 : };
44 : template <int ByteSize>
45 : struct WorkingTypeMapper<ByteSize, false>
46 : {
47 : using WorkingType = uint64_t;
48 : };
49 : template <>
50 : struct WorkingTypeMapper<3, false>
51 : {
52 : using WorkingType = uint32_t;
53 : };
54 : } // namespace detail
55 :
56 : template <int ByteSize, bool IsSigned>
57 : struct OddSizedInteger
58 : {
59 : // WorkingType is the type we use to represent the value as an actual
60 : // integer that we can do arithmetic, greater/less-than compares, etc on.
61 : using WorkingType = typename detail::WorkingTypeMapper<ByteSize, IsSigned>::WorkingType;
62 :
63 : // StorageType is the type "at rest" in the attribute store. It's a
64 : // native-endian byte buffer.
65 : using StorageType = uint8_t[ByteSize];
66 : };
67 :
68 : namespace detail {
69 : template <int ByteSize, bool IsBigEndian>
70 : struct IntegerByteIndexing;
71 :
72 : template <int ByteSize>
73 : struct IntegerByteIndexing<ByteSize, true>
74 : {
75 : static constexpr int highIndex = 0;
76 : static constexpr int lowIndex = ByteSize - 1;
77 : static constexpr int lowerIndex = 1;
78 : static constexpr int raiseIndex = -1;
79 : static constexpr int pastLowIndex = ByteSize;
80 : static constexpr int pastHighIndex = -1;
81 : };
82 :
83 : template <int ByteSize>
84 : struct IntegerByteIndexing<ByteSize, false>
85 : {
86 : static constexpr int highIndex = ByteSize - 1;
87 : static constexpr int lowIndex = 0;
88 : static constexpr int lowerIndex = -1;
89 : static constexpr int raiseIndex = 1;
90 : static constexpr int pastLowIndex = -1;
91 : static constexpr int pastHighIndex = ByteSize;
92 : };
93 : } // namespace detail
94 :
95 : namespace NumericLimits {
96 :
97 : // Generic size information for unsigned values.
98 : //
99 : // Assumes non-nullable types. Nullable types reserve one of the values as NULL (the max)
100 146 : inline constexpr uint64_t MaxUnsignedValue(unsigned ByteSize)
101 : {
102 146 : if (ByteSize == 8)
103 : {
104 19 : return std::numeric_limits<uint64_t>::max();
105 : }
106 127 : return (1ULL << (8 * ByteSize)) - 1;
107 : }
108 :
109 : /// Readability-method to express that the maximum unsigned value is a null value
110 : ///
111 : /// Our encoding states that max int value is the NULL value
112 116 : inline constexpr uint64_t UnsignedMaxValueToNullValue(uint64_t value)
113 : {
114 116 : return value;
115 : }
116 :
117 : // Generic size information for signed values.
118 : //
119 : // Assumes non-nullable types. Nullable types reserve one of the values as NULL (the min)
120 123 : inline constexpr int64_t MaxSignedValue(unsigned ByteSize)
121 : {
122 123 : if (ByteSize == 8)
123 : {
124 16 : return std::numeric_limits<int64_t>::max();
125 : }
126 107 : return (static_cast<int64_t>(1) << (8 * ByteSize - 1)) - 1;
127 : }
128 :
129 185 : inline constexpr int64_t MinSignedValue(unsigned ByteSize)
130 : {
131 185 : if (ByteSize == 8)
132 : {
133 25 : return std::numeric_limits<int64_t>::min();
134 : }
135 160 : return -(static_cast<int64_t>(1) << (8 * ByteSize - 1));
136 : }
137 :
138 : /// Readability-method to express that the maximum signed value is a null value
139 : ///
140 : /// Our encoding states that min int value is the NULL value
141 133 : inline constexpr int64_t SignedMinValueToNullValue(int64_t value)
142 : {
143 133 : return value;
144 : }
145 :
146 : } // namespace NumericLimits
147 :
148 : template <int ByteSize, bool IsSigned, bool IsBigEndian>
149 : struct NumericAttributeTraits<OddSizedInteger<ByteSize, IsSigned>, IsBigEndian> : detail::IntegerByteIndexing<ByteSize, IsBigEndian>
150 : {
151 : using IntType = OddSizedInteger<ByteSize, IsSigned>;
152 : // StorageType is the type "at rest" in the attribute store. It's a
153 : // native-endian byte buffer.
154 : using StorageType = typename IntType::StorageType;
155 : // WorkingType is the type we use to represent the value as an actual
156 : // integer that we can do arithmetic, greater/less-than compares, etc on.
157 : using WorkingType = typename IntType::WorkingType;
158 :
159 : using Indexing = detail::IntegerByteIndexing<ByteSize, IsBigEndian>;
160 : using Indexing::highIndex;
161 : using Indexing::lowerIndex;
162 : using Indexing::lowIndex;
163 : using Indexing::pastHighIndex;
164 : using Indexing::pastLowIndex;
165 : using Indexing::raiseIndex;
166 :
167 : static constexpr WorkingType StorageToWorking(StorageType storageValue)
168 : {
169 : // WorkingType can always fit all of our bit-shifting, because it has at
170 : // least one extra byte.
171 : WorkingType value = 0;
172 : for (int i = highIndex; i != pastLowIndex; i += lowerIndex)
173 : {
174 : value = (value << 8) | storageValue[i];
175 : }
176 :
177 : // If unsigned, we are done. If signed, we need to make sure our high
178 : // bit gets treated as a sign bit, not a value bit, with our bits in 2s
179 : // complement.
180 : if (IsSigned)
181 : {
182 : constexpr WorkingType MaxPositive = (static_cast<WorkingType>(1) << (8 * ByteSize - 1)) - 1;
183 : if (value > MaxPositive)
184 : {
185 : value = value - (static_cast<WorkingType>(1) << (8 * ByteSize));
186 : }
187 : }
188 :
189 : return value;
190 : }
191 :
192 : static constexpr void WorkingToStorage(WorkingType workingValue, StorageType & storageValue)
193 : {
194 : // We can just grab the low ByteSize bytes of workingValue.
195 : for (int i = lowIndex; i != pastHighIndex; i += raiseIndex)
196 : {
197 : // Casting to uint8_t exactly grabs the lowest byte.
198 : storageValue[i] = static_cast<uint8_t>(workingValue);
199 : workingValue = workingValue >> 8;
200 : }
201 : }
202 :
203 0 : static constexpr bool IsNullValue(StorageType value)
204 : {
205 : if (IsSigned)
206 : {
207 : // Check for the equivalent of the most negative integer, in 2s
208 : // complement notation.
209 0 : if (value[highIndex] != 0x80)
210 : {
211 0 : return false;
212 : }
213 0 : for (int i = highIndex + lowerIndex; i != pastLowIndex; i += lowerIndex)
214 : {
215 0 : if (value[i] != 0x00)
216 : {
217 0 : return false;
218 : }
219 : }
220 0 : return true;
221 : }
222 :
223 : // Check for the equivalent of the largest unsigned integer.
224 0 : for (int i = highIndex; i != pastLowIndex; i += lowerIndex)
225 : {
226 0 : if (value[i] != 0xFF)
227 : {
228 0 : return false;
229 : }
230 : }
231 0 : return true;
232 : }
233 :
234 : static constexpr void SetNull(StorageType & value)
235 : {
236 : if (IsSigned)
237 : {
238 : value[highIndex] = 0x80;
239 : for (int i = highIndex + lowerIndex; i != pastLowIndex; i += lowerIndex)
240 : {
241 : value[i] = 0x00;
242 : }
243 : }
244 : else
245 : {
246 : for (int i = highIndex; i != pastLowIndex; i += lowerIndex)
247 : {
248 : value[i] = 0xFF;
249 : }
250 : }
251 : }
252 :
253 : static constexpr bool CanRepresentValue(bool isNullable, StorageType value) { return !isNullable || !IsNullValue(value); }
254 :
255 : static constexpr bool CanRepresentValue(bool isNullable, WorkingType value)
256 : {
257 : return MinValue(isNullable) <= value && value <= MaxValue(isNullable);
258 : }
259 :
260 : static CHIP_ERROR Encode(TLV::TLVWriter & writer, TLV::Tag tag, StorageType value)
261 : {
262 : return writer.Put(tag, StorageToWorking(value));
263 : }
264 :
265 : static uint8_t * ToAttributeStoreRepresentation(StorageType & value) { return value; }
266 :
267 : static WorkingType MinValue(bool isNullable)
268 : {
269 : if constexpr (!IsSigned)
270 : {
271 : return 0;
272 : }
273 :
274 : // Since WorkingType has at least one extra byte, the bitshift cannot overflow.
275 : constexpr WorkingType signedMin = -(static_cast<WorkingType>(1) << (8 * ByteSize - 1));
276 : if (isNullable)
277 : {
278 : // Smallest negative value is excluded for nullable signed types.
279 : return signedMin + 1;
280 : }
281 :
282 : return signedMin;
283 : }
284 :
285 : static WorkingType MaxValue(bool isNullable)
286 : {
287 : // Since WorkingType has at least one extra byte, none of our bitshifts
288 : // overflow.
289 : if constexpr (IsSigned)
290 : {
291 : return (static_cast<WorkingType>(1) << (8 * ByteSize - 1)) - 1;
292 : }
293 :
294 : constexpr WorkingType unsignedMax = (static_cast<WorkingType>(1) << (8 * ByteSize)) - 1;
295 : if (isNullable)
296 : {
297 : // Largest value is excluded for nullable unsigned types.
298 : return unsignedMax - 1;
299 : }
300 :
301 : return unsignedMax;
302 : }
303 : };
304 :
305 : } // namespace app
306 : } // namespace chip
|