add handler logic to handle SysEntityName

Add handler logic to handler for SysEntityName such that it splits the
true IPMI processing from the business logic.

Tested: Only ran unit-tests (added new ones).
Change-Id: I6d672a80f85843ff98c2c7e5daf4689932ff96f9
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/handler.cpp b/handler.cpp
index 8a076f2..2018ef6 100644
--- a/handler.cpp
+++ b/handler.cpp
@@ -24,11 +24,15 @@
 #include <cstdio>
 #include <filesystem>
 #include <fstream>
+#include <map>
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/elog-errors.hpp>
 #include <phosphor-logging/log.hpp>
 #include <sdbusplus/bus.hpp>
 #include <sstream>
 #include <string>
 #include <tuple>
+#include <xyz/openbmc_project/Common/error.hpp>
 
 // The phosphor-host-ipmi daemon requires a configuration that maps
 // the if_name to the IPMI LAN channel.  However, that doesn't strictly
@@ -51,6 +55,10 @@
 namespace ipmi
 {
 namespace fs = std::filesystem;
+using Json = nlohmann::json;
+using namespace phosphor::logging;
+using InternalFailure =
+    sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
 
 std::tuple<std::uint8_t, std::string> Handler::getEthDetails() const
 {
@@ -190,6 +198,93 @@
     }
 }
 
+static Json config{};
+static bool parsed = false;
+static const std::map<uint8_t, std::string> entityIdToName{
+    {0x03, "cpu"},
+    {0x04, "storage_device"},
+    {0x06, "system_management_module"},
+    {0x08, "memory_module"},
+    {0x0B, "add_in_card"},
+    {0x17, "system_chassis"},
+    {0x20, "memory_device"}};
+static constexpr auto configFile =
+    "/usr/share/ipmi-entity-association/entity_association_map.json";
+
+Json parse_config()
+{
+    std::ifstream jsonFile(configFile);
+    if (!jsonFile.is_open())
+    {
+        log<level::ERR>("Entity association JSON file not found");
+        elog<InternalFailure>();
+    }
+
+    auto data = Json::parse(jsonFile, nullptr, false);
+    if (data.is_discarded())
+    {
+        log<level::ERR>("Entity association JSON parser failure");
+        elog<InternalFailure>();
+    }
+
+    return data;
+}
+
+std::string read(const std::string& type, uint8_t instance, const Json& config)
+{
+    static const std::vector<Json> empty{};
+    std::vector<Json> readings = config.value(type, empty);
+    std::string name = "";
+    for (const auto& j : readings)
+    {
+        uint8_t instanceNum = j.value("instance", 0);
+        // Not the instance we're interested in
+        if (instanceNum != instance)
+        {
+            continue;
+        }
+
+        // Found the instance we're interested in
+        name = j.value("name", "");
+
+        break;
+    }
+    return name;
+}
+
+std::string Handler::getEntityName(std::uint8_t id, std::uint8_t instance)
+{
+    // Check if we support this Entity ID.
+    auto it = entityIdToName.find(id);
+    if (it == entityIdToName.end())
+    {
+        log<level::ERR>("Unknown Entity ID", entry("ENTITY_ID=%d", id));
+        throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST);
+    }
+
+    std::string entityName;
+    try
+    {
+        // Parse the JSON config file.
+        if (!parsed)
+        {
+            config = parse_config();
+            parsed = true;
+        }
+
+        // Find the "entity id:entity instance" mapping to entity name.
+        entityName = read(it->second, instance, config);
+        if (entityName.empty())
+            throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST);
+    }
+    catch (InternalFailure& e)
+    {
+        throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
+    }
+
+    return entityName;
+}
+
 Handler handlerImpl;
 
 } // namespace ipmi