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/pack.hpp b/include/ipmid/message/pack.hpp
new file mode 100644
index 0000000..e6bbbce
--- /dev/null
+++ b/include/ipmid/message/pack.hpp
@@ -0,0 +1,222 @@
+/**
+ * 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 <memory>
+#include <phosphor-logging/log.hpp>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+namespace ipmi
+{
+
+namespace message
+{
+
+namespace details
+{
+
+/**************************************
+ * ipmi return type helpers
+ **************************************/
+
+template <typename NumericType, size_t byteIndex = 0>
+void PackBytes(uint8_t* pointer, const NumericType& i)
+{
+    if constexpr (byteIndex < sizeof(NumericType))
+    {
+        *pointer = static_cast<uint8_t>(i >> (8 * byteIndex));
+        PackBytes<NumericType, byteIndex + 1>(pointer + 1, i);
+    }
+}
+
+template <typename NumericType, size_t byteIndex = 0>
+void PackBytesUnaligned(Payload& p, const NumericType& i)
+{
+    if constexpr (byteIndex < sizeof(NumericType))
+    {
+        p.appendBits(CHAR_BIT, static_cast<uint8_t>(i >> (8 * byteIndex)));
+        PackBytesUnaligned<NumericType, byteIndex + 1>(p, i);
+    }
+}
+
+/** @struct PackSingle
+ *  @brief Utility to pack a single C++ element into a Payload
+ *
+ *  User-defined types are expected to specialize this template in order to
+ *  get their functionality.
+ *
+ *  @tparam S - Type of element to pack.
+ */
+template <typename T>
+struct PackSingle
+{
+    /** @brief Do the operation to pack element.
+     *
+     *  @param[in] p - Payload to pack into.
+     *  @param[out] t - The reference to pack item into.
+     */
+    static int op(Payload& p, T& t)
+    {
+        // if not on a byte boundary, must pack values LSbit/LSByte first
+        if (p.bitCount)
+        {
+            PackBytesUnaligned<T>(p, t);
+        }
+        else
+        {
+            // copy in bits to vector....
+            p.raw.resize(p.raw.size() + sizeof(T));
+            uint8_t* out = p.raw.data() + p.raw.size() - sizeof(T);
+            PackBytes<T>(out, t);
+        }
+        return 0;
+    }
+};
+
+/** @brief Specialization of PackSingle for std::string
+ *  represented as a UCSD-Pascal style string
+ */
+template <>
+struct PackSingle<std::string>
+{
+    static int op(Payload& p, std::string& t)
+    {
+        // check length first
+        uint8_t len;
+        if (t.length() > std::numeric_limits<decltype(len)>::max())
+        {
+            using namespace phosphor::logging;
+            log<level::ERR>("long string truncated on IPMI message pack");
+            return 1;
+        }
+        len = static_cast<uint8_t>(t.length());
+        PackSingle<uint8_t>::op(p, len);
+        p.append(t.c_str(), t.c_str() + t.length());
+        return 0;
+    }
+};
+
+/** @brief Specialization of PackSingle for fixed_uint_t types
+ */
+template <unsigned N>
+struct PackSingle<fixed_uint_t<N>>
+{
+    static int op(Payload& p, fixed_uint_t<N>& t)
+    {
+        size_t count = N;
+        static_assert(N <= (details::bitStreamSize - CHAR_BIT));
+        uint64_t bits = t;
+        while (count > 0)
+        {
+            size_t appendCount = std::min(count, static_cast<size_t>(CHAR_BIT));
+            p.appendBits(appendCount, static_cast<uint8_t>(bits));
+            bits >>= CHAR_BIT;
+            count -= appendCount;
+        }
+        return 0;
+    }
+};
+
+/** @brief Specialization of PackSingle for bool. */
+template <>
+struct PackSingle<bool>
+{
+    static int op(Payload& p, bool& b)
+    {
+        p.appendBits(1, b);
+        return 0;
+    }
+};
+
+/** @brief Specialization of PackSingle for std::bitset<N> */
+template <size_t N>
+struct PackSingle<std::bitset<N>>
+{
+    static int op(Payload& p, std::bitset<N>& t)
+    {
+        size_t count = N;
+        static_assert(N <= (details::bitStreamSize - CHAR_BIT));
+        unsigned long long bits = t.to_ullong();
+        while (count > 0)
+        {
+            size_t appendCount = std::min(count, size_t(CHAR_BIT));
+            p.appendBits(appendCount, static_cast<uint8_t>(bits));
+            bits >>= CHAR_BIT;
+            count -= appendCount;
+        }
+        return 0;
+    }
+};
+
+/** @brief Specialization of PackSingle for std::array<T, N> */
+template <typename T, size_t N>
+struct PackSingle<std::array<T, N>>
+{
+    static int op(Payload& p, std::array<T, N>& t)
+    {
+        int ret = 0;
+        for (auto& v : t)
+        {
+            int ret = PackSingle<T>::op(p, v);
+            if (ret)
+            {
+                break;
+            }
+        }
+        return ret;
+    }
+};
+
+/** @brief Specialization of PackSingle for std::vector<T> */
+template <typename T>
+struct PackSingle<std::vector<T>>
+{
+    static int op(Payload& p, std::vector<T>& t)
+    {
+        int ret = 0;
+        for (auto& v : t)
+        {
+            int ret = PackSingle<T>::op(p, v);
+            if (ret)
+            {
+                break;
+            }
+        }
+        return ret;
+    }
+};
+
+/** @brief Specialization of PackSingle for std::vector<uint8_t> */
+template <>
+struct PackSingle<std::vector<uint8_t>>
+{
+    static int op(Payload& p, std::vector<uint8_t>& t)
+    {
+        p.raw.reserve(p.raw.size() + t.size());
+        p.raw.insert(p.raw.end(), t.begin(), t.end());
+        return 0;
+    }
+};
+
+} // namespace details
+
+} // namespace message
+
+} // namespace ipmi
diff --git a/include/ipmid/message/types.hpp b/include/ipmid/message/types.hpp
new file mode 100644
index 0000000..b79ddba
--- /dev/null
+++ b/include/ipmid/message/types.hpp
@@ -0,0 +1,110 @@
+/**
+ * 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 <bitset>
+#include <boost/multiprecision/cpp_int.hpp>
+#include <ipmid/utility.hpp>
+#include <tuple>
+
+// unsigned fixed-bit sizes
+template <unsigned N>
+using fixed_uint_t =
+    boost::multiprecision::number<boost::multiprecision::cpp_int_backend<
+        N, N, boost::multiprecision::unsigned_magnitude,
+        boost::multiprecision::unchecked, void>>;
+// signed fixed-bit sizes
+template <unsigned N>
+using fixed_int_t =
+    boost::multiprecision::number<boost::multiprecision::cpp_int_backend<
+        N, N, boost::multiprecision::signed_magnitude,
+        boost::multiprecision::unchecked, void>>;
+
+using uint1_t = fixed_uint_t<1>;
+using uint2_t = fixed_uint_t<2>;
+using uint3_t = fixed_uint_t<3>;
+using uint4_t = fixed_uint_t<4>;
+using uint5_t = fixed_uint_t<5>;
+using uint6_t = fixed_uint_t<6>;
+using uint7_t = fixed_uint_t<7>;
+// native uint8_t
+using uint9_t = fixed_uint_t<9>;
+using uint10_t = fixed_uint_t<10>;
+using uint11_t = fixed_uint_t<11>;
+using uint12_t = fixed_uint_t<12>;
+using uint13_t = fixed_uint_t<13>;
+using uint14_t = fixed_uint_t<14>;
+using uint15_t = fixed_uint_t<15>;
+// native uint16_t
+using uint24_t = fixed_uint_t<24>;
+
+// signed fixed-bit sizes
+using int2_t = fixed_int_t<2>;
+using int3_t = fixed_int_t<3>;
+using int4_t = fixed_int_t<4>;
+using int5_t = fixed_int_t<5>;
+using int6_t = fixed_int_t<6>;
+using int7_t = fixed_int_t<7>;
+// native int8_t
+using int9_t = fixed_int_t<9>;
+using int10_t = fixed_int_t<10>;
+using int11_t = fixed_int_t<11>;
+using int12_t = fixed_int_t<12>;
+using int13_t = fixed_int_t<13>;
+using int14_t = fixed_int_t<14>;
+using int15_t = fixed_int_t<15>;
+// native int16_t
+using int24_t = fixed_int_t<24>;
+
+// bool is more efficient than a uint1_t
+using bit = bool;
+
+// Mechanism for going from uint7_t, int7_t, or std::bitset<7> to 7 bits
+// use nrFixedBits<uint7_t> or nrFixedBits<decltype(u7)>
+namespace types
+{
+namespace details
+{
+
+template <size_t N>
+struct Size
+{
+    static constexpr size_t value = N;
+};
+
+template <unsigned Bits>
+constexpr auto getNrBits(const fixed_int_t<Bits>&) -> Size<Bits>;
+template <unsigned Bits>
+constexpr auto getNrBits(const fixed_uint_t<Bits>&) -> Size<Bits>;
+template <size_t Bits>
+constexpr auto getNrBits(const std::bitset<Bits>&) -> Size<Bits>;
+
+} // namespace details
+
+/**
+ * @brief mechanism to get N from a type like fixed_int_t<N>
+ *
+ * helper template to extract N from a fixed_(u)int_t variable
+ *
+ * @tparam T - a type of fixed_int_t<N> or fixed_unint_t<N>
+ *
+ * @return size_t - evaluates to a constexpr size_t of N
+ */
+template <typename T>
+constexpr auto nrFixedBits =
+    decltype(details::getNrBits(std::declval<T>()))::value;
+
+} // namespace types
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