From e7329c71f3d22e010c38a7f738e81ab78330038e Mon Sep 17 00:00:00 2001 From: Vernon Mauery Date: Mon, 8 Oct 2018 12:05:16 -0700 Subject: ipmid: Compiler-generated unpacking and packing of messages handler.hpp has the templated wrapping bits for ipmi command handler callbacks implemented. message.hpp has the serialization/deserialization of the ipmi data stream into packed tuples for functions. message/pack.hpp and message/unpack.hpp contain the actual serialization and deserialization of types. Change-Id: If997f8768c8488ab6ac022526a5ef9a1bce57fcb Signed-off-by: Vernon Mauery --- include/ipmid/api.hpp | 243 +++++++++++++++ include/ipmid/handler.hpp | 396 ++++++++++++++++++++++++ include/ipmid/message.hpp | 633 +++++++++++++++++++++++++++++++++++++++ include/ipmid/message/pack.hpp | 222 ++++++++++++++ include/ipmid/message/types.hpp | 110 +++++++ include/ipmid/message/unpack.hpp | 340 +++++++++++++++++++++ include/ipmid/registration.hpp | 327 ++++++++++++++++++++ include/ipmid/utility.hpp | 210 +++++++++++++ 8 files changed, 2481 insertions(+) create mode 100644 include/ipmid/api.hpp create mode 100644 include/ipmid/handler.hpp create mode 100644 include/ipmid/message.hpp create mode 100644 include/ipmid/message/pack.hpp create mode 100644 include/ipmid/message/types.hpp create mode 100644 include/ipmid/message/unpack.hpp create mode 100644 include/ipmid/registration.hpp create mode 100644 include/ipmid/utility.hpp (limited to 'include/ipmid') diff --git a/include/ipmid/api.hpp b/include/ipmid/api.hpp new file mode 100644 index 0000000..47dc04d --- /dev/null +++ b/include/ipmid/api.hpp @@ -0,0 +1,243 @@ +/* + * 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 + +#define ALLOW_DEPRECATED_API 1 + +#include +#include +#include +#include +#include + +/* NOTE: + * + * This is intended for native C++ use. For the legacy C api, include + * ipmid-api.h for a reduced functionality. Note that the C api is now marked + * as deprecated and will be removed once all the internal users of it have + * been updated to use the new C++ api. + */ + +namespace ipmi +{ + +using Iana = oem::Number; + +using Group = uint8_t; +constexpr Group groupPICMG = 0x00; +constexpr Group groupDMTG = 0x01; +constexpr Group groupSSI = 0x02; +constexpr Group groupVSO = 0x03; +constexpr Group groupDCMI = 0xDC; + +/* + * Set the priority as the lowest number that is necessary so + * it is possible that others can override it if desired. + * This may be linked to what level of integration the handler + * is being created at. + */ +constexpr int prioOpenBmcBase = 10; +constexpr int prioOemBase = 20; +constexpr int prioOdmBase = 30; +constexpr int prioCustomBase = 40; +constexpr int prioMax = 50; + +/* + * Channel IDs pulled from the IPMI 2.0 specification + */ +constexpr int channelPrimaryIpmb = 0x00; +// 0x01-0x0B Implementation specific +// Implementation specific channel numbers are specified +// by a configuration file external to ipmid +// 0x0C-0x0D reserved +constexpr int channelCurrentIface = 0x0E; // 'Present I/F' +constexpr int channelSystemIface = 0x0F; + +/* + * Specifies the minimum privilege level required to execute the command + * This means the command can be executed at a given privilege level or higher + * privilege level. Those commands which can be executed via system interface + * only should use SYSTEM_INTERFACE + */ +enum class Privilege : uint8_t +{ + None = 0x00, + Callback, + User, + Operator, + Admin, + Oem, +}; + +// IPMI Net Function number as specified by IPMI V2.0 spec. +using NetFn = uint8_t; + +// IPMI Command for a Net Function number as specified by IPMI V2.0 spec. +using Cmd = uint8_t; + +// ipmi function return the status code +using Cc = uint8_t; + +// These are the command network functions, the response +// network functions are the function + 1. So to determine +// the proper network function which issued the command +// associated with a response, subtract 1. +// Note: these will be left shifted when combined with the LUN +constexpr NetFn netFnChassis = 0x00; +constexpr NetFn netFnBridge = 0x02; +constexpr NetFn netFnSensor = 0x04; +constexpr NetFn netFnApp = 0x06; +constexpr NetFn netFnFirmware = 0x08; +constexpr NetFn netFnStorage = 0x0A; +constexpr NetFn netFnTransport = 0x0C; +// reserved 0Eh..28h +constexpr NetFn netFnGroup = 0x2C; +constexpr NetFn netFnOem = 0x2E; +constexpr NetFn netFnOemOne = 0x30; +constexpr NetFn netFnOemTwo = 0x32; +constexpr NetFn netFnOemThree = 0x34; +constexpr NetFn netFnOemFour = 0x36; +constexpr NetFn netFnOemFive = 0x38; +constexpr NetFn netFnOemSix = 0x3A; +constexpr NetFn netFnOemSeven = 0x3C; +constexpr NetFn netFnOemEight = 0x3E; + +// IPMI commands for net functions. Callbacks using this should be careful to +// parse arguments to the sub-functions and can take advantage of the built-in +// message handling mechanism to create custom routing +constexpr Cmd cmdWildcard = 0xFF; + +// IPMI standard completion codes specified by the IPMI V2.0 spec. +// +// This might have been an enum class, but that would make it hard for +// OEM- and command-specific completion codes to be added elsewhere. +// +// Custom completion codes can be defined in individual modules for +// command specific errors in the 0x80-0xBE range +// +// Alternately, OEM completion codes are in the 0x01-0x7E range +constexpr Cc ccSuccess = 0x00; +constexpr Cc ccBusy = 0xC0; +constexpr Cc ccInvalidCommand = 0xC1; +constexpr Cc ccInvalidCommandOnLun = 0xC2; +constexpr Cc ccTimeout = 0xC2; +constexpr Cc ccOutOfSpace = 0xC2; +constexpr Cc ccInvalidReservationId = 0xC5; +constexpr Cc ccReqDataTruncated = 0xC6; +constexpr Cc ccReqDataLenInvalid = 0xC7; +constexpr Cc ccReqDataLenExceeded = 0xC8; +constexpr Cc ccParmOutOfRange = 0xC9; +constexpr Cc ccRetBytesUnavailable = 0xCA; +constexpr Cc ccSensorInvalid = 0xCB; +constexpr Cc ccInvalidFieldRequest = 0xCC; +constexpr Cc ccIllegalCommand = 0xCD; +constexpr Cc ccResponseError = 0xCE; +constexpr Cc ccDuplicateRequest = 0xCF; +constexpr Cc ccCmdFailSdrMode = 0xD0; +constexpr Cc ccCmdFailFwUpdMode = 0xD1; +constexpr Cc ccCmdFailInitAgent = 0xD2; +constexpr Cc ccDestinationUnavailable = 0xD3; +constexpr Cc ccInsufficientPrivilege = 0xD4; +constexpr Cc ccCommandNotAvailable = 0xD5; +constexpr Cc ccCommandDisabled = 0xD6; +constexpr Cc ccUnspecifiedError = 0xFF; + +/* ipmi often has two return types: + * 1. Failure: CC is non-zero; no trailing data + * 2. Success: CC is zero; trailing data (usually a fixed type) + * + * using ipmi::response(cc, ...), it will automatically always pack + * the correct type for the response without having to explicitly type out all + * the parameters that the function would return. + * + * To enable this feature, you just define the ipmi function as returning an + * ipmi::RspType which has the optional trailing data built in, with your types + * defined as parameters. + */ + +template +using RspType = std::tuple>>; + +/** + * @brief helper function to create an IPMI response tuple + * + * IPMI handlers all return a tuple with two parts: a completion code and an + * optional tuple containing the rest of the data to return. This helper + * function makes it easier by constructing that out of an arbitrary number of + * arguments. + * + * @param cc - the completion code for the response + * @param args... - the optional list of values to return + * + * @return a standard IPMI return type (as described above) + */ +template +static inline auto response(ipmi::Cc cc, Args&&... args) +{ + return std::make_tuple(cc, std::make_optional(std::make_tuple(args...))); +} +static inline auto response(ipmi::Cc cc) +{ + return std::make_tuple(cc, std::nullopt); +} + +/** + * @brief helper function to create an IPMI success response tuple + * + * IPMI handlers all return a tuple with two parts: a completion code and an + * optional tuple containing the rest of the data to return. This helper + * function makes it easier by constructing that out of an arbitrary number of + * arguments. Because it is a success response, this automatically packs + * the completion code, without needing to explicitly pass it in. + * + * @param args... - the optional list of values to return + * + * @return a standard IPMI return type (as described above) + */ +template +static inline auto responseSuccess(Args&&... args) +{ + return std::make_tuple(ipmi::ccSuccess, + std::make_optional(std::make_tuple(args...))); +} +static inline auto responseSuccess() +{ + return std::make_tuple(ipmi::ccSuccess, std::nullopt); +} + +} // namespace ipmi + +// any client can interact with the main asio service +std::shared_ptr getIoService(); + +// any client can interact with the main sdbus +std::shared_ptr getSdBus(); + +/** + * @brief post some work to the async exection queue + * + * The IPMI daemon runs an async exection queue; this allows any function to + * pass in work to be executed in that context + * + * @tparam WorkFn - a function of type void(void) + * @param work - the callback function to be executed + */ +template +static inline void post_work(WorkFn work) +{ + getIoService()->post(std::forward(work)); +} diff --git a/include/ipmid/handler.hpp b/include/ipmid/handler.hpp new file mode 100644 index 0000000..203dcad --- /dev/null +++ b/include/ipmid/handler.hpp @@ -0,0 +1,396 @@ +/** + * 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(); + auto payload = std::make_tuple(cc, args...); + response->pack(payload); + return response; +} +static inline message::Response::ptr + errorResponse(message::Request::ptr request, ipmi::Cc cc) +{ + message::Response::ptr response = request->makeResponse(); + response->pack(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) : handler_(handler) + { + } + + private: + ipmid_callback_t 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->netFn, request->ctx->cmd, + request->payload.data(), response->payload.data(), + &len, nullptr); + } + 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 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) +{ + HandlerBase::ptr ptr(new IpmiHandler(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 diff --git a/include/ipmid/message.hpp b/include/ipmid/message.hpp new file mode 100644 index 0000000..e628ff0 --- /dev/null +++ b/include/ipmid/message.hpp @@ -0,0 +1,633 @@ +/** + * 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 + +namespace ipmi +{ + +struct Context +{ + using ptr = std::shared_ptr; + + Context() = default; + + Context(NetFn netFn, Cmd cmd, int channel, int userId, Privilege priv, + boost::asio::yield_context* yield = nullptr) : + netFn(netFn), + cmd(cmd), channel(channel), userId(userId), priv(priv), yield(yield) + { + } + + // normal IPMI context (what call is this, from whence it came...) + NetFn netFn = 0; + Cmd cmd = 0; + int channel = 0; + int userId = 0; + Privilege priv = Privilege::None; + // if non-null, use this to do blocking asynchronous asio calls + boost::asio::yield_context* yield = nullptr; +}; + +namespace message +{ + +namespace details +{ + +template +struct UnpackSingle; + +template +using UnpackSingle_t = UnpackSingle>; + +template +struct PackSingle; + +template +using PackSingle_t = PackSingle>; + +// size to hold 64 bits plus one (possibly-)partial byte +static constexpr size_t bitStreamSize = ((sizeof(uint64_t) + 1) * CHAR_BIT); + +} // namespace details + +/** + * @brief a payload class that provides a mechanism to pack and unpack data + * + * When a new request is being executed, the Payload class is responsible for + * attempting to unpack all the required arguments from the incoming blob. For + * variable-length functions, it is possible to have function signature have a + * Payload object, which will then allow the remaining data to be extracted as + * needed. + * + * When creating a response, the parameters returned from the callback use a + * newly created payload object to pack all the parameters into a buffer that is + * then returned to the requester. + * + * These interfaces make calls into the message/pack.hpp and message/unpack.hpp + * functions. + */ +struct Payload +{ + Payload() = default; + Payload(const Payload&) = default; + Payload& operator=(const Payload&) = default; + Payload(Payload&&) = default; + Payload& operator=(Payload&&) = default; + + explicit Payload(std::vector&& data) : + raw(std::move(data)), unpackCheck(false) + { + } + + ~Payload() + { + using namespace phosphor::logging; + if (trailingOk && !unpackCheck && !fullyUnpacked()) + { + log("Failed to check request for full unpack"); + } + } + + /****************************************************************** + * raw vector access + *****************************************************************/ + /** + * @brief return the size of the underlying raw buffer + */ + size_t size() const + { + return raw.size(); + } + /** + * @brief resize the underlying raw buffer to a new size + * + * @param sz - new size for the buffer + */ + void resize(size_t sz) + { + raw.resize(sz); + } + /** + * @brief return a pointer to the underlying raw buffer + */ + uint8_t* data() + { + return raw.data(); + } + /** + * @brief return a const pointer to the underlying raw buffer + */ + const uint8_t* data() const + { + return raw.data(); + } + + /****************************************************************** + * Response operations + *****************************************************************/ + /** + * @brief append a series of bytes to the buffer + * + * @tparam T - the type pointer to return; must be compatible to a byte + * + * @param begin - a pointer to the beginning of the series + * @param end - a pointer to the end of the series + */ + template + void append(T* begin, T* end) + { + static_assert( + std::is_same_v, int8_t> || + std::is_same_v, uint8_t> || + std::is_same_v, char>, + "begin and end must be signed or unsigned byte pointers"); + // this interface only allows full-byte access; pack in partial bytes + drain(); + raw.insert(raw.end(), reinterpret_cast(begin), + reinterpret_cast(end)); + } + + /** + * @brief append a series of bits to the buffer + * + * Only the lowest @count order of bits will be appended, with the most + * significant of those bits getting appended first. + * + * @param count - number of bits to append + * @param bits - a byte with count significant bits to append + */ + void appendBits(size_t count, uint8_t bits) + { + // drain whole bytes out + drain(true); + + // add in the new bits as the higher-order bits, filling LSBit first + fixed_uint_t tmp = bits; + tmp <<= bitCount; + bitStream |= tmp; + bitCount += count; + + // drain any whole bytes we have appended + drain(true); + } + + /** + * @brief empty out the bucket and pack it as bytes LSB-first + * + * @param wholeBytesOnly - if true, only the whole bytes will be drained + */ + void drain(bool wholeBytesOnly = false) + { + while (bitCount > 0) + { + uint8_t retVal; + if (bitCount < CHAR_BIT) + { + if (wholeBytesOnly) + { + break; + } + } + size_t bitsOut = std::min(static_cast(CHAR_BIT), bitCount); + retVal = static_cast(bitStream); + raw.push_back(retVal); + bitStream >>= bitsOut; + bitCount -= bitsOut; + } + } + + // base empty pack + int pack() + { + return 0; + } + + /** + * @brief pack arbitrary values (of any supported type) into the buffer + * + * @tparam Arg - the type of the first argument + * @tparam Args - the type of the optional remaining arguments + * + * @param arg - the first argument to pack + * @param args... - the optional remaining arguments to pack + * + * @return int - non-zero on pack errors + */ + template + int pack(Arg&& arg, Args&&... args) + { + int packRet = + details::PackSingle_t::op(*this, std::forward(arg)); + if (packRet) + { + return packRet; + } + packRet = pack(std::forward(args)...); + drain(); + return packRet; + } + + /** + * @brief pack a tuple of values (of any supported type) into the buffer + * + * This will pack the elements of the tuple as if each one was passed in + * individually, as if passed into the above variadic function. + * + * @tparam Types - the implicitly declared list of the tuple element types + * + * @param t - the tuple of values to pack + * + * @return int - non-zero on pack errors + */ + template + int pack(std::tuple& t) + { + return std::apply([this](Types&... args) { return pack(args...); }, t); + } + + /****************************************************************** + * Request operations + *****************************************************************/ + /** + * @brief pop a series of bytes from the raw buffer + * + * @tparam T - the type pointer to return; must be compatible to a byte + * + * @param count - the number of bytes to return + * + * @return - a tuple of pointers (begin,begin+count) + */ + template + auto pop(size_t count) + { + static_assert( + std::is_same_v, int8_t> || + std::is_same_v, uint8_t> || + std::is_same_v, char>, + "T* must be signed or unsigned byte pointers"); + // this interface only allows full-byte access; skip partial bits + if (bitCount) + { + // WARN on unused bits? + discardBits(); + } + if (count <= (raw.size() - rawIndex)) + { + auto range = std::make_tuple( + reinterpret_cast(raw.data() + rawIndex), + reinterpret_cast(raw.data() + rawIndex + count)); + rawIndex += count; + return range; + } + unpackError = true; + return std::make_tuple(reinterpret_cast(NULL), + reinterpret_cast(NULL)); + } + + /** + * @brief fill bit stream with at least count bits for consumption + * + * @param count - number of bit needed + * + * @return - unpackError + */ + bool fillBits(size_t count) + { + // add more bits to the top end of the bitstream + // so we consume bits least-significant first + if (count > (details::bitStreamSize - CHAR_BIT)) + { + unpackError = true; + return unpackError; + } + while (bitCount < count) + { + if (rawIndex < raw.size()) + { + fixed_uint_t tmp = raw[rawIndex++]; + tmp <<= bitCount; + bitStream |= tmp; + bitCount += CHAR_BIT; + } + else + { + // raw has run out of bytes to pop + unpackError = true; + return unpackError; + } + } + return false; + } + + /** + * @brief consume count bits from bitstream (must call fillBits first) + * + * @param count - number of bit needed + * + * @return - count bits from stream + */ + uint8_t popBits(size_t count) + { + if (bitCount < count) + { + unpackError = true; + return 0; + } + // consume bits low-order bits first + auto bits = bitStream.convert_to(); + bits &= ((1 << count) - 1); + bitStream >>= count; + bitCount -= count; + return bits; + } + + /** + * @brief discard all partial bits + */ + void discardBits() + { + bitStream = 0; + bitCount = 0; + } + + /** + * @brief fully reset the unpack stream + */ + void reset() + { + discardBits(); + rawIndex = 0; + unpackError = false; + } + + /** + * @brief check to see if the stream has been fully unpacked + * + * @return bool - true if the stream has been unpacked and has no errors + */ + bool fullyUnpacked() + { + unpackCheck = true; + return raw.size() == rawIndex && bitCount == 0 && !unpackError; + } + + // base empty unpack + int unpack() + { + return 0; + } + + /** + * @brief unpack arbitrary values (of any supported type) from the buffer + * + * @tparam Arg - the type of the first argument + * @tparam Args - the type of the optional remaining arguments + * + * @param arg - the first argument to unpack + * @param args... - the optional remaining arguments to unpack + * + * @return int - non-zero for unpack error + */ + template + int unpack(Arg&& arg, Args&&... args) + { + int unpackRet = + details::UnpackSingle_t::op(*this, std::forward(arg)); + if (unpackRet) + { + unpackError = true; + return unpackRet; + } + return unpack(std::forward(args)...); + } + + /** + * @brief unpack a tuple of values (of any supported type) from the buffer + * + * This will unpack the elements of the tuple as if each one was passed in + * individually, as if passed into the above variadic function. + * + * @tparam Types - the implicitly declared list of the tuple element types + * + * @param t - the tuple of values to unpack + * + * @return int - non-zero on unpack error + */ + template + int unpack(std::tuple& t) + { + // roll back checkpoint so that unpacking a tuple is atomic + size_t priorBitCount = bitCount; + size_t priorIndex = rawIndex; + fixed_uint_t priorBits = bitStream; + + int ret = + std::apply([this](Types&... args) { return unpack(args...); }, t); + if (ret) + { + bitCount = priorBitCount; + bitStream = priorBits; + rawIndex = priorIndex; + } + + return ret; + } + + // partial bytes in the form of bits + fixed_uint_t bitStream; + size_t bitCount = 0; + std::vector raw; + size_t rawIndex = 0; + bool trailingOk = false; + bool unpackCheck = true; + bool unpackError = false; +}; + +/** + * @brief high-level interface to an IPMI response + * + * Make it easy to just pack in the response args from the callback into a + * buffer that goes back to the requester. + */ +struct Response +{ + /* Define all of the basic class operations: + * Not allowed: + * - Default constructor to avoid nullptrs. + * Allowed: + * - Copy operations. + * - Move operations. + * - Destructor. + */ + Response() = delete; + Response(const Response&) = default; + Response& operator=(const Response&) = default; + Response(Response&&) = default; + Response& operator=(Response&&) = default; + ~Response() = default; + + using ptr = std::shared_ptr; + + explicit Response(Context::ptr& context) : + payload(), ctx(context), cc(ccSuccess) + { + } + + /** + * @brief pack arbitrary values (of any supported type) into the payload + * + * @tparam Args - the type of the optional arguments + * + * @param args... - the optional arguments to pack + * + * @return int - non-zero on pack errors + */ + template + int pack(Args&&... args) + { + return payload.pack(std::forward(args)...); + } + + /** + * @brief pack a tuple of values (of any supported type) into the payload + * + * This will pack the elements of the tuple as if each one was passed in + * individually, as if passed into the above variadic function. + * + * @tparam Types - the implicitly declared list of the tuple element types + * + * @param t - the tuple of values to pack + * + * @return int - non-zero on pack errors + */ + template + int pack(std::tuple& t) + { + return payload.pack(t); + } + + Payload payload; + Context::ptr ctx; + Cc cc; +}; + +/** + * @brief high-level interface to an IPMI request + * + * Make it easy to unpack the buffer into the request args for the callback. + */ +struct Request +{ + /* Define all of the basic class operations: + * Not allowed: + * - Default constructor to avoid nullptrs. + * Allowed: + * - Copy operations. + * - Move operations. + * - Destructor. + */ + Request() = delete; + Request(const Request&) = default; + Request& operator=(const Request&) = default; + Request(Request&&) = default; + Request& operator=(Request&&) = default; + ~Request() = default; + + using ptr = std::shared_ptr; + + explicit Request(Context::ptr context, std::vector&& d) : + payload(std::forward>(d)), ctx(context) + { + } + + /** + * @brief unpack arbitrary values (of any supported type) from the payload + * + * @tparam Args - the type of the optional arguments + * + * @param args... - the optional arguments to unpack + * + * @return int - non-zero for unpack error + */ + template + int unpack(Args&&... args) + { + int unpackRet = payload.unpack(std::forward(args)...); + if (unpackRet == ipmi::ccSuccess) + { + if (!payload.trailingOk) + { + if (!payload.fullyUnpacked()) + { + // not all bits were consumed by requested parameters + return ipmi::ccReqDataLenInvalid; + } + payload.unpackCheck = false; + } + } + return unpackRet; + } + + /** + * @brief unpack a tuple of values (of any supported type) from the payload + * + * This will unpack the elements of the tuple as if each one was passed in + * individually, as if passed into the above variadic function. + * + * @tparam Types - the implicitly declared list of the tuple element types + * + * @param t - the tuple of values to unpack + * + * @return int - non-zero on unpack error + */ + template + int unpack(std::tuple& t) + { + return std::apply([this](Types&... args) { return unpack(args...); }, + t); + } + + /** @brief Create a response message that corresponds to this request + * + * @return A shared_ptr to the response message created + */ + Response::ptr makeResponse() + { + return std::make_shared(ctx); + } + + Payload payload; + Context::ptr ctx; +}; + +} // namespace message + +} // namespace ipmi + +// include packing and unpacking of types +#include +#include diff --git a/include/ipmid/message/pack.hpp b/include/ipmid/message/pack.hpp new file mode 100644 index 0000000..e6bbbce --- /dev/null +++ b/include/ipmid/message/pack.hpp @@ -0,0 +1,222 @@ +/** + * 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 + +namespace ipmi +{ + +namespace message +{ + +namespace details +{ + +/************************************** + * ipmi return type helpers + **************************************/ + +template +void PackBytes(uint8_t* pointer, const NumericType& i) +{ + if constexpr (byteIndex < sizeof(NumericType)) + { + *pointer = static_cast(i >> (8 * byteIndex)); + PackBytes(pointer + 1, i); + } +} + +template +void PackBytesUnaligned(Payload& p, const NumericType& i) +{ + if constexpr (byteIndex < sizeof(NumericType)) + { + p.appendBits(CHAR_BIT, static_cast(i >> (8 * byteIndex))); + PackBytesUnaligned(p, i); + } +} + +/** @struct PackSingle + * @brief Utility to pack a single C++ element into a Payload + * + * User-defined types are expected to specialize this template in order to + * get their functionality. + * + * @tparam S - Type of element to pack. + */ +template +struct PackSingle +{ + /** @brief Do the operation to pack element. + * + * @param[in] p - Payload to pack into. + * @param[out] t - The reference to pack item into. + */ + static int op(Payload& p, T& t) + { + // if not on a byte boundary, must pack values LSbit/LSByte first + if (p.bitCount) + { + PackBytesUnaligned(p, t); + } + else + { + // copy in bits to vector.... + p.raw.resize(p.raw.size() + sizeof(T)); + uint8_t* out = p.raw.data() + p.raw.size() - sizeof(T); + PackBytes(out, t); + } + return 0; + } +}; + +/** @brief Specialization of PackSingle for std::string + * represented as a UCSD-Pascal style string + */ +template <> +struct PackSingle +{ + static int op(Payload& p, std::string& t) + { + // check length first + uint8_t len; + if (t.length() > std::numeric_limits::max()) + { + using namespace phosphor::logging; + log("long string truncated on IPMI message pack"); + return 1; + } + len = static_cast(t.length()); + PackSingle::op(p, len); + p.append(t.c_str(), t.c_str() + t.length()); + return 0; + } +}; + +/** @brief Specialization of PackSingle for fixed_uint_t types + */ +template +struct PackSingle> +{ + static int op(Payload& p, fixed_uint_t& t) + { + size_t count = N; + static_assert(N <= (details::bitStreamSize - CHAR_BIT)); + uint64_t bits = t; + while (count > 0) + { + size_t appendCount = std::min(count, static_cast(CHAR_BIT)); + p.appendBits(appendCount, static_cast(bits)); + bits >>= CHAR_BIT; + count -= appendCount; + } + return 0; + } +}; + +/** @brief Specialization of PackSingle for bool. */ +template <> +struct PackSingle +{ + static int op(Payload& p, bool& b) + { + p.appendBits(1, b); + return 0; + } +}; + +/** @brief Specialization of PackSingle for std::bitset */ +template +struct PackSingle> +{ + static int op(Payload& p, std::bitset& t) + { + size_t count = N; + static_assert(N <= (details::bitStreamSize - CHAR_BIT)); + unsigned long long bits = t.to_ullong(); + while (count > 0) + { + size_t appendCount = std::min(count, size_t(CHAR_BIT)); + p.appendBits(appendCount, static_cast(bits)); + bits >>= CHAR_BIT; + count -= appendCount; + } + return 0; + } +}; + +/** @brief Specialization of PackSingle for std::array */ +template +struct PackSingle> +{ + static int op(Payload& p, std::array& t) + { + int ret = 0; + for (auto& v : t) + { + int ret = PackSingle::op(p, v); + if (ret) + { + break; + } + } + return ret; + } +}; + +/** @brief Specialization of PackSingle for std::vector */ +template +struct PackSingle> +{ + static int op(Payload& p, std::vector& t) + { + int ret = 0; + for (auto& v : t) + { + int ret = PackSingle::op(p, v); + if (ret) + { + break; + } + } + return ret; + } +}; + +/** @brief Specialization of PackSingle for std::vector */ +template <> +struct PackSingle> +{ + static int op(Payload& p, std::vector& t) + { + p.raw.reserve(p.raw.size() + t.size()); + p.raw.insert(p.raw.end(), t.begin(), t.end()); + return 0; + } +}; + +} // namespace details + +} // namespace message + +} // namespace ipmi diff --git a/include/ipmid/message/types.hpp b/include/ipmid/message/types.hpp new file mode 100644 index 0000000..b79ddba --- /dev/null +++ b/include/ipmid/message/types.hpp @@ -0,0 +1,110 @@ +/** + * 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 + +// unsigned fixed-bit sizes +template +using fixed_uint_t = + boost::multiprecision::number>; +// signed fixed-bit sizes +template +using fixed_int_t = + boost::multiprecision::number>; + +using uint1_t = fixed_uint_t<1>; +using uint2_t = fixed_uint_t<2>; +using uint3_t = fixed_uint_t<3>; +using uint4_t = fixed_uint_t<4>; +using uint5_t = fixed_uint_t<5>; +using uint6_t = fixed_uint_t<6>; +using uint7_t = fixed_uint_t<7>; +// native uint8_t +using uint9_t = fixed_uint_t<9>; +using uint10_t = fixed_uint_t<10>; +using uint11_t = fixed_uint_t<11>; +using uint12_t = fixed_uint_t<12>; +using uint13_t = fixed_uint_t<13>; +using uint14_t = fixed_uint_t<14>; +using uint15_t = fixed_uint_t<15>; +// native uint16_t +using uint24_t = fixed_uint_t<24>; + +// signed fixed-bit sizes +using int2_t = fixed_int_t<2>; +using int3_t = fixed_int_t<3>; +using int4_t = fixed_int_t<4>; +using int5_t = fixed_int_t<5>; +using int6_t = fixed_int_t<6>; +using int7_t = fixed_int_t<7>; +// native int8_t +using int9_t = fixed_int_t<9>; +using int10_t = fixed_int_t<10>; +using int11_t = fixed_int_t<11>; +using int12_t = fixed_int_t<12>; +using int13_t = fixed_int_t<13>; +using int14_t = fixed_int_t<14>; +using int15_t = fixed_int_t<15>; +// native int16_t +using int24_t = fixed_int_t<24>; + +// bool is more efficient than a uint1_t +using bit = bool; + +// Mechanism for going from uint7_t, int7_t, or std::bitset<7> to 7 bits +// use nrFixedBits or nrFixedBits +namespace types +{ +namespace details +{ + +template +struct Size +{ + static constexpr size_t value = N; +}; + +template +constexpr auto getNrBits(const fixed_int_t&) -> Size; +template +constexpr auto getNrBits(const fixed_uint_t&) -> Size; +template +constexpr auto getNrBits(const std::bitset&) -> Size; + +} // namespace details + +/** + * @brief mechanism to get N from a type like fixed_int_t + * + * helper template to extract N from a fixed_(u)int_t variable + * + * @tparam T - a type of fixed_int_t or fixed_unint_t + * + * @return size_t - evaluates to a constexpr size_t of N + */ +template +constexpr auto nrFixedBits = + decltype(details::getNrBits(std::declval()))::value; + +} // namespace types diff --git a/include/ipmid/message/unpack.hpp b/include/ipmid/message/unpack.hpp new file mode 100644 index 0000000..d96928f --- /dev/null +++ b/include/ipmid/message/unpack.hpp @@ -0,0 +1,340 @@ +/** + * 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 + +namespace ipmi +{ + +namespace message +{ + +namespace details +{ + +/************************************** + * ipmi return type helpers + **************************************/ + +template +void UnpackBytes(uint8_t* pointer, NumericType& i) +{ + if constexpr (byteIndex < sizeof(NumericType)) + { + i |= static_cast(*pointer) << (CHAR_BIT * byteIndex); + UnpackBytes(pointer + 1, i); + } +} + +template +void UnpackBytesUnaligned(Payload& p, NumericType& i) +{ + if constexpr (byteIndex < sizeof(NumericType)) + { + i |= static_cast(p.popBits(CHAR_BIT)) + << (CHAR_BIT * byteIndex); + UnpackBytesUnaligned(p, i); + } +} + +/** @struct UnpackSingle + * @brief Utility to unpack a single C++ element from a Payload + * + * User-defined types are expected to specialize this template in order to + * get their functionality. + * + * @tparam T - Type of element to unpack. + */ +template +struct UnpackSingle +{ + /** @brief Do the operation to unpack element. + * + * @param[in] p - Payload to unpack from. + * @param[out] t - The reference to unpack item into. + */ + static int op(Payload& p, T& t) + { + if constexpr (std::is_fundamental::value) + { + t = 0; + if (p.bitCount) + { + if (p.fillBits(CHAR_BIT * sizeof(t))) + { + return 1; + } + UnpackBytesUnaligned(p, t); + } + else + { + // copy out bits from vector.... + if (p.raw.size() < (p.rawIndex + sizeof(t))) + { + return 1; + } + auto iter = p.raw.data() + p.rawIndex; + t = 0; + UnpackBytes(iter, t); + p.rawIndex += sizeof(t); + } + return 0; + } + else + { + if constexpr (utility::is_tuple::value) + { + bool priorError = p.unpackError; + size_t priorIndex = p.rawIndex; + // more stuff to unroll if partial bytes are out + size_t priorBitCount = p.bitCount; + fixed_uint_t priorBits = p.bitStream; + int ret = p.unpack(t); + if (ret != 0) + { + t = T(); + p.rawIndex = priorIndex; + p.bitStream = priorBits; + p.bitCount = priorBitCount; + p.unpackError = priorError; + } + return 0; + } + } + } +}; + +/** @struct UnpackSingle + * @brief Utility to unpack a single C++ element from a Payload + * + * Specialization to unpack std::string represented as a + * UCSD-Pascal style string + */ +template <> +struct UnpackSingle +{ + static int op(Payload& p, std::string& t) + { + // pop len first + if (p.rawIndex > (p.raw.size() - sizeof(uint8_t))) + { + return 1; + } + uint8_t len = p.raw[p.rawIndex++]; + // check to see that there are n bytes left + auto [first, last] = p.pop(len); + if (first == last) + { + return 1; + } + t.reserve(last - first); + t.insert(0, first, (last - first)); + return 0; + } +}; + +/** @brief Specialization of UnpackSingle for fixed_uint_t types + */ +template +struct UnpackSingle> +{ + static int op(Payload& p, fixed_uint_t& t) + { + static_assert(N <= (details::bitStreamSize - CHAR_BIT)); + constexpr size_t count = N; + // acquire enough bits in the stream to fulfill the Payload + if (p.fillBits(count)) + { + return -1; + } + fixed_uint_t bitmask = ((1 << count) - 1); + t = (p.bitStream & bitmask).convert_to>(); + p.bitStream >>= count; + p.bitCount -= count; + return 0; + } +}; + +/** @brief Specialization of UnpackSingle for bool. */ +template <> +struct UnpackSingle +{ + static int op(Payload& p, bool& b) + { + // acquire enough bits in the stream to fulfill the Payload + if (p.fillBits(1)) + { + return -1; + } + b = static_cast(p.bitStream & 0x01); + // clear bits from stream + p.bitStream >>= 1; + p.bitCount -= 1; + return 0; + } +}; + +/** @brief Specialization of UnpackSingle for std::bitset + */ +template +struct UnpackSingle> +{ + static int op(Payload& p, std::bitset& t) + { + static_assert(N <= (details::bitStreamSize - CHAR_BIT)); + size_t count = N; + // acquire enough bits in the stream to fulfill the Payload + if (p.fillBits(count)) + { + return -1; + } + fixed_uint_t bitmask = ((1 << count) - 1); + t |= (p.bitStream & bitmask).convert_to(); + p.bitStream >>= count; + p.bitCount -= count; + return 0; + } +}; + +/** @brief Specialization of UnpackSingle for std::optional */ +template +struct UnpackSingle> +{ + static int op(Payload& p, std::optional& t) + { + bool priorError = p.unpackError; + size_t priorIndex = p.rawIndex; + // more stuff to unroll if partial bytes are out + size_t priorBitCount = p.bitCount; + fixed_uint_t priorBits = p.bitStream; + t.emplace(); + int ret = UnpackSingle::op(p, *t); + if (ret != 0) + { + t.reset(); + p.rawIndex = priorIndex; + p.bitStream = priorBits; + p.bitCount = priorBitCount; + p.unpackError = priorError; + } + return 0; + } +}; + +/** @brief Specialization of UnpackSingle for std::array */ +template +struct UnpackSingle> +{ + static int op(Payload& p, std::array& t) + { + int ret = 0; + size_t priorIndex = p.rawIndex; + for (auto& v : t) + { + ret = UnpackSingle::op(p, v); + if (ret) + { + p.rawIndex = priorIndex; + t = std::array(); + break; + } + } + return ret; + } +}; + +/** @brief Specialization of UnpackSingle for std::array */ +template +struct UnpackSingle> +{ + static int op(Payload& p, std::array& t) + { + if (p.raw.size() - p.rawIndex < N) + { + t.fill(0); + return -1; + } + // copy out the bytes + std::copy(p.raw.begin() + p.rawIndex, p.raw.begin() + p.rawIndex + N, + t.begin()); + p.rawIndex += N; + return 0; + } +}; + +/** @brief Specialization of UnpackSingle for std::vector */ +template +struct UnpackSingle> +{ + static int op(Payload& p, std::vector& t) + { + int ret = 0; + while (p.rawIndex < p.raw.size()) + { + t.emplace_back(); + ret = UnpackSingle::op(p, t.back()); + if (ret) + { + t.pop_back(); + break; + } + } + return ret; + } +}; + +/** @brief Specialization of UnpackSingle for std::vector */ +template <> +struct UnpackSingle> +{ + static int op(Payload& p, std::vector& t) + { + // copy out the remainder of the message + t.reserve(p.raw.size() - p.rawIndex); + t.insert(t.begin(), p.raw.begin() + p.rawIndex, p.raw.end()); + p.rawIndex = p.raw.size(); + return 0; + } +}; + +/** @brief Specialization of UnpackSingle for Payload */ +template <> +struct UnpackSingle +{ + static int op(Payload& p, Payload& t) + { + // mark that this payload is being included in the args + p.trailingOk = true; + t = p; + // reset the unpacking flags so it can be properly checked + t.trailingOk = false; + t.unpackCheck = true; + t.unpackError = false; + return 0; + } +}; + +} // namespace details + +} // namespace message + +} // namespace ipmi diff --git a/include/ipmid/registration.hpp b/include/ipmid/registration.hpp new file mode 100644 index 0000000..151aca1 --- /dev/null +++ b/include/ipmid/registration.hpp @@ -0,0 +1,327 @@ +/** + * 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 + +namespace ipmi +{ + +namespace impl +{ + +// IPMI command handler registration implementation +bool registerHandler(int prio, NetFn netFn, 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); +} + +} // 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 */ + +// IPMI 2.0 and DCMI 1.5 standard commands, namespaced by NetFn +// OEM and non-standard commands should be defined where they are used +namespace ipmi +{ +namespace app +{ +// 0x00 reserved +constexpr Cmd cmdGetDeviceId = 0x01; +constexpr Cmd cmdColdReset = 0x02; +constexpr Cmd cmdWarmReset = 0x03; +constexpr Cmd cmdGetSelfTestResults = 0x04; +constexpr Cmd cmdManufacturingTestOn = 0x05; +constexpr Cmd cmdSetAcpiPowerState = 0x06; +constexpr Cmd cmdGetAcpiPowerState = 0x07; +constexpr Cmd cmdGetDeviceGuid = 0x08; +constexpr Cmd cmdGetNetFnSupport = 0x09; +constexpr Cmd cmdGetCmdSupport = 0x0A; +constexpr Cmd cmdGetCmdSubFnSupport = 0x0B; +constexpr Cmd cmdGetConfigurableCmds = 0x0C; +constexpr Cmd cmdGetConfigurableCmdSubFns = 0x0D; +// 0x0E-0x21 unassigned +constexpr Cmd cmdResetWatchdogTimer = 0x22; +// 0x23 unassigned +constexpr Cmd cmdSetWatchdogTimer = 0x24; +constexpr Cmd cmdGetWatchdogTimer = 0x25; +// 0x26-0x2D unassigned +constexpr Cmd cmdSetBmcGlobalEnables = 0x2E; +constexpr Cmd cmdGetBmcGlobalEnables = 0x2F; +constexpr Cmd cmdClearMessageFlags = 0x30; +constexpr Cmd cmdGetMessageFlags = 0x31; +constexpr Cmd cmdEnableMessageChannelRcv = 0x32; +constexpr Cmd cmdGetMessage = 0x33; +constexpr Cmd cmdSendMessage = 0x34; +constexpr Cmd cmdReadEventMessageBuffer = 0x35; +constexpr Cmd cmdGetBtIfaceCapabilities = 0x36; +constexpr Cmd cmdGetSystemGuid = 0x37; +constexpr Cmd cmdGetChannelAuthCapabilities = 0x38; +constexpr Cmd cmdGetSessionChallenge = 0x39; +constexpr Cmd cmdActivateSession = 0x3A; +constexpr Cmd cmdSetSessionPrivilegeLevel = 0x3B; +constexpr Cmd cmdCloseSession = 0x3C; +constexpr Cmd cmdGetSessionInfo = 0x3D; +// 0x3E unassigned +constexpr Cmd cmdGetAuthCode = 0x3F; +constexpr Cmd cmdSetChannelAccess = 0x40; +constexpr Cmd cmdGetChannelAccess = 0x41; +constexpr Cmd cmdGetChannelInfoCommand = 0x42; +constexpr Cmd cmdSetUserAccessCommand = 0x43; +constexpr Cmd cmdGetUserAccessCommand = 0x44; +constexpr Cmd cmdSetUserName = 0x45; +constexpr Cmd cmdGetUserNameCommand = 0x46; +constexpr Cmd cmdSetUserPasswordCommand = 0x47; +constexpr Cmd cmdActivatePayload = 0x48; +constexpr Cmd cmdDeactivatePayload = 0x49; +constexpr Cmd cmdGetPayloadActivationStatus = 0x4A; +constexpr Cmd cmdGetPayloadInstanceInfo = 0x4B; +constexpr Cmd cmdSetUserPayloadAccess = 0x4C; +constexpr Cmd cmdGetUserPayloadAccess = 0x4D; +constexpr Cmd cmdGetChannelPayloadSupport = 0x4E; +constexpr Cmd cmdGetChannelPayloadVersion = 0x4F; +constexpr Cmd cmdGetChannelOemPayloadInfo = 0x50; +// 0x51 unassigned +constexpr Cmd cmdMasterWriteRead = 0x52; +// 0x53 unassigned +constexpr Cmd cmdGetChannelCipherSuites = 0x54; +constexpr Cmd cmdSuspendResumePayloadEnc = 0x55; +constexpr Cmd cmdSetChannelSecurityKeys = 0x56; +constexpr Cmd cmdGetSystemIfCapabilities = 0x57; +constexpr Cmd cmdSetSystemInfoParameters = 0x58; +constexpr Cmd cmdGetSystemInfoParameters = 0x59; +// 0x5A-0x5F unassigned +constexpr Cmd cmdSetCommandEnables = 0x60; +constexpr Cmd cmdGetCommandEnables = 0x61; +constexpr Cmd cmdSetCommandSubFnEnables = 0x62; +constexpr Cmd cmdGetCommandSubFnEnables = 0x63; +constexpr Cmd cmdGetOemNetFnIanaSupport = 0x64; +// 0x65-0xff unassigned +} // namespace app + +namespace chassis +{ +constexpr Cmd cmdGetChassisCapabilities = 0x00; +constexpr Cmd cmdGetChassisStatus = 0x01; +constexpr Cmd cmdChassisControl = 0x02; +constexpr Cmd cmdChassisReset = 0x03; +constexpr Cmd cmdChassisIdentify = 0x04; +constexpr Cmd cmdSetChassisCapabilities = 0x05; +constexpr Cmd cmdSetPowerRestorePolicy = 0x06; +constexpr Cmd cmdGetSystemRestartCause = 0x07; +constexpr Cmd cmdSetSystemBootOptions = 0x08; +constexpr Cmd cmdGetSystemBootOptions = 0x09; +constexpr Cmd cmdSetFrontPanelButtonEnables = 0x0A; +constexpr Cmd cmdSetPowerCycleInterval = 0x0B; +// 0x0C-0x0E unassigned +constexpr Cmd cmdGetPohCounter = 0x0F; +// 0x10-0xFF unassigned +} // namespace chassis + +namespace sensor_event +{ +constexpr Cmd cmdSetEventReceiver = 0x00; +constexpr Cmd cmdGetEventReceiver = 0x01; +constexpr Cmd cmdPlatformEvent = 0x02; +// 0x03-0x0F unassigned +constexpr Cmd cmdGetPefCapabilities = 0x10; +constexpr Cmd cmdArmPefPostponeTimer = 0x11; +constexpr Cmd cmdSetPefConfigurationParams = 0x12; +constexpr Cmd cmdGetPefConfigurationParams = 0x13; +constexpr Cmd cmdSetLastProcessedEventId = 0x14; +constexpr Cmd cmdGetLastProcessedEventId = 0x15; +constexpr Cmd cmdAlertImmediate = 0x16; +constexpr Cmd cmdPetAcknowledge = 0x17; +constexpr Cmd cmdGetDeviceSdrInfo = 0x20; +constexpr Cmd cmdGetDeviceSdr = 0x21; +constexpr Cmd cmdReserveDeviceSdrRepository = 0x22; +constexpr Cmd cmdGetSensorReadingFactors = 0x23; +constexpr Cmd cmdSetSensorHysteresis = 0x24; +constexpr Cmd cmdGetSensorHysteresis = 0x25; +constexpr Cmd cmdSetSensorThreshold = 0x26; +constexpr Cmd cmdGetSensorThreshold = 0x27; +constexpr Cmd cmdSetSensorEventEnable = 0x28; +constexpr Cmd cmdGetSensorEventEnable = 0x29; +constexpr Cmd cmdRearmSensorEvents = 0x2A; +constexpr Cmd cmdGetSensorEventStatus = 0x2B; +constexpr Cmd cmdGetSensorReading = 0x2D; +constexpr Cmd cmdSetSensorType = 0x2E; +constexpr Cmd cmdGetSensorType = 0x2F; +constexpr Cmd cmdSetSensorReadingAndEvtSts = 0x30; +// 0x31-0xFF unassigned +} // namespace sensor_event + +namespace storage +{ +// 0x00-0x0F unassigned +constexpr Cmd cmdGetFruInventoryAreaInfo = 0x10; +constexpr Cmd cmdReadFruData = 0x11; +constexpr Cmd cmdWriteFruData = 0x12; +// 0x13-0x1F unassigned +constexpr Cmd cmdGetSdrRepositoryInfo = 0x20; +constexpr Cmd cmdGetSdrRepositoryAllocInfo = 0x21; +constexpr Cmd cmdReserveSdrRepository = 0x22; +constexpr Cmd cmdGetSdr = 0x23; +constexpr Cmd cmdAddSdr = 0x24; +constexpr Cmd cmdPartialAddSdr = 0x25; +constexpr Cmd cmdDeleteSdr = 0x26; +constexpr Cmd cmdClearSdrRepository = 0x27; +constexpr Cmd cmdGetSdrRepositoryTime = 0x28; +constexpr Cmd cmdSetSdrRepositoryTime = 0x29; +constexpr Cmd cmdEnterSdrRepoUpdateMode = 0x2A; +constexpr Cmd cmdExitSdrReposUpdateMode = 0x2B; +constexpr Cmd cmdRunInitializationAgent = 0x2C; +// 0x2D-0x3F unassigned +constexpr Cmd cmdGetSelInfo = 0x40; +constexpr Cmd cmdGetSelAllocationInfo = 0x41; +constexpr Cmd cmdReserveSel = 0x42; +constexpr Cmd cmdGetSelEntry = 0x43; +constexpr Cmd cmdAddSelEntry = 0x44; +constexpr Cmd cmdPartialAddSelEntry = 0x45; +constexpr Cmd cmdDeleteSelEntry = 0x46; +constexpr Cmd cmdClearSel = 0x47; +constexpr Cmd cmdGetSelTime = 0x48; +constexpr Cmd cmdSetSelTime = 0x49; +constexpr Cmd cmdGetAuxiliaryLogStatus = 0x5A; +constexpr Cmd cmdSetAuxiliaryLogStatus = 0x5B; +constexpr Cmd cmdGetSelTimeUtcOffset = 0x5C; +constexpr Cmd cmdSetSelTimeUtcOffset = 0x5D; +// 0x5E-0xFF unassigned +} // namespace storage + +namespace transport +{ +constexpr Cmd cmdSetLanConfigParameters = 0x01; +constexpr Cmd cmdGetLanConfigParameters = 0x02; +constexpr Cmd cmdSuspendBmcArps = 0x03; +constexpr Cmd cmdGetIpUdpRmcpStatistics = 0x04; +constexpr Cmd cmdSetSerialModemConfig = 0x10; +constexpr Cmd cmdGetSerialModemConfig = 0x11; +constexpr Cmd cmdSetSerialModemMux = 0x12; +constexpr Cmd cmdGetTapResponseCodes = 0x13; +constexpr Cmd cmdSetPppUdpProxyTransmitData = 0x14; +constexpr Cmd cmdGetPppUdpProxyTransmitData = 0x15; +constexpr Cmd cmdSendPppUdpProxyPacket = 0x16; +constexpr Cmd cmdGetPppUdpProxyReceiveData = 0x17; +constexpr Cmd cmdSerialModemConnActive = 0x18; +constexpr Cmd cmdCallback = 0x19; +constexpr Cmd cmdSetUserCallbackOptions = 0x1A; +constexpr Cmd cmdGetUserCallbackOptions = 0x1B; +constexpr Cmd cmdSetSerialRoutingMux = 0x1C; +constexpr Cmd cmdSolActivating = 0x20; +constexpr Cmd cmdSetSolConfigParameters = 0x21; +constexpr Cmd cmdGetSolConfigParameters = 0x22; +constexpr Cmd cmdForwardedCommand = 0x30; +constexpr Cmd cmdSetForwardedCommands = 0x31; +constexpr Cmd cmdGetForwardedCommands = 0x32; +constexpr Cmd cmdEnableForwardedCommands = 0x33; +} // namespace transport + +namespace bridge +{ +constexpr Cmd cmdGetBridgeState = 0x00; +constexpr Cmd cmdSetBridgeState = 0x01; +constexpr Cmd cmdGetIcmbAddress = 0x02; +constexpr Cmd cmdSetIcmbAddress = 0x03; +constexpr Cmd cmdSetBridgeProxyAddress = 0x04; +constexpr Cmd cmdGetBridgeStatistics = 0x05; +constexpr Cmd cmdGetIcmbCapabilities = 0x06; +constexpr Cmd cmdClearBridgeStatistics = 0x08; +constexpr Cmd cmdGetBridgeProxyAddress = 0x09; +constexpr Cmd cmdGetIcmbConnectorInfo = 0x0A; +constexpr Cmd cmdGetIcmbConnectionId = 0x0B; +constexpr Cmd cmdSendIcmbConnectionId = 0x0C; +constexpr Cmd cmdPrepareForDiscovery = 0x10; +constexpr Cmd cmdGetAddresses = 0x11; +constexpr Cmd cmdSetDiscovered = 0x12; +constexpr Cmd cmdGetChassisDeviceId = 0x13; +constexpr Cmd cmdSetChassisDeviceId = 0x14; +constexpr Cmd cmdBridgeRequest = 0x20; +constexpr Cmd cmdBridgeMessage = 0x21; +// 0x22-0x2F unassigned +constexpr Cmd cmdGetEventCount = 0x30; +constexpr Cmd cmdSetEventDestination = 0x31; +constexpr Cmd cmdSetEventReceptionState = 0x32; +constexpr Cmd cmdSendIcmbEventMessage = 0x33; +constexpr Cmd cmdGetEventDestination = 0x34; +constexpr Cmd cmdGetEventReceptionState = 0x35; +// 0xC0-0xFE OEM Commands +constexpr Cmd cmdErrorReport = 0xFF; +} // namespace bridge + +namespace dcmi +{ +constexpr Cmd cmdGetDcmiCapabilitiesInfo = 0x01; +constexpr Cmd cmdGetPowerReading = 0x02; +constexpr Cmd cmdGetPowerLimit = 0x03; +constexpr Cmd cmdSetPowerLimit = 0x04; +constexpr Cmd cmdActDeactivatePwrLimit = 0x05; +constexpr Cmd cmdGetAssetTag = 0x06; +constexpr Cmd cmdGetDcmiSensorInfo = 0x07; +constexpr Cmd cmdSetAssetTag = 0x08; +constexpr Cmd cmdGetMgmtCntlrIdString = 0x09; +constexpr Cmd cmdSetMgmtCntlrIdString = 0x0A; +constexpr Cmd cmdSetThermalLimit = 0x0B; +constexpr Cmd cmdGetThermalLimit = 0x0C; +constexpr Cmd cmdGetTemperatureReadings = 0x10; +constexpr Cmd cmdSetDcmiConfigParameters = 0x12; +constexpr Cmd cmdGetDcmiConfigParameters = 0x13; +} // namespace dcmi + +} // namespace ipmi diff --git a/include/ipmid/utility.hpp b/include/ipmid/utility.hpp new file mode 100644 index 0000000..3a36434 --- /dev/null +++ b/include/ipmid/utility.hpp @@ -0,0 +1,210 @@ +/** + * 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 + +namespace ipmi +{ + +struct Context; + +namespace utility +{ + +/** + * @brief a utility template to extract the args after N from a tuple + * + * Given a tuple of type , provide type = tuple + */ +template +struct StripFirstArgs; + +template +struct StripFirstArgs> + : StripFirstArgs> +{ +}; + +template +struct StripFirstArgs<0, std::tuple> +{ + using type = std::tuple; +}; +template +struct StripFirstArgs> +{ + using type = std::tuple<>; +}; + +/** + * @brief a utility template to extract the remaining args from a tuple + * + * Given a tuple of type , provide type = tuple + */ +template +using StripFirstArg = StripFirstArgs<1, T>; + +/** + * @brief a utility template to find the number of non-special arguments + * + * Given a tuple, count the args after the first special args + */ +template +struct NonIpmiArgsCount; + +template <> +struct NonIpmiArgsCount> +{ + constexpr static std::size_t size() + { + return 0; + } +}; +template +struct NonIpmiArgsCount> +{ + constexpr static std::size_t size() + { + if constexpr (std::is_same::value || + std::is_same::value) + { + return 1 + NonIpmiArgsCount>::size(); + } + else + { + return NonIpmiArgsCount>::size(); + } + } +}; + +/** + * @brief a utility template to find the type of the first arg + * + * Given a tuple, provide the type of the first element + */ +template +struct GetFirstArg +{ + using type = void; +}; + +template +struct GetFirstArg> +{ + using type = FirstArg; +}; + +/** + * @brief a utility template to remove const and reference from types + * + * Given a tuple, provide the type of the first element + */ +template +struct DecayTuple; + +template +struct DecayTuple> +{ + using type = std::tuple::type...>; +}; + +/** @brief Convert T[N] to T* if is_same + * + * @tparam Tbase - The base type expected. + * @tparam T - The type to convert. + */ +template +using ArrayToPtr_t = typename std::conditional_t< + std::is_array::value, + std::conditional_t>::value, + std::add_pointer_t>, T>, + T>; + +/** @brief Downcast type submembers. + * + * This allows std::tuple and std::pair members to be downcast to their + * non-const, nonref versions of themselves to limit duplication in template + * specializations + * + * 1. Remove references. + * 2. Remove 'const' and 'volatile'. + * 3. Convert 'char[N]' to 'char*'. + */ +template +struct DowncastMembers +{ + using type = T; +}; +template +struct DowncastMembers> +{ + using type = std::pair>>...>; +}; + +template +struct DowncastMembers> +{ + using type = std::tuple>>...>; +}; + +template +using DowncastMembers_t = typename DowncastMembers::type; + +/** @brief Convert some C++ types to others for 'TypeId' conversion purposes. + * + * Similar C++ types have the same dbus type-id, so 'downcast' those to limit + * duplication in TypeId template specializations. + * + * 1. Remove references. + * 2. Remove 'const' and 'volatile'. + * 3. Convert 'char[N]' to 'char*'. + */ +template +struct TypeIdDowncast +{ + using type = utility::ArrayToPtr_t< + char, DowncastMembers_t>>>; +}; + +template +using TypeIdDowncast_t = typename TypeIdDowncast::type; + +/** @brief Detect if a type is a tuple + * + */ +template +struct is_tuple : std::false_type +{ +}; + +template +struct is_tuple> : std::true_type +{ +}; + +} // namespace utility + +} // namespace ipmi -- cgit v1.2.1