blob: 62fcbe95fe64be590336c16de3a471480db01241 [file] [log] [blame]
/**
* 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 */