google-ipmi-sys: Add "Entity Id:Entity Instance" mapping to Entity name

Say the system has a JSON file like :
{
    "cpu": [
        {"instance": 1, "name": "CPU0"},
        {"instance": 2, "name": "CPU1"}
    ],
    "memory_module": [
        {"instance": 1, "name": "DIMM_A1"},
        {"instance": 2, "name": "DIMM_A2"},
        {"instance": 3, "name": "DIMM_B1"},
        {"instance": 4, "name": "DIMM_B2"},
        {"instance": 5, "name": "DIMM_C1"},
        {"instance": 6, "name": "DIMM_C2"},
        {"instance": 7, "name": "DIMM_D1"},
        {"instance": 8, "name": "DIMM_D2"},
        {"instance": 9, "name": "DIMM_E1"},
        {"instance": 10, "name": "DIMM_E2"},
        {"instance": 11, "name": "DIMM_F1"},
    ],
    "add_in_card": [
        {"instance": 1, "name": "slot1"},
        {"instance": 2, "name": "slot2"},
        {"instance": 3, "name": "slot3"},
        {"instance": 4, "name": "slot5"}
    ],
    "storage_device": [
        {"instance": 1, "name": "SATA0"},
        {"instance": 2, "name": "SATA1"},
        {"instance": 3, "name": "SATA2"},
        {"instance": 4, "name": "SATA3"}
    ]
}

The system can query the BMC for an Entity name given the Entity ID and Entity Instance.

Tested:
Yes;
1) Sending GetEntityName command; Expecting error since entity ID and
entity instance are not specified.
root@adler:~# ipmitool -I dbus raw 0x2e 0x32 0x79 0x2b 0x00 0x06
Unable to send RAW command (channel=0x0 netfn=0x2e lun=0x0 cmd=0x32
rsp=0xc7): Request data length invalid

2) Sending GetEntityName command; Expecting error since entity instance
is not specified.
root@adler:~# ipmitool -I dbus raw 0x2e 0x32 0x79 0x2b 0x00 0x06 0x01
Unable to send RAW command (channel=0x0 netfn=0x2e lun=0x0 cmd=0x32
rsp=0xc7): Request data length invalid

3) Sending GetEntityName command; Expecting error since Entity Id = 0x07
is not supported.
root@adler:~# ipmitool -I dbus raw 0x2e 0x32 0x79 0x2b 0x00 0x06 0x07 0x01
Unable to send RAW command (channel=0x0 netfn=0x2e lun=0x0 cmd=0x32
rsp=0xcc): Invalid data field in request

4) Sending GetEntityName command; Expecting error since Entity Instance
= 0x06 is not added in the JSON file.
root@adler:~# ipmitool -I dbus raw 0x2e 0x32 0x79 0x2b 0x00 0x06 0x3
0x06
Unable to send RAW command (channel=0x0 netfn=0x2e lun=0x0 cmd=0x32
rsp=0xcc): Invalid data field in request

5) Sending GetEntityName command; Expecting success.
root@adler:~# ipmitool -I dbus raw 0x2e 0x32 0x79 0x2b 0x00 0x06 0x3
0x01
 79 2b 00 06 04 43 50 55 30
root@adler:~# ipmitool -I dbus raw 0x2e 0x32 0x79 0x2b 0x00 0x06 0x0B
0x01
 79 2b 00 06 05 73 6c 6f 74 31
root@adler:~#

Change-Id: Idf6caae863acacd62460cbb7f73bf56fc927bcff
Signed-off-by: Jaghathiswari Rankappagounder Natarajan <jaghu@google.com>
diff --git a/Makefile.am b/Makefile.am
index 953a5ae..4090b70 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -7,7 +7,7 @@
 
 libsyscmdsdir = ${libdir}/ipmid-providers
 libsyscmds_LTLIBRARIES = libsyscmds.la
-libsyscmds_la_SOURCES = main.cpp cable.cpp cpld.cpp eth.cpp psu.cpp pcie_i2c.cpp
+libsyscmds_la_SOURCES = main.cpp cable.cpp cpld.cpp eth.cpp psu.cpp pcie_i2c.cpp entity_name.cpp
 
 libsyscmds_la_LDFLAGS = \
 	$(SDBUSPLUS_LIBS) \
diff --git a/README.md b/README.md
index d25e81d..ceb1d95 100644
--- a/README.md
+++ b/README.md
@@ -139,3 +139,25 @@
 |0x01|I2C bus number|The I2C bus number which is input to the above PCIe slot
 |0x02|PCIe slot name length|The PCIe slot name length
 |0x03...|PCIe slot name|The PCIe slot name without null terminator
+
+### GetEntityName - SubCommand 0x06
+
+Gsys can get the "Entity ID:Entity Instance" to Entity name mapping from BMC
+using this command. When BMC receives this command, BMC can check the related
+JSON file and then send the name for that particular entity as this command
+response.
+
+Request
+
+|Byte(s) |Value |Data
+|--------|------|----
+|0x00|0x06|Subcommand
+|0x01|Entity ID|Entity ID
+|0x02|Entity Instance|Entity Instance
+
+Response
+
+|Byte(s) |Value |Data
+|0x00|0x06|Subcommand
+|0x01|Entity name length (say N)|Entity name length
+|0x02...0x02 + N - 1|Entity name|Entity name without null terminator
diff --git a/entity_name.cpp b/entity_name.cpp
new file mode 100644
index 0000000..6d184c3
--- /dev/null
+++ b/entity_name.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * 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 "entity_name.hpp"
+
+#include "main.hpp"
+
+#include <cstdint>
+#include <cstring>
+#include <experimental/filesystem>
+#include <fstream>
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
+#include <regex>
+#include <sstream>
+#include <string>
+#include <system_error>
+#include <unordered_map>
+#include <vector>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+namespace google
+{
+namespace ipmi
+{
+namespace fs = std::experimental::filesystem;
+
+namespace
+{
+
+// TODO (jaghu) : Add a call to get getChannelMaxTransferSize.
+#ifndef MAX_IPMI_BUFFER
+#define MAX_IPMI_BUFFER 64
+#endif
+
+using Json = nlohmann::json;
+
+using namespace phosphor::logging;
+using InternalFailure =
+    sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+
+static constexpr auto configFile =
+    "/usr/share/ipmi-entity-association/entity_association_map.json";
+
+static const std::map<uint8_t, std::string> entityIdToName{
+    {0x03, "cpu"},
+    {0x04, "storage_device"},
+    {0x0B, "add_in_card"},
+    {0x20, "memory_module"}};
+
+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;
+}
+
+} // namespace
+
+struct GetEntityNameRequest
+{
+    uint8_t subcommand;
+    uint8_t entity_id;
+    uint8_t entity_instance;
+} __attribute__((packed));
+
+struct GetEntityNameReply
+{
+    uint8_t subcommand;
+    uint8_t entity_name_len;
+    uint8_t entity_name[0];
+} __attribute__((packed));
+
+ipmi_ret_t GetEntityName(const uint8_t* reqBuf, uint8_t* replyBuf,
+                         size_t* dataLen)
+{
+    struct GetEntityNameRequest request;
+
+    if ((*dataLen) < sizeof(request))
+    {
+        std::fprintf(stderr, "Invalid command length: %u\n",
+                     static_cast<uint32_t>(*dataLen));
+        return IPMI_CC_REQ_DATA_LEN_INVALID;
+    }
+
+    std::memcpy(&request, &reqBuf[0], sizeof(request));
+
+    // Check if we support this Entity ID.
+    auto it = entityIdToName.find(request.entity_id);
+    if (it == entityIdToName.end())
+    {
+        log<level::ERR>("Unknown Entity ID",
+                        entry("ENTITY_ID=%d", request.entity_id));
+        return IPMI_CC_INVALID_FIELD_REQUEST;
+    }
+
+    static Json config{};
+    static bool parsed = false;
+
+    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, request.entity_instance, config);
+        if (entityName.empty())
+            return IPMI_CC_INVALID_FIELD_REQUEST;
+    }
+    catch (InternalFailure& e)
+    {
+        return IPMI_CC_UNSPECIFIED_ERROR;
+    }
+
+    int length = sizeof(struct GetEntityNameReply) + entityName.length();
+
+    // TODO (jaghu) : Add a call to get getChannelMaxTransferSize.
+    if (length > MAX_IPMI_BUFFER)
+    {
+        std::fprintf(stderr, "Response would overflow response buffer\n");
+        return IPMI_CC_INVALID;
+    }
+
+    auto reply = reinterpret_cast<struct GetEntityNameReply*>(&replyBuf[0]);
+    reply->subcommand = SysEntityName;
+    reply->entity_name_len = entityName.length();
+    std::memcpy(reply->entity_name, entityName.c_str(), entityName.length());
+
+    (*dataLen) = length;
+    return IPMI_CC_OK;
+}
+} // namespace ipmi
+} // namespace google
diff --git a/entity_name.hpp b/entity_name.hpp
new file mode 100644
index 0000000..3b4cd89
--- /dev/null
+++ b/entity_name.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <host-ipmid/ipmid-api.h>
+
+namespace google
+{
+namespace ipmi
+{
+
+// Handle the "entity id:entity instance" to entity name mapping command.
+// Sys can query the entity name for a particular "entity id:entity instance".
+ipmi_ret_t GetEntityName(const uint8_t* reqBuf, uint8_t* replyBuf,
+                         size_t* dataLen);
+
+} // namespace ipmi
+} // namespace google
diff --git a/main.cpp b/main.cpp
index f0c61a9..673174a 100644
--- a/main.cpp
+++ b/main.cpp
@@ -18,6 +18,7 @@
 
 #include "cable.hpp"
 #include "cpld.hpp"
+#include "entity_name.hpp"
 #include "eth.hpp"
 #include "pcie_i2c.hpp"
 #include "psu.hpp"
@@ -67,6 +68,8 @@
             return PcieSlotCount(reqBuf, replyCmdBuf, dataLen);
         case SysPcieSlotI2cBusMapping:
             return PcieSlotI2cBusMapping(reqBuf, replyCmdBuf, dataLen);
+        case SysEntityName:
+            return GetEntityName(reqBuf, replyCmdBuf, dataLen);
         default:
             std::fprintf(stderr, "Invalid subcommand: 0x%x\n", reqBuf[0]);
             return IPMI_CC_INVALID;
diff --git a/main.hpp b/main.hpp
index f1cfccf..dfe8f29 100644
--- a/main.hpp
+++ b/main.hpp
@@ -19,6 +19,8 @@
     SysPcieSlotCount = 4,
     // The Sys pcie slot to i2c bus mapping command.
     SysPcieSlotI2cBusMapping = 5,
+    // The Sys "entity id:entity instance" to entity name mapping command.
+    SysEntityName = 6,
 };
 
 } // namespace ipmi