blob: 17beb42e03f16c8920326c3c24986d589ef2b086 [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 <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