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/handler.hpp b/include/ipmid/handler.hpp
new file mode 100644
index 0000000..203dcad
--- /dev/null
+++ b/include/ipmid/handler.hpp
@@ -0,0 +1,396 @@
+/**
+ * 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 <algorithm>
+#include <boost/asio/spawn.hpp>
+#include <boost/callable_traits.hpp>
+#include <cstdint>
+#include <exception>
+#include <ipmid/api.hpp>
+#include <ipmid/message.hpp>
+#include <memory>
+#include <optional>
+#include <phosphor-logging/log.hpp>
+#include <tuple>
+#include <user_channel/channel_layer.hpp>
+#include <utility>
+
+#ifdef ALLOW_DEPRECATED_API
+#include <ipmid/api.h>
+
+#include <ipmid/oemrouter.hpp>
+#endif /* ALLOW_DEPRECATED_API */
+
+namespace ipmi
+{
+
+template <typename... Args>
+static inline message::Response::ptr
+    errorResponse(message::Request::ptr request, ipmi::Cc cc, Args&&... args)
+{
+    message::Response::ptr response = request->makeResponse();
+    auto payload = std::make_tuple(cc, args...);
+    response->pack(payload);
+    return response;
+}
+static inline message::Response::ptr
+    errorResponse(message::Request::ptr request, ipmi::Cc cc)
+{
+    message::Response::ptr response = request->makeResponse();
+    response->pack(cc);
+    return response;
+}
+
+/**
+ * @brief Handler base class for dealing with IPMI request/response
+ *
+ * The subclasses are all templated so they can provide access to any type
+ * of command callback functions.
+ */
+class HandlerBase
+{
+  public:
+    using ptr = std::shared_ptr<HandlerBase>;
+
+    /** @brief wrap the call to the registered handler with the request
+     *
+     * This is called from the running queue context after it has already
+     * created a request object that contains all the information required to
+     * execute the ipmi command. This function will return the response object
+     * pointer that owns the response object that will ultimately get sent back
+     * to the requester.
+     *
+     * This is a non-virtual function wrapper to the virtualized executeCallback
+     * function that actually does the work. This is required because of how
+     * templates and virtualization work together.
+     *
+     * @param request a shared_ptr to a Request object
+     *
+     * @return a shared_ptr to a Response object
+     */
+    message::Response::ptr call(message::Request::ptr request)
+    {
+        return executeCallback(request);
+    }
+
+  private:
+    /** @brief call the registered handler with the request
+     *
+     * This is called from the running queue context after it has already
+     * created a request object that contains all the information required to
+     * execute the ipmi command. This function will return the response object
+     * pointer that owns the response object that will ultimately get sent back
+     * to the requester.
+     *
+     * @param request a shared_ptr to a Request object
+     *
+     * @return a shared_ptr to a Response object
+     */
+    virtual message::Response::ptr
+        executeCallback(message::Request::ptr request) = 0;
+};
+
+/**
+ * @brief Main IPMI handler class
+ *
+ * New IPMI handlers will resolve into this class, which will read the signature
+ * of the registering function, attempt to extract the appropriate arguments
+ * from a request, pass the arguments to the function, and then pack the
+ * response of the function back into an IPMI response.
+ */
+template <typename Handler>
+class IpmiHandler final : public HandlerBase
+{
+  public:
+    explicit IpmiHandler(Handler&& handler) :
+        handler_(std::forward<Handler>(handler))
+    {
+    }
+
+  private:
+    Handler handler_;
+
+    /** @brief call the registered handler with the request
+     *
+     * This is called from the running queue context after it has already
+     * created a request object that contains all the information required to
+     * execute the ipmi command. This function will return the response object
+     * pointer that owns the response object that will ultimately get sent back
+     * to the requester.
+     *
+     * Because this is the new variety of IPMI handler, this is the function
+     * that attempts to extract the requested parameters in order to pass them
+     * onto the callback function and then packages up the response into a plain
+     * old vector to pass back to the caller.
+     *
+     * @param request a shared_ptr to a Request object
+     *
+     * @return a shared_ptr to a Response object
+     */
+    message::Response::ptr
+        executeCallback(message::Request::ptr request) override
+    {
+        message::Response::ptr response = request->makeResponse();
+
+        using CallbackSig = boost::callable_traits::args_t<Handler>;
+        using InputArgsType = typename utility::DecayTuple<CallbackSig>::type;
+        using UnpackArgsType = typename utility::StripFirstArgs<
+            utility::NonIpmiArgsCount<InputArgsType>::size(),
+            InputArgsType>::type;
+        using ResultType = boost::callable_traits::return_type_t<Handler>;
+
+        UnpackArgsType unpackArgs;
+        ipmi::Cc unpackError = request->unpack(unpackArgs);
+        if (unpackError != ipmi::ccSuccess)
+        {
+            response->cc = unpackError;
+            return response;
+        }
+        /* callbacks can contain an optional first argument of one of:
+         * 1) boost::asio::yield_context
+         * 2) ipmi::Context::ptr
+         * 3) ipmi::message::Request::ptr
+         *
+         * If any of those is part of the callback signature as the first
+         * argument, it will automatically get packed into the parameter pack
+         * here.
+         *
+         * One more special optional argument is an ipmi::message::Payload.
+         * This argument can be in any position, though logically it makes the
+         * most sense if it is the last. If this class is included in the
+         * handler signature, it will allow for the handler to unpack optional
+         * parameters. For example, the Set LAN Configuration Parameters
+         * command takes variable length (and type) values for each of the LAN
+         * parameters. This means that the  only fixed data is the channel and
+         * parameter selector. All the remaining data can be extracted using
+         * the Payload class and the unpack API available to the Payload class.
+         */
+        std::optional<InputArgsType> inputArgs;
+        if constexpr (std::tuple_size<InputArgsType>::value > 0)
+        {
+            if constexpr (std::is_same<std::tuple_element_t<0, InputArgsType>,
+                                       boost::asio::yield_context>::value)
+            {
+                inputArgs.emplace(std::tuple_cat(
+                    std::forward_as_tuple(*(request->ctx->yield)),
+                    std::move(unpackArgs)));
+            }
+            else if constexpr (std::is_same<
+                                   std::tuple_element_t<0, InputArgsType>,
+                                   ipmi::Context::ptr>::value)
+            {
+                inputArgs.emplace(
+                    std::tuple_cat(std::forward_as_tuple(request->ctx),
+                                   std::move(unpackArgs)));
+            }
+            else if constexpr (std::is_same<
+                                   std::tuple_element_t<0, InputArgsType>,
+                                   ipmi::message::Request::ptr>::value)
+            {
+                inputArgs.emplace(std::tuple_cat(std::forward_as_tuple(request),
+                                                 std::move(unpackArgs)));
+            }
+            else
+            {
+                // no special parameters were requested (but others were)
+                inputArgs.emplace(std::move(unpackArgs));
+            }
+        }
+        else
+        {
+            // no parameters were requested
+            inputArgs = std::move(unpackArgs);
+        }
+        ResultType result;
+        try
+        {
+            // execute the registered callback function and get the
+            // ipmi::RspType<>
+            result = std::apply(handler_, *inputArgs);
+        }
+        catch (const std::exception& e)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "Handler failed to catch exception",
+                phosphor::logging::entry("EXCEPTION=%s", e.what()),
+                phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
+                phosphor::logging::entry("CMD=%x", request->ctx->cmd));
+            return errorResponse(request, ccUnspecifiedError);
+        }
+        catch (...)
+        {
+            std::exception_ptr eptr;
+            try
+            {
+                eptr = std::current_exception();
+                if (eptr)
+                {
+                    std::rethrow_exception(eptr);
+                }
+            }
+            catch (const std::exception& e)
+            {
+                phosphor::logging::log<phosphor::logging::level::ERR>(
+                    "Handler failed to catch exception",
+                    phosphor::logging::entry("EXCEPTION=%s", e.what()),
+                    phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
+                    phosphor::logging::entry("CMD=%x", request->ctx->cmd));
+                return errorResponse(request, ccUnspecifiedError);
+            }
+        }
+
+        response->cc = std::get<0>(result);
+        auto payload = std::get<1>(result);
+        // check for optional payload
+        if (payload)
+        {
+            response->pack(*payload);
+        }
+        return response;
+    }
+};
+
+#ifdef ALLOW_DEPRECATED_API
+/**
+ * @brief Legacy IPMI handler class
+ *
+ * Legacy IPMI handlers will resolve into this class, which will behave the same
+ * way as the legacy IPMI queue, passing in a big buffer for the request and a
+ * big buffer for the response.
+ *
+ * As soon as all the handlers have been rewritten, this class will be marked as
+ * deprecated and eventually removed.
+ */
+template <>
+class IpmiHandler<ipmid_callback_t> final : public HandlerBase
+{
+  public:
+    explicit IpmiHandler(const ipmid_callback_t& handler) : handler_(handler)
+    {
+    }
+
+  private:
+    ipmid_callback_t handler_;
+
+    /** @brief call the registered handler with the request
+     *
+     * This is called from the running queue context after it has already
+     * created a request object that contains all the information required to
+     * execute the ipmi command. This function will return the response object
+     * pointer that owns the response object that will ultimately get sent back
+     * to the requester.
+     *
+     * Because this is the legacy variety of IPMI handler, this function does
+     * not really have to do much other than pass the payload to the callback
+     * and return response to the caller.
+     *
+     * @param request a shared_ptr to a Request object
+     *
+     * @return a shared_ptr to a Response object
+     */
+    message::Response::ptr
+        executeCallback(message::Request::ptr request) override
+    {
+        message::Response::ptr response = request->makeResponse();
+        size_t len = request->payload.size();
+        // allocate a big response buffer here
+        response->payload.resize(
+            getChannelMaxTransferSize(request->ctx->channel));
+
+        Cc ccRet{ccSuccess};
+        try
+        {
+            ccRet = handler_(request->ctx->netFn, request->ctx->cmd,
+                             request->payload.data(), response->payload.data(),
+                             &len, nullptr);
+        }
+        catch (const std::exception& e)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "Legacy Handler failed to catch exception",
+                phosphor::logging::entry("EXCEPTION=%s", e.what()),
+                phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
+                phosphor::logging::entry("CMD=%x", request->ctx->cmd));
+            return errorResponse(request, ccUnspecifiedError);
+        }
+        catch (...)
+        {
+            std::exception_ptr eptr;
+            try
+            {
+                eptr = std::current_exception();
+                if (eptr)
+                {
+                    std::rethrow_exception(eptr);
+                }
+            }
+            catch (const std::exception& e)
+            {
+                phosphor::logging::log<phosphor::logging::level::ERR>(
+                    "Handler failed to catch exception",
+                    phosphor::logging::entry("EXCEPTION=%s", e.what()),
+                    phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
+                    phosphor::logging::entry("CMD=%x", request->ctx->cmd));
+                return errorResponse(request, ccUnspecifiedError);
+            }
+        }
+        response->cc = ccRet;
+        response->payload.resize(len);
+        return response;
+    }
+};
+
+/**
+ * @brief create a legacy IPMI handler class and return a shared_ptr
+ *
+ * The queue uses a map of pointers to do the lookup. This function returns the
+ * shared_ptr that owns the Handler object.
+ *
+ * This is called internally via the ipmi_register_callback function.
+ *
+ * @param handler the function pointer to the callback
+ *
+ * @return A shared_ptr to the created handler object
+ */
+inline auto makeLegacyHandler(const ipmid_callback_t& handler)
+{
+    HandlerBase::ptr ptr(new IpmiHandler<ipmid_callback_t>(handler));
+    return ptr;
+}
+
+#endif // ALLOW_DEPRECATED_API
+
+/**
+ * @brief create an IPMI handler class and return a shared_ptr
+ *
+ * The queue uses a map of pointers to do the lookup. This function returns the
+ * shared_ptr that owns the Handler object.
+ *
+ * This is called internally via the ipmi::registerHandler function.
+ *
+ * @param handler the function pointer to the callback
+ *
+ * @return A shared_ptr to the created handler object
+ */
+template <typename Handler>
+inline auto makeHandler(Handler&& handler)
+{
+    HandlerBase::ptr ptr(
+        new IpmiHandler<Handler>(std::forward<Handler>(handler)));
+    return ptr;
+}
+
+} // namespace ipmi