dbus-sdr: Initial Dynamic Sensor Stack

Migrate intel-ipmi-oem dynamic sensor stack into
phosphor-host-ipmid for more general use.

The files are copied from
https://gerrit.openbmc-project.xyz/c/openbmc/intel-ipmi-oem/+/39743

https://gerrit.openbmc-project.xyz/plugins/gitiles/openbmc/intel-ipmi-oem/+/b910987a7d832e38e9342f0946aeb555a48f9cb0

Created `libdynamiccmds` to enable dynamic sensor stack.

Minor changes in the migration include:

1, Removing the use of `commandutils.hpp` in all files since it is only used
for
```
static constexpr bool debug = false;
```
It is added to `sdrutils.hpp` instead.

2, Update lastWriteAddr to size_t to match the vector.size() type
during comparison.

3, Renamed the sensorcommand unit test to sensorcommands_unitest.cpp

4, Removed unused variables.
  - sensorcommands
```
constexpr uint8_t thresholdMask = 0xFF;
```
  - sensorcommands_unitest
```
double yError = std::abs(y - yRoundtrip);
```
5, Removed Intel Specific Changes
  - Redfish logs
  - node manager/ME
  - BIOS to SEL event
6, Removed externing a global variable for sensorTree.
  - Replaced it with a method that returns a singleton
  - auto& sensorTree = getSensorTree(); for access
7, Replaced intel_oem namespace with dynamic_sensors

8, Removed types.hpp and use `ipmid/types.hpp` directly
  - Updated the types to match ipmid/types
  - Added Association and std::vector<Association>> under Value.

9, Add cpp files for sdrutils and sensorutils.

10, link libipmid as it is required for getManagedObjects needed
    by sensorcommands.cpp

Signed-off-by: Willy Tu <wltu@google.com>
Change-Id: If944620c895ecf4c9f4c3efe72479f4de276f4fb
Signed-off-by: Vijay Khemka <vijaykhemkalinux@gmail.com>
diff --git a/dbus-sdr/sdrutils.cpp b/dbus-sdr/sdrutils.cpp
new file mode 100644
index 0000000..a6468bb
--- /dev/null
+++ b/dbus-sdr/sdrutils.cpp
@@ -0,0 +1,436 @@
+/*
+// 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"
+
+namespace details
+{
+bool getSensorSubtree(std::shared_ptr<SensorSubTree>& subtree)
+{
+    static std::shared_ptr<SensorSubTree> sensorTreePtr;
+    std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
+    static sdbusplus::bus::match::match sensorAdded(
+        *dbus,
+        "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/"
+        "sensors/'",
+        [](sdbusplus::message::message& m) { sensorTreePtr.reset(); });
+
+    static sdbusplus::bus::match::match sensorRemoved(
+        *dbus,
+        "type='signal',member='InterfacesRemoved',arg0path='/xyz/"
+        "openbmc_project/sensors/'",
+        [](sdbusplus::message::message& m) { sensorTreePtr.reset(); });
+
+    bool sensorTreeUpdated = false;
+    if (sensorTreePtr)
+    {
+        subtree = sensorTreePtr;
+        return sensorTreeUpdated;
+    }
+
+    sensorTreePtr = std::make_shared<SensorSubTree>();
+
+    auto mapperCall =
+        dbus->new_method_call("xyz.openbmc_project.ObjectMapper",
+                              "/xyz/openbmc_project/object_mapper",
+                              "xyz.openbmc_project.ObjectMapper", "GetSubTree");
+    static constexpr const int32_t depth = 2;
+    static constexpr std::array<const char*, 3> interfaces = {
+        "xyz.openbmc_project.Sensor.Value",
+        "xyz.openbmc_project.Sensor.Threshold.Warning",
+        "xyz.openbmc_project.Sensor.Threshold.Critical"};
+    mapperCall.append("/xyz/openbmc_project/sensors", depth, interfaces);
+
+    try
+    {
+        auto mapperReply = dbus->call(mapperCall);
+        mapperReply.read(*sensorTreePtr);
+    }
+    catch (sdbusplus::exception_t& e)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
+        return sensorTreeUpdated;
+    }
+    subtree = sensorTreePtr;
+    sensorTreeUpdated = true;
+    return sensorTreeUpdated;
+}
+
+bool getSensorNumMap(std::shared_ptr<SensorNumMap>& sensorNumMap)
+{
+    static std::shared_ptr<SensorNumMap> sensorNumMapPtr;
+    bool sensorNumMapUpated = false;
+
+    std::shared_ptr<SensorSubTree> sensorTree;
+    bool sensorTreeUpdated = details::getSensorSubtree(sensorTree);
+    if (!sensorTree)
+    {
+        return sensorNumMapUpated;
+    }
+
+    if (!sensorTreeUpdated && sensorNumMapPtr)
+    {
+        sensorNumMap = sensorNumMapPtr;
+        return sensorNumMapUpated;
+    }
+
+    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;
+}
+
+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>(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 (std::out_of_range& e)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
+        return invalidSensorNumber;
+    }
+}
+
+uint8_t getSensorEventTypeFromPath(const std::string& path)
+{
+    // TODO: Add support for additional reading types as needed
+    return 0x1; // reading type = threshold
+}
+
+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 (std::out_of_range& e)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
+        return std::string();
+    }
+}
+
+namespace ipmi
+{
+
+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::message 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::message response = dbus->call(getObjectMessage);
+        response.read(interfacesResponse);
+    }
+    catch (const std::exception& e)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Failed to GetObject", phosphor::logging::entry("PATH=%s", path),
+            phosphor::logging::entry("WHAT=%s", e.what()));
+    }
+
+    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::message getProperties =
+        dbus->new_method_call("xyz.openbmc_project.EntityManager", path,
+                              "org.freedesktop.DBus.Properties", "GetAll");
+    getProperties.append(interface);
+
+    try
+    {
+        sdbusplus::message::message response = dbus->call(getProperties);
+        response.read(properties);
+    }
+    catch (const std::exception& e)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Failed to GetAll", phosphor::logging::entry("PATH=%s", path),
+            phosphor::logging::entry("INTF=%s", interface),
+            phosphor::logging::entry("WHAT=%s", e.what()));
+    }
+
+    return properties;
+}
+
+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 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.
+        std::map<std::string, Value> ipmiProperties =
+            getEntityManagerProperties(
+                endpoint.c_str(),
+                "xyz.openbmc_project.Inventory.Decorator.Ipmi");
+
+        auto entityIdProp = ipmiProperties.find("EntityId");
+        auto 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::map<std::string, std::vector<std::string>>
+            sensorInterfacesResponse =
+                getObjectInterfaces(sensorConfigPath.c_str());
+
+        const std::string* configurationInterface =
+            getSensorConfigurationInterface(sensorInterfacesResponse);
+
+        // We didnt' find a configuration interface for this sensor, but we
+        // followed the Association property to get here, so we're done
+        // searching.
+        if (!configurationInterface)
+        {
+            break;
+        }
+
+        // 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..b6099ec
--- /dev/null
+++ b/dbus-sdr/sensorcommands.cpp
@@ -0,0 +1,1597 @@
+/*
+// 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/sensorcommands.hpp"
+
+#include "dbus-sdr/sdrutils.hpp"
+#include "dbus-sdr/sensorutils.hpp"
+#include "dbus-sdr/storagecommands.hpp"
+
+#include <algorithm>
+#include <array>
+#include <boost/algorithm/string.hpp>
+#include <boost/container/flat_map.hpp>
+#include <chrono>
+#include <cmath>
+#include <cstring>
+#include <iostream>
+#include <ipmid/api.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <map>
+#include <memory>
+#include <optional>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/bus.hpp>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <variant>
+
+namespace ipmi
+{
+using SDRObjectType =
+    boost::container::flat_map<uint16_t, std::vector<uint8_t>>;
+
+static constexpr int sensorMapUpdatePeriod = 10;
+
+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;
+static constexpr int GENERAL_ERROR = -1;
+
+SDRObjectType sensorDataRecords;
+
+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}}};
+
+void registerSensorFunctions() __attribute__((constructor));
+
+static sdbusplus::bus::match::match sensorAdded(
+    *getSdBus(),
+    "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/"
+    "sensors/'",
+    [](sdbusplus::message::message& m) {
+        getSensorTree().clear();
+        sdrLastAdd = std::chrono::duration_cast<std::chrono::seconds>(
+                         std::chrono::system_clock::now().time_since_epoch())
+                         .count();
+    });
+
+static sdbusplus::bus::match::match sensorRemoved(
+    *getSdBus(),
+    "type='signal',member='InterfacesRemoved',arg0path='/xyz/openbmc_project/"
+    "sensors/'",
+    [](sdbusplus::message::message& m) {
+        getSensorTree().clear();
+        sdrLastRemove = std::chrono::duration_cast<std::chrono::seconds>(
+                            std::chrono::system_clock::now().time_since_epoch())
+                            .count();
+    });
+
+// 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::match thresholdChanged(
+    *getSdBus(),
+    "type='signal',member='PropertiesChanged',interface='org.freedesktop.DBus."
+    "Properties',arg0namespace='xyz.openbmc_project.Sensor.Threshold'",
+    [](sdbusplus::message::message& 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)
+            {
+                phosphor::logging::log<phosphor::logging::level::ERR>(
+                    "thresholdChanged: Assert non bool");
+                return;
+            }
+            if (*ptr)
+            {
+                phosphor::logging::log<phosphor::logging::level::INFO>(
+                    "thresholdChanged: Assert",
+                    phosphor::logging::entry("SENSOR=%s", m.get_path()));
+                thresholdDeassertMap[m.get_path()][findAssert->first] = *ptr;
+            }
+            else
+            {
+                auto& value =
+                    thresholdDeassertMap[m.get_path()][findAssert->first];
+                if (value)
+                {
+                    phosphor::logging::log<phosphor::logging::level::INFO>(
+                        "thresholdChanged: deassert",
+                        phosphor::logging::entry("SENSOR=%s", m.get_path()));
+                    value = *ptr;
+                }
+            }
+        }
+    });
+
+static void getSensorMaxMin(const DbusInterfaceMap& sensorMap, double& max,
+                            double& min)
+{
+    max = 127;
+    min = -128;
+
+    auto sensorObject = sensorMap.find("xyz.openbmc_project.Sensor.Value");
+    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);
+            min = std::min(value, min);
+        }
+        if (upper != critical->second.end())
+        {
+            double value = std::visit(VariantToDoubleVisitor(), upper->second);
+            max = std::max(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);
+            min = std::min(value, min);
+        }
+        if (upper != warning->second.end())
+        {
+            double value = std::visit(VariantToDoubleVisitor(), upper->second);
+            max = std::max(value, max);
+        }
+    }
+}
+
+static bool getSensorMap(ipmi::Context::ptr ctx, std::string sensorConnection,
+                         std::string sensorPath, DbusInterfaceMap& sensorMap)
+{
+    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() > sensorMapUpdatePeriod)
+    {
+        updateTimeMap[sensorConnection] = now;
+
+        ObjectValueTree managedObjects;
+        boost::system::error_code ec = getManagedObjects(
+            ctx, sensorConnection.c_str(), "/", managedObjects);
+        if (ec)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "GetMangagedObjects for getSensorMap failed",
+                phosphor::logging::entry("ERROR=%s", ec.message().c_str()));
+
+            return false;
+        }
+
+        SensorCache[sensorConnection] = managedObjects;
+    }
+    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;
+}
+
+ipmi::RspType<> ipmiSenPlatformEvent(uint8_t generatorID, uint8_t evmRev,
+                                     uint8_t sensorType, uint8_t sensorNum,
+                                     uint8_t eventType, uint8_t eventData1,
+                                     std::optional<uint8_t> eventData2,
+                                     std::optional<uint8_t> eventData3)
+{
+    return ipmi::responseSuccess();
+}
+
+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;
+
+    auto status = getSensorConnection(ctx, sensnum, connection, path);
+    if (status)
+    {
+        return ipmi::response(status);
+    }
+
+    DbusInterfaceMap sensorMap;
+    if (!getSensorMap(ctx, connection, path, sensorMap))
+    {
+        return ipmi::responseResponseError();
+    }
+    auto sensorObject = sensorMap.find("xyz.openbmc_project.Sensor.Value");
+
+    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);
+    }
+
+    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, uint8_t lowerNonRecoverable,
+    uint8_t upperNonCritical, uint8_t upperCritical,
+    uint8_t upperNonRecoverable)
+{
+    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();
+    }
+
+    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("xyz.openbmc_project.Sensor.Value");
+
+        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);
+                resp.warningHigh = scaleIPMIValueFromDouble(
+                    value, mValue, rExp, bValue, bExp, bSigned);
+            }
+            if (warningLow != warningMap.end())
+            {
+                double value =
+                    std::visit(VariantToDoubleVisitor(), warningLow->second);
+                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);
+                resp.criticalHigh = scaleIPMIValueFromDouble(
+                    value, mValue, rExp, bValue, bExp, bSigned);
+            }
+            if (criticalLow != criticalMap.end())
+            {
+                double value =
+                    std::visit(VariantToDoubleVisitor(), criticalLow->second);
+                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;
+
+    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 (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;
+
+    auto status = getSensorConnection(ctx, sensorNum, connection, path);
+    if (status)
+    {
+        return ipmi::response(status);
+    }
+
+    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())
+            {
+                assertionEnabledLsb |= static_cast<uint8_t>(
+                    IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh);
+                deassertionEnabledLsb |= static_cast<uint8_t>(
+                    IPMISensorEventEnableThresholds::upperNonCriticalGoingLow);
+            }
+            if (warningLow != warningMap.end())
+            {
+                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())
+            {
+                assertionEnabledMsb |= static_cast<uint8_t>(
+                    IPMISensorEventEnableThresholds::upperCriticalGoingHigh);
+                deassertionEnabledMsb |= static_cast<uint8_t>(
+                    IPMISensorEventEnableThresholds::upperCriticalGoingLow);
+            }
+            if (criticalLow != criticalMap.end())
+            {
+                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)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "ipmiSenGetSensorEventStatus: Sensor connection Error",
+            phosphor::logging::entry("SENSOR=%d", sensorNum));
+        return ipmi::response(status);
+    }
+
+    DbusInterfaceMap sensorMap;
+    if (!getSensorMap(ctx, connection, path, sensorMap))
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "ipmiSenGetSensorEventStatus: Sensor Mapping Error",
+            phosphor::logging::entry("SENSOR=%s", path.c_str()));
+        return ipmi::responseResponseError();
+    }
+    auto warningInterface =
+        sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
+    auto criticalInterface =
+        sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
+
+    uint8_t sensorEventStatus =
+        static_cast<uint8_t>(IPMISensorEventEnableByte2::sensorScanningEnable);
+
+    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"];
+
+    std::bitset<16> assertions = 0;
+    std::bitset<16> deassertions = 0;
+
+    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);
+}
+
+static int getSensorDataRecords(ipmi::Context::ptr ctx)
+{
+    auto& sensorTree = getSensorTree();
+    size_t recordID = 0;
+    size_t fruCount = 0;
+
+    ipmi::Cc ret = ipmi::storage::getFruSdrCount(ctx, fruCount);
+    if (ret != ipmi::ccSuccess)
+    {
+        return GENERAL_ERROR;
+    }
+
+    size_t lastRecord =
+        sensorTree.size() + fruCount + ipmi::storage::type12Count - 1;
+    if (lastRecord > lastRecordIndex)
+    {
+        return GENERAL_ERROR;
+    }
+
+    std::string connection;
+    std::string path;
+    for (const auto& sensor : sensorTree)
+    {
+
+        connection = sensor.second.begin()->first;
+        path = sensor.first;
+
+        DbusInterfaceMap sensorMap;
+        if (!getSensorMap(ctx, connection, path, sensorMap))
+        {
+            return GENERAL_ERROR;
+        }
+        uint16_t sensorNum = getSensorNumberFromPath(path);
+        if (sensorNum == invalidSensorNumber)
+        {
+            return GENERAL_ERROR;
+        }
+        uint8_t sensornumber = static_cast<uint8_t>(sensorNum);
+        uint8_t lun = static_cast<uint8_t>(sensorNum >> 8);
+
+        get_sdr::SensorDataFullRecord record = {0};
+
+        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_FULL_RECORD;
+        record.header.record_length = sizeof(get_sdr::SensorDataFullRecord) -
+                                      sizeof(get_sdr::SensorDataRecordHeader);
+        record.key.owner_id = 0x20;
+        record.key.owner_lun = lun;
+        record.key.sensor_number = sensornumber;
+
+        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("xyz.openbmc_project.Sensor.Value");
+        if (sensorObject == sensorMap.end())
+        {
+            return GENERAL_ERROR;
+        }
+
+        uint8_t entityId = 0;
+        uint8_t entityInstance = 0x01;
+
+        // follow the association chain to get the parent board's entityid and
+        // entityInstance
+        updateIpmiFromAssociation(path, sensorMap, entityId, entityInstance);
+
+        record.body.entity_id = entityId;
+        record.body.entity_instance = entityInstance;
+
+        auto maxObject = sensorObject->second.find("MaxValue");
+        auto minObject = sensorObject->second.find("MinValue");
+
+        // If min and/or max are left unpopulated,
+        // then default to what a signed byte would be, namely (-128,127) range.
+        auto max = static_cast<double>(std::numeric_limits<int8_t>::max());
+        auto min = static_cast<double>(std::numeric_limits<int8_t>::lowest());
+        if (maxObject != sensorObject->second.end())
+        {
+            max = std::visit(VariantToDoubleVisitor(), maxObject->second);
+        }
+
+        if (minObject != sensorObject->second.end())
+        {
+            min = std::visit(VariantToDoubleVisitor(), minObject->second);
+        }
+
+        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))
+        {
+            return GENERAL_ERROR;
+        }
+
+        // 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
+        std::string name;
+        size_t nameStart = path.rfind("/");
+        if (nameStart != std::string::npos)
+        {
+            name = path.substr(nameStart + 1, std::string::npos - nameStart);
+        }
+
+        std::replace(name.begin(), name.end(), '_', ' ');
+        if (name.size() > FULL_RECORD_ID_STR_MAX_LENGTH)
+        {
+            // try to not truncate by replacing common 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);
+            }
+
+            name.resize(FULL_RECORD_ID_STR_MAX_LENGTH);
+        }
+        record.body.id_string_info = name.size();
+        std::strncpy(record.body.id_string, name.c_str(),
+                     sizeof(record.body.id_string));
+
+        IPMIThresholds thresholdData;
+        try
+        {
+            thresholdData = getIPMIThresholds(sensorMap);
+        }
+        catch (std::exception&)
+        {
+            return GENERAL_ERROR;
+        }
+
+        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];
+
+        // insert the record into the map
+        std::vector<uint8_t> sdr;
+        sdr.insert(sdr.end(), (uint8_t*)&record,
+                   ((uint8_t*)&record) + sizeof(record));
+        sensorDataRecords.insert_or_assign(recordID, sdr);
+        recordID++;
+    }
+
+    size_t nonSensorRecCount = fruCount + ipmi::storage::type12Count;
+    do
+    {
+        size_t fruIndex = recordID - sensorTree.size();
+
+        if (fruIndex >= fruCount)
+        {
+            // handle type 12 hardcoded records
+            size_t type12Index = fruIndex - fruCount;
+            if (type12Index >= ipmi::storage::type12Count)
+            {
+                return GENERAL_ERROR;
+            }
+            std::vector<uint8_t> record =
+                ipmi::storage::getType12SDRs(type12Index, recordID);
+            sensorDataRecords.insert_or_assign(recordID, record);
+        }
+        else
+        {
+            // handle fru records
+            get_sdr::SensorDataFruRecord data;
+            ret = ipmi::storage::getFruSdrs(ctx, fruIndex, data);
+            if (ret != IPMI_CC_OK)
+            {
+                return GENERAL_ERROR;
+            }
+            get_sdr::header::set_record_id(
+                recordID,
+                reinterpret_cast<get_sdr::SensorDataRecordHeader*>(&data));
+
+            std::vector<uint8_t> record;
+            record.insert(record.end(), (uint8_t*)&data,
+                          ((uint8_t*)&data) + sizeof(data));
+            sensorDataRecords.insert_or_assign(recordID, record);
+        }
+        recordID++;
+    } while (--nonSensorRecCount);
+    return 0;
+}
+
+/** @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
+ */
+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> count)
+{
+    auto& sensorTree = getSensorTree();
+    uint8_t sdrCount = 0;
+    // Sensors are dynamically allocated, and there is at least one LUN
+    uint8_t lunsAndDynamicPopulation = 0x80;
+    constexpr uint8_t getSdrCount = 0x01;
+    constexpr uint8_t getSensorCount = 0x00;
+
+    if (!getSensorSubtree(sensorTree) || sensorTree.empty())
+    {
+        return ipmi::responseResponseError();
+    }
+
+    if (sensorDataRecords.empty() && getSensorDataRecords(ctx))
+    {
+        return ipmi::responseResponseError();
+    }
+
+    uint16_t numSensors = sensorTree.size();
+    if (count.value_or(0) == getSdrCount)
+    {
+        // Count the number of Type 1 SDR entries assigned to the LUN
+        for (auto sdr : sensorDataRecords)
+        {
+            get_sdr::SensorDataRecordHeader* hdr =
+                reinterpret_cast<get_sdr::SensorDataRecordHeader*>(
+                    sdr.second.data());
+            if (hdr && hdr->record_type == get_sdr::SENSOR_DATA_FULL_RECORD)
+            {
+                get_sdr::SensorDataFullRecord* record =
+                    reinterpret_cast<get_sdr::SensorDataFullRecord*>(
+                        sdr.second.data());
+                if (ctx->lun == record->key.owner_lun)
+                {
+                    sdrCount++;
+                }
+            }
+        }
+    }
+    else if (count.value_or(0) == getSensorCount)
+    {
+        // Return the number of sensors attached to the LUN
+        if ((ctx->lun == 0) && (numSensors > 0))
+        {
+            sdrCount =
+                (numSensors > maxSensorsPerLUN) ? maxSensorsPerLUN : numSensors;
+        }
+        else if ((ctx->lun == 1) && (numSensors > maxSensorsPerLUN))
+        {
+            sdrCount = (numSensors > (2 * maxSensorsPerLUN))
+                           ? maxSensorsPerLUN
+                           : (numSensors - maxSensorsPerLUN) & maxSensorsPerLUN;
+        }
+        else if (ctx->lun == 3)
+        {
+            if (numSensors <= maxIPMISensors)
+            {
+                sdrCount =
+                    (numSensors - (2 * maxSensorsPerLUN)) & maxSensorsPerLUN;
+            }
+            else
+            {
+                // error
+                throw std::out_of_range(
+                    "Maximum number of IPMI sensors exceeded.");
+            }
+        }
+    }
+    else
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    // Get Sensor count. This returns the number of sensors
+    if (numSensors > 0)
+    {
+        lunsAndDynamicPopulation |= 1;
+    }
+    if (numSensors > maxSensorsPerLUN)
+    {
+        lunsAndDynamicPopulation |= 2;
+    }
+    if (numSensors >= (maxSensorsPerLUN * 2))
+    {
+        lunsAndDynamicPopulation |= 8;
+    }
+    if (numSensors > maxIPMISensors)
+    {
+        // error
+        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)
+{
+    auto& sensorTree = getSensorTree();
+    constexpr const uint16_t unspecifiedFreeSpace = 0xFFFF;
+    if (sensorTree.empty() && !getSensorSubtree(sensorTree))
+    {
+        return ipmi::responseResponseError();
+    }
+
+    size_t fruCount = 0;
+    ipmi::Cc ret = ipmi::storage::getFruSdrCount(ctx, fruCount);
+    if (ret != ipmi::ccSuccess)
+    {
+        return ipmi::response(ret);
+    }
+
+    uint16_t recordCount =
+        sensorTree.size() + fruCount + ipmi::storage::type12Count;
+
+    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)
+    {
+        return ipmi::responseInvalidReservationId();
+    }
+
+    if (sensorDataRecords.empty() && getSensorDataRecords(ctx))
+    {
+        return ipmi::responseResponseError();
+    }
+
+    auto& sensorTree = getSensorTree();
+    if (sensorTree.empty() && !getSensorSubtree(sensorTree))
+    {
+        return ipmi::responseResponseError();
+    }
+
+    size_t fruCount = 0;
+    ipmi::Cc ret = ipmi::storage::getFruSdrCount(ctx, fruCount);
+    if (ret != ipmi::ccSuccess)
+    {
+        return ipmi::response(ret);
+    }
+
+    size_t lastRecord =
+        sensorTree.size() + fruCount + ipmi::storage::type12Count - 1;
+    if (recordID == lastRecordIndex)
+    {
+        recordID = lastRecord;
+    }
+    if (recordID > lastRecord)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    get_sdr::SensorDataRecordHeader* hdr =
+        reinterpret_cast<get_sdr::SensorDataRecordHeader*>(
+            sensorDataRecords[recordID].data());
+
+    if (!hdr)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Error: record header is null");
+        std::vector<uint8_t> emptyData;
+        uint16_t nextRecordId = lastRecord > recordID ? recordID + 1 : 0XFFFF;
+        return ipmi::responseSuccess(nextRecordId, emptyData);
+    }
+
+    size_t sdrLength =
+        sizeof(get_sdr::SensorDataRecordHeader) + hdr->record_length;
+    if (sdrLength < (offset + bytesToRead))
+    {
+        bytesToRead = sdrLength - offset;
+    }
+
+    uint8_t* respStart = reinterpret_cast<uint8_t*>(hdr) + offset;
+    if (!respStart)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Error: record is null");
+        std::vector<uint8_t> emptyData;
+        uint16_t nextRecordId = lastRecord > recordID ? recordID + 1 : 0XFFFF;
+        return ipmi::responseSuccess(nextRecordId, emptyData);
+    }
+
+    std::vector<uint8_t> recordData(respStart, respStart + bytesToRead);
+    uint16_t nextRecordId = lastRecord > recordID ? recordID + 1 : 0XFFFF;
+    return ipmi::responseSuccess(nextRecordId, recordData);
+}
+/* end storage commands */
+
+void registerSensorFunctions()
+{
+    // <Platform Event>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+                          ipmi::sensor_event::cmdPlatformEvent,
+                          ipmi::Privilege::Operator, ipmiSenPlatformEvent);
+
+    // <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);
+}
+} // namespace ipmi
diff --git a/dbus-sdr/sensorutils.cpp b/dbus-sdr/sensorutils.cpp
new file mode 100644
index 0000000..d31dbed
--- /dev/null
+++ b/dbus-sdr/sensorutils.cpp
@@ -0,0 +1,325 @@
+/*
+// 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..5926615
--- /dev/null
+++ b/dbus-sdr/storagecommands.cpp
@@ -0,0 +1,1293 @@
+/*
+// 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/container/flat_map.hpp>
+#include <boost/process.hpp>
+#include <filesystem>
+#include <functional>
+#include <iostream>
+#include <ipmid/api.hpp>
+#include <ipmid/message.hpp>
+#include <ipmid/types.hpp>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <sdbusplus/timer.hpp>
+#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";
+
+void save()
+{
+    // open the file, creating it if necessary
+    int fd = open(selEraseTimestamp, O_WRONLY | O_CREAT | O_CLOEXEC, 0644);
+    if (fd < 0)
+    {
+        std::cerr << "Failed to open file\n";
+        return;
+    }
+
+    // update the file timestamp to the current time
+    if (futimens(fd, NULL) < 0)
+    {
+        std::cerr << "Failed to update timestamp: "
+                  << std::string(strerror(errno));
+    }
+    close(fd);
+}
+
+int get()
+{
+    return getFileTimestamp(selEraseTimestamp);
+}
+} // namespace erase_time
+} // namespace dynamic_sensors::ipmi::sel
+
+namespace ipmi
+{
+
+namespace storage
+{
+
+constexpr static const size_t maxMessageSize = 64;
+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 char* entityManagerServiceName =
+    "xyz.openbmc_project.EntityManager";
+constexpr static const size_t writeTimeoutSeconds = 10;
+constexpr static const char* chassisTypeRackMount = "23";
+
+// event direction is bit[7] of eventType where 1b = Deassertion event
+constexpr static const uint8_t deassertionEvent = 0x80;
+
+static std::vector<uint8_t> fruCache;
+static uint8_t cacheBus = 0xFF;
+static uint8_t cacheAddr = 0XFF;
+static uint8_t lastDevId = 0xFF;
+
+static uint8_t writeBus = 0xFF;
+static uint8_t writeAddr = 0XFF;
+
+std::unique_ptr<phosphor::Timer> writeTimer = nullptr;
+static std::vector<sdbusplus::bus::match::match> 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<uint8_t, uint8_t>> deviceHashes;
+
+void registerStorageFunctions() __attribute__((constructor));
+
+bool writeFru()
+{
+    if (writeBus == 0xFF && writeAddr == 0xFF)
+    {
+        return true;
+    }
+    std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
+    sdbusplus::message::message writeFru = dbus->new_method_call(
+        fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
+        "xyz.openbmc_project.FruDeviceManager", "WriteFru");
+    writeFru.append(writeBus, writeAddr, fruCache);
+    try
+    {
+        sdbusplus::message::message writeFruResp = dbus->call(writeFru);
+    }
+    catch (sdbusplus::exception_t&)
+    {
+        // todo: log sel?
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "error writing fru");
+        return false;
+    }
+    writeBus = 0xFF;
+    writeAddr = 0xFF;
+    return true;
+}
+
+void createTimers()
+{
+    writeTimer = std::make_unique<phosphor::Timer>(writeFru);
+}
+
+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())
+        {
+            phosphor::logging::log<phosphor::logging::level::INFO>(
+                "fru device missing Bus or Address",
+                phosphor::logging::entry("FRU=%s", fru.first.str.c_str()));
+            continue;
+        }
+
+        uint8_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)
+        {
+            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<uint8_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,
+                     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)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "GetMangagedObjects for replaceCacheFru failed",
+            phosphor::logging::entry("ERROR=%s", ec.message().c_str()));
+
+        return;
+    }
+    recalculateHashes();
+}
+
+ipmi::Cc getFru(ipmi::Context::ptr ctx, uint8_t devId)
+{
+    if (lastDevId == devId && devId != 0xFF)
+    {
+        return ipmi::ccSuccess;
+    }
+
+    // Set devId to 1 if devId is 0.
+    // 0 is reserved for baseboard and set to 1 in recalculateHashes().
+    if (!devId)
+        devId = 1;
+
+    auto deviceFind = deviceHashes.find(devId);
+    if (deviceFind == deviceHashes.end())
+    {
+        return IPMI_CC_SENSOR_INVALID;
+    }
+
+    fruCache.clear();
+
+    cacheBus = deviceFind->second.first;
+    cacheAddr = deviceFind->second.second;
+
+    boost::system::error_code ec;
+
+    fruCache = ctx->bus->yield_method_call<std::vector<uint8_t>>(
+        ctx->yield, ec, fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
+        "xyz.openbmc_project.FruDeviceManager", "GetRawFru", cacheBus,
+        cacheAddr);
+    if (ec)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Couldn't get raw fru",
+            phosphor::logging::entry("ERROR=%s", ec.message().c_str()));
+
+        cacheBus = 0xFF;
+        cacheAddr = 0xFF;
+        return ipmi::ccResponseError;
+    }
+
+    lastDevId = devId;
+    return ipmi::ccSuccess;
+}
+
+void writeFruIfRunning()
+{
+    if (!writeTimer->isRunning())
+    {
+        return;
+    }
+    writeTimer->stop();
+    writeFru();
+}
+
+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::message& message) {
+                                sdbusplus::message::object_path path;
+                                ObjectType object;
+                                try
+                                {
+                                    message.read(path, object);
+                                }
+                                catch (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::message& message) {
+                                sdbusplus::message::object_path path;
+                                std::set<std::string> interfaces;
+                                try
+                                {
+                                    message.read(path, interfaces);
+                                }
+                                catch (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);
+    });
+}
+
+/** @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();
+    }
+
+    ipmi::Cc status = getFru(ctx, fruDeviceId);
+
+    if (status != ipmi::ccSuccess)
+    {
+        return ipmi::response(status);
+    }
+
+    size_t fromFruByteLen = 0;
+    if (countToRead + fruInventoryOffset < fruCache.size())
+    {
+        fromFruByteLen = countToRead;
+    }
+    else if (fruCache.size() > fruInventoryOffset)
+    {
+        fromFruByteLen = fruCache.size() - fruInventoryOffset;
+    }
+    else
+    {
+        return ipmi::responseReqDataLenExceeded();
+    }
+
+    std::vector<uint8_t> requestedData;
+
+    requestedData.insert(
+        requestedData.begin(), fruCache.begin() + fruInventoryOffset,
+        fruCache.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();
+
+    ipmi::Cc status = getFru(ctx, fruDeviceId);
+    if (status != ipmi::ccSuccess)
+    {
+        return ipmi::response(status);
+    }
+    size_t lastWriteAddr = fruInventoryOffset + writeLen;
+    if (fruCache.size() < lastWriteAddr)
+    {
+        fruCache.resize(fruInventoryOffset + writeLen);
+    }
+
+    std::copy(dataToWrite.begin(), dataToWrite.begin() + writeLen,
+              fruCache.begin() + fruInventoryOffset);
+
+    bool atEnd = false;
+
+    if (fruCache.size() >= sizeof(FRUHeader))
+    {
+        FRUHeader* header = reinterpret_cast<FRUHeader*>(fruCache.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 = fruCache[lastRecordStart + 1] & 0x80;
+                // Third byte in the MultiRecord header is the length
+                areaLength = fruCache[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 = fruCache[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())
+        {
+            return ipmi::responseInvalidFieldRequest();
+        }
+        countWritten = std::min(fruCache.size(), static_cast<size_t>(0xFF));
+    }
+    else
+    {
+        // 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();
+    }
+
+    ipmi::Cc ret = getFru(ctx, fruDeviceId);
+    if (ret != ipmi::ccSuccess)
+    {
+        return ipmi::response(ret);
+    }
+
+    constexpr uint8_t accessType =
+        static_cast<uint8_t>(GetFRUAreaAccessType::byte);
+
+    return ipmi::responseSuccess(fruCache.size(), accessType);
+}
+
+ipmi_ret_t getFruSdrCount(ipmi::Context::ptr ctx, size_t& count)
+{
+    count = deviceHashes.size();
+    return IPMI_CC_OK;
+}
+
+ipmi_ret_t getFruSdrs(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;
+    uint8_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;
+    }
+
+#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 = ctx->bus->yield_method_call<ManagedObjectType>(
+        ctx->yield, ec, entityManagerServiceName, "/",
+        "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
+
+    if (ec)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "GetMangagedObjects for ipmiStorageGetFruInvAreaInfo failed",
+            phosphor::logging::entry("ERROR=%s", ec.message().c_str()));
+
+        return ipmi::ccResponseError;
+    }
+
+    auto entity = std::find_if(
+        entities.begin(), entities.end(),
+        [bus, address, &entityData](ManagedEntry& entry) {
+            auto findFruDevice = entry.second.find(
+                "xyz.openbmc_project.Inventory.Decorator.FruDevice");
+            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;
+            }
+
+            // 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::string name;
+    auto findProductName = fruData->find("BOARD_PRODUCT_NAME");
+    auto findBoardName = fruData->find("PRODUCT_PRODUCT_NAME");
+    if (findProductName != fruData->end())
+    {
+        name = std::get<std::string>(findProductName->second);
+    }
+    else if (findBoardName != fruData->end())
+    {
+        name = std::get<std::string>(findBoardName->second);
+    }
+    else
+    {
+        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 = 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 (std::invalid_argument& e)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
+            return -1;
+        }
+        catch (std::out_of_range& e)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
+            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"))
+        {
+            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);
+            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});
+    }
+
+    return ipmi::responseUnspecifiedError();
+}
+
+ipmi::RspType<uint16_t> ipmiStorageAddSELEntry(
+    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)
+{
+    // 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();
+
+    // Save the erase time
+    dynamic_sensors::ipmi::sel::erase_time::save();
+
+    // Clear the SEL by deleting the log files
+    std::vector<std::filesystem::path> selLogFiles;
+    if (getSELLogFiles(selLogFiles))
+    {
+        for (const std::filesystem::path& file : selLogFiles)
+        {
+            std::error_code ec;
+            std::filesystem::remove(file, ec);
+        }
+    }
+
+    // Reload rsyslog so it knows to start new log files
+    std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
+    sdbusplus::message::message rsyslogReload = dbus->new_method_call(
+        "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
+        "org.freedesktop.systemd1.Manager", "ReloadUnit");
+    rsyslogReload.append("rsyslog.service", "replace");
+    try
+    {
+        sdbusplus::message::message reloadResponse = dbus->call(rsyslogReload);
+    }
+    catch (sdbusplus::exception_t& e)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
+    }
+
+    return ipmi::responseSuccess(ipmi::sel::eraseComplete);
+}
+
+ipmi::RspType<uint32_t> ipmiStorageGetSELTime()
+{
+    struct timespec selTime = {};
+
+    if (clock_gettime(CLOCK_REALTIME, &selTime) < 0)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    return ipmi::responseSuccess(selTime.tv_sec);
+}
+
+ipmi::RspType<> ipmiStorageSetSELTime(uint32_t selTime)
+{
+    // Set SEL Time is not supported
+    return ipmi::responseInvalidCommand();
+}
+
+std::vector<uint8_t> getType12SDRs(uint16_t index, uint16_t recordId)
+{
+    std::vector<uint8_t> resp;
+    if (index == 0)
+    {
+        Type12Record bmc = {};
+        bmc.header.record_id_lsb = recordId;
+        bmc.header.record_id_msb = recordId >> 8;
+        bmc.header.sdr_version = ipmiSdrVersion;
+        bmc.header.record_type = 0x12;
+        bmc.header.record_length = 0x1b;
+        bmc.slaveAddress = 0x20;
+        bmc.channelNumber = 0;
+        bmc.powerStateNotification = 0;
+        bmc.deviceCapabilities = 0xBF;
+        bmc.reserved = 0;
+        bmc.entityID = 0x2E;
+        bmc.entityInstance = 1;
+        bmc.oem = 0;
+        bmc.typeLengthCode = 0xD0;
+        std::string bmcName = "Basbrd Mgmt Ctlr";
+        std::copy(bmcName.begin(), bmcName.end(), bmc.name);
+        uint8_t* bmcPtr = reinterpret_cast<uint8_t*>(&bmc);
+        resp.insert(resp.end(), bmcPtr, bmcPtr + sizeof(Type12Record));
+    }
+    else if (index == 1)
+    {
+        Type12Record me = {};
+        me.header.record_id_lsb = recordId;
+        me.header.record_id_msb = recordId >> 8;
+        me.header.sdr_version = ipmiSdrVersion;
+        me.header.record_type = 0x12;
+        me.header.record_length = 0x16;
+        me.slaveAddress = 0x2C;
+        me.channelNumber = 6;
+        me.powerStateNotification = 0x24;
+        me.deviceCapabilities = 0x21;
+        me.reserved = 0;
+        me.entityID = 0x2E;
+        me.entityInstance = 2;
+        me.oem = 0;
+        me.typeLengthCode = 0xCB;
+        std::string meName = "Mgmt Engine";
+        std::copy(meName.begin(), meName.end(), me.name);
+        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::prioOemBase, 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);
+
+    // <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);
+}
+} // namespace storage
+} // namespace ipmi