import phosphor-net-ipmid as rmcpbridge

Change-Id: I4e62b33c178c9ee02d623579ba174c1359720525
diff --git a/.build.sh b/.build.sh
new file mode 100755
index 0000000..d4e4b26
--- /dev/null
+++ b/.build.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+Dockerfile=$(cat << EOF
+FROM ubuntu:15.10
+RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get upgrade -yy
+RUN DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -yy make g++ gcc libsystemd-dev libc6-dev pkg-config
+RUN groupadd -g ${GROUPS[0]} ${USER} && useradd -d ${HOME} -m -u ${UID} -g ${GROUPS[0]} ${USER}
+USER ${USER}
+ENV HOME ${HOME}
+RUN /bin/bash
+EOF
+)
+
+docker pull ubuntu:15.10
+docker build -t temp - <<< "${Dockerfile}"
+
+gcc --version
+
+docker run --cap-add=sys_admin --net=host --rm=true --user="${USER}" \
+    -w "${PWD}" -v "${HOME}":"${HOME}" -t temp make
diff --git a/.clang-tidy b/.clang-tidy
index 4b6eca0..75d64dc 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -1,7 +1,12 @@
 Checks: '
-    -*,
-    bugprone-unchecked-optional-access,
-    readability-identifier-naming
+-*,
+modernize-use-nullptr,
 '
+
+# Treat all warnings as errors
 WarningsAsErrors: '*'
+
+# Apply checks to all files
 HeaderFilterRegex: '(?!^subprojects).*'
+
+CheckOptions: []
diff --git a/.gitignore b/.gitignore
index 51ef08e..f0b223f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,17 @@
+# ignore vim swap files
+.*.sw*
+# failures from patch
+*.orig
+*.rej
+# backup files from some editors
+*~
+.cscope/
+
+# Meson
 /build*/
 /subprojects/*
-!subprojects/*.wrap
+!/subprojects/*.wrap
+subprojects/CLI11.wrap
+subprojects/function2.wrap
+subprojects/googletest.wrap
+subprojects/stdplus.wrap
diff --git a/.shellcheck b/.shellcheck
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/.shellcheck
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..d8d6c1d
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,9 @@
+language: c
+
+sudo: required
+
+services:
+    - docker
+
+script:
+    - ./.build.sh
diff --git a/LICENSE b/LICENSE
index 8dada3e..8f71f43 100644
--- a/LICENSE
+++ b/LICENSE
@@ -199,3 +199,4 @@
    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.
+
diff --git a/OWNERS b/OWNERS
index 05f2bbe..9d16375 100644
--- a/OWNERS
+++ b/OWNERS
@@ -32,14 +32,30 @@
 # data within them will be kept sorted.
 
 owners:
+- liuxiwei@ieisystem.com
 - rushtotom@gmail.com
 - vernon.mauery@gmail.com
 
 reviewers:
+- anoo@us.ibm.com
+- deepak.kodihalli.83@gmail.com
+- ratankgupta31@gmail.com
 
 matchers:
 
 openbmc:
+- name: Adriana Kobylak
+  email: anoo@us.ibm.com
+  discord: anoo
+- name: Deepak Kodihalli
+  email: deepak.kodihalli.83@gmail.com
+  discord: dkodihal
+- name: George Liu
+  email: liuxiwei@ieisystem.com
+  discord: George Liu
+- name: Ratan Gupta
+  email: ratankgupta31@gmail.com
+  discord: rgupta
 - name: Tom Joseph
   email: rushtotom@gmail.com
   discord: tomjose
diff --git a/README.md b/README.md
index 89aba20..9c53680 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,41 @@
-# phosphor-net-ipmid
+# phosphor-host-ipmid
 
-## To Build
+## Compile ipmid with default options
 
-To build this package, do the following steps:
-
-```sh
-1. ./bootstrap.sh
-2. ./configure ${CONFIGURE_FLAGS}
-3. make
+```ascii
+meson builddir
+ninja -C builddir
 ```
 
-To clean the repository run `./bootstrap.sh clean`.
+## Compile ipmid with yocto defaults
+
+```ascii
+meson builddir -Dbuildtype=minsize -Db_lto=true -Dtests=disabled
+ninja -C builddir
+```
+
+If any of the dependencies are not found on the host system during
+configuration, meson automatically gets them via its wrap dependencies mentioned
+in `ipmid/subprojects`.
+
+## Enable/Disable meson wrap feature
+
+```ascii
+meson builddir -Dwrap_mode=nofallback
+ninja -C builddir
+```
+
+## Enable debug traces
+
+```ascii
+meson builddir -Dbuildtype=debug
+ninja -C builddir
+```
+
+## Generate test coverage report
+
+```ascii
+meson builddir -Db_coverage=true -Dtests=enabled
+ninja -C builddir test
+ninja -C builddir coverage
+```
diff --git a/app/channel.cpp b/app/channel.cpp
new file mode 100644
index 0000000..e7acf36
--- /dev/null
+++ b/app/channel.cpp
@@ -0,0 +1,180 @@
+#include "channel.hpp"
+
+#include "user_channel/channel_layer.hpp"
+
+#include <arpa/inet.h>
+
+#include <boost/process/v1/child.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <fstream>
+#include <set>
+#include <string>
+
+using namespace phosphor::logging;
+using namespace sdbusplus::error::xyz::openbmc_project::common;
+
+namespace cipher
+{
+
+/** @brief Get the supported Cipher records
+ *
+ * The cipher records are read from the JSON file and converted into
+ * 1. cipher suite record format mentioned in the IPMI specification. The
+ * records can be either OEM or standard cipher. Each json entry is parsed and
+ * converted into the cipher record format and pushed into the vector.
+ * 2. Algorithms listed in vector format
+ *
+ * @return pair of vector containing 1. all the cipher suite records. 2.
+ * Algorithms supported
+ *
+ */
+std::pair<std::vector<uint8_t>, std::vector<uint8_t>> getCipherRecords()
+{
+    std::vector<uint8_t> cipherRecords;
+    std::vector<uint8_t> supportedAlgorithmRecords;
+    // create set to get the unique supported algorithms
+    std::set<uint8_t> supportedAlgorithmSet;
+
+    std::ifstream jsonFile(configFile);
+    if (!jsonFile.is_open())
+    {
+        lg2::error("Channel Cipher suites file not found");
+        elog<InternalFailure>();
+    }
+
+    auto data = Json::parse(jsonFile, nullptr, false);
+    if (data.is_discarded())
+    {
+        lg2::error("Parsing channel cipher suites JSON failed");
+        elog<InternalFailure>();
+    }
+
+    for (const auto& record : data)
+    {
+        if (record.find(oem) != record.end())
+        {
+            // OEM cipher suite - 0xC1
+            cipherRecords.push_back(oemCipherSuite);
+            // Cipher Suite ID
+            cipherRecords.push_back(record.value(cipher, 0));
+            // OEM IANA - 3 bytes
+            cipherRecords.push_back(record.value(oem, 0));
+            cipherRecords.push_back(record.value(oem, 0) >> 8);
+            cipherRecords.push_back(record.value(oem, 0) >> 16);
+        }
+        else
+        {
+            // Standard cipher suite - 0xC0
+            cipherRecords.push_back(stdCipherSuite);
+            // Cipher Suite ID
+            cipherRecords.push_back(record.value(cipher, 0));
+        }
+
+        // Authentication algorithm number
+        cipherRecords.push_back(record.value(auth, 0));
+        supportedAlgorithmSet.insert(record.value(auth, 0));
+
+        // Integrity algorithm number
+        cipherRecords.push_back(record.value(integrity, 0) | integrityTag);
+        supportedAlgorithmSet.insert(record.value(integrity, 0) | integrityTag);
+
+        // Confidentiality algorithm number
+        cipherRecords.push_back(record.value(conf, 0) | confTag);
+        supportedAlgorithmSet.insert(record.value(conf, 0) | confTag);
+    }
+
+    // copy the set to supportedAlgorithmRecord which is vector based.
+    std::copy(supportedAlgorithmSet.begin(), supportedAlgorithmSet.end(),
+              std::back_inserter(supportedAlgorithmRecords));
+
+    return std::make_pair(cipherRecords, supportedAlgorithmRecords);
+}
+
+} // namespace cipher
+
+/** @brief this command is used to look up what authentication, integrity,
+ *  confidentiality algorithms are supported.
+ *
+ *  @ param ctx - context pointer
+ *  @ param channelNumber - channel number
+ *  @ param payloadType - payload type
+ *  @ param listIndex - list index
+ *  @ param algoSelectBit - list algorithms
+ *
+ *  @returns ipmi completion code plus response data
+ *  - rspChannel - channel number for authentication algorithm.
+ *  - rspRecords - cipher suite records.
+ **/
+ipmi::RspType<uint8_t,             // Channel Number
+              std::vector<uint8_t> // Cipher Records
+              >
+    getChannelCipherSuites(ipmi::Context::ptr ctx, uint4_t channelNumber,
+                           uint4_t reserved1, uint8_t payloadType,
+                           uint6_t listIndex, uint1_t reserved2,
+                           uint1_t algoSelectBit)
+{
+    static std::vector<uint8_t> cipherRecords;
+    static std::vector<uint8_t> supportedAlgorithms;
+    static auto recordInit = false;
+
+    uint8_t rspChannel = ipmi::convertCurrentChannelNum(
+        static_cast<uint8_t>(channelNumber), ctx->channel);
+
+    if (!ipmi::isValidChannel(rspChannel) || reserved1 != 0 || reserved2 != 0)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+    if (!ipmi::isValidPayloadType(static_cast<ipmi::PayloadType>(payloadType)))
+    {
+        lg2::debug("Get channel cipher suites - Invalid payload type");
+        constexpr uint8_t ccPayloadTypeNotSupported = 0x80;
+        return ipmi::response(ccPayloadTypeNotSupported);
+    }
+
+    if (!recordInit)
+    {
+        try
+        {
+            std::tie(cipherRecords, supportedAlgorithms) =
+                cipher::getCipherRecords();
+            recordInit = true;
+        }
+        catch (const std::exception& e)
+        {
+            return ipmi::responseUnspecifiedError();
+        }
+    }
+
+    const std::vector<uint8_t>& records =
+        algoSelectBit ? cipherRecords : supportedAlgorithms;
+    static constexpr auto respSize = 16;
+
+    // Session support is available in active LAN channels.
+    if ((ipmi::getChannelSessionSupport(rspChannel) ==
+         ipmi::EChannelSessSupported::none) ||
+        !(ipmi::doesDeviceExist(rspChannel)))
+    {
+        lg2::debug("Get channel cipher suites - Device does not exist");
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    // List index(00h-3Fh), 0h selects the first set of 16, 1h selects the next
+    // set of 16 and so on.
+
+    // Calculate the number of record data bytes to be returned.
+    auto start =
+        std::min(static_cast<size_t>(listIndex) * respSize, records.size());
+    auto end = std::min((static_cast<size_t>(listIndex) * respSize) + respSize,
+                        records.size());
+    auto size = end - start;
+
+    std::vector<uint8_t> rspRecords;
+    std::copy_n(records.data() + start, size, std::back_inserter(rspRecords));
+
+    return ipmi::responseSuccess(rspChannel, rspRecords);
+}
diff --git a/app/channel.hpp b/app/channel.hpp
new file mode 100644
index 0000000..a6b8703
--- /dev/null
+++ b/app/channel.hpp
@@ -0,0 +1,43 @@
+#include "nlohmann/json.hpp"
+
+#include <ipmid/api.hpp>
+
+/** @brief this command is used to look up what authentication, integrity,
+ *  confidentiality algorithms are supported.
+ *
+ *  @ param ctx - context pointer
+ *  @ param channelNumber - channel number
+ *  @ param payloadType - payload type
+ *  @ param listIndex - list index
+ *  @ param algoSelectBit - list algorithms
+ *
+ *  @returns ipmi completion code plus response data
+ *  - rspChannel - channel number for authentication algorithm.
+ *  - rspRecords - cipher suite records.
+ **/
+ipmi::RspType<uint8_t,             // Channel Number
+              std::vector<uint8_t> // Cipher Records
+              >
+    getChannelCipherSuites(ipmi::Context::ptr ctx, uint4_t channelNumber,
+                           uint4_t reserved1, uint8_t payloadType,
+                           uint6_t listIndex, uint1_t reserved2,
+                           uint1_t algoSelectBit);
+
+namespace cipher
+{
+
+static constexpr auto listCipherSuite = 0x80;
+
+using Json = nlohmann::json;
+static constexpr auto configFile = "/usr/share/ipmi-providers/cipher_list.json";
+static constexpr auto cipher = "cipher";
+static constexpr auto stdCipherSuite = 0xC0;
+static constexpr auto oemCipherSuite = 0xC1;
+static constexpr auto oem = "oemiana";
+static constexpr auto auth = "authentication";
+static constexpr auto integrity = "integrity";
+static constexpr auto integrityTag = 0x40;
+static constexpr auto conf = "confidentiality";
+static constexpr auto confTag = 0x80;
+
+} // namespace cipher
diff --git a/app/meson.build b/app/meson.build
new file mode 100644
index 0000000..bc7c598
--- /dev/null
+++ b/app/meson.build
@@ -0,0 +1,14 @@
+app_inc = include_directories('.')
+
+app_pre = declare_dependency(include_directories: [root_inc, app_inc])
+
+app_lib = static_library(
+    'app',
+    'channel.cpp',
+    'watchdog.cpp',
+    'watchdog_service.cpp',
+    implicit_include_directories: false,
+    dependencies: app_pre,
+)
+
+app_dep = declare_dependency(link_with: app_lib, dependencies: app_pre)
diff --git a/app/watchdog.cpp b/app/watchdog.cpp
new file mode 100644
index 0000000..aea13fd
--- /dev/null
+++ b/app/watchdog.cpp
@@ -0,0 +1,436 @@
+#include "watchdog.hpp"
+
+#include "watchdog_service.hpp"
+
+#include <endian.h>
+
+#include <ipmid/api.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <bitset>
+#include <cstdint>
+#include <string>
+
+using phosphor::logging::commit;
+using phosphor::logging::level;
+using phosphor::logging::log;
+using sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+
+static bool lastCallSuccessful = false;
+
+void reportError()
+{
+    // We don't want to fill the SEL with errors if the daemon dies and doesn't
+    // come back but the watchdog keeps on ticking. Instead, we only report the
+    // error if we haven't reported one since the last successful call
+    if (!lastCallSuccessful)
+    {
+        return;
+    }
+    lastCallSuccessful = false;
+
+    // TODO: This slow down the end of the IPMI transaction waiting
+    // for the commit to finish. commit<>() can take at least 5 seconds
+    // to complete. 5s is very slow for an IPMI command and ends up
+    // congesting the IPMI channel needlessly, especially if the watchdog
+    // is ticking fairly quickly and we have some transient issues.
+    commit<InternalFailure>();
+}
+
+ipmi::RspType<> ipmiAppResetWatchdogTimer()
+{
+    try
+    {
+        WatchdogService wd_service;
+
+        // Notify the caller if we haven't initialized our timer yet
+        // so it can configure actions and timeouts
+        if (!wd_service.getInitialized())
+        {
+            lastCallSuccessful = true;
+
+            constexpr uint8_t ccWatchdogNotInit = 0x80;
+            return ipmi::response(ccWatchdogNotInit);
+        }
+
+        // The ipmi standard dictates we enable the watchdog during reset
+        wd_service.resetTimeRemaining(true);
+        lastCallSuccessful = true;
+        return ipmi::responseSuccess();
+    }
+    catch (const InternalFailure& e)
+    {
+        reportError();
+        return ipmi::responseUnspecifiedError();
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("wd_reset: {ERROR}", "ERROR", e);
+        return ipmi::responseUnspecifiedError();
+    }
+    catch (...)
+    {
+        lg2::error("wd_reset: Unknown Error");
+        return ipmi::responseUnspecifiedError();
+    }
+}
+
+static constexpr uint8_t wd_timeout_action_mask = 0x3;
+
+static constexpr uint8_t wdTimerUseResTimer1 = 0x0;
+static constexpr uint8_t wdTimerUseResTimer2 = 0x6;
+static constexpr uint8_t wdTimerUseResTimer3 = 0x7;
+
+static constexpr uint8_t wdTimeoutActionMax = 3;
+static constexpr uint8_t wdTimeoutInterruptTimer = 0x04;
+
+enum class IpmiAction : uint8_t
+{
+    None = 0x0,
+    HardReset = 0x1,
+    PowerOff = 0x2,
+    PowerCycle = 0x3,
+};
+
+/** @brief Converts an IPMI Watchdog Action to DBUS defined action
+ *  @param[in] ipmi_action The IPMI Watchdog Action
+ *  @return The Watchdog Action that the ipmi_action maps to
+ */
+WatchdogService::Action ipmiActionToWdAction(IpmiAction ipmi_action)
+{
+    switch (ipmi_action)
+    {
+        case IpmiAction::None:
+        {
+            return WatchdogService::Action::None;
+        }
+        case IpmiAction::HardReset:
+        {
+            return WatchdogService::Action::HardReset;
+        }
+        case IpmiAction::PowerOff:
+        {
+            return WatchdogService::Action::PowerOff;
+        }
+        case IpmiAction::PowerCycle:
+        {
+            return WatchdogService::Action::PowerCycle;
+        }
+        default:
+        {
+            throw std::domain_error("IPMI Action is invalid");
+        }
+    }
+}
+
+enum class IpmiTimerUse : uint8_t
+{
+    Reserved = 0x0,
+    BIOSFRB2 = 0x1,
+    BIOSPOST = 0x2,
+    OSLoad = 0x3,
+    SMSOS = 0x4,
+    OEM = 0x5,
+};
+
+WatchdogService::TimerUse ipmiTimerUseToWdTimerUse(IpmiTimerUse ipmiTimerUse)
+{
+    switch (ipmiTimerUse)
+    {
+        case IpmiTimerUse::Reserved:
+        {
+            return WatchdogService::TimerUse::Reserved;
+        }
+        case IpmiTimerUse::BIOSFRB2:
+        {
+            return WatchdogService::TimerUse::BIOSFRB2;
+        }
+        case IpmiTimerUse::BIOSPOST:
+        {
+            return WatchdogService::TimerUse::BIOSPOST;
+        }
+        case IpmiTimerUse::OSLoad:
+        {
+            return WatchdogService::TimerUse::OSLoad;
+        }
+        case IpmiTimerUse::SMSOS:
+        {
+            return WatchdogService::TimerUse::SMSOS;
+        }
+        case IpmiTimerUse::OEM:
+        {
+            return WatchdogService::TimerUse::OEM;
+        }
+        default:
+        {
+            return WatchdogService::TimerUse::Reserved;
+        }
+    }
+}
+
+static bool timerNotLogFlags = false;
+static std::bitset<8> timerUseExpirationFlags = 0;
+static uint3_t timerPreTimeoutInterrupt = 0;
+static constexpr uint8_t wdExpirationFlagReservedBit0 = 0x0;
+static constexpr uint8_t wdExpirationFlagReservedBit6 = 0x6;
+static constexpr uint8_t wdExpirationFlagReservedBit7 = 0x7;
+
+/**@brief The Set Watchdog Timer ipmi command.
+ *
+ * @param
+ * - timerUse
+ * - dontStopTimer
+ * - dontLog
+ * - timerAction
+ * - pretimeout
+ * - expireFlags
+ * - initialCountdown
+ *
+ * @return completion code on success.
+ **/
+ipmi::RspType<> ipmiSetWatchdogTimer(
+    uint3_t timerUse, uint3_t reserved, bool dontStopTimer, bool dontLog,
+    uint3_t timeoutAction, uint1_t reserved1, uint3_t preTimeoutInterrupt,
+    uint1_t reserved2, uint8_t preTimeoutInterval, std::bitset<8> expFlagValue,
+    uint16_t initialCountdown)
+{
+    if ((timerUse == wdTimerUseResTimer1) ||
+        (timerUse == wdTimerUseResTimer2) ||
+        (timerUse == wdTimerUseResTimer3) ||
+        (timeoutAction > wdTimeoutActionMax) ||
+        (preTimeoutInterrupt == wdTimeoutInterruptTimer) ||
+        (reserved | reserved1 | reserved2 |
+         expFlagValue.test(wdExpirationFlagReservedBit0) |
+         expFlagValue.test(wdExpirationFlagReservedBit6) |
+         expFlagValue.test(wdExpirationFlagReservedBit7)))
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    if (preTimeoutInterval > (initialCountdown / 10))
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    timerNotLogFlags = dontLog;
+    timerPreTimeoutInterrupt = preTimeoutInterrupt;
+
+    try
+    {
+        WatchdogService wd_service;
+        // Stop the timer if the don't stop bit is not set
+        if (!(dontStopTimer))
+        {
+            wd_service.setEnabled(false);
+        }
+
+        // Set the action based on the request
+        const auto ipmi_action = static_cast<IpmiAction>(
+            static_cast<uint8_t>(timeoutAction) & wd_timeout_action_mask);
+        wd_service.setExpireAction(ipmiActionToWdAction(ipmi_action));
+
+        const auto ipmiTimerUse = types::enum_cast<IpmiTimerUse>(timerUse);
+        wd_service.setTimerUse(ipmiTimerUseToWdTimerUse(ipmiTimerUse));
+
+        wd_service.setExpiredTimerUse(WatchdogService::TimerUse::Reserved);
+
+        timerUseExpirationFlags &= ~expFlagValue;
+
+        // Set the new interval and the time remaining deci -> mill seconds
+        const uint64_t interval = initialCountdown * 100;
+        wd_service.setInterval(interval);
+        wd_service.resetTimeRemaining(false);
+
+        // Mark as initialized so that future resets behave correctly
+        wd_service.setInitialized(true);
+        wd_service.setLogTimeout(!dontLog);
+
+        lastCallSuccessful = true;
+        return ipmi::responseSuccess();
+    }
+    catch (const std::domain_error&)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+    catch (const InternalFailure& e)
+    {
+        reportError();
+        return ipmi::responseUnspecifiedError();
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("wd_set: {ERROR}", "ERROR", e);
+        return ipmi::responseUnspecifiedError();
+    }
+    catch (...)
+    {
+        lg2::error("wd_set: Unknown Error");
+        return ipmi::responseUnspecifiedError();
+    }
+}
+
+/** @brief Converts a DBUS Watchdog Action to IPMI defined action
+ *  @param[in] wd_action The DBUS Watchdog Action
+ *  @return The IpmiAction that the wd_action maps to
+ */
+IpmiAction wdActionToIpmiAction(WatchdogService::Action wd_action)
+{
+    switch (wd_action)
+    {
+        case WatchdogService::Action::None:
+        {
+            return IpmiAction::None;
+        }
+        case WatchdogService::Action::HardReset:
+        {
+            return IpmiAction::HardReset;
+        }
+        case WatchdogService::Action::PowerOff:
+        {
+            return IpmiAction::PowerOff;
+        }
+        case WatchdogService::Action::PowerCycle:
+        {
+            return IpmiAction::PowerCycle;
+        }
+        default:
+        {
+            // We have no method via IPMI to signal that the action is unknown
+            // or unmappable in some way.
+            // Just ignore the error and return NONE so the host can reconcile.
+            return IpmiAction::None;
+        }
+    }
+}
+
+IpmiTimerUse wdTimerUseToIpmiTimerUse(WatchdogService::TimerUse wdTimerUse)
+{
+    switch (wdTimerUse)
+    {
+        case WatchdogService::TimerUse::Reserved:
+        {
+            return IpmiTimerUse::Reserved;
+        }
+        case WatchdogService::TimerUse::BIOSFRB2:
+        {
+            return IpmiTimerUse::BIOSFRB2;
+        }
+        case WatchdogService::TimerUse::BIOSPOST:
+        {
+            return IpmiTimerUse::BIOSPOST;
+        }
+        case WatchdogService::TimerUse::OSLoad:
+        {
+            return IpmiTimerUse::OSLoad;
+        }
+
+        case WatchdogService::TimerUse::SMSOS:
+        {
+            return IpmiTimerUse::SMSOS;
+        }
+        case WatchdogService::TimerUse::OEM:
+        {
+            return IpmiTimerUse::OEM;
+        }
+        default:
+        {
+            return IpmiTimerUse::Reserved;
+        }
+    }
+}
+
+/**@brief The getWatchdogTimer ipmi command.
+ *
+ * @return Completion code plus timer details.
+ * - timerUse
+ * - timerAction
+ * - pretimeout
+ * - expireFlags
+ * - initialCountdown
+ * - presentCountdown
+ **/
+ipmi::RspType<uint3_t,        // timerUse - timer use
+              uint3_t,        // timerUse - reserved
+              bool,           // timerUse - timer is started
+              bool,           // timerUse - don't log
+
+              uint3_t,        // timerAction - timeout action
+              uint1_t,        // timerAction - reserved
+              uint3_t,        // timerAction - pre-timeout interrupt
+              uint1_t,        // timerAction - reserved
+
+              uint8_t,        // pretimeout
+              std::bitset<8>, // expireFlags
+              uint16_t,       // initial Countdown - Little Endian (deciseconds)
+              uint16_t        // present Countdown - Little Endian (deciseconds)
+              >
+    ipmiGetWatchdogTimer()
+{
+    uint16_t presentCountdown = 0;
+    uint8_t pretimeout = 0;
+
+    try
+    {
+        WatchdogService wd_service;
+        WatchdogService::Properties wd_prop = wd_service.getProperties();
+
+        // Build and return the response
+        // Interval and timeRemaining need converted from milli -> deci seconds
+        uint16_t initialCountdown = htole16(wd_prop.interval / 100);
+
+        if (wd_prop.expiredTimerUse != WatchdogService::TimerUse::Reserved)
+        {
+            timerUseExpirationFlags.set(static_cast<uint8_t>(
+                wdTimerUseToIpmiTimerUse(wd_prop.expiredTimerUse)));
+        }
+
+        if (wd_prop.enabled)
+        {
+            presentCountdown = htole16(wd_prop.timeRemaining / 100);
+        }
+        else
+        {
+            if (wd_prop.expiredTimerUse == WatchdogService::TimerUse::Reserved)
+            {
+                presentCountdown = initialCountdown;
+            }
+            else
+            {
+                presentCountdown = 0;
+                // Automatically clear it whenever a timer expiration occurs.
+                timerNotLogFlags = false;
+            }
+        }
+
+        // TODO: Do something about having pretimeout support
+        pretimeout = 0;
+
+        lastCallSuccessful = true;
+        return ipmi::responseSuccess(
+            types::enum_cast<uint3_t>(
+                wdTimerUseToIpmiTimerUse(wd_prop.timerUse)),
+            0, wd_prop.enabled, timerNotLogFlags,
+            types::enum_cast<uint3_t>(
+                wdActionToIpmiAction(wd_prop.expireAction)),
+            0, timerPreTimeoutInterrupt, 0, pretimeout, timerUseExpirationFlags,
+            initialCountdown, presentCountdown);
+    }
+    catch (const InternalFailure& e)
+    {
+        reportError();
+        return ipmi::responseUnspecifiedError();
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("wd_get: {ERROR}", "ERROR", e);
+        return ipmi::responseUnspecifiedError();
+    }
+    catch (...)
+    {
+        lg2::error("wd_get: Unknown Error");
+        return ipmi::responseUnspecifiedError();
+    }
+}
diff --git a/app/watchdog.hpp b/app/watchdog.hpp
new file mode 100644
index 0000000..fa53ac7
--- /dev/null
+++ b/app/watchdog.hpp
@@ -0,0 +1,45 @@
+#pragma once
+
+#include <ipmid/api.hpp>
+
+/** @brief The RESET watchdog IPMI command.
+ */
+ipmi::RspType<> ipmiAppResetWatchdogTimer();
+
+/**@brief The setWatchdogTimer ipmi command.
+ *
+ * @param
+ * - timerUse
+ * - dontStopTimer
+ * - dontLog
+ * - timerAction
+ * - pretimeout
+ * - expireFlags
+ * - initialCountdown
+ *
+ * @return completion code on success.
+ **/
+ipmi::RspType<> ipmiSetWatchdogTimer(
+    uint3_t timerUse, uint3_t reserved, bool dontStopTimer, bool dontLog,
+    uint3_t timeoutAction, uint1_t reserved1, uint3_t preTimeoutInterrupt,
+    uint1_t reserved2, uint8_t preTimeoutInterval, std::bitset<8> expFlagValue,
+    uint16_t initialCountdown);
+
+/**@brief The getWatchdogTimer ipmi command.
+ *
+ * @return
+ * - timerUse
+ * - timerActions
+ * - pretimeout
+ * - timeruseFlags
+ * - initialCountdown
+ * - presentCountdown
+ **/
+ipmi::RspType<uint3_t, uint3_t, bool, bool,       // timerUse
+              uint3_t, uint1_t, uint3_t, uint1_t, // timerAction
+              uint8_t,                            // pretimeout
+              std::bitset<8>,                     // expireFlags
+              uint16_t, // initial Countdown - Little Endian (deciseconds)
+              uint16_t  // present Countdown - Little Endian (deciseconds)
+              >
+    ipmiGetWatchdogTimer();
diff --git a/app/watchdog_service.cpp b/app/watchdog_service.cpp
new file mode 100644
index 0000000..b67b5c7
--- /dev/null
+++ b/app/watchdog_service.cpp
@@ -0,0 +1,206 @@
+#include "watchdog_service.hpp"
+
+#include <ipmid/api.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/message.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/State/Watchdog/server.hpp>
+
+#include <exception>
+#include <stdexcept>
+#include <string>
+
+using phosphor::logging::elog;
+using phosphor::logging::entry;
+using phosphor::logging::level;
+using phosphor::logging::log;
+using sdbusplus::common::xyz::openbmc_project::state::convertForMessage;
+using sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+using sdbusplus::server::xyz::openbmc_project::state::Watchdog;
+
+static constexpr char wd_path[] = "/xyz/openbmc_project/watchdog/host0";
+static constexpr char wd_intf[] = "xyz.openbmc_project.State.Watchdog";
+static constexpr char prop_intf[] = "org.freedesktop.DBus.Properties";
+
+ipmi::ServiceCache WatchdogService::wd_service(wd_intf, wd_path);
+
+WatchdogService::WatchdogService() : bus(ipmid_get_sd_bus_connection()) {}
+
+void WatchdogService::resetTimeRemaining(bool enableWatchdog)
+{
+    bool wasValid = wd_service.isValid(bus);
+    auto request = wd_service.newMethodCall(bus, wd_intf, "ResetTimeRemaining");
+    request.append(enableWatchdog);
+    try
+    {
+        auto response = bus.call(request);
+    }
+    catch (const std::exception& e)
+    {
+        wd_service.invalidate();
+        if (wasValid)
+        {
+            // Retry the request once in case the cached service was stale
+            return resetTimeRemaining(enableWatchdog);
+        }
+        lg2::error("WatchdogService: Method error resetting time remaining, "
+                   "ENABLE_WATCHDOG: {ENABLE_WATCHDOG}, ERROR: {ERROR}",
+                   "ENABLE_WATCHDOG", enableWatchdog, "ERROR", e);
+        elog<InternalFailure>();
+    }
+}
+
+WatchdogService::Properties WatchdogService::getProperties()
+{
+    bool wasValid = wd_service.isValid(bus);
+    auto request = wd_service.newMethodCall(bus, prop_intf, "GetAll");
+    request.append(wd_intf);
+
+    std::map<std::string, std::variant<bool, uint64_t, std::string>> properties;
+    try
+    {
+        auto response = bus.call(request);
+        response.read(properties);
+    }
+    catch (const std::exception& e)
+    {
+        wd_service.invalidate();
+        if (wasValid)
+        {
+            // Retry the request once in case the cached service was stale
+            return getProperties();
+        }
+        lg2::error("WatchdogService: Method error getting properties: {ERROR}",
+                   "ERROR", e);
+        elog<InternalFailure>();
+    }
+
+    try
+    {
+        Properties wd_prop;
+        wd_prop.initialized = std::get<bool>(properties.at("Initialized"));
+        wd_prop.enabled = std::get<bool>(properties.at("Enabled"));
+        wd_prop.expireAction = Watchdog::convertActionFromString(
+            std::get<std::string>(properties.at("ExpireAction")));
+        wd_prop.timerUse = Watchdog::convertTimerUseFromString(
+            std::get<std::string>(properties.at("CurrentTimerUse")));
+        wd_prop.expiredTimerUse = Watchdog::convertTimerUseFromString(
+            std::get<std::string>(properties.at("ExpiredTimerUse")));
+
+        wd_prop.interval = std::get<uint64_t>(properties.at("Interval"));
+        wd_prop.timeRemaining =
+            std::get<uint64_t>(properties.at("TimeRemaining"));
+        return wd_prop;
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("WatchdogService: Decode error in get properties: {ERROR}",
+                   "ERROR", e);
+        elog<InternalFailure>();
+    }
+
+    // Needed instead of elog<InternalFailure>() since the compiler can't
+    // deduce the that elog<>() always throws
+    throw std::runtime_error(
+        "WatchdogService: Should not reach end of getProperties");
+}
+
+template <typename T>
+T WatchdogService::getProperty(const std::string& key)
+{
+    bool wasValid = wd_service.isValid(bus);
+    auto request = wd_service.newMethodCall(bus, prop_intf, "Get");
+    request.append(wd_intf, key);
+    try
+    {
+        auto response = bus.call(request);
+        std::variant<T> value;
+        response.read(value);
+        return std::get<T>(value);
+    }
+    catch (const std::exception& e)
+    {
+        wd_service.invalidate();
+        if (wasValid)
+        {
+            // Retry the request once in case the cached service was stale
+            return getProperty<T>(key);
+        }
+        lg2::error("WatchdogService: Method error getting {PROPERTY}: {ERROR}",
+                   "PROPERTY", key, "ERROR", e);
+        elog<InternalFailure>();
+    }
+
+    // Needed instead of elog<InternalFailure>() since the compiler can't
+    // deduce the that elog<>() always throws
+    throw std::runtime_error(
+        "WatchdogService: Should not reach end of getProperty");
+}
+
+template <typename T>
+void WatchdogService::setProperty(const std::string& key, const T& val)
+{
+    bool wasValid = wd_service.isValid(bus);
+    auto request = wd_service.newMethodCall(bus, prop_intf, "Set");
+    request.append(wd_intf, key, std::variant<T>(val));
+    try
+    {
+        auto response = bus.call(request);
+    }
+    catch (const std::exception& e)
+    {
+        wd_service.invalidate();
+        if (wasValid)
+        {
+            // Retry the request once in case the cached service was stale
+            setProperty(key, val);
+            return;
+        }
+        lg2::error("WatchdogService: Method error setting {PROPERTY}: {ERROR}",
+                   "PROPERTY", key, "ERROR", e);
+        elog<InternalFailure>();
+    }
+}
+
+bool WatchdogService::getInitialized()
+{
+    return getProperty<bool>("Initialized");
+}
+
+void WatchdogService::setInitialized(bool initialized)
+{
+    setProperty("Initialized", initialized);
+}
+
+void WatchdogService::setEnabled(bool enabled)
+{
+    setProperty("Enabled", enabled);
+}
+
+void WatchdogService::setLogTimeout(bool LogTimeout)
+{
+    setProperty("LogTimeout", LogTimeout);
+}
+
+void WatchdogService::setExpireAction(Action expireAction)
+{
+    setProperty("ExpireAction", convertForMessage(expireAction));
+}
+
+void WatchdogService::setTimerUse(TimerUse timerUse)
+{
+    setProperty("CurrentTimerUse", convertForMessage(timerUse));
+}
+
+void WatchdogService::setExpiredTimerUse(TimerUse timerUse)
+{
+    setProperty("ExpiredTimerUse", convertForMessage(timerUse));
+}
+
+void WatchdogService::setInterval(uint64_t interval)
+{
+    setProperty("Interval", interval);
+}
diff --git a/app/watchdog_service.hpp b/app/watchdog_service.hpp
new file mode 100644
index 0000000..5717066
--- /dev/null
+++ b/app/watchdog_service.hpp
@@ -0,0 +1,122 @@
+#pragma once
+#include <ipmid/utils.hpp>
+#include <sdbusplus/bus.hpp>
+#include <xyz/openbmc_project/State/Watchdog/server.hpp>
+
+/** @class WatchdogService
+ *  @brief Access to the running OpenBMC watchdog implementation.
+ *  @details Easy accessor for servers that implement the
+ *  xyz.openbmc_project.State.Watchdog DBus API.
+ */
+class WatchdogService
+{
+  public:
+    WatchdogService();
+
+    using Action =
+        sdbusplus::server::xyz::openbmc_project::state::Watchdog::Action;
+    using TimerUse =
+        sdbusplus::server::xyz::openbmc_project::state::Watchdog::TimerUse;
+
+    /** @brief Resets the time remaining on the watchdog.
+     *         Equivalent to setTimeRemaining(getInterval()).
+     *         Optionally enables the watchdog.
+     *
+     *  @param[in] enableWatchdog - Should the call also enable the watchdog
+     */
+    void resetTimeRemaining(bool enableWatchdog);
+
+    /** @brief Contains a copy of the properties enumerated by the
+     *         watchdog service.
+     */
+    struct Properties
+    {
+        bool initialized;
+        bool enabled;
+        Action expireAction;
+        TimerUse timerUse;
+        TimerUse expiredTimerUse;
+        uint64_t interval;
+        uint64_t timeRemaining;
+    };
+
+    /** @brief Retrieves a copy of the currently set properties on the
+     *         host watchdog
+     *
+     *  @return A populated WatchdogProperties struct
+     */
+    Properties getProperties();
+
+    /** @brief Get the value of the initialized property on the host
+     *         watchdog
+     *
+     *  @return The value of the property
+     */
+    bool getInitialized();
+
+    /** @brief Sets the value of the initialized property on the host
+     *         watchdog
+     *
+     *  @param[in] initialized - The new initializedvalue
+     */
+    void setInitialized(bool initialized);
+
+    /** @brief Sets the value of the enabled property on the host watchdog
+     *
+     *  @param[in] enabled - The new enabled value
+     */
+    void setEnabled(bool enabled);
+
+    /** @brief Sets the value of the LogTimeout property on the host watchdog
+     *
+     *  @param[in] LogTimeout - The new LogTimeout value
+     */
+    void setLogTimeout(bool LogTimeout);
+
+    /** @brief Sets the value of the expireAction property on the host watchdog
+     *
+     *  @param[in] expireAction - The new expireAction value
+     */
+    void setExpireAction(Action expireAction);
+
+    /** @brief Sets the value of the timerUse property on the host watchdog
+     *
+     *  @param[in] timerUse - The new timerUse value
+     */
+    void setTimerUse(TimerUse timerUse);
+
+    /** @brief Sets the value of the ExpiredTimerUse property on the host
+     * watchdog
+     *
+     *  @param[in] timerUse - The new timerUse value
+     */
+    void setExpiredTimerUse(TimerUse timerUse);
+
+    /** @brief Sets the value of the interval property on the host watchdog
+     *
+     *  @param[in] interval - The new interval value
+     */
+    void setInterval(uint64_t interval);
+
+  private:
+    /** @brief sdbusplus handle */
+    sdbusplus::bus_t bus;
+    /** @brief The name of the mapped host watchdog service */
+    static ipmi::ServiceCache wd_service;
+
+    /** @brief Gets the value of the property on the host watchdog
+     *
+     *  @param[in] key - The name of the property
+     *  @return The value of the property
+     */
+    template <typename T>
+    T getProperty(const std::string& key);
+
+    /** @brief Sets the value of the property on the host watchdog
+     *
+     *  @param[in] key - The name of the property
+     *  @param[in] val - The new value
+     */
+    template <typename T>
+    void setProperty(const std::string& key, const T& val);
+};
diff --git a/apphandler.cpp b/apphandler.cpp
new file mode 100644
index 0000000..1e32061
--- /dev/null
+++ b/apphandler.cpp
@@ -0,0 +1,1873 @@
+#include "config.h"
+
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/i2c-dev.h>
+#include <linux/i2c.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <systemd/sd-bus.h>
+#include <unistd.h>
+
+#include <app/channel.hpp>
+#include <app/watchdog.hpp>
+#include <apphandler.hpp>
+#include <ipmid/api.hpp>
+#include <ipmid/sessiondef.hpp>
+#include <ipmid/sessionhelper.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <sys_info_param.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/Control/Power/ACPIPowerState/server.hpp>
+#include <xyz/openbmc_project/Software/Activation/server.hpp>
+#include <xyz/openbmc_project/Software/Version/server.hpp>
+#include <xyz/openbmc_project/State/BMC/server.hpp>
+
+#include <algorithm>
+#include <array>
+#include <charconv>
+#include <cstddef>
+#include <cstdint>
+#include <filesystem>
+#include <fstream>
+#include <memory>
+#include <regex>
+#include <string>
+#include <string_view>
+#include <tuple>
+#include <vector>
+
+extern sd_bus* bus;
+
+constexpr auto bmc_state_interface = "xyz.openbmc_project.State.BMC";
+constexpr auto bmc_state_property = "CurrentBMCState";
+constexpr auto versionPurposeHostEnd = ".Host";
+
+static constexpr auto redundancyIntf =
+    "xyz.openbmc_project.Software.RedundancyPriority";
+static constexpr auto versionIntf = "xyz.openbmc_project.Software.Version";
+static constexpr auto activationIntf =
+    "xyz.openbmc_project.Software.Activation";
+static constexpr auto softwareRoot = "/xyz/openbmc_project/software";
+
+void registerNetFnAppFunctions() __attribute__((constructor));
+
+using namespace phosphor::logging;
+using namespace sdbusplus::error::xyz::openbmc_project::common;
+using Version = sdbusplus::server::xyz::openbmc_project::software::Version;
+using Activation =
+    sdbusplus::server::xyz::openbmc_project::software::Activation;
+using BMC = sdbusplus::server::xyz::openbmc_project::state::BMC;
+namespace fs = std::filesystem;
+
+#ifdef ENABLE_I2C_WHITELIST_CHECK
+typedef struct
+{
+    uint8_t busId;
+    uint8_t targetAddr;
+    uint8_t targetAddrMask;
+    std::vector<uint8_t> data;
+    std::vector<uint8_t> dataMask;
+} i2cControllerWRAllowlist;
+
+static std::vector<i2cControllerWRAllowlist>& getWRAllowlist()
+{
+    static std::vector<i2cControllerWRAllowlist> wrAllowlist;
+    return wrAllowlist;
+}
+
+static constexpr const char* i2cControllerWRAllowlistFile =
+    "/usr/share/ipmi-providers/master_write_read_white_list.json";
+
+static constexpr const char* filtersStr = "filters";
+static constexpr const char* busIdStr = "busId";
+static constexpr const char* targetAddrStr = "slaveAddr";
+static constexpr const char* targetAddrMaskStr = "slaveAddrMask";
+static constexpr const char* cmdStr = "command";
+static constexpr const char* cmdMaskStr = "commandMask";
+static constexpr int base_16 = 16;
+#endif // ENABLE_I2C_WHITELIST_CHECK
+static constexpr uint8_t oemCmdStart = 192;
+static constexpr uint8_t invalidParamSelectorStart = 8;
+static constexpr uint8_t invalidParamSelectorEnd = 191;
+
+/**
+ * @brief Returns the Version info from primary s/w object
+ *
+ * Get the Version info from the active s/w object which is having high
+ * "Priority" value(a smaller number is a higher priority) and "Purpose"
+ * is "BMC" from the list of all s/w objects those are implementing
+ * RedundancyPriority interface from the given softwareRoot path.
+ *
+ * @return On success returns the Version info from primary s/w object.
+ *
+ */
+std::string getActiveSoftwareVersionInfo(ipmi::Context::ptr ctx)
+{
+    std::string revision{};
+    ipmi::ObjectTree objectTree;
+    try
+    {
+        objectTree =
+            ipmi::getAllDbusObjects(*ctx->bus, softwareRoot, redundancyIntf);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error("Failed to fetch redundancy object from dbus, "
+                   "interface: {INTERFACE},  error: {ERROR}",
+                   "INTERFACE", redundancyIntf, "ERROR", e);
+        elog<InternalFailure>();
+    }
+
+    auto objectFound = false;
+    for (auto& softObject : objectTree)
+    {
+        auto service =
+            ipmi::getService(*ctx->bus, redundancyIntf, softObject.first);
+        auto objValueTree =
+            ipmi::getManagedObjects(*ctx->bus, service, softwareRoot);
+
+        auto minPriority = 0xFF;
+        for (const auto& objIter : objValueTree)
+        {
+            try
+            {
+                auto& intfMap = objIter.second;
+                auto& redundancyPriorityProps = intfMap.at(redundancyIntf);
+                auto& versionProps = intfMap.at(versionIntf);
+                auto& activationProps = intfMap.at(activationIntf);
+                auto priority =
+                    std::get<uint8_t>(redundancyPriorityProps.at("Priority"));
+                auto purpose =
+                    std::get<std::string>(versionProps.at("Purpose"));
+                auto activation =
+                    std::get<std::string>(activationProps.at("Activation"));
+                auto version =
+                    std::get<std::string>(versionProps.at("Version"));
+                if ((Version::convertVersionPurposeFromString(purpose) ==
+                     Version::VersionPurpose::BMC) &&
+                    (Activation::convertActivationsFromString(activation) ==
+                     Activation::Activations::Active))
+                {
+                    if (priority < minPriority)
+                    {
+                        minPriority = priority;
+                        objectFound = true;
+                        revision = std::move(version);
+                    }
+                }
+            }
+            catch (const std::exception& e)
+            {
+                lg2::error("error message: {ERROR}", "ERROR", e);
+            }
+        }
+    }
+
+    if (!objectFound)
+    {
+        lg2::error("Could not found an BMC software Object");
+        elog<InternalFailure>();
+    }
+
+    return revision;
+}
+
+bool getCurrentBmcState()
+{
+    sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+
+    // Get the Inventory object implementing the BMC interface
+    ipmi::DbusObjectInfo bmcObject =
+        ipmi::getDbusObject(bus, bmc_state_interface);
+    auto variant =
+        ipmi::getDbusProperty(bus, bmcObject.second, bmcObject.first,
+                              bmc_state_interface, bmc_state_property);
+
+    return std::holds_alternative<std::string>(variant) &&
+           BMC::convertBMCStateFromString(std::get<std::string>(variant)) ==
+               BMC::BMCState::Ready;
+}
+
+bool getCurrentBmcStateWithFallback(const bool fallbackAvailability)
+{
+    try
+    {
+        return getCurrentBmcState();
+    }
+    catch (...)
+    {
+        // Nothing provided the BMC interface, therefore return whatever was
+        // configured as the default.
+        return fallbackAvailability;
+    }
+}
+
+namespace acpi_state
+{
+using namespace sdbusplus::server::xyz::openbmc_project::control::power;
+
+const static constexpr char* acpiInterface =
+    "xyz.openbmc_project.Control.Power.ACPIPowerState";
+const static constexpr char* sysACPIProp = "SysACPIStatus";
+const static constexpr char* devACPIProp = "DevACPIStatus";
+
+enum class PowerStateType : uint8_t
+{
+    sysPowerState = 0x00,
+    devPowerState = 0x01,
+};
+
+// Defined in 20.6 of ipmi doc
+enum class PowerState : uint8_t
+{
+    s0G0D0 = 0x00,
+    s1D1 = 0x01,
+    s2D2 = 0x02,
+    s3D3 = 0x03,
+    s4 = 0x04,
+    s5G2 = 0x05,
+    s4S5 = 0x06,
+    g3 = 0x07,
+    sleep = 0x08,
+    g1Sleep = 0x09,
+    override = 0x0a,
+    legacyOn = 0x20,
+    legacyOff = 0x21,
+    unknown = 0x2a,
+    noChange = 0x7f,
+};
+
+static constexpr uint8_t stateChanged = 0x80;
+
+std::map<ACPIPowerState::ACPI, PowerState> dbusToIPMI = {
+    {ACPIPowerState::ACPI::S0_G0_D0, PowerState::s0G0D0},
+    {ACPIPowerState::ACPI::S1_D1, PowerState::s1D1},
+    {ACPIPowerState::ACPI::S2_D2, PowerState::s2D2},
+    {ACPIPowerState::ACPI::S3_D3, PowerState::s3D3},
+    {ACPIPowerState::ACPI::S4, PowerState::s4},
+    {ACPIPowerState::ACPI::S5_G2, PowerState::s5G2},
+    {ACPIPowerState::ACPI::S4_S5, PowerState::s4S5},
+    {ACPIPowerState::ACPI::G3, PowerState::g3},
+    {ACPIPowerState::ACPI::SLEEP, PowerState::sleep},
+    {ACPIPowerState::ACPI::G1_SLEEP, PowerState::g1Sleep},
+    {ACPIPowerState::ACPI::OVERRIDE, PowerState::override},
+    {ACPIPowerState::ACPI::LEGACY_ON, PowerState::legacyOn},
+    {ACPIPowerState::ACPI::LEGACY_OFF, PowerState::legacyOff},
+    {ACPIPowerState::ACPI::Unknown, PowerState::unknown}};
+
+bool isValidACPIState(acpi_state::PowerStateType type, uint8_t state)
+{
+    if (type == acpi_state::PowerStateType::sysPowerState)
+    {
+        if ((state <= static_cast<uint8_t>(acpi_state::PowerState::override)) ||
+            (state == static_cast<uint8_t>(acpi_state::PowerState::legacyOn)) ||
+            (state ==
+             static_cast<uint8_t>(acpi_state::PowerState::legacyOff)) ||
+            (state == static_cast<uint8_t>(acpi_state::PowerState::unknown)) ||
+            (state == static_cast<uint8_t>(acpi_state::PowerState::noChange)))
+        {
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+    else if (type == acpi_state::PowerStateType::devPowerState)
+    {
+        if ((state <= static_cast<uint8_t>(acpi_state::PowerState::s3D3)) ||
+            (state == static_cast<uint8_t>(acpi_state::PowerState::unknown)) ||
+            (state == static_cast<uint8_t>(acpi_state::PowerState::noChange)))
+        {
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+    else
+    {
+        return false;
+    }
+    return false;
+}
+} // namespace acpi_state
+
+/** @brief implements Set ACPI Power State command
+ * @param sysAcpiState - ACPI system power state to set
+ * @param devAcpiState - ACPI device power state to set
+ *
+ * @return IPMI completion code on success
+ **/
+ipmi::RspType<> ipmiSetAcpiPowerState(uint8_t sysAcpiState,
+                                      uint8_t devAcpiState)
+{
+    auto s = static_cast<uint8_t>(acpi_state::PowerState::unknown);
+
+    sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+
+    auto value = acpi_state::ACPIPowerState::ACPI::Unknown;
+
+    if (sysAcpiState & acpi_state::stateChanged)
+    {
+        // set system power state
+        s = sysAcpiState & ~acpi_state::stateChanged;
+
+        if (!acpi_state::isValidACPIState(
+                acpi_state::PowerStateType::sysPowerState, s))
+        {
+            lg2::error("set_acpi_power sys invalid input, S: {S}", "S", s);
+            return ipmi::responseParmOutOfRange();
+        }
+
+        // valid input
+        if (s == static_cast<uint8_t>(acpi_state::PowerState::noChange))
+        {
+            lg2::debug("No change for system power state");
+        }
+        else
+        {
+            auto found = std::find_if(
+                acpi_state::dbusToIPMI.begin(), acpi_state::dbusToIPMI.end(),
+                [&s](const auto& iter) {
+                    return (static_cast<uint8_t>(iter.second) == s);
+                });
+
+            value = found->first;
+
+            try
+            {
+                auto acpiObject =
+                    ipmi::getDbusObject(bus, acpi_state::acpiInterface);
+                ipmi::setDbusProperty(bus, acpiObject.second, acpiObject.first,
+                                      acpi_state::acpiInterface,
+                                      acpi_state::sysACPIProp,
+                                      convertForMessage(value));
+            }
+            catch (const InternalFailure& e)
+            {
+                lg2::error("Failed in set ACPI system property: {ERROR}",
+                           "ERROR", e);
+                return ipmi::responseUnspecifiedError();
+            }
+        }
+    }
+    else
+    {
+        lg2::debug("Do not change system power state");
+    }
+
+    if (devAcpiState & acpi_state::stateChanged)
+    {
+        // set device power state
+        s = devAcpiState & ~acpi_state::stateChanged;
+        if (!acpi_state::isValidACPIState(
+                acpi_state::PowerStateType::devPowerState, s))
+        {
+            lg2::error("set_acpi_power dev invalid input, S: {S}", "S", s);
+            return ipmi::responseParmOutOfRange();
+        }
+
+        // valid input
+        if (s == static_cast<uint8_t>(acpi_state::PowerState::noChange))
+        {
+            lg2::debug("No change for device power state");
+        }
+        else
+        {
+            auto found = std::find_if(
+                acpi_state::dbusToIPMI.begin(), acpi_state::dbusToIPMI.end(),
+                [&s](const auto& iter) {
+                    return (static_cast<uint8_t>(iter.second) == s);
+                });
+
+            value = found->first;
+
+            try
+            {
+                auto acpiObject =
+                    ipmi::getDbusObject(bus, acpi_state::acpiInterface);
+                ipmi::setDbusProperty(bus, acpiObject.second, acpiObject.first,
+                                      acpi_state::acpiInterface,
+                                      acpi_state::devACPIProp,
+                                      convertForMessage(value));
+            }
+            catch (const InternalFailure& e)
+            {
+                lg2::error("Failed in set ACPI device property: {ERROR}",
+                           "ERROR", e);
+                return ipmi::responseUnspecifiedError();
+            }
+        }
+    }
+    else
+    {
+        lg2::debug("Do not change device power state");
+    }
+    return ipmi::responseSuccess();
+}
+
+/**
+ *  @brief implements the get ACPI power state command
+ *
+ *  @return IPMI completion code plus response data on success.
+ *   -  ACPI system power state
+ *   -  ACPI device power state
+ **/
+ipmi::RspType<uint8_t, // acpiSystemPowerState
+              uint8_t  // acpiDevicePowerState
+              >
+    ipmiGetAcpiPowerState()
+{
+    uint8_t sysAcpiState;
+    uint8_t devAcpiState;
+
+    sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+
+    try
+    {
+        auto acpiObject = ipmi::getDbusObject(bus, acpi_state::acpiInterface);
+
+        auto sysACPIVal = ipmi::getDbusProperty(
+            bus, acpiObject.second, acpiObject.first, acpi_state::acpiInterface,
+            acpi_state::sysACPIProp);
+        auto sysACPI = acpi_state::ACPIPowerState::convertACPIFromString(
+            std::get<std::string>(sysACPIVal));
+        sysAcpiState = static_cast<uint8_t>(acpi_state::dbusToIPMI.at(sysACPI));
+
+        auto devACPIVal = ipmi::getDbusProperty(
+            bus, acpiObject.second, acpiObject.first, acpi_state::acpiInterface,
+            acpi_state::devACPIProp);
+        auto devACPI = acpi_state::ACPIPowerState::convertACPIFromString(
+            std::get<std::string>(devACPIVal));
+        devAcpiState = static_cast<uint8_t>(acpi_state::dbusToIPMI.at(devACPI));
+    }
+    catch (const InternalFailure& e)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    return ipmi::responseSuccess(sysAcpiState, devAcpiState);
+}
+
+typedef struct
+{
+    char major;
+    char minor;
+    uint8_t aux[4];
+} Revision;
+
+/* Use regular expression searching matched pattern X.Y, and convert it to  */
+/* Major (X) and Minor (Y) version.                                         */
+/* Example:                                                                 */
+/* version = 2.14.0-dev                                                     */
+/*           ^ ^                                                            */
+/*           | |---------------- Minor                                      */
+/*           |------------------ Major                                      */
+/*                                                                          */
+/* Default regex string only tries to match Major and Minor version.        */
+/*                                                                          */
+/* To match more firmware version info, platforms need to define it own     */
+/* regex string to match more strings, and assign correct mapping index in  */
+/* matches array.                                                           */
+/*                                                                          */
+/* matches[0]: matched index for major ver                                  */
+/* matches[1]: matched index for minor ver                                  */
+/* matches[2]: matched index for aux[0] (set 0 to skip)                     */
+/* matches[3]: matched index for aux[1] (set 0 to skip)                     */
+/* matches[4]: matched index for aux[2] (set 0 to skip)                     */
+/* matches[5]: matched index for aux[3] (set 0 to skip)                     */
+/* Example:                                                                 */
+/* regex = "([\d]+).([\d]+).([\d]+)-dev-([\d]+)-g([0-9a-fA-F]{2})           */
+/*          ([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})"               */
+/* matches = {1,2,5,6,7,8}                                                  */
+/* version = 2.14.0-dev-750-g37a7c5ad1-dirty                                */
+/*           ^ ^  ^     ^    ^ ^ ^ ^                                        */
+/*           | |  |     |    | | | |                                        */
+/*           | |  |     |    | | | |-- Aux byte 3 (0xAD), index 8           */
+/*           | |  |     |    | | |---- Aux byte 2 (0xC5), index 7           */
+/*           | |  |     |    | |------ Aux byte 1 (0xA7), index 6           */
+/*           | |  |     |    |-------- Aux byte 0 (0x37), index 5           */
+/*           | |  |     |------------- Not used, index 4                    */
+/*           | |  |------------------- Not used, index 3                    */
+/*           | |---------------------- Minor (14), index 2                  */
+/*           |------------------------ Major (2), index 1                   */
+int convertVersion(std::string s, Revision& rev)
+{
+    static const std::vector<size_t> matches = {
+        MAJOR_MATCH_INDEX, MINOR_MATCH_INDEX, AUX_0_MATCH_INDEX,
+        AUX_1_MATCH_INDEX, AUX_2_MATCH_INDEX, AUX_3_MATCH_INDEX};
+    std::regex fw_regex(FW_VER_REGEX);
+    std::smatch m;
+    Revision r = {0};
+    size_t val;
+
+    if (std::regex_search(s, m, fw_regex))
+    {
+        if (m.size() < *std::max_element(matches.begin(), matches.end()))
+        { // max index higher than match count
+            return -1;
+        }
+
+        // convert major
+        {
+            std::string str = m[matches[0]].str();
+            const auto& [ptr, ec] =
+                std::from_chars(str.data(), str.data() + str.size(), val);
+            if (ec != std::errc() || ptr != str.data() + str.size())
+            { // failed to convert major string
+                return -1;
+            }
+
+            if (val >= 2000)
+            { // For the platforms use year as major version, it would expect to
+              // have major version between 0 - 99. If the major version is
+              // greater than or equal to 2000, it is treated as a year and
+              // converted to 0 - 99.
+                r.major = val % 100;
+            }
+            else
+            {
+                r.major = val & 0x7F;
+            }
+        }
+
+        // convert minor
+        {
+            std::string str = m[matches[1]].str();
+            const auto& [ptr, ec] =
+                std::from_chars(str.data(), str.data() + str.size(), val);
+            if (ec != std::errc() || ptr != str.data() + str.size())
+            { // failed to convert minor string
+                return -1;
+            }
+            r.minor = val & 0xFF;
+        }
+
+        // convert aux bytes
+        {
+            size_t i;
+            for (i = 0; i < 4; i++)
+            {
+                if (matches[i + 2] == 0)
+                {
+                    continue;
+                }
+
+                std::string str = m[matches[i + 2]].str();
+                const char* cstr = str.c_str();
+                auto [ptr,
+                      ec] = std::from_chars(cstr, cstr + str.size(), val, 16);
+                if (ec != std::errc() || ptr != cstr + str.size())
+                { // failed to convert aux byte string
+                    break;
+                }
+
+                r.aux[i] = val & 0xFF;
+            }
+
+            if (i != 4)
+            { // something wrong durign converting aux bytes
+                return -1;
+            }
+        }
+
+        // all matched
+        rev = r;
+        return 0;
+    }
+
+    return -1;
+}
+
+/* @brief: Implement the Get Device ID IPMI command per the IPMI spec
+ *  @param[in] ctx - shared_ptr to an IPMI context struct
+ *
+ *  @returns IPMI completion code plus response data
+ *   - Device ID (manufacturer defined)
+ *   - Device revision[4 bits]; reserved[3 bits]; SDR support[1 bit]
+ *   - FW revision major[7 bits] (binary encoded); available[1 bit]
+ *   - FW Revision minor (BCD encoded)
+ *   - IPMI version (0x02 for IPMI 2.0)
+ *   - device support (bitfield of supported options)
+ *   - MFG IANA ID (3 bytes)
+ *   - product ID (2 bytes)
+ *   - AUX info (4 bytes)
+ */
+ipmi::RspType<uint8_t,  // Device ID
+              uint8_t,  // Device Revision
+              uint8_t,  // Firmware Revision Major
+              uint8_t,  // Firmware Revision minor
+              uint8_t,  // IPMI version
+              uint8_t,  // Additional device support
+              uint24_t, // MFG ID
+              uint16_t, // Product ID
+              uint32_t  // AUX info
+              >
+    ipmiAppGetDeviceId([[maybe_unused]] ipmi::Context::ptr ctx)
+{
+    static struct
+    {
+        uint8_t id;
+        uint8_t revision;
+        uint8_t fw[2];
+        uint8_t ipmiVer;
+        uint8_t addnDevSupport;
+        uint24_t manufId;
+        uint16_t prodId;
+        uint32_t aux;
+    } devId;
+    static bool dev_id_initialized = false;
+    static bool defaultActivationSetting = true;
+    const char* filename = "/usr/share/ipmi-providers/dev_id.json";
+    constexpr auto ipmiDevIdStateShift = 7;
+    constexpr auto ipmiDevIdFw1Mask = ~(1 << ipmiDevIdStateShift);
+
+#ifdef GET_DBUS_ACTIVE_SOFTWARE
+    static bool haveBMCVersion = false;
+    if (!haveBMCVersion || !dev_id_initialized)
+    {
+        int r = -1;
+        Revision rev = {0, 0, {0, 0, 0, 0}};
+        try
+        {
+            auto version = getActiveSoftwareVersionInfo(ctx);
+            r = convertVersion(version, rev);
+        }
+        catch (const std::exception& e)
+        {
+            lg2::error("error message: {ERROR}", "ERROR", e);
+        }
+
+        if (r >= 0)
+        {
+            // bit7 identifies if the device is available
+            // 0=normal operation
+            // 1=device firmware, SDR update,
+            // or self-initialization in progress.
+            // The availability may change in run time, so mask here
+            // and initialize later.
+            devId.fw[0] = rev.major & ipmiDevIdFw1Mask;
+
+            rev.minor = (rev.minor > 99 ? 99 : rev.minor);
+            devId.fw[1] = rev.minor % 10 + (rev.minor / 10) * 16;
+            std::memcpy(&devId.aux, rev.aux, sizeof(rev.aux));
+            haveBMCVersion = true;
+        }
+    }
+#endif
+    if (!dev_id_initialized)
+    {
+        // IPMI Spec version 2.0
+        devId.ipmiVer = 2;
+
+        std::ifstream devIdFile(filename);
+        if (devIdFile.is_open())
+        {
+            auto data = nlohmann::json::parse(devIdFile, nullptr, false);
+            if (!data.is_discarded())
+            {
+                devId.id = data.value("id", 0);
+                devId.revision = data.value("revision", 0);
+                devId.addnDevSupport = data.value("addn_dev_support", 0);
+                devId.manufId = data.value("manuf_id", 0);
+                devId.prodId = data.value("prod_id", 0);
+#ifdef GET_DBUS_ACTIVE_SOFTWARE
+                if (!(AUX_0_MATCH_INDEX || AUX_1_MATCH_INDEX ||
+                      AUX_2_MATCH_INDEX || AUX_3_MATCH_INDEX))
+#endif
+                {
+                    devId.aux = data.value("aux", 0);
+                }
+
+                if (data.contains("firmware_revision"))
+                {
+                    const auto& firmwareRevision = data.at("firmware_revision");
+                    if (firmwareRevision.contains("major"))
+                    {
+                        firmwareRevision.at("major").get_to(devId.fw[0]);
+                    }
+                    if (firmwareRevision.contains("minor"))
+                    {
+                        firmwareRevision.at("minor").get_to(devId.fw[1]);
+                    }
+                }
+
+                // Set the availablitity of the BMC.
+                defaultActivationSetting = data.value("availability", true);
+
+                // Don't read the file every time if successful
+                dev_id_initialized = true;
+            }
+            else
+            {
+                lg2::error("Device ID JSON parser failure");
+                return ipmi::responseUnspecifiedError();
+            }
+        }
+        else
+        {
+            lg2::error("Device ID file not found");
+            return ipmi::responseUnspecifiedError();
+        }
+    }
+
+    // Set availability to the actual current BMC state
+    devId.fw[0] &= ipmiDevIdFw1Mask;
+    if (!getCurrentBmcStateWithFallback(defaultActivationSetting))
+    {
+        devId.fw[0] |= (1 << ipmiDevIdStateShift);
+    }
+
+    return ipmi::responseSuccess(
+        devId.id, devId.revision, devId.fw[0], devId.fw[1], devId.ipmiVer,
+        devId.addnDevSupport, devId.manufId, devId.prodId, devId.aux);
+}
+
+auto ipmiAppGetSelfTestResults() -> ipmi::RspType<uint8_t, uint8_t>
+{
+    // Byte 2:
+    //  55h - No error.
+    //  56h - Self Test function not implemented in this controller.
+    //  57h - Corrupted or inaccesssible data or devices.
+    //  58h - Fatal hardware error.
+    //  FFh - reserved.
+    //  all other: Device-specific 'internal failure'.
+    //  Byte 3:
+    //      For byte 2 = 55h, 56h, FFh:     00h
+    //      For byte 2 = 58h, all other:    Device-specific
+    //      For byte 2 = 57h:   self-test error bitfield.
+    //      Note: returning 57h does not imply that all test were run.
+    //      [7] 1b = Cannot access SEL device.
+    //      [6] 1b = Cannot access SDR Repository.
+    //      [5] 1b = Cannot access BMC FRU device.
+    //      [4] 1b = IPMB signal lines do not respond.
+    //      [3] 1b = SDR Repository empty.
+    //      [2] 1b = Internal Use Area of BMC FRU corrupted.
+    //      [1] 1b = controller update 'boot block' firmware corrupted.
+    //      [0] 1b = controller operational firmware corrupted.
+    constexpr uint8_t notImplemented = 0x56;
+    constexpr uint8_t zero = 0;
+    return ipmi::responseSuccess(notImplemented, zero);
+}
+
+static constexpr size_t uuidBinaryLength = 16;
+static std::array<uint8_t, uuidBinaryLength> rfc4122ToIpmi(std::string rfc4122)
+{
+    using Argument = xyz::openbmc_project::common::InvalidArgument;
+    // UUID is in RFC4122 format. Ex: 61a39523-78f2-11e5-9862-e6402cfc3223
+    // Per IPMI Spec 2.0 need to convert to 16 hex bytes and reverse the byte
+    // order
+    // Ex: 0x2332fc2c40e66298e511f2782395a361
+    constexpr size_t uuidHexLength = (2 * uuidBinaryLength);
+    constexpr size_t uuidRfc4122Length = (uuidHexLength + 4);
+    std::array<uint8_t, uuidBinaryLength> uuid;
+    if (rfc4122.size() == uuidRfc4122Length)
+    {
+        rfc4122.erase(std::remove(rfc4122.begin(), rfc4122.end(), '-'),
+                      rfc4122.end());
+    }
+    if (rfc4122.size() != uuidHexLength)
+    {
+        elog<InvalidArgument>(Argument::ARGUMENT_NAME("rfc4122"),
+                              Argument::ARGUMENT_VALUE(rfc4122.c_str()));
+    }
+    for (size_t ind = 0; ind < uuidHexLength; ind += 2)
+    {
+        char v[3];
+        v[0] = rfc4122[ind];
+        v[1] = rfc4122[ind + 1];
+        v[2] = 0;
+        size_t err;
+        long b;
+        try
+        {
+            b = std::stoul(v, &err, 16);
+        }
+        catch (const std::exception& e)
+        {
+            elog<InvalidArgument>(Argument::ARGUMENT_NAME("rfc4122"),
+                                  Argument::ARGUMENT_VALUE(rfc4122.c_str()));
+        }
+        // check that exactly two ascii bytes were converted
+        if (err != 2)
+        {
+            elog<InvalidArgument>(Argument::ARGUMENT_NAME("rfc4122"),
+                                  Argument::ARGUMENT_VALUE(rfc4122.c_str()));
+        }
+        uuid[uuidBinaryLength - (ind / 2) - 1] = static_cast<uint8_t>(b);
+    }
+    return uuid;
+}
+
+auto ipmiAppGetDeviceGuid()
+    -> ipmi::RspType<std::array<uint8_t, uuidBinaryLength>>
+{
+    // return a fixed GUID based on /etc/machine-id
+    // This should match the /redfish/v1/Managers/bmc's UUID data
+
+    // machine specific application ID (for BMC ID)
+    // generated by systemd-id128 -p new as per man page
+    static constexpr sd_id128_t bmcUuidAppId = SD_ID128_MAKE(
+        e0, e1, 73, 76, 64, 61, 47, da, a5, 0c, d0, cc, 64, 12, 45, 78);
+
+    sd_id128_t bmcUuid;
+    // create the UUID from /etc/machine-id via the systemd API
+    sd_id128_get_machine_app_specific(bmcUuidAppId, &bmcUuid);
+
+    char bmcUuidCstr[SD_ID128_STRING_MAX];
+    std::string systemUuid = sd_id128_to_string(bmcUuid, bmcUuidCstr);
+
+    std::array<uint8_t, uuidBinaryLength> uuid = rfc4122ToIpmi(systemUuid);
+    return ipmi::responseSuccess(uuid);
+}
+
+auto ipmiAppGetBtCapabilities()
+    -> ipmi::RspType<uint8_t, uint8_t, uint8_t, uint8_t, uint8_t>
+{
+    // Per IPMI 2.0 spec, the input and output buffer size must be the max
+    // buffer size minus one byte to allocate space for the length byte.
+    constexpr uint8_t nrOutstanding = 0x01;
+    constexpr uint8_t inputBufferSize = MAX_IPMI_BUFFER - 1;
+    constexpr uint8_t outputBufferSize = MAX_IPMI_BUFFER - 1;
+    constexpr uint8_t transactionTime = 0x0A;
+    constexpr uint8_t nrRetries = 0x01;
+
+    return ipmi::responseSuccess(nrOutstanding, inputBufferSize,
+                                 outputBufferSize, transactionTime, nrRetries);
+}
+
+auto ipmiAppGetSystemGuid(ipmi::Context::ptr& ctx)
+    -> ipmi::RspType<std::array<uint8_t, 16>>
+{
+    static constexpr auto uuidInterface = "xyz.openbmc_project.Common.UUID";
+    static constexpr auto uuidProperty = "UUID";
+
+    // Get the Inventory object implementing BMC interface
+    ipmi::DbusObjectInfo objectInfo{};
+    boost::system::error_code ec = ipmi::getDbusObject(
+        ctx, uuidInterface, ipmi::sensor::inventoryRoot, objectInfo);
+    if (ec.value())
+    {
+        lg2::error("Failed to locate System UUID object, "
+                   "interface: {INTERFACE}, error: {ERROR}",
+                   "INTERFACE", uuidInterface, "ERROR", ec.message());
+    }
+
+    // Read UUID property value from bmcObject
+    // UUID is in RFC4122 format Ex: 61a39523-78f2-11e5-9862-e6402cfc3223
+    std::string rfc4122Uuid{};
+    ec = ipmi::getDbusProperty(ctx, objectInfo.second, objectInfo.first,
+                               uuidInterface, uuidProperty, rfc4122Uuid);
+    if (ec.value())
+    {
+        lg2::error("Failed to read System UUID property, "
+                   "interface: {INTERFACE}, property: {PROPERTY}, "
+                   "error: {ERROR}",
+                   "INTERFACE", uuidInterface, "PROPERTY", uuidProperty,
+                   "ERROR", ec.message());
+        return ipmi::responseUnspecifiedError();
+    }
+    std::array<uint8_t, 16> uuid;
+    try
+    {
+        // convert to IPMI format
+        uuid = rfc4122ToIpmi(rfc4122Uuid);
+    }
+    catch (const InvalidArgument& e)
+    {
+        lg2::error("Failed in parsing BMC UUID property, "
+                   "interface: {INTERFACE}, property: {PROPERTY}, "
+                   "value: {VALUE}, error: {ERROR}",
+                   "INTERFACE", uuidInterface, "PROPERTY", uuidProperty,
+                   "VALUE", rfc4122Uuid, "ERROR", e);
+        return ipmi::responseUnspecifiedError();
+    }
+    return ipmi::responseSuccess(uuid);
+}
+
+/**
+ * @brief set the session state as teardown
+ *
+ * This function is to set the session state to tear down in progress if the
+ * state is active.
+ *
+ * @param[in] busp - Dbus obj
+ * @param[in] service - service name
+ * @param[in] obj - object path
+ *
+ * @return success completion code if it sets the session state to
+ * tearDownInProgress else return the corresponding error completion code.
+ **/
+uint8_t setSessionState(std::shared_ptr<sdbusplus::asio::connection>& busp,
+                        const std::string& service, const std::string& obj)
+{
+    try
+    {
+        uint8_t sessionState = std::get<uint8_t>(ipmi::getDbusProperty(
+            *busp, service, obj, session::sessionIntf, "State"));
+
+        if (sessionState == static_cast<uint8_t>(session::State::active))
+        {
+            ipmi::setDbusProperty(
+                *busp, service, obj, session::sessionIntf, "State",
+                static_cast<uint8_t>(session::State::tearDownInProgress));
+            return ipmi::ccSuccess;
+        }
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Failed in getting session state property, "
+                   "service: {SERVICE}, object path: {OBJECT_PATH}, "
+                   "interface: {INTERFACE}, error: {ERROR}",
+                   "SERVICE", service, "OBJECT_PATH", obj, "INTERFACE",
+                   session::sessionIntf, "ERROR", e);
+        return ipmi::ccUnspecifiedError;
+    }
+
+    return ipmi::ccInvalidFieldRequest;
+}
+
+ipmi::RspType<> ipmiAppCloseSession(uint32_t reqSessionId,
+                                    std::optional<uint8_t> requestSessionHandle)
+{
+    auto busp = getSdBus();
+    uint8_t reqSessionHandle =
+        requestSessionHandle.value_or(session::defaultSessionHandle);
+
+    if (reqSessionId == session::sessionZero &&
+        reqSessionHandle == session::defaultSessionHandle)
+    {
+        return ipmi::response(session::ccInvalidSessionId);
+    }
+
+    if (reqSessionId == session::sessionZero &&
+        reqSessionHandle == session::invalidSessionHandle)
+    {
+        return ipmi::response(session::ccInvalidSessionHandle);
+    }
+
+    if (reqSessionId != session::sessionZero &&
+        reqSessionHandle != session::defaultSessionHandle)
+    {
+        return ipmi::response(ipmi::ccInvalidFieldRequest);
+    }
+
+    try
+    {
+        ipmi::ObjectTree objectTree = ipmi::getAllDbusObjects(
+            *busp, session::sessionManagerRootPath, session::sessionIntf);
+
+        for (auto& objectTreeItr : objectTree)
+        {
+            const std::string obj = objectTreeItr.first;
+
+            if (isSessionObjectMatched(obj, reqSessionId, reqSessionHandle))
+            {
+                auto& serviceMap = objectTreeItr.second;
+
+                // Session id and session handle are unique for each session.
+                // Session id and handler are retrived from the object path and
+                // object path will be unique for each session. Checking if
+                // multiple objects exist with same object path under multiple
+                // services.
+                if (serviceMap.size() != 1)
+                {
+                    return ipmi::responseUnspecifiedError();
+                }
+
+                auto itr = serviceMap.begin();
+                const std::string service = itr->first;
+                return ipmi::response(setSessionState(busp, service, obj));
+            }
+        }
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error("Failed to fetch object from dbus, "
+                   "interface: {INTERFACE}, error: {ERROR}",
+                   "INTERFACE", session::sessionIntf, "ERROR", e);
+        return ipmi::responseUnspecifiedError();
+    }
+
+    return ipmi::responseInvalidFieldRequest();
+}
+
+uint8_t getTotalSessionCount()
+{
+    uint8_t count = 0, ch = 0;
+
+    while (ch < ipmi::maxIpmiChannels &&
+           count < session::maxNetworkInstanceSupported)
+    {
+        ipmi::ChannelInfo chInfo{};
+        ipmi::getChannelInfo(ch, chInfo);
+        if (static_cast<ipmi::EChannelMediumType>(chInfo.mediumType) ==
+            ipmi::EChannelMediumType::lan8032)
+        {
+            count++;
+        }
+        ch++;
+    }
+    return count * session::maxSessionCountPerChannel;
+}
+
+/**
+ * @brief get session info request data.
+ *
+ * This function validates the request data and retrive request session id,
+ * session handle.
+ *
+ * @param[in] ctx - context of current session.
+ * @param[in] sessionIndex - request session index
+ * @param[in] payload - input payload
+ * @param[in] reqSessionId - unpacked session Id will be asigned
+ * @param[in] reqSessionHandle - unpacked session handle will be asigned
+ *
+ * @return success completion code if request data is valid
+ * else return the correcponding error completion code.
+ **/
+uint8_t getSessionInfoRequestData(
+    const ipmi::Context::ptr ctx, const uint8_t sessionIndex,
+    ipmi::message::Payload& payload, uint32_t& reqSessionId,
+    uint8_t& reqSessionHandle)
+{
+    if ((sessionIndex > session::maxSessionCountPerChannel) &&
+        (sessionIndex < session::searchSessionByHandle))
+    {
+        return ipmi::ccInvalidFieldRequest;
+    }
+
+    switch (sessionIndex)
+    {
+        case session::searchCurrentSession:
+
+            ipmi::ChannelInfo chInfo;
+            ipmi::getChannelInfo(ctx->channel, chInfo);
+
+            if (static_cast<ipmi::EChannelMediumType>(chInfo.mediumType) !=
+                ipmi::EChannelMediumType::lan8032)
+            {
+                return ipmi::ccInvalidFieldRequest;
+            }
+
+            if (!payload.fullyUnpacked())
+            {
+                return ipmi::ccReqDataLenInvalid;
+            }
+            // Check if current sessionId is 0, sessionId 0 is reserved.
+            if (ctx->sessionId == session::sessionZero)
+            {
+                return session::ccInvalidSessionId;
+            }
+            reqSessionId = ctx->sessionId;
+            break;
+
+        case session::searchSessionByHandle:
+
+            if ((payload.unpack(reqSessionHandle)) ||
+                (!payload.fullyUnpacked()))
+            {
+                return ipmi::ccReqDataLenInvalid;
+            }
+
+            if ((reqSessionHandle == session::sessionZero) ||
+                ((reqSessionHandle & session::multiIntfaceSessionHandleMask) >
+                 session::maxSessionCountPerChannel))
+            {
+                return session::ccInvalidSessionHandle;
+            }
+            break;
+
+        case session::searchSessionById:
+
+            if ((payload.unpack(reqSessionId)) || (!payload.fullyUnpacked()))
+            {
+                return ipmi::ccReqDataLenInvalid;
+            }
+
+            if (reqSessionId == session::sessionZero)
+            {
+                return session::ccInvalidSessionId;
+            }
+            break;
+
+        default:
+            if (!payload.fullyUnpacked())
+            {
+                return ipmi::ccReqDataLenInvalid;
+            }
+            break;
+    }
+    return ipmi::ccSuccess;
+}
+
+uint8_t getSessionState(ipmi::Context::ptr ctx, const std::string& service,
+                        const std::string& objPath, uint8_t& sessionState)
+{
+    boost::system::error_code ec = ipmi::getDbusProperty(
+        ctx, service, objPath, session::sessionIntf, "State", sessionState);
+    if (ec)
+    {
+        lg2::error("Failed to fetch state property, service: {SERVICE}, "
+                   "object path: {OBJECTPATH}, interface: {INTERFACE}, "
+                   "error: {ERROR}",
+                   "SERVICE", service, "OBJECTPATH", objPath, "INTERFACE",
+                   session::sessionIntf, "ERROR", ec.message());
+        return ipmi::ccUnspecifiedError;
+    }
+    return ipmi::ccSuccess;
+}
+
+static constexpr uint8_t macAddrLen = 6;
+/** Alias SessionDetails - contain the optional information about an
+ *        RMCP+ session.
+ *
+ *  @param userID - uint6_t session user ID (0-63)
+ *  @param reserved - uint2_t reserved
+ *  @param privilege - uint4_t session privilege (0-5)
+ *  @param reserved - uint4_t reserved
+ *  @param channel - uint4_t session channel number
+ *  @param protocol - uint4_t session protocol
+ *  @param remoteIP - uint32_t remote IP address
+ *  @param macAddr - std::array<uint8_t, 6> mac address
+ *  @param port - uint16_t remote port
+ */
+using SessionDetails =
+    std::tuple<uint2_t, uint6_t, uint4_t, uint4_t, uint4_t, uint4_t, uint32_t,
+               std::array<uint8_t, macAddrLen>, uint16_t>;
+
+/** @brief get session details for a given session
+ *
+ *  @param[in] ctx - ipmi::Context pointer for accessing D-Bus
+ *  @param[in] service - D-Bus service name to fetch details from
+ *  @param[in] objPath - D-Bus object path for session
+ *  @param[out] sessionHandle - return session handle for session
+ *  @param[out] sessionState - return session state for session
+ *  @param[out] details - return a SessionDetails tuple containing other
+ *                        session info
+ *  @return - ipmi::Cc success or error code
+ */
+ipmi::Cc getSessionDetails(ipmi::Context::ptr ctx, const std::string& service,
+                           const std::string& objPath, uint8_t& sessionHandle,
+                           uint8_t& sessionState, SessionDetails& details)
+{
+    ipmi::PropertyMap sessionProps;
+    boost::system::error_code ec = ipmi::getAllDbusProperties(
+        ctx, service, objPath, session::sessionIntf, sessionProps);
+
+    if (ec)
+    {
+        lg2::error("Failed to fetch state property, service: {SERVICE}, "
+                   "object path: {OBJECTPATH}, interface: {INTERFACE}, "
+                   "error: {ERROR}",
+                   "SERVICE", service, "OBJECTPATH", objPath, "INTERFACE",
+                   session::sessionIntf, "ERROR", ec.message());
+        return ipmi::ccUnspecifiedError;
+    }
+
+    sessionState = ipmi::mappedVariant<uint8_t>(
+        sessionProps, "State", static_cast<uint8_t>(session::State::inactive));
+    if (sessionState == static_cast<uint8_t>(session::State::active))
+    {
+        sessionHandle =
+            ipmi::mappedVariant<uint8_t>(sessionProps, "SessionHandle", 0);
+        std::get<0>(details) =
+            ipmi::mappedVariant<uint8_t>(sessionProps, "UserID", 0xff);
+        // std::get<1>(details) = 0; // (default constructed to 0)
+        std::get<2>(details) =
+            ipmi::mappedVariant<uint8_t>(sessionProps, "CurrentPrivilege", 0);
+        // std::get<3>(details) = 0; // (default constructed to 0)
+        std::get<4>(details) =
+            ipmi::mappedVariant<uint8_t>(sessionProps, "ChannelNum", 0xff);
+        constexpr uint4_t rmcpPlusProtocol = 1;
+        std::get<5>(details) = rmcpPlusProtocol;
+        std::get<6>(details) =
+            ipmi::mappedVariant<uint32_t>(sessionProps, "RemoteIPAddr", 0);
+        // std::get<7>(details) = {{0}}; // default constructed to all 0
+        std::get<8>(details) =
+            ipmi::mappedVariant<uint16_t>(sessionProps, "RemotePort", 0);
+    }
+
+    return ipmi::ccSuccess;
+}
+
+ipmi::RspType<uint8_t, // session handle,
+              uint8_t, // total session count
+              uint8_t, // active session count
+              std::optional<SessionDetails>>
+    ipmiAppGetSessionInfo(ipmi::Context::ptr ctx, uint8_t sessionIndex,
+                          ipmi::message::Payload& payload)
+{
+    uint32_t reqSessionId = 0;
+    uint8_t reqSessionHandle = session::defaultSessionHandle;
+    // initializing state to 0xff as 0 represents state as inactive.
+    uint8_t state = 0xFF;
+
+    uint8_t completionCode = getSessionInfoRequestData(
+        ctx, sessionIndex, payload, reqSessionId, reqSessionHandle);
+
+    if (completionCode)
+    {
+        return ipmi::response(completionCode);
+    }
+    ipmi::ObjectTree objectTree;
+    boost::system::error_code ec = ipmi::getAllDbusObjects(
+        ctx, session::sessionManagerRootPath, session::sessionIntf, objectTree);
+    if (ec)
+    {
+        lg2::error("Failed to fetch object from dbus, "
+                   "interface: {INTERFACE}, error: {ERROR}",
+                   "INTERFACE", session::sessionIntf, "ERROR", ec.message());
+        return ipmi::responseUnspecifiedError();
+    }
+
+    uint8_t totalSessionCount = getTotalSessionCount();
+    uint8_t activeSessionCount = 0;
+    uint8_t sessionHandle = session::defaultSessionHandle;
+    uint8_t activeSessionHandle = 0;
+    std::optional<SessionDetails> maybeDetails;
+    uint8_t index = 0;
+    for (auto& objectTreeItr : objectTree)
+    {
+        uint32_t sessionId = 0;
+        std::string objectPath = objectTreeItr.first;
+
+        if (!parseCloseSessionInputPayload(objectPath, sessionId,
+                                           sessionHandle))
+        {
+            continue;
+        }
+        index++;
+        auto& serviceMap = objectTreeItr.second;
+        auto itr = serviceMap.begin();
+
+        if (serviceMap.size() != 1)
+        {
+            return ipmi::responseUnspecifiedError();
+        }
+
+        std::string service = itr->first;
+        uint8_t sessionState = 0;
+        completionCode =
+            getSessionState(ctx, service, objectPath, sessionState);
+        if (completionCode)
+        {
+            return ipmi::response(completionCode);
+        }
+
+        if (sessionState == static_cast<uint8_t>(session::State::active))
+        {
+            activeSessionCount++;
+        }
+
+        if (index == sessionIndex || reqSessionId == sessionId ||
+            reqSessionHandle == sessionHandle)
+        {
+            SessionDetails details{};
+            completionCode = getSessionDetails(ctx, service, objectPath,
+                                               sessionHandle, state, details);
+
+            if (completionCode)
+            {
+                return ipmi::response(completionCode);
+            }
+            activeSessionHandle = sessionHandle;
+            maybeDetails = std::move(details);
+        }
+    }
+
+    if (state == static_cast<uint8_t>(session::State::active) ||
+        state == static_cast<uint8_t>(session::State::tearDownInProgress))
+    {
+        return ipmi::responseSuccess(activeSessionHandle, totalSessionCount,
+                                     activeSessionCount, maybeDetails);
+    }
+
+    return ipmi::responseInvalidFieldRequest();
+}
+
+std::optional<std::string> getSysFWVersion(ipmi::Context::ptr& ctx)
+{
+    /*
+     * The System Firmware version is detected via following steps:
+     * - Get all of object paths that include
+     * "xyz.openbmc_project.Software.Version" interface.
+     * - Get the Purpose property of above object paths.
+     * - If the Purpose is Host then get the Version property.
+     */
+    ipmi::ObjectTree objectTree;
+    boost::system::error_code ec =
+        ipmi::getAllDbusObjects(ctx, softwareRoot, versionIntf, objectTree);
+    if (ec.value())
+    {
+        return std::nullopt;
+    }
+
+    for (const auto& [objPath, serviceMap] : objectTree)
+    {
+        for (const auto& [service, intfs] : serviceMap)
+        {
+            ipmi::PropertyMap props;
+            ec = ipmi::getAllDbusProperties(ctx, service, objPath, versionIntf,
+                                            props);
+            if (ec.value())
+            {
+                continue;
+            }
+
+            std::string purposeProp = std::string(
+                ipmi::mappedVariant<std::string>(props, "Purpose", ""));
+
+            if (!purposeProp.ends_with(versionPurposeHostEnd))
+            {
+                continue;
+            }
+
+            std::string sysFWVersion = std::string(
+                ipmi::mappedVariant<std::string>(props, "Version", ""));
+
+            if (sysFWVersion.empty())
+            {
+                return std::nullopt;
+            }
+
+            return sysFWVersion;
+        }
+    }
+
+    return std::nullopt;
+}
+
+static std::unique_ptr<SysInfoParamStore> sysInfoParamStore;
+
+static std::string sysInfoReadSystemName()
+{
+    // Use the BMC hostname as the "System Name."
+    char hostname[HOST_NAME_MAX + 1] = {};
+    if (gethostname(hostname, HOST_NAME_MAX) != 0)
+    {
+        perror("System info parameter: system name");
+    }
+    return hostname;
+}
+
+static constexpr uint8_t paramRevision = 0x11;
+static constexpr size_t configParameterLength = 16;
+
+static constexpr size_t smallChunkSize = 14;
+static constexpr size_t fullChunkSize = 16;
+static constexpr uint8_t progressMask = 0x3;
+static constexpr uint8_t maxValidEncodingData = 0x02;
+
+static constexpr uint8_t setComplete = 0x0;
+static constexpr uint8_t setInProgress = 0x1;
+static uint8_t transferStatus = setComplete;
+
+static constexpr uint8_t configDataOverhead = 2;
+
+namespace ipmi
+{
+constexpr Cc ccParmNotSupported = 0x80;
+constexpr Cc ccSetInProgressActive = 0x81;
+
+static inline auto responseParmNotSupported()
+{
+    return response(ccParmNotSupported);
+}
+static inline auto responseSetInProgressActive()
+{
+    return response(ccSetInProgressActive);
+}
+} // namespace ipmi
+
+ipmi::RspType<uint8_t,                // Parameter revision
+              std::optional<uint8_t>, // data1 / setSelector / ProgressStatus
+              std::optional<std::vector<uint8_t>>> // data2-17
+    ipmiAppGetSystemInfo(ipmi::Context::ptr ctx, uint7_t reserved,
+                         bool getRevision, uint8_t paramSelector,
+                         uint8_t setSelector, uint8_t BlockSelector)
+{
+    if (reserved || (paramSelector >= invalidParamSelectorStart &&
+                     paramSelector <= invalidParamSelectorEnd))
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+    if (paramSelector >= oemCmdStart)
+    {
+        return ipmi::responseParmNotSupported();
+    }
+    if (getRevision)
+    {
+        return ipmi::responseSuccess(paramRevision, std::nullopt, std::nullopt);
+    }
+
+    if (paramSelector == 0)
+    {
+        return ipmi::responseSuccess(paramRevision, transferStatus,
+                                     std::nullopt);
+    }
+
+    if (BlockSelector != 0) // 00h if parameter does not require a block number
+    {
+        return ipmi::responseParmNotSupported();
+    }
+
+    if (sysInfoParamStore == nullptr)
+    {
+        sysInfoParamStore = std::make_unique<SysInfoParamStore>();
+        sysInfoParamStore->update(IPMI_SYSINFO_SYSTEM_NAME,
+                                  sysInfoReadSystemName);
+    }
+
+    if (paramSelector == IPMI_SYSINFO_SYSTEM_FW_VERSION)
+    {
+        auto fwVersion = getSysFWVersion(ctx);
+
+        if (fwVersion == std::nullopt)
+        {
+            return ipmi::responseUnspecifiedError();
+        }
+        sysInfoParamStore->update(IPMI_SYSINFO_SYSTEM_FW_VERSION, *fwVersion);
+    }
+
+    // Parameters other than Set In Progress are assumed to be strings.
+    std::tuple<bool, std::string> ret =
+        sysInfoParamStore->lookup(paramSelector);
+    bool found = std::get<0>(ret);
+    if (!found)
+    {
+        return ipmi::responseSensorInvalid();
+    }
+    std::string& paramString = std::get<1>(ret);
+    std::vector<uint8_t> configData;
+    size_t count = 0;
+    if (setSelector == 0)
+    {                               // First chunk has only 14 bytes.
+        configData.emplace_back(0); // encoding
+        configData.emplace_back(paramString.length()); // string length
+        count = std::min(paramString.length(), smallChunkSize);
+        configData.resize(count + configDataOverhead);
+        std::copy_n(paramString.begin(), count,
+                    configData.begin() + configDataOverhead); // 14 bytes chunk
+
+        // Append zero's to remaining bytes
+        if (configData.size() < configParameterLength)
+        {
+            std::fill_n(std::back_inserter(configData),
+                        configParameterLength - configData.size(), 0x00);
+        }
+    }
+    else
+    {
+        size_t offset = (setSelector * fullChunkSize) - configDataOverhead;
+        if (offset >= paramString.length())
+        {
+            return ipmi::responseParmOutOfRange();
+        }
+        count = std::min(paramString.length() - offset, fullChunkSize);
+        configData.resize(count);
+        std::copy_n(paramString.begin() + offset, count,
+                    configData.begin()); // 16 bytes chunk
+    }
+    return ipmi::responseSuccess(paramRevision, setSelector, configData);
+}
+
+ipmi::RspType<> ipmiAppSetSystemInfo(uint8_t paramSelector, uint8_t data1,
+                                     std::vector<uint8_t> configData)
+{
+    if (paramSelector >= invalidParamSelectorStart &&
+        paramSelector <= invalidParamSelectorEnd)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+    if (paramSelector >= oemCmdStart)
+    {
+        return ipmi::responseParmNotSupported();
+    }
+
+    if (paramSelector == 0)
+    {
+        // attempt to set the 'set in progress' value (in parameter #0)
+        // when not in the set complete state.
+        if ((transferStatus != setComplete) && (data1 == setInProgress))
+        {
+            return ipmi::responseSetInProgressActive();
+        }
+        // only following 2 states are supported
+        if (data1 > setInProgress)
+        {
+            lg2::error("illegal SetInProgress status");
+            return ipmi::responseInvalidFieldRequest();
+        }
+
+        transferStatus = data1 & progressMask;
+        return ipmi::responseSuccess();
+    }
+
+    if (configData.size() > configParameterLength)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    // Append zero's to remaining bytes
+    if (configData.size() < configParameterLength)
+    {
+        fill_n(back_inserter(configData),
+               (configParameterLength - configData.size()), 0x00);
+    }
+
+    if (!sysInfoParamStore)
+    {
+        sysInfoParamStore = std::make_unique<SysInfoParamStore>();
+        sysInfoParamStore->update(IPMI_SYSINFO_SYSTEM_NAME,
+                                  sysInfoReadSystemName);
+    }
+
+    // lookup
+    std::tuple<bool, std::string> ret =
+        sysInfoParamStore->lookup(paramSelector);
+    bool found = std::get<0>(ret);
+    std::string& paramString = std::get<1>(ret);
+    if (!found)
+    {
+        // parameter does not exist. Init new
+        paramString = "";
+    }
+
+    uint8_t setSelector = data1;
+    size_t count = 0;
+    if (setSelector == 0) // First chunk has only 14 bytes.
+    {
+        uint8_t encoding = configData.at(0);
+        if (encoding > maxValidEncodingData)
+        {
+            return ipmi::responseInvalidFieldRequest();
+        }
+
+        size_t stringLen = configData.at(1); // string length
+        count = std::min(stringLen, smallChunkSize);
+        count = std::min(count, configData.size());
+        paramString.resize(stringLen); // reserve space
+        std::copy_n(configData.begin() + configDataOverhead, count,
+                    paramString.begin());
+    }
+    else
+    {
+        size_t offset = (setSelector * fullChunkSize) - configDataOverhead;
+        if (offset >= paramString.length())
+        {
+            return ipmi::responseParmOutOfRange();
+        }
+        count = std::min(paramString.length() - offset, configData.size());
+        std::copy_n(configData.begin(), count, paramString.begin() + offset);
+    }
+    sysInfoParamStore->update(paramSelector, paramString);
+    return ipmi::responseSuccess();
+}
+
+#ifdef ENABLE_I2C_WHITELIST_CHECK
+inline std::vector<uint8_t> convertStringToData(const std::string& command)
+{
+    std::istringstream iss(command);
+    std::string token;
+    std::vector<uint8_t> dataValue;
+    while (std::getline(iss, token, ' '))
+    {
+        dataValue.emplace_back(
+            static_cast<uint8_t>(std::stoul(token, nullptr, base_16)));
+    }
+    return dataValue;
+}
+
+static bool populateI2CControllerWRAllowlist()
+{
+    nlohmann::json data = nullptr;
+    std::ifstream jsonFile(i2cControllerWRAllowlistFile);
+
+    if (!jsonFile.good())
+    {
+        lg2::warning("i2c allow list file not found! file name: {FILE_NAME}",
+                     "FILE_NAME", i2cControllerWRAllowlistFile);
+        return false;
+    }
+
+    try
+    {
+        data = nlohmann::json::parse(jsonFile, nullptr, false);
+    }
+    catch (const nlohmann::json::parse_error& e)
+    {
+        lg2::error("Corrupted i2c allow list config file, "
+                   "file name: {FILE_NAME}, error: {ERROR}",
+                   "FILE_NAME", i2cControllerWRAllowlistFile, "ERROR", e);
+        return false;
+    }
+
+    try
+    {
+        // Example JSON Structure format
+        // "filters": [
+        //    {
+        //      "Description": "Allow full read - ignore first byte write value
+        //      for 0x40 to 0x4F",
+        //      "busId": "0x01",
+        //      "slaveAddr": "0x40",
+        //      "slaveAddrMask": "0x0F",
+        //      "command": "0x00",
+        //      "commandMask": "0xFF"
+        //    },
+        //    {
+        //      "Description": "Allow full read - first byte match 0x05 and
+        //      ignore second byte",
+        //      "busId": "0x01",
+        //      "slaveAddr": "0x57",
+        //      "slaveAddrMask": "0x00",
+        //      "command": "0x05 0x00",
+        //      "commandMask": "0x00 0xFF"
+        //    },]
+
+        nlohmann::json filters = data[filtersStr].get<nlohmann::json>();
+        std::vector<i2cControllerWRAllowlist>& allowlist = getWRAllowlist();
+        for (const auto& it : filters.items())
+        {
+            nlohmann::json filter = it.value();
+            if (filter.is_null())
+            {
+                lg2::error(
+                    "Corrupted I2C controller write read allowlist config file, "
+                    "file name: {FILE_NAME}",
+                    "FILE_NAME", i2cControllerWRAllowlistFile);
+                return false;
+            }
+            const std::vector<uint8_t>& writeData =
+                convertStringToData(filter[cmdStr].get<std::string>());
+            const std::vector<uint8_t>& writeDataMask =
+                convertStringToData(filter[cmdMaskStr].get<std::string>());
+            if (writeDataMask.size() != writeData.size())
+            {
+                lg2::error("I2C controller write read allowlist filter "
+                           "mismatch for command & mask size");
+                return false;
+            }
+            allowlist.push_back(
+                {static_cast<uint8_t>(std::stoul(
+                     filter[busIdStr].get<std::string>(), nullptr, base_16)),
+                 static_cast<uint8_t>(
+                     std::stoul(filter[targetAddrStr].get<std::string>(),
+                                nullptr, base_16)),
+                 static_cast<uint8_t>(
+                     std::stoul(filter[targetAddrMaskStr].get<std::string>(),
+                                nullptr, base_16)),
+                 writeData, writeDataMask});
+        }
+        if (allowlist.size() != filters.size())
+        {
+            lg2::error(
+                "I2C controller write read allowlist filter size mismatch");
+            return false;
+        }
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("I2C controller write read allowlist "
+                   "unexpected exception: {ERROR}",
+                   "ERROR", e);
+        return false;
+    }
+    return true;
+}
+
+static inline bool isWriteDataAllowlisted(const std::vector<uint8_t>& data,
+                                          const std::vector<uint8_t>& dataMask,
+                                          const std::vector<uint8_t>& writeData)
+{
+    std::vector<uint8_t> processedDataBuf(data.size());
+    std::vector<uint8_t> processedReqBuf(dataMask.size());
+    std::transform(writeData.begin(), writeData.end(), dataMask.begin(),
+                   processedReqBuf.begin(), std::bit_or<uint8_t>());
+    std::transform(data.begin(), data.end(), dataMask.begin(),
+                   processedDataBuf.begin(), std::bit_or<uint8_t>());
+
+    return (processedDataBuf == processedReqBuf);
+}
+
+static bool isCmdAllowlisted(uint8_t busId, uint8_t targetAddr,
+                             std::vector<uint8_t>& writeData)
+{
+    std::vector<i2cControllerWRAllowlist>& allowList = getWRAllowlist();
+    for (const auto& wlEntry : allowList)
+    {
+        if ((busId == wlEntry.busId) &&
+            ((targetAddr | wlEntry.targetAddrMask) ==
+             (wlEntry.targetAddr | wlEntry.targetAddrMask)))
+        {
+            const std::vector<uint8_t>& dataMask = wlEntry.dataMask;
+            // Skip as no-match, if requested write data is more than the
+            // write data mask size
+            if (writeData.size() > dataMask.size())
+            {
+                continue;
+            }
+            if (isWriteDataAllowlisted(wlEntry.data, dataMask, writeData))
+            {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+#else
+static bool populateI2CControllerWRAllowlist()
+{
+    lg2::info("I2C_WHITELIST_CHECK is disabled, do not populate allowlist");
+    return true;
+}
+#endif // ENABLE_I2C_WHITELIST_CHECK
+
+/** @brief implements controller write read IPMI command which can be used for
+ * low-level I2C/SMBus write, read or write-read access
+ *  @param isPrivateBus -to indicate private bus usage
+ *  @param busId - bus id
+ *  @param channelNum - channel number
+ *  @param reserved - skip 1 bit
+ *  @param targetAddr - target address
+ *  @param read count - number of bytes to be read
+ *  @param writeData - data to be written
+ *
+ *  @returns IPMI completion code plus response data
+ *   - readData - i2c response data
+ */
+ipmi::RspType<std::vector<uint8_t>> ipmiControllerWriteRead(
+    [[maybe_unused]] bool isPrivateBus, uint3_t busId,
+    [[maybe_unused]] uint4_t channelNum, bool reserved, uint7_t targetAddr,
+    uint8_t readCount, std::vector<uint8_t> writeData)
+{
+    if (reserved)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+    const size_t writeCount = writeData.size();
+    if (!readCount && !writeCount)
+    {
+        lg2::error("Controller write read command: Read & write count are 0");
+        return ipmi::responseInvalidFieldRequest();
+    }
+#ifdef ENABLE_I2C_WHITELIST_CHECK
+    if (!isCmdAllowlisted(static_cast<uint8_t>(busId),
+                          static_cast<uint8_t>(targetAddr), writeData))
+    {
+        lg2::error("Controller write read request blocked!, "
+                   "bus: {BUS}, addr: {ADDR}",
+                   "BUS", static_cast<uint8_t>(busId), "ADDR", lg2::hex,
+                   static_cast<uint8_t>(targetAddr));
+    }
+#endif // ENABLE_I2C_WHITELIST_CHECK
+    std::vector<uint8_t> readBuf(readCount);
+    std::string i2cBus =
+        "/dev/i2c-" + std::to_string(static_cast<uint8_t>(busId));
+
+    ipmi::Cc ret = ipmi::i2cWriteRead(i2cBus, static_cast<uint8_t>(targetAddr),
+                                      writeData, readBuf);
+    if (ret != ipmi::ccSuccess)
+    {
+        return ipmi::response(ret);
+    }
+    return ipmi::responseSuccess(readBuf);
+}
+
+void registerNetFnAppFunctions()
+{
+    // <Get Device ID>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdGetDeviceId, ipmi::Privilege::User,
+                          ipmiAppGetDeviceId);
+
+    // <Get BT Interface Capabilities>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdGetBtIfaceCapabilities,
+                          ipmi::Privilege::User, ipmiAppGetBtCapabilities);
+
+    // <Reset Watchdog Timer>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdResetWatchdogTimer,
+                          ipmi::Privilege::Operator, ipmiAppResetWatchdogTimer);
+
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdGetSessionInfo, ipmi::Privilege::User,
+                          ipmiAppGetSessionInfo);
+
+    // <Set Watchdog Timer>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdSetWatchdogTimer,
+                          ipmi::Privilege::Operator, ipmiSetWatchdogTimer);
+
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdCloseSession, ipmi::Privilege::Callback,
+                          ipmiAppCloseSession);
+
+    // <Get Watchdog Timer>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdGetWatchdogTimer, ipmi::Privilege::User,
+                          ipmiGetWatchdogTimer);
+
+    // <Get Self Test Results>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdGetSelfTestResults,
+                          ipmi::Privilege::User, ipmiAppGetSelfTestResults);
+
+    // <Get Device GUID>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdGetDeviceGuid, ipmi::Privilege::User,
+                          ipmiAppGetDeviceGuid);
+
+    // <Set ACPI Power State>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdSetAcpiPowerState,
+                          ipmi::Privilege::Admin, ipmiSetAcpiPowerState);
+    // <Get ACPI Power State>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdGetAcpiPowerState,
+                          ipmi::Privilege::User, ipmiGetAcpiPowerState);
+
+    // Note: For security reason, this command will be registered only when
+    // there are proper I2C Controller write read allowlist
+    if (populateI2CControllerWRAllowlist())
+    {
+        // Note: For security reasons, registering controller write read as
+        // admin privilege command, even though IPMI 2.0 specification allows it
+        // as operator privilege.
+        ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                              ipmi::app::cmdMasterWriteRead,
+                              ipmi::Privilege::Admin, ipmiControllerWriteRead);
+    }
+
+    // <Get System GUID Command>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdGetSystemGuid, ipmi::Privilege::User,
+                          ipmiAppGetSystemGuid);
+
+    // <Get Channel Cipher Suites Command>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdGetChannelCipherSuites,
+                          ipmi::Privilege::None, getChannelCipherSuites);
+
+    // <Get System Info Command>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdGetSystemInfoParameters,
+                          ipmi::Privilege::User, ipmiAppGetSystemInfo);
+    // <Set System Info Command>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdSetSystemInfoParameters,
+                          ipmi::Privilege::Admin, ipmiAppSetSystemInfo);
+    return;
+}
diff --git a/apphandler.hpp b/apphandler.hpp
new file mode 100644
index 0000000..56dd0f8
--- /dev/null
+++ b/apphandler.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <stdint.h>
+
+enum ipmi_app_sysinfo_params
+{
+    IPMI_SYSINFO_SET_STATE = 0x00,
+    IPMI_SYSINFO_SYSTEM_FW_VERSION = 0x01,
+    IPMI_SYSINFO_SYSTEM_NAME = 0x02,
+    IPMI_SYSINFO_PRIMARY_OS_NAME = 0x03,
+    IPMI_SYSINFO_OS_NAME = 0x04,
+    IPMI_SYSINFO_OS_VERSION = 0x05,
+    IPMI_SYSINFO_BMC_URL = 0x06,
+    IPMI_SYSINFO_OS_HYP_URL = 0x07,
+    IPMI_SYSINFO_OEM_START = 0xC0, // Start of range of OEM parameters
+};
diff --git a/chassishandler.cpp b/chassishandler.cpp
new file mode 100644
index 0000000..07e5544
--- /dev/null
+++ b/chassishandler.cpp
@@ -0,0 +1,2430 @@
+#include "config.h"
+
+#include "chassishandler.hpp"
+
+#include <arpa/inet.h>
+#include <endian.h>
+#include <limits.h>
+#include <netinet/in.h>
+
+#include <ipmid/api.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <sdbusplus/timer.hpp>
+#include <settings.hpp>
+#include <xyz/openbmc_project/Chassis/Intrusion/client.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/Control/Boot/Mode/server.hpp>
+#include <xyz/openbmc_project/Control/Boot/Source/server.hpp>
+#include <xyz/openbmc_project/Control/Boot/Type/server.hpp>
+#include <xyz/openbmc_project/Control/Power/RestorePolicy/server.hpp>
+#include <xyz/openbmc_project/State/Chassis/server.hpp>
+#include <xyz/openbmc_project/State/Host/server.hpp>
+#include <xyz/openbmc_project/State/PowerOnHours/server.hpp>
+
+#include <array>
+#include <chrono>
+#include <cstring>
+#include <filesystem>
+#include <fstream>
+#include <future>
+#include <map>
+#include <sstream>
+#include <string>
+
+std::unique_ptr<sdbusplus::Timer> identifyTimer
+    __attribute__((init_priority(101)));
+
+static ChassisIDState chassisIDState = ChassisIDState::reserved;
+
+constexpr size_t sizeVersion = 2;
+constexpr size_t DEFAULT_IDENTIFY_TIME_OUT = 15;
+
+// PetiBoot-Specific
+static constexpr uint8_t netConfInitialBytes[] = {0x80, 0x21, 0x70, 0x62,
+                                                  0x21, 0x00, 0x01, 0x06};
+static constexpr uint8_t oemParmStart = 96;
+static constexpr uint8_t oemParmEnd = 127;
+
+static constexpr size_t cookieOffset = 1;
+static constexpr size_t versionOffset = 5;
+static constexpr size_t addrSizeOffset = 8;
+static constexpr size_t macOffset = 9;
+static constexpr size_t addrTypeOffset = 16;
+static constexpr size_t ipAddrOffset = 17;
+
+namespace ipmi
+{
+constexpr Cc ccParmNotSupported = 0x80;
+
+static inline auto responseParmNotSupported()
+{
+    return response(ccParmNotSupported);
+}
+} // namespace ipmi
+
+void registerNetFnChassisFunctions() __attribute__((constructor));
+
+// Host settings in dbus
+// Service name should be referenced by connection name got via object mapper
+const char* settings_object_name = "/org/openbmc/settings/host0";
+const char* settings_intf_name = "org.freedesktop.DBus.Properties";
+const char* identify_led_object_name =
+    "/xyz/openbmc_project/led/groups/enclosure_identify";
+
+constexpr auto SETTINGS_ROOT = "/";
+constexpr auto SETTINGS_MATCH = "host0";
+
+constexpr auto IP_INTERFACE = "xyz.openbmc_project.Network.IP";
+constexpr auto MAC_INTERFACE = "xyz.openbmc_project.Network.MACAddress";
+
+static constexpr auto chassisStateRoot = "/xyz/openbmc_project/state";
+static constexpr auto chassisPOHStateIntf =
+    "xyz.openbmc_project.State.PowerOnHours";
+static constexpr auto pohCounterProperty = "POHCounter";
+static constexpr auto match = "chassis0";
+const static constexpr char chassisCapIntf[] =
+    "xyz.openbmc_project.Control.ChassisCapabilities";
+const static constexpr char chassisIntrusionProp[] = "ChassisIntrusionEnabled";
+const static constexpr char chassisFrontPanelLockoutProp[] =
+    "ChassisFrontPanelLockoutEnabled";
+const static constexpr char chassisNMIProp[] = "ChassisNMIEnabled";
+const static constexpr char chassisPowerInterlockProp[] =
+    "ChassisPowerInterlockEnabled";
+const static constexpr char chassisFRUDevAddrProp[] = "FRUDeviceAddress";
+const static constexpr char chassisSDRDevAddrProp[] = "SDRDeviceAddress";
+const static constexpr char chassisSELDevAddrProp[] = "SELDeviceAddress";
+const static constexpr char chassisSMDevAddrProp[] = "SMDeviceAddress";
+const static constexpr char chassisBridgeDevAddrProp[] = "BridgeDeviceAddress";
+static constexpr uint8_t chassisCapAddrMask = 0xfe;
+static constexpr const char* powerButtonIntf =
+    "xyz.openbmc_project.Chassis.Buttons.Power";
+static constexpr const char* powerButtonPath =
+    "/xyz/openbmc_project/Chassis/Buttons/Power0";
+static constexpr const char* resetButtonIntf =
+    "xyz.openbmc_project.Chassis.Buttons.Reset";
+static constexpr const char* resetButtonPath =
+    "/xyz/openbmc_project/Chassis/Buttons/Reset0";
+
+// Phosphor Host State manager
+namespace State = sdbusplus::server::xyz::openbmc_project::state;
+namespace fs = std::filesystem;
+
+using namespace phosphor::logging;
+using namespace sdbusplus::error::xyz::openbmc_project::common;
+using namespace sdbusplus::server::xyz::openbmc_project::control::boot;
+using Intrusion = sdbusplus::client::xyz::openbmc_project::chassis::Intrusion<>;
+
+namespace chassis
+{
+namespace internal
+{
+
+constexpr auto bootSettingsPath = "/xyz/openbmc_project/control/host0/boot";
+constexpr auto bootEnableIntf = "xyz.openbmc_project.Object.Enable";
+constexpr auto bootModeIntf = "xyz.openbmc_project.Control.Boot.Mode";
+constexpr auto bootTypeIntf = "xyz.openbmc_project.Control.Boot.Type";
+constexpr auto bootSourceIntf = "xyz.openbmc_project.Control.Boot.Source";
+constexpr auto bootSettingsOneTimePath =
+    "/xyz/openbmc_project/control/host0/boot/one_time";
+constexpr auto bootOneTimeIntf = "xyz.openbmc_project.Object.Enable";
+
+constexpr auto powerRestoreIntf =
+    "xyz.openbmc_project.Control.Power.RestorePolicy";
+sdbusplus::bus_t dbus(ipmid_get_sd_bus_connection());
+
+namespace cache
+{
+
+std::unique_ptr<settings::Objects> objectsPtr = nullptr;
+
+settings::Objects& getObjects()
+{
+    if (objectsPtr == nullptr)
+    {
+        objectsPtr = std::make_unique<settings::Objects>(
+            dbus, std::vector<std::string>{bootModeIntf, bootTypeIntf,
+                                           bootSourceIntf, powerRestoreIntf});
+    }
+    return *objectsPtr;
+}
+
+} // namespace cache
+} // namespace internal
+} // namespace chassis
+
+namespace poh
+{
+
+constexpr auto minutesPerCount = 60;
+
+} // namespace poh
+
+int getHostNetworkData(ipmi::message::Payload& payload)
+{
+    ipmi::PropertyMap properties;
+    int rc = 0;
+    uint8_t addrSize = ipmi::network::IPV4_ADDRESS_SIZE_BYTE;
+
+    try
+    {
+        // TODO There may be cases where an interface is implemented by multiple
+        // objects,to handle such cases we are interested on that object
+        //  which are on interested busname.
+        //  Currenlty mapper doesn't give the readable busname(gives busid)
+        //  so we can't match with bus name so giving some object specific info
+        //  as SETTINGS_MATCH.
+        //  Later SETTINGS_MATCH will be replaced with busname.
+
+        sdbusplus::bus_t bus(ipmid_get_sd_bus_connection());
+
+        auto ipObjectInfo = ipmi::getDbusObject(bus, IP_INTERFACE,
+                                                SETTINGS_ROOT, SETTINGS_MATCH);
+
+        auto macObjectInfo = ipmi::getDbusObject(bus, MAC_INTERFACE,
+                                                 SETTINGS_ROOT, SETTINGS_MATCH);
+
+        properties = ipmi::getAllDbusProperties(
+            bus, ipObjectInfo.second, ipObjectInfo.first, IP_INTERFACE);
+        auto variant = ipmi::getDbusProperty(
+            bus, macObjectInfo.second, macObjectInfo.first, MAC_INTERFACE,
+            "MACAddress");
+
+        auto ipAddress = std::get<std::string>(properties["Address"]);
+
+        auto gateway = std::get<std::string>(properties["Gateway"]);
+
+        auto prefix = std::get<uint8_t>(properties["PrefixLength"]);
+
+        uint8_t isStatic =
+            (std::get<std::string>(properties["Origin"]) ==
+             "xyz.openbmc_project.Network.IP.AddressOrigin.Static")
+                ? 1
+                : 0;
+
+        auto MACAddress = std::get<std::string>(variant);
+
+        // it is expected here that we should get the valid data
+        // but we may also get the default values.
+        // Validation of the data is done by settings.
+        //
+        // if mac address is default mac address then
+        // don't send blank override.
+        if ((MACAddress == ipmi::network::DEFAULT_MAC_ADDRESS))
+        {
+            rc = -1;
+            return rc;
+        }
+        // if addr is static then ipaddress,gateway,prefix
+        // should not be default one,don't send blank override.
+        if (isStatic)
+        {
+            if ((ipAddress == ipmi::network::DEFAULT_ADDRESS) ||
+                (gateway == ipmi::network::DEFAULT_ADDRESS) || (!prefix))
+            {
+                rc = -1;
+                return rc;
+            }
+        }
+
+        std::string token;
+        std::stringstream ss(MACAddress);
+
+        // First pack macOffset no of bytes in payload.
+        // Latter this PetiBoot-Specific data will be populated.
+        std::vector<uint8_t> payloadInitialBytes(macOffset);
+        payload.pack(payloadInitialBytes);
+
+        while (std::getline(ss, token, ':'))
+        {
+            payload.pack(stoi(token, nullptr, 16));
+        }
+
+        payload.pack(0x00);
+
+        payload.pack(isStatic);
+
+        uint8_t addressFamily = (std::get<std::string>(properties["Type"]) ==
+                                 "xyz.openbmc_project.Network.IP.Protocol.IPv4")
+                                    ? AF_INET
+                                    : AF_INET6;
+
+        addrSize = (addressFamily == AF_INET)
+                       ? ipmi::network::IPV4_ADDRESS_SIZE_BYTE
+                       : ipmi::network::IPV6_ADDRESS_SIZE_BYTE;
+
+        // ipaddress and gateway would be in IPv4 format
+        std::vector<uint8_t> addrInBinary(addrSize);
+        inet_pton(addressFamily, ipAddress.c_str(),
+                  reinterpret_cast<void*>(addrInBinary.data()));
+
+        payload.pack(addrInBinary);
+
+        payload.pack(prefix);
+
+        std::vector<uint8_t> gatewayDetails(addrSize);
+        inet_pton(addressFamily, gateway.c_str(),
+                  reinterpret_cast<void*>(gatewayDetails.data()));
+        payload.pack(gatewayDetails);
+    }
+    catch (const InternalFailure& e)
+    {
+        commit<InternalFailure>();
+        rc = -1;
+        return rc;
+    }
+
+    // PetiBoot-Specific
+    // If success then copy the first 9 bytes to the payload message
+    // payload first 2 bytes contain the parameter values. Skip that 2 bytes.
+    uint8_t skipFirstTwoBytes = 2;
+    size_t payloadSize = payload.size();
+    uint8_t* configDataStartingAddress = payload.data() + skipFirstTwoBytes;
+
+    if (payloadSize < skipFirstTwoBytes + sizeof(netConfInitialBytes))
+    {
+        lg2::error("Invalid net config");
+        rc = -1;
+        return rc;
+    }
+    std::copy(netConfInitialBytes,
+              netConfInitialBytes + sizeof(netConfInitialBytes),
+              configDataStartingAddress);
+
+    if (payloadSize < skipFirstTwoBytes + addrSizeOffset + sizeof(addrSize))
+    {
+        lg2::error("Invalid length of address size");
+        rc = -1;
+        return rc;
+    }
+    std::copy(&addrSize, &(addrSize) + sizeof(addrSize),
+              configDataStartingAddress + addrSizeOffset);
+
+#ifdef _IPMI_DEBUG_
+    std::printf("\n===Printing the IPMI Formatted Data========\n");
+
+    for (uint8_t pos = 0; pos < index; pos++)
+    {
+        std::printf("%02x ", payloadStartingAddress[pos]);
+    }
+#endif
+
+    return rc;
+}
+
+/** @brief convert IPv4 and IPv6 addresses from binary to text form.
+ *  @param[in] family - IPv4/Ipv6
+ *  @param[in] data - req data pointer.
+ *  @param[in] offset - offset in the data.
+ *  @param[in] addrSize - size of the data which needs to be read from offset.
+ *  @returns address in text form.
+ */
+
+std::string getAddrStr(uint8_t family, uint8_t* data, uint8_t offset,
+                       uint8_t addrSize)
+{
+    char ipAddr[INET6_ADDRSTRLEN] = {};
+
+    switch (family)
+    {
+        case AF_INET:
+        {
+            struct sockaddr_in addr4{};
+            std::memcpy(&addr4.sin_addr.s_addr, &data[offset], addrSize);
+
+            inet_ntop(AF_INET, &addr4.sin_addr, ipAddr, INET_ADDRSTRLEN);
+
+            break;
+        }
+        case AF_INET6:
+        {
+            struct sockaddr_in6 addr6{};
+            std::memcpy(&addr6.sin6_addr.s6_addr, &data[offset], addrSize);
+
+            inet_ntop(AF_INET6, &addr6.sin6_addr, ipAddr, INET6_ADDRSTRLEN);
+
+            break;
+        }
+        default:
+        {
+            return {};
+        }
+    }
+
+    return ipAddr;
+}
+
+ipmi::Cc setHostNetworkData(ipmi::message::Payload& data)
+{
+    using namespace std::string_literals;
+    std::string hostNetworkConfig;
+    std::string mac("00:00:00:00:00:00");
+    std::string ipAddress, gateway;
+    std::string addrOrigin{0};
+    uint8_t addrSize{0};
+    std::string addressOrigin =
+        "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP";
+    std::string addressType = "xyz.openbmc_project.Network.IP.Protocol.IPv4";
+    uint8_t prefix{0};
+    uint8_t family = AF_INET;
+
+    // cookie starts from second byte
+    // version starts from sixth byte
+
+    try
+    {
+        do
+        {
+            // cookie ==  0x21 0x70 0x62 0x21
+            data.trailingOk = true;
+            auto msgLen = data.size();
+            std::vector<uint8_t> msgPayloadBytes(msgLen);
+            if (data.unpack(msgPayloadBytes) != 0 || !data.fullyUnpacked())
+            {
+                lg2::error("Error in unpacking message of setHostNetworkData");
+                return ipmi::ccReqDataLenInvalid;
+            }
+
+            uint8_t* msgPayloadStartingPos = msgPayloadBytes.data();
+            constexpr size_t cookieSize = 4;
+            if (msgLen < cookieOffset + cookieSize)
+            {
+                lg2::error("Error in cookie getting of setHostNetworkData");
+                return ipmi::ccReqDataLenInvalid;
+            }
+            if (std::equal(msgPayloadStartingPos + cookieOffset,
+                           msgPayloadStartingPos + cookieOffset + cookieSize,
+                           (netConfInitialBytes + cookieOffset)) != 0)
+            {
+                // all cookie == 0
+                if (std::all_of(msgPayloadStartingPos + cookieOffset,
+                                msgPayloadStartingPos + cookieOffset +
+                                    cookieSize,
+                                [](int i) { return i == 0; }) == true)
+                {
+                    // need to zero out the network settings.
+                    break;
+                }
+
+                lg2::error("Invalid Cookie");
+                elog<InternalFailure>();
+            }
+
+            // vesion == 0x00 0x01
+            if (msgLen < versionOffset + sizeVersion)
+            {
+                lg2::error("Error in version getting of setHostNetworkData");
+                return ipmi::ccReqDataLenInvalid;
+            }
+            if (std::equal(msgPayloadStartingPos + versionOffset,
+                           msgPayloadStartingPos + versionOffset + sizeVersion,
+                           (netConfInitialBytes + versionOffset)) != 0)
+            {
+                lg2::error("Invalid Version");
+                elog<InternalFailure>();
+            }
+
+            if (msgLen < macOffset + 6)
+            {
+                lg2::error(
+                    "Error in mac address getting of setHostNetworkData");
+                return ipmi::ccReqDataLenInvalid;
+            }
+            std::stringstream result;
+            std::copy((msgPayloadStartingPos + macOffset),
+                      (msgPayloadStartingPos + macOffset + 5),
+                      std::ostream_iterator<int>(result, ":"));
+            mac = result.str();
+
+            if (msgLen < addrTypeOffset + sizeof(decltype(addrOrigin)))
+            {
+                lg2::error(
+                    "Error in original address getting of setHostNetworkData");
+                return ipmi::ccReqDataLenInvalid;
+            }
+            std::copy(msgPayloadStartingPos + addrTypeOffset,
+                      msgPayloadStartingPos + addrTypeOffset +
+                          sizeof(decltype(addrOrigin)),
+                      std::ostream_iterator<int>(result, ""));
+            addrOrigin = result.str();
+
+            if (!addrOrigin.empty())
+            {
+                addressOrigin =
+                    "xyz.openbmc_project.Network.IP.AddressOrigin.Static";
+            }
+
+            if (msgLen < addrSizeOffset + sizeof(decltype(addrSize)))
+            {
+                lg2::error(
+                    "Error in address size getting of setHostNetworkData");
+                return ipmi::ccReqDataLenInvalid;
+            }
+            // Get the address size
+            std::copy(msgPayloadStartingPos + addrSizeOffset,
+                      (msgPayloadStartingPos + addrSizeOffset +
+                       sizeof(decltype(addrSize))),
+                      &addrSize);
+
+            uint8_t prefixOffset = ipAddrOffset + addrSize;
+            if (msgLen < prefixOffset + sizeof(decltype(prefix)))
+            {
+                lg2::error("Error in prefix getting of setHostNetworkData");
+                return ipmi::ccReqDataLenInvalid;
+            }
+            // std::copy(msgPayloadStartingPos + prefixOffset,
+            //           msgPayloadStartingPos + prefixOffset +
+            //               sizeof(decltype(prefix)),
+            //           &prefix);
+            // Workaround compiler misdetecting out of bounds memcpy
+            prefix = msgPayloadStartingPos[prefixOffset];
+
+            uint8_t gatewayOffset = prefixOffset + sizeof(decltype(prefix));
+            if (addrSize != ipmi::network::IPV4_ADDRESS_SIZE_BYTE)
+            {
+                addressType = "xyz.openbmc_project.Network.IP.Protocol.IPv6";
+                family = AF_INET6;
+            }
+
+            if (msgLen < ipAddrOffset + addrSize)
+            {
+                lg2::error("Error in IP address getting of setHostNetworkData");
+                return ipmi::ccReqDataLenInvalid;
+            }
+            ipAddress = getAddrStr(family, msgPayloadStartingPos, ipAddrOffset,
+                                   addrSize);
+
+            if (msgLen < gatewayOffset + addrSize)
+            {
+                lg2::error(
+                    "Error in gateway address getting of setHostNetworkData");
+                return ipmi::ccReqDataLenInvalid;
+            }
+            gateway = getAddrStr(family, msgPayloadStartingPos, gatewayOffset,
+                                 addrSize);
+
+        } while (0);
+
+        // Cookie == 0 or it is a valid cookie
+        hostNetworkConfig +=
+            "ipaddress="s + ipAddress + ",prefix="s + std::to_string(prefix) +
+            ",gateway="s + gateway + ",mac="s + mac + ",addressOrigin="s +
+            addressOrigin;
+
+        sdbusplus::bus_t bus(ipmid_get_sd_bus_connection());
+
+        auto ipObjectInfo = ipmi::getDbusObject(bus, IP_INTERFACE,
+                                                SETTINGS_ROOT, SETTINGS_MATCH);
+        auto macObjectInfo = ipmi::getDbusObject(bus, MAC_INTERFACE,
+                                                 SETTINGS_ROOT, SETTINGS_MATCH);
+        // set the dbus property
+        ipmi::setDbusProperty(bus, ipObjectInfo.second, ipObjectInfo.first,
+                              IP_INTERFACE, "Address", std::string(ipAddress));
+        ipmi::setDbusProperty(bus, ipObjectInfo.second, ipObjectInfo.first,
+                              IP_INTERFACE, "PrefixLength", prefix);
+        ipmi::setDbusProperty(bus, ipObjectInfo.second, ipObjectInfo.first,
+                              IP_INTERFACE, "Origin", addressOrigin);
+        ipmi::setDbusProperty(bus, ipObjectInfo.second, ipObjectInfo.first,
+                              IP_INTERFACE, "Gateway", std::string(gateway));
+        ipmi::setDbusProperty(
+            bus, ipObjectInfo.second, ipObjectInfo.first, IP_INTERFACE, "Type",
+            std::string("xyz.openbmc_project.Network.IP.Protocol.IPv4"));
+        ipmi::setDbusProperty(bus, macObjectInfo.second, macObjectInfo.first,
+                              MAC_INTERFACE, "MACAddress", std::string(mac));
+
+        lg2::debug("Network configuration changed: {NETWORKCONFIG}",
+                   "NETWORKCONFIG", hostNetworkConfig);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        commit<InternalFailure>();
+        lg2::error("Error in ipmiChassisSetSysBootOptions call");
+        return ipmi::ccUnspecifiedError;
+    }
+
+    return ipmi::ccSuccess;
+}
+
+uint32_t getPOHCounter()
+{
+    sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+
+    auto chassisStateObj =
+        ipmi::getDbusObject(bus, chassisPOHStateIntf, chassisStateRoot, match);
+
+    auto service =
+        ipmi::getService(bus, chassisPOHStateIntf, chassisStateObj.first);
+
+    auto propValue =
+        ipmi::getDbusProperty(bus, service, chassisStateObj.first,
+                              chassisPOHStateIntf, pohCounterProperty);
+
+    return std::get<uint32_t>(propValue);
+}
+
+/** @brief Implements the get chassis capabilities command
+ *
+ *  @returns IPMI completion code plus response data
+ *  chassisCapFlags        - chassis capability flag
+ *  chassisFRUInfoDevAddr  - chassis FRU info Device Address
+ *  chassisSDRDevAddr      - chassis SDR device address
+ *  chassisSELDevAddr      - chassis SEL device address
+ *  chassisSMDevAddr       - chassis system management device address
+ *  chassisBridgeDevAddr   - chassis bridge device address
+ */
+ipmi::RspType<bool,    // chassis intrusion sensor
+              bool,    // chassis Front panel lockout
+              bool,    // chassis NMI
+              bool,    // chassis power interlock
+              uint4_t, // reserved
+              uint8_t, // chassis FRU info Device Address
+              uint8_t, // chassis SDR device address
+              uint8_t, // chassis SEL device address
+              uint8_t, // chassis system management device address
+              uint8_t  // chassis bridge device address
+              >
+    ipmiGetChassisCap()
+{
+    ipmi::PropertyMap properties;
+    try
+    {
+        sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+
+        ipmi::DbusObjectInfo chassisCapObject =
+            ipmi::getDbusObject(bus, chassisCapIntf);
+
+        // capabilities flags
+        // [7..4] - reserved
+        // [3] – 1b = provides power interlock  (IPM 1.5)
+        // [2] – 1b = provides Diagnostic Interrupt (FP NMI)
+        // [1] – 1b = provides “Front Panel Lockout” (indicates that the chassis
+        // has capabilities
+        //            to lock out external power control and reset button or
+        //            front panel interfaces and/or detect tampering with those
+        //            interfaces).
+        // [0] -1b = Chassis provides intrusion (physical security) sensor.
+        // set to default value 0x0.
+
+        properties =
+            ipmi::getAllDbusProperties(bus, chassisCapObject.second,
+                                       chassisCapObject.first, chassisCapIntf);
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Failed to fetch Chassis Capability properties: {ERROR}",
+                   "ERROR", e);
+        return ipmi::responseUnspecifiedError();
+    }
+
+    bool* chassisIntrusionFlag =
+        std::get_if<bool>(&properties[chassisIntrusionProp]);
+    if (chassisIntrusionFlag == nullptr)
+    {
+        lg2::error("Error to get chassis Intrusion flags");
+        return ipmi::responseUnspecifiedError();
+    }
+    bool* chassisFrontPanelFlag =
+        std::get_if<bool>(&properties[chassisFrontPanelLockoutProp]);
+    if (chassisFrontPanelFlag == nullptr)
+    {
+        lg2::error("Error to get chassis intrusion flags");
+        return ipmi::responseUnspecifiedError();
+    }
+    bool* chassisNMIFlag = std::get_if<bool>(&properties[chassisNMIProp]);
+    if (chassisNMIFlag == nullptr)
+    {
+        lg2::error("Error to get chassis NMI flags");
+        return ipmi::responseUnspecifiedError();
+    }
+    bool* chassisPowerInterlockFlag =
+        std::get_if<bool>(&properties[chassisPowerInterlockProp]);
+    if (chassisPowerInterlockFlag == nullptr)
+    {
+        lg2::error("Error to get chassis power interlock flags");
+        return ipmi::responseUnspecifiedError();
+    }
+    uint8_t* chassisFRUInfoDevAddr =
+        std::get_if<uint8_t>(&properties[chassisFRUDevAddrProp]);
+    if (chassisFRUInfoDevAddr == nullptr)
+    {
+        lg2::error("Error to get chassis FRU info device address");
+        return ipmi::responseUnspecifiedError();
+    }
+    uint8_t* chassisSDRDevAddr =
+        std::get_if<uint8_t>(&properties[chassisSDRDevAddrProp]);
+    if (chassisSDRDevAddr == nullptr)
+    {
+        lg2::error("Error to get chassis SDR device address");
+        return ipmi::responseUnspecifiedError();
+    }
+    uint8_t* chassisSELDevAddr =
+        std::get_if<uint8_t>(&properties[chassisSELDevAddrProp]);
+    if (chassisSELDevAddr == nullptr)
+    {
+        lg2::error("Error to get chassis SEL device address");
+        return ipmi::responseUnspecifiedError();
+    }
+    uint8_t* chassisSMDevAddr =
+        std::get_if<uint8_t>(&properties[chassisSMDevAddrProp]);
+    if (chassisSMDevAddr == nullptr)
+    {
+        lg2::error("Error to get chassis SM device address");
+        return ipmi::responseUnspecifiedError();
+    }
+    uint8_t* chassisBridgeDevAddr =
+        std::get_if<uint8_t>(&properties[chassisBridgeDevAddrProp]);
+    if (chassisBridgeDevAddr == nullptr)
+    {
+        lg2::error("Error to get chassis bridge device address");
+        return ipmi::responseUnspecifiedError();
+    }
+
+    return ipmi::responseSuccess(
+        *chassisIntrusionFlag, *chassisFrontPanelFlag, *chassisNMIFlag,
+        *chassisPowerInterlockFlag, 0, *chassisFRUInfoDevAddr,
+        *chassisSDRDevAddr, *chassisSELDevAddr, *chassisSMDevAddr,
+        *chassisBridgeDevAddr);
+}
+
+/** @brief implements set chassis capalibities command
+ *  @param intrusion        - chassis intrusion
+ *  @param fpLockout        - frontpannel lockout
+ *  @param reserved1        - skip one bit
+ *  @param fruDeviceAddr    - chassis FRU info Device Address
+ *  @param sdrDeviceAddr    - chassis SDR device address
+ *  @param selDeviceAddr    - chassis SEL device address
+ *  @param smDeviceAddr     - chassis system management device address
+ *  @param bridgeDeviceAddr - chassis bridge device address
+ *
+ *  @returns IPMI completion code
+ */
+ipmi::RspType<> ipmiSetChassisCap(
+    bool intrusion, bool fpLockout, uint6_t reserved1,
+
+    uint8_t fruDeviceAddr,
+
+    uint8_t sdrDeviceAddr,
+
+    uint8_t selDeviceAddr,
+
+    uint8_t smDeviceAddr,
+
+    uint8_t bridgeDeviceAddr)
+{
+    // check input data
+    if (reserved1 != 0)
+    {
+        lg2::error("Unsupported request parameter");
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    if ((fruDeviceAddr & ~chassisCapAddrMask) != 0)
+    {
+        lg2::error("Unsupported request parameter(FRU Addr) for REQ={REQ}",
+                   "REQ", lg2::hex, fruDeviceAddr);
+        return ipmi::responseInvalidFieldRequest();
+    }
+    if ((sdrDeviceAddr & ~chassisCapAddrMask) != 0)
+    {
+        lg2::error("Unsupported request parameter(SDR Addr) for REQ={REQ}",
+                   "REQ", lg2::hex, sdrDeviceAddr);
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    if ((selDeviceAddr & ~chassisCapAddrMask) != 0)
+    {
+        lg2::error("Unsupported request parameter(SEL Addr) for REQ={REQ}",
+                   "REQ", lg2::hex, selDeviceAddr);
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    if ((smDeviceAddr & ~chassisCapAddrMask) != 0)
+    {
+        lg2::error("Unsupported request parameter(SM Addr) for REQ={REQ}",
+                   "REQ", lg2::hex, smDeviceAddr);
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    if ((bridgeDeviceAddr & ~chassisCapAddrMask) != 0)
+    {
+        lg2::error("Unsupported request parameter(Bridge Addr) for REQ={REQ}",
+                   "REQ", lg2::hex, bridgeDeviceAddr);
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    try
+    {
+        sdbusplus::bus_t bus(ipmid_get_sd_bus_connection());
+        ipmi::DbusObjectInfo chassisCapObject =
+            ipmi::getDbusObject(bus, chassisCapIntf);
+
+        ipmi::setDbusProperty(bus, chassisCapObject.second,
+                              chassisCapObject.first, chassisCapIntf,
+                              chassisIntrusionProp, intrusion);
+
+        ipmi::setDbusProperty(bus, chassisCapObject.second,
+                              chassisCapObject.first, chassisCapIntf,
+                              chassisFrontPanelLockoutProp, fpLockout);
+
+        ipmi::setDbusProperty(bus, chassisCapObject.second,
+                              chassisCapObject.first, chassisCapIntf,
+                              chassisFRUDevAddrProp, fruDeviceAddr);
+
+        ipmi::setDbusProperty(bus, chassisCapObject.second,
+                              chassisCapObject.first, chassisCapIntf,
+                              chassisSDRDevAddrProp, sdrDeviceAddr);
+
+        ipmi::setDbusProperty(bus, chassisCapObject.second,
+                              chassisCapObject.first, chassisCapIntf,
+                              chassisSELDevAddrProp, selDeviceAddr);
+
+        ipmi::setDbusProperty(bus, chassisCapObject.second,
+                              chassisCapObject.first, chassisCapIntf,
+                              chassisSMDevAddrProp, smDeviceAddr);
+
+        ipmi::setDbusProperty(bus, chassisCapObject.second,
+                              chassisCapObject.first, chassisCapIntf,
+                              chassisBridgeDevAddrProp, bridgeDeviceAddr);
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Failed to set chassis capability properties: {ERR}", "ERR",
+                   e);
+        return ipmi::responseUnspecifiedError();
+    }
+    return ipmi::responseSuccess();
+}
+
+//------------------------------------------
+// Calls into Host State Manager Dbus object
+//------------------------------------------
+int initiateHostStateTransition(ipmi::Context::ptr& ctx,
+                                State::Host::Transition transition)
+{
+    // OpenBMC Host State Manager dbus framework
+    constexpr auto hostStatePath = "/xyz/openbmc_project/state/host0";
+    constexpr auto hostStateIntf = "xyz.openbmc_project.State.Host";
+
+    // Convert to string equivalent of the passed in transition enum.
+    auto request =
+        sdbusplus::common::xyz::openbmc_project::state::convertForMessage(
+            transition);
+
+    std::string service;
+    boost::system::error_code ec =
+        ipmi::getService(ctx, hostStateIntf, hostStatePath, service);
+
+    if (!ec)
+    {
+        ec = ipmi::setDbusProperty(ctx, service, hostStatePath, hostStateIntf,
+                                   "RequestedHostTransition", request);
+    }
+    if (ec)
+    {
+        lg2::error(
+            "Failed to initiate transition for request {REQUEST}: {EXCEPTION}",
+            "REQUEST", request, "EXCEPTION", ec.message());
+        return -1;
+    }
+    lg2::info(
+        "Transition request {REQUEST} initiated successfully by user {USERID}",
+        "REQUEST", request, "USERID", ctx->userId);
+    return 0;
+}
+
+//------------------------------------------
+// Calls into Chassis State Manager Dbus object
+//------------------------------------------
+int initiateChassisStateTransition(ipmi::Context::ptr& ctx,
+                                   State::Chassis::Transition transition)
+{
+    // OpenBMC Chassis State Manager dbus framework
+    constexpr auto chassisStatePath = "/xyz/openbmc_project/state/chassis0";
+    constexpr auto chassisStateIntf = "xyz.openbmc_project.State.Chassis";
+
+    std::string service;
+    boost::system::error_code ec =
+        ipmi::getService(ctx, chassisStateIntf, chassisStatePath, service);
+
+    // Convert to string equivalent of the passed in transition enum.
+    auto request =
+        sdbusplus::common::xyz::openbmc_project::state::convertForMessage(
+            transition);
+
+    if (!ec)
+    {
+        ec = ipmi::setDbusProperty(ctx, service, chassisStatePath,
+                                   chassisStateIntf, "RequestedPowerTransition",
+                                   request);
+    }
+    if (ec)
+    {
+        lg2::error("Failed to initiate transition {REQUEST}: {EXCEPTION}",
+                   "REQUEST", request, "EXCEPTION", ec.message());
+
+        return -1;
+    }
+
+    return 0;
+}
+
+//------------------------------------------
+// Trigger an NMI on the host via dbus
+//------------------------------------------
+static int doNmi(ipmi::Context::ptr& ctx)
+{
+    constexpr const char* nmiIntfName = "xyz.openbmc_project.Control.Host.NMI";
+    ipmi::DbusObjectInfo nmiObj{};
+    boost::system::error_code ec;
+
+    ec = ipmi::getDbusObject(ctx, nmiIntfName, nmiObj);
+    if (ec)
+    {
+        lg2::error("Failed to find NMI service: {ERROR}", "ERROR",
+                   ec.message());
+        return -1;
+    }
+
+    ec = ipmi::callDbusMethod(ctx, nmiObj.second, nmiObj.first, nmiIntfName,
+                              "NMI");
+    if (ec)
+    {
+        lg2::error("NMI call failed: {ERROR}", "ERROR", ec.message());
+        elog<InternalFailure>();
+        return -1;
+    }
+
+    return 0;
+}
+
+namespace power_policy
+{
+
+using namespace sdbusplus::server::xyz::openbmc_project::control::power;
+using IpmiValue = uint8_t;
+using DbusValue = RestorePolicy::Policy;
+
+const std::map<DbusValue, IpmiValue> dbusToIpmi = {
+    {RestorePolicy::Policy::AlwaysOff, 0x00},
+    {RestorePolicy::Policy::Restore, 0x01},
+    {RestorePolicy::Policy::AlwaysOn, 0x02},
+    {RestorePolicy::Policy::None, 0x03}};
+
+static constexpr uint8_t noChange = 0x03;
+static constexpr uint8_t allSupport = 0x01 | 0x02 | 0x04;
+
+/* helper function for Get Chassis Status Command
+ */
+std::optional<uint2_t> getPowerRestorePolicy()
+{
+    uint2_t restorePolicy = 0;
+    using namespace chassis::internal;
+
+    settings::Objects& objects = cache::getObjects();
+
+    try
+    {
+        const auto& powerRestoreSetting =
+            objects.map.at(powerRestoreIntf).front();
+        ipmi::Value result = ipmi::getDbusProperty(
+            *getSdBus(),
+            objects.service(powerRestoreSetting, powerRestoreIntf).c_str(),
+            powerRestoreSetting.c_str(), powerRestoreIntf,
+            "PowerRestorePolicy");
+        auto powerRestore = RestorePolicy::convertPolicyFromString(
+            std::get<std::string>(result));
+        restorePolicy = dbusToIpmi.at(powerRestore);
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error(
+            "Failed to fetch pgood property ({PATH}/{INTERFACE}): {ERROR}",
+            "PATH", objects.map.at(powerRestoreIntf).front(), "INTERFACE",
+            powerRestoreIntf, "ERROR", e);
+        cache::objectsPtr.reset();
+        return std::nullopt;
+    }
+    return std::make_optional(restorePolicy);
+}
+
+/*
+ * getPowerStatus
+ * helper function for Get Chassis Status Command
+ * return - optional value for pgood (no value on error)
+ */
+std::optional<bool> getPowerStatus()
+{
+    bool powerGood = false;
+    std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus();
+    try
+    {
+        constexpr const char* chassisStatePath =
+            "/xyz/openbmc_project/state/chassis0";
+        constexpr const char* chassisStateIntf =
+            "xyz.openbmc_project.State.Chassis";
+        auto service =
+            ipmi::getService(*busp, chassisStateIntf, chassisStatePath);
+
+        ipmi::Value powerState =
+            ipmi::getDbusProperty(*busp, service, chassisStatePath,
+                                  chassisStateIntf, "CurrentPowerState");
+        std::string powerStateStr = std::get<std::string>(powerState);
+        if (powerStateStr.ends_with(".On") ||
+            powerStateStr.ends_with(".TransitioningToOff"))
+        {
+            powerGood = true;
+        }
+    }
+    catch (const std::exception& e)
+    {
+        try
+        {
+            // FIXME: some legacy modules use the older path; try that next
+            constexpr const char* legacyPwrCtrlObj =
+                "/org/openbmc/control/power0";
+            constexpr const char* legacyPwrCtrlIntf =
+                "org.openbmc.control.Power";
+            auto service =
+                ipmi::getService(*busp, legacyPwrCtrlIntf, legacyPwrCtrlObj);
+
+            ipmi::Value variant = ipmi::getDbusProperty(
+                *busp, service, legacyPwrCtrlObj, legacyPwrCtrlIntf, "pgood");
+            powerGood = static_cast<bool>(std::get<int>(variant));
+        }
+        catch (const std::exception& e)
+        {
+            lg2::error("Failed to fetch pgood property: {ERROR}", "ERROR", e);
+            return std::nullopt;
+        }
+    }
+    return std::make_optional(powerGood);
+}
+
+/*
+ * getACFailStatus
+ * helper function for Get Chassis Status Command
+ * return - bool value for ACFail (false on error)
+ */
+bool getACFailStatus()
+{
+    constexpr const char* powerControlObj =
+        "/xyz/openbmc_project/Chassis/Control/Power0";
+    constexpr const char* powerControlIntf =
+        "xyz.openbmc_project.Chassis.Control.Power";
+    bool acFail = false;
+    std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus();
+    try
+    {
+        auto service =
+            ipmi::getService(*bus, powerControlIntf, powerControlObj);
+
+        ipmi::Value variant = ipmi::getDbusProperty(
+            *bus, service, powerControlObj, powerControlIntf, "PFail");
+        acFail = std::get<bool>(variant);
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error(
+            "Failed to fetch PFail property ({PATH}/{INTERFACE}): {ERROR}",
+            "PATH", powerControlObj, "INTERFACE", powerControlIntf, "ERROR", e);
+    }
+    return acFail;
+}
+} // namespace power_policy
+
+static std::optional<bool> getButtonDisabled(ipmi::Context::ptr& ctx,
+                                             const std::string& buttonPath,
+                                             const std::string& buttonIntf)
+{
+    bool buttonDisabled = false;
+    boost::system::error_code ec;
+    std::string service;
+    ec = ipmi::getService(ctx, buttonIntf, buttonPath, service);
+    if (!ec)
+    {
+        bool enabled;
+        ec = ipmi::getDbusProperty(ctx, service, buttonPath, buttonIntf,
+                                   "Enabled", enabled);
+        buttonDisabled = !enabled;
+    }
+
+    if (ec)
+    {
+        lg2::error("Fail to get button Enabled property ({PATH}): {ERROR}",
+                   "PATH", buttonPath, "ERROR", ec.message());
+        return std::nullopt;
+    }
+    return std::make_optional(buttonDisabled);
+}
+
+static bool setButtonDisabled(ipmi::Context::ptr& ctx,
+                              const std::string& buttonPath,
+                              const std::string& buttonIntf, bool disable)
+{
+    std::string service;
+    boost::system::error_code ec;
+    ec = ipmi::getService(ctx, buttonIntf, buttonPath, service);
+    if (!ec)
+    {
+        ec = ipmi::setDbusProperty(ctx, service, buttonPath, buttonIntf,
+                                   "Enabled", !disable);
+    }
+    if (ec)
+    {
+        lg2::error(
+            "Fail to set button Enabled property ({SERVICE}:{PATH}): {ERROR}",
+            "SERVICE", service, "PATH", buttonPath, "ERROR", ec.message());
+        return false;
+    }
+    return true;
+}
+
+static std::optional<bool> getChassisIntrusionStatus(ipmi::Context::ptr& ctx)
+{
+    std::vector<std::string> interfaces = {std::string(Intrusion::interface)};
+    ipmi::ObjectTree objs;
+    std::string propVal;
+    std::optional<bool> ret = std::nullopt;
+
+    boost::system::error_code ec =
+        ipmi::getSubTree(ctx, interfaces, std::string("/"), 0, objs);
+
+    if (ec)
+    {
+        lg2::error("Fail to find Chassis Intrusion Interface on D-Bus "
+                   "({INTERFACE}): {ERROR}",
+                   "INTERFACE", Intrusion::interface, "ERROR", ec.message());
+        return ret;
+    }
+
+    for (const auto& [path, map] : objs)
+    {
+        for (const auto& [service, intfs] : map)
+        {
+            ec = ipmi::getDbusProperty<std::string>(
+                ctx, service, path, Intrusion::interface, "Status", propVal);
+            if (ec)
+            {
+                lg2::error("Fail to get Chassis Intrusion Status property "
+                           "({SERVICE}/{PATH}/{INTERFACE}): {ERROR}",
+                           "SERVICE", service, "PATH", path, "INTERFACE",
+                           Intrusion::interface, "ERROR", ec.message());
+            }
+            else
+            {
+                // return false if all values are Normal
+                // return true if one value is not Normal
+                // return nullopt when no value can be retrieved from D-Bus
+                auto status =
+                    sdbusplus::message::convert_from_string<Intrusion::Status>(
+                        propVal)
+                        .value();
+                if (status == Intrusion::Status::Normal)
+                {
+                    ret = std::make_optional(false);
+                }
+                else
+                {
+                    ret = std::make_optional(true);
+                    return ret;
+                }
+            }
+        }
+    }
+    return ret;
+}
+
+//----------------------------------------------------------------------
+// Get Chassis Status commands
+//----------------------------------------------------------------------
+ipmi::RspType<bool,    // Power is on
+              bool,    // Power overload
+              bool,    // Interlock
+              bool,    // power fault
+              bool,    // power control fault
+              uint2_t, // power restore policy
+              bool,    // reserved
+
+              bool,    // AC failed
+              bool,    // last power down caused by a Power overload
+              bool,    // last power down caused by a power interlock
+              bool,    // last power down caused by power fault
+              bool,    // last ‘Power is on’ state was entered via IPMI command
+              uint3_t, // reserved
+
+              bool,    // Chassis intrusion active
+              bool,    // Front Panel Lockout active
+              bool,    // Drive Fault
+              bool,    // Cooling/fan fault detected
+              uint2_t, // Chassis Identify State
+              bool,    // Chassis Identify command and state info supported
+              bool,    // reserved
+
+              bool,    // Power off button disabled
+              bool,    // Reset button disabled
+              bool,    // Diagnostic Interrupt button disabled
+              bool,    // Standby (sleep) button disabled
+              bool,    // Power off button disable allowed
+              bool,    // Reset button disable allowed
+              bool,    // Diagnostic Interrupt button disable allowed
+              bool     // Standby (sleep) button disable allowed
+              >
+    ipmiGetChassisStatus(ipmi::Context::ptr& ctx)
+{
+    using namespace chassis::internal;
+    std::optional<uint2_t> restorePolicy =
+        power_policy::getPowerRestorePolicy();
+    std::optional<bool> powerGood = power_policy::getPowerStatus();
+    if (!restorePolicy || !powerGood)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    //  Front Panel Button Capabilities and disable/enable status(Optional)
+    std::optional<bool> powerButtonReading =
+        getButtonDisabled(ctx, powerButtonPath, powerButtonIntf);
+    // allow disable if the interface is present
+    bool powerButtonDisableAllow = static_cast<bool>(powerButtonReading);
+    // default return the button is enabled (not disabled)
+    bool powerButtonDisabled = false;
+    if (powerButtonDisableAllow)
+    {
+        // return the real value of the button status, if present
+        powerButtonDisabled = *powerButtonReading;
+    }
+
+    std::optional<bool> resetButtonReading =
+        getButtonDisabled(ctx, resetButtonPath, resetButtonIntf);
+    // allow disable if the interface is present
+    bool resetButtonDisableAllow = static_cast<bool>(resetButtonReading);
+    // default return the button is enabled (not disabled)
+    bool resetButtonDisabled = false;
+    if (resetButtonDisableAllow)
+    {
+        // return the real value of the button status, if present
+        resetButtonDisabled = *resetButtonReading;
+    }
+
+    bool powerDownAcFailed = power_policy::getACFailStatus();
+
+    bool chassisIntrusionActive = false;
+    std::optional<bool> chassisIntrusionStatus = getChassisIntrusionStatus(ctx);
+    if (chassisIntrusionStatus)
+    {
+        chassisIntrusionActive = chassisIntrusionStatus.value();
+    }
+
+    // This response has a lot of hard-coded, unsupported fields
+    // They are set to false or 0
+    constexpr bool powerOverload = false;
+    constexpr bool chassisInterlock = false;
+    constexpr bool powerFault = false;
+    constexpr bool powerControlFault = false;
+    constexpr bool powerDownOverload = false;
+    constexpr bool powerDownInterlock = false;
+    constexpr bool powerDownPowerFault = false;
+    constexpr bool powerStatusIPMI = false;
+    constexpr bool frontPanelLockoutActive = false;
+    constexpr bool driveFault = false;
+    constexpr bool coolingFanFault = false;
+    // chassisIdentifySupport set because this command is implemented
+    constexpr bool chassisIdentifySupport = true;
+    uint2_t chassisIdentifyState = types::enum_cast<uint2_t>(chassisIDState);
+    constexpr bool diagButtonDisabled = false;
+    constexpr bool sleepButtonDisabled = false;
+    constexpr bool diagButtonDisableAllow = false;
+    constexpr bool sleepButtonDisableAllow = false;
+
+    return ipmi::responseSuccess(
+        *powerGood, powerOverload, chassisInterlock, powerFault,
+        powerControlFault, *restorePolicy,
+        false, // reserved
+
+        powerDownAcFailed, powerDownOverload, powerDownInterlock,
+        powerDownPowerFault, powerStatusIPMI,
+        uint3_t(0), // reserved
+
+        chassisIntrusionActive, frontPanelLockoutActive, driveFault,
+        coolingFanFault, chassisIdentifyState, chassisIdentifySupport,
+        false, // reserved
+
+        powerButtonDisabled, resetButtonDisabled, diagButtonDisabled,
+        sleepButtonDisabled, powerButtonDisableAllow, resetButtonDisableAllow,
+        diagButtonDisableAllow, sleepButtonDisableAllow);
+}
+
+enum class IpmiRestartCause
+{
+    Unknown = 0x0,
+    RemoteCommand = 0x1,
+    ResetButton = 0x2,
+    PowerButton = 0x3,
+    WatchdogTimer = 0x4,
+    PowerPolicyAlwaysOn = 0x6,
+    PowerPolicyPreviousState = 0x7,
+    SoftReset = 0xa,
+};
+
+static IpmiRestartCause restartCauseToIpmiRestartCause(
+    State::Host::RestartCause cause)
+{
+    switch (cause)
+    {
+        case State::Host::RestartCause::Unknown:
+        {
+            return IpmiRestartCause::Unknown;
+        }
+        case State::Host::RestartCause::RemoteCommand:
+        {
+            return IpmiRestartCause::RemoteCommand;
+        }
+        case State::Host::RestartCause::ResetButton:
+        {
+            return IpmiRestartCause::ResetButton;
+        }
+        case State::Host::RestartCause::PowerButton:
+        {
+            return IpmiRestartCause::PowerButton;
+        }
+        case State::Host::RestartCause::WatchdogTimer:
+        {
+            return IpmiRestartCause::WatchdogTimer;
+        }
+        case State::Host::RestartCause::PowerPolicyAlwaysOn:
+        {
+            return IpmiRestartCause::PowerPolicyAlwaysOn;
+        }
+        case State::Host::RestartCause::PowerPolicyPreviousState:
+        {
+            return IpmiRestartCause::PowerPolicyPreviousState;
+        }
+        case State::Host::RestartCause::SoftReset:
+        {
+            return IpmiRestartCause::SoftReset;
+        }
+        default:
+        {
+            return IpmiRestartCause::Unknown;
+        }
+    }
+}
+
+/*
+ * getRestartCause
+ * helper function for Get Host restart cause Command
+ * return - optional value for RestartCause (no value on error)
+ */
+static std::optional<uint4_t> getRestartCause(ipmi::Context::ptr ctx)
+{
+    constexpr const char* restartCausePath = "/xyz/openbmc_project/state/host0";
+    constexpr const char* restartCauseIntf = "xyz.openbmc_project.State.Host";
+
+    std::string service;
+    boost::system::error_code ec =
+        ipmi::getService(ctx, restartCauseIntf, restartCausePath, service);
+    if (!ec)
+    {
+        std::string restartCauseStr;
+        ec = ipmi::getDbusProperty<std::string>(
+            ctx, service, restartCausePath, restartCauseIntf, "RestartCause",
+            restartCauseStr);
+        if (!ec)
+        {
+            auto cause =
+                State::Host::convertRestartCauseFromString(restartCauseStr);
+            return types::enum_cast<uint4_t>(
+                restartCauseToIpmiRestartCause(cause));
+        }
+    }
+
+    lg2::error(
+        "Failed to fetch RestartCause property ({PATH}/{INTERFACE}): {ERROR}",
+        "ERROR", ec.message(), "PATH", restartCausePath, "INTERFACE",
+        restartCauseIntf);
+    return std::nullopt;
+}
+
+ipmi::RspType<uint4_t, // Restart Cause
+              uint4_t, // reserved
+              uint8_t  // channel number (not supported)
+              >
+    ipmiGetSystemRestartCause(ipmi::Context::ptr ctx)
+{
+    std::optional<uint4_t> cause = getRestartCause(ctx);
+    if (!cause)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    constexpr uint4_t reserved = 0;
+    auto channel = static_cast<uint8_t>(ctx->channel);
+    return ipmi::responseSuccess(cause.value(), reserved, channel);
+}
+/** @brief Implementation of chassis control command
+ *
+ *  @param - chassisControl command byte
+ *
+ *  @return  Success or InvalidFieldRequest.
+ */
+ipmi::RspType<> ipmiChassisControl(ipmi::Context::ptr& ctx,
+                                   uint8_t chassisControl)
+{
+    int rc = 0;
+    switch (chassisControl)
+    {
+        case CMD_POWER_ON:
+            rc = initiateHostStateTransition(ctx, State::Host::Transition::On);
+            break;
+        case CMD_POWER_OFF:
+            rc = initiateChassisStateTransition(
+                ctx, State::Chassis::Transition::Off);
+            break;
+        case CMD_HARD_RESET:
+            rc = initiateHostStateTransition(
+                ctx, State::Host::Transition::ForceWarmReboot);
+            break;
+        case CMD_POWER_CYCLE:
+            rc = initiateHostStateTransition(ctx,
+                                             State::Host::Transition::Reboot);
+            break;
+        case CMD_SOFT_OFF_VIA_OVER_TEMP:
+            rc = initiateHostStateTransition(ctx, State::Host::Transition::Off);
+            break;
+        case CMD_PULSE_DIAGNOSTIC_INTR:
+            rc = doNmi(ctx);
+            break;
+
+        default:
+        {
+            lg2::error("Invalid Chassis Control command: {CMD}", "CMD",
+                       lg2::hex, chassisControl);
+            return ipmi::responseInvalidFieldRequest();
+        }
+    }
+
+    return ((rc < 0) ? ipmi::responseUnspecifiedError()
+                     : ipmi::responseSuccess());
+}
+
+/** @brief Return D-Bus connection string to enclosure identify LED object
+ *
+ *  @param[in, out] connection - connection to D-Bus object
+ *  @return a IPMI return code
+ */
+std::string getEnclosureIdentifyConnection()
+{
+    // lookup enclosure_identify group owner(s) in mapper
+    try
+    {
+        return ipmi::getService(*getSdBus(), "xyz.openbmc_project.Led.Group",
+                                identify_led_object_name);
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Chassis Identify: Error communicating to mapper: {ERROR}",
+                   "ERROR", e);
+        elog<InternalFailure>();
+    }
+}
+
+/** @brief Turn On/Off enclosure identify LED
+ *
+ *  @param[in] flag - true to turn on LED, false to turn off
+ *  @return a IPMI return code
+ */
+void enclosureIdentifyLed(bool flag)
+{
+    using namespace chassis::internal;
+    try
+    {
+        std::string connection = getEnclosureIdentifyConnection();
+
+        auto msg = std::string("enclosureIdentifyLed(") +
+                   boost::lexical_cast<std::string>(flag) + ")";
+        lg2::debug(msg.c_str());
+
+        ipmi::setDbusProperty(*getSdBus(), connection, identify_led_object_name,
+                              "xyz.openbmc_project.Led.Group", "Asserted",
+                              flag);
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Chassis Identify: Error Setting State {LED_STATE}: {ERROR}",
+                   "LED_STATE", flag, "ERROR", e);
+        elog<InternalFailure>();
+    }
+}
+
+/** @brief Callback method to turn off LED
+ */
+void enclosureIdentifyLedOff()
+{
+    try
+    {
+        chassisIDState = ChassisIDState::off;
+        enclosureIdentifyLed(false);
+    }
+    catch (const InternalFailure& e)
+    {
+        report<InternalFailure>();
+    }
+}
+
+/** @brief Create timer to turn on and off the enclosure LED
+ */
+void createIdentifyTimer()
+{
+    if (!identifyTimer)
+    {
+        identifyTimer =
+            std::make_unique<sdbusplus::Timer>(enclosureIdentifyLedOff);
+    }
+}
+
+ipmi::RspType<> ipmiChassisIdentify(std::optional<uint8_t> interval,
+                                    std::optional<uint8_t> force)
+{
+    uint8_t identifyInterval = interval.value_or(DEFAULT_IDENTIFY_TIME_OUT);
+    bool forceIdentify = force.value_or(0) & 0x01;
+
+    if (identifyInterval || forceIdentify)
+    {
+        // stop the timer if already started;
+        // for force identify we should not turn off LED
+        identifyTimer->stop();
+        try
+        {
+            chassisIDState = ChassisIDState::temporaryOn;
+            enclosureIdentifyLed(true);
+        }
+        catch (const InternalFailure& e)
+        {
+            report<InternalFailure>();
+            return ipmi::responseResponseError();
+        }
+
+        if (forceIdentify)
+        {
+            chassisIDState = ChassisIDState::indefiniteOn;
+            return ipmi::responseSuccess();
+        }
+        // start the timer
+        auto time = std::chrono::duration_cast<std::chrono::microseconds>(
+            std::chrono::seconds(identifyInterval));
+        identifyTimer->start(time);
+    }
+    else if (!identifyInterval)
+    {
+        identifyTimer->stop();
+        enclosureIdentifyLedOff();
+    }
+    return ipmi::responseSuccess();
+}
+
+namespace boot_options
+{
+
+using namespace sdbusplus::server::xyz::openbmc_project::control::boot;
+using IpmiValue = uint8_t;
+constexpr auto ipmiDefault = 0;
+
+std::map<IpmiValue, Type::Types> typeIpmiToDbus = {{0x00, Type::Types::Legacy},
+                                                   {0x01, Type::Types::EFI}};
+
+std::map<IpmiValue, Source::Sources> sourceIpmiToDbus = {
+    {0x01, Source::Sources::Network},
+    {0x02, Source::Sources::Disk},
+    {0x05, Source::Sources::ExternalMedia},
+    {0x0f, Source::Sources::RemovableMedia},
+    {ipmiDefault, Source::Sources::Default}};
+
+std::map<IpmiValue, Mode::Modes> modeIpmiToDbus = {
+#ifdef ENABLE_BOOT_FLAG_SAFE_MODE_SUPPORT
+    {0x03, Mode::Modes::Safe},
+#endif // ENABLE_BOOT_SAFE_MODE_SUPPORT
+    {0x06, Mode::Modes::Setup},
+    {ipmiDefault, Mode::Modes::Regular}};
+
+std::map<Type::Types, IpmiValue> typeDbusToIpmi = {{Type::Types::Legacy, 0x00},
+                                                   {Type::Types::EFI, 0x01}};
+
+std::map<Source::Sources, IpmiValue> sourceDbusToIpmi = {
+    {Source::Sources::Network, 0x01},
+    {Source::Sources::Disk, 0x02},
+    {Source::Sources::ExternalMedia, 0x05},
+    {Source::Sources::RemovableMedia, 0x0f},
+    {Source::Sources::Default, ipmiDefault}};
+
+std::map<Mode::Modes, IpmiValue> modeDbusToIpmi = {
+#ifdef ENABLE_BOOT_FLAG_SAFE_MODE_SUPPORT
+    {Mode::Modes::Safe, 0x03},
+#endif // ENABLE_BOOT_SAFE_MODE_SUPPORT
+    {Mode::Modes::Setup, 0x06},
+    {Mode::Modes::Regular, ipmiDefault}};
+
+} // namespace boot_options
+
+/** @brief Get the property value for boot source
+ *  @param[in] ctx - context pointer
+ *  @param[out] source - boot source value
+ *  @return On failure return IPMI error.
+ */
+static ipmi::Cc getBootSource(ipmi::Context::ptr& ctx, Source::Sources& source)
+{
+    using namespace chassis::internal;
+    std::string result;
+    std::string service;
+    boost::system::error_code ec =
+        getService(ctx, bootSourceIntf, bootSettingsPath, service);
+    if (!ec)
+    {
+        ec = ipmi::getDbusProperty(ctx, service, bootSettingsPath,
+                                   bootSourceIntf, "BootSource", result);
+        if (!ec)
+        {
+            source = Source::convertSourcesFromString(result);
+            return ipmi::ccSuccess;
+        }
+    }
+    lg2::error("Error in BootSource Get: {ERROR}", "ERROR", ec.message());
+    return ipmi::ccUnspecifiedError;
+}
+
+/** @brief Set the property value for boot source
+ *  @param[in] ctx - context pointer
+ *  @param[in] source - boot source value
+ *  @return On failure return IPMI error.
+ */
+static ipmi::Cc setBootSource(ipmi::Context::ptr& ctx,
+                              const Source::Sources& source)
+{
+    using namespace chassis::internal;
+    std::string service;
+    boost::system::error_code ec =
+        getService(ctx, bootSourceIntf, bootSettingsPath, service);
+    if (!ec)
+    {
+        ec = ipmi::setDbusProperty(ctx, service, bootSettingsPath,
+                                   bootSourceIntf, "BootSource",
+                                   convertForMessage(source));
+        if (!ec)
+        {
+            return ipmi::ccSuccess;
+        }
+    }
+    lg2::error("Error in BootSource Set: {ERROR}", "ERROR", ec.message());
+    return ipmi::ccUnspecifiedError;
+}
+
+/** @brief Get the property value for boot mode
+ *  @param[in] ctx - context pointer
+ *  @param[out] mode - boot mode value
+ *  @return On failure return IPMI error.
+ */
+static ipmi::Cc getBootMode(ipmi::Context::ptr& ctx, Mode::Modes& mode)
+{
+    using namespace chassis::internal;
+    std::string result;
+    std::string service;
+    boost::system::error_code ec =
+        getService(ctx, bootModeIntf, bootSettingsPath, service);
+    if (!ec)
+    {
+        ec = ipmi::getDbusProperty(ctx, service, bootSettingsPath, bootModeIntf,
+                                   "BootMode", result);
+        if (!ec)
+        {
+            mode = Mode::convertModesFromString(result);
+            return ipmi::ccSuccess;
+        }
+    }
+    lg2::error("Error in BootMode Get: {ERROR}", "ERROR", ec.message());
+    return ipmi::ccUnspecifiedError;
+}
+
+/** @brief Set the property value for boot mode
+ *  @param[in] ctx - context pointer
+ *  @param[in] mode - boot mode value
+ *  @return On failure return IPMI error.
+ */
+static ipmi::Cc setBootMode(ipmi::Context::ptr& ctx, const Mode::Modes& mode)
+{
+    using namespace chassis::internal;
+    std::string service;
+    boost::system::error_code ec =
+        getService(ctx, bootModeIntf, bootSettingsPath, service);
+    if (!ec)
+    {
+        ec = ipmi::setDbusProperty(ctx, service, bootSettingsPath, bootModeIntf,
+                                   "BootMode", convertForMessage(mode));
+        if (!ec)
+        {
+            return ipmi::ccSuccess;
+        }
+    }
+    lg2::error("Error in BootMode Set: {ERROR}", "ERROR", ec.message());
+    return ipmi::ccUnspecifiedError;
+}
+
+/** @brief Get the property value for boot type
+ *  @param[in] ctx - context pointer
+ *  @param[out] type - boot type value
+ *  @return On failure return IPMI error.
+ */
+static ipmi::Cc getBootType(ipmi::Context::ptr& ctx, Type::Types& type)
+{
+    using namespace chassis::internal;
+    std::string result;
+    std::string service;
+    boost::system::error_code ec =
+        getService(ctx, bootTypeIntf, bootSettingsPath, service);
+
+    // Don't throw error if BootType interface is not present.
+    // This interface is not relevant for some Host architectures
+    // (for example POWER). In this case we don't won't IPMI to
+    // return an error, but simply return bootType as EFI.
+    type = Type::Types::EFI;
+    if (!ec)
+    {
+        ec = ipmi::getDbusProperty(ctx, service, bootSettingsPath, bootTypeIntf,
+                                   "BootType", result);
+        if (ec)
+        {
+            lg2::error("Error in BootType Get: {ERROR}", "ERROR", ec.message());
+            return ipmi::ccUnspecifiedError;
+        }
+        type = Type::convertTypesFromString(result);
+    }
+
+    return ipmi::ccSuccess;
+}
+
+/** @brief Set the property value for boot type
+ *  @param[in] ctx - context pointer
+ *  @param[in] type - boot type value
+ *  @return On failure return IPMI error.
+ */
+static ipmi::Cc setBootType(ipmi::Context::ptr& ctx, const Type::Types& type)
+{
+    using namespace chassis::internal;
+    std::string service;
+    boost::system::error_code ec =
+        getService(ctx, bootTypeIntf, bootSettingsPath, service);
+    if (!ec)
+    {
+        ec = ipmi::setDbusProperty(ctx, service, bootSettingsPath, bootTypeIntf,
+                                   "BootType", convertForMessage(type));
+        if (ec)
+        {
+            lg2::error("Error in BootType Set: {ERROR}", "ERROR", ec.message());
+            return ipmi::ccUnspecifiedError;
+        }
+    }
+    // Don't throw error if BootType interface is not present.
+    // This interface is not relevant for some Host architectures
+    // (for example POWER). In this case we don't won't IPMI to
+    // return an error, but want to just skip this function.
+    return ipmi::ccSuccess;
+}
+
+/** @brief Get the property value for boot override enable
+ *  @param[in] ctx - context pointer
+ *  @param[out] enable - boot override enable
+ *  @return On failure return IPMI error.
+ */
+static ipmi::Cc getBootEnable(ipmi::Context::ptr& ctx, bool& enable)
+{
+    using namespace chassis::internal;
+    std::string result;
+    std::string service;
+    boost::system::error_code ec =
+        getService(ctx, bootEnableIntf, bootSettingsPath, service);
+    if (!ec)
+    {
+        ec = ipmi::getDbusProperty(ctx, service, bootSettingsPath,
+                                   bootEnableIntf, "Enabled", enable);
+        if (!ec)
+        {
+            return ipmi::ccSuccess;
+        }
+    }
+    lg2::error("Error in Boot Override Enable Get: {ERROR}", "ERROR",
+               ec.message());
+    return ipmi::ccUnspecifiedError;
+}
+
+/** @brief Set the property value for boot override enable
+ *  @param[in] ctx - context pointer
+ *  @param[in] enable - boot override enable
+ *  @return On failure return IPMI error.
+ */
+static ipmi::Cc setBootEnable(ipmi::Context::ptr& ctx, const bool& enable)
+{
+    using namespace chassis::internal;
+    std::string service;
+    boost::system::error_code ec =
+        getService(ctx, bootEnableIntf, bootSettingsPath, service);
+    if (!ec)
+    {
+        ec = ipmi::setDbusProperty(ctx, service, bootSettingsPath,
+                                   bootEnableIntf, "Enabled", enable);
+        if (!ec)
+        {
+            return ipmi::ccSuccess;
+        }
+    }
+    lg2::error("Error in Boot Source Override Enable Set: {ERROR}", "ERROR",
+               ec.message());
+    return ipmi::ccUnspecifiedError;
+}
+
+/** @brief Get the property value for boot override one-time
+ *  @param[in] ctx - context pointer
+ *  @param[out] onetime - boot override one-time
+ *  @return On failure return IPMI error.
+ */
+static ipmi::Cc getBootOneTime(ipmi::Context::ptr& ctx, bool& onetime)
+{
+    using namespace chassis::internal;
+    std::string result;
+    std::string service;
+    boost::system::error_code ec =
+        getService(ctx, bootOneTimeIntf, bootSettingsOneTimePath, service);
+    if (!ec)
+    {
+        ec = ipmi::getDbusProperty(ctx, service, bootSettingsOneTimePath,
+                                   bootOneTimeIntf, "Enabled", onetime);
+        if (!ec)
+        {
+            return ipmi::ccSuccess;
+        }
+    }
+    lg2::error("Error in Boot Override OneTime Get: {ERROR}", "ERROR",
+               ec.message());
+    return ipmi::ccUnspecifiedError;
+}
+
+/** @brief Set the property value for boot override one-time
+ *  @param[in] ctx - context pointer
+ *  @param[in] onetime - boot override one-time
+ *  @return On failure return IPMI error.
+ */
+static ipmi::Cc setBootOneTime(ipmi::Context::ptr& ctx, const bool& onetime)
+{
+    using namespace chassis::internal;
+    std::string service;
+    boost::system::error_code ec =
+        getService(ctx, bootOneTimeIntf, bootSettingsOneTimePath, service);
+    if (!ec)
+    {
+        ec = ipmi::setDbusProperty(ctx, service, bootSettingsOneTimePath,
+                                   bootOneTimeIntf, "Enabled", onetime);
+        if (!ec)
+        {
+            return ipmi::ccSuccess;
+        }
+    }
+    lg2::error("Error in Boot Source Override OneTime Set: {ERROR}", "ERROR",
+               ec.message());
+    return ipmi::ccUnspecifiedError;
+}
+
+static constexpr uint8_t setComplete = 0x0;
+static constexpr uint8_t setInProgress = 0x1;
+static uint8_t transferStatus = setComplete;
+static uint8_t bootFlagValidBitClr = 0;
+static uint5_t bootInitiatorAckData = 0x0;
+static bool cmosClear = false;
+static uint2_t biosVerbosity = 0x0;
+
+/** @brief implements the Get Chassis system boot option
+ *  @param ctx - context pointer
+ *  @param bootOptionParameter   - boot option parameter selector
+ *  @param reserved1    - reserved bit
+ *  @param setSelector  - selects a particular block or set of parameters
+ *                        under the given parameter selector
+ *                        write as 00h if parameter doesn't use a setSelector
+ *  @param blockSelector- selects a particular block within a set of
+ *                        parameters write as 00h if parameter doesn't use a
+ *                        blockSelector
+ *
+ *  @return IPMI completion code plus response data
+ *  @return Payload contains below parameters:
+ *   version             - parameter version
+ *   bootOptionParameter - boot option parameter selector
+ *   parmIndicator - parameter valid/invalid indicator
+ *   data          - configuration parameter data
+ */
+ipmi::RspType<ipmi::message::Payload> ipmiChassisGetSysBootOptions(
+    ipmi::Context::ptr ctx, uint7_t bootOptionParameter, bool reserved1,
+    [[maybe_unused]] uint8_t setSelector,
+    [[maybe_unused]] uint8_t blockSelector)
+{
+    ipmi::Cc rc;
+    if (reserved1)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    constexpr uint4_t version = 0x01;
+    ipmi::message::Payload response;
+    response.pack(version, uint4_t{});
+    using namespace boot_options;
+
+    IpmiValue bootOption = ipmiDefault;
+
+    if (types::enum_cast<BootOptionParameter>(bootOptionParameter) ==
+        BootOptionParameter::setInProgress)
+    {
+        response.pack(bootOptionParameter, reserved1, transferStatus);
+        return ipmi::responseSuccess(std::move(response));
+    }
+
+    if (types::enum_cast<BootOptionParameter>(bootOptionParameter) ==
+        BootOptionParameter::bootInfo)
+    {
+        constexpr uint8_t writeMask = 0;
+        response.pack(bootOptionParameter, reserved1, writeMask,
+                      bootInitiatorAckData);
+        return ipmi::responseSuccess(std::move(response));
+    }
+
+    if (types::enum_cast<BootOptionParameter>(bootOptionParameter) ==
+        BootOptionParameter::bootFlagValidClr)
+    {
+        response.pack(bootOptionParameter, reserved1,
+                      uint5_t{bootFlagValidBitClr}, uint3_t{});
+        return ipmi::responseSuccess(std::move(response));
+    }
+
+    /*
+     * Parameter #5 means boot flags. Please refer to 28.13 of ipmi doc.
+     * This is the only parameter used by petitboot.
+     */
+    if (types::enum_cast<BootOptionParameter>(bootOptionParameter) ==
+        BootOptionParameter::bootFlags)
+    {
+        using namespace chassis::internal;
+        using namespace chassis::internal::cache;
+
+        try
+        {
+            Source::Sources bootSource;
+            rc = getBootSource(ctx, bootSource);
+            if (rc != ipmi::ccSuccess)
+            {
+                return ipmi::response(rc);
+            }
+
+            Type::Types bootType;
+            rc = getBootType(ctx, bootType);
+            if (rc != ipmi::ccSuccess)
+            {
+                return ipmi::response(rc);
+            }
+
+            Mode::Modes bootMode;
+            rc = getBootMode(ctx, bootMode);
+            if (rc != ipmi::ccSuccess)
+            {
+                return ipmi::response(rc);
+            }
+
+            bootOption = sourceDbusToIpmi.at(bootSource);
+            if ((Mode::Modes::Regular == bootMode) &&
+                (Source::Sources::Default == bootSource))
+            {
+                bootOption = ipmiDefault;
+            }
+            else if (Source::Sources::Default == bootSource)
+            {
+                bootOption = modeDbusToIpmi.at(bootMode);
+            }
+
+            IpmiValue biosBootType = typeDbusToIpmi.at(bootType);
+
+            bool oneTimeEnabled;
+            rc = getBootOneTime(ctx, oneTimeEnabled);
+            if (rc != ipmi::ccSuccess)
+            {
+                return ipmi::response(rc);
+            }
+
+            uint1_t permanent = oneTimeEnabled ? 0 : 1;
+
+            bool valid;
+            rc = getBootEnable(ctx, valid);
+            if (rc != ipmi::ccSuccess)
+            {
+                return ipmi::response(rc);
+            }
+
+            uint1_t validFlag = valid ? 1 : 0;
+
+            response.pack(
+                bootOptionParameter, reserved1, uint5_t{},
+                uint1_t{biosBootType}, uint1_t{permanent}, uint1_t{validFlag},
+                uint2_t{}, uint4_t{bootOption}, uint1_t{}, cmosClear, uint5_t{},
+                uint2_t{biosVerbosity}, uint1_t{}, uint8_t{}, uint8_t{});
+            return ipmi::responseSuccess(std::move(response));
+        }
+        catch (const InternalFailure& e)
+        {
+            cache::objectsPtr.reset();
+            report<InternalFailure>();
+            return ipmi::responseUnspecifiedError();
+        }
+    }
+    else
+    {
+        if ((bootOptionParameter >= oemParmStart) &&
+            (bootOptionParameter <= oemParmEnd))
+        {
+            if (types::enum_cast<BootOptionParameter>(bootOptionParameter) ==
+                BootOptionParameter::opalNetworkSettings)
+            {
+                response.pack(bootOptionParameter, reserved1);
+                int ret = getHostNetworkData(response);
+                if (ret < 0)
+                {
+                    response.trailingOk = true;
+                    lg2::error(
+                        "getHostNetworkData failed for GetSysBootOptions.");
+                    return ipmi::responseUnspecifiedError();
+                }
+                else
+                {
+                    return ipmi::responseSuccess(std::move(response));
+                }
+            }
+            else
+            {
+                lg2::error(
+                    "ipmiChassisGetSysBootOptions: Unsupported parameter {PARAM}",
+                    "PARAM", lg2::hex,
+                    static_cast<uint8_t>(bootOptionParameter));
+                return ipmi::responseParmNotSupported();
+            }
+        }
+        else
+        {
+            lg2::error(
+                "ipmiChassisGetSysBootOptions: Unsupported parameter {PARAM}",
+                "PARAM", lg2::hex, static_cast<uint8_t>(bootOptionParameter));
+            return ipmi::responseParmNotSupported();
+        }
+    }
+    return ipmi::responseUnspecifiedError();
+}
+
+ipmi::RspType<> ipmiChassisSetSysBootOptions(ipmi::Context::ptr ctx,
+                                             uint7_t parameterSelector, bool,
+                                             ipmi::message::Payload& data)
+{
+    using namespace boot_options;
+    ipmi::Cc rc;
+
+    if (types::enum_cast<BootOptionParameter>(parameterSelector) ==
+        BootOptionParameter::setInProgress)
+    {
+        uint2_t setInProgressFlag;
+        uint6_t rsvd;
+        if (data.unpack(setInProgressFlag, rsvd) != 0 || !data.fullyUnpacked())
+        {
+            return ipmi::responseReqDataLenInvalid();
+        }
+        if (rsvd)
+        {
+            return ipmi::responseInvalidFieldRequest();
+        }
+        if ((transferStatus == setInProgress) &&
+            (static_cast<uint8_t>(setInProgressFlag) != setComplete))
+        {
+            return ipmi::response(IPMI_CC_FAIL_SET_IN_PROGRESS);
+        }
+        transferStatus = static_cast<uint8_t>(setInProgressFlag);
+        return ipmi::responseSuccess();
+    }
+
+    /*  000101
+     * Parameter #5 means boot flags. Please refer to 28.13 of ipmi doc.
+     * This is the only parameter used by petitboot.
+     */
+
+    if (types::enum_cast<BootOptionParameter>(parameterSelector) ==
+        BootOptionParameter::bootFlags)
+    {
+        uint5_t rsvd;
+        bool validFlag;
+        bool permanent;
+        bool biosBootType;
+        bool lockOutResetButton;
+        bool screenBlank;
+        uint4_t bootDeviceSelector;
+        bool lockKeyboard;
+        uint5_t biosCtrls;
+        bool lockOutPower;
+        uint4_t biosInfo;
+        uint4_t rsvd1;
+        uint5_t deviceInstance;
+        uint3_t rsvd2;
+
+        if (data.unpack(rsvd, biosBootType, permanent, validFlag,
+                        lockOutResetButton, screenBlank, bootDeviceSelector,
+                        lockKeyboard, cmosClear, biosCtrls, biosVerbosity,
+                        lockOutPower, biosInfo, rsvd1, deviceInstance, rsvd2) !=
+                0 ||
+            !data.fullyUnpacked())
+        {
+            return ipmi::responseReqDataLenInvalid();
+        }
+        if (rsvd || rsvd1 || rsvd2)
+        {
+            return ipmi::responseInvalidFieldRequest();
+        }
+
+        using namespace chassis::internal;
+        using namespace chassis::internal::cache;
+
+        try
+        {
+            rc = setBootOneTime(ctx, !permanent);
+            if (rc != ipmi::ccSuccess)
+            {
+                return ipmi::response(rc);
+            }
+
+            rc = setBootEnable(ctx, validFlag);
+            if (rc != ipmi::ccSuccess)
+            {
+                return ipmi::response(rc);
+            }
+
+            auto modeItr =
+                modeIpmiToDbus.find(static_cast<uint8_t>(bootDeviceSelector));
+            auto typeItr =
+                typeIpmiToDbus.find(static_cast<uint8_t>(biosBootType));
+            auto sourceItr =
+                sourceIpmiToDbus.find(static_cast<uint8_t>(bootDeviceSelector));
+            if (sourceIpmiToDbus.end() != sourceItr)
+            {
+                rc = setBootSource(ctx, sourceItr->second);
+                if (rc != ipmi::ccSuccess)
+                {
+                    return ipmi::response(rc);
+                }
+                // If a set boot device is mapping to a boot source, then reset
+                // the boot mode D-Bus property to default.
+                // This way the ipmid code can determine which property is not
+                // at the default value
+                if (sourceItr->second != Source::Sources::Default)
+                {
+                    rc = setBootMode(ctx, Mode::Modes::Regular);
+                    if (rc != ipmi::ccSuccess)
+                    {
+                        return ipmi::response(rc);
+                    }
+                }
+            }
+
+            if (typeIpmiToDbus.end() != typeItr)
+            {
+                rc = setBootType(ctx, typeItr->second);
+                if (rc != ipmi::ccSuccess)
+                {
+                    return ipmi::response(rc);
+                }
+            }
+
+            if (modeIpmiToDbus.end() != modeItr)
+            {
+                rc = setBootMode(ctx, modeItr->second);
+                if (rc != ipmi::ccSuccess)
+                {
+                    return ipmi::response(rc);
+                }
+                // If a set boot device is mapping to a boot mode, then reset
+                // the boot source D-Bus property to default.
+                // This way the ipmid code can determine which property is not
+                // at the default value
+                if (modeItr->second != Mode::Modes::Regular)
+                {
+                    rc = setBootSource(ctx, Source::Sources::Default);
+                    if (rc != ipmi::ccSuccess)
+                    {
+                        return ipmi::response(rc);
+                    }
+                }
+            }
+            if ((modeIpmiToDbus.end() == modeItr) &&
+                (typeIpmiToDbus.end() == typeItr) &&
+                (sourceIpmiToDbus.end() == sourceItr))
+            {
+                // return error if boot option is not supported
+                lg2::error(
+                    "ipmiChassisSetSysBootOptions: Boot option not supported");
+                return ipmi::responseInvalidFieldRequest();
+            }
+        }
+        catch (const sdbusplus::exception_t& e)
+        {
+            objectsPtr.reset();
+            report<InternalFailure>();
+            lg2::error("ipmiChassisSetSysBootOptions: Error in setting Boot "
+                       "flag parameters");
+            return ipmi::responseUnspecifiedError();
+        }
+    }
+    else if (types::enum_cast<BootOptionParameter>(parameterSelector) ==
+             BootOptionParameter::bootInfo)
+    {
+        uint8_t writeMak;
+        uint5_t bootInfoAck;
+        uint3_t rsvd;
+
+        if (data.unpack(writeMak, bootInfoAck, rsvd) != 0 ||
+            !data.fullyUnpacked())
+        {
+            return ipmi::responseReqDataLenInvalid();
+        }
+        if (rsvd)
+        {
+            return ipmi::responseInvalidFieldRequest();
+        }
+        bootInitiatorAckData &= ~writeMak;
+        bootInitiatorAckData |= (writeMak & bootInfoAck);
+        lg2::info("ipmiChassisSetSysBootOptions: bootInfo parameter set "
+                  "successfully");
+        data.trailingOk = true;
+        return ipmi::responseSuccess();
+    }
+    else if (types::enum_cast<BootOptionParameter>(parameterSelector) ==
+             BootOptionParameter::bootFlagValidClr)
+    {
+        uint5_t bootFlagValidClr;
+        uint3_t rsvd;
+
+        if (data.unpack(bootFlagValidClr, rsvd) != 0 || !data.fullyUnpacked())
+        {
+            return ipmi::responseReqDataLenInvalid();
+        }
+        if (rsvd)
+        {
+            return ipmi::responseInvalidFieldRequest();
+        }
+        // store boot flag valid bits clear value
+        bootFlagValidBitClr = static_cast<uint8_t>(bootFlagValidClr);
+        lg2::info(
+            "ipmiChassisSetSysBootOptions: bootFlagValidBits parameter set "
+            "successfully to {VALUE}",
+            "VALUE", lg2::hex, bootFlagValidBitClr);
+        return ipmi::responseSuccess();
+    }
+    else
+    {
+        if ((parameterSelector >= static_cast<uint7_t>(oemParmStart)) &&
+            (parameterSelector <= static_cast<uint7_t>(oemParmEnd)))
+        {
+            if (types::enum_cast<BootOptionParameter>(parameterSelector) ==
+                BootOptionParameter::opalNetworkSettings)
+            {
+                ipmi::Cc ret = setHostNetworkData(data);
+                if (ret != ipmi::ccSuccess)
+                {
+                    lg2::error("ipmiChassisSetSysBootOptions: Error in "
+                               "setHostNetworkData");
+                    data.trailingOk = true;
+                    return ipmi::response(ret);
+                }
+                data.trailingOk = true;
+                return ipmi::responseSuccess();
+            }
+            else
+            {
+                lg2::error(
+                    "ipmiChassisSetSysBootOptions: Unsupported param: {PARAM}",
+                    "PARAM", lg2::hex, static_cast<uint8_t>(parameterSelector));
+                data.trailingOk = true;
+                return ipmi::responseParmNotSupported();
+            }
+        }
+        data.trailingOk = true;
+        return ipmi::responseParmNotSupported();
+    }
+    return ipmi::responseSuccess();
+}
+
+/** @brief implements Get POH counter command
+ *  @parameter
+ *   -  none
+ *  @returns IPMI completion code plus response data
+ *   - minPerCount - Minutes per count
+ *   - counterReading - counter reading
+ */
+ipmi::RspType<uint8_t, // Minutes per count
+              uint32_t // Counter reading
+              >
+    ipmiGetPOHCounter()
+{
+    // sd_bus error
+    try
+    {
+        return ipmi::responseSuccess(static_cast<uint8_t>(poh::minutesPerCount),
+                                     getPOHCounter());
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("getPOHCounter error: {ERROR}", "ERROR", e);
+        return ipmi::responseUnspecifiedError();
+    }
+}
+
+ipmi::RspType<uint3_t, // policy support
+              uint5_t  // reserved
+              >
+    ipmiChassisSetPowerRestorePolicy(ipmi::Context::ptr ctx, uint3_t policy,
+                                     uint5_t reserved)
+{
+    power_policy::DbusValue value =
+        power_policy::RestorePolicy::Policy::AlwaysOff;
+
+    if (reserved || (policy > power_policy::noChange))
+    {
+        lg2::error("Reserved request parameter: {REQ}", "REQ", lg2::hex,
+                   static_cast<int>(policy));
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    if (policy == power_policy::noChange)
+    {
+        // just return the supported policy
+        return ipmi::responseSuccess(power_policy::allSupport, reserved);
+    }
+
+    for (const auto& it : power_policy::dbusToIpmi)
+    {
+        if (it.second == policy)
+        {
+            value = it.first;
+            break;
+        }
+    }
+
+    try
+    {
+        settings::Objects& objects = chassis::internal::cache::getObjects();
+        const settings::Path& powerRestoreSetting =
+            objects.map.at(chassis::internal::powerRestoreIntf).front();
+
+        boost::system::error_code ec = ipmi::setDbusProperty(
+            ctx,
+            objects.service(powerRestoreSetting,
+                            chassis::internal::powerRestoreIntf),
+            powerRestoreSetting, chassis::internal::powerRestoreIntf,
+            "PowerRestorePolicy", convertForMessage(value));
+        if (ec)
+        {
+            lg2::error("Unspecified Error");
+            return ipmi::responseUnspecifiedError();
+        }
+    }
+    catch (const InternalFailure& e)
+    {
+        chassis::internal::cache::objectsPtr.reset();
+        report<InternalFailure>();
+        return ipmi::responseUnspecifiedError();
+    }
+
+    return ipmi::responseSuccess(power_policy::allSupport, reserved);
+}
+
+ipmi::RspType<> ipmiSetFrontPanelButtonEnables(
+    ipmi::Context::ptr ctx, bool disablePowerButton, bool disableResetButton,
+    bool, bool, uint4_t)
+{
+    using namespace chassis::internal;
+
+    // set power button Enabled property
+    bool success = setButtonDisabled(ctx, powerButtonPath, powerButtonIntf,
+                                     disablePowerButton);
+
+    // set reset button Enabled property
+    success &= setButtonDisabled(ctx, resetButtonPath, resetButtonIntf,
+                                 disableResetButton);
+
+    if (!success)
+    {
+        // not all buttons were successfully set
+        return ipmi::responseUnspecifiedError();
+    }
+    return ipmi::responseSuccess();
+}
+
+void registerNetFnChassisFunctions()
+{
+    createIdentifyTimer();
+
+    // Get Chassis Capabilities
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+                          ipmi::chassis::cmdGetChassisCapabilities,
+                          ipmi::Privilege::User, ipmiGetChassisCap);
+
+    // Set Front Panel Button Enables
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+                          ipmi::chassis::cmdSetFrontPanelButtonEnables,
+                          ipmi::Privilege::Admin,
+                          ipmiSetFrontPanelButtonEnables);
+
+    // Set Chassis Capabilities
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+                          ipmi::chassis::cmdSetChassisCapabilities,
+                          ipmi::Privilege::User, ipmiSetChassisCap);
+
+    // <Get System Boot Options>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+                          ipmi::chassis::cmdGetSystemBootOptions,
+                          ipmi::Privilege::Operator,
+                          ipmiChassisGetSysBootOptions);
+
+    // <Get Chassis Status>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+                          ipmi::chassis::cmdGetChassisStatus,
+                          ipmi::Privilege::User, ipmiGetChassisStatus);
+
+    // <Chassis Get System Restart Cause>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+                          ipmi::chassis::cmdGetSystemRestartCause,
+                          ipmi::Privilege::User, ipmiGetSystemRestartCause);
+
+    // <Chassis Control>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+                          ipmi::chassis::cmdChassisControl,
+                          ipmi::Privilege::Operator, ipmiChassisControl);
+
+    // <Chassis Identify>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+                          ipmi::chassis::cmdChassisIdentify,
+                          ipmi::Privilege::Operator, ipmiChassisIdentify);
+
+    // <Set System Boot Options>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+                          ipmi::chassis::cmdSetSystemBootOptions,
+                          ipmi::Privilege::Operator,
+                          ipmiChassisSetSysBootOptions);
+
+    // <Get POH Counter>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+                          ipmi::chassis::cmdGetPohCounter,
+                          ipmi::Privilege::User, ipmiGetPOHCounter);
+
+    // <Set Power Restore Policy>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+                          ipmi::chassis::cmdSetPowerRestorePolicy,
+                          ipmi::Privilege::Operator,
+                          ipmiChassisSetPowerRestorePolicy);
+}
diff --git a/chassishandler.hpp b/chassishandler.hpp
new file mode 100644
index 0000000..8d8af3b
--- /dev/null
+++ b/chassishandler.hpp
@@ -0,0 +1,54 @@
+#pragma once
+
+#include <stdint.h>
+
+#include <cstddef>
+
+// Command specific completion codes
+enum ipmi_chassis_return_codes
+{
+    IPMI_OK = 0x0,
+    IPMI_CC_PARM_NOT_SUPPORTED = 0x80,
+    IPMI_CC_FAIL_SET_IN_PROGRESS = 0x81,
+};
+
+// Generic completion codes,
+// see IPMI doc section 5.2
+enum ipmi_generic_return_codes
+{
+    IPMI_OUT_OF_SPACE = 0xC4,
+};
+
+// Various Chassis operations under a single command.
+enum ipmi_chassis_control_cmds : uint8_t
+{
+    CMD_POWER_OFF = 0x00,
+    CMD_POWER_ON = 0x01,
+    CMD_POWER_CYCLE = 0x02,
+    CMD_HARD_RESET = 0x03,
+    CMD_PULSE_DIAGNOSTIC_INTR = 0x04,
+    CMD_SOFT_OFF_VIA_OVER_TEMP = 0x05,
+};
+enum class BootOptionParameter : size_t
+{
+    setInProgress = 0x0,
+    bootFlagValidClr = 0x3,
+    bootInfo = 0x4,
+    bootFlags = 0x5,
+    opalNetworkSettings = 0x61
+};
+
+enum class BootOptionResponseSize : size_t
+{
+    setInProgress = 3,
+    bootFlags = 5,
+    opalNetworkSettings = 50
+};
+
+enum class ChassisIDState : uint8_t
+{
+    off = 0x0,
+    temporaryOn = 0x1,
+    indefiniteOn = 0x2,
+    reserved = 0x3
+};
diff --git a/dbus-sdr/meson.build b/dbus-sdr/meson.build
new file mode 100644
index 0000000..a937485
--- /dev/null
+++ b/dbus-sdr/meson.build
@@ -0,0 +1,43 @@
+sensorutils_lib = static_library(
+    'sensorutils',
+    'sensorutils.cpp',
+    include_directories: root_inc,
+    implicit_include_directories: false,
+)
+
+sensorutils_dep = declare_dependency(link_with: sensorutils_lib)
+
+hybrid_src = []
+
+if get_option('hybrid-sensors').allowed()
+    hybrid_src = [
+        'sensorhandler.cpp',
+        'sensordatahandler.cpp',
+        'ipmisensor.cpp',
+        generated_src,
+    ]
+endif
+
+sensorsoem_src = []
+if get_option('sensors-oem').allowed()
+    sensorsoem_src = ['dbus-sdr/sensorcommands_oem.cpp']
+endif
+
+dbus_sdr_pre = declare_dependency(
+    include_directories: root_inc,
+    dependencies: [
+        crypto,
+        nlohmann_json_dep,
+        phosphor_logging_dep,
+        ipmid_dep,
+        sensorutils_dep,
+    ],
+)
+
+dbus_sdr_src = [
+    'dbus-sdr/sdrutils.cpp',
+    'dbus-sdr/sensorcommands.cpp',
+    'dbus-sdr/storagecommands.cpp',
+    hybrid_src,
+    sensorsoem_src,
+]
diff --git a/dbus-sdr/sdrutils.cpp b/dbus-sdr/sdrutils.cpp
new file mode 100644
index 0000000..9523593
--- /dev/null
+++ b/dbus-sdr/sdrutils.cpp
@@ -0,0 +1,652 @@
+/*
+// Copyright (c) 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.
+*/
+
+#include "dbus-sdr/sdrutils.hpp"
+
+#include <ipmid/utils.hpp>
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <fstream>
+#include <optional>
+#include <unordered_set>
+
+#ifdef FEATURE_HYBRID_SENSORS
+
+#include <ipmid/utils.hpp>
+namespace ipmi
+{
+namespace sensor
+{
+extern const IdInfoMap sensors;
+} // namespace sensor
+} // namespace ipmi
+
+#endif
+
+boost::container::flat_map<
+    const char*, std::pair<SensorTypeCodes, SensorEventTypeCodes>, CmpStr>
+    sensorTypes{
+        {{"temperature", std::make_pair(SensorTypeCodes::temperature,
+                                        SensorEventTypeCodes::threshold)},
+         {"voltage", std::make_pair(SensorTypeCodes::voltage,
+                                    SensorEventTypeCodes::threshold)},
+         {"current", std::make_pair(SensorTypeCodes::current,
+                                    SensorEventTypeCodes::threshold)},
+         {"fan_tach", std::make_pair(SensorTypeCodes::fan,
+                                     SensorEventTypeCodes::threshold)},
+         {"fan_pwm", std::make_pair(SensorTypeCodes::fan,
+                                    SensorEventTypeCodes::threshold)},
+         {"intrusion", std::make_pair(SensorTypeCodes::physical_security,
+                                      SensorEventTypeCodes::sensorSpecified)},
+         {"processor", std::make_pair(SensorTypeCodes::processor,
+                                      SensorEventTypeCodes::sensorSpecified)},
+         {"power", std::make_pair(SensorTypeCodes::other,
+                                  SensorEventTypeCodes::threshold)},
+         {"memory", std::make_pair(SensorTypeCodes::memory,
+                                   SensorEventTypeCodes::sensorSpecified)},
+         {"state", std::make_pair(SensorTypeCodes::power_unit,
+                                  SensorEventTypeCodes::sensorSpecified)},
+         {"buttons", std::make_pair(SensorTypeCodes::buttons,
+                                    SensorEventTypeCodes::sensorSpecified)},
+         {"watchdog", std::make_pair(SensorTypeCodes::watchdog2,
+                                     SensorEventTypeCodes::sensorSpecified)},
+         {"entity", std::make_pair(SensorTypeCodes::entity,
+                                   SensorEventTypeCodes::sensorSpecified)},
+         {"energy", std::make_pair(SensorTypeCodes::other,
+                                   SensorEventTypeCodes::threshold)}}};
+
+namespace details
+{
+
+// IPMI supports a smaller number of sensors than are available via Redfish.
+// Trim the list of sensors, via a configuration file.
+// Read the IPMI Sensor Filtering section in docs/configuration.md for
+// a more detailed description.
+static void filterSensors(SensorSubTree& subtree)
+{
+    constexpr const char* filterFilename =
+        "/usr/share/ipmi-providers/sensor_filter.json";
+    std::ifstream filterFile(filterFilename);
+    if (!filterFile.good())
+    {
+        return;
+    }
+    nlohmann::json sensorFilterJSON =
+        nlohmann::json::parse(filterFile, nullptr, false);
+    nlohmann::json::iterator svcFilterit =
+        sensorFilterJSON.find("ServiceFilter");
+    if (svcFilterit == sensorFilterJSON.end())
+    {
+        return;
+    }
+
+    subtree.erase(std::remove_if(subtree.begin(), subtree.end(),
+                                 [svcFilterit](SensorSubTree::value_type& kv) {
+                                     auto& [_, serviceToIfaces] = kv;
+
+                                     for (auto service = svcFilterit->begin();
+                                          service != svcFilterit->end();
+                                          ++service)
+                                     {
+                                         serviceToIfaces.erase(*service);
+                                     }
+                                     return serviceToIfaces.empty();
+                                 }),
+                  subtree.end());
+}
+
+uint16_t getSensorSubtree(std::shared_ptr<SensorSubTree>& subtree)
+{
+    static std::shared_ptr<SensorSubTree> sensorTreePtr;
+    static uint16_t sensorUpdatedIndex = 0;
+    std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
+    static sdbusplus::bus::match_t sensorAdded(
+        *dbus,
+        "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/"
+        "sensors/'",
+        [](sdbusplus::message_t&) { sensorTreePtr.reset(); });
+
+    static sdbusplus::bus::match_t sensorRemoved(
+        *dbus,
+        "type='signal',member='InterfacesRemoved',arg0path='/xyz/"
+        "openbmc_project/sensors/'",
+        [](sdbusplus::message_t&) { sensorTreePtr.reset(); });
+
+    if (sensorTreePtr)
+    {
+        subtree = sensorTreePtr;
+        return sensorUpdatedIndex;
+    }
+
+    sensorTreePtr = std::make_shared<SensorSubTree>();
+
+    static constexpr const int32_t depth = 2;
+
+    auto lbdUpdateSensorTree = [&dbus](const char* path,
+                                       const auto& interfaces) {
+        auto mapperCall = dbus->new_method_call(
+            "xyz.openbmc_project.ObjectMapper",
+            "/xyz/openbmc_project/object_mapper",
+            "xyz.openbmc_project.ObjectMapper", "GetSubTree");
+        SensorSubTree sensorTreePartial;
+
+        mapperCall.append(path, depth, interfaces);
+
+        try
+        {
+            auto mapperReply = dbus->call(mapperCall);
+            mapperReply.read(sensorTreePartial);
+        }
+        catch (const sdbusplus::exception_t& e)
+        {
+            lg2::error("Failed to update subtree, path: {PATH}, error: {ERROR}",
+                       "PATH", path, "ERROR", e);
+            return false;
+        }
+        if constexpr (debug)
+        {
+            std::fprintf(stderr, "IPMI updated: %zu sensors under %s\n",
+                         sensorTreePartial.size(), path);
+        }
+        sensorTreePtr->merge(std::move(sensorTreePartial));
+        return true;
+    };
+
+    // Add sensors to SensorTree
+    static constexpr const std::array sensorInterfaces = {
+        "xyz.openbmc_project.Sensor.Value",
+        "xyz.openbmc_project.Sensor.ValueMutability",
+        "xyz.openbmc_project.Sensor.Threshold.Warning",
+        "xyz.openbmc_project.Sensor.Threshold.Critical"};
+    static constexpr const std::array vrInterfaces = {
+        "xyz.openbmc_project.Control.VoltageRegulatorMode"};
+
+    bool sensorRez =
+        lbdUpdateSensorTree("/xyz/openbmc_project/sensors", sensorInterfaces);
+
+#ifdef FEATURE_HYBRID_SENSORS
+
+    if (!ipmi::sensor::sensors.empty())
+    {
+        for (const auto& sensor : ipmi::sensor::sensors)
+        {
+            // Threshold sensors should not be emplaced in here.
+            if (boost::starts_with(sensor.second.sensorPath,
+                                   "/xyz/openbmc_project/sensors/"))
+            {
+                continue;
+            }
+
+            // The bus service name is not listed in ipmi::sensor::Info. Give it
+            // an empty string. For those function using non-threshold sensors,
+            // the bus service name will be retrieved in an alternative way.
+            boost::container::flat_map<std::string, std::vector<std::string>>
+                connectionMap{
+                    {"", {sensor.second.propertyInterfaces.begin()->first}}};
+            sensorTreePtr->emplace(sensor.second.sensorPath, connectionMap);
+        }
+    }
+
+#endif
+
+    // Error if searching for sensors failed.
+    if (!sensorRez)
+    {
+        return sensorUpdatedIndex;
+    }
+
+    filterSensors(*sensorTreePtr);
+    // Add VR control as optional search path.
+    (void)lbdUpdateSensorTree("/xyz/openbmc_project/vr", vrInterfaces);
+
+    subtree = sensorTreePtr;
+    sensorUpdatedIndex++;
+    // The SDR is being regenerated, wipe the old stats
+    sdrStatsTable.wipeTable();
+    sdrWriteTable.wipeTable();
+    return sensorUpdatedIndex;
+}
+
+bool getSensorNumMap(std::shared_ptr<SensorNumMap>& sensorNumMap)
+{
+    static std::shared_ptr<SensorNumMap> sensorNumMapPtr;
+    bool sensorNumMapUpated = false;
+    static uint16_t prevSensorUpdatedIndex = 0;
+    std::shared_ptr<SensorSubTree> sensorTree;
+    uint16_t curSensorUpdatedIndex = details::getSensorSubtree(sensorTree);
+    if (!sensorTree)
+    {
+        return sensorNumMapUpated;
+    }
+
+    if ((curSensorUpdatedIndex == prevSensorUpdatedIndex) && sensorNumMapPtr)
+    {
+        sensorNumMap = sensorNumMapPtr;
+        return sensorNumMapUpated;
+    }
+    prevSensorUpdatedIndex = curSensorUpdatedIndex;
+
+    sensorNumMapPtr = std::make_shared<SensorNumMap>();
+
+    uint16_t sensorNum = 0;
+    uint16_t sensorIndex = 0;
+    for (const auto& sensor : *sensorTree)
+    {
+        sensorNumMapPtr->insert(
+            SensorNumMap::value_type(sensorNum, sensor.first));
+        sensorIndex++;
+        if (sensorIndex == maxSensorsPerLUN)
+        {
+            sensorIndex = lun1Sensor0;
+        }
+        else if (sensorIndex == (lun1Sensor0 | maxSensorsPerLUN))
+        {
+            // Skip assigning LUN 0x2 any sensors
+            sensorIndex = lun3Sensor0;
+        }
+        else if (sensorIndex == (lun3Sensor0 | maxSensorsPerLUN))
+        {
+            // this is an error, too many IPMI sensors
+            throw std::out_of_range("Maximum number of IPMI sensors exceeded.");
+        }
+        sensorNum = sensorIndex;
+    }
+    sensorNumMap = sensorNumMapPtr;
+    sensorNumMapUpated = true;
+    return sensorNumMapUpated;
+}
+} // namespace details
+
+bool getSensorSubtree(SensorSubTree& subtree)
+{
+    std::shared_ptr<SensorSubTree> sensorTree;
+    details::getSensorSubtree(sensorTree);
+    if (!sensorTree)
+    {
+        return false;
+    }
+
+    subtree = *sensorTree;
+    return true;
+}
+
+#ifdef FEATURE_HYBRID_SENSORS
+// Static sensors are listed in sensor-gen.cpp.
+ipmi::sensor::IdInfoMap::const_iterator findStaticSensor(
+    const std::string& path)
+{
+    return std::find_if(
+        ipmi::sensor::sensors.begin(), ipmi::sensor::sensors.end(),
+        [&path](const ipmi::sensor::IdInfoMap::value_type& findSensor) {
+            return findSensor.second.sensorPath == path;
+        });
+}
+#endif
+
+std::string getSensorTypeStringFromPath(const std::string& path)
+{
+    // get sensor type string from path, path is defined as
+    // /xyz/openbmc_project/sensors/<type>/label
+    size_t typeEnd = path.rfind("/");
+    if (typeEnd == std::string::npos)
+    {
+        return path;
+    }
+    size_t typeStart = path.rfind("/", typeEnd - 1);
+    if (typeStart == std::string::npos)
+    {
+        return path;
+    }
+    // Start at the character after the '/'
+    typeStart++;
+    return path.substr(typeStart, typeEnd - typeStart);
+}
+
+uint8_t getSensorTypeFromPath(const std::string& path)
+{
+    uint8_t sensorType = 0;
+    std::string type = getSensorTypeStringFromPath(path);
+    auto findSensor = sensorTypes.find(type.c_str());
+    if (findSensor != sensorTypes.end())
+    {
+        sensorType =
+            static_cast<uint8_t>(std::get<sensorTypeCodes>(findSensor->second));
+    } // else default 0x0 RESERVED
+
+    return sensorType;
+}
+
+uint16_t getSensorNumberFromPath(const std::string& path)
+{
+    std::shared_ptr<SensorNumMap> sensorNumMapPtr;
+    details::getSensorNumMap(sensorNumMapPtr);
+    if (!sensorNumMapPtr)
+    {
+        return invalidSensorNumber;
+    }
+
+    try
+    {
+        return sensorNumMapPtr->right.at(path);
+    }
+    catch (const std::out_of_range& e)
+    {
+        return invalidSensorNumber;
+    }
+}
+
+uint8_t getSensorEventTypeFromPath(const std::string& path)
+{
+    uint8_t sensorEventType = 0;
+    std::string type = getSensorTypeStringFromPath(path);
+    auto findSensor = sensorTypes.find(type.c_str());
+    if (findSensor != sensorTypes.end())
+    {
+        sensorEventType = static_cast<uint8_t>(
+            std::get<sensorEventTypeCodes>(findSensor->second));
+    }
+
+    return sensorEventType;
+}
+
+std::string getPathFromSensorNumber(uint16_t sensorNum)
+{
+    std::shared_ptr<SensorNumMap> sensorNumMapPtr;
+    details::getSensorNumMap(sensorNumMapPtr);
+    if (!sensorNumMapPtr)
+    {
+        return std::string();
+    }
+
+    try
+    {
+        return sensorNumMapPtr->left.at(sensorNum);
+    }
+    catch (const std::out_of_range& e)
+    {
+        return std::string();
+    }
+}
+
+namespace ipmi
+{
+
+std::optional<std::map<std::string, std::vector<std::string>>>
+    getObjectInterfaces(const char* path)
+{
+    std::map<std::string, std::vector<std::string>> interfacesResponse;
+    std::vector<std::string> interfaces;
+    std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
+
+    sdbusplus::message_t getObjectMessage =
+        dbus->new_method_call("xyz.openbmc_project.ObjectMapper",
+                              "/xyz/openbmc_project/object_mapper",
+                              "xyz.openbmc_project.ObjectMapper", "GetObject");
+    getObjectMessage.append(path, interfaces);
+
+    try
+    {
+        sdbusplus::message_t response = dbus->call(getObjectMessage);
+        response.read(interfacesResponse);
+    }
+    catch (const std::exception& e)
+    {
+        return std::nullopt;
+    }
+
+    return interfacesResponse;
+}
+
+std::map<std::string, Value> getEntityManagerProperties(const char* path,
+                                                        const char* interface)
+{
+    std::map<std::string, Value> properties;
+    std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
+
+    sdbusplus::message_t getProperties =
+        dbus->new_method_call("xyz.openbmc_project.EntityManager", path,
+                              "org.freedesktop.DBus.Properties", "GetAll");
+    getProperties.append(interface);
+
+    try
+    {
+        sdbusplus::message_t response = dbus->call(getProperties);
+        response.read(properties);
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Failed to GetAll, path: {PATH}, interface: {INTERFACE}, "
+                   "error: {ERROR}",
+                   "PATH", path, "INTERFACE", interface, "ERROR", e);
+    }
+
+    return properties;
+}
+
+// Fetch the ipmiDecoratorPaths to get the list of dbus objects that
+// have ipmi decorator to prevent unnessary dbus call to fetch the info
+std::optional<std::unordered_set<std::string>>& getIpmiDecoratorPaths(
+    const std::optional<ipmi::Context::ptr>& ctx)
+{
+    static std::optional<std::unordered_set<std::string>> ipmiDecoratorPaths;
+
+    if (!ctx.has_value() || ipmiDecoratorPaths != std::nullopt)
+    {
+        return ipmiDecoratorPaths;
+    }
+
+    using Paths = std::vector<std::string>;
+    boost::system::error_code ec;
+    Paths paths = ipmi::callDbusMethod<Paths>(
+        *ctx, ec, "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/", int32_t(0),
+        std::array<const char*, 1>{
+            "xyz.openbmc_project.Inventory.Decorator.Ipmi"});
+
+    if (ec)
+    {
+        return ipmiDecoratorPaths;
+    }
+
+    ipmiDecoratorPaths =
+        std::unordered_set<std::string>(paths.begin(), paths.end());
+    return ipmiDecoratorPaths;
+}
+
+const std::string* getSensorConfigurationInterface(
+    const std::map<std::string, std::vector<std::string>>&
+        sensorInterfacesResponse)
+{
+    auto entityManagerService =
+        sensorInterfacesResponse.find("xyz.openbmc_project.EntityManager");
+    if (entityManagerService == sensorInterfacesResponse.end())
+    {
+        return nullptr;
+    }
+
+    // Find the fan configuration first (fans can have multiple configuration
+    // interfaces).
+    for (const auto& entry : entityManagerService->second)
+    {
+        if (entry == "xyz.openbmc_project.Configuration.AspeedFan" ||
+            entry == "xyz.openbmc_project.Configuration.I2CFan" ||
+            entry == "xyz.openbmc_project.Configuration.NuvotonFan")
+        {
+            return &entry;
+        }
+    }
+
+    for (const auto& entry : entityManagerService->second)
+    {
+        if (boost::algorithm::starts_with(entry,
+                                          "xyz.openbmc_project.Configuration."))
+        {
+            return &entry;
+        }
+    }
+
+    return nullptr;
+}
+
+// Follow Association properties for Sensor back to the Board dbus object to
+// check for an EntityId and EntityInstance property.
+void updateIpmiFromAssociation(
+    const std::string& path,
+    const std::unordered_set<std::string>& ipmiDecoratorPaths,
+    const DbusInterfaceMap& sensorMap, uint8_t& entityId,
+    uint8_t& entityInstance)
+{
+    namespace fs = std::filesystem;
+
+    auto sensorAssociationObject =
+        sensorMap.find("xyz.openbmc_project.Association.Definitions");
+    if (sensorAssociationObject == sensorMap.end())
+    {
+        if constexpr (debug)
+        {
+            std::fprintf(stderr, "path=%s, no association interface found\n",
+                         path.c_str());
+        }
+
+        return;
+    }
+
+    auto associationObject =
+        sensorAssociationObject->second.find("Associations");
+    if (associationObject == sensorAssociationObject->second.end())
+    {
+        if constexpr (debug)
+        {
+            std::fprintf(stderr, "path=%s, no association records found\n",
+                         path.c_str());
+        }
+
+        return;
+    }
+
+    std::vector<Association> associationValues =
+        std::get<std::vector<Association>>(associationObject->second);
+
+    // loop through the Associations looking for the right one:
+    for (const auto& entry : associationValues)
+    {
+        // forward, reverse, endpoint
+        const std::string& forward = std::get<0>(entry);
+        const std::string& reverse = std::get<1>(entry);
+        const std::string& endpoint = std::get<2>(entry);
+
+        // We only currently concern ourselves with chassis+all_sensors.
+        if (!(forward == "chassis" && reverse == "all_sensors"))
+        {
+            continue;
+        }
+
+        // the endpoint is the board entry provided by
+        // Entity-Manager. so let's grab its properties if it has
+        // the right interface.
+
+        // just try grabbing the properties first.
+        ipmi::PropertyMap::iterator entityIdProp;
+        ipmi::PropertyMap::iterator entityInstanceProp;
+        if (ipmiDecoratorPaths.contains(endpoint))
+        {
+            std::map<std::string, Value> ipmiProperties =
+                getEntityManagerProperties(
+                    endpoint.c_str(),
+                    "xyz.openbmc_project.Inventory.Decorator.Ipmi");
+
+            entityIdProp = ipmiProperties.find("EntityId");
+            entityInstanceProp = ipmiProperties.find("EntityInstance");
+            if (entityIdProp != ipmiProperties.end())
+            {
+                entityId = static_cast<uint8_t>(
+                    std::get<uint64_t>(entityIdProp->second));
+            }
+            if (entityInstanceProp != ipmiProperties.end())
+            {
+                entityInstance = static_cast<uint8_t>(
+                    std::get<uint64_t>(entityInstanceProp->second));
+            }
+        }
+
+        // Now check the entity-manager entry for this sensor to see
+        // if it has its own value and use that instead.
+        //
+        // In theory, checking this first saves us from checking
+        // both, except in most use-cases identified, there won't be
+        // a per sensor override, so we need to always check both.
+        std::string sensorNameFromPath = fs::path(path).filename();
+
+        std::string sensorConfigPath = endpoint + "/" + sensorNameFromPath;
+
+        // Download the interfaces for the sensor from
+        // Entity-Manager to find the name of the configuration
+        // interface.
+        std::optional<std::map<std::string, std::vector<std::string>>>
+            sensorInterfacesResponseOpt =
+                getObjectInterfaces(sensorConfigPath.c_str());
+
+        if (!sensorInterfacesResponseOpt.has_value())
+        {
+            lg2::debug("Failed to GetObject, path: {PATH}", "PATH",
+                       sensorConfigPath);
+            continue;
+        }
+
+        const std::string* configurationInterface =
+            getSensorConfigurationInterface(
+                sensorInterfacesResponseOpt.value());
+
+        // If there are multi association path settings and only one path exist,
+        // we need to continue if cannot find configuration interface for this
+        // sensor.
+        if (!configurationInterface)
+        {
+            continue;
+        }
+
+        // We found a configuration interface.
+        std::map<std::string, Value> configurationProperties =
+            getEntityManagerProperties(sensorConfigPath.c_str(),
+                                       configurationInterface->c_str());
+
+        entityIdProp = configurationProperties.find("EntityId");
+        entityInstanceProp = configurationProperties.find("EntityInstance");
+        if (entityIdProp != configurationProperties.end())
+        {
+            entityId =
+                static_cast<uint8_t>(std::get<uint64_t>(entityIdProp->second));
+        }
+        if (entityInstanceProp != configurationProperties.end())
+        {
+            entityInstance = static_cast<uint8_t>(
+                std::get<uint64_t>(entityInstanceProp->second));
+        }
+
+        // stop searching Association records.
+        break;
+    } // end for Association vectors.
+
+    if constexpr (debug)
+    {
+        std::fprintf(stderr, "path=%s, entityId=%d, entityInstance=%d\n",
+                     path.c_str(), entityId, entityInstance);
+    }
+}
+
+} // namespace ipmi
diff --git a/dbus-sdr/sensorcommands.cpp b/dbus-sdr/sensorcommands.cpp
new file mode 100644
index 0000000..2ef33b9
--- /dev/null
+++ b/dbus-sdr/sensorcommands.cpp
@@ -0,0 +1,2882 @@
+/*
+// Copyright (c) 2017 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.
+*/
+
+#include "config.h"
+
+#include "dbus-sdr/sensorcommands.hpp"
+
+#include "dbus-sdr/sdrutils.hpp"
+#include "dbus-sdr/sensorutils.hpp"
+#include "dbus-sdr/storagecommands.hpp"
+
+#include <boost/algorithm/string.hpp>
+#include <boost/container/flat_map.hpp>
+#include <ipmid/api.hpp>
+#include <ipmid/entity_map_json.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+#include <user_channel/channel_layer.hpp>
+
+#include <algorithm>
+#include <array>
+#include <chrono>
+#include <cmath>
+#include <cstring>
+#include <format>
+#include <iostream>
+#include <map>
+#include <optional>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <variant>
+
+#ifdef FEATURE_HYBRID_SENSORS
+
+#include "sensordatahandler.hpp"
+namespace ipmi
+{
+namespace sensor
+{
+extern const IdInfoMap sensors;
+} // namespace sensor
+} // namespace ipmi
+#endif
+namespace ipmi
+{
+namespace dcmi
+{
+// Refer Table 6-14, DCMI Entity ID Extension, DCMI v1.5 spec
+static const std::map<uint8_t, uint8_t> validEntityId{
+    {0x40, 0x37}, {0x37, 0x40}, {0x41, 0x03},
+    {0x03, 0x41}, {0x42, 0x07}, {0x07, 0x42}};
+constexpr uint8_t temperatureSensorType = 0x01;
+constexpr uint8_t maxRecords = 8;
+} // namespace dcmi
+} // namespace ipmi
+constexpr std::array<const char*, 7> suffixes = {
+    "_Output_Voltage", "_Input_Voltage", "_Output_Current", "_Input_Current",
+    "_Output_Power",   "_Input_Power",   "_Temperature"};
+namespace ipmi
+{
+
+using phosphor::logging::entry;
+using phosphor::logging::level;
+using phosphor::logging::log;
+
+static constexpr int sensorMapUpdatePeriod = 10;
+static constexpr int sensorMapSdrUpdatePeriod = 60;
+
+// BMC I2C address is generally at 0x20
+static constexpr uint8_t bmcI2CAddr = 0x20;
+
+constexpr size_t maxSDRTotalSize =
+    76; // Largest SDR Record Size (type 01) + SDR Overheader Size
+constexpr static const uint32_t noTimestamp = 0xFFFFFFFF;
+
+static uint16_t sdrReservationID;
+static uint32_t sdrLastAdd = noTimestamp;
+static uint32_t sdrLastRemove = noTimestamp;
+static constexpr size_t lastRecordIndex = 0xFFFF;
+
+// The IPMI spec defines four Logical Units (LUN), each capable of supporting
+// 255 sensors. The 256 values assigned to LUN 2 are special and are not used
+// for general purpose sensors. Each LUN reserves location 0xFF. The maximum
+// number of IPMI sensors are LUN 0 + LUN 1 + LUN 3, less the reserved
+// location.
+static constexpr size_t maxIPMISensors = ((3 * 256) - (3 * 1));
+
+static constexpr uint8_t lun0 = 0x0;
+static constexpr uint8_t lun1 = 0x1;
+static constexpr uint8_t lun3 = 0x3;
+
+static constexpr size_t lun0MaxSensorNum = 0xfe;
+static constexpr size_t lun1MaxSensorNum = 0x1fe;
+static constexpr size_t lun3MaxSensorNum = 0x3fe;
+static constexpr int GENERAL_ERROR = -1;
+
+static boost::container::flat_map<std::string, ObjectValueTree> SensorCache;
+
+// Specify the comparison required to sort and find char* map objects
+struct CmpStr
+{
+    bool operator()(const char* a, const char* b) const
+    {
+        return std::strcmp(a, b) < 0;
+    }
+};
+const static boost::container::flat_map<const char*, SensorUnits, CmpStr>
+    sensorUnits{{{"temperature", SensorUnits::degreesC},
+                 {"voltage", SensorUnits::volts},
+                 {"current", SensorUnits::amps},
+                 {"fan_tach", SensorUnits::rpm},
+                 {"power", SensorUnits::watts},
+                 {"energy", SensorUnits::joules}}};
+
+void registerSensorFunctions() __attribute__((constructor));
+
+static sdbusplus::bus::match_t sensorAdded(
+    *getSdBus(),
+    "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/"
+    "sensors/'",
+    [](sdbusplus::message_t&) {
+        getSensorTree().clear();
+        getIpmiDecoratorPaths(/*ctx=*/std::nullopt).reset();
+        sdrLastAdd = std::chrono::duration_cast<std::chrono::seconds>(
+                         std::chrono::system_clock::now().time_since_epoch())
+                         .count();
+    });
+
+static sdbusplus::bus::match_t sensorRemoved(
+    *getSdBus(),
+    "type='signal',member='InterfacesRemoved',arg0path='/xyz/openbmc_project/"
+    "sensors/'",
+    [](sdbusplus::message_t&) {
+        getSensorTree().clear();
+        getIpmiDecoratorPaths(/*ctx=*/std::nullopt).reset();
+        sdrLastRemove = std::chrono::duration_cast<std::chrono::seconds>(
+                            std::chrono::system_clock::now().time_since_epoch())
+                            .count();
+    });
+
+ipmi_ret_t getSensorConnection(ipmi::Context::ptr ctx, uint8_t sensnum,
+                               std::string& connection, std::string& path,
+                               std::vector<std::string>* interfaces)
+{
+    auto& sensorTree = getSensorTree();
+    if (!getSensorSubtree(sensorTree) && sensorTree.empty())
+    {
+        return IPMI_CC_RESPONSE_ERROR;
+    }
+
+    if (ctx == nullptr)
+    {
+        return IPMI_CC_RESPONSE_ERROR;
+    }
+
+    path = getPathFromSensorNumber((ctx->lun << 8) | sensnum);
+    if (path.empty())
+    {
+        return IPMI_CC_INVALID_FIELD_REQUEST;
+    }
+
+    for (const auto& sensor : sensorTree)
+    {
+        if (path == sensor.first)
+        {
+            connection = sensor.second.begin()->first;
+            if (interfaces)
+                *interfaces = sensor.second.begin()->second;
+            break;
+        }
+    }
+
+    return 0;
+}
+
+SensorSubTree& getSensorTree()
+{
+    static SensorSubTree sensorTree;
+    return sensorTree;
+}
+
+// this keeps track of deassertions for sensor event status command. A
+// deasertion can only happen if an assertion was seen first.
+static boost::container::flat_map<
+    std::string, boost::container::flat_map<std::string, std::optional<bool>>>
+    thresholdDeassertMap;
+
+static sdbusplus::bus::match_t thresholdChanged(
+    *getSdBus(),
+    "type='signal',member='PropertiesChanged',interface='org.freedesktop.DBus."
+    "Properties',arg0namespace='xyz.openbmc_project.Sensor.Threshold'",
+    [](sdbusplus::message_t& m) {
+        boost::container::flat_map<std::string, std::variant<bool, double>>
+            values;
+        m.read(std::string(), values);
+
+        auto findAssert =
+            std::find_if(values.begin(), values.end(), [](const auto& pair) {
+                return pair.first.find("Alarm") != std::string::npos;
+            });
+        if (findAssert != values.end())
+        {
+            auto ptr = std::get_if<bool>(&(findAssert->second));
+            if (ptr == nullptr)
+            {
+                lg2::error("thresholdChanged: Assert non bool");
+                return;
+            }
+            if (*ptr)
+            {
+                lg2::info(
+                    "thresholdChanged: Assert, sensor path: {SENSOR_PATH}",
+                    "SENSOR_PATH", m.get_path());
+                thresholdDeassertMap[m.get_path()][findAssert->first] = *ptr;
+            }
+            else
+            {
+                auto& value =
+                    thresholdDeassertMap[m.get_path()][findAssert->first];
+                if (value)
+                {
+                    lg2::info(
+                        "thresholdChanged: deassert, sensor path: {SENSOR_PATH}",
+                        "SENSOR_PATH", m.get_path());
+                    value = *ptr;
+                }
+            }
+        }
+    });
+
+namespace sensor
+{
+static constexpr const char* vrInterface =
+    "xyz.openbmc_project.Control.VoltageRegulatorMode";
+static constexpr const char* sensorInterface =
+    "xyz.openbmc_project.Sensor.Value";
+} // namespace sensor
+
+static void getSensorMaxMin(const DbusInterfaceMap& sensorMap, double& max,
+                            double& min)
+{
+    max = 127;
+    min = -128;
+
+    auto sensorObject = sensorMap.find(sensor::sensorInterface);
+    auto critical =
+        sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
+    auto warning =
+        sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
+
+    if (sensorObject != sensorMap.end())
+    {
+        auto maxMap = sensorObject->second.find("MaxValue");
+        auto minMap = sensorObject->second.find("MinValue");
+
+        if (maxMap != sensorObject->second.end())
+        {
+            max = std::visit(VariantToDoubleVisitor(), maxMap->second);
+        }
+        if (minMap != sensorObject->second.end())
+        {
+            min = std::visit(VariantToDoubleVisitor(), minMap->second);
+        }
+    }
+    if (critical != sensorMap.end())
+    {
+        auto lower = critical->second.find("CriticalLow");
+        auto upper = critical->second.find("CriticalHigh");
+        if (lower != critical->second.end())
+        {
+            double value = std::visit(VariantToDoubleVisitor(), lower->second);
+            if (std::isfinite(value))
+            {
+                min = std::fmin(value, min);
+            }
+        }
+        if (upper != critical->second.end())
+        {
+            double value = std::visit(VariantToDoubleVisitor(), upper->second);
+            if (std::isfinite(value))
+            {
+                max = std::fmax(value, max);
+            }
+        }
+    }
+    if (warning != sensorMap.end())
+    {
+        auto lower = warning->second.find("WarningLow");
+        auto upper = warning->second.find("WarningHigh");
+        if (lower != warning->second.end())
+        {
+            double value = std::visit(VariantToDoubleVisitor(), lower->second);
+            if (std::isfinite(value))
+            {
+                min = std::fmin(value, min);
+            }
+        }
+        if (upper != warning->second.end())
+        {
+            double value = std::visit(VariantToDoubleVisitor(), upper->second);
+            if (std::isfinite(value))
+            {
+                max = std::fmax(value, max);
+            }
+        }
+    }
+}
+
+static bool getSensorMap(ipmi::Context::ptr ctx, std::string sensorConnection,
+                         std::string sensorPath, DbusInterfaceMap& sensorMap,
+                         int updatePeriod = sensorMapUpdatePeriod)
+{
+#ifdef FEATURE_HYBRID_SENSORS
+    if (auto sensor = findStaticSensor(sensorPath);
+        sensor != ipmi::sensor::sensors.end() &&
+        getSensorEventTypeFromPath(sensorPath) !=
+            static_cast<uint8_t>(SensorEventTypeCodes::threshold))
+    {
+        // If the incoming sensor is a discrete sensor, it might fail in
+        // getManagedObjects(), return true, and use its own getFunc to get
+        // value.
+        return true;
+    }
+#endif
+
+    static boost::container::flat_map<
+        std::string, std::chrono::time_point<std::chrono::steady_clock>>
+        updateTimeMap;
+
+    auto updateFind = updateTimeMap.find(sensorConnection);
+    auto lastUpdate = std::chrono::time_point<std::chrono::steady_clock>();
+    if (updateFind != updateTimeMap.end())
+    {
+        lastUpdate = updateFind->second;
+    }
+
+    auto now = std::chrono::steady_clock::now();
+
+    if (std::chrono::duration_cast<std::chrono::seconds>(now - lastUpdate)
+            .count() > updatePeriod)
+    {
+        bool found = false;
+
+        // Object managers for different kinds of OpenBMC DBus interfaces.
+        // Documented in the phosphor-dbus-interfaces repository.
+        const char* paths[] = {
+            "/xyz/openbmc_project/sensors",
+            "/xyz/openbmc_project/vr",
+        };
+        constexpr size_t num_paths = sizeof(paths) / sizeof(paths[0]);
+        ObjectValueTree allManagedObjects;
+
+        for (size_t i = 0; i < num_paths; i++)
+        {
+            ObjectValueTree managedObjects;
+            boost::system::error_code ec = getManagedObjects(
+                ctx, sensorConnection.c_str(), paths[i], managedObjects);
+            if (ec)
+            {
+                continue;
+            }
+            allManagedObjects.merge(managedObjects);
+            found = true;
+        }
+
+        if (!found)
+        {
+            lg2::error("GetMangagedObjects for getSensorMap failed, "
+                       "service: {SERVICE}",
+                       "SERVICE", sensorConnection);
+
+            return false;
+        }
+
+        SensorCache[sensorConnection] = allManagedObjects;
+        // Update time after finish building the map which allow the
+        // data to be cached for updatePeriod plus the build time.
+        updateTimeMap[sensorConnection] = std::chrono::steady_clock::now();
+    }
+    auto connection = SensorCache.find(sensorConnection);
+    if (connection == SensorCache.end())
+    {
+        return false;
+    }
+    auto path = connection->second.find(sensorPath);
+    if (path == connection->second.end())
+    {
+        return false;
+    }
+    sensorMap = path->second;
+
+    return true;
+}
+
+namespace sensor
+{
+// Read VR profiles from sensor(daemon) interface
+static std::optional<std::vector<std::string>> getSupportedVrProfiles(
+    const ipmi::DbusInterfaceMap::mapped_type& object)
+{
+    // get VR mode profiles from Supported Interface
+    auto supportedProperty = object.find("Supported");
+    if (supportedProperty == object.end() ||
+        object.find("Selected") == object.end())
+    {
+        lg2::error("Missing the required Supported and Selected properties");
+        return std::nullopt;
+    }
+
+    const auto profilesPtr =
+        std::get_if<std::vector<std::string>>(&supportedProperty->second);
+
+    if (profilesPtr == nullptr)
+    {
+        lg2::error("property is not array of string");
+        return std::nullopt;
+    }
+    return *profilesPtr;
+}
+
+// Calculate VR Mode from input IPMI discrete event bytes
+static std::optional<std::string> calculateVRMode(
+    uint15_t assertOffset, const ipmi::DbusInterfaceMap::mapped_type& VRObject)
+{
+    // get VR mode profiles from Supported Interface
+    auto profiles = getSupportedVrProfiles(VRObject);
+    if (!profiles)
+    {
+        return std::nullopt;
+    }
+
+    // interpret IPMI cmd bits into profiles' index
+    long unsigned int index = 0;
+    // only one bit should be set and the highest bit should not be used.
+    if (assertOffset == 0 || assertOffset == (1u << 15) ||
+        (assertOffset & (assertOffset - 1)))
+    {
+        lg2::error("IPMI cmd format incorrect, bytes: {BYTES}", "BYTES",
+                   lg2::hex, static_cast<uint16_t>(assertOffset));
+        return std::nullopt;
+    }
+
+    while (assertOffset != 1)
+    {
+        assertOffset >>= 1;
+        index++;
+    }
+
+    if (index >= profiles->size())
+    {
+        lg2::error("profile index out of boundary");
+        return std::nullopt;
+    }
+
+    return profiles->at(index);
+}
+
+// Calculate sensor value from IPMI reading byte
+static std::optional<double> calculateValue(
+    uint8_t reading, const ipmi::DbusInterfaceMap& sensorMap,
+    const ipmi::DbusInterfaceMap::mapped_type& valueObject)
+{
+    if (valueObject.find("Value") == valueObject.end())
+    {
+        lg2::error("Missing the required Value property");
+        return std::nullopt;
+    }
+
+    double max = 0;
+    double min = 0;
+    getSensorMaxMin(sensorMap, max, min);
+
+    int16_t mValue = 0;
+    int16_t bValue = 0;
+    int8_t rExp = 0;
+    int8_t bExp = 0;
+    bool bSigned = false;
+
+    if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
+    {
+        return std::nullopt;
+    }
+
+    double value = bSigned ? ((int8_t)reading) : reading;
+
+    value *= ((double)mValue);
+    value += ((double)bValue) * std::pow(10.0, bExp);
+    value *= std::pow(10.0, rExp);
+
+    return value;
+}
+
+// Extract file name from sensor path as the sensors SDR ID. Simplify the name
+// if it is too long.
+std::string parseSdrIdFromPath(const std::string& path)
+{
+    std::string name;
+    size_t nameStart = path.rfind("/");
+    if (nameStart != std::string::npos)
+    {
+        name = path.substr(nameStart + 1, std::string::npos - nameStart);
+    }
+
+    if (name.size() > FULL_RECORD_ID_STR_MAX_LENGTH)
+    {
+#ifdef SHORTNAME_REMOVE_SUFFIX
+        for (const auto& suffix : suffixes)
+        {
+            if (boost::ends_with(name, suffix))
+            {
+                boost::replace_all(name, suffix, "");
+                break;
+            }
+        }
+#endif
+#ifdef SHORTNAME_REPLACE_WORDS
+        constexpr std::array<std::pair<const char*, const char*>, 2>
+            replaceWords = {std::make_pair("Output", "Out"),
+                            std::make_pair("Input", "In")};
+        for (const auto& [find, replace] : replaceWords)
+        {
+            boost::replace_all(name, find, replace);
+        }
+#endif
+
+        // as a backup and if nothing else is configured
+        name.resize(FULL_RECORD_ID_STR_MAX_LENGTH);
+    }
+    return name;
+}
+
+bool getVrEventStatus(ipmi::Context::ptr ctx, const std::string& connection,
+                      const std::string& path,
+                      const ipmi::DbusInterfaceMap::mapped_type& object,
+                      std::bitset<16>& assertions)
+{
+    auto profiles = sensor::getSupportedVrProfiles(object);
+    if (!profiles)
+    {
+        return false;
+    }
+    std::string mode;
+
+    auto ec = getDbusProperty(ctx, connection, path, sensor::vrInterface,
+                              "Selected", mode);
+    if (ec)
+    {
+        lg2::error("Failed to get Selected, path: {PATH}, "
+                   "interface: {INTERFACE}, error: {ERROR}",
+                   "PATH", path, "INTERFACE", sensor::sensorInterface, "ERROR",
+                   ec.message());
+        return false;
+    }
+
+    auto itr = std::find(profiles->begin(), profiles->end(), mode);
+    if (itr == profiles->end())
+    {
+        lg2::error("VR mode doesn't match any of its profiles, path: {PATH}",
+                   "PATH", path);
+        return false;
+    }
+    std::size_t index =
+        static_cast<std::size_t>(std::distance(profiles->begin(), itr));
+
+    // map index to response event assertion bit.
+    if (index < 16)
+    {
+        assertions.set(index);
+    }
+    else
+    {
+        lg2::error("VR profile index reaches max assertion bit, "
+                   "path: {PATH}, index: {INDEX}",
+                   "PATH", path, "INDEX", index);
+        return false;
+    }
+    if constexpr (debug)
+    {
+        std::cerr << "VR sensor " << sensor::parseSdrIdFromPath(path)
+                  << " mode is: [" << index << "] " << mode << std::endl;
+    }
+    return true;
+}
+
+/*
+ * Handle every Sensor Data Record besides Type 01
+ *
+ * The D-Bus sensors work well for generating Type 01 SDRs.
+ * After the Type 01 sensors are processed the remaining sensor types require
+ * special handling. Each BMC vendor is going to have their own requirements for
+ * insertion of non-Type 01 records.
+ * Manage non-Type 01 records:
+ *
+ * Create a new file: dbus-sdr/sensorcommands_oem.cpp
+ * Populate it with the two weakly linked functions below, without adding the
+ * 'weak' attribute definition prior to the function definition.
+ *    getOtherSensorsCount(...)
+ *    getOtherSensorsDataRecord(...)
+ *    Example contents are provided in the weak definitions below
+ *    Enable 'sensors-oem' in your phosphor-ipmi-host bbappend file
+ *      'EXTRA_OEMESON:append = " -Dsensors-oem=enabled"'
+ * The contents of the sensorcommands_oem.cpp file will then override the code
+ * provided below.
+ */
+
+size_t getOtherSensorsCount(ipmi::Context::ptr ctx) __attribute__((weak));
+size_t getOtherSensorsCount(ipmi::Context::ptr ctx)
+{
+    size_t fruCount = 0;
+
+    ipmi::Cc ret = ipmi::storage::getFruSdrCount(ctx, fruCount);
+    if (ret != ipmi::ccSuccess)
+    {
+        lg2::error("getOtherSensorsCount: getFruSdrCount error");
+        return std::numeric_limits<size_t>::max();
+    }
+
+    const auto& entityRecords =
+        ipmi::sensor::EntityInfoMapContainer::getContainer()
+            ->getIpmiEntityRecords();
+    size_t entityCount = entityRecords.size();
+
+    return fruCount + ipmi::storage::type12Count + entityCount;
+}
+
+int getOtherSensorsDataRecord(ipmi::Context::ptr ctx, uint16_t recordID,
+                              std::vector<uint8_t>& recordData)
+    __attribute__((weak));
+int getOtherSensorsDataRecord(ipmi::Context::ptr ctx, uint16_t recordID,
+                              std::vector<uint8_t>& recordData)
+{
+    size_t otherCount{ipmi::sensor::getOtherSensorsCount(ctx)};
+    if (otherCount == std::numeric_limits<size_t>::max())
+    {
+        return GENERAL_ERROR;
+    }
+    const auto& entityRecords =
+        ipmi::sensor::EntityInfoMapContainer::getContainer()
+            ->getIpmiEntityRecords();
+
+    size_t sdrIndex(recordID - ipmi::getNumberOfSensors());
+    size_t entityCount{entityRecords.size()};
+    size_t fruCount{otherCount - ipmi::storage::type12Count - entityCount};
+
+    if (sdrIndex > otherCount)
+    {
+        return std::numeric_limits<int>::min();
+    }
+    else if (sdrIndex >= fruCount + ipmi::storage::type12Count)
+    {
+        // handle type 8 entity map records
+        ipmi::sensor::EntityInfoMap::const_iterator entity =
+            entityRecords.find(static_cast<uint8_t>(
+                sdrIndex - fruCount - ipmi::storage::type12Count));
+
+        if (entity == entityRecords.end())
+        {
+            return GENERAL_ERROR;
+        }
+        recordData = ipmi::storage::getType8SDRs(entity, recordID);
+    }
+    else if (sdrIndex >= fruCount)
+    {
+        // handle type 12 hardcoded records
+        size_t type12Index = sdrIndex - fruCount;
+        if (type12Index >= ipmi::storage::type12Count)
+        {
+            lg2::error("getSensorDataRecord: type12Index error");
+            return GENERAL_ERROR;
+        }
+        recordData = ipmi::storage::getType12SDRs(type12Index, recordID);
+    }
+    else
+    {
+        // handle fru records
+        get_sdr::SensorDataFruRecord data;
+        if (ipmi::Cc ret = ipmi::storage::getFruSdrs(ctx, sdrIndex, data);
+            ret != IPMI_CC_OK)
+        {
+            return GENERAL_ERROR;
+        }
+        data.header.record_id_msb = recordID >> 8;
+        data.header.record_id_lsb = recordID & 0xFF;
+        recordData.insert(recordData.end(), reinterpret_cast<uint8_t*>(&data),
+                          reinterpret_cast<uint8_t*>(&data) + sizeof(data));
+    }
+
+    return 0;
+}
+
+} // namespace sensor
+
+ipmi::RspType<> ipmiSenPlatformEvent(ipmi::Context::ptr ctx,
+                                     ipmi::message::Payload& p)
+{
+    constexpr const uint8_t validEnvmRev = 0x04;
+    constexpr const uint8_t lastSensorType = 0x2C;
+    constexpr const uint8_t oemReserved = 0xC0;
+
+    uint8_t sysgeneratorID = 0;
+    uint8_t evmRev = 0;
+    uint8_t sensorType = 0;
+    uint8_t sensorNum = 0;
+    uint8_t eventType = 0;
+    uint8_t eventData1 = 0;
+    std::optional<uint8_t> eventData2 = 0;
+    std::optional<uint8_t> eventData3 = 0;
+    [[maybe_unused]] uint16_t generatorID = 0;
+    ipmi::ChannelInfo chInfo;
+
+    if (ipmi::getChannelInfo(ctx->channel, chInfo) != ipmi::ccSuccess)
+    {
+        lg2::error("Failed to get Channel Info, channel: {CHANNEL}", "CHANNEL",
+                   ctx->channel);
+        return ipmi::responseUnspecifiedError();
+    }
+
+    if (static_cast<ipmi::EChannelMediumType>(chInfo.mediumType) ==
+        ipmi::EChannelMediumType::systemInterface)
+    {
+        p.unpack(sysgeneratorID, evmRev, sensorType, sensorNum, eventType,
+                 eventData1, eventData2, eventData3);
+        constexpr const uint8_t isSoftwareID = 0x01;
+        if (!(sysgeneratorID & isSoftwareID))
+        {
+            return ipmi::responseInvalidFieldRequest();
+        }
+        // Refer to IPMI Spec Table 32: SEL Event Records
+        generatorID = (ctx->channel << 12) // Channel
+                      | (0x0 << 10)        // Reserved
+                      | (0x0 << 8)         // 0x0 for sys-soft ID
+                      | sysgeneratorID;
+    }
+    else
+    {
+        p.unpack(evmRev, sensorType, sensorNum, eventType, eventData1,
+                 eventData2, eventData3);
+        // Refer to IPMI Spec Table 32: SEL Event Records
+        generatorID = (ctx->channel << 12)      // Channel
+                      | (0x0 << 10)             // Reserved
+                      | ((ctx->lun & 0x3) << 8) // Lun
+                      | (ctx->rqSA << 1);
+    }
+
+    if (!p.fullyUnpacked())
+    {
+        return ipmi::responseReqDataLenInvalid();
+    }
+
+    // Check for valid evmRev and Sensor Type(per Table 42 of spec)
+    if (evmRev != validEnvmRev)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+    if ((sensorType > lastSensorType) && (sensorType < oemReserved))
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    return ipmi::responseSuccess();
+}
+
+ipmi::RspType<> ipmiSetSensorReading(
+    ipmi::Context::ptr ctx, uint8_t sensorNumber, uint8_t, uint8_t reading,
+    uint15_t assertOffset, bool, uint15_t, bool, uint8_t, uint8_t, uint8_t)
+{
+    std::string connection;
+    std::string path;
+    std::vector<std::string> interfaces;
+
+    ipmi::Cc status =
+        getSensorConnection(ctx, sensorNumber, connection, path, &interfaces);
+    if (status)
+    {
+        return ipmi::response(status);
+    }
+
+    // we can tell the sensor type by its interface type
+    if (std::find(interfaces.begin(), interfaces.end(),
+                  sensor::sensorInterface) != interfaces.end())
+    {
+        DbusInterfaceMap sensorMap;
+        if (!getSensorMap(ctx, connection, path, sensorMap))
+        {
+            return ipmi::responseResponseError();
+        }
+        auto sensorObject = sensorMap.find(sensor::sensorInterface);
+        if (sensorObject == sensorMap.end())
+        {
+            return ipmi::responseResponseError();
+        }
+
+        // Only allow external SetSensor if write permission granted
+        if (!details::sdrWriteTable.getWritePermission(
+                (ctx->lun << 8) | sensorNumber))
+        {
+            return ipmi::responseResponseError();
+        }
+
+        auto value =
+            sensor::calculateValue(reading, sensorMap, sensorObject->second);
+        if (!value)
+        {
+            return ipmi::responseResponseError();
+        }
+
+        if constexpr (debug)
+        {
+            lg2::info("IPMI SET_SENSOR, sensor number: {SENSOR_NUM}, "
+                      "byte: {BYTE}, value: {VALUE}",
+                      "SENSOR_NUM", sensorNumber, "BYTE", (unsigned int)reading,
+                      "VALUE", *value);
+        }
+
+        boost::system::error_code ec =
+            setDbusProperty(ctx, connection, path, sensor::sensorInterface,
+                            "Value", ipmi::Value(*value));
+
+        // setDbusProperty intended to resolve dbus exception/rc within the
+        // function but failed to achieve that. Catch exception in the ipmi
+        // callback functions for now (e.g. ipmiSetSensorReading).
+        if (ec)
+        {
+            lg2::error("Failed to set Value, path: {PATH}, "
+                       "interface: {INTERFACE}, ERROR: {ERROR}",
+                       "PATH", path, "INTERFACE", sensor::sensorInterface,
+                       "ERROR", ec.message());
+            return ipmi::responseResponseError();
+        }
+        return ipmi::responseSuccess();
+    }
+
+    if (std::find(interfaces.begin(), interfaces.end(), sensor::vrInterface) !=
+        interfaces.end())
+    {
+        DbusInterfaceMap sensorMap;
+        if (!getSensorMap(ctx, connection, path, sensorMap))
+        {
+            return ipmi::responseResponseError();
+        }
+        auto sensorObject = sensorMap.find(sensor::vrInterface);
+        if (sensorObject == sensorMap.end())
+        {
+            return ipmi::responseResponseError();
+        }
+
+        // VR sensors are treated as a special case and we will not check the
+        // write permission for VR sensors, since they always deemed writable
+        // and permission table are not applied to VR sensors.
+        auto vrMode =
+            sensor::calculateVRMode(assertOffset, sensorObject->second);
+        if (!vrMode)
+        {
+            return ipmi::responseResponseError();
+        }
+        boost::system::error_code ec = setDbusProperty(
+            ctx, connection, path, sensor::vrInterface, "Selected", *vrMode);
+        // setDbusProperty intended to resolve dbus exception/rc within the
+        // function but failed to achieve that. Catch exception in the ipmi
+        // callback functions for now (e.g. ipmiSetSensorReading).
+        if (ec)
+        {
+            lg2::error("Failed to set Selected, path: {PATH}, "
+                       "interface: {INTERFACE}, ERROR: {ERROR}",
+                       "PATH", path, "INTERFACE", sensor::sensorInterface,
+                       "ERROR", ec.message());
+        }
+        return ipmi::responseSuccess();
+    }
+
+    lg2::error("unknown sensor type, path: {PATH}", "PATH", path);
+    return ipmi::responseResponseError();
+}
+
+ipmi::RspType<uint8_t, uint8_t, uint8_t, std::optional<uint8_t>>
+    ipmiSenGetSensorReading(ipmi::Context::ptr ctx, uint8_t sensnum)
+{
+    std::string connection;
+    std::string path;
+
+    if (sensnum == reservedSensorNumber)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    auto status = getSensorConnection(ctx, sensnum, connection, path);
+    if (status)
+    {
+        return ipmi::response(status);
+    }
+
+#ifdef FEATURE_HYBRID_SENSORS
+    if (auto sensor = findStaticSensor(path);
+        sensor != ipmi::sensor::sensors.end() &&
+        getSensorEventTypeFromPath(path) !=
+            static_cast<uint8_t>(SensorEventTypeCodes::threshold))
+    {
+        if (ipmi::sensor::Mutability::Read !=
+            (sensor->second.mutability & ipmi::sensor::Mutability::Read))
+        {
+            return ipmi::responseIllegalCommand();
+        }
+
+        uint8_t operation;
+        try
+        {
+            ipmi::sensor::GetSensorResponse getResponse =
+                sensor->second.getFunc(sensor->second);
+
+            if (getResponse.readingOrStateUnavailable)
+            {
+                operation |= static_cast<uint8_t>(
+                    IPMISensorReadingByte2::readingStateUnavailable);
+            }
+            if (getResponse.scanningEnabled)
+            {
+                operation |= static_cast<uint8_t>(
+                    IPMISensorReadingByte2::sensorScanningEnable);
+            }
+            if (getResponse.allEventMessagesEnabled)
+            {
+                operation |= static_cast<uint8_t>(
+                    IPMISensorReadingByte2::eventMessagesEnable);
+            }
+            return ipmi::responseSuccess(
+                getResponse.reading, operation,
+                getResponse.thresholdLevelsStates,
+                getResponse.discreteReadingSensorStates);
+        }
+        catch (const std::exception& e)
+        {
+            operation |= static_cast<uint8_t>(
+                IPMISensorReadingByte2::readingStateUnavailable);
+            return ipmi::responseSuccess(0, operation, 0, std::nullopt);
+        }
+    }
+#endif
+
+    DbusInterfaceMap sensorMap;
+    if (!getSensorMap(ctx, connection, path, sensorMap))
+    {
+        return ipmi::responseResponseError();
+    }
+    auto sensorObject = sensorMap.find(sensor::sensorInterface);
+
+    if (sensorObject == sensorMap.end() ||
+        sensorObject->second.find("Value") == sensorObject->second.end())
+    {
+        return ipmi::responseResponseError();
+    }
+    auto& valueVariant = sensorObject->second["Value"];
+    double reading = std::visit(VariantToDoubleVisitor(), valueVariant);
+
+    double max = 0;
+    double min = 0;
+    getSensorMaxMin(sensorMap, max, min);
+
+    int16_t mValue = 0;
+    int16_t bValue = 0;
+    int8_t rExp = 0;
+    int8_t bExp = 0;
+    bool bSigned = false;
+
+    if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
+    {
+        return ipmi::responseResponseError();
+    }
+
+    uint8_t value =
+        scaleIPMIValueFromDouble(reading, mValue, rExp, bValue, bExp, bSigned);
+    uint8_t operation =
+        static_cast<uint8_t>(IPMISensorReadingByte2::sensorScanningEnable);
+    operation |=
+        static_cast<uint8_t>(IPMISensorReadingByte2::eventMessagesEnable);
+    bool notReading = std::isnan(reading);
+
+    if (!notReading)
+    {
+        auto availableObject =
+            sensorMap.find("xyz.openbmc_project.State.Decorator.Availability");
+        if (availableObject != sensorMap.end())
+        {
+            auto findAvailable = availableObject->second.find("Available");
+            if (findAvailable != availableObject->second.end())
+            {
+                bool* available = std::get_if<bool>(&(findAvailable->second));
+                if (available && !(*available))
+                {
+                    notReading = true;
+                }
+            }
+        }
+    }
+
+    if (notReading)
+    {
+        operation |= static_cast<uint8_t>(
+            IPMISensorReadingByte2::readingStateUnavailable);
+    }
+
+    if constexpr (details::enableInstrumentation)
+    {
+        int byteValue;
+        if (bSigned)
+        {
+            byteValue = static_cast<int>(static_cast<int8_t>(value));
+        }
+        else
+        {
+            byteValue = static_cast<int>(static_cast<uint8_t>(value));
+        }
+
+        // Keep stats on the reading just obtained, even if it is "NaN"
+        if (details::sdrStatsTable.updateReading((ctx->lun << 8) | sensnum,
+                                                 reading, byteValue))
+        {
+            // This is the first reading, show the coefficients
+            double step = (max - min) / 255.0;
+            std::cerr
+                << "IPMI sensor "
+                << details::sdrStatsTable.getName((ctx->lun << 8) | sensnum)
+                << ": Range min=" << min << " max=" << max << ", step=" << step
+                << ", Coefficients mValue=" << static_cast<int>(mValue)
+                << " rExp=" << static_cast<int>(rExp)
+                << " bValue=" << static_cast<int>(bValue)
+                << " bExp=" << static_cast<int>(bExp)
+                << " bSigned=" << static_cast<int>(bSigned) << "\n";
+        }
+    }
+
+    uint8_t thresholds = 0;
+
+    auto warningObject =
+        sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
+    if (warningObject != sensorMap.end())
+    {
+        auto alarmHigh = warningObject->second.find("WarningAlarmHigh");
+        auto alarmLow = warningObject->second.find("WarningAlarmLow");
+        if (alarmHigh != warningObject->second.end())
+        {
+            if (std::get<bool>(alarmHigh->second))
+            {
+                thresholds |= static_cast<uint8_t>(
+                    IPMISensorReadingByte3::upperNonCritical);
+            }
+        }
+        if (alarmLow != warningObject->second.end())
+        {
+            if (std::get<bool>(alarmLow->second))
+            {
+                thresholds |= static_cast<uint8_t>(
+                    IPMISensorReadingByte3::lowerNonCritical);
+            }
+        }
+    }
+
+    auto criticalObject =
+        sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
+    if (criticalObject != sensorMap.end())
+    {
+        auto alarmHigh = criticalObject->second.find("CriticalAlarmHigh");
+        auto alarmLow = criticalObject->second.find("CriticalAlarmLow");
+        if (alarmHigh != criticalObject->second.end())
+        {
+            if (std::get<bool>(alarmHigh->second))
+            {
+                thresholds |=
+                    static_cast<uint8_t>(IPMISensorReadingByte3::upperCritical);
+            }
+        }
+        if (alarmLow != criticalObject->second.end())
+        {
+            if (std::get<bool>(alarmLow->second))
+            {
+                thresholds |=
+                    static_cast<uint8_t>(IPMISensorReadingByte3::lowerCritical);
+            }
+        }
+    }
+
+    // no discrete as of today so optional byte is never returned
+    return ipmi::responseSuccess(value, operation, thresholds, std::nullopt);
+}
+
+/** @brief implements the Set Sensor threshold command
+ *  @param sensorNumber        - sensor number
+ *  @param lowerNonCriticalThreshMask
+ *  @param lowerCriticalThreshMask
+ *  @param lowerNonRecovThreshMask
+ *  @param upperNonCriticalThreshMask
+ *  @param upperCriticalThreshMask
+ *  @param upperNonRecovThreshMask
+ *  @param reserved
+ *  @param lowerNonCritical    - lower non-critical threshold
+ *  @param lowerCritical       - Lower critical threshold
+ *  @param lowerNonRecoverable - Lower non recovarable threshold
+ *  @param upperNonCritical    - Upper non-critical threshold
+ *  @param upperCritical       - Upper critical
+ *  @param upperNonRecoverable - Upper Non-recoverable
+ *
+ *  @returns IPMI completion code
+ */
+ipmi::RspType<> ipmiSenSetSensorThresholds(
+    ipmi::Context::ptr ctx, uint8_t sensorNum, bool lowerNonCriticalThreshMask,
+    bool lowerCriticalThreshMask, bool lowerNonRecovThreshMask,
+    bool upperNonCriticalThreshMask, bool upperCriticalThreshMask,
+    bool upperNonRecovThreshMask, uint2_t reserved, uint8_t lowerNonCritical,
+    uint8_t lowerCritical, [[maybe_unused]] uint8_t lowerNonRecoverable,
+    uint8_t upperNonCritical, uint8_t upperCritical,
+    [[maybe_unused]] uint8_t upperNonRecoverable)
+{
+    if (sensorNum == reservedSensorNumber || reserved)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    // lower nc and upper nc not suppported on any sensor
+    if (lowerNonRecovThreshMask || upperNonRecovThreshMask)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    // if none of the threshold mask are set, nothing to do
+    if (!(lowerNonCriticalThreshMask | lowerCriticalThreshMask |
+          lowerNonRecovThreshMask | upperNonCriticalThreshMask |
+          upperCriticalThreshMask | upperNonRecovThreshMask))
+    {
+        return ipmi::responseSuccess();
+    }
+
+    std::string connection;
+    std::string path;
+
+    ipmi::Cc status = getSensorConnection(ctx, sensorNum, connection, path);
+    if (status)
+    {
+        return ipmi::response(status);
+    }
+    DbusInterfaceMap sensorMap;
+    if (!getSensorMap(ctx, connection, path, sensorMap))
+    {
+        return ipmi::responseResponseError();
+    }
+
+    double max = 0;
+    double min = 0;
+    getSensorMaxMin(sensorMap, max, min);
+
+    int16_t mValue = 0;
+    int16_t bValue = 0;
+    int8_t rExp = 0;
+    int8_t bExp = 0;
+    bool bSigned = false;
+
+    if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
+    {
+        return ipmi::responseResponseError();
+    }
+
+    // store a vector of property name, value to set, and interface
+    std::vector<std::tuple<std::string, uint8_t, std::string>> thresholdsToSet;
+
+    // define the indexes of the tuple
+    constexpr uint8_t propertyName = 0;
+    constexpr uint8_t thresholdValue = 1;
+    constexpr uint8_t interface = 2;
+    // verifiy all needed fields are present
+    if (lowerCriticalThreshMask || upperCriticalThreshMask)
+    {
+        auto findThreshold =
+            sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
+        if (findThreshold == sensorMap.end())
+        {
+            return ipmi::responseInvalidFieldRequest();
+        }
+        if (lowerCriticalThreshMask)
+        {
+            auto findLower = findThreshold->second.find("CriticalLow");
+            if (findLower == findThreshold->second.end())
+            {
+                return ipmi::responseInvalidFieldRequest();
+            }
+            thresholdsToSet.emplace_back("CriticalLow", lowerCritical,
+                                         findThreshold->first);
+        }
+        if (upperCriticalThreshMask)
+        {
+            auto findUpper = findThreshold->second.find("CriticalHigh");
+            if (findUpper == findThreshold->second.end())
+            {
+                return ipmi::responseInvalidFieldRequest();
+            }
+            thresholdsToSet.emplace_back("CriticalHigh", upperCritical,
+                                         findThreshold->first);
+        }
+    }
+    if (lowerNonCriticalThreshMask || upperNonCriticalThreshMask)
+    {
+        auto findThreshold =
+            sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
+        if (findThreshold == sensorMap.end())
+        {
+            return ipmi::responseInvalidFieldRequest();
+        }
+        if (lowerNonCriticalThreshMask)
+        {
+            auto findLower = findThreshold->second.find("WarningLow");
+            if (findLower == findThreshold->second.end())
+            {
+                return ipmi::responseInvalidFieldRequest();
+            }
+            thresholdsToSet.emplace_back("WarningLow", lowerNonCritical,
+                                         findThreshold->first);
+        }
+        if (upperNonCriticalThreshMask)
+        {
+            auto findUpper = findThreshold->second.find("WarningHigh");
+            if (findUpper == findThreshold->second.end())
+            {
+                return ipmi::responseInvalidFieldRequest();
+            }
+            thresholdsToSet.emplace_back("WarningHigh", upperNonCritical,
+                                         findThreshold->first);
+        }
+    }
+    for (const auto& property : thresholdsToSet)
+    {
+        // from section 36.3 in the IPMI Spec, assume all linear
+        double valueToSet = ((mValue * std::get<thresholdValue>(property)) +
+                             (bValue * std::pow(10.0, bExp))) *
+                            std::pow(10.0, rExp);
+        setDbusProperty(
+            *getSdBus(), connection, path, std::get<interface>(property),
+            std::get<propertyName>(property), ipmi::Value(valueToSet));
+    }
+    return ipmi::responseSuccess();
+}
+
+IPMIThresholds getIPMIThresholds(const DbusInterfaceMap& sensorMap)
+{
+    IPMIThresholds resp;
+    auto warningInterface =
+        sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
+    auto criticalInterface =
+        sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
+
+    if ((warningInterface != sensorMap.end()) ||
+        (criticalInterface != sensorMap.end()))
+    {
+        auto sensorPair = sensorMap.find(sensor::sensorInterface);
+
+        if (sensorPair == sensorMap.end())
+        {
+            // should not have been able to find a sensor not implementing
+            // the sensor object
+            throw std::runtime_error("Invalid sensor map");
+        }
+
+        double max = 0;
+        double min = 0;
+        getSensorMaxMin(sensorMap, max, min);
+
+        int16_t mValue = 0;
+        int16_t bValue = 0;
+        int8_t rExp = 0;
+        int8_t bExp = 0;
+        bool bSigned = false;
+
+        if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
+        {
+            throw std::runtime_error("Invalid sensor atrributes");
+        }
+        if (warningInterface != sensorMap.end())
+        {
+            auto& warningMap = warningInterface->second;
+
+            auto warningHigh = warningMap.find("WarningHigh");
+            auto warningLow = warningMap.find("WarningLow");
+
+            if (warningHigh != warningMap.end())
+            {
+                double value =
+                    std::visit(VariantToDoubleVisitor(), warningHigh->second);
+                if (std::isfinite(value))
+                {
+                    resp.warningHigh = scaleIPMIValueFromDouble(
+                        value, mValue, rExp, bValue, bExp, bSigned);
+                }
+            }
+            if (warningLow != warningMap.end())
+            {
+                double value =
+                    std::visit(VariantToDoubleVisitor(), warningLow->second);
+                if (std::isfinite(value))
+                {
+                    resp.warningLow = scaleIPMIValueFromDouble(
+                        value, mValue, rExp, bValue, bExp, bSigned);
+                }
+            }
+        }
+        if (criticalInterface != sensorMap.end())
+        {
+            auto& criticalMap = criticalInterface->second;
+
+            auto criticalHigh = criticalMap.find("CriticalHigh");
+            auto criticalLow = criticalMap.find("CriticalLow");
+
+            if (criticalHigh != criticalMap.end())
+            {
+                double value =
+                    std::visit(VariantToDoubleVisitor(), criticalHigh->second);
+                if (std::isfinite(value))
+                {
+                    resp.criticalHigh = scaleIPMIValueFromDouble(
+                        value, mValue, rExp, bValue, bExp, bSigned);
+                }
+            }
+            if (criticalLow != criticalMap.end())
+            {
+                double value =
+                    std::visit(VariantToDoubleVisitor(), criticalLow->second);
+                if (std::isfinite(value))
+                {
+                    resp.criticalLow = scaleIPMIValueFromDouble(
+                        value, mValue, rExp, bValue, bExp, bSigned);
+                }
+            }
+        }
+    }
+    return resp;
+}
+
+ipmi::RspType<uint8_t, // readable
+              uint8_t, // lowerNCrit
+              uint8_t, // lowerCrit
+              uint8_t, // lowerNrecoverable
+              uint8_t, // upperNC
+              uint8_t, // upperCrit
+              uint8_t> // upperNRecoverable
+    ipmiSenGetSensorThresholds(ipmi::Context::ptr ctx, uint8_t sensorNumber)
+{
+    std::string connection;
+    std::string path;
+
+    if (sensorNumber == reservedSensorNumber)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    auto status = getSensorConnection(ctx, sensorNumber, connection, path);
+    if (status)
+    {
+        return ipmi::response(status);
+    }
+
+    DbusInterfaceMap sensorMap;
+    if (!getSensorMap(ctx, connection, path, sensorMap))
+    {
+        return ipmi::responseResponseError();
+    }
+
+    IPMIThresholds thresholdData;
+    try
+    {
+        thresholdData = getIPMIThresholds(sensorMap);
+    }
+    catch (const std::exception&)
+    {
+        return ipmi::responseResponseError();
+    }
+
+    uint8_t readable = 0;
+    uint8_t lowerNC = 0;
+    uint8_t lowerCritical = 0;
+    uint8_t lowerNonRecoverable = 0;
+    uint8_t upperNC = 0;
+    uint8_t upperCritical = 0;
+    uint8_t upperNonRecoverable = 0;
+
+    if (thresholdData.warningHigh)
+    {
+        readable |=
+            1 << static_cast<uint8_t>(IPMIThresholdRespBits::upperNonCritical);
+        upperNC = *thresholdData.warningHigh;
+    }
+    if (thresholdData.warningLow)
+    {
+        readable |=
+            1 << static_cast<uint8_t>(IPMIThresholdRespBits::lowerNonCritical);
+        lowerNC = *thresholdData.warningLow;
+    }
+
+    if (thresholdData.criticalHigh)
+    {
+        readable |=
+            1 << static_cast<uint8_t>(IPMIThresholdRespBits::upperCritical);
+        upperCritical = *thresholdData.criticalHigh;
+    }
+    if (thresholdData.criticalLow)
+    {
+        readable |=
+            1 << static_cast<uint8_t>(IPMIThresholdRespBits::lowerCritical);
+        lowerCritical = *thresholdData.criticalLow;
+    }
+
+    return ipmi::responseSuccess(readable, lowerNC, lowerCritical,
+                                 lowerNonRecoverable, upperNC, upperCritical,
+                                 upperNonRecoverable);
+}
+
+/** @brief implements the get Sensor event enable command
+ *  @param sensorNumber - sensor number
+ *
+ *  @returns IPMI completion code plus response data
+ *   - enabled               - Sensor Event messages
+ *   - assertionEnabledLsb   - Assertion event messages
+ *   - assertionEnabledMsb   - Assertion event messages
+ *   - deassertionEnabledLsb - Deassertion event messages
+ *   - deassertionEnabledMsb - Deassertion event messages
+ */
+
+ipmi::RspType<uint8_t, // enabled
+              uint8_t, // assertionEnabledLsb
+              uint8_t, // assertionEnabledMsb
+              uint8_t, // deassertionEnabledLsb
+              uint8_t> // deassertionEnabledMsb
+    ipmiSenGetSensorEventEnable(ipmi::Context::ptr ctx, uint8_t sensorNum)
+{
+    std::string connection;
+    std::string path;
+
+    uint8_t enabled = 0;
+    uint8_t assertionEnabledLsb = 0;
+    uint8_t assertionEnabledMsb = 0;
+    uint8_t deassertionEnabledLsb = 0;
+    uint8_t deassertionEnabledMsb = 0;
+
+    if (sensorNum == reservedSensorNumber)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    auto status = getSensorConnection(ctx, sensorNum, connection, path);
+    if (status)
+    {
+        return ipmi::response(status);
+    }
+
+#ifdef FEATURE_HYBRID_SENSORS
+    if (auto sensor = findStaticSensor(path);
+        sensor != ipmi::sensor::sensors.end() &&
+        getSensorEventTypeFromPath(path) !=
+            static_cast<uint8_t>(SensorEventTypeCodes::threshold))
+    {
+        enabled = static_cast<uint8_t>(
+            IPMISensorEventEnableByte2::sensorScanningEnable);
+        uint16_t assertionEnabled = 0;
+        for (auto& offsetValMap : sensor->second.propertyInterfaces.begin()
+                                      ->second.begin()
+                                      ->second.second)
+        {
+            assertionEnabled |= (1 << offsetValMap.first);
+        }
+        assertionEnabledLsb = static_cast<uint8_t>((assertionEnabled & 0xFF));
+        assertionEnabledMsb =
+            static_cast<uint8_t>(((assertionEnabled >> 8) & 0xFF));
+
+        return ipmi::responseSuccess(enabled, assertionEnabledLsb,
+                                     assertionEnabledMsb, deassertionEnabledLsb,
+                                     deassertionEnabledMsb);
+    }
+#endif
+
+    DbusInterfaceMap sensorMap;
+    if (!getSensorMap(ctx, connection, path, sensorMap))
+    {
+        return ipmi::responseResponseError();
+    }
+
+    auto warningInterface =
+        sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
+    auto criticalInterface =
+        sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
+    if ((warningInterface != sensorMap.end()) ||
+        (criticalInterface != sensorMap.end()))
+    {
+        enabled = static_cast<uint8_t>(
+            IPMISensorEventEnableByte2::sensorScanningEnable);
+        if (warningInterface != sensorMap.end())
+        {
+            auto& warningMap = warningInterface->second;
+
+            auto warningHigh = warningMap.find("WarningHigh");
+            auto warningLow = warningMap.find("WarningLow");
+            if (warningHigh != warningMap.end())
+            {
+                double value =
+                    std::visit(VariantToDoubleVisitor(), warningHigh->second);
+                if (std::isfinite(value))
+                {
+                    assertionEnabledLsb |= static_cast<uint8_t>(
+                        IPMISensorEventEnableThresholds::
+                            upperNonCriticalGoingHigh);
+                    deassertionEnabledLsb |= static_cast<uint8_t>(
+                        IPMISensorEventEnableThresholds::
+                            upperNonCriticalGoingLow);
+                }
+            }
+            if (warningLow != warningMap.end())
+            {
+                double value =
+                    std::visit(VariantToDoubleVisitor(), warningLow->second);
+                if (std::isfinite(value))
+                {
+                    assertionEnabledLsb |= static_cast<uint8_t>(
+                        IPMISensorEventEnableThresholds::
+                            lowerNonCriticalGoingLow);
+                    deassertionEnabledLsb |= static_cast<uint8_t>(
+                        IPMISensorEventEnableThresholds::
+                            lowerNonCriticalGoingHigh);
+                }
+            }
+        }
+        if (criticalInterface != sensorMap.end())
+        {
+            auto& criticalMap = criticalInterface->second;
+
+            auto criticalHigh = criticalMap.find("CriticalHigh");
+            auto criticalLow = criticalMap.find("CriticalLow");
+
+            if (criticalHigh != criticalMap.end())
+            {
+                double value =
+                    std::visit(VariantToDoubleVisitor(), criticalHigh->second);
+                if (std::isfinite(value))
+                {
+                    assertionEnabledMsb |= static_cast<uint8_t>(
+                        IPMISensorEventEnableThresholds::
+                            upperCriticalGoingHigh);
+                    deassertionEnabledMsb |= static_cast<uint8_t>(
+                        IPMISensorEventEnableThresholds::upperCriticalGoingLow);
+                }
+            }
+            if (criticalLow != criticalMap.end())
+            {
+                double value =
+                    std::visit(VariantToDoubleVisitor(), criticalLow->second);
+                if (std::isfinite(value))
+                {
+                    assertionEnabledLsb |= static_cast<uint8_t>(
+                        IPMISensorEventEnableThresholds::lowerCriticalGoingLow);
+                    deassertionEnabledLsb |= static_cast<uint8_t>(
+                        IPMISensorEventEnableThresholds::
+                            lowerCriticalGoingHigh);
+                }
+            }
+        }
+    }
+
+    return ipmi::responseSuccess(enabled, assertionEnabledLsb,
+                                 assertionEnabledMsb, deassertionEnabledLsb,
+                                 deassertionEnabledMsb);
+}
+
+/** @brief implements the get Sensor event status command
+ *  @param sensorNumber - sensor number, FFh = reserved
+ *
+ *  @returns IPMI completion code plus response data
+ *   - sensorEventStatus - Sensor Event messages state
+ *   - assertions        - Assertion event messages
+ *   - deassertions      - Deassertion event messages
+ */
+ipmi::RspType<uint8_t,         // sensorEventStatus
+              std::bitset<16>, // assertions
+              std::bitset<16>  // deassertion
+              >
+    ipmiSenGetSensorEventStatus(ipmi::Context::ptr ctx, uint8_t sensorNum)
+{
+    if (sensorNum == reservedSensorNumber)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    std::string connection;
+    std::string path;
+    auto status = getSensorConnection(ctx, sensorNum, connection, path);
+    if (status)
+    {
+        lg2::error("ipmiSenGetSensorEventStatus: Sensor connection Error, "
+                   "sensor number: {SENSOR_NUM}",
+                   "SENSOR_NUM", sensorNum);
+        return ipmi::response(status);
+    }
+
+#ifdef FEATURE_HYBRID_SENSORS
+    if (auto sensor = findStaticSensor(path);
+        sensor != ipmi::sensor::sensors.end() &&
+        getSensorEventTypeFromPath(path) !=
+            static_cast<uint8_t>(SensorEventTypeCodes::threshold))
+    {
+        auto response = ipmi::sensor::get::mapDbusToAssertion(
+            sensor->second, path, sensor->second.sensorInterface);
+        std::bitset<16> assertions;
+        // deassertions are not used.
+        std::bitset<16> deassertions = 0;
+        uint8_t sensorEventStatus;
+        if (response.readingOrStateUnavailable)
+        {
+            sensorEventStatus |= static_cast<uint8_t>(
+                IPMISensorReadingByte2::readingStateUnavailable);
+        }
+        if (response.scanningEnabled)
+        {
+            sensorEventStatus |= static_cast<uint8_t>(
+                IPMISensorReadingByte2::sensorScanningEnable);
+        }
+        if (response.allEventMessagesEnabled)
+        {
+            sensorEventStatus |= static_cast<uint8_t>(
+                IPMISensorReadingByte2::eventMessagesEnable);
+        }
+        assertions |= response.discreteReadingSensorStates << 8;
+        assertions |= response.thresholdLevelsStates;
+        return ipmi::responseSuccess(sensorEventStatus, assertions,
+                                     deassertions);
+    }
+#endif
+
+    DbusInterfaceMap sensorMap;
+    if (!getSensorMap(ctx, connection, path, sensorMap))
+    {
+        lg2::error("ipmiSenGetSensorEventStatus: Sensor Mapping Error, "
+                   "sensor path: {SENSOR_PATH}",
+                   "SENSOR_PATH", path);
+        return ipmi::responseResponseError();
+    }
+
+    uint8_t sensorEventStatus =
+        static_cast<uint8_t>(IPMISensorEventEnableByte2::sensorScanningEnable);
+    std::bitset<16> assertions = 0;
+    std::bitset<16> deassertions = 0;
+
+    // handle VR typed sensor
+    auto vrInterface = sensorMap.find(sensor::vrInterface);
+    if (vrInterface != sensorMap.end())
+    {
+        if (!sensor::getVrEventStatus(ctx, connection, path,
+                                      vrInterface->second, assertions))
+        {
+            return ipmi::responseResponseError();
+        }
+
+        // both Event Message and Sensor Scanning are disable for VR.
+        sensorEventStatus = 0;
+        return ipmi::responseSuccess(sensorEventStatus, assertions,
+                                     deassertions);
+    }
+
+    auto warningInterface =
+        sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
+    auto criticalInterface =
+        sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
+
+    std::optional<bool> criticalDeassertHigh =
+        thresholdDeassertMap[path]["CriticalAlarmHigh"];
+    std::optional<bool> criticalDeassertLow =
+        thresholdDeassertMap[path]["CriticalAlarmLow"];
+    std::optional<bool> warningDeassertHigh =
+        thresholdDeassertMap[path]["WarningAlarmHigh"];
+    std::optional<bool> warningDeassertLow =
+        thresholdDeassertMap[path]["WarningAlarmLow"];
+
+    if (criticalDeassertHigh && !*criticalDeassertHigh)
+    {
+        deassertions.set(static_cast<size_t>(
+            IPMIGetSensorEventEnableThresholds::upperCriticalGoingHigh));
+    }
+    if (criticalDeassertLow && !*criticalDeassertLow)
+    {
+        deassertions.set(static_cast<size_t>(
+            IPMIGetSensorEventEnableThresholds::upperCriticalGoingLow));
+    }
+    if (warningDeassertHigh && !*warningDeassertHigh)
+    {
+        deassertions.set(static_cast<size_t>(
+            IPMIGetSensorEventEnableThresholds::upperNonCriticalGoingHigh));
+    }
+    if (warningDeassertLow && !*warningDeassertLow)
+    {
+        deassertions.set(static_cast<size_t>(
+            IPMIGetSensorEventEnableThresholds::lowerNonCriticalGoingHigh));
+    }
+    if ((warningInterface != sensorMap.end()) ||
+        (criticalInterface != sensorMap.end()))
+    {
+        sensorEventStatus = static_cast<size_t>(
+            IPMISensorEventEnableByte2::eventMessagesEnable);
+        if (warningInterface != sensorMap.end())
+        {
+            auto& warningMap = warningInterface->second;
+
+            auto warningHigh = warningMap.find("WarningAlarmHigh");
+            auto warningLow = warningMap.find("WarningAlarmLow");
+            auto warningHighAlarm = false;
+            auto warningLowAlarm = false;
+
+            if (warningHigh != warningMap.end())
+            {
+                warningHighAlarm = std::get<bool>(warningHigh->second);
+            }
+            if (warningLow != warningMap.end())
+            {
+                warningLowAlarm = std::get<bool>(warningLow->second);
+            }
+            if (warningHighAlarm)
+            {
+                assertions.set(static_cast<size_t>(
+                    IPMIGetSensorEventEnableThresholds::
+                        upperNonCriticalGoingHigh));
+            }
+            if (warningLowAlarm)
+            {
+                assertions.set(static_cast<size_t>(
+                    IPMIGetSensorEventEnableThresholds::
+                        lowerNonCriticalGoingLow));
+            }
+        }
+        if (criticalInterface != sensorMap.end())
+        {
+            auto& criticalMap = criticalInterface->second;
+
+            auto criticalHigh = criticalMap.find("CriticalAlarmHigh");
+            auto criticalLow = criticalMap.find("CriticalAlarmLow");
+            auto criticalHighAlarm = false;
+            auto criticalLowAlarm = false;
+
+            if (criticalHigh != criticalMap.end())
+            {
+                criticalHighAlarm = std::get<bool>(criticalHigh->second);
+            }
+            if (criticalLow != criticalMap.end())
+            {
+                criticalLowAlarm = std::get<bool>(criticalLow->second);
+            }
+            if (criticalHighAlarm)
+            {
+                assertions.set(static_cast<size_t>(
+                    IPMIGetSensorEventEnableThresholds::
+                        upperCriticalGoingHigh));
+            }
+            if (criticalLowAlarm)
+            {
+                assertions.set(static_cast<size_t>(
+                    IPMIGetSensorEventEnableThresholds::lowerCriticalGoingLow));
+            }
+        }
+    }
+
+    return ipmi::responseSuccess(sensorEventStatus, assertions, deassertions);
+}
+
+// Construct a type 1 SDR for threshold sensor.
+void constructSensorSdrHeaderKey(uint16_t sensorNum, uint16_t recordID,
+                                 get_sdr::SensorDataFullRecord& record)
+{
+    get_sdr::header::set_record_id(
+        recordID, reinterpret_cast<get_sdr::SensorDataRecordHeader*>(&record));
+
+    uint8_t sensornumber = static_cast<uint8_t>(sensorNum);
+    uint8_t lun = static_cast<uint8_t>(sensorNum >> 8);
+
+    record.header.sdr_version = ipmiSdrVersion;
+    record.header.record_type = get_sdr::SENSOR_DATA_FULL_RECORD;
+    record.header.record_length = sizeof(get_sdr::SensorDataFullRecord) -
+                                  sizeof(get_sdr::SensorDataRecordHeader);
+    record.key.owner_id = bmcI2CAddr;
+    record.key.owner_lun = lun;
+    record.key.sensor_number = sensornumber;
+}
+bool constructSensorSdr(
+    ipmi::Context::ptr ctx,
+    const std::unordered_set<std::string>& ipmiDecoratorPaths,
+    uint16_t sensorNum, uint16_t recordID, const std::string& service,
+    const std::string& path, get_sdr::SensorDataFullRecord& record)
+{
+    constructSensorSdrHeaderKey(sensorNum, recordID, record);
+
+    DbusInterfaceMap sensorMap;
+    if (!getSensorMap(ctx, service, path, sensorMap, sensorMapSdrUpdatePeriod))
+    {
+        lg2::error("Failed to update sensor map for threshold sensor, "
+                   "service: {SERVICE}, path: {PATH}",
+                   "SERVICE", service, "PATH", path);
+        return false;
+    }
+
+    record.body.sensor_capabilities = 0x68; // auto rearm - todo hysteresis
+    record.body.sensor_type = getSensorTypeFromPath(path);
+    std::string type = getSensorTypeStringFromPath(path);
+    auto typeCstr = type.c_str();
+    auto findUnits = sensorUnits.find(typeCstr);
+    if (findUnits != sensorUnits.end())
+    {
+        record.body.sensor_units_2_base =
+            static_cast<uint8_t>(findUnits->second);
+    } // else default 0x0 unspecified
+
+    record.body.event_reading_type = getSensorEventTypeFromPath(path);
+
+    auto sensorObject = sensorMap.find(sensor::sensorInterface);
+    if (sensorObject == sensorMap.end())
+    {
+        lg2::error("constructSensorSdr: sensorObject error");
+        return false;
+    }
+
+    uint8_t entityId = 0;
+    uint8_t entityInstance = 0x01;
+
+    // follow the association chain to get the parent board's entityid and
+    // entityInstance
+    updateIpmiFromAssociation(path, ipmiDecoratorPaths, sensorMap, entityId,
+                              entityInstance);
+
+    record.body.entity_id = entityId;
+    record.body.entity_instance = entityInstance;
+
+    double max = 0;
+    double min = 0;
+    getSensorMaxMin(sensorMap, max, min);
+
+    int16_t mValue = 0;
+    int8_t rExp = 0;
+    int16_t bValue = 0;
+    int8_t bExp = 0;
+    bool bSigned = false;
+
+    if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
+    {
+        lg2::error("constructSensorSdr: getSensorAttributes error");
+        return false;
+    }
+
+    // The record.body is a struct SensorDataFullRecordBody
+    // from sensorhandler.hpp in phosphor-ipmi-host.
+    // The meaning of these bits appears to come from
+    // table 43.1 of the IPMI spec.
+    // The above 5 sensor attributes are stuffed in as follows:
+    // Byte 21 = AA000000 = analog interpretation, 10 signed, 00 unsigned
+    // Byte 22-24 are for other purposes
+    // Byte 25 = MMMMMMMM = LSB of M
+    // Byte 26 = MMTTTTTT = MSB of M (signed), and Tolerance
+    // Byte 27 = BBBBBBBB = LSB of B
+    // Byte 28 = BBAAAAAA = MSB of B (signed), and LSB of Accuracy
+    // Byte 29 = AAAAEE00 = MSB of Accuracy, exponent of Accuracy
+    // Byte 30 = RRRRBBBB = rExp (signed), bExp (signed)
+
+    // apply M, B, and exponents, M and B are 10 bit values, exponents are 4
+    record.body.m_lsb = mValue & 0xFF;
+
+    uint8_t mBitSign = (mValue < 0) ? 1 : 0;
+    uint8_t mBitNine = (mValue & 0x0100) >> 8;
+
+    // move the smallest bit of the MSB into place (bit 9)
+    // the MSbs are bits 7:8 in m_msb_and_tolerance
+    record.body.m_msb_and_tolerance = (mBitSign << 7) | (mBitNine << 6);
+
+    record.body.b_lsb = bValue & 0xFF;
+
+    uint8_t bBitSign = (bValue < 0) ? 1 : 0;
+    uint8_t bBitNine = (bValue & 0x0100) >> 8;
+
+    // move the smallest bit of the MSB into place (bit 9)
+    // the MSbs are bits 7:8 in b_msb_and_accuracy_lsb
+    record.body.b_msb_and_accuracy_lsb = (bBitSign << 7) | (bBitNine << 6);
+
+    uint8_t rExpSign = (rExp < 0) ? 1 : 0;
+    uint8_t rExpBits = rExp & 0x07;
+
+    uint8_t bExpSign = (bExp < 0) ? 1 : 0;
+    uint8_t bExpBits = bExp & 0x07;
+
+    // move rExp and bExp into place
+    record.body.r_b_exponents =
+        (rExpSign << 7) | (rExpBits << 4) | (bExpSign << 3) | bExpBits;
+
+    // Set the analog reading byte interpretation accordingly
+    record.body.sensor_units_1 = (bSigned ? 1 : 0) << 7;
+
+    // TODO(): Perhaps care about Tolerance, Accuracy, and so on
+    // These seem redundant, but derivable from the above 5 attributes
+    // Original comment said "todo fill out rest of units"
+
+    // populate sensor name from path
+    auto name = sensor::parseSdrIdFromPath(path);
+    get_sdr::body::set_id_strlen(name.size(), &record.body);
+    get_sdr::body::set_id_type(3, &record.body); // "8-bit ASCII + Latin 1"
+    std::memcpy(record.body.id_string, name.c_str(),
+                std::min(name.length() + 1, sizeof(record.body.id_string)));
+
+    // Remember the sensor name, as determined for this sensor number
+    details::sdrStatsTable.updateName(sensorNum, name);
+
+    bool sensorSettable = false;
+    auto mutability =
+        sensorMap.find("xyz.openbmc_project.Sensor.ValueMutability");
+    if (mutability != sensorMap.end())
+    {
+        sensorSettable =
+            mappedVariant<bool>(mutability->second, "Mutable", false);
+    }
+    get_sdr::body::init_settable_state(sensorSettable, &record.body);
+
+    // Grant write permission to sensors deemed externally settable
+    details::sdrWriteTable.setWritePermission(sensorNum, sensorSettable);
+
+    IPMIThresholds thresholdData;
+    try
+    {
+        thresholdData = getIPMIThresholds(sensorMap);
+    }
+    catch (const std::exception&)
+    {
+        lg2::error("constructSensorSdr: getIPMIThresholds error");
+        return false;
+    }
+
+    if (thresholdData.criticalHigh)
+    {
+        record.body.upper_critical_threshold = *thresholdData.criticalHigh;
+        record.body.supported_deassertions[1] |= static_cast<uint8_t>(
+            IPMISensorEventEnableThresholds::criticalThreshold);
+        record.body.supported_deassertions[1] |= static_cast<uint8_t>(
+            IPMISensorEventEnableThresholds::upperCriticalGoingHigh);
+        record.body.supported_assertions[1] |= static_cast<uint8_t>(
+            IPMISensorEventEnableThresholds::upperCriticalGoingHigh);
+        record.body.discrete_reading_setting_mask[0] |=
+            static_cast<uint8_t>(IPMISensorReadingByte3::upperCritical);
+    }
+    if (thresholdData.warningHigh)
+    {
+        record.body.upper_noncritical_threshold = *thresholdData.warningHigh;
+        record.body.supported_deassertions[1] |= static_cast<uint8_t>(
+            IPMISensorEventEnableThresholds::nonCriticalThreshold);
+        record.body.supported_deassertions[0] |= static_cast<uint8_t>(
+            IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh);
+        record.body.supported_assertions[0] |= static_cast<uint8_t>(
+            IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh);
+        record.body.discrete_reading_setting_mask[0] |=
+            static_cast<uint8_t>(IPMISensorReadingByte3::upperNonCritical);
+    }
+    if (thresholdData.criticalLow)
+    {
+        record.body.lower_critical_threshold = *thresholdData.criticalLow;
+        record.body.supported_assertions[1] |= static_cast<uint8_t>(
+            IPMISensorEventEnableThresholds::criticalThreshold);
+        record.body.supported_deassertions[0] |= static_cast<uint8_t>(
+            IPMISensorEventEnableThresholds::lowerCriticalGoingLow);
+        record.body.supported_assertions[0] |= static_cast<uint8_t>(
+            IPMISensorEventEnableThresholds::lowerCriticalGoingLow);
+        record.body.discrete_reading_setting_mask[0] |=
+            static_cast<uint8_t>(IPMISensorReadingByte3::lowerCritical);
+    }
+    if (thresholdData.warningLow)
+    {
+        record.body.lower_noncritical_threshold = *thresholdData.warningLow;
+        record.body.supported_assertions[1] |= static_cast<uint8_t>(
+            IPMISensorEventEnableThresholds::nonCriticalThreshold);
+        record.body.supported_deassertions[0] |= static_cast<uint8_t>(
+            IPMISensorEventEnableThresholds::lowerNonCriticalGoingLow);
+        record.body.supported_assertions[0] |= static_cast<uint8_t>(
+            IPMISensorEventEnableThresholds::lowerNonCriticalGoingLow);
+        record.body.discrete_reading_setting_mask[0] |=
+            static_cast<uint8_t>(IPMISensorReadingByte3::lowerNonCritical);
+    }
+
+    // everything that is readable is setable
+    record.body.discrete_reading_setting_mask[1] =
+        record.body.discrete_reading_setting_mask[0];
+    return true;
+}
+
+#ifdef FEATURE_HYBRID_SENSORS
+// Construct a type 1 SDR for discrete Sensor typed sensor.
+void constructStaticSensorSdr(ipmi::Context::ptr, uint16_t sensorNum,
+                              uint16_t recordID,
+                              ipmi::sensor::IdInfoMap::const_iterator sensor,
+                              get_sdr::SensorDataFullRecord& record)
+{
+    constructSensorSdrHeaderKey(sensorNum, recordID, record);
+
+    record.body.entity_id = sensor->second.entityType;
+    record.body.sensor_type = sensor->second.sensorType;
+    record.body.event_reading_type = sensor->second.sensorReadingType;
+    record.body.entity_instance = sensor->second.instance;
+    if (ipmi::sensor::Mutability::Write ==
+        (sensor->second.mutability & ipmi::sensor::Mutability::Write))
+    {
+        get_sdr::body::init_settable_state(true, &(record.body));
+    }
+
+    auto id_string = sensor->second.sensorName;
+
+    if (id_string.empty())
+    {
+        id_string = sensor->second.sensorNameFunc(sensor->second);
+    }
+
+    if (id_string.length() > FULL_RECORD_ID_STR_MAX_LENGTH)
+    {
+        get_sdr::body::set_id_strlen(FULL_RECORD_ID_STR_MAX_LENGTH,
+                                     &(record.body));
+    }
+    else
+    {
+        get_sdr::body::set_id_strlen(id_string.length(), &(record.body));
+    }
+    get_sdr::body::set_id_type(3, &record.body); // "8-bit ASCII + Latin 1"
+    std::strncpy(record.body.id_string, id_string.c_str(),
+                 get_sdr::body::get_id_strlen(&(record.body)));
+}
+#endif
+
+// Construct type 3 SDR header and key (for VR and other discrete sensors)
+void constructEventSdrHeaderKey(uint16_t sensorNum, uint16_t recordID,
+                                get_sdr::SensorDataEventRecord& record)
+{
+    uint8_t sensornumber = static_cast<uint8_t>(sensorNum);
+    uint8_t lun = static_cast<uint8_t>(sensorNum >> 8);
+
+    get_sdr::header::set_record_id(
+        recordID, reinterpret_cast<get_sdr::SensorDataRecordHeader*>(&record));
+
+    record.header.sdr_version = ipmiSdrVersion;
+    record.header.record_type = get_sdr::SENSOR_DATA_EVENT_RECORD;
+    record.header.record_length = sizeof(get_sdr::SensorDataEventRecord) -
+                                  sizeof(get_sdr::SensorDataRecordHeader);
+    record.key.owner_id = bmcI2CAddr;
+    record.key.owner_lun = lun;
+    record.key.sensor_number = sensornumber;
+
+    record.body.entity_id = 0x00;
+    record.body.entity_instance = 0x01;
+}
+
+// Construct a type 3 SDR for VR typed sensor(daemon).
+bool constructVrSdr(ipmi::Context::ptr ctx,
+                    const std::unordered_set<std::string>& ipmiDecoratorPaths,
+                    uint16_t sensorNum, uint16_t recordID,
+                    const std::string& service, const std::string& path,
+                    get_sdr::SensorDataEventRecord& record)
+{
+    constructEventSdrHeaderKey(sensorNum, recordID, record);
+
+    DbusInterfaceMap sensorMap;
+    if (!getSensorMap(ctx, service, path, sensorMap, sensorMapSdrUpdatePeriod))
+    {
+        lg2::error("Failed to update sensor map for VR sensor, "
+                   "service: {SERVICE}, path: {PATH}",
+                   "SERVICE", service, "PATH", path);
+        return false;
+    }
+    // follow the association chain to get the parent board's entityid and
+    // entityInstance
+    updateIpmiFromAssociation(path, ipmiDecoratorPaths, sensorMap,
+                              record.body.entity_id,
+                              record.body.entity_instance);
+
+    // Sensor type is hardcoded as a module/board type instead of parsing from
+    // sensor path. This is because VR control is allocated in an independent
+    // path(/xyz/openbmc_project/vr/profile/...) which is not categorized by
+    // types.
+    static constexpr const uint8_t module_board_type = 0x15;
+    record.body.sensor_type = module_board_type;
+    record.body.event_reading_type = 0x00;
+
+    record.body.sensor_record_sharing_1 = 0x00;
+    record.body.sensor_record_sharing_2 = 0x00;
+
+    // populate sensor name from path
+    auto name = sensor::parseSdrIdFromPath(path);
+    int nameSize = std::min(name.size(), sizeof(record.body.id_string));
+    get_sdr::body::set_id_strlen(nameSize, &record.body);
+    get_sdr::body::set_id_type(3, &record.body); // "8-bit ASCII + Latin 1"
+    std::memset(record.body.id_string, 0x00, sizeof(record.body.id_string));
+    std::memcpy(record.body.id_string, name.c_str(), nameSize);
+
+    // Remember the sensor name, as determined for this sensor number
+    details::sdrStatsTable.updateName(sensorNum, name);
+
+    return true;
+}
+
+uint16_t getNumberOfSensors()
+{
+    return std::min(getSensorTree().size(), maxIPMISensors);
+}
+
+static int getSensorDataRecord(
+    ipmi::Context::ptr ctx,
+    const std::unordered_set<std::string>& ipmiDecoratorPaths,
+    std::vector<uint8_t>& recordData, uint16_t recordID,
+    uint8_t readBytes = std::numeric_limits<uint8_t>::max())
+{
+    recordData.clear();
+    size_t lastRecord = ipmi::getNumberOfSensors() +
+                        ipmi::sensor::getOtherSensorsCount(ctx) - 1;
+    uint16_t nextRecord(recordID + 1);
+
+    if (recordID == lastRecordIndex)
+    {
+        recordID = lastRecord;
+    }
+    if (recordID == lastRecord)
+    {
+        nextRecord = lastRecordIndex;
+    }
+    if (recordID > lastRecord)
+    {
+        lg2::error("getSensorDataRecord: recordID > lastRecord error");
+        return GENERAL_ERROR;
+    }
+    if (recordID >= ipmi::getNumberOfSensors())
+    {
+        if (auto err = ipmi::sensor::getOtherSensorsDataRecord(ctx, recordID,
+                                                               recordData);
+            err < 0)
+        {
+            return lastRecordIndex;
+        }
+        return nextRecord;
+    }
+
+    // Perform a incremental scan of the SDR Record ID's and translate the
+    // first 765 SDR records (i.e. maxIPMISensors) into IPMI Sensor
+    // Numbers. The IPMI sensor numbers are not linear, and have a reserved
+    // gap at 0xff. This code creates 254 sensors per LUN, excepting LUN 2
+    // which has special meaning.
+    std::string connection;
+    std::string path;
+    std::vector<std::string> interfaces;
+    uint16_t sensNumFromRecID{recordID};
+    if ((recordID > lun0MaxSensorNum) && (recordID < lun1MaxSensorNum))
+    {
+        // LUN 0 has one reserved sensor number. Compensate here by adding one
+        // to the record ID
+        sensNumFromRecID = recordID + 1;
+        ctx->lun = lun1;
+    }
+    else if ((recordID >= lun1MaxSensorNum) && (recordID < maxIPMISensors))
+    {
+        // LUN 0, 1 have a reserved sensor number. Compensate here by adding 2
+        // to the record ID. Skip all 256 sensors in LUN 2, as it has special
+        // rules governing its use.
+        sensNumFromRecID = recordID + (maxSensorsPerLUN + 1) + 2;
+        ctx->lun = lun3;
+    }
+
+    auto status =
+        getSensorConnection(ctx, static_cast<uint8_t>(sensNumFromRecID),
+                            connection, path, &interfaces);
+    if (status)
+    {
+        lg2::error("getSensorDataRecord: getSensorConnection error");
+        return GENERAL_ERROR;
+    }
+    uint16_t sensorNum = getSensorNumberFromPath(path);
+    // Return an error on LUN 2 assingments, and any sensor number beyond the
+    // range of LUN 3
+    if (((sensorNum > lun1MaxSensorNum) && (sensorNum <= maxIPMISensors)) ||
+        (sensorNum > lun3MaxSensorNum))
+    {
+        lg2::error("getSensorDataRecord: invalidSensorNumber");
+        return GENERAL_ERROR;
+    }
+    uint8_t sensornumber = static_cast<uint8_t>(sensorNum);
+    uint8_t lun = static_cast<uint8_t>(sensorNum >> 8);
+
+    if ((sensornumber != static_cast<uint8_t>(sensNumFromRecID)) &&
+        (lun != ctx->lun))
+    {
+        lg2::error("getSensorDataRecord: sensor record mismatch");
+        return GENERAL_ERROR;
+    }
+
+    // Construct full record (SDR type 1) for the threshold sensors
+    if (std::find(interfaces.begin(), interfaces.end(),
+                  sensor::sensorInterface) != interfaces.end())
+    {
+        get_sdr::SensorDataFullRecord record = {};
+
+        // If the request doesn't read SDR body, construct only header and key
+        // part to avoid additional DBus transaction.
+        if (readBytes <= sizeof(record.header) + sizeof(record.key))
+        {
+            constructSensorSdrHeaderKey(sensorNum, recordID, record);
+        }
+        else if (!constructSensorSdr(ctx, ipmiDecoratorPaths, sensorNum,
+                                     recordID, connection, path, record))
+        {
+            return GENERAL_ERROR;
+        }
+
+        recordData.insert(recordData.end(), reinterpret_cast<uint8_t*>(&record),
+                          reinterpret_cast<uint8_t*>(&record) + sizeof(record));
+
+        return nextRecord;
+    }
+
+#ifdef FEATURE_HYBRID_SENSORS
+    if (auto sensor = findStaticSensor(path);
+        sensor != ipmi::sensor::sensors.end() &&
+        getSensorEventTypeFromPath(path) !=
+            static_cast<uint8_t>(SensorEventTypeCodes::threshold))
+    {
+        get_sdr::SensorDataFullRecord record = {};
+
+        // If the request doesn't read SDR body, construct only header and key
+        // part to avoid additional DBus transaction.
+        if (readBytes <= sizeof(record.header) + sizeof(record.key))
+        {
+            constructSensorSdrHeaderKey(sensorNum, recordID, record);
+        }
+        else
+        {
+            constructStaticSensorSdr(ctx, sensorNum, recordID, sensor, record);
+        }
+
+        recordData.insert(recordData.end(), reinterpret_cast<uint8_t*>(&record),
+                          reinterpret_cast<uint8_t*>(&record) + sizeof(record));
+
+        return nextRecord;
+    }
+#endif
+
+    // Contruct SDR type 3 record for VR sensor (daemon)
+    if (std::find(interfaces.begin(), interfaces.end(), sensor::vrInterface) !=
+        interfaces.end())
+    {
+        get_sdr::SensorDataEventRecord record = {};
+
+        // If the request doesn't read SDR body, construct only header and key
+        // part to avoid additional DBus transaction.
+        if (readBytes <= sizeof(record.header) + sizeof(record.key))
+        {
+            constructEventSdrHeaderKey(sensorNum, recordID, record);
+        }
+        else if (!constructVrSdr(ctx, ipmiDecoratorPaths, sensorNum, recordID,
+                                 connection, path, record))
+        {
+            return GENERAL_ERROR;
+        }
+        recordData.insert(recordData.end(), reinterpret_cast<uint8_t*>(&record),
+                          reinterpret_cast<uint8_t*>(&record) + sizeof(record));
+    }
+
+    return nextRecord;
+}
+
+/** @brief implements the get SDR Info command
+ *  @param operation : 0 or not supplied returns sensor count
+ *                     1 return SDR count
+ *
+ *  @returns IPMI completion code plus response data
+ *   - sdrCount - sensor/SDR count
+ *   - lunsAndDynamicPopulation - static/Dynamic sensor population flag
+ */
+static ipmi::RspType<uint8_t, // respcount
+                     uint8_t, // dynamic population flags
+                     uint32_t // last time a sensor was added
+                     >
+    ipmiSensorGetDeviceSdrInfo(ipmi::Context::ptr ctx,
+                               std::optional<uint8_t> operation)
+{
+    auto& sensorTree{getSensorTree()};
+    uint8_t sdrCount{};
+    // Sensors are dynamically allocated
+    uint8_t lunsAndDynamicPopulation{0x80};
+    constexpr uint8_t getSdrCount{1};
+    constexpr uint8_t getSensorCount{0};
+
+    if (!getSensorSubtree(sensorTree) || sensorTree.empty())
+    {
+        return ipmi::responseResponseError();
+    }
+    uint16_t numSensors{ipmi::getNumberOfSensors()};
+    if (operation.value_or(0) == getSdrCount)
+    {
+        sdrCount = numSensors + ipmi::sensor::getOtherSensorsCount(ctx) - 1;
+    }
+    else if (operation.value_or(0) == getSensorCount)
+    {
+        // Return the number of sensors attached to the LUN
+        if ((ctx->lun == lun0) && (numSensors > 0))
+        {
+            sdrCount =
+                (numSensors > maxSensorsPerLUN) ? maxSensorsPerLUN : numSensors;
+        }
+        else if ((ctx->lun == lun1) && (numSensors > maxSensorsPerLUN))
+        {
+            sdrCount = (numSensors > (2 * maxSensorsPerLUN))
+                           ? maxSensorsPerLUN
+                           : (numSensors - maxSensorsPerLUN) & maxSensorsPerLUN;
+        }
+        else if (ctx->lun == lun3)
+        {
+            if (numSensors <= maxIPMISensors)
+            {
+                sdrCount = (numSensors - (2 * maxSensorsPerLUN)) &
+                           maxSensorsPerLUN;
+            }
+            else
+            {
+                throw std::out_of_range(
+                    "Maximum number of IPMI sensors exceeded.");
+            }
+        }
+    }
+    else
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    // Flag which LUNs have sensors associated
+    if (numSensors > 0)
+    {
+        lunsAndDynamicPopulation |= 1;
+    }
+    if (numSensors > maxSensorsPerLUN)
+    {
+        lunsAndDynamicPopulation |= 2;
+    }
+    if (numSensors >= (maxSensorsPerLUN * 2))
+    {
+        lunsAndDynamicPopulation |= 8;
+    }
+    if (numSensors > maxIPMISensors)
+    {
+        throw std::out_of_range("Maximum number of IPMI sensors exceeded.");
+    }
+
+    return ipmi::responseSuccess(sdrCount, lunsAndDynamicPopulation,
+                                 sdrLastAdd);
+}
+
+/* end sensor commands */
+
+/* storage commands */
+
+ipmi::RspType<uint8_t,  // sdr version
+              uint16_t, // record count
+              uint16_t, // free space
+              uint32_t, // most recent addition
+              uint32_t, // most recent erase
+              uint8_t   // operationSupport
+              >
+    ipmiStorageGetSDRRepositoryInfo(ipmi::Context::ptr ctx)
+{
+    constexpr const uint16_t unspecifiedFreeSpace = 0xFFFF;
+    uint16_t recordCount =
+        ipmi::getNumberOfSensors() + ipmi::sensor::getOtherSensorsCount(ctx);
+
+    uint8_t operationSupport = static_cast<uint8_t>(
+        SdrRepositoryInfoOps::overflow); // write not supported
+
+    operationSupport |=
+        static_cast<uint8_t>(SdrRepositoryInfoOps::allocCommandSupported);
+    operationSupport |= static_cast<uint8_t>(
+        SdrRepositoryInfoOps::reserveSDRRepositoryCommandSupported);
+    return ipmi::responseSuccess(ipmiSdrVersion, recordCount,
+                                 unspecifiedFreeSpace, sdrLastAdd,
+                                 sdrLastRemove, operationSupport);
+}
+
+/** @brief implements the get SDR allocation info command
+ *
+ *  @returns IPMI completion code plus response data
+ *   - allocUnits    - Number of possible allocation units
+ *   - allocUnitSize - Allocation unit size in bytes.
+ *   - allocUnitFree - Number of free allocation units
+ *   - allocUnitLargestFree - Largest free block in allocation units
+ *   - maxRecordSize    - Maximum record size in allocation units.
+ */
+ipmi::RspType<uint16_t, // allocUnits
+              uint16_t, // allocUnitSize
+              uint16_t, // allocUnitFree
+              uint16_t, // allocUnitLargestFree
+              uint8_t   // maxRecordSize
+              >
+    ipmiStorageGetSDRAllocationInfo()
+{
+    // 0000h unspecified number of alloc units
+    constexpr uint16_t allocUnits = 0;
+
+    constexpr uint16_t allocUnitFree = 0;
+    constexpr uint16_t allocUnitLargestFree = 0;
+    // only allow one block at a time
+    constexpr uint8_t maxRecordSize = 1;
+
+    return ipmi::responseSuccess(allocUnits, maxSDRTotalSize, allocUnitFree,
+                                 allocUnitLargestFree, maxRecordSize);
+}
+
+/** @brief implements the reserve SDR command
+ *  @returns IPMI completion code plus response data
+ *   - sdrReservationID
+ */
+ipmi::RspType<uint16_t> ipmiStorageReserveSDR()
+{
+    sdrReservationID++;
+    if (sdrReservationID == 0)
+    {
+        sdrReservationID++;
+    }
+
+    return ipmi::responseSuccess(sdrReservationID);
+}
+
+ipmi::RspType<uint16_t,            // next record ID
+              std::vector<uint8_t> // payload
+              >
+    ipmiStorageGetSDR(ipmi::Context::ptr ctx, uint16_t reservationID,
+                      uint16_t recordID, uint8_t offset, uint8_t bytesToRead)
+{
+    // reservation required for partial reads with non zero offset into
+    // record
+    if ((sdrReservationID == 0 || reservationID != sdrReservationID) && offset)
+    {
+        lg2::error("ipmiStorageGetSDR: responseInvalidReservationId");
+        return ipmi::responseInvalidReservationId();
+    }
+
+    auto& sensorTree = getSensorTree();
+    if (!getSensorSubtree(sensorTree) && sensorTree.empty())
+    {
+        lg2::error("ipmiStorageGetSDR: getSensorSubtree error");
+        return ipmi::responseResponseError();
+    }
+
+    auto& ipmiDecoratorPaths = getIpmiDecoratorPaths(ctx);
+
+    std::vector<uint8_t> record;
+    int nextRecordId = getSensorDataRecord(
+        ctx, ipmiDecoratorPaths.value_or(std::unordered_set<std::string>()),
+        record, recordID, offset + bytesToRead);
+
+    if (nextRecordId < 0)
+    {
+        lg2::error("ipmiStorageGetSDR: fail to get SDR");
+        return ipmi::responseInvalidFieldRequest();
+    }
+    get_sdr::SensorDataRecordHeader* hdr =
+        reinterpret_cast<get_sdr::SensorDataRecordHeader*>(record.data());
+    if (!hdr)
+    {
+        lg2::error("ipmiStorageGetSDR: record header is null");
+        return ipmi::responseSuccess(nextRecordId, record);
+    }
+
+    size_t sdrLength =
+        sizeof(get_sdr::SensorDataRecordHeader) + hdr->record_length;
+    if (offset >= sdrLength)
+    {
+        lg2::error("ipmiStorageGetSDR: offset is outside the record");
+        return ipmi::responseParmOutOfRange();
+    }
+    if (sdrLength < (offset + bytesToRead))
+    {
+        bytesToRead = sdrLength - offset;
+    }
+
+    uint8_t* respStart = reinterpret_cast<uint8_t*>(hdr) + offset;
+    if (!respStart)
+    {
+        lg2::error("ipmiStorageGetSDR: record is null");
+        return ipmi::responseSuccess(nextRecordId, record);
+    }
+
+    std::vector<uint8_t> recordData(respStart, respStart + bytesToRead);
+
+    return ipmi::responseSuccess(nextRecordId, recordData);
+}
+namespace dcmi
+{
+
+std::tuple<uint8_t,                // Total of instance sensors
+           std::vector<sensorInfo> // The list of sensors
+           >
+    getSensorsByEntityId(ipmi::Context::ptr ctx, uint8_t entityId,
+                         uint8_t entityInstance, uint8_t instanceStart)
+{
+    std::vector<sensorInfo> sensorList;
+    uint8_t totalInstSensor = 0;
+    auto match = ipmi::dcmi::validEntityId.find(entityId);
+
+    if (match == ipmi::dcmi::validEntityId.end())
+    {
+        return std::make_tuple(totalInstSensor, sensorList);
+    }
+
+    auto& sensorTree = getSensorTree();
+    if (!getSensorSubtree(sensorTree) && sensorTree.empty())
+    {
+        return std::make_tuple(totalInstSensor, sensorList);
+    }
+
+    auto& ipmiDecoratorPaths = getIpmiDecoratorPaths(ctx);
+
+    size_t invalidSensorNumberErrCount = 0;
+    for (const auto& sensor : sensorTree)
+    {
+        const std::string& sensorObjPath = sensor.first;
+        const auto& sensorTypeValue = getSensorTypeFromPath(sensorObjPath);
+
+        /*
+         * In the DCMI specification, it only supports the sensor type is 0x01
+         * (temperature type) for both Get Sensor Info and Get Temperature
+         * Readings commands.
+         */
+        if (sensorTypeValue != ipmi::dcmi::temperatureSensorType)
+        {
+            continue;
+        }
+
+        const auto& connection = sensor.second.begin()->first;
+        DbusInterfaceMap sensorMap;
+
+        if (!getSensorMap(ctx, connection, sensorObjPath, sensorMap,
+                          sensorMapSdrUpdatePeriod))
+        {
+            lg2::error("Failed to update sensor map for threshold sensor, "
+                       "service: {SERVICE}, path: {PATH}",
+                       "SERVICE", connection, "PATH", sensorObjPath);
+            continue;
+        }
+
+        uint8_t entityIdValue = 0;
+        uint8_t entityInstanceValue = 0;
+
+        /*
+         * Get the Entity ID, Entity Instance information which are configured
+         * in the Entity-Manger.
+         */
+        updateIpmiFromAssociation(
+            sensorObjPath,
+            ipmiDecoratorPaths.value_or(std::unordered_set<std::string>()),
+            sensorMap, entityIdValue, entityInstanceValue);
+
+        if (entityIdValue == match->first || entityIdValue == match->second)
+        {
+            totalInstSensor++;
+
+            /*
+             * When Entity Instance parameter is not 0, we only get the first
+             * sensor whose Entity Instance number is equal input Entity
+             * Instance parameter.
+             */
+            if (entityInstance)
+            {
+                if (!sensorList.empty())
+                {
+                    continue;
+                }
+
+                if (entityInstanceValue == entityInstance)
+                {
+                    auto recordId = getSensorNumberFromPath(sensorObjPath);
+                    if (recordId == invalidSensorNumber)
+                    {
+                        ++invalidSensorNumberErrCount;
+                        continue;
+                    }
+                    sensorList.emplace_back(sensorObjPath, sensorTypeValue,
+                                            recordId, entityIdValue,
+                                            entityInstanceValue);
+                }
+            }
+            else if (entityInstanceValue >= instanceStart)
+            {
+                auto recordId = getSensorNumberFromPath(sensorObjPath);
+                if (recordId == invalidSensorNumber)
+                {
+                    ++invalidSensorNumberErrCount;
+                    continue;
+                }
+                sensorList.emplace_back(sensorObjPath, sensorTypeValue,
+                                        recordId, entityIdValue,
+                                        entityInstanceValue);
+            }
+        }
+    }
+    if (invalidSensorNumberErrCount != 0)
+    {
+        lg2::error("getSensorNumberFromPath returned invalidSensorNumber "
+                   "{ERR_COUNT} times",
+                   "ERR_COUNT", invalidSensorNumberErrCount);
+    }
+
+    auto cmpFunc = [](sensorInfo first, sensorInfo second) {
+        return first.entityInstance <= second.entityInstance;
+    };
+
+    sort(sensorList.begin(), sensorList.end(), cmpFunc);
+
+    return std::make_tuple(totalInstSensor, sensorList);
+}
+
+std::tuple<bool,    // Reading result
+           uint7_t, // Temp value
+           bool>    // Sign bit
+    readTemp(ipmi::Context::ptr ctx, const std::string& objectPath)
+{
+    std::string service{};
+    boost::system::error_code ec =
+        ipmi::getService(ctx, sensor::sensorInterface, objectPath, service);
+    if (ec.value())
+    {
+        return std::make_tuple(false, 0, false);
+    }
+
+    ipmi::PropertyMap properties{};
+    ec = ipmi::getAllDbusProperties(ctx, service, objectPath,
+                                    sensor::sensorInterface, properties);
+    if (ec.value())
+    {
+        return std::make_tuple(false, 0, false);
+    }
+
+    auto scaleIt = properties.find("Scale");
+    double scaleVal = 0.0;
+    if (scaleIt != properties.end())
+    {
+        scaleVal = std::visit(ipmi::VariantToDoubleVisitor(), scaleIt->second);
+    }
+
+    auto tempValIt = properties.find("Value");
+    double tempVal = 0.0;
+    if (tempValIt == properties.end())
+    {
+        return std::make_tuple(false, 0, false);
+    }
+
+    const double maxTemp = 127;
+    double absTempVal = 0.0;
+    bool signBit = false;
+
+    tempVal = std::visit(ipmi::VariantToDoubleVisitor(), tempValIt->second);
+    tempVal = std::pow(10, scaleVal) * tempVal;
+    absTempVal = std::abs(tempVal);
+    absTempVal = std::min(absTempVal, maxTemp);
+    signBit = (tempVal < 0) ? true : false;
+
+    return std::make_tuple(true, static_cast<uint7_t>(absTempVal), signBit);
+}
+
+ipmi::RspType<uint8_t,              // No of instances for requested id
+              uint8_t,              // No of record ids in the response
+              std::vector<uint16_t> // SDR Record ID corresponding to the Entity
+                                    // IDs
+              >
+    getSensorInfo(ipmi::Context::ptr ctx, uint8_t sensorType, uint8_t entityId,
+                  uint8_t entityInstance, uint8_t instanceStart)
+{
+    auto match = ipmi::dcmi::validEntityId.find(entityId);
+    if (match == ipmi::dcmi::validEntityId.end())
+    {
+        lg2::error("Unknown Entity ID: {ENTITY_ID}", "ENTITY_ID", entityId);
+
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    if (sensorType != ipmi::dcmi::temperatureSensorType)
+    {
+        lg2::error("Invalid sensor type: {SENSOR_TYPE}", "SENSOR_TYPE",
+                   sensorType);
+
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    std::vector<uint16_t> sensorRec{};
+    const auto& [totalSensorInst, sensorList] =
+        getSensorsByEntityId(ctx, entityId, entityInstance, instanceStart);
+
+    if (sensorList.empty())
+    {
+        return ipmi::responseSuccess(totalSensorInst, 0, sensorRec);
+    }
+
+    /*
+     * As DCMI specification, the maximum number of Record Ids of response data
+     * is 1 if Entity Instance paramter is not 0. Else the maximum number of
+     * Record Ids of response data is 8. Therefore, not all of sensors are shown
+     * in response data.
+     */
+    uint8_t numOfRec = (entityInstance != 0) ? 1 : ipmi::dcmi::maxRecords;
+
+    for (const auto& sensor : sensorList)
+    {
+        sensorRec.emplace_back(sensor.recordId);
+        if (sensorRec.size() >= numOfRec)
+        {
+            break;
+        }
+    }
+
+    return ipmi::responseSuccess(
+        totalSensorInst, static_cast<uint8_t>(sensorRec.size()), sensorRec);
+}
+
+ipmi::RspType<uint8_t,                // No of instances for requested id
+              uint8_t,                // No of record ids in the response
+              std::vector<            // Temperature Data
+                  std::tuple<uint7_t, // Temperature value
+                             bool,    // Sign bit
+                             uint8_t  // Entity Instance of sensor
+                             >>>
+    getTempReadings(ipmi::Context::ptr ctx, uint8_t sensorType,
+                    uint8_t entityId, uint8_t entityInstance,
+                    uint8_t instanceStart)
+{
+    auto match = ipmi::dcmi::validEntityId.find(entityId);
+    if (match == ipmi::dcmi::validEntityId.end())
+    {
+        lg2::error("Unknown Entity ID: {ENTITY_ID}", "ENTITY_ID", entityId);
+
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    if (sensorType != ipmi::dcmi::temperatureSensorType)
+    {
+        lg2::error("Invalid sensor type: {SENSOR_TYPE}", "SENSOR_TYPE",
+                   sensorType);
+
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    std::vector<std::tuple<uint7_t, bool, uint8_t>> tempReadingVal{};
+    const auto& [totalSensorInst, sensorList] =
+        getSensorsByEntityId(ctx, entityId, entityInstance, instanceStart);
+
+    if (sensorList.empty())
+    {
+        return ipmi::responseSuccess(totalSensorInst, 0, tempReadingVal);
+    }
+
+    /*
+     * As DCMI specification, the maximum number of Record Ids of response data
+     * is 1 if Entity Instance paramter is not 0. Else the maximum number of
+     * Record Ids of response data is 8. Therefore, not all of sensors are shown
+     * in response data.
+     */
+    uint8_t numOfRec = (entityInstance != 0) ? 1 : ipmi::dcmi::maxRecords;
+
+    for (const auto& sensor : sensorList)
+    {
+        const auto& [readResult, tempVal, signBit] =
+            readTemp(ctx, sensor.objectPath);
+
+        if (readResult)
+        {
+            tempReadingVal.emplace_back(
+                std::make_tuple(tempVal, signBit, sensor.entityInstance));
+
+            if (tempReadingVal.size() >= numOfRec)
+            {
+                break;
+            }
+        }
+    }
+
+    return ipmi::responseSuccess(totalSensorInst,
+                                 static_cast<uint8_t>(tempReadingVal.size()),
+                                 tempReadingVal);
+}
+
+} // namespace dcmi
+
+/* end storage commands */
+
+void registerSensorFunctions()
+{
+    // <Platform Event>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+                          ipmi::sensor_event::cmdPlatformEvent,
+                          ipmi::Privilege::Operator, ipmiSenPlatformEvent);
+
+    // <Set Sensor Reading and Event Status>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+                          ipmi::sensor_event::cmdSetSensorReadingAndEvtSts,
+                          ipmi::Privilege::Operator, ipmiSetSensorReading);
+
+    // <Get Sensor Reading>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+                          ipmi::sensor_event::cmdGetSensorReading,
+                          ipmi::Privilege::User, ipmiSenGetSensorReading);
+
+    // <Get Sensor Threshold>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+                          ipmi::sensor_event::cmdGetSensorThreshold,
+                          ipmi::Privilege::User, ipmiSenGetSensorThresholds);
+
+    // <Set Sensor Threshold>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+                          ipmi::sensor_event::cmdSetSensorThreshold,
+                          ipmi::Privilege::Operator,
+                          ipmiSenSetSensorThresholds);
+
+    // <Get Sensor Event Enable>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+                          ipmi::sensor_event::cmdGetSensorEventEnable,
+                          ipmi::Privilege::User, ipmiSenGetSensorEventEnable);
+
+    // <Get Sensor Event Status>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+                          ipmi::sensor_event::cmdGetSensorEventStatus,
+                          ipmi::Privilege::User, ipmiSenGetSensorEventStatus);
+
+    // register all storage commands for both Sensor and Storage command
+    // versions
+
+    // <Get SDR Repository Info>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdGetSdrRepositoryInfo,
+                          ipmi::Privilege::User,
+                          ipmiStorageGetSDRRepositoryInfo);
+
+    // <Get Device SDR Info>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+                          ipmi::sensor_event::cmdGetDeviceSdrInfo,
+                          ipmi::Privilege::User, ipmiSensorGetDeviceSdrInfo);
+
+    // <Get SDR Allocation Info>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdGetSdrRepositoryAllocInfo,
+                          ipmi::Privilege::User,
+                          ipmiStorageGetSDRAllocationInfo);
+
+    // <Reserve SDR Repo>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+                          ipmi::sensor_event::cmdReserveDeviceSdrRepository,
+                          ipmi::Privilege::User, ipmiStorageReserveSDR);
+
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdReserveSdrRepository,
+                          ipmi::Privilege::User, ipmiStorageReserveSDR);
+
+    // <Get Sdr>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+                          ipmi::sensor_event::cmdGetDeviceSdr,
+                          ipmi::Privilege::User, ipmiStorageGetSDR);
+
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdGetSdr, ipmi::Privilege::User,
+                          ipmiStorageGetSDR);
+    // <Get DCMI Sensor Info>
+    ipmi::registerGroupHandler(
+        ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+        ipmi::dcmi::cmdGetDcmiSensorInfo, ipmi::Privilege::Operator,
+        ipmi::dcmi::getSensorInfo);
+    // <Get Temperature Readings>
+    ipmi::registerGroupHandler(
+        ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+        ipmi::dcmi::cmdGetTemperatureReadings, ipmi::Privilege::User,
+        ipmi::dcmi::getTempReadings);
+}
+} // namespace ipmi
diff --git a/dbus-sdr/sensorutils.cpp b/dbus-sdr/sensorutils.cpp
new file mode 100644
index 0000000..8c6952b
--- /dev/null
+++ b/dbus-sdr/sensorutils.cpp
@@ -0,0 +1,324 @@
+/*
+// Copyright (c) 2017 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.
+*/
+
+#include "dbus-sdr/sensorutils.hpp"
+
+#include <algorithm>
+#include <cmath>
+#include <iostream>
+
+namespace ipmi
+{
+
+// Helper function to avoid repeated complicated expression
+static bool baseInRange(double base)
+{
+    auto min10 = static_cast<double>(minInt10);
+    auto max10 = static_cast<double>(maxInt10);
+
+    return ((base >= min10) && (base <= max10));
+}
+
+// Helper function for internal use by getSensorAttributes()
+// Ensures floating-point "base" is within bounds,
+// and adjusts integer exponent "expShift" accordingly.
+// To minimize data loss when later truncating to integer,
+// the floating-point "base" will be as large as possible,
+// but still within the bounds (minInt10,maxInt10).
+// The bounds of "expShift" are (minInt4,maxInt4).
+// Consider this equation: n = base * (10.0 ** expShift)
+// This function will try to maximize "base",
+// adjusting "expShift" to keep the value "n" unchanged,
+// while keeping base and expShift within bounds.
+// Returns true if successful, modifies values in-place
+static bool scaleFloatExp(double& base, int8_t& expShift)
+{
+    // Comparing with zero should be OK, zero is special in floating-point
+    // If base is exactly zero, no adjustment of the exponent is necessary
+    if (base == 0.0)
+    {
+        return true;
+    }
+
+    // As long as base value is within allowed range, expand precision
+    // This will help to avoid loss when later rounding to integer
+    while (baseInRange(base))
+    {
+        if (expShift <= minInt4)
+        {
+            // Already at the minimum expShift, can not decrement it more
+            break;
+        }
+
+        // Multiply by 10, but shift decimal point to the left, no net change
+        base *= 10.0;
+        --expShift;
+    }
+
+    // As long as base value is *not* within range, shrink precision
+    // This will pull base value closer to zero, thus within range
+    while (!(baseInRange(base)))
+    {
+        if (expShift >= maxInt4)
+        {
+            // Already at the maximum expShift, can not increment it more
+            break;
+        }
+
+        // Divide by 10, but shift decimal point to the right, no net change
+        base /= 10.0;
+        ++expShift;
+    }
+
+    // If the above loop was not able to pull it back within range,
+    // the base value is beyond what expShift can represent, return false.
+    return baseInRange(base);
+}
+
+// Helper function for internal use by getSensorAttributes()
+// Ensures integer "ibase" is no larger than necessary,
+// by normalizing it so that the decimal point shift is in the exponent,
+// whenever possible.
+// This provides more consistent results,
+// as many equivalent solutions are collapsed into one consistent solution.
+// If integer "ibase" is a clean multiple of 10,
+// divide it by 10 (this is lossless), so it is closer to zero.
+// Also modify floating-point "dbase" at the same time,
+// as both integer and floating-point base share the same expShift.
+// Example: (ibase=300, expShift=2) becomes (ibase=3, expShift=4)
+// because the underlying value is the same: 200*(10**2) == 2*(10**4)
+// Always successful, modifies values in-place
+static void normalizeIntExp(int16_t& ibase, int8_t& expShift, double& dbase)
+{
+    for (;;)
+    {
+        // If zero, already normalized, ensure exponent also zero
+        if (ibase == 0)
+        {
+            expShift = 0;
+            break;
+        }
+
+        // If not cleanly divisible by 10, already normalized
+        if ((ibase % 10) != 0)
+        {
+            break;
+        }
+
+        // If exponent already at max, already normalized
+        if (expShift >= maxInt4)
+        {
+            break;
+        }
+
+        // Bring values closer to zero, correspondingly shift exponent,
+        // without changing the underlying number that this all represents,
+        // similar to what is done by scaleFloatExp().
+        // The floating-point base must be kept in sync with the integer base,
+        // as both floating-point and integer share the same exponent.
+        ibase /= 10;
+        dbase /= 10.0;
+        ++expShift;
+    }
+}
+
+// The IPMI equation:
+// y = (Mx + (B * 10^(bExp))) * 10^(rExp)
+// Section 36.3 of this document:
+// https://www.intel.com/content/dam/www/public/us/en/documents/product-briefs/ipmi-second-gen-interface-spec-v2-rev1-1.pdf
+//
+// The goal is to exactly match the math done by the ipmitool command,
+// at the other side of the interface:
+// https://github.com/ipmitool/ipmitool/blob/42a023ff0726c80e8cc7d30315b987fe568a981d/lib/ipmi_sdr.c#L360
+//
+// To use with Wolfram Alpha, make all variables single letters
+// bExp becomes E, rExp becomes R
+// https://www.wolframalpha.com/input/?i=y%3D%28%28M*x%29%2B%28B*%2810%5EE%29%29%29*%2810%5ER%29
+bool getSensorAttributes(const double max, const double min, int16_t& mValue,
+                         int8_t& rExp, int16_t& bValue, int8_t& bExp,
+                         bool& bSigned)
+{
+    if (!(std::isfinite(min)))
+    {
+        std::cerr << "getSensorAttributes: Min value is unusable\n";
+        return false;
+    }
+    if (!(std::isfinite(max)))
+    {
+        std::cerr << "getSensorAttributes: Max value is unusable\n";
+        return false;
+    }
+
+    // Because NAN has already been tested for, this comparison works
+    if (max <= min)
+    {
+        std::cerr << "getSensorAttributes: Max must be greater than min\n";
+        return false;
+    }
+
+    // Given min and max, we must solve for M, B, bExp, rExp
+    // y comes in from D-Bus (the actual sensor reading)
+    // x is calculated from y by scaleIPMIValueFromDouble() below
+    // If y is min, x should equal = 0 (or -128 if signed)
+    // If y is max, x should equal 255 (or 127 if signed)
+    double fullRange = max - min;
+    double lowestX;
+
+    rExp = 0;
+    bExp = 0;
+
+    // TODO(): The IPMI document is ambiguous, as to whether
+    // the resulting byte should be signed or unsigned,
+    // essentially leaving it up to the caller.
+    // The document just refers to it as "raw reading",
+    // or "byte of reading", without giving further details.
+    // Previous code set it signed if min was less than zero,
+    // so I'm sticking with that, until I learn otherwise.
+    if (min < 0.0)
+    {
+        // TODO(): It would be worth experimenting with the range (-127,127),
+        // instead of the range (-128,127), because this
+        // would give good symmetry around zero, and make results look better.
+        // Divide by 254 instead of 255, and change -128 to -127 elsewhere.
+        bSigned = true;
+        lowestX = -128.0;
+    }
+    else
+    {
+        bSigned = false;
+        lowestX = 0.0;
+    }
+
+    // Step 1: Set y to (max - min), set x to 255, set B to 0, solve for M
+    // This works, regardless of signed or unsigned,
+    // because total range is the same.
+    double dM = fullRange / 255.0;
+
+    // Step 2: Constrain M, and set rExp accordingly
+    if (!(scaleFloatExp(dM, rExp)))
+    {
+        std::cerr << "getSensorAttributes: Multiplier range exceeds scale (M="
+                  << dM << ", rExp=" << (int)rExp << ")\n";
+        return false;
+    }
+
+    mValue = static_cast<int16_t>(std::round(dM));
+
+    normalizeIntExp(mValue, rExp, dM);
+
+    // The multiplier can not be zero, for obvious reasons
+    if (mValue == 0)
+    {
+        std::cerr << "getSensorAttributes: Multiplier range below scale\n";
+        return false;
+    }
+
+    // Step 3: set y to min, set x to min, keep M and rExp, solve for B
+    // If negative, x will be -128 (the most negative possible byte), not 0
+
+    // Solve the IPMI equation for B, instead of y
+    // https://www.wolframalpha.com/input/?i=solve+y%3D%28%28M*x%29%2B%28B*%2810%5EE%29%29%29*%2810%5ER%29+for+B
+    // B = 10^(-rExp - bExp) (y - M 10^rExp x)
+    // TODO(): Compare with this alternative solution from SageMathCell
+    // https://sagecell.sagemath.org/?z=eJyrtC1LLNJQr1TX5KqAMCuATF8I0xfIdIIwnYDMIteKAggPxAIKJMEFkiACxfk5Zaka0ZUKtrYKGhq-CloKFZoK2goaTkCWhqGBgpaWAkilpqYmQgBklmasjoKTJgDAECTH&lang=sage&interacts=eJyLjgUAARUAuQ==
+    double dB = std::pow(10.0, ((-rExp) - bExp)) *
+                (min - ((dM * std::pow(10.0, rExp) * lowestX)));
+
+    // Step 4: Constrain B, and set bExp accordingly
+    if (!(scaleFloatExp(dB, bExp)))
+    {
+        std::cerr << "getSensorAttributes: Offset (B=" << dB << ", bExp="
+                  << (int)bExp << ") exceeds multiplier scale (M=" << dM
+                  << ", rExp=" << (int)rExp << ")\n";
+        return false;
+    }
+
+    bValue = static_cast<int16_t>(std::round(dB));
+
+    normalizeIntExp(bValue, bExp, dB);
+
+    // Unlike the multiplier, it is perfectly OK for bValue to be zero
+    return true;
+}
+
+uint8_t scaleIPMIValueFromDouble(const double value, const int16_t mValue,
+                                 const int8_t rExp, const int16_t bValue,
+                                 const int8_t bExp, const bool bSigned)
+{
+    // Avoid division by zero below
+    if (mValue == 0)
+    {
+        throw std::out_of_range("Scaling multiplier is uninitialized");
+    }
+
+    auto dM = static_cast<double>(mValue);
+    auto dB = static_cast<double>(bValue);
+
+    // Solve the IPMI equation for x, instead of y
+    // https://www.wolframalpha.com/input/?i=solve+y%3D%28%28M*x%29%2B%28B*%2810%5EE%29%29%29*%2810%5ER%29+for+x
+    // x = (10^(-rExp) (y - B 10^(rExp + bExp)))/M and M 10^rExp!=0
+    // TODO(): Compare with this alternative solution from SageMathCell
+    // https://sagecell.sagemath.org/?z=eJyrtC1LLNJQr1TX5KqAMCuATF8I0xfIdIIwnYDMIteKAggPxAIKJMEFkiACxfk5Zaka0ZUKtrYKGhq-CloKFZoK2goaTkCWhqGBgpaWAkilpqYmQgBklmasDlAlAMB8JP0=&lang=sage&interacts=eJyLjgUAARUAuQ==
+    double dX =
+        (std::pow(10.0, -rExp) * (value - (dB * std::pow(10.0, rExp + bExp)))) /
+        dM;
+
+    auto scaledValue = static_cast<int32_t>(std::round(dX));
+
+    int32_t minClamp;
+    int32_t maxClamp;
+
+    // Because of rounding and integer truncation of scaling factors,
+    // sometimes the resulting byte is slightly out of range.
+    // Still allow this, but clamp the values to range.
+    if (bSigned)
+    {
+        minClamp = std::numeric_limits<int8_t>::lowest();
+        maxClamp = std::numeric_limits<int8_t>::max();
+    }
+    else
+    {
+        minClamp = std::numeric_limits<uint8_t>::lowest();
+        maxClamp = std::numeric_limits<uint8_t>::max();
+    }
+
+    auto clampedValue = std::clamp(scaledValue, minClamp, maxClamp);
+
+    // This works for both signed and unsigned,
+    // because it is the same underlying byte storage.
+    return static_cast<uint8_t>(clampedValue);
+}
+
+uint8_t getScaledIPMIValue(const double value, const double max,
+                           const double min)
+{
+    int16_t mValue = 0;
+    int8_t rExp = 0;
+    int16_t bValue = 0;
+    int8_t bExp = 0;
+    bool bSigned = false;
+
+    bool result =
+        getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned);
+    if (!result)
+    {
+        throw std::runtime_error("Illegal sensor attributes");
+    }
+
+    return scaleIPMIValueFromDouble(value, mValue, rExp, bValue, bExp, bSigned);
+}
+
+} // namespace ipmi
diff --git a/dbus-sdr/storagecommands.cpp b/dbus-sdr/storagecommands.cpp
new file mode 100644
index 0000000..e8c4223
--- /dev/null
+++ b/dbus-sdr/storagecommands.cpp
@@ -0,0 +1,1296 @@
+/*
+// Copyright (c) 2017-2019 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.
+*/
+
+#include "dbus-sdr/storagecommands.hpp"
+
+#include "dbus-sdr/sdrutils.hpp"
+#include "selutility.hpp"
+
+#include <boost/algorithm/string.hpp>
+#include <boost/asio/detached.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/process.hpp>
+#include <ipmid/api.hpp>
+#include <ipmid/message.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <sdbusplus/timer.hpp>
+
+#include <filesystem>
+#include <fstream>
+#include <functional>
+#include <iostream>
+#include <stdexcept>
+#include <string_view>
+
+static constexpr bool DEBUG = false;
+
+namespace dynamic_sensors::ipmi::sel
+{
+static const std::filesystem::path selLogDir = "/var/log";
+static const std::string selLogFilename = "ipmi_sel";
+
+static int getFileTimestamp(const std::filesystem::path& file)
+{
+    struct stat st;
+
+    if (stat(file.c_str(), &st) >= 0)
+    {
+        return st.st_mtime;
+    }
+    return ::ipmi::sel::invalidTimeStamp;
+}
+
+namespace erase_time
+{
+static constexpr const char* selEraseTimestamp = "/var/lib/ipmi/sel_erase_time";
+
+int get()
+{
+    return getFileTimestamp(selEraseTimestamp);
+}
+} // namespace erase_time
+} // namespace dynamic_sensors::ipmi::sel
+
+namespace ipmi
+{
+
+namespace storage
+{
+
+constexpr static const size_t maxFruSdrNameSize = 16;
+using ObjectType =
+    boost::container::flat_map<std::string,
+                               boost::container::flat_map<std::string, Value>>;
+using ManagedObjectType =
+    boost::container::flat_map<sdbusplus::message::object_path, ObjectType>;
+using ManagedEntry = std::pair<sdbusplus::message::object_path, ObjectType>;
+
+constexpr static const char* fruDeviceServiceName =
+    "xyz.openbmc_project.FruDevice";
+constexpr static const size_t writeTimeoutSeconds = 10;
+constexpr static const char* chassisTypeRackMount = "23";
+constexpr static const char* chassisTypeMainServer = "17";
+
+static std::vector<uint8_t> fruCache;
+static constexpr uint16_t invalidBus = 0xFFFF;
+static constexpr uint8_t invalidAddr = 0xFF;
+static constexpr uint8_t typeASCIILatin8 = 0xC0;
+static uint16_t cacheBus = invalidBus;
+static uint8_t cacheAddr = invalidAddr;
+static uint8_t lastDevId = 0xFF;
+
+static uint16_t writeBus = invalidBus;
+static uint8_t writeAddr = invalidAddr;
+
+std::unique_ptr<sdbusplus::Timer> writeTimer = nullptr;
+static std::vector<sdbusplus::bus::match_t> fruMatches;
+
+ManagedObjectType frus;
+
+// we unfortunately have to build a map of hashes in case there is a
+// collision to verify our dev-id
+boost::container::flat_map<uint8_t, std::pair<uint16_t, uint8_t>> deviceHashes;
+void registerStorageFunctions() __attribute__((constructor));
+
+bool writeFru(const std::vector<uint8_t>& fru)
+{
+    if (writeBus == invalidBus && writeAddr == invalidAddr)
+    {
+        return true;
+    }
+    lastDevId = 0xFF;
+    std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
+    sdbusplus::message_t writeFru = dbus->new_method_call(
+        fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
+        "xyz.openbmc_project.FruDeviceManager", "WriteFru");
+    writeFru.append(writeBus, writeAddr, fru);
+    try
+    {
+        sdbusplus::message_t writeFruResp = dbus->call(writeFru);
+    }
+    catch (const sdbusplus::exception_t&)
+    {
+        // todo: log sel?
+        lg2::error("error writing fru");
+        return false;
+    }
+    writeBus = invalidBus;
+    writeAddr = invalidAddr;
+    return true;
+}
+
+void writeFruCache()
+{
+    writeFru(fruCache);
+}
+
+void createTimers()
+{
+    writeTimer = std::make_unique<sdbusplus::Timer>(writeFruCache);
+}
+
+void recalculateHashes()
+{
+    deviceHashes.clear();
+    // hash the object paths to create unique device id's. increment on
+    // collision
+    std::hash<std::string> hasher;
+    for (const auto& fru : frus)
+    {
+        auto fruIface = fru.second.find("xyz.openbmc_project.FruDevice");
+        if (fruIface == fru.second.end())
+        {
+            continue;
+        }
+
+        auto busFind = fruIface->second.find("BUS");
+        auto addrFind = fruIface->second.find("ADDRESS");
+        if (busFind == fruIface->second.end() ||
+            addrFind == fruIface->second.end())
+        {
+            lg2::info("fru device missing Bus or Address, fru: {FRU}", "FRU",
+                      fru.first.str);
+            continue;
+        }
+
+        uint16_t fruBus = std::get<uint32_t>(busFind->second);
+        uint8_t fruAddr = std::get<uint32_t>(addrFind->second);
+        auto chassisFind = fruIface->second.find("CHASSIS_TYPE");
+        std::string chassisType;
+        if (chassisFind != fruIface->second.end())
+        {
+            chassisType = std::get<std::string>(chassisFind->second);
+        }
+
+        uint8_t fruHash = 0;
+        if (chassisType.compare(chassisTypeRackMount) != 0 &&
+            chassisType.compare(chassisTypeMainServer) != 0)
+        {
+            fruHash = hasher(fru.first.str);
+            // can't be 0xFF based on spec, and 0 is reserved for baseboard
+            if (fruHash == 0 || fruHash == 0xFF)
+            {
+                fruHash = 1;
+            }
+        }
+        std::pair<uint16_t, uint8_t> newDev(fruBus, fruAddr);
+
+        bool emplacePassed = false;
+        while (!emplacePassed)
+        {
+            auto resp = deviceHashes.emplace(fruHash, newDev);
+            emplacePassed = resp.second;
+            if (!emplacePassed)
+            {
+                fruHash++;
+                // can't be 0xFF based on spec, and 0 is reserved for
+                // baseboard
+                if (fruHash == 0XFF)
+                {
+                    fruHash = 0x1;
+                }
+            }
+        }
+    }
+}
+
+void replaceCacheFru(
+    const std::shared_ptr<sdbusplus::asio::connection>& bus,
+    boost::asio::yield_context& yield,
+    [[maybe_unused]] const std::optional<std::string>& path = std::nullopt)
+{
+    boost::system::error_code ec;
+
+    frus = bus->yield_method_call<ManagedObjectType>(
+        yield, ec, fruDeviceServiceName, "/",
+        "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
+    if (ec)
+    {
+        lg2::error("GetMangagedObjects for replaceCacheFru failed: {ERROR}",
+                   "ERROR", ec.message());
+
+        return;
+    }
+    recalculateHashes();
+}
+
+std::pair<ipmi::Cc, std::vector<uint8_t>> getFru(ipmi::Context::ptr ctx,
+                                                 uint8_t devId)
+{
+    if (lastDevId == devId && devId != 0xFF)
+    {
+        return {ipmi::ccSuccess, fruCache};
+    }
+
+    auto deviceFind = deviceHashes.find(devId);
+    if (deviceFind == deviceHashes.end())
+    {
+        return {IPMI_CC_SENSOR_INVALID, {}};
+    }
+
+    cacheBus = deviceFind->second.first;
+    cacheAddr = deviceFind->second.second;
+
+    boost::system::error_code ec;
+    std::vector<uint8_t> fru = ipmi::callDbusMethod<std::vector<uint8_t>>(
+        ctx, ec, fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
+        "xyz.openbmc_project.FruDeviceManager", "GetRawFru", cacheBus,
+        cacheAddr);
+
+    if (ec)
+    {
+        lg2::error("Couldn't get raw fru: {ERROR}", "ERROR", ec.message());
+
+        cacheBus = invalidBus;
+        cacheAddr = invalidAddr;
+        return {ipmi::ccResponseError, {}};
+    }
+
+    fruCache.clear();
+    lastDevId = devId;
+    fruCache = fru;
+
+    return {ipmi::ccSuccess, fru};
+}
+
+void writeFruIfRunning()
+{
+    if (!writeTimer->isRunning())
+    {
+        return;
+    }
+    writeTimer->stop();
+    writeFruCache();
+}
+
+void startMatch(void)
+{
+    if (fruMatches.size())
+    {
+        return;
+    }
+
+    fruMatches.reserve(2);
+
+    auto bus = getSdBus();
+    fruMatches.emplace_back(
+        *bus,
+        "type='signal',arg0path='/xyz/openbmc_project/"
+        "FruDevice/',member='InterfacesAdded'",
+        [](sdbusplus::message_t& message) {
+            sdbusplus::message::object_path path;
+            ObjectType object;
+            try
+            {
+                message.read(path, object);
+            }
+            catch (const sdbusplus::exception_t&)
+            {
+                return;
+            }
+            auto findType = object.find("xyz.openbmc_project.FruDevice");
+            if (findType == object.end())
+            {
+                return;
+            }
+            writeFruIfRunning();
+            frus[path] = object;
+            recalculateHashes();
+            lastDevId = 0xFF;
+        });
+
+    fruMatches.emplace_back(
+        *bus,
+        "type='signal',arg0path='/xyz/openbmc_project/"
+        "FruDevice/',member='InterfacesRemoved'",
+        [](sdbusplus::message_t& message) {
+            sdbusplus::message::object_path path;
+            std::set<std::string> interfaces;
+            try
+            {
+                message.read(path, interfaces);
+            }
+            catch (const sdbusplus::exception_t&)
+            {
+                return;
+            }
+            auto findType = interfaces.find("xyz.openbmc_project.FruDevice");
+            if (findType == interfaces.end())
+            {
+                return;
+            }
+            writeFruIfRunning();
+            frus.erase(path);
+            recalculateHashes();
+            lastDevId = 0xFF;
+        });
+
+    // call once to populate
+    boost::asio::spawn(
+        *getIoContext(),
+        [](boost::asio::yield_context yield) {
+            replaceCacheFru(getSdBus(), yield);
+        },
+        boost::asio::detached);
+}
+
+/** @brief implements the read FRU data command
+ *  @param fruDeviceId        - FRU Device ID
+ *  @param fruInventoryOffset - FRU Inventory Offset to write
+ *  @param countToRead        - Count to read
+ *
+ *  @returns ipmi completion code plus response data
+ *   - countWritten  - Count written
+ */
+ipmi::RspType<uint8_t,             // Count
+              std::vector<uint8_t> // Requested data
+              >
+    ipmiStorageReadFruData(ipmi::Context::ptr ctx, uint8_t fruDeviceId,
+                           uint16_t fruInventoryOffset, uint8_t countToRead)
+{
+    if (fruDeviceId == 0xFF)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    auto [status, fru] = getFru(ctx, fruDeviceId);
+    if (status != ipmi::ccSuccess)
+    {
+        return ipmi::response(status);
+    }
+
+    size_t fromFruByteLen = 0;
+    if (countToRead + fruInventoryOffset < fru.size())
+    {
+        fromFruByteLen = countToRead;
+    }
+    else if (fru.size() > fruInventoryOffset)
+    {
+        fromFruByteLen = fru.size() - fruInventoryOffset;
+    }
+    else
+    {
+        return ipmi::responseReqDataLenExceeded();
+    }
+
+    std::vector<uint8_t> requestedData;
+
+    requestedData.insert(requestedData.begin(),
+                         fru.begin() + fruInventoryOffset,
+                         fru.begin() + fruInventoryOffset + fromFruByteLen);
+
+    return ipmi::responseSuccess(static_cast<uint8_t>(requestedData.size()),
+                                 requestedData);
+}
+
+/** @brief implements the write FRU data command
+ *  @param fruDeviceId        - FRU Device ID
+ *  @param fruInventoryOffset - FRU Inventory Offset to write
+ *  @param dataToWrite        - Data to write
+ *
+ *  @returns ipmi completion code plus response data
+ *   - countWritten  - Count written
+ */
+ipmi::RspType<uint8_t> ipmiStorageWriteFruData(
+    ipmi::Context::ptr ctx, uint8_t fruDeviceId, uint16_t fruInventoryOffset,
+    std::vector<uint8_t>& dataToWrite)
+{
+    if (fruDeviceId == 0xFF)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    size_t writeLen = dataToWrite.size();
+
+    auto [status, fru] = getFru(ctx, fruDeviceId);
+    if (status != ipmi::ccSuccess)
+    {
+        return ipmi::response(status);
+    }
+    size_t lastWriteAddr = fruInventoryOffset + writeLen;
+    if (fru.size() < lastWriteAddr)
+    {
+        fru.resize(fruInventoryOffset + writeLen);
+    }
+
+    std::copy(dataToWrite.begin(), dataToWrite.begin() + writeLen,
+              fru.begin() + fruInventoryOffset);
+
+    bool atEnd = false;
+
+    if (fru.size() >= sizeof(FRUHeader))
+    {
+        FRUHeader* header = reinterpret_cast<FRUHeader*>(fru.data());
+
+        size_t areaLength = 0;
+        size_t lastRecordStart = std::max(
+            {header->internalOffset, header->chassisOffset, header->boardOffset,
+             header->productOffset, header->multiRecordOffset});
+        lastRecordStart *= 8; // header starts in are multiples of 8 bytes
+
+        if (header->multiRecordOffset)
+        {
+            // This FRU has a MultiRecord Area
+            uint8_t endOfList = 0;
+            // Walk the MultiRecord headers until the last record
+            while (!endOfList)
+            {
+                // The MSB in the second byte of the MultiRecord header signals
+                // "End of list"
+                endOfList = fru[lastRecordStart + 1] & 0x80;
+                // Third byte in the MultiRecord header is the length
+                areaLength = fru[lastRecordStart + 2];
+                // This length is in bytes (not 8 bytes like other headers)
+                areaLength += 5; // The length omits the 5 byte header
+                if (!endOfList)
+                {
+                    // Next MultiRecord header
+                    lastRecordStart += areaLength;
+                }
+            }
+        }
+        else
+        {
+            // This FRU does not have a MultiRecord Area
+            // Get the length of the area in multiples of 8 bytes
+            if (lastWriteAddr > (lastRecordStart + 1))
+            {
+                // second byte in record area is the length
+                areaLength = fru[lastRecordStart + 1];
+                areaLength *= 8; // it is in multiples of 8 bytes
+            }
+        }
+        if (lastWriteAddr >= (areaLength + lastRecordStart))
+        {
+            atEnd = true;
+        }
+    }
+    uint8_t countWritten = 0;
+
+    writeBus = cacheBus;
+    writeAddr = cacheAddr;
+    if (atEnd)
+    {
+        // cancel timer, we're at the end so might as well send it
+        writeTimer->stop();
+        if (!writeFru(fru))
+        {
+            return ipmi::responseInvalidFieldRequest();
+        }
+        countWritten = std::min(fru.size(), static_cast<size_t>(0xFF));
+    }
+    else
+    {
+        fruCache = fru; // Write-back
+        // start a timer, if no further data is sent  to check to see if it is
+        // valid
+        writeTimer->start(std::chrono::duration_cast<std::chrono::microseconds>(
+            std::chrono::seconds(writeTimeoutSeconds)));
+        countWritten = 0;
+    }
+
+    return ipmi::responseSuccess(countWritten);
+}
+
+/** @brief implements the get FRU inventory area info command
+ *  @param fruDeviceId  - FRU Device ID
+ *
+ *  @returns IPMI completion code plus response data
+ *   - inventorySize - Number of possible allocation units
+ *   - accessType    - Allocation unit size in bytes.
+ */
+ipmi::RspType<uint16_t, // inventorySize
+              uint8_t>  // accessType
+    ipmiStorageGetFruInvAreaInfo(ipmi::Context::ptr ctx, uint8_t fruDeviceId)
+{
+    if (fruDeviceId == 0xFF)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    auto [ret, fru] = getFru(ctx, fruDeviceId);
+    if (ret != ipmi::ccSuccess)
+    {
+        return ipmi::response(ret);
+    }
+
+    constexpr uint8_t accessType =
+        static_cast<uint8_t>(GetFRUAreaAccessType::byte);
+
+    return ipmi::responseSuccess(fru.size(), accessType);
+}
+
+ipmi_ret_t getFruSdrCount(ipmi::Context::ptr, size_t& count)
+{
+    count = deviceHashes.size();
+    return IPMI_CC_OK;
+}
+
+ipmi_ret_t getFruSdrs([[maybe_unused]] ipmi::Context::ptr ctx, size_t index,
+                      get_sdr::SensorDataFruRecord& resp)
+{
+    if (deviceHashes.size() < index)
+    {
+        return IPMI_CC_INVALID_FIELD_REQUEST;
+    }
+    auto device = deviceHashes.begin() + index;
+    uint16_t& bus = device->second.first;
+    uint8_t& address = device->second.second;
+
+    boost::container::flat_map<std::string, Value>* fruData = nullptr;
+    auto fru = std::find_if(
+        frus.begin(), frus.end(),
+        [bus, address, &fruData](ManagedEntry& entry) {
+            auto findFruDevice =
+                entry.second.find("xyz.openbmc_project.FruDevice");
+            if (findFruDevice == entry.second.end())
+            {
+                return false;
+            }
+            fruData = &(findFruDevice->second);
+            auto findBus = findFruDevice->second.find("BUS");
+            auto findAddress = findFruDevice->second.find("ADDRESS");
+            if (findBus == findFruDevice->second.end() ||
+                findAddress == findFruDevice->second.end())
+            {
+                return false;
+            }
+            if (std::get<uint32_t>(findBus->second) != bus)
+            {
+                return false;
+            }
+            if (std::get<uint32_t>(findAddress->second) != address)
+            {
+                return false;
+            }
+            return true;
+        });
+    if (fru == frus.end())
+    {
+        return IPMI_CC_RESPONSE_ERROR;
+    }
+    std::string name;
+
+#ifdef USING_ENTITY_MANAGER_DECORATORS
+
+    boost::container::flat_map<std::string, Value>* entityData = nullptr;
+
+    // todo: this should really use caching, this is a very inefficient lookup
+    boost::system::error_code ec;
+    ManagedObjectType entities = ipmi::callDbusMethod<ManagedObjectType>(
+        ctx, ec, "xyz.openbmc_project.EntityManager",
+        "/xyz/openbmc_project/inventory", "org.freedesktop.DBus.ObjectManager",
+        "GetManagedObjects");
+
+    if (ec)
+    {
+        lg2::error("GetMangagedObjects for ipmiStorageGetFruInvAreaInfo "
+                   "failed: {ERROR}",
+                   "ERROR", ec.message());
+
+        return ipmi::ccResponseError;
+    }
+
+    auto entity = std::find_if(
+        entities.begin(), entities.end(),
+        [bus, address, &entityData, &name](ManagedEntry& entry) {
+            auto findFruDevice = entry.second.find(
+                "xyz.openbmc_project.Inventory.Decorator.I2CDevice");
+            if (findFruDevice == entry.second.end())
+            {
+                return false;
+            }
+
+            // Integer fields added via Entity-Manager json are uint64_ts by
+            // default.
+            auto findBus = findFruDevice->second.find("Bus");
+            auto findAddress = findFruDevice->second.find("Address");
+
+            if (findBus == findFruDevice->second.end() ||
+                findAddress == findFruDevice->second.end())
+            {
+                return false;
+            }
+            if ((std::get<uint64_t>(findBus->second) != bus) ||
+                (std::get<uint64_t>(findAddress->second) != address))
+            {
+                return false;
+            }
+
+            auto fruName = findFruDevice->second.find("Name");
+            if (fruName != findFruDevice->second.end())
+            {
+                name = std::get<std::string>(fruName->second);
+            }
+
+            // At this point we found the device entry and should return
+            // true.
+            auto findIpmiDevice = entry.second.find(
+                "xyz.openbmc_project.Inventory.Decorator.Ipmi");
+            if (findIpmiDevice != entry.second.end())
+            {
+                entityData = &(findIpmiDevice->second);
+            }
+
+            return true;
+        });
+
+    if (entity == entities.end())
+    {
+        if constexpr (DEBUG)
+        {
+            std::fprintf(stderr, "Ipmi or FruDevice Decorator interface "
+                                 "not found for Fru\n");
+        }
+    }
+
+#endif
+
+    std::vector<std::string> nameProperties = {
+        "PRODUCT_PRODUCT_NAME",  "BOARD_PRODUCT_NAME",   "PRODUCT_PART_NUMBER",
+        "BOARD_PART_NUMBER",     "PRODUCT_MANUFACTURER", "BOARD_MANUFACTURER",
+        "PRODUCT_SERIAL_NUMBER", "BOARD_SERIAL_NUMBER"};
+
+    for (const std::string& prop : nameProperties)
+    {
+        auto findProp = fruData->find(prop);
+        if (findProp != fruData->end())
+        {
+            name = std::get<std::string>(findProp->second);
+            break;
+        }
+    }
+
+    if (name.empty())
+    {
+        name = "UNKNOWN";
+    }
+    if (name.size() > maxFruSdrNameSize)
+    {
+        name = name.substr(0, maxFruSdrNameSize);
+    }
+    size_t sizeDiff = maxFruSdrNameSize - name.size();
+
+    resp.header.record_id_lsb = 0x0; // calling code is to implement these
+    resp.header.record_id_msb = 0x0;
+    resp.header.sdr_version = ipmiSdrVersion;
+    resp.header.record_type = get_sdr::SENSOR_DATA_FRU_RECORD;
+    resp.header.record_length = sizeof(resp.body) + sizeof(resp.key) - sizeDiff;
+    resp.key.deviceAddress = 0x20;
+    resp.key.fruID = device->first;
+    resp.key.accessLun = 0x80; // logical / physical fru device
+    resp.key.channelNumber = 0x0;
+    resp.body.reserved = 0x0;
+    resp.body.deviceType = 0x10;
+    resp.body.deviceTypeModifier = 0x0;
+
+    uint8_t entityID = 0;
+    uint8_t entityInstance = 0x1;
+
+#ifdef USING_ENTITY_MANAGER_DECORATORS
+    if (entityData)
+    {
+        auto entityIdProperty = entityData->find("EntityId");
+        auto entityInstanceProperty = entityData->find("EntityInstance");
+
+        if (entityIdProperty != entityData->end())
+        {
+            entityID = static_cast<uint8_t>(
+                std::get<uint64_t>(entityIdProperty->second));
+        }
+        if (entityInstanceProperty != entityData->end())
+        {
+            entityInstance = static_cast<uint8_t>(
+                std::get<uint64_t>(entityInstanceProperty->second));
+        }
+    }
+#endif
+
+    resp.body.entityID = entityID;
+    resp.body.entityInstance = entityInstance;
+
+    resp.body.oem = 0x0;
+    resp.body.deviceIDLen = ipmi::storage::typeASCIILatin8 | name.size();
+    name.copy(resp.body.deviceID, name.size());
+
+    return IPMI_CC_OK;
+}
+
+static bool getSELLogFiles(std::vector<std::filesystem::path>& selLogFiles)
+{
+    // Loop through the directory looking for ipmi_sel log files
+    for (const std::filesystem::directory_entry& dirEnt :
+         std::filesystem::directory_iterator(
+             dynamic_sensors::ipmi::sel::selLogDir))
+    {
+        std::string filename = dirEnt.path().filename();
+        if (boost::starts_with(filename,
+                               dynamic_sensors::ipmi::sel::selLogFilename))
+        {
+            // If we find an ipmi_sel log file, save the path
+            selLogFiles.emplace_back(
+                dynamic_sensors::ipmi::sel::selLogDir / filename);
+        }
+    }
+    // As the log files rotate, they are appended with a ".#" that is higher for
+    // the older logs. Since we don't expect more than 10 log files, we
+    // can just sort the list to get them in order from newest to oldest
+    std::sort(selLogFiles.begin(), selLogFiles.end());
+
+    return !selLogFiles.empty();
+}
+
+static int countSELEntries()
+{
+    // Get the list of ipmi_sel log files
+    std::vector<std::filesystem::path> selLogFiles;
+    if (!getSELLogFiles(selLogFiles))
+    {
+        return 0;
+    }
+    int numSELEntries = 0;
+    // Loop through each log file and count the number of logs
+    for (const std::filesystem::path& file : selLogFiles)
+    {
+        std::ifstream logStream(file);
+        if (!logStream.is_open())
+        {
+            continue;
+        }
+
+        std::string line;
+        while (std::getline(logStream, line))
+        {
+            numSELEntries++;
+        }
+    }
+    return numSELEntries;
+}
+
+static bool findSELEntry(const int recordID,
+                         const std::vector<std::filesystem::path>& selLogFiles,
+                         std::string& entry)
+{
+    // Record ID is the first entry field following the timestamp. It is
+    // preceded by a space and followed by a comma
+    std::string search = " " + std::to_string(recordID) + ",";
+
+    // Loop through the ipmi_sel log entries
+    for (const std::filesystem::path& file : selLogFiles)
+    {
+        std::ifstream logStream(file);
+        if (!logStream.is_open())
+        {
+            continue;
+        }
+
+        while (std::getline(logStream, entry))
+        {
+            // Check if the record ID matches
+            if (entry.find(search) != std::string::npos)
+            {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+static uint16_t getNextRecordID(
+    const uint16_t recordID,
+    const std::vector<std::filesystem::path>& selLogFiles)
+{
+    uint16_t nextRecordID = recordID + 1;
+    std::string entry;
+    if (findSELEntry(nextRecordID, selLogFiles, entry))
+    {
+        return nextRecordID;
+    }
+    else
+    {
+        return ipmi::sel::lastEntry;
+    }
+}
+
+static int fromHexStr(const std::string& hexStr, std::vector<uint8_t>& data)
+{
+    for (unsigned int i = 0; i < hexStr.size(); i += 2)
+    {
+        try
+        {
+            data.push_back(static_cast<uint8_t>(
+                std::stoul(hexStr.substr(i, 2), nullptr, 16)));
+        }
+        catch (const std::invalid_argument& e)
+        {
+            lg2::error("Invalid argument: {ERROR}", "ERROR", e);
+            return -1;
+        }
+        catch (const std::out_of_range& e)
+        {
+            lg2::error("Out of range: {ERROR}", "ERROR", e);
+            return -1;
+        }
+    }
+    return 0;
+}
+
+ipmi::RspType<uint8_t,  // SEL version
+              uint16_t, // SEL entry count
+              uint16_t, // free space
+              uint32_t, // last add timestamp
+              uint32_t, // last erase timestamp
+              uint8_t>  // operation support
+    ipmiStorageGetSELInfo()
+{
+    constexpr uint8_t selVersion = ipmi::sel::selVersion;
+    uint16_t entries = countSELEntries();
+    uint32_t addTimeStamp = dynamic_sensors::ipmi::sel::getFileTimestamp(
+        dynamic_sensors::ipmi::sel::selLogDir /
+        dynamic_sensors::ipmi::sel::selLogFilename);
+    uint32_t eraseTimeStamp = dynamic_sensors::ipmi::sel::erase_time::get();
+    constexpr uint8_t operationSupport =
+        dynamic_sensors::ipmi::sel::selOperationSupport;
+    constexpr uint16_t freeSpace =
+        0xffff; // Spec indicates that more than 64kB is free
+
+    return ipmi::responseSuccess(selVersion, entries, freeSpace, addTimeStamp,
+                                 eraseTimeStamp, operationSupport);
+}
+
+using systemEventType = std::tuple<
+    uint32_t, // Timestamp
+    uint16_t, // Generator ID
+    uint8_t,  // EvM Rev
+    uint8_t,  // Sensor Type
+    uint8_t,  // Sensor Number
+    uint7_t,  // Event Type
+    bool,     // Event Direction
+    std::array<uint8_t, dynamic_sensors::ipmi::sel::systemEventSize>>; // Event
+                                                                       // Data
+using oemTsEventType = std::tuple<
+    uint32_t, // Timestamp
+    std::array<uint8_t, dynamic_sensors::ipmi::sel::oemTsEventSize>>; // Event
+                                                                      // Data
+using oemEventType =
+    std::array<uint8_t, dynamic_sensors::ipmi::sel::oemEventSize>; // Event Data
+
+ipmi::RspType<uint16_t,                   // Next Record ID
+              uint16_t,                   // Record ID
+              uint8_t,                    // Record Type
+              std::variant<systemEventType, oemTsEventType,
+                           oemEventType>> // Record Content
+    ipmiStorageGetSELEntry(uint16_t reservationID, uint16_t targetID,
+                           uint8_t offset, uint8_t size)
+{
+    // Only support getting the entire SEL record. If a partial size or non-zero
+    // offset is requested, return an error
+    if (offset != 0 || size != ipmi::sel::entireRecord)
+    {
+        return ipmi::responseRetBytesUnavailable();
+    }
+
+    // Check the reservation ID if one is provided or required (only if the
+    // offset is non-zero)
+    if (reservationID != 0 || offset != 0)
+    {
+        if (!checkSELReservation(reservationID))
+        {
+            return ipmi::responseInvalidReservationId();
+        }
+    }
+
+    // Get the ipmi_sel log files
+    std::vector<std::filesystem::path> selLogFiles;
+    if (!getSELLogFiles(selLogFiles))
+    {
+        return ipmi::responseSensorInvalid();
+    }
+
+    std::string targetEntry;
+
+    if (targetID == ipmi::sel::firstEntry)
+    {
+        // The first entry will be at the top of the oldest log file
+        std::ifstream logStream(selLogFiles.back());
+        if (!logStream.is_open())
+        {
+            return ipmi::responseUnspecifiedError();
+        }
+
+        if (!std::getline(logStream, targetEntry))
+        {
+            return ipmi::responseUnspecifiedError();
+        }
+    }
+    else if (targetID == ipmi::sel::lastEntry)
+    {
+        // The last entry will be at the bottom of the newest log file
+        std::ifstream logStream(selLogFiles.front());
+        if (!logStream.is_open())
+        {
+            return ipmi::responseUnspecifiedError();
+        }
+
+        std::string line;
+        while (std::getline(logStream, line))
+        {
+            targetEntry = line;
+        }
+    }
+    else
+    {
+        if (!findSELEntry(targetID, selLogFiles, targetEntry))
+        {
+            return ipmi::responseSensorInvalid();
+        }
+    }
+
+    // The format of the ipmi_sel message is "<Timestamp>
+    // <ID>,<Type>,<EventData>,[<Generator ID>,<Path>,<Direction>]".
+    // First get the Timestamp
+    size_t space = targetEntry.find_first_of(" ");
+    if (space == std::string::npos)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+    std::string entryTimestamp = targetEntry.substr(0, space);
+    // Then get the log contents
+    size_t entryStart = targetEntry.find_first_not_of(" ", space);
+    if (entryStart == std::string::npos)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+    std::string_view entry(targetEntry);
+    entry.remove_prefix(entryStart);
+    // Use split to separate the entry into its fields
+    std::vector<std::string> targetEntryFields;
+    boost::split(targetEntryFields, entry, boost::is_any_of(","),
+                 boost::token_compress_on);
+    if (targetEntryFields.size() < 3)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+    std::string& recordIDStr = targetEntryFields[0];
+    std::string& recordTypeStr = targetEntryFields[1];
+    std::string& eventDataStr = targetEntryFields[2];
+
+    uint16_t recordID;
+    uint8_t recordType;
+    try
+    {
+        recordID = std::stoul(recordIDStr);
+        recordType = std::stoul(recordTypeStr, nullptr, 16);
+    }
+    catch (const std::invalid_argument&)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+    uint16_t nextRecordID = getNextRecordID(recordID, selLogFiles);
+    std::vector<uint8_t> eventDataBytes;
+    if (fromHexStr(eventDataStr, eventDataBytes) < 0)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    if (recordType == dynamic_sensors::ipmi::sel::systemEvent)
+    {
+        // Get the timestamp
+        std::tm timeStruct = {};
+        std::istringstream entryStream(entryTimestamp);
+
+        uint32_t timestamp = ipmi::sel::invalidTimeStamp;
+        if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
+        {
+            timeStruct.tm_isdst = -1;
+            timestamp = std::mktime(&timeStruct);
+        }
+
+        // Set the event message revision
+        uint8_t evmRev = dynamic_sensors::ipmi::sel::eventMsgRev;
+
+        uint16_t generatorID = 0;
+        uint8_t sensorType = 0;
+        uint16_t sensorAndLun = 0;
+        uint8_t sensorNum = 0xFF;
+        uint7_t eventType = 0;
+        bool eventDir = 0;
+        // System type events should have six fields
+        if (targetEntryFields.size() >= 6)
+        {
+            std::string& generatorIDStr = targetEntryFields[3];
+            std::string& sensorPath = targetEntryFields[4];
+            std::string& eventDirStr = targetEntryFields[5];
+
+            // Get the generator ID
+            try
+            {
+                generatorID = std::stoul(generatorIDStr, nullptr, 16);
+            }
+            catch (const std::invalid_argument&)
+            {
+                std::cerr << "Invalid Generator ID\n";
+            }
+
+            // Get the sensor type, sensor number, and event type for the sensor
+            sensorType = getSensorTypeFromPath(sensorPath);
+            sensorAndLun = getSensorNumberFromPath(sensorPath);
+            sensorNum = static_cast<uint8_t>(sensorAndLun);
+            if ((generatorID & 0x0001) == 0)
+            {
+                // IPMB Address
+                generatorID |= sensorAndLun & 0x0300;
+            }
+            else
+            {
+                // system software
+                generatorID |= sensorAndLun >> 8;
+            }
+            eventType = getSensorEventTypeFromPath(sensorPath);
+
+            // Get the event direction
+            try
+            {
+                eventDir = std::stoul(eventDirStr) ? 0 : 1;
+            }
+            catch (const std::invalid_argument&)
+            {
+                std::cerr << "Invalid Event Direction\n";
+            }
+        }
+
+        // Only keep the eventData bytes that fit in the record
+        std::array<uint8_t, dynamic_sensors::ipmi::sel::systemEventSize>
+            eventData{};
+        std::copy_n(eventDataBytes.begin(),
+                    std::min(eventDataBytes.size(), eventData.size()),
+                    eventData.begin());
+
+        return ipmi::responseSuccess(
+            nextRecordID, recordID, recordType,
+            systemEventType{timestamp, generatorID, evmRev, sensorType,
+                            sensorNum, eventType, eventDir, eventData});
+    }
+
+    if (recordType >= dynamic_sensors::ipmi::sel::oemTsEventFirst &&
+        recordType <= dynamic_sensors::ipmi::sel::oemTsEventLast)
+    {
+        // Get the timestamp
+        std::tm timeStruct = {};
+        std::istringstream entryStream(entryTimestamp);
+
+        uint32_t timestamp = ipmi::sel::invalidTimeStamp;
+        if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
+        {
+            timeStruct.tm_isdst = -1;
+            timestamp = std::mktime(&timeStruct);
+        }
+
+        // Only keep the bytes that fit in the record
+        std::array<uint8_t, dynamic_sensors::ipmi::sel::oemTsEventSize>
+            eventData{};
+        std::copy_n(eventDataBytes.begin(),
+                    std::min(eventDataBytes.size(), eventData.size()),
+                    eventData.begin());
+
+        return ipmi::responseSuccess(nextRecordID, recordID, recordType,
+                                     oemTsEventType{timestamp, eventData});
+    }
+
+    if (recordType >= dynamic_sensors::ipmi::sel::oemEventFirst)
+    {
+        // Only keep the bytes that fit in the record
+        std::array<uint8_t, dynamic_sensors::ipmi::sel::oemEventSize>
+            eventData{};
+        std::copy_n(eventDataBytes.begin(),
+                    std::min(eventDataBytes.size(), eventData.size()),
+                    eventData.begin());
+
+        return ipmi::responseSuccess(nextRecordID, recordID, recordType,
+                                     eventData);
+    }
+
+    return ipmi::responseUnspecifiedError();
+}
+
+/*
+Unused arguments
+  uint16_t recordID, uint8_t recordType, uint32_t timestamp,
+  uint16_t generatorID, uint8_t evmRev, uint8_t sensorType, uint8_t sensorNum,
+  uint8_t eventType, uint8_t eventData1, uint8_t eventData2,
+  uint8_t eventData3
+*/
+ipmi::RspType<uint16_t> ipmiStorageAddSELEntry(
+    uint16_t, uint8_t, uint32_t, uint16_t, uint8_t, uint8_t, uint8_t, uint8_t,
+    uint8_t, uint8_t, uint8_t)
+{
+    // Per the IPMI spec, need to cancel any reservation when a SEL entry is
+    // added
+    cancelSELReservation();
+
+    uint16_t responseID = 0xFFFF;
+    return ipmi::responseSuccess(responseID);
+}
+
+ipmi::RspType<uint8_t> ipmiStorageClearSEL(
+    ipmi::Context::ptr ctx, uint16_t reservationID,
+    const std::array<uint8_t, 3>& clr, uint8_t eraseOperation)
+{
+    if (!checkSELReservation(reservationID))
+    {
+        return ipmi::responseInvalidReservationId();
+    }
+
+    static constexpr std::array<uint8_t, 3> clrExpected = {'C', 'L', 'R'};
+    if (clr != clrExpected)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    // Erasure status cannot be fetched, so always return erasure status as
+    // `erase completed`.
+    if (eraseOperation == ipmi::sel::getEraseStatus)
+    {
+        return ipmi::responseSuccess(ipmi::sel::eraseComplete);
+    }
+
+    // Check that initiate erase is correct
+    if (eraseOperation != ipmi::sel::initiateErase)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    // Per the IPMI spec, need to cancel any reservation when the SEL is
+    // cleared
+    cancelSELReservation();
+
+    boost::system::error_code ec =
+        ipmi::callDbusMethod(ctx, "xyz.openbmc_project.Logging.IPMI",
+                             "/xyz/openbmc_project/Logging/IPMI",
+                             "xyz.openbmc_project.Logging.IPMI", "Clear");
+    if (ec)
+    {
+        std::cerr << "error in clear SEL: " << ec.message() << std::endl;
+        return ipmi::responseUnspecifiedError();
+    }
+
+    return ipmi::responseSuccess(ipmi::sel::eraseComplete);
+}
+
+std::vector<uint8_t> getType8SDRs(
+    ipmi::sensor::EntityInfoMap::const_iterator& entity, uint16_t recordId)
+{
+    std::vector<uint8_t> resp;
+    get_sdr::SensorDataEntityRecord data{};
+
+    /* Header */
+    get_sdr::header::set_record_id(recordId, &(data.header));
+    // Based on IPMI Spec v2.0 rev 1.1
+    data.header.sdr_version = SDR_VERSION;
+    data.header.record_type = 0x08;
+    data.header.record_length = sizeof(data.key) + sizeof(data.body);
+
+    /* Key */
+    data.key.containerEntityId = entity->second.containerEntityId;
+    data.key.containerEntityInstance = entity->second.containerEntityInstance;
+    get_sdr::key::set_flags(entity->second.isList, entity->second.isLinked,
+                            &(data.key));
+    data.key.entityId1 = entity->second.containedEntities[0].first;
+    data.key.entityInstance1 = entity->second.containedEntities[0].second;
+
+    /* Body */
+    data.body.entityId2 = entity->second.containedEntities[1].first;
+    data.body.entityInstance2 = entity->second.containedEntities[1].second;
+    data.body.entityId3 = entity->second.containedEntities[2].first;
+    data.body.entityInstance3 = entity->second.containedEntities[2].second;
+    data.body.entityId4 = entity->second.containedEntities[3].first;
+    data.body.entityInstance4 = entity->second.containedEntities[3].second;
+
+    resp.insert(resp.end(), (uint8_t*)&data, ((uint8_t*)&data) + sizeof(data));
+
+    return resp;
+}
+
+std::vector<uint8_t> getType12SDRs(uint16_t index, uint16_t recordId)
+{
+    std::vector<uint8_t> resp;
+    if (index == 0)
+    {
+        std::string bmcName = "Basbrd Mgmt Ctlr";
+        Type12Record bmc(recordId, 0x20, 0, 0, 0xbf, 0x2e, 1, 0, bmcName);
+        uint8_t* bmcPtr = reinterpret_cast<uint8_t*>(&bmc);
+        resp.insert(resp.end(), bmcPtr, bmcPtr + sizeof(Type12Record));
+    }
+    else if (index == 1)
+    {
+        std::string meName = "Mgmt Engine";
+        Type12Record me(recordId, 0x2c, 6, 0x24, 0x21, 0x2e, 2, 0, meName);
+        uint8_t* mePtr = reinterpret_cast<uint8_t*>(&me);
+        resp.insert(resp.end(), mePtr, mePtr + sizeof(Type12Record));
+    }
+    else
+    {
+        throw std::runtime_error(
+            "getType12SDRs:: Illegal index " + std::to_string(index));
+    }
+
+    return resp;
+}
+
+void registerStorageFunctions()
+{
+    createTimers();
+    startMatch();
+
+    // <Get FRU Inventory Area Info>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdGetFruInventoryAreaInfo,
+                          ipmi::Privilege::User, ipmiStorageGetFruInvAreaInfo);
+    // <READ FRU Data>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdReadFruData, ipmi::Privilege::User,
+                          ipmiStorageReadFruData);
+
+    // <WRITE FRU Data>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdWriteFruData,
+                          ipmi::Privilege::Operator, ipmiStorageWriteFruData);
+
+    // <Get SEL Info>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User,
+                          ipmiStorageGetSELInfo);
+
+    // <Get SEL Entry>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdGetSelEntry, ipmi::Privilege::User,
+                          ipmiStorageGetSELEntry);
+
+    // <Add SEL Entry>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdAddSelEntry,
+                          ipmi::Privilege::Operator, ipmiStorageAddSELEntry);
+
+    // <Clear SEL>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdClearSel, ipmi::Privilege::Operator,
+                          ipmiStorageClearSEL);
+}
+} // namespace storage
+} // namespace ipmi
diff --git a/dcmihandler.cpp b/dcmihandler.cpp
new file mode 100644
index 0000000..769081a
--- /dev/null
+++ b/dcmihandler.cpp
@@ -0,0 +1,1245 @@
+#include "config.h"
+
+#include "dcmihandler.hpp"
+
+#include "user_channel/channel_layer.hpp"
+
+#include <ipmid/api.hpp>
+#include <ipmid/utils.hpp>
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/Network/EthernetInterface/server.hpp>
+
+#include <bitset>
+#include <cmath>
+#include <fstream>
+#include <variant>
+
+using namespace phosphor::logging;
+using sdbusplus::server::xyz::openbmc_project::network::EthernetInterface;
+
+using InternalFailure =
+    sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+
+void registerNetFnDcmiFunctions() __attribute__((constructor));
+
+constexpr auto pcapPath = "/xyz/openbmc_project/control/host0/power_cap";
+constexpr auto pcapInterface = "xyz.openbmc_project.Control.Power.Cap";
+
+constexpr auto powerCapProp = "PowerCap";
+constexpr auto powerCapEnableProp = "PowerCapEnable";
+
+using namespace phosphor::logging;
+
+namespace dcmi
+{
+constexpr auto assetTagMaxOffset = 62;
+constexpr auto assetTagMaxSize = 63;
+constexpr auto maxBytes = 16;
+constexpr size_t maxCtrlIdStrLen = 63;
+
+constexpr uint8_t parameterRevision = 2;
+constexpr uint8_t specMajorVersion = 1;
+constexpr uint8_t specMinorVersion = 5;
+constexpr auto sensorValueIntf = "xyz.openbmc_project.Sensor.Value";
+constexpr auto sensorValueProp = "Value";
+constexpr uint8_t configParameterRevision = 1;
+constexpr auto option12Mask = 0x01;
+constexpr auto activateDhcpReply = 0x00;
+constexpr uint8_t dhcpTiming1 = 0x04;  // 4 sec
+constexpr uint16_t dhcpTiming2 = 0x78; // 120 sec
+constexpr uint16_t dhcpTiming3 = 0x40; // 60 sec
+// When DHCP Option 12 is enabled the string "SendHostName=true" will be
+// added into n/w configuration file and the parameter
+// SendHostNameEnabled will set to true.
+constexpr auto dhcpOpt12Enabled = "SendHostNameEnabled";
+
+enum class DCMIConfigParameters : uint8_t
+{
+    ActivateDHCP = 1,
+    DiscoveryConfig,
+    DHCPTiming1,
+    DHCPTiming2,
+    DHCPTiming3,
+};
+
+// Refer Table 6-14, DCMI Entity ID Extension, DCMI v1.5 spec
+static const std::map<uint8_t, std::string> entityIdToName{
+    {0x40, "inlet"}, {0x37, "inlet"},     {0x41, "cpu"},
+    {0x03, "cpu"},   {0x42, "baseboard"}, {0x07, "baseboard"}};
+
+nlohmann::json parseJSONConfig(const std::string& configFile)
+{
+    std::ifstream jsonFile(configFile);
+    if (!jsonFile.is_open())
+    {
+        lg2::error("Temperature readings JSON file not found");
+        elog<InternalFailure>();
+    }
+
+    auto data = nlohmann::json::parse(jsonFile, nullptr, false);
+    if (data.is_discarded())
+    {
+        lg2::error("Temperature readings JSON parser failure");
+        elog<InternalFailure>();
+    }
+
+    return data;
+}
+
+bool isDCMIPowerMgmtSupported()
+{
+    static bool parsed = false;
+    static bool supported = false;
+    if (!parsed)
+    {
+        auto data = parseJSONConfig(gDCMICapabilitiesConfig);
+
+        supported = (gDCMIPowerMgmtSupported ==
+                     data.value(gDCMIPowerMgmtCapability, 0));
+    }
+    return supported;
+}
+
+std::optional<uint32_t> getPcap(ipmi::Context::ptr& ctx)
+{
+    std::string service{};
+    boost::system::error_code ec =
+        ipmi::getService(ctx, pcapInterface, pcapPath, service);
+    if (ec.value())
+    {
+        return std::nullopt;
+    }
+    uint32_t pcap{};
+    ec = ipmi::getDbusProperty(ctx, service, pcapPath, pcapInterface,
+                               powerCapProp, pcap);
+    if (ec.value())
+    {
+        lg2::error("Error in getPcap prop: {ERROR}", "ERROR", ec.message());
+        elog<InternalFailure>();
+        return std::nullopt;
+    }
+    return pcap;
+}
+
+std::optional<bool> getPcapEnabled(ipmi::Context::ptr& ctx)
+{
+    std::string service{};
+    boost::system::error_code ec =
+        ipmi::getService(ctx, pcapInterface, pcapPath, service);
+    if (ec.value())
+    {
+        return std::nullopt;
+    }
+    bool pcapEnabled{};
+    ec = ipmi::getDbusProperty(ctx, service, pcapPath, pcapInterface,
+                               powerCapEnableProp, pcapEnabled);
+    if (ec.value())
+    {
+        lg2::error("Error in getPcap prop");
+        elog<InternalFailure>();
+        return std::nullopt;
+    }
+    return pcapEnabled;
+}
+
+bool setPcap(ipmi::Context::ptr& ctx, const uint32_t powerCap)
+{
+    std::string service{};
+    boost::system::error_code ec =
+        ipmi::getService(ctx, pcapInterface, pcapPath, service);
+    if (ec.value())
+    {
+        return false;
+    }
+
+    ec = ipmi::setDbusProperty(ctx, service, pcapPath, pcapInterface,
+                               powerCapProp, powerCap);
+    if (ec.value())
+    {
+        lg2::error("Error in setPcap property: {ERROR}", "ERROR", ec.message());
+        elog<InternalFailure>();
+        return false;
+    }
+    return true;
+}
+
+bool setPcapEnable(ipmi::Context::ptr& ctx, bool enabled)
+{
+    std::string service{};
+    boost::system::error_code ec =
+        ipmi::getService(ctx, pcapInterface, pcapPath, service);
+    if (ec.value())
+    {
+        return false;
+    }
+
+    ec = ipmi::setDbusProperty(ctx, service, pcapPath, pcapInterface,
+                               powerCapEnableProp, enabled);
+    if (ec.value())
+    {
+        lg2::error("Error in setPcapEnabled property: {ERROR}", "ERROR",
+                   ec.message());
+        elog<InternalFailure>();
+        return false;
+    }
+    return true;
+}
+
+std::optional<std::string> readAssetTag(ipmi::Context::ptr& ctx)
+{
+    // Read the object tree with the inventory root to figure out the object
+    // that has implemented the Asset tag interface.
+    ipmi::DbusObjectInfo objectInfo;
+    boost::system::error_code ec = getDbusObject(
+        ctx, dcmi::assetTagIntf, ipmi::sensor::inventoryRoot, "", objectInfo);
+    if (ec.value())
+    {
+        return std::nullopt;
+    }
+
+    std::string assetTag{};
+    ec =
+        ipmi::getDbusProperty(ctx, objectInfo.second, objectInfo.first,
+                              dcmi::assetTagIntf, dcmi::assetTagProp, assetTag);
+    if (ec.value())
+    {
+        lg2::error("Error in reading asset tag: {ERROR}", "ERROR",
+                   ec.message());
+        elog<InternalFailure>();
+        return std::nullopt;
+    }
+
+    return assetTag;
+}
+
+bool writeAssetTag(ipmi::Context::ptr& ctx, const std::string& assetTag)
+{
+    // Read the object tree with the inventory root to figure out the object
+    // that has implemented the Asset tag interface.
+    ipmi::DbusObjectInfo objectInfo;
+    boost::system::error_code ec = getDbusObject(
+        ctx, dcmi::assetTagIntf, ipmi::sensor::inventoryRoot, "", objectInfo);
+    if (ec.value())
+    {
+        return false;
+    }
+
+    ec =
+        ipmi::setDbusProperty(ctx, objectInfo.second, objectInfo.first,
+                              dcmi::assetTagIntf, dcmi::assetTagProp, assetTag);
+    if (ec.value())
+    {
+        lg2::error("Error in writing asset tag: {ERROR}", "ERROR",
+                   ec.message());
+        elog<InternalFailure>();
+        return false;
+    }
+    return true;
+}
+
+std::optional<std::string> getHostName(ipmi::Context::ptr& ctx)
+{
+    std::string service{};
+    boost::system::error_code ec =
+        ipmi::getService(ctx, networkConfigIntf, networkConfigObj, service);
+    if (ec.value())
+    {
+        return std::nullopt;
+    }
+    std::string hostname{};
+    ec = ipmi::getDbusProperty(ctx, service, networkConfigObj,
+                               networkConfigIntf, hostNameProp, hostname);
+    if (ec.value())
+    {
+        lg2::error("Error fetching hostname");
+        elog<InternalFailure>();
+        return std::nullopt;
+    }
+    return hostname;
+}
+
+std::optional<EthernetInterface::DHCPConf> getDHCPEnabled(
+    ipmi::Context::ptr& ctx)
+{
+    auto ethdevice = ipmi::getChannelName(ethernetDefaultChannelNum);
+    ipmi::DbusObjectInfo ethernetObj{};
+    boost::system::error_code ec = ipmi::getDbusObject(
+        ctx, ethernetIntf, networkRoot, ethdevice, ethernetObj);
+    if (ec.value())
+    {
+        return std::nullopt;
+    }
+    std::string service{};
+    ec = ipmi::getService(ctx, ethernetIntf, ethernetObj.first, service);
+    if (ec.value())
+    {
+        return std::nullopt;
+    }
+    std::string dhcpVal{};
+    ec = ipmi::getDbusProperty(ctx, service, ethernetObj.first, ethernetIntf,
+                               "DHCPEnabled", dhcpVal);
+    if (ec.value())
+    {
+        return std::nullopt;
+    }
+
+    return EthernetInterface::convertDHCPConfFromString(dhcpVal);
+}
+
+std::optional<bool> getDHCPOption(ipmi::Context::ptr& ctx,
+                                  const std::string& prop)
+{
+    ipmi::ObjectTree objectTree;
+    if (ipmi::getAllDbusObjects(ctx, networkRoot, dhcpIntf, objectTree))
+    {
+        return std::nullopt;
+    }
+
+    for (const auto& [path, serviceMap] : objectTree)
+    {
+        for (const auto& [service, object] : serviceMap)
+        {
+            bool value{};
+            if (ipmi::getDbusProperty(ctx, service, path, dhcpIntf, prop,
+                                      value))
+            {
+                return std::nullopt;
+            }
+
+            if (value)
+            {
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
+
+bool setDHCPOption(ipmi::Context::ptr& ctx, std::string prop, bool value)
+{
+    ipmi::ObjectTree objectTree;
+    if (ipmi::getAllDbusObjects(ctx, networkRoot, dhcpIntf, objectTree))
+    {
+        return false;
+    }
+
+    for (const auto& [path, serviceMap] : objectTree)
+    {
+        for (const auto& [service, object] : serviceMap)
+        {
+            if (ipmi::setDbusProperty(ctx, service, path, dhcpIntf, prop,
+                                      value))
+            {
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+} // namespace dcmi
+
+constexpr uint8_t exceptionPowerOff = 0x01;
+ipmi::RspType<uint16_t, // reserved
+              uint8_t,  // exception actions
+              uint16_t, // power limit requested in watts
+              uint32_t, // correction time in milliseconds
+              uint16_t, // reserved
+              uint16_t  // statistics sampling period in seconds
+              >
+    getPowerLimit(ipmi::Context::ptr ctx, uint16_t reserved)
+{
+    if (!dcmi::isDCMIPowerMgmtSupported())
+    {
+        return ipmi::responseInvalidCommand();
+    }
+    if (reserved)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    std::optional<uint16_t> pcapValue = dcmi::getPcap(ctx);
+    std::optional<bool> pcapEnable = dcmi::getPcapEnabled(ctx);
+    if (!pcapValue || !pcapEnable)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    constexpr uint16_t reserved1{};
+    constexpr uint16_t reserved2{};
+    /*
+     * Exception action if power limit is exceeded and cannot be controlled
+     * with the correction time limit is hardcoded to Hard Power Off system
+     * and log event to SEL.
+     */
+    constexpr uint8_t exception = exceptionPowerOff;
+    /*
+     * Correction time limit and Statistics sampling period is currently not
+     * populated.
+     */
+    constexpr uint32_t correctionTime{};
+    constexpr uint16_t statsPeriod{};
+    if (*pcapEnable == false)
+    {
+        constexpr ipmi::Cc responseNoPowerLimitSet = 0x80;
+        return ipmi::response(responseNoPowerLimitSet, reserved1, exception,
+                              *pcapValue, correctionTime, reserved2,
+                              statsPeriod);
+    }
+    return ipmi::responseSuccess(reserved1, exception, *pcapValue,
+                                 correctionTime, reserved2, statsPeriod);
+}
+
+ipmi::RspType<> setPowerLimit(ipmi::Context::ptr& ctx, uint16_t reserved1,
+                              uint8_t reserved2, uint8_t exceptionAction,
+                              uint16_t powerLimit, uint32_t correctionTime,
+                              uint16_t reserved3, uint16_t statsPeriod)
+{
+    if (!dcmi::isDCMIPowerMgmtSupported())
+    {
+        lg2::error("DCMI Power management is unsupported!");
+        return ipmi::responseInvalidCommand();
+    }
+
+    // Only process the power limit requested in watts. Return errors
+    // for other fields that are set
+    if (reserved1 || reserved2 || reserved3 || correctionTime || statsPeriod ||
+        exceptionAction != exceptionPowerOff)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    if (!dcmi::setPcap(ctx, powerLimit))
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    lg2::info("Set Power Cap: {POWERCAP}", "POWERCAP", powerLimit);
+
+    return ipmi::responseSuccess();
+}
+
+ipmi::RspType<> applyPowerLimit(ipmi::Context::ptr& ctx, bool enabled,
+                                uint7_t reserved1, uint16_t reserved2)
+{
+    if (!dcmi::isDCMIPowerMgmtSupported())
+    {
+        lg2::error("DCMI Power management is unsupported!");
+        return ipmi::responseInvalidCommand();
+    }
+    if (reserved1 || reserved2)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    if (!dcmi::setPcapEnable(ctx, enabled))
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    lg2::info("Set Power Cap Enable: {POWERCAPENABLE}", "POWERCAPENABLE",
+              enabled);
+
+    return ipmi::responseSuccess();
+}
+
+ipmi::RspType<uint8_t,          // total tag length
+              std::vector<char> // tag data
+              >
+    getAssetTag(ipmi::Context::ptr& ctx, uint8_t offset, uint8_t count)
+{
+    // Verify offset to read and number of bytes to read are not exceeding
+    // the range.
+    if ((offset > dcmi::assetTagMaxOffset) || (count > dcmi::maxBytes) ||
+        ((offset + count) > dcmi::assetTagMaxSize))
+    {
+        return ipmi::responseParmOutOfRange();
+    }
+
+    std::optional<std::string> assetTagResp = dcmi::readAssetTag(ctx);
+    if (!assetTagResp)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    std::string& assetTag = assetTagResp.value();
+    // If the asset tag is longer than 63 bytes, restrict it to 63 bytes to
+    // suit Get Asset Tag command.
+    if (assetTag.size() > dcmi::assetTagMaxSize)
+    {
+        assetTag.resize(dcmi::assetTagMaxSize);
+    }
+
+    if (offset >= assetTag.size())
+    {
+        return ipmi::responseParmOutOfRange();
+    }
+
+    // silently truncate reads beyond the end of assetTag
+    if ((offset + count) >= assetTag.size())
+    {
+        count = assetTag.size() - offset;
+    }
+
+    auto totalTagSize = static_cast<uint8_t>(assetTag.size());
+    std::vector<char> data{assetTag.begin() + offset,
+                           assetTag.begin() + offset + count};
+
+    return ipmi::responseSuccess(totalTagSize, data);
+}
+
+ipmi::RspType<uint8_t // new asset tag length
+              >
+    setAssetTag(ipmi::Context::ptr& ctx, uint8_t offset, uint8_t count,
+                const std::vector<char>& data)
+{
+    // Verify offset to read and number of bytes to read are not exceeding
+    // the range.
+    if ((offset > dcmi::assetTagMaxOffset) || (count > dcmi::maxBytes) ||
+        ((offset + count) > dcmi::assetTagMaxSize))
+    {
+        return ipmi::responseParmOutOfRange();
+    }
+    if (data.size() != count)
+    {
+        return ipmi::responseReqDataLenInvalid();
+    }
+
+    std::optional<std::string> assetTagResp = dcmi::readAssetTag(ctx);
+    if (!assetTagResp)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    std::string& assetTag = assetTagResp.value();
+
+    if (offset > assetTag.size())
+    {
+        return ipmi::responseParmOutOfRange();
+    }
+
+    // operation is to truncate at offset and append new data
+    assetTag.resize(offset);
+    assetTag.append(data.begin(), data.end());
+
+    if (!dcmi::writeAssetTag(ctx, assetTag))
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    auto totalTagSize = static_cast<uint8_t>(assetTag.size());
+    return ipmi::responseSuccess(totalTagSize);
+}
+
+ipmi::RspType<uint8_t,          // length
+              std::vector<char> // data
+              >
+    getMgmntCtrlIdStr(ipmi::Context::ptr& ctx, uint8_t offset, uint8_t count)
+{
+    if (count > dcmi::maxBytes || offset + count > dcmi::maxCtrlIdStrLen)
+    {
+        return ipmi::responseParmOutOfRange();
+    }
+
+    std::optional<std::string> hostnameResp = dcmi::getHostName(ctx);
+    if (!hostnameResp)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    std::string& hostname = hostnameResp.value();
+    // If the id string is longer than 63 bytes, restrict it to 63 bytes to
+    // suit set management ctrl str  command.
+    if (hostname.size() > dcmi::maxCtrlIdStrLen)
+    {
+        hostname.resize(dcmi::maxCtrlIdStrLen);
+    }
+
+    if (offset >= hostname.size())
+    {
+        return ipmi::responseParmOutOfRange();
+    }
+
+    // silently truncate reads beyond the end of hostname
+    if ((offset + count) >= hostname.size())
+    {
+        count = hostname.size() - offset;
+    }
+
+    auto nameSize = static_cast<uint8_t>(hostname.size());
+    std::vector<char> data{hostname.begin() + offset,
+                           hostname.begin() + offset + count};
+
+    return ipmi::responseSuccess(nameSize, data);
+}
+
+ipmi::RspType<uint8_t> setMgmntCtrlIdStr(ipmi::Context::ptr& ctx,
+                                         uint8_t offset, uint8_t count,
+                                         std::vector<char> data)
+{
+    if ((offset > dcmi::maxCtrlIdStrLen) || (count > dcmi::maxBytes) ||
+        ((offset + count) > dcmi::maxCtrlIdStrLen))
+    {
+        return ipmi::responseParmOutOfRange();
+    }
+    if (data.size() != count)
+    {
+        return ipmi::responseReqDataLenInvalid();
+    }
+    bool terminalWrite{data.back() == '\0'};
+    if (terminalWrite)
+    {
+        // remove the null termination from the data (no need with std::string)
+        data.resize(count - 1);
+    }
+
+    static std::string hostname{};
+    // read in the current value if not starting at offset 0
+    if (hostname.size() == 0 && offset != 0)
+    {
+        /* read old ctrlIdStr */
+        std::optional<std::string> hostnameResp = dcmi::getHostName(ctx);
+        if (!hostnameResp)
+        {
+            return ipmi::responseUnspecifiedError();
+        }
+        hostname = hostnameResp.value();
+        hostname.resize(offset);
+    }
+
+    // operation is to truncate at offset and append new data
+    hostname.append(data.begin(), data.end());
+
+    // do the update if this is the last write
+    if (terminalWrite)
+    {
+        boost::system::error_code ec = ipmi::setDbusProperty(
+            ctx, dcmi::networkServiceName, dcmi::networkConfigObj,
+            dcmi::networkConfigIntf, dcmi::hostNameProp, hostname);
+        hostname.clear();
+        if (ec.value())
+        {
+            return ipmi::responseUnspecifiedError();
+        }
+    }
+
+    auto totalIdSize = static_cast<uint8_t>(offset + count);
+    return ipmi::responseSuccess(totalIdSize);
+}
+
+ipmi::RspType<ipmi::message::Payload> getDCMICapabilities(uint8_t parameter)
+{
+    std::ifstream dcmiCapFile(dcmi::gDCMICapabilitiesConfig);
+    if (!dcmiCapFile.is_open())
+    {
+        lg2::error("DCMI Capabilities file not found");
+        return ipmi::responseUnspecifiedError();
+    }
+
+    auto data = nlohmann::json::parse(dcmiCapFile, nullptr, false);
+    if (data.is_discarded())
+    {
+        lg2::error("DCMI Capabilities JSON parser failure");
+        return ipmi::responseUnspecifiedError();
+    }
+
+    constexpr bool reserved1{};
+    constexpr uint5_t reserved5{};
+    constexpr uint7_t reserved7{};
+    constexpr uint8_t reserved8{};
+    constexpr uint16_t reserved16{};
+
+    ipmi::message::Payload payload;
+    payload.pack(dcmi::specMajorVersion, dcmi::specMinorVersion,
+                 dcmi::parameterRevision);
+
+    enum class DCMICapParameters : uint8_t
+    {
+        SupportedDcmiCaps = 0x01,             // Supported DCMI Capabilities
+        MandatoryPlatAttributes = 0x02,       // Mandatory Platform Attributes
+        OptionalPlatAttributes = 0x03,        // Optional Platform Attributes
+        ManageabilityAccessAttributes = 0x04, // Manageability Access Attributes
+    };
+
+    switch (static_cast<DCMICapParameters>(parameter))
+    {
+        case DCMICapParameters::SupportedDcmiCaps:
+        {
+            bool powerManagement = data.value("PowerManagement", 0);
+            bool oobSecondaryLan = data.value("OOBSecondaryLan", 0);
+            bool serialTMode = data.value("SerialTMODE", 0);
+            bool inBandSystemInterfaceChannel =
+                data.value("InBandSystemInterfaceChannel", 0);
+            payload.pack(reserved8, powerManagement, reserved7,
+                         inBandSystemInterfaceChannel, serialTMode,
+                         oobSecondaryLan, reserved5);
+            break;
+        }
+            // Mandatory Platform Attributes
+        case DCMICapParameters::MandatoryPlatAttributes:
+        {
+            bool selAutoRollOver = data.value("SELAutoRollOver", 0);
+            bool flushEntireSELUponRollOver =
+                data.value("FlushEntireSELUponRollOver", 0);
+            bool recordLevelSELFlushUponRollOver =
+                data.value("RecordLevelSELFlushUponRollOver", 0);
+            uint12_t numberOfSELEntries =
+                data.value("NumberOfSELEntries", 0xcac);
+            uint8_t tempMonitoringSamplingFreq =
+                data.value("TempMonitoringSamplingFreq", 0);
+            payload.pack(numberOfSELEntries, reserved1,
+                         recordLevelSELFlushUponRollOver,
+                         flushEntireSELUponRollOver, selAutoRollOver,
+                         reserved16, tempMonitoringSamplingFreq);
+            break;
+        }
+        // Optional Platform Attributes
+        case DCMICapParameters::OptionalPlatAttributes:
+        {
+            uint7_t powerMgmtDeviceTargetAddress =
+                data.value("PowerMgmtDeviceSlaveAddress", 0);
+            uint4_t bmcChannelNumber = data.value("BMCChannelNumber", 0);
+            uint4_t deviceRivision = data.value("DeviceRivision", 0);
+            payload.pack(powerMgmtDeviceTargetAddress, reserved1,
+                         deviceRivision, bmcChannelNumber);
+            break;
+        }
+        // Manageability Access Attributes
+        case DCMICapParameters::ManageabilityAccessAttributes:
+        {
+            uint8_t mandatoryPrimaryLanOOBSupport =
+                data.value("MandatoryPrimaryLanOOBSupport", 0xff);
+            uint8_t optionalSecondaryLanOOBSupport =
+                data.value("OptionalSecondaryLanOOBSupport", 0xff);
+            uint8_t optionalSerialOOBMTMODECapability =
+                data.value("OptionalSerialOOBMTMODECapability", 0xff);
+            payload.pack(mandatoryPrimaryLanOOBSupport,
+                         optionalSecondaryLanOOBSupport,
+                         optionalSerialOOBMTMODECapability);
+            break;
+        }
+        default:
+        {
+            lg2::error("Invalid input parameter");
+            return ipmi::responseInvalidFieldRequest();
+        }
+    }
+
+    return ipmi::responseSuccess(payload);
+}
+
+namespace dcmi
+{
+namespace temp_readings
+{
+
+std::tuple<bool, bool, uint8_t> readTemp(ipmi::Context::ptr& ctx,
+                                         const std::string& dbusService,
+                                         const std::string& dbusPath)
+{
+    // Read the temperature value from d-bus object. Need some conversion.
+    // As per the interface xyz.openbmc_project.Sensor.Value, the
+    // temperature is an double and in degrees C. It needs to be scaled by
+    // using the formula Value * 10^Scale. The ipmi spec has the temperature
+    // as a uint8_t, with a separate single bit for the sign.
+
+    ipmi::PropertyMap result{};
+    boost::system::error_code ec = ipmi::getAllDbusProperties(
+        ctx, dbusService, dbusPath, "xyz.openbmc_project.Sensor.Value", result);
+    if (ec.value())
+    {
+        return std::make_tuple(false, false, 0);
+    }
+    auto temperature =
+        std::visit(ipmi::VariantToDoubleVisitor(), result.at("Value"));
+    double absTemp = std::abs(temperature);
+
+    auto findFactor = result.find("Scale");
+    double factor = 0.0;
+    if (findFactor != result.end())
+    {
+        factor = std::visit(ipmi::VariantToDoubleVisitor(), findFactor->second);
+    }
+    double scale = std::pow(10, factor);
+
+    auto tempDegrees = absTemp * scale;
+    // Max absolute temp as per ipmi spec is 127.
+    constexpr auto maxTemp = 127;
+    if (tempDegrees > maxTemp)
+    {
+        tempDegrees = maxTemp;
+    }
+
+    return std::make_tuple(true, (temperature < 0),
+                           static_cast<uint8_t>(tempDegrees));
+}
+
+std::tuple<std::vector<std::tuple<uint7_t, bool, uint8_t>>, uint8_t> read(
+    ipmi::Context::ptr& ctx, const std::string& type, uint8_t instance,
+    size_t count)
+{
+    std::vector<std::tuple<uint7_t, bool, uint8_t>> response{};
+
+    auto data = parseJSONConfig(gDCMISensorsConfig);
+    static const std::vector<nlohmann::json> empty{};
+    std::vector<nlohmann::json> readings = data.value(type, empty);
+    for (const auto& j : readings)
+    {
+        // Max of 8 response data sets
+        if (response.size() == count)
+        {
+            break;
+        }
+
+        uint8_t instanceNum = j.value("instance", 0);
+        // Not in the instance range we're interested in
+        if (instanceNum < instance)
+        {
+            continue;
+        }
+
+        std::string path = j.value("dbus", "");
+        std::string service{};
+        boost::system::error_code ec = ipmi::getService(
+            ctx, "xyz.openbmc_project.Sensor.Value", path, service);
+        if (ec.value())
+        {
+            // not found on dbus
+            continue;
+        }
+
+        const auto& [ok, sign, temp] = readTemp(ctx, service, path);
+        if (ok)
+        {
+            response.emplace_back(uint7_t{temp}, sign, instanceNum);
+        }
+    }
+
+    auto totalInstances =
+        static_cast<uint8_t>(std::min(readings.size(), maxInstances));
+    return std::make_tuple(response, totalInstances);
+}
+
+} // namespace temp_readings
+} // namespace dcmi
+
+ipmi::RspType<uint8_t,                // total instances for entity id
+              uint8_t,                // number of instances in this reply
+              std::vector<            // zero or more of the following two bytes
+                  std::tuple<uint7_t, // temperature value
+                             bool,    // sign bit
+                             uint8_t  // entity instance
+                             >>>
+    getTempReadings(ipmi::Context::ptr& ctx, uint8_t sensorType,
+                    uint8_t entityId, uint8_t entityInstance,
+                    uint8_t instanceStart)
+{
+    auto it = dcmi::entityIdToName.find(entityId);
+    if (it == dcmi::entityIdToName.end())
+    {
+        lg2::error("Unknown Entity ID: {ENTITY_ID}", "ENTITY_ID", entityId);
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    if (sensorType != dcmi::temperatureSensorType)
+    {
+        lg2::error("Invalid sensor type: {SENSOR_TYPE}", "SENSOR_TYPE",
+                   sensorType);
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    uint8_t requestedRecords = (entityInstance == 0) ? dcmi::maxRecords : 1;
+
+    // Read requested instances
+    const auto& [temps, totalInstances] = dcmi::temp_readings::read(
+        ctx, it->second, instanceStart, requestedRecords);
+
+    auto numInstances = static_cast<uint8_t>(temps.size());
+
+    return ipmi::responseSuccess(totalInstances, numInstances, temps);
+}
+
+ipmi::RspType<> setDCMIConfParams(ipmi::Context::ptr& ctx, uint8_t parameter,
+                                  uint8_t setSelector,
+                                  ipmi::message::Payload& payload)
+{
+    if (setSelector)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+    // Take action based on the Parameter Selector
+    switch (static_cast<dcmi::DCMIConfigParameters>(parameter))
+    {
+        case dcmi::DCMIConfigParameters::ActivateDHCP:
+        {
+            uint7_t reserved{};
+            bool activate{};
+            if (payload.unpack(activate, reserved) || !payload.fullyUnpacked())
+            {
+                return ipmi::responseReqDataLenInvalid();
+            }
+            if (reserved)
+            {
+                return ipmi::responseInvalidFieldRequest();
+            }
+            std::optional<EthernetInterface::DHCPConf> dhcpEnabled =
+                dcmi::getDHCPEnabled(ctx);
+            if (!dhcpEnabled)
+            {
+                return ipmi::responseUnspecifiedError();
+            }
+            if (activate &&
+                (dhcpEnabled.value() != EthernetInterface::DHCPConf::none))
+            {
+                // When these conditions are met we have to trigger DHCP
+                // protocol restart using the latest parameter settings,
+                // but as per n/w manager design, each time when we
+                // update n/w parameters, n/w service is restarted. So
+                // we no need to take any action in this case.
+            }
+            break;
+        }
+        case dcmi::DCMIConfigParameters::DiscoveryConfig:
+        {
+            bool option12{};
+            uint6_t reserved1{};
+            bool randBackOff{};
+            if (payload.unpack(option12, reserved1, randBackOff) ||
+                !payload.fullyUnpacked())
+            {
+                return ipmi::responseReqDataLenInvalid();
+            }
+            // Systemd-networkd doesn't support Random Back off
+            if (reserved1 || randBackOff)
+            {
+                return ipmi::responseInvalidFieldRequest();
+            }
+            dcmi::setDHCPOption(ctx, dcmi::dhcpOpt12Enabled, option12);
+            break;
+        }
+        // Systemd-networkd doesn't allow to configure DHCP timigs
+        case dcmi::DCMIConfigParameters::DHCPTiming1:
+        case dcmi::DCMIConfigParameters::DHCPTiming2:
+        case dcmi::DCMIConfigParameters::DHCPTiming3:
+        default:
+            return ipmi::responseInvalidFieldRequest();
+    }
+    return ipmi::responseSuccess();
+}
+
+ipmi::RspType<ipmi::message::Payload> getDCMIConfParams(
+    ipmi::Context::ptr& ctx, uint8_t parameter, uint8_t setSelector)
+{
+    if (setSelector)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+    ipmi::message::Payload payload;
+    payload.pack(dcmi::specMajorVersion, dcmi::specMinorVersion,
+                 dcmi::configParameterRevision);
+
+    // Take action based on the Parameter Selector
+    switch (static_cast<dcmi::DCMIConfigParameters>(parameter))
+    {
+        case dcmi::DCMIConfigParameters::ActivateDHCP:
+            payload.pack(dcmi::activateDhcpReply);
+            break;
+        case dcmi::DCMIConfigParameters::DiscoveryConfig:
+        {
+            uint8_t discovery{};
+            std::optional<bool> enabled =
+                dcmi::getDHCPOption(ctx, dcmi::dhcpOpt12Enabled);
+            if (!enabled.has_value())
+            {
+                return ipmi::responseUnspecifiedError();
+            }
+            if (enabled.value())
+            {
+                discovery = dcmi::option12Mask;
+            }
+            payload.pack(discovery);
+            break;
+        }
+        // Get below values from Systemd-networkd source code
+        case dcmi::DCMIConfigParameters::DHCPTiming1:
+            payload.pack(dcmi::dhcpTiming1);
+            break;
+        case dcmi::DCMIConfigParameters::DHCPTiming2:
+            payload.pack(dcmi::dhcpTiming2);
+            break;
+        case dcmi::DCMIConfigParameters::DHCPTiming3:
+            payload.pack(dcmi::dhcpTiming3);
+            break;
+        default:
+            return ipmi::responseInvalidFieldRequest();
+    }
+
+    return ipmi::responseSuccess(payload);
+}
+
+static std::optional<uint16_t> readPower(ipmi::Context::ptr& ctx)
+{
+    std::ifstream sensorFile(POWER_READING_SENSOR);
+    std::string objectPath;
+    if (!sensorFile.is_open())
+    {
+        lg2::error(
+            "Power reading configuration file not found: {POWER_SENSOR_FILE}",
+            "POWER_SENSOR_FILE", std::string_view{POWER_READING_SENSOR});
+        return std::nullopt;
+    }
+
+    auto data = nlohmann::json::parse(sensorFile, nullptr, false);
+    if (data.is_discarded())
+    {
+        lg2::error("Error in parsing configuration file: {POWER_SENSOR_FILE}",
+                   "POWER_SENSOR_FILE", std::string_view{POWER_READING_SENSOR});
+        return std::nullopt;
+    }
+
+    objectPath = data.value("path", "");
+    if (objectPath.empty())
+    {
+        lg2::error(
+            "Power sensor D-Bus object path is empty: {POWER_SENSOR_FILE}",
+            "POWER_SENSOR_FILE", std::string_view{POWER_READING_SENSOR});
+        return std::nullopt;
+    }
+
+    // Return default value if failed to read from D-Bus object
+    std::string service{};
+    boost::system::error_code ec =
+        ipmi::getService(ctx, dcmi::sensorValueIntf, objectPath, service);
+    if (ec.value())
+    {
+        lg2::error("Failed to fetch service for D-Bus object, "
+                   "object path: {OBJECT_PATH}, interface: {INTERFACE}",
+                   "OBJECT_PATH", objectPath, "INTERFACE",
+                   dcmi::sensorValueIntf);
+        return std::nullopt;
+    }
+
+    // Read the sensor value and scale properties
+    double value{};
+    ec = ipmi::getDbusProperty(ctx, service, objectPath, dcmi::sensorValueIntf,
+                               dcmi::sensorValueProp, value);
+    if (ec.value())
+    {
+        lg2::error("Failed to read power value from D-Bus object, "
+                   "object path: {OBJECT_PATH}, interface: {INTERFACE}",
+                   "OBJECT_PATH", objectPath, "INTERFACE",
+                   dcmi::sensorValueIntf);
+        return std::nullopt;
+    }
+    auto power = static_cast<uint16_t>(value);
+    return power;
+}
+
+ipmi::RspType<uint16_t, // current power
+              uint16_t, // minimum power
+              uint16_t, // maximum power
+              uint16_t, // average power
+              uint32_t, // timestamp
+              uint32_t, // sample period ms
+              uint6_t,  // reserved
+              bool,     // power measurement active
+              bool      // reserved
+              >
+    getPowerReading(ipmi::Context::ptr& ctx, uint8_t mode, uint8_t attributes,
+                    uint8_t reserved)
+{
+    if (!dcmi::isDCMIPowerMgmtSupported())
+    {
+        lg2::error("DCMI Power management is unsupported!");
+        return ipmi::responseInvalidCommand();
+    }
+    if (reserved)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    enum class PowerMode : uint8_t
+    {
+        SystemPowerStatistics = 1,
+        EnhancedSystemPowerStatistics = 2,
+    };
+
+    if (static_cast<PowerMode>(mode) != PowerMode::SystemPowerStatistics)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+    if (attributes)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    std::optional<uint16_t> powerResp = readPower(ctx);
+    if (!powerResp)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+    auto& power = powerResp.value();
+
+    // TODO: openbmc/openbmc#2819
+    // Minimum, Maximum, Average power, TimeFrame, TimeStamp,
+    // PowerReadingState readings need to be populated
+    // after Telemetry changes.
+    constexpr uint32_t samplePeriod = 1;
+    constexpr uint6_t reserved1 = 0;
+    constexpr bool measurementActive = true;
+    constexpr bool reserved2 = false;
+    auto timestamp = static_cast<uint32_t>(time(nullptr));
+    return ipmi::responseSuccess(power, power, power, power, timestamp,
+                                 samplePeriod, reserved1, measurementActive,
+                                 reserved2);
+}
+
+namespace dcmi
+{
+namespace sensor_info
+{
+
+std::tuple<std::vector<uint16_t>, uint8_t> read(
+    const std::string& type, uint8_t instance, const nlohmann::json& config,
+    uint8_t count)
+{
+    std::vector<uint16_t> responses{};
+
+    static const std::vector<nlohmann::json> empty{};
+    std::vector<nlohmann::json> readings = config.value(type, empty);
+    uint8_t totalInstances = std::min(readings.size(), maxInstances);
+    for (const auto& reading : readings)
+    {
+        // limit to requested count
+        if (responses.size() == count)
+        {
+            break;
+        }
+
+        uint8_t instanceNum = reading.value("instance", 0);
+        // Not in the instance range we're interested in
+        if (instanceNum < instance)
+        {
+            continue;
+        }
+
+        uint16_t recordId = reading.value("record_id", 0);
+        responses.emplace_back(recordId);
+    }
+
+    return std::make_tuple(responses, totalInstances);
+}
+
+} // namespace sensor_info
+} // namespace dcmi
+
+ipmi::RspType<uint8_t,              // total available instances
+              uint8_t,              // number of records in this response
+              std::vector<uint16_t> // records
+              >
+    getSensorInfo(uint8_t sensorType, uint8_t entityId, uint8_t entityInstance,
+                  uint8_t instanceStart)
+{
+    auto it = dcmi::entityIdToName.find(entityId);
+    if (it == dcmi::entityIdToName.end())
+    {
+        lg2::error("Unknown Entity ID: {ENTITY_ID}", "ENTITY_ID", entityId);
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    if (sensorType != dcmi::temperatureSensorType)
+    {
+        lg2::error("Invalid sensor type: {SENSOR_TYPE}", "SENSOR_TYPE",
+                   sensorType);
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    nlohmann::json config = dcmi::parseJSONConfig(dcmi::gDCMISensorsConfig);
+
+    uint8_t requestedRecords = (entityInstance == 0) ? dcmi::maxRecords : 1;
+    // Read requested instances
+    const auto& [sensors, totalInstances] = dcmi::sensor_info::read(
+        it->second, instanceStart, config, requestedRecords);
+    uint8_t numRecords = sensors.size();
+
+    return ipmi::responseSuccess(totalInstances, numRecords, sensors);
+}
+
+void registerNetFnDcmiFunctions()
+{
+    // <Get Power Limit>
+    registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+                         ipmi::dcmi::cmdGetPowerLimit, ipmi::Privilege::User,
+                         getPowerLimit);
+
+    // <Set Power Limit>
+    registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+                         ipmi::dcmi::cmdSetPowerLimit,
+                         ipmi::Privilege::Operator, setPowerLimit);
+
+    // <Activate/Deactivate Power Limit>
+    registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+                         ipmi::dcmi::cmdActDeactivatePwrLimit,
+                         ipmi::Privilege::Operator, applyPowerLimit);
+
+    // <Get Asset Tag>
+    registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+                         ipmi::dcmi::cmdGetAssetTag, ipmi::Privilege::User,
+                         getAssetTag);
+
+    // <Set Asset Tag>
+    registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+                         ipmi::dcmi::cmdSetAssetTag, ipmi::Privilege::Operator,
+                         setAssetTag);
+
+    // <Get Management Controller Identifier String>
+    registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+                         ipmi::dcmi::cmdGetMgmtCntlrIdString,
+                         ipmi::Privilege::User, getMgmntCtrlIdStr);
+
+    // <Set Management Controller Identifier String>
+    registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+                         ipmi::dcmi::cmdSetMgmtCntlrIdString,
+                         ipmi::Privilege::Admin, setMgmntCtrlIdStr);
+
+    // <Get DCMI capabilities>
+    registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+                         ipmi::dcmi::cmdGetDcmiCapabilitiesInfo,
+                         ipmi::Privilege::User, getDCMICapabilities);
+
+    // <Get Power Reading>
+    registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+                         ipmi::dcmi::cmdGetPowerReading, ipmi::Privilege::User,
+                         getPowerReading);
+
+// The Get sensor should get the senor details dynamically when
+// FEATURE_DYNAMIC_SENSORS is enabled.
+#ifndef FEATURE_DYNAMIC_SENSORS
+    // <Get Sensor Info>
+    registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+                         ipmi::dcmi::cmdGetDcmiSensorInfo,
+                         ipmi::Privilege::Operator, getSensorInfo);
+
+    // <Get Temperature Readings>
+    registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+                         ipmi::dcmi::cmdGetTemperatureReadings,
+                         ipmi::Privilege::User, getTempReadings);
+#endif
+    // <Get DCMI Configuration Parameters>
+    registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+                         ipmi::dcmi::cmdGetDcmiConfigParameters,
+                         ipmi::Privilege::User, getDCMIConfParams);
+
+    // <Set DCMI Configuration Parameters>
+    registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+                         ipmi::dcmi::cmdSetDcmiConfigParameters,
+                         ipmi::Privilege::Admin, setDCMIConfParams);
+
+    return;
+}
diff --git a/dcmihandler.hpp b/dcmihandler.hpp
new file mode 100644
index 0000000..8fa5e82
--- /dev/null
+++ b/dcmihandler.hpp
@@ -0,0 +1,51 @@
+#pragma once
+
+#include "nlohmann/json.hpp"
+
+#include <sdbusplus/bus.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace dcmi
+{
+
+static constexpr auto propIntf = "org.freedesktop.DBus.Properties";
+static constexpr auto assetTagIntf =
+    "xyz.openbmc_project.Inventory.Decorator.AssetTag";
+static constexpr auto assetTagProp = "AssetTag";
+static constexpr auto networkServiceName = "xyz.openbmc_project.Network";
+static constexpr auto networkConfigObj = "/xyz/openbmc_project/network/config";
+static constexpr auto networkConfigIntf =
+    "xyz.openbmc_project.Network.SystemConfiguration";
+static constexpr auto hostNameProp = "HostName";
+static constexpr auto temperatureSensorType = 0x01;
+static constexpr size_t maxInstances = 255;
+static constexpr uint8_t maxRecords = 8;
+static constexpr auto gDCMISensorsConfig =
+    "/usr/share/ipmi-providers/dcmi_sensors.json";
+static constexpr auto ethernetIntf =
+    "xyz.openbmc_project.Network.EthernetInterface";
+static constexpr auto ethernetDefaultChannelNum = 0x1;
+static constexpr auto networkRoot = "/xyz/openbmc_project/network";
+static constexpr auto dhcpIntf =
+    "xyz.openbmc_project.Network.DHCPConfiguration";
+static constexpr auto systemBusName = "org.freedesktop.systemd1";
+static constexpr auto systemPath = "/org/freedesktop/systemd1";
+static constexpr auto systemIntf = "org.freedesktop.systemd1.Manager";
+static constexpr auto gDCMICapabilitiesConfig =
+    "/usr/share/ipmi-providers/dcmi_cap.json";
+static constexpr auto gDCMIPowerMgmtCapability = "PowerManagement";
+static constexpr auto gDCMIPowerMgmtSupported = 0x1;
+static constexpr auto gMaxSELEntriesMask = 0xFFF;
+static constexpr auto gByteBitSize = 8;
+
+/** @brief Check whether DCMI power management is supported
+ *         in the DCMI Capabilities config file.
+ *
+ *  @return True if DCMI power management is supported
+ */
+bool isDCMIPowerMgmtSupported();
+
+} // namespace dcmi
diff --git a/docs/configuration.md b/docs/configuration.md
new file mode 100644
index 0000000..139f025
--- /dev/null
+++ b/docs/configuration.md
@@ -0,0 +1,58 @@
+# Configuration
+
+## Device ID Configuration
+
+There is a default dev_id.json file provided by
+meta-phosphor/common/recipes-phosphor/ipmi/phosphor-ipmi-host.bb
+
+Any target can override the default json file by providing a
+phosphor-ipmi-host.bbappend with an ODM or platform customizable configuration.
+
+For a specific example, see:
+[Witherspoon](https://github.com/openbmc/openbmc/blob/master/meta-openbmc-machines/meta-openpower/meta-ibm/meta-witherspoon/recipes-phosphor/ipmi/phosphor-ipmi-host.bbappend)
+
+The JSON format for get_device_id:
+
+    {"id": 0, "revision": 0, "addn_dev_support": 0,
+        "manuf_id": 0, "prod_id": 0, "aux": 0}
+
+Each value in this JSON object should be an integer. The file is placed in
+/usr/share/ipmi-providers/ by Yocto, and will be parsed upon the first call to
+get_device_id. The data is then cached for future use. If you change the data at
+runtime, simply restart the service to see the new data fetched by a call to
+get_device_id.
+
+## IPMI D-Bus Sensor Filtering
+
+Phosphor-ipmi-host provides a compile time option to control how IPMI sensors
+are populated. The default model is for the sensors to be culled from a set of
+JSON files. There is another model that collects the sensors to display from
+D-Bus. The D-Bus method is the default mode used by Redfish. Redfish does not
+have a fixed limit on the number of sensors that can be reported.
+
+IPMI supports a smaller number of sensors than are available via Redfish. The
+limit being the number of Sensor Data Records (SDR) supported in the IPMI
+specification. Enabling IPMI to use D-Bus may cause the number of sensors
+retrieved to exceed the number SDRs IPMI can support. Even if the number of
+sensors retrieved is within the SDR limit IPMI can support, it may be desirable
+to filter entries that are uninteresting.
+
+Meson uses the _dyanmic-sensors_ configuration option to enable retrieving the
+sensors via D-Bus. When dynamic sensors are active all of the sensors placed on
+D-Bus by a service are added to the IPMI sensor list. In the event that too many
+sensors are exposed on D-Bus, the list can be filtered by adding a list of
+services to filter to _/usr/share/ipmi-providers/sensor_filter.json_.
+
+Example filtering:
+
+```json
+{
+  "ServiceFilter": [
+    "xyz.openbmc_project.RedfishSensors1",
+    "xyz.openbmc_project.RedfishSensors2"
+  ]
+}
+```
+
+Any sensors published by the example services are blocked from being added to
+the IPMI SDR table.
diff --git a/docs/contributing.md b/docs/contributing.md
new file mode 100644
index 0000000..8be4ecf
--- /dev/null
+++ b/docs/contributing.md
@@ -0,0 +1,122 @@
+# Contributing Guidelines
+
+This document attempts to outline some basic rules to follow when contributing
+to OpenBMC's IPMI stack. It does _not_ outline coding style; we follow the
+[OpenBMC C++ style guide](https://github.com/openbmc/docs/blob/master/cpp-style-and-conventions)
+for that.
+
+## Organizing Commits
+
+A good commit does exactly one thing. We prefer many small, atomic commits to
+one large commit which makes many functional changes.
+
+- Too large: "convert foo to new API; also fix CVE 1234 in bar"
+- Too small: "move abc.h to top of include list" and "move xyz.h to bottom of
+  include list"
+- Just right: "convert foo to new API" and "convert foo from tab to space"
+
+Often, creating small commits this way results in a number of commits which are
+dependent on prior commits; Gerrit handles this situation well, so feel free to
+push commits which are based on your change still in review. However, when
+possible, your commit should stand alone on top of master - "Fix whitespace in
+bar()" does not need to depend on "refactor foo()". Said differently, ensure
+that topics which are not related to each other semantically are also not
+related to each other in Git until they are merged into master.
+
+When pushing a stack of patches (current branch is >1 commits ahead of
+origin/master), these commits will show up with that same relationship in
+gerrit. This means that each patch must be merged in order of that relationship.
+So if one of the patches in the middle needs to be changed, all the patches from
+that point on would need to be pushed to maintain the relationship. This will
+effectively rebase the unchanged patches, which would in turn trigger a new CI
+build. Ideally, changes from the entire patchset could be done all in one go to
+reduce unnecessary rebasing.
+
+When someone makes a comment on your commit in Gerrit, modify that commit and
+send it again to Gerrit. This typically involves `git rebase --interactive` or
+`git commit --amend`, for which there are many guides online. As mentioned in
+the paragraph above, when possible you should make changes to multiple patches
+in the same stack before you push, in order to minimize CI and notification
+churn from the rebase operations.
+
+Commits which include changes that can be tested by a unit test should also
+include a unit test to exercise that change, within the same commit. Unit tests
+should be clearly written - even moreso than production code, unit tests are
+meant primarily to be read by humans - and should test both good and bad
+behaviors. Refer to the
+[testing documentation](https://github.com/openbmc/phosphor-host-ipmid/blob/master/docs/testing.md)
+for help writing tests, as well as best practices.
+
+## Formatting Commit Messages
+
+Your commit message should explain:
+
+- Concisely, _what_ change are you making? e.g. "docs: convert from US to UK
+  spelling" This first line of your commit message is the subject line.
+- Comprehensively, _why_ are you making that change? In some cases, like a small
+  refactor, the why is fairly obvious. But in cases like the inclusion of a new
+  feature, you should explain why the feature is needed.
+- Concisely, _how_ did you test? This comes in the form of a "Tested:" footer in
+  your commit message and is required for all code changes in the IPMI stack. It
+  typically consists of copy-pasted ipmitool requests and responses. When
+  possible, use the high-level ipmitool commands (e.g. "ipmitool sensor read
+  0x1"). In cases where that's not possible, or when testing edge or error
+  cases, it is acceptable to use "ipmitool raw" - but an explanation of your
+  output is appreciated. If the change can be validated entirely by running unit
+  tests, say so in the "Tested:" tag.
+
+Try to include the component you are changing at the front of your subject line;
+this typically comes in the form of the class, module, handler, or directory you
+are modifying. e.g. "apphandler: refactor foo to new API"
+
+Loosely, we try to follow the 50/72 rule for commit messages - that is, the
+subject line should not exceed 50 characters and the body should not exceed 72
+characters. This is common practice in many projects which use Git.
+
+All commit messages must include a Signed-off-by line, which indicates that you
+the contributor have agreed to the Developer Certificate of Origin. This line
+must include the name you commonly use, often a given name and a family name or
+surname. (ok: A. U. Thor, Sam Samuelsson, robert a. heinlein; not ok: xXthorXx,
+Sam, RAH)
+
+## Sending Patches
+
+Like most projects in OpenBMC, we use Gerrit to review patches. Please check the
+MAINTAINERS file to determine who needs to approve your review in order for your
+change to be merged. Submitters will need to manually add their reviewers in
+Gerrit; reviewers are not currently added automatically. Maintainers may not see
+the commit if they have not been added to the review!
+
+## Pace of Review
+
+Contributors who are used to code reviews by their team internal to their own
+company, or who are not used to code reviews at all, are sometimes surprised by
+the pace of code reviews in open source projects. Try to keep in mind that those
+reviewing your patch may be contributing to OpenBMC in a volunteer or
+partial-time capacity, may be in a timezone far removed from your own, and may
+have very deep review queues already of patches which have been waiting longer
+than yours.
+
+If you feel your patch has been missed entirely, of course it's alright to email
+the maintainers (addresses available in MAINTAINERS file) - but a reasonable
+timeframe to do so is on the order of a week, not on the order of hours.
+
+Additionally, the IPMI stack has a set of policies for when and how changes can
+be approved; please check the MAINTAINERS file for the full list ("Change
+approval rules").
+
+The maintainers' job is to ensure that incoming patches are as correct and easy
+to maintain as possible. Part of the nature of open source is attrition -
+contributors can come and go easily - so maintainers tend not to put stock in
+promises such as "I will add unit tests in a later patch" or "I will be
+implementing this proposal by the end of next month." This often manifests as
+reviews which may seem harsh or exacting; please keep in mind that the community
+is trying to collaborate with you to build a patch that will benefit the project
+on its own.
+
+## Code of Conduct
+
+We enthusiastically adhere to the same
+[Code of Conduct](https://github.com/openbmc/docs/blob/master/code-of-conduct.md)
+as the rest of OpenBMC. If you have any concerns, please check that document for
+guidelines on who can help you resolve them.
diff --git a/docs/ipmi-network-format.md b/docs/ipmi-network-format.md
new file mode 100644
index 0000000..140afd5
--- /dev/null
+++ b/docs/ipmi-network-format.md
@@ -0,0 +1,57 @@
+# IPMI network format
+
+On platforms running AMI BMC firmware (Habanero and Firestone at the moment),
+the 'Get/Set' System Boot Options' command has been extended to allow overrides
+to the network configuration to be specified. In Petitboot this will cause any
+existing network configuration to be overridden by the specified configuration.
+The format of each IPMI request is similar to that of the usual boot options
+request, with a slightly different payload.
+
+The start of the request is similar to a usual boot option override, but
+specifies the fields of interest differently, ie;
+
+Specify 'chassis bootdev', field 96, data1 0x00 0x08 0x61 0x80
+
+The rest of request format is defined by Petitboot as: - 4 byte cookie value
+(always 0x21 0x70 0x62 0x21) - 2 byte version value (always 0x00 0x01) - 1 byte
+hardware address size (eg. 0x06 for MAC address) - 1 byte IP address size (eg.
+0x04 for IPv4) - Hardware (MAC) address - 1 byte flags for 'ignore' and
+'method', where method = 0 is DHCP and method = 1 is Static. And for static
+configs: - IP Address - 1 byte subnet value - Gateway address
+
+Describing each field in more detail:
+
+Specify 'chassis bootdev', field 96, data1 0x00 0x08 0x61 0x80
+
+Set a special cookie that Petitboot will recognise: 0x21 0x70 0x62 0x21
+
+Specify the version (only 1 at the moment) 0x00 0x01
+
+Specify the size of the MAC address and IP address. This is used to
+differentiate between IPv4 and IPv6 addresses, or potential future support for
+Infiniband/etc. 0x06 0x04 (6-byte MAC address, IPv4 IP address)
+
+Set the hardware address of the interface you want to override, eg: 0xf4 0x52
+0x14 0xf3 0x01 0xdf
+
+Specify 'ignore' or 'static/dynamic' flags. The second byte specifies to use
+either DHCP or static IP configuration (0 for DHCP). 0x00 0x01
+
+The below fields are required if setting a static configuration:
+
+Set the IP address you want to use, eg: 0x0a 0x3d 0xa1 0x42
+
+Set the subnet mask (short notation), eg '16': 0x10
+
+Set the gateway address, eg: 0x0a 0x3d 0x2 0x1
+
+All together this should look like: 0x00 0x08 0x61 0x80 0x21 0x70 0x62 0x21 0x00
+0x01 0x06 0x04 0xf4 0x52 0x14 0xf3 0x01 0xdf 0x00 0x01 0x0a 0x3d 0xa1 0x42 0x10
+0x0a 0x3d 0x2 0x1
+
+To clear a network override, it is sufficient to clear out the request, or set a
+zero-cookie which Petitboot will reject. Eg: 0x00 0x08 0x61 0x80 0x00 0x00 0x00
+0x00
+
+You can 'Get' the override back with 0x00 0x09 0x61 0x80 0x00 which should
+return whatever is currently set.
diff --git a/docs/ipmitool-commands-cheatsheet.md b/docs/ipmitool-commands-cheatsheet.md
new file mode 100644
index 0000000..b35317f
--- /dev/null
+++ b/docs/ipmitool-commands-cheatsheet.md
@@ -0,0 +1,36 @@
+# IPMI command cheat sheet
+
+This document is intended to provide a set of IPMI commands for quick reference.
+
+Note: If the ipmitool is on the BMC then set the interface as "-I dbus" and if
+the ipmitool is outside the BMC (i.e on the network) then set the interface as
+"-I lanplus".
+
+## Network Configuration
+
+### Set the interface mode
+
+`ipmitool lan set <channel> ipsrc static`
+
+### Set the IP Address
+
+`ipmitool lan set <channel> ipaddr <x.x.x.x>`
+
+### Set the network mask
+
+`ipmitool lan set <channel> netmask <x.x.x.x>`
+
+### Set the default gateway
+
+`ipmitool lan set <channel> defgw ipaddr <x.x.x.x>`
+
+### Set the VLAN
+
+`ipmitool lan set <channel> vlan id <id>`
+
+### Delete the VLAN
+
+`ipmitool lan set <channel> vlan id off`
+
+NOTE: The user can group multiple set operations since the IPMI daemon waits for
+10 seconds after each set operation before applying the configuration.
diff --git a/docs/oem-extension-numbering.md b/docs/oem-extension-numbering.md
new file mode 100644
index 0000000..29daa3c
--- /dev/null
+++ b/docs/oem-extension-numbering.md
@@ -0,0 +1,170 @@
+# Sketch of OpenBMC OEM message formats
+
+This document describes OEM Requests to be supported using the OpenBMC OEM
+Number.
+
+## What's in the box?
+
+- Briefly recap OEM Extension layout as described in IPMI Specification.
+- Enumerate Command codes allocated for use with the OpenBMC OEM Number.
+- For each such code, describe the associated messages, including
+  representation, function, meaning, limits, and so on.
+
+## OEM Extensions, Block Transfer Transport Example
+
+This table and the next briefly recap OEM Extension messages as described in the
+[IPMI Specification](http://www.intel.com/content/www/us/en/servers/ipmi/ipmi-second-gen-interface-spec-v2-rev1-1.html).
+To keep it both simple and concrete, only BT Tansport is described. Please
+consult that specification for the full story.
+
+### OEM Request
+
+Per section 11.1 of IPMI Spec
+
+|  Bytes  | Bits | Spec ID  | Value | Description                               |
+| :-----: | ---: | :------- | :---: | :---------------------------------------- |
+|    0    |      | Length\* |   -   | Number of bytes to follow in this request |
+|    1    |  2:7 | NetFn    | 0x2E  | OEM Request                               |
+|    1    |  0:1 | LUN      |   -   | Allow any LUN                             |
+|    2    |      | Seq\*    |   -   | Per section 11.3 of IPMI Spec             |
+|    3    |      | Cmd      |   -   | Table 3 - OpenBMC Cmd Codes               |
+|  4 ~ 6  |      | OEN      | 49871 | OEM Enterprise Number                     |
+| 2 ~ n+6 |      | Data     |   -   | n data bytes, encoding depends on Cmd     |
+
+Notes:
+
+- Length and Seq are specific to BT transport - other transports may not have
+  them.
+
+- Serialize numbers larger than 1 byte LSB first - e.g., represent OEM
+  Enterprise Number 49871 as `{0xcf, 0xc2, 0x00}`
+
+### OEM Response
+
+Per section 11.2 of IPMI Spec
+
+|  Bytes  | Bits | Spec ID  | Value | Description                                     |
+| :-----: | ---: | :------- | :---: | :---------------------------------------------- |
+|    0    |      | Length\* |   -   | Number of bytes to follow in this response      |
+|    1    |  2:7 | NetFn    | 0x2F  | OEM Response                                    |
+|    1    |  0:1 | LUN      |   -   | LUN of request to which this is a response      |
+|    2    |      | Seq\*    |   -   | Seq of request to which this is a response      |
+|    3    |      | Cmd      |   -   | Cmd code of request to which this is a response |
+|    4    |      | CC       |   -   | Completion code, Section 5.2 of IPMI Spec v2.0  |
+|  5 ~ 7  |      | OEN      | 49871 | OEM Enterprise Number                           |
+| 8 ~ n+7 |      | Data     |   -   | n data bytes, encoding depends on Cmd           |
+
+## OpenBMC OEM Cmd Codes
+
+```c
+/*
+ * This is the OpenBMC IANA OEM Number
+ */
+constexpr OemNumber obmcOemNumber = 49871;
+```
+
+These are the Command codes allocated for use with the OpenBMC OEM Number.
+
+|   Cmd   | Identifier   | Description         |
+| :-----: | :----------- | :------------------ |
+|    0    | -            | Reserved            |
+|    1    | gpioCmd      | GPIO Access         |
+|    2    | i2cCmd       | I2C Device Access   |
+|    3    | flashCmd     | Flash Device Access |
+|    4    | fanManualCmd | Manual Fan Controls |
+| 5 ~ 255 | -            | Unallocated         |
+
+## I2C Device Access (Command 2)
+
+The next subsections describe command and response messages supporting the I2C
+Device Access extension OpenBMC OEM extension (command code 2).
+
+### I2C Request Message - Overall
+
+|  Bytes  | Bits | Identifier    | Description               |
+| :-----: | :--- | :------------ | :------------------------ |
+|    0    |      | bus           | Logical I2C bus.          |
+|    1    |      | xferFlags     | Flags for all steps.      |
+|         | 7    | I2cFlagUsePec | 1 => use PEC - see note.  |
+|         | 6:0  |               | Reserved(0)               |
+| 2 ~ n-1 |      |               | Step sequence - see next. |
+
+Notes
+
+- Total length of step sequence must exactly fill request.
+
+- Intent is to handle
+  [Linux Kernel SMBus Protocol](https://www.kernel.org/doc/Documentation/i2c/smbus-protocol),
+  with com generalized to m byte sequence - e.g., at24c64 uses 2 address bytes,
+  and n bytes of received data, rather than specific operations for various
+  sizes.
+
+- Goal is to support SMBus v2 32-byte data block length limit; but easily
+  supports new 4 and 8 byte transfers added for
+  [SMBus v3](http://smbus.org/specs/SMBus_3_0_20141220.pdf).
+
+- PEC refers to SMBus Packet Error Check.
+
+- SMBus address resolution, alerts, and non-standard protocols not supported. So
+  for example, there is no way to insert a stop command within a transfer.
+
+- Depending on options, i2cdetect uses either quick write or 1 byte read;
+  default is 1-byte read for eeprom/spd memory ranges, else quick write.
+
+### I2C Request Message - Step Properties
+
+| Bytes | Bits | Identifier     | Description                                   |
+| :---: | :--: | :------------- | :-------------------------------------------- |
+|   0   |      | devAndDir      |                                               |
+|       | 7:1  | dev            | 7-bit I2C device address.                     |
+|       |  0   | isRead         | 1 = read, 0 = write.                          |
+|   1   |      | stepFlags      |                                               |
+|       |  7   | i2cFlagRecvLen | 1 if block read, else regular; see table.     |
+|       |  6   | i2cFlagNoStart | 1 to suppress I2C start.                      |
+|       | 5:0  |                | Reserved(0)                                   |
+|   2   |      | p              | Count parameter; see table                    |
+| 3:m+2 |      | wrPayload      | Nonempty iff p supplies nonzero m; see table. |
+
+#### Allowed step property combinations
+
+| is_read | RecvLen |  p  | Size | Description                             |
+| :-----: | :-----: | :-: | :--- | :-------------------------------------- |
+|    0    |    0    |  0  | 3    | Quick write 0; same as write m=0 bytes. |
+|    0    |    0    |  m  | m+3  | Consume and write next m bytes.         |
+|    0    |    1    |  -  | 3    | Reserved.                               |
+|    1    |    0    |  0  | 3    | Quick write 1; same as read n=0 bytes.  |
+|    1    |    0    |  n  | 3    | Read n bytes.                           |
+|    1    |    1    |  -  | 3    | Read count from device.                 |
+
+Notes
+
+- All other combinations are reserved.
+
+- RecvLen corresponds to Linux
+  [I2C_M_RECV_LEN message flags bit](http://elixir.free-electrons.com/linux/v4.10.17/source/include/uapi/linux/i2c.h#L78)
+
+- Transfers include byte count in SMBUS block modes, and PEC byte when selected.
+  Allows for use with SMBUS compatibility layer.
+
+- In case of write steps, wrPayload may include:
+  - register address byte or bytes;
+  - count byte if implementing SMBUS block or call operation;
+  - up to 32 bytes of logical payload; and
+  - PEC byte.
+
+### I2C Response Message
+
+| Bytes | Identifier |  Size  | Description        |
+| :---: | :--------- | :----: | :----------------- |
+| 0:n-1 | rd_data    | 0 ~ 34 | byte sequence read |
+
+Notes
+
+- Maximum logical payload is 32.
+
+- In the unlikely event a transfer specifies multiple read steps, all bytes read
+  are simply concatenated in the order read.
+
+- However, maximum reply limit is sized for the largest single read.
+
+- RecvLen case w/ PEC can return up to 34 bytes: count + payload + PEC
diff --git a/docs/testing.md b/docs/testing.md
new file mode 100644
index 0000000..9ee1dcb
--- /dev/null
+++ b/docs/testing.md
@@ -0,0 +1,473 @@
+# Running Tests
+
+## Setting Up Your Environment
+
+For the purposes of this tutorial, we'll be setting up an environment in Docker.
+Docker is handy because it's fairly portable, and won't interfere with the rest
+of your machine setup. It also offers a strong guarantee that you're testing the
+same way that others working on the project are. Finally, we can get away with
+using the same Docker image that's run by the OpenBMC continuous integration
+bot, so we have even more confidence that we're running relevant tests the way
+they'd be run upstream.
+
+### Install Docker
+
+Installation of Docker CE (Community Edition) varies by platform, and may differ
+in your organization. But the
+[Docker Docs](https://docs.docker.com/v17.12/install) are a good place to start
+looking.
+
+Check that the installation was successful by running
+`sudo docker run hello-world`.
+
+### Download OpenBMC Continuous Integration Image
+
+You'll want a couple of different repositories, so start by making a place for
+it all to go, and clone the CI scripts:
+
+```shell
+mkdir openbmc-ci-tests
+cd openbmc-ci-tests
+git clone https://github.com/openbmc/openbmc-build-scripts.git
+```
+
+## Add `phosphor-host-ipmid`
+
+We also need to put a copy of the project you want to test against here. But
+you've probably got a copy checked out already, so we're going to make a _git
+worktree_. You can read more about those by running `git help worktree`, but the
+basic idea is that it's like having a second copy of your repo - but the second
+copy is in sync with your main copy, knows about your local branches, and
+protects you from checking out the same branch in two places.
+
+Your new worktree doesn't know about any untracked files you have in your main
+worktree, so you should get in the habit of committing everything you want to
+run tests against. However, because of the implementation of
+`run-unit-test-docker.sh`, you can't run the CI with untracked changes anyways,
+so this isn't the worst thing in the world. (If you make untracked changes in
+your testing worktree, it's easy to update a commit with those.)
+
+Note the placeholders in the following steps; modify the commands to match your
+directory layout.
+
+```shell
+cd /my/dir/for/phosphor-host-ipmid
+git worktree add /path/to/openbmc-ci-tests/phosphor-host-ipmid
+```
+
+Now, if you `cd /path/to/openbmc-ci-tests`, you should see a directory
+`phosphor-host-ipmid/`, and if you enter it and run `git status` you will see
+that you're likely on a new branch named `phosphor-host-ipmid`. This is just for
+convenience, since you can't check out a branch in your worktree that's already
+checked out somewhere else; you can safely ignore or delete that branch later.
+
+However, Git won't be able to figure out how to get to your main worktree
+(`/my/dir/for/phosphor-host-ipmid`), so we'll need to mount it when we run. Open
+up `/path/to/openbmc-ci-tests/openbmc-build-scripts/run-unit-test-docker.sh` and
+find where we call `docker run`, way down at the bottom. Add an additional
+argument, remembering to escape the newline ('\'):
+
+```shell
+PHOSPHOR_IPMI_HOST_PATH="/my/dir/for/phosphor-host-ipmid"
+
+docker run --blah-blah-existing-flags \
+  -v ${PHOSPHOR_IPMI_HOST_PATH}:${PHOSPHOR_IPMI_HOST_PATH} \
+  -other \
+  -args
+```
+
+Then commit this, so you can make sure not to lose it if you update the scripts
+repo:
+
+```shell
+cd openbmc-build-scripts
+git add run-unit-test-docker.sh
+git commit -m "mount phosphor-host-ipmid"
+```
+
+NOTE: There are other ways to do this besides a worktree; other approaches trade
+the cruft of mounting extra paths to the Docker container for different cruft:
+
+You can create a local upstream:
+
+```shell
+cd openbmc-ci-tests
+mkdir phosphor-host-ipmid
+cd phosphor-host-ipmid
+git init
+cd /my/dir/for/phosphor-host-ipmid
+git remote add /path/to/openbmc-ci-tests/phosphor-host-ipmid ci
+git push ci
+```
+
+This method would require you to push your topic branch to `ci` and then
+`git checkout` the appropriate branch every time you switched topics:
+
+```shell
+cd /my/dir/for/phosphor-host-ipmid
+git commit -m "my changes to be tested"
+git push ci
+cd /path/to/openbmc-ci-tests/phosphor-host-ipmid
+git checkout topic-branch
+```
+
+You can also create a symlink from your Git workspace into `openbmc-ci-tests/`.
+This is especially not recommended, since you won't be able to work on your code
+in parallel while the tests run, and since the CI scripts are unhappy when you
+have untracked changes - which you're likely to have during active development.
+
+### Building and Running
+
+The OpenBMC CI scripts take care of the build for you, and run the test suite.
+Build and run like so:
+
+```shell
+sudo WORKSPACE=$(pwd) UNIT_TEST_PKG=phosphor-host-ipmid \
+  ./openbmc-build-scripts/run-unit-test-docker.sh
+```
+
+The first run will take a long time! But afterwards it shouldn't be so bad, as
+many parts of the Docker container are already downloaded and configured.
+
+### Reading Output
+
+Your results will appear in
+`openbmc-ci-tests/phosphor-host-ipmid/test/test-suite.log`, as well as being
+printed to `stdout`. You will also see other `.log` files generated for each
+test file, for example `sample_unittest.log`. All these `*.log` files are
+human-readable and can be examined to determine why something failed
+
+### Writing Tests
+
+Now that you've got an easy working environment for running tests, let's write
+some new ones.
+
+### Setting Up Your Environment
+
+In `/my/dir/for/phosphor-host-ipmid`, create a new branch based on `master` (or
+your current patchset). For this tutorial, let's call it `sensorhandler-tests`.
+
+```shell
+git checkout -b sensorhandler-tests master
+```
+
+This creates a new branch `sensorhandler-tests` which is based on `master`, then
+checks out that branch for you to start hacking.
+
+### Write Some Tests
+
+For this tutorial, we'll be adding some basic unit testing of the struct
+accessors for `get_sdr::GetSdrReq`, just because they're fairly simple. The text
+of the struct and accessors is recreated here:
+
+```c++
+/**
+ * Get SDR
+ */
+namespace get_sdr
+{
+
+struct GetSdrReq
+{
+    uint8_t reservation_id_lsb;
+    uint8_t reservation_id_msb;
+    uint8_t record_id_lsb;
+    uint8_t record_id_msb;
+    uint8_t offset;
+    uint8_t bytes_to_read;
+} __attribute__((packed));
+
+namespace request
+{
+
+inline uint8_t get_reservation_id(GetSdrReq* req)
+{
+    return (req->reservation_id_lsb + (req->reservation_id_msb << 8));
+};
+
+inline uint16_t get_record_id(GetSdrReq* req)
+{
+    return (req->record_id_lsb + (req->record_id_msb << 8));
+};
+
+} // namespace request
+
+...
+} // namespace get_sdr
+```
+
+We'll create the tests in `test/sensorhandler_unittest.cpp`; go ahead and start
+that file with your editor.
+
+First, include the header you want to test, as well as the GTest header:
+
+```c++
+#include <sensorhandler.hpp>
+
+#include <gtest/gtest.h>
+```
+
+Let's plan the test cases we care about before we build any additional
+scaffolding. We've only got two functions - `get_reservation_id()` and
+`get_record_id()`. We want to test:
+
+- "Happy path" - in an ideal case, everything works correctly
+- Error handling - when given bad input, things break as expected
+- Edge cases - when given extremes (e.g. very large or very small numbers),
+  things work correctly or break as expected
+
+For `get_reservation_id()`:
+
+```c++
+TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_HappyPath)
+{
+}
+
+TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_NullInputDies)
+{
+}
+
+TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_Uint16MaxWorksCorrectly)
+{
+}
+```
+
+For `get_record_id()`, we have pretty much the same set of tests:
+
+```c++
+TEST(SensorHandlerTest, GetSdrReq_get_record_id_HappyPath)
+{
+}
+
+TEST(SensorHandlerTest, GetSdrReq_get_record_id_NullInputDies)
+{
+}
+
+TEST(SensorHandlerTest, GetSdrReq_get_record_id_Uint16MaxWorksCorrectly)
+{
+}
+```
+
+In the case of these two methods, there's really not much else to test. Some
+types of edge cases - like overflow/underflow - are prevented by C++'s strong
+typing; other types - like passing the incorrect type - are impossible to
+insulate against because it's possible to cast anything to a `GetSdrReq*` if we
+want. Since these are particularly boring, they make a good example for a
+tutorial like this; in practice, tests you write will likely be for more
+complicated code! We'll talk more about this in the Best Practices section
+below.
+
+Let's implement the `get_reservation_id()` items first. The implementations for
+`get_record_id()` will be identical, so we won't cover them here.
+
+For the happy path, we want to make it very clear that the test value we're
+using is within range, so we express it in binary. We also want to be able to
+ensure that the MSB and LSB are being combined in the correct order, so we make
+sure that the MSB and LSB values are different (don't use `0x3333` as the
+expected ID here). Finally, we want it to be obvious to the reader if we have
+populated the `GetSdrReq` incorrectly, so we've labeled all the fields. Since we
+are only testing one operation, it's okay to use either `ASSERT_EQ` or
+`EXPECT_EQ`; more on that in the Best Practices section.
+
+```c++
+TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_HappyPath)
+{
+    uint16_t expected_id = 0x1234; // Expected ID spans both bytes.
+    GetSdrReq input = {0x34,  // Reservation ID LSB
+                       0x12,  // Reservation ID MSB
+                       0x00,  // Record ID LSB
+                       0x00,  // Record ID MSB
+                       0x00,  // Offset
+                       0x00}; // Bytes to Read
+
+    uint16_t actual = get_sdr::request::get_reservation_id(&input);
+    ASSERT_EQ(actual, expected_id);
+}
+```
+
+We don't expect that our `GetSdrReq` pointer will ever be null; in this case,
+the null pointer validation is done much, much earlier. So it's okay for us to
+specify that in the unlikely case we're given a null pointer, we die. We don't
+really care what the output message is.
+
+```c++
+TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_NullInputDies)
+{
+    ASSERT_DEATH(get_sdr::request::get_reservation_id(nullptr), ".*");
+}
+```
+
+Finally, while negative values are taken care of by C++'s type system, we can at
+least check that our code still works happily with `UINT16_MAX`. This test is
+similar to the happy path test, but uses an edge value instead.
+
+```c++
+TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_Uint16MaxWorksCorrectly)
+{
+    uint16_t expected_id = 0xFFFF; // Expected ID spans both bytes.
+    GetSdrReq input = {0xFF,  // Reservation ID LSB
+                       0xFF,  // Reservation ID MSB
+                       0x00,  // Record ID LSB
+                       0x00,  // Record ID MSB
+                       0x00,  // Offset
+                       0x00}; // Bytes to Read
+
+    uint16_t actual = get_sdr::request::get_reservation_id(&input);
+    ASSERT_EQ(actual, expected_id);
+}
+```
+
+The `get_record_id()` tests are identical, except that they are testing the
+Record ID field. They will not be duplicated here.
+
+Finally, we'll need to add the new tests to `test/Makefile.am`. You can mimic
+other existing test setups:
+
+```make
+# Build/add sensorhandler_unittest to test suite
+sensorhandler_unittest_CPPFLAGS = \
+    -Igtest \
+    $(GTEST_CPPFLAGS) \
+    $(AM_CPPFLAGS)
+sensorhandler_unittest_CXXFLAGS = \
+    $(PTHREAD_CFLAGS) \
+    $(CODE_COVERAGE_CXXFLAGS) \
+    $(CODE_COVERAGE_CFLAGS) \
+    -DBOOST_COROUTINES_NO_DEPRECATION_WARNING
+sensorhandler_unittest_LDFLAGS = \
+    -lgtest_main \
+    -lgtest \
+    -pthread \
+    $(OESDK_TESTCASE_FLAGS) \
+    $(CODE_COVERAGE_LDFLAGS)
+sensorhandler_unittest_SOURCES = \
+    %reldir%/sensorhandler_unittest.cpp
+check_PROGRAMS += %reldir%/sensorhandler_unittest
+```
+
+### Run the New Tests
+
+Commit your test changes. Then, you'll want to checkout the
+`sensorhandler-tests` branch in your CI worktree, which will involve releasing
+it from your main worktree:
+
+```shell
+cd /my/dir/for/phosphor-host-ipmid
+git add test/sensorhandler_unittest.cpp
+git commit -s
+git checkout master   # Here you can use any branch except sensorhandler-tests
+cd /path/to/openbmc-ci-tests/phosphor-host-ipmid
+git checkout sensorhandler-tests
+```
+
+Now you can run the test suite as described earlier in the document. If you see
+a linter error when you run, you can actually apply the cleaned-up code easily:
+
+```shell
+cd ./phosphor-host-ipmid
+git diff    # Examine the proposed changes
+git add -u  # Apply the proposed changes
+git commit --amend
+```
+
+(If you will need to apply the proposed changes to multiple commits, you can do
+this with interactive rebase, which won't be described here.)
+
+### Best Practices
+
+While a good unit test can ensure your code's stability, a flaky or
+poorly-written unit test can make life harder for contributors down the road.
+Some things to remember:
+
+Include both positive (happy-path) and negative (error) testing in your
+testbench. It's not enough to know that the code works when it's supposed to; we
+also need to know that it fails gracefully when something goes wrong. Applying
+edge-case testing helps us find bugs that may take years to occur (and
+reproduce!) in the field.
+
+Keep your tests small. Avoid branching - if you feel a desire to, instead
+explore that codepath in another test. The best tests are easy to read and
+understand.
+
+When a test fails, it's useful if the test is named in such a way that you can
+tell _what it's supposed to do_ and _when_. That way you can be certain whether
+the change you made really broke it or not. A good pattern is
+`Object_NameCircumstanceResult` - for example,
+`FooFactory_OutOfBoundsNameThrowsException`. From the name, it's very clear that
+when some "name" is out of bounds, an exception should be thrown. (What "name"
+is should be clear from the context of the function in `FooFactory`.)
+
+Don't test other people's code. Make sure to limit the test assertions to the
+code under test. For example, don't test what happens if you give a bad input to
+`sdbusplus` when you're supposed to be testing `phosphor-host-ipmid`.
+
+However, don't trust other people's code, either! Try to test _how you respond_
+when a service fails unexpectedly. Rather than checking if `sdbusplus` fails on
+bad inputs, check whether you handle an `sdbusplus` failure gracefully. You can
+use GMock for this kind of testing.
+
+Think about testing when you write your business logic code. Concepts like
+dependency injection and small functions make your code more testable - you'll
+be thanking yourself later when you're writing tests.
+
+Finally, you're very likely to find bugs while writing tests, especially if it's
+for code that wasn't previously unit-tested. It's okay to check in a bugfix
+along with a test that verifies the fix worked, if you're only doing one test
+and one bugfix. If you're checking in a large suite of tests, do the bugfixes in
+independent commits which your test suite commit is based on:
+
+master -> fix Foo.Abc() -> fix Foo.Def() -> Fix Foo.Ghi() -> test Foo class
+
+### Sending for Review
+
+You can send your test update and any associated bugfixes for review to Gerrit
+as you would send any other change. For the `Tested:` field in the commit
+message, you can state that you ran the new unit tests written.
+
+### Reviewing Tests
+
+Tests are written primarily to be read. So when you review a test, you're the
+first customer of that test!
+
+## Best Practices
+
+First, all the best practices listed above for writing tests are things you
+should check for and encourage when you're reading tests.
+
+Next, you should ensure that you can tell what's going on when you read the
+test. If it's not clear to you, it's not going to be clear to someone else, and
+the test is more prone to error - ask!
+
+Finally, think about what's _not_ being tested. If there's a case you're curious
+about and it isn't covered, you should mention it to the committer.
+
+## Quickly Running At Home
+
+Now that you've got a handy setup as described earlier in this document, you can
+quickly download and run the tests yourself. Within the Gerrit change, you
+should be able to find a button that says "Download", which will give you
+commands for various types of downloads into an existing Git repo. Use
+"Checkout", for example:
+
+```shell
+cd openbmc-ci-tests/phosphor-host-ipmid
+git fetch "https://gerrit.openbmc-project.xyz/openbmc/phosphor-host-ipmid" \
+  refs/changes/43/23043/1 && git checkout FETCH_HEAD
+```
+
+This won't disturb the rest of your Git repo state, and will put your CI
+worktree into detached-HEAD mode pointing to the commit that's under review. You
+can then run your tests normally, and even make changes and push again if the
+commit was abandoned or otherwise left to rot by its author.
+
+Doing so can be handy in a number of scenarios:
+
+- Jenkins isn't responding
+- The Jenkins build is broken for a reason beyond the committer's control
+- The committer doesn't have "Ok-To-Test" permission, and you don't have
+  permission to grant it to them
+
+## Credits
+
+Thanks very much to Patrick Venture for his prior work putting together
+documentation on this topic internal to Google.
diff --git a/error-HostEvent.hpp b/error-HostEvent.hpp
new file mode 100644
index 0000000..9e6397a
--- /dev/null
+++ b/error-HostEvent.hpp
@@ -0,0 +1,61 @@
+#pragma once
+
+#include <sdbusplus/exception.hpp>
+
+#include <cerrno>
+
+namespace sdbusplus::error::org::open_power::host
+{
+struct Event final : public sdbusplus::exception::generated_exception
+{
+    static constexpr auto errName = "org.open_power.Host.Error.Event";
+    static constexpr auto errDesc = "A host system event was received";
+    static constexpr auto errWhat =
+        "org.open_power.Host.Error.Event: A host system event was received";
+
+    const char* name() const noexcept override
+    {
+        return errName;
+    }
+    const char* description() const noexcept override
+    {
+        return errDesc;
+    }
+    const char* what() const noexcept override
+    {
+        return errWhat;
+    }
+};
+struct MaintenanceProcedure final :
+    public sdbusplus::exception::generated_exception
+{
+    static constexpr auto errName =
+        "org.open_power.Host.Error.MaintenanceProcedure";
+    static constexpr auto errDesc =
+        "A host system event with a procedure callout";
+    static constexpr auto errWhat =
+        "org.open_power.Host.Error.MaintenanceProcedure: A host system event with a procedure callout";
+
+    const char* name() const noexcept override
+    {
+        return errName;
+    }
+    const char* description() const noexcept override
+    {
+        return errDesc;
+    }
+    const char* what() const noexcept override
+    {
+        return errWhat;
+    }
+};
+} // namespace sdbusplus::error::org::open_power::host
+
+#ifndef SDBUSPP_REMOVE_DEPRECATED_NAMESPACE
+namespace sdbusplus::org::open_power::Host::Error
+{
+using Event = sdbusplus::error::org::open_power::host::Event;
+using MaintenanceProcedure =
+    sdbusplus::error::org::open_power::host::MaintenanceProcedure;
+} // namespace sdbusplus::org::open_power::Host::Error
+#endif
diff --git a/fruread.hpp b/fruread.hpp
new file mode 100644
index 0000000..a2fb650
--- /dev/null
+++ b/fruread.hpp
@@ -0,0 +1,36 @@
+#pragma once
+
+#include <systemd/sd-bus.h>
+
+#include <array>
+#include <map>
+#include <string>
+#include <vector>
+
+struct IPMIFruData
+{
+    std::string section;
+    std::string property;
+    std::string delimiter;
+};
+
+using DbusProperty = std::string;
+using DbusPropertyVec = std::vector<std::pair<DbusProperty, IPMIFruData>>;
+
+using DbusInterface = std::string;
+using DbusInterfaceVec = std::vector<std::pair<DbusInterface, DbusPropertyVec>>;
+
+using FruInstancePath = std::string;
+
+struct FruInstance
+{
+    uint8_t entityID;
+    uint8_t entityInstance;
+    FruInstancePath path;
+    DbusInterfaceVec interfaces;
+};
+
+using FruInstanceVec = std::vector<FruInstance>;
+
+using FruId = uint32_t;
+using FruMap = std::map<FruId, FruInstanceVec>;
diff --git a/generate_whitelist.sh b/generate_whitelist.sh
new file mode 100755
index 0000000..d3d03e3
--- /dev/null
+++ b/generate_whitelist.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+# Ensure some files have been passed.
+if [ -z "$*" ]; then
+    echo "Usage: $0 [allowlist_files+]" >&2
+    exit 1
+fi
+
+cat << EOF
+#include <ipmiallowlist.hpp>
+
+const std::vector<netfncmd_pair> allowlist = {
+
+EOF
+
+# Output each row of allowlist vector.
+# Concatenate all the passed files.
+# Remove comments and empty lines.
+# Sort the list [numerically].
+# Remove any duplicates.
+# Turn "a:b //<NetFn>:<Command>" -> "{ a, b }, //<NetFn>:<Command>"
+sed "s/#.*//" "$*" | sed '/^$/d' | sort -n | uniq | sed "s/^/    { /" | \
+    sed "s/\:\(....\)\(.*\)/ , \1 }, \2/"
+
+cat << EOF
+};
+EOF
diff --git a/generate_whitelist_create.sh b/generate_whitelist_create.sh
new file mode 100755
index 0000000..654a20a
--- /dev/null
+++ b/generate_whitelist_create.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+# Ensure some files have been passed.
+./generate_whitelist.sh "$*" > ipmiwhitelist.cpp
diff --git a/globalhandler.cpp b/globalhandler.cpp
new file mode 100644
index 0000000..7b64d42
--- /dev/null
+++ b/globalhandler.cpp
@@ -0,0 +1,54 @@
+#include <ipmid/api.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/State/BMC/server.hpp>
+
+#include <string>
+
+static constexpr auto bmcStateRoot = "/xyz/openbmc_project/state";
+static constexpr auto bmcStateIntf = "xyz.openbmc_project.State.BMC";
+static constexpr auto reqTransition = "RequestedBMCTransition";
+static constexpr auto match = "bmc0";
+
+using BMC = sdbusplus::server::xyz::openbmc_project::state::BMC;
+
+void registerNetFnGlobalFunctions() __attribute__((constructor));
+
+/** @brief implements cold and warm reset commands
+ *  @param - None
+ *  @returns IPMI completion code.
+ */
+ipmi::RspType<> ipmiGlobalReset(ipmi::Context::ptr ctx)
+{
+    ipmi::DbusObjectInfo bmcStateObj;
+    boost::system::error_code ec = ipmi::getDbusObject(
+        ctx, bmcStateIntf, bmcStateRoot, match, bmcStateObj);
+    if (!ec)
+    {
+        std::string service;
+        ec = ipmi::getService(ctx, bmcStateIntf, bmcStateObj.first, service);
+        if (!ec)
+        {
+            ec = ipmi::setDbusProperty(
+                ctx, service, bmcStateObj.first, bmcStateIntf, reqTransition,
+                convertForMessage(BMC::Transition::Reboot));
+        }
+    }
+    if (ec)
+    {
+        lg2::error("Exception in Global Reset: {ERROR}", "ERROR", ec.message());
+        return ipmi::responseUnspecifiedError();
+    }
+
+    // Status code.
+    return ipmi::responseSuccess();
+}
+
+void registerNetFnGlobalFunctions()
+{
+    // Cold Reset
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdColdReset, ipmi::Privilege::Admin,
+                          ipmiGlobalReset);
+    return;
+}
diff --git a/groupext.cpp b/groupext.cpp
new file mode 100644
index 0000000..f61bf1f
--- /dev/null
+++ b/groupext.cpp
@@ -0,0 +1,31 @@
+#include <ipmid/api.hpp>
+
+#include <cstdio>
+
+#define GRPEXT_GET_GROUP_CMD 0
+void registerNetFnGroupExtFunctions() __attribute__((constructor));
+
+ipmi_ret_t ipmi_groupext(ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t,
+                         ipmi_response_t response, ipmi_data_len_t data_len,
+                         ipmi_context_t)
+{
+    // Generic return from IPMI commands.
+    ipmi_ret_t rc = IPMI_CC_OK;
+    uint8_t* p = (uint8_t*)response;
+
+    std::printf("IPMI GROUP EXTENSIONS\n");
+
+    *data_len = 1;
+    *p = 0;
+
+    return rc;
+}
+
+void registerNetFnGroupExtFunctions()
+{
+    // <Group Extension Command>
+    ipmi_register_callback(NETFUN_GRPEXT, GRPEXT_GET_GROUP_CMD, nullptr,
+                           ipmi_groupext, PRIVILEGE_USER);
+
+    return;
+}
diff --git a/host-cmd-manager.cpp b/host-cmd-manager.cpp
new file mode 100644
index 0000000..3b3304f
--- /dev/null
+++ b/host-cmd-manager.cpp
@@ -0,0 +1,195 @@
+#include "config.h"
+
+#include "host-cmd-manager.hpp"
+
+#include "systemintfcmds.hpp"
+
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <sdbusplus/timer.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/State/Host/server.hpp>
+
+#include <chrono>
+
+namespace phosphor
+{
+namespace host
+{
+namespace command
+{
+
+constexpr auto HOST_STATE_PATH = "/xyz/openbmc_project/state/host0";
+constexpr auto HOST_STATE_INTERFACE = "xyz.openbmc_project.State.Host";
+constexpr auto HOST_TRANS_PROP = "RequestedHostTransition";
+
+// For throwing exceptions
+using namespace phosphor::logging;
+using InternalFailure =
+    sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+
+namespace sdbusRule = sdbusplus::bus::match::rules;
+
+Manager::Manager(sdbusplus::bus_t& bus) :
+    bus(bus), timer(std::bind(&Manager::hostTimeout, this)),
+    hostTransitionMatch(
+        bus,
+        sdbusRule::propertiesChanged(HOST_STATE_PATH, HOST_STATE_INTERFACE),
+        std::bind(&Manager::clearQueueOnPowerOn, this, std::placeholders::_1))
+{
+    // Nothing to do here.
+}
+
+// Called as part of READ_MSG_DATA command
+IpmiCmdData Manager::getNextCommand()
+{
+    // Stop the timer. Don't have to Err failure doing so.
+    auto r = timer.stop();
+    if (r < 0)
+    {
+        lg2::error("Failure to STOP the timer: {ERROR}", "ERROR", strerror(-r));
+    }
+
+    if (this->workQueue.empty())
+    {
+        // Just return a heartbeat in this case.  A spurious SMS_ATN was
+        // asserted for the host (probably from a previous boot).
+        lg2::debug("Control Host work queue is empty!");
+
+        return std::make_pair(CMD_HEARTBEAT, 0x00);
+    }
+
+    // Pop the processed entry off the queue
+    auto command = this->workQueue.front();
+    this->workQueue.pop();
+
+    // IPMI command is the first element in pair
+    auto ipmiCmdData = std::get<0>(command);
+
+    // Now, call the user registered functions so that
+    // implementation specific CommandComplete signals
+    // can be sent. `true` indicating Success.
+    std::get<CallBack>(command)(ipmiCmdData, true);
+
+    // Check for another entry in the queue and kick it off
+    this->checkQueueAndAlertHost();
+
+    // Tuple of command and data
+    return ipmiCmdData;
+}
+
+// Called when initial timer goes off post sending SMS_ATN
+void Manager::hostTimeout()
+{
+    lg2::error("Host control timeout hit!");
+
+    clearQueue();
+}
+
+void Manager::clearQueue()
+{
+    // Dequeue all entries and send fail signal
+    while (!this->workQueue.empty())
+    {
+        auto command = this->workQueue.front();
+        this->workQueue.pop();
+
+        // IPMI command is the first element in pair
+        auto ipmiCmdData = std::get<0>(command);
+
+        // Call the implementation specific Command Failure.
+        // `false` indicating Failure
+        std::get<CallBack>(command)(ipmiCmdData, false);
+    }
+}
+
+// Called for alerting the host
+void Manager::checkQueueAndAlertHost()
+{
+    if (this->workQueue.size() >= 1)
+    {
+        lg2::debug("Asserting SMS Attention");
+
+        std::string HOST_IPMI_SVC("org.openbmc.HostIpmi");
+        std::string IPMI_PATH("/org/openbmc/HostIpmi/1");
+        std::string IPMI_INTERFACE("org.openbmc.HostIpmi");
+
+        // Start the timer for this transaction
+        auto time = std::chrono::duration_cast<std::chrono::microseconds>(
+            std::chrono::seconds(IPMI_SMS_ATN_ACK_TIMEOUT_SECS));
+
+        auto r = timer.start(time);
+        if (r < 0)
+        {
+            lg2::error("Error starting timer for control host");
+            return;
+        }
+
+        auto method =
+            this->bus.new_method_call(HOST_IPMI_SVC.c_str(), IPMI_PATH.c_str(),
+                                      IPMI_INTERFACE.c_str(), "setAttention");
+
+        try
+        {
+            auto reply = this->bus.call(method);
+
+            lg2::debug("SMS Attention asserted");
+        }
+        catch (sdbusplus::exception_t& e)
+        {
+            lg2::error("Error when call setAttention method");
+        }
+    }
+}
+
+// Called by specific implementations that provide commands
+void Manager::execute(CommandHandler command)
+{
+    lg2::debug("Pushing cmd on to queue, command: {COMMAND}", "COMMAND",
+               std::get<0>(command).first);
+
+    this->workQueue.emplace(command);
+
+    // Alert host if this is only command in queue otherwise host will
+    // be notified of next message after processing the current one
+    if (this->workQueue.size() == 1)
+    {
+        this->checkQueueAndAlertHost();
+    }
+    else
+    {
+        lg2::info("Command in process, no attention");
+    }
+
+    return;
+}
+
+void Manager::clearQueueOnPowerOn(sdbusplus::message_t& msg)
+{
+    namespace server = sdbusplus::server::xyz::openbmc_project::state;
+
+    ::ipmi::DbusInterface interface;
+    ::ipmi::PropertyMap properties;
+
+    msg.read(interface, properties);
+
+    if (properties.find(HOST_TRANS_PROP) == properties.end())
+    {
+        return;
+    }
+
+    auto& requestedState =
+        std::get<std::string>(properties.at(HOST_TRANS_PROP));
+
+    if (server::Host::convertTransitionFromString(requestedState) ==
+        server::Host::Transition::On)
+    {
+        clearQueue();
+    }
+}
+
+} // namespace command
+} // namespace host
+} // namespace phosphor
diff --git a/host-cmd-manager.hpp b/host-cmd-manager.hpp
new file mode 100644
index 0000000..8937e43
--- /dev/null
+++ b/host-cmd-manager.hpp
@@ -0,0 +1,113 @@
+#pragma once
+
+#include <ipmid-host/cmd-utils.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/timer.hpp>
+
+#include <queue>
+#include <tuple>
+
+namespace phosphor
+{
+namespace host
+{
+namespace command
+{
+
+/** @class
+ *  @brief Manages commands that are to be sent to Host
+ */
+class Manager
+{
+  public:
+    Manager() = delete;
+    ~Manager() = default;
+    Manager(const Manager&) = delete;
+    Manager& operator=(const Manager&) = delete;
+    Manager(Manager&&) = delete;
+    Manager& operator=(Manager&&) = delete;
+
+    /** @brief Constructs Manager object
+     *
+     *  @param[in] bus   - dbus handler
+     *  @param[in] event - pointer to sd_event
+     */
+    explicit Manager(sdbusplus::bus_t& bus);
+
+    /** @brief  Extracts the next entry in the queue and returns
+     *          Command and data part of it.
+     *
+     *  @detail Also calls into the registered handlers so that they can now
+     *          send the CommandComplete signal since the interface contract
+     *          is that we emit this signal once the message has been
+     *          passed to the host (which is required when calling this)
+     *
+     *          Also, if the queue has more commands, then it will alert the
+     *          host
+     */
+    IpmiCmdData getNextCommand();
+
+    /** @brief  Pushes the command onto the queue.
+     *
+     *  @detail If the queue is empty, then it alerts the Host. If not,
+     *          then it returns and the API documented above will handle
+     *          the commands in Queue.
+     *
+     *  @param[in] command - tuple of <IPMI command, data, callback>
+     */
+    void execute(CommandHandler command);
+
+  private:
+    /** @brief Check if anything in queue and alert host if so */
+    void checkQueueAndAlertHost();
+
+    /** @brief  Call back interface on message timeouts to host.
+     *
+     *  @detail When this happens, a failure message would be sent
+     *          to all the commands that are in the Queue and queue
+     *          will be purged
+     */
+    void hostTimeout();
+
+    /** @brief Clears the command queue
+     *
+     *  @detail Clears the command queue and calls all callbacks
+     *          specifying the command wasn't successful.
+     */
+    void clearQueue();
+
+    /** @brief Clears the command queue on a power on
+     *
+     *  @detail The properties changed handler for the
+     *          RequestedHostTransition property.  When this property
+     *          changes to 'On', this function will purge the command
+     *          queue.
+     *
+     *          This is done to avoid having commands that were issued
+     *          before the host powers on from getting sent to the host,
+     *          either due to race conditions around state transitions
+     *          or from a user doing something like requesting an already
+     *          powered off system to power off again and then immediately
+     *          requesting a power on.
+     *
+     *  @param[in] msg - the sdbusplus message containing the property
+     */
+    void clearQueueOnPowerOn(sdbusplus::message_t& msg);
+
+    /** @brief Reference to the dbus handler */
+    sdbusplus::bus_t& bus;
+
+    /** @brief Queue to store the requested commands */
+    std::queue<CommandHandler> workQueue{};
+
+    /** @brief Timer for commands to host */
+    sdbusplus::Timer timer;
+
+    /** @brief Match handler for the requested host state */
+    sdbusplus::bus::match_t hostTransitionMatch;
+};
+
+} // namespace command
+} // namespace host
+} // namespace phosphor
diff --git a/host-interface.cpp b/host-interface.cpp
new file mode 100644
index 0000000..bc01d65
--- /dev/null
+++ b/host-interface.cpp
@@ -0,0 +1,122 @@
+
+#include "config.h"
+
+#include "host-interface.hpp"
+
+#include "systemintfcmds.hpp"
+
+#include <ipmid-host/cmd-utils.hpp>
+#include <ipmid-host/cmd.hpp>
+#include <ipmid/api.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <functional>
+#include <memory>
+#include <optional>
+
+namespace phosphor
+{
+namespace host
+{
+namespace command
+{
+
+// When you see Base:: you know we're referencing our base class
+namespace Base = sdbusplus::server::xyz::openbmc_project::control;
+
+// IPMI OEM command.
+// https://github.com/openbmc/openbmc/issues/2082 for handling
+// Non-OEM commands that need to send SMS_ATN
+using OEMCmd = uint8_t;
+
+// Map of IPMI OEM command to its equivalent interface command.
+// This is needed when invoking the callback handler to indicate
+// the status of the executed command.
+static const std::map<OEMCmd, Host::Command> intfCommand = {
+    {CMD_HEARTBEAT, Base::Host::Command::Heartbeat},
+    {CMD_POWER, Base::Host::Command::SoftOff}};
+
+// Map of Interface command to its corresponding IPMI OEM command.
+// This is needed when pushing IPMI commands to command manager's
+// queue. The same pair will be returned when IPMI asks us
+// why a SMS_ATN was sent
+static const std::map<Host::Command, IpmiCmdData> ipmiCommand = {
+    {Base::Host::Command::Heartbeat, std::make_pair(CMD_HEARTBEAT, 0x00)},
+    {Base::Host::Command::SoftOff, std::make_pair(CMD_POWER, SOFT_OFF)}};
+
+// Called at user request
+void Host::execute(Base::Host::Command command)
+{
+    lg2::debug("Pushing cmd on to queue, control host cmd: {CONTROL_HOST_CMD}",
+               "CONTROL_HOST_CMD", convertForMessage(command));
+
+    auto cmd = std::make_tuple(
+        ipmiCommand.at(command),
+        std::bind(&Host::commandStatusHandler, this, std::placeholders::_1,
+                  std::placeholders::_2));
+
+    ipmid_send_cmd_to_host(std::move(cmd));
+}
+
+// Called into by Command Manager
+void Host::commandStatusHandler(IpmiCmdData cmd, bool status)
+{
+    // Need to convert <cmd> to the equivalent one mentioned in spec
+    auto value = status ? Result::Success : Result::Failure;
+
+    // Fire a signal
+    this->commandComplete(intfCommand.at(std::get<0>(cmd)), value);
+}
+
+Host::FirmwareCondition Host::currentFirmwareCondition() const
+{
+    // shared object used to wait for host response
+    auto hostCondition =
+        std::make_shared<std::optional<Host::FirmwareCondition>>();
+
+    // callback for command to host
+    auto hostAckCallback = [hostCondition](IpmiCmdData, bool status) {
+        auto value = status ? Host::FirmwareCondition::Running
+                            : Host::FirmwareCondition::Off;
+
+        lg2::debug("currentFirmwareCondition:hostAckCallback fired, "
+                   "control host cmd: {CONTROL_HOST_CMD}",
+                   "CONTROL_HOST_CMD", value);
+
+        *(hostCondition.get()) = value;
+        return;
+    };
+
+    auto cmd = phosphor::host::command::CommandHandler(
+        ipmiCommand.at(Base::Host::Command::Heartbeat),
+        std::move(hostAckCallback));
+
+    ipmid_send_cmd_to_host(std::move(cmd));
+
+    // Timer to ensure this function returns something within a reasonable time
+    sdbusplus::Timer hostAckTimer([hostCondition]() {
+        lg2::debug("currentFirmwareCondition: timer expired!");
+        *(hostCondition.get()) = Host::FirmwareCondition::Off;
+    });
+
+    // Wait 1 second past the ATN_ACK timeout to ensure we wait for as
+    // long as the timeout
+    hostAckTimer.start(std::chrono::seconds(IPMI_SMS_ATN_ACK_TIMEOUT_SECS + 1));
+
+    auto io = getIoContext();
+
+    while (!hostCondition.get()->has_value())
+    {
+        lg2::debug("currentFirmwareCondition: waiting for host response");
+        io->run_for(std::chrono::milliseconds(100));
+    }
+    hostAckTimer.stop();
+
+    lg2::debug("currentFirmwareCondition: hostCondition is ready!");
+    return hostCondition.get()->value();
+}
+
+} // namespace command
+} // namespace host
+} // namespace phosphor
diff --git a/host-interface.hpp b/host-interface.hpp
new file mode 100644
index 0000000..52dbab1
--- /dev/null
+++ b/host-interface.hpp
@@ -0,0 +1,69 @@
+#pragma once
+
+#include <host-cmd-manager.hpp>
+#include <sdbusplus/bus.hpp>
+#include <xyz/openbmc_project/Condition/HostFirmware/server.hpp>
+#include <xyz/openbmc_project/Control/Host/server.hpp>
+namespace phosphor
+{
+namespace host
+{
+namespace command
+{
+
+/** @class Host
+ *  @brief OpenBMC control and condition host interface implementation.
+ *  @details A concrete implementation for xyz.openbmc_project.Control.Host
+ *  and xyz.openbmc_project.Condition.HostFirmware DBus API's.
+ */
+class Host :
+    public sdbusplus::server::object_t<
+        sdbusplus::server::xyz::openbmc_project::control::Host,
+        sdbusplus::server::xyz::openbmc_project::condition::HostFirmware>
+{
+  public:
+    /** @brief Constructs Host Control and Condition Interfaces
+     *
+     *  @param[in] bus     - The Dbus bus object
+     *  @param[in] objPath - The Dbus object path
+     */
+    Host(sdbusplus::bus_t& bus, const char* objPath) :
+        sdbusplus::server::object_t<
+            sdbusplus::server::xyz::openbmc_project::control::Host,
+            sdbusplus::server::xyz::openbmc_project::condition::HostFirmware>(
+            bus, objPath),
+        bus(bus)
+    {
+        // Nothing to do
+    }
+
+    /** @brief Send input command to host
+     *         Note that the command will be queued in a FIFO if
+     *         other commands to the host have yet to be run
+     *
+     *  @param[in] command - Input command to execute
+     */
+    void execute(Command command) override;
+
+    /** @brief Override reads to CurrentFirmwareCondition */
+    FirmwareCondition currentFirmwareCondition() const override;
+
+  private:
+    /** @brief sdbusplus DBus bus connection. */
+    sdbusplus::bus_t& bus;
+
+    /** @brief  Callback function to be invoked by command manager
+     *
+     *  @detail Conveys the status of the last Host bound command.
+     *          Depending on the status,  a CommandComplete or
+     *          CommandFailure signal would be sent
+     *
+     *  @param[in] cmd    - IPMI command and data sent to Host
+     *  @param[in] status - Success or Failure
+     */
+    void commandStatusHandler(IpmiCmdData cmd, bool status);
+};
+
+} // namespace command
+} // namespace host
+} // namespace phosphor
diff --git a/host-ipmid-whitelist.conf b/host-ipmid-whitelist.conf
new file mode 100644
index 0000000..9dd1eb1
--- /dev/null
+++ b/host-ipmid-whitelist.conf
@@ -0,0 +1,51 @@
+#<NetFn>:<Command
+0x00:0x00    //<Chassis>:<Chassis Capabiliti>
+0x00:0x01    //<Chassis>:<Get Chassis Status>
+0x00:0x02    //<Chassis>:<Chassis Control>
+0x00:0x05    //<Chassis>:<Set Chassis Capabilities>
+0x00:0x06    //<Chassis>:<Set Power Restore Policy>
+0x00:0x08    //<Chassis>:<Set System Boot Options>
+0x00:0x09    //<Chassis>:<Get System Boot Options>
+0x00:0x0F    //<Chassis>:<Get POH Counter Command>
+0x04:0x02    //<Sensor/Event>:<Platform event>
+0x04:0x2D    //<Sensor/Event>:<Get Sensor Reading>
+0x04:0x2F    //<Sensor/Event>:<Get Sensor Type>
+0x04:0x30    //<Sensor/Event>:<Set Sensor Reading and Event Status>
+0x06:0x01    //<App>:<Get Device ID>
+0x06:0x04    //<App>:<Get Self Test Results>
+0x06:0x06    //<App>:<Set ACPI Power State>
+0x06:0x07    //<App>:<Get ACPI Power State>
+0x06:0x08    //<App>:<Get Device GUID>
+0x06:0x22    //<App>:<Reset Watchdog Timer>
+0x06:0x24    //<App>:<Set Watchdog Timer>
+0x06:0x25    //<App>:<Get Watchdog Timer>
+0x06:0x2E    //<App>:<Set BMC Global Enables>
+0x06:0x2F    //<App>:<Get BMC Global Enables>
+0x06:0x31    //<App>:<Get Message Flags>
+0x06:0x35    //<App>:<Read Event Message Buffer>
+0x06:0x36    //<App>:<Get BT Interface Capabilities>
+0x06:0x37    //<App>:<Get System GUID>
+0x06:0x42    //<App>:<Get Channel Info Command>
+0x06:0x4D    //<App>:<Get User Payload Access>
+0x06:0x4E    //<App>:<Get Channel Payload Support>
+0x06:0x4F    //<App>:<Get Channel Payload Version>
+0x06:0x54    //<App>:<Get Channel Cipher Suites>
+0x0A:0x10    //<Storage>:<Get FRU Inventory Area Info>
+0x0A:0x11    //<Storage>:<Read FRU Data>
+0x0A:0x20    //<Storage>:<Get SDR Repository Info>
+0x0A:0x22    //<Storage>:<Reserve SDR Repository>
+0x0A:0x23    //<Storage>:<Get SDR>
+0x0A:0x40    //<Storage>:<Get SEL Info>
+0x0A:0x42    //<Storage>:<Reserve SEL>
+0x0A:0x44    //<Storage>:<Add SEL Entry>
+0x0A:0x48    //<Storage>:<Get SEL Time>
+0x0A:0x49    //<Storage>:<Set SEL Time>
+0x0A:0x5C    //<Storage>:<Get SEL Time UTC Offset>
+0x0C:0x02    //<Transport>:<Get LAN Configuration Parameters>
+0x2C:0x00    //<Group Extension>:<Group Extension Command>
+0x2C:0x01    //<Group Extension>:<Get DCMI Capabilities>
+0x2C:0x02    //<Group Extension>:<Get Power Reading>
+0x2C:0x03    //<Group Extension>:<Get Power Limit>
+0x2C:0x06    //<Group Extension>:<Get Asset Tag>
+0x2C:0x07    //<Group Extension>:<Get Sensor Info>
+0x2C:0x10    //<Group Extension>:<Get Temperature Readings>
diff --git a/include/dbus-sdr/sdrutils.hpp b/include/dbus-sdr/sdrutils.hpp
new file mode 100644
index 0000000..b52351d
--- /dev/null
+++ b/include/dbus-sdr/sdrutils.hpp
@@ -0,0 +1,363 @@
+/*
+// Copyright (c) 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.
+*/
+
+#include <boost/algorithm/string.hpp>
+#include <boost/bimap.hpp>
+#include <boost/container/flat_map.hpp>
+#include <ipmid/api.hpp>
+#include <ipmid/types.hpp>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/bus/match.hpp>
+
+#include <cstdio>
+#include <cstring>
+#include <exception>
+#include <filesystem>
+#include <map>
+#include <optional>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#pragma once
+
+static constexpr bool debug = false;
+
+struct CmpStrVersion
+{
+    bool operator()(std::string a, std::string b) const
+    {
+        return strverscmp(a.c_str(), b.c_str()) < 0;
+    }
+};
+
+using SensorSubTree = boost::container::flat_map<
+    std::string,
+    boost::container::flat_map<std::string, std::vector<std::string>>,
+    CmpStrVersion>;
+
+using SensorNumMap = boost::bimap<int, std::string>;
+
+static constexpr uint16_t maxSensorsPerLUN = 255;
+static constexpr uint16_t maxIPMISensors = (maxSensorsPerLUN * 3);
+static constexpr uint16_t lun1Sensor0 = 0x100;
+static constexpr uint16_t lun3Sensor0 = 0x300;
+static constexpr uint16_t invalidSensorNumber = 0xFFFF;
+static constexpr uint8_t reservedSensorNumber = 0xFF;
+
+namespace details
+{
+// Enable/disable the logging of stats instrumentation
+static constexpr bool enableInstrumentation = false;
+
+class IPMIStatsEntry
+{
+  private:
+    int numReadings = 0;
+    int numMissings = 0;
+    int numStreakRead = 0;
+    int numStreakMiss = 0;
+    double minValue = 0.0;
+    double maxValue = 0.0;
+    std::string sensorName;
+
+  public:
+    const std::string& getName(void) const
+    {
+        return sensorName;
+    }
+
+    void updateName(std::string_view name)
+    {
+        sensorName = name;
+    }
+
+    // Returns true if this is the first successful reading
+    // This is so the caller can log the coefficients used
+    bool updateReading(double reading, int raw)
+    {
+        if constexpr (!enableInstrumentation)
+        {
+            return false;
+        }
+
+        bool first = ((numReadings == 0) && (numMissings == 0));
+
+        // Sensors can use "nan" to indicate unavailable reading
+        if (!(std::isfinite(reading)))
+        {
+            // Only show this if beginning a new streak
+            if (numStreakMiss == 0)
+            {
+                std::cerr << "IPMI sensor " << sensorName
+                          << ": Missing reading, byte=" << raw
+                          << ", Reading counts good=" << numReadings
+                          << " miss=" << numMissings
+                          << ", Prior good streak=" << numStreakRead << "\n";
+            }
+
+            numStreakRead = 0;
+            ++numMissings;
+            ++numStreakMiss;
+
+            return first;
+        }
+
+        // Only show this if beginning a new streak and not the first time
+        if ((numStreakRead == 0) && (numReadings != 0))
+        {
+            std::cerr << "IPMI sensor " << sensorName
+                      << ": Recovered reading, value=" << reading << " byte="
+                      << raw << ", Reading counts good=" << numReadings
+                      << " miss=" << numMissings
+                      << ", Prior miss streak=" << numStreakMiss << "\n";
+        }
+
+        // Initialize min/max if the first successful reading
+        if (numReadings == 0)
+        {
+            std::cerr << "IPMI sensor " << sensorName
+                      << ": First reading, value=" << reading << " byte=" << raw
+                      << "\n";
+
+            minValue = reading;
+            maxValue = reading;
+        }
+
+        numStreakMiss = 0;
+        ++numReadings;
+        ++numStreakRead;
+
+        // Only provide subsequent output if new min/max established
+        if (reading < minValue)
+        {
+            std::cerr << "IPMI sensor " << sensorName
+                      << ": Lowest reading, value=" << reading
+                      << " byte=" << raw << "\n";
+
+            minValue = reading;
+        }
+
+        if (reading > maxValue)
+        {
+            std::cerr << "IPMI sensor " << sensorName
+                      << ": Highest reading, value=" << reading
+                      << " byte=" << raw << "\n";
+
+            maxValue = reading;
+        }
+
+        return first;
+    }
+};
+
+class IPMIStatsTable
+{
+  private:
+    std::vector<IPMIStatsEntry> entries;
+
+  private:
+    void padEntries(size_t index)
+    {
+        char hexbuf[16];
+
+        // Pad vector until entries[index] becomes a valid index
+        while (entries.size() <= index)
+        {
+            // As name not known yet, use human-readable hex as name
+            IPMIStatsEntry newEntry;
+            sprintf(hexbuf, "0x%02zX", entries.size());
+            newEntry.updateName(hexbuf);
+
+            entries.push_back(std::move(newEntry));
+        }
+    }
+
+  public:
+    void wipeTable(void)
+    {
+        entries.clear();
+    }
+
+    const std::string& getName(size_t index)
+    {
+        padEntries(index);
+        return entries[index].getName();
+    }
+
+    void updateName(size_t index, std::string_view name)
+    {
+        padEntries(index);
+        entries[index].updateName(name);
+    }
+
+    bool updateReading(size_t index, double reading, int raw)
+    {
+        padEntries(index);
+        return entries[index].updateReading(reading, raw);
+    }
+};
+
+class IPMIWriteEntry
+{
+  private:
+    bool writePermission = false;
+
+  public:
+    bool getWritePermission(void) const
+    {
+        return writePermission;
+    }
+
+    void setWritePermission(bool permission)
+    {
+        writePermission = permission;
+    }
+};
+
+class IPMIWriteTable
+{
+  private:
+    std::vector<IPMIWriteEntry> entries;
+
+  private:
+    void padEntries(size_t index)
+    {
+        // Pad vector until entries[index] becomes a valid index
+        if (entries.size() <= index)
+        {
+            entries.resize(index + 1);
+        }
+    }
+
+  public:
+    void wipeTable(void)
+    {
+        entries.clear();
+    }
+
+    bool getWritePermission(size_t index)
+    {
+        padEntries(index);
+        return entries[index].getWritePermission();
+    }
+
+    void setWritePermission(size_t index, bool permission)
+    {
+        padEntries(index);
+        entries[index].setWritePermission(permission);
+    }
+};
+
+// Store information for threshold sensors and they are not used by VR
+// sensors. These objects are global singletons, used from a variety of places.
+inline IPMIStatsTable sdrStatsTable;
+inline IPMIWriteTable sdrWriteTable;
+
+/**
+ * Search ObjectMapper for sensors and update them to subtree.
+ *
+ * The function will search for sensors under either
+ * /xyz/openbmc_project/sensors or /xyz/openbmc_project/extsensors. It will
+ * optionally search VR typed sensors under /xyz/openbmc_project/vr
+ *
+ * @return the updated amount of times any of "sensors" or "extsensors" sensor
+ * paths updated successfully, previous amount if all failed. The "vr"
+ * sensor path is optional, and does not participate in the return value.
+ */
+uint16_t getSensorSubtree(std::shared_ptr<SensorSubTree>& subtree);
+
+bool getSensorNumMap(std::shared_ptr<SensorNumMap>& sensorNumMap);
+} // namespace details
+
+bool getSensorSubtree(SensorSubTree& subtree);
+
+#ifdef FEATURE_HYBRID_SENSORS
+ipmi::sensor::IdInfoMap::const_iterator findStaticSensor(
+    const std::string& path);
+#endif
+
+struct CmpStr
+{
+    bool operator()(const char* a, const char* b) const
+    {
+        return std::strcmp(a, b) < 0;
+    }
+};
+
+static constexpr size_t sensorTypeCodes = 0;
+static constexpr size_t sensorEventTypeCodes = 1;
+
+enum class SensorTypeCodes : uint8_t
+{
+    reserved = 0x00,
+    temperature = 0x01,
+    voltage = 0x02,
+    current = 0x03,
+    fan = 0x04,
+    physical_security = 0x5,
+    processor = 0x07,
+    power_unit = 0x09,
+    other = 0x0b,
+    memory = 0x0c,
+    buttons = 0x14,
+    watchdog2 = 0x23,
+    entity = 0x25,
+    oemC0 = 0xc0,
+};
+
+enum class SensorEventTypeCodes : uint8_t
+{
+    unspecified = 0x00,
+    threshold = 0x01,
+    sensorSpecified = 0x6f
+};
+
+extern boost::container::flat_map<
+    const char*, std::pair<SensorTypeCodes, SensorEventTypeCodes>, CmpStr>
+    sensorTypes;
+
+std::string getSensorTypeStringFromPath(const std::string& path);
+
+uint8_t getSensorTypeFromPath(const std::string& path);
+
+uint16_t getSensorNumberFromPath(const std::string& path);
+
+uint8_t getSensorEventTypeFromPath(const std::string& path);
+
+std::string getPathFromSensorNumber(uint16_t sensorNum);
+
+namespace ipmi
+{
+std::optional<std::map<std::string, std::vector<std::string>>>
+    getObjectInterfaces(const char* path);
+
+std::map<std::string, Value> getEntityManagerProperties(const char* path,
+                                                        const char* interface);
+
+std::optional<std::unordered_set<std::string>>& getIpmiDecoratorPaths(
+    const std::optional<ipmi::Context::ptr>& ctx);
+
+const std::string* getSensorConfigurationInterface(
+    const std::map<std::string, std::vector<std::string>>&
+        sensorInterfacesResponse);
+
+void updateIpmiFromAssociation(
+    const std::string& path,
+    const std::unordered_set<std::string>& ipmiDecoratorPaths,
+    const DbusInterfaceMap& sensorMap, uint8_t& entityId,
+    uint8_t& entityInstance);
+} // namespace ipmi
diff --git a/include/dbus-sdr/sensorcommands.hpp b/include/dbus-sdr/sensorcommands.hpp
new file mode 100644
index 0000000..9184633
--- /dev/null
+++ b/include/dbus-sdr/sensorcommands.hpp
@@ -0,0 +1,163 @@
+/*
+// Copyright (c) 2017 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 <dbus-sdr/sdrutils.hpp>
+
+#include <cstdint>
+
+#pragma pack(push, 1)
+
+struct SensorThresholdResp
+{
+    uint8_t readable;
+    uint8_t lowernc;
+    uint8_t lowercritical;
+    uint8_t lowernonrecoverable;
+    uint8_t uppernc;
+    uint8_t uppercritical;
+    uint8_t uppernonrecoverable;
+};
+
+#pragma pack(pop)
+
+enum class IPMIThresholdRespBits
+{
+    lowerNonCritical,
+    lowerCritical,
+    lowerNonRecoverable,
+    upperNonCritical,
+    upperCritical,
+    upperNonRecoverable
+};
+
+enum class IPMISensorReadingByte2 : uint8_t
+{
+    eventMessagesEnable = (1 << 7),
+    sensorScanningEnable = (1 << 6),
+    readingStateUnavailable = (1 << 5),
+};
+
+enum class IPMISensorReadingByte3 : uint8_t
+{
+    upperNonRecoverable = (1 << 5),
+    upperCritical = (1 << 4),
+    upperNonCritical = (1 << 3),
+    lowerNonRecoverable = (1 << 2),
+    lowerCritical = (1 << 1),
+    lowerNonCritical = (1 << 0),
+};
+
+enum class IPMISensorEventEnableByte2 : uint8_t
+{
+    eventMessagesEnable = (1 << 7),
+    sensorScanningEnable = (1 << 6),
+};
+
+enum class IPMISensorEventEnableThresholds : uint8_t
+{
+    nonRecoverableThreshold = (1 << 6),
+    criticalThreshold = (1 << 5),
+    nonCriticalThreshold = (1 << 4),
+    upperNonRecoverableGoingHigh = (1 << 3),
+    upperNonRecoverableGoingLow = (1 << 2),
+    upperCriticalGoingHigh = (1 << 1),
+    upperCriticalGoingLow = (1 << 0),
+    upperNonCriticalGoingHigh = (1 << 7),
+    upperNonCriticalGoingLow = (1 << 6),
+    lowerNonRecoverableGoingHigh = (1 << 5),
+    lowerNonRecoverableGoingLow = (1 << 4),
+    lowerCriticalGoingHigh = (1 << 3),
+    lowerCriticalGoingLow = (1 << 2),
+    lowerNonCriticalGoingHigh = (1 << 1),
+    lowerNonCriticalGoingLow = (1 << 0),
+};
+
+enum class IPMIGetSensorEventEnableThresholds : uint8_t
+{
+    lowerNonCriticalGoingLow = 0,
+    lowerNonCriticalGoingHigh = 1,
+    lowerCriticalGoingLow = 2,
+    lowerCriticalGoingHigh = 3,
+    lowerNonRecoverableGoingLow = 4,
+    lowerNonRecoverableGoingHigh = 5,
+    upperNonCriticalGoingLow = 6,
+    upperNonCriticalGoingHigh = 7,
+    upperCriticalGoingLow = 8,
+    upperCriticalGoingHigh = 9,
+    upperNonRecoverableGoingLow = 10,
+    upperNonRecoverableGoingHigh = 11,
+};
+
+namespace ipmi
+{
+
+uint16_t getNumberOfSensors();
+
+SensorSubTree& getSensorTree();
+
+ipmi_ret_t getSensorConnection(ipmi::Context::ptr ctx, uint8_t sensnum,
+                               std::string& connection, std::string& path,
+                               std::vector<std::string>* interfaces = nullptr);
+
+struct IPMIThresholds
+{
+    std::optional<uint8_t> warningLow;
+    std::optional<uint8_t> warningHigh;
+    std::optional<uint8_t> criticalLow;
+    std::optional<uint8_t> criticalHigh;
+};
+
+namespace sensor
+{
+/**
+ * @brief Retrieve the number of sensors that are not included in the list of
+ * sensors published via D-Bus
+ *
+ * @param[in]: ctx: the pointer to the D-Bus context
+ * @return: The number of additional sensors separate from those published
+ * dynamically on D-Bus
+ */
+size_t getOtherSensorsCount(ipmi::Context::ptr ctx);
+
+/**
+ * @brief Retrieve the record data for the sensors not published via D-Bus
+ *
+ * @param[in]: ctx: the pointer to the D-Bus context
+ * @param[in]: recordID: the integer index for the sensor to retrieve
+ * @param[out]: SDR data for the indexed sensor
+ * @return: 0: success
+ *          negative number: error condition
+ */
+int getOtherSensorsDataRecord(ipmi::Context::ptr ctx, uint16_t recordID,
+                              std::vector<uint8_t>& recordData);
+} // namespace sensor
+
+namespace dcmi
+{
+
+struct sensorInfo
+{
+    std::string objectPath;
+    uint8_t type;
+    uint16_t recordId;
+    uint8_t entityId;
+    uint8_t entityInstance;
+};
+
+} // namespace dcmi
+
+} // namespace ipmi
diff --git a/include/dbus-sdr/sensorutils.hpp b/include/dbus-sdr/sensorutils.hpp
new file mode 100644
index 0000000..51fbeaf
--- /dev/null
+++ b/include/dbus-sdr/sensorutils.hpp
@@ -0,0 +1,40 @@
+/*
+// Copyright (c) 2017 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 <cmath>
+#include <cstdint>
+#include <iostream>
+
+namespace ipmi
+{
+static constexpr int16_t maxInt10 = 0x1FF;
+static constexpr int16_t minInt10 = -0x200;
+static constexpr int8_t maxInt4 = 7;
+static constexpr int8_t minInt4 = -8;
+
+bool getSensorAttributes(const double max, const double min, int16_t& mValue,
+                         int8_t& rExp, int16_t& bValue, int8_t& bExp,
+                         bool& bSigned);
+
+uint8_t scaleIPMIValueFromDouble(const double value, const int16_t mValue,
+                                 const int8_t rExp, const int16_t bValue,
+                                 const int8_t bExp, const bool bSigned);
+
+uint8_t getScaledIPMIValue(const double value, const double max,
+                           const double min);
+} // namespace ipmi
diff --git a/include/dbus-sdr/storagecommands.hpp b/include/dbus-sdr/storagecommands.hpp
new file mode 100644
index 0000000..21c7613
--- /dev/null
+++ b/include/dbus-sdr/storagecommands.hpp
@@ -0,0 +1,136 @@
+/*
+// Copyright (c) 2017 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 "sensorhandler.hpp"
+
+#include <cstdint>
+
+static constexpr uint8_t ipmiSdrVersion = 0x51;
+
+namespace dynamic_sensors::ipmi::sel
+{
+static constexpr uint8_t selOperationSupport = 0x02;
+static constexpr uint8_t systemEvent = 0x02;
+static constexpr size_t systemEventSize = 3;
+static constexpr uint8_t oemTsEventFirst = 0xC0;
+static constexpr uint8_t oemTsEventLast = 0xDF;
+static constexpr size_t oemTsEventSize = 9;
+static constexpr uint8_t oemEventFirst = 0xE0;
+static constexpr uint8_t oemEventLast = 0xFF;
+static constexpr size_t oemEventSize = 13;
+static constexpr uint8_t eventMsgRev = 0x04;
+} // namespace dynamic_sensors::ipmi::sel
+
+enum class SdrRepositoryInfoOps : uint8_t
+{
+    allocCommandSupported = 0x1,
+    reserveSDRRepositoryCommandSupported = 0x2,
+    partialAddSDRSupported = 0x4,
+    deleteSDRSupported = 0x8,
+    reserved = 0x10,
+    modalLSB = 0x20,
+    modalMSB = 0x40,
+    overflow = 0x80
+};
+
+enum class GetFRUAreaAccessType : uint8_t
+{
+    byte = 0x0,
+    words = 0x1
+};
+
+enum class SensorUnits : uint8_t
+{
+    unspecified = 0x0,
+    degreesC = 0x1,
+    volts = 0x4,
+    amps = 0x5,
+    watts = 0x6,
+    joules = 0x7,
+    rpm = 0x12,
+};
+
+#pragma pack(push, 1)
+struct FRUHeader
+{
+    uint8_t commonHeaderFormat;
+    uint8_t internalOffset;
+    uint8_t chassisOffset;
+    uint8_t boardOffset;
+    uint8_t productOffset;
+    uint8_t multiRecordOffset;
+    uint8_t pad;
+    uint8_t checksum;
+};
+#pragma pack(pop)
+
+#pragma pack(push, 1)
+struct Type12Record
+{
+    get_sdr::SensorDataRecordHeader header;
+    uint8_t targetAddress;
+    uint8_t channelNumber;
+    uint8_t powerStateNotification;
+    uint8_t deviceCapabilities;
+    // define reserved bytes explicitly. The uint24_t is silently expanded to
+    // uint32_t, which ruins the byte alignment required by this structure.
+    uint8_t reserved[3];
+    uint8_t entityID;
+    uint8_t entityInstance;
+    uint8_t oem;
+    uint8_t typeLengthCode;
+    char name[16];
+
+    Type12Record(uint16_t recordID, uint8_t address, uint8_t chNumber,
+                 uint8_t pwrStateNotification, uint8_t capabilities,
+                 uint8_t eid, uint8_t entityInst, uint8_t mfrDefined,
+                 const std::string& sensorname) :
+        targetAddress(address), channelNumber(chNumber),
+        powerStateNotification(pwrStateNotification),
+        deviceCapabilities(capabilities), reserved{}, entityID(eid),
+        entityInstance(entityInst), oem(mfrDefined)
+    {
+        get_sdr::header::set_record_id(recordID, &header);
+        header.sdr_version = ipmiSdrVersion;
+        header.record_type = 0x12;
+        size_t nameLen = std::min(sensorname.size(), sizeof(name));
+        header.record_length =
+            sizeof(Type12Record) - sizeof(get_sdr::SensorDataRecordHeader) -
+            sizeof(name) + nameLen;
+        typeLengthCode = 0xc0 | nameLen;
+        std::copy(sensorname.begin(), sensorname.begin() + nameLen, name);
+    }
+};
+#pragma pack(pop)
+
+namespace ipmi
+{
+namespace storage
+{
+
+constexpr const size_t type12Count = 2;
+ipmi_ret_t getFruSdrs(ipmi::Context::ptr ctx, size_t index,
+                      get_sdr::SensorDataFruRecord& resp);
+
+ipmi_ret_t getFruSdrCount(ipmi::Context::ptr ctx, size_t& count);
+
+std::vector<uint8_t> getType8SDRs(
+    ipmi::sensor::EntityInfoMap::const_iterator& entity, uint16_t recordId);
+std::vector<uint8_t> getType12SDRs(uint16_t index, uint16_t recordId);
+std::vector<uint8_t> getNMDiscoverySDR(uint16_t index, uint16_t recordId);
+} // namespace storage
+} // namespace ipmi
diff --git a/include/ipmid-host/cmd-utils.hpp b/include/ipmid-host/cmd-utils.hpp
new file mode 100644
index 0000000..6646f03
--- /dev/null
+++ b/include/ipmid-host/cmd-utils.hpp
@@ -0,0 +1,52 @@
+#pragma once
+
+#include <unistd.h>
+
+#include <cstdint>
+#include <functional>
+#include <tuple>
+
+namespace phosphor
+{
+namespace host
+{
+namespace command
+{
+/** @detail After sending SMS_ATN to the Host, Host comes down and
+ *          asks why an 'SMS_ATN` was sent.
+ *          BMC then sends 'There is a Message to be Read` as response.
+ *          Host then comes down asks for Message and the specified
+ *          commands and data would go as data conforming to IPMI spec.
+ *
+ *          Refer: 6.13.2 Send Message Command From System Interface
+ *          in IPMI V2.0 spec.
+ */
+
+/** @brief IPMI command */
+using IPMIcmd = uint8_t;
+
+/** @brief Data associated with command */
+using Data = uint8_t;
+
+/** @brief <IPMI command, Data> to be sent as payload when Host asks for
+ *          the message that can be associated with the previous SMS_ATN
+ */
+using IpmiCmdData = std::pair<IPMIcmd, Data>;
+
+/** @detail Implementation specific callback function to be invoked
+ *          conveying the status of the executed command. Specific
+ *          implementations may then broadcast an agreed signal
+ */
+using CallBack = std::function<void(IpmiCmdData, bool)>;
+
+/** @detail Tuple encapsulating above 2 to enable using Manager by
+ *          different implementations. Users of Manager will supply
+ *          <Ipmi command, Data> along with the callback handler.
+ *          Manager will invoke the handler onveying the status of
+ *          the command.
+ */
+using CommandHandler = std::tuple<IpmiCmdData, CallBack>;
+
+} // namespace command
+} // namespace host
+} // namespace phosphor
diff --git a/include/ipmid-host/cmd.hpp b/include/ipmid-host/cmd.hpp
new file mode 100644
index 0000000..e844e8f
--- /dev/null
+++ b/include/ipmid-host/cmd.hpp
@@ -0,0 +1,9 @@
+#include <ipmid-host/cmd-utils.hpp>
+#include <sdbusplus/asio/connection.hpp>
+
+#include <memory>
+
+// Global Host Bound Command manager
+extern void ipmid_send_cmd_to_host(phosphor::host::command::CommandHandler&&);
+extern std::unique_ptr<sdbusplus::asio::connection>&
+    ipmid_get_sdbus_plus_handler();
diff --git a/include/ipmid/api-types.hpp b/include/ipmid/api-types.hpp
new file mode 100644
index 0000000..52c68ee
--- /dev/null
+++ b/include/ipmid/api-types.hpp
@@ -0,0 +1,565 @@
+/*
+ * 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/iana.hpp>
+
+#include <cstdint>
+#include <optional>
+#include <tuple>
+
+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;
+#ifdef ARM_SBMR_SUPPORT
+constexpr Group groupSBMR = 0xAE;
+#endif
+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;
+
+// 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 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
+
+#ifdef ARM_SBMR_SUPPORT
+namespace sbmr
+{
+constexpr Cmd cmdSendBootProgressCode = 0x02;
+constexpr Cmd cmdGetBootProgressCode = 0x03;
+} // namespace sbmr
+#endif
+
+// 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 = 0xC3;
+constexpr Cc ccOutOfSpace = 0xC4;
+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 response(ipmi::ccSuccess, args...);
+}
+static inline auto responseSuccess()
+{
+    return response(ipmi::ccSuccess);
+}
+
+/* helper functions for the various standard error response types */
+static inline auto responseBusy()
+{
+    return response(ccBusy);
+}
+static inline auto responseInvalidCommand()
+{
+    return response(ccInvalidCommand);
+}
+static inline auto responseInvalidCommandOnLun()
+{
+    return response(ccInvalidCommandOnLun);
+}
+static inline auto responseTimeout()
+{
+    return response(ccTimeout);
+}
+static inline auto responseOutOfSpace()
+{
+    return response(ccOutOfSpace);
+}
+static inline auto responseInvalidReservationId()
+{
+    return response(ccInvalidReservationId);
+}
+static inline auto responseReqDataTruncated()
+{
+    return response(ccReqDataTruncated);
+}
+static inline auto responseReqDataLenInvalid()
+{
+    return response(ccReqDataLenInvalid);
+}
+static inline auto responseReqDataLenExceeded()
+{
+    return response(ccReqDataLenExceeded);
+}
+static inline auto responseParmOutOfRange()
+{
+    return response(ccParmOutOfRange);
+}
+static inline auto responseRetBytesUnavailable()
+{
+    return response(ccRetBytesUnavailable);
+}
+static inline auto responseSensorInvalid()
+{
+    return response(ccSensorInvalid);
+}
+static inline auto responseInvalidFieldRequest()
+{
+    return response(ccInvalidFieldRequest);
+}
+static inline auto responseIllegalCommand()
+{
+    return response(ccIllegalCommand);
+}
+static inline auto responseResponseError()
+{
+    return response(ccResponseError);
+}
+static inline auto responseDuplicateRequest()
+{
+    return response(ccDuplicateRequest);
+}
+static inline auto responseCmdFailSdrMode()
+{
+    return response(ccCmdFailSdrMode);
+}
+static inline auto responseCmdFailFwUpdMode()
+{
+    return response(ccCmdFailFwUpdMode);
+}
+static inline auto responseCmdFailInitAgent()
+{
+    return response(ccCmdFailInitAgent);
+}
+static inline auto responseDestinationUnavailable()
+{
+    return response(ccDestinationUnavailable);
+}
+static inline auto responseInsufficientPrivilege()
+{
+    return response(ccInsufficientPrivilege);
+}
+static inline auto responseCommandNotAvailable()
+{
+    return response(ccCommandNotAvailable);
+}
+static inline auto responseCommandDisabled()
+{
+    return response(ccCommandDisabled);
+}
+static inline auto responseUnspecifiedError()
+{
+    return response(ccUnspecifiedError);
+}
+
+} // namespace ipmi
diff --git a/include/ipmid/api.h b/include/ipmid/api.h
new file mode 100644
index 0000000..fd6fee6
--- /dev/null
+++ b/include/ipmid/api.h
@@ -0,0 +1,153 @@
+#ifndef __HOST_IPMID_IPMI_COMMON_H__
+#define __HOST_IPMID_IPMI_COMMON_H__
+
+#include <systemd/sd-bus.h>
+
+#include <cstddef>
+
+/*
+ * 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 CommandPrivilege
+{
+    PRIVILEGE_CALLBACK = 0x01,
+    PRIVILEGE_USER,
+    PRIVILEGE_OPERATOR,
+    PRIVILEGE_ADMIN,
+    PRIVILEGE_OEM,
+    SYSTEM_INTERFACE = 0xFF,
+};
+
+// length of Completion Code and its ALWAYS _1_
+#define IPMI_CC_LEN 1
+
+// IPMI Net Function number as specified by IPMI V2.0 spec.
+// Example :
+// NETFUN_APP      =   (0x06 << 2),
+typedef unsigned char ipmi_netfn_t;
+
+// IPMI Command for a Net Function number as specified by IPMI V2.0 spec.
+typedef unsigned char ipmi_cmd_t;
+
+// Buffer containing data from sender of netfn and command as part of request
+typedef void* ipmi_request_t;
+
+// This is the response buffer that the provider of [netfn,cmd] will send back
+// to the caller. Provider will allocate the memory inside the handler and then
+// will do a memcpy to this response buffer and also will set the data size
+// parameter to the size of the buffer.
+// EXAMPLE :
+// unsigned char str[] = {0x00, 0x01, 0xFE, 0xFF, 0x0A, 0x01};
+// *data_len = 6;
+// memcpy(response, &str, *data_len);
+typedef void* ipmi_response_t;
+
+// This buffer contains any *user specific* data that is of interest only to the
+// plugin. For a ipmi function router, this data is opaque. At the time of
+// registering the plugin handlers, plugin may optionally allocate a memory and
+// fill in whatever needed that will be of help during the actual handling of
+// command. IPMID will just pass the netfn, cmd and also this data to plugins
+// during the command handler invocation.
+typedef void* ipmi_context_t;
+
+// Length of request / response buffer depending on whether the data is a
+// request or a response from a plugin handler.
+typedef std::size_t* ipmi_data_len_t;
+
+// Plugin function return the status code
+typedef unsigned char ipmi_ret_t;
+
+typedef enum CommandPrivilege ipmi_cmd_privilege_t;
+
+// This is the callback handler that the plugin registers with IPMID. IPMI
+// function router will then make a call to this callback handler with the
+// necessary arguments of netfn, cmd, request, response, size and context.
+typedef ipmi_ret_t (*ipmid_callback_t)(ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t,
+                                       ipmi_response_t, ipmi_data_len_t,
+                                       ipmi_context_t);
+
+// This is the constructor function that is called into by each plugin handlers.
+// When ipmi sets up the callback handlers, a call is made to this with
+// information of netfn, cmd, callback handler pointer and context data.
+void ipmi_register_callback(ipmi_netfn_t, ipmi_cmd_t, ipmi_context_t,
+                            ipmid_callback_t, ipmi_cmd_privilege_t);
+
+unsigned short reserveSel(void);
+bool checkSELReservation(unsigned short id);
+void cancelSELReservation(void);
+
+// 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 are also shifted left to make room for the LUN.
+enum ipmi_net_fns
+{
+    NETFUN_CHASSIS = 0x00,
+    NETFUN_BRIDGE = 0x02,
+    NETFUN_SENSOR = 0x04,
+    NETFUN_APP = 0x06,
+    NETFUN_FIRMWARE = 0x08,
+    NETFUN_STORAGE = 0x0a,
+    NETFUN_TRANSPORT = 0x0c,
+    NETFUN_GRPEXT = 0x2c,
+    NETFUN_OEM_GROUP = 0x2e,
+    NETFUN_NONE = 0x30,
+    NETFUN_OEM = 0x32,
+    NETFUN_IBM_OEM = 0x3A
+};
+
+// Return (completion) codes from a IPMI operation as needed by IPMI V2.0 spec.
+enum ipmi_return_codes
+{
+    IPMI_CC_OK = 0x00,
+    IPMI_DCMI_CC_NO_ACTIVE_POWER_LIMIT = 0x80,
+    IPMI_WDOG_CC_NOT_INIT = 0x80,
+    IPMI_CC_SYSTEM_INFO_PARAMETER_NOT_SUPPORTED = 0x80,
+    IPMI_CC_SYSTEM_INFO_PARAMETER_SET_READ_ONLY = 0x82,
+    IPMI_CC_BUSY = 0xC0,
+    IPMI_CC_INVALID = 0xC1,
+    IPMI_CC_TIMEOUT = 0xC3,
+    IPMI_CC_OUT_OF_SPACE = 0xC4,
+    IPMI_CC_INVALID_RESERVATION_ID = 0xC5,
+    IPMI_CC_REQ_DATA_TRUNCATED = 0xC6,
+    IPMI_CC_REQ_DATA_LEN_INVALID = 0xC7,
+    IPMI_CC_PARM_OUT_OF_RANGE = 0xC9,
+    IPMI_CC_REQUESTED_TOO_MANY_BYTES = 0xCA,
+    IPMI_CC_SENSOR_INVALID = 0xCB,
+    IPMI_CC_INVALID_FIELD_REQUEST = 0xCC,
+    IPMI_CC_ILLEGAL_COMMAND = 0xCD,
+    IPMI_CC_RESPONSE_ERROR = 0xCE,
+    IPMI_CC_INSUFFICIENT_PRIVILEGE = 0xD4,
+    IPMI_CC_UNSPECIFIED_ERROR = 0xFF,
+};
+
+// Temp solution: To detect the request source channel.
+// There is no stright forward way to get the exact
+// channel so we are hardcoding it to KCS in case host-ipmid
+// and LAN1 for netipmid.
+// we can't differentiate between LAN1 & LAN2 for netipmid in this logic.
+// As our current design will not be able to support the same. This is done
+// so that in all the places where ever we need to use the self channel can be
+// be implemented properly and based on new architecture.this can be updated.
+typedef enum
+{
+    interfaceKCS = 0,
+    interfaceLAN1 = 1,
+    interfaceUnknown = 0xFF
+} EInterfaceIndex;
+
+EInterfaceIndex getInterfaceIndex(void);
+
+sd_bus* ipmid_get_sd_bus_connection(void);
+sd_event* ipmid_get_sd_event_connection(void);
+sd_bus_slot* ipmid_get_sd_bus_slot(void);
+
+// move this from ipmid.hpp, which is now gone
+// this should not be used. Use the channel API to get the channel size
+#define MAX_IPMI_BUFFER 64
+
+#endif
diff --git a/include/ipmid/api.hpp b/include/ipmid/api.hpp
new file mode 100644
index 0000000..68e24ea
--- /dev/null
+++ b/include/ipmid/api.hpp
@@ -0,0 +1,87 @@
+/*
+ * 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
+// make it possible to ONLY include api.hpp during the transition
+#ifdef ALLOW_DEPRECATED_API
+#include <ipmid/api.h>
+#endif
+
+#include <boost/asio/io_context.hpp>
+#include <ipmid/api-types.hpp>
+#include <ipmid/filter.hpp>
+#include <ipmid/handler.hpp>
+#include <ipmid/message/types.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+// any client can interact with the main asio context
+std::shared_ptr<boost::asio::io_context> getIoContext();
+
+// 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)
+{
+    boost::asio::post(*getIoContext(), std::forward<WorkFn>(work));
+}
+
+enum class SignalResponse : int
+{
+    breakExecution,
+    continueExecution,
+};
+
+/**
+ * @brief add a signal handler
+ *
+ * This registers a handler to be called asynchronously via the execution
+ * queue when the specified signal is received.
+ *
+ * Priority allows a signal handler to specify what order in the handler
+ * chain it gets called. Lower priority numbers will cause the handler to
+ * be executed later in the chain, while the highest priority numbers will cause
+ * the handler to be executed first.
+ *
+ * In order to facilitate a chain of handlers, each handler in the chain will be
+ * able to return breakExecution or continueExecution. Returning breakExecution
+ * will break the chain and no further handlers will execute for that signal.
+ * Returning continueExecution will allow lower-priority handlers to execute.
+ *
+ * By default, the main asio execution loop will register a low priority
+ * (prioOpenBmcBase) handler for SIGINT and SIGTERM to cause the process to stop
+ * on either of those signals. To prevent one of those signals from causing the
+ * process to stop, simply register a higher priority handler that returns
+ * breakExecution.
+ *
+ * @param int - priority of handler
+ * @param int - signal number to wait for
+ * @param handler - the callback function to be executed
+ */
+void registerSignalHandler(int priority, int signalNumber,
+                           const std::function<SignalResponse(int)>& handler);
diff --git a/include/ipmid/entity_map_json.hpp b/include/ipmid/entity_map_json.hpp
new file mode 100644
index 0000000..bfc8e72
--- /dev/null
+++ b/include/ipmid/entity_map_json.hpp
@@ -0,0 +1,57 @@
+#pragma once
+
+#include <ipmid/types.hpp>
+#include <nlohmann/json.hpp>
+
+#include <memory>
+
+namespace ipmi
+{
+namespace sensor
+{
+
+/**
+ * @brief Grab a handle to the entity map.
+ */
+const EntityInfoMap& getIpmiEntityRecords();
+
+/**
+ * @brief Open the default entity map json file, and if present and valid json,
+ * return a built entity map.
+ *
+ * @return the map
+ */
+EntityInfoMap buildEntityMapFromFile();
+
+/**
+ * @brief Given json data validate the data matches the expected format for the
+ * entity map configuration and parse the data into a map of the entities.
+ *
+ * If any entry is invalid, the entire contents passed in is disregarded as
+ * possibly corrupt.
+ *
+ * @param[in] data - the json data
+ * @return the map
+ */
+EntityInfoMap buildJsonEntityMap(const nlohmann::json& data);
+
+/**
+ * @brief Owner of the EntityInfoMap.
+ */
+class EntityInfoMapContainer
+{
+  public:
+    /** Get ahold of the owner. */
+    static EntityInfoMapContainer* getContainer();
+    /** Get ahold of the records. */
+    const EntityInfoMap& getIpmiEntityRecords();
+
+  private:
+    EntityInfoMapContainer(const EntityInfoMap& entityRecords) :
+        entityRecords(entityRecords)
+    {}
+    EntityInfoMap entityRecords;
+};
+
+} // namespace sensor
+} // namespace ipmi
diff --git a/include/ipmid/filter.hpp b/include/ipmid/filter.hpp
new file mode 100644
index 0000000..d57b838
--- /dev/null
+++ b/include/ipmid/filter.hpp
@@ -0,0 +1,136 @@
+/**
+ * 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/callable_traits.hpp>
+#include <ipmid/api-types.hpp>
+#include <ipmid/message.hpp>
+
+#include <algorithm>
+#include <cstdint>
+#include <memory>
+#include <tuple>
+#include <utility>
+
+namespace ipmi
+{
+
+using FilterFunction = ipmi::Cc(ipmi::message::Request::ptr);
+
+/**
+ * @brief Filter 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 FilterBase
+{
+  public:
+    using ptr = std::shared_ptr<FilterBase>;
+
+    virtual ~FilterBase() = default;
+
+    virtual ipmi::Cc call(message::Request::ptr request) = 0;
+};
+
+/**
+ * @brief filter concrete class
+ *
+ * This is the base template that ipmi filters will resolve into. This is
+ * essentially just a wrapper to hold the filter callback so it can be stored in
+ * the filter list.
+ *
+ * Filters are called with a ipmi::message::Request shared_ptr on all IPMI
+ * commands in priority order and each filter has the opportunity to reject the
+ * command (by returning an IPMI error competion code.) If all the filters
+ * return success, the actual IPMI command will be executed. Filters can reject
+ * the command for any reason, based on system state, the context, the command
+ * payload, etc.
+ */
+template <typename Filter>
+class IpmiFilter : public FilterBase
+{
+  public:
+    IpmiFilter(Filter&& filter) : filter_(std::move(filter)) {}
+
+    ipmi::Cc call(message::Request::ptr request) override
+    {
+        return filter_(request);
+    }
+
+  private:
+    Filter filter_;
+};
+
+/**
+ * @brief helper function to construct a filter object
+ *
+ * This is called internally by the ipmi::registerFilter function.
+ */
+template <typename Filter>
+static inline auto makeFilter(Filter&& filter)
+{
+    FilterBase::ptr ptr(new IpmiFilter<Filter>(std::forward<Filter>(filter)));
+    return ptr;
+}
+template <typename Filter>
+static inline auto makeFilter(const Filter& filter)
+{
+    Filter lFilter = filter;
+    return makeFilter(std::forward<Filter>(lFilter));
+}
+
+namespace impl
+{
+
+// IPMI command filter registration implementation
+void registerFilter(int prio, ::ipmi::FilterBase::ptr filter);
+
+} // namespace impl
+
+/**
+ * @brief IPMI command filter registration function
+ *
+ * This function should be used to register IPMI command filter functions.
+ * This function just passes the callback to makeFilter, which creates a
+ * wrapper functor object that ultimately calls the callback.
+ *
+ * Filters are called with a ipmi::message::Request shared_ptr on all IPMI
+ * commands in priority order and each filter has the opportunity to reject the
+ * command (by returning an IPMI error competion code.) If all the filters
+ * return success, the actual IPMI command will be executed. Filters can reject
+ * the command for any reason, based on system state, the context, the command
+ * payload, etc.
+ *
+ * @param prio - priority at which to register; see api.hpp
+ * @param filter - the callback function that will handle this request
+ *
+ * @return bool - success of registering the handler
+ */
+template <typename Filter>
+void registerFilter(int prio, Filter&& filter)
+{
+    auto f = ipmi::makeFilter(std::forward<Filter>(filter));
+    impl::registerFilter(prio, f);
+}
+
+template <typename Filter>
+void registerFilter(int prio, const Filter& filter)
+{
+    auto f = ipmi::makeFilter(filter);
+    impl::registerFilter(prio, f);
+}
+
+} // namespace ipmi
diff --git a/include/ipmid/handler.hpp b/include/ipmid/handler.hpp
new file mode 100644
index 0000000..c3d5f91
--- /dev/null
+++ b/include/ipmid/handler.hpp
@@ -0,0 +1,677 @@
+/**
+ * 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 <cxxabi.h>
+
+#include <boost/asio/spawn.hpp>
+#include <boost/callable_traits.hpp>
+#include <ipmid/api-types.hpp>
+#include <ipmid/message.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <phosphor-logging/log.hpp>
+#include <user_channel/channel_layer.hpp>
+
+#include <algorithm>
+#include <cstdint>
+#include <exception>
+#include <memory>
+#include <optional>
+#include <stdexcept>
+#include <tuple>
+#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();
+    response->cc = cc;
+    response->pack(args...);
+    return response;
+}
+static inline message::Response::ptr errorResponse(
+    message::Request::ptr request, ipmi::Cc cc)
+{
+    message::Response::ptr response = request->makeResponse();
+    response->cc = cc;
+    return response;
+}
+
+/** @brief Exception extension that allows setting an IPMI return code */
+class HandlerCompletion
+{
+  public:
+    HandlerCompletion(Cc cc) noexcept : cc(cc) {}
+
+    Cc code() const noexcept
+    {
+        return cc;
+    }
+
+  private:
+    Cc cc;
+};
+
+/** @brief Exception extension that allows setting an IPMI return code and
+ * printing out a logged error */
+class HandlerException : public HandlerCompletion, public std::runtime_error
+{
+  public:
+    HandlerException(Cc cc, const char* what) :
+        HandlerCompletion(cc), std::runtime_error(what)
+    {}
+    HandlerException(Cc cc, const std::string& what) :
+        HandlerException(cc, what.c_str())
+    {}
+};
+
+static inline const char* currentExceptionType()
+{
+    int status;
+    return abi::__cxa_demangle(abi::__cxa_current_exception_type()->name(),
+                               nullptr, nullptr, &status);
+}
+
+/**
+ * @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>;
+
+    virtual ~HandlerBase() = default;
+
+    /** @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{};
+        request->payload.trailingOk = false;
+        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.
+         */
+        ResultType result;
+        try
+        {
+            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);
+            }
+
+            // execute the registered callback function and get the
+            // ipmi::RspType<>
+            result = std::apply(handler_, *inputArgs);
+        }
+        catch (const HandlerException& e)
+        {
+            lg2::info("Handler produced exception, NetFn: {NETFN}, "
+                      "Cmd: {CMD}: {ERROR}",
+                      "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
+                      request->ctx->cmd, "ERROR", e);
+            return errorResponse(request, e.code());
+        }
+        catch (const std::exception& e)
+        {
+            lg2::error("Handler failed to catch exception, NetFn: {NETFN}, "
+                       "Cmd: {CMD}: {ERROR}",
+                       "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
+                       request->ctx->cmd, "ERROR", e);
+            return errorResponse(request, ccUnspecifiedError);
+        }
+        catch (const HandlerCompletion& c)
+        {
+            return errorResponse(request, c.code());
+        }
+        catch (...)
+        {
+            const char* what = currentExceptionType();
+            lg2::error("Handler failed to catch exception, NetFn: {NETFN}, "
+                       "Cmd: {CMD}: {ERROR}",
+                       "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
+                       request->ctx->cmd, "ERROR", what);
+            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
+static constexpr size_t maxLegacyBufferSize = 64 * 1024;
+/**
+ * @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, void* ctx = nullptr) :
+        handler_(handler), handlerCtx(ctx)
+    {}
+
+  private:
+    ipmid_callback_t handler_;
+    void* handlerCtx;
+
+    /** @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();
+        // allocate a big response buffer here
+        response->payload.resize(maxLegacyBufferSize);
+
+        size_t len = request->payload.size() - request->payload.rawIndex;
+        Cc ccRet{ccSuccess};
+        try
+        {
+            ccRet =
+                handler_(request->ctx->netFn, request->ctx->cmd,
+                         request->payload.data() + request->payload.rawIndex,
+                         response->payload.data(), &len, handlerCtx);
+        }
+        catch (const HandlerException& e)
+        {
+            lg2::info("Legacy Handler produced exception, NetFn: {NETFN}, "
+                      "Cmd: {CMD}: {ERROR}",
+                      "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
+                      request->ctx->cmd, "ERROR", e);
+            return errorResponse(request, e.code());
+        }
+        catch (const std::exception& e)
+        {
+            lg2::error("Legacy Handler failed to catch exception, "
+                       "NetFn: {NETFN}, Cmd: {CMD}: {ERROR}",
+                       "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
+                       request->ctx->cmd, "ERROR", e);
+            return errorResponse(request, ccUnspecifiedError);
+        }
+        catch (const HandlerCompletion& c)
+        {
+            return errorResponse(request, c.code());
+        }
+        catch (...)
+        {
+            const char* what = currentExceptionType();
+            lg2::error("Handler failed to catch exception, NetFn: {NETFN}, "
+                       "Cmd: {CMD}: {ERROR}",
+                       "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
+                       request->ctx->cmd, "ERROR", what);
+            return errorResponse(request, ccUnspecifiedError);
+        }
+        response->cc = ccRet;
+        response->payload.resize(len);
+        return response;
+    }
+};
+
+/**
+ * @brief Legacy IPMI OEM handler class
+ *
+ * Legacy IPMI OEM 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<oem::Handler> final : public HandlerBase
+{
+  public:
+    explicit IpmiHandler(const oem::Handler& handler) : handler_(handler) {}
+
+  private:
+    oem::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 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();
+        // allocate a big response buffer here
+        response->payload.resize(maxLegacyBufferSize);
+
+        size_t len = request->payload.size() - request->payload.rawIndex;
+        Cc ccRet{ccSuccess};
+        try
+        {
+            ccRet =
+                handler_(request->ctx->cmd,
+                         request->payload.data() + request->payload.rawIndex,
+                         response->payload.data(), &len);
+        }
+        catch (const HandlerException& e)
+        {
+            lg2::info("Legacy OEM Handler produced exception, NetFn: {NETFN}, "
+                      "Cmd: {CMD}: {ERROR}",
+                      "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
+                      request->ctx->cmd, "ERROR", e);
+            return errorResponse(request, e.code());
+        }
+        catch (const std::exception& e)
+        {
+            lg2::error("Legacy OEM Handler failed to catch exception, "
+                       "NetFn: {NETFN}, Cmd: {CMD}: {ERROR}",
+                       "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
+                       request->ctx->cmd, "ERROR", e);
+            return errorResponse(request, ccUnspecifiedError);
+        }
+        catch (const HandlerCompletion& c)
+        {
+            return errorResponse(request, c.code());
+        }
+        catch (...)
+        {
+            const char* what = currentExceptionType();
+            lg2::error("Legacy failed to catch exception, NetFn: {NETFN}, "
+                       "Cmd: {CMD}: {ERROR}",
+                       "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
+                       request->ctx->cmd, "ERROR", what);
+            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,
+                              void* ctx = nullptr)
+{
+    HandlerBase::ptr ptr(new IpmiHandler<ipmid_callback_t>(handler, ctx));
+    return ptr;
+}
+
+/**
+ * @brief create a legacy IPMI OEM 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 Router::registerHandler method.
+ *
+ * @param handler the function pointer to the callback
+ *
+ * @return A shared_ptr to the created handler object
+ */
+inline auto makeLegacyHandler(oem::Handler&& handler)
+{
+    HandlerBase::ptr ptr(
+        new IpmiHandler<oem::Handler>(std::forward<oem::Handler>(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 impl
+{
+
+// IPMI command handler registration implementation
+bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv,
+                     ::ipmi::HandlerBase::ptr handler);
+bool registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv,
+                          ::ipmi::HandlerBase::ptr handler);
+bool registerOemHandler(int prio, Iana iana, 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);
+}
+
+/**
+ * @brief register a IPMI OEM group handler
+ *
+ * From IPMI 2.0 spec Network Function Codes Table (Row 2Ch):
+ * The first data byte position in requests and responses under this network
+ * function identifies the defining body that specifies command functionality.
+ * Software assumes that the command and completion code field positions will
+ * hold command and completion code values.
+ *
+ * The following values are used to identify the defining body:
+ * 00h PICMG - PCI Industrial Computer Manufacturer’s Group.  (www.picmg.com)
+ * 01h DMTF Pre-OS Working Group ASF Specification (www.dmtf.org)
+ * 02h Server System Infrastructure (SSI) Forum (www.ssiforum.org)
+ * 03h VITA Standards Organization (VSO) (www.vita.com)
+ * DCh DCMI Specifications (www.intel.com/go/dcmi)
+ * all other Reserved
+ *
+ * When this network function is used, the ID for the defining body occupies
+ * the first data byte in a request, and the second data byte (following the
+ * completion code) in a response.
+ *
+ * @tparam Handler - implicitly specified callback function type
+ * @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>
+void registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv,
+                          Handler&& handler)
+{
+    auto h = ipmi::makeHandler(handler);
+    impl::registerGroupHandler(prio, group, cmd, priv, h);
+}
+
+/**
+ * @brief register a IPMI OEM IANA handler
+ *
+ * From IPMI spec Network Function Codes Table (Row 2Eh):
+ * The first three data bytes of requests and responses under this network
+ * function explicitly identify the OEM or non-IPMI group that specifies the
+ * command functionality. While the OEM or non-IPMI group defines the
+ * functional semantics for the cmd and remaining data fields, the cmd field
+ * is required to hold the same value in requests and responses for a given
+ * operation in order to be supported under the IPMI message handling and
+ * transport mechanisms.
+ *
+ * When this network function is used, the IANA Enterprise Number for the
+ * defining body occupies the first three data bytes in a request, and the
+ * first three data bytes following the completion code position in a
+ * response.
+ *
+ * @tparam Handler - implicitly specified callback function type
+ * @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>
+void registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv,
+                        Handler&& handler)
+{
+    auto h = ipmi::makeHandler(handler);
+    impl::registerOemHandler(prio, iana, 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 */
diff --git a/include/ipmid/iana.hpp b/include/ipmid/iana.hpp
new file mode 100644
index 0000000..aa9e173
--- /dev/null
+++ b/include/ipmid/iana.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+#include <cstdint>
+
+namespace oem
+{
+using Number = std::uint32_t; // smallest standard size >= 24.
+
+/*
+ * This is the OpenBMC IANA OEM Number
+ */
+constexpr Number obmcOemNumber = 49871;
+
+/*
+ * This is the Google IANA OEM Number
+ */
+constexpr Number googOemNumber = 11129;
+
+} // namespace oem
diff --git a/include/ipmid/message.hpp b/include/ipmid/message.hpp
new file mode 100644
index 0000000..6698581
--- /dev/null
+++ b/include/ipmid/message.hpp
@@ -0,0 +1,668 @@
+/**
+ * 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 <ipmid/api-types.hpp>
+#include <ipmid/message/types.hpp>
+#include <ipmid/types.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/asio/connection.hpp>
+
+#include <algorithm>
+#include <cstdint>
+#include <exception>
+#include <memory>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+namespace ipmi
+{
+
+struct Context
+{
+    using ptr = std::shared_ptr<Context>;
+
+    Context() = delete;
+    Context(const Context&) = default;
+    Context& operator=(const Context&) = default;
+    Context(Context&&) = delete;
+    Context& operator=(Context&&) = delete;
+
+    Context(std::shared_ptr<sdbusplus::asio::connection> bus, NetFn netFn,
+            uint8_t lun, Cmd cmd, int channel, int userId, uint32_t sessionId,
+            Privilege priv, int rqSA, int hostIdx,
+            boost::asio::yield_context& yield) :
+        bus(bus), netFn(netFn), lun(lun), cmd(cmd), channel(channel),
+        userId(userId), sessionId(sessionId), priv(priv), group(0), rqSA(rqSA),
+        hostIdx(hostIdx), yield(yield)
+    {}
+
+    std::shared_ptr<sdbusplus::asio::connection> bus;
+    // normal IPMI context (what call is this, from whence it came...)
+    NetFn netFn;
+    uint8_t lun;
+    Cmd cmd;
+    int channel;
+    int userId;
+    uint32_t sessionId;
+    Privilege priv;
+    // defining body code for netFnGroup
+    Group group;
+    // srcAddr is only set on IPMB requests because
+    // Platform Event Message needs it to determine the incoming format
+    int rqSA;
+    int hostIdx;
+    boost::asio::yield_context yield;
+};
+
+namespace message
+{
+
+namespace details
+{
+
+template <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(SecureBuffer&& data) : raw(std::move(data)) {}
+
+    ~Payload()
+    {
+        if (raw.size() != 0 && std::uncaught_exceptions() == 0 && !trailingOk &&
+            !unpackCheck && !unpackError)
+        {
+            lg2::error(
+                "Failed to check request for full unpack: raw size: {RAW_SIZE}",
+                "RAW_SIZE", raw.size());
+        }
+    }
+
+    /******************************************************************
+     * raw vector access
+     *****************************************************************/
+    /**
+     * @brief return the size of the underlying raw buffer
+     */
+    size_t size() const
+    {
+        return raw.size();
+    }
+    /**
+     * @brief resize the underlying raw buffer to a new size
+     *
+     * @param sz - new size for the buffer
+     */
+    void resize(size_t sz)
+    {
+        raw.resize(sz);
+    }
+    /**
+     * @brief return a pointer to the underlying raw buffer
+     */
+    uint8_t* data()
+    {
+        return raw.data();
+    }
+    /**
+     * @brief return a const pointer to the underlying raw buffer
+     */
+    const uint8_t* data() const
+    {
+        return raw.data();
+    }
+
+    /******************************************************************
+     * Response operations
+     *****************************************************************/
+    /**
+     * @brief append a series of bytes to the buffer
+     *
+     * @tparam T - the type pointer to return; must be compatible to a byte
+     *
+     * @param begin - a pointer to the beginning of the series
+     * @param end - a pointer to the end of the series
+     */
+    template <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 Prepends another payload to this one
+     *
+     * Avoid using this unless absolutely required since it inserts into the
+     * front of the response payload.
+     *
+     * @param p - The payload to prepend
+     *
+     * @retunr int - non-zero on prepend errors
+     */
+    int prepend(const ipmi::message::Payload& p)
+    {
+        if (bitCount != 0 || p.bitCount != 0)
+        {
+            return 1;
+        }
+        raw.reserve(raw.size() + p.raw.size());
+        raw.insert(raw.begin(), p.raw.begin(), p.raw.end());
+        return 0;
+    }
+
+    /******************************************************************
+     * Request operations
+     *****************************************************************/
+    /**
+     * @brief pop a series of bytes from the raw buffer
+     *
+     * @tparam T - the type pointer to return; must be compatible to a byte
+     *
+     * @param count - the number of bytes to return
+     *
+     * @return - a tuple of pointers (begin,begin+count)
+     */
+    template <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;
+    SecureBuffer raw;
+    size_t rawIndex = 0;
+    bool trailingOk = true;
+    bool unpackCheck = false;
+    bool unpackError = false;
+};
+
+/**
+ * @brief high-level interface to an IPMI response
+ *
+ * Make it easy to just pack in the response args from the callback into a
+ * buffer that goes back to the requester.
+ */
+struct Response
+{
+    /* Define all of the basic class operations:
+     *     Not allowed:
+     *         - Default constructor to avoid nullptrs.
+     *     Allowed:
+     *         - Copy operations.
+     *         - Move operations.
+     *         - Destructor.
+     */
+    Response() = delete;
+    Response(const Response&) = default;
+    Response& operator=(const Response&) = default;
+    Response(Response&&) = default;
+    Response& operator=(Response&&) = default;
+    ~Response() = default;
+
+    using ptr = std::shared_ptr<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);
+    }
+
+    /**
+     * @brief Prepends another payload to this one
+     *
+     * Avoid using this unless absolutely required since it inserts into the
+     * front of the response payload.
+     *
+     * @param p - The payload to prepend
+     *
+     * @retunr int - non-zero on prepend errors
+     */
+    int prepend(const ipmi::message::Payload& p)
+    {
+        return payload.prepend(p);
+    }
+
+    Payload payload;
+    Context::ptr ctx;
+    Cc cc;
+};
+
+/**
+ * @brief high-level interface to an IPMI request
+ *
+ * Make it easy to unpack the buffer into the request args for the callback.
+ */
+struct Request
+{
+    /* Define all of the basic class operations:
+     *     Not allowed:
+     *         - Default constructor to avoid nullptrs.
+     *     Allowed:
+     *         - Copy operations.
+     *         - Move operations.
+     *         - Destructor.
+     */
+    Request() = delete;
+    Request(const Request&) = default;
+    Request& operator=(const Request&) = default;
+    Request(Request&&) = default;
+    Request& operator=(Request&&) = default;
+    ~Request() = default;
+
+    using ptr = std::shared_ptr<Request>;
+
+    explicit Request(Context::ptr context, SecureBuffer&& d) :
+        payload(std::forward<SecureBuffer>(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)
+        {
+            // not all bits were consumed by requested parameters
+            return ipmi::ccReqDataLenInvalid;
+        }
+        if (!payload.trailingOk)
+        {
+            if (!payload.fullyUnpacked())
+            {
+                // not all bits were consumed by requested parameters
+                return ipmi::ccReqDataLenInvalid;
+            }
+        }
+        return ipmi::ccSuccess;
+    }
+
+    /**
+     * @brief unpack a tuple of values (of any supported type) from the payload
+     *
+     * This will unpack the elements of the tuple as if each one was passed in
+     * individually, as if passed into the above variadic function.
+     *
+     * @tparam Types - the implicitly declared list of the tuple element types
+     *
+     * @param t - the tuple of values to unpack
+     *
+     * @return int - non-zero on unpack error
+     */
+    template <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..724bffc
--- /dev/null
+++ b/include/ipmid/message/pack.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 <ipmid/message.hpp>
+#include <ipmid/message/types.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <phosphor-logging/log.hpp>
+
+#include <array>
+#include <memory>
+#include <optional>
+#include <span>
+#include <string_view>
+#include <tuple>
+#include <utility>
+#include <variant>
+#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, const T& t)
+    {
+        static_assert(std::is_integral_v<T>,
+                      "Attempt to pack a type that has no IPMI pack operation");
+        // 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::tuple<T> */
+template <typename... T>
+struct PackSingle<std::tuple<T...>>
+{
+    static int op(Payload& p, const std::tuple<T...>& v)
+    {
+        return std::apply([&p](const T&... args) { return p.pack(args...); },
+                          v);
+    }
+};
+
+/** @brief Specialization of PackSingle for std::string
+ *  represented as a UCSD-Pascal style string
+ */
+template <>
+struct PackSingle<std::string>
+{
+    static int op(Payload& p, const std::string& t)
+    {
+        // check length first
+        uint8_t len;
+        if (t.length() > std::numeric_limits<decltype(len)>::max())
+        {
+            lg2::error("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 <bitcount_t N>
+struct PackSingle<fixed_uint_t<N>>
+{
+    static int op(Payload& p, const fixed_uint_t<N>& t)
+    {
+        size_t count = N;
+        static_assert(N <= (details::bitStreamSize - CHAR_BIT));
+        static_assert(N <= std::numeric_limits<uint64_t>::digits,
+                      "Type exceeds uint64_t limit");
+        uint64_t bits = static_cast<uint64_t>(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, const 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, const 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::optional<T> */
+template <typename T>
+struct PackSingle<std::optional<T>>
+{
+    static int op(Payload& p, const std::optional<T>& t)
+    {
+        int ret = 0;
+        if (t)
+        {
+            ret = PackSingle<T>::op(p, *t);
+        }
+        return ret;
+    }
+};
+
+/** @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, const std::array<T, N>& t)
+    {
+        int ret = 0;
+        for (const 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, const std::vector<T>& t)
+    {
+        int ret = 0;
+        for (const 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, const std::vector<uint8_t>& t)
+    {
+        if (p.bitCount != 0)
+        {
+            return 1;
+        }
+        p.raw.reserve(p.raw.size() + t.size());
+        p.raw.insert(p.raw.end(), t.begin(), t.end());
+        return 0;
+    }
+};
+
+/** @brief Specialization of PackSingle for SecureBuffer */
+template <>
+struct PackSingle<SecureBuffer>
+{
+    static int op(Payload& p, const SecureBuffer& t)
+    {
+        if (p.bitCount != 0)
+        {
+            return 1;
+        }
+        p.raw.reserve(p.raw.size() + t.size());
+        p.raw.insert(p.raw.end(), t.begin(), t.end());
+        return 0;
+    }
+};
+
+/** @brief Specialization of PackSingle for std::span<const uint8_t> */
+template <>
+struct PackSingle<std::span<const uint8_t>>
+{
+    static int op(Payload& p, const std::span<const uint8_t>& t)
+    {
+        if (p.bitCount != 0)
+        {
+            return 1;
+        }
+        p.raw.reserve(p.raw.size() + t.size());
+        p.raw.insert(p.raw.end(), t.begin(), t.end());
+        return 0;
+    }
+};
+
+/** @brief Specialization of PackSingle for std::string_view */
+template <>
+struct PackSingle<std::string_view>
+{
+    static int op(Payload& p, const std::string_view& t)
+    {
+        if (p.bitCount != 0)
+        {
+            return 1;
+        }
+        p.raw.reserve(p.raw.size() + t.size());
+        p.raw.insert(p.raw.end(), t.begin(), t.end());
+        return 0;
+    }
+};
+
+/** @brief Specialization of PackSingle for std::variant<T, N> */
+template <typename... T>
+struct PackSingle<std::variant<T...>>
+{
+    static int op(Payload& p, const std::variant<T...>& v)
+    {
+        return std::visit(
+            [&p](const auto& arg) {
+                return PackSingle<std::decay_t<decltype(arg)>>::op(p, arg);
+            },
+            v);
+    }
+};
+
+/** @brief Specialization of PackSingle for Payload */
+template <>
+struct PackSingle<Payload>
+{
+    static int op(Payload& p, const Payload& t)
+    {
+        if (p.bitCount != 0 || t.bitCount != 0)
+        {
+            return 1;
+        }
+        p.raw.reserve(p.raw.size() + t.raw.size());
+        p.raw.insert(p.raw.end(), t.raw.begin(), t.raw.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..c53529b
--- /dev/null
+++ b/include/ipmid/message/types.hpp
@@ -0,0 +1,138 @@
+/**
+ * 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/multiprecision/cpp_int.hpp>
+#include <boost/version.hpp>
+#include <ipmid/utility.hpp>
+
+#include <bitset>
+#include <tuple>
+
+#if BOOST_VERSION < 107900
+using bitcount_t = unsigned;
+#else
+using bitcount_t = std::size_t;
+#endif
+
+// unsigned fixed-bit sizes
+template <bitcount_t 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 <bitcount_t 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 <bitcount_t N>
+struct Size
+{
+    static constexpr bitcount_t value = N;
+};
+
+template <bitcount_t Bits>
+constexpr auto getNrBits(const fixed_int_t<Bits>&) -> Size<Bits>;
+template <bitcount_t Bits>
+constexpr auto getNrBits(const fixed_uint_t<Bits>&) -> Size<Bits>;
+template <bitcount_t Bits>
+constexpr auto getNrBits(const std::bitset<Bits>&) -> Size<Bits>;
+
+template <typename U>
+using underlying_t =
+    typename std::conditional_t<std::is_enum_v<U>, std::underlying_type<U>,
+                                std::enable_if<true, U>>::type;
+
+} // 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;
+
+/**
+ * @brief Converts a number or enum class to another
+ * @tparam R - The output type
+ * @tparam T - The input type
+ * @param t - An enum or integer value to cast
+ * @return The value in R form
+ */
+template <typename R, typename T>
+inline R enum_cast(T t)
+{
+    auto tu = static_cast<details::underlying_t<T>>(t);
+    auto ru = static_cast<details::underlying_t<R>>(tu);
+    return static_cast<R>(ru);
+}
+
+} // namespace types
diff --git a/include/ipmid/message/unpack.hpp b/include/ipmid/message/unpack.hpp
new file mode 100644
index 0000000..2e545b0
--- /dev/null
+++ b/include/ipmid/message/unpack.hpp
@@ -0,0 +1,378 @@
+/**
+ * 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/message.hpp>
+#include <ipmid/message/types.hpp>
+
+#include <array>
+#include <optional>
+#include <span>
+#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 ret;
+        }
+        else
+        {
+            static_assert(
+                utility::dependent_false<T>::value,
+                "Attempt to unpack a type that has no IPMI unpack operation");
+        }
+    }
+};
+
+/** @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 <bitcount_t 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 =
+            ~fixed_uint_t<details::bitStreamSize>(0) >>
+            (details::bitStreamSize - count);
+        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 value;
+        int ret = UnpackSingle<T>::op(p, value);
+        if (ret != 0)
+        {
+            t.reset();
+            p.rawIndex = priorIndex;
+            p.bitStream = priorBits;
+            p.bitCount = priorBitCount;
+            p.unpackError = priorError;
+        }
+        else
+        {
+            t.emplace(std::move(value));
+        }
+        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)
+    {
+        while (p.rawIndex < p.raw.size())
+        {
+            t.emplace_back();
+            if (UnpackSingle<T>::op(p, t.back()))
+            {
+                t.pop_back();
+                break;
+            }
+        }
+        // unpacking a vector is always successful:
+        // either stuff was unpacked successfully (return 0)
+        // or stuff was not unpacked, but should still return
+        // success because an empty vector or a not-fully-unpacked
+        // payload is not a failure.
+        return 0;
+    }
+};
+
+/** @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 SecureBuffer */
+template <>
+struct UnpackSingle<SecureBuffer>
+{
+    static int op(Payload& p, SecureBuffer& 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 std::span<const uint8_t> */
+template <>
+struct UnpackSingle<std::span<const uint8_t>>
+{
+    static int op(Payload& p, std::span<const uint8_t>& t)
+    {
+        // copy out the remainder of the message
+        t = std::span<const uint8_t>(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)
+    {
+        t = p;
+        // mark that this payload is being included in the args
+        p.trailingOk = true;
+        return 0;
+    }
+};
+
+} // namespace details
+
+} // namespace message
+
+} // namespace ipmi
diff --git a/include/ipmid/oemopenbmc.hpp b/include/ipmid/oemopenbmc.hpp
new file mode 100644
index 0000000..b7b3a30
--- /dev/null
+++ b/include/ipmid/oemopenbmc.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <ipmid/api.h>
+
+#include <ipmid/oemrouter.hpp>
+
+namespace oem
+{
+/*
+ * OpenBMC OEM Extension IPMI Command codes.
+ */
+enum Cmd
+{
+    gpioCmd = 1,
+    i2cCmd = 2,
+    flashCmd = 3,
+    fanManualCmd = 4,
+    ethStatsCmd = 48,
+    blobTransferCmd = 128,
+};
+
+} // namespace oem
diff --git a/include/ipmid/oemrouter.hpp b/include/ipmid/oemrouter.hpp
new file mode 100644
index 0000000..4759950
--- /dev/null
+++ b/include/ipmid/oemrouter.hpp
@@ -0,0 +1,84 @@
+#pragma once
+
+#include <ipmid/api.h>
+
+#include <ipmid/iana.hpp>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <vector>
+
+namespace oem
+{
+constexpr std::size_t groupMagicSize = 3;
+
+using Group = std::array<std::uint8_t, groupMagicSize>;
+
+// Handler signature includes ipmi cmd to support wildcard cmd match.
+// Buffers and lengths exclude the OemGroup bytes in the IPMI message.
+// dataLen supplies length of reqBuf upon call, and should be set to the
+// length of replyBuf upon return - conventional in this code base.
+using Handler = std::function<ipmi_ret_t(ipmi_cmd_t,          // cmd byte
+                                         const std::uint8_t*, // reqBuf
+                                         std::uint8_t*,       // replyBuf
+                                         std::size_t*)>;      // dataLen
+
+/// Router Interface class.
+/// @brief Abstract Router Interface
+class Router
+{
+  public:
+    virtual ~Router() {}
+
+    /// Enable message routing to begin.
+    virtual void activate() = 0;
+
+    /// Register a handler for given OEMNumber & cmd.
+    /// Use cmdWildcard to catch any unregistered cmd
+    /// for the given OEMNumber.
+    ///
+    /// @param[in] oen - the OEM Number.
+    /// @param[in] cmd - the Command.
+    /// @param[in] handler - the handler to call given that OEN and
+    ///                      command.
+    virtual void registerHandler(Number oen, ipmi_cmd_t cmd,
+                                 Handler handler) = 0;
+};
+
+/// Expose mutable Router for configuration & activation.
+///
+/// @returns pointer to OEM Router to use.
+Router* mutableRouter();
+
+/// Convert a group to an OEN.
+///
+/// @param[in] oeg - request buffer for IPMI command.
+/// @return the OEN.
+constexpr Number toOemNumber(const std::uint8_t oeg[groupMagicSize])
+{
+    return (oeg[2] << 16) | (oeg[1] << 8) | oeg[0];
+}
+
+/// Given a Group convert to an OEN.
+///
+/// @param[in] oeg - OEM Group reference.
+/// @return the OEN.
+constexpr Number toOemNumber(const Group& oeg)
+{
+    return (oeg[2] << 16) | (oeg[1] << 8) | oeg[0];
+}
+
+/// Given an OEN, conver to the OEM Group.
+///
+/// @param[in] oen - the OEM Number.
+/// @return the OEM Group.
+constexpr Group toOemGroup(Number oen)
+{
+    return Group{static_cast<std::uint8_t>(oen),
+                 static_cast<std::uint8_t>(oen >> 8),
+                 static_cast<std::uint8_t>(oen >> 16)};
+}
+
+} // namespace oem
diff --git a/include/ipmid/sessiondef.hpp b/include/ipmid/sessiondef.hpp
new file mode 100644
index 0000000..0311c91
--- /dev/null
+++ b/include/ipmid/sessiondef.hpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright © 2019 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 <stddef.h>
+#include <stdint.h>
+
+namespace session
+{
+
+static constexpr auto sessionManagerRootPath =
+    "/xyz/openbmc_project/ipmi/session";
+static constexpr auto sessionIntf = "xyz.openbmc_project.Ipmi.SessionInfo";
+static constexpr uint8_t ipmi20VerSession = 0x01;
+static constexpr size_t maxSessionCountPerChannel = 15;
+static constexpr size_t sessionZero = 0;
+static constexpr size_t maxSessionlessCount = 1;
+static constexpr uint8_t invalidSessionID = 0;
+static constexpr uint8_t invalidSessionHandle = 0;
+static constexpr uint8_t defaultSessionHandle = 0xFF;
+static constexpr uint8_t maxNetworkInstanceSupported = 4;
+static constexpr uint8_t ccInvalidSessionId = 0x87;
+static constexpr uint8_t ccInvalidSessionHandle = 0x88;
+static constexpr uint8_t searchCurrentSession = 0;
+static constexpr uint8_t searchSessionByHandle = 0xFE;
+static constexpr uint8_t searchSessionById = 0xFF;
+// MSB BIT 7 BIT 6 assigned for netipmid instance in session handle.
+static constexpr uint8_t multiIntfaceSessionHandleMask = 0x3F;
+
+// MSB BIT 31-BIT30 assigned for netipmid instance in session ID
+static constexpr uint32_t multiIntfaceSessionIDMask = 0x3FFFFFFF;
+
+enum class State : uint8_t
+{
+    inactive,           // Session is not in use
+    setupInProgress,    // Session Setup Sequence is progressing
+    active,             // Session is active
+    tearDownInProgress, // When Closing Session
+};
+
+} // namespace session
diff --git a/include/ipmid/sessionhelper.hpp b/include/ipmid/sessionhelper.hpp
new file mode 100644
index 0000000..5c09bfc
--- /dev/null
+++ b/include/ipmid/sessionhelper.hpp
@@ -0,0 +1,91 @@
+#pragma once
+
+#include <cstdint>
+#include <sstream>
+#include <string>
+
+/**
+ * @brief parse session input payload.
+ *
+ * This function retrives the session id and session handle from the session
+ * object path.
+ * A valid object path will be in the form
+ * "/xyz/openbmc_project/ipmi/session/channel/sessionId_sessionHandle"
+ *
+ * Ex: "/xyz/openbmc_project/ipmi/session/eth0/12a4567d_8a"
+ * SessionId    : 0X12a4567d
+ * SessionHandle: 0X8a
+
+ * @param[in] objectPath - session object path
+ * @param[in] sessionId - retrived session id will be asigned.
+ * @param[in] sessionHandle - retrived session handle will be asigned.
+ *
+ * @return true if session id and session handle are retrived else returns
+ * false.
+ */
+bool parseCloseSessionInputPayload(const std::string& objectPath,
+                                   uint32_t& sessionId, uint8_t& sessionHandle)
+{
+    if (objectPath.empty())
+    {
+        return false;
+    }
+    // getting the position of session id and session handle string from
+    // object path.
+    std::size_t ptrPosition = objectPath.rfind("/");
+    uint16_t tempSessionHandle = 0;
+
+    if (ptrPosition != std::string::npos)
+    {
+        // get the sessionid & session handle string from the session object
+        // path Ex: sessionIdString: "12a4567d_8a"
+        std::string sessionIdString = objectPath.substr(ptrPosition + 1);
+        std::size_t pos = sessionIdString.rfind("_");
+
+        if (pos != std::string::npos)
+        {
+            // extracting the session handle
+            std::string sessionHandleString = sessionIdString.substr(pos + 1);
+            // extracting the session id
+            sessionIdString = sessionIdString.substr(0, pos);
+            // converting session id string  and session handle string to
+            // hexadecimal.
+            std::stringstream handle(sessionHandleString);
+            handle >> std::hex >> tempSessionHandle;
+            sessionHandle = tempSessionHandle & 0xFF;
+            std::stringstream idString(sessionIdString);
+            idString >> std::hex >> sessionId;
+            return true;
+        }
+    }
+    return false;
+}
+
+/**
+ * @brief is session object matched.
+ *
+ * This function checks whether the objectPath contains reqSessionId and
+ * reqSessionHandle, e.g., "/xyz/openbmc_project/ipmi/session/eth0/12a4567d_8a"
+ * matches sessionId 0x12a4567d and sessionHandle 0x8a.
+ *
+ * @param[in] objectPath - session object path
+ * @param[in] reqSessionId - request session id
+ * @param[in] reqSessionHandle - request session handle
+ *
+ * @return true if the object is matched else return false
+ **/
+bool isSessionObjectMatched(const std::string objectPath,
+                            const uint32_t reqSessionId,
+                            const uint8_t reqSessionHandle)
+{
+    uint32_t sessionId = 0;
+    uint8_t sessionHandle = 0;
+
+    if (parseCloseSessionInputPayload(objectPath, sessionId, sessionHandle))
+    {
+        return (reqSessionId == sessionId) ||
+               (reqSessionHandle == sessionHandle);
+    }
+
+    return false;
+}
diff --git a/include/ipmid/types.hpp b/include/ipmid/types.hpp
new file mode 100644
index 0000000..91ff346
--- /dev/null
+++ b/include/ipmid/types.hpp
@@ -0,0 +1,326 @@
+#pragma once
+
+#include <openssl/crypto.h>
+#include <stdint.h>
+
+#include <sdbusplus/server.hpp>
+
+#include <map>
+#include <string>
+#include <variant>
+
+namespace ipmi
+{
+
+using DbusObjectPath = std::string;
+using DbusService = std::string;
+using DbusInterface = std::string;
+using DbusObjectInfo = std::pair<DbusObjectPath, DbusService>;
+using DbusProperty = std::string;
+
+using Association = std::tuple<std::string, std::string, std::string>;
+using BootProgressCode = std::tuple<std::vector<uint8_t>, std::vector<uint8_t>>;
+
+using Value = std::variant<bool, uint8_t, int16_t, uint16_t, int32_t, uint32_t,
+                           int64_t, uint64_t, double, std::string,
+                           std::vector<uint8_t>, std::vector<uint16_t>,
+                           std::vector<uint32_t>, std::vector<std::string>,
+                           std::vector<Association>, BootProgressCode>;
+
+using PropertyMap = std::map<DbusProperty, Value>;
+
+using ObjectTree =
+    std::map<DbusObjectPath, std::map<DbusService, std::vector<DbusInterface>>>;
+
+using InterfaceList = std::vector<std::string>;
+
+using DbusInterfaceMap = std::map<DbusInterface, PropertyMap>;
+
+using ObjectValueTree =
+    std::map<sdbusplus::message::object_path, DbusInterfaceMap>;
+
+namespace sensor
+{
+
+using Offset = uint8_t;
+
+/**
+ * @enum SkipAssertion
+ * Matching value for skipping the update
+ */
+enum class SkipAssertion
+{
+    NONE,     // No skip defined
+    ASSERT,   // Skip on Assert
+    DEASSERT, // Skip on Deassert
+};
+
+struct Values
+{
+    SkipAssertion skip;
+    Value assert;
+    Value deassert;
+};
+
+/**
+ * @enum PreReqValues
+ * Pre-req conditions for a property.
+ */
+struct PreReqValues
+{
+    Value assert;   // Value in case of assert.
+    Value deassert; // Value in case of deassert.
+};
+
+using PreReqOffsetValueMap = std::map<Offset, PreReqValues>;
+
+/**
+ * @struct SetSensorReadingReq
+ *
+ * IPMI Request data for Set Sensor Reading and Event Status Command
+ */
+struct SetSensorReadingReq
+{
+    uint8_t number;
+    uint8_t operation;
+    uint8_t reading;
+    uint8_t assertOffset0_7;
+    uint8_t assertOffset8_14;
+    uint8_t deassertOffset0_7;
+    uint8_t deassertOffset8_14;
+    uint8_t eventData1;
+    uint8_t eventData2;
+    uint8_t eventData3;
+} __attribute__((packed));
+
+/**
+ * @struct GetReadingResponse
+ *
+ * IPMI response data for Get Sensor Reading command.
+ */
+struct GetReadingResponse
+{
+    uint8_t reading;          //!< Sensor reading.
+    uint8_t operation;        //!< Sensor scanning status / reading state.
+    uint8_t assertOffset0_7;  //!< Discrete assertion states(0-7).
+    uint8_t assertOffset8_14; //!< Discrete assertion states(8-14).
+} __attribute__((packed));
+
+constexpr auto inventoryRoot = "/xyz/openbmc_project/inventory";
+
+struct GetSensorResponse
+{
+    uint8_t reading;                     // sensor reading
+    bool readingOrStateUnavailable;      // 1 = reading/state unavailable
+    bool scanningEnabled;                // 0 = sensor scanning disabled
+    bool allEventMessagesEnabled;        // 0 = All Event Messages disabled
+    uint8_t thresholdLevelsStates;       // threshold/discrete sensor states
+    uint8_t discreteReadingSensorStates; // discrete-only, optional states
+};
+
+using OffsetValueMap = std::map<Offset, Values>;
+
+using DbusPropertyValues = std::pair<PreReqOffsetValueMap, OffsetValueMap>;
+
+using DbusPropertyMap = std::map<DbusProperty, DbusPropertyValues>;
+
+using DbusInterfaceMap = std::map<DbusInterface, DbusPropertyMap>;
+
+using InstancePath = std::string;
+using Type = uint8_t;
+using ReadingType = uint8_t;
+using Multiplier = uint16_t;
+using OffsetB = int16_t;
+using Exponent = int8_t;
+using ScaledOffset = double;
+using Scale = int16_t;
+using Unit = std::string;
+using EntityType = uint8_t;
+using EntityInst = uint8_t;
+using SensorName = std::string;
+using SensorUnits1 = uint8_t;
+
+enum class Mutability
+{
+    Read = 1 << 0,
+    Write = 1 << 1,
+};
+
+inline Mutability operator|(Mutability lhs, Mutability rhs)
+{
+    return static_cast<Mutability>(
+        static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs));
+}
+
+inline Mutability operator&(Mutability lhs, Mutability rhs)
+{
+    return static_cast<Mutability>(
+        static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs));
+}
+
+struct Info
+{
+    EntityType entityType;
+    EntityInst instance;
+    Type sensorType;
+    InstancePath sensorPath;
+    DbusInterface sensorInterface;
+    ReadingType sensorReadingType;
+    Multiplier coefficientM;
+    OffsetB coefficientB;
+    Exponent exponentB;
+    ScaledOffset scaledOffset;
+    Exponent exponentR;
+    bool hasScale;
+    Scale scale;
+    SensorUnits1 sensorUnits1;
+    Unit unit;
+    std::function<uint8_t(SetSensorReadingReq&, const Info&)> updateFunc;
+#ifndef FEATURE_SENSORS_CACHE
+    std::function<GetSensorResponse(const Info&)> getFunc;
+#else
+    std::function<std::optional<GetSensorResponse>(uint8_t, const Info&,
+                                                   const ipmi::PropertyMap&)>
+        getFunc;
+#endif
+    Mutability mutability;
+    SensorName sensorName;
+    std::function<SensorName(const Info&)> sensorNameFunc;
+    DbusInterfaceMap propertyInterfaces;
+};
+
+using Id = uint8_t;
+using IdInfoMap = std::map<Id, Info>;
+
+using PropertyMap = ipmi::PropertyMap;
+
+using InterfaceMap = std::map<DbusInterface, PropertyMap>;
+
+using Object = sdbusplus::message::object_path;
+using ObjectMap = std::map<Object, InterfaceMap>;
+
+using IpmiUpdateData = sdbusplus::message_t;
+
+struct SelData
+{
+    Id sensorID;
+    Type sensorType;
+    ReadingType eventReadingType;
+    Offset eventOffset;
+};
+
+using InventoryPath = std::string;
+
+using InvObjectIDMap = std::map<InventoryPath, SelData>;
+
+enum class ThresholdMask
+{
+    NON_CRITICAL_LOW_MASK = 0x01,
+    CRITICAL_LOW_MASK = 0x02,
+    NON_RECOVERABLE_LOW_MASK = 0x4,
+    NON_CRITICAL_HIGH_MASK = 0x08,
+    CRITICAL_HIGH_MASK = 0x10,
+    NON_RECOVERABLE_HIGH_MASK = 0x20,
+};
+
+static constexpr uint8_t maxContainedEntities = 4;
+using ContainedEntitiesArray =
+    std::array<std::pair<uint8_t, uint8_t>, maxContainedEntities>;
+
+struct EntityInfo
+{
+    uint8_t containerEntityId;
+    uint8_t containerEntityInstance;
+    bool isList;
+    bool isLinked;
+    ContainedEntitiesArray containedEntities;
+};
+
+using EntityInfoMap = std::map<Id, EntityInfo>;
+
+#ifdef FEATURE_SENSORS_CACHE
+/**
+ * @struct SensorData
+ *
+ * The data to cache for sensors
+ */
+struct SensorData
+{
+    double value;
+    bool available;
+    bool functional;
+    GetSensorResponse response;
+};
+
+using SensorCacheMap = std::map<uint8_t, std::optional<SensorData>>;
+#endif
+
+} // namespace sensor
+
+namespace network
+{
+constexpr auto MAC_ADDRESS_FORMAT = "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx";
+
+constexpr auto IPV4_ADDRESS_SIZE_BYTE = 4;
+constexpr auto IPV6_ADDRESS_SIZE_BYTE = 16;
+
+constexpr auto DEFAULT_MAC_ADDRESS = "00:00:00:00:00:00";
+constexpr auto DEFAULT_ADDRESS = "0.0.0.0";
+
+} // namespace network
+
+template <typename T>
+class SecureAllocator : public std::allocator<T>
+{
+  public:
+    template <typename U>
+    struct rebind
+    {
+        typedef SecureAllocator<U> other;
+    };
+
+    void deallocate(T* p, size_t n)
+    {
+        OPENSSL_cleanse(p, n);
+        return std::allocator<T>::deallocate(p, n);
+    }
+};
+
+using SecureStringBase =
+    std::basic_string<char, std::char_traits<char>, SecureAllocator<char>>;
+class SecureString : public SecureStringBase
+{
+  public:
+    using SecureStringBase::basic_string;
+    SecureString(const SecureStringBase& other) : SecureStringBase(other) {};
+    SecureString(SecureString&) = default;
+    SecureString(const SecureString&) = default;
+    SecureString(SecureString&&) = default;
+    SecureString& operator=(SecureString&&) = default;
+    SecureString& operator=(const SecureString&) = default;
+
+    ~SecureString()
+    {
+        OPENSSL_cleanse(this->data(), this->size());
+    }
+};
+
+using SecureBufferBase = std::vector<uint8_t, SecureAllocator<uint8_t>>;
+
+class SecureBuffer : public SecureBufferBase
+{
+  public:
+    using SecureBufferBase::vector;
+    SecureBuffer(const SecureBufferBase& other) : SecureBufferBase(other) {};
+    SecureBuffer(SecureBuffer&) = default;
+    SecureBuffer(const SecureBuffer&) = default;
+    SecureBuffer(SecureBuffer&&) = default;
+    SecureBuffer& operator=(SecureBuffer&&) = default;
+    SecureBuffer& operator=(const SecureBuffer&) = default;
+
+    ~SecureBuffer()
+    {
+        OPENSSL_cleanse(this->data(), this->size());
+    }
+};
+} // namespace ipmi
diff --git a/include/ipmid/utility.hpp b/include/ipmid/utility.hpp
new file mode 100644
index 0000000..a3f306f
--- /dev/null
+++ b/include/ipmid/utility.hpp
@@ -0,0 +1,223 @@
+/**
+ * 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
+{
+
+// forward declare Context and Request for NonIpmiArgsCount
+struct Context;
+namespace message
+{
+struct Request;
+}
+
+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,
+                          std::shared_ptr<ipmi::message::Request>>::value ||
+                      std::is_same<FirstArg,
+                                   std::shared_ptr<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
+{};
+
+/** @brief used for static_assert in a constexpr-if else statement
+ */
+template <typename T>
+struct dependent_false : std::false_type
+{};
+
+} // namespace utility
+
+} // namespace ipmi
diff --git a/include/ipmid/utils.hpp b/include/ipmid/utils.hpp
new file mode 100644
index 0000000..4547dc0
--- /dev/null
+++ b/include/ipmid/utils.hpp
@@ -0,0 +1,506 @@
+#pragma once
+
+#include <boost/system/error_code.hpp>
+#include <ipmid/api-types.hpp>
+#include <ipmid/message.hpp>
+#include <ipmid/types.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/server.hpp>
+
+#include <chrono>
+#include <optional>
+
+namespace ipmi
+{
+
+using namespace std::literals::chrono_literals;
+
+constexpr auto MAPPER_BUS_NAME = "xyz.openbmc_project.ObjectMapper";
+constexpr auto MAPPER_OBJ = "/xyz/openbmc_project/object_mapper";
+constexpr auto MAPPER_INTF = "xyz.openbmc_project.ObjectMapper";
+
+constexpr auto ROOT = "/";
+constexpr auto HOST_MATCH = "host0";
+
+constexpr auto PROP_INTF = "org.freedesktop.DBus.Properties";
+constexpr auto DELETE_INTERFACE = "xyz.openbmc_project.Object.Delete";
+
+constexpr auto METHOD_GET = "Get";
+constexpr auto METHOD_GET_ALL = "GetAll";
+constexpr auto METHOD_SET = "Set";
+
+/* Use a value of 5s which aligns with BT/KCS bridged timeouts, rather
+ * than the default 25s D-Bus timeout. */
+constexpr std::chrono::microseconds IPMI_DBUS_TIMEOUT = 5s;
+
+/** @class ServiceCache
+ *  @brief Caches lookups of service names from the object mapper.
+ *  @details Most ipmi commands need to talk to other dbus daemons to perform
+ *           their intended actions on the BMC. This usually means they will
+ *           first look up the service name providing the interface they
+ *           require. This class reduces the number of such calls by caching
+ *           the lookup for a specific service.
+ */
+class ServiceCache
+{
+  public:
+    /** @brief Creates a new service cache for the given interface
+     *         and path.
+     *
+     *  @param[in] intf - The interface used for each lookup
+     *  @param[in] path - The path used for each lookup
+     */
+    ServiceCache(const std::string& intf, const std::string& path);
+    ServiceCache(std::string&& intf, std::string&& path);
+
+    /** @brief Gets the service name from the cache or does in a
+     *         lookup when invalid.
+     *
+     *  @param[in] bus - The bus associated with and used for looking
+     *                   up the service.
+     */
+    const std::string& getService(sdbusplus::bus_t& bus);
+
+    /** @brief Invalidates the current service name */
+    void invalidate();
+
+    /** @brief A wrapper around sdbusplus bus.new_method_call
+     *
+     *  @param[in] bus - The bus used for calling the method
+     *  @param[in] intf - The interface containing the method
+     *  @param[in] method - The method name
+     *  @return The message containing the method call.
+     */
+    sdbusplus::message_t newMethodCall(sdbusplus::bus_t& bus, const char* intf,
+                                       const char* method);
+
+    /** @brief Check to see if the current cache is valid
+     *
+     * @param[in] bus - The bus used for the service lookup
+     * @return True if the cache is valid false otherwise.
+     */
+    bool isValid(sdbusplus::bus_t& bus) const;
+
+  private:
+    /** @brief DBUS interface provided by the service */
+    const std::string intf;
+    /** @brief DBUS path provided by the service */
+    const std::string path;
+    /** @brief The name of the service if valid */
+    std::optional<std::string> cachedService;
+    /** @brief The name of the bus used in the service lookup */
+    std::optional<std::string> cachedBusName;
+};
+
+/**
+ * @brief Get the DBUS Service name for the input dbus path
+ *
+ * @param[in] bus - DBUS Bus Object
+ * @param[in] intf - DBUS Interface
+ * @param[in] path - DBUS Object Path
+ *
+ */
+std::string getService(sdbusplus::bus_t& bus, const std::string& intf,
+                       const std::string& path);
+
+/** @brief Gets the dbus sub tree implementing the given interface.
+ *  @param[in] bus - DBUS Bus Object.
+ *  @param[in] interfaces - Dbus interface.
+ *  @param[in] subtreePath - subtree from where the search should start.
+ *  @param[in] depth - Search depth
+ *  @return map of object path and service info.
+ */
+ObjectTree getSubTree(sdbusplus::bus_t& bus, const InterfaceList& interface,
+                      const std::string& subtreePath = ROOT, int32_t depth = 0);
+
+/** @brief Gets the dbus object info implementing the given interface
+ *         from the given subtree.
+ *  @param[in] bus - DBUS Bus Object.
+ *  @param[in] interface - Dbus interface.
+ *  @param[in] subtreePath - subtree from where the search should start.
+ *  @param[in] match - identifier for object.
+ *  @return On success returns the object having objectpath and servicename.
+ */
+DbusObjectInfo getDbusObject(
+    sdbusplus::bus_t& bus, const std::string& interface,
+    const std::string& subtreePath = ROOT, const std::string& match = {});
+
+/** @brief Gets the value associated with the given object
+ *         and the interface.
+ *  @param[in] bus - DBUS Bus Object.
+ *  @param[in] service - Dbus service name.
+ *  @param[in] objPath - Dbus object path.
+ *  @param[in] interface - Dbus interface.
+ *  @param[in] property - name of the property.
+ *  @return On success returns the value of the property.
+ */
+Value getDbusProperty(sdbusplus::bus_t& bus, const std::string& service,
+                      const std::string& objPath, const std::string& interface,
+                      const std::string& property,
+                      std::chrono::microseconds timeout = IPMI_DBUS_TIMEOUT);
+
+/** @brief Gets all the properties associated with the given object
+ *         and the interface.
+ *  @param[in] bus - DBUS Bus Object.
+ *  @param[in] service - Dbus service name.
+ *  @param[in] objPath - Dbus object path.
+ *  @param[in] interface - Dbus interface.
+ *  @return On success returns the map of name value pair.
+ */
+PropertyMap getAllDbusProperties(
+    sdbusplus::bus_t& bus, const std::string& service,
+    const std::string& objPath, const std::string& interface,
+    std::chrono::microseconds timeout = IPMI_DBUS_TIMEOUT);
+
+/** @brief Gets all managed objects associated with the given object
+ *         path and service.
+ *  @param[in] bus - D-Bus Bus Object.
+ *  @param[in] service - D-Bus service name.
+ *  @param[in] objPath - D-Bus object path.
+ *  @return On success returns the map of name value pair.
+ */
+ObjectValueTree getManagedObjects(sdbusplus::bus_t& bus,
+                                  const std::string& service,
+                                  const std::string& objPath);
+
+/** @brief Sets the property value of the given object.
+ *  @param[in] bus - DBUS Bus Object.
+ *  @param[in] service - Dbus service name.
+ *  @param[in] objPath - Dbus object path.
+ *  @param[in] interface - Dbus interface.
+ *  @param[in] property - name of the property.
+ *  @param[in] value - value which needs to be set.
+ */
+void setDbusProperty(sdbusplus::bus_t& bus, const std::string& service,
+                     const std::string& objPath, const std::string& interface,
+                     const std::string& property, const Value& value,
+                     std::chrono::microseconds timeout = IPMI_DBUS_TIMEOUT);
+
+/** @brief  Gets all the dbus objects from the given service root
+ *          which matches the object identifier.
+ *  @param[in] bus - DBUS Bus Object.
+ *  @param[in] serviceRoot - Service root path.
+ *  @param[in] interface - Dbus interface.
+ *  @param[in] match - Identifier for a path.
+ *  @returns map of object path and service info.
+ */
+ObjectTree getAllDbusObjects(
+    sdbusplus::bus_t& bus, const std::string& serviceRoot,
+    const std::string& interface, const std::string& match = {});
+
+/** @brief Deletes all the dbus objects from the given service root
+           which matches the object identifier.
+ *  @param[in] bus - DBUS Bus Object.
+ *  @param[in] serviceRoot - Service root path.
+ *  @param[in] interface - Dbus interface.
+ *  @param[in] match - Identifier for object.
+ */
+void deleteAllDbusObjects(sdbusplus::bus_t& bus, const std::string& serviceRoot,
+                          const std::string& interface,
+                          const std::string& match = {})
+    __attribute__((deprecated));
+
+/** @brief Gets the ancestor objects of the given object
+           which implements the given interface.
+ *  @param[in] bus - Dbus bus object.
+ *  @param[in] path - Child Dbus object path.
+ *  @param[in] interfaces - Dbus interface list.
+ *  @return map of object path and service info.
+ */
+ObjectTree getAllAncestors(sdbusplus::bus_t& bus, const std::string& path,
+                           InterfaceList&& interfaces)
+    __attribute__((deprecated));
+
+/********* Begin co-routine yielding alternatives ***************/
+
+/** @brief Get the D-Bus Service name for the input D-Bus path
+ *
+ *  @param[in] ctx - ipmi::Context::ptr
+ *  @param[in] intf - D-Bus Interface
+ *  @param[in] path - D-Bus Object Path
+ *  @param[out] service - requested service name
+ *  @return boost error code
+ *
+ */
+boost::system::error_code getService(Context::ptr ctx, const std::string& intf,
+                                     const std::string& path,
+                                     std::string& service);
+
+/** @brief Gets the dbus sub tree implementing the given interface.
+ *  @param[in] ctx - ipmi::Context::ptr
+ *  @param[in] bus - DBUS Bus Object.
+ *  @param[in] interfaces - Dbus interface.
+ *  @param[in] subtreePath - subtree from where the search should start.
+ *  @param[in] depth - Search depth
+ *  @param[out] objectTree - map of object path and service info.
+ *  @return map of object path and service info.
+ */
+boost::system::error_code getSubTree(
+    Context::ptr ctx, const InterfaceList& interface,
+    const std::string& subtreePath, int32_t depth, ObjectTree& objectTree);
+
+/** @brief Gets the D-Bus object info implementing the given interface
+ *         from the given subtree.
+ *  @param[in] ctx - ipmi::Context::ptr
+ *  @param[in] interface - D-Bus interface.
+ *  @param[in][optional] subtreePath - subtree from where the search starts.
+ *  @param[in][optional] match - identifier for object.
+ *  @param[out] D-Bus object with path and service name
+ *  @return - boost error code object
+ */
+boost::system::error_code getDbusObject(
+    Context::ptr ctx, const std::string& interface,
+    const std::string& subtreePath, const std::string& match,
+    DbusObjectInfo& dbusObject);
+
+// default for ROOT for subtreePath and std::string{} for match
+static inline boost::system::error_code getDbusObject(
+    Context::ptr ctx, const std::string& interface, DbusObjectInfo& dbusObject)
+{
+    return getDbusObject(ctx, interface, ROOT, {}, dbusObject);
+}
+
+// default std::string{} for match
+static inline boost::system::error_code getDbusObject(
+    Context::ptr ctx, const std::string& interface,
+    const std::string& subtreePath, DbusObjectInfo& dbusObject)
+{
+    return getDbusObject(ctx, interface, subtreePath, {}, dbusObject);
+}
+
+/** @brief Gets the value associated with the given object
+ *         and the interface.
+ *  @param[in] ctx - ipmi::Context::ptr
+ *  @param[in] service - D-Bus service name.
+ *  @param[in] objPath - D-Bus object path.
+ *  @param[in] interface - D-Bus interface.
+ *  @param[in] property - name of the property.
+ *  @param[out] propertyValue - value of the D-Bus property.
+ *  @return - boost error code object
+ */
+template <typename Type>
+boost::system::error_code getDbusProperty(
+    Context::ptr ctx, const std::string& service, const std::string& objPath,
+    const std::string& interface, const std::string& property,
+    Type& propertyValue)
+{
+    boost::system::error_code ec;
+    auto variant = ctx->bus->yield_method_call<std::variant<Type>>(
+        ctx->yield, ec, service.c_str(), objPath.c_str(), PROP_INTF, METHOD_GET,
+        interface, property);
+    if (!ec)
+    {
+        Type* tmp = std::get_if<Type>(&variant);
+        if (tmp)
+        {
+            propertyValue = *tmp;
+            return ec;
+        }
+        // user requested incorrect type; make an error code for them
+        ec = boost::system::errc::make_error_code(
+            boost::system::errc::invalid_argument);
+    }
+    return ec;
+}
+
+/** @brief Gets all the properties associated with the given object
+ *         and the interface.
+ *  @param[in] ctx - ipmi::Context::ptr
+ *  @param[in] service - D-Bus service name.
+ *  @param[in] objPath - D-Bus object path.
+ *  @param[in] interface - D-Bus interface.
+ *  @param[out] properties - map of name value pair.
+ *  @return - boost error code object
+ */
+boost::system::error_code getAllDbusProperties(
+    Context::ptr ctx, const std::string& service, const std::string& objPath,
+    const std::string& interface, PropertyMap& properties);
+
+/** @brief Sets the property value of the given object.
+ *  @param[in] ctx - ipmi::Context::ptr
+ *  @param[in] service - D-Bus service name.
+ *  @param[in] objPath - D-Bus object path.
+ *  @param[in] interface - D-Bus interface.
+ *  @param[in] property - name of the property.
+ *  @param[in] value - value which needs to be set.
+ *  @return - boost error code object
+ */
+boost::system::error_code setDbusProperty(
+    Context::ptr ctx, const std::string& service, const std::string& objPath,
+    const std::string& interface, const std::string& property,
+    const Value& value);
+
+/** @brief  Gets all the D-Bus objects from the given service root
+ *          which matches the object identifier.
+ *  @param[in] ctx - ipmi::Context::ptr
+ *  @param[in] serviceRoot - Service root path.
+ *  @param[in] interface - D-Bus interface.
+ *  @param[in][optional] match - Identifier for a path.
+ *  @param[out] objectree - map of object path and service info.
+ *  @return - boost error code object
+ */
+boost::system::error_code getAllDbusObjects(
+    Context::ptr ctx, const std::string& serviceRoot,
+    const std::string& interface, const std::string& match,
+    ObjectTree& objectTree);
+
+// default std::string{} for match
+static inline boost::system::error_code getAllDbusObjects(
+    Context::ptr ctx, const std::string& serviceRoot,
+    const std::string& interface, ObjectTree& objectTree)
+{
+    return getAllDbusObjects(ctx, serviceRoot, interface, {}, objectTree);
+}
+
+/** @brief Deletes all the D-Bus objects from the given service root
+           which matches the object identifier.
+ *  @param[in] ctx - ipmi::Context::ptr
+ *  @param[out] ec - boost error code object
+ *  @param[in] serviceRoot - Service root path.
+ *  @param[in] interface - D-Bus interface.
+ *  @param[in] match - Identifier for object.
+ */
+boost::system::error_code deleteAllDbusObjects(
+    Context::ptr ctx, const std::string& serviceRoot,
+    const std::string& interface, const std::string& match = {})
+    __attribute__((deprecated));
+
+/** @brief Gets all managed objects associated with the given object
+ *         path and service.
+ *  @param[in] ctx - ipmi::Context::ptr
+ *  @param[in] service - D-Bus service name.
+ *  @param[in] objPath - D-Bus object path.
+ *  @param[out] objects - map of name value pair.
+ *  @return - boost error code object
+ */
+boost::system::error_code getManagedObjects(
+    Context::ptr ctx, const std::string& service, const std::string& objPath,
+    ObjectValueTree& objects);
+
+/** @brief Gets the ancestor objects of the given object
+           which implements the given interface.
+ *  @param[in] ctx - ipmi::Context::ptr
+ *  @param[in] path - Child D-Bus object path.
+ *  @param[in] interfaces - D-Bus interface list.
+ *  @param[out] ObjectTree - map of object path and service info.
+ *  @return - boost error code object
+ */
+boost::system::error_code getAllAncestors(
+    Context::ptr ctx, const std::string& path, const InterfaceList& interfaces,
+    ObjectTree& objectTree) __attribute__((deprecated));
+
+/** @brief Gets the value associated with the given object
+ *         and the interface.
+ *  @param[in] ctx - ipmi::Context::ptr
+ *  @param[in] service - D-Bus service name.
+ *  @param[in] objPath - D-Bus object path.
+ *  @param[in] interface - D-Bus interface.
+ *  @param[in] method - name of the method.
+ *  @return - boost error code object
+ */
+
+boost::system::error_code callDbusMethod(
+    Context::ptr ctx, const std::string& service, const std::string& objPath,
+    const std::string& interface, const std::string& method);
+
+/********* End co-routine yielding alternatives ***************/
+
+/** @brief Retrieve the value from map of variants,
+ *         returning a default if the key does not exist or the
+ *         type of the value does not match the expected type
+ *
+ *  @tparam T - type of expected value to return
+ *  @param[in] props - D-Bus propery map (Map of variants)
+ *  @param[in] name - key name of property to fetch
+ *  @param[in] defaultValue - default value to return on error
+ *  @return - value from propery map at name, or defaultValue
+ */
+template <typename T>
+T mappedVariant(const ipmi::PropertyMap& props, const std::string& name,
+                const T& defaultValue)
+{
+    auto item = props.find(name);
+    if (item == props.end())
+    {
+        return defaultValue;
+    }
+    const T* prop = std::get_if<T>(&item->second);
+    if (!prop)
+    {
+        return defaultValue;
+    }
+    return *prop;
+}
+
+/** @struct VariantToDoubleVisitor
+ *  @brief Visitor to convert variants to doubles
+ *  @details Performs a static cast on the underlying type
+ */
+struct VariantToDoubleVisitor
+{
+    template <typename T>
+    std::enable_if_t<std::is_arithmetic<T>::value, double> operator()(
+        const T& t) const
+    {
+        return static_cast<double>(t);
+    }
+
+    template <typename T>
+    std::enable_if_t<!std::is_arithmetic<T>::value, double> operator()(
+        const T&) const
+    {
+        throw std::invalid_argument("Cannot translate type to double");
+    }
+};
+
+namespace method_no_args
+{
+
+/** @brief Calls the Dbus method which waits for response.
+ *  @param[in] bus - DBUS Bus Object.
+ *  @param[in] service - Dbus service name.
+ *  @param[in] objPath - Dbus object path.
+ *  @param[in] interface - Dbus interface.
+ *  @param[in] method - Dbus method.
+ */
+void callDbusMethod(sdbusplus::bus_t& bus, const std::string& service,
+                    const std::string& objPath, const std::string& interface,
+                    const std::string& method);
+
+} // namespace method_no_args
+
+template <typename... InputArgs>
+boost::system::error_code callDbusMethod(
+    ipmi::Context::ptr ctx, const std::string& service,
+    const std::string& objPath, const std::string& interface,
+    const std::string& method, const InputArgs&... args)
+{
+    boost::system::error_code ec;
+    ctx->bus->yield_method_call(ctx->yield, ec, service, objPath, interface,
+                                method, args...);
+
+    return ec;
+}
+
+template <typename RetType, typename... InputArgs>
+RetType callDbusMethod(ipmi::Context::ptr ctx, boost::system::error_code& ec,
+                       const std::string& service, const std::string& objPath,
+                       const std::string& interface, const std::string& method,
+                       const InputArgs&... args)
+{
+    auto rc = ctx->bus->yield_method_call<RetType>(
+        ctx->yield, ec, service, objPath, interface, method, args...);
+
+    return rc;
+}
+
+/** @brief Perform the low-level i2c bus write-read.
+ *  @param[in] i2cBus - i2c bus device node name, such as /dev/i2c-2.
+ *  @param[in] targetAddr - i2c device target address.
+ *  @param[in] writeData - The data written to i2c device.
+ *  @param[out] readBuf - Data read from the i2c device.
+ */
+ipmi::Cc i2cWriteRead(std::string i2cBus, const uint8_t targetAddr,
+                      std::vector<uint8_t> writeData,
+                      std::vector<uint8_t>& readBuf);
+} // namespace ipmi
diff --git a/include/meson.build b/include/meson.build
new file mode 100644
index 0000000..65b8385
--- /dev/null
+++ b/include/meson.build
@@ -0,0 +1,24 @@
+install_subdir(
+    'dbus-sdr',
+    install_dir: get_option('includedir'),
+    strip_directory: false,
+    exclude_files: '*.cpp',
+)
+
+install_subdir(
+    'ipmid',
+    install_dir: get_option('includedir'),
+    strip_directory: false,
+    exclude_files: '*.cpp',
+)
+
+install_subdir(
+    'ipmid-host',
+    install_dir: get_option('includedir'),
+    strip_directory: false,
+    exclude_files: '*.cpp',
+)
+
+# install the ipmid-host and ipmid includes
+install_subdir('ipmid-host', install_dir: get_option('includedir'))
+install_subdir('ipmid', install_dir: get_option('includedir'))
diff --git a/ipmi_fru_info_area.cpp b/ipmi_fru_info_area.cpp
new file mode 100644
index 0000000..d259731
--- /dev/null
+++ b/ipmi_fru_info_area.cpp
@@ -0,0 +1,485 @@
+#include "ipmi_fru_info_area.hpp"
+
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <algorithm>
+#include <ctime>
+#include <iomanip>
+#include <map>
+#include <numeric>
+#include <sstream>
+
+namespace ipmi
+{
+namespace fru
+{
+using namespace phosphor::logging;
+
+// Property variables
+static constexpr auto partNumber = "Part Number";
+static constexpr auto serialNumber = "Serial Number";
+static constexpr auto manufacturer = "Manufacturer";
+static constexpr auto buildDate = "Mfg Date";
+static constexpr auto modelNumber = "Model Number";
+static constexpr auto prettyName = "Name";
+static constexpr auto version = "Version";
+static constexpr auto type = "Type";
+
+// Board info areas
+static constexpr auto board = "Board";
+static constexpr auto chassis = "Chassis";
+static constexpr auto product = "Product";
+
+static constexpr auto specVersion = 0x1;
+static constexpr auto recordUnitOfMeasurement = 0x8; // size in bytes
+static constexpr auto checksumSize = 0x1;            // size in bytes
+static constexpr auto recordNotPresent = 0x0;
+static constexpr auto englishLanguageCode = 0x0;
+static constexpr auto typeLengthByteNull = 0x0;
+static constexpr auto endOfCustomFields = 0xC1;
+static constexpr auto commonHeaderFormatSize = 0x8; // size in bytes
+static constexpr auto areaSizeOffset = 0x1;
+static constexpr uint8_t typeASCII = 0xC0;
+static constexpr auto maxRecordAttributeValue = 0x3F;
+
+static constexpr auto secs_from_1970_1996 = 820454400;
+static constexpr auto maxMfgDateValue = 0xFFFFFF; // 3 Byte length
+static constexpr auto secs_per_min = 60;
+static constexpr auto secsToMaxMfgdate =
+    secs_from_1970_1996 + secs_per_min * maxMfgDateValue;
+
+// Minimum size of resulting FRU blob.
+// This is also the theoretical maximum size according to the spec:
+// 8 bytes header + 5 areas at 0xff*8 bytes max each
+// 8 + 5*0xff*8 = 0x27e0
+static constexpr auto fruMinSize = 0x27E0;
+
+// Value to use for padding.
+// Using 0xff to match the default (blank) value in a physical EEPROM.
+static constexpr auto fruPadValue = 0xff;
+
+/**
+ * @brief Format Beginning of Individual IPMI FRU Data Section
+ *
+ * @param[in] langCode Language code
+ * @param[in/out] data FRU area data
+ */
+void preFormatProcessing(bool langCode, FruAreaData& data)
+{
+    // Add id for version of FRU Info Storage Spec used
+    data.emplace_back(specVersion);
+
+    // Add Data Size - 0 as a placeholder, can edit after the data is finalized
+    data.emplace_back(typeLengthByteNull);
+
+    if (langCode)
+    {
+        data.emplace_back(englishLanguageCode);
+    }
+}
+
+/**
+ * @brief Append checksum of the FRU area data
+ *
+ * @param[in/out] data FRU area data
+ */
+void appendDataChecksum(FruAreaData& data)
+{
+    uint8_t checksumVal = std::accumulate(data.begin(), data.end(), 0);
+    // Push the Zero checksum as the last byte of this data
+    // This appears to be a simple summation of all the bytes
+    data.emplace_back(-checksumVal);
+}
+
+/**
+ * @brief Append padding bytes for the FRU area data
+ *
+ * @param[in/out] data FRU area data
+ */
+void padData(FruAreaData& data)
+{
+    uint8_t pad = (data.size() + checksumSize) % recordUnitOfMeasurement;
+    if (pad)
+    {
+        data.resize((data.size() + recordUnitOfMeasurement - pad));
+    }
+}
+
+/**
+ * @brief Format End of Individual IPMI FRU Data Section
+ *
+ * @param[in/out] fruAreaData FRU area info data
+ */
+void postFormatProcessing(FruAreaData& data)
+{
+    // This area needs to be padded to a multiple of 8 bytes (after checksum)
+    padData(data);
+
+    // Set size of data info area
+    data.at(areaSizeOffset) =
+        (data.size() + checksumSize) / (recordUnitOfMeasurement);
+
+    // Finally add area checksum
+    appendDataChecksum(data);
+}
+
+/**
+ * @brief Read chassis type property value from inventory and append to the FRU
+ * area data.
+ *
+ * @param[in] propMap map of property values
+ * @param[in,out] data FRU area data to be appended
+ */
+void appendChassisType(const PropertyMap& propMap, FruAreaData& data)
+{
+    uint8_t chassisType = 0; // Not specified
+    auto iter = propMap.find(type);
+    if (iter != propMap.end())
+    {
+        auto value = iter->second;
+        try
+        {
+            chassisType = std::stoi(value);
+        }
+        catch (const std::exception& e)
+        {
+            lg2::error("Could not parse chassis type, value: {VALUE}, "
+                       "error: {ERROR}",
+                       "VALUE", value, "ERROR", e);
+            chassisType = 0;
+        }
+    }
+    data.emplace_back(chassisType);
+}
+
+/**
+ * @brief Read property value from inventory and append to the FRU area data
+ *
+ * @param[in] key key to search for in the property inventory data
+ * @param[in] propMap map of property values
+ * @param[in,out] data FRU area data to be appended
+ */
+void appendData(const Property& key, const PropertyMap& propMap,
+                FruAreaData& data)
+{
+    auto iter = propMap.find(key);
+    if (iter != propMap.end())
+    {
+        auto value = iter->second;
+        // If starts with 0x or 0X remove them
+        // ex: 0x123a just take 123a
+        if ((value.compare(0, 2, "0x")) == 0 ||
+            (value.compare(0, 2, "0X") == 0))
+        {
+            value.erase(0, 2);
+        }
+
+        // 6 bits for length as per FRU spec v1.0
+        // if length is greater then 63(2^6) bytes then trim the data to 63
+        // bytess.
+        auto valueLength = (value.length() > maxRecordAttributeValue)
+                               ? maxRecordAttributeValue
+                               : value.length();
+        // 2 bits for type
+        // Set the type to ascii
+        uint8_t typeLength = valueLength | ipmi::fru::typeASCII;
+
+        data.emplace_back(typeLength);
+        std::copy(value.begin(), value.begin() + valueLength,
+                  std::back_inserter(data));
+    }
+    else
+    {
+        // set 0 size
+        data.emplace_back(typeLengthByteNull);
+    }
+}
+
+std::time_t timeStringToRaw(const std::string& input)
+{
+    // TODO: For non-US region timestamps, pass in region information for the
+    // FRU to avoid the month/day swap.
+    // 2017-02-24 - 13:59:00, Tue Nov 20 23:08:00 2018
+    static const std::vector<std::string> patterns = {"%Y-%m-%d - %H:%M:%S",
+                                                      "%a %b %d %H:%M:%S %Y"};
+
+    std::tm time = {};
+
+    for (const auto& pattern : patterns)
+    {
+        std::istringstream timeStream(input);
+        timeStream >> std::get_time(&time, pattern.c_str());
+        if (!timeStream.fail())
+        {
+            break;
+        }
+    }
+
+    return timegm(&time);
+}
+
+/**
+ * @brief Appends Build Date
+ *
+ * @param[in] propMap map of property values
+ * @param[in/out] data FRU area to add the manfufacture date
+ */
+void appendMfgDate(const PropertyMap& propMap, FruAreaData& data)
+{
+    // MFG Date/Time
+    auto iter = propMap.find(buildDate);
+    if ((iter != propMap.end()) && (iter->second.size() > 0))
+    {
+        std::time_t raw = timeStringToRaw(iter->second);
+
+        // From FRU Spec:
+        // "Mfg. Date / Time
+        // Number of minutes from 0:00 hrs 1/1/96.
+        // LSbyte first (little endian)
+        // 00_00_00h = unspecified."
+        if ((raw >= secs_from_1970_1996) && (raw <= secsToMaxMfgdate))
+        {
+            raw -= secs_from_1970_1996;
+            raw /= secs_per_min;
+            uint8_t fru_raw[3];
+            fru_raw[0] = raw & 0xFF;
+            fru_raw[1] = (raw >> 8) & 0xFF;
+            fru_raw[2] = (raw >> 16) & 0xFF;
+            std::copy(fru_raw, fru_raw + 3, std::back_inserter(data));
+            return;
+        }
+        std::fprintf(stderr, "MgfDate invalid date: %u secs since UNIX epoch\n",
+                     static_cast<unsigned int>(raw));
+    }
+    // Blank date
+    data.emplace_back(0);
+    data.emplace_back(0);
+    data.emplace_back(0);
+}
+
+/**
+ * @brief Builds a section of the common header
+ *
+ * @param[in] infoAreaSize size of the FRU area to write
+ * @param[in] offset Current offset for data in overall record
+ * @param[in/out] data Common Header section data container
+ */
+void buildCommonHeaderSection(const uint32_t& infoAreaSize, uint16_t& offset,
+                              FruAreaData& data)
+{
+    // Check if data for internal use section populated
+    if (infoAreaSize == 0)
+    {
+        // Indicate record not present
+        data.emplace_back(recordNotPresent);
+    }
+    else
+    {
+        // offset should be multiple of 8.
+        auto remainder = offset % recordUnitOfMeasurement;
+        // add the padding bytes in the offset so that offset
+        // will be multiple of 8 byte.
+        offset += (remainder > 0) ? recordUnitOfMeasurement - remainder : 0;
+        // Place data to define offset to area data section
+        data.emplace_back(offset / recordUnitOfMeasurement);
+
+        offset += infoAreaSize;
+    }
+}
+
+/**
+ * @brief Builds the Chassis info area data section
+ *
+ * @param[in] propMap map of properties for chassis info area
+ * @return FruAreaData container with chassis info area
+ */
+FruAreaData buildChassisInfoArea(const PropertyMap& propMap)
+{
+    FruAreaData fruAreaData;
+    if (!propMap.empty())
+    {
+        // Set formatting data that goes at the beginning of the record
+        preFormatProcessing(false, fruAreaData);
+
+        // chassis type
+        appendChassisType(propMap, fruAreaData);
+
+        // Chasiss part number, in config.yaml it is configured as model
+        appendData(modelNumber, propMap, fruAreaData);
+
+        // Board serial number
+        appendData(serialNumber, propMap, fruAreaData);
+
+        // Indicate End of Custom Fields
+        fruAreaData.emplace_back(endOfCustomFields);
+
+        // Complete record data formatting
+        postFormatProcessing(fruAreaData);
+    }
+    return fruAreaData;
+}
+
+/**
+ * @brief Builds the Board info area data section
+ *
+ * @param[in] propMap map of properties for board info area
+ * @return FruAreaData container with board info area
+ */
+FruAreaData buildBoardInfoArea(const PropertyMap& propMap)
+{
+    FruAreaData fruAreaData;
+    if (!propMap.empty())
+    {
+        preFormatProcessing(true, fruAreaData);
+
+        // Manufacturing date
+        appendMfgDate(propMap, fruAreaData);
+
+        // manufacturer
+        appendData(manufacturer, propMap, fruAreaData);
+
+        // Product name/Pretty name
+        appendData(prettyName, propMap, fruAreaData);
+
+        // Board serial number
+        appendData(serialNumber, propMap, fruAreaData);
+
+        // Board part number
+        appendData(partNumber, propMap, fruAreaData);
+
+        // FRU File ID - Empty
+        fruAreaData.emplace_back(typeLengthByteNull);
+
+        // Empty FRU File ID bytes
+        fruAreaData.emplace_back(recordNotPresent);
+
+        // End of custom fields
+        fruAreaData.emplace_back(endOfCustomFields);
+
+        postFormatProcessing(fruAreaData);
+    }
+    return fruAreaData;
+}
+
+/**
+ * @brief Builds the Product info area data section
+ *
+ * @param[in] propMap map of FRU properties for Board info area
+ * @return FruAreaData container with product info area data
+ */
+FruAreaData buildProductInfoArea(const PropertyMap& propMap)
+{
+    FruAreaData fruAreaData;
+    if (!propMap.empty())
+    {
+        // Set formatting data that goes at the beginning of the record
+        preFormatProcessing(true, fruAreaData);
+
+        // manufacturer
+        appendData(manufacturer, propMap, fruAreaData);
+
+        // Product name/Pretty name
+        appendData(prettyName, propMap, fruAreaData);
+
+        // Product part/model number
+        appendData(modelNumber, propMap, fruAreaData);
+
+        // Product version
+        appendData(version, propMap, fruAreaData);
+
+        // Serial Number
+        appendData(serialNumber, propMap, fruAreaData);
+
+        // Add Asset Tag
+        fruAreaData.emplace_back(recordNotPresent);
+
+        // FRU File ID - Empty
+        fruAreaData.emplace_back(typeLengthByteNull);
+
+        // Empty FRU File ID bytes
+        fruAreaData.emplace_back(recordNotPresent);
+
+        // End of custom fields
+        fruAreaData.emplace_back(endOfCustomFields);
+
+        postFormatProcessing(fruAreaData);
+    }
+    return fruAreaData;
+}
+
+FruAreaData buildFruAreaData(const FruInventoryData& inventory)
+{
+    FruAreaData combFruArea{};
+    // Now build common header with data for this FRU Inv Record
+    // Use this variable to increment size of header as we go along to determine
+    // offset for the subsequent area offsets
+    uint16_t curDataOffset = commonHeaderFormatSize;
+    // First byte is id for version of FRU Info Storage Spec used
+    combFruArea.emplace_back(specVersion);
+
+    // 2nd byte is offset to internal use data
+    combFruArea.emplace_back(recordNotPresent);
+
+    // 3rd byte is offset to chassis data
+    FruAreaData chassisArea;
+    auto chassisIt = inventory.find(chassis);
+    if (chassisIt != inventory.end())
+    {
+        chassisArea = buildChassisInfoArea(chassisIt->second);
+    }
+    // update the offset to chassis data.
+    buildCommonHeaderSection(chassisArea.size(), curDataOffset, combFruArea);
+
+    // 4th byte is offset to board data
+    FruAreaData boardArea;
+    auto boardIt = inventory.find(board);
+    if (boardIt != inventory.end())
+    {
+        boardArea = buildBoardInfoArea(boardIt->second);
+    }
+    // update the offset to the board data.
+    buildCommonHeaderSection(boardArea.size(), curDataOffset, combFruArea);
+
+    // 5th byte is offset to product data
+    FruAreaData prodArea;
+    auto prodIt = inventory.find(product);
+    if (prodIt != inventory.end())
+    {
+        prodArea = buildProductInfoArea(prodIt->second);
+    }
+    // update the offset to the product data.
+    buildCommonHeaderSection(prodArea.size(), curDataOffset, combFruArea);
+
+    // 6th byte is offset to multirecord data
+    combFruArea.emplace_back(recordNotPresent);
+
+    // 7th byte is PAD
+    combFruArea.emplace_back(recordNotPresent);
+
+    // 8th (Final byte of Header Format) is the checksum
+    appendDataChecksum(combFruArea);
+
+    // Combine everything into one full IPMI FRU specification Record
+    // add chassis use area data
+    combFruArea.insert(combFruArea.end(), chassisArea.begin(),
+                       chassisArea.end());
+
+    // add board area data
+    combFruArea.insert(combFruArea.end(), boardArea.begin(), boardArea.end());
+
+    // add product use area data
+    combFruArea.insert(combFruArea.end(), prodArea.begin(), prodArea.end());
+
+    // If area is smaller than the minimum size, pad it. This enables ipmitool
+    // to update the FRU blob with values longer than the original payload.
+    if (combFruArea.size() < fruMinSize)
+    {
+        combFruArea.resize(fruMinSize, fruPadValue);
+    }
+
+    return combFruArea;
+}
+
+} // namespace fru
+} // namespace ipmi
diff --git a/ipmi_fru_info_area.hpp b/ipmi_fru_info_area.hpp
new file mode 100644
index 0000000..ea70401
--- /dev/null
+++ b/ipmi_fru_info_area.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <cstdint>
+#include <map>
+#include <string>
+#include <vector>
+
+namespace ipmi
+{
+namespace fru
+{
+using FruAreaData = std::vector<uint8_t>;
+using Section = std::string;
+using Value = std::string;
+using Property = std::string;
+using PropertyMap = std::map<Property, Value>;
+using FruInventoryData = std::map<Section, PropertyMap>;
+
+/**
+ * @brief Builds Fru area data from inventory data
+ *
+ * @param[in] invData FRU properties values read from inventory
+ *
+ * @return FruAreaData FRU area data as per IPMI specification
+ */
+FruAreaData buildFruAreaData(const FruInventoryData& inventory);
+
+} // namespace fru
+} // namespace ipmi
diff --git a/ipmiallowlist.hpp b/ipmiallowlist.hpp
new file mode 100644
index 0000000..7519de8
--- /dev/null
+++ b/ipmiallowlist.hpp
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <utility>
+#include <vector>
+
+using netfncmd_pair = std::pair<unsigned char, unsigned char>;
+
+extern const std::vector<netfncmd_pair> allowlist;
diff --git a/ipmid-new.cpp b/ipmid-new.cpp
new file mode 100644
index 0000000..7d3dd2d
--- /dev/null
+++ b/ipmid-new.cpp
@@ -0,0 +1,910 @@
+/**
+ * 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.
+ */
+#include "config.h"
+
+#include "settings.hpp"
+
+#include <dlfcn.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/asio/detached.hpp>
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/spawn.hpp>
+#include <host-cmd-manager.hpp>
+#include <ipmid-host/cmd.hpp>
+#include <ipmid/api.hpp>
+#include <ipmid/handler.hpp>
+#include <ipmid/message.hpp>
+#include <ipmid/oemrouter.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/asio/sd_event.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/timer.hpp>
+
+#include <algorithm>
+#include <any>
+#include <exception>
+#include <filesystem>
+#include <forward_list>
+#include <map>
+#include <memory>
+#include <optional>
+#include <tuple>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+namespace fs = std::filesystem;
+
+using namespace phosphor::logging;
+
+// IPMI Spec, shared Reservation ID.
+static unsigned short selReservationID = 0xFFFF;
+static bool selReservationValid = false;
+
+unsigned short reserveSel(void)
+{
+    // IPMI spec, Reservation ID, the value simply increases against each
+    // execution of the Reserve SEL command.
+    if (++selReservationID == 0)
+    {
+        selReservationID = 1;
+    }
+    selReservationValid = true;
+    return selReservationID;
+}
+
+bool checkSELReservation(unsigned short id)
+{
+    return (selReservationValid && selReservationID == id);
+}
+
+void cancelSELReservation(void)
+{
+    selReservationValid = false;
+}
+
+EInterfaceIndex getInterfaceIndex(void)
+{
+    return interfaceKCS;
+}
+
+sd_bus* bus;
+sd_event* events = nullptr;
+sd_event* ipmid_get_sd_event_connection(void)
+{
+    return events;
+}
+sd_bus* ipmid_get_sd_bus_connection(void)
+{
+    return bus;
+}
+
+namespace ipmi
+{
+
+static inline unsigned int makeCmdKey(unsigned int cluster, unsigned int cmd)
+{
+    return (cluster << 8) | cmd;
+}
+
+using HandlerTuple = std::tuple<int,                        /* prio */
+                                Privilege, HandlerBase::ptr /* handler */
+                                >;
+
+/* map to handle standard registered commands */
+static std::unordered_map<unsigned int, /* key is NetFn/Cmd */
+                          HandlerTuple>
+    handlerMap;
+
+/* special map for decoding Group registered commands (NetFn 2Ch) */
+static std::unordered_map<unsigned int, /* key is Group/Cmd (NetFn is 2Ch) */
+                          HandlerTuple>
+    groupHandlerMap;
+
+/* special map for decoding OEM registered commands (NetFn 2Eh) */
+static std::unordered_map<unsigned int, /* key is Iana/Cmd (NetFn is 2Eh) */
+                          HandlerTuple>
+    oemHandlerMap;
+
+using FilterTuple = std::tuple<int,            /* prio */
+                               FilterBase::ptr /* filter */
+                               >;
+
+/* list to hold all registered ipmi command filters */
+static std::forward_list<FilterTuple> filterList;
+
+namespace impl
+{
+/* common function to register all standard IPMI handlers */
+bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv,
+                     HandlerBase::ptr handler)
+{
+    // check for valid NetFn: even; 00-0Ch, 30-3Eh
+    if (netFn & 1 || (netFn > netFnTransport && netFn < netFnGroup) ||
+        netFn > netFnOemEight)
+    {
+        return false;
+    }
+
+    // create key and value for this handler
+    unsigned int netFnCmd = makeCmdKey(netFn, cmd);
+    HandlerTuple item(prio, priv, handler);
+
+    // consult the handler map and look for a match
+    auto& mapCmd = handlerMap[netFnCmd];
+    if (!std::get<HandlerBase::ptr>(mapCmd) || std::get<int>(mapCmd) <= prio)
+    {
+        mapCmd = item;
+        return true;
+    }
+    return false;
+}
+
+/* common function to register all Group IPMI handlers */
+bool registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv,
+                          HandlerBase::ptr handler)
+{
+    // create key and value for this handler
+    unsigned int netFnCmd = makeCmdKey(group, cmd);
+    HandlerTuple item(prio, priv, handler);
+
+    // consult the handler map and look for a match
+    auto& mapCmd = groupHandlerMap[netFnCmd];
+    if (!std::get<HandlerBase::ptr>(mapCmd) || std::get<int>(mapCmd) <= prio)
+    {
+        mapCmd = item;
+        return true;
+    }
+    return false;
+}
+
+/* common function to register all OEM IPMI handlers */
+bool registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv,
+                        HandlerBase::ptr handler)
+{
+    // create key and value for this handler
+    unsigned int netFnCmd = makeCmdKey(iana, cmd);
+    HandlerTuple item(prio, priv, handler);
+
+    // consult the handler map and look for a match
+    auto& mapCmd = oemHandlerMap[netFnCmd];
+    if (!std::get<HandlerBase::ptr>(mapCmd) || std::get<int>(mapCmd) <= prio)
+    {
+        mapCmd = item;
+        lg2::debug("registered OEM Handler: NetFn/Cmd={NETFNCMD}", "IANA",
+                   lg2::hex, iana, "CMD", lg2::hex, cmd, "NETFNCMD", lg2::hex,
+                   netFnCmd);
+        return true;
+    }
+
+    lg2::warning("could not register OEM Handler: NetFn/Cmd={NETFNCMD}", "IANA",
+                 lg2::hex, iana, "CMD", lg2::hex, cmd, "NETFNCMD", lg2::hex,
+                 netFnCmd);
+    return false;
+}
+
+/* common function to register all IPMI filter handlers */
+void registerFilter(int prio, FilterBase::ptr filter)
+{
+    // check for initial placement
+    if (filterList.empty() || std::get<int>(filterList.front()) < prio)
+    {
+        filterList.emplace_front(std::make_tuple(prio, filter));
+        return;
+    }
+    // walk the list and put it in the right place
+    auto j = filterList.begin();
+    for (auto i = j; i != filterList.end() && std::get<int>(*i) > prio; i++)
+    {
+        j = i;
+    }
+    filterList.emplace_after(j, std::make_tuple(prio, filter));
+}
+
+} // namespace impl
+
+message::Response::ptr filterIpmiCommand(message::Request::ptr request)
+{
+    // pass the command through the filter mechanism
+    // This can be the firmware firewall or any OEM mechanism like
+    // whitelist filtering based on operational mode
+    for (auto& item : filterList)
+    {
+        FilterBase::ptr filter = std::get<FilterBase::ptr>(item);
+        ipmi::Cc cc = filter->call(request);
+        if (ipmi::ccSuccess != cc)
+        {
+            return errorResponse(request, cc);
+        }
+    }
+    return message::Response::ptr();
+}
+
+message::Response::ptr executeIpmiCommandCommon(
+    std::unordered_map<unsigned int, HandlerTuple>& handlers,
+    unsigned int keyCommon, message::Request::ptr request)
+{
+    // filter the command first; a non-null message::Response::ptr
+    // means that the message has been rejected for some reason
+    message::Response::ptr filterResponse = filterIpmiCommand(request);
+
+    Cmd cmd = request->ctx->cmd;
+    unsigned int key = makeCmdKey(keyCommon, cmd);
+    auto cmdIter = handlers.find(key);
+    if (cmdIter != handlers.end())
+    {
+        // only return the filter response if the command is found
+        if (filterResponse)
+        {
+            lg2::debug("request for NetFn/Cmd {NETFN}/{CMD} has been filtered",
+                       "NETFN", lg2::hex, keyCommon, "CMD", lg2::hex, cmd);
+            return filterResponse;
+        }
+        HandlerTuple& chosen = cmdIter->second;
+        if (request->ctx->priv < std::get<Privilege>(chosen))
+        {
+            return errorResponse(request, ccInsufficientPrivilege);
+        }
+        return std::get<HandlerBase::ptr>(chosen)->call(request);
+    }
+    else
+    {
+        unsigned int wildcard = makeCmdKey(keyCommon, cmdWildcard);
+        cmdIter = handlers.find(wildcard);
+        if (cmdIter != handlers.end())
+        {
+            // only return the filter response if the command is found
+            if (filterResponse)
+            {
+                lg2::debug(
+                    "request for NetFn/Cmd {NETFN}/{CMD} has been filtered",
+                    "NETFN", lg2::hex, keyCommon, "CMD", lg2::hex, cmd);
+                return filterResponse;
+            }
+            HandlerTuple& chosen = cmdIter->second;
+            if (request->ctx->priv < std::get<Privilege>(chosen))
+            {
+                return errorResponse(request, ccInsufficientPrivilege);
+            }
+            return std::get<HandlerBase::ptr>(chosen)->call(request);
+        }
+    }
+    return errorResponse(request, ccInvalidCommand);
+}
+
+message::Response::ptr executeIpmiGroupCommand(message::Request::ptr request)
+{
+    // look up the group for this request
+    uint8_t bytes;
+    if (0 != request->payload.unpack(bytes))
+    {
+        return errorResponse(request, ccReqDataLenInvalid);
+    }
+    auto group = static_cast<Group>(bytes);
+    // Set defining body code
+    request->ctx->group = group;
+    message::Response::ptr response =
+        executeIpmiCommandCommon(groupHandlerMap, group, request);
+    ipmi::message::Payload prefix;
+    prefix.pack(bytes);
+    response->prepend(prefix);
+    return response;
+}
+
+message::Response::ptr executeIpmiOemCommand(message::Request::ptr request)
+{
+    // look up the iana for this request
+    uint24_t bytes;
+    if (0 != request->payload.unpack(bytes))
+    {
+        return errorResponse(request, ccReqDataLenInvalid);
+    }
+    auto iana = static_cast<Iana>(bytes);
+
+    lg2::debug("unpack IANA {IANA}", "IANA", lg2::hex, iana);
+
+    message::Response::ptr response =
+        executeIpmiCommandCommon(oemHandlerMap, iana, request);
+    ipmi::message::Payload prefix;
+    prefix.pack(bytes);
+    response->prepend(prefix);
+    return response;
+}
+
+message::Response::ptr executeIpmiCommand(message::Request::ptr request)
+{
+    NetFn netFn = request->ctx->netFn;
+    if (netFnGroup == netFn)
+    {
+        return executeIpmiGroupCommand(request);
+    }
+    else if (netFnOem == netFn)
+    {
+        return executeIpmiOemCommand(request);
+    }
+    return executeIpmiCommandCommon(handlerMap, netFn, request);
+}
+
+namespace utils
+{
+template <typename AssocContainer, typename UnaryPredicate>
+void assoc_erase_if(AssocContainer& c, UnaryPredicate p)
+{
+    typename AssocContainer::iterator next = c.begin();
+    typename AssocContainer::iterator last = c.end();
+    while ((next = std::find_if(next, last, p)) != last)
+    {
+        c.erase(next++);
+    }
+}
+} // namespace utils
+
+namespace
+{
+std::unordered_map<std::string, uint8_t> uniqueNameToChannelNumber;
+
+// sdbusplus::bus::match::rules::arg0namespace() wants the prefix
+// to match without any trailing '.'
+constexpr const char ipmiDbusChannelMatch[] =
+    "xyz.openbmc_project.Ipmi.Channel";
+void updateOwners(sdbusplus::asio::connection& conn, const std::string& name)
+{
+    conn.async_method_call(
+        [name](const boost::system::error_code ec,
+               const std::string& nameOwner) {
+            if (ec)
+            {
+                lg2::error("Error getting dbus owner for {INTERFACE}",
+                           "INTERFACE", name);
+                return;
+            }
+            // start after ipmiDbusChannelPrefix (after the '.')
+            std::string chName =
+                name.substr(std::strlen(ipmiDbusChannelMatch) + 1);
+            try
+            {
+                uint8_t channel = getChannelByName(chName);
+                uniqueNameToChannelNumber[nameOwner] = channel;
+                lg2::info(
+                    "New interface mapping: {INTERFACE} -> channel {CHANNEL}",
+                    "INTERFACE", name, "CHANNEL", channel);
+            }
+            catch (const std::exception& e)
+            {
+                lg2::info("Failed interface mapping, no such name: {INTERFACE}",
+                          "INTERFACE", name);
+            }
+        },
+        "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetNameOwner",
+        name);
+}
+
+void doListNames(sdbusplus::asio::connection& conn)
+{
+    conn.async_method_call(
+        [&conn](const boost::system::error_code ec,
+                std::vector<std::string> busNames) {
+            if (ec)
+            {
+                lg2::error("Error getting dbus names: {ERROR}", "ERROR",
+                           ec.message());
+                std::exit(EXIT_FAILURE);
+                return;
+            }
+            // Try to make startup consistent
+            std::sort(busNames.begin(), busNames.end());
+
+            const std::string channelPrefix =
+                std::string(ipmiDbusChannelMatch) + ".";
+            for (const std::string& busName : busNames)
+            {
+                if (busName.find(channelPrefix) == 0)
+                {
+                    updateOwners(conn, busName);
+                }
+            }
+        },
+        "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus",
+        "ListNames");
+}
+
+void nameChangeHandler(sdbusplus::message_t& message)
+{
+    std::string name;
+    std::string oldOwner;
+    std::string newOwner;
+
+    message.read(name, oldOwner, newOwner);
+
+    if (!oldOwner.empty())
+    {
+        if (boost::starts_with(oldOwner, ":"))
+        {
+            // Connection removed
+            auto it = uniqueNameToChannelNumber.find(oldOwner);
+            if (it != uniqueNameToChannelNumber.end())
+            {
+                uniqueNameToChannelNumber.erase(it);
+            }
+        }
+    }
+    if (!newOwner.empty())
+    {
+        // start after ipmiDbusChannelMatch (and after the '.')
+        std::string chName = name.substr(std::strlen(ipmiDbusChannelMatch) + 1);
+        try
+        {
+            uint8_t channel = getChannelByName(chName);
+            uniqueNameToChannelNumber[newOwner] = channel;
+            lg2::info("New interface mapping: {INTERFACE} -> channel {CHANNEL}",
+                      "INTERFACE", name, "CHANNEL", channel);
+        }
+        catch (const std::exception& e)
+        {
+            lg2::info("Failed interface mapping, no such name: {INTERFACE}",
+                      "INTERFACE", name);
+        }
+    }
+};
+
+} // anonymous namespace
+
+static constexpr const char intraBmcName[] = "INTRABMC";
+uint8_t channelFromMessage(sdbusplus::message_t& msg)
+{
+    // channel name for ipmitool to resolve to
+    std::string sender = msg.get_sender();
+    auto chIter = uniqueNameToChannelNumber.find(sender);
+    if (chIter != uniqueNameToChannelNumber.end())
+    {
+        return chIter->second;
+    }
+    // FIXME: currently internal connections are ephemeral and hard to pin down
+    try
+    {
+        return getChannelByName(intraBmcName);
+    }
+    catch (const std::exception& e)
+    {
+        return invalidChannel;
+    }
+} // namespace ipmi
+
+/* called from sdbus async server context */
+auto executionEntry(boost::asio::yield_context yield, sdbusplus::message_t& m,
+                    NetFn netFn, uint8_t lun, Cmd cmd, ipmi::SecureBuffer& data,
+                    std::map<std::string, ipmi::Value>& options)
+{
+    const auto dbusResponse =
+        [netFn, lun, cmd](Cc cc, const ipmi::SecureBuffer& data = {}) {
+            constexpr uint8_t netFnResponse = 0x01;
+            uint8_t retNetFn = netFn | netFnResponse;
+            return std::make_tuple(retNetFn, lun, cmd, cc, data);
+        };
+    std::string sender = m.get_sender();
+    Privilege privilege = Privilege::None;
+    int rqSA = 0;
+    int hostIdx = 0;
+    uint8_t userId = 0; // undefined user
+    uint32_t sessionId = 0;
+
+    // figure out what channel the request came in on
+    uint8_t channel = channelFromMessage(m);
+    if (channel == invalidChannel)
+    {
+        // unknown sender channel; refuse to service the request
+        lg2::error("ERROR determining source IPMI channel from "
+                   "{SENDER} NetFn/Cmd {NETFN}/{CMD}",
+                   "SENDER", sender, "NETFN", lg2::hex, netFn, "CMD", lg2::hex,
+                   cmd);
+        return dbusResponse(ipmi::ccDestinationUnavailable);
+    }
+
+    // session-based channels are required to provide userId, privilege and
+    // sessionId
+    if (getChannelSessionSupport(channel) != EChannelSessSupported::none)
+    {
+        try
+        {
+            Value requestPriv = options.at("privilege");
+            Value requestUserId = options.at("userId");
+            Value requestSessionId = options.at("currentSessionId");
+            privilege = static_cast<Privilege>(std::get<int>(requestPriv));
+            userId = static_cast<uint8_t>(std::get<int>(requestUserId));
+            sessionId =
+                static_cast<uint32_t>(std::get<uint32_t>(requestSessionId));
+        }
+        catch (const std::exception& e)
+        {
+            lg2::error("ERROR determining IPMI session credentials on "
+                       "channel {CHANNEL} for userid {USERID}",
+                       "CHANNEL", channel, "USERID", userId, "NETFN", lg2::hex,
+                       netFn, "CMD", lg2::hex, cmd);
+            return dbusResponse(ipmi::ccUnspecifiedError);
+        }
+    }
+    else
+    {
+        // get max privilege for session-less channels
+        // For now, there is not a way to configure this, default to Admin
+        privilege = Privilege::Admin;
+
+        // ipmb should supply rqSA
+        ChannelInfo chInfo;
+        getChannelInfo(channel, chInfo);
+        if (static_cast<EChannelMediumType>(chInfo.mediumType) ==
+            EChannelMediumType::ipmb)
+        {
+            const auto iter = options.find("rqSA");
+            if (iter != options.end())
+            {
+                if (std::holds_alternative<int>(iter->second))
+                {
+                    rqSA = std::get<int>(iter->second);
+                }
+            }
+            const auto iteration = options.find("hostId");
+            if (iteration != options.end())
+            {
+                if (std::holds_alternative<int>(iteration->second))
+                {
+                    hostIdx = std::get<int>(iteration->second);
+                }
+            }
+        }
+    }
+    // check to see if the requested priv/username is valid
+    lg2::debug("Set up ipmi context: Ch:NetFn/Cmd={CHANNEL}:{NETFN}/{CMD}",
+               "SENDER", sender, "NETFN", lg2::hex, netFn, "LUN", lg2::hex, lun,
+               "CMD", lg2::hex, cmd, "CHANNEL", channel, "USERID", userId,
+               "SESSIONID", lg2::hex, sessionId, "PRIVILEGE",
+               static_cast<uint8_t>(privilege), "RQSA", lg2::hex, rqSA);
+
+    auto ctx = std::make_shared<ipmi::Context>(
+        getSdBus(), netFn, lun, cmd, channel, userId, sessionId, privilege,
+        rqSA, hostIdx, yield);
+    auto request = std::make_shared<ipmi::message::Request>(
+        ctx, std::forward<ipmi::SecureBuffer>(data));
+    message::Response::ptr response = executeIpmiCommand(request);
+
+    return dbusResponse(response->cc, response->payload.raw);
+}
+
+/** @struct IpmiProvider
+ *
+ *  RAII wrapper for dlopen so that dlclose gets called on exit
+ */
+struct IpmiProvider
+{
+  public:
+    /** @brief address of the opened library */
+    void* addr;
+    std::string name;
+
+    IpmiProvider() = delete;
+    IpmiProvider(const IpmiProvider&) = delete;
+    IpmiProvider& operator=(const IpmiProvider&) = delete;
+    IpmiProvider(IpmiProvider&&) = delete;
+    IpmiProvider& operator=(IpmiProvider&&) = delete;
+
+    /** @brief dlopen a shared object file by path
+     *  @param[in]  filename - path of shared object to open
+     */
+    explicit IpmiProvider(const char* fname) : addr(nullptr), name(fname)
+    {
+        lg2::debug("Open IPMI provider library: {PROVIDER}", "PROVIDER", name);
+        try
+        {
+            addr = dlopen(name.c_str(), RTLD_NOW);
+        }
+        catch (const std::exception& e)
+        {
+            lg2::error("ERROR opening IPMI provider {PROVIDER}: {ERROR}",
+                       "PROVIDER", name, "ERROR", e);
+        }
+        catch (...)
+        {
+            const char* what = currentExceptionType();
+            lg2::error("ERROR opening IPMI provider {PROVIDER}: {ERROR}",
+                       "PROVIDER", name, "ERROR", what);
+        }
+        if (!isOpen())
+        {
+            lg2::error("ERROR opening IPMI provider {PROVIDER}: {ERROR}",
+                       "PROVIDER", name, "ERROR", dlerror());
+        }
+    }
+
+    ~IpmiProvider()
+    {
+        if (isOpen())
+        {
+            dlclose(addr);
+        }
+    }
+    bool isOpen() const
+    {
+        return (nullptr != addr);
+    }
+};
+
+// Plugin libraries need to contain .so either at the end or in the middle
+constexpr const char ipmiPluginExtn[] = ".so";
+
+/* return a list of self-closing library handles */
+std::forward_list<IpmiProvider> loadProviders(const fs::path& ipmiLibsPath)
+{
+    std::vector<fs::path> libs;
+    for (const auto& libPath : fs::directory_iterator(ipmiLibsPath))
+    {
+        std::error_code ec;
+        fs::path fname = libPath.path();
+        if (fs::is_symlink(fname, ec) || ec)
+        {
+            // it's a symlink or some other error; skip it
+            continue;
+        }
+        while (fname.has_extension())
+        {
+            fs::path extn = fname.extension();
+            if (extn == ipmiPluginExtn)
+            {
+                libs.push_back(libPath.path());
+                break;
+            }
+            fname.replace_extension();
+        }
+    }
+    std::sort(libs.begin(), libs.end());
+
+    std::forward_list<IpmiProvider> handles;
+    for (auto& lib : libs)
+    {
+#ifdef __IPMI_DEBUG__
+        lg2::debug("Registering handler {HANDLER}", "HANDLER", lib);
+#endif
+        handles.emplace_front(lib.c_str());
+    }
+    return handles;
+}
+
+} // namespace ipmi
+
+#ifdef ALLOW_DEPRECATED_API
+/* legacy registration */
+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)
+{
+    auto h = ipmi::makeLegacyHandler(handler, context);
+    // translate priv from deprecated enum to current
+    ipmi::Privilege realPriv;
+    switch (priv)
+    {
+        case PRIVILEGE_CALLBACK:
+            realPriv = ipmi::Privilege::Callback;
+            break;
+        case PRIVILEGE_USER:
+            realPriv = ipmi::Privilege::User;
+            break;
+        case PRIVILEGE_OPERATOR:
+            realPriv = ipmi::Privilege::Operator;
+            break;
+        case PRIVILEGE_ADMIN:
+            realPriv = ipmi::Privilege::Admin;
+            break;
+        case PRIVILEGE_OEM:
+            realPriv = ipmi::Privilege::Oem;
+            break;
+        case SYSTEM_INTERFACE:
+            realPriv = ipmi::Privilege::Admin;
+            break;
+        default:
+            realPriv = ipmi::Privilege::Admin;
+            break;
+    }
+    // The original ipmi_register_callback allowed for group OEM handlers
+    // to be registered via this same interface. It just so happened that
+    // all the handlers were part of the DCMI group, so default to that.
+    if (netFn == NETFUN_GRPEXT)
+    {
+        ipmi::impl::registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+                                         cmd, realPriv, h);
+    }
+    else
+    {
+        ipmi::impl::registerHandler(ipmi::prioOpenBmcBase, netFn, cmd, realPriv,
+                                    h);
+    }
+}
+
+namespace oem
+{
+
+class LegacyRouter : public oem::Router
+{
+  public:
+    virtual ~LegacyRouter() {}
+
+    /// Enable message routing to begin.
+    void activate() override {}
+
+    void registerHandler(Number oen, ipmi_cmd_t cmd, Handler handler) override
+    {
+        auto h = ipmi::makeLegacyHandler(std::forward<Handler>(handler));
+        ipmi::impl::registerOemHandler(ipmi::prioOpenBmcBase, oen, cmd,
+                                       ipmi::Privilege::Admin, h);
+    }
+};
+static LegacyRouter legacyRouter;
+
+Router* mutableRouter()
+{
+    return &legacyRouter;
+}
+
+} // namespace oem
+
+/* legacy alternative to executionEntry */
+void handleLegacyIpmiCommand(sdbusplus::message_t& m)
+{
+    // make a copy so the next two moves don't wreak havoc on the stack
+    sdbusplus::message_t b{m};
+    boost::asio::spawn(
+        *getIoContext(),
+        [b = std::move(b)](boost::asio::yield_context yield) {
+            sdbusplus::message_t m{std::move(b)};
+            unsigned char seq = 0, netFn = 0, lun = 0, cmd = 0;
+            ipmi::SecureBuffer data;
+
+            m.read(seq, netFn, lun, cmd, data);
+            std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus();
+            auto ctx = std::make_shared<ipmi::Context>(
+                bus, netFn, lun, cmd, 0, 0, 0, ipmi::Privilege::Admin, 0, 0,
+                yield);
+            auto request = std::make_shared<ipmi::message::Request>(
+                ctx, std::forward<ipmi::SecureBuffer>(data));
+            ipmi::message::Response::ptr response =
+                ipmi::executeIpmiCommand(request);
+
+            // Responses in IPMI require a bit set.  So there ya go...
+            netFn |= 0x01;
+
+            constexpr const char* DBUS_INTF = "org.openbmc.HostIpmi";
+
+            std::string dest = m.get_sender();
+            std::string path = m.get_path();
+            boost::system::error_code ec = ipmi::callDbusMethod(
+                ctx, dest, path, DBUS_INTF, "sendMessage", seq, netFn, lun, cmd,
+                response->cc, response->payload.raw);
+
+            if (ec)
+            {
+                lg2::error(
+                    "Failed to send response to requestor ({NETFN}/{CMD}): {ERROR}",
+                    "ERROR", ec.message(), "SENDER", dest, "NETFN", lg2::hex,
+                    netFn, "CMD", lg2::hex, cmd);
+            }
+        },
+        boost::asio::detached);
+}
+
+#endif /* ALLOW_DEPRECATED_API */
+
+// Calls host command manager to do the right thing for the command
+using CommandHandler = phosphor::host::command::CommandHandler;
+std::unique_ptr<phosphor::host::command::Manager> cmdManager;
+void ipmid_send_cmd_to_host(CommandHandler&& cmd)
+{
+    cmdManager->execute(std::forward<CommandHandler>(cmd));
+}
+
+std::unique_ptr<phosphor::host::command::Manager>& ipmid_get_host_cmd_manager()
+{
+    return cmdManager;
+}
+
+// These are symbols that are present in libipmid, but not expected
+// to be used except here (or maybe a unit test), so declare them here
+extern void setIoContext(std::shared_ptr<boost::asio::io_context>& newIo);
+extern void setSdBus(std::shared_ptr<sdbusplus::asio::connection>& newBus);
+
+int main(int argc, char* argv[])
+{
+    // Connect to system bus
+    auto io = std::make_shared<boost::asio::io_context>();
+    setIoContext(io);
+    if (argc > 1 && std::string(argv[1]) == "-session")
+    {
+        sd_bus_default_user(&bus);
+    }
+    else
+    {
+        sd_bus_default_system(&bus);
+    }
+    auto sdbusp = std::make_shared<sdbusplus::asio::connection>(*io, bus);
+    setSdBus(sdbusp);
+
+    // TODO: Hack to keep the sdEvents running.... Not sure why the sd_event
+    //       queue stops running if we don't have a timer that keeps re-arming
+    sdbusplus::Timer t2([]() { ; });
+    t2.start(std::chrono::microseconds(500000), true);
+
+    // TODO: Remove all vestiges of sd_event from phosphor-host-ipmid
+    //       until that is done, add the sd_event wrapper to the io object
+    sdbusplus::asio::sd_event_wrapper sdEvents(*io);
+
+    cmdManager = std::make_unique<phosphor::host::command::Manager>(*sdbusp);
+
+    // Register all command providers and filters
+    std::forward_list<ipmi::IpmiProvider> providers =
+        ipmi::loadProviders(HOST_IPMI_LIB_PATH);
+
+#ifdef ALLOW_DEPRECATED_API
+    // listen on deprecated signal interface for kcs/bt commands
+    constexpr const char* FILTER = "type='signal',interface='org.openbmc."
+                                   "HostIpmi',member='ReceivedMessage'";
+    sdbusplus::bus::match_t oldIpmiInterface(*sdbusp, FILTER,
+                                             handleLegacyIpmiCommand);
+#endif /* ALLOW_DEPRECATED_API */
+
+    // set up bus name watching to match channels with bus names
+    sdbusplus::bus::match_t nameOwnerChanged(
+        *sdbusp,
+        sdbusplus::bus::match::rules::nameOwnerChanged() +
+            sdbusplus::bus::match::rules::arg0namespace(
+                ipmi::ipmiDbusChannelMatch),
+        ipmi::nameChangeHandler);
+    ipmi::doListNames(*sdbusp);
+
+    int exitCode = 0;
+    // set up boost::asio signal handling
+    std::function<SignalResponse(int)> stopAsioRunLoop = [&io, &exitCode](
+                                                             int signalNumber) {
+        lg2::info("Received signal {SIGNAL}; quitting", "SIGNAL", signalNumber);
+        io->stop();
+        exitCode = signalNumber;
+        return SignalResponse::breakExecution;
+    };
+    registerSignalHandler(ipmi::prioOpenBmcBase, SIGINT, stopAsioRunLoop);
+    registerSignalHandler(ipmi::prioOpenBmcBase, SIGTERM, stopAsioRunLoop);
+
+    sdbusp->request_name("xyz.openbmc_project.Ipmi.Host");
+    // Add bindings for inbound IPMI requests
+    auto server = sdbusplus::asio::object_server(sdbusp);
+    auto iface = server.add_interface("/xyz/openbmc_project/Ipmi",
+                                      "xyz.openbmc_project.Ipmi.Server");
+    iface->register_method("execute", ipmi::executionEntry);
+    iface->initialize();
+
+    io->run();
+
+    // destroy all the IPMI handlers so the providers can unload safely
+    ipmi::handlerMap.clear();
+    ipmi::groupHandlerMap.clear();
+    ipmi::oemHandlerMap.clear();
+    ipmi::filterList.clear();
+    // unload the provider libraries
+    providers.clear();
+
+    std::exit(exitCode);
+}
diff --git a/ipmisensor.cpp b/ipmisensor.cpp
new file mode 100644
index 0000000..b66c585
--- /dev/null
+++ b/ipmisensor.cpp
@@ -0,0 +1,347 @@
+#include "sensorhandler.hpp"
+
+#include <malloc.h>
+
+#include <phosphor-logging/lg2.hpp>
+
+extern uint8_t find_type_for_sensor_number(uint8_t);
+
+struct sensorRES_t
+{
+    uint8_t sensor_number;
+    uint8_t operation;
+    uint8_t sensor_reading;
+    uint8_t assert_state7_0;
+    uint8_t assert_state14_8;
+    uint8_t deassert_state7_0;
+    uint8_t deassert_state14_8;
+    uint8_t event_data1;
+    uint8_t event_data2;
+    uint8_t event_data3;
+} __attribute__((packed));
+
+#define ISBITSET(x, y) (((x) >> (y)) & 0x01)
+#define ASSERTINDEX 0
+#define DEASSERTINDEX 1
+
+// Sensor Type,  Offset, function handler, Dbus Method, Assert value, Deassert
+// value
+struct lookup_t
+{
+    uint8_t sensor_type;
+    uint8_t offset;
+    int (*func)(const sensorRES_t*, const lookup_t*, const char*);
+    char member[16];
+    char assertion[64];
+    char deassertion[64];
+};
+
+extern int updateDbusInterface(uint8_t, const char*, const char*);
+
+int set_sensor_dbus_state_simple(const sensorRES_t* pRec,
+                                 const lookup_t* pTable, const char* value)
+{
+    return set_sensor_dbus_state_s(pRec->sensor_number, pTable->member, value);
+}
+
+struct event_data_t
+{
+    uint8_t data;
+    char text[64];
+};
+
+event_data_t g_fwprogress02h[] = {
+    {0x00, "Unspecified"},
+    {0x01, "Memory Init"},
+    {0x02, "HD Init"},
+    {0x03, "Secondary Proc Init"},
+    {0x04, "User Authentication"},
+    {0x05, "User init system setup"},
+    {0x06, "USB configuration"},
+    {0x07, "PCI configuration"},
+    {0x08, "Option ROM Init"},
+    {0x09, "Video Init"},
+    {0x0A, "Cache Init"},
+    {0x0B, "SM Bus init"},
+    {0x0C, "Keyboard Init"},
+    {0x0D, "Embedded ctrl init"},
+    {0x0E, "Docking station attachment"},
+    {0x0F, "Enable docking station"},
+    {0x10, "Docking station ejection"},
+    {0x11, "Disabling docking station"},
+    {0x12, "Calling OS Wakeup"},
+    {0x13, "Starting OS"},
+    {0x14, "Baseboard Init"},
+    {0x15, ""},
+    {0x16, "Floppy Init"},
+    {0x17, "Keyboard Test"},
+    {0x18, "Pointing Device Test"},
+    {0x19, "Primary Proc Init"},
+    {0xFF, "Unknown"}};
+
+event_data_t g_fwprogress00h[] = {
+    {0x00, "Unspecified."},
+    {0x01, "No system memory detected"},
+    {0x02, "No usable system memory"},
+    {0x03, "Unrecoverable hard-disk/ATAPI/IDE"},
+    {0x04, "Unrecoverable system-board"},
+    {0x05, "Unrecoverable diskette"},
+    {0x06, "Unrecoverable hard-disk controller"},
+    {0x07, "Unrecoverable PS/2 or USB keyboard"},
+    {0x08, "Removable boot media not found"},
+    {0x09, "Unrecoverable video controller"},
+    {0x0A, "No video device detected"},
+    {0x0B, "Firmware ROM corruption detected"},
+    {0x0C, "CPU voltage mismatch"},
+    {0x0D, "CPU speed matching"},
+    {0xFF, "unknown"},
+};
+
+char* event_data_lookup(event_data_t* p, uint8_t b)
+{
+    while (p->data != 0xFF)
+    {
+        if (p->data == b)
+        {
+            break;
+        }
+        p++;
+    }
+
+    return p->text;
+}
+
+//  The fw progress sensor contains some additional information that needs to be
+//  processed prior to calling the dbus code.
+int set_sensor_dbus_state_fwprogress(const sensorRES_t* pRec,
+                                     const lookup_t* pTable, const char*)
+{
+    char valuestring[128];
+    char* p = valuestring;
+
+    switch (pTable->offset)
+    {
+        case 0x00:
+            std::snprintf(
+                p, sizeof(valuestring), "POST Error, %s",
+                event_data_lookup(g_fwprogress00h, pRec->event_data2));
+            break;
+        case 0x01: /* Using g_fwprogress02h for 0x01 because that's what the
+                      ipmi spec says to do */
+            std::snprintf(
+                p, sizeof(valuestring), "FW Hang, %s",
+                event_data_lookup(g_fwprogress02h, pRec->event_data2));
+            break;
+        case 0x02:
+            std::snprintf(
+                p, sizeof(valuestring), "FW Progress, %s",
+                event_data_lookup(g_fwprogress02h, pRec->event_data2));
+            break;
+        default:
+            std::snprintf(
+                p, sizeof(valuestring),
+                "Internal warning, fw_progres offset unknown (0x%02x)",
+                pTable->offset);
+            break;
+    }
+
+    return set_sensor_dbus_state_s(pRec->sensor_number, pTable->member, p);
+}
+
+// Handling this special OEM sensor by coping what is in byte 4.  I also think
+// that is odd considering byte 3 is for sensor reading.  This seems like a
+// misuse of the IPMI spec
+int set_sensor_dbus_state_osbootcount(const sensorRES_t* pRec, const lookup_t*,
+                                      const char*)
+{
+    return set_sensor_dbus_state_y(pRec->sensor_number, "setValue",
+                                   pRec->assert_state7_0);
+}
+
+int set_sensor_dbus_state_system_event(const sensorRES_t* pRec,
+                                       const lookup_t* pTable, const char*)
+{
+    char valuestring[128];
+    char* p = valuestring;
+
+    switch (pTable->offset)
+    {
+        case 0x00:
+            std::snprintf(p, sizeof(valuestring), "System Reconfigured");
+            break;
+        case 0x01:
+            std::snprintf(p, sizeof(valuestring), "OEM Boot Event");
+            break;
+        case 0x02:
+            std::snprintf(p, sizeof(valuestring),
+                          "Undetermined System Hardware Failure");
+            break;
+        case 0x03:
+            std::snprintf(
+                p, sizeof(valuestring),
+                "System Failure see error log for more details (0x%02x)",
+                pRec->event_data2);
+            break;
+        case 0x04:
+            std::snprintf(
+                p, sizeof(valuestring),
+                "System Failure see PEF error log for more details (0x%02x)",
+                pRec->event_data2);
+            break;
+        default:
+            std::snprintf(
+                p, sizeof(valuestring),
+                "Internal warning, system_event offset unknown (0x%02x)",
+                pTable->offset);
+            break;
+    }
+
+    return set_sensor_dbus_state_s(pRec->sensor_number, pTable->member, p);
+}
+
+//  This table lists only senors we care about telling dbus about.
+//  Offset definition cab be found in section 42.2 of the IPMI 2.0
+//  spec.  Add more if/when there are more items of interest.
+lookup_t g_ipmidbuslookup[] = {
+
+    {0xe9, 0x00, set_sensor_dbus_state_simple, "setValue", "Disabled",
+     ""}, // OCC Inactive 0
+    {0xe9, 0x01, set_sensor_dbus_state_simple, "setValue", "Enabled",
+     ""}, // OCC Active 1
+    // Turbo Allowed
+    {0xda, 0x00, set_sensor_dbus_state_simple, "setValue", "True", "False"},
+    // Power Supply Derating
+    {0xb4, 0x00, set_sensor_dbus_state_simple, "setValue", "", ""},
+    // Power Cap
+    {0xC2, 0x00, set_sensor_dbus_state_simple, "setValue", "", ""},
+    {0x07, 0x07, set_sensor_dbus_state_simple, "setPresent", "True", "False"},
+    {0x07, 0x08, set_sensor_dbus_state_simple, "setFault", "True", "False"},
+    {0x0C, 0x06, set_sensor_dbus_state_simple, "setPresent", "True", "False"},
+    {0x0C, 0x04, set_sensor_dbus_state_simple, "setFault", "True", "False"},
+    {0x0F, 0x02, set_sensor_dbus_state_fwprogress, "setValue", "True", "False"},
+    {0x0F, 0x01, set_sensor_dbus_state_fwprogress, "setValue", "True", "False"},
+    {0x0F, 0x00, set_sensor_dbus_state_fwprogress, "setValue", "True", "False"},
+    {0xC7, 0x01, set_sensor_dbus_state_simple, "setFault", "True", "False"},
+    {0xc3, 0x00, set_sensor_dbus_state_osbootcount, "setValue", "", ""},
+    {0x1F, 0x00, set_sensor_dbus_state_simple, "setValue",
+     "Boot completed (00)", ""},
+    {0x1F, 0x01, set_sensor_dbus_state_simple, "setValue",
+     "Boot completed (01)", ""},
+    {0x1F, 0x02, set_sensor_dbus_state_simple, "setValue", "PXE boot completed",
+     ""},
+    {0x1F, 0x03, set_sensor_dbus_state_simple, "setValue",
+     "Diagnostic boot completed", ""},
+    {0x1F, 0x04, set_sensor_dbus_state_simple, "setValue",
+     "CD-ROM boot completed", ""},
+    {0x1F, 0x05, set_sensor_dbus_state_simple, "setValue", "ROM boot completed",
+     ""},
+    {0x1F, 0x06, set_sensor_dbus_state_simple, "setValue",
+     "Boot completed (06)", ""},
+    {0x12, 0x00, set_sensor_dbus_state_system_event, "setValue", "", ""},
+    {0x12, 0x01, set_sensor_dbus_state_system_event, "setValue", "", ""},
+    {0x12, 0x02, set_sensor_dbus_state_system_event, "setValue", "", ""},
+    {0x12, 0x03, set_sensor_dbus_state_system_event, "setValue", "", ""},
+    {0x12, 0x04, set_sensor_dbus_state_system_event, "setValue", "", ""},
+    {0xCA, 0x00, set_sensor_dbus_state_simple, "setValue", "Disabled", ""},
+    {0xCA, 0x01, set_sensor_dbus_state_simple, "setValue", "Enabled", ""},
+    {0xFF, 0xFF, nullptr, "", "", ""}};
+
+void reportSensorEventAssert(const sensorRES_t* pRec, int index)
+{
+    lookup_t* pTable = &g_ipmidbuslookup[index];
+    (*pTable->func)(pRec, pTable, pTable->assertion);
+}
+void reportSensorEventDeassert(const sensorRES_t* pRec, int index)
+{
+    lookup_t* pTable = &g_ipmidbuslookup[index];
+    (*pTable->func)(pRec, pTable, pTable->deassertion);
+}
+
+int findindex(const uint8_t sensor_type, int offset, int* index)
+{
+    int i = 0, rc = 0;
+    lookup_t* pTable = g_ipmidbuslookup;
+
+    do
+    {
+        if (((pTable + i)->sensor_type == sensor_type) &&
+            ((pTable + i)->offset == offset))
+        {
+            rc = 1;
+            *index = i;
+            break;
+        }
+        i++;
+    } while ((pTable + i)->sensor_type != 0xFF);
+
+    return rc;
+}
+
+bool shouldReport(uint8_t sensorType, int offset, int* index)
+{
+    bool rc = false;
+
+    if (findindex(sensorType, offset, index))
+    {
+        rc = true;
+    }
+    if (rc == false)
+    {
+#ifdef __IPMI_DEBUG__
+        lg2::debug("LOOKATME: Sensor should not be reported, "
+                   "sensor type: {SENSORTYPE}, offset: {OFFSET}",
+                   SENSORTYPE, lg2::hex, sensorType, "OFFSET", lg2::hex,
+                   offset);
+#endif
+    }
+
+    return rc;
+}
+
+int updateSensorRecordFromSSRAESC(const void* record)
+{
+    auto pRec = static_cast<const sensorRES_t*>(record);
+    uint8_t stype;
+    int index;
+
+    stype = find_type_for_sensor_number(pRec->sensor_number);
+
+    // 0xC3 types use the assertion7_0 for the value to be set
+    // so skip the reseach and call the correct event reporting
+    // function
+    if (stype == 0xC3)
+    {
+        shouldReport(stype, 0x00, &index);
+        reportSensorEventAssert(pRec, index);
+    }
+    else
+    {
+        // Scroll through each bit position .  Determine
+        // if any bit is either asserted or Deasserted.
+        for (int i = 0; i < 8; i++)
+        {
+            if ((ISBITSET(pRec->assert_state7_0, i)) &&
+                (shouldReport(stype, i, &index)))
+            {
+                reportSensorEventAssert(pRec, index);
+            }
+            if ((ISBITSET(pRec->assert_state14_8, i)) &&
+                (shouldReport(stype, i + 8, &index)))
+            {
+                reportSensorEventAssert(pRec, index);
+            }
+            if ((ISBITSET(pRec->deassert_state7_0, i)) &&
+                (shouldReport(stype, i, &index)))
+            {
+                reportSensorEventDeassert(pRec, index);
+            }
+            if ((ISBITSET(pRec->deassert_state14_8, i)) &&
+                (shouldReport(stype, i + 8, &index)))
+            {
+                reportSensorEventDeassert(pRec, index);
+            }
+        }
+    }
+
+    return 0;
+}
diff --git a/libipmid/entity_map_json.cpp b/libipmid/entity_map_json.cpp
new file mode 100644
index 0000000..76fbb25
--- /dev/null
+++ b/libipmid/entity_map_json.cpp
@@ -0,0 +1,119 @@
+#include <ipmid/entity_map_json.hpp>
+#include <ipmid/types.hpp>
+#include <nlohmann/json.hpp>
+
+#include <exception>
+#include <fstream>
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace ipmi
+{
+namespace sensor
+{
+
+EntityInfoMapContainer* EntityInfoMapContainer::getContainer()
+{
+    static std::unique_ptr<EntityInfoMapContainer> instance;
+
+    if (!instance)
+    {
+        /* TODO: With multi-threading this would all need to be locked so
+         * the first thread to hit it would set it up.
+         */
+        EntityInfoMap builtEntityMap = buildEntityMapFromFile();
+        instance = std::unique_ptr<EntityInfoMapContainer>(
+            new EntityInfoMapContainer(builtEntityMap));
+    }
+
+    return instance.get();
+}
+
+const EntityInfoMap& EntityInfoMapContainer::getIpmiEntityRecords()
+{
+    return entityRecords;
+}
+
+EntityInfoMap buildEntityMapFromFile()
+{
+    const char* entityMapJsonFilename =
+        "/usr/share/ipmi-providers/entity-map.json";
+    EntityInfoMap builtMap;
+
+    std::ifstream mapFile(entityMapJsonFilename);
+    if (!mapFile.is_open())
+    {
+        return builtMap;
+    }
+
+    auto data = nlohmann::json::parse(mapFile, nullptr, false);
+    if (data.is_discarded())
+    {
+        return builtMap;
+    }
+
+    return buildJsonEntityMap(data);
+}
+
+EntityInfoMap buildJsonEntityMap(const nlohmann::json& data)
+{
+    EntityInfoMap builtMap;
+
+    if (data.type() != nlohmann::json::value_t::array)
+    {
+        return builtMap;
+    }
+
+    try
+    {
+        for (const auto& entry : data)
+        {
+            /* It's an array entry with the following fields: id,
+             * containerEntityId, containerEntityInstance, isList, isLinked,
+             * entities[4]
+             */
+            EntityInfo obj;
+            Id recordId = entry.at("id").get<Id>();
+            obj.containerEntityId =
+                entry.at("containerEntityId").get<uint8_t>();
+            obj.containerEntityInstance =
+                entry.at("containerEntityInstance").get<uint8_t>();
+            obj.isList = entry.at("isList").get<bool>();
+            obj.isLinked = entry.at("isLinked").get<bool>();
+
+            auto jsonEntities = entry.at("entities");
+
+            if (jsonEntities.type() != nlohmann::json::value_t::array)
+            {
+                throw std::runtime_error(
+                    "Invalid type for entities entry, must be array");
+            }
+            if (jsonEntities.size() != obj.containedEntities.size())
+            {
+                throw std::runtime_error(
+                    "Entities must be in pairs of " +
+                    std::to_string(obj.containedEntities.size()));
+            }
+
+            for (std::size_t i = 0; i < obj.containedEntities.size(); i++)
+            {
+                obj.containedEntities[i] = std::make_pair(
+                    jsonEntities[i].at("id").get<uint8_t>(),
+                    jsonEntities[i].at("instance").get<uint8_t>());
+            }
+
+            builtMap.insert({recordId, obj});
+        }
+    }
+    catch (const std::exception& e)
+    {
+        /* If any entry is invalid, the entire file cannot be trusted. */
+        builtMap.clear();
+    }
+
+    return builtMap;
+}
+
+} // namespace sensor
+} // namespace ipmi
diff --git a/libipmid/meson.build b/libipmid/meson.build
new file mode 100644
index 0000000..f9953b0
--- /dev/null
+++ b/libipmid/meson.build
@@ -0,0 +1,44 @@
+ipmid_pre = [
+    boost,
+    libsystemd_dep,
+    phosphor_dbus_interfaces_dep,
+    phosphor_logging_dep,
+    sdbusplus_dep,
+]
+
+entity_map_json_lib = static_library(
+    'entity_map_json',
+    'entity_map_json.cpp',
+    include_directories: root_inc,
+    dependencies: [nlohmann_json_dep, sdbusplus_dep],
+    implicit_include_directories: false,
+)
+
+entity_map_json_dep = declare_dependency(link_whole: entity_map_json_lib)
+
+libipmid = library(
+    'ipmid',
+    'sdbus-asio.cpp',
+    'signals.cpp',
+    'systemintf-sdbus.cpp',
+    'utils.cpp',
+    dependencies: [ipmid_pre, entity_map_json_dep],
+    version: meson.project_version(),
+    include_directories: root_inc,
+    install: true,
+    install_dir: get_option('libdir'),
+    override_options: ['b_lundef=false'],
+)
+
+ipmid_dep = declare_dependency(
+    dependencies: ipmid_pre,
+    include_directories: root_inc,
+    link_with: libipmid,
+)
+
+import('pkgconfig').generate(
+    libipmid,
+    name: 'libipmid',
+    version: meson.project_version(),
+    description: 'ipmid',
+)
diff --git a/libipmid/sdbus-asio.cpp b/libipmid/sdbus-asio.cpp
new file mode 100644
index 0000000..0f4cdaa
--- /dev/null
+++ b/libipmid/sdbus-asio.cpp
@@ -0,0 +1,32 @@
+#include <boost/asio/io_context.hpp>
+#include <sdbusplus/asio/connection.hpp>
+
+#include <memory>
+
+namespace
+{
+
+std::shared_ptr<boost::asio::io_context> ioCtx;
+std::shared_ptr<sdbusplus::asio::connection> sdbusp;
+
+} // namespace
+
+void setIoContext(std::shared_ptr<boost::asio::io_context>& newIo)
+{
+    ioCtx = newIo;
+}
+
+std::shared_ptr<boost::asio::io_context> getIoContext()
+{
+    return ioCtx;
+}
+
+void setSdBus(std::shared_ptr<sdbusplus::asio::connection>& newBus)
+{
+    sdbusp = newBus;
+}
+
+std::shared_ptr<sdbusplus::asio::connection> getSdBus()
+{
+    return sdbusp;
+}
diff --git a/libipmid/signals.cpp b/libipmid/signals.cpp
new file mode 100644
index 0000000..5eca6ad
--- /dev/null
+++ b/libipmid/signals.cpp
@@ -0,0 +1,106 @@
+#include <boost/asio/signal_set.hpp>
+#include <ipmid/api.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <forward_list>
+#include <memory>
+#include <vector>
+
+namespace
+{
+
+class SignalHandler
+{
+  public:
+    SignalHandler(std::shared_ptr<boost::asio::io_context>& io, int sigNum) :
+        signal(std::make_unique<boost::asio::signal_set>(*io, sigNum))
+    {
+        asyncWait();
+    }
+
+    ~SignalHandler()
+    {
+        // unregister with asio to unmask the signal
+        signal->cancel();
+        signal->clear();
+    }
+
+    void registerHandler(int prio,
+                         const std::function<SignalResponse(int)>& handler)
+    {
+        // check for initial placement
+        if (handlers.empty() || std::get<0>(handlers.front()) < prio)
+        {
+            handlers.emplace_front(std::make_tuple(prio, handler));
+            return;
+        }
+        // walk the list and put it in the right place
+        auto j = handlers.begin();
+        for (auto i = j; i != handlers.end() && std::get<0>(*i) > prio; i++)
+        {
+            j = i;
+        }
+        handlers.emplace_after(j, std::make_tuple(prio, handler));
+    }
+
+    void handleSignal(const boost::system::error_code& ec, int sigNum)
+    {
+        if (ec)
+        {
+            lg2::error("Error in common signal handler, "
+                       "signal: {SIGNAL}, error: {ERROR}",
+                       "SIGNAL", sigNum, "ERROR", ec.message());
+            return;
+        }
+        for (auto h = handlers.begin(); h != handlers.end(); h++)
+        {
+            std::function<SignalResponse(int)>& handler = std::get<1>(*h);
+            if (handler(sigNum) == SignalResponse::breakExecution)
+            {
+                break;
+            }
+        }
+        // start the wait for the next signal
+        asyncWait();
+    }
+
+  protected:
+    void asyncWait()
+    {
+        signal->async_wait([this](const boost::system::error_code& ec,
+                                  int sigNum) { handleSignal(ec, sigNum); });
+    }
+
+    std::forward_list<std::tuple<int, std::function<SignalResponse(int)>>>
+        handlers;
+    std::unique_ptr<boost::asio::signal_set> signal;
+};
+
+// SIGRTMAX is defined as a non-constexpr function call and thus cannot be used
+// as an array size. Get around this by making a vector and resizing it the
+// first time it is needed
+std::vector<std::unique_ptr<SignalHandler>> signals;
+
+} // namespace
+
+void registerSignalHandler(int priority, int signalNumber,
+                           const std::function<SignalResponse(int)>& handler)
+{
+    if (signalNumber >= SIGRTMAX)
+    {
+        return;
+    }
+
+    if (signals.empty())
+    {
+        signals.resize(SIGRTMAX);
+    }
+
+    if (!signals[signalNumber])
+    {
+        std::shared_ptr<boost::asio::io_context> io = getIoContext();
+        signals[signalNumber] =
+            std::make_unique<SignalHandler>(io, signalNumber);
+    }
+    signals[signalNumber]->registerHandler(priority, handler);
+}
diff --git a/libipmid/systemintf-sdbus.cpp b/libipmid/systemintf-sdbus.cpp
new file mode 100644
index 0000000..a9a4e18
--- /dev/null
+++ b/libipmid/systemintf-sdbus.cpp
@@ -0,0 +1,33 @@
+#include <ipmid/api.hpp>
+#include <sdbusplus/asio/connection.hpp>
+
+#include <memory>
+
+namespace
+{
+
+std::unique_ptr<sdbusplus::asio::connection> sdbusp;
+
+} // namespace
+
+/**
+ * @brief ipmid_get_sdbus_plus_handler is used by some ipmi providers
+ *
+ * @return: a reference to a unique pointer of the systemd connection
+ *          managed by the systemintfcmds code
+ */
+std::unique_ptr<sdbusplus::asio::connection>& ipmid_get_sdbus_plus_handler()
+{
+    if (!sdbusp)
+    {
+        // Create a new sdbus connection so it can have a well-known name
+        sd_bus* bus = nullptr;
+        sd_bus_open_system(&bus);
+        if (bus)
+        {
+            sdbusp = std::make_unique<sdbusplus::asio::connection>(
+                *getIoContext(), bus);
+        }
+    }
+    return sdbusp;
+}
diff --git a/libipmid/utils.cpp b/libipmid/utils.cpp
new file mode 100644
index 0000000..f8e8083
--- /dev/null
+++ b/libipmid/utils.cpp
@@ -0,0 +1,609 @@
+#include <arpa/inet.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <linux/i2c-dev.h>
+#include <linux/i2c.h>
+#include <net/if.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <algorithm>
+#include <chrono>
+
+namespace ipmi
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::error::xyz::openbmc_project::common;
+
+namespace network
+{
+
+/** @brief checks if the given ip is Link Local Ip or not.
+ *  @param[in] ipaddress - IPAddress.
+ */
+bool isLinkLocalIP(const std::string& ipaddress);
+
+} // namespace network
+
+// TODO There may be cases where an interface is implemented by multiple
+//  objects,to handle such cases we are interested on that object
+//  which are on interested busname.
+//  Currently mapper doesn't give the readable busname(gives busid) so we can't
+//  use busname to find the object,will do later once the support is there.
+
+DbusObjectInfo getDbusObject(
+    sdbusplus::bus_t& bus, const std::string& interface,
+    const std::string& serviceRoot, const std::string& match)
+{
+    std::vector<DbusInterface> interfaces;
+    interfaces.emplace_back(interface);
+
+    ObjectTree objectTree = getSubTree(bus, interfaces, serviceRoot);
+    if (objectTree.empty())
+    {
+        lg2::error("No Object has implemented the interface: {INTERFACE}",
+                   "INTERFACE", interface);
+        elog<InternalFailure>();
+    }
+
+    DbusObjectInfo objectInfo;
+
+    // if match is empty then return the first object
+    if (match == "")
+    {
+        objectInfo = std::make_pair(
+            objectTree.begin()->first,
+            std::move(objectTree.begin()->second.begin()->first));
+        return objectInfo;
+    }
+
+    // else search the match string in the object path
+    auto found = std::find_if(
+        objectTree.begin(), objectTree.end(), [&match](const auto& object) {
+            return (object.first.find(match) != std::string::npos);
+        });
+
+    if (found == objectTree.end())
+    {
+        lg2::error("Failed to find object which matches: {MATCH}", "MATCH",
+                   match);
+        elog<InternalFailure>();
+        // elog<> throws an exception.
+    }
+
+    return make_pair(found->first, std::move(found->second.begin()->first));
+}
+
+Value getDbusProperty(sdbusplus::bus_t& bus, const std::string& service,
+                      const std::string& objPath, const std::string& interface,
+                      const std::string& property,
+                      std::chrono::microseconds timeout)
+{
+    Value value;
+
+    auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
+                                      PROP_INTF, METHOD_GET);
+
+    method.append(interface, property);
+
+    auto reply = bus.call(method, timeout.count());
+    reply.read(value);
+
+    return value;
+}
+
+PropertyMap getAllDbusProperties(
+    sdbusplus::bus_t& bus, const std::string& service,
+    const std::string& objPath, const std::string& interface,
+    std::chrono::microseconds timeout)
+{
+    PropertyMap properties;
+
+    auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
+                                      PROP_INTF, METHOD_GET_ALL);
+
+    method.append(interface);
+
+    auto reply = bus.call(method, timeout.count());
+    reply.read(properties);
+
+    return properties;
+}
+
+ObjectValueTree getManagedObjects(sdbusplus::bus_t& bus,
+                                  const std::string& service,
+                                  const std::string& objPath)
+{
+    ipmi::ObjectValueTree interfaces;
+
+    auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
+                                      "org.freedesktop.DBus.ObjectManager",
+                                      "GetManagedObjects");
+    auto reply = bus.call(method);
+    reply.read(interfaces);
+
+    return interfaces;
+}
+
+void setDbusProperty(sdbusplus::bus_t& bus, const std::string& service,
+                     const std::string& objPath, const std::string& interface,
+                     const std::string& property, const Value& value,
+                     std::chrono::microseconds timeout)
+{
+    auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
+                                      PROP_INTF, METHOD_SET);
+
+    method.append(interface, property, value);
+
+    if (!bus.call(method, timeout.count()))
+    {
+        lg2::error("Failed to set {PROPERTY}, path: {PATH}, "
+                   "interface: {INTERFACE}",
+                   "PROPERTY", property, "PATH", objPath, "INTERFACE",
+                   interface);
+        elog<InternalFailure>();
+    }
+}
+
+ServiceCache::ServiceCache(const std::string& intf, const std::string& path) :
+    intf(intf), path(path), cachedService(std::nullopt),
+    cachedBusName(std::nullopt)
+{}
+
+ServiceCache::ServiceCache(std::string&& intf, std::string&& path) :
+    intf(std::move(intf)), path(std::move(path)), cachedService(std::nullopt),
+    cachedBusName(std::nullopt)
+{}
+
+const std::string& ServiceCache::getService(sdbusplus::bus_t& bus)
+{
+    if (!isValid(bus))
+    {
+        cachedBusName = bus.get_unique_name();
+        cachedService = ::ipmi::getService(bus, intf, path);
+    }
+    return cachedService.value();
+}
+
+void ServiceCache::invalidate()
+{
+    cachedBusName = std::nullopt;
+    cachedService = std::nullopt;
+}
+
+sdbusplus::message_t ServiceCache::newMethodCall(
+    sdbusplus::bus_t& bus, const char* intf, const char* method)
+{
+    return bus.new_method_call(getService(bus).c_str(), path.c_str(), intf,
+                               method);
+}
+
+bool ServiceCache::isValid(sdbusplus::bus_t& bus) const
+{
+    return cachedService && cachedBusName == bus.get_unique_name();
+}
+
+std::string getService(sdbusplus::bus_t& bus, const std::string& intf,
+                       const std::string& path)
+{
+    auto mapperCall =
+        bus.new_method_call("xyz.openbmc_project.ObjectMapper",
+                            "/xyz/openbmc_project/object_mapper",
+                            "xyz.openbmc_project.ObjectMapper", "GetObject");
+
+    mapperCall.append(path);
+    mapperCall.append(std::vector<std::string>({intf}));
+
+    auto mapperResponseMsg = bus.call(mapperCall);
+
+    std::map<std::string, std::vector<std::string>> mapperResponse;
+    mapperResponseMsg.read(mapperResponse);
+
+    if (mapperResponse.begin() == mapperResponse.end())
+    {
+        throw std::runtime_error("ERROR in reading the mapper response");
+    }
+
+    return mapperResponse.begin()->first;
+}
+
+ObjectTree getSubTree(sdbusplus::bus_t& bus, const InterfaceList& interfaces,
+                      const std::string& subtreePath, int32_t depth)
+{
+    auto mapperCall = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ,
+                                          MAPPER_INTF, "GetSubTree");
+
+    mapperCall.append(subtreePath, depth, interfaces);
+
+    auto mapperReply = bus.call(mapperCall);
+    ObjectTree objectTree;
+    mapperReply.read(objectTree);
+
+    return objectTree;
+}
+
+ipmi::ObjectTree getAllDbusObjects(
+    sdbusplus::bus_t& bus, const std::string& serviceRoot,
+    const std::string& interface, const std::string& match)
+{
+    std::vector<std::string> interfaces;
+    interfaces.emplace_back(interface);
+
+    ObjectTree objectTree = getSubTree(bus, interfaces, serviceRoot);
+    for (auto it = objectTree.begin(); it != objectTree.end();)
+    {
+        if (it->first.find(match) == std::string::npos)
+        {
+            it = objectTree.erase(it);
+        }
+        else
+        {
+            ++it;
+        }
+    }
+
+    return objectTree;
+}
+
+void deleteAllDbusObjects(sdbusplus::bus_t& bus, const std::string& serviceRoot,
+                          const std::string& interface,
+                          const std::string& match)
+{
+    try
+    {
+        auto objectTree = getAllDbusObjects(bus, serviceRoot, interface, match);
+
+        for (auto& object : objectTree)
+        {
+            method_no_args::callDbusMethod(bus, object.second.begin()->first,
+                                           object.first, DELETE_INTERFACE,
+                                           "Delete");
+        }
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::info("sdbusplus exception - Unable to delete the objects, "
+                  "service: {SERVICE}, interface: {INTERFACE}, error: {ERROR}",
+                  "SERVICE", serviceRoot, "INTERFACE", interface, "ERROR", e);
+    }
+}
+
+static inline std::string convertToString(const InterfaceList& interfaces)
+{
+    std::string intfStr;
+    for (const auto& intf : interfaces)
+    {
+        intfStr += "," + intf;
+    }
+    return intfStr;
+}
+
+ObjectTree getAllAncestors(sdbusplus::bus_t& bus, const std::string& path,
+                           InterfaceList&& interfaces)
+{
+    auto mapperCall = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ,
+                                          MAPPER_INTF, "GetAncestors");
+    mapperCall.append(path, interfaces);
+
+    auto mapperReply = bus.call(mapperCall);
+    ObjectTree objectTree;
+    mapperReply.read(objectTree);
+
+    if (objectTree.empty())
+    {
+        lg2::error("No Object has implemented the interface: {INTERFACE}, "
+                   "path: {PATH}",
+                   "INTERFACE", convertToString(interfaces), "PATH", path);
+        elog<InternalFailure>();
+    }
+
+    return objectTree;
+}
+
+namespace method_no_args
+{
+
+void callDbusMethod(sdbusplus::bus_t& bus, const std::string& service,
+                    const std::string& objPath, const std::string& interface,
+                    const std::string& method)
+
+{
+    auto busMethod = bus.new_method_call(service.c_str(), objPath.c_str(),
+                                         interface.c_str(), method.c_str());
+    auto reply = bus.call(busMethod);
+}
+
+} // namespace method_no_args
+
+/********* Begin co-routine yielding alternatives ***************/
+
+boost::system::error_code getService(Context::ptr ctx, const std::string& intf,
+                                     const std::string& path,
+                                     std::string& service)
+{
+    boost::system::error_code ec;
+    std::map<std::string, std::vector<std::string>> mapperResponse =
+        ctx->bus->yield_method_call<decltype(mapperResponse)>(
+            ctx->yield, ec, "xyz.openbmc_project.ObjectMapper",
+            "/xyz/openbmc_project/object_mapper",
+            "xyz.openbmc_project.ObjectMapper", "GetObject", path,
+            std::vector<std::string>({intf}));
+
+    if (!ec)
+    {
+        service = std::move(mapperResponse.begin()->first);
+    }
+    return ec;
+}
+
+boost::system::error_code getSubTree(
+    Context::ptr ctx, const InterfaceList& interfaces,
+    const std::string& subtreePath, int32_t depth, ObjectTree& objectTree)
+{
+    boost::system::error_code ec;
+    objectTree = ctx->bus->yield_method_call<ObjectTree>(
+        ctx->yield, ec, MAPPER_BUS_NAME, MAPPER_OBJ, MAPPER_INTF, "GetSubTree",
+        subtreePath, depth, interfaces);
+
+    return ec;
+}
+
+boost::system::error_code getDbusObject(
+    Context::ptr ctx, const std::string& interface,
+    const std::string& subtreePath, const std::string& match,
+    DbusObjectInfo& dbusObject)
+{
+    std::vector<DbusInterface> interfaces;
+    interfaces.emplace_back(interface);
+
+    auto depth = 0;
+    ObjectTree objectTree;
+    boost::system::error_code ec =
+        getSubTree(ctx, interfaces, subtreePath, depth, objectTree);
+
+    if (ec)
+    {
+        return ec;
+    }
+
+    if (objectTree.empty())
+    {
+        lg2::error("No Object has implemented the interface: {INTERFACE}, "
+                   "NetFn: {NETFN}, Cmd: {CMD}",
+                   "INTERFACE", interface, "NETFN", lg2::hex, ctx->netFn, "CMD",
+                   lg2::hex, ctx->cmd);
+        return boost::system::errc::make_error_code(
+            boost::system::errc::no_such_process);
+    }
+
+    // if match is empty then return the first object
+    if (match == "")
+    {
+        dbusObject = std::make_pair(
+            std::move(objectTree.begin()->first),
+            std::move(objectTree.begin()->second.begin()->first));
+        return ec;
+    }
+
+    // else search the match string in the object path
+    auto found = std::find_if(
+        objectTree.begin(), objectTree.end(), [&match](const auto& object) {
+            return (object.first.find(match) != std::string::npos);
+        });
+
+    if (found == objectTree.end())
+    {
+        lg2::error("Failed to find object which matches: {MATCH}, "
+                   "NetFn: {NETFN}, Cmd: {CMD}",
+                   "MATCH", match, "NETFN", lg2::hex, ctx->netFn, "CMD",
+                   lg2::hex, ctx->cmd);
+        // set ec
+        return boost::system::errc::make_error_code(
+            boost::system::errc::no_such_file_or_directory);
+    }
+
+    dbusObject = std::make_pair(std::move(found->first),
+                                std::move(found->second.begin()->first));
+    return ec;
+}
+
+boost::system::error_code getAllDbusProperties(
+    Context::ptr ctx, const std::string& service, const std::string& objPath,
+    const std::string& interface, PropertyMap& properties)
+{
+    boost::system::error_code ec;
+    properties = ctx->bus->yield_method_call<PropertyMap>(
+        ctx->yield, ec, service.c_str(), objPath.c_str(), PROP_INTF,
+        METHOD_GET_ALL, interface);
+    return ec;
+}
+
+boost::system::error_code setDbusProperty(
+    Context::ptr ctx, const std::string& service, const std::string& objPath,
+    const std::string& interface, const std::string& property,
+    const Value& value)
+{
+    boost::system::error_code ec;
+    ctx->bus->yield_method_call(ctx->yield, ec, service.c_str(),
+                                objPath.c_str(), PROP_INTF, METHOD_SET,
+                                interface, property, value);
+    return ec;
+}
+
+boost::system::error_code getAllDbusObjects(
+    Context::ptr ctx, const std::string& serviceRoot,
+    const std::string& interface, const std::string& match,
+    ObjectTree& objectTree)
+{
+    std::vector<std::string> interfaces;
+    interfaces.emplace_back(interface);
+
+    auto depth = 0;
+    boost::system::error_code ec =
+        getSubTree(ctx, interfaces, serviceRoot, depth, objectTree);
+    if (ec)
+    {
+        return ec;
+    }
+
+    for (auto it = objectTree.begin(); it != objectTree.end();)
+    {
+        if (it->first.find(match) == std::string::npos)
+        {
+            it = objectTree.erase(it);
+        }
+        else
+        {
+            ++it;
+        }
+    }
+
+    return ec;
+}
+
+boost::system::error_code deleteAllDbusObjects(
+    Context::ptr ctx, const std::string& serviceRoot,
+    const std::string& interface, const std::string& match)
+{
+    ObjectTree objectTree;
+    boost::system::error_code ec =
+        getAllDbusObjects(ctx, serviceRoot, interface, match, objectTree);
+    if (ec)
+    {
+        return ec;
+    }
+
+    for (auto& object : objectTree)
+    {
+        ctx->bus->yield_method_call(ctx->yield, ec,
+                                    object.second.begin()->first, object.first,
+                                    DELETE_INTERFACE, "Delete");
+        if (ec)
+        {
+            lg2::error("Failed to delete all objects, service: {SERVICE}, "
+                       "interface: {INTERFACE}, NetFn: {NETFN}, "
+                       "Cmd: {CMD}, Error: {ERROR}",
+                       "SERVICE", serviceRoot, "INTERFACE", interface, "NETFN",
+                       lg2::hex, ctx->netFn, "CMD", lg2::hex, ctx->cmd, "ERROR",
+                       ec.message());
+            break;
+        }
+    }
+    return ec;
+}
+
+boost::system::error_code getManagedObjects(
+    Context::ptr ctx, const std::string& service, const std::string& objPath,
+    ObjectValueTree& objects)
+{
+    boost::system::error_code ec;
+    objects = ctx->bus->yield_method_call<ipmi::ObjectValueTree>(
+        ctx->yield, ec, service.c_str(), objPath.c_str(),
+        "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
+    return ec;
+}
+
+boost::system::error_code getAllAncestors(
+    Context::ptr ctx, const std::string& path, const InterfaceList& interfaces,
+    ObjectTree& objectTree)
+{
+    std::string interfaceList = convertToString(interfaces);
+
+    boost::system::error_code ec;
+    objectTree = ctx->bus->yield_method_call<ObjectTree>(
+        ctx->yield, ec, MAPPER_BUS_NAME, MAPPER_OBJ, MAPPER_INTF,
+        "GetAncestors", path, interfaceList);
+
+    if (ec)
+    {
+        return ec;
+    }
+
+    if (objectTree.empty())
+    {
+        lg2::error("No Object has implemented the interface: {INTERFACE}, "
+                   "path: {PATH}",
+                   "INTERFACE", interfaceList, "PATH", path);
+        elog<InternalFailure>();
+    }
+
+    return ec;
+}
+
+boost::system::error_code callDbusMethod(
+    Context::ptr ctx, const std::string& service, const std::string& objPath,
+    const std::string& interface, const std::string& method)
+{
+    boost::system::error_code ec;
+    ctx->bus->yield_method_call(ctx->yield, ec, service, objPath, interface,
+                                method);
+    return ec;
+}
+
+/********* End co-routine yielding alternatives ***************/
+
+ipmi::Cc i2cWriteRead(std::string i2cBus, const uint8_t targetAddr,
+                      std::vector<uint8_t> writeData,
+                      std::vector<uint8_t>& readBuf)
+{
+    // Open the i2c device, for low-level combined data write/read
+    int i2cDev = ::open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
+    if (i2cDev < 0)
+    {
+        lg2::error("Failed to open i2c bus: {BUS}", "BUS", i2cBus);
+        return ipmi::ccInvalidFieldRequest;
+    }
+
+    const size_t writeCount = writeData.size();
+    const size_t readCount = readBuf.size();
+    int msgCount = 0;
+    i2c_msg i2cmsg[2] = {};
+    if (writeCount)
+    {
+        // Data will be writtern to the target address
+        i2cmsg[msgCount].addr = targetAddr;
+        i2cmsg[msgCount].flags = 0x00;
+        i2cmsg[msgCount].len = writeCount;
+        i2cmsg[msgCount].buf = writeData.data();
+        msgCount++;
+    }
+    if (readCount)
+    {
+        // Data will be read into the buffer from the target address
+        i2cmsg[msgCount].addr = targetAddr;
+        i2cmsg[msgCount].flags = I2C_M_RD;
+        i2cmsg[msgCount].len = readCount;
+        i2cmsg[msgCount].buf = readBuf.data();
+        msgCount++;
+    }
+
+    i2c_rdwr_ioctl_data msgReadWrite = {};
+    msgReadWrite.msgs = i2cmsg;
+    msgReadWrite.nmsgs = msgCount;
+
+    // Perform the combined write/read
+    int ret = ::ioctl(i2cDev, I2C_RDWR, &msgReadWrite);
+    ::close(i2cDev);
+
+    if (ret < 0)
+    {
+        lg2::error("I2C WR Failed! {RET}", "RET", ret);
+        return ipmi::ccUnspecifiedError;
+    }
+    if (readCount)
+    {
+        readBuf.resize(msgReadWrite.msgs[msgCount - 1].len);
+    }
+
+    return ipmi::ccSuccess;
+}
+
+} // namespace ipmi
diff --git a/meson.build b/meson.build
index 0e449d4..a492697 100644
--- a/meson.build
+++ b/meson.build
@@ -1,40 +1,78 @@
 project(
-    'phosphor-net-ipmid',
+    'phosphor-host-ipmid',
     'cpp',
-    version: '1.0.0',
+    version: '0.1',
     meson_version: '>=1.1.1',
     default_options: [
-        'warning_level=3',
         'werror=true',
+        'warning_level=3',
         'cpp_std=c++23',
-        'buildtype=debugoptimized',
         'b_lto=true',
     ],
 )
 
+# Setting up config data
 conf_data = configuration_data()
-conf_data.set('RMCP_PING', get_option('rmcp_ping').allowed())
-conf_data.set('PAM_AUTHENTICATE', get_option('pam_authenticate').allowed())
 
-configure_file(output: 'config.h', configuration: conf_data)
+# The name of the callout's forward association
+conf_data.set_quoted('CALLOUT_FWD_ASSOCIATION', 'callout')
+conf_data.set_quoted('BOARD_SENSOR', get_option('board-sensor'))
+conf_data.set_quoted('SYSTEM_SENSOR', get_option('system-sensor'))
+conf_data.set(
+    'IPMI_SMS_ATN_ACK_TIMEOUT_SECS',
+    get_option('ipmi-sms-atn-ack-timeout-secs'),
+)
 
-sdbusplus_dep = dependency('sdbusplus')
-phosphor_dbus_interfaces_dep = dependency('phosphor-dbus-interfaces')
-phosphor_logging_dep = dependency('phosphor-logging')
-libsystemd_dep = dependency('libsystemd')
-libcrypto_dep = dependency('libcrypto')
-ipmid_dep = dependency('libipmid')
-userlayer_dep = dependency('libuserlayer')
-channellayer_dep = dependency('libchannellayer')
+# Soft Power off related.
+if get_option('softoff').allowed()
+    conf_data.set_quoted('SOFTOFF_BUSNAME', get_option('softoff-busname'))
+    conf_data.set_quoted('SOFTOFF_OBJPATH', get_option('softoff-objpath'))
+    conf_data.set(
+        'IPMI_HOST_SHUTDOWN_COMPLETE_TIMEOUT_SECS',
+        get_option('ipmi-host-shutdown-complete-timeout-secs'),
+    )
+    conf_data.set_quoted(
+        'HOST_INBAND_REQUEST_DIR',
+        get_option('host-inband-request-dir'),
+    )
+    conf_data.set_quoted(
+        'HOST_INBAND_REQUEST_FILE',
+        get_option('host-inband-request-file'),
+    )
+endif
+
+conf_data.set_quoted('CONTROL_HOST_BUSNAME', get_option('control-host-busname'))
+conf_data.set_quoted('CONTROL_HOST_OBJ_MGR', get_option('control-host-obj-mgr'))
+conf_data.set_quoted('HOST_NAME', get_option('host-name'))
+conf_data.set_quoted('POWER_READING_SENSOR', get_option('power-reading-sensor'))
+conf_data.set_quoted('HOST_IPMI_LIB_PATH', get_option('host-ipmi-lib-path'))
+conf_data.set_quoted('FW_VER_REGEX', get_option('fw-ver-regex'))
+
+if get_option('shortname-remove-suffix').allowed()
+    conf_data.set_quoted('SHORTNAME_REMOVE_SUFFIX', '1')
+endif
+if get_option('shortname-replace-words').allowed()
+    conf_data.set_quoted('SHORTNAME_REPLACE_WORDS', '1')
+endif
+if get_option('open-power').allowed()
+    conf_data.set_quoted('OPEN_POWER_SUPPORT', '1')
+endif
+
+matches_map = get_option('matches-map')
+conf_data.set('MAJOR_MATCH_INDEX', matches_map[0])
+conf_data.set('MINOR_MATCH_INDEX', matches_map[1])
+conf_data.set('AUX_0_MATCH_INDEX', matches_map[2])
+conf_data.set('AUX_1_MATCH_INDEX', matches_map[3])
+conf_data.set('AUX_2_MATCH_INDEX', matches_map[4])
+conf_data.set('AUX_3_MATCH_INDEX', matches_map[5])
+
+conf_h = configure_file(output: 'config.h', configuration: conf_data)
+
+root = meson.current_source_dir()
+root_inc = include_directories('.', 'include')
 
 # Project Arguments
 cpp = meson.get_compiler('cpp')
-if cpp.has_header('CLI/CLI.hpp')
-    cli11_dep = declare_dependency()
-else
-    cli11_dep = dependency('CLI11')
-endif
-
 add_project_arguments(
     cpp.get_supported_arguments(
         [
@@ -48,73 +86,283 @@
     language: 'cpp',
 )
 
-deps = [
-    cli11_dep,
-    ipmid_dep,
-    userlayer_dep,
-    channellayer_dep,
-    libcrypto_dep,
-    libsystemd_dep,
-    phosphor_dbus_interfaces_dep,
-    phosphor_logging_dep,
+if get_option('get-dbus-active-software').allowed()
+    add_project_arguments(
+        cpp.get_supported_arguments(['-DGET_DBUS_ACTIVE_SOFTWARE']),
+        language: 'cpp',
+    )
+endif
+
+feature_map = {
+    'boot-flag-safe-mode-support': '-DENABLE_BOOT_FLAG_SAFE_MODE_SUPPORT',
+    'i2c-whitelist-check'        : '-DENABLE_I2C_WHITELIST_CHECK',
+    'update-functional-on-fail'  : '-DUPDATE_FUNCTIONAL_ON_FAIL',
+    'dynamic-sensors'            : '-DFEATURE_DYNAMIC_SENSORS',
+    'dynamic-sensors-write'      : '-DFEATURE_DYNAMIC_SENSORS_WRITE',
+    'entity-manager-decorators'  : '-DUSING_ENTITY_MANAGER_DECORATORS',
+    'hybrid-sensors'             : '-DFEATURE_HYBRID_SENSORS',
+    'sensors-cache'              : '-DFEATURE_SENSORS_CACHE',
+    'dynamic-storages-only'      : '-DFEATURE_DYNAMIC_STORAGES_ONLY',
+    'arm-sbmr'                   : '-DARM_SBMR_SUPPORT',
+}
+
+foreach option_key, option_value : feature_map
+    if (get_option(option_key).allowed())
+        summary(option_key, option_value, section: 'Enabled Features')
+        add_project_arguments(option_value, language: 'cpp')
+    endif
+endforeach
+
+add_project_arguments(
+    cpp.get_supported_arguments(
+        [
+            '-Wno-psabi',
+            '-Wno-missing-field-initializers',
+            '-Wno-pedantic',
+            '-Wno-non-virtual-dtor',
+        ],
+    ),
+    language: 'cpp',
+)
+
+# Dependencies
+
+boost = dependency('boost', modules: ['context', 'coroutine'], required: false)
+
+if not boost.found()
+    cmake = import('cmake')
+    opt = cmake.subproject_options()
+    opt.add_cmake_defines(
+        {
+            'BOOST_INCLUDE_LIBRARIES': 'asio;bimap;callable_traits;context;coroutine;interprocess;multiprecision;process',
+            'CMAKE_POSITION_INDEPENDENT_CODE': true,
+        },
+    )
+    boost_cmake = cmake.subproject('boost', required: true, options: opt)
+    boost_asio = boost_cmake.dependency('boost_asio').as_system()
+    boost_bimap = boost_cmake.dependency('boost_bimap').as_system()
+    boost_callable_traits = boost_cmake.dependency('boost_callable_traits').as_system()
+    boost_context = boost_cmake.dependency('boost_context').as_system()
+    boost_coroutine = boost_cmake.dependency('boost_coroutine').as_system()
+    boost_interprocess = boost_cmake.dependency('boost_interprocess').as_system()
+    boost_multiprecision = boost_cmake.dependency('boost_multiprecision').as_system()
+    boost_process = boost_cmake.dependency('boost_process').as_system()
+    boost = [
+        boost_asio,
+        boost_bimap,
+        boost_callable_traits,
+        boost_context,
+        boost_coroutine,
+        boost_interprocess,
+        boost_multiprecision,
+        boost_process,
+    ]
+endif
+
+phosphor_logging_dep = dependency('phosphor-logging')
+phosphor_dbus_interfaces_dep = dependency('phosphor-dbus-interfaces')
+sdeventplus_dep = dependency('sdeventplus')
+libsystemd_dep = dependency('libsystemd')
+crypto = dependency('libcrypto', version: '>=1.0.2g')
+pam = cpp.find_library('pam', required: true)
+sdbusplus_dep = dependency('sdbusplus')
+stdplus_dep = dependency('stdplus')
+
+nlohmann_json_dep = dependency('nlohmann_json', include_type: 'system')
+
+generated_src = []
+
+# Subfolders
+subdir('libipmid')
+subdir('include')
+subdir('user_channel')
+subdir('scripts')
+
+if get_option('softoff').allowed()
+    subdir('xyz/openbmc_project/Ipmi/Internal/SoftPowerOff')
+    subdir('softoff')
+endif
+
+# whitelist
+if get_option('ipmi-whitelist').allowed()
+    generate_whitelist_script = files('generate_whitelist_create.sh')
+
+    whitelist_conf = get_option('whitelist-conf')
+    ipmiwhitelist = run_command( \
+            'bash', \
+            generate_whitelist_script, \
+            whitelist_conf,
+    )
+
+    whitelist_pre = declare_dependency(
+        include_directories: root_inc,
+        dependencies: [
+            crypto,
+            ipmid_dep,
+            phosphor_dbus_interfaces_dep,
+            phosphor_logging_dep,
+            sdbusplus_dep,
+        ],
+    )
+
+    whitelist_lib = library(
+        'whitelist',
+        'whitelist-filter.cpp',
+        'ipmiwhitelist.cpp',
+        implicit_include_directories: false,
+        dependencies: whitelist_pre,
+        version: meson.project_version(),
+        override_options: ['b_lundef=false'],
+        install: true,
+        install_dir: get_option('libdir') / 'ipmid-providers',
+    )
+endif
+
+# libsysintfcmds
+sysintfcmds_pre = declare_dependency(
+    include_directories: root_inc,
+    dependencies: [
+        channellayer_dep,
+        crypto,
+        nlohmann_json_dep,
+        phosphor_dbus_interfaces_dep,
+        phosphor_logging_dep,
+        sdbusplus_dep,
+        ipmid_dep,
+    ],
+)
+
+sysintfcmds_lib = library(
+    'sysintfcmds',
+    'systemintfcmds.cpp',
+    'host-interface.cpp',
+    implicit_include_directories: false,
+    dependencies: sysintfcmds_pre,
+    version: meson.project_version(),
+    override_options: ['b_lundef=false'],
+    install: true,
+    install_dir: get_option('libdir') / 'ipmid-providers',
+)
+
+# ipmid
+ipmid_pre = [
     sdbusplus_dep,
+    stdplus_dep,
+    phosphor_logging_dep,
+    phosphor_dbus_interfaces_dep,
+    boost,
+    crypto,
+    ipmid_dep,
+    channellayer_dep,
 ]
 
-sources = [
-    'auth_algo.cpp',
-    'sessions_manager.cpp',
-    'message_parsers.cpp',
-    'message_handler.cpp',
-    'command_table.cpp',
-    'command/channel_auth.cpp',
-    'command/guid.cpp',
-    'command/open_session.cpp',
-    'command/rakp12.cpp',
-    'command/rakp34.cpp',
-    'command/session_cmds.cpp',
-    'comm_module.cpp',
-    'main.cpp',
-    'integrity_algo.cpp',
-    'crypt_algo.cpp',
-    'sd_event_loop.cpp',
-    'sol/sol_manager.cpp',
-    'sol/sol_context.cpp',
-    'command/sol_cmds.cpp',
-    'command/payload_cmds.cpp',
-    'sol_module.cpp',
+transportoem_src = []
+if get_option('transport-oem').allowed()
+    transportoem_src = ['transporthandler_oem.cpp']
+endif
+
+storage_cmds_src = []
+if get_option('dynamic-sensors').disabled() and not get_option(
+    'dynamic-storages-only',
+).disabled()
+    storage_cmds_src = ['dbus-sdr/storagecommands.cpp', 'dbus-sdr/sdrutils.cpp']
+endif
+
+openpower_cmds_src = []
+if get_option('open-power').allowed()
+    openpower_cmds_src = ['storageaddsel.cpp']
+endif
+
+arm_sbmr_cmds_src = []
+if get_option('arm-sbmr').allowed()
+    arm_sbmr_cmds_src = ['sbmrhandler.cpp']
+endif
+
+libipmi20_src = [
+    'app/channel.cpp',
+    'app/watchdog.cpp',
+    'app/watchdog_service.cpp',
+    'apphandler.cpp',
+    'sys_info_param.cpp',
+    'sensorhandler.cpp',
+    'storagehandler.cpp',
+    'chassishandler.cpp',
+    'dcmihandler.cpp',
+    'ipmisensor.cpp',
+    'transporthandler.cpp',
+    'globalhandler.cpp',
+    'groupext.cpp',
+    'selutility.cpp',
+    'ipmi_fru_info_area.cpp',
+    'read_fru_data.cpp',
+    'sensordatahandler.cpp',
+    'user_channel/channelcommands.cpp',
+    generated_src,
+    transportoem_src,
+    storage_cmds_src,
+    openpower_cmds_src,
+    arm_sbmr_cmds_src,
+    conf_h,
 ]
 
+ipmi20_lib = library(
+    'ipmi20',
+    libipmi20_src,
+    dependencies: [ipmid_pre, nlohmann_json_dep],
+    include_directories: root_inc,
+    install: true,
+    install_dir: get_option('libdir') / 'ipmid-providers',
+    version: meson.project_version(),
+    override_options: ['b_lundef=false'],
+)
+
+libipmi20_dep = declare_dependency(
+    dependencies: ipmid_pre,
+    include_directories: root_inc,
+    link_with: ipmi20_lib,
+)
+
+# ipmid binary
 executable(
-    'netipmid',
-    sources,
-    implicit_include_directories: true,
-    include_directories: ['command', 'sol'],
-    dependencies: deps,
+    'ipmid',
+    'ipmid-new.cpp',
+    'host-cmd-manager.cpp',
+    'settings.cpp',
+    implicit_include_directories: false,
+    dependencies: [libipmi20_dep],
+    include_directories: root_inc,
+    export_dynamic: true,
     install: true,
     install_dir: get_option('bindir'),
 )
 
-systemd = dependency('systemd')
-systemd_system_unit_dir = systemd.get_variable(
-    'systemdsystemunitdir',
-    pkgconfig_define: ['prefix', get_option('prefix')],
-)
+# Dynamic Sensor Stack
+subdir('dbus-sdr')
 
-configure_file(
-    input: 'phosphor-ipmi-net@.service',
-    output: 'phosphor-ipmi-net@.service',
-    copy: true,
-    install_dir: systemd_system_unit_dir,
-)
-
-configure_file(
-    input: 'phosphor-ipmi-net@.socket',
-    output: 'phosphor-ipmi-net@.socket',
-    copy: true,
-    install_dir: systemd_system_unit_dir,
-)
-
-build_tests = get_option('tests')
-if build_tests.allowed()
-    subdir('test')
+if get_option('dynamic-sensors').disabled() or not get_option('tests').allowed()
+    library(
+        'dynamiccmds',
+        dbus_sdr_src,
+        implicit_include_directories: false,
+        dependencies: dbus_sdr_pre,
+        version: meson.project_version(),
+        override_options: ['b_lundef=false'],
+        install: true,
+        install_dir: get_option('libdir') / 'ipmid-providers',
+    )
 endif
+
+if get_option('tests').allowed()
+    subdir('test')
+    subdir('transport/serialbridge')
+endif
+
+install_subdir(
+    'user_channel',
+    install_dir: get_option('includedir'),
+    strip_directory: false,
+    exclude_files: '*.cpp',
+)
+
+# HW Transport
+subdir('transport')
diff --git a/meson.options b/meson.options
index 6f9cbaa..fcc8851 100644
--- a/meson.options
+++ b/meson.options
@@ -1,15 +1,260 @@
-option('tests', type: 'feature', value: 'enabled', description: 'Build tests')
 
+option('tests', type: 'feature', description: 'Build tests')
 option(
-    'rmcp_ping',
+    'boot-flag-safe-mode-support',
     type: 'feature',
-    value: 'enabled',
-    description: 'Enable RMCP Ping support',
+    description: 'Add option to enable/disable safe mode in boot flags',
+)
+option(
+    'i2c-whitelist-check',
+    type: 'feature',
+    description: 'Add option to enable/disable i2c master write read command white list checking',
 )
 
+# SoftPowerOff
+option('softoff', type: 'feature', description: 'Builds soft power off')
 option(
-    'pam_authenticate',
+    'softoff-busname',
+    type: 'string',
+    value: 'xyz.openbmc_project.Ipmi.Internal.SoftPowerOff',
+    description: 'The Dbus busname to own for SoftPowerOff',
+)
+option(
+    'softoff-objpath',
+    type: 'string',
+    value: '/xyz/openbmc_project/ipmi/internal/soft_power_off',
+    description: 'The SoftPowerOff Dbus root',
+)
+option(
+    'ipmi-sms-atn-ack-timeout-secs',
+    type: 'integer',
+    value: 3,
+    description: 'Timeout for host to ack and query SMS_ATN from BMC',
+)
+option(
+    'ipmi-host-shutdown-complete-timeout-secs',
+    type: 'integer',
+    value: 2700,
+    description: 'Wait time for host to shutdown',
+)
+# Indicates an in-band power off or reboot request from the host
+# This file is used to ensure the soft off service does not run for host
+# initiated shutdown or reboot requests
+option(
+    'host-inband-request-dir',
+    type: 'string',
+    value: '/run/openbmc/',
+    description: 'Directory to store host initiated shutdown file',
+)
+option(
+    'host-inband-request-file',
+    type: 'string',
+    value: 'host@%u-request',
+    description: 'File to create if host has initiated shutdown or reboot',
+)
+
+
+# Config Variables
+option(
+    'board-sensor',
+    type: 'string',
+    value: '/xyz/openbmc_project/inventory/system/chassis/motherboard',
+    description: 'The inventory path to the motherboard fault sensor',
+)
+option(
+    'system-sensor',
+    type: 'string',
+    value: '/xyz/openbmc_project/inventory/system',
+    description: 'The inventory path to the system event sensor',
+)
+
+# Control Host Interfaces
+option(
+    'control-host-busname',
+    type: 'string',
+    value: 'xyz.openbmc_project.Control.Host',
+    description: 'The Control Host Dbus busname to own',
+)
+option(
+    'control-host-obj-mgr',
+    type: 'string',
+    value: '/xyz/openbmc_project/control',
+    description: 'The Control Host D-Bus Object Manager',
+)
+option(
+    'host-name',
+    type: 'string',
+    value: 'host',
+    description: 'The Control Host D-Bus Object Manager',
+)
+
+# Power reading sensor configuration file
+option(
+    'power-reading-sensor',
+    type: 'string',
+    value: '/usr/share/ipmi-providers/power_reading.json',
+    description: 'Power reading sensor configuration file',
+)
+option(
+    'host-ipmi-lib-path',
+    type: 'string',
+    value: '/usr/lib/ipmid-providers/',
+    description: 'The file path to search for libraries',
+)
+
+# When a sensor read fails, hwmon will update the OperationalState interface's Functional property.
+# This will mark the sensor as not functional and we will skip reading from that sensor.
+option(
+    'update-functional-on-fail',
+    type: 'feature',
+    value: 'disabled',
+    description: 'Check functional property to skip reading from faulty sensors',
+)
+
+# Features
+
+# When libuserlayer is disabled, libuserlayer won't be included in the build.
+option(
+    'libuserlayer',
+    type: 'feature',
+    description: 'Option to exclue exclude libuserlayer',
+)
+
+# When transport-oem is enabled, the transporthandler_oem.cpp contents
+# are compiled and added to the project. The transporthandler_oem.cpp file is
+# copied from your own customization layer in the
+# phosphor-ipmi-host_%.bbappend file. It is not necessary to create this file
+# unless OEM Parameter extensions are required.
+option(
+    'transport-oem',
+    type: 'feature',
+    value: 'disabled',
+    description: 'Enable transporthandler_oem contents',
+)
+
+# IPMI whitelist mechanism is not needed by everyone; offer a way to disable it
+option(
+    'ipmi-whitelist',
+    type: 'feature',
+    description: 'Enable/disable IPMI whitelist filtering',
+)
+option(
+    'whitelist-conf',
+    type: 'string',
+    value: 'host-ipmid-whitelist.conf',
+    description: 'Paths to IPMI whitelisted commands conf files',
+)
+
+# Entity Manager Decorators
+option(
+    'entity-manager-decorators',
     type: 'feature',
     value: 'enabled',
-    description: 'Enable Pam Authenticate',
+    description: 'The Entity Manager Decorators flag is enabled by default; offer a way to disable it',
+)
+
+# Dynamic Sensor Stack
+option(
+    'dynamic-sensors',
+    type: 'feature',
+    value: 'disabled',
+    description: 'Dynamic sensors stack is enabled by default; offer a way to disable it',
+)
+option(
+    'dynamic-sensors-write',
+    type: 'feature',
+    value: 'disabled',
+    description: 'Dynamic sensors stack is enabled by default; offer a way to disable it',
+)
+option(
+    'hybrid-sensors',
+    type: 'feature',
+    value: 'disabled',
+    description: 'Hybrid sensors stack is disabled by default; offer a way to enable it',
+)
+option(
+    'sensors-oem',
+    type: 'feature',
+    value: 'disabled',
+    description: 'OEM sensor SDR parsing is disabled by default; offer a way to enable it',
+)
+
+# Sensor Cache
+option(
+    'sensors-cache',
+    type: 'feature',
+    value: 'disabled',
+    description: 'Sensor cache stack is disabled by default; offer a way to enable it',
+)
+
+# Short Sensor Names for IPMI
+option(
+    'shortname-remove-suffix',
+    type: 'feature',
+    value: 'enabled',
+    description: 'shortname-remove-suffix is enabled by default',
+)
+option(
+    'shortname-replace-words',
+    type: 'feature',
+    value: 'disabled',
+    description: 'shortname-replace-words is disabled by default',
+)
+
+# Generate configuration from Yaml
+option('sensor-yaml-gen', type: 'string', value: 'sensor-example.yaml')
+option(
+    'invsensor-yaml-gen',
+    type: 'string',
+    value: 'inventory-sensor-example.yaml',
+)
+option('fru-yaml-gen', type: 'string', value: 'fru-read-example.yaml')
+
+# Software Version
+option(
+    'get-dbus-active-software',
+    type: 'feature',
+    description: 'Use the  getActiveSoftwareVersionInfo for the BMC version and dev_id.json as backup',
+)
+option(
+    'fw-ver-regex',
+    type: 'string',
+    value: '(\\\\d+)\\\\.(\\\\d+)',
+    description: 'Regular expressions for parsing firmware revision',
+)
+option(
+    'matches-map',
+    type: 'array',
+    value: ['1', '2', '0', '0', '0', '0'],
+    description: 'An array of integers',
+)
+
+# libipmi20.so library
+option(
+    'dynamic-storages-only',
+    type: 'feature',
+    value: 'disabled',
+    description: 'Request to compile storage commands in the libipmi20 library',
+)
+
+# open-power specific functionality.
+option(
+    'open-power',
+    type: 'feature',
+    description: 'Support open-power specific functions',
+)
+
+# HW transport
+option(
+    'transport-implementation',
+    type: 'combo',
+    choices: ['null', 'serial'],
+    description: 'transport',
+)
+
+# arm-sbmr specific functionality.
+option(
+    'arm-sbmr',
+    type: 'feature',
+    description: 'Support Arm SBMR specific functions',
 )
diff --git a/read_fru_data.cpp b/read_fru_data.cpp
new file mode 100644
index 0000000..9e5fd59
--- /dev/null
+++ b/read_fru_data.cpp
@@ -0,0 +1,180 @@
+#include "read_fru_data.hpp"
+
+#include "fruread.hpp"
+
+#include <ipmid/api.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <algorithm>
+#include <map>
+
+extern const FruMap frus;
+namespace ipmi
+{
+namespace fru
+{
+
+using namespace phosphor::logging;
+using InternalFailure =
+    sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+std::unique_ptr<sdbusplus::bus::match_t> matchPtr
+    __attribute__((init_priority(101)));
+
+namespace cache
+{
+// User initiate read FRU info area command followed by
+// FRU read command. Also data is read in small chunks of
+// the specified offset and count.
+// Caching the data which will be invalidated when ever there
+// is a change in FRU properties.
+FRUAreaMap fruMap;
+} // namespace cache
+/**
+ * @brief Read all the property value's for the specified interface
+ *  from Inventory.
+ *
+ * @param[in] intf Interface
+ * @param[in] path Object path
+ * @return map of properties
+ */
+ipmi::PropertyMap readAllProperties(const std::string& intf,
+                                    const std::string& path)
+{
+    ipmi::PropertyMap properties;
+    sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+    std::string service;
+    std::string objPath;
+
+    // Is the path the full dbus path?
+    if (path.find(xyzPrefix) != std::string::npos)
+    {
+        service = ipmi::getService(bus, intf, path);
+        objPath = path;
+    }
+    else
+    {
+        service = ipmi::getService(bus, invMgrInterface, invObjPath);
+        objPath = invObjPath + path;
+    }
+
+    auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
+                                      propInterface, "GetAll");
+    method.append(intf);
+    try
+    {
+        auto reply = bus.call(method);
+        reply.read(properties);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        // If property is not found simply return empty value
+        lg2::error("Error in reading property values: {ERROR}, path: {PATH}, "
+                   "interface: {INTERFACE}",
+                   "ERROR", e, "PATH", objPath, "INTERFACE", intf);
+    }
+
+    return properties;
+}
+
+void processFruPropChange(sdbusplus::message_t& msg)
+{
+    if (cache::fruMap.empty())
+    {
+        return;
+    }
+    std::string path = msg.get_path();
+    // trim the object base path, if found at the beginning
+    if (path.compare(0, strlen(invObjPath), invObjPath) == 0)
+    {
+        path.erase(0, strlen(invObjPath));
+    }
+    for (const auto& [fruId, instanceList] : frus)
+    {
+        auto found = std::find_if(
+            instanceList.begin(), instanceList.end(),
+            [&path](const auto& iter) { return (iter.path == path); });
+
+        if (found != instanceList.end())
+        {
+            cache::fruMap.erase(fruId);
+            break;
+        }
+    }
+}
+
+// register for fru property change
+int registerCallbackHandler()
+{
+    if (matchPtr == nullptr)
+    {
+        using namespace sdbusplus::bus::match::rules;
+        sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+        matchPtr = std::make_unique<sdbusplus::bus::match_t>(
+            bus,
+            path_namespace(invObjPath) + type::signal() +
+                member("PropertiesChanged") + interface(propInterface),
+            std::bind(processFruPropChange, std::placeholders::_1));
+    }
+    return 0;
+}
+
+/**
+ * @brief Read FRU property values from Inventory
+ *
+ * @param[in] fruNum  FRU id
+ * @return populate FRU Inventory data
+ */
+FruInventoryData readDataFromInventory(const FRUId& fruNum)
+{
+    auto iter = frus.find(fruNum);
+    if (iter == frus.end())
+    {
+        lg2::error("Unsupported FRU ID: {FRUID}", "FRUID", fruNum);
+        elog<InternalFailure>();
+    }
+
+    FruInventoryData data;
+    auto& instanceList = iter->second;
+    for (auto& instance : instanceList)
+    {
+        for (auto& intf : instance.interfaces)
+        {
+            ipmi::PropertyMap allProp =
+                readAllProperties(intf.first, instance.path);
+            for (auto& properties : intf.second)
+            {
+                auto iter = allProp.find(properties.first);
+                if (iter != allProp.end())
+                {
+                    data[properties.second.section].emplace(
+                        properties.second.property,
+                        std::move(
+                            std::get<std::string>(allProp[properties.first])));
+                }
+            }
+        }
+    }
+    return data;
+}
+
+const FruAreaData& getFruAreaData(const FRUId& fruNum)
+{
+    auto iter = cache::fruMap.find(fruNum);
+    if (iter != cache::fruMap.end())
+    {
+        return iter->second;
+    }
+    auto invData = readDataFromInventory(fruNum);
+
+    // Build area info based on inventory data
+    FruAreaData newdata = buildFruAreaData(std::move(invData));
+    cache::fruMap.emplace(fruNum, std::move(newdata));
+    return cache::fruMap.at(fruNum);
+}
+} // namespace fru
+} // namespace ipmi
diff --git a/read_fru_data.hpp b/read_fru_data.hpp
new file mode 100644
index 0000000..e0997fd
--- /dev/null
+++ b/read_fru_data.hpp
@@ -0,0 +1,38 @@
+#pragma once
+#include "ipmi_fru_info_area.hpp"
+
+#include <sdbusplus/bus.hpp>
+
+#include <string>
+
+namespace ipmi
+{
+namespace fru
+{
+using FRUId = uint8_t;
+using FRUAreaMap = std::map<FRUId, FruAreaData>;
+
+static constexpr auto xyzPrefix = "/xyz/openbmc_project/";
+static constexpr auto invMgrInterface = "xyz.openbmc_project.Inventory.Manager";
+static constexpr auto invObjPath = "/xyz/openbmc_project/inventory";
+static constexpr auto propInterface = "org.freedesktop.DBus.Properties";
+static constexpr auto invItemInterface = "xyz.openbmc_project.Inventory.Item";
+static constexpr auto itemPresentProp = "Present";
+
+/**
+ * @brief Get fru area data as per IPMI specification
+ *
+ * @param[in] fruNum FRU ID
+ *
+ * @return FRU area data as per IPMI specification
+ */
+const FruAreaData& getFruAreaData(const FRUId& fruNum);
+
+/**
+ * @brief Register callback handler into DBUS for PropertyChange events
+ *
+ * @return negative value on failure
+ */
+int registerCallbackHandler();
+} // namespace fru
+} // namespace ipmi
diff --git a/sbmrhandler.cpp b/sbmrhandler.cpp
new file mode 100644
index 0000000..328d503
--- /dev/null
+++ b/sbmrhandler.cpp
@@ -0,0 +1,317 @@
+#include <ipmid/api.hpp>
+#include <ipmid/filter.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+constexpr auto sbmrBootStateIntf = "xyz.openbmc_project.State.Boot.Raw";
+constexpr auto sbmrHostStateIntf = "xyz.openbmc_project.State.Boot.Progress";
+constexpr auto sbmrBootProgressCodeSize = 9;
+
+constexpr auto bootProgressOem = "OEM";
+constexpr auto bootProgressOsRuning = "OSRunning";
+constexpr auto bootProgressOsStart = "OSStart";
+constexpr auto bootProgressPciInit = "PCIInit";
+constexpr auto bootProgressSystemInitComplete = "SystemInitComplete";
+constexpr auto bootProgressSystemSetup = "SystemSetup";
+
+// EFI_STATUS_CODE_TYPE
+constexpr auto efiProgressCode = 0x01;
+constexpr auto efiCodeSeverityNone = 0;
+
+// EFI_STATUS_CODE_CLASS
+constexpr auto efiIoBus = 0x02;
+constexpr auto efiSoftware = 0x03;
+
+// EFI_STATUS_CODE_SUBCLASS
+constexpr auto efiIoBusPci = 0x01;
+constexpr auto efiSoftwareDxeCore = 0x04;
+constexpr auto efiSoftwareDxeBsDriver = 0x05;
+constexpr auto efiSoftwareEfiBootService = 0x10;
+
+// EFI_STATUS_CODE_OPERATION
+constexpr auto efiIoBusPciResAlloc = 0x0110;
+constexpr auto efiSwDxeCorePcHandoffToNext = 0x0110;
+constexpr auto efiSwPcUserSetup = 0x0700;
+constexpr auto efiSwOsLoaderStart = 0x0180;
+constexpr auto efiSwBsPcExitBootServices = 0x1910;
+
+void registerNetfnSBMRFunctions() __attribute__((constructor));
+
+namespace ipmi
+{
+
+std::string getSbmrBootProgressStage(uint8_t codeType, uint8_t codeSeverity,
+                                     uint8_t codeClass, uint8_t codeSubClass,
+                                     uint16_t codeOperation)
+{
+    // Return OEM if code type or severity are unexpected
+    if (codeType != efiProgressCode || codeSeverity != efiCodeSeverityNone)
+    {
+        return bootProgressOem;
+    }
+
+    // Code Class Software
+    if (codeClass == efiSoftware)
+    {
+        if (codeSubClass == efiSoftwareDxeCore &&
+            codeOperation == efiSwDxeCorePcHandoffToNext)
+        {
+            return bootProgressSystemInitComplete;
+        }
+        else if (codeSubClass == efiSoftwareDxeBsDriver &&
+                 codeOperation == efiSwPcUserSetup)
+        {
+            return bootProgressSystemSetup;
+        }
+        else if (codeSubClass == efiSoftwareDxeBsDriver &&
+                 codeOperation == efiSwOsLoaderStart)
+        {
+            return bootProgressOsStart;
+        }
+        else if (codeSubClass == efiSoftwareEfiBootService &&
+                 codeOperation == efiSwBsPcExitBootServices)
+        {
+            return bootProgressOsRuning;
+        }
+    }
+    // Code Class IO Bus
+    else if (codeClass == efiIoBus)
+    {
+        if (codeSubClass == efiIoBusPci && codeOperation == efiIoBusPciResAlloc)
+        {
+            return bootProgressPciInit;
+        }
+    }
+
+    // Fallback to OEM if no conditions met
+    return bootProgressOem;
+}
+
+bool updateBootProgressProperty(ipmi::Context::ptr& ctx,
+                                const std::string& value)
+{
+    std::string bootProgress =
+        "xyz.openbmc_project.State.Boot.Progress.ProgressStages." + value;
+    ipmi::DbusObjectInfo sbmrHostStateObject{};
+
+    /* Get Host State Object */
+    boost::system::error_code ec =
+        ipmi::getDbusObject(ctx, sbmrHostStateIntf, sbmrHostStateObject);
+    if (ec.value())
+    {
+        lg2::error("Failed to get Host State object, Error={ERROR}", "ERROR",
+                   ec.message());
+        return false;
+    }
+
+    /* Set Host State property */
+    ec = ipmi::setDbusProperty(ctx, sbmrHostStateObject.second,
+                               sbmrHostStateObject.first, sbmrHostStateIntf,
+                               "BootProgress", bootProgress);
+    if (ec.value())
+    {
+        lg2::error(
+            "updateBootProgressProperty, can't set progerty - Error={ERROR}",
+            "ERROR", ec.message());
+        return false;
+    }
+
+    return true;
+}
+
+bool updateBootProgressLastUpdateProperty(ipmi::Context::ptr& ctx,
+                                          uint64_t timeStamp)
+{
+    ipmi::DbusObjectInfo sbmrHostStateObject{};
+
+    /* Get Host State Object */
+    boost::system::error_code ec =
+        ipmi::getDbusObject(ctx, sbmrHostStateIntf, sbmrHostStateObject);
+    if (ec.value())
+    {
+        lg2::error("Failed to get Host State object, Error={ERROR}", "ERROR",
+                   ec.message());
+        return false;
+    }
+
+    /* Set Host State property */
+    ec = ipmi::setDbusProperty(ctx, sbmrHostStateObject.second,
+                               sbmrHostStateObject.first, sbmrHostStateIntf,
+                               "BootProgressLastUpdate", timeStamp);
+    if (ec.value())
+    {
+        lg2::error(
+            "updateBootProgressLastUpdateProperty, can't set property - Error={ERROR}",
+            "ERROR", ec.message());
+        return false;
+    }
+
+    return true;
+}
+
+ipmi::RspType<> sendBootProgressCode(
+    ipmi::Context::ptr ctx, uint8_t codeType, uint8_t codeReserved1,
+    uint8_t codeReserved2, uint8_t codeSeverity, uint8_t codeOperation1,
+    uint8_t codeOperation2, uint8_t codeSubClass, uint8_t codeClass,
+    uint8_t instance)
+{
+    /* Update boot progress code to Dbus property */
+    ipmi::DbusObjectInfo sbmrBootStateObject{};
+
+    /* Get Boot State Object */
+    boost::system::error_code ec =
+        ipmi::getDbusObject(ctx, sbmrBootStateIntf, sbmrBootStateObject);
+    if (ec.value())
+    {
+        lg2::error("Failed to get Boot State object, Error={ERROR}", "ERROR",
+                   ec.message());
+        return ipmi::responseUnspecifiedError();
+    }
+
+    /* Set Boot State property */
+    BootProgressCode bpCode(
+        {codeType, codeReserved1, codeReserved2, codeSeverity, codeOperation1,
+         codeOperation2, codeSubClass, codeClass, instance},
+        {});
+    ec = ipmi::setDbusProperty(ctx, sbmrBootStateObject.second,
+                               sbmrBootStateObject.first, sbmrBootStateIntf,
+                               "Value", bpCode);
+    if (ec.value())
+    {
+        lg2::error("Failed to set boot progress code, Error={ERROR}", "ERROR",
+                   ec.message());
+        return ipmi::responseUnspecifiedError();
+    }
+
+    /* Update Redfish BootProgress object */
+    auto timeStamp = std::chrono::duration_cast<std::chrono::microseconds>(
+                         std::chrono::system_clock::now().time_since_epoch())
+                         .count();
+    if (!updateBootProgressLastUpdateProperty(ctx, timeStamp))
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    /* Chek for BootProgressTypes */
+    uint16_t codeOperation =
+        static_cast<uint16_t>(codeOperation1) << 8 | codeOperation2;
+
+    std::string stage = getSbmrBootProgressStage(
+        codeType, codeSeverity, codeClass, codeSubClass, codeOperation);
+
+    if (!updateBootProgressProperty(ctx, stage))
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    return ipmi::responseSuccess();
+}
+
+ipmi::RspType<uint8_t, // STATUS_CODE_TYPE
+              uint8_t, // STATUS_CODE_RESERVED1
+              uint8_t, // STATUS_CODE_RESERVED2
+              uint8_t, // STATUS_CODE_SEVERITY
+              uint8_t, // STATUS_CODE_OPERATION1
+              uint8_t, // STATUS_CODE_OPERATION2
+              uint8_t, // STATUS_CODE_SUBCLASS
+              uint8_t, // STATUS_CODE_CLASS
+              uint8_t> // Instance
+    getBootProgressCode(ipmi::Context::ptr ctx)
+{
+    ipmi::DbusObjectInfo sbmrBootStateObject{};
+
+    /* Get Boot State Object */
+    boost::system::error_code ec =
+        ipmi::getDbusObject(ctx, sbmrBootStateIntf, sbmrBootStateObject);
+    if (ec.value())
+    {
+        lg2::error("Failed to get Boot State object, Error={ERROR}", "ERROR",
+                   ec.message());
+        return ipmi::responseUnspecifiedError();
+    }
+
+    /* Get Boot State property */
+    BootProgressCode value;
+    ec = ipmi::getDbusProperty(ctx, sbmrBootStateObject.second,
+                               sbmrBootStateObject.first, sbmrBootStateIntf,
+                               "Value", value);
+    if (ec.value())
+    {
+        lg2::error("Can't get property Value, Error={ERROR}", "ERROR",
+                   ec.message());
+        return ipmi::responseUnspecifiedError();
+    }
+
+    auto respBootProgressCode = std::get<0>(std::move(value));
+    if (respBootProgressCode.size() != sbmrBootProgressCodeSize)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    return ipmi::responseSuccess(
+        respBootProgressCode[0], respBootProgressCode[1],
+        respBootProgressCode[2], respBootProgressCode[3],
+        respBootProgressCode[4], respBootProgressCode[5],
+        respBootProgressCode[6], respBootProgressCode[7],
+        respBootProgressCode[8]);
+}
+
+bool checkAllowedMediumType(uint8_t mediumType)
+{
+    if (mediumType ==
+            static_cast<uint8_t>(ipmi::EChannelMediumType::smbusV20) ||
+        mediumType ==
+            static_cast<uint8_t>(ipmi::EChannelMediumType::systemInterface) ||
+        mediumType == static_cast<uint8_t>(ipmi::EChannelMediumType::oem))
+    {
+        return true;
+    }
+
+    return false;
+}
+
+ipmi::Cc sbmrFilterCommands(ipmi::message::Request::ptr request)
+{
+    if (request->ctx->netFn != ipmi::netFnGroup ||
+        request->ctx->group != ipmi::groupSBMR)
+    {
+        // Skip if not group SBMR
+        return ipmi::ccSuccess;
+    }
+
+    ipmi::ChannelInfo chInfo;
+    if (ipmi::getChannelInfo(request->ctx->channel, chInfo) != ipmi::ccSuccess)
+    {
+        lg2::error("Failed to get Channel Info, channel={CHANNEL}", "CHANNEL",
+                   request->ctx->channel);
+        return ipmi::ccUnspecifiedError;
+    }
+
+    if (request->ctx->cmd == ipmi::sbmr::cmdSendBootProgressCode &&
+        !checkAllowedMediumType(chInfo.mediumType))
+    {
+        lg2::error("Error - Medium interface not supported, medium={TYPE}",
+                   "TYPE", chInfo.mediumType);
+        return ipmi::ccCommandNotAvailable;
+    }
+
+    return ipmi::ccSuccess;
+}
+
+} // namespace ipmi
+
+void registerNetfnSBMRFunctions()
+{
+    registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupSBMR,
+                         ipmi::sbmr::cmdSendBootProgressCode,
+                         ipmi::Privilege::Admin, ipmi::sendBootProgressCode);
+
+    registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupSBMR,
+                         ipmi::sbmr::cmdGetBootProgressCode,
+                         ipmi::Privilege::User, ipmi::getBootProgressCode);
+
+    ipmi::registerFilter(ipmi::prioOemBase,
+                         [](ipmi::message::Request::ptr request) {
+                             return ipmi::sbmrFilterCommands(request);
+                         });
+}
diff --git a/scripts/entity-example.md b/scripts/entity-example.md
new file mode 100644
index 0000000..abeb581
--- /dev/null
+++ b/scripts/entity-example.md
@@ -0,0 +1,156 @@
+# Entity-example
+
+If your platform requires the entity container map, you can provide a json file
+of the format:
+
+```json
+[
+  {
+    "id": 1,
+    "containerEntityId": 2,
+    "containerEntityInstance": 3,
+    "isList": false,
+    "isLinked": false,
+    "entities": [
+      { "id": 1, "instance": 2 },
+      { "id": 1, "instance": 3 },
+      { "id": 1, "instance": 4 },
+      { "id": 1, "instance": 5 }
+    ]
+  }
+]
+```
+
+as part of your `phosphor-ipmi-config`
+
+The above json is identical to the original YAML documented below:
+
+```text
+# This record has:
+# Container Entity Id and Container Entity Instance = (0x13, 0x81)
+# Contained Entity Id and Contained Entity Instance = (0x0A, 0x1),
+# (0x0A, 0x3), (0x0A, 0x5), (0x0A, 0x7)
+# Entity Record id is the key
+0x01:
+  # Container entity contains other entities
+  # Entity Id and entity Instance for the container entity
+  containerEntityId: 0x13
+  containerEntityInstance: 0x81
+  # A record can have contained entities as a four entry list or as upto
+  # two ranges of entity instances; this record has contained entities
+  # as a four entry list
+  isList: "true"
+  # Records can be linked if necessary to extend the number of contained
+  # entities within a container entity; this record is not linked
+  isLinked: "false"
+  entityId1: 0x0A
+  entityInstance1: 0x1
+  entityId2: 0x0A
+  entityInstance2: 0x3
+  entityId3: 0x0A
+  entityInstance3: 0x5
+  entityId4: 0x0A
+  entityInstance4: 0x7
+
+# The below two records have:
+# Container Entity Id and Container Entity Instance = (0x18, 0x2)
+# Contained Entity Id and Contained Entity Instance = (0x1D, 0x1),
+# (0x1D, 0x4), (0x1D, 0x6), (0x2B, 0x1), (0x2B, 0x3), (0x0F, 0x1),
+# (0x0F, 0x3), (0x10, 0x5)
+0x02:
+  containerEntityId: 0x18
+  containerEntityInstance: 0x2
+  # This record  has contained entities as a four entry list
+  isList: "true"
+  # This record is linked with the below record; this record and the
+  # below record have the same container entity Id and container entity
+  # instance;
+  isLinked: "true"
+  entityId1: 0x1D
+  entityInstance1: 0x1
+  entityId2: 0x1D
+  entityInstance2: 0x4
+  entityId3: 0x1D
+  entityInstance3: 0x6
+  entityId4: 0x2B
+  entityInstance4: 0x1
+
+0x03:
+  containerEntityId: 0x18
+  containerEntityInstance: 0x2
+  # This record  has contained entities as a four entry list
+  isList: "true"
+  # This record is linked with the above record; this record and the
+  # above record have the same container entity Id and container entity
+  # instance
+  isLinked: "true"
+  entityId1: 0x2B
+  entityInstance1: 0x3
+  entityId2: 0x0F
+  entityInstance2: 0x1
+  entityId3: 0x0F
+  entityInstance3: 0x3
+  entityId4: 0x10
+  entityInstance4: 0x5
+
+# This record has:
+# Container Entity Id and Container Entity Instance = (0x1E, 0x1)
+# Contained Entity Id and Contained Entity Instance = (0x20, 0x1),
+# (0x20, 0x2), (0x20, 0x3), (0x20, 0x7), (0x20, 0x8), (0x20, 0x9)
+0x04:
+  containerEntityId: 0x1E
+  containerEntityInstance: 0x1
+  # This record has contained entities as two ranges of entity instances
+  isList: "false"
+  # This record is not linked
+  isLinked: "false"
+  entityId1: 0x20
+  entityInstance1: 0x1
+  entityId2: 0x20
+  entityInstance2: 0x3
+  entityId3: 0x20
+  entityInstance3: 0x7
+  entityId4: 0x20
+  entityInstance4: 0x9
+
+# The below two records have:
+# Container Entity Id and Container Entity Instance = (0x1E, 0x3)
+# Contained Entity Id and Contained Entity Instance = (0x20, 0x1),
+# (0x20, 0x2), (0x20, 0x3), (0x20, 0x6), (0x20, 0x7), (0x20, 0x8),
+# (0x20, 0xA), (0x20, 0xB), (0x20, 0xD), (0x20, 0xE), (0x20, 0xF)
+0x05:
+  containerEntityId: 0x1E
+  containerEntityInstance: 0x03
+  # This record has contained entities as two ranges of entity instances
+  isList: "false"
+  # This record is linked with the below record; this record and the
+  # below record have the same container entity Id and container entity
+  # instance;
+  isLinked: "true"
+  entityId1: 0x20
+  entityInstance1: 0x1
+  entityId2: 0x20
+  entityInstance2: 0x3
+  entityId3: 0x20
+  entityInstance3: 0x6
+  entityId4: 0x20
+  entityInstance4: 0x8
+
+0x06:
+  containerEntityId: 0x1E
+  containerEntityInstance: 0x03
+  # This record has contained entities as two ranges of entity instances
+  isList: "false"
+  # This record is linked with the above record; this record and the
+  # above record have the same container entity Id and container entity
+  # instance;
+  isLinked: "true"
+  entityId1: 0x20
+  entityInstance1: 0xA
+  entityId2: 0x20
+  entityInstance2: 0xB
+  entityId3: 0x20
+  entityInstance3: 0xD
+  entityId4: 0x20
+  entityInstance4: 0xF
+```
diff --git a/scripts/fru-read-example.yaml b/scripts/fru-read-example.yaml
new file mode 100644
index 0000000..a186794
--- /dev/null
+++ b/scripts/fru-read-example.yaml
@@ -0,0 +1,139 @@
+# A YAML similar to this example would have to be generated, for eg with MRW
+# inputs and system configuration, to depict IPMI Fru information.
+#
+# This file maps IPMI properties to phosphor dbus inventory properties
+#
+# This YAML could help generate C++ code.
+# Format of the YAML:
+# Fruid:
+#   Associated Fru paths
+#     d-bus Interfaces
+#       d-bus Properties
+#         IPMI Fru mapping
+0:
+    /system:
+        entityID: 23
+        entityInstance: 1
+        interfaces:
+            xyz.openbmc_project.Inventory.Item:
+                PrettyName:
+                    IPMIFruProperty: Product Name
+                    IPMIFruSection: Product
+            xyz.openbmc_project.Inventory.Decorator.Asset:
+                Manufacturer:
+                    IPMIFruProperty: Manufacturer
+                    IPMIFruSection: Product
+                PartNumber:
+                    IPMIFruProperty: Part Number
+                    IPMIFruSection: Product
+                SerialNumber:
+                    IPMIFruProperty: Serial Number
+                    IPMIFruSection: Product
+                BuildDate:
+                    IPMIFruProperty: Mfg Date
+                    IPMIFruSection: Product
+            xyz.openbmc_project.Inventory.Decorator.Revision:
+                Version:
+                    IPMIFruProperty: Version
+                    IPMIFruSection: Product
+            xyz.openbmc_project.Inventory.Item.System:
+1:
+    /system/chassis/motherboard/dimm0:
+        entityID: 32
+        entityInstance: 1
+        interfaces:
+            xyz.openbmc_project.Inventory.Item:
+                PrettyName:
+                    IPMIFruProperty: Product Name
+                    IPMIFruSection: Product
+            xyz.openbmc_project.Inventory.Decorator.Asset:
+                Manufacturer:
+                    IPMIFruProperty: Manufacturer
+                    IPMIFruSection: Product
+                BuildDate:
+                    IPMIFruProperty: Mfg Date
+                    IPMIFruSection: Product
+                SerialNumber:
+                    IPMIFruProperty: Serial Number
+                    IPMIFruSection: Product
+                PartNumber:
+                    IPMIFruProperty: Part Number
+                    IPMIFruSection: Product
+            xyz.openbmc_project.Inventory.Decorator.Revision:
+                Version:
+                    IPMIFruProperty: Version
+                    IPMIFruSection: Product
+            xyz.openbmc_project.Inventory.Item.Dimm:
+2:
+    /system/chassis/motherboard/dimm1:
+        entityID: 32
+        entityInstance: 2
+        interfaces:
+            xyz.openbmc_project.Inventory.Item:
+                PrettyName:
+                    IPMIFruProperty: Product Name
+                    IPMIFruSection: Product
+            xyz.openbmc_project.Inventory.Decorator.Asset:
+                Manufacturer:
+                    IPMIFruProperty: Manufacturer
+                    IPMIFruSection: Product
+                BuildDate:
+                    IPMIFruProperty: Mfg Date
+                    IPMIFruSection: Product
+                SerialNumber:
+                    IPMIFruProperty: Serial Number
+                    IPMIFruSection: Product
+                PartNumber:
+                    IPMIFruProperty: Part Number
+                    IPMIFruSection: Product
+            xyz.openbmc_project.Inventory.Decorator.Revision:
+                Version:
+                    IPMIFruProperty: Version
+                    IPMIFruSection: Product
+            xyz.openbmc_project.Inventory.Item.Dimm:
+3:
+    /system/chassis/motherboard/cpu0:
+        entityID: 3
+        entityInstance: 1
+        interfaces:
+            xyz.openbmc_project.Inventory.Item:
+                PrettyName:
+                    IPMIFruProperty: Product Name
+                    IPMIFruSection: Board
+            xyz.openbmc_project.Inventory.Decorator.Asset:
+                BuildDate:
+                    IPMIFruProperty: Mfg Date
+                    IPMIFruSection: Board
+                SerialNumber:
+                    IPMIFruProperty: Serial Number
+                    IPMIFruSection: Board
+                PartNumber:
+                    IPMIFruProperty: Part Number
+                    IPMIFruSection: Board
+                Manufacturer:
+                    IPMIFruProperty: Manufacturer
+                    IPMIFruSection: Board
+            xyz.openbmc_project.Inventory.Item.Cpu:
+4:
+    /system/chassis/motherboard/cpu1:
+        entityID: 3
+        entityInstance: 2
+        interfaces:
+            xyz.openbmc_project.Inventory.Item:
+                PrettyName:
+                    IPMIFruProperty: Product Name
+                    IPMIFruSection: Board
+            xyz.openbmc_project.Inventory.Decorator.Asset:
+                BuildDate:
+                    IPMIFruProperty: Mfg Date
+                    IPMIFruSection: Board
+                SerialNumber:
+                    IPMIFruProperty: Serial Number
+                    IPMIFruSection: Board
+                PartNumber:
+                    IPMIFruProperty: Part Number
+                    IPMIFruSection: Board
+                Manufacturer:
+                    IPMIFruProperty: Manufacturer
+                    IPMIFruSection: Board
+            xyz.openbmc_project.Inventory.Item.Cpu:
diff --git a/scripts/fru_gen.py b/scripts/fru_gen.py
new file mode 100755
index 0000000..fd0941c
--- /dev/null
+++ b/scripts/fru_gen.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import sys
+
+import yaml
+from mako.template import Template
+
+
+def generate_cpp(inventory_yaml, output_dir):
+    with open(inventory_yaml, "r") as f:
+        ifile = yaml.safe_load(f)
+        if not isinstance(ifile, dict):
+            ifile = {}
+
+        # Render the mako template
+
+        t = Template(filename=os.path.join(script_dir, "readfru.cpp.mako"))
+
+        output_hpp = os.path.join(output_dir, "fru-read-gen.cpp")
+        with open(output_hpp, "w") as fd:
+            fd.write(t.render(fruDict=ifile))
+
+
+def main():
+    valid_commands = {"generate-cpp": generate_cpp}
+    parser = argparse.ArgumentParser(description="IPMI FRU map code generator")
+
+    parser.add_argument(
+        "-i",
+        "--inventory_yaml",
+        dest="inventory_yaml",
+        default="example.yaml",
+        help="input inventory yaml file to parse",
+    )
+
+    parser.add_argument(
+        "-o",
+        "--output-dir",
+        dest="outputdir",
+        default=".",
+        help="output directory",
+    )
+
+    parser.add_argument(
+        "command",
+        metavar="COMMAND",
+        type=str,
+        choices=valid_commands.keys(),
+        help="Command to run.",
+    )
+
+    args = parser.parse_args()
+
+    if not (os.path.isfile(args.inventory_yaml)):
+        sys.exit("Can not find input yaml file " + args.inventory_yaml)
+
+    function = valid_commands[args.command]
+    function(args.inventory_yaml, args.outputdir)
+
+
+if __name__ == "__main__":
+    script_dir = os.path.dirname(os.path.realpath(__file__))
+    main()
diff --git a/scripts/inventory-sensor-example.yaml b/scripts/inventory-sensor-example.yaml
new file mode 100644
index 0000000..5dde38f
--- /dev/null
+++ b/scripts/inventory-sensor-example.yaml
@@ -0,0 +1,340 @@
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0:
+    sensorID: 0xa6
+    sensorType: 0x0C
+    eventReadingType: 0x6F
+    offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm1:
+    sensorID: 0xa8
+    sensorType: 0x0C
+    eventReadingType: 0x6F
+    offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm10:
+    sensorID: 0xba
+    sensorType: 0x0C
+    eventReadingType: 0x6F
+    offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm11:
+    sensorID: 0xbc
+    sensorType: 0x0C
+    eventReadingType: 0x6F
+    offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm12:
+    sensorID: 0xbe
+    sensorType: 0x0C
+    eventReadingType: 0x6F
+    offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm13:
+    sensorID: 0xc0
+    sensorType: 0x0C
+    eventReadingType: 0x6F
+    offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm14:
+    sensorID: 0xc2
+    sensorType: 0x0C
+    eventReadingType: 0x6F
+    offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm15:
+    sensorID: 0xc4
+    sensorType: 0x0C
+    eventReadingType: 0x6F
+    offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm2:
+    sensorID: 0xaa
+    sensorType: 0x0C
+    eventReadingType: 0x6F
+    offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm3:
+    sensorID: 0xac
+    sensorType: 0x0C
+    eventReadingType: 0x6F
+    offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm4:
+    sensorID: 0xae
+    sensorType: 0x0C
+    eventReadingType: 0x6F
+    offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm5:
+    sensorID: 0xb0
+    sensorType: 0x0C
+    eventReadingType: 0x6F
+    offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm6:
+    sensorID: 0xb2
+    sensorType: 0x0C
+    eventReadingType: 0x6F
+    offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm7:
+    sensorID: 0xb4
+    sensorType: 0x0C
+    eventReadingType: 0x6F
+    offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm8:
+    sensorID: 0xb6
+    sensorType: 0x0C
+    eventReadingType: 0x6F
+    offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm9:
+    sensorID: 0xb8
+    sensorType: 0x0C
+    eventReadingType: 0x6F
+    offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0:
+    sensorID: 0x5a
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core0:
+    sensorID: 0x12
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core1:
+    sensorID: 0x15
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core2:
+    sensorID: 0x18
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core3:
+    sensorID: 0x1b
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core4:
+    sensorID: 0x1e
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core5:
+    sensorID: 0x21
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core6:
+    sensorID: 0x24
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core7:
+    sensorID: 0x27
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core8:
+    sensorID: 0x2a
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core9:
+    sensorID: 0x2d
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core10:
+    sensorID: 0x30
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core11:
+    sensorID: 0x33
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core12:
+    sensorID: 0x36
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core13:
+    sensorID: 0x39
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core14:
+    sensorID: 0x3c
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core15:
+    sensorID: 0x3f
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core16:
+    sensorID: 0x42
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core17:
+    sensorID: 0x45
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core18:
+    sensorID: 0x48
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core19:
+    sensorID: 0x4b
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core20:
+    sensorID: 0x4e
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core21:
+    sensorID: 0x51
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core22:
+    sensorID: 0x54
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core23:
+    sensorID: 0x57
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1:
+    sensorID: 0xa4
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core0:
+    sensorID: 0x5c
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core1:
+    sensorID: 0x5f
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core2:
+    sensorID: 0x62
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core3:
+    sensorID: 0x65
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core4:
+    sensorID: 0x68
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core5:
+    sensorID: 0x6b
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core6:
+    sensorID: 0x6e
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core7:
+    sensorID: 0x71
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core8:
+    sensorID: 0x74
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core9:
+    sensorID: 0x77
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core10:
+    sensorID: 0x7a
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core11:
+    sensorID: 0x7d
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core12:
+    sensorID: 0x80
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core13:
+    sensorID: 0x83
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core14:
+    sensorID: 0x86
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core15:
+    sensorID: 0x89
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core16:
+    sensorID: 0x8c
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core17:
+    sensorID: 0x8f
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core18:
+    sensorID: 0x92
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core19:
+    sensorID: 0x95
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core20:
+    sensorID: 0x98
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core21:
+    sensorID: 0x9b
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core22:
+    sensorID: 0x9e
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core23:
+    sensorID: 0xa1
+    sensorType: 0x07
+    eventReadingType: 0x6F
+    offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard:
+    sensorID: 0x0c
+    sensorType: 0xC7
+    eventReadingType: 0x03
+    offset: 0x00
+/xyz/openbmc_project/inventory/system:
+    sensorID: 0x01
+    sensorType: 0x12
+    eventReadingType: 0x6F
+    offset: 0x02
diff --git a/scripts/inventory-sensor.py b/scripts/inventory-sensor.py
new file mode 100755
index 0000000..8c7bc19
--- /dev/null
+++ b/scripts/inventory-sensor.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import sys
+
+import yaml
+from mako.template import Template
+
+
+def generate_cpp(sensor_yaml, output_dir):
+    with open(sensor_yaml, "r") as f:
+        ifile = yaml.safe_load(f)
+        if not isinstance(ifile, dict):
+            ifile = {}
+
+        # Render the mako template
+
+        t = Template(
+            filename=os.path.join(script_dir, "inventorysensor.cpp.mako")
+        )
+
+        output_cpp = os.path.join(output_dir, "inventory-sensor-gen.cpp")
+        with open(output_cpp, "w") as fd:
+            fd.write(t.render(sensorDict=ifile))
+
+
+def main():
+    valid_commands = {"generate-cpp": generate_cpp}
+    parser = argparse.ArgumentParser(
+        description="Inventory Object to IPMI SensorID code generator"
+    )
+
+    parser.add_argument(
+        "-i",
+        "--sensor_yaml",
+        dest="sensor_yaml",
+        default="example.yaml",
+        help="input sensor yaml file to parse",
+    )
+
+    parser.add_argument(
+        "-o",
+        "--output-dir",
+        dest="outputdir",
+        default=".",
+        help="output directory",
+    )
+
+    parser.add_argument(
+        "command",
+        metavar="COMMAND",
+        type=str,
+        choices=valid_commands.keys(),
+        help="Command to run.",
+    )
+
+    args = parser.parse_args()
+
+    if not (os.path.isfile(args.sensor_yaml)):
+        sys.exit("Can not find input yaml file " + args.sensor_yaml)
+
+    function = valid_commands[args.command]
+    function(args.sensor_yaml, args.outputdir)
+
+
+if __name__ == "__main__":
+    script_dir = os.path.dirname(os.path.realpath(__file__))
+    main()
diff --git a/scripts/inventorysensor.cpp.mako b/scripts/inventorysensor.cpp.mako
new file mode 100644
index 0000000..be3bb1e
--- /dev/null
+++ b/scripts/inventorysensor.cpp.mako
@@ -0,0 +1,26 @@
+## This file is a template.  The comment below is emitted
+## into the rendered file; feel free to edit this file.
+
+// !!! WARNING: This is a GENERATED Code..Please do NOT Edit !!!
+
+#include <ipmid/types.hpp>
+using namespace ipmi::sensor;
+
+extern const InvObjectIDMap __attribute__((init_priority(101))) invSensors = {
+% for key in sensorDict.keys():
+   % if key:
+{"${key}",
+    {
+<%
+       objectPath = sensorDict[key]
+       sensorID = objectPath["sensorID"]
+       sensorType = objectPath["sensorType"]
+       eventReadingType = objectPath["eventReadingType"]
+       offset = objectPath["offset"]
+%>
+        ${sensorID},${sensorType},${eventReadingType},${offset}
+    }
+},
+   % endif
+% endfor
+};
diff --git a/scripts/meson.build b/scripts/meson.build
new file mode 100644
index 0000000..c5fcae5
--- /dev/null
+++ b/scripts/meson.build
@@ -0,0 +1,50 @@
+# Generate Configuration Files from Yaml
+python_exe = find_program('python3', 'python')
+
+sensor_gen = custom_target(
+    'sensor-gen',
+    output: 'sensor-gen.cpp',
+    input: ['sensor_gen.py', get_option('sensor-yaml-gen')],
+    command: [
+        python_exe,
+        '@INPUT0@',
+        '-i',
+        '@INPUT1@',
+        '-o',
+        meson.current_build_dir(),
+        'generate-cpp',
+    ],
+)
+generated_src += sensor_gen
+
+invsensor_gen = custom_target(
+    'invsensor-gen',
+    output: 'inventory-sensor-gen.cpp',
+    input: ['inventory-sensor.py', get_option('invsensor-yaml-gen')],
+    command: [
+        python_exe,
+        '@INPUT0@',
+        '-i',
+        '@INPUT1@',
+        '-o',
+        meson.current_build_dir(),
+        'generate-cpp',
+    ],
+)
+generated_src += invsensor_gen
+
+fru_gen = custom_target(
+    'fru-gen',
+    output: 'fru-read-gen.cpp',
+    input: ['fru_gen.py', get_option('fru-yaml-gen')],
+    command: [
+        python_exe,
+        '@INPUT0@',
+        '-i',
+        '@INPUT1@',
+        '-o',
+        meson.current_build_dir(),
+        'generate-cpp',
+    ],
+)
+generated_src += fru_gen
diff --git a/scripts/readfru.cpp.mako b/scripts/readfru.cpp.mako
new file mode 100644
index 0000000..e7fbab8
--- /dev/null
+++ b/scripts/readfru.cpp.mako
@@ -0,0 +1,42 @@
+// !!! WARNING: This is a GENERATED Code..Please do NOT Edit !!!
+#include <iostream>
+#include "fruread.hpp"
+
+extern const FruMap __attribute__((init_priority(101))) frus = {
+% for key in fruDict.keys():
+   {${key},{
+<%
+    instanceList = fruDict[key]
+%>
+    % for instancePath,instanceInfo in instanceList.items():
+<%
+        entityID = instanceInfo["entityID"]
+        entityInstance = instanceInfo["entityInstance"]
+        interfaces = instanceInfo["interfaces"]
+%>
+         {${entityID}, ${entityInstance}, "${instancePath}",{
+         % for interface,properties in interfaces.items():
+             {"${interface}",{
+            % if properties:
+                % for dbus_property,property_value in properties.items():
+                    {"${dbus_property}",{
+                        "${property_value.get("IPMIFruSection", "")}",
+                        "${property_value.get("IPMIFruProperty", "")}",\
+<%
+    delimiter = property_value.get("IPMIFruValueDelimiter")
+    if not delimiter:
+        delimiter = ""
+    else:
+        delimiter = '\\' + hex(delimiter)[1:]
+%>
+                     "${delimiter}"
+                 }},
+                % endfor
+            %endif
+             }},
+         % endfor
+        }},
+    % endfor
+   }},
+% endfor
+};
diff --git a/scripts/sensor-example.yaml b/scripts/sensor-example.yaml
new file mode 100644
index 0000000..89430e4
--- /dev/null
+++ b/scripts/sensor-example.yaml
@@ -0,0 +1,194 @@
+# Sensor id is the key
+0x60:
+    sensorType: 0x07
+    sensorReadingType: 0x6F
+    # A "set" operation on this sensor should update this d-bus path.
+    # If the path is not specified, an MRW parser will try to determine the path
+    # based on the sensor id, on MRW based systems.  This typically happens for
+    # inventory items.
+    path: /org/open_power/control/occ0
+    # The interface that exposes method(s) to update the path above.
+    serviceInterface: org.freedesktop.DBus.Properties
+    # Where the sensor value is represented - assertion bits/reading/event data
+    readingType: assertion
+    # indicate if a sensor is READ/WRITE/RW.
+    # This particular sensor read and write operation is allowed
+    mutability: Mutability::Write|Mutability::Read
+    # Sensor name would be occ0
+    sensorNamePattern: nameLeaf
+    eventType: 0x6F
+    # All the d-bus interfaces : properties that must be updated for this path
+    interfaces:
+        # One or more interface dict entries
+        org.open_power.OCC.Status:
+            OccActive:
+                Offsets:
+                    # Sensor type specific offset
+                    0x06:
+                        # OccActive is a boolean
+                        type: "bool"
+                        # If offset 0x06 is asserted, set OccActive as false.
+                        assert: "false"
+                        deassert: "true"
+
+0x61:
+    sensorType: 0x04
+    sensorReadingType: 0x6F
+    # Inventory paths intentionally leave out the inventory root,
+    # /xyz/openbmc_project/inventory, because phosphor-inventory-manager
+    # adds that.
+    path: /system/chassis/motherboard/dimm1
+    serviceInterface: xyz.openbmc_project.Inventory.Manager
+    readingType: assertion
+    mutability: Mutability::Write|Mutability::Read
+    sensorNamePattern: nameLeaf
+    interfaces:
+        xyz.openbmc_project.State.Decorator.OperationalStatus:
+            Functional:
+                #Offsets contain the offsets in the sensor data.
+                Offsets:
+                    0x06:
+                        assert: true
+                        deassert: false
+                        type: bool
+                #Prereqs are pre-requisites for this property value to be true.
+                Prereqs:
+                    0x04:
+                        assert: false
+                        deassert: true
+                        type: bool
+        xyz.openbmc_project.Inventory.Item:
+            Present:
+                Offsets:
+                    0x04:
+                        assert: false
+                        deassert: true
+                        type: bool
+
+0x63:
+    interfaces:
+        xyz.openbmc_project.Control.Boot.RebootAttempts:
+            AttemptsLeft:
+                Offsets:
+                    0xFF:
+                        type: uint32_t
+    path: /xyz/openbmc_project/state/host0
+    # A special case of assertion, where the entire assert bitfield
+    # serves as the value, or reading. Hence, the offset above is intentionally
+    # 0xFF, to indicate not to check any specific bits in the assertion.
+    readingType: readingAssertion
+    # Sensor name would be AttemptsLeft
+    sensorNamePattern: nameProperty
+    sensorReadingType: 0x6F
+    mutability: Mutability::Write|Mutability::Read
+    sensorType: 0xC3
+    serviceInterface: org.freedesktop.DBus.Properties
+
+0x62:
+    interfaces:
+        xyz.openbmc_project.Control.Boot.RebootAttempts:
+            AttemptsLeft:
+                Offsets:
+                    0xFF:
+                        type: uint32_t
+    path: /xyz/openbmc_project/state/host1
+    readingType: readingAssertion
+    # set an explicit name for the sensor
+    sensorName: RebootAttempts
+    sensorReadingType: 0x6F
+    mutability: Mutability::Write|Mutability::Read
+    sensorType: 0xC3
+    serviceInterface: org.freedesktop.DBus.Properties
+
+0xD0:
+    sensorType: 0x01
+    path: /xyz/openbmc_project/sensors/temperature/fleeting0
+    sensorReadingType: 0x01
+    multiplierM: 511
+    offsetB: 0
+    bExp: 0
+    # Result exponent field in Type 1 SDR(2's complement, signed)
+    rExp: 0
+    # Applies for analog sensors, the actual reading value for the sensor is
+    # Value * 10^N
+    scale: -3
+    # Indicate Analog Data Format, Rate unit, Modifier unit and Percentage
+    sensorUnits1: 0x80
+    mutability: Mutability::Write|Mutability::Read
+    serviceInterface: org.freedesktop.DBus.Properties
+    readingType: readingData
+    sensorNamePattern: nameLeaf
+    interfaces:
+        xyz.openbmc_project.Sensor.Value:
+            Value:
+                Offsets:
+                    0xFF:
+                        type: int64_t
+
+0x54:
+    sensorType: 0x07
+    path: /system/chassis/motherboard/cpu0/core22
+    sensorReadingType: 0x6F
+    serviceInterface: xyz.openbmc_project.Inventory.Manager
+    readingType: assertion
+    mutability: Mutability::Write|Mutability::Read
+    # Sensor name would be cpu0_core22
+    sensorNamePattern: nameParentLeaf
+    interfaces:
+        xyz.openbmc_project.State.Decorator.OperationalStatus:
+            Functional:
+                Offsets:
+                    0x08:
+                        assert: false
+                        deassert: true
+                        type: bool
+                Prereqs:
+                    0x07:
+                        assert: true
+                        deassert: false
+        xyz.openbmc_project.Inventory.Item:
+            Present:
+                Offsets:
+                    0x07:
+                        assert: true
+                        deassert: false
+                        #The update will be skipped based on the value of skipOn
+                        #in this case if offset 0x07 is deasserted
+                        #the update will be skipped.
+                        skipOn: deassert
+                        type: bool
+
+0xC5:
+    sensorType: 0x17
+    path: /system/chassis/motherboard/gv100card0
+    sensorReadingType: 1
+    serviceInterface: xyz.openbmc_project.Inventory.Manager
+    readingType: assertion
+    mutability: Mutability::Write|Mutability::Read
+    sensorNamePattern: nameLeaf
+    interfaces:
+        xyz.openbmc_project.Inventory.Decorator.Replaceable:
+            FieldReplaceable:
+                Offsets:
+                    7:
+                        assert: true
+                        type: bool
+        xyz.openbmc_project.Inventory.Item:
+            Present:
+                Offsets:
+                    7:
+                        assert: true
+                        deassert: false
+                        type: bool
+        # Example of an interface with no attached properties
+        xyz.openbmc_project.Inventory.Item.Accelerator:
+        xyz.openbmc_project.State.Decorator.OperationalStatus:
+            Functional:
+                Offsets:
+                    8:
+                        assert: false
+                        type: bool
+                Prereqs:
+                    7:
+                        assert: true
+                        type: bool
diff --git a/scripts/sensor_gen.py b/scripts/sensor_gen.py
new file mode 100755
index 0000000..5da5909
--- /dev/null
+++ b/scripts/sensor_gen.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import sys
+
+import yaml
+from mako.template import Template
+
+
+def generate_cpp(sensor_yaml, output_dir):
+    with open(sensor_yaml, "r") as f:
+        ifile = yaml.safe_load(f)
+        if not isinstance(ifile, dict):
+            ifile = {}
+
+        # Render the mako template
+
+        t = Template(filename=os.path.join(script_dir, "writesensor.cpp.mako"))
+
+        output_cpp = os.path.join(output_dir, "sensor-gen.cpp")
+        with open(output_cpp, "w") as fd:
+            fd.write(t.render(sensorDict=ifile))
+
+
+def main():
+    valid_commands = {"generate-cpp": generate_cpp}
+    parser = argparse.ArgumentParser(
+        description="IPMI Sensor parser and code generator"
+    )
+
+    parser.add_argument(
+        "-i",
+        "--sensor_yaml",
+        dest="sensor_yaml",
+        default="example.yaml",
+        help="input sensor yaml file to parse",
+    )
+
+    parser.add_argument(
+        "-o",
+        "--output-dir",
+        dest="outputdir",
+        default=".",
+        help="output directory",
+    )
+
+    parser.add_argument(
+        "command",
+        metavar="COMMAND",
+        type=str,
+        choices=valid_commands.keys(),
+        help="Command to run.",
+    )
+
+    args = parser.parse_args()
+
+    if not (os.path.isfile(args.sensor_yaml)):
+        sys.exit("Can not find input yaml file " + args.sensor_yaml)
+
+    function = valid_commands[args.command]
+    function(args.sensor_yaml, args.outputdir)
+
+
+if __name__ == "__main__":
+    script_dir = os.path.dirname(os.path.realpath(__file__))
+    main()
diff --git a/scripts/writesensor.cpp.mako b/scripts/writesensor.cpp.mako
new file mode 100644
index 0000000..7123c37
--- /dev/null
+++ b/scripts/writesensor.cpp.mako
@@ -0,0 +1,182 @@
+## This file is a template.  The comment below is emitted
+## into the rendered file; feel free to edit this file.
+// !!! WARNING: This is a GENERATED Code..Please do NOT Edit !!!
+<%
+interfaceDict = {}
+sensorNameMaxLength = 16
+%>\
+%for key in sensorDict.keys():
+<%
+    sensor = sensorDict[key]
+    serviceInterface = sensor["serviceInterface"]
+    if serviceInterface == "org.freedesktop.DBus.Properties":
+        updateFunc = "set::"
+        getFunc = "get::"
+    elif serviceInterface == "xyz.openbmc_project.Inventory.Manager":
+        updateFunc = "notify::"
+        getFunc = "inventory::get::"
+    else:
+        assert "Un-supported interface: " + serviceInterface
+    endif
+    if serviceInterface not in interfaceDict:
+        interfaceDict[serviceInterface] = {}
+        interfaceDict[serviceInterface]["updateFunc"] = updateFunc
+        interfaceDict[serviceInterface]["getFunc"] = getFunc
+%>\
+% endfor
+
+#include "sensordatahandler.hpp"
+
+#include <ipmid/types.hpp>
+
+namespace ipmi {
+namespace sensor {
+
+extern const IdInfoMap __attribute__((init_priority(101))) sensors = {
+% for key in sensorDict.keys():
+   % if key:
+{${key},{
+<%
+       sensor = sensorDict[key]
+       interfaces = sensor["interfaces"]
+       path = sensor["path"]
+       serviceInterface = sensor["serviceInterface"]
+       sensorType = sensor["sensorType"]
+       entityID = sensor.get("entityID", 0)
+       instance = sensor.get("entityInstance", 0)
+       readingType = sensor["sensorReadingType"]
+       multiplier = sensor.get("multiplierM", 1)
+       offsetB = sensor.get("offsetB", 0)
+       bExp = sensor.get("bExp", 0)
+       rExp = sensor.get("rExp", 0)
+       sensorUnits1 = sensor.get("sensorUnits1", 0)
+       unit = sensor.get("unit", "")
+       scale = sensor.get("scale", 0)
+       hasScale = "true" if "scale" in sensor.keys() else "false"
+       valueReadingType = sensor["readingType"]
+       updateFunc = interfaceDict[serviceInterface]["updateFunc"]
+       updateFunc += sensor["readingType"]
+       getFunc = interfaceDict[serviceInterface]["getFunc"]
+       getFunc += sensor["readingType"]
+       sensorName = sensor.get("sensorName", None)
+       if sensorName:
+           assert len(sensorName) <= sensorNameMaxLength, \
+                   "sensor name '%s' is too long (%d bytes max)" % \
+                   (sensorName, sensorNameMaxLength)
+       else:
+           sensorNameFunc = "get::" + sensor.get("sensorNamePattern",
+                   "nameLeaf")
+
+       if "readingAssertion" == valueReadingType or "readingData" == valueReadingType:
+           for interface,properties in interfaces.items():
+               for dbus_property,property_value in properties.items():
+                   for offset,values in property_value["Offsets"].items():
+                       valueType = values["type"]
+           updateFunc = "set::" + valueReadingType + "<" + valueType + ">"
+           getFunc = "get::" + valueReadingType + "<" + valueType + ">"
+       sensorInterface = serviceInterface
+       if serviceInterface == "org.freedesktop.DBus.Properties":
+           sensorInterface = next(iter(interfaces))
+       mutability = sensor.get("mutability", "Mutability::Read")
+%>
+        .entityType = ${entityID},
+        .instance = ${instance},
+        .sensorType = ${sensorType},
+        .sensorPath = "${path}",
+        .sensorInterface = "${sensorInterface}",
+        .sensorReadingType = ${readingType},
+        .coefficientM = ${multiplier},
+        .coefficientB = ${offsetB},
+        .exponentB = ${bExp},
+        .scaledOffset = ${offsetB * pow(10,bExp)},
+        .exponentR = ${rExp},
+        .hasScale = ${hasScale},
+        .scale = ${scale},
+        .sensorUnits1 = ${sensorUnits1},
+        .unit = "${unit}",
+        .updateFunc = ${updateFunc},
+        .getFunc = ${getFunc},
+        .mutability = Mutability(${mutability}),
+    % if sensorName:
+        .sensorName = "${sensorName}",
+        .sensorNameFunc = nullptr,
+    % else:
+        .sensorName = "",
+        .sensorNameFunc = ${sensorNameFunc},
+    % endif
+        .propertyInterfaces = {
+    % for interface,properties in interfaces.items():
+            {"${interface}",{
+            % if properties:
+                % for dbus_property,property_value in properties.items():
+                    {"${dbus_property}",{
+<%
+try:
+    preReq = property_value["Prereqs"]
+except KeyError:
+    preReq = dict()
+%>\
+                    {
+                        % for preOffset,preValues in preReq.items():
+                        { ${preOffset},{
+                            % for name,value in preValues.items():
+                                % if name == "type":
+<%                              continue %>\
+                                % endif
+<%                          value = str(value).lower() %>\
+                                ${value},
+                            % endfor
+                            }
+                        },
+                        % endfor
+                    },
+                    {
+                    % for offset,values in property_value["Offsets"].items():
+                        { ${offset},{
+                            % if offset == 0xFF:
+                                }},
+<%                          continue %>\
+                            % endif
+<%                          valueType = values["type"] %>\
+<%
+try:
+    skip = values["skipOn"]
+    if skip == "assert":
+        skipVal = "SkipAssertion::ASSERT"
+    elif skip == "deassert":
+        skipVal = "SkipAssertion::DEASSERT"
+    else:
+        assert "Unknown skip value " + str(skip)
+except KeyError:
+    skipVal = "SkipAssertion::NONE"
+%>\
+                                ${skipVal},
+                        % for name,value in values.items():
+                            % if name == "type" or name == "skipOn":
+<%                          continue %>\
+                            % endif
+                            % if valueType == "string":
+                            std::string("${value}"),
+                            % elif valueType == "bool":
+<%                         value = str(value).lower() %>\
+                            ${value},
+                            % else:
+                            ${value},
+                            % endif
+                        % endfor
+                            }
+                        },
+                    % endfor
+                    }}},
+                % endfor
+            % endif
+            }},
+    % endfor
+     },
+}},
+   % endif
+% endfor
+};
+
+} // namespace sensor
+} // namespace ipmi
diff --git a/selutility.cpp b/selutility.cpp
new file mode 100644
index 0000000..944b21c
--- /dev/null
+++ b/selutility.cpp
@@ -0,0 +1,411 @@
+#include "config.h"
+
+#include "selutility.hpp"
+
+#include <ipmid/api.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <charconv>
+#include <chrono>
+#include <filesystem>
+#include <vector>
+
+extern const ipmi::sensor::InvObjectIDMap invSensors;
+using namespace phosphor::logging;
+using InternalFailure =
+    sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+
+namespace
+{
+
+constexpr auto systemEventRecord = 0x02;
+constexpr auto generatorID = 0x2000;
+constexpr auto eventMsgRevision = 0x04;
+constexpr auto assertEvent = 0x00;
+constexpr auto deassertEvent = 0x80;
+constexpr auto selDataSize = 3;
+constexpr auto oemCDDataSize = 9;
+constexpr auto oemEFDataSize = 13;
+
+constexpr auto propAdditionalData = "AdditionalData";
+constexpr auto propResolved = "Resolved";
+
+constexpr auto strEventDir = "EVENT_DIR";
+constexpr auto strGenerateId = "GENERATOR_ID";
+constexpr auto strRecordType = "RECORD_TYPE";
+constexpr auto strSensorData = "SENSOR_DATA";
+constexpr auto strSensorPath = "SENSOR_PATH";
+
+} // namespace
+
+namespace ipmi
+{
+
+namespace sel
+{
+
+namespace internal
+{
+
+inline bool isRecordOEM(uint8_t recordType)
+{
+    return recordType != systemEventRecord;
+}
+
+using entryDataMap = std::map<PropertyName, PropertyType>;
+
+int convert(const std::string_view& str, int base = 10)
+{
+    int ret = 0;
+    std::from_chars(str.data(), str.data() + str.size(), ret, base);
+    return ret;
+}
+
+// Convert the string to a vector of uint8_t, where the str is formatted as hex
+std::vector<uint8_t> convertVec(const std::string_view& str)
+{
+    std::vector<uint8_t> ret;
+    auto len = str.size() / 2;
+    ret.reserve(len);
+    for (size_t i = 0; i < len; ++i)
+    {
+        ret.emplace_back(
+            static_cast<uint8_t>(convert(str.substr(i * 2, 2), 16)));
+    }
+    return ret;
+}
+
+/** Construct OEM SEL record according to IPMI spec 32.2, 32.3. */
+void constructOEMSEL(uint8_t recordType, std::chrono::milliseconds timestamp,
+                     const AdditionalData& data, GetSELEntryResponse& record)
+{
+    auto dataIter = data.find(strSensorData);
+    assert(dataIter != data.end());
+    auto sensorData = convertVec(dataIter->second);
+    if (recordType >= 0xC0 && recordType < 0xE0)
+    {
+        record.event.oemCD.timeStamp = static_cast<uint32_t>(
+            std::chrono::duration_cast<std::chrono::seconds>(timestamp)
+                .count());
+        record.event.oemCD.recordType = recordType;
+        // The ManufactureID and OEM Defined are packed in the sensor data
+        // Fill the 9 bytes of Manufacture ID and oemDefined
+        memcpy(&record.event.oemCD.manufacturerID, sensorData.data(),
+               std::min(sensorData.size(), static_cast<size_t>(oemCDDataSize)));
+    }
+    else if (recordType >= 0xE0)
+    {
+        record.event.oemEF.recordType = recordType;
+        // The remaining 13 bytes are the OEM Defined data
+        memcpy(&record.event.oemEF.oemDefined, sensorData.data(),
+               std::min(sensorData.size(), static_cast<size_t>(oemEFDataSize)));
+    }
+}
+
+void constructSEL(uint8_t recordType, std::chrono::milliseconds timestamp,
+                  const AdditionalData& data, const entryDataMap&,
+                  GetSELEntryResponse& record)
+{
+    if (recordType != systemEventRecord)
+    {
+        lg2::error("Invalid recordType");
+        elog<InternalFailure>();
+    }
+
+    // Default values when there is no matched sensor
+    record.event.eventRecord.sensorType = 0;
+    record.event.eventRecord.sensorNum = 0xFF;
+    record.event.eventRecord.eventType = 0;
+
+    auto iter = data.find(strSensorPath);
+    assert(iter != data.end());
+    const auto& sensorPath = iter->second;
+    auto sensorIter = invSensors.find(sensorPath);
+
+    if (sensorIter != invSensors.end())
+    {
+        // There is a matched sensor
+        record.event.eventRecord.sensorType = sensorIter->second.sensorType;
+        record.event.eventRecord.sensorNum = sensorIter->second.sensorID;
+
+        iter = data.find(strEventDir);
+        assert(iter != data.end());
+        auto eventDir = static_cast<uint8_t>(convert(iter->second));
+        uint8_t assert = eventDir ? assertEvent : deassertEvent;
+        record.event.eventRecord.eventType =
+            assert | sensorIter->second.eventReadingType;
+    }
+    record.event.eventRecord.recordType = recordType;
+    record.event.eventRecord.timeStamp = static_cast<uint32_t>(
+        std::chrono::duration_cast<std::chrono::seconds>(timestamp).count());
+    iter = data.find(strGenerateId);
+    assert(iter != data.end());
+    record.event.eventRecord.generatorID =
+        static_cast<uint16_t>(convert(iter->second));
+    record.event.eventRecord.eventMsgRevision = eventMsgRevision;
+    iter = data.find(strSensorData);
+    assert(iter != data.end());
+    auto sensorData = convertVec(iter->second);
+    // The remaining 3 bytes are the sensor data
+    memcpy(&record.event.eventRecord.eventData1, sensorData.data(),
+           std::min(sensorData.size(), static_cast<size_t>(selDataSize)));
+}
+
+GetSELEntryResponse prepareSELEntry(
+    const std::string& objPath,
+    ipmi::sensor::InvObjectIDMap::const_iterator iter)
+{
+    GetSELEntryResponse record{};
+
+    sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+    auto service = ipmi::getService(bus, logEntryIntf, objPath);
+
+    // Read all the log entry properties.
+    auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(),
+                                          propIntf, "GetAll");
+    methodCall.append(logEntryIntf);
+
+    entryDataMap entryData;
+    try
+    {
+        auto reply = bus.call(methodCall);
+        reply.read(entryData);
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Error in reading logging property entries: {ERROR}",
+                   "ERROR", e);
+        elog<InternalFailure>();
+    }
+
+    // Read Id from the log entry.
+    static constexpr auto propId = "Id";
+    auto iterId = entryData.find(propId);
+    if (iterId == entryData.end())
+    {
+        lg2::error("Error in reading Id of logging entry");
+        elog<InternalFailure>();
+    }
+
+    // Read Timestamp from the log entry.
+    static constexpr auto propTimeStamp = "Timestamp";
+    auto iterTimeStamp = entryData.find(propTimeStamp);
+    if (iterTimeStamp == entryData.end())
+    {
+        lg2::error("Error in reading Timestamp of logging entry");
+        elog<InternalFailure>();
+    }
+    std::chrono::milliseconds chronoTimeStamp(
+        std::get<uint64_t>(iterTimeStamp->second));
+
+    bool isFromSELLogger = false;
+
+    // The recordID are with the same offset between different types,
+    // so we are safe to set the recordID here
+    record.event.eventRecord.recordID =
+        static_cast<uint16_t>(std::get<uint32_t>(iterId->second));
+
+    iterId = entryData.find(propAdditionalData);
+    if (iterId != entryData.end())
+    {
+        // Check if it's a SEL from phosphor-sel-logger which shall contain
+        // the record ID, etc
+        const auto& addData = std::get<AdditionalData>(iterId->second);
+        auto recordTypeIter = addData.find(strRecordType);
+        if (recordTypeIter != addData.end())
+        {
+            // It is a SEL from phosphor-sel-logger
+            isFromSELLogger = true;
+        }
+        else
+        {
+            // Not a SEL from phosphor-sel-logger, it shall have a valid
+            // invSensor
+            if (iter == invSensors.end())
+            {
+                lg2::error("System event sensor not found");
+                elog<InternalFailure>();
+            }
+        }
+    }
+
+    if (isFromSELLogger)
+    {
+        // It is expected to be a custom SEL entry
+        const auto& addData = std::get<AdditionalData>(iterId->second);
+        auto recordType =
+            static_cast<uint8_t>(convert(addData.find(strRecordType)->second));
+        auto isOEM = isRecordOEM(recordType);
+        if (isOEM)
+        {
+            constructOEMSEL(recordType, chronoTimeStamp, addData, record);
+        }
+        else
+        {
+            constructSEL(recordType, chronoTimeStamp, addData, entryData,
+                         record);
+        }
+    }
+    else
+    {
+        record.event.eventRecord.timeStamp = static_cast<uint32_t>(
+            std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp)
+                .count());
+
+        record.event.eventRecord.recordType = systemEventRecord;
+        record.event.eventRecord.generatorID = generatorID;
+        record.event.eventRecord.eventMsgRevision = eventMsgRevision;
+
+        record.event.eventRecord.sensorType = iter->second.sensorType;
+        record.event.eventRecord.sensorNum = iter->second.sensorID;
+        record.event.eventRecord.eventData1 = iter->second.eventOffset;
+
+        // Read Resolved from the log entry.
+        auto iterResolved = entryData.find(propResolved);
+        if (iterResolved == entryData.end())
+        {
+            lg2::error("Error in reading Resolved field of logging entry");
+            elog<InternalFailure>();
+        }
+
+        // Evaluate if the event is assertion or deassertion event
+        if (std::get<bool>(iterResolved->second))
+        {
+            record.event.eventRecord.eventType =
+                deassertEvent | iter->second.eventReadingType;
+        }
+        else
+        {
+            record.event.eventRecord.eventType = iter->second.eventReadingType;
+        }
+    }
+
+    return record;
+}
+
+} // namespace internal
+
+GetSELEntryResponse convertLogEntrytoSEL(const std::string& objPath)
+{
+    sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+
+    static constexpr auto assocIntf =
+        "xyz.openbmc_project.Association.Definitions";
+    static constexpr auto assocProp = "Associations";
+
+    std::vector<ipmi::Association> assocs;
+    try
+    {
+        auto service = ipmi::getService(bus, assocIntf, objPath);
+        auto propValue =
+            ipmi::getDbusProperty(bus, service, objPath, assocIntf, assocProp);
+        assocs = std::get<std::vector<ipmi::Association>>(propValue);
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Error in reading Associations interface: {ERROR}", "ERROR",
+                   e);
+        elog<InternalFailure>();
+    }
+
+    /*
+     * Check if the log entry has any callout associations, if there is a
+     * callout association try to match the inventory path to the corresponding
+     * IPMI sensor.
+     */
+    for (const auto& item : assocs)
+    {
+        if (std::get<0>(item).compare(CALLOUT_FWD_ASSOCIATION) == 0)
+        {
+            auto iter = invSensors.find(std::get<2>(item));
+            if (iter == invSensors.end())
+            {
+                iter = invSensors.find(BOARD_SENSOR);
+                if (iter == invSensors.end())
+                {
+                    lg2::error("Motherboard sensor not found");
+                    elog<InternalFailure>();
+                }
+            }
+
+            return internal::prepareSELEntry(objPath, iter);
+        }
+    }
+
+    // If there are no callout associations link the log entry to system event
+    // sensor
+    auto iter = invSensors.find(SYSTEM_SENSOR);
+    return internal::prepareSELEntry(objPath, iter);
+}
+
+std::chrono::seconds getEntryTimeStamp(const std::string& objPath)
+{
+    sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+
+    static constexpr auto propTimeStamp = "Timestamp";
+
+    uint64_t timeStamp;
+    try
+    {
+        auto service = ipmi::getService(bus, logEntryIntf, objPath);
+        auto propValue = ipmi::getDbusProperty(bus, service, objPath,
+                                               logEntryIntf, propTimeStamp);
+        timeStamp = std::get<uint64_t>(propValue);
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Error in reading Timestamp from Entry interface: {ERROR}",
+                   "ERROR", e);
+        elog<InternalFailure>();
+    }
+
+    std::chrono::milliseconds chronoTimeStamp(timeStamp);
+
+    return std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp);
+}
+
+void readLoggingObjectPaths(ObjectPaths& paths)
+{
+    sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+    auto depth = 0;
+    paths.clear();
+
+    auto mapperCall = bus.new_method_call(mapperBusName, mapperObjPath,
+                                          mapperIntf, "GetSubTreePaths");
+    mapperCall.append(logBasePath);
+    mapperCall.append(depth);
+    mapperCall.append(ObjectPaths({logEntryIntf}));
+
+    try
+    {
+        auto reply = bus.call(mapperCall);
+        reply.read(paths);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        if (strcmp(e.name(),
+                   "xyz.openbmc_project.Common.Error.ResourceNotFound"))
+        {
+            throw;
+        }
+    }
+
+    std::sort(paths.begin(), paths.end(),
+              [](const std::string& a, const std::string& b) {
+                  namespace fs = std::filesystem;
+                  fs::path pathA(a);
+                  fs::path pathB(b);
+                  auto idA = std::stoul(pathA.filename().string());
+                  auto idB = std::stoul(pathB.filename().string());
+
+                  return idA < idB;
+              });
+}
+
+} // namespace sel
+
+} // namespace ipmi
diff --git a/selutility.hpp b/selutility.hpp
new file mode 100644
index 0000000..c6ba6b0
--- /dev/null
+++ b/selutility.hpp
@@ -0,0 +1,210 @@
+#pragma once
+
+#include <ipmid/types.hpp>
+#include <sdbusplus/server.hpp>
+
+#include <chrono>
+#include <cstdint>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+
+namespace ipmi
+{
+
+namespace sel
+{
+
+static constexpr auto mapperBusName = "xyz.openbmc_project.ObjectMapper";
+static constexpr auto mapperObjPath = "/xyz/openbmc_project/object_mapper";
+static constexpr auto mapperIntf = "xyz.openbmc_project.ObjectMapper";
+
+static constexpr auto logWatchPath = "/xyz/openbmc_project/logging";
+static constexpr auto logBasePath = "/xyz/openbmc_project/logging/entry";
+static constexpr auto logEntryIntf = "xyz.openbmc_project.Logging.Entry";
+static constexpr auto logDeleteIntf = "xyz.openbmc_project.Object.Delete";
+
+static constexpr auto logObj = "/xyz/openbmc_project/logging";
+static constexpr auto logIntf = "xyz.openbmc_project.Collection.DeleteAll";
+static constexpr auto logDeleteAllMethod = "DeleteAll";
+
+static constexpr auto propIntf = "org.freedesktop.DBus.Properties";
+
+using ObjectPaths = std::vector<std::string>;
+using PropertyName = std::string;
+using Resolved = bool;
+using Id = uint32_t;
+using Timestamp = uint64_t;
+using Message = std::string;
+using AdditionalData = std::map<std::string, std::string>;
+using PropertyType =
+    std::variant<Resolved, Id, Timestamp, Message, AdditionalData>;
+
+static constexpr auto selVersion = 0x51;
+static constexpr auto invalidTimeStamp = 0xFFFFFFFF;
+
+static constexpr auto firstEntry = 0x0000;
+static constexpr auto lastEntry = 0xFFFF;
+static constexpr auto entireRecord = 0xFF;
+static constexpr auto selRecordSize = 16;
+
+namespace operationSupport
+{
+static constexpr bool overflow = false;
+static constexpr bool deleteSel = true;
+static constexpr bool partialAddSelEntry = false;
+static constexpr bool reserveSel = true;
+static constexpr bool getSelAllocationInfo = false;
+} // namespace operationSupport
+
+/** @struct GetSELEntryRequest
+ *
+ *  IPMI payload for Get SEL Entry command request.
+ */
+struct GetSELEntryRequest
+{
+    uint16_t reservationID; //!< Reservation ID.
+    uint16_t selRecordID;   //!< SEL Record ID.
+    uint8_t offset;         //!< Offset into record.
+    uint8_t readLength;     //!< Bytes to read.
+} __attribute__((packed));
+
+constexpr size_t SELRecordLength = 16;
+
+/** @struct SELEventRecord
+ *
+ * IPMI SEL Event Record
+ */
+struct SELEventRecord
+{
+    uint16_t recordID;        //!< Record ID.
+    uint8_t recordType;       //!< Record Type.
+    uint32_t timeStamp;       //!< Timestamp.
+    uint16_t generatorID;     //!< Generator ID.
+    uint8_t eventMsgRevision; //!< Event Message Revision.
+    uint8_t sensorType;       //!< Sensor Type.
+    uint8_t sensorNum;        //!< Sensor Number.
+    uint8_t eventType;        //!< Event Dir | Event Type.
+    uint8_t eventData1;       //!< Event Data 1.
+    uint8_t eventData2;       //!< Event Data 2.
+    uint8_t eventData3;       //!< Event Data 3.
+} __attribute__((packed));
+
+static_assert(sizeof(SELEventRecord) == SELRecordLength);
+
+/** @struct SELOEMRecordTypeCD
+ *
+ * IPMI SEL OEM Record - Type C0h-DFh
+ */
+struct SELOEMRecordTypeCD
+{
+    uint16_t recordID;         //!< Record ID.
+    uint8_t recordType;        //!< Record Type.
+    uint32_t timeStamp;        //!< Timestamp.
+    uint8_t manufacturerID[3]; //!< Manufacturer ID.
+    uint8_t oemDefined[6];     //!< OEM Defined data.
+} __attribute__((packed));
+
+static_assert(sizeof(SELOEMRecordTypeCD) == SELRecordLength);
+
+/** @struct SELOEMRecordTypeEF
+ *
+ * IPMI SEL OEM Record - Type E0h-FFh
+ */
+struct SELOEMRecordTypeEF
+{
+    uint16_t recordID;      //!< Record ID.
+    uint8_t recordType;     //!< Record Type.
+    uint8_t oemDefined[13]; //!< OEM Defined data.
+} __attribute__((packed));
+
+static_assert(sizeof(SELOEMRecordTypeEF) == SELRecordLength);
+
+union SELEventRecordFormat
+{
+    SELEventRecord eventRecord;
+    SELOEMRecordTypeCD oemCD;
+    SELOEMRecordTypeEF oemEF;
+};
+
+/** @struct GetSELEntryResponse
+ *
+ *  IPMI payload for Get SEL Entry command response.
+ */
+struct GetSELEntryResponse
+{
+    uint16_t nextRecordID;      //!< Next RecordID.
+    SELEventRecordFormat event; // !< The Event Record.
+} __attribute__((packed));
+
+static_assert(sizeof(GetSELEntryResponse) ==
+              SELRecordLength + sizeof(uint16_t));
+
+static constexpr auto initiateErase = 0xAA;
+static constexpr auto getEraseStatus = 0x00;
+static constexpr auto eraseComplete = 0x01;
+
+/** @brief Convert logging entry to SEL
+ *
+ *  @param[in] objPath - DBUS object path of the logging entry.
+ *
+ *  @return On success return the response of Get SEL entry command.
+ */
+GetSELEntryResponse convertLogEntrytoSEL(const std::string& objPath);
+
+/** @brief Get the timestamp of the log entry
+ *
+ *  @param[in] objPath - DBUS object path of the logging entry.
+ *
+ *  @return On success return the timestamp of the log entry as number of
+ *          seconds from epoch.
+ */
+std::chrono::seconds getEntryTimeStamp(const std::string& objPath);
+
+/** @brief Read the logging entry object paths
+ *
+ *  This API would read the logging dbus logging entry object paths and sorting
+ *  the filename in the numeric order. The paths is cleared before populating
+ *  the object paths.
+ *
+ *  @param[in,out] paths - sorted list of logging entry object paths.
+ *
+ *  @note This function is invoked when the Get SEL Info command or the Delete
+ *        SEL entry command is invoked. The Get SEL Entry command is preceded
+ *        typically by Get SEL Info command, so readLoggingObjectPaths is not
+ *        invoked before each Get SEL entry command.
+ */
+void readLoggingObjectPaths(ObjectPaths& paths);
+
+template <typename T>
+std::string toHexStr(const T& data)
+{
+    std::stringstream stream;
+    stream << std::hex << std::uppercase << std::setfill('0');
+    for (const auto& v : data)
+    {
+        stream << std::setw(2) << static_cast<int>(v);
+    }
+    return stream.str();
+}
+namespace internal
+{
+
+/** @brief Convert logging entry to SEL event record
+ *
+ *  @param[in] objPath - DBUS object path of the logging entry.
+ *  @param[in] iter - Iterator to the sensor data corresponding to the logging
+ *                    entry
+ *
+ *  @return On success return the SEL event record, throw an exception in case
+ *          of failure.
+ */
+GetSELEntryResponse prepareSELEntry(
+    const std::string& objPath,
+    ipmi::sensor::InvObjectIDMap::const_iterator iter);
+
+} // namespace internal
+
+} // namespace sel
+
+} // namespace ipmi
diff --git a/sensordatahandler.cpp b/sensordatahandler.cpp
new file mode 100644
index 0000000..3505dfb
--- /dev/null
+++ b/sensordatahandler.cpp
@@ -0,0 +1,419 @@
+#include "sensordatahandler.hpp"
+
+#include "sensorhandler.hpp"
+
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <bitset>
+#include <filesystem>
+#include <optional>
+
+namespace ipmi
+{
+namespace sensor
+{
+
+using namespace phosphor::logging;
+using InternalFailure =
+    sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+
+AssertionSet getAssertionSet(const SetSensorReadingReq& cmdData)
+{
+    Assertion assertionStates =
+        (static_cast<Assertion>(cmdData.assertOffset8_14)) << 8 |
+        cmdData.assertOffset0_7;
+    Deassertion deassertionStates =
+        (static_cast<Deassertion>(cmdData.deassertOffset8_14)) << 8 |
+        cmdData.deassertOffset0_7;
+    return std::make_pair(assertionStates, deassertionStates);
+}
+
+ipmi_ret_t updateToDbus(IpmiUpdateData& msg)
+{
+    sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+    try
+    {
+        auto serviceResponseMsg = bus.call(msg);
+    }
+    catch (const InternalFailure& e)
+    {
+        lg2::error("Error in D-Bus call: {ERROR}", "ERROR", e);
+        commit<InternalFailure>();
+        return IPMI_CC_UNSPECIFIED_ERROR;
+    }
+    return IPMI_CC_OK;
+}
+
+namespace get
+{
+
+SensorName nameParentLeaf(const Info& sensorInfo)
+{
+    const auto pos = sensorInfo.sensorPath.find_last_of('/');
+    const auto leaf = sensorInfo.sensorPath.substr(pos + 1);
+
+    const auto remaining = sensorInfo.sensorPath.substr(0, pos);
+
+    const auto parentPos = remaining.find_last_of('/');
+    auto parent = remaining.substr(parentPos + 1);
+
+    parent += "_" + leaf;
+    return parent;
+}
+
+GetSensorResponse mapDbusToAssertion(const Info& sensorInfo,
+                                     const InstancePath& path,
+                                     const DbusInterface& interface)
+{
+    sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+    GetSensorResponse response{};
+
+    enableScanning(&response);
+
+    auto service = ipmi::getService(bus, interface, path);
+
+    const auto& interfaceList = sensorInfo.propertyInterfaces;
+
+    for (const auto& interface : interfaceList)
+    {
+        for (const auto& property : interface.second)
+        {
+            auto propValue = ipmi::getDbusProperty(
+                bus, service, path, interface.first, property.first);
+
+            for (const auto& value : std::get<OffsetValueMap>(property.second))
+            {
+                if (propValue == value.second.assert)
+                {
+                    setOffset(value.first, &response);
+                    break;
+                }
+            }
+        }
+    }
+
+    return response;
+}
+
+GetSensorResponse mapDbusToEventdata2(const Info& sensorInfo)
+{
+    sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+    GetSensorResponse response{};
+
+    enableScanning(&response);
+
+    auto service = ipmi::getService(bus, sensorInfo.sensorInterface,
+                                    sensorInfo.sensorPath);
+
+    const auto& interfaceList = sensorInfo.propertyInterfaces;
+
+    for (const auto& interface : interfaceList)
+    {
+        for (const auto& property : interface.second)
+        {
+            auto propValue =
+                ipmi::getDbusProperty(bus, service, sensorInfo.sensorPath,
+                                      interface.first, property.first);
+
+            for (const auto& value : std::get<OffsetValueMap>(property.second))
+            {
+                if (propValue == value.second.assert)
+                {
+                    setReading(value.first, &response);
+                    break;
+                }
+            }
+        }
+    }
+
+    return response;
+}
+
+#ifndef FEATURE_SENSORS_CACHE
+GetSensorResponse assertion(const Info& sensorInfo)
+{
+    return mapDbusToAssertion(sensorInfo, sensorInfo.sensorPath,
+                              sensorInfo.sensorInterface);
+}
+
+GetSensorResponse eventdata2(const Info& sensorInfo)
+{
+    return mapDbusToEventdata2(sensorInfo);
+}
+#else
+std::optional<GetSensorResponse> assertion(uint8_t id, const Info& sensorInfo,
+                                           const PropertyMap& /*properties*/)
+{
+    // The assertion may contain multiple properties
+    // So we have to get the properties from DBus anyway
+    auto response = mapDbusToAssertion(sensorInfo, sensorInfo.sensorPath,
+                                       sensorInfo.sensorInterface);
+
+    if (!sensorCacheMap[id].has_value())
+    {
+        sensorCacheMap[id] = SensorData{};
+    }
+    sensorCacheMap[id]->response = response;
+    return response;
+}
+
+std::optional<GetSensorResponse> eventdata2(uint8_t id, const Info& sensorInfo,
+                                            const PropertyMap& /*properties*/)
+{
+    // The eventdata2 may contain multiple properties
+    // So we have to get the properties from DBus anyway
+    auto response = mapDbusToEventdata2(sensorInfo);
+
+    if (!sensorCacheMap[id].has_value())
+    {
+        sensorCacheMap[id] = SensorData{};
+    }
+    sensorCacheMap[id]->response = response;
+    return response;
+}
+
+#endif // FEATURE_SENSORS_CACHE
+
+} // namespace get
+
+namespace set
+{
+
+IpmiUpdateData makeDbusMsg(const std::string& updateInterface,
+                           const std::string& sensorPath,
+                           const std::string& command,
+                           const std::string& sensorInterface)
+{
+    sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+    using namespace std::string_literals;
+
+    auto dbusService = getService(bus, sensorInterface, sensorPath);
+
+    return bus.new_method_call(dbusService.c_str(), sensorPath.c_str(),
+                               updateInterface.c_str(), command.c_str());
+}
+
+ipmi_ret_t eventdata(const SetSensorReadingReq&, const Info& sensorInfo,
+                     uint8_t data)
+{
+    auto msg =
+        makeDbusMsg("org.freedesktop.DBus.Properties", sensorInfo.sensorPath,
+                    "Set", sensorInfo.sensorInterface);
+
+    const auto& interface = sensorInfo.propertyInterfaces.begin();
+    msg.append(interface->first);
+    for (const auto& property : interface->second)
+    {
+        msg.append(property.first);
+        const auto& iter = std::get<OffsetValueMap>(property.second).find(data);
+        if (iter == std::get<OffsetValueMap>(property.second).end())
+        {
+            lg2::error("Invalid event data");
+            return IPMI_CC_PARM_OUT_OF_RANGE;
+        }
+        msg.append(iter->second.assert);
+    }
+    return updateToDbus(msg);
+}
+
+ipmi_ret_t assertion(const SetSensorReadingReq& cmdData, const Info& sensorInfo)
+{
+    std::bitset<16> assertionSet(getAssertionSet(cmdData).first);
+    std::bitset<16> deassertionSet(getAssertionSet(cmdData).second);
+    auto bothSet = assertionSet ^ deassertionSet;
+
+    const auto& interface = sensorInfo.propertyInterfaces.begin();
+
+    for (const auto& property : interface->second)
+    {
+        std::optional<Value> tmp;
+        for (const auto& value : std::get<OffsetValueMap>(property.second))
+        {
+            if (bothSet.size() <= value.first || !bothSet.test(value.first))
+            {
+                // A BIOS shouldn't do this but ignore if they do.
+                continue;
+            }
+
+            if (assertionSet.test(value.first))
+            {
+                tmp = value.second.assert;
+                break;
+            }
+            if (deassertionSet.test(value.first))
+            {
+                tmp = value.second.deassert;
+                break;
+            }
+        }
+
+        if (tmp)
+        {
+            auto msg = makeDbusMsg("org.freedesktop.DBus.Properties",
+                                   sensorInfo.sensorPath, "Set",
+                                   sensorInfo.sensorInterface);
+            msg.append(interface->first);
+            msg.append(property.first);
+            msg.append(*tmp);
+
+            auto rc = updateToDbus(msg);
+            if (rc)
+            {
+                return rc;
+            }
+        }
+    }
+
+    return IPMI_CC_OK;
+}
+
+} // namespace set
+
+namespace notify
+{
+
+IpmiUpdateData makeDbusMsg(const std::string& updateInterface,
+                           const std::string&, const std::string& command,
+                           const std::string&)
+{
+    sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+    using namespace std::string_literals;
+
+    static const auto dbusPath = "/xyz/openbmc_project/inventory"s;
+    std::string dbusService = ipmi::getService(bus, updateInterface, dbusPath);
+
+    return bus.new_method_call(dbusService.c_str(), dbusPath.c_str(),
+                               updateInterface.c_str(), command.c_str());
+}
+
+ipmi_ret_t assertion(const SetSensorReadingReq& cmdData, const Info& sensorInfo)
+{
+    auto msg = makeDbusMsg(sensorInfo.sensorInterface, sensorInfo.sensorPath,
+                           "Notify", sensorInfo.sensorInterface);
+
+    std::bitset<16> assertionSet(getAssertionSet(cmdData).first);
+    std::bitset<16> deassertionSet(getAssertionSet(cmdData).second);
+    ipmi::sensor::ObjectMap objects;
+    ipmi::sensor::InterfaceMap interfaces;
+    for (const auto& interface : sensorInfo.propertyInterfaces)
+    {
+        // An interface with no properties - It is possible that the sensor
+        // object on DBUS implements a DBUS interface with no properties.
+        // Make sure we add the interface to the list if interfaces on the
+        // object with an empty property map.
+        if (interface.second.empty())
+        {
+            interfaces.emplace(interface.first, ipmi::sensor::PropertyMap{});
+            continue;
+        }
+        // For a property like functional state the result will be
+        // calculated based on the true value of all conditions.
+        for (const auto& property : interface.second)
+        {
+            ipmi::sensor::PropertyMap props;
+            bool valid = false;
+            auto result = true;
+            for (const auto& value : std::get<OffsetValueMap>(property.second))
+            {
+                if (assertionSet.test(value.first))
+                {
+                    // Skip update if skipOn is ASSERT
+                    if (SkipAssertion::ASSERT == value.second.skip)
+                    {
+                        return IPMI_CC_OK;
+                    }
+                    result = result && std::get<bool>(value.second.assert);
+                    valid = true;
+                }
+                else if (deassertionSet.test(value.first))
+                {
+                    // Skip update if skipOn is DEASSERT
+                    if (SkipAssertion::DEASSERT == value.second.skip)
+                    {
+                        return IPMI_CC_OK;
+                    }
+                    result = result && std::get<bool>(value.second.deassert);
+                    valid = true;
+                }
+            }
+            for (const auto& value :
+                 std::get<PreReqOffsetValueMap>(property.second))
+            {
+                if (assertionSet.test(value.first))
+                {
+                    result = result && std::get<bool>(value.second.assert);
+                }
+                else if (deassertionSet.test(value.first))
+                {
+                    result = result && std::get<bool>(value.second.deassert);
+                }
+            }
+            if (valid)
+            {
+                props.emplace(property.first, result);
+                interfaces.emplace(interface.first, std::move(props));
+            }
+        }
+    }
+
+    objects.emplace(sensorInfo.sensorPath, std::move(interfaces));
+    msg.append(std::move(objects));
+    return updateToDbus(msg);
+}
+
+} // namespace notify
+
+namespace inventory
+{
+
+namespace get
+{
+
+#ifndef FEATURE_SENSORS_CACHE
+
+GetSensorResponse assertion(const Info& sensorInfo)
+{
+    namespace fs = std::filesystem;
+
+    fs::path path{ipmi::sensor::inventoryRoot};
+    path += sensorInfo.sensorPath;
+
+    return ipmi::sensor::get::mapDbusToAssertion(
+        sensorInfo, path.string(),
+        sensorInfo.propertyInterfaces.begin()->first);
+}
+
+#else
+
+std::optional<GetSensorResponse> assertion(uint8_t id, const Info& sensorInfo,
+                                           const PropertyMap& /*properties*/)
+{
+    // The assertion may contain multiple properties
+    // So we have to get the properties from DBus anyway
+    namespace fs = std::filesystem;
+
+    fs::path path{ipmi::sensor::inventoryRoot};
+    path += sensorInfo.sensorPath;
+
+    auto response = ipmi::sensor::get::mapDbusToAssertion(
+        sensorInfo, path.string(),
+        sensorInfo.propertyInterfaces.begin()->first);
+
+    if (!sensorCacheMap[id].has_value())
+    {
+        sensorCacheMap[id] = SensorData{};
+    }
+    sensorCacheMap[id]->response = response;
+    return response;
+}
+
+#endif
+
+} // namespace get
+
+} // namespace inventory
+} // namespace sensor
+} // namespace ipmi
diff --git a/sensordatahandler.hpp b/sensordatahandler.hpp
new file mode 100644
index 0000000..1cedda5
--- /dev/null
+++ b/sensordatahandler.hpp
@@ -0,0 +1,670 @@
+#pragma once
+
+#include "config.h"
+
+#include "sensorhandler.hpp"
+
+#include <ipmid/api.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/message/types.hpp>
+
+#include <cmath>
+
+#ifdef FEATURE_SENSORS_CACHE
+
+extern ipmi::sensor::SensorCacheMap sensorCacheMap;
+
+// The signal's message type is 0x04 from DBus spec:
+// https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-messages
+static constexpr auto msgTypeSignal = 0x04;
+
+#endif
+
+namespace ipmi
+{
+namespace sensor
+{
+
+using Assertion = uint16_t;
+using Deassertion = uint16_t;
+using AssertionSet = std::pair<Assertion, Deassertion>;
+using Service = std::string;
+using Path = std::string;
+using Interface = std::string;
+using ServicePath = std::pair<Path, Service>;
+using Interfaces = std::vector<Interface>;
+using MapperResponseType = std::map<Path, std::map<Service, Interfaces>>;
+using PropertyMap = ipmi::PropertyMap;
+
+using namespace phosphor::logging;
+
+/** @brief Make assertion set from input data
+ *  @param[in] cmdData - Input sensor data
+ *  @return pair of assertion and deassertion set
+ */
+AssertionSet getAssertionSet(const SetSensorReadingReq& cmdData);
+
+/** @brief send the message to DBus
+ *  @param[in] msg - message to send
+ *  @return failure status in IPMI error code
+ */
+ipmi_ret_t updateToDbus(IpmiUpdateData& msg);
+
+namespace get
+{
+
+/** @brief Populate sensor name from the D-Bus property associated with the
+ *         sensor. In the example entry from the yaml, the name of the D-bus
+ *         property "AttemptsLeft" is the sensor name.
+ *
+ *         0x07:
+ *            sensorType: 195
+ *            path: /xyz/openbmc_project/state/host0
+ *            sensorReadingType: 0x6F
+ *            serviceInterface: org.freedesktop.DBus.Properties
+ *            readingType: readingAssertion
+ *            sensorNamePattern: nameProperty
+ *            interfaces:
+ *              xyz.openbmc_project.Control.Boot.RebootAttempts:
+ *                AttemptsLeft:
+ *                    Offsets:
+ *                        0xFF:
+ *                          type: uint32_t
+ *
+ *
+ *  @param[in] sensorInfo - Dbus info related to sensor.
+ *
+ *  @return On success return the sensor name for the sensor.
+ */
+inline SensorName nameProperty(const Info& sensorInfo)
+{
+    return sensorInfo.propertyInterfaces.begin()->second.begin()->first;
+}
+
+/** @brief Populate sensor name from the D-Bus object associated with the
+ *         sensor. If the object path is /system/chassis/motherboard/dimm0 then
+ *         the leaf dimm0 is considered as the sensor name.
+ *
+ *  @param[in] sensorInfo - Dbus info related to sensor.
+ *
+ *  @return On success return the sensor name for the sensor.
+ */
+inline SensorName nameLeaf(const Info& sensorInfo)
+{
+    return sensorInfo.sensorPath.substr(
+        sensorInfo.sensorPath.find_last_of('/') + 1,
+        sensorInfo.sensorPath.length());
+}
+
+/** @brief Populate sensor name from the D-Bus object associated with the
+ *         sensor and the property.
+ *         If the object path is /xyz/openbmc_project/inventory/Fan0 and
+ *         the property is Present, the leaf Fan0 and the Property is
+ *         joined to Fan0_Present as the sensor name.
+ *
+ *  @param[in] sensorInfo - Dbus info related to sensor.
+ *
+ *  @return On success return the sensor name for the sensor.
+ */
+inline SensorName nameLeafProperty(const Info& sensorInfo)
+{
+    return nameLeaf(sensorInfo) + "_" + nameProperty(sensorInfo);
+}
+
+/** @brief Populate sensor name from the D-Bus object associated with the
+ *         sensor. If the object path is /system/chassis/motherboard/cpu0/core0
+ *         then the sensor name is cpu0_core0. The leaf and the parent is put
+ *         together to get the sensor name.
+ *
+ *  @param[in] sensorInfo - Dbus info related to sensor.
+ *
+ *  @return On success return the sensor name for the sensor.
+ */
+SensorName nameParentLeaf(const Info& sensorInfo);
+
+/**
+ *  @brief Helper function to map the dbus info to sensor's assertion status
+ *         for the get sensor reading command.
+ *
+ *  @param[in] sensorInfo - Dbus info related to sensor.
+ *  @param[in] path - Dbus object path.
+ *  @param[in] interface - Dbus interface.
+ *
+ *  @return Response for get sensor reading command.
+ */
+GetSensorResponse mapDbusToAssertion(const Info& sensorInfo,
+                                     const InstancePath& path,
+                                     const DbusInterface& interface);
+
+#ifndef FEATURE_SENSORS_CACHE
+/**
+ *  @brief Map the Dbus info to sensor's assertion status in the Get sensor
+ *         reading command response.
+ *
+ *  @param[in] sensorInfo - Dbus info related to sensor.
+ *
+ *  @return Response for get sensor reading command.
+ */
+GetSensorResponse assertion(const Info& sensorInfo);
+
+/**
+ *  @brief Maps the Dbus info to the reading field in the Get sensor reading
+ *         command response.
+ *
+ *  @param[in] sensorInfo - Dbus info related to sensor.
+ *
+ *  @return Response for get sensor reading command.
+ */
+GetSensorResponse eventdata2(const Info& sensorInfo);
+
+/**
+ *  @brief readingAssertion is a case where the entire assertion state field
+ *         serves as the sensor value.
+ *
+ *  @tparam T - type of the dbus property related to sensor.
+ *  @param[in] sensorInfo - Dbus info related to sensor.
+ *
+ *  @return Response for get sensor reading command.
+ */
+template <typename T>
+GetSensorResponse readingAssertion(const Info& sensorInfo)
+{
+    sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+    GetSensorResponse response{};
+
+    enableScanning(&response);
+
+    auto service = ipmi::getService(bus, sensorInfo.sensorInterface,
+                                    sensorInfo.sensorPath);
+
+    auto propValue = ipmi::getDbusProperty(
+        bus, service, sensorInfo.sensorPath,
+        sensorInfo.propertyInterfaces.begin()->first,
+        sensorInfo.propertyInterfaces.begin()->second.begin()->first);
+
+    setAssertionBytes(static_cast<uint16_t>(std::get<T>(propValue)), &response);
+
+    return response;
+}
+
+/** @brief Map the Dbus info to the reading field in the Get sensor reading
+ *         command response
+ *
+ *  @tparam T - type of the dbus property related to sensor.
+ *  @param[in] sensorInfo - Dbus info related to sensor.
+ *
+ *  @return Response for get sensor reading command.
+ */
+template <typename T>
+GetSensorResponse readingData(const Info& sensorInfo)
+{
+    sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+
+    GetSensorResponse response{};
+
+    enableScanning(&response);
+
+    auto service = ipmi::getService(bus, sensorInfo.sensorInterface,
+                                    sensorInfo.sensorPath);
+
+#ifdef UPDATE_FUNCTIONAL_ON_FAIL
+    // Check the OperationalStatus interface for functional property
+    if (sensorInfo.propertyInterfaces.begin()->first ==
+        "xyz.openbmc_project.Sensor.Value")
+    {
+        bool functional = true;
+        try
+        {
+            auto funcValue = ipmi::getDbusProperty(
+                bus, service, sensorInfo.sensorPath,
+                "xyz.openbmc_project.State.Decorator.OperationalStatus",
+                "Functional");
+            functional = std::get<bool>(funcValue);
+        }
+        catch (...)
+        {
+            // No-op if Functional property could not be found since this
+            // check is only valid for Sensor.Value read for hwmonio
+        }
+        if (!functional)
+        {
+            throw SensorFunctionalError();
+        }
+    }
+#endif
+
+    auto propValue = ipmi::getDbusProperty(
+        bus, service, sensorInfo.sensorPath,
+        sensorInfo.propertyInterfaces.begin()->first,
+        sensorInfo.propertyInterfaces.begin()->second.begin()->first);
+
+    double value = std::get<T>(propValue) *
+                   std::pow(10, sensorInfo.scale - sensorInfo.exponentR);
+    int32_t rawData =
+        (value - sensorInfo.scaledOffset) / sensorInfo.coefficientM;
+
+    constexpr uint8_t sensorUnitsSignedBits = 2 << 6;
+    constexpr uint8_t signedDataFormat = 0x80;
+    // if sensorUnits1 [7:6] = 10b, sensor is signed
+    int32_t minClamp;
+    int32_t maxClamp;
+    if ((sensorInfo.sensorUnits1 & sensorUnitsSignedBits) == signedDataFormat)
+    {
+        minClamp = std::numeric_limits<int8_t>::lowest();
+        maxClamp = std::numeric_limits<int8_t>::max();
+    }
+    else
+    {
+        minClamp = std::numeric_limits<uint8_t>::lowest();
+        maxClamp = std::numeric_limits<uint8_t>::max();
+    }
+    setReading(static_cast<uint8_t>(std::clamp(rawData, minClamp, maxClamp)),
+               &response);
+
+    if (!std::isfinite(value))
+    {
+        response.readingOrStateUnavailable = 1;
+    }
+
+    bool critAlarmHigh;
+    try
+    {
+        critAlarmHigh = std::get<bool>(ipmi::getDbusProperty(
+            bus, service, sensorInfo.sensorPath,
+            "xyz.openbmc_project.Sensor.Threshold.Critical",
+            "CriticalAlarmHigh"));
+    }
+    catch (const std::exception& e)
+    {
+        critAlarmHigh = false;
+    }
+    bool critAlarmLow;
+    try
+    {
+        critAlarmLow = std::get<bool>(ipmi::getDbusProperty(
+            bus, service, sensorInfo.sensorPath,
+            "xyz.openbmc_project.Sensor.Threshold.Critical",
+            "CriticalAlarmLow"));
+    }
+    catch (const std::exception& e)
+    {
+        critAlarmLow = false;
+    }
+    bool warningAlarmHigh;
+    try
+    {
+        warningAlarmHigh = std::get<bool>(ipmi::getDbusProperty(
+            bus, service, sensorInfo.sensorPath,
+            "xyz.openbmc_project.Sensor.Threshold.Warning",
+            "WarningAlarmHigh"));
+    }
+    catch (const std::exception& e)
+    {
+        warningAlarmHigh = false;
+    }
+    bool warningAlarmLow;
+    try
+    {
+        warningAlarmLow = std::get<bool>(ipmi::getDbusProperty(
+            bus, service, sensorInfo.sensorPath,
+            "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningAlarmLow"));
+    }
+    catch (const std::exception& e)
+    {
+        warningAlarmLow = false;
+    }
+    response.thresholdLevelsStates =
+        (static_cast<uint8_t>(critAlarmHigh) << 3) |
+        (static_cast<uint8_t>(critAlarmLow) << 2) |
+        (static_cast<uint8_t>(warningAlarmHigh) << 1) |
+        (static_cast<uint8_t>(warningAlarmLow));
+
+    return response;
+}
+
+#else
+
+/**
+ *  @brief Map the Dbus info to sensor's assertion status in the Get sensor
+ *         reading command response.
+ *
+ *  @param[in] id - The sensor id
+ *  @param[in] sensorInfo - Dbus info related to sensor.
+ *  @param[in] msg - Dbus message from match callback.
+ *
+ *  @return Response for get sensor reading command.
+ */
+std::optional<GetSensorResponse> assertion(uint8_t id, const Info& sensorInfo,
+                                           const PropertyMap& properties);
+
+/**
+ *  @brief Maps the Dbus info to the reading field in the Get sensor reading
+ *         command response.
+ *
+ *  @param[in] id - The sensor id
+ *  @param[in] sensorInfo - Dbus info related to sensor.
+ *  @param[in] msg - Dbus message from match callback.
+ *
+ *  @return Response for get sensor reading command.
+ */
+std::optional<GetSensorResponse> eventdata2(uint8_t id, const Info& sensorInfo,
+                                            const PropertyMap& properties);
+
+/**
+ *  @brief readingAssertion is a case where the entire assertion state field
+ *         serves as the sensor value.
+ *
+ *  @tparam T - type of the dbus property related to sensor.
+ *  @param[in] id - The sensor id
+ *  @param[in] sensorInfo - Dbus info related to sensor.
+ *  @param[in] msg - Dbus message from match callback.
+ *
+ *  @return Response for get sensor reading command.
+ */
+template <typename T>
+std::optional<GetSensorResponse> readingAssertion(
+    uint8_t id, const Info& sensorInfo, const PropertyMap& properties)
+{
+    GetSensorResponse response{};
+    enableScanning(&response);
+
+    auto iter = properties.find(
+        sensorInfo.propertyInterfaces.begin()->second.begin()->first);
+    if (iter == properties.end())
+    {
+        return {};
+    }
+
+    setAssertionBytes(static_cast<uint16_t>(std::get<T>(iter->second)),
+                      &response);
+
+    if (!sensorCacheMap[id].has_value())
+    {
+        sensorCacheMap[id] = SensorData{};
+    }
+    sensorCacheMap[id]->response = response;
+    return response;
+}
+
+/** @brief Get sensor reading from the dbus message from match
+ *
+ *  @tparam T - type of the dbus property related to sensor.
+ *  @param[in] id - The sensor id
+ *  @param[in] sensorInfo - Dbus info related to sensor.
+ *  @param[in] msg - Dbus message from match callback.
+ *
+ *  @return Response for get sensor reading command.
+ */
+template <typename T>
+std::optional<GetSensorResponse> readingData(uint8_t id, const Info& sensorInfo,
+                                             const PropertyMap& properties)
+{
+    auto iter = properties.find("Functional");
+    if (iter != properties.end())
+    {
+        sensorCacheMap[id]->functional = std::get<bool>(iter->second);
+    }
+    iter = properties.find("Available");
+    if (iter != properties.end())
+    {
+        sensorCacheMap[id]->available = std::get<bool>(iter->second);
+    }
+#ifdef UPDATE_FUNCTIONAL_ON_FAIL
+    if (sensorCacheMap[id])
+    {
+        if (!sensorCacheMap[id]->functional)
+        {
+            throw SensorFunctionalError();
+        }
+    }
+#endif
+
+    GetSensorResponse response{};
+
+    enableScanning(&response);
+
+    iter = properties.find(
+        sensorInfo.propertyInterfaces.begin()->second.begin()->first);
+    if (iter == properties.end())
+    {
+        return {};
+    }
+
+    double value = std::get<T>(iter->second) *
+                   std::pow(10, sensorInfo.scale - sensorInfo.exponentR);
+    int32_t rawData =
+        (value - sensorInfo.scaledOffset) / sensorInfo.coefficientM;
+
+    constexpr uint8_t sensorUnitsSignedBits = 2 << 6;
+    constexpr uint8_t signedDataFormat = 0x80;
+    // if sensorUnits1 [7:6] = 10b, sensor is signed
+    if ((sensorInfo.sensorUnits1 & sensorUnitsSignedBits) == signedDataFormat)
+    {
+        if (rawData > std::numeric_limits<int8_t>::max() ||
+            rawData < std::numeric_limits<int8_t>::lowest())
+        {
+            lg2::error("Value out of range");
+            throw std::out_of_range("Value out of range");
+        }
+        setReading(static_cast<int8_t>(rawData), &response);
+    }
+    else
+    {
+        if (rawData > std::numeric_limits<uint8_t>::max() ||
+            rawData < std::numeric_limits<uint8_t>::lowest())
+        {
+            lg2::error("Value out of range");
+            throw std::out_of_range("Value out of range");
+        }
+        setReading(static_cast<uint8_t>(rawData), &response);
+    }
+
+    if (!std::isfinite(value))
+    {
+        response.readingOrStateUnavailable = 1;
+    }
+
+    if (!sensorCacheMap[id].has_value())
+    {
+        sensorCacheMap[id] = SensorData{};
+    }
+    sensorCacheMap[id]->response = response;
+
+    return response;
+}
+
+#endif // FEATURE_SENSORS_CACHE
+
+} // namespace get
+
+namespace set
+{
+
+/** @brief Make a DBus message for a Dbus call
+ *  @param[in] updateInterface - Interface name
+ *  @param[in] sensorPath - Path of the sensor
+ *  @param[in] command - command to be executed
+ *  @param[in] sensorInterface - DBus interface of sensor
+ *  @return a dbus message
+ */
+IpmiUpdateData makeDbusMsg(const std::string& updateInterface,
+                           const std::string& sensorPath,
+                           const std::string& command,
+                           const std::string& sensorInterface);
+
+/** @brief Update d-bus based on assertion type sensor data
+ *  @param[in] cmdData - input sensor data
+ *  @param[in] sensorInfo - sensor d-bus info
+ *  @return a IPMI error code
+ */
+ipmi_ret_t assertion(const SetSensorReadingReq& cmdData,
+                     const Info& sensorInfo);
+
+/** @brief Update d-bus based on a reading assertion
+ *  @tparam T - type of d-bus property mapping this sensor
+ *  @param[in] cmdData - input sensor data
+ *  @param[in] sensorInfo - sensor d-bus info
+ *  @return a IPMI error code
+ */
+template <typename T>
+ipmi_ret_t readingAssertion(const SetSensorReadingReq& cmdData,
+                            const Info& sensorInfo)
+{
+    auto msg =
+        makeDbusMsg("org.freedesktop.DBus.Properties", sensorInfo.sensorPath,
+                    "Set", sensorInfo.sensorInterface);
+
+    const auto& interface = sensorInfo.propertyInterfaces.begin();
+    msg.append(interface->first);
+    for (const auto& property : interface->second)
+    {
+        msg.append(property.first);
+        std::variant<T> value = static_cast<T>(
+            (cmdData.assertOffset8_14 << 8) | cmdData.assertOffset0_7);
+        msg.append(value);
+    }
+    return updateToDbus(msg);
+}
+
+/** @brief Update d-bus based on a discrete reading
+ *  @param[in] cmdData - input sensor data
+ *  @param[in] sensorInfo - sensor d-bus info
+ *  @return an IPMI error code
+ */
+template <typename T>
+ipmi_ret_t readingData(const SetSensorReadingReq& cmdData,
+                       const Info& sensorInfo)
+{
+    T raw_value = (sensorInfo.coefficientM * cmdData.reading) +
+                  sensorInfo.scaledOffset;
+
+    raw_value *= std::pow(10, sensorInfo.exponentR - sensorInfo.scale);
+
+    auto msg =
+        makeDbusMsg("org.freedesktop.DBus.Properties", sensorInfo.sensorPath,
+                    "Set", sensorInfo.sensorInterface);
+
+    const auto& interface = sensorInfo.propertyInterfaces.begin();
+    msg.append(interface->first);
+
+    for (const auto& property : interface->second)
+    {
+        msg.append(property.first);
+        std::variant<T> value = raw_value;
+        msg.append(value);
+    }
+    return updateToDbus(msg);
+}
+
+/** @brief Update d-bus based on eventdata type sensor data
+ *  @param[in] cmdData - input sensor data
+ *  @param[in] sensorInfo - sensor d-bus info
+ *  @return a IPMI error code
+ */
+ipmi_ret_t eventdata(const SetSensorReadingReq& cmdData, const Info& sensorInfo,
+                     uint8_t data);
+
+/** @brief Update d-bus based on eventdata1 type sensor data
+ *  @param[in] cmdData - input sensor data
+ *  @param[in] sensorInfo - sensor d-bus info
+ *  @return a IPMI error code
+ */
+inline ipmi_ret_t eventdata1(const SetSensorReadingReq& cmdData,
+                             const Info& sensorInfo)
+{
+    return eventdata(cmdData, sensorInfo, cmdData.eventData1);
+}
+
+/** @brief Update d-bus based on eventdata2 type sensor data
+ *  @param[in] cmdData - input sensor data
+ *  @param[in] sensorInfo - sensor d-bus info
+ *  @return a IPMI error code
+ */
+inline ipmi_ret_t eventdata2(const SetSensorReadingReq& cmdData,
+                             const Info& sensorInfo)
+{
+    return eventdata(cmdData, sensorInfo, cmdData.eventData2);
+}
+
+/** @brief Update d-bus based on eventdata3 type sensor data
+ *  @param[in] cmdData - input sensor data
+ *  @param[in] sensorInfo - sensor d-bus info
+ *  @return a IPMI error code
+ */
+inline ipmi_ret_t eventdata3(const SetSensorReadingReq& cmdData,
+                             const Info& sensorInfo)
+{
+    return eventdata(cmdData, sensorInfo, cmdData.eventData3);
+}
+
+} // namespace set
+
+namespace notify
+{
+
+/** @brief Make a DBus message for a Dbus call
+ *  @param[in] updateInterface - Interface name
+ *  @param[in] sensorPath - Path of the sensor
+ *  @param[in] command - command to be executed
+ *  @param[in] sensorInterface - DBus interface of sensor
+ *  @return a dbus message
+ */
+IpmiUpdateData makeDbusMsg(const std::string& updateInterface,
+                           const std::string& sensorPath,
+                           const std::string& command,
+                           const std::string& sensorInterface);
+
+/** @brief Update d-bus based on assertion type sensor data
+ *  @param[in] interfaceMap - sensor interface
+ *  @param[in] cmdData - input sensor data
+ *  @param[in] sensorInfo - sensor d-bus info
+ *  @return a IPMI error code
+ */
+ipmi_ret_t assertion(const SetSensorReadingReq& cmdData,
+                     const Info& sensorInfo);
+
+} // namespace notify
+
+namespace inventory
+{
+
+namespace get
+{
+
+#ifndef FEATURE_SENSORS_CACHE
+
+/**
+ *  @brief Map the Dbus info to sensor's assertion status in the Get sensor
+ *         reading command response.
+ *
+ *  @param[in] sensorInfo - Dbus info related to sensor.
+ *
+ *  @return Response for get sensor reading command.
+ */
+GetSensorResponse assertion(const Info& sensorInfo);
+
+#else
+
+/**
+ *  @brief Map the Dbus info to sensor's assertion status in the Get sensor
+ *         reading command response.
+ *
+ *  @param[in] id - The sensor id
+ *  @param[in] sensorInfo - Dbus info related to sensor.
+ *  @param[in] msg - Dbus message from match callback.
+ *
+ *  @return Response for get sensor reading command.
+ */
+std::optional<GetSensorResponse> assertion(uint8_t id, const Info& sensorInfo,
+                                           const PropertyMap& properties);
+
+#endif
+
+} // namespace get
+
+} // namespace inventory
+} // namespace sensor
+} // namespace ipmi
diff --git a/sensorhandler.cpp b/sensorhandler.cpp
new file mode 100644
index 0000000..134eaa6
--- /dev/null
+++ b/sensorhandler.cpp
@@ -0,0 +1,1588 @@
+#include "config.h"
+
+#include "sensorhandler.hpp"
+
+#include "fruread.hpp"
+
+#include <systemd/sd-bus.h>
+
+#include <ipmid/api.hpp>
+#include <ipmid/entity_map_json.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/Sensor/Value/server.hpp>
+
+#include <bitset>
+#include <cmath>
+#include <cstring>
+#include <set>
+
+static constexpr uint8_t fruInventoryDevice = 0x10;
+static constexpr uint8_t IPMIFruInventory = 0x02;
+static constexpr uint8_t BMCTargetAddress = 0x20;
+
+extern int updateSensorRecordFromSSRAESC(const void*);
+extern sd_bus* bus;
+
+namespace ipmi
+{
+namespace sensor
+{
+extern const IdInfoMap sensors;
+} // namespace sensor
+} // namespace ipmi
+
+extern const FruMap frus;
+
+using namespace phosphor::logging;
+using InternalFailure =
+    sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+
+void registerNetFnSenFunctions() __attribute__((constructor));
+
+struct sensorTypemap_t
+{
+    uint8_t number;
+    uint8_t typecode;
+    char dbusname[32];
+};
+
+sensorTypemap_t g_SensorTypeMap[] = {
+
+    {0x01, 0x6F, "Temp"},
+    {0x0C, 0x6F, "DIMM"},
+    {0x0C, 0x6F, "MEMORY_BUFFER"},
+    {0x07, 0x6F, "PROC"},
+    {0x07, 0x6F, "CORE"},
+    {0x07, 0x6F, "CPU"},
+    {0x0F, 0x6F, "BootProgress"},
+    {0xe9, 0x09, "OccStatus"}, // E9 is an internal mapping to handle sensor
+                               // type code os 0x09
+    {0xC3, 0x6F, "BootCount"},
+    {0x1F, 0x6F, "OperatingSystemStatus"},
+    {0x12, 0x6F, "SYSTEM_EVENT"},
+    {0xC7, 0x03, "SYSTEM"},
+    {0xC7, 0x03, "MAIN_PLANAR"},
+    {0xC2, 0x6F, "PowerCap"},
+    {0x0b, 0xCA, "PowerSupplyRedundancy"},
+    {0xDA, 0x03, "TurboAllowed"},
+    {0xD8, 0xC8, "PowerSupplyDerating"},
+    {0xFF, 0x00, ""},
+};
+
+struct sensor_data_t
+{
+    uint8_t sennum;
+} __attribute__((packed));
+
+using SDRCacheMap = std::unordered_map<uint8_t, get_sdr::SensorDataFullRecord>;
+SDRCacheMap sdrCacheMap __attribute__((init_priority(101)));
+
+using SensorThresholdMap =
+    std::unordered_map<uint8_t, get_sdr::GetSensorThresholdsResponse>;
+SensorThresholdMap sensorThresholdMap __attribute__((init_priority(101)));
+
+#ifdef FEATURE_SENSORS_CACHE
+std::map<uint8_t, std::unique_ptr<sdbusplus::bus::match_t>> sensorAddedMatches
+    __attribute__((init_priority(101)));
+std::map<uint8_t, std::unique_ptr<sdbusplus::bus::match_t>> sensorUpdatedMatches
+    __attribute__((init_priority(101)));
+std::map<uint8_t, std::unique_ptr<sdbusplus::bus::match_t>> sensorRemovedMatches
+    __attribute__((init_priority(101)));
+std::unique_ptr<sdbusplus::bus::match_t> sensorsOwnerMatch
+    __attribute__((init_priority(101)));
+
+ipmi::sensor::SensorCacheMap sensorCacheMap __attribute__((init_priority(101)));
+
+// It is needed to know which objects belong to which service, so that when a
+// service exits without interfacesRemoved signal, we could invaildate the cache
+// that is related to the service. It uses below two variables:
+// - idToServiceMap records which sensors are known to have a related service;
+// - serviceToIdMap maps a service to the sensors.
+using sensorIdToServiceMap = std::unordered_map<uint8_t, std::string>;
+sensorIdToServiceMap idToServiceMap __attribute__((init_priority(101)));
+
+using sensorServiceToIdMap = std::unordered_map<std::string, std::set<uint8_t>>;
+sensorServiceToIdMap serviceToIdMap __attribute__((init_priority(101)));
+
+static void fillSensorIdServiceMap(const std::string&,
+                                   const std::string& /*intf*/, uint8_t id,
+                                   const std::string& service)
+{
+    if (idToServiceMap.find(id) != idToServiceMap.end())
+    {
+        return;
+    }
+    idToServiceMap[id] = service;
+    serviceToIdMap[service].insert(id);
+}
+
+static void fillSensorIdServiceMap(const std::string& obj,
+                                   const std::string& intf, uint8_t id)
+{
+    if (idToServiceMap.find(id) != idToServiceMap.end())
+    {
+        return;
+    }
+    try
+    {
+        sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+        auto service = ipmi::getService(bus, intf, obj);
+        idToServiceMap[id] = service;
+        serviceToIdMap[service].insert(id);
+    }
+    catch (...)
+    {
+        // Ignore
+    }
+}
+
+void initSensorMatches()
+{
+    using namespace sdbusplus::bus::match::rules;
+    sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+    for (const auto& s : ipmi::sensor::sensors)
+    {
+        sensorAddedMatches.emplace(
+            s.first,
+            std::make_unique<sdbusplus::bus::match_t>(
+                bus, interfacesAdded() + argNpath(0, s.second.sensorPath),
+                [id = s.first, obj = s.second.sensorPath,
+                 intf = s.second.propertyInterfaces.begin()->first](
+                    auto& /*msg*/) { fillSensorIdServiceMap(obj, intf, id); }));
+        sensorRemovedMatches.emplace(
+            s.first,
+            std::make_unique<sdbusplus::bus::match_t>(
+                bus, interfacesRemoved() + argNpath(0, s.second.sensorPath),
+                [id = s.first](auto& /*msg*/) {
+                    // Ideally this should work.
+                    // But when a service is terminated or crashed, it does not
+                    // emit interfacesRemoved signal. In that case it's handled
+                    // by sensorsOwnerMatch
+                    sensorCacheMap[id].reset();
+                }));
+        sensorUpdatedMatches.emplace(
+            s.first,
+            std::make_unique<sdbusplus::bus::match_t>(
+                bus,
+                type::signal() + path(s.second.sensorPath) +
+                    member("PropertiesChanged"s) +
+                    interface("org.freedesktop.DBus.Properties"s),
+                [&s](auto& msg) {
+                    fillSensorIdServiceMap(
+                        s.second.sensorPath,
+                        s.second.propertyInterfaces.begin()->first, s.first);
+                    try
+                    {
+                        // This is signal callback
+                        std::string interfaceName;
+                        msg.read(interfaceName);
+                        ipmi::PropertyMap props;
+                        msg.read(props);
+                        s.second.getFunc(s.first, s.second, props);
+                    }
+                    catch (const std::exception& e)
+                    {
+                        sensorCacheMap[s.first].reset();
+                    }
+                }));
+    }
+    sensorsOwnerMatch = std::make_unique<sdbusplus::bus::match_t>(
+        bus, nameOwnerChanged(), [](auto& msg) {
+            std::string name;
+            std::string oldOwner;
+            std::string newOwner;
+            msg.read(name, oldOwner, newOwner);
+
+            if (!name.empty() && newOwner.empty())
+            {
+                // The service exits
+                const auto it = serviceToIdMap.find(name);
+                if (it == serviceToIdMap.end())
+                {
+                    return;
+                }
+                for (const auto& id : it->second)
+                {
+                    // Invalidate cache
+                    sensorCacheMap[id].reset();
+                }
+            }
+        });
+}
+#endif
+
+// Use a lookup table to find the interface name of a specific sensor
+// This will be used until an alternative is found.  this is the first
+// step for mapping IPMI
+int find_openbmc_path(uint8_t num, dbus_interface_t* interface)
+{
+    const auto& sensor_it = ipmi::sensor::sensors.find(num);
+    if (sensor_it == ipmi::sensor::sensors.end())
+    {
+        // The sensor map does not contain the sensor requested
+        return -EINVAL;
+    }
+
+    const auto& info = sensor_it->second;
+
+    std::string serviceName{};
+    try
+    {
+        sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+        serviceName =
+            ipmi::getService(bus, info.sensorInterface, info.sensorPath);
+    }
+    catch (const sdbusplus::exception_t&)
+    {
+        std::fprintf(stderr, "Failed to get %s busname: %s\n",
+                     info.sensorPath.c_str(), serviceName.c_str());
+        return -EINVAL;
+    }
+
+    interface->sensortype = info.sensorType;
+    strcpy(interface->bus, serviceName.c_str());
+    strcpy(interface->path, info.sensorPath.c_str());
+    // Take the interface name from the beginning of the DbusInterfaceMap. This
+    // works for the Value interface but may not suffice for more complex
+    // sensors.
+    // tracked https://github.com/openbmc/phosphor-host-ipmid/issues/103
+    strcpy(interface->interface,
+           info.propertyInterfaces.begin()->first.c_str());
+    interface->sensornumber = num;
+
+    return 0;
+}
+
+/////////////////////////////////////////////////////////////////////
+//
+// Routines used by ipmi commands wanting to interact on the dbus
+//
+/////////////////////////////////////////////////////////////////////
+int set_sensor_dbus_state_s(uint8_t number, const char* method,
+                            const char* value)
+{
+    dbus_interface_t a;
+    int r;
+    sd_bus_error error = SD_BUS_ERROR_NULL;
+    sd_bus_message* m = nullptr;
+
+    r = find_openbmc_path(number, &a);
+
+    if (r < 0)
+    {
+        std::fprintf(stderr, "Failed to find Sensor 0x%02x\n", number);
+        return 0;
+    }
+
+    r = sd_bus_message_new_method_call(bus, &m, a.bus, a.path, a.interface,
+                                       method);
+    if (r < 0)
+    {
+        std::fprintf(stderr, "Failed to create a method call: %s",
+                     strerror(-r));
+        goto final;
+    }
+
+    r = sd_bus_message_append(m, "v", "s", value);
+    if (r < 0)
+    {
+        std::fprintf(stderr, "Failed to create a input parameter: %s",
+                     strerror(-r));
+        goto final;
+    }
+
+    r = sd_bus_call(bus, m, 0, &error, nullptr);
+    if (r < 0)
+    {
+        std::fprintf(stderr, "Failed to call the method: %s", strerror(-r));
+    }
+
+final:
+    sd_bus_error_free(&error);
+    m = sd_bus_message_unref(m);
+
+    return 0;
+}
+int set_sensor_dbus_state_y(uint8_t number, const char* method,
+                            const uint8_t value)
+{
+    dbus_interface_t a;
+    int r;
+    sd_bus_error error = SD_BUS_ERROR_NULL;
+    sd_bus_message* m = nullptr;
+
+    r = find_openbmc_path(number, &a);
+
+    if (r < 0)
+    {
+        std::fprintf(stderr, "Failed to find Sensor 0x%02x\n", number);
+        return 0;
+    }
+
+    r = sd_bus_message_new_method_call(bus, &m, a.bus, a.path, a.interface,
+                                       method);
+    if (r < 0)
+    {
+        std::fprintf(stderr, "Failed to create a method call: %s",
+                     strerror(-r));
+        goto final;
+    }
+
+    r = sd_bus_message_append(m, "v", "i", value);
+    if (r < 0)
+    {
+        std::fprintf(stderr, "Failed to create a input parameter: %s",
+                     strerror(-r));
+        goto final;
+    }
+
+    r = sd_bus_call(bus, m, 0, &error, nullptr);
+    if (r < 0)
+    {
+        std::fprintf(stderr, "12 Failed to call the method: %s", strerror(-r));
+    }
+
+final:
+    sd_bus_error_free(&error);
+    m = sd_bus_message_unref(m);
+
+    return 0;
+}
+
+uint8_t dbus_to_sensor_type(char* p)
+{
+    sensorTypemap_t* s = g_SensorTypeMap;
+    char r = 0;
+    while (s->number != 0xFF)
+    {
+        if (!strcmp(s->dbusname, p))
+        {
+            r = s->typecode;
+            break;
+        }
+        s++;
+    }
+
+    if (s->number == 0xFF)
+        printf("Failed to find Sensor Type %s\n", p);
+
+    return r;
+}
+
+uint8_t get_type_from_interface(dbus_interface_t dbus_if)
+{
+    uint8_t type;
+
+    // This is where sensors that do not exist in dbus but do
+    // exist in the host code stop.  This should indicate it
+    // is not a supported sensor
+    if (dbus_if.interface[0] == 0)
+    {
+        return 0;
+    }
+
+    // Fetch type from interface itself.
+    if (dbus_if.sensortype != 0)
+    {
+        type = dbus_if.sensortype;
+    }
+    else
+    {
+        // Non InventoryItems
+        char* p = strrchr(dbus_if.path, '/');
+        type = dbus_to_sensor_type(p + 1);
+    }
+
+    return type;
+}
+
+// Replaces find_sensor
+uint8_t find_type_for_sensor_number(uint8_t num)
+{
+    int r;
+    dbus_interface_t dbus_if;
+    r = find_openbmc_path(num, &dbus_if);
+    if (r < 0)
+    {
+        std::fprintf(stderr, "Could not find sensor %d\n", num);
+        return 0;
+    }
+    return get_type_from_interface(dbus_if);
+}
+
+/**
+ *  @brief implements the get sensor type command.
+ *  @param - sensorNumber
+ *
+ *  @return IPMI completion code plus response data on success.
+ *   - sensorType
+ *   - eventType
+ **/
+
+ipmi::RspType<uint8_t, // sensorType
+              uint8_t  // eventType
+              >
+    ipmiGetSensorType(uint8_t sensorNumber)
+{
+    const auto it = ipmi::sensor::sensors.find(sensorNumber);
+    if (it == ipmi::sensor::sensors.end())
+    {
+        // The sensor map does not contain the sensor requested
+        return ipmi::responseSensorInvalid();
+    }
+
+    const auto& info = it->second;
+    uint8_t sensorType = info.sensorType;
+    uint8_t eventType = info.sensorReadingType;
+
+    return ipmi::responseSuccess(sensorType, eventType);
+}
+
+const std::set<std::string> analogSensorInterfaces = {
+    "xyz.openbmc_project.Sensor.Value",
+    "xyz.openbmc_project.Control.FanPwm",
+};
+
+bool isAnalogSensor(const std::string& interface)
+{
+    return (analogSensorInterfaces.count(interface));
+}
+
+/**
+@brief This command is used to set sensorReading.
+
+@param
+    -  sensorNumber
+    -  operation
+    -  reading
+    -  assertOffset0_7
+    -  assertOffset8_14
+    -  deassertOffset0_7
+    -  deassertOffset8_14
+    -  eventData1
+    -  eventData2
+    -  eventData3
+
+@return completion code on success.
+**/
+
+ipmi::RspType<> ipmiSetSensorReading(
+    uint8_t sensorNumber, uint8_t operation, uint8_t reading,
+    uint8_t assertOffset0_7, uint8_t assertOffset8_14,
+    uint8_t deassertOffset0_7, uint8_t deassertOffset8_14, uint8_t eventData1,
+    uint8_t eventData2, uint8_t eventData3)
+{
+    lg2::debug("IPMI SET_SENSOR, sensorNumber: {SENSOR_NUM}", "SENSOR_NUM",
+               lg2::hex, sensorNumber);
+
+    if (sensorNumber == 0xFF)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+    ipmi::sensor::SetSensorReadingReq cmdData;
+
+    cmdData.number = sensorNumber;
+    cmdData.operation = operation;
+    cmdData.reading = reading;
+    cmdData.assertOffset0_7 = assertOffset0_7;
+    cmdData.assertOffset8_14 = assertOffset8_14;
+    cmdData.deassertOffset0_7 = deassertOffset0_7;
+    cmdData.deassertOffset8_14 = deassertOffset8_14;
+    cmdData.eventData1 = eventData1;
+    cmdData.eventData2 = eventData2;
+    cmdData.eventData3 = eventData3;
+
+    // Check if the Sensor Number is present
+    const auto iter = ipmi::sensor::sensors.find(sensorNumber);
+    if (iter == ipmi::sensor::sensors.end())
+    {
+        updateSensorRecordFromSSRAESC(&sensorNumber);
+        return ipmi::responseSuccess();
+    }
+
+    try
+    {
+        if (ipmi::sensor::Mutability::Write !=
+            (iter->second.mutability & ipmi::sensor::Mutability::Write))
+        {
+            lg2::error("Sensor Set operation is not allowed, "
+                       "sensorNumber: {SENSOR_NUM}",
+                       "SENSOR_NUM", lg2::hex, sensorNumber);
+            return ipmi::responseIllegalCommand();
+        }
+        auto ipmiRC = iter->second.updateFunc(cmdData, iter->second);
+        return ipmi::response(ipmiRC);
+    }
+    catch (const InternalFailure& e)
+    {
+        lg2::error("Set sensor failed, sensorNumber: {SENSOR_NUM}",
+                   "SENSOR_NUM", lg2::hex, sensorNumber);
+        commit<InternalFailure>();
+        return ipmi::responseUnspecifiedError();
+    }
+    catch (const std::runtime_error& e)
+    {
+        lg2::error("runtime error: {ERROR}", "ERROR", e);
+        return ipmi::responseUnspecifiedError();
+    }
+}
+
+/** @brief implements the get sensor reading command
+ *  @param sensorNum - sensor number
+ *
+ *  @returns IPMI completion code plus response data
+ *   - senReading           - sensor reading
+ *   - reserved
+ *   - readState            - sensor reading state enabled
+ *   - senScanState         - sensor scan state disabled
+ *   - allEventMessageState - all Event message state disabled
+ *   - assertionStatesLsb   - threshold levels states
+ *   - assertionStatesMsb   - discrete reading sensor states
+ */
+ipmi::RspType<uint8_t, // sensor reading
+
+              uint5_t, // reserved
+              bool,    // reading state
+              bool,    // 0 = sensor scanning state disabled
+              bool,    // 0 = all event messages disabled
+
+              uint8_t, // threshold levels states
+              uint8_t  // discrete reading sensor states
+              >
+    ipmiSensorGetSensorReading([[maybe_unused]] ipmi::Context::ptr& ctx,
+                               uint8_t sensorNum)
+{
+    if (sensorNum == 0xFF)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    const auto iter = ipmi::sensor::sensors.find(sensorNum);
+    if (iter == ipmi::sensor::sensors.end())
+    {
+        return ipmi::responseSensorInvalid();
+    }
+    if (ipmi::sensor::Mutability::Read !=
+        (iter->second.mutability & ipmi::sensor::Mutability::Read))
+    {
+        return ipmi::responseIllegalCommand();
+    }
+
+    try
+    {
+#ifdef FEATURE_SENSORS_CACHE
+        auto& sensorData = sensorCacheMap[sensorNum];
+        if (!sensorData.has_value())
+        {
+            // No cached value, try read it
+            std::string service;
+            boost::system::error_code ec;
+            const auto& sensorInfo = iter->second;
+            ec = ipmi::getService(ctx, sensorInfo.sensorInterface,
+                                  sensorInfo.sensorPath, service);
+            if (ec)
+            {
+                return ipmi::responseUnspecifiedError();
+            }
+            fillSensorIdServiceMap(sensorInfo.sensorPath,
+                                   sensorInfo.propertyInterfaces.begin()->first,
+                                   iter->first, service);
+
+            ipmi::PropertyMap props;
+            ec = ipmi::getAllDbusProperties(
+                ctx, service, sensorInfo.sensorPath,
+                sensorInfo.propertyInterfaces.begin()->first, props);
+            if (ec)
+            {
+                fprintf(stderr, "Failed to get sensor %s, %d: %s\n",
+                        sensorInfo.sensorPath.c_str(), ec.value(),
+                        ec.message().c_str());
+                // Intitilizing with default values
+                constexpr uint8_t senReading = 0;
+                constexpr uint5_t reserved{0};
+                constexpr bool readState = true;
+                constexpr bool senScanState = false;
+                constexpr bool allEventMessageState = false;
+                constexpr uint8_t assertionStatesLsb = 0;
+                constexpr uint8_t assertionStatesMsb = 0;
+
+                return ipmi::responseSuccess(
+                    senReading, reserved, readState, senScanState,
+                    allEventMessageState, assertionStatesLsb,
+                    assertionStatesMsb);
+            }
+            sensorInfo.getFunc(sensorNum, sensorInfo, props);
+        }
+        return ipmi::responseSuccess(
+            sensorData->response.reading, uint5_t(0),
+            sensorData->response.readingOrStateUnavailable,
+            sensorData->response.scanningEnabled,
+            sensorData->response.allEventMessagesEnabled,
+            sensorData->response.thresholdLevelsStates,
+            sensorData->response.discreteReadingSensorStates);
+
+#else
+        ipmi::sensor::GetSensorResponse getResponse =
+            iter->second.getFunc(iter->second);
+
+        return ipmi::responseSuccess(
+            getResponse.reading, uint5_t(0),
+            getResponse.readingOrStateUnavailable, getResponse.scanningEnabled,
+            getResponse.allEventMessagesEnabled,
+            getResponse.thresholdLevelsStates,
+            getResponse.discreteReadingSensorStates);
+#endif
+    }
+#ifdef UPDATE_FUNCTIONAL_ON_FAIL
+    catch (const SensorFunctionalError& e)
+    {
+        return ipmi::responseResponseError();
+    }
+#endif
+    catch (const std::exception& e)
+    {
+        // Intitilizing with default values
+        constexpr uint8_t senReading = 0;
+        constexpr uint5_t reserved{0};
+        constexpr bool readState = true;
+        constexpr bool senScanState = false;
+        constexpr bool allEventMessageState = false;
+        constexpr uint8_t assertionStatesLsb = 0;
+        constexpr uint8_t assertionStatesMsb = 0;
+
+        return ipmi::responseSuccess(senReading, reserved, readState,
+                                     senScanState, allEventMessageState,
+                                     assertionStatesLsb, assertionStatesMsb);
+    }
+}
+
+void updateWarningThreshold(uint8_t lowerValue, uint8_t upperValue,
+                            get_sdr::GetSensorThresholdsResponse& resp)
+{
+    resp.lowerNonCritical = lowerValue;
+    resp.upperNonCritical = upperValue;
+    if (lowerValue)
+    {
+        resp.validMask |= static_cast<uint8_t>(
+            ipmi::sensor::ThresholdMask::NON_CRITICAL_LOW_MASK);
+    }
+
+    if (upperValue)
+    {
+        resp.validMask |= static_cast<uint8_t>(
+            ipmi::sensor::ThresholdMask::NON_CRITICAL_HIGH_MASK);
+    }
+}
+
+void updateCriticalThreshold(uint8_t lowerValue, uint8_t upperValue,
+                             get_sdr::GetSensorThresholdsResponse& resp)
+{
+    resp.lowerCritical = lowerValue;
+    resp.upperCritical = upperValue;
+    if (lowerValue)
+    {
+        resp.validMask |= static_cast<uint8_t>(
+            ipmi::sensor::ThresholdMask::CRITICAL_LOW_MASK);
+    }
+
+    if (upperValue)
+    {
+        resp.validMask |= static_cast<uint8_t>(
+            ipmi::sensor::ThresholdMask::CRITICAL_HIGH_MASK);
+    }
+}
+
+void updateNonRecoverableThreshold(uint8_t lowerValue, uint8_t upperValue,
+                                   get_sdr::GetSensorThresholdsResponse& resp)
+{
+    resp.lowerNonRecoverable = lowerValue;
+    resp.upperNonRecoverable = upperValue;
+    if (lowerValue)
+    {
+        resp.validMask |= static_cast<uint8_t>(
+            ipmi::sensor::ThresholdMask::NON_RECOVERABLE_LOW_MASK);
+    }
+
+    if (upperValue)
+    {
+        resp.validMask |= static_cast<uint8_t>(
+            ipmi::sensor::ThresholdMask::NON_RECOVERABLE_HIGH_MASK);
+    }
+}
+
+get_sdr::GetSensorThresholdsResponse getSensorThresholds(
+    ipmi::Context::ptr& ctx, uint8_t sensorNum)
+{
+    get_sdr::GetSensorThresholdsResponse resp{};
+    const auto iter = ipmi::sensor::sensors.find(sensorNum);
+    const auto info = iter->second;
+
+    std::string service;
+    boost::system::error_code ec;
+    ec = ipmi::getService(ctx, info.sensorInterface, info.sensorPath, service);
+    if (ec)
+    {
+        return resp;
+    }
+
+    int32_t minClamp;
+    int32_t maxClamp;
+    int32_t rawData;
+    constexpr uint8_t sensorUnitsSignedBits = 2 << 6;
+    constexpr uint8_t signedDataFormat = 0x80;
+    if ((info.sensorUnits1 & sensorUnitsSignedBits) == signedDataFormat)
+    {
+        minClamp = std::numeric_limits<int8_t>::lowest();
+        maxClamp = std::numeric_limits<int8_t>::max();
+    }
+    else
+    {
+        minClamp = std::numeric_limits<uint8_t>::lowest();
+        maxClamp = std::numeric_limits<uint8_t>::max();
+    }
+
+    static std::vector<std::string> thresholdNames{"Warning", "Critical",
+                                                   "NonRecoverable"};
+
+    for (const auto& thresholdName : thresholdNames)
+    {
+        std::string thresholdInterface =
+            "xyz.openbmc_project.Sensor.Threshold." + thresholdName;
+        std::string thresholdLow = thresholdName + "Low";
+        std::string thresholdHigh = thresholdName + "High";
+
+        ipmi::PropertyMap thresholds;
+        ec = ipmi::getAllDbusProperties(ctx, service, info.sensorPath,
+                                        thresholdInterface, thresholds);
+        if (ec)
+        {
+            continue;
+        }
+
+        double lowValue = ipmi::mappedVariant<double>(
+            thresholds, thresholdLow, std::numeric_limits<double>::quiet_NaN());
+        double highValue = ipmi::mappedVariant<double>(
+            thresholds, thresholdHigh,
+            std::numeric_limits<double>::quiet_NaN());
+
+        uint8_t lowerValue = 0;
+        uint8_t upperValue = 0;
+        if (std::isfinite(lowValue))
+        {
+            lowValue *= std::pow(10, info.scale - info.exponentR);
+            rawData = round((lowValue - info.scaledOffset) / info.coefficientM);
+            lowerValue =
+                static_cast<uint8_t>(std::clamp(rawData, minClamp, maxClamp));
+        }
+
+        if (std::isfinite(highValue))
+        {
+            highValue *= std::pow(10, info.scale - info.exponentR);
+            rawData =
+                round((highValue - info.scaledOffset) / info.coefficientM);
+            upperValue =
+                static_cast<uint8_t>(std::clamp(rawData, minClamp, maxClamp));
+        }
+
+        if (thresholdName == "Warning")
+        {
+            updateWarningThreshold(lowerValue, upperValue, resp);
+        }
+        else if (thresholdName == "Critical")
+        {
+            updateCriticalThreshold(lowerValue, upperValue, resp);
+        }
+        else if (thresholdName == "NonRecoverable")
+        {
+            updateNonRecoverableThreshold(lowerValue, upperValue, resp);
+        }
+    }
+
+    return resp;
+}
+
+/** @brief implements the get sensor thresholds command
+ *  @param ctx - IPMI context pointer
+ *  @param sensorNum - sensor number
+ *
+ *  @returns IPMI completion code plus response data
+ *   - validMask - threshold mask
+ *   - lower non-critical threshold - IPMI messaging state
+ *   - lower critical threshold - link authentication state
+ *   - lower non-recoverable threshold - callback state
+ *   - upper non-critical threshold
+ *   - upper critical
+ *   - upper non-recoverable
+ */
+ipmi::RspType<uint8_t, // validMask
+              uint8_t, // lowerNonCritical
+              uint8_t, // lowerCritical
+              uint8_t, // lowerNonRecoverable
+              uint8_t, // upperNonCritical
+              uint8_t, // upperCritical
+              uint8_t  // upperNonRecoverable
+              >
+    ipmiSensorGetSensorThresholds(ipmi::Context::ptr& ctx, uint8_t sensorNum)
+{
+    constexpr auto valueInterface = "xyz.openbmc_project.Sensor.Value";
+
+    const auto iter = ipmi::sensor::sensors.find(sensorNum);
+    if (iter == ipmi::sensor::sensors.end())
+    {
+        return ipmi::responseSensorInvalid();
+    }
+
+    const auto info = iter->second;
+
+    // Proceed only if the sensor value interface is implemented.
+    if (info.propertyInterfaces.find(valueInterface) ==
+        info.propertyInterfaces.end())
+    {
+        // return with valid mask as 0
+        return ipmi::responseSuccess();
+    }
+
+    auto it = sensorThresholdMap.find(sensorNum);
+    if (it == sensorThresholdMap.end())
+    {
+        auto resp = getSensorThresholds(ctx, sensorNum);
+        if (resp.validMask == 0)
+        {
+            return ipmi::responseSensorInvalid();
+        }
+        sensorThresholdMap[sensorNum] = std::move(resp);
+    }
+
+    const auto& resp = sensorThresholdMap[sensorNum];
+
+    return ipmi::responseSuccess(
+        resp.validMask, resp.lowerNonCritical, resp.lowerCritical,
+        resp.lowerNonRecoverable, resp.upperNonCritical, resp.upperCritical,
+        resp.upperNonRecoverable);
+}
+
+/** @brief implements the Set Sensor threshold command
+ *  @param sensorNumber        - sensor number
+ *  @param lowerNonCriticalThreshMask
+ *  @param lowerCriticalThreshMask
+ *  @param lowerNonRecovThreshMask
+ *  @param upperNonCriticalThreshMask
+ *  @param upperCriticalThreshMask
+ *  @param upperNonRecovThreshMask
+ *  @param reserved
+ *  @param lowerNonCritical    - lower non-critical threshold
+ *  @param lowerCritical       - Lower critical threshold
+ *  @param lowerNonRecoverable - Lower non recovarable threshold
+ *  @param upperNonCritical    - Upper non-critical threshold
+ *  @param upperCritical       - Upper critical
+ *  @param upperNonRecoverable - Upper Non-recoverable
+ *
+ *  @returns IPMI completion code
+ */
+ipmi::RspType<> ipmiSenSetSensorThresholds(
+    ipmi::Context::ptr& ctx, uint8_t sensorNum, bool lowerNonCriticalThreshMask,
+    bool lowerCriticalThreshMask, bool lowerNonRecovThreshMask,
+    bool upperNonCriticalThreshMask, bool upperCriticalThreshMask,
+    bool upperNonRecovThreshMask, uint2_t reserved, uint8_t lowerNonCritical,
+    uint8_t lowerCritical, uint8_t, uint8_t upperNonCritical,
+    uint8_t upperCritical, uint8_t)
+{
+    if (reserved)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    // lower nc and upper nc not suppported on any sensor
+    if (lowerNonRecovThreshMask || upperNonRecovThreshMask)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    // if none of the threshold mask are set, nothing to do
+    if (!(lowerNonCriticalThreshMask | lowerCriticalThreshMask |
+          lowerNonRecovThreshMask | upperNonCriticalThreshMask |
+          upperCriticalThreshMask | upperNonRecovThreshMask))
+    {
+        return ipmi::responseSuccess();
+    }
+
+    constexpr auto valueInterface = "xyz.openbmc_project.Sensor.Value";
+
+    const auto iter = ipmi::sensor::sensors.find(sensorNum);
+    if (iter == ipmi::sensor::sensors.end())
+    {
+        return ipmi::responseSensorInvalid();
+    }
+
+    const auto& info = iter->second;
+
+    // Proceed only if the sensor value interface is implemented.
+    if (info.propertyInterfaces.find(valueInterface) ==
+        info.propertyInterfaces.end())
+    {
+        // return with valid mask as 0
+        return ipmi::responseSuccess();
+    }
+
+    constexpr auto warningThreshIntf =
+        "xyz.openbmc_project.Sensor.Threshold.Warning";
+    constexpr auto criticalThreshIntf =
+        "xyz.openbmc_project.Sensor.Threshold.Critical";
+
+    std::string service;
+    boost::system::error_code ec;
+    ec = ipmi::getService(ctx, info.sensorInterface, info.sensorPath, service);
+    if (ec)
+    {
+        return ipmi::responseResponseError();
+    }
+    // store a vector of property name, value to set, and interface
+    std::vector<std::tuple<std::string, uint8_t, std::string>> thresholdsToSet;
+
+    // define the indexes of the tuple
+    constexpr uint8_t propertyName = 0;
+    constexpr uint8_t thresholdValue = 1;
+    constexpr uint8_t interface = 2;
+    // verifiy all needed fields are present
+    if (lowerCriticalThreshMask || upperCriticalThreshMask)
+    {
+        ipmi::PropertyMap findThreshold;
+        ec = ipmi::getAllDbusProperties(ctx, service, info.sensorPath,
+                                        criticalThreshIntf, findThreshold);
+
+        if (!ec)
+        {
+            if (lowerCriticalThreshMask)
+            {
+                auto findLower = findThreshold.find("CriticalLow");
+                if (findLower == findThreshold.end())
+                {
+                    return ipmi::responseInvalidFieldRequest();
+                }
+                thresholdsToSet.emplace_back("CriticalLow", lowerCritical,
+                                             criticalThreshIntf);
+            }
+            if (upperCriticalThreshMask)
+            {
+                auto findUpper = findThreshold.find("CriticalHigh");
+                if (findUpper == findThreshold.end())
+                {
+                    return ipmi::responseInvalidFieldRequest();
+                }
+                thresholdsToSet.emplace_back("CriticalHigh", upperCritical,
+                                             criticalThreshIntf);
+            }
+        }
+    }
+    if (lowerNonCriticalThreshMask || upperNonCriticalThreshMask)
+    {
+        ipmi::PropertyMap findThreshold;
+        ec = ipmi::getAllDbusProperties(ctx, service, info.sensorPath,
+                                        warningThreshIntf, findThreshold);
+
+        if (!ec)
+        {
+            if (lowerNonCriticalThreshMask)
+            {
+                auto findLower = findThreshold.find("WarningLow");
+                if (findLower == findThreshold.end())
+                {
+                    return ipmi::responseInvalidFieldRequest();
+                }
+                thresholdsToSet.emplace_back("WarningLow", lowerNonCritical,
+                                             warningThreshIntf);
+            }
+            if (upperNonCriticalThreshMask)
+            {
+                auto findUpper = findThreshold.find("WarningHigh");
+                if (findUpper == findThreshold.end())
+                {
+                    return ipmi::responseInvalidFieldRequest();
+                }
+                thresholdsToSet.emplace_back("WarningHigh", upperNonCritical,
+                                             warningThreshIntf);
+            }
+        }
+    }
+    for (const auto& property : thresholdsToSet)
+    {
+        // from section 36.3 in the IPMI Spec, assume all linear
+        double valueToSet =
+            ((info.coefficientM * std::get<thresholdValue>(property)) +
+             (info.scaledOffset * std::pow(10.0, info.scale))) *
+            std::pow(10.0, info.exponentR);
+        ipmi::setDbusProperty(
+            ctx, service, info.sensorPath, std::get<interface>(property),
+            std::get<propertyName>(property), ipmi::Value(valueToSet));
+    }
+
+    // Invalidate the cache
+    sensorThresholdMap.erase(sensorNum);
+    return ipmi::responseSuccess();
+}
+
+/** @brief implements the get SDR Info command
+ *  @param count - Operation
+ *
+ *  @returns IPMI completion code plus response data
+ *   - sdrCount - sensor/SDR count
+ *   - lunsAndDynamicPopulation - static/Dynamic sensor population flag
+ */
+ipmi::RspType<uint8_t, // respcount
+              uint8_t  // dynamic population flags
+              >
+    ipmiSensorGetDeviceSdrInfo(std::optional<uint8_t> count)
+{
+    uint8_t sdrCount;
+    // multiple LUNs not supported.
+    constexpr uint8_t lunsAndDynamicPopulation = 1;
+    constexpr uint8_t getSdrCount = 0x01;
+    constexpr uint8_t getSensorCount = 0x00;
+
+    if (count.value_or(0) == getSdrCount)
+    {
+        // Get SDR count. This returns the total number of SDRs in the device.
+        const auto& entityRecords =
+            ipmi::sensor::EntityInfoMapContainer::getContainer()
+                ->getIpmiEntityRecords();
+        sdrCount = ipmi::sensor::sensors.size() + frus.size() +
+                   entityRecords.size();
+    }
+    else if (count.value_or(0) == getSensorCount)
+    {
+        // Get Sensor count. This returns the number of sensors
+        sdrCount = ipmi::sensor::sensors.size();
+    }
+    else
+    {
+        return ipmi::responseInvalidCommandOnLun();
+    }
+
+    return ipmi::responseSuccess(sdrCount, lunsAndDynamicPopulation);
+}
+
+/** @brief implements the reserve SDR command
+ *  @returns IPMI completion code plus response data
+ *   - reservationID - reservation ID
+ */
+ipmi::RspType<uint16_t> ipmiSensorReserveSdr()
+{
+    // A constant reservation ID is okay until we implement add/remove SDR.
+    constexpr uint16_t reservationID = 1;
+
+    return ipmi::responseSuccess(reservationID);
+}
+
+void setUnitFieldsForObject(const ipmi::sensor::Info* info,
+                            get_sdr::SensorDataFullRecordBody* body)
+{
+    namespace server = sdbusplus::server::xyz::openbmc_project::sensor;
+    try
+    {
+        auto unit = server::Value::convertUnitFromString(info->unit);
+        // Unit strings defined in
+        // phosphor-dbus-interfaces/xyz/openbmc_project/Sensor/Value.interface.yaml
+        switch (unit)
+        {
+            case server::Value::Unit::DegreesC:
+                body->sensor_units_2_base = get_sdr::SENSOR_UNIT_DEGREES_C;
+                break;
+            case server::Value::Unit::RPMS:
+                body->sensor_units_2_base = get_sdr::SENSOR_UNIT_RPM;
+                break;
+            case server::Value::Unit::Volts:
+                body->sensor_units_2_base = get_sdr::SENSOR_UNIT_VOLTS;
+                break;
+            case server::Value::Unit::Meters:
+                body->sensor_units_2_base = get_sdr::SENSOR_UNIT_METERS;
+                break;
+            case server::Value::Unit::Amperes:
+                body->sensor_units_2_base = get_sdr::SENSOR_UNIT_AMPERES;
+                break;
+            case server::Value::Unit::Joules:
+                body->sensor_units_2_base = get_sdr::SENSOR_UNIT_JOULES;
+                break;
+            case server::Value::Unit::Watts:
+                body->sensor_units_2_base = get_sdr::SENSOR_UNIT_WATTS;
+                break;
+            default:
+                // Cannot be hit.
+                std::fprintf(stderr, "Unknown value unit type: = %s\n",
+                             info->unit.c_str());
+        }
+    }
+    catch (const sdbusplus::exception::InvalidEnumString& e)
+    {
+        lg2::warning("Warning: no unit provided for sensor!");
+    }
+}
+
+ipmi_ret_t populate_record_from_dbus(get_sdr::SensorDataFullRecordBody* body,
+                                     const ipmi::sensor::Info* info,
+                                     ipmi_data_len_t)
+{
+    /* Functional sensor case */
+    if (isAnalogSensor(info->propertyInterfaces.begin()->first))
+    {
+        body->sensor_units_1 = info->sensorUnits1; // default is 0. unsigned, no
+                                                   // rate, no modifier, not a %
+        /* Unit info */
+        setUnitFieldsForObject(info, body);
+
+        get_sdr::body::set_b(info->coefficientB, body);
+        get_sdr::body::set_m(info->coefficientM, body);
+        get_sdr::body::set_b_exp(info->exponentB, body);
+        get_sdr::body::set_r_exp(info->exponentR, body);
+    }
+
+    /* ID string */
+    auto id_string = info->sensorName;
+
+    if (id_string.empty())
+    {
+        id_string = info->sensorNameFunc(*info);
+    }
+
+    if (id_string.length() > FULL_RECORD_ID_STR_MAX_LENGTH)
+    {
+        get_sdr::body::set_id_strlen(FULL_RECORD_ID_STR_MAX_LENGTH, body);
+    }
+    else
+    {
+        get_sdr::body::set_id_strlen(id_string.length(), body);
+    }
+    get_sdr::body::set_id_type(3, body); // "8-bit ASCII + Latin 1"
+    strncpy(body->id_string, id_string.c_str(),
+            get_sdr::body::get_id_strlen(body));
+
+    return IPMI_CC_OK;
+};
+
+ipmi_ret_t ipmi_fru_get_sdr(ipmi_request_t request, ipmi_response_t response,
+                            ipmi_data_len_t data_len)
+{
+    auto req = reinterpret_cast<get_sdr::GetSdrReq*>(request);
+    auto resp = reinterpret_cast<get_sdr::GetSdrResp*>(response);
+    get_sdr::SensorDataFruRecord record{};
+    auto dataLength = 0;
+
+    auto fru = frus.begin();
+    uint8_t fruID{};
+    auto recordID = get_sdr::request::get_record_id(req);
+
+    fruID = recordID - FRU_RECORD_ID_START;
+    fru = frus.find(fruID);
+    if (fru == frus.end())
+    {
+        return IPMI_CC_SENSOR_INVALID;
+    }
+
+    /* Header */
+    get_sdr::header::set_record_id(recordID, &(record.header));
+    record.header.sdr_version = SDR_VERSION; // Based on IPMI Spec v2.0 rev 1.1
+    record.header.record_type = get_sdr::SENSOR_DATA_FRU_RECORD;
+    record.header.record_length = sizeof(record.key) + sizeof(record.body);
+
+    /* Key */
+    record.key.fruID = fruID;
+    record.key.accessLun |= IPMI_LOGICAL_FRU;
+    record.key.deviceAddress = BMCTargetAddress;
+
+    /* Body */
+    record.body.entityID = fru->second[0].entityID;
+    record.body.entityInstance = fru->second[0].entityInstance;
+    record.body.deviceType = fruInventoryDevice;
+    record.body.deviceTypeModifier = IPMIFruInventory;
+
+    /* Device ID string */
+    auto deviceID =
+        fru->second[0].path.substr(fru->second[0].path.find_last_of('/') + 1,
+                                   fru->second[0].path.length());
+
+    if (deviceID.length() > get_sdr::FRU_RECORD_DEVICE_ID_MAX_LENGTH)
+    {
+        get_sdr::body::set_device_id_strlen(
+            get_sdr::FRU_RECORD_DEVICE_ID_MAX_LENGTH, &(record.body));
+    }
+    else
+    {
+        get_sdr::body::set_device_id_strlen(deviceID.length(), &(record.body));
+    }
+
+    strncpy(record.body.deviceID, deviceID.c_str(),
+            get_sdr::body::get_device_id_strlen(&(record.body)));
+
+    if (++fru == frus.end())
+    {
+        // we have reached till end of fru, so assign the next record id to
+        // 512(Max fru ID = 511) + Entity Record ID(may start with 0).
+        const auto& entityRecords =
+            ipmi::sensor::EntityInfoMapContainer::getContainer()
+                ->getIpmiEntityRecords();
+        auto next_record_id =
+            (entityRecords.size())
+                ? entityRecords.begin()->first + ENTITY_RECORD_ID_START
+                : END_OF_RECORD;
+        get_sdr::response::set_next_record_id(next_record_id, resp);
+    }
+    else
+    {
+        get_sdr::response::set_next_record_id(
+            (FRU_RECORD_ID_START + fru->first), resp);
+    }
+
+    // Check for invalid offset size
+    if (req->offset > sizeof(record))
+    {
+        return IPMI_CC_PARM_OUT_OF_RANGE;
+    }
+
+    dataLength = std::min(static_cast<size_t>(req->bytes_to_read),
+                          sizeof(record) - req->offset);
+
+    std::memcpy(resp->record_data,
+                reinterpret_cast<uint8_t*>(&record) + req->offset, dataLength);
+
+    *data_len = dataLength;
+    *data_len += 2; // additional 2 bytes for next record ID
+
+    return IPMI_CC_OK;
+}
+
+ipmi_ret_t ipmi_entity_get_sdr(ipmi_request_t request, ipmi_response_t response,
+                               ipmi_data_len_t data_len)
+{
+    auto req = reinterpret_cast<get_sdr::GetSdrReq*>(request);
+    auto resp = reinterpret_cast<get_sdr::GetSdrResp*>(response);
+    get_sdr::SensorDataEntityRecord record{};
+    auto dataLength = 0;
+
+    const auto& entityRecords =
+        ipmi::sensor::EntityInfoMapContainer::getContainer()
+            ->getIpmiEntityRecords();
+    auto entity = entityRecords.begin();
+    uint8_t entityRecordID;
+    auto recordID = get_sdr::request::get_record_id(req);
+
+    entityRecordID = recordID - ENTITY_RECORD_ID_START;
+    entity = entityRecords.find(entityRecordID);
+    if (entity == entityRecords.end())
+    {
+        return IPMI_CC_SENSOR_INVALID;
+    }
+
+    /* Header */
+    get_sdr::header::set_record_id(recordID, &(record.header));
+    record.header.sdr_version = SDR_VERSION; // Based on IPMI Spec v2.0 rev 1.1
+    record.header.record_type = get_sdr::SENSOR_DATA_ENTITY_RECORD;
+    record.header.record_length = sizeof(record.key) + sizeof(record.body);
+
+    /* Key */
+    record.key.containerEntityId = entity->second.containerEntityId;
+    record.key.containerEntityInstance = entity->second.containerEntityInstance;
+    get_sdr::key::set_flags(entity->second.isList, entity->second.isLinked,
+                            &(record.key));
+    record.key.entityId1 = entity->second.containedEntities[0].first;
+    record.key.entityInstance1 = entity->second.containedEntities[0].second;
+
+    /* Body */
+    record.body.entityId2 = entity->second.containedEntities[1].first;
+    record.body.entityInstance2 = entity->second.containedEntities[1].second;
+    record.body.entityId3 = entity->second.containedEntities[2].first;
+    record.body.entityInstance3 = entity->second.containedEntities[2].second;
+    record.body.entityId4 = entity->second.containedEntities[3].first;
+    record.body.entityInstance4 = entity->second.containedEntities[3].second;
+
+    if (++entity == entityRecords.end())
+    {
+        get_sdr::response::set_next_record_id(END_OF_RECORD,
+                                              resp); // last record
+    }
+    else
+    {
+        get_sdr::response::set_next_record_id(
+            (ENTITY_RECORD_ID_START + entity->first), resp);
+    }
+
+    // Check for invalid offset size
+    if (req->offset > sizeof(record))
+    {
+        return IPMI_CC_PARM_OUT_OF_RANGE;
+    }
+
+    dataLength = std::min(static_cast<size_t>(req->bytes_to_read),
+                          sizeof(record) - req->offset);
+
+    std::memcpy(resp->record_data,
+                reinterpret_cast<uint8_t*>(&record) + req->offset, dataLength);
+
+    *data_len = dataLength;
+    *data_len += 2; // additional 2 bytes for next record ID
+
+    return IPMI_CC_OK;
+}
+
+ipmi_ret_t ipmi_sen_get_sdr(ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t request,
+                            ipmi_response_t response, ipmi_data_len_t data_len,
+                            ipmi_context_t)
+{
+    ipmi_ret_t ret = IPMI_CC_OK;
+    get_sdr::GetSdrReq* req = (get_sdr::GetSdrReq*)request;
+    get_sdr::GetSdrResp* resp = (get_sdr::GetSdrResp*)response;
+
+    // Note: we use an iterator so we can provide the next ID at the end of
+    // the call.
+    auto sensor = ipmi::sensor::sensors.begin();
+    auto recordID = get_sdr::request::get_record_id(req);
+
+    // At the beginning of a scan, the host side will send us id=0.
+    if (recordID != 0)
+    {
+        // recordID 0 to 255 means it is a FULL record.
+        // recordID 256 to 511 means it is a FRU record.
+        // recordID greater then 511 means it is a Entity Association
+        // record. Currently we are supporting three record types: FULL
+        // record, FRU record and Enttiy Association record.
+        if (recordID >= ENTITY_RECORD_ID_START)
+        {
+            return ipmi_entity_get_sdr(request, response, data_len);
+        }
+        else if (recordID >= FRU_RECORD_ID_START &&
+                 recordID < ENTITY_RECORD_ID_START)
+        {
+            return ipmi_fru_get_sdr(request, response, data_len);
+        }
+        else
+        {
+            sensor = ipmi::sensor::sensors.find(recordID);
+            if (sensor == ipmi::sensor::sensors.end())
+            {
+                return IPMI_CC_SENSOR_INVALID;
+            }
+        }
+    }
+
+    uint8_t sensor_id = sensor->first;
+
+    auto it = sdrCacheMap.find(sensor_id);
+    if (it == sdrCacheMap.end())
+    {
+        /* Header */
+        get_sdr::SensorDataFullRecord record = {};
+        get_sdr::header::set_record_id(sensor_id, &(record.header));
+        record.header.sdr_version = 0x51; // Based on IPMI Spec v2.0 rev 1.1
+        record.header.record_type = get_sdr::SENSOR_DATA_FULL_RECORD;
+        record.header.record_length = sizeof(record.key) + sizeof(record.body);
+
+        /* Key */
+        get_sdr::key::set_owner_id_bmc(&(record.key));
+        record.key.sensor_number = sensor_id;
+
+        /* Body */
+        record.body.entity_id = sensor->second.entityType;
+        record.body.sensor_type = sensor->second.sensorType;
+        record.body.event_reading_type = sensor->second.sensorReadingType;
+        record.body.entity_instance = sensor->second.instance;
+        if (ipmi::sensor::Mutability::Write ==
+            (sensor->second.mutability & ipmi::sensor::Mutability::Write))
+        {
+            get_sdr::body::init_settable_state(true, &(record.body));
+        }
+
+        // Set the type-specific details given the DBus interface
+        populate_record_from_dbus(&(record.body), &(sensor->second), data_len);
+        sdrCacheMap[sensor_id] = std::move(record);
+    }
+
+    const auto& record = sdrCacheMap[sensor_id];
+
+    if (++sensor == ipmi::sensor::sensors.end())
+    {
+        // we have reached till end of sensor, so assign the next record id
+        // to 256(Max Sensor ID = 255) + FRU ID(may start with 0).
+        auto next_record_id = (frus.size())
+                                  ? frus.begin()->first + FRU_RECORD_ID_START
+                                  : END_OF_RECORD;
+
+        get_sdr::response::set_next_record_id(next_record_id, resp);
+    }
+    else
+    {
+        get_sdr::response::set_next_record_id(sensor->first, resp);
+    }
+
+    if (req->offset > sizeof(record))
+    {
+        return IPMI_CC_PARM_OUT_OF_RANGE;
+    }
+
+    // data_len will ultimately be the size of the record, plus
+    // the size of the next record ID:
+    *data_len = std::min(static_cast<size_t>(req->bytes_to_read),
+                         sizeof(record) - req->offset);
+
+    std::memcpy(resp->record_data,
+                reinterpret_cast<const uint8_t*>(&record) + req->offset,
+                *data_len);
+
+    // data_len should include the LSB and MSB:
+    *data_len += sizeof(resp->next_record_id_lsb) +
+                 sizeof(resp->next_record_id_msb);
+
+    return ret;
+}
+
+static bool isFromSystemChannel()
+{
+    // TODO we could not figure out where the request is from based on IPMI
+    // command handler parameters. because of it, we can not differentiate
+    // request from SMS/SMM or IPMB channel
+    return true;
+}
+
+ipmi_ret_t ipmicmdPlatformEvent(ipmi_netfn_t, ipmi_cmd_t,
+                                ipmi_request_t request, ipmi_response_t,
+                                ipmi_data_len_t dataLen, ipmi_context_t)
+{
+    uint16_t generatorID;
+    size_t count;
+    bool assert = true;
+    std::string sensorPath;
+    size_t paraLen = *dataLen;
+    PlatformEventRequest* req;
+    *dataLen = 0;
+
+    if ((paraLen < selSystemEventSizeWith1Bytes) ||
+        (paraLen > selSystemEventSizeWith3Bytes))
+    {
+        return IPMI_CC_REQ_DATA_LEN_INVALID;
+    }
+
+    if (isFromSystemChannel())
+    { // first byte for SYSTEM Interface is Generator ID
+        // +1 to get common struct
+        req = reinterpret_cast<PlatformEventRequest*>((uint8_t*)request + 1);
+        // Capture the generator ID
+        generatorID = *reinterpret_cast<uint8_t*>(request);
+        // Platform Event usually comes from other firmware, like BIOS.
+        // Unlike BMC sensor, it does not have BMC DBUS sensor path.
+        sensorPath = "System";
+    }
+    else
+    {
+        req = reinterpret_cast<PlatformEventRequest*>(request);
+        // TODO GenratorID for IPMB is combination of RqSA and RqLUN
+        generatorID = 0xff;
+        sensorPath = "IPMB";
+    }
+    // Content of event data field depends on sensor class.
+    // When data0 bit[5:4] is non-zero, valid data counts is 3.
+    // When data0 bit[7:6] is non-zero, valid data counts is 2.
+    if (((req->data[0] & byte3EnableMask) != 0 &&
+         paraLen < selSystemEventSizeWith3Bytes) ||
+        ((req->data[0] & byte2EnableMask) != 0 &&
+         paraLen < selSystemEventSizeWith2Bytes))
+    {
+        return IPMI_CC_REQ_DATA_LEN_INVALID;
+    }
+
+    // Count bytes of Event Data
+    if ((req->data[0] & byte3EnableMask) != 0)
+    {
+        count = 3;
+    }
+    else if ((req->data[0] & byte2EnableMask) != 0)
+    {
+        count = 2;
+    }
+    else
+    {
+        count = 1;
+    }
+    assert = req->eventDirectionType & directionMask ? false : true;
+    std::vector<uint8_t> eventData(req->data, req->data + count);
+
+    sdbusplus::bus_t dbus(bus);
+    std::string service =
+        ipmi::getService(dbus, ipmiSELAddInterface, ipmiSELPath);
+    sdbusplus::message_t writeSEL = dbus.new_method_call(
+        service.c_str(), ipmiSELPath, ipmiSELAddInterface, "IpmiSelAdd");
+    writeSEL.append(ipmiSELAddMessage, sensorPath, eventData, assert,
+                    generatorID);
+    try
+    {
+        dbus.call(writeSEL);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error("exception message: {ERROR}", "ERROR", e);
+        return IPMI_CC_UNSPECIFIED_ERROR;
+    }
+    return IPMI_CC_OK;
+}
+
+void registerNetFnSenFunctions()
+{
+    // Handlers with dbus-sdr handler implementation.
+    // Do not register the hander if it dynamic sensors stack is used.
+
+#ifndef FEATURE_DYNAMIC_SENSORS
+
+#ifdef FEATURE_SENSORS_CACHE
+    // Initialize the sensor matches
+    initSensorMatches();
+#endif
+
+    // <Set Sensor Reading and Event Status>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+                          ipmi::sensor_event::cmdSetSensorReadingAndEvtSts,
+                          ipmi::Privilege::Operator, ipmiSetSensorReading);
+    // <Get Sensor Reading>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+                          ipmi::sensor_event::cmdGetSensorReading,
+                          ipmi::Privilege::User, ipmiSensorGetSensorReading);
+
+    // <Reserve Device SDR Repository>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+                          ipmi::sensor_event::cmdReserveDeviceSdrRepository,
+                          ipmi::Privilege::User, ipmiSensorReserveSdr);
+
+    // <Get Device SDR Info>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+                          ipmi::sensor_event::cmdGetDeviceSdrInfo,
+                          ipmi::Privilege::User, ipmiSensorGetDeviceSdrInfo);
+
+    // <Get Sensor Thresholds>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+                          ipmi::sensor_event::cmdGetSensorThreshold,
+                          ipmi::Privilege::User, ipmiSensorGetSensorThresholds);
+
+    // <Set Sensor Thresholds>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+                          ipmi::sensor_event::cmdSetSensorThreshold,
+                          ipmi::Privilege::User, ipmiSenSetSensorThresholds);
+
+    // <Get Device SDR>
+    ipmi_register_callback(NETFUN_SENSOR, ipmi::sensor_event::cmdGetDeviceSdr,
+                           nullptr, ipmi_sen_get_sdr, PRIVILEGE_USER);
+
+#endif
+
+    // Common Handers used by both implementation.
+
+    // <Platform Event Message>
+    ipmi_register_callback(NETFUN_SENSOR, ipmi::sensor_event::cmdPlatformEvent,
+                           nullptr, ipmicmdPlatformEvent, PRIVILEGE_OPERATOR);
+
+    // <Get Sensor Type>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+                          ipmi::sensor_event::cmdGetSensorType,
+                          ipmi::Privilege::User, ipmiGetSensorType);
+
+    return;
+}
diff --git a/sensorhandler.hpp b/sensorhandler.hpp
new file mode 100644
index 0000000..74b1862
--- /dev/null
+++ b/sensorhandler.hpp
@@ -0,0 +1,766 @@
+#pragma once
+
+#include <stdint.h>
+
+#include <ipmid/api.hpp>
+#include <ipmid/types.hpp>
+
+#include <exception>
+
+/**
+ * @enum device_type
+ * IPMI FRU device types
+ */
+enum device_type
+{
+    IPMI_PHYSICAL_FRU = 0x00,
+    IPMI_LOGICAL_FRU = 0x80,
+};
+
+// Discrete sensor types.
+enum ipmi_sensor_types
+{
+    IPMI_SENSOR_TEMP = 0x01,
+    IPMI_SENSOR_VOLTAGE = 0x02,
+    IPMI_SENSOR_CURRENT = 0x03,
+    IPMI_SENSOR_FAN = 0x04,
+    IPMI_SENSOR_TPM = 0xCC,
+};
+
+/** @brief Custom exception for reading sensors that are not funcitonal.
+ */
+struct SensorFunctionalError : public std::exception
+{
+    const char* what() const noexcept
+    {
+        return "Sensor not functional";
+    }
+};
+
+#define MAX_DBUS_PATH 128
+struct dbus_interface_t
+{
+    uint8_t sensornumber;
+    uint8_t sensortype;
+
+    char bus[MAX_DBUS_PATH];
+    char path[MAX_DBUS_PATH];
+    char interface[MAX_DBUS_PATH];
+};
+
+struct PlatformEventRequest
+{
+    uint8_t eventMessageRevision;
+    uint8_t sensorType;
+    uint8_t sensorNumber;
+    uint8_t eventDirectionType;
+    uint8_t data[3];
+};
+
+static constexpr const char* ipmiSELPath = "/xyz/openbmc_project/Logging/IPMI";
+static constexpr const char* ipmiSELAddInterface =
+    "xyz.openbmc_project.Logging.IPMI";
+static const std::string ipmiSELAddMessage = "IPMI generated SEL Entry";
+
+static constexpr int selSystemEventSizeWith3Bytes = 8;
+static constexpr int selSystemEventSizeWith2Bytes = 7;
+static constexpr int selSystemEventSizeWith1Bytes = 6;
+static constexpr int selIPMBEventSize = 7;
+static constexpr uint8_t directionMask = 0x80;
+static constexpr uint8_t byte3EnableMask = 0x30;
+static constexpr uint8_t byte2EnableMask = 0xC0;
+
+int set_sensor_dbus_state_s(uint8_t, const char*, const char*);
+int set_sensor_dbus_state_y(uint8_t, const char*, const uint8_t);
+int find_openbmc_path(uint8_t, dbus_interface_t*);
+
+ipmi_ret_t ipmi_sen_get_sdr(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);
+
+ipmi::RspType<uint16_t> ipmiSensorReserveSdr();
+
+static const uint16_t FRU_RECORD_ID_START = 256;
+static const uint16_t ENTITY_RECORD_ID_START = 512;
+static const uint8_t SDR_VERSION = 0x51;
+static const uint16_t END_OF_RECORD = 0xFFFF;
+static const uint8_t LENGTH_MASK = 0x1F;
+
+/**
+ * Get SDR Info
+ */
+
+namespace get_sdr_info
+{
+namespace request
+{
+// Note: for some reason the ipmi_request_t appears to be the
+// raw value for this call.
+inline bool get_count(void* req)
+{
+    return (bool)((uint64_t)(req) & 1);
+}
+} // namespace request
+} // namespace get_sdr_info
+
+/**
+ * Get SDR
+ */
+namespace get_sdr
+{
+
+struct GetSdrReq
+{
+    uint8_t reservation_id_lsb;
+    uint8_t reservation_id_msb;
+    uint8_t record_id_lsb;
+    uint8_t record_id_msb;
+    uint8_t offset;
+    uint8_t bytes_to_read;
+} __attribute__((packed));
+
+namespace request
+{
+
+inline uint16_t get_reservation_id(GetSdrReq* req)
+{
+    return (req->reservation_id_lsb + (req->reservation_id_msb << 8));
+};
+
+inline uint16_t get_record_id(GetSdrReq* req)
+{
+    return (req->record_id_lsb + (req->record_id_msb << 8));
+};
+
+} // namespace request
+
+// Response
+struct GetSdrResp
+{
+    uint8_t next_record_id_lsb;
+    uint8_t next_record_id_msb;
+    uint8_t record_data[64];
+} __attribute__((packed));
+
+namespace response
+{
+
+inline void set_next_record_id(uint16_t next, GetSdrResp* resp)
+{
+    resp->next_record_id_lsb = next & 0xff;
+    resp->next_record_id_msb = (next >> 8) & 0xff;
+};
+
+} // namespace response
+
+// Record header
+struct SensorDataRecordHeader
+{
+    uint8_t record_id_lsb;
+    uint8_t record_id_msb;
+    uint8_t sdr_version;
+    uint8_t record_type;
+    uint8_t record_length; // Length not counting the header
+} __attribute__((packed));
+
+namespace header
+{
+
+inline void set_record_id(int id, SensorDataRecordHeader* hdr)
+{
+    hdr->record_id_lsb = (id & 0xFF);
+    hdr->record_id_msb = (id >> 8) & 0xFF;
+};
+
+} // namespace header
+
+enum SensorDataRecordType
+{
+    SENSOR_DATA_FULL_RECORD = 0x1,
+    SENSOR_DATA_COMPACT_RECORD = 0x2,
+    SENSOR_DATA_EVENT_RECORD = 0x3,
+    SENSOR_DATA_ENTITY_RECORD = 0x8,
+    SENSOR_DATA_FRU_RECORD = 0x11,
+    SENSOR_DATA_MGMT_CTRL_LOCATOR = 0x12,
+};
+
+// Record key
+struct SensorDataRecordKey
+{
+    uint8_t owner_id;
+    uint8_t owner_lun;
+    uint8_t sensor_number;
+} __attribute__((packed));
+
+/** @struct SensorDataFruRecordKey
+ *
+ *  FRU Device Locator Record(key) - SDR Type 11
+ */
+struct SensorDataFruRecordKey
+{
+    uint8_t deviceAddress;
+    uint8_t fruID;
+    uint8_t accessLun;
+    uint8_t channelNumber;
+} __attribute__((packed));
+
+/** @struct SensorDataEntityRecordKey
+ *
+ *  Entity Association Record(key) - SDR Type 8
+ */
+struct SensorDataEntityRecordKey
+{
+    uint8_t containerEntityId;
+    uint8_t containerEntityInstance;
+    uint8_t flags;
+    uint8_t entityId1;
+    uint8_t entityInstance1;
+} __attribute__((packed));
+
+namespace key
+{
+
+static constexpr uint8_t listOrRangeBit = 7;
+static constexpr uint8_t linkedBit = 6;
+
+inline void set_owner_id_ipmb(SensorDataRecordKey* key)
+{
+    key->owner_id &= ~0x01;
+};
+
+inline void set_owner_id_system_sw(SensorDataRecordKey* key)
+{
+    key->owner_id |= 0x01;
+};
+
+inline void set_owner_id_bmc(SensorDataRecordKey* key)
+{
+    key->owner_id |= 0x20;
+};
+
+inline void set_owner_id_address(uint8_t addr, SensorDataRecordKey* key)
+{
+    key->owner_id &= 0x01;
+    key->owner_id |= addr << 1;
+};
+
+inline void set_owner_lun(uint8_t lun, SensorDataRecordKey* key)
+{
+    key->owner_lun &= ~0x03;
+    key->owner_lun |= (lun & 0x03);
+};
+
+inline void set_owner_lun_channel(uint8_t channel, SensorDataRecordKey* key)
+{
+    key->owner_lun &= 0x0f;
+    key->owner_lun |= ((channel & 0xf) << 4);
+};
+
+inline void set_flags(bool isList, bool isLinked,
+                      SensorDataEntityRecordKey* key)
+{
+    key->flags = 0x00;
+    if (!isList)
+        key->flags |= 1 << listOrRangeBit;
+
+    if (isLinked)
+        key->flags |= 1 << linkedBit;
+};
+
+} // namespace key
+
+/** @struct GetSensorThresholdsResponse
+ *
+ *  Response structure for Get Sensor Thresholds command
+ */
+struct GetSensorThresholdsResponse
+{
+    uint8_t validMask;           //!< valid mask
+    uint8_t lowerNonCritical;    //!< lower non-critical threshold
+    uint8_t lowerCritical;       //!< lower critical threshold
+    uint8_t lowerNonRecoverable; //!< lower non-recoverable threshold
+    uint8_t upperNonCritical;    //!< upper non-critical threshold
+    uint8_t upperCritical;       //!< upper critical threshold
+    uint8_t upperNonRecoverable; //!< upper non-recoverable threshold
+} __attribute__((packed));
+
+// Body - full record
+#define FULL_RECORD_ID_STR_MAX_LENGTH 16
+
+static const int FRU_RECORD_DEVICE_ID_MAX_LENGTH = 16;
+
+struct SensorDataFullRecordBody
+{
+    uint8_t entity_id;
+    uint8_t entity_instance;
+    uint8_t sensor_initialization;
+    uint8_t sensor_capabilities; // no macro support
+    uint8_t sensor_type;
+    uint8_t event_reading_type;
+    uint8_t supported_assertions[2];          // no macro support
+    uint8_t supported_deassertions[2];        // no macro support
+    uint8_t discrete_reading_setting_mask[2]; // no macro support
+    uint8_t sensor_units_1;
+    uint8_t sensor_units_2_base;
+    uint8_t sensor_units_3_modifier;
+    uint8_t linearization;
+    uint8_t m_lsb;
+    uint8_t m_msb_and_tolerance;
+    uint8_t b_lsb;
+    uint8_t b_msb_and_accuracy_lsb;
+    uint8_t accuracy_and_sensor_direction;
+    uint8_t r_b_exponents;
+    uint8_t analog_characteristic_flags; // no macro support
+    uint8_t nominal_reading;
+    uint8_t normal_max;
+    uint8_t normal_min;
+    uint8_t sensor_max;
+    uint8_t sensor_min;
+    uint8_t upper_nonrecoverable_threshold;
+    uint8_t upper_critical_threshold;
+    uint8_t upper_noncritical_threshold;
+    uint8_t lower_nonrecoverable_threshold;
+    uint8_t lower_critical_threshold;
+    uint8_t lower_noncritical_threshold;
+    uint8_t positive_threshold_hysteresis;
+    uint8_t negative_threshold_hysteresis;
+    uint16_t reserved;
+    uint8_t oem_reserved;
+    uint8_t id_string_info;
+    char id_string[FULL_RECORD_ID_STR_MAX_LENGTH];
+} __attribute__((packed));
+
+/** @struct SensorDataCompactRecord
+ *
+ *  Compact Sensor Record(body) - SDR Type 2
+ */
+struct SensorDataCompactRecordBody
+{
+    uint8_t entity_id;
+    uint8_t entity_instance;
+    uint8_t sensor_initialization;
+    uint8_t sensor_capabilities; // no macro support
+    uint8_t sensor_type;
+    uint8_t event_reading_type;
+    uint8_t supported_assertions[2];          // no macro support
+    uint8_t supported_deassertions[2];        // no macro support
+    uint8_t discrete_reading_setting_mask[2]; // no macro support
+    uint8_t sensor_units_1;
+    uint8_t sensor_units_2_base;
+    uint8_t sensor_units_3_modifier;
+    uint8_t record_sharing[2];
+    uint8_t positive_threshold_hysteresis;
+    uint8_t negative_threshold_hysteresis;
+    uint8_t reserved[3];
+    uint8_t oem_reserved;
+    uint8_t id_string_info;
+    char id_string[FULL_RECORD_ID_STR_MAX_LENGTH];
+} __attribute__((packed));
+
+/** @struct SensorDataEventRecord
+ *
+ *  Event Only Sensor Record(body) - SDR Type 3
+ */
+struct SensorDataEventRecordBody
+{
+    uint8_t entity_id;
+    uint8_t entity_instance;
+    uint8_t sensor_type;
+    uint8_t event_reading_type;
+    uint8_t sensor_record_sharing_1;
+    uint8_t sensor_record_sharing_2;
+    uint8_t reserved;
+    uint8_t oem_reserved;
+    uint8_t id_string_info;
+    char id_string[FULL_RECORD_ID_STR_MAX_LENGTH];
+} __attribute__((packed));
+
+/** @struct SensorDataFruRecordBody
+ *
+ *  FRU Device Locator Record(body) - SDR Type 11
+ */
+struct SensorDataFruRecordBody
+{
+    uint8_t reserved;
+    uint8_t deviceType;
+    uint8_t deviceTypeModifier;
+    uint8_t entityID;
+    uint8_t entityInstance;
+    uint8_t oem;
+    uint8_t deviceIDLen;
+    char deviceID[FRU_RECORD_DEVICE_ID_MAX_LENGTH];
+} __attribute__((packed));
+
+/** @struct SensorDataEntityRecordBody
+ *
+ *  Entity Association Record(body) - SDR Type 8
+ */
+struct SensorDataEntityRecordBody
+{
+    uint8_t entityId2;
+    uint8_t entityInstance2;
+    uint8_t entityId3;
+    uint8_t entityInstance3;
+    uint8_t entityId4;
+    uint8_t entityInstance4;
+} __attribute__((packed));
+
+namespace body
+{
+
+inline void set_entity_instance_number(uint8_t n,
+                                       SensorDataFullRecordBody* body)
+{
+    body->entity_instance &= 1 << 7;
+    body->entity_instance |= (n & ~(1 << 7));
+};
+inline void set_entity_physical_entity(SensorDataFullRecordBody* body)
+{
+    body->entity_instance &= ~(1 << 7);
+};
+inline void set_entity_logical_container(SensorDataFullRecordBody* body)
+{
+    body->entity_instance |= 1 << 7;
+};
+
+inline void sensor_scanning_state(bool enabled, SensorDataFullRecordBody* body)
+{
+    if (enabled)
+    {
+        body->sensor_initialization |= 1 << 0;
+    }
+    else
+    {
+        body->sensor_initialization &= ~(1 << 0);
+    };
+};
+inline void event_generation_state(bool enabled, SensorDataFullRecordBody* body)
+{
+    if (enabled)
+    {
+        body->sensor_initialization |= 1 << 1;
+    }
+    else
+    {
+        body->sensor_initialization &= ~(1 << 1);
+    }
+};
+inline void init_types_state(bool enabled, SensorDataFullRecordBody* body)
+{
+    if (enabled)
+    {
+        body->sensor_initialization |= 1 << 2;
+    }
+    else
+    {
+        body->sensor_initialization &= ~(1 << 2);
+    }
+};
+inline void init_hyst_state(bool enabled, SensorDataFullRecordBody* body)
+{
+    if (enabled)
+    {
+        body->sensor_initialization |= 1 << 3;
+    }
+    else
+    {
+        body->sensor_initialization &= ~(1 << 3);
+    }
+};
+inline void init_thresh_state(bool enabled, SensorDataFullRecordBody* body)
+{
+    if (enabled)
+    {
+        body->sensor_initialization |= 1 << 4;
+    }
+    else
+    {
+        body->sensor_initialization &= ~(1 << 4);
+    }
+};
+inline void init_events_state(bool enabled, SensorDataFullRecordBody* body)
+{
+    if (enabled)
+    {
+        body->sensor_initialization |= 1 << 5;
+    }
+    else
+    {
+        body->sensor_initialization &= ~(1 << 5);
+    }
+};
+inline void init_scanning_state(bool enabled, SensorDataFullRecordBody* body)
+{
+    if (enabled)
+    {
+        body->sensor_initialization |= 1 << 6;
+    }
+    else
+    {
+        body->sensor_initialization &= ~(1 << 6);
+    }
+};
+inline void init_settable_state(bool enabled, SensorDataFullRecordBody* body)
+{
+    if (enabled)
+    {
+        body->sensor_initialization |= 1 << 7;
+    }
+    else
+    {
+        body->sensor_initialization &= ~(1 << 7);
+    }
+};
+
+inline void set_percentage(SensorDataFullRecordBody* body)
+{
+    body->sensor_units_1 |= 1 << 0;
+};
+inline void unset_percentage(SensorDataFullRecordBody* body)
+{
+    body->sensor_units_1 &= ~(1 << 0);
+};
+inline void set_modifier_operation(uint8_t op, SensorDataFullRecordBody* body)
+{
+    body->sensor_units_1 &= ~(3 << 1);
+    body->sensor_units_1 |= (op & 0x3) << 1;
+};
+inline void set_rate_unit(uint8_t unit, SensorDataFullRecordBody* body)
+{
+    body->sensor_units_1 &= ~(7 << 3);
+    body->sensor_units_1 |= (unit & 0x7) << 3;
+};
+inline void set_analog_data_format(uint8_t format,
+                                   SensorDataFullRecordBody* body)
+{
+    body->sensor_units_1 &= ~(3 << 6);
+    body->sensor_units_1 |= (format & 0x3) << 6;
+};
+
+inline void set_m(uint16_t m, SensorDataFullRecordBody* body)
+{
+    body->m_lsb = m & 0xff;
+    body->m_msb_and_tolerance &= ~(3 << 6);
+    body->m_msb_and_tolerance |= ((m & (3 << 8)) >> 2);
+};
+inline void set_tolerance(uint8_t tol, SensorDataFullRecordBody* body)
+{
+    body->m_msb_and_tolerance &= ~0x3f;
+    body->m_msb_and_tolerance |= tol & 0x3f;
+};
+
+inline void set_b(uint16_t b, SensorDataFullRecordBody* body)
+{
+    body->b_lsb = b & 0xff;
+    body->b_msb_and_accuracy_lsb &= ~(3 << 6);
+    body->b_msb_and_accuracy_lsb |= ((b & (3 << 8)) >> 2);
+};
+inline void set_accuracy(uint16_t acc, SensorDataFullRecordBody* body)
+{
+    // bottom 6 bits
+    body->b_msb_and_accuracy_lsb &= ~0x3f;
+    body->b_msb_and_accuracy_lsb |= acc & 0x3f;
+    // top 4 bits
+    body->accuracy_and_sensor_direction &= 0x0f;
+    body->accuracy_and_sensor_direction |= ((acc >> 6) & 0xf) << 4;
+};
+inline void set_accuracy_exp(uint8_t exp, SensorDataFullRecordBody* body)
+{
+    body->accuracy_and_sensor_direction &= ~(3 << 2);
+    body->accuracy_and_sensor_direction |= (exp & 3) << 2;
+};
+inline void set_sensor_dir(uint8_t dir, SensorDataFullRecordBody* body)
+{
+    body->accuracy_and_sensor_direction &= ~(3 << 0);
+    body->accuracy_and_sensor_direction |= (dir & 3);
+};
+
+inline void set_b_exp(uint8_t exp, SensorDataFullRecordBody* body)
+{
+    body->r_b_exponents &= 0xf0;
+    body->r_b_exponents |= exp & 0x0f;
+};
+inline void set_r_exp(uint8_t exp, SensorDataFullRecordBody* body)
+{
+    body->r_b_exponents &= 0x0f;
+    body->r_b_exponents |= (exp & 0x0f) << 4;
+};
+
+inline void set_id_strlen(uint8_t len, SensorDataFullRecordBody* body)
+{
+    body->id_string_info &= ~(0x1f);
+    body->id_string_info |= len & 0x1f;
+};
+inline void set_id_strlen(uint8_t len, SensorDataEventRecordBody* body)
+{
+    body->id_string_info &= ~(0x1f);
+    body->id_string_info |= len & 0x1f;
+};
+inline uint8_t get_id_strlen(SensorDataFullRecordBody* body)
+{
+    return body->id_string_info & 0x1f;
+};
+inline void set_id_type(uint8_t type, SensorDataFullRecordBody* body)
+{
+    body->id_string_info &= ~(3 << 6);
+    body->id_string_info |= (type & 0x3) << 6;
+};
+inline void set_id_type(uint8_t type, SensorDataEventRecordBody* body)
+{
+    body->id_string_info &= ~(3 << 6);
+    body->id_string_info |= (type & 0x3) << 6;
+};
+
+inline void set_device_id_strlen(uint8_t len, SensorDataFruRecordBody* body)
+{
+    body->deviceIDLen &= ~(LENGTH_MASK);
+    body->deviceIDLen |= len & LENGTH_MASK;
+};
+
+inline uint8_t get_device_id_strlen(SensorDataFruRecordBody* body)
+{
+    return body->deviceIDLen & LENGTH_MASK;
+};
+
+inline void set_readable_mask(uint8_t mask, SensorDataFullRecordBody* body)
+{
+    body->discrete_reading_setting_mask[1] = mask & 0x3F;
+}
+
+} // namespace body
+
+// More types contained in section 43.17 Sensor Unit Type Codes,
+// IPMI spec v2 rev 1.1
+enum SensorUnitTypeCodes
+{
+    SENSOR_UNIT_UNSPECIFIED = 0,
+    SENSOR_UNIT_DEGREES_C = 1,
+    SENSOR_UNIT_VOLTS = 4,
+    SENSOR_UNIT_AMPERES = 5,
+    SENSOR_UNIT_WATTS = 6,
+    SENSOR_UNIT_JOULES = 7,
+    SENSOR_UNIT_RPM = 18,
+    SENSOR_UNIT_METERS = 34,
+    SENSOR_UNIT_REVOLUTIONS = 41,
+};
+
+struct SensorDataFullRecord
+{
+    SensorDataRecordHeader header;
+    SensorDataRecordKey key;
+    SensorDataFullRecordBody body;
+} __attribute__((packed));
+
+/** @struct SensorDataComapactRecord
+ *
+ *  Compact Sensor Record - SDR Type 2
+ */
+struct SensorDataCompactRecord
+{
+    SensorDataRecordHeader header;
+    SensorDataRecordKey key;
+    SensorDataCompactRecordBody body;
+} __attribute__((packed));
+
+/** @struct SensorDataEventRecord
+ *
+ *  Event Only Sensor Record - SDR Type 3
+ */
+struct SensorDataEventRecord
+{
+    SensorDataRecordHeader header;
+    SensorDataRecordKey key;
+    SensorDataEventRecordBody body;
+} __attribute__((packed));
+
+/** @struct SensorDataFruRecord
+ *
+ *  FRU Device Locator Record - SDR Type 11
+ */
+struct SensorDataFruRecord
+{
+    SensorDataRecordHeader header;
+    SensorDataFruRecordKey key;
+    SensorDataFruRecordBody body;
+} __attribute__((packed));
+
+/** @struct SensorDataEntityRecord
+ *
+ *  Entity Association Record - SDR Type 8
+ */
+struct SensorDataEntityRecord
+{
+    SensorDataRecordHeader header;
+    SensorDataEntityRecordKey key;
+    SensorDataEntityRecordBody body;
+} __attribute__((packed));
+
+} // namespace get_sdr
+
+namespace ipmi
+{
+
+namespace sensor
+{
+
+/**
+ * @brief Map offset to the corresponding bit in the assertion byte.
+ *
+ * The discrete sensors support up to 14 states. 0-7 offsets are stored in one
+ * byte and offsets 8-14 in the second byte.
+ *
+ * @param[in] offset - offset number.
+ * @param[in/out] resp - get sensor reading response.
+ */
+inline void setOffset(uint8_t offset, ipmi::sensor::GetSensorResponse* resp)
+{
+    if (offset > 7)
+    {
+        resp->discreteReadingSensorStates |= 1 << (offset - 8);
+    }
+    else
+    {
+        resp->thresholdLevelsStates |= 1 << offset;
+    }
+}
+
+/**
+ * @brief Set the reading field in the response.
+ *
+ * @param[in] offset - offset number.
+ * @param[in/out] resp - get sensor reading response.
+ */
+inline void setReading(uint8_t value, ipmi::sensor::GetSensorResponse* resp)
+{
+    resp->reading = value;
+}
+
+/**
+ * @brief Map the value to the assertion bytes. The assertion states are stored
+ *        in 2 bytes.
+ *
+ * @param[in] value - value to mapped to the assertion byte.
+ * @param[in/out] resp - get sensor reading response.
+ */
+inline void setAssertionBytes(uint16_t value,
+                              ipmi::sensor::GetSensorResponse* resp)
+{
+    resp->thresholdLevelsStates = static_cast<uint8_t>(value & 0x00FF);
+    resp->discreteReadingSensorStates = static_cast<uint8_t>(value >> 8);
+}
+
+/**
+ * @brief Set the scanning enabled bit in the response.
+ *
+ * @param[in/out] resp - get sensor reading response.
+ */
+inline void enableScanning(ipmi::sensor::GetSensorResponse* resp)
+{
+    resp->readingOrStateUnavailable = false;
+    resp->scanningEnabled = true;
+    resp->allEventMessagesEnabled = false;
+}
+
+} // namespace sensor
+
+} // namespace ipmi
diff --git a/settings.cpp b/settings.cpp
new file mode 100644
index 0000000..4564fa4
--- /dev/null
+++ b/settings.cpp
@@ -0,0 +1,74 @@
+#include "settings.hpp"
+
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+namespace settings
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::error::xyz::openbmc_project::common;
+
+constexpr auto mapperService = "xyz.openbmc_project.ObjectMapper";
+constexpr auto mapperPath = "/xyz/openbmc_project/object_mapper";
+constexpr auto mapperIntf = "xyz.openbmc_project.ObjectMapper";
+
+Objects::Objects(sdbusplus::bus_t& bus, const std::vector<Interface>& filter) :
+    bus(bus)
+{
+    ipmi::ObjectTree objectTree;
+    try
+    {
+        objectTree = ipmi::getSubTree(bus, filter);
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Failed to call the getSubTree method: {ERROR}", "ERROR", e);
+        elog<InternalFailure>();
+    }
+
+    for (auto& iter : objectTree)
+    {
+        const auto& path = iter.first;
+        for (auto& interface : iter.second.begin()->second)
+        {
+            auto found = map.find(interface);
+            if (map.end() != found)
+            {
+                auto& paths = found->second;
+                paths.push_back(path);
+            }
+            else
+            {
+                map.emplace(std::move(interface), std::vector<Path>({path}));
+            }
+        }
+    }
+}
+
+Service Objects::service(const Path& path, const Interface& interface) const
+{
+    using Interfaces = std::vector<Interface>;
+    auto mapperCall =
+        bus.new_method_call(mapperService, mapperPath, mapperIntf, "GetObject");
+    mapperCall.append(path);
+    mapperCall.append(Interfaces({interface}));
+
+    std::map<Service, Interfaces> result;
+    try
+    {
+        auto response = bus.call(mapperCall);
+        response.read(result);
+        return result.begin()->first;
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Invalid response from mapper: {ERROR}", "ERROR", e);
+        elog<InternalFailure>();
+    }
+}
+
+} // namespace settings
diff --git a/settings.hpp b/settings.hpp
new file mode 100644
index 0000000..a3505c3
--- /dev/null
+++ b/settings.hpp
@@ -0,0 +1,52 @@
+#pragma once
+
+#include <sdbusplus/bus.hpp>
+
+#include <string>
+#include <tuple>
+
+namespace settings
+{
+
+using Path = std::string;
+using Service = std::string;
+using Interface = std::string;
+
+/** @class Objects
+ *  @brief Fetch paths of settings d-bus objects of interest, upon construction
+ */
+struct Objects
+{
+  public:
+    /** @brief Constructor - fetch settings objects
+     *
+     * @param[in] bus - The Dbus bus object
+     * @param[in] filter - A vector of settings interfaces the caller is
+     *            interested in.
+     */
+    Objects(sdbusplus::bus_t& bus, const std::vector<Interface>& filter);
+    Objects(const Objects&) = default;
+    Objects& operator=(const Objects&) = delete;
+    Objects(Objects&&) = delete;
+    Objects& operator=(Objects&&) = delete;
+    ~Objects() = default;
+
+    /** @brief Fetch d-bus service, given a path and an interface. The
+     *         service can't be cached because mapper returns unique
+     *         service names.
+     *
+     * @param[in] path - The Dbus object
+     * @param[in] interface - The Dbus interface
+     *
+     * @return std::string - the dbus service
+     */
+    Service service(const Path& path, const Interface& interface) const;
+
+    /** @brief map of settings objects */
+    std::map<Interface, std::vector<Path>> map;
+
+    /** @brief The Dbus bus object */
+    sdbusplus::bus_t& bus;
+};
+
+} // namespace settings
diff --git a/softoff/mainapp.cpp b/softoff/mainapp.cpp
new file mode 100644
index 0000000..0791615
--- /dev/null
+++ b/softoff/mainapp.cpp
@@ -0,0 +1,82 @@
+/**
+ * Copyright © 2016 IBM 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.
+ */
+#include "config.h"
+
+#include "softoff.hpp"
+
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/exception.hpp>
+#include <xyz/openbmc_project/State/Host/error.hpp>
+
+// Return -1 on any errors to ensure we follow the calling targets OnFailure=
+// path
+int main(int, char**)
+{
+    using namespace phosphor::logging;
+
+    // Get a handle to system dbus.
+    auto bus = sdbusplus::bus::new_default();
+
+    // Add systemd object manager.
+    sdbusplus::server::manager_t(bus, SOFTOFF_OBJPATH);
+
+    // Get default event loop
+    auto event = sdeventplus::Event::get_default();
+
+    // Attach the bus to sd_event to service user requests
+    bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
+
+    // Claim the bus. Delaying it until sending SMS_ATN may result
+    // in a race condition between this available and IPMI trying to send
+    // message as a response to ack from host.
+    bus.request_name(SOFTOFF_BUSNAME);
+
+    // Create the SoftPowerOff object.
+    phosphor::ipmi::SoftPowerOff powerObj(bus, event.get(), SOFTOFF_OBJPATH);
+
+    // Wait for client requests until this application has processed
+    // at least one successful SoftPowerOff or we timed out
+    while (!powerObj.isCompleted() && !powerObj.isTimerExpired())
+    {
+        try
+        {
+            event.run(std::nullopt);
+        }
+        catch (const sdeventplus::SdEventError& e)
+        {
+            lg2::error("Failure in processing request: {ERROR}", "ERROR", e);
+            return 1;
+        }
+    }
+
+    // Log an error if we timed out after getting Ack for SMS_ATN and before
+    // getting the Host Shutdown response
+    if (powerObj.isTimerExpired() &&
+        (powerObj.responseReceived() ==
+         phosphor::ipmi::Base::SoftPowerOff::HostResponse::SoftOffReceived))
+    {
+        using error =
+            sdbusplus::error::xyz::openbmc_project::state::host::SoftOffTimeout;
+        using errorMetadata = xyz::openbmc_project::state::host::SoftOffTimeout;
+        report<error>(prev_entry<errorMetadata::TIMEOUT_IN_MSEC>());
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/softoff/meson.build b/softoff/meson.build
new file mode 100644
index 0000000..a723c51
--- /dev/null
+++ b/softoff/meson.build
@@ -0,0 +1,34 @@
+softpower_pre = [
+    boost,
+    ipmid_dep,
+    libsystemd_dep,
+    phosphor_dbus_interfaces_dep,
+    phosphor_logging_dep,
+    sdbusplus_dep,
+    sdeventplus_dep,
+    softoff_dbus,
+]
+
+softpower_lib = static_library(
+    'softpower_lib',
+    'softoff.cpp',
+    conf_h,
+    dependencies: softpower_pre,
+    include_directories: root_inc,
+)
+
+softpower_dep = declare_dependency(
+    dependencies: softpower_pre,
+    include_directories: root_inc,
+    link_with: softpower_lib,
+)
+
+executable(
+    'phosphor-softpoweroff',
+    'mainapp.cpp',
+    implicit_include_directories: false,
+    dependencies: softpower_dep,
+    include_directories: root_inc,
+    install: true,
+    install_dir: get_option('bindir'),
+)
diff --git a/softoff/softoff.cpp b/softoff/softoff.cpp
new file mode 100644
index 0000000..96695b9
--- /dev/null
+++ b/softoff/softoff.cpp
@@ -0,0 +1,136 @@
+/**
+ * Copyright © 2016 IBM 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.
+ */
+#include "config.h"
+
+#include "softoff.hpp"
+
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Control/Host/server.hpp>
+
+#include <chrono>
+namespace phosphor
+{
+namespace ipmi
+{
+
+using namespace sdbusplus::server::xyz::openbmc_project::control;
+
+void SoftPowerOff::sendHostShutDownCmd()
+{
+    auto ctrlHostPath =
+        std::string{CONTROL_HOST_OBJ_MGR} + '/' + HOST_NAME + '0';
+    auto host = ::ipmi::getService(this->bus, CONTROL_HOST_BUSNAME,
+                                   ctrlHostPath.c_str());
+
+    auto method = bus.new_method_call(host.c_str(), ctrlHostPath.c_str(),
+                                      CONTROL_HOST_BUSNAME, "Execute");
+
+    method.append(convertForMessage(Host::Command::SoftOff).c_str());
+    try
+    {
+        auto reply = bus.call(method);
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Error in call to control host Execute: {ERROR}", "ERROR",
+                   e);
+        // TODO openbmc/openbmc#851 - Once available, throw returned error
+        throw std::runtime_error("Error in call to control host Execute");
+    }
+}
+
+// Function called on host control signals
+void SoftPowerOff::hostControlEvent(sdbusplus::message_t& msg)
+{
+    std::string cmdCompleted{};
+    std::string cmdStatus{};
+
+    msg.read(cmdCompleted, cmdStatus);
+
+    lg2::debug(
+        "Host control signal values, command: {COMMAND}, status:{STATUS}",
+        "COMMAND", cmdCompleted, "STATUS", cmdStatus);
+
+    if (Host::convertResultFromString(cmdStatus) == Host::Result::Success)
+    {
+        // Set our internal property indicating we got host attention
+        sdbusplus::server::xyz::openbmc_project::ipmi::internal::SoftPowerOff::
+            responseReceived(HostResponse::SoftOffReceived);
+
+        // Start timer for host shutdown
+        using namespace std::chrono;
+        auto time = duration_cast<microseconds>(
+            seconds(IPMI_HOST_SHUTDOWN_COMPLETE_TIMEOUT_SECS));
+        auto r = startTimer(time);
+        if (r < 0)
+        {
+            lg2::error(
+                "Failure to start Host shutdown wait timer, ERRNO: {ERRNO}",
+                "ERRNO", lg2::hex, -r);
+        }
+        else
+        {
+            lg2::info("Timer started waiting for host to shutdown, "
+                      "TIMEOUT_IN_MSEC: {TIMEOUT_IN_MSEC}",
+                      "TIMEOUT_IN_MSEC",
+                      (duration_cast<milliseconds>(
+                           seconds(IPMI_HOST_SHUTDOWN_COMPLETE_TIMEOUT_SECS)))
+                          .count());
+        }
+    }
+    else
+    {
+        // An error on the initial attention is not considered an error, just
+        // exit normally and allow remaining shutdown targets to run
+        lg2::info("Timeout on host attention, continue with power down");
+        completed = true;
+    }
+    return;
+}
+
+// Starts a timer
+int SoftPowerOff::startTimer(const std::chrono::microseconds& usec)
+{
+    return timer.start(usec);
+}
+
+// Host Response handler
+auto SoftPowerOff::responseReceived(HostResponse response) -> HostResponse
+{
+    using namespace std::chrono;
+
+    if (response == HostResponse::HostShutdown)
+    {
+        // Disable the timer since Host has quiesced and we are
+        // done with soft power off part
+        auto r = timer.stop();
+        if (r < 0)
+        {
+            lg2::error("Failure to STOP the timer, ERRNO: {ERRNO}", "ERRNO",
+                       lg2::hex, -r);
+        }
+
+        // This marks the completion of soft power off sequence.
+        completed = true;
+    }
+
+    return sdbusplus::server::xyz::openbmc_project::ipmi::internal::
+        SoftPowerOff::responseReceived(response);
+}
+
+} // namespace ipmi
+} // namespace phosphor
diff --git a/softoff/softoff.hpp b/softoff/softoff.hpp
new file mode 100644
index 0000000..14742e1
--- /dev/null
+++ b/softoff/softoff.hpp
@@ -0,0 +1,145 @@
+#pragma once
+
+#include "config.h"
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <sdbusplus/timer.hpp>
+#include <xyz/openbmc_project/Control/Host/server.hpp>
+#include <xyz/openbmc_project/Ipmi/Internal/SoftPowerOff/server.hpp>
+
+#include <functional>
+namespace phosphor
+{
+namespace ipmi
+{
+
+namespace Base = sdbusplus::server::xyz::openbmc_project::ipmi::internal;
+using namespace sdbusplus::server::xyz::openbmc_project::control;
+
+namespace sdbusRule = sdbusplus::bus::match::rules;
+
+namespace
+{
+using SoftPowerOffInherit = sdbusplus::server::object_t<Base::SoftPowerOff>;
+}
+
+/** @class SoftPowerOff
+ *  @brief Responsible for coordinating Host SoftPowerOff operation
+ */
+class SoftPowerOff : public SoftPowerOffInherit
+{
+  public:
+    /** @brief Constructs SoftPowerOff object.
+     *
+     *  @param[in] bus       - system dbus handler
+     *  @param[in] event     - sd_event handler
+     *  @param[in] objPath   - The Dbus path hosting SoftPowerOff function
+     */
+    SoftPowerOff(sdbusplus::bus_t& bus, sd_event* event, const char* objPath) :
+        SoftPowerOffInherit(bus, objPath,
+                            SoftPowerOffInherit::action::defer_emit),
+        bus(bus), timer(event),
+        hostControlSignal(
+            bus,
+            sdbusRule::type::signal() + sdbusRule::member("CommandComplete") +
+                sdbusRule::path("/xyz/openbmc_project/control/host0") +
+                sdbusRule::interface(CONTROL_HOST_BUSNAME) +
+                sdbusRule::argN(0, convertForMessage(Host::Command::SoftOff)),
+            std::bind(std::mem_fn(&SoftPowerOff::hostControlEvent), this,
+                      std::placeholders::_1))
+    {
+        // Need to announce since we may get the response
+        // very quickly on host shutdown command
+        emit_object_added();
+
+        // The whole purpose of this application is to send a host shutdown
+        // command and watch for the soft power off to go through. We need
+        // the interface added signal emitted before we send the shutdown
+        // command just to attend to lightning fast response from host
+        sendHostShutDownCmd();
+    }
+
+    /** @brief Tells if the objective of this application is completed */
+    inline auto isCompleted()
+    {
+        return completed;
+    }
+
+    /** @brief Tells if the referenced timer is expired or not */
+    inline auto isTimerExpired()
+    {
+        return timer.isExpired();
+    }
+
+    /** @brief overloaded property setter function
+     *
+     *  @param[in] value - One of SoftOffReceived / HostShutdown
+     *
+     *  @return Success or exception thrown
+     */
+    HostResponse responseReceived(HostResponse value) override;
+
+    /** @brief Using the base class's getter method */
+    using Base::SoftPowerOff::responseReceived;
+
+    /** @brief Calls to start a timer
+     *
+     *  @param[in] usec - Time in microseconds
+     *
+     *  @return Success or exception thrown
+     */
+    int startTimer(const std::chrono::microseconds& usec);
+
+  private:
+    // Need this to send SMS_ATTN
+    // TODO : Switch over to using mapper service in a different patch
+    static constexpr auto HOST_IPMI_BUS = "org.openbmc.HostIpmi";
+    static constexpr auto HOST_IPMI_OBJ = "/org/openbmc/HostIpmi/1";
+    static constexpr auto HOST_IPMI_INTF = "org.openbmc.HostIpmi";
+
+    /* @brief sdbusplus handle */
+    sdbusplus::bus_t& bus;
+
+    /** @brief Reference to Timer object */
+    sdbusplus::Timer timer;
+
+    /** @brief Marks the end of life of this application.
+     *
+     *  This is set to true if host gives appropriate responses
+     *  for the sequence of commands.
+     */
+    bool completed = false;
+
+    /** @brief Subscribe to host control signals
+     *
+     *  Protocol is to send the host power off request to the host
+     *  control interface and then wait for a signal indicating pass/fail
+     **/
+    sdbusplus::bus::match_t hostControlSignal;
+
+    /** @brief Sends host control command to tell host to shut down
+     *
+     *  After sending the command, wait for a signal indicating the status
+     *  of the command.
+     *
+     *  After receiving the initial response, start a timer for 30 minutes
+     *  to let host do a clean shutdown of partitions. When the response is
+     *  received from the host, it indicates that BMC can do a power off.
+     *  If BMC fails to get any response, then a hard power off would
+     *  be forced.
+     *
+     *  @return - Does not return anything. Error will result in exception
+     *            being thrown
+     */
+    void sendHostShutDownCmd();
+
+    /** @brief Callback function on host control signals
+     *
+     * @param[in]  msg       - Data associated with subscribed signal
+     *
+     */
+    void hostControlEvent(sdbusplus::message_t& msg);
+};
+} // namespace ipmi
+} // namespace phosphor
diff --git a/storageaddsel.cpp b/storageaddsel.cpp
new file mode 100644
index 0000000..2b9866f
--- /dev/null
+++ b/storageaddsel.cpp
@@ -0,0 +1,69 @@
+#include "error-HostEvent.hpp"
+#include "sensorhandler.hpp"
+
+#include <systemd/sd-bus.h>
+
+#include <ipmid/api.hpp>
+#include <ipmid/types.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Logging/Entry/server.hpp>
+
+#include <algorithm>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <vector>
+
+using namespace std;
+using namespace phosphor::logging;
+using namespace sdbusplus::server::xyz::openbmc_project::logging;
+
+std::string readESEL(const char* fileName)
+{
+    std::string content;
+    std::ifstream handle(fileName);
+
+    if (handle.fail())
+    {
+        lg2::error("Failed to open eSEL, file name: {FILENAME}", "FILENAME",
+                   fileName);
+        return content;
+    }
+
+    handle.seekg(0, std::ios::end);
+    content.resize(handle.tellg());
+    handle.seekg(0, std::ios::beg);
+    handle.read(&content[0], content.size());
+    handle.close();
+
+    return content;
+}
+
+void createProcedureLogEntry(uint8_t procedureNum)
+{
+    // Read the eSEL data from the file.
+    static constexpr auto eSELFile = "/tmp/esel";
+    auto eSELData = readESEL(eSELFile);
+
+    // Each byte in eSEL is formatted as %02x with a space between bytes and
+    // insert '/0' at the end of the character array.
+    static constexpr auto byteSeparator = 3;
+    std::unique_ptr<char[]> data(
+        new char[(eSELData.size() * byteSeparator) + 1]());
+
+    for (size_t i = 0; i < eSELData.size(); i++)
+    {
+        sprintf(&data[i * byteSeparator], "%02x ", eSELData[i]);
+    }
+    data[eSELData.size() * byteSeparator] = '\0';
+
+    using error = sdbusplus::error::org::open_power::host::MaintenanceProcedure;
+    using metadata = org::open_power::host::MaintenanceProcedure;
+
+    report<error>(metadata::ESEL(data.get()),
+                  metadata::PROCEDURE(static_cast<uint32_t>(procedureNum)));
+}
diff --git a/storageaddsel.hpp b/storageaddsel.hpp
new file mode 100644
index 0000000..9183472
--- /dev/null
+++ b/storageaddsel.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+#include <stdint.h>
+
+#include <string>
+
+/** @brief Read eSEL data into a string
+ *
+ *  @param[in] filename - filename of file containing eSEL
+ *
+ *  @return On success return the eSEL data
+ */
+std::string readESEL(const char* filename);
+
+/** @brief Create a log entry with maintenance procedure
+ *
+ *  @param[in] procedureNum - procedure number associated with the log entry
+ */
+void createProcedureLogEntry(uint8_t procedureNum);
diff --git a/storagehandler.cpp b/storagehandler.cpp
new file mode 100644
index 0000000..d85673f
--- /dev/null
+++ b/storagehandler.cpp
@@ -0,0 +1,961 @@
+#include "config.h"
+
+#include "fruread.hpp"
+#include "read_fru_data.hpp"
+#include "selutility.hpp"
+#include "sensorhandler.hpp"
+#include "storageaddsel.hpp"
+
+#include <arpa/inet.h>
+#include <systemd/sd-bus.h>
+
+#include <ipmid/api.hpp>
+#include <ipmid/entity_map_json.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/server.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/Logging/SEL/error.hpp>
+
+#include <algorithm>
+#include <chrono>
+#include <cstdio>
+#include <cstring>
+#include <filesystem>
+#include <optional>
+#include <string>
+#include <variant>
+
+void registerNetFnStorageFunctions() __attribute__((constructor));
+
+unsigned int g_sel_time = 0xFFFFFFFF;
+namespace ipmi
+{
+namespace sensor
+{
+extern const IdInfoMap sensors;
+} // namespace sensor
+} // namespace ipmi
+extern const ipmi::sensor::InvObjectIDMap invSensors;
+extern const FruMap frus;
+constexpr uint8_t eventDataSize = 3;
+namespace
+{
+constexpr auto SystemdTimeService = "org.freedesktop.timedate1";
+constexpr auto SystemdTimePath = "/org/freedesktop/timedate1";
+constexpr auto SystemdTimeInterface = "org.freedesktop.timedate1";
+
+constexpr auto TIME_INTERFACE = "xyz.openbmc_project.Time.EpochTime";
+constexpr auto BMC_TIME_PATH = "/xyz/openbmc_project/time/bmc";
+constexpr auto DBUS_PROPERTIES = "org.freedesktop.DBus.Properties";
+constexpr auto PROPERTY_ELAPSED = "Elapsed";
+} // namespace
+
+using InternalFailure =
+    sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+using namespace phosphor::logging;
+using namespace ipmi::fru;
+using namespace xyz::openbmc_project::logging::sel;
+using SELCreated =
+    sdbusplus::error::xyz::openbmc_project::logging::sel::Created;
+
+using SELRecordID = uint16_t;
+using SELEntry = ipmi::sel::SELEventRecordFormat;
+using SELCacheMap = std::map<SELRecordID, SELEntry>;
+
+SELCacheMap selCacheMap __attribute__((init_priority(101)));
+bool selCacheMapInitialized;
+std::unique_ptr<sdbusplus::bus::match_t> selAddedMatch
+    __attribute__((init_priority(101)));
+std::unique_ptr<sdbusplus::bus::match_t> selRemovedMatch
+    __attribute__((init_priority(101)));
+std::unique_ptr<sdbusplus::bus::match_t> selUpdatedMatch
+    __attribute__((init_priority(101)));
+
+static inline uint16_t getLoggingId(const std::string& p)
+{
+    namespace fs = std::filesystem;
+    fs::path entryPath(p);
+    return std::stoul(entryPath.filename().string());
+}
+
+static inline std::string getLoggingObjPath(uint16_t id)
+{
+    return std::string(ipmi::sel::logBasePath) + "/" + std::to_string(id);
+}
+
+std::optional<std::pair<uint16_t, SELEntry>> parseLoggingEntry(
+    const std::string& p)
+{
+    try
+    {
+        auto id = getLoggingId(p);
+        ipmi::sel::GetSELEntryResponse record{};
+        record = ipmi::sel::convertLogEntrytoSEL(p);
+        return std::pair<uint16_t, SELEntry>({id, std::move(record.event)});
+    }
+    catch (const std::exception& e)
+    {
+        fprintf(stderr, "Failed to convert %s to SEL: %s\n", p.c_str(),
+                e.what());
+    }
+    return std::nullopt;
+}
+
+static void selAddedCallback(sdbusplus::message_t& m)
+{
+    sdbusplus::message::object_path objPath;
+    try
+    {
+        m.read(objPath);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error("Failed to read object path");
+        return;
+    }
+    std::string p = objPath;
+    auto entry = parseLoggingEntry(p);
+    if (entry)
+    {
+        selCacheMap.insert(std::move(*entry));
+    }
+}
+
+static void selRemovedCallback(sdbusplus::message_t& m)
+{
+    sdbusplus::message::object_path objPath;
+    try
+    {
+        m.read(objPath);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error("Failed to read object path");
+    }
+    try
+    {
+        std::string p = objPath;
+        selCacheMap.erase(getLoggingId(p));
+    }
+    catch (const std::invalid_argument& e)
+    {
+        lg2::error("Invalid logging entry ID");
+    }
+}
+
+static void selUpdatedCallback(sdbusplus::message_t& m)
+{
+    std::string p = m.get_path();
+    auto entry = parseLoggingEntry(p);
+    if (entry)
+    {
+        selCacheMap.insert_or_assign(entry->first, std::move(entry->second));
+    }
+}
+
+void registerSelCallbackHandler()
+{
+    using namespace sdbusplus::bus::match::rules;
+    sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+    if (!selAddedMatch)
+    {
+        selAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
+            bus, interfacesAdded(ipmi::sel::logWatchPath),
+            std::bind(selAddedCallback, std::placeholders::_1));
+    }
+    if (!selRemovedMatch)
+    {
+        selRemovedMatch = std::make_unique<sdbusplus::bus::match_t>(
+            bus, interfacesRemoved(ipmi::sel::logWatchPath),
+            std::bind(selRemovedCallback, std::placeholders::_1));
+    }
+    if (!selUpdatedMatch)
+    {
+        selUpdatedMatch = std::make_unique<sdbusplus::bus::match_t>(
+            bus,
+            type::signal() + member("PropertiesChanged"s) +
+                interface("org.freedesktop.DBus.Properties"s) +
+                argN(0, ipmi::sel::logEntryIntf),
+            std::bind(selUpdatedCallback, std::placeholders::_1));
+    }
+}
+
+void initSELCache()
+{
+    registerSelCallbackHandler();
+    ipmi::sel::ObjectPaths paths;
+    try
+    {
+        ipmi::sel::readLoggingObjectPaths(paths);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error("Failed to get logging object paths");
+        return;
+    }
+    for (const auto& p : paths)
+    {
+        auto entry = parseLoggingEntry(p);
+        if (entry)
+        {
+            selCacheMap.insert(std::move(*entry));
+        }
+    }
+    selCacheMapInitialized = true;
+}
+
+/**
+ * @enum Device access mode
+ */
+enum class AccessMode
+{
+    bytes, ///< Device is accessed by bytes
+    words  ///< Device is accessed by words
+};
+
+/** @brief implements the get SEL Info command
+ *  @returns IPMI completion code plus response data
+ *   - selVersion - SEL revision
+ *   - entries    - Number of log entries in SEL.
+ *   - freeSpace  - Free Space in bytes.
+ *   - addTimeStamp - Most recent addition timestamp
+ *   - eraseTimeStamp - Most recent erase timestamp
+ *   - operationSupport - Reserve & Delete SEL operations supported
+ */
+
+ipmi::RspType<uint8_t,  // SEL revision.
+              uint16_t, // number of log entries in SEL.
+              uint16_t, // free Space in bytes.
+              uint32_t, // most recent addition timestamp
+              uint32_t, // most recent erase timestamp.
+
+              bool,     // SEL allocation info supported
+              bool,     // reserve SEL supported
+              bool,     // partial Add SEL Entry supported
+              bool,     // delete SEL supported
+              uint3_t,  // reserved
+              bool      // overflow flag
+              >
+    ipmiStorageGetSelInfo()
+{
+    uint16_t entries = 0;
+    // Most recent addition timestamp.
+    uint32_t addTimeStamp = ipmi::sel::invalidTimeStamp;
+
+    if (!selCacheMapInitialized)
+    {
+        // In case the initSELCache() fails, try it again
+        initSELCache();
+    }
+    if (!selCacheMap.empty())
+    {
+        entries = static_cast<uint16_t>(selCacheMap.size());
+
+        try
+        {
+            auto objPath = getLoggingObjPath(selCacheMap.rbegin()->first);
+            addTimeStamp = static_cast<uint32_t>(
+                (ipmi::sel::getEntryTimeStamp(objPath).count()));
+        }
+        catch (const InternalFailure& e)
+        {}
+        catch (const std::runtime_error& e)
+        {
+            lg2::error("runtime error: {ERROR}", "ERROR", e);
+        }
+    }
+
+    constexpr uint8_t selVersion = ipmi::sel::selVersion;
+    constexpr uint16_t freeSpace = 0xFFFF;
+    constexpr uint32_t eraseTimeStamp = ipmi::sel::invalidTimeStamp;
+    constexpr uint3_t reserved{0};
+
+    return ipmi::responseSuccess(
+        selVersion, entries, freeSpace, addTimeStamp, eraseTimeStamp,
+        ipmi::sel::operationSupport::getSelAllocationInfo,
+        ipmi::sel::operationSupport::reserveSel,
+        ipmi::sel::operationSupport::partialAddSelEntry,
+        ipmi::sel::operationSupport::deleteSel, reserved,
+        ipmi::sel::operationSupport::overflow);
+}
+
+ipmi_ret_t getSELEntry(ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t request,
+                       ipmi_response_t response, ipmi_data_len_t data_len,
+                       ipmi_context_t)
+{
+    if (*data_len != sizeof(ipmi::sel::GetSELEntryRequest))
+    {
+        *data_len = 0;
+        return IPMI_CC_REQ_DATA_LEN_INVALID;
+    }
+
+    auto requestData =
+        reinterpret_cast<const ipmi::sel::GetSELEntryRequest*>(request);
+
+    if (requestData->reservationID != 0)
+    {
+        if (!checkSELReservation(requestData->reservationID))
+        {
+            *data_len = 0;
+            return IPMI_CC_INVALID_RESERVATION_ID;
+        }
+    }
+
+    if (!selCacheMapInitialized)
+    {
+        // In case the initSELCache() fails, try it again
+        initSELCache();
+    }
+
+    if (selCacheMap.empty())
+    {
+        *data_len = 0;
+        return IPMI_CC_SENSOR_INVALID;
+    }
+
+    SELCacheMap::const_iterator iter;
+
+    // Check for the requested SEL Entry.
+    if (requestData->selRecordID == ipmi::sel::firstEntry)
+    {
+        iter = selCacheMap.begin();
+    }
+    else if (requestData->selRecordID == ipmi::sel::lastEntry)
+    {
+        if (selCacheMap.size() > 1)
+        {
+            iter = selCacheMap.end();
+            --iter;
+        }
+        else
+        {
+            // Only one entry exists, return the first
+            iter = selCacheMap.begin();
+        }
+    }
+    else
+    {
+        iter = selCacheMap.find(requestData->selRecordID);
+        if (iter == selCacheMap.end())
+        {
+            *data_len = 0;
+            return IPMI_CC_SENSOR_INVALID;
+        }
+    }
+
+    ipmi::sel::GetSELEntryResponse record{0, iter->second};
+    // Identify the next SEL record ID
+    ++iter;
+    if (iter == selCacheMap.end())
+    {
+        record.nextRecordID = ipmi::sel::lastEntry;
+    }
+    else
+    {
+        record.nextRecordID = iter->first;
+    }
+
+    if (requestData->readLength == ipmi::sel::entireRecord)
+    {
+        std::memcpy(response, &record, sizeof(record));
+        *data_len = sizeof(record);
+    }
+    else
+    {
+        if (requestData->offset >= ipmi::sel::selRecordSize ||
+            requestData->readLength > ipmi::sel::selRecordSize)
+        {
+            *data_len = 0;
+            return IPMI_CC_INVALID_FIELD_REQUEST;
+        }
+
+        auto diff = ipmi::sel::selRecordSize - requestData->offset;
+        auto readLength =
+            std::min(diff, static_cast<int>(requestData->readLength));
+
+        uint16_t nextRecordID = record.nextRecordID;
+        std::memcpy(response, &nextRecordID, sizeof(nextRecordID));
+
+        const ipmi::sel::SELEventRecordFormat* evt = &record.event;
+        std::memcpy(static_cast<uint8_t*>(response) + sizeof(nextRecordID),
+                    reinterpret_cast<const uint8_t*>(evt) + requestData->offset,
+                    readLength);
+        *data_len = sizeof(nextRecordID) + readLength;
+    }
+
+    return IPMI_CC_OK;
+}
+
+/** @brief implements the delete SEL entry command
+ * @request
+ *   - reservationID; // reservation ID.
+ *   - selRecordID;   // SEL record ID.
+ *
+ *  @returns ipmi completion code plus response data
+ *   - Record ID of the deleted record
+ */
+ipmi::RspType<uint16_t // deleted record ID
+              >
+    deleteSELEntry(uint16_t reservationID, uint16_t selRecordID)
+{
+    namespace fs = std::filesystem;
+
+    if (!checkSELReservation(reservationID))
+    {
+        return ipmi::responseInvalidReservationId();
+    }
+
+    // Per the IPMI spec, need to cancel the reservation when a SEL entry is
+    // deleted
+    cancelSELReservation();
+
+    if (!selCacheMapInitialized)
+    {
+        // In case the initSELCache() fails, try it again
+        initSELCache();
+    }
+
+    if (selCacheMap.empty())
+    {
+        return ipmi::responseSensorInvalid();
+    }
+
+    SELCacheMap::const_iterator iter;
+    uint16_t delRecordID = 0;
+
+    if (selRecordID == ipmi::sel::firstEntry)
+    {
+        delRecordID = selCacheMap.begin()->first;
+    }
+    else if (selRecordID == ipmi::sel::lastEntry)
+    {
+        delRecordID = selCacheMap.rbegin()->first;
+    }
+    else
+    {
+        delRecordID = selRecordID;
+    }
+
+    iter = selCacheMap.find(delRecordID);
+    if (iter == selCacheMap.end())
+    {
+        return ipmi::responseSensorInvalid();
+    }
+
+    sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+    std::string service;
+
+    auto objPath = getLoggingObjPath(iter->first);
+    try
+    {
+        service = ipmi::getService(bus, ipmi::sel::logDeleteIntf, objPath);
+    }
+    catch (const std::runtime_error& e)
+    {
+        lg2::error("runtime error: {ERROR}", "ERROR", e);
+        return ipmi::responseUnspecifiedError();
+    }
+
+    auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(),
+                                          ipmi::sel::logDeleteIntf, "Delete");
+    try
+    {
+        auto reply = bus.call(methodCall);
+    }
+    catch (const std::exception& e)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    return ipmi::responseSuccess(delRecordID);
+}
+
+/** @brief implements the Clear SEL command
+ * @request
+ *   - reservationID   // Reservation ID.
+ *   - clr             // char array { 'C'(0x43h), 'L'(0x4Ch), 'R'(0x52h) }
+ *   - eraseOperation; // requested operation.
+ *
+ *  @returns ipmi completion code plus response data
+ *   - erase status
+ */
+
+ipmi::RspType<uint8_t // erase status
+              >
+    clearSEL(uint16_t reservationID, const std::array<char, 3>& clr,
+             uint8_t eraseOperation)
+{
+    static constexpr std::array<char, 3> clrOk = {'C', 'L', 'R'};
+    if (clr != clrOk)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    if (!checkSELReservation(reservationID))
+    {
+        return ipmi::responseInvalidReservationId();
+    }
+
+    /*
+     * Erasure status cannot be fetched from DBUS, so always return erasure
+     * status as `erase completed`.
+     */
+    if (eraseOperation == ipmi::sel::getEraseStatus)
+    {
+        return ipmi::responseSuccess(
+            static_cast<uint8_t>(ipmi::sel::eraseComplete));
+    }
+
+    // Check that initiate erase is correct
+    if (eraseOperation != ipmi::sel::initiateErase)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    // Per the IPMI spec, need to cancel any reservation when the SEL is cleared
+    cancelSELReservation();
+
+    sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+    auto service = ipmi::getService(bus, ipmi::sel::logIntf, ipmi::sel::logObj);
+    auto method =
+        bus.new_method_call(service.c_str(), ipmi::sel::logObj,
+                            ipmi::sel::logIntf, ipmi::sel::logDeleteAllMethod);
+    try
+    {
+        bus.call_noreply(method);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error("Error eraseAll: {ERROR}", "ERROR", e);
+        return ipmi::responseUnspecifiedError();
+    }
+
+    return ipmi::responseSuccess(
+        static_cast<uint8_t>(ipmi::sel::eraseComplete));
+}
+
+/** @brief implements the get SEL time command
+ *  @returns IPMI completion code plus response data
+ *   -current time
+ */
+ipmi::RspType<uint32_t> // current time
+    ipmiStorageGetSelTime()
+{
+    using namespace std::chrono;
+    uint64_t bmc_time_usec = 0;
+    std::stringstream bmcTime;
+
+    try
+    {
+        sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+        auto service = ipmi::getService(bus, TIME_INTERFACE, BMC_TIME_PATH);
+        auto propValue = ipmi::getDbusProperty(
+            bus, service, BMC_TIME_PATH, TIME_INTERFACE, PROPERTY_ELAPSED);
+        bmc_time_usec = std::get<uint64_t>(propValue);
+    }
+    catch (const InternalFailure& e)
+    {
+        lg2::error("Internal Failure: {ERROR}", "ERROR", e);
+        return ipmi::responseUnspecifiedError();
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("exception message: {ERROR}", "ERROR", e);
+        return ipmi::responseUnspecifiedError();
+    }
+
+    lg2::debug("BMC time: {BMC_TIME}", "BMC_TIME",
+               duration_cast<seconds>(microseconds(bmc_time_usec)).count());
+
+    // Time is really long int but IPMI wants just uint32. This works okay until
+    // the number of seconds since 1970 overflows uint32 size.. Still a whole
+    // lot of time here to even think about that.
+    return ipmi::responseSuccess(
+        duration_cast<seconds>(microseconds(bmc_time_usec)).count());
+}
+
+/** @brief implements the set SEL time command
+ *  @param selDeviceTime - epoch time
+ *        -local time as the number of seconds from 00:00:00, January 1, 1970
+ *  @returns IPMI completion code
+ */
+ipmi::RspType<> ipmiStorageSetSelTime(uint32_t selDeviceTime)
+{
+    using namespace std::chrono;
+    microseconds usec{seconds(selDeviceTime)};
+
+    try
+    {
+        sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+        bool ntp = std::get<bool>(
+            ipmi::getDbusProperty(bus, SystemdTimeService, SystemdTimePath,
+                                  SystemdTimeInterface, "NTP"));
+        if (ntp)
+        {
+            return ipmi::responseCommandNotAvailable();
+        }
+
+        auto service = ipmi::getService(bus, TIME_INTERFACE, BMC_TIME_PATH);
+        std::variant<uint64_t> value{(uint64_t)usec.count()};
+
+        // Set bmc time
+        auto method = bus.new_method_call(service.c_str(), BMC_TIME_PATH,
+                                          DBUS_PROPERTIES, "Set");
+
+        method.append(TIME_INTERFACE, PROPERTY_ELAPSED, value);
+        auto reply = bus.call(method);
+    }
+    catch (const InternalFailure& e)
+    {
+        lg2::error("Internal Failure: {ERROR}", "ERROR", e);
+        return ipmi::responseUnspecifiedError();
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("exception message: {ERROR}", "ERROR", e);
+        return ipmi::responseUnspecifiedError();
+    }
+
+    return ipmi::responseSuccess();
+}
+
+/** @brief implements the get SEL timezone command
+ *  @returns IPMI completion code plus response data
+ *   -current timezone
+ */
+ipmi::RspType<int16_t> ipmiStorageGetSelTimeUtcOffset()
+{
+    time_t timep;
+    struct tm* gmTime;
+    struct tm* localTime;
+
+    time(&timep);
+    localTime = localtime(&timep);
+    auto validLocalTime = mktime(localTime);
+    gmTime = gmtime(&timep);
+    auto validGmTime = mktime(gmTime);
+    auto timeEquation = (validLocalTime - validGmTime) / 60;
+
+    return ipmi::responseSuccess(timeEquation);
+}
+
+/** @brief implements the reserve SEL command
+ *  @returns IPMI completion code plus response data
+ *   - SEL reservation ID.
+ */
+ipmi::RspType<uint16_t> ipmiStorageReserveSel()
+{
+    return ipmi::responseSuccess(reserveSel());
+}
+
+/** @brief implements the Add SEL entry command
+ * @request
+ *
+ *   - recordID      ID used for SEL Record access
+ *   - recordType    Record Type
+ *   - timeStamp     Time when event was logged. LS byte first
+ *   - generatorID   software ID if event was generated from
+ *                   system software
+ *   - evmRev        event message format version
+ *   - sensorType    sensor type code for service that generated
+ *                   the event
+ *   - sensorNumber  number of sensors that generated the event
+ *   - eventDir     event dir
+ *   - eventData    event data field contents
+ *
+ *  @returns ipmi completion code plus response data
+ *   - RecordID of the Added SEL entry
+ */
+ipmi::RspType<uint16_t // recordID of the Added SEL entry
+              >
+    ipmiStorageAddSEL(uint16_t recordID, uint8_t recordType,
+                      [[maybe_unused]] uint32_t timeStamp, uint16_t generatorID,
+                      [[maybe_unused]] uint8_t evmRev,
+                      [[maybe_unused]] uint8_t sensorType, uint8_t sensorNumber,
+                      uint8_t eventDir,
+                      std::array<uint8_t, eventDataSize> eventData)
+{
+    std::string objpath;
+    static constexpr auto systemRecordType = 0x02;
+#ifdef OPEN_POWER_SUPPORT
+    // Hostboot sends SEL with OEM record type 0xDE to indicate that there is
+    // a maintenance procedure associated with eSEL record.
+    static constexpr auto procedureType = 0xDE;
+#endif
+    cancelSELReservation();
+    if (recordType == systemRecordType)
+    {
+        for (const auto& it : invSensors)
+        {
+            if (it.second.sensorID == sensorNumber)
+            {
+                objpath = it.first;
+                break;
+            }
+        }
+        auto selDataStr = ipmi::sel::toHexStr(eventData);
+
+        bool assert = (eventDir & 0x80) ? false : true;
+
+        recordID = report<SELCreated>(
+            Created::RECORD_TYPE(recordType),
+            Created::GENERATOR_ID(generatorID),
+            Created::SENSOR_DATA(selDataStr.c_str()),
+            Created::EVENT_DIR(assert), Created::SENSOR_PATH(objpath.c_str()));
+    }
+#ifdef OPEN_POWER_SUPPORT
+    else if (recordType == procedureType)
+    {
+        // In the OEM record type 0xDE, byte 11 in the SEL record indicate the
+        // procedure number.
+        createProcedureLogEntry(sensorType);
+    }
+#endif
+
+    return ipmi::responseSuccess(recordID);
+}
+
+bool isFruPresent(ipmi::Context::ptr& ctx, const std::string& fruPath)
+{
+    using namespace ipmi::fru;
+
+    std::string service;
+    boost::system::error_code ec =
+        getService(ctx, invItemInterface, invObjPath + fruPath, service);
+    if (!ec)
+    {
+        bool result;
+        ec = ipmi::getDbusProperty(ctx, service, invObjPath + fruPath,
+                                   invItemInterface, itemPresentProp, result);
+        if (!ec)
+        {
+            return result;
+        }
+    }
+
+    ipmi::ObjectValueTree managedObjects;
+    ec = getManagedObjects(ctx, "xyz.openbmc_project.EntityManager",
+                           "/xyz/openbmc_project/inventory", managedObjects);
+    if (!ec)
+    {
+        auto connection = managedObjects.find(fruPath);
+        if (connection != managedObjects.end())
+        {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/** @brief implements the get FRU Inventory Area Info command
+ *
+ *  @returns IPMI completion code plus response data
+ *   - FRU Inventory area size in bytes,
+ *   - access bit
+ **/
+ipmi::RspType<uint16_t, // FRU Inventory area size in bytes,
+              uint8_t   // access size (bytes / words)
+              >
+    ipmiStorageGetFruInvAreaInfo(ipmi::Context::ptr ctx, uint8_t fruID)
+{
+    auto iter = frus.find(fruID);
+    if (iter == frus.end())
+    {
+        return ipmi::responseSensorInvalid();
+    }
+
+    auto path = iter->second[0].path;
+    if (!isFruPresent(ctx, path))
+    {
+        return ipmi::responseSensorInvalid();
+    }
+
+    try
+    {
+        return ipmi::responseSuccess(
+            static_cast<uint16_t>(getFruAreaData(fruID).size()),
+            static_cast<uint8_t>(AccessMode::bytes));
+    }
+    catch (const InternalFailure& e)
+    {
+        lg2::error("Internal Failure: {ERROR}", "ERROR", e);
+        return ipmi::responseUnspecifiedError();
+    }
+}
+
+/**@brief implements the Read FRU Data command
+ * @param fruDeviceId - FRU device ID. FFh = reserved
+ * @param offset      - FRU inventory offset to read
+ * @param readCount   - count to read
+ *
+ * @return IPMI completion code plus response data
+ * - returnCount - response data count.
+ * - data        -  response data
+ */
+ipmi::RspType<uint8_t,              // count returned
+              std::vector<uint8_t>> // FRU data
+    ipmiStorageReadFruData(uint8_t fruDeviceId, uint16_t offset,
+                           uint8_t readCount)
+{
+    if (fruDeviceId == 0xFF)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    auto iter = frus.find(fruDeviceId);
+    if (iter == frus.end())
+    {
+        return ipmi::responseSensorInvalid();
+    }
+
+    try
+    {
+        const auto& fruArea = getFruAreaData(fruDeviceId);
+        auto size = fruArea.size();
+
+        if (offset >= size)
+        {
+            return ipmi::responseParmOutOfRange();
+        }
+
+        // Write the count of response data.
+        uint8_t returnCount;
+        if ((offset + readCount) <= size)
+        {
+            returnCount = readCount;
+        }
+        else
+        {
+            returnCount = size - offset;
+        }
+
+        std::vector<uint8_t> fruData((fruArea.begin() + offset),
+                                     (fruArea.begin() + offset + returnCount));
+
+        return ipmi::responseSuccess(returnCount, fruData);
+    }
+    catch (const InternalFailure& e)
+    {
+        lg2::error("Internal Failure: {ERROR}", "ERROR", e);
+        return ipmi::responseUnspecifiedError();
+    }
+}
+
+ipmi::RspType<uint8_t,  // SDR version
+              uint16_t, // record count LS first
+              uint16_t, // free space in bytes, LS first
+              uint32_t, // addition timestamp LS first
+              uint32_t, // deletion timestamp LS first
+              uint8_t>  // operation Support
+    ipmiGetRepositoryInfo()
+{
+    constexpr uint8_t sdrVersion = 0x51;
+    constexpr uint16_t freeSpace = 0xFFFF;
+    constexpr uint32_t additionTimestamp = 0x0;
+    constexpr uint32_t deletionTimestamp = 0x0;
+    constexpr uint8_t operationSupport = 0;
+
+    // Get SDR count. This returns the total number of SDRs in the device.
+    const auto& entityRecords =
+        ipmi::sensor::EntityInfoMapContainer::getContainer()
+            ->getIpmiEntityRecords();
+    uint16_t records =
+        ipmi::sensor::sensors.size() + frus.size() + entityRecords.size();
+
+    return ipmi::responseSuccess(sdrVersion, records, freeSpace,
+                                 additionTimestamp, deletionTimestamp,
+                                 operationSupport);
+}
+
+void registerNetFnStorageFunctions()
+{
+    selCacheMapInitialized = false;
+    initSELCache();
+    // Handlers with dbus-sdr handler implementation.
+    // Do not register the hander if it dynamic sensors stack is used.
+
+#ifndef FEATURE_DYNAMIC_SENSORS
+
+#ifndef FEATURE_DYNAMIC_STORAGES_ONLY
+    // <Get SEL Info>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User,
+                          ipmiStorageGetSelInfo);
+
+    // <Get SEL Timezone>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdGetSelTimeUtcOffset,
+                          ipmi::Privilege::User,
+                          ipmiStorageGetSelTimeUtcOffset);
+
+    // <Get SEL Entry>
+    ipmi_register_callback(NETFUN_STORAGE, ipmi::storage::cmdGetSelEntry,
+                           nullptr, getSELEntry, PRIVILEGE_USER);
+
+    // <Delete SEL Entry>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdDeleteSelEntry,
+                          ipmi::Privilege::Operator, deleteSELEntry);
+
+    // <Add SEL Entry>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdAddSelEntry,
+                          ipmi::Privilege::Operator, ipmiStorageAddSEL);
+
+    // <Clear SEL>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdClearSel, ipmi::Privilege::Operator,
+                          clearSEL);
+
+    // <Get FRU Inventory Area Info>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdGetFruInventoryAreaInfo,
+                          ipmi::Privilege::User, ipmiStorageGetFruInvAreaInfo);
+
+    // <READ FRU Data>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdReadFruData,
+                          ipmi::Privilege::Operator, ipmiStorageReadFruData);
+
+#endif // FEATURE_DYNAMIC_STORAGES_ONLY
+
+    // <Get Repository Info>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdGetSdrRepositoryInfo,
+                          ipmi::Privilege::User, ipmiGetRepositoryInfo);
+
+    // <Reserve SDR Repository>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdReserveSdrRepository,
+                          ipmi::Privilege::User, ipmiSensorReserveSdr);
+
+    // <Get SDR>
+    ipmi_register_callback(NETFUN_STORAGE, ipmi::storage::cmdGetSdr, nullptr,
+                           ipmi_sen_get_sdr, PRIVILEGE_USER);
+
+#endif
+
+    // Common Handers used by both implementation.
+
+    // <Reserve SEL>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdReserveSel, ipmi::Privilege::User,
+                          ipmiStorageReserveSel);
+
+    // <Get SEL Time>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdGetSelTime, ipmi::Privilege::User,
+                          ipmiStorageGetSelTime);
+
+    // <Set SEL Time>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdSetSelTime,
+                          ipmi::Privilege::Operator, ipmiStorageSetSelTime);
+
+    ipmi::fru::registerCallbackHandler();
+    return;
+}
diff --git a/subprojects/boost.wrap b/subprojects/boost.wrap
new file mode 100644
index 0000000..5912c6f
--- /dev/null
+++ b/subprojects/boost.wrap
@@ -0,0 +1,9 @@
+[wrap-file]
+directory = boost-1.84.0
+
+source_url = https://github.com/boostorg/boost/releases/download/boost-1.84.0/boost-1.84.0.tar.gz
+source_hash = 4d27e9efed0f6f152dc28db6430b9d3dfb40c0345da7342eaa5a987dde57bd95
+source_filename = 1_84_0.tar.gz
+
+[provide]
+boost = boost_dep
diff --git a/subprojects/nlohmann_json.wrap b/subprojects/nlohmann_json.wrap
new file mode 100644
index 0000000..3745380
--- /dev/null
+++ b/subprojects/nlohmann_json.wrap
@@ -0,0 +1,6 @@
+[wrap-git]
+revision = HEAD
+url = https://github.com/nlohmann/json.git
+
+[provide]
+nlohmann_json = nlohmann_json_dep
diff --git a/subprojects/phosphor-dbus-interfaces.wrap b/subprojects/phosphor-dbus-interfaces.wrap
index 346aa0c..0bf731e 100644
--- a/subprojects/phosphor-dbus-interfaces.wrap
+++ b/subprojects/phosphor-dbus-interfaces.wrap
@@ -1,3 +1,4 @@
+
 [wrap-git]
 url = https://github.com/openbmc/phosphor-dbus-interfaces.git
 revision = HEAD
diff --git a/subprojects/phosphor-logging.wrap b/subprojects/phosphor-logging.wrap
index 71eee8b..6876a6e 100644
--- a/subprojects/phosphor-logging.wrap
+++ b/subprojects/phosphor-logging.wrap
@@ -1,5 +1,5 @@
 [wrap-git]
-url = https://github.com/openbmc/phosphor-logging.git
+url = https://github.com/openbmc/phosphor-logging
 revision = HEAD
 
 [provide]
diff --git a/subprojects/sdbusplus.wrap b/subprojects/sdbusplus.wrap
index 7b076d0..42cfcee 100644
--- a/subprojects/sdbusplus.wrap
+++ b/subprojects/sdbusplus.wrap
@@ -1,5 +1,5 @@
 [wrap-git]
-url = https://github.com/openbmc/sdbusplus.git
+url = https://github.com/openbmc/sdbusplus
 revision = HEAD
 
 [provide]
diff --git a/subprojects/sdeventplus.wrap b/subprojects/sdeventplus.wrap
new file mode 100644
index 0000000..a506404
--- /dev/null
+++ b/subprojects/sdeventplus.wrap
@@ -0,0 +1,6 @@
+[wrap-git]
+url = https://github.com/openbmc/sdeventplus
+revision = HEAD
+
+[provide]
+sdeventplus = sdeventplus_dep
diff --git a/sys_info_param.cpp b/sys_info_param.cpp
new file mode 100644
index 0000000..79bb29c
--- /dev/null
+++ b/sys_info_param.cpp
@@ -0,0 +1,33 @@
+#include "sys_info_param.hpp"
+
+std::tuple<bool, std::string> SysInfoParamStore::lookup(
+    uint8_t paramSelector) const
+{
+    const auto iterator = params.find(paramSelector);
+    if (iterator == params.end())
+    {
+        return std::make_tuple(false, "");
+    }
+
+    auto& callback = iterator->second;
+    auto s = callback();
+    return std::make_tuple(true, s);
+}
+
+void SysInfoParamStore::update(uint8_t paramSelector, const std::string& s)
+{
+    // Add a callback that captures a copy of the string passed and returns it
+    // when invoked.
+
+    // clang-format off
+    update(paramSelector, [s]() {
+        return s;
+    });
+    // clang-format on
+}
+
+void SysInfoParamStore::update(uint8_t paramSelector,
+                               const std::function<std::string()>& callback)
+{
+    params[paramSelector] = callback;
+}
diff --git a/sys_info_param.hpp b/sys_info_param.hpp
new file mode 100644
index 0000000..5f6607d
--- /dev/null
+++ b/sys_info_param.hpp
@@ -0,0 +1,64 @@
+#pragma once
+
+#include <cstdint>
+#include <functional>
+#include <map>
+#include <string>
+#include <tuple>
+
+/**
+ * Key-value store for string-type system info parameters.
+ */
+class SysInfoParamStoreIntf
+{
+  public:
+    virtual ~SysInfoParamStoreIntf() {}
+
+    /**
+     * Returns true if parameter is found. If and only if s is non-null,
+     * invokes the parameter's callback and writes the value.
+     *
+     * @param[in] paramSelector - the key to lookup.
+     * @return tuple of bool and string, true if parameter is found and
+     * string set accordingly.
+     */
+    virtual std::tuple<bool, std::string> lookup(
+        uint8_t paramSelector) const = 0;
+
+    /**
+     * Update a parameter by its code with a string value.
+     *
+     * @param[in] paramSelector - the key to update.
+     * @param[in] s - the value to set.
+     */
+    virtual void update(uint8_t paramSelector, const std::string& s) = 0;
+
+    /**
+     * Update a parameter by its code with a callback that is called to retrieve
+     * its value whenever called. Callback must be idempotent, as it may be
+     * called multiple times by the host to retrieve the parameter by chunks.
+     *
+     * @param[in] paramSelector - the key to update.
+     * @param[in] callback - the callback to use for parameter retrieval.
+     */
+    virtual void update(uint8_t paramSelector,
+                        const std::function<std::string()>& callback) = 0;
+
+    // TODO: Store "read-only" flag for each parameter.
+    // TODO: Function to erase a parameter?
+};
+
+/**
+ * Implement the system info parameters store as a map of callbacks.
+ */
+class SysInfoParamStore : public SysInfoParamStoreIntf
+{
+  public:
+    std::tuple<bool, std::string> lookup(uint8_t paramSelector) const override;
+    void update(uint8_t paramSelector, const std::string& s) override;
+    void update(uint8_t paramSelector,
+                const std::function<std::string()>& callback) override;
+
+  private:
+    std::map<uint8_t, std::function<std::string()>> params;
+};
diff --git a/systemintfcmds.cpp b/systemintfcmds.cpp
new file mode 100644
index 0000000..c7206af
--- /dev/null
+++ b/systemintfcmds.cpp
@@ -0,0 +1,189 @@
+#include "config.h"
+
+#include "systemintfcmds.hpp"
+
+#include "host-cmd-manager.hpp"
+#include "host-interface.hpp"
+
+#include <ipmid-host/cmd.hpp>
+#include <ipmid/api.hpp>
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <cstring>
+#include <fstream>
+
+void registerNetFnAppFunctions() __attribute__((constructor));
+
+using namespace sdbusplus::server::xyz::openbmc_project::control;
+
+// For accessing Host command manager
+using cmdManagerPtr = std::unique_ptr<phosphor::host::command::Manager>;
+extern cmdManagerPtr& ipmid_get_host_cmd_manager();
+
+//-------------------------------------------------------------------
+// Called by Host post response from Get_Message_Flags
+//-------------------------------------------------------------------
+ipmi::RspType<uint16_t,              // id
+              uint8_t,               // type
+              uint24_t,              //  manuf_id
+              uint32_t,              // timestamp
+              uint8_t,               // netfun
+              uint8_t,               // cmd
+              std::array<uint8_t, 4> // data
+              >
+    ipmiAppReadEventBuffer(ipmi::Context::ptr& ctx)
+{
+    // require this to be limited to system interface
+    if (ctx->channel != ipmi::channelSystemIface)
+    {
+        return ipmi::responseInvalidCommand();
+    }
+
+    constexpr uint16_t selOemId = 0x5555;
+    constexpr uint8_t selRecordTypeOem = 0xc0;
+
+    // read manufacturer ID from dev_id file
+    static uint24_t manufId{};
+    if (!manufId)
+    {
+        const char* filename = "/usr/share/ipmi-providers/dev_id.json";
+        std::ifstream devIdFile(filename);
+        if (devIdFile.is_open())
+        {
+            auto data = nlohmann::json::parse(devIdFile, nullptr, false);
+            if (!data.is_discarded())
+            {
+                manufId = data.value("manuf_id", 0);
+            }
+        }
+    }
+
+    constexpr uint32_t timestamp{0};
+
+    // per IPMI spec NetFuntion for OEM
+    constexpr uint8_t netfun = 0x3a;
+
+    // Read from the Command Manager queue. What gets returned is a
+    // pair of <command, data> that can be directly used here
+    const auto& [cmd, data0] = ipmid_get_host_cmd_manager()->getNextCommand();
+    constexpr uint8_t dataUnused = 0xff;
+
+    return ipmi::responseSuccess(
+        selOemId, selRecordTypeOem, manufId, timestamp, netfun, cmd,
+        std::to_array<uint8_t>({data0, dataUnused, dataUnused, dataUnused}));
+}
+
+//---------------------------------------------------------------------
+// Called by Host on seeing a SMS_ATN bit set. Return a hardcoded
+// value of 0x0 to indicate Event Message Buffer is not supported
+//-------------------------------------------------------------------
+ipmi::RspType<uint8_t> ipmiAppGetMessageFlags()
+{
+    // From IPMI spec V2.0 for Get Message Flags Command :
+    // bit:[1] from LSB : 1b = Event Message Buffer Full.
+    // Return as 0 if Event Message Buffer is not supported,
+    // or when the Event Message buffer is disabled.
+    // This path is used to communicate messages to the host
+    // from within the phosphor::host::command::Manager
+    constexpr uint8_t setEventMsgBufferNotSupported = 0x0;
+    return ipmi::responseSuccess(setEventMsgBufferNotSupported);
+}
+
+ipmi::RspType<bool,    // Receive Message Queue Interrupt Enabled
+              bool,    // Event Message Buffer Full Interrupt Enabled
+              bool,    // Event Message Buffer Enabled
+              bool,    // System Event Logging Enabled
+              uint1_t, // Reserved
+              bool,    // OEM 0 enabled
+              bool,    // OEM 1 enabled
+              bool     // OEM 2 enabled
+              >
+    ipmiAppGetBMCGlobalEnable()
+{
+    return ipmi::responseSuccess(true, false, false, true, 0, false, false,
+                                 false);
+}
+
+ipmi::RspType<> ipmiAppSetBMCGlobalEnable(
+    ipmi::Context::ptr ctx, bool receiveMessageQueueInterruptEnabled,
+    bool eventMessageBufferFullInterruptEnabled, bool eventMessageBufferEnabled,
+    bool systemEventLogEnable, uint1_t reserved, bool OEM0Enabled,
+    bool OEM1Enabled, bool OEM2Enabled)
+{
+    ipmi::ChannelInfo chInfo;
+
+    if (ipmi::getChannelInfo(ctx->channel, chInfo) != ipmi::ccSuccess)
+    {
+        lg2::error("Failed to get Channel Info, channel={CHANNEL}", "CHANNEL",
+                   ctx->channel);
+        return ipmi::responseUnspecifiedError();
+    }
+
+    if (chInfo.mediumType !=
+        static_cast<uint8_t>(ipmi::EChannelMediumType::systemInterface))
+    {
+        lg2::error("Error - supported only in system interface");
+        return ipmi::responseCommandNotAvailable();
+    }
+
+    // Recv Message Queue and SEL are enabled by default.
+    // Event Message buffer are disabled by default (not supported).
+    // Any request that try to change the mask will be rejected
+    if (!receiveMessageQueueInterruptEnabled || !systemEventLogEnable ||
+        eventMessageBufferFullInterruptEnabled || eventMessageBufferEnabled ||
+        OEM0Enabled || OEM1Enabled || OEM2Enabled || reserved)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    return ipmi::responseSuccess();
+}
+
+namespace
+{
+// Static storage to keep the object alive during process life
+std::unique_ptr<phosphor::host::command::Host> host
+    __attribute__((init_priority(101)));
+std::unique_ptr<sdbusplus::server::manager_t> objManager
+    __attribute__((init_priority(101)));
+} // namespace
+
+void registerNetFnAppFunctions()
+{
+    // <Read Event Message Buffer>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdReadEventMessageBuffer,
+                          ipmi::Privilege::Admin, ipmiAppReadEventBuffer);
+
+    // <Set BMC Global Enables>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdSetBmcGlobalEnables,
+                          ipmi::Privilege::Admin, ipmiAppSetBMCGlobalEnable);
+
+    // <Get BMC Global Enables>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdGetBmcGlobalEnables,
+                          ipmi::Privilege::User, ipmiAppGetBMCGlobalEnable);
+
+    // <Get Message Flags>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdGetMessageFlags, ipmi::Privilege::Admin,
+                          ipmiAppGetMessageFlags);
+
+    // Create new xyz.openbmc_project.host object on the bus
+    auto objPath = std::string{CONTROL_HOST_OBJ_MGR} + '/' + HOST_NAME + '0';
+
+    std::unique_ptr<sdbusplus::asio::connection>& sdbusp =
+        ipmid_get_sdbus_plus_handler();
+
+    // Add sdbusplus ObjectManager.
+    objManager = std::make_unique<sdbusplus::server::manager_t>(
+        *sdbusp, CONTROL_HOST_OBJ_MGR);
+
+    host = std::make_unique<phosphor::host::command::Host>(
+        *sdbusp, objPath.c_str());
+    sdbusp->request_name(CONTROL_HOST_BUSNAME);
+
+    return;
+}
diff --git a/systemintfcmds.hpp b/systemintfcmds.hpp
new file mode 100644
index 0000000..b263a96
--- /dev/null
+++ b/systemintfcmds.hpp
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <stdint.h>
+
+// These are per skiboot ipmi-sel code
+
+// Minor command for soft shurdown
+#define SOFT_OFF 0x00
+// Major command for Any kind of power ops
+#define CMD_POWER 0x04
+// Major command for the heartbeat operation (verify host is alive)
+#define CMD_HEARTBEAT 0xFF
diff --git a/test/dbus-sdr/sensorcommands_unittest.cpp b/test/dbus-sdr/sensorcommands_unittest.cpp
new file mode 100644
index 0000000..bcd2f44
--- /dev/null
+++ b/test/dbus-sdr/sensorcommands_unittest.cpp
@@ -0,0 +1,489 @@
+#include "dbus-sdr/sensorutils.hpp"
+
+#include <cmath>
+
+#include "gtest/gtest.h"
+
+// There is a surprising amount of slop in the math,
+// thanks to all the rounding and conversion.
+// The "x" byte value can drift by up to 2 away, I have seen.
+static constexpr int8_t expectedSlopX = 2;
+
+// Unlike expectedSlopX, this is a ratio, not an integer
+// It scales based on the range of "y"
+static constexpr double expectedSlopY = 0.01;
+
+// The algorithm here was copied from ipmitool
+// sdr_convert_sensor_reading() function
+// https://github.com/ipmitool/ipmitool/blob/42a023ff0726c80e8cc7d30315b987fe568a981d/lib/ipmi_sdr.c#L360
+double ipmitool_y_from_x(uint8_t x, int m, int k2_rExp, int b, int k1_bExp,
+                         bool bSigned)
+{
+    double result;
+
+    // Rename to exactly match names and types (except analog) from ipmitool
+    uint8_t val = x;
+    double k1 = k1_bExp;
+    double k2 = k2_rExp;
+    int analog = bSigned ? 2 : 0;
+
+    // Begin paste here
+    // Only change is to comment out complicated structure in switch statement
+
+    switch (/*sensor->cmn.unit.*/ analog)
+    {
+        case 0:
+            result = (double)(((m * val) + (b * pow(10, k1))) * pow(10, k2));
+            break;
+        case 1:
+            if (val & 0x80)
+                val++;
+            /* Deliberately fall through to case 2. */
+            [[fallthrough]];
+        case 2:
+            result =
+                (double)(((m * (int8_t)val) + (b * pow(10, k1))) * pow(10, k2));
+            break;
+        default:
+            /* Oops! This isn't an analog sensor. */
+            return 0.0;
+    }
+
+    // End paste here
+    // Ignoring linearization curves and postprocessing that follows,
+    // assuming all sensors are perfectly linear
+    return result;
+}
+
+void testValue(int x, double y, int16_t M, int8_t rExp, int16_t B, int8_t bExp,
+               bool bSigned, double yRange)
+{
+    double yRoundtrip;
+    int result;
+
+    // There is intentionally no exception catching here,
+    // because if getSensorAttributes() returned true,
+    // it is a promise that all of these should work.
+    if (bSigned)
+    {
+        int8_t expect = x;
+        int8_t actual =
+            ipmi::scaleIPMIValueFromDouble(y, M, rExp, B, bExp, bSigned);
+
+        result = actual;
+        yRoundtrip = ipmitool_y_from_x(actual, M, rExp, B, bExp, bSigned);
+
+        EXPECT_NEAR(actual, expect, expectedSlopX);
+    }
+    else
+    {
+        uint8_t expect = x;
+        uint8_t actual =
+            ipmi::scaleIPMIValueFromDouble(y, M, rExp, B, bExp, bSigned);
+
+        result = actual;
+        yRoundtrip = ipmitool_y_from_x(actual, M, rExp, B, bExp, bSigned);
+
+        EXPECT_NEAR(actual, expect, expectedSlopX);
+    }
+
+    // Scale the amount of allowed slop in y based on range, so ratio similar
+    double yTolerance = yRange * expectedSlopY;
+    // double yError = std::abs(y - yRoundtrip);
+
+    EXPECT_NEAR(y, yRoundtrip, yTolerance);
+
+    char szFormat[1024];
+    sprintf(szFormat,
+            "Value | xExpect %4d | xResult %4d "
+            "| M %5d | rExp %3d "
+            "| B %5d | bExp %3d | bSigned %1d | y %18.3f | yRoundtrip %18.3f\n",
+            x, result, M, (int)rExp, B, (int)bExp, (int)bSigned, y, yRoundtrip);
+    std::cout << szFormat;
+}
+
+void testBounds(double yMin, double yMax, bool bExpectedOutcome = true)
+{
+    int16_t mValue;
+    int8_t rExp;
+    int16_t bValue;
+    int8_t bExp;
+    bool bSigned;
+    bool result;
+
+    result = ipmi::getSensorAttributes(yMax, yMin, mValue, rExp, bValue, bExp,
+                                       bSigned);
+    EXPECT_EQ(result, bExpectedOutcome);
+
+    if (!result)
+    {
+        return;
+    }
+
+    char szFormat[1024];
+    sprintf(szFormat,
+            "Bounds | yMin %18.3f | yMax %18.3f | M %5d"
+            " | rExp %3d | B %5d | bExp %3d | bSigned %1d\n",
+            yMin, yMax, mValue, (int)rExp, bValue, (int)bExp, (int)bSigned);
+    std::cout << szFormat;
+
+    double y50p = (yMin + yMax) / 2.0;
+
+    // Average the average
+    double y25p = (yMin + y50p) / 2.0;
+    double y75p = (y50p + yMax) / 2.0;
+
+    // This range value is only used for tolerance checking, not computation
+    double yRange = yMax - yMin;
+
+    if (bSigned)
+    {
+        int8_t xMin = -128;
+        int8_t x25p = -64;
+        int8_t x50p = 0;
+        int8_t x75p = 64;
+        int8_t xMax = 127;
+
+        testValue(xMin, yMin, mValue, rExp, bValue, bExp, bSigned, yRange);
+        testValue(x25p, y25p, mValue, rExp, bValue, bExp, bSigned, yRange);
+        testValue(x50p, y50p, mValue, rExp, bValue, bExp, bSigned, yRange);
+        testValue(x75p, y75p, mValue, rExp, bValue, bExp, bSigned, yRange);
+        testValue(xMax, yMax, mValue, rExp, bValue, bExp, bSigned, yRange);
+    }
+    else
+    {
+        uint8_t xMin = 0;
+        uint8_t x25p = 64;
+        uint8_t x50p = 128;
+        uint8_t x75p = 192;
+        uint8_t xMax = 255;
+
+        testValue(xMin, yMin, mValue, rExp, bValue, bExp, bSigned, yRange);
+        testValue(x25p, y25p, mValue, rExp, bValue, bExp, bSigned, yRange);
+        testValue(x50p, y50p, mValue, rExp, bValue, bExp, bSigned, yRange);
+        testValue(x75p, y75p, mValue, rExp, bValue, bExp, bSigned, yRange);
+        testValue(xMax, yMax, mValue, rExp, bValue, bExp, bSigned, yRange);
+    }
+}
+
+void testRanges(void)
+{
+    // The ranges from the main TEST function
+    testBounds(0x0, 0xFF);
+    testBounds(-128, 127);
+    testBounds(0, 16000);
+    testBounds(0, 20);
+    testBounds(8000, 16000);
+    testBounds(-10, 10);
+    testBounds(0, 277);
+    testBounds(0, 0, false);
+    testBounds(10, 12);
+
+    // Additional test cases recommended to me by hardware people
+    testBounds(-40, 150);
+    testBounds(0, 1);
+    testBounds(0, 2);
+    testBounds(0, 4);
+    testBounds(0, 8);
+    testBounds(35, 65);
+    testBounds(0, 18);
+    testBounds(0, 25);
+    testBounds(0, 80);
+    testBounds(0, 500);
+
+    // Additional sanity checks
+    testBounds(0, 255);
+    testBounds(-255, 0);
+    testBounds(-255, 255);
+    testBounds(0, 1000);
+    testBounds(-1000, 0);
+    testBounds(-1000, 1000);
+    testBounds(0, 255000);
+    testBounds(-128000000, 127000000);
+    testBounds(-50000, 0);
+    testBounds(-40000, 10000);
+    testBounds(-30000, 20000);
+    testBounds(-20000, 30000);
+    testBounds(-10000, 40000);
+    testBounds(0, 50000);
+    testBounds(-1e3, 1e6);
+    testBounds(-1e6, 1e3);
+
+    // Extreme ranges are now possible
+    testBounds(0, 1e10);
+    testBounds(0, 1e11);
+    testBounds(0, 1e12);
+    testBounds(0, 1e13, false);
+    testBounds(-1e10, 0);
+    testBounds(-1e11, 0);
+    testBounds(-1e12, 0);
+    testBounds(-1e13, 0, false);
+    testBounds(-1e9, 1e9);
+    testBounds(-1e10, 1e10);
+    testBounds(-1e11, 1e11);
+    testBounds(-1e12, 1e12, false);
+
+    // Large multiplier but small offset
+    testBounds(1e4, 1e4 + 255);
+    testBounds(1e5, 1e5 + 255);
+    testBounds(1e6, 1e6 + 255);
+    testBounds(1e7, 1e7 + 255);
+    testBounds(1e8, 1e8 + 255);
+    testBounds(1e9, 1e9 + 255);
+    testBounds(1e10, 1e10 + 255, false);
+
+    // Input validation against garbage
+    testBounds(0, INFINITY, false);
+    testBounds(-INFINITY, 0, false);
+    testBounds(-INFINITY, INFINITY, false);
+    testBounds(0, NAN, false);
+    testBounds(NAN, 0, false);
+    testBounds(NAN, NAN, false);
+
+    // Noteworthy binary integers
+    testBounds(0, std::pow(2.0, 32.0) - 1.0);
+    testBounds(0, std::pow(2.0, 32.0));
+    testBounds(0.0 - std::pow(2.0, 31.0), std::pow(2.0, 31.0));
+    testBounds((0.0 - std::pow(2.0, 31.0)) - 1.0, std::pow(2.0, 31.0));
+
+    // Similar but negative (note additional commented-out below)
+    testBounds(-1e1, (-1e1) + 255);
+    testBounds(-1e2, (-1e2) + 255);
+
+    // Ranges of negative numbers (note additional commented-out below)
+    testBounds(-10400, -10000);
+    testBounds(-15000, -14000);
+    testBounds(-10000, -9000);
+    testBounds(-1000, -900);
+    testBounds(-1000, -800);
+    testBounds(-1000, -700);
+    testBounds(-1000, -740);
+
+    // Very small ranges (note additional commented-out below)
+    testBounds(0, 0.1);
+    testBounds(0, 0.01);
+    testBounds(0, 0.001);
+    testBounds(0, 0.0001);
+    testBounds(0, 0.000001, false);
+
+#if 0
+    // TODO(): The algorithm in this module is better than it was before,
+    // but the resulting value of X is still wrong under certain conditions,
+    // such as when the range between min and max is around 255,
+    // and the offset is fairly extreme compared to the multiplier.
+    // Not sure why this is, but these ranges are contrived,
+    // and real-world examples would most likely never be this way.
+    testBounds(-10290, -10000);
+    testBounds(-10280, -10000);
+    testBounds(-10275,-10000);
+    testBounds(-10270,-10000);
+    testBounds(-10265,-10000);
+    testBounds(-10260,-10000);
+    testBounds(-10255,-10000);
+    testBounds(-10250,-10000);
+    testBounds(-10245,-10000);
+    testBounds(-10256,-10000);
+    testBounds(-10512, -10000);
+    testBounds(-11024, -10000);
+
+    // TODO(): This also fails, due to extreme small range, loss of precision
+    testBounds(0, 0.00001);
+
+    // TODO(): Interestingly, if bSigned is forced false,
+    // causing "x" to have range of (0,255) instead of (-128,127),
+    // these test cases change from failing to passing!
+    // Not sure why this is, perhaps a mathematician might know.
+    testBounds(-10300, -10000);
+    testBounds(-1000,-750);
+    testBounds(-1e3, (-1e3) + 255);
+    testBounds(-1e4, (-1e4) + 255);
+    testBounds(-1e5, (-1e5) + 255);
+    testBounds(-1e6, (-1e6) + 255);
+#endif
+}
+
+TEST(sensorutils, TranslateToIPMI)
+{
+    /*bool getSensorAttributes(double maxValue, double minValue, int16_t
+       &mValue, int8_t &rExp, int16_t &bValue, int8_t &bExp, bool &bSigned); */
+    // normal unsigned sensor
+    double maxValue = 0xFF;
+    double minValue = 0x0;
+    int16_t mValue;
+    int8_t rExp;
+    int16_t bValue;
+    int8_t bExp;
+    bool bSigned;
+    bool result;
+
+    uint8_t scaledVal;
+
+    result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+                                       bExp, bSigned);
+    EXPECT_EQ(result, true);
+    if (result)
+    {
+        EXPECT_EQ(bSigned, false);
+        EXPECT_EQ(mValue, 1);
+        EXPECT_EQ(rExp, 0);
+        EXPECT_EQ(bValue, 0);
+        EXPECT_EQ(bExp, 0);
+    }
+    double expected = 0x50;
+    scaledVal = ipmi::scaleIPMIValueFromDouble(0x50, mValue, rExp, bValue, bExp,
+                                               bSigned);
+    EXPECT_NEAR(scaledVal, expected, expected * 0.01);
+
+    // normal signed sensor
+    maxValue = 127;
+    minValue = -128;
+
+    result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+                                       bExp, bSigned);
+    EXPECT_EQ(result, true);
+
+    if (result)
+    {
+        EXPECT_EQ(bSigned, true);
+        EXPECT_EQ(mValue, 1);
+        EXPECT_EQ(rExp, 0);
+        EXPECT_EQ(bValue, 0);
+        EXPECT_EQ(bExp, 0);
+    }
+
+    // check negative values
+    expected = 236; // 2s compliment -20
+    scaledVal = ipmi::scaleIPMIValueFromDouble(-20, mValue, rExp, bValue, bExp,
+                                               bSigned);
+    EXPECT_NEAR(scaledVal, expected, expected * 0.01);
+
+    // fan example
+    maxValue = 16000;
+    minValue = 0;
+
+    result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+                                       bExp, bSigned);
+    EXPECT_EQ(result, true);
+    if (result)
+    {
+        EXPECT_EQ(bSigned, false);
+        EXPECT_EQ(mValue, floor((16000.0 / 0xFF) + 0.5));
+        EXPECT_EQ(rExp, 0);
+        EXPECT_EQ(bValue, 0);
+        EXPECT_EQ(bExp, 0);
+    }
+
+    // voltage sensor example
+    maxValue = 20;
+    minValue = 0;
+
+    result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+                                       bExp, bSigned);
+    EXPECT_EQ(result, true);
+    if (result)
+    {
+        EXPECT_EQ(bSigned, false);
+        EXPECT_EQ(mValue, floor(((20.0 / 0xFF) / std::pow(10, rExp)) + 0.5));
+        EXPECT_EQ(rExp, -3);
+        EXPECT_EQ(bValue, 0);
+        EXPECT_EQ(bExp, 0);
+    }
+    scaledVal = ipmi::scaleIPMIValueFromDouble(12.2, mValue, rExp, bValue, bExp,
+                                               bSigned);
+
+    expected = 12.2 / (mValue * std::pow(10, rExp));
+    EXPECT_NEAR(scaledVal, expected, expected * 0.01);
+
+    // shifted fan example
+    maxValue = 16000;
+    minValue = 8000;
+
+    result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+                                       bExp, bSigned);
+    EXPECT_EQ(result, true);
+
+    if (result)
+    {
+        EXPECT_EQ(bSigned, false);
+        EXPECT_EQ(mValue, floor(((8000.0 / 0xFF) / std::pow(10, rExp)) + 0.5));
+        EXPECT_EQ(rExp, -1);
+        EXPECT_EQ(bValue, 8);
+        EXPECT_EQ(bExp, 4);
+    }
+
+    // signed voltage sensor example
+    maxValue = 10;
+    minValue = -10;
+
+    result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+                                       bExp, bSigned);
+    EXPECT_EQ(result, true);
+    if (result)
+    {
+        EXPECT_EQ(bSigned, true);
+        EXPECT_EQ(mValue, floor(((20.0 / 0xFF) / std::pow(10, rExp)) + 0.5));
+        EXPECT_EQ(rExp, -3);
+        // Although this seems like a weird magic number,
+        // it is because the range (-128,127) is not symmetrical about zero,
+        // unlike the range (-10,10), so this introduces some distortion.
+        EXPECT_EQ(bValue, 392);
+        EXPECT_EQ(bExp, -1);
+    }
+
+    scaledVal =
+        ipmi::scaleIPMIValueFromDouble(5, mValue, rExp, bValue, bExp, bSigned);
+
+    expected = 5 / (mValue * std::pow(10, rExp));
+    EXPECT_NEAR(scaledVal, expected, expected * 0.01);
+
+    // reading = max example
+    maxValue = 277;
+    minValue = 0;
+
+    result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+                                       bExp, bSigned);
+    EXPECT_EQ(result, true);
+    if (result)
+    {
+        EXPECT_EQ(bSigned, false);
+    }
+
+    scaledVal = ipmi::scaleIPMIValueFromDouble(maxValue, mValue, rExp, bValue,
+                                               bExp, bSigned);
+
+    expected = 0xFF;
+    EXPECT_NEAR(scaledVal, expected, expected * 0.01);
+
+    // 0, 0 failure
+    maxValue = 0;
+    minValue = 0;
+    result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+                                       bExp, bSigned);
+    EXPECT_EQ(result, false);
+
+    // too close *success* (was previously failure!)
+    maxValue = 12;
+    minValue = 10;
+    result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+                                       bExp, bSigned);
+    EXPECT_EQ(result, true);
+    if (result)
+    {
+        EXPECT_EQ(bSigned, false);
+        EXPECT_EQ(mValue, floor(((2.0 / 0xFF) / std::pow(10, rExp)) + 0.5));
+        EXPECT_EQ(rExp, -4);
+        EXPECT_EQ(bValue, 1);
+        EXPECT_EQ(bExp, 5);
+    }
+}
+
+TEST(sensorUtils, TestRanges)
+{
+    // Additional test ranges, each running through a series of values,
+    // to make sure the values of "x" and "y" go together and make sense,
+    // for the resulting scaling attributes from each range.
+    // Unlike the TranslateToIPMI test, exact matches of the
+    // getSensorAttributes() results (the coefficients) are not required,
+    // because they are tested through actual use, relating "x" to "y".
+    testRanges();
+}
diff --git a/test/entitymap_json_unittest.cpp b/test/entitymap_json_unittest.cpp
new file mode 100644
index 0000000..5f31e1b
--- /dev/null
+++ b/test/entitymap_json_unittest.cpp
@@ -0,0 +1,223 @@
+#include <ipmid/entity_map_json.hpp>
+#include <ipmid/types.hpp>
+#include <nlohmann/json.hpp>
+
+#include <utility>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace ipmi
+{
+namespace sensor
+{
+
+namespace
+{
+using ::testing::IsEmpty;
+
+TEST(ValidateJson, FailWithNonArrayReturnsEmpty)
+{
+    /* The entity map input json is expected to be an array of objects. */
+    auto j = R"(
+        {
+            "id" : 1,
+            "containerEntityId" : 2,
+            "containerEntityInstance" : 3,
+            "isList" : false,
+            "isLinked" : false,
+            "entities" : [
+                {"id" : 1, "instance" : 2},
+                {"id" : 1, "instance" : 3},
+                {"id" : 1, "instance" : 4},
+                {"id" : 1, "instance" : 5}
+            ]
+        }
+    )"_json;
+
+    EXPECT_THAT(buildJsonEntityMap(j), IsEmpty());
+}
+
+TEST(ValidateJson, FailWithMissingFieldReturnsEmpty)
+{
+    /* There are many required fields, let's just validate that if one is
+     * missing, it returns the empty map.
+     */
+    auto j = R"(
+        [
+            {
+                "id" : 1,
+                "containerEntityId" : 2,
+                "containerEntityInstance" : 3,
+                "isList" : false,
+                "entities" : [
+                    {"id" : 1, "instance" : 2},
+                    {"id" : 1, "instance" : 3},
+                    {"id" : 1, "instance" : 4},
+                    {"id" : 1, "instance" : 5}
+                ]
+            }
+        ]
+    )"_json;
+
+    EXPECT_THAT(buildJsonEntityMap(j), IsEmpty());
+}
+
+TEST(ValidateJson, AllValidEntryReturnsExpectedMap)
+{
+    /* Boring test where we provide completely valid information and expect the
+     * resulting map contains that information.
+     */
+    auto j = R"(
+        [
+            {
+                "id" : 1,
+                "containerEntityId" : 2,
+                "containerEntityInstance" : 3,
+                "isList" : false,
+                "isLinked" : false,
+                "entities" : [
+                    {"id" : 1, "instance" : 2},
+                    {"id" : 1, "instance" : 3},
+                    {"id" : 1, "instance" : 4},
+                    {"id" : 1, "instance" : 5}
+                ]
+            }
+        ]
+    )"_json;
+
+    auto map = buildJsonEntityMap(j);
+    EXPECT_FALSE(map.find(1) == map.end());
+    auto entry = map.find(1);
+    EXPECT_EQ(entry->first, 1);
+
+    /* TODO: someone could write an equality operator for this object. */
+    EXPECT_EQ(entry->second.containerEntityId, 2);
+    EXPECT_EQ(entry->second.containerEntityInstance, 3);
+    EXPECT_FALSE(entry->second.isList);
+    EXPECT_FALSE(entry->second.isLinked);
+    ContainedEntitiesArray expected = {
+        std::make_pair(1, 2), std::make_pair(1, 3), std::make_pair(1, 4),
+        std::make_pair(1, 5)};
+    EXPECT_EQ(entry->second.containedEntities, expected);
+}
+
+TEST(ValidateJson, EntryHasInsufficientContainerEntryCountReturnsEmpty)
+{
+    /* The container must have four pairs. (I don't know why, and maybe this
+     * restriction will change).
+     */
+    auto j = R"(
+        [
+            {
+                "id" : 1,
+                "containerEntityId" : 2,
+                "containerEntityInstance" : 3,
+                "isList" : false,
+                "isLinked" : false,
+                "entities" : [
+                    {"id" : 1, "instance" : 2},
+                    {"id" : 1, "instance" : 3},
+                    {"id" : 1, "instance" : 4}
+                ]
+            }
+        ]
+    )"_json;
+
+    EXPECT_THAT(buildJsonEntityMap(j), IsEmpty());
+}
+
+TEST(ValidateJson, ThereAreTwoEntriesOneInvalidReturnsEmpty)
+{
+    /* If any entry in the file is corrupt, the file is disregarded. */
+    auto j = R"(
+        [
+            {
+                "id" : 1,
+                "containerEntityId" : 2,
+                "containerEntityInstance" : 3,
+                "isList" : false,
+                "isLinked" : false,
+                "entities" : [
+                    {"id" : 1, "instance" : 2},
+                    {"id" : 1, "instance" : 3},
+                    {"id" : 1, "instance" : 4},
+                    {"id" : 1, "instance" : 5}
+                ]
+            },
+            {
+                "id" : 2,
+                "containerEntityId" : 2,
+                "containerEntityInstance" : 3,
+                "isList" : false,
+                "isLinked" : false,
+                "entities" : [
+                    {"id" : 1, "instance" : 2},
+                    {"id" : 1, "instance" : 3},
+                    {"id" : 1, "instance" : 4}
+                ]
+            }
+        ]
+    )"_json;
+
+    EXPECT_THAT(buildJsonEntityMap(j), IsEmpty());
+}
+
+TEST(ValidateJson, ThereAreTwoEntriesBothValidReturnsBoth)
+{
+    /* The map supports more than one entry, just validate this. */
+    auto j = R"(
+        [
+            {
+                "id" : 1,
+                "containerEntityId" : 2,
+                "containerEntityInstance" : 3,
+                "isList" : false,
+                "isLinked" : false,
+                "entities" : [
+                    {"id" : 1, "instance" : 2},
+                    {"id" : 1, "instance" : 3},
+                    {"id" : 1, "instance" : 4},
+                    {"id" : 1, "instance" : 5}
+                ]
+            },
+            {
+                "id" : 2,
+                "containerEntityId" : 2,
+                "containerEntityInstance" : 3,
+                "isList" : false,
+                "isLinked" : false,
+                "entities" : [
+                    {"id" : 1, "instance" : 6},
+                    {"id" : 1, "instance" : 7},
+                    {"id" : 1, "instance" : 8},
+                    {"id" : 1, "instance" : 9}
+                ]
+            }
+        ]
+    )"_json;
+
+    auto map = buildJsonEntityMap(j);
+    EXPECT_FALSE(map.find(1) == map.end());
+    EXPECT_FALSE(map.find(2) == map.end());
+
+    auto entry = map.find(1);
+    EXPECT_EQ(entry->first, 1);
+    EXPECT_EQ(entry->second.containerEntityId, 2);
+    EXPECT_EQ(entry->second.containerEntityInstance, 3);
+    EXPECT_FALSE(entry->second.isList);
+    EXPECT_FALSE(entry->second.isLinked);
+    ContainedEntitiesArray expected = {
+        std::make_pair(1, 2), std::make_pair(1, 3), std::make_pair(1, 4),
+        std::make_pair(1, 5)};
+    EXPECT_EQ(entry->second.containedEntities, expected);
+
+    entry = map.find(2);
+    expected = {std::make_pair(1, 6), std::make_pair(1, 7),
+                std::make_pair(1, 8), std::make_pair(1, 9)};
+    EXPECT_EQ(entry->second.containedEntities, expected);
+}
+
+} // namespace
+} // namespace sensor
+} // namespace ipmi
diff --git a/test/meson.build b/test/meson.build
index 6fcfd04..949a121 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -1,38 +1,103 @@
-gtest_dep = dependency('gtest', main: true, disabler: true, required: false)
-gmock_dep = dependency('gmock', disabler: true, required: false)
-if not gtest_dep.found() or not gmock_dep.found()
-    gtest_proj = import('cmake').subproject('googletest', required: false)
+gtest = dependency('gtest', main: true, disabler: true, required: false)
+gmock = dependency('gmock', disabler: true, required: false)
+if not gtest.found() or not gmock.found()
+    gtest_opts = import('cmake').subproject_options()
+    gtest_opts.add_cmake_defines({'CMAKE_CXX_FLAGS': '-Wno-pedantic'})
+    gtest_proj = import('cmake').subproject(
+        'googletest',
+        options: gtest_opts,
+        required: false,
+    )
     if gtest_proj.found()
-        gtest_dep = declare_dependency(
+        gtest = declare_dependency(
             dependencies: [
                 dependency('threads'),
                 gtest_proj.dependency('gtest'),
                 gtest_proj.dependency('gtest_main'),
             ],
         )
-        gmock_dep = gtest_proj.dependency('gmock')
+        gmock = gtest_proj.dependency('gmock')
     else
-        assert(
-            not get_option('tests').enabled(),
-            'Googletest is required if tests are enabled',
-        )
+        assert(not get_option('tests').enabled(), 'Googletest is required')
     endif
 endif
 
-test_sources = ['../integrity_algo.cpp', '../crypt_algo.cpp']
+test(
+    'entitymap_json',
+    executable(
+        'entitymap_json',
+        'entitymap_json_unittest.cpp',
+        include_directories: root_inc,
+        build_by_default: false,
+        implicit_include_directories: false,
+        dependencies: [
+            entity_map_json_dep,
+            gmock,
+            gtest,
+            nlohmann_json_dep,
+            sdbusplus_dep,
+        ],
+    ),
+)
 
-tests = ['cipher.cpp']
+# Build/add oemrouter_unittest to test suite
+# Issue #3325
+# test('oemrouter',
+#  executable(
+#    'oemrouter',
+#    'oemrouter_unittest.cpp',
+#    include_directories: root_inc,
+#    build_by_default: false,
+#    implicit_include_directories: false,
+#    dependencies: [gtest, gmock]
+#  ))
 
-foreach t : tests
-    test(
-        t,
-        executable(
-            t.underscorify(),
-            t,
-            test_sources,
-            include_directories: ['..'],
-            dependencies: [gtest_dep, gmock_dep, libcrypto_dep],
-        ),
-        workdir: meson.current_source_dir(),
-    )
-endforeach
+# Build/add message packing/unpacking unit tests
+test(
+    'message',
+    executable(
+        'message',
+        'message/pack.cpp',
+        'message/payload.cpp',
+        'message/unpack.cpp',
+        include_directories: root_inc,
+        build_by_default: false,
+        override_options: ['b_lundef=true'],
+        implicit_include_directories: false,
+        dependencies: [
+            boost,
+            crypto,
+            gmock,
+            gtest,
+            libsystemd_dep,
+            phosphor_logging_dep,
+            sdbusplus_dep,
+        ],
+    ),
+)
+
+# Build/add closesession_unittest to test suite
+test(
+    'session/closesession',
+    executable(
+        'session_closesession',
+        'session/closesession_unittest.cpp',
+        include_directories: root_inc,
+        build_by_default: false,
+        implicit_include_directories: false,
+        dependencies: [gtest, gmock],
+    ),
+)
+
+# Build/add sensorcommands_unittest to test suite
+test(
+    'dbus-sdr/sensorcommands',
+    executable(
+        'dbus-sdr_sensorcommands',
+        'dbus-sdr/sensorcommands_unittest.cpp',
+        include_directories: root_inc,
+        build_by_default: false,
+        implicit_include_directories: false,
+        dependencies: [sensorutils_dep, gtest, gmock],
+    ),
+)
diff --git a/test/message/pack.cpp b/test/message/pack.cpp
new file mode 100644
index 0000000..57bff06
--- /dev/null
+++ b/test/message/pack.cpp
@@ -0,0 +1,497 @@
+/**
+ * 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.
+ */
+#include <ipmid/api.hpp>
+#include <ipmid/message.hpp>
+
+#include <gtest/gtest.h>
+
+// TODO: Add testing of Payload response API
+
+TEST(PackBasics, Uint8)
+{
+    ipmi::message::Payload p;
+    uint8_t v = 4;
+    p.pack(v);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), sizeof(v));
+    // check that the bytes were correctly packed (LSB first)
+    ipmi::SecureBuffer k = {0x04};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Uint16)
+{
+    ipmi::message::Payload p;
+    uint16_t v = 0x8604;
+    p.pack(v);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), sizeof(v));
+    // check that the bytes were correctly packed (LSB first)
+    ipmi::SecureBuffer k = {0x04, 0x86};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Uint32)
+{
+    ipmi::message::Payload p;
+    uint32_t v = 0x02008604;
+    p.pack(v);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), sizeof(v));
+    // check that the bytes were correctly packed (LSB first)
+    ipmi::SecureBuffer k = {0x04, 0x86, 0x00, 0x02};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Uint64)
+{
+    ipmi::message::Payload p;
+    uint64_t v = 0x1122334402008604ull;
+    p.pack(v);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), sizeof(v));
+    // check that the bytes were correctly packed (LSB first)
+    ipmi::SecureBuffer k = {0x04, 0x86, 0x00, 0x02, 0x44, 0x33, 0x22, 0x11};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Uint24)
+{
+    ipmi::message::Payload p;
+    uint24_t v = 0x112358;
+    p.pack(v);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), types::nrFixedBits<decltype(v)> / CHAR_BIT);
+    // check that the bytes were correctly packed (LSB first)
+    ipmi::SecureBuffer k = {0x58, 0x23, 0x11};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Uint3Uint5)
+{
+    // individual bytes are packed low-order-bits first
+    // v1 will occupy [2:0], v2 will occupy [7:3]
+    ipmi::message::Payload p;
+    uint3_t v1 = 0x1;
+    uint5_t v2 = 0x19;
+    p.pack(v1, v2);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), (types::nrFixedBits<decltype(v1)> +
+                         types::nrFixedBits<decltype(v2)>) /
+                            CHAR_BIT);
+    // check that the bytes were correctly packed (LSB first)
+    ipmi::SecureBuffer k = {0xc9};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Boolx8)
+{
+    // individual bytes are packed low-order-bits first
+    // [v8, v7, v6, v5, v4, v3, v2, v1]
+    ipmi::message::Payload p;
+    bool v8 = true, v7 = true, v6 = false, v5 = false;
+    bool v4 = true, v3 = false, v2 = false, v1 = true;
+    p.pack(v1, v2, v3, v4, v5, v6, v7, v8);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), sizeof(uint8_t));
+    // check that the bytes were correctly packed (LSB first)
+    ipmi::SecureBuffer k = {0xc9};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Bitset8)
+{
+    // individual bytes are packed low-order-bits first
+    // a bitset for 8 bits fills the full byte
+    ipmi::message::Payload p;
+    std::bitset<8> v(0xc9);
+    p.pack(v);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), v.size() / CHAR_BIT);
+    // check that the bytes were correctly packed (LSB first)
+    ipmi::SecureBuffer k = {0xc9};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Bitset3Bitset5)
+{
+    // individual bytes are packed low-order-bits first
+    // v1 will occupy [2:0], v2 will occupy [7:3]
+    ipmi::message::Payload p;
+    std::bitset<3> v1(0x1);
+    std::bitset<5> v2(0x19);
+    p.pack(v1, v2);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), (v1.size() + v2.size()) / CHAR_BIT);
+    // check that the bytes were correctly packed (LSB first)
+    ipmi::SecureBuffer k = {0xc9};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Bitset32)
+{
+    // individual bytes are packed low-order-bits first
+    // v1 will occupy 4 bytes, but in LSByte first order
+    // v1[7:0] v1[15:9] v1[23:16] v1[31:24]
+    ipmi::message::Payload p;
+    std::bitset<32> v(0x02008604);
+    p.pack(v);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), v.size() / CHAR_BIT);
+    // check that the bytes were correctly packed (LSB first)
+    ipmi::SecureBuffer k = {0x04, 0x86, 0x00, 0x02};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Tuple)
+{
+    // tuples are the new struct, pack a tuple
+    ipmi::message::Payload p;
+    auto v = std::make_tuple(static_cast<uint16_t>(0x8604), 'A');
+    p.pack(v);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), sizeof(uint16_t) + sizeof(char));
+    // check that the bytes were correctly packed (LSB first)
+    ipmi::SecureBuffer k = {0x04, 0x86, 0x41};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Array4xUint8)
+{
+    // an array of bytes will be output verbatim, low-order element first
+    ipmi::message::Payload p;
+    std::array<uint8_t, 4> v = {{0x02, 0x00, 0x86, 0x04}};
+    p.pack(v);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), v.size() * sizeof(v[0]));
+    // check that the bytes were correctly packed (in byte order)
+    ipmi::SecureBuffer k = {0x02, 0x00, 0x86, 0x04};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Array4xUint32)
+{
+    // an array of multi-byte values will be output in order low-order
+    // element first, each multi-byte element in LSByte order
+    // v[0][7:0] v[0][15:9] v[0][23:16] v[0][31:24]
+    // v[1][7:0] v[1][15:9] v[1][23:16] v[1][31:24]
+    // v[2][7:0] v[2][15:9] v[2][23:16] v[2][31:24]
+    // v[3][7:0] v[3][15:9] v[3][23:16] v[3][31:24]
+    ipmi::message::Payload p;
+    std::array<uint32_t, 4> v = {
+        {0x11223344, 0x22446688, 0x33557799, 0x12345678}};
+    p.pack(v);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), v.size() * sizeof(v[0]));
+    // check that the bytes were correctly packed (in byte order)
+    ipmi::SecureBuffer k = {0x44, 0x33, 0x22, 0x11, 0x88, 0x66, 0x44, 0x22,
+                            0x99, 0x77, 0x55, 0x33, 0x78, 0x56, 0x34, 0x12};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, VectorUint32)
+{
+    // a vector of multi-byte values will be output in order low-order
+    // element first, each multi-byte element in LSByte order
+    // v[0][7:0] v[0][15:9] v[0][23:16] v[0][31:24]
+    // v[1][7:0] v[1][15:9] v[1][23:16] v[1][31:24]
+    // v[2][7:0] v[2][15:9] v[2][23:16] v[2][31:24]
+    // v[3][7:0] v[3][15:9] v[3][23:16] v[3][31:24]
+    ipmi::message::Payload p;
+    std::vector<uint32_t> v = {
+        {0x11223344, 0x22446688, 0x33557799, 0x12345678}};
+    p.pack(v);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), v.size() * sizeof(v[0]));
+    // check that the bytes were correctly packed (in byte order)
+    ipmi::SecureBuffer k = {0x44, 0x33, 0x22, 0x11, 0x88, 0x66, 0x44, 0x22,
+                            0x99, 0x77, 0x55, 0x33, 0x78, 0x56, 0x34, 0x12};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, VectorUint8)
+{
+    // a vector of bytes will be output verbatim, low-order element first
+    ipmi::message::Payload p;
+    std::vector<uint8_t> v = {0x02, 0x00, 0x86, 0x04};
+    p.pack(v);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), v.size() * sizeof(v[0]));
+    // check that the bytes were correctly packed (in byte order)
+    ipmi::SecureBuffer k = {0x02, 0x00, 0x86, 0x04};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, VectorUnaligned)
+{
+    ipmi::message::Payload p;
+    EXPECT_EQ(p.pack(true, std::vector<uint8_t>{1}), 1);
+    EXPECT_EQ(p.raw, ipmi::SecureBuffer{0b1});
+}
+
+TEST(PackBasics, StringView)
+{
+    ipmi::message::Payload p;
+    EXPECT_EQ(p.pack(std::string_view{"\x24\x30\x11"}), 0);
+    EXPECT_EQ(p.raw, ipmi::SecureBuffer({0x24, 0x30, 0x11}));
+}
+
+TEST(PackBasics, StringViewUnaligned)
+{
+    ipmi::message::Payload p;
+    EXPECT_EQ(p.pack(true, std::string_view{"abc"}), 1);
+    EXPECT_EQ(p.raw, ipmi::SecureBuffer({0b1}));
+}
+
+TEST(PackBasics, OptionalEmpty)
+{
+    // an optional will only pack if the value is present
+    ipmi::message::Payload p;
+    std::optional<uint32_t> v;
+    p.pack(v);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), 0);
+    // check that the bytes were correctly packed (in byte order)
+    ipmi::SecureBuffer k = {};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, OptionalContainsValue)
+{
+    // an optional will only pack if the value is present
+    ipmi::message::Payload p;
+    std::optional<uint32_t> v(0x04860002);
+    p.pack(v);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), sizeof(uint32_t));
+    // check that the bytes were correctly packed (in byte order)
+    ipmi::SecureBuffer k = {0x02, 0x00, 0x86, 0x04};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Payload)
+{
+    ipmi::message::Payload p;
+    EXPECT_EQ(p.pack(true), 0);
+    EXPECT_EQ(p.pack(ipmi::message::Payload({0x24, 0x30})), 0);
+    EXPECT_EQ(p.raw, ipmi::SecureBuffer({0b1, 0x24, 0x30}));
+}
+
+TEST(PackBasics, PayloadUnaligned)
+{
+    ipmi::message::Payload p;
+    EXPECT_EQ(p.pack(true, ipmi::message::Payload({0x24})), 1);
+    EXPECT_EQ(p.raw, ipmi::SecureBuffer({0b1}));
+}
+
+TEST(PackBasics, PayloadOtherUnaligned)
+{
+    ipmi::message::Payload p, q;
+    q.appendBits(1, 1);
+    EXPECT_EQ(p.pack(true), 0);
+    EXPECT_EQ(p.pack(q), 1);
+    EXPECT_EQ(p.raw, ipmi::SecureBuffer({0b1}));
+}
+
+TEST(PackBasics, PrependPayload)
+{
+    ipmi::message::Payload p;
+    EXPECT_EQ(p.pack(true), 0);
+    EXPECT_EQ(p.prepend(ipmi::message::Payload({0x24, 0x30})), 0);
+    EXPECT_EQ(p.raw, ipmi::SecureBuffer({0x24, 0x30, 0b1}));
+}
+
+TEST(PackBasics, PrependPayloadUnaligned)
+{
+    ipmi::message::Payload p;
+    p.appendBits(1, 1);
+    EXPECT_EQ(p.prepend(ipmi::message::Payload({0x24})), 1);
+    p.drain();
+    EXPECT_EQ(p.raw, ipmi::SecureBuffer({0b1}));
+}
+
+TEST(PackBasics, PrependPayloadOtherUnaligned)
+{
+    ipmi::message::Payload p, q;
+    q.appendBits(1, 1);
+    EXPECT_EQ(p.pack(true), 0);
+    EXPECT_EQ(p.prepend(q), 1);
+    EXPECT_EQ(p.raw, ipmi::SecureBuffer({0b1}));
+}
+
+TEST(PackAdvanced, Uints)
+{
+    // all elements will be processed in order, with each multi-byte
+    // element being processed LSByte first
+    // v1[7:0] v2[7:0] v2[15:8] v3[7:0] v3[15:8] v3[23:16] v3[31:24]
+    // v4[7:0] v4[15:8] v4[23:16] v4[31:24]
+    // v4[39:25] v4[47:40] v4[55:48] v4[63:56]
+    ipmi::message::Payload p;
+    uint8_t v1 = 0x02;
+    uint16_t v2 = 0x0604;
+    uint32_t v3 = 0x44332211;
+    uint64_t v4 = 0xccbbaa9988776655ull;
+    p.pack(v1, v2, v3, v4);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), sizeof(v1) + sizeof(v2) + sizeof(v3) + sizeof(v4));
+    // check that the bytes were correctly packed (LSB first)
+    ipmi::SecureBuffer k = {0x02, 0x04, 0x06, 0x11, 0x22, 0x33, 0x44, 0x55,
+                            0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackAdvanced, TupleInts)
+{
+    // all elements will be processed in order, with each multi-byte
+    // element being processed LSByte first
+    // v1[7:0] v2[7:0] v2[15:8] v3[7:0] v3[15:8] v3[23:16] v3[31:24]
+    // v4[7:0] v4[15:8] v4[23:16] v4[31:24]
+    // v4[39:25] v4[47:40] v4[55:48] v4[63:56]
+    ipmi::message::Payload p;
+    uint8_t v1 = 0x02;
+    uint16_t v2 = 0x0604;
+    uint32_t v3 = 0x44332211;
+    uint64_t v4 = 0xccbbaa9988776655ull;
+    auto v = std::make_tuple(v1, v2, v3, v4);
+    p.pack(v);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), sizeof(v1) + sizeof(v2) + sizeof(v3) + sizeof(v4));
+    // check that the bytes were correctly packed (LSB first)
+    ipmi::SecureBuffer k = {0x02, 0x04, 0x06, 0x11, 0x22, 0x33, 0x44, 0x55,
+                            0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackAdvanced, VariantArray)
+{
+    ipmi::message::Payload p;
+    std::variant<std::array<uint8_t, 2>, uint32_t> variant;
+    auto data = std::array<uint8_t, 2>{2, 4};
+    variant = data;
+
+    p.pack(variant);
+    ASSERT_EQ(p.size(), sizeof(data));
+
+    // check that the bytes were correctly packed packed (LSB first)
+    ipmi::SecureBuffer k = {2, 4};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackAdvanced, BoolsnBitfieldsnFixedIntsOhMy)
+{
+    // each element will be added, filling the low-order bits first
+    // with multi-byte values getting added LSByte first
+    // v1 will occupy k[0][1:0]
+    // v2 will occupy k[0][2]
+    // v3[4:0] will occupy k[0][7:3], v3[6:5] will occupy k[1][1:0]
+    // v4 will occupy k[1][2]
+    // v5 will occupy k[1][7:3]
+    ipmi::message::Payload p;
+    uint2_t v1 = 2;          // binary 0b10
+    bool v2 = true;          // binary 0b1
+    std::bitset<7> v3(0x73); // binary 0b1110011
+    bool v4 = false;         // binary 0b0
+    uint5_t v5 = 27;         // binary 0b11011
+    // concat binary: 0b1101101110011110 -> 0xdb9e -> 0x9e 0xdb (LSByte first)
+    p.pack(v1, v2, v3, v4, v5);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), sizeof(uint16_t));
+    // check that the bytes were correctly packed (LSB first)
+    ipmi::SecureBuffer k = {0x9e, 0xdb};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackAdvanced, UnalignedBitPacking)
+{
+    // unaligned multi-byte values will be packed the same as
+    // other bits, effectively building up a large value, low-order
+    // bits first, then outputting a stream of LSByte values
+    // v1 will occupy k[0][1:0]
+    // v2[5:0] will occupy k[0][7:2], v2[7:6] will occupy k[1][1:0]
+    // v3 will occupy k[1][2]
+    // v4[4:0] will occupy k[1][7:3] v4[12:5] will occupy k[2][7:0]
+    // v4[15:13] will occupy k[3][2:0]
+    // v5 will occupy k[3][3]
+    // v6[3:0] will occupy k[3][7:0] v6[11:4] will occupy k[4][7:0]
+    // v6[19:12] will occupy k[5][7:0] v6[27:20] will occupy k[6][7:0]
+    // v6[31:28] will occupy k[7][3:0]
+    // v7 will occupy k[7][7:4]
+    ipmi::message::Payload p;
+    uint2_t v1 = 2;           // binary 0b10
+    uint8_t v2 = 0xa5;        // binary 0b10100101
+    bool v3 = false;          // binary 0b0
+    uint16_t v4 = 0xa55a;     // binary 0b1010010101011010
+    bool v5 = true;           // binary 0b1
+    uint32_t v6 = 0xdbc3bd3c; // binary 0b11011011110000111011110100111100
+    uint4_t v7 = 9;           // binary 0b1001
+    // concat binary:
+    //   0b1001110110111100001110111101001111001101001010101101001010010110
+    //   -> 0x9dbc3bd3cd2ad296 -> 0x96 0xd2 0x2a 0xcd 0xd3 0x3b 0xbc 0x9d
+    p.pack(v1, v2, v3, v4, v5, v6, v7);
+    // check that the number of bytes matches
+    ASSERT_EQ(p.size(), sizeof(uint64_t));
+    // check that the bytes were correctly packed (LSB first)
+    ipmi::SecureBuffer k = {0x96, 0xd2, 0x2a, 0xcd, 0xd3, 0x3b, 0xbc, 0x9d};
+    ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackAdvanced, ComplexOptionalTuple)
+{
+    constexpr size_t macSize = 6;
+    // inspired from a real-world case of Get Session Info
+    constexpr uint8_t handle = 0x23;       // handle for active session
+    constexpr uint8_t maxSessions = 15;    // number of possible active sessions
+    constexpr uint8_t currentSessions = 4; // number of current active sessions
+    std::optional<                         // only returned for active session
+        std::tuple<uint8_t,                // user ID
+                   uint8_t,                // privilege
+                   uint4_t,                // channel number
+                   uint4_t                 // protocol (RMCP+)
+                   >>
+        activeSession;
+    std::optional<           // only returned for channel type LAN
+        std::tuple<uint32_t, // IPv4 address
+                   std::array<uint8_t, macSize>, // MAC address
+                   uint16_t                      // port
+                   >>
+        lanSession;
+
+    constexpr uint8_t userID = 7;
+    constexpr uint8_t priv = 4;
+    constexpr uint4_t channel = 2;
+    constexpr uint4_t protocol = 1;
+    activeSession.emplace(userID, priv, channel, protocol);
+    constexpr std::array<uint8_t, macSize> macAddr{0};
+    lanSession.emplace(0x0a010105, macAddr, 55327);
+
+    ipmi::message::Payload p;
+    p.pack(handle, maxSessions, currentSessions, activeSession, lanSession);
+    ASSERT_EQ(p.size(),
+              sizeof(handle) + sizeof(maxSessions) + sizeof(currentSessions) +
+                  3 * sizeof(uint8_t) + sizeof(uint32_t) +
+                  sizeof(uint8_t) * macSize + sizeof(uint16_t));
+    uint8_t protocol_channel =
+        (static_cast<uint8_t>(protocol) << 4) | static_cast<uint8_t>(channel);
+    ipmi::SecureBuffer k = {
+        handle, maxSessions, currentSessions, userID, priv, protocol_channel,
+        // ip addr
+        0x05, 0x01, 0x01, 0x0a,
+        // mac addr
+        0, 0, 0, 0, 0, 0,
+        // port
+        0x1f, 0xd8};
+    ASSERT_EQ(p.raw, k);
+}
diff --git a/test/message/payload.cpp b/test/message/payload.cpp
new file mode 100644
index 0000000..38977e4
--- /dev/null
+++ b/test/message/payload.cpp
@@ -0,0 +1,436 @@
+/**
+ * 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.
+ */
+#define SD_JOURNAL_SUPPRESS_LOCATION
+
+#include <systemd/sd-journal.h>
+
+#include <ipmid/api.hpp>
+#include <ipmid/message.hpp>
+
+#include <stdexcept>
+
+#include <gtest/gtest.h>
+
+TEST(Payload, InputSize)
+{
+    ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    size_t input_size = i.size();
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    ASSERT_EQ(input_size, p.size());
+}
+
+TEST(Payload, OutputSize)
+{
+    ipmi::message::Payload p;
+    ASSERT_EQ(0, p.size());
+    ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    p.pack(i);
+    ASSERT_EQ(i.size(), p.size());
+    p.pack(i);
+    p.pack(i);
+    ASSERT_EQ(3 * i.size(), p.size());
+}
+
+TEST(Payload, Resize)
+{
+    ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p;
+    p.pack(i);
+    p.resize(16);
+    ASSERT_EQ(p.size(), 16);
+}
+
+TEST(Payload, Data)
+{
+    ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p;
+    p.pack(i);
+    ASSERT_NE(nullptr, p.data());
+}
+
+TEST(PayloadResponse, Append)
+{
+    std::string s("0123456789abcdef");
+    ipmi::message::Payload p;
+    p.append(s.data(), s.data() + s.size());
+    ASSERT_EQ(s.size(), p.size());
+}
+
+TEST(PayloadResponse, AppendDrain)
+{
+    std::string s("0123456789abcdef");
+    ipmi::message::Payload p;
+    bool b = true;
+    // first pack a lone bit
+    p.pack(b);
+    p.append(s.data(), s.data() + s.size());
+    // append will 'drain' first, padding the lone bit into a full byte
+    ASSERT_EQ(s.size() + 1, p.size());
+}
+
+TEST(PayloadResponse, AppendBits)
+{
+    ipmi::message::Payload p;
+    p.appendBits(3, 0b101);
+    ASSERT_EQ(p.bitStream, 0b101);
+    p.appendBits(4, 0b1100);
+    ASSERT_EQ(p.bitStream, 0b1100101);
+    p.appendBits(1, 0b1);
+    ASSERT_EQ(p.bitStream, 0);
+    ASSERT_EQ(p.bitCount, 0);
+    // appended 8 bits, should be one byte
+    ASSERT_EQ(p.size(), 1);
+    ipmi::SecureBuffer k1 = {0b11100101};
+    ASSERT_EQ(p.raw, k1);
+    p.appendBits(7, 0b1110111);
+    // appended 7 more bits, should still be one byte
+    ASSERT_EQ(p.size(), 1);
+    p.drain();
+    // drain forces padding; should be two bytes now
+    ASSERT_EQ(p.size(), 2);
+    ipmi::SecureBuffer k2 = {0b11100101, 0b01110111};
+    ASSERT_EQ(p.raw, k2);
+}
+
+TEST(PayloadResponse, Drain16Bits)
+{
+    ipmi::message::Payload p;
+    p.bitStream = 0b1011010011001111;
+    p.bitCount = 16;
+    p.drain();
+    ASSERT_EQ(p.size(), 2);
+    ASSERT_EQ(p.bitCount, 0);
+    ASSERT_EQ(p.bitStream, 0);
+    ipmi::SecureBuffer k1 = {0b11001111, 0b10110100};
+    ASSERT_EQ(p.raw, k1);
+}
+
+TEST(PayloadResponse, Drain15Bits)
+{
+    ipmi::message::Payload p;
+    p.bitStream = 0b101101001100111;
+    p.bitCount = 15;
+    p.drain();
+    ASSERT_EQ(p.size(), 2);
+    ASSERT_EQ(p.bitCount, 0);
+    ASSERT_EQ(p.bitStream, 0);
+    ipmi::SecureBuffer k1 = {0b1100111, 0b1011010};
+    ASSERT_EQ(p.raw, k1);
+}
+
+TEST(PayloadResponse, Drain15BitsWholeBytesOnly)
+{
+    ipmi::message::Payload p;
+    p.bitStream = 0b101101001100111;
+    p.bitCount = 15;
+    p.drain(true);
+    // only the first whole byte should have been 'drained' into p.raw
+    ASSERT_EQ(p.size(), 1);
+    ASSERT_EQ(p.bitCount, 7);
+    ASSERT_EQ(p.bitStream, 0b1011010);
+    ipmi::SecureBuffer k1 = {0b1100111};
+    ASSERT_EQ(p.raw, k1);
+}
+
+TEST(PayloadRequest, Pop)
+{
+    ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    const auto& [vb, ve] = p.pop<uint8_t>(4);
+    std::vector<uint8_t> v(vb, ve);
+    std::vector<uint8_t> k = {0xbf, 0x04, 0x86, 0x00};
+    ASSERT_EQ(v, k);
+}
+
+TEST(PayloadRequest, FillBits)
+{
+    ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    p.fillBits(5);
+    ASSERT_FALSE(p.unpackError);
+    ASSERT_EQ(p.bitStream, 0xbf);
+    ASSERT_EQ(p.bitCount, 8);
+    // should still have 5 bits available, no change
+    p.fillBits(5);
+    ASSERT_FALSE(p.unpackError);
+    ASSERT_EQ(p.bitStream, 0xbf);
+    ASSERT_EQ(p.bitCount, 8);
+    // discard 5 bits (low order)
+    p.popBits(5);
+    // should add another byte into the stream (high order)
+    p.fillBits(5);
+    ASSERT_FALSE(p.unpackError);
+    ASSERT_EQ(p.bitStream, 0x25);
+    ASSERT_EQ(p.bitCount, 11);
+}
+
+TEST(PayloadRequest, FillBitsTooManyBits)
+{
+    ipmi::SecureBuffer i = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    p.fillBits(72);
+    ASSERT_TRUE(p.unpackError);
+}
+
+TEST(PayloadRequest, FillBitsNotEnoughBytes)
+{
+    ipmi::SecureBuffer i = {1, 2, 3, 4};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    p.fillBits(48);
+    ASSERT_TRUE(p.unpackError);
+}
+
+TEST(PayloadRequest, PopBits)
+{
+    ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    p.fillBits(4);
+    uint8_t v = p.popBits(4);
+    ASSERT_FALSE(p.unpackError);
+    ASSERT_EQ(p.bitStream, 0x0b);
+    ASSERT_EQ(p.bitCount, 4);
+    ASSERT_EQ(v, 0x0f);
+}
+
+TEST(PayloadRequest, PopBitsNoFillBits)
+{
+    ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    p.popBits(4);
+    ASSERT_TRUE(p.unpackError);
+}
+
+TEST(PayloadRequest, DiscardBits)
+{
+    ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    p.fillBits(5);
+    ASSERT_FALSE(p.unpackError);
+    ASSERT_EQ(p.bitStream, 0xbf);
+    ASSERT_EQ(p.bitCount, 8);
+    p.discardBits();
+    ASSERT_FALSE(p.unpackError);
+    ASSERT_EQ(p.bitStream, 0);
+    ASSERT_EQ(p.bitCount, 0);
+}
+
+TEST(PayloadRequest, FullyUnpacked)
+{
+    ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint32_t v1;
+    p.unpack(v1);
+    // still one remaining byte
+    ASSERT_FALSE(p.fullyUnpacked());
+    p.fillBits(3);
+    p.popBits(3);
+    // still five remaining bits
+    ASSERT_FALSE(p.fullyUnpacked());
+    p.fillBits(5);
+    p.popBits(5);
+    // fully unpacked, should be no errors
+    ASSERT_TRUE(p.fullyUnpacked());
+    p.fillBits(4);
+    // fullyUnpacked fails because an attempt to unpack too many bytes
+    ASSERT_FALSE(p.fullyUnpacked());
+}
+
+TEST(PayloadRequest, ResetInternal)
+{
+    ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    p.fillBits(4);
+    p.unpackError = true;
+    p.reset();
+    ASSERT_EQ(p.rawIndex, 0);
+    ASSERT_EQ(p.bitStream, 0);
+    ASSERT_EQ(p.bitCount, 0);
+    ASSERT_FALSE(p.unpackError);
+}
+
+TEST(PayloadRequest, ResetUsage)
+{
+    // Payload.reset is used to rewind the unpacking to the initial
+    // state. This is needed so that OEM commands can unpack the group
+    // number or the IANA to determine which handler needs to be called
+    ipmi::SecureBuffer i = {0x04, 0x86};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint8_t v1;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v1), 0);
+    // check that the payload was not fully unpacked
+    ASSERT_FALSE(p.fullyUnpacked());
+    uint8_t k1 = 0x04;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v1, k1);
+    // do a reset on the payload
+    p.reset();
+    // unpack a uint16
+    uint16_t v2;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v2), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    uint16_t k2 = 0x8604;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v2, k2);
+}
+
+TEST(PayloadRequest, PartialPayload)
+{
+    ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint8_t v1;
+    ipmi::message::Payload localPayload;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v1, localPayload), 0);
+    // check that the payload was partially unpacked and not in error
+    ASSERT_FALSE(p.fullyUnpacked());
+    ASSERT_FALSE(p.unpackError);
+    // check that the 'extracted' payload is not fully unpacked
+    ASSERT_FALSE(localPayload.fullyUnpacked());
+    uint8_t k1 = 0xbf;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v1, k1);
+    uint32_t v2;
+    // unpack using the 'extracted' payload
+    ASSERT_EQ(localPayload.unpack(v2), 0);
+    ASSERT_TRUE(localPayload.fullyUnpacked());
+    uint32_t k2 = 0x02008604;
+    ASSERT_EQ(v2, k2);
+}
+
+std::vector<std::string> logs;
+
+extern "C"
+{
+int sd_journal_sendv(const struct iovec* iov, int /* n */)
+{
+    logs.push_back(std::string((char*)iov[0].iov_base, iov[0].iov_len));
+    return 0;
+}
+}
+
+class PayloadLogging : public testing::Test
+{
+  public:
+    void SetUp()
+    {
+        logs.clear();
+    }
+};
+
+TEST_F(PayloadLogging, TrailingOk)
+{
+    {
+        ipmi::message::Payload p({1, 2});
+    }
+    EXPECT_EQ(logs.size(), 0);
+}
+
+TEST_F(PayloadLogging, EnforcingUnchecked)
+{
+    {
+        ipmi::message::Payload p({1, 2});
+        p.trailingOk = false;
+    }
+    EXPECT_EQ(logs.size(), 1);
+}
+
+TEST_F(PayloadLogging, EnforcingUncheckedUnpacked)
+{
+    {
+        ipmi::message::Payload p({1, 2});
+        p.trailingOk = false;
+        uint8_t out;
+        p.unpack(out, out);
+    }
+    EXPECT_EQ(logs.size(), 1);
+}
+
+TEST_F(PayloadLogging, EnforcingUncheckedError)
+{
+    {
+        ipmi::message::Payload p({1, 2});
+        p.trailingOk = false;
+        uint32_t out;
+        p.unpack(out);
+    }
+    EXPECT_EQ(logs.size(), 0);
+}
+
+TEST_F(PayloadLogging, EnforcingChecked)
+{
+    {
+        ipmi::message::Payload p({1, 2});
+        p.trailingOk = false;
+        EXPECT_FALSE(p.fullyUnpacked());
+    }
+    EXPECT_EQ(logs.size(), 0);
+}
+
+TEST_F(PayloadLogging, EnforcingCheckedUnpacked)
+{
+    {
+        ipmi::message::Payload p({1, 2});
+        p.trailingOk = false;
+        uint8_t out;
+        p.unpack(out, out);
+        EXPECT_TRUE(p.fullyUnpacked());
+    }
+    EXPECT_EQ(logs.size(), 0);
+}
+
+TEST_F(PayloadLogging, EnforcingUnpackPayload)
+{
+    {
+        ipmi::message::Payload p;
+        {
+            ipmi::message::Payload q({1, 2});
+            q.trailingOk = false;
+            q.unpack(p);
+        }
+        EXPECT_EQ(logs.size(), 0);
+    }
+    EXPECT_EQ(logs.size(), 1);
+}
+
+TEST_F(PayloadLogging, EnforcingMove)
+{
+    {
+        ipmi::message::Payload p;
+        {
+            ipmi::message::Payload q({1, 2});
+            q.trailingOk = false;
+            p = std::move(q);
+        }
+        EXPECT_EQ(logs.size(), 0);
+    }
+    EXPECT_EQ(logs.size(), 1);
+}
+
+TEST_F(PayloadLogging, EnforcingException)
+{
+    try
+    {
+        ipmi::message::Payload p({1, 2});
+        p.trailingOk = false;
+        throw std::runtime_error("test");
+    }
+    catch (...)
+    {}
+    EXPECT_EQ(logs.size(), 0);
+}
diff --git a/test/message/unpack.cpp b/test/message/unpack.cpp
new file mode 100644
index 0000000..acc6088
--- /dev/null
+++ b/test/message/unpack.cpp
@@ -0,0 +1,897 @@
+/**
+ * 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.
+ */
+#include <ipmid/api.hpp>
+#include <ipmid/message.hpp>
+
+#include <gtest/gtest.h>
+
+TEST(Uints, Uint8)
+{
+    ipmi::SecureBuffer i = {0x04};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint8_t v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    uint8_t k = 0x04;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Uints, Uint8TooManyBytes)
+{
+    ipmi::SecureBuffer i = {0x04, 0x86};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint8_t v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was not fully unpacked
+    ASSERT_FALSE(p.fullyUnpacked());
+    uint8_t k = 0x04;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Uints, Uint8InsufficientBytes)
+{
+    ipmi::SecureBuffer i = {};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint8_t v = 0;
+    // check that the number of bytes matches
+    ASSERT_NE(p.unpack(v), 0);
+    // check that the payload was not fully unpacked (comprehends unpack errors)
+    ASSERT_FALSE(p.fullyUnpacked());
+    // check that v is zero
+    ASSERT_EQ(v, 0);
+}
+
+TEST(Uints, Uint16)
+{
+    ipmi::SecureBuffer i = {0x04, 0x86};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint16_t v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    uint16_t k = 0x8604;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Uints, Uint16TooManyBytes)
+{
+    ipmi::SecureBuffer i = {0x04, 0x86, 0x00};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint16_t v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was not fully unpacked
+    ASSERT_FALSE(p.fullyUnpacked());
+    uint16_t k = 0x8604;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Uints, Uint16InsufficientBytes)
+{
+    ipmi::SecureBuffer i = {0x04};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint16_t v = 0;
+    // check that the number of bytes matches
+    ASSERT_NE(p.unpack(v), 0);
+    // check that the payload was not fully unpacked (comprehends unpack errors)
+    ASSERT_FALSE(p.fullyUnpacked());
+    // check that v is zero
+    ASSERT_EQ(v, 0);
+}
+
+TEST(Uints, Uint32)
+{
+    ipmi::SecureBuffer i = {0x04, 0x86, 0x00, 0x02};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint32_t v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    uint32_t k = 0x02008604;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Uints, Uint32TooManyBytes)
+{
+    ipmi::SecureBuffer i = {0x04, 0x86, 0x00, 0x02, 0x44};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint32_t v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was not fully unpacked
+    ASSERT_FALSE(p.fullyUnpacked());
+    uint32_t k = 0x02008604;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Uints, Uint32InsufficientBytes)
+{
+    ipmi::SecureBuffer i = {0x04, 0x86, 0x00};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint32_t v = 0;
+    // check that the number of bytes matches
+    ASSERT_NE(p.unpack(v), 0);
+    // check that the payload was not fully unpacked (comprehends unpack errors)
+    ASSERT_FALSE(p.fullyUnpacked());
+    // check that v is zero
+    ASSERT_EQ(v, 0);
+}
+
+TEST(Uints, Uint64)
+{
+    ipmi::SecureBuffer i = {0x04, 0x86, 0x00, 0x02, 0x44, 0x33, 0x22, 0x11};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint64_t v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    uint64_t k = 0x1122334402008604ull;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Uints, Uint64TooManyBytes)
+{
+    ipmi::SecureBuffer i = {0x04, 0x86, 0x00, 0x02, 0x44,
+                            0x33, 0x22, 0x11, 0x55};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint64_t v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was not fully unpacked
+    ASSERT_FALSE(p.fullyUnpacked());
+    uint64_t k = 0x1122334402008604ull;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Uints, Uint64InsufficientBytes)
+{
+    ipmi::SecureBuffer i = {0x04, 0x86, 0x00, 0x02, 0x44, 0x33, 0x22};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint64_t v = 0;
+    // check that the number of bytes matches
+    ASSERT_NE(p.unpack(v), 0);
+    // check that the payload was not fully unpacked (comprehends unpack errors)
+    ASSERT_FALSE(p.fullyUnpacked());
+    // check that v is zero
+    ASSERT_EQ(v, 0);
+}
+
+TEST(Uints, Uint24)
+{
+    ipmi::SecureBuffer i = {0x58, 0x23, 0x11};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint24_t v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    uint24_t k = 0x112358;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v, k);
+}
+
+TEST(FixedInts, Uint24TooManyBytes)
+{
+    ipmi::SecureBuffer i = {0x58, 0x23, 0x11, 0x00};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint24_t v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was not fully unpacked
+    ASSERT_FALSE(p.fullyUnpacked());
+    uint24_t k = 0x112358;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v, k);
+}
+
+TEST(FixedInts, Uint24InsufficientBytes)
+{
+    ipmi::SecureBuffer i = {0x58, 0x23};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint24_t v = 0;
+    // check that the number of bytes matches
+    ASSERT_NE(p.unpack(v), 0);
+    // check that the payload was not fully unpacked (comprehends unpack errors)
+    ASSERT_FALSE(p.fullyUnpacked());
+    // check that v is zero
+    ASSERT_EQ(v, 0);
+}
+
+TEST(FixedInts, Uint3Uint5)
+{
+    // individual bytes are unpacked low-order-bits first
+    // v1 will use [2:0], v2 will use [7:3]
+    ipmi::SecureBuffer i = {0xc9};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint3_t v1;
+    uint5_t v2;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v1, v2), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    uint3_t k1 = 0x1;
+    uint5_t k2 = 0x19;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v1, k1);
+    ASSERT_EQ(v2, k2);
+}
+
+TEST(FixedInts, Uint3Uint4TooManyBits)
+{
+    // high order bit should not get unpacked
+    ipmi::SecureBuffer i = {0xc9};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint3_t v1;
+    uint4_t v2;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v1, v2), 0);
+    // check that the payload was not fully unpacked
+    ASSERT_FALSE(p.fullyUnpacked());
+    uint3_t k1 = 0x1;
+    uint4_t k2 = 0x9;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v1, k1);
+    ASSERT_EQ(v2, k2);
+}
+
+TEST(FixedInts, Uint3Uint6InsufficientBits)
+{
+    // insufficient bits to unpack v2
+    ipmi::SecureBuffer i = {0xc9};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint3_t v1;
+    uint6_t v2;
+    // check that the number of bytes matches
+    ASSERT_NE(p.unpack(v1, v2), 0);
+    // check that the payload was not fully unpacked (comprehends unpack errors)
+    ASSERT_FALSE(p.fullyUnpacked());
+    uint3_t k1 = 0x1;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v1, k1);
+    // check that v2 is zero
+    ASSERT_EQ(v2, 0);
+}
+
+TEST(Bools, Boolx8)
+{
+    // individual bytes are unpacked low-order-bits first
+    // [v8, v7, v6, v5, v4, v3, v2, v1]
+    ipmi::SecureBuffer i = {0xc9};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    bool v8, v7, v6, v5;
+    bool v4, v3, v2, v1;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v1, v2, v3, v4, v5, v6, v7, v8), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    // check that the bytes were correctly unpacked (LSB first)
+    bool k8 = true, k7 = true, k6 = false, k5 = false;
+    bool k4 = true, k3 = false, k2 = false, k1 = true;
+    ASSERT_EQ(v1, k1);
+    ASSERT_EQ(v2, k2);
+    ASSERT_EQ(v3, k3);
+    ASSERT_EQ(v4, k4);
+    ASSERT_EQ(v5, k5);
+    ASSERT_EQ(v6, k6);
+    ASSERT_EQ(v7, k7);
+    ASSERT_EQ(v8, k8);
+}
+
+TEST(Bools, Boolx8TooManyBits)
+{
+    // high order bit should not get unpacked
+    // individual bytes are unpacked low-order-bits first
+    // [v7, v6, v5, v4, v3, v2, v1]
+    ipmi::SecureBuffer i = {0xc9};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    bool v7, v6, v5;
+    bool v4, v3, v2, v1;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v1, v2, v3, v4, v5, v6, v7), 0);
+    // check that the payload was not fully unpacked
+    ASSERT_FALSE(p.fullyUnpacked());
+    // check that the bytes were correctly unpacked (LSB first)
+    bool k7 = true, k6 = false, k5 = false;
+    bool k4 = true, k3 = false, k2 = false, k1 = true;
+    ASSERT_EQ(v1, k1);
+    ASSERT_EQ(v2, k2);
+    ASSERT_EQ(v3, k3);
+    ASSERT_EQ(v4, k4);
+    ASSERT_EQ(v5, k5);
+    ASSERT_EQ(v6, k6);
+    ASSERT_EQ(v7, k7);
+}
+
+TEST(Bools, Boolx8InsufficientBits)
+{
+    // individual bytes are unpacked low-order-bits first
+    // [v8, v7, v6, v5, v4, v3, v2, v1]
+    ipmi::SecureBuffer i = {0xc9};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    bool v9;
+    bool v8, v7, v6, v5;
+    bool v4, v3, v2, v1;
+    // check that the number of bytes matches
+    ASSERT_NE(p.unpack(v1, v2, v3, v4, v5, v6, v7, v8, v9), 0);
+    // check that the payload was not fully unpacked (comprehends unpack errors)
+    ASSERT_FALSE(p.fullyUnpacked());
+    // check that the bytes were correctly unpacked (LSB first)
+    bool k8 = true, k7 = true, k6 = false, k5 = false;
+    bool k4 = true, k3 = false, k2 = false, k1 = true;
+    ASSERT_EQ(v1, k1);
+    ASSERT_EQ(v2, k2);
+    ASSERT_EQ(v3, k3);
+    ASSERT_EQ(v4, k4);
+    ASSERT_EQ(v5, k5);
+    ASSERT_EQ(v6, k6);
+    ASSERT_EQ(v7, k7);
+    ASSERT_EQ(v8, k8);
+}
+
+TEST(Bitsets, Bitset8)
+{
+    // individual bytes are unpacked low-order-bits first
+    // a bitset for 8 bits fills the full byte
+    ipmi::SecureBuffer i = {0xc9};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::bitset<8> v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    std::bitset<8> k(0xc9);
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Bitsets, Bitset7TooManyBits)
+{
+    // individual bytes are unpacked low-order-bits first
+    // a bitset for 8 bits fills the full byte
+    ipmi::SecureBuffer i = {0xc9};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::bitset<7> v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was not fully unpacked
+    ASSERT_FALSE(p.fullyUnpacked());
+    std::bitset<7> k(0x49);
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Bitsets, Bitset9InsufficientBits)
+{
+    // individual bytes are unpacked low-order-bits first
+    // a bitset for 8 bits fills the full byte
+    ipmi::SecureBuffer i = {0xc9};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::bitset<9> v;
+    // check that the number of bytes matches
+    ASSERT_NE(p.unpack(v), 0);
+    // check that the payload was not fully unpacked (comprehends unpack errors)
+    ASSERT_FALSE(p.fullyUnpacked());
+    std::bitset<9> k(0);
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Bitsets, Bitset3Bitset5)
+{
+    // individual bytes are unpacked low-order-bits first
+    // v1 will use [2:0], v2 will use [7:3]
+    ipmi::SecureBuffer i = {0xc9};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::bitset<3> v1;
+    std::bitset<5> v2;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v1, v2), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    std::bitset<3> k1(0x1);
+    std::bitset<5> k2(0x19);
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v1, k1);
+    ASSERT_EQ(v2, k2);
+}
+
+TEST(Bitsets, Bitset3Bitset4TooManyBits)
+{
+    // high order bit should not get unpacked
+    ipmi::SecureBuffer i = {0xc9};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::bitset<3> v1;
+    std::bitset<4> v2;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v1, v2), 0);
+    // check that the payload was not fully unpacked
+    ASSERT_FALSE(p.fullyUnpacked());
+    std::bitset<3> k1 = 0x1;
+    std::bitset<4> k2 = 0x9;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v1, k1);
+    ASSERT_EQ(v2, k2);
+}
+
+TEST(Bitsets, Bitset3Bitset6InsufficientBits)
+{
+    // insufficient bits to unpack v2
+    ipmi::SecureBuffer i = {0xc9};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::bitset<3> v1;
+    std::bitset<6> v2;
+    // check that the number of bytes matches
+    ASSERT_NE(p.unpack(v1, v2), 0);
+    // check that the payload was not fully unpacked (comprehends unpack errors)
+    ASSERT_FALSE(p.fullyUnpacked());
+    std::bitset<3> k1 = 0x1;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v1, k1);
+    // check that v2 is zero
+    ASSERT_EQ(v2, 0);
+}
+
+TEST(Bitsets, Bitset32)
+{
+    // individual bytes are unpacked low-order-bits first
+    // v1 will use 4 bytes, but in LSByte first order
+    // v1[7:0] v1[15:9] v1[23:16] v1[31:24]
+    ipmi::SecureBuffer i = {0xb4, 0x86, 0x91, 0xc2};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::bitset<32> v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    std::bitset<32> k(0xc29186b4);
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Bitsets, Bitset31TooManyBits)
+{
+    // high order bit should not get unpacked
+    ipmi::SecureBuffer i = {0xb4, 0x86, 0x91, 0xc2};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::bitset<31> v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was not fully unpacked
+    ASSERT_FALSE(p.fullyUnpacked());
+    std::bitset<31> k(0x429186b4);
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Bitsets, Bitset33InsufficientBits)
+{
+    // insufficient bits to unpack v2
+    ipmi::SecureBuffer i = {0xb4, 0x86, 0x91, 0xc2};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::bitset<33> v;
+    // check that the number of bytes matches
+    ASSERT_NE(p.unpack(v), 0);
+    // check that the payload was not fully unpacked (comprehends unpack errors)
+    ASSERT_FALSE(p.fullyUnpacked());
+    // check that v is zero
+    ASSERT_EQ(v, 0);
+}
+
+TEST(Arrays, Array4xUint8)
+{
+    // an array of bytes will be read verbatim, low-order element first
+    ipmi::SecureBuffer i = {0x02, 0x00, 0x86, 0x04};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::array<uint8_t, 4> v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    std::array<uint8_t, 4> k = {{0x02, 0x00, 0x86, 0x04}};
+    // check that the bytes were correctly unpacked (in byte order)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Arrays, Array4xUint8TooManyBytes)
+{
+    // last byte should not get unpacked
+    // an array of bytes will be read verbatim, low-order element first
+    ipmi::SecureBuffer i = {0x02, 0x00, 0x86, 0x04, 0x22};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::array<uint8_t, 4> v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was not fully unpacked
+    ASSERT_FALSE(p.fullyUnpacked());
+    std::array<uint8_t, 4> k = {{0x02, 0x00, 0x86, 0x04}};
+    // check that the bytes were correctly unpacked (in byte order)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Arrays, Array4xUint8InsufficientBytes)
+{
+    // last byte should not get unpacked
+    // an array of bytes will be read verbatim, low-order element first
+    ipmi::SecureBuffer i = {0x02, 0x00, 0x86};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::array<uint8_t, 4> v;
+    // check that the number of bytes matches
+    ASSERT_NE(p.unpack(v), 0);
+    // check that the payload was not fully unpacked
+    ASSERT_FALSE(p.fullyUnpacked());
+    // arrays of uint8_t will be unpacked all at once
+    // so nothing will get unpacked
+    std::array<uint8_t, 4> k = {{0, 0, 0, 0}};
+    // check that the bytes were correctly unpacked (in byte order)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Arrays, Array4xUint32)
+{
+    // an array of multi-byte values will be unpacked in order low-order
+    // element first, each multi-byte element in LSByte order
+    // v[0][7:0] v[0][15:9] v[0][23:16] v[0][31:24]
+    // v[1][7:0] v[1][15:9] v[1][23:16] v[1][31:24]
+    // v[2][7:0] v[2][15:9] v[2][23:16] v[2][31:24]
+    // v[3][7:0] v[3][15:9] v[3][23:16] v[3][31:24]
+    ipmi::SecureBuffer i = {0x44, 0x33, 0x22, 0x11, 0x88, 0x66, 0x44, 0x22,
+                            0x99, 0x77, 0x55, 0x33, 0x78, 0x56, 0x34, 0x12};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::array<uint32_t, 4> v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    std::array<uint32_t, 4> k = {
+        {0x11223344, 0x22446688, 0x33557799, 0x12345678}};
+    // check that the bytes were correctly unpacked (in byte order)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Arrays, Array4xUint32TooManyBytes)
+{
+    // last byte should not get unpacked
+    // an array of multi-byte values will be unpacked in order low-order
+    // element first, each multi-byte element in LSByte order
+    // v[0][7:0] v[0][15:9] v[0][23:16] v[0][31:24]
+    // v[1][7:0] v[1][15:9] v[1][23:16] v[1][31:24]
+    // v[2][7:0] v[2][15:9] v[2][23:16] v[2][31:24]
+    // v[3][7:0] v[3][15:9] v[3][23:16] v[3][31:24]
+    ipmi::SecureBuffer i = {0x44, 0x33, 0x22, 0x11, 0x88, 0x66,
+                            0x44, 0x22, 0x99, 0x77, 0x55, 0x33,
+                            0x78, 0x56, 0x34, 0x12, 0xaa};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::array<uint32_t, 4> v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was not fully unpacked
+    ASSERT_FALSE(p.fullyUnpacked());
+    std::array<uint32_t, 4> k = {
+        {0x11223344, 0x22446688, 0x33557799, 0x12345678}};
+    // check that the bytes were correctly unpacked (in byte order)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Arrays, Array4xUint32InsufficientBytes)
+{
+    // last value should not get unpacked
+    // an array of multi-byte values will be unpacked in order low-order
+    // element first, each multi-byte element in LSByte order
+    // v[0][7:0] v[0][15:9] v[0][23:16] v[0][31:24]
+    // v[1][7:0] v[1][15:9] v[1][23:16] v[1][31:24]
+    // v[2][7:0] v[2][15:9] v[2][23:16] v[2][31:24]
+    // v[3][7:0] v[3][15:9] v[3][23:16] v[3][31:24]
+    ipmi::SecureBuffer i = {0x44, 0x33, 0x22, 0x11, 0x88, 0x66, 0x44, 0x22,
+                            0x99, 0x77, 0x55, 0x33, 0x78, 0x56, 0x34};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::array<uint32_t, 4> v;
+    // check that the number of bytes matches
+    ASSERT_NE(p.unpack(v), 0);
+    // check that the payload was not fully unpacked
+    ASSERT_FALSE(p.fullyUnpacked());
+    // arrays of uint32_t will be unpacked in a way that looks atomic
+    std::array<uint32_t, 4> k = {{0, 0, 0, 0}};
+    // check that the bytes were correctly unpacked (in byte order)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Vectors, VectorUint32)
+{
+    // a vector of multi-byte values will be unpacked in order low-order
+    // element first, each multi-byte element in LSByte order
+    // v[0][7:0] v[0][15:9] v[0][23:16] v[0][31:24]
+    // v[1][7:0] v[1][15:9] v[1][23:16] v[1][31:24]
+    // v[2][7:0] v[2][15:9] v[2][23:16] v[2][31:24]
+    // v[3][7:0] v[3][15:9] v[3][23:16] v[3][31:24]
+    ipmi::SecureBuffer i = {0x44, 0x33, 0x22, 0x11, 0x88, 0x66, 0x44, 0x22,
+                            0x99, 0x77, 0x55, 0x33, 0x78, 0x56, 0x34, 0x12};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::vector<uint32_t> v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    std::vector<uint32_t> k = {0x11223344, 0x22446688, 0x33557799, 0x12345678};
+    // check that the bytes were correctly unpacked (in byte order)
+    ASSERT_EQ(v, k);
+}
+
+// combination of TooManyBytes and InsufficientBytes because
+// vectors will attempt to unpack full <T>s until the end of the input
+TEST(Vectors, VectorUint32NonIntegralBytes)
+{
+    // last value should not get unpacked
+    // a vector of multi-byte values will be unpacked in order low-order
+    // element first, each multi-byte element in LSByte order,
+    // and will attempt to consume all bytes remaining
+    // v[0][7:0] v[0][15:9] v[0][23:16] v[0][31:24]
+    // v[1][7:0] v[1][15:9] v[1][23:16] v[1][31:24]
+    // v[2][7:0] v[2][15:9] v[2][23:16] v[2][31:24]
+    // v[3][7:0] v[3][15:9] v[3][23:16] v[3][31:24]
+    ipmi::SecureBuffer i = {0x44, 0x33, 0x22, 0x11, 0x88, 0x66, 0x44, 0x22,
+                            0x99, 0x77, 0x55, 0x33, 0x78, 0x56, 0x34};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::vector<uint32_t> v;
+    // check that the vector unpacks successfully
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was not fully unpacked
+    ASSERT_FALSE(p.fullyUnpacked());
+    // arrays of uint32_t will be unpacked one at a time, so the
+    // last entry should not get unpacked properly
+    std::vector<uint32_t> k = {0x11223344, 0x22446688, 0x33557799};
+    // check that the bytes were correctly unpacked (in byte order)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Vectors, VectorUint8)
+{
+    // a vector of bytes will be unpacked verbatim, low-order element first
+    ipmi::SecureBuffer i = {0x02, 0x00, 0x86, 0x04};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::vector<uint8_t> v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    std::vector<uint8_t> k = {0x02, 0x00, 0x86, 0x04};
+    // check that the bytes were correctly unpacked (in byte order)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Vectors, VectorEmptyOk)
+{
+    // an empty input vector to show that unpacking elements is okay
+    ipmi::SecureBuffer i{};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::vector<uint32_t> v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    std::vector<uint32_t> k{};
+    // check that the unpacked vector is empty as expected
+    ASSERT_EQ(v, k);
+}
+
+TEST(Vectors, VectorOfTuplesOk)
+{
+    // a vector of bytes will be unpacked verbatim, low-order element first
+    ipmi::SecureBuffer i = {0x02, 0x00, 0x86, 0x04};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::vector<std::tuple<uint8_t, uint8_t>> v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    std::vector<std::tuple<uint8_t, uint8_t>> k = {{0x02, 0x00}, {0x86, 0x04}};
+    // check that the bytes were correctly unpacked (in byte order)
+    ASSERT_EQ(v, k);
+}
+
+TEST(Vectors, VectorOfTuplesInsufficientBytes)
+{
+    // a vector of bytes will be unpacked verbatim, low-order element first
+    ipmi::SecureBuffer i = {0x02, 0x00, 0x86, 0x04, 0xb4};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::vector<std::tuple<uint8_t, uint8_t>> v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was not fully unpacked
+    ASSERT_FALSE(p.fullyUnpacked());
+    std::vector<std::tuple<uint8_t, uint8_t>> k = {{0x02, 0x00}, {0x86, 0x04}};
+    // check that the bytes were correctly unpacked (in byte order)
+    ASSERT_EQ(v, k);
+}
+
+// Cannot test TooManyBytes or InsufficientBytes for vector<uint8_t>
+// because it will always unpack whatever bytes are remaining
+// TEST(Vectors, VectorUint8TooManyBytes) {}
+// TEST(Vectors, VectorUint8InsufficientBytes) {}
+
+TEST(UnpackAdvanced, OptionalOk)
+{
+    // a vector of bytes will be unpacked verbatim, low-order element first
+    ipmi::SecureBuffer i = {0xbe, 0x02, 0x00, 0x86, 0x04};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::optional<std::tuple<uint8_t, uint32_t>> v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    std::optional<std::tuple<uint8_t, uint32_t>> k{{0xbe, 0x04860002}};
+    // check that the bytes were correctly unpacked (in byte order)
+    ASSERT_EQ(v, k);
+}
+
+TEST(UnpackAdvanced, OptionalInsufficientBytes)
+{
+    // a vector of bytes will be unpacked verbatim, low-order element first
+    ipmi::SecureBuffer i = {0x02, 0x00, 0x86, 0x04};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::optional<std::tuple<uint8_t, uint32_t>> v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was fully unpacked
+    ASSERT_FALSE(p.fullyUnpacked());
+    std::optional<std::tuple<uint8_t, uint32_t>> k;
+    // check that the bytes were correctly unpacked (in byte order)
+    ASSERT_EQ(v, k);
+}
+
+TEST(UnpackAdvanced, Uints)
+{
+    // all elements will be unpacked in order, with each multi-byte
+    // element being processed LSByte first
+    // v1[7:0] v2[7:0] v2[15:8] v3[7:0] v3[15:8] v3[23:16] v3[31:24]
+    // v4[7:0] v4[15:8] v4[23:16] v4[31:24]
+    // v4[39:25] v4[47:40] v4[55:48] v4[63:56]
+    ipmi::SecureBuffer i = {0x02, 0x04, 0x06, 0x11, 0x22, 0x33, 0x44, 0x55,
+                            0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint8_t v1;
+    uint16_t v2;
+    uint32_t v3;
+    uint64_t v4;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v1, v2, v3, v4), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    uint8_t k1 = 0x02;
+    uint16_t k2 = 0x0604;
+    uint32_t k3 = 0x44332211;
+    uint64_t k4 = 0xccbbaa9988776655ull;
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v1, k1);
+    ASSERT_EQ(v2, k2);
+    ASSERT_EQ(v3, k3);
+    ASSERT_EQ(v4, k4);
+}
+
+TEST(UnpackAdvanced, TupleInts)
+{
+    // all elements will be unpacked in order, with each multi-byte
+    // element being processed LSByte first
+    // v1[7:0] v2[7:0] v2[15:8] v3[7:0] v3[15:8] v3[23:16] v3[31:24]
+    // v4[7:0] v4[15:8] v4[23:16] v4[31:24]
+    // v4[39:25] v4[47:40] v4[55:48] v4[63:56]
+    ipmi::SecureBuffer i = {0x02, 0x04, 0x06, 0x11, 0x22, 0x33, 0x44, 0x55,
+                            0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    std::tuple<uint8_t, uint16_t, uint32_t, uint64_t> v;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    uint8_t k1 = 0x02;
+    uint16_t k2 = 0x0604;
+    uint32_t k3 = 0x44332211;
+    uint64_t k4 = 0xccbbaa9988776655ull;
+    auto k = std::make_tuple(k1, k2, k3, k4);
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v, k);
+}
+
+TEST(UnpackAdvanced, BoolsnBitfieldsnFixedIntsOhMy)
+{
+    // each element will be unpacked, filling the low-order bits first
+    // with multi-byte values getting unpacked LSByte first
+    // v1 will use k[0][1:0]
+    // v2 will use k[0][2]
+    // v3[4:0] will use k[0][7:3], v3[6:5] will use k[1][1:0]
+    // v4 will use k[1][2]
+    // v5 will use k[1][7:3]
+    ipmi::SecureBuffer i = {0x9e, 0xdb};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint2_t v1;
+    bool v2;
+    std::bitset<7> v3;
+    bool v4;
+    uint5_t v5;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v1, v2, v3, v4, v5), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    uint2_t k1 = 2;          // binary 0b10
+    bool k2 = true;          // binary 0b1
+    std::bitset<7> k3(0x73); // binary 0b1110011
+    bool k4 = false;         // binary 0b0
+    uint5_t k5 = 27;         // binary 0b11011
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v1, k1);
+    ASSERT_EQ(v2, k2);
+    ASSERT_EQ(v3, k3);
+    ASSERT_EQ(v4, k4);
+    ASSERT_EQ(v5, k5);
+}
+
+TEST(UnpackAdvanced, UnalignedBitUnpacking)
+{
+    // unaligned multi-byte values will be unpacked the same as
+    // other bits, effectively reading from a large value, low-order
+    // bits first, then consuming the stream LSByte first
+    // v1 will use k[0][1:0]
+    // v2[5:0] will use k[0][7:2], v2[7:6] will use k[1][1:0]
+    // v3 will use k[1][2]
+    // v4[4:0] will use k[1][7:3] v4[12:5] will use k[2][7:0]
+    // v4[15:13] will use k[3][2:0]
+    // v5 will use k[3][3]
+    // v6[3:0] will use k[3][7:0] v6[11:4] will use k[4][7:0]
+    // v6[19:12] will use k[5][7:0] v6[27:20] will use k[6][7:0]
+    // v6[31:28] will use k[7][3:0]
+    // v7 will use k[7][7:4]
+    ipmi::SecureBuffer i = {0x96, 0xd2, 0x2a, 0xcd, 0xd3, 0x3b, 0xbc, 0x9d};
+    ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+    uint2_t v1;
+    uint8_t v2;
+    bool v3;
+    uint16_t v4;
+    bool v5;
+    uint32_t v6;
+    uint4_t v7;
+    // check that the number of bytes matches
+    ASSERT_EQ(p.unpack(v1, v2, v3, v4, v5, v6, v7), 0);
+    // check that the payload was fully unpacked
+    ASSERT_TRUE(p.fullyUnpacked());
+    uint2_t k1 = 2;           // binary 0b10
+    uint8_t k2 = 0xa5;        // binary 0b10100101
+    bool k3 = false;          // binary 0b0
+    uint16_t k4 = 0xa55a;     // binary 0b1010010101011010
+    bool k5 = true;           // binary 0b1
+    uint32_t k6 = 0xdbc3bd3c; // binary 0b11011011110000111011110100111100
+    uint4_t k7 = 9;           // binary 0b1001
+    // check that the bytes were correctly unpacked (LSB first)
+    ASSERT_EQ(v1, k1);
+    ASSERT_EQ(v2, k2);
+    ASSERT_EQ(v3, k3);
+    ASSERT_EQ(v4, k4);
+    ASSERT_EQ(v5, k5);
+    ASSERT_EQ(v6, k6);
+    ASSERT_EQ(v7, k7);
+}
diff --git a/test/oemrouter_unittest.cpp b/test/oemrouter_unittest.cpp
new file mode 100644
index 0000000..f0c0a01
--- /dev/null
+++ b/test/oemrouter_unittest.cpp
@@ -0,0 +1,175 @@
+#include <ipmid/api.h>
+
+#include <include/ipmid/api-types.hpp>
+#include <ipmid/oemrouter.hpp>
+
+#include <cstring>
+
+#include <gtest/gtest.h>
+
+// Watch for correct singleton behavior.
+static oem::Router* singletonUnderTest;
+
+static ipmid_callback_t wildHandler;
+
+static ipmi_netfn_t lastNetFunction;
+
+// Fake ipmi_register_callback() for this test.
+void ipmi_register_callback(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
+                            ipmi_context_t context, ipmid_callback_t cb,
+                            ipmi_cmd_privilege_t priv)
+{
+    EXPECT_EQ(NETFUN_OEM_GROUP, netfn);
+    EXPECT_EQ(ipmi::cmdWildcard, cmd);
+    EXPECT_EQ(reinterpret_cast<void*>(singletonUnderTest), context);
+    EXPECT_EQ(PRIVILEGE_OEM, priv);
+    lastNetFunction = netfn;
+    wildHandler = cb;
+}
+
+namespace oem
+{
+
+namespace
+{
+void MakeRouter()
+{
+    if (!singletonUnderTest)
+    {
+        singletonUnderTest = mutableRouter();
+    }
+    ASSERT_EQ(singletonUnderTest, mutableRouter());
+}
+
+void ActivateRouter()
+{
+    MakeRouter();
+    singletonUnderTest->activate();
+    ASSERT_EQ(NETFUN_OEM_GROUP, lastNetFunction);
+}
+
+void RegisterWithRouter(Number oen, ipmi_cmd_t cmd, Handler cb)
+{
+    ActivateRouter();
+    singletonUnderTest->registerHandler(oen, cmd, cb);
+}
+
+uint8_t msgPlain[] = {0x56, 0x34, 0x12};
+uint8_t replyPlain[] = {0x56, 0x34, 0x12, 0x31, 0x41};
+uint8_t msgPlus2[] = {0x67, 0x45, 0x23, 0x10, 0x20};
+uint8_t msgBadOen[] = {0x57, 0x34, 0x12};
+
+void RegisterTwoWays(ipmi_cmd_t* nextCmd)
+{
+    Handler f = [](ipmi_cmd_t cmd, [[maybe_unused]] const uint8_t* reqBuf,
+                   uint8_t* replyBuf, size_t* dataLen) {
+        // Check inputs
+        EXPECT_EQ(0x78, cmd);
+        EXPECT_EQ(0, *dataLen); // Excludes OEN
+
+        // Generate reply.
+        *dataLen = 2;
+        std::memcpy(replyBuf, replyPlain + 3, *dataLen);
+        return 0;
+    };
+    RegisterWithRouter(0x123456, 0x78, f);
+
+    *nextCmd = ipmi::cmdWildcard;
+    Handler g = [nextCmd](ipmi_cmd_t cmd, const uint8_t* reqBuf,
+                          [[maybe_unused]] uint8_t* replyBuf, size_t* dataLen) {
+        // Check inputs
+        EXPECT_EQ(*nextCmd, cmd);
+        EXPECT_EQ(2, *dataLen); // Excludes OEN
+        if (2 != *dataLen)
+        {
+            return 0xE0;
+        }
+        EXPECT_EQ(msgPlus2[3], reqBuf[0]);
+        EXPECT_EQ(msgPlus2[4], reqBuf[1]);
+
+        // Generate reply.
+        *dataLen = 0;
+        return 0;
+    };
+    RegisterWithRouter(0x234567, ipmi::cmdWildcard, g);
+}
+} // namespace
+
+TEST(OemRouterTest, MakeRouterProducesConsistentSingleton)
+{
+    MakeRouter();
+}
+
+TEST(OemRouterTest, ActivateRouterSetsLastNetToOEMGROUP)
+{
+    lastNetFunction = 0;
+    ActivateRouter();
+}
+
+TEST(OemRouterTest, VerifiesSpecificCommandMatches)
+{
+    ipmi_cmd_t cmd;
+    uint8_t reply[256];
+    size_t dataLen;
+
+    RegisterTwoWays(&cmd);
+
+    dataLen = 3;
+    EXPECT_EQ(0, wildHandler(NETFUN_OEM_GROUP, 0x78, msgPlain, reply, &dataLen,
+                             nullptr));
+    EXPECT_EQ(5, dataLen);
+    EXPECT_EQ(replyPlain[0], reply[0]);
+    EXPECT_EQ(replyPlain[1], reply[1]);
+    EXPECT_EQ(replyPlain[2], reply[2]);
+    EXPECT_EQ(replyPlain[3], reply[3]);
+    EXPECT_EQ(replyPlain[4], reply[4]);
+}
+
+TEST(OemRouterTest, WildCardMatchesTwoRandomCodes)
+{
+    ipmi_cmd_t cmd;
+    uint8_t reply[256];
+    size_t dataLen;
+
+    RegisterTwoWays(&cmd);
+
+    // Check two random command codes.
+    dataLen = 5;
+    cmd = 0x89;
+    EXPECT_EQ(0, wildHandler(NETFUN_OEM_GROUP, cmd, msgPlus2, reply, &dataLen,
+                             nullptr));
+    EXPECT_EQ(3, dataLen);
+
+    dataLen = 5;
+    cmd = 0x67;
+    EXPECT_EQ(0, wildHandler(NETFUN_OEM_GROUP, cmd, msgPlus2, reply, &dataLen,
+                             nullptr));
+    EXPECT_EQ(3, dataLen);
+}
+
+TEST(OemRouterTest, CommandsAreRejectedIfInvalid)
+{
+    ipmi_cmd_t cmd;
+    uint8_t reply[256];
+    size_t dataLen;
+
+    RegisterTwoWays(&cmd);
+
+    // Message too short to include whole OEN?
+    dataLen = 2;
+    EXPECT_EQ(IPMI_CC_REQ_DATA_LEN_INVALID,
+              wildHandler(NETFUN_OEM_GROUP, 0x78, msgPlain, reply, &dataLen,
+                          nullptr));
+
+    // Wrong specific command?
+    dataLen = 3;
+    EXPECT_EQ(IPMI_CC_INVALID, wildHandler(NETFUN_OEM_GROUP, 0x89, msgPlain,
+                                           reply, &dataLen, nullptr));
+
+    // Wrong OEN?
+    dataLen = 3;
+    EXPECT_EQ(IPMI_CC_INVALID, wildHandler(NETFUN_OEM_GROUP, 0x78, msgBadOen,
+                                           reply, &dataLen, nullptr));
+}
+
+} // namespace oem
diff --git a/test/session/closesession_unittest.cpp b/test/session/closesession_unittest.cpp
new file mode 100644
index 0000000..2b184ca
--- /dev/null
+++ b/test/session/closesession_unittest.cpp
@@ -0,0 +1,114 @@
+#include <ipmid/sessionhelper.hpp>
+
+#include <gtest/gtest.h>
+
+TEST(parseSessionInputPayloadTest, ValidObjectPath)
+{
+    uint32_t sessionId = 0;
+    uint8_t sessionHandle = 0;
+    std::string objectPath =
+        "/xyz/openbmc_project/ipmi/session/eth0/12a4567d_8a";
+
+    EXPECT_TRUE(
+        parseCloseSessionInputPayload(objectPath, sessionId, sessionHandle));
+    EXPECT_EQ(0x12a4567d, sessionId);
+    EXPECT_EQ(0x8a, sessionHandle);
+}
+
+TEST(parseSessionInputPayloadTest, InvalidObjectPath)
+{
+    uint32_t sessionId = 0;
+    uint8_t sessionHandle = 0;
+    // A valid object path will be like
+    // "/xyz/openbmc_project/ipmi/session/channel/sessionId_sessionHandle"
+    // Ex: "/xyz/openbmc_project/ipmi/session/eth0/12a4567d_8a"
+    // SessionId    : 0X12a4567d
+    // SessionHandle: 0X8a
+    std::string objectPath = "/xyz/openbmc_project/ipmi/session/eth0/12a4567d";
+
+    EXPECT_FALSE(
+        parseCloseSessionInputPayload(objectPath, sessionId, sessionHandle));
+}
+
+TEST(parseSessionInputPayloadTest, NoObjectPath)
+{
+    uint32_t sessionId = 0;
+    uint8_t sessionHandle = 0;
+    std::string objectPath;
+
+    EXPECT_FALSE(
+        parseCloseSessionInputPayload(objectPath, sessionId, sessionHandle));
+}
+
+TEST(isSessionObjectMatchedTest, ValidSessionId)
+{
+    std::string objectPath =
+        "/xyz/openbmc_project/ipmi/session/eth0/12a4567d_8a";
+    uint32_t sessionId = 0x12a4567d;
+    uint8_t sessionHandle = 0;
+
+    EXPECT_TRUE(isSessionObjectMatched(objectPath, sessionId, sessionHandle));
+}
+
+TEST(isSessionObjectMatchedTest, ValidSessionHandle)
+{
+    std::string objectPath =
+        "/xyz/openbmc_project/ipmi/session/eth0/12a4567d_8a";
+    uint32_t sessionId = 0;
+    uint8_t sessionHandle = 0x8a;
+
+    EXPECT_TRUE(isSessionObjectMatched(objectPath, sessionId, sessionHandle));
+}
+
+TEST(isSessionObjectMatchedTest, InvalidSessionId)
+{
+    std::string objectPath =
+        "/xyz/openbmc_project/ipmi/session/eth0/12a4567d_8a";
+    uint32_t sessionId = 0x1234b67d;
+    uint8_t sessionHandle = 0;
+
+    EXPECT_FALSE(isSessionObjectMatched(objectPath, sessionId, sessionHandle));
+}
+
+TEST(isSessionObjectMatchedTest, InvalidSessionHandle)
+{
+    std::string objectPath =
+        "/xyz/openbmc_project/ipmi/session/eth0/12a4567d_8a";
+    uint32_t sessionId = 0;
+    uint8_t sessionHandle = 0x9b;
+
+    EXPECT_FALSE(isSessionObjectMatched(objectPath, sessionId, sessionHandle));
+}
+
+TEST(isSessionObjectMatchedTest, ZeroSessionId_ZeroSessionHandle)
+{
+    std::string objectPath =
+        "/xyz/openbmc_project/ipmi/session/eth0/12a4567d_8a";
+    uint32_t sessionId = 0;
+    uint8_t sessionHandle = 0;
+
+    EXPECT_FALSE(isSessionObjectMatched(objectPath, sessionId, sessionHandle));
+}
+
+TEST(isSessionObjectMatchedTest, InvalidObjectPath)
+{
+    // A valid object path will be like
+    // "/xyz/openbmc_project/ipmi/session/channel/sessionId_sessionHandle"
+    // Ex: "/xyz/openbmc_project/ipmi/session/eth0/12a4567d_8a"
+    // SessionId    : 0X12a4567d
+    // SessionHandle: 0X8a
+    std::string objectPath = "/xyz/openbmc_project/ipmi/session/eth0/12a4567d";
+    uint32_t sessionId = 0x12a4567d;
+    uint8_t sessionHandle = 0;
+
+    EXPECT_FALSE(isSessionObjectMatched(objectPath, sessionId, sessionHandle));
+}
+
+TEST(isSessionObjectMatchedTest, NoObjectPath)
+{
+    std::string objectPath;
+    uint32_t sessionId = 0x12a4567d;
+    uint8_t sessionHandle = 0x8a;
+
+    EXPECT_FALSE(isSessionObjectMatched(objectPath, sessionId, sessionHandle));
+}
diff --git a/transport/meson.build b/transport/meson.build
new file mode 100644
index 0000000..9e5a241
--- /dev/null
+++ b/transport/meson.build
@@ -0,0 +1,3 @@
+if get_option('transport-implementation') == 'serial'
+    subdir('serialbridge')
+endif
diff --git a/transport/rmcpbridge/.clang-format b/transport/rmcpbridge/.clang-format
new file mode 100644
index 0000000..e5530e6
--- /dev/null
+++ b/transport/rmcpbridge/.clang-format
@@ -0,0 +1,136 @@
+---
+Language:        Cpp
+# BasedOnStyle:  LLVM
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Right
+AlignOperands:  Align
+AlignTrailingComments:
+  Kind: Always
+  OverEmptyLines: 1
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: Empty
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Empty
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLambdasOnASingleLine: true
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakBeforeMultilineStrings: false
+BinPackArguments: true
+BinPackParameters: true
+BitFieldColonSpacing: None
+BraceWrapping:
+  AfterCaseLabel:  true
+  AfterClass:      true
+  AfterControlStatement: true
+  AfterEnum:       true
+  AfterExternBlock: true
+  AfterFunction:   true
+  AfterNamespace:  true
+  AfterObjCDeclaration: true
+  AfterStruct:     true
+  AfterUnion:      true
+  BeforeCatch:     true
+  BeforeElse:      true
+  BeforeLambdaBody: false
+  BeforeWhile:     false
+  IndentBraces:    false
+  SplitEmptyFunction:   false
+  SplitEmptyRecord:     false
+  SplitEmptyNamespace:  false
+BreakAfterAttributes: Never
+BreakAfterReturnType: Automatic
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: AfterColon
+BreakInheritanceList: AfterColon
+BreakStringLiterals: false
+BreakTemplateDeclarations: Yes
+ColumnLimit:     80
+CommentPragmas:  '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+DisableFormat:   false
+FixNamespaceComments: true
+ForEachMacros:
+  - foreach
+  - Q_FOREACH
+  - BOOST_FOREACH
+IncludeBlocks: Regroup
+IncludeCategories:
+  - Regex:           '^[<"](gtest|gmock)'
+    Priority:        7
+  - Regex:           '^"config.h"'
+    Priority:        -1
+  - Regex:           '^".*\.h"'
+    Priority:        1
+  - Regex:           '^".*\.hpp"'
+    Priority:        2
+  - Regex:           '^<.*\.h>'
+    Priority:        3
+  - Regex:           '^<.*\.hpp>'
+    Priority:        4
+  - Regex:           '^<.*'
+    Priority:        5
+  - Regex:           '.*'
+    Priority:        6
+IndentCaseLabels: true
+IndentExternBlock: NoIndent
+IndentRequiresClause: true
+IndentWidth:     4
+IndentWrappedFunctionNames: true
+InsertNewlineAtEOF: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+LambdaBodyIndentation: Signature
+LineEnding: LF
+MacroBlockBegin: ''
+MacroBlockEnd:   ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PackConstructorInitializers: BinPack
+PenaltyBreakAssignment: 25
+PenaltyBreakBeforeFirstCallParameter: 50
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 150
+PenaltyIndentedWhitespace: 1
+PointerAlignment: Left
+QualifierAlignment: Left
+ReferenceAlignment: Left
+ReflowComments:  true
+RequiresClausePosition: OwnLine
+RequiresExpressionIndentation: Keyword
+SortIncludes: CaseSensitive
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: Never
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard:        Latest
+TabWidth:        4
+UseTab:          Never
+...
+
diff --git a/transport/rmcpbridge/.clang-tidy b/transport/rmcpbridge/.clang-tidy
new file mode 100644
index 0000000..4b6eca0
--- /dev/null
+++ b/transport/rmcpbridge/.clang-tidy
@@ -0,0 +1,7 @@
+Checks: '
+    -*,
+    bugprone-unchecked-optional-access,
+    readability-identifier-naming
+'
+WarningsAsErrors: '*'
+HeaderFilterRegex: '(?!^subprojects).*'
diff --git a/transport/rmcpbridge/.gitignore b/transport/rmcpbridge/.gitignore
new file mode 100644
index 0000000..51ef08e
--- /dev/null
+++ b/transport/rmcpbridge/.gitignore
@@ -0,0 +1,3 @@
+/build*/
+/subprojects/*
+!subprojects/*.wrap
diff --git a/transport/rmcpbridge/LICENSE b/transport/rmcpbridge/LICENSE
new file mode 100644
index 0000000..8dada3e
--- /dev/null
+++ b/transport/rmcpbridge/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   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.
diff --git a/transport/rmcpbridge/OWNERS b/transport/rmcpbridge/OWNERS
new file mode 100644
index 0000000..05f2bbe
--- /dev/null
+++ b/transport/rmcpbridge/OWNERS
@@ -0,0 +1,48 @@
+# OWNERS
+# ------
+#
+# The OWNERS file maintains the list of individuals responsible for various
+# parts of this repository, including code review and approval.  We use the
+# Gerrit 'owners' plugin, which consumes this file, along with some extra
+# keywords for our own purposes and tooling.
+#
+# For details on the configuration used by 'owners' see:
+#  https://gerrit.googlesource.com/plugins/owners/+/refs/heads/master/owners/src/main/resources/Documentation/config.md
+#
+# An OWNERS file must be in the root of a repository but may also be present
+# in any subdirectory.  The contents of the subdirectory OWNERS file are
+# combined with parent directories unless 'inherit: false' is set.
+#
+# The owners file is YAML and has [up to] 4 top-level keywords.
+#   * owners: A list of individuals who have approval authority on the
+#     repository.
+#
+#   * reviewers: A list of individuals who have requested review notification
+#     on the repository.
+#
+#   * matchers: A list of specific file/path matchers for granular 'owners' and
+#     'reviewers'.  See 'owners' plugin documentation.
+#
+#   * openbmc: A list of openbmc-specific meta-data about owners and reviewers.
+#     - name: preferred name of the individual.
+#     - email: preferred email address of the individual.
+#     - discord: Discord nickname of the individual.
+#
+# It is expected that these 4 sections will be listed in the order above and
+# data within them will be kept sorted.
+
+owners:
+- rushtotom@gmail.com
+- vernon.mauery@gmail.com
+
+reviewers:
+
+matchers:
+
+openbmc:
+- name: Tom Joseph
+  email: rushtotom@gmail.com
+  discord: tomjose
+- name: Vernon Mauery
+  email: vernon.mauery@gmail.com
+  discord: vmauery
diff --git a/transport/rmcpbridge/README.md b/transport/rmcpbridge/README.md
new file mode 100644
index 0000000..89aba20
--- /dev/null
+++ b/transport/rmcpbridge/README.md
@@ -0,0 +1,13 @@
+# phosphor-net-ipmid
+
+## To Build
+
+To build this package, do the following steps:
+
+```sh
+1. ./bootstrap.sh
+2. ./configure ${CONFIGURE_FLAGS}
+3. make
+```
+
+To clean the repository run `./bootstrap.sh clean`.
diff --git a/auth_algo.cpp b/transport/rmcpbridge/auth_algo.cpp
similarity index 100%
rename from auth_algo.cpp
rename to transport/rmcpbridge/auth_algo.cpp
diff --git a/auth_algo.hpp b/transport/rmcpbridge/auth_algo.hpp
similarity index 100%
rename from auth_algo.hpp
rename to transport/rmcpbridge/auth_algo.hpp
diff --git a/comm_module.cpp b/transport/rmcpbridge/comm_module.cpp
similarity index 100%
rename from comm_module.cpp
rename to transport/rmcpbridge/comm_module.cpp
diff --git a/comm_module.hpp b/transport/rmcpbridge/comm_module.hpp
similarity index 100%
rename from comm_module.hpp
rename to transport/rmcpbridge/comm_module.hpp
diff --git a/command/channel_auth.cpp b/transport/rmcpbridge/command/channel_auth.cpp
similarity index 100%
rename from command/channel_auth.cpp
rename to transport/rmcpbridge/command/channel_auth.cpp
diff --git a/command/channel_auth.hpp b/transport/rmcpbridge/command/channel_auth.hpp
similarity index 100%
rename from command/channel_auth.hpp
rename to transport/rmcpbridge/command/channel_auth.hpp
diff --git a/command/guid.cpp b/transport/rmcpbridge/command/guid.cpp
similarity index 100%
rename from command/guid.cpp
rename to transport/rmcpbridge/command/guid.cpp
diff --git a/command/guid.hpp b/transport/rmcpbridge/command/guid.hpp
similarity index 100%
rename from command/guid.hpp
rename to transport/rmcpbridge/command/guid.hpp
diff --git a/command/open_session.cpp b/transport/rmcpbridge/command/open_session.cpp
similarity index 100%
rename from command/open_session.cpp
rename to transport/rmcpbridge/command/open_session.cpp
diff --git a/command/open_session.hpp b/transport/rmcpbridge/command/open_session.hpp
similarity index 100%
rename from command/open_session.hpp
rename to transport/rmcpbridge/command/open_session.hpp
diff --git a/command/payload_cmds.cpp b/transport/rmcpbridge/command/payload_cmds.cpp
similarity index 100%
rename from command/payload_cmds.cpp
rename to transport/rmcpbridge/command/payload_cmds.cpp
diff --git a/command/payload_cmds.hpp b/transport/rmcpbridge/command/payload_cmds.hpp
similarity index 100%
rename from command/payload_cmds.hpp
rename to transport/rmcpbridge/command/payload_cmds.hpp
diff --git a/command/rakp12.cpp b/transport/rmcpbridge/command/rakp12.cpp
similarity index 100%
rename from command/rakp12.cpp
rename to transport/rmcpbridge/command/rakp12.cpp
diff --git a/command/rakp12.hpp b/transport/rmcpbridge/command/rakp12.hpp
similarity index 100%
rename from command/rakp12.hpp
rename to transport/rmcpbridge/command/rakp12.hpp
diff --git a/command/rakp34.cpp b/transport/rmcpbridge/command/rakp34.cpp
similarity index 100%
rename from command/rakp34.cpp
rename to transport/rmcpbridge/command/rakp34.cpp
diff --git a/command/rakp34.hpp b/transport/rmcpbridge/command/rakp34.hpp
similarity index 100%
rename from command/rakp34.hpp
rename to transport/rmcpbridge/command/rakp34.hpp
diff --git a/command/session_cmds.cpp b/transport/rmcpbridge/command/session_cmds.cpp
similarity index 100%
rename from command/session_cmds.cpp
rename to transport/rmcpbridge/command/session_cmds.cpp
diff --git a/command/session_cmds.hpp b/transport/rmcpbridge/command/session_cmds.hpp
similarity index 100%
rename from command/session_cmds.hpp
rename to transport/rmcpbridge/command/session_cmds.hpp
diff --git a/command/sol_cmds.cpp b/transport/rmcpbridge/command/sol_cmds.cpp
similarity index 100%
rename from command/sol_cmds.cpp
rename to transport/rmcpbridge/command/sol_cmds.cpp
diff --git a/command/sol_cmds.hpp b/transport/rmcpbridge/command/sol_cmds.hpp
similarity index 100%
rename from command/sol_cmds.hpp
rename to transport/rmcpbridge/command/sol_cmds.hpp
diff --git a/command_table.cpp b/transport/rmcpbridge/command_table.cpp
similarity index 100%
rename from command_table.cpp
rename to transport/rmcpbridge/command_table.cpp
diff --git a/command_table.hpp b/transport/rmcpbridge/command_table.hpp
similarity index 100%
rename from command_table.hpp
rename to transport/rmcpbridge/command_table.hpp
diff --git a/crypt_algo.cpp b/transport/rmcpbridge/crypt_algo.cpp
similarity index 100%
rename from crypt_algo.cpp
rename to transport/rmcpbridge/crypt_algo.cpp
diff --git a/crypt_algo.hpp b/transport/rmcpbridge/crypt_algo.hpp
similarity index 100%
rename from crypt_algo.hpp
rename to transport/rmcpbridge/crypt_algo.hpp
diff --git a/endian.hpp b/transport/rmcpbridge/endian.hpp
similarity index 100%
rename from endian.hpp
rename to transport/rmcpbridge/endian.hpp
diff --git a/integrity_algo.cpp b/transport/rmcpbridge/integrity_algo.cpp
similarity index 100%
rename from integrity_algo.cpp
rename to transport/rmcpbridge/integrity_algo.cpp
diff --git a/integrity_algo.hpp b/transport/rmcpbridge/integrity_algo.hpp
similarity index 100%
rename from integrity_algo.hpp
rename to transport/rmcpbridge/integrity_algo.hpp
diff --git a/main.cpp b/transport/rmcpbridge/main.cpp
similarity index 100%
rename from main.cpp
rename to transport/rmcpbridge/main.cpp
diff --git a/main.hpp b/transport/rmcpbridge/main.hpp
similarity index 100%
rename from main.hpp
rename to transport/rmcpbridge/main.hpp
diff --git a/transport/rmcpbridge/meson.build b/transport/rmcpbridge/meson.build
new file mode 100644
index 0000000..0e449d4
--- /dev/null
+++ b/transport/rmcpbridge/meson.build
@@ -0,0 +1,120 @@
+project(
+    'phosphor-net-ipmid',
+    'cpp',
+    version: '1.0.0',
+    meson_version: '>=1.1.1',
+    default_options: [
+        'warning_level=3',
+        'werror=true',
+        'cpp_std=c++23',
+        'buildtype=debugoptimized',
+        'b_lto=true',
+    ],
+)
+
+conf_data = configuration_data()
+conf_data.set('RMCP_PING', get_option('rmcp_ping').allowed())
+conf_data.set('PAM_AUTHENTICATE', get_option('pam_authenticate').allowed())
+
+configure_file(output: 'config.h', configuration: conf_data)
+
+sdbusplus_dep = dependency('sdbusplus')
+phosphor_dbus_interfaces_dep = dependency('phosphor-dbus-interfaces')
+phosphor_logging_dep = dependency('phosphor-logging')
+libsystemd_dep = dependency('libsystemd')
+libcrypto_dep = dependency('libcrypto')
+ipmid_dep = dependency('libipmid')
+userlayer_dep = dependency('libuserlayer')
+channellayer_dep = dependency('libchannellayer')
+
+# Project Arguments
+cpp = meson.get_compiler('cpp')
+if cpp.has_header('CLI/CLI.hpp')
+    cli11_dep = declare_dependency()
+else
+    cli11_dep = dependency('CLI11')
+endif
+
+add_project_arguments(
+    cpp.get_supported_arguments(
+        [
+            '-DBOOST_ERROR_CODE_HEADER_ONLY',
+            '-DBOOST_SYSTEM_NO_DEPRECATED',
+            '-DBOOST_COROUTINES_NO_DEPRECATION_WARNING',
+            '-DBOOST_ASIO_DISABLE_THREADS',
+            '-DBOOST_ALL_NO_LIB',
+        ],
+    ),
+    language: 'cpp',
+)
+
+deps = [
+    cli11_dep,
+    ipmid_dep,
+    userlayer_dep,
+    channellayer_dep,
+    libcrypto_dep,
+    libsystemd_dep,
+    phosphor_dbus_interfaces_dep,
+    phosphor_logging_dep,
+    sdbusplus_dep,
+]
+
+sources = [
+    'auth_algo.cpp',
+    'sessions_manager.cpp',
+    'message_parsers.cpp',
+    'message_handler.cpp',
+    'command_table.cpp',
+    'command/channel_auth.cpp',
+    'command/guid.cpp',
+    'command/open_session.cpp',
+    'command/rakp12.cpp',
+    'command/rakp34.cpp',
+    'command/session_cmds.cpp',
+    'comm_module.cpp',
+    'main.cpp',
+    'integrity_algo.cpp',
+    'crypt_algo.cpp',
+    'sd_event_loop.cpp',
+    'sol/sol_manager.cpp',
+    'sol/sol_context.cpp',
+    'command/sol_cmds.cpp',
+    'command/payload_cmds.cpp',
+    'sol_module.cpp',
+]
+
+executable(
+    'netipmid',
+    sources,
+    implicit_include_directories: true,
+    include_directories: ['command', 'sol'],
+    dependencies: deps,
+    install: true,
+    install_dir: get_option('bindir'),
+)
+
+systemd = dependency('systemd')
+systemd_system_unit_dir = systemd.get_variable(
+    'systemdsystemunitdir',
+    pkgconfig_define: ['prefix', get_option('prefix')],
+)
+
+configure_file(
+    input: 'phosphor-ipmi-net@.service',
+    output: 'phosphor-ipmi-net@.service',
+    copy: true,
+    install_dir: systemd_system_unit_dir,
+)
+
+configure_file(
+    input: 'phosphor-ipmi-net@.socket',
+    output: 'phosphor-ipmi-net@.socket',
+    copy: true,
+    install_dir: systemd_system_unit_dir,
+)
+
+build_tests = get_option('tests')
+if build_tests.allowed()
+    subdir('test')
+endif
diff --git a/transport/rmcpbridge/meson.options b/transport/rmcpbridge/meson.options
new file mode 100644
index 0000000..6f9cbaa
--- /dev/null
+++ b/transport/rmcpbridge/meson.options
@@ -0,0 +1,15 @@
+option('tests', type: 'feature', value: 'enabled', description: 'Build tests')
+
+option(
+    'rmcp_ping',
+    type: 'feature',
+    value: 'enabled',
+    description: 'Enable RMCP Ping support',
+)
+
+option(
+    'pam_authenticate',
+    type: 'feature',
+    value: 'enabled',
+    description: 'Enable Pam Authenticate',
+)
diff --git a/message.hpp b/transport/rmcpbridge/message.hpp
similarity index 100%
rename from message.hpp
rename to transport/rmcpbridge/message.hpp
diff --git a/message_handler.cpp b/transport/rmcpbridge/message_handler.cpp
similarity index 100%
rename from message_handler.cpp
rename to transport/rmcpbridge/message_handler.cpp
diff --git a/message_handler.hpp b/transport/rmcpbridge/message_handler.hpp
similarity index 100%
rename from message_handler.hpp
rename to transport/rmcpbridge/message_handler.hpp
diff --git a/message_parsers.cpp b/transport/rmcpbridge/message_parsers.cpp
similarity index 100%
rename from message_parsers.cpp
rename to transport/rmcpbridge/message_parsers.cpp
diff --git a/message_parsers.hpp b/transport/rmcpbridge/message_parsers.hpp
similarity index 100%
rename from message_parsers.hpp
rename to transport/rmcpbridge/message_parsers.hpp
diff --git a/phosphor-ipmi-net@.service b/transport/rmcpbridge/phosphor-ipmi-net@.service
similarity index 100%
rename from phosphor-ipmi-net@.service
rename to transport/rmcpbridge/phosphor-ipmi-net@.service
diff --git a/phosphor-ipmi-net@.socket b/transport/rmcpbridge/phosphor-ipmi-net@.socket
similarity index 100%
rename from phosphor-ipmi-net@.socket
rename to transport/rmcpbridge/phosphor-ipmi-net@.socket
diff --git a/prng.hpp b/transport/rmcpbridge/prng.hpp
similarity index 100%
rename from prng.hpp
rename to transport/rmcpbridge/prng.hpp
diff --git a/rmcp.hpp b/transport/rmcpbridge/rmcp.hpp
similarity index 100%
rename from rmcp.hpp
rename to transport/rmcpbridge/rmcp.hpp
diff --git a/sd_event_loop.cpp b/transport/rmcpbridge/sd_event_loop.cpp
similarity index 100%
rename from sd_event_loop.cpp
rename to transport/rmcpbridge/sd_event_loop.cpp
diff --git a/sd_event_loop.hpp b/transport/rmcpbridge/sd_event_loop.hpp
similarity index 100%
rename from sd_event_loop.hpp
rename to transport/rmcpbridge/sd_event_loop.hpp
diff --git a/session.hpp b/transport/rmcpbridge/session.hpp
similarity index 100%
rename from session.hpp
rename to transport/rmcpbridge/session.hpp
diff --git a/sessions_manager.cpp b/transport/rmcpbridge/sessions_manager.cpp
similarity index 100%
rename from sessions_manager.cpp
rename to transport/rmcpbridge/sessions_manager.cpp
diff --git a/sessions_manager.hpp b/transport/rmcpbridge/sessions_manager.hpp
similarity index 100%
rename from sessions_manager.hpp
rename to transport/rmcpbridge/sessions_manager.hpp
diff --git a/socket_channel.hpp b/transport/rmcpbridge/socket_channel.hpp
similarity index 100%
rename from socket_channel.hpp
rename to transport/rmcpbridge/socket_channel.hpp
diff --git a/sol/console_buffer.hpp b/transport/rmcpbridge/sol/console_buffer.hpp
similarity index 100%
rename from sol/console_buffer.hpp
rename to transport/rmcpbridge/sol/console_buffer.hpp
diff --git a/sol/sol_context.cpp b/transport/rmcpbridge/sol/sol_context.cpp
similarity index 100%
rename from sol/sol_context.cpp
rename to transport/rmcpbridge/sol/sol_context.cpp
diff --git a/sol/sol_context.hpp b/transport/rmcpbridge/sol/sol_context.hpp
similarity index 100%
rename from sol/sol_context.hpp
rename to transport/rmcpbridge/sol/sol_context.hpp
diff --git a/sol/sol_manager.cpp b/transport/rmcpbridge/sol/sol_manager.cpp
similarity index 100%
rename from sol/sol_manager.cpp
rename to transport/rmcpbridge/sol/sol_manager.cpp
diff --git a/sol/sol_manager.hpp b/transport/rmcpbridge/sol/sol_manager.hpp
similarity index 100%
rename from sol/sol_manager.hpp
rename to transport/rmcpbridge/sol/sol_manager.hpp
diff --git a/sol_module.cpp b/transport/rmcpbridge/sol_module.cpp
similarity index 100%
rename from sol_module.cpp
rename to transport/rmcpbridge/sol_module.cpp
diff --git a/sol_module.hpp b/transport/rmcpbridge/sol_module.hpp
similarity index 100%
rename from sol_module.hpp
rename to transport/rmcpbridge/sol_module.hpp
diff --git a/subprojects/CLI11.wrap b/transport/rmcpbridge/subprojects/CLI11.wrap
similarity index 100%
rename from subprojects/CLI11.wrap
rename to transport/rmcpbridge/subprojects/CLI11.wrap
diff --git a/subprojects/googletest.wrap b/transport/rmcpbridge/subprojects/googletest.wrap
similarity index 100%
rename from subprojects/googletest.wrap
rename to transport/rmcpbridge/subprojects/googletest.wrap
diff --git a/transport/rmcpbridge/subprojects/phosphor-dbus-interfaces.wrap b/transport/rmcpbridge/subprojects/phosphor-dbus-interfaces.wrap
new file mode 100644
index 0000000..346aa0c
--- /dev/null
+++ b/transport/rmcpbridge/subprojects/phosphor-dbus-interfaces.wrap
@@ -0,0 +1,6 @@
+[wrap-git]
+url = https://github.com/openbmc/phosphor-dbus-interfaces.git
+revision = HEAD
+
+[provide]
+phosphor-dbus-interfaces = phosphor_dbus_interfaces_dep
diff --git a/subprojects/phosphor-host-ipmid.wrap b/transport/rmcpbridge/subprojects/phosphor-host-ipmid.wrap
similarity index 100%
rename from subprojects/phosphor-host-ipmid.wrap
rename to transport/rmcpbridge/subprojects/phosphor-host-ipmid.wrap
diff --git a/transport/rmcpbridge/subprojects/phosphor-logging.wrap b/transport/rmcpbridge/subprojects/phosphor-logging.wrap
new file mode 100644
index 0000000..71eee8b
--- /dev/null
+++ b/transport/rmcpbridge/subprojects/phosphor-logging.wrap
@@ -0,0 +1,6 @@
+[wrap-git]
+url = https://github.com/openbmc/phosphor-logging.git
+revision = HEAD
+
+[provide]
+phosphor-logging = phosphor_logging_dep
diff --git a/transport/rmcpbridge/subprojects/sdbusplus.wrap b/transport/rmcpbridge/subprojects/sdbusplus.wrap
new file mode 100644
index 0000000..7b076d0
--- /dev/null
+++ b/transport/rmcpbridge/subprojects/sdbusplus.wrap
@@ -0,0 +1,6 @@
+[wrap-git]
+url = https://github.com/openbmc/sdbusplus.git
+revision = HEAD
+
+[provide]
+sdbusplus = sdbusplus_dep
diff --git a/test/cipher.cpp b/transport/rmcpbridge/test/cipher.cpp
similarity index 100%
rename from test/cipher.cpp
rename to transport/rmcpbridge/test/cipher.cpp
diff --git a/transport/rmcpbridge/test/meson.build b/transport/rmcpbridge/test/meson.build
new file mode 100644
index 0000000..6fcfd04
--- /dev/null
+++ b/transport/rmcpbridge/test/meson.build
@@ -0,0 +1,38 @@
+gtest_dep = dependency('gtest', main: true, disabler: true, required: false)
+gmock_dep = dependency('gmock', disabler: true, required: false)
+if not gtest_dep.found() or not gmock_dep.found()
+    gtest_proj = import('cmake').subproject('googletest', required: false)
+    if gtest_proj.found()
+        gtest_dep = declare_dependency(
+            dependencies: [
+                dependency('threads'),
+                gtest_proj.dependency('gtest'),
+                gtest_proj.dependency('gtest_main'),
+            ],
+        )
+        gmock_dep = gtest_proj.dependency('gmock')
+    else
+        assert(
+            not get_option('tests').enabled(),
+            'Googletest is required if tests are enabled',
+        )
+    endif
+endif
+
+test_sources = ['../integrity_algo.cpp', '../crypt_algo.cpp']
+
+tests = ['cipher.cpp']
+
+foreach t : tests
+    test(
+        t,
+        executable(
+            t.underscorify(),
+            t,
+            test_sources,
+            include_directories: ['..'],
+            dependencies: [gtest_dep, gmock_dep, libcrypto_dep],
+        ),
+        workdir: meson.current_source_dir(),
+    )
+endforeach
diff --git a/transport/serialbridge/meson.build b/transport/serialbridge/meson.build
new file mode 100644
index 0000000..6919c75
--- /dev/null
+++ b/transport/serialbridge/meson.build
@@ -0,0 +1,41 @@
+CLI11_dep = dependency('CLI11')
+
+deps = [
+    dependency('libsystemd'),
+    dependency('systemd'),
+    sdeventplus_dep,
+    stdplus_dep,
+    sdbusplus_dep,
+    phosphor_logging_dep,
+    CLI11_dep,
+]
+
+serialbridged = executable(
+    'serialbridged',
+    'serialbridged.cpp',
+    'serialcmd.cpp',
+    dependencies: deps,
+    install: true,
+    install_dir: get_option('libexecdir'),
+)
+
+# Configure and install systemd unit files
+systemd = dependency('systemd')
+if systemd.found()
+    conf_data = configuration_data()
+    conf_data.set(
+        'BIN',
+        get_option('prefix') / get_option('libexecdir') / serialbridged.name(),
+    )
+    configure_file(
+        input: 'serialbridge@.service.in',
+        output: 'serialbridge@.service',
+        configuration: conf_data,
+        install: true,
+        install_dir: systemd.get_variable(pkgconfig: 'systemdsystemunitdir'),
+    )
+endif
+
+if not get_option('tests').disabled()
+    subdir('test')
+endif
diff --git a/transport/serialbridge/serialbridge@.service.in b/transport/serialbridge/serialbridge@.service.in
new file mode 100644
index 0000000..a7bfd45
--- /dev/null
+++ b/transport/serialbridge/serialbridge@.service.in
@@ -0,0 +1,18 @@
+[Unit]
+Description=Phosphor IPMI Serial DBus Bridge
+StartLimitBurst=3
+StartLimitIntervalSec=300
+After=phosphor-ipmi-host.service
+
+[Service]
+Restart=always
+RestartSec=10
+TimeoutStartSec=60
+TimeoutStopSec=60
+ExecStartPre=/bin/stty -F /dev/"%i" 115200 litout -crtscts -ixon -echo raw
+ExecStart=@BIN@ -d "%i"
+SyslogIdentifier=serialbridged-%i
+
+[Install]
+WantedBy=multi-user.target
+RequiredBy=
diff --git a/transport/serialbridge/serialbridged.cpp b/transport/serialbridge/serialbridged.cpp
new file mode 100644
index 0000000..b022dc6
--- /dev/null
+++ b/transport/serialbridge/serialbridged.cpp
@@ -0,0 +1,85 @@
+#include "serialcmd.hpp"
+
+#include <systemd/sd-daemon.h>
+
+#include <CLI/CLI.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/slot.hpp>
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/source/io.hpp>
+#include <sdeventplus/source/signal.hpp>
+#include <stdplus/exception.hpp>
+#include <stdplus/fd/create.hpp>
+#include <stdplus/fd/ops.hpp>
+#include <stdplus/signal.hpp>
+
+namespace serialbridge
+{
+
+using sdeventplus::source::IO;
+using sdeventplus::source::Signal;
+using stdplus::fd::OpenAccess;
+using stdplus::fd::OpenFlag;
+using stdplus::fd::OpenFlags;
+
+int execute(const std::string& channel, const bool& verbose)
+{
+    // Set up DBus and event loop
+    auto event = sdeventplus::Event::get_default();
+    auto bus = sdbusplus::bus::new_default();
+    bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
+
+    // Configure basic signal handling
+    auto exit_handler = [&event](Signal&, const struct signalfd_siginfo*) {
+        lg2::error("Interrupted, Exiting\n");
+        event.exit(0);
+    };
+    stdplus::signal::block(SIGINT);
+    Signal sig_init(event, SIGINT, exit_handler);
+    stdplus::signal::block(SIGTERM);
+    Signal sig_term(event, SIGTERM, exit_handler);
+
+    // Open an FD for the UART channel
+    stdplus::ManagedFd uart = stdplus::fd::open(
+        std::format("/dev/{}", channel.c_str()),
+        OpenFlags(OpenAccess::ReadWrite).set(OpenFlag::NonBlock));
+    sdbusplus::slot_t slot(nullptr);
+
+    std::unique_ptr<SerialChannel> serialchannel =
+        std::make_unique<SerialChannel>(verbose);
+
+    // Add a reader to the bus for handling inbound IPMI
+    IO ioSource(event, uart.get(), EPOLLIN | EPOLLET,
+                stdplus::exception::ignore(
+                    [&serialchannel, &uart, &bus, &slot](IO&, int, uint32_t) {
+                        serialchannel->read(uart, bus, slot);
+                    }));
+
+    sd_notify(0, "READY=1");
+    return event.loop();
+}
+
+} // namespace serialbridge
+
+int main(int argc, char* argv[])
+{
+    std::string device;
+    bool verbose = 0;
+
+    // Parse input parameter
+    CLI::App app("Serial IPMI Bridge");
+    app.add_option("-d,--device", device, "select uart device");
+    app.add_option("-v,--verbose", verbose, "enable debug message");
+    CLI11_PARSE(app, argc, argv);
+
+    try
+    {
+        return serialbridge::execute(device, verbose);
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("FAILED: {MSG}\n", "MSG", e);
+        return 1;
+    }
+}
diff --git a/transport/serialbridge/serialcmd.cpp b/transport/serialbridge/serialcmd.cpp
new file mode 100644
index 0000000..ec84f5c
--- /dev/null
+++ b/transport/serialbridge/serialcmd.cpp
@@ -0,0 +1,340 @@
+#include "serialcmd.hpp"
+
+#include <fmt/format.h>
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/exception.hpp>
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/slot.hpp>
+#include <stdplus/exception.hpp>
+#include <stdplus/fd/ops.hpp>
+
+#include <numeric>
+#include <ranges>
+#include <unordered_map>
+
+namespace serialbridge
+{
+
+/**
+ * @brief Table of special characters
+ */
+static const std::unordered_map<uint8_t, uint8_t> characters = {
+    {bmStart, 0xB0},     /* start */
+    {bmStop, 0xB5},      /* stop */
+    {bmHandshake, 0xB6}, /* packet handshake */
+    {bmEscape, 0xBA},    /* data escape */
+    {0x1B, 0x3B}         /* escape */
+};
+
+/**
+ * @brief Calculate IPMI checksum
+ */
+uint8_t SerialChannel::calculateChecksum(std::span<uint8_t> data)
+{
+    uint8_t checksum;
+
+    checksum = std::accumulate(data.begin(), data.end(), 0);
+    checksum = (~checksum) + 1;
+
+    // return checksum
+    return checksum;
+}
+
+/**
+ * @brief Return unescaped character for the given one
+ */
+uint8_t SerialChannel::getUnescapedCharacter(uint8_t c)
+{
+    auto search =
+        std::find_if(characters.begin(), characters.end(),
+                     [c](const auto& map_set) { return map_set.second == c; });
+
+    if (search == characters.end())
+    {
+        return c;
+    }
+
+    return search->first;
+}
+
+/**
+ * @brief Process IPMI Serial Request State Machine
+ */
+int SerialChannel::consumeIpmiSerialPacket(
+    std::span<uint8_t>& escapedDataBytes,
+    std::vector<uint8_t>& unescapedDataBytes)
+{
+    unescapedDataBytes.reserve(escapedDataBytes.size());
+
+    for (auto c : escapedDataBytes)
+    {
+        if (c == bmStart) // START
+        {
+            msgState = MsgState::msgInProgress;
+        }
+        else if (msgState == MsgState::msgIdle)
+        {
+            continue;
+        }
+        else if (msgState == MsgState::msgInEscape)
+        {
+            uint8_t unescapedCharacter;
+            unescapedCharacter = getUnescapedCharacter(c);
+
+            if (unescapedCharacter == c)
+            {
+                // error, then reset
+                msgState = MsgState::msgIdle;
+                unescapedDataBytes.clear();
+                continue;
+            }
+
+            unescapedDataBytes.push_back(unescapedCharacter);
+            msgState = MsgState::msgInProgress;
+        }
+        else if (c == bmEscape)
+        {
+            msgState = MsgState::msgInEscape;
+            continue;
+        }
+        else if (c == bmStop) // STOP
+        {
+            msgState = MsgState::msgIdle;
+            return true;
+        }
+        else if (c == bmHandshake) // Handshake
+        {
+            unescapedDataBytes.clear();
+            continue;
+        }
+        else if (msgState == MsgState::msgInProgress)
+        {
+            unescapedDataBytes.push_back(c);
+        }
+    }
+
+    return false;
+}
+
+/**
+ * @brief Encapsluate response to avoid escape character
+ */
+uint8_t SerialChannel::processEscapedCharacter(std::vector<uint8_t>& buffer,
+                                               const std::vector<uint8_t>& data)
+{
+    uint8_t checksum = 0;
+
+    std::ranges::for_each(data.begin(), data.end(),
+                          [&buffer, &checksum](const auto& c) {
+                              auto search = characters.find(c);
+                              if (search != characters.end())
+                              {
+                                  buffer.push_back(bmEscape);
+                                  buffer.push_back(search->second);
+                              }
+                              else
+                              {
+                                  buffer.push_back(c);
+                              }
+
+                              checksum += c;
+                          });
+
+    return checksum;
+}
+
+/**
+ * @brief Write function
+ */
+int SerialChannel::write(stdplus::Fd& uart, uint8_t rsAddr, uint8_t rqAddr,
+                         uint8_t seq, sdbusplus::message_t&& m)
+{
+    std::span<uint8_t> out;
+    uint8_t checksum;
+
+    try
+    {
+        if (m.is_method_error())
+        {
+            // Extra copy to workaround lack of `const sd_bus_error` constructor
+            auto error = *m.get_error();
+            throw sdbusplus::exception::SdBusError(&error, "ipmid response");
+        }
+
+        uint8_t netFn = 0xff;
+        uint8_t lun = 0xff;
+        uint8_t cmd = 0xff;
+        uint8_t cc = 0xff;
+        std::vector<uint8_t> data;
+
+        m.read(std::tie(netFn, lun, cmd, cc, data));
+
+        uint8_t netFnLun = (netFn << netFnShift) | (lun & lunMask);
+        uint8_t seqLun = (seq << netFnShift) | (lun & lunMask);
+
+        std::vector<uint8_t> connectionHeader = {rqAddr, netFnLun};
+        std::vector<uint8_t> messageHeader = {rsAddr, seqLun, cmd, cc};
+
+        // Reserve the buffer size to avoid relloc and copy
+        responseBuffer.clear();
+        responseBuffer.reserve(
+            sizeof(struct IpmiSerialHeader) + 2 * data.size() +
+            4); // 4 for bmStart & bmStop & 2 checksums
+
+        // bmStart
+        responseBuffer.push_back(bmStart);
+
+        // Assemble connection header and checksum
+        checksum = processEscapedCharacter(responseBuffer, connectionHeader);
+        responseBuffer.push_back(-checksum); // checksum1
+
+        // Assemble response message and checksum
+        checksum = processEscapedCharacter(responseBuffer, messageHeader);
+        checksum +=
+            processEscapedCharacter(responseBuffer, std::vector<uint8_t>(data));
+        responseBuffer.push_back(-checksum); // checksum2
+
+        // bmStop
+        responseBuffer.push_back(bmStop);
+
+        out = std::span<uint8_t>(responseBuffer.begin(), responseBuffer.end());
+
+        if (verbose)
+        {
+            lg2::info(
+                "Write serial request message with len={LEN}, netfn={NETFN}, "
+                "lun={LUN}, cmd={CMD}, seq={SEQ}",
+                "LEN", responseBuffer.size(), "NETFN", netFn, "LUN", lun, "CMD",
+                cmd, "SEQ", seq);
+
+            std::string msgData = "Tx: ";
+            for (auto c : responseBuffer)
+            {
+                msgData += std::format("{:#x} ", c);
+            }
+            lg2::info(msgData.c_str());
+        }
+
+        stdplus::fd::writeExact(uart, out);
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("IPMI Response failure: {MSG}", "MSG", e);
+
+        return -1;
+    }
+
+    return out.size();
+}
+
+/**
+ * @brief Read function
+ */
+void SerialChannel::read(stdplus::Fd& uart, sdbusplus::bus_t& bus,
+                         sdbusplus::slot_t& outstanding)
+{
+    std::array<uint8_t, ipmiSerialMaxBufferSize> buffer;
+    auto ipmiSerialPacket = stdplus::fd::read(uart, buffer);
+
+    if (ipmiSerialPacket.empty())
+    {
+        return;
+    }
+
+    if (outstanding)
+    {
+        lg2::error("Canceling outstanding request \n");
+        outstanding = sdbusplus::slot_t(nullptr);
+    }
+
+    // process ipmi serial packet
+    if (!consumeIpmiSerialPacket(ipmiSerialPacket, requestBuffer))
+    {
+        lg2::info("Wait for more data ... \n");
+        return;
+    }
+
+    // validate ipmi serial packet length
+    if (requestBuffer.size() <
+        (sizeof(struct IpmiSerialHeader) + ipmiSerialChecksumSize))
+    {
+        lg2::error("Invalid request length, ignoring \n");
+        requestBuffer.clear();
+        return;
+    }
+
+    // validate checksum1
+    if (calculateChecksum(std::span<uint8_t>(requestBuffer.begin(),
+                                             ipmiSerialConnectionHeaderLength)))
+    {
+        lg2::error("Invalid request checksum 1 \n");
+        requestBuffer.clear();
+        return;
+    }
+
+    // validate checksum2
+    if (calculateChecksum(std::span<uint8_t>(
+            &requestBuffer[ipmiSerialConnectionHeaderLength],
+            requestBuffer.size() - ipmiSerialConnectionHeaderLength)))
+    {
+        lg2::error("Invalid request checksum 2 \n");
+        requestBuffer.clear();
+        return;
+    }
+
+    auto m = bus.new_method_call("xyz.openbmc_project.Ipmi.Host",
+                                 "/xyz/openbmc_project/Ipmi",
+                                 "xyz.openbmc_project.Ipmi.Server", "execute");
+
+    std::map<std::string, std::variant<int>> options;
+    struct IpmiSerialHeader* header =
+        reinterpret_cast<struct IpmiSerialHeader*>(requestBuffer.data());
+
+    uint8_t rsAddr = header->rsAddr;
+    uint8_t netFn = header->rsNetFnLUN >> netFnShift;
+    uint8_t lun = header->rsNetFnLUN & lunMask;
+    uint8_t rqAddr = header->rqAddr;
+    uint8_t seq = header->rqSeqLUN >> netFnShift;
+    uint8_t cmd = header->cmd;
+
+    std::span reqSpan{requestBuffer.begin(),
+                      requestBuffer.end() -
+                          ipmiSerialChecksumSize}; // remove checksum 2
+    m.append(netFn, lun, cmd, reqSpan.subspan(sizeof(IpmiSerialHeader)),
+             options);
+
+    if (verbose)
+    {
+        lg2::info("Read serial request message with len={LEN}, netFn={NETFN}, "
+                  "lun={LUN}, cmd={CMD}, seq={SEQ}",
+                  "LEN", requestBuffer.size(), "NETFN", netFn, "LUN", lun,
+                  "CMD", cmd, "SEQ", seq);
+
+        std::string msgData = "Rx: ";
+        for (auto c : requestBuffer)
+        {
+            msgData += std::format("{:#x} ", c);
+        }
+        lg2::info(msgData.c_str());
+    }
+
+    outstanding = m.call_async(stdplus::exception::ignore(
+        [&outstanding, this, &uart, _rsAddr{rsAddr}, _rqAddr{rqAddr},
+         _seq{seq}](sdbusplus::message_t&& m) {
+            outstanding = sdbusplus::slot_t(nullptr);
+
+            if (write(uart, _rsAddr, _rqAddr, _seq, std::move(m)) < 0)
+            {
+                lg2::error(
+                    "Occur an error while attempting to send the response.");
+            }
+        }));
+
+    requestBuffer.clear();
+
+    return;
+}
+
+} // namespace serialbridge
diff --git a/transport/serialbridge/serialcmd.hpp b/transport/serialbridge/serialcmd.hpp
new file mode 100644
index 0000000..985921c
--- /dev/null
+++ b/transport/serialbridge/serialcmd.hpp
@@ -0,0 +1,64 @@
+#pragma once
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/slot.hpp>
+#include <stdplus/fd/intf.hpp>
+
+namespace serialbridge
+{
+
+static constexpr auto bmStart = 0xA0;
+static constexpr auto bmStop = 0xA5;
+static constexpr auto bmHandshake = 0xA6;
+static constexpr auto bmEscape = 0xAA;
+
+static constexpr auto ipmiSerialConnectionHeaderLength = 3;
+static constexpr auto ipmiSerialChecksumSize = 1;
+static constexpr auto ipmiSerialMaxBufferSize = 256;
+
+/**
+ * @brief IPMI Serial Message Structure
+ */
+struct IpmiSerialHeader
+{
+    uint8_t rsAddr;
+    uint8_t rsNetFnLUN;
+    uint8_t checksum1;
+    uint8_t rqAddr;
+    uint8_t rqSeqLUN;
+    uint8_t cmd;
+} __attribute__((packed));
+
+class SerialChannel
+{
+  public:
+    static constexpr uint8_t netFnShift = 2;
+    static constexpr uint8_t lunMask = (1 << netFnShift) - 1;
+
+    SerialChannel(bool debug) : verbose(debug), msgState(MsgState::msgIdle) {};
+
+    int write(stdplus::Fd& uart, uint8_t rsAddr, uint8_t rqAddr, uint8_t seq,
+              sdbusplus::message_t&& m);
+    void read(stdplus::Fd& serial, sdbusplus::bus_t& bus,
+              sdbusplus::slot_t& outstanding);
+    uint8_t calculateChecksum(std::span<uint8_t> data);
+    uint8_t getUnescapedCharacter(uint8_t c);
+    int consumeIpmiSerialPacket(std::span<uint8_t>& escapedDataBytes,
+                                std::vector<uint8_t>& unescapedDataBytes);
+    uint8_t processEscapedCharacter(std::vector<uint8_t>& buffer,
+                                    const std::vector<uint8_t>& data);
+
+  private:
+    bool verbose;
+    enum class MsgState
+    {
+        msgIdle = 0,
+        msgInProgress,
+        msgInEscape,
+    };
+    MsgState msgState;
+    std::vector<uint8_t> requestBuffer;
+    std::vector<uint8_t> responseBuffer;
+};
+
+} // namespace serialbridge
diff --git a/transport/serialbridge/test/meson.build b/transport/serialbridge/test/meson.build
new file mode 100644
index 0000000..39835d2
--- /dev/null
+++ b/transport/serialbridge/test/meson.build
@@ -0,0 +1,44 @@
+gtest = dependency('gtest', main: true, disabler: true, required: false)
+gmock = dependency('gmock', disabler: true, required: false)
+if not gtest.found() or not gmock.found()
+    gtest_opts = import('cmake').subproject_options()
+    gtest_opts.add_cmake_defines({'CMAKE_CXX_FLAGS': '-Wno-pedantic'})
+    gtest_proj = import('cmake').subproject(
+        'googletest',
+        options: gtest_opts,
+        required: false,
+    )
+    if gtest_proj.found()
+        gtest = declare_dependency(
+            dependencies: [
+                dependency('threads'),
+                gtest_proj.dependency('gtest'),
+                gtest_proj.dependency('gtest_main'),
+            ],
+        )
+        gmock = gtest_proj.dependency('gmock')
+    else
+        assert(not get_option('tests').enabled(), 'Googletest is required')
+    endif
+endif
+
+# Build/add serial_unittest to test suite
+test(
+    'transport_serial',
+    executable(
+        'transport_serial_unittest',
+        'serial_unittest.cpp',
+        '../serialcmd.cpp',
+        include_directories: root_inc,
+        build_by_default: false,
+        implicit_include_directories: false,
+        dependencies: [
+            sdbusplus_dep,
+            stdplus_dep,
+            phosphor_logging_dep,
+            sdeventplus_dep,
+            gtest,
+            gmock,
+        ],
+    ),
+)
diff --git a/transport/serialbridge/test/serial_unittest.cpp b/transport/serialbridge/test/serial_unittest.cpp
new file mode 100644
index 0000000..3fb0227
--- /dev/null
+++ b/transport/serialbridge/test/serial_unittest.cpp
@@ -0,0 +1,84 @@
+#include <transport/serialbridge/serialcmd.hpp>
+
+#include <gtest/gtest.h>
+
+namespace serialbridge
+{
+
+/**
+ * @brief Table of special characters
+ */
+std::unordered_map<uint8_t, uint8_t> testsets = {
+    {bmStart, 0xB0},     /* start */
+    {bmStop, 0xB5},      /* stop */
+    {bmHandshake, 0xB6}, /* packet handshake */
+    {bmEscape, 0xBA},    /* data escape */
+    {0x1B, 0x3B}         /* escape */
+};
+
+TEST(TestSpecialCharact, getUnescapedCharact)
+{
+    uint8_t c;
+    auto channel = std::make_shared<SerialChannel>(0);
+
+    for (const auto& set : testsets)
+    {
+        c = channel->getUnescapedCharacter(set.second);
+        ASSERT_EQ(c, set.first);
+    }
+}
+
+TEST(TestSpecialCharact, processEscapedCharacter)
+{
+    std::vector<uint8_t> buffer;
+    uint8_t unescaped = 0xd0;
+    auto channel = std::make_shared<SerialChannel>(0);
+
+    channel->processEscapedCharacter(buffer, std::vector<uint8_t>{bmStart});
+
+    ASSERT_EQ(buffer.at(0), bmEscape);
+    ASSERT_EQ(buffer.at(1), testsets.at(bmStart));
+
+    buffer.clear();
+    channel->processEscapedCharacter(buffer, std::vector<uint8_t>{unescaped});
+
+    ASSERT_EQ(buffer.at(0), unescaped);
+}
+
+TEST(TestChecksum, calculateChecksum)
+{
+    std::array<uint8_t, 5> dataBytes{0x01, 0x10, 0x60, 0xf0, 0x50};
+    auto channel = std::make_shared<SerialChannel>(0);
+
+    uint8_t checksum =
+        channel->calculateChecksum(std::span<uint8_t>(dataBytes));
+
+    checksum += (~checksum) + 1;
+    ASSERT_EQ(checksum, 0);
+}
+
+TEST(TestIpmiSerialPacket, consumeIpmiSerialPacket)
+{
+    std::vector<uint8_t> dataBytes{bmStart, 0x20, 0x18, 0xc8, 0x81,
+                                   0xc,     0x46, 0x01, 0x2c, bmStop};
+    std::vector<uint8_t> dataBytesSplit1{bmStart, 0x20, 0x18, 0xc8};
+    std::vector<uint8_t> dataBytesSplit2{0x81, 0xc, 0x46, 0x01, 0x2c, bmStop};
+    std::span<uint8_t> input(dataBytes);
+    std::span<uint8_t> input1(dataBytesSplit1);
+    std::span<uint8_t> input2(dataBytesSplit2);
+    std::vector<uint8_t> output;
+
+    auto channel = std::make_shared<SerialChannel>(0);
+
+    auto result = channel->consumeIpmiSerialPacket(input, output);
+
+    ASSERT_EQ(result, true);
+
+    output.clear();
+    result = channel->consumeIpmiSerialPacket(input1, output);
+    ASSERT_EQ(result, false);
+    result = channel->consumeIpmiSerialPacket(input2, output);
+    ASSERT_EQ(result, true);
+}
+
+} // namespace serialbridge
diff --git a/transportconstants.hpp b/transportconstants.hpp
new file mode 100644
index 0000000..3f865c6
--- /dev/null
+++ b/transportconstants.hpp
@@ -0,0 +1,160 @@
+#pragma once
+
+#include <ipmid/api-types.hpp>
+#include <stdplus/zstring_view.hpp>
+
+#include <cstdint>
+
+namespace ipmi
+{
+namespace transport
+{
+
+using stdplus::operator""_zsv;
+
+// D-Bus Network Daemon definitions
+constexpr auto PATH_ROOT = "/xyz/openbmc_project/network"_zsv;
+constexpr auto INTF_ETHERNET = "xyz.openbmc_project.Network.EthernetInterface";
+constexpr auto INTF_IP = "xyz.openbmc_project.Network.IP";
+constexpr auto INTF_IP_CREATE = "xyz.openbmc_project.Network.IP.Create";
+constexpr auto INTF_MAC = "xyz.openbmc_project.Network.MACAddress";
+constexpr auto INTF_NEIGHBOR = "xyz.openbmc_project.Network.Neighbor";
+constexpr auto INTF_NEIGHBOR_CREATE_STATIC =
+    "xyz.openbmc_project.Network.Neighbor.CreateStatic";
+constexpr auto INTF_VLAN = "xyz.openbmc_project.Network.VLAN";
+constexpr auto INTF_VLAN_CREATE = "xyz.openbmc_project.Network.VLAN.Create";
+
+/** @brief IPMI LAN Parameters */
+enum class LanParam : uint8_t
+{
+    SetStatus = 0,
+    AuthSupport = 1,
+    AuthEnables = 2,
+    IP = 3,
+    IPSrc = 4,
+    MAC = 5,
+    SubnetMask = 6,
+    Gateway1 = 12,
+    Gateway1MAC = 13,
+    VLANId = 20,
+    CiphersuiteSupport = 22,
+    CiphersuiteEntries = 23,
+    cipherSuitePrivilegeLevels = 24,
+    IPFamilySupport = 50,
+    IPFamilyEnables = 51,
+    IPv6Status = 55,
+    IPv6StaticAddresses = 56,
+    IPv6DynamicAddresses = 59,
+    IPv6RouterControl = 64,
+    IPv6StaticRouter1IP = 65,
+    IPv6StaticRouter1MAC = 66,
+    IPv6StaticRouter1PrefixLength = 67,
+    IPv6StaticRouter1PrefixValue = 68,
+};
+
+/** @brief IPMI IP Origin Types */
+enum class IPSrc : uint8_t
+{
+    Unspecified = 0,
+    Static = 1,
+    DHCP = 2,
+    BIOS = 3,
+    BMC = 4,
+};
+
+/** @brief IPMI Set Status */
+enum class SetStatus : uint8_t
+{
+    Complete = 0,
+    InProgress = 1,
+    Commit = 2,
+};
+
+/** @brief IPMI Family Suport Bits */
+namespace IPFamilySupportFlag
+{
+constexpr uint8_t IPv6Only = 0;
+constexpr uint8_t DualStack = 1;
+constexpr uint8_t IPv6Alerts = 2;
+} // namespace IPFamilySupportFlag
+
+/** @brief IPMI IPFamily Enables Flag */
+enum class IPFamilyEnables : uint8_t
+{
+    IPv4Only = 0,
+    IPv6Only = 1,
+    DualStack = 2,
+};
+
+/** @brief IPMI IPv6 Dyanmic Status Bits */
+namespace IPv6StatusFlag
+{
+constexpr uint8_t DHCP = 0;
+constexpr uint8_t SLAAC = 1;
+}; // namespace IPv6StatusFlag
+
+/** @brief IPMI IPv6 Source */
+enum class IPv6Source : uint8_t
+{
+    Static = 0,
+    SLAAC = 1,
+    DHCP = 2,
+};
+
+/** @brief IPMI IPv6 Address Status */
+enum class IPv6AddressStatus : uint8_t
+{
+    Active = 0,
+    Disabled = 1,
+};
+
+namespace IPv6RouterControlFlag
+{
+constexpr uint8_t Static = 0;
+constexpr uint8_t Dynamic = 1;
+}; // namespace IPv6RouterControlFlag
+
+// LAN Handler specific response codes
+constexpr Cc ccParamNotSupported = 0x80;
+constexpr Cc ccParamSetLocked = 0x81;
+constexpr Cc ccParamReadOnly = 0x82;
+
+// VLANs are a 12-bit value
+constexpr uint16_t VLAN_VALUE_MASK = 0x0fff;
+constexpr uint16_t VLAN_ENABLE_FLAG = 0x8000;
+
+// Arbitrary v4 Address Limits
+constexpr uint8_t MAX_IPV4_ADDRESSES = 2;
+
+// Arbitrary v6 Address Limits to prevent too much output in ipmitool
+constexpr uint8_t MAX_IPV6_STATIC_ADDRESSES = 15;
+constexpr uint8_t MAX_IPV6_DYNAMIC_ADDRESSES = 15;
+
+// Prefix length limits of phosphor-networkd
+constexpr uint8_t MIN_IPV4_PREFIX_LENGTH = 1;
+constexpr uint8_t MAX_IPV4_PREFIX_LENGTH = 32;
+constexpr uint8_t MIN_IPV6_PREFIX_LENGTH = 1;
+constexpr uint8_t MAX_IPV6_PREFIX_LENGTH = 128;
+
+/** @enum SolConfParam
+ *
+ *  using for Set/Get SOL configuration parameters command.
+ */
+enum class SolConfParam : uint8_t
+{
+    Progress,       //!< Set In Progress.
+    Enable,         //!< SOL Enable.
+    Authentication, //!< SOL Authentication.
+    Accumulate,     //!< Character Accumulate Interval & Send Threshold.
+    Retry,          //!< SOL Retry.
+    NonVbitrate,    //!< SOL non-volatile bit rate.
+    Vbitrate,       //!< SOL volatile bit rate.
+    Channel,        //!< SOL payload channel.
+    Port,           //!< SOL payload port.
+};
+
+constexpr uint8_t ipmiCCParamNotSupported = 0x80;
+constexpr uint8_t ipmiCCWriteReadParameter = 0x82;
+
+} // namespace transport
+} // namespace ipmi
diff --git a/transporthandler.cpp b/transporthandler.cpp
new file mode 100644
index 0000000..7218da9
--- /dev/null
+++ b/transporthandler.cpp
@@ -0,0 +1,1741 @@
+#include "transporthandler.hpp"
+
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <stdplus/net/addr/subnet.hpp>
+#include <stdplus/raw.hpp>
+
+#include <array>
+#include <fstream>
+
+using phosphor::logging::elog;
+using sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+using sdbusplus::error::xyz::openbmc_project::common::InvalidArgument;
+using sdbusplus::server::xyz::openbmc_project::network::EthernetInterface;
+using sdbusplus::server::xyz::openbmc_project::network::IP;
+using sdbusplus::server::xyz::openbmc_project::network::Neighbor;
+
+namespace cipher
+{
+
+std::vector<uint8_t> getCipherList()
+{
+    std::vector<uint8_t> cipherList;
+
+    std::ifstream jsonFile(cipher::configFile);
+    if (!jsonFile.is_open())
+    {
+        lg2::error("Channel Cipher suites file not found");
+        elog<InternalFailure>();
+    }
+
+    auto data = Json::parse(jsonFile, nullptr, false);
+    if (data.is_discarded())
+    {
+        lg2::error("Parsing channel cipher suites JSON failed");
+        elog<InternalFailure>();
+    }
+
+    // Byte 1 is reserved
+    cipherList.push_back(0x00);
+
+    for (const auto& record : data)
+    {
+        cipherList.push_back(record.value(cipher, 0));
+    }
+
+    return cipherList;
+}
+} // namespace cipher
+
+namespace ipmi
+{
+namespace transport
+{
+
+/** @brief Valid address origins for IPv4 */
+const std::unordered_set<IP::AddressOrigin> originsV4 = {
+    IP::AddressOrigin::Static,
+    IP::AddressOrigin::DHCP,
+};
+
+static constexpr uint8_t oemCmdStart = 192;
+
+// Checks if the ifname is part of the networkd path
+// This assumes the path came from the network subtree PATH_ROOT
+bool ifnameInPath(std::string_view ifname, std::string_view path)
+{
+    constexpr auto rs = PATH_ROOT.size() + 1; // ROOT + separator
+    const auto is = rs + ifname.size();       // ROOT + sep + ifname
+    return path.size() > rs && path.substr(rs).starts_with(ifname) &&
+           (path.size() == is || path[is] == '/');
+}
+
+std::optional<ChannelParams> maybeGetChannelParams(sdbusplus::bus_t& bus,
+                                                   uint8_t channel)
+{
+    auto ifname = getChannelName(channel);
+    if (ifname.empty())
+    {
+        return std::nullopt;
+    }
+
+    // Enumerate all VLAN + ETHERNET interfaces
+    std::vector<std::string> interfaces = {INTF_VLAN, INTF_ETHERNET};
+    ipmi::ObjectTree objs =
+        ipmi::getSubTree(bus, interfaces, std::string{PATH_ROOT});
+
+    ChannelParams params;
+    for (const auto& [path, impls] : objs)
+    {
+        if (!ifnameInPath(ifname, path))
+        {
+            continue;
+        }
+        for (const auto& [service, intfs] : impls)
+        {
+            bool vlan = false;
+            bool ethernet = false;
+            for (const auto& intf : intfs)
+            {
+                if (intf == INTF_VLAN)
+                {
+                    vlan = true;
+                }
+                else if (intf == INTF_ETHERNET)
+                {
+                    ethernet = true;
+                }
+            }
+            if (params.service.empty() && (vlan || ethernet))
+            {
+                params.service = service;
+            }
+            if (params.ifPath.empty() && !vlan && ethernet)
+            {
+                params.ifPath = path;
+            }
+            if (params.logicalPath.empty() && vlan)
+            {
+                params.logicalPath = path;
+            }
+        }
+    }
+
+    // We must have a path for the underlying interface
+    if (params.ifPath.empty())
+    {
+        return std::nullopt;
+    }
+    // We don't have a VLAN so the logical path is the same
+    if (params.logicalPath.empty())
+    {
+        params.logicalPath = params.ifPath;
+    }
+
+    params.id = channel;
+    params.ifname = std::move(ifname);
+    return params;
+}
+
+ChannelParams getChannelParams(sdbusplus::bus_t& bus, uint8_t channel)
+{
+    auto params = maybeGetChannelParams(bus, channel);
+    if (!params)
+    {
+        lg2::error("Failed to get channel params: {CHANNEL}", "CHANNEL",
+                   channel);
+        elog<InternalFailure>();
+    }
+    return std::move(*params);
+}
+
+/** @brief Get / Set the Property value from phosphor-networkd EthernetInterface
+ */
+template <typename T>
+static T getEthProp(sdbusplus::bus_t& bus, const ChannelParams& params,
+                    const std::string& prop)
+{
+    return std::get<T>(getDbusProperty(bus, params.service, params.logicalPath,
+                                       INTF_ETHERNET, prop));
+}
+template <typename T>
+static void setEthProp(sdbusplus::bus_t& bus, const ChannelParams& params,
+                       const std::string& prop, const T& t)
+{
+    return setDbusProperty(bus, params.service, params.logicalPath,
+                           INTF_ETHERNET, prop, t);
+}
+
+/** @brief Determines the MAC of the ethernet interface
+ *
+ *  @param[in] bus    - The bus object used for lookups
+ *  @param[in] params - The parameters for the channel
+ *  @return The configured mac address
+ */
+stdplus::EtherAddr getMACProperty(sdbusplus::bus_t& bus,
+                                  const ChannelParams& params)
+{
+    auto prop = getDbusProperty(bus, params.service, params.ifPath, INTF_MAC,
+                                "MACAddress");
+    return stdplus::fromStr<stdplus::EtherAddr>(std::get<std::string>(prop));
+}
+
+/** @brief Sets the system value for MAC address on the given interface
+ *
+ *  @param[in] bus    - The bus object used for lookups
+ *  @param[in] params - The parameters for the channel
+ *  @param[in] mac    - MAC address to apply
+ */
+void setMACProperty(sdbusplus::bus_t& bus, const ChannelParams& params,
+                    stdplus::EtherAddr mac)
+{
+    setDbusProperty(bus, params.service, params.ifPath, INTF_MAC, "MACAddress",
+                    stdplus::toStr(mac));
+}
+
+void deleteObjectIfExists(sdbusplus::bus_t& bus, const std::string& service,
+                          const std::string& path)
+{
+    if (path.empty())
+    {
+        return;
+    }
+    try
+    {
+        auto req = bus.new_method_call(service.c_str(), path.c_str(),
+                                       ipmi::DELETE_INTERFACE, "Delete");
+        bus.call_noreply(req);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        if (strcmp(e.name(),
+                   "xyz.openbmc_project.Common.Error.InternalFailure") != 0 &&
+            strcmp(e.name(), "org.freedesktop.DBus.Error.UnknownObject") != 0)
+        {
+            // We want to rethrow real errors
+            throw;
+        }
+    }
+}
+
+/** @brief Sets the address info configured for the interface
+ *         If a previous address path exists then it will be removed
+ *         before the new address is added.
+ *
+ *  @param[in] bus     - The bus object used for lookups
+ *  @param[in] params  - The parameters for the channel
+ *  @param[in] address - The address of the new IP
+ *  @param[in] prefix  - The prefix of the new IP
+ */
+template <int family>
+void createIfAddr(sdbusplus::bus_t& bus, const ChannelParams& params,
+                  typename AddrFamily<family>::addr address, uint8_t prefix)
+{
+    auto newreq =
+        bus.new_method_call(params.service.c_str(), params.logicalPath.c_str(),
+                            INTF_IP_CREATE, "IP");
+    std::string protocol =
+        sdbusplus::common::xyz::openbmc_project::network::convertForMessage(
+            AddrFamily<family>::protocol);
+    stdplus::ToStrHandle<stdplus::ToStr<typename AddrFamily<family>::addr>> tsh;
+    newreq.append(protocol, tsh(address), prefix, "");
+    bus.call_noreply(newreq);
+}
+
+/** @brief Trivial helper for getting the IPv4 address from getIfAddrs()
+ *
+ *  @param[in] bus    - The bus object used for lookups
+ *  @param[in] params - The parameters for the channel
+ *  @return The address and prefix if found
+ */
+auto getIfAddr4(sdbusplus::bus_t& bus, const ChannelParams& params)
+{
+    std::optional<IfAddr<AF_INET>> ifaddr4 = std::nullopt;
+    IP::AddressOrigin src;
+
+    try
+    {
+        src = std::get<bool>(
+                  getDbusProperty(bus, params.service, params.logicalPath,
+                                  INTF_ETHERNET, "DHCP4"))
+                  ? IP::AddressOrigin::DHCP
+                  : IP::AddressOrigin::Static;
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error("Failed to get IPv4 source");
+        return ifaddr4;
+    }
+
+    for (uint8_t i = 0; i < MAX_IPV4_ADDRESSES; ++i)
+    {
+        ifaddr4 = getIfAddr<AF_INET>(bus, params, i, originsV4);
+        if (ifaddr4 && src == ifaddr4->origin)
+        {
+            break;
+        }
+        else
+        {
+            ifaddr4 = std::nullopt;
+        }
+    }
+    return ifaddr4;
+}
+
+/** @brief Reconfigures the IPv4 address info configured for the interface
+ *
+ *  @param[in] bus     - The bus object used for lookups
+ *  @param[in] params  - The parameters for the channel
+ *  @param[in] address - The new address if specified
+ *  @param[in] prefix  - The new address prefix if specified
+ */
+void reconfigureIfAddr4(sdbusplus::bus_t& bus, const ChannelParams& params,
+                        std::optional<stdplus::In4Addr> address,
+                        std::optional<uint8_t> prefix)
+{
+    auto ifaddr = getIfAddr4(bus, params);
+    if (!ifaddr && !address)
+    {
+        lg2::error("Missing address for IPv4 assignment");
+        elog<InternalFailure>();
+    }
+    uint8_t fallbackPrefix = AddrFamily<AF_INET>::defaultPrefix;
+    auto addr = stdplus::In4Addr{};
+    if (ifaddr)
+    {
+        addr = ifaddr->address;
+        fallbackPrefix = ifaddr->prefix;
+        deleteObjectIfExists(bus, params.service, ifaddr->path);
+    }
+    addr = address.value_or(addr);
+    if (addr != stdplus::In4Addr{})
+    {
+        createIfAddr<AF_INET>(bus, params, addr,
+                              prefix.value_or(fallbackPrefix));
+    }
+}
+
+template <int family>
+std::optional<IfNeigh<family>> findGatewayNeighbor(sdbusplus::bus_t& bus,
+                                                   const ChannelParams& params,
+                                                   ObjectLookupCache& neighbors)
+{
+    auto gateway = getGatewayProperty<family>(bus, params);
+    if (!gateway)
+    {
+        return std::nullopt;
+    }
+
+    return findStaticNeighbor<family>(bus, params, *gateway, neighbors);
+}
+
+template <int family>
+std::optional<IfNeigh<family>> getGatewayNeighbor(sdbusplus::bus_t& bus,
+                                                  const ChannelParams& params)
+{
+    ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR);
+    return findGatewayNeighbor<family>(bus, params, neighbors);
+}
+
+template <int family>
+void reconfigureGatewayMAC(sdbusplus::bus_t& bus, const ChannelParams& params,
+                           stdplus::EtherAddr mac)
+{
+    auto gateway = getGatewayProperty<family>(bus, params);
+    if (!gateway)
+    {
+        lg2::error("Tried to set Gateway MAC without Gateway");
+        elog<InternalFailure>();
+    }
+
+    ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR);
+    auto neighbor =
+        findStaticNeighbor<family>(bus, params, *gateway, neighbors);
+    if (neighbor)
+    {
+        deleteObjectIfExists(bus, params.service, neighbor->path);
+    }
+
+    createNeighbor<family>(bus, params, *gateway, mac);
+}
+
+/** @brief Deconfigures the IPv6 address info configured for the interface
+ *
+ *  @param[in] bus     - The bus object used for lookups
+ *  @param[in] params  - The parameters for the channel
+ *  @param[in] idx     - The address index to operate on
+ */
+void deconfigureIfAddr6(sdbusplus::bus_t& bus, const ChannelParams& params,
+                        uint8_t idx)
+{
+    auto ifaddr = getIfAddr<AF_INET6>(bus, params, idx, originsV6Static);
+    if (ifaddr)
+    {
+        deleteObjectIfExists(bus, params.service, ifaddr->path);
+    }
+}
+
+/** @brief Reconfigures the IPv6 address info configured for the interface
+ *
+ *  @param[in] bus     - The bus object used for lookups
+ *  @param[in] params  - The parameters for the channel
+ *  @param[in] idx     - The address index to operate on
+ *  @param[in] address - The new address
+ *  @param[in] prefix  - The new address prefix
+ */
+void reconfigureIfAddr6(sdbusplus::bus_t& bus, const ChannelParams& params,
+                        uint8_t idx, stdplus::In6Addr address, uint8_t prefix)
+{
+    deconfigureIfAddr6(bus, params, idx);
+    createIfAddr<AF_INET6>(bus, params, address, prefix);
+}
+
+/** @brief Converts the AddressOrigin into an IPv6Source
+ *
+ *  @param[in] origin - The DBus Address Origin to convert
+ *  @return The IPv6Source version of the origin
+ */
+IPv6Source originToSourceType(IP::AddressOrigin origin)
+{
+    switch (origin)
+    {
+        case IP::AddressOrigin::Static:
+            return IPv6Source::Static;
+        case IP::AddressOrigin::DHCP:
+            return IPv6Source::DHCP;
+        case IP::AddressOrigin::SLAAC:
+            return IPv6Source::SLAAC;
+        default:
+        {
+            auto originStr = sdbusplus::common::xyz::openbmc_project::network::
+                convertForMessage(origin);
+            lg2::error("Invalid IP::AddressOrigin conversion to IPv6Source, "
+                       "origin: {ORIGIN}",
+                       "ORIGIN", originStr);
+            elog<InternalFailure>();
+        }
+    }
+}
+
+/** @brief Packs the IPMI message response with IPv6 address data
+ *
+ *  @param[out] ret     - The IPMI response payload to be packed
+ *  @param[in]  channel - The channel id corresponding to an ethernet interface
+ *  @param[in]  set     - The set selector for determining address index
+ *  @param[in]  origins - Set of valid origins for address filtering
+ */
+void getLanIPv6Address(message::Payload& ret, uint8_t channel, uint8_t set,
+                       const std::unordered_set<IP::AddressOrigin>& origins)
+{
+    auto source = IPv6Source::Static;
+    bool enabled = false;
+    stdplus::In6Addr addr{};
+    uint8_t prefix{};
+    auto status = IPv6AddressStatus::Disabled;
+
+    auto ifaddr = channelCall<getIfAddr<AF_INET6>>(channel, set, origins);
+    if (ifaddr)
+    {
+        source = originToSourceType(ifaddr->origin);
+        enabled = (origins == originsV6Static);
+        addr = ifaddr->address;
+        prefix = ifaddr->prefix;
+        status = IPv6AddressStatus::Active;
+    }
+
+    ret.pack(set);
+    ret.pack(types::enum_cast<uint4_t>(source), uint3_t{}, enabled);
+    ret.pack(stdplus::raw::asView<char>(addr));
+    ret.pack(prefix);
+    ret.pack(types::enum_cast<uint8_t>(status));
+}
+
+/** @brief Gets the vlan ID configured on the interface
+ *
+ *  @param[in] bus    - The bus object used for lookups
+ *  @param[in] params - The parameters for the channel
+ *  @return VLAN id or the standard 0 for no VLAN
+ */
+uint16_t getVLANProperty(sdbusplus::bus_t& bus, const ChannelParams& params)
+{
+    // VLAN devices will always have a separate logical object
+    if (params.ifPath == params.logicalPath)
+    {
+        return 0;
+    }
+
+    auto vlan = std::get<uint32_t>(getDbusProperty(
+        bus, params.service, params.logicalPath, INTF_VLAN, "Id"));
+    if ((vlan & VLAN_VALUE_MASK) != vlan)
+    {
+        lg2::error("networkd returned an invalid vlan: {VLAN} "
+                   "(CH={CHANNEL}, IF={IFNAME})",
+                   "CHANNEL", params.id, "IFNAME", params.ifname, "VLAN", vlan);
+        elog<InternalFailure>();
+    }
+    return vlan;
+}
+
+/** @brief Deletes all of the possible configuration parameters for a channel
+ *
+ *  @param[in] bus    - The bus object used for lookups
+ *  @param[in] params - The parameters for the channel
+ */
+void deconfigureChannel(sdbusplus::bus_t& bus, ChannelParams& params)
+{
+    // Delete all objects associated with the interface
+    ObjectTree objs =
+        ipmi::getSubTree(bus, std::vector<std::string>{DELETE_INTERFACE},
+                         std::string{PATH_ROOT});
+    for (const auto& [path, impls] : objs)
+    {
+        if (!ifnameInPath(params.ifname, path))
+        {
+            continue;
+        }
+        for (const auto& [service, intfs] : impls)
+        {
+            deleteObjectIfExists(bus, service, path);
+        }
+        // Update params to reflect the deletion of vlan
+        if (path == params.logicalPath)
+        {
+            params.logicalPath = params.ifPath;
+        }
+    }
+
+    // Clear out any settings on the lower physical interface
+    setEthProp(bus, params, "DHCP4", false);
+    setEthProp(bus, params, "DHCP6", false);
+    setEthProp(bus, params, "IPv6AcceptRA", false);
+}
+
+/** @brief Creates a new VLAN on the specified interface
+ *
+ *  @param[in] bus    - The bus object used for lookups
+ *  @param[in] params - The parameters for the channel
+ *  @param[in] vlan   - The id of the new vlan
+ */
+void createVLAN(sdbusplus::bus_t& bus, ChannelParams& params, uint16_t vlan)
+{
+    if (vlan == 0)
+    {
+        return;
+    }
+
+    auto req = bus.new_method_call(params.service.c_str(), PATH_ROOT.c_str(),
+                                   INTF_VLAN_CREATE, "VLAN");
+    req.append(params.ifname, static_cast<uint32_t>(vlan));
+    auto reply = bus.call(req);
+    sdbusplus::message::object_path newPath;
+    reply.read(newPath);
+    params.logicalPath = std::move(newPath);
+}
+
+/** @brief Performs the necessary reconfiguration to change the VLAN
+ *
+ *  @param[in] bus    - The bus object used for lookups
+ *  @param[in] params - The parameters for the channel
+ *  @param[in] vlan   - The new vlan id to use
+ */
+void reconfigureVLAN(sdbusplus::bus_t& bus, ChannelParams& params,
+                     uint16_t vlan)
+{
+    // Unfortunatetly we don't have built-in functions to migrate our interface
+    // customizations to new VLAN interfaces, or have some kind of decoupling.
+    // We therefore must retain all of our old information, setup the new VLAN
+    // configuration, then restore the old info.
+
+    // Save info from the old logical interface
+    bool dhcp4 = getEthProp<bool>(bus, params, "DHCP4");
+    bool dhcp6 = getEthProp<bool>(bus, params, "DHCP6");
+    bool ra = getEthProp<bool>(bus, params, "IPv6AcceptRA");
+    ObjectLookupCache ips(bus, params, INTF_IP);
+    auto ifaddr4 = findIfAddr<AF_INET>(bus, params, 0, originsV4, ips);
+    std::vector<IfAddr<AF_INET6>> ifaddrs6;
+    for (uint8_t i = 0; i < MAX_IPV6_STATIC_ADDRESSES; ++i)
+    {
+        auto ifaddr6 =
+            findIfAddr<AF_INET6>(bus, params, i, originsV6Static, ips);
+        if (!ifaddr6)
+        {
+            break;
+        }
+        ifaddrs6.push_back(std::move(*ifaddr6));
+    }
+    ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR);
+    auto neighbor4 = findGatewayNeighbor<AF_INET>(bus, params, neighbors);
+    auto neighbor6 = findGatewayNeighbor<AF_INET6>(bus, params, neighbors);
+
+    deconfigureChannel(bus, params);
+    createVLAN(bus, params, vlan);
+
+    // Re-establish the saved settings
+    setEthProp(bus, params, "DHCP4", dhcp4);
+    setEthProp(bus, params, "DHCP6", dhcp6);
+    setEthProp(bus, params, "IPv6AcceptRA", ra);
+    if (ifaddr4)
+    {
+        createIfAddr<AF_INET>(bus, params, ifaddr4->address, ifaddr4->prefix);
+    }
+    for (const auto& ifaddr6 : ifaddrs6)
+    {
+        createIfAddr<AF_INET6>(bus, params, ifaddr6.address, ifaddr6.prefix);
+    }
+    if (neighbor4)
+    {
+        createNeighbor<AF_INET>(bus, params, neighbor4->ip, neighbor4->mac);
+    }
+    if (neighbor6)
+    {
+        createNeighbor<AF_INET6>(bus, params, neighbor6->ip, neighbor6->mac);
+    }
+}
+
+// We need to store this value so it can be returned to the client
+// It is volatile so safe to store in daemon memory.
+static std::unordered_map<uint8_t, SetStatus> setStatus;
+
+// Until we have good support for fixed versions of IPMI tool
+// we need to return the VLAN id for disabled VLANs. The value is only
+// used for verification that a disable operation succeeded and will only
+// be sent if our system indicates that vlans are disabled.
+static std::unordered_map<uint8_t, uint16_t> lastDisabledVlan;
+
+/** @brief Gets the set status for the channel if it exists
+ *         Otherise populates and returns the default value.
+ *
+ *  @param[in] channel - The channel id corresponding to an ethernet interface
+ *  @return A reference to the SetStatus for the channel
+ */
+SetStatus& getSetStatus(uint8_t channel)
+{
+    auto it = setStatus.find(channel);
+    if (it != setStatus.end())
+    {
+        return it->second;
+    }
+    return setStatus[channel] = SetStatus::Complete;
+}
+
+/** @brief Unpacks the trivially copyable type from the message */
+template <typename T>
+static T unpackT(message::Payload& req)
+{
+    std::array<uint8_t, sizeof(T)> bytes;
+    if (req.unpack(bytes) != 0)
+    {
+        throw ccReqDataLenInvalid;
+    }
+    return stdplus::raw::copyFrom<T>(bytes);
+}
+
+/** @brief Ensure the message is fully unpacked */
+static void unpackFinal(message::Payload& req)
+{
+    if (!req.fullyUnpacked())
+    {
+        throw ccReqDataTruncated;
+    }
+}
+
+/**
+ * Define placeholder command handlers for the OEM Extension bytes for the Set
+ * LAN Configuration Parameters and Get LAN Configuration Parameters
+ * commands. Using "weak" linking allows the placeholder setLanOem/getLanOem
+ * functions below to be overridden.
+ * To create handlers for your own proprietary command set:
+ *   Create/modify a phosphor-ipmi-host Bitbake append file within your Yocto
+ *   recipe
+ *   Create C++ file(s) that define IPMI handler functions matching the
+ *     function names below (i.e. setLanOem). The default name for the
+ *     transport IPMI commands is transporthandler_oem.cpp.
+ *   Add:
+ *      EXTRA_OEMESON:append = "-Dtransport-oem=enabled"
+ *   Create a do_configure:prepend()/do_install:append() method in your
+ *   bbappend file to copy the file to the build directory.
+ *   Add:
+ *   PROJECT_SRC_DIR := "${THISDIR}/${PN}"
+ *   # Copy the "strong" functions into the working directory, overriding the
+ *   # placeholder functions.
+ *   do_configure:prepend(){
+ *      cp -f ${PROJECT_SRC_DIR}/transporthandler_oem.cpp ${S}
+ *   }
+ *
+ *   # Clean up after complilation has completed
+ *   do_install:append(){
+ *      rm -f ${S}/transporthandler_oem.cpp
+ *   }
+ *
+ */
+
+/**
+ * Define the placeholder OEM commands as having weak linkage. Create
+ * setLanOem, and getLanOem functions in the transporthandler_oem.cpp
+ * file. The functions defined there must not have the "weak" attribute
+ * applied to them.
+ */
+RspType<> setLanOem(uint8_t channel, uint8_t parameter, message::Payload& req)
+    __attribute__((weak));
+RspType<message::Payload> getLanOem(uint8_t channel, uint8_t parameter,
+                                    uint8_t set, uint8_t block)
+    __attribute__((weak));
+
+RspType<> setLanOem(uint8_t, uint8_t, message::Payload& req)
+{
+    req.trailingOk = true;
+    return response(ccParamNotSupported);
+}
+
+RspType<message::Payload> getLanOem(uint8_t, uint8_t, uint8_t, uint8_t)
+{
+    return response(ccParamNotSupported);
+}
+
+/**
+ * @brief is a valid LAN channel.
+ *
+ * This function checks whether the input channel is a valid LAN channel or not.
+ *
+ * @param[in] channel: the channel number.
+ * @return nullopt if the channel is invalid, false if the channel is not a LAN
+ * channel, true if the channel is a LAN channel.
+ **/
+std::optional<bool> isLanChannel(uint8_t channel)
+{
+    ChannelInfo chInfo;
+    auto cc = getChannelInfo(channel, chInfo);
+    if (cc != ccSuccess)
+    {
+        return std::nullopt;
+    }
+
+    return chInfo.mediumType ==
+           static_cast<uint8_t>(EChannelMediumType::lan8032);
+}
+
+RspType<> setLanInt(Context::ptr ctx, uint4_t channelBits, uint4_t reserved1,
+                    uint8_t parameter, message::Payload& req)
+{
+    const uint8_t channel = convertCurrentChannelNum(
+        static_cast<uint8_t>(channelBits), ctx->channel);
+    if (reserved1 || !isValidChannel(channel))
+    {
+        lg2::error("Set Lan - Invalid field in request");
+        req.trailingOk = true;
+        return responseInvalidFieldRequest();
+    }
+
+    if (!isLanChannel(channel).value_or(false))
+    {
+        lg2::error("Set Lan - Not a LAN channel");
+        return responseInvalidFieldRequest();
+    }
+
+    switch (static_cast<LanParam>(parameter))
+    {
+        case LanParam::SetStatus:
+        {
+            uint2_t flag;
+            uint6_t rsvd;
+            if (req.unpack(flag, rsvd) != 0)
+            {
+                return responseReqDataLenInvalid();
+            }
+            unpackFinal(req);
+            if (rsvd)
+            {
+                return responseInvalidFieldRequest();
+            }
+            auto status = static_cast<SetStatus>(static_cast<uint8_t>(flag));
+            switch (status)
+            {
+                case SetStatus::Complete:
+                {
+                    getSetStatus(channel) = status;
+                    return responseSuccess();
+                }
+                case SetStatus::InProgress:
+                {
+                    auto& storedStatus = getSetStatus(channel);
+                    if (storedStatus == SetStatus::InProgress)
+                    {
+                        return response(ccParamSetLocked);
+                    }
+                    storedStatus = status;
+                    return responseSuccess();
+                }
+                case SetStatus::Commit:
+                    if (getSetStatus(channel) != SetStatus::InProgress)
+                    {
+                        return responseInvalidFieldRequest();
+                    }
+                    return responseSuccess();
+            }
+            return response(ccParamNotSupported);
+        }
+        case LanParam::AuthSupport:
+        {
+            req.trailingOk = true;
+            return response(ccParamReadOnly);
+        }
+        case LanParam::AuthEnables:
+        {
+            req.trailingOk = true;
+            return response(ccParamReadOnly);
+        }
+        case LanParam::IP:
+        {
+            if (channelCall<getEthProp<bool>>(channel, "DHCP4"))
+            {
+                return responseCommandNotAvailable();
+            }
+            auto ip = unpackT<stdplus::In4Addr>(req);
+            unpackFinal(req);
+            channelCall<reconfigureIfAddr4>(channel, ip, std::nullopt);
+            return responseSuccess();
+        }
+        case LanParam::IPSrc:
+        {
+            uint4_t flag;
+            uint4_t rsvd;
+            if (req.unpack(flag, rsvd) != 0)
+            {
+                return responseReqDataLenInvalid();
+            }
+            unpackFinal(req);
+            if (rsvd)
+            {
+                return responseInvalidFieldRequest();
+            }
+            switch (static_cast<IPSrc>(static_cast<uint8_t>(flag)))
+            {
+                case IPSrc::DHCP:
+                    // The IPSrc IPMI command is only for IPv4
+                    // management. Modifying IPv6 state is done using
+                    // a completely different Set LAN Configuration
+                    // subcommand.
+                    channelCall<setEthProp<bool>>(channel, "DHCP4", true);
+                    return responseSuccess();
+                case IPSrc::Unspecified:
+                case IPSrc::Static:
+                    channelCall<setEthProp<bool>>(channel, "DHCP4", false);
+                    return responseSuccess();
+                case IPSrc::BIOS:
+                case IPSrc::BMC:
+                    return responseInvalidFieldRequest();
+            }
+            return response(ccParamNotSupported);
+        }
+        case LanParam::MAC:
+        {
+            auto mac = unpackT<stdplus::EtherAddr>(req);
+            unpackFinal(req);
+            channelCall<setMACProperty>(channel, mac);
+            return responseSuccess();
+        }
+        case LanParam::SubnetMask:
+        {
+            if (channelCall<getEthProp<bool>>(channel, "DHCP4"))
+            {
+                return responseCommandNotAvailable();
+            }
+            auto pfx = stdplus::maskToPfx(unpackT<stdplus::In4Addr>(req));
+            unpackFinal(req);
+            channelCall<reconfigureIfAddr4>(channel, std::nullopt, pfx);
+            return responseSuccess();
+        }
+        case LanParam::Gateway1:
+        {
+            if (channelCall<getEthProp<bool>>(channel, "DHCP4"))
+            {
+                return responseCommandNotAvailable();
+            }
+            auto gateway = unpackT<stdplus::In4Addr>(req);
+            unpackFinal(req);
+            channelCall<setGatewayProperty<AF_INET>>(channel, gateway);
+            return responseSuccess();
+        }
+        case LanParam::Gateway1MAC:
+        {
+            auto gatewayMAC = unpackT<stdplus::EtherAddr>(req);
+            unpackFinal(req);
+            channelCall<reconfigureGatewayMAC<AF_INET>>(channel, gatewayMAC);
+            return responseSuccess();
+        }
+        case LanParam::VLANId:
+        {
+            uint12_t vlanData;
+            uint3_t rsvd;
+            bool vlanEnable;
+
+            if (req.unpack(vlanData, rsvd, vlanEnable) != 0)
+            {
+                return responseReqDataLenInvalid();
+            }
+            unpackFinal(req);
+
+            if (rsvd)
+            {
+                return responseInvalidFieldRequest();
+            }
+
+            uint16_t vlan = static_cast<uint16_t>(vlanData);
+
+            if (!vlanEnable)
+            {
+                lastDisabledVlan[channel] = vlan;
+                vlan = 0;
+            }
+            else if (vlan == 0 || vlan == VLAN_VALUE_MASK)
+            {
+                return responseInvalidFieldRequest();
+            }
+
+            channelCall<reconfigureVLAN>(channel, vlan);
+            return responseSuccess();
+        }
+        case LanParam::CiphersuiteSupport:
+        case LanParam::CiphersuiteEntries:
+        case LanParam::IPFamilySupport:
+        {
+            req.trailingOk = true;
+            return response(ccParamReadOnly);
+        }
+        case LanParam::IPFamilyEnables:
+        {
+            uint8_t enables;
+            if (req.unpack(enables) != 0)
+            {
+                return responseReqDataLenInvalid();
+            }
+            unpackFinal(req);
+            switch (static_cast<IPFamilyEnables>(enables))
+            {
+                case IPFamilyEnables::DualStack:
+                    return responseSuccess();
+                case IPFamilyEnables::IPv4Only:
+                case IPFamilyEnables::IPv6Only:
+                    return response(ccParamNotSupported);
+            }
+            return response(ccParamNotSupported);
+        }
+        case LanParam::IPv6Status:
+        {
+            req.trailingOk = true;
+            return response(ccParamReadOnly);
+        }
+        case LanParam::IPv6StaticAddresses:
+        {
+            uint8_t set;
+            uint7_t rsvd;
+            bool enabled;
+            uint8_t prefix;
+            uint8_t status;
+            if (req.unpack(set, rsvd, enabled) != 0)
+            {
+                return responseReqDataLenInvalid();
+            }
+            auto ip = unpackT<stdplus::In6Addr>(req);
+            if (req.unpack(prefix, status) != 0)
+            {
+                return responseReqDataLenInvalid();
+            }
+            unpackFinal(req);
+            if (rsvd)
+            {
+                return responseInvalidFieldRequest();
+            }
+            if (enabled)
+            {
+                if (prefix < MIN_IPV6_PREFIX_LENGTH ||
+                    prefix > MAX_IPV6_PREFIX_LENGTH)
+                {
+                    return responseParmOutOfRange();
+                }
+                channelCall<reconfigureIfAddr6>(channel, set, ip, prefix);
+            }
+            else
+            {
+                channelCall<deconfigureIfAddr6>(channel, set);
+            }
+            return responseSuccess();
+        }
+        case LanParam::IPv6DynamicAddresses:
+        {
+            req.trailingOk = true;
+            return response(ccParamReadOnly);
+        }
+        case LanParam::IPv6RouterControl:
+        {
+            std::bitset<8> control;
+            constexpr uint8_t reservedRACCBits = 0xfc;
+            if (req.unpack(control) != 0)
+            {
+                return responseReqDataLenInvalid();
+            }
+            unpackFinal(req);
+            if (std::bitset<8> expected(
+                    control & std::bitset<8>(reservedRACCBits));
+                expected.any())
+            {
+                return response(ccParamNotSupported);
+            }
+
+            bool enableRA = control[IPv6RouterControlFlag::Dynamic];
+            channelCall<setEthProp<bool>>(channel, "IPv6AcceptRA", enableRA);
+            channelCall<setEthProp<bool>>(channel, "DHCP6", enableRA);
+            return responseSuccess();
+        }
+        case LanParam::IPv6StaticRouter1IP:
+        {
+            auto gateway = unpackT<stdplus::In6Addr>(req);
+            unpackFinal(req);
+            channelCall<setGatewayProperty<AF_INET6>>(channel, gateway);
+            return responseSuccess();
+        }
+        case LanParam::IPv6StaticRouter1MAC:
+        {
+            auto mac = unpackT<stdplus::EtherAddr>(req);
+            unpackFinal(req);
+            channelCall<reconfigureGatewayMAC<AF_INET6>>(channel, mac);
+            return responseSuccess();
+        }
+        case LanParam::IPv6StaticRouter1PrefixLength:
+        {
+            uint8_t prefix;
+            if (req.unpack(prefix) != 0)
+            {
+                return responseReqDataLenInvalid();
+            }
+            unpackFinal(req);
+            if (prefix != 0)
+            {
+                return responseInvalidFieldRequest();
+            }
+            return responseSuccess();
+        }
+        case LanParam::IPv6StaticRouter1PrefixValue:
+        {
+            unpackT<stdplus::In6Addr>(req);
+            unpackFinal(req);
+            // Accept any prefix value since our prefix length has to be 0
+            return responseSuccess();
+        }
+        case LanParam::cipherSuitePrivilegeLevels:
+        {
+            uint8_t rsvd;
+            std::array<uint4_t, ipmi::maxCSRecords> cipherSuitePrivs;
+
+            if (req.unpack(rsvd, cipherSuitePrivs))
+            {
+                return responseReqDataLenInvalid();
+            }
+            unpackFinal(req);
+
+            if (rsvd)
+            {
+                return responseInvalidFieldRequest();
+            }
+
+            uint8_t resp =
+                getCipherConfigObject(csPrivFileName, csPrivDefaultFileName)
+                    .setCSPrivilegeLevels(channel, cipherSuitePrivs);
+            if (!resp)
+            {
+                return responseSuccess();
+            }
+            else
+            {
+                req.trailingOk = true;
+                return response(resp);
+            }
+        }
+    }
+
+    if (parameter >= oemCmdStart)
+    {
+        return setLanOem(channel, parameter, req);
+    }
+
+    req.trailingOk = true;
+    return response(ccParamNotSupported);
+}
+
+RspType<> setLan(Context::ptr ctx, uint4_t channelBits, uint4_t reserved1,
+                 uint8_t parameter, message::Payload& req)
+{
+    try
+    {
+        return setLanInt(ctx, channelBits, reserved1, parameter, req);
+    }
+    catch (ipmi::Cc cc)
+    {
+        return response(cc);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        if (std::string_view{InvalidArgument::errName} == e.name())
+        {
+            return responseInvalidFieldRequest();
+        }
+        throw;
+    }
+}
+
+RspType<message::Payload> getLan(Context::ptr ctx, uint4_t channelBits,
+                                 uint3_t reserved, bool revOnly,
+                                 uint8_t parameter, uint8_t set, uint8_t block)
+{
+    message::Payload ret;
+    constexpr uint8_t current_revision = 0x11;
+    ret.pack(current_revision);
+
+    if (revOnly)
+    {
+        return responseSuccess(std::move(ret));
+    }
+
+    const uint8_t channel = convertCurrentChannelNum(
+        static_cast<uint8_t>(channelBits), ctx->channel);
+    if (reserved || !isValidChannel(channel))
+    {
+        lg2::error("Get Lan - Invalid field in request");
+        return responseInvalidFieldRequest();
+    }
+
+    if (!isLanChannel(channel).value_or(false))
+    {
+        lg2::error("Set Lan - Not a LAN channel");
+        return responseInvalidFieldRequest();
+    }
+
+    static std::vector<uint8_t> cipherList;
+    static bool listInit = false;
+    if (!listInit)
+    {
+        try
+        {
+            cipherList = cipher::getCipherList();
+            listInit = true;
+        }
+        catch (const std::exception& e)
+        {}
+    }
+
+    switch (static_cast<LanParam>(parameter))
+    {
+        case LanParam::SetStatus:
+        {
+            SetStatus status;
+            try
+            {
+                status = setStatus.at(channel);
+            }
+            catch (const std::out_of_range&)
+            {
+                status = SetStatus::Complete;
+            }
+            ret.pack(types::enum_cast<uint2_t>(status), uint6_t{});
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::AuthSupport:
+        {
+            std::bitset<6> support;
+            ret.pack(support, uint2_t{});
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::AuthEnables:
+        {
+            std::bitset<6> enables;
+            ret.pack(enables, uint2_t{}); // Callback
+            ret.pack(enables, uint2_t{}); // User
+            ret.pack(enables, uint2_t{}); // Operator
+            ret.pack(enables, uint2_t{}); // Admin
+            ret.pack(enables, uint2_t{}); // OEM
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IP:
+        {
+            auto ifaddr = channelCall<getIfAddr4>(channel);
+            stdplus::In4Addr addr{};
+            if (ifaddr)
+            {
+                addr = ifaddr->address;
+            }
+            ret.pack(stdplus::raw::asView<char>(addr));
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IPSrc:
+        {
+            auto src = channelCall<getEthProp<bool>>(channel, "DHCP4")
+                           ? IPSrc::DHCP
+                           : IPSrc::Static;
+            ret.pack(types::enum_cast<uint4_t>(src), uint4_t{});
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::MAC:
+        {
+            auto mac = channelCall<getMACProperty>(channel);
+            ret.pack(stdplus::raw::asView<char>(mac));
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::SubnetMask:
+        {
+            auto ifaddr = channelCall<getIfAddr4>(channel);
+            uint8_t prefix = AddrFamily<AF_INET>::defaultPrefix;
+            if (ifaddr)
+            {
+                prefix = ifaddr->prefix;
+            }
+            auto netmask = stdplus::pfxToMask<stdplus::In4Addr>(prefix);
+            ret.pack(stdplus::raw::asView<char>(netmask));
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::Gateway1:
+        {
+            auto gateway = channelCall<getGatewayProperty<AF_INET>>(channel);
+            ret.pack(stdplus::raw::asView<char>(
+                gateway.value_or(stdplus::In4Addr{})));
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::Gateway1MAC:
+        {
+            stdplus::EtherAddr mac{};
+            auto neighbor = channelCall<getGatewayNeighbor<AF_INET>>(channel);
+            if (neighbor)
+            {
+                mac = neighbor->mac;
+            }
+            ret.pack(stdplus::raw::asView<char>(mac));
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::VLANId:
+        {
+            uint16_t vlan = channelCall<getVLANProperty>(channel);
+            if (vlan != 0)
+            {
+                vlan |= VLAN_ENABLE_FLAG;
+            }
+            else
+            {
+                vlan = lastDisabledVlan[channel];
+            }
+            ret.pack(vlan);
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::CiphersuiteSupport:
+        {
+            if (getChannelSessionSupport(channel) ==
+                EChannelSessSupported::none)
+            {
+                return responseInvalidFieldRequest();
+            }
+            if (!listInit)
+            {
+                return responseUnspecifiedError();
+            }
+            ret.pack(static_cast<uint8_t>(cipherList.size() - 1));
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::CiphersuiteEntries:
+        {
+            if (getChannelSessionSupport(channel) ==
+                EChannelSessSupported::none)
+            {
+                return responseInvalidFieldRequest();
+            }
+            if (!listInit)
+            {
+                return responseUnspecifiedError();
+            }
+            ret.pack(cipherList);
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IPFamilySupport:
+        {
+            std::bitset<8> support;
+            support[IPFamilySupportFlag::IPv6Only] = 0;
+            support[IPFamilySupportFlag::DualStack] = 1;
+            support[IPFamilySupportFlag::IPv6Alerts] = 1;
+            ret.pack(support);
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IPFamilyEnables:
+        {
+            ret.pack(static_cast<uint8_t>(IPFamilyEnables::DualStack));
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IPv6Status:
+        {
+            ret.pack(MAX_IPV6_STATIC_ADDRESSES);
+            ret.pack(MAX_IPV6_DYNAMIC_ADDRESSES);
+            std::bitset<8> support;
+            support[IPv6StatusFlag::DHCP] = 1;
+            support[IPv6StatusFlag::SLAAC] = 1;
+            ret.pack(support);
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IPv6StaticAddresses:
+        {
+            if (set >= MAX_IPV6_STATIC_ADDRESSES)
+            {
+                return responseParmOutOfRange();
+            }
+            getLanIPv6Address(ret, channel, set, originsV6Static);
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IPv6DynamicAddresses:
+        {
+            if (set >= MAX_IPV6_DYNAMIC_ADDRESSES)
+            {
+                return responseParmOutOfRange();
+            }
+            getLanIPv6Address(ret, channel, set, originsV6Dynamic);
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IPv6RouterControl:
+        {
+            std::bitset<8> control;
+            control[IPv6RouterControlFlag::Dynamic] =
+                channelCall<getEthProp<bool>>(channel, "IPv6AcceptRA");
+            control[IPv6RouterControlFlag::Static] = 1;
+            ret.pack(control);
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IPv6StaticRouter1IP:
+        {
+            stdplus::In6Addr gateway{};
+            if (!channelCall<getEthProp<bool>>(channel, "IPv6AcceptRA"))
+            {
+                gateway =
+                    channelCall<getGatewayProperty<AF_INET6>>(channel).value_or(
+                        stdplus::In6Addr{});
+            }
+            ret.pack(stdplus::raw::asView<char>(gateway));
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IPv6StaticRouter1MAC:
+        {
+            stdplus::EtherAddr mac{};
+            auto neighbor = channelCall<getGatewayNeighbor<AF_INET6>>(channel);
+            if (neighbor)
+            {
+                mac = neighbor->mac;
+            }
+            ret.pack(stdplus::raw::asView<char>(mac));
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IPv6StaticRouter1PrefixLength:
+        {
+            ret.pack(uint8_t{0});
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IPv6StaticRouter1PrefixValue:
+        {
+            ret.pack(stdplus::raw::asView<char>(stdplus::In6Addr{}));
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::cipherSuitePrivilegeLevels:
+        {
+            std::array<uint4_t, ipmi::maxCSRecords> csPrivilegeLevels;
+
+            uint8_t resp =
+                getCipherConfigObject(csPrivFileName, csPrivDefaultFileName)
+                    .getCSPrivilegeLevels(channel, csPrivilegeLevels);
+            if (!resp)
+            {
+                constexpr uint8_t reserved1 = 0x00;
+                ret.pack(reserved1, csPrivilegeLevels);
+                return responseSuccess(std::move(ret));
+            }
+            else
+            {
+                return response(resp);
+            }
+        }
+    }
+
+    if (parameter >= oemCmdStart)
+    {
+        return getLanOem(channel, parameter, set, block);
+    }
+
+    return response(ccParamNotSupported);
+}
+
+constexpr const char* solInterface = "xyz.openbmc_project.Ipmi.SOL";
+constexpr const char* solPath = "/xyz/openbmc_project/ipmi/sol/";
+constexpr const uint16_t solDefaultPort = 623;
+
+RspType<> setSolConfParams(Context::ptr ctx, uint4_t channelBits,
+                           uint4_t /*reserved*/, uint8_t parameter,
+                           message::Payload& req)
+{
+    const uint8_t channel = convertCurrentChannelNum(
+        static_cast<uint8_t>(channelBits), ctx->channel);
+
+    if (!isValidChannel(channel))
+    {
+        lg2::error("Set Sol Config - Invalid channel in request");
+        return responseInvalidFieldRequest();
+    }
+
+    std::string solService{};
+    std::string solPathWitheEthName = solPath + ipmi::getChannelName(channel);
+
+    if (ipmi::getService(ctx, solInterface, solPathWitheEthName, solService))
+    {
+        lg2::error("Set Sol Config - Invalid solInterface, service: {SERVICE}, "
+                   "object path: {OBJPATH}, interface: {INTERFACE}",
+                   "SERVICE", solService, "OBJPATH", solPathWitheEthName,
+                   "INTERFACE", solInterface);
+        return responseInvalidFieldRequest();
+    }
+
+    switch (static_cast<SolConfParam>(parameter))
+    {
+        case SolConfParam::Progress:
+        {
+            uint8_t progress;
+            if (req.unpack(progress) != 0 || !req.fullyUnpacked())
+            {
+                return responseReqDataLenInvalid();
+            }
+
+            if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName,
+                                      solInterface, "Progress", progress))
+            {
+                return responseUnspecifiedError();
+            }
+            break;
+        }
+        case SolConfParam::Enable:
+        {
+            bool enable;
+            uint7_t reserved2;
+
+            if (req.unpack(enable, reserved2) != 0 || !req.fullyUnpacked())
+            {
+                return responseReqDataLenInvalid();
+            }
+
+            if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName,
+                                      solInterface, "Enable", enable))
+            {
+                return responseUnspecifiedError();
+            }
+            break;
+        }
+        case SolConfParam::Authentication:
+        {
+            uint4_t privilegeBits{};
+            uint2_t reserved2{};
+            bool forceAuth = false;
+            bool forceEncrypt = false;
+
+            if (req.unpack(privilegeBits, reserved2, forceAuth, forceEncrypt) !=
+                    0 ||
+                !req.fullyUnpacked())
+            {
+                return responseReqDataLenInvalid();
+            }
+
+            uint8_t privilege = static_cast<uint8_t>(privilegeBits);
+            if (privilege < static_cast<uint8_t>(Privilege::User) ||
+                privilege > static_cast<uint8_t>(Privilege::Oem))
+            {
+                return ipmi::responseInvalidFieldRequest();
+            }
+
+            if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName,
+                                      solInterface, "Privilege", privilege))
+            {
+                return responseUnspecifiedError();
+            }
+
+            if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName,
+                                      solInterface, "ForceEncryption",
+                                      forceEncrypt))
+            {
+                return responseUnspecifiedError();
+            }
+
+            if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName,
+                                      solInterface, "ForceAuthentication",
+                                      forceAuth))
+            {
+                return responseUnspecifiedError();
+            }
+            break;
+        }
+        case SolConfParam::Accumulate:
+        {
+            uint8_t interval;
+            uint8_t threshold;
+            if (req.unpack(interval, threshold) != 0 || !req.fullyUnpacked())
+            {
+                return responseReqDataLenInvalid();
+            }
+
+            if (threshold == 0)
+            {
+                return responseInvalidFieldRequest();
+            }
+
+            if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName,
+                                      solInterface, "AccumulateIntervalMS",
+                                      interval))
+            {
+                return responseUnspecifiedError();
+            }
+
+            if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName,
+                                      solInterface, "Threshold", threshold))
+            {
+                return responseUnspecifiedError();
+            }
+            break;
+        }
+        case SolConfParam::Retry:
+        {
+            uint3_t countBits;
+            uint5_t reserved2;
+            uint8_t interval;
+
+            if (req.unpack(countBits, reserved2, interval) != 0 ||
+                !req.fullyUnpacked())
+            {
+                return responseReqDataLenInvalid();
+            }
+
+            uint8_t count = static_cast<uint8_t>(countBits);
+            if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName,
+                                      solInterface, "RetryCount", count))
+            {
+                return responseUnspecifiedError();
+            }
+
+            if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName,
+                                      solInterface, "RetryIntervalMS",
+                                      interval))
+            {
+                return responseUnspecifiedError();
+            }
+            break;
+        }
+        case SolConfParam::Port:
+        {
+            return response(ipmiCCWriteReadParameter);
+        }
+        case SolConfParam::NonVbitrate:
+        case SolConfParam::Vbitrate:
+        case SolConfParam::Channel:
+        default:
+            return response(ipmiCCParamNotSupported);
+    }
+    return responseSuccess();
+}
+
+RspType<message::Payload> getSolConfParams(
+    Context::ptr ctx, uint4_t channelBits, uint3_t /*reserved*/, bool revOnly,
+    uint8_t parameter, uint8_t /*set*/, uint8_t /*block*/)
+{
+    message::Payload ret;
+    constexpr uint8_t current_revision = 0x11;
+    ret.pack(current_revision);
+    if (revOnly)
+    {
+        return responseSuccess(std::move(ret));
+    }
+
+    const uint8_t channel = convertCurrentChannelNum(
+        static_cast<uint8_t>(channelBits), ctx->channel);
+
+    if (!isValidChannel(channel))
+    {
+        lg2::error("Get Sol Config - Invalid channel in request");
+        return responseInvalidFieldRequest();
+    }
+
+    std::string solService{};
+    std::string solPathWitheEthName = solPath + ipmi::getChannelName(channel);
+
+    if (ipmi::getService(ctx, solInterface, solPathWitheEthName, solService))
+    {
+        lg2::error("Set Sol Config - Invalid solInterface, service: {SERVICE}, "
+                   "object path: {OBJPATH}, interface: {INTERFACE}",
+                   "SERVICE", solService, "OBJPATH", solPathWitheEthName,
+                   "INTERFACE", solInterface);
+        return responseInvalidFieldRequest();
+    }
+
+    switch (static_cast<SolConfParam>(parameter))
+    {
+        case SolConfParam::Progress:
+        {
+            uint8_t progress;
+            if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName,
+                                      solInterface, "Progress", progress))
+            {
+                return responseUnspecifiedError();
+            }
+            ret.pack(progress);
+            return responseSuccess(std::move(ret));
+        }
+        case SolConfParam::Enable:
+        {
+            bool enable{};
+            if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName,
+                                      solInterface, "Enable", enable))
+            {
+                return responseUnspecifiedError();
+            }
+            ret.pack(enable, uint7_t{});
+            return responseSuccess(std::move(ret));
+        }
+        case SolConfParam::Authentication:
+        {
+            // 4bits, cast when pack
+            uint8_t privilege;
+            bool forceAuth = false;
+            bool forceEncrypt = false;
+
+            if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName,
+                                      solInterface, "Privilege", privilege))
+            {
+                return responseUnspecifiedError();
+            }
+
+            if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName,
+                                      solInterface, "ForceAuthentication",
+                                      forceAuth))
+            {
+                return responseUnspecifiedError();
+            }
+
+            if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName,
+                                      solInterface, "ForceEncryption",
+                                      forceEncrypt))
+            {
+                return responseUnspecifiedError();
+            }
+            ret.pack(uint4_t{privilege}, uint2_t{}, forceAuth, forceEncrypt);
+            return responseSuccess(std::move(ret));
+        }
+        case SolConfParam::Accumulate:
+        {
+            uint8_t interval{}, threshold{};
+
+            if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName,
+                                      solInterface, "AccumulateIntervalMS",
+                                      interval))
+            {
+                return responseUnspecifiedError();
+            }
+
+            if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName,
+                                      solInterface, "Threshold", threshold))
+            {
+                return responseUnspecifiedError();
+            }
+            ret.pack(interval, threshold);
+            return responseSuccess(std::move(ret));
+        }
+        case SolConfParam::Retry:
+        {
+            // 3bits, cast when cast
+            uint8_t count{};
+            uint8_t interval{};
+
+            if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName,
+                                      solInterface, "RetryCount", count))
+            {
+                return responseUnspecifiedError();
+            }
+
+            if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName,
+                                      solInterface, "RetryIntervalMS",
+                                      interval))
+            {
+                return responseUnspecifiedError();
+            }
+            ret.pack(uint3_t{count}, uint5_t{}, interval);
+            return responseSuccess(std::move(ret));
+        }
+        case SolConfParam::Port:
+        {
+            auto port = solDefaultPort;
+            ret.pack(static_cast<uint16_t>(port));
+            return responseSuccess(std::move(ret));
+        }
+        case SolConfParam::Channel:
+        {
+            ret.pack(channel);
+            return responseSuccess(std::move(ret));
+        }
+        case SolConfParam::NonVbitrate:
+        {
+            uint64_t baudRate;
+            uint8_t encodedBitRate = 0;
+            if (ipmi::getDbusProperty(
+                    ctx, "xyz.openbmc_project.Console.default",
+                    "/xyz/openbmc_project/console/default",
+                    "xyz.openbmc_project.Console.UART", "Baud", baudRate))
+            {
+                return ipmi::responseUnspecifiedError();
+            }
+            switch (baudRate)
+            {
+                case 9600:
+                    encodedBitRate = 0x06;
+                    break;
+                case 19200:
+                    encodedBitRate = 0x07;
+                    break;
+                case 38400:
+                    encodedBitRate = 0x08;
+                    break;
+                case 57600:
+                    encodedBitRate = 0x09;
+                    break;
+                case 115200:
+                    encodedBitRate = 0x0a;
+                    break;
+                default:
+                    break;
+            }
+            ret.pack(encodedBitRate);
+            return responseSuccess(std::move(ret));
+        }
+        case SolConfParam::Vbitrate:
+        default:
+            return response(ipmiCCParamNotSupported);
+    }
+
+    return response(ccParamNotSupported);
+}
+
+} // namespace transport
+} // namespace ipmi
+
+void registerNetFnTransportFunctions() __attribute__((constructor));
+
+void registerNetFnTransportFunctions()
+{
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnTransport,
+                          ipmi::transport::cmdSetLanConfigParameters,
+                          ipmi::Privilege::Admin, ipmi::transport::setLan);
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnTransport,
+                          ipmi::transport::cmdGetLanConfigParameters,
+                          ipmi::Privilege::Operator, ipmi::transport::getLan);
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnTransport,
+                          ipmi::transport::cmdSetSolConfigParameters,
+                          ipmi::Privilege::Admin,
+                          ipmi::transport::setSolConfParams);
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnTransport,
+                          ipmi::transport::cmdGetSolConfigParameters,
+                          ipmi::Privilege::User,
+                          ipmi::transport::getSolConfParams);
+}
diff --git a/transporthandler.hpp b/transporthandler.hpp
new file mode 100644
index 0000000..de1bb66
--- /dev/null
+++ b/transporthandler.hpp
@@ -0,0 +1,452 @@
+#pragma once
+
+#include "app/channel.hpp"
+#include "transportconstants.hpp"
+#include "user_channel/cipher_mgmt.hpp"
+
+#include <ipmid/api-types.hpp>
+#include <ipmid/api.hpp>
+#include <ipmid/message.hpp>
+#include <ipmid/message/types.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/exception.hpp>
+#include <stdplus/net/addr/ether.hpp>
+#include <stdplus/net/addr/ip.hpp>
+#include <stdplus/str/conv.hpp>
+#include <stdplus/zstring_view.hpp>
+#include <user_channel/channel_layer.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/Network/EthernetInterface/server.hpp>
+#include <xyz/openbmc_project/Network/IP/server.hpp>
+#include <xyz/openbmc_project/Network/Neighbor/server.hpp>
+
+#include <cinttypes>
+#include <functional>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+
+namespace ipmi
+{
+namespace transport
+{
+
+/** @brief The dbus parameters for the interface corresponding to a channel
+ *         This helps reduce the number of mapper lookups we need for each
+ *         query and simplifies finding the VLAN interface if needed.
+ */
+struct ChannelParams
+{
+    /** @brief The channel ID */
+    int id;
+    /** @brief channel name for the interface */
+    std::string ifname;
+    /** @brief Name of the service on the bus */
+    std::string service;
+    /** @brief Lower level adapter path that is guaranteed to not be a VLAN */
+    std::string ifPath;
+    /** @brief Logical adapter path used for address assignment */
+    std::string logicalPath;
+};
+
+/** @brief Determines the ethernet interface name corresponding to a channel
+ *         Tries to map a VLAN object first so that the address information
+ *         is accurate. Otherwise it gets the standard ethernet interface.
+ *
+ *  @param[in] bus     - The bus object used for lookups
+ *  @param[in] channel - The channel id corresponding to an ethernet interface
+ *  @return Ethernet interface service and object path if it exists
+ */
+std::optional<ChannelParams> maybeGetChannelParams(sdbusplus::bus_t& bus,
+                                                   uint8_t channel);
+
+/** @brief A trivial helper around maybeGetChannelParams() that throws an
+ *         exception when it is unable to acquire parameters for the channel.
+ *
+ *  @param[in] bus     - The bus object used for lookups
+ *  @param[in] channel - The channel id corresponding to an ethernet interface
+ *  @return Ethernet interface service and object path
+ */
+ChannelParams getChannelParams(sdbusplus::bus_t& bus, uint8_t channel);
+
+/** @brief Trivializes using parameter getter functions by providing a bus
+ *         and channel parameters automatically.
+ *
+ *  @param[in] channel - The channel id corresponding to an ethernet interface
+ *  ...
+ */
+template <auto func, typename... Args>
+auto channelCall(uint8_t channel, Args&&... args)
+{
+    sdbusplus::bus_t bus(ipmid_get_sd_bus_connection());
+    auto params = getChannelParams(bus, channel);
+    return std::invoke(func, bus, params, std::forward<Args>(args)...);
+}
+
+/** @brief Generic paramters for different address families */
+template <int family>
+struct AddrFamily
+{};
+
+/** @brief Parameter specialization for IPv4 */
+template <>
+struct AddrFamily<AF_INET>
+{
+    using addr = stdplus::In4Addr;
+    static constexpr auto protocol =
+        sdbusplus::server::xyz::openbmc_project::network::IP::Protocol::IPv4;
+    static constexpr size_t maxStrLen = INET6_ADDRSTRLEN;
+    static constexpr uint8_t defaultPrefix = 32;
+    static constexpr char propertyGateway[] = "DefaultGateway";
+};
+
+/** @brief Parameter specialization for IPv6 */
+template <>
+struct AddrFamily<AF_INET6>
+{
+    using addr = stdplus::In6Addr;
+    static constexpr auto protocol =
+        sdbusplus::server::xyz::openbmc_project::network::IP::Protocol::IPv6;
+    static constexpr size_t maxStrLen = INET6_ADDRSTRLEN;
+    static constexpr uint8_t defaultPrefix = 128;
+    static constexpr char propertyGateway[] = "DefaultGateway6";
+};
+
+/** @brief Interface Neighbor configuration parameters */
+template <int family>
+struct IfNeigh
+{
+    std::string path;
+    typename AddrFamily<family>::addr ip;
+    stdplus::EtherAddr mac;
+};
+
+/** @brief Interface IP Address configuration parameters */
+template <int family>
+struct IfAddr
+{
+    std::string path;
+    typename AddrFamily<family>::addr address;
+    sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin origin;
+    uint8_t prefix;
+};
+
+/** @brief Valid address origins for IPv6 */
+static inline const std::unordered_set<
+    sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin>
+    originsV6Static = {sdbusplus::server::xyz::openbmc_project::network::IP::
+                           AddressOrigin::Static};
+static inline const std::unordered_set<
+    sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin>
+    originsV6Dynamic = {
+        sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin::
+            DHCP,
+        sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin::
+            SLAAC,
+};
+
+/** @brief A lazy lookup mechanism for iterating over object properties stored
+ *         in DBus. This will only perform the object lookup when needed, and
+ *         retains a cache of previous lookups to speed up future iterations.
+ */
+class ObjectLookupCache
+{
+  public:
+    using PropertiesCache = std::unordered_map<std::string, PropertyMap>;
+
+    /** @brief Creates a new ObjectLookupCache for the interface on the bus
+     *         NOTE: The inputs to this object must outlive the object since
+     *         they are only referenced by it.
+     *
+     *  @param[in] bus    - The bus object used for lookups
+     *  @param[in] params - The parameters for the channel
+     *  @param[in] intf   - The interface we are looking up
+     */
+    ObjectLookupCache(sdbusplus::bus_t& bus, const ChannelParams& params,
+                      const char* intf) :
+        bus(bus), params(params), intf(intf),
+        objs(getAllDbusObjects(bus, params.logicalPath, intf, ""))
+    {}
+
+    class iterator : public ObjectTree::const_iterator
+    {
+      public:
+        using value_type = PropertiesCache::value_type;
+
+        iterator(ObjectTree::const_iterator it, ObjectLookupCache& container) :
+            ObjectTree::const_iterator(it), container(container),
+            ret(container.cache.end())
+        {}
+        value_type& operator*()
+        {
+            ret = container.get(ObjectTree::const_iterator::operator*().first);
+            return *ret;
+        }
+        value_type* operator->()
+        {
+            return &operator*();
+        }
+
+      private:
+        ObjectLookupCache& container;
+        PropertiesCache::iterator ret;
+    };
+
+    iterator begin() noexcept
+    {
+        return iterator(objs.begin(), *this);
+    }
+
+    iterator end() noexcept
+    {
+        return iterator(objs.end(), *this);
+    }
+
+  private:
+    sdbusplus::bus_t& bus;
+    const ChannelParams& params;
+    const char* const intf;
+    const ObjectTree objs;
+    PropertiesCache cache;
+
+    /** @brief Gets a cached copy of the object properties if possible
+     *         Otherwise performs a query on DBus to look them up
+     *
+     *  @param[in] path - The object path to lookup
+     *  @return An iterator for the specified object path + properties
+     */
+    PropertiesCache::iterator get(const std::string& path)
+    {
+        auto it = cache.find(path);
+        if (it != cache.end())
+        {
+            return it;
+        }
+        auto properties = getAllDbusProperties(bus, params.service, path, intf);
+        return cache.insert({path, std::move(properties)}).first;
+    }
+};
+
+/** @brief Searches the ip object lookup cache for an address matching
+ *         the input parameters. NOTE: The index lacks stability across address
+ *         changes since the network daemon has no notion of stable indicies.
+ *
+ *  @param[in] bus     - The bus object used for lookups
+ *  @param[in] params  - The parameters for the channel
+ *  @param[in] idx     - The index of the desired address on the interface
+ *  @param[in] origins - The allowed origins for the address objects
+ *  @param[in] ips     - The object lookup cache holding all of the address info
+ *  @return The address and prefix if it was found
+ */
+template <int family>
+std::optional<IfAddr<family>> findIfAddr(
+    [[maybe_unused]] sdbusplus::bus_t& bus,
+    [[maybe_unused]] const ChannelParams& params, uint8_t idx,
+    const std::unordered_set<
+        sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin>&
+        origins,
+    ObjectLookupCache& ips)
+{
+    for (const auto& [path, properties] : ips)
+    {
+        try
+        {
+            typename AddrFamily<family>::addr addr;
+            addr = stdplus::fromStr<typename AddrFamily<family>::addr>(
+                std::get<std::string>(properties.at("Address")));
+
+            sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin
+                origin = sdbusplus::server::xyz::openbmc_project::network::IP::
+                    convertAddressOriginFromString(
+                        std::get<std::string>(properties.at("Origin")));
+            if (origins.find(origin) == origins.end())
+            {
+                continue;
+            }
+
+            if (idx > 0)
+            {
+                idx--;
+                continue;
+            }
+
+            IfAddr<family> ifaddr;
+            ifaddr.path = path;
+            ifaddr.address = addr;
+            ifaddr.prefix = std::get<uint8_t>(properties.at("PrefixLength"));
+            ifaddr.origin = origin;
+
+            return ifaddr;
+        }
+        catch (...)
+        {
+            continue;
+        }
+    }
+
+    return std::nullopt;
+}
+/** @brief Trivial helper around findIfAddr that simplifies calls
+ *         for one off lookups. Don't use this if you intend to do multiple
+ *         lookups at a time.
+ *
+ *  @param[in] bus     - The bus object used for lookups
+ *  @param[in] params  - The parameters for the channel
+ *  @param[in] idx     - The index of the desired address on the interface
+ *  @param[in] origins - The allowed origins for the address objects
+ *  @return The address and prefix if it was found
+ */
+template <int family>
+auto getIfAddr(
+    sdbusplus::bus_t& bus, const ChannelParams& params, uint8_t idx,
+    const std::unordered_set<
+        sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin>&
+        origins)
+{
+    ObjectLookupCache ips(bus, params, INTF_IP);
+    return findIfAddr<family>(bus, params, idx, origins, ips);
+}
+
+/** @brief Reconfigures the IPv6 address info configured for the interface
+ *
+ *  @param[in] bus     - The bus object used for lookups
+ *  @param[in] params  - The parameters for the channel
+ *  @param[in] idx     - The address index to operate on
+ *  @param[in] address - The new address
+ *  @param[in] prefix  - The new address prefix
+ */
+void reconfigureIfAddr6(sdbusplus::bus_t& bus, const ChannelParams& params,
+                        uint8_t idx, stdplus::In6Addr address, uint8_t prefix);
+
+/** @brief Retrieves the current gateway for the address family on the system
+ *         NOTE: The gateway is per channel instead of the system wide one.
+ *
+ *  @param[in] bus    - The bus object used for lookups
+ *  @param[in] params - The parameters for the channel
+ *  @return An address representing the gateway address if it exists
+ */
+template <int family>
+std::optional<typename AddrFamily<family>::addr> getGatewayProperty(
+    sdbusplus::bus_t& bus, const ChannelParams& params)
+{
+    auto objPath = "/xyz/openbmc_project/network/" + params.ifname;
+    auto gatewayStr = std::get<std::string>(
+        getDbusProperty(bus, params.service, objPath, INTF_ETHERNET,
+                        AddrFamily<family>::propertyGateway));
+    if (gatewayStr.empty())
+    {
+        return std::nullopt;
+    }
+    return stdplus::fromStr<typename AddrFamily<family>::addr>(gatewayStr);
+}
+
+template <int family>
+std::optional<IfNeigh<family>> findStaticNeighbor(
+    sdbusplus::bus_t&, const ChannelParams&,
+    typename AddrFamily<family>::addr ip, ObjectLookupCache& neighbors)
+{
+    using sdbusplus::server::xyz::openbmc_project::network::Neighbor;
+    const auto state =
+        sdbusplus::common::xyz::openbmc_project::network::convertForMessage(
+            Neighbor::State::Permanent);
+    for (const auto& [path, neighbor] : neighbors)
+    {
+        try
+        {
+            typename AddrFamily<family>::addr neighIP;
+            neighIP = stdplus::fromStr<typename AddrFamily<family>::addr>(
+                std::get<std::string>(neighbor.at("IPAddress")));
+
+            if (neighIP != ip)
+            {
+                continue;
+            }
+            if (state != std::get<std::string>(neighbor.at("State")))
+            {
+                continue;
+            }
+
+            IfNeigh<family> ret;
+            ret.path = path;
+            ret.ip = ip;
+            ret.mac = stdplus::fromStr<stdplus::EtherAddr>(
+                std::get<std::string>(neighbor.at("MACAddress")));
+
+            return ret;
+        }
+        catch (...)
+        {
+            continue;
+        }
+    }
+
+    return std::nullopt;
+}
+
+template <int family>
+void createNeighbor(sdbusplus::bus_t& bus, const ChannelParams& params,
+                    typename AddrFamily<family>::addr address,
+                    stdplus::EtherAddr mac)
+{
+    auto newreq =
+        bus.new_method_call(params.service.c_str(), params.logicalPath.c_str(),
+                            INTF_NEIGHBOR_CREATE_STATIC, "Neighbor");
+    stdplus::ToStrHandle<stdplus::ToStr<stdplus::EtherAddr>> macToStr;
+    stdplus::ToStrHandle<stdplus::ToStr<typename AddrFamily<family>::addr>>
+        addrToStr;
+    newreq.append(addrToStr(address), macToStr(mac));
+    bus.call_noreply(newreq);
+}
+
+/** @brief Deletes the dbus object. Ignores empty objects or objects that are
+ *         missing from the bus.
+ *
+ *  @param[in] bus     - The bus object used for lookups
+ *  @param[in] service - The name of the service
+ *  @param[in] path    - The path of the object to delete
+ */
+void deleteObjectIfExists(sdbusplus::bus_t& bus, const std::string& service,
+                          const std::string& path);
+
+/** @brief Sets the value for the default gateway of the channel
+ *
+ *  @param[in] bus     - The bus object used for lookups
+ *  @param[in] params  - The parameters for the channel
+ *  @param[in] gateway - Gateway address to apply
+ */
+template <int family>
+void setGatewayProperty(sdbusplus::bus_t& bus, const ChannelParams& params,
+                        typename AddrFamily<family>::addr address)
+{
+    // Save the old gateway MAC address if it exists so we can recreate it
+    auto gateway = getGatewayProperty<family>(bus, params);
+    std::optional<IfNeigh<family>> neighbor;
+    if (gateway)
+    {
+        ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR);
+        neighbor = findStaticNeighbor<family>(bus, params, *gateway, neighbors);
+    }
+
+    auto objPath = "/xyz/openbmc_project/network/" + params.ifname;
+    setDbusProperty(bus, params.service, objPath, INTF_ETHERNET,
+                    AddrFamily<family>::propertyGateway,
+                    stdplus::toStr(address));
+
+    // Restore the gateway MAC if we had one
+    if (neighbor)
+    {
+        deleteObjectIfExists(bus, params.service, neighbor->path);
+        createNeighbor<family>(bus, params, address, neighbor->mac);
+    }
+}
+
+} // namespace transport
+} // namespace ipmi
diff --git a/user_channel/channel_layer.cpp b/user_channel/channel_layer.cpp
new file mode 100644
index 0000000..26bc3fa
--- /dev/null
+++ b/user_channel/channel_layer.cpp
@@ -0,0 +1,152 @@
+/*
+// Copyright (c) 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.
+*/
+
+#include "channel_layer.hpp"
+
+#include "channel_mgmt.hpp"
+#include "cipher_mgmt.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+namespace ipmi
+{
+
+bool doesDeviceExist(const uint8_t chNum)
+{
+    // TODO: This is not the reliable way to find the device
+    // associated with ethernet interface as the channel number to
+    // eth association is not done. Need to revisit later
+    struct stat fileStat = {};
+    std::string devName("/sys/class/net/" + getChannelName(chNum));
+
+    if (stat(devName.data(), &fileStat) != 0)
+    {
+        lg2::debug("Ethernet device not found");
+        return false;
+    }
+
+    return true;
+}
+
+bool isValidPrivLimit(const uint8_t privLimit)
+{
+    // Callback privilege is deprecated in OpenBMC
+    // At present, "OEM Privilege" is not used in OpenBMC
+    return ((privLimit > PRIVILEGE_CALLBACK) && (privLimit < PRIVILEGE_OEM));
+}
+
+bool isValidAccessMode(const uint8_t accessMode)
+{
+    return (accessMode <= static_cast<uint8_t>(EChannelAccessMode::shared));
+}
+
+bool isValidChannel(const uint8_t chNum)
+{
+    return getChannelConfigObject().isValidChannel(chNum);
+}
+
+bool isValidAuthType(const uint8_t chNum, const EAuthType& authType)
+{
+    return getChannelConfigObject().isValidAuthType(chNum, authType);
+}
+
+EChannelSessSupported getChannelSessionSupport(const uint8_t chNum)
+{
+    return getChannelConfigObject().getChannelSessionSupport(chNum);
+}
+
+int getChannelActiveSessions(const uint8_t chNum)
+{
+    return getChannelConfigObject().getChannelActiveSessions(chNum);
+}
+
+size_t getChannelMaxTransferSize(uint8_t chNum)
+{
+    return getChannelConfigObject().getChannelMaxTransferSize(chNum);
+}
+
+Cc ipmiChannelInit()
+{
+    getChannelConfigObject();
+    getCipherConfigObject(csPrivFileName, csPrivDefaultFileName);
+    return ccSuccess;
+}
+
+Cc getChannelInfo(const uint8_t chNum, ChannelInfo& chInfo)
+{
+    return getChannelConfigObject().getChannelInfo(chNum, chInfo);
+}
+
+Cc getChannelAccessData(const uint8_t chNum, ChannelAccess& chAccessData)
+{
+    return getChannelConfigObject().getChannelAccessData(chNum, chAccessData);
+}
+
+Cc setChannelAccessData(const uint8_t chNum, const ChannelAccess& chAccessData,
+                        const uint8_t setFlag)
+{
+    return getChannelConfigObject().setChannelAccessData(chNum, chAccessData,
+                                                         setFlag);
+}
+
+Cc getChannelAccessPersistData(const uint8_t chNum, ChannelAccess& chAccessData)
+{
+    return getChannelConfigObject().getChannelAccessPersistData(
+        chNum, chAccessData);
+}
+
+Cc setChannelAccessPersistData(const uint8_t chNum,
+                               const ChannelAccess& chAccessData,
+                               const uint8_t setFlag)
+{
+    return getChannelConfigObject().setChannelAccessPersistData(
+        chNum, chAccessData, setFlag);
+}
+
+Cc getChannelAuthTypeSupported(const uint8_t chNum, uint8_t& authTypeSupported)
+{
+    return getChannelConfigObject().getChannelAuthTypeSupported(
+        chNum, authTypeSupported);
+}
+
+Cc getChannelEnabledAuthType(const uint8_t chNum, const uint8_t priv,
+                             EAuthType& authType)
+{
+    return getChannelConfigObject().getChannelEnabledAuthType(
+        chNum, priv, authType);
+}
+
+std::string getChannelName(const uint8_t chNum)
+{
+    return getChannelConfigObject().getChannelName(chNum);
+}
+
+uint8_t getChannelByName(const std::string& chName)
+{
+    return getChannelConfigObject().getChannelByName(chName);
+}
+
+bool isValidPayloadType(const PayloadType payloadType)
+{
+    return (
+        payloadType == PayloadType::IPMI || payloadType == PayloadType::SOL ||
+        payloadType == PayloadType::OPEN_SESSION_REQUEST ||
+        payloadType == PayloadType::OPEN_SESSION_RESPONSE ||
+        payloadType == PayloadType::RAKP1 ||
+        payloadType == PayloadType::RAKP2 ||
+        payloadType == PayloadType::RAKP3 || payloadType == PayloadType::RAKP4);
+}
+} // namespace ipmi
diff --git a/user_channel/channel_layer.hpp b/user_channel/channel_layer.hpp
new file mode 100644
index 0000000..37d2b90
--- /dev/null
+++ b/user_channel/channel_layer.hpp
@@ -0,0 +1,374 @@
+/*
+// Copyright (c) 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 <openssl/crypto.h>
+
+#include <ipmid/api.hpp>
+
+#include <array>
+#include <string>
+
+namespace ipmi
+{
+
+static constexpr uint8_t maxIpmiChannels = 16;
+static constexpr uint8_t currentChNum = 0xE;
+static constexpr uint8_t invalidChannel = 0xff;
+static constexpr const uint8_t ccActionNotSupportedForChannel = 0x82;
+static constexpr const uint8_t ccAccessModeNotSupportedForChannel = 0x83;
+
+/**
+ * @array of privilege levels
+ */
+extern const std::array<std::string, PRIVILEGE_OEM + 1> privList;
+
+/**
+ * @enum Channel Protocol Type (refer spec sec 6.4)
+ */
+enum class EChannelProtocolType : uint8_t
+{
+    na = 0x00,
+    ipmbV10 = 0x01,
+    icmbV11 = 0x02,
+    reserved = 0x03,
+    ipmiSmbus = 0x04,
+    kcs = 0x05,
+    smic = 0x06,
+    bt10 = 0x07,
+    bt15 = 0x08,
+    tMode = 0x09,
+    oem = 0x1C,
+};
+
+/**
+ * @enum Channel Medium Type (refer spec sec 6.5)
+ */
+enum class EChannelMediumType : uint8_t
+{
+    reserved = 0x00,
+    ipmb = 0x01,
+    icmbV10 = 0x02,
+    icmbV09 = 0x03,
+    lan8032 = 0x04,
+    serial = 0x05,
+    otherLan = 0x06,
+    pciSmbus = 0x07,
+    smbusV11 = 0x08,
+    smbusV20 = 0x09,
+    usbV1x = 0x0A,
+    usbV2x = 0x0B,
+    systemInterface = 0x0C,
+    oem = 0x60,
+    unknown = 0x82,
+};
+
+/**
+ * @enum Channel Session Type (refer spec sec 22.24 -
+ * response data byte 5)
+ */
+enum class EChannelSessSupported : uint8_t
+{
+    none = 0,
+    single = 1,
+    multi = 2,
+    any = 3,
+};
+
+/**
+ * @enum Channel Access Mode (refer spec sec 6.6)
+ */
+enum class EChannelAccessMode : uint8_t
+{
+    disabled = 0,
+    preboot = 1,
+    alwaysAvail = 2,
+    shared = 3,
+};
+
+/**
+ * @enum Authentication Types (refer spec sec 13.6 - IPMI
+ * Session Header)
+ */
+enum class EAuthType : uint8_t
+{
+    none = (1 << 0x0),
+    md2 = (1 << 0x1),
+    md5 = (1 << 0x2),
+    reserved = (1 << 0x3),
+    straightPasswd = (1 << 0x4),
+    oem = (1 << 0x5),
+};
+
+// TODO: Remove duplicate 'PayloadType' definition from netipmid's message.hpp
+// to phosphor-ipmi-host/include
+/**
+ * @enum Payload Types (refer spec sec 13.27.3)
+ */
+enum class PayloadType : uint8_t
+{
+    IPMI = 0x00,
+    SOL = 0x01,
+    OPEN_SESSION_REQUEST = 0x10,
+    OPEN_SESSION_RESPONSE = 0x11,
+    RAKP1 = 0x12,
+    RAKP2 = 0x13,
+    RAKP3 = 0x14,
+    RAKP4 = 0x15,
+    INVALID = 0xFF,
+};
+
+/**
+ * @enum Access mode for channel access set/get (refer spec
+ * sec 22.22 - request byte 2[7:6])
+ */
+typedef enum
+{
+    doNotSet = 0x00,
+    nvData = 0x01,
+    activeData = 0x02,
+    reserved = 0x03,
+} EChannelActionType;
+
+/**
+ * @enum Access set flag to determine changes that has to be updated
+ * in channel access data configuration.
+ */
+enum AccessSetFlag
+{
+    setAccessMode = (1 << 0),
+    setUserAuthEnabled = (1 << 1),
+    setMsgAuthEnabled = (1 << 2),
+    setAlertingEnabled = (1 << 3),
+    setPrivLimit = (1 << 4),
+};
+
+/** @struct ChannelAccess
+ *
+ *  Structure to store channel access related information, defined in IPMI
+ * specification and used in Get / Set channel access (refer spec sec 22.22
+ * & 22.23)
+ */
+struct ChannelAccess
+{
+    uint8_t accessMode;
+    bool userAuthDisabled;
+    bool perMsgAuthDisabled;
+    bool alertingDisabled;
+    uint8_t privLimit;
+};
+
+/** @struct ChannelInfo
+ *
+ *  Structure to store data about channel information, which identifies each
+ *  channel type and information as defined in IPMI specification. (refer spec
+ * sec 22.22 & 22.23)
+ */
+struct ChannelInfo
+{
+    uint8_t mediumType;
+    uint8_t protocolType;
+    uint8_t sessionSupported;
+    bool isIpmi; // Is session IPMI
+    // This is used in Get LAN Configuration parameter.
+    // This holds the supported AuthTypes for a given channel.
+    uint8_t authTypeSupported;
+};
+
+/** @brief determines valid channel
+ *
+ *  @param[in] chNum- channel number
+ *
+ *  @return true if valid, false otherwise
+ */
+bool isValidChannel(const uint8_t chNum);
+
+/** @brief determines whether channel device exist
+ *
+ *  @param[in] chNum - channel number
+ *
+ *  @return true if valid, false otherwise
+ */
+bool doesDeviceExist(const uint8_t chNum);
+
+/** @brief determines whether privilege limit is valid
+ *
+ *  @param[in] privLimit - Privilege limit
+ *
+ *  @return true if valid, false otherwise
+ */
+bool isValidPrivLimit(const uint8_t privLimit);
+
+/** @brief determines whether access mode  is valid
+ *
+ *  @param[in] accessMode - Access mode
+ *
+ *  @return true if valid, false otherwise
+ */
+bool isValidAccessMode(const uint8_t accessMode);
+
+/** @brief determines valid authentication type based on channel number
+ *
+ *  @param[in] chNum - channel number
+ *  @param[in] authType - authentication type
+ *
+ *  @return true if valid, false otherwise
+ */
+bool isValidAuthType(const uint8_t chNum, const EAuthType& authType);
+
+/** @brief determines supported session type of a channel
+ *
+ *  @param[in] chNum - channel number
+ *
+ *  @return EChannelSessSupported - supported session type
+ */
+EChannelSessSupported getChannelSessionSupport(const uint8_t chNum);
+
+/** @brief determines number of active sessions on a channel
+ *
+ *  @param[in] chNum - channel number
+ *
+ *  @return numer of active sessions
+ */
+int getChannelActiveSessions(const uint8_t chNum);
+
+/** @brief determines maximum transfer size for a channel
+ *
+ *  @param[in] chNum - channel number
+ *
+ *  @return maximum bytes that can be transferred on this channel
+ */
+size_t getChannelMaxTransferSize(uint8_t chNum);
+
+/** @brief initializes channel management
+ *
+ *  @return ccSuccess for success, others for failure.
+ */
+Cc ipmiChannelInit();
+
+/** @brief provides channel info details
+ *
+ *  @param[in] chNum - channel number
+ *  @param[out] chInfo - channel info details
+ *
+ *  @return ccSuccess for success, others for failure.
+ */
+Cc getChannelInfo(const uint8_t chNum, ChannelInfo& chInfo);
+
+/** @brief provides channel access data
+ *
+ *  @param[in] chNum - channel number
+ *  @param[out] chAccessData -channel access data
+ *
+ *  @return ccSuccess for success, others for failure.
+ */
+Cc getChannelAccessData(const uint8_t chNum, ChannelAccess& chAccessData);
+
+/** @brief provides function to convert current channel number (0xE)
+ *
+ *  @param[in] chNum - channel number as requested in commands.
+ *  @param[in] devChannel - channel number as provided by device (not 0xE)
+ *
+ *  @return same channel number or proper channel number for current channel
+ * number (0xE).
+ */
+static inline uint8_t convertCurrentChannelNum(const uint8_t chNum,
+                                               const uint8_t devChannel)
+{
+    if (chNum == currentChNum)
+    {
+        return devChannel;
+    }
+    return chNum;
+}
+
+/** @brief to set channel access data
+ *
+ *  @param[in] chNum - channel number
+ *  @param[in] chAccessData - channel access data
+ *  @param[in] setFlag - flag to indicate updatable fields
+ *
+ *  @return ccSuccess for success, others for failure.
+ */
+Cc setChannelAccessData(const uint8_t chNum, const ChannelAccess& chAccessData,
+                        const uint8_t setFlag);
+
+/** @brief to get channel access data persistent data
+ *
+ *  @param[in] chNum - channel number
+ *  @param[out] chAccessData - channel access data
+ *
+ *  @return ccSuccess for success, others for failure.
+ */
+Cc getChannelAccessPersistData(const uint8_t chNum,
+                               ChannelAccess& chAccessData);
+
+/** @brief to set channel access data persistent data
+ *
+ *  @param[in] chNum - channel number
+ *  @param[in] chAccessData - channel access data
+ *  @param[in] setFlag - flag to indicate updatable fields
+ *
+ *  @return ccSuccess for success, others for failure.
+ */
+Cc setChannelAccessPersistData(const uint8_t chNum,
+                               const ChannelAccess& chAccessData,
+                               const uint8_t setFlag);
+
+/** @brief provides supported authentication type for the channel
+ *
+ *  @param[in] chNum - channel number
+ *  @param[out] authTypeSupported - supported authentication type
+ *
+ *  @return ccSuccess for success, others for failure.
+ */
+Cc getChannelAuthTypeSupported(const uint8_t chNum, uint8_t& authTypeSupported);
+
+/** @brief provides enabled authentication type for the channel
+ *
+ *  @param[in] chNum - channel number
+ *  @param[in] priv - privilege
+ *  @param[out] authType - enabled authentication type
+ *
+ *  @return ccSuccess for success, others for failure.
+ */
+Cc getChannelEnabledAuthType(const uint8_t chNum, const uint8_t priv,
+                             EAuthType& authType);
+
+/** @brief Retrieves the LAN channel name from the IPMI channel number
+ *
+ *  @param[in] chNum - IPMI channel number
+ *
+ *  @return the LAN channel name (i.e. eth0)
+ */
+std::string getChannelName(const uint8_t chNum);
+
+/** @brief Retrieves the LAN channel number from the IPMI channel name
+ *
+ *  @param[in] chName - IPMI channel name (i.e. eth0)
+ *
+ *  @return the LAN channel number
+ */
+uint8_t getChannelByName(const std::string& chName);
+
+/** @brief determines whether payload type is valid
+ *
+ *	@param[in] payload type - Payload Type
+ *
+ *	@return true if valid, false otherwise
+ */
+bool isValidPayloadType(const PayloadType payloadType);
+
+} // namespace ipmi
diff --git a/user_channel/channel_mgmt.cpp b/user_channel/channel_mgmt.cpp
new file mode 100644
index 0000000..8594d65
--- /dev/null
+++ b/user_channel/channel_mgmt.cpp
@@ -0,0 +1,1445 @@
+/*
+// Copyright (c) 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.
+*/
+
+#include "channel_mgmt.hpp"
+
+#include "user_layer.hpp"
+
+#include <ifaddrs.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <boost/interprocess/sync/scoped_lock.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/server/object.hpp>
+
+#include <cerrno>
+#include <exception>
+#include <filesystem>
+#include <fstream>
+#include <unordered_map>
+
+namespace ipmi
+{
+
+static constexpr const char* channelAccessDefaultFilename =
+    "/usr/share/ipmi-providers/channel_access.json";
+static constexpr const char* channelConfigDefaultFilename =
+    "/usr/share/ipmi-providers/channel_config.json";
+static constexpr const char* channelNvDataFilename =
+    "/var/lib/ipmi/channel_access_nv.json";
+static constexpr const char* channelVolatileDataFilename =
+    "/run/ipmi/channel_access_volatile.json";
+
+// TODO: Get the service name dynamically..
+static constexpr const char* networkIntfServiceName =
+    "xyz.openbmc_project.Network";
+static constexpr const char* networkIntfObjectBasePath =
+    "/xyz/openbmc_project/network";
+static constexpr const char* networkChConfigIntfName =
+    "xyz.openbmc_project.Channel.ChannelAccess";
+static constexpr const char* privilegePropertyString = "MaxPrivilege";
+static constexpr const char* dBusPropertiesInterface =
+    "org.freedesktop.DBus.Properties";
+static constexpr const char* propertiesChangedSignal = "PropertiesChanged";
+static constexpr const char* interfaceAddedSignal = "InterfacesAdded";
+static constexpr const char* interfaceRemovedSignal = "InterfacesRemoved";
+
+// STRING DEFINES: Should sync with key's in JSON
+static constexpr const char* nameString = "name";
+static constexpr const char* isValidString = "is_valid";
+static constexpr const char* activeSessionsString = "active_sessions";
+static constexpr const char* maxTransferSizeString = "max_transfer_size";
+static constexpr const char* channelInfoString = "channel_info";
+static constexpr const char* mediumTypeString = "medium_type";
+static constexpr const char* protocolTypeString = "protocol_type";
+static constexpr const char* sessionSupportedString = "session_supported";
+static constexpr const char* isIpmiString = "is_ipmi";
+static constexpr const char* isManagementNIC = "is_management_nic";
+static constexpr const char* accessModeString = "access_mode";
+static constexpr const char* userAuthDisabledString = "user_auth_disabled";
+static constexpr const char* perMsgAuthDisabledString = "per_msg_auth_disabled";
+static constexpr const char* alertingDisabledString = "alerting_disabled";
+static constexpr const char* privLimitString = "priv_limit";
+
+// Default values
+static constexpr const char* defaultChannelName = "RESERVED";
+static constexpr const uint8_t defaultMediumType =
+    static_cast<uint8_t>(EChannelMediumType::reserved);
+static constexpr const uint8_t defaultProtocolType =
+    static_cast<uint8_t>(EChannelProtocolType::reserved);
+static constexpr const uint8_t defaultSessionSupported =
+    static_cast<uint8_t>(EChannelSessSupported::none);
+static constexpr const uint8_t defaultAuthType =
+    static_cast<uint8_t>(EAuthType::none);
+static constexpr const bool defaultIsIpmiState = false;
+static constexpr size_t smallChannelSize = 64;
+
+std::unique_ptr<sdbusplus::bus::match_t> chPropertiesSignal
+    __attribute__((init_priority(101)));
+
+std::unique_ptr<sdbusplus::bus::match_t> chInterfaceAddedSignal
+    __attribute__((init_priority(101)));
+
+std::unique_ptr<sdbusplus::bus::match_t> chInterfaceRemovedSignal
+    __attribute__((init_priority(101)));
+
+// String mappings use in JSON config file
+static std::unordered_map<std::string, EChannelMediumType> mediumTypeMap = {
+    {"reserved", EChannelMediumType::reserved},
+    {"ipmb", EChannelMediumType::ipmb},
+    {"icmb-v1.0", EChannelMediumType::icmbV10},
+    {"icmb-v0.9", EChannelMediumType::icmbV09},
+    {"lan-802.3", EChannelMediumType::lan8032},
+    {"serial", EChannelMediumType::serial},
+    {"other-lan", EChannelMediumType::otherLan},
+    {"pci-smbus", EChannelMediumType::pciSmbus},
+    {"smbus-v1.0", EChannelMediumType::smbusV11},
+    {"smbus-v2.0", EChannelMediumType::smbusV20},
+    {"usb-1x", EChannelMediumType::usbV1x},
+    {"usb-2x", EChannelMediumType::usbV2x},
+    {"system-interface", EChannelMediumType::systemInterface},
+    {"oem", EChannelMediumType::oem},
+    {"unknown", EChannelMediumType::unknown}};
+
+static std::unordered_map<EInterfaceIndex, std::string> interfaceMap = {
+    {interfaceKCS, "SMS"},
+    {interfaceLAN1, "eth0"},
+    {interfaceUnknown, "unknown"}};
+
+static std::unordered_map<std::string, EChannelProtocolType> protocolTypeMap = {
+    {"na", EChannelProtocolType::na},
+    {"ipmb-1.0", EChannelProtocolType::ipmbV10},
+    {"icmb-2.0", EChannelProtocolType::icmbV11},
+    {"reserved", EChannelProtocolType::reserved},
+    {"ipmi-smbus", EChannelProtocolType::ipmiSmbus},
+    {"kcs", EChannelProtocolType::kcs},
+    {"smic", EChannelProtocolType::smic},
+    {"bt-10", EChannelProtocolType::bt10},
+    {"bt-15", EChannelProtocolType::bt15},
+    {"tmode", EChannelProtocolType::tMode},
+    {"oem", EChannelProtocolType::oem}};
+
+static std::array<std::string, 4> accessModeList = {
+    "disabled", "pre-boot", "always_available", "shared"};
+
+static std::array<std::string, 4> sessionSupportList = {
+    "session-less", "single-session", "multi-session", "session-based"};
+
+const std::array<std::string, PRIVILEGE_OEM + 1> privList = {
+    "priv-reserved", "priv-callback", "priv-user",
+    "priv-operator", "priv-admin",    "priv-oem"};
+
+std::string ChannelConfig::getChannelName(const uint8_t chNum)
+{
+    if (!isValidChannel(chNum))
+    {
+        lg2::error("Invalid channel number: {CHANNEL_ID}", "CHANNEL_ID", chNum);
+        throw std::invalid_argument("Invalid channel number");
+    }
+
+    return channelData[chNum].chName;
+}
+
+int ChannelConfig::convertToChannelNumberFromChannelName(
+    const std::string& chName)
+{
+    for (const auto& it : channelData)
+    {
+        if (it.chName == chName)
+        {
+            return it.chID;
+        }
+    }
+    lg2::error("Invalid channel name: {CHANNEL}", "CHANNEL", chName);
+    throw std::invalid_argument("Invalid channel name");
+
+    return -1;
+}
+
+std::string ChannelConfig::getChannelNameFromPath(const std::string& path)
+{
+    const size_t length = strlen(networkIntfObjectBasePath);
+    if (((length + 1) >= path.size()) ||
+        path.compare(0, length, networkIntfObjectBasePath))
+    {
+        lg2::error("Invalid object path: {PATH}", "PATH", path);
+        throw std::invalid_argument("Invalid object path");
+    }
+    std::string chName(path, length + 1);
+    return chName;
+}
+
+void ChannelConfig::processChAccessPropChange(
+    const std::string& path, const DbusChObjProperties& chProperties)
+{
+    // Get interface name from path. ex: '/xyz/openbmc_project/network/eth0'
+    std::string chName;
+    try
+    {
+        chName = getChannelNameFromPath(path);
+    }
+    catch (const std::invalid_argument& e)
+    {
+        lg2::error("Exception: {MSG}", "MSG", e.what());
+        return;
+    }
+
+    // Get the MaxPrivilege property value from the signal
+    std::string intfPrivStr;
+    std::string propName;
+    for (const auto& prop : chProperties)
+    {
+        if (prop.first == privilegePropertyString)
+        {
+            propName = privilegePropertyString;
+            intfPrivStr = std::get<std::string>(prop.second);
+            break;
+        }
+    }
+
+    if (propName != privilegePropertyString)
+    {
+        lg2::error("Unknown signal caught.");
+        return;
+    }
+
+    if (intfPrivStr.empty())
+    {
+        lg2::error("Invalid privilege string for intf {INTF}", "INTF", chName);
+        return;
+    }
+
+    uint8_t intfPriv = 0;
+    int chNum;
+    try
+    {
+        intfPriv = static_cast<uint8_t>(convertToPrivLimitIndex(intfPrivStr));
+        chNum = convertToChannelNumberFromChannelName(chName);
+    }
+    catch (const std::invalid_argument& e)
+    {
+        lg2::error("Exception: {MSG}", "MSG", e.what());
+        return;
+    }
+
+    boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+        channelLock{*channelMutex};
+    // skip updating the values, if this property change originated from IPMI.
+    if (signalFlag & (1 << chNum))
+    {
+        signalFlag &= ~(1 << chNum);
+        lg2::debug("Request originated from IPMI so ignoring signal");
+        return;
+    }
+
+    // Update both volatile & Non-volatile, if there is mismatch.
+    // as property change other than IPMI, has to update both volatile &
+    // non-volatile data.
+    checkAndReloadVolatileData();
+    checkAndReloadNVData();
+    if (channelData[chNum].chAccess.chNonVolatileData.privLimit != intfPriv)
+    {
+        // Update NV data
+        channelData[chNum].chAccess.chNonVolatileData.privLimit = intfPriv;
+        if (writeChannelPersistData() != 0)
+        {
+            lg2::error("Failed to update the persist data file");
+            return;
+        }
+
+        // Update Volatile data
+        if (channelData[chNum].chAccess.chVolatileData.privLimit != intfPriv)
+        {
+            channelData[chNum].chAccess.chVolatileData.privLimit = intfPriv;
+            if (writeChannelVolatileData() != 0)
+            {
+                lg2::error("Failed to update the volatile data file");
+                return;
+            }
+        }
+    }
+
+    return;
+}
+
+ChannelConfig& getChannelConfigObject()
+{
+    static ChannelConfig channelConfig;
+    return channelConfig;
+}
+
+ChannelConfig::~ChannelConfig()
+{
+    if (signalHndlrObjectState)
+    {
+        chPropertiesSignal.reset();
+        chInterfaceAddedSignal.reset();
+        chInterfaceRemovedSignal.reset();
+        sigHndlrLock.unlock();
+    }
+}
+
+ChannelConfig::ChannelConfig() : bus(ipmid_get_sd_bus_connection())
+{
+    std::ofstream mutexCleanUpFile;
+    mutexCleanUpFile.open(ipmiChMutexCleanupLockFile,
+                          std::ofstream::out | std::ofstream::app);
+    if (!mutexCleanUpFile.good())
+    {
+        lg2::debug("Unable to open mutex cleanup file");
+        return;
+    }
+    mutexCleanUpFile.close();
+    mutexCleanupLock =
+        boost::interprocess::file_lock(ipmiChMutexCleanupLockFile);
+    if (mutexCleanupLock.try_lock())
+    {
+        boost::interprocess::named_recursive_mutex::remove(ipmiChannelMutex);
+        channelMutex =
+            std::make_unique<boost::interprocess::named_recursive_mutex>(
+                boost::interprocess::open_or_create, ipmiChannelMutex);
+        mutexCleanupLock.lock_sharable();
+    }
+    else
+    {
+        mutexCleanupLock.lock_sharable();
+        channelMutex =
+            std::make_unique<boost::interprocess::named_recursive_mutex>(
+                boost::interprocess::open_or_create, ipmiChannelMutex);
+    }
+
+    initChannelPersistData();
+
+    sigHndlrLock = boost::interprocess::file_lock(channelNvDataFilename);
+    // Register it for single object and single process either netipmid /
+    // host-ipmid
+    if (chPropertiesSignal == nullptr && sigHndlrLock.try_lock())
+    {
+        lg2::debug("Registering channel signal handler.");
+        chPropertiesSignal = std::make_unique<sdbusplus::bus::match_t>(
+            bus,
+            sdbusplus::bus::match::rules::path_namespace(
+                networkIntfObjectBasePath) +
+                sdbusplus::bus::match::rules::type::signal() +
+                sdbusplus::bus::match::rules::member(propertiesChangedSignal) +
+                sdbusplus::bus::match::rules::interface(
+                    dBusPropertiesInterface) +
+                sdbusplus::bus::match::rules::argN(0, networkChConfigIntfName),
+            [&](sdbusplus::message_t& msg) {
+                DbusChObjProperties props;
+                std::string iface;
+                std::string path = msg.get_path();
+                msg.read(iface, props);
+                processChAccessPropChange(path, props);
+            });
+        signalHndlrObjectState = true;
+
+        chInterfaceAddedSignal = std::make_unique<sdbusplus::bus::match_t>(
+            bus,
+            sdbusplus::bus::match::rules::type::signal() +
+                sdbusplus::bus::match::rules::member(interfaceAddedSignal) +
+                sdbusplus::bus::match::rules::argNpath(
+                    0, std::string(networkIntfObjectBasePath) + "/"),
+            [&](sdbusplus::message_t&) { initChannelPersistData(); });
+
+        chInterfaceRemovedSignal = std::make_unique<sdbusplus::bus::match_t>(
+            bus,
+            sdbusplus::bus::match::rules::type::signal() +
+                sdbusplus::bus::match::rules::member(interfaceRemovedSignal) +
+                sdbusplus::bus::match::rules::argNpath(
+                    0, std::string(networkIntfObjectBasePath) + "/"),
+            [&](sdbusplus::message_t&) { initChannelPersistData(); });
+    }
+}
+
+bool ChannelConfig::isValidChannel(const uint8_t chNum)
+{
+    if (chNum >= maxIpmiChannels)
+    {
+        lg2::debug("Invalid channel ID - Out of range");
+        return false;
+    }
+
+    if (channelData[chNum].isChValid == false)
+    {
+        lg2::debug("Channel is not valid");
+    }
+
+    return channelData[chNum].isChValid;
+}
+
+EChannelSessSupported ChannelConfig::getChannelSessionSupport(
+    const uint8_t chNum)
+{
+    EChannelSessSupported chSessSupport =
+        (EChannelSessSupported)channelData[chNum].chInfo.sessionSupported;
+    return chSessSupport;
+}
+
+bool ChannelConfig::isValidAuthType(const uint8_t chNum,
+                                    const EAuthType& authType)
+{
+    if ((authType < EAuthType::md2) || (authType > EAuthType::oem))
+    {
+        lg2::debug("Invalid authentication type");
+        return false;
+    }
+
+    uint8_t authTypeSupported = channelData[chNum].chInfo.authTypeSupported;
+    if (!(authTypeSupported & (1 << static_cast<uint8_t>(authType))))
+    {
+        lg2::debug("Authentication type is not supported.");
+        return false;
+    }
+
+    return true;
+}
+
+int ChannelConfig::getChannelActiveSessions(const uint8_t chNum)
+{
+    // TODO: TEMPORARY FIX
+    // Channels active session count is managed separately
+    // by monitoring channel session which includes LAN and
+    // RAKP layer changes. This will be updated, once the
+    // authentication part is implemented.
+    return channelData[chNum].activeSessCount;
+}
+
+size_t ChannelConfig::getChannelMaxTransferSize(uint8_t chNum)
+{
+    return channelData[chNum].maxTransferSize;
+}
+
+Cc ChannelConfig::getChannelInfo(const uint8_t chNum, ChannelInfo& chInfo)
+{
+    if (!isValidChannel(chNum))
+    {
+        lg2::debug("Invalid channel");
+        return ccInvalidFieldRequest;
+    }
+
+    std::copy_n(reinterpret_cast<uint8_t*>(&channelData[chNum].chInfo),
+                sizeof(channelData[chNum].chInfo),
+                reinterpret_cast<uint8_t*>(&chInfo));
+    return ccSuccess;
+}
+
+Cc ChannelConfig::getChannelAccessData(const uint8_t chNum,
+                                       ChannelAccess& chAccessData)
+{
+    if (!isValidChannel(chNum))
+    {
+        lg2::debug("Invalid channel");
+        return ccInvalidFieldRequest;
+    }
+
+    if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
+    {
+        lg2::debug("Session-less channel doesn't have access data.");
+        return ccActionNotSupportedForChannel;
+    }
+
+    if (checkAndReloadVolatileData() != 0)
+    {
+        return ccUnspecifiedError;
+    }
+
+    std::copy_n(
+        reinterpret_cast<uint8_t*>(&channelData[chNum].chAccess.chVolatileData),
+        sizeof(channelData[chNum].chAccess.chVolatileData),
+        reinterpret_cast<uint8_t*>(&chAccessData));
+
+    return ccSuccess;
+}
+
+Cc ChannelConfig::setChannelAccessData(const uint8_t chNum,
+                                       const ChannelAccess& chAccessData,
+                                       const uint8_t setFlag)
+{
+    if (!isValidChannel(chNum))
+    {
+        lg2::debug("Invalid channel");
+        return ccInvalidFieldRequest;
+    }
+
+    if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
+    {
+        lg2::debug("Session-less channel doesn't have access data.");
+        return ccActionNotSupportedForChannel;
+    }
+
+    if ((setFlag & setAccessMode) &&
+        (!isValidAccessMode(chAccessData.accessMode)))
+    {
+        lg2::debug("Invalid access mode specified");
+        return ccAccessModeNotSupportedForChannel;
+    }
+    if ((setFlag & setPrivLimit) && (!isValidPrivLimit(chAccessData.privLimit)))
+    {
+        lg2::debug("Invalid privilege limit specified");
+        return ccInvalidFieldRequest;
+    }
+
+    boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+        channelLock{*channelMutex};
+
+    if (checkAndReloadVolatileData() != 0)
+    {
+        return ccUnspecifiedError;
+    }
+
+    if (setFlag & setAccessMode)
+    {
+        channelData[chNum].chAccess.chVolatileData.accessMode =
+            chAccessData.accessMode;
+    }
+    if (setFlag & setUserAuthEnabled)
+    {
+        channelData[chNum].chAccess.chVolatileData.userAuthDisabled =
+            chAccessData.userAuthDisabled;
+    }
+    if (setFlag & setMsgAuthEnabled)
+    {
+        channelData[chNum].chAccess.chVolatileData.perMsgAuthDisabled =
+            chAccessData.perMsgAuthDisabled;
+    }
+    if (setFlag & setAlertingEnabled)
+    {
+        channelData[chNum].chAccess.chVolatileData.alertingDisabled =
+            chAccessData.alertingDisabled;
+    }
+    if (setFlag & setPrivLimit)
+    {
+        channelData[chNum].chAccess.chVolatileData.privLimit =
+            chAccessData.privLimit;
+    }
+
+    // Write Volatile data to file
+    if (writeChannelVolatileData() != 0)
+    {
+        lg2::debug("Failed to update the channel volatile data");
+        return ccUnspecifiedError;
+    }
+    return ccSuccess;
+}
+
+Cc ChannelConfig::getChannelAccessPersistData(const uint8_t chNum,
+                                              ChannelAccess& chAccessData)
+{
+    if (!isValidChannel(chNum))
+    {
+        lg2::debug("Invalid channel");
+        return ccInvalidFieldRequest;
+    }
+
+    if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
+    {
+        lg2::debug("Session-less channel doesn't have access data.");
+        return ccActionNotSupportedForChannel;
+    }
+
+    if (checkAndReloadNVData() != 0)
+    {
+        return ccUnspecifiedError;
+    }
+
+    std::copy_n(reinterpret_cast<uint8_t*>(
+                    &channelData[chNum].chAccess.chNonVolatileData),
+                sizeof(channelData[chNum].chAccess.chNonVolatileData),
+                reinterpret_cast<uint8_t*>(&chAccessData));
+
+    return ccSuccess;
+}
+
+Cc ChannelConfig::setChannelAccessPersistData(const uint8_t chNum,
+                                              const ChannelAccess& chAccessData,
+                                              const uint8_t setFlag)
+{
+    if (!isValidChannel(chNum))
+    {
+        lg2::debug("Invalid channel");
+        return ccInvalidFieldRequest;
+    }
+
+    if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
+    {
+        lg2::debug("Session-less channel doesn't have access data.");
+        return ccActionNotSupportedForChannel;
+    }
+
+    if ((setFlag & setAccessMode) &&
+        (!isValidAccessMode(chAccessData.accessMode)))
+    {
+        lg2::debug("Invalid access mode specified");
+        return ccAccessModeNotSupportedForChannel;
+    }
+    if ((setFlag & setPrivLimit) && (!isValidPrivLimit(chAccessData.privLimit)))
+    {
+        lg2::debug("Invalid privilege limit specified");
+        return ccInvalidFieldRequest;
+    }
+
+    boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+        channelLock{*channelMutex};
+
+    if (checkAndReloadNVData() != 0)
+    {
+        return ccUnspecifiedError;
+    }
+
+    if (setFlag & setAccessMode)
+    {
+        channelData[chNum].chAccess.chNonVolatileData.accessMode =
+            chAccessData.accessMode;
+    }
+    if (setFlag & setUserAuthEnabled)
+    {
+        channelData[chNum].chAccess.chNonVolatileData.userAuthDisabled =
+            chAccessData.userAuthDisabled;
+    }
+    if (setFlag & setMsgAuthEnabled)
+    {
+        channelData[chNum].chAccess.chNonVolatileData.perMsgAuthDisabled =
+            chAccessData.perMsgAuthDisabled;
+    }
+    if (setFlag & setAlertingEnabled)
+    {
+        channelData[chNum].chAccess.chNonVolatileData.alertingDisabled =
+            chAccessData.alertingDisabled;
+    }
+    if (setFlag & setPrivLimit)
+    {
+        // Send Update to network channel config interfaces over dbus
+        std::string privStr = convertToPrivLimitString(chAccessData.privLimit);
+        std::string networkIntfObj = std::string(networkIntfObjectBasePath) +
+                                     "/" + channelData[chNum].chName;
+        try
+        {
+            if (0 != setDbusProperty(networkIntfServiceName, networkIntfObj,
+                                     networkChConfigIntfName,
+                                     privilegePropertyString, privStr))
+            {
+                lg2::debug("Network interface '{INTERFACE}' does not exist",
+                           "INTERFACE", channelData[chNum].chName);
+                return ccUnspecifiedError;
+            }
+        }
+        catch (const sdbusplus::exception_t& e)
+        {
+            lg2::error("Exception: Network interface does not exist");
+            return ccInvalidFieldRequest;
+        }
+        signalFlag |= (1 << chNum);
+        channelData[chNum].chAccess.chNonVolatileData.privLimit =
+            chAccessData.privLimit;
+    }
+
+    // Write persistent data to file
+    if (writeChannelPersistData() != 0)
+    {
+        lg2::debug("Failed to update the presist data file");
+        return ccUnspecifiedError;
+    }
+    return ccSuccess;
+}
+
+Cc ChannelConfig::getChannelAuthTypeSupported(const uint8_t chNum,
+                                              uint8_t& authTypeSupported)
+{
+    if (!isValidChannel(chNum))
+    {
+        lg2::debug("Invalid channel");
+        return ccInvalidFieldRequest;
+    }
+
+    authTypeSupported = channelData[chNum].chInfo.authTypeSupported;
+    return ccSuccess;
+}
+
+Cc ChannelConfig::getChannelEnabledAuthType(
+    const uint8_t chNum, const uint8_t priv, EAuthType& authType)
+{
+    if (!isValidChannel(chNum))
+    {
+        lg2::debug("Invalid channel");
+        return ccInvalidFieldRequest;
+    }
+
+    if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
+    {
+        lg2::debug("Sessionless channel doesn't have access data.");
+        return ccInvalidFieldRequest;
+    }
+
+    if (!isValidPrivLimit(priv))
+    {
+        lg2::debug("Invalid privilege specified.");
+        return ccInvalidFieldRequest;
+    }
+
+    // TODO: Hardcoded for now. Need to implement.
+    authType = EAuthType::none;
+
+    return ccSuccess;
+}
+
+std::time_t ChannelConfig::getUpdatedFileTime(const std::string& fileName)
+{
+    struct stat fileStat;
+    if (stat(fileName.c_str(), &fileStat) != 0)
+    {
+        lg2::debug("Error in getting last updated time stamp");
+        return -EIO;
+    }
+    return fileStat.st_mtime;
+}
+
+EChannelAccessMode ChannelConfig::convertToAccessModeIndex(
+    const std::string& mode)
+{
+    auto iter = std::find(accessModeList.begin(), accessModeList.end(), mode);
+    if (iter == accessModeList.end())
+    {
+        lg2::error("Invalid access mode: {MODE_STR}", "MODE_STR", mode);
+        throw std::invalid_argument("Invalid access mode.");
+    }
+
+    return static_cast<EChannelAccessMode>(
+        std::distance(accessModeList.begin(), iter));
+}
+
+std::string ChannelConfig::convertToAccessModeString(const uint8_t value)
+{
+    if (accessModeList.size() <= value)
+    {
+        lg2::error("Invalid access mode: {MODE_IDX}", "MODE_IDX", value);
+        throw std::invalid_argument("Invalid access mode.");
+    }
+
+    return accessModeList.at(value);
+}
+
+CommandPrivilege ChannelConfig::convertToPrivLimitIndex(
+    const std::string& value)
+{
+    auto iter = std::find(privList.begin(), privList.end(), value);
+    if (iter == privList.end())
+    {
+        lg2::error("Invalid privilege: {PRIV_STR}", "PRIV_STR", value);
+        throw std::invalid_argument("Invalid privilege.");
+    }
+
+    return static_cast<CommandPrivilege>(std::distance(privList.begin(), iter));
+}
+
+std::string ChannelConfig::convertToPrivLimitString(const uint8_t value)
+{
+    if (privList.size() <= value)
+    {
+        lg2::error("Invalid privilege: {PRIV_IDX.", "PRIV_IDX", value);
+        throw std::invalid_argument("Invalid privilege.");
+    }
+
+    return privList.at(value);
+}
+
+EChannelSessSupported ChannelConfig::convertToSessionSupportIndex(
+    const std::string& value)
+{
+    auto iter =
+        std::find(sessionSupportList.begin(), sessionSupportList.end(), value);
+    if (iter == sessionSupportList.end())
+    {
+        lg2::error("Invalid session supported: {SESS_STR}", "SESS_STR", value);
+        throw std::invalid_argument("Invalid session supported.");
+    }
+
+    return static_cast<EChannelSessSupported>(
+        std::distance(sessionSupportList.begin(), iter));
+}
+
+EChannelMediumType ChannelConfig::convertToMediumTypeIndex(
+    const std::string& value)
+{
+    std::unordered_map<std::string, EChannelMediumType>::iterator it =
+        mediumTypeMap.find(value);
+    if (it == mediumTypeMap.end())
+    {
+        lg2::error("Invalid medium type: {MEDIUM_STR}", "MEDIUM_STR", value);
+        throw std::invalid_argument("Invalid medium type.");
+    }
+
+    return static_cast<EChannelMediumType>(it->second);
+}
+
+EChannelProtocolType ChannelConfig::convertToProtocolTypeIndex(
+    const std::string& value)
+{
+    std::unordered_map<std::string, EChannelProtocolType>::iterator it =
+        protocolTypeMap.find(value);
+    if (it == protocolTypeMap.end())
+    {
+        lg2::error("Invalid protocol type: {PROTO_STR}", "PROTO_STR", value);
+        throw std::invalid_argument("Invalid protocol type.");
+    }
+
+    return static_cast<EChannelProtocolType>(it->second);
+}
+
+Json ChannelConfig::readJsonFile(const std::string& configFile)
+{
+    std::ifstream jsonFile(configFile);
+    if (!jsonFile.good())
+    {
+        lg2::info("JSON file '{FILE_NAME}' not found", "FILE_NAME", configFile);
+        return nullptr;
+    }
+
+    Json data = nullptr;
+    try
+    {
+        data = Json::parse(jsonFile, nullptr, false);
+    }
+    catch (const Json::parse_error& e)
+    {
+        lg2::debug("Corrupted channel config: {MSG}", "MSG", e.what());
+        throw std::runtime_error("Corrupted channel config file");
+    }
+
+    return data;
+}
+
+int ChannelConfig::writeJsonFile(const std::string& configFile,
+                                 const Json& jsonData)
+{
+    const std::string tmpFile = configFile + "_tmp";
+    int fd = open(tmpFile.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_SYNC,
+                  S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+    if (fd < 0)
+    {
+        lg2::error("Error in creating json file '{FILE_NAME}'", "FILE_NAME",
+                   tmpFile);
+        return -EIO;
+    }
+    const auto& writeData = jsonData.dump();
+    if (write(fd, writeData.c_str(), writeData.size()) !=
+        static_cast<ssize_t>(writeData.size()))
+    {
+        close(fd);
+        lg2::error("Error in writing configuration file '{FILE_NAME}'",
+                   "FILE_NAME", tmpFile);
+        return -EIO;
+    }
+    close(fd);
+
+    if (std::rename(tmpFile.c_str(), configFile.c_str()) != 0)
+    {
+        lg2::error("Error in renaming temporary data file '{FILE_NAME}'",
+                   "FILE_NAME", tmpFile);
+        return -EIO;
+    }
+
+    return 0;
+}
+
+void ChannelConfig::setDefaultChannelConfig(const uint8_t chNum,
+                                            const std::string& chName)
+{
+    channelData[chNum].chName = chName;
+    channelData[chNum].chID = chNum;
+    channelData[chNum].isChValid = false;
+    channelData[chNum].activeSessCount = 0;
+    channelData[chNum].isManagementNIC = false;
+
+    channelData[chNum].chInfo.mediumType = defaultMediumType;
+    channelData[chNum].chInfo.protocolType = defaultProtocolType;
+    channelData[chNum].chInfo.sessionSupported = defaultSessionSupported;
+    channelData[chNum].chInfo.isIpmi = defaultIsIpmiState;
+    channelData[chNum].chInfo.authTypeSupported = defaultAuthType;
+}
+
+uint8_t ChannelConfig::getManagementNICID()
+{
+    static bool idFound = false;
+    static uint8_t id = 0;
+
+    if (idFound)
+    {
+        return id;
+    }
+
+    for (uint8_t chIdx = 0; chIdx < maxIpmiChannels; chIdx++)
+    {
+        if (channelData[chIdx].isManagementNIC)
+        {
+            id = chIdx;
+            idFound = true;
+            break;
+        }
+    }
+
+    if (!idFound)
+    {
+        id = static_cast<uint8_t>(EChannelID::chanLan1);
+        idFound = true;
+    }
+    return id;
+}
+
+int ChannelConfig::loadChannelConfig()
+{
+    boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+        channelLock{*channelMutex};
+
+    Json data = readJsonFile(channelConfigDefaultFilename);
+    if (data.empty())
+    {
+        lg2::debug("Error in opening IPMI Channel data file");
+        return -EIO;
+    }
+
+    channelData.fill(ChannelProperties{});
+
+    // Collect the list of NIC interfaces connected to the BMC. Use this
+    // information to only add IPMI channels that have active NIC interfaces.
+    struct ifaddrs *ifaddr = nullptr, *ifa = nullptr;
+    if (int err = getifaddrs(&ifaddr); err < 0)
+    {
+        lg2::debug("Unable to acquire network interfaces");
+        return -EIO;
+    }
+
+    for (int chNum = 0; chNum < maxIpmiChannels; chNum++)
+    {
+        try
+        {
+            std::string chKey = std::to_string(chNum);
+            Json jsonChData = data[chKey].get<Json>();
+            if (jsonChData.is_null())
+            {
+                // If user didn't want to configure specific channel (say
+                // reserved channel), then load that index with default values.
+                setDefaultChannelConfig(chNum, defaultChannelName);
+                continue;
+            }
+            Json jsonChInfo = jsonChData[channelInfoString].get<Json>();
+            if (jsonChInfo.is_null())
+            {
+                lg2::error("Invalid/corrupted channel config file");
+                freeifaddrs(ifaddr);
+                return -EBADMSG;
+            }
+
+            bool channelFound = true;
+            // Confirm the LAN channel is present
+            if (jsonChInfo[mediumTypeString].get<std::string>() == "lan-802.3")
+            {
+                channelFound = false;
+                for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next)
+                {
+                    if (jsonChData[nameString].get<std::string>() ==
+                        ifa->ifa_name)
+                    {
+                        channelFound = true;
+                        break;
+                    }
+                }
+            }
+            ChannelProperties& chData = channelData[chNum];
+            chData.chID = chNum;
+            chData.chName = jsonChData[nameString].get<std::string>();
+            chData.isChValid = channelFound &&
+                               jsonChData[isValidString].get<bool>();
+            chData.activeSessCount = jsonChData.value(activeSessionsString, 0);
+            chData.maxTransferSize =
+                jsonChData.value(maxTransferSizeString, smallChannelSize);
+            if (jsonChData.count(isManagementNIC) != 0)
+            {
+                chData.isManagementNIC =
+                    jsonChData[isManagementNIC].get<bool>();
+            }
+
+            std::string medTypeStr =
+                jsonChInfo[mediumTypeString].get<std::string>();
+            chData.chInfo.mediumType =
+                static_cast<uint8_t>(convertToMediumTypeIndex(medTypeStr));
+            std::string protoTypeStr =
+                jsonChInfo[protocolTypeString].get<std::string>();
+            chData.chInfo.protocolType =
+                static_cast<uint8_t>(convertToProtocolTypeIndex(protoTypeStr));
+            std::string sessStr =
+                jsonChInfo[sessionSupportedString].get<std::string>();
+            chData.chInfo.sessionSupported =
+                static_cast<uint8_t>(convertToSessionSupportIndex(sessStr));
+            chData.chInfo.isIpmi = jsonChInfo[isIpmiString].get<bool>();
+            chData.chInfo.authTypeSupported = defaultAuthType;
+        }
+        catch (const Json::exception& e)
+        {
+            lg2::debug("Json Exception caught: {MSG}", "MSG", e.what());
+            freeifaddrs(ifaddr);
+
+            return -EBADMSG;
+        }
+        catch (const std::invalid_argument& e)
+        {
+            lg2::error("Corrupted config: {MSG}", "MSG", e.what());
+            freeifaddrs(ifaddr);
+            return -EBADMSG;
+        }
+    }
+    freeifaddrs(ifaddr);
+
+    return 0;
+}
+
+int ChannelConfig::readChannelVolatileData()
+{
+    boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+        channelLock{*channelMutex};
+
+    Json data = readJsonFile(channelVolatileDataFilename);
+    if (data == nullptr)
+    {
+        lg2::debug("Error in opening IPMI Channel data file");
+        return -EIO;
+    }
+    try
+    {
+        // Fill in global structure
+        for (auto it = data.begin(); it != data.end(); ++it)
+        {
+            std::string chKey = it.key();
+            uint8_t chNum = std::stoi(chKey, nullptr, 10);
+            if (chNum >= maxIpmiChannels)
+            {
+                lg2::debug("Invalid channel access entry in config file");
+                throw std::out_of_range("Out of range - channel number");
+            }
+            Json jsonChData = it.value();
+            if (!jsonChData.is_null())
+            {
+                std::string accModeStr =
+                    jsonChData[accessModeString].get<std::string>();
+                channelData[chNum].chAccess.chVolatileData.accessMode =
+                    static_cast<uint8_t>(convertToAccessModeIndex(accModeStr));
+                channelData[chNum].chAccess.chVolatileData.userAuthDisabled =
+                    jsonChData[userAuthDisabledString].get<bool>();
+                channelData[chNum].chAccess.chVolatileData.perMsgAuthDisabled =
+                    jsonChData[perMsgAuthDisabledString].get<bool>();
+                channelData[chNum].chAccess.chVolatileData.alertingDisabled =
+                    jsonChData[alertingDisabledString].get<bool>();
+                std::string privStr =
+                    jsonChData[privLimitString].get<std::string>();
+                channelData[chNum].chAccess.chVolatileData.privLimit =
+                    static_cast<uint8_t>(convertToPrivLimitIndex(privStr));
+            }
+            else
+            {
+                lg2::error(
+                    "Invalid/corrupted volatile channel access file '{FILE}'",
+                    "FILE", channelVolatileDataFilename);
+                throw std::runtime_error(
+                    "Corrupted volatile channel access file");
+            }
+        }
+    }
+    catch (const Json::exception& e)
+    {
+        lg2::debug("Json Exception caught: {MSG}", "MSG", e.what());
+        throw std::runtime_error("Corrupted volatile channel access file");
+    }
+    catch (const std::invalid_argument& e)
+    {
+        lg2::error("Corrupted config: {MSG}", "MSG", e.what());
+        throw std::runtime_error("Corrupted volatile channel access file");
+    }
+
+    // Update the timestamp
+    voltFileLastUpdatedTime = getUpdatedFileTime(channelVolatileDataFilename);
+    return 0;
+}
+
+int ChannelConfig::readChannelPersistData()
+{
+    boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+        channelLock{*channelMutex};
+
+    Json data = readJsonFile(channelNvDataFilename);
+    if (data == nullptr)
+    {
+        lg2::debug("Error in opening IPMI Channel data file");
+        return -EIO;
+    }
+    try
+    {
+        // Fill in global structure
+        for (auto it = data.begin(); it != data.end(); ++it)
+        {
+            std::string chKey = it.key();
+            uint8_t chNum = std::stoi(chKey, nullptr, 10);
+            if (chNum >= maxIpmiChannels)
+            {
+                lg2::debug("Invalid channel access entry in config file");
+                throw std::out_of_range("Out of range - channel number");
+            }
+            Json jsonChData = it.value();
+            if (!jsonChData.is_null())
+            {
+                std::string accModeStr =
+                    jsonChData[accessModeString].get<std::string>();
+                channelData[chNum].chAccess.chNonVolatileData.accessMode =
+                    static_cast<uint8_t>(convertToAccessModeIndex(accModeStr));
+                channelData[chNum].chAccess.chNonVolatileData.userAuthDisabled =
+                    jsonChData[userAuthDisabledString].get<bool>();
+                channelData[chNum]
+                    .chAccess.chNonVolatileData.perMsgAuthDisabled =
+                    jsonChData[perMsgAuthDisabledString].get<bool>();
+                channelData[chNum].chAccess.chNonVolatileData.alertingDisabled =
+                    jsonChData[alertingDisabledString].get<bool>();
+                std::string privStr =
+                    jsonChData[privLimitString].get<std::string>();
+                channelData[chNum].chAccess.chNonVolatileData.privLimit =
+                    static_cast<uint8_t>(convertToPrivLimitIndex(privStr));
+            }
+            else
+            {
+                lg2::error("Invalid/corrupted nv channel access file {FILE}",
+                           "FILE", channelNvDataFilename);
+                throw std::runtime_error("Corrupted nv channel access file");
+            }
+        }
+    }
+    catch (const Json::exception& e)
+    {
+        lg2::debug("Json Exception caught: {MSG}", "MSG", e.what());
+        throw std::runtime_error("Corrupted nv channel access file");
+    }
+    catch (const std::invalid_argument& e)
+    {
+        lg2::error("Corrupted config: {MSG}", "MSG", e.what());
+        throw std::runtime_error("Corrupted nv channel access file");
+    }
+
+    // Update the timestamp
+    nvFileLastUpdatedTime = getUpdatedFileTime(channelNvDataFilename);
+    return 0;
+}
+
+int ChannelConfig::writeChannelVolatileData()
+{
+    boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+        channelLock{*channelMutex};
+    Json outData;
+
+    try
+    {
+        for (uint8_t chNum = 0; chNum < maxIpmiChannels; chNum++)
+        {
+            if (getChannelSessionSupport(chNum) != EChannelSessSupported::none)
+            {
+                Json jsonObj;
+                std::string chKey = std::to_string(chNum);
+                std::string accModeStr = convertToAccessModeString(
+                    channelData[chNum].chAccess.chVolatileData.accessMode);
+                jsonObj[accessModeString] = accModeStr;
+                jsonObj[userAuthDisabledString] =
+                    channelData[chNum].chAccess.chVolatileData.userAuthDisabled;
+                jsonObj[perMsgAuthDisabledString] =
+                    channelData[chNum]
+                        .chAccess.chVolatileData.perMsgAuthDisabled;
+                jsonObj[alertingDisabledString] =
+                    channelData[chNum].chAccess.chVolatileData.alertingDisabled;
+                std::string privStr = convertToPrivLimitString(
+                    channelData[chNum].chAccess.chVolatileData.privLimit);
+                jsonObj[privLimitString] = privStr;
+
+                outData[chKey] = jsonObj;
+            }
+        }
+    }
+    catch (const std::invalid_argument& e)
+    {
+        lg2::error("Corrupted config: {MSG}", "MSG", e.what());
+        return -EINVAL;
+    }
+
+    if (writeJsonFile(channelVolatileDataFilename, outData) != 0)
+    {
+        lg2::debug("Error in write JSON data to file");
+        return -EIO;
+    }
+
+    // Update the timestamp
+    voltFileLastUpdatedTime = getUpdatedFileTime(channelVolatileDataFilename);
+    return 0;
+}
+
+int ChannelConfig::writeChannelPersistData()
+{
+    boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+        channelLock{*channelMutex};
+    Json outData;
+
+    try
+    {
+        for (uint8_t chNum = 0; chNum < maxIpmiChannels; chNum++)
+        {
+            if (getChannelSessionSupport(chNum) != EChannelSessSupported::none)
+            {
+                Json jsonObj;
+                std::string chKey = std::to_string(chNum);
+                std::string accModeStr = convertToAccessModeString(
+                    channelData[chNum].chAccess.chNonVolatileData.accessMode);
+                jsonObj[accessModeString] = accModeStr;
+                jsonObj[userAuthDisabledString] =
+                    channelData[chNum]
+                        .chAccess.chNonVolatileData.userAuthDisabled;
+                jsonObj[perMsgAuthDisabledString] =
+                    channelData[chNum]
+                        .chAccess.chNonVolatileData.perMsgAuthDisabled;
+                jsonObj[alertingDisabledString] =
+                    channelData[chNum]
+                        .chAccess.chNonVolatileData.alertingDisabled;
+                std::string privStr = convertToPrivLimitString(
+                    channelData[chNum].chAccess.chNonVolatileData.privLimit);
+                jsonObj[privLimitString] = privStr;
+
+                outData[chKey] = jsonObj;
+            }
+        }
+    }
+    catch (const std::invalid_argument& e)
+    {
+        lg2::error("Corrupted config: {MSG}", "MSG", e.what());
+        return -EINVAL;
+    }
+
+    if (writeJsonFile(channelNvDataFilename, outData) != 0)
+    {
+        lg2::debug("Error in write JSON data to file");
+        return -EIO;
+    }
+
+    // Update the timestamp
+    nvFileLastUpdatedTime = getUpdatedFileTime(channelNvDataFilename);
+    return 0;
+}
+
+int ChannelConfig::checkAndReloadNVData()
+{
+    std::time_t updateTime = getUpdatedFileTime(channelNvDataFilename);
+    int ret = 0;
+    if (updateTime != nvFileLastUpdatedTime || updateTime == -EIO)
+    {
+        try
+        {
+            ret = readChannelPersistData();
+        }
+        catch (const std::exception& e)
+        {
+            lg2::error("Exception caught in readChannelPersistData: {MSG}",
+                       "MSG", e.what());
+            ret = -EIO;
+        }
+    }
+    return ret;
+}
+
+int ChannelConfig::checkAndReloadVolatileData()
+{
+    std::time_t updateTime = getUpdatedFileTime(channelVolatileDataFilename);
+    int ret = 0;
+    if (updateTime != voltFileLastUpdatedTime || updateTime == -EIO)
+    {
+        try
+        {
+            ret = readChannelVolatileData();
+        }
+        catch (const std::exception& e)
+        {
+            lg2::error("Exception caught in readChannelVolatileData: {MSG}",
+                       "MSG", e.what());
+            ret = -EIO;
+        }
+    }
+    return ret;
+}
+
+int ChannelConfig::setDbusProperty(
+    const std::string& service, const std::string& objPath,
+    const std::string& interface, const std::string& property,
+    const DbusVariant& value)
+{
+    try
+    {
+        auto method =
+            bus.new_method_call(service.c_str(), objPath.c_str(),
+                                "org.freedesktop.DBus.Properties", "Set");
+
+        method.append(interface, property, value);
+
+        auto reply = bus.call(method);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::debug(
+            "set-property {SERVICE}:{OBJPATH}/{INTERFACE}.{PROP} failed: {MSG}",
+            "SERVICE", service, "OBJPATH", objPath, "INTERFACE", interface,
+            "PROP", property, "MSG", e);
+        return -EIO;
+    }
+
+    return 0;
+}
+
+int ChannelConfig::syncNetworkChannelConfig()
+{
+    boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+        channelLock{*channelMutex};
+    bool isUpdated = false;
+    for (uint8_t chNum = 0; chNum < maxIpmiChannels; chNum++)
+    {
+        if (getChannelSessionSupport(chNum) != EChannelSessSupported::none)
+        {
+            std::string intfPrivStr;
+            uint8_t intfPriv = 0;
+            try
+            {
+                std::string networkIntfObj =
+                    std::string(networkIntfObjectBasePath) + "/" +
+                    channelData[chNum].chName;
+                auto propValue = ipmi::getDbusProperty(
+                    bus, networkIntfServiceName, networkIntfObj,
+                    networkChConfigIntfName, privilegePropertyString);
+
+                intfPrivStr = std::get<std::string>(propValue);
+                intfPriv =
+                    static_cast<uint8_t>(convertToPrivLimitIndex(intfPrivStr));
+            }
+            catch (const std::bad_variant_access& e)
+            {
+                lg2::debug("Network interface '{INTERFACE}' does not exist",
+                           "INTERFACE", channelData[chNum].chName);
+                continue;
+            }
+            catch (const sdbusplus::exception_t& e)
+            {
+                lg2::debug("Network interface '{INTERFACE}' does not exist",
+                           "INTERFACE", channelData[chNum].chName);
+                continue;
+            }
+            catch (const std::invalid_argument& e)
+            {
+                lg2::debug("exception: Invalid privilege");
+                continue;
+            }
+
+            if (channelData[chNum].chAccess.chNonVolatileData.privLimit !=
+                intfPriv)
+            {
+                isUpdated = true;
+                channelData[chNum].chAccess.chNonVolatileData.privLimit =
+                    intfPriv;
+                channelData[chNum].chAccess.chVolatileData.privLimit = intfPriv;
+            }
+        }
+    }
+
+    if (isUpdated)
+    {
+        // Write persistent data to file
+        if (writeChannelPersistData() != 0)
+        {
+            lg2::debug("Failed to update the persistent data file");
+            return -EIO;
+        }
+        // Write Volatile data to file
+        if (writeChannelVolatileData() != 0)
+        {
+            lg2::debug("Failed to update the channel volatile data");
+            return -EIO;
+        }
+    }
+
+    return 0;
+}
+
+void ChannelConfig::initChannelPersistData()
+{
+    boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+        channelLock{*channelMutex};
+
+    /* Always read the channel config */
+    if (loadChannelConfig() != 0)
+    {
+        lg2::error("Failed to read channel config file");
+        throw std::ios_base::failure("Failed to load channel configuration");
+    }
+
+    /* Populate the channel persist data */
+    if (readChannelPersistData() != 0)
+    {
+        // Copy default NV data to RW location
+        std::filesystem::copy_file(channelAccessDefaultFilename,
+                                   channelNvDataFilename);
+
+        // Load the channel access NV data
+        if (readChannelPersistData() != 0)
+        {
+            lg2::error("Failed to read channel access NV data");
+            throw std::ios_base::failure(
+                "Failed to read channel access NV configuration");
+        }
+    }
+
+    // First check the volatile data file
+    // If not present, load the default values
+    if (readChannelVolatileData() != 0)
+    {
+        // Copy default volatile data to temporary location
+        // NV file(channelNvDataFilename) must have created by now.
+        std::filesystem::copy_file(channelNvDataFilename,
+                                   channelVolatileDataFilename);
+
+        // Load the channel access volatile data
+        if (readChannelVolatileData() != 0)
+        {
+            lg2::error("Failed to read channel access volatile data");
+            throw std::ios_base::failure(
+                "Failed to read channel access volatile configuration");
+        }
+    }
+
+    // Synchronize the channel config(priv) with network channel
+    // configuration(priv) over dbus
+    if (syncNetworkChannelConfig() != 0)
+    {
+        lg2::error(
+            "Failed to synchronize data with network channel config over dbus");
+        throw std::ios_base::failure(
+            "Failed to synchronize data with network channel config over dbus");
+    }
+
+    lg2::debug("Successfully completed channel data initialization.");
+    return;
+}
+
+} // namespace ipmi
diff --git a/user_channel/channel_mgmt.hpp b/user_channel/channel_mgmt.hpp
new file mode 100644
index 0000000..5ec5a16
--- /dev/null
+++ b/user_channel/channel_mgmt.hpp
@@ -0,0 +1,428 @@
+/*
+// Copyright (c) 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 "channel_layer.hpp"
+#include "ipmid/api-types.hpp"
+
+#include <boost/interprocess/sync/file_lock.hpp>
+#include <boost/interprocess/sync/named_recursive_mutex.hpp>
+#include <nlohmann/json.hpp>
+#include <sdbusplus/bus.hpp>
+
+#include <cstdint>
+#include <ctime>
+#include <variant>
+
+namespace ipmi
+{
+
+using Json = nlohmann::json;
+
+using DbusVariant = std::variant<std::vector<std::string>, std::string, bool>;
+
+using DbusChObjProperties = std::vector<std::pair<std::string, DbusVariant>>;
+
+static constexpr const char* ipmiChannelMutex = "ipmi_channel_mutex";
+static constexpr const char* ipmiChMutexCleanupLockFile =
+    "/run/ipmi/ipmi_channel_mutex_cleanup";
+
+/** @struct ChannelAccessData
+ *
+ *  Structure to store both non-volatile and volatile channel access information
+ *  as used by IPMI specification (refer spec sec 22.22 to 22.24)
+ */
+struct ChannelAccessData
+{
+    ChannelAccess chNonVolatileData;
+    ChannelAccess chVolatileData;
+};
+
+/** @struct ChannelProperties
+ *
+ *  Structure for channel information - base structure to get all information
+ * about the channel.(refer spec sec 22.22 to 22.24)
+ */
+struct ChannelProperties
+{
+    std::string chName;
+    uint8_t chID;
+    bool isChValid;
+    uint8_t activeSessCount;
+    ChannelInfo chInfo;
+    ChannelAccessData chAccess;
+    size_t maxTransferSize;
+    bool isManagementNIC;
+};
+
+class ChannelConfig;
+
+ChannelConfig& getChannelConfigObject();
+
+class ChannelConfig
+{
+  public:
+    ChannelConfig(const ChannelConfig&) = delete;
+    ChannelConfig& operator=(const ChannelConfig&) = delete;
+    ChannelConfig(ChannelConfig&&) = delete;
+    ChannelConfig& operator=(ChannelConfig&&) = delete;
+
+    ~ChannelConfig();
+    ChannelConfig();
+
+    /** @brief determines valid channel
+     *
+     *  @param[in] chNum - channel number
+     *
+     *  @return true if valid, false otherwise
+     */
+    bool isValidChannel(const uint8_t chNum);
+
+    /** @brief determines valid authentication type
+     *
+     *  @param[in] chNum - channel number
+     *  @param[in] authType - authentication type
+     *
+     *  @return true if valid, false otherwise
+     */
+    bool isValidAuthType(const uint8_t chNum, const EAuthType& authType);
+
+    /** @brief function to get channel name from channel number
+     *
+     *  @param[in] chNum - channel number index
+     *
+     *  @return network channel interface name
+     */
+    std::string getChannelName(const uint8_t chNum);
+
+    /** @brief function to get channel number from channel name
+     *
+     *  @param[in] chName - channel name
+     *
+     *  @return network channel interface number
+     */
+
+    uint8_t getChannelByName(const std::string& chName)
+    {
+        return convertToChannelNumberFromChannelName(chName);
+    }
+
+    /** @brief determines supported session type of a channel
+     *
+     *  @param[in] chNum - channel number
+     *
+     *  @return EChannelSessSupported - supported session type
+     */
+    EChannelSessSupported getChannelSessionSupport(const uint8_t chNum);
+
+    /** @brief determines number of active sessions on a channel
+     *
+     *  @param[in] chNum - channel number
+     *
+     *  @return numer of active sessions
+     */
+    int getChannelActiveSessions(const uint8_t chNum);
+
+    /** @brief determines maximum transfer size for a channel
+     *
+     *  @param[in] chNum - channel number
+     *
+     *  @return maximum bytes that can be transferred on this channel
+     */
+    size_t getChannelMaxTransferSize(uint8_t chNum);
+
+    /** @brief provides channel info details
+     *
+     *  @param[in] chNum - channel number
+     *  @param[out] chInfo - channel info details
+     *
+     *  @return ccSuccess for success, others for failure.
+     */
+    Cc getChannelInfo(const uint8_t chNum, ChannelInfo& chInfo);
+
+    /** @brief provides channel access data
+     *
+     *  @param[in] chNum - channel number
+     *  @param[out] chAccessData - channel access data
+     *
+     *  @return ccSuccess for success, others for failure.
+     */
+    Cc getChannelAccessData(const uint8_t chNum, ChannelAccess& chAccessData);
+
+    /** @brief to set channel access data
+     *
+     *  @param[in] chNum - channel number
+     *  @param[in] chAccessData - channel access data
+     *  @param[in] setFlag - flag to indicate updatable fields
+     *
+     *  @return ccSuccess for success, others for failure.
+     */
+    Cc setChannelAccessData(const uint8_t chNum,
+                            const ChannelAccess& chAccessData,
+                            const uint8_t setFlag);
+
+    /** @brief to get channel access data persistent data
+     *
+     *  @param[in] chNum - channel number
+     *  @param[out] chAccessData - channel access data
+     *
+     *  @return ccSuccess for success, others for failure.
+     */
+    Cc getChannelAccessPersistData(const uint8_t chNum,
+                                   ChannelAccess& chAccessData);
+
+    /** @brief to set channel access data persistent data
+     *
+     *  @param[in] chNum - channel number
+     *  @param[in] chAccessData - channel access data
+     *  @param[in] setFlag - flag to indicate updatable fields
+     *
+     *  @return ccSuccess for success, others for failure.
+     */
+    Cc setChannelAccessPersistData(const uint8_t chNum,
+                                   const ChannelAccess& chAccessData,
+                                   const uint8_t setFlag);
+
+    /** @brief provides supported authentication type for the channel
+     *
+     *  @param[in] chNum - channel number
+     *  @param[out] authTypeSupported - supported authentication type
+     *
+     *  @return ccSuccess for success, others for failure.
+     */
+    Cc getChannelAuthTypeSupported(const uint8_t chNum,
+                                   uint8_t& authTypeSupported);
+
+    /** @brief provides enabled authentication type for the channel
+     *
+     *  @param[in] chNum - channel number
+     *  @param[in] priv - privilege
+     *  @param[out] authType - enabled authentication type
+     *
+     *  @return ccSuccess for success, others for failure.
+     */
+    Cc getChannelEnabledAuthType(const uint8_t chNum, const uint8_t priv,
+                                 EAuthType& authType);
+
+    /** @brief conver to channel privilege from system privilege
+     *
+     *  @param[in] value - privilege value
+     *
+     *  @return Channel privilege
+     */
+    CommandPrivilege convertToPrivLimitIndex(const std::string& value);
+
+    /** @brief function to write persistent channel configuration to config file
+     *
+     *  @return 0 for success, -errno for failure.
+     */
+    int writeChannelPersistData();
+
+    /** @brief function to write volatile channel configuration to config file
+     *
+     *  @return 0 for success, -errno for failure.
+     */
+    int writeChannelVolatileData();
+
+    /** @brief Returns the IPMI channel ID authorized to push IPMI privilege
+     * changes to phosphor-user-manager. Any channel access changes made on
+     * any other channel are ignored.
+     *
+     *  @return IPMI channel ID as defined in channel_config.json
+     */
+    uint8_t getManagementNICID();
+
+  private:
+    uint32_t signalFlag = 0;
+    std::unique_ptr<boost::interprocess::named_recursive_mutex> channelMutex{
+        nullptr};
+    std::array<ChannelProperties, maxIpmiChannels> channelData;
+    std::time_t nvFileLastUpdatedTime;
+    std::time_t voltFileLastUpdatedTime;
+    boost::interprocess::file_lock mutexCleanupLock;
+    sdbusplus::bus_t bus;
+    bool signalHndlrObjectState = false;
+    boost::interprocess::file_lock sigHndlrLock;
+
+    /** @brief function to initialize persistent channel configuration
+     *
+     */
+    void initChannelPersistData();
+
+    /** @brief function to set default channel configuration based on channel
+     * number
+     *
+     *  @param[in] chNum - channel number
+     *  @param[in] chName - channel name
+     */
+    void setDefaultChannelConfig(const uint8_t chNum,
+                                 const std::string& chName);
+
+    /** @brief function to load all channel configuration
+     *
+     *  @return 0 for success, -errno for failure.
+     */
+    int loadChannelConfig();
+
+    /** @brief function to read persistent channel data
+     *
+     *  @return 0 for success, -errno for failure.
+     */
+    int readChannelPersistData();
+
+    /** @brief function to read volatile channel data
+     *
+     *  @return 0 for success, -errno for failure.
+     */
+    int readChannelVolatileData();
+
+    /** @brief function to check and reload persistent channel data
+     *
+     *  @return 0 for success, -errno for failure.
+     */
+    int checkAndReloadNVData();
+
+    /** @brief function to check and reload volatile channel data
+     *
+     *  @return 0 for success, -errno for failure.
+     */
+    int checkAndReloadVolatileData();
+
+    /** @brief function to sync channel privilege with system network channel
+     * privilege
+     *
+     *  @return 0 for success, -errno for failure.
+     */
+    int syncNetworkChannelConfig();
+
+    /** @brief function to set D-Bus property value
+     *
+     *  @param[in] service - service name
+     *  @param[in] objPath - object path
+     *  @param[in] interface - interface
+     *  @param[in] property - property name
+     *  @param[in] value - property value
+     *
+     *  @return 0 for success, -errno for failure.
+     */
+    int setDbusProperty(const std::string& service, const std::string& objPath,
+                        const std::string& interface,
+                        const std::string& property, const DbusVariant& value);
+
+    /** @brief function to read json config file
+     *
+     *  @param[in] configFile - configuration file name
+     *
+     *  @return Json object
+     */
+    Json readJsonFile(const std::string& configFile);
+
+    /** @brief function to write json config file
+     *
+     *  @param[in] configFile - configuration file name
+     *  @param[in] jsonData - json object
+     *
+     *  @return 0 for success, -errno for failure.
+     */
+    int writeJsonFile(const std::string& configFile, const Json& jsonData);
+
+    /** @brief function to convert system access mode to Channel access mode
+     * type
+     *
+     *  @param[in] mode - access mode in string
+     *
+     *  @return Channel access mode.
+     */
+    EChannelAccessMode convertToAccessModeIndex(const std::string& mode);
+
+    /** @brief function to convert access mode value to string
+     *
+     *  @param[in] value - acess mode value
+     *
+     *  @return access mode in string
+     */
+    std::string convertToAccessModeString(const uint8_t value);
+
+    /** @brief function to convert privilege value to string
+     *
+     *  @param[in] value - privilege value
+     *
+     *  @return privilege in string
+     */
+    std::string convertToPrivLimitString(const uint8_t value);
+
+    /** @brief function to convert session support string to value type
+     *
+     *  @param[in] value - session support type in string
+     *
+     *  @return support session type
+     */
+    EChannelSessSupported convertToSessionSupportIndex(
+        const std::string& value);
+
+    /** @brief function to convert medium type string to value type
+     *
+     *  @param[in] value - medium type in string
+     *
+     *  @return channel medium type
+     */
+    EChannelMediumType convertToMediumTypeIndex(const std::string& value);
+
+    /** @brief function to convert protocol type string to value type
+     *
+     *  @param[in] value - protocol type in string
+     *
+     *  @return channel protocol  type
+     */
+    EChannelProtocolType convertToProtocolTypeIndex(const std::string& value);
+
+    /** @brief function to convert channel name to the IPMI channel number.
+     *
+     *  @param[in] chName - the channel name defined in the JSON input file
+     *  (i.e. LAN1)
+     *
+     *  @return IPMI channel number
+     */
+    int convertToChannelNumberFromChannelName(const std::string& chName);
+
+    /** @brief function to handle Channel access property update through the
+     * D-Bus handler.
+     *
+     *  @param[in] path - D-Bus path to the network element (i.e. eth0)
+     *  @param[in] chProperties - D-Bus channel properties
+     */
+    void processChAccessPropChange(const std::string& path,
+                                   const DbusChObjProperties& chProperties);
+
+    /** @brief function to retrieve last modification time for the named file
+     *
+     *  @param[in] fileName - the name of the file for which to acquire
+     *  timestamp data
+     *
+     *  @return time the file was last modified
+     */
+    std::time_t getUpdatedFileTime(const std::string& fileName);
+
+    /** @brief function to convert the DBus path to a network channel name
+     *
+     *  @param[in] path - The DBus path to the device
+     *
+     *  @return network channel name (i.e. eth0)
+     */
+    std::string getChannelNameFromPath(const std::string& path);
+};
+
+} // namespace ipmi
diff --git a/user_channel/channelcommands.cpp b/user_channel/channelcommands.cpp
new file mode 100644
index 0000000..cb4da6b
--- /dev/null
+++ b/user_channel/channelcommands.cpp
@@ -0,0 +1,420 @@
+/*
+// Copyright (c) 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.
+*/
+
+#include "channel_layer.hpp"
+
+#include <ipmid/api.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <regex>
+
+namespace ipmi
+{
+
+/** @brief implements the set channel access command
+ *  @ param ctx - context pointer
+ *  @ param channel - channel number
+ *  @ param reserved - skip 4 bits
+ *  @ param accessMode - access mode for IPMI messaging
+ *  @ param usrAuth - user level authentication (enable/disable)
+ *  @ param msgAuth - per message authentication (enable/disable)
+ *  @ param alertDisabled - PEF alerting (enable/disable)
+ *  @ param chanAccess - channel access
+ *  @ param channelPrivLimit - channel privilege limit
+ *  @ param reserved - skip 3 bits
+ *  @ param channelPrivMode - channel priviledge mode
+ *
+ *  @ returns IPMI completion code
+ **/
+RspType<> ipmiSetChannelAccess(
+    Context::ptr ctx, uint4_t channel, uint4_t reserved1, uint3_t accessMode,
+    bool usrAuth, bool msgAuth, bool alertDisabled, uint2_t chanAccess,
+    uint4_t channelPrivLimit, uint2_t reserved2, uint2_t channelPrivMode)
+{
+    if (reserved1 || reserved2 ||
+        !isValidPrivLimit(static_cast<uint8_t>(channelPrivLimit)))
+    {
+        lg2::debug("Set channel access - Invalid field in request");
+        return responseInvalidFieldRequest();
+    }
+
+    const uint8_t chNum =
+        convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel);
+    if ((getChannelSessionSupport(chNum) == EChannelSessSupported::none) ||
+        (!isValidChannel(chNum)))
+    {
+        lg2::debug("Set channel access - No support on channel: {CHANNEL}",
+                   "CHANNEL", chNum);
+        return response(ccActionNotSupportedForChannel);
+    }
+
+    ChannelAccess chActData;
+    ChannelAccess chNVData;
+    uint8_t setActFlag = 0;
+    uint8_t setNVFlag = 0;
+    Cc compCode;
+
+    // cannot static cast directly from uint2_t to enum; must go via int
+    uint8_t channelAccessAction = static_cast<uint8_t>(chanAccess);
+    switch (static_cast<EChannelActionType>(channelAccessAction))
+    {
+        case doNotSet:
+            break;
+        case nvData:
+            chNVData.accessMode = static_cast<uint8_t>(accessMode);
+            chNVData.userAuthDisabled = usrAuth;
+            chNVData.perMsgAuthDisabled = msgAuth;
+            chNVData.alertingDisabled = alertDisabled;
+            setNVFlag |= (setAccessMode | setUserAuthEnabled |
+                          setMsgAuthEnabled | setAlertingEnabled);
+            break;
+
+        case activeData:
+            chActData.accessMode = static_cast<uint8_t>(accessMode);
+            chActData.userAuthDisabled = usrAuth;
+            chActData.perMsgAuthDisabled = msgAuth;
+            chActData.alertingDisabled = alertDisabled;
+            setActFlag |= (setAccessMode | setUserAuthEnabled |
+                           setMsgAuthEnabled | setAlertingEnabled);
+            break;
+
+        case reserved:
+        default:
+            lg2::debug("Set channel access - Invalid access set mode");
+            return response(ccAccessModeNotSupportedForChannel);
+    }
+
+    // cannot static cast directly from uint2_t to enum; must go via int
+    uint8_t channelPrivAction = static_cast<uint8_t>(channelPrivMode);
+    switch (static_cast<EChannelActionType>(channelPrivAction))
+    {
+        case doNotSet:
+            break;
+        case nvData:
+            chNVData.privLimit = static_cast<uint8_t>(channelPrivLimit);
+            setNVFlag |= setPrivLimit;
+            break;
+        case activeData:
+            chActData.privLimit = static_cast<uint8_t>(channelPrivLimit);
+
+            setActFlag |= setPrivLimit;
+            break;
+        case reserved:
+        default:
+            lg2::debug("Set channel access - Invalid access priv mode");
+            return response(ccAccessModeNotSupportedForChannel);
+    }
+
+    if (setNVFlag != 0)
+    {
+        compCode = setChannelAccessPersistData(chNum, chNVData, setNVFlag);
+        if (compCode != ccSuccess)
+        {
+            lg2::debug("Set channel access - Failed to set access data");
+            return response(compCode);
+        }
+    }
+
+    if (setActFlag != 0)
+    {
+        compCode = setChannelAccessData(chNum, chActData, setActFlag);
+        if (compCode != ccSuccess)
+        {
+            lg2::debug("Set channel access - Failed to set access data");
+            return response(compCode);
+        }
+    }
+
+    return responseSuccess();
+}
+
+/** @brief implements the get channel access command
+ *  @ param ctx - context pointer
+ *  @ param channel - channel number
+ *  @ param reserved1 - skip 4 bits
+ *  @ param reserved2 - skip 6 bits
+ *  @ param accessMode - get access mode
+ *
+ *  @returns ipmi completion code plus response data
+ *  - accessMode - get access mode
+ *  - usrAuthDisabled - user level authentication status
+ *  - msgAuthDisabled - message level authentication status
+ *  - alertDisabled - alerting status
+ *  - reserved - skip 2 bits
+ *  - privLimit - channel privilege limit
+ *  - reserved - skip 4 bits
+ * */
+ipmi ::RspType<uint3_t, // access mode,
+               bool,    // user authentication status,
+               bool,    // message authentication status,
+               bool,    // alerting status,
+               uint2_t, // reserved,
+
+               uint4_t, // channel privilege,
+               uint4_t  // reserved
+               >
+    ipmiGetChannelAccess(Context::ptr ctx, uint4_t channel, uint4_t reserved1,
+                         uint6_t reserved2, uint2_t accessSetMode)
+{
+    if (reserved1 || reserved2)
+    {
+        lg2::debug("Get channel access - Invalid field in request");
+        return responseInvalidFieldRequest();
+    }
+
+    if ((types::enum_cast<EChannelActionType>(accessSetMode) == doNotSet) ||
+        (types::enum_cast<EChannelActionType>(accessSetMode) == reserved))
+    {
+        lg2::debug("Get channel access - Invalid Access mode");
+        return responseInvalidFieldRequest();
+    }
+
+    const uint8_t chNum =
+        convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel);
+
+    if ((getChannelSessionSupport(chNum) == EChannelSessSupported::none) ||
+        (!isValidChannel(chNum)))
+    {
+        lg2::debug("Get channel access - No support on channel: {CHANNEL}",
+                   "CHANNEL", chNum);
+        return response(ccActionNotSupportedForChannel);
+    }
+
+    ChannelAccess chAccess = {};
+
+    Cc compCode = ipmi::ccUnspecifiedError;
+
+    if (types::enum_cast<EChannelActionType>(accessSetMode) == nvData)
+    {
+        compCode = getChannelAccessPersistData(chNum, chAccess);
+    }
+    else if (types::enum_cast<EChannelActionType>(accessSetMode) == activeData)
+    {
+        compCode = getChannelAccessData(chNum, chAccess);
+    }
+
+    if (compCode != ccSuccess)
+    {
+        return response(compCode);
+    }
+
+    constexpr uint2_t reservedOut1 = 0;
+    constexpr uint4_t reservedOut2 = 0;
+
+    return responseSuccess(
+        types::enum_cast<uint3_t>(chAccess.accessMode),
+        chAccess.userAuthDisabled, chAccess.perMsgAuthDisabled,
+        chAccess.alertingDisabled, reservedOut1,
+        types::enum_cast<uint4_t>(chAccess.privLimit), reservedOut2);
+}
+
+/** @brief implements the get channel info command
+ *  @ param ctx - context pointer
+ *  @ param channel - channel number
+ *  @ param reserved - skip 4 bits
+ *
+ *  @returns ipmi completion code plus response data
+ *  - chNum - the channel number for this request
+ *  - mediumType - see Table 6-3, Channel Medium Type Numbers
+ *  - protocolType - Table 6-2, Channel Protocol Type Numbers
+ *  - activeSessionCount - number of active sessions
+ *  - sessionType - channel support for sessions
+ *  - vendorId - vendor for this channel protocol (IPMI - 7154)
+ *  - auxChInfo - auxiliary info for channel
+ * */
+RspType<uint4_t,  // chNum
+        uint4_t,  // reserved
+        uint7_t,  // mediumType
+        bool,     // reserved
+        uint5_t,  // protocolType
+        uint3_t,  // reserved
+        uint6_t,  // activeSessionCount
+        uint2_t,  // sessionType
+        uint24_t, // Vendor IANA
+        uint16_t  // aux info
+        >
+    ipmiGetChannelInfo(Context::ptr ctx, uint4_t channel, uint4_t reserved)
+{
+    if (reserved)
+    {
+        lg2::debug("Get channel access - Invalid field in request");
+        return responseInvalidFieldRequest();
+    }
+
+    uint8_t chNum =
+        convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel);
+    if (!isValidChannel(chNum))
+    {
+        lg2::debug("Get channel Info - No support on channel: {CHANNEL}",
+                   "CHANNEL", chNum);
+        return responseInvalidFieldRequest();
+    }
+
+    ChannelInfo chInfo;
+    Cc compCode = getChannelInfo(chNum, chInfo);
+    if (compCode != ccSuccess)
+    {
+        lg2::error("Failed to get channel info, channel: {CHANNEL}, "
+                   "errno: {ERRNO}",
+                   "CHANNEL", chNum, "ERRNO", compCode);
+        return response(compCode);
+    }
+
+    constexpr uint4_t reserved1 = 0;
+    constexpr bool reserved2 = false;
+    constexpr uint3_t reserved3 = 0;
+    uint8_t mediumType = chInfo.mediumType;
+    uint8_t protocolType = chInfo.protocolType;
+    uint2_t sessionType = chInfo.sessionSupported;
+    uint6_t activeSessionCount = getChannelActiveSessions(chNum);
+    // IPMI Spec: The IPMI Enterprise Number is: 7154 (decimal)
+    constexpr uint24_t vendorId = 7154;
+    constexpr uint16_t auxChInfo = 0;
+
+    return responseSuccess(chNum, reserved1, mediumType, reserved2,
+                           protocolType, reserved3, activeSessionCount,
+                           sessionType, vendorId, auxChInfo);
+}
+
+namespace
+{
+constexpr uint16_t standardPayloadBit(PayloadType p)
+{
+    return (1 << static_cast<size_t>(p));
+}
+
+constexpr uint16_t sessionPayloadBit(PayloadType p)
+{
+    constexpr size_t sessionShift =
+        static_cast<size_t>(PayloadType::OPEN_SESSION_REQUEST);
+    return ((1 << static_cast<size_t>(p)) >> sessionShift);
+}
+} // namespace
+
+/** @brief implements get channel payload support command
+ *  @ param ctx - ipmi context pointer
+ *  @ param chNum - channel number
+ *  @ param reserved - skip 4 bits
+ *
+ *  @ returns IPMI completion code plus response data
+ *  - stdPayloadType - bitmask of supported standard payload types
+ *  - sessSetupPayloadType - bitmask of supported session setup payload types
+ *  - OEMPayloadType - bitmask of supported OEM payload types
+ *  - reserved - 2 bytes of 0
+ **/
+RspType<uint16_t, // stdPayloadType
+        uint16_t, // sessSetupPayloadType
+        uint16_t, // OEMPayloadType
+        uint16_t  // reserved
+        >
+    ipmiGetChannelPayloadSupport(Context::ptr ctx, uint4_t channel,
+                                 uint4_t reserved)
+{
+    uint8_t chNum =
+        convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel);
+
+    if (!doesDeviceExist(chNum) || !isValidChannel(chNum) || reserved)
+    {
+        lg2::debug("Get channel payload - Invalid field in request");
+        return responseInvalidFieldRequest();
+    }
+
+    // Session support is available in active LAN channels.
+    if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
+    {
+        lg2::debug("Get channel payload - No support on channel");
+        return response(ccActionNotSupportedForChannel);
+    }
+    constexpr uint16_t stdPayloadType = standardPayloadBit(PayloadType::IPMI) |
+                                        standardPayloadBit(PayloadType::SOL);
+    constexpr uint16_t sessSetupPayloadType =
+        sessionPayloadBit(PayloadType::OPEN_SESSION_REQUEST) |
+        sessionPayloadBit(PayloadType::OPEN_SESSION_RESPONSE) |
+        sessionPayloadBit(PayloadType::RAKP1) |
+        sessionPayloadBit(PayloadType::RAKP2) |
+        sessionPayloadBit(PayloadType::RAKP3) |
+        sessionPayloadBit(PayloadType::RAKP4);
+    constexpr uint16_t OEMPayloadType = 0;
+    constexpr uint16_t rspRsvd = 0;
+    return responseSuccess(stdPayloadType, sessSetupPayloadType, OEMPayloadType,
+                           rspRsvd);
+}
+
+/** @brief implements the get channel payload version command
+ *  @param ctx - IPMI context pointer (for channel)
+ *  @param chNum - channel number to get info about
+ *  @param reserved - skip 4 bits
+ *  @param payloadTypeNum - to get payload type info
+
+ *  @returns IPMI completion code plus response data
+ *   - formatVersion - BCD encoded format version info
+ */
+
+RspType<uint8_t> // formatVersion
+    ipmiGetChannelPayloadVersion(Context::ptr ctx, uint4_t chNum,
+                                 uint4_t reserved, uint8_t payloadTypeNum)
+{
+    uint8_t channel =
+        convertCurrentChannelNum(static_cast<uint8_t>(chNum), ctx->channel);
+    constexpr uint8_t payloadTypeNotSupported = 0x80;
+
+    if (reserved || !isValidChannel(channel))
+    {
+        lg2::debug("Get channel payload version - Invalid field in request");
+        return responseInvalidFieldRequest();
+    }
+
+    if (getChannelSessionSupport(channel) == EChannelSessSupported::none)
+    {
+        lg2::debug("Get channel payload version - No support on channel");
+        return response(payloadTypeNotSupported);
+    }
+
+    if (!isValidPayloadType(static_cast<PayloadType>(payloadTypeNum)))
+    {
+        lg2::error("Get channel payload version - Payload type unavailable");
+
+        return response(payloadTypeNotSupported);
+    }
+
+    // BCD encoded version representation - 1.0
+    constexpr uint8_t formatVersion = 0x10;
+
+    return responseSuccess(formatVersion);
+}
+
+void registerChannelFunctions() __attribute__((constructor));
+void registerChannelFunctions()
+{
+    ipmiChannelInit();
+
+    registerHandler(prioOpenBmcBase, netFnApp, app::cmdSetChannelAccess,
+                    Privilege::Admin, ipmiSetChannelAccess);
+
+    registerHandler(prioOpenBmcBase, netFnApp, app::cmdGetChannelAccess,
+                    Privilege::User, ipmiGetChannelAccess);
+
+    registerHandler(prioOpenBmcBase, netFnApp, app::cmdGetChannelInfoCommand,
+                    Privilege::User, ipmiGetChannelInfo);
+
+    registerHandler(prioOpenBmcBase, netFnApp, app::cmdGetChannelPayloadSupport,
+                    Privilege::User, ipmiGetChannelPayloadSupport);
+
+    registerHandler(prioOpenBmcBase, netFnApp, app::cmdGetChannelPayloadVersion,
+                    Privilege::User, ipmiGetChannelPayloadVersion);
+}
+
+} // namespace ipmi
diff --git a/user_channel/cipher_mgmt.cpp b/user_channel/cipher_mgmt.cpp
new file mode 100644
index 0000000..c954f65
--- /dev/null
+++ b/user_channel/cipher_mgmt.cpp
@@ -0,0 +1,244 @@
+/*
+// Copyright (c) 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.
+*/
+#include "cipher_mgmt.hpp"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <filesystem>
+#include <fstream>
+
+namespace ipmi
+{
+
+using Json = nlohmann::json;
+namespace fs = std::filesystem;
+
+CipherConfig& getCipherConfigObject(const std::string& csFileName,
+                                    const std::string& csDefaultFileName)
+{
+    static CipherConfig cipherConfig(csFileName, csDefaultFileName);
+    return cipherConfig;
+}
+
+CipherConfig::CipherConfig(const std::string& csFileName,
+                           const std::string& csDefaultFileName) :
+    cipherSuitePrivFileName(csFileName),
+    cipherSuiteDefaultPrivFileName(csDefaultFileName)
+{
+    loadCSPrivilegesToMap();
+}
+
+void CipherConfig::loadCSPrivilegesToMap()
+{
+    if (!fs::exists(cipherSuiteDefaultPrivFileName))
+    {
+        lg2::error("CS privilege levels default file does not exist...");
+    }
+    else
+    {
+        // read default privileges
+        Json data = readCSPrivilegeLevels(cipherSuiteDefaultPrivFileName);
+
+        // load default privileges
+        updateCSPrivilegesMap(data);
+
+        // check for user-saved privileges
+        if (fs::exists(cipherSuitePrivFileName))
+        {
+            data = readCSPrivilegeLevels(cipherSuitePrivFileName);
+            if (data != nullptr)
+            {
+                // update map with user-saved privileges by merging (overriding)
+                // values from the defaults
+                updateCSPrivilegesMap(data);
+            }
+        }
+    }
+}
+
+void CipherConfig::updateCSPrivilegesMap(const Json& jsonData)
+{
+    for (uint8_t chNum = 0; chNum < ipmi::maxIpmiChannels; chNum++)
+    {
+        std::string chKey = "Channel" + std::to_string(chNum);
+        for (uint8_t csNum = 0; csNum < maxCSRecords; csNum++)
+        {
+            auto csKey = "CipherID" + std::to_string(csNum);
+
+            if (jsonData.find(chKey) != jsonData.end())
+            {
+                csPrivilegeMap[{chNum, csNum}] = convertToPrivLimitIndex(
+                    static_cast<std::string>(jsonData[chKey][csKey]));
+            }
+        }
+    }
+}
+
+Json CipherConfig::readCSPrivilegeLevels(const std::string& csFileName)
+{
+    std::ifstream jsonFile(csFileName);
+    if (!jsonFile.good())
+    {
+        lg2::error("JSON file not found");
+        return nullptr;
+    }
+
+    Json data = nullptr;
+    try
+    {
+        data = Json::parse(jsonFile, nullptr, false);
+    }
+    catch (const Json::parse_error& e)
+    {
+        lg2::error(
+            "Corrupted cipher suite privilege levels config file: {ERROR}",
+            "ERROR", e);
+    }
+
+    return data;
+}
+
+int CipherConfig::writeCSPrivilegeLevels(const Json& jsonData)
+{
+    std::string tmpFile =
+        static_cast<std::string>(cipherSuitePrivFileName) + "_tmpXXXXXX";
+
+    std::vector<char> tmpRandomFile(tmpFile.length() + 1);
+    strncpy(tmpRandomFile.data(), tmpFile.c_str(), tmpFile.length() + 1);
+
+    int fd = mkstemp(tmpRandomFile.data());
+    fchmod(fd, 0644);
+
+    if (fd < 0)
+    {
+        lg2::error("Error opening CS privilege level config file: {FILE_NAME}",
+                   "FILE_NAME", tmpFile);
+        return -EIO;
+    }
+    const auto& writeData = jsonData.dump();
+    if (write(fd, writeData.c_str(), writeData.size()) !=
+        static_cast<ssize_t>(writeData.size()))
+    {
+        close(fd);
+        lg2::error("Error writing CS privilege level config file: {FILE_NAME}",
+                   "FILE_NAME", tmpFile);
+        unlink(tmpRandomFile.data());
+        return -EIO;
+    }
+    close(fd);
+
+    if (std::rename(tmpRandomFile.data(), cipherSuitePrivFileName.c_str()))
+    {
+        lg2::error("Error renaming CS privilege level config file: {FILE_NAME}",
+                   "FILE_NAME", tmpFile);
+        unlink(tmpRandomFile.data());
+        return -EIO;
+    }
+
+    return 0;
+}
+
+uint4_t CipherConfig::convertToPrivLimitIndex(const std::string& value)
+{
+    auto iter = std::find(ipmi::privList.begin(), ipmi::privList.end(), value);
+    if (iter == privList.end())
+    {
+        lg2::error("Invalid privilege: {PRIV_STR}", "PRIV_STR", value);
+        return ccUnspecifiedError;
+    }
+
+    return static_cast<uint4_t>(std::distance(ipmi::privList.begin(), iter));
+}
+
+std::string CipherConfig::convertToPrivLimitString(const uint4_t& value)
+{
+    return ipmi::privList.at(static_cast<size_t>(value));
+}
+
+ipmi::Cc CipherConfig::getCSPrivilegeLevels(
+    uint8_t chNum, std::array<uint4_t, maxCSRecords>& csPrivilegeLevels)
+{
+    if (!isValidChannel(chNum))
+    {
+        lg2::error("Invalid channel number: {CHANNEL}", "CHANNEL", chNum);
+        return ccInvalidFieldRequest;
+    }
+
+    for (size_t csNum = 0; csNum < maxCSRecords; ++csNum)
+    {
+        csPrivilegeLevels[csNum] = csPrivilegeMap[{chNum, csNum}];
+    }
+    return ccSuccess;
+}
+
+ipmi::Cc CipherConfig::setCSPrivilegeLevels(
+    uint8_t chNum, const std::array<uint4_t, maxCSRecords>& requestData)
+{
+    if (!isValidChannel(chNum))
+    {
+        lg2::error("Invalid channel number: {CHANNEL}", "CHANNEL", chNum);
+        return ccInvalidFieldRequest;
+    }
+
+    Json jsonData;
+    if (!fs::exists(cipherSuitePrivFileName))
+    {
+        lg2::info("CS privilege levels user settings file does not "
+                  "exist. Creating...");
+    }
+    else
+    {
+        jsonData = readCSPrivilegeLevels(cipherSuitePrivFileName);
+        if (jsonData == nullptr)
+        {
+            return ccUnspecifiedError;
+        }
+    }
+
+    Json privData;
+    std::string csKey;
+    constexpr auto privMaxValue = static_cast<uint8_t>(ipmi::Privilege::Oem);
+    for (size_t csNum = 0; csNum < maxCSRecords; ++csNum)
+    {
+        csKey = "CipherID" + std::to_string(csNum);
+        auto priv = static_cast<uint8_t>(requestData[csNum]);
+
+        if (priv > privMaxValue)
+        {
+            return ccInvalidFieldRequest;
+        }
+        privData[csKey] = convertToPrivLimitString(priv);
+    }
+
+    std::string chKey = "Channel" + std::to_string(chNum);
+    jsonData[chKey] = privData;
+
+    if (writeCSPrivilegeLevels(jsonData))
+    {
+        lg2::error("Error in setting CS Privilege Levels.");
+        return ccUnspecifiedError;
+    }
+
+    updateCSPrivilegesMap(jsonData);
+    return ccSuccess;
+}
+
+} // namespace ipmi
diff --git a/user_channel/cipher_mgmt.hpp b/user_channel/cipher_mgmt.hpp
new file mode 100644
index 0000000..0bd6b1d
--- /dev/null
+++ b/user_channel/cipher_mgmt.hpp
@@ -0,0 +1,132 @@
+/*
+// Copyright (c) 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 "channel_layer.hpp"
+
+#include <ipmid/api-types.hpp>
+#include <ipmid/message/types.hpp>
+#include <nlohmann/json.hpp>
+
+#include <map>
+
+namespace ipmi
+{
+static const std::string csPrivDefaultFileName =
+    "/usr/share/ipmi-providers/cs_privilege_levels.json";
+
+static const std::string csPrivFileName =
+    "/var/lib/ipmi/cs_privilege_levels.json";
+
+static const size_t maxCSRecords = 16;
+
+using ChannelNumCipherIDPair = std::pair<uint8_t, uint8_t>;
+using privMap = std::map<ChannelNumCipherIDPair, uint4_t>;
+
+/** @class CipherConfig
+ *  @brief Class to provide cipher suite functionalities
+ */
+class CipherConfig
+{
+  public:
+    ~CipherConfig() = default;
+    explicit CipherConfig(const std::string& csFileName,
+                          const std::string& csDefaultFileName);
+    CipherConfig() = delete;
+
+    /** @brief function to get cipher suite privileges from config file
+     *
+     *  @param[in] chNum - channel number for which we want to get cipher suite
+     * privilege levels
+     *
+     *  @param[in] csPrivilegeLevels - gets filled by cipher suite privilege
+     * levels
+     *
+     *  @return 0 for success, non zero value for failure
+     */
+    ipmi::Cc getCSPrivilegeLevels(
+        uint8_t chNum, std::array<uint4_t, maxCSRecords>& csPrivilegeLevels);
+
+    /** @brief function to set/update cipher suite privileges in config file
+     *
+     *  @param[in] chNum - channel number for which we want to update cipher
+     * suite privilege levels
+     *
+     *  @param[in] csPrivilegeLevels - cipher suite privilege levels to update
+     * in config file
+     *
+     *  @return 0 for success, non zero value for failure
+     */
+    ipmi::Cc setCSPrivilegeLevels(
+        uint8_t chNum,
+        const std::array<uint4_t, maxCSRecords>& csPrivilegeLevels);
+
+  private:
+    std::string cipherSuitePrivFileName, cipherSuiteDefaultPrivFileName;
+
+    privMap csPrivilegeMap;
+
+    /** @brief function to read json config file
+     *
+     *  @return nlohmann::json object
+     */
+    nlohmann::json readCSPrivilegeLevels(const std::string& csFileName);
+
+    /** @brief function to write json config file
+     *
+     *  @param[in] jsonData - json object
+     *
+     *  @return 0 for success, -errno for failure.
+     */
+    int writeCSPrivilegeLevels(const nlohmann::json& jsonData);
+
+    /** @brief convert to cipher suite privilege from string to value
+     *
+     *  @param[in] value - privilege value
+     *
+     *  @return cipher suite privilege index
+     */
+    uint4_t convertToPrivLimitIndex(const std::string& value);
+
+    /** @brief function to convert privilege value to string
+     *
+     *  @param[in] value - privilege value
+     *
+     *  @return privilege in string
+     */
+    std::string convertToPrivLimitString(const uint4_t& value);
+
+    /** @brief function to load CS Privilege Levels from json file/files to map
+     *
+     */
+    void loadCSPrivilegesToMap();
+
+    /** @brief function to update CS privileges map from json object data,
+     * jsonData
+     *
+     */
+    void updateCSPrivilegesMap(const nlohmann::json& jsonData);
+};
+
+/** @brief function to create static CipherConfig object
+ *
+ *  @param[in] csFileName - user setting cipher suite privilege file name
+ *  @param[in] csDefaultFileName - default cipher suite privilege file name
+ *
+ *  @return static CipherConfig object
+ */
+CipherConfig& getCipherConfigObject(const std::string& csFileName,
+                                    const std::string& csDefaultFileName);
+} // namespace ipmi
diff --git a/user_channel/file.hpp b/user_channel/file.hpp
new file mode 100644
index 0000000..5be3a18
--- /dev/null
+++ b/user_channel/file.hpp
@@ -0,0 +1,84 @@
+#pragma once
+
+#include <stdio.h>
+
+#include <filesystem>
+namespace phosphor
+{
+namespace user
+{
+
+namespace fs = std::filesystem;
+
+/** @class File
+ *  @brief Responsible for handling file pointer
+ *  Needed by putspent(3)
+ */
+class File
+{
+  private:
+    /** @brief handler for operating on file */
+    FILE* fp = nullptr;
+
+    /** @brief File name. Needed in the case where the temp
+     *         needs to be removed
+     */
+    const std::string& name;
+
+    /** @brief Should the file be removed at exit */
+    bool removeOnExit = false;
+
+  public:
+    File() = delete;
+    File(const File&) = delete;
+    File& operator=(const File&) = delete;
+    File(File&&) = delete;
+    File& operator=(File&&) = delete;
+
+    /** @brief Opens file and uses it to do file operation
+     *
+     *  @param[in] name         - File name
+     *  @param[in] mode         - File open mode
+     *  @param[in] removeOnExit - File to be removed at exit or no
+     */
+    File(const std::string& name, const std::string& mode,
+         bool removeOnExit = false) : name(name), removeOnExit(removeOnExit)
+    {
+        fp = fopen(name.c_str(), mode.c_str());
+    }
+
+    /** @brief Opens file using provided file descriptor
+     *
+     *  @param[in] fd           - File descriptor
+     *  @param[in] name         - File name
+     *  @param[in] mode         - File open mode
+     *  @param[in] removeOnExit - File to be removed at exit or no
+     */
+    File(int fd, const std::string& name, const std::string& mode,
+         bool removeOnExit = false) : name(name), removeOnExit(removeOnExit)
+    {
+        fp = fdopen(fd, mode.c_str());
+    }
+
+    ~File()
+    {
+        if (fp)
+        {
+            fclose(fp);
+        }
+
+        // Needed for exception safety
+        if (removeOnExit && fs::exists(name))
+        {
+            fs::remove(name);
+        }
+    }
+
+    auto operator()()
+    {
+        return fp;
+    }
+};
+
+} // namespace user
+} // namespace phosphor
diff --git a/user_channel/meson.build b/user_channel/meson.build
new file mode 100644
index 0000000..701c86a
--- /dev/null
+++ b/user_channel/meson.build
@@ -0,0 +1,101 @@
+user_channel_inc = include_directories('.')
+
+channellayer_pre = declare_dependency(
+    include_directories: [root_inc, user_channel_inc],
+    dependencies: [
+        crypto,
+        ipmid_dep,
+        libsystemd_dep,
+        nlohmann_json_dep,
+        phosphor_dbus_interfaces_dep,
+        phosphor_logging_dep,
+    ],
+)
+
+channellayer_src = ['channel_layer.cpp', 'channel_mgmt.cpp', 'cipher_mgmt.cpp']
+
+channellayer_lib = library(
+    'channellayer',
+    channellayer_src,
+    implicit_include_directories: false,
+    dependencies: channellayer_pre,
+    version: meson.project_version(),
+    install: true,
+    install_dir: get_option('libdir'),
+    override_options: ['b_lundef=false'],
+)
+
+channellayer_dep = declare_dependency(
+    link_with: channellayer_lib,
+    dependencies: channellayer_pre,
+)
+
+import('pkgconfig').generate(
+    channellayer_lib,
+    name: 'libchannellayer',
+    version: meson.project_version(),
+    description: 'libchannellayer',
+)
+
+if get_option('libuserlayer').allowed()
+    userlayer_pre = declare_dependency(
+        include_directories: [root_inc, user_channel_inc],
+        dependencies: [
+            channellayer_dep,
+            crypto,
+            ipmid_dep,
+            libsystemd_dep,
+            nlohmann_json_dep,
+            pam,
+            phosphor_dbus_interfaces_dep,
+            phosphor_logging_dep,
+        ],
+    )
+
+    userlayer_src = ['user_layer.cpp', 'user_mgmt.cpp', 'passwd_mgr.cpp']
+
+    userlayer_lib = library(
+        'userlayer',
+        userlayer_src,
+        implicit_include_directories: false,
+        dependencies: userlayer_pre,
+        version: meson.project_version(),
+        install: true,
+        install_dir: get_option('libdir'),
+        override_options: ['b_lundef=false'],
+    )
+
+    userlayer_dep = declare_dependency(
+        link_with: userlayer_lib,
+        dependencies: userlayer_pre,
+    )
+
+    usercmds_pre = declare_dependency(
+        include_directories: [root_inc, user_channel_inc],
+        dependencies: [
+            phosphor_logging_dep,
+            ipmid_dep,
+            userlayer_dep,
+            channellayer_dep,
+        ],
+    )
+
+    usercmds_lib = library(
+        'usercmds',
+        'usercommands.cpp',
+        implicit_include_directories: false,
+        dependencies: usercmds_pre,
+        install: true,
+        install_dir: get_option('libdir') / 'ipmid-providers',
+        version: meson.project_version(),
+        override_options: ['b_lundef=false'],
+    )
+
+    import('pkgconfig').generate(
+        userlayer_lib,
+        name: 'libuserlayer',
+        version: meson.project_version(),
+        description: 'libuserlayer',
+    )
+
+endif
diff --git a/user_channel/passwd_mgr.cpp b/user_channel/passwd_mgr.cpp
new file mode 100644
index 0000000..929a83c
--- /dev/null
+++ b/user_channel/passwd_mgr.cpp
@@ -0,0 +1,603 @@
+/*
+// Copyright (c) 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.
+*/
+
+#include "passwd_mgr.hpp"
+
+#include "file.hpp"
+#include "shadowlock.hpp"
+
+#include <openssl/hmac.h>
+#include <openssl/rand.h>
+#include <openssl/sha.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <cerrno>
+#include <cstring>
+#include <fstream>
+#include <iomanip>
+
+namespace ipmi
+{
+
+static const char* passwdFileName = "/etc/ipmi_pass";
+static const char* encryptKeyFileName = "/etc/key_file";
+static const size_t maxKeySize = 8;
+
+constexpr mode_t modeMask =
+    (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO);
+
+#define META_PASSWD_SIG "=OPENBMC="
+
+/*
+ * Meta data struct for encrypted password file
+ */
+struct MetaPassStruct
+{
+    char signature[10];
+    unsigned char reseved[2];
+    size_t hashSize;
+    size_t ivSize;
+    size_t dataSize;
+    size_t padSize;
+    size_t macSize;
+};
+
+PasswdMgr::PasswdMgr()
+{
+    restrictFilesPermission();
+    initPasswordMap();
+}
+
+void PasswdMgr::restrictFilesPermission(void)
+{
+    struct stat st = {};
+    // Restrict file permission to owner read & write
+    if (stat(passwdFileName, &st) == 0)
+    {
+        if ((st.st_mode & modeMask) != (S_IRUSR | S_IWUSR))
+        {
+            if (chmod(passwdFileName, S_IRUSR | S_IWUSR) == -1)
+            {
+                lg2::debug("Error setting chmod for ipmi_pass file");
+            }
+        }
+    }
+
+    if (stat(encryptKeyFileName, &st) == 0)
+    {
+        if ((st.st_mode & modeMask) != (S_IRUSR | S_IWUSR))
+        {
+            if (chmod(encryptKeyFileName, S_IRUSR | S_IWUSR) == -1)
+            {
+                lg2::debug("Error setting chmod for ipmi_pass file");
+            }
+        }
+    }
+}
+
+SecureString PasswdMgr::getPasswdByUserName(const std::string& userName)
+{
+    checkAndReload();
+    auto iter = passwdMapList.find(userName);
+    if (iter == passwdMapList.end())
+    {
+        return SecureString();
+    }
+    return iter->second;
+}
+
+int PasswdMgr::updateUserEntry(const std::string& userName,
+                               const std::string& newUserName)
+{
+    std::time_t updatedTime = getUpdatedFileTime();
+    // Check file time stamp to know passwdMapList is up-to-date.
+    // If not up-to-date, then updatePasswdSpecialFile will read and
+    // check the user entry existance.
+    if (fileLastUpdatedTime == updatedTime && updatedTime != -EIO)
+    {
+        if (passwdMapList.find(userName) == passwdMapList.end())
+        {
+            lg2::debug("User not found");
+            return 0;
+        }
+    }
+
+    // Write passwdMap to Encryted file
+    if (updatePasswdSpecialFile(userName, newUserName) != 0)
+    {
+        lg2::debug("Passwd file update failed");
+        return -EIO;
+    }
+
+    lg2::debug("Passwd file updated successfully");
+    return 0;
+}
+
+void PasswdMgr::checkAndReload(void)
+{
+    std::time_t updatedTime = getUpdatedFileTime();
+    if (fileLastUpdatedTime != updatedTime && updatedTime != -1)
+    {
+        lg2::debug("Reloading password map list");
+        passwdMapList.clear();
+        initPasswordMap();
+    }
+}
+
+int PasswdMgr::encryptDecryptData(
+    bool doEncrypt, const EVP_CIPHER* cipher, uint8_t* key, size_t keyLen,
+    uint8_t* iv, size_t ivLen, uint8_t* inBytes, size_t inBytesLen,
+    uint8_t* mac, size_t* macLen, unsigned char* outBytes, size_t* outBytesLen)
+{
+    if (cipher == nullptr || key == nullptr || iv == nullptr ||
+        inBytes == nullptr || outBytes == nullptr || mac == nullptr ||
+        inBytesLen == 0 || (size_t)EVP_CIPHER_key_length(cipher) > keyLen ||
+        (size_t)EVP_CIPHER_iv_length(cipher) > ivLen)
+    {
+        lg2::debug("Error Invalid Inputs");
+        return -EINVAL;
+    }
+
+    if (!doEncrypt)
+    {
+        // verify MAC before decrypting the data.
+        std::array<uint8_t, EVP_MAX_MD_SIZE> calMac;
+        size_t calMacLen = calMac.size();
+        // calculate MAC for the encrypted message.
+        if (nullptr ==
+            HMAC(EVP_sha256(), key, keyLen, inBytes, inBytesLen, calMac.data(),
+                 reinterpret_cast<unsigned int*>(&calMacLen)))
+        {
+            lg2::debug("Error: Failed to calculate MAC");
+            return -EIO;
+        }
+        if (!((calMacLen == *macLen) &&
+              (std::memcmp(calMac.data(), mac, calMacLen) == 0)))
+        {
+            lg2::debug("Authenticated message doesn't match");
+            return -EBADMSG;
+        }
+    }
+
+    std::unique_ptr<EVP_CIPHER_CTX, decltype(&::EVP_CIPHER_CTX_free)> ctx(
+        EVP_CIPHER_CTX_new(), ::EVP_CIPHER_CTX_free);
+
+    if (!ctx)
+    {
+        lg2::debug("Error: EVP_CIPHER_CTX is NULL");
+        return -ENOMEM;
+    }
+
+    EVP_CIPHER_CTX_set_padding(ctx.get(), 1);
+
+    // Set key & IV
+    int retval = EVP_CipherInit_ex(ctx.get(), cipher, nullptr, key, iv,
+                                   static_cast<int>(doEncrypt));
+    if (!retval)
+    {
+        lg2::debug("EVP_CipherInit_ex failed: {RET_VAL}", "RET_VAL", retval);
+        return -EIO;
+    }
+
+    int outLen = 0, outEVPLen = 0;
+    if ((retval = EVP_CipherUpdate(ctx.get(), outBytes + outLen, &outEVPLen,
+                                   inBytes, inBytesLen)))
+    {
+        outLen += outEVPLen;
+        if ((retval =
+                 EVP_CipherFinal(ctx.get(), outBytes + outLen, &outEVPLen)))
+        {
+            outLen += outEVPLen;
+            *outBytesLen = outLen;
+        }
+        else
+        {
+            lg2::debug("EVP_CipherFinal fails: {RET_VAL}", "RET_VAL", retval);
+            return -EIO;
+        }
+    }
+    else
+    {
+        lg2::debug("EVP_CipherUpdate fails: {RET_VAL}", "RET_VAL", retval);
+        return -EIO;
+    }
+
+    if (doEncrypt)
+    {
+        // Create MAC for the encrypted message
+        if (nullptr == HMAC(EVP_sha256(), key, keyLen, outBytes, *outBytesLen,
+                            mac, reinterpret_cast<unsigned int*>(macLen)))
+        {
+            lg2::debug("Failed to create authentication");
+            return -EIO;
+        }
+    }
+    return 0;
+}
+
+void PasswdMgr::initPasswordMap(void)
+{
+    // TODO  phosphor-host-ipmid#170 phosphor::user::shadow::Lock lock{};
+    SecureString dataBuf;
+
+    if (readPasswdFileData(dataBuf) != 0)
+    {
+        lg2::debug("Error in reading the encrypted pass file");
+        return;
+    }
+
+    if (dataBuf.size() != 0)
+    {
+        // populate the user list with password
+        char* outPtr = dataBuf.data();
+        char* nToken = nullptr;
+        char* linePtr = strtok_r(outPtr, "\n", &nToken);
+        size_t lineSize = 0;
+        while (linePtr != nullptr)
+        {
+            size_t userEPos = 0;
+            SecureString lineStr(linePtr);
+            if ((userEPos = lineStr.find(":")) != std::string::npos)
+            {
+                lineSize = lineStr.size();
+                passwdMapList.emplace(
+                    lineStr.substr(0, userEPos),
+                    lineStr.substr(userEPos + 1, lineSize - (userEPos + 1)));
+            }
+            linePtr = strtok_r(nullptr, "\n", &nToken);
+        }
+    }
+
+    // Update the timestamp
+    fileLastUpdatedTime = getUpdatedFileTime();
+    return;
+}
+
+int PasswdMgr::readPasswdFileData(SecureString& outBytes)
+{
+    std::array<uint8_t, maxKeySize> keyBuff;
+    std::ifstream keyFile(encryptKeyFileName, std::ios::in | std::ios::binary);
+    if (!keyFile.is_open())
+    {
+        lg2::debug("Error in opening encryption key file");
+        return -EIO;
+    }
+    keyFile.read(reinterpret_cast<char*>(keyBuff.data()), keyBuff.size());
+    if (keyFile.fail())
+    {
+        lg2::debug("Error in reading encryption key file");
+        return -EIO;
+    }
+
+    std::ifstream passwdFile(passwdFileName, std::ios::in | std::ios::binary);
+    if (!passwdFile.is_open())
+    {
+        lg2::debug("Error in opening ipmi password file");
+        return -EIO;
+    }
+
+    // calculate file size and read the data
+    passwdFile.seekg(0, std::ios::end);
+    ssize_t fileSize = passwdFile.tellg();
+    passwdFile.seekg(0, std::ios::beg);
+    std::vector<uint8_t> input(fileSize);
+    passwdFile.read(reinterpret_cast<char*>(input.data()), fileSize);
+    if (passwdFile.fail())
+    {
+        lg2::debug("Error in reading encryption key file");
+        return -EIO;
+    }
+
+    // verify the signature first
+    MetaPassStruct* metaData = reinterpret_cast<MetaPassStruct*>(input.data());
+    if (std::strncmp(metaData->signature, META_PASSWD_SIG,
+                     sizeof(metaData->signature)))
+    {
+        lg2::debug("Error signature mismatch in password file");
+        return -EBADMSG;
+    }
+
+    size_t inBytesLen = metaData->dataSize + metaData->padSize;
+    // If data is empty i.e no password map then return success
+    if (inBytesLen == 0)
+    {
+        lg2::debug("Empty password file");
+        return 0;
+    }
+
+    // compute the key needed to decrypt
+    std::array<uint8_t, EVP_MAX_KEY_LENGTH> key;
+    auto keyLen = key.size();
+    if (nullptr == HMAC(EVP_sha256(), keyBuff.data(), keyBuff.size(),
+                        input.data() + sizeof(*metaData), metaData->hashSize,
+                        key.data(), reinterpret_cast<unsigned int*>(&keyLen)))
+    {
+        lg2::debug("Failed to create MAC for authentication");
+        return -EIO;
+    }
+
+    // decrypt the data
+    uint8_t* iv = input.data() + sizeof(*metaData) + metaData->hashSize;
+    size_t ivLen = metaData->ivSize;
+    uint8_t* inBytes = iv + ivLen;
+    uint8_t* mac = inBytes + inBytesLen;
+    size_t macLen = metaData->macSize;
+
+    size_t outBytesLen = 0;
+    // Resize to actual data size
+    outBytes.resize(inBytesLen + EVP_MAX_BLOCK_LENGTH, '\0');
+    if (encryptDecryptData(false, EVP_aes_128_cbc(), key.data(), keyLen, iv,
+                           ivLen, inBytes, inBytesLen, mac, &macLen,
+                           reinterpret_cast<unsigned char*>(outBytes.data()),
+                           &outBytesLen) != 0)
+    {
+        lg2::debug("Error in decryption");
+        return -EIO;
+    }
+    // Resize the vector to outBytesLen
+    outBytes.resize(outBytesLen);
+
+    OPENSSL_cleanse(key.data(), keyLen);
+    OPENSSL_cleanse(iv, ivLen);
+
+    return 0;
+}
+
+int PasswdMgr::updatePasswdSpecialFile(const std::string& userName,
+                                       const std::string& newUserName)
+{
+    // TODO  phosphor-host-ipmid#170 phosphor::user::shadow::Lock lock{};
+
+    size_t bytesWritten = 0;
+    size_t inBytesLen = 0;
+    size_t isUsrFound = false;
+    const EVP_CIPHER* cipher = EVP_aes_128_cbc();
+    SecureString dataBuf;
+
+    // Read the encrypted file and get the file data
+    // Check user existance and return if not exist.
+    if (readPasswdFileData(dataBuf) != 0)
+    {
+        lg2::debug("Error in reading the encrypted pass file");
+        return -EIO;
+    }
+
+    if (dataBuf.size() != 0)
+    {
+        inBytesLen = dataBuf.size() + newUserName.size() +
+                     EVP_CIPHER_block_size(cipher);
+    }
+
+    SecureString inBytes(inBytesLen, '\0');
+    if (inBytesLen != 0)
+    {
+        char* outPtr = reinterpret_cast<char*>(dataBuf.data());
+        char* nToken = nullptr;
+        char* linePtr = strtok_r(outPtr, "\n", &nToken);
+        while (linePtr != nullptr)
+        {
+            size_t userEPos = 0;
+
+            SecureString lineStr(linePtr);
+            if ((userEPos = lineStr.find(":")) != std::string::npos)
+            {
+                if (userName.compare(lineStr.substr(0, userEPos)) == 0)
+                {
+                    isUsrFound = true;
+                    if (!newUserName.empty())
+                    {
+                        bytesWritten += std::snprintf(
+                            &inBytes[0] + bytesWritten,
+                            (inBytesLen - bytesWritten), "%s%s\n",
+                            newUserName.c_str(),
+                            lineStr.substr(userEPos, lineStr.size()).data());
+                    }
+                }
+                else
+                {
+                    bytesWritten += std::snprintf(&inBytes[0] + bytesWritten,
+                                                  (inBytesLen - bytesWritten),
+                                                  "%s\n", lineStr.data());
+                }
+            }
+            linePtr = strtok_r(nullptr, "\n", &nToken);
+        }
+        inBytesLen = bytesWritten;
+    }
+    if (!isUsrFound)
+    {
+        lg2::debug("User doesn't exist");
+        return 0;
+    }
+
+    // Read the key buff from key file
+    std::array<uint8_t, maxKeySize> keyBuff;
+    std::ifstream keyFile(encryptKeyFileName, std::ios::in | std::ios::binary);
+    if (!keyFile.good())
+    {
+        lg2::debug("Error in opening encryption key file");
+        return -EIO;
+    }
+    keyFile.read(reinterpret_cast<char*>(keyBuff.data()), keyBuff.size());
+    if (keyFile.fail())
+    {
+        lg2::debug("Error in reading encryption key file");
+        return -EIO;
+    }
+    keyFile.close();
+
+    // Read the original passwd file mode
+    struct stat st = {};
+    if (stat(passwdFileName, &st) != 0)
+    {
+        lg2::debug("Error in getting password file fstat()");
+        return -EIO;
+    }
+
+    // Create temporary file for write
+    std::string pwdFile(passwdFileName);
+    std::vector<char> tempFileName(pwdFile.begin(), pwdFile.end());
+    std::vector<char> fileTemplate = {'_', '_', 'X', 'X', 'X',
+                                      'X', 'X', 'X', '\0'};
+    tempFileName.insert(tempFileName.end(), fileTemplate.begin(),
+                        fileTemplate.end());
+    int fd = mkstemp((char*)tempFileName.data());
+    if (fd == -1)
+    {
+        lg2::debug("Error creating temp file");
+        return -EIO;
+    }
+
+    std::string strTempFileName(tempFileName.data());
+    // Open the temp file for writing from provided fd
+    // By "true", remove it at exit if still there.
+    // This is needed to cleanup the temp file at exception
+    phosphor::user::File temp(fd, strTempFileName, "w", true);
+    if ((temp)() == nullptr)
+    {
+        close(fd);
+        lg2::debug("Error creating temp file");
+        return -EIO;
+    }
+
+    // Set the file mode as read-write for owner only
+    if (fchmod(fileno((temp)()), S_IRUSR | S_IWUSR) < 0)
+    {
+        lg2::debug("Error setting fchmod for temp file");
+        return -EIO;
+    }
+
+    const EVP_MD* digest = EVP_sha256();
+    size_t hashLen = EVP_MD_block_size(digest);
+    std::vector<uint8_t> hash(hashLen);
+    size_t ivLen = EVP_CIPHER_iv_length(cipher);
+    std::vector<uint8_t> iv(ivLen);
+    std::array<uint8_t, EVP_MAX_KEY_LENGTH> key;
+    size_t keyLen = key.size();
+    std::array<uint8_t, EVP_MAX_MD_SIZE> mac;
+    size_t macLen = mac.size();
+
+    // Create random hash and generate hash key which will be used for
+    // encryption.
+    if (RAND_bytes(hash.data(), hashLen) != 1)
+    {
+        lg2::debug("Hash genertion failed, bailing out");
+        return -EIO;
+    }
+    if (nullptr ==
+        HMAC(digest, keyBuff.data(), keyBuff.size(), hash.data(), hashLen,
+             key.data(), reinterpret_cast<unsigned int*>(&keyLen)))
+    {
+        lg2::debug("Failed to create MAC for authentication");
+        return -EIO;
+    }
+
+    // Generate IV values
+    if (RAND_bytes(iv.data(), ivLen) != 1)
+    {
+        lg2::debug("UV genertion failed, bailing out");
+        return -EIO;
+    }
+
+    // Encrypt the input data
+    std::vector<uint8_t> outBytes(inBytesLen + EVP_MAX_BLOCK_LENGTH);
+    size_t outBytesLen = 0;
+    if (inBytesLen != 0)
+    {
+        if (encryptDecryptData(
+                true, EVP_aes_128_cbc(), key.data(), keyLen, iv.data(), ivLen,
+                reinterpret_cast<unsigned char*>(inBytes.data()), inBytesLen,
+                mac.data(), &macLen, outBytes.data(), &outBytesLen) != 0)
+        {
+            lg2::debug("Error while encrypting the data");
+            return -EIO;
+        }
+        outBytes[outBytesLen] = 0;
+    }
+    OPENSSL_cleanse(key.data(), keyLen);
+
+    // Update the meta password structure.
+    MetaPassStruct metaData = {META_PASSWD_SIG, {0, 0}, 0, 0, 0, 0, 0};
+    metaData.hashSize = hashLen;
+    metaData.ivSize = ivLen;
+    metaData.dataSize = bytesWritten;
+    metaData.padSize = outBytesLen - bytesWritten;
+    metaData.macSize = macLen;
+
+    if (fwrite(&metaData, 1, sizeof(metaData), (temp)()) != sizeof(metaData))
+    {
+        lg2::debug("Error in writing meta data");
+        return -EIO;
+    }
+
+    if (fwrite(&hash[0], 1, hashLen, (temp)()) != hashLen)
+    {
+        lg2::debug("Error in writing hash data");
+        return -EIO;
+    }
+
+    if (fwrite(&iv[0], 1, ivLen, (temp)()) != ivLen)
+    {
+        lg2::debug("Error in writing IV data");
+        return -EIO;
+    }
+
+    if (fwrite(&outBytes[0], 1, outBytesLen, (temp)()) != outBytesLen)
+    {
+        lg2::debug("Error in writing encrypted data");
+        return -EIO;
+    }
+
+    if (fwrite(&mac[0], 1, macLen, (temp)()) != macLen)
+    {
+        lg2::debug("Error in writing MAC data");
+        return -EIO;
+    }
+
+    if (fflush((temp)()))
+    {
+        lg2::debug("File fflush error while writing entries to special file");
+        return -EIO;
+    }
+
+    OPENSSL_cleanse(iv.data(), ivLen);
+
+    // Rename the tmp  file to actual file
+    if (std::rename(strTempFileName.data(), passwdFileName) != 0)
+    {
+        lg2::debug("Failed to rename tmp file to ipmi-pass");
+        return -EIO;
+    }
+
+    return 0;
+}
+
+std::time_t PasswdMgr::getUpdatedFileTime()
+{
+    struct stat fileStat = {};
+    if (stat(passwdFileName, &fileStat) != 0)
+    {
+        lg2::debug("Error - Getting passwd file time stamp");
+        return -EIO;
+    }
+    return fileStat.st_mtime;
+}
+
+} // namespace ipmi
diff --git a/user_channel/passwd_mgr.hpp b/user_channel/passwd_mgr.hpp
new file mode 100644
index 0000000..8932ac5
--- /dev/null
+++ b/user_channel/passwd_mgr.hpp
@@ -0,0 +1,133 @@
+/*
+// Copyright (c) 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 <openssl/evp.h>
+
+#include <ipmid/types.hpp>
+
+#include <ctime>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace ipmi
+{
+
+class PasswdMgr
+{
+  public:
+    ~PasswdMgr() = default;
+    PasswdMgr(const PasswdMgr&) = delete;
+    PasswdMgr& operator=(const PasswdMgr&) = delete;
+    PasswdMgr(PasswdMgr&&) = delete;
+    PasswdMgr& operator=(PasswdMgr&&) = delete;
+
+    /** @brief Constructs user password list
+     *
+     */
+    PasswdMgr();
+
+    /** @brief Get password for the user
+     *
+     *  @param[in] userName - user name
+     *
+     * @return password string. will return empty string, if unable to locate
+     * the user
+     */
+    SecureString getPasswdByUserName(const std::string& userName);
+
+    /** @brief Update / clear  username and password entry for the specified
+     * user
+     *
+     *  @param[in] userName - user name that has to be renamed / deleted
+     *  @param[in] newUserName - new user name. If empty, userName will be
+     *   deleted.
+     *
+     * @return error response
+     */
+    int updateUserEntry(const std::string& userName,
+                        const std::string& newUserName);
+
+  private:
+    using UserName = std::string;
+    using Password = SecureString;
+    std::unordered_map<UserName, Password> passwdMapList;
+    std::time_t fileLastUpdatedTime;
+
+    /** @brief restrict file permission
+     *
+     */
+    void restrictFilesPermission(void);
+    /** @brief check timestamp and reload password map if required
+     *
+     */
+    void checkAndReload(void);
+    /** @brief initializes passwdMapList by reading the encrypted file
+     *
+     * Initializes the passwordMapList members after decrypting the
+     * password file. passwordMapList will be used further in IPMI
+     * authentication.
+     */
+    void initPasswordMap(void);
+
+    /** @brief Function to read the encrypted password file data
+     *
+     *  @param[out] outBytes - vector to hold decrypted password file data
+     *
+     * @return error response
+     */
+    int readPasswdFileData(SecureString& outBytes);
+    /** @brief  Updates special password file by clearing the password entry
+     *  for the user specified.
+     *
+     *  @param[in] userName - user name that has to be renamed / deleted
+     *  @param[in] newUserName - new user name. If empty, userName will be
+     *   deleted.
+     *
+     * @return error response
+     */
+    int updatePasswdSpecialFile(const std::string& userName,
+                                const std::string& newUserName);
+    /** @brief encrypts or decrypt the data provided
+     *
+     *  @param[in] doEncrypt - do encrypt if set to true, else do decrypt.
+     *  @param[in] cipher - cipher to be used
+     *  @param[in] key - pointer to the key
+     *  @param[in] keyLen - Length of the key to be used
+     *  @param[in] iv - pointer to initialization vector
+     *  @param[in] ivLen - Length of the iv
+     *  @param[in] inBytes - input data to be encrypted / decrypted
+     *  @param[in] inBytesLen - input size to be encrypted / decrypted
+     *  @param[in] mac - message authentication code - to figure out corruption
+     *  @param[in] macLen - size of MAC
+     *  @param[in] outBytes - ptr to store output bytes
+     *  @param[in] outBytesLen - outbut data length.
+     *
+     * @return error response
+     */
+    int encryptDecryptData(
+        bool doEncrypt, const EVP_CIPHER* cipher, uint8_t* key, size_t keyLen,
+        uint8_t* iv, size_t ivLen, uint8_t* inBytes, size_t inBytesLen,
+        uint8_t* mac, size_t* macLen, uint8_t* outBytes, size_t* outBytesLen);
+
+    /** @brief  returns updated file time of passwd file entry.
+     *
+     * @return timestamp or -1 for error.
+     */
+    std::time_t getUpdatedFileTime();
+};
+
+} // namespace ipmi
diff --git a/user_channel/shadowlock.hpp b/user_channel/shadowlock.hpp
new file mode 100644
index 0000000..1dc5f3b
--- /dev/null
+++ b/user_channel/shadowlock.hpp
@@ -0,0 +1,51 @@
+#pragma once
+
+#include <shadow.h>
+
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+namespace phosphor
+{
+namespace user
+{
+namespace shadow
+{
+
+using InternalFailure =
+    sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+using namespace phosphor::logging;
+
+/** @class Lock
+ *  @brief Responsible for locking and unlocking /etc/shadow
+ */
+class Lock
+{
+  public:
+    Lock(const Lock&) = delete;
+    Lock& operator=(const Lock&) = delete;
+    Lock(Lock&&) = delete;
+    Lock& operator=(Lock&&) = delete;
+
+    /** @brief Default constructor that just locks the shadow file */
+    Lock()
+    {
+        if (!lckpwdf())
+        {
+            lg2::error("Locking Shadow failed");
+            elog<InternalFailure>();
+        }
+    }
+    ~Lock()
+    {
+        if (!ulckpwdf())
+        {
+            lg2::error("Un-Locking Shadow failed");
+            elog<InternalFailure>();
+        }
+    }
+};
+
+} // namespace shadow
+} // namespace user
+} // namespace phosphor
diff --git a/user_channel/user_layer.cpp b/user_channel/user_layer.cpp
new file mode 100644
index 0000000..5a2e7e0
--- /dev/null
+++ b/user_channel/user_layer.cpp
@@ -0,0 +1,226 @@
+/*
+// Copyright (c) 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.
+*/
+
+#include "user_layer.hpp"
+
+#include "passwd_mgr.hpp"
+#include "user_mgmt.hpp"
+
+namespace
+{
+ipmi::PasswdMgr passwdMgr;
+}
+
+namespace ipmi
+{
+
+Cc ipmiUserInit()
+{
+    getUserAccessObject();
+    return ccSuccess;
+}
+
+SecureString ipmiUserGetPassword(const std::string& userName)
+{
+    return passwdMgr.getPasswdByUserName(userName);
+}
+
+Cc ipmiClearUserEntryPassword(const std::string& userName)
+{
+    if (passwdMgr.updateUserEntry(userName, "") != 0)
+    {
+        return ccUnspecifiedError;
+    }
+    return ccSuccess;
+}
+
+Cc ipmiRenameUserEntryPassword(const std::string& userName,
+                               const std::string& newUserName)
+{
+    if (passwdMgr.updateUserEntry(userName, newUserName) != 0)
+    {
+        return ccUnspecifiedError;
+    }
+    return ccSuccess;
+}
+
+bool ipmiUserIsValidUserId(const uint8_t userId)
+{
+    return UserAccess::isValidUserId(userId);
+}
+
+bool ipmiUserIsValidPrivilege(const uint8_t priv)
+{
+    return UserAccess::isValidPrivilege(priv);
+}
+
+uint8_t ipmiUserGetUserId(const std::string& userName)
+{
+    return getUserAccessObject().getUserId(userName);
+}
+
+Cc ipmiUserSetUserName(const uint8_t userId, const char* userName)
+{
+    std::string newUser(userName, 0, ipmiMaxUserName);
+    return getUserAccessObject().setUserName(userId, newUser);
+}
+
+Cc ipmiUserSetUserName(const uint8_t userId, const std::string& userName)
+{
+    std::string newUser(userName, 0, ipmiMaxUserName);
+    return getUserAccessObject().setUserName(userId, newUser);
+}
+
+Cc ipmiUserGetUserName(const uint8_t userId, std::string& userName)
+{
+    return getUserAccessObject().getUserName(userId, userName);
+}
+
+Cc ipmiUserSetUserPassword(const uint8_t userId, const char* userPassword)
+{
+    return getUserAccessObject().setUserPassword(userId, userPassword);
+}
+
+Cc ipmiSetSpecialUserPassword(const std::string& userName,
+                              const SecureString& userPassword)
+{
+    return getUserAccessObject().setSpecialUserPassword(userName, userPassword);
+}
+
+Cc ipmiUserGetAllCounts(uint8_t& maxChUsers, uint8_t& enabledUsers,
+                        uint8_t& fixedUsers)
+{
+    maxChUsers = ipmiMaxUsers;
+    UsersTbl* userData = getUserAccessObject().getUsersTblPtr();
+    enabledUsers = 0;
+    fixedUsers = 0;
+    // user index 0 is reserved, starts with 1
+    for (size_t count = 1; count <= ipmiMaxUsers; ++count)
+    {
+        if (userData->user[count].userEnabled)
+        {
+            enabledUsers++;
+        }
+        if (userData->user[count].fixedUserName)
+        {
+            fixedUsers++;
+        }
+    }
+    return ccSuccess;
+}
+
+Cc ipmiUserUpdateEnabledState(const uint8_t userId, const bool& state)
+{
+    return getUserAccessObject().setUserEnabledState(userId, state);
+}
+
+Cc ipmiUserCheckEnabled(const uint8_t userId, bool& state)
+{
+    if (!UserAccess::isValidUserId(userId))
+    {
+        return ccParmOutOfRange;
+    }
+    UserInfo* userInfo = getUserAccessObject().getUserInfo(userId);
+    state = userInfo->userEnabled;
+    return ccSuccess;
+}
+
+Cc ipmiUserGetPrivilegeAccess(const uint8_t userId, const uint8_t chNum,
+                              PrivAccess& privAccess)
+{
+    if (!UserAccess::isValidChannel(chNum))
+    {
+        return ccInvalidFieldRequest;
+    }
+    if (!UserAccess::isValidUserId(userId))
+    {
+        return ccParmOutOfRange;
+    }
+    UserInfo* userInfo = getUserAccessObject().getUserInfo(userId);
+    privAccess.privilege = userInfo->userPrivAccess[chNum].privilege;
+    privAccess.ipmiEnabled = userInfo->userPrivAccess[chNum].ipmiEnabled;
+    privAccess.linkAuthEnabled =
+        userInfo->userPrivAccess[chNum].linkAuthEnabled;
+    privAccess.accessCallback = userInfo->userPrivAccess[chNum].accessCallback;
+    return ccSuccess;
+}
+
+Cc ipmiUserSetPrivilegeAccess(const uint8_t userId, const uint8_t chNum,
+                              const PrivAccess& privAccess,
+                              const bool& otherPrivUpdates)
+{
+    UserPrivAccess userPrivAccess;
+    userPrivAccess.privilege = privAccess.privilege;
+    if (otherPrivUpdates)
+    {
+        userPrivAccess.ipmiEnabled = privAccess.ipmiEnabled;
+        userPrivAccess.linkAuthEnabled = privAccess.linkAuthEnabled;
+        userPrivAccess.accessCallback = privAccess.accessCallback;
+    }
+    return getUserAccessObject().setUserPrivilegeAccess(
+        userId, chNum, userPrivAccess, otherPrivUpdates);
+}
+
+bool ipmiUserPamAuthenticate(std::string_view userName,
+                             std::string_view userPassword)
+{
+    return pamUserCheckAuthenticate(userName, userPassword);
+}
+
+Cc ipmiUserSetUserPayloadAccess(const uint8_t chNum, const uint8_t operation,
+                                const uint8_t userId,
+                                const PayloadAccess& payloadAccess)
+{
+    if (!UserAccess::isValidChannel(chNum))
+    {
+        return ccInvalidFieldRequest;
+    }
+    if (!UserAccess::isValidUserId(userId))
+    {
+        return ccParmOutOfRange;
+    }
+
+    return getUserAccessObject().setUserPayloadAccess(chNum, operation, userId,
+                                                      payloadAccess);
+}
+
+Cc ipmiUserGetUserPayloadAccess(const uint8_t chNum, const uint8_t userId,
+                                PayloadAccess& payloadAccess)
+{
+    if (!UserAccess::isValidChannel(chNum))
+    {
+        return ccInvalidFieldRequest;
+    }
+    if (!UserAccess::isValidUserId(userId))
+    {
+        return ccParmOutOfRange;
+    }
+
+    UserInfo* userInfo = getUserAccessObject().getUserInfo(userId);
+
+    payloadAccess.stdPayloadEnables1 =
+        userInfo->payloadAccess[chNum].stdPayloadEnables1;
+    payloadAccess.stdPayloadEnables2Reserved =
+        userInfo->payloadAccess[chNum].stdPayloadEnables2Reserved;
+    payloadAccess.oemPayloadEnables1 =
+        userInfo->payloadAccess[chNum].oemPayloadEnables1;
+    payloadAccess.oemPayloadEnables2Reserved =
+        userInfo->payloadAccess[chNum].oemPayloadEnables2Reserved;
+
+    return ccSuccess;
+}
+
+} // namespace ipmi
diff --git a/user_channel/user_layer.hpp b/user_channel/user_layer.hpp
new file mode 100644
index 0000000..ad215e3
--- /dev/null
+++ b/user_channel/user_layer.hpp
@@ -0,0 +1,270 @@
+/*
+// Copyright (c) 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/types.hpp>
+
+#include <bitset>
+#include <string>
+
+namespace ipmi
+{
+
+// TODO: Has to be replaced with proper channel number assignment logic
+/**
+ * @enum Channel Id
+ */
+enum class EChannelID : uint8_t
+{
+    chanLan1 = 0x01
+};
+
+static constexpr uint8_t invalidUserId = 0xFF;
+static constexpr uint8_t reservedUserId = 0x0;
+static constexpr uint8_t ipmiMaxUserName = 16;
+static constexpr uint8_t ipmiMaxUsers = 15;
+static constexpr uint8_t ipmiMaxChannels = 16;
+static constexpr uint8_t maxIpmi20PasswordSize = 20;
+static constexpr uint8_t maxIpmi15PasswordSize = 16;
+static constexpr uint8_t payloadsPerByte = 8;
+
+/** @struct PrivAccess
+ *
+ *  User privilege related access data as per IPMI specification.(refer spec
+ * sec 22.26)
+ */
+struct PrivAccess
+{
+#if BYTE_ORDER == LITTLE_ENDIAN
+    uint8_t privilege:4;
+    uint8_t ipmiEnabled:1;
+    uint8_t linkAuthEnabled:1;
+    uint8_t accessCallback:1;
+    uint8_t reserved:1;
+#endif
+#if BYTE_ORDER == BIG_ENDIAN
+    uint8_t reserved:1;
+    uint8_t accessCallback:1;
+    uint8_t linkAuthEnabled:1;
+    uint8_t ipmiEnabled:1;
+    uint8_t privilege:4;
+#endif
+} __attribute__((packed));
+
+/** @struct UserPayloadAccess
+ *
+ *  Structure to denote payload access restrictions applicable for a
+ *  given user and channel. (refer spec sec 24.6)
+ */
+struct PayloadAccess
+{
+    std::bitset<payloadsPerByte> stdPayloadEnables1;
+    std::bitset<payloadsPerByte> stdPayloadEnables2Reserved;
+    std::bitset<payloadsPerByte> oemPayloadEnables1;
+    std::bitset<payloadsPerByte> oemPayloadEnables2Reserved;
+};
+
+/** @brief initializes user management
+ *
+ *  @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserInit();
+
+/** @brief The ipmi get user password layer call
+ *
+ *  @param[in] userName - user name
+ *
+ *  @return password or empty string
+ */
+SecureString ipmiUserGetPassword(const std::string& userName);
+
+/** @brief The IPMI call to clear password entry associated with specified
+ * username
+ *
+ *  @param[in] userName - user name to be removed
+ *
+ *  @return 0 on success, non-zero otherwise.
+ */
+Cc ipmiClearUserEntryPassword(const std::string& userName);
+
+/** @brief The IPMI call to reuse password entry for the renamed user
+ *  to another one
+ *
+ *  @param[in] userName - user name which has to be renamed
+ *  @param[in] newUserName - new user name
+ *
+ *  @return 0 on success, non-zero otherwise.
+ */
+Cc ipmiRenameUserEntryPassword(const std::string& userName,
+                               const std::string& newUserName);
+
+/** @brief determines valid userId
+ *
+ *  @param[in] userId - user id
+ *
+ *  @return true if valid, false otherwise
+ */
+bool ipmiUserIsValidUserId(const uint8_t userId);
+
+/** @brief determines valid privilege level
+ *
+ *  @param[in] priv - privilege level
+ *
+ *  @return true if valid, false otherwise
+ */
+bool ipmiUserIsValidPrivilege(const uint8_t priv);
+
+/** @brief get user id corresponding to the user name
+ *
+ *  @param[in] userName - user name
+ *
+ *  @return userid. Will return 0xff if no user id found
+ */
+uint8_t ipmiUserGetUserId(const std::string& userName);
+
+/** @brief set's user name
+ *  This API is deprecated
+ */
+Cc ipmiUserSetUserName(const uint8_t userId, const char* userName)
+    __attribute__((deprecated));
+
+/** @brief set's user name
+ *
+ *  @param[in] userId - user id
+ *  @param[in] userName - user name
+ *
+ *  @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserSetUserName(const uint8_t userId, const std::string& userName);
+
+/** @brief set user password
+ *
+ *  @param[in] userId - user id
+ *  @param[in] userPassword - New Password
+ *
+ *  @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserSetUserPassword(const uint8_t userId, const char* userPassword);
+
+/** @brief set special user password (non-ipmi accounts)
+ *
+ *  @param[in] userName - user name
+ *  @param[in] userPassword - New Password
+ *
+ *  @return ccSuccess for success, others for failure.
+ */
+Cc ipmiSetSpecialUserPassword(const std::string& userName,
+                              const SecureString& userPassword);
+
+/** @brief get user name
+ *
+ *  @param[in] userId - user id
+ *  @param[out] userName - user name
+ *
+ *  @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserGetUserName(const uint8_t userId, std::string& userName);
+
+/** @brief provides available fixed, max, and enabled user counts
+ *
+ *  @param[out] maxChUsers - max channel users
+ *  @param[out] enabledUsers - enabled user count
+ *  @param[out] fixedUsers - fixed user count
+ *
+ *  @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserGetAllCounts(uint8_t& maxChUsers, uint8_t& enabledUsers,
+                        uint8_t& fixedUsers);
+
+/** @brief function to update user enabled state
+ *
+ *  @param[in] userId - user id
+ *..@param[in] state - state of the user to be updated, true - user enabled.
+ *
+ *  @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserUpdateEnabledState(const uint8_t userId, const bool& state);
+
+/** @brief determines whether user is enabled
+ *
+ *  @param[in] userId - user id
+ *..@param[out] state - state of the user
+ *
+ *  @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserCheckEnabled(const uint8_t userId, bool& state);
+
+/** @brief provides user privilege access data
+ *
+ *  @param[in] userId - user id
+ *  @param[in] chNum - channel number
+ *  @param[out] privAccess - privilege access data
+ *
+ *  @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserGetPrivilegeAccess(const uint8_t userId, const uint8_t chNum,
+                              PrivAccess& privAccess);
+
+/** @brief sets user privilege access data
+ *
+ *  @param[in] userId - user id
+ *  @param[in] chNum - channel number
+ *  @param[in] privAccess - privilege access data
+ *  @param[in] otherPrivUpdate - flags to indicate other fields update
+ *
+ *  @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserSetPrivilegeAccess(const uint8_t userId, const uint8_t chNum,
+                              const PrivAccess& privAccess,
+                              const bool& otherPrivUpdate);
+
+/** @brief check for user pam authentication. This is to determine, whether user
+ * is already locked out for failed login attempt
+ *
+ *  @param[in] username - username
+ *  @param[in] password - password
+ *
+ *  @return status
+ */
+bool ipmiUserPamAuthenticate(std::string_view userName,
+                             std::string_view userPassword);
+
+/** @brief sets user payload access data
+ *
+ *  @param[in] chNum - channel number
+ *  @param[in] operation - ENABLE / DISABLE operation
+ *  @param[in] userId - user id
+ *  @param[in] payloadAccess - payload access data
+ *
+ *  @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserSetUserPayloadAccess(const uint8_t chNum, const uint8_t operation,
+                                const uint8_t userId,
+                                const PayloadAccess& payloadAccess);
+
+/** @brief provides user payload access data
+ *
+ *  @param[in] chNum - channel number
+ *  @param[in] userId - user id
+ *  @param[out] payloadAccess - payload access data
+ *
+ *  @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserGetUserPayloadAccess(const uint8_t chNum, const uint8_t userId,
+                                PayloadAccess& payloadAccess);
+
+} // namespace ipmi
diff --git a/user_channel/user_mgmt.cpp b/user_channel/user_mgmt.cpp
new file mode 100644
index 0000000..29262cb
--- /dev/null
+++ b/user_channel/user_mgmt.cpp
@@ -0,0 +1,1748 @@
+/*
+// Copyright (c) 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.
+*/
+#include "user_mgmt.hpp"
+
+#include "channel_layer.hpp"
+#include "channel_mgmt.hpp"
+
+#include <security/pam_appl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <boost/interprocess/sync/named_recursive_mutex.hpp>
+#include <boost/interprocess/sync/scoped_lock.hpp>
+#include <ipmid/types.hpp>
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/User/Common/error.hpp>
+
+#include <cerrno>
+#include <fstream>
+#include <regex>
+#include <variant>
+
+namespace ipmi
+{
+
+// TODO: Move D-Bus & Object Manager related stuff, to common files
+// D-Bus property related
+static constexpr const char* dBusPropertiesInterface =
+    "org.freedesktop.DBus.Properties";
+static constexpr const char* getAllPropertiesMethod = "GetAll";
+static constexpr const char* propertiesChangedSignal = "PropertiesChanged";
+static constexpr const char* setPropertiesMethod = "Set";
+
+// Object Manager related
+static constexpr const char* dBusObjManager =
+    "org.freedesktop.DBus.ObjectManager";
+static constexpr const char* getManagedObjectsMethod = "GetManagedObjects";
+// Object Manager signals
+static constexpr const char* intfAddedSignal = "InterfacesAdded";
+static constexpr const char* intfRemovedSignal = "InterfacesRemoved";
+
+static constexpr const char* ipmiUserMutex = "ipmi_usr_mutex";
+static constexpr const char* ipmiMutexCleanupLockFile =
+    "/run/ipmi/ipmi_usr_mutex_cleanup";
+static constexpr const char* ipmiUserSignalLockFile =
+    "/run/ipmi/ipmi_usr_signal_mutex";
+static constexpr const char* ipmiUserDataFile = "/var/lib/ipmi/ipmi_user.json";
+static constexpr const char* ipmiGrpName = "ipmi";
+static constexpr size_t privNoAccess = 0xF;
+static constexpr size_t privMask = 0xF;
+
+// User manager related
+static constexpr const char* userMgrService =
+    "xyz.openbmc_project.User.Manager";
+static constexpr const char* userMgrObjBasePath = "/xyz/openbmc_project/user";
+static constexpr const char* userObjBasePath = "/xyz/openbmc_project/user";
+static constexpr const char* userMgrInterface =
+    "xyz.openbmc_project.User.Manager";
+static constexpr const char* usersInterface =
+    "xyz.openbmc_project.User.Attributes";
+static constexpr const char* deleteUserInterface =
+    "xyz.openbmc_project.Object.Delete";
+
+static constexpr const char* createUserMethod = "CreateUser";
+static constexpr const char* deleteUserMethod = "Delete";
+static constexpr const char* renameUserMethod = "RenameUser";
+// User manager signal memebers
+static constexpr const char* userRenamedSignal = "UserRenamed";
+// Mgr interface properties
+static constexpr const char* allPrivProperty = "AllPrivileges";
+static constexpr const char* allGrpProperty = "AllGroups";
+// User interface properties
+static constexpr const char* userPrivProperty = "UserPrivilege";
+static constexpr const char* userGrpProperty = "UserGroups";
+static constexpr const char* userEnabledProperty = "UserEnabled";
+
+static std::array<std::string, (PRIVILEGE_OEM + 1)> ipmiPrivIndex = {
+    "priv-reserved", // PRIVILEGE_RESERVED - 0
+    "priv-callback", // PRIVILEGE_CALLBACK - 1
+    "priv-user",     // PRIVILEGE_USER - 2
+    "priv-operator", // PRIVILEGE_OPERATOR - 3
+    "priv-admin",    // PRIVILEGE_ADMIN - 4
+    "priv-custom"    // PRIVILEGE_OEM - 5
+};
+
+using namespace phosphor::logging;
+using Json = nlohmann::json;
+
+using PrivAndGroupType = std::variant<std::string, std::vector<std::string>>;
+
+using NoResource =
+    sdbusplus::error::xyz::openbmc_project::user::common::NoResource;
+
+using InternalFailure =
+    sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+
+std::unique_ptr<sdbusplus::bus::match_t> userUpdatedSignal
+    __attribute__((init_priority(101)));
+std::unique_ptr<sdbusplus::bus::match_t> userMgrRenamedSignal
+    __attribute__((init_priority(101)));
+std::unique_ptr<sdbusplus::bus::match_t> userPropertiesSignal
+    __attribute__((init_priority(101)));
+
+void setDbusProperty(sdbusplus::bus_t& bus, const std::string& service,
+                     const std::string& objPath, const std::string& interface,
+                     const std::string& property,
+                     const DbusUserPropVariant& value)
+{
+    try
+    {
+        auto method =
+            bus.new_method_call(service.c_str(), objPath.c_str(),
+                                dBusPropertiesInterface, setPropertiesMethod);
+        method.append(interface, property, value);
+        bus.call(method);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error("Failed to set {PROPERTY}, path: {PATH}, "
+                   "interface: {INTERFACE}",
+                   "PROPERTY", property, "PATH", objPath, "INTERFACE",
+                   interface);
+        throw;
+    }
+}
+
+UserAccess& getUserAccessObject()
+{
+    static UserAccess userAccess;
+    return userAccess;
+}
+
+int getUserNameFromPath(const std::string& path, std::string& userName)
+{
+    sdbusplus::message::object_path objPath(path);
+    userName.assign(objPath.filename());
+    return 0;
+}
+
+void userUpdateHelper(UserAccess& usrAccess, const UserUpdateEvent& userEvent,
+                      const std::string& userName, const std::string& priv,
+                      const bool& enabled, const std::string& newUserName)
+{
+    UsersTbl* userData = usrAccess.getUsersTblPtr();
+    if (userEvent == UserUpdateEvent::userCreated)
+    {
+        if (usrAccess.addUserEntry(userName, priv, enabled) == false)
+        {
+            return;
+        }
+    }
+    else
+    {
+        // user index 0 is reserved, starts with 1
+        size_t usrIndex = 1;
+        for (; usrIndex <= ipmiMaxUsers; ++usrIndex)
+        {
+            std::string curName(
+                reinterpret_cast<char*>(userData->user[usrIndex].userName), 0,
+                ipmiMaxUserName);
+            if (userName == curName)
+            {
+                break; // found the entry
+            }
+        }
+        if (usrIndex > ipmiMaxUsers)
+        {
+            lg2::debug("User not found for signal, user name: {USER_NAME}, "
+                       "user event: {USER_EVENT}",
+                       "USER_NAME", userName, "USER_EVENT", userEvent);
+            return;
+        }
+        switch (userEvent)
+        {
+            case UserUpdateEvent::userDeleted:
+            {
+                usrAccess.deleteUserIndex(usrIndex);
+                break;
+            }
+            case UserUpdateEvent::userPrivUpdated:
+            {
+                uint8_t userPriv =
+                    static_cast<uint8_t>(
+                        UserAccess::convertToIPMIPrivilege(priv)) &
+                    privMask;
+                // Update all channels privileges, only if it is not equivalent
+                // to getUsrMgmtSyncIndex()
+                if (userData->user[usrIndex]
+                        .userPrivAccess[UserAccess::getUsrMgmtSyncIndex()]
+                        .privilege != userPriv)
+                {
+                    for (size_t chIndex = 0; chIndex < ipmiMaxChannels;
+                         ++chIndex)
+                    {
+                        userData->user[usrIndex]
+                            .userPrivAccess[chIndex]
+                            .privilege = userPriv;
+                    }
+                }
+                break;
+            }
+            case UserUpdateEvent::userRenamed:
+            {
+                std::fill(
+                    static_cast<uint8_t*>(userData->user[usrIndex].userName),
+                    static_cast<uint8_t*>(userData->user[usrIndex].userName) +
+                        sizeof(userData->user[usrIndex].userName),
+                    0);
+                std::strncpy(
+                    reinterpret_cast<char*>(userData->user[usrIndex].userName),
+                    newUserName.c_str(), ipmiMaxUserName);
+                ipmiRenameUserEntryPassword(userName, newUserName);
+                break;
+            }
+            case UserUpdateEvent::userStateUpdated:
+            {
+                userData->user[usrIndex].userEnabled = enabled;
+                break;
+            }
+            default:
+            {
+                lg2::error("Unhandled user event: {USER_EVENT}", "USER_EVENT",
+                           userEvent);
+                return;
+            }
+        }
+    }
+    usrAccess.writeUserData();
+    lg2::debug("User event handled successfully, user name: {USER_NAME}, "
+               "user event: {USER_EVENT}",
+               "USER_NAME", userName.c_str(), "USER_EVENT", userEvent);
+
+    return;
+}
+
+void userUpdatedSignalHandler(UserAccess& usrAccess, sdbusplus::message_t& msg)
+{
+    static sdbusplus::bus_t bus(ipmid_get_sd_bus_connection());
+    std::string signal = msg.get_member();
+    std::string userName, priv, newUserName;
+    std::vector<std::string> groups;
+    bool enabled = false;
+    UserUpdateEvent userEvent = UserUpdateEvent::reservedEvent;
+    if (signal == intfAddedSignal)
+    {
+        DbusUserObjPath objPath;
+        DbusUserObjValue objValue;
+        msg.read(objPath, objValue);
+        getUserNameFromPath(objPath.str, userName);
+        if (usrAccess.getUserObjProperties(objValue, groups, priv, enabled) !=
+            0)
+        {
+            return;
+        }
+        if (std::find(groups.begin(), groups.end(), ipmiGrpName) ==
+            groups.end())
+        {
+            return;
+        }
+        userEvent = UserUpdateEvent::userCreated;
+    }
+    else if (signal == intfRemovedSignal)
+    {
+        DbusUserObjPath objPath;
+        std::vector<std::string> interfaces;
+        msg.read(objPath, interfaces);
+        getUserNameFromPath(objPath.str, userName);
+        userEvent = UserUpdateEvent::userDeleted;
+    }
+    else if (signal == userRenamedSignal)
+    {
+        msg.read(userName, newUserName);
+        userEvent = UserUpdateEvent::userRenamed;
+    }
+    else if (signal == propertiesChangedSignal)
+    {
+        getUserNameFromPath(msg.get_path(), userName);
+    }
+    else
+    {
+        lg2::error("Unknown user update signal: {SIGNAL}", "SIGNAL", signal);
+        return;
+    }
+
+    if (signal.empty() || userName.empty() ||
+        (signal == userRenamedSignal && newUserName.empty()))
+    {
+        lg2::error("Invalid inputs received");
+        return;
+    }
+
+    boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+        userLock{*(usrAccess.userMutex)};
+    usrAccess.checkAndReloadUserData();
+
+    if (signal == propertiesChangedSignal)
+    {
+        std::string intfName;
+        DbusUserObjProperties chProperties;
+        msg.read(intfName, chProperties); // skip reading 3rd argument.
+        for (const auto& prop : chProperties)
+        {
+            userEvent = UserUpdateEvent::reservedEvent;
+            std::string member = prop.first;
+            if (member == userPrivProperty)
+            {
+                priv = std::get<std::string>(prop.second);
+                userEvent = UserUpdateEvent::userPrivUpdated;
+            }
+            else if (member == userGrpProperty)
+            {
+                groups = std::get<std::vector<std::string>>(prop.second);
+                userEvent = UserUpdateEvent::userGrpUpdated;
+            }
+            else if (member == userEnabledProperty)
+            {
+                enabled = std::get<bool>(prop.second);
+                userEvent = UserUpdateEvent::userStateUpdated;
+            }
+            // Process based on event type.
+            if (userEvent == UserUpdateEvent::userGrpUpdated)
+            {
+                if (std::find(groups.begin(), groups.end(), ipmiGrpName) ==
+                    groups.end())
+                {
+                    // remove user from ipmi user list.
+                    userUpdateHelper(usrAccess, UserUpdateEvent::userDeleted,
+                                     userName, priv, enabled, newUserName);
+                }
+                else
+                {
+                    DbusUserObjProperties properties;
+                    try
+                    {
+                        auto method = bus.new_method_call(
+                            userMgrService, msg.get_path(),
+                            dBusPropertiesInterface, getAllPropertiesMethod);
+                        method.append(usersInterface);
+                        auto reply = bus.call(method);
+                        reply.read(properties);
+                    }
+                    catch (const sdbusplus::exception_t& e)
+                    {
+                        lg2::debug("Failed to excute {METHOD}, path: {PATH}",
+                                   "METHOD", getAllPropertiesMethod, "PATH",
+                                   msg.get_path());
+                        return;
+                    }
+                    usrAccess.getUserProperties(properties, groups, priv,
+                                                enabled);
+                    // add user to ipmi user list.
+                    userUpdateHelper(usrAccess, UserUpdateEvent::userCreated,
+                                     userName, priv, enabled, newUserName);
+                }
+            }
+            else if (userEvent != UserUpdateEvent::reservedEvent)
+            {
+                userUpdateHelper(usrAccess, userEvent, userName, priv, enabled,
+                                 newUserName);
+            }
+        }
+    }
+    else if (userEvent != UserUpdateEvent::reservedEvent)
+    {
+        userUpdateHelper(usrAccess, userEvent, userName, priv, enabled,
+                         newUserName);
+    }
+    return;
+}
+
+UserAccess::~UserAccess()
+{
+    if (signalHndlrObject)
+    {
+        userUpdatedSignal.reset();
+        userMgrRenamedSignal.reset();
+        userPropertiesSignal.reset();
+        sigHndlrLock.unlock();
+    }
+}
+
+UserAccess::UserAccess() : bus(ipmid_get_sd_bus_connection())
+{
+    std::ofstream mutexCleanUpFile;
+    mutexCleanUpFile.open(ipmiMutexCleanupLockFile,
+                          std::ofstream::out | std::ofstream::app);
+    if (!mutexCleanUpFile.good())
+    {
+        lg2::debug("Unable to open mutex cleanup file");
+        return;
+    }
+    mutexCleanUpFile.close();
+    mutexCleanupLock = boost::interprocess::file_lock(ipmiMutexCleanupLockFile);
+    if (mutexCleanupLock.try_lock())
+    {
+        boost::interprocess::named_recursive_mutex::remove(ipmiUserMutex);
+    }
+    mutexCleanupLock.lock_sharable();
+    userMutex = std::make_unique<boost::interprocess::named_recursive_mutex>(
+        boost::interprocess::open_or_create, ipmiUserMutex);
+
+    cacheUserDataFile();
+    getSystemPrivAndGroups();
+}
+
+UserInfo* UserAccess::getUserInfo(const uint8_t userId)
+{
+    checkAndReloadUserData();
+    return &usersTbl.user[userId];
+}
+
+void UserAccess::setUserInfo(const uint8_t userId, UserInfo* userInfo)
+{
+    checkAndReloadUserData();
+    std::copy(reinterpret_cast<uint8_t*>(userInfo),
+              reinterpret_cast<uint8_t*>(userInfo) + sizeof(*userInfo),
+              reinterpret_cast<uint8_t*>(&usersTbl.user[userId]));
+    writeUserData();
+}
+
+bool UserAccess::isValidChannel(const uint8_t chNum)
+{
+    return (chNum < ipmiMaxChannels);
+}
+
+bool UserAccess::isValidUserId(const uint8_t userId)
+{
+    return ((userId <= ipmiMaxUsers) && (userId != reservedUserId));
+}
+
+bool UserAccess::isValidPrivilege(const uint8_t priv)
+{
+    // Callback privilege is deprecated in OpenBMC
+    return isValidPrivLimit(priv);
+}
+
+uint8_t UserAccess::getUsrMgmtSyncIndex()
+{
+    // Identify the IPMI channel used to assign system user privilege levels
+    // in phosphor-user-manager. The default value is IPMI Channel 1. To
+    // assign a different channel add:
+    //      "is_management_nic" : true
+    // into the channel_config.json file describing the assignment of the IPMI
+    // channels. It is only necessary to add the string above to ONE record in
+    // the channel_config.json file. All other records will be automatically
+    // assigned a "false" value.
+    return getChannelConfigObject().getManagementNICID();
+}
+
+CommandPrivilege UserAccess::convertToIPMIPrivilege(const std::string& value)
+{
+    auto iter = std::find(ipmiPrivIndex.begin(), ipmiPrivIndex.end(), value);
+    if (iter == ipmiPrivIndex.end())
+    {
+        if (value == "")
+        {
+            return static_cast<CommandPrivilege>(privNoAccess);
+        }
+        lg2::error("Error in converting to IPMI privilege: {PRIV}", "PRIV",
+                   value);
+        throw std::out_of_range("Out of range - convertToIPMIPrivilege");
+    }
+    else
+    {
+        return static_cast<CommandPrivilege>(
+            std::distance(ipmiPrivIndex.begin(), iter));
+    }
+}
+
+std::string UserAccess::convertToSystemPrivilege(const CommandPrivilege& value)
+{
+    if (value == static_cast<CommandPrivilege>(privNoAccess))
+    {
+        return "";
+    }
+    try
+    {
+        return ipmiPrivIndex.at(value);
+    }
+    catch (const std::out_of_range& e)
+    {
+        lg2::error("Error in converting to system privilege: {PRIV}", "PRIV",
+                   value);
+        throw std::out_of_range("Out of range - convertToSystemPrivilege");
+    }
+}
+
+bool UserAccess::isValidUserName(const std::string& userName)
+{
+    if (userName.empty())
+    {
+        lg2::error("userName is empty");
+        return false;
+    }
+    if (!std::regex_match(userName.c_str(),
+                          std::regex("[a-zA-Z_][a-zA-Z_0-9]*")))
+    {
+        lg2::error("Unsupported characters in user name");
+        return false;
+    }
+    if (userName == "root")
+    {
+        lg2::error("Invalid user name - root");
+        return false;
+    }
+    std::map<DbusUserObjPath, DbusUserObjValue> properties;
+    try
+    {
+        auto method =
+            bus.new_method_call(userMgrService, userMgrObjBasePath,
+                                dBusObjManager, getManagedObjectsMethod);
+        auto reply = bus.call(method);
+        reply.read(properties);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error("Failed to excute {METHOD}, path: {PATH}", "METHOD",
+                   getManagedObjectsMethod, "PATH", userMgrObjBasePath);
+        return false;
+    }
+
+    sdbusplus::message::object_path tempUserPath(userObjBasePath);
+    tempUserPath /= userName;
+    std::string usersPath(tempUserPath);
+
+    if (properties.find(usersPath) != properties.end())
+    {
+        lg2::debug("Username {USER_NAME} already exists", "USER_NAME",
+                   userName);
+        return false;
+    }
+
+    return true;
+}
+
+/** @brief Information exchanged by pam module and application.
+ *
+ *  @param[in] numMsg - length of the array of pointers,msg.
+ *
+ *  @param[in] msg -  pointer  to an array of pointers to pam_message structure
+ *
+ *  @param[out] resp - struct pam response array
+ *
+ *  @param[in] appdataPtr - member of pam_conv structure
+ *
+ *  @return the response in pam response structure.
+ */
+
+static int pamFunctionConversation(int numMsg, const struct pam_message** msg,
+                                   struct pam_response** resp, void* appdataPtr)
+{
+    if (appdataPtr == nullptr)
+    {
+        return PAM_CONV_ERR;
+    }
+
+    if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG)
+    {
+        return PAM_CONV_ERR;
+    }
+
+    for (int i = 0; i < numMsg; ++i)
+    {
+        /* Ignore all PAM messages except prompting for hidden input */
+        if (msg[i]->msg_style != PAM_PROMPT_ECHO_OFF)
+        {
+            continue;
+        }
+
+        /* Assume PAM is only prompting for the password as hidden input */
+        /* Allocate memory only when PAM_PROMPT_ECHO_OFF is encounterred */
+
+        char* appPass = reinterpret_cast<char*>(appdataPtr);
+        size_t appPassSize = std::strlen(appPass);
+
+        if (appPassSize >= PAM_MAX_RESP_SIZE)
+        {
+            return PAM_CONV_ERR;
+        }
+
+        char* pass = reinterpret_cast<char*>(malloc(appPassSize + 1));
+        if (pass == nullptr)
+        {
+            return PAM_BUF_ERR;
+        }
+
+        void* ptr =
+            calloc(static_cast<size_t>(numMsg), sizeof(struct pam_response));
+        if (ptr == nullptr)
+        {
+            free(pass);
+            return PAM_BUF_ERR;
+        }
+
+        std::strncpy(pass, appPass, appPassSize + 1);
+
+        *resp = reinterpret_cast<pam_response*>(ptr);
+        resp[i]->resp = pass;
+
+        return PAM_SUCCESS;
+    }
+
+    return PAM_CONV_ERR;
+}
+
+/** @brief Updating the PAM password
+ *
+ *  @param[in] username - username in string
+ *
+ *  @param[in] password  - new password in string
+ *
+ *  @return status
+ */
+
+int pamUpdatePasswd(const char* username, const char* password)
+{
+    const struct pam_conv localConversation = {pamFunctionConversation,
+                                               const_cast<char*>(password)};
+    pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
+
+    int retval =
+        pam_start("passwd", username, &localConversation, &localAuthHandle);
+
+    if (retval != PAM_SUCCESS)
+    {
+        return retval;
+    }
+
+    retval = pam_chauthtok(localAuthHandle, PAM_SILENT);
+    if (retval != PAM_SUCCESS)
+    {
+        pam_end(localAuthHandle, retval);
+        return retval;
+    }
+
+    return pam_end(localAuthHandle, PAM_SUCCESS);
+}
+
+bool pamUserCheckAuthenticate(std::string_view username,
+                              std::string_view password)
+{
+    const struct pam_conv localConversation = {
+        pamFunctionConversation, const_cast<char*>(password.data())};
+
+    pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
+
+    if (pam_start("dropbear", username.data(), &localConversation,
+                  &localAuthHandle) != PAM_SUCCESS)
+    {
+        lg2::error("User Authentication Failure");
+        return false;
+    }
+
+    int retval = pam_authenticate(localAuthHandle,
+                                  PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK);
+
+    if (retval != PAM_SUCCESS)
+    {
+        lg2::debug("pam_authenticate returned failure: {ERROR}", "ERROR",
+                   retval);
+
+        pam_end(localAuthHandle, retval);
+        return false;
+    }
+
+    if (pam_acct_mgmt(localAuthHandle, PAM_DISALLOW_NULL_AUTHTOK) !=
+        PAM_SUCCESS)
+    {
+        pam_end(localAuthHandle, PAM_SUCCESS);
+        return false;
+    }
+
+    if (pam_end(localAuthHandle, PAM_SUCCESS) != PAM_SUCCESS)
+    {
+        return false;
+    }
+    return true;
+}
+
+Cc UserAccess::setSpecialUserPassword(const std::string& userName,
+                                      const SecureString& userPassword)
+{
+    if (pamUpdatePasswd(userName.c_str(), userPassword.c_str()) != PAM_SUCCESS)
+    {
+        lg2::debug("Failed to update password");
+        return ccUnspecifiedError;
+    }
+    return ccSuccess;
+}
+
+Cc UserAccess::setUserPassword(const uint8_t userId, const char* userPassword)
+{
+    std::string userName;
+    if (ipmiUserGetUserName(userId, userName) != ccSuccess)
+    {
+        lg2::debug("User Name not found, user Id: {USER_ID}", "USER_ID",
+                   userId);
+        return ccParmOutOfRange;
+    }
+
+    ipmi::SecureString passwd;
+    passwd.assign(reinterpret_cast<const char*>(userPassword), 0,
+                  maxIpmi20PasswordSize);
+    int retval = pamUpdatePasswd(userName.c_str(), passwd.c_str());
+
+    switch (retval)
+    {
+        case PAM_SUCCESS:
+        {
+            return ccSuccess;
+        }
+        case PAM_AUTHTOK_ERR:
+        {
+            lg2::debug("Bad authentication token");
+            return ccInvalidFieldRequest;
+        }
+        default:
+        {
+            lg2::debug("Failed to update password, user Id: {USER_ID}",
+                       "USER_ID", userId);
+            return ccUnspecifiedError;
+        }
+    }
+}
+
+Cc UserAccess::setUserEnabledState(const uint8_t userId,
+                                   const bool& enabledState)
+{
+    if (!isValidUserId(userId))
+    {
+        return ccParmOutOfRange;
+    }
+    boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+        userLock{*userMutex};
+    UserInfo* userInfo = getUserInfo(userId);
+    std::string userName;
+    userName.assign(reinterpret_cast<char*>(userInfo->userName), 0,
+                    ipmiMaxUserName);
+    if (userName.empty())
+    {
+        lg2::debug("User name not set / invalid");
+        return ccUnspecifiedError;
+    }
+    if (userInfo->userEnabled != enabledState)
+    {
+        sdbusplus::message::object_path tempUserPath(userObjBasePath);
+        tempUserPath /= userName;
+        std::string userPath(tempUserPath);
+        setDbusProperty(bus, userMgrService, userPath, usersInterface,
+                        userEnabledProperty, enabledState);
+        userInfo->userEnabled = enabledState;
+        try
+        {
+            writeUserData();
+        }
+        catch (const std::exception& e)
+        {
+            lg2::debug("Write user data failed");
+            return ccUnspecifiedError;
+        }
+    }
+    return ccSuccess;
+}
+
+Cc UserAccess::setUserPayloadAccess(
+    const uint8_t chNum, const uint8_t operation, const uint8_t userId,
+    const PayloadAccess& payloadAccess)
+{
+    constexpr uint8_t enable = 0x0;
+    constexpr uint8_t disable = 0x1;
+
+    if (!isValidChannel(chNum))
+    {
+        return ccInvalidFieldRequest;
+    }
+    if (!isValidUserId(userId))
+    {
+        return ccParmOutOfRange;
+    }
+    if (operation != enable && operation != disable)
+    {
+        return ccInvalidFieldRequest;
+    }
+    // Check operation & payloadAccess if required.
+    boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+        userLock{*userMutex};
+    UserInfo* userInfo = getUserInfo(userId);
+
+    if (operation == enable)
+    {
+        userInfo->payloadAccess[chNum].stdPayloadEnables1 |=
+            payloadAccess.stdPayloadEnables1;
+
+        userInfo->payloadAccess[chNum].oemPayloadEnables1 |=
+            payloadAccess.oemPayloadEnables1;
+    }
+    else
+    {
+        userInfo->payloadAccess[chNum].stdPayloadEnables1 &=
+            ~(payloadAccess.stdPayloadEnables1);
+
+        userInfo->payloadAccess[chNum].oemPayloadEnables1 &=
+            ~(payloadAccess.oemPayloadEnables1);
+    }
+
+    try
+    {
+        writeUserData();
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Write user data failed");
+        return ccUnspecifiedError;
+    }
+    return ccSuccess;
+}
+
+Cc UserAccess::setUserPrivilegeAccess(const uint8_t userId, const uint8_t chNum,
+                                      const UserPrivAccess& privAccess,
+                                      const bool& otherPrivUpdates)
+{
+    if (!isValidChannel(chNum))
+    {
+        return ccInvalidFieldRequest;
+    }
+    if (!isValidUserId(userId))
+    {
+        return ccParmOutOfRange;
+    }
+    boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+        userLock{*userMutex};
+    UserInfo* userInfo = getUserInfo(userId);
+    std::string userName;
+    userName.assign(reinterpret_cast<char*>(userInfo->userName), 0,
+                    ipmiMaxUserName);
+    if (userName.empty())
+    {
+        lg2::debug("User name not set / invalid");
+        return ccUnspecifiedError;
+    }
+    std::string priv = convertToSystemPrivilege(
+        static_cast<CommandPrivilege>(privAccess.privilege));
+    uint8_t syncIndex = getUsrMgmtSyncIndex();
+    if (chNum == syncIndex &&
+        privAccess.privilege != userInfo->userPrivAccess[syncIndex].privilege)
+    {
+        sdbusplus::message::object_path tempUserPath(userObjBasePath);
+        tempUserPath /= userName;
+        std::string userPath(tempUserPath);
+        setDbusProperty(bus, userMgrService, userPath, usersInterface,
+                        userPrivProperty, priv);
+    }
+    userInfo->userPrivAccess[chNum].privilege = privAccess.privilege;
+
+    if (otherPrivUpdates)
+    {
+        userInfo->userPrivAccess[chNum].ipmiEnabled = privAccess.ipmiEnabled;
+        userInfo->userPrivAccess[chNum].linkAuthEnabled =
+            privAccess.linkAuthEnabled;
+        userInfo->userPrivAccess[chNum].accessCallback =
+            privAccess.accessCallback;
+    }
+    try
+    {
+        writeUserData();
+    }
+    catch (const std::exception& e)
+    {
+        lg2::debug("Write user data failed");
+        return ccUnspecifiedError;
+    }
+    return ccSuccess;
+}
+
+uint8_t UserAccess::getUserId(const std::string& userName)
+{
+    boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+        userLock{*userMutex};
+    checkAndReloadUserData();
+    // user index 0 is reserved, starts with 1
+    size_t usrIndex = 1;
+    for (; usrIndex <= ipmiMaxUsers; ++usrIndex)
+    {
+        std::string curName(
+            reinterpret_cast<char*>(usersTbl.user[usrIndex].userName), 0,
+            ipmiMaxUserName);
+        if (userName == curName)
+        {
+            break; // found the entry
+        }
+    }
+    if (usrIndex > ipmiMaxUsers)
+    {
+        lg2::debug("Username {USER_NAME} not found", "USER_NAME", userName);
+        return invalidUserId;
+    }
+
+    return usrIndex;
+}
+
+Cc UserAccess::getUserName(const uint8_t userId, std::string& userName)
+{
+    if (!isValidUserId(userId))
+    {
+        return ccParmOutOfRange;
+    }
+    UserInfo* userInfo = getUserInfo(userId);
+    userName.assign(reinterpret_cast<char*>(userInfo->userName), 0,
+                    ipmiMaxUserName);
+    return ccSuccess;
+}
+
+bool UserAccess::isIpmiInAvailableGroupList()
+{
+    if (std::find(availableGroups.begin(), availableGroups.end(),
+                  ipmiGrpName) != availableGroups.end())
+    {
+        return true;
+    }
+    if (availableGroups.empty())
+    {
+        // available groups shouldn't be empty, re-query
+        getSystemPrivAndGroups();
+        if (std::find(availableGroups.begin(), availableGroups.end(),
+                      ipmiGrpName) != availableGroups.end())
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+Cc UserAccess::setUserName(const uint8_t userId, const std::string& userName)
+{
+    if (!isValidUserId(userId))
+    {
+        return ccParmOutOfRange;
+    }
+
+    boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+        userLock{*userMutex};
+    std::string oldUser;
+    getUserName(userId, oldUser);
+
+    if (oldUser == userName)
+    {
+        // requesting to set the same user name, return success.
+        return ccSuccess;
+    }
+
+    bool validUser = isValidUserName(userName);
+    UserInfo* userInfo = getUserInfo(userId);
+    if (userName.empty() && !oldUser.empty())
+    {
+        // Delete existing user
+        sdbusplus::message::object_path tempUserPath(userObjBasePath);
+        tempUserPath /= oldUser;
+        std::string userPath(tempUserPath);
+        try
+        {
+            auto method =
+                bus.new_method_call(userMgrService, userPath.c_str(),
+                                    deleteUserInterface, deleteUserMethod);
+            auto reply = bus.call(method);
+        }
+        catch (const sdbusplus::exception_t& e)
+        {
+            lg2::debug("Failed to excute {METHOD}, path:{PATH}", "METHOD",
+                       deleteUserMethod, "PATH", userPath);
+            return ccUnspecifiedError;
+        }
+        deleteUserIndex(userId);
+    }
+    else if (oldUser.empty() && !userName.empty() && validUser)
+    {
+        try
+        {
+            if (!isIpmiInAvailableGroupList())
+            {
+                return ccUnspecifiedError;
+            }
+            // Create new user
+            auto method =
+                bus.new_method_call(userMgrService, userMgrObjBasePath,
+                                    userMgrInterface, createUserMethod);
+            method.append(userName.c_str(), availableGroups,
+                          ipmiPrivIndex[PRIVILEGE_USER], false);
+            auto reply = bus.call(method);
+        }
+        catch (const sdbusplus::exception_t& e)
+        {
+            lg2::debug("Failed to excute {METHOD}, path: {PATH}", "METHOD",
+                       createUserMethod, "PATH", userMgrObjBasePath);
+            return ccUnspecifiedError;
+        }
+
+        std::memset(userInfo->userName, 0, sizeof(userInfo->userName));
+        std::memcpy(userInfo->userName,
+                    static_cast<const void*>(userName.data()), userName.size());
+        userInfo->userInSystem = true;
+        for (size_t chIndex = 0; chIndex < ipmiMaxChannels; chIndex++)
+        {
+            userInfo->userPrivAccess[chIndex].privilege =
+                static_cast<uint8_t>(PRIVILEGE_USER);
+        }
+    }
+    else if (oldUser != userName && validUser)
+    {
+        try
+        {
+            // User rename
+            auto method =
+                bus.new_method_call(userMgrService, userMgrObjBasePath,
+                                    userMgrInterface, renameUserMethod);
+            method.append(oldUser.c_str(), userName.c_str());
+            auto reply = bus.call(method);
+        }
+        catch (const sdbusplus::exception_t& e)
+        {
+            lg2::debug("Failed to excute {METHOD}, path: {PATH}", "METHOD",
+                       renameUserMethod, "PATH", userMgrObjBasePath);
+            return ccUnspecifiedError;
+        }
+        std::fill(static_cast<uint8_t*>(userInfo->userName),
+                  static_cast<uint8_t*>(userInfo->userName) +
+                      sizeof(userInfo->userName),
+                  0);
+
+        std::memset(userInfo->userName, 0, sizeof(userInfo->userName));
+        std::memcpy(userInfo->userName,
+                    static_cast<const void*>(userName.data()), userName.size());
+
+        ipmiRenameUserEntryPassword(oldUser, userName);
+        userInfo->userInSystem = true;
+    }
+    else if (!validUser)
+    {
+        return ccInvalidFieldRequest;
+    }
+    try
+    {
+        writeUserData();
+    }
+    catch (const std::exception& e)
+    {
+        lg2::debug("Write user data failed");
+        return ccUnspecifiedError;
+    }
+    return ccSuccess;
+}
+
+static constexpr const char* jsonUserName = "user_name";
+static constexpr const char* jsonPriv = "privilege";
+static constexpr const char* jsonIpmiEnabled = "ipmi_enabled";
+static constexpr const char* jsonLinkAuthEnabled = "link_auth_enabled";
+static constexpr const char* jsonAccCallbk = "access_callback";
+static constexpr const char* jsonUserEnabled = "user_enabled";
+static constexpr const char* jsonUserInSys = "user_in_system";
+static constexpr const char* jsonFixedUser = "fixed_user_name";
+static constexpr const char* payloadEnabledStr = "payload_enabled";
+static constexpr const char* stdPayloadStr = "std_payload";
+static constexpr const char* oemPayloadStr = "OEM_payload";
+
+/** @brief to construct a JSON object from the given payload access details.
+ *
+ *  @param[in] stdPayload - stdPayloadEnables1 in a 2D-array. (input)
+ *  @param[in] oemPayload - oemPayloadEnables1 in a 2D-array. (input)
+ *
+ *  @details Sample output JSON object format :
+ *  "payload_enabled":{
+ *  "OEM_payload0":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ *  "OEM_payload1":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ *  "OEM_payload2":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ *  "OEM_payload3":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ *  "OEM_payload4":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ *  "OEM_payload5":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ *  "OEM_payload6":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ *  "OEM_payload7":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ *  "std_payload0":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ *  "std_payload1":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ *  "std_payload2":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ *  "std_payload3":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ *  "std_payload4":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ *  "std_payload5":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ *  "std_payload6":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ *  "std_payload7":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ *  }
+ */
+static const Json constructJsonPayloadEnables(
+    const std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>&
+        stdPayload,
+    const std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>&
+        oemPayload)
+{
+    Json jsonPayloadEnabled;
+
+    for (auto payloadNum = 0; payloadNum < payloadsPerByte; payloadNum++)
+    {
+        std::ostringstream stdPayloadStream;
+        std::ostringstream oemPayloadStream;
+
+        stdPayloadStream << stdPayloadStr << payloadNum;
+        oemPayloadStream << oemPayloadStr << payloadNum;
+
+        jsonPayloadEnabled.push_back(Json::object_t::value_type(
+            stdPayloadStream.str(), stdPayload[payloadNum]));
+
+        jsonPayloadEnabled.push_back(Json::object_t::value_type(
+            oemPayloadStream.str(), oemPayload[payloadNum]));
+    }
+    return jsonPayloadEnabled;
+}
+
+void UserAccess::readPayloadAccessFromUserInfo(
+    const UserInfo& userInfo,
+    std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>& stdPayload,
+    std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>& oemPayload)
+{
+    for (auto payloadNum = 0; payloadNum < payloadsPerByte; payloadNum++)
+    {
+        for (auto chIndex = 0; chIndex < ipmiMaxChannels; chIndex++)
+        {
+            stdPayload[payloadNum][chIndex] =
+                userInfo.payloadAccess[chIndex].stdPayloadEnables1[payloadNum];
+
+            oemPayload[payloadNum][chIndex] =
+                userInfo.payloadAccess[chIndex].oemPayloadEnables1[payloadNum];
+        }
+    }
+}
+
+void UserAccess::updatePayloadAccessInUserInfo(
+    const std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>&
+        stdPayload,
+    const std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>&,
+    UserInfo& userInfo)
+{
+    for (size_t chIndex = 0; chIndex < ipmiMaxChannels; ++chIndex)
+    {
+        // Ensure that reserved/unsupported payloads are marked to zero.
+        userInfo.payloadAccess[chIndex].stdPayloadEnables1.reset();
+        userInfo.payloadAccess[chIndex].oemPayloadEnables1.reset();
+        userInfo.payloadAccess[chIndex].stdPayloadEnables2Reserved.reset();
+        userInfo.payloadAccess[chIndex].oemPayloadEnables2Reserved.reset();
+        // Update SOL status as it is the only supported payload currently.
+        userInfo.payloadAccess[chIndex]
+            .stdPayloadEnables1[static_cast<uint8_t>(ipmi::PayloadType::SOL)] =
+            stdPayload[static_cast<uint8_t>(ipmi::PayloadType::SOL)][chIndex];
+    }
+}
+
+void UserAccess::readUserData()
+{
+    boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+        userLock{*userMutex};
+
+    std::ifstream iUsrData(ipmiUserDataFile, std::ios::in | std::ios::binary);
+    if (!iUsrData.good())
+    {
+        lg2::error("Error in reading IPMI user data file");
+        throw std::ios_base::failure("Error opening IPMI user data file");
+    }
+
+    Json jsonUsersTbl = Json::array();
+    jsonUsersTbl = Json::parse(iUsrData, nullptr, false);
+
+    if (jsonUsersTbl.size() != ipmiMaxUsers)
+    {
+        lg2::error("Error in reading IPMI user data file - User count issues");
+        throw std::runtime_error(
+            "Corrupted IPMI user data file - invalid user count");
+    }
+
+    // user index 0 is reserved, starts with 1
+    for (size_t usrIndex = 1; usrIndex <= ipmiMaxUsers; ++usrIndex)
+    {
+        Json userInfo = jsonUsersTbl[usrIndex - 1]; // json array starts with 0.
+        if (userInfo.is_null())
+        {
+            lg2::error("Error in reading IPMI user data file - "
+                       "user info corrupted");
+            throw std::runtime_error(
+                "Corrupted IPMI user data file - invalid user info");
+        }
+        std::string userName = userInfo[jsonUserName].get<std::string>();
+        std::strncpy(reinterpret_cast<char*>(usersTbl.user[usrIndex].userName),
+                     userName.c_str(), ipmiMaxUserName);
+
+        std::vector<std::string> privilege =
+            userInfo[jsonPriv].get<std::vector<std::string>>();
+        std::vector<bool> ipmiEnabled =
+            userInfo[jsonIpmiEnabled].get<std::vector<bool>>();
+        std::vector<bool> linkAuthEnabled =
+            userInfo[jsonLinkAuthEnabled].get<std::vector<bool>>();
+        std::vector<bool> accessCallback =
+            userInfo[jsonAccCallbk].get<std::vector<bool>>();
+
+        // Payload Enables Processing.
+        std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>
+            stdPayload = {};
+        std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>
+            oemPayload = {};
+        try
+        {
+            const auto jsonPayloadEnabled = userInfo.at(payloadEnabledStr);
+            for (auto payloadNum = 0; payloadNum < payloadsPerByte;
+                 payloadNum++)
+            {
+                std::ostringstream stdPayloadStream;
+                std::ostringstream oemPayloadStream;
+
+                stdPayloadStream << stdPayloadStr << payloadNum;
+                oemPayloadStream << oemPayloadStr << payloadNum;
+
+                stdPayload[payloadNum] =
+                    jsonPayloadEnabled[stdPayloadStream.str()]
+                        .get<std::array<bool, ipmiMaxChannels>>();
+                oemPayload[payloadNum] =
+                    jsonPayloadEnabled[oemPayloadStream.str()]
+                        .get<std::array<bool, ipmiMaxChannels>>();
+
+                if (stdPayload[payloadNum].size() != ipmiMaxChannels ||
+                    oemPayload[payloadNum].size() != ipmiMaxChannels)
+                {
+                    lg2::error("Error in reading IPMI user data file - "
+                               "payload properties corrupted");
+                    throw std::runtime_error(
+                        "Corrupted IPMI user data file - payload properties");
+                }
+            }
+        }
+        catch (const Json::out_of_range& e)
+        {
+            // Key not found in 'userInfo'; possibly an old JSON file. Use
+            // default values for all payloads, and SOL payload default is true.
+            stdPayload[static_cast<uint8_t>(ipmi::PayloadType::SOL)].fill(true);
+        }
+
+        if (privilege.size() != ipmiMaxChannels ||
+            ipmiEnabled.size() != ipmiMaxChannels ||
+            linkAuthEnabled.size() != ipmiMaxChannels ||
+            accessCallback.size() != ipmiMaxChannels)
+        {
+            lg2::error("Error in reading IPMI user data file - "
+                       "properties corrupted");
+            throw std::runtime_error(
+                "Corrupted IPMI user data file - properties");
+        }
+        for (size_t chIndex = 0; chIndex < ipmiMaxChannels; ++chIndex)
+        {
+            usersTbl.user[usrIndex].userPrivAccess[chIndex].privilege =
+                static_cast<uint8_t>(
+                    convertToIPMIPrivilege(privilege[chIndex]));
+            usersTbl.user[usrIndex].userPrivAccess[chIndex].ipmiEnabled =
+                ipmiEnabled[chIndex];
+            usersTbl.user[usrIndex].userPrivAccess[chIndex].linkAuthEnabled =
+                linkAuthEnabled[chIndex];
+            usersTbl.user[usrIndex].userPrivAccess[chIndex].accessCallback =
+                accessCallback[chIndex];
+        }
+        updatePayloadAccessInUserInfo(stdPayload, oemPayload,
+                                      usersTbl.user[usrIndex]);
+        usersTbl.user[usrIndex].userEnabled =
+            userInfo[jsonUserEnabled].get<bool>();
+        usersTbl.user[usrIndex].userInSystem =
+            userInfo[jsonUserInSys].get<bool>();
+        usersTbl.user[usrIndex].fixedUserName =
+            userInfo[jsonFixedUser].get<bool>();
+    }
+
+    lg2::debug("User data read from IPMI data file");
+    iUsrData.close();
+    // Update the timestamp
+    fileLastUpdatedTime = getUpdatedFileTime();
+    return;
+}
+
+void UserAccess::writeUserData()
+{
+    boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+        userLock{*userMutex};
+
+    Json jsonUsersTbl = Json::array();
+    // user index 0 is reserved, starts with 1
+    for (size_t usrIndex = 1; usrIndex <= ipmiMaxUsers; ++usrIndex)
+    {
+        Json jsonUserInfo;
+        jsonUserInfo[jsonUserName] = std::string(
+            reinterpret_cast<char*>(usersTbl.user[usrIndex].userName), 0,
+            ipmiMaxUserName);
+        std::vector<std::string> privilege(ipmiMaxChannels);
+        std::vector<bool> ipmiEnabled(ipmiMaxChannels);
+        std::vector<bool> linkAuthEnabled(ipmiMaxChannels);
+        std::vector<bool> accessCallback(ipmiMaxChannels);
+
+        std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>
+            stdPayload;
+        std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>
+            oemPayload;
+
+        for (size_t chIndex = 0; chIndex < ipmiMaxChannels; chIndex++)
+        {
+            privilege[chIndex] =
+                convertToSystemPrivilege(static_cast<CommandPrivilege>(
+                    usersTbl.user[usrIndex].userPrivAccess[chIndex].privilege));
+            ipmiEnabled[chIndex] =
+                usersTbl.user[usrIndex].userPrivAccess[chIndex].ipmiEnabled;
+            linkAuthEnabled[chIndex] =
+                usersTbl.user[usrIndex].userPrivAccess[chIndex].linkAuthEnabled;
+            accessCallback[chIndex] =
+                usersTbl.user[usrIndex].userPrivAccess[chIndex].accessCallback;
+        }
+        jsonUserInfo[jsonPriv] = privilege;
+        jsonUserInfo[jsonIpmiEnabled] = ipmiEnabled;
+        jsonUserInfo[jsonLinkAuthEnabled] = linkAuthEnabled;
+        jsonUserInfo[jsonAccCallbk] = accessCallback;
+        jsonUserInfo[jsonUserEnabled] = usersTbl.user[usrIndex].userEnabled;
+        jsonUserInfo[jsonUserInSys] = usersTbl.user[usrIndex].userInSystem;
+        jsonUserInfo[jsonFixedUser] = usersTbl.user[usrIndex].fixedUserName;
+
+        readPayloadAccessFromUserInfo(usersTbl.user[usrIndex], stdPayload,
+                                      oemPayload);
+        Json jsonPayloadEnabledInfo =
+            constructJsonPayloadEnables(stdPayload, oemPayload);
+        jsonUserInfo[payloadEnabledStr] = jsonPayloadEnabledInfo;
+
+        jsonUsersTbl.push_back(jsonUserInfo);
+    }
+
+    static std::string tmpFile{std::string(ipmiUserDataFile) + "_tmp"};
+    int fd = open(tmpFile.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_SYNC,
+                  S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+    if (fd < 0)
+    {
+        lg2::error("Error in creating temporary IPMI user data file");
+        throw std::ios_base::failure(
+            "Error in creating temporary IPMI user data file");
+    }
+    const auto& writeStr = jsonUsersTbl.dump();
+    if (write(fd, writeStr.c_str(), writeStr.size()) !=
+        static_cast<ssize_t>(writeStr.size()))
+    {
+        close(fd);
+        lg2::error("Error in writing temporary IPMI user data file");
+        throw std::ios_base::failure(
+            "Error in writing temporary IPMI user data file");
+    }
+    close(fd);
+
+    if (std::rename(tmpFile.c_str(), ipmiUserDataFile) != 0)
+    {
+        lg2::error("Error in renaming temporary IPMI user data file");
+        throw std::runtime_error("Error in renaming IPMI user data file");
+    }
+    // Update the timestamp
+    fileLastUpdatedTime = getUpdatedFileTime();
+    return;
+}
+
+bool UserAccess::addUserEntry(const std::string& userName,
+                              const std::string& sysPriv, const bool& enabled)
+{
+    UsersTbl* userData = getUsersTblPtr();
+    size_t freeIndex = 0xFF;
+    // user index 0 is reserved, starts with 1
+    for (size_t usrIndex = 1; usrIndex <= ipmiMaxUsers; ++usrIndex)
+    {
+        std::string curName(
+            reinterpret_cast<char*>(userData->user[usrIndex].userName), 0,
+            ipmiMaxUserName);
+        if (userName == curName)
+        {
+            lg2::debug("Username {USER_NAME} exists", "USER_NAME", userName);
+            return false; // user name exists.
+        }
+
+        if ((!userData->user[usrIndex].userInSystem) &&
+            (userData->user[usrIndex].userName[0] == '\0') &&
+            (freeIndex == 0xFF))
+        {
+            freeIndex = usrIndex;
+        }
+    }
+    if (freeIndex == 0xFF)
+    {
+        lg2::error("No empty slots found");
+        return false;
+    }
+    std::strncpy(reinterpret_cast<char*>(userData->user[freeIndex].userName),
+                 userName.c_str(), ipmiMaxUserName);
+    uint8_t priv =
+        static_cast<uint8_t>(UserAccess::convertToIPMIPrivilege(sysPriv)) &
+        privMask;
+    for (size_t chIndex = 0; chIndex < ipmiMaxChannels; ++chIndex)
+    {
+        userData->user[freeIndex].userPrivAccess[chIndex].privilege = priv;
+        userData->user[freeIndex].userPrivAccess[chIndex].ipmiEnabled = true;
+        userData->user[freeIndex].userPrivAccess[chIndex].linkAuthEnabled =
+            true;
+        userData->user[freeIndex].userPrivAccess[chIndex].accessCallback = true;
+    }
+    userData->user[freeIndex].userInSystem = true;
+    userData->user[freeIndex].userEnabled = enabled;
+
+    return true;
+}
+
+void UserAccess::deleteUserIndex(const size_t& usrIdx)
+{
+    UsersTbl* userData = getUsersTblPtr();
+
+    std::string userName(
+        reinterpret_cast<char*>(userData->user[usrIdx].userName), 0,
+        ipmiMaxUserName);
+    ipmiClearUserEntryPassword(userName);
+    std::fill(static_cast<uint8_t*>(userData->user[usrIdx].userName),
+              static_cast<uint8_t*>(userData->user[usrIdx].userName) +
+                  sizeof(userData->user[usrIdx].userName),
+              0);
+    for (size_t chIndex = 0; chIndex < ipmiMaxChannels; ++chIndex)
+    {
+        userData->user[usrIdx].userPrivAccess[chIndex].privilege = privNoAccess;
+        userData->user[usrIdx].userPrivAccess[chIndex].ipmiEnabled = false;
+        userData->user[usrIdx].userPrivAccess[chIndex].linkAuthEnabled = false;
+        userData->user[usrIdx].userPrivAccess[chIndex].accessCallback = false;
+    }
+    userData->user[usrIdx].userInSystem = false;
+    userData->user[usrIdx].userEnabled = false;
+    return;
+}
+
+void UserAccess::checkAndReloadUserData()
+{
+    std::timespec updateTime = getUpdatedFileTime();
+    if ((updateTime.tv_sec != fileLastUpdatedTime.tv_sec ||
+         updateTime.tv_nsec != fileLastUpdatedTime.tv_nsec) ||
+        (updateTime.tv_sec == 0 && updateTime.tv_nsec == 0))
+    {
+        std::fill(reinterpret_cast<uint8_t*>(&usersTbl),
+                  reinterpret_cast<uint8_t*>(&usersTbl) + sizeof(usersTbl), 0);
+        readUserData();
+    }
+    return;
+}
+
+UsersTbl* UserAccess::getUsersTblPtr()
+{
+    // reload data before using it.
+    checkAndReloadUserData();
+    return &usersTbl;
+}
+
+void UserAccess::getSystemPrivAndGroups()
+{
+    std::map<std::string, PrivAndGroupType> properties;
+    try
+    {
+        auto method = bus.new_method_call(userMgrService, userMgrObjBasePath,
+                                          dBusPropertiesInterface,
+                                          getAllPropertiesMethod);
+        method.append(userMgrInterface);
+
+        auto reply = bus.call(method);
+        reply.read(properties);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::debug("Failed to excute {METHOD}, path: {PATH}", "METHOD",
+                   getAllPropertiesMethod, "PATH", userMgrObjBasePath);
+        return;
+    }
+    for (const auto& t : properties)
+    {
+        auto key = t.first;
+        if (key == allPrivProperty)
+        {
+            availablePrivileges = std::get<std::vector<std::string>>(t.second);
+        }
+        else if (key == allGrpProperty)
+        {
+            availableGroups = std::get<std::vector<std::string>>(t.second);
+        }
+    }
+    // TODO: Implement Supported Privilege & Groups verification logic
+    return;
+}
+
+std::timespec UserAccess::getUpdatedFileTime()
+{
+    struct stat fileStat;
+    if (stat(ipmiUserDataFile, &fileStat) != 0)
+    {
+        lg2::debug("Error in getting last updated time stamp");
+        return std::timespec{0, 0};
+    }
+    return fileStat.st_mtim;
+}
+
+void UserAccess::getUserProperties(const DbusUserObjProperties& properties,
+                                   std::vector<std::string>& usrGrps,
+                                   std::string& usrPriv, bool& usrEnabled)
+{
+    for (const auto& t : properties)
+    {
+        std::string key = t.first;
+        if (key == userPrivProperty)
+        {
+            usrPriv = std::get<std::string>(t.second);
+        }
+        else if (key == userGrpProperty)
+        {
+            usrGrps = std::get<std::vector<std::string>>(t.second);
+        }
+        else if (key == userEnabledProperty)
+        {
+            usrEnabled = std::get<bool>(t.second);
+        }
+    }
+    return;
+}
+
+int UserAccess::getUserObjProperties(const DbusUserObjValue& userObjs,
+                                     std::vector<std::string>& usrGrps,
+                                     std::string& usrPriv, bool& usrEnabled)
+{
+    auto usrObj = userObjs.find(usersInterface);
+    if (usrObj != userObjs.end())
+    {
+        getUserProperties(usrObj->second, usrGrps, usrPriv, usrEnabled);
+        return 0;
+    }
+    return -EIO;
+}
+
+void UserAccess::cacheUserDataFile()
+{
+    boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+        userLock{*userMutex};
+    try
+    {
+        readUserData();
+    }
+    catch (const std::ios_base::failure& e)
+    { // File is empty, create it for the first time
+        std::fill(reinterpret_cast<uint8_t*>(&usersTbl),
+                  reinterpret_cast<uint8_t*>(&usersTbl) + sizeof(usersTbl), 0);
+        // user index 0 is reserved, starts with 1
+        for (size_t userIndex = 1; userIndex <= ipmiMaxUsers; ++userIndex)
+        {
+            for (size_t chIndex = 0; chIndex < ipmiMaxChannels; ++chIndex)
+            {
+                usersTbl.user[userIndex].userPrivAccess[chIndex].privilege =
+                    privNoAccess;
+                usersTbl.user[userIndex]
+                    .payloadAccess[chIndex]
+                    .stdPayloadEnables1[static_cast<uint8_t>(
+                        ipmi::PayloadType::SOL)] = true;
+            }
+        }
+        writeUserData();
+    }
+    // Create lock file if it does not exist
+    int fd = open(ipmiUserSignalLockFile, O_CREAT | O_TRUNC | O_SYNC,
+                  S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+    if (fd < 0)
+    {
+        lg2::error("Error in creating IPMI user signal lock file");
+        throw std::ios_base::failure(
+            "Error in creating temporary IPMI user signal lock file");
+    }
+    close(fd);
+
+    sigHndlrLock = boost::interprocess::file_lock(ipmiUserSignalLockFile);
+    // Register it for single object and single process either netipmid /
+    // host-ipmid
+    if (userUpdatedSignal == nullptr && sigHndlrLock.try_lock())
+    {
+        lg2::debug("Registering signal handler");
+        userUpdatedSignal = std::make_unique<sdbusplus::bus::match_t>(
+            bus,
+            sdbusplus::bus::match::rules::type::signal() +
+                sdbusplus::bus::match::rules::interface(dBusObjManager) +
+                sdbusplus::bus::match::rules::path(userMgrObjBasePath),
+            [&](sdbusplus::message_t& msg) {
+                userUpdatedSignalHandler(*this, msg);
+            });
+        userMgrRenamedSignal = std::make_unique<sdbusplus::bus::match_t>(
+            bus,
+            sdbusplus::bus::match::rules::type::signal() +
+                sdbusplus::bus::match::rules::interface(userMgrInterface) +
+                sdbusplus::bus::match::rules::path(userMgrObjBasePath),
+            [&](sdbusplus::message_t& msg) {
+                userUpdatedSignalHandler(*this, msg);
+            });
+        userPropertiesSignal = std::make_unique<sdbusplus::bus::match_t>(
+            bus,
+            sdbusplus::bus::match::rules::type::signal() +
+                sdbusplus::bus::match::rules::path_namespace(userObjBasePath) +
+                sdbusplus::bus::match::rules::interface(
+                    dBusPropertiesInterface) +
+                sdbusplus::bus::match::rules::member(propertiesChangedSignal) +
+                sdbusplus::bus::match::rules::argN(0, usersInterface),
+            [&](sdbusplus::message_t& msg) {
+                userUpdatedSignalHandler(*this, msg);
+            });
+        signalHndlrObject = true;
+    }
+    std::map<DbusUserObjPath, DbusUserObjValue> managedObjs;
+    try
+    {
+        auto method =
+            bus.new_method_call(userMgrService, userMgrObjBasePath,
+                                dBusObjManager, getManagedObjectsMethod);
+        auto reply = bus.call(method);
+        reply.read(managedObjs);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::debug("Failed to excute {METHOD}, path: {PATH}", "METHOD",
+                   getManagedObjectsMethod, "PATH", userMgrObjBasePath);
+        return;
+    }
+    bool updateRequired = false;
+    UsersTbl* userData = &usersTbl;
+    // user index 0 is reserved, starts with 1
+    for (size_t usrIdx = 1; usrIdx <= ipmiMaxUsers; ++usrIdx)
+    {
+        if ((userData->user[usrIdx].userInSystem) &&
+            (userData->user[usrIdx].userName[0] != '\0'))
+        {
+            std::vector<std::string> usrGrps;
+            std::string usrPriv;
+
+            std::string userName(
+                reinterpret_cast<char*>(userData->user[usrIdx].userName), 0,
+                ipmiMaxUserName);
+            sdbusplus::message::object_path tempUserPath(userObjBasePath);
+            tempUserPath /= userName;
+            std::string usersPath(tempUserPath);
+
+            auto usrObj = managedObjs.find(usersPath);
+            if (usrObj != managedObjs.end())
+            {
+                bool usrEnabled = false;
+
+                // User exist. Lets check and update other fileds
+                getUserObjProperties(usrObj->second, usrGrps, usrPriv,
+                                     usrEnabled);
+                if (std::find(usrGrps.begin(), usrGrps.end(), ipmiGrpName) ==
+                    usrGrps.end())
+                {
+                    updateRequired = true;
+                    // Group "ipmi" is removed so lets remove user in IPMI
+                    deleteUserIndex(usrIdx);
+                }
+                else
+                {
+                    // Group "ipmi" is present so lets update other properties
+                    // in IPMI
+                    uint8_t priv = UserAccess::convertToIPMIPrivilege(usrPriv) &
+                                   privMask;
+                    // Update all channels priv, only if it is not equivalent to
+                    // getUsrMgmtSyncIndex()
+                    if (userData->user[usrIdx]
+                            .userPrivAccess[getUsrMgmtSyncIndex()]
+                            .privilege != priv)
+                    {
+                        updateRequired = true;
+                        for (size_t chIndex = 0; chIndex < ipmiMaxChannels;
+                             ++chIndex)
+                        {
+                            userData->user[usrIdx]
+                                .userPrivAccess[chIndex]
+                                .privilege = priv;
+                        }
+                    }
+                    if (userData->user[usrIdx].userEnabled != usrEnabled)
+                    {
+                        updateRequired = true;
+                        userData->user[usrIdx].userEnabled = usrEnabled;
+                    }
+                }
+
+                // We are done with this obj. lets delete from MAP
+                managedObjs.erase(usrObj);
+            }
+            else
+            {
+                updateRequired = true;
+                deleteUserIndex(usrIdx);
+            }
+        }
+    }
+
+    // Walk through remnaining managedObj users list
+    // Add them to ipmi data base
+    for (const auto& usrObj : managedObjs)
+    {
+        std::vector<std::string> usrGrps;
+        std::string usrPriv, userName;
+        bool usrEnabled = false;
+        std::string usrObjPath = std::string(usrObj.first);
+        if (getUserNameFromPath(usrObj.first.str, userName) != 0)
+        {
+            lg2::error("Error in user object path");
+            continue;
+        }
+        getUserObjProperties(usrObj.second, usrGrps, usrPriv, usrEnabled);
+        // Add 'ipmi' group users
+        if (std::find(usrGrps.begin(), usrGrps.end(), ipmiGrpName) !=
+            usrGrps.end())
+        {
+            updateRequired = true;
+            // CREATE NEW USER
+            if (true != addUserEntry(userName, usrPriv, usrEnabled))
+            {
+                break;
+            }
+        }
+    }
+
+    if (updateRequired)
+    {
+        // All userData slots update done. Lets write the data
+        writeUserData();
+    }
+
+    return;
+}
+} // namespace ipmi
diff --git a/user_channel/user_mgmt.hpp b/user_channel/user_mgmt.hpp
new file mode 100644
index 0000000..74166c2
--- /dev/null
+++ b/user_channel/user_mgmt.hpp
@@ -0,0 +1,406 @@
+/*.
+// Copyright (c) 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 "user_layer.hpp"
+
+#include <boost/interprocess/sync/file_lock.hpp>
+#include <boost/interprocess/sync/named_recursive_mutex.hpp>
+#include <ipmid/api.hpp>
+#include <sdbusplus/bus.hpp>
+
+#include <cstdint>
+#include <ctime>
+#include <variant>
+
+namespace ipmi
+{
+
+using DbusUserPropVariant =
+    std::variant<std::vector<std::string>, std::string, bool>;
+
+using DbusUserObjPath = sdbusplus::message::object_path;
+
+using DbusUserObjProperties =
+    std::vector<std::pair<std::string, DbusUserPropVariant>>;
+
+using DbusUserObjValue = std::map<std::string, DbusUserObjProperties>;
+
+/**
+ * @enum User update events.
+ */
+enum class UserUpdateEvent
+{
+    reservedEvent,
+    userCreated,
+    userDeleted,
+    userRenamed,
+    userGrpUpdated,
+    userPrivUpdated,
+    userStateUpdated
+};
+
+/** @struct UserPrivAccess
+ *
+ *  Structure for user privilege access (refer spec sec 22.22)
+ */
+struct UserPrivAccess
+{
+    uint8_t privilege;
+    bool ipmiEnabled;
+    bool linkAuthEnabled;
+    bool accessCallback;
+};
+
+/** @struct UserInfo
+ *
+ *  Structure for user related information
+ */
+struct UserInfo
+{
+    uint8_t userName[ipmiMaxUserName];
+    UserPrivAccess userPrivAccess[ipmiMaxChannels];
+    bool userEnabled;
+    bool userInSystem;
+    bool fixedUserName;
+    PayloadAccess payloadAccess[ipmiMaxChannels];
+};
+
+/** @struct UsersTbl
+ *
+ *  Structure for array of user related information
+ */
+struct UsersTbl
+{
+    //+1 to map with UserId directly. UserId 0 is reserved.
+    UserInfo user[ipmiMaxUsers + 1];
+};
+
+/** @brief PAM User Authentication check
+ *
+ *  @param[in] username - username in string
+ *  @param[in] password	- password in string
+ *
+ *  @return status
+ */
+bool pamUserCheckAuthenticate(std::string_view username,
+                              std::string_view password);
+
+class UserAccess;
+
+UserAccess& getUserAccessObject();
+
+class UserAccess
+{
+  public:
+    UserAccess(const UserAccess&) = delete;
+    UserAccess& operator=(const UserAccess&) = delete;
+    UserAccess(UserAccess&&) = delete;
+    UserAccess& operator=(UserAccess&&) = delete;
+
+    ~UserAccess();
+    UserAccess();
+
+    /** @brief determines valid channel
+     *
+     *  @param[in] chNum - channel number
+     *
+     *  @return true if valid, false otherwise
+     */
+    static bool isValidChannel(const uint8_t chNum);
+
+    /** @brief determines valid userId
+     *
+     *  @param[in] userId - user id
+     *
+     *  @return true if valid, false otherwise
+     */
+    static bool isValidUserId(const uint8_t userId);
+
+    /** @brief determines valid user privilege
+     *
+     *  @param[in] priv - Privilege
+     *
+     *  @return true if valid, false otherwise
+     */
+    static bool isValidPrivilege(const uint8_t priv);
+
+    /** @brief determines sync index to be mapped with common-user-management
+     *
+     *  @return Index which will be used as sync index
+     */
+    static uint8_t getUsrMgmtSyncIndex();
+
+    /** @brief Converts system privilege to IPMI privilege
+     *
+     *  @param[in] value - Privilege in string
+     *
+     *  @return CommandPrivilege - IPMI privilege type
+     */
+    static CommandPrivilege convertToIPMIPrivilege(const std::string& value);
+
+    /** @brief Converts IPMI privilege to system privilege
+     *
+     *  @param[in] value - IPMI privilege
+     *
+     *  @return System privilege in string
+     */
+    static std::string convertToSystemPrivilege(const CommandPrivilege& value);
+
+    /** @brief determines whether user name is valid
+     *
+     *  @param[in] userNameInChar - user name
+     *
+     *  @return true if valid, false otherwise
+     */
+    bool isValidUserName(const std::string& userName);
+
+    /** @brief determines whether ipmi is in available groups list
+     *
+     * @return true if ipmi group is present, false otherwise
+     */
+    bool isIpmiInAvailableGroupList();
+
+    /** @brief provides user id of the user
+     *
+     *  @param[in] userName - user name
+     *
+     *  @return user id of the user, else invalid user id (0xFF), if user not
+     * found
+     */
+    uint8_t getUserId(const std::string& userName);
+
+    /** @brief provides user information
+     *
+     *  @param[in] userId - user id
+     *
+     *  @return UserInfo for the specified user id
+     */
+    UserInfo* getUserInfo(const uint8_t userId);
+
+    /** @brief sets user information
+     *
+     *  @param[in] userId - user id
+     *  @param[in] userInfo - user information
+     *
+     */
+    void setUserInfo(const uint8_t userId, UserInfo* userInfo);
+
+    /** @brief provides user name
+     *
+     *  @param[in] userId - user id
+     *  @param[out] userName - user name
+     *
+     *  @return ccSuccess for success, others for failure.
+     */
+    Cc getUserName(const uint8_t userId, std::string& userName);
+
+    /** @brief to set user name
+     *
+     *  @param[in] userId - user id
+     *  @param[in] userName - user name
+     *
+     *  @return ccSuccess for success, others for failure.
+     */
+    Cc setUserName(const uint8_t userId, const std::string& userName);
+
+    /** @brief to set user enabled state
+     *
+     *  @param[in] userId - user id
+     *  @param[in] enabledState - enabled state of the user
+     *
+     *  @return ccSuccess for success, others for failure.
+     */
+    Cc setUserEnabledState(const uint8_t userId, const bool& enabledState);
+
+    /** @brief to set user password
+     *
+     *  @param[in] userId - user id
+     *  @param[in] userPassword  - new password of the user
+     *
+     *  @return ccSuccess for success, others for failure.
+     */
+    Cc setUserPassword(const uint8_t userId, const char* userPassword);
+
+    /** @brief to set special user password
+     *
+     *  @param[in] userName - user name
+     *  @param[in] userPassword  - new password of the user
+     *
+     *  @return ccSuccess for success, others for failure.
+     */
+    Cc setSpecialUserPassword(const std::string& userName,
+                              const SecureString& userPassword);
+
+    /** @brief to set user privilege and access details
+     *
+     *  @param[in] userId - user id
+     *  @param[in] chNum - channel number
+     *  @param[in] privAccess - privilege access
+     *  @param[in] otherPrivUpdates - other privilege update flag to update ipmi
+     * enable, link authentication and access callback
+     *
+     *  @return ccSuccess for success, others for failure.
+     */
+    Cc setUserPrivilegeAccess(const uint8_t userId, const uint8_t chNum,
+                              const UserPrivAccess& privAccess,
+                              const bool& otherPrivUpdates);
+
+    /** @brief to get user payload access details from userInfo entry.
+     *
+     *  @param[in] userInfo    - userInfo entry in usersTbl.
+     *  @param[out] stdPayload - stdPayloadEnables1 in a 2D-array.
+     *  @param[out] oemPayload - oemPayloadEnables1 in a 2D-array.
+     *
+     *  @details Update the given 2D-arrays using the payload access details
+     *  available in the given userInfo entry (from usersTbl).
+     *  This 2D-array will be mapped to a JSON object (which will be written to
+     *  a JSON file subsequently).
+     */
+    void readPayloadAccessFromUserInfo(
+        const UserInfo& userInfo,
+        std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>&
+            stdPayload,
+        std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>&
+            oemPayload);
+
+    /** @brief to update user payload access details in userInfo entry.
+     *
+     *  @param[in] stdPayload - stdPayloadEnables1 in a 2D-array.
+     *  @param[in] oemPayload - oemPayloadEnables1 in a 2D-array.
+     *  @param[out] userInfo  - userInfo entry in usersTbl.
+     *
+     *  @details Update user payload access details of a given userInfo
+     *  entry (in usersTbl) with the information provided in given 2D-arrays.
+     *  This 2D-array was created out of a JSON object (which was created by
+     *  parsing a JSON file).
+     */
+    void updatePayloadAccessInUserInfo(
+        const std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>&
+            stdPayload,
+        const std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>&
+            oemPayload,
+        UserInfo& userInfo);
+
+    /** @brief to set user payload access details
+     *
+     *  @param[in] chNum - channel number
+     *  @param[in] operation - Enable / Disable
+     *  @param[in] userId - user id
+     *  @param[in] payloadAccess - payload access
+     *
+     *  @return ccSuccess for success, others for failure.
+     */
+    Cc setUserPayloadAccess(const uint8_t chNum, const uint8_t operation,
+                            const uint8_t userId,
+                            const PayloadAccess& payloadAccess);
+
+    /** @brief reads user management related data from configuration file
+     *
+     */
+    void readUserData();
+
+    /** @brief writes user management related data to configuration file
+     *
+     */
+    void writeUserData();
+
+    /** @brief Funtion which checks and reload configuration file data if
+     * needed.
+     *
+     */
+    void checkAndReloadUserData();
+
+    /** @brief provides user details from D-Bus user property data
+     *
+     *  @param[in] properties - D-Bus user property
+     *  @param[out] usrGrps - user group details
+     *  @param[out] usrPriv - user privilege
+     *  @param[out] usrEnabled - enabled state of the user.
+     *
+     *  @return 0 for success, -errno for failure.
+     */
+    void getUserProperties(const DbusUserObjProperties& properties,
+                           std::vector<std::string>& usrGrps,
+                           std::string& usrPriv, bool& usrEnabled);
+
+    /** @brief provides user details from D-Bus user object data
+     *
+     *  @param[in] userObjs - D-Bus user object
+     *  @param[out] usrGrps - user group details
+     *  @param[out] usrPriv - user privilege
+     *  @param[out] usrEnabled - enabled state of the user.
+     *
+     *  @return 0 for success, -errno for failure.
+     */
+    int getUserObjProperties(const DbusUserObjValue& userObjs,
+                             std::vector<std::string>& usrGrps,
+                             std::string& usrPriv, bool& usrEnabled);
+
+    /** @brief function to add user entry information to the configuration
+     *
+     *  @param[in] userName - user name
+     *  @param[in] priv - privilege of the user
+     *  @param[in] enabled - enabled state of the user
+     *
+     *  @return true for success, false for failure
+     */
+    bool addUserEntry(const std::string& userName, const std::string& priv,
+                      const bool& enabled);
+
+    /** @brief function to delete user entry based on user index
+     *
+     *  @param[in] usrIdx - user index
+     *
+     */
+    void deleteUserIndex(const size_t& usrIdx);
+
+    /** @brief function to get users table
+     *
+     */
+    UsersTbl* getUsersTblPtr();
+
+    std::unique_ptr<boost::interprocess::named_recursive_mutex> userMutex{
+        nullptr};
+
+  private:
+    UsersTbl usersTbl;
+    std::vector<std::string> availablePrivileges;
+    std::vector<std::string> availableGroups;
+    sdbusplus::bus_t bus;
+    std::timespec fileLastUpdatedTime;
+    bool signalHndlrObject = false;
+    boost::interprocess::file_lock sigHndlrLock;
+    boost::interprocess::file_lock mutexCleanupLock;
+
+    /** @brief function to get user configuration file timestamp
+     *
+     *  @return time stamp or -EIO for failure
+     */
+    std::timespec getUpdatedFileTime();
+
+    /** @brief function to available system privileges and groups
+     *
+     */
+    void getSystemPrivAndGroups();
+
+    /** @brief function to init user data from configuration & D-Bus objects
+     * and to register for signals
+     *
+     */
+    void cacheUserDataFile();
+};
+
+} // namespace ipmi
diff --git a/user_channel/usercommands.cpp b/user_channel/usercommands.cpp
new file mode 100644
index 0000000..3e1b3ca
--- /dev/null
+++ b/user_channel/usercommands.cpp
@@ -0,0 +1,679 @@
+/*
+// Copyright (c) 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.
+*/
+
+#include "usercommands.hpp"
+
+#include "channel_layer.hpp"
+#include "user_layer.hpp"
+
+#include <security/pam_appl.h>
+
+#include <ipmid/api.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <regex>
+
+namespace ipmi
+{
+
+static constexpr uint8_t enableOperation = 0x00;
+static constexpr uint8_t disableOperation = 0x01;
+
+/** @brief implements the set user access command
+ *  @param ctx - IPMI context pointer (for channel)
+ *  @param channel - channel number
+ *  @param ipmiEnabled - indicates ipmi messaging state
+ *  @param linkAuthEnabled - indicates link authentication state
+ *  @param accessCallback - indicates callback state
+ *  @param bitsUpdate - indicates update request
+ *  @param userId - user id
+ *  @param reserved1 - skip 2 bits
+ *  @param privilege - user privilege
+ *  @param reserved2 - skip 4 bits
+ *  @param sessionLimit - optional - unused for now
+ *
+ *  @returns ipmi completion code
+ */
+ipmi::RspType<> ipmiSetUserAccess(
+    ipmi::Context::ptr ctx, uint4_t channel, uint1_t ipmiEnabled,
+    uint1_t linkAuthEnabled, uint1_t accessCallback, uint1_t bitsUpdate,
+
+    uint6_t userId, uint2_t reserved1,
+
+    uint4_t privilege, uint4_t reserved2,
+
+    std::optional<uint8_t> sessionLimit)
+{
+    uint8_t sessLimit = sessionLimit.value_or(0);
+    if (reserved1 || reserved2 || sessLimit ||
+        !ipmiUserIsValidPrivilege(static_cast<uint8_t>(privilege)))
+    {
+        lg2::debug("Set user access - Invalid field in request");
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    uint8_t chNum =
+        convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel);
+    if (!isValidChannel(chNum))
+    {
+        lg2::debug("Set user access - Invalid channel request");
+        return ipmi::response(invalidChannel);
+    }
+    if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
+    {
+        lg2::debug("Set user access - No support on channel");
+        return ipmi::response(ccActionNotSupportedForChannel);
+    }
+    if (!ipmiUserIsValidUserId(static_cast<uint8_t>(userId)))
+    {
+        lg2::debug("Set user access - Parameter out of range");
+        return ipmi::responseParmOutOfRange();
+    }
+
+    PrivAccess privAccess = {};
+    if (bitsUpdate)
+    {
+        privAccess.ipmiEnabled = static_cast<uint8_t>(ipmiEnabled);
+        privAccess.linkAuthEnabled = static_cast<uint8_t>(linkAuthEnabled);
+        privAccess.accessCallback = static_cast<uint8_t>(accessCallback);
+    }
+    privAccess.privilege = static_cast<uint8_t>(privilege);
+    return ipmi::response(
+        ipmiUserSetPrivilegeAccess(static_cast<uint8_t>(userId), chNum,
+                                   privAccess, static_cast<bool>(bitsUpdate)));
+}
+
+/** @brief implements the set user access command
+ *  @param ctx - IPMI context pointer (for channel)
+ *  @param channel - channel number
+ *  @param reserved1 - skip 4 bits
+ *  @param userId - user id
+ *  @param reserved2 - skip 2 bits
+ *
+ *  @returns ipmi completion code plus response data
+ *   - maxChUsers - max channel users
+ *   - reserved1 - skip 2 bits
+ *   - enabledUsers - enabled users count
+ *   - enabledStatus - enabled status
+ *   - fixedUsers - fixed users count
+ *   - reserved2 - skip 2 bits
+ *   - privilege - user privilege
+ *   - ipmiEnabled - ipmi messaging state
+ *   - linkAuthEnabled - link authenticatin state
+ *   - accessCallback - callback state
+ *   - reserved - skip 1 bit
+ */
+ipmi::RspType<uint6_t, // max channel users
+              uint2_t, // reserved1
+
+              uint6_t, // enabled users count
+              uint2_t, // enabled status
+
+              uint6_t, // fixed users count
+              uint2_t, // reserved2
+
+              uint4_t, // privilege
+              uint1_t, // ipmi messaging state
+              uint1_t, // link authentication state
+              uint1_t, // access callback state
+              uint1_t  // reserved3
+              >
+    ipmiGetUserAccess(ipmi::Context::ptr ctx, uint4_t channel,
+                      uint4_t reserved1,
+
+                      uint6_t userId, uint2_t reserved2)
+{
+    uint8_t chNum =
+        convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel);
+
+    if (reserved1 || reserved2 || !isValidChannel(chNum))
+    {
+        lg2::debug("Get user access - Invalid field in request");
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
+    {
+        lg2::debug("Get user access - No support on channel");
+        return ipmi::response(ccActionNotSupportedForChannel);
+    }
+    if (!ipmiUserIsValidUserId(static_cast<uint8_t>(userId)))
+    {
+        lg2::debug("Get user access - Parameter out of range");
+        return ipmi::responseParmOutOfRange();
+    }
+
+    uint8_t maxChUsers = 0, enabledUsers = 0, fixedUsers = 0;
+    ipmi::Cc retStatus;
+    retStatus = ipmiUserGetAllCounts(maxChUsers, enabledUsers, fixedUsers);
+    if (retStatus != ccSuccess)
+    {
+        return ipmi::response(retStatus);
+    }
+
+    bool enabledState = false;
+    retStatus =
+        ipmiUserCheckEnabled(static_cast<uint8_t>(userId), enabledState);
+    if (retStatus != ccSuccess)
+    {
+        return ipmi::response(retStatus);
+    }
+
+    uint2_t enabledStatus = enabledState ? userIdEnabledViaSetPassword
+                                         : userIdDisabledViaSetPassword;
+    PrivAccess privAccess{};
+    retStatus = ipmiUserGetPrivilegeAccess(static_cast<uint8_t>(userId), chNum,
+                                           privAccess);
+    if (retStatus != ccSuccess)
+    {
+        return ipmi::response(retStatus);
+    }
+    constexpr uint2_t res2Bits = 0;
+    return ipmi::responseSuccess(
+        static_cast<uint6_t>(maxChUsers), res2Bits,
+
+        static_cast<uint6_t>(enabledUsers), enabledStatus,
+
+        static_cast<uint6_t>(fixedUsers), res2Bits,
+
+        static_cast<uint4_t>(privAccess.privilege),
+        static_cast<uint1_t>(privAccess.ipmiEnabled),
+        static_cast<uint1_t>(privAccess.linkAuthEnabled),
+        static_cast<uint1_t>(privAccess.accessCallback),
+        static_cast<uint1_t>(privAccess.reserved));
+}
+
+/** @brief implementes the get user name command
+ *  @param[in] ctx - ipmi command context
+ *  @param[in] userId - 6-bit user ID
+ *  @param[in] reserved - 2-bits reserved
+ *  @param[in] name - 16-byte array for username
+
+ *  @returns ipmi response
+ */
+ipmi::RspType<> ipmiSetUserName(
+    [[maybe_unused]] ipmi::Context::ptr ctx, uint6_t id, uint2_t reserved,
+    const std::array<uint8_t, ipmi::ipmiMaxUserName>& name)
+{
+    if (reserved)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+    uint8_t userId = static_cast<uint8_t>(id);
+    if (!ipmiUserIsValidUserId(userId))
+    {
+        lg2::debug("Set user name - Invalid user id");
+        return ipmi::responseParmOutOfRange();
+    }
+
+    size_t nameLen = strnlen(reinterpret_cast<const char*>(name.data()),
+                             ipmi::ipmiMaxUserName);
+    const std::string strUserName(reinterpret_cast<const char*>(name.data()),
+                                  nameLen);
+
+    ipmi::Cc res = ipmiUserSetUserName(userId, strUserName);
+    return ipmi::response(res);
+}
+
+/** @brief implementes the get user name command
+ *  @param[in] ctx - ipmi command context
+ *  @param[in] userId - 6-bit user ID
+ *  @param[in] reserved - 2-bits reserved
+
+ *  @returns ipmi response with 16-byte username
+ */
+ipmi::RspType<std::array<uint8_t, ipmi::ipmiMaxUserName>> // user name
+    ipmiGetUserName([[maybe_unused]] ipmi::Context::ptr ctx, uint6_t id,
+                    uint2_t reserved)
+{
+    if (reserved)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    uint8_t userId = static_cast<uint8_t>(id);
+    std::string userName;
+    if (ipmiUserGetUserName(userId, userName) != ccSuccess)
+    { // Invalid User ID
+        lg2::debug("User Name not found, user Id: {USER_ID}", "USER_ID",
+                   userId);
+        return ipmi::responseParmOutOfRange();
+    }
+    // copy the std::string into a fixed array
+    if (userName.size() > ipmi::ipmiMaxUserName)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+    std::array<uint8_t, ipmi::ipmiMaxUserName> userNameFixed;
+    std::fill(userNameFixed.begin(), userNameFixed.end(), 0);
+    std::copy(userName.begin(), userName.end(), userNameFixed.begin());
+    return ipmi::responseSuccess(std::move(userNameFixed));
+}
+
+/** @brief implementes the get user name command
+ *  @param[in] ctx - ipmi command context
+ *  @param[in] userId - 6-bit user ID
+ *  @param[in] reserved - 2-bits reserved
+
+ *  @returns ipmi response with 16-byte username
+ */
+ipmi::RspType<> // user name
+    ipmiSetUserPassword([[maybe_unused]] ipmi::Context::ptr ctx, uint6_t id,
+                        bool reserved1, bool pwLen20, uint2_t operation,
+                        uint6_t reserved2, SecureBuffer& userPassword)
+{
+    if (reserved1 || reserved2)
+    {
+        lg2::debug("Invalid data field in request");
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    static constexpr uint2_t opDisableUser = 0x00;
+    static constexpr uint2_t opEnableUser = 0x01;
+    static constexpr uint2_t opSetPassword = 0x02;
+    static constexpr uint2_t opTestPassword = 0x03;
+
+    // If set / test password operation then password size has to be 16 or 20
+    // bytes based on the password size bit
+    if (((operation == opSetPassword) || (operation == opTestPassword)) &&
+        ((pwLen20 && (userPassword.size() != maxIpmi20PasswordSize)) ||
+         (!pwLen20 && (userPassword.size() != maxIpmi15PasswordSize))))
+    {
+        lg2::debug("Invalid Length");
+        return ipmi::responseReqDataLenInvalid();
+    }
+
+    size_t passwordLength = userPassword.size();
+
+    uint8_t userId = static_cast<uint8_t>(id);
+    std::string userName;
+    if (ipmiUserGetUserName(userId, userName) != ccSuccess)
+    {
+        lg2::debug("User Name not found, user Id: {USER_ID}", "USER_ID",
+                   userId);
+        return ipmi::responseParmOutOfRange();
+    }
+
+    if (operation == opSetPassword)
+    {
+        // turn the non-nul terminated SecureBuffer into a SecureString
+        SecureString password(
+            reinterpret_cast<const char*>(userPassword.data()), passwordLength);
+        ipmi::Cc res = ipmiUserSetUserPassword(userId, password.data());
+        return ipmi::response(res);
+    }
+    else if (operation == opEnableUser || operation == opDisableUser)
+    {
+        ipmi::Cc res =
+            ipmiUserUpdateEnabledState(userId, static_cast<bool>(operation));
+        return ipmi::response(res);
+    }
+    else if (operation == opTestPassword)
+    {
+        SecureString password = ipmiUserGetPassword(userName);
+        // extend with zeros, if needed
+        if (password.size() < passwordLength)
+        {
+            password.resize(passwordLength, '\0');
+        }
+        SecureString testPassword(
+            reinterpret_cast<const char*>(userPassword.data()), passwordLength);
+        // constant time string compare: always compare exactly as many bytes
+        // as the length of the input, resizing the actual password to match,
+        // maintaining a knowledge if the sizes differed originally
+        static const std::array<char, maxIpmi20PasswordSize> empty = {'\0'};
+        size_t cmpLen = testPassword.size();
+        bool pwLenDiffers = password.size() != cmpLen;
+        const char* cmpPassword = nullptr;
+        if (pwLenDiffers)
+        {
+            cmpPassword = empty.data();
+        }
+        else
+        {
+            cmpPassword = password.data();
+        }
+        bool pwBad = CRYPTO_memcmp(cmpPassword, testPassword.data(), cmpLen);
+        pwBad |= pwLenDiffers;
+        if (pwBad)
+        {
+            lg2::debug("Test password failed, user Id: {USER_ID}", "USER_ID",
+                       userId);
+            return ipmi::response(ipmiCCPasswdFailMismatch);
+        }
+        return ipmi::responseSuccess();
+    }
+    return ipmi::responseInvalidFieldRequest();
+}
+
+/** @brief implements the get channel authentication command
+ *  @param ctx - IPMI context pointer (for channel)
+ *  @param extData - get IPMI 2.0 extended data
+ *  @param reserved1 - skip 3 bits
+ *  @param chNum - channel number to get info about
+ *  @param reserved2 - skip 4 bits
+ *  @param privLevel - requested privilege level
+
+ *  @returns ipmi completion code plus response data
+ *   - channel number
+ *   - rmcpAuthTypes - RMCP auth types (IPMI 1.5)
+ *   - reserved1
+ *   - extDataSupport - true for IPMI 2.0 extensions
+ *   - anonymousLogin - true for anonymous login enabled
+ *   - nullUsers - true for null user names enabled
+ *   - nonNullUsers - true for non-null usernames enabled
+ *   - userAuth - false for user authentication enabled
+ *   - perMessageAuth - false for per message authentication enabled
+ *   - KGStatus - true for Kg required for authentication
+ *   - reserved2
+ *   - rmcp - RMCP (IPMI 1.5) connection support
+ *   - rmcpp - RMCP+ (IPMI 2.0) connection support
+ *   - reserved3
+ *   - oemID - OEM IANA of any OEM auth support
+ *   - oemAuxillary - OEM data for auth
+ */
+ipmi::RspType<uint8_t,  // channel number
+              uint6_t,  // rmcpAuthTypes
+              bool,     // reserved1
+              bool,     // extDataSupport
+              bool,     // anonymousLogin
+              bool,     // nullUsers
+              bool,     // nonNullUsers
+              bool,     // userAuth
+              bool,     // perMessageAuth
+              bool,     // KGStatus
+              uint2_t,  // reserved2
+              bool,     // rmcp
+              bool,     // rmcpp
+              uint6_t,  // reserved3
+              uint24_t, // oemID
+              uint8_t   // oemAuxillary
+              >
+    ipmiGetChannelAuthenticationCapabilities(
+        ipmi::Context::ptr ctx, uint4_t chNum, uint3_t reserved1,
+        [[maybe_unused]] bool extData, uint4_t privLevel, uint4_t reserved2)
+{
+    uint8_t channel =
+        convertCurrentChannelNum(static_cast<uint8_t>(chNum), ctx->channel);
+
+    if (reserved1 || reserved2 || !isValidChannel(channel) ||
+        !isValidPrivLimit(static_cast<uint8_t>(privLevel)))
+    {
+        lg2::debug("Get channel auth capabilities - Invalid field in request");
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    if (getChannelSessionSupport(channel) == EChannelSessSupported::none)
+    {
+        lg2::debug("Get channel auth capabilities - No support on channel");
+        return ipmi::response(ccActionNotSupportedForChannel);
+    }
+
+    constexpr bool extDataSupport = true; // true for IPMI 2.0 extensions
+    constexpr bool reserved3 = false;
+    constexpr uint6_t rmcpAuthTypes = 0;  // IPMI 1.5 auth types - not supported
+    constexpr uint2_t reserved4 = 0;
+    constexpr bool KGStatus = false;      // Not supporting now.
+    constexpr bool perMessageAuth = false; // Per message auth - enabled
+    constexpr bool userAuth = false;       // User authentication - enabled
+    constexpr bool nullUsers = false;      // Null user names - not supported
+    constexpr bool anonymousLogin = false; // Anonymous login - not supported
+    constexpr uint6_t reserved5 = 0;
+    constexpr bool rmcpp = true;           // IPMI 2.0 - supported
+    constexpr bool rmcp = false;           // IPMI 1.5 - not supported
+    constexpr uint24_t oemID = 0;
+    constexpr uint8_t oemAuxillary = 0;
+
+    bool nonNullUsers = 0;
+    uint8_t maxChUsers = 0, enabledUsers = 0, fixedUsers = 0;
+    ipmi::ipmiUserGetAllCounts(maxChUsers, enabledUsers, fixedUsers);
+    nonNullUsers = enabledUsers > 0;
+
+    return ipmi::responseSuccess(
+        channel, rmcpAuthTypes, reserved3, extDataSupport, anonymousLogin,
+        nullUsers, nonNullUsers, userAuth, perMessageAuth, KGStatus, reserved4,
+        rmcp, rmcpp, reserved5, oemID, oemAuxillary);
+}
+
+/** @brief implements the set user payload access command.
+ *  @param ctx - IPMI context pointer (for channel)
+ *  @param channel - channel number (4 bits)
+ *  @param reserved1 - skip 4 bits
+ *  @param userId - user id (6 bits)
+ *  @param operation - access ENABLE /DISABLE. (2 bits)
+ *  @param stdPayload0 - IPMI - reserved. (1 bit)
+ *  @param stdPayload1 - SOL.             (1 bit)
+ *  @param stdPayload2 -                  (1 bit)
+ *  @param stdPayload3 -                  (1 bit)
+ *  @param stdPayload4 -                  (1 bit)
+ *  @param stdPayload5 -                  (1 bit)
+ *  @param stdPayload6 -                  (1 bit)
+ *  @param stdPayload7 -                  (1 bit)
+ *  @param stdPayloadEnables2Reserved -   (8 bits)
+ *  @param oemPayload0 -                  (1 bit)
+ *  @param oemPayload1 -                  (1 bit)
+ *  @param oemPayload2 -                  (1 bit)
+ *  @param oemPayload3 -                  (1 bit)
+ *  @param oemPayload4 -                  (1 bit)
+ *  @param oemPayload5 -                  (1 bit)
+ *  @param oemPayload6 -                  (1 bit)
+ *  @param oemPayload7 -                  (1 bit)
+ *  @param oemPayloadEnables2Reserved -   (8 bits)
+ *
+ *  @returns IPMI completion code
+ */
+ipmi::RspType<> ipmiSetUserPayloadAccess(
+    ipmi::Context::ptr ctx,
+
+    uint4_t channel, uint4_t reserved,
+
+    uint6_t userId, uint2_t operation,
+
+    bool stdPayload0ipmiReserved, bool stdPayload1SOL, bool stdPayload2,
+    bool stdPayload3, bool stdPayload4, bool stdPayload5, bool stdPayload6,
+    bool stdPayload7,
+
+    uint8_t stdPayloadEnables2Reserved,
+
+    bool oemPayload0, bool oemPayload1, bool oemPayload2, bool oemPayload3,
+    bool oemPayload4, bool oemPayload5, bool oemPayload6, bool oemPayload7,
+
+    uint8_t oemPayloadEnables2Reserved)
+{
+    auto chNum =
+        convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel);
+    // Validate the reserved args. Only SOL payload is supported as on date.
+    if (reserved || stdPayload0ipmiReserved || stdPayload2 || stdPayload3 ||
+        stdPayload4 || stdPayload5 || stdPayload6 || stdPayload7 ||
+        oemPayload0 || oemPayload1 || oemPayload2 || oemPayload3 ||
+        oemPayload4 || oemPayload5 || oemPayload6 || oemPayload7 ||
+        stdPayloadEnables2Reserved || oemPayloadEnables2Reserved ||
+        !isValidChannel(chNum))
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    if ((operation != enableOperation && operation != disableOperation))
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+    if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
+    {
+        return ipmi::response(ccActionNotSupportedForChannel);
+    }
+    if (!ipmiUserIsValidUserId(static_cast<uint8_t>(userId)))
+    {
+        return ipmi::responseParmOutOfRange();
+    }
+
+    PayloadAccess payloadAccess = {};
+    payloadAccess.stdPayloadEnables1[1] = stdPayload1SOL;
+
+    return ipmi::response(ipmiUserSetUserPayloadAccess(
+        chNum, static_cast<uint8_t>(operation), static_cast<uint8_t>(userId),
+        payloadAccess));
+}
+
+/** @brief implements the get user payload access command
+ *  This command returns information about user payload enable settings
+ *  that were set using the 'Set User Payload Access' Command.
+ *
+ *  @param ctx - IPMI context pointer (for channel)
+ *  @param channel - channel number
+ *  @param reserved1 - skip 4 bits
+ *  @param userId - user id
+ *  @param reserved2 - skip 2 bits
+ *
+ *  @returns IPMI completion code plus response data
+ *   - stdPayload0ipmiReserved - IPMI payload (reserved).
+ *   - stdPayload1SOL - SOL payload
+ *   - stdPayload2
+ *   - stdPayload3
+ *   - stdPayload4
+ *   - stdPayload5
+ *   - stdPayload6
+ *   - stdPayload7
+
+ *   - stdPayloadEnables2Reserved - Reserved.
+
+ *   - oemPayload0
+ *   - oemPayload1
+ *   - oemPayload2
+ *   - oemPayload3
+ *   - oemPayload4
+ *   - oemPayload5
+ *   - oemPayload6
+ *   - oemPayload7
+
+ *  - oemPayloadEnables2Reserved - Reserved
+ */
+ipmi::RspType<bool,    // stdPayload0ipmiReserved
+              bool,    // stdPayload1SOL
+              bool,    // stdPayload2
+              bool,    // stdPayload3
+              bool,    // stdPayload4
+              bool,    // stdPayload5
+              bool,    // stdPayload6
+              bool,    // stdPayload7
+
+              uint8_t, // stdPayloadEnables2Reserved
+
+              bool,    // oemPayload0
+              bool,    // oemPayload1
+              bool,    // oemPayload2
+              bool,    // oemPayload3
+              bool,    // oemPayload4
+              bool,    // oemPayload5
+              bool,    // oemPayload6
+              bool,    // oemPayload7
+
+              uint8_t  // oemPayloadEnables2Reserved
+              >
+    ipmiGetUserPayloadAccess(ipmi::Context::ptr ctx,
+
+                             uint4_t channel, uint4_t reserved1,
+
+                             uint6_t userId, uint2_t reserved2)
+{
+    uint8_t chNum =
+        convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel);
+
+    if (reserved1 || reserved2 || !isValidChannel(chNum))
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+    if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
+    {
+        return ipmi::response(ccActionNotSupportedForChannel);
+    }
+    if (!ipmiUserIsValidUserId(static_cast<uint8_t>(userId)))
+    {
+        return ipmi::responseParmOutOfRange();
+    }
+
+    ipmi::Cc retStatus;
+    PayloadAccess payloadAccess = {};
+    retStatus = ipmiUserGetUserPayloadAccess(
+        chNum, static_cast<uint8_t>(userId), payloadAccess);
+    if (retStatus != ccSuccess)
+    {
+        return ipmi::response(retStatus);
+    }
+    constexpr uint8_t res8bits = 0;
+    return ipmi::responseSuccess(
+        payloadAccess.stdPayloadEnables1.test(0),
+        payloadAccess.stdPayloadEnables1.test(1),
+        payloadAccess.stdPayloadEnables1.test(2),
+        payloadAccess.stdPayloadEnables1.test(3),
+        payloadAccess.stdPayloadEnables1.test(4),
+        payloadAccess.stdPayloadEnables1.test(5),
+        payloadAccess.stdPayloadEnables1.test(6),
+        payloadAccess.stdPayloadEnables1.test(7),
+
+        res8bits,
+
+        payloadAccess.oemPayloadEnables1.test(0),
+        payloadAccess.oemPayloadEnables1.test(1),
+        payloadAccess.oemPayloadEnables1.test(2),
+        payloadAccess.oemPayloadEnables1.test(3),
+        payloadAccess.oemPayloadEnables1.test(4),
+        payloadAccess.oemPayloadEnables1.test(5),
+        payloadAccess.oemPayloadEnables1.test(6),
+        payloadAccess.oemPayloadEnables1.test(7),
+
+        res8bits);
+}
+
+void registerUserIpmiFunctions() __attribute__((constructor));
+void registerUserIpmiFunctions()
+{
+    post_work([]() { ipmiUserInit(); });
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdSetUserAccessCommand,
+                          ipmi::Privilege::Admin, ipmiSetUserAccess);
+
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdGetUserAccessCommand,
+                          ipmi::Privilege::Admin, ipmiGetUserAccess);
+
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdGetUserNameCommand,
+                          ipmi::Privilege::Admin, ipmiGetUserName);
+
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdSetUserName, ipmi::Privilege::Admin,
+                          ipmiSetUserName);
+
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdSetUserPasswordCommand,
+                          ipmi::Privilege::Admin, ipmiSetUserPassword);
+
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdGetChannelAuthCapabilities,
+                          ipmi::Privilege::Callback,
+                          ipmiGetChannelAuthenticationCapabilities);
+
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdSetUserPayloadAccess,
+                          ipmi::Privilege::Admin, ipmiSetUserPayloadAccess);
+
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+                          ipmi::app::cmdGetUserPayloadAccess,
+                          ipmi::Privilege::Operator, ipmiGetUserPayloadAccess);
+
+    return;
+}
+} // namespace ipmi
diff --git a/user_channel/usercommands.hpp b/user_channel/usercommands.hpp
new file mode 100644
index 0000000..a1fb7b9
--- /dev/null
+++ b/user_channel/usercommands.hpp
@@ -0,0 +1,36 @@
+/*
+// Copyright (c) 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 <cstdint>
+
+namespace ipmi
+{
+
+/**
+ * @enum IPMI set password return codes (refer spec sec 22.30)
+ */
+enum ipmi_set_password_return_codes : uint8_t
+{
+    ipmiCCPasswdFailMismatch = 0x80,
+    ipmiCCPasswdFailWrongSize = 0x81,
+};
+
+static constexpr uint8_t userIdEnabledViaSetPassword = 0x1;
+static constexpr uint8_t userIdDisabledViaSetPassword = 0x2;
+
+void registerUserIpmiFunctions();
+} // namespace ipmi
diff --git a/whitelist-filter.cpp b/whitelist-filter.cpp
new file mode 100644
index 0000000..7fe4e22
--- /dev/null
+++ b/whitelist-filter.cpp
@@ -0,0 +1,253 @@
+#include <ipmiallowlist.hpp>
+#include <ipmid/api.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <settings.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/Control/Security/RestrictionMode/server.hpp>
+
+#include <algorithm>
+#include <array>
+
+using namespace phosphor::logging;
+using namespace sdbusplus::error::xyz::openbmc_project::common;
+
+namespace ipmi
+{
+
+// put the filter provider in an unnamed namespace
+namespace
+{
+
+/** @class AllowlistFilter
+ *
+ * Class that implements an IPMI message filter based
+ * on incoming interface and a restriction mode setting
+ */
+class AllowlistFilter
+{
+  public:
+    AllowlistFilter();
+    ~AllowlistFilter() = default;
+    AllowlistFilter(const AllowlistFilter&) = delete;
+    AllowlistFilter(AllowlistFilter&&) = delete;
+    AllowlistFilter& operator=(const AllowlistFilter&) = delete;
+    AllowlistFilter& operator=(AllowlistFilter&&) = delete;
+
+  private:
+    void postInit();
+    void cacheRestrictedMode(const std::vector<std::string>& devices);
+    void handleRestrictedModeChange(
+        sdbusplus::message_t& m,
+        const std::map<std::string, size_t>& deviceList);
+    ipmi::Cc filterMessage(ipmi::message::Request::ptr request);
+
+    std::vector<bool> restrictedMode;
+    std::shared_ptr<sdbusplus::asio::connection> bus;
+    std::unique_ptr<settings::Objects> objects;
+    std::unique_ptr<sdbusplus::bus::match_t> modeChangeMatch;
+
+    static constexpr const char restrictionModeIntf[] =
+        "xyz.openbmc_project.Control.Security.RestrictionMode";
+};
+
+AllowlistFilter::AllowlistFilter()
+{
+    bus = getSdBus();
+
+    lg2::info("Loading allowlist filter");
+    ipmi::registerFilter(ipmi::prioOpenBmcBase,
+                         [this](ipmi::message::Request::ptr request) {
+                             return filterMessage(request);
+                         });
+
+    // wait until io->run is going to fetch RestrictionMode
+    post_work([this]() { postInit(); });
+}
+
+/** @brief Get RestrictionMode of the devices which has RestrictionMode support
+ * enabled
+ *  @param[in] devices - vector of devices object path
+ *  @returns void.
+ */
+
+void AllowlistFilter::cacheRestrictedMode(
+    const std::vector<std::string>& devices)
+{
+    using namespace sdbusplus::server::xyz::openbmc_project::control::security;
+    std::string restrictionModeSetting;
+    std::string restrictionModeService;
+
+    for (auto& dev : devices)
+    {
+        try
+        {
+            restrictionModeSetting = dev;
+            restrictionModeService =
+                objects->service(restrictionModeSetting, restrictionModeIntf);
+        }
+        catch (const std::out_of_range& e)
+        {
+            lg2::error(
+                "Could not look up restriction mode interface from cache");
+            return;
+        }
+
+        std::string mode;
+        try
+        {
+            auto propValue = ipmi::getDbusProperty(
+                *bus, restrictionModeService, restrictionModeSetting,
+                restrictionModeIntf, "RestrictionMode");
+            mode = std::get<std::string>(propValue);
+        }
+        catch (const std::exception& e)
+        {
+            lg2::error("Error in RestrictionMode Get");
+            // Fail-safe to true.
+            size_t index = std::distance(&*std::begin(devices), &dev);
+            restrictedMode[index] = true;
+        }
+
+        auto restrictionMode = RestrictionMode::convertModesFromString(mode);
+
+        bool restrictMode =
+            (restrictionMode == RestrictionMode::Modes::Allowlist);
+        restrictedMode.emplace_back(restrictMode);
+
+        lg2::info("Set restrictedMode = {RESTRICTED_MODE}", "RESTRICTED_MODE",
+                  restrictMode);
+    }
+}
+
+/** @brief Update RestrictionMode if any changes in RestrictionMode
+ *  @param[in] m - sdbusplus message. Using this to get Updated Mode dbus path
+ *  @param[in] deviceList - map to store devices path and their index
+ *  @returns void.
+ */
+
+void AllowlistFilter::handleRestrictedModeChange(
+    sdbusplus::message_t& m, const std::map<std::string, size_t>& deviceList)
+{
+    using namespace sdbusplus::server::xyz::openbmc_project::control::security;
+    std::string intf;
+    std::vector<std::pair<std::string, ipmi::Value>> propertyList;
+    m.read(intf, propertyList);
+
+    std::string path = m.get_path();
+    size_t hostId = 0;
+    auto it = deviceList.find(path);
+
+    if (it == deviceList.end())
+    {
+        lg2::error("Key not found in deviceList ");
+    }
+    else
+    {
+        hostId = it->second;
+    }
+
+    for (const auto& property : propertyList)
+    {
+        if (property.first == "RestrictionMode")
+        {
+            RestrictionMode::Modes restrictionMode =
+                RestrictionMode::convertModesFromString(
+                    std::get<std::string>(property.second));
+            bool restrictMode =
+                (restrictionMode == RestrictionMode::Modes::Allowlist);
+            restrictedMode[hostId] = restrictMode;
+
+            lg2::info("Updated restrictedMode = {RESTRICTED_MODE}",
+                      "RESTRICTED_MODE", restrictMode);
+        }
+    }
+}
+
+/** @brief Get and Update RestrictionModes of supported devices
+ *  @param[in] void
+ *  @returns void.
+ */
+
+void AllowlistFilter::postInit()
+{
+    objects = std::make_unique<settings::Objects>(
+        *bus, std::vector<settings::Interface>({restrictionModeIntf}));
+    if (!objects)
+    {
+        lg2::error(
+            "Failed to create settings object; defaulting to restricted mode");
+        return;
+    }
+
+    std::vector<std::string> devices;
+    try
+    {
+        devices = objects->map.at(restrictionModeIntf);
+    }
+    catch (const std::out_of_range& e)
+    {
+        lg2::error("Could not look up restriction mode interface from cache");
+        return;
+    }
+
+    // Initialize restricted mode
+    cacheRestrictedMode(devices);
+    // Wait for changes on Restricted mode
+    std::map<std::string, size_t> deviceList;
+
+    for (size_t index = 0; index < devices.size(); index++)
+    {
+        deviceList.emplace(devices[index], index);
+    }
+
+    std::string filterStr;
+    std::string devicesDbusPath{"/xyz/openbmc_project/control"};
+
+    filterStr = sdbusplus::bus::match::rules::propertiesChangedNamespace(
+        devicesDbusPath, restrictionModeIntf);
+
+    modeChangeMatch = std::make_unique<sdbusplus::bus::match_t>(
+        *bus, filterStr, [this, deviceList](sdbusplus::message_t& m) {
+            handleRestrictedModeChange(m, deviceList);
+        });
+}
+
+/** @brief Filter IPMI messages with RestrictedMode
+ *  @param[in] request - IPMI messahe request
+ *  @returns IPMI completion code success or error.
+ */
+
+ipmi::Cc AllowlistFilter::filterMessage(ipmi::message::Request::ptr request)
+{
+    /* Getting hostIdx for all IPMI devices like hosts, debugcard and other
+   devices from ipmi::message::Request and call postInit() to get the
+   restriction mode for all the IPMI commands */
+
+    size_t hostIdx = request->ctx->hostIdx;
+
+    if (request->ctx->channel == ipmi::channelSystemIface &&
+        restrictedMode[hostIdx])
+    {
+        if (!std::binary_search(
+                allowlist.cbegin(), allowlist.cend(),
+                std::make_pair(request->ctx->netFn, request->ctx->cmd)))
+        {
+            lg2::error("Net function not allowlisted, "
+                       "NetFn: {NETFN}, Cmd: {CMD}",
+                       "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
+                       request->ctx->cmd);
+
+            return ipmi::ccInsufficientPrivilege;
+        }
+    }
+    return ipmi::ccSuccess;
+}
+
+// instantiate the AllowlistFilter when this shared object is loaded
+AllowlistFilter allowlistFilter;
+
+} // namespace
+
+} // namespace ipmi
diff --git a/xyz/openbmc_project/Ipmi/Internal/SoftPowerOff.interface.yaml b/xyz/openbmc_project/Ipmi/Internal/SoftPowerOff.interface.yaml
new file mode 100644
index 0000000..9e34734
--- /dev/null
+++ b/xyz/openbmc_project/Ipmi/Internal/SoftPowerOff.interface.yaml
@@ -0,0 +1,35 @@
+description: >
+    Implement the Soft Power Off function. On receiving the SMS_ATTN from BMC,
+    Host will respond with a GetMessageFlags command and the BMC will respond
+    with a static data indicating that Event Message Buffer is full. Host then
+    sends 'ReadEvent' command and BMC responds with an architected packet
+    mentioning that the type is SOFT_OFF. Host then goes ahead and starts to
+    quiesce. Once that is done, Host will send a hard power off command to BMC
+    and then BMC will issue a hard power off.
+
+properties:
+    - name: ResponseReceived
+      type: enum[self.HostResponse]
+      default: NotApplicable
+      description: >
+          When the response is received for 'SMS_ATN', this is set to
+          'SoftOffReceived' and is set to 'HostShutdown' when Host sends a Power
+          Off request.
+
+enumerations:
+    - name: HostResponse
+      description: >
+          Possible response types from Host for a Soft Power Off function.
+      values:
+          - name: NotApplicable
+            description: >
+                Default initial value.
+          - name: SoftOffReceived
+            description: >
+                Host has received the SMS_ATN from BMC indicating that Host
+                needs to do a Soft Power Off.
+          - name: HostShutdown
+            description: >
+                Host has sufficiently quiesced and acknowledged the shutdown
+                request such that the hardware shutdown sequence can safely be
+                performed.
diff --git a/xyz/openbmc_project/Ipmi/Internal/SoftPowerOff/meson.build b/xyz/openbmc_project/Ipmi/Internal/SoftPowerOff/meson.build
new file mode 100644
index 0000000..56c2019
--- /dev/null
+++ b/xyz/openbmc_project/Ipmi/Internal/SoftPowerOff/meson.build
@@ -0,0 +1,69 @@
+sdbuspp_prog = find_program('sdbus++')
+
+domain = 'xyz.openbmc_project.Ipmi.Internal.SoftPowerOff'
+if_yaml_file = files('../SoftPowerOff.interface.yaml')
+
+if_cpp = custom_target(
+    'server.cpp',
+    output: 'server.cpp',
+    input: if_yaml_file,
+    capture: true,
+    command: [sdbuspp_prog, '-r', root, 'interface', 'server-cpp', domain],
+)
+
+if_hpp = custom_target(
+    'server.hpp',
+    output: 'server.hpp',
+    input: if_yaml_file,
+    capture: true,
+    command: [sdbuspp_prog, '-r', root, 'interface', 'server-header', domain],
+    install: true,
+    install_dir: get_option('includedir') / 'xyz/openbmc_project/Ipmi/Internal/SoftPowerOff',
+)
+
+if_common_hpp = custom_target(
+    'common.hpp',
+    output: 'common.hpp',
+    input: if_yaml_file,
+    capture: true,
+    command: [sdbuspp_prog, '-r', root, 'interface', 'common-header', domain],
+    install: true,
+    install_dir: get_option('includedir') / 'xyz/openbmc_project/Ipmi/Internal/SoftPowerOff',
+)
+
+softoff_dbus_deps = [
+    dependency('phosphor-dbus-interfaces'),
+    dependency('sdbusplus'),
+]
+
+softoff_dbus_lib = library(
+    'softoff-dbus',
+    [if_cpp, if_hpp, if_common_hpp],
+    implicit_include_directories: false,
+    include_directories: root_inc,
+    version: meson.project_version(),
+    dependencies: softoff_dbus_deps,
+    override_options: ['b_lundef=false'],
+    install: true,
+)
+
+softoff_dbus = declare_dependency(
+    dependencies: softoff_dbus_deps,
+    sources: [if_hpp, if_common_hpp],
+    link_with: softoff_dbus_lib,
+)
+
+softoff_dbus_reqs = []
+foreach dep : softoff_dbus_deps
+    if dep.type_name() == 'pkgconfig'
+        softoff_dbus_reqs += dep
+    endif
+endforeach
+
+import('pkgconfig').generate(
+    name: 'softoff-dbus',
+    description: 'SoftPowerOff DBus Bindings',
+    version: meson.project_version(),
+    libraries: softoff_dbus,
+    requires: softoff_dbus_reqs,
+)