summaryrefslogtreecommitdiffstats
path: root/include/ipmid/handler.hpp
diff options
context:
space:
mode:
authorVernon Mauery <vernon.mauery@linux.intel.com>2018-10-08 12:05:16 -0700
committerVernon Mauery <vernon.mauery@linux.intel.com>2019-02-25 14:27:21 -0800
commite7329c71f3d22e010c38a7f738e81ab78330038e (patch)
tree8cb355cbde96d7a341f1c707c7f0e8c334f88e58 /include/ipmid/handler.hpp
parent1bb0c7fc55b21bb40b7afb567f2f938f15411ca1 (diff)
downloadphosphor-host-ipmid-e7329c71f3d22e010c38a7f738e81ab78330038e.tar.gz
phosphor-host-ipmid-e7329c71f3d22e010c38a7f738e81ab78330038e.zip
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>
Diffstat (limited to 'include/ipmid/handler.hpp')
-rw-r--r--include/ipmid/handler.hpp396
1 files changed, 396 insertions, 0 deletions
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
OpenPOWER on IntegriCloud