move Intel FW update commands from ipmi-providers

These commands are the old-style and ready to be rewritten. This is just
the base commit to make it easy to see all the changes in this repo.

Change-Id: Ieac78eee606b2ff74f045a4c8f0c62899c6ac88c
Signed-off-by: Vernon Mauery <vernon.mauery@linux.intel.com>
diff --git a/src/firmware-update.cpp b/src/firmware-update.cpp
new file mode 100644
index 0000000..dd9ea83
--- /dev/null
+++ b/src/firmware-update.cpp
@@ -0,0 +1,1879 @@
+#include <ipmid/api.h>
+#include <openssl/evp.h>
+#include <openssl/sha.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/asio.hpp>
+#include <boost/process/child.hpp>
+#include <boost/uuid/random_generator.hpp>
+#include <boost/uuid/uuid_io.hpp>
+#include <chrono>
+#include <commandutils.hpp>
+#include <cstdint>
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <random>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <sdbusplus/timer.hpp>
+#include <sstream>
+
+static void register_netfn_firmware_functions() __attribute__((constructor));
+
+// oem return code for firmware update control
+constexpr ipmi_ret_t IPMI_CC_REQ_INVALID_PHASE = 0xd5;
+constexpr ipmi_ret_t IPMI_CC_USB_ATTACH_FAIL = 0x80;
+
+static sdbusplus::bus::bus _bus(ipmid_get_sd_bus_connection());
+
+static constexpr bool DEBUG = false;
+
+static constexpr char FW_UPDATE_SERVER_DBUS_NAME[] =
+    "xyz.openbmc_project.fwupdate1.server";
+
+static constexpr char FW_UPDATE_SERVER_PATH[] =
+    "/xyz/openbmc_project/fwupdate1";
+static constexpr char FW_UPDATE_SERVER_INFO_PATH[] =
+    "/xyz/openbmc_project/fwupdate1/info";
+static constexpr char FW_UPDATE_ACTIVE_INFO_PATH[] =
+    "/xyz/openbmc_project/fwupdate1/info/bmc_active";
+static constexpr char FW_UPDATE_BACKUP_INFO_PATH[] =
+    "/xyz/openbmc_project/fwupdate1/info/bmc_backup";
+
+static constexpr char FW_UPDATE_INTERFACE[] = "xyz.openbmc_project.fwupdate1";
+static constexpr char FW_UPDATE_INFO_INTERFACE[] =
+    "xyz.openbmc_project.fwupdate1.fwinfo";
+static constexpr char FW_UPDATE_SECURITY_INTERFACE[] =
+    "xyz.openbmc_project.fwupdate1.security";
+
+constexpr std::size_t operator""_MB(unsigned long long v)
+{
+    return 1024u * 1024u * v;
+}
+static constexpr int FIRMWARE_BUFFER_MAX_SIZE = 32_MB;
+
+static constexpr char FIRMWARE_BUFFER_FILE[] = "/tmp/fw-download.bin";
+static bool local_download_is_active(void)
+{
+    struct stat sb;
+    if (stat(FIRMWARE_BUFFER_FILE, &sb) < 0)
+        return false;
+    return true;
+}
+
+class fw_update_status_cache
+{
+  public:
+    enum
+    {
+        FW_STATE_INIT = 0,
+        FW_STATE_IDLE,
+        FW_STATE_DOWNLOAD,
+        FW_STATE_VERIFY,
+        FW_STATE_WRITE,
+        FW_STATE_READY,
+        FW_STATE_ERROR = 0x0f,
+        FW_STATE_AC_CYCLE_REQUIRED = 0x83,
+    };
+    fw_update_status_cache()
+    {
+        _match = std::make_shared<sdbusplus::bus::match::match>(
+            _bus,
+            sdbusplus::bus::match::rules::propertiesChanged(
+                FW_UPDATE_SERVER_PATH, FW_UPDATE_INTERFACE),
+            [&](sdbusplus::message::message &msg) {
+                if (DEBUG)
+                    std::cerr << "propertiesChanged lambda\n";
+                std::map<std::string, ipmi::DbusVariant> props;
+                std::vector<std::string> inval;
+                std::string iface;
+                msg.read(iface, props, inval);
+                _parse_props(props);
+            });
+        _initial_fetch();
+    }
+
+    uint8_t state()
+    {
+        if (DEBUG)
+            std::cerr << "fw-state: 0x" << std::hex << (int)_state << '\n';
+        if ((_state == FW_STATE_IDLE || _state == FW_STATE_INIT) &&
+            local_download_is_active())
+        {
+            _state = FW_STATE_DOWNLOAD;
+            _percent = 0;
+        }
+        return _state;
+    }
+    uint8_t percent()
+    {
+        return _percent;
+    }
+    std::string msg()
+    {
+        return _msg;
+    }
+#ifdef UPDATER_ENABLED
+    std::string get_software_obj_path()
+    {
+        return _software_obj_path;
+    }
+    void set_software_obj_path(std::string &obj_path)
+    {
+        _software_obj_path = obj_path;
+        _state = FW_STATE_WRITE;
+        _percent = 0;
+        _match = std::make_shared<sdbusplus::bus::match::match>(
+            _bus,
+            sdbusplus::bus::match::rules::propertiesChanged(
+                _software_obj_path,
+                "xyz.openbmc_project.Software.ActivationProgress"),
+            [&](sdbusplus::message::message &msg) {
+                if (DEBUG)
+                    std::cerr << "propertiesChanged lambda\n";
+                std::map<std::string, ipmi::DbusVariant> props;
+                std::vector<std::string> inval;
+                std::string iface;
+                msg.read(iface, props, inval);
+                _parse_props(props);
+            });
+    }
+    uint8_t activation_timer_timeout()
+    {
+        std::cerr << "activation_timer_timout(): increase percentage...\n";
+        _percent = _percent + 5;
+        std::cerr << "_percent = " << std::string((char *)&_percent) << "\n";
+        return _percent;
+    }
+#endif
+  protected:
+    void _parse_props(std::map<std::string, ipmi::DbusVariant> &properties)
+    {
+        if (DEBUG)
+            std::cerr << "propertiesChanged (" << properties.size()
+                      << " elements)";
+        for (const auto &t : properties)
+        {
+            auto key = t.first;
+            auto value = t.second;
+            if (key == "state")
+            {
+                auto state =
+                    sdbusplus::message::variant_ns::get<std::string>(value);
+                if (DEBUG)
+                    std::cerr << ", state=" << state;
+                if (state == "INIT")
+                    _state = FW_STATE_INIT;
+                else if (state == "IDLE")
+                    _state = FW_STATE_IDLE;
+                else if (state == "DOWNLOAD")
+                    _state = FW_STATE_DOWNLOAD;
+                else if (state == "VERIFY")
+                    _state = FW_STATE_VERIFY;
+                else if (state == "WRITE")
+                    _state = FW_STATE_WRITE;
+                else if (state == "READY")
+                    _state = FW_STATE_READY;
+                else if (state == "ERROR")
+                    _state = FW_STATE_ERROR;
+                else if (state == "AC_CYCLE_REQUIRED")
+                    _state = FW_STATE_AC_CYCLE_REQUIRED;
+                else
+                {
+                    _state = FW_STATE_ERROR;
+                    _msg = "internal error";
+                }
+            }
+            else if (key == "percent")
+            {
+                _percent = sdbusplus::message::variant_ns::get<int32_t>(value);
+                if (DEBUG)
+                    std::cerr << ", pct=" << (int)_percent;
+            }
+            else if (key == "msg")
+            {
+                _msg = sdbusplus::message::variant_ns::get<std::string>(value);
+                if (DEBUG)
+                    std::cerr << ", msg='" << _msg << '\'';
+            }
+            else if (key == "Progress")
+            {
+                _percent = sdbusplus::message::variant_ns::get<uint8_t>(value);
+                ;
+                if (_percent == 100)
+                    _state = FW_STATE_READY;
+            }
+        }
+        if ((_state == FW_STATE_IDLE || _state == FW_STATE_INIT) &&
+            local_download_is_active())
+        {
+            _state = FW_STATE_DOWNLOAD;
+            _percent = 0;
+        }
+        if (DEBUG)
+            std::cerr << '\n';
+    }
+    void _initial_fetch()
+    {
+#ifndef UPDATER_ENABLED
+        auto method = _bus.new_method_call(
+            FW_UPDATE_SERVER_DBUS_NAME, FW_UPDATE_SERVER_PATH,
+            "org.freedesktop.DBus.Properties", "GetAll");
+        method.append(FW_UPDATE_INTERFACE);
+        if (DEBUG)
+            std::cerr << "fetch fw status via dbus...\n";
+        try
+        {
+            auto reply = _bus.call(method);
+
+            std::map<std::string, ipmi::DbusVariant> properties;
+            reply.read(properties);
+            _parse_props(properties);
+        }
+        catch (sdbusplus::exception::SdBusError &e)
+        {
+            std::cerr << "Failed in _initial_fetch(): SDBus Error: " << e.what()
+                      << "\n";
+            return;
+        }
+#endif
+    }
+
+    std::shared_ptr<sdbusplus::bus::match::match> _match;
+    uint8_t _state = 0;
+    uint8_t _percent = 0;
+    std::string _msg;
+
+  private:
+    std::string _software_obj_path;
+};
+
+static fw_update_status_cache fw_update_status;
+
+static std::chrono::steady_clock::time_point fw_random_number_timestamp;
+static constexpr int FW_RANDOM_NUMBER_LENGTH = 8;
+static constexpr auto FW_RANDOM_NUMBER_TTL = std::chrono::seconds(30);
+static uint8_t fw_random_number[FW_RANDOM_NUMBER_LENGTH];
+
+static ipmi_ret_t ipmi_firmware_get_fw_random_number(
+    ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request,
+    ipmi_response_t response, ipmi_data_len_t data_len, ipmi_context_t context)
+{
+    std::random_device rd;
+    std::default_random_engine gen(rd());
+    std::uniform_int_distribution<> dist{0, 255};
+
+    if (*data_len != 0)
+    {
+        *data_len = 0;
+        return IPMI_CC_REQ_DATA_LEN_INVALID;
+    }
+
+    fw_random_number_timestamp = std::chrono::steady_clock::now();
+
+    uint8_t *msg_reply = static_cast<uint8_t *>(response);
+    for (int i = 0; i < FW_RANDOM_NUMBER_LENGTH; i++)
+        fw_random_number[i] = msg_reply[i] = dist(gen);
+
+    if (DEBUG)
+        std::cerr << "FW Rand Num: 0x" << std::hex << (int)msg_reply[0] << " 0x"
+                  << (int)msg_reply[1] << " 0x" << (int)msg_reply[2] << " 0x"
+                  << (int)msg_reply[3] << " 0x" << (int)msg_reply[4] << " 0x"
+                  << (int)msg_reply[5] << " 0x" << (int)msg_reply[6] << " 0x"
+                  << (int)msg_reply[7] << '\n';
+
+    *data_len = FW_RANDOM_NUMBER_LENGTH;
+
+    return IPMI_CC_OK;
+}
+
+static ipmi_ret_t ipmi_firmware_enter_fw_transfer_mode(
+    ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request,
+    ipmi_response_t response, ipmi_data_len_t data_len, ipmi_context_t context)
+{
+    if (DEBUG)
+        std::cerr << "Enter FW transfer mode requested, data_len = "
+                  << *data_len << '\n';
+
+    if (*data_len != FW_RANDOM_NUMBER_LENGTH)
+    {
+        *data_len = 0;
+        return IPMI_CC_REQ_DATA_LEN_INVALID;
+    }
+    *data_len = 0;
+
+    auto rq_time = std::chrono::steady_clock::now();
+    if (DEBUG)
+        std::cerr << "now - fwts = "
+                  << std::chrono::duration_cast<std::chrono::microseconds>(
+                         rq_time - fw_random_number_timestamp)
+                         .count()
+                  << " us\n";
+    if (std::chrono::duration_cast<std::chrono::microseconds>(
+            rq_time - fw_random_number_timestamp)
+            .count() > std::chrono::duration_cast<std::chrono::microseconds>(
+                           FW_RANDOM_NUMBER_TTL)
+                           .count())
+    {
+        if (DEBUG)
+            std::cerr << "key timeout\n";
+        return IPMI_CC_PARM_OUT_OF_RANGE;
+    }
+
+    uint8_t *msg_request = static_cast<uint8_t *>(request);
+    for (int i = 0; i < FW_RANDOM_NUMBER_LENGTH; i++)
+    {
+        if (fw_random_number[i] != msg_request[i])
+        {
+            if (DEBUG)
+                std::cerr << "key error" << (int)fw_random_number[i]
+                          << "!=" << (int)msg_request[i] << "\n";
+            return IPMI_CC_INVALID_FIELD_REQUEST;
+        }
+    }
+
+    if (fw_update_status.state() != fw_update_status_cache::FW_STATE_IDLE
+        // TODO: Allowing FW_STATE_INIT here to let image activation available
+        // without being in FW_STATE_IDLE, need to fix/adjust the state machine
+        // to match xyz.openbmc_project.Software.BMC.Updater service activation
+        // mechanism at finer grain
+        && fw_update_status.state() != fw_update_status_cache::FW_STATE_INIT)
+    {
+        if (DEBUG)
+            std::cerr << "not in INIT or IDLE\n";
+        return IPMI_CC_INVALID_FIELD_REQUEST;
+    }
+    // FIXME? c++ doesn't off an option for exclusive file creation
+    FILE *fp = fopen(FIRMWARE_BUFFER_FILE, "wx");
+    if (!fp)
+    {
+        if (DEBUG)
+            std::cerr << "failed to create buffer file\n";
+        return IPMI_CC_INVALID_FIELD_REQUEST;
+    }
+    fclose(fp);
+
+    return IPMI_CC_OK;
+}
+
+static ipmi_ret_t ipmi_firmware_exit_fw_update_mode(
+    ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request,
+    ipmi_response_t response, ipmi_data_len_t data_len, ipmi_context_t context)
+{
+    if (DEBUG)
+        std::cerr << "Exit FW update mode\n";
+    *data_len = 0;
+
+    ipmi_ret_t rc = IPMI_CC_OK;
+    switch (fw_update_status.state())
+    {
+        case fw_update_status_cache::FW_STATE_INIT:
+        case fw_update_status_cache::FW_STATE_IDLE:
+            rc = IPMI_CC_INVALID_FIELD_REQUEST;
+            break;
+        case fw_update_status_cache::FW_STATE_DOWNLOAD:
+            unlink(FIRMWARE_BUFFER_FILE);
+        case fw_update_status_cache::FW_STATE_VERIFY:
+            break;
+        case fw_update_status_cache::FW_STATE_WRITE:
+            rc = IPMI_CC_INVALID_FIELD_REQUEST;
+            break;
+        case fw_update_status_cache::FW_STATE_READY:
+        case fw_update_status_cache::FW_STATE_ERROR:
+            unlink(FIRMWARE_BUFFER_FILE);
+            break;
+        case fw_update_status_cache::FW_STATE_AC_CYCLE_REQUIRED:
+            rc = IPMI_CC_INVALID_FIELD_REQUEST;
+            break;
+    }
+    if (rc == IPMI_CC_OK)
+    {
+        // attempt to reset the state machine -- this may fail
+        auto method = _bus.new_method_call(
+            FW_UPDATE_SERVER_DBUS_NAME, FW_UPDATE_SERVER_PATH,
+            "org.freedesktop.DBus.Properties", "Abort");
+        try
+        {
+            auto reply = _bus.call(method);
+
+            ipmi::DbusVariant retval;
+            reply.read(retval);
+            if (sdbusplus::message::variant_ns::get<int>(retval) != 0)
+                rc = IPMI_CC_INVALID_FIELD_REQUEST;
+        }
+        catch (sdbusplus::exception::SdBusError &e)
+        {
+            std::cerr << "SDBus Error: " << e.what() << "\n";
+            return IPMI_CC_UNSPECIFIED_ERROR;
+        }
+    }
+
+    return rc;
+}
+
+static void post_transfer_complete_handler(
+    std::unique_ptr<sdbusplus::bus::match::match> &fw_update_matcher);
+static bool request_start_firmware_update(const std::string &uri)
+{
+    if (DEBUG)
+        std::cerr << "request start firmware update()\n";
+
+#ifdef UPDATER_ENABLED
+    // fwupdate URIs start with file:// or usb:// or tftp:// etc. By the time
+    // the code gets to this point, the file should be transferred start the
+    // request (creating a new file in /tmp/images causes the update manager to
+    // check if it is ready for activation)
+    static std::unique_ptr<sdbusplus::bus::match::match> fw_update_matcher;
+    post_transfer_complete_handler(fw_update_matcher);
+    std::filesystem::rename(
+        uri, "/tmp/images/" +
+                 boost::uuids::to_string(boost::uuids::random_generator()()));
+#else
+    auto method =
+        _bus.new_method_call(FW_UPDATE_SERVER_DBUS_NAME, FW_UPDATE_SERVER_PATH,
+                             FW_UPDATE_INTERFACE, "start");
+    if (DEBUG)
+        std::cerr << "fwupdate1.start: " << uri << '\n';
+    method.append(uri);
+    try
+    {
+        auto reply = _bus.call(method);
+
+        uint32_t retval;
+        reply.read(retval);
+        if (retval != 0)
+        {
+            if (DEBUG)
+                std::cerr << "method returned non-zero: " << retval << "\n";
+            return false;
+        }
+        return true;
+    }
+    catch (sdbusplus::exception::SdBusError &e)
+    {
+        std::cerr << "SDBus Error: " << e.what();
+        return false;
+    }
+#endif
+    return true;
+}
+
+static bool request_abort_firmware_update()
+{
+    auto method =
+        _bus.new_method_call(FW_UPDATE_SERVER_DBUS_NAME, FW_UPDATE_SERVER_PATH,
+                             FW_UPDATE_INTERFACE, "abort");
+    try
+    {
+        auto reply = _bus.call(method);
+
+        unlink(FIRMWARE_BUFFER_FILE);
+        uint32_t retval;
+        reply.read(retval);
+        if (retval != 0)
+            return false;
+    }
+    catch (sdbusplus::exception::SdBusError &e)
+    {
+        std::cerr << "SDBus Error: " << e.what() << "\n";
+        unlink(FIRMWARE_BUFFER_FILE);
+        return false;
+    }
+    return true;
+}
+
+class transfer_hash_check
+{
+  public:
+    enum hash_check
+    {
+        CHECK_NOT_REQUESTED = 0,
+        CHECK_REQUESTED,
+        CHECK_PASSED_SHA2,
+        CHECK_RESVD1,
+        CHECK_FAILED_SHA2 = 0xe2,
+        CHECK_RESVD2 = 0xe3,
+    };
+
+  protected:
+    EVP_MD_CTX *_ctx;
+    std::vector<uint8_t> _expected;
+    enum hash_check _check;
+    bool _started;
+
+  public:
+    transfer_hash_check() : _check(CHECK_NOT_REQUESTED), _started(false)
+    {
+    }
+    ~transfer_hash_check()
+    {
+        if (_ctx)
+        {
+            EVP_MD_CTX_destroy(_ctx);
+            _ctx = NULL;
+        }
+    }
+    void init(const std::vector<uint8_t> &expected)
+    {
+        _expected = expected;
+        _check = CHECK_REQUESTED;
+        _ctx = EVP_MD_CTX_create();
+        EVP_DigestInit(_ctx, EVP_sha256());
+    }
+    void hash(const std::vector<uint8_t> &data)
+    {
+        if (!_started)
+            _started = true;
+        EVP_DigestUpdate(_ctx, data.data(), data.size());
+    }
+    void clear()
+    {
+        // if not started, nothing to clear
+        if (_started)
+        {
+            if (_ctx)
+                EVP_MD_CTX_destroy(_ctx);
+            if (_check != CHECK_NOT_REQUESTED)
+                _check = CHECK_REQUESTED;
+            _ctx = EVP_MD_CTX_create();
+            EVP_DigestInit(_ctx, EVP_sha256());
+        }
+    }
+    enum hash_check check()
+    {
+        if (_check == CHECK_REQUESTED)
+        {
+            unsigned int len;
+            std::vector<uint8_t> digest(EVP_MD_size(EVP_sha256()));
+            EVP_DigestFinal(_ctx, digest.data(), &len);
+            if (digest == _expected)
+            {
+                if (DEBUG)
+                    std::cerr << "transfer sha2 check passed\n";
+                _check = CHECK_PASSED_SHA2;
+            }
+            else
+            {
+                if (DEBUG)
+                    std::cerr << "transfer sha2 check failed\n";
+                _check = CHECK_FAILED_SHA2;
+            }
+        }
+        return _check;
+    }
+    uint8_t status() const
+    {
+        return static_cast<uint8_t>(_check);
+    }
+};
+
+std::shared_ptr<transfer_hash_check> xfer_hash_check;
+
+#ifdef UPDATER_ENABLED
+static void activate_image(const char *obj_path)
+{
+    if (DEBUG)
+    {
+        std::cerr << "activateImage()...\n";
+        std::cerr << "obj_path = " << obj_path << "\n";
+    }
+    auto method_call = _bus.new_method_call(
+        "xyz.openbmc_project.Software.BMC.Updater", obj_path,
+        "org.freedesktop.DBus.Properties", "Set");
+    method_call.append("xyz.openbmc_project.Software.Activation",
+                       "RequestedActivation",
+                       sdbusplus::message::variant<std::string>(
+                           "xyz.openbmc_project.Software.Activation."
+                           "RequestedActivations.Active"));
+
+    try
+    {
+        auto method_call_reply = _bus.call(method_call);
+    }
+    catch (sdbusplus::exception::SdBusError &e)
+    {
+        std::cerr << "Failed in activate_image(): SDBus Error: " << e.what()
+                  << "\n";
+        return;
+    }
+}
+
+static void post_transfer_complete_handler(
+    std::unique_ptr<sdbusplus::bus::match::match> &fw_update_matcher)
+{
+    // Setup timer for watching signal
+    static phosphor::Timer timer(
+        [&fw_update_matcher]() { fw_update_matcher = nullptr; });
+
+    static phosphor::Timer activation_status_timer([]() {
+        if (fw_update_status.activation_timer_timeout() >= 95)
+        {
+            activation_status_timer.stop();
+        }
+    });
+
+    timer.start(std::chrono::microseconds(5000000), false);
+
+    // callback function for capturing signal
+    auto callback = [&fw_update_matcher](sdbusplus::message::message &m) {
+        if (DEBUG)
+            std::cerr << "[complete] Match fired\n";
+        bool flag = false;
+
+        std::vector<std::pair<
+            std::string,
+            std::vector<std::pair<std::string,
+                                  sdbusplus::message::variant<std::string>>>>>
+            interfaces_properties;
+
+        sdbusplus::message::object_path obj_path;
+
+        try
+        {
+            m.read(obj_path, interfaces_properties); // Read in the object path
+                                                     // that was just created
+        }
+        catch (std::exception &e)
+        {
+            std::cerr
+                << "[complete] Failed at post_transfer_complete-handler : "
+                << e.what() << "\n";
+        }
+        // constructing response message
+        if (DEBUG)
+            std::cerr << "[complete] obj path = " << obj_path.str << "\n";
+        for (auto &interface : interfaces_properties)
+        {
+            if (DEBUG)
+                std::cerr << "[complete] interface = " << interface.first
+                          << "\n";
+
+            if (interface.first == "xyz.openbmc_project.Software.Activation")
+            {
+                // cancel timer only when
+                // xyz.openbmc_project.Software.Activation interface is
+                // added
+
+                if (DEBUG)
+                    std::cerr << "[complete] Attempt to cancel timer...\n";
+                try
+                {
+                    timer.stop();
+                    activation_status_timer.start(
+                        std::chrono::microseconds(3000000), true);
+                }
+                catch (std::exception &e)
+                {
+                    std::cerr << "[complete] cancel timer error: " << e.what()
+                              << "\n";
+                }
+
+                fw_update_status.set_software_obj_path(obj_path.str);
+                activate_image(obj_path.str.c_str());
+                if (DEBUG)
+                    std::cerr << "[complete] returned from activeImage()\n";
+
+                fw_update_matcher = nullptr;
+            }
+        }
+    };
+
+    // Adding matcher
+    fw_update_matcher = std::make_unique<sdbusplus::bus::match::match>(
+        _bus,
+        "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
+        "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
+        callback);
+}
+#endif
+
+class MappedFile
+{
+  public:
+    MappedFile(const std::string &fname) : addr(nullptr), fsize(0)
+    {
+        std::error_code ec;
+        size_t sz = std::filesystem::file_size(fname, ec);
+        int fd = open(fname.c_str(), O_RDONLY);
+        if (!ec || fd < 0)
+        {
+            return;
+        }
+        void *tmp = mmap(NULL, sz, PROT_READ, MAP_SHARED, fd, 0);
+        close(fd);
+        if (tmp == MAP_FAILED)
+        {
+            return;
+        }
+        addr = tmp;
+        fsize = sz;
+    }
+
+    ~MappedFile()
+    {
+        if (addr)
+        {
+            munmap(addr, fsize);
+        }
+    }
+    const uint8_t *data() const
+    {
+        return static_cast<const uint8_t *>(addr);
+    }
+    size_t size() const
+    {
+        return fsize;
+    }
+
+  private:
+    size_t fsize;
+    void *addr;
+};
+
+static int transfer_from_file(const std::string &uri, bool move = true)
+{
+    std::error_code ec;
+    if (DEBUG)
+        std::cerr << "transfer_from_file(" << uri << ")\n";
+    if (move)
+    {
+        std::filesystem::rename(uri, FIRMWARE_BUFFER_FILE, ec);
+    }
+    else
+    {
+        std::filesystem::copy(uri, FIRMWARE_BUFFER_FILE,
+                              std::filesystem::copy_options::overwrite_existing,
+                              ec);
+    }
+    if (xfer_hash_check)
+    {
+        MappedFile mappedfw(uri);
+        xfer_hash_check->hash(
+            {mappedfw.data(), mappedfw.data() + mappedfw.size()});
+    }
+    if (ec.value())
+    {
+        std::cerr << "cp/mv returns: " << ec.message() << "(" << ec.value()
+                  << ")\n";
+    }
+    return ec.value();
+}
+
+template <typename... ArgTypes>
+static int executeCmd(const char *path, ArgTypes &&... tArgs)
+{
+    boost::process::child execProg(path, const_cast<char *>(tArgs)...);
+    execProg.wait();
+    return execProg.exit_code();
+}
+
+constexpr char USB_CTRL_PATH[] = "/usr/bin/usb-ctrl";
+constexpr char FWUPDATE_MOUNT_POINT[] = "/tmp/usb-fwupd.mnt";
+constexpr char FWUPDATE_USB_VOL_IMG[] = "/tmp/usb-fwupd.img";
+constexpr char FWUPDATE_USB_DEV_NAME[] = "fw-usb-mass-storage-dev";
+constexpr size_t fwPathMaxLength = 255;
+static int transfer_from_usb(const std::string &uri)
+{
+    int ret, sysret;
+    char fwpath[fwPathMaxLength];
+    if (DEBUG)
+        std::cerr << "transfer_from_usb(" << uri << ")\n";
+    ret = executeCmd(USB_CTRL_PATH, "mount", FWUPDATE_USB_VOL_IMG,
+                     FWUPDATE_MOUNT_POINT);
+    if (ret)
+    {
+        return ret;
+    }
+
+    std::string usb_path = std::string(FWUPDATE_MOUNT_POINT) + "/" + uri;
+    ret = transfer_from_file(usb_path, false);
+
+    executeCmd(USB_CTRL_PATH, "cleanup", FWUPDATE_USB_VOL_IMG,
+               FWUPDATE_MOUNT_POINT);
+    return ret;
+}
+
+static bool transfer_firmware_from_uri(const std::string &uri)
+{
+    static constexpr char FW_URI_FILE[] = "file://";
+    static constexpr char FW_URI_USB[] = "usb://";
+    if (DEBUG)
+        std::cerr << "transfer_firmware_from_uri(" << uri << ")\n";
+    if (boost::algorithm::starts_with(uri, FW_URI_FILE))
+    {
+        std::string fname = uri.substr(sizeof(FW_URI_FILE) - 1);
+        if (fname != FIRMWARE_BUFFER_FILE)
+        {
+            return 0 == transfer_from_file(fname);
+        }
+        return true;
+    }
+    if (boost::algorithm::starts_with(uri, FW_URI_USB))
+    {
+        std::string fname = uri.substr(sizeof(FW_URI_USB) - 1);
+        return 0 == transfer_from_usb(fname);
+    }
+    return false;
+}
+
+/* Get USB-mass-storage device status: inserted => true, ejected => false */
+static int usb_get_status()
+{
+    static constexpr char usb_gadget_base[] = "/sys/kernel/config/usb_gadget/";
+    auto usb_device =
+        std::filesystem::path(usb_gadget_base) / FWUPDATE_USB_DEV_NAME;
+    std::error_code ec;
+    return std::filesystem::exists(usb_device, ec) && !ec;
+}
+
+/* Insert the USB-mass-storage device status: success => 0, failure => non-0 */
+static int usb_attach_device()
+{
+    if (usb_get_status())
+    {
+        return 1;
+    }
+    int ret =
+        executeCmd(USB_CTRL_PATH, "setup", FWUPDATE_USB_VOL_IMG,
+                   std::to_string(FIRMWARE_BUFFER_MAX_SIZE / 1_MB).c_str());
+    if (!ret)
+    {
+        ret = executeCmd(USB_CTRL_PATH, "insert", FWUPDATE_USB_DEV_NAME,
+                         FWUPDATE_USB_VOL_IMG);
+    }
+    return ret;
+}
+
+/* Eject the USB-mass-storage device status: success => 0, failure => non-0 */
+static int usb_detach_device()
+{
+    if (!usb_get_status())
+    {
+        return 1;
+    }
+    return executeCmd(USB_CTRL_PATH, "eject", FWUPDATE_USB_DEV_NAME);
+}
+
+constexpr uint8_t controls_init = 0x00;
+constexpr uint8_t controls_transfer_started = 0x01;
+constexpr uint8_t controls_transfer_completed = 0x02;
+constexpr uint8_t controls_transfer_aborted = 0x04;
+constexpr uint8_t controls_usb_attached = 0x08;
+
+struct fw_update_control_request
+{
+    enum knob
+    {
+        CTRL_GET = 0,
+        CTRL_XFER_START,
+        CTRL_XFER_COMPLETE,
+        CTRL_XFER_ABORT,
+        CTRL_SET_FILENAME,
+        CTRL_USB_ATTACH,
+        CTRL_USB_DETACH,
+    } __attribute__((packed));
+    enum knob control;
+    uint8_t nlen;
+    char filename[fwPathMaxLength];
+} __attribute__((packed));
+
+static ipmi_ret_t ipmi_firmware_control(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
+                                        ipmi_request_t request,
+                                        ipmi_response_t response,
+                                        ipmi_data_len_t data_len,
+                                        ipmi_context_t context)
+{
+    static std::string fw_xfer_uri;
+
+    if (DEBUG)
+        std::cerr << "FW update control\n";
+    *data_len = 0;
+
+    static uint8_t controls = controls_init;
+    ipmi_ret_t rc = IPMI_CC_OK;
+    auto ctrl_req = reinterpret_cast<fw_update_control_request *>(request);
+    auto ctrl_resp = reinterpret_cast<uint8_t *>(response);
+
+    if (usb_get_status())
+    {
+        controls |= controls_usb_attached;
+    }
+    else
+    {
+        controls &= ~controls_usb_attached;
+    }
+
+    switch (ctrl_req->control)
+    {
+        case fw_update_control_request::CTRL_GET:
+            break;
+        case fw_update_control_request::CTRL_XFER_START:
+        {
+            controls |= controls_transfer_started;
+            // reset buffer to empty (truncate file)
+            std::ofstream out(FIRMWARE_BUFFER_FILE,
+                              std::ofstream::binary | std::ofstream::trunc);
+            fw_xfer_uri = std::string("file://") + FIRMWARE_BUFFER_FILE;
+            if (xfer_hash_check)
+            {
+                xfer_hash_check->clear();
+            }
+            if (DEBUG)
+                std::cerr << "transfer start\n";
+        }
+        break;
+        case fw_update_control_request::CTRL_XFER_COMPLETE:
+        {
+            if (usb_get_status())
+            {
+                rc = IPMI_CC_REQ_INVALID_PHASE;
+            }
+            // finish transfer based on URI
+            if (!transfer_firmware_from_uri(fw_xfer_uri))
+            {
+                rc = IPMI_CC_UNSPECIFIED_ERROR;
+                break;
+            }
+            // transfer complete
+            if (xfer_hash_check)
+            {
+                if (transfer_hash_check::CHECK_PASSED_SHA2 !=
+                    xfer_hash_check->check())
+                {
+                    if (DEBUG)
+                        std::cerr << "xfer_hash_check returns not "
+                                     "CHECK_PASSED_SHA2\n";
+                    rc = IPMI_CC_UNSPECIFIED_ERROR;
+                    break;
+                }
+            }
+            // start the request
+            if (!request_start_firmware_update(FIRMWARE_BUFFER_FILE))
+            {
+                if (DEBUG)
+                    std::cerr
+                        << "request_start_firmware_update returns failure\n";
+                rc = IPMI_CC_UNSPECIFIED_ERROR;
+            }
+            if (rc == IPMI_CC_OK)
+            {
+                controls |= controls_transfer_completed;
+            }
+        }
+        break;
+        case fw_update_control_request::CTRL_XFER_ABORT:
+            if (DEBUG)
+                std::cerr << "send abort request\n";
+            if (usb_get_status())
+            {
+                if (0 != usb_detach_device())
+                {
+                    rc = IPMI_CC_USB_ATTACH_FAIL;
+                }
+            }
+            if (!request_abort_firmware_update())
+            {
+                if (DEBUG)
+                    std::cerr
+                        << "abort_start_firmware_update returns failure\n";
+                rc = IPMI_CC_UNSPECIFIED_ERROR;
+            }
+            controls |= controls_transfer_aborted;
+            break;
+        case fw_update_control_request::CTRL_SET_FILENAME:
+            fw_xfer_uri.clear();
+            fw_xfer_uri.insert(0, ctrl_req->filename, ctrl_req->nlen);
+            break;
+        case fw_update_control_request::CTRL_USB_ATTACH:
+            if (usb_get_status())
+            {
+                rc = IPMI_CC_INVALID_FIELD_REQUEST;
+            }
+            else if (0 != usb_attach_device())
+            {
+                rc = IPMI_CC_USB_ATTACH_FAIL;
+            }
+            else
+            {
+                rc = IPMI_CC_OK;
+            }
+            break;
+        case fw_update_control_request::CTRL_USB_DETACH:
+            if (!usb_get_status())
+            {
+                rc = IPMI_CC_INVALID_FIELD_REQUEST;
+            }
+            if (0 != usb_detach_device())
+            {
+                rc = IPMI_CC_USB_ATTACH_FAIL;
+            }
+            else
+            {
+                rc = IPMI_CC_OK;
+            }
+            break;
+        default:
+            if (DEBUG)
+                std::cerr << "control byte " << std::hex << ctrl_req->control
+                          << " unknown\n";
+            rc = IPMI_CC_INVALID_FIELD_REQUEST;
+            break;
+    }
+
+    if (rc == IPMI_CC_OK)
+    {
+        *ctrl_resp = controls;
+        *data_len = sizeof(*ctrl_resp);
+    }
+
+    return rc;
+}
+
+struct fw_version_info
+{
+    uint8_t id_tag;
+    uint8_t major;
+    uint8_t minor;
+    uint32_t build;
+    uint32_t build_time;
+    uint32_t update_time;
+} __attribute__((packed));
+
+static ipmi_ret_t ipmi_firmware_get_fw_version_info(
+    ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request,
+    ipmi_response_t response, ipmi_data_len_t data_len, ipmi_context_t context)
+{
+    if (DEBUG)
+        std::cerr << "Get FW Version Info\n";
+
+    // Byte 1 - Count (N) Number of devices data is being returned for.
+    // Byte 2 - ID Tag 00 – reserved 01 – BMC Active Image 02 – BBU Active Image
+    //                 03 – BMC Backup Image 04 – BBU Backup Image 05 – BBR
+    //                 Image
+    // Byte 3 - Major Version Number
+    // Byte 4 - Minor Version Number
+    // Bytes 5:8 - Build Number
+    // Bytes 9:12 - Build Timestamp Format: LSB first, same format as SEL
+    // timestamp
+    // Bytes 13:16 - Update Timestamp
+    // Bytes - 17:(15xN) - Repeat of 2 through 16
+
+    uint8_t count = 0;
+    auto ret_count = reinterpret_cast<uint8_t *>(response);
+    auto info = reinterpret_cast<struct fw_version_info *>(ret_count + 1);
+
+    for (uint8_t id_tag = 1; id_tag < 6; id_tag++)
+    {
+        const char *fw_path;
+        switch (id_tag)
+        {
+            case 1:
+                fw_path = FW_UPDATE_ACTIVE_INFO_PATH;
+                break;
+            case 2:
+                fw_path = FW_UPDATE_BACKUP_INFO_PATH;
+                break;
+            case 3:
+            case 4:
+            case 5:
+                continue; // skip for now
+                break;
+        }
+        auto method =
+            _bus.new_method_call(FW_UPDATE_SERVER_DBUS_NAME, fw_path,
+                                 "org.freedesktop.DBus.Properties", "GetAll");
+        method.append(FW_UPDATE_INFO_INTERFACE);
+        std::vector<std::pair<std::string, ipmi::DbusVariant>> properties;
+        try
+        {
+            auto reply = _bus.call(method);
+
+            if (reply.is_method_error())
+                continue;
+
+            reply.read(properties);
+        }
+        catch (sdbusplus::exception::SdBusError &e)
+        {
+            std::cerr << "SDBus Error: " << e.what();
+            return IPMI_CC_UNSPECIFIED_ERROR;
+        }
+        uint8_t major = 0;
+        uint8_t minor = 0;
+        uint32_t build = 0;
+        int32_t build_time = 0;
+        int32_t update_time = 0;
+        for (const auto &t : properties)
+        {
+            auto key = t.first;
+            auto value = t.second;
+            if (key == "version")
+            {
+                auto strver =
+                    sdbusplus::message::variant_ns::get<std::string>(value);
+                std::stringstream ss;
+                ss << std::hex << strver;
+                uint32_t t;
+                ss >> t;
+                major = t;
+                ss.ignore();
+                ss >> t;
+                minor = t;
+                ss.ignore();
+                ss >> build;
+            }
+            else if (key == "build_time")
+            {
+                build_time =
+                    sdbusplus::message::variant_ns::get<int32_t>(value);
+            }
+            else if (key == "update_time")
+            {
+                update_time =
+                    sdbusplus::message::variant_ns::get<int32_t>(value);
+            }
+        }
+
+        info->id_tag = id_tag;
+        info->major = major;
+        info->minor = minor;
+        info->build = build;
+        info->build_time = build_time;
+        info->update_time = update_time;
+        count++;
+        info++;
+    }
+    *ret_count = count;
+
+    // Status code.
+    ipmi_ret_t rc = IPMI_CC_OK;
+    *data_len = sizeof(count) + count * sizeof(*info);
+
+    return rc;
+}
+
+struct fw_security_revision_info
+{
+    uint8_t id_tag;
+    uint16_t sec_rev;
+} __attribute__((packed));
+
+static ipmi_ret_t ipmi_firmware_get_fw_security_revision(
+    ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request,
+    ipmi_response_t response, ipmi_data_len_t data_len, ipmi_context_t context)
+{
+    if (DEBUG)
+        std::cerr << "Get FW security revision info\n";
+
+    // Byte 1 - Count (N) Number of devices data is being returned for.
+    // Byte 2 - ID Tag 00 – reserved 01 – BMC Active Image 02 – BBU Active Image
+    //                 03 – BMC Backup Image 04 – BBU Backup Image 05 – BBR
+    //                 Image
+    // Byte 3 - Major Version Number
+    // Byte 4 - Minor Version Number
+    // Bytes 5:8 - Build Number
+    // Bytes 9:12 - Build Timestamp Format: LSB first, same format as SEL
+    // timestamp
+    // Bytes 13:16 - Update Timestamp
+    // Bytes - 17:(15xN) - Repeat of 2 through 16
+
+    uint8_t count = 0;
+    auto ret_count = reinterpret_cast<uint8_t *>(response);
+    auto info =
+        reinterpret_cast<struct fw_security_revision_info *>(ret_count + 1);
+
+    for (uint8_t id_tag = 1; id_tag < 6; id_tag++)
+    {
+        const char *fw_path;
+        switch (id_tag)
+        {
+            case 1:
+                fw_path = FW_UPDATE_ACTIVE_INFO_PATH;
+                break;
+            case 2:
+                fw_path = FW_UPDATE_BACKUP_INFO_PATH;
+                break;
+            case 3:
+            case 4:
+            case 5:
+                continue; // skip for now
+                break;
+        }
+        auto method =
+            _bus.new_method_call(FW_UPDATE_SERVER_DBUS_NAME, fw_path,
+                                 "org.freedesktop.DBus.Properties", "GetAll");
+        method.append(FW_UPDATE_INFO_INTERFACE, "security_version");
+        ipmi::DbusVariant sec_rev;
+        try
+        {
+            auto reply = _bus.call(method);
+
+            if (reply.is_method_error())
+                continue;
+
+            reply.read(sec_rev);
+        }
+        catch (sdbusplus::exception::SdBusError &e)
+        {
+            std::cerr << "SDBus Error: " << e.what();
+            return IPMI_CC_UNSPECIFIED_ERROR;
+        }
+
+        info->id_tag = id_tag;
+        info->sec_rev = sdbusplus::message::variant_ns::get<int>(sec_rev);
+        count++;
+        info++;
+    }
+    *ret_count = count;
+
+    // Status code.
+    ipmi_ret_t rc = IPMI_CC_OK;
+    *data_len = sizeof(count) + count * sizeof(*info);
+
+    return rc;
+}
+
+struct fw_channel_size
+{
+    uint8_t channel_id;
+    uint32_t channel_size;
+} __attribute__((packed));
+
+enum
+{
+    CHANNEL_RESVD = 0,
+    CHANNEL_KCS,
+    CHANNEL_RMCP_PLUS,
+    CHANNEL_USB_DATA,
+    CHANNEL_USB_MASS_STORAGE,
+} channel_transfer_type;
+
+static ipmi_ret_t ipmi_firmware_max_transfer_size(
+    ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request,
+    ipmi_response_t response, ipmi_data_len_t data_len, ipmi_context_t context)
+{
+    if (DEBUG)
+        std::cerr << "Get FW max transfer size\n";
+
+    // Byte 1 - Count (N) Number of devices data is being returned for.
+    // Byte 2 - ID Tag 00 – reserved 01 – kcs 02 – rmcp+,
+    //                 03 – usb data, 04 – usb mass storage
+    // Byte 3-6 - transfer size (little endian)
+    // Bytes - 7:(5xN) - Repeat of 2 through 6
+
+    uint8_t count = 0;
+    auto ret_count = reinterpret_cast<uint8_t *>(response);
+    auto info = reinterpret_cast<struct fw_channel_size *>(ret_count + 1);
+
+    info->channel_id = CHANNEL_KCS;
+    info->channel_size = 128;
+    info++;
+    count++;
+
+    info->channel_id = CHANNEL_RMCP_PLUS;
+    info->channel_size = 50 * 1024;
+    info++;
+    count++;
+
+    /*
+    info->channel_id = CHANNEL_USB_MASS_STORAGE;
+    info->channel_size = 128;
+    info++;
+    count++;
+    */
+
+    *ret_count = count;
+
+    // Status code.
+    ipmi_ret_t rc = IPMI_CC_OK;
+    *data_len = sizeof(count) + count * sizeof(*info);
+
+    return rc;
+}
+
+enum
+{
+    EXEC_CTX_RESVD = 0,
+    EXEC_CTX_FULL_LINUX = 0x10,
+    EXEC_CTX_SAFE_MODE_LINUX = 0x11,
+} bmc_execution_context;
+
+struct fw_execution_context
+{
+    uint8_t context;
+    uint8_t image_selection;
+} __attribute__((packed));
+
+static ipmi_ret_t ipmi_firmware_get_fw_execution_context(
+    ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request,
+    ipmi_response_t response, ipmi_data_len_t data_len, ipmi_context_t context)
+{
+    if (DEBUG)
+        std::cerr << "Get FW execution context\n";
+
+    // Byte 1 - execution context
+    //          0x10 - full linux stack, 0x11 - safe-mode linux stack
+    // Byte 2 - current image selection
+    //          1 - primary, 2 - secondary
+
+    auto info = reinterpret_cast<struct fw_execution_context *>(response);
+    auto method =
+        _bus.new_method_call(FW_UPDATE_SERVER_DBUS_NAME, FW_UPDATE_SERVER_PATH,
+                             "org.freedesktop.DBus.Properties", "GetAll");
+    method.append(FW_UPDATE_SECURITY_INTERFACE);
+    int active_img;
+    bool safe_mode;
+    std::string cert;
+    try
+    {
+        auto reply = _bus.call(method);
+
+        if (!reply.is_method_error())
+        {
+            std::vector<std::pair<std::string, ipmi::DbusVariant>> properties;
+            reply.read(properties);
+
+            for (const auto &t : properties)
+            {
+                auto key = t.first;
+                auto value = t.second;
+                if (key == "active_partition")
+                {
+                    active_img =
+                        sdbusplus::message::variant_ns::get<int>(value);
+                }
+                else if (key == "safe_mode")
+                {
+                    safe_mode =
+                        sdbusplus::message::variant_ns::get<bool>(value);
+                }
+            }
+        }
+    }
+    catch (sdbusplus::exception::SdBusError &e)
+    {
+        std::cerr << "SDBus Error: " << e.what();
+        return IPMI_CC_UNSPECIFIED_ERROR;
+    }
+
+    if (safe_mode)
+        info->context = EXEC_CTX_SAFE_MODE_LINUX;
+    else
+        info->context = EXEC_CTX_FULL_LINUX;
+
+    info->image_selection = active_img;
+
+    // Status code.
+    ipmi_ret_t rc = IPMI_CC_OK;
+    *data_len = sizeof(*info);
+
+    return rc;
+}
+
+struct fw_update_status_response
+{
+    uint8_t status;
+    uint8_t percent;
+    uint8_t check;
+} __attribute__((packed));
+
+static ipmi_ret_t ipmi_firmware_get_status(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
+                                           ipmi_request_t request,
+                                           ipmi_response_t response,
+                                           ipmi_data_len_t data_len,
+                                           ipmi_context_t context)
+{
+    if (DEBUG)
+        std::cerr << "Get FW update status\n";
+
+    // Byte 1 - status (0=init, 1=idle, 2=download, 3=validate, 4=write,
+    //                  5=ready, f=error, 83=ac cycle required)
+    // Byte 2 - percent
+    // Byte 3 - integrity check status (0=none, 1=req, 2=sha2ok, e2=sha2fail)
+
+    auto fw_status =
+        reinterpret_cast<struct fw_update_status_response *>(response);
+
+    fw_status->status = fw_update_status.state();
+    fw_status->percent = fw_update_status.percent();
+    fw_status->check = xfer_hash_check ? xfer_hash_check->status() : 0;
+
+    // Status code.
+    *data_len = sizeof(*fw_status);
+    return IPMI_CC_OK;
+}
+
+static constexpr uint8_t FW_UPDATE_OPTIONS_NO_DOWNREV = (1 << 0);
+static constexpr uint8_t FW_UPDATE_OPTIONS_DEFER_RESTART = (1 << 1);
+static constexpr uint8_t FW_UPDATE_OPTIONS_SHA2_CHECK = (1 << 2);
+static constexpr uint8_t FW_UPDATE_OPTIONS_RESVD1 = (1 << 3);
+struct fw_update_options_request
+{
+    uint8_t mask;
+    uint8_t options;
+} __attribute__((packed));
+
+bool fw_update_set_dbus_property(const std::string &path,
+                                 const std::string &iface,
+                                 const std::string &name,
+                                 ipmi::DbusVariant value)
+{
+    auto method =
+        _bus.new_method_call(FW_UPDATE_SERVER_DBUS_NAME, path.c_str(),
+                             "org.freedesktop.DBus.Properties", "Set");
+    method.append(iface, name, value);
+    try
+    {
+        auto reply = _bus.call(method);
+        auto err = reply.is_method_error();
+        if (err)
+            if (DEBUG)
+                std::cerr << "failed to set prop " << path << '/' << iface
+                          << '.' << name << '\n';
+        return err;
+    }
+    catch (sdbusplus::exception::SdBusError &e)
+    {
+        std::cerr << "SDBus Error: " << e.what();
+        return false;
+    }
+}
+
+uint32_t fw_update_options = 0;
+static ipmi_ret_t ipmi_firmware_update_options(
+    ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request,
+    ipmi_response_t response, ipmi_data_len_t data_len, ipmi_context_t context)
+{
+    if (DEBUG)
+        std::cerr << "Get/set FW update options\n";
+
+    // request:
+    // Byte 1 - mask
+    // Byte 2 - options
+    // Byte 3-34 - optional integrity check expected value
+    // response:
+    // Byte 1 - set options
+
+    auto fw_options =
+        reinterpret_cast<struct fw_update_options_request *>(request);
+
+    const char *path = FW_UPDATE_SERVER_INFO_PATH;
+    const char *iface = FW_UPDATE_SECURITY_INTERFACE;
+    if ((fw_options->mask & FW_UPDATE_OPTIONS_NO_DOWNREV) &&
+        (fw_options->options & FW_UPDATE_OPTIONS_NO_DOWNREV) !=
+            (fw_update_options & FW_UPDATE_OPTIONS_NO_DOWNREV))
+    {
+        if (fw_options->options & FW_UPDATE_OPTIONS_NO_DOWNREV)
+        {
+            fw_update_options |= FW_UPDATE_OPTIONS_NO_DOWNREV;
+        }
+        else
+        {
+            fw_update_options &= ~FW_UPDATE_OPTIONS_NO_DOWNREV;
+            // send dbus
+        }
+        const char *name = "inhibit_downgrade";
+        fw_update_set_dbus_property(
+            path, iface, name,
+            (bool)(!!(fw_update_options & FW_UPDATE_OPTIONS_NO_DOWNREV)));
+    }
+    if ((fw_options->mask & FW_UPDATE_OPTIONS_DEFER_RESTART) &&
+        (fw_options->options & FW_UPDATE_OPTIONS_DEFER_RESTART) !=
+            (fw_update_options & FW_UPDATE_OPTIONS_DEFER_RESTART))
+    {
+        if (fw_options->options & FW_UPDATE_OPTIONS_DEFER_RESTART)
+        {
+            fw_update_options |= FW_UPDATE_OPTIONS_DEFER_RESTART;
+        }
+        else
+        {
+            fw_update_options &= ~FW_UPDATE_OPTIONS_DEFER_RESTART;
+        }
+        const char *name = "defer_restart";
+        fw_update_set_dbus_property(
+            path, iface, name,
+            (bool)(!!(fw_update_options & FW_UPDATE_OPTIONS_DEFER_RESTART)));
+    }
+    if (fw_options->mask & FW_UPDATE_OPTIONS_SHA2_CHECK)
+    {
+        auto hash_size = EVP_MD_size(EVP_sha256());
+        if (fw_options->options & FW_UPDATE_OPTIONS_SHA2_CHECK)
+        {
+            if (*data_len != (sizeof(*fw_options) + hash_size))
+            {
+                *data_len = 0;
+                return IPMI_CC_REQ_DATA_LEN_INVALID;
+            }
+            xfer_hash_check = std::make_shared<transfer_hash_check>();
+            auto exp_hash = reinterpret_cast<uint8_t *>(fw_options + 1);
+            xfer_hash_check->init({exp_hash, exp_hash + hash_size});
+            fw_update_options |= FW_UPDATE_OPTIONS_SHA2_CHECK;
+        }
+        else
+        {
+            fw_update_options &= ~FW_UPDATE_OPTIONS_SHA2_CHECK;
+            // delete the xfer_hash_check object
+            xfer_hash_check.reset();
+        }
+    }
+    auto options_rsp = reinterpret_cast<uint8_t *>(response);
+    *options_rsp = fw_update_options;
+
+    if (DEBUG)
+        std::cerr << "current fw_update_options = " << std::hex
+                  << fw_update_options << '\n';
+    // Status code.
+    *data_len = sizeof(*options_rsp);
+    return IPMI_CC_OK;
+}
+
+struct fw_cert_info
+{
+    uint16_t cert_len;
+    uint64_t serial;
+    uint8_t subject_len;
+    char subject[255];
+} __attribute__((packed));
+
+static ipmi_ret_t ipmi_firmware_get_root_cert_info(
+    ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request,
+    ipmi_response_t response, ipmi_data_len_t data_len, ipmi_context_t context)
+{
+    if (DEBUG)
+        std::cerr << "Get FW root cert info\n";
+
+    // request:
+    // Byte 1 - certificate ID: request which certificate (ignored)
+
+    // response:
+    // Byte 1-2  - certificate length (little endian)
+    // Byte 3-10 - serial number (little endian)
+    // Byte 11   - subject length
+    // Byte 12-N - subject data
+
+    auto cert_info = reinterpret_cast<struct fw_cert_info *>(response);
+    auto method = _bus.new_method_call(
+        FW_UPDATE_SERVER_DBUS_NAME, FW_UPDATE_SERVER_INFO_PATH,
+        "org.freedesktop.DBus.Properties", "GetAll");
+    method.append(FW_UPDATE_SECURITY_INTERFACE);
+    std::string subject;
+    uint64_t serial;
+    std::string cert;
+    try
+    {
+        auto reply = _bus.call(method);
+
+        std::vector<std::pair<std::string, ipmi::DbusVariant>> properties;
+        reply.read(properties);
+
+        for (const auto &t : properties)
+        {
+            auto key = t.first;
+            auto value = t.second;
+            if (key == "certificate_subject")
+            {
+                subject =
+                    sdbusplus::message::variant_ns::get<std::string>(value);
+            }
+            else if (key == "cetificate_serial")
+            {
+                serial = sdbusplus::message::variant_ns::get<uint64_t>(value);
+            }
+            else if (key == "certificate")
+            {
+                cert = sdbusplus::message::variant_ns::get<std::string>(value);
+            }
+        }
+    }
+    catch (sdbusplus::exception::SdBusError &e)
+    {
+        std::cerr << "SDBus Error: " << e.what();
+        return IPMI_CC_UNSPECIFIED_ERROR;
+    }
+
+    cert_info->cert_len = cert.size();
+    cert_info->serial = serial;
+    // truncate subject so it fits in the 255-byte array (if necessary)
+    if (subject.size() > sizeof(cert_info->subject))
+        subject.resize(sizeof(cert_info->subject));
+    cert_info->subject_len = subject.size();
+    std::copy(subject.begin(), subject.end(), cert_info->subject);
+
+    // Status code.
+    ipmi_ret_t rc = IPMI_CC_OK;
+    // make sure to account for the *actual* size of the subject
+    *data_len = sizeof(*cert_info) - sizeof(cert_info->subject) +
+                cert_info->subject_len;
+
+    return rc;
+}
+
+struct fw_cert_data_req
+{
+    uint8_t cert_id;
+    uint16_t offset;
+    uint16_t count;
+} __attribute__((packed));
+
+static ipmi_ret_t ipmi_firmware_get_root_cert_data(
+    ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request,
+    ipmi_response_t response, ipmi_data_len_t data_len, ipmi_context_t context)
+{
+    if (DEBUG)
+        std::cerr << "Get FW root cert data\n";
+
+    // request:
+    // Byte 1 - certificate ID: request which certificate (ignored)
+    // Byte 2-3 - offset within cert to start at
+    // Byte 4-5 - number of bytes to return
+
+    // response:
+    // Byte 1-N  - certificate data
+
+    if (*data_len != sizeof(fw_cert_data_req))
+        return IPMI_CC_REQ_DATA_LEN_INVALID;
+
+    auto cert_data_req = reinterpret_cast<struct fw_cert_data_req *>(request);
+    auto method = _bus.new_method_call(
+        FW_UPDATE_SERVER_DBUS_NAME, FW_UPDATE_SERVER_INFO_PATH,
+        "org.freedesktop.DBus.Properties", "Get");
+    method.append(FW_UPDATE_SECURITY_INTERFACE, "certificate");
+    ipmi::DbusVariant cert;
+    try
+    {
+        auto reply = _bus.call(method);
+        reply.read(cert);
+    }
+    catch (sdbusplus::exception::SdBusError &e)
+    {
+        std::cerr << "SDBus Error: " << e.what();
+        return IPMI_CC_UNSPECIFIED_ERROR;
+    }
+    auto cert_data = sdbusplus::message::variant_ns::get<std::string>(cert);
+
+    if (cert_data_req->offset >= cert_data.size())
+    {
+        *data_len = 0;
+        return IPMI_CC_INVALID_FIELD_REQUEST;
+    }
+    auto first = cert_data.begin() + cert_data_req->offset;
+    auto last = first + cert_data_req->count;
+    if (last > cert_data.end())
+        last = cert_data.end();
+
+    auto data_out = reinterpret_cast<char *>(response);
+    std::copy(first, last, data_out);
+
+    // Status code.
+    ipmi_ret_t rc = IPMI_CC_OK;
+    *data_len = (last - first);
+
+    return rc;
+}
+
+static ipmi_ret_t ipmi_firmware_write_data(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
+                                           ipmi_request_t request,
+                                           ipmi_response_t response,
+                                           ipmi_data_len_t data_len,
+                                           ipmi_context_t context)
+{
+    if (DEBUG)
+        std::cerr << "write fw data (" << *data_len << " bytes)\n";
+
+    auto bytes_in = *data_len;
+    *data_len = 0;
+    if (fw_update_status.state() != fw_update_status_cache::FW_STATE_DOWNLOAD)
+        return IPMI_CC_INVALID;
+
+    std::ofstream out(FIRMWARE_BUFFER_FILE,
+                      std::ofstream::binary | std::ofstream::app);
+    if (!out)
+    {
+        return IPMI_CC_UNSPECIFIED_ERROR;
+    }
+    if (out.tellp() > FIRMWARE_BUFFER_MAX_SIZE)
+    {
+        return IPMI_CC_INVALID_FIELD_REQUEST;
+    }
+    auto data = reinterpret_cast<uint8_t *>(request);
+    out.write(reinterpret_cast<char *>(data), bytes_in);
+    out.close();
+    if (xfer_hash_check)
+    {
+        xfer_hash_check->hash({data, data + bytes_in});
+    }
+
+    // Status code.
+    return IPMI_CC_OK;
+}
+
+static constexpr char NOT_IMPLEMENTED[] = "NOT IMPLEMENTED";
+
+static ipmi_ret_t ipmi_firmware_wildcard_handler(
+    ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request,
+    ipmi_response_t response, ipmi_data_len_t data_len, ipmi_context_t context)
+{
+    if (DEBUG)
+        std::cerr << "Handling stubbed Netfn:[0x" << std::hex << +netfn
+                  << "], Cmd:[0x" << std::hex << +cmd << "]\n";
+
+    // Status code.
+    ipmi_ret_t rc = IPMI_CC_OK;
+
+    *data_len = sizeof(NOT_IMPLEMENTED);
+
+    // Now pack actual response
+    char *msg_reply = static_cast<char *>(response);
+    std::copy(std::begin(NOT_IMPLEMENTED), std::end(NOT_IMPLEMENTED),
+              msg_reply);
+
+    return rc;
+}
+
+struct intc_app_get_buffer_size_resp
+{
+    uint8_t kcs_size;
+    uint8_t ipmb_size;
+} __attribute__((packed));
+
+static constexpr int KCS_MAX_BUFFER_SIZE = 63;
+static constexpr int IPMB_MAX_BUFFER_SIZE = 128;
+static ipmi_ret_t ipmi_intel_app_get_buffer_size(
+    ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request,
+    ipmi_response_t response, ipmi_data_len_t data_len, ipmi_context_t context)
+{
+    auto msg_reply =
+        reinterpret_cast<intc_app_get_buffer_size_resp *>(response);
+    // for now this is hard coded; really this number is dependent on
+    // the BMC kcs driver as well as the host kcs driver....
+    // we can't know the latter.
+    msg_reply->kcs_size = KCS_MAX_BUFFER_SIZE / 4;
+    msg_reply->ipmb_size = IPMB_MAX_BUFFER_SIZE / 4;
+    *data_len = sizeof(*msg_reply);
+
+    return IPMI_CC_OK;
+}
+
+static constexpr ipmi_cmd_t IPMI_CMD_FW_GET_FW_VERSION_INFO = 0x20;
+static constexpr ipmi_cmd_t IPMI_CMD_FW_GET_FW_SEC_VERSION_INFO = 0x21;
+static constexpr ipmi_cmd_t IPMI_CMD_FW_GET_FW_UPD_CHAN_INFO = 0x22;
+static constexpr ipmi_cmd_t IPMI_CMD_FW_GET_BMC_EXEC_CTX = 0x23;
+static constexpr ipmi_cmd_t IPMI_CMD_FW_GET_ROOT_CERT_INFO = 0x24;
+static constexpr ipmi_cmd_t IPMI_CMD_FW_GET_ROOT_CERT_DATA = 0x25;
+static constexpr ipmi_cmd_t IPMI_CMD_FW_GET_FW_UPDATE_RAND_NUM = 0x26;
+static constexpr ipmi_cmd_t IPMI_CMD_FW_SET_FW_UPDATE_MODE = 0x27;
+static constexpr ipmi_cmd_t IPMI_CMD_FW_EXIT_FW_UPDATE_MODE = 0x28;
+static constexpr ipmi_cmd_t IPMI_CMD_FW_UPDATE_CONTROL = 0x29;
+static constexpr ipmi_cmd_t IPMI_CMD_FW_GET_STATUS = 0x2a;
+static constexpr ipmi_cmd_t IPMI_CMD_FW_SET_FW_UPDATE_OPTIONS = 0x2b;
+static constexpr ipmi_cmd_t IPMI_CMD_FW_IMAGE_WRITE = 0x2c;
+static constexpr ipmi_cmd_t IPMI_CMD_FW_GET_TIMESTAMP = 0x2d;
+static constexpr ipmi_cmd_t IPMI_CMD_FW_GET_UPDATE_ERR_MSG = 0xe0;
+static constexpr ipmi_cmd_t IPMI_CMD_FW_GET_REMOTE_FW_INFO = 0xf0;
+
+static constexpr ipmi_netfn_t NETFUN_INTC_APP = 0x30;
+static constexpr ipmi_cmd_t IPMI_CMD_INTC_GET_BUFFER_SIZE = 0x66;
+
+static void register_netfn_firmware_functions()
+{
+    // guarantee that we start with an already timed out timestamp
+    fw_random_number_timestamp =
+        std::chrono::steady_clock::now() - FW_RANDOM_NUMBER_TTL;
+
+    unlink(FIRMWARE_BUFFER_FILE);
+
+    // <Get BT Interface Capabilities>
+    if (DEBUG)
+        std::cerr << "Registering firmware update commands\n";
+
+    // get firmware version information
+    ipmi_register_callback(NETFUN_FIRMWARE, IPMI_CMD_FW_GET_FW_VERSION_INFO,
+                           NULL, ipmi_firmware_get_fw_version_info,
+                           PRIVILEGE_ADMIN);
+
+    // get firmware security version information
+    ipmi_register_callback(NETFUN_FIRMWARE, IPMI_CMD_FW_GET_FW_SEC_VERSION_INFO,
+                           NULL, ipmi_firmware_get_fw_security_revision,
+                           PRIVILEGE_ADMIN);
+
+    // get channel information (max transfer sizes)
+    ipmi_register_callback(NETFUN_FIRMWARE, IPMI_CMD_FW_GET_FW_UPD_CHAN_INFO,
+                           NULL, ipmi_firmware_max_transfer_size,
+                           PRIVILEGE_ADMIN);
+
+    // get bmc execution context
+    ipmi_register_callback(NETFUN_FIRMWARE, IPMI_CMD_FW_GET_BMC_EXEC_CTX, NULL,
+                           ipmi_firmware_get_fw_execution_context,
+                           PRIVILEGE_ADMIN);
+
+    // get root certificate information
+    ipmi_register_callback(NETFUN_FIRMWARE, IPMI_CMD_FW_GET_ROOT_CERT_INFO,
+                           NULL, ipmi_firmware_get_root_cert_info,
+                           PRIVILEGE_ADMIN);
+
+    // get root certificate data
+    ipmi_register_callback(NETFUN_FIRMWARE, IPMI_CMD_FW_GET_ROOT_CERT_DATA,
+                           NULL, ipmi_firmware_get_root_cert_data,
+                           PRIVILEGE_ADMIN);
+
+    // generate bmc fw update random number (for enter fw tranfer mode)
+    ipmi_register_callback(NETFUN_FIRMWARE, IPMI_CMD_FW_GET_FW_UPDATE_RAND_NUM,
+                           NULL, ipmi_firmware_get_fw_random_number,
+                           PRIVILEGE_ADMIN);
+
+    // enter firmware update mode
+    ipmi_register_callback(NETFUN_FIRMWARE, IPMI_CMD_FW_SET_FW_UPDATE_MODE,
+                           NULL, ipmi_firmware_enter_fw_transfer_mode,
+                           PRIVILEGE_ADMIN);
+
+    // exit firmware update mode
+    ipmi_register_callback(NETFUN_FIRMWARE, IPMI_CMD_FW_EXIT_FW_UPDATE_MODE,
+                           NULL, ipmi_firmware_exit_fw_update_mode,
+                           PRIVILEGE_ADMIN);
+
+    // firmware control mechanism (set filename, usb, etc.)
+    ipmi_register_callback(NETFUN_FIRMWARE, IPMI_CMD_FW_UPDATE_CONTROL, NULL,
+                           ipmi_firmware_control, PRIVILEGE_ADMIN);
+
+    // get firmware update status
+    ipmi_register_callback(NETFUN_FIRMWARE, IPMI_CMD_FW_GET_STATUS, NULL,
+                           ipmi_firmware_get_status, PRIVILEGE_ADMIN);
+
+    // set firmware update options (no downgrade, etc.)
+    ipmi_register_callback(NETFUN_FIRMWARE, IPMI_CMD_FW_SET_FW_UPDATE_OPTIONS,
+                           NULL, ipmi_firmware_update_options, PRIVILEGE_ADMIN);
+
+    // write image data
+    ipmi_register_callback(NETFUN_FIRMWARE, IPMI_CMD_FW_IMAGE_WRITE, NULL,
+                           ipmi_firmware_write_data, PRIVILEGE_ADMIN);
+
+    // get update timestamps
+    ipmi_register_callback(NETFUN_FIRMWARE, IPMI_CMD_FW_GET_TIMESTAMP, NULL,
+                           ipmi_firmware_wildcard_handler, PRIVILEGE_ADMIN);
+
+    // get error message (when in error state)
+    ipmi_register_callback(NETFUN_FIRMWARE, IPMI_CMD_FW_GET_UPDATE_ERR_MSG,
+                           NULL, ipmi_firmware_wildcard_handler,
+                           PRIVILEGE_ADMIN);
+
+    // get remote firmware information (PSU, HSBP, etc.)
+    ipmi_register_callback(NETFUN_FIRMWARE, IPMI_CMD_FW_GET_REMOTE_FW_INFO,
+                           NULL, ipmi_firmware_wildcard_handler,
+                           PRIVILEGE_ADMIN);
+
+    // <Wildcard Command>
+    ipmi_register_callback(NETFUN_FIRMWARE, IPMI_CMD_WILDCARD, NULL,
+                           ipmi_firmware_wildcard_handler, PRIVILEGE_ADMIN);
+
+    // get buffer size is used by fw update (exclusively?)
+    ipmi_register_callback(NETFUN_INTC_APP, IPMI_CMD_INTC_GET_BUFFER_SIZE, NULL,
+                           ipmi_intel_app_get_buffer_size, PRIVILEGE_USER);
+    return;
+}