1 /**
2  * Copyright © 2018 Intel Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #pragma once
17 
18 #include <ipmid/message/types.hpp>
19 
20 #include <array>
21 #include <optional>
22 #include <span>
23 #include <string>
24 #include <tuple>
25 #include <vector>
26 
27 namespace ipmi
28 {
29 
30 namespace message
31 {
32 
33 namespace details
34 {
35 
36 /**************************************
37  * ipmi return type helpers
38  **************************************/
39 
40 template <typename NumericType, size_t byteIndex = 0>
UnpackBytes(uint8_t * pointer,NumericType & i)41 void UnpackBytes(uint8_t* pointer, NumericType& i)
42 {
43     if constexpr (byteIndex < sizeof(NumericType))
44     {
45         i |= static_cast<NumericType>(*pointer) << (CHAR_BIT * byteIndex);
46         UnpackBytes<NumericType, byteIndex + 1>(pointer + 1, i);
47     }
48 }
49 
50 template <typename NumericType, size_t byteIndex = 0>
UnpackBytesUnaligned(Payload & p,NumericType & i)51 void UnpackBytesUnaligned(Payload& p, NumericType& i)
52 {
53     if constexpr (byteIndex < sizeof(NumericType))
54     {
55         i |= static_cast<NumericType>(p.popBits(CHAR_BIT))
56              << (CHAR_BIT * byteIndex);
57         UnpackBytesUnaligned<NumericType, byteIndex + 1>(p, i);
58     }
59 }
60 
61 /** @struct UnpackSingle
62  *  @brief Utility to unpack a single C++ element from a Payload
63  *
64  *  User-defined types are expected to specialize this template in order to
65  *  get their functionality.
66  *
67  *  @tparam T - Type of element to unpack.
68  */
69 template <typename T>
70 struct UnpackSingle
71 {
72     /** @brief Do the operation to unpack element.
73      *
74      *  @param[in] p - Payload to unpack from.
75      *  @param[out] t - The reference to unpack item into.
76      */
opipmi::message::details::UnpackSingle77     static int op(Payload& p, T& t)
78     {
79         if constexpr (std::is_fundamental<T>::value)
80         {
81             t = 0;
82             if (p.bitCount)
83             {
84                 if (p.fillBits(CHAR_BIT * sizeof(t)))
85                 {
86                     return 1;
87                 }
88                 UnpackBytesUnaligned<T>(p, t);
89             }
90             else
91             {
92                 // copy out bits from vector....
93                 if (p.raw.size() < (p.rawIndex + sizeof(t)))
94                 {
95                     return 1;
96                 }
97                 auto iter = p.raw.data() + p.rawIndex;
98                 t = 0;
99                 UnpackBytes<T>(iter, t);
100                 p.rawIndex += sizeof(t);
101             }
102             return 0;
103         }
104         else if constexpr (utility::is_tuple<T>::value)
105         {
106             bool priorError = p.unpackError;
107             size_t priorIndex = p.rawIndex;
108             // more stuff to unroll if partial bytes are out
109             size_t priorBitCount = p.bitCount;
110             fixed_uint_t<details::bitStreamSize> priorBits = p.bitStream;
111             int ret = p.unpack(t);
112             if (ret != 0)
113             {
114                 t = T();
115                 p.rawIndex = priorIndex;
116                 p.bitStream = priorBits;
117                 p.bitCount = priorBitCount;
118                 p.unpackError = priorError;
119             }
120             return ret;
121         }
122         else
123         {
124             static_assert(
125                 utility::dependent_false<T>::value,
126                 "Attempt to unpack a type that has no IPMI unpack operation");
127         }
128     }
129 };
130 
131 /** @struct UnpackSingle
132  *  @brief Utility to unpack a single C++ element from a Payload
133  *
134  *  Specialization to unpack std::string represented as a
135  *  UCSD-Pascal style string
136  */
137 template <>
138 struct UnpackSingle<std::string>
139 {
opipmi::message::details::UnpackSingle140     static int op(Payload& p, std::string& t)
141     {
142         // pop len first
143         if (p.rawIndex > (p.raw.size() - sizeof(uint8_t)))
144         {
145             return 1;
146         }
147         uint8_t len = p.raw[p.rawIndex++];
148         // check to see that there are n bytes left
149         auto [first, last] = p.pop<char>(len);
150         if (first == last)
151         {
152             return 1;
153         }
154         t.reserve(last - first);
155         t.insert(0, first, (last - first));
156         return 0;
157     }
158 };
159 
160 /** @brief Specialization of UnpackSingle for fixed_uint_t types
161  */
162 template <bitcount_t N>
163 struct UnpackSingle<fixed_uint_t<N>>
164 {
opipmi::message::details::UnpackSingle165     static int op(Payload& p, fixed_uint_t<N>& t)
166     {
167         static_assert(N <= (details::bitStreamSize - CHAR_BIT));
168         constexpr size_t count = N;
169         // acquire enough bits in the stream to fulfill the Payload
170         if (p.fillBits(count))
171         {
172             return -1;
173         }
174         fixed_uint_t<details::bitStreamSize> bitmask = ((1 << count) - 1);
175         t = (p.bitStream & bitmask).convert_to<fixed_uint_t<N>>();
176         p.bitStream >>= count;
177         p.bitCount -= count;
178         return 0;
179     }
180 };
181 
182 /** @brief Specialization of UnpackSingle for bool. */
183 template <>
184 struct UnpackSingle<bool>
185 {
opipmi::message::details::UnpackSingle186     static int op(Payload& p, bool& b)
187     {
188         // acquire enough bits in the stream to fulfill the Payload
189         if (p.fillBits(1))
190         {
191             return -1;
192         }
193         b = static_cast<bool>(p.bitStream & 0x01);
194         // clear bits from stream
195         p.bitStream >>= 1;
196         p.bitCount -= 1;
197         return 0;
198     }
199 };
200 
201 /** @brief Specialization of UnpackSingle for std::bitset<N>
202  */
203 template <size_t N>
204 struct UnpackSingle<std::bitset<N>>
205 {
opipmi::message::details::UnpackSingle206     static int op(Payload& p, std::bitset<N>& t)
207     {
208         static_assert(N <= (details::bitStreamSize - CHAR_BIT));
209         size_t count = N;
210         // acquire enough bits in the stream to fulfill the Payload
211         if (p.fillBits(count))
212         {
213             return -1;
214         }
215         fixed_uint_t<details::bitStreamSize> bitmask =
216             ~fixed_uint_t<details::bitStreamSize>(0) >>
217             (details::bitStreamSize - count);
218         t |= (p.bitStream & bitmask).convert_to<unsigned long long>();
219         p.bitStream >>= count;
220         p.bitCount -= count;
221         return 0;
222     }
223 };
224 
225 /** @brief Specialization of UnpackSingle for std::optional<T> */
226 template <typename T>
227 struct UnpackSingle<std::optional<T>>
228 {
opipmi::message::details::UnpackSingle229     static int op(Payload& p, std::optional<T>& t)
230     {
231         bool priorError = p.unpackError;
232         size_t priorIndex = p.rawIndex;
233         // more stuff to unroll if partial bytes are out
234         size_t priorBitCount = p.bitCount;
235         fixed_uint_t<details::bitStreamSize> priorBits = p.bitStream;
236         T value;
237         int ret = UnpackSingle<T>::op(p, value);
238         if (ret != 0)
239         {
240             t.reset();
241             p.rawIndex = priorIndex;
242             p.bitStream = priorBits;
243             p.bitCount = priorBitCount;
244             p.unpackError = priorError;
245         }
246         else
247         {
248             t.emplace(std::move(value));
249         }
250         return 0;
251     }
252 };
253 
254 /** @brief Specialization of UnpackSingle for std::array<T, N> */
255 template <typename T, size_t N>
256 struct UnpackSingle<std::array<T, N>>
257 {
opipmi::message::details::UnpackSingle258     static int op(Payload& p, std::array<T, N>& t)
259     {
260         int ret = 0;
261         size_t priorIndex = p.rawIndex;
262         for (auto& v : t)
263         {
264             ret = UnpackSingle<T>::op(p, v);
265             if (ret)
266             {
267                 p.rawIndex = priorIndex;
268                 t = std::array<T, N>();
269                 break;
270             }
271         }
272         return ret;
273     }
274 };
275 
276 /** @brief Specialization of UnpackSingle for std::array<uint8_t> */
277 template <size_t N>
278 struct UnpackSingle<std::array<uint8_t, N>>
279 {
opipmi::message::details::UnpackSingle280     static int op(Payload& p, std::array<uint8_t, N>& t)
281     {
282         if (p.raw.size() - p.rawIndex < N)
283         {
284             t.fill(0);
285             return -1;
286         }
287         // copy out the bytes
288         std::copy(p.raw.begin() + p.rawIndex, p.raw.begin() + p.rawIndex + N,
289                   t.begin());
290         p.rawIndex += N;
291         return 0;
292     }
293 };
294 
295 /** @brief Specialization of UnpackSingle for std::vector<T> */
296 template <typename T>
297 struct UnpackSingle<std::vector<T>>
298 {
opipmi::message::details::UnpackSingle299     static int op(Payload& p, std::vector<T>& t)
300     {
301         while (p.rawIndex < p.raw.size())
302         {
303             t.emplace_back();
304             if (UnpackSingle<T>::op(p, t.back()))
305             {
306                 t.pop_back();
307                 break;
308             }
309         }
310         // unpacking a vector is always successful:
311         // either stuff was unpacked successfully (return 0)
312         // or stuff was not unpacked, but should still return
313         // success because an empty vector or a not-fully-unpacked
314         // payload is not a failure.
315         return 0;
316     }
317 };
318 
319 /** @brief Specialization of UnpackSingle for std::vector<uint8_t> */
320 template <>
321 struct UnpackSingle<std::vector<uint8_t>>
322 {
opipmi::message::details::UnpackSingle323     static int op(Payload& p, std::vector<uint8_t>& t)
324     {
325         // copy out the remainder of the message
326         t.reserve(p.raw.size() - p.rawIndex);
327         t.insert(t.begin(), p.raw.begin() + p.rawIndex, p.raw.end());
328         p.rawIndex = p.raw.size();
329         return 0;
330     }
331 };
332 
333 /** @brief Specialization of UnpackSingle for SecureBuffer */
334 template <>
335 struct UnpackSingle<SecureBuffer>
336 {
opipmi::message::details::UnpackSingle337     static int op(Payload& p, SecureBuffer& t)
338     {
339         // copy out the remainder of the message
340         t.reserve(p.raw.size() - p.rawIndex);
341         t.insert(t.begin(), p.raw.begin() + p.rawIndex, p.raw.end());
342         p.rawIndex = p.raw.size();
343         return 0;
344     }
345 };
346 
347 /** @brief Specialization of UnpackSingle for std::span<const uint8_t> */
348 template <>
349 struct UnpackSingle<std::span<const uint8_t>>
350 {
opipmi::message::details::UnpackSingle351     static int op(Payload& p, std::span<const uint8_t>& t)
352     {
353         // copy out the remainder of the message
354         t = std::span<const uint8_t>(p.raw.begin() + p.rawIndex, p.raw.end());
355         p.rawIndex = p.raw.size();
356         return 0;
357     }
358 };
359 
360 /** @brief Specialization of UnpackSingle for Payload */
361 template <>
362 struct UnpackSingle<Payload>
363 {
opipmi::message::details::UnpackSingle364     static int op(Payload& p, Payload& t)
365     {
366         t = p;
367         // mark that this payload is being included in the args
368         p.trailingOk = true;
369         return 0;
370     }
371 };
372 
373 } // namespace details
374 
375 } // namespace message
376 
377 } // namespace ipmi
378