ipmid: Compiler-generated unpacking and packing of messages

handler.hpp has the templated wrapping bits for ipmi command handler
callbacks implemented.

message.hpp has the serialization/deserialization of the ipmi data
stream into packed tuples for functions.
message/pack.hpp and message/unpack.hpp contain the actual serialization
and deserialization of types.

Change-Id: If997f8768c8488ab6ac022526a5ef9a1bce57fcb
Signed-off-by: Vernon Mauery <vernon.mauery@linux.intel.com>
diff --git a/include/ipmid/message/unpack.hpp b/include/ipmid/message/unpack.hpp
new file mode 100644
index 0000000..d96928f
--- /dev/null
+++ b/include/ipmid/message/unpack.hpp
@@ -0,0 +1,340 @@
+/**
+ * Copyright © 2018 Intel Corporation
+ *
+ * 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 <array>
+#include <ipmid/message/types.hpp>
+#include <optional>
+#include <string>
+#include <tuple>
+#include <vector>
+
+namespace ipmi
+{
+
+namespace message
+{
+
+namespace details
+{
+
+/**************************************
+ * ipmi return type helpers
+ **************************************/
+
+template <typename NumericType, size_t byteIndex = 0>
+void UnpackBytes(uint8_t* pointer, NumericType& i)
+{
+    if constexpr (byteIndex < sizeof(NumericType))
+    {
+        i |= static_cast<NumericType>(*pointer) << (CHAR_BIT * byteIndex);
+        UnpackBytes<NumericType, byteIndex + 1>(pointer + 1, i);
+    }
+}
+
+template <typename NumericType, size_t byteIndex = 0>
+void UnpackBytesUnaligned(Payload& p, NumericType& i)
+{
+    if constexpr (byteIndex < sizeof(NumericType))
+    {
+        i |= static_cast<NumericType>(p.popBits(CHAR_BIT))
+             << (CHAR_BIT * byteIndex);
+        UnpackBytesUnaligned<NumericType, byteIndex + 1>(p, i);
+    }
+}
+
+/** @struct UnpackSingle
+ *  @brief Utility to unpack a single C++ element from a Payload
+ *
+ *  User-defined types are expected to specialize this template in order to
+ *  get their functionality.
+ *
+ *  @tparam T - Type of element to unpack.
+ */
+template <typename T>
+struct UnpackSingle
+{
+    /** @brief Do the operation to unpack element.
+     *
+     *  @param[in] p - Payload to unpack from.
+     *  @param[out] t - The reference to unpack item into.
+     */
+    static int op(Payload& p, T& t)
+    {
+        if constexpr (std::is_fundamental<T>::value)
+        {
+            t = 0;
+            if (p.bitCount)
+            {
+                if (p.fillBits(CHAR_BIT * sizeof(t)))
+                {
+                    return 1;
+                }
+                UnpackBytesUnaligned<T>(p, t);
+            }
+            else
+            {
+                // copy out bits from vector....
+                if (p.raw.size() < (p.rawIndex + sizeof(t)))
+                {
+                    return 1;
+                }
+                auto iter = p.raw.data() + p.rawIndex;
+                t = 0;
+                UnpackBytes<T>(iter, t);
+                p.rawIndex += sizeof(t);
+            }
+            return 0;
+        }
+        else
+        {
+            if constexpr (utility::is_tuple<T>::value)
+            {
+                bool priorError = p.unpackError;
+                size_t priorIndex = p.rawIndex;
+                // more stuff to unroll if partial bytes are out
+                size_t priorBitCount = p.bitCount;
+                fixed_uint_t<details::bitStreamSize> priorBits = p.bitStream;
+                int ret = p.unpack(t);
+                if (ret != 0)
+                {
+                    t = T();
+                    p.rawIndex = priorIndex;
+                    p.bitStream = priorBits;
+                    p.bitCount = priorBitCount;
+                    p.unpackError = priorError;
+                }
+                return 0;
+            }
+        }
+    }
+};
+
+/** @struct UnpackSingle
+ *  @brief Utility to unpack a single C++ element from a Payload
+ *
+ *  Specialization to unpack std::string represented as a
+ *  UCSD-Pascal style string
+ */
+template <>
+struct UnpackSingle<std::string>
+{
+    static int op(Payload& p, std::string& t)
+    {
+        // pop len first
+        if (p.rawIndex > (p.raw.size() - sizeof(uint8_t)))
+        {
+            return 1;
+        }
+        uint8_t len = p.raw[p.rawIndex++];
+        // check to see that there are n bytes left
+        auto [first, last] = p.pop<char>(len);
+        if (first == last)
+        {
+            return 1;
+        }
+        t.reserve(last - first);
+        t.insert(0, first, (last - first));
+        return 0;
+    }
+};
+
+/** @brief Specialization of UnpackSingle for fixed_uint_t types
+ */
+template <unsigned N>
+struct UnpackSingle<fixed_uint_t<N>>
+{
+    static int op(Payload& p, fixed_uint_t<N>& t)
+    {
+        static_assert(N <= (details::bitStreamSize - CHAR_BIT));
+        constexpr size_t count = N;
+        // acquire enough bits in the stream to fulfill the Payload
+        if (p.fillBits(count))
+        {
+            return -1;
+        }
+        fixed_uint_t<details::bitStreamSize> bitmask = ((1 << count) - 1);
+        t = (p.bitStream & bitmask).convert_to<fixed_uint_t<N>>();
+        p.bitStream >>= count;
+        p.bitCount -= count;
+        return 0;
+    }
+};
+
+/** @brief Specialization of UnpackSingle for bool. */
+template <>
+struct UnpackSingle<bool>
+{
+    static int op(Payload& p, bool& b)
+    {
+        // acquire enough bits in the stream to fulfill the Payload
+        if (p.fillBits(1))
+        {
+            return -1;
+        }
+        b = static_cast<bool>(p.bitStream & 0x01);
+        // clear bits from stream
+        p.bitStream >>= 1;
+        p.bitCount -= 1;
+        return 0;
+    }
+};
+
+/** @brief Specialization of UnpackSingle for std::bitset<N>
+ */
+template <size_t N>
+struct UnpackSingle<std::bitset<N>>
+{
+    static int op(Payload& p, std::bitset<N>& t)
+    {
+        static_assert(N <= (details::bitStreamSize - CHAR_BIT));
+        size_t count = N;
+        // acquire enough bits in the stream to fulfill the Payload
+        if (p.fillBits(count))
+        {
+            return -1;
+        }
+        fixed_uint_t<details::bitStreamSize> bitmask = ((1 << count) - 1);
+        t |= (p.bitStream & bitmask).convert_to<unsigned long long>();
+        p.bitStream >>= count;
+        p.bitCount -= count;
+        return 0;
+    }
+};
+
+/** @brief Specialization of UnpackSingle for std::optional<T> */
+template <typename T>
+struct UnpackSingle<std::optional<T>>
+{
+    static int op(Payload& p, std::optional<T>& t)
+    {
+        bool priorError = p.unpackError;
+        size_t priorIndex = p.rawIndex;
+        // more stuff to unroll if partial bytes are out
+        size_t priorBitCount = p.bitCount;
+        fixed_uint_t<details::bitStreamSize> priorBits = p.bitStream;
+        t.emplace();
+        int ret = UnpackSingle<T>::op(p, *t);
+        if (ret != 0)
+        {
+            t.reset();
+            p.rawIndex = priorIndex;
+            p.bitStream = priorBits;
+            p.bitCount = priorBitCount;
+            p.unpackError = priorError;
+        }
+        return 0;
+    }
+};
+
+/** @brief Specialization of UnpackSingle for std::array<T, N> */
+template <typename T, size_t N>
+struct UnpackSingle<std::array<T, N>>
+{
+    static int op(Payload& p, std::array<T, N>& t)
+    {
+        int ret = 0;
+        size_t priorIndex = p.rawIndex;
+        for (auto& v : t)
+        {
+            ret = UnpackSingle<T>::op(p, v);
+            if (ret)
+            {
+                p.rawIndex = priorIndex;
+                t = std::array<T, N>();
+                break;
+            }
+        }
+        return ret;
+    }
+};
+
+/** @brief Specialization of UnpackSingle for std::array<uint8_t> */
+template <size_t N>
+struct UnpackSingle<std::array<uint8_t, N>>
+{
+    static int op(Payload& p, std::array<uint8_t, N>& t)
+    {
+        if (p.raw.size() - p.rawIndex < N)
+        {
+            t.fill(0);
+            return -1;
+        }
+        // copy out the bytes
+        std::copy(p.raw.begin() + p.rawIndex, p.raw.begin() + p.rawIndex + N,
+                  t.begin());
+        p.rawIndex += N;
+        return 0;
+    }
+};
+
+/** @brief Specialization of UnpackSingle for std::vector<T> */
+template <typename T>
+struct UnpackSingle<std::vector<T>>
+{
+    static int op(Payload& p, std::vector<T>& t)
+    {
+        int ret = 0;
+        while (p.rawIndex < p.raw.size())
+        {
+            t.emplace_back();
+            ret = UnpackSingle<T>::op(p, t.back());
+            if (ret)
+            {
+                t.pop_back();
+                break;
+            }
+        }
+        return ret;
+    }
+};
+
+/** @brief Specialization of UnpackSingle for std::vector<uint8_t> */
+template <>
+struct UnpackSingle<std::vector<uint8_t>>
+{
+    static int op(Payload& p, std::vector<uint8_t>& t)
+    {
+        // copy out the remainder of the message
+        t.reserve(p.raw.size() - p.rawIndex);
+        t.insert(t.begin(), p.raw.begin() + p.rawIndex, p.raw.end());
+        p.rawIndex = p.raw.size();
+        return 0;
+    }
+};
+
+/** @brief Specialization of UnpackSingle for Payload */
+template <>
+struct UnpackSingle<Payload>
+{
+    static int op(Payload& p, Payload& t)
+    {
+        // mark that this payload is being included in the args
+        p.trailingOk = true;
+        t = p;
+        // reset the unpacking flags so it can be properly checked
+        t.trailingOk = false;
+        t.unpackCheck = true;
+        t.unpackError = false;
+        return 0;
+    }
+};
+
+} // namespace details
+
+} // namespace message
+
+} // namespace ipmi