/** * 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 #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 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; 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 inputArgs; if constexpr (std::tuple_size::value > 0) { if constexpr (std::is_same, 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( "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( "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 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( "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( "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 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( "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( "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(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 ipmi