/** * 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 namespace ipmi { struct Context { using ptr = std::shared_ptr; Context() = delete; Context(const Context&) = default; Context& operator=(const Context&) = default; Context(Context&&) = delete; Context& operator=(Context&&) = delete; Context(std::shared_ptr bus, NetFn netFn, uint8_t lun, Cmd cmd, int channel, int userId, uint32_t sessionId, Privilege priv, int rqSA, int hostIdx, boost::asio::yield_context& yield) : bus(bus), netFn(netFn), lun(lun), cmd(cmd), channel(channel), userId(userId), sessionId(sessionId), priv(priv), rqSA(rqSA), hostIdx(hostIdx), yield(yield) {} std::shared_ptr bus; // normal IPMI context (what call is this, from whence it came...) NetFn netFn; uint8_t lun; Cmd cmd; int channel; int userId; uint32_t sessionId; Privilege priv; // srcAddr is only set on IPMB requests because // Platform Event Message needs it to determine the incoming format int rqSA; int hostIdx; boost::asio::yield_context yield; }; 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(SecureBuffer&& data) : raw(std::move(data)) {} ~Payload() { if (raw.size() != 0 && std::uncaught_exceptions() == 0 && !trailingOk && !unpackCheck && !unpackError) { lg2::error( "Failed to check request for full unpack: raw size: {RAW_SIZE}", "RAW_SIZE", raw.size()); } } /****************************************************************** * 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 Prepends another payload to this one * * Avoid using this unless absolutely required since it inserts into the * front of the response payload. * * @param p - The payload to prepend * * @retunr int - non-zero on prepend errors */ int prepend(const ipmi::message::Payload& p) { if (bitCount != 0 || p.bitCount != 0) { return 1; } raw.reserve(raw.size() + p.raw.size()); raw.insert(raw.begin(), p.raw.begin(), p.raw.end()); return 0; } /****************************************************************** * 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; SecureBuffer raw; size_t rawIndex = 0; bool trailingOk = true; bool unpackCheck = false; 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); } /** * @brief Prepends another payload to this one * * Avoid using this unless absolutely required since it inserts into the * front of the response payload. * * @param p - The payload to prepend * * @retunr int - non-zero on prepend errors */ int prepend(const ipmi::message::Payload& p) { return payload.prepend(p); } 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, SecureBuffer&& 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) { // not all bits were consumed by requested parameters return ipmi::ccReqDataLenInvalid; } if (!payload.trailingOk) { if (!payload.fullyUnpacked()) { // not all bits were consumed by requested parameters return ipmi::ccReqDataLenInvalid; } } return ipmi::ccSuccess; } /** * @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