blob: 030618f262ac78a9de26c60afdae1be6c7b75e09 [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 <cstdint>
#include <ipmid/api-types.hpp>
#include <ipmid/message/types.hpp>
#include <memory>
#include <phosphor-logging/log.hpp>
#include <tuple>
#include <utility>
#include <vector>
namespace ipmi
{
struct Context
{
using ptr = std::shared_ptr<Context>;
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 <typename A>
struct UnpackSingle;
template <typename T>
using UnpackSingle_t = UnpackSingle<utility::TypeIdDowncast_t<T>>;
template <typename A>
struct PackSingle;
template <typename T>
using PackSingle_t = PackSingle<utility::TypeIdDowncast_t<T>>;
// 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<uint8_t>&& data) :
raw(std::move(data)), unpackCheck(false)
{
}
~Payload()
{
using namespace phosphor::logging;
if (trailingOk && !unpackCheck && !fullyUnpacked())
{
log<level::ERR>("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 <typename T>
void append(T* begin, T* end)
{
static_assert(
std::is_same_v<utility::TypeIdDowncast_t<T>, int8_t> ||
std::is_same_v<utility::TypeIdDowncast_t<T>, uint8_t> ||
std::is_same_v<utility::TypeIdDowncast_t<T>, 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<const uint8_t*>(begin),
reinterpret_cast<const uint8_t*>(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<details::bitStreamSize> 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<size_t>(CHAR_BIT), bitCount);
retVal = static_cast<uint8_t>(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 <typename Arg, typename... Args>
int pack(Arg&& arg, Args&&... args)
{
int packRet =
details::PackSingle_t<Arg>::op(*this, std::forward<Arg>(arg));
if (packRet)
{
return packRet;
}
packRet = pack(std::forward<Args>(args)...);
drain();
return packRet;
}
/******************************************************************
* 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 <typename T>
auto pop(size_t count)
{
static_assert(
std::is_same_v<utility::TypeIdDowncast_t<T>, int8_t> ||
std::is_same_v<utility::TypeIdDowncast_t<T>, uint8_t> ||
std::is_same_v<utility::TypeIdDowncast_t<T>, 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<T*>(raw.data() + rawIndex),
reinterpret_cast<T*>(raw.data() + rawIndex + count));
rawIndex += count;
return range;
}
unpackError = true;
return std::make_tuple(reinterpret_cast<T*>(NULL),
reinterpret_cast<T*>(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<details::bitStreamSize> 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<uint8_t>();
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 <typename Arg, typename... Args>
int unpack(Arg&& arg, Args&&... args)
{
int unpackRet =
details::UnpackSingle_t<Arg>::op(*this, std::forward<Arg>(arg));
if (unpackRet)
{
unpackError = true;
return unpackRet;
}
return unpack(std::forward<Args>(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 <typename... Types>
int unpack(std::tuple<Types...>& t)
{
// roll back checkpoint so that unpacking a tuple is atomic
size_t priorBitCount = bitCount;
size_t priorIndex = rawIndex;
fixed_uint_t<details::bitStreamSize> 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<details::bitStreamSize> bitStream;
size_t bitCount = 0;
std::vector<uint8_t> 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<Response>;
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 <typename... Args>
int pack(Args&&... args)
{
return payload.pack(std::forward<Args>(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 <typename... Types>
int pack(std::tuple<Types...>& 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<Request>;
explicit Request(Context::ptr context, std::vector<uint8_t>&& d) :
payload(std::forward<std::vector<uint8_t>>(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 <typename... Args>
int unpack(Args&&... args)
{
int unpackRet = payload.unpack(std::forward<Args>(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 <typename... Types>
int unpack(std::tuple<Types...>& 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<Response>(ctx);
}
Payload payload;
Context::ptr ctx;
};
} // namespace message
} // namespace ipmi
// include packing and unpacking of types
#include <ipmid/message/pack.hpp>
#include <ipmid/message/unpack.hpp>