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 <vernon.mauery@linux.intel.com>
diff --git a/include/Makefile.am b/include/Makefile.am
index 50f54d0..4c0e899 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -1,4 +1,11 @@
 nobase_include_HEADERS = \
+	ipmid/api.hpp \
+	ipmid/handler.hpp \
+	ipmid/message.hpp \
+	ipmid/message/pack.hpp \
+	ipmid/message/types.hpp \
+	ipmid/message/unpack.hpp \
+	ipmid/registration.hpp \
 	ipmid/api.h \
 	ipmid/iana.hpp \
 	ipmid/oemopenbmc.hpp \
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 <ipmid/iana.hpp>
+#include <ipmid/message/types.hpp>
+#include <optional>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+/* 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 <typename... RetTypes>
+using RspType = std::tuple<ipmi::Cc, std::optional<std::tuple<RetTypes...>>>;
+
+/**
+ * @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 <typename... Args>
+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 <typename... Args>
+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<boost::asio::io_service> getIoService();
+
+// any client can interact with the main sdbus
+std::shared_ptr<sdbusplus::asio::connection> 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 <typename WorkFn>
+static inline void post_work(WorkFn work)
+{
+    getIoService()->post(std::forward<WorkFn>(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 <algorithm>
+#include <boost/asio/spawn.hpp>
+#include <boost/callable_traits.hpp>
+#include <cstdint>
+#include <exception>
+#include <ipmid/api.hpp>
+#include <ipmid/message.hpp>
+#include <memory>
+#include <optional>
+#include <phosphor-logging/log.hpp>
+#include <tuple>
+#include <user_channel/channel_layer.hpp>
+#include <utility>
+
+#ifdef ALLOW_DEPRECATED_API
+#include <ipmid/api.h>
+
+#include <ipmid/oemrouter.hpp>
+#endif /* ALLOW_DEPRECATED_API */
+
+namespace ipmi
+{
+
+template <typename... Args>
+static inline message::Response::ptr
+    errorResponse(message::Request::ptr request, ipmi::Cc cc, Args&&... args)
+{
+    message::Response::ptr response = request->makeResponse();
+    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<HandlerBase>;
+
+    /** @brief wrap the call to the registered handler with the request
+     *
+     * This is called from the running queue context after it has already
+     * created a request object that contains all the information required to
+     * execute the ipmi command. This function will return the response object
+     * pointer that owns the response object that will ultimately get sent back
+     * to the requester.
+     *
+     * This is a non-virtual function wrapper to the virtualized executeCallback
+     * function that actually does the work. This is required because of how
+     * templates and virtualization work together.
+     *
+     * @param request a shared_ptr to a Request object
+     *
+     * @return a shared_ptr to a Response object
+     */
+    message::Response::ptr call(message::Request::ptr request)
+    {
+        return executeCallback(request);
+    }
+
+  private:
+    /** @brief call the registered handler with the request
+     *
+     * This is called from the running queue context after it has already
+     * created a request object that contains all the information required to
+     * execute the ipmi command. This function will return the response object
+     * pointer that owns the response object that will ultimately get sent back
+     * to the requester.
+     *
+     * @param request a shared_ptr to a Request object
+     *
+     * @return a shared_ptr to a Response object
+     */
+    virtual message::Response::ptr
+        executeCallback(message::Request::ptr request) = 0;
+};
+
+/**
+ * @brief Main IPMI handler class
+ *
+ * New IPMI handlers will resolve into this class, which will read the signature
+ * of the registering function, attempt to extract the appropriate arguments
+ * from a request, pass the arguments to the function, and then pack the
+ * response of the function back into an IPMI response.
+ */
+template <typename Handler>
+class IpmiHandler final : public HandlerBase
+{
+  public:
+    explicit IpmiHandler(Handler&& handler) :
+        handler_(std::forward<Handler>(handler))
+    {
+    }
+
+  private:
+    Handler handler_;
+
+    /** @brief call the registered handler with the request
+     *
+     * This is called from the running queue context after it has already
+     * created a request object that contains all the information required to
+     * execute the ipmi command. This function will return the response object
+     * pointer that owns the response object that will ultimately get sent back
+     * to the requester.
+     *
+     * Because this is the new variety of IPMI handler, this is the function
+     * that attempts to extract the requested parameters in order to pass them
+     * onto the callback function and then packages up the response into a plain
+     * old vector to pass back to the caller.
+     *
+     * @param request a shared_ptr to a Request object
+     *
+     * @return a shared_ptr to a Response object
+     */
+    message::Response::ptr
+        executeCallback(message::Request::ptr request) override
+    {
+        message::Response::ptr response = request->makeResponse();
+
+        using CallbackSig = boost::callable_traits::args_t<Handler>;
+        using InputArgsType = typename utility::DecayTuple<CallbackSig>::type;
+        using UnpackArgsType = typename utility::StripFirstArgs<
+            utility::NonIpmiArgsCount<InputArgsType>::size(),
+            InputArgsType>::type;
+        using ResultType = boost::callable_traits::return_type_t<Handler>;
+
+        UnpackArgsType unpackArgs;
+        ipmi::Cc unpackError = request->unpack(unpackArgs);
+        if (unpackError != ipmi::ccSuccess)
+        {
+            response->cc = unpackError;
+            return response;
+        }
+        /* callbacks can contain an optional first argument of one of:
+         * 1) boost::asio::yield_context
+         * 2) ipmi::Context::ptr
+         * 3) ipmi::message::Request::ptr
+         *
+         * If any of those is part of the callback signature as the first
+         * argument, it will automatically get packed into the parameter pack
+         * here.
+         *
+         * One more special optional argument is an ipmi::message::Payload.
+         * This argument can be in any position, though logically it makes the
+         * most sense if it is the last. If this class is included in the
+         * handler signature, it will allow for the handler to unpack optional
+         * parameters. For example, the Set LAN Configuration Parameters
+         * command takes variable length (and type) values for each of the LAN
+         * parameters. This means that the  only fixed data is the channel and
+         * parameter selector. All the remaining data can be extracted using
+         * the Payload class and the unpack API available to the Payload class.
+         */
+        std::optional<InputArgsType> inputArgs;
+        if constexpr (std::tuple_size<InputArgsType>::value > 0)
+        {
+            if constexpr (std::is_same<std::tuple_element_t<0, InputArgsType>,
+                                       boost::asio::yield_context>::value)
+            {
+                inputArgs.emplace(std::tuple_cat(
+                    std::forward_as_tuple(*(request->ctx->yield)),
+                    std::move(unpackArgs)));
+            }
+            else if constexpr (std::is_same<
+                                   std::tuple_element_t<0, InputArgsType>,
+                                   ipmi::Context::ptr>::value)
+            {
+                inputArgs.emplace(
+                    std::tuple_cat(std::forward_as_tuple(request->ctx),
+                                   std::move(unpackArgs)));
+            }
+            else if constexpr (std::is_same<
+                                   std::tuple_element_t<0, InputArgsType>,
+                                   ipmi::message::Request::ptr>::value)
+            {
+                inputArgs.emplace(std::tuple_cat(std::forward_as_tuple(request),
+                                                 std::move(unpackArgs)));
+            }
+            else
+            {
+                // no special parameters were requested (but others were)
+                inputArgs.emplace(std::move(unpackArgs));
+            }
+        }
+        else
+        {
+            // no parameters were requested
+            inputArgs = std::move(unpackArgs);
+        }
+        ResultType result;
+        try
+        {
+            // execute the registered callback function and get the
+            // ipmi::RspType<>
+            result = std::apply(handler_, *inputArgs);
+        }
+        catch (const std::exception& e)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "Handler failed to catch exception",
+                phosphor::logging::entry("EXCEPTION=%s", e.what()),
+                phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
+                phosphor::logging::entry("CMD=%x", request->ctx->cmd));
+            return errorResponse(request, ccUnspecifiedError);
+        }
+        catch (...)
+        {
+            std::exception_ptr eptr;
+            try
+            {
+                eptr = std::current_exception();
+                if (eptr)
+                {
+                    std::rethrow_exception(eptr);
+                }
+            }
+            catch (const std::exception& e)
+            {
+                phosphor::logging::log<phosphor::logging::level::ERR>(
+                    "Handler failed to catch exception",
+                    phosphor::logging::entry("EXCEPTION=%s", e.what()),
+                    phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
+                    phosphor::logging::entry("CMD=%x", request->ctx->cmd));
+                return errorResponse(request, ccUnspecifiedError);
+            }
+        }
+
+        response->cc = std::get<0>(result);
+        auto payload = std::get<1>(result);
+        // check for optional payload
+        if (payload)
+        {
+            response->pack(*payload);
+        }
+        return response;
+    }
+};
+
+#ifdef ALLOW_DEPRECATED_API
+/**
+ * @brief Legacy IPMI handler class
+ *
+ * Legacy IPMI handlers will resolve into this class, which will behave the same
+ * way as the legacy IPMI queue, passing in a big buffer for the request and a
+ * big buffer for the response.
+ *
+ * As soon as all the handlers have been rewritten, this class will be marked as
+ * deprecated and eventually removed.
+ */
+template <>
+class IpmiHandler<ipmid_callback_t> final : public HandlerBase
+{
+  public:
+    explicit IpmiHandler(const ipmid_callback_t& handler) : 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<phosphor::logging::level::ERR>(
+                "Legacy Handler failed to catch exception",
+                phosphor::logging::entry("EXCEPTION=%s", e.what()),
+                phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
+                phosphor::logging::entry("CMD=%x", request->ctx->cmd));
+            return errorResponse(request, ccUnspecifiedError);
+        }
+        catch (...)
+        {
+            std::exception_ptr eptr;
+            try
+            {
+                eptr = std::current_exception();
+                if (eptr)
+                {
+                    std::rethrow_exception(eptr);
+                }
+            }
+            catch (const std::exception& e)
+            {
+                phosphor::logging::log<phosphor::logging::level::ERR>(
+                    "Handler failed to catch exception",
+                    phosphor::logging::entry("EXCEPTION=%s", e.what()),
+                    phosphor::logging::entry("NETFN=%x", request->ctx->netFn),
+                    phosphor::logging::entry("CMD=%x", request->ctx->cmd));
+                return errorResponse(request, ccUnspecifiedError);
+            }
+        }
+        response->cc = ccRet;
+        response->payload.resize(len);
+        return response;
+    }
+};
+
+/**
+ * @brief 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<ipmid_callback_t>(handler));
+    return ptr;
+}
+
+#endif // ALLOW_DEPRECATED_API
+
+/**
+ * @brief create an IPMI handler class and return a shared_ptr
+ *
+ * The queue uses a map of pointers to do the lookup. This function returns the
+ * shared_ptr that owns the Handler object.
+ *
+ * This is called internally via the ipmi::registerHandler function.
+ *
+ * @param handler the function pointer to the callback
+ *
+ * @return A shared_ptr to the created handler object
+ */
+template <typename Handler>
+inline auto makeHandler(Handler&& handler)
+{
+    HandlerBase::ptr ptr(
+        new IpmiHandler<Handler>(std::forward<Handler>(handler)));
+    return ptr;
+}
+
+} // namespace ipmi
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 <algorithm>
+#include <boost/asio/spawn.hpp>
+#include <cstdint>
+#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;
+    }
+
+    /**
+     * @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 <typename... Types>
+    int pack(std::tuple<Types...>& 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 <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>
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 <array>
+#include <ipmid/message/types.hpp>
+#include <memory>
+#include <phosphor-logging/log.hpp>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+namespace ipmi
+{
+
+namespace message
+{
+
+namespace details
+{
+
+/**************************************
+ * ipmi return type helpers
+ **************************************/
+
+template <typename NumericType, size_t byteIndex = 0>
+void PackBytes(uint8_t* pointer, const NumericType& i)
+{
+    if constexpr (byteIndex < sizeof(NumericType))
+    {
+        *pointer = static_cast<uint8_t>(i >> (8 * byteIndex));
+        PackBytes<NumericType, byteIndex + 1>(pointer + 1, i);
+    }
+}
+
+template <typename NumericType, size_t byteIndex = 0>
+void PackBytesUnaligned(Payload& p, const NumericType& i)
+{
+    if constexpr (byteIndex < sizeof(NumericType))
+    {
+        p.appendBits(CHAR_BIT, static_cast<uint8_t>(i >> (8 * byteIndex)));
+        PackBytesUnaligned<NumericType, byteIndex + 1>(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 <typename T>
+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<T>(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<T>(out, t);
+        }
+        return 0;
+    }
+};
+
+/** @brief Specialization of PackSingle for std::string
+ *  represented as a UCSD-Pascal style string
+ */
+template <>
+struct PackSingle<std::string>
+{
+    static int op(Payload& p, std::string& t)
+    {
+        // check length first
+        uint8_t len;
+        if (t.length() > std::numeric_limits<decltype(len)>::max())
+        {
+            using namespace phosphor::logging;
+            log<level::ERR>("long string truncated on IPMI message pack");
+            return 1;
+        }
+        len = static_cast<uint8_t>(t.length());
+        PackSingle<uint8_t>::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 <unsigned N>
+struct PackSingle<fixed_uint_t<N>>
+{
+    static int op(Payload& p, fixed_uint_t<N>& 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<size_t>(CHAR_BIT));
+            p.appendBits(appendCount, static_cast<uint8_t>(bits));
+            bits >>= CHAR_BIT;
+            count -= appendCount;
+        }
+        return 0;
+    }
+};
+
+/** @brief Specialization of PackSingle for bool. */
+template <>
+struct PackSingle<bool>
+{
+    static int op(Payload& p, bool& b)
+    {
+        p.appendBits(1, b);
+        return 0;
+    }
+};
+
+/** @brief Specialization of PackSingle for std::bitset<N> */
+template <size_t N>
+struct PackSingle<std::bitset<N>>
+{
+    static int op(Payload& p, std::bitset<N>& 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<uint8_t>(bits));
+            bits >>= CHAR_BIT;
+            count -= appendCount;
+        }
+        return 0;
+    }
+};
+
+/** @brief Specialization of PackSingle for std::array<T, N> */
+template <typename T, size_t N>
+struct PackSingle<std::array<T, N>>
+{
+    static int op(Payload& p, std::array<T, N>& t)
+    {
+        int ret = 0;
+        for (auto& v : t)
+        {
+            int ret = PackSingle<T>::op(p, v);
+            if (ret)
+            {
+                break;
+            }
+        }
+        return ret;
+    }
+};
+
+/** @brief Specialization of PackSingle for std::vector<T> */
+template <typename T>
+struct PackSingle<std::vector<T>>
+{
+    static int op(Payload& p, std::vector<T>& t)
+    {
+        int ret = 0;
+        for (auto& v : t)
+        {
+            int ret = PackSingle<T>::op(p, v);
+            if (ret)
+            {
+                break;
+            }
+        }
+        return ret;
+    }
+};
+
+/** @brief Specialization of PackSingle for std::vector<uint8_t> */
+template <>
+struct PackSingle<std::vector<uint8_t>>
+{
+    static int op(Payload& p, std::vector<uint8_t>& 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 <bitset>
+#include <boost/multiprecision/cpp_int.hpp>
+#include <ipmid/utility.hpp>
+#include <tuple>
+
+// unsigned fixed-bit sizes
+template <unsigned N>
+using fixed_uint_t =
+    boost::multiprecision::number<boost::multiprecision::cpp_int_backend<
+        N, N, boost::multiprecision::unsigned_magnitude,
+        boost::multiprecision::unchecked, void>>;
+// signed fixed-bit sizes
+template <unsigned N>
+using fixed_int_t =
+    boost::multiprecision::number<boost::multiprecision::cpp_int_backend<
+        N, N, boost::multiprecision::signed_magnitude,
+        boost::multiprecision::unchecked, void>>;
+
+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<uint7_t> or nrFixedBits<decltype(u7)>
+namespace types
+{
+namespace details
+{
+
+template <size_t N>
+struct Size
+{
+    static constexpr size_t value = N;
+};
+
+template <unsigned Bits>
+constexpr auto getNrBits(const fixed_int_t<Bits>&) -> Size<Bits>;
+template <unsigned Bits>
+constexpr auto getNrBits(const fixed_uint_t<Bits>&) -> Size<Bits>;
+template <size_t Bits>
+constexpr auto getNrBits(const std::bitset<Bits>&) -> Size<Bits>;
+
+} // namespace details
+
+/**
+ * @brief mechanism to get N from a type like fixed_int_t<N>
+ *
+ * helper template to extract N from a fixed_(u)int_t variable
+ *
+ * @tparam T - a type of fixed_int_t<N> or fixed_unint_t<N>
+ *
+ * @return size_t - evaluates to a constexpr size_t of N
+ */
+template <typename T>
+constexpr auto nrFixedBits =
+    decltype(details::getNrBits(std::declval<T>()))::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 <array>
+#include <ipmid/message/types.hpp>
+#include <optional>
+#include <string>
+#include <tuple>
+#include <vector>
+
+namespace ipmi
+{
+
+namespace message
+{
+
+namespace details
+{
+
+/**************************************
+ * ipmi return type helpers
+ **************************************/
+
+template <typename NumericType, size_t byteIndex = 0>
+void UnpackBytes(uint8_t* pointer, NumericType& i)
+{
+    if constexpr (byteIndex < sizeof(NumericType))
+    {
+        i |= static_cast<NumericType>(*pointer) << (CHAR_BIT * byteIndex);
+        UnpackBytes<NumericType, byteIndex + 1>(pointer + 1, i);
+    }
+}
+
+template <typename NumericType, size_t byteIndex = 0>
+void UnpackBytesUnaligned(Payload& p, NumericType& i)
+{
+    if constexpr (byteIndex < sizeof(NumericType))
+    {
+        i |= static_cast<NumericType>(p.popBits(CHAR_BIT))
+             << (CHAR_BIT * byteIndex);
+        UnpackBytesUnaligned<NumericType, byteIndex + 1>(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 <typename T>
+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<T>::value)
+        {
+            t = 0;
+            if (p.bitCount)
+            {
+                if (p.fillBits(CHAR_BIT * sizeof(t)))
+                {
+                    return 1;
+                }
+                UnpackBytesUnaligned<T>(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<T>(iter, t);
+                p.rawIndex += sizeof(t);
+            }
+            return 0;
+        }
+        else
+        {
+            if constexpr (utility::is_tuple<T>::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<details::bitStreamSize> 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<std::string>
+{
+    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<char>(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 <unsigned N>
+struct UnpackSingle<fixed_uint_t<N>>
+{
+    static int op(Payload& p, fixed_uint_t<N>& 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<details::bitStreamSize> bitmask = ((1 << count) - 1);
+        t = (p.bitStream & bitmask).convert_to<fixed_uint_t<N>>();
+        p.bitStream >>= count;
+        p.bitCount -= count;
+        return 0;
+    }
+};
+
+/** @brief Specialization of UnpackSingle for bool. */
+template <>
+struct UnpackSingle<bool>
+{
+    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<bool>(p.bitStream & 0x01);
+        // clear bits from stream
+        p.bitStream >>= 1;
+        p.bitCount -= 1;
+        return 0;
+    }
+};
+
+/** @brief Specialization of UnpackSingle for std::bitset<N>
+ */
+template <size_t N>
+struct UnpackSingle<std::bitset<N>>
+{
+    static int op(Payload& p, std::bitset<N>& 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<details::bitStreamSize> bitmask = ((1 << count) - 1);
+        t |= (p.bitStream & bitmask).convert_to<unsigned long long>();
+        p.bitStream >>= count;
+        p.bitCount -= count;
+        return 0;
+    }
+};
+
+/** @brief Specialization of UnpackSingle for std::optional<T> */
+template <typename T>
+struct UnpackSingle<std::optional<T>>
+{
+    static int op(Payload& p, std::optional<T>& 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<details::bitStreamSize> priorBits = p.bitStream;
+        t.emplace();
+        int ret = UnpackSingle<T>::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<T, N> */
+template <typename T, size_t N>
+struct UnpackSingle<std::array<T, N>>
+{
+    static int op(Payload& p, std::array<T, N>& t)
+    {
+        int ret = 0;
+        size_t priorIndex = p.rawIndex;
+        for (auto& v : t)
+        {
+            ret = UnpackSingle<T>::op(p, v);
+            if (ret)
+            {
+                p.rawIndex = priorIndex;
+                t = std::array<T, N>();
+                break;
+            }
+        }
+        return ret;
+    }
+};
+
+/** @brief Specialization of UnpackSingle for std::array<uint8_t> */
+template <size_t N>
+struct UnpackSingle<std::array<uint8_t, N>>
+{
+    static int op(Payload& p, std::array<uint8_t, N>& 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<T> */
+template <typename T>
+struct UnpackSingle<std::vector<T>>
+{
+    static int op(Payload& p, std::vector<T>& t)
+    {
+        int ret = 0;
+        while (p.rawIndex < p.raw.size())
+        {
+            t.emplace_back();
+            ret = UnpackSingle<T>::op(p, t.back());
+            if (ret)
+            {
+                t.pop_back();
+                break;
+            }
+        }
+        return ret;
+    }
+};
+
+/** @brief Specialization of UnpackSingle for std::vector<uint8_t> */
+template <>
+struct UnpackSingle<std::vector<uint8_t>>
+{
+    static int op(Payload& p, std::vector<uint8_t>& 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<Payload>
+{
+    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 <ipmid/api.hpp>
+#include <ipmid/handler.hpp>
+
+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 <typename Handler>
+bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv,
+                     Handler&& handler)
+{
+    auto h = ipmi::makeHandler(std::forward<Handler>(handler));
+    return impl::registerHandler(prio, netFn, cmd, priv, h);
+}
+
+} // 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 <boost/asio/spawn.hpp>
+#include <boost/callable_traits.hpp>
+#include <cstdint>
+#include <map>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <vector>
+
+namespace ipmi
+{
+
+struct Context;
+
+namespace utility
+{
+
+/**
+ * @brief a utility template to extract the args after N from a tuple
+ *
+ * Given a tuple of type <T1, ...TN, TN+1, ...>, provide type = tuple<TN+1,...>
+ */
+template <std::size_t N, typename FirstArg, typename... Rest>
+struct StripFirstArgs;
+
+template <std::size_t N, typename FirstArg, typename... Rest>
+struct StripFirstArgs<N, std::tuple<FirstArg, Rest...>>
+    : StripFirstArgs<N - 1, std::tuple<Rest...>>
+{
+};
+
+template <typename FirstArg, typename... Rest>
+struct StripFirstArgs<0, std::tuple<FirstArg, Rest...>>
+{
+    using type = std::tuple<FirstArg, Rest...>;
+};
+template <std::size_t N>
+struct StripFirstArgs<N, std::tuple<>>
+{
+    using type = std::tuple<>;
+};
+
+/**
+ * @brief a utility template to extract the remaining args from a tuple
+ *
+ * Given a tuple of type <T1, T2,...>, provide type = tuple<T2,...>
+ */
+template <typename T>
+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 <typename FirstArg, typename... Rest>
+struct NonIpmiArgsCount;
+
+template <>
+struct NonIpmiArgsCount<std::tuple<>>
+{
+    constexpr static std::size_t size()
+    {
+        return 0;
+    }
+};
+template <typename FirstArg, typename... OtherArgs>
+struct NonIpmiArgsCount<std::tuple<FirstArg, OtherArgs...>>
+{
+    constexpr static std::size_t size()
+    {
+        if constexpr (std::is_same<FirstArg, ipmi::Context>::value ||
+                      std::is_same<FirstArg, boost::asio::yield_context>::value)
+        {
+            return 1 + NonIpmiArgsCount<std::tuple<OtherArgs...>>::size();
+        }
+        else
+        {
+            return NonIpmiArgsCount<std::tuple<OtherArgs...>>::size();
+        }
+    }
+};
+
+/**
+ * @brief a utility template to find the type of the first arg
+ *
+ * Given a tuple, provide the type of the first element
+ */
+template <typename T>
+struct GetFirstArg
+{
+    using type = void;
+};
+
+template <typename FirstArg, typename... Rest>
+struct GetFirstArg<std::tuple<FirstArg, Rest...>>
+{
+    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 <typename... Args>
+struct DecayTuple;
+
+template <typename... Args>
+struct DecayTuple<std::tuple<Args...>>
+{
+    using type = std::tuple<typename std::decay<Args>::type...>;
+};
+
+/** @brief Convert T[N] to T* if is_same<Tbase,T>
+ *
+ *  @tparam Tbase - The base type expected.
+ *  @tparam T - The type to convert.
+ */
+template <typename Tbase, typename T>
+using ArrayToPtr_t = typename std::conditional_t<
+    std::is_array<T>::value,
+    std::conditional_t<std::is_same<Tbase, std::remove_extent_t<T>>::value,
+                       std::add_pointer_t<std::remove_extent_t<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 <typename T>
+struct DowncastMembers
+{
+    using type = T;
+};
+template <typename... Args>
+struct DowncastMembers<std::pair<Args...>>
+{
+    using type = std::pair<utility::ArrayToPtr_t<
+        char, std::remove_cv_t<std::remove_reference_t<Args>>>...>;
+};
+
+template <typename... Args>
+struct DowncastMembers<std::tuple<Args...>>
+{
+    using type = std::tuple<utility::ArrayToPtr_t<
+        char, std::remove_cv_t<std::remove_reference_t<Args>>>...>;
+};
+
+template <typename T>
+using DowncastMembers_t = typename DowncastMembers<T>::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 <typename T>
+struct TypeIdDowncast
+{
+    using type = utility::ArrayToPtr_t<
+        char, DowncastMembers_t<std::remove_cv_t<std::remove_reference_t<T>>>>;
+};
+
+template <typename T>
+using TypeIdDowncast_t = typename TypeIdDowncast<T>::type;
+
+/** @brief Detect if a type is a tuple
+ *
+ */
+template <typename>
+struct is_tuple : std::false_type
+{
+};
+
+template <typename... T>
+struct is_tuple<std::tuple<T...>> : std::true_type
+{
+};
+
+} // namespace utility
+
+} // namespace ipmi