/** * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ALLOW_DEPRECATED_API #include #include #endif /* ALLOW_DEPRECATED_API */ namespace ipmi { template 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; /** @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 class IpmiHandler final : public HandlerBase { public: explicit IpmiHandler(Handler&& handler) : handler_(std::forward(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; using InputArgsType = typename utility::DecayTuple::type; using UnpackArgsType = typename utility::StripFirstArgs< utility::NonIpmiArgsCount::size(), InputArgsType>::type; using ResultType = boost::callable_traits::return_type_t; 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 inputArgs; if constexpr (std::tuple_size::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( "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( "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( "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 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( "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( "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( "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 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( "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( "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( "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(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(std::forward(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 inline auto makeHandler(Handler&& handler) { HandlerBase::ptr ptr( new IpmiHandler(std::forward(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 bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv, Handler&& handler) { auto h = ipmi::makeHandler(std::forward(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 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 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 */