| /** |
| * 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 <cxxabi.h> |
| |
| #include <boost/asio/spawn.hpp> |
| #include <boost/callable_traits.hpp> |
| #include <ipmid/api-types.hpp> |
| #include <ipmid/message.hpp> |
| #include <phosphor-logging/log.hpp> |
| #include <user_channel/channel_layer.hpp> |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <exception> |
| #include <memory> |
| #include <optional> |
| #include <stdexcept> |
| #include <tuple> |
| #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(); |
| response->cc = cc; |
| response->pack(args...); |
| return response; |
| } |
| static inline message::Response::ptr |
| errorResponse(message::Request::ptr request, ipmi::Cc cc) |
| { |
| message::Response::ptr response = request->makeResponse(); |
| response->cc = cc; |
| return response; |
| } |
| |
| /** @brief Exception extension that allows setting an IPMI return code */ |
| class HandlerCompletion |
| { |
| public: |
| HandlerCompletion(Cc cc) noexcept : cc(cc) {} |
| |
| Cc code() const noexcept |
| { |
| return cc; |
| } |
| |
| private: |
| Cc cc; |
| }; |
| |
| /** @brief Exception extension that allows setting an IPMI return code and |
| * printing out a logged error */ |
| class HandlerException : public HandlerCompletion, public std::runtime_error |
| { |
| public: |
| HandlerException(Cc cc, const char* what) : |
| HandlerCompletion(cc), std::runtime_error(what) |
| {} |
| HandlerException(Cc cc, const std::string& what) : |
| HandlerException(cc, what.c_str()) |
| {} |
| }; |
| |
| static inline const char* currentExceptionType() |
| { |
| int status; |
| return abi::__cxa_demangle(abi::__cxa_current_exception_type()->name(), 0, |
| 0, &status); |
| } |
| |
| /** |
| * @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>; |
| |
| virtual ~HandlerBase() = default; |
| |
| /** @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; |
| request->payload.trailingOk = false; |
| 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. |
| */ |
| ResultType result; |
| try |
| { |
| 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); |
| } |
| |
| // execute the registered callback function and get the |
| // ipmi::RspType<> |
| result = std::apply(handler_, *inputArgs); |
| } |
| catch (const HandlerException& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Handler produced exception", |
| phosphor::logging::entry("CC=%x", e.code()), |
| 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, e.code()); |
| } |
| 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 (const HandlerCompletion& c) |
| { |
| return errorResponse(request, c.code()); |
| } |
| catch (...) |
| { |
| const char* what = currentExceptionType(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Handler failed to catch exception", |
| phosphor::logging::entry("EXCEPTION=%s", 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 |
| static constexpr size_t maxLegacyBufferSize = 64 * 1024; |
| /** |
| * @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, void* ctx = nullptr) : |
| handler_(handler), handlerCtx(ctx) |
| {} |
| |
| private: |
| ipmid_callback_t handler_; |
| void* handlerCtx; |
| |
| /** @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(); |
| // allocate a big response buffer here |
| response->payload.resize(maxLegacyBufferSize); |
| |
| size_t len = request->payload.size() - request->payload.rawIndex; |
| Cc ccRet{ccSuccess}; |
| try |
| { |
| ccRet = |
| handler_(request->ctx->netFn, request->ctx->cmd, |
| request->payload.data() + request->payload.rawIndex, |
| response->payload.data(), &len, handlerCtx); |
| } |
| catch (const HandlerException& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Legacy Handler produced exception", |
| phosphor::logging::entry("CC=%x", e.code()), |
| 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, e.code()); |
| } |
| 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 (const HandlerCompletion& c) |
| { |
| return errorResponse(request, c.code()); |
| } |
| catch (...) |
| { |
| const char* what = currentExceptionType(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Handler failed to catch exception", |
| phosphor::logging::entry("EXCEPTION=%s", 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 Legacy IPMI OEM handler class |
| * |
| * Legacy IPMI OEM 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<oem::Handler> final : public HandlerBase |
| { |
| public: |
| explicit IpmiHandler(const oem::Handler& handler) : handler_(handler) {} |
| |
| private: |
| oem::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 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(); |
| // allocate a big response buffer here |
| response->payload.resize(maxLegacyBufferSize); |
| |
| size_t len = request->payload.size() - request->payload.rawIndex; |
| Cc ccRet{ccSuccess}; |
| try |
| { |
| ccRet = |
| handler_(request->ctx->cmd, |
| request->payload.data() + request->payload.rawIndex, |
| response->payload.data(), &len); |
| } |
| catch (const HandlerException& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Legacy OEM Handler produced exception", |
| phosphor::logging::entry("CC=%x", e.code()), |
| 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, e.code()); |
| } |
| catch (const std::exception& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Legacy OEM 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 (const HandlerCompletion& c) |
| { |
| return errorResponse(request, c.code()); |
| } |
| catch (...) |
| { |
| const char* what = currentExceptionType(); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Handler failed to catch exception", |
| phosphor::logging::entry("EXCEPTION=%s", 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, |
| void* ctx = nullptr) |
| { |
| HandlerBase::ptr ptr(new IpmiHandler<ipmid_callback_t>(handler, ctx)); |
| return ptr; |
| } |
| |
| /** |
| * @brief create a legacy IPMI OEM 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 Router::registerHandler method. |
| * |
| * @param handler the function pointer to the callback |
| * |
| * @return A shared_ptr to the created handler object |
| */ |
| inline auto makeLegacyHandler(oem::Handler&& handler) |
| { |
| HandlerBase::ptr ptr( |
| new IpmiHandler<oem::Handler>(std::forward<oem::Handler>(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 impl |
| { |
| |
| // IPMI command handler registration implementation |
| bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv, |
| ::ipmi::HandlerBase::ptr handler); |
| bool registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv, |
| ::ipmi::HandlerBase::ptr handler); |
| bool registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv, |
| ::ipmi::HandlerBase::ptr handler); |
| |
| } // namespace impl |
| |
| /** |
| * @brief main IPMI handler registration function |
| * |
| * This function should be used to register all new-style IPMI handler |
| * functions. This function just passes the callback to makeHandler, which |
| * creates a new wrapper object that will automatically extract the appropriate |
| * parameters for the callback function as well as pack up the response. |
| * |
| * @param prio - priority at which to register; see api.hpp |
| * @param netFn - the IPMI net function number to register |
| * @param cmd - the IPMI command number to register |
| * @param priv - the IPMI user privilige required for this command |
| * @param handler - the callback function that will handle this request |
| * |
| * @return bool - success of registering the handler |
| */ |
| template <typename Handler> |
| bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv, |
| Handler&& handler) |
| { |
| auto h = ipmi::makeHandler(std::forward<Handler>(handler)); |
| return impl::registerHandler(prio, netFn, cmd, priv, h); |
| } |
| |
| /** |
| * @brief register a IPMI OEM group handler |
| * |
| * From IPMI 2.0 spec Network Function Codes Table (Row 2Ch): |
| * The first data byte position in requests and responses under this network |
| * function identifies the defining body that specifies command functionality. |
| * Software assumes that the command and completion code field positions will |
| * hold command and completion code values. |
| * |
| * The following values are used to identify the defining body: |
| * 00h PICMG - PCI Industrial Computer Manufacturer’s Group. (www.picmg.com) |
| * 01h DMTF Pre-OS Working Group ASF Specification (www.dmtf.org) |
| * 02h Server System Infrastructure (SSI) Forum (www.ssiforum.org) |
| * 03h VITA Standards Organization (VSO) (www.vita.com) |
| * DCh DCMI Specifications (www.intel.com/go/dcmi) |
| * all other Reserved |
| * |
| * When this network function is used, the ID for the defining body occupies |
| * the first data byte in a request, and the second data byte (following the |
| * completion code) in a response. |
| * |
| * @tparam Handler - implicitly specified callback function type |
| * @param prio - priority at which to register; see api.hpp |
| * @param netFn - the IPMI net function number to register |
| * @param cmd - the IPMI command number to register |
| * @param priv - the IPMI user privilige required for this command |
| * @param handler - the callback function that will handle this request |
| * |
| * @return bool - success of registering the handler |
| * |
| */ |
| template <typename Handler> |
| void registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv, |
| Handler&& handler) |
| { |
| auto h = ipmi::makeHandler(handler); |
| impl::registerGroupHandler(prio, group, cmd, priv, h); |
| } |
| |
| /** |
| * @brief register a IPMI OEM IANA handler |
| * |
| * From IPMI spec Network Function Codes Table (Row 2Eh): |
| * The first three data bytes of requests and responses under this network |
| * function explicitly identify the OEM or non-IPMI group that specifies the |
| * command functionality. While the OEM or non-IPMI group defines the |
| * functional semantics for the cmd and remaining data fields, the cmd field |
| * is required to hold the same value in requests and responses for a given |
| * operation in order to be supported under the IPMI message handling and |
| * transport mechanisms. |
| * |
| * When this network function is used, the IANA Enterprise Number for the |
| * defining body occupies the first three data bytes in a request, and the |
| * first three data bytes following the completion code position in a |
| * response. |
| * |
| * @tparam Handler - implicitly specified callback function type |
| * @param prio - priority at which to register; see api.hpp |
| * @param netFn - the IPMI net function number to register |
| * @param cmd - the IPMI command number to register |
| * @param priv - the IPMI user privilige required for this command |
| * @param handler - the callback function that will handle this request |
| * |
| * @return bool - success of registering the handler |
| * |
| */ |
| template <typename Handler> |
| void registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv, |
| Handler&& handler) |
| { |
| auto h = ipmi::makeHandler(handler); |
| impl::registerOemHandler(prio, iana, cmd, priv, h); |
| } |
| |
| } // namespace ipmi |
| |
| #ifdef ALLOW_DEPRECATED_API |
| /** |
| * @brief legacy IPMI handler registration function |
| * |
| * This function should be used to register all legacy IPMI handler |
| * functions. This function just behaves just as the legacy registration |
| * mechanism did, silently replacing any existing handler with a new one. |
| * |
| * @param netFn - the IPMI net function number to register |
| * @param cmd - the IPMI command number to register |
| * @param context - ignored |
| * @param handler - the callback function that will handle this request |
| * @param priv - the IPMI user privilige required for this command |
| */ |
| // [[deprecated("Use ipmi::registerHandler() instead")]] |
| void ipmi_register_callback(ipmi_netfn_t netFn, ipmi_cmd_t cmd, |
| ipmi_context_t context, ipmid_callback_t handler, |
| ipmi_cmd_privilege_t priv); |
| |
| #endif /* ALLOW_DEPRECATED_API */ |