| /** |
| * 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(); |
| 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 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, 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(); |
| 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, handlerCtx); |
| } |
| 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 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(); |
| 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->cmd, request->payload.data(), |
| response->payload.data(), &len); |
| } |
| 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 (...) |
| { |
| 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, |
| 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 ipmi |